diff options
Diffstat (limited to 'sql')
353 files changed, 137104 insertions, 70217 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 6648b7a2612..fa69d5c3066 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -14,56 +14,98 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +IF(WITH_WSREP AND NOT EMBEDDED_LIBRARY) + SET(WSREP_INCLUDES ${CMAKE_SOURCE_DIR}/wsrep) + SET(WSREP_SOURCES + wsrep_check_opts.cc + wsrep_hton.cc + wsrep_mysqld.cc + wsrep_notify.cc + wsrep_sst.cc + wsrep_utils.cc + wsrep_var.cc + wsrep_binlog.cc + wsrep_applier.cc + wsrep_thd.cc + wsrep_xid.cc + ) + SET(WSREP_LIB wsrep) +ELSE() + SET(WSREP_SOURCES wsrep_dummy.cc) +ENDIF() + INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/sql -${CMAKE_SOURCE_DIR}/regex +${PCRE_INCLUDES} ${ZLIB_INCLUDE_DIR} ${SSL_INCLUDE_DIRS} ${CMAKE_BINARY_DIR}/sql +${WSREP_INCLUDES} ) SET(GEN_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.h ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.cc -${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h +${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h +${CMAKE_CURRENT_BINARY_DIR}/lex_token.h ) -SET_SOURCE_FILES_PROPERTIES(${GEN_SOURCES} PROPERTIES GENERATED 1) +SET_SOURCE_FILES_PROPERTIES(${GEN_SOURCES} + PROPERTIES GENERATED 1) -ADD_DEFINITIONS(-DMYSQL_SERVER -DHAVE_EVENT_SCHEDULER) + +IF(NOT CMAKE_CROSSCOMPILING) + ADD_EXECUTABLE(gen_lex_token gen_lex_token.cc + ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.h) +ENDIF() + +ADD_CUSTOM_COMMAND( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lex_token.h + COMMAND gen_lex_token > lex_token.h + DEPENDS gen_lex_token +) + +ADD_DEFINITIONS(-DMYSQL_SERVER -DHAVE_EVENT_SCHEDULER) IF(SSL_DEFINES) ADD_DEFINITIONS(${SSL_DEFINES}) ENDIF() SET (SQL_SOURCE - ../sql-common/client.c derror.cc des_key_file.cc + ../sql-common/client.c compat56.cc derror.cc des_key_file.cc discover.cc ../libmysql/errmsg.c field.cc field_conv.cc - filesort.cc gstream.cc sha2.cc + filesort_utils.cc + filesort.cc gstream.cc signal_handler.cc - handler.cc hash_filo.h sql_plugin_services.h + handler.cc hostname.cc init.cc item.cc item_buff.cc item_cmpfunc.cc item_create.cc item_func.cc item_geofunc.cc item_row.cc item_strfunc.cc item_subselect.cc item_sum.cc item_timefunc.cc key.cc log.cc lock.cc log_event.cc rpl_record.cc rpl_reporting.cc log_event_old.cc rpl_record_old.cc - message.h mf_iocache.cc my_decimal.cc ../sql-common/my_time.c + mf_iocache.cc my_decimal.cc ../sql-common/my_time.c mysqld.cc net_serv.cc keycaches.cc ../sql-common/client_plugin.c - opt_range.cc opt_range.h opt_sum.cc + opt_range.cc opt_sum.cc ../sql-common/pack.c parse_file.cc password.c procedure.cc protocol.cc records.cc repl_failsafe.cc rpl_filter.cc set_var.cc slave.cc sp.cc sp_cache.cc sp_head.cc sp_pcontext.cc sp_rcontext.cc spatial.cc sql_acl.cc sql_analyse.cc sql_base.cc - sql_cache.cc sql_class.cc sql_client.cc sql_crypt.cc sql_crypt.h - sql_cursor.cc sql_db.cc sql_delete.cc sql_derived.cc sql_do.cc - sql_error.cc sql_handler.cc sql_help.cc sql_insert.cc sql_lex.cc - sql_list.cc sql_load.cc sql_manager.cc sql_parse.cc + sql_cache.cc sql_class.cc sql_client.cc sql_crypt.cc + sql_cursor.cc sql_db.cc sql_delete.cc sql_derived.cc + sql_digest.cc sql_do.cc + sql_error.cc sql_handler.cc sql_get_diagnostics.cc + sql_help.cc sql_insert.cc sql_lex.cc + sql_list.cc sql_load.cc sql_manager.cc + sql_parse.cc sql_bootstrap.cc sql_partition.cc sql_plugin.cc sql_prepare.cc sql_rename.cc - debug_sync.cc debug_sync.h - sql_repl.cc sql_select.cc sql_show.cc sql_state.c sql_string.cc + debug_sync.cc + sql_repl.cc sql_select.cc sql_show.cc sql_state.c + group_by_handler.cc + sql_statistics.cc sql_string.cc sql_table.cc sql_test.cc sql_trigger.cc sql_udf.cc sql_union.cc sql_update.cc sql_view.cc strfunc.cc table.cc thr_malloc.cc sql_time.cc tztime.cc uniques.cc unireg.cc item_xmlfunc.cc @@ -76,16 +118,23 @@ SET (SQL_SOURCE sql_profile.cc event_parse_data.cc sql_alter.cc sql_signal.cc rpl_handler.cc mdl.cc sql_admin.cc transaction.cc sys_vars.cc sql_truncate.cc datadict.cc - sql_reload.cc + sql_reload.cc item_inetfunc.cc # added in MariaDB: - sql_lifo_buffer.h sql_join_cache.h sql_join_cache.cc + sql_explain.cc + sql_analyze_stmt.cc + sql_join_cache.cc create_options.cc multi_range_read.cc opt_index_cond_pushdown.cc opt_subselect.cc opt_table_elimination.cc sql_expression_cache.cc gcalc_slicescan.cc gcalc_tools.cc - threadpool_common.cc - ../sql-common/mysql_async.c + threadpool_common.cc ../sql-common/mysql_async.c + my_apc.cc mf_iocache_encr.cc + my_json_writer.cc + rpl_gtid.cc rpl_parallel.cc + sql_type.cc + ${WSREP_SOURCES} + table_cache.cc encryption.cc ${CMAKE_CURRENT_BINARY_DIR}/sql_builtin.cc ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE} @@ -95,7 +144,7 @@ IF (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Windows" OR CMAKE_SYSTEM_NAME MATCHES "SunOS" OR HAVE_KQUEUE) - ADD_DEFINITIONS(-DHAVE_POOL_OF_THREADS) + ADD_DEFINITIONS(-DHAVE_POOL_OF_THREADS) IF(WIN32) SET(SQL_SOURCE ${SQL_SOURCE} threadpool_win.cc) ELSE() @@ -110,17 +159,96 @@ ADD_LIBRARY(sql STATIC ${SQL_SOURCE}) ADD_DEPENDENCIES(sql GenServerSource) DTRACE_INSTRUMENT(sql) TARGET_LINK_LIBRARIES(sql ${MYSQLD_STATIC_PLUGIN_LIBS} - mysys dbug strings vio regex ${LIBJEMALLOC} + mysys mysys_ssl dbug strings vio pcre ${LIBJEMALLOC} ${LIBWRAP} ${LIBCRYPT} ${LIBDL} ${CMAKE_THREAD_LIBS_INIT} - ${SSL_LIBRARIES}) + ${WSREP_LIB} + ${SSL_LIBRARIES} + ${LIBSYSTEMD}) IF(WIN32) - SET(MYSQLD_SOURCE main.cc nt_servc.cc nt_servc.h message.rc) + SET(MYSQLD_SOURCE main.cc nt_servc.cc message.rc) TARGET_LINK_LIBRARIES(sql psapi) ELSE() SET(MYSQLD_SOURCE main.cc ${DTRACE_PROBES_ALL}) ENDIF() + +IF(MSVC AND NOT WITHOUT_DYNAMIC_PLUGINS) + + # mysqld.exe must to export symbols from some specific libs. + # These symbols are used by dynamic plugins, that "link" to mysqld. + # + # To do that, we + # + # 1. Generate mysqld_lib.def text file with all symbols from static + # libraries mysys, dbug, strings, sql. + # 2. Then we call + # lib.exe /DEF:mysqld_lib.def ... + # to create import library mysqld_lib.lib and export library mysqld_lib.exp + # 3. mysqld.exe links with mysqld_lib.exp (exporting symbols) + # 4. plugins link with mysqld_lib.lib (importing symbols) + # + # We do not not regenerate .def, .lib and .exp + # without necessity.E.g source modifications, that do not + # change list of exported symbols, will not result in a relink for plugins. + + SET(MYSQLD_DEF ${CMAKE_CURRENT_BINARY_DIR}/mysqld_lib.def) + SET(MYSQLD_EXP ${CMAKE_CURRENT_BINARY_DIR}/mysqld_lib.exp) + SET(MYSQLD_LIB ${CMAKE_CURRENT_BINARY_DIR}/mysqld_lib.lib) + SET(MYSQLD_CORELIBS sql mysys dbug strings) + FOREACH (CORELIB ${MYSQLD_CORELIBS}) + GET_TARGET_PROPERTY(LOC ${CORELIB} LOCATION) + FILE(TO_NATIVE_PATH ${LOC} LOC) + SET (LIB_LOCATIONS ${LIB_LOCATIONS} ${LOC}) + ENDFOREACH (CORELIB) + + SET(_PLATFORM x86) + IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(_PLATFORM x64) + ENDIF() + # Create a cmake script to generate import and export libs + # from a .def file + SET(CMAKE_CONFIGURABLE_FILE_CONTENT " + IF ((mysqld_lib.def IS_NEWER_THAN mysqld_lib.lib) OR + (mysqld_lib.def IS_NEWER_THAN mysqld_lib.exp)) + FILE(REMOVE mysqld_lib.lib mysqld_lib.exp) + SET(ENV{VS_UNICODE_OUTPUT}) + EXECUTE_PROCESS ( + COMMAND \"${CMAKE_LINKER}\" /lib /NAME:mysqld.exe \"/DEF:${MYSQLD_DEF}\" /MACHINE:${_PLATFORM} + RESULT_VARIABLE ret) + IF(NOT ret EQUAL 0) + MESSAGE(FATAL_ERROR \"process failed ret=\${ret}\") + ENDIF() + ENDIF() + ") + + CONFIGURE_FILE( + ${PROJECT_SOURCE_DIR}/cmake/configurable_file_content.in + make_mysqld_lib.cmake) + + IF(CMAKE_VERSION VERSION_GREATER "3.2.0") + SET(MYSQLD_LIB_BYPRODUCTS BYPRODUCTS ${MYSQLD_DEF} ${MYSQLD_LIB} ${MYSQLD_EXP}) + ENDIF() + + ADD_CUSTOM_COMMAND( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mysqld_lib.stamp + ${MYSQLD_LIB_BYPRODUCTS} + COMMENT "Generating mysqld_lib.def, mysqld_lib.lib, mysqld_lib.exp" + COMMAND cscript //nologo ${PROJECT_SOURCE_DIR}/win/create_def_file.js + ${_PLATFORM} /forLib ${LIB_LOCATIONS} > mysqld_lib.def.tmp + COMMAND ${CMAKE_COMMAND} -E copy_if_different mysqld_lib.def.tmp mysqld_lib.def + COMMAND ${CMAKE_COMMAND} -E remove mysqld_lib.def.tmp + COMMAND ${CMAKE_COMMAND} -P make_mysqld_lib.cmake + COMMAND ${CMAKE_COMMAND} -E touch mysqld_lib.stamp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${MYSQLD_CORELIBS} + ) + + ADD_CUSTOM_TARGET(gen_mysqld_lib DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/mysqld_lib.stamp) + ADD_LIBRARY(mysqld_import_lib UNKNOWN IMPORTED GLOBAL) + SET_TARGET_PROPERTIES(mysqld_import_lib PROPERTIES IMPORTED_LOCATION ${MYSQLD_LIB}) +ENDIF() + MYSQL_ADD_EXECUTABLE(mysqld ${MYSQLD_SOURCE} DESTINATION ${INSTALL_SBINDIR} COMPONENT Server) IF(APPLE) @@ -132,7 +260,9 @@ IF(APPLE) ENDIF() IF(NOT WITHOUT_DYNAMIC_PLUGINS) - SET_TARGET_PROPERTIES(mysqld PROPERTIES ENABLE_EXPORTS TRUE) + IF(NOT MSVC) + SET_TARGET_PROPERTIES(mysqld PROPERTIES ENABLE_EXPORTS TRUE) + ENDIF() GET_TARGET_PROPERTY(mysqld_link_flags mysqld LINK_FLAGS) IF(NOT mysqld_link_flags) SET(mysqld_link_flags) @@ -141,28 +271,11 @@ IF(NOT WITHOUT_DYNAMIC_PLUGINS) SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS "${mysqld_link_flags} -Wl,--export-all-symbols") ENDIF() IF(MSVC) - # Set module definition file. Also use non-incremental linker, - # incremental appears to crash from time to time,if used with /DEF option - SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS "${mysqld_link_flags} /DEF:mysqld.def /INCREMENTAL:NO") - - FOREACH (CORELIB sql mysys dbug strings) - GET_TARGET_PROPERTY(LOC ${CORELIB} LOCATION) - FILE(TO_NATIVE_PATH ${LOC} LOC) - SET (LIB_LOCATIONS ${LIB_LOCATIONS} ${LOC}) - ENDFOREACH (CORELIB ${MYSQLD_CORE_LIBS}) - SET(_PLATFORM x86) - IF(CMAKE_SIZEOF_VOID_P EQUAL 8) - SET(_PLATFORM x64) - ENDIF() - ADD_CUSTOM_COMMAND(TARGET mysqld PRE_LINK - COMMAND echo ${_PLATFORM} && cscript ARGS //nologo ${PROJECT_SOURCE_DIR}/win/create_def_file.js - ${_PLATFORM} ${LIB_LOCATIONS} > mysqld.def - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - ADD_DEPENDENCIES(sql GenError) - ENDIF(MSVC) + SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS "${mysqld_link_flags} \"${MYSQLD_EXP}\"") + ADD_DEPENDENCIES(mysqld gen_mysqld_lib) + ENDIF() ENDIF(NOT WITHOUT_DYNAMIC_PLUGINS) -SET_TARGET_PROPERTIES(mysqld PROPERTIES ENABLE_EXPORTS TRUE) TARGET_LINK_LIBRARIES(mysqld sql) # Provide plugins with minimal set of libraries @@ -175,7 +288,7 @@ ENDIF() # On Solaris, some extra effort is required in order to get dtrace probes # from static libraries DTRACE_INSTRUMENT_STATIC_LIBS(mysqld - "sql;mysys;${MYSQLD_STATIC_PLUGIN_LIBS}") + "sql;mysys;mysys_ssl;${MYSQLD_STATIC_PLUGIN_LIBS}") SET(WITH_MYSQLD_LDFLAGS "" CACHE STRING "Additional linker flags for mysqld") @@ -188,10 +301,6 @@ IF(WITH_MYSQLD_LDFLAGS) SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS "${MYSQLD_LINK_FLAGS} ${WITH_MYSQLD_LDFLAGS}") ENDIF() -INSTALL_DEBUG_TARGET(mysqld - DESTINATION ${INSTALL_SBINDIR} - PDB_DESTINATION ${INSTALL_SBINDIR}/debug - RENAME mysqld-debug) INCLUDE(${CMAKE_SOURCE_DIR}/cmake/bison.cmake) @@ -218,7 +327,9 @@ RUN_BISON( ) # Gen_lex_hash -ADD_EXECUTABLE(gen_lex_hash gen_lex_hash.cc) +IF(NOT CMAKE_CROSSCOMPILING) + ADD_EXECUTABLE(gen_lex_hash gen_lex_hash.cc) +ENDIF() ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h @@ -228,7 +339,7 @@ ADD_CUSTOM_COMMAND( MYSQL_ADD_EXECUTABLE(mysql_tzinfo_to_sql tztime.cc COMPONENT Server) SET_TARGET_PROPERTIES(mysql_tzinfo_to_sql PROPERTIES COMPILE_FLAGS "-DTZINFO2SQL") -TARGET_LINK_LIBRARIES(mysql_tzinfo_to_sql mysys) +TARGET_LINK_LIBRARIES(mysql_tzinfo_to_sql mysys mysys_ssl) ADD_CUSTOM_TARGET( GenServerSource @@ -239,18 +350,9 @@ ADD_CUSTOM_TARGET( SET_TARGET_PROPERTIES(GenServerSource PROPERTIES EXCLUDE_FROM_ALL TRUE) IF(WIN32 OR HAVE_DLOPEN AND NOT DISABLE_SHARED) - ADD_LIBRARY(udf_example MODULE udf_example.c) + ADD_LIBRARY(udf_example MODULE udf_example.c udf_example.def) SET_TARGET_PROPERTIES(udf_example PROPERTIES PREFIX "") - # udf_example depends on strings - IF(WIN32) - IF(MSVC) - SET_TARGET_PROPERTIES(udf_example PROPERTIES LINK_FLAGS "/DEF:${CMAKE_CURRENT_SOURCE_DIR}/udf_example.def") - ENDIF() - TARGET_LINK_LIBRARIES(udf_example strings) - ELSE() - # udf_example is using safemutex exported by mysqld - TARGET_LINK_LIBRARIES(udf_example mysqld) - ENDIF() + TARGET_LINK_LIBRARIES(udf_example strings) ENDIF() FOREACH(tool glibtoolize libtoolize aclocal autoconf autoheader automake gtar @@ -272,7 +374,7 @@ ADD_CUSTOM_TARGET(dist ADD_CUSTOM_TARGET(distclean COMMAND ${CMAKE_COMMAND} -E echo WARNING: distclean target is not functional - COMMAND ${CMAKE_COMMAND} -E echo Use 'git clean -fdx' instead + COMMAND ${CMAKE_COMMAND} -E echo Use 'git clean -Xdf' instead VERBATIM ) @@ -351,6 +453,7 @@ IF(WIN32) ${CMAKE_CURRENT_BINARY_DIR}/mysql_bootstrap_sql.c COMPONENT Server ) + SET_TARGET_PROPERTIES(mysql_install_db PROPERTIES COMPILE_FLAGS -DINSTALL_PLUGINDIR=${INSTALL_PLUGINDIR}) TARGET_LINK_LIBRARIES(mysql_install_db mysys shlwapi) ADD_LIBRARY(winservice STATIC winservice.c) @@ -363,6 +466,5 @@ ENDIF(WIN32) INSTALL(DIRECTORY . DESTINATION ${INSTALL_INCLUDEDIR}/private COMPONENT Development FILES_MATCHING PATTERN "*.h" - PATTERN examples EXCLUDE PATTERN share EXCLUDE PATTERN CMakeFiles EXCLUDE) diff --git a/sql/authors.h b/sql/authors.h index 84c29f8c127..3a8f5497248 100644 --- a/sql/authors.h +++ b/sql/authors.h @@ -31,41 +31,73 @@ struct show_table_authors_st { Don't be offended if your name is not in here, just add it! - IMPORTANT: Names should be added in alphabetical order (by last name). + Active people in the MariaDB are listed first, active people in MySQL + then, not active last. Names should be encoded using UTF-8. + + See also https://mariadb.com/kb/en/log-of-mariadb-contributions/ */ struct show_table_authors_st show_table_authors[]= { + /* Active people on MariaDB */ { "Michael (Monty) Widenius", "Tusby, Finland", "Lead developer and main author" }, - { "David Axmark", "London, England", - "MySQL founder; Small stuff long time ago, Monty ripped it out!" }, { "Sergei Golubchik", "Kerpen, Germany", - "Full-text search, precision math" }, + "Architect, Full-text search, precision math, plugin framework, merges etc" }, { "Igor Babaev", "Bellevue, USA", "Optimizer, keycache, core work"}, { "Sergey Petrunia", "St. Petersburg, Russia", "Optimizer"}, { "Oleksandr Byelkin", "Lugansk, Ukraine", "Query Cache (4.0), Subqueries (4.1), Views (5.0)" }, - { "Brian (Krow) Aker", "Seattle, WA, USA", - "Architecture, archive, federated, bunch of little stuff :)" }, - { "Marc Alff", "Denver, CO, USA", "Signal, Resignal, Performance schema" }, - { "Venu Anuganti", "", "Client/server protocol (4.1)" }, + { "Timour Katchaounov", "Sofia , Bulgaria", "Optimizer"}, { "Kristian Nielsen", "Copenhagen, Denmark", - "General build stuff," }, + "Replication, Async client prototocol, General buildbot stuff" }, { "Alexander (Bar) Barkov", "Izhevsk, Russia", - "Unicode and character sets (4.1)" }, - { "Guilhem Bichot", "Bordeaux, France", "Replication (since 4.0)" }, + "Unicode and character sets" }, + { "Alexey Botchkov (Holyfoot)", "Izhevsk, Russia", + "GIS extensions, embedded server, precision math"}, + { "Daniel Bartholomew", "Raleigh, USA", "MariaDB documentation, Buildbot, releases"}, + { "Colin Charles", "Selangor, Malesia", "MariaDB documentation, talks at a LOT of conferences"}, + { "Sergey Vojtovich", "Izhevsk, Russia", + "initial implementation of plugin architecture, maintained native storage engines (MyISAM, MEMORY, ARCHIVE, etc), rewrite of table cache"}, + { "Vladislav Vaintroub", "Mannheim, Germany", "MariaDB Java connector, new thread pool, Windows optimizations"}, + { "Elena Stepanova", "Sankt Petersburg, Russia", "QA, test cases"}, + { "Georg Richter", "Heidelberg, Germany", "New LGPL C connector, PHP connector"}, + { "Jan Lindström", "Ylämylly, Finland", "Working on InnoDB"}, + { "Lixun Peng", "Hangzhou, China", "Multi Source replication" }, + { "Olivier Bertrand", "Paris, France", "CONNECT storage engine"}, + { "Kentoku Shiba", "Tokyo, Japan", "Spider storage engine, metadata_lock_info Information schema"}, + { "Percona", "CA, USA", "XtraDB, microslow patches, extensions to slow log"}, + { "Vicentiu Ciorbaru", "Bucharest, Romania", "Roles"}, + { "Sudheera Palihakkara", "", "PCRE Regular Expressions" }, + { "Pavel Ivanov", "USA", "Some patches and bug fixes"}, { "Konstantin Osipov", "Moscow, Russia", - "Prepared statements (4.1), Cursors (5.0)" }, + "Prepared statements (4.1), Cursors (5.0), GET_LOCK (10.0)" }, + { "Ian Gilfillan", "South Africa", "MariaDB documentation"}, + { "Federico Razolli", "Italy", "MariaDB documentation Italian translation"}, + + /* People working on MySQL code base (not NDB) */ + { "Guilhem Bichot", "Bordeaux, France", "Replication (since 4.0)" }, + { "Andrei Elkin", "Espoo, Finland", "Replication" }, { "Dmitri Lenev", "Moscow, Russia", "Time zones support (4.1), Triggers (5.0)" }, + { "Marc Alff", "Denver, CO, USA", "Signal, Resignal, Performance schema" }, + { "Mikael Ronström", "Stockholm, Sweden", + "NDB Cluster, Partitioning, online alter table" }, + { "Ingo Strüwing", "Berlin, Germany", + "Bug fixing in MyISAM, Merge tables etc" }, + {"Marko Mäkelä", "Helsinki, Finland", "InnoDB core developer"}, + + /* People not active anymore */ + { "David Axmark", "London, England", + "MySQL founder; Small stuff long time ago, Monty ripped it out!" }, + { "Brian (Krow) Aker", "Seattle, WA, USA", + "Architecture, archive, blackhole, federated, bunch of little stuff :)" }, + { "Venu Anuganti", "", "Client/server protocol (4.1)" }, { "Omer BarNir", "Sunnyvale, CA, USA", "Testing (sometimes) and general QA stuff" }, { "John Birrell", "", "Emulation of pthread_mutex() for OS/2" }, { "Andreas F. Bobak", "", "AGGREGATE extension to user-defined functions" }, - { "Alexey Botchkov (Holyfoot)", "Izhevsk, Russia", - "GIS extensions (4.1), embedded server (4.1), precision math (5.0)"}, { "Reggie Burnett", "Nashville, TN, USA", "Windows development, Connectors" }, { "Kent Boortz", "Orebro, Sweden", "Test platform, and general build stuff" }, { "Tim Bunce", "", "mysqlhotcopy" }, @@ -81,7 +113,6 @@ struct show_table_authors_st show_table_authors[]= { { "Antony T. Curtis", "Norwalk, CA, USA", "Parser, port to OS/2, storage engines and some random stuff" }, { "Yuri Dario", "", "OS/2 port" }, - { "Andrei Elkin", "Espoo, Finland", "Replication" }, { "Patrick Galbraith", "Sharon, NH", "Federated Engine, mysqlslap" }, { "Lenz Grimmer", "Hamburg, Germany", "Production (build and release) engineering" }, @@ -96,7 +127,6 @@ struct show_table_authors_st show_table_authors[]= { { "Mats Kindahl", "Storvreta, Sweden", "Replication" }, { "Serge Kozlov", "Velikie Luki, Russia", "Testing - Cluster" }, { "Hakan Küçükyılmaz", "Walldorf, Germany", "Testing - Server" }, - { "Greg (Groggy) Lehey", "Uchunga, SA, Australia", "Backup" }, { "Matthias Leich", "Berlin, Germany", "Testing - Server" }, { "Arjen Lentz", "Brisbane, Australia", "Documentation (2001-2004), Dutch error messages, LOG2()" }, @@ -125,9 +155,7 @@ struct show_table_authors_st show_table_authors[]= { "Extended MERGE storage engine to handle INSERT" }, { "Igor Romanenko", "", "mysqldump" }, - { "Mikael Ronström", "Stockholm, Sweden", - "NDB Cluster, Partitioning (5.1), Optimizations" }, - { "Tõnu Samuel", "", + { "Tõnu Samuel", "Estonia", "VIO interface, other miscellaneous features" }, { "Carsten Segieth (Pino)", "Fredersdorf, Germany", "Testing - Server"}, { "Martin Sköld", "Stockholm, Sweden", @@ -138,7 +166,6 @@ struct show_table_authors_st show_table_authors[]= { "Windows development, Windows NT service"}, { "Punita Srivastava", "Austin, TX, USA", "Testing - Merlin"}, { "Alexey Stroganov (Ranger)", "Lugansk, Ukraine", "Testing - Benchmarks"}, - { "Ingo Strüwing", "Berlin, Germany", "Bug fixing" }, { "Magnus Svensson", "Öregrund, Sweden", "NDB Cluster: Integration into MySQL, test framework" }, { "Zeev Suraski", "", "FROM_UNIXTIME(), ENCRYPT()" }, @@ -157,7 +184,6 @@ struct show_table_authors_st show_table_authors[]= { { "Peter Zaitsev", "Tacoma, WA, USA", "SHA1(), AES_ENCRYPT(), AES_DECRYPT(), bug fixing" }, {"Mark Mark Callaghan", "Texas, USA", "Statistics patches"}, - {"Percona", "CA, USA", "Microslow patches"}, {NULL, NULL, NULL} }; diff --git a/sql/bounded_queue.h b/sql/bounded_queue.h new file mode 100644 index 00000000000..070ae46c347 --- /dev/null +++ b/sql/bounded_queue.h @@ -0,0 +1,195 @@ +/* Copyright (c) 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 Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#ifndef BOUNDED_QUEUE_INCLUDED +#define BOUNDED_QUEUE_INCLUDED + +#include <string.h> +#include "my_global.h" +#include "my_base.h" +#include "my_sys.h" +#include "queues.h" + +class Sort_param; + +/** + A priority queue with a fixed, limited size. + + This is a wrapper on top of QUEUE and the queue_xxx() functions. + It keeps the top-N elements which are inserted. + + Elements of type Element_type are pushed into the queue. + For each element, we call a user-supplied keymaker_function, + to generate a key of type Key_type for the element. + Instances of Key_type are compared with the user-supplied compare_function. + + The underlying QUEUE implementation needs one extra element for replacing + the lowest/highest element when pushing into a full queue. + */ +template<typename Element_type, typename Key_type> +class Bounded_queue +{ +public: + Bounded_queue() + { + memset(&m_queue, 0, sizeof(m_queue)); + } + + ~Bounded_queue() + { + delete_queue(&m_queue); + } + + /** + Function for making sort-key from input data. + @param param Sort parameters. + @param to Where to put the key. + @param from The input data. + */ + typedef void (*keymaker_function)(Sort_param *param, + Key_type *to, + Element_type *from); + + /** + Function for comparing two keys. + @param n Pointer to number of bytes to compare. + @param a First key. + @param b Second key. + @retval -1, 0, or 1 depending on whether the left argument is + less than, equal to, or greater than the right argument. + */ + typedef int (*compare_function)(size_t *n, Key_type **a, Key_type **b); + + /** + Initialize the queue. + + @param max_elements The size of the queue. + @param max_at_top Set to true if you want biggest element on top. + false: We keep the n largest elements. + pop() will return the smallest key in the result set. + true: We keep the n smallest elements. + pop() will return the largest key in the result set. + @param compare Compare function for elements, takes 3 arguments. + If NULL, we use get_ptr_compare(compare_length). + @param compare_length Length of the data (i.e. the keys) used for sorting. + @param keymaker Function which generates keys for elements. + @param sort_param Sort parameters. + @param sort_keys Array of pointers to keys to sort. + + @retval 0 OK, 1 Could not allocate memory. + + We do *not* take ownership of any of the input pointer arguments. + */ + int init(ha_rows max_elements, bool max_at_top, + compare_function compare, size_t compare_length, + keymaker_function keymaker, Sort_param *sort_param, + Key_type **sort_keys); + + /** + Pushes an element on the queue. + If the queue is already full, we discard one element. + Calls keymaker_function to generate a key for the element. + + @param element The element to be pushed. + */ + void push(Element_type *element); + + /** + Removes the top element from the queue. + + @retval Pointer to the (key of the) removed element. + + @note This function is for unit testing, where we push elements into to the + queue, and test that the appropriate keys are retained. + Interleaving of push() and pop() operations has not been tested. + */ + Key_type **pop() + { + // Don't return the extra element to the client code. + if (queue_is_full((&m_queue))) + queue_remove(&m_queue, 0); + DBUG_ASSERT(m_queue.elements > 0); + if (m_queue.elements == 0) + return NULL; + return reinterpret_cast<Key_type**>(queue_remove(&m_queue, 0)); + } + + /** + The number of elements in the queue. + */ + uint num_elements() const { return m_queue.elements; } + + /** + Is the queue initialized? + */ + bool is_initialized() const { return m_queue.max_elements > 0; } + +private: + Key_type **m_sort_keys; + size_t m_compare_length; + keymaker_function m_keymaker; + Sort_param *m_sort_param; + st_queue m_queue; +}; + + +template<typename Element_type, typename Key_type> +int Bounded_queue<Element_type, Key_type>::init(ha_rows max_elements, + bool max_at_top, + compare_function compare, + size_t compare_length, + keymaker_function keymaker, + Sort_param *sort_param, + Key_type **sort_keys) +{ + DBUG_ASSERT(sort_keys != NULL); + + m_sort_keys= sort_keys; + m_compare_length= compare_length; + m_keymaker= keymaker; + m_sort_param= sort_param; + // init_queue() takes an uint, and also does (max_elements + 1) + if (max_elements >= (UINT_MAX - 1)) + return 1; + if (compare == NULL) + compare= + reinterpret_cast<compare_function>(get_ptr_compare(compare_length)); + // We allocate space for one extra element, for replace when queue is full. + return init_queue(&m_queue, (uint) max_elements + 1, + 0, max_at_top, + reinterpret_cast<queue_compare>(compare), + &m_compare_length, 0, 0); +} + + +template<typename Element_type, typename Key_type> +void Bounded_queue<Element_type, Key_type>::push(Element_type *element) +{ + DBUG_ASSERT(is_initialized()); + if (queue_is_full((&m_queue))) + { + // Replace top element with new key, and re-order the queue. + Key_type **pq_top= reinterpret_cast<Key_type **>(queue_top(&m_queue)); + (*m_keymaker)(m_sort_param, *pq_top, element); + queue_replace_top(&m_queue); + } else { + // Insert new key into the queue. + (*m_keymaker)(m_sort_param, m_sort_keys[m_queue.elements], element); + queue_insert(&m_queue, + reinterpret_cast<uchar*>(&m_sort_keys[m_queue.elements])); + } +} + +#endif // BOUNDED_QUEUE_INCLUDED diff --git a/sql/client_settings.h b/sql/client_settings.h index 54cb72f9412..f2ad1797b8e 100644 --- a/sql/client_settings.h +++ b/sql/client_settings.h @@ -30,13 +30,14 @@ */ #define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | \ CLIENT_LONG_FLAG | \ - CLIENT_SECURE_CONNECTION | \ CLIENT_TRANSACTIONS | \ CLIENT_PROTOCOL_41 | \ CLIENT_SECURE_CONNECTION | \ - CLIENT_PLUGIN_AUTH) + CLIENT_PLUGIN_AUTH | \ + CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | \ + CLIENT_CONNECT_ATTRS) -#define read_user_name(A) {} +#define read_user_name(A) A[0]= 0 #undef _CUSTOMCONFIG_ #define mysql_server_init(a,b,c) mysql_client_plugin_init() diff --git a/sql/compat56.cc b/sql/compat56.cc new file mode 100644 index 00000000000..357b4bcf78b --- /dev/null +++ b/sql/compat56.cc @@ -0,0 +1,448 @@ +/* + Copyright (c) 2004, 2012, Oracle and/or its affiliates. + Copyright (c) 2013, MariaDB Foundation. + + 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 */ + +#include "my_global.h" +#include "compat56.h" +#include "myisampack.h" +#include "my_time.h" + +/*** MySQL56 TIME low-level memory and disk representation routines ***/ + +/* + In-memory format: + + 1 bit sign (Used for sign, when on disk) + 1 bit unused (Reserved for wider hour range, e.g. for intervals) + 10 bit hour (0-836) + 6 bit minute (0-59) + 6 bit second (0-59) + 24 bits microseconds (0-999999) + + Total: 48 bits = 6 bytes + Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff +*/ + + +/** + Convert time value to MySQL56 numeric packed representation. + + @param ltime The value to convert. + @return Numeric packed representation. +*/ +longlong TIME_to_longlong_time_packed(const MYSQL_TIME *ltime) +{ + /* If month is 0, we mix day with hours: "1 00:10:10" -> "24:00:10" */ + long hms= (((ltime->month ? 0 : ltime->day * 24) + ltime->hour) << 12) | + (ltime->minute << 6) | ltime->second; + longlong tmp= MY_PACKED_TIME_MAKE(hms, ltime->second_part); + return ltime->neg ? -tmp : tmp; +} + + + +/** + Convert MySQL56 time packed numeric representation to time. + + @param OUT ltime The MYSQL_TIME variable to set. + @param tmp The packed numeric representation. +*/ +void TIME_from_longlong_time_packed(MYSQL_TIME *ltime, longlong tmp) +{ + long hms; + if ((ltime->neg= (tmp < 0))) + tmp= -tmp; + hms= MY_PACKED_TIME_GET_INT_PART(tmp); + ltime->year= (uint) 0; + ltime->month= (uint) 0; + ltime->day= (uint) 0; + ltime->hour= (uint) (hms >> 12) % (1 << 10); /* 10 bits starting at 12th */ + ltime->minute= (uint) (hms >> 6) % (1 << 6); /* 6 bits starting at 6th */ + ltime->second= (uint) hms % (1 << 6); /* 6 bits starting at 0th */ + ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp); + ltime->time_type= MYSQL_TIMESTAMP_TIME; +} + + +/** + Calculate binary size of MySQL56 packed numeric time representation. + + @param dec Precision. +*/ +uint my_time_binary_length(uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + return 3 + (dec + 1) / 2; +} + + +/* + On disk we convert from signed representation to unsigned + representation using TIMEF_OFS, so all values become binary comparable. +*/ +#define TIMEF_OFS 0x800000000000LL +#define TIMEF_INT_OFS 0x800000LL + + +/** + Convert MySQL56 in-memory numeric time representation to on-disk representation + + @param nr Value in packed numeric time format. + @param OUT ptr The buffer to put value at. + @param dec Precision. +*/ +void my_time_packed_to_binary(longlong nr, uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + /* Make sure the stored value was previously properly rounded or truncated */ + DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) % + (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0); + + switch (dec) + { + case 0: + default: + mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr)); + break; + + case 1: + case 2: + mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr)); + ptr[3]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000); + break; + + case 4: + case 3: + mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr)); + mi_int2store(ptr + 3, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100); + break; + + case 5: + case 6: + mi_int6store(ptr, nr + TIMEF_OFS); + break; + } +} + + +/** + Convert MySQL56 on-disk time representation to in-memory packed numeric + representation. + + @param ptr The pointer to read the value at. + @param dec Precision. + @return Packed numeric time representation. +*/ +longlong my_time_packed_from_binary(const uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + + switch (dec) + { + case 0: + default: + { + longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS; + return MY_PACKED_TIME_MAKE_INT(intpart); + } + case 1: + case 2: + { + longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS; + int frac= (uint) ptr[3]; + if (intpart < 0 && frac) + { + /* + Negative values are stored with reverse fractional part order, + for binary sort compatibility. + + Disk value intpart frac Time value Memory value + 800000.00 0 0 00:00:00.00 0000000000.000000 + 7FFFFF.FF -1 255 -00:00:00.01 FFFFFFFFFF.FFD8F0 + 7FFFFF.9D -1 99 -00:00:00.99 FFFFFFFFFF.F0E4D0 + 7FFFFF.00 -1 0 -00:00:01.00 FFFFFFFFFF.000000 + 7FFFFE.FF -1 255 -00:00:01.01 FFFFFFFFFE.FFD8F0 + 7FFFFE.F6 -2 246 -00:00:01.10 FFFFFFFFFE.FE7960 + + Formula to convert fractional part from disk format + (now stored in "frac" variable) to absolute value: "0x100 - frac". + To reconstruct in-memory value, we shift + to the next integer value and then substruct fractional part. + */ + intpart++; /* Shift to the next integer value */ + frac-= 0x100; /* -(0x100 - frac) */ + } + return MY_PACKED_TIME_MAKE(intpart, frac * 10000); + } + + case 3: + case 4: + { + longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS; + int frac= mi_uint2korr(ptr + 3); + if (intpart < 0 && frac) + { + /* + Fix reverse fractional part order: "0x10000 - frac". + See comments for FSP=1 and FSP=2 above. + */ + intpart++; /* Shift to the next integer value */ + frac-= 0x10000; /* -(0x10000-frac) */ + } + return MY_PACKED_TIME_MAKE(intpart, frac * 100); + } + + case 5: + case 6: + return ((longlong) mi_uint6korr(ptr)) - TIMEF_OFS; + } +} + + +/*** MySQL56 DATETIME low-level memory and disk representation routines ***/ + +/* + 1 bit sign (used when on disk) + 17 bits year*13+month (year 0-9999, month 0-12) + 5 bits day (0-31) + 5 bits hour (0-23) + 6 bits minute (0-59) + 6 bits second (0-59) + 24 bits microseconds (0-999999) + + Total: 64 bits = 8 bytes + + SYYYYYYY.YYYYYYYY.YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff +*/ + +/** + Convert datetime to MySQL56 packed numeric datetime representation. + @param ltime The value to convert. + @return Packed numeric representation of ltime. +*/ +longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME *ltime) +{ + longlong ymd= ((ltime->year * 13 + ltime->month) << 5) | ltime->day; + longlong hms= (ltime->hour << 12) | (ltime->minute << 6) | ltime->second; + longlong tmp= MY_PACKED_TIME_MAKE(((ymd << 17) | hms), ltime->second_part); + DBUG_ASSERT(!check_datetime_range(ltime)); /* Make sure no overflow */ + return ltime->neg ? -tmp : tmp; +} + + +/** + Convert MySQL56 packed numeric datetime representation to MYSQL_TIME. + @param OUT ltime The datetime variable to convert to. + @param tmp The packed numeric datetime value. +*/ +void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong tmp) +{ + longlong ymd, hms; + longlong ymdhms, ym; + + DBUG_ASSERT(tmp != LONGLONG_MIN); + + if ((ltime->neg= (tmp < 0))) + tmp= -tmp; + + ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp); + ymdhms= MY_PACKED_TIME_GET_INT_PART(tmp); + + ymd= ymdhms >> 17; + ym= ymd >> 5; + hms= ymdhms % (1 << 17); + + ltime->day= ymd % (1 << 5); + ltime->month= ym % 13; + ltime->year= ym / 13; + + ltime->second= hms % (1 << 6); + ltime->minute= (hms >> 6) % (1 << 6); + ltime->hour= (hms >> 12); + + ltime->time_type= MYSQL_TIMESTAMP_DATETIME; +} + + +/** + Calculate binary size of MySQL56 packed datetime representation. + @param dec Precision. +*/ +uint my_datetime_binary_length(uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + return 5 + (dec + 1) / 2; +} + + +/* + On disk we store as unsigned number with DATETIMEF_INT_OFS offset, + for HA_KETYPE_BINARY compatibilty purposes. +*/ +#define DATETIMEF_INT_OFS 0x8000000000LL + + +/** + Convert MySQL56 on-disk datetime representation + to in-memory packed numeric representation. + + @param ptr The pointer to read value at. + @param dec Precision. + @return In-memory packed numeric datetime representation. +*/ +longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec) +{ + longlong intpart= mi_uint5korr(ptr) - DATETIMEF_INT_OFS; + int frac; + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + switch (dec) + { + case 0: + default: + return MY_PACKED_TIME_MAKE_INT(intpart); + case 1: + case 2: + frac= ((int) (signed char) ptr[5]) * 10000; + break; + case 3: + case 4: + frac= mi_sint2korr(ptr + 5) * 100; + break; + case 5: + case 6: + frac= mi_sint3korr(ptr + 5); + break; + } + return MY_PACKED_TIME_MAKE(intpart, frac); +} + + +/** + Store MySQL56 in-memory numeric packed datetime representation to disk. + + @param nr In-memory numeric packed datetime representation. + @param OUT ptr The pointer to store at. + @param dec Precision, 1-6. +*/ +void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + /* The value being stored must have been properly rounded or truncated */ + DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) % + (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0); + + mi_int5store(ptr, MY_PACKED_TIME_GET_INT_PART(nr) + DATETIMEF_INT_OFS); + switch (dec) + { + case 0: + default: + break; + case 1: + case 2: + ptr[5]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000); + break; + case 3: + case 4: + mi_int2store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100); + break; + case 5: + case 6: + mi_int3store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr)); + } +} + + +/*** MySQL56 TIMESTAMP low-level memory and disk representation routines ***/ + +/** + Calculate on-disk size of a timestamp value. + + @param dec Precision. +*/ +uint my_timestamp_binary_length(uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + return 4 + (dec + 1) / 2; +} + + +/** + Convert MySQL56 binary timestamp representation to in-memory representation. + + @param OUT tm The variable to convert to. + @param ptr The pointer to read the value from. + @param dec Precision. +*/ +void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + tm->tv_sec= mi_uint4korr(ptr); + switch (dec) + { + case 0: + default: + tm->tv_usec= 0; + break; + case 1: + case 2: + tm->tv_usec= ((int) ptr[4]) * 10000; + break; + case 3: + case 4: + tm->tv_usec= mi_sint2korr(ptr + 4) * 100; + break; + case 5: + case 6: + tm->tv_usec= mi_sint3korr(ptr + 4); + } +} + + +/** + Convert MySQL56 in-memory timestamp representation to on-disk representation. + + @param tm The value to convert. + @param OUT ptr The pointer to store the value to. + @param dec Precision. +*/ +void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec) +{ + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + /* Stored value must have been previously properly rounded or truncated */ + DBUG_ASSERT((tm->tv_usec % + (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0); + mi_int4store(ptr, tm->tv_sec); + switch (dec) + { + case 0: + default: + break; + case 1: + case 2: + ptr[4]= (unsigned char) (char) (tm->tv_usec / 10000); + break; + case 3: + case 4: + mi_int2store(ptr + 4, tm->tv_usec / 100); + break; + /* Impossible second precision. Fall through */ + case 5: + case 6: + mi_int3store(ptr + 4, tm->tv_usec); + } +} + +/****************************************/ diff --git a/sql/compat56.h b/sql/compat56.h new file mode 100644 index 00000000000..bb5e2670f7d --- /dev/null +++ b/sql/compat56.h @@ -0,0 +1,46 @@ +#ifndef COMPAT56_H_INCLUDED +#define COMPAT56_H_INCLUDED +/* + Copyright (c) 2004, 2012, Oracle and/or its affiliates. + Copyright (c) 2013 MariaDB Foundation. + + 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 */ + + +/** MySQL56 routines and macros **/ +#define MY_PACKED_TIME_GET_INT_PART(x) ((x) >> 24) +#define MY_PACKED_TIME_GET_FRAC_PART(x) ((x) % (1LL << 24)) +#define MY_PACKED_TIME_MAKE(i, f) ((((longlong) (i)) << 24) + (f)) +#define MY_PACKED_TIME_MAKE_INT(i) ((((longlong) (i)) << 24)) + +longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME *); +longlong TIME_to_longlong_time_packed(const MYSQL_TIME *); + +void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong nr); +void TIME_from_longlong_time_packed(MYSQL_TIME *ltime, longlong nr); + +void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec); +longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec); +uint my_datetime_binary_length(uint dec); + +void my_time_packed_to_binary(longlong nr, uchar *ptr, uint dec); +longlong my_time_packed_from_binary(const uchar *ptr, uint dec); +uint my_time_binary_length(uint dec); + +void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec); +void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec); +uint my_timestamp_binary_length(uint dec); +/** End of MySQL routines and macros **/ + +#endif /* COMPAT56_H_INCLUDED */ diff --git a/sql/contributors.h b/sql/contributors.h index b1ba2c20997..69f8fa6bd4c 100644 --- a/sql/contributors.h +++ b/sql/contributors.h @@ -30,12 +30,35 @@ struct show_table_contributors_st { Get permission before editing. - IMPORTANT: Names should be left in historical order. - Names should be encoded using UTF-8. + + See also https://mariadb.com/kb/en/log-of-mariadb-contributions/ */ struct show_table_contributors_st show_table_contributors[]= { + /* MariaDB foundation sponsors, in contribution, size , time order */ + {"Booking.com", "https://www.booking.com", "Founding member, Platinum Sponsor of the MariaDB Foundation"}, + {"Alibaba Cloud", "https://www.alibabacloud.com/", "Platinum Sponsor of the MariaDB Foundation"}, + {"Tencent Cloud", "https://cloud.tencent.com", "Platinum Sponsor of the MariaDB Foundation"}, + {"Microsoft", "https://microsoft.com/", "Platinum Sponsor of the MariaDB Foundation"}, + {"MariaDB Corporation", "https://mariadb.com", "Founding member, Platinum Sponsor of the MariaDB Foundation"}, + {"Visma", "https://visma.com", "Gold Sponsor of the MariaDB Foundation"}, + {"DBS", "https://dbs.com", "Gold Sponsor of the MariaDB Foundation"}, + {"IBM", "https://www.ibm.com", "Gold Sponsor of the MariaDB Foundation"}, + {"Tencent Games", "http://game.qq.com/", "Gold Sponsor of the MariaDB Foundation"}, + {"Nexedi", "https://www.nexedi.com", "Silver Sponsor of the MariaDB Foundation"}, + {"Acronis", "https://www.acronis.com", "Silver Sponsor of the MariaDB Foundation"}, + {"Verkkokauppa.com", "https://www.verkkokauppa.com", "Bronze Sponsor of the MariaDB Foundation"}, + {"Virtuozzo", "https://virtuozzo.com", "Bronze Sponsor of the MariaDB Foundation"}, + {"Tencent Game DBA", "http://tencentdba.com/about", "Bronze Sponsor of the MariaDB Foundation"}, + {"Tencent TDSQL", "http://tdsql.org", "Bronze Sponsor of the MariaDB Foundation"}, + {"Percona", "https://www.percona.com/", "Bronze Sponsor of the MariaDB Foundation"}, + + /* Sponsors of important features */ + {"Google", "USA", "Sponsoring encryption, parallel replication and GTID"}, + {"Facebook", "USA", "Sponsoring non-blocking API, LIMIT ROWS EXAMINED etc"}, + + /* Individual contributors, names in historical order, newer first */ {"Ronald Bradford", "Brisbane, Australia", "EFF contribution for UC2006 Auction"}, {"Sheeri Kritzer", "Boston, Mass. USA", "EFF contribution for UC2006 Auction"}, {"Mark Shuttleworth", "London, UK.", "EFF contribution for UC2006 Auction"}, diff --git a/sql/create_options.cc b/sql/create_options.cc index 5cedfa03a63..f6bf391e294 100644 --- a/sql/create_options.cc +++ b/sql/create_options.cc @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /** @file @@ -21,6 +21,7 @@ #include "create_options.h" #include <my_getopt.h> +#include "set_var.h" #define FRM_QUOTED_VALUE 0x8000 @@ -74,7 +75,7 @@ void engine_option_value::link(engine_option_value **start, } static bool report_wrong_value(THD *thd, const char *name, const char *val, - my_bool suppress_warning) + bool suppress_warning) { if (suppress_warning) return 0; @@ -86,13 +87,13 @@ static bool report_wrong_value(THD *thd, const char *name, const char *val, return 1; } - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_BAD_OPTION_VALUE, - ER(ER_BAD_OPTION_VALUE), val, name); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BAD_OPTION_VALUE, + ER_THD(thd, ER_BAD_OPTION_VALUE), val, name); return 0; } static bool report_unknown_option(THD *thd, engine_option_value *val, - my_bool suppress_warning) + bool suppress_warning) { DBUG_ENTER("report_unknown_option"); @@ -109,14 +110,17 @@ static bool report_unknown_option(THD *thd, engine_option_value *val, DBUG_RETURN(TRUE); } - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_UNKNOWN_OPTION, ER(ER_UNKNOWN_OPTION), val->name.str); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_UNKNOWN_OPTION, ER_THD(thd, ER_UNKNOWN_OPTION), + val->name.str); DBUG_RETURN(FALSE); } +#define value_ptr(STRUCT,OPT) ((char*)(STRUCT) + (OPT)->offset) + static bool set_one_value(ha_create_table_option *opt, - THD *thd, LEX_STRING *value, void *base, - my_bool suppress_warning, + THD *thd, const LEX_STRING *value, void *base, + bool suppress_warning, MEM_ROOT *root) { DBUG_ENTER("set_one_value"); @@ -126,9 +130,11 @@ static bool set_one_value(ha_create_table_option *opt, (value->str ? value->str : "<DEFAULT>"))); switch (opt->type) { + case HA_OPTION_TYPE_SYSVAR: + DBUG_ASSERT(0); // HA_OPTION_TYPE_SYSVAR's are replaced in resolve_sysvars() case HA_OPTION_TYPE_ULL: { - ulonglong *val= (ulonglong*)((char*)base + opt->offset); + ulonglong *val= (ulonglong*)value_ptr(base, opt); if (!value->str) { *val= opt->def_value; @@ -152,7 +158,7 @@ static bool set_one_value(ha_create_table_option *opt, } case HA_OPTION_TYPE_STRING: { - char **val= (char **)((char *)base + opt->offset); + char **val= (char **)value_ptr(base, opt); if (!value->str) { *val= 0; @@ -165,7 +171,7 @@ static bool set_one_value(ha_create_table_option *opt, } case HA_OPTION_TYPE_ENUM: { - uint *val= (uint *)((char *)base + opt->offset), num; + uint *val= (uint *)value_ptr(base, opt), num; *val= (uint) opt->def_value; if (!value->str) @@ -197,7 +203,7 @@ static bool set_one_value(ha_create_table_option *opt, } case HA_OPTION_TYPE_BOOL: { - bool *val= (bool *)((char *)base + opt->offset); + bool *val= (bool *)value_ptr(base, opt); *val= opt->def_value; if (!value->str) @@ -257,52 +263,110 @@ static const size_t ha_option_type_sizeof[]= @retval FALSE OK */ -my_bool parse_option_list(THD* thd, void *option_struct_arg, - engine_option_value *option_list, - ha_create_table_option *rules, - my_bool suppress_warning, - MEM_ROOT *root) +bool parse_option_list(THD* thd, handlerton *hton, void *option_struct_arg, + engine_option_value **option_list, + ha_create_table_option *rules, + bool suppress_warning, MEM_ROOT *root) { ha_create_table_option *opt; size_t option_struct_size= 0; - engine_option_value *val= option_list; + engine_option_value *val, *last; void **option_struct= (void**)option_struct_arg; DBUG_ENTER("parse_option_list"); DBUG_PRINT("enter", - ("struct: 0x%lx list: 0x%lx rules: 0x%lx suppres %u root 0x%lx", - (ulong) *option_struct, (ulong)option_list, (ulong)rules, - (uint) suppress_warning, (ulong) root)); + ("struct: %p list: %p rules: %p suppress_warning: %u root: %p", + *option_struct, *option_list, rules, + (uint) suppress_warning, root)); if (rules) { - LEX_STRING default_val= {NULL, 0}; for (opt= rules; opt->name; opt++) set_if_bigger(option_struct_size, opt->offset + ha_option_type_sizeof[opt->type]); *option_struct= alloc_root(root, option_struct_size); - - /* set all values to default */ - for (opt= rules; opt->name; opt++) - set_one_value(opt, thd, &default_val, *option_struct, - suppress_warning, root); } - for (; val; val= val->next) + for (opt= rules; rules && opt->name; opt++) { - for (opt= rules; opt && opt->name; opt++) + bool seen=false; + for (val= *option_list; val; val= val->next) { + last= val; if (my_strnncoll(system_charset_info, (uchar*)opt->name, opt->name_length, (uchar*)val->name.str, val->name.length)) continue; + /* skip duplicates (see engine_option_value constructor above) */ + if (val->parsed && !val->value.str) + continue; + if (set_one_value(opt, thd, &val->value, *option_struct, suppress_warning || val->parsed, root)) DBUG_RETURN(TRUE); val->parsed= true; + seen=true; break; } + if (!seen || (opt->var && !last->value.str)) + { + LEX_STRING default_val= null_lex_str; + + /* + Okay, here's the logic for sysvar options: + 1. When we parse CREATE TABLE and sysvar option was not explicitly + mentioned we add it to the list as if it was specified with the + *current* value of the underlying sysvar. + 2. But only if the underlying sysvar value is different from the + sysvar's default. + 3. If it's ALTER TABLE and the sysvar option was not explicitly + mentioned - do nothing, do not add it to the list. + 4. But if it was ALTER TABLE with sysvar option = DEFAULT, we + add it to the list (under the same condition #2). + 5. If we're here parsing the option list from the .frm file + for a normal open_table() and the sysvar option was not there - + do not add it to the list (makes no sense anyway) and + use the *default* value of the underlying sysvar. Because + sysvar value can change, but it should not affect existing tables. + + This is how it's implemented: the current sysvar value is added + to the list if suppress_warning is FALSE (meaning a table is created, + that is CREATE TABLE or ALTER TABLE) and it's actually a CREATE TABLE + command or it's an ALTER TABLE and the option was seen (=DEFAULT). + + Note that if the option was set explicitly (not =DEFAULT) it wouldn't + have passes the if() condition above. + */ + if (!suppress_warning && opt->var && + (thd->lex->sql_command == SQLCOM_CREATE_TABLE || seen)) + { + // take a value from the variable and add it to the list + sys_var *sysvar= find_hton_sysvar(hton, opt->var); + DBUG_ASSERT(sysvar); + + if (!sysvar->session_is_default(thd)) + { + char buf[256]; + String sbuf(buf, sizeof(buf), system_charset_info), *str; + if ((str= sysvar->val_str(&sbuf, thd, OPT_SESSION, &null_lex_str))) + { + LEX_STRING name= { const_cast<char*>(opt->name), opt->name_length }; + default_val.str= strmake_root(root, str->ptr(), str->length()); + default_val.length= str->length(); + val= new (root) engine_option_value(name, default_val, + opt->type != HA_OPTION_TYPE_ULL, option_list, &last); + val->parsed= true; + } + } + } + set_one_value(opt, thd, &default_val, *option_struct, + suppress_warning, root); + } + } + + for (val= *option_list; val; val= val->next) + { if (report_unknown_option(thd, val, suppress_warning)) DBUG_RETURN(TRUE); val->parsed= true; @@ -313,6 +377,103 @@ my_bool parse_option_list(THD* thd, void *option_struct_arg, /** + Resolves all HA_OPTION_TYPE_SYSVAR elements. + + This is done when an engine is loaded. +*/ +static bool resolve_sysvars(handlerton *hton, ha_create_table_option *rules) +{ + for (ha_create_table_option *opt= rules; rules && opt->name; opt++) + { + if (opt->type == HA_OPTION_TYPE_SYSVAR) + { + struct my_option optp; + plugin_opt_set_limits(&optp, opt->var); + switch(optp.var_type) { + case GET_ULL: + case GET_ULONG: + case GET_UINT: + opt->type= HA_OPTION_TYPE_ULL; + opt->def_value= (ulonglong)optp.def_value; + opt->min_value= (ulonglong)optp.min_value; + opt->max_value= (ulonglong)optp.max_value; + opt->block_size= (ulonglong)optp.block_size; + break; + case GET_STR: + case GET_STR_ALLOC: + opt->type= HA_OPTION_TYPE_STRING; + break; + case GET_BOOL: + opt->type= HA_OPTION_TYPE_BOOL; + opt->def_value= optp.def_value; + break; + case GET_ENUM: + { + opt->type= HA_OPTION_TYPE_ENUM; + opt->def_value= optp.def_value; + + char buf[256]; + String str(buf, sizeof(buf), system_charset_info); + str.length(0); + for (const char **s= optp.typelib->type_names; *s; s++) + { + if (str.append(*s) || str.append(',')) + return 1; + } + DBUG_ASSERT(str.length()); + opt->values= my_strndup(str.ptr(), str.length()-1, MYF(MY_WME)); + if (!opt->values) + return 1; + break; + } + default: + DBUG_ASSERT(0); + } + } + } + return 0; +} + +bool resolve_sysvar_table_options(handlerton *hton) +{ + return resolve_sysvars(hton, hton->table_options) || + resolve_sysvars(hton, hton->field_options) || + resolve_sysvars(hton, hton->index_options); +} + +/* + Restore HA_OPTION_TYPE_SYSVAR options back as they were + before resolve_sysvars(). + + This is done when the engine is unloaded, so that we could + call resolve_sysvars() if the engine is installed again. +*/ +static void free_sysvars(handlerton *hton, ha_create_table_option *rules) +{ + for (ha_create_table_option *opt= rules; rules && opt->name; opt++) + { + if (opt->var) + { + my_free(const_cast<char*>(opt->values)); + opt->type= HA_OPTION_TYPE_SYSVAR; + opt->def_value= 0; + opt->min_value= 0; + opt->max_value= 0; + opt->block_size= 0; + opt->values= 0; + } + } +} + +void free_sysvar_table_options(handlerton *hton) +{ + free_sysvars(hton, hton->table_options); + free_sysvars(hton, hton->field_options); + free_sysvars(hton, hton->index_options); +} + + +/** Parses all table/fields/keys options @param thd thread handler @@ -323,27 +484,27 @@ my_bool parse_option_list(THD* thd, void *option_struct_arg, @retval FALSE OK */ -my_bool parse_engine_table_options(THD *thd, handlerton *ht, - TABLE_SHARE *share) +bool parse_engine_table_options(THD *thd, handlerton *ht, TABLE_SHARE *share) { MEM_ROOT *root= &share->mem_root; DBUG_ENTER("parse_engine_table_options"); - if (parse_option_list(thd, &share->option_struct, share->option_list, + if (parse_option_list(thd, ht, &share->option_struct, & share->option_list, ht->table_options, TRUE, root)) DBUG_RETURN(TRUE); for (Field **field= share->field; *field; field++) { - if (parse_option_list(thd, &(*field)->option_struct, (*field)->option_list, + if (parse_option_list(thd, ht, &(*field)->option_struct, + & (*field)->option_list, ht->field_options, TRUE, root)) DBUG_RETURN(TRUE); } for (uint index= 0; index < share->keys; index ++) { - if (parse_option_list(thd, &share->key_info[index].option_struct, - share->key_info[index].option_list, + if (parse_option_list(thd, ht, &share->key_info[index].option_struct, + & share->key_info[index].option_list, ht->index_options, TRUE, root)) DBUG_RETURN(TRUE); } @@ -352,6 +513,26 @@ my_bool parse_engine_table_options(THD *thd, handlerton *ht, } +bool engine_options_differ(void *old_struct, void *new_struct, + ha_create_table_option *rules) +{ + ha_create_table_option *opt; + for (opt= rules; rules && opt->name; opt++) + { + char **old_val= (char**)value_ptr(old_struct, opt); + char **new_val= (char**)value_ptr(new_struct, opt); + int neq; + if (opt->type == HA_OPTION_TYPE_STRING) + neq= (*old_val && *new_val) ? strcmp(*old_val, *new_val) : *old_val != *new_val; + else + neq= memcmp(old_val, new_val, ha_option_type_sizeof[opt->type]); + if (neq) + return true; + } + return false; +} + + /** Returns representation length of key and value in the frm file */ @@ -504,20 +685,25 @@ uchar *engine_table_options_frm_image(uchar *buff, @returns pointer to byte after last recorded in the buffer */ -uchar *engine_option_value::frm_read(const uchar *buff, engine_option_value **start, +uchar *engine_option_value::frm_read(const uchar *buff, const uchar *buff_end, + engine_option_value **start, engine_option_value **end, MEM_ROOT *root) { LEX_STRING name, value; uint len; +#define need_buff(N) if (buff + (N) >= buff_end) return NULL + need_buff(3); name.length= buff[0]; buff++; + need_buff(name.length + 2); if (!(name.str= strmake_root(root, (const char*)buff, name.length))) return NULL; buff+= name.length; len= uint2korr(buff); value.length= len & ~FRM_QUOTED_VALUE; buff+= 2; + need_buff(value.length); if (!(value.str= strmake_root(root, (const char*)buff, value.length))) return NULL; buff+= value.length; @@ -543,8 +729,8 @@ uchar *engine_option_value::frm_read(const uchar *buff, engine_option_value **st @retval FALSE OK */ -my_bool engine_table_options_frm_read(const uchar *buff, uint length, - TABLE_SHARE *share) +bool engine_table_options_frm_read(const uchar *buff, uint length, + TABLE_SHARE *share) { const uchar *buff_end= buff + length; engine_option_value *UNINIT_VAR(end); @@ -554,8 +740,8 @@ my_bool engine_table_options_frm_read(const uchar *buff, uint length, while (buff < buff_end && *buff) { - if (!(buff= engine_option_value::frm_read(buff, &share->option_list, &end, - root))) + if (!(buff= engine_option_value::frm_read(buff, buff_end, + &share->option_list, &end, root))) DBUG_RETURN(TRUE); } buff++; @@ -564,7 +750,7 @@ my_bool engine_table_options_frm_read(const uchar *buff, uint length, { while (buff < buff_end && *buff) { - if (!(buff= engine_option_value::frm_read(buff, + if (!(buff= engine_option_value::frm_read(buff, buff_end, &share->field[count]->option_list, &end, root))) DBUG_RETURN(TRUE); @@ -576,7 +762,7 @@ my_bool engine_table_options_frm_read(const uchar *buff, uint length, { while (buff < buff_end && *buff) { - if (!(buff= engine_option_value::frm_read(buff, + if (!(buff= engine_option_value::frm_read(buff, buff_end, &share->key_info[count].option_list, &end, root))) DBUG_RETURN(TRUE); @@ -600,16 +786,32 @@ engine_option_value *merge_engine_table_options(engine_option_value *first, engine_option_value *second, MEM_ROOT *root) { - engine_option_value *end, *opt; + engine_option_value *UNINIT_VAR(end), *opt; DBUG_ENTER("merge_engine_table_options"); - LINT_INIT(end); - /* find last element */ - if (first && second) - for (end= first; end->next; end= end->next) /* no-op */; + /* Create copy of first list */ + for (opt= first, first= 0; opt; opt= opt->next) + new (root) engine_option_value(opt, &first, &end); for (opt= second; opt; opt= opt->next) new (root) engine_option_value(opt->name, opt->value, opt->quoted_value, &first, &end); DBUG_RETURN(first); } + +bool is_engine_option_known(engine_option_value *opt, + ha_create_table_option *rules) +{ + if (!rules) + return false; + + for (; rules->name; rules++) + { + if (!my_strnncoll(system_charset_info, + (uchar*)rules->name, rules->name_length, + (uchar*)opt->name.str, opt->name.length)) + return true; + } + return false; +} + diff --git a/sql/create_options.h b/sql/create_options.h index ae918f6cea1..191ec88750a 100644 --- a/sql/create_options.h +++ b/sql/create_options.h @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /** @file @@ -23,7 +23,8 @@ #define SQL_CREATE_OPTIONS_INCLUDED #include "sql_class.h" -//#include "handler.h" + +enum { ENGINE_OPTION_MAX_LENGTH=32767 }; class engine_option_value: public Sql_alloc { @@ -34,6 +35,13 @@ class engine_option_value: public Sql_alloc bool parsed; ///< to detect unrecognized options bool quoted_value; ///< option=VAL vs. option='VAL' + engine_option_value(engine_option_value *src, + engine_option_value **start, engine_option_value **end) : + name(src->name), value(src->value), + next(NULL), parsed(src->parsed), quoted_value(src->quoted_value) + { + link(start, end); + } engine_option_value(LEX_STRING &name_arg, LEX_STRING &value_arg, bool quoted, engine_option_value **start, engine_option_value **end) : name(name_arg), value(value_arg), @@ -59,7 +67,8 @@ class engine_option_value: public Sql_alloc link(start, end); } } - static uchar *frm_read(const uchar *buff, engine_option_value **start, + static uchar *frm_read(const uchar *buff, const uchar *buff_end, + engine_option_value **start, engine_option_value **end, MEM_ROOT *root); void link(engine_option_value **start, engine_option_value **end); uint frm_length(); @@ -69,16 +78,15 @@ class engine_option_value: public Sql_alloc typedef struct st_key KEY; class Create_field; -my_bool parse_engine_table_options(THD *thd, handlerton *ht, +bool resolve_sysvar_table_options(handlerton *hton); +void free_sysvar_table_options(handlerton *hton); +bool parse_engine_table_options(THD *thd, handlerton *ht, TABLE_SHARE *share); +bool parse_option_list(THD* thd, handlerton *hton, void *option_struct, + engine_option_value **option_list, + ha_create_table_option *rules, + bool suppress_warning, MEM_ROOT *root); +bool engine_table_options_frm_read(const uchar *buff, uint length, TABLE_SHARE *share); -my_bool parse_option_list(THD* thd, void *option_struct, - engine_option_value *option_list, - ha_create_table_option *rules, - my_bool suppress_warning, - MEM_ROOT *root); -my_bool engine_table_options_frm_read(const uchar *buff, - uint length, - TABLE_SHARE *share); engine_option_value *merge_engine_table_options(engine_option_value *source, engine_option_value *changes, MEM_ROOT *root); @@ -90,4 +98,9 @@ uchar *engine_table_options_frm_image(uchar *buff, engine_option_value *table_option_list, List<Create_field> &create_fields, uint keys, KEY *key_info); + +bool engine_options_differ(void *old_struct, void *new_struct, + ha_create_table_option *rules); +bool is_engine_option_known(engine_option_value *opt, + ha_create_table_option *rules); #endif diff --git a/sql/datadict.cc b/sql/datadict.cc index 4e4fcafa31b..f01d61f531b 100644 --- a/sql/datadict.cc +++ b/sql/datadict.cc @@ -13,34 +13,55 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "datadict.h" #include "sql_priv.h" #include "sql_class.h" #include "sql_table.h" +static int read_string(File file, uchar**to, size_t length) +{ + DBUG_ENTER("read_string"); + + my_free(*to); + if (!(*to= (uchar*) my_malloc(length+1,MYF(MY_WME))) || + mysql_file_read(file, *to, length, MYF(MY_NABP))) + { + my_free(*to); + *to= 0; + DBUG_RETURN(1); + } + *((char*) *to+length)= '\0'; // C-style safety + DBUG_RETURN (0); +} + /** Check type of .frm if we are not going to parse it. - @param[in] thd The current session. - @param[in] path path to FRM file. - @param[out] dbt db_type of the table if FRMTYPE_TABLE, otherwise undefined. + @param[in] thd The current session. + @param[in] path path to FRM file. + @param[in/out] engine_name table engine name (length < NAME_CHAR_LEN) + + engine_name is a LEX_STRING, where engine_name->str must point to + a buffer of at least NAME_CHAR_LEN+1 bytes. + If engine_name is 0, then the function will only test if the file is a + view or not @retval FRMTYPE_ERROR error @retval FRMTYPE_TABLE table @retval FRMTYPE_VIEW view */ -frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) +frm_type_enum dd_frm_type(THD *thd, char *path, LEX_STRING *engine_name) { File file; uchar header[10]; //"TYPE=VIEW\n" it is 10 characters size_t error; frm_type_enum type= FRMTYPE_ERROR; + uchar dbt; DBUG_ENTER("dd_frm_type"); - *dbt= DB_TYPE_UNKNOWN; - if ((file= mysql_file_open(key_file_frm, path, O_RDONLY | O_SHARE, MYF(0))) < 0) DBUG_RETURN(FRMTYPE_ERROR); error= mysql_file_read(file, (uchar*) header, sizeof(header), MYF(MY_NABP)); @@ -53,21 +74,37 @@ frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) goto err; } - type= FRMTYPE_TABLE; - /* - This is just a check for DB_TYPE. We'll return default unknown type - if the following test is true (arg #3). This should not have effect - on return value from this function (default FRMTYPE_TABLE) + We return FRMTYPE_TABLE if we can read the .frm file. This allows us + to drop a bad .frm file with DROP TABLE */ - if (header[0] != (uchar) 254 || header[1] != 1 || - (header[2] != FRM_VER && header[2] != FRM_VER+1 && - (header[2] < FRM_VER+3 || header[2] > FRM_VER+4))) + type= FRMTYPE_TABLE; + + /* engine_name is 0 if we only want to know if table is view or not */ + if (!engine_name) + goto err; + + /* Initialize engine name in case we are not able to find it out */ + engine_name->length= 0; + engine_name->str[0]= 0; + + if (!is_binary_frm_header(header)) goto err; - *dbt= (enum legacy_db_type) (uint) *(header + 3); + dbt= header[3]; - if (*dbt >= DB_TYPE_FIRST_DYNAMIC) /* read the true engine name */ + /* cannot use ha_resolve_by_legacy_type without a THD */ + if (thd && dbt < DB_TYPE_FIRST_DYNAMIC) + { + handlerton *ht= ha_resolve_by_legacy_type(thd, (enum legacy_db_type)dbt); + if (ht) + { + *engine_name= hton2plugin[ht->slot]->name; + goto err; + } + } + + /* read the true engine name */ { MY_STAT state; uchar *frm_image= 0; @@ -84,9 +121,9 @@ frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) if ((n_length= uint4korr(frm_image+55))) { - uint record_offset= (uint2korr(frm_image+6)+ + uint record_offset= uint2korr(frm_image+6)+ ((uint2korr(frm_image+14) == 0xffff ? - uint4korr(frm_image+47) : uint2korr(frm_image+14)))); + uint4korr(frm_image+47) : uint2korr(frm_image+14))); uint reclength= uint2korr(frm_image+16); uchar *next_chunk= frm_image + record_offset + reclength; @@ -95,15 +132,10 @@ frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) next_chunk+= connect_string_length + 2; if (next_chunk + 2 < buff_end) { - uint str_db_type_length= uint2korr(next_chunk); - LEX_STRING name; - name.str= (char*) next_chunk + 2; - name.length= str_db_type_length; - plugin_ref tmp_plugin= ha_resolve_by_name(thd, &name); - if (tmp_plugin) - *dbt= plugin_data(tmp_plugin, handlerton *)->db_type; - else - *dbt= DB_TYPE_UNKNOWN; + uint len= uint2korr(next_chunk); + if (len <= NAME_CHAR_LEN) + strmake(engine_name->str, (char*)next_chunk + 2, + engine_name->length= len); } } @@ -117,117 +149,44 @@ err: } -/** - Given a table name, check type of .frm and legacy table type. - - @param[in] thd The current session. - @param[in] db Table schema. - @param[in] table_name Table database. - @param[out] table_type handlerton of the table if FRMTYPE_TABLE, - otherwise undefined. - - @return FALSE if FRMTYPE_TABLE and storage engine found. TRUE otherwise. -*/ - -bool dd_frm_storage_engine(THD *thd, const char *db, const char *table_name, - handlerton **table_type) -{ - char path[FN_REFLEN + 1]; - enum legacy_db_type db_type; - LEX_STRING db_name = {(char *) db, strlen(db)}; - - /* There should be at least some lock on the table. */ - DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, - table_name, MDL_SHARED)); - - if (check_db_name(&db_name)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str); - return TRUE; - } - - if (check_table_name(table_name, strlen(table_name), FALSE)) - { - my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name); - return TRUE; - } - - (void) build_table_filename(path, sizeof(path) - 1, db, - table_name, reg_ext, 0); - - dd_frm_type(thd, path, &db_type); - - /* Type is unknown if the object is not found or is not a table. */ - if (db_type == DB_TYPE_UNKNOWN || - !(*table_type= ha_resolve_by_legacy_type(thd, db_type))) - { - my_error(ER_NO_SUCH_TABLE, MYF(0), db, table_name); - return TRUE; - } - - return FALSE; -} - - -/** - Given a table name, check if the storage engine for the - table referred by this name supports an option 'flag'. - Return an error if the table does not exist or is not a - base table. - - @pre Any metadata lock on the table. - - @param[in] thd The current session. - @param[in] db Table schema. - @param[in] table_name Table database. - @param[in] flag The option to check. - @param[out] yes_no The result. Undefined if error. -*/ - -bool dd_check_storage_engine_flag(THD *thd, - const char *db, const char *table_name, - uint32 flag, bool *yes_no) -{ - handlerton *table_type; - - if (dd_frm_storage_engine(thd, db, table_name, &table_type)) - return TRUE; - - *yes_no= ha_check_storage_engine_flag(table_type, flag); - - return FALSE; -} - - /* Regenerate a metadata locked table. @param thd Thread context. @param db Name of the database to which the table belongs to. @param name Table name. + @param path For temporary tables only - path to table files. + Otherwise NULL (the path is calculated from db and table names). @retval FALSE Success. @retval TRUE Error. */ -bool dd_recreate_table(THD *thd, const char *db, const char *table_name) +bool dd_recreate_table(THD *thd, const char *db, const char *table_name, + const char *path) { bool error= TRUE; HA_CREATE_INFO create_info; - char path[FN_REFLEN + 1]; + char path_buf[FN_REFLEN + 1]; DBUG_ENTER("dd_recreate_table"); - /* There should be a exclusive metadata lock on the table. */ - DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, - MDL_EXCLUSIVE)); - memset(&create_info, 0, sizeof(create_info)); - /* Create a path to the table, but without a extension. */ - build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0); + if (path) + create_info.options|= HA_LEX_CREATE_TMP_TABLE; + else + { + build_table_filename(path_buf, sizeof(path_buf) - 1, + db, table_name, "", 0); + path= path_buf; + + /* There should be a exclusive metadata lock on the table. */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, + MDL_EXCLUSIVE)); + } /* Attempt to reconstruct the table. */ - error= ha_create_table(thd, path, db, table_name, &create_info, TRUE); + error= ha_create_table(thd, path, db, table_name, &create_info, NULL); DBUG_RETURN(error); } diff --git a/sql/datadict.h b/sql/datadict.h index f852b02f52c..9b180a882f9 100644 --- a/sql/datadict.h +++ b/sql/datadict.h @@ -28,14 +28,21 @@ enum frm_type_enum FRMTYPE_VIEW }; -frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt); - -bool dd_frm_storage_engine(THD *thd, const char *db, const char *table_name, - handlerton **table_type); -bool dd_check_storage_engine_flag(THD *thd, - const char *db, const char *table_name, - uint32 flag, - bool *yes_no); -bool dd_recreate_table(THD *thd, const char *db, const char *table_name); +/* + Take extra care when using dd_frm_type() - it only checks the .frm file, + and it won't work for any engine that supports discovery. + + Prefer to use ha_table_exists() instead. + To check whether it's an frm of a view, use dd_frm_is_view(). +*/ +frm_type_enum dd_frm_type(THD *thd, char *path, LEX_STRING *engine_name); + +static inline bool dd_frm_is_view(THD *thd, char *path) +{ + return dd_frm_type(thd, path, NULL) == FRMTYPE_VIEW; +} + +bool dd_recreate_table(THD *thd, const char *db, const char *table_name, + const char *path = NULL); #endif // DATADICT_INCLUDED diff --git a/sql/debug_sync.cc b/sql/debug_sync.cc index 82203505f50..3dfbcdbcf81 100644 --- a/sql/debug_sync.cc +++ b/sql/debug_sync.cc @@ -10,11 +10,12 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ /* see include/mysql/service_debug_sync.h for debug sync documentation */ +#include <my_global.h> #include "debug_sync.h" #if defined(ENABLED_DEBUG_SYNC) @@ -38,7 +39,7 @@ */ struct st_debug_sync_action { - ulong activation_count; /* max(hit_limit, execute) */ + ulong activation_count; /* MY_MAX(hit_limit, execute) */ ulong hit_limit; /* hits before kill query */ ulong execute; /* executes before self-clear */ ulong timeout; /* wait_for timeout */ @@ -82,8 +83,6 @@ struct st_debug_sync_globals }; static st_debug_sync_globals debug_sync_global; /* All globals in one object */ -extern uint opt_debug_sync_timeout; - /** Callbacks from C files. */ @@ -112,14 +111,11 @@ static void init_debug_sync_psi_keys(void) const char* category= "sql"; int count; - if (PSI_server == NULL) - return; - count= array_elements(all_debug_sync_mutexes); - PSI_server->register_mutex(category, all_debug_sync_mutexes, count); + mysql_mutex_register(category, all_debug_sync_mutexes, count); count= array_elements(all_debug_sync_conds); - PSI_server->register_cond(category, all_debug_sync_conds, count); + mysql_cond_register(category, all_debug_sync_conds, count); } #endif /* HAVE_PSI_INTERFACE */ @@ -239,7 +235,8 @@ void debug_sync_init_thread(THD *thd) if (opt_debug_sync_timeout) { thd->debug_sync_control= (st_debug_sync_control*) - my_malloc(sizeof(st_debug_sync_control), MYF(MY_WME | MY_ZEROFILL)); + my_malloc(sizeof(st_debug_sync_control), + MYF(MY_WME | MY_ZEROFILL | MY_THREAD_SPECIFIC)); if (!thd->debug_sync_control) { /* @@ -587,7 +584,7 @@ static void debug_sync_remove_action(st_debug_sync_control *ds_control, memmove(save_action, action, sizeof(st_debug_sync_action)); /* Move actions down. */ - memmove(ds_control->ds_action + dsp_idx, + memmove((void*)(ds_control->ds_action + dsp_idx), ds_control->ds_action + dsp_idx + 1, (ds_control->ds_active - dsp_idx) * sizeof(st_debug_sync_action)); @@ -598,8 +595,8 @@ static void debug_sync_remove_action(st_debug_sync_control *ds_control, produced by the shift. Again do not use an assignment operator to avoid string allocation/copy. */ - memmove(ds_control->ds_action + ds_control->ds_active, save_action, - sizeof(st_debug_sync_action)); + memmove((void*)(ds_control->ds_action + ds_control->ds_active), + save_action, sizeof(st_debug_sync_action)); } DBUG_VOID_RETURN; @@ -740,7 +737,7 @@ static bool debug_sync_set_action(THD *thd, st_debug_sync_action *action) DBUG_ASSERT(action); DBUG_ASSERT(ds_control); - action->activation_count= max(action->hit_limit, action->execute); + action->activation_count= MY_MAX(action->hit_limit, action->execute); if (!action->activation_count) { debug_sync_remove_action(ds_control, action); @@ -782,7 +779,7 @@ static bool debug_sync_set_action(THD *thd, st_debug_sync_action *action) point decremented it to 0. In this case the following happened: - an error message was reported with my_error() and - - the statement was killed with thd->killed= KILL_QUERY. + - the statement was killed with thd->killed= THD::KILL_QUERY. If a statement reports an error, it must not call send_ok(). The calling functions will not call send_ok(), if we return TRUE @@ -984,6 +981,7 @@ static bool debug_sync_eval_action(THD *thd, char *action_str) DBUG_ENTER("debug_sync_eval_action"); DBUG_ASSERT(thd); DBUG_ASSERT(action_str); + DBUG_PRINT("debug_sync", ("action_str: '%s'", action_str)); /* Get debug sync point name. Or a special command. @@ -1350,8 +1348,7 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action) if (action->execute) { - const char *old_proc_info; - LINT_INIT(old_proc_info); + const char *UNINIT_VAR(old_proc_info); action->execute--; @@ -1396,8 +1393,9 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action) if (action->wait_for.length()) { - mysql_mutex_t *old_mutex; + mysql_mutex_t *old_mutex= NULL; mysql_cond_t *old_cond= NULL; + bool restore_current_mutex; int error= 0; struct timespec abstime; @@ -1414,11 +1412,12 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action) { old_mutex= thd->mysys_var->current_mutex; old_cond= thd->mysys_var->current_cond; + restore_current_mutex = true; thd->mysys_var->current_mutex= &debug_sync_global.ds_mutex; thd->mysys_var->current_cond= &debug_sync_global.ds_cond; } else - old_mutex= NULL; + restore_current_mutex = false; set_timespec(abstime, action->timeout); DBUG_EXECUTE("debug_sync_exec", { @@ -1448,8 +1447,14 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action) sig_wait, sig_glob, error));}); if (error == ETIMEDOUT || error == ETIME) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_DEBUG_SYNC_TIMEOUT, ER(ER_DEBUG_SYNC_TIMEOUT)); + // We should not make the statement fail, even if in strict mode. + const bool save_abort_on_warning= thd->abort_on_warning; + thd->abort_on_warning= false; + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_DEBUG_SYNC_TIMEOUT, + ER_THD(thd, ER_DEBUG_SYNC_TIMEOUT)); + thd->abort_on_warning= save_abort_on_warning; + DBUG_EXECUTE_IF("debug_sync_abort_on_timeout", DBUG_ABORT();); break; } error= 0; @@ -1473,7 +1478,7 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action) is locked. (See comment in THD::exit_cond().) */ mysql_mutex_unlock(&debug_sync_global.ds_mutex); - if (old_mutex) + if (restore_current_mutex) { mysql_mutex_lock(&thd->mysys_var->mutex); thd->mysys_var->current_mutex= old_mutex; @@ -1497,7 +1502,7 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action) { if (!--action->hit_limit) { - thd->killed= KILL_QUERY; + thd->set_killed(KILL_QUERY); my_error(ER_DEBUG_SYNC_HIT_LIMIT, MYF(0)); } DBUG_PRINT("debug_sync_exec", ("hit_limit: %lu at: '%s'", @@ -1519,9 +1524,10 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action) static void debug_sync(THD *thd, const char *sync_point_name, size_t name_len) { if (!thd) - thd= current_thd; - if (!thd) - return; + { + if (!(thd= current_thd)) + return; + } st_debug_sync_control *ds_control= thd->debug_sync_control; st_debug_sync_action *action; diff --git a/sql/debug_sync.h b/sql/debug_sync.h index 4d29d6e7508..25b379e5892 100644 --- a/sql/debug_sync.h +++ b/sql/debug_sync.h @@ -32,6 +32,9 @@ class THD; #if defined(ENABLED_DEBUG_SYNC) +/* Command line option --debug-sync-timeout. See mysqld.cc. */ +extern MYSQL_PLUGIN_IMPORT uint opt_debug_sync_timeout; + /* Default WAIT_FOR timeout if command line option is given without argument. */ #define DEBUG_SYNC_DEFAULT_WAIT_TIMEOUT 300 @@ -41,6 +44,7 @@ extern void debug_sync_end(void); extern void debug_sync_init_thread(THD *thd); extern void debug_sync_end_thread(THD *thd); extern bool debug_sync_set_action(THD *thd, const char *action_str, size_t len); +extern bool debug_sync_update(THD *thd, char *val_str); #endif /* defined(ENABLED_DEBUG_SYNC) */ diff --git a/sql/derror.cc b/sql/derror.cc index 33835992258..5f0bc455caf 100644 --- a/sql/derror.cc +++ b/sql/derror.cc @@ -21,6 +21,7 @@ Read language depeneded messagefile */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "derror.h" @@ -34,7 +35,7 @@ static void init_myfunc_errs(void); C_MODE_START -static const char **get_server_errmsgs() +static const char **get_server_errmsgs(void) { if (!current_thd) return DEFAULT_ERRMSGS; @@ -76,7 +77,7 @@ bool init_errmessage(void) &errmsgs, ER_ERROR_LAST - ER_ERROR_FIRST + 1) && !errmsgs) { - free(errmsgs); + my_free(errmsgs); if (org_errmsgs) { @@ -99,7 +100,7 @@ bool init_errmessage(void) } } else - free(org_errmsgs); // Free old language + my_free(org_errmsgs); // Free old language /* Register messages for use with my_error(). */ if (my_error_register(get_server_errmsgs, ER_ERROR_FIRST, ER_ERROR_LAST)) @@ -146,18 +147,16 @@ bool read_texts(const char *file_name, const char *language, const char ***point, uint error_messages) { register uint i; - uint count,funktpos,textcount; - size_t length; + uint count,funktpos; + size_t offset, length; File file; char name[FN_REFLEN]; char lang_path[FN_REFLEN]; - uchar *buff; + uchar *UNINIT_VAR(buff); uchar head[32],*pos; DBUG_ENTER("read_texts"); *point= 0; - - LINT_INIT(buff); funktpos=0; convert_dirname(lang_path, language, NullS); (void) my_load_path(lang_path, lang_path, lc_messages_dir); @@ -186,12 +185,11 @@ bool read_texts(const char *file_name, const char *language, goto err; funktpos=2; if (head[0] != (uchar) 254 || head[1] != (uchar) 254 || - head[2] != 2 || head[3] != 1) + head[2] != 2 || head[3] != 3) goto err; /* purecov: inspected */ - textcount=head[4]; error_message_charset_info= system_charset_info; - length=uint2korr(head+6); count=uint2korr(head+8); + length=uint4korr(head+6); count=uint2korr(head+10); if (count < error_messages) { @@ -203,7 +201,7 @@ Error message file '%s' had only %d error messages, but it should contain at lea } if (!(*point= (const char**) - my_malloc((size_t) (length+count*sizeof(char*)),MYF(0)))) + my_malloc((size_t) (MY_MAX(length,count*2)+count*sizeof(char*)),MYF(0)))) { funktpos=3; /* purecov: inspected */ goto err; /* purecov: inspected */ @@ -212,18 +210,15 @@ Error message file '%s' had only %d error messages, but it should contain at lea if (mysql_file_read(file, buff, (size_t) count*2, MYF(MY_NABP))) goto err; - for (i=0, pos= buff ; i< count ; i++) + for (i=0, offset=0, pos= buff ; i< count ; i++) { - (*point)[i]= (char*) buff+uint2korr(pos); + (*point)[i]= (char*) buff+offset; + offset+= uint2korr(pos); pos+=2; } if (mysql_file_read(file, buff, length, MYF(MY_NABP))) goto err; - for (i=1 ; i < textcount ; i++) - { - point[i]= *point +uint2korr(head+10+i+i); - } (void) mysql_file_close(file, MYF(0)); i= check_error_mesg(file_name, *point); @@ -249,20 +244,20 @@ static void init_myfunc_errs() init_glob_errs(); /* Initiate english errors */ if (!(specialflag & SPECIAL_ENGLISH)) { - EE(EE_FILENOTFOUND) = ER(ER_FILE_NOT_FOUND); - EE(EE_CANTCREATEFILE) = ER(ER_CANT_CREATE_FILE); - EE(EE_READ) = ER(ER_ERROR_ON_READ); - EE(EE_WRITE) = ER(ER_ERROR_ON_WRITE); - EE(EE_BADCLOSE) = ER(ER_ERROR_ON_CLOSE); - EE(EE_OUTOFMEMORY) = ER(ER_OUTOFMEMORY); - EE(EE_DELETE) = ER(ER_CANT_DELETE_FILE); - EE(EE_LINK) = ER(ER_ERROR_ON_RENAME); - EE(EE_EOFERR) = ER(ER_UNEXPECTED_EOF); - EE(EE_CANTLOCK) = ER(ER_CANT_LOCK); - EE(EE_DIR) = ER(ER_CANT_READ_DIR); - EE(EE_STAT) = ER(ER_CANT_GET_STAT); - EE(EE_GETWD) = ER(ER_CANT_GET_WD); - EE(EE_SETWD) = ER(ER_CANT_SET_WD); - EE(EE_DISK_FULL) = ER(ER_DISK_FULL); + EE(EE_FILENOTFOUND) = ER_DEFAULT(ER_FILE_NOT_FOUND); + EE(EE_CANTCREATEFILE) = ER_DEFAULT(ER_CANT_CREATE_FILE); + EE(EE_READ) = ER_DEFAULT(ER_ERROR_ON_READ); + EE(EE_WRITE) = ER_DEFAULT(ER_ERROR_ON_WRITE); + EE(EE_BADCLOSE) = ER_DEFAULT(ER_ERROR_ON_CLOSE); + EE(EE_OUTOFMEMORY) = ER_DEFAULT(ER_OUTOFMEMORY); + EE(EE_DELETE) = ER_DEFAULT(ER_CANT_DELETE_FILE); + EE(EE_LINK) = ER_DEFAULT(ER_ERROR_ON_RENAME); + EE(EE_EOFERR) = ER_DEFAULT(ER_UNEXPECTED_EOF); + EE(EE_CANTLOCK) = ER_DEFAULT(ER_CANT_LOCK); + EE(EE_DIR) = ER_DEFAULT(ER_CANT_READ_DIR); + EE(EE_STAT) = ER_DEFAULT(ER_CANT_GET_STAT); + EE(EE_GETWD) = ER_DEFAULT(ER_CANT_GET_WD); + EE(EE_SETWD) = ER_DEFAULT(ER_CANT_SET_WD); + EE(EE_DISK_FULL) = ER_DEFAULT(ER_DISK_FULL); } } diff --git a/sql/des_key_file.cc b/sql/des_key_file.cc index b6b6f4536bc..ede2e9fa9d4 100644 --- a/sql/des_key_file.cc +++ b/sql/des_key_file.cc @@ -13,7 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "my_global.h" // HAVE_* +#include <my_global.h> // HAVE_* #include "sql_priv.h" #include "des_key_file.h" // st_des_keyschedule, st_des_keyblock #include "log.h" // sql_print_error diff --git a/sql/discover.cc b/sql/discover.cc index b9dba92a780..d8bf6ca79c5 100644 --- a/sql/discover.cc +++ b/sql/discover.cc @@ -21,6 +21,7 @@ Functions for discover of frm file from handler */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "discover.h" @@ -45,7 +46,7 @@ 3 Could not allocate data for read. Could not read file */ -int readfrm(const char *name, uchar **frmdata, size_t *len) +int readfrm(const char *name, const uchar **frmdata, size_t *len) { int error; char index_file[FN_REFLEN]; @@ -70,13 +71,17 @@ int readfrm(const char *name, uchar **frmdata, size_t *len) error= 2; if (mysql_file_fstat(file, &state, MYF(0))) goto err; - read_len= (size_t)state.st_size; + read_len= (size_t)MY_MIN(FRM_MAX_SIZE, state.st_size); // safety // Read whole frm file error= 3; - read_data= 0; // Nothing to free - if (read_string(file, &read_data, read_len)) + if (!(read_data= (uchar*)my_malloc(read_len, MYF(MY_WME)))) goto err; + if (mysql_file_read(file, read_data, read_len, MYF(MY_NABP))) + { + my_free(read_data); + goto err; + } // Setup return data *frmdata= (uchar*) read_data; @@ -84,8 +89,7 @@ int readfrm(const char *name, uchar **frmdata, size_t *len) error= 0; err: - if (file > 0) - (void) mysql_file_close(file, MYF(MY_WME)); + (void) mysql_file_close(file, MYF(MY_WME)); err_end: /* Here when no file */ DBUG_RETURN (error); @@ -96,7 +100,7 @@ int readfrm(const char *name, uchar **frmdata, size_t *len) Write the content of a frm data pointer to a frm file. - @param name path to table-file "db/name" + @param path path to table-file "db/name" @param frmdata frm data @param len length of the frmdata @@ -106,29 +110,161 @@ int readfrm(const char *name, uchar **frmdata, size_t *len) 2 Could not write file */ -int writefrm(const char *name, const uchar *frmdata, size_t len) +int writefrm(const char *path, const char *db, const char *table, + bool tmp_table, const uchar *frmdata, size_t len) { - File file; - char index_file[FN_REFLEN]; + char file_name[FN_REFLEN+1]; int error; + int create_flags= O_RDWR | O_TRUNC; DBUG_ENTER("writefrm"); - DBUG_PRINT("enter",("name: '%s' len: %lu ",name, (ulong) len)); + DBUG_PRINT("enter",("name: '%s' len: %lu ",path, (ulong) len)); - error= 0; - if ((file= mysql_file_create(key_file_frm, - fn_format(index_file, name, "", reg_ext, - MY_UNPACK_FILENAME | MY_APPEND_EXT), - CREATE_MODE, O_RDWR | O_TRUNC, - MYF(MY_WME))) >= 0) + if (tmp_table) + create_flags|= O_EXCL | O_NOFOLLOW; + + strxnmov(file_name, sizeof(file_name)-1, path, reg_ext, NullS); + + File file= mysql_file_create(key_file_frm, file_name, + CREATE_MODE, create_flags, MYF(0)); + + if ((error= file < 0)) { - if (mysql_file_write(file, frmdata, len, MYF(MY_WME | MY_NABP))) - error= 2; - (void) mysql_file_close(file, MYF(0)); + if (my_errno == ENOENT) + my_error(ER_BAD_DB_ERROR, MYF(0), db); + else + my_error(ER_CANT_CREATE_TABLE, MYF(0), db, table, my_errno); + } + else + { + error= mysql_file_write(file, frmdata, len, MYF(MY_WME | MY_NABP)); + + if (!error && !tmp_table && opt_sync_frm) + error= mysql_file_sync(file, MYF(MY_WME)) || + my_sync_dir_by_file(file_name, MYF(MY_WME)); + + error|= mysql_file_close(file, MYF(MY_WME)); } DBUG_RETURN(error); } /* writefrm */ +static inline void advance(FILEINFO* &from, FILEINFO* &to, + FILEINFO* cur, bool &skip) +{ + if (skip) // if not copying + from= cur; // just advance the start pointer + else // if copying + if (to == from) // but to the same place (not shifting the data) + from= to= cur; // advance both pointers + else // otherwise + while (from < cur) // have to copy [from...cur) to [to...) + *to++ = *from++; + skip= false; +} + +/** + Go through the directory listing looking for files with a specified + extension and add them to the result list + + @details + This function may be called many times on the same directory listing + but with different extensions. To avoid discovering the same table twice, + whenever a table file is discovered, all files with the same name + (independently from the extensions) are removed from the list. + Example: the list contained + { "db.opt", "t1.MYD", "t1.MYI", "t1.frm", "t2.ARZ", "t3.ARZ", "t3.frm" } + on discovering all ".frm" files, tables "t1" and "t3" will be found, + and list will become + { "db.opt", "t2.ARZ" } + and now ".ARZ" discovery can discover the table "t2" + @note + This function assumes that the directory listing is sorted alphabetically. + @note Partitioning makes this more complicated. A partitioned table t1 might + have files, like t1.frm, t1#P#part1.ibd, t1#P#foo.ibd, etc. + That means we need to compare file names only up to the first '#' or '.' + whichever comes first. +*/ +int extension_based_table_discovery(MY_DIR *dirp, const char *ext_meta, + handlerton::discovered_list *result) +{ + CHARSET_INFO *cs= character_set_filesystem; + size_t ext_meta_len= strlen(ext_meta); + FILEINFO *from, *to, *cur, *end; + bool skip= false; + + from= to= cur= dirp->dir_entry; + end= cur + dirp->number_of_files; + while (cur < end) + { + char *octothorp= strchr(cur->name + 1, '#'); + char *ext= strchr(octothorp ? octothorp : cur->name, FN_EXTCHAR); + + if (ext) + { + size_t len= (octothorp ? octothorp : ext) - cur->name; + if (from != cur && + (strlen(from->name) <= len || + my_strnncoll(cs, (uchar*)from->name, len, (uchar*)cur->name, len) || + (from->name[len] != FN_EXTCHAR && from->name[len] != '#'))) + advance(from, to, cur, skip); + + if (my_strnncoll(cs, (uchar*)ext, strlen(ext), + (uchar*)ext_meta, ext_meta_len) == 0) + { + *ext = 0; + if (result->add_file(cur->name)) + return 1; + *ext = FN_EXTCHAR; + skip= true; // table discovered, skip all files with the same name + } + } + else + { + advance(from, to, cur, skip); + from++; + } + + cur++; + } + advance(from, to, cur, skip); + dirp->number_of_files= to - dirp->dir_entry; + return 0; +} + +/** + Simple, not reusable file-based table discovery + + @details + simplified version of extension_based_table_discovery(), that does not + modify the list of files. It cannot be called many times for the same + directory listing, otherwise it'll produce duplicate results. +*/ +int ext_table_discovery_simple(MY_DIR *dirp, + handlerton::discovered_list *result) +{ + CHARSET_INFO *cs= character_set_filesystem; + FILEINFO *cur, *end; + + cur= dirp->dir_entry; + end= cur + dirp->number_of_files; + while (cur < end) + { + char *ext= strrchr(cur->name, FN_EXTCHAR); + + if (ext) + { + if (my_strnncoll(cs, (uchar*)ext, strlen(ext), + (uchar*)reg_ext, reg_ext_length) == 0) + { + *ext = 0; + if (result->add_file(cur->name)) + return 1; + } + } + cur++; + } + return 0; +} diff --git a/sql/discover.h b/sql/discover.h index a663e44128d..e1508107235 100644 --- a/sql/discover.h +++ b/sql/discover.h @@ -18,7 +18,24 @@ #include "my_global.h" /* uchar */ -int readfrm(const char *name, uchar **data, size_t *length); -int writefrm(const char* name, const uchar* data, size_t len); +int extension_based_table_discovery(MY_DIR *dirp, const char *ext, + handlerton::discovered_list *tl); + +#ifdef MYSQL_SERVER +int readfrm(const char *name, const uchar **data, size_t *length); +int writefrm(const char *path, const char *db, const char *table, + bool tmp_table, const uchar *frmdata, size_t len); + +/* a helper to delete an frm file, given a path w/o .frm extension */ +inline void deletefrm(const char *path) +{ + char frm_name[FN_REFLEN]; + strxmov(frm_name, path, reg_ext, NullS); + mysql_file_delete(key_file_frm, frm_name, MYF(0)); +} + +int ext_table_discovery_simple(MY_DIR *dirp, + handlerton::discovered_list *result); +#endif #endif /* DISCOVER_INCLUDED */ diff --git a/sql/encryption.cc b/sql/encryption.cc new file mode 100644 index 00000000000..e5b54633066 --- /dev/null +++ b/sql/encryption.cc @@ -0,0 +1,245 @@ +/* Copyright (C) 2015 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 */ + +#include <my_global.h> +#include <mysql/plugin_encryption.h> +#include "log.h" +#include "sql_plugin.h" +#include <my_crypt.h> + +/* there can be only one encryption plugin enabled */ +static plugin_ref encryption_manager= 0; +struct encryption_service_st encryption_handler; + +extern "C" { + +uint no_get_key(uint, uint, uchar*, uint*) +{ + return ENCRYPTION_KEY_VERSION_INVALID; +} +uint no_key(uint) +{ + return ENCRYPTION_KEY_VERSION_INVALID; +} +uint zero_size(uint,uint) +{ + return 0; +} + +static int ctx_init(void *ctx, const unsigned char* key, unsigned int klen, + const unsigned char* iv, unsigned int ivlen, int flags, + unsigned int key_id, unsigned int key_version) +{ + return my_aes_crypt_init(ctx, MY_AES_CBC, flags, key, klen, iv, ivlen); +} + +static unsigned int get_length(unsigned int slen, unsigned int key_id, + unsigned int key_version) +{ + return my_aes_get_size(MY_AES_CBC, slen); +} + +uint ctx_size(unsigned int, unsigned int) +{ + return MY_AES_CTX_SIZE; +} + +} /* extern "C" */ + +int initialize_encryption_plugin(st_plugin_int *plugin) +{ + if (encryption_manager) + return 1; + + if (plugin->plugin->init && plugin->plugin->init(plugin)) + { + sql_print_error("Plugin '%s' init function returned error.", + plugin->name.str); + return 1; + } + + encryption_manager= plugin_lock(NULL, plugin_int_to_ref(plugin)); + st_mariadb_encryption *handle= + (struct st_mariadb_encryption*) plugin->plugin->info; + + /* + Copmiler on Spark doesn't like the '?' operator here as it + belives the (uint (*)...) implies the C++ call model. + */ + if (handle->crypt_ctx_size) + encryption_handler.encryption_ctx_size_func= handle->crypt_ctx_size; + else + encryption_handler.encryption_ctx_size_func= ctx_size; + + encryption_handler.encryption_ctx_init_func= + handle->crypt_ctx_init ? handle->crypt_ctx_init : ctx_init; + + encryption_handler.encryption_ctx_update_func= + handle->crypt_ctx_update ? handle->crypt_ctx_update : my_aes_crypt_update; + + encryption_handler.encryption_ctx_finish_func= + handle->crypt_ctx_finish ? handle->crypt_ctx_finish : my_aes_crypt_finish; + + encryption_handler.encryption_encrypted_length_func= + handle->encrypted_length ? handle->encrypted_length : get_length; + + encryption_handler.encryption_key_get_func= + handle->get_key; + + encryption_handler.encryption_key_get_latest_version_func= + handle->get_latest_key_version; // must be the last + + return 0; +} + +int finalize_encryption_plugin(st_plugin_int *plugin) +{ + bool used= plugin_ref_to_int(encryption_manager) == plugin; + + if (used) + { + encryption_handler.encryption_key_get_func= no_get_key; + encryption_handler.encryption_key_get_latest_version_func= no_key; + encryption_handler.encryption_ctx_size_func= zero_size; + } + + if (plugin && plugin->plugin->deinit && plugin->plugin->deinit(NULL)) + { + DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.", + plugin->name.str)); + } + + if (used) + { + plugin_unlock(NULL, encryption_manager); + encryption_manager= 0; + } + return 0; +} + +/****************************************************************** + Encryption Scheme service +******************************************************************/ +static uint scheme_get_key(st_encryption_scheme *scheme, + st_encryption_scheme_key *key) +{ + if (scheme->locker) + scheme->locker(scheme, 0); + + // Check if we already have key + for (uint i = 0; i < array_elements(scheme->key); i++) + { + if (scheme->key[i].version == 0) // no more keys + break; + + if (scheme->key[i].version == key->version) + { + *key= scheme->key[i]; + if (scheme->locker) + scheme->locker(scheme, 1); + return 0; + } + } + + // Not found! + scheme->keyserver_requests++; + + uchar global_key[MY_AES_MAX_KEY_LENGTH]; + uint global_key_len= sizeof(global_key), key_len; + + uint rc = encryption_key_get(scheme->key_id, key->version, + global_key, & global_key_len); + if (rc) + goto ret; + + /* Now generate the local key by encrypting IV using the global key */ + rc = my_aes_crypt(MY_AES_ECB, ENCRYPTION_FLAG_ENCRYPT | ENCRYPTION_FLAG_NOPAD, + scheme->iv, sizeof(scheme->iv), key->key, &key_len, + global_key, global_key_len, NULL, 0); + + DBUG_ASSERT(key_len == sizeof(key->key)); + + if (rc) + goto ret; + + // Rotate keys to make room for a new + for (uint i = array_elements(scheme->key) - 1; i; i--) + scheme->key[i] = scheme->key[i - 1]; + + scheme->key[0]= *key; + +ret: + if (scheme->locker) + scheme->locker(scheme, 1); + return rc; +} + +int do_crypt(const unsigned char* src, unsigned int slen, + unsigned char* dst, unsigned int* dlen, + struct st_encryption_scheme *scheme, + unsigned int key_version, unsigned int i32_1, + unsigned int i32_2, unsigned long long i64, + int flag) +{ + compile_time_assert(ENCRYPTION_SCHEME_KEY_INVALID == + (int)ENCRYPTION_KEY_VERSION_INVALID); + + // Maybe temporal solution for MDEV-8173 + // Rationale: scheme->type is currently global/object + // and when used here might not represent actual state + // of smaller granularity objects e.g. InnoDB page state + // as type is stored to tablespace (FIL) and could represent + // state where key rotation is trying to reach + //DBUG_ASSERT(scheme->type == 1); + + if (key_version == ENCRYPTION_KEY_VERSION_INVALID || + key_version == ENCRYPTION_KEY_NOT_ENCRYPTED) + return ENCRYPTION_SCHEME_KEY_INVALID; + + st_encryption_scheme_key key; + key.version= key_version; + uint rc= scheme_get_key(scheme, &key); + if (rc) + return (int)rc; + + unsigned char iv[4 + 4 + 8]; + int4store(iv + 0, i32_1); + int4store(iv + 4, i32_2); + int8store(iv + 8, i64); + + return encryption_crypt(src, slen, dst, dlen, key.key, sizeof(key.key), + iv, sizeof(iv), flag, scheme->key_id, key_version); +} + +int encryption_scheme_encrypt(const unsigned char* src, unsigned int slen, + unsigned char* dst, unsigned int* dlen, + struct st_encryption_scheme *scheme, + unsigned int key_version, unsigned int i32_1, + unsigned int i32_2, unsigned long long i64) +{ + return do_crypt(src, slen, dst, dlen, scheme, key_version, i32_1, + i32_2, i64, ENCRYPTION_FLAG_NOPAD | ENCRYPTION_FLAG_ENCRYPT); +} + + +int encryption_scheme_decrypt(const unsigned char* src, unsigned int slen, + unsigned char* dst, unsigned int* dlen, + struct st_encryption_scheme *scheme, + unsigned int key_version, unsigned int i32_1, + unsigned int i32_2, unsigned long long i64) +{ + return do_crypt(src, slen, dst, dlen, scheme, key_version, i32_1, + i32_2, i64, ENCRYPTION_FLAG_NOPAD | ENCRYPTION_FLAG_DECRYPT); +} diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index dfd35583581..6ef9fa9f8ef 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -15,7 +15,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define MYSQL_LEX 1 -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "unireg.h" #include "sql_parse.h" // parse_sql @@ -209,7 +209,7 @@ Event_basic::Event_basic() { DBUG_ENTER("Event_basic::Event_basic"); /* init memory root */ - init_sql_alloc(&mem_root, 256, 512); + init_sql_alloc(&mem_root, 256, 512, MYF(0)); dbname.str= name.str= NULL; dbname.length= name.length= 0; time_zone= NULL; @@ -608,9 +608,9 @@ Event_timed::load_from_row(THD *thd, TABLE *table) table, &creation_ctx)) { push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_WARN, + Sql_condition::WARN_LEVEL_WARN, ER_EVENT_INVALID_CREATION_CTX, - ER(ER_EVENT_INVALID_CREATION_CTX), + ER_THD(thd, ER_EVENT_INVALID_CREATION_CTX), (const char *) dbname.str, (const char *) name.str); } @@ -1337,7 +1337,7 @@ Event_job_data::execute(THD *thd, bool drop) DBUG_ENTER("Event_job_data::execute"); - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(); /* MySQL parser currently assumes that current database is either @@ -1462,13 +1462,44 @@ end: NOTE: even if we run in read-only mode, we should be able to lock the mysql.event table for writing. In order to achieve this, we should call mysql_lock_tables() under the super-user. + + Same goes for transaction access mode. + Temporarily reset it to read-write. */ saved_master_access= thd->security_ctx->master_access; thd->security_ctx->master_access |= SUPER_ACL; + bool save_tx_read_only= thd->tx_read_only; + thd->tx_read_only= false; + + /* + This code is processing event execution and does not have client + connection. Here, event execution will now execute a prepared + DROP EVENT statement, but thd->lex->sql_command is set to + SQLCOM_CREATE_PROCEDURE + DROP EVENT will be logged in binlog, and we have to + replicate it to make all nodes have consistent event definitions + Wsrep DDL replication is triggered inside Events::drop_event(), + and here we need to prepare the THD so that DDL replication is + possible, essentially it requires setting sql_command to + SQLCOMM_DROP_EVENT, we will switch sql_command for the duration + of DDL replication only. + */ + const enum_sql_command sql_command_save= thd->lex->sql_command; + const bool sql_command_set= WSREP(thd); + + if (sql_command_set) + thd->lex->sql_command = SQLCOM_DROP_EVENT; ret= Events::drop_event(thd, dbname, name, FALSE); + if (sql_command_set) + { + WSREP_TO_ISOLATION_END; + thd->lex->sql_command = sql_command_save; + } + + thd->tx_read_only= save_tx_read_only; thd->security_ctx->master_access= saved_master_access; } } diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h index 2483c564dff..8113fcb0e2e 100644 --- a/sql/event_data_objects.h +++ b/sql/event_data_objects.h @@ -85,7 +85,7 @@ class Event_queue_element : public Event_basic public: int on_completion; int status; - longlong originator; + uint32 originator; my_time_t last_executed; my_time_t execute_at; diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 481a49bf5b0..3afd2659a29 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -1,6 +1,5 @@ /* - Copyright (c) 2006, 2017, Oracle and/or its affiliates. - Copyright (c) 2009, 2018, MariaDB + Copyright (c) 2006, 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 @@ -15,6 +14,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_base.h" // close_thread_tables @@ -165,24 +165,10 @@ const TABLE_FIELD_TYPE event_table_fields[ET_FIELD_COUNT] = }; static const TABLE_FIELD_DEF - event_table_def= {ET_FIELD_COUNT, event_table_fields}; - -class Event_db_intact : public Table_check_intact -{ -protected: - void report_error(uint, const char *fmt, ...) - { - va_list args; - va_start(args, fmt); - error_log_print(ERROR_LEVEL, fmt, args); - va_end(args); - } -public: - Event_db_intact() { has_keys= TRUE; } -}; +event_table_def= {ET_FIELD_COUNT, event_table_fields, 0, (uint*) 0}; /** In case of an error, a message is printed to the error log. */ -static Event_db_intact table_intact; +static Table_check_intact_log_error table_intact; /** @@ -226,7 +212,8 @@ mysql_event_fill_row(THD *thd, Safety: this can only happen if someone started the server and then altered mysql.event. */ - my_error(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED, MYF(0), table->alias.c_ptr(), + my_error(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2, MYF(0), + table->s->db.str, table->alias.c_ptr(), (int) ET_FIELD_COUNT, table->s->fields); DBUG_RETURN(TRUE); } @@ -416,9 +403,7 @@ Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, CHARSET_INFO *scs= system_charset_info; KEY *key_info; uint key_len; - uchar *key_buf= NULL; - LINT_INIT(key_buf); - + uchar *key_buf; DBUG_ENTER("Event_db_repository::index_read_for_db_for_i_s"); DBUG_PRINT("info", ("Using prefix scanning on PK")); @@ -432,11 +417,11 @@ Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, key_info= event_table->key_info; - if (key_info->key_parts == 0 || + if (key_info->user_defined_key_parts == 0 || key_info->key_part[0].field != event_table->field[ET_FIELD_DB]) { /* Corrupted table: no index or index on a wrong column */ - my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event"); + my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), "mysql", "event"); ret= 1; goto end; } @@ -477,7 +462,7 @@ Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, end: event_table->file->ha_index_end(); - DBUG_RETURN(test(ret)); + DBUG_RETURN(MY_TEST(ret)); } @@ -656,7 +641,6 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, bool Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data, - bool create_if_not, bool *event_already_exists) { int ret= 1; @@ -687,18 +671,30 @@ Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data, DBUG_PRINT("info", ("check existance of an event with the same name")); if (!find_named_event(parse_data->dbname, parse_data->name, table)) { - if (create_if_not) + if (thd->lex->create_info.or_replace()) + { + *event_already_exists= false; // Force the caller to update event_queue + if ((ret= table->file->ha_delete_row(table->record[0]))) + { + table->file->print_error(ret, MYF(0)); + goto end; + } + } + else if (thd->lex->create_info.if_not_exists()) { *event_already_exists= true; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_EVENT_ALREADY_EXISTS, ER(ER_EVENT_ALREADY_EXISTS), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_EVENT_ALREADY_EXISTS, + ER_THD(thd, ER_EVENT_ALREADY_EXISTS), parse_data->name.str); ret= 0; + goto end; } else + { my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), parse_data->name.str); - - goto end; + goto end; + } } else *event_already_exists= false; @@ -747,7 +743,7 @@ end: thd->mdl_context.rollback_to_savepoint(mdl_savepoint); thd->variables.sql_mode= saved_mode; - DBUG_RETURN(test(ret)); + DBUG_RETURN(MY_TEST(ret)); } @@ -837,9 +833,6 @@ Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data, (int) table->field[ET_FIELD_ON_COMPLETION]->val_int())) goto end; - /* Don't update create on row update. */ - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - /* mysql_event_fill_row() calls my_error() in case of error so no need to handle it here @@ -865,7 +858,7 @@ end: thd->mdl_context.rollback_to_savepoint(mdl_savepoint); thd->variables.sql_mode= saved_mode; - DBUG_RETURN(test(ret)); + DBUG_RETURN(MY_TEST(ret)); } @@ -916,8 +909,8 @@ Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name, goto end; } - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SP_DOES_NOT_EXIST, ER_THD(thd, ER_SP_DOES_NOT_EXIST), "Event", name.str); ret= 0; @@ -925,7 +918,7 @@ end: close_thread_tables(thd); thd->mdl_context.rollback_to_savepoint(mdl_savepoint); - DBUG_RETURN(test(ret)); + DBUG_RETURN(MY_TEST(ret)); } @@ -941,7 +934,7 @@ end: @retval FALSE an event with such db/name key exists - @retval TRUE no record found or an error occured. + @retval TRUE no record found or an error occurred. */ bool @@ -962,7 +955,7 @@ Event_db_repository::find_named_event(LEX_STRING db, LEX_STRING name, if (db.length > table->field[ET_FIELD_DB]->field_length || name.length > table->field[ET_FIELD_NAME]->field_length || table->s->keys == 0 || - table->key_info[0].key_parts != 2 || + table->key_info[0].user_defined_key_parts != 2 || table->key_info[0].key_part[0].fieldnr != ET_FIELD_DB+1 || table->key_info[0].key_part[1].fieldnr != ET_FIELD_NAME+1) DBUG_RETURN(TRUE); @@ -1092,7 +1085,7 @@ Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname, if ((ret= find_named_event(dbname, name, event_table.table))) my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str); else if ((ret= etn->load_from_row(thd, event_table.table))) - my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event"); + my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), "mysql", "event"); close_system_tables(thd, &open_tables_backup); } @@ -1120,17 +1113,15 @@ update_timing_fields_for_event(THD *thd, TABLE *table= NULL; Field **fields; int ret= 1; - bool save_binlog_row_based; + enum_binlog_format save_binlog_format; MYSQL_TIME time; - DBUG_ENTER("Event_db_repository::update_timing_fields_for_event"); /* Turn off row binlogging of event timing updates. These are not used for RBR of events replicated to the slave. */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); DBUG_ASSERT(thd->security_ctx->master_access & SUPER_ACL); @@ -1143,8 +1134,6 @@ update_timing_fields_for_event(THD *thd, goto end; store_record(table, record[1]); - /* Don't update create on row update. */ - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; my_tz_OFFSET0->gmt_sec_to_TIME(&time, last_executed); fields[ET_FIELD_LAST_EXECUTED]->set_notnull(); @@ -1165,12 +1154,9 @@ end: if (table) close_mysql_tables(thd); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + thd->restore_stmt_binlog_format(save_binlog_format); - DBUG_RETURN(test(ret)); + DBUG_RETURN(MY_TEST(ret)); } @@ -1248,7 +1234,7 @@ Event_db_repository::check_system_tables(THD *thd) close_mysql_tables(thd); } - DBUG_RETURN(test(ret)); + DBUG_RETURN(MY_TEST(ret)); } /** diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h index a2862790be1..e7b52bacc2a 100644 --- a/sql/event_db_repository.h +++ b/sql/event_db_repository.h @@ -74,7 +74,7 @@ public: Event_db_repository(){} bool - create_event(THD *thd, Event_parse_data *parse_data, bool create_if_not, + create_event(THD *thd, Event_parse_data *parse_data, bool *event_already_exists); bool update_event(THD *thd, Event_parse_data *parse_data, LEX_STRING *new_dbname, diff --git a/sql/event_parse_data.cc b/sql/event_parse_data.cc index ad812a6aa5d..6c123c8e641 100644 --- a/sql/event_parse_data.cc +++ b/sql/event_parse_data.cc @@ -14,6 +14,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sp_head.h" @@ -126,9 +127,9 @@ Event_parse_data::check_if_in_the_past(THD *thd, my_time_t ltime_utc) { switch (thd->lex->sql_command) { case SQLCOM_CREATE_EVENT: - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_EVENT_CANNOT_CREATE_IN_THE_PAST, - ER(ER_EVENT_CANNOT_CREATE_IN_THE_PAST)); + ER_THD(thd, ER_EVENT_CANNOT_CREATE_IN_THE_PAST)); break; case SQLCOM_ALTER_EVENT: my_error(ER_EVENT_CANNOT_ALTER_IN_THE_PAST, MYF(0)); @@ -143,9 +144,9 @@ Event_parse_data::check_if_in_the_past(THD *thd, my_time_t ltime_utc) { status= Event_parse_data::DISABLED; status_changed= true; - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_EVENT_EXEC_TIME_IN_THE_PAST, - ER(ER_EVENT_EXEC_TIME_IN_THE_PAST)); + ER_THD(thd, ER_EVENT_EXEC_TIME_IN_THE_PAST)); } } @@ -564,7 +565,8 @@ Event_parse_data::init_definer(THD *thd) void Event_parse_data::check_originator_id(THD *thd) { /* Disable replicated events on slave. */ - if ((thd->system_thread == SYSTEM_THREAD_SLAVE_SQL) || + if ((WSREP(thd) && IF_WSREP(thd->wsrep_applier, 0)) || + (thd->system_thread == SYSTEM_THREAD_SLAVE_SQL) || (thd->system_thread == SYSTEM_THREAD_SLAVE_IO)) { DBUG_PRINT("info", ("Invoked object status set to SLAVESIDE_DISABLED.")); @@ -574,8 +576,8 @@ void Event_parse_data::check_originator_id(THD *thd) status= Event_parse_data::SLAVESIDE_DISABLED; status_changed= true; } - originator = thd->server_id; + originator = thd->variables.server_id; } else - originator = server_id; + originator = global_system_variables.server_id; } diff --git a/sql/event_parse_data.h b/sql/event_parse_data.h index faf42db623a..3ca7fcaab72 100644 --- a/sql/event_parse_data.h +++ b/sql/event_parse_data.h @@ -57,7 +57,7 @@ public: int on_completion; int status; bool status_changed; - longlong originator; + uint32 originator; /* do_not_create will be set if STARTS time is in the past and on_completion == ON_COMPLETION_DROP. diff --git a/sql/event_queue.cc b/sql/event_queue.cc index 40fddff094c..ae8ba258717 100644 --- a/sql/event_queue.cc +++ b/sql/event_queue.cc @@ -10,9 +10,10 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "event_queue.h" @@ -190,7 +191,7 @@ Event_queue::deinit_queue() @param[out] created set to TRUE if no error and the element is added to the queue, FALSE otherwise - @retval TRUE an error occured. The value of created is undefined, + @retval TRUE an error occurred. The value of created is undefined, the element was not deleted. @retval FALSE success */ @@ -559,9 +560,6 @@ Event_queue::dbug_dump_queue(my_time_t when) #endif } -static const char *queue_empty_msg= "Waiting on empty queue"; -static const char *queue_wait_msg= "Waiting for next activation"; - /* Checks whether the top of the queue is elligible for execution and returns an Event_job_data instance in case it should be executed. @@ -608,7 +606,7 @@ Event_queue::get_top_for_execution_if_time(THD *thd, mysql_audit_release(thd); /* Wait on condition until signaled. Release LOCK_queue while waiting. */ - cond_wait(thd, NULL, queue_empty_msg, SCHED_FUNC, __LINE__); + cond_wait(thd, NULL, & stage_waiting_on_empty_queue, SCHED_FUNC, __FILE__, __LINE__); continue; } @@ -629,7 +627,8 @@ Event_queue::get_top_for_execution_if_time(THD *thd, /* Release any held audit resources before waiting */ mysql_audit_release(thd); - cond_wait(thd, &top_time, queue_wait_msg, SCHED_FUNC, __LINE__); + cond_wait(thd, &top_time, &stage_waiting_for_next_activation, SCHED_FUNC, __FILE__, __LINE__); + continue; } @@ -759,16 +758,16 @@ Event_queue::unlock_data(const char *func, uint line) */ void -Event_queue::cond_wait(THD *thd, struct timespec *abstime, const char* msg, - const char *func, uint line) +Event_queue::cond_wait(THD *thd, struct timespec *abstime, const PSI_stage_info *stage, + const char *src_func, const char *src_file, uint src_line) { DBUG_ENTER("Event_queue::cond_wait"); waiting_on_cond= TRUE; - mutex_last_unlocked_at_line= line; + mutex_last_unlocked_at_line= src_line; mutex_queue_data_locked= FALSE; - mutex_last_unlocked_in_func= func; + mutex_last_unlocked_in_func= src_func; - thd->enter_cond(&COND_queue_state, &LOCK_event_queue, msg); + thd->enter_cond(&COND_queue_state, &LOCK_event_queue, stage, NULL, src_func, src_file, src_line); if (!thd->killed) { @@ -779,8 +778,8 @@ Event_queue::cond_wait(THD *thd, struct timespec *abstime, const char* msg, mysql_cond_timedwait(&COND_queue_state, &LOCK_event_queue, abstime); } - mutex_last_locked_in_func= func; - mutex_last_locked_at_line= line; + mutex_last_locked_in_func= src_func; + mutex_last_locked_at_line= src_line; mutex_queue_data_locked= TRUE; waiting_on_cond= FALSE; @@ -788,8 +787,8 @@ Event_queue::cond_wait(THD *thd, struct timespec *abstime, const char* msg, This will free the lock so we need to relock. Not the best thing to do but we need to obey cond_wait() */ - thd->exit_cond(""); - lock_data(func, line); + thd->exit_cond(NULL, src_func, src_file, src_line); + lock_data(src_func, src_line); DBUG_VOID_RETURN; } diff --git a/sql/event_queue.h b/sql/event_queue.h index affa306b259..fdd5937ee17 100644 --- a/sql/event_queue.h +++ b/sql/event_queue.h @@ -12,8 +12,8 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ /** @@ -94,8 +94,8 @@ private: unlock_data(const char *func, uint line); void - cond_wait(THD *thd, struct timespec *abstime, const char* msg, - const char *func, uint line); + cond_wait(THD *thd, struct timespec *abstime, const PSI_stage_info *stage, + const char *src_func, const char *src_file, uint src_line); void find_n_remove_event(LEX_STRING db, LEX_STRING name); diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index a5a3f7110b3..e02b618a80a 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2006, 2013, 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 @@ -13,6 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "event_scheduler.h" @@ -38,8 +39,8 @@ #define LOCK_DATA() lock_data(SCHED_FUNC, __LINE__) #define UNLOCK_DATA() unlock_data(SCHED_FUNC, __LINE__) -#define COND_STATE_WAIT(mythd, abstime, msg) \ - cond_wait(mythd, abstime, msg, SCHED_FUNC, __LINE__) +#define COND_STATE_WAIT(mythd, abstime, stage) \ + cond_wait(mythd, abstime, stage, SCHED_FUNC, __FILE__, __LINE__) extern pthread_attr_t connection_attrib; extern ulong event_executed; @@ -75,9 +76,9 @@ struct scheduler_param { void Event_worker_thread::print_warnings(THD *thd, Event_job_data *et) { - MYSQL_ERROR *err; + const Sql_condition *err; DBUG_ENTER("evex_print_warnings"); - if (thd->warning_info->is_empty()) + if (thd->get_stmt_da()->is_warning_info_empty()) DBUG_VOID_RETURN; char msg_buf[10 * STRING_BUFFER_USUAL_SIZE]; @@ -93,7 +94,8 @@ Event_worker_thread::print_warnings(THD *thd, Event_job_data *et) prefix.append(et->name.str, et->name.length, system_charset_info); prefix.append("] ", 2); - List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); + Diagnostics_area::Sql_condition_iterator it= + thd->get_stmt_da()->sql_conditions(); while ((err= it++)) { String err_msg(msg_buf, sizeof(msg_buf), system_charset_info); @@ -147,14 +149,15 @@ void deinit_event_thread(THD *thd) { thd->proc_info= "Clearing"; - DBUG_ASSERT(thd->net.buff != 0); - net_end(&thd->net); DBUG_PRINT("exit", ("Event thread finishing")); + mysql_mutex_lock(&LOCK_thread_count); - thread_count--; - delete thd; - mysql_cond_broadcast(&COND_thread_count); + thd->unlink(); mysql_mutex_unlock(&LOCK_thread_count); + + delete thd; + thread_safe_decrement32(&thread_count); + signal_thd_deleted(); } @@ -175,21 +178,23 @@ deinit_event_thread(THD *thd) void pre_init_event_thread(THD* thd) { + THD *orig_thd= current_thd; DBUG_ENTER("pre_init_event_thread"); + + set_current_thd(thd); thd->client_capabilities= 0; thd->security_ctx->master_access= 0; thd->security_ctx->db_access= 0; thd->security_ctx->host_or_ip= (char*)my_localhost; - my_net_init(&thd->net, NULL); + my_net_init(&thd->net, NULL, thd, MYF(MY_THREAD_SPECIFIC)); thd->security_ctx->set_user((char*)"event_scheduler"); thd->net.read_timeout= slave_net_timeout; - thd->slave_thread= 0; thd->variables.option_bits|= OPTION_AUTO_IS_NULL; thd->client_capabilities|= CLIENT_MULTI_RESULTS; + thread_safe_increment32(&thread_count); mysql_mutex_lock(&LOCK_thread_count); thd->thread_id= thd->variables.pseudo_thread_id= thread_id++; threads.append(thd); - thread_count++; mysql_mutex_unlock(&LOCK_thread_count); /* @@ -203,6 +208,7 @@ pre_init_event_thread(THD* thd) /* Do not use user-supplied timeout value for system threads. */ thd->variables.lock_wait_timeout= LONG_TIMEOUT; + set_current_thd(orig_thd); DBUG_VOID_RETURN; } @@ -292,6 +298,9 @@ Event_worker_thread::run(THD *thd, Event_queue_element_for_exec *event) Event_job_data job_data; bool res; + DBUG_ASSERT(thd->m_digest == NULL); + DBUG_ASSERT(thd->m_statement_psi == NULL); + thd->thread_stack= &my_stack; // remember where our stack is res= post_init_event_thread(thd); @@ -321,6 +330,8 @@ Event_worker_thread::run(THD *thd, Event_queue_element_for_exec *event) job_data.definer.str, job_data.dbname.str, job_data.name.str); end: + DBUG_ASSERT(thd->m_statement_psi == NULL); + DBUG_ASSERT(thd->m_digest == NULL); DBUG_PRINT("info", ("Done with Event %s.%s", event->dbname.str, event->name.str)); @@ -394,17 +405,23 @@ Event_scheduler::start(int *err_no) ret= true; goto end; } + pre_init_event_thread(new_thd); new_thd->system_thread= SYSTEM_THREAD_EVENT_SCHEDULER; - new_thd->command= COM_DAEMON; + new_thd->set_command(COM_DAEMON); /* We should run the event scheduler thread under the super-user privileges. In particular, this is needed to be able to lock the mysql.event table for writing when the server is running in the read-only mode. + + Same goes for transaction access mode. Set it to read-write for this thd. */ new_thd->security_ctx->master_access |= SUPER_ACL; + new_thd->variables.tx_read_only= false; + new_thd->tx_read_only= false; + /* This should not be marked with MY_THREAD_SPECIFIC */ scheduler_param_value= (struct scheduler_param *)my_malloc(sizeof(struct scheduler_param), MYF(0)); scheduler_param_value->thd= new_thd; @@ -522,6 +539,7 @@ Event_scheduler::execute_top(Event_queue_element_for_exec *event_name) pthread_t th; int res= 0; DBUG_ENTER("Event_scheduler::execute_top"); + if (!(new_thd= new THD())) goto error; @@ -617,7 +635,7 @@ Event_scheduler::stop() { /* Synchronously wait until the scheduler stops. */ while (state != INITIALIZED) - COND_STATE_WAIT(thd, NULL, "Waiting for the scheduler to stop"); + COND_STATE_WAIT(thd, NULL, &stage_waiting_for_scheduler_to_stop); goto end; } @@ -659,7 +677,7 @@ Event_scheduler::stop() */ struct timespec top_time; set_timespec(top_time, 2); - COND_STATE_WAIT(thd, &top_time, "Waiting scheduler to stop"); + COND_STATE_WAIT(thd, &top_time, &stage_waiting_for_scheduler_to_stop); } while (state == STOPPING); DBUG_PRINT("info", ("Scheduler thread has cleaned up. Set state to INIT")); sql_print_information("Event Scheduler: Stopped"); @@ -753,16 +771,17 @@ Event_scheduler::unlock_data(const char *func, uint line) */ void -Event_scheduler::cond_wait(THD *thd, struct timespec *abstime, const char* msg, - const char *func, uint line) +Event_scheduler::cond_wait(THD *thd, struct timespec *abstime, const PSI_stage_info *stage, + const char *src_func, const char *src_file, uint src_line) { DBUG_ENTER("Event_scheduler::cond_wait"); waiting_on_cond= TRUE; - mutex_last_unlocked_at_line= line; + mutex_last_unlocked_at_line= src_line; mutex_scheduler_data_locked= FALSE; - mutex_last_unlocked_in_func= func; + mutex_last_unlocked_in_func= src_func; if (thd) - thd->enter_cond(&COND_state, &LOCK_scheduler_state, msg); + thd->enter_cond(&COND_state, &LOCK_scheduler_state, stage, + NULL, src_func, src_file, src_line); DBUG_PRINT("info", ("mysql_cond_%swait", abstime? "timed":"")); if (!abstime) @@ -775,11 +794,11 @@ Event_scheduler::cond_wait(THD *thd, struct timespec *abstime, const char* msg, This will free the lock so we need to relock. Not the best thing to do but we need to obey cond_wait() */ - thd->exit_cond(""); + thd->exit_cond(NULL, src_func, src_file, src_line); LOCK_DATA(); } - mutex_last_locked_in_func= func; - mutex_last_locked_at_line= line; + mutex_last_locked_in_func= src_func; + mutex_last_locked_at_line= src_line; mutex_scheduler_data_locked= TRUE; waiting_on_cond= FALSE; DBUG_VOID_RETURN; diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h index 538514b8b12..6ec7dccefb9 100644 --- a/sql/event_scheduler.h +++ b/sql/event_scheduler.h @@ -12,8 +12,8 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ /** @addtogroup Event_Scheduler @@ -114,8 +114,8 @@ private: unlock_data(const char *func, uint line); void - cond_wait(THD *thd, struct timespec *abstime, const char* msg, - const char *func, uint line); + cond_wait(THD *thd, struct timespec *abstime, const PSI_stage_info *stage, + const char *src_func, const char *src_file, uint src_line); mysql_mutex_t LOCK_scheduler_state; diff --git a/sql/events.cc b/sql/events.cc index 008b6223702..187a3208d9f 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -1,5 +1,6 @@ /* Copyright (c) 2005, 2013, Oracle and/or its affiliates. + Copyright (c) 2017, 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 @@ -14,6 +15,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_parse.h" // check_access @@ -79,7 +81,8 @@ Event_queue *Events::event_queue; Event_scheduler *Events::scheduler; Event_db_repository *Events::db_repository; ulong Events::opt_event_scheduler= Events::EVENTS_OFF; -bool Events::check_system_tables_error= FALSE; +ulong Events::startup_state= Events::EVENTS_OFF; +ulong Events::inited; /* @@ -113,7 +116,7 @@ bool Events::check_if_system_tables_error() { DBUG_ENTER("Events::check_if_system_tables_error"); - if (check_system_tables_error) + if (!inited) { my_error(ER_EVENTS_DB_ERROR, MYF(0)); DBUG_RETURN(TRUE); @@ -258,10 +261,10 @@ common_1_lev_code: /** - Create a new query string for removing executable comments - for avoiding leak and keeping consistency of the execution + Create a new query string for removing executable comments + for avoiding leak and keeping consistency of the execution on master and slave. - + @param[in] thd Thread handler @param[in] buf Query string @@ -272,8 +275,14 @@ common_1_lev_code: static int create_query_string(THD *thd, String *buf) { + buf->length(0); /* Append the "CREATE" part of the query */ - if (buf->append(STRING_WITH_LEN("CREATE "))) + if (thd->lex->create_info.or_replace()) + { + if (buf->append(STRING_WITH_LEN("CREATE OR REPLACE "))) + return 1; + } + else if (buf->append(STRING_WITH_LEN("CREATE "))) return 1; /* Append definer */ append_definer(thd, buf, &(thd->lex->definer->user), &(thd->lex->definer->host)); @@ -282,7 +291,7 @@ create_query_string(THD *thd, String *buf) thd->lex->stmt_definition_end - thd->lex->stmt_definition_begin)) return 1; - + return 0; } @@ -292,8 +301,7 @@ create_query_string(THD *thd, String *buf) @param[in,out] thd THD @param[in] parse_data Event's data from parsing stage - @param[in] if_not_exists Whether IF NOT EXISTS was - specified + In case there is an event with the same name (db) and IF NOT EXISTS is specified, an warning is put into the stack. @sa Events::drop_event for the notes about locking, pre-locking @@ -304,11 +312,11 @@ create_query_string(THD *thd, String *buf) */ bool -Events::create_event(THD *thd, Event_parse_data *parse_data, - bool if_not_exists) +Events::create_event(THD *thd, Event_parse_data *parse_data) { bool ret; - bool save_binlog_row_based, event_already_exists; + bool event_already_exists; + enum_binlog_format save_binlog_format; DBUG_ENTER("Events::create_event"); if (check_if_system_tables_error()) @@ -327,6 +335,11 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, if (check_access(thd, EVENT_ACL, parse_data->dbname.str, NULL, NULL, 0, 0)) DBUG_RETURN(TRUE); + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + + if (lock_object_name(thd, MDL_key::EVENT, + parse_data->dbname.str, parse_data->name.str)) + DBUG_RETURN(TRUE); if (check_db_dir_existence(parse_data->dbname.str)) { @@ -336,19 +349,17 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, if (parse_data->do_not_create) DBUG_RETURN(FALSE); - /* - Turn off row binlogging of this statement and use statement-based + /* + Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for CREATE EVENT command. */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); - if (lock_object_name(thd, MDL_key::EVENT, - parse_data->dbname.str, parse_data->name.str)) - DBUG_RETURN(TRUE); + if (thd->lex->create_info.or_replace() && event_queue) + event_queue->drop_event(thd, parse_data->dbname, parse_data->name); /* On error conditions my_error() is called so no need to handle here */ - if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists, + if (!(ret= db_repository->create_event(thd, parse_data, &event_already_exists))) { Event_queue_element *new_element; @@ -382,11 +393,14 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, { /* Binlog the create event. */ DBUG_ASSERT(thd->query() && thd->query_length()); - String log_query; + char buffer[1024]; + String log_query(buffer, sizeof(buffer), &my_charset_bin); if (create_query_string(thd, &log_query)) { - sql_print_error("Event Error: An error occurred while creating query " - "string, before writing it into binary log."); + my_message_sql(ER_STARTUP, + "Event Error: An error occurred while creating query " + "string, before writing it into binary log.", + MYF(ME_NOREFRESH)); ret= true; } else @@ -400,12 +414,14 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, } } } - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + + thd->restore_stmt_binlog_format(save_binlog_format); DBUG_RETURN(ret); + +WSREP_ERROR_LABEL: + DBUG_RETURN(TRUE); + } @@ -433,7 +449,7 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, LEX_STRING *new_dbname, LEX_STRING *new_name) { int ret; - bool save_binlog_row_based; + enum_binlog_format save_binlog_format; Event_queue_element *new_element; DBUG_ENTER("Events::update_event"); @@ -447,6 +463,19 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, if (check_access(thd, EVENT_ACL, parse_data->dbname.str, NULL, NULL, 0, 0)) DBUG_RETURN(TRUE); + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); + + if (lock_object_name(thd, MDL_key::EVENT, + parse_data->dbname.str, parse_data->name.str)) + DBUG_RETURN(TRUE); + + if (check_db_dir_existence(parse_data->dbname.str)) + { + my_error(ER_BAD_DB_ERROR, MYF(0), parse_data->dbname.str); + DBUG_RETURN(TRUE); + } + + if (new_dbname) /* It's a rename */ { /* Check that the new and the old names differ. */ @@ -468,6 +497,13 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, if (check_access(thd, EVENT_ACL, new_dbname->str, NULL, NULL, 0, 0)) DBUG_RETURN(TRUE); + /* + Acquire mdl exclusive lock on target database name. + */ + if (lock_object_name(thd, MDL_key::EVENT, + new_dbname->str, new_name->str)) + DBUG_RETURN(TRUE); + /* Check that the target database exists */ if (check_db_dir_existence(new_dbname->str)) { @@ -476,16 +512,11 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, } } - /* - Turn off row binlogging of this statement and use statement-based + /* + Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for UPDATE EVENT command. */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - - if (lock_object_name(thd, MDL_key::EVENT, - parse_data->dbname.str, parse_data->name.str)) - DBUG_RETURN(TRUE); + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); /* On error conditions my_error() is called so no need to handle here */ if (!(ret= db_repository->update_event(thd, parse_data, @@ -515,12 +546,12 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } } - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + thd->restore_stmt_binlog_format(save_binlog_format); DBUG_RETURN(ret); + +WSREP_ERROR_LABEL: + DBUG_RETURN(TRUE); } @@ -552,7 +583,7 @@ bool Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) { int ret; - bool save_binlog_row_based; + enum_binlog_format save_binlog_format; DBUG_ENTER("Events::drop_event"); if (check_if_system_tables_error()) @@ -561,12 +592,13 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) if (check_access(thd, EVENT_ACL, dbname.str, NULL, NULL, 0, 0)) DBUG_RETURN(TRUE); + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); + /* Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for DROP EVENT command. */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); if (lock_object_name(thd, MDL_key::EVENT, dbname.str, name.str)) @@ -580,11 +612,12 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) DBUG_ASSERT(thd->query() && thd->query_length()); ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + + thd->restore_stmt_binlog_format(save_binlog_format); DBUG_RETURN(ret); + +WSREP_ERROR_LABEL: + DBUG_RETURN(TRUE); } @@ -607,6 +640,8 @@ Events::drop_schema_events(THD *thd, char *db) DBUG_ENTER("Events::drop_schema_events"); DBUG_PRINT("enter", ("dropping events from %s", db)); + DBUG_ASSERT(ok_for_lower_case_names(db)); + /* Sic: no check if the scheduler is disabled or system tables are damaged, as intended. @@ -632,36 +667,45 @@ send_show_create_event(THD *thd, Event_timed *et, Protocol *protocol) List<Item> field_list; LEX_STRING sql_mode; const String *tz_name; - + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("send_show_create_event"); show_str.length(0); if (et->get_create_event(thd, &show_str)) DBUG_RETURN(TRUE); - field_list.push_back(new Item_empty_string("Event", NAME_CHAR_LEN)); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Event", NAME_CHAR_LEN), + mem_root); if (sql_mode_string_representation(thd, et->sql_mode, &sql_mode)) DBUG_RETURN(TRUE); - field_list.push_back(new Item_empty_string("sql_mode", (uint) sql_mode.length)); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "sql_mode", + (uint) sql_mode.length), mem_root); tz_name= et->time_zone->get_name(); - field_list.push_back(new Item_empty_string("time_zone", - tz_name->length())); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "time_zone", tz_name->length()), + mem_root); - field_list.push_back(new Item_empty_string("Create Event", - show_str.length())); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Create Event", + show_str.length()), mem_root); - field_list.push_back( - new Item_empty_string("character_set_client", MY_CS_NAME_SIZE)); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "character_set_client", + MY_CS_NAME_SIZE), mem_root); - field_list.push_back( - new Item_empty_string("collation_connection", MY_CS_NAME_SIZE)); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "collation_connection", + MY_CS_NAME_SIZE), mem_root); - field_list.push_back( - new Item_empty_string("Database Collation", MY_CS_NAME_SIZE)); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Database Collation", + MY_CS_NAME_SIZE), mem_root); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) @@ -760,6 +804,13 @@ Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) int ret; DBUG_ENTER("Events::fill_schema_events"); + /* + If we didn't start events because of --skip-grant-tables, return an + empty set + */ + if (opt_noacl) + DBUG_RETURN(0); + if (check_if_system_tables_error()) DBUG_RETURN(1); @@ -775,6 +826,9 @@ Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) NULL, NULL, 0, 0)) DBUG_RETURN(1); db= thd->lex->select_lex.db; + + if (lower_case_table_names) + my_casedn_str(system_charset_info, db); } ret= db_repository->fill_schema_events(thd, tables, db); @@ -785,6 +839,7 @@ Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) /** Initializes the scheduler's structures. + @param THD or null (if called by init) @param opt_noacl_or_bootstrap TRUE if there is --skip-grant-tables or --bootstrap option. In that case we disable the event scheduler. @@ -792,34 +847,55 @@ Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) @note This function is not synchronized. @retval FALSE Perhaps there was an error, and the event scheduler - is disabled. But the error is not fatal and the + is disabled. But the error is not fatal and the server start up can continue. @retval TRUE Fatal error. Startup must terminate (call unireg_abort()). */ bool -Events::init(bool opt_noacl_or_bootstrap) +Events::init(THD *thd, bool opt_noacl_or_bootstrap) { - - THD *thd; int err_no; bool res= FALSE; - + bool had_thd= thd != 0; DBUG_ENTER("Events::init"); + DBUG_ASSERT(inited == 0); + + /* + Was disabled explicitly from the command line + */ + if (opt_event_scheduler == Events::EVENTS_DISABLED || + opt_noacl_or_bootstrap) + DBUG_RETURN(FALSE); + /* We need a temporary THD during boot */ - if (!(thd= new THD())) + if (!thd) { - res= TRUE; - goto end; + + if (!(thd= new THD())) + { + res= TRUE; + goto end; + } + /* + The thread stack does not start from this function but we cannot + guess the real value. So better some value that doesn't assert than + no value. + */ + thd->thread_stack= (char*) &thd; + thd->store_globals(); + /* + Set current time for the thread that handles events. + Current time is stored in data member start_time of THD class. + Subsequently, this value is used to check whether event was expired + when make loading events from storage. Check for event expiration time + is done at Event_queue_element::compute_next_execution_time() where + event's status set to Event_parse_data::DISABLED and dropped flag set + to true if event was expired. + */ + thd->set_time(); } - /* - The thread stack does not start from this function but we cannot - guess the real value. So better some value that doesn't assert than - no value. - */ - thd->thread_stack= (char*) &thd; - thd->store_globals(); /* We will need Event_db_repository anyway, even if the scheduler is @@ -840,28 +916,19 @@ Events::init(bool opt_noacl_or_bootstrap) are most likely not there and we're going to disable the event scheduler anyway. */ - if (opt_noacl_or_bootstrap || Event_db_repository::check_system_tables(thd)) + if (Event_db_repository::check_system_tables(thd)) { - if (! opt_noacl_or_bootstrap) - { - sql_print_error("Event Scheduler: An error occurred when initializing " - "system tables. Disabling the Event Scheduler."); - check_system_tables_error= TRUE; - } - + delete db_repository; + db_repository= 0; + my_message(ER_STARTUP, + "Event Scheduler: An error occurred when initializing " + "system tables. Disabling the Event Scheduler.", + MYF(ME_NOREFRESH)); /* Disable the scheduler since the system tables are not up to date */ - opt_event_scheduler= EVENTS_DISABLED; + opt_event_scheduler= EVENTS_OFF; goto end; } - /* - Was disabled explicitly from the command line, or because we're running - with --skip-grant-tables, or --bootstrap, or because we have no system - tables. - */ - if (opt_event_scheduler == Events::EVENTS_DISABLED) - goto end; - DBUG_ASSERT(opt_event_scheduler == Events::EVENTS_ON || opt_event_scheduler == Events::EVENTS_OFF); @@ -876,22 +943,20 @@ Events::init(bool opt_noacl_or_bootstrap) if (event_queue->init_queue(thd) || load_events_from_db(thd) || (opt_event_scheduler == EVENTS_ON && scheduler->start(&err_no))) { - sql_print_error("Event Scheduler: Error while loading from disk."); + my_message_sql(ER_STARTUP, + "Event Scheduler: Error while loading from mysql.event table.", + MYF(ME_NOREFRESH)); res= TRUE; /* fatal error: request unireg_abort */ goto end; } Event_worker_thread::init(db_repository); + inited= 1; end: if (res) - { - delete db_repository; - delete event_queue; - delete scheduler; - } - delete thd; - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, NULL); + deinit(); + if (!had_thd) + delete thd; DBUG_RETURN(res); } @@ -911,17 +976,14 @@ Events::deinit() { DBUG_ENTER("Events::deinit"); - if (opt_event_scheduler != EVENTS_DISABLED) - { - delete scheduler; - scheduler= NULL; /* safety */ - delete event_queue; - event_queue= NULL; /* safety */ - } - + delete scheduler; + scheduler= NULL; /* For restart */ + delete event_queue; + event_queue= NULL; /* For restart */ delete db_repository; - db_repository= NULL; /* safety */ + db_repository= NULL; /* For restart */ + inited= 0; DBUG_VOID_RETURN; } @@ -950,23 +1012,37 @@ static PSI_thread_info all_events_threads[]= { &key_thread_event_scheduler, "event_scheduler", PSI_FLAG_GLOBAL}, { &key_thread_event_worker, "event_worker", 0} }; +#endif /* HAVE_PSI_INTERFACE */ + +PSI_stage_info stage_waiting_on_empty_queue= { 0, "Waiting on empty queue", 0}; +PSI_stage_info stage_waiting_for_next_activation= { 0, "Waiting for next activation", 0}; +PSI_stage_info stage_waiting_for_scheduler_to_stop= { 0, "Waiting for the scheduler to stop", 0}; + +#ifdef HAVE_PSI_INTERFACE +PSI_stage_info *all_events_stages[]= +{ + & stage_waiting_on_empty_queue, + & stage_waiting_for_next_activation, + & stage_waiting_for_scheduler_to_stop +}; static void init_events_psi_keys(void) { const char* category= "sql"; int count; - if (PSI_server == NULL) - return; - count= array_elements(all_events_mutexes); - PSI_server->register_mutex(category, all_events_mutexes, count); + mysql_mutex_register(category, all_events_mutexes, count); count= array_elements(all_events_conds); - PSI_server->register_cond(category, all_events_conds, count); + mysql_cond_register(category, all_events_conds, count); count= array_elements(all_events_threads); - PSI_server->register_thread(category, all_events_threads, count); + mysql_thread_register(category, all_events_threads, count); + + count= array_elements(all_events_stages); + mysql_stage_register(category, all_events_stages, count); + } #endif /* HAVE_PSI_INTERFACE */ @@ -1010,7 +1086,7 @@ Events::dump_internal_status() holding LOCK_global_system_variables. */ mysql_mutex_lock(&LOCK_global_system_variables); - if (opt_event_scheduler == EVENTS_DISABLED) + if (!inited) puts("The Event Scheduler is disabled"); else { @@ -1024,11 +1100,13 @@ Events::dump_internal_status() bool Events::start(int *err_no) { + DBUG_ASSERT(inited); return scheduler->start(err_no); } bool Events::stop() { + DBUG_ASSERT(inited); return scheduler->stop(); } @@ -1058,7 +1136,6 @@ Events::load_events_from_db(THD *thd) bool ret= TRUE; uint count= 0; ulong saved_master_access; - DBUG_ENTER("Events::load_events_from_db"); DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd)); @@ -1066,18 +1143,26 @@ Events::load_events_from_db(THD *thd) NOTE: even if we run in read-only mode, we should be able to lock the mysql.event table for writing. In order to achieve this, we should call mysql_lock_tables() under the super user. + + Same goes for transaction access mode. + Temporarily reset it to read-write. */ saved_master_access= thd->security_ctx->master_access; thd->security_ctx->master_access |= SUPER_ACL; + bool save_tx_read_only= thd->tx_read_only; + thd->tx_read_only= false; ret= db_repository->open_event_table(thd, TL_WRITE, &table); + thd->tx_read_only= save_tx_read_only; thd->security_ctx->master_access= saved_master_access; if (ret) { - sql_print_error("Event Scheduler: Failed to open table mysql.event"); + my_message_sql(ER_STARTUP, + "Event Scheduler: Failed to open table mysql.event", + MYF(ME_NOREFRESH)); DBUG_RETURN(TRUE); } @@ -1090,8 +1175,7 @@ Events::load_events_from_db(THD *thd) while (!(read_record_info.read_record(&read_record_info))) { Event_queue_element *et; - bool created; - bool drop_on_completion; + bool created, dropped; if (!(et= new Event_queue_element)) goto end; @@ -1100,16 +1184,20 @@ Events::load_events_from_db(THD *thd) if (et->load_from_row(thd, table)) { - sql_print_error("Event Scheduler: " - "Error while loading events from mysql.event. " - "The table probably contains bad data or is corrupted"); + my_message(ER_STARTUP, + "Event Scheduler: " + "Error while loading events from mysql.event. " + "The table probably contains bad data or is corrupted", + MYF(ME_NOREFRESH)); delete et; goto end; } - drop_on_completion= (et->on_completion == - Event_parse_data::ON_COMPLETION_DROP); - - + /** + Since the Event_queue_element object could be deleted inside + Event_queue::create_event we should save the value of dropped flag + into the temporary variable. + */ + dropped= et->dropped; if (event_queue->create_event(thd, et, &created)) { /* Out of memory */ @@ -1118,7 +1206,7 @@ Events::load_events_from_db(THD *thd) } if (created) count++; - else if (drop_on_completion) + else if (dropped) { /* If not created, a stale event - drop if immediately if @@ -1137,9 +1225,12 @@ Events::load_events_from_db(THD *thd) } } } - if (global_system_variables.log_warnings) - sql_print_information("Event Scheduler: Loaded %d event%s", - count, (count == 1) ? "" : "s"); + my_printf_error(ER_STARTUP, + "Event Scheduler: Loaded %d event%s", + MYF(ME_NOREFRESH | + (global_system_variables.log_warnings) ? + ME_JUST_INFO: 0), + count, (count == 1) ? "" : "s"); ret= FALSE; end: @@ -1149,6 +1240,21 @@ end: DBUG_RETURN(ret); } +#ifdef WITH_WSREP +int wsrep_create_event_query(THD *thd, uchar** buf, size_t* buf_len) +{ + char buffer[1024]; + String log_query(buffer, sizeof(buffer), &my_charset_bin); + + if (create_query_string(thd, &log_query)) + { + WSREP_WARN("events create string failed: schema: %s, query: %s", + (thd->db ? thd->db : "(null)"), thd->query()); + return 1; + } + return wsrep_to_buf_helper(thd, log_query.ptr(), log_query.length(), buf, buf_len); +} +#endif /* WITH_WSREP */ /** @} (End of group Event_Scheduler) */ diff --git a/sql/events.h b/sql/events.h index 223f68c8370..368aa9a05d5 100644 --- a/sql/events.h +++ b/sql/events.h @@ -31,6 +31,11 @@ extern PSI_cond_key key_event_scheduler_COND_state; extern PSI_thread_key key_thread_event_scheduler, key_thread_event_worker; #endif /* HAVE_PSI_INTERFACE */ +/* Always defined, for SHOW PROCESSLIST. */ +extern PSI_stage_info stage_waiting_on_empty_queue; +extern PSI_stage_info stage_waiting_for_next_activation; +extern PSI_stage_info stage_waiting_for_scheduler_to_stop; + #include "sql_string.h" /* LEX_STRING */ #include "my_time.h" /* interval_type */ @@ -74,9 +79,11 @@ public: and the @@global.event_scheduler SQL variable. See sys_var.cc */ - enum enum_opt_event_scheduler { EVENTS_OFF, EVENTS_ON, EVENTS_DISABLED }; + enum enum_opt_event_scheduler { EVENTS_OFF, EVENTS_ON, EVENTS_DISABLED, + EVENTS_ORIGINAL }; /* Protected using LOCK_global_system_variables only. */ - static ulong opt_event_scheduler; + static ulong opt_event_scheduler, startup_state; + static ulong inited; static bool check_if_system_tables_error(); static bool start(int *err_no); static bool stop(); @@ -86,8 +93,7 @@ public: static Event_db_repository * get_db_repository() { return db_repository; } - static bool - init(bool opt_noacl); + static bool init(THD *thd, bool opt_noacl); static void deinit(); @@ -99,7 +105,7 @@ public: destroy_mutexes(); static bool - create_event(THD *thd, Event_parse_data *parse_data, bool if_exists); + create_event(THD *thd, Event_parse_data *parse_data); static bool update_event(THD *thd, Event_parse_data *parse_data, @@ -125,6 +131,11 @@ public: static void dump_internal_status(); + static void set_original_state(ulong startup_state_org) + { + startup_state= startup_state_org; + } + private: static bool @@ -134,8 +145,6 @@ private: static Event_queue *event_queue; static Event_scheduler *scheduler; static Event_db_repository *db_repository; - /* Set to TRUE if an error at start up */ - static bool check_system_tables_error; private: /* Prevent use of these */ diff --git a/sql/examples/CMakeLists.txt b/sql/examples/CMakeLists.txt deleted file mode 100644 index c4ea4c25679..00000000000 --- a/sql/examples/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2006, 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-1301 USA - -INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/sql - ${CMAKE_SOURCE_DIR}/extra/yassl/include - ${CMAKE_SOURCE_DIR}/regex) - -IF(WITH_EXAMPLE_STORAGE_ENGINE) -ADD_LIBRARY(example ha_example.cc) -ADD_DEPENDENCIES(example GenError) -ENDIF(WITH_EXAMPLE_STORAGE_ENGINE) diff --git a/sql/field.cc b/sql/field.cc index 28b3d743ed8..a4308a378b3 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -27,6 +27,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include "sql_select.h" #include "rpl_rli.h" // Pull in Relay_log_info @@ -41,7 +42,6 @@ #include "filesort.h" // change_double_for_sort #include "log_event.h" // class Table_map_log_event #include <m_ctype.h> -#include <errno.h> // Maximum allowed exponent value for converting string to decimal #define MAX_EXPONENT 1024 @@ -50,11 +50,6 @@ Instansiate templates and static variables *****************************************************************************/ -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class List<Create_field>; -template class List_iterator<Create_field>; -#endif - static const char *zero_timestamp="0000-00-00 00:00:00.000000"; /* number of bytes to store second_part part of the TIMESTAMP(N) */ @@ -73,10 +68,10 @@ const char field_separator=','; #define LONGLONG_TO_STRING_CONVERSION_BUFFER_SIZE 128 #define DECIMAL_TO_STRING_CONVERSION_BUFFER_SIZE 128 #define BLOB_PACK_LENGTH_TO_MAX_LENGH(arg) \ -((ulong) ((LL(1) << min(arg, 4) * 8) - LL(1))) + ((ulong) ((1LL << MY_MIN(arg, 4) * 8) - 1)) #define ASSERT_COLUMN_MARKED_FOR_READ DBUG_ASSERT(!table || (!table->read_set || bitmap_is_set(table->read_set, field_index))) -#define ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED DBUG_ASSERT(!table || (!table->write_set || bitmap_is_set(table->write_set, field_index) || bitmap_is_set(table->vcol_set, field_index))) +#define ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED DBUG_ASSERT(is_stat_field || !table || (!table->write_set || bitmap_is_set(table->write_set, field_index) || (table->vcol_set && bitmap_is_set(table->vcol_set, field_index)))) #define FLAGSTR(S,F) ((S) & (F) ? #F " " : "") @@ -92,6 +87,7 @@ const char field_separator=','; #define FIELDTYPE_NUM (FIELDTYPE_TEAR_FROM + (255 - FIELDTYPE_TEAR_TO)) static inline int field_type2index (enum_field_types field_type) { + field_type= real_type_to_type(field_type); return (field_type < FIELDTYPE_TEAR_FROM ? field_type : ((int)FIELDTYPE_TEAR_FROM) + (field_type - FIELDTYPE_TEAR_TO) - 1); @@ -952,8 +948,10 @@ static enum_field_types field_types_merge_rules [FIELDTYPE_NUM][FIELDTYPE_NUM]= enum_field_types Field::field_type_merge(enum_field_types a, enum_field_types b) { - DBUG_ASSERT(a < FIELDTYPE_TEAR_FROM || a > FIELDTYPE_TEAR_TO); - DBUG_ASSERT(b < FIELDTYPE_TEAR_FROM || b > FIELDTYPE_TEAR_TO); + DBUG_ASSERT(real_type_to_type(a) < FIELDTYPE_TEAR_FROM || + real_type_to_type(a) > FIELDTYPE_TEAR_TO); + DBUG_ASSERT(real_type_to_type(b) < FIELDTYPE_TEAR_FROM || + real_type_to_type(b) > FIELDTYPE_TEAR_TO); return field_types_merge_rules[field_type2index(a)] [field_type2index(b)]; } @@ -1047,8 +1045,8 @@ CPP_UNNAMED_NS_END Item_result Field::result_merge_type(enum_field_types field_type) { - DBUG_ASSERT(field_type < FIELDTYPE_TEAR_FROM || field_type - > FIELDTYPE_TEAR_TO); + DBUG_ASSERT(real_type_to_type(field_type) < FIELDTYPE_TEAR_FROM || + real_type_to_type(field_type) > FIELDTYPE_TEAR_TO); return field_types_result_type[field_type2index(field_type)]; } @@ -1057,37 +1055,6 @@ Item_result Field::result_merge_type(enum_field_types field_type) *****************************************************************************/ /** - Output a warning for erroneous conversion of strings to numerical - values. For use with ER_TRUNCATED_WRONG_VALUE[_FOR_FIELD] - - @param thd THD object - @param str pointer to string that failed to be converted - @param length length of string - @param cs charset for string - @param typestr string describing type converted to - @param error error value to output - @param field_name (for *_FOR_FIELD) name of field - @param row_num (for *_FOR_FIELD) row number - */ -static void push_numerical_conversion_warning(THD* thd, const char* str, - uint length, CHARSET_INFO* cs, - const char* typestr, int error, - const char* field_name="UNKNOWN", - ulong row_num=0) -{ - char buf[max(max(DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE, - LONGLONG_TO_STRING_CONVERSION_BUFFER_SIZE), - DECIMAL_TO_STRING_CONVERSION_BUFFER_SIZE)]; - - String tmp(buf, sizeof(buf), cs); - tmp.copy(str, length, cs); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - error, ER(error), typestr, tmp.c_ptr(), - field_name, row_num); -} - - -/** Check whether a field type can be partially indexed by a key. This is a static method, rather than a virtual function, because we need @@ -1135,6 +1102,227 @@ void Field::make_sort_key(uchar *buff,uint length) /** + @brief + Determine the relative position of the field value in a numeric interval + + @details + The function returns a double number between 0.0 and 1.0 as the relative + position of the value of the this field in the numeric interval of [min,max]. + If the value is not in the interval the the function returns 0.0 when + the value is less than min, and, 1.0 when the value is greater than max. + + @param min value of the left end of the interval + @param max value of the right end of the interval + + @return + relative position of the field value in the numeric interval [min,max] +*/ + +double Field::pos_in_interval_val_real(Field *min, Field *max) +{ + double n, d; + n= val_real() - min->val_real(); + if (n < 0) + return 0.0; + d= max->val_real() - min->val_real(); + if (d <= 0) + return 1.0; + return MY_MIN(n/d, 1.0); +} + + +static +inline ulonglong char_prefix_to_ulonglong(uchar *src) +{ + uint sz= sizeof(ulonglong); + for (uint i= 0; i < sz/2; i++) + { + uchar tmp= src[i]; + src[i]= src[sz-1-i]; + src[sz-1-i]= tmp; + } + return uint8korr(src); +} + +/* + Compute res = a - b, without losing precision and taking care that these are + unsigned numbers. +*/ +static inline double safe_substract(ulonglong a, ulonglong b) +{ + return (a > b)? double(a - b) : -double(b - a); +} + + +/** + @brief + Determine the relative position of the field value in a string interval + + @details + The function returns a double number between 0.0 and 1.0 as the relative + position of the value of the this field in the string interval of [min,max]. + If the value is not in the interval the the function returns 0.0 when + the value is less than min, and, 1.0 when the value is greater than max. + + @note + To calculate the relative position of the string value v in the interval + [min, max] the function first converts the beginning of these three + strings v, min, max into the strings that are used for byte comparison. + For each string not more sizeof(ulonglong) first bytes are taken + from the result of conversion. Then these bytes are interpreted as the + big-endian representation of an ulonglong integer. The values of these + integer numbers obtained for the strings v, min, max are used to calculate + the position of v in [min,max] in the same way is it's done for numeric + fields (see Field::pos_in_interval_val_real). + + @todo + Improve the procedure for the case when min and max have the same + beginning + + @param min value of the left end of the interval + @param max value of the right end of the interval + + @return + relative position of the field value in the string interval [min,max] +*/ + +double Field::pos_in_interval_val_str(Field *min, Field *max, uint data_offset) +{ + uchar mp_prefix[sizeof(ulonglong)]; + uchar minp_prefix[sizeof(ulonglong)]; + uchar maxp_prefix[sizeof(ulonglong)]; + ulonglong mp, minp, maxp; + my_strnxfrm(charset(), mp_prefix, sizeof(mp), + ptr + data_offset, + data_length()); + my_strnxfrm(charset(), minp_prefix, sizeof(minp), + min->ptr + data_offset, + min->data_length()); + my_strnxfrm(charset(), maxp_prefix, sizeof(maxp), + max->ptr + data_offset, + max->data_length()); + mp= char_prefix_to_ulonglong(mp_prefix); + minp= char_prefix_to_ulonglong(minp_prefix); + maxp= char_prefix_to_ulonglong(maxp_prefix); + double n, d; + n= safe_substract(mp, minp); + if (n < 0) + return 0.0; + d= safe_substract(maxp, minp); + if (d <= 0) + return 1.0; + return MY_MIN(n/d, 1.0); +} + + +bool Field::test_if_equality_guarantees_uniqueness(const Item *item) const +{ + DBUG_ASSERT(cmp_type() != STRING_RESULT); // For STRING_RESULT see Field_str + /* + We use result_type() rather than cmp_type() in the below condition, + because it covers a special case that string literals guarantee uniqueness + for temporal columns, so the query: + WHERE temporal_column='string' + cannot return multiple distinct temporal values. + QQ: perhaps we could allow INT/DECIMAL/DOUBLE types for temporal items. + */ + return result_type() == item->result_type(); +} + + +/** + Check whether a field item can be substituted for an equal item + + @details + The function checks whether a substitution of a field item for + an equal item is valid. + + @param arg *arg != NULL <-> the field is in the context + where substitution for an equal item is valid + + @note + The following statement is not always true: + @n + x=y => F(x)=F(x/y). + @n + This means substitution of an item for an equal item not always + yields an equavalent condition. Here's an example: + @code + 'a'='a ' + (LENGTH('a')=1) != (LENGTH('a ')=2) + @endcode + Such a substitution is surely valid if either the substituted + field is not of a STRING type or if it is an argument of + a comparison predicate. + + @retval + TRUE substitution is valid + @retval + FALSE otherwise +*/ + +bool Field::can_be_substituted_to_equal_item(const Context &ctx, + const Item_equal *item_equal) +{ + DBUG_ASSERT(item_equal->compare_type() != STRING_RESULT); + DBUG_ASSERT(cmp_type() != STRING_RESULT); + switch (ctx.subst_constraint()) { + case ANY_SUBST: + /* + Disable const propagation for items used in different comparison contexts. + This must be done because, for example, Item_hex_string->val_int() is not + the same as (Item_hex_string->val_str() in BINARY column)->val_int(). + We cannot simply disable the replacement in a particular context ( + e.g. <bin_col> = <int_col> AND <bin_col> = <hex_string>) since + Items don't know the context they are in and there are functions like + IF (<hex_string>, 'yes', 'no'). + */ + return ctx.compare_type() == item_equal->compare_type(); + case IDENTITY_SUBST: + return true; + } + return false; +} + + +/* + This handles all numeric and BIT data types. +*/ +bool Field::can_optimize_keypart_ref(const Item_bool_func *cond, + const Item *item) const +{ + DBUG_ASSERT(cmp_type() != STRING_RESULT); + DBUG_ASSERT(cmp_type() != TIME_RESULT); + return item->cmp_type() != TIME_RESULT; +} + + +/* + This handles all numeric and BIT data types. +*/ +bool Field::can_optimize_group_min_max(const Item_bool_func *cond, + const Item *const_item) const +{ + DBUG_ASSERT(cmp_type() != STRING_RESULT); + DBUG_ASSERT(cmp_type() != TIME_RESULT); + return const_item->cmp_type() != TIME_RESULT; +} + + +/* + This covers all numeric types, ENUM, SET, BIT +*/ +bool Field::can_optimize_range(const Item_bool_func *cond, + const Item *item, + bool is_eq_func) const +{ + DBUG_ASSERT(cmp_type() != TIME_RESULT); // Handled in Field_temporal + DBUG_ASSERT(cmp_type() != STRING_RESULT); // Handled in Field_longstr + return item->cmp_type() != TIME_RESULT; +} + + +/** Numeric fields base class constructor. */ Field_num::Field_num(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, @@ -1152,7 +1340,7 @@ Field_num::Field_num(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, } -void Field_num::prepend_zeros(String *value) +void Field_num::prepend_zeros(String *value) const { int diff; if ((diff= (int) (field_length - value->length())) > 0) @@ -1169,52 +1357,166 @@ void Field_num::prepend_zeros(String *value) } } + +Item *Field_num::get_equal_zerofill_const_item(THD *thd, const Context &ctx, + Item *const_item) +{ + switch (ctx.subst_constraint()) { + case IDENTITY_SUBST: + return NULL; // Not safe to propagate if not in comparison. See MDEV-8369. + case ANY_SUBST: + break; + } + DBUG_ASSERT(const_item->const_item()); + DBUG_ASSERT(ctx.compare_type() != STRING_RESULT); + return const_item; +} + + /** - Test if given number is a int. + Contruct warning parameters using thd->no_errors + to determine whether to generate or suppress warnings. + We can get here in a query like this: + SELECT COUNT(@@basedir); + from Item_func_get_system_var::update_null_value(). +*/ +Value_source::Warn_filter::Warn_filter(const THD *thd) + :m_want_warning_edom(!thd->no_errors), + m_want_note_truncated_spaces(!thd->no_errors) +{ } - @todo - Make this multi-byte-character safe - @param str String to test +/** + Check string-to-number conversion and produce a warning if + - could not convert any digits (EDOM-alike error) + - found garbage at the end of the string + - found trailing spaces (a note) + See also Field_num::check_edom_and_truncation() for a similar function. + + @param thd - the thread + @param filter - which warnings/notes are allowed + @param type - name of the data type (e.g. "INTEGER", "DECIMAL", "DOUBLE") + @param cs - character set of the original string + @param str - the original string + @param end - the end of the string + + Unlike Field_num::check_edom_and_truncation(), this function does not + distinguish between EDOM and truncation and reports the same warning for + both cases. Perhaps we should eventually print different warnings, to make + the explicit CAST work closer to the implicit cast in Field_xxx::store(). +*/ +void +Value_source::Converter_string_to_number::check_edom_and_truncation(THD *thd, + Warn_filter filter, + const char *type, + CHARSET_INFO *cs, + const char *str, + size_t length) const +{ + DBUG_ASSERT(str <= m_end_of_num); + DBUG_ASSERT(m_end_of_num <= str + length); + if (m_edom || (m_end_of_num < str + length && + !check_if_only_end_space(cs, m_end_of_num, str + length))) + { + // EDOM or important trailing data truncation + if (filter.want_warning_edom()) + { + /* + We can use err.ptr() here as ErrConvString is guranteed to put an + end \0 here. + */ + THD *wthd= thd ? thd : current_thd; + push_warning_printf(wthd, Sql_condition::WARN_LEVEL_WARN, + ER_TRUNCATED_WRONG_VALUE, + ER_THD(wthd, ER_TRUNCATED_WRONG_VALUE), type, + ErrConvString(str, length, cs).ptr()); + } + } + else if (m_end_of_num < str + length) + { + // Unimportant trailing data (spaces) truncation + if (filter.want_note_truncated_spaces()) + { + THD *wthd= thd ? thd : current_thd; + push_warning_printf(wthd, Sql_condition::WARN_LEVEL_NOTE, + ER_TRUNCATED_WRONG_VALUE, + ER_THD(wthd, ER_TRUNCATED_WRONG_VALUE), type, + ErrConvString(str, length, cs).ptr()); + } + } +} + + +/** + Check a string-to-number conversion routine result and generate warnings + in case when it: + - could not convert any digits + - found garbage at the end of the string. + + @param type Data type name (e.g. "decimal", "integer", "double") + @param edom Indicates that the string-to-number routine retuned + an error code equivalent to EDOM (value out of domain), + i.e. the string fully consisted of garbage and the + conversion routine could not get any digits from it. + @param str The original string @param length Length of 'str' - @param int_end Pointer to char after last used digit - @param cs Character set + @param cs Character set + @param end Pointer to char after last used digit @note - This is called after one has called strntoull10rnd() function. + This is called after one has called one of the following functions: + - strntoull10rnd() + - my_strntod() + - str2my_decimal() @retval - 0 OK + 0 OK @retval - 1 error: empty string or wrong integer. + 1 error: could not scan any digits (EDOM), + e.g. empty string, or garbage. @retval - 2 error: garbage at the end of string. + 2 error: scanned some digits, + but then found garbage at the end of the string. */ -int Field_num::check_int(CHARSET_INFO *cs, const char *str, int length, - const char *int_end, int error) + +int Field_num::check_edom_and_important_data_truncation(const char *type, + bool edom, + CHARSET_INFO *cs, + const char *str, + uint length, + const char *end) { - /* Test if we get an empty string or wrong integer */ - if (str == int_end || error == MY_ERRNO_EDOM) + /* Test if we get an empty string or garbage */ + if (edom) { ErrConvString err(str, length, cs); - push_warning_printf(table->in_use, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, - ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), - "integer", err.ptr(), field_name, - (ulong) table->in_use->warning_info->current_row_for_warning()); + set_warning_truncated_wrong_value(type, err.ptr()); return 1; } /* Test if we have garbage at the end of the given string. */ - if (test_if_important_data(cs, int_end, str + length)) + if (test_if_important_data(cs, end, str + length)) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); return 2; } return 0; } +int Field_num::check_edom_and_truncation(const char *type, bool edom, + CHARSET_INFO *cs, + const char *str, uint length, + const char *end) +{ + int rc= check_edom_and_important_data_truncation(type, edom, + cs, str, length, end); + if (!rc && end < str + length) + set_note(WARN_DATA_TRUNCATED, 1); + return rc; +} + + /* Conver a string to an integer then check bounds. @@ -1271,16 +1573,35 @@ bool Field_num::get_int(CHARSET_INFO *cs, const char *from, uint len, goto out_of_range; } } - if (table->in_use->count_cuted_fields && + if (get_thd()->count_cuted_fields && check_int(cs, from, len, end, error)) return 1; return 0; out_of_range: - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); return 1; } + +double Field_real::get_double(const char *str, uint length, CHARSET_INFO *cs, + int *error) +{ + char *end; + double nr= my_strntod(cs,(char*) str, length, &end, error); + if (*error) + { + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); + *error= 1; + } + else if (get_thd()->count_cuted_fields && + check_edom_and_truncation("double", str == end, + cs, str, length, end)) + *error= 1; + return nr; +} + + /** Process decimal library return codes and issue warnings for overflow and truncation. @@ -1297,12 +1618,12 @@ int Field::warn_if_overflow(int op_result) { if (op_result == E_DEC_OVERFLOW) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); return 1; } if (op_result == E_DEC_TRUNCATED) { - set_warning(MYSQL_ERROR::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1); + set_note(WARN_DATA_TRUNCATED, 1); /* We return 0 here as this is not a critical issue */ } return 0; @@ -1342,13 +1663,18 @@ Field::Field(uchar *ptr_arg,uint32 length_arg,uchar *null_ptr_arg, option_struct(0), key_start(0), part_of_key(0), part_of_key_not_clustered(0), part_of_sortkey(0), unireg_check(unireg_check_arg), field_length(length_arg), - null_bit(null_bit_arg), is_created_from_null_item(FALSE), vcol_info(0), + null_bit(null_bit_arg), is_created_from_null_item(FALSE), + read_stats(NULL), collected_stats(0), + vcol_info(0), stored_in_db(TRUE) { flags=null_ptr ? 0: NOT_NULL_FLAG; comment.str= (char*) ""; comment.length=0; - field_index= 0; + field_index= 0; + is_stat_field= FALSE; + cond_selectivity= 1.0; + next_equal_field= NULL; } @@ -1448,10 +1774,11 @@ int Field::store(const char *to, uint length, CHARSET_INFO *cs, enum_check_fields check_level) { int res; - enum_check_fields old_check_level= table->in_use->count_cuted_fields; - table->in_use->count_cuted_fields= check_level; + THD *thd= get_thd(); + enum_check_fields old_check_level= thd->count_cuted_fields; + thd->count_cuted_fields= check_level; res= store(to, length, cs); - table->in_use->count_cuted_fields= old_check_level; + thd->count_cuted_fields= old_check_level; return res; } @@ -1622,7 +1949,7 @@ longlong Field::convert_decimal2longlong(const my_decimal *val, { if (val->sign()) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); i= 0; *err= 1; } @@ -1666,7 +1993,7 @@ int Field_num::store_decimal(const my_decimal *val) ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; int err= 0; longlong i= convert_decimal2longlong(val, unsigned_flag, &err); - return test(err | store(i, unsigned_flag)); + return MY_TEST(err | store(i, unsigned_flag)); } @@ -1694,6 +2021,16 @@ my_decimal* Field_num::val_decimal(my_decimal *decimal_value) } +bool Field_num::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) +{ + ASSERT_COLUMN_MARKED_FOR_READ; + longlong nr= val_int(); + bool neg= !(flags & UNSIGNED_FLAG) && nr < 0; + return int_to_datetime_with_warn(neg, neg ? -nr : nr, ltime, fuzzydate, + field_name); +} + + Field_str::Field_str(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, utype unireg_check_arg, const char *field_name_arg, CHARSET_INFO *charset_arg) @@ -1708,6 +2045,49 @@ Field_str::Field_str(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, } +bool Field_str::test_if_equality_guarantees_uniqueness(const Item *item) const +{ + /* + Can't guarantee uniqueness when comparing a CHAR/VARCHAR/TEXT, + BINARY/VARBINARY/BLOB, ENUM,SET columns to an item with cmp_type() + of INT_RESULT, DOUBLE_RESULT, DECIMAL_RESULT or TIME_RESULT. + Example: + SELECT * FROM t1 WHERE varchar_column=DATE'2001-01-01' + return non-unuque values, e.g. '2001-01-01' and '2001-01-01x'. + */ + if (!field_charset->coll->propagate(field_charset, 0, 0) || + item->cmp_type() != STRING_RESULT) + return false; + /* + Can't guarantee uniqueness when comparing to + an item of a different collation. + Example: + SELECT * FROM t1 + WHERE latin1_bin_column = _latin1'A' COLLATE latin1_swedish_ci + return non-unique values 'a' and 'A'. + */ + DTCollation tmp(field_charset, field_derivation, repertoire()); + return !tmp.aggregate(item->collation) && tmp.collation == field_charset; +} + + +bool Field_str::can_be_substituted_to_equal_item(const Context &ctx, + const Item_equal *item_equal) +{ + DBUG_ASSERT(item_equal->compare_type() == STRING_RESULT); + switch (ctx.subst_constraint()) { + case ANY_SUBST: + return ctx.compare_type() == item_equal->compare_type() && + (ctx.compare_type() != STRING_RESULT || + ctx.compare_collation() == item_equal->compare_collation()); + case IDENTITY_SUBST: + return ((charset()->state & MY_CS_BINSORT) && + (charset()->state & MY_CS_NOPAD)); + } + return false; +} + + void Field_num::make_field(Send_field *field) { Field::make_field(field); @@ -1763,7 +2143,7 @@ uint Field::fill_cache_field(CACHE_FIELD *copy) if (flags & BLOB_FLAG) { copy->type= CACHE_BLOB; - copy->length-= table->s->blob_ptr_size; + copy->length-= portable_sizeof_char_ptr; return copy->length; } else if (!zero_pack() && @@ -1794,7 +2174,7 @@ bool Field::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) String tmp(buff,sizeof(buff),&my_charset_bin),*res; if (!(res=val_str(&tmp)) || str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(), - ltime, fuzzydate) <= MYSQL_TIMESTAMP_ERROR) + ltime, fuzzydate)) return 1; return 0; } @@ -1819,12 +2199,12 @@ int Field::store_time_dec(MYSQL_TIME *ltime, uint dec) bool Field::optimize_range(uint idx, uint part) { - return test(table->file->index_flags(idx, part, 1) & HA_READ_RANGE); + return MY_TEST(table->file->index_flags(idx, part, 1) & HA_READ_RANGE); } -Field *Field::new_field(MEM_ROOT *root, TABLE *new_table, - bool keep_type __attribute__((unused))) +Field *Field::make_new_field(MEM_ROOT *root, TABLE *new_table, + bool keep_type __attribute__((unused))) { Field *tmp; if (!(tmp= (Field*) memdup_root(root,(char*) this,size_of()))) @@ -1836,6 +2216,10 @@ Field *Field::new_field(MEM_ROOT *root, TABLE *new_table, tmp->key_start.init(0); tmp->part_of_key.init(0); tmp->part_of_sortkey.init(0); + /* + TODO: it is not clear why this method needs to reset unireg_check. + Try not to reset it, or explain why it needs to be reset. + */ tmp->unireg_check= Field::NONE; tmp->flags&= (NOT_NULL_FLAG | BLOB_FLAG | UNSIGNED_FLAG | ZEROFILL_FLAG | BINARY_FLAG | ENUM_FLAG | SET_FLAG); @@ -1845,11 +2229,11 @@ Field *Field::new_field(MEM_ROOT *root, TABLE *new_table, Field *Field::new_key_field(MEM_ROOT *root, TABLE *new_table, - uchar *new_ptr, uchar *new_null_ptr, - uint new_null_bit) + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit) { Field *tmp; - if ((tmp= new_field(root, new_table, table == new_table))) + if ((tmp= make_new_field(root, new_table, table == new_table))) { tmp->ptr= new_ptr; tmp->null_ptr= new_null_ptr; @@ -1874,6 +2258,32 @@ Field *Field::clone(MEM_ROOT *root, TABLE *new_table) } + +Field *Field::clone(MEM_ROOT *root, TABLE *new_table, my_ptrdiff_t diff, + bool stat_flag) +{ + Field *tmp; + if ((tmp= (Field*) memdup_root(root,(char*) this,size_of()))) + { + tmp->init(new_table); + tmp->move_field_offset(diff); + } + tmp->is_stat_field= stat_flag; + return tmp; +} + + +Field *Field::clone(MEM_ROOT *root, my_ptrdiff_t diff) +{ + Field *tmp; + if ((tmp= (Field*) memdup_root(root,(char*) this,size_of()))) + { + tmp->move_field_offset(diff); + } + return tmp; +} + + /**************************************************************************** Field_null, a field that always return NULL ****************************************************************************/ @@ -1901,7 +2311,7 @@ void Field_decimal::overflow(bool negative) uint len=field_length; uchar *to=ptr, filler= '9'; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); if (negative) { if (!unsigned_flag) @@ -1988,7 +2398,7 @@ int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs) uchar *left_wall,*right_wall; uchar tmp_char; /* - To remember if table->in_use->cuted_fields has already been incremented, + To remember if get_thd()->cuted_fields has already been incremented, to do that only once */ bool is_cuted_fields_incr=0; @@ -2009,7 +2419,7 @@ int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs) from++; if (from == end) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); is_cuted_fields_incr=1; } else if (*from == '+' || *from == '-') // Found some sign ? @@ -2079,13 +2489,13 @@ int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs) it makes the code easer to read. */ - if (table->in_use->count_cuted_fields) + if (get_thd()->count_cuted_fields) { // Skip end spaces for (;from != end && my_isspace(&my_charset_bin, *from); from++) ; if (from != end) // If still something left, warn { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); is_cuted_fields_incr=1; } } @@ -2122,7 +2532,7 @@ int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs) tmp_uint=tmp_dec+(uint)(int_digits_end-int_digits_from); else if (expo_sign_char == '-') { - tmp_uint=min(exponent,(uint)(int_digits_end-int_digits_from)); + tmp_uint=MY_MIN(exponent,(uint)(int_digits_end-int_digits_from)); frac_digits_added_zeros=exponent-tmp_uint; int_digits_end -= tmp_uint; frac_digits_head_end=int_digits_end+tmp_uint; @@ -2130,7 +2540,7 @@ int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs) } else // (expo_sign_char=='+') { - tmp_uint=min(exponent,(uint)(frac_digits_end-frac_digits_from)); + tmp_uint=MY_MIN(exponent,(uint)(frac_digits_end-frac_digits_from)); int_digits_added_zeros=exponent-tmp_uint; int_digits_tail_from=frac_digits_from; frac_digits_from=frac_digits_from+tmp_uint; @@ -2231,7 +2641,7 @@ int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs) /* Write digits of the frac_% parts ; - Depending on table->in_use->count_cutted_fields, we may also want + Depending on get_thd()->count_cutted_fields, we may also want to know if some non-zero tail of these parts will be truncated (for example, 0.002->0.00 will generate a warning, while 0.000->0.00 will not) @@ -2249,7 +2659,7 @@ int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs) { if (pos == right_wall) { - if (table->in_use->count_cuted_fields && !is_cuted_fields_incr) + if (get_thd()->count_cuted_fields && !is_cuted_fields_incr) break; // Go on below to see if we lose non zero digits return 0; } @@ -2263,8 +2673,7 @@ int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs) if (tmp_char != '0') // Losing a non zero digit ? { if (!is_cuted_fields_incr) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); return 0; } continue; @@ -2286,7 +2695,7 @@ int Field_decimal::store(const char *from_arg, uint len, CHARSET_INFO *cs) This is a note, not a warning, as we don't want to abort when we cut decimals in strict mode */ - set_warning(MYSQL_ERROR::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1); + set_note(WARN_DATA_TRUNCATED, 1); } return 0; } @@ -2531,7 +2940,7 @@ Field_new_decimal::Field_new_decimal(uint32 len_arg, } -Field *Field_new_decimal::create_from_item (Item *item) +Field *Field_new_decimal::create_from_item(MEM_ROOT *mem_root, Item *item) { uint8 dec= item->decimals; uint8 intg= item->decimal_precision() - dec; @@ -2549,7 +2958,7 @@ Field *Field_new_decimal::create_from_item (Item *item) { signed int overflow; - dec= min(dec, DECIMAL_MAX_SCALE); + dec= MY_MIN(dec, DECIMAL_MAX_SCALE); /* If the value still overflows the field with the corrected dec, @@ -2565,13 +2974,14 @@ Field *Field_new_decimal::create_from_item (Item *item) overflow= required_length - len; if (overflow > 0) - dec= max(0, dec - overflow); // too long, discard fract + dec= MY_MAX(0, dec - overflow); // too long, discard fract else /* Corrected value fits. */ len= required_length; } - return new Field_new_decimal(len, item->maybe_null, item->name, - dec, item->unsigned_flag); + return new (mem_root) + Field_new_decimal(len, item->maybe_null, item->name, + dec, item->unsigned_flag); } @@ -2612,7 +3022,8 @@ void Field_new_decimal::set_value_on_overflow(my_decimal *decimal_value, If it does, stores the decimal in the buffer using binary format. Otherwise sets maximal number that can be stored in the field. - @param decimal_value my_decimal + @param decimal_value my_decimal + @param [OUT] native_error the error returned by my_decimal2binary(). @retval 0 ok @@ -2620,7 +3031,8 @@ void Field_new_decimal::set_value_on_overflow(my_decimal *decimal_value, 1 error */ -bool Field_new_decimal::store_value(const my_decimal *decimal_value) +bool Field_new_decimal::store_value(const my_decimal *decimal_value, + int *native_error) { ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; int error= 0; @@ -2636,7 +3048,7 @@ bool Field_new_decimal::store_value(const my_decimal *decimal_value) if (unsigned_flag && decimal_value->sign()) { DBUG_PRINT("info", ("unsigned overflow")); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; decimal_value= &decimal_zero; } @@ -2649,11 +3061,14 @@ bool Field_new_decimal::store_value(const my_decimal *decimal_value) } #endif - if (warn_if_overflow(my_decimal2binary(E_DEC_FATAL_ERROR & ~E_DEC_OVERFLOW, - decimal_value, ptr, precision, dec))) + *native_error= my_decimal2binary(E_DEC_FATAL_ERROR & ~E_DEC_OVERFLOW, + decimal_value, ptr, precision, dec); + + if (*native_error == E_DEC_OVERFLOW) { my_decimal buff; DBUG_PRINT("info", ("overflow")); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); set_value_on_overflow(&buff, decimal_value->sign()); my_decimal2binary(E_DEC_FATAL_ERROR, &buff, ptr, precision, dec); error= 1; @@ -2664,49 +3079,68 @@ bool Field_new_decimal::store_value(const my_decimal *decimal_value) } +bool Field_new_decimal::store_value(const my_decimal *decimal_value) +{ + int native_error; + bool rc= store_value(decimal_value, &native_error); + if (!rc && native_error == E_DEC_TRUNCATED) + set_note(WARN_DATA_TRUNCATED, 1); + return rc; +} + + int Field_new_decimal::store(const char *from, uint length, CHARSET_INFO *charset_arg) { ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; - int err; my_decimal decimal_value; + THD *thd= get_thd(); DBUG_ENTER("Field_new_decimal::store(char*)"); - if ((err= str2my_decimal(E_DEC_FATAL_ERROR & - ~(E_DEC_OVERFLOW | E_DEC_BAD_NUM), + const char *end; + int err= str2my_decimal(E_DEC_FATAL_ERROR & + ~(E_DEC_OVERFLOW | E_DEC_BAD_NUM), from, length, charset_arg, - &decimal_value)) && - table->in_use->abort_on_warning) - { - ErrConvString errmsg(from, length, &my_charset_bin); - push_warning_printf(table->in_use, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, - ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), - "decimal", errmsg.ptr(), field_name, - (ulong) table->in_use->warning_info->current_row_for_warning()); + &decimal_value, &end); - DBUG_RETURN(err); + if (err == E_DEC_OVERFLOW) // Too many digits (>81) in the integer part + { + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); + if (!thd->abort_on_warning) + { + set_value_on_overflow(&decimal_value, decimal_value.sign()); + store_decimal(&decimal_value); + } + DBUG_RETURN(1); } - switch (err) { - case E_DEC_TRUNCATED: - set_warning(MYSQL_ERROR::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1); - break; - case E_DEC_OVERFLOW: - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); - set_value_on_overflow(&decimal_value, decimal_value.sign()); - break; - case E_DEC_BAD_NUM: + if (thd->count_cuted_fields) + { + if (check_edom_and_important_data_truncation("decimal", + err && err != E_DEC_TRUNCATED, + charset_arg, + from, length, end)) { - ErrConvString errmsg(from, length, &my_charset_bin); - push_warning_printf(table->in_use, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, - ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), - "decimal", errmsg.ptr(), field_name, - (ulong) table->in_use->warning_info-> - current_row_for_warning()); - my_decimal_set_zero(&decimal_value); - break; + if (!thd->abort_on_warning) + { + if (err && err != E_DEC_TRUNCATED) + { + /* + If check_decimal() failed because of EDOM-alike error, + (e.g. E_DEC_BAD_NUM), we have to initialize decimal_value to zero. + Note: if check_decimal() failed because of truncation, + decimal_value is alreay properly initialized. + */ + my_decimal_set_zero(&decimal_value); + /* + TODO: check str2my_decimal() with HF. It seems to do + decimal_make_zero() on fatal errors, so my_decimal_set_zero() + is probably not needed here. + */ + } + store_decimal(&decimal_value); + } + DBUG_RETURN(1); } } @@ -2715,8 +3149,22 @@ int Field_new_decimal::store(const char *from, uint length, DBUG_PRINT("enter", ("value: %s", dbug_decimal_as_string(dbug_buff, &decimal_value))); #endif - store_value(&decimal_value); - DBUG_RETURN(err); + int err2; + if (store_value(&decimal_value, &err2)) + DBUG_RETURN(1); + + /* + E_DEC_TRUNCATED means minor truncation, a note should be enough: + - in err: str2my_decimal() truncated '1e-1000000000000' to 0.0 + - in err2: store_value() truncated 1.123 to 1.12, e.g. for DECIMAL(10,2) + Also, we send a note if a string had some trailing spaces: '1.12 ' + */ + if (thd->count_cuted_fields && + (err == E_DEC_TRUNCATED || + err2 == E_DEC_TRUNCATED || + end < from + length)) + set_note(WARN_DATA_TRUNCATED, 1); + DBUG_RETURN(0); } @@ -2731,6 +3179,7 @@ int Field_new_decimal::store(double nr) ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; my_decimal decimal_value; int err; + THD *thd= get_thd(); DBUG_ENTER("Field_new_decimal::store(double)"); err= double2my_decimal(E_DEC_FATAL_ERROR & ~E_DEC_OVERFLOW, nr, @@ -2740,11 +3189,11 @@ int Field_new_decimal::store(double nr) if (check_overflow(err)) set_value_on_overflow(&decimal_value, decimal_value.sign()); /* Only issue a warning if store_value doesn't issue an warning */ - table->in_use->got_warning= 0; + thd->got_warning= 0; } if (store_value(&decimal_value)) err= 1; - else if (err && !table->in_use->got_warning) + else if (err && !thd->got_warning) err= warn_if_overflow(err); DBUG_RETURN(err); } @@ -2762,11 +3211,11 @@ int Field_new_decimal::store(longlong nr, bool unsigned_val) if (check_overflow(err)) set_value_on_overflow(&decimal_value, decimal_value.sign()); /* Only issue a warning if store_value doesn't issue an warning */ - table->in_use->got_warning= 0; + get_thd()->got_warning= 0; } if (store_value(&decimal_value)) err= 1; - else if (err && !table->in_use->got_warning) + else if (err && !get_thd()->got_warning) err= warn_if_overflow(err); return err; } @@ -2779,7 +3228,7 @@ int Field_new_decimal::store_decimal(const my_decimal *decimal_value) } -int Field_new_decimal::store_time_dec(MYSQL_TIME *ltime, uint dec) +int Field_new_decimal::store_time_dec(MYSQL_TIME *ltime, uint dec_arg) { my_decimal decimal_value; return store_value(date2my_decimal(ltime, &decimal_value)); @@ -2832,6 +3281,14 @@ String *Field_new_decimal::val_str(String *val_buffer, } +bool Field_new_decimal::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) +{ + my_decimal value; + return decimal_to_datetime_with_warn(val_decimal(&value), + ltime, fuzzydate, field_name); +} + + int Field_new_decimal::cmp(const uchar *a,const uchar*b) { return memcmp(a, b, bin_size); @@ -2975,7 +3432,41 @@ Field_new_decimal::unpack(uchar* to, const uchar *from, const uchar *from_end, return from+len; } -int Field_num::store_time_dec(MYSQL_TIME *ltime, uint dec) + +Item *Field_new_decimal::get_equal_const_item(THD *thd, const Context &ctx, + Item *const_item) +{ + if (flags & ZEROFILL_FLAG) + return Field_num::get_equal_zerofill_const_item(thd, ctx, const_item); + switch (ctx.subst_constraint()) { + case IDENTITY_SUBST: + if (const_item->field_type() != MYSQL_TYPE_NEWDECIMAL || + const_item->decimal_scale() != decimals()) + { + my_decimal *val, val_buffer, val_buffer2; + if (!(val= const_item->val_decimal(&val_buffer))) + { + DBUG_ASSERT(0); + return const_item; + } + /* + Truncate or extend the decimal value to the scale of the field. + See comments about truncation in the same place in + Field_time::get_equal_const_item(). + */ + my_decimal_round(E_DEC_FATAL_ERROR, val, decimals(), true, &val_buffer2); + return new (thd->mem_root) Item_decimal(thd, field_name, &val_buffer2, + decimals(), field_length); + } + break; + case ANY_SUBST: + break; + } + return const_item; +} + + +int Field_num::store_time_dec(MYSQL_TIME *ltime, uint dec_arg) { longlong v= TIME_to_ulonglong(ltime); if (ltime->neg == 0) @@ -3010,13 +3501,13 @@ int Field_tiny::store(double nr) if (nr < 0.0) { *ptr=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > 255.0) { *ptr= (uchar) 255; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3027,13 +3518,13 @@ int Field_tiny::store(double nr) if (nr < -128.0) { *ptr= (uchar) -128; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > 127.0) { *ptr=127; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3053,13 +3544,13 @@ int Field_tiny::store(longlong nr, bool unsigned_val) if (nr < 0 && !unsigned_val) { *ptr= 0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if ((ulonglong) nr > (ulonglong) 255) { *ptr= (char) 255; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3072,13 +3563,13 @@ int Field_tiny::store(longlong nr, bool unsigned_val) if (nr < -128) { *ptr= (char) -128; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > 127) { *ptr=127; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3112,7 +3603,7 @@ String *Field_tiny::val_str(String *val_buffer, ASSERT_COLUMN_MARKED_FOR_READ; CHARSET_INFO *cs= &my_charset_numeric; uint length; - uint mlength=max(field_length+1,5*cs->mbmaxlen); + uint mlength=MY_MAX(field_length+1,5*cs->mbmaxlen); val_buffer->alloc(mlength); char *to=(char*) val_buffer->ptr(); @@ -3189,13 +3680,13 @@ int Field_short::store(double nr) if (nr < 0) { res=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (double) UINT_MAX16) { res=(int16) UINT_MAX16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3206,13 +3697,13 @@ int Field_short::store(double nr) if (nr < (double) INT_MIN16) { res=INT_MIN16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (double) INT_MAX16) { res=INT_MAX16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3234,13 +3725,13 @@ int Field_short::store(longlong nr, bool unsigned_val) if (nr < 0L && !unsigned_val) { res=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if ((ulonglong) nr > (ulonglong) UINT_MAX16) { res=(int16) UINT_MAX16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3254,13 +3745,13 @@ int Field_short::store(longlong nr, bool unsigned_val) if (nr < INT_MIN16) { res=INT_MIN16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (longlong) INT_MAX16) { res=INT_MAX16; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3294,7 +3785,7 @@ String *Field_short::val_str(String *val_buffer, ASSERT_COLUMN_MARKED_FOR_READ; CHARSET_INFO *cs= &my_charset_numeric; uint length; - uint mlength=max(field_length+1,7*cs->mbmaxlen); + uint mlength=MY_MAX(field_length+1,7*cs->mbmaxlen); val_buffer->alloc(mlength); char *to=(char*) val_buffer->ptr(); short j; @@ -3377,14 +3868,14 @@ int Field_medium::store(double nr) if (nr < 0) { int3store(ptr,0); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr >= (double) (long) (1L << 24)) { uint32 tmp=(uint32) (1L << 24)-1L; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3396,14 +3887,14 @@ int Field_medium::store(double nr) { long tmp=(long) INT_MIN24; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (double) INT_MAX24) { long tmp=(long) INT_MAX24; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3423,14 +3914,14 @@ int Field_medium::store(longlong nr, bool unsigned_val) if (nr < 0 && !unsigned_val) { int3store(ptr,0); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if ((ulonglong) nr >= (ulonglong) (long) (1L << 24)) { long tmp= (long) (1L << 24)-1L; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3445,14 +3936,14 @@ int Field_medium::store(longlong nr, bool unsigned_val) { long tmp= (long) INT_MIN24; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else if (nr > (longlong) INT_MAX24) { long tmp=(long) INT_MAX24; int3store(ptr,tmp); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3484,7 +3975,7 @@ String *Field_medium::val_str(String *val_buffer, ASSERT_COLUMN_MARKED_FOR_READ; CHARSET_INFO *cs= &my_charset_numeric; uint length; - uint mlength=max(field_length+1,10*cs->mbmaxlen); + uint mlength=MY_MAX(field_length+1,10*cs->mbmaxlen); val_buffer->alloc(mlength); char *to=(char*) val_buffer->ptr(); long j= unsigned_flag ? (long) uint3korr(ptr) : sint3korr(ptr); @@ -3574,7 +4065,7 @@ int Field_long::store(double nr) else if (nr > (double) UINT_MAX32) { res= UINT_MAX32; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } else @@ -3596,7 +4087,7 @@ int Field_long::store(double nr) res=(int32) (longlong) nr; } if (error) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); int4store(ptr,res); return error; @@ -3616,7 +4107,7 @@ int Field_long::store(longlong nr, bool unsigned_val) res=0; error= 1; } - else if ((ulonglong) nr >= (LL(1) << 32)) + else if ((ulonglong) nr >= (1LL << 32)) { res=(int32) (uint32) ~0L; error= 1; @@ -3642,7 +4133,7 @@ int Field_long::store(longlong nr, bool unsigned_val) res=(int32) nr; } if (error) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); int4store(ptr,res); return error; @@ -3662,7 +4153,7 @@ longlong Field_long::val_int(void) ASSERT_COLUMN_MARKED_FOR_READ; int32 j; /* See the comment in Field_long::store(long long) */ - DBUG_ASSERT(table->in_use == current_thd); + DBUG_ASSERT(!table || table->in_use == current_thd); j=sint4korr(ptr); return unsigned_flag ? (longlong) (uint32) j : (longlong) j; } @@ -3673,7 +4164,7 @@ String *Field_long::val_str(String *val_buffer, ASSERT_COLUMN_MARKED_FOR_READ; CHARSET_INFO *cs= &my_charset_numeric; uint length; - uint mlength=max(field_length+1,12*cs->mbmaxlen); + uint mlength=MY_MAX(field_length+1,12*cs->mbmaxlen); val_buffer->alloc(mlength); char *to=(char*) val_buffer->ptr(); int32 j; @@ -3741,10 +4232,10 @@ int Field_longlong::store(const char *from,uint len,CHARSET_INFO *cs) tmp= cs->cset->strntoull10rnd(cs,from,len,unsigned_flag,&end,&error); if (error == MY_ERRNO_ERANGE) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } - else if (table->in_use->count_cuted_fields && + else if (get_thd()->count_cuted_fields && check_int(cs, from, len, end, error)) error= 1; else @@ -3763,7 +4254,7 @@ int Field_longlong::store(double nr) res= double_to_longlong(nr, unsigned_flag, &error); if (error) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); int8store(ptr,res); return error; @@ -3784,7 +4275,7 @@ int Field_longlong::store(longlong nr, bool unsigned_val) if (unsigned_flag != unsigned_val) { nr= unsigned_flag ? (ulonglong) 0 : (ulonglong) LONGLONG_MAX; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); error= 1; } } @@ -3823,7 +4314,7 @@ String *Field_longlong::val_str(String *val_buffer, { CHARSET_INFO *cs= &my_charset_numeric; uint length; - uint mlength=max(field_length+1,22*cs->mbmaxlen); + uint mlength=MY_MAX(field_length+1,22*cs->mbmaxlen); val_buffer->alloc(mlength); char *to=(char*) val_buffer->ptr(); longlong j; @@ -3893,16 +4384,7 @@ void Field_longlong::sql_type(String &res) const int Field_float::store(const char *from,uint len,CHARSET_INFO *cs) { int error; - char *end; - double nr= my_strntod(cs,(char*) from,len,&end,&error); - if (error || (!len || ((uint) (end-from) != len && - table->in_use->count_cuted_fields))) - { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, - (error ? ER_WARN_DATA_OUT_OF_RANGE : WARN_DATA_TRUNCATED), 1); - error= error ? 1 : 2; - } - Field_float::store(nr); + Field_float::store(get_double(from, len, cs, &error)); return error; } @@ -3915,7 +4397,7 @@ int Field_float::store(double nr) unsigned_flag, FLT_MAX); if (error) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); if (error < 0) // Wrong double value { error= 1; @@ -4082,16 +4564,7 @@ void Field_float::sql_type(String &res) const int Field_double::store(const char *from,uint len,CHARSET_INFO *cs) { int error; - char *end; - double nr= my_strntod(cs,(char*) from, len, &end, &error); - if (error || (!len || ((uint) (end-from) != len && - table->in_use->count_cuted_fields))) - { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, - (error ? ER_WARN_DATA_OUT_OF_RANGE : WARN_DATA_TRUNCATED), 1); - error= error ? 1 : 2; - } - Field_double::store(nr); + Field_double::store(get_double(from, len, cs, &error)); return error; } @@ -4104,7 +4577,7 @@ int Field_double::store(double nr) unsigned_flag, DBL_MAX); if (error) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); if (error < 0) // Wrong double value { error= 1; @@ -4243,7 +4716,7 @@ int Field_real::store_decimal(const my_decimal *dm) return store(dbl); } -int Field_real::store_time_dec(MYSQL_TIME *ltime, uint dec) +int Field_real::store_time_dec(MYSQL_TIME *ltime, uint dec_arg) { return store(TIME_to_double(ltime)); } @@ -4257,7 +4730,7 @@ double Field_double::val_real(void) return j; } -longlong Field_double::val_int(void) +longlong Field_double::val_int_from_real(bool want_unsigned_result) { ASSERT_COLUMN_MARKED_FOR_READ; double j; @@ -4265,13 +4738,21 @@ longlong Field_double::val_int(void) bool error; float8get(j,ptr); - res= double_to_longlong(j, 0, &error); - if (error) + res= double_to_longlong(j, want_unsigned_result, &error); + /* + Note, val_uint() is currently used for auto_increment purposes only, + and we want to suppress all warnings in such cases. + If we ever start using val_uint() for other purposes, + val_int_from_real() will need a new separate parameter to + suppress warnings. + */ + if (error && !want_unsigned_result) { + THD *thd= get_thd(); ErrConvDouble err(j); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), "INTEGER", + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), "INTEGER", err.ptr()); } return res; @@ -4294,6 +4775,26 @@ bool Field_real::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) } +Item *Field_real::get_equal_const_item(THD *thd, const Context &ctx, + Item *const_item) +{ + if (flags & ZEROFILL_FLAG) + return Field_num::get_equal_zerofill_const_item(thd, ctx, const_item); + switch (ctx.subst_constraint()) { + case IDENTITY_SUBST: + if (const_item->decimal_scale() != Field_real::decimals()) + { + double val= const_item->val_real(); + return new (thd->mem_root) Item_float(thd, val, Field_real::decimals()); + } + break; + case ANY_SUBST: + break; + } + return const_item; +} + + String *Field_double::val_str(String *val_buffer, String *val_ptr __attribute__((unused))) { @@ -4389,16 +4890,10 @@ void Field_double::sql_type(String &res) const 2038-01-01 00:00:00 UTC stored as number of seconds since Unix Epoch in UTC. - Up to one of timestamps columns in the table can be automatically - set on row update and/or have NOW() as default value. - TABLE::timestamp_field points to Field object for such timestamp with - auto-set-on-update. TABLE::time_stamp holds offset in record + 1 for this - field, and is used by handler code which performs updates required. - Actually SQL-99 says that we should allow niladic functions (like NOW()) - as defaults for any field. Current limitations (only NOW() and only - for one TIMESTAMP field) are because of restricted binary .frm format - and should go away in the future. + as defaults for any field. The current limitation (only NOW() and only + for TIMESTAMP and DATETIME fields) are because of restricted binary .frm + format and should go away in the future. Also because of this limitation of binary .frm format we use 5 different unireg_check values with TIMESTAMP field to distinguish various cases of @@ -4432,17 +4927,18 @@ Field_timestamp::Field_timestamp(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, - TABLE_SHARE *share, - CHARSET_INFO *cs) - :Field_str(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) + TABLE_SHARE *share) + :Field_temporal(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) { /* For 4.0 MYD and 4.0 InnoDB compatibility */ - flags|= UNSIGNED_FLAG | BINARY_FLAG; - if (unireg_check != NONE && !share->timestamp_field) + flags|= UNSIGNED_FLAG; + if (unireg_check != NONE) { - /* This timestamp has auto-update */ - share->timestamp_field= this; + /* + We mark the flag with TIMESTAMP_FLAG to indicate to the client that + this field will be automaticly updated on insert. + */ flags|= TIMESTAMP_FLAG; if (unireg_check != TIMESTAMP_DN_FIELD) flags|= ON_UPDATE_NOW_FLAG; @@ -4450,61 +4946,34 @@ Field_timestamp::Field_timestamp(uchar *ptr_arg, uint32 len_arg, } -/** - Get auto-set type for TIMESTAMP field. - - Returns value indicating during which operations this TIMESTAMP field - should be auto-set to current timestamp. -*/ -timestamp_auto_set_type Field_timestamp::get_auto_set_type() const -{ - switch (unireg_check) - { - case TIMESTAMP_DN_FIELD: - return TIMESTAMP_AUTO_SET_ON_INSERT; - case TIMESTAMP_UN_FIELD: - return TIMESTAMP_AUTO_SET_ON_UPDATE; - case TIMESTAMP_OLD_FIELD: - /* - Although we can have several such columns in legacy tables this - function should be called only for first of them (i.e. the one - having auto-set property). - */ - DBUG_ASSERT(table->timestamp_field == this); - /* Fall-through */ - case TIMESTAMP_DNUN_FIELD: - return TIMESTAMP_AUTO_SET_ON_BOTH; - default: - /* - Normally this function should not be called for TIMESTAMPs without - auto-set property. - */ - DBUG_ASSERT(0); - return TIMESTAMP_NO_AUTO_SET; - } -} - -my_time_t Field_timestamp::get_timestamp(ulong *sec_part) const +my_time_t Field_timestamp::get_timestamp(const uchar *pos, + ulong *sec_part) const { ASSERT_COLUMN_MARKED_FOR_READ; *sec_part= 0; - return sint4korr(ptr); + return sint4korr(pos); } int Field_timestamp::store_TIME_with_warning(THD *thd, MYSQL_TIME *l_time, const ErrConv *str, - bool was_cut, + int was_cut, bool have_smth_to_conv) { ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; uint error = 0; my_time_t timestamp; - if (was_cut || !have_smth_to_conv) + if (MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) || !have_smth_to_conv) { error= 1; - set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, + set_datetime_warning(WARN_DATA_TRUNCATED, + str, MYSQL_TIMESTAMP_DATETIME, 1); + } + else if (MYSQL_TIME_WARN_HAVE_NOTES(was_cut)) + { + error= 3; + set_datetime_warning(Sql_condition::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, str, MYSQL_TIMESTAMP_DATETIME, 1); } /* Only convert a correct date (not a zero date) */ @@ -4516,7 +4985,7 @@ int Field_timestamp::store_TIME_with_warning(THD *thd, MYSQL_TIME *l_time, conversion_error= ER_WARN_DATA_OUT_OF_RANGE; if (conversion_error) { - set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, conversion_error, + set_datetime_warning(conversion_error, str, MYSQL_TIMESTAMP_DATETIME, !error); error= 1; } @@ -4531,13 +5000,24 @@ int Field_timestamp::store_TIME_with_warning(THD *thd, MYSQL_TIME *l_time, } +static bool +copy_or_convert_to_datetime(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to) +{ + if (from->time_type == MYSQL_TIMESTAMP_TIME) + return time_to_datetime(thd, from, to); + *to= *from; + return false; +} + + int Field_timestamp::store_time_dec(MYSQL_TIME *ltime, uint dec) { - THD *thd= table->in_use; int unused; - MYSQL_TIME l_time= *ltime; ErrConvTime str(ltime); - bool valid= !check_date(&l_time, pack_time(&l_time) != 0, + THD *thd= get_thd(); + MYSQL_TIME l_time; + bool valid= !copy_or_convert_to_datetime(thd, ltime, &l_time) && + !check_date(&l_time, pack_time(&l_time) != 0, (thd->variables.sql_mode & MODE_NO_ZERO_DATE) | MODE_NO_ZERO_IN_DATE, &unused); @@ -4548,18 +5028,18 @@ int Field_timestamp::store_time_dec(MYSQL_TIME *ltime, uint dec) int Field_timestamp::store(const char *from,uint len,CHARSET_INFO *cs) { MYSQL_TIME l_time; - int error; - int have_smth_to_conv; + MYSQL_TIME_STATUS status; + bool have_smth_to_conv; ErrConvString str(from, len, cs); - THD *thd= table->in_use; + THD *thd= get_thd(); /* We don't want to store invalid or fuzzy datetime values in TIMESTAMP */ - have_smth_to_conv= (str_to_datetime(cs, from, len, &l_time, + have_smth_to_conv= !str_to_datetime(cs, from, len, &l_time, (thd->variables.sql_mode & MODE_NO_ZERO_DATE) | - MODE_NO_ZERO_IN_DATE, &error) > - MYSQL_TIMESTAMP_ERROR); - return store_TIME_with_warning(thd, &l_time, &str, error, have_smth_to_conv); + MODE_NO_ZERO_IN_DATE, &status); + return store_TIME_with_warning(thd, &l_time, &str, + status.warnings, have_smth_to_conv); } @@ -4568,7 +5048,7 @@ int Field_timestamp::store(double nr) MYSQL_TIME l_time; int error; ErrConvDouble str(nr); - THD *thd= table->in_use; + THD *thd= get_thd(); longlong tmp= double_to_datetime(nr, &l_time, (thd->variables.sql_mode & MODE_NO_ZERO_DATE) | @@ -4582,13 +5062,30 @@ int Field_timestamp::store(longlong nr, bool unsigned_val) MYSQL_TIME l_time; int error; ErrConvInteger str(nr, unsigned_val); - THD *thd= table->in_use; + THD *thd= get_thd(); /* We don't want to store invalid or fuzzy datetime values in TIMESTAMP */ longlong tmp= number_to_datetime(nr, 0, &l_time, (thd->variables.sql_mode & MODE_NO_ZERO_DATE) | MODE_NO_ZERO_IN_DATE, &error); - return store_TIME_with_warning(thd, &l_time, &str, error, tmp != LL(-1)); + return store_TIME_with_warning(thd, &l_time, &str, error, tmp != -1); +} + + +int Field_timestamp::store_timestamp(Field_timestamp *from) +{ + ulong sec_part; + my_time_t ts= from->get_timestamp(&sec_part); + store_TIME(ts, sec_part); + if (!ts && !sec_part && get_thd()->variables.sql_mode & MODE_NO_ZERO_DATE) + { + ErrConvString s( + STRING_WITH_LEN("0000-00-00 00:00:00.000000") - (decimals() ? 6 - decimals() : 7), + system_charset_info); + set_datetime_warning(WARN_DATA_TRUNCATED, &s, MYSQL_TIMESTAMP_DATETIME, 1); + return 1; + } + return 0; } @@ -4614,6 +5111,7 @@ String *Field_timestamp::val_str(String *val_buffer, String *val_ptr) { MYSQL_TIME ltime; uint32 temp, temp2; + uint dec; char *to; val_buffer->alloc(field_length+1); @@ -4668,13 +5166,33 @@ String *Field_timestamp::val_str(String *val_buffer, String *val_ptr) *to++= (char) ('0'+(char) (temp)); *to= 0; val_buffer->set_charset(&my_charset_numeric); + + if ((dec= decimals())) + { + ulong sec_part= (ulong) sec_part_shift(ltime.second_part, dec); + char *buf= const_cast<char*>(val_buffer->ptr() + MAX_DATETIME_WIDTH); + for (int i= dec; i > 0; i--, sec_part/= 10) + buf[i]= (char)(sec_part % 10) + '0'; + buf[0]= '.'; + buf[dec + 1]= 0; + } return val_buffer; } +bool +Field_timestamp::validate_value_in_record(THD *thd, const uchar *record) const +{ + DBUG_ASSERT(!is_null_in_record(record)); + ulong sec_part; + return !get_timestamp(ptr_in_record(record), &sec_part) && !sec_part && + (sql_mode_for_dates(thd) & TIME_NO_ZERO_DATE) != 0; +} + + bool Field_timestamp::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) { - THD *thd= table->in_use; + THD *thd= get_thd(); thd->time_zone_used= 1; ulong sec_part; my_time_t temp= get_timestamp(&sec_part); @@ -4682,7 +5200,7 @@ bool Field_timestamp::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) { /* Zero time is "000000" */ if (fuzzydate & TIME_NO_ZERO_DATE) return 1; - bzero((char*) ltime,sizeof(*ltime)); + set_zero_time(ltime, MYSQL_TIMESTAMP_DATETIME); } else { @@ -4721,23 +5239,50 @@ void Field_timestamp::sort_string(uchar *to,uint length __attribute__((unused))) void Field_timestamp::sql_type(String &res) const { - res.set_ascii(STRING_WITH_LEN("timestamp")); + if (!decimals()) + { + res.set_ascii(STRING_WITH_LEN("timestamp")); + return; + } + CHARSET_INFO *cs=res.charset(); + res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), + "timestamp(%u)", decimals())); } int Field_timestamp::set_time() { - THD *thd= table->in_use; set_notnull(); - store_TIME(thd->query_start(), 0); + store_TIME(get_thd()->query_start(), 0); return 0; } -void Field_timestamp_hires::sql_type(String &res) const +/** + Mark the field as having an explicit default value. + + @param value if available, the value that the field is being set to + + @note + Fields that have an explicit default value should not be updated + automatically via the DEFAULT or ON UPDATE functions. The functions + that deal with data change functionality (INSERT/UPDATE/LOAD), + determine if there is an explicit value for each field before performing + the data change, and call this method to mark the field. + + For timestamp columns, the only case where a column is not marked + as been given a value are: + - It's explicitly assigned with DEFAULT + - We assign NULL to a timestamp field that is defined as NOT NULL. + This is how MySQL has worked since it's start. +*/ + +void Field_timestamp::set_explicit_default(Item *value) { - CHARSET_INFO *cs=res.charset(); - res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), - "timestamp(%u)", dec)); + if (((value->type() == Item::DEFAULT_VALUE_ITEM && + !((Item_default_value*)value)->arg) || + (!maybe_null() && value->null_value))) + return; + set_has_explicit_value(); } #ifdef NOT_USED @@ -4826,14 +5371,15 @@ void Field_timestamp_hires::store_TIME(my_time_t timestamp, ulong sec_part) store_bigendian(sec_part_shift(sec_part, dec), ptr+4, sec_part_bytes[dec]); } -my_time_t Field_timestamp_hires::get_timestamp(ulong *sec_part) const +my_time_t Field_timestamp_hires::get_timestamp(const uchar *pos, + ulong *sec_part) const { ASSERT_COLUMN_MARKED_FOR_READ; - *sec_part= (long)sec_part_unshift(read_bigendian(ptr+4, sec_part_bytes[dec]), dec); - return mi_uint4korr(ptr); + *sec_part= (long)sec_part_unshift(read_bigendian(pos+4, sec_part_bytes[dec]), dec); + return mi_uint4korr(pos); } -double Field_timestamp_hires::val_real(void) +double Field_timestamp_with_dec::val_real(void) { MYSQL_TIME ltime; if (get_date(<ime, TIME_NO_ZERO_DATE)) @@ -4844,39 +5390,21 @@ double Field_timestamp_hires::val_real(void) ltime.minute * 1e2 + ltime.second + ltime.second_part*1e-6; } -String *Field_timestamp_hires::val_str(String *val_buffer, String *val_ptr) -{ - String *tmp= Field_timestamp::val_str(val_buffer, val_ptr); - ulong sec_part= (ulong)read_bigendian(ptr+4, sec_part_bytes[dec]); - - if (tmp->ptr() == zero_timestamp) - return tmp; - - char *buf= const_cast<char*>(tmp->ptr() + MAX_DATETIME_WIDTH); - for (int i=dec; i>0; i--, sec_part/=10) - buf[i]= (char)(sec_part % 10) + '0'; - buf[0]= '.'; - buf[dec+1]= 0; - return tmp; -} - - -my_decimal *Field_timestamp_hires::val_decimal(my_decimal *d) +my_decimal *Field_timestamp_with_dec::val_decimal(my_decimal *d) { MYSQL_TIME ltime; get_date(<ime, 0); - longlong intg= TIME_to_ulonglong(<ime); - return seconds2my_decimal(ltime.neg, intg, ltime.second_part, d); + return TIME_to_my_decimal(<ime, d); } -int Field_timestamp_hires::store_decimal(const my_decimal *d) +int Field_timestamp::store_decimal(const my_decimal *d) { ulonglong nr; ulong sec_part; int error; MYSQL_TIME ltime; longlong tmp; - THD *thd= table->in_use; + THD *thd= get_thd(); ErrConvDecimal str(d); if (my_decimal2seconds(d, &nr, &sec_part)) @@ -4886,21 +5414,22 @@ int Field_timestamp_hires::store_decimal(const my_decimal *d) } else tmp= number_to_datetime(nr, sec_part, <ime, TIME_NO_ZERO_IN_DATE | - (thd->variables.sql_mode & - MODE_NO_ZERO_DATE), &error); + (thd->variables.sql_mode & + MODE_NO_ZERO_DATE), &error); return store_TIME_with_warning(thd, <ime, &str, error, tmp != -1); } -int Field_timestamp_hires::set_time() +int Field_timestamp_with_dec::set_time() { - THD *thd= table->in_use; + THD *thd= get_thd(); set_notnull(); - store_TIME(thd->query_start(), thd->query_start_sec_part()); + // Avoid writing microseconds into binlog for FSP=0 + store_TIME(thd->query_start(), decimals() ? thd->query_start_sec_part() : 0); return 0; } -bool Field_timestamp_hires::send_binary(Protocol *protocol) +bool Field_timestamp_with_dec::send_binary(Protocol *protocol) { MYSQL_TIME ltime; Field_timestamp::get_date(<ime, 0); @@ -4921,23 +5450,73 @@ int Field_timestamp_hires::cmp(const uchar *a_ptr, const uchar *b_ptr) } -void Field_timestamp_hires::sort_string(uchar *to,uint length) -{ - DBUG_ASSERT(length == Field_timestamp_hires::pack_length()); - memcpy(to, ptr, length); -} - uint32 Field_timestamp_hires::pack_length() const { return 4 + sec_part_bytes[dec]; } -void Field_timestamp_hires::make_field(Send_field *field) +void Field_timestamp_with_dec::make_field(Send_field *field) { Field::make_field(field); field->decimals= dec; } + +/************************************************************* +** MySQL-5.6 compatible TIMESTAMP(N) +**************************************************************/ + +void Field_timestampf::store_TIME(my_time_t timestamp, ulong sec_part) +{ + struct timeval tm; + tm.tv_sec= timestamp; + tm.tv_usec= sec_part; + my_timeval_trunc(&tm, dec); + my_timestamp_to_binary(&tm, ptr, dec); +} + + +my_time_t Field_timestampf::get_timestamp(const uchar *pos, + ulong *sec_part) const +{ + struct timeval tm; + my_timestamp_from_binary(&tm, pos, dec); + *sec_part= tm.tv_usec; + return tm.tv_sec; +} + + +/*************************************************************/ +uint Field_temporal::is_equal(Create_field *new_field) +{ + return new_field->sql_type == real_type() && + new_field->length == max_display_length(); +} + + +void Field_temporal::set_warnings(Sql_condition::enum_warning_level trunc_level, + const ErrConv *str, int was_cut, + timestamp_type ts_type) +{ + /* + error code logic: + MYSQL_TIME_WARN_TRUNCATED means that the value was not a date/time at all. + it will be stored as zero date/time. + MYSQL_TIME_WARN_OUT_OF_RANGE means that the value was a date/time, + that is, it was parsed as such, but the value was invalid. + + Also, MYSQL_TIME_WARN_TRUNCATED is used when storing a DATETIME in + a DATE field and non-zero time part is thrown away. + */ + if (was_cut & MYSQL_TIME_WARN_TRUNCATED) + set_datetime_warning(trunc_level, WARN_DATA_TRUNCATED, + str, mysql_type_to_time_type(type()), 1); + if (was_cut & MYSQL_TIME_WARN_OUT_OF_RANGE) + set_datetime_warning(ER_WARN_DATA_OUT_OF_RANGE, + str, mysql_type_to_time_type(type()), 1); +} + + /* Store string into a date/time field @@ -4948,151 +5527,119 @@ void Field_timestamp_hires::make_field(Send_field *field) 3 Datetime value that was cut (warning level NOTE) This is used by opt_range.cc:get_mm_leaf(). */ -int Field_temporal::store_TIME_with_warning(MYSQL_TIME *ltime, - const ErrConv *str, - int was_cut, int have_smth_to_conv) +int Field_temporal_with_date::store_TIME_with_warning(MYSQL_TIME *ltime, + const ErrConv *str, + int was_cut, + int have_smth_to_conv) { - MYSQL_ERROR::enum_warning_level trunc_level= MYSQL_ERROR::WARN_LEVEL_WARN; + Sql_condition::enum_warning_level trunc_level= Sql_condition::WARN_LEVEL_WARN; int ret= 2; ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; -#if MARIADB_VERSION_ID < 1000000 - /* - Check if the YYYYMMDD part was truncated. - Translate a note into a warning. - In MariaDB-10.0 we have a better warnings/notes handling, - so this code is not needed. - */ - if (was_cut & MYSQL_TIME_NOTE_TRUNCATED) - was_cut|= MYSQL_TIME_WARN_TRUNCATED; -#endif - - if (was_cut == 0 && - have_smth_to_conv == 0 && - mysql_type_to_time_type(type()) != MYSQL_TIMESTAMP_TIME) // special case: zero date + if (was_cut == 0 && have_smth_to_conv == 0) // special case: zero date + { was_cut= MYSQL_TIME_WARN_OUT_OF_RANGE; - else - if (!have_smth_to_conv) + } + else if (!have_smth_to_conv) { bzero(ltime, sizeof(*ltime)); was_cut= MYSQL_TIME_WARN_TRUNCATED; ret= 1; } - else if (!(was_cut & MYSQL_TIME_WARN_TRUNCATED) && - mysql_type_to_time_type(type()) == MYSQL_TIMESTAMP_DATE && - (ltime->hour || ltime->minute || ltime->second || ltime->second_part)) - { - trunc_level= MYSQL_ERROR::WARN_LEVEL_NOTE; - was_cut|= MYSQL_TIME_WARN_TRUNCATED; - ret= 3; - } - else if (!(was_cut & MYSQL_TIME_WARN_TRUNCATED) && - mysql_type_to_time_type(type()) == MYSQL_TIMESTAMP_TIME && - (ltime->year || ltime->month)) + else if (!MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) && + (MYSQL_TIME_WARN_HAVE_NOTES(was_cut) || + (mysql_type_to_time_type(type()) == MYSQL_TIMESTAMP_DATE && + (ltime->hour || ltime->minute || ltime->second || ltime->second_part)))) { - ltime->year= ltime->month= ltime->day= 0; - trunc_level= MYSQL_ERROR::WARN_LEVEL_NOTE; + trunc_level= Sql_condition::WARN_LEVEL_NOTE; was_cut|= MYSQL_TIME_WARN_TRUNCATED; ret= 3; } - - /* - error code logic: - MYSQL_TIME_WARN_TRUNCATED means that the value was not a date/time at all. - it will be stored as zero date/time. - MYSQL_TIME_WARN_OUT_OF_RANGE means that the value was a date/time, - that is, it was parsed as such, but the value was invalid. - - Also, MYSQL_TIME_WARN_TRUNCATED is used when storing a DATETIME in - a DATE field and non-zero time part is thrown away. - */ - if (was_cut & MYSQL_TIME_WARN_TRUNCATED) - set_datetime_warning(trunc_level, WARN_DATA_TRUNCATED, - str, mysql_type_to_time_type(type()), 1); - if (was_cut & MYSQL_TIME_WARN_OUT_OF_RANGE) - set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, - str, mysql_type_to_time_type(type()), 1); - + set_warnings(trunc_level, str, was_cut, mysql_type_to_time_type(type())); store_TIME(ltime); return was_cut ? ret : 0; } -int Field_temporal::store(const char *from,uint len,CHARSET_INFO *cs) +int Field_temporal_with_date::store(const char *from, uint len, CHARSET_INFO *cs) { MYSQL_TIME ltime; - int error; - enum enum_mysql_timestamp_type func_res; - THD *thd= table->in_use; + MYSQL_TIME_STATUS status; + THD *thd= get_thd(); ErrConvString str(from, len, cs); - - func_res= str_to_datetime(cs, from, len, <ime, - (thd->variables.sql_mode & - (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | - MODE_INVALID_DATES)), - &error); - return store_TIME_with_warning(<ime, &str, error, func_res > MYSQL_TIMESTAMP_ERROR); + bool func_res= !str_to_datetime(cs, from, len, <ime, + sql_mode_for_dates(thd), + &status); + return store_TIME_with_warning(<ime, &str, status.warnings, func_res); } -int Field_temporal::store(double nr) +int Field_temporal_with_date::store(double nr) { int error= 0; MYSQL_TIME ltime; - THD *thd= table->in_use; + THD *thd= get_thd(); ErrConvDouble str(nr); longlong tmp= double_to_datetime(nr, <ime, - (thd->variables.sql_mode & - (MODE_NO_ZERO_IN_DATE | - MODE_NO_ZERO_DATE | - MODE_INVALID_DATES)), &error); + sql_mode_for_dates(thd), &error); return store_TIME_with_warning(<ime, &str, error, tmp != -1); } -int Field_temporal::store(longlong nr, bool unsigned_val) +int Field_temporal_with_date::store(longlong nr, bool unsigned_val) { int error; MYSQL_TIME ltime; longlong tmp; - THD *thd= table->in_use; + THD *thd= get_thd(); ErrConvInteger str(nr, unsigned_val); - tmp= number_to_datetime(nr, 0, <ime, (thd->variables.sql_mode & - (MODE_NO_ZERO_IN_DATE | - MODE_NO_ZERO_DATE | - MODE_INVALID_DATES)), &error); + tmp= number_to_datetime(nr, 0, <ime, sql_mode_for_dates(thd), &error); return store_TIME_with_warning(<ime, &str, error, tmp != -1); } -int Field_temporal::store_time_dec(MYSQL_TIME *ltime, uint dec) +int Field_temporal_with_date::store_time_dec(MYSQL_TIME *ltime, uint dec) { - int error = 0, have_smth_to_conv= 1; - MYSQL_TIME l_time= *ltime; + int error= 0, have_smth_to_conv= 1; ErrConvTime str(ltime); + MYSQL_TIME l_time; - if (l_time.time_type == MYSQL_TIMESTAMP_TIME && time_to_datetime(&l_time)) + if (copy_or_convert_to_datetime(get_thd(), ltime, &l_time)) { - have_smth_to_conv= 0; - error= 1; - goto store; + /* + Set have_smth_to_conv and error in a way to have + store_TIME_with_warning do bzero(). + */ + have_smth_to_conv= false; + error= MYSQL_TIME_WARN_OUT_OF_RANGE; + } + else + { + /* + We don't perform range checking here since values stored in TIME + structure always fit into DATETIME range. + */ + have_smth_to_conv= !check_date(&l_time, pack_time(&l_time) != 0, + sql_mode_for_dates(get_thd()), &error); } - /* - We don't perform range checking here since values stored in TIME - structure always fit into DATETIME range. - */ - have_smth_to_conv= !check_date(&l_time, pack_time(&l_time) != 0, - (current_thd->variables.sql_mode & - (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | - MODE_INVALID_DATES)), &error); -store: return store_TIME_with_warning(&l_time, &str, error, have_smth_to_conv); } + +bool +Field_temporal_with_date::validate_value_in_record(THD *thd, + const uchar *record) const +{ + DBUG_ASSERT(!is_null_in_record(record)); + MYSQL_TIME ltime; + return get_TIME(<ime, ptr_in_record(record), sql_mode_for_dates(thd)); +} + + my_decimal *Field_temporal::val_decimal(my_decimal *d) { MYSQL_TIME ltime; @@ -5101,16 +5648,102 @@ my_decimal *Field_temporal::val_decimal(my_decimal *d) bzero(<ime, sizeof(ltime)); ltime.time_type= mysql_type_to_time_type(type()); } - longlong intg= TIME_to_ulonglong(<ime); - return seconds2my_decimal(ltime.neg, intg, ltime.second_part, d); + return TIME_to_my_decimal(<ime, d); +} + + +bool Field_temporal::can_optimize_keypart_ref(const Item_bool_func *cond, + const Item *value) const +{ + return true; // Field is of TIME_RESULT, which supersedes everything else. +} + + +bool Field_temporal::can_optimize_group_min_max(const Item_bool_func *cond, + const Item *const_item) const +{ + return true; // Field is of TIME_RESULT, which supersedes everything else. +} + + +Item *Field_temporal::get_equal_const_item_datetime(THD *thd, + const Context &ctx, + Item *const_item) +{ + switch (ctx.subst_constraint()) { + case IDENTITY_SUBST: + if ((const_item->field_type() != MYSQL_TYPE_DATETIME && + const_item->field_type() != MYSQL_TYPE_TIMESTAMP) || + const_item->decimals != decimals()) + { + MYSQL_TIME ltime; + if (const_item->field_type() == MYSQL_TYPE_TIME ? + const_item->get_date_with_conversion(<ime, 0) : + const_item->get_date(<ime, 0)) + return NULL; + /* + See comments about truncation in the same place in + Field_time::get_equal_const_item(). + */ + return new (thd->mem_root) Item_datetime_literal(thd, <ime, + decimals()); + } + break; + case ANY_SUBST: + if (!is_temporal_type_with_date(const_item->field_type())) + { + MYSQL_TIME ltime; + if (const_item->get_date_with_conversion(<ime, + TIME_FUZZY_DATES | + TIME_INVALID_DATES)) + return NULL; + return new (thd->mem_root) + Item_datetime_literal_for_invalid_dates(thd, <ime, + ltime.second_part ? + TIME_SECOND_PART_DIGITS : 0); + } + break; + } + return const_item; } + /**************************************************************************** ** time type ** In string context: HH:MM:SS ** In number context: HHMMSS ** Stored as a 3 byte unsigned int ****************************************************************************/ +int Field_time::store_TIME_with_warning(MYSQL_TIME *ltime, + const ErrConv *str, + int was_cut, + int have_smth_to_conv) +{ + Sql_condition::enum_warning_level trunc_level= Sql_condition::WARN_LEVEL_WARN; + int ret= 2; + ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; + + if (!have_smth_to_conv) + { + bzero(ltime, sizeof(*ltime)); + was_cut= MYSQL_TIME_WARN_TRUNCATED; + ret= 1; + } + else if (!MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) && + ((ltime->year || ltime->month) || + MYSQL_TIME_WARN_HAVE_NOTES(was_cut))) + { + if (ltime->year || ltime->month) + ltime->year= ltime->month= ltime->day= 0; + trunc_level= Sql_condition::WARN_LEVEL_NOTE; + was_cut|= MYSQL_TIME_WARN_TRUNCATED; + ret= 3; + } + set_warnings(trunc_level, str, was_cut, MYSQL_TIMESTAMP_TIME); + store_TIME(ltime); + return was_cut ? ret : 0; +} + void Field_time::store_TIME(MYSQL_TIME *ltime) { @@ -5124,16 +5757,47 @@ void Field_time::store_TIME(MYSQL_TIME *ltime) int Field_time::store(const char *from,uint len,CHARSET_INFO *cs) { MYSQL_TIME ltime; + MYSQL_TIME_STATUS status; ErrConvString str(from, len, cs); - int was_cut; - int have_smth_to_conv= - str_to_time(cs, from, len, <ime, - table->in_use->variables.sql_mode & - (MODE_NO_ZERO_DATE | MODE_NO_ZERO_IN_DATE | - MODE_INVALID_DATES), - &was_cut) > MYSQL_TIMESTAMP_ERROR; + bool have_smth_to_conv= + !str_to_time(cs, from, len, <ime, sql_mode_for_dates(get_thd()), + &status); - return store_TIME_with_warning(<ime, &str, was_cut, have_smth_to_conv); + return store_TIME_with_warning(<ime, &str, + status.warnings, have_smth_to_conv); +} + + +/** + subtract a given number of days from DATETIME, return TIME + + optimized version of calc_time_diff() + + @note it might generate TIME values outside of the valid TIME range! +*/ +static void calc_datetime_days_diff(MYSQL_TIME *ltime, long days) +{ + long daydiff= calc_daynr(ltime->year, ltime->month, ltime->day) - days; + ltime->year= ltime->month= 0; + if (daydiff >=0 ) + ltime->day= daydiff; + else + { + longlong timediff= ((((daydiff * 24LL + + ltime->hour) * 60LL + + ltime->minute) * 60LL + + ltime->second) * 1000000LL + + ltime->second_part); + unpack_time(timediff, ltime); + /* + unpack_time() broke down hours into ltime members hour,day,month. + Mix them back to ltime->hour using the same factors + that pack_time()/unpack_time() use (i.e. 32 for month). + */ + ltime->hour+= (ltime->month * 32 + ltime->day) * 24; + ltime->month= ltime->day= 0; + } + ltime->time_type= MYSQL_TIMESTAMP_TIME; } @@ -5143,6 +5807,9 @@ int Field_time::store_time_dec(MYSQL_TIME *ltime, uint dec) ErrConvTime str(ltime); int was_cut= 0; + if (curdays && l_time.time_type != MYSQL_TIMESTAMP_TIME) + calc_datetime_days_diff(&l_time, curdays); + int have_smth_to_conv= !check_time_range(&l_time, decimals(), &was_cut); return store_TIME_with_warning(&l_time, &str, was_cut, have_smth_to_conv); } @@ -5177,8 +5844,30 @@ int Field_time::store(longlong nr, bool unsigned_val) return store_TIME_with_warning(<ime, &str, was_cut, have_smth_to_conv); } - - + + +void Field_time::set_curdays(THD *thd) +{ + MYSQL_TIME ltime; + set_current_date(thd, <ime); + curdays= calc_daynr(ltime.year, ltime.month, ltime.day); +} + + +Field *Field_time::new_key_field(MEM_ROOT *root, TABLE *new_table, + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit) +{ + THD *thd= get_thd(); + Field_time *res= + (Field_time*) Field::new_key_field(root, new_table, new_ptr, length, + new_null_ptr, new_null_bit); + if (!(thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) && res) + res->set_curdays(thd); + return res; +} + + double Field_time::val_real(void) { ASSERT_COLUMN_MARKED_FOR_READ; @@ -5199,32 +5888,31 @@ longlong Field_time::val_int(void) my_charset_bin */ -String *Field_time::val_str(String *val_buffer, - String *val_ptr __attribute__((unused))) +String *Field_time::val_str(String *str, + String *unused __attribute__((unused))) { ASSERT_COLUMN_MARKED_FOR_READ; MYSQL_TIME ltime; - long tmp=(long) sint3korr(ptr); - ltime.neg= 0; - if (tmp < 0) - { - tmp= -tmp; - ltime.neg= 1; - } - ltime.year= ltime.month= 0; - ltime.day= (uint) 0; - ltime.hour= (uint) (tmp/10000); - ltime.minute= (uint) (tmp/100 % 100); - ltime.second= (uint) (tmp % 100); - ltime.second_part= 0; + get_date(<ime, TIME_TIME_ONLY); + str->alloc(field_length + 1); + str->length(my_time_to_str(<ime, const_cast<char*>(str->ptr()), decimals())); + str->set_charset(&my_charset_numeric); + return str; +} - val_buffer->alloc(MAX_DATE_STRING_REP_LENGTH); - uint length= (uint) my_time_to_str(<ime, - const_cast<char*>(val_buffer->ptr()), 0); - val_buffer->length(length); - val_buffer->set_charset(&my_charset_numeric); - return val_buffer; +bool Field_time::check_zero_in_date_with_warn(ulonglong fuzzydate) +{ + if (!(fuzzydate & TIME_TIME_ONLY) && (fuzzydate & TIME_NO_ZERO_IN_DATE)) + { + THD *thd= get_thd(); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_DATA_OUT_OF_RANGE, + ER_THD(thd, ER_WARN_DATA_OUT_OF_RANGE), field_name, + thd->get_stmt_da()->current_row_for_warning()); + return true; + } + return false; } @@ -5237,16 +5925,8 @@ String *Field_time::val_str(String *val_buffer, bool Field_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) { - THD *thd= table->in_use; - if (!(fuzzydate & TIME_TIME_ONLY) && - (fuzzydate & TIME_NO_ZERO_IN_DATE)) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_DATA_OUT_OF_RANGE, - ER(ER_WARN_DATA_OUT_OF_RANGE), field_name, - thd->warning_info->current_row_for_warning()); - return 1; - } + if (check_zero_in_date_with_warn(fuzzydate)) + return true; long tmp=(long) sint3korr(ptr); ltime->neg=0; if (tmp < 0) @@ -5268,8 +5948,8 @@ bool Field_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) bool Field_time::send_binary(Protocol *protocol) { MYSQL_TIME ltime; - Field_time::get_date(<ime, TIME_TIME_ONLY); - return protocol->store_time(<ime, 0); + get_date(<ime, TIME_TIME_ONLY); + return protocol->store_time(<ime, decimals()); } @@ -5290,7 +5970,14 @@ void Field_time::sort_string(uchar *to,uint length __attribute__((unused))) void Field_time::sql_type(String &res) const { - res.set_ascii(STRING_WITH_LEN("time")); + if (decimals() == 0) + { + res.set_ascii(STRING_WITH_LEN("time")); + return; + } + CHARSET_INFO *cs= res.charset(); + res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), + "time(%d)", decimals())); } int Field_time_hires::reset() @@ -5306,7 +5993,7 @@ void Field_time_hires::store_TIME(MYSQL_TIME *ltime) store_bigendian(packed, ptr, Field_time_hires::pack_length()); } -int Field_time_hires::store_decimal(const my_decimal *d) +int Field_time::store_decimal(const my_decimal *d) { ulonglong nr; ulong sec_part; @@ -5320,42 +6007,92 @@ int Field_time_hires::store_decimal(const my_decimal *d) return store_TIME_with_warning(<ime, &str, was_cut, have_smth_to_conv); } + +Item *Field_time::get_equal_const_item(THD *thd, const Context &ctx, + Item *const_item) +{ + switch (ctx.subst_constraint()) { + case ANY_SUBST: + if (const_item->field_type() != MYSQL_TYPE_TIME) + { + MYSQL_TIME ltime; + // Get the value of const_item with conversion from DATETIME to TIME + if (const_item->get_time_with_conversion(thd, <ime, + TIME_TIME_ONLY | + TIME_FUZZY_DATES | + TIME_INVALID_DATES)) + return NULL; + /* + Replace a DATE/DATETIME constant to a TIME constant: + WHERE LENGTH(time_column)=8 + AND time_column=TIMESTAMP'2015-08-30 10:20:30'; + to: + WHERE LENGTH(time_column)=10 + AND time_column=TIME'10:20:30' + + (assuming CURRENT_DATE is '2015-08-30' + */ + return new (thd->mem_root) Item_time_literal(thd, <ime, + ltime.second_part ? + TIME_SECOND_PART_DIGITS : + 0); + } + break; + case IDENTITY_SUBST: + if (const_item->field_type() != MYSQL_TYPE_TIME || + const_item->decimals != decimals()) + { + MYSQL_TIME ltime; + if (const_item->get_time_with_conversion(thd, <ime, TIME_TIME_ONLY)) + return NULL; + /* + Note, the value returned in "ltime" can have more fractional + digits that decimals(). The Item_time_literal constructor will + truncate these digits. We could disallow propagation is such + cases, but it's still useful (and safe) to optimize: + WHERE time0_column='00:00:00.123' AND LENGTH(a)=12 + to + WHERE time0_column='00:00:00.123' AND LENGTH(TIME'00:00:00')=12 + and then to + WHERE FALSE + The original WHERE would do the full table scan (in case of no keys). + The optimized WHERE will return with "Impossible WHERE", without + having to do the full table scan. + */ + return new (thd->mem_root) Item_time_literal(thd, <ime, decimals()); + } + break; + } + return const_item; +} + + uint32 Field_time_hires::pack_length() const { return time_hires_bytes[dec]; } -longlong Field_time_hires::val_int(void) +longlong Field_time_with_dec::val_int(void) { ASSERT_COLUMN_MARKED_FOR_READ; MYSQL_TIME ltime; - Field_time_hires::get_date(<ime, TIME_TIME_ONLY); + get_date(<ime, TIME_TIME_ONLY); longlong val= TIME_to_ulonglong_time(<ime); return ltime.neg ? -val : val; } -double Field_time_hires::val_real(void) +double Field_time_with_dec::val_real(void) { ASSERT_COLUMN_MARKED_FOR_READ; MYSQL_TIME ltime; - Field_time_hires::get_date(<ime, TIME_TIME_ONLY); + get_date(<ime, TIME_TIME_ONLY); return TIME_to_double(<ime); } -String *Field_time_hires::val_str(String *str, - String *unused __attribute__((unused))) -{ - ASSERT_COLUMN_MARKED_FOR_READ; - MYSQL_TIME ltime; - Field_time_hires::get_date(<ime, TIME_TIME_ONLY); - str->alloc(field_length+1); - str->length(my_time_to_str(<ime, (char*) str->ptr(), dec)); - str->set_charset(&my_charset_bin); - return str; -} - bool Field_time_hires::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) { + if (check_zero_in_date_with_warn(fuzzydate)) + return true; uint32 len= pack_length(); longlong packed= read_bigendian(ptr, len); @@ -5369,15 +6106,7 @@ bool Field_time_hires::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) ltime->time_type= MYSQL_TIMESTAMP_TIME; ltime->hour+= (ltime->month*32+ltime->day)*24; ltime->month= ltime->day= 0; - return !(fuzzydate & TIME_TIME_ONLY) && (fuzzydate & TIME_NO_ZERO_IN_DATE); -} - - -bool Field_time_hires::send_binary(Protocol *protocol) -{ - MYSQL_TIME ltime; - Field_time_hires::get_date(<ime, TIME_TIME_ONLY); - return protocol->store_time(<ime, dec); + return false; } @@ -5395,17 +6124,38 @@ void Field_time_hires::sort_string(uchar *to,uint length __attribute__((unused)) to[0]^= 128; } -void Field_time_hires::sql_type(String &res) const +void Field_time_with_dec::make_field(Send_field *field) { - CHARSET_INFO *cs=res.charset(); - res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), - "time(%u)", dec)); + Field::make_field(field); + field->decimals= dec; } -void Field_time_hires::make_field(Send_field *field) +/**************************************************************************** +** time type with fsp (MySQL-5.6 version) +** In string context: HH:MM:SS.FFFFFF +** In number context: HHMMSS.FFFFFF +****************************************************************************/ + +int Field_timef::reset() { - Field::make_field(field); - field->decimals= dec; + my_time_packed_to_binary(0, ptr, dec); + return 0; +} + +void Field_timef::store_TIME(MYSQL_TIME *ltime) +{ + my_time_trunc(ltime, decimals()); + longlong tmp= TIME_to_longlong_time_packed(ltime); + my_time_packed_to_binary(tmp, ptr, dec); +} + +bool Field_timef::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) +{ + if (check_zero_in_date_with_warn(fuzzydate)) + return true; + longlong tmp= my_time_packed_from_binary(ptr, dec); + TIME_from_longlong_time_packed(ltime, tmp); + return false; } /**************************************************************************** @@ -5425,10 +6175,10 @@ int Field_year::store(const char *from, uint len,CHARSET_INFO *cs) error == MY_ERRNO_ERANGE) { *ptr=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); return 1; } - if (table->in_use->count_cuted_fields && + if (get_thd()->count_cuted_fields && (error= check_int(cs, from, len, end, error))) { if (error == 1) /* empty or incorrect string */ @@ -5468,7 +6218,7 @@ int Field_year::store(longlong nr, bool unsigned_val) if (nr < 0 || (nr >= 100 && nr <= 1900) || nr > 2155) { *ptr= 0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); return 1; } if (nr != 0 || field_length != 4) // 0000 -> 0; 00 -> 2000 @@ -5483,14 +6233,13 @@ int Field_year::store(longlong nr, bool unsigned_val) } -int Field_year::store_time_dec(MYSQL_TIME *ltime, uint dec) +int Field_year::store_time_dec(MYSQL_TIME *ltime, uint dec_arg) { ErrConvTime str(ltime); if (Field_year::store(ltime->year, 0)) return 1; - set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, - &str, ltime->time_type, 1); + set_datetime_warning(WARN_DATA_TRUNCATED, &str, ltime->time_type, 1); return 0; } @@ -5594,18 +6343,25 @@ longlong Field_date::val_int(void) } +bool Field_date::get_TIME(MYSQL_TIME *ltime, const uchar *pos, + ulonglong fuzzydate) const +{ + ASSERT_COLUMN_MARKED_FOR_READ; + int32 tmp= sint4korr(pos); + ltime->year= (int) ((uint32) tmp/10000L % 10000); + ltime->month= (int) ((uint32) tmp/100 % 100); + ltime->day= (int) ((uint32) tmp % 100); + ltime->time_type= MYSQL_TIMESTAMP_DATE; + ltime->hour= ltime->minute= ltime->second= ltime->second_part= ltime->neg= 0; + return validate_MMDD(tmp, ltime->month, ltime->day, fuzzydate); +} + + String *Field_date::val_str(String *val_buffer, String *val_ptr __attribute__((unused))) { - ASSERT_COLUMN_MARKED_FOR_READ; MYSQL_TIME ltime; - int32 tmp; - tmp=sint4korr(ptr); - ltime.neg= 0; - ltime.year= (int) ((uint32) tmp/10000L % 10000); - ltime.month= (int) ((uint32) tmp/100 % 100); - ltime.day= (int) ((uint32) tmp % 100); - + get_TIME(<ime, ptr, 0); val_buffer->alloc(MAX_DATE_STRING_REP_LENGTH); uint length= (uint) my_date_to_str(<ime, const_cast<char*>(val_buffer->ptr())); @@ -5706,19 +6462,17 @@ String *Field_newdate::val_str(String *val_buffer, } -bool Field_newdate::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) +bool Field_newdate::get_TIME(MYSQL_TIME *ltime, const uchar *pos, + ulonglong fuzzydate) const { - uint32 tmp=(uint32) uint3korr(ptr); + ASSERT_COLUMN_MARKED_FOR_READ; + uint32 tmp=(uint32) uint3korr(pos); ltime->day= tmp & 31; ltime->month= (tmp >> 5) & 15; ltime->year= (tmp >> 9); ltime->time_type= MYSQL_TIMESTAMP_DATE; ltime->hour= ltime->minute= ltime->second= ltime->second_part= ltime->neg= 0; - if (!tmp) - return fuzzydate & TIME_NO_ZERO_DATE; - if (!ltime->month || !ltime->day) - return fuzzydate & TIME_NO_ZERO_IN_DATE; - return 0; + return validate_MMDD(tmp, ltime->month, ltime->day, fuzzydate); } @@ -5745,6 +6499,56 @@ void Field_newdate::sql_type(String &res) const } +Item *Field_newdate::get_equal_const_item(THD *thd, const Context &ctx, + Item *const_item) +{ + switch (ctx.subst_constraint()) { + case ANY_SUBST: + if (!is_temporal_type_with_date(const_item->field_type())) + { + MYSQL_TIME ltime; + // Get the value of const_item with conversion from TIME to DATETIME + if (const_item->get_date_with_conversion(<ime, + TIME_FUZZY_DATES | TIME_INVALID_DATES)) + return NULL; + /* + Replace the constant to a DATE or DATETIME constant. + Example: + WHERE LENGTH(date_column)=10 + AND date_column=TIME'10:20:30'; + to: + WHERE LENGTH(date_column)=10 + AND date_column=TIMESTAMP'2015-08-30 10:20:30' + + (assuming CURRENT_DATE is '2015-08-30' + */ + if (non_zero_hhmmssuu(<ime)) + return new (thd->mem_root) + Item_datetime_literal_for_invalid_dates(thd, <ime, + ltime.second_part ? + TIME_SECOND_PART_DIGITS : 0); + datetime_to_date(<ime); + return new (thd->mem_root) + Item_date_literal_for_invalid_dates(thd, <ime); + } + break; + case IDENTITY_SUBST: + if (const_item->field_type() != MYSQL_TYPE_DATE) + { + MYSQL_TIME ltime; + if (const_item->field_type() == MYSQL_TYPE_TIME ? + const_item->get_date_with_conversion(<ime, 0) : + const_item->get_date(<ime, 0)) + return NULL; + datetime_to_date(<ime); + return new (thd->mem_root) Item_date_literal(thd, <ime); + } + break; + } + return const_item; +} + + /**************************************************************************** ** datetime type ** In string context: YYYY-MM-DD HH:MM:DD @@ -5764,8 +6568,8 @@ bool Field_datetime::send_binary(Protocol *protocol) Field_datetime::get_date(&tm, 0); return protocol->store(&tm, 0); } - - + + double Field_datetime::val_real(void) { return (double) Field_datetime::val_int(); @@ -5798,8 +6602,8 @@ String *Field_datetime::val_str(String *val_buffer, Avoid problem with slow longlong arithmetic and sprintf */ - part1=(long) (tmp/LL(1000000)); - part2=(long) (tmp - (ulonglong) part1*LL(1000000)); + part1=(long) (tmp/1000000LL); + part2=(long) (tmp - (ulonglong) part1*1000000LL); pos=(char*) val_buffer->ptr() + MAX_DATETIME_WIDTH; *pos--=0; @@ -5826,12 +6630,14 @@ String *Field_datetime::val_str(String *val_buffer, return val_buffer; } -bool Field_datetime::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) +bool Field_datetime::get_TIME(MYSQL_TIME *ltime, const uchar *pos, + ulonglong fuzzydate) const { - longlong tmp=Field_datetime::val_int(); + ASSERT_COLUMN_MARKED_FOR_READ; + longlong tmp= sint8korr(pos); uint32 part1,part2; - part1=(uint32) (tmp/LL(1000000)); - part2=(uint32) (tmp - (ulonglong) part1*LL(1000000)); + part1=(uint32) (tmp/1000000LL); + part2=(uint32) (tmp - (ulonglong) part1*1000000LL); ltime->time_type= MYSQL_TIMESTAMP_DATETIME; ltime->neg= 0; @@ -5842,13 +6648,10 @@ bool Field_datetime::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) ltime->day= (int) (part1%100); ltime->month= (int) (part1/100%100); ltime->year= (int) (part1/10000); - if (!tmp) - return fuzzydate & TIME_NO_ZERO_DATE; - if (!ltime->month || !ltime->day) - return fuzzydate & TIME_NO_ZERO_IN_DATE; - return 0; + return validate_MMDD(tmp, ltime->month, ltime->day, fuzzydate); } + int Field_datetime::cmp(const uchar *a_ptr, const uchar *b_ptr) { longlong a,b; @@ -5873,23 +6676,44 @@ void Field_datetime::sort_string(uchar *to,uint length __attribute__((unused))) void Field_datetime::sql_type(String &res) const { - res.set_ascii(STRING_WITH_LEN("datetime")); + if (decimals() == 0) + { + res.set_ascii(STRING_WITH_LEN("datetime")); + return; + } + CHARSET_INFO *cs= res.charset(); + res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), + "datetime(%u)", decimals())); +} + + +int Field_datetime::set_time() +{ + THD *thd= table->in_use; + MYSQL_TIME now_time; + thd->variables.time_zone->gmt_sec_to_TIME(&now_time, thd->query_start()); + now_time.second_part= thd->query_start_sec_part(); + set_notnull(); + store_TIME(&now_time); + thd->time_zone_used= 1; + return 0; } + void Field_datetime_hires::store_TIME(MYSQL_TIME *ltime) { ulonglong packed= sec_part_shift(pack_time(ltime), dec); store_bigendian(packed, ptr, Field_datetime_hires::pack_length()); } -int Field_datetime_hires::store_decimal(const my_decimal *d) +int Field_temporal_with_date::store_decimal(const my_decimal *d) { ulonglong nr; ulong sec_part; int error; MYSQL_TIME ltime; longlong tmp; - THD *thd= table->in_use; + THD *thd= get_thd(); ErrConvDecimal str(d); if (my_decimal2seconds(d, &nr, &sec_part)) @@ -5898,60 +6722,58 @@ int Field_datetime_hires::store_decimal(const my_decimal *d) error= 2; } else - tmp= number_to_datetime(nr, sec_part, <ime, (thd->variables.sql_mode & - (MODE_NO_ZERO_IN_DATE | - MODE_NO_ZERO_DATE | - MODE_INVALID_DATES)), &error); + tmp= number_to_datetime(nr, sec_part, <ime, sql_mode_for_dates(thd), + &error); return store_TIME_with_warning(<ime, &str, error, tmp != -1); } -bool Field_datetime_hires::send_binary(Protocol *protocol) +bool Field_datetime_with_dec::send_binary(Protocol *protocol) { MYSQL_TIME ltime; - Field_datetime_hires::get_date(<ime, 0); + get_date(<ime, 0); return protocol->store(<ime, dec); } -double Field_datetime_hires::val_real(void) +double Field_datetime_with_dec::val_real(void) { MYSQL_TIME ltime; - Field_datetime_hires::get_date(<ime, 0); + get_date(<ime, 0); return TIME_to_double(<ime); } -longlong Field_datetime_hires::val_int(void) +longlong Field_datetime_with_dec::val_int(void) { MYSQL_TIME ltime; - Field_datetime_hires::get_date(<ime, 0); + get_date(<ime, 0); return TIME_to_ulonglong_datetime(<ime); } -String *Field_datetime_hires::val_str(String *str, - String *unused __attribute__((unused))) +String *Field_datetime_with_dec::val_str(String *str, + String *unused __attribute__((unused))) { MYSQL_TIME ltime; - Field_datetime_hires::get_date(<ime, 0); + get_date(<ime, 0); str->alloc(field_length+1); str->length(field_length); my_datetime_to_str(<ime, (char*) str->ptr(), dec); - str->set_charset(&my_charset_bin); + str->set_charset(&my_charset_numeric); return str; } -bool Field_datetime_hires::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) + +bool Field_datetime_hires::get_TIME(MYSQL_TIME *ltime, const uchar *pos, + ulonglong fuzzydate) const { - ulonglong packed= read_bigendian(ptr, Field_datetime_hires::pack_length()); + ASSERT_COLUMN_MARKED_FOR_READ; + ulonglong packed= read_bigendian(pos, Field_datetime_hires::pack_length()); unpack_time(sec_part_unshift(packed, dec), ltime); - if (!packed) - return fuzzydate & TIME_NO_ZERO_DATE; - if (!ltime->month || !ltime->day) - return fuzzydate & TIME_NO_ZERO_IN_DATE; - return 0; + return validate_MMDD(packed, ltime->month, ltime->day, fuzzydate); } + uint32 Field_datetime_hires::pack_length() const { return datetime_hires_bytes[dec]; @@ -5964,25 +6786,37 @@ int Field_datetime_hires::cmp(const uchar *a_ptr, const uchar *b_ptr) return a < b ? -1 : a > b ? 1 : 0; } -void Field_datetime_hires::sort_string(uchar *to, - uint length __attribute__((unused))) +void Field_datetime_with_dec::make_field(Send_field *field) { - DBUG_ASSERT(length == Field_datetime_hires::pack_length()); - memcpy(to, ptr, length); + Field::make_field(field); + field->decimals= dec; } -void Field_datetime_hires::sql_type(String &res) const +/**************************************************************************** +** MySQL-5.6 compatible DATETIME(N) +** +****************************************************************************/ +int Field_datetimef::reset() { - CHARSET_INFO *cs=res.charset(); - res.length(cs->cset->snprintf(cs, (char*) res.ptr(), res.alloced_length(), - "datetime(%u)", dec)); + my_datetime_packed_to_binary(0, ptr, dec); + return 0; } -void Field_datetime_hires::make_field(Send_field *field) +void Field_datetimef::store_TIME(MYSQL_TIME *ltime) { - Field::make_field(field); - field->decimals= dec; + my_time_trunc(ltime, decimals()); + longlong tmp= TIME_to_longlong_datetime_packed(ltime); + my_datetime_packed_to_binary(tmp, ptr, dec); +} + +bool Field_datetimef::get_TIME(MYSQL_TIME *ltime, const uchar *pos, + ulonglong fuzzydate) const +{ + ASSERT_COLUMN_MARKED_FOR_READ; + longlong tmp= my_datetime_packed_from_binary(pos, dec); + TIME_from_longlong_datetime_packed(ltime, tmp); + return validate_MMDD(tmp, ltime->month, ltime->day, fuzzydate); } /**************************************************************************** @@ -5996,10 +6830,9 @@ void Field_datetime_hires::make_field(Send_field *field) SYNOPSIS check_string_copy_error() - field - Field - well_formed_error_pos - where not well formed data was first met - cannot_convert_error_pos - where a not-convertable character was first met - end - end of the string + copier - the conversion status + end - the very end of the source string + that was just copied cs - character set of the string NOTES @@ -6016,29 +6849,19 @@ void Field_datetime_hires::make_field(Send_field *field) TRUE - If an error happened */ -static bool -check_string_copy_error(Field_str *field, - const char *well_formed_error_pos, - const char *cannot_convert_error_pos, - const char *end, - CHARSET_INFO *cs) +bool +Field_longstr::check_string_copy_error(const String_copier *copier, + const char *end, + CHARSET_INFO *cs) { const char *pos; char tmp[32]; - THD *thd= field->table->in_use; - if (!(pos= well_formed_error_pos) && - !(pos= cannot_convert_error_pos)) + if (!(pos= copier->most_important_error_pos())) return FALSE; convert_to_printable(tmp, sizeof(tmp), pos, (end - pos), cs, 6); - - push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, - ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), - "string", tmp, field->field_name, - thd->warning_info->current_row_for_warning()); + set_warning_truncated_wrong_value("string", tmp); return TRUE; } @@ -6067,19 +6890,20 @@ int Field_longstr::report_if_important_data(const char *pstr, const char *end, bool count_spaces) { - if ((pstr < end) && table->in_use->count_cuted_fields) + THD *thd= get_thd(); + if ((pstr < end) && thd->count_cuted_fields) { if (test_if_important_data(field_charset, pstr, end)) { - if (table->in_use->abort_on_warning) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1); + if (thd->abort_on_warning) + set_warning(ER_DATA_TOO_LONG, 1); else - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); return 2; } else if (count_spaces) { /* If we lost only spaces then produce a NOTE, not a WARNING */ - set_warning(MYSQL_ERROR::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1); + set_note(WARN_DATA_TRUNCATED, 1); return 2; } } @@ -6093,20 +6917,15 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) { ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; uint copy_length; - const char *well_formed_error_pos; - const char *cannot_convert_error_pos; - const char *from_end_pos; + String_copier copier; /* See the comment for Field_long::store(long long) */ - DBUG_ASSERT(table->in_use == current_thd); + DBUG_ASSERT(!table || table->in_use == current_thd); - copy_length= well_formed_copy_nchars(field_charset, + copy_length= copier.well_formed_copy(field_charset, (char*) ptr, field_length, cs, from, length, - field_length / field_charset->mbmaxlen, - &well_formed_error_pos, - &cannot_convert_error_pos, - &from_end_pos); + field_length / field_charset->mbmaxlen); /* Append spaces if the string was shorter than the field. */ if (copy_length < field_length) @@ -6114,11 +6933,7 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) field_length-copy_length, field_charset->pad_char); - if (check_string_copy_error(this, well_formed_error_pos, - cannot_convert_error_pos, from + length, cs)) - return 2; - - return report_if_important_data(from_end_pos, from + length, FALSE); + return check_conversion_status(&copier, from + length, cs, false); } @@ -6144,15 +6959,14 @@ int Field_str::store(double nr) if (error) { - if (table->in_use->abort_on_warning) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1); + if (get_thd()->abort_on_warning) + set_warning(ER_DATA_TOO_LONG, 1); else - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); } return store(buff, length, &my_charset_numeric); } - uint Field::is_equal(Create_field *new_field) { return (new_field->sql_type == real_type()); @@ -6161,9 +6975,6 @@ uint Field::is_equal(Create_field *new_field) uint Field_str::is_equal(Create_field *new_field) { - if (field_flags_are_binary() != new_field->field_flags_are_binary()) - return 0; - return ((new_field->sql_type == real_type()) && new_field->charset == field_charset && new_field->length == max_display_length()); @@ -6195,51 +7006,98 @@ uint32 Field_longstr::max_data_length() const } +bool +Field_longstr::cmp_to_string_with_same_collation(const Item_bool_func *cond, + const Item *item) const +{ + return item->cmp_type() == STRING_RESULT && + charset() == cond->compare_collation(); +} + + +bool +Field_longstr::cmp_to_string_with_stricter_collation(const Item_bool_func *cond, + const Item *item) const +{ + return item->cmp_type() == STRING_RESULT && + (charset() == cond->compare_collation() || + cond->compare_collation()->state & MY_CS_BINSORT); +} + + +bool Field_longstr::can_optimize_keypart_ref(const Item_bool_func *cond, + const Item *item) const +{ + DBUG_ASSERT(cmp_type() == STRING_RESULT); + return cmp_to_string_with_stricter_collation(cond, item); +} + + +bool Field_longstr::can_optimize_hash_join(const Item_bool_func *cond, + const Item *item) const +{ + DBUG_ASSERT(cmp_type() == STRING_RESULT); + return cmp_to_string_with_same_collation(cond, item); +} + + +bool Field_longstr::can_optimize_group_min_max(const Item_bool_func *cond, + const Item *const_item) const +{ + /* + Can't use indexes when comparing a string to a number or a date + Don't use an index when comparing strings of different collations. + */ + DBUG_ASSERT(cmp_type() == STRING_RESULT); + return cmp_to_string_with_same_collation(cond, const_item); +} + + +bool Field_longstr::can_optimize_range(const Item_bool_func *cond, + const Item *item, + bool is_eq_func) const +{ + return is_eq_func ? + cmp_to_string_with_stricter_collation(cond, item) : + cmp_to_string_with_same_collation(cond, item); +} + + +/** + This overrides the default behavior of the parent constructor + Warn_filter(thd) to suppress notes about trailing spaces in case of CHAR(N), + as they are truncated during val_str(). + We still do want truncation notes in case of BINARY(N), + as trailing spaces are not truncated in val_str(). +*/ +Field_string::Warn_filter_string::Warn_filter_string(const THD *thd, + const Field_string *field) + :Warn_filter(!thd->no_errors, + !thd->no_errors && + field->Field_string::charset() == &my_charset_bin) +{ } + + double Field_string::val_real(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int error; - char *end; - CHARSET_INFO *cs= charset(); - double result; - - result= my_strntod(cs,(char*) ptr,field_length,&end,&error); - if (!table->in_use->no_errors && - (error || (field_length != (uint32)(end - (char*) ptr) && - !check_if_only_end_space(cs, end, - (char*) ptr + field_length)))) - { - ErrConvString err((char*) ptr, field_length, cs); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), "DOUBLE", - err.ptr()); - } - return result; + THD *thd= get_thd(); + return Converter_strntod_with_warn(get_thd(), + Warn_filter_string(thd, this), + Field_string::charset(), + (const char *) ptr, + field_length).result(); } longlong Field_string::val_int(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int error; - char *end; - CHARSET_INFO *cs= charset(); - longlong result; - - result= my_strntoll(cs, (char*) ptr,field_length,10,&end,&error); - if (!table->in_use->no_errors && - (error || (field_length != (uint32)(end - (char*) ptr) && - !check_if_only_end_space(cs, end, - (char*) ptr + field_length)))) - { - ErrConvString err((char*) ptr, field_length, cs); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), - "INTEGER", err.ptr()); - } - return result; + THD *thd= get_thd(); + return Converter_strntoll_with_warn(thd, Warn_filter_string(thd, this), + Field_string::charset(), + (const char *) ptr, + field_length).result(); } @@ -6248,9 +7106,9 @@ String *Field_string::val_str(String *val_buffer __attribute__((unused)), { ASSERT_COLUMN_MARKED_FOR_READ; /* See the comment for Field_long::store(long long) */ - DBUG_ASSERT(table->in_use == current_thd); + DBUG_ASSERT(!table || table->in_use == current_thd); uint length; - if (table->in_use->variables.sql_mode & + if (get_thd()->variables.sql_mode & MODE_PAD_CHAR_TO_FULL_LENGTH) length= my_charpos(field_charset, ptr, ptr + field_length, field_length / field_charset->mbmaxlen); @@ -6265,17 +7123,13 @@ String *Field_string::val_str(String *val_buffer __attribute__((unused)), my_decimal *Field_string::val_decimal(my_decimal *decimal_value) { ASSERT_COLUMN_MARKED_FOR_READ; - int err= str2my_decimal(E_DEC_FATAL_ERROR, (char*) ptr, field_length, - charset(), decimal_value); - if (!table->in_use->no_errors && err) - { - ErrConvString errmsg((char*) ptr, field_length, charset()); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), - "DECIMAL", errmsg.ptr()); - } - + THD *thd= get_thd(); + Converter_str2my_decimal_with_warn(thd, + Warn_filter_string(thd, this), + E_DEC_FATAL_ERROR, + Field_string::charset(), + (const char *) ptr, + field_length, decimal_value); return decimal_value; } @@ -6340,7 +7194,13 @@ int Field_string::cmp(const uchar *a_ptr, const uchar *b_ptr) void Field_string::sort_string(uchar *to,uint length) { uint tmp __attribute__((unused))= - my_strnxfrm(field_charset, to, length, ptr, field_length); + field_charset->coll->strnxfrm(field_charset, + to, length, + char_length() * + field_charset->strxfrm_multiply, + ptr, field_length, + MY_STRXFRM_PAD_WITH_SPACE | + MY_STRXFRM_PAD_TO_MAXLEN); DBUG_ASSERT(tmp == length); } @@ -6366,7 +7226,7 @@ void Field_string::sql_type(String &res) const uchar *Field_string::pack(uchar *to, const uchar *from, uint max_length) { - uint length= min(field_length,max_length); + uint length= MY_MIN(field_length,max_length); uint local_char_length= max_length/field_charset->mbmaxlen; DBUG_PRINT("debug", ("Packing field '%s' - length: %u ", field_name, length)); @@ -6533,14 +7393,15 @@ uint Field_string::get_key_image(uchar *buff, uint length, imagetype type_arg) } -Field *Field_string::new_field(MEM_ROOT *root, TABLE *new_table, - bool keep_type) +Field *Field_string::make_new_field(MEM_ROOT *root, TABLE *new_table, + bool keep_type) { Field *field; if (type() != MYSQL_TYPE_VAR_STRING || keep_type) - field= Field::new_field(root, new_table, keep_type); - else if ((field= new Field_varstring(field_length, maybe_null(), field_name, - new_table->s, charset()))) + field= Field::make_new_field(root, new_table, keep_type); + else if ((field= new (root) Field_varstring(field_length, maybe_null(), + field_name, + new_table->s, charset()))) { /* Old VARCHAR field which should be modified to a VARCHAR on copy @@ -6549,10 +7410,11 @@ Field *Field_string::new_field(MEM_ROOT *root, TABLE *new_table, */ field->init(new_table); /* - Normally orig_table is different from table only if field was created - via ::new_field. Here we alter the type of field, so ::new_field is - not applicable. But we still need to preserve the original field - metadata for the client-server protocol. + Normally orig_table is different from table only if field was + created via ::make_new_field. Here we alter the type of field, + so ::make_new_field is not applicable. But we still need to + preserve the original field metadata for the client-server + protocol. */ field->orig_table= orig_table; } @@ -6600,29 +7462,19 @@ int Field_varstring::store(const char *from,uint length,CHARSET_INFO *cs) { ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; uint copy_length; - const char *well_formed_error_pos; - const char *cannot_convert_error_pos; - const char *from_end_pos; + String_copier copier; - copy_length= well_formed_copy_nchars(field_charset, + copy_length= copier.well_formed_copy(field_charset, (char*) ptr + length_bytes, field_length, cs, from, length, - field_length / field_charset->mbmaxlen, - &well_formed_error_pos, - &cannot_convert_error_pos, - &from_end_pos); - + field_length / field_charset->mbmaxlen); if (length_bytes == 1) *ptr= (uchar) copy_length; else int2store(ptr, copy_length); - if (check_string_copy_error(this, well_formed_error_pos, - cannot_convert_error_pos, from + length, cs)) - return 2; - - return report_if_important_data(from_end_pos, from + length, TRUE); + return check_conversion_status(&copier, from + length, cs, true); } @@ -6643,54 +7495,30 @@ int Field_varstring::store(longlong nr, bool unsigned_val) double Field_varstring::val_real(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int error; - char *end; - double result; - CHARSET_INFO* cs= charset(); - - uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); - result= my_strntod(cs, (char*)ptr+length_bytes, length, &end, &error); - - if (!table->in_use->no_errors && - (error || (length != (uint)(end - (char*)ptr+length_bytes) && - !check_if_only_end_space(cs, end, (char*)ptr+length_bytes+length)))) - { - push_numerical_conversion_warning(current_thd, (char*)ptr+length_bytes, - length, cs,"DOUBLE", - ER_TRUNCATED_WRONG_VALUE); - } - return result; + THD *thd= get_thd(); + return Converter_strntod_with_warn(thd, Warn_filter(thd), + Field_varstring::charset(), + (const char *) get_data(), + get_length()).result(); } longlong Field_varstring::val_int(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int error; - char *end; - CHARSET_INFO *cs= charset(); - - uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); - longlong result= my_strntoll(cs, (char*) ptr+length_bytes, length, 10, - &end, &error); - - if (!table->in_use->no_errors && - (error || (length != (uint)(end - (char*)ptr+length_bytes) && - !check_if_only_end_space(cs, end, (char*)ptr+length_bytes+length)))) - { - push_numerical_conversion_warning(current_thd, (char*)ptr+length_bytes, - length, cs, "INTEGER", - ER_TRUNCATED_WRONG_VALUE); - } - return result; + THD *thd= get_thd(); + return Converter_strntoll_with_warn(thd, Warn_filter(thd), + Field_varstring::charset(), + (const char *) get_data(), + get_length()).result(); } + String *Field_varstring::val_str(String *val_buffer __attribute__((unused)), String *val_ptr) { ASSERT_COLUMN_MARKED_FOR_READ; - uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); - val_ptr->set((const char*) ptr+length_bytes, length, field_charset); + val_ptr->set((const char*) get_data(), get_length(), field_charset); return val_ptr; } @@ -6698,18 +7526,14 @@ String *Field_varstring::val_str(String *val_buffer __attribute__((unused)), my_decimal *Field_varstring::val_decimal(my_decimal *decimal_value) { ASSERT_COLUMN_MARKED_FOR_READ; - CHARSET_INFO *cs= charset(); - uint length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); - int error= str2my_decimal(E_DEC_FATAL_ERROR, (char*) ptr+length_bytes, length, - cs, decimal_value); - - if (!table->in_use->no_errors && error) - { - push_numerical_conversion_warning(current_thd, (char*)ptr+length_bytes, - length, cs, "DECIMAL", - ER_TRUNCATED_WRONG_VALUE); - } + THD *thd= get_thd(); + Converter_str2my_decimal_with_warn(thd, Warn_filter(thd), + E_DEC_FATAL_ERROR, + Field_varstring::charset(), + (const char *) get_data(), + get_length(), decimal_value); return decimal_value; + } @@ -6797,9 +7621,13 @@ void Field_varstring::sort_string(uchar *to,uint length) length-= length_bytes; } - tot_length= my_strnxfrm(field_charset, - to, length, ptr + length_bytes, - tot_length); + tot_length= field_charset->coll->strnxfrm(field_charset, + to, length, + char_length() * + field_charset->strxfrm_multiply, + ptr + length_bytes, tot_length, + MY_STRXFRM_PAD_WITH_SPACE | + MY_STRXFRM_PAD_TO_MAXLEN); DBUG_ASSERT(tot_length == length); } @@ -6924,7 +7752,8 @@ uint Field_varstring::max_packed_col_length(uint max_length) return (max_length > 255 ? 2 : 1)+max_length; } -uint Field_varstring::get_key_image(uchar *buff, uint length, imagetype type) +uint Field_varstring::get_key_image(uchar *buff, uint length, + imagetype type_arg) { uint f_length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); uint local_char_length= length / field_charset->mbmaxlen; @@ -6978,28 +7807,26 @@ int Field_varstring::cmp_binary(const uchar *a_ptr, const uchar *b_ptr, } -Field *Field_varstring::new_field(MEM_ROOT *root, TABLE *new_table, - bool keep_type) +Field *Field_varstring::make_new_field(MEM_ROOT *root, TABLE *new_table, + bool keep_type) { - Field_varstring *res= (Field_varstring*) Field::new_field(root, new_table, - keep_type); + Field_varstring *res= (Field_varstring*) Field::make_new_field(root, + new_table, + keep_type); if (res) res->length_bytes= length_bytes; return res; } -Field *Field_varstring::new_key_field(MEM_ROOT *root, - TABLE *new_table, - uchar *new_ptr, uchar *new_null_ptr, - uint new_null_bit) +Field *Field_varstring::new_key_field(MEM_ROOT *root, TABLE *new_table, + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit) { Field_varstring *res; - if ((res= (Field_varstring*) Field::new_key_field(root, - new_table, - new_ptr, - new_null_ptr, - new_null_bit))) + if ((res= (Field_varstring*) Field::new_key_field(root, new_table, + new_ptr, length, + new_null_ptr, new_null_bit))) { /* Keys length prefixes are always packed with 2 bytes */ res->length_bytes= 2; @@ -7007,7 +7834,6 @@ Field *Field_varstring::new_key_field(MEM_ROOT *root, return res; } - uint Field_varstring::is_equal(Create_field *new_field) { if (new_field->sql_type == real_type() && @@ -7106,9 +7932,8 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) { ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; uint copy_length, new_length; - const char *well_formed_error_pos; - const char *cannot_convert_error_pos; - const char *from_end_pos, *tmp; + String_copier copier; + char *tmp; char buff[STRING_BUFFER_USUAL_SIZE]; String tmpstr(buff,sizeof(buff), &my_charset_bin); @@ -7119,6 +7944,35 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) } /* + For min/max fields of statistical data 'table' is set to NULL. + It could not be otherwise as this data is shared by many instances + of the same base table. + */ + + if (table && table->blob_storage) // GROUP_CONCAT with ORDER BY | DISTINCT + { + DBUG_ASSERT(!f_is_hex_escape(flags)); + DBUG_ASSERT(field_charset == cs); + DBUG_ASSERT(length <= max_data_length()); + + new_length= length; + copy_length= table->in_use->variables.group_concat_max_len; + if (new_length > copy_length) + { + int well_formed_error; + new_length= cs->cset->well_formed_len(cs, from, from + copy_length, + new_length, &well_formed_error); + table->blob_storage->set_truncated_value(true); + } + if (!(tmp= table->blob_storage->store(from, new_length))) + goto oom_error; + + Field_blob::store_length(new_length); + bmove(ptr + packlength, (uchar*) &tmp, sizeof(char*)); + return 0; + } + + /* If the 'from' address is in the range of the temporary 'value'- object we need to copy the content to a different location or it will be invalidated when the 'value'-object is reallocated to make room for @@ -7141,43 +7995,27 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) from= tmpstr.ptr(); } - new_length= min(max_data_length(), field_charset->mbmaxlen * length); + new_length= MY_MIN(max_data_length(), field_charset->mbmaxlen * length); if (value.alloc(new_length)) goto oom_error; - + tmp= const_cast<char*>(value.ptr()); if (f_is_hex_escape(flags)) { copy_length= my_copy_with_hex_escaping(field_charset, - (char*) value.ptr(), new_length, - from, length); + tmp, new_length, + from, length); Field_blob::store_length(copy_length); - tmp= value.ptr(); bmove(ptr + packlength, (uchar*) &tmp, sizeof(char*)); return 0; } - /* - "length" is OK as "nchars" argument to well_formed_copy_nchars as this - is never used to limit the length of the data. The cut of long data - is done with the new_length value. - */ - copy_length= well_formed_copy_nchars(field_charset, + copy_length= copier.well_formed_copy(field_charset, (char*) value.ptr(), new_length, - cs, from, length, - length, - &well_formed_error_pos, - &cannot_convert_error_pos, - &from_end_pos); - + cs, from, length); Field_blob::store_length(copy_length); - tmp= value.ptr(); bmove(ptr+packlength,(uchar*) &tmp,sizeof(char*)); - if (check_string_copy_error(this, well_formed_error_pos, - cannot_convert_error_pos, from + length, cs)) - return 2; - - return report_if_important_data(from_end_pos, from + length, TRUE); + return check_conversion_status(&copier, from + length, cs, true); oom_error: /* Fatal OOM error */ @@ -7205,32 +8043,31 @@ int Field_blob::store(longlong nr, bool unsigned_val) double Field_blob::val_real(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int not_used; - char *end_not_used, *blob; - uint32 length; - CHARSET_INFO *cs; - + char *blob; memcpy(&blob, ptr+packlength, sizeof(char*)); if (!blob) return 0.0; - length= get_length(ptr); - cs= charset(); - return my_strntod(cs, blob, length, &end_not_used, ¬_used); + THD *thd= get_thd(); + return Converter_strntod_with_warn(thd, Warn_filter(thd), + Field_blob::charset(), + blob, get_length(ptr)).result(); } longlong Field_blob::val_int(void) { ASSERT_COLUMN_MARKED_FOR_READ; - int not_used; char *blob; memcpy(&blob, ptr+packlength, sizeof(char*)); if (!blob) return 0; - uint32 length=get_length(ptr); - return my_strntoll(charset(),blob,length,10,NULL,¬_used); + THD *thd= get_thd(); + return Converter_strntoll_with_warn(thd, Warn_filter(thd), + Field_blob::charset(), + blob, get_length(ptr)).result(); } + String *Field_blob::val_str(String *val_buffer __attribute__((unused)), String *val_ptr) { @@ -7259,8 +8096,11 @@ my_decimal *Field_blob::val_decimal(my_decimal *decimal_value) else length= get_length(ptr); - str2my_decimal(E_DEC_FATAL_ERROR, blob, length, charset(), - decimal_value); + THD *thd= get_thd(); + Converter_str2my_decimal_with_warn(thd, Warn_filter(thd), + E_DEC_FATAL_ERROR, + Field_blob::charset(), + blob, length, decimal_value); return decimal_value; } @@ -7301,7 +8141,7 @@ int Field_blob::cmp_binary(const uchar *a_ptr, const uchar *b_ptr, b_length=get_length(b_ptr); if (b_length > max_length) b_length=max_length; - diff=memcmp(a,b,min(a_length,b_length)); + diff=memcmp(a,b,MY_MIN(a_length,b_length)); return diff ? diff : (int) (a_length - b_length); } @@ -7393,6 +8233,18 @@ int Field_blob::key_cmp(const uchar *a,const uchar *b) } +Field *Field_blob::new_key_field(MEM_ROOT *root, TABLE *new_table, + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit) +{ + Field_varstring *res= new (root) Field_varstring(new_ptr, length, 2, + new_null_ptr, new_null_bit, Field::NONE, + field_name, table->s, charset()); + res->init(new_table); + return res; +} + + /** Save the field metadata for blob fields. @@ -7414,7 +8266,7 @@ int Field_blob::do_save_field_metadata(uchar *metadata_ptr) uint32 Field_blob::sort_length() const { - return (uint32) (current_thd->variables.max_sort_length + + return (uint32) (get_thd()->variables.max_sort_length + (field_charset == &my_charset_bin ? 0 : packlength)); } @@ -7442,8 +8294,11 @@ void Field_blob::sort_string(uchar *to,uint length) } memcpy(&blob, ptr+packlength, sizeof(char*)); - blob_length=my_strnxfrm(field_charset, - to, length, blob, blob_length); + blob_length= field_charset->coll->strnxfrm(field_charset, + to, length, length, + blob, blob_length, + MY_STRXFRM_PAD_WITH_SPACE | + MY_STRXFRM_PAD_TO_MAXLEN); DBUG_ASSERT(blob_length == length); } } @@ -7479,7 +8334,7 @@ uchar *Field_blob::pack(uchar *to, const uchar *from, uint max_length) length given is smaller than the actual length of the blob, we just store the initial bytes of the blob. */ - store_length(to, packlength, min(length, max_length)); + store_length(to, packlength, MY_MIN(length, max_length)); /* Store the actual blob data, which will occupy 'length' bytes. @@ -7549,9 +8404,6 @@ uint Field_blob::max_packed_col_length(uint max_length) uint Field_blob::is_equal(Create_field *new_field) { - if (field_flags_are_binary() != new_field->field_flags_are_binary()) - return 0; - return ((new_field->sql_type == get_blob_type_from_length(max_data_length())) && new_field->charset == field_charset && new_field->pack_length == pack_length()); @@ -7559,6 +8411,104 @@ uint Field_blob::is_equal(Create_field *new_field) #ifdef HAVE_SPATIAL +/* Values 1-40 reserved for 1-byte options, + 41-80 for 2-byte options, + 81-120 for 4-byte options, + 121-160 for 8-byte options, + other - varied length in next 1-3 bytes. +*/ +enum extra2_gis_field_options { + FIELDGEOM_END=0, + FIELDGEOM_STORAGE_MODEL=1, + FIELDGEOM_PRECISION=2, + FIELDGEOM_SCALE=3, + FIELDGEOM_SRID=81, +}; + + +uint gis_field_options_image(uchar *buff, List<Create_field> &create_fields) +{ + uint image_size= 0; + List_iterator<Create_field> it(create_fields); + Create_field *field; + while ((field= it++)) + { + if (field->sql_type != MYSQL_TYPE_GEOMETRY) + continue; + if (buff) + { + uchar *cbuf= buff + image_size; + + cbuf[0]= FIELDGEOM_STORAGE_MODEL; + cbuf[1]= (uchar) Field_geom::GEOM_STORAGE_WKB; + + cbuf[2]= FIELDGEOM_PRECISION; + cbuf[3]= (uchar) field->length; + + cbuf[4]= FIELDGEOM_SCALE; + cbuf[5]= (uchar) field->decimals; + + cbuf[6]= FIELDGEOM_SRID; + int4store(cbuf + 7, ((uint32) field->srid)); + + cbuf[11]= FIELDGEOM_END; + } + image_size+= 12; + } + + return image_size; +} + + +uint gis_field_options_read(const uchar *buf, uint buf_len, + Field_geom::storage_type *st_type,uint *precision, uint *scale, uint *srid) +{ + const uchar *buf_end= buf + buf_len; + const uchar *cbuf= buf; + int option_id; + + *precision= *scale= *srid= 0; + *st_type= Field_geom::GEOM_STORAGE_WKB; + + if (!buf) /* can only happen with the old FRM file */ + goto end_of_record; + + while (cbuf < buf_end) + { + switch ((option_id= *(cbuf++))) + { + case FIELDGEOM_STORAGE_MODEL: + *st_type= (Field_geom::storage_type) cbuf[0]; + break; + case FIELDGEOM_PRECISION: + *precision= cbuf[0]; + break; + case FIELDGEOM_SCALE: + *scale= cbuf[0]; + break; + case FIELDGEOM_SRID: + *srid= uint4korr(cbuf); + break; + case FIELDGEOM_END: + goto end_of_record; + } + if (option_id > 0 && option_id <= 40) + cbuf+= 1; + else if (option_id > 40 && option_id <= 80) + cbuf+= 2; + else if (option_id > 80 && option_id <= 120) + cbuf+= 4; + else if (option_id > 120 && option_id <= 160) + cbuf+= 8; + else /* > 160 and <=255 */ + cbuf+= cbuf[0] ? 1 + cbuf[0] : 3 + uint2korr(cbuf+1); + } + +end_of_record: + return cbuf - buf; +} + + void Field_geom::sql_type(String &res) const { @@ -7595,7 +8545,7 @@ void Field_geom::sql_type(String &res) const int Field_geom::store(double nr) { my_message(ER_CANT_CREATE_GEOMETRY_OBJECT, - ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); + ER_THD(get_thd(), ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); return -1; } @@ -7603,7 +8553,7 @@ int Field_geom::store(double nr) int Field_geom::store(longlong nr, bool unsigned_val) { my_message(ER_CANT_CREATE_GEOMETRY_OBJECT, - ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); + ER_THD(get_thd(), ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); return -1; } @@ -7611,7 +8561,7 @@ int Field_geom::store(longlong nr, bool unsigned_val) int Field_geom::store_decimal(const my_decimal *) { my_message(ER_CANT_CREATE_GEOMETRY_OBJECT, - ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); + ER_THD(get_thd(), ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); return -1; } @@ -7638,10 +8588,13 @@ int Field_geom::store(const char *from, uint length, CHARSET_INFO *cs) (uint32) geom_type != wkb_type) { my_printf_error(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, - ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), MYF(0), - Geometry::ci_collection[geom_type]->m_name.str, - Geometry::ci_collection[wkb_type]->m_name.str, field_name, - (ulong) table->in_use->warning_info->current_row_for_warning()); + ER_THD(get_thd(), ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), + MYF(0), + Geometry::ci_collection[geom_type]->m_name.str, + Geometry::ci_collection[wkb_type]->m_name.str, + field_name, + (ulong) table->in_use->get_stmt_da()-> + current_row_for_warning()); goto err_exit; } @@ -7658,7 +8611,7 @@ int Field_geom::store(const char *from, uint length, CHARSET_INFO *cs) err: my_message(ER_CANT_CREATE_GEOMETRY_OBJECT, - ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); + ER_THD(get_thd(), ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); err_exit: bzero(ptr, Field_blob::pack_length()); return -1; @@ -7672,6 +8625,27 @@ Field::geometry_type Field_geom::geometry_type_merge(geometry_type a, return Field::GEOM_GEOMETRY; } + +uint Field_geom::is_equal(Create_field *new_field) +{ + return new_field->sql_type == MYSQL_TYPE_GEOMETRY && + /* + - Allow ALTER..INPLACE to supertype (GEOMETRY), + e.g. POINT to GEOMETRY or POLYGON to GEOMETRY. + - Allow ALTER..INPLACE to the same geometry type: POINT -> POINT + */ + (new_field->geom_type == geom_type || + new_field->geom_type == GEOM_GEOMETRY); +} + + +bool Field_geom::can_optimize_range(const Item_bool_func *cond, + const Item *item, + bool is_eq_func) const +{ + return item->cmp_type() == STRING_RESULT; +} + #endif /*HAVE_SPATIAL*/ /**************************************************************************** @@ -7732,13 +8706,13 @@ int Field_enum::store(const char *from,uint length,CHARSET_INFO *cs) if (err || end != from+length || tmp > typelib->count) { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); } - if (!table->in_use->count_cuted_fields) + if (!get_thd()->count_cuted_fields) err= 0; } else - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); } store_type((ulonglong) tmp); return err; @@ -7757,8 +8731,8 @@ int Field_enum::store(longlong nr, bool unsigned_val) int error= 0; if ((ulonglong) nr > typelib->count || nr == 0) { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); - if (nr != 0 || table->in_use->count_cuted_fields) + set_warning(WARN_DATA_TRUNCATED, 1); + if (nr != 0 || get_thd()->count_cuted_fields) { nr= 0; error= 1; @@ -7861,10 +8835,11 @@ void Field_enum::sql_type(String &res) const } -Field *Field_enum::new_field(MEM_ROOT *root, TABLE *new_table, - bool keep_type) +Field *Field_enum::make_new_field(MEM_ROOT *root, TABLE *new_table, + bool keep_type) { - Field_enum *res= (Field_enum*) Field::new_field(root, new_table, keep_type); + Field_enum *res= (Field_enum*) Field::make_new_field(root, new_table, + keep_type); if (res) res->typelib= copy_typelib(root, typelib); return res; @@ -7910,11 +8885,11 @@ int Field_set::store(const char *from,uint length,CHARSET_INFO *cs) tmp > (ulonglong) (((longlong) 1 << typelib->count) - (longlong) 1)) { tmp=0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); } } else if (got_warning) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); store_type(tmp); return err; } @@ -7929,12 +8904,12 @@ int Field_set::store(longlong nr, bool unsigned_val) if (sizeof(ulonglong)*8 <= typelib->count) max_nr= ULONGLONG_MAX; else - max_nr= (ULL(1) << typelib->count) - 1; + max_nr= (1ULL << typelib->count) - 1; if ((ulonglong) nr > max_nr) { nr&= max_nr; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); + set_warning(WARN_DATA_TRUNCATED, 1); error=1; } store_type((ulonglong) nr); @@ -8075,8 +9050,7 @@ uint Field_enum::is_equal(Create_field *new_field) The fields are compatible if they have the same flags, type, charset and have the same underlying length. */ - if (new_field->field_flags_are_binary() != field_flags_are_binary() || - new_field->sql_type != real_type() || + if (new_field->sql_type != real_type() || new_field->charset != field_charset || new_field->pack_length != pack_length()) return IS_EQUAL_NO; @@ -8150,6 +9124,30 @@ uint Field_num::is_equal(Create_field *new_field) } +bool Field_enum::can_optimize_keypart_ref(const Item_bool_func *cond, + const Item *item) const +{ + DBUG_ASSERT(cmp_type() == INT_RESULT); + DBUG_ASSERT(result_type() == STRING_RESULT); + + switch (item->cmp_type()) + { + case TIME_RESULT: + return false; + case INT_RESULT: + case DECIMAL_RESULT: + case REAL_RESULT: + return true; + case STRING_RESULT: + return charset() == cond->compare_collation(); + case ROW_RESULT: + DBUG_ASSERT(0); + break; + } + return false; +} + + /* Bit field. @@ -8245,15 +9243,13 @@ Field_bit::do_last_null_byte() const } -Field *Field_bit::new_key_field(MEM_ROOT *root, - TABLE *new_table, - uchar *new_ptr, uchar *new_null_ptr, - uint new_null_bit) +Field *Field_bit::new_key_field(MEM_ROOT *root, TABLE *new_table, + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit) { Field_bit *res; - if ((res= (Field_bit*) Field::new_key_field(root, new_table, - new_ptr, new_null_ptr, - new_null_bit))) + if ((res= (Field_bit*) Field::new_key_field(root, new_table, new_ptr, length, + new_null_ptr, new_null_bit))) { /* Move bits normally stored in null_pointer to new_ptr */ res->bit_ptr= new_ptr; @@ -8287,10 +9283,10 @@ int Field_bit::store(const char *from, uint length, CHARSET_INFO *cs) { set_rec_bits((1 << bit_len) - 1, bit_ptr, bit_ofs, bit_len); memset(ptr, 0xff, bytes_in_rec); - if (table->in_use->really_abort_on_warning()) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1); + if (get_thd()->really_abort_on_warning()) + set_warning(ER_DATA_TOO_LONG, 1); else - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); return 1; } /* delta is >= -1 here */ @@ -8339,7 +9335,7 @@ int Field_bit::store_decimal(const my_decimal *val) { int err= 0; longlong i= convert_decimal2longlong(val, 1, &err); - return test(err | store(i, TRUE)); + return MY_TEST(err | store(i, TRUE)); } @@ -8378,7 +9374,7 @@ String *Field_bit::val_str(String *val_buffer, { ASSERT_COLUMN_MARKED_FOR_READ; char buff[sizeof(longlong)]; - uint length= min(pack_length(), sizeof(longlong)); + uint length= MY_MIN(pack_length(), sizeof(longlong)); ulonglong bits= val_int(); mi_int8store(buff,bits); @@ -8423,6 +9419,8 @@ int Field_bit::cmp_max(const uchar *a, const uchar *b, uint max_len) if ((flag= (int) (bits_a - bits_b))) return flag; } + if (!bytes_in_rec) + return 0; return memcmp(a, b, bytes_in_rec); } @@ -8464,9 +9462,9 @@ uint Field_bit::get_key_image(uchar *buff, uint length, imagetype type_arg) *buff++= bits; length--; } - uint data_length = min(length, bytes_in_rec); - memcpy(buff, ptr, data_length); - return data_length + 1; + uint tmp_data_length = MY_MIN(length, bytes_in_rec); + memcpy(buff, ptr, tmp_data_length); + return tmp_data_length + 1; } @@ -8588,7 +9586,7 @@ Field_bit::pack(uchar *to, const uchar *from, uint max_length) uchar bits= get_rec_bits(bit_ptr + (from - ptr), bit_ofs, bit_len); *to++= bits; } - length= min(bytes_in_rec, max_length - (bit_len > 0)); + length= MY_MIN(bytes_in_rec, max_length - (bit_len > 0)); memcpy(to, from, length); return to + length; } @@ -8626,7 +9624,7 @@ Field_bit::unpack(uchar *to, const uchar *from, const uchar *from_end, if (param_data == 0 || ((from_bit_len == bit_len) && (from_len == bytes_in_rec))) { - if (from + bytes_in_rec + test(bit_len) > from_end) + if (from + bytes_in_rec + MY_TEST(bit_len) > from_end) return 0; // Error in data if (bit_len > 0) @@ -8682,8 +9680,8 @@ void Field_bit::set_default() { if (bit_len > 0) { - my_ptrdiff_t const offset= table->s->default_values - table->record[0]; - uchar bits= get_rec_bits(bit_ptr + offset, bit_ofs, bit_len); + my_ptrdiff_t const col_offset= table->s->default_values - table->record[0]; + uchar bits= get_rec_bits(bit_ptr + col_offset, bit_ofs, bit_len); set_rec_bits(bits, bit_ptr, bit_ofs, bit_len); } Field::set_default(); @@ -8722,10 +9720,10 @@ int Field_bit_as_char::store(const char *from, uint length, CHARSET_INFO *cs) memset(ptr, 0xff, bytes_in_rec); if (bits) *ptr&= ((1 << bits) - 1); /* set first uchar */ - if (table->in_use->really_abort_on_warning()) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1); + if (get_thd()->really_abort_on_warning()) + set_warning(ER_DATA_TOO_LONG, 1); else - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + set_warning(ER_WARN_DATA_OUT_OF_RANGE, 1); return 1; } bzero(ptr, delta); @@ -8781,17 +9779,22 @@ void Create_field::create_length_to_internal_length(void) { pack_length= length / 8; /* We need one extra byte to store the bits we save among the null bits */ - key_length= pack_length + test(length & 7); + key_length= pack_length + MY_TEST(length & 7); } break; case MYSQL_TYPE_NEWDECIMAL: - key_length= pack_length= - my_decimal_get_binary_size(my_decimal_length_to_precision(length, - decimals, - flags & - UNSIGNED_FLAG), - decimals); + { + /* + This code must be identical to code in + Field_new_decimal::Field_new_decimal as otherwise the record layout + gets out of sync. + */ + uint precision= my_decimal_length_to_precision(length, decimals, + flags & UNSIGNED_FLAG); + set_if_smaller(precision, DECIMAL_MAX_PRECISION); + key_length= pack_length= my_decimal_get_binary_size(precision, decimals); break; + } default: key_length= pack_length= calc_pack_length(sql_type, length); break; @@ -8904,80 +9907,107 @@ void Create_field::init_for_tmp_table(enum_field_types sql_type_arg, FLAGSTR(pack_flag, FIELDFLAG_DECIMAL), f_packtype(pack_flag))); vcol_info= 0; + create_if_not_exists= FALSE; stored_in_db= TRUE; DBUG_VOID_RETURN; } -/** - Initialize field definition for create. - - @param thd Thread handle - @param fld_name Field name - @param fld_type Field type - @param fld_length Field length - @param fld_decimals Decimal (if any) - @param fld_type_modifier Additional type information - @param fld_default_value Field default value (if any) - @param fld_on_update_value The value of ON UPDATE clause - @param fld_comment Field comment - @param fld_change Field change - @param fld_interval_list Interval list (if any) - @param fld_charset Field charset - @param fld_geom_type Field geometry type (if any) - @param fld_vcol_info Virtual column data +static inline bool is_item_func(Item* x) +{ + return x != NULL && x->type() == Item::FUNC_ITEM; +} - @retval - FALSE on success - @retval - TRUE on error -*/ -bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, - char *fld_length, char *fld_decimals, - uint fld_type_modifier, Item *fld_default_value, - Item *fld_on_update_value, LEX_STRING *fld_comment, - char *fld_change, List<String> *fld_interval_list, - CHARSET_INFO *fld_charset, uint fld_geom_type, - Virtual_column_info *fld_vcol_info, - engine_option_value *create_opt) +bool Create_field::check(THD *thd) { + const uint conditional_type_modifiers= AUTO_INCREMENT_FLAG; uint sign_len, allowed_type_modifier= 0; ulong max_field_charlength= MAX_FIELD_CHARLENGTH; + DBUG_ENTER("Create_field::check"); - DBUG_ENTER("Create_field::init()"); + if (vcol_info) + { + vcol_info->set_field_type(sql_type); + sql_type= (enum enum_field_types)MYSQL_TYPE_VIRTUAL; + } + + if (length > MAX_FIELD_BLOBLENGTH) + { + my_error(ER_TOO_BIG_DISPLAYWIDTH, MYF(0), field_name, MAX_FIELD_BLOBLENGTH); + DBUG_RETURN(1); + } - field= 0; - field_name= fld_name; - def= fld_default_value; - flags= fld_type_modifier; - option_list= create_opt; - unireg_check= (fld_type_modifier & AUTO_INCREMENT_FLAG ? - Field::NEXT_NUMBER : Field::NONE); - decimals= fld_decimals ? (uint)atoi(fld_decimals) : 0; if (decimals >= NOT_FIXED_DEC) { - my_error(ER_TOO_BIG_SCALE, MYF(0), decimals, fld_name, - static_cast<ulong>(NOT_FIXED_DEC - 1)); + my_error(ER_TOO_BIG_SCALE, MYF(0), static_cast<ulonglong>(decimals), + field_name, static_cast<ulong>(NOT_FIXED_DEC - 1)); DBUG_RETURN(TRUE); } - sql_type= fld_type; - length= 0; - change= fld_change; - interval= 0; - pack_length= key_length= 0; - charset= fld_charset; - geom_type= (Field::geometry_type) fld_geom_type; - interval_list.empty(); + if (def) + { + /* + Default value should be literal => basic constants => + no need fix_fields() - comment= *fld_comment; - vcol_info= fld_vcol_info; - stored_in_db= TRUE; + We allow only one function as part of default value - + NOW() as default for TIMESTAMP and DATETIME type. + */ + if (def->type() == Item::FUNC_ITEM && + (static_cast<Item_func*>(def)->functype() != Item_func::NOW_FUNC || + (mysql_type_to_time_type(sql_type) != MYSQL_TIMESTAMP_DATETIME) || + def->decimals < length)) + { + my_error(ER_INVALID_DEFAULT, MYF(0), field_name); + DBUG_RETURN(1); + } + else if (def->type() == Item::NULL_ITEM) + { + def= 0; + if ((flags & (NOT_NULL_FLAG | AUTO_INCREMENT_FLAG)) == NOT_NULL_FLAG) + { + my_error(ER_INVALID_DEFAULT, MYF(0), field_name); + DBUG_RETURN(1); + } + } + else if (flags & AUTO_INCREMENT_FLAG) + { + my_error(ER_INVALID_DEFAULT, MYF(0), field_name); + DBUG_RETURN(1); + } + } + + if (is_item_func(def)) + { + /* There is a function default for insertions. */ + def= NULL; + unireg_check= (is_item_func(on_update) ? + Field::TIMESTAMP_DNUN_FIELD : // for insertions and for updates. + Field::TIMESTAMP_DN_FIELD); // only for insertions. + } + else + { + /* No function default for insertions. Either NULL or a constant. */ + if (is_item_func(on_update)) + unireg_check= Field::TIMESTAMP_UN_FIELD; // function default for updates + else + unireg_check= ((flags & AUTO_INCREMENT_FLAG) ? + Field::NEXT_NUMBER : // Automatic increment. + Field::NONE); + } + + if (on_update && + (mysql_type_to_time_type(sql_type) != MYSQL_TIMESTAMP_DATETIME || + on_update->decimals < length)) + { + my_error(ER_INVALID_ON_UPDATE, MYF(0), field_name); + DBUG_RETURN(1); + } /* Initialize data for a computed field */ - if ((uchar)fld_type == (uchar)MYSQL_TYPE_VIRTUAL) + if (sql_type == MYSQL_TYPE_VIRTUAL) { DBUG_ASSERT(vcol_info && vcol_info->expr_item); stored_in_db= vcol_info->is_stored(); @@ -8997,56 +10027,34 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, Field::vcol_info. It is is always NULL for a column that is not computed. */ - sql_type= fld_type= vcol_info->get_real_type(); + sql_type= vcol_info->get_real_type(); } - /* - Set NO_DEFAULT_VALUE_FLAG if this field doesn't have a default value and - it is NOT NULL, not an AUTO_INCREMENT field and not a TIMESTAMP. - */ - if (!fld_default_value && !(fld_type_modifier & AUTO_INCREMENT_FLAG) && - (fld_type_modifier & NOT_NULL_FLAG) && fld_type != MYSQL_TYPE_TIMESTAMP) - flags|= NO_DEFAULT_VALUE_FLAG; - - if (fld_length != NULL) - { - errno= 0; - length= strtoul(fld_length, NULL, 10); - if ((errno != 0) || (length > MAX_FIELD_BLOBLENGTH)) - { - my_error(ER_TOO_BIG_DISPLAYWIDTH, MYF(0), fld_name, MAX_FIELD_BLOBLENGTH); - DBUG_RETURN(TRUE); - } - - if (length == 0) - fld_length= NULL; /* purecov: inspected */ - } + sign_len= flags & UNSIGNED_FLAG ? 0 : 1; - sign_len= fld_type_modifier & UNSIGNED_FLAG ? 0 : 1; - - switch (fld_type) { + switch (sql_type) { case MYSQL_TYPE_TINY: - if (!fld_length) + if (!length) length= MAX_TINYINT_WIDTH+sign_len; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; case MYSQL_TYPE_SHORT: - if (!fld_length) + if (!length) length= MAX_SMALLINT_WIDTH+sign_len; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; case MYSQL_TYPE_INT24: - if (!fld_length) + if (!length) length= MAX_MEDIUMINT_WIDTH+sign_len; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; case MYSQL_TYPE_LONG: - if (!fld_length) + if (!length) length= MAX_INT_WIDTH+sign_len; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; case MYSQL_TYPE_LONGLONG: - if (!fld_length) + if (!length) length= MAX_BIGINT_WIDTH; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; @@ -9056,18 +10064,17 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, my_decimal_trim(&length, &decimals); if (length > DECIMAL_MAX_PRECISION) { - my_error(ER_TOO_BIG_PRECISION, MYF(0), static_cast<int>(length), - fld_name, static_cast<ulong>(DECIMAL_MAX_PRECISION)); + my_error(ER_TOO_BIG_PRECISION, MYF(0), length, field_name, + DECIMAL_MAX_PRECISION); DBUG_RETURN(TRUE); } if (length < decimals) { - my_error(ER_M_BIGGER_THAN_D, MYF(0), fld_name); + my_error(ER_M_BIGGER_THAN_D, MYF(0), field_name); DBUG_RETURN(TRUE); } length= - my_decimal_precision_to_length(length, decimals, - fld_type_modifier & UNSIGNED_FLAG); + my_decimal_precision_to_length(length, decimals, flags & UNSIGNED_FLAG); pack_length= my_decimal_get_binary_size(length, decimals); break; @@ -9085,21 +10092,19 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_GEOMETRY: - if (fld_default_value) + if (def) { /* Allow empty as default value. */ String str,*res; - res= fld_default_value->val_str(&str); + res= def->val_str(&str); /* A default other than '' is always an error, and any non-NULL specified default is an error in strict mode. */ - if (res->length() || (thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES))) + if (res->length() || thd->is_strict_mode()) { my_error(ER_BLOB_CANT_HAVE_DEFAULT, MYF(0), - fld_name); /* purecov: inspected */ + field_name); /* purecov: inspected */ DBUG_RETURN(TRUE); } else @@ -9107,42 +10112,24 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, /* Otherwise a default of '' is just a warning. */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BLOB_CANT_HAVE_DEFAULT, - ER(ER_BLOB_CANT_HAVE_DEFAULT), - fld_name); + ER_THD(thd, ER_BLOB_CANT_HAVE_DEFAULT), + field_name); } def= 0; } flags|= BLOB_FLAG; break; case MYSQL_TYPE_YEAR: - if (!fld_length || length != 2) + if (!length || length != 2) length= 4; /* Default length */ flags|= ZEROFILL_FLAG | UNSIGNED_FLAG; break; case MYSQL_TYPE_FLOAT: /* change FLOAT(precision) to FLOAT or DOUBLE */ allowed_type_modifier= AUTO_INCREMENT_FLAG; - if (fld_length && !fld_decimals) - { - uint tmp_length= length; - if (tmp_length > PRECISION_FOR_DOUBLE) - { - my_error(ER_WRONG_FIELD_SPEC, MYF(0), fld_name); - DBUG_RETURN(TRUE); - } - else if (tmp_length > PRECISION_FOR_FLOAT) - { - sql_type= MYSQL_TYPE_DOUBLE; - length= MAX_DOUBLE_STR_LENGTH; - } - else - length= MAX_FLOAT_STR_LENGTH; - decimals= NOT_FIXED_DEC; - break; - } - if (!fld_length && !fld_decimals) + if (!length && !decimals) { length= MAX_FLOAT_STR_LENGTH; decimals= NOT_FIXED_DEC; @@ -9150,13 +10137,13 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, if (length < decimals && decimals != NOT_FIXED_DEC) { - my_error(ER_M_BIGGER_THAN_D, MYF(0), fld_name); + my_error(ER_M_BIGGER_THAN_D, MYF(0), field_name); DBUG_RETURN(TRUE); } break; case MYSQL_TYPE_DOUBLE: allowed_type_modifier= AUTO_INCREMENT_FLAG; - if (!fld_length && !fld_decimals) + if (!length && !decimals) { length= DBL_DIG+7; decimals= NOT_FIXED_DEC; @@ -9164,57 +10151,20 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, if (length < decimals && decimals != NOT_FIXED_DEC) { - my_error(ER_M_BIGGER_THAN_D, MYF(0), fld_name); + my_error(ER_M_BIGGER_THAN_D, MYF(0), field_name); DBUG_RETURN(TRUE); } break; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: if (length > MAX_DATETIME_PRECISION) { - my_error(ER_TOO_BIG_PRECISION, MYF(0), length, fld_name, + my_error(ER_TOO_BIG_PRECISION, MYF(0), length, field_name, MAX_DATETIME_PRECISION); DBUG_RETURN(TRUE); } length+= MAX_DATETIME_WIDTH + (length ? 1 : 0); flags|= UNSIGNED_FLAG; - - if (fld_default_value) - { - /* Grammar allows only NOW() value for ON UPDATE clause */ - if (fld_default_value->type() == Item::FUNC_ITEM && - ((Item_func*)fld_default_value)->functype() == Item_func::NOW_FUNC) - { - unireg_check= (fld_on_update_value ? Field::TIMESTAMP_DNUN_FIELD: - Field::TIMESTAMP_DN_FIELD); - /* - We don't need default value any longer moreover it is dangerous. - Everything handled by unireg_check further. - */ - def= 0; - } - else - unireg_check= (fld_on_update_value ? Field::TIMESTAMP_UN_FIELD: - Field::NONE); - } - else - { - /* - If we have default TIMESTAMP NOT NULL column without explicit DEFAULT - or ON UPDATE values then for the sake of compatiblity we should treat - this column as having DEFAULT NOW() ON UPDATE NOW() (when we don't - have another TIMESTAMP column with auto-set option before this one) - or DEFAULT 0 (in other cases). - So here we are setting TIMESTAMP_OLD_FIELD only temporary, and will - replace this value by TIMESTAMP_DNUN_FIELD or NONE later when - information about all TIMESTAMP fields in table will be availiable. - - If we have TIMESTAMP NULL column without explicit DEFAULT value - we treat it as having DEFAULT NULL attribute. - */ - unireg_check= (fld_on_update_value ? Field::TIMESTAMP_UN_FIELD : - (flags & NOT_NULL_FLAG ? Field::TIMESTAMP_OLD_FIELD : - Field::NONE)); - } break; case MYSQL_TYPE_DATE: /* We don't support creation of MYSQL_TYPE_DATE anymore */ @@ -9224,61 +10174,42 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, length= MAX_DATE_WIDTH; break; case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: if (length > MAX_DATETIME_PRECISION) { - my_error(ER_TOO_BIG_PRECISION, MYF(0), length, fld_name, + my_error(ER_TOO_BIG_PRECISION, MYF(0), length, field_name, MAX_DATETIME_PRECISION); DBUG_RETURN(TRUE); } length+= MIN_TIME_WIDTH + (length ? 1 : 0); break; case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: if (length > MAX_DATETIME_PRECISION) { - my_error(ER_TOO_BIG_PRECISION, MYF(0), length, fld_name, + my_error(ER_TOO_BIG_PRECISION, MYF(0), length, field_name, MAX_DATETIME_PRECISION); DBUG_RETURN(TRUE); } length+= MAX_DATETIME_WIDTH + (length ? 1 : 0); break; case MYSQL_TYPE_SET: - { - pack_length= get_set_pack_length(fld_interval_list->elements); - - List_iterator<String> it(*fld_interval_list); - String *tmp; - while ((tmp= it++)) - interval_list.push_back(tmp); - /* - Set fake length to 1 to pass the below conditions. - Real length will be set in mysql_prepare_table() - when we know the character set of the column - */ - length= 1; - break; - } + pack_length= get_set_pack_length(interval_list.elements); + break; case MYSQL_TYPE_ENUM: - { - /* Should be safe. */ - pack_length= get_enum_pack_length(fld_interval_list->elements); - - List_iterator<String> it(*fld_interval_list); - String *tmp; - while ((tmp= it++)) - interval_list.push_back(tmp); - length= 1; /* See comment for MYSQL_TYPE_SET above. */ - break; - } + /* Should be safe. */ + pack_length= get_enum_pack_length(interval_list.elements); + break; case MYSQL_TYPE_VAR_STRING: DBUG_ASSERT(0); /* Impossible. */ break; case MYSQL_TYPE_BIT: { - if (!fld_length) + if (!length) length= 1; if (length > MAX_BIT_FIELD_LENGTH) { - my_error(ER_TOO_BIG_DISPLAYWIDTH, MYF(0), fld_name, + my_error(ER_TOO_BIG_DISPLAYWIDTH, MYF(0), field_name, static_cast<ulong>(MAX_BIT_FIELD_LENGTH)); DBUG_RETURN(TRUE); } @@ -9287,48 +10218,54 @@ bool Create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, } case MYSQL_TYPE_DECIMAL: DBUG_ASSERT(0); /* Was obsolete */ - } + } /* Remember the value of length */ char_length= length; + /* + Set NO_DEFAULT_VALUE_FLAG if this field doesn't have a default value and + it is NOT NULL, not an AUTO_INCREMENT field. + We need to do this check here and in mysql_create_prepare_table() as + sp_head::fill_field_definition() calls this function. + */ + if (!def && unireg_check == Field::NONE && (flags & NOT_NULL_FLAG)) + { + /* + TIMESTAMP columns get implicit DEFAULT value when + explicit_defaults_for_timestamp is not set. + */ + if (opt_explicit_defaults_for_timestamp || + !is_timestamp_type(sql_type)) + { + flags|= NO_DEFAULT_VALUE_FLAG; + } + } + if (!(flags & BLOB_FLAG) && - ((length > max_field_charlength && fld_type != MYSQL_TYPE_SET && - fld_type != MYSQL_TYPE_ENUM && - (fld_type != MYSQL_TYPE_VARCHAR || fld_default_value)) || - ((length == 0) && - fld_type != MYSQL_TYPE_STRING && - fld_type != MYSQL_TYPE_VARCHAR && fld_type != MYSQL_TYPE_GEOMETRY))) - { - my_error((fld_type == MYSQL_TYPE_VAR_STRING || - fld_type == MYSQL_TYPE_VARCHAR || - fld_type == MYSQL_TYPE_STRING) ? ER_TOO_BIG_FIELDLENGTH : + ((length > max_field_charlength && + (sql_type != MYSQL_TYPE_VARCHAR || def)) || + (length == 0 && + sql_type != MYSQL_TYPE_ENUM && sql_type != MYSQL_TYPE_SET && + sql_type != MYSQL_TYPE_STRING && sql_type != MYSQL_TYPE_VARCHAR && + sql_type != MYSQL_TYPE_GEOMETRY))) + { + my_error((sql_type == MYSQL_TYPE_VAR_STRING || + sql_type == MYSQL_TYPE_VARCHAR || + sql_type == MYSQL_TYPE_STRING) ? ER_TOO_BIG_FIELDLENGTH : ER_TOO_BIG_DISPLAYWIDTH, MYF(0), - fld_name, max_field_charlength); /* purecov: inspected */ + field_name, max_field_charlength); /* purecov: inspected */ DBUG_RETURN(TRUE); } - fld_type_modifier&= AUTO_INCREMENT_FLAG; - if ((~allowed_type_modifier) & fld_type_modifier) + if ((~allowed_type_modifier) & flags & conditional_type_modifiers) { - my_error(ER_WRONG_FIELD_SPEC, MYF(0), fld_name); + my_error(ER_WRONG_FIELD_SPEC, MYF(0), field_name); DBUG_RETURN(TRUE); } - switch (fld_type) { - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_NEWDATE: - case MYSQL_TYPE_TIME: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_TIMESTAMP: - charset= &my_charset_numeric; - flags|= BINARY_FLAG; - default: break; - } - DBUG_RETURN(FALSE); /* success */ } - enum_field_types get_blob_type_from_length(ulong length) { enum_field_types type; @@ -9363,10 +10300,16 @@ uint32 calc_pack_length(enum_field_types type,uint32 length) case MYSQL_TYPE_TIME: return length > MIN_TIME_WIDTH ? time_hires_bytes[length - 1 - MIN_TIME_WIDTH] : 3; + case MYSQL_TYPE_TIME2: + return length > MIN_TIME_WIDTH ? + my_time_binary_length(length - MIN_TIME_WIDTH - 1) : 3; case MYSQL_TYPE_TIMESTAMP: return length > MAX_DATETIME_WIDTH ? 4 + sec_part_bytes[length - 1 - MAX_DATETIME_WIDTH] : 4; + case MYSQL_TYPE_TIMESTAMP2: + return length > MAX_DATETIME_WIDTH ? + my_timestamp_binary_length(length - MAX_DATETIME_WIDTH - 1) : 4; case MYSQL_TYPE_DATE: case MYSQL_TYPE_LONG : return 4; case MYSQL_TYPE_FLOAT : return sizeof(float); @@ -9375,6 +10318,9 @@ uint32 calc_pack_length(enum_field_types type,uint32 length) return length > MAX_DATETIME_WIDTH ? datetime_hires_bytes[length - 1 - MAX_DATETIME_WIDTH] : 8; + case MYSQL_TYPE_DATETIME2: + return length > MAX_DATETIME_WIDTH ? + my_datetime_binary_length(length - MAX_DATETIME_WIDTH - 1) : 5; case MYSQL_TYPE_LONGLONG: return 8; /* Don't crash if no longlong */ case MYSQL_TYPE_NULL : return 0; case MYSQL_TYPE_TINY_BLOB: return 1+portable_sizeof_char_ptr; @@ -9406,18 +10352,21 @@ uint pack_length_to_packflag(uint type) } -Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length, +Field *make_field(TABLE_SHARE *share, + MEM_ROOT *mem_root, + uchar *ptr, uint32 field_length, uchar *null_pos, uchar null_bit, uint pack_flag, enum_field_types field_type, CHARSET_INFO *field_charset, - Field::geometry_type geom_type, + Field::geometry_type geom_type, uint srid, Field::utype unireg_check, TYPELIB *interval, const char *field_name) { uchar *UNINIT_VAR(bit_ptr); uchar UNINIT_VAR(bit_offset); + if (field_type == MYSQL_TYPE_BIT && !f_bit_as_char(pack_flag)) { bit_ptr= null_pos; @@ -9439,16 +10388,6 @@ Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length, null_bit= ((uchar) 1) << null_bit; } - switch (field_type) { - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_NEWDATE: - case MYSQL_TYPE_TIME: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_TIMESTAMP: - field_charset= &my_charset_numeric; - default: break; - } - DBUG_PRINT("debug", ("field_type: %d, field_length: %u, interval: %p, pack_flag: %s%s%s%s%s", field_type, field_length, interval, FLAGSTR(pack_flag, FIELDFLAG_BINARY), @@ -9464,16 +10403,18 @@ Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length, if (field_type == MYSQL_TYPE_STRING || field_type == MYSQL_TYPE_DECIMAL || // 3.23 or 4.0 string field_type == MYSQL_TYPE_VAR_STRING) - return new Field_string(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - field_charset); + return new (mem_root) + Field_string(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + field_charset); if (field_type == MYSQL_TYPE_VARCHAR) - return new Field_varstring(ptr,field_length, - HA_VARCHAR_PACKLENGTH(field_length), - null_pos,null_bit, - unireg_check, field_name, - share, - field_charset); + return new (mem_root) + Field_varstring(ptr,field_length, + HA_VARCHAR_PACKLENGTH(field_length), + null_pos,null_bit, + unireg_check, field_name, + share, + field_charset); return 0; // Error } @@ -9485,117 +10426,160 @@ Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length, if (f_is_geom(pack_flag)) { status_var_increment(current_thd->status_var.feature_gis); - return new Field_geom(ptr,null_pos,null_bit, - unireg_check, field_name, share, - pack_length, geom_type); + return new (mem_root) + Field_geom(ptr,null_pos,null_bit, + unireg_check, field_name, share, + pack_length, geom_type, srid); } #endif if (f_is_blob(pack_flag)) - return new Field_blob(ptr,null_pos,null_bit, - unireg_check, field_name, share, - pack_length, field_charset); + return new (mem_root) + Field_blob(ptr,null_pos,null_bit, + unireg_check, field_name, share, + pack_length, field_charset); if (interval) { if (f_is_enum(pack_flag)) - return new Field_enum(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - pack_length, interval, field_charset); + return new (mem_root) + Field_enum(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + pack_length, interval, field_charset); else - return new Field_set(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - pack_length, interval, field_charset); + return new (mem_root) + Field_set(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + pack_length, interval, field_charset); } } switch (field_type) { case MYSQL_TYPE_DECIMAL: - return new Field_decimal(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - f_decimals(pack_flag), - f_is_zerofill(pack_flag) != 0, - f_is_dec(pack_flag) == 0); + return new (mem_root) + Field_decimal(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + f_decimals(pack_flag), + f_is_zerofill(pack_flag) != 0, + f_is_dec(pack_flag) == 0); case MYSQL_TYPE_NEWDECIMAL: - return new Field_new_decimal(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - f_decimals(pack_flag), - f_is_zerofill(pack_flag) != 0, - f_is_dec(pack_flag) == 0); + return new (mem_root) + Field_new_decimal(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + f_decimals(pack_flag), + f_is_zerofill(pack_flag) != 0, + f_is_dec(pack_flag) == 0); case MYSQL_TYPE_FLOAT: - return new Field_float(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - f_decimals(pack_flag), - f_is_zerofill(pack_flag) != 0, - f_is_dec(pack_flag)== 0); + return new (mem_root) + Field_float(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + f_decimals(pack_flag), + f_is_zerofill(pack_flag) != 0, + f_is_dec(pack_flag)== 0); case MYSQL_TYPE_DOUBLE: - return new Field_double(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - f_decimals(pack_flag), - f_is_zerofill(pack_flag) != 0, - f_is_dec(pack_flag)== 0); + return new (mem_root) + Field_double(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + f_decimals(pack_flag), + f_is_zerofill(pack_flag) != 0, + f_is_dec(pack_flag)== 0); case MYSQL_TYPE_TINY: - return new Field_tiny(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - f_is_zerofill(pack_flag) != 0, - f_is_dec(pack_flag) == 0); + return new (mem_root) + Field_tiny(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + f_is_zerofill(pack_flag) != 0, + f_is_dec(pack_flag) == 0); case MYSQL_TYPE_SHORT: - return new Field_short(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - f_is_zerofill(pack_flag) != 0, - f_is_dec(pack_flag) == 0); + return new (mem_root) + Field_short(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + f_is_zerofill(pack_flag) != 0, + f_is_dec(pack_flag) == 0); case MYSQL_TYPE_INT24: - return new Field_medium(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - f_is_zerofill(pack_flag) != 0, - f_is_dec(pack_flag) == 0); + return new (mem_root) + Field_medium(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + f_is_zerofill(pack_flag) != 0, + f_is_dec(pack_flag) == 0); case MYSQL_TYPE_LONG: - return new Field_long(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - f_is_zerofill(pack_flag) != 0, - f_is_dec(pack_flag) == 0); + return new (mem_root) + Field_long(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + f_is_zerofill(pack_flag) != 0, + f_is_dec(pack_flag) == 0); case MYSQL_TYPE_LONGLONG: - return new Field_longlong(ptr,field_length,null_pos,null_bit, - unireg_check, field_name, - f_is_zerofill(pack_flag) != 0, - f_is_dec(pack_flag) == 0); + return new (mem_root) + Field_longlong(ptr,field_length,null_pos,null_bit, + unireg_check, field_name, + f_is_zerofill(pack_flag) != 0, + f_is_dec(pack_flag) == 0); case MYSQL_TYPE_TIMESTAMP: { uint dec= field_length > MAX_DATETIME_WIDTH ? field_length - MAX_DATETIME_WIDTH - 1: 0; - return new_Field_timestamp(ptr, null_pos, null_bit, unireg_check, - field_name, share, dec, field_charset); + return new_Field_timestamp(mem_root, ptr, null_pos, null_bit, unireg_check, + field_name, share, dec); + } + case MYSQL_TYPE_TIMESTAMP2: + { + uint dec= field_length > MAX_DATETIME_WIDTH ? + field_length - MAX_DATETIME_WIDTH - 1: 0; + return new (mem_root) + Field_timestampf(ptr, null_pos, null_bit, unireg_check, + field_name, share, dec); } case MYSQL_TYPE_YEAR: - return new Field_year(ptr,field_length,null_pos,null_bit, - unireg_check, field_name); + return new (mem_root) + Field_year(ptr,field_length,null_pos,null_bit, + unireg_check, field_name); case MYSQL_TYPE_DATE: - return new Field_date(ptr,null_pos,null_bit, - unireg_check, field_name, field_charset); + return new (mem_root) + Field_date(ptr,null_pos,null_bit, + unireg_check, field_name); case MYSQL_TYPE_NEWDATE: - return new Field_newdate(ptr,null_pos,null_bit, - unireg_check, field_name, field_charset); + return new (mem_root) + Field_newdate(ptr,null_pos,null_bit, + unireg_check, field_name); case MYSQL_TYPE_TIME: { uint dec= field_length > MIN_TIME_WIDTH ? field_length - MIN_TIME_WIDTH - 1: 0; - return new_Field_time(ptr, null_pos, null_bit, unireg_check, - field_name, dec, field_charset); + return new_Field_time(mem_root, ptr, null_pos, null_bit, unireg_check, + field_name, dec); + } + case MYSQL_TYPE_TIME2: + { + uint dec= field_length > MIN_TIME_WIDTH ? + field_length - MIN_TIME_WIDTH - 1: 0; + return new (mem_root) + Field_timef(ptr, null_pos, null_bit, unireg_check, + field_name, dec); } case MYSQL_TYPE_DATETIME: { uint dec= field_length > MAX_DATETIME_WIDTH ? field_length - MAX_DATETIME_WIDTH - 1: 0; - return new_Field_datetime(ptr, null_pos, null_bit, unireg_check, - field_name, dec, field_charset); + return new_Field_datetime(mem_root, ptr, null_pos, null_bit, unireg_check, + field_name, dec); + } + case MYSQL_TYPE_DATETIME2: + { + uint dec= field_length > MAX_DATETIME_WIDTH ? + field_length - MAX_DATETIME_WIDTH - 1: 0; + return new (mem_root) + Field_datetimef(ptr, null_pos, null_bit, unireg_check, + field_name, dec); } case MYSQL_TYPE_NULL: - return new Field_null(ptr, field_length, unireg_check, field_name, - field_charset); + return new (mem_root) + Field_null(ptr, field_length, unireg_check, field_name, + field_charset); case MYSQL_TYPE_BIT: - return f_bit_as_char(pack_flag) ? - new Field_bit_as_char(ptr, field_length, null_pos, null_bit, - unireg_check, field_name) : - new Field_bit(ptr, field_length, null_pos, null_bit, bit_ptr, - bit_offset, unireg_check, field_name); + return (f_bit_as_char(pack_flag) ? + new (mem_root) + Field_bit_as_char(ptr, field_length, null_pos, null_bit, + unireg_check, field_name) : + new (mem_root) + Field_bit(ptr, field_length, null_pos, null_bit, bit_ptr, + bit_offset, unireg_check, field_name)); default: // Impossible (Wrong version) break; @@ -9606,7 +10590,7 @@ Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length, /** Create a field suitable for create of table. */ -Create_field::Create_field(Field *old_field,Field *orig_field) +Create_field::Create_field(THD *thd, Field *old_field, Field *orig_field) { field= old_field; field_name=change=old_field->field_name; @@ -9620,15 +10604,11 @@ Create_field::Create_field(Field *old_field,Field *orig_field) comment= old_field->comment; decimals= old_field->decimals(); vcol_info= old_field->vcol_info; + create_if_not_exists= FALSE; stored_in_db= old_field->stored_in_db; option_list= old_field->option_list; option_struct= old_field->option_struct; - /* Fix if the original table had 4 byte pointer blobs */ - if (flags & BLOB_FLAG) - pack_length= (pack_length- old_field->table->s->blob_ptr_size + - portable_sizeof_char_ptr); - switch (sql_type) { case MYSQL_TYPE_BLOB: switch (pack_length - portable_sizeof_char_ptr) { @@ -9656,16 +10636,17 @@ Create_field::Create_field(Field *old_field,Field *orig_field) #ifdef HAVE_SPATIAL case MYSQL_TYPE_GEOMETRY: geom_type= ((Field_geom*)old_field)->geom_type; + srid= ((Field_geom*)old_field)->srid; break; #endif case MYSQL_TYPE_YEAR: if (length != 4) { char buff[sizeof("YEAR()") + MY_INT64_NUM_DECIMAL_DIGITS + 1]; - my_snprintf(buff, sizeof(buff), "YEAR(%lu)", length); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + my_snprintf(buff, sizeof(buff), "YEAR(%llu)", length); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_WARN_DEPRECATED_SYNTAX, - ER(ER_WARN_DEPRECATED_SYNTAX), + ER_THD(thd, ER_WARN_DEPRECATED_SYNTAX), buff, "YEAR(4)"); } break; @@ -9680,29 +10661,47 @@ Create_field::Create_field(Field *old_field,Field *orig_field) def=0; char_length= length; + /* + Copy the default (constant/function) from the column object orig_field, if + supplied. We do this if all these conditions are met: + + - The column allows a default. + + - The column type is not a BLOB type. + + - The original column (old_field) was properly initialized with a record + buffer pointer. + */ if (!(flags & (NO_DEFAULT_VALUE_FLAG | BLOB_FLAG)) && - old_field->ptr && orig_field && - (sql_type != MYSQL_TYPE_TIMESTAMP || /* set def only if */ - old_field->table->timestamp_field != old_field || /* timestamp field */ - unireg_check == Field::TIMESTAMP_UN_FIELD)) /* has default val */ - { - char buff[MAX_FIELD_WIDTH]; - String tmp(buff,sizeof(buff), charset); - my_ptrdiff_t diff; - - /* Get the value from default_values */ - diff= (my_ptrdiff_t) (orig_field->table->s->default_values- - orig_field->table->record[0]); - orig_field->move_field_offset(diff); // Points now at default_values - if (!orig_field->is_real_null()) + old_field->ptr != NULL && + orig_field != NULL) + { + bool default_now= false; + if (real_type_with_now_as_default(sql_type)) { - char buff[MAX_FIELD_WIDTH], *pos; - String tmp(buff, sizeof(buff), charset), *res; - res= orig_field->val_str(&tmp); - pos= (char*) sql_strmake(res->ptr(), res->length()); - def= new Item_string(pos, res->length(), charset); + // The SQL type of the new field allows a function default: + default_now= orig_field->has_insert_default_function(); + bool update_now= orig_field->has_update_default_function(); + + if (default_now && update_now) + unireg_check= Field::TIMESTAMP_DNUN_FIELD; + else if (default_now) + unireg_check= Field::TIMESTAMP_DN_FIELD; + else if (update_now) + unireg_check= Field::TIMESTAMP_UN_FIELD; + } + if (!default_now) // Give a constant default + { + /* Get the value from default_values */ + const uchar *dv= orig_field->table->s->default_values; + if (!orig_field->is_null_in_record(dv)) + { + StringBuffer<MAX_FIELD_WIDTH> tmp(charset); + String *res= orig_field->val_str(&tmp, orig_field->ptr_in_record(dv)); + char *pos= (char*) sql_strmake(res->ptr(), res->length()); + def= new (thd->mem_root) Item_string(thd, pos, res->length(), charset); + } } - orig_field->move_field_offset(-diff); // Back to record[0] } } @@ -9784,11 +10783,11 @@ uint32 Field_blob::max_display_length() *****************************************************************************/ /** - Produce warning or note about data saved into field. +* Produce warning or note about data saved into field. @param level - level of message (Note/Warning/Error) @param code - error code of message to be produced - @param cuted_increment - whenever we should increase cut fields count or not + @param cut_increment - whenever we should increase cut fields count @note This function won't produce warning and increase cut fields counter @@ -9796,23 +10795,30 @@ uint32 Field_blob::max_display_length() if count_cuted_fields == CHECK_FIELD_IGNORE then we ignore notes. This allows us to avoid notes in optimisation, like convert_constant_item(). + + @retval + 1 if count_cuted_fields == CHECK_FIELD_IGNORE and error level is not NOTE + @retval + 0 otherwise */ -void -Field::set_warning(MYSQL_ERROR::enum_warning_level level, uint code, - int cuted_increment) +bool +Field::set_warning(Sql_condition::enum_warning_level level, uint code, + int cut_increment) const { /* If this field was created only for type conversion purposes it will have table == NULL. */ - THD *thd= table ? table->in_use : current_thd; + THD *thd= get_thd(); if (thd->count_cuted_fields) { - thd->cuted_fields+= cuted_increment; - push_warning_printf(thd, level, code, ER(code), field_name, - thd->warning_info->current_row_for_warning()); + thd->cuted_fields+= cut_increment; + push_warning_printf(thd, level, code, ER_THD(thd, code), field_name, + thd->get_stmt_da()->current_row_for_warning()); + return 0; } + return level >= Sql_condition::WARN_LEVEL_WARN; } @@ -9834,18 +10840,32 @@ Field::set_warning(MYSQL_ERROR::enum_warning_level level, uint code, */ -void Field::set_datetime_warning(MYSQL_ERROR::enum_warning_level level, +void Field::set_datetime_warning(Sql_condition::enum_warning_level level, uint code, const ErrConv *str, timestamp_type ts_type, int cuted_increment) + const { - THD *thd= table->in_use; - if (thd->really_abort_on_warning() && level >= MYSQL_ERROR::WARN_LEVEL_WARN) + THD *thd= get_thd(); + if (thd->really_abort_on_warning() && level >= Sql_condition::WARN_LEVEL_WARN) make_truncated_value_warning(thd, level, str, ts_type, field_name); else set_warning(level, code, cuted_increment); } +void Field::set_warning_truncated_wrong_value(const char *type_arg, + const char *value) +{ + THD *thd= get_thd(); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), + type_arg, value, field_name, + static_cast<ulong>(thd->get_stmt_da()-> + current_row_for_warning())); +} + + /* @brief Return possible keys for a field @@ -9868,3 +10888,48 @@ key_map Field::get_possible_keys() return (table->pos_in_table_list->is_materialized_derived() ? part_of_key : key_start); } + + +/** + Mark the field as having an explicit default value. + + @param value if available, the value that the field is being set to + + @note + Fields that have an explicit default value should not be updated + automatically via the DEFAULT or ON UPDATE functions. The functions + that deal with data change functionality (INSERT/UPDATE/LOAD), + determine if there is an explicit value for each field before performing + the data change, and call this method to mark the field. + + If the 'value' parameter is NULL, then the field is marked unconditionally + as having an explicit value. If 'value' is not NULL, then it can be further + analyzed to check if it really should count as a value. +*/ + +void Field::set_explicit_default(Item *value) +{ + if (value->type() == Item::DEFAULT_VALUE_ITEM && + !((Item_default_value*)value)->arg) + return; + set_has_explicit_value(); +} + + +bool Field::validate_value_in_record_with_warn(THD *thd, const uchar *record) +{ + my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); + bool rc; + if ((rc= validate_value_in_record(thd, record))) + { + // Get and report val_str() for the DEFAULT value + StringBuffer<MAX_FIELD_WIDTH> tmp; + val_str(&tmp, ptr_in_record(record)); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_INVALID_DEFAULT_VALUE_FOR_FIELD, + ER_THD(thd, ER_INVALID_DEFAULT_VALUE_FOR_FIELD), + ErrConvString(&tmp).ptr(), field_name); + } + dbug_tmp_restore_column_map(table->read_set, old_map); + return rc; +} diff --git a/sql/field.h b/sql/field.h index d484b31d682..86853f7d9d9 100644 --- a/sql/field.h +++ b/sql/field.h @@ -17,7 +17,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* - Because of the function new_field() all field classes that have static + Because of the function make_new_field() all field classes that have static variables must declare the size_of() member function. */ @@ -29,13 +29,19 @@ #include "table.h" /* TABLE */ #include "sql_string.h" /* String */ #include "my_decimal.h" /* my_decimal */ -#include "sql_error.h" /* MYSQL_ERROR */ +#include "sql_error.h" /* Sql_condition */ +#include "compat56.h" class Send_field; class Protocol; class Create_field; class Relay_log_info; class Field; +class Column_statistics; +class Column_statistics_collected; +class Item_func; +class Item_bool_func; +class Item_equal; enum enum_check_fields { @@ -45,6 +51,319 @@ enum enum_check_fields }; +/* + Common declarations for Field and Item +*/ +class Value_source +{ +protected: + + // Parameters for warning and note generation + class Warn_filter + { + bool m_want_warning_edom; + bool m_want_note_truncated_spaces; + public: + Warn_filter(bool want_warning_edom, bool want_note_truncated_spaces) : + m_want_warning_edom(want_warning_edom), + m_want_note_truncated_spaces(want_note_truncated_spaces) + { } + Warn_filter(const THD *thd); + bool want_warning_edom() const + { return m_want_warning_edom; } + bool want_note_truncated_spaces() const + { return m_want_note_truncated_spaces; } + }; + class Warn_filter_all: public Warn_filter + { + public: + Warn_filter_all() :Warn_filter(true, true) { } + }; + + + // String-to-number converters + class Converter_string_to_number + { + protected: + char *m_end_of_num; // Where the low-level conversion routine stopped + int m_error; // The error code returned by the low-level routine + bool m_edom; // If EDOM-alike error happened during conversion + /** + Check string-to-number conversion and produce a warning if + - could not convert any digits (EDOM-alike error) + - found garbage at the end of the string + - found extra spaces at the end (a note) + See also Field_num::check_edom_and_truncation() for a similar function. + + @param thd - the thread that will be used to generate warnings. + Can be NULL (which means current_thd will be used + if a warning is really necessary). + @param type - name of the data type + (e.g. "INTEGER", "DECIMAL", "DOUBLE") + @param cs - character set of the original string + @param str - the original string + @param end - the end of the string + @param allow_notes - tells if trailing space notes should be displayed + or suppressed. + + Unlike Field_num::check_edom_and_truncation(), this function does not + distinguish between EDOM and truncation and reports the same warning for + both cases. Perhaps we should eventually print different warnings, + to make the explicit CAST work closer to the implicit cast in + Field_xxx::store(). + */ + void check_edom_and_truncation(THD *thd, Warn_filter filter, + const char *type, + CHARSET_INFO *cs, + const char *str, + size_t length) const; + public: + int error() const { return m_error; } + }; + + class Converter_strntod: public Converter_string_to_number + { + double m_result; + public: + Converter_strntod(CHARSET_INFO *cs, const char *str, size_t length) + { + m_result= my_strntod(cs, (char *) str, length, &m_end_of_num, &m_error); + // strntod() does not set an error if the input string was empty + m_edom= m_error !=0 || str == m_end_of_num; + } + double result() const { return m_result; } + }; + + class Converter_string_to_longlong: public Converter_string_to_number + { + protected: + longlong m_result; + public: + longlong result() const { return m_result; } + }; + + class Converter_strntoll: public Converter_string_to_longlong + { + public: + Converter_strntoll(CHARSET_INFO *cs, const char *str, size_t length) + { + m_result= my_strntoll(cs, str, length, 10, &m_end_of_num, &m_error); + /* + All non-zero errors means EDOM error. + strntoll() does not set an error if the input string was empty. + Check it here. + Notice the different with the same condition in Converter_strntoll10. + */ + m_edom= m_error != 0 || str == m_end_of_num; + } + }; + + class Converter_strtoll10: public Converter_string_to_longlong + { + public: + Converter_strtoll10(CHARSET_INFO *cs, const char *str, size_t length) + { + m_end_of_num= (char *) str + length; + m_result= (*(cs->cset->strtoll10))(cs, str, &m_end_of_num, &m_error); + /* + Negative error means "good negative number". + Only a positive m_error value means a real error. + strtoll10() sets error to MY_ERRNO_EDOM in case of an empty string, + so we don't have to additionally catch empty strings here. + */ + m_edom= m_error > 0; + } + }; + + class Converter_str2my_decimal: public Converter_string_to_number + { + public: + Converter_str2my_decimal(uint mask, + CHARSET_INFO *cs, const char *str, size_t length, + my_decimal *buf) + { + m_error= str2my_decimal(mask, str, length, cs, + buf, (const char **) &m_end_of_num); + // E_DEC_TRUNCATED means a very minor truncation: '1e-100' -> 0 + m_edom= m_error && m_error != E_DEC_TRUNCATED; + } + }; + + + // String-to-number converters with automatic warning generation + class Converter_strntod_with_warn: public Converter_strntod + { + public: + Converter_strntod_with_warn(THD *thd, Warn_filter filter, + CHARSET_INFO *cs, + const char *str, size_t length) + :Converter_strntod(cs, str, length) + { + check_edom_and_truncation(thd, filter, "DOUBLE", cs, str, length); + } + }; + + class Converter_strntoll_with_warn: public Converter_strntoll + { + public: + Converter_strntoll_with_warn(THD *thd, Warn_filter filter, + CHARSET_INFO *cs, + const char *str, size_t length) + :Converter_strntoll(cs, str, length) + { + check_edom_and_truncation(thd, filter, "INTEGER", cs, str, length); + } + }; + + class Converter_strtoll10_with_warn: public Converter_strtoll10 + { + public: + Converter_strtoll10_with_warn(THD *thd, Warn_filter filter, + CHARSET_INFO *cs, + const char *str, size_t length) + :Converter_strtoll10(cs, str, length) + { + check_edom_and_truncation(thd, filter, "INTEGER", cs, str, length); + } + }; + + class Converter_str2my_decimal_with_warn: public Converter_str2my_decimal + { + public: + Converter_str2my_decimal_with_warn(THD *thd, Warn_filter filter, + uint mask, CHARSET_INFO *cs, + const char *str, size_t length, + my_decimal *buf) + :Converter_str2my_decimal(mask, cs, str, length, buf) + { + check_edom_and_truncation(thd, filter, "DECIMAL", cs, str, length); + } + }; + + + // String-to-number convertion methods for the old code compatibility + longlong longlong_from_string_with_check(CHARSET_INFO *cs, const char *cptr, + const char *end) const + { + /* + TODO: Give error if we wanted a signed integer and we got an unsigned + one + + Notice, longlong_from_string_with_check() honors thd->no_error, because + it's used to handle queries like this: + SELECT COUNT(@@basedir); + and is called when Item_func_get_system_var::update_null_value() + suppresses warnings and then calls val_int(). + The other methods {double|decimal}_from_string_with_check() ignore + thd->no_errors, because they are not used for update_null_value() + and they always allow all kind of warnings. + */ + THD *thd= current_thd; + return Converter_strtoll10_with_warn(thd, Warn_filter(thd), + cs, cptr, end - cptr).result(); + } + + double double_from_string_with_check(CHARSET_INFO *cs, const char *cptr, + const char *end) const + { + return Converter_strntod_with_warn(NULL, Warn_filter_all(), + cs, cptr, end - cptr).result(); + } + my_decimal *decimal_from_string_with_check(my_decimal *decimal_value, + CHARSET_INFO *cs, + const char *cptr, + const char *end) + { + Converter_str2my_decimal_with_warn(NULL, Warn_filter_all(), + E_DEC_FATAL_ERROR & ~E_DEC_BAD_NUM, + cs, cptr, end - cptr, decimal_value); + return decimal_value; + } + + longlong longlong_from_string_with_check(const String *str) const + { + return longlong_from_string_with_check(str->charset(), + str->ptr(), str->end()); + } + double double_from_string_with_check(const String *str) const + { + return double_from_string_with_check(str->charset(), + str->ptr(), str->end()); + } + my_decimal *decimal_from_string_with_check(my_decimal *decimal_value, + const String *str) + { + return decimal_from_string_with_check(decimal_value, str->charset(), + str->ptr(), str->end()); + } + // End of String-to-number conversion methods + +public: + /* + The enumeration Subst_constraint is currently used only in implementations + of the virtual function subst_argument_checker. + */ + enum Subst_constraint + { + ANY_SUBST, /* Any substitution for a field is allowed */ + IDENTITY_SUBST /* Substitution for a field is allowed if any two + different values of the field type are not equal */ + }; + /* + Item context attributes. + Comparison functions pass their attributes to propagate_equal_fields(). + For exmple, for string comparison, the collation of the comparison + operation is important inside propagate_equal_fields(). + */ + class Context + { + /* + Which type of propagation is allowed: + - ANY_SUBST (loose equality, according to the collation), or + - IDENTITY_SUBST (strict binary equality). + */ + Subst_constraint m_subst_constraint; + /* + Comparison type. + Impostant only when ANY_SUBSTS. + */ + Item_result m_compare_type; + /* + Collation of the comparison operation. + Important only when ANY_SUBST. + */ + CHARSET_INFO *m_compare_collation; + public: + Context(Subst_constraint subst, Item_result type, CHARSET_INFO *cs) + :m_subst_constraint(subst), + m_compare_type(type), + m_compare_collation(cs) { } + Subst_constraint subst_constraint() const { return m_subst_constraint; } + Item_result compare_type() const + { + DBUG_ASSERT(m_subst_constraint == ANY_SUBST); + return m_compare_type; + } + CHARSET_INFO *compare_collation() const + { + DBUG_ASSERT(m_subst_constraint == ANY_SUBST); + return m_compare_collation; + } + }; + class Context_identity: public Context + { // Use this to request only exact value, no invariants. + public: + Context_identity() + :Context(IDENTITY_SUBST, STRING_RESULT, &my_charset_bin) { } + }; + class Context_boolean: public Context + { // Use this when an item is [a part of] a boolean expression + public: + Context_boolean() :Context(ANY_SUBST, INT_RESULT, &my_charset_bin) { } + }; +}; + + enum Derivation { DERIVATION_IGNORABLE= 6, @@ -56,6 +375,7 @@ enum Derivation DERIVATION_EXPLICIT= 0 }; + #define STORAGE_TYPE_MASK 7 #define COLUMN_FORMAT_MASK 7 #define COLUMN_FORMAT_SHIFT 3 @@ -64,12 +384,16 @@ enum Derivation #define MY_REPERTOIRE_NUMERIC MY_REPERTOIRE_ASCII /* The length of the header part for each virtual column in the .frm file */ -#define FRM_VCOL_HEADER_SIZE(b) (3 + test(b)) +#define FRM_VCOL_HEADER_SIZE(b) (3 + MY_TEST(b)) + +class Count_distinct_field; struct ha_field_option_struct; struct st_cache_field; int field_conv(Field *to,Field *from); +int field_conv_incompatible(Field *to,Field *from); +bool memcpy_field_possible(Field *to, Field *from); int truncate_double(double *nr, uint field_length, uint dec, bool unsigned_flag, double max_value); longlong double_to_longlong(double nr, bool unsigned_flag, bool *error); @@ -86,12 +410,89 @@ inline uint get_set_pack_length(int elements) } +/** + Tests if field type is temporal and has date part, + i.e. represents DATE, DATETIME or TIMESTAMP types in SQL. + + @param type Field type, as returned by field->type(). + @retval true If field type is temporal type with date part. + @retval false If field type is not temporal type with date part. +*/ +inline bool is_temporal_type_with_date(enum_field_types type) +{ + switch (type) + { + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return true; + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_TIMESTAMP2: + DBUG_ASSERT(0); // field->real_type() should not get to here. + default: + return false; + } +} + + +/** + Tests if a field real type can have "DEFAULT CURRENT_TIMESTAMP" + + @param type Field type, as returned by field->real_type(). + @retval true If field real type can have "DEFAULT CURRENT_TIMESTAMP". + @retval false If field real type can not have "DEFAULT CURRENT_TIMESTAMP". +*/ +inline bool real_type_with_now_as_default(enum_field_types type) +{ + return type == MYSQL_TYPE_TIMESTAMP || type == MYSQL_TYPE_TIMESTAMP2 || + type == MYSQL_TYPE_DATETIME || type == MYSQL_TYPE_DATETIME2; +} + + +/** + Recognizer for concrete data type (called real_type for some reason), + returning true if it is one of the TIMESTAMP types. +*/ +inline bool is_timestamp_type(enum_field_types type) +{ + return type == MYSQL_TYPE_TIMESTAMP || type == MYSQL_TYPE_TIMESTAMP2; +} + + +/** + Convert temporal real types as retuned by field->real_type() + to field type as returned by field->type(). + + @param real_type Real type. + @retval Field type. +*/ +inline enum_field_types real_type_to_type(enum_field_types real_type) +{ + switch (real_type) + { + case MYSQL_TYPE_TIME2: + return MYSQL_TYPE_TIME; + case MYSQL_TYPE_DATETIME2: + return MYSQL_TYPE_DATETIME; + case MYSQL_TYPE_TIMESTAMP2: + return MYSQL_TYPE_TIMESTAMP; + case MYSQL_TYPE_NEWDATE: + return MYSQL_TYPE_DATE; + /* Note: NEWDECIMAL is a type, not only a real_type */ + default: return real_type; + } +} + + static inline enum enum_mysql_timestamp_type mysql_type_to_time_type(enum enum_field_types mysql_type) { switch(mysql_type) { + case MYSQL_TYPE_TIME2: case MYSQL_TYPE_TIME: return MYSQL_TIMESTAMP_TIME; + case MYSQL_TYPE_TIMESTAMP2: case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATETIME2: case MYSQL_TYPE_DATETIME: return MYSQL_TIMESTAMP_DATETIME; case MYSQL_TYPE_NEWDATE: case MYSQL_TYPE_DATE: return MYSQL_TIMESTAMP_DATE; @@ -199,16 +600,27 @@ public: { in_partitioning_expr= TRUE; } + bool is_equal(Virtual_column_info* vcol) + { + return field_type == vcol->get_real_type() + && stored_in_db == vcol->is_stored() + && expr_str.length == vcol->expr_str.length + && memcmp(expr_str.str, vcol->expr_str.str, expr_str.length) == 0; + } }; -class Field +class Field: public Value_source { Field(const Item &); /* Prevent use of these */ void operator=(Field &); public: + static void *operator new(size_t size, MEM_ROOT *mem_root) throw () + { return alloc_root(mem_root, size); } static void *operator new(size_t size) throw () { return sql_alloc(size); } static void operator delete(void *ptr_arg, size_t size) { TRASH_FREE(ptr_arg, size); } + static void operator delete(void *ptr, MEM_ROOT *mem_root) + { DBUG_ASSERT(0); } uchar *ptr; // Position to field in record /** @@ -230,6 +642,12 @@ public: LEX_STRING comment; /* Field is part of the following keys */ key_map key_start, part_of_key, part_of_key_not_clustered; + + /* + Bitmap of indexes that have records ordered by col1, ... this_field, ... + + For example, INDEX (col(prefix_n)) is not present in col.part_of_sortkey. + */ key_map part_of_sortkey; /* We use three additional unireg types for TIMESTAMP to overcome limitation @@ -267,6 +685,35 @@ public: */ bool is_created_from_null_item; + /* TRUE in Field objects created for column min/max values */ + bool is_stat_field; + + /* + Selectivity of the range condition over this field. + When calculating this selectivity a range predicate + is taken into account only if: + - it is extracted from the WHERE clause + - it depends only on the table the field belongs to + */ + double cond_selectivity; + + /* + The next field in the class of equal fields at the top AND level + of the WHERE clause + */ + Field *next_equal_field; + + /* + This structure is used for statistical data on the column + that has been read from the statistical table column_stat + */ + Column_statistics *read_stats; + /* + This structure is used for statistical data on the column that + is collected by the function collect_statistics_for_table + */ + Column_statistics_collected *collected_stats; + /* This is additional data provided for any computed(virtual) field. In particular it includes a pointer to the item by which this field @@ -294,8 +741,15 @@ public: { return store_time_dec(ltime, TIME_SECOND_PART_DIGITS); } int store(const char *to, uint length, CHARSET_INFO *cs, enum_check_fields check_level); + int store(const LEX_STRING *ls, CHARSET_INFO *cs) + { return store(ls->str, ls->length, cs); } virtual double val_real(void)=0; virtual longlong val_int(void)=0; + virtual ulonglong val_uint(void) + { + return (ulonglong) val_int(); + } + virtual bool val_bool(void)= 0; virtual my_decimal *val_decimal(my_decimal *); inline String *val_str(String *str) { return val_str(str, str); } /* @@ -359,6 +813,26 @@ public: virtual uint32 data_length() { return pack_length(); } virtual uint32 sort_length() const { return pack_length(); } + /* + Get the number bytes occupied by the value in the field. + CHAR values are stripped of trailing spaces. + Flexible values are stripped of their length. + */ + virtual uint32 value_length() + { + uint len; + if (!zero_pack() && + (type() == MYSQL_TYPE_STRING && + (len= pack_length()) >= 4 && len < 256)) + { + uchar *str, *end; + for (str= ptr, end= str+len; end > str && end[-1] == ' '; end--) {} + len=(uint) (end-str); + return len; + } + return data_length(); + } + /** Get the maximum size of the data in packed format. @@ -371,21 +845,114 @@ public: virtual int reset(void) { bzero(ptr,pack_length()); return 0; } virtual void reset_fields() {} + const uchar *ptr_in_record(const uchar *record) const + { + my_ptrdiff_t l_offset= (my_ptrdiff_t) (record - table->record[0]); + return ptr + l_offset; + } virtual void set_default() { my_ptrdiff_t l_offset= (my_ptrdiff_t) (table->s->default_values - table->record[0]); memcpy(ptr, ptr + l_offset, pack_length()); - if (null_ptr) + if (maybe_null_in_table()) *null_ptr= ((*null_ptr & (uchar) ~null_bit) | (null_ptr[l_offset] & null_bit)); } + + bool has_insert_default_function() const + { + return unireg_check == TIMESTAMP_DN_FIELD || + unireg_check == TIMESTAMP_DNUN_FIELD; + } + + bool has_update_default_function() const + { + return unireg_check == TIMESTAMP_UN_FIELD || + unireg_check == TIMESTAMP_DNUN_FIELD; + } + + /* + Mark the field as having a value supplied by the client, thus it should + not be auto-updated. + */ + void set_has_explicit_value() + { + flags|= HAS_EXPLICIT_VALUE; + } + + virtual void set_explicit_default(Item *value); + + /** + Evaluates the @c INSERT default function and stores the result in the + field. If no such function exists for the column, or the function is not + valid for the column's data type, invoking this function has no effect. + */ + virtual int evaluate_insert_default_function() { return 0; } + + + /** + Evaluates the @c UPDATE default function, if one exists, and stores the + result in the record buffer. If no such function exists for the column, + or the function is not valid for the column's data type, invoking this + function has no effect. + */ + virtual int evaluate_update_default_function() { return 0; } + virtual bool binary() const { return 1; } virtual bool zero_pack() const { return 1; } virtual enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } virtual uint32 key_length() const { return pack_length(); } virtual enum_field_types type() const =0; virtual enum_field_types real_type() const { return type(); } + virtual enum_field_types binlog_type() const + { + /* + Binlog stores field->type() as type code by default. For example, + it puts MYSQL_TYPE_STRING in case of CHAR, VARCHAR, SET and ENUM, + with extra data type details put into metadata. + + Binlog behaviour slightly differs between various MySQL and MariaDB + versions for the temporal data types TIME, DATETIME and TIMESTAMP. + + MySQL prior to 5.6 uses MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME + and MYSQL_TYPE_TIMESTAMP type codes in binlog and stores no + additional metadata. + + MariaDB-5.3 implements new versions for TIME, DATATIME, TIMESTAMP + with fractional second precision, but uses the old format for the + types TIME(0), DATETIME(0), TIMESTAMP(0), and it still stores + MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME and MYSQL_TYPE_TIMESTAMP in binlog, + with no additional metadata. + So row-based replication between temporal data types of + different precision is not possible in MariaDB. + + MySQL-5.6 also implements a new version of TIME, DATETIME, TIMESTAMP + which support fractional second precision 0..6, and use the new + format even for the types TIME(0), DATETIME(0), TIMESTAMP(0). + For these new data types, MySQL-5.6 stores new type codes + MYSQL_TYPE_TIME2, MYSQL_TYPE_DATETIME2, MYSQL_TYPE_TIMESTAMP2 in binlog, + with fractional precision 0..6 put into metadata. + This makes it in theory possible to do row-based replication between + columns of different fractional precision (e.g. from TIME(1) on master + to TIME(6) on slave). However, it's not currently fully implemented yet. + MySQL-5.6 can only do row-based replication from the old types + TIME, DATETIME, TIMESTAMP (represented by MYSQL_TYPE_TIME, + MYSQL_TYPE_DATETIME and MYSQL_TYPE_TIMESTAMP type codes in binlog) + to the new corresponding types TIME(0), DATETIME(0), TIMESTAMP(0). + + Note: MariaDB starting from the version 10.0 understands the new + MySQL-5.6 type codes MYSQL_TYPE_TIME2, MYSQL_TYPE_DATETIME2, + MYSQL_TYPE_TIMESTAMP2. When started over MySQL-5.6 tables both on + master and on slave, MariaDB-10.0 can also do row-based replication + from the old types TIME, DATETIME, TIMESTAMP to the new MySQL-5.6 + types TIME(0), DATETIME(0), TIMESTAMP(0). + + Note: perhaps binlog should eventually be modified to store + real_type() instead of type() for all column types. + */ + return type(); + } inline int cmp(const uchar *str) { return cmp(ptr,str); } virtual int cmp_max(const uchar *a, const uchar *b, uint max_len) { return cmp(a, b); } @@ -400,6 +967,40 @@ public: { return cmp(a, b); } virtual int key_cmp(const uchar *str, uint length) { return cmp(ptr,str); } + /* + Update the value m of the 'min_val' field with the current value v + of this field if force_update is set to TRUE or if v < m. + Return TRUE if the value has been updated. + */ + virtual bool update_min(Field *min_val, bool force_update) + { + bool update_fl= force_update || cmp(ptr, min_val->ptr) < 0; + if (update_fl) + { + min_val->set_notnull(); + memcpy(min_val->ptr, ptr, pack_length()); + } + return update_fl; + } + /* + Update the value m of the 'max_val' field with the current value v + of this field if force_update is set to TRUE or if v > m. + Return TRUE if the value has been updated. + */ + virtual bool update_max(Field *max_val, bool force_update) + { + bool update_fl= force_update || cmp(ptr, max_val->ptr) > 0; + if (update_fl) + { + max_val->set_notnull(); + memcpy(max_val->ptr, ptr, pack_length()); + } + return update_fl; + } + virtual void store_field_value(uchar *val, uint len) + { + memcpy(ptr, val, len); + } virtual uint decimals() const { return 0; } /* Caller beware: sql_type can change str.Ptr, so check @@ -408,32 +1009,67 @@ public: */ virtual void sql_type(String &str) const =0; virtual uint size_of() const =0; // For new field - inline bool is_null(my_ptrdiff_t row_offset= 0) - { return null_ptr ? (null_ptr[row_offset] & null_bit ? 1 : 0) : table->null_row; } - inline bool is_real_null(my_ptrdiff_t row_offset= 0) - { return null_ptr ? (null_ptr[row_offset] & null_bit ? 1 : 0) : 0; } - inline bool is_null_in_record(const uchar *record) - { - if (!null_ptr) - return 0; - return test(record[(uint) (null_ptr -table->record[0])] & - null_bit); - } - inline bool is_null_in_record_with_offset(my_ptrdiff_t col_offset) - { - if (!null_ptr) - return 0; - return test(null_ptr[col_offset] & null_bit); + inline bool is_null(my_ptrdiff_t row_offset= 0) const + { + /* + The table may have been marked as containing only NULL values + for all fields if it is a NULL-complemented row of an OUTER JOIN + or if the query is an implicitly grouped query (has aggregate + functions but no GROUP BY clause) with no qualifying rows. If + this is the case (in which TABLE::null_row is true), the field + is considered to be NULL. + + Note that if a table->null_row is set then also all null_bits are + set for the row. + + In the case of the 'result_field' for GROUP BY, table->null_row might + refer to the *next* row in the table (when the algorithm is: read the + next row, see if any of group column values have changed, send the + result - grouped - row to the client if yes). So, table->null_row might + be wrong, but such a result_field is always nullable (that's defined by + original_field->maybe_null()) and we trust its null bit. + */ + return null_ptr ? null_ptr[row_offset] & null_bit : table->null_row; + } + inline bool is_real_null(my_ptrdiff_t row_offset= 0) const + { return null_ptr && (null_ptr[row_offset] & null_bit); } + inline bool is_null_in_record(const uchar *record) const + { + if (maybe_null_in_table()) + return record[(uint) (null_ptr - table->record[0])] & null_bit; + return 0; } inline void set_null(my_ptrdiff_t row_offset= 0) { if (null_ptr) null_ptr[row_offset]|= null_bit; } inline void set_notnull(my_ptrdiff_t row_offset= 0) { if (null_ptr) null_ptr[row_offset]&= (uchar) ~null_bit; } - inline bool maybe_null(void) { return null_ptr != 0 || table->maybe_null; } - /** - Signals that this field is NULL-able. + inline bool maybe_null(void) const + { return null_ptr != 0 || table->maybe_null; } + + /* @return true if this field is NULL-able (even if temporarily) */ + inline bool real_maybe_null(void) const { return null_ptr != 0; } + uint null_offset(const uchar *record) const + { return (uint) (null_ptr - record); } + /* + For a NULL-able field (that can actually store a NULL value in a table) + null_ptr points to the "null bitmap" in the table->record[0] header. For + NOT NULL fields it is either 0 or points outside table->record[0] into the + table->triggers->extra_null_bitmap (so that the field can store a NULL + value temporarily, only in memory) */ - inline bool real_maybe_null(void) { return null_ptr != 0; } + bool maybe_null_in_table() const + { return null_ptr >= table->record[0] && null_ptr <= ptr; } + + uint null_offset() const + { return null_offset(table->record[0]); } + void set_null_ptr(uchar *p_null_ptr, uint p_null_bit) + { + null_ptr= p_null_ptr; + null_bit= p_null_bit; + } + + inline THD *get_thd() const + { return likely(table) ? table->in_use : current_thd; } enum { LAST_NULL_BYTE_UNDEF= 0 @@ -466,12 +1102,15 @@ public: virtual void sort_string(uchar *buff,uint length)=0; virtual bool optimize_range(uint idx, uint part); virtual void free() {} - virtual Field *new_field(MEM_ROOT *root, TABLE *new_table, - bool keep_type); + virtual Field *make_new_field(MEM_ROOT *root, TABLE *new_table, + bool keep_type); virtual Field *new_key_field(MEM_ROOT *root, TABLE *new_table, - uchar *new_ptr, uchar *new_null_ptr, - uint new_null_bit); + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit); Field *clone(MEM_ROOT *mem_root, TABLE *new_table); + Field *clone(MEM_ROOT *mem_root, TABLE *new_table, my_ptrdiff_t diff, + bool stat_flag= FALSE); + Field *clone(MEM_ROOT *mem_root, my_ptrdiff_t diff); inline void move_field(uchar *ptr_arg,uchar *null_ptr_arg,uchar null_bit_arg) { ptr=ptr_arg; null_ptr=null_ptr_arg; null_bit=null_bit_arg; @@ -587,16 +1226,34 @@ public: uint repertoire_arg) { } virtual int set_time() { return 1; } - void set_warning(MYSQL_ERROR::enum_warning_level, unsigned int code, - int cuted_increment); - void set_datetime_warning(MYSQL_ERROR::enum_warning_level, uint code, + bool set_warning(Sql_condition::enum_warning_level, unsigned int code, + int cuted_increment) const; +protected: + bool set_warning(unsigned int code, int cuted_increment) const + { + return set_warning(Sql_condition::WARN_LEVEL_WARN, code, cuted_increment); + } + bool set_note(unsigned int code, int cuted_increment) const + { + return set_warning(Sql_condition::WARN_LEVEL_NOTE, code, cuted_increment); + } + void set_datetime_warning(Sql_condition::enum_warning_level, uint code, + const ErrConv *str, timestamp_type ts_type, + int cuted_increment) const; + void set_datetime_warning(uint code, const ErrConv *str, timestamp_type ts_type, - int cuted_increment); + int cuted_increment) const + { + set_datetime_warning(Sql_condition::WARN_LEVEL_WARN, code, str, ts_type, + cuted_increment); + } + void set_warning_truncated_wrong_value(const char *type, const char *value); inline bool check_overflow(int op_result) { return (op_result == E_DEC_OVERFLOW); } int warn_if_overflow(int op_result); +public: void set_table_name(String *alias) { table_name= &alias->Ptr; @@ -634,11 +1291,55 @@ public: return GEOM_GEOMETRY; } + ha_storage_media field_storage_type() const + { + return (ha_storage_media) + ((flags >> FIELD_FLAGS_STORAGE_MEDIA) & 3); + } + + void set_storage_type(ha_storage_media storage_type_arg) + { + DBUG_ASSERT(field_storage_type() == HA_SM_DEFAULT); + flags |= (storage_type_arg << FIELD_FLAGS_STORAGE_MEDIA); + } + + column_format_type column_format() const + { + return (column_format_type) + ((flags >> FIELD_FLAGS_COLUMN_FORMAT) & 3); + } + + void set_column_format(column_format_type column_format_arg) + { + DBUG_ASSERT(column_format() == COLUMN_FORMAT_TYPE_DEFAULT); + flags |= (column_format_arg << FIELD_FLAGS_COLUMN_FORMAT); + } + + /* + Validate a non-null field value stored in the given record + according to the current thread settings, e.g. sql_mode. + @param thd - the thread + @param record - the record to check in + */ + virtual bool validate_value_in_record(THD *thd, const uchar *record) const + { return false; } + bool validate_value_in_record_with_warn(THD *thd, const uchar *record); key_map get_possible_keys(); /* Hash value */ virtual void hash(ulong *nr, ulong *nr2); + /** + Get the upper limit of the MySQL integral and floating-point type. + + @return maximum allowed value for the field + */ + virtual ulonglong get_max_int_value() const + { + DBUG_ASSERT(false); + return 0ULL; + } + /** Checks whether a string field is part of write_set. @@ -653,6 +1354,58 @@ public: virtual bool hash_join_is_possible() { return TRUE; } virtual bool eq_cmp_as_binary() { return TRUE; } + /* Position of the field value within the interval of [min, max] */ + virtual double pos_in_interval(Field *min, Field *max) + { + return (double) 0.5; + } + + /* + Check if comparison between the field and an item unambiguously + identifies a distinct field value. + + Example1: SELECT * FROM t1 WHERE int_column=10; + This example returns distinct integer value of 10. + + Example2: SELECT * FROM t1 WHERE varchar_column=DATE'2001-01-01' + This example returns non-distinct values. + Comparison as DATE will return '2001-01-01' and '2001-01-01x', + but these two values are not equal to each other as VARCHARs. + See also the function with the same name in sql_select.cc. + */ + virtual bool test_if_equality_guarantees_uniqueness(const Item *const_item) + const; + virtual bool can_be_substituted_to_equal_item(const Context &ctx, + const Item_equal *item); + virtual Item *get_equal_const_item(THD *thd, const Context &ctx, + Item *const_item) + { + return const_item; + } + virtual bool can_optimize_keypart_ref(const Item_bool_func *cond, + const Item *item) const; + virtual bool can_optimize_hash_join(const Item_bool_func *cond, + const Item *item) const + { + return can_optimize_keypart_ref(cond, item); + } + virtual bool can_optimize_group_min_max(const Item_bool_func *cond, + const Item *const_item) const; + /** + Test if Field can use range optimizer for a standard comparison operation: + <=, <, =, <=>, >, >= + Note, this method does not cover spatial operations. + */ + virtual bool can_optimize_range(const Item_bool_func *cond, + const Item *item, + bool is_eq_func) const; + + bool can_optimize_outer_join_table_elimination(const Item_bool_func *cond, + const Item *item) const + { + // Exactly the same rules with REF access + return can_optimize_keypart_ref(cond, item); + } friend int cre_myisam(char * name, register TABLE *form, uint options, ulonglong auto_increment_value); friend class Copy_field; @@ -660,7 +1413,6 @@ public: friend class Item_std_field; friend class Item_sum_num; friend class Item_sum_sum; - friend class Item_sum_str; friend class Item_sum_count; friend class Item_sum_avg; friend class Item_sum_std; @@ -728,15 +1480,34 @@ protected: const uchar *unpack_int64(uchar* to, const uchar *from, const uchar *from_end) { return unpack_int(to, from, from_end, 8); } - bool field_flags_are_binary() - { - return (flags & (BINCMP_FLAG | BINARY_FLAG)) != 0; - } - + double pos_in_interval_val_real(Field *min, Field *max); + double pos_in_interval_val_str(Field *min, Field *max, uint data_offset); }; class Field_num :public Field { +protected: + int check_edom_and_important_data_truncation(const char *type, bool edom, + CHARSET_INFO *cs, + const char *str, uint length, + const char *end_of_num); + int check_edom_and_truncation(const char *type, bool edom, + CHARSET_INFO *cs, + const char *str, uint length, + const char *end_of_num); + int check_int(CHARSET_INFO *cs, const char *str, uint length, + const char *int_end, int error) + { + return check_edom_and_truncation("integer", + error == MY_ERRNO_EDOM || str == int_end, + cs, str, length, int_end); + } + bool get_int(CHARSET_INFO *cs, const char *from, uint len, + longlong *rnd, ulonglong unsigned_max, + longlong signed_min, longlong signed_max); + void prepend_zeros(String *value) const; + Item *get_equal_zerofill_const_item(THD *thd, const Context &ctx, + Item *const_item); public: const uint8 dec; bool zerofill,unsigned_flag; // Purify cannot handle bit fields @@ -748,7 +1519,12 @@ public: enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; } CHARSET_INFO *charset(void) const { return &my_charset_numeric; } - void prepend_zeros(String *value); + Item *get_equal_const_item(THD *thd, const Context &ctx, Item *const_item) + { + return (flags & ZEROFILL_FLAG) ? + get_equal_zerofill_const_item(thd, ctx, const_item) : + const_item; + } void add_zerofill_and_unsigned(String &res) const; friend class Create_field; void make_field(Send_field *); @@ -757,6 +1533,7 @@ public: bool eq_def(Field *field); int store_decimal(const my_decimal *); my_decimal *val_decimal(my_decimal *); + bool val_bool() { return val_int() != 0; } uint is_equal(Create_field *new_field); uint row_pack_length() const { return pack_length(); } uint32 pack_length_from_metadata(uint field_metadata) { @@ -766,11 +1543,11 @@ public: return length; } int store_time_dec(MYSQL_TIME *ltime, uint dec); - int check_int(CHARSET_INFO *cs, const char *str, int length, - const char *int_end, int error); - bool get_int(CHARSET_INFO *cs, const char *from, uint len, - longlong *rnd, ulonglong unsigned_max, - longlong signed_min, longlong signed_max); + double pos_in_interval(Field *min, Field *max) + { + return pos_in_interval_val_real(min, max); + } + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); }; @@ -781,27 +1558,17 @@ protected: enum Derivation field_derivation; uint field_repertoire; public: + bool can_be_substituted_to_equal_item(const Context &ctx, + const Item_equal *item_equal); Field_str(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, utype unireg_check_arg, const char *field_name_arg, CHARSET_INFO *charset); Item_result result_type () const { return STRING_RESULT; } - /* - match_collation_to_optimize_range() is to distinguish in - range optimizer (see opt_range.cc) between real string types: - CHAR, VARCHAR, TEXT - and the other string-alike types with result_type() == STRING_RESULT: - DATE, TIME, DATETIME, TIMESTAMP - We need it to decide whether to test if collation of the operation - matches collation of the field (needed only for real string types). - QQ: shouldn't DATE/TIME types have their own XXX_RESULT types eventually? - */ - virtual bool match_collation_to_optimize_range() const=0; uint decimals() const { return NOT_FIXED_DEC; } int store(double nr); int store(longlong nr, bool unsigned_val)=0; int store_decimal(const my_decimal *); int store(const char *to,uint length,CHARSET_INFO *cs)=0; - uint size_of() const { return sizeof(*this); } uint repertoire(void) const { return field_repertoire; } CHARSET_INFO *charset(void) const { return field_charset; } enum Derivation derivation(void) const { return field_derivation; } @@ -815,9 +1582,16 @@ public: uint32 max_display_length() { return field_length; } friend class Create_field; my_decimal *val_decimal(my_decimal *); + bool val_bool() { return val_real() != 0e0; } virtual bool str_needs_quotes() { return TRUE; } uint is_equal(Create_field *new_field); - bool eq_cmp_as_binary() { return test(flags & BINARY_FLAG); } + bool eq_cmp_as_binary() { return MY_TEST(flags & BINARY_FLAG); } + virtual uint length_size() { return 0; } + double pos_in_interval(Field *min, Field *max) + { + return pos_in_interval_val_str(min, max, length_size()); + } + bool test_if_equality_guarantees_uniqueness(const Item *const_item) const; }; /* base class for Field_string, Field_varstring and Field_blob */ @@ -827,6 +1601,21 @@ class Field_longstr :public Field_str protected: int report_if_important_data(const char *ptr, const char *end, bool count_spaces); + bool check_string_copy_error(const String_copier *copier, + const char *end, CHARSET_INFO *cs); + int check_conversion_status(const String_copier *copier, + const char *end, CHARSET_INFO *cs, + bool count_spaces) + { + if (check_string_copy_error(copier, end, cs)) + return 2; + return report_if_important_data(copier->source_end_pos(), + end, count_spaces); + } + bool cmp_to_string_with_same_collation(const Item_bool_func *cond, + const Item *item) const; + bool cmp_to_string_with_stricter_collation(const Item_bool_func *cond, + const Item *item) const; public: Field_longstr(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, utype unireg_check_arg, @@ -837,15 +1626,29 @@ public: int store_decimal(const my_decimal *d); uint32 max_data_length() const; + bool is_updatable() const { DBUG_ASSERT(table && table->write_set); return bitmap_is_set(table->write_set, field_index); } + bool match_collation_to_optimize_range() const { return true; } + + bool can_optimize_keypart_ref(const Item_bool_func *cond, + const Item *item) const; + bool can_optimize_hash_join(const Item_bool_func *cond, + const Item *item) const; + bool can_optimize_group_min_max(const Item_bool_func *cond, + const Item *const_item) const; + bool can_optimize_range(const Item_bool_func *cond, + const Item *item, + bool is_eq_func) const; }; /* base class for float and double and decimal (old one) */ class Field_real :public Field_num { +protected: + double get_double(const char *str, uint length, CHARSET_INFO *cs, int *err); public: bool not_fixed; @@ -862,8 +1665,10 @@ public: int store_time_dec(MYSQL_TIME *ltime, uint dec); bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); my_decimal *val_decimal(my_decimal *); + bool val_bool() { return val_real() != 0e0; } uint32 max_display_length() { return field_length; } uint size_of() const { return sizeof(*this); } + Item *get_equal_const_item(THD *thd, const Context &ctx, Item *const_item); }; @@ -925,6 +1730,7 @@ public: Item_result result_type () const { return DECIMAL_RESULT; } int reset(void); bool store_value(const my_decimal *decimal_value); + bool store_value(const my_decimal *decimal_value, int *native_error); void set_value_on_overflow(my_decimal *decimal_value, bool sign); int store(const char *to, uint length, CHARSET_INFO *charset); int store(double nr); @@ -935,6 +1741,13 @@ public: longlong val_int(void); my_decimal *val_decimal(my_decimal *); String *val_str(String*, String *); + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); + bool val_bool() + { + my_decimal decimal_value; + my_decimal *val= val_decimal(&decimal_value); + return val ? !my_decimal_is_zero(val) : 0; + } int cmp(const uchar *, const uchar *); void sort_string(uchar *buff, uint length); bool zero_pack() const { return 0; } @@ -948,7 +1761,8 @@ public: uint16 mflags, int *order_var); uint is_equal(Create_field *new_field); virtual const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end, uint param_data); - static Field *create_from_item (Item *); + static Field *create_from_item(MEM_ROOT *root, Item *); + Item *get_equal_const_item(THD *thd, const Context &ctx, Item *const_item); }; @@ -993,6 +1807,11 @@ public: *to= *from; return from + 1; } + + virtual ulonglong get_max_int_value() const + { + return unsigned_flag ? 0xFFULL : 0x7FULL; + } }; @@ -1034,6 +1853,11 @@ public: virtual const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end, uint param_data) { return unpack_int16(to, from, from_end); } + + virtual ulonglong get_max_int_value() const + { + return unsigned_flag ? 0xFFFFULL : 0x7FFFULL; + } }; class Field_medium :public Field_num { @@ -1067,6 +1891,11 @@ public: { return Field::pack(to, from, max_length); } + + virtual ulonglong get_max_int_value() const + { + return unsigned_flag ? 0xFFFFFFULL : 0x7FFFFFULL; + } }; @@ -1112,6 +1941,11 @@ public: { return unpack_int32(to, from, from_end); } + + virtual ulonglong get_max_int_value() const + { + return unsigned_flag ? 0xFFFFFFFFULL : 0x7FFFFFFFULL; + } }; @@ -1161,6 +1995,10 @@ public: { return unpack_int64(to, from, from_end); } + virtual ulonglong get_max_int_value() const + { + return unsigned_flag ? 0xFFFFFFFFFFFFFFFFULL : 0x7FFFFFFFFFFFFFFFULL; + } }; @@ -1194,12 +2032,20 @@ public: uint32 pack_length() const { return sizeof(float); } uint row_pack_length() const { return pack_length(); } void sql_type(String &str) const; + virtual ulonglong get_max_int_value() const + { + /* + We use the maximum as per IEEE754-2008 standard, 2^24 + */ + return 0x1000000ULL; + } private: int do_save_field_metadata(uchar *first_byte); }; class Field_double :public Field_real { + longlong val_int_from_real(bool want_unsigned_result); public: Field_double(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, @@ -1226,7 +2072,8 @@ public: int store(longlong nr, bool unsigned_val); int reset(void) { bzero(ptr,sizeof(double)); return 0; } double val_real(void); - longlong val_int(void); + longlong val_int(void) { return val_int_from_real(false); } + ulonglong val_uint(void) { return (ulonglong) val_int_from_real(true); } String *val_str(String*,String *); bool send_binary(Protocol *protocol); int cmp(const uchar *,const uchar *); @@ -1234,6 +2081,13 @@ public: uint32 pack_length() const { return sizeof(double); } uint row_pack_length() const { return pack_length(); } void sql_type(String &str) const; + virtual ulonglong get_max_int_value() const + { + /* + We use the maximum as per IEEE754-2008 standard, 2^53 + */ + return 0x20000000000000ULL; + } private: int do_save_field_metadata(uchar *first_byte); }; @@ -1251,7 +2105,6 @@ public: unireg_check_arg, field_name_arg, cs) {} enum_field_types type() const { return MYSQL_TYPE_NULL;} - bool match_collation_to_optimize_range() const { return FALSE; } int store(const char *to, uint length, CHARSET_INFO *cs) { null[0]=1; return 0; } int store(double nr) { null[0]=1; return 0; } @@ -1260,6 +2113,7 @@ public: int reset(void) { return 0; } double val_real(void) { return 0.0;} longlong val_int(void) { return 0;} + bool val_bool(void) { return false; } my_decimal *val_decimal(my_decimal *) { return 0; } String *val_str(String *value,String *value2) { value2->length(0); return value2;} @@ -1270,32 +2124,124 @@ public: uint size_of() const { return sizeof(*this); } uint32 max_display_length() { return 4; } void move_field_offset(my_ptrdiff_t ptr_diff) {} + bool can_optimize_keypart_ref(const Item_bool_func *cond, + const Item *item) const + { + DBUG_ASSERT(0); + return false; + } + bool can_optimize_group_min_max(const Item_bool_func *cond, + const Item *const_item) const + { + DBUG_ASSERT(0); + return false; + } }; -class Field_timestamp :public Field_str { +class Field_temporal: public Field { +protected: + Item *get_equal_const_item_datetime(THD *thd, const Context &ctx, + Item *const_item); +public: + Field_temporal(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, + uchar null_bit_arg, utype unireg_check_arg, + const char *field_name_arg) + :Field(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg, + field_name_arg) + { flags|= BINARY_FLAG; } + Item_result result_type () const { return STRING_RESULT; } + uint32 max_display_length() { return field_length; } + bool str_needs_quotes() { return TRUE; } + enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } + uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; } + CHARSET_INFO *charset(void) const { return &my_charset_numeric; } + CHARSET_INFO *sort_charset(void) const { return &my_charset_bin; } + bool binary() const { return true; } + enum Item_result cmp_type () const { return TIME_RESULT; } + bool val_bool() { return val_real() != 0e0; } + uint is_equal(Create_field *new_field); + bool eq_def(Field *field) + { + return (Field::eq_def(field) && decimals() == field->decimals()); + } + my_decimal *val_decimal(my_decimal*); + void set_warnings(Sql_condition::enum_warning_level trunc_level, + const ErrConv *str, int was_cut, timestamp_type ts_type); + double pos_in_interval(Field *min, Field *max) + { + return pos_in_interval_val_real(min, max); + } + bool can_optimize_keypart_ref(const Item_bool_func *cond, + const Item *item) const; + bool can_optimize_group_min_max(const Item_bool_func *cond, + const Item *const_item) const; + bool can_optimize_range(const Item_bool_func *cond, + const Item *item, + bool is_eq_func) const + { + return true; + } +}; + + +/** + Abstract class for: + - DATE + - DATETIME + - DATETIME(1..6) + - DATETIME(0..6) - MySQL56 version +*/ +class Field_temporal_with_date: public Field_temporal { +protected: + int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, + int was_cut, int have_smth_to_conv); + virtual void store_TIME(MYSQL_TIME *ltime) = 0; + virtual bool get_TIME(MYSQL_TIME *ltime, const uchar *pos, + ulonglong fuzzydate) const = 0; + bool validate_MMDD(bool not_zero_date, uint month, uint day, + ulonglong fuzzydate) const + { + if (!not_zero_date) + return fuzzydate & TIME_NO_ZERO_DATE; + if (!month || !day) + return fuzzydate & TIME_NO_ZERO_IN_DATE; + return false; + } +public: + Field_temporal_with_date(uchar *ptr_arg, uint32 len_arg, + uchar *null_ptr_arg, uchar null_bit_arg, + utype unireg_check_arg, + const char *field_name_arg) + :Field_temporal(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) + {} + int store(const char *to, uint length, CHARSET_INFO *charset); + int store(double nr); + int store(longlong nr, bool unsigned_val); + int store_time_dec(MYSQL_TIME *ltime, uint dec); + int store_decimal(const my_decimal *); + bool validate_value_in_record(THD *thd, const uchar *record) const; +}; + + +class Field_timestamp :public Field_temporal { protected: int store_TIME_with_warning(THD *, MYSQL_TIME *, const ErrConv *, - bool, bool); + int warnings, bool have_smth_to_conv); public: Field_timestamp(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, - TABLE_SHARE *share, CHARSET_INFO *cs); - Field_timestamp(bool maybe_null_arg, const char *field_name_arg, - CHARSET_INFO *cs); + TABLE_SHARE *share); enum_field_types type() const { return MYSQL_TYPE_TIMESTAMP;} - bool match_collation_to_optimize_range() const { return FALSE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONG_INT; } - enum Item_result cmp_type () const { return TIME_RESULT; } - enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } - uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; } - CHARSET_INFO *charset(void) const { return &my_charset_numeric; } - bool binary() const { return 1; } int store(const char *to,uint length,CHARSET_INFO *charset); int store(double nr); int store(longlong nr, bool unsigned_val); int store_time_dec(MYSQL_TIME *ltime, uint dec); + int store_decimal(const my_decimal *); + int store_timestamp(Field_timestamp *from); double val_real(void); longlong val_int(void); String *val_str(String*,String *); @@ -1305,24 +2251,40 @@ public: uint32 pack_length() const { return 4; } void sql_type(String &str) const; bool zero_pack() const { return 0; } - uint decimals() const { return 0; } virtual int set_time(); virtual void set_default() { - if (table->timestamp_field == this && - unireg_check != TIMESTAMP_UN_FIELD) + if (has_insert_default_function()) set_time(); else Field::set_default(); } + virtual void set_explicit_default(Item *value); + virtual int evaluate_insert_default_function() + { + int res= 0; + if (has_insert_default_function()) + res= set_time(); + return res; + } + virtual int evaluate_update_default_function() + { + int res= 0; + if (has_update_default_function()) + res= set_time(); + return res; + } /* Get TIMESTAMP field value as seconds since begging of Unix Epoch */ - virtual my_time_t get_timestamp(ulong *sec_part) const; + virtual my_time_t get_timestamp(const uchar *pos, ulong *sec_part) const; + my_time_t get_timestamp(ulong *sec_part) const + { + return get_timestamp(ptr, sec_part); + } virtual void store_TIME(my_time_t timestamp, ulong sec_part) { int4store(ptr,timestamp); } bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); - timestamp_auto_set_type get_auto_set_type() const; uchar *pack(uchar *to, const uchar *from, uint max_length __attribute__((unused))) { @@ -1333,46 +2295,114 @@ public: { return unpack_int32(to, from, from_end); } + bool validate_value_in_record(THD *thd, const uchar *record) const; + Item *get_equal_const_item(THD *thd, const Context &ctx, Item *const_item) + { + return get_equal_const_item_datetime(thd, ctx, const_item); + } + uint size_of() const { return sizeof(*this); } }; -class Field_timestamp_hires :public Field_timestamp { +/** + Abstract class for: + - TIMESTAMP(1..6) + - TIMESTAMP(0..6) - MySQL56 version +*/ +class Field_timestamp_with_dec :public Field_timestamp { +protected: uint dec; public: - Field_timestamp_hires(uchar *ptr_arg, - uchar *null_ptr_arg, uchar null_bit_arg, - enum utype unireg_check_arg, const char *field_name_arg, - TABLE_SHARE *share, uint dec_arg, CHARSET_INFO *cs) : - Field_timestamp(ptr_arg, MAX_DATETIME_WIDTH + dec_arg + 1, null_ptr_arg, - null_bit_arg, unireg_check_arg, field_name_arg, share, cs), + Field_timestamp_with_dec(uchar *ptr_arg, + uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, + const char *field_name_arg, + TABLE_SHARE *share, uint dec_arg) : + Field_timestamp(ptr_arg, + MAX_DATETIME_WIDTH + dec_arg + MY_TEST(dec_arg), null_ptr_arg, + null_bit_arg, unireg_check_arg, field_name_arg, share), dec(dec_arg) { - DBUG_ASSERT(dec); DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); } - void sql_type(String &str) const; - my_time_t get_timestamp(ulong *sec_part) const; - void store_TIME(my_time_t timestamp, ulong sec_part); - int store_decimal(const my_decimal *d); - double val_real(void); - String *val_str(String*,String *); - my_decimal* val_decimal(my_decimal*); - bool send_binary(Protocol *protocol); - int cmp(const uchar *,const uchar *); - void sort_string(uchar *buff,uint length); uint decimals() const { return dec; } - int set_time(); enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } - void make_field(Send_field *field); - uint32 pack_length() const; uchar *pack(uchar *to, const uchar *from, uint max_length) { return Field::pack(to, from, max_length); } const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end, uint param_data) { return Field::unpack(to, from, from_end, param_data); } + void make_field(Send_field *field); + void sort_string(uchar *to, uint length) + { + DBUG_ASSERT(length == pack_length()); + memcpy(to, ptr, length); + } + bool send_binary(Protocol *protocol); + double val_real(void); + my_decimal* val_decimal(my_decimal*); + int set_time(); +}; + + +class Field_timestamp_hires :public Field_timestamp_with_dec { +public: + Field_timestamp_hires(uchar *ptr_arg, + uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, + const char *field_name_arg, + TABLE_SHARE *share, uint dec_arg) : + Field_timestamp_with_dec(ptr_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg, share, dec_arg) + { + DBUG_ASSERT(dec); + } + my_time_t get_timestamp(const uchar *pos, ulong *sec_part) const; + void store_TIME(my_time_t timestamp, ulong sec_part); + int cmp(const uchar *,const uchar *); + uint32 pack_length() const; + uint size_of() const { return sizeof(*this); } +}; + + +/** + TIMESTAMP(0..6) - MySQL56 version +*/ +class Field_timestampf :public Field_timestamp_with_dec { + int do_save_field_metadata(uchar *metadata_ptr) + { + *metadata_ptr= decimals(); + return 1; + } +public: + Field_timestampf(uchar *ptr_arg, + uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, + const char *field_name_arg, + TABLE_SHARE *share, uint dec_arg) : + Field_timestamp_with_dec(ptr_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg, share, dec_arg) + {} + enum_field_types real_type() const { return MYSQL_TYPE_TIMESTAMP2; } + enum_field_types binlog_type() const { return MYSQL_TYPE_TIMESTAMP2; } + uint32 pack_length() const + { + return my_timestamp_binary_length(dec); + } + uint row_pack_length() const { return pack_length(); } + uint pack_length_from_metadata(uint field_metadata) + { + DBUG_ENTER("Field_timestampf::pack_length_from_metadata"); + uint tmp= my_timestamp_binary_length(field_metadata); + DBUG_RETURN(tmp); + } + int cmp(const uchar *a_ptr,const uchar *b_ptr) + { + return memcmp(a_ptr, b_ptr, pack_length()); + } + void store_TIME(my_time_t timestamp, ulong sec_part); + my_time_t get_timestamp(const uchar *pos, ulong *sec_part) const; uint size_of() const { return sizeof(*this); } - bool eq_def(Field *field) - { return Field_str::eq_def(field) && dec == field->decimals(); } }; @@ -1399,56 +2429,27 @@ public: }; -class Field_temporal: public Field_str { -protected: - int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, - int was_cut, int have_smth_to_conv); - virtual void store_TIME(MYSQL_TIME *ltime) = 0; -public: - Field_temporal(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, - uchar null_bit_arg, utype unireg_check_arg, - const char *field_name_arg, CHARSET_INFO *charset_arg) - :Field_str(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg, - field_name_arg, charset_arg) - { flags|= BINARY_FLAG; } - enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } - uint repertoire(void) const { return MY_REPERTOIRE_NUMERIC; } - CHARSET_INFO *charset(void) const { return &my_charset_numeric; } - bool binary() const { return 1; } - bool match_collation_to_optimize_range() const { return FALSE; } - enum Item_result cmp_type () const { return TIME_RESULT; } - int store(const char *to,uint length,CHARSET_INFO *charset); - int store(double nr); - int store(longlong nr, bool unsigned_val); - int store_time_dec(MYSQL_TIME *ltime, uint dec); - my_decimal *val_decimal(my_decimal*); - bool eq_def(Field *field) - { - return (Field_str::eq_def(field) && decimals() == field->decimals()); - } -}; - -class Field_date :public Field_temporal { +class Field_date :public Field_temporal_with_date { void store_TIME(MYSQL_TIME *ltime); + bool get_TIME(MYSQL_TIME *ltime, const uchar *pos, ulonglong fuzzydate) const; public: Field_date(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, - enum utype unireg_check_arg, const char *field_name_arg, - CHARSET_INFO *cs) - :Field_temporal(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) {} + enum utype unireg_check_arg, const char *field_name_arg) + :Field_temporal_with_date(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) {} enum_field_types type() const { return MYSQL_TYPE_DATE;} enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONG_INT; } int reset(void) { ptr[0]=ptr[1]=ptr[2]=ptr[3]=0; return 0; } + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) + { return Field_date::get_TIME(ltime, ptr, fuzzydate); } double val_real(void); longlong val_int(void); String *val_str(String*,String *); - uint decimals() const { return 0; } bool send_binary(Protocol *protocol); int cmp(const uchar *,const uchar *); void sort_string(uchar *buff,uint length); uint32 pack_length() const { return 4; } void sql_type(String &str) const; - bool zero_pack() const { return 1; } uchar *pack(uchar* to, const uchar *from, uint max_length __attribute__((unused))) { @@ -1459,23 +2460,23 @@ public: { return unpack_int32(to, from, from_end); } + uint size_of() const { return sizeof(*this); } }; -class Field_newdate :public Field_temporal { +class Field_newdate :public Field_temporal_with_date { void store_TIME(MYSQL_TIME *ltime); + bool get_TIME(MYSQL_TIME *ltime, const uchar *pos, ulonglong fuzzydate) const; public: Field_newdate(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, - enum utype unireg_check_arg, const char *field_name_arg, - CHARSET_INFO *cs) - :Field_temporal(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) + enum utype unireg_check_arg, const char *field_name_arg) + :Field_temporal_with_date(ptr_arg, MAX_DATE_WIDTH, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) {} enum_field_types type() const { return MYSQL_TYPE_DATE;} enum_field_types real_type() const { return MYSQL_TYPE_NEWDATE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_UINT24; } int reset(void) { ptr[0]=ptr[1]=ptr[2]=0; return 0; } - uint decimals() const { return 0; } double val_real(void); longlong val_int(void); String *val_str(String*,String *); @@ -1484,19 +2485,31 @@ public: void sort_string(uchar *buff,uint length); uint32 pack_length() const { return 3; } void sql_type(String &str) const; - bool zero_pack() const { return 1; } - bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) + { return Field_newdate::get_TIME(ltime, ptr, fuzzydate); } + uint size_of() const { return sizeof(*this); } + Item *get_equal_const_item(THD *thd, const Context &ctx, Item *const_item); }; class Field_time :public Field_temporal { - void store_TIME(MYSQL_TIME *ltime); + /* + when this Field_time instance is used for storing values for index lookups + (see class store_key, Field::new_key_field(), etc), the following + might be set to TO_DAYS(CURDATE()). See also Field_time::store_time_dec() + */ + long curdays; +protected: + virtual void store_TIME(MYSQL_TIME *ltime); + int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, + int was_cut, int have_smth_to_conv); + bool check_zero_in_date_with_warn(ulonglong fuzzydate); public: Field_time(uchar *ptr_arg, uint length_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, - const char *field_name_arg, CHARSET_INFO *cs) + const char *field_name_arg) :Field_temporal(ptr_arg, length_arg, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) + unireg_check_arg, field_name_arg), curdays(0) {} enum_field_types type() const { return MYSQL_TYPE_TIME;} enum ha_base_keytype key_type() const { return HA_KEYTYPE_INT24; } @@ -1504,7 +2517,7 @@ public: int store(const char *to,uint length,CHARSET_INFO *charset); int store(double nr); int store(longlong nr, bool unsigned_val); - uint decimals() const { return 0; } + int store_decimal(const my_decimal *); double val_real(void); longlong val_int(void); String *val_str(String*,String *); @@ -1514,55 +2527,128 @@ public: void sort_string(uchar *buff,uint length); uint32 pack_length() const { return 3; } void sql_type(String &str) const; - bool zero_pack() const { return 1; } + uint size_of() const { return sizeof(*this); } + void set_curdays(THD *thd); + Field *new_key_field(MEM_ROOT *root, TABLE *new_table, + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit); + Item *get_equal_const_item(THD *thd, const Context &ctx, Item *const_item); }; -class Field_time_hires :public Field_time { + +/** + Abstract class for: + - TIME(1..6) + - TIME(0..6) - MySQL56 version +*/ +class Field_time_with_dec :public Field_time { +protected: uint dec; +public: + Field_time_with_dec(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, const char *field_name_arg, + uint dec_arg) + :Field_time(ptr_arg, MIN_TIME_WIDTH + dec_arg + MY_TEST(dec_arg), + null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg), + dec(dec_arg) + { + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + } + uint decimals() const { return dec; } + enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } + longlong val_int(void); + double val_real(void); + void make_field(Send_field *); +}; + + +/** + TIME(1..6) +*/ +class Field_time_hires :public Field_time_with_dec { longlong zero_point; void store_TIME(MYSQL_TIME *ltime); public: Field_time_hires(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, - uint dec_arg, CHARSET_INFO *cs) - :Field_time(ptr_arg, MIN_TIME_WIDTH + dec_arg + 1, null_ptr_arg, - null_bit_arg, unireg_check_arg, field_name_arg, cs), - dec(dec_arg) + uint dec_arg) + :Field_time_with_dec(ptr_arg, null_ptr_arg, + null_bit_arg, unireg_check_arg, field_name_arg, + dec_arg) { DBUG_ASSERT(dec); - DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); zero_point= sec_part_shift( ((TIME_MAX_VALUE_SECONDS+1LL)*TIME_SECOND_PART_FACTOR), dec); } - enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } - uint decimals() const { return dec; } - int store_decimal(const my_decimal *d); - longlong val_int(void); - double val_real(void); - String *val_str(String*,String *); int reset(void); bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); - bool send_binary(Protocol *protocol); int cmp(const uchar *,const uchar *); void sort_string(uchar *buff,uint length); uint32 pack_length() const; - void sql_type(String &str) const; - void make_field(Send_field *); uint size_of() const { return sizeof(*this); } }; -class Field_datetime :public Field_temporal { + +/** + TIME(0..6) - MySQL56 version +*/ +class Field_timef :public Field_time_with_dec { + void store_TIME(MYSQL_TIME *ltime); + int do_save_field_metadata(uchar *metadata_ptr) + { + *metadata_ptr= decimals(); + return 1; + } +public: + Field_timef(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, + enum utype unireg_check_arg, const char *field_name_arg, + uint dec_arg) + :Field_time_with_dec(ptr_arg, null_ptr_arg, + null_bit_arg, unireg_check_arg, field_name_arg, + dec_arg) + { + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + } + enum_field_types real_type() const { return MYSQL_TYPE_TIME2; } + enum_field_types binlog_type() const { return MYSQL_TYPE_TIME2; } + uint32 pack_length() const + { + return my_time_binary_length(dec); + } + uint row_pack_length() const { return pack_length(); } + uint pack_length_from_metadata(uint field_metadata) + { + DBUG_ENTER("Field_timef::pack_length_from_metadata"); + uint tmp= my_time_binary_length(field_metadata); + DBUG_RETURN(tmp); + } + void sort_string(uchar *to, uint length) + { + DBUG_ASSERT(length == Field_timef::pack_length()); + memcpy(to, ptr, length); + } + int cmp(const uchar *a_ptr, const uchar *b_ptr) + { + return memcmp(a_ptr, b_ptr, pack_length()); + } + int reset(); + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); + uint size_of() const { return sizeof(*this); } +}; + + +class Field_datetime :public Field_temporal_with_date { void store_TIME(MYSQL_TIME *ltime); + bool get_TIME(MYSQL_TIME *ltime, const uchar *pos, ulonglong fuzzydate) const; public: Field_datetime(uchar *ptr_arg, uint length_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, - const char *field_name_arg, CHARSET_INFO *cs) - :Field_temporal(ptr_arg, length_arg, null_ptr_arg, null_bit_arg, - unireg_check_arg, field_name_arg, cs) + const char *field_name_arg) + :Field_temporal_with_date(ptr_arg, length_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg) {} enum_field_types type() const { return MYSQL_TYPE_DATETIME;} enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONGLONG; } - uint decimals() const { return 0; } double val_real(void); longlong val_int(void); String *val_str(String*,String *); @@ -1571,8 +2657,30 @@ public: void sort_string(uchar *buff,uint length); uint32 pack_length() const { return 8; } void sql_type(String &str) const; - bool zero_pack() const { return 1; } - bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) + { return Field_datetime::get_TIME(ltime, ptr, fuzzydate); } + virtual int set_time(); + virtual void set_default() + { + if (has_insert_default_function()) + set_time(); + else + Field::set_default(); + } + virtual int evaluate_insert_default_function() + { + int res= 0; + if (has_insert_default_function()) + res= set_time(); + return res; + } + virtual int evaluate_update_default_function() + { + int res= 0; + if (has_update_default_function()) + res= set_time(); + return res; + } uchar *pack(uchar* to, const uchar *from, uint max_length __attribute__((unused))) { @@ -1583,88 +2691,170 @@ public: { return unpack_int64(to, from, from_end); } + Item *get_equal_const_item(THD *thd, const Context &ctx, Item *const_item) + { + return get_equal_const_item_datetime(thd, ctx, const_item); + } + uint size_of() const { return sizeof(*this); } }; -class Field_datetime_hires :public Field_datetime { - void store_TIME(MYSQL_TIME *ltime); +/** + Abstract class for: + - DATETIME(1..6) + - DATETIME(0..6) - MySQL56 version +*/ +class Field_datetime_with_dec :public Field_datetime { +protected: uint dec; public: - Field_datetime_hires(uchar *ptr_arg, uchar *null_ptr_arg, - uchar null_bit_arg, enum utype unireg_check_arg, - const char *field_name_arg, uint dec_arg, - CHARSET_INFO *cs) - :Field_datetime(ptr_arg, MAX_DATETIME_WIDTH + dec_arg + 1, + Field_datetime_with_dec(uchar *ptr_arg, uchar *null_ptr_arg, + uchar null_bit_arg, enum utype unireg_check_arg, + const char *field_name_arg, uint dec_arg) + :Field_datetime(ptr_arg, MAX_DATETIME_WIDTH + dec_arg + MY_TEST(dec_arg), null_ptr_arg, null_bit_arg, unireg_check_arg, - field_name_arg, cs), dec(dec_arg) + field_name_arg), dec(dec_arg) { - DBUG_ASSERT(dec); DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); } - enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } uint decimals() const { return dec; } + enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } void make_field(Send_field *field); - int store_decimal(const my_decimal *d); - double val_real(void); - longlong val_int(void); - String *val_str(String*,String *); bool send_binary(Protocol *protocol); - int cmp(const uchar *,const uchar *); - void sort_string(uchar *buff,uint length); - uint32 pack_length() const; - void sql_type(String &str) const; - bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); uchar *pack(uchar *to, const uchar *from, uint max_length) { return Field::pack(to, from, max_length); } const uchar *unpack(uchar* to, const uchar *from, const uchar *from_end, uint param_data) { return Field::unpack(to, from, from_end, param_data); } + void sort_string(uchar *to, uint length) + { + DBUG_ASSERT(length == pack_length()); + memcpy(to, ptr, length); + } + double val_real(void); + longlong val_int(void); + String *val_str(String*,String *); +}; + + +/** + DATETIME(1..6) +*/ +class Field_datetime_hires :public Field_datetime_with_dec { + void store_TIME(MYSQL_TIME *ltime); + bool get_TIME(MYSQL_TIME *ltime, const uchar *pos, ulonglong fuzzydate) const; +public: + Field_datetime_hires(uchar *ptr_arg, uchar *null_ptr_arg, + uchar null_bit_arg, enum utype unireg_check_arg, + const char *field_name_arg, uint dec_arg) + :Field_datetime_with_dec(ptr_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg, dec_arg) + { + DBUG_ASSERT(dec); + } + int cmp(const uchar *,const uchar *); + uint32 pack_length() const; + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) + { return Field_datetime_hires::get_TIME(ltime, ptr, fuzzydate); } uint size_of() const { return sizeof(*this); } }; + +/** + DATETIME(0..6) - MySQL56 version +*/ +class Field_datetimef :public Field_datetime_with_dec { + void store_TIME(MYSQL_TIME *ltime); + bool get_TIME(MYSQL_TIME *ltime, const uchar *pos, ulonglong fuzzydate) const; + int do_save_field_metadata(uchar *metadata_ptr) + { + *metadata_ptr= decimals(); + return 1; + } +public: + Field_datetimef(uchar *ptr_arg, uchar *null_ptr_arg, + uchar null_bit_arg, enum utype unireg_check_arg, + const char *field_name_arg, uint dec_arg) + :Field_datetime_with_dec(ptr_arg, null_ptr_arg, null_bit_arg, + unireg_check_arg, field_name_arg, dec_arg) + {} + enum_field_types real_type() const { return MYSQL_TYPE_DATETIME2; } + enum_field_types binlog_type() const { return MYSQL_TYPE_DATETIME2; } + uint32 pack_length() const + { + return my_datetime_binary_length(dec); + } + uint row_pack_length() const { return pack_length(); } + uint pack_length_from_metadata(uint field_metadata) + { + DBUG_ENTER("Field_datetimef::pack_length_from_metadata"); + uint tmp= my_datetime_binary_length(field_metadata); + DBUG_RETURN(tmp); + } + int cmp(const uchar *a_ptr, const uchar *b_ptr) + { + return memcmp(a_ptr, b_ptr, pack_length()); + } + int reset(); + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) + { return Field_datetimef::get_TIME(ltime, ptr, fuzzydate); } + uint size_of() const { return sizeof(*this); } +}; + + static inline Field_timestamp * -new_Field_timestamp(uchar *ptr, uchar *null_ptr, uchar null_bit, +new_Field_timestamp(MEM_ROOT *root,uchar *ptr, uchar *null_ptr, uchar null_bit, enum Field::utype unireg_check, const char *field_name, - TABLE_SHARE *share, uint dec, CHARSET_INFO *cs) + TABLE_SHARE *share, uint dec) { if (dec==0) - return new Field_timestamp(ptr, MAX_DATETIME_WIDTH, null_ptr, null_bit, - unireg_check, field_name, share, cs); + return new (root) + Field_timestamp(ptr, MAX_DATETIME_WIDTH, null_ptr, + null_bit, unireg_check, field_name, share); if (dec == NOT_FIXED_DEC) dec= MAX_DATETIME_PRECISION; - return new Field_timestamp_hires(ptr, null_ptr, null_bit, unireg_check, - field_name, share, dec, cs); + return new (root) + Field_timestamp_hires(ptr, null_ptr, null_bit, unireg_check, + field_name, share, dec); } static inline Field_time * -new_Field_time(uchar *ptr, uchar *null_ptr, uchar null_bit, +new_Field_time(MEM_ROOT *root, uchar *ptr, uchar *null_ptr, uchar null_bit, enum Field::utype unireg_check, const char *field_name, - uint dec, CHARSET_INFO *cs) + uint dec) { if (dec == 0) - return new Field_time(ptr, MIN_TIME_WIDTH, null_ptr, null_bit, - unireg_check, field_name, cs); + return new (root) + Field_time(ptr, MIN_TIME_WIDTH, null_ptr, null_bit, unireg_check, + field_name); if (dec == NOT_FIXED_DEC) dec= MAX_DATETIME_PRECISION; - return new Field_time_hires(ptr, null_ptr, null_bit, - unireg_check, field_name, dec, cs); + return new (root) + Field_time_hires(ptr, null_ptr, null_bit, unireg_check, field_name, dec); } static inline Field_datetime * -new_Field_datetime(uchar *ptr, uchar *null_ptr, uchar null_bit, +new_Field_datetime(MEM_ROOT *root, uchar *ptr, uchar *null_ptr, uchar null_bit, enum Field::utype unireg_check, - const char *field_name, uint dec, CHARSET_INFO *cs) + const char *field_name, uint dec) { if (dec == 0) - return new Field_datetime(ptr, MAX_DATETIME_WIDTH, null_ptr, null_bit, - unireg_check, field_name, cs); + return new (root) + Field_datetime(ptr, MAX_DATETIME_WIDTH, null_ptr, null_bit, + unireg_check, field_name); if (dec == NOT_FIXED_DEC) dec= MAX_DATETIME_PRECISION; - return new Field_datetime_hires(ptr, null_ptr, null_bit, - unireg_check, field_name, dec, cs); + return new (root) + Field_datetime_hires(ptr, null_ptr, null_bit, + unireg_check, field_name, dec); } class Field_string :public Field_longstr { + class Warn_filter_string: public Warn_filter + { + public: + Warn_filter_string(const THD *thd, const Field_string *field); + }; public: bool can_alter_field_type; Field_string(uchar *ptr_arg, uint32 len_arg,uchar *null_ptr_arg, @@ -1688,7 +2878,6 @@ public: orig_table->s->frm_version < FRM_VER_TRUE_VARCHAR ? MYSQL_TYPE_VAR_STRING : MYSQL_TYPE_STRING); } - bool match_collation_to_optimize_range() const { return TRUE; } enum ha_base_keytype key_type() const { return binary() ? HA_KEYTYPE_BINARY : HA_KEYTYPE_TEXT; } bool zero_pack() const { return 0; } @@ -1731,7 +2920,7 @@ public: enum_field_types real_type() const { return MYSQL_TYPE_STRING; } bool has_charset(void) const { return charset() == &my_charset_bin ? FALSE : TRUE; } - Field *new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type); + Field *make_new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type); virtual uint get_key_image(uchar *buff,uint length, imagetype type); private: int do_save_field_metadata(uchar *first_byte); @@ -1739,6 +2928,14 @@ private: class Field_varstring :public Field_longstr { + uchar *get_data() const + { + return ptr + length_bytes; + } + uint get_length() const + { + return length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); + } public: /* The maximum space available in a Field_varstring, in bytes. See @@ -1769,7 +2966,6 @@ public: } enum_field_types type() const { return MYSQL_TYPE_VARCHAR; } - bool match_collation_to_optimize_range() const { return TRUE; } enum ha_base_keytype key_type() const; uint row_pack_length() const { return field_length; } bool zero_pack() const { return 0; } @@ -1810,12 +3006,13 @@ public: enum_field_types real_type() const { return MYSQL_TYPE_VARCHAR; } bool has_charset(void) const { return charset() == &my_charset_bin ? FALSE : TRUE; } - Field *new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type); + Field *make_new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type); Field *new_key_field(MEM_ROOT *root, TABLE *new_table, - uchar *new_ptr, uchar *new_null_ptr, - uint new_null_bit); + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit); uint is_equal(Create_field *new_field); void hash(ulong *nr, ulong *nr2); + uint length_size() { return length_bytes; } private: int do_save_field_metadata(uchar *first_byte); }; @@ -1862,8 +3059,8 @@ public: Field_blob(uint32 packlength_arg) :Field_longstr((uchar*) 0, 0, (uchar*) "", 0, NONE, "temp", system_charset_info), packlength(packlength_arg) {} + /* Note that the default copy constructor is used, in clone() */ enum_field_types type() const { return MYSQL_TYPE_BLOB;} - bool match_collation_to_optimize_range() const { return TRUE; } enum ha_base_keytype key_type() const { return binary() ? HA_KEYTYPE_VARBINARY2 : HA_KEYTYPE_VARTEXT2; } int store(const char *to,uint length,CHARSET_INFO *charset); @@ -1880,10 +3077,14 @@ public: int cmp_binary(const uchar *a,const uchar *b, uint32 max_length=~0L); int key_cmp(const uchar *,const uchar*); int key_cmp(const uchar *str, uint length); + /* Never update the value of min_val for a blob field */ + bool update_min(Field *min_val, bool force_update) { return FALSE; } + /* Never update the value of max_val for a blob field */ + bool update_max(Field *max_val, bool force_update) { return FALSE; } uint32 key_length() const { return 0; } void sort_string(uchar *buff,uint length); uint32 pack_length() const - { return (uint32) (packlength+table->s->blob_ptr_size); } + { return (uint32) (packlength + portable_sizeof_char_ptr); } /** Return the packed length without the pointer size added. @@ -1897,6 +3098,7 @@ public: { return (uint32) (packlength); } uint row_pack_length() const { return pack_length_no_ptr(); } uint32 sort_length() const; + uint32 value_length() { return get_length(); } virtual uint32 max_data_length() const { return (uint32) (((ulonglong) 1 << (packlength*8)) -1); @@ -1940,6 +3142,9 @@ public: int copy_value(Field_blob *from); uint get_key_image(uchar *buff,uint length, imagetype type); void set_key_image(const uchar *buff,uint length); + Field *new_key_field(MEM_ROOT *root, TABLE *new_table, + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit); void sql_type(String &str) const; inline bool copy() { @@ -1961,15 +3166,13 @@ public: uint max_packed_col_length(uint max_length); void free() { value.free(); } inline void clear_temporary() { bzero((uchar*) &value,sizeof(value)); } - friend int field_conv(Field *to,Field *from); + friend int field_conv_incompatible(Field *to,Field *from); uint size_of() const { return sizeof(*this); } bool has_charset(void) const { return charset() == &my_charset_bin ? FALSE : TRUE; } uint32 max_display_length(); uint32 char_length(); uint is_equal(Create_field *new_field); - inline bool in_read_set() { return bitmap_is_set(table->read_set, field_index); } - inline bool in_write_set() { return bitmap_is_set(table->write_set, field_index); } private: int do_save_field_metadata(uchar *first_byte); }; @@ -1979,22 +3182,29 @@ private: class Field_geom :public Field_blob { public: enum geometry_type geom_type; + uint srid; + uint precision; + enum storage_type { GEOM_STORAGE_WKB= 0, GEOM_STORAGE_BINARY= 1}; + enum storage_type storage; Field_geom(uchar *ptr_arg, uchar *null_ptr_arg, uint null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, TABLE_SHARE *share, uint blob_pack_length, - enum geometry_type geom_type_arg) + enum geometry_type geom_type_arg, uint field_srid) :Field_blob(ptr_arg, null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg, share, blob_pack_length, &my_charset_bin) - { geom_type= geom_type_arg; } + { geom_type= geom_type_arg; srid= field_srid; } Field_geom(uint32 len_arg,bool maybe_null_arg, const char *field_name_arg, TABLE_SHARE *share, enum geometry_type geom_type_arg) :Field_blob(len_arg, maybe_null_arg, field_name_arg, &my_charset_bin) - { geom_type= geom_type_arg; } + { geom_type= geom_type_arg; srid= 0; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_VARBINARY2; } enum_field_types type() const { return MYSQL_TYPE_GEOMETRY; } - bool match_collation_to_optimize_range() const { return FALSE; } + bool can_optimize_range(const Item_bool_func *cond, + const Item *item, + bool is_eq_func) const; void sql_type(String &str) const; + uint is_equal(Create_field *new_field); int store(const char *to, uint length, CHARSET_INFO *charset); int store(double nr); int store(longlong nr, bool unsigned_val); @@ -2017,7 +3227,13 @@ public: geometry_type get_geometry_type() { return geom_type; }; static geometry_type geometry_type_merge(geometry_type, geometry_type); + uint get_srid() { return srid; } }; + +uint gis_field_options_image(uchar *buff, List<Create_field> &create_fields); +uint gis_field_options_read(const uchar *buf, uint buf_len, + Field_geom::storage_type *st_type,uint *precision, uint *scale, uint *srid); + #endif /*HAVE_SPATIAL*/ @@ -2038,9 +3254,8 @@ public: { flags|=ENUM_FLAG; } - Field *new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type); + Field *make_new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type); enum_field_types type() const { return MYSQL_TYPE_STRING; } - bool match_collation_to_optimize_range() const { return FALSE; } enum Item_result cmp_type () const { return INT_RESULT; } enum ha_base_keytype key_type() const; int store(const char *to,uint length,CHARSET_INFO *charset); @@ -2071,6 +3286,20 @@ public: virtual const uchar *unpack(uchar *to, const uchar *from, const uchar *from_end, uint param_data); + bool can_optimize_keypart_ref(const Item_bool_func *cond, + const Item *item) const; + bool can_optimize_group_min_max(const Item_bool_func *cond, + const Item *const_item) const + { + /* + Can't use GROUP_MIN_MAX optimization for ENUM and SET, + because the values are stored as numbers in index, + while MIN() and MAX() work as strings. + It would return the records with min and max enum numeric indexes. + "Bug#45300 MAX() and ENUM type" should be fixed first. + */ + return false; + } private: int do_save_field_metadata(uchar *first_byte); uint is_equal(Create_field *new_field); @@ -2152,13 +3381,14 @@ public: String *val_str(String*, String *); virtual bool str_needs_quotes() { return TRUE; } my_decimal *val_decimal(my_decimal *); + bool val_bool() { return val_int() != 0; } int cmp(const uchar *a, const uchar *b) { DBUG_ASSERT(ptr == a || ptr == b); if (ptr == a) - return Field_bit::key_cmp(b, bytes_in_rec+test(bit_len)); + return Field_bit::key_cmp(b, bytes_in_rec + MY_TEST(bit_len)); else - return Field_bit::key_cmp(a, bytes_in_rec+test(bit_len)) * -1; + return Field_bit::key_cmp(a, bytes_in_rec + MY_TEST(bit_len)) * -1; } int cmp_binary_offset(uint row_offset) { return cmp_offset(row_offset); } @@ -2167,6 +3397,36 @@ public: { return cmp_binary((uchar *) a, (uchar *) b); } int key_cmp(const uchar *str, uint length); int cmp_offset(uint row_offset); + bool update_min(Field *min_val, bool force_update) + { + longlong val= val_int(); + bool update_fl= force_update || val < min_val->val_int(); + if (update_fl) + { + min_val->set_notnull(); + min_val->store(val, FALSE); + } + return update_fl; + } + bool update_max(Field *max_val, bool force_update) + { + longlong val= val_int(); + bool update_fl= force_update || val > max_val->val_int(); + if (update_fl) + { + max_val->set_notnull(); + max_val->store(val, FALSE); + } + return update_fl; + } + void store_field_value(uchar *val, uint len) + { + store(*((longlong *)val), TRUE); + } + double pos_in_interval(Field *min, Field *max) + { + return pos_in_interval_val_real(min, max); + } void get_image(uchar *buff, uint length, CHARSET_INFO *cs) { get_key_image(buff, length, itRAW); } void set_image(const uchar *buff,uint length, CHARSET_INFO *cs) @@ -2190,8 +3450,8 @@ public: virtual void set_default(); Field *new_key_field(MEM_ROOT *root, TABLE *new_table, - uchar *new_ptr, uchar *new_null_ptr, - uint new_null_bit); + uchar *new_ptr, uint32 length, + uchar *new_null_ptr, uint new_null_bit); void set_bit_ptr(uchar *bit_ptr_arg, uchar bit_ofs_arg) { bit_ptr= bit_ptr_arg; @@ -2239,6 +3499,7 @@ public: }; +extern const LEX_STRING null_lex_str; /* Create field class for CREATE TABLE */ @@ -2250,13 +3511,13 @@ public: const char *change; // If done with alter table const char *after; // Put column after this one LEX_STRING comment; // Comment for field - Item *def; // Default value + Item *def, *on_update; // Default value enum enum_field_types sql_type; /* At various stages in execution this can be length of field in bytes or max number of characters. */ - ulong length; + ulonglong length; /* The value of `length' as set by parser: is the number of characters for most of the types, or of bytes for BLOBs or numeric types. @@ -2269,16 +3530,18 @@ public: // Used only for UCS2 intervals List<String> interval_list; CHARSET_INFO *charset; + uint32 srid; Field::geometry_type geom_type; Field *field; // For alter table engine_option_value *option_list; /** structure with parsed options (for comparing fields in ALTER TABLE) */ ha_field_option_struct *option_struct; - uint8 row,col,sc_length,interval_id; // For rea_create_table + uint8 interval_id; // For rea_create_table uint offset,pack_flag; + bool create_if_not_exists; // Used in ALTER TABLE IF NOT EXISTS - /* + /* This is additinal data provided for any computed(virtual) field. In particular it includes a pointer to the item by which this field can be computed from other fields. @@ -2291,9 +3554,18 @@ public: */ bool stored_in_db; - Create_field() :after(0), option_list(NULL), option_struct(NULL) - {} - Create_field(Field *field, Field *orig_field); + Create_field() :change(0), after(0), comment(null_lex_str), + def(0), on_update(0), sql_type(MYSQL_TYPE_NULL), + flags(0), pack_length(0), key_length(0), interval(0), + srid(0), geom_type(Field::GEOM_GEOMETRY), + field(0), option_list(NULL), option_struct(NULL), + create_if_not_exists(false), vcol_info(0), + stored_in_db(true) + { + interval_list.empty(); + } + + Create_field(THD *thd, Field *field, Field *orig_field); /* Used to make a clone of this object for ALTER/CREATE TABLE */ Create_field *clone(MEM_ROOT *mem_root) const; void create_length_to_internal_length(void); @@ -2304,23 +3576,32 @@ public: bool maybe_null, bool is_unsigned, uint pack_length = ~0U); - bool init(THD *thd, char *field_name, enum_field_types type, char *length, - char *decimals, uint type_modifier, Item *default_value, - Item *on_update_value, LEX_STRING *comment, char *change, - List<String> *interval_list, CHARSET_INFO *cs, - uint uint_geom_type, Virtual_column_info *vcol_info, - engine_option_value *option_list); + bool check(THD *thd); - bool field_flags_are_binary() + ha_storage_media field_storage_type() const { - return (flags & (BINCMP_FLAG | BINARY_FLAG)) != 0; + return (ha_storage_media) + ((flags >> FIELD_FLAGS_STORAGE_MEDIA) & 3); } + + column_format_type column_format() const + { + return (column_format_type) + ((flags >> FIELD_FLAGS_COLUMN_FORMAT) & 3); + } + uint virtual_col_expr_maxlen() { return 255 - FRM_VCOL_HEADER_SIZE(interval != NULL); } -private: - const String empty_set_string; + + bool has_default_function() const + { + return (unireg_check == Field::TIMESTAMP_DN_FIELD || + unireg_check == Field::TIMESTAMP_DNUN_FIELD || + unireg_check == Field::TIMESTAMP_UN_FIELD || + unireg_check == Field::NEXT_NUMBER); + } }; @@ -2386,11 +3667,12 @@ public: }; -Field *make_field(TABLE_SHARE *share, uchar *ptr, uint32 field_length, +Field *make_field(TABLE_SHARE *share, MEM_ROOT *mem_root, + uchar *ptr, uint32 field_length, uchar *null_pos, uchar null_bit, uint pack_flag, enum_field_types field_type, CHARSET_INFO *cs, - Field::geometry_type geom_type, + Field::geometry_type geom_type, uint srid, Field::utype unireg_check, TYPELIB *interval, const char *field_name); uint pack_length_to_packflag(uint type); @@ -2398,6 +3680,7 @@ enum_field_types get_blob_type_from_length(ulong length); uint32 calc_pack_length(enum_field_types type,uint32 length); int set_field_to_null(Field *field); int set_field_to_null_with_conversions(Field *field, bool no_conversions); +int convert_null_to_field_value_or_error(Field *field); /* The following are for the interface with the .frm file diff --git a/sql/field_conv.cc b/sql/field_conv.cc index 6e48bf00abc..cdef8e8d746 100644 --- a/sql/field_conv.cc +++ b/sql/field_conv.cc @@ -25,6 +25,7 @@ gives much more speed. */ +#include <my_global.h> #include "sql_priv.h" #include "sql_class.h" // THD #include <m_ctype.h> @@ -114,8 +115,25 @@ static void do_outer_field_to_null_str(Copy_field *copy) } -int -set_field_to_null(Field *field) +static int set_bad_null_error(Field *field, int err) +{ + switch (field->table->in_use->count_cuted_fields) { + case CHECK_FIELD_WARN: + field->set_warning(Sql_condition::WARN_LEVEL_WARN, err, 1); + /* fall through */ + case CHECK_FIELD_IGNORE: + return 0; + case CHECK_FIELD_ERROR_FOR_NULL: + if (!field->table->in_use->no_errors) + my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name); + return -1; + } + DBUG_ASSERT(0); // impossible + return -1; +} + + +int set_field_to_null(Field *field) { if (field->table->null_catch_flags & CHECK_ROW_FOR_NULLS_TO_REJECT) { @@ -129,21 +147,39 @@ set_field_to_null(Field *field) return 0; } field->reset(); - switch (field->table->in_use->count_cuted_fields) { - case CHECK_FIELD_WARN: - field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); - /* fall through */ - case CHECK_FIELD_IGNORE: + return set_bad_null_error(field, WARN_DATA_TRUNCATED); +} + + +/** + Set TIMESTAMP to NOW(), AUTO_INCREMENT to the next number, or report an error + + @param field Field to update + + @retval + 0 Field could take 0 or an automatic conversion was used + @retval + -1 Field could not take NULL and no conversion was used. + If no_conversion was not set, an error message is printed +*/ + +int convert_null_to_field_value_or_error(Field *field) +{ + if (field->type() == MYSQL_TYPE_TIMESTAMP) + { + ((Field_timestamp*) field)->set_time(); return 0; - case CHECK_FIELD_ERROR_FOR_NULL: - if (!field->table->in_use->no_errors) - my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name); - return -1; } - DBUG_ASSERT(0); // impossible - return -1; -} + field->reset(); // Note: we ignore any potential failure of reset() here. + + if (field == field->table->next_number_field) + { + field->table->auto_increment_field_not_null= FALSE; + return 0; // field is set in fill_record() + } + return set_bad_null_error(field, ER_BAD_NULL_ERROR); +} /** Set field to NULL or TIMESTAMP or to next auto_increment number. @@ -179,41 +215,17 @@ set_field_to_null_with_conversions(Field *field, bool no_conversions) if (no_conversions) return -1; - /* - Check if this is a special type, which will get a special walue - when set to NULL (TIMESTAMP fields which allow setting to NULL - are handled by first check). - */ - if (field->type() == MYSQL_TYPE_TIMESTAMP) - { - ((Field_timestamp*) field)->set_time(); - return 0; // Ok to set time to NULL - } - - // Note: we ignore any potential failure of reset() here. - field->reset(); - - if (field == field->table->next_number_field) - { - field->table->auto_increment_field_not_null= FALSE; - return 0; // field is set in fill_record() - } - switch (field->table->in_use->count_cuted_fields) { - case CHECK_FIELD_WARN: - field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_BAD_NULL_ERROR, 1); - /* fall through */ - case CHECK_FIELD_IGNORE: - return 0; - case CHECK_FIELD_ERROR_FOR_NULL: - if (!field->table->in_use->no_errors) - my_error(ER_BAD_NULL_ERROR, MYF(0), field->field_name); - return -1; - } - DBUG_ASSERT(0); // impossible - return -1; + return convert_null_to_field_value_or_error(field); } +static int copy_timestamp_fields(Field *from, Field *to) +{ + DBUG_ASSERT(from->type() == MYSQL_TYPE_TIMESTAMP); + DBUG_ASSERT(to->type() == MYSQL_TYPE_TIMESTAMP); + return ((Field_timestamp*)to)->store_timestamp((Field_timestamp*)from); +} + static void do_skip(Copy_field *copy __attribute__((unused))) { } @@ -270,7 +282,7 @@ static void do_copy_nullable_row_to_notnull(Copy_field *copy) if (*copy->null_row || (copy->from_null_ptr && (*copy->from_null_ptr & copy->from_bit))) { - copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); copy->to_field->reset(); } @@ -286,7 +298,7 @@ static void do_copy_not_null(Copy_field *copy) { if (*copy->from_null_ptr & copy->from_bit) { - copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); copy->to_field->reset(); } @@ -395,7 +407,7 @@ static void do_field_int(Copy_field *copy) { longlong value= copy->from_field->val_int(); copy->to_field->store(value, - test(copy->from_field->flags & UNSIGNED_FLAG)); + MY_TEST(copy->from_field->flags & UNSIGNED_FLAG)); } static void do_field_real(Copy_field *copy) @@ -412,6 +424,12 @@ static void do_field_decimal(Copy_field *copy) } +static void do_field_timestamp(Copy_field *copy) +{ + copy_timestamp_fields(copy->from_field, copy->to_field); +} + + static void do_field_temporal(Copy_field *copy) { MYSQL_TIME ltime; @@ -436,7 +454,7 @@ static void do_cut_string(Copy_field *copy) (char*) copy->from_ptr + copy->from_length, MY_SEQ_SPACES) < copy->from_length - copy->to_length) { - copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); } } @@ -467,7 +485,7 @@ static void do_cut_string_complex(Copy_field *copy) (char*) from_end, MY_SEQ_SPACES) < (copy->from_length - copy_length)) { - copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); } @@ -506,7 +524,7 @@ static void do_varstring1(Copy_field *copy) length=copy->to_length - 1; if (copy->from_field->table->in_use->count_cuted_fields && copy->to_field) - copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); } *(uchar*) copy->to_ptr= (uchar) length; @@ -527,7 +545,7 @@ static void do_varstring1_mb(Copy_field *copy) if (length < from_length) { if (current_thd->count_cuted_fields) - copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); } *copy->to_ptr= (uchar) length; @@ -543,7 +561,7 @@ static void do_varstring2(Copy_field *copy) length=copy->to_length-HA_KEY_BLOB_LENGTH; if (copy->from_field->table->in_use->count_cuted_fields && copy->to_field) - copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); } int2store(copy->to_ptr,length); @@ -565,7 +583,7 @@ static void do_varstring2_mb(Copy_field *copy) if (length < from_length) { if (current_thd->count_cuted_fields) - copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + copy->to_field->set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1); } int2store(copy->to_ptr, length); @@ -624,9 +642,6 @@ void Copy_field::set(uchar *to,Field *from) Field_blob::store. Is this in order to trigger the call to well_formed_copy_nchars, by changing the pointer copy->tmp.ptr()? That call will take place anyway in all known cases. - - - The above causes a truncation to MAX_FIELD_WIDTH. Is this the intended - effect? Truncation is handled by well_formed_copy_nchars anyway. */ void Copy_field::set(Field *to,Field *from,bool save) { @@ -722,7 +737,9 @@ Copy_field::get_copy_func(Field *to,Field *from) ((to->table->in_use->variables.sql_mode & (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE)) && mysql_type_to_time_type(to->type()) != MYSQL_TIMESTAMP_TIME)) - return do_field_temporal; + return (from->type() == MYSQL_TYPE_TIMESTAMP && + to->type() == MYSQL_TYPE_TIMESTAMP) + ? do_field_timestamp : do_field_temporal; /* Do binary copy */ } // Check if identical fields @@ -782,6 +799,10 @@ Copy_field::get_copy_func(Field *to,Field *from) else if (to->real_type() != from->real_type() || to_length != from_length) { + if ((to->real_type() == MYSQL_TYPE_ENUM || + to->real_type() == MYSQL_TYPE_SET) && + from->real_type() == MYSQL_TYPE_NEWDECIMAL) + return do_field_decimal; if (to->real_type() == MYSQL_TYPE_DECIMAL || to->result_type() == STRING_RESULT) return do_field_string; @@ -814,43 +835,81 @@ Copy_field::get_copy_func(Field *to,Field *from) return do_field_eq; } +/** + Check if it is possible just copy value of the fields + + @param to The field to copy to + @param from The field to copy from + + @retval TRUE - it is possible to just copy value of 'from' to 'to'. + @retval FALSE - conversion is needed +*/ + +bool memcpy_field_possible(Field *to,Field *from) +{ + const enum_field_types to_real_type= to->real_type(); + const enum_field_types from_real_type= from->real_type(); + /* + Warning: Calling from->type() may be unsafe in some (unclear) circumstances + related to SPs. See MDEV-6799. + */ + return (to_real_type == from_real_type && + !(to->flags & BLOB_FLAG && to->table->copy_blobs) && + to->pack_length() == from->pack_length() && + !(to->flags & UNSIGNED_FLAG && !(from->flags & UNSIGNED_FLAG)) && + to->decimals() == from->decimals() && + to_real_type != MYSQL_TYPE_ENUM && + to_real_type != MYSQL_TYPE_SET && + to_real_type != MYSQL_TYPE_BIT && + (to_real_type != MYSQL_TYPE_NEWDECIMAL || + to->field_length == from->field_length) && + from->charset() == to->charset() && + (!sql_mode_for_dates(to->table->in_use) || + (from->type()!= MYSQL_TYPE_DATE && + from->type()!= MYSQL_TYPE_DATETIME && + from->type()!= MYSQL_TYPE_TIMESTAMP)) && + (from_real_type != MYSQL_TYPE_VARCHAR || + ((Field_varstring*)from)->length_bytes == + ((Field_varstring*)to)->length_bytes)); +} + /** Simple quick field convert that is called on insert. */ int field_conv(Field *to,Field *from) { - bool blob_type_dest= to->flags & BLOB_FLAG; - if (to->real_type() == from->real_type() && - !(blob_type_dest && to->table->copy_blobs)) - { - if (to->pack_length() == from->pack_length() && - !(to->flags & UNSIGNED_FLAG && !(from->flags & UNSIGNED_FLAG)) && - to->decimals() == from->decimals() && - to->real_type() != MYSQL_TYPE_ENUM && - to->real_type() != MYSQL_TYPE_SET && - to->real_type() != MYSQL_TYPE_BIT && - (to->real_type() != MYSQL_TYPE_NEWDECIMAL || - to->field_length == from->field_length) && - from->charset() == to->charset() && - (!(to->table->in_use->variables.sql_mode & - (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_INVALID_DATES)) || - (to->type() != MYSQL_TYPE_DATE && - to->type() != MYSQL_TYPE_DATETIME)) && - (from->real_type() != MYSQL_TYPE_VARCHAR || - ((Field_varstring*)from)->length_bytes == - ((Field_varstring*)to)->length_bytes)) - { // Identical fields - /* - This may happen if one does 'UPDATE ... SET x=x' - The test is here mostly for valgrind, but can also be relevant - if memcpy() is implemented with prefetch-write - */ - if (to->ptr != from->ptr) - memcpy(to->ptr,from->ptr,to->pack_length()); - return 0; - } + if (memcpy_field_possible(to, from)) + { // Identical fields + /* + This may happen if one does 'UPDATE ... SET x=x' + The test is here mostly for valgrind, but can also be relevant + if memcpy() is implemented with prefetch-write + */ + if (to->ptr != from->ptr) + memcpy(to->ptr, from->ptr, to->pack_length()); + return 0; } - if (blob_type_dest) + return field_conv_incompatible(to, from); +} + + +/** + Copy value of the field with conversion. + + @note Impossibility of simple copy should be checked before this call. + + @param to The field to copy to + @param from The field to copy from + + @retval TRUE ERROR + @retval FALSE OK +*/ + +int field_conv_incompatible(Field *to, Field *from) +{ + const enum_field_types to_real_type= to->real_type(); + const enum_field_types from_real_type= from->real_type(); + if (to->flags & BLOB_FLAG) { // Be sure the value is stored Field_blob *blob=(Field_blob*) to; from->val_str(&blob->value); @@ -865,20 +924,25 @@ int field_conv(Field *to,Field *from) return blob->store(blob->value.ptr(),blob->value.length(),from->charset()); } - if (from->real_type() == MYSQL_TYPE_ENUM && - to->real_type() == MYSQL_TYPE_ENUM && + if (from_real_type == MYSQL_TYPE_ENUM && + to_real_type == MYSQL_TYPE_ENUM && from->val_int() == 0) { ((Field_enum *)(to))->store_type(0); return 0; } - if (from->result_type() == REAL_RESULT) + Item_result from_result_type= from->result_type(); + if (from_result_type == REAL_RESULT) return to->store(from->val_real()); - if (from->result_type() == DECIMAL_RESULT) + if (from_result_type == DECIMAL_RESULT) { my_decimal buff; return to->store_decimal(from->val_decimal(&buff)); } + if (from->type() == MYSQL_TYPE_TIMESTAMP && to->type() == MYSQL_TYPE_TIMESTAMP) + { + return copy_timestamp_fields(from, to); + } if (from->cmp_type() == TIME_RESULT) { MYSQL_TIME ltime; @@ -887,10 +951,10 @@ int field_conv(Field *to,Field *from) else return to->store_time_dec(<ime, from->decimals()); } - if ((from->result_type() == STRING_RESULT && + if ((from_result_type == STRING_RESULT && (to->result_type() == STRING_RESULT || - (from->real_type() != MYSQL_TYPE_ENUM && - from->real_type() != MYSQL_TYPE_SET))) || + (from_real_type != MYSQL_TYPE_ENUM && + from_real_type != MYSQL_TYPE_SET))) || to->type() == MYSQL_TYPE_DECIMAL) { char buff[MAX_FIELD_WIDTH]; @@ -904,5 +968,5 @@ int field_conv(Field *to,Field *from) */ return to->store(result.c_ptr_quick(),result.length(),from->charset()); } - return to->store(from->val_int(), test(from->flags & UNSIGNED_FLAG)); + return to->store(from->val_int(), MY_TEST(from->flags & UNSIGNED_FLAG)); } diff --git a/sql/filesort.cc b/sql/filesort.cc index 5674786d8b2..7f7407fc2dc 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -22,9 +22,9 @@ Sorts a database */ +#include <my_global.h> #include "sql_priv.h" #include "filesort.h" -#include "unireg.h" // REQUIRED by other includes #ifdef HAVE_STDDEF_H #include <stddef.h> /* for macro offsetof */ #endif @@ -34,9 +34,11 @@ #include "sql_base.h" // update_virtual_fields #include "sql_test.h" // TEST_filesort #include "opt_range.h" // SQL_SELECT +#include "bounded_queue.h" +#include "filesort_utils.h" +#include "sql_select.h" #include "log_slow.h" #include "debug_sync.h" -#include "sql_base.h" /// How to write record_ref. #define WRITE_REF(file,from) \ @@ -47,22 +49,63 @@ if (my_b_write((file),(uchar*) (from),param->ref_length)) \ static uchar *read_buffpek_from_file(IO_CACHE *buffer_file, uint count, uchar *buf); -static ha_rows find_all_keys(SORTPARAM *param,SQL_SELECT *select, - uchar * *sort_keys, uchar *sort_keys_buf, - IO_CACHE *buffer_file, IO_CACHE *tempfile); -static bool write_keys(SORTPARAM *param,uchar * *sort_keys, - uint count, IO_CACHE *buffer_file, IO_CACHE *tempfile); -static void make_sortkey(SORTPARAM *param,uchar *to, uchar *ref_pos); -static void register_used_fields(SORTPARAM *param); -static bool save_index(SORTPARAM *param,uchar **sort_keys, uint count, - FILESORT_INFO *table_sort); +static ha_rows find_all_keys(THD *thd, Sort_param *param, SQL_SELECT *select, + Filesort_info *fs_info, + IO_CACHE *buffer_file, + IO_CACHE *tempfile, + Bounded_queue<uchar, uchar> *pq, + ha_rows *found_rows); +static bool write_keys(Sort_param *param, Filesort_info *fs_info, + uint count, IO_CACHE *buffer_file, IO_CACHE *tempfile); +static void make_sortkey(Sort_param *param, uchar *to, uchar *ref_pos); +static void register_used_fields(Sort_param *param); +static bool save_index(Sort_param *param, uint count, + Filesort_info *table_sort); static uint suffix_length(ulong string_length); static uint sortlength(THD *thd, SORT_FIELD *sortorder, uint s_length, bool *multi_byte_charset); -static SORT_ADDON_FIELD *get_addon_fields(THD *thd, Field **ptabfield, +static SORT_ADDON_FIELD *get_addon_fields(ulong max_length_for_sort_data, + Field **ptabfield, uint sortlength, uint *plength); static void unpack_addon_fields(struct st_sort_addon_field *addon_field, uchar *buff, uchar *buff_end); +static bool check_if_pq_applicable(Sort_param *param, Filesort_info *info, + TABLE *table, + ha_rows records, ulong memory_available); + + +void Sort_param::init_for_filesort(uint sortlen, TABLE *table, + ulong max_length_for_sort_data, + ha_rows maxrows, bool sort_positions) +{ + sort_length= sortlen; + ref_length= table->file->ref_length; + if (!(table->file->ha_table_flags() & HA_FAST_KEY_READ) && + !table->fulltext_searched && !sort_positions) + { + /* + Get the descriptors of all fields whose values are appended + to sorted fields and get its total length in addon_length. + */ + addon_field= get_addon_fields(max_length_for_sort_data, + table->field, sort_length, &addon_length); + } + if (addon_field) + res_length= addon_length; + else + { + res_length= ref_length; + /* + The reference to the record is considered + as an additional sorted field + */ + sort_length+= ref_length; + } + rec_length= sort_length + addon_length; + max_rows= maxrows; +} + + /** Sort a table. Creates a set of pointers that can be used to read the rows @@ -75,15 +118,17 @@ static void unpack_addon_fields(struct st_sort_addon_field *addon_field, The result set is stored in table->io_cache or table->record_pointers. - @param thd Current thread - @param table Table to sort - @param sortorder How to sort the table - @param s_length Number of elements in sortorder - @param select condition to apply to the rows - @param max_rows Return only this many rows - @param sort_positions Set to 1 if we want to force sorting by position - (Needed by UPDATE/INSERT or ALTER TABLE) - @param examined_rows Store number of examined rows here + @param thd Current thread + @param table Table to sort + @param sortorder How to sort the table + @param s_length Number of elements in sortorder + @param select Condition to apply to the rows + @param max_rows Return only this many rows + @param sort_positions Set to TRUE if we want to force sorting by position + (Needed by UPDATE/INSERT or ALTER TABLE or + when rowids are required by executor) + @param[out] examined_rows Store number of examined rows here + @param[out] found_rows Store the number of found rows here @note If we sort by position (like if sort_positions is 1) filesort() will @@ -93,31 +138,31 @@ static void unpack_addon_fields(struct st_sort_addon_field *addon_field, HA_POS_ERROR Error @retval \# Number of rows - @retval - examined_rows will be set to number of examined rows */ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, SQL_SELECT *select, ha_rows max_rows, - bool sort_positions, ha_rows *examined_rows) + bool sort_positions, + ha_rows *examined_rows, + ha_rows *found_rows, + Filesort_tracker* tracker) { int error; size_t memory_available= thd->variables.sortbuff_size; - size_t min_sort_memory; - size_t sort_buff_sz; uint maxbuffer; BUFFPEK *buffpek; ha_rows num_rows= HA_POS_ERROR; - uchar **sort_keys= 0; IO_CACHE tempfile, buffpek_pointers, *outfile; - SORTPARAM param; + Sort_param param; bool multi_byte_charset; + Bounded_queue<uchar, uchar> pq; + DBUG_ENTER("filesort"); DBUG_EXECUTE("info",TEST_filesort(sortorder,s_length);); #ifdef SKIP_DBUG_IN_FILESORT DBUG_PUSH(""); /* No DBUG here */ #endif - FILESORT_INFO table_sort; + Filesort_info table_sort= table->sort; TABLE_LIST *tab= table->pos_in_table_list; Item_subselect *subselect= tab ? tab->containing_subselect() : 0; @@ -135,7 +180,6 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, QUICK_INDEX_MERGE_SELECT. Work with a copy and put it back at the end when index_merge select has finished with it. */ - memcpy(&table_sort, &table->sort, sizeof(FILESORT_INFO)); table->sort.io_cache= NULL; DBUG_ASSERT(table_sort.record_pointers == NULL); @@ -144,82 +188,90 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, my_b_clear(&buffpek_pointers); buffpek=0; error= 1; - bzero((char*) ¶m,sizeof(param)); - param.sort_length= sortlength(thd, sortorder, s_length, &multi_byte_charset); - param.ref_length= table->file->ref_length; - if (!(table->file->ha_table_flags() & HA_FAST_KEY_READ) && - !table->fulltext_searched && !sort_positions) - { - /* - Get the descriptors of all fields whose values are appended - to sorted fields and get its total length in param.spack_length. - */ - param.addon_field= get_addon_fields(thd, table->field, - param.sort_length, - ¶m.addon_length); - } + *found_rows= HA_POS_ERROR; + + param.init_for_filesort(sortlength(thd, sortorder, s_length, + &multi_byte_charset), + table, + thd->variables.max_length_for_sort_data, + max_rows, sort_positions); table_sort.addon_buf= 0; table_sort.addon_length= param.addon_length; table_sort.addon_field= param.addon_field; table_sort.unpack= unpack_addon_fields; - if (param.addon_field) - { - param.res_length= param.addon_length; - if (!(table_sort.addon_buf= (uchar *) my_malloc(param.addon_length, - MYF(MY_WME)))) - goto err; - } - else - { - param.res_length= param.ref_length; - /* - The reference to the record is considered - as an additional sorted field - */ - param.sort_length+= param.ref_length; - } - param.rec_length= param.sort_length+param.addon_length; - param.max_rows= max_rows; + if (param.addon_field && + !(table_sort.addon_buf= + (uchar *) my_malloc(param.addon_length, MYF(MY_WME | + MY_THREAD_SPECIFIC)))) + goto err; if (select && select->quick) - status_var_increment(thd->status_var.filesort_range_count); + thd->inc_status_sort_range(); else - status_var_increment(thd->status_var.filesort_scan_count); + thd->inc_status_sort_scan(); thd->query_plan_flags|= QPLAN_FILESORT; + tracker->report_use(max_rows); // If number of rows is not known, use as much of sort buffer as possible. num_rows= table->file->estimate_rows_upper_bound(); if (multi_byte_charset && - !(param.tmp_buffer= (char*) my_malloc(param.sort_length,MYF(MY_WME)))) + !(param.tmp_buffer= (char*) my_malloc(param.sort_length, + MYF(MY_WME | MY_THREAD_SPECIFIC)))) goto err; - min_sort_memory= max(MIN_SORT_MEMORY, param.sort_length * MERGEBUFF2); - set_if_bigger(min_sort_memory, sizeof(BUFFPEK*)*MERGEBUFF2); - if (!table_sort.sort_keys) + if (check_if_pq_applicable(¶m, &table_sort, + table, num_rows, memory_available)) { - /* - Cannot depend on num_rows. For external sort, space for upto MERGEBUFF2 - rows is required. - */ - if (num_rows < MERGEBUFF2) - num_rows= MERGEBUFF2; + DBUG_PRINT("info", ("filesort PQ is applicable")); + thd->query_plan_flags|= QPLAN_FILESORT_PRIORITY_QUEUE; + status_var_increment(thd->status_var.filesort_pq_sorts_); + tracker->incr_pq_used(); + const size_t compare_length= param.sort_length; + if (pq.init(param.max_rows, + true, // max_at_top + NULL, // compare_function + compare_length, + &make_sortkey, ¶m, table_sort.get_sort_keys())) + { + /* + If we fail to init pq, we have to give up: + out of memory means my_malloc() will call my_error(). + */ + DBUG_PRINT("info", ("failed to allocate PQ")); + table_sort.free_sort_buffer(); + DBUG_ASSERT(thd->is_error()); + goto err; + } + // For PQ queries (with limit) we initialize all pointers. + table_sort.init_record_pointers(); + } + else + { + DBUG_PRINT("info", ("filesort PQ is not applicable")); + size_t min_sort_memory= MY_MAX(MIN_SORT_MEMORY, param.sort_length*MERGEBUFF2); + set_if_bigger(min_sort_memory, sizeof(BUFFPEK*)*MERGEBUFF2); while (memory_available >= min_sort_memory) { ulonglong keys= memory_available / (param.rec_length + sizeof(char*)); - table_sort.keys= (uint) min(num_rows, keys); - /* Cast to size_t to avoid overflow when result is greater than uint. */ - sort_buff_sz= ((size_t)table_sort.keys) * - (param.rec_length + sizeof(char*)); - set_if_bigger(sort_buff_sz, param.rec_length * MERGEBUFF2); - - DBUG_EXECUTE_IF("make_sort_keys_alloc_fail", - DBUG_SET("+d,simulate_out_of_memory");); - - if ((table_sort.sort_keys= - (uchar**) my_malloc(sort_buff_sz, MYF(0)))) + param.max_keys_per_buffer= (uint) MY_MIN(num_rows, keys); + if (table_sort.get_sort_keys()) + { + // If we have already allocated a buffer, it better have same size! + if (!table_sort.check_sort_buffer_properties(param.max_keys_per_buffer, + param.rec_length)) + { + /* + table->sort will still have a pointer to the same buffer, + but that will be overwritten by the assignment below. + */ + table_sort.free_sort_buffer(); + } + } + table_sort.alloc_sort_buffer(param.max_keys_per_buffer, param.rec_length); + if (table_sort.get_sort_keys()) break; size_t old_memory_available= memory_available; memory_available= memory_available/4*3; @@ -227,40 +279,40 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, old_memory_available > min_sort_memory) memory_available= min_sort_memory; } + if (memory_available < min_sort_memory) + { + my_error(ER_OUT_OF_SORTMEMORY,MYF(ME_ERROR + ME_FATALERROR)); + goto err; + } + tracker->report_sort_buffer_size(table_sort.sort_buffer_size()); } - sort_keys= table_sort.sort_keys; - param.keys= table_sort.keys; - if (memory_available < min_sort_memory) - { - my_error(ER_OUT_OF_SORTMEMORY,MYF(ME_ERROR + ME_FATALERROR)); - goto err; - } if (open_cached_file(&buffpek_pointers,mysql_tmpdir,TEMP_PREFIX, - DISK_BUFFER_SIZE, MYF(ME_ERROR | MY_WME))) + DISK_BUFFER_SIZE, MYF(MY_WME))) goto err; param.sort_form= table; param.end=(param.local_sortorder=sortorder)+s_length; - num_rows= find_all_keys(¶m, - select, - sort_keys, - (uchar *)(sort_keys+param.keys), + num_rows= find_all_keys(thd, ¶m, select, + &table_sort, &buffpek_pointers, - &tempfile); + &tempfile, + pq.is_initialized() ? &pq : NULL, + found_rows); if (num_rows == HA_POS_ERROR) goto err; + maxbuffer= (uint) (my_b_tell(&buffpek_pointers)/sizeof(*buffpek)); + tracker->report_merge_passes_at_start(thd->query_plan_fsort_passes); + tracker->report_row_numbers(param.examined_rows, *found_rows, num_rows); if (maxbuffer == 0) // The whole set is in memory { - if (save_index(¶m,sort_keys,(uint) num_rows, &table_sort)) + if (save_index(¶m, (uint) num_rows, &table_sort)) goto err; } else { - thd->query_plan_flags|= QPLAN_FILESORT_DISK; - /* filesort cannot handle zero-length records during merge. */ DBUG_ASSERT(param.sort_length != 0); @@ -279,7 +331,7 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, /* Open cached file if it isn't open */ if (! my_b_inited(outfile) && open_cached_file(outfile,mysql_tmpdir,TEMP_PREFIX,READ_RECORD_BUFFER, - MYF(ME_ERROR | MY_WME))) + MYF(MY_WME))) goto err; if (reinit_io_cache(outfile,WRITE_CACHE,0L,0,0)) goto err; @@ -288,18 +340,20 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, Use also the space previously used by string pointers in sort_buffer for temporary key storage. */ - param.keys=((param.keys * - (param.rec_length+sizeof(char*))) / - param.rec_length - 1); + param.max_keys_per_buffer=((param.max_keys_per_buffer * + (param.rec_length + sizeof(char*))) / + param.rec_length - 1); maxbuffer--; // Offset from 0 - if (merge_many_buff(¶m,(uchar*) sort_keys,buffpek,&maxbuffer, + if (merge_many_buff(¶m, + (uchar*) table_sort.get_sort_keys(), + buffpek,&maxbuffer, &tempfile)) goto err; if (flush_io_cache(&tempfile) || reinit_io_cache(&tempfile,READ_CACHE,0L,0,0)) goto err; if (merge_index(¶m, - (uchar*) sort_keys, + (uchar*) table_sort.get_sort_keys(), buffpek, maxbuffer, &tempfile, @@ -314,12 +368,11 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, } error= 0; - err: + err: my_free(param.tmp_buffer); if (!subselect || !subselect->is_uncacheable()) { - my_free(sort_keys); - table_sort.sort_keys= 0; + table_sort.free_sort_buffer(); my_free(buffpek); table_sort.buffpek= 0; table_sort.buffpek_len= 0; @@ -338,6 +391,7 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, outfile->end_of_file=save_pos; } } + tracker->report_merge_passes_at_end(thd->query_plan_fsort_passes); if (error) { int kill_errno= thd->killed_errno(); @@ -354,11 +408,12 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, "%s: %s", MYF(0), ER_THD(thd, ER_FILSORT_ABORT), - kill_errno ? ER(kill_errno) : - thd->killed == ABORT_QUERY ? "" : thd->stmt_da->message()); + kill_errno ? ER_THD(thd, kill_errno) : + thd->killed == ABORT_QUERY ? "" : + thd->get_stmt_da()->message()); if (global_system_variables.log_warnings > 1) - { + { sql_print_warning("%s, host: %s, user: %s, thread: %lu, query: %-.4096s", ER_THD(thd, ER_FILSORT_ABORT), thd->security_ctx->host_or_ip, @@ -368,8 +423,7 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, } } else - statistic_add(thd->status_var.filesort_rows, - (ulong) num_rows, &LOCK_status); + thd->inc_status_sort_rows(num_rows); *examined_rows= param.examined_rows; #ifdef SKIP_DBUG_IN_FILESORT DBUG_POP(); /* Ok to DBUG */ @@ -378,8 +432,12 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, /* table->sort.io_cache should be free by this time */ DBUG_ASSERT(NULL == table->sort.io_cache); - memcpy(&table->sort, &table_sort, sizeof(FILESORT_INFO)); - DBUG_PRINT("exit",("num_rows: %ld", (long) num_rows)); + // Assign the copy back! + table->sort= table_sort; + + DBUG_PRINT("exit", + ("num_rows: %ld examined_rows: %ld found_rows: %ld", + (long) num_rows, (long) *examined_rows, (long) *found_rows)); MYSQL_FILESORT_DONE(error, num_rows); DBUG_RETURN(error ? HA_POS_ERROR : num_rows); } /* filesort */ @@ -388,22 +446,26 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, void filesort_free_buffers(TABLE *table, bool full) { DBUG_ENTER("filesort_free_buffers"); + my_free(table->sort.record_pointers); table->sort.record_pointers= NULL; - if (full) + if (unlikely(full)) { - my_free(table->sort.sort_keys); - table->sort.sort_keys= NULL; + table->sort.free_sort_buffer(); my_free(table->sort.buffpek); table->sort.buffpek= NULL; table->sort.buffpek_len= 0; } - my_free(table->sort.addon_buf); - my_free(table->sort.addon_field); - table->sort.addon_buf= NULL; - table->sort.addon_field= NULL; + /* addon_buf is only allocated if addon_field is set */ + if (unlikely(table->sort.addon_field)) + { + my_free(table->sort.addon_field); + my_free(table->sort.addon_buf); + table->sort.addon_buf= NULL; + table->sort.addon_field= NULL; + } DBUG_VOID_RETURN; } @@ -419,7 +481,7 @@ static uchar *read_buffpek_from_file(IO_CACHE *buffpek_pointers, uint count, if (count > UINT_MAX/sizeof(BUFFPEK)) return 0; /* sizeof(BUFFPEK)*count will overflow */ if (!tmp) - tmp= (uchar *)my_malloc(length, MYF(MY_WME)); + tmp= (uchar *)my_malloc(length, MYF(MY_WME | MY_THREAD_SPECIFIC)); if (tmp) { if (reinit_io_cache(buffpek_pointers,READ_CACHE,0L,0,0) || @@ -562,8 +624,10 @@ static void dbug_print_record(TABLE *table, bool print_rowid) #endif + /** - Search after sort_keys and write them into tempfile. + Search after sort_keys, and write them into tempfile + (if we run out of space in the sort_keys buffer). All produced sequences are guaranteed to be non-empty. @param param Sorting parameter @@ -572,19 +636,28 @@ static void dbug_print_record(TABLE *table, bool print_rowid) @param buffpek_pointers File to write BUFFPEKs describing sorted segments in tempfile. @param tempfile File to write sorted sequences of sortkeys to. + @param pq If !NULL, use it for keeping top N elements + @param [out] found_rows The number of FOUND_ROWS(). + For a query with LIMIT, this value will typically + be larger than the function return value. @note Basic idea: @verbatim while (get_next_sortkey()) { - if (no free space in sort_keys buffers) + if (using priority queue) + push sort key into queue + else { - sort sort_keys buffer; - dump sorted sequence to 'tempfile'; - dump BUFFPEK describing sequence location into 'buffpek_pointers'; + if (no free space in sort_keys buffers) + { + sort sort_keys buffer; + dump sorted sequence to 'tempfile'; + dump BUFFPEK describing sequence location into 'buffpek_pointers'; + } + put sort key into 'sort_keys'; } - put sort key into 'sort_keys'; } if (sort_keys has some elements && dumped at least once) sort-dump-dump as above; @@ -598,21 +671,21 @@ static void dbug_print_record(TABLE *table, bool print_rowid) HA_POS_ERROR on error. */ -static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, - uchar **sort_keys, uchar *sort_keys_buf, +static ha_rows find_all_keys(THD *thd, Sort_param *param, SQL_SELECT *select, + Filesort_info *fs_info, IO_CACHE *buffpek_pointers, - IO_CACHE *tempfile) + IO_CACHE *tempfile, + Bounded_queue<uchar, uchar> *pq, + ha_rows *found_rows) { int error,flag,quick_select; uint idx,indexpos,ref_length; uchar *ref_pos,*next_pos,ref_buff[MAX_REFLENGTH]; my_off_t record; TABLE *sort_form; - THD *thd= current_thd; - volatile killed_state *killed= &thd->killed; handler *file; MY_BITMAP *save_read_set, *save_write_set, *save_vcol_set; - uchar *next_sort_key= sort_keys_buf; + DBUG_ENTER("find_all_keys"); DBUG_PRINT("info",("using: %s", (select ? select->quick ? "ranges" : "where": @@ -626,10 +699,16 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, ref_pos= ref_buff; quick_select=select && select->quick; record=0; + *found_rows= 0; flag= ((file->ha_table_flags() & HA_REC_NOT_IN_SEQ) || quick_select); if (flag) ref_pos= &file->ref[0]; next_pos=ref_pos; + + DBUG_EXECUTE_IF("show_explain_in_find_all_keys", + dbug_serve_apcs(thd, 1); + ); + if (!quick_select) { next_pos=(uchar*) 0; /* Find records in sequence */ @@ -641,7 +720,6 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, current_thd->variables.read_buff_size); } - /* Remember original bitmaps */ save_read_set= sort_form->read_set; save_write_set= sort_form->write_set; @@ -699,7 +777,7 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, break; } - if (*killed) + if (thd->check_killed()) { DBUG_PRINT("info",("Sort killed by user")); if (!quick_select) @@ -744,18 +822,23 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, if (write_record) { - if (idx == param->keys) + ++(*found_rows); + if (pq) + { + pq->push(ref_pos); + idx= pq->num_elements(); + } + else { - if (write_keys(param, sort_keys, - idx, buffpek_pointers, tempfile)) - DBUG_RETURN(HA_POS_ERROR); - idx= 0; - next_sort_key= sort_keys_buf; - indexpos++; + if (idx == param->max_keys_per_buffer) + { + if (write_keys(param, fs_info, idx, buffpek_pointers, tempfile)) + DBUG_RETURN(HA_POS_ERROR); + idx= 0; + indexpos++; + } + make_sortkey(param, fs_info->get_record_buffer(idx++), ref_pos); } - sort_keys[idx++]= next_sort_key; - make_sortkey(param, next_sort_key, ref_pos); - next_sort_key+= param->rec_length; } /* It does not make sense to read more keys in case of a fatal error */ @@ -789,12 +872,12 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, DBUG_RETURN(HA_POS_ERROR); /* purecov: inspected */ } if (indexpos && idx && - write_keys(param, sort_keys, - idx, buffpek_pointers, tempfile)) + write_keys(param, fs_info, idx, buffpek_pointers, tempfile)) DBUG_RETURN(HA_POS_ERROR); /* purecov: inspected */ const ha_rows retval= my_b_inited(tempfile) ? (ha_rows) (my_b_tell(tempfile)/param->rec_length) : idx; + DBUG_PRINT("info", ("find_all_keys return %u", (uint) retval)); DBUG_RETURN(retval); } /* find_all_keys */ @@ -822,21 +905,19 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, */ static bool -write_keys(SORTPARAM *param, register uchar **sort_keys, uint count, +write_keys(Sort_param *param, Filesort_info *fs_info, uint count, IO_CACHE *buffpek_pointers, IO_CACHE *tempfile) { - size_t sort_length, rec_length; + size_t rec_length; uchar **end; BUFFPEK buffpek; DBUG_ENTER("write_keys"); - sort_length= param->sort_length; rec_length= param->rec_length; -#ifdef MC68000 - quicksort(sort_keys,count,sort_length); -#else - my_string_ptr_sort((uchar*) sort_keys, (uint) count, sort_length); -#endif + uchar **sort_keys= fs_info->get_sort_keys(); + + fs_info->sort_buffer(param, count); + if (!my_b_inited(tempfile) && open_cached_file(tempfile, mysql_tmpdir, TEMP_PREFIX, DISK_BUFFER_SIZE, MYF(MY_WME))) @@ -844,6 +925,7 @@ write_keys(SORTPARAM *param, register uchar **sort_keys, uint count, /* check we won't have more buffpeks than we can possibly keep in memory */ if (my_b_tell(buffpek_pointers) + sizeof(BUFFPEK) > (ulonglong)UINT_MAX) goto err; + bzero(&buffpek, sizeof(buffpek)); buffpek.file_pos= my_b_tell(tempfile); if ((ha_rows) count > param->max_rows) count=(uint) param->max_rows; /* purecov: inspected */ @@ -885,8 +967,8 @@ static inline void store_length(uchar *to, uint length, uint pack_length) /** Make a sort-key from record. */ -static void make_sortkey(register SORTPARAM *param, - register uchar *to, uchar *ref_pos) +static void make_sortkey(register Sort_param *param, + register uchar *to, uchar *ref_pos) { reg3 Field *field; reg1 SORT_FIELD *sort_field; @@ -922,7 +1004,7 @@ static void make_sortkey(register SORTPARAM *param, if (!res) { if (maybe_null) - bzero((char*) to-1,sort_field->length+1); + memset(to-1, 0, sort_field->length+1); else { /* purecov: begin deadcode */ @@ -934,7 +1016,7 @@ static void make_sortkey(register SORTPARAM *param, DBUG_ASSERT(0); DBUG_PRINT("warning", ("Got null on something that shouldn't be null")); - bzero((char*) to,sort_field->length); // Avoid crash + memset(to, 0, sort_field->length); // Avoid crash /* purecov: end */ } break; @@ -943,8 +1025,12 @@ static void make_sortkey(register SORTPARAM *param, if (sort_field->need_strxnfrm) { uint tmp_length __attribute__((unused)); - tmp_length= my_strnxfrm(cs, to ,sort_field->length, - (uchar*) res->ptr(), length); + tmp_length= cs->coll->strnxfrm(cs, to, sort_field->length, + item->max_char_length() * + cs->strxfrm_multiply, + (uchar*) res->ptr(), length, + MY_STRXFRM_PAD_WITH_SPACE | + MY_STRXFRM_PAD_TO_MAXLEN); DBUG_ASSERT(tmp_length == sort_field->length); } else @@ -990,12 +1076,19 @@ static void make_sortkey(register SORTPARAM *param, } if (maybe_null) { + *to++=1; /* purecov: inspected */ if (item->null_value) { - bzero((char*) to++, sort_field->length+1); + if (maybe_null) + memset(to-1, 0, sort_field->length+1); + else + { + DBUG_PRINT("warning", + ("Got null on something that shouldn't be null")); + memset(to, 0, sort_field->length); + } break; } - *to++=1; /* purecov: inspected */ } to[7]= (uchar) value; to[6]= (uchar) (value >> 8); @@ -1017,7 +1110,8 @@ static void make_sortkey(register SORTPARAM *param, { if (item->null_value) { - bzero((char*) to++, sort_field->length+1); + memset(to, 0, sort_field->length+1); + to++; break; } *to++=1; @@ -1034,7 +1128,7 @@ static void make_sortkey(register SORTPARAM *param, { if (item->null_value) { - bzero((char*) to,sort_field->length+1); + memset(to, 0, sort_field->length+1); to++; break; } @@ -1079,7 +1173,7 @@ static void make_sortkey(register SORTPARAM *param, SORT_ADDON_FIELD *addonf= param->addon_field; uchar *nulls= to; DBUG_ASSERT(addonf != 0); - bzero((char *) nulls, addonf->offset); + memset(nulls, 0, addonf->offset); to+= addonf->offset; for ( ; (field= addonf->field) ; addonf++) { @@ -1118,7 +1212,7 @@ static void make_sortkey(register SORTPARAM *param, Register fields used by sorting in the sorted table's read set */ -static void register_used_fields(SORTPARAM *param) +static void register_used_fields(Sort_param *param) { reg1 SORT_FIELD *sort_field; TABLE *table=param->sort_form; @@ -1163,21 +1257,20 @@ static void register_used_fields(SORTPARAM *param) } -static bool save_index(SORTPARAM *param, uchar **sort_keys, uint count, - FILESORT_INFO *table_sort) +static bool save_index(Sort_param *param, uint count, Filesort_info *table_sort) { uint offset,res_length; uchar *to; DBUG_ENTER("save_index"); - my_string_ptr_sort((uchar*) sort_keys, (uint) count, param->sort_length); + table_sort->sort_buffer(param, count); res_length= param->res_length; offset= param->rec_length-res_length; - if ((ha_rows) count > param->max_rows) - count=(uint) param->max_rows; if (!(to= table_sort->record_pointers= - (uchar*) my_malloc(res_length*count, MYF(MY_WME)))) + (uchar*) my_malloc(res_length*count, + MYF(MY_WME | MY_THREAD_SPECIFIC)))) DBUG_RETURN(1); /* purecov: inspected */ + uchar **sort_keys= table_sort->get_sort_keys(); for (uchar **end= sort_keys+count ; sort_keys != end ; sort_keys++) { memcpy(to, *sort_keys+offset, res_length); @@ -1187,10 +1280,150 @@ static bool save_index(SORTPARAM *param, uchar **sort_keys, uint count, } +/** + Test whether priority queue is worth using to get top elements of an + ordered result set. If it is, then allocates buffer for required amount of + records + + @param param Sort parameters. + @param filesort_info Filesort information. + @param table Table to sort. + @param num_rows Estimate of number of rows in source record set. + @param memory_available Memory available for sorting. + + DESCRIPTION + Given a query like this: + SELECT ... FROM t ORDER BY a1,...,an LIMIT max_rows; + This function tests whether a priority queue should be used to keep + the result. Necessary conditions are: + - estimate that it is actually cheaper than merge-sort + - enough memory to store the <max_rows> records. + + If we don't have space for <max_rows> records, but we *do* have + space for <max_rows> keys, we may rewrite 'table' to sort with + references to records instead of additional data. + (again, based on estimates that it will actually be cheaper). + + @retval + true - if it's ok to use PQ + false - PQ will be slower than merge-sort, or there is not enough memory. +*/ + +bool check_if_pq_applicable(Sort_param *param, + Filesort_info *filesort_info, + TABLE *table, ha_rows num_rows, + ulong memory_available) +{ + DBUG_ENTER("check_if_pq_applicable"); + + /* + How much Priority Queue sort is slower than qsort. + Measurements (see unit test) indicate that PQ is roughly 3 times slower. + */ + const double PQ_slowness= 3.0; + + if (param->max_rows == HA_POS_ERROR) + { + DBUG_PRINT("info", ("No LIMIT")); + DBUG_RETURN(false); + } + + if (param->max_rows + 2 >= UINT_MAX) + { + DBUG_PRINT("info", ("Too large LIMIT")); + DBUG_RETURN(false); + } + + ulong num_available_keys= + memory_available / (param->rec_length + sizeof(char*)); + // We need 1 extra record in the buffer, when using PQ. + param->max_keys_per_buffer= (uint) param->max_rows + 1; + + if (num_rows < num_available_keys) + { + // The whole source set fits into memory. + if (param->max_rows < num_rows/PQ_slowness ) + { + filesort_info->alloc_sort_buffer(param->max_keys_per_buffer, + param->rec_length); + DBUG_RETURN(filesort_info->get_sort_keys() != NULL); + } + else + { + // PQ will be slower. + DBUG_RETURN(false); + } + } + + // Do we have space for LIMIT rows in memory? + if (param->max_keys_per_buffer < num_available_keys) + { + filesort_info->alloc_sort_buffer(param->max_keys_per_buffer, + param->rec_length); + DBUG_RETURN(filesort_info->get_sort_keys() != NULL); + } + + // Try to strip off addon fields. + if (param->addon_field) + { + const ulong row_length= + param->sort_length + param->ref_length + sizeof(char*); + num_available_keys= memory_available / row_length; + + // Can we fit all the keys in memory? + if (param->max_keys_per_buffer < num_available_keys) + { + const double sort_merge_cost= + get_merge_many_buffs_cost_fast(num_rows, + num_available_keys, + row_length); + /* + PQ has cost: + (insert + qsort) * log(queue size) / TIME_FOR_COMPARE_ROWID + + cost of file lookup afterwards. + The lookup cost is a bit pessimistic: we take scan_time and assume + that on average we find the row after scanning half of the file. + A better estimate would be lookup cost, but note that we are doing + random lookups here, rather than sequential scan. + */ + const double pq_cpu_cost= + (PQ_slowness * num_rows + param->max_keys_per_buffer) * + log((double) param->max_keys_per_buffer) / TIME_FOR_COMPARE_ROWID; + const double pq_io_cost= + param->max_rows * table->file->scan_time() / 2.0; + const double pq_cost= pq_cpu_cost + pq_io_cost; + + if (sort_merge_cost < pq_cost) + DBUG_RETURN(false); + + filesort_info->alloc_sort_buffer(param->max_keys_per_buffer, + param->sort_length + param->ref_length); + if (filesort_info->get_sort_keys()) + { + // Make attached data to be references instead of fields. + my_free(filesort_info->addon_buf); + my_free(filesort_info->addon_field); + filesort_info->addon_buf= NULL; + filesort_info->addon_field= NULL; + param->addon_field= NULL; + param->addon_length= 0; + + param->res_length= param->ref_length; + param->sort_length+= param->ref_length; + param->rec_length= param->sort_length; + + DBUG_RETURN(true); + } + } + } + DBUG_RETURN(false); +} + + /** Merge buffers to make < MERGEBUFF2 buffers. */ -int merge_many_buff(SORTPARAM *param, uchar *sort_buffer, - BUFFPEK *buffpek, uint *maxbuffer, IO_CACHE *t_file) +int merge_many_buff(Sort_param *param, uchar *sort_buffer, + BUFFPEK *buffpek, uint *maxbuffer, IO_CACHE *t_file) { register uint i; IO_CACHE t_file2,*from_file,*to_file,*temp; @@ -1253,12 +1486,11 @@ uint read_to_buffer(IO_CACHE *fromfile, BUFFPEK *buffpek, register uint count; uint length; - if ((count=(uint) min((ha_rows) buffpek->max_keys,buffpek->count))) + if ((count=(uint) MY_MIN((ha_rows) buffpek->max_keys,buffpek->count))) { - if (mysql_file_pread(fromfile->file, (uchar*) buffpek->base, - (length= rec_length*count), - buffpek->file_pos, MYF_RW)) - return((uint) -1); /* purecov: inspected */ + if (my_b_pread(fromfile, (uchar*) buffpek->base, + (length= rec_length*count), buffpek->file_pos)) + return ((uint) -1); buffpek->key=buffpek->base; buffpek->file_pos+= length; /* New filepos */ buffpek->count-= count; @@ -1321,7 +1553,7 @@ void reuse_freed_buff(QUEUE *queue, BUFFPEK *reuse, uint key_length) other error */ -int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, +int merge_buffers(Sort_param *param, IO_CACHE *from_file, IO_CACHE *to_file, uchar *sort_buffer, BUFFPEK *lastbuff, BUFFPEK *Fb, BUFFPEK *Tb, int flag) @@ -1339,18 +1571,13 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, void *first_cmp_arg; element_count dupl_count= 0; uchar *src; - killed_state not_killable; uchar *unique_buff= param->unique_buff; - volatile killed_state *killed= ¤t_thd->killed; + const bool killable= !param->not_killable; + THD* const thd=current_thd; DBUG_ENTER("merge_buffers"); - status_var_increment(current_thd->status_var.filesort_merge_passes); - current_thd->query_plan_fsort_passes++; - if (param->not_killable) - { - killed= ¬_killable; - not_killable= NOT_KILLED; - } + thd->inc_status_sort_merge_passes(); + thd->query_plan_fsort_passes++; error=0; rec_length= param->rec_length; @@ -1363,7 +1590,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, (flag && min_dupl_count ? sizeof(dupl_count) : 0)-res_length); uint wr_len= flag ? res_length : rec_length; uint wr_offset= flag ? offset : 0; - maxcount= (ulong) (param->keys/((uint) (Tb-Fb) +1)); + maxcount= (ulong) (param->max_keys_per_buffer/((uint) (Tb-Fb) +1)); to_start_filepos= my_b_tell(to_file); strpos= sort_buffer; org_max_rows=max_rows= param->max_rows; @@ -1427,7 +1654,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, while (queue.elements > 1) { - if (*killed) + if (killable && thd->check_killed()) { error= 1; goto err; /* purecov: inspected */ } @@ -1503,7 +1730,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, } buffpek= (BUFFPEK*) queue_top(&queue); buffpek->base= (uchar*) sort_buffer; - buffpek->max_keys= param->keys; + buffpek->max_keys= param->max_keys_per_buffer; /* As we know all entries in the buffer are unique, we only have to @@ -1583,7 +1810,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, != -1 && error != 0); end: - lastbuff->count= min(org_max_rows-max_rows, param->max_rows); + lastbuff->count= MY_MIN(org_max_rows-max_rows, param->max_rows); lastbuff->file_pos= to_start_filepos; err: delete_queue(&queue); @@ -1593,9 +1820,9 @@ err: /* Do a merge to output-file (save only positions) */ -int merge_index(SORTPARAM *param, uchar *sort_buffer, - BUFFPEK *buffpek, uint maxbuffer, - IO_CACHE *tempfile, IO_CACHE *outfile) +int merge_index(Sort_param *param, uchar *sort_buffer, + BUFFPEK *buffpek, uint maxbuffer, + IO_CACHE *tempfile, IO_CACHE *outfile) { DBUG_ENTER("merge_index"); if (merge_buffers(param,tempfile,outfile,sort_buffer,buffpek,buffpek, @@ -1640,7 +1867,7 @@ static uint sortlength(THD *thd, SORT_FIELD *sortorder, uint s_length, bool *multi_byte_charset) { - reg2 uint length; + uint length; CHARSET_INFO *cs; *multi_byte_charset= 0; @@ -1742,7 +1969,8 @@ sortlength(THD *thd, SORT_FIELD *sortorder, uint s_length, */ static SORT_ADDON_FIELD * -get_addon_fields(THD *thd, Field **ptabfield, uint sortlength, uint *plength) +get_addon_fields(ulong max_length_for_sort_data, + Field **ptabfield, uint sortlength, uint *plength) { Field **pfield; Field *field; @@ -1779,9 +2007,11 @@ get_addon_fields(THD *thd, Field **ptabfield, uint sortlength, uint *plength) return 0; length+= (null_fields+7)/8; - if (length+sortlength > thd->variables.max_length_for_sort_data || + if (length+sortlength > max_length_for_sort_data || !(addonf= (SORT_ADDON_FIELD *) my_malloc(sizeof(SORT_ADDON_FIELD)* - (fields+1), MYF(MY_WME)))) + (fields+1), + MYF(MY_WME | + MY_THREAD_SPECIFIC)))) return 0; *plength= length; @@ -1862,7 +2092,7 @@ void change_double_for_sort(double nr,uchar *to) if (nr == 0.0) { /* Change to zero string */ tmp[0]=(uchar) 128; - bzero((char*) tmp+1,sizeof(nr)-1); + memset(tmp+1, 0, sizeof(nr)-1); } else { diff --git a/sql/filesort.h b/sql/filesort.h index 8ee8999d055..4c95f1202b2 100644 --- a/sql/filesort.h +++ b/sql/filesort.h @@ -25,14 +25,14 @@ class SQL_SELECT; class THD; struct TABLE; typedef struct st_sort_field SORT_FIELD; +class Filesort_tracker; ha_rows filesort(THD *thd, TABLE *table, st_sort_field *sortorder, uint s_length, SQL_SELECT *select, ha_rows max_rows, bool sort_positions, - ha_rows *examined_rows); + ha_rows *examined_rows, ha_rows *found_rows, + Filesort_tracker* tracker); void filesort_free_buffers(TABLE *table, bool full); -double get_merge_many_buffs_cost(uint *buffer, uint last_n_elems, - int elem_size); void change_double_for_sort(double nr,uchar *to); #endif /* FILESORT_INCLUDED */ diff --git a/sql/filesort_utils.cc b/sql/filesort_utils.cc new file mode 100644 index 00000000000..1e0cf096145 --- /dev/null +++ b/sql/filesort_utils.cc @@ -0,0 +1,143 @@ +/* Copyright (c) 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 Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include "filesort_utils.h" +#include "sql_const.h" +#include "sql_sort.h" +#include "table.h" +#include "my_sys.h" + + +namespace { +/** + A local helper function. See comments for get_merge_buffers_cost(). + */ +double get_merge_cost(ha_rows num_elements, ha_rows num_buffers, uint elem_size) +{ + return + 2.0 * ((double) num_elements * elem_size) / IO_SIZE + + (double) num_elements * log((double) num_buffers) / + (TIME_FOR_COMPARE_ROWID * M_LN2); +} +} + +/** + This is a simplified, and faster version of @see get_merge_many_buffs_cost(). + We calculate the cost of merging buffers, by simulating the actions + of @see merge_many_buff. For explanations of formulas below, + see comments for get_merge_buffers_cost(). + TODO: Use this function for Unique::get_use_cost(). +*/ +double get_merge_many_buffs_cost_fast(ha_rows num_rows, + ha_rows num_keys_per_buffer, + uint elem_size) +{ + ha_rows num_buffers= num_rows / num_keys_per_buffer; + ha_rows last_n_elems= num_rows % num_keys_per_buffer; + double total_cost; + + // Calculate CPU cost of sorting buffers. + total_cost= + ( num_buffers * num_keys_per_buffer * log(1.0 + num_keys_per_buffer) + + last_n_elems * log(1.0 + last_n_elems) ) + / TIME_FOR_COMPARE_ROWID; + + // Simulate behavior of merge_many_buff(). + while (num_buffers >= MERGEBUFF2) + { + // Calculate # of calls to merge_buffers(). + const ha_rows loop_limit= num_buffers - MERGEBUFF*3/2; + const ha_rows num_merge_calls= 1 + loop_limit/MERGEBUFF; + const ha_rows num_remaining_buffs= + num_buffers - num_merge_calls * MERGEBUFF; + + // Cost of merge sort 'num_merge_calls'. + total_cost+= + num_merge_calls * + get_merge_cost(num_keys_per_buffer * MERGEBUFF, MERGEBUFF, elem_size); + + // # of records in remaining buffers. + last_n_elems+= num_remaining_buffs * num_keys_per_buffer; + + // Cost of merge sort of remaining buffers. + total_cost+= + get_merge_cost(last_n_elems, 1 + num_remaining_buffs, elem_size); + + num_buffers= num_merge_calls; + num_keys_per_buffer*= MERGEBUFF; + } + + // Simulate final merge_buff call. + last_n_elems+= num_keys_per_buffer * num_buffers; + total_cost+= get_merge_cost(last_n_elems, 1 + num_buffers, elem_size); + return total_cost; +} + +uchar **Filesort_buffer::alloc_sort_buffer(uint num_records, uint record_length) +{ + ulong sort_buff_sz; + + DBUG_ENTER("alloc_sort_buffer"); + + DBUG_EXECUTE_IF("alloc_sort_buffer_fail", + DBUG_SET("+d,simulate_out_of_memory");); + + if (m_idx_array.is_null()) + { + sort_buff_sz= ((size_t)num_records) * (record_length + sizeof(uchar*)); + set_if_bigger(sort_buff_sz, record_length * MERGEBUFF2); + uchar **sort_keys= + (uchar**) my_malloc(sort_buff_sz, MYF(MY_THREAD_SPECIFIC)); + m_idx_array= Idx_array(sort_keys, num_records); + m_record_length= record_length; + uchar **start_of_data= m_idx_array.array() + m_idx_array.size(); + m_start_of_data= reinterpret_cast<uchar*>(start_of_data); + } + else + { + DBUG_ASSERT(num_records == m_idx_array.size()); + DBUG_ASSERT(record_length == m_record_length); + } + DBUG_RETURN(m_idx_array.array()); +} + + +void Filesort_buffer::free_sort_buffer() +{ + my_free(m_idx_array.array()); + m_idx_array= Idx_array(); + m_record_length= 0; + m_start_of_data= NULL; +} + + +void Filesort_buffer::sort_buffer(const Sort_param *param, uint count) +{ + size_t size= param->sort_length; + if (count <= 1 || size == 0) + return; + uchar **keys= get_sort_keys(); + uchar **buffer= NULL; + if (radixsort_is_appliccable(count, param->sort_length) && + (buffer= (uchar**) my_malloc(count*sizeof(char*), + MYF(MY_THREAD_SPECIFIC)))) + { + radixsort_for_str_ptr(keys, count, param->sort_length, buffer); + my_free(buffer); + return; + } + + my_qsort2(keys, count, sizeof(uchar*), get_ptr_compare(size), &size); +} diff --git a/sql/filesort_utils.h b/sql/filesort_utils.h new file mode 100644 index 00000000000..00fa6f2566b --- /dev/null +++ b/sql/filesort_utils.h @@ -0,0 +1,129 @@ +/* Copyright (c) 2010, 2012 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-1301 USA */ + +#ifndef FILESORT_UTILS_INCLUDED +#define FILESORT_UTILS_INCLUDED + +#include "my_global.h" +#include "my_base.h" +#include "sql_array.h" + +class Sort_param; +/* + Calculate cost of merge sort + + @param num_rows Total number of rows. + @param num_keys_per_buffer Number of keys per buffer. + @param elem_size Size of each element. + + Calculates cost of merge sort by simulating call to merge_many_buff(). + + @retval + Computed cost of merge sort in disk seeks. + + @note + Declared here in order to be able to unit test it, + since library dependencies have not been sorted out yet. + + See also comments get_merge_many_buffs_cost(). +*/ + +double get_merge_many_buffs_cost_fast(ha_rows num_rows, + ha_rows num_keys_per_buffer, + uint elem_size); + + +/** + A wrapper class around the buffer used by filesort(). + The buffer is a contiguous chunk of memory, + where the first part is <num_records> pointers to the actual data. + + We wrap the buffer in order to be able to do lazy initialization of the + pointers: the buffer is often much larger than what we actually need. + + The buffer must be kept available for multiple executions of the + same sort operation, so we have explicit allocate and free functions, + rather than doing alloc/free in CTOR/DTOR. +*/ +class Filesort_buffer +{ +public: + Filesort_buffer() : + m_idx_array(), m_record_length(0), m_start_of_data(NULL) + {} + + /** Sort me... */ + void sort_buffer(const Sort_param *param, uint count); + + /// Initializes a record pointer. + uchar *get_record_buffer(uint idx) + { + m_idx_array[idx]= m_start_of_data + (idx * m_record_length); + return m_idx_array[idx]; + } + + /// Initializes all the record pointers. + void init_record_pointers() + { + for (uint ix= 0; ix < m_idx_array.size(); ++ix) + (void) get_record_buffer(ix); + } + + /// Returns total size: pointer array + record buffers. + size_t sort_buffer_size() const + { + return m_idx_array.size() * (m_record_length + sizeof(uchar*)); + } + + /// Allocates the buffer, but does *not* initialize pointers. + uchar **alloc_sort_buffer(uint num_records, uint record_length); + + + /// Check <num_records, record_length> for the buffer + bool check_sort_buffer_properties(uint num_records, uint record_length) + { + return (static_cast<uint>(m_idx_array.size()) == num_records && + m_record_length == record_length); + } + + /// Frees the buffer. + void free_sort_buffer(); + + /// Getter, for calling routines which still use the uchar** interface. + uchar **get_sort_keys() { return m_idx_array.array(); } + + /** + We need an assignment operator, see filesort(). + This happens to have the same semantics as the one that would be + generated by the compiler. We still implement it here, to show shallow + assignment explicitly: we have two objects sharing the same array. + */ + Filesort_buffer &operator=(const Filesort_buffer &rhs) + { + m_idx_array= rhs.m_idx_array; + m_record_length= rhs.m_record_length; + m_start_of_data= rhs.m_start_of_data; + return *this; + } + +private: + typedef Bounds_checked_array<uchar*> Idx_array; + + Idx_array m_idx_array; + uint m_record_length; + uchar *m_start_of_data; +}; + +#endif // FILESORT_UTILS_INCLUDED diff --git a/sql/frm_crypt.cc b/sql/frm_crypt.cc deleted file mode 100644 index 5612908aea5..00000000000 --- a/sql/frm_crypt.cc +++ /dev/null @@ -1,37 +0,0 @@ -/* 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-1301 USA */ - - -/* -** change the following to the output of password('our password') -** split into 2 parts of 8 characters each. -** This is done to make it impossible to search after a text string in the -** mysql binary. -*/ - -#include "sql_priv.h" -#include "frm_crypt.h" - -#ifdef HAVE_CRYPTED_FRM - -/* password('test') */ -ulong password_seed[2]={0x378b243e, 0x220ca493}; - -SQL_CRYPT *get_crypt_for_frm(void) -{ - return new SQL_CRYPT(password_seed); -} - -#endif diff --git a/sql/frm_crypt.h b/sql/frm_crypt.h deleted file mode 100644 index 0605644b3e0..00000000000 --- a/sql/frm_crypt.h +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright (c) 2006, 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-1301 USA */ - -#ifndef FRM_CRYPT_INCLUDED -#define FRM_CRYPT_INCLUDED - -class SQL_CRYPT; - -SQL_CRYPT *get_crypt_for_frm(void); - -#endif /* FRM_CRYPT_INCLUDED */ diff --git a/sql/gcalc_slicescan.cc b/sql/gcalc_slicescan.cc index c5db5053fb9..644ab4b8710 100644 --- a/sql/gcalc_slicescan.cc +++ b/sql/gcalc_slicescan.cc @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <my_global.h> @@ -982,6 +982,8 @@ void Gcalc_heap::reset() { if (m_n_points) { + if (m_hook) + *m_hook= NULL; free_list(m_first); m_n_points= 0; } @@ -1961,7 +1963,7 @@ double Gcalc_scan_iterator::get_h() const state.pi->calc_xy(&x, &next_y); } else - next_y= state.pi->node.shape.y; + next_y= state.pi->next ? state.pi->get_next()->node.shape.y : 0.0; return next_y - cur_y; } @@ -1974,7 +1976,7 @@ double Gcalc_scan_iterator::get_sp_x(const point *sp) const dy= sp->next_pi->node.shape.y - sp->pi->node.shape.y; if (fabs(dy) < 1e-12) return sp->pi->node.shape.x; - return (sp->next_pi->node.shape.x - sp->pi->node.shape.x) * dy; + return sp->pi->node.shape.x + (sp->next_pi->node.shape.x - sp->pi->node.shape.x) * dy; } diff --git a/sql/gcalc_slicescan.h b/sql/gcalc_slicescan.h index 4996287ca88..b9516fc8d8c 100644 --- a/sql/gcalc_slicescan.h +++ b/sql/gcalc_slicescan.h @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GCALC_SLICESCAN_INCLUDED diff --git a/sql/gcalc_tools.cc b/sql/gcalc_tools.cc index f3c24f9bdf3..71118ae1c9f 100644 --- a/sql/gcalc_tools.cc +++ b/sql/gcalc_tools.cc @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <my_global.h> @@ -143,6 +143,9 @@ int Gcalc_function::count_internal(const char *cur_func, uint set_type, { if (set_type == 0) result= i_states[n_shape] | b_states[n_shape]; + /* the last call for the count_internal outside of all shapes. */ + else if (set_type == 1) + result= 0; else if (set_type == op_border) result= b_states[n_shape]; else if (set_type == op_internals) @@ -158,7 +161,8 @@ int Gcalc_function::count_internal(const char *cur_func, uint set_type, if (next_func == op_border || next_func == op_internals) { - result= count_internal(cur_func, next_func, &cur_func); + result= count_internal(cur_func, + (set_type == 1) ? set_type : next_func, &cur_func); goto exit; } @@ -180,16 +184,34 @@ int Gcalc_function::count_internal(const char *cur_func, uint set_type, switch (next_func) { case op_union: - result= result | next_res; + if (result == result_true || next_res == result_true) + result= result_true; + else if (result == result_unknown || next_res == result_unknown) + result= result_unknown; + else + result= result_false; break; case op_intersection: - result= result & next_res; + if (result == result_false || next_res == result_false) + result= result_false; + else if (result == result_unknown || next_res == result_unknown) + result= result_unknown; + else + result= result_true; break; case op_symdifference: - result= result ^ next_res; + if (result == result_unknown || next_res == result_unknown) + result= result_unknown; + else + result= result ^ next_res; break; case op_difference: - result= result & !next_res; + if (result == result_false || next_res == result_true) + result= result_false; + else if (result == result_unknown || next_res == result_unknown) + result= result_unknown; + else + result= result_true; break; default: GCALC_DBUG_ASSERT(FALSE); @@ -197,24 +219,35 @@ int Gcalc_function::count_internal(const char *cur_func, uint set_type, } exit: - result^= mask; + if (result != result_unknown) + result^= mask; if (v_state != v_empty) { switch (v_state) { case v_find_t: - if (result) + if (result == result_true) { c_op= (c_op & ~v_mask) | v_t_found; int4store(sav_cur_func, c_op); - }; + } + else + { + if (set_type != 1) + result= result_unknown; + } break; case v_find_f: - if (!result) + if (result == result_false) { c_op= (c_op & ~v_mask) | v_f_found; int4store(sav_cur_func, c_op); - }; + } + else + { + if (set_type != 1) + result= result_unknown; + } break; case v_t_found: result= 1; @@ -264,6 +297,7 @@ int Gcalc_function::check_function(Gcalc_scan_iterator &scan_it) { const Gcalc_scan_iterator::point *eq_start, *cur_eq; const Gcalc_scan_iterator::event_point *events; + int result; GCALC_DBUG_ENTER("Gcalc_function::check_function"); while (scan_it.more_points()) @@ -288,8 +322,8 @@ int Gcalc_function::check_function(Gcalc_scan_iterator &scan_it) if (events->event == scev_end) set_b_state(events->get_shape()); - if (count()) - GCALC_DBUG_RETURN(1); + if ((result= count()) != result_unknown) + GCALC_DBUG_RETURN(result); clear_b_states(); continue; } @@ -300,15 +334,15 @@ int Gcalc_function::check_function(Gcalc_scan_iterator &scan_it) gcalc_shape_info si= events->get_shape(); if (events->event == scev_thread || events->event == scev_end || - events->event == scev_single_point || (get_shape_kind(si) == Gcalc_function::shape_polygon)) set_b_state(si); - else if (get_shape_kind(si) == Gcalc_function::shape_line) + else if (events->event == scev_single_point || + get_shape_kind(si) == Gcalc_function::shape_line) set_i_state(si); } - if (count()) - GCALC_DBUG_RETURN(1); + if ((result= count()) != result_unknown) + GCALC_DBUG_RETURN(result); /* Set back states changed in the loop above. */ for (events= scan_it.get_events(); events; events= events->get_next()) @@ -316,10 +350,10 @@ int Gcalc_function::check_function(Gcalc_scan_iterator &scan_it) gcalc_shape_info si= events->get_shape(); if (events->event == scev_thread || events->event == scev_end || - events->event == scev_single_point || - (get_shape_kind(si) == Gcalc_function::shape_polygon)) + get_shape_kind(si) == Gcalc_function::shape_polygon) clear_b_state(si); - else if (get_shape_kind(si) == Gcalc_function::shape_line) + else if (events->event == scev_single_point || + get_shape_kind(si) == Gcalc_function::shape_line) clear_i_state(si); } @@ -343,8 +377,8 @@ int Gcalc_function::check_function(Gcalc_scan_iterator &scan_it) else invert_i_state(si); } - if (count()) - GCALC_DBUG_RETURN(1); + if ((result= count()) != result_unknown) + GCALC_DBUG_RETURN(result); for (cur_eq= eq_start; cur_eq != pit.point(); cur_eq= cur_eq->get_next()) { @@ -357,12 +391,13 @@ int Gcalc_function::check_function(Gcalc_scan_iterator &scan_it) else invert_i_state(cur_eq->get_shape()); } - if (count()) - GCALC_DBUG_RETURN(1); + if ((result= count()) != result_unknown) + GCALC_DBUG_RETURN(result); + eq_start= pit.point(); } while (pit.point() != scan_it.get_event_end()); } - GCALC_DBUG_RETURN(0); + GCALC_DBUG_RETURN(count_last()); } diff --git a/sql/gcalc_tools.h b/sql/gcalc_tools.h index 12ee56732a2..8bda3c144a6 100644 --- a/sql/gcalc_tools.h +++ b/sql/gcalc_tools.h @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GCALC_TOOLS_INCLUDED @@ -82,6 +82,12 @@ public: shape_polygon= 2, shape_hole= 3 }; + enum count_result + { + result_false= 0, + result_true= 1, + result_unknown= 2 + }; Gcalc_function() : n_shapes(0) {} gcalc_shape_info add_new_shape(uint32 shape_id, shape_type shape_kind); /* @@ -116,6 +122,8 @@ public: int get_b_state(gcalc_shape_info shape) { return b_states[shape]; } int count() { return count_internal(function_buffer.ptr(), 0, 0); } + int count_last() + { return count_internal(function_buffer.ptr(), 1, 0); } void clear_i_states(); void clear_b_states(); void reset(); diff --git a/sql/gen_lex_token.cc b/sql/gen_lex_token.cc new file mode 100644 index 00000000000..eefe9163819 --- /dev/null +++ b/sql/gen_lex_token.cc @@ -0,0 +1,359 @@ +/* + 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, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ + +#include <my_global.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +/* We only need the tokens here */ +#define YYSTYPE_IS_DECLARED +#include <sql_yacc.h> +#include <lex.h> + +#include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ + +/* + This is a tool used during build only, + so MY_MAX_TOKEN does not need to be exact, + only big enough to hold: + - 256 character terminal tokens + - YYNTOKENS named terminal tokens + from bison. + See also YYMAXUTOK. +*/ +#define MY_MAX_TOKEN 1000 +/** Generated token. */ +struct gen_lex_token_string +{ + const char *m_token_string; + int m_token_length; + bool m_append_space; + bool m_start_expr; +}; + +gen_lex_token_string compiled_token_array[MY_MAX_TOKEN]; +int max_token_seen= 0; + +char char_tokens[256]; + +int tok_generic_value= 0; +int tok_generic_value_list= 0; +int tok_row_single_value= 0; +int tok_row_single_value_list= 0; +int tok_row_multiple_value= 0; +int tok_row_multiple_value_list= 0; +int tok_ident= 0; +int tok_unused= 0; + +void set_token(int tok, const char *str) +{ + if (tok <= 0) + { + fprintf(stderr, "Bad token found\n"); + exit(1); + } + + if (tok > max_token_seen) + { + max_token_seen= tok; + } + + if (max_token_seen >= MY_MAX_TOKEN) + { + fprintf(stderr, "Added that many new keywords ? Increase MY_MAX_TOKEN\n"); + exit(1); + } + + compiled_token_array[tok].m_token_string= str; + compiled_token_array[tok].m_token_length= strlen(str); + compiled_token_array[tok].m_append_space= true; + compiled_token_array[tok].m_start_expr= false; +} + +void set_start_expr_token(int tok) +{ + compiled_token_array[tok].m_start_expr= true; +} + +void compute_tokens() +{ + int tok; + unsigned int i; + char *str; + + /* + Default value. + */ + for (tok= 0; tok < MY_MAX_TOKEN; tok++) + { + compiled_token_array[tok].m_token_string= "(unknown)"; + compiled_token_array[tok].m_token_length= 9; + compiled_token_array[tok].m_append_space= true; + compiled_token_array[tok].m_start_expr= false; + } + + /* + Tokens made of just one terminal character + */ + for (tok=0; tok < 256; tok++) + { + str= & char_tokens[tok]; + str[0]= (char) tok; + compiled_token_array[tok].m_token_string= str; + compiled_token_array[tok].m_token_length= 1; + compiled_token_array[tok].m_append_space= true; + } + + max_token_seen= 255; + + /* + String terminal tokens, used in sql_yacc.yy + */ + set_token(NEG, "~"); + set_token(TABLE_REF_PRIORITY, "TABLE_REF_PRIORITY"); + + /* + Tokens hard coded in sql_lex.cc + */ + + set_token(WITH_CUBE_SYM, "WITH CUBE"); + set_token(WITH_ROLLUP_SYM, "WITH ROLLUP"); + set_token(NOT2_SYM, "!"); + set_token(OR2_SYM, "|"); + set_token(PARAM_MARKER, "?"); + set_token(SET_VAR, ":="); + set_token(UNDERSCORE_CHARSET, "(_charset)"); + set_token(END_OF_INPUT, ""); + + /* + Values. + These tokens are all normalized later, + so this strings will never be displayed. + */ + set_token(BIN_NUM, "(bin)"); + set_token(DECIMAL_NUM, "(decimal)"); + set_token(FLOAT_NUM, "(float)"); + set_token(HEX_NUM, "(hex)"); + set_token(LEX_HOSTNAME, "(hostname)"); + set_token(LONG_NUM, "(long)"); + set_token(NUM, "(num)"); + set_token(TEXT_STRING, "(text)"); + set_token(NCHAR_STRING, "(nchar)"); + set_token(ULONGLONG_NUM, "(ulonglong)"); + + /* + Identifiers. + */ + set_token(IDENT, "(id)"); + set_token(IDENT_QUOTED, "(id_quoted)"); + + /* + Unused tokens + */ + set_token(LOCATOR_SYM, "LOCATOR"); + set_token(SERVER_OPTIONS, "SERVER_OPTIONS"); + set_token(UDF_RETURNS_SYM, "UDF_RETURNS"); + + /* + See symbols[] in sql/lex.h + */ + for (i= 0; i< sizeof(symbols)/sizeof(symbols[0]); i++) + { + set_token(symbols[i].tok, symbols[i].name); + } + + /* + See sql_functions[] in sql/lex.h + */ + for (i= 0; i< sizeof(sql_functions)/sizeof(sql_functions[0]); i++) + { + set_token(sql_functions[i].tok, sql_functions[i].name); + } + + /* + Additional FAKE tokens, + used internally to normalize a digest text. + */ + + max_token_seen++; + tok_generic_value= max_token_seen; + set_token(tok_generic_value, "?"); + + max_token_seen++; + tok_generic_value_list= max_token_seen; + set_token(tok_generic_value_list, "?, ..."); + + max_token_seen++; + tok_row_single_value= max_token_seen; + set_token(tok_row_single_value, "(?)"); + + max_token_seen++; + tok_row_single_value_list= max_token_seen; + set_token(tok_row_single_value_list, "(?) /* , ... */"); + + max_token_seen++; + tok_row_multiple_value= max_token_seen; + set_token(tok_row_multiple_value, "(...)"); + + max_token_seen++; + tok_row_multiple_value_list= max_token_seen; + set_token(tok_row_multiple_value_list, "(...) /* , ... */"); + + max_token_seen++; + tok_ident= max_token_seen; + set_token(tok_ident, "(tok_id)"); + + max_token_seen++; + tok_unused= max_token_seen; + set_token(tok_unused, "UNUSED"); + + /* + Fix whitespace for some special tokens. + */ + + /* + The lexer parses "@@variable" as '@', '@', 'variable', + returning a token for '@' alone. + + This is incorrect, '@' is not really a token, + because the syntax "@ @ variable" (with spaces) is not accepted: + The lexer keeps some internal state after the '@' fake token. + + To work around this, digest text are printed as "@@variable". + */ + compiled_token_array[(int) '@'].m_append_space= false; + + /* + Define additional properties for tokens. + + List all the token that are followed by an expression. + This is needed to differentiate unary from binary + '+' and '-' operators, because we want to: + - reduce <unary +> <NUM> to <?>, + - preserve <...> <binary +> <NUM> as is. + */ + set_start_expr_token('('); + set_start_expr_token(','); + set_start_expr_token(EVERY_SYM); + set_start_expr_token(AT_SYM); + set_start_expr_token(STARTS_SYM); + set_start_expr_token(ENDS_SYM); + set_start_expr_token(DEFAULT); + set_start_expr_token(RETURN_SYM); + set_start_expr_token(IF_SYM); + set_start_expr_token(ELSEIF_SYM); + set_start_expr_token(CASE_SYM); + set_start_expr_token(WHEN_SYM); + set_start_expr_token(WHILE_SYM); + set_start_expr_token(UNTIL_SYM); + set_start_expr_token(SELECT_SYM); + + set_start_expr_token(OR_SYM); + set_start_expr_token(OR2_SYM); + set_start_expr_token(XOR); + set_start_expr_token(AND_SYM); + set_start_expr_token(AND_AND_SYM); + set_start_expr_token(NOT_SYM); + set_start_expr_token(BETWEEN_SYM); + set_start_expr_token(LIKE); + set_start_expr_token(REGEXP); + + set_start_expr_token('|'); + set_start_expr_token('&'); + set_start_expr_token(SHIFT_LEFT); + set_start_expr_token(SHIFT_RIGHT); + set_start_expr_token('+'); + set_start_expr_token('-'); + set_start_expr_token(INTERVAL_SYM); + set_start_expr_token('*'); + set_start_expr_token('/'); + set_start_expr_token('%'); + set_start_expr_token(DIV_SYM); + set_start_expr_token(MOD_SYM); + set_start_expr_token('^'); +} + +void print_tokens() +{ + int tok; + + printf("#ifdef LEX_TOKEN_WITH_DEFINITION\n"); + printf("lex_token_string lex_token_array[]=\n"); + printf("{\n"); + printf("/* PART 1: character tokens. */\n"); + + for (tok= 0; tok<256; tok++) + { + printf("/* %03d */ { \"\\x%02x\", 1, %s, %s},\n", + tok, + tok, + compiled_token_array[tok].m_append_space ? "true" : "false", + compiled_token_array[tok].m_start_expr ? "true" : "false"); + } + + printf("/* PART 2: named tokens. */\n"); + + for (tok= 256; tok<= max_token_seen; tok++) + { + printf("/* %03d */ { \"%s\", %d, %s, %s},\n", + tok, + compiled_token_array[tok].m_token_string, + compiled_token_array[tok].m_token_length, + compiled_token_array[tok].m_append_space ? "true" : "false", + compiled_token_array[tok].m_start_expr ? "true" : "false"); + } + + printf("/* DUMMY */ { \"\", 0, false, false}\n"); + printf("};\n"); + printf("#endif /* LEX_TOKEN_WITH_DEFINITION */\n"); + + printf("/* DIGEST specific tokens. */\n"); + printf("#define TOK_GENERIC_VALUE %d\n", tok_generic_value); + printf("#define TOK_GENERIC_VALUE_LIST %d\n", tok_generic_value_list); + printf("#define TOK_ROW_SINGLE_VALUE %d\n", tok_row_single_value); + printf("#define TOK_ROW_SINGLE_VALUE_LIST %d\n", tok_row_single_value_list); + printf("#define TOK_ROW_MULTIPLE_VALUE %d\n", tok_row_multiple_value); + printf("#define TOK_ROW_MULTIPLE_VALUE_LIST %d\n", tok_row_multiple_value_list); + printf("#define TOK_IDENT %d\n", tok_ident); + printf("#define TOK_UNUSED %d\n", tok_unused); +} + +int main(int argc,char **argv) +{ + puts("/*"); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2011")); + puts("*/"); + + printf("/*\n"); + printf(" This file is generated, do not edit.\n"); + printf(" See file sql/gen_lex_token.cc.\n"); + printf("*/\n"); + printf("struct lex_token_string\n"); + printf("{\n"); + printf(" const char *m_token_string;\n"); + printf(" int m_token_length;\n"); + printf(" bool m_append_space;\n"); + printf(" bool m_start_expr;\n"); + printf("};\n"); + printf("typedef struct lex_token_string lex_token_string;\n"); + + compute_tokens(); + print_tokens(); + + return 0; +} + diff --git a/sql/group_by_handler.cc b/sql/group_by_handler.cc new file mode 100644 index 00000000000..c1b5cfbe254 --- /dev/null +++ b/sql/group_by_handler.cc @@ -0,0 +1,142 @@ +/* + Copyright (c) 2014, 2015 SkySQL Ab & MariaDB Foundation + + 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 02111-1301 USA */ + +/* + This file implements the group_by_handler code. This interface + can be used by storage handlers that can intercept summary or GROUP + BY queries from MariaDB and itself return the result to the user or + upper level. +*/ + +#include "sql_priv.h" +#include "sql_select.h" + +/* + Same return values as do_select(); + + @retval + 0 if ok + @retval + 1 if error is sent + @retval + -1 if error should be sent +*/ + +int Pushdown_query::execute(JOIN *join) +{ + int err; + ha_rows max_limit; + ha_rows *reset_limit= 0; + Item **reset_item= 0; + THD *thd= handler->thd; + TABLE *table= handler->table; + DBUG_ENTER("Pushdown_query::execute"); + + if ((err= handler->init_scan())) + goto error; + + if (store_data_in_temp_table) + { + max_limit= join->tmp_table_param.end_write_records; + reset_limit= &join->unit->select_limit_cnt; + } + else + { + max_limit= join->unit->select_limit_cnt; + if (join->unit->fake_select_lex) + reset_item= &join->unit->fake_select_lex->select_limit; + } + + while (!(err= handler->next_row())) + { + if (thd->check_killed()) + { + thd->send_kill_message(); + handler->end_scan(); + DBUG_RETURN(-1); + } + + /* Check if we can accept the row */ + if (!having || having->val_bool()) + { + if (store_data_in_temp_table) + { + if ((err= table->file->ha_write_tmp_row(table->record[0]))) + { + bool is_duplicate; + if (!table->file->is_fatal_error(err, HA_CHECK_DUP)) + continue; // Distinct elimination + + if (create_internal_tmp_table_from_heap(thd, table, + join->tmp_table_param. + start_recinfo, + &join->tmp_table_param. + recinfo, + err, 1, &is_duplicate)) + DBUG_RETURN(1); + if (is_duplicate) + continue; + } + } + else + { + if (join->do_send_rows) + { + int error; + /* result < 0 if row was not accepted and should not be counted */ + if ((error= join->result->send_data(*join->fields))) + { + handler->end_scan(); + DBUG_RETURN(error < 0 ? 0 : -1); + } + } + } + + /* limit handling */ + if (++join->send_records >= max_limit && join->do_send_rows) + { + if (!(join->select_options & OPTION_FOUND_ROWS)) + break; // LIMIT reached + join->do_send_rows= 0; // Calculate FOUND_ROWS() + if (reset_limit) + *reset_limit= HA_POS_ERROR; + if (reset_item) + *reset_item= 0; + } + } + } + if (err != 0 && err != HA_ERR_END_OF_FILE) + goto error; + + if ((err= handler->end_scan())) + goto error_2; + if (!store_data_in_temp_table && join->result->send_eof()) + DBUG_RETURN(1); // Don't send error to client + + DBUG_RETURN(0); + +error: + handler->end_scan(); +error_2: + handler->print_error(err, MYF(0)); + DBUG_RETURN(-1); // Error not sent to client +} + + +void group_by_handler::print_error(int error, myf errflag) +{ + my_error(ER_GET_ERRNO, MYF(0), error, hton_name(ht)->str); +} diff --git a/sql/group_by_handler.h b/sql/group_by_handler.h new file mode 100644 index 00000000000..d3f48a15c24 --- /dev/null +++ b/sql/group_by_handler.h @@ -0,0 +1,102 @@ +/* + Copyright (c) 2014, 2015 SkySQL Ab & MariaDB Foundation + + 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 02111-1301 USA */ + +/* + This file implements the group_by_handler interface. This interface + can be used by storage handlers that can intercept summary or GROUP + BY queries from MariaDB and itself return the result to the user or + upper level. It is part of the Storage Engine API + + Both main and sub queries are supported. Here are some examples of what the + storage engine could intersept: + + SELECT count(*) FROM t1; + SELECT a,count(*) FROM t1 group by a; + SELECT a,count(*) as sum FROM t1 where b > 10 group by a, order by sum; + SELECT a,count(*) FROM t1,t2; + SELECT a, (select sum(*) from t2 where t1.a=t2.a) from t2; +*/ + +/** + The structure describing various parts of the query + + The engine is supposed to take out parts that it can do internally. + For example, if the engine can return results sorted according to + the specified order_by clause, it sets Query::order_by=NULL before + returning. + + At the moment the engine must take group_by (or return an error), and + optionally can take distinct, where, order_by, and having. + + The engine should not modify the select list. It is the extended SELECT + clause (extended, because it has more items than the original + user-specified SELECT clause) and it contains all aggregate functions, + used in the query. +*/ +struct Query +{ + List<Item> *select; + bool distinct; + TABLE_LIST *from; + Item *where; + ORDER *group_by; + ORDER *order_by; + Item *having; + // LIMIT +}; + +class group_by_handler +{ +public: + THD *thd; + handlerton *ht; + + /* + Temporary table where all results should be stored in record[0] + The table has a field for every item from the Query::select list. + */ + TABLE *table; + + group_by_handler(THD *thd_arg, handlerton *ht_arg) + : thd(thd_arg), ht(ht_arg), table(0) {} + virtual ~group_by_handler() {} + + /* + Functions to scan data. All these returns 0 if ok, error code in case + of error + */ + + /* + Initialize group_by scan, prepare for next_row(). + If this is a sub query with group by, this can be called many times for + a query. + */ + virtual int init_scan()= 0; + + /* + Return next group by result in table->record[0]. + Return 0 if row found, HA_ERR_END_OF_FILE if last row and other error + number in case of fatal error. + */ + virtual int next_row()= 0; + + /* End scanning */ + virtual int end_scan()=0; + + /* Report errors */ + virtual void print_error(int error, myf errflag); +}; + diff --git a/sql/gstream.cc b/sql/gstream.cc index 3a9e478c376..adb46083621 100644 --- a/sql/gstream.cc +++ b/sql/gstream.cc @@ -18,6 +18,7 @@ NOTE: These functions assumes that the string is end \0 terminated! */ +#include <my_global.h> #include "sql_priv.h" #include "gstream.h" #include "m_string.h" // LEX_STRING diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc deleted file mode 100644 index 2878f25ed14..00000000000 --- a/sql/ha_ndbcluster.cc +++ /dev/null @@ -1,11053 +0,0 @@ -/* Copyright (c) 2004, 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-1301 USA */ - -/** - @file - - @brief - This file defines the NDB Cluster handler: the interface between - MySQL and NDB Cluster -*/ - -#ifdef USE_PRAGMA_IMPLEMENTATION -#pragma implementation // gcc: Class implementation -#endif - -#include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes -#include "sql_table.h" // build_table_filename, - // tablename_to_filename, - // filename_to_tablename -#include "sql_partition.h" // HA_CAN_*, partition_info, part_id_range -#include "sql_base.h" // close_cached_tables -#include "discover.h" // readfrm -#include "sql_acl.h" // wild_case_compare -#include "rpl_mi.h" -#include "transaction.h" - -/* - There is an incompatibility between GNU ar and the Solaris linker - which makes the Solaris linker return an elf error when compiling - without NDB support (which makes libndb.a an empty library). - To avoid this we add a dummy declaration of a static variable - which makes us avoid this bug. -*/ -int ha_ndb_dummy; -#include <my_dir.h> -#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE -#include "ha_ndbcluster.h" -#include <ndbapi/NdbApi.hpp> -#include "ha_ndbcluster_cond.h" -#include <../util/Bitmask.hpp> -#include <ndbapi/NdbIndexStat.hpp> - -#include "ha_ndbcluster_binlog.h" -#include "ha_ndbcluster_tables.h" - -#include "sql_plugin.h" -#include "probes_mysql.h" -#include "sql_show.h" // init_fill_schema_files_row, - // schema_table_store_record -#include "sql_test.h" // print_where - -#ifdef ndb_dynamite -#undef assert -#define assert(x) do { if(x) break; ::printf("%s %d: assert failed: %s\n", __FILE__, __LINE__, #x); ::fflush(stdout); ::signal(SIGABRT,SIG_DFL); ::abort(); ::kill(::getpid(),6); ::kill(::getpid(),9); } while (0) -#endif - -// ndb interface initialization/cleanup functions -extern "C" void ndb_init_internal(); -extern "C" void ndb_end_internal(); - -static const int DEFAULT_PARALLELISM= 0; -static const ha_rows DEFAULT_AUTO_PREFETCH= 32; -static const ulong ONE_YEAR_IN_SECONDS= (ulong) 3600L*24L*365L; - -ulong opt_ndb_extra_logging; -static ulong opt_ndb_cache_check_time; -static char* opt_ndb_connectstring; -static char* opt_ndb_mgmd_host; -static uint opt_ndb_nodeid; - - -static MYSQL_THDVAR_UINT( - autoincrement_prefetch_sz, /* name */ - PLUGIN_VAR_RQCMDARG, - "Specify number of autoincrement values that are prefetched.", - NULL, /* check func. */ - NULL, /* update func. */ - 1, /* default */ - 1, /* min */ - 256, /* max */ - 0 /* block */ -); - - -static MYSQL_THDVAR_BOOL( - force_send, /* name */ - PLUGIN_VAR_OPCMDARG, - "Force send of buffers to ndb immediately without waiting for " - "other threads.", - NULL, /* check func. */ - NULL, /* update func. */ - 1 /* default */ -); - - -static MYSQL_THDVAR_BOOL( - use_exact_count, /* name */ - PLUGIN_VAR_OPCMDARG, - "Use exact records count during query planning and for fast " - "select count(*), disable for faster queries.", - NULL, /* check func. */ - NULL, /* update func. */ - 1 /* default */ -); - - -static MYSQL_THDVAR_BOOL( - use_transactions, /* name */ - PLUGIN_VAR_OPCMDARG, - "Use transactions for large inserts, if enabled then large " - "inserts will be split into several smaller transactions", - NULL, /* check func. */ - NULL, /* update func. */ - 1 /* default */ -); - - -static MYSQL_THDVAR_BOOL( - use_copying_alter_table, /* name */ - PLUGIN_VAR_OPCMDARG, - "Force ndbcluster to always copy tables at alter table (should " - "only be used if on-line alter table fails).", - NULL, /* check func. */ - NULL, /* update func. */ - 0 /* default */ -); - - -static MYSQL_THDVAR_UINT( - optimized_node_selection, /* name */ - PLUGIN_VAR_OPCMDARG, - "Select nodes for transactions in a more optimal way.", - NULL, /* check func. */ - NULL, /* update func. */ - 3, /* default */ - 0, /* min */ - 3, /* max */ - 0 /* block */ -); - - -static MYSQL_THDVAR_BOOL( - index_stat_enable, /* name */ - PLUGIN_VAR_OPCMDARG, - "Use ndb index statistics in query optimization.", - NULL, /* check func. */ - NULL, /* update func. */ - FALSE /* default */ -); - - -static MYSQL_THDVAR_ULONG( - index_stat_cache_entries, /* name */ - PLUGIN_VAR_NOCMDARG, - "", - NULL, /* check func. */ - NULL, /* update func. */ - 32, /* default */ - 0, /* min */ - ULONG_MAX, /* max */ - 0 /* block */ -); - - -static MYSQL_THDVAR_ULONG( - index_stat_update_freq, /* name */ - PLUGIN_VAR_NOCMDARG, - "", - NULL, /* check func. */ - NULL, /* update func. */ - 20, /* default */ - 0, /* min */ - ULONG_MAX, /* max */ - 0 /* block */ -); - -// Default value for parallelism -static const int parallelism= 0; - -// Default value for max number of transactions -// createable against NDB from this handler -static const int max_transactions= 3; // should really be 2 but there is a transaction to much allocated when loch table is used - -static uint ndbcluster_partition_flags(); -static uint ndbcluster_alter_table_flags(uint flags); -static int ndbcluster_init(void *); -static int ndbcluster_end(handlerton *hton, ha_panic_function flag); -static bool ndbcluster_show_status(handlerton *hton, THD*, - stat_print_fn *, - enum ha_stat_type); -static int ndbcluster_alter_tablespace(handlerton *hton, - THD* thd, - st_alter_tablespace *info); -static int ndbcluster_fill_is_table(handlerton *hton, - THD *thd, - TABLE_LIST *tables, - COND *cond, - enum enum_schema_tables); -static int ndbcluster_fill_files_table(handlerton *hton, - THD *thd, - TABLE_LIST *tables, - COND *cond); - -handlerton *ndbcluster_hton; - -static handler *ndbcluster_create_handler(handlerton *hton, - TABLE_SHARE *table, - MEM_ROOT *mem_root) -{ - return new (mem_root) ha_ndbcluster(hton, table); -} - -static uint ndbcluster_partition_flags() -{ - return (HA_CAN_PARTITION | HA_CAN_UPDATE_PARTITION_KEY | - HA_CAN_PARTITION_UNIQUE | HA_USE_AUTO_PARTITION); -} - -static uint ndbcluster_alter_table_flags(uint flags) -{ - if (flags & ALTER_DROP_PARTITION) - return 0; - else - return (HA_ONLINE_ADD_INDEX | HA_ONLINE_DROP_INDEX | - HA_ONLINE_ADD_UNIQUE_INDEX | HA_ONLINE_DROP_UNIQUE_INDEX | - HA_PARTITION_FUNCTION_SUPPORTED); - -} - -#define NDB_AUTO_INCREMENT_RETRIES 10 - -#define ERR_PRINT(err) \ - DBUG_PRINT("error", ("%d message: %s", err.code, err.message)) - -#define ERR_RETURN(err) \ -{ \ - const NdbError& tmp= err; \ - set_ndb_err(current_thd, tmp); \ - DBUG_RETURN(ndb_to_mysql_error(&tmp)); \ -} - -#define ERR_RETURN_PREPARE(rc, err) \ -{ \ - const NdbError& tmp= err; \ - set_ndb_err(current_thd, tmp); \ - rc= ndb_to_mysql_error(&tmp); \ -} - -#define ERR_BREAK(err, code) \ -{ \ - const NdbError& tmp= err; \ - set_ndb_err(current_thd, tmp); \ - code= ndb_to_mysql_error(&tmp); \ - break; \ -} - -static int ndbcluster_inited= 0; -int ndbcluster_terminating= 0; - -static Ndb* g_ndb= NULL; -Ndb_cluster_connection* g_ndb_cluster_connection= NULL; -uchar g_node_id_map[max_ndb_nodes]; - -/// Handler synchronization -mysql_mutex_t ndbcluster_mutex; - -/// Table lock handling -HASH ndbcluster_open_tables; - -static uchar *ndbcluster_get_key(NDB_SHARE *share, size_t *length, - my_bool not_used __attribute__((unused))); -#ifdef HAVE_NDB_BINLOG -static int rename_share(NDB_SHARE *share, const char *new_key); -#endif -static int ndb_get_table_statistics(ha_ndbcluster*, bool, Ndb*, const NDBTAB *, - struct Ndb_statistics *); - - -// Util thread variables -pthread_t ndb_util_thread; -int ndb_util_thread_running= 0; -mysql_mutex_t LOCK_ndb_util_thread; -mysql_cond_t COND_ndb_util_thread; -mysql_cond_t COND_ndb_util_ready; -pthread_handler_t ndb_util_thread_func(void *arg); - -/** - Dummy buffer to read zero pack_length fields - which are mapped to 1 char. -*/ -static uint32 dummy_buf; - -/** - Stats that can be retrieved from ndb. -*/ - -struct Ndb_statistics { - Uint64 row_count; - Uint64 commit_count; - Uint64 row_size; - Uint64 fragment_memory; -}; - -/* Status variables shown with 'show status like 'Ndb%' */ - -static long ndb_cluster_node_id= 0; -static const char * ndb_connected_host= 0; -static long ndb_connected_port= 0; -static long ndb_number_of_replicas= 0; -long ndb_number_of_data_nodes= 0; -long ndb_number_of_ready_data_nodes= 0; -long ndb_connect_count= 0; - -static int update_status_variables(Ndb_cluster_connection *c) -{ - ndb_cluster_node_id= c->node_id(); - ndb_connected_port= c->get_connected_port(); - ndb_connected_host= c->get_connected_host(); - ndb_number_of_replicas= 0; - ndb_number_of_ready_data_nodes= c->get_no_ready(); - ndb_number_of_data_nodes= c->no_db_nodes(); - ndb_connect_count= c->get_connect_count(); - return 0; -} - -SHOW_VAR ndb_status_variables[]= { - {"cluster_node_id", (char*) &ndb_cluster_node_id, SHOW_LONG}, - {"config_from_host", (char*) &ndb_connected_host, SHOW_CHAR_PTR}, - {"config_from_port", (char*) &ndb_connected_port, SHOW_LONG}, -// {"number_of_replicas", (char*) &ndb_number_of_replicas, SHOW_LONG}, - {"number_of_data_nodes",(char*) &ndb_number_of_data_nodes, SHOW_LONG}, - {NullS, NullS, SHOW_LONG} -}; - -/* - Error handling functions -*/ - -/* Note for merge: old mapping table, moved to storage/ndb/ndberror.c */ - -static int ndb_to_mysql_error(const NdbError *ndberr) -{ - /* read the mysql mapped error code */ - int error= ndberr->mysql_code; - - switch (error) - { - /* errors for which we do not add warnings, just return mapped error code - */ - case HA_ERR_NO_SUCH_TABLE: - case HA_ERR_KEY_NOT_FOUND: - return error; - - /* Mapping missing, go with the ndb error code*/ - case -1: - error= ndberr->code; - break; - /* Mapping exists, go with the mapped code */ - default: - break; - } - - /* - Push the NDB error message as warning - - Used to be able to use SHOW WARNINGS toget more info on what the error is - - Used by replication to see if the error was temporary - */ - if (ndberr->status == NdbError::TemporaryError) - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_TEMPORARY_ERRMSG, ER(ER_GET_TEMPORARY_ERRMSG), - ndberr->code, ndberr->message, "NDB"); - else - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - ndberr->code, ndberr->message, "NDB"); - return error; -} - -int execute_no_commit_ignore_no_key(ha_ndbcluster *h, NdbTransaction *trans) -{ - if (trans->execute(NdbTransaction::NoCommit, - NdbOperation::AO_IgnoreError, - h->m_force_send) == -1) - return -1; - - const NdbError &err= trans->getNdbError(); - if (err.classification != NdbError::NoError && - err.classification != NdbError::ConstraintViolation && - err.classification != NdbError::NoDataFound) - return -1; - - return 0; -} - -inline -int execute_no_commit(ha_ndbcluster *h, NdbTransaction *trans, - bool force_release) -{ - h->release_completed_operations(trans, force_release); - return h->m_ignore_no_key ? - execute_no_commit_ignore_no_key(h,trans) : - trans->execute(NdbTransaction::NoCommit, - NdbOperation::AbortOnError, - h->m_force_send); -} - -inline -int execute_commit(ha_ndbcluster *h, NdbTransaction *trans) -{ - return trans->execute(NdbTransaction::Commit, - NdbOperation::AbortOnError, - h->m_force_send); -} - -inline -int execute_commit(THD *thd, NdbTransaction *trans) -{ - return trans->execute(NdbTransaction::Commit, - NdbOperation::AbortOnError, - THDVAR(thd, force_send)); -} - -inline -int execute_no_commit_ie(ha_ndbcluster *h, NdbTransaction *trans, - bool force_release) -{ - h->release_completed_operations(trans, force_release); - return trans->execute(NdbTransaction::NoCommit, - NdbOperation::AO_IgnoreError, - h->m_force_send); -} - -/* - Place holder for ha_ndbcluster thread specific data -*/ -typedef struct st_thd_ndb_share { - const void *key; - struct Ndb_local_table_statistics stat; -} THD_NDB_SHARE; -static -uchar *thd_ndb_share_get_key(THD_NDB_SHARE *thd_ndb_share, size_t *length, - my_bool not_used __attribute__((unused))) -{ - *length= sizeof(thd_ndb_share->key); - return (uchar*) &thd_ndb_share->key; -} - -Thd_ndb::Thd_ndb() -{ - ndb= new Ndb(g_ndb_cluster_connection, ""); - lock_count= 0; - start_stmt_count= 0; - count= 0; - trans= NULL; - m_error= FALSE; - m_error_code= 0; - query_state&= NDB_QUERY_NORMAL; - options= 0; - (void) my_hash_init(&open_tables, &my_charset_bin, 5, 0, 0, - (my_hash_get_key)thd_ndb_share_get_key, 0, 0); -} - -Thd_ndb::~Thd_ndb() -{ - if (ndb) - { -#ifndef DBUG_OFF - Ndb::Free_list_usage tmp; - tmp.m_name= 0; - while (ndb->get_free_list_usage(&tmp)) - { - uint leaked= (uint) tmp.m_created - tmp.m_free; - if (leaked) - fprintf(stderr, "NDB: Found %u %s%s that %s not been released\n", - leaked, tmp.m_name, - (leaked == 1)?"":"'s", - (leaked == 1)?"has":"have"); - } -#endif - delete ndb; - ndb= NULL; - } - changed_tables.empty(); - my_hash_free(&open_tables); -} - -void -Thd_ndb::init_open_tables() -{ - count= 0; - m_error= FALSE; - m_error_code= 0; - my_hash_reset(&open_tables); -} - -inline -Ndb *ha_ndbcluster::get_ndb() -{ - return get_thd_ndb(current_thd)->ndb; -} - -/* - * manage uncommitted insert/deletes during transactio to get records correct - */ - -void ha_ndbcluster::set_rec_per_key() -{ - DBUG_ENTER("ha_ndbcluster::get_status_const"); - for (uint i=0 ; i < table_share->keys ; i++) - { - table->key_info[i].rec_per_key[table->key_info[i].key_parts-1]= 1; - } - DBUG_VOID_RETURN; -} - -ha_rows ha_ndbcluster::records() -{ - ha_rows retval; - DBUG_ENTER("ha_ndbcluster::records"); - struct Ndb_local_table_statistics *local_info= m_table_info; - DBUG_PRINT("info", ("id=%d, no_uncommitted_rows_count=%d", - ((const NDBTAB *)m_table)->getTableId(), - local_info->no_uncommitted_rows_count)); - - Ndb *ndb= get_ndb(); - ndb->setDatabaseName(m_dbname); - struct Ndb_statistics stat; - if (ndb_get_table_statistics(this, TRUE, ndb, m_table, &stat) == 0) - { - retval= stat.row_count; - } - else - { - DBUG_RETURN(HA_POS_ERROR); - } - - THD *thd= current_thd; - if (get_thd_ndb(thd)->m_error) - local_info->no_uncommitted_rows_count= 0; - - DBUG_RETURN(retval + local_info->no_uncommitted_rows_count); -} - -int ha_ndbcluster::records_update() -{ - if (m_ha_not_exact_count) - return 0; - DBUG_ENTER("ha_ndbcluster::records_update"); - int result= 0; - - struct Ndb_local_table_statistics *local_info= m_table_info; - DBUG_PRINT("info", ("id=%d, no_uncommitted_rows_count=%d", - ((const NDBTAB *)m_table)->getTableId(), - local_info->no_uncommitted_rows_count)); - { - Ndb *ndb= get_ndb(); - struct Ndb_statistics stat; - if (ndb->setDatabaseName(m_dbname)) - { - return my_errno= HA_ERR_OUT_OF_MEM; - } - result= ndb_get_table_statistics(this, TRUE, ndb, m_table, &stat); - if (result == 0) - { - stats.mean_rec_length= stat.row_size; - stats.data_file_length= stat.fragment_memory; - local_info->records= stat.row_count; - } - } - { - THD *thd= current_thd; - if (get_thd_ndb(thd)->m_error) - local_info->no_uncommitted_rows_count= 0; - } - if (result == 0) - stats.records= local_info->records+ local_info->no_uncommitted_rows_count; - DBUG_RETURN(result); -} - -void ha_ndbcluster::no_uncommitted_rows_execute_failure() -{ - if (m_ha_not_exact_count) - return; - DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_execute_failure"); - get_thd_ndb(current_thd)->m_error= TRUE; - get_thd_ndb(current_thd)->m_error_code= 0; - DBUG_VOID_RETURN; -} - -void ha_ndbcluster::no_uncommitted_rows_update(int c) -{ - if (m_ha_not_exact_count) - return; - DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_update"); - struct Ndb_local_table_statistics *local_info= m_table_info; - local_info->no_uncommitted_rows_count+= c; - DBUG_PRINT("info", ("id=%d, no_uncommitted_rows_count=%d", - ((const NDBTAB *)m_table)->getTableId(), - local_info->no_uncommitted_rows_count)); - DBUG_VOID_RETURN; -} - -void ha_ndbcluster::no_uncommitted_rows_reset(THD *thd) -{ - if (m_ha_not_exact_count) - return; - DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_reset"); - Thd_ndb *thd_ndb= get_thd_ndb(thd); - thd_ndb->count++; - thd_ndb->m_error= FALSE; - DBUG_VOID_RETURN; -} - -/* - Sets the latest ndb error code on the thd_ndb object such that it - can be retrieved later to know which ndb error caused the handler - error. -*/ -static void set_ndb_err(THD *thd, const NdbError &err) -{ - DBUG_ENTER("set_ndb_err"); - ERR_PRINT(err); - - Thd_ndb *thd_ndb= get_thd_ndb(thd); - if (thd_ndb == NULL) - DBUG_VOID_RETURN; -#ifdef NOT_YET - /* - Check if error code is overwritten, in this case the original - failure cause will be lost. E.g. if 4350 error is given. So - push a warning so that it can be detected which is the root - error cause. - */ - if (thd_ndb->m_query_id == thd->query_id && - thd_ndb->m_error_code != 0 && - thd_ndb->m_error_code != err.code) - { - char buf[FN_REFLEN]; - ndb_error_string(thd_ndb->m_error_code, buf, sizeof(buf)); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - thd_ndb->m_error_code, buf, "NDB"); - } -#endif - thd_ndb->m_query_id= thd->query_id; - thd_ndb->m_error_code= err.code; - DBUG_VOID_RETURN; -} - -int ha_ndbcluster::ndb_err(NdbTransaction *trans) -{ - THD *thd= current_thd; - int res; - NdbError err= trans->getNdbError(); - DBUG_ENTER("ndb_err"); - - set_ndb_err(thd, err); - - switch (err.classification) { - case NdbError::SchemaError: - { - // TODO perhaps we need to do more here, invalidate also in the cache - m_table->setStatusInvalid(); - /* Close other open handlers not used by any thread */ - TABLE_LIST table_list; - bzero((char*) &table_list,sizeof(table_list)); - table_list.db= m_dbname; - table_list.alias= table_list.table_name= m_tabname; - close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT); - break; - } - default: - break; - } - res= ndb_to_mysql_error(&err); - DBUG_PRINT("info", ("transformed ndbcluster error %d to mysql error %d", - err.code, res)); - if (res == HA_ERR_FOUND_DUPP_KEY) - { - char *error_data= err.details; - uint dupkey= MAX_KEY; - - for (uint i= 0; i < MAX_KEY; i++) - { - if (m_index[i].type == UNIQUE_INDEX || - m_index[i].type == UNIQUE_ORDERED_INDEX) - { - const NDBINDEX *unique_index= - (const NDBINDEX *) m_index[i].unique_index; - if (unique_index && - (char *) unique_index->getObjectId() == error_data) - { - dupkey= i; - break; - } - } - } - if (m_rows_to_insert == 1) - { - /* - We can only distinguish between primary and non-primary - violations here, so we need to return MAX_KEY for non-primary - to signal that key is unknown - */ - m_dupkey= err.code == 630 ? table_share->primary_key : dupkey; - } - else - { - /* We are batching inserts, offending key is not available */ - m_dupkey= (uint) -1; - } - } - DBUG_RETURN(res); -} - - -/** - Override the default get_error_message in order to add the - error message of NDB . -*/ - -bool ha_ndbcluster::get_error_message(int error, - String *buf) -{ - DBUG_ENTER("ha_ndbcluster::get_error_message"); - DBUG_PRINT("enter", ("error: %d", error)); - - Ndb *ndb= check_ndb_in_thd(current_thd); - if (!ndb) - DBUG_RETURN(FALSE); - - const NdbError err= ndb->getNdbError(error); - bool temporary= err.status==NdbError::TemporaryError; - buf->set(err.message, strlen(err.message), &my_charset_bin); - DBUG_PRINT("exit", ("message: %s, temporary: %d", buf->ptr(), temporary)); - DBUG_RETURN(temporary); -} - - -#ifndef DBUG_OFF -/** - Check if type is supported by NDB. -*/ - -static bool ndb_supported_type(enum_field_types type) -{ - switch (type) { - case MYSQL_TYPE_TINY: - case MYSQL_TYPE_SHORT: - case MYSQL_TYPE_LONG: - case MYSQL_TYPE_INT24: - case MYSQL_TYPE_LONGLONG: - case MYSQL_TYPE_FLOAT: - case MYSQL_TYPE_DOUBLE: - case MYSQL_TYPE_DECIMAL: - case MYSQL_TYPE_NEWDECIMAL: - case MYSQL_TYPE_TIMESTAMP: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_NEWDATE: - case MYSQL_TYPE_TIME: - case MYSQL_TYPE_YEAR: - case MYSQL_TYPE_STRING: - case MYSQL_TYPE_VAR_STRING: - case MYSQL_TYPE_VARCHAR: - case MYSQL_TYPE_TINY_BLOB: - case MYSQL_TYPE_BLOB: - case MYSQL_TYPE_MEDIUM_BLOB: - case MYSQL_TYPE_LONG_BLOB: - case MYSQL_TYPE_ENUM: - case MYSQL_TYPE_SET: - case MYSQL_TYPE_BIT: - case MYSQL_TYPE_GEOMETRY: - return TRUE; - case MYSQL_TYPE_NULL: - break; - } - return FALSE; -} -#endif /* !DBUG_OFF */ - - -/** - Check if MySQL field type forces var part in ndb storage. -*/ -static bool field_type_forces_var_part(enum_field_types type) -{ - switch (type) { - case MYSQL_TYPE_VAR_STRING: - case MYSQL_TYPE_VARCHAR: - return TRUE; - case MYSQL_TYPE_TINY_BLOB: - case MYSQL_TYPE_BLOB: - case MYSQL_TYPE_MEDIUM_BLOB: - case MYSQL_TYPE_LONG_BLOB: - case MYSQL_TYPE_GEOMETRY: - return FALSE; - default: - return FALSE; - } -} - -/** - Instruct NDB to set the value of the hidden primary key. -*/ - -bool ha_ndbcluster::set_hidden_key(NdbOperation *ndb_op, - uint fieldnr, const uchar *field_ptr) -{ - DBUG_ENTER("set_hidden_key"); - DBUG_RETURN(ndb_op->equal(fieldnr, (char*)field_ptr) != 0); -} - - -/** - Instruct NDB to set the value of one primary key attribute. -*/ - -int ha_ndbcluster::set_ndb_key(NdbOperation *ndb_op, Field *field, - uint fieldnr, const uchar *field_ptr) -{ - uint32 pack_len= field->pack_length(); - DBUG_ENTER("set_ndb_key"); - DBUG_PRINT("enter", ("%d: %s, ndb_type: %u, len=%d", - fieldnr, field->field_name, field->type(), - pack_len)); - DBUG_DUMP("key", field_ptr, pack_len); - - DBUG_ASSERT(ndb_supported_type(field->type())); - DBUG_ASSERT(! (field->flags & BLOB_FLAG)); - // Common implementation for most field types - DBUG_RETURN(ndb_op->equal(fieldnr, (char*) field_ptr, pack_len) != 0); -} - - -/** - Instruct NDB to set the value of one attribute. -*/ - -int ha_ndbcluster::set_ndb_value(NdbOperation *ndb_op, Field *field, - uint fieldnr, int row_offset, - bool *set_blob_value) -{ - const uchar* field_ptr= field->ptr + row_offset; - uint32 pack_len= field->pack_length(); - DBUG_ENTER("set_ndb_value"); - DBUG_PRINT("enter", ("%d: %s type: %u len=%d is_null=%s", - fieldnr, field->field_name, field->type(), - pack_len, field->is_null(row_offset) ? "Y" : "N")); - DBUG_DUMP("value", field_ptr, pack_len); - - DBUG_ASSERT(ndb_supported_type(field->type())); - { - // ndb currently does not support size 0 - uint32 empty_field; - if (pack_len == 0) - { - pack_len= sizeof(empty_field); - field_ptr= (uchar *)&empty_field; - if (field->is_null(row_offset)) - empty_field= 0; - else - empty_field= 1; - } - if (! (field->flags & BLOB_FLAG)) - { - if (field->type() != MYSQL_TYPE_BIT) - { - if (field->is_null(row_offset)) - { - DBUG_PRINT("info", ("field is NULL")); - // Set value to NULL - DBUG_RETURN((ndb_op->setValue(fieldnr, (char*)NULL) != 0)); - } - // Common implementation for most field types - DBUG_RETURN(ndb_op->setValue(fieldnr, (char*)field_ptr) != 0); - } - else // if (field->type() == MYSQL_TYPE_BIT) - { - longlong bits= field->val_int(); - - // Round up bit field length to nearest word boundry - pack_len= ((pack_len + 3) >> 2) << 2; - DBUG_ASSERT(pack_len <= 8); - if (field->is_null(row_offset)) - // Set value to NULL - DBUG_RETURN((ndb_op->setValue(fieldnr, (char*)NULL) != 0)); - DBUG_PRINT("info", ("bit field")); - DBUG_DUMP("value", (uchar*)&bits, pack_len); -#ifdef WORDS_BIGENDIAN - /* store lsw first */ - bits = ((bits >> 32) & 0x00000000FFFFFFFFLL) - | ((bits << 32) & 0xFFFFFFFF00000000LL); -#endif - DBUG_RETURN(ndb_op->setValue(fieldnr, (char*)&bits) != 0); - } - } - // Blob type - NdbBlob *ndb_blob= ndb_op->getBlobHandle(fieldnr); - if (ndb_blob != NULL) - { - if (field->is_null(row_offset)) - DBUG_RETURN(ndb_blob->setNull() != 0); - - Field_blob *field_blob= (Field_blob*)field; - - // Get length and pointer to data - uint32 blob_len= field_blob->get_length(field_ptr); - uchar* blob_ptr= NULL; - field_blob->get_ptr(&blob_ptr); - - // Looks like NULL ptr signals length 0 blob - if (blob_ptr == NULL) { - DBUG_ASSERT(blob_len == 0); - blob_ptr= (uchar*)""; - } - - DBUG_PRINT("value", ("set blob ptr: 0x%lx len: %u", - (long) blob_ptr, blob_len)); - DBUG_DUMP("value", blob_ptr, min(blob_len, 26)); - - if (set_blob_value) - *set_blob_value= TRUE; - // No callback needed to write value - DBUG_RETURN(ndb_blob->setValue(blob_ptr, blob_len) != 0); - } - DBUG_RETURN(1); - } -} - - -NdbBlob::ActiveHook g_get_ndb_blobs_value; - -/** - Callback to read all blob values. - - not done in unpack_record because unpack_record is valid - after execute(Commit) but reading blobs is not - - may only generate read operations; they have to be executed - somewhere before the data is available - - due to single buffer for all blobs, we let the last blob - process all blobs (last so that all are active) - - null bit is still set in unpack_record. - - @todo - allocate blob part aligned buffers -*/ - -int g_get_ndb_blobs_value(NdbBlob *ndb_blob, void *arg) -{ - DBUG_ENTER("g_get_ndb_blobs_value"); - if (ndb_blob->blobsNextBlob() != NULL) - DBUG_RETURN(0); - ha_ndbcluster *ha= (ha_ndbcluster *)arg; - int ret= get_ndb_blobs_value(ha->table, ha->m_value, - ha->m_blobs_buffer, ha->m_blobs_buffer_size, - ha->m_blobs_offset); - DBUG_RETURN(ret); -} - -/* - This routine is shared by injector. There is no common blobs buffer - so the buffer and length are passed by reference. Injector also - passes a record pointer diff. - */ -int get_ndb_blobs_value(TABLE* table, NdbValue* value_array, - uchar*& buffer, uint& buffer_size, - my_ptrdiff_t ptrdiff) -{ - DBUG_ENTER("get_ndb_blobs_value"); - - // Field has no field number so cannot use TABLE blob_field - // Loop twice, first only counting total buffer size - for (int loop= 0; loop <= 1; loop++) - { - uint32 offset= 0; - for (uint i= 0; i < table->s->fields; i++) - { - Field *field= table->field[i]; - NdbValue value= value_array[i]; - if (! (field->flags & BLOB_FLAG)) - continue; - if (value.blob == NULL) - { - DBUG_PRINT("info",("[%u] skipped", i)); - continue; - } - Field_blob *field_blob= (Field_blob *)field; - NdbBlob *ndb_blob= value.blob; - int isNull; - if (ndb_blob->getNull(isNull) != 0) - ERR_RETURN(ndb_blob->getNdbError()); - if (isNull == 0) { - Uint64 len64= 0; - if (ndb_blob->getLength(len64) != 0) - ERR_RETURN(ndb_blob->getNdbError()); - // Align to Uint64 - uint32 size= len64; - if (size % 8 != 0) - size+= 8 - size % 8; - if (loop == 1) - { - uchar *buf= buffer + offset; - uint32 len= 0xffffffff; // Max uint32 - if (ndb_blob->readData(buf, len) != 0) - ERR_RETURN(ndb_blob->getNdbError()); - DBUG_PRINT("info", ("[%u] offset: %u buf: 0x%lx len=%u [ptrdiff=%d]", - i, offset, (long) buf, len, (int)ptrdiff)); - DBUG_ASSERT(len == len64); - // Ugly hack assumes only ptr needs to be changed - field_blob->set_ptr_offset(ptrdiff, len, buf); - } - offset+= size; - } - else if (loop == 1) // undefined or null - { - // have to set length even in this case - uchar *buf= buffer + offset; // or maybe NULL - uint32 len= 0; - field_blob->set_ptr_offset(ptrdiff, len, buf); - DBUG_PRINT("info", ("[%u] isNull=%d", i, isNull)); - } - } - if (loop == 0 && offset > buffer_size) - { - my_free(buffer); - buffer_size= 0; - DBUG_PRINT("info", ("allocate blobs buffer size %u", offset)); - buffer= (uchar*) my_malloc(offset, MYF(MY_WME)); - if (buffer == NULL) - { - sql_print_error("ha_ndbcluster::get_ndb_blobs_value: " - "my_malloc(%u) failed", offset); - DBUG_RETURN(-1); - } - buffer_size= offset; - } - } - DBUG_RETURN(0); -} - - -/** - Instruct NDB to fetch one field. - - Data is read directly into buffer provided by field - if field is NULL, data is read into memory provided by NDBAPI. -*/ - -int ha_ndbcluster::get_ndb_value(NdbOperation *ndb_op, Field *field, - uint fieldnr, uchar* buf) -{ - DBUG_ENTER("get_ndb_value"); - DBUG_PRINT("enter", ("fieldnr: %d flags: %o", fieldnr, - (int)(field != NULL ? field->flags : 0))); - - if (field != NULL) - { - DBUG_ASSERT(buf); - DBUG_ASSERT(ndb_supported_type(field->type())); - DBUG_ASSERT(field->ptr != NULL); - if (! (field->flags & BLOB_FLAG)) - { - if (field->type() != MYSQL_TYPE_BIT) - { - uchar *field_buf; - if (field->pack_length() != 0) - field_buf= buf + (field->ptr - table->record[0]); - else - field_buf= (uchar *)&dummy_buf; - m_value[fieldnr].rec= ndb_op->getValue(fieldnr, - (char*) field_buf); - } - else // if (field->type() == MYSQL_TYPE_BIT) - { - m_value[fieldnr].rec= ndb_op->getValue(fieldnr); - } - DBUG_RETURN(m_value[fieldnr].rec == NULL); - } - - // Blob type - NdbBlob *ndb_blob= ndb_op->getBlobHandle(fieldnr); - m_value[fieldnr].blob= ndb_blob; - if (ndb_blob != NULL) - { - // Set callback - m_blobs_offset= buf - (uchar*) table->record[0]; - void *arg= (void *)this; - DBUG_RETURN(ndb_blob->setActiveHook(g_get_ndb_blobs_value, arg) != 0); - } - DBUG_RETURN(1); - } - - // Used for hidden key only - m_value[fieldnr].rec= ndb_op->getValue(fieldnr, (char*) m_ref); - DBUG_RETURN(m_value[fieldnr].rec == NULL); -} - -/* - Instruct NDB to fetch the partition id (fragment id) -*/ -int ha_ndbcluster::get_ndb_partition_id(NdbOperation *ndb_op) -{ - DBUG_ENTER("get_ndb_partition_id"); - DBUG_RETURN(ndb_op->getValue(NdbDictionary::Column::FRAGMENT, - (char *)&m_part_id) == NULL); -} - -/** - Check if any set or get of blob value in current query. -*/ - -bool ha_ndbcluster::uses_blob_value() -{ - MY_BITMAP *bitmap; - uint *blob_index, *blob_index_end; - if (table_share->blob_fields == 0) - return FALSE; - - bitmap= m_write_op ? table->write_set : table->read_set; - blob_index= table_share->blob_field; - blob_index_end= blob_index + table_share->blob_fields; - do - { - if (bitmap_is_set(bitmap, table->field[*blob_index]->field_index)) - return TRUE; - } while (++blob_index != blob_index_end); - return FALSE; -} - - -/** - Get metadata for this table from NDB. - - Check that frm-file on disk is equal to frm-file - of table accessed in NDB. - - @retval - 0 ok - @retval - -2 Meta data has changed; Re-read data and try again -*/ - -int cmp_frm(const NDBTAB *ndbtab, const void *pack_data, - uint pack_length) -{ - DBUG_ENTER("cmp_frm"); - /* - Compare FrmData in NDB with frm file from disk. - */ - if ((pack_length != ndbtab->getFrmLength()) || - (memcmp(pack_data, ndbtab->getFrmData(), pack_length))) - DBUG_RETURN(1); - DBUG_RETURN(0); -} - -int ha_ndbcluster::get_metadata(const char *path) -{ - Ndb *ndb= get_ndb(); - NDBDICT *dict= ndb->getDictionary(); - const NDBTAB *tab; - int error; - DBUG_ENTER("get_metadata"); - DBUG_PRINT("enter", ("m_tabname: %s, path: %s", m_tabname, path)); - - DBUG_ASSERT(m_table == NULL); - DBUG_ASSERT(m_table_info == NULL); - - uchar *data= NULL, *pack_data= NULL; - size_t length, pack_length; - - /* - Compare FrmData in NDB with frm file from disk. - */ - error= 0; - if (readfrm(path, &data, &length) || - packfrm(data, length, &pack_data, &pack_length)) - { - my_free(data); - my_free(pack_data); - DBUG_RETURN(1); - } - - Ndb_table_guard ndbtab_g(dict, m_tabname); - if (!(tab= ndbtab_g.get_table())) - ERR_RETURN(dict->getNdbError()); - - if (get_ndb_share_state(m_share) != NSS_ALTERED - && cmp_frm(tab, pack_data, pack_length)) - { - DBUG_PRINT("error", - ("metadata, pack_length: %lu getFrmLength: %d memcmp: %d", - (ulong) pack_length, tab->getFrmLength(), - memcmp(pack_data, tab->getFrmData(), pack_length))); - DBUG_DUMP("pack_data", (uchar*) pack_data, pack_length); - DBUG_DUMP("frm", (uchar*) tab->getFrmData(), tab->getFrmLength()); - error= HA_ERR_TABLE_DEF_CHANGED; - } - my_free(data); - my_free(pack_data); - - if (error) - goto err; - - DBUG_PRINT("info", ("fetched table %s", tab->getName())); - m_table= tab; - if ((error= open_indexes(ndb, table, FALSE)) == 0) - { - ndbtab_g.release(); - DBUG_RETURN(0); - } -err: - ndbtab_g.invalidate(); - m_table= NULL; - DBUG_RETURN(error); -} - -static int fix_unique_index_attr_order(NDB_INDEX_DATA &data, - const NDBINDEX *index, - KEY *key_info) -{ - DBUG_ENTER("fix_unique_index_attr_order"); - unsigned sz= index->getNoOfIndexColumns(); - - if (data.unique_index_attrid_map) - my_free(data.unique_index_attrid_map); - data.unique_index_attrid_map= (uchar*)my_malloc(sz,MYF(MY_WME)); - if (data.unique_index_attrid_map == 0) - { - sql_print_error("fix_unique_index_attr_order: my_malloc(%u) failure", - (unsigned int)sz); - DBUG_RETURN(HA_ERR_OUT_OF_MEM); - } - - KEY_PART_INFO* key_part= key_info->key_part; - KEY_PART_INFO* end= key_part+key_info->key_parts; - DBUG_ASSERT(key_info->key_parts == sz); - for (unsigned i= 0; key_part != end; key_part++, i++) - { - const char *field_name= key_part->field->field_name; -#ifndef DBUG_OFF - data.unique_index_attrid_map[i]= 255; -#endif - for (unsigned j= 0; j < sz; j++) - { - const NDBCOL *c= index->getColumn(j); - if (strcmp(field_name, c->getName()) == 0) - { - data.unique_index_attrid_map[i]= j; - break; - } - } - DBUG_ASSERT(data.unique_index_attrid_map[i] != 255); - } - DBUG_RETURN(0); -} - -/* - Create all the indexes for a table. - If any index should fail to be created, - the error is returned immediately -*/ -int ha_ndbcluster::create_indexes(Ndb *ndb, TABLE *tab) -{ - uint i; - int error= 0; - const char *index_name; - KEY* key_info= tab->key_info; - const char **key_name= tab->s->keynames.type_names; - DBUG_ENTER("ha_ndbcluster::create_indexes"); - - for (i= 0; i < tab->s->keys; i++, key_info++, key_name++) - { - index_name= *key_name; - NDB_INDEX_TYPE idx_type= get_index_type_from_table(i); - error= create_index(index_name, key_info, idx_type, i); - if (error) - { - DBUG_PRINT("error", ("Failed to create index %u", i)); - break; - } - } - - DBUG_RETURN(error); -} - -static void ndb_init_index(NDB_INDEX_DATA &data) -{ - data.type= UNDEFINED_INDEX; - data.status= UNDEFINED; - data.unique_index= NULL; - data.index= NULL; - data.unique_index_attrid_map= NULL; - data.index_stat=NULL; - data.index_stat_cache_entries=0; - data.index_stat_update_freq=0; - data.index_stat_query_count=0; -} - -static void ndb_clear_index(NDB_INDEX_DATA &data) -{ - if (data.unique_index_attrid_map) - { - my_free(data.unique_index_attrid_map); - } - if (data.index_stat) - { - delete data.index_stat; - } - ndb_init_index(data); -} - -/* - Associate a direct reference to an index handle - with an index (for faster access) - */ -int ha_ndbcluster::add_index_handle(THD *thd, NDBDICT *dict, KEY *key_info, - const char *index_name, uint index_no) -{ - int error= 0; - NDB_INDEX_TYPE idx_type= get_index_type_from_table(index_no); - m_index[index_no].type= idx_type; - DBUG_ENTER("ha_ndbcluster::add_index_handle"); - DBUG_PRINT("enter", ("table %s", m_tabname)); - - if (idx_type != PRIMARY_KEY_INDEX && idx_type != UNIQUE_INDEX) - { - DBUG_PRINT("info", ("Get handle to index %s", index_name)); - const NDBINDEX *index; - do - { - index= dict->getIndexGlobal(index_name, *m_table); - if (!index) - ERR_RETURN(dict->getNdbError()); - DBUG_PRINT("info", ("index: 0x%lx id: %d version: %d.%d status: %d", - (long) index, - index->getObjectId(), - index->getObjectVersion() & 0xFFFFFF, - index->getObjectVersion() >> 24, - index->getObjectStatus())); - DBUG_ASSERT(index->getObjectStatus() == - NdbDictionary::Object::Retrieved); - break; - } while (1); - m_index[index_no].index= index; - // ordered index - add stats - NDB_INDEX_DATA& d=m_index[index_no]; - delete d.index_stat; - d.index_stat=NULL; - if (THDVAR(thd, index_stat_enable)) - { - d.index_stat=new NdbIndexStat(index); - d.index_stat_cache_entries=THDVAR(thd, index_stat_cache_entries); - d.index_stat_update_freq=THDVAR(thd, index_stat_update_freq); - d.index_stat_query_count=0; - d.index_stat->alloc_cache(d.index_stat_cache_entries); - DBUG_PRINT("info", ("index %s stat=on cache_entries=%u update_freq=%u", - index->getName(), - d.index_stat_cache_entries, - d.index_stat_update_freq)); - } else - { - DBUG_PRINT("info", ("index %s stat=off", index->getName())); - } - } - if (idx_type == UNIQUE_ORDERED_INDEX || idx_type == UNIQUE_INDEX) - { - char unique_index_name[FN_LEN + 1]; - static const char* unique_suffix= "$unique"; - m_has_unique_index= TRUE; - strxnmov(unique_index_name, FN_LEN, index_name, unique_suffix, NullS); - DBUG_PRINT("info", ("Get handle to unique_index %s", unique_index_name)); - const NDBINDEX *index; - do - { - index= dict->getIndexGlobal(unique_index_name, *m_table); - if (!index) - ERR_RETURN(dict->getNdbError()); - DBUG_PRINT("info", ("index: 0x%lx id: %d version: %d.%d status: %d", - (long) index, - index->getObjectId(), - index->getObjectVersion() & 0xFFFFFF, - index->getObjectVersion() >> 24, - index->getObjectStatus())); - DBUG_ASSERT(index->getObjectStatus() == - NdbDictionary::Object::Retrieved); - break; - } while (1); - m_index[index_no].unique_index= index; - error= fix_unique_index_attr_order(m_index[index_no], index, key_info); - } - if (!error) - m_index[index_no].status= ACTIVE; - - DBUG_RETURN(error); -} - -/* - Associate index handles for each index of a table -*/ -int ha_ndbcluster::open_indexes(Ndb *ndb, TABLE *tab, bool ignore_error) -{ - uint i; - int error= 0; - THD *thd=current_thd; - NDBDICT *dict= ndb->getDictionary(); - KEY* key_info= tab->key_info; - const char **key_name= tab->s->keynames.type_names; - DBUG_ENTER("ha_ndbcluster::open_indexes"); - m_has_unique_index= FALSE; - for (i= 0; i < tab->s->keys; i++, key_info++, key_name++) - { - if ((error= add_index_handle(thd, dict, key_info, *key_name, i))) - { - if (ignore_error) - m_index[i].index= m_index[i].unique_index= NULL; - else - break; - } - m_index[i].null_in_unique_index= FALSE; - if (check_index_fields_not_null(key_info)) - m_index[i].null_in_unique_index= TRUE; - } - - if (error && !ignore_error) - { - while (i > 0) - { - i--; - if (m_index[i].index) - { - dict->removeIndexGlobal(*m_index[i].index, 1); - m_index[i].index= NULL; - } - if (m_index[i].unique_index) - { - dict->removeIndexGlobal(*m_index[i].unique_index, 1); - m_index[i].unique_index= NULL; - } - } - } - - DBUG_ASSERT(error == 0 || error == 4243); - - DBUG_RETURN(error); -} - -/* - Renumber indexes in index list by shifting out - indexes that are to be dropped - */ -void ha_ndbcluster::renumber_indexes(Ndb *ndb, TABLE *tab) -{ - uint i; - const char *index_name; - KEY* key_info= tab->key_info; - const char **key_name= tab->s->keynames.type_names; - DBUG_ENTER("ha_ndbcluster::renumber_indexes"); - - for (i= 0; i < tab->s->keys; i++, key_info++, key_name++) - { - index_name= *key_name; - NDB_INDEX_TYPE idx_type= get_index_type_from_table(i); - m_index[i].type= idx_type; - if (m_index[i].status == TO_BE_DROPPED) - { - DBUG_PRINT("info", ("Shifting index %s(%i) out of the list", - index_name, i)); - NDB_INDEX_DATA tmp; - uint j= i + 1; - // Shift index out of list - while(j != MAX_KEY && m_index[j].status != UNDEFINED) - { - tmp= m_index[j - 1]; - m_index[j - 1]= m_index[j]; - m_index[j]= tmp; - j++; - } - } - } - - DBUG_VOID_RETURN; -} - -/* - Drop all indexes that are marked for deletion -*/ -int ha_ndbcluster::drop_indexes(Ndb *ndb, TABLE *tab) -{ - uint i; - int error= 0; - const char *index_name; - KEY* key_info= tab->key_info; - NDBDICT *dict= ndb->getDictionary(); - DBUG_ENTER("ha_ndbcluster::drop_indexes"); - - for (i= 0; i < tab->s->keys; i++, key_info++) - { - NDB_INDEX_TYPE idx_type= get_index_type_from_table(i); - m_index[i].type= idx_type; - if (m_index[i].status == TO_BE_DROPPED) - { - const NdbDictionary::Index *index= m_index[i].index; - const NdbDictionary::Index *unique_index= m_index[i].unique_index; - - if (index) - { - index_name= index->getName(); - DBUG_PRINT("info", ("Dropping index %u: %s", i, index_name)); - // Drop ordered index from ndb - error= dict->dropIndexGlobal(*index); - if (!error) - { - dict->removeIndexGlobal(*index, 1); - m_index[i].index= NULL; - } - } - if (!error && unique_index) - { - index_name= unique_index->getName(); - DBUG_PRINT("info", ("Dropping unique index %u: %s", i, index_name)); - // Drop unique index from ndb - error= dict->dropIndexGlobal(*unique_index); - if (!error) - { - dict->removeIndexGlobal(*unique_index, 1); - m_index[i].unique_index= NULL; - } - } - if (error) - DBUG_RETURN(error); - ndb_clear_index(m_index[i]); - continue; - } - } - - DBUG_RETURN(error); -} - -/** - Decode the type of an index from information - provided in table object. -*/ -NDB_INDEX_TYPE ha_ndbcluster::get_index_type_from_table(uint inx) const -{ - return get_index_type_from_key(inx, table_share->key_info, - inx == table_share->primary_key); -} - -NDB_INDEX_TYPE ha_ndbcluster::get_index_type_from_key(uint inx, - KEY *key_info, - bool primary) const -{ - bool is_hash_index= (key_info[inx].algorithm == - HA_KEY_ALG_HASH); - if (primary) - return is_hash_index ? PRIMARY_KEY_INDEX : PRIMARY_KEY_ORDERED_INDEX; - - return ((key_info[inx].flags & HA_NOSAME) ? - (is_hash_index ? UNIQUE_INDEX : UNIQUE_ORDERED_INDEX) : - ORDERED_INDEX); -} - -bool ha_ndbcluster::check_index_fields_not_null(KEY* key_info) -{ - KEY_PART_INFO* key_part= key_info->key_part; - KEY_PART_INFO* end= key_part+key_info->key_parts; - DBUG_ENTER("ha_ndbcluster::check_index_fields_not_null"); - - for (; key_part != end; key_part++) - { - Field* field= key_part->field; - if (field->maybe_null()) - DBUG_RETURN(TRUE); - } - - DBUG_RETURN(FALSE); -} - -void ha_ndbcluster::release_metadata(THD *thd, Ndb *ndb) -{ - uint i; - - DBUG_ENTER("release_metadata"); - DBUG_PRINT("enter", ("m_tabname: %s", m_tabname)); - - NDBDICT *dict= ndb->getDictionary(); - int invalidate_indexes= 0; - if (thd && thd->lex && thd->lex->sql_command == SQLCOM_FLUSH) - { - invalidate_indexes = 1; - } - if (m_table != NULL) - { - if (m_table->getObjectStatus() == NdbDictionary::Object::Invalid) - invalidate_indexes= 1; - dict->removeTableGlobal(*m_table, invalidate_indexes); - } - // TODO investigate - DBUG_ASSERT(m_table_info == NULL); - m_table_info= NULL; - - // Release index list - for (i= 0; i < MAX_KEY; i++) - { - if (m_index[i].unique_index) - { - DBUG_ASSERT(m_table != NULL); - dict->removeIndexGlobal(*m_index[i].unique_index, invalidate_indexes); - } - if (m_index[i].index) - { - DBUG_ASSERT(m_table != NULL); - dict->removeIndexGlobal(*m_index[i].index, invalidate_indexes); - } - ndb_clear_index(m_index[i]); - } - - m_table= NULL; - DBUG_VOID_RETURN; -} - -int ha_ndbcluster::get_ndb_lock_type(enum thr_lock_type type) -{ - if (type >= TL_WRITE_ALLOW_WRITE) - return NdbOperation::LM_Exclusive; - if (type == TL_READ_WITH_SHARED_LOCKS || - uses_blob_value()) - return NdbOperation::LM_Read; - return NdbOperation::LM_CommittedRead; -} - -static const ulong index_type_flags[]= -{ - /* UNDEFINED_INDEX */ - 0, - - /* PRIMARY_KEY_INDEX */ - HA_ONLY_WHOLE_INDEX, - - /* PRIMARY_KEY_ORDERED_INDEX */ - /* - Enable HA_KEYREAD_ONLY when "sorted" indexes are supported, - thus ORDERD BY clauses can be optimized by reading directly - through the index. - */ - // HA_KEYREAD_ONLY | - HA_READ_NEXT | - HA_READ_PREV | - HA_READ_RANGE | - HA_READ_ORDER, - - /* UNIQUE_INDEX */ - HA_ONLY_WHOLE_INDEX, - - /* UNIQUE_ORDERED_INDEX */ - HA_READ_NEXT | - HA_READ_PREV | - HA_READ_RANGE | - HA_READ_ORDER, - - /* ORDERED_INDEX */ - HA_READ_NEXT | - HA_READ_PREV | - HA_READ_RANGE | - HA_READ_ORDER -}; - -static const int index_flags_size= sizeof(index_type_flags)/sizeof(ulong); - -inline NDB_INDEX_TYPE ha_ndbcluster::get_index_type(uint idx_no) const -{ - DBUG_ASSERT(idx_no < MAX_KEY); - return m_index[idx_no].type; -} - -inline bool ha_ndbcluster::has_null_in_unique_index(uint idx_no) const -{ - DBUG_ASSERT(idx_no < MAX_KEY); - return m_index[idx_no].null_in_unique_index; -} - - -/** - Get the flags for an index. - - @return - flags depending on the type of the index. -*/ - -inline ulong ha_ndbcluster::index_flags(uint idx_no, uint part, - bool all_parts) const -{ - DBUG_ENTER("ha_ndbcluster::index_flags"); - DBUG_PRINT("enter", ("idx_no: %u", idx_no)); - DBUG_ASSERT(get_index_type_from_table(idx_no) < index_flags_size); - DBUG_RETURN(index_type_flags[get_index_type_from_table(idx_no)] | - HA_KEY_SCAN_NOT_ROR); -} - -static void shrink_varchar(Field* field, const uchar* & ptr, uchar* buf) -{ - if (field->type() == MYSQL_TYPE_VARCHAR && ptr != NULL) { - Field_varstring* f= (Field_varstring*)field; - if (f->length_bytes == 1) { - uint pack_len= field->pack_length(); - DBUG_ASSERT(1 <= pack_len && pack_len <= 256); - if (ptr[1] == 0) { - buf[0]= ptr[0]; - } else { - DBUG_ASSERT(FALSE); - buf[0]= 255; - } - memmove(buf + 1, ptr + 2, pack_len - 1); - ptr= buf; - } - } -} - -int ha_ndbcluster::set_primary_key(NdbOperation *op, const uchar *key) -{ - KEY* key_info= table->key_info + table_share->primary_key; - KEY_PART_INFO* key_part= key_info->key_part; - KEY_PART_INFO* end= key_part+key_info->key_parts; - DBUG_ENTER("set_primary_key"); - - for (; key_part != end; key_part++) - { - Field* field= key_part->field; - const uchar* ptr= key; - uchar buf[256]; - shrink_varchar(field, ptr, buf); - if (set_ndb_key(op, field, - key_part->fieldnr-1, ptr)) - ERR_RETURN(op->getNdbError()); - key += key_part->store_length; - } - DBUG_RETURN(0); -} - - -int ha_ndbcluster::set_primary_key_from_record(NdbOperation *op, const uchar *record) -{ - KEY* key_info= table->key_info + table_share->primary_key; - KEY_PART_INFO* key_part= key_info->key_part; - KEY_PART_INFO* end= key_part+key_info->key_parts; - DBUG_ENTER("set_primary_key_from_record"); - - for (; key_part != end; key_part++) - { - Field* field= key_part->field; - if (set_ndb_key(op, field, - key_part->fieldnr-1, record+key_part->offset)) - ERR_RETURN(op->getNdbError()); - } - DBUG_RETURN(0); -} - -bool ha_ndbcluster::check_index_fields_in_write_set(uint keyno) -{ - KEY* key_info= table->key_info + keyno; - KEY_PART_INFO* key_part= key_info->key_part; - KEY_PART_INFO* end= key_part+key_info->key_parts; - uint i; - DBUG_ENTER("check_index_fields_in_write_set"); - - for (i= 0; key_part != end; key_part++, i++) - { - Field* field= key_part->field; - if (!bitmap_is_set(table->write_set, field->field_index)) - { - DBUG_RETURN(false); - } - } - - DBUG_RETURN(true); -} - -int ha_ndbcluster::set_index_key_from_record(NdbOperation *op, - const uchar *record, uint keyno) -{ - KEY* key_info= table->key_info + keyno; - KEY_PART_INFO* key_part= key_info->key_part; - KEY_PART_INFO* end= key_part+key_info->key_parts; - uint i; - DBUG_ENTER("set_index_key_from_record"); - - for (i= 0; key_part != end; key_part++, i++) - { - Field* field= key_part->field; - if (set_ndb_key(op, field, m_index[keyno].unique_index_attrid_map[i], - record+key_part->offset)) - ERR_RETURN(m_active_trans->getNdbError()); - } - DBUG_RETURN(0); -} - -int -ha_ndbcluster::set_index_key(NdbOperation *op, - const KEY *key_info, - const uchar * key_ptr) -{ - DBUG_ENTER("ha_ndbcluster::set_index_key"); - uint i; - KEY_PART_INFO* key_part= key_info->key_part; - KEY_PART_INFO* end= key_part+key_info->key_parts; - - for (i= 0; key_part != end; key_part++, i++) - { - Field* field= key_part->field; - const uchar* ptr= key_part->null_bit ? key_ptr + 1 : key_ptr; - uchar buf[256]; - shrink_varchar(field, ptr, buf); - if (set_ndb_key(op, field, m_index[active_index].unique_index_attrid_map[i], ptr)) - ERR_RETURN(m_active_trans->getNdbError()); - key_ptr+= key_part->store_length; - } - DBUG_RETURN(0); -} - -inline -int ha_ndbcluster::define_read_attrs(uchar* buf, NdbOperation* op) -{ - uint i; - DBUG_ENTER("define_read_attrs"); - - // Define attributes to read - for (i= 0; i < table_share->fields; i++) - { - Field *field= table->field[i]; - if (bitmap_is_set(table->read_set, i) || - ((field->flags & PRI_KEY_FLAG))) - { - if (get_ndb_value(op, field, i, buf)) - ERR_RETURN(op->getNdbError()); - } - else - { - m_value[i].ptr= NULL; - } - } - - if (table_share->primary_key == MAX_KEY) - { - DBUG_PRINT("info", ("Getting hidden key")); - // Scanning table with no primary key - int hidden_no= table_share->fields; -#ifndef DBUG_OFF - const NDBTAB *tab= (const NDBTAB *) m_table; - if (!tab->getColumn(hidden_no)) - DBUG_RETURN(1); -#endif - if (get_ndb_value(op, NULL, hidden_no, NULL)) - ERR_RETURN(op->getNdbError()); - } - DBUG_RETURN(0); -} - - -/** - Read one record from NDB using primary key. -*/ - -int ha_ndbcluster::pk_read(const uchar *key, uint key_len, uchar *buf, - uint32 part_id) -{ - uint no_fields= table_share->fields; - NdbConnection *trans= m_active_trans; - NdbOperation *op; - - int res; - DBUG_ENTER("pk_read"); - DBUG_PRINT("enter", ("key_len: %u", key_len)); - DBUG_DUMP("key", key, key_len); - m_write_op= FALSE; - - NdbOperation::LockMode lm= - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); - if (!(op= trans->getNdbOperation((const NDBTAB *) m_table)) || - op->readTuple(lm) != 0) - ERR_RETURN(trans->getNdbError()); - - if (table_share->primary_key == MAX_KEY) - { - // This table has no primary key, use "hidden" primary key - DBUG_PRINT("info", ("Using hidden key")); - DBUG_DUMP("key", key, 8); - if (set_hidden_key(op, no_fields, key)) - ERR_RETURN(trans->getNdbError()); - - // Read key at the same time, for future reference - if (get_ndb_value(op, NULL, no_fields, NULL)) - ERR_RETURN(trans->getNdbError()); - } - else - { - if ((res= set_primary_key(op, key))) - return res; - } - - if ((res= define_read_attrs(buf, op))) - DBUG_RETURN(res); - - if (m_use_partition_function) - { - op->setPartitionId(part_id); - // If table has user defined partitioning - // and no indexes, we need to read the partition id - // to support ORDER BY queries - if (table_share->primary_key == MAX_KEY && - get_ndb_partition_id(op)) - ERR_RETURN(trans->getNdbError()); - } - - if ((res = execute_no_commit_ie(this,trans,FALSE)) != 0 || - op->getNdbError().code) - { - table->status= STATUS_NOT_FOUND; - DBUG_RETURN(ndb_err(trans)); - } - - // The value have now been fetched from NDB - unpack_record(buf); - table->status= 0; - DBUG_RETURN(0); -} - -/** - Read one complementing record from NDB using primary key from old_data - or hidden key. -*/ - -int ha_ndbcluster::complemented_read(const uchar *old_data, uchar *new_data, - uint32 old_part_id) -{ - uint no_fields= table_share->fields, i; - NdbTransaction *trans= m_active_trans; - NdbOperation *op; - DBUG_ENTER("complemented_read"); - m_write_op= FALSE; - - if (bitmap_is_set_all(table->read_set)) - { - // We have allready retrieved all fields, nothing to complement - DBUG_RETURN(0); - } - - NdbOperation::LockMode lm= - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); - if (!(op= trans->getNdbOperation((const NDBTAB *) m_table)) || - op->readTuple(lm) != 0) - ERR_RETURN(trans->getNdbError()); - if (table_share->primary_key != MAX_KEY) - { - if (set_primary_key_from_record(op, old_data)) - ERR_RETURN(trans->getNdbError()); - } - else - { - // This table has no primary key, use "hidden" primary key - if (set_hidden_key(op, table->s->fields, m_ref)) - ERR_RETURN(op->getNdbError()); - } - - if (m_use_partition_function) - op->setPartitionId(old_part_id); - - // Read all unreferenced non-key field(s) - for (i= 0; i < no_fields; i++) - { - Field *field= table->field[i]; - if (!((field->flags & PRI_KEY_FLAG) || - bitmap_is_set(table->read_set, i)) && - !bitmap_is_set(table->write_set, i)) - { - if (get_ndb_value(op, field, i, new_data)) - ERR_RETURN(trans->getNdbError()); - } - } - - if (execute_no_commit(this,trans,FALSE) != 0) - { - table->status= STATUS_NOT_FOUND; - DBUG_RETURN(ndb_err(trans)); - } - - // The value have now been fetched from NDB - unpack_record(new_data); - table->status= 0; - - /* - * restore m_value - */ - for (i= 0; i < no_fields; i++) - { - Field *field= table->field[i]; - if (!((field->flags & PRI_KEY_FLAG) || - bitmap_is_set(table->read_set, i))) - { - m_value[i].ptr= NULL; - } - } - - DBUG_RETURN(0); -} - -/** - Check that all operations between first and last all - have gotten the errcode - If checking for HA_ERR_KEY_NOT_FOUND then update m_dupkey - for all succeeding operations -*/ -bool ha_ndbcluster::check_all_operations_for_error(NdbTransaction *trans, - const NdbOperation *first, - const NdbOperation *last, - uint errcode) -{ - const NdbOperation *op= first; - DBUG_ENTER("ha_ndbcluster::check_all_operations_for_error"); - - while(op) - { - NdbError err= op->getNdbError(); - if (err.status != NdbError::Success) - { - if (ndb_to_mysql_error(&err) != (int) errcode) - DBUG_RETURN(FALSE); - if (op == last) break; - op= trans->getNextCompletedOperation(op); - } - else - { - // We found a duplicate - if (op->getType() == NdbOperation::UniqueIndexAccess) - { - if (errcode == HA_ERR_KEY_NOT_FOUND) - { - NdbIndexOperation *iop= (NdbIndexOperation *) op; - const NDBINDEX *index= iop->getIndex(); - // Find the key_no of the index - for(uint i= 0; i<table->s->keys; i++) - { - if (m_index[i].unique_index == index) - { - m_dupkey= i; - break; - } - } - } - } - else - { - // Must have been primary key access - DBUG_ASSERT(op->getType() == NdbOperation::PrimaryKeyAccess); - if (errcode == HA_ERR_KEY_NOT_FOUND) - m_dupkey= table->s->primary_key; - } - DBUG_RETURN(FALSE); - } - } - DBUG_RETURN(TRUE); -} - - -/** - * Check if record contains any null valued columns that are part of a key - */ -static -int -check_null_in_record(const KEY* key_info, const uchar *record) -{ - KEY_PART_INFO *curr_part, *end_part; - curr_part= key_info->key_part; - end_part= curr_part + key_info->key_parts; - - while (curr_part != end_part) - { - if (curr_part->null_bit && - (record[curr_part->null_offset] & curr_part->null_bit)) - return 1; - curr_part++; - } - return 0; - /* - We could instead pre-compute a bitmask in table_share with one bit for - every null-bit in the key, and so check this just by OR'ing the bitmask - with the null bitmap in the record. - But not sure it's worth it. - */ -} - -/** - Peek to check if any rows already exist with conflicting - primary key or unique index values -*/ - -int ha_ndbcluster::peek_indexed_rows(const uchar *record, - NDB_WRITE_OP write_op) -{ - NdbTransaction *trans= m_active_trans; - NdbOperation *op; - const NdbOperation *first, *last; - uint i; - int res; - DBUG_ENTER("peek_indexed_rows"); - - NdbOperation::LockMode lm= - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); - first= NULL; - if (write_op != NDB_UPDATE && table->s->primary_key != MAX_KEY) - { - /* - * Fetch any row with colliding primary key - */ - if (!(op= trans->getNdbOperation((const NDBTAB *) m_table)) || - op->readTuple(lm) != 0) - ERR_RETURN(trans->getNdbError()); - - first= op; - if ((res= set_primary_key_from_record(op, record))) - ERR_RETURN(trans->getNdbError()); - - if (m_use_partition_function) - { - uint32 part_id; - int error; - longlong func_value; - my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); - error= m_part_info->get_partition_id(m_part_info, &part_id, &func_value); - dbug_tmp_restore_column_map(table->read_set, old_map); - if (error) - { - m_part_info->err_value= func_value; - DBUG_RETURN(error); - } - op->setPartitionId(part_id); - } - } - /* - * Fetch any rows with colliding unique indexes - */ - KEY* key_info; - KEY_PART_INFO *key_part, *end; - for (i= 0, key_info= table->key_info; i < table->s->keys; i++, key_info++) - { - if (i != table->s->primary_key && - key_info->flags & HA_NOSAME) - { - /* - A unique index is defined on table. - We cannot look up a NULL field value in a unique index. But since - keys with NULLs are not indexed, such rows cannot conflict anyway, so - we just skip the index in this case. - */ - if (check_null_in_record(key_info, record)) - { - DBUG_PRINT("info", ("skipping check for key with NULL")); - continue; - } - if (write_op != NDB_INSERT && !check_index_fields_in_write_set(i)) - { - DBUG_PRINT("info", ("skipping check for key %u not in write_set", i)); - continue; - } - NdbIndexOperation *iop; - const NDBINDEX *unique_index = m_index[i].unique_index; - key_part= key_info->key_part; - end= key_part + key_info->key_parts; - if (!(iop= trans->getNdbIndexOperation(unique_index, m_table)) || - iop->readTuple(lm) != 0) - ERR_RETURN(trans->getNdbError()); - - if (!first) - first= iop; - if ((res= set_index_key_from_record(iop, record, i))) - ERR_RETURN(trans->getNdbError()); - } - } - last= trans->getLastDefinedOperation(); - if (first) - res= execute_no_commit_ie(this,trans,FALSE); - else - { - // Table has no keys - table->status= STATUS_NOT_FOUND; - DBUG_RETURN(HA_ERR_KEY_NOT_FOUND); - } - if (check_all_operations_for_error(trans, first, last, - HA_ERR_KEY_NOT_FOUND)) - { - table->status= STATUS_NOT_FOUND; - DBUG_RETURN(ndb_err(trans)); - } - else - { - DBUG_PRINT("info", ("m_dupkey %d", m_dupkey)); - } - DBUG_RETURN(0); -} - - -/** - Read one record from NDB using unique secondary index. -*/ - -int ha_ndbcluster::unique_index_read(const uchar *key, - uint key_len, uchar *buf) -{ - int res; - NdbTransaction *trans= m_active_trans; - NdbIndexOperation *op; - DBUG_ENTER("ha_ndbcluster::unique_index_read"); - DBUG_PRINT("enter", ("key_len: %u, index: %u", key_len, active_index)); - DBUG_DUMP("key", key, key_len); - - NdbOperation::LockMode lm= - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); - if (!(op= trans->getNdbIndexOperation(m_index[active_index].unique_index, - m_table)) || - op->readTuple(lm) != 0) - ERR_RETURN(trans->getNdbError()); - - // Set secondary index key(s) - if ((res= set_index_key(op, table->key_info + active_index, key))) - DBUG_RETURN(res); - - if ((res= define_read_attrs(buf, op))) - DBUG_RETURN(res); - - if (execute_no_commit_ie(this,trans,FALSE) != 0 || - op->getNdbError().code) - { - int err= ndb_err(trans); - if(err==HA_ERR_KEY_NOT_FOUND) - table->status= STATUS_NOT_FOUND; - else - table->status= STATUS_GARBAGE; - - DBUG_RETURN(err); - } - - // The value have now been fetched from NDB - unpack_record(buf); - table->status= 0; - DBUG_RETURN(0); -} - -inline int ha_ndbcluster::fetch_next(NdbScanOperation* cursor) -{ - DBUG_ENTER("fetch_next"); - int local_check; - NdbTransaction *trans= m_active_trans; - - if (m_lock_tuple) - { - /* - Lock level m_lock.type either TL_WRITE_ALLOW_WRITE - (SELECT FOR UPDATE) or TL_READ_WITH_SHARED_LOCKS (SELECT - LOCK WITH SHARE MODE) and row was not explictly unlocked - with unlock_row() call - */ - NdbConnection *con_trans= m_active_trans; - NdbOperation *op; - // Lock row - DBUG_PRINT("info", ("Keeping lock on scanned row")); - - if (!(op= m_active_cursor->lockCurrentTuple())) - { - /* purecov: begin inspected */ - m_lock_tuple= FALSE; - ERR_RETURN(con_trans->getNdbError()); - /* purecov: end */ - } - m_ops_pending++; - } - m_lock_tuple= FALSE; - - bool contact_ndb= m_lock.type < TL_WRITE_ALLOW_WRITE && - m_lock.type != TL_READ_WITH_SHARED_LOCKS;; - do { - DBUG_PRINT("info", ("Call nextResult, contact_ndb: %d", contact_ndb)); - /* - We can only handle one tuple with blobs at a time. - */ - if (m_ops_pending && m_blobs_pending) - { - if (execute_no_commit(this,trans,FALSE) != 0) - DBUG_RETURN(ndb_err(trans)); - m_ops_pending= 0; - m_blobs_pending= FALSE; - } - - if ((local_check= cursor->nextResult(contact_ndb, m_force_send)) == 0) - { - /* - Explicitly lock tuple if "select for update" or - "select lock in share mode" - */ - m_lock_tuple= (m_lock.type == TL_WRITE_ALLOW_WRITE - || - m_lock.type == TL_READ_WITH_SHARED_LOCKS); - DBUG_RETURN(0); - } - else if (local_check == 1 || local_check == 2) - { - // 1: No more records - // 2: No more cached records - - /* - Before fetching more rows and releasing lock(s), - all pending update or delete operations should - be sent to NDB - */ - DBUG_PRINT("info", ("ops_pending: %ld", (long) m_ops_pending)); - if (m_ops_pending) - { - if (m_transaction_on) - { - if (execute_no_commit(this,trans,FALSE) != 0) - DBUG_RETURN(-1); - } - else - { - if (execute_commit(this,trans) != 0) - DBUG_RETURN(-1); - if (trans->restart() != 0) - { - DBUG_ASSERT(0); - DBUG_RETURN(-1); - } - } - m_ops_pending= 0; - } - contact_ndb= (local_check == 2); - } - else - { - DBUG_RETURN(-1); - } - } while (local_check == 2); - - DBUG_RETURN(1); -} - -/** - Get the next record of a started scan. Try to fetch - it locally from NdbApi cached records if possible, - otherwise ask NDB for more. - - @note - If this is a update/delete make sure to not contact - NDB before any pending ops have been sent to NDB. -*/ - -inline int ha_ndbcluster::next_result(uchar *buf) -{ - int res; - DBUG_ENTER("next_result"); - - if (!m_active_cursor) - DBUG_RETURN(HA_ERR_END_OF_FILE); - - if ((res= fetch_next(m_active_cursor)) == 0) - { - DBUG_PRINT("info", ("One more record found")); - - unpack_record(buf); - table->status= 0; - DBUG_RETURN(0); - } - else if (res == 1) - { - // No more records - table->status= STATUS_NOT_FOUND; - - DBUG_PRINT("info", ("No more records")); - DBUG_RETURN(HA_ERR_END_OF_FILE); - } - else - { - DBUG_RETURN(ndb_err(m_active_trans)); - } -} - -/** - Set bounds for ordered index scan. -*/ - -int ha_ndbcluster::set_bounds(NdbIndexScanOperation *op, - uint inx, - bool rir, - const key_range *keys[2], - uint range_no) -{ - const KEY *const key_info= table->key_info + inx; - const uint key_parts= key_info->key_parts; - uint key_tot_len[2]; - uint tot_len; - uint i, j; - - DBUG_ENTER("set_bounds"); - DBUG_PRINT("info", ("key_parts=%d", key_parts)); - - for (j= 0; j <= 1; j++) - { - const key_range *key= keys[j]; - if (key != NULL) - { - // for key->flag see ha_rkey_function - DBUG_PRINT("info", ("key %d length=%d flag=%d", - j, key->length, key->flag)); - key_tot_len[j]= key->length; - } - else - { - DBUG_PRINT("info", ("key %d not present", j)); - key_tot_len[j]= 0; - } - } - tot_len= 0; - - for (i= 0; i < key_parts; i++) - { - KEY_PART_INFO *key_part= &key_info->key_part[i]; - Field *field= key_part->field; -#ifndef DBUG_OFF - uint part_len= key_part->length; -#endif - uint part_store_len= key_part->store_length; - // Info about each key part - struct part_st { - bool part_last; - const key_range *key; - const uchar *part_ptr; - bool part_null; - int bound_type; - const uchar* bound_ptr; - }; - struct part_st part[2]; - - for (j= 0; j <= 1; j++) - { - struct part_st &p= part[j]; - p.key= NULL; - p.bound_type= -1; - if (tot_len < key_tot_len[j]) - { - p.part_last= (tot_len + part_store_len >= key_tot_len[j]); - p.key= keys[j]; - p.part_ptr= &p.key->key[tot_len]; - p.part_null= key_part->null_bit && *p.part_ptr; - p.bound_ptr= (const char *) - p.part_null ? 0 : key_part->null_bit ? p.part_ptr + 1 : p.part_ptr; - - if (j == 0) - { - switch (p.key->flag) - { - case HA_READ_KEY_EXACT: - if (! rir) - p.bound_type= NdbIndexScanOperation::BoundEQ; - else // differs for records_in_range - p.bound_type= NdbIndexScanOperation::BoundLE; - break; - // ascending - case HA_READ_KEY_OR_NEXT: - p.bound_type= NdbIndexScanOperation::BoundLE; - break; - case HA_READ_AFTER_KEY: - if (! p.part_last) - p.bound_type= NdbIndexScanOperation::BoundLE; - else - p.bound_type= NdbIndexScanOperation::BoundLT; - break; - // descending - case HA_READ_PREFIX_LAST: // weird - p.bound_type= NdbIndexScanOperation::BoundEQ; - break; - case HA_READ_PREFIX_LAST_OR_PREV: // weird - p.bound_type= NdbIndexScanOperation::BoundGE; - break; - case HA_READ_BEFORE_KEY: - if (! p.part_last) - p.bound_type= NdbIndexScanOperation::BoundGE; - else - p.bound_type= NdbIndexScanOperation::BoundGT; - break; - default: - break; - } - } - if (j == 1) { - switch (p.key->flag) - { - // ascending - case HA_READ_BEFORE_KEY: - if (! p.part_last) - p.bound_type= NdbIndexScanOperation::BoundGE; - else - p.bound_type= NdbIndexScanOperation::BoundGT; - break; - case HA_READ_AFTER_KEY: // weird - p.bound_type= NdbIndexScanOperation::BoundGE; - break; - default: - break; - // descending strangely sets no end key - } - } - - if (p.bound_type == -1) - { - DBUG_PRINT("error", ("key %d unknown flag %d", j, p.key->flag)); - DBUG_ASSERT(FALSE); - // Stop setting bounds but continue with what we have - DBUG_RETURN(op->end_of_bound(range_no)); - } - } - } - - // Seen with e.g. b = 1 and c > 1 - if (part[0].bound_type == NdbIndexScanOperation::BoundLE && - part[1].bound_type == NdbIndexScanOperation::BoundGE && - memcmp(part[0].part_ptr, part[1].part_ptr, part_store_len) == 0) - { - DBUG_PRINT("info", ("replace LE/GE pair by EQ")); - part[0].bound_type= NdbIndexScanOperation::BoundEQ; - part[1].bound_type= -1; - } - // Not seen but was in previous version - if (part[0].bound_type == NdbIndexScanOperation::BoundEQ && - part[1].bound_type == NdbIndexScanOperation::BoundGE && - memcmp(part[0].part_ptr, part[1].part_ptr, part_store_len) == 0) - { - DBUG_PRINT("info", ("remove GE from EQ/GE pair")); - part[1].bound_type= -1; - } - - for (j= 0; j <= 1; j++) - { - struct part_st &p= part[j]; - // Set bound if not done with this key - if (p.key != NULL) - { - DBUG_PRINT("info", ("key %d:%d offset: %d length: %d last: %d bound: %d", - j, i, tot_len, part_len, p.part_last, p.bound_type)); - DBUG_DUMP("info", p.part_ptr, part_store_len); - - // Set bound if not cancelled via type -1 - if (p.bound_type != -1) - { - const uchar* ptr= p.bound_ptr; - uchar buf[256]; - shrink_varchar(field, ptr, buf); - if (op->setBound(i, p.bound_type, ptr)) - ERR_RETURN(op->getNdbError()); - } - } - } - - tot_len+= part_store_len; - } - DBUG_RETURN(op->end_of_bound(range_no)); -} - -/** - Start ordered index scan in NDB. -*/ - -int ha_ndbcluster::ordered_index_scan(const key_range *start_key, - const key_range *end_key, - bool sorted, bool descending, - uchar* buf, part_id_range *part_spec) -{ - int res; - bool restart; - NdbTransaction *trans= m_active_trans; - NdbIndexScanOperation *op; - - DBUG_ENTER("ha_ndbcluster::ordered_index_scan"); - DBUG_PRINT("enter", ("index: %u, sorted: %d, descending: %d", - active_index, sorted, descending)); - DBUG_PRINT("enter", ("Starting new ordered scan on %s", m_tabname)); - m_write_op= FALSE; - - // Check that sorted seems to be initialised - DBUG_ASSERT(sorted == 0 || sorted == 1); - - if (m_active_cursor == 0) - { - restart= FALSE; - NdbOperation::LockMode lm= - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); - bool need_pk = (lm == NdbOperation::LM_Read); - if (!(op= trans->getNdbIndexScanOperation(m_index[active_index].index, - m_table)) || - op->readTuples(lm, 0, parallelism, sorted, descending, FALSE, need_pk)) - ERR_RETURN(trans->getNdbError()); - if (m_use_partition_function && part_spec != NULL && - part_spec->start_part == part_spec->end_part) - op->setPartitionId(part_spec->start_part); - m_active_cursor= op; - } else { - restart= TRUE; - op= (NdbIndexScanOperation*)m_active_cursor; - - if (m_use_partition_function && part_spec != NULL && - part_spec->start_part == part_spec->end_part) - op->setPartitionId(part_spec->start_part); - DBUG_ASSERT(op->getSorted() == sorted); - DBUG_ASSERT(op->getLockMode() == - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type)); - if (op->reset_bounds(m_force_send)) - DBUG_RETURN(ndb_err(m_active_trans)); - } - - { - const key_range *keys[2]= { start_key, end_key }; - res= set_bounds(op, active_index, FALSE, keys); - if (res) - DBUG_RETURN(res); - } - - if (!restart) - { - if (m_cond && m_cond->generate_scan_filter(op)) - DBUG_RETURN(ndb_err(trans)); - - if ((res= define_read_attrs(buf, op))) - { - DBUG_RETURN(res); - } - - // If table has user defined partitioning - // and no primary key, we need to read the partition id - // to support ORDER BY queries - if (m_use_partition_function && - (table_share->primary_key == MAX_KEY) && - (get_ndb_partition_id(op))) - ERR_RETURN(trans->getNdbError()); - } - - if (execute_no_commit(this,trans,FALSE) != 0) - DBUG_RETURN(ndb_err(trans)); - - DBUG_RETURN(next_result(buf)); -} - -static -int -guess_scan_flags(NdbOperation::LockMode lm, - const NDBTAB* tab, const MY_BITMAP* readset) -{ - int flags= 0; - flags|= (lm == NdbOperation::LM_Read) ? NdbScanOperation::SF_KeyInfo : 0; - if (tab->checkColumns(0, 0) & 2) - { - int ret = tab->checkColumns(readset->bitmap, no_bytes_in_map(readset)); - - if (ret & 2) - { // If disk columns...use disk scan - flags |= NdbScanOperation::SF_DiskScan; - } - else if ((ret & 4) == 0 && (lm == NdbOperation::LM_Exclusive)) - { - // If no mem column is set and exclusive...guess disk scan - flags |= NdbScanOperation::SF_DiskScan; - } - } - return flags; -} - - -/* - Unique index scan in NDB (full table scan with scan filter) - */ - -int ha_ndbcluster::unique_index_scan(const KEY* key_info, - const uchar *key, - uint key_len, - uchar *buf) -{ - int res; - NdbScanOperation *op; - NdbTransaction *trans= m_active_trans; - part_id_range part_spec; - - DBUG_ENTER("unique_index_scan"); - DBUG_PRINT("enter", ("Starting new scan on %s", m_tabname)); - - NdbOperation::LockMode lm= - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); - int flags= guess_scan_flags(lm, m_table, table->read_set); - if (!(op=trans->getNdbScanOperation((const NDBTAB *) m_table)) || - op->readTuples(lm, flags, parallelism)) - ERR_RETURN(trans->getNdbError()); - m_active_cursor= op; - - if (m_use_partition_function) - { - part_spec.start_part= 0; - part_spec.end_part= m_part_info->get_tot_partitions() - 1; - prune_partition_set(table, &part_spec); - DBUG_PRINT("info", ("part_spec.start_part = %u, part_spec.end_part = %u", - part_spec.start_part, part_spec.end_part)); - /* - If partition pruning has found no partition in set - we can return HA_ERR_END_OF_FILE - If partition pruning has found exactly one partition in set - we can optimize scan to run towards that partition only. - */ - if (part_spec.start_part > part_spec.end_part) - { - DBUG_RETURN(HA_ERR_END_OF_FILE); - } - else if (part_spec.start_part == part_spec.end_part) - { - /* - Only one partition is required to scan, if sorted is required we - don't need it any more since output from one ordered partitioned - index is always sorted. - */ - m_active_cursor->setPartitionId(part_spec.start_part); - } - // If table has user defined partitioning - // and no primary key, we need to read the partition id - // to support ORDER BY queries - if ((table_share->primary_key == MAX_KEY) && - (get_ndb_partition_id(op))) - ERR_RETURN(trans->getNdbError()); - } - if (!m_cond) - m_cond= new ha_ndbcluster_cond; - if (!m_cond) - { - my_errno= HA_ERR_OUT_OF_MEM; - DBUG_RETURN(my_errno); - } - if (m_cond->generate_scan_filter_from_key(op, key_info, key, key_len, buf)) - DBUG_RETURN(ndb_err(trans)); - if ((res= define_read_attrs(buf, op))) - DBUG_RETURN(res); - - if (execute_no_commit(this,trans,FALSE) != 0) - DBUG_RETURN(ndb_err(trans)); - DBUG_PRINT("exit", ("Scan started successfully")); - DBUG_RETURN(next_result(buf)); -} - - -/** - Start full table scan in NDB. -*/ -int ha_ndbcluster::full_table_scan(uchar *buf) -{ - int res; - NdbScanOperation *op; - NdbTransaction *trans= m_active_trans; - part_id_range part_spec; - - DBUG_ENTER("full_table_scan"); - DBUG_PRINT("enter", ("Starting new scan on %s", m_tabname)); - m_write_op= FALSE; - - NdbOperation::LockMode lm= - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); - int flags= guess_scan_flags(lm, m_table, table->read_set); - if (!(op=trans->getNdbScanOperation(m_table)) || - op->readTuples(lm, flags, parallelism)) - ERR_RETURN(trans->getNdbError()); - m_active_cursor= op; - - if (m_use_partition_function) - { - part_spec.start_part= 0; - part_spec.end_part= m_part_info->get_tot_partitions() - 1; - prune_partition_set(table, &part_spec); - DBUG_PRINT("info", ("part_spec.start_part: %u part_spec.end_part: %u", - part_spec.start_part, part_spec.end_part)); - /* - If partition pruning has found no partition in set - we can return HA_ERR_END_OF_FILE - If partition pruning has found exactly one partition in set - we can optimize scan to run towards that partition only. - */ - if (part_spec.start_part > part_spec.end_part) - { - DBUG_RETURN(HA_ERR_END_OF_FILE); - } - else if (part_spec.start_part == part_spec.end_part) - { - /* - Only one partition is required to scan, if sorted is required we - don't need it any more since output from one ordered partitioned - index is always sorted. - */ - m_active_cursor->setPartitionId(part_spec.start_part); - } - // If table has user defined partitioning - // and no primary key, we need to read the partition id - // to support ORDER BY queries - if ((table_share->primary_key == MAX_KEY) && - (get_ndb_partition_id(op))) - ERR_RETURN(trans->getNdbError()); - } - - if (m_cond && m_cond->generate_scan_filter(op)) - DBUG_RETURN(ndb_err(trans)); - if ((res= define_read_attrs(buf, op))) - DBUG_RETURN(res); - - if (execute_no_commit(this,trans,FALSE) != 0) - DBUG_RETURN(ndb_err(trans)); - DBUG_PRINT("exit", ("Scan started successfully")); - DBUG_RETURN(next_result(buf)); -} - -int -ha_ndbcluster::set_auto_inc(Field *field) -{ - DBUG_ENTER("ha_ndbcluster::set_auto_inc"); - Ndb *ndb= get_ndb(); - bool read_bit= bitmap_is_set(table->read_set, field->field_index); - bitmap_set_bit(table->read_set, field->field_index); - Uint64 next_val= (Uint64) field->val_int() + 1; - if (!read_bit) - bitmap_clear_bit(table->read_set, field->field_index); -#ifndef DBUG_OFF - char buff[22]; - DBUG_PRINT("info", - ("Trying to set next auto increment value to %s", - llstr(next_val, buff))); -#endif - if (ndb->checkUpdateAutoIncrementValue(m_share->tuple_id_range, next_val)) - { - Ndb_tuple_id_range_guard g(m_share); - if (ndb->setAutoIncrementValue(m_table, g.range, next_val, TRUE) - == -1) - ERR_RETURN(ndb->getNdbError()); - } - DBUG_RETURN(0); -} - -/** - Insert one record into NDB. -*/ -int ha_ndbcluster::write_row(uchar *record) -{ - bool has_auto_increment; - uint i; - NdbTransaction *trans= m_active_trans; - NdbOperation *op; - int res; - THD *thd= table->in_use; - longlong func_value= 0; - DBUG_ENTER("ha_ndbcluster::write_row"); - - m_write_op= TRUE; - has_auto_increment= (table->next_number_field && record == table->record[0]); - if (table_share->primary_key != MAX_KEY) - { - /* - * Increase any auto_incremented primary key - */ - if (has_auto_increment) - { - int error; - - m_skip_auto_increment= FALSE; - if ((error= update_auto_increment())) - DBUG_RETURN(error); - m_skip_auto_increment= (insert_id_for_cur_row == 0); - } - } - - /* - * If IGNORE the ignore constraint violations on primary and unique keys - */ - if (!m_use_write && m_ignore_dup_key) - { - /* - compare if expression with that in start_bulk_insert() - start_bulk_insert will set parameters to ensure that each - write_row is committed individually - */ - int peek_res= peek_indexed_rows(record, NDB_INSERT); - - if (!peek_res) - { - DBUG_RETURN(HA_ERR_FOUND_DUPP_KEY); - } - if (peek_res != HA_ERR_KEY_NOT_FOUND) - DBUG_RETURN(peek_res); - } - - ha_statistic_increment(&SSV::ha_write_count); - if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) - table->timestamp_field->set_time(); - - if (!(op= trans->getNdbOperation(m_table))) - ERR_RETURN(trans->getNdbError()); - - res= (m_use_write) ? op->writeTuple() :op->insertTuple(); - if (res != 0) - ERR_RETURN(trans->getNdbError()); - - if (m_use_partition_function) - { - uint32 part_id; - int error; - my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); - error= m_part_info->get_partition_id(m_part_info, &part_id, &func_value); - dbug_tmp_restore_column_map(table->read_set, old_map); - if (error) - { - m_part_info->err_value= func_value; - DBUG_RETURN(error); - } - op->setPartitionId(part_id); - } - - if (table_share->primary_key == MAX_KEY) - { - // Table has hidden primary key - Ndb *ndb= get_ndb(); - Uint64 auto_value; - uint retries= NDB_AUTO_INCREMENT_RETRIES; - int retry_sleep= 30; /* 30 milliseconds, transaction */ - for (;;) - { - Ndb_tuple_id_range_guard g(m_share); - if (ndb->getAutoIncrementValue(m_table, g.range, auto_value, 1) == -1) - { - if (--retries && - ndb->getNdbError().status == NdbError::TemporaryError) - { - my_sleep(retry_sleep); - continue; - } - ERR_RETURN(ndb->getNdbError()); - } - break; - } - if (set_hidden_key(op, table_share->fields, (const uchar*)&auto_value)) - ERR_RETURN(op->getNdbError()); - } - else - { - int error; - if ((error= set_primary_key_from_record(op, record))) - DBUG_RETURN(error); - } - - // Set non-key attribute(s) - bool set_blob_value= FALSE; - my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); - for (i= 0; i < table_share->fields; i++) - { - Field *field= table->field[i]; - if (!(field->flags & PRI_KEY_FLAG) && - (bitmap_is_set(table->write_set, i) || !m_use_write) && - set_ndb_value(op, field, i, record-table->record[0], &set_blob_value)) - { - m_skip_auto_increment= TRUE; - dbug_tmp_restore_column_map(table->read_set, old_map); - ERR_RETURN(op->getNdbError()); - } - } - dbug_tmp_restore_column_map(table->read_set, old_map); - - if (m_use_partition_function) - { - /* - We need to set the value of the partition function value in - NDB since the NDB kernel doesn't have easy access to the function - to calculate the value. - */ - if (func_value >= INT_MAX32) - func_value= INT_MAX32; - uint32 part_func_value= (uint32)func_value; - uint no_fields= table_share->fields; - if (table_share->primary_key == MAX_KEY) - no_fields++; - op->setValue(no_fields, part_func_value); - } - - if (unlikely(m_slow_path)) - { - /* - ignore TNTO_NO_LOGGING for slave thd. It is used to indicate - log-slave-updates option. This is instead handled in the - injector thread, by looking explicitly at the - opt_log_slave_updates flag. - */ - Thd_ndb *thd_ndb= get_thd_ndb(thd); - if (thd->slave_thread) - op->setAnyValue(thd->server_id); - else if (thd_ndb->trans_options & TNTO_NO_LOGGING) - op->setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING); - } - m_rows_changed++; - - /* - Execute write operation - NOTE When doing inserts with many values in - each INSERT statement it should not be necessary - to NoCommit the transaction between each row. - Find out how this is detected! - */ - m_rows_inserted++; - no_uncommitted_rows_update(1); - m_bulk_insert_not_flushed= TRUE; - if ((m_rows_to_insert == (ha_rows) 1) || - ((m_rows_inserted % m_bulk_insert_rows) == 0) || - m_primary_key_update || - set_blob_value) - { - // Send rows to NDB - DBUG_PRINT("info", ("Sending inserts to NDB, "\ - "rows_inserted: %d bulk_insert_rows: %d", - (int)m_rows_inserted, (int)m_bulk_insert_rows)); - - m_bulk_insert_not_flushed= FALSE; - if (m_transaction_on) - { - if (execute_no_commit(this,trans,FALSE) != 0) - { - m_skip_auto_increment= TRUE; - no_uncommitted_rows_execute_failure(); - DBUG_RETURN(ndb_err(trans)); - } - } - else - { - if (execute_commit(this,trans) != 0) - { - m_skip_auto_increment= TRUE; - no_uncommitted_rows_execute_failure(); - DBUG_RETURN(ndb_err(trans)); - } - if (trans->restart() != 0) - { - DBUG_ASSERT(0); - DBUG_RETURN(-1); - } - } - } - if ((has_auto_increment) && (m_skip_auto_increment)) - { - int ret_val; - if ((ret_val= set_auto_inc(table->next_number_field))) - { - DBUG_RETURN(ret_val); - } - } - m_skip_auto_increment= TRUE; - - DBUG_PRINT("exit",("ok")); - DBUG_RETURN(0); -} - - -/** - Compare if a key in a row has changed. -*/ - -int ha_ndbcluster::key_cmp(uint keynr, const uchar * old_row, - const uchar * new_row) -{ - KEY_PART_INFO *key_part=table->key_info[keynr].key_part; - KEY_PART_INFO *end=key_part+table->key_info[keynr].key_parts; - - for (; key_part != end ; key_part++) - { - if (key_part->null_bit) - { - if ((old_row[key_part->null_offset] & key_part->null_bit) != - (new_row[key_part->null_offset] & key_part->null_bit)) - return 1; - } - if (key_part->key_part_flag & (HA_BLOB_PART | HA_VAR_LENGTH_PART)) - { - - if (key_part->field->cmp_binary((old_row + key_part->offset), - (new_row + key_part->offset), - (ulong) key_part->length)) - return 1; - } - else - { - if (memcmp(old_row+key_part->offset, new_row+key_part->offset, - key_part->length)) - return 1; - } - } - return 0; -} - -/** - Update one record in NDB using primary key. -*/ - -int ha_ndbcluster::update_row(const uchar *old_data, uchar *new_data) -{ - THD *thd= table->in_use; - NdbTransaction *trans= m_active_trans; - NdbScanOperation* cursor= m_active_cursor; - NdbOperation *op; - uint i; - uint32 old_part_id= 0, new_part_id= 0; - int error; - longlong func_value; - bool pk_update= (table_share->primary_key != MAX_KEY && - key_cmp(table_share->primary_key, old_data, new_data)); - DBUG_ENTER("update_row"); - m_write_op= TRUE; - - /* - * If IGNORE the ignore constraint violations on primary and unique keys, - * but check that it is not part of INSERT ... ON DUPLICATE KEY UPDATE - */ - if (m_ignore_dup_key && (thd->lex->sql_command == SQLCOM_UPDATE || - thd->lex->sql_command == SQLCOM_UPDATE_MULTI)) - { - NDB_WRITE_OP write_op= (pk_update) ? NDB_PK_UPDATE : NDB_UPDATE; - int peek_res= peek_indexed_rows(new_data, write_op); - - if (!peek_res) - { - DBUG_RETURN(HA_ERR_FOUND_DUPP_KEY); - } - if (peek_res != HA_ERR_KEY_NOT_FOUND) - DBUG_RETURN(peek_res); - } - - ha_statistic_increment(&SSV::ha_update_count); - if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) - { - table->timestamp_field->set_time(); - bitmap_set_bit(table->write_set, table->timestamp_field->field_index); - } - - if (m_use_partition_function && - (error= get_parts_for_update(old_data, new_data, table->record[0], - m_part_info, &old_part_id, &new_part_id, - &func_value))) - { - m_part_info->err_value= func_value; - DBUG_RETURN(error); - } - - /* - * Check for update of primary key or partition change - * for special handling - */ - if (pk_update || old_part_id != new_part_id) - { - int read_res, insert_res, delete_res, undo_res; - - DBUG_PRINT("info", ("primary key update or partition change, " - "doing read+delete+insert")); - // Get all old fields, since we optimize away fields not in query - read_res= complemented_read(old_data, new_data, old_part_id); - if (read_res) - { - DBUG_PRINT("info", ("read failed")); - DBUG_RETURN(read_res); - } - // Delete old row - m_primary_key_update= TRUE; - delete_res= delete_row(old_data); - m_primary_key_update= FALSE; - if (delete_res) - { - DBUG_PRINT("info", ("delete failed")); - DBUG_RETURN(delete_res); - } - // Insert new row - DBUG_PRINT("info", ("delete succeded")); - m_primary_key_update= TRUE; - /* - If we are updating a primary key with auto_increment - then we need to update the auto_increment counter - */ - if (table->found_next_number_field && - bitmap_is_set(table->write_set, - table->found_next_number_field->field_index) && - (error= set_auto_inc(table->found_next_number_field))) - { - DBUG_RETURN(error); - } - insert_res= write_row(new_data); - m_primary_key_update= FALSE; - if (insert_res) - { - DBUG_PRINT("info", ("insert failed")); - if (trans->commitStatus() == NdbConnection::Started) - { - // Undo delete_row(old_data) - m_primary_key_update= TRUE; - undo_res= write_row((uchar *)old_data); - if (undo_res) - push_warning(current_thd, - MYSQL_ERROR::WARN_LEVEL_WARN, - undo_res, - "NDB failed undoing delete at primary key update"); - m_primary_key_update= FALSE; - } - DBUG_RETURN(insert_res); - } - DBUG_PRINT("info", ("delete+insert succeeded")); - DBUG_RETURN(0); - } - /* - If we are updating a unique key with auto_increment - then we need to update the auto_increment counter - */ - if (table->found_next_number_field && - bitmap_is_set(table->write_set, - table->found_next_number_field->field_index) && - (error= set_auto_inc(table->found_next_number_field))) - { - DBUG_RETURN(error); - } - if (cursor) - { - /* - We are scanning records and want to update the record - that was just found, call updateTuple on the cursor - to take over the lock to a new update operation - And thus setting the primary key of the record from - the active record in cursor - */ - DBUG_PRINT("info", ("Calling updateTuple on cursor")); - if (!(op= cursor->updateCurrentTuple())) - ERR_RETURN(trans->getNdbError()); - m_lock_tuple= FALSE; - m_ops_pending++; - if (uses_blob_value()) - m_blobs_pending= TRUE; - if (m_use_partition_function) - cursor->setPartitionId(new_part_id); - } - else - { - if (!(op= trans->getNdbOperation(m_table)) || - op->updateTuple() != 0) - ERR_RETURN(trans->getNdbError()); - - if (m_use_partition_function) - op->setPartitionId(new_part_id); - if (table_share->primary_key == MAX_KEY) - { - // This table has no primary key, use "hidden" primary key - DBUG_PRINT("info", ("Using hidden key")); - - // Require that the PK for this record has previously been - // read into m_ref - DBUG_DUMP("key", m_ref, NDB_HIDDEN_PRIMARY_KEY_LENGTH); - - if (set_hidden_key(op, table->s->fields, m_ref)) - ERR_RETURN(op->getNdbError()); - } - else - { - int res; - if ((res= set_primary_key_from_record(op, old_data))) - DBUG_RETURN(res); - } - } - - m_rows_changed++; - - // Set non-key attribute(s) - my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); - for (i= 0; i < table_share->fields; i++) - { - Field *field= table->field[i]; - if (bitmap_is_set(table->write_set, i) && - (!(field->flags & PRI_KEY_FLAG)) && - set_ndb_value(op, field, i, new_data - table->record[0])) - { - dbug_tmp_restore_column_map(table->read_set, old_map); - ERR_RETURN(op->getNdbError()); - } - } - dbug_tmp_restore_column_map(table->read_set, old_map); - - if (m_use_partition_function) - { - if (func_value >= INT_MAX32) - func_value= INT_MAX32; - uint32 part_func_value= (uint32)func_value; - uint no_fields= table_share->fields; - if (table_share->primary_key == MAX_KEY) - no_fields++; - op->setValue(no_fields, part_func_value); - } - - if (unlikely(m_slow_path)) - { - /* - ignore TNTO_NO_LOGGING for slave thd. It is used to indicate - log-slave-updates option. This is instead handled in the - injector thread, by looking explicitly at the - opt_log_slave_updates flag. - */ - Thd_ndb *thd_ndb= get_thd_ndb(thd); - if (thd->slave_thread) - op->setAnyValue(thd->server_id); - else if (thd_ndb->trans_options & TNTO_NO_LOGGING) - op->setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING); - } - /* - Execute update operation if we are not doing a scan for update - and there exist UPDATE AFTER triggers - */ - - if ((!cursor || m_update_cannot_batch) && - execute_no_commit(this,trans,false) != 0) { - no_uncommitted_rows_execute_failure(); - DBUG_RETURN(ndb_err(trans)); - } - - DBUG_RETURN(0); -} - - -/** - Delete one record from NDB, using primary key . -*/ - -int ha_ndbcluster::delete_row(const uchar *record) -{ - THD *thd= table->in_use; - NdbTransaction *trans= m_active_trans; - NdbScanOperation* cursor= m_active_cursor; - NdbOperation *op; - uint32 part_id; - int error; - DBUG_ENTER("delete_row"); - m_write_op= TRUE; - - ha_statistic_increment(&SSV::ha_delete_count); - m_rows_changed++; - - if (m_use_partition_function && - (error= get_part_for_delete(record, table->record[0], m_part_info, - &part_id))) - { - DBUG_RETURN(error); - } - - if (cursor) - { - /* - We are scanning records and want to delete the record - that was just found, call deleteTuple on the cursor - to take over the lock to a new delete operation - And thus setting the primary key of the record from - the active record in cursor - */ - DBUG_PRINT("info", ("Calling deleteTuple on cursor")); - if (cursor->deleteCurrentTuple() != 0) - ERR_RETURN(trans->getNdbError()); - m_lock_tuple= FALSE; - m_ops_pending++; - - if (m_use_partition_function) - cursor->setPartitionId(part_id); - - no_uncommitted_rows_update(-1); - - if (unlikely(m_slow_path)) - { - /* - ignore TNTO_NO_LOGGING for slave thd. It is used to indicate - log-slave-updates option. This is instead handled in the - injector thread, by looking explicitly at the - opt_log_slave_updates flag. - */ - Thd_ndb *thd_ndb= get_thd_ndb(thd); - if (thd->slave_thread) - ((NdbOperation *)trans->getLastDefinedOperation())-> - setAnyValue(thd->server_id); - else if (thd_ndb->trans_options & TNTO_NO_LOGGING) - ((NdbOperation *)trans->getLastDefinedOperation())-> - setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING); - } - if (!(m_primary_key_update || m_delete_cannot_batch)) - // If deleting from cursor, NoCommit will be handled in next_result - DBUG_RETURN(0); - } - else - { - - if (!(op=trans->getNdbOperation(m_table)) || - op->deleteTuple() != 0) - ERR_RETURN(trans->getNdbError()); - - if (m_use_partition_function) - op->setPartitionId(part_id); - - no_uncommitted_rows_update(-1); - - if (table_share->primary_key == MAX_KEY) - { - // This table has no primary key, use "hidden" primary key - DBUG_PRINT("info", ("Using hidden key")); - - if (set_hidden_key(op, table->s->fields, m_ref)) - ERR_RETURN(op->getNdbError()); - } - else - { - if ((error= set_primary_key_from_record(op, record))) - DBUG_RETURN(error); - } - - if (unlikely(m_slow_path)) - { - /* - ignore TNTO_NO_LOGGING for slave thd. It is used to indicate - log-slave-updates option. This is instead handled in the - injector thread, by looking explicitly at the - opt_log_slave_updates flag. - */ - Thd_ndb *thd_ndb= get_thd_ndb(thd); - if (thd->slave_thread) - op->setAnyValue(thd->server_id); - else if (thd_ndb->trans_options & TNTO_NO_LOGGING) - op->setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING); - } - } - - // Execute delete operation - if (execute_no_commit(this,trans,FALSE) != 0) { - no_uncommitted_rows_execute_failure(); - DBUG_RETURN(ndb_err(trans)); - } - DBUG_RETURN(0); -} - -/** - Unpack a record read from NDB. - - @param buf Buffer to store read row - - @note - The data for each row is read directly into the - destination buffer. This function is primarily - called in order to check if any fields should be - set to null. -*/ - -void ndb_unpack_record(TABLE *table, NdbValue *value, - MY_BITMAP *defined, uchar *buf) -{ - Field **p_field= table->field, *field= *p_field; - my_ptrdiff_t row_offset= (my_ptrdiff_t) (buf - table->record[0]); - my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->write_set); - DBUG_ENTER("ndb_unpack_record"); - - /* - Set the filler bits of the null byte, since they are - not touched in the code below. - - The filler bits are the MSBs in the last null byte - */ - if (table->s->null_bytes > 0) - buf[table->s->null_bytes - 1]|= 256U - (1U << - table->s->last_null_bit_pos); - /* - Set null flag(s) - */ - for ( ; field; - p_field++, value++, field= *p_field) - { - field->set_notnull(row_offset); - if ((*value).ptr) - { - if (!(field->flags & BLOB_FLAG)) - { - int is_null= (*value).rec->isNULL(); - if (is_null) - { - if (is_null > 0) - { - DBUG_PRINT("info",("[%u] NULL", - (*value).rec->getColumn()->getColumnNo())); - field->set_null(row_offset); - } - else - { - DBUG_PRINT("info",("[%u] UNDEFINED", - (*value).rec->getColumn()->getColumnNo())); - bitmap_clear_bit(defined, - (*value).rec->getColumn()->getColumnNo()); - } - } - else if (field->type() == MYSQL_TYPE_BIT) - { - Field_bit *field_bit= static_cast<Field_bit*>(field); - - /* - Move internal field pointer to point to 'buf'. Calling - the correct member function directly since we know the - type of the object. - */ - field_bit->Field_bit::move_field_offset(row_offset); - if (field->pack_length() < 5) - { - DBUG_PRINT("info", ("bit field H'%.8X", - (*value).rec->u_32_value())); - field_bit->Field_bit::store((longlong) (*value).rec->u_32_value(), - FALSE); - } - else - { - DBUG_PRINT("info", ("bit field H'%.8X%.8X", - *(Uint32 *)(*value).rec->aRef(), - *((Uint32 *)(*value).rec->aRef()+1))); -#ifdef WORDS_BIGENDIAN - /* lsw is stored first */ - Uint32 *buf= (Uint32 *)(*value).rec->aRef(); - field_bit->Field_bit::store((((longlong)*buf) - & 0x000000000FFFFFFFFLL) - | - ((((longlong)*(buf+1)) << 32) - & 0xFFFFFFFF00000000LL), - TRUE); -#else - field_bit->Field_bit::store((longlong) - (*value).rec->u_64_value(), TRUE); -#endif - } - /* - Move back internal field pointer to point to original - value (usually record[0]). - */ - field_bit->Field_bit::move_field_offset(-row_offset); - DBUG_PRINT("info",("[%u] SET", - (*value).rec->getColumn()->getColumnNo())); - DBUG_DUMP("info", field->ptr, field->pack_length()); - } - else - { - DBUG_PRINT("info",("[%u] SET", - (*value).rec->getColumn()->getColumnNo())); - DBUG_DUMP("info", field->ptr, field->pack_length()); - } - } - else - { - NdbBlob *ndb_blob= (*value).blob; - uint col_no = ndb_blob->getColumn()->getColumnNo(); - int isNull; - ndb_blob->getDefined(isNull); - if (isNull == 1) - { - DBUG_PRINT("info",("[%u] NULL", col_no)); - field->set_null(row_offset); - } - else if (isNull == -1) - { - DBUG_PRINT("info",("[%u] UNDEFINED", col_no)); - bitmap_clear_bit(defined, col_no); - } - else - { -#ifndef DBUG_OFF - // pointer vas set in get_ndb_blobs_value - Field_blob *field_blob= (Field_blob*)field; - uchar *ptr; - field_blob->get_ptr(&ptr, row_offset); - uint32 len= field_blob->get_length(row_offset); - DBUG_PRINT("info",("[%u] SET ptr: 0x%lx len: %u", - col_no, (long) ptr, len)); -#endif - } - } - } - } - dbug_tmp_restore_column_map(table->write_set, old_map); - DBUG_VOID_RETURN; -} - -void ha_ndbcluster::unpack_record(uchar *buf) -{ - ndb_unpack_record(table, m_value, 0, buf); -#ifndef DBUG_OFF - // Read and print all values that was fetched - if (table_share->primary_key == MAX_KEY) - { - // Table with hidden primary key - int hidden_no= table_share->fields; - const NDBTAB *tab= m_table; - char buff[22]; - const NDBCOL *hidden_col= tab->getColumn(hidden_no); - const NdbRecAttr* rec= m_value[hidden_no].rec; - DBUG_ASSERT(rec); - DBUG_PRINT("hidden", ("%d: %s \"%s\"", hidden_no, - hidden_col->getName(), - llstr(rec->u_64_value(), buff))); - } - //DBUG_EXECUTE("value", print_results();); -#endif -} - -/** - Utility function to print/dump the fetched field. - - To avoid unnecessary work, wrap in DBUG_EXECUTE as in: - DBUG_EXECUTE("value", print_results();); -*/ - -void ha_ndbcluster::print_results() -{ - DBUG_ENTER("print_results"); - -#ifndef DBUG_OFF - - char buf_type[MAX_FIELD_WIDTH], buf_val[MAX_FIELD_WIDTH]; - String type(buf_type, sizeof(buf_type), &my_charset_bin); - String val(buf_val, sizeof(buf_val), &my_charset_bin); - for (uint f= 0; f < table_share->fields; f++) - { - /* Use DBUG_PRINT since DBUG_FILE cannot be filtered out */ - char buf[2000]; - Field *field; - void* ptr; - NdbValue value; - - buf[0]= 0; - field= table->field[f]; - if (!(value= m_value[f]).ptr) - { - strmov(buf, "not read"); - goto print_value; - } - - ptr= field->ptr; - - if (! (field->flags & BLOB_FLAG)) - { - if (value.rec->isNULL()) - { - strmov(buf, "NULL"); - goto print_value; - } - type.length(0); - val.length(0); - field->sql_type(type); - field->val_str(&val); - my_snprintf(buf, sizeof(buf), "%s %s", type.c_ptr(), val.c_ptr()); - } - else - { - NdbBlob *ndb_blob= value.blob; - bool isNull= TRUE; - ndb_blob->getNull(isNull); - if (isNull) - strmov(buf, "NULL"); - } - -print_value: - DBUG_PRINT("value", ("%u,%s: %s", f, field->field_name, buf)); - } -#endif - DBUG_VOID_RETURN; -} - - -int ha_ndbcluster::index_init(uint index, bool sorted) -{ - DBUG_ENTER("ha_ndbcluster::index_init"); - DBUG_PRINT("enter", ("index: %u sorted: %d", index, sorted)); - active_index= index; - m_sorted= sorted; - /* - Locks are are explicitly released in scan - unless m_lock.type == TL_READ_HIGH_PRIORITY - and no sub-sequent call to unlock_row() - */ - m_lock_tuple= FALSE; - DBUG_RETURN(0); -} - - -int ha_ndbcluster::index_end() -{ - DBUG_ENTER("ha_ndbcluster::index_end"); - DBUG_RETURN(close_scan()); -} - -/** - Check if key contains null. -*/ -static -int -check_null_in_key(const KEY* key_info, const uchar *key, uint key_len) -{ - KEY_PART_INFO *curr_part, *end_part; - const uchar* end_ptr= key + key_len; - curr_part= key_info->key_part; - end_part= curr_part + key_info->key_parts; - - for (; curr_part != end_part && key < end_ptr; curr_part++) - { - if (curr_part->null_bit && *key) - return 1; - - key += curr_part->store_length; - } - return 0; -} - -int ha_ndbcluster::index_read(uchar *buf, - const uchar *key, uint key_len, - enum ha_rkey_function find_flag) -{ - key_range start_key; - bool descending= FALSE; - int rc; - DBUG_ENTER("ha_ndbcluster::index_read"); - DBUG_PRINT("enter", ("active_index: %u, key_len: %u, find_flag: %d", - active_index, key_len, find_flag)); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - - start_key.key= key; - start_key.length= key_len; - start_key.flag= find_flag; - descending= FALSE; - switch (find_flag) { - case HA_READ_KEY_OR_PREV: - case HA_READ_BEFORE_KEY: - case HA_READ_PREFIX_LAST: - case HA_READ_PREFIX_LAST_OR_PREV: - descending= TRUE; - break; - default: - break; - } - rc= read_range_first_to_buf(&start_key, 0, descending, - m_sorted, buf); - MYSQL_INDEX_READ_ROW_DONE(rc); - DBUG_RETURN(rc); -} - - -int ha_ndbcluster::index_next(uchar *buf) -{ - int rc; - DBUG_ENTER("ha_ndbcluster::index_next"); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - ha_statistic_increment(&SSV::ha_read_next_count); - rc= next_result(buf); - MYSQL_INDEX_READ_ROW_DONE(rc); - DBUG_RETURN(rc); -} - - -int ha_ndbcluster::index_prev(uchar *buf) -{ - int rc; - DBUG_ENTER("ha_ndbcluster::index_prev"); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - ha_statistic_increment(&SSV::ha_read_prev_count); - rc= next_result(buf); - MYSQL_INDEX_READ_ROW_DONE(rc); - DBUG_RETURN(rc); -} - - -int ha_ndbcluster::index_first(uchar *buf) -{ - int rc; - DBUG_ENTER("ha_ndbcluster::index_first"); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - ha_statistic_increment(&SSV::ha_read_first_count); - // Start the ordered index scan and fetch the first row - - // Only HA_READ_ORDER indexes get called by index_first - rc= ordered_index_scan(0, 0, TRUE, FALSE, buf, NULL); - MYSQL_INDEX_READ_ROW_DONE(rc); - DBUG_RETURN(rc); -} - - -int ha_ndbcluster::index_last(uchar *buf) -{ - int rc; - DBUG_ENTER("ha_ndbcluster::index_last"); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - ha_statistic_increment(&SSV::ha_read_last_count); - rc= ordered_index_scan(0, 0, TRUE, TRUE, buf, NULL); - MYSQL_INDEX_READ_ROW_DONE(rc); - DBUG_RETURN(rc); -} - -int ha_ndbcluster::index_read_last(uchar * buf, const uchar * key, uint key_len) -{ - DBUG_ENTER("ha_ndbcluster::index_read_last"); - DBUG_RETURN(index_read(buf, key, key_len, HA_READ_PREFIX_LAST)); -} - -int ha_ndbcluster::read_range_first_to_buf(const key_range *start_key, - const key_range *end_key, - bool desc, bool sorted, - uchar* buf) -{ - part_id_range part_spec; - ndb_index_type type= get_index_type(active_index); - const KEY* key_info= table->key_info+active_index; - int error; - DBUG_ENTER("ha_ndbcluster::read_range_first_to_buf"); - DBUG_PRINT("info", ("desc: %d, sorted: %d", desc, sorted)); - - if (m_use_partition_function) - { - get_partition_set(table, buf, active_index, start_key, &part_spec); - DBUG_PRINT("info", ("part_spec.start_part: %u part_spec.end_part: %u", - part_spec.start_part, part_spec.end_part)); - /* - If partition pruning has found no partition in set - we can return HA_ERR_END_OF_FILE - If partition pruning has found exactly one partition in set - we can optimize scan to run towards that partition only. - */ - if (part_spec.start_part > part_spec.end_part) - { - DBUG_RETURN(HA_ERR_END_OF_FILE); - } - else if (part_spec.start_part == part_spec.end_part) - { - /* - Only one partition is required to scan, if sorted is required we - don't need it any more since output from one ordered partitioned - index is always sorted. - */ - sorted= FALSE; - } - } - - m_write_op= FALSE; - switch (type){ - case PRIMARY_KEY_ORDERED_INDEX: - case PRIMARY_KEY_INDEX: - if (start_key && - start_key->length == key_info->key_length && - start_key->flag == HA_READ_KEY_EXACT) - { - if (m_active_cursor && (error= close_scan())) - DBUG_RETURN(error); - error= pk_read(start_key->key, start_key->length, buf, - part_spec.start_part); - DBUG_RETURN(error == HA_ERR_KEY_NOT_FOUND ? HA_ERR_END_OF_FILE : error); - } - break; - case UNIQUE_ORDERED_INDEX: - case UNIQUE_INDEX: - if (start_key && start_key->length == key_info->key_length && - start_key->flag == HA_READ_KEY_EXACT && - !check_null_in_key(key_info, start_key->key, start_key->length)) - { - if (m_active_cursor && (error= close_scan())) - DBUG_RETURN(error); - - error= unique_index_read(start_key->key, start_key->length, buf); - DBUG_RETURN(error == HA_ERR_KEY_NOT_FOUND ? HA_ERR_END_OF_FILE : error); - } - else if (type == UNIQUE_INDEX) - DBUG_RETURN(unique_index_scan(key_info, - start_key->key, - start_key->length, - buf)); - break; - default: - break; - } - // Start the ordered index scan and fetch the first row - DBUG_RETURN(ordered_index_scan(start_key, end_key, sorted, desc, buf, - &part_spec)); -} - -int ha_ndbcluster::read_range_first(const key_range *start_key, - const key_range *end_key, - bool eq_r, bool sorted) -{ - int rc; - uchar* buf= table->record[0]; - DBUG_ENTER("ha_ndbcluster::read_range_first"); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - rc= read_range_first_to_buf(start_key, end_key, FALSE, - sorted, buf); - MYSQL_INDEX_READ_ROW_DONE(rc); - DBUG_RETURN(rc); -} - -int ha_ndbcluster::read_range_next() -{ - int rc; - DBUG_ENTER("ha_ndbcluster::read_range_next"); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - rc= next_result(table->record[0]); - MYSQL_INDEX_READ_ROW_DONE(rc); - DBUG_RETURN(rc); -} - - -int ha_ndbcluster::rnd_init(bool scan) -{ - NdbScanOperation *cursor= m_active_cursor; - DBUG_ENTER("rnd_init"); - DBUG_PRINT("enter", ("scan: %d", scan)); - // Check if scan is to be restarted - if (cursor) - { - if (!scan) - DBUG_RETURN(1); - if (cursor->restart(m_force_send) != 0) - { - DBUG_ASSERT(0); - DBUG_RETURN(-1); - } - } - index_init(table_share->primary_key, 0); - DBUG_RETURN(0); -} - -int ha_ndbcluster::close_scan() -{ - NdbTransaction *trans= m_active_trans; - DBUG_ENTER("close_scan"); - - m_multi_cursor= 0; - if (!m_active_cursor && !m_multi_cursor) - DBUG_RETURN(0); - - NdbScanOperation *cursor= m_active_cursor ? m_active_cursor : m_multi_cursor; - - if (m_lock_tuple) - { - /* - Lock level m_lock.type either TL_WRITE_ALLOW_WRITE - (SELECT FOR UPDATE) or TL_READ_WITH_SHARED_LOCKS (SELECT - LOCK WITH SHARE MODE) and row was not explictly unlocked - with unlock_row() call - */ - NdbOperation *op; - // Lock row - DBUG_PRINT("info", ("Keeping lock on scanned row")); - - if (!(op= cursor->lockCurrentTuple())) - { - m_lock_tuple= FALSE; - ERR_RETURN(trans->getNdbError()); - } - m_ops_pending++; - } - m_lock_tuple= FALSE; - if (m_ops_pending) - { - /* - Take over any pending transactions to the - deleteing/updating transaction before closing the scan - */ - DBUG_PRINT("info", ("ops_pending: %ld", (long) m_ops_pending)); - if (execute_no_commit(this,trans,FALSE) != 0) { - no_uncommitted_rows_execute_failure(); - DBUG_RETURN(ndb_err(trans)); - } - m_ops_pending= 0; - } - - cursor->close(m_force_send, TRUE); - m_active_cursor= m_multi_cursor= NULL; - DBUG_RETURN(0); -} - -int ha_ndbcluster::rnd_end() -{ - DBUG_ENTER("rnd_end"); - DBUG_RETURN(close_scan()); -} - - -int ha_ndbcluster::rnd_next(uchar *buf) -{ - int rc; - DBUG_ENTER("rnd_next"); - MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str, - TRUE); - ha_statistic_increment(&SSV::ha_read_rnd_next_count); - - if (!m_active_cursor) - rc= full_table_scan(buf); - else - rc= next_result(buf); - MYSQL_READ_ROW_DONE(rc); - DBUG_RETURN(rc); -} - - -/** - An "interesting" record has been found and it's pk - retrieved by calling position. Now it's time to read - the record from db once again. -*/ - -int ha_ndbcluster::rnd_pos(uchar *buf, uchar *pos) -{ - int rc; - DBUG_ENTER("rnd_pos"); - MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str, - FALSE); - ha_statistic_increment(&SSV::ha_read_rnd_count); - // The primary key for the record is stored in pos - // Perform a pk_read using primary key "index" - { - part_id_range part_spec; - uint key_length= ref_length; - if (m_use_partition_function) - { - if (table_share->primary_key == MAX_KEY) - { - /* - The partition id has been fetched from ndb - and has been stored directly after the hidden key - */ - DBUG_DUMP("key+part", pos, key_length); - key_length= ref_length - sizeof(m_part_id); - part_spec.start_part= part_spec.end_part= *(uint32 *)(pos + key_length); - } - else - { - key_range key_spec; - KEY *key_info= table->key_info + table_share->primary_key; - key_spec.key= pos; - key_spec.length= key_length; - key_spec.flag= HA_READ_KEY_EXACT; - get_full_part_id_from_key(table, buf, key_info, - &key_spec, &part_spec); - DBUG_ASSERT(part_spec.start_part == part_spec.end_part); - } - DBUG_PRINT("info", ("partition id %u", part_spec.start_part)); - } - DBUG_DUMP("key", pos, key_length); - rc= pk_read(pos, key_length, buf, part_spec.start_part); - MYSQL_READ_ROW_DONE(rc); - DBUG_RETURN(rc); - } -} - - -/** - Store the primary key of this record in ref - variable, so that the row can be retrieved again later - using "reference" in rnd_pos. -*/ - -void ha_ndbcluster::position(const uchar *record) -{ - KEY *key_info; - KEY_PART_INFO *key_part; - KEY_PART_INFO *end; - uchar *buff; - uint key_length; - - DBUG_ENTER("position"); - - if (table_share->primary_key != MAX_KEY) - { - key_length= ref_length; - key_info= table->key_info + table_share->primary_key; - key_part= key_info->key_part; - end= key_part + key_info->key_parts; - buff= ref; - - for (; key_part != end; key_part++) - { - if (key_part->null_bit) { - /* Store 0 if the key part is a NULL part */ - if (record[key_part->null_offset] - & key_part->null_bit) { - *buff++= 1; - continue; - } - *buff++= 0; - } - - size_t len = key_part->length; - const uchar * ptr = record + key_part->offset; - Field *field = key_part->field; - if (field->type() == MYSQL_TYPE_VARCHAR) - { - if (((Field_varstring*)field)->length_bytes == 1) - { - /** - * Keys always use 2 bytes length - */ - buff[0] = ptr[0]; - buff[1] = 0; - memcpy(buff+2, ptr + 1, len); - } - else - { - memcpy(buff, ptr, len + 2); - } - len += 2; - } - else - { - memcpy(buff, ptr, len); - } - buff += len; - } - } - else - { - // No primary key, get hidden key - DBUG_PRINT("info", ("Getting hidden key")); - // If table has user defined partition save the partition id as well - if(m_use_partition_function) - { - DBUG_PRINT("info", ("Saving partition id %u", m_part_id)); - key_length= ref_length - sizeof(m_part_id); - memcpy(ref+key_length, (void *)&m_part_id, sizeof(m_part_id)); - } - else - key_length= ref_length; -#ifndef DBUG_OFF - int hidden_no= table->s->fields; - const NDBTAB *tab= m_table; - const NDBCOL *hidden_col= tab->getColumn(hidden_no); - DBUG_ASSERT(hidden_col->getPrimaryKey() && - hidden_col->getAutoIncrement() && - key_length == NDB_HIDDEN_PRIMARY_KEY_LENGTH); -#endif - memcpy(ref, m_ref, key_length); - } -#ifndef DBUG_OFF - if (table_share->primary_key == MAX_KEY && m_use_partition_function) - DBUG_DUMP("key+part", ref, key_length+sizeof(m_part_id)); -#endif - DBUG_DUMP("ref", ref, key_length); - DBUG_VOID_RETURN; -} - - -int ha_ndbcluster::info(uint flag) -{ - int result= 0; - DBUG_ENTER("info"); - DBUG_PRINT("enter", ("flag: %d", flag)); - - if (flag & HA_STATUS_POS) - DBUG_PRINT("info", ("HA_STATUS_POS")); - if (flag & HA_STATUS_NO_LOCK) - DBUG_PRINT("info", ("HA_STATUS_NO_LOCK")); - if (flag & HA_STATUS_TIME) - DBUG_PRINT("info", ("HA_STATUS_TIME")); - if (flag & HA_STATUS_VARIABLE) - { - DBUG_PRINT("info", ("HA_STATUS_VARIABLE")); - if (m_table_info) - { - if (m_ha_not_exact_count) - stats.records= 100; - else - result= records_update(); - } - else - { - if ((my_errno= check_ndb_connection())) - DBUG_RETURN(my_errno); - Ndb *ndb= get_ndb(); - ndb->setDatabaseName(m_dbname); - struct Ndb_statistics stat; - if (ndb->setDatabaseName(m_dbname)) - { - DBUG_RETURN(my_errno= HA_ERR_OUT_OF_MEM); - } - if (THDVAR(current_thd, use_exact_count) && - (result= ndb_get_table_statistics(this, TRUE, ndb, m_table, &stat)) - == 0) - { - stats.mean_rec_length= stat.row_size; - stats.data_file_length= stat.fragment_memory; - stats.records= stat.row_count; - } - else - { - stats.mean_rec_length= 0; - stats.records= 100; - } - } - } - if (flag & HA_STATUS_CONST) - { - DBUG_PRINT("info", ("HA_STATUS_CONST")); - set_rec_per_key(); - } - if (flag & HA_STATUS_ERRKEY) - { - DBUG_PRINT("info", ("HA_STATUS_ERRKEY")); - errkey= m_dupkey; - } - if (flag & HA_STATUS_AUTO) - { - DBUG_PRINT("info", ("HA_STATUS_AUTO")); - if (m_table && table->found_next_number_field) - { - if ((my_errno= check_ndb_connection())) - DBUG_RETURN(my_errno); - Ndb *ndb= get_ndb(); - Ndb_tuple_id_range_guard g(m_share); - - Uint64 auto_increment_value64; - if (ndb->readAutoIncrementValue(m_table, g.range, - auto_increment_value64) == -1) - { - const NdbError err= ndb->getNdbError(); - sql_print_error("Error %lu in readAutoIncrementValue(): %s", - (ulong) err.code, err.message); - stats.auto_increment_value= ~(ulonglong)0; - } - else - stats.auto_increment_value= (ulonglong)auto_increment_value64; - } - } - - if(result == -1) - result= HA_ERR_NO_CONNECTION; - - DBUG_RETURN(result); -} - - -void ha_ndbcluster::get_dynamic_partition_info(PARTITION_STATS *stat_info, - uint part_id) -{ - /* - This functions should be fixed. Suggested fix: to - implement ndb function which retrives the statistics - about ndb partitions. - */ - bzero((char*) stat_info, sizeof(PARTITION_STATS)); - return; -} - - -int ha_ndbcluster::extra(enum ha_extra_function operation) -{ - DBUG_ENTER("extra"); - switch (operation) { - case HA_EXTRA_IGNORE_DUP_KEY: /* Dup keys don't rollback everything*/ - DBUG_PRINT("info", ("HA_EXTRA_IGNORE_DUP_KEY")); - DBUG_PRINT("info", ("Ignoring duplicate key")); - m_ignore_dup_key= TRUE; - break; - case HA_EXTRA_NO_IGNORE_DUP_KEY: - DBUG_PRINT("info", ("HA_EXTRA_NO_IGNORE_DUP_KEY")); - m_ignore_dup_key= FALSE; - break; - case HA_EXTRA_IGNORE_NO_KEY: - DBUG_PRINT("info", ("HA_EXTRA_IGNORE_NO_KEY")); - DBUG_PRINT("info", ("Turning on AO_IgnoreError at Commit/NoCommit")); - m_ignore_no_key= TRUE; - break; - case HA_EXTRA_NO_IGNORE_NO_KEY: - DBUG_PRINT("info", ("HA_EXTRA_NO_IGNORE_NO_KEY")); - DBUG_PRINT("info", ("Turning on AO_IgnoreError at Commit/NoCommit")); - m_ignore_no_key= FALSE; - break; - case HA_EXTRA_WRITE_CAN_REPLACE: - DBUG_PRINT("info", ("HA_EXTRA_WRITE_CAN_REPLACE")); - if (!m_has_unique_index || - current_thd->slave_thread) /* always set if slave, quick fix for bug 27378 */ - { - DBUG_PRINT("info", ("Turning ON use of write instead of insert")); - m_use_write= TRUE; - } - break; - case HA_EXTRA_WRITE_CANNOT_REPLACE: - DBUG_PRINT("info", ("HA_EXTRA_WRITE_CANNOT_REPLACE")); - DBUG_PRINT("info", ("Turning OFF use of write instead of insert")); - m_use_write= FALSE; - break; - case HA_EXTRA_DELETE_CANNOT_BATCH: - DBUG_PRINT("info", ("HA_EXTRA_DELETE_CANNOT_BATCH")); - m_delete_cannot_batch= TRUE; - break; - case HA_EXTRA_UPDATE_CANNOT_BATCH: - DBUG_PRINT("info", ("HA_EXTRA_UPDATE_CANNOT_BATCH")); - m_update_cannot_batch= TRUE; - break; - default: - break; - } - - DBUG_RETURN(0); -} - - -int ha_ndbcluster::reset() -{ - DBUG_ENTER("ha_ndbcluster::reset"); - if (m_cond) - { - m_cond->cond_clear(); - } - - /* - Regular partition pruning will set the bitmap appropriately. - Some queries like ALTER TABLE doesn't use partition pruning and - thus the 'used_partitions' bitmap needs to be initialized - */ - if (m_part_info) - bitmap_set_all(&m_part_info->used_partitions); - - /* reset flags set by extra calls */ - m_ignore_dup_key= FALSE; - m_use_write= FALSE; - m_ignore_no_key= FALSE; - m_delete_cannot_batch= FALSE; - m_update_cannot_batch= FALSE; - - DBUG_RETURN(0); -} - - -/** - Start of an insert, remember number of rows to be inserted, it will - be used in write_row and get_autoincrement to send an optimal number - of rows in each roundtrip to the server. - - @param - rows number of rows to insert, 0 if unknown -*/ - -void ha_ndbcluster::start_bulk_insert(ha_rows rows) -{ - int bytes, batch; - const NDBTAB *tab= m_table; - - DBUG_ENTER("start_bulk_insert"); - DBUG_PRINT("enter", ("rows: %d", (int)rows)); - - m_rows_inserted= (ha_rows) 0; - if (!m_use_write && m_ignore_dup_key) - { - /* - compare if expression with that in write_row - we have a situation where peek_indexed_rows() will be called - so we cannot batch - */ - DBUG_PRINT("info", ("Batching turned off as duplicate key is " - "ignored by using peek_row")); - m_rows_to_insert= 1; - m_bulk_insert_rows= 1; - DBUG_VOID_RETURN; - } - if (rows == (ha_rows) 0) - { - /* We don't know how many will be inserted, guess */ - m_rows_to_insert= m_autoincrement_prefetch; - } - else - m_rows_to_insert= rows; - - /* - Calculate how many rows that should be inserted - per roundtrip to NDB. This is done in order to minimize the - number of roundtrips as much as possible. However performance will - degrade if too many bytes are inserted, thus it's limited by this - calculation. - */ - const int bytesperbatch= 8192; - bytes= 12 + tab->getRowSizeInBytes() + 4 * tab->getNoOfColumns(); - batch= bytesperbatch/bytes; - batch= batch == 0 ? 1 : batch; - DBUG_PRINT("info", ("batch: %d, bytes: %d", batch, bytes)); - m_bulk_insert_rows= batch; - - DBUG_VOID_RETURN; -} - -/** - End of an insert. -*/ -int ha_ndbcluster::end_bulk_insert() -{ - int error= 0; - DBUG_ENTER("end_bulk_insert"); - - // Check if last inserts need to be flushed - if (m_bulk_insert_not_flushed) - { - NdbTransaction *trans= m_active_trans; - // Send rows to NDB - DBUG_PRINT("info", ("Sending inserts to NDB, "\ - "rows_inserted: %d bulk_insert_rows: %d", - (int) m_rows_inserted, (int) m_bulk_insert_rows)); - m_bulk_insert_not_flushed= FALSE; - if (m_transaction_on) - { - if (execute_no_commit(this, trans,FALSE) != 0) - { - no_uncommitted_rows_execute_failure(); - my_errno= error= ndb_err(trans); - } - } - else - { - if (execute_commit(this, trans) != 0) - { - no_uncommitted_rows_execute_failure(); - my_errno= error= ndb_err(trans); - } - else - { - int res __attribute__((unused))= trans->restart(); - DBUG_ASSERT(res == 0); - } - } - } - - m_rows_inserted= (ha_rows) 0; - m_rows_to_insert= (ha_rows) 1; - DBUG_RETURN(error); -} - - -int ha_ndbcluster::extra_opt(enum ha_extra_function operation, ulong cache_size) -{ - DBUG_ENTER("extra_opt"); - DBUG_PRINT("enter", ("cache_size: %lu", cache_size)); - DBUG_RETURN(extra(operation)); -} - -static const char *ha_ndbcluster_exts[] = { - ha_ndb_ext, - NullS -}; - -const char** ha_ndbcluster::bas_ext() const -{ - return ha_ndbcluster_exts; -} - -/** - How many seeks it will take to read through the table. - - This is to be comparable to the number returned by records_in_range so - that we can decide if we should scan the table or use keys. -*/ - -double ha_ndbcluster::scan_time() -{ - DBUG_ENTER("ha_ndbcluster::scan_time()"); - double res= rows2double(stats.records*1000); - DBUG_PRINT("exit", ("table: %s value: %f", - m_tabname, res)); - DBUG_RETURN(res); -} - -/* - Convert MySQL table locks into locks supported by Ndb Cluster. - Note that MySQL Cluster does currently not support distributed - table locks, so to be safe one should set cluster in Single - User Mode, before relying on table locks when updating tables - from several MySQL servers -*/ - -THR_LOCK_DATA **ha_ndbcluster::store_lock(THD *thd, - THR_LOCK_DATA **to, - enum thr_lock_type lock_type) -{ - DBUG_ENTER("store_lock"); - if (lock_type != TL_IGNORE && m_lock.type == TL_UNLOCK) - { - - /* If we are not doing a LOCK TABLE, then allow multiple - writers */ - - /* Since NDB does not currently have table locks - this is treated as a ordinary lock */ - - if ((lock_type >= TL_WRITE_CONCURRENT_INSERT && - lock_type <= TL_WRITE) && !thd->in_lock_tables) - lock_type= TL_WRITE_ALLOW_WRITE; - - /* In queries of type INSERT INTO t1 SELECT ... FROM t2 ... - MySQL would use the lock TL_READ_NO_INSERT on t2, and that - would conflict with TL_WRITE_ALLOW_WRITE, blocking all inserts - to t2. Convert the lock to a normal read lock to allow - concurrent inserts to t2. */ - - if (lock_type == TL_READ_NO_INSERT && !thd->in_lock_tables) - lock_type= TL_READ; - - m_lock.type=lock_type; - } - *to++= &m_lock; - - DBUG_PRINT("exit", ("lock_type: %d", lock_type)); - - DBUG_RETURN(to); -} - -#ifndef DBUG_OFF -#define PRINT_OPTION_FLAGS(t) { \ - if (t->variables.option_bits & OPTION_NOT_AUTOCOMMIT) \ - DBUG_PRINT("thd->variables.option_bits", ("OPTION_NOT_AUTOCOMMIT")); \ - if (t->variables.option_bits & OPTION_BEGIN) \ - DBUG_PRINT("thd->variables.option_bits", ("OPTION_BEGIN")); \ - if (t->variables.option_bits & OPTION_TABLE_LOCK) \ - DBUG_PRINT("thd->variables.option_bits", ("OPTION_TABLE_LOCK")); \ -} -#else -#define PRINT_OPTION_FLAGS(t) -#endif - - -/* - As MySQL will execute an external lock for every new table it uses - we can use this to start the transactions. - If we are in auto_commit mode we just need to start a transaction - for the statement, this will be stored in thd_ndb.stmt. - If not, we have to start a master transaction if there doesn't exist - one from before, this will be stored in thd_ndb.all - - When a table lock is held one transaction will be started which holds - the table lock and for each statement a hupp transaction will be started - If we are locking the table then: - - save the NdbDictionary::Table for easy access - - save reference to table statistics - - refresh list of the indexes for the table if needed (if altered) - */ - -#ifdef HAVE_NDB_BINLOG -extern Master_info *active_mi; -static int ndbcluster_update_apply_status(THD *thd, int do_update) -{ - Thd_ndb *thd_ndb= get_thd_ndb(thd); - Ndb *ndb= thd_ndb->ndb; - NDBDICT *dict= ndb->getDictionary(); - const NDBTAB *ndbtab; - NdbTransaction *trans= thd_ndb->trans; - ndb->setDatabaseName(NDB_REP_DB); - Ndb_table_guard ndbtab_g(dict, NDB_APPLY_TABLE); - if (!(ndbtab= ndbtab_g.get_table())) - { - return -1; - } - NdbOperation *op= 0; - int r= 0; - r|= (op= trans->getNdbOperation(ndbtab)) == 0; - DBUG_ASSERT(r == 0); - if (do_update) - r|= op->updateTuple(); - else - r|= op->writeTuple(); - DBUG_ASSERT(r == 0); - // server_id - r|= op->equal(0u, (Uint32)thd->server_id); - DBUG_ASSERT(r == 0); - if (!do_update) - { - // epoch - r|= op->setValue(1u, (Uint64)0); - DBUG_ASSERT(r == 0); - } - // log_name - char tmp_buf[FN_REFLEN]; - ndb_pack_varchar(ndbtab->getColumn(2u), tmp_buf, - active_mi->rli.group_master_log_name, - strlen(active_mi->rli.group_master_log_name)); - r|= op->setValue(2u, tmp_buf); - DBUG_ASSERT(r == 0); - // start_pos - r|= op->setValue(3u, (Uint64)active_mi->rli.group_master_log_pos); - DBUG_ASSERT(r == 0); - // end_pos - r|= op->setValue(4u, (Uint64)active_mi->rli.group_master_log_pos + - ((Uint64)active_mi->rli.future_event_relay_log_pos - - (Uint64)active_mi->rli.group_relay_log_pos)); - DBUG_ASSERT(r == 0); - return 0; -} -#endif /* HAVE_NDB_BINLOG */ - -void ha_ndbcluster::transaction_checks(THD *thd) -{ - if (thd->lex->sql_command == SQLCOM_LOAD) - { - m_transaction_on= FALSE; - /* Would be simpler if has_transactions() didn't always say "yes" */ - thd->transaction.all.modified_non_trans_table= - thd->transaction.stmt.modified_non_trans_table= TRUE; - } - else if (!thd->transaction.on) - m_transaction_on= FALSE; - else - m_transaction_on= THDVAR(thd, use_transactions); -} - -int ha_ndbcluster::start_statement(THD *thd, - Thd_ndb *thd_ndb, - Ndb *ndb) -{ - DBUG_ENTER("ha_ndbcluster::start_statement"); - PRINT_OPTION_FLAGS(thd); - - trans_register_ha(thd, FALSE, ndbcluster_hton); - if (!thd_ndb->trans) - { - if (thd->in_multi_stmt_transaction_mode()) - trans_register_ha(thd, TRUE, ndbcluster_hton); - DBUG_PRINT("trans",("Starting transaction")); - thd_ndb->trans= ndb->startTransaction(); - if (thd_ndb->trans == NULL) - ERR_RETURN(ndb->getNdbError()); - thd_ndb->init_open_tables(); - thd_ndb->query_state&= NDB_QUERY_NORMAL; - thd_ndb->trans_options= 0; - thd_ndb->m_slow_path= FALSE; - if (!(thd->variables.option_bits & OPTION_BIN_LOG) || - thd->variables.binlog_format == BINLOG_FORMAT_STMT) - { - thd_ndb->trans_options|= TNTO_NO_LOGGING; - thd_ndb->m_slow_path= TRUE; - } - else if (thd->slave_thread) - thd_ndb->m_slow_path= TRUE; - } - /* - If this is the start of a LOCK TABLE, a table look - should be taken on the table in NDB - - Check if it should be read or write lock - */ - if (thd->variables.option_bits & OPTION_TABLE_LOCK) - { - //lockThisTable(); - DBUG_PRINT("info", ("Locking the table..." )); - } - DBUG_RETURN(0); -} - -int ha_ndbcluster::init_handler_for_statement(THD *thd, Thd_ndb *thd_ndb) -{ - /* - This is the place to make sure this handler instance - has a started transaction. - - The transaction is started by the first handler on which - MySQL Server calls external lock - - Other handlers in the same stmt or transaction should use - the same NDB transaction. This is done by setting up the m_active_trans - pointer to point to the NDB transaction. - */ - - DBUG_ENTER("ha_ndbcluster::init_handler_for_statement"); - // store thread specific data first to set the right context - m_force_send= THDVAR(thd, force_send); - m_ha_not_exact_count= !THDVAR(thd, use_exact_count); - m_autoincrement_prefetch= - (THDVAR(thd, autoincrement_prefetch_sz) > - DEFAULT_AUTO_PREFETCH) ? - (ha_rows) THDVAR(thd, autoincrement_prefetch_sz) - : (ha_rows) DEFAULT_AUTO_PREFETCH; - m_active_trans= thd_ndb->trans; - DBUG_ASSERT(m_active_trans); - // Start of transaction - m_rows_changed= 0; - m_ops_pending= 0; - m_slow_path= thd_ndb->m_slow_path; -#ifdef HAVE_NDB_BINLOG - if (unlikely(m_slow_path)) - { - if (m_share == ndb_apply_status_share && thd->slave_thread) - thd_ndb->trans_options|= TNTO_INJECTED_APPLY_STATUS; - } -#endif - - if (thd->in_multi_stmt_transaction_mode()) - { - const void *key= m_table; - HASH_SEARCH_STATE state; - THD_NDB_SHARE *thd_ndb_share= - (THD_NDB_SHARE*)my_hash_first(&thd_ndb->open_tables, (uchar *)&key, sizeof(key), &state); - while (thd_ndb_share && thd_ndb_share->key != key) - thd_ndb_share= (THD_NDB_SHARE*)my_hash_next(&thd_ndb->open_tables, (uchar *)&key, sizeof(key), &state); - if (thd_ndb_share == 0) - { - thd_ndb_share= (THD_NDB_SHARE *) alloc_root(&thd->transaction.mem_root, - sizeof(THD_NDB_SHARE)); - if (!thd_ndb_share) - { - mem_alloc_error(sizeof(THD_NDB_SHARE)); - DBUG_RETURN(1); - } - thd_ndb_share->key= key; - thd_ndb_share->stat.last_count= thd_ndb->count; - thd_ndb_share->stat.no_uncommitted_rows_count= 0; - thd_ndb_share->stat.records= ~(ha_rows)0; - my_hash_insert(&thd_ndb->open_tables, (uchar *)thd_ndb_share); - } - else if (thd_ndb_share->stat.last_count != thd_ndb->count) - { - thd_ndb_share->stat.last_count= thd_ndb->count; - thd_ndb_share->stat.no_uncommitted_rows_count= 0; - thd_ndb_share->stat.records= ~(ha_rows)0; - } - DBUG_PRINT("exit", ("thd_ndb_share: 0x%lx key: 0x%lx", - (long) thd_ndb_share, (long) key)); - m_table_info= &thd_ndb_share->stat; - } - else - { - struct Ndb_local_table_statistics &stat= m_table_info_instance; - stat.last_count= thd_ndb->count; - stat.no_uncommitted_rows_count= 0; - stat.records= ~(ha_rows)0; - m_table_info= &stat; - } - DBUG_RETURN(0); -} - -int ha_ndbcluster::external_lock(THD *thd, int lock_type) -{ - int error=0; - DBUG_ENTER("external_lock"); - - /* - Check that this handler instance has a connection - set up to the Ndb object of thd - */ - if (check_ndb_connection(thd)) - DBUG_RETURN(1); - - Thd_ndb *thd_ndb= get_thd_ndb(thd); - Ndb *ndb= thd_ndb->ndb; - - DBUG_PRINT("enter", ("this: 0x%lx thd: 0x%lx thd_ndb: 0x%lx " - "thd_ndb->lock_count: %d", - (long) this, (long) thd, (long) thd_ndb, - thd_ndb->lock_count)); - - if (lock_type != F_UNLCK) - { - DBUG_PRINT("info", ("lock_type != F_UNLCK")); - transaction_checks(thd); - if (!thd_ndb->lock_count++) - { - if ((error= start_statement(thd, thd_ndb, ndb))) - goto error; - } - if ((error= init_handler_for_statement(thd, thd_ndb))) - goto error; - DBUG_RETURN(0); - } - else - { - DBUG_PRINT("info", ("lock_type == F_UNLCK")); - - if (opt_ndb_cache_check_time && m_rows_changed) - { - DBUG_PRINT("info", ("Rows has changed and util thread is running")); - if (thd->in_multi_stmt_transaction_mode()) - { - DBUG_PRINT("info", ("Add share to list of tables to be invalidated")); - /* NOTE push_back allocates memory using transactions mem_root! */ - thd_ndb->changed_tables.push_back(m_share, &thd->transaction.mem_root); - } - - mysql_mutex_lock(&m_share->mutex); - DBUG_PRINT("info", ("Invalidating commit_count")); - m_share->commit_count= 0; - m_share->commit_count_lock++; - mysql_mutex_unlock(&m_share->mutex); - } - - if (!--thd_ndb->lock_count) - { - DBUG_PRINT("trans", ("Last external_lock")); - PRINT_OPTION_FLAGS(thd); - - if (!thd->in_multi_stmt_transaction_mode()) - { - if (thd_ndb->trans) - { - /* - Unlock is done without a transaction commit / rollback. - This happens if the thread didn't update any rows - We must in this case close the transaction to release resources - */ - DBUG_PRINT("trans",("ending non-updating transaction")); - ndb->closeTransaction(thd_ndb->trans); - thd_ndb->trans= NULL; - } - } - } - m_table_info= NULL; - - /* - This is the place to make sure this handler instance - no longer are connected to the active transaction. - - And since the handler is no longer part of the transaction - it can't have open cursors, ops or blobs pending. - */ - m_active_trans= NULL; - - if (m_active_cursor) - DBUG_PRINT("warning", ("m_active_cursor != NULL")); - m_active_cursor= NULL; - - if (m_multi_cursor) - DBUG_PRINT("warning", ("m_multi_cursor != NULL")); - m_multi_cursor= NULL; - - if (m_blobs_pending) - DBUG_PRINT("warning", ("blobs_pending != 0")); - m_blobs_pending= 0; - - if (m_ops_pending) - DBUG_PRINT("warning", ("ops_pending != 0L")); - m_ops_pending= 0; - DBUG_RETURN(0); - } -error: - thd_ndb->lock_count--; - DBUG_RETURN(error); -} - -/** - Unlock the last row read in an open scan. - Rows are unlocked by default in ndb, but - for SELECT FOR UPDATE and SELECT LOCK WIT SHARE MODE - locks are kept if unlock_row() is not called. -*/ - -void ha_ndbcluster::unlock_row() -{ - DBUG_ENTER("unlock_row"); - - DBUG_PRINT("info", ("Unlocking row")); - m_lock_tuple= FALSE; - DBUG_VOID_RETURN; -} - -/** - Start a transaction for running a statement if one is not - already running in a transaction. This will be the case in - a BEGIN; COMMIT; block - When using LOCK TABLE's external_lock will start a transaction - since ndb does not currently does not support table locking. -*/ - -int ha_ndbcluster::start_stmt(THD *thd, thr_lock_type lock_type) -{ - int error=0; - DBUG_ENTER("start_stmt"); - - Thd_ndb *thd_ndb= get_thd_ndb(thd); - transaction_checks(thd); - if (!thd_ndb->start_stmt_count++) - { - Ndb *ndb= thd_ndb->ndb; - if ((error= start_statement(thd, thd_ndb, ndb))) - goto error; - } - if ((error= init_handler_for_statement(thd, thd_ndb))) - goto error; - DBUG_RETURN(0); -error: - thd_ndb->start_stmt_count--; - DBUG_RETURN(error); -} - - -/** - Commit a transaction started in NDB. -*/ - -static int ndbcluster_commit(handlerton *hton, THD *thd, bool all) -{ - int res= 0; - Thd_ndb *thd_ndb= get_thd_ndb(thd); - Ndb *ndb= thd_ndb->ndb; - NdbTransaction *trans= thd_ndb->trans; - - DBUG_ENTER("ndbcluster_commit"); - DBUG_ASSERT(ndb); - PRINT_OPTION_FLAGS(thd); - DBUG_PRINT("enter", ("Commit %s", (all ? "all" : "stmt"))); - thd_ndb->start_stmt_count= 0; - if (trans == NULL || (!all && thd->in_multi_stmt_transaction_mode())) - { - /* - An odditity in the handler interface is that commit on handlerton - is called to indicate end of statement only in cases where - autocommit isn't used and the all flag isn't set. - - We also leave quickly when a transaction haven't even been started, - in this case we are safe that no clean up is needed. In this case - the MySQL Server could handle the query without contacting the - NDB kernel. - */ - DBUG_PRINT("info", ("Commit before start or end-of-statement only")); - DBUG_RETURN(0); - } - -#ifdef HAVE_NDB_BINLOG - if (unlikely(thd_ndb->m_slow_path)) - { - if (thd->slave_thread) - ndbcluster_update_apply_status - (thd, thd_ndb->trans_options & TNTO_INJECTED_APPLY_STATUS); - } -#endif /* HAVE_NDB_BINLOG */ - - if (execute_commit(thd,trans) != 0) - { - const NdbError err= trans->getNdbError(); - const NdbOperation *error_op= trans->getNdbErrorOperation(); - set_ndb_err(thd, err); - res= ndb_to_mysql_error(&err); - if (res != -1) - ndbcluster_print_error(res, error_op); - } - ndb->closeTransaction(trans); - thd_ndb->trans= NULL; - - /* Clear commit_count for tables changed by transaction */ - NDB_SHARE* share; - List_iterator_fast<NDB_SHARE> it(thd_ndb->changed_tables); - while ((share= it++)) - { - mysql_mutex_lock(&share->mutex); - DBUG_PRINT("info", ("Invalidate commit_count for %s, share->commit_count: %lu", - share->table_name, (ulong) share->commit_count)); - share->commit_count= 0; - share->commit_count_lock++; - mysql_mutex_unlock(&share->mutex); - } - thd_ndb->changed_tables.empty(); - - DBUG_RETURN(res); -} - - -/** - Rollback a transaction started in NDB. -*/ - -static int ndbcluster_rollback(handlerton *hton, THD *thd, bool all) -{ - int res= 0; - Thd_ndb *thd_ndb= get_thd_ndb(thd); - Ndb *ndb= thd_ndb->ndb; - NdbTransaction *trans= thd_ndb->trans; - - DBUG_ENTER("ndbcluster_rollback"); - DBUG_ASSERT(ndb); - thd_ndb->start_stmt_count= 0; - if (trans == NULL || (!all && - thd->in_multi_stmt_transaction_mode())) - { - /* Ignore end-of-statement until real rollback or commit is called */ - DBUG_PRINT("info", ("Rollback before start or end-of-statement only")); - DBUG_RETURN(0); - } - - if (trans->execute(NdbTransaction::Rollback) != 0) - { - const NdbError err= trans->getNdbError(); - const NdbOperation *error_op= trans->getNdbErrorOperation(); - set_ndb_err(thd, err); - res= ndb_to_mysql_error(&err); - if (res != -1) - ndbcluster_print_error(res, error_op); - } - ndb->closeTransaction(trans); - thd_ndb->trans= NULL; - - /* Clear list of tables changed by transaction */ - thd_ndb->changed_tables.empty(); - - DBUG_RETURN(res); -} - - -/** - Define NDB column based on Field. - - Not member of ha_ndbcluster because NDBCOL cannot be declared. - - MySQL text types with character set "binary" are mapped to true - NDB binary types without a character set. This may change. - - @return - Returns 0 or mysql error code. -*/ - -static int create_ndb_column(NDBCOL &col, - Field *field, - HA_CREATE_INFO *info) -{ - // Set name - if (col.setName(field->field_name)) - { - return (my_errno= errno); - } - // Get char set - CHARSET_INFO *cs= field->charset(); - // Set type and sizes - const enum enum_field_types mysql_type= field->real_type(); - switch (mysql_type) { - // Numeric types - case MYSQL_TYPE_TINY: - if (field->flags & UNSIGNED_FLAG) - col.setType(NDBCOL::Tinyunsigned); - else - col.setType(NDBCOL::Tinyint); - col.setLength(1); - break; - case MYSQL_TYPE_SHORT: - if (field->flags & UNSIGNED_FLAG) - col.setType(NDBCOL::Smallunsigned); - else - col.setType(NDBCOL::Smallint); - col.setLength(1); - break; - case MYSQL_TYPE_LONG: - if (field->flags & UNSIGNED_FLAG) - col.setType(NDBCOL::Unsigned); - else - col.setType(NDBCOL::Int); - col.setLength(1); - break; - case MYSQL_TYPE_INT24: - if (field->flags & UNSIGNED_FLAG) - col.setType(NDBCOL::Mediumunsigned); - else - col.setType(NDBCOL::Mediumint); - col.setLength(1); - break; - case MYSQL_TYPE_LONGLONG: - if (field->flags & UNSIGNED_FLAG) - col.setType(NDBCOL::Bigunsigned); - else - col.setType(NDBCOL::Bigint); - col.setLength(1); - break; - case MYSQL_TYPE_FLOAT: - col.setType(NDBCOL::Float); - col.setLength(1); - break; - case MYSQL_TYPE_DOUBLE: - col.setType(NDBCOL::Double); - col.setLength(1); - break; - case MYSQL_TYPE_DECIMAL: - { - Field_decimal *f= (Field_decimal*)field; - uint precision= f->pack_length(); - uint scale= f->decimals(); - if (field->flags & UNSIGNED_FLAG) - { - col.setType(NDBCOL::Olddecimalunsigned); - precision-= (scale > 0); - } - else - { - col.setType(NDBCOL::Olddecimal); - precision-= 1 + (scale > 0); - } - col.setPrecision(precision); - col.setScale(scale); - col.setLength(1); - } - break; - case MYSQL_TYPE_NEWDECIMAL: - { - Field_new_decimal *f= (Field_new_decimal*)field; - uint precision= f->precision; - uint scale= f->decimals(); - if (field->flags & UNSIGNED_FLAG) - { - col.setType(NDBCOL::Decimalunsigned); - } - else - { - col.setType(NDBCOL::Decimal); - } - col.setPrecision(precision); - col.setScale(scale); - col.setLength(1); - } - break; - // Date types - case MYSQL_TYPE_DATETIME: - col.setType(NDBCOL::Datetime); - col.setLength(1); - break; - case MYSQL_TYPE_DATE: // ? - col.setType(NDBCOL::Char); - col.setLength(field->pack_length()); - break; - case MYSQL_TYPE_NEWDATE: - col.setType(NDBCOL::Date); - col.setLength(1); - break; - case MYSQL_TYPE_TIME: - col.setType(NDBCOL::Time); - col.setLength(1); - break; - case MYSQL_TYPE_YEAR: - col.setType(NDBCOL::Year); - col.setLength(1); - break; - case MYSQL_TYPE_TIMESTAMP: - col.setType(NDBCOL::Timestamp); - col.setLength(1); - break; - // Char types - case MYSQL_TYPE_STRING: - if (field->pack_length() == 0) - { - col.setType(NDBCOL::Bit); - col.setLength(1); - } - else if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin) - { - col.setType(NDBCOL::Binary); - col.setLength(field->pack_length()); - } - else - { - col.setType(NDBCOL::Char); - col.setCharset(cs); - col.setLength(field->pack_length()); - } - break; - case MYSQL_TYPE_VAR_STRING: // ? - case MYSQL_TYPE_VARCHAR: - { - Field_varstring* f= (Field_varstring*)field; - if (f->length_bytes == 1) - { - if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin) - col.setType(NDBCOL::Varbinary); - else { - col.setType(NDBCOL::Varchar); - col.setCharset(cs); - } - } - else if (f->length_bytes == 2) - { - if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin) - col.setType(NDBCOL::Longvarbinary); - else { - col.setType(NDBCOL::Longvarchar); - col.setCharset(cs); - } - } - else - { - return HA_ERR_UNSUPPORTED; - } - col.setLength(field->field_length); - } - break; - // Blob types (all come in as MYSQL_TYPE_BLOB) - mysql_type_tiny_blob: - case MYSQL_TYPE_TINY_BLOB: - if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin) - col.setType(NDBCOL::Blob); - else { - col.setType(NDBCOL::Text); - col.setCharset(cs); - } - col.setInlineSize(256); - // No parts - col.setPartSize(0); - col.setStripeSize(0); - break; - //mysql_type_blob: - case MYSQL_TYPE_GEOMETRY: - case MYSQL_TYPE_BLOB: - if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin) - col.setType(NDBCOL::Blob); - else { - col.setType(NDBCOL::Text); - col.setCharset(cs); - } - { - Field_blob *field_blob= (Field_blob *)field; - /* - * max_data_length is 2^8-1, 2^16-1, 2^24-1 for tiny, blob, medium. - * Tinyblob gets no blob parts. The other cases are just a crude - * way to control part size and striping. - * - * In mysql blob(256) is promoted to blob(65535) so it does not - * in fact fit "inline" in NDB. - */ - if (field_blob->max_data_length() < (1 << 8)) - goto mysql_type_tiny_blob; - else if (field_blob->max_data_length() < (1 << 16)) - { - col.setInlineSize(256); - col.setPartSize(2000); - col.setStripeSize(16); - } - else if (field_blob->max_data_length() < (1 << 24)) - goto mysql_type_medium_blob; - else - goto mysql_type_long_blob; - } - break; - mysql_type_medium_blob: - case MYSQL_TYPE_MEDIUM_BLOB: - if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin) - col.setType(NDBCOL::Blob); - else { - col.setType(NDBCOL::Text); - col.setCharset(cs); - } - col.setInlineSize(256); - col.setPartSize(4000); - col.setStripeSize(8); - break; - mysql_type_long_blob: - case MYSQL_TYPE_LONG_BLOB: - if ((field->flags & BINARY_FLAG) && cs == &my_charset_bin) - col.setType(NDBCOL::Blob); - else { - col.setType(NDBCOL::Text); - col.setCharset(cs); - } - col.setInlineSize(256); - col.setPartSize(8000); - col.setStripeSize(4); - break; - // Other types - case MYSQL_TYPE_ENUM: - col.setType(NDBCOL::Char); - col.setLength(field->pack_length()); - break; - case MYSQL_TYPE_SET: - col.setType(NDBCOL::Char); - col.setLength(field->pack_length()); - break; - case MYSQL_TYPE_BIT: - { - int no_of_bits= field->field_length; - col.setType(NDBCOL::Bit); - if (!no_of_bits) - col.setLength(1); - else - col.setLength(no_of_bits); - break; - } - case MYSQL_TYPE_NULL: - goto mysql_type_unsupported; - mysql_type_unsupported: - default: - return HA_ERR_UNSUPPORTED; - } - // Set nullable and pk - col.setNullable(field->maybe_null()); - col.setPrimaryKey(field->flags & PRI_KEY_FLAG); - // Set autoincrement - if (field->flags & AUTO_INCREMENT_FLAG) - { -#ifndef DBUG_OFF - char buff[22]; -#endif - col.setAutoIncrement(TRUE); - ulonglong value= info->auto_increment_value ? - info->auto_increment_value : (ulonglong) 1; - DBUG_PRINT("info", ("Autoincrement key, initial: %s", llstr(value, buff))); - col.setAutoIncrementInitialValue(value); - } - else - col.setAutoIncrement(FALSE); - return 0; -} - -/** - Create a table in NDB Cluster -*/ - -int ha_ndbcluster::create(const char *name, - TABLE *form, - HA_CREATE_INFO *create_info) -{ - THD *thd= current_thd; - NDBTAB tab; - NDBCOL col; - size_t pack_length, length; - uint i, pk_length= 0; - uchar *data= NULL, *pack_data= NULL; - bool create_from_engine= (create_info->table_options & HA_OPTION_CREATE_FROM_ENGINE); - bool is_truncate= (thd->lex->sql_command == SQLCOM_TRUNCATE); - char tablespace[FN_LEN + 1]; - NdbDictionary::Table::SingleUserMode single_user_mode= NdbDictionary::Table::SingleUserModeLocked; - - DBUG_ENTER("ha_ndbcluster::create"); - DBUG_PRINT("enter", ("name: %s", name)); - - DBUG_ASSERT(*fn_rext((char*)name) == 0); - set_dbname(name); - set_tabname(name); - - if ((my_errno= check_ndb_connection())) - DBUG_RETURN(my_errno); - - Ndb *ndb= get_ndb(); - NDBDICT *dict= ndb->getDictionary(); - - if (is_truncate) - { - { - Ndb_table_guard ndbtab_g(dict, m_tabname); - if (!(m_table= ndbtab_g.get_table())) - ERR_RETURN(dict->getNdbError()); - if ((get_tablespace_name(thd, tablespace, FN_LEN))) - create_info->tablespace= tablespace; - m_table= NULL; - } - DBUG_PRINT("info", ("Dropping and re-creating table for TRUNCATE")); - if ((my_errno= delete_table(name))) - DBUG_RETURN(my_errno); - } - table= form; - if (create_from_engine) - { - /* - Table already exists in NDB and frm file has been created by - caller. - Do Ndb specific stuff, such as create a .ndb file - */ - if ((my_errno= write_ndb_file(name))) - DBUG_RETURN(my_errno); -#ifdef HAVE_NDB_BINLOG - ndbcluster_create_binlog_setup(get_ndb(), name, strlen(name), - m_dbname, m_tabname, FALSE); -#endif /* HAVE_NDB_BINLOG */ - DBUG_RETURN(my_errno); - } - -#ifdef HAVE_NDB_BINLOG - /* - Don't allow table creation unless - schema distribution table is setup - ( unless it is a creation of the schema dist table itself ) - */ - if (!ndb_schema_share) - { - if (!(strcmp(m_dbname, NDB_REP_DB) == 0 && - strcmp(m_tabname, NDB_SCHEMA_TABLE) == 0)) - { - DBUG_PRINT("info", ("Schema distribution table not setup")); - DBUG_ASSERT(ndb_schema_share); - DBUG_RETURN(HA_ERR_NO_CONNECTION); - } - single_user_mode = NdbDictionary::Table::SingleUserModeReadWrite; - } -#endif /* HAVE_NDB_BINLOG */ - - DBUG_PRINT("table", ("name: %s", m_tabname)); - if (tab.setName(m_tabname)) - { - DBUG_RETURN(my_errno= errno); - } - tab.setLogging(!(create_info->options & HA_LEX_CREATE_TMP_TABLE)); - tab.setSingleUserMode(single_user_mode); - - // Save frm data for this table - if (readfrm(name, &data, &length)) - DBUG_RETURN(1); - if (packfrm(data, length, &pack_data, &pack_length)) - { - my_free(data); - DBUG_RETURN(2); - } - DBUG_PRINT("info", - ("setFrm data: 0x%lx len: %lu", (long) pack_data, - (ulong) pack_length)); - tab.setFrm(pack_data, pack_length); - my_free(data); - my_free(pack_data); - - /* - Check for disk options - */ - if (create_info->storage_media == HA_SM_DISK) - { - if (create_info->tablespace) - tab.setTablespaceName(create_info->tablespace); - else - tab.setTablespaceName("DEFAULT-TS"); - } - else if (create_info->tablespace) - { - if (create_info->storage_media == HA_SM_MEMORY) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_ILLEGAL_HA_CREATE_OPTION, - ER(ER_ILLEGAL_HA_CREATE_OPTION), - ndbcluster_hton_name, - "TABLESPACE currently only supported for " - "STORAGE DISK"); - DBUG_RETURN(HA_ERR_UNSUPPORTED); - } - tab.setTablespaceName(create_info->tablespace); - create_info->storage_media = HA_SM_DISK; //if use tablespace, that also means store on disk - } - - /* - Handle table row type - - Default is to let table rows have var part reference so that online - add column can be performed in the future. Explicitly setting row - type to fixed will omit var part reference, which will save data - memory in ndb, but at the cost of not being able to online add - column to this table - */ - switch (create_info->row_type) { - case ROW_TYPE_FIXED: - tab.setForceVarPart(FALSE); - break; - case ROW_TYPE_DYNAMIC: - /* fall through, treat as default */ - default: - /* fall through, treat as default */ - case ROW_TYPE_DEFAULT: - tab.setForceVarPart(TRUE); - break; - } - - /* - Setup columns - */ - for (i= 0; i < form->s->fields; i++) - { - Field *field= form->field[i]; - DBUG_PRINT("info", ("name: %s type: %u pack_length: %d", - field->field_name, field->real_type(), - field->pack_length())); - if ((my_errno= create_ndb_column(col, field, create_info))) - DBUG_RETURN(my_errno); - - if (create_info->storage_media == HA_SM_DISK) - col.setStorageType(NdbDictionary::Column::StorageTypeDisk); - else - col.setStorageType(NdbDictionary::Column::StorageTypeMemory); - - switch (create_info->row_type) { - case ROW_TYPE_FIXED: - if (field_type_forces_var_part(field->type())) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_ILLEGAL_HA_CREATE_OPTION, - ER(ER_ILLEGAL_HA_CREATE_OPTION), - ndbcluster_hton_name, - "Row format FIXED incompatible with " - "variable sized attribute"); - DBUG_RETURN(HA_ERR_UNSUPPORTED); - } - break; - case ROW_TYPE_DYNAMIC: - /* - Future: make columns dynamic in this case - */ - break; - default: - break; - } - if (tab.addColumn(col)) - { - DBUG_RETURN(my_errno= errno); - } - if (col.getPrimaryKey()) - pk_length += (field->pack_length() + 3) / 4; - } - - KEY* key_info; - for (i= 0, key_info= form->key_info; i < form->s->keys; i++, key_info++) - { - KEY_PART_INFO *key_part= key_info->key_part; - KEY_PART_INFO *end= key_part + key_info->key_parts; - for (; key_part != end; key_part++) - tab.getColumn(key_part->fieldnr-1)->setStorageType( - NdbDictionary::Column::StorageTypeMemory); - } - - // No primary key, create shadow key as 64 bit, auto increment - if (form->s->primary_key == MAX_KEY) - { - DBUG_PRINT("info", ("Generating shadow key")); - if (col.setName("$PK")) - { - DBUG_RETURN(my_errno= errno); - } - col.setType(NdbDictionary::Column::Bigunsigned); - col.setLength(1); - col.setNullable(FALSE); - col.setPrimaryKey(TRUE); - col.setAutoIncrement(TRUE); - if (tab.addColumn(col)) - { - DBUG_RETURN(my_errno= errno); - } - pk_length += 2; - } - - // Make sure that blob tables don't have to big part size - for (i= 0; i < form->s->fields; i++) - { - /** - * The extra +7 concists - * 2 - words from pk in blob table - * 5 - from extra words added by tup/dict?? - */ - switch (form->field[i]->real_type()) { - case MYSQL_TYPE_GEOMETRY: - case MYSQL_TYPE_BLOB: - case MYSQL_TYPE_MEDIUM_BLOB: - case MYSQL_TYPE_LONG_BLOB: - { - NdbDictionary::Column * column= tab.getColumn(i); - int size= pk_length + (column->getPartSize()+3)/4 + 7; - if (size > NDB_MAX_TUPLE_SIZE_IN_WORDS && - (pk_length+7) < NDB_MAX_TUPLE_SIZE_IN_WORDS) - { - size= NDB_MAX_TUPLE_SIZE_IN_WORDS - pk_length - 7; - column->setPartSize(4*size); - } - /** - * If size > NDB_MAX and pk_length+7 >= NDB_MAX - * then the table can't be created anyway, so skip - * changing part size, and have error later - */ - } - default: - break; - } - } - - // Check partition info - partition_info *part_info= form->part_info; - if ((my_errno= set_up_partition_info(part_info, form, (void*)&tab))) - { - DBUG_RETURN(my_errno); - } - - // Create the table in NDB - if (dict->createTable(tab) != 0) - { - const NdbError err= dict->getNdbError(); - set_ndb_err(thd, err); - my_errno= ndb_to_mysql_error(&err); - DBUG_RETURN(my_errno); - } - - Ndb_table_guard ndbtab_g(dict, m_tabname); - // temporary set m_table during create - // reset at return - m_table= ndbtab_g.get_table(); - // TODO check also that we have the same frm... - if (!m_table) - { - /* purecov: begin deadcode */ - const NdbError err= dict->getNdbError(); - set_ndb_err(thd, err); - my_errno= ndb_to_mysql_error(&err); - DBUG_RETURN(my_errno); - /* purecov: end */ - } - - DBUG_PRINT("info", ("Table %s/%s created successfully", - m_dbname, m_tabname)); - - // Create secondary indexes - my_errno= create_indexes(ndb, form); - - if (!my_errno) - my_errno= write_ndb_file(name); - else - { - /* - Failed to create an index, - drop the table (and all it's indexes) - */ - while (dict->dropTableGlobal(*m_table)) - { - switch (dict->getNdbError().status) - { - case NdbError::TemporaryError: - if (!thd->killed) - continue; // retry indefinitly - break; - default: - break; - } - break; - } - m_table = 0; - DBUG_RETURN(my_errno); - } - -#ifdef HAVE_NDB_BINLOG - if (!my_errno) - { - NDB_SHARE *share= 0; - mysql_mutex_lock(&ndbcluster_mutex); - /* - First make sure we get a "fresh" share here, not an old trailing one... - */ - { - uint length= (uint) strlen(name); - if ((share= (NDB_SHARE*) my_hash_search(&ndbcluster_open_tables, - (uchar*) name, length))) - handle_trailing_share(share); - } - /* - get a new share - */ - - /* ndb_share reference create */ - if (!(share= get_share(name, form, TRUE, TRUE))) - { - sql_print_error("NDB: allocating table share for %s failed", name); - /* my_errno is set */ - } - else - { - DBUG_PRINT("NDB_SHARE", ("%s binlog create use_count: %u", - share->key, share->use_count)); - } - mysql_mutex_unlock(&ndbcluster_mutex); - - while (!IS_TMP_PREFIX(m_tabname)) - { - String event_name(INJECTOR_EVENT_LEN); - ndb_rep_event_name(&event_name,m_dbname,m_tabname); - int do_event_op= ndb_binlog_running; - - if (!ndb_schema_share && - strcmp(share->db, NDB_REP_DB) == 0 && - strcmp(share->table_name, NDB_SCHEMA_TABLE) == 0) - do_event_op= 1; - - /* - Always create an event for the table, as other mysql servers - expect it to be there. - */ - if (!ndbcluster_create_event(ndb, m_table, event_name.c_ptr(), share, - share && do_event_op ? 2 : 1/* push warning */)) - { - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: CREATE TABLE Event: %s", - event_name.c_ptr()); - if (share && - ndbcluster_create_event_ops(share, m_table, event_name.c_ptr())) - { - sql_print_error("NDB Binlog: FAILED CREATE TABLE event operations." - " Event: %s", name); - /* a warning has been issued to the client */ - } - } - /* - warning has been issued if ndbcluster_create_event failed - and (share && do_event_op) - */ - if (share && !do_event_op) - share->flags|= NSF_NO_BINLOG; - ndbcluster_log_schema_op(thd, share, - thd->query(), thd->query_length(), - share->db, share->table_name, - m_table->getObjectId(), - m_table->getObjectVersion(), - (is_truncate) ? - SOT_TRUNCATE_TABLE : SOT_CREATE_TABLE, - 0, 0); - break; - } - } -#endif /* HAVE_NDB_BINLOG */ - - m_table= 0; - DBUG_RETURN(my_errno); -} - -int ha_ndbcluster::create_handler_files(const char *file, - const char *old_name, - int action_flag, - HA_CREATE_INFO *create_info) -{ - Ndb* ndb; - const NDBTAB *tab; - uchar *data= NULL, *pack_data= NULL; - size_t length, pack_length; - int error= 0; - - DBUG_ENTER("create_handler_files"); - - if (action_flag != CHF_INDEX_FLAG) - { - DBUG_RETURN(FALSE); - } - DBUG_PRINT("enter", ("file: %s", file)); - if (!(ndb= get_ndb())) - DBUG_RETURN(HA_ERR_NO_CONNECTION); - - NDBDICT *dict= ndb->getDictionary(); - if (!create_info->frm_only) - DBUG_RETURN(0); // Must be a create, ignore since frm is saved in create - - // TODO handle this - DBUG_ASSERT(m_table != 0); - - set_dbname(file); - set_tabname(file); - Ndb_table_guard ndbtab_g(dict, m_tabname); - DBUG_PRINT("info", ("m_dbname: %s, m_tabname: %s", m_dbname, m_tabname)); - if (!(tab= ndbtab_g.get_table())) - DBUG_RETURN(0); // Unkown table, must be temporary table - - DBUG_ASSERT(get_ndb_share_state(m_share) == NSS_ALTERED); - if (readfrm(file, &data, &length) || - packfrm(data, length, &pack_data, &pack_length)) - { - DBUG_PRINT("info", ("Missing frm for %s", m_tabname)); - my_free(data); - my_free(pack_data); - error= 1; - } - else - { - DBUG_PRINT("info", ("Table %s has changed, altering frm in ndb", - m_tabname)); - NdbDictionary::Table new_tab= *tab; - new_tab.setFrm(pack_data, pack_length); - if (dict->alterTableGlobal(*tab, new_tab)) - { - set_ndb_err(current_thd, dict->getNdbError()); - error= ndb_to_mysql_error(&dict->getNdbError()); - } - my_free(data); - my_free(pack_data); - } - - set_ndb_share_state(m_share, NSS_INITIAL); - /* ndb_share reference schema(?) free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog schema(?) free use_count: %u", - m_share->key, m_share->use_count)); - free_share(&m_share); // Decrease ref_count - - DBUG_RETURN(error); -} - -int ha_ndbcluster::create_index(const char *name, KEY *key_info, - NDB_INDEX_TYPE idx_type, uint idx_no) -{ - int error= 0; - char unique_name[FN_LEN + 1]; - static const char* unique_suffix= "$unique"; - DBUG_ENTER("ha_ndbcluster::create_ordered_index"); - DBUG_PRINT("info", ("Creating index %u: %s", idx_no, name)); - - if (idx_type == UNIQUE_ORDERED_INDEX || idx_type == UNIQUE_INDEX) - { - strxnmov(unique_name, FN_LEN, name, unique_suffix, NullS); - DBUG_PRINT("info", ("Created unique index name \'%s\' for index %d", - unique_name, idx_no)); - } - - switch (idx_type){ - case PRIMARY_KEY_INDEX: - // Do nothing, already created - break; - case PRIMARY_KEY_ORDERED_INDEX: - error= create_ordered_index(name, key_info); - break; - case UNIQUE_ORDERED_INDEX: - if (!(error= create_ordered_index(name, key_info))) - error= create_unique_index(unique_name, key_info); - break; - case UNIQUE_INDEX: - if (check_index_fields_not_null(key_info)) - { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_NULL_COLUMN_IN_INDEX, - "Ndb does not support unique index on NULL valued attributes, index access with NULL value will become full table scan"); - } - error= create_unique_index(unique_name, key_info); - break; - case ORDERED_INDEX: - if (key_info->algorithm == HA_KEY_ALG_HASH) - { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_ILLEGAL_HA_CREATE_OPTION, - ER(ER_ILLEGAL_HA_CREATE_OPTION), - ndbcluster_hton_name, - "Ndb does not support non-unique " - "hash based indexes"); - error= HA_ERR_UNSUPPORTED; - break; - } - error= create_ordered_index(name, key_info); - break; - default: - DBUG_ASSERT(FALSE); - break; - } - - DBUG_RETURN(error); -} - -int ha_ndbcluster::create_ordered_index(const char *name, - KEY *key_info) -{ - DBUG_ENTER("ha_ndbcluster::create_ordered_index"); - DBUG_RETURN(create_ndb_index(name, key_info, FALSE)); -} - -int ha_ndbcluster::create_unique_index(const char *name, - KEY *key_info) -{ - - DBUG_ENTER("ha_ndbcluster::create_unique_index"); - DBUG_RETURN(create_ndb_index(name, key_info, TRUE)); -} - - -/** - Create an index in NDB Cluster. - - @todo - Only temporary ordered indexes supported -*/ - -int ha_ndbcluster::create_ndb_index(const char *name, - KEY *key_info, - bool unique) -{ - Ndb *ndb= get_ndb(); - NdbDictionary::Dictionary *dict= ndb->getDictionary(); - KEY_PART_INFO *key_part= key_info->key_part; - KEY_PART_INFO *end= key_part + key_info->key_parts; - - DBUG_ENTER("ha_ndbcluster::create_index"); - DBUG_PRINT("enter", ("name: %s ", name)); - - NdbDictionary::Index ndb_index(name); - if (unique) - ndb_index.setType(NdbDictionary::Index::UniqueHashIndex); - else - { - ndb_index.setType(NdbDictionary::Index::OrderedIndex); - // TODO Only temporary ordered indexes supported - ndb_index.setLogging(FALSE); - } - if (ndb_index.setTable(m_tabname)) - { - DBUG_RETURN(my_errno= errno); - } - - for (; key_part != end; key_part++) - { - Field *field= key_part->field; - DBUG_PRINT("info", ("attr: %s", field->field_name)); - if (ndb_index.addColumnName(field->field_name)) - { - DBUG_RETURN(my_errno= errno); - } - } - - if (dict->createIndex(ndb_index, *m_table)) - ERR_RETURN(dict->getNdbError()); - - // Success - DBUG_PRINT("info", ("Created index %s", name)); - DBUG_RETURN(0); -} - -/* - Prepare for an on-line alter table -*/ -void ha_ndbcluster::prepare_for_alter() -{ - /* ndb_share reference schema */ - ndbcluster_get_share(m_share); // Increase ref_count - DBUG_PRINT("NDB_SHARE", ("%s binlog schema use_count: %u", - m_share->key, m_share->use_count)); - set_ndb_share_state(m_share, NSS_ALTERED); -} - -/* - Add an index on-line to a table -*/ -int ha_ndbcluster::add_index(TABLE *table_arg, - KEY *key_info, uint num_of_keys) -{ - int error= 0; - uint idx; - DBUG_ENTER("ha_ndbcluster::add_index"); - DBUG_PRINT("enter", ("table %s", table_arg->s->table_name.str)); - DBUG_ASSERT(m_share->state == NSS_ALTERED); - - for (idx= 0; idx < num_of_keys; idx++) - { - KEY *key= key_info + idx; - KEY_PART_INFO *key_part= key->key_part; - KEY_PART_INFO *end= key_part + key->key_parts; - NDB_INDEX_TYPE idx_type= get_index_type_from_key(idx, key_info, false); - DBUG_PRINT("info", ("Adding index: '%s'", key_info[idx].name)); - // Add fields to key_part struct - for (; key_part != end; key_part++) - key_part->field= table->field[key_part->fieldnr]; - // Check index type - // Create index in ndb - if((error= create_index(key_info[idx].name, key, idx_type, idx))) - break; - } - if (error) - { - set_ndb_share_state(m_share, NSS_INITIAL); - /* ndb_share reference schema free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog schema free use_count: %u", - m_share->key, m_share->use_count)); - free_share(&m_share); // Decrease ref_count - } - DBUG_RETURN(error); -} - -/* - Mark one or several indexes for deletion. and - renumber the remaining indexes -*/ -int ha_ndbcluster::prepare_drop_index(TABLE *table_arg, - uint *key_num, uint num_of_keys) -{ - DBUG_ENTER("ha_ndbcluster::prepare_drop_index"); - DBUG_ASSERT(m_share->state == NSS_ALTERED); - // Mark indexes for deletion - uint idx; - for (idx= 0; idx < num_of_keys; idx++) - { - DBUG_PRINT("info", ("ha_ndbcluster::prepare_drop_index %u", *key_num)); - m_index[*key_num++].status= TO_BE_DROPPED; - } - // Renumber indexes - THD *thd= current_thd; - Thd_ndb *thd_ndb= get_thd_ndb(thd); - Ndb *ndb= thd_ndb->ndb; - renumber_indexes(ndb, table_arg); - DBUG_RETURN(0); -} - -/* - Really drop all indexes marked for deletion -*/ -int ha_ndbcluster::final_drop_index(TABLE *table_arg) -{ - int error; - DBUG_ENTER("ha_ndbcluster::final_drop_index"); - DBUG_PRINT("info", ("ha_ndbcluster::final_drop_index")); - // Really drop indexes - THD *thd= current_thd; - Thd_ndb *thd_ndb= get_thd_ndb(thd); - Ndb *ndb= thd_ndb->ndb; - if((error= drop_indexes(ndb, table_arg))) - { - m_share->state= NSS_INITIAL; - /* ndb_share reference schema free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog schema free use_count: %u", - m_share->key, m_share->use_count)); - free_share(&m_share); // Decrease ref_count - } - DBUG_RETURN(error); -} - -/** - Rename a table in NDB Cluster. -*/ - -int ha_ndbcluster::rename_table(const char *from, const char *to) -{ - NDBDICT *dict; - char old_dbname[FN_HEADLEN]; - char new_dbname[FN_HEADLEN]; - char new_tabname[FN_HEADLEN]; - const NDBTAB *orig_tab; - int result; - bool recreate_indexes= FALSE; - NDBDICT::List index_list; - - DBUG_ENTER("ha_ndbcluster::rename_table"); - DBUG_PRINT("info", ("Renaming %s to %s", from, to)); - set_dbname(from, old_dbname); - set_dbname(to, new_dbname); - set_tabname(from); - set_tabname(to, new_tabname); - - if (check_ndb_connection()) - DBUG_RETURN(my_errno= HA_ERR_NO_CONNECTION); - - Ndb *ndb= get_ndb(); - ndb->setDatabaseName(old_dbname); - dict= ndb->getDictionary(); - Ndb_table_guard ndbtab_g(dict, m_tabname); - if (!(orig_tab= ndbtab_g.get_table())) - ERR_RETURN(dict->getNdbError()); - -#ifdef HAVE_NDB_BINLOG - int ndb_table_id= orig_tab->getObjectId(); - int ndb_table_version= orig_tab->getObjectVersion(); - - /* ndb_share reference temporary */ - NDB_SHARE *share= get_share(from, 0, FALSE); - if (share) - { - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - int r __attribute__((unused))= rename_share(share, to); - DBUG_ASSERT(r == 0); - } -#endif - if (my_strcasecmp(system_charset_info, new_dbname, old_dbname)) - { - dict->listIndexes(index_list, *orig_tab); - recreate_indexes= TRUE; - } - // Change current database to that of target table - set_dbname(to); - if (ndb->setDatabaseName(m_dbname)) - { - ERR_RETURN(ndb->getNdbError()); - } - - NdbDictionary::Table new_tab= *orig_tab; - new_tab.setName(new_tabname); - if (dict->alterTableGlobal(*orig_tab, new_tab) != 0) - { - NdbError ndb_error= dict->getNdbError(); -#ifdef HAVE_NDB_BINLOG - if (share) - { - int ret __attribute__((unused))= rename_share(share, from); - DBUG_ASSERT(ret == 0); - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - } -#endif - ERR_RETURN(ndb_error); - } - - // Rename .ndb file - if ((result= handler::rename_table(from, to))) - { - // ToDo in 4.1 should rollback alter table... -#ifdef HAVE_NDB_BINLOG - if (share) - { - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - free_share(&share); - } -#endif - DBUG_RETURN(result); - } - -#ifdef HAVE_NDB_BINLOG - int is_old_table_tmpfile= 1; - if (share && share->op) - dict->forceGCPWait(); - - /* handle old table */ - if (!IS_TMP_PREFIX(m_tabname)) - { - is_old_table_tmpfile= 0; - String event_name(INJECTOR_EVENT_LEN); - ndb_rep_event_name(&event_name, from + sizeof(share_prefix) - 1, 0); - ndbcluster_handle_drop_table(ndb, event_name.c_ptr(), share, - "rename table"); - } - - if (!result && !IS_TMP_PREFIX(new_tabname)) - { - /* always create an event for the table */ - String event_name(INJECTOR_EVENT_LEN); - ndb_rep_event_name(&event_name, to + sizeof(share_prefix) - 1, 0); - Ndb_table_guard ndbtab_g2(dict, new_tabname); - const NDBTAB *ndbtab= ndbtab_g2.get_table(); - - if (!ndbcluster_create_event(ndb, ndbtab, event_name.c_ptr(), share, - share && ndb_binlog_running ? 2 : 1/* push warning */)) - { - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: RENAME Event: %s", - event_name.c_ptr()); - if (share && - ndbcluster_create_event_ops(share, ndbtab, event_name.c_ptr())) - { - sql_print_error("NDB Binlog: FAILED create event operations " - "during RENAME. Event %s", event_name.c_ptr()); - /* a warning has been issued to the client */ - } - } - /* - warning has been issued if ndbcluster_create_event failed - and (share && ndb_binlog_running) - */ - if (!is_old_table_tmpfile) - ndbcluster_log_schema_op(current_thd, share, - current_thd->query(), - current_thd->query_length(), - old_dbname, m_tabname, - ndb_table_id, ndb_table_version, - SOT_RENAME_TABLE, - m_dbname, new_tabname); - } - - // If we are moving tables between databases, we need to recreate - // indexes - if (recreate_indexes) - { - for (unsigned i = 0; i < index_list.count; i++) - { - NDBDICT::List::Element& index_el = index_list.elements[i]; - // Recreate any indexes not stored in the system database - if (my_strcasecmp(system_charset_info, - index_el.database, NDB_SYSTEM_DATABASE)) - { - set_dbname(from); - ndb->setDatabaseName(m_dbname); - const NDBINDEX * index= dict->getIndexGlobal(index_el.name, new_tab); - DBUG_PRINT("info", ("Creating index %s/%s", - index_el.database, index->getName())); - dict->createIndex(*index, new_tab); - DBUG_PRINT("info", ("Dropping index %s/%s", - index_el.database, index->getName())); - set_dbname(from); - ndb->setDatabaseName(m_dbname); - dict->dropIndexGlobal(*index); - } - } - } - if (share) - { - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - } -#endif - - DBUG_RETURN(result); -} - - -/** - Delete table from NDB Cluster. -*/ - -/* static version which does not need a handler */ - -int -ha_ndbcluster::delete_table(ha_ndbcluster *h, Ndb *ndb, - const char *path, - const char *db, - const char *table_name) -{ - THD *thd= current_thd; - DBUG_ENTER("ha_ndbcluster::ndbcluster_delete_table"); - NDBDICT *dict= ndb->getDictionary(); - int ndb_table_id= 0; - int ndb_table_version= 0; -#ifdef HAVE_NDB_BINLOG - /* - Don't allow drop table unless - schema distribution table is setup - */ - if (!ndb_schema_share) - { - DBUG_PRINT("info", ("Schema distribution table not setup")); - DBUG_ASSERT(ndb_schema_share); - DBUG_RETURN(HA_ERR_NO_CONNECTION); - } - /* ndb_share reference temporary */ - NDB_SHARE *share= get_share(path, 0, FALSE); - if (share) - { - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - } -#endif - - /* Drop the table from NDB */ - - int res= 0; - if (h && h->m_table) - { -retry_temporary_error1: - if (dict->dropTableGlobal(*h->m_table) == 0) - { - ndb_table_id= h->m_table->getObjectId(); - ndb_table_version= h->m_table->getObjectVersion(); - DBUG_PRINT("info", ("success 1")); - } - else - { - switch (dict->getNdbError().status) - { - case NdbError::TemporaryError: - if (!thd->killed) - goto retry_temporary_error1; // retry indefinitly - break; - default: - break; - } - set_ndb_err(thd, dict->getNdbError()); - res= ndb_to_mysql_error(&dict->getNdbError()); - DBUG_PRINT("info", ("error(1) %u", res)); - } - h->release_metadata(thd, ndb); - } - else - { - ndb->setDatabaseName(db); - while (1) - { - Ndb_table_guard ndbtab_g(dict, table_name); - if (ndbtab_g.get_table()) - { - retry_temporary_error2: - if (dict->dropTableGlobal(*ndbtab_g.get_table()) == 0) - { - ndb_table_id= ndbtab_g.get_table()->getObjectId(); - ndb_table_version= ndbtab_g.get_table()->getObjectVersion(); - DBUG_PRINT("info", ("success 2")); - break; - } - else - { - switch (dict->getNdbError().status) - { - case NdbError::TemporaryError: - if (!thd->killed) - goto retry_temporary_error2; // retry indefinitly - break; - default: - if (dict->getNdbError().code == NDB_INVALID_SCHEMA_OBJECT) - { - ndbtab_g.invalidate(); - continue; - } - break; - } - } - } - set_ndb_err(thd, dict->getNdbError()); - res= ndb_to_mysql_error(&dict->getNdbError()); - DBUG_PRINT("info", ("error(2) %u", res)); - break; - } - } - - if (res) - { -#ifdef HAVE_NDB_BINLOG - /* the drop table failed for some reason, drop the share anyways */ - if (share) - { - mysql_mutex_lock(&ndbcluster_mutex); - if (share->state != NSS_DROPPED) - { - /* - The share kept by the server has not been freed, free it - */ - share->state= NSS_DROPPED; - /* ndb_share reference create free */ - DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u", - share->key, share->use_count)); - free_share(&share, TRUE); - } - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share, TRUE); - mysql_mutex_unlock(&ndbcluster_mutex); - } -#endif - DBUG_RETURN(res); - } - -#ifdef HAVE_NDB_BINLOG - /* stop the logging of the dropped table, and cleanup */ - - /* - drop table is successful even if table does not exist in ndb - and in case table was actually not dropped, there is no need - to force a gcp, and setting the event_name to null will indicate - that there is no event to be dropped - */ - int table_dropped= dict->getNdbError().code != 709; - - if (!IS_TMP_PREFIX(table_name) && share && - current_thd->lex->sql_command != SQLCOM_TRUNCATE) - { - ndbcluster_log_schema_op(thd, share, - thd->query(), thd->query_length(), - share->db, share->table_name, - ndb_table_id, ndb_table_version, - SOT_DROP_TABLE, 0, 0); - } - else if (table_dropped && share && share->op) /* ndbcluster_log_schema_op - will do a force GCP */ - dict->forceGCPWait(); - - if (!IS_TMP_PREFIX(table_name)) - { - String event_name(INJECTOR_EVENT_LEN); - ndb_rep_event_name(&event_name, path + sizeof(share_prefix) - 1, 0); - ndbcluster_handle_drop_table(ndb, - table_dropped ? event_name.c_ptr() : 0, - share, "delete table"); - } - - if (share) - { - mysql_mutex_lock(&ndbcluster_mutex); - if (share->state != NSS_DROPPED) - { - /* - The share kept by the server has not been freed, free it - */ - share->state= NSS_DROPPED; - /* ndb_share reference create free */ - DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u", - share->key, share->use_count)); - free_share(&share, TRUE); - } - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share, TRUE); - mysql_mutex_unlock(&ndbcluster_mutex); - } -#endif - DBUG_RETURN(0); -} - -int ha_ndbcluster::delete_table(const char *name) -{ - DBUG_ENTER("ha_ndbcluster::delete_table"); - DBUG_PRINT("enter", ("name: %s", name)); - set_dbname(name); - set_tabname(name); - -#ifdef HAVE_NDB_BINLOG - /* - Don't allow drop table unless - schema distribution table is setup - */ - if (!ndb_schema_share) - { - DBUG_PRINT("info", ("Schema distribution table not setup")); - DBUG_ASSERT(ndb_schema_share); - DBUG_RETURN(HA_ERR_NO_CONNECTION); - } -#endif - - if (check_ndb_connection()) - DBUG_RETURN(HA_ERR_NO_CONNECTION); - - /* Call ancestor function to delete .ndb file */ - handler::delete_table(name); - - DBUG_RETURN(delete_table(this, get_ndb(),name, m_dbname, m_tabname)); -} - - -void ha_ndbcluster::get_auto_increment(ulonglong offset, ulonglong increment, - ulonglong nb_desired_values, - ulonglong *first_value, - ulonglong *nb_reserved_values) -{ - uint cache_size; - Uint64 auto_value; - THD *thd= current_thd; - DBUG_ENTER("get_auto_increment"); - DBUG_PRINT("enter", ("m_tabname: %s", m_tabname)); - Ndb *ndb= get_ndb(); - - if (m_rows_inserted > m_rows_to_insert) - { - /* We guessed too low */ - m_rows_to_insert+= m_autoincrement_prefetch; - } - uint remaining= m_rows_to_insert - m_rows_inserted; - ha_rows prefetch= THDVAR(thd, autoincrement_prefetch_sz); - uint min_prefetch= - (remaining < prefetch) ? prefetch : remaining; - cache_size= ((remaining < m_autoincrement_prefetch) ? - min_prefetch - : remaining); - uint retries= NDB_AUTO_INCREMENT_RETRIES; - int retry_sleep= 30; /* 30 milliseconds, transaction */ - for (;;) - { - Ndb_tuple_id_range_guard g(m_share); - if ((m_skip_auto_increment && - ndb->readAutoIncrementValue(m_table, g.range, auto_value)) || - ndb->getAutoIncrementValue(m_table, g.range, auto_value, cache_size, increment, offset)) - { - if (--retries && - ndb->getNdbError().status == NdbError::TemporaryError) - { - my_sleep(retry_sleep); - continue; - } - const NdbError err= ndb->getNdbError(); - sql_print_error("Error %lu in ::get_auto_increment(): %s", - (ulong) err.code, err.message); - *first_value= ~(ulonglong) 0; - DBUG_VOID_RETURN; - } - break; - } - *first_value= (longlong)auto_value; - /* From the point of view of MySQL, NDB reserves one row at a time */ - *nb_reserved_values= 1; - DBUG_VOID_RETURN; -} - - -/** - Constructor for the NDB Cluster table handler . -*/ - -/* - Normal flags for binlogging is that ndb has HA_HAS_OWN_BINLOGGING - and preferes HA_BINLOG_ROW_CAPABLE - Other flags are set under certain circumstaces in table_flags() -*/ -#define HA_NDBCLUSTER_TABLE_FLAGS \ - HA_REC_NOT_IN_SEQ | \ - HA_NULL_IN_KEY | \ - HA_AUTO_PART_KEY | \ - HA_NO_PREFIX_CHAR_KEYS | \ - HA_NEED_READ_RANGE_BUFFER | \ - HA_CAN_GEOMETRY | \ - HA_CAN_BIT_FIELD | \ - HA_PRIMARY_KEY_REQUIRED_FOR_POSITION | \ - HA_PRIMARY_KEY_REQUIRED_FOR_DELETE | \ - HA_PARTIAL_COLUMN_READ | \ - HA_HAS_OWN_BINLOGGING | \ - HA_BINLOG_ROW_CAPABLE | \ - HA_HAS_RECORDS - -ha_ndbcluster::ha_ndbcluster(handlerton *hton, TABLE_SHARE *table_arg): - handler(hton, table_arg), - m_active_trans(NULL), - m_active_cursor(NULL), - m_table(NULL), - m_table_info(NULL), - m_table_flags(HA_NDBCLUSTER_TABLE_FLAGS), - m_share(0), - m_part_info(NULL), - m_use_partition_function(FALSE), - m_sorted(FALSE), - m_use_write(FALSE), - m_ignore_dup_key(FALSE), - m_has_unique_index(FALSE), - m_primary_key_update(FALSE), - m_ignore_no_key(FALSE), - m_rows_to_insert((ha_rows) 1), - m_rows_inserted((ha_rows) 0), - m_bulk_insert_rows((ha_rows) 1024), - m_rows_changed((ha_rows) 0), - m_bulk_insert_not_flushed(FALSE), - m_delete_cannot_batch(FALSE), - m_update_cannot_batch(FALSE), - m_ops_pending(0), - m_skip_auto_increment(TRUE), - m_blobs_pending(0), - m_blobs_offset(0), - m_blobs_buffer(0), - m_blobs_buffer_size(0), - m_dupkey((uint) -1), - m_ha_not_exact_count(FALSE), - m_force_send(TRUE), - m_autoincrement_prefetch(DEFAULT_AUTO_PREFETCH), - m_transaction_on(TRUE), - m_cond(NULL), - m_multi_cursor(NULL) -{ - int i; - - DBUG_ENTER("ha_ndbcluster"); - - m_tabname[0]= '\0'; - m_dbname[0]= '\0'; - - stats.records= ~(ha_rows)0; // uninitialized - stats.block_size= 1024; - - for (i= 0; i < MAX_KEY; i++) - ndb_init_index(m_index[i]); - - DBUG_VOID_RETURN; -} - - -int ha_ndbcluster::ha_initialise() -{ - DBUG_ENTER("ha_ndbcluster::ha_initialise"); - if (check_ndb_in_thd(current_thd)) - { - DBUG_RETURN(FALSE); - } - DBUG_RETURN(TRUE); -} - -/** - Destructor for NDB Cluster table handler. -*/ - -ha_ndbcluster::~ha_ndbcluster() -{ - THD *thd= current_thd; - Ndb *ndb= thd ? check_ndb_in_thd(thd) : g_ndb; - DBUG_ENTER("~ha_ndbcluster"); - - if (m_share) - { - /* ndb_share reference handler free */ - DBUG_PRINT("NDB_SHARE", ("%s handler free use_count: %u", - m_share->key, m_share->use_count)); - free_share(&m_share); - } - release_metadata(thd, ndb); - my_free(m_blobs_buffer); - m_blobs_buffer= 0; - - // Check for open cursor/transaction - if (m_active_cursor) { - } - DBUG_ASSERT(m_active_cursor == NULL); - if (m_active_trans) { - } - DBUG_ASSERT(m_active_trans == NULL); - - // Discard any generated condition - DBUG_PRINT("info", ("Deleting generated condition")); - if (m_cond) - { - delete m_cond; - m_cond= NULL; - } - - DBUG_VOID_RETURN; -} - - - -/** - Open a table for further use. - - - fetch metadata for this table from NDB - - check that table exists - - @retval - 0 ok - @retval - < 0 Table has changed -*/ - -int ha_ndbcluster::open(const char *name, int mode, uint test_if_locked) -{ - int res; - KEY *key; - DBUG_ENTER("ha_ndbcluster::open"); - DBUG_PRINT("enter", ("name: %s mode: %d test_if_locked: %d", - name, mode, test_if_locked)); - - /* - Setup ref_length to make room for the whole - primary key to be written in the ref variable - */ - - if (table_share->primary_key != MAX_KEY) - { - key= table->key_info+table_share->primary_key; - ref_length= key->key_length; - } - else // (table_share->primary_key == MAX_KEY) - { - if (m_use_partition_function) - { - ref_length+= sizeof(m_part_id); - } - } - - DBUG_PRINT("info", ("ref_length: %d", ref_length)); - - // Init table lock structure - /* ndb_share reference handler */ - if (!(m_share=get_share(name, table))) - DBUG_RETURN(1); - DBUG_PRINT("NDB_SHARE", ("%s handler use_count: %u", - m_share->key, m_share->use_count)); - thr_lock_data_init(&m_share->lock,&m_lock,(void*) 0); - - set_dbname(name); - set_tabname(name); - - if ((res= check_ndb_connection()) || - (res= get_metadata(name))) - { - /* ndb_share reference handler free */ - DBUG_PRINT("NDB_SHARE", ("%s handler free use_count: %u", - m_share->key, m_share->use_count)); - free_share(&m_share); - m_share= 0; - DBUG_RETURN(res); - } - while (1) - { - Ndb *ndb= get_ndb(); - if (ndb->setDatabaseName(m_dbname)) - { - set_ndb_err(current_thd, ndb->getNdbError()); - res= ndb_to_mysql_error(&ndb->getNdbError()); - break; - } - struct Ndb_statistics stat; - res= ndb_get_table_statistics(NULL, FALSE, ndb, m_table, &stat); - stats.mean_rec_length= stat.row_size; - stats.data_file_length= stat.fragment_memory; - stats.records= stat.row_count; - if(!res) - res= info(HA_STATUS_CONST); - break; - } - if (res) - { - free_share(&m_share); - m_share= 0; - release_metadata(current_thd, get_ndb()); - DBUG_RETURN(res); - } -#ifdef HAVE_NDB_BINLOG - if (!ndb_binlog_tables_inited) - { - table->db_stat|= HA_READ_ONLY; - sql_print_information("table '%s' opened read only", name); - } -#endif - DBUG_RETURN(0); -} - -/* - Set partition info - - SYNOPSIS - set_part_info() - part_info - - RETURN VALUE - NONE - - DESCRIPTION - Set up partition info when handler object created -*/ - -void ha_ndbcluster::set_part_info(partition_info *part_info) -{ - m_part_info= part_info; - if (!(m_part_info->part_type == HASH_PARTITION && - m_part_info->list_of_part_fields && - !m_part_info->is_sub_partitioned())) - m_use_partition_function= TRUE; -} - -/** - Close the table; release resources setup by open(). -*/ - -int ha_ndbcluster::close(void) -{ - DBUG_ENTER("close"); - THD *thd= table->in_use; - Ndb *ndb= thd ? check_ndb_in_thd(thd) : g_ndb; - /* ndb_share reference handler free */ - DBUG_PRINT("NDB_SHARE", ("%s handler free use_count: %u", - m_share->key, m_share->use_count)); - free_share(&m_share); - m_share= 0; - release_metadata(thd, ndb); - DBUG_RETURN(0); -} - - -/** - @todo - - Alt.1 If init fails because to many allocated Ndb - wait on condition for a Ndb object to be released. - - Alt.2 Seize/release from pool, wait until next release -*/ -Thd_ndb* ha_ndbcluster::seize_thd_ndb() -{ - Thd_ndb *thd_ndb; - DBUG_ENTER("seize_thd_ndb"); - - thd_ndb= new Thd_ndb(); - if (thd_ndb == NULL) - { - my_errno= HA_ERR_OUT_OF_MEM; - return NULL; - } - if (thd_ndb->ndb->init(max_transactions) != 0) - { - ERR_PRINT(thd_ndb->ndb->getNdbError()); - /* - TODO - Alt.1 If init fails because to many allocated Ndb - wait on condition for a Ndb object to be released. - Alt.2 Seize/release from pool, wait until next release - */ - delete thd_ndb; - thd_ndb= NULL; - } - DBUG_RETURN(thd_ndb); -} - - -void ha_ndbcluster::release_thd_ndb(Thd_ndb* thd_ndb) -{ - DBUG_ENTER("release_thd_ndb"); - delete thd_ndb; - DBUG_VOID_RETURN; -} - - -/** - If this thread already has a Thd_ndb object allocated - in current THD, reuse it. Otherwise - seize a Thd_ndb object, assign it to current THD and use it. - -*/ - -Ndb* check_ndb_in_thd(THD* thd) -{ - Thd_ndb *thd_ndb= get_thd_ndb(thd); - if (!thd_ndb) - { - if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb())) - return NULL; - set_thd_ndb(thd, thd_ndb); - } - return thd_ndb->ndb; -} - - - -int ha_ndbcluster::check_ndb_connection(THD* thd) -{ - Ndb *ndb; - DBUG_ENTER("check_ndb_connection"); - - if (!(ndb= check_ndb_in_thd(thd))) - DBUG_RETURN(HA_ERR_NO_CONNECTION); - if (ndb->setDatabaseName(m_dbname)) - { - ERR_RETURN(ndb->getNdbError()); - } - DBUG_RETURN(0); -} - - -static int ndbcluster_close_connection(handlerton *hton, THD *thd) -{ - Thd_ndb *thd_ndb= get_thd_ndb(thd); - DBUG_ENTER("ndbcluster_close_connection"); - if (thd_ndb) - { - ha_ndbcluster::release_thd_ndb(thd_ndb); - set_thd_ndb(thd, NULL); // not strictly required but does not hurt either - } - DBUG_RETURN(0); -} - - -/** - Try to discover one table from NDB. -*/ - -int ndbcluster_discover(handlerton *hton, THD* thd, const char *db, - const char *name, - uchar **frmblob, - size_t *frmlen) -{ - int error= 0; - NdbError ndb_error; - size_t len; - uchar* data= NULL; - Ndb* ndb; - char key[FN_REFLEN + 1]; - DBUG_ENTER("ndbcluster_discover"); - DBUG_PRINT("enter", ("db: %s, name: %s", db, name)); - - if (!(ndb= check_ndb_in_thd(thd))) - DBUG_RETURN(HA_ERR_NO_CONNECTION); - if (ndb->setDatabaseName(db)) - { - ERR_RETURN(ndb->getNdbError()); - } - NDBDICT* dict= ndb->getDictionary(); - build_table_filename(key, sizeof(key) - 1, db, name, "", 0); - /* ndb_share reference temporary */ - NDB_SHARE *share= get_share(key, 0, FALSE); - if (share) - { - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - } - if (share && get_ndb_share_state(share) == NSS_ALTERED) - { - // Frm has been altered on disk, but not yet written to ndb - if (readfrm(key, &data, &len)) - { - DBUG_PRINT("error", ("Could not read frm")); - error= 1; - goto err; - } - } - else - { - Ndb_table_guard ndbtab_g(dict, name); - const NDBTAB *tab= ndbtab_g.get_table(); - if (!tab) - { - const NdbError err= dict->getNdbError(); - if (err.code == 709 || err.code == 723) - { - error= -1; - DBUG_PRINT("info", ("ndb_error.code: %u", ndb_error.code)); - } - else - { - error= -1; - ndb_error= err; - DBUG_PRINT("info", ("ndb_error.code: %u", ndb_error.code)); - } - goto err; - } - DBUG_PRINT("info", ("Found table %s", tab->getName())); - - len= tab->getFrmLength(); - if (len == 0 || tab->getFrmData() == NULL) - { - DBUG_PRINT("error", ("No frm data found.")); - error= 1; - goto err; - } - - if (unpackfrm(&data, &len, (uchar*) tab->getFrmData())) - { - DBUG_PRINT("error", ("Could not unpack table")); - error= 1; - goto err; - } - } - - *frmlen= len; - *frmblob= data; - - if (share) - { - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - } - - DBUG_RETURN(0); -err: - my_free(data); - if (share) - { - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - } - if (ndb_error.code) - { - ERR_RETURN(ndb_error); - } - DBUG_RETURN(error); -} - -/** - Check if a table exists in NDB. -*/ - -int ndbcluster_table_exists_in_engine(handlerton *hton, THD* thd, - const char *db, - const char *name) -{ - Ndb* ndb; - DBUG_ENTER("ndbcluster_table_exists_in_engine"); - DBUG_PRINT("enter", ("db: %s name: %s", db, name)); - - if (!(ndb= check_ndb_in_thd(thd))) - DBUG_RETURN(HA_ERR_NO_CONNECTION); - NDBDICT* dict= ndb->getDictionary(); - NdbDictionary::Dictionary::List list; - if (dict->listObjects(list, NdbDictionary::Object::UserTable) != 0) - ERR_RETURN(dict->getNdbError()); - for (uint i= 0 ; i < list.count ; i++) - { - NdbDictionary::Dictionary::List::Element& elmt= list.elements[i]; - if (my_strcasecmp(system_charset_info, elmt.database, db)) - continue; - if (my_strcasecmp(system_charset_info, elmt.name, name)) - continue; - DBUG_PRINT("info", ("Found table")); - DBUG_RETURN(HA_ERR_TABLE_EXIST); - } - DBUG_RETURN(HA_ERR_NO_SUCH_TABLE); -} - - - -extern "C" uchar* tables_get_key(const char *entry, size_t *length, - my_bool not_used __attribute__((unused))) -{ - *length= strlen(entry); - return (uchar*) entry; -} - - -/** - Drop a database in NDB Cluster - - @note - add a dummy void function, since stupid handlerton is returning void instead of int... -*/ -int ndbcluster_drop_database_impl(const char *path) -{ - DBUG_ENTER("ndbcluster_drop_database"); - THD *thd= current_thd; - char dbname[FN_HEADLEN]; - Ndb* ndb; - NdbDictionary::Dictionary::List list; - uint i; - char *tabname; - List<char> drop_list; - int ret= 0; - ha_ndbcluster::set_dbname(path, (char *)&dbname); - DBUG_PRINT("enter", ("db: %s", dbname)); - - if (!(ndb= check_ndb_in_thd(thd))) - DBUG_RETURN(-1); - - // List tables in NDB - NDBDICT *dict= ndb->getDictionary(); - if (dict->listObjects(list, - NdbDictionary::Object::UserTable) != 0) - DBUG_RETURN(-1); - for (i= 0 ; i < list.count ; i++) - { - NdbDictionary::Dictionary::List::Element& elmt= list.elements[i]; - DBUG_PRINT("info", ("Found %s/%s in NDB", elmt.database, elmt.name)); - - // Add only tables that belongs to db - if (my_strcasecmp(system_charset_info, elmt.database, dbname)) - continue; - DBUG_PRINT("info", ("%s must be dropped", elmt.name)); - drop_list.push_back(thd->strdup(elmt.name)); - } - // Drop any tables belonging to database - char full_path[FN_REFLEN + 1]; - char *tmp= full_path + - build_table_filename(full_path, sizeof(full_path) - 1, dbname, "", "", 0); - if (ndb->setDatabaseName(dbname)) - { - ERR_RETURN(ndb->getNdbError()); - } - List_iterator_fast<char> it(drop_list); - while ((tabname=it++)) - { - tablename_to_filename(tabname, tmp, FN_REFLEN - (tmp - full_path)-1); - if (ha_ndbcluster::delete_table(0, ndb, full_path, dbname, tabname)) - { - const NdbError err= dict->getNdbError(); - if (err.code != 709 && err.code != 723) - { - set_ndb_err(thd, err); - ret= ndb_to_mysql_error(&err); - } - } - } - DBUG_RETURN(ret); -} - -static void ndbcluster_drop_database(handlerton *hton, char *path) -{ - DBUG_ENTER("ndbcluster_drop_database"); -#ifdef HAVE_NDB_BINLOG - /* - Don't allow drop database unless - schema distribution table is setup - */ - if (!ndb_schema_share) - { - DBUG_PRINT("info", ("Schema distribution table not setup")); - DBUG_ASSERT(ndb_schema_share); - DBUG_VOID_RETURN; - } -#endif - ndbcluster_drop_database_impl(path); -#ifdef HAVE_NDB_BINLOG - char db[FN_REFLEN]; - THD *thd= current_thd; - ha_ndbcluster::set_dbname(path, db); - ndbcluster_log_schema_op(thd, 0, - thd->query(), thd->query_length(), - db, "", 0, 0, SOT_DROP_DB, 0, 0); -#endif - DBUG_VOID_RETURN; -} - -int ndb_create_table_from_engine(THD *thd, const char *db, - const char *table_name) -{ - LEX *old_lex= thd->lex, newlex; - thd->lex= &newlex; - newlex.current_select= NULL; - int res= ha_create_table_from_engine(thd, db, table_name); - thd->lex= old_lex; - return res; -} - -/* - find all tables in ndb and discover those needed -*/ -int ndbcluster_find_all_files(THD *thd) -{ - Ndb* ndb; - char key[FN_REFLEN + 1]; - NDBDICT *dict; - int unhandled, retries= 5, skipped; - DBUG_ENTER("ndbcluster_find_all_files"); - - if (!(ndb= check_ndb_in_thd(thd))) - DBUG_RETURN(HA_ERR_NO_CONNECTION); - - dict= ndb->getDictionary(); - - LINT_INIT(unhandled); - LINT_INIT(skipped); - do - { - NdbDictionary::Dictionary::List list; - if (dict->listObjects(list, NdbDictionary::Object::UserTable) != 0) - ERR_RETURN(dict->getNdbError()); - unhandled= 0; - skipped= 0; - retries--; - for (uint i= 0 ; i < list.count ; i++) - { - NDBDICT::List::Element& elmt= list.elements[i]; - if (IS_TMP_PREFIX(elmt.name) || IS_NDB_BLOB_PREFIX(elmt.name)) - { - DBUG_PRINT("info", ("Skipping %s.%s in NDB", elmt.database, elmt.name)); - continue; - } - DBUG_PRINT("info", ("Found %s.%s in NDB", elmt.database, elmt.name)); - if (elmt.state != NDBOBJ::StateOnline && - elmt.state != NDBOBJ::StateBackup && - elmt.state != NDBOBJ::StateBuilding) - { - sql_print_information("NDB: skipping setup table %s.%s, in state %d", - elmt.database, elmt.name, elmt.state); - skipped++; - continue; - } - - ndb->setDatabaseName(elmt.database); - Ndb_table_guard ndbtab_g(dict, elmt.name); - const NDBTAB *ndbtab= ndbtab_g.get_table(); - if (!ndbtab) - { - if (retries == 0) - sql_print_error("NDB: failed to setup table %s.%s, error: %d, %s", - elmt.database, elmt.name, - dict->getNdbError().code, - dict->getNdbError().message); - unhandled++; - continue; - } - - if (ndbtab->getFrmLength() == 0) - continue; - - /* check if database exists */ - char *end= key + - build_table_filename(key, sizeof(key) - 1, elmt.database, "", "", 0); - if (my_access(key, F_OK)) - { - /* no such database defined, skip table */ - continue; - } - /* finalize construction of path */ - end+= tablename_to_filename(elmt.name, end, - sizeof(key)-(end-key)); - uchar *data= 0, *pack_data= 0; - size_t length, pack_length; - int discover= 0; - if (readfrm(key, &data, &length) || - packfrm(data, length, &pack_data, &pack_length)) - { - discover= 1; - sql_print_information("NDB: missing frm for %s.%s, discovering...", - elmt.database, elmt.name); - } - else if (cmp_frm(ndbtab, pack_data, pack_length)) - { - /* ndb_share reference temporary */ - NDB_SHARE *share= get_share(key, 0, FALSE); - if (share) - { - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - } - if (!share || get_ndb_share_state(share) != NSS_ALTERED) - { - discover= 1; - sql_print_information("NDB: mismatch in frm for %s.%s, discovering...", - elmt.database, elmt.name); - } - if (share) - { - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - } - } - my_free(data); - my_free(pack_data); - - if (discover) - { - /* ToDo 4.1 database needs to be created if missing */ - if (ndb_create_table_from_engine(thd, elmt.database, elmt.name)) - { - /* ToDo 4.1 handle error */ - } - } -#ifdef HAVE_NDB_BINLOG - else - { - /* set up replication for this table */ - ndbcluster_create_binlog_setup(ndb, key, end-key, - elmt.database, elmt.name, - TRUE); - } -#endif - } - } - while (unhandled && retries); - - DBUG_RETURN(-(skipped + unhandled)); -} - -int ndbcluster_find_files(handlerton *hton, THD *thd, - const char *db, - const char *path, - const char *wild, bool dir, List<LEX_STRING> *files) -{ - DBUG_ENTER("ndbcluster_find_files"); - DBUG_PRINT("enter", ("db: %s", db)); - { // extra bracket to avoid gcc 2.95.3 warning - uint i; - Ndb* ndb; - char name[FN_REFLEN + 1]; - HASH ndb_tables, ok_tables; - NDBDICT::List list; - - if (!(ndb= check_ndb_in_thd(thd))) - DBUG_RETURN(HA_ERR_NO_CONNECTION); - - if (dir) - DBUG_RETURN(0); // Discover of databases not yet supported - - // List tables in NDB - NDBDICT *dict= ndb->getDictionary(); - if (dict->listObjects(list, - NdbDictionary::Object::UserTable) != 0) - ERR_RETURN(dict->getNdbError()); - - if (my_hash_init(&ndb_tables, system_charset_info,list.count,0,0, - (my_hash_get_key)tables_get_key,0,0)) - { - DBUG_PRINT("error", ("Failed to init HASH ndb_tables")); - DBUG_RETURN(-1); - } - - if (my_hash_init(&ok_tables, system_charset_info,32,0,0, - (my_hash_get_key)tables_get_key,0,0)) - { - DBUG_PRINT("error", ("Failed to init HASH ok_tables")); - my_hash_free(&ndb_tables); - DBUG_RETURN(-1); - } - - for (i= 0 ; i < list.count ; i++) - { - NDBDICT::List::Element& elmt= list.elements[i]; - if (IS_TMP_PREFIX(elmt.name) || IS_NDB_BLOB_PREFIX(elmt.name)) - { - DBUG_PRINT("info", ("Skipping %s.%s in NDB", elmt.database, elmt.name)); - continue; - } - DBUG_PRINT("info", ("Found %s/%s in NDB", elmt.database, elmt.name)); - - // Add only tables that belongs to db - if (my_strcasecmp(system_charset_info, elmt.database, db)) - continue; - - // Apply wildcard to list of tables in NDB - if (wild) - { - if (lower_case_table_names) - { - if (wild_case_compare(files_charset_info, elmt.name, wild)) - continue; - } - else if (wild_compare(elmt.name,wild,0)) - continue; - } - DBUG_PRINT("info", ("Inserting %s into ndb_tables hash", elmt.name)); - my_hash_insert(&ndb_tables, (uchar*)thd->strdup(elmt.name)); - } - - LEX_STRING *file_name; - List_iterator<LEX_STRING> it(*files); - List<char> delete_list; - char *file_name_str; - while ((file_name=it++)) - { - bool file_on_disk= FALSE; - DBUG_PRINT("info", ("%s", file_name->str)); - if (my_hash_search(&ndb_tables, (uchar*) file_name->str, - file_name->length)) - { - build_table_filename(name, sizeof(name) - 1, db, - file_name->str, reg_ext, 0); - if (my_access(name, F_OK)) - { - DBUG_PRINT("info", ("Table %s listed and need discovery", - file_name->str)); - if (ndb_create_table_from_engine(thd, db, file_name->str)) - { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TABLE_EXISTS_ERROR, - "Discover of table %s.%s failed", - db, file_name->str); - continue; - } - } - DBUG_PRINT("info", ("%s existed in NDB _and_ on disk ", file_name->str)); - file_on_disk= TRUE; - } - - // Check for .ndb file with this name - build_table_filename(name, sizeof(name) - 1, db, - file_name->str, ha_ndb_ext, 0); - DBUG_PRINT("info", ("Check access for %s", name)); - if (my_access(name, F_OK)) - { - DBUG_PRINT("info", ("%s did not exist on disk", name)); - // .ndb file did not exist on disk, another table type - if (file_on_disk) - { - // Ignore this ndb table - uchar *record= my_hash_search(&ndb_tables, (uchar*) file_name->str, - file_name->length); - DBUG_ASSERT(record); - my_hash_delete(&ndb_tables, record); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TABLE_EXISTS_ERROR, - "Local table %s.%s shadows ndb table", - db, file_name->str); - } - continue; - } - if (file_on_disk) - { - // File existed in NDB and as frm file, put in ok_tables list - my_hash_insert(&ok_tables, (uchar*) file_name->str); - continue; - } - DBUG_PRINT("info", ("%s existed on disk", name)); - // The .ndb file exists on disk, but it's not in list of tables in ndb - // Verify that handler agrees table is gone. - if (ndbcluster_table_exists_in_engine(hton, thd, db, file_name->str) == - HA_ERR_NO_SUCH_TABLE) - { - DBUG_PRINT("info", ("NDB says %s does not exists", file_name->str)); - it.remove(); - // Put in list of tables to remove from disk - delete_list.push_back(thd->strdup(file_name->str)); - } - } - -#ifdef HAVE_NDB_BINLOG - /* setup logging to binlog for all discovered tables */ - { - char *end, *end1= name + - build_table_filename(name, sizeof(name) - 1, db, "", "", 0); - for (i= 0; i < ok_tables.records; i++) - { - file_name_str= (char*)my_hash_element(&ok_tables, i); - end= end1 + - tablename_to_filename(file_name_str, end1, sizeof(name) - (end1 - name)); - ndbcluster_create_binlog_setup(ndb, name, end-name, - db, file_name_str, TRUE); - } - } -#endif - - // Check for new files to discover - DBUG_PRINT("info", ("Checking for new files to discover")); - List<char> create_list; - for (i= 0 ; i < ndb_tables.records ; i++) - { - file_name_str= (char*) my_hash_element(&ndb_tables, i); - if (!my_hash_search(&ok_tables, (uchar*) file_name_str, - strlen(file_name_str))) - { - build_table_filename(name, sizeof(name) - 1, - db, file_name_str, reg_ext, 0); - if (my_access(name, F_OK)) - { - DBUG_PRINT("info", ("%s must be discovered", file_name_str)); - // File is in list of ndb tables and not in ok_tables - // This table need to be created - create_list.push_back(thd->strdup(file_name_str)); - } - } - } - - /* - Delete old files. - - ndbcluster_find_files() may be called from I_S code and ndbcluster_binlog - thread in situations when some tables are already open. This means that - code below will try to obtain exclusive metadata lock on some table - while holding shared meta-data lock on other tables. This might lead to a - deadlock but such a deadlock should be detected by MDL deadlock detector. - - XXX: the scenario described above is not covered with any test. - */ - List_iterator_fast<char> it3(delete_list); - while ((file_name_str= it3++)) - { - DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str)); - /* Delete the table and all related files. */ - TABLE_LIST table_list; - table_list.init_one_table(db, strlen(db), file_name_str, - strlen(file_name_str), file_name_str, - TL_WRITE); - table_list.mdl_request.set_type(MDL_EXCLUSIVE); - (void)mysql_rm_table_part2(thd, &table_list, - FALSE, /* if_exists */ - FALSE, /* drop_temporary */ - FALSE, /* drop_view */ - TRUE /* dont_log_query*/); - trans_commit_implicit(thd); /* Safety, should be unnecessary. */ - thd->mdl_context.release_transactional_locks(); - /* Clear error message that is returned when table is deleted */ - thd->clear_error(); - } - - /* Lock mutex before creating .FRM files. */ - /* Create new files. */ - List_iterator_fast<char> it2(create_list); - while ((file_name_str=it2++)) - { - DBUG_PRINT("info", ("Table %s need discovery", file_name_str)); - if (ndb_create_table_from_engine(thd, db, file_name_str) == 0) - { - LEX_STRING *tmp_file_name= 0; - tmp_file_name= thd->make_lex_string(tmp_file_name, file_name_str, - strlen(file_name_str), TRUE); - files->push_back(tmp_file_name); - } - } - - my_hash_free(&ok_tables); - my_hash_free(&ndb_tables); - - // Delete schema file from files - if (!strcmp(db, NDB_REP_DB)) - { - uint count = 0; - while (count++ < files->elements) - { - file_name = (LEX_STRING *)files->pop(); - if (!strcmp(file_name->str, NDB_SCHEMA_TABLE)) - { - DBUG_PRINT("info", ("skip %s.%s table, it should be hidden to user", - NDB_REP_DB, NDB_SCHEMA_TABLE)); - continue; - } - files->push_back(file_name); - } - } - } // extra bracket to avoid gcc 2.95.3 warning - DBUG_RETURN(0); -} - - -/* - Initialise all gloal variables before creating - a NDB Cluster table handler - */ - -/* Call back after cluster connect */ -static int connect_callback() -{ - mysql_mutex_lock(&LOCK_ndb_util_thread); - update_status_variables(g_ndb_cluster_connection); - - uint node_id, i= 0; - Ndb_cluster_connection_node_iter node_iter; - memset((void *)g_node_id_map, 0xFFFF, sizeof(g_node_id_map)); - while ((node_id= g_ndb_cluster_connection->get_next_node(node_iter))) - g_node_id_map[node_id]= i++; - - mysql_cond_signal(&COND_ndb_util_thread); - mysql_mutex_unlock(&LOCK_ndb_util_thread); - return 0; -} - -extern int ndb_dictionary_is_mysqld; - -#ifdef HAVE_PSI_INTERFACE - -#ifdef HAVE_NDB_BINLOG -PSI_mutex_key key_injector_mutex, key_ndb_schema_share_mutex, - key_ndb_schema_object_mutex; -#endif /* HAVE_NDB_BINLOG */ - -PSI_mutex_key key_NDB_SHARE_mutex, key_ndbcluster_mutex, - key_LOCK_ndb_util_thread; - -static PSI_mutex_info all_ndbcluster_mutexes[]= -{ -#ifdef HAVE_NDB_BINLOG - {& key_injector_mutex, "injector_mutex", PSI_FLAG_GLOBAL}, - {& key_ndb_schema_share_mutex, "ndb_schema_share_mutex", PSI_FLAG_GLOBAL}, - {& key_ndb_schema_object_mutex, "ndb_schema_object_mutex", PSI_FLAG_GLOBAL}, -#endif /* HAVE_NDB_BINLOG */ - {& key_NDB_SHARE_mutex, "NDB_SHARE::mutex", PSI_FLAG_GLOBAL}, - {& key_ndbcluster_mutex, "ndbcluster_mutex", PSI_FLAG_GLOBAL}, - {& key_LOCK_ndb_util_thread, "LOCK_ndb_util_thread", PSI_FLAG_GLOBAL} -}; - -#ifdef HAVE_NDB_BINLOG -PSI_cond_key key_injector_cond; -#endif /* HAVE_NDB_BINLOG */ - -PSI_cond_key key_COND_ndb_util_thread, key_COND_ndb_util_ready; - -static PSI_cond_info all_ndbcluster_conds[]= -{ -#ifdef HAVE_NDB_BINLOG - {& key_injector_cond, "injector_cond", PSI_FLAG_GLOBAL}, -#endif /* HAVE_NDB_BINLOG */ - {& key_COND_ndb_util_thread, "COND_ndb_util_thread", PSI_FLAG_GLOBAL}, - {& key_COND_ndb_util_ready, "COND_ndb_util_ready", PSI_FLAG_GLOBAL} -}; - -#ifdef HAVE_NDB_BINLOG -PSI_thread_key key_thread_ndb_binlog; -#endif /* HAVE_NDB_BINLOG */ -PSI_thread_key key_thread_ndb_util; - -static PSI_thread_info all_ndbcluster_threads[]= -{ -#ifdef HAVE_NDB_BINLOG - { &key_thread_ndb_binlog, "ndb_binlog", PSI_FLAG_GLOBAL}, -#endif /* HAVE_NDB_BINLOG */ - { &key_thread_ndb_util, "ndb_util", PSI_FLAG_GLOBAL} -}; - -PSI_file_key key_file_ndb; - -static PSI_file_info all_ndbcluster_files[]= -{ - { &key_file_ndb, "ndb", 0} -}; - -void init_ndbcluster_psi_keys() -{ - const char* category= "ndbcluster"; - int count; - - if (PSI_server == NULL) - return; - - count= array_elements(all_ndbcluster_mutexes); - PSI_server->register_mutex(category, all_ndbcluster_mutexes, count); - - count= array_elements(all_ndbcluster_conds); - PSI_server->register_cond(category, all_ndbcluster_conds, count); - - count= array_elements(all_ndbcluster_threads); - PSI_server->register_thread(category, all_ndbcluster_threads, count); - - count= array_elements(all_ndbcluster_files); - PSI_server->register_file(category, all_ndbcluster_files, count); -} -#endif /* HAVE_PSI_INTERFACE */ - -static int ndbcluster_init(void *p) -{ - int res; - DBUG_ENTER("ndbcluster_init"); - - if (ndbcluster_inited) - DBUG_RETURN(FALSE); - -#ifdef HAVE_PSI_INTERFACE - init_ndbcluster_psi_keys(); -#endif - - mysql_mutex_init(key_ndbcluster_mutex, - &ndbcluster_mutex, MY_MUTEX_INIT_FAST); - mysql_mutex_init(key_LOCK_ndb_util_thread, - &LOCK_ndb_util_thread, MY_MUTEX_INIT_FAST); - mysql_cond_init(key_COND_ndb_util_thread, &COND_ndb_util_thread, NULL); - mysql_cond_init(key_COND_ndb_util_ready, &COND_ndb_util_ready, NULL); - ndb_util_thread_running= -1; - ndbcluster_terminating= 0; - ndb_dictionary_is_mysqld= 1; - ndbcluster_hton= (handlerton *)p; - - { - handlerton *h= ndbcluster_hton; - h->state= SHOW_OPTION_YES; - h->db_type= DB_TYPE_NDBCLUSTER; - h->close_connection= ndbcluster_close_connection; - h->commit= ndbcluster_commit; - h->rollback= ndbcluster_rollback; - h->create= ndbcluster_create_handler; /* Create a new handler */ - h->drop_database= ndbcluster_drop_database; /* Drop a database */ - h->panic= ndbcluster_end; /* Panic call */ - h->show_status= ndbcluster_show_status; /* Show status */ - h->alter_tablespace= ndbcluster_alter_tablespace; /* Show status */ - h->partition_flags= ndbcluster_partition_flags; /* Partition flags */ - h->alter_table_flags=ndbcluster_alter_table_flags; /* Alter table flags */ - h->fill_is_table= ndbcluster_fill_is_table; -#ifdef HAVE_NDB_BINLOG - ndbcluster_binlog_init_handlerton(); -#endif - h->flags= HTON_CAN_RECREATE | HTON_TEMPORARY_NOT_SUPPORTED; - h->discover= ndbcluster_discover; - h->find_files= ndbcluster_find_files; - h->table_exists_in_engine= ndbcluster_table_exists_in_engine; - } - - // Format the connect string to be used for connecting to the cluster - int pos= 0; - char connectstring_buf[1024] = {0}; - if (opt_ndb_nodeid != 0) - pos+= my_snprintf(connectstring_buf, sizeof(connectstring_buf), - "nodeid=%u", opt_ndb_nodeid); - if (opt_ndb_mgmd_host) - pos+= my_snprintf(connectstring_buf+pos, sizeof(connectstring_buf)-pos, - "%s%s", pos ? "," : "", opt_ndb_mgmd_host); - if (opt_ndb_connectstring) - pos+= my_snprintf(connectstring_buf+pos, sizeof(connectstring_buf)-pos, - "%s%s", pos ? "," : "", opt_ndb_connectstring); - - - // Initialize ndb interface - ndb_init_internal(); - - // Set connectstring if specified - if (opt_ndb_connectstring != 0) - DBUG_PRINT("connectstring", ("%s", opt_ndb_connectstring)); - if ((g_ndb_cluster_connection= - new Ndb_cluster_connection(opt_ndb_connectstring)) == 0) - { - DBUG_PRINT("error",("Ndb_cluster_connection(%s)", - opt_ndb_connectstring)); - my_errno= HA_ERR_OUT_OF_MEM; - goto ndbcluster_init_error; - } - { - char buf[128]; - my_snprintf(buf, sizeof(buf), "mysqld --server-id=%lu", server_id); - g_ndb_cluster_connection->set_name(buf); - } - g_ndb_cluster_connection->set_optimized_node_selection - (THDVAR(0, optimized_node_selection)); - - // Create a Ndb object to open the connection to NDB - if ( (g_ndb= new Ndb(g_ndb_cluster_connection, "sys")) == 0 ) - { - DBUG_PRINT("error", ("failed to create global ndb object")); - my_errno= HA_ERR_OUT_OF_MEM; - goto ndbcluster_init_error; - } - if (g_ndb->init() != 0) - { - ERR_PRINT (g_ndb->getNdbError()); - goto ndbcluster_init_error; - } - - if ((res= g_ndb_cluster_connection->connect(0,0,0)) == 0) - { - connect_callback(); - DBUG_PRINT("info",("NDBCLUSTER storage engine at %s on port %d", - g_ndb_cluster_connection->get_connected_host(), - g_ndb_cluster_connection->get_connected_port())); - g_ndb_cluster_connection->wait_until_ready(10,3); - } - else if (res == 1) - { - if (g_ndb_cluster_connection->start_connect_thread(connect_callback)) - { - DBUG_PRINT("error", ("g_ndb_cluster_connection->start_connect_thread()")); - goto ndbcluster_init_error; - } -#ifndef DBUG_OFF - { - char buf[1024]; - DBUG_PRINT("info", - ("NDBCLUSTER storage engine not started, " - "will connect using %s", - g_ndb_cluster_connection-> - get_connectstring(buf,sizeof(buf)))); - } -#endif - } - else - { - DBUG_ASSERT(res == -1); - DBUG_PRINT("error", ("permanent error")); - goto ndbcluster_init_error; - } - - (void) my_hash_init(&ndbcluster_open_tables,system_charset_info,32,0,0, - (my_hash_get_key) ndbcluster_get_key,0,0); -#ifdef HAVE_NDB_BINLOG - /* start the ndb injector thread */ - if (ndbcluster_binlog_start()) - goto ndbcluster_init_error; -#endif /* HAVE_NDB_BINLOG */ - - // Create utility thread - pthread_t tmp; - if (mysql_thread_create(key_thread_ndb_util, - &tmp, &connection_attrib, ndb_util_thread_func, 0)) - { - DBUG_PRINT("error", ("Could not create ndb utility thread")); - my_hash_free(&ndbcluster_open_tables); - mysql_mutex_destroy(&ndbcluster_mutex); - mysql_mutex_destroy(&LOCK_ndb_util_thread); - mysql_cond_destroy(&COND_ndb_util_thread); - mysql_cond_destroy(&COND_ndb_util_ready); - goto ndbcluster_init_error; - } - - /* Wait for the util thread to start */ - mysql_mutex_lock(&LOCK_ndb_util_thread); - while (ndb_util_thread_running < 0) - mysql_cond_wait(&COND_ndb_util_ready, &LOCK_ndb_util_thread); - mysql_mutex_unlock(&LOCK_ndb_util_thread); - - if (!ndb_util_thread_running) - { - DBUG_PRINT("error", ("ndb utility thread exited prematurely")); - my_hash_free(&ndbcluster_open_tables); - mysql_mutex_destroy(&ndbcluster_mutex); - mysql_mutex_destroy(&LOCK_ndb_util_thread); - mysql_cond_destroy(&COND_ndb_util_thread); - mysql_cond_destroy(&COND_ndb_util_ready); - goto ndbcluster_init_error; - } - - ndbcluster_inited= 1; - DBUG_RETURN(FALSE); - -ndbcluster_init_error: - if (g_ndb) - delete g_ndb; - g_ndb= NULL; - if (g_ndb_cluster_connection) - delete g_ndb_cluster_connection; - g_ndb_cluster_connection= NULL; - ndbcluster_hton->state= SHOW_OPTION_DISABLED; // If we couldn't use handler - - DBUG_RETURN(TRUE); -} - -/** - Used to fill in INFORMATION_SCHEMA* tables. - - @param hton handle to the handlerton structure - @param thd the thread/connection descriptor - @param[in,out] tables the information schema table that is filled up - @param cond used for conditional pushdown to storage engine - @param schema_table_idx the table id that distinguishes the type of table - - @return Operation status - */ -static int ndbcluster_fill_is_table(handlerton *hton, - THD *thd, - TABLE_LIST *tables, - COND *cond, - enum enum_schema_tables schema_table_idx) -{ - int ret= 0; - - if (schema_table_idx == SCH_FILES) - { - ret= ndbcluster_fill_files_table(hton, thd, tables, cond); - } - - return ret; -} - - -static int ndbcluster_end(handlerton *hton, ha_panic_function type) -{ - DBUG_ENTER("ndbcluster_end"); - - if (!ndbcluster_inited) - DBUG_RETURN(0); - ndbcluster_inited= 0; - - /* wait for util thread to finish */ - sql_print_information("Stopping Cluster Utility thread"); - mysql_mutex_lock(&LOCK_ndb_util_thread); - ndbcluster_terminating= 1; - mysql_cond_signal(&COND_ndb_util_thread); - while (ndb_util_thread_running > 0) - mysql_cond_wait(&COND_ndb_util_ready, &LOCK_ndb_util_thread); - mysql_mutex_unlock(&LOCK_ndb_util_thread); - - -#ifdef HAVE_NDB_BINLOG - { - mysql_mutex_lock(&ndbcluster_mutex); - while (ndbcluster_open_tables.records) - { - NDB_SHARE *share= - (NDB_SHARE*) my_hash_element(&ndbcluster_open_tables, 0); -#ifndef DBUG_OFF - fprintf(stderr, "NDB: table share %s with use_count %d not freed\n", - share->key, share->use_count); -#endif - ndbcluster_real_free_share(&share); - } - mysql_mutex_unlock(&ndbcluster_mutex); - } -#endif - my_hash_free(&ndbcluster_open_tables); - - if (g_ndb) - { -#ifndef DBUG_OFF - Ndb::Free_list_usage tmp; - tmp.m_name= 0; - while (g_ndb->get_free_list_usage(&tmp)) - { - uint leaked= (uint) tmp.m_created - tmp.m_free; - if (leaked) - fprintf(stderr, "NDB: Found %u %s%s that %s not been released\n", - leaked, tmp.m_name, - (leaked == 1)?"":"'s", - (leaked == 1)?"has":"have"); - } -#endif - delete g_ndb; - g_ndb= NULL; - } - delete g_ndb_cluster_connection; - g_ndb_cluster_connection= NULL; - - // cleanup ndb interface - ndb_end_internal(); - - mysql_mutex_destroy(&ndbcluster_mutex); - mysql_mutex_destroy(&LOCK_ndb_util_thread); - mysql_cond_destroy(&COND_ndb_util_thread); - mysql_cond_destroy(&COND_ndb_util_ready); - DBUG_RETURN(0); -} - -void ha_ndbcluster::print_error(int error, myf errflag) -{ - DBUG_ENTER("ha_ndbcluster::print_error"); - DBUG_PRINT("enter", ("error: %d", error)); - - if (error == HA_ERR_NO_PARTITION_FOUND) - m_part_info->print_no_partition_found(table); - else - handler::print_error(error, errflag); - DBUG_VOID_RETURN; -} - - -/** - Static error print function called from static handler method - ndbcluster_commit and ndbcluster_rollback. -*/ - -void ndbcluster_print_error(int error, const NdbOperation *error_op) -{ - DBUG_ENTER("ndbcluster_print_error"); - TABLE_SHARE share; - const char *tab_name= (error_op) ? error_op->getTableName() : ""; - share.db.str= (char*) ""; - share.db.length= 0; - share.table_name.str= (char *) tab_name; - share.table_name.length= strlen(tab_name); - ha_ndbcluster error_handler(ndbcluster_hton, &share); - error_handler.print_error(error, MYF(0)); - DBUG_VOID_RETURN; -} - -/** - Set a given location from full pathname to database name. -*/ - -void ha_ndbcluster::set_dbname(const char *path_name, char *dbname) -{ - char *end, *ptr, *tmp_name; - char tmp_buff[FN_REFLEN + 1]; - - tmp_name= tmp_buff; - /* Scan name from the end */ - ptr= strend(path_name)-1; - while (ptr >= path_name && *ptr != '\\' && *ptr != '/') { - ptr--; - } - ptr--; - end= ptr; - while (ptr >= path_name && *ptr != '\\' && *ptr != '/') { - ptr--; - } - uint name_len= end - ptr; - memcpy(tmp_name, ptr + 1, name_len); - tmp_name[name_len]= '\0'; -#ifdef __WIN__ - /* Put to lower case */ - - ptr= tmp_name; - - while (*ptr != '\0') { - *ptr= tolower(*ptr); - ptr++; - } -#endif - filename_to_tablename(tmp_name, dbname, sizeof(tmp_buff) - 1); -} - -/** - Set m_dbname from full pathname to table file. -*/ - -void ha_ndbcluster::set_dbname(const char *path_name) -{ - set_dbname(path_name, m_dbname); -} - -/** - Set a given location from full pathname to table file. -*/ - -void -ha_ndbcluster::set_tabname(const char *path_name, char * tabname) -{ - char *end, *ptr, *tmp_name; - char tmp_buff[FN_REFLEN + 1]; - - tmp_name= tmp_buff; - /* Scan name from the end */ - end= strend(path_name)-1; - ptr= end; - while (ptr >= path_name && *ptr != '\\' && *ptr != '/') { - ptr--; - } - uint name_len= end - ptr; - memcpy(tmp_name, ptr + 1, end - ptr); - tmp_name[name_len]= '\0'; -#ifdef __WIN__ - /* Put to lower case */ - ptr= tmp_name; - - while (*ptr != '\0') { - *ptr= tolower(*ptr); - ptr++; - } -#endif - filename_to_tablename(tmp_name, tabname, sizeof(tmp_buff) - 1); -} - -/** - Set m_tabname from full pathname to table file. -*/ - -void ha_ndbcluster::set_tabname(const char *path_name) -{ - set_tabname(path_name, m_tabname); -} - - -ha_rows -ha_ndbcluster::records_in_range(uint inx, key_range *min_key, - key_range *max_key) -{ - KEY *key_info= table->key_info + inx; - uint key_length= key_info->key_length; - NDB_INDEX_TYPE idx_type= get_index_type(inx); - - DBUG_ENTER("records_in_range"); - // Prevent partial read of hash indexes by returning HA_POS_ERROR - if ((idx_type == UNIQUE_INDEX || idx_type == PRIMARY_KEY_INDEX) && - ((min_key && min_key->length < key_length) || - (max_key && max_key->length < key_length))) - DBUG_RETURN(HA_POS_ERROR); - - // Read from hash index with full key - // This is a "const" table which returns only one record! - if ((idx_type != ORDERED_INDEX) && - ((min_key && min_key->length == key_length) || - (max_key && max_key->length == key_length))) - DBUG_RETURN(1); - - if ((idx_type == PRIMARY_KEY_ORDERED_INDEX || - idx_type == UNIQUE_ORDERED_INDEX || - idx_type == ORDERED_INDEX) && - m_index[inx].index_stat != NULL) - { - NDB_INDEX_DATA& d=m_index[inx]; - const NDBINDEX* index= d.index; - Ndb* ndb=get_ndb(); - NdbTransaction* trans=NULL; - NdbIndexScanOperation* op=NULL; - int res=0; - Uint64 rows; - - do - { - // We must provide approx table rows - Uint64 table_rows=0; - Ndb_local_table_statistics *ndb_info= m_table_info; - if (ndb_info->records != ~(ha_rows)0 && ndb_info->records != 0) - { - table_rows = ndb_info->records; - DBUG_PRINT("info", ("use info->records: %lu", (ulong) table_rows)); - } - else - { - Ndb_statistics stat; - if ((res=ndb_get_table_statistics(this, TRUE, ndb, m_table, &stat))) - break; - table_rows=stat.row_count; - DBUG_PRINT("info", ("use db row_count: %lu", (ulong) table_rows)); - if (table_rows == 0) { - // Problem if autocommit=0 -#ifdef ndb_get_table_statistics_uses_active_trans - rows=0; - break; -#endif - } - } - - // Define scan op for the range - if ((trans=m_active_trans) == NULL || - trans->commitStatus() != NdbTransaction::Started) - { - DBUG_PRINT("info", ("no active trans")); - if (! (trans=ndb->startTransaction())) - ERR_BREAK(ndb->getNdbError(), res); - } - if (! (op=trans->getNdbIndexScanOperation(index, (NDBTAB*)m_table))) - ERR_BREAK(trans->getNdbError(), res); - if ((op->readTuples(NdbOperation::LM_CommittedRead)) == -1) - ERR_BREAK(op->getNdbError(), res); - const key_range *keys[2]={ min_key, max_key }; - if ((res=set_bounds(op, inx, TRUE, keys)) != 0) - break; - - // Decide if db should be contacted - int flags=0; - if (d.index_stat_query_count < d.index_stat_cache_entries || - (d.index_stat_update_freq != 0 && - d.index_stat_query_count % d.index_stat_update_freq == 0)) - { - DBUG_PRINT("info", ("force stat from db")); - flags|=NdbIndexStat::RR_UseDb; - } - if (d.index_stat->records_in_range(index, op, table_rows, &rows, flags) == -1) - ERR_BREAK(d.index_stat->getNdbError(), res); - d.index_stat_query_count++; - } while (0); - - if (trans != m_active_trans && rows == 0) - rows = 1; - if (trans != m_active_trans && trans != NULL) - ndb->closeTransaction(trans); - if (res != 0) - DBUG_RETURN(HA_POS_ERROR); - DBUG_RETURN(rows); - } - - DBUG_RETURN(10); /* Good guess when you don't know anything */ -} - -ulonglong ha_ndbcluster::table_flags(void) const -{ - THD *thd= current_thd; - ulonglong f= m_table_flags; - if (m_ha_not_exact_count) - f= f & ~HA_STATS_RECORDS_IS_EXACT; - /* - To allow for logging of ndb tables during stmt based logging; - flag cabablity, but also turn off flag for OWN_BINLOGGING - */ - if (thd->variables.binlog_format == BINLOG_FORMAT_STMT) - f= (f | HA_BINLOG_STMT_CAPABLE) & ~HA_HAS_OWN_BINLOGGING; - return f; -} -const char * ha_ndbcluster::table_type() const -{ - return("NDBCLUSTER"); -} -uint ha_ndbcluster::max_supported_record_length() const -{ - return NDB_MAX_TUPLE_SIZE; -} -uint ha_ndbcluster::max_supported_keys() const -{ - return MAX_KEY; -} -uint ha_ndbcluster::max_supported_key_parts() const -{ - return NDB_MAX_NO_OF_ATTRIBUTES_IN_KEY; -} -uint ha_ndbcluster::max_supported_key_length() const -{ - return NDB_MAX_KEY_SIZE; -} -uint ha_ndbcluster::max_supported_key_part_length() const -{ - return NDB_MAX_KEY_SIZE; -} -bool ha_ndbcluster::low_byte_first() const -{ -#ifdef WORDS_BIGENDIAN - return FALSE; -#else - return TRUE; -#endif -} -const char* ha_ndbcluster::index_type(uint key_number) -{ - switch (get_index_type(key_number)) { - case ORDERED_INDEX: - case UNIQUE_ORDERED_INDEX: - case PRIMARY_KEY_ORDERED_INDEX: - return "BTREE"; - case UNIQUE_INDEX: - case PRIMARY_KEY_INDEX: - default: - return "HASH"; - } -} - -uint8 ha_ndbcluster::table_cache_type() -{ - DBUG_ENTER("ha_ndbcluster::table_cache_type=HA_CACHE_TBL_ASKTRANSACT"); - DBUG_RETURN(HA_CACHE_TBL_ASKTRANSACT); -} - - -uint ndb_get_commitcount(THD *thd, char *dbname, char *tabname, - Uint64 *commit_count) -{ - char name[FN_REFLEN + 1]; - NDB_SHARE *share; - DBUG_ENTER("ndb_get_commitcount"); - - build_table_filename(name, sizeof(name) - 1, - dbname, tabname, "", 0); - DBUG_PRINT("enter", ("name: %s", name)); - mysql_mutex_lock(&ndbcluster_mutex); - if (!(share=(NDB_SHARE*) my_hash_search(&ndbcluster_open_tables, - (uchar*) name, - strlen(name)))) - { - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_PRINT("info", ("Table %s not found in ndbcluster_open_tables", name)); - DBUG_RETURN(1); - } - /* ndb_share reference temporary, free below */ - share->use_count++; - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - mysql_mutex_unlock(&ndbcluster_mutex); - - mysql_mutex_lock(&share->mutex); - if (opt_ndb_cache_check_time > 0) - { - if (share->commit_count != 0) - { - *commit_count= share->commit_count; -#ifndef DBUG_OFF - char buff[22]; -#endif - DBUG_PRINT("info", ("Getting commit_count: %s from share", - llstr(share->commit_count, buff))); - mysql_mutex_unlock(&share->mutex); - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - DBUG_RETURN(0); - } - } - DBUG_PRINT("info", ("Get commit_count from NDB")); - Ndb *ndb; - if (!(ndb= check_ndb_in_thd(thd))) - DBUG_RETURN(1); - if (ndb->setDatabaseName(dbname)) - { - ERR_RETURN(ndb->getNdbError()); - } - uint lock= share->commit_count_lock; - mysql_mutex_unlock(&share->mutex); - - struct Ndb_statistics stat; - { - Ndb_table_guard ndbtab_g(ndb->getDictionary(), tabname); - if (ndbtab_g.get_table() == 0 - || ndb_get_table_statistics(NULL, FALSE, ndb, ndbtab_g.get_table(), &stat)) - { - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - DBUG_RETURN(1); - } - } - - mysql_mutex_lock(&share->mutex); - if (share->commit_count_lock == lock) - { -#ifndef DBUG_OFF - char buff[22]; -#endif - DBUG_PRINT("info", ("Setting commit_count to %s", - llstr(stat.commit_count, buff))); - share->commit_count= stat.commit_count; - *commit_count= stat.commit_count; - } - else - { - DBUG_PRINT("info", ("Discarding commit_count, comit_count_lock changed")); - *commit_count= 0; - } - mysql_mutex_unlock(&share->mutex); - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - DBUG_RETURN(0); -} - - -/** - Check if a cached query can be used. - - This is done by comparing the supplied engine_data to commit_count of - the table. - - The commit_count is either retrieved from the share for the table, where - it has been cached by the util thread. If the util thread is not started, - NDB has to be contacetd to retrieve the commit_count, this will introduce - a small delay while waiting for NDB to answer. - - - @param thd thread handle - @param full_name concatenation of database name, - the null character '\\0', and the table name - @param full_name_len length of the full name, - i.e. len(dbname) + len(tablename) + 1 - @param engine_data parameter retrieved when query was first inserted into - the cache. If the value of engine_data is changed, - all queries for this table should be invalidated. - - @retval - TRUE Yes, use the query from cache - @retval - FALSE No, don't use the cached query, and if engine_data - has changed, all queries for this table should be invalidated - -*/ - -static my_bool -ndbcluster_cache_retrieval_allowed(THD *thd, - char *full_name, uint full_name_len, - ulonglong *engine_data) -{ - Uint64 commit_count; - char *dbname= full_name; - char *tabname= dbname+strlen(dbname)+1; -#ifndef DBUG_OFF - char buff[22], buff2[22]; -#endif - DBUG_ENTER("ndbcluster_cache_retrieval_allowed"); - DBUG_PRINT("enter", ("dbname: %s, tabname: %s", dbname, tabname)); - - if (thd->in_multi_stmt_transaction_mode()) - { - DBUG_PRINT("exit", ("No, don't use cache in transaction")); - DBUG_RETURN(FALSE); - } - - if (ndb_get_commitcount(thd, dbname, tabname, &commit_count)) - { - *engine_data= 0; /* invalidate */ - DBUG_PRINT("exit", ("No, could not retrieve commit_count")); - DBUG_RETURN(FALSE); - } - DBUG_PRINT("info", ("*engine_data: %s, commit_count: %s", - llstr(*engine_data, buff), llstr(commit_count, buff2))); - if (commit_count == 0) - { - *engine_data= 0; /* invalidate */ - DBUG_PRINT("exit", ("No, local commit has been performed")); - DBUG_RETURN(FALSE); - } - else if (*engine_data != commit_count) - { - *engine_data= commit_count; /* invalidate */ - DBUG_PRINT("exit", ("No, commit_count has changed")); - DBUG_RETURN(FALSE); - } - - DBUG_PRINT("exit", ("OK to use cache, engine_data: %s", - llstr(*engine_data, buff))); - DBUG_RETURN(TRUE); -} - - -/** - Register a table for use in the query cache. - - Fetch the commit_count for the table and return it in engine_data, - this will later be used to check if the table has changed, before - the cached query is reused. - - @param thd thread handle - @param full_name concatenation of database name, - the null character '\\0', and the table name - @param full_name_len length of the full name, - i.e. len(dbname) + len(tablename) + 1 - @param engine_callback function to be called before using cache on - this table - @param[out] engine_data commit_count for this table - - @retval - TRUE Yes, it's ok to cahce this query - @retval - FALSE No, don't cach the query -*/ - -my_bool -ha_ndbcluster::register_query_cache_table(THD *thd, - char *full_name, uint full_name_len, - qc_engine_callback *engine_callback, - ulonglong *engine_data) -{ - Uint64 commit_count; -#ifndef DBUG_OFF - char buff[22]; -#endif - DBUG_ENTER("ha_ndbcluster::register_query_cache_table"); - DBUG_PRINT("enter",("dbname: %s, tabname: %s", m_dbname, m_tabname)); - - if (thd->in_multi_stmt_transaction_mode()) - { - DBUG_PRINT("exit", ("Can't register table during transaction")); - DBUG_RETURN(FALSE); - } - - if (ndb_get_commitcount(thd, m_dbname, m_tabname, &commit_count)) - { - *engine_data= 0; - DBUG_PRINT("exit", ("Error, could not get commitcount")); - DBUG_RETURN(FALSE); - } - *engine_data= commit_count; - *engine_callback= ndbcluster_cache_retrieval_allowed; - DBUG_PRINT("exit", ("commit_count: %s", llstr(commit_count, buff))); - DBUG_RETURN(commit_count > 0); -} - - -/** - Handling the shared NDB_SHARE structure that is needed to - provide table locking. - - It's also used for sharing data with other NDB handlers - in the same MySQL Server. There is currently not much - data we want to or can share. -*/ - -static uchar *ndbcluster_get_key(NDB_SHARE *share, size_t *length, - my_bool not_used __attribute__((unused))) -{ - *length= share->key_length; - return (uchar*) share->key; -} - - -#ifndef DBUG_OFF - -static void print_share(const char* where, NDB_SHARE* share) -{ - fprintf(DBUG_FILE, - "%s %s.%s: use_count: %u, commit_count: %lu\n", - where, share->db, share->table_name, share->use_count, - (ulong) share->commit_count); - fprintf(DBUG_FILE, - " - key: %s, key_length: %d\n", - share->key, share->key_length); - -#ifdef HAVE_NDB_BINLOG - if (share->table) - fprintf(DBUG_FILE, - " - share->table: %p %s.%s\n", - share->table, share->table->s->db.str, - share->table->s->table_name.str); -#endif -} - - -static void print_ndbcluster_open_tables() -{ - DBUG_LOCK_FILE; - fprintf(DBUG_FILE, ">ndbcluster_open_tables\n"); - for (uint i= 0; i < ndbcluster_open_tables.records; i++) - print_share("", - (NDB_SHARE*)my_hash_element(&ndbcluster_open_tables, i)); - fprintf(DBUG_FILE, "<ndbcluster_open_tables\n"); - DBUG_UNLOCK_FILE; -} - -#endif - - -#define dbug_print_open_tables() \ - DBUG_EXECUTE("info", \ - print_ndbcluster_open_tables();); - -#define dbug_print_share(t, s) \ - DBUG_LOCK_FILE; \ - DBUG_EXECUTE("info", \ - print_share((t), (s));); \ - DBUG_UNLOCK_FILE; - - -#ifdef HAVE_NDB_BINLOG -/* - For some reason a share is still around, try to salvage the situation - by closing all cached tables. If the share still exists, there is an - error somewhere but only report this to the error log. Keep this - "trailing share" but rename it since there are still references to it - to avoid segmentation faults. There is a risk that the memory for - this trailing share leaks. - - Must be called with previous mysql_mutex_lock(&ndbcluster_mutex) -*/ -int handle_trailing_share(NDB_SHARE *share) -{ - THD *thd= current_thd; - static ulong trailing_share_id= 0; - DBUG_ENTER("handle_trailing_share"); - - /* ndb_share reference temporary, free below */ - ++share->use_count; - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - mysql_mutex_unlock(&ndbcluster_mutex); - - TABLE_LIST table_list; - bzero((char*) &table_list,sizeof(table_list)); - table_list.db= share->db; - table_list.alias= table_list.table_name= share->table_name; - close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT); - - mysql_mutex_lock(&ndbcluster_mutex); - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - if (!--share->use_count) - { - if (opt_ndb_extra_logging) - sql_print_information("NDB_SHARE: trailing share " - "%s(connect_count: %u) " - "released by close_cached_tables at " - "connect_count: %u", - share->key, - share->connect_count, - g_ndb_cluster_connection->get_connect_count()); - ndbcluster_real_free_share(&share); - DBUG_RETURN(0); - } - - /* - share still exists, if share has not been dropped by server - release that share - */ - if (share->state != NSS_DROPPED) - { - share->state= NSS_DROPPED; - /* ndb_share reference create free */ - DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u", - share->key, share->use_count)); - --share->use_count; - - if (share->use_count == 0) - { - if (opt_ndb_extra_logging) - sql_print_information("NDB_SHARE: trailing share " - "%s(connect_count: %u) " - "released after NSS_DROPPED check " - "at connect_count: %u", - share->key, - share->connect_count, - g_ndb_cluster_connection->get_connect_count()); - ndbcluster_real_free_share(&share); - DBUG_RETURN(0); - } - } - - sql_print_warning("NDB_SHARE: %s already exists use_count=%d." - " Moving away for safety, but possible memleak.", - share->key, share->use_count); - dbug_print_open_tables(); - - /* - Ndb share has not been released as it should - */ -#ifdef NOT_YET - DBUG_ASSERT(FALSE); -#endif - - /* - This is probably an error. We can however save the situation - at the cost of a possible mem leak, by "renaming" the share - - First remove from hash - */ - my_hash_delete(&ndbcluster_open_tables, (uchar*) share); - - /* - now give it a new name, just a running number - if space is not enough allocate some more - */ - { - const uint min_key_length= 10; - if (share->key_length < min_key_length) - { - share->key= (char*) alloc_root(&share->mem_root, min_key_length + 1); - share->key_length= min_key_length; - } - share->key_length= - my_snprintf(share->key, min_key_length + 1, "#leak%lu", - trailing_share_id++); - } - /* Keep it for possible the future trailing free */ - my_hash_insert(&ndbcluster_open_tables, (uchar*) share); - - DBUG_RETURN(0); -} - -/* - Rename share is used during rename table. -*/ -static int rename_share(NDB_SHARE *share, const char *new_key) -{ - NDB_SHARE *tmp; - mysql_mutex_lock(&ndbcluster_mutex); - uint new_length= (uint) strlen(new_key); - DBUG_PRINT("rename_share", ("old_key: %s old__length: %d", - share->key, share->key_length)); - if ((tmp= (NDB_SHARE*) my_hash_search(&ndbcluster_open_tables, - (uchar*) new_key, new_length))) - handle_trailing_share(tmp); - - /* remove the share from hash */ - my_hash_delete(&ndbcluster_open_tables, (uchar*) share); - dbug_print_open_tables(); - - /* save old stuff if insert should fail */ - uint old_length= share->key_length; - char *old_key= share->key; - - /* - now allocate and set the new key, db etc - enough space for key, db, and table_name - */ - share->key= (char*) alloc_root(&share->mem_root, 2 * (new_length + 1)); - strmov(share->key, new_key); - share->key_length= new_length; - - if (my_hash_insert(&ndbcluster_open_tables, (uchar*) share)) - { - // ToDo free the allocated stuff above? - DBUG_PRINT("error", ("rename_share: my_hash_insert %s failed", - share->key)); - share->key= old_key; - share->key_length= old_length; - if (my_hash_insert(&ndbcluster_open_tables, (uchar*) share)) - { - sql_print_error("rename_share: failed to recover %s", share->key); - DBUG_PRINT("error", ("rename_share: my_hash_insert %s failed", - share->key)); - } - dbug_print_open_tables(); - mysql_mutex_unlock(&ndbcluster_mutex); - return -1; - } - dbug_print_open_tables(); - - share->db= share->key + new_length + 1; - ha_ndbcluster::set_dbname(new_key, share->db); - share->table_name= share->db + strlen(share->db) + 1; - ha_ndbcluster::set_tabname(new_key, share->table_name); - - dbug_print_share("rename_share:", share); - if (share->table) - { - if (share->op == 0) - { - share->table->s->db.str= share->db; - share->table->s->db.length= strlen(share->db); - share->table->s->table_name.str= share->table_name; - share->table->s->table_name.length= strlen(share->table_name); - } - } - /* else rename will be handled when the ALTER event comes */ - share->old_names= old_key; - // ToDo free old_names after ALTER EVENT - - mysql_mutex_unlock(&ndbcluster_mutex); - return 0; -} -#endif - -/* - Increase refcount on existing share. - Always returns share and cannot fail. -*/ -NDB_SHARE *ndbcluster_get_share(NDB_SHARE *share) -{ - mysql_mutex_lock(&ndbcluster_mutex); - share->use_count++; - - dbug_print_open_tables(); - dbug_print_share("ndbcluster_get_share:", share); - mysql_mutex_unlock(&ndbcluster_mutex); - return share; -} - - -/* - Get a share object for key - - Returns share for key, and increases the refcount on the share. - - create_if_not_exists == TRUE: - creates share if it does not alreade exist - returns 0 only due to out of memory, and then sets my_error - - create_if_not_exists == FALSE: - returns 0 if share does not exist - - have_lock == TRUE, mysql_mutex_lock(&ndbcluster_mutex) already taken -*/ - -NDB_SHARE *ndbcluster_get_share(const char *key, TABLE *table, - bool create_if_not_exists, - bool have_lock) -{ - NDB_SHARE *share; - uint length= (uint) strlen(key); - DBUG_ENTER("ndbcluster_get_share"); - DBUG_PRINT("enter", ("key: '%s'", key)); - - if (!have_lock) - mysql_mutex_lock(&ndbcluster_mutex); - if (!(share= (NDB_SHARE*) my_hash_search(&ndbcluster_open_tables, - (uchar*) key, - length))) - { - if (!create_if_not_exists) - { - DBUG_PRINT("error", ("get_share: %s does not exist", key)); - if (!have_lock) - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_RETURN(0); - } - if ((share= (NDB_SHARE*) my_malloc(sizeof(*share), - MYF(MY_WME | MY_ZEROFILL)))) - { - MEM_ROOT **root_ptr= - my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC); - MEM_ROOT *old_root= *root_ptr; - init_sql_alloc(&share->mem_root, 1024, 0); - *root_ptr= &share->mem_root; // remember to reset before return - share->state= NSS_INITIAL; - /* enough space for key, db, and table_name */ - share->key= (char*) alloc_root(*root_ptr, 2 * (length + 1)); - share->key_length= length; - strmov(share->key, key); - if (my_hash_insert(&ndbcluster_open_tables, (uchar*) share)) - { - free_root(&share->mem_root, MYF(0)); - my_free(share); - *root_ptr= old_root; - if (!have_lock) - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_RETURN(0); - } - thr_lock_init(&share->lock); - mysql_mutex_init(key_NDB_SHARE_mutex, &share->mutex, MY_MUTEX_INIT_FAST); - share->commit_count= 0; - share->commit_count_lock= 0; - share->db= share->key + length + 1; - ha_ndbcluster::set_dbname(key, share->db); - share->table_name= share->db + strlen(share->db) + 1; - ha_ndbcluster::set_tabname(key, share->table_name); -#ifdef HAVE_NDB_BINLOG - if (ndbcluster_binlog_init_share(share, table)) - { - DBUG_PRINT("error", ("get_share: %s could not init share", key)); - ndbcluster_real_free_share(&share); - *root_ptr= old_root; - if (!have_lock) - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_RETURN(0); - } -#endif - *root_ptr= old_root; - } - else - { - DBUG_PRINT("error", ("get_share: failed to alloc share")); - if (!have_lock) - mysql_mutex_unlock(&ndbcluster_mutex); - my_error(ER_OUTOFMEMORY, MYF(0), static_cast<int>(sizeof(*share))); - DBUG_RETURN(0); - } - } - share->use_count++; - - dbug_print_open_tables(); - dbug_print_share("ndbcluster_get_share:", share); - if (!have_lock) - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_RETURN(share); -} - - -void ndbcluster_real_free_share(NDB_SHARE **share) -{ - DBUG_ENTER("ndbcluster_real_free_share"); - dbug_print_share("ndbcluster_real_free_share:", *share); - - my_hash_delete(&ndbcluster_open_tables, (uchar*) *share); - thr_lock_delete(&(*share)->lock); - mysql_mutex_destroy(&(*share)->mutex); - -#ifdef HAVE_NDB_BINLOG - if ((*share)->table) - { - // (*share)->table->mem_root is freed by closefrm - closefrm((*share)->table, 0); - // (*share)->table_share->mem_root is freed by free_table_share - free_table_share((*share)->table_share); -#ifndef DBUG_OFF - bzero((uchar*)(*share)->table_share, sizeof(*(*share)->table_share)); - bzero((uchar*)(*share)->table, sizeof(*(*share)->table)); - (*share)->table_share= 0; - (*share)->table= 0; -#endif - } -#endif - free_root(&(*share)->mem_root, MYF(0)); - my_free(*share); - *share= 0; - - dbug_print_open_tables(); - DBUG_VOID_RETURN; -} - - -void ndbcluster_free_share(NDB_SHARE **share, bool have_lock) -{ - if (!have_lock) - mysql_mutex_lock(&ndbcluster_mutex); - if ((*share)->util_lock == current_thd) - (*share)->util_lock= 0; - if (!--(*share)->use_count) - { - ndbcluster_real_free_share(share); - } - else - { - dbug_print_open_tables(); - dbug_print_share("ndbcluster_free_share:", *share); - } - if (!have_lock) - mysql_mutex_unlock(&ndbcluster_mutex); -} - - -static -int -ndb_get_table_statistics(ha_ndbcluster* file, bool report_error, Ndb* ndb, const NDBTAB *ndbtab, - struct Ndb_statistics * ndbstat) -{ - NdbTransaction* pTrans; - NdbError error; - int retries= 10; - int reterr= 0; - int retry_sleep= 30; /* 30 milliseconds, transaction */ -#ifndef DBUG_OFF - char buff[22], buff2[22], buff3[22], buff4[22]; -#endif - DBUG_ENTER("ndb_get_table_statistics"); - DBUG_PRINT("enter", ("table: %s", ndbtab->getName())); - - DBUG_ASSERT(ndbtab != 0); - - do - { - Uint64 rows, commits, fixed_mem, var_mem; - Uint32 size; - Uint32 count= 0; - Uint64 sum_rows= 0; - Uint64 sum_commits= 0; - Uint64 sum_row_size= 0; - Uint64 sum_mem= 0; - NdbScanOperation*pOp; - int check; - - if ((pTrans= ndb->startTransaction()) == NULL) - { - error= ndb->getNdbError(); - goto retry; - } - - if ((pOp= pTrans->getNdbScanOperation(ndbtab)) == NULL) - { - error= pTrans->getNdbError(); - goto retry; - } - - if (pOp->readTuples(NdbOperation::LM_CommittedRead)) - { - error= pOp->getNdbError(); - goto retry; - } - - if (pOp->interpret_exit_last_row() == -1) - { - error= pOp->getNdbError(); - goto retry; - } - - pOp->getValue(NdbDictionary::Column::ROW_COUNT, (char*)&rows); - pOp->getValue(NdbDictionary::Column::COMMIT_COUNT, (char*)&commits); - pOp->getValue(NdbDictionary::Column::ROW_SIZE, (char*)&size); - pOp->getValue(NdbDictionary::Column::FRAGMENT_FIXED_MEMORY, - (char*)&fixed_mem); - pOp->getValue(NdbDictionary::Column::FRAGMENT_VARSIZED_MEMORY, - (char*)&var_mem); - - if (pTrans->execute(NdbTransaction::NoCommit, - NdbOperation::AbortOnError, - TRUE) == -1) - { - error= pTrans->getNdbError(); - goto retry; - } - - while ((check= pOp->nextResult(TRUE, TRUE)) == 0) - { - sum_rows+= rows; - sum_commits+= commits; - if (sum_row_size < size) - sum_row_size= size; - sum_mem+= fixed_mem + var_mem; - count++; - } - - if (check == -1) - { - error= pOp->getNdbError(); - goto retry; - } - - pOp->close(TRUE); - - ndb->closeTransaction(pTrans); - - ndbstat->row_count= sum_rows; - ndbstat->commit_count= sum_commits; - ndbstat->row_size= sum_row_size; - ndbstat->fragment_memory= sum_mem; - - DBUG_PRINT("exit", ("records: %s commits: %s " - "row_size: %s mem: %s count: %u", - llstr(sum_rows, buff), - llstr(sum_commits, buff2), - llstr(sum_row_size, buff3), - llstr(sum_mem, buff4), - count)); - - DBUG_RETURN(0); -retry: - if(report_error) - { - if (file && pTrans) - { - reterr= file->ndb_err(pTrans); - } - else - { - const NdbError& tmp= error; - ERR_PRINT(tmp); - reterr= ndb_to_mysql_error(&tmp); - } - } - else - reterr= error.code; - - if (pTrans) - { - ndb->closeTransaction(pTrans); - pTrans= NULL; - } - if (error.status == NdbError::TemporaryError && retries--) - { - my_sleep(retry_sleep); - continue; - } - set_ndb_err(current_thd, error); - break; - } while(1); - DBUG_PRINT("exit", ("failed, reterr: %u, NdbError %u(%s)", reterr, - error.code, error.message)); - DBUG_RETURN(reterr); -} - -/** - Create a .ndb file to serve as a placeholder indicating - that the table with this name is a ndb table. -*/ - -int ha_ndbcluster::write_ndb_file(const char *name) -{ - File file; - bool error=1; - char path[FN_REFLEN]; - - DBUG_ENTER("write_ndb_file"); - DBUG_PRINT("enter", ("name: %s", name)); - - (void)strxnmov(path, FN_REFLEN-1, - mysql_data_home,"/",name,ha_ndb_ext,NullS); - - if ((file= mysql_file_create(key_file_ndb, path, CREATE_MODE, - O_RDWR | O_TRUNC, MYF(MY_WME))) >= 0) - { - // It's an empty file - error=0; - mysql_file_close(file, MYF(0)); - } - DBUG_RETURN(error); -} - -void -ha_ndbcluster::release_completed_operations(NdbTransaction *trans, - bool force_release) -{ - if (trans->hasBlobOperation()) - { - /* We are reading/writing BLOB fields, - releasing operation records is unsafe - */ - return; - } - if (!force_release) - { - if (get_thd_ndb(current_thd)->query_state & NDB_QUERY_MULTI_READ_RANGE) - { - /* We are batching reads and have not consumed all fetched - rows yet, releasing operation records is unsafe - */ - return; - } - } - trans->releaseCompletedOperations(); -} - -bool -ha_ndbcluster::null_value_index_search(KEY_MULTI_RANGE *ranges, - KEY_MULTI_RANGE *end_range, - HANDLER_BUFFER *buffer) -{ - DBUG_ENTER("null_value_index_search"); - KEY* key_info= table->key_info + active_index; - KEY_MULTI_RANGE *range= ranges; - ulong reclength= table->s->reclength; - uchar *curr= (uchar*)buffer->buffer; - uchar *end_of_buffer= (uchar*)buffer->buffer_end; - - for (; range<end_range && curr+reclength <= end_of_buffer; - range++) - { - const uchar *key= range->start_key.key; - uint key_len= range->start_key.length; - if (check_null_in_key(key_info, key, key_len)) - DBUG_RETURN(TRUE); - curr += reclength; - } - DBUG_RETURN(FALSE); -} - -#if 0 -/* MRR/NDB is disabled, for details see method declarations in ha_ndbcluster.h */ -int -ha_ndbcluster::read_multi_range_first(KEY_MULTI_RANGE **found_range_p, - KEY_MULTI_RANGE *ranges, - uint range_count, - bool sorted, - HANDLER_BUFFER *buffer) -{ - m_write_op= FALSE; - int res; - KEY* key_info= table->key_info + active_index; - NDB_INDEX_TYPE cur_index_type= get_index_type(active_index); - ulong reclength= table_share->reclength; - NdbOperation* op; - Thd_ndb *thd_ndb= get_thd_ndb(current_thd); - DBUG_ENTER("ha_ndbcluster::read_multi_range_first"); - - /** - * blobs and unique hash index with NULL can't be batched currently - */ - if (uses_blob_value() || - (cur_index_type == UNIQUE_INDEX && - has_null_in_unique_index(active_index) && - null_value_index_search(ranges, ranges+range_count, buffer)) - || m_delete_cannot_batch || m_update_cannot_batch) - { - m_disable_multi_read= TRUE; - DBUG_RETURN(handler::read_multi_range_first(found_range_p, - ranges, - range_count, - sorted, - buffer)); - } - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - thd_ndb->query_state|= NDB_QUERY_MULTI_READ_RANGE; - m_disable_multi_read= FALSE; - - /* - * Copy arguments into member variables - */ - m_multi_ranges= ranges; - multi_range_curr= ranges; - multi_range_end= ranges+range_count; - multi_range_sorted= sorted; - multi_range_buffer= buffer; - - /* - * read multi range will read ranges as follows (if not ordered) - * - * input read order - * ====== ========== - * pk-op 1 pk-op 1 - * pk-op 2 pk-op 2 - * range 3 range (3,5) NOTE result rows will be intermixed - * pk-op 4 pk-op 4 - * range 5 - * pk-op 6 pk-ok 6 - */ - - /* - * Variables for loop - */ - uchar *curr= (uchar*)buffer->buffer; - uchar *end_of_buffer= (uchar*)buffer->buffer_end; - NdbOperation::LockMode lm= - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); - bool need_pk = (lm == NdbOperation::LM_Read); - const NDBTAB *tab= m_table; - const NDBINDEX *unique_idx= m_index[active_index].unique_index; - const NDBINDEX *idx= m_index[active_index].index; - const NdbOperation* lastOp= m_active_trans->getLastDefinedOperation(); - NdbIndexScanOperation* scanOp= 0; - for (; multi_range_curr<multi_range_end && curr+reclength <= end_of_buffer; - multi_range_curr++) - { - part_id_range part_spec; - if (m_use_partition_function) - { - get_partition_set(table, curr, active_index, - &multi_range_curr->start_key, - &part_spec); - DBUG_PRINT("info", ("part_spec.start_part: %u part_spec.end_part: %u", - part_spec.start_part, part_spec.end_part)); - /* - If partition pruning has found no partition in set - we can skip this scan - */ - if (part_spec.start_part > part_spec.end_part) - { - /* - We can skip this partition since the key won't fit into any - partition - */ - curr += reclength; - multi_range_curr->range_flag |= SKIP_RANGE; - continue; - } - } - switch (cur_index_type) { - case PRIMARY_KEY_ORDERED_INDEX: - if (!(multi_range_curr->start_key.length == key_info->key_length && - multi_range_curr->start_key.flag == HA_READ_KEY_EXACT)) - goto range; - // else fall through - case PRIMARY_KEY_INDEX: - { - multi_range_curr->range_flag |= UNIQUE_RANGE; - if ((op= m_active_trans->getNdbOperation(tab)) && - !op->readTuple(lm) && - !set_primary_key(op, multi_range_curr->start_key.key) && - !define_read_attrs(curr, op) && - (!m_use_partition_function || - (op->setPartitionId(part_spec.start_part), TRUE))) - curr += reclength; - else - { - ERR_RETURN_PREPARE(res, - op ? op->getNdbError() : - m_active_trans->getNdbError()) - MYSQL_INDEX_READ_ROW_DONE(res); - DBUG_RETURN(res); - } - break; - } - break; - case UNIQUE_ORDERED_INDEX: - if (!(multi_range_curr->start_key.length == key_info->key_length && - multi_range_curr->start_key.flag == HA_READ_KEY_EXACT && - !check_null_in_key(key_info, multi_range_curr->start_key.key, - multi_range_curr->start_key.length))) - goto range; - // else fall through - case UNIQUE_INDEX: - { - multi_range_curr->range_flag |= UNIQUE_RANGE; - if ((op= m_active_trans->getNdbIndexOperation(unique_idx, tab)) && - !op->readTuple(lm) && - !set_index_key(op, key_info, multi_range_curr->start_key.key) && - !define_read_attrs(curr, op)) - curr += reclength; - else - { - ERR_RETURN_PREPARE(res, - op ? op->getNdbError() : - m_active_trans->getNdbError()); - MYSQL_INDEX_READ_ROW_DONE(res); - DBUG_RETURN(res); - } - break; - } - case ORDERED_INDEX: { - range: - multi_range_curr->range_flag &= ~(uint)UNIQUE_RANGE; - if (scanOp == 0) - { - if (m_multi_cursor) - { - scanOp= m_multi_cursor; - DBUG_ASSERT(scanOp->getSorted() == sorted); - DBUG_ASSERT(scanOp->getLockMode() == - (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type)); - if (scanOp->reset_bounds(m_force_send)) - { - res= ndb_err(m_active_trans); - MYSQL_INDEX_READ_ROW_DONE(res); - DBUG_RETURN(res); - } - - end_of_buffer -= reclength; - } - else if ((scanOp= m_active_trans->getNdbIndexScanOperation(idx, tab)) - &&!scanOp->readTuples(lm, 0, parallelism, sorted, - FALSE, TRUE, need_pk, TRUE) - &&!(m_cond && m_cond->generate_scan_filter(scanOp)) - &&!define_read_attrs(end_of_buffer-reclength, scanOp)) - { - m_multi_cursor= scanOp; - m_multi_range_cursor_result_ptr= end_of_buffer-reclength; - } - else - { - ERR_RETURN_PREPARE(res, - scanOp ? scanOp->getNdbError() : - m_active_trans->getNdbError()); - MYSQL_INDEX_READ_ROW_DONE(res); - DBUG_RETURN(res); - } - } - - const key_range *keys[2]= { &multi_range_curr->start_key, - &multi_range_curr->end_key }; - if ((res= set_bounds(scanOp, active_index, FALSE, keys, - multi_range_curr-ranges))) - { - MYSQL_INDEX_READ_ROW_DONE(res); - DBUG_RETURN(res); - } - break; - } - case UNDEFINED_INDEX: - DBUG_ASSERT(FALSE); - MYSQL_INDEX_READ_ROW_DONE(1); - DBUG_RETURN(1); - break; - } - } - - if (multi_range_curr != multi_range_end) - { - /* - * Mark that we're using entire buffer (even if might not) as - * we haven't read all ranges for some reason - * This as we don't want mysqld to reuse the buffer when we read - * the remaining ranges - */ - buffer->end_of_used_area= (uchar*)buffer->buffer_end; - } - else - { - buffer->end_of_used_area= curr; - } - - /* - * Set first operation in multi range - */ - m_current_multi_operation= - lastOp ? lastOp->next() : m_active_trans->getFirstDefinedOperation(); - if (!(res= execute_no_commit_ie(this, m_active_trans,true))) - { - m_multi_range_defined= multi_range_curr; - multi_range_curr= ranges; - m_multi_range_result_ptr= (uchar*)buffer->buffer; - res= loc_read_multi_range_next(found_range_p); - MYSQL_INDEX_READ_ROW_DONE(res); - DBUG_RETURN(res); - } - ERR_RETURN_PREPARE(res, m_active_trans->getNdbError()); - MYSQL_INDEX_READ_ROW_DONE(res); - DBUG_RETURN(res); -} - -#if 0 -#define DBUG_MULTI_RANGE(x) DBUG_PRINT("info", ("read_multi_range_next: case %d\n", x)); -#else -#define DBUG_MULTI_RANGE(x) -#endif - -int -ha_ndbcluster::read_multi_range_next(KEY_MULTI_RANGE ** multi_range_found_p) -{ - int rc; - DBUG_ENTER("ha_ndbcluster::read_multi_range_next"); - if (m_disable_multi_read) - { - DBUG_MULTI_RANGE(11); - DBUG_RETURN(handler::read_multi_range_next(multi_range_found_p)); - } - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - rc= loc_read_multi_range_next(multi_range_found_p); - MYSQL_INDEX_READ_ROW_DONE(rc); - DBUG_RETURN(rc); -} - -int ha_ndbcluster::loc_read_multi_range_next( - KEY_MULTI_RANGE **multi_range_found_p) -{ - int res; - int range_no; - ulong reclength= table_share->reclength; - const NdbOperation* op= m_current_multi_operation; - DBUG_ENTER("ha_ndbcluster::loc_read_multi_range_next"); - - for (;multi_range_curr < m_multi_range_defined; multi_range_curr++) - { - DBUG_MULTI_RANGE(12); - if (multi_range_curr->range_flag & SKIP_RANGE) - continue; - if (multi_range_curr->range_flag & UNIQUE_RANGE) - { - if (op->getNdbError().code == 0) - { - DBUG_MULTI_RANGE(13); - goto found_next; - } - - op= m_active_trans->getNextCompletedOperation(op); - m_multi_range_result_ptr += reclength; - continue; - } - else if (m_multi_cursor && !multi_range_sorted) - { - DBUG_MULTI_RANGE(1); - if ((res= fetch_next(m_multi_cursor)) == 0) - { - DBUG_MULTI_RANGE(2); - range_no= m_multi_cursor->get_range_no(); - goto found; - } - else - { - DBUG_MULTI_RANGE(14); - goto close_scan; - } - } - else if (m_multi_cursor && multi_range_sorted) - { - if (m_active_cursor && (res= fetch_next(m_multi_cursor))) - { - DBUG_MULTI_RANGE(3); - goto close_scan; - } - - range_no= m_multi_cursor->get_range_no(); - uint current_range_no= multi_range_curr - m_multi_ranges; - if ((uint) range_no == current_range_no) - { - DBUG_MULTI_RANGE(4); - // return current row - goto found; - } - else if (range_no > (int)current_range_no) - { - DBUG_MULTI_RANGE(5); - // wait with current row - m_active_cursor= 0; - continue; - } - else - { - DBUG_MULTI_RANGE(6); - // First fetch from cursor - DBUG_ASSERT(range_no == -1); - if ((res= m_multi_cursor->nextResult(TRUE))) - { - DBUG_MULTI_RANGE(15); - goto close_scan; - } - multi_range_curr--; // Will be increased in for-loop - continue; - } - } - else /* m_multi_cursor == 0 */ - { - DBUG_MULTI_RANGE(7); - /* - * Corresponds to range 5 in example in read_multi_range_first - */ - (void)1; - continue; - } - - DBUG_ASSERT(FALSE); // Should only get here via goto's -close_scan: - if (res == 1) - { - m_multi_cursor->close(FALSE, TRUE); - m_active_cursor= m_multi_cursor= 0; - DBUG_MULTI_RANGE(8); - continue; - } - else - { - DBUG_MULTI_RANGE(9); - DBUG_RETURN(ndb_err(m_active_trans)); - } - } - - if (multi_range_curr == multi_range_end) - { - DBUG_MULTI_RANGE(16); - Thd_ndb *thd_ndb= get_thd_ndb(current_thd); - thd_ndb->query_state&= NDB_QUERY_NORMAL; - DBUG_RETURN(HA_ERR_END_OF_FILE); - } - - /* - * Read remaining ranges - */ - MYSQL_INDEX_READ_ROW_DONE(1); - DBUG_RETURN(read_multi_range_first(multi_range_found_p, - multi_range_curr, - multi_range_end - multi_range_curr, - multi_range_sorted, - multi_range_buffer)); - -found: - /* - * Found a record belonging to a scan - */ - m_active_cursor= m_multi_cursor; - * multi_range_found_p= m_multi_ranges + range_no; - memcpy(table->record[0], m_multi_range_cursor_result_ptr, reclength); - setup_recattr(m_active_cursor->getFirstRecAttr()); - unpack_record(table->record[0]); - table->status= 0; - DBUG_RETURN(0); - -found_next: - /* - * Found a record belonging to a pk/index op, - * copy result and move to next to prepare for next call - */ - * multi_range_found_p= multi_range_curr; - memcpy(table->record[0], m_multi_range_result_ptr, reclength); - setup_recattr(op->getFirstRecAttr()); - unpack_record(table->record[0]); - table->status= 0; - - multi_range_curr++; - m_current_multi_operation= m_active_trans->getNextCompletedOperation(op); - m_multi_range_result_ptr += reclength; - DBUG_RETURN(0); -} -#endif - -int -ha_ndbcluster::setup_recattr(const NdbRecAttr* curr) -{ - DBUG_ENTER("setup_recattr"); - - Field **field, **end; - NdbValue *value= m_value; - - end= table->field + table_share->fields; - - for (field= table->field; field < end; field++, value++) - { - if ((* value).ptr) - { - DBUG_ASSERT(curr != 0); - NdbValue* val= m_value + curr->getColumn()->getColumnNo(); - DBUG_ASSERT(val->ptr); - val->rec= curr; - curr= curr->next(); - } - } - - DBUG_RETURN(0); -} - -/** - @param[in] comment table comment defined by user - - @return - table comment + additional -*/ -char* -ha_ndbcluster::update_table_comment( - /* out: table comment + additional */ - const char* comment)/* in: table comment defined by user */ -{ - uint length= strlen(comment); - if (length > 64000 - 3) - { - return((char*)comment); /* string too long */ - } - - Ndb* ndb; - if (!(ndb= get_ndb())) - { - return((char*)comment); - } - - if (ndb->setDatabaseName(m_dbname)) - { - return((char*)comment); - } - const NDBTAB* tab= m_table; - DBUG_ASSERT(tab != NULL); - - char *str; - const char *fmt="%s%snumber_of_replicas: %d"; - const unsigned fmt_len_plus_extra= length + strlen(fmt); - if ((str= (char*) my_malloc(fmt_len_plus_extra, MYF(0))) == NULL) - { - sql_print_error("ha_ndbcluster::update_table_comment: " - "my_malloc(%u) failed", (unsigned int)fmt_len_plus_extra); - return (char*)comment; - } - - my_snprintf(str,fmt_len_plus_extra,fmt,comment, - length > 0 ? " ":"", - tab->getReplicaCount()); - return str; -} - - -/** - Utility thread main loop. -*/ -pthread_handler_t ndb_util_thread_func(void *arg __attribute__((unused))) -{ - THD *thd; /* needs to be first for thread_stack */ - struct timespec abstime; - Thd_ndb *thd_ndb; - uint share_list_size= 0; - NDB_SHARE **share_list= NULL; - - my_thread_init(); - DBUG_ENTER("ndb_util_thread"); - DBUG_PRINT("enter", ("cache_check_time: %lu", opt_ndb_cache_check_time)); - - mysql_mutex_lock(&LOCK_ndb_util_thread); - - thd= new THD; /* note that contructor of THD uses DBUG_ */ - if (thd == NULL) - { - my_errno= HA_ERR_OUT_OF_MEM; - DBUG_RETURN(NULL); - } - THD_CHECK_SENTRY(thd); - pthread_detach_this_thread(); - ndb_util_thread= pthread_self(); - - thd->thread_stack= (char*)&thd; /* remember where our stack is */ - if (thd->store_globals()) - goto ndb_util_thread_fail; - thd->init_for_queries(); - thd->main_security_ctx.host_or_ip= ""; - thd->client_capabilities = 0; - my_net_init(&thd->net, 0); - thd->main_security_ctx.master_access= ~0; - thd->main_security_ctx.priv_user[0] = 0; - /* Do not use user-supplied timeout value for system threads. */ - thd->variables.lock_wait_timeout= LONG_TIMEOUT; - - CHARSET_INFO *charset_connection; - charset_connection= get_charset_by_csname("utf8", - MY_CS_PRIMARY, MYF(MY_WME)); - thd->variables.character_set_client= charset_connection; - thd->variables.character_set_results= charset_connection; - thd->variables.collation_connection= charset_connection; - thd->update_charset(); - - /* Signal successful initialization */ - ndb_util_thread_running= 1; - mysql_cond_signal(&COND_ndb_util_ready); - mysql_mutex_unlock(&LOCK_ndb_util_thread); - - /* - wait for mysql server to start - */ - mysql_mutex_lock(&LOCK_server_started); - while (!mysqld_server_started) - { - set_timespec(abstime, 1); - mysql_cond_timedwait(&COND_server_started, &LOCK_server_started, - &abstime); - if (ndbcluster_terminating) - { - mysql_mutex_unlock(&LOCK_server_started); - mysql_mutex_lock(&LOCK_ndb_util_thread); - goto ndb_util_thread_end; - } - } - mysql_mutex_unlock(&LOCK_server_started); - - /* - Wait for cluster to start - */ - mysql_mutex_lock(&LOCK_ndb_util_thread); - while (!ndb_cluster_node_id && (ndbcluster_hton->slot != ~(uint)0)) - { - /* ndb not connected yet */ - mysql_cond_wait(&COND_ndb_util_thread, &LOCK_ndb_util_thread); - if (ndbcluster_terminating) - goto ndb_util_thread_end; - } - mysql_mutex_unlock(&LOCK_ndb_util_thread); - - /* Get thd_ndb for this thread */ - if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb())) - { - sql_print_error("Could not allocate Thd_ndb object"); - mysql_mutex_lock(&LOCK_ndb_util_thread); - goto ndb_util_thread_end; - } - set_thd_ndb(thd, thd_ndb); - thd_ndb->options|= TNO_NO_LOG_SCHEMA_OP; - -#ifdef HAVE_NDB_BINLOG - if (opt_ndb_extra_logging && ndb_binlog_running) - sql_print_information("NDB Binlog: Ndb tables initially read only."); - /* create tables needed by the replication */ - ndbcluster_setup_binlog_table_shares(thd); -#else - /* - Get all table definitions from the storage node - */ - ndbcluster_find_all_files(thd); -#endif - - set_timespec(abstime, 0); - for (;;) - { - mysql_mutex_lock(&LOCK_ndb_util_thread); - if (!ndbcluster_terminating) - mysql_cond_timedwait(&COND_ndb_util_thread, - &LOCK_ndb_util_thread, - &abstime); - if (ndbcluster_terminating) /* Shutting down server */ - goto ndb_util_thread_end; - mysql_mutex_unlock(&LOCK_ndb_util_thread); -#ifdef NDB_EXTRA_DEBUG_UTIL_THREAD - DBUG_PRINT("ndb_util_thread", ("Started, opt_ndb_cache_check_time: %lu", - opt_ndb_cache_check_time)); -#endif - -#ifdef HAVE_NDB_BINLOG - /* - Check that the ndb_apply_status_share and ndb_schema_share - have been created. - If not try to create it - */ - if (!ndb_binlog_tables_inited) - ndbcluster_setup_binlog_table_shares(thd); -#endif - - if (opt_ndb_cache_check_time == 0) - { - /* Wake up in 1 second to check if value has changed */ - set_timespec(abstime, 1); - continue; - } - - /* Lock mutex and fill list with pointers to all open tables */ - NDB_SHARE *share; - mysql_mutex_lock(&ndbcluster_mutex); - uint i, open_count, record_count= ndbcluster_open_tables.records; - if (share_list_size < record_count) - { - NDB_SHARE ** new_share_list= new NDB_SHARE * [record_count]; - if (!new_share_list) - { - sql_print_warning("ndb util thread: malloc failure, " - "query cache not maintained properly"); - mysql_mutex_unlock(&ndbcluster_mutex); - goto next; // At least do not crash - } - delete [] share_list; - share_list_size= record_count; - share_list= new_share_list; - } - for (i= 0, open_count= 0; i < record_count; i++) - { - share= (NDB_SHARE *)my_hash_element(&ndbcluster_open_tables, i); -#ifdef HAVE_NDB_BINLOG - if ((share->use_count - (int) (share->op != 0) - (int) (share->op != 0)) - <= 0) - continue; // injector thread is the only user, skip statistics - share->util_lock= current_thd; // Mark that util thread has lock -#endif /* HAVE_NDB_BINLOG */ - /* ndb_share reference temporary, free below */ - share->use_count++; /* Make sure the table can't be closed */ - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - DBUG_PRINT("ndb_util_thread", - ("Found open table[%d]: %s, use_count: %d", - i, share->table_name, share->use_count)); - - /* Store pointer to table */ - share_list[open_count++]= share; - } - mysql_mutex_unlock(&ndbcluster_mutex); - - /* Iterate through the open files list */ - for (i= 0; i < open_count; i++) - { - share= share_list[i]; -#ifdef HAVE_NDB_BINLOG - if ((share->use_count - (int) (share->op != 0) - (int) (share->op != 0)) - <= 1) - { - /* - Util thread and injector thread is the only user, skip statistics - */ - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - continue; - } -#endif /* HAVE_NDB_BINLOG */ - DBUG_PRINT("ndb_util_thread", - ("Fetching commit count for: %s", share->key)); - - struct Ndb_statistics stat; - uint lock; - mysql_mutex_lock(&share->mutex); - lock= share->commit_count_lock; - mysql_mutex_unlock(&share->mutex); - { - /* Contact NDB to get commit count for table */ - Ndb* ndb= thd_ndb->ndb; - if (ndb->setDatabaseName(share->db)) - { - goto loop_next; - } - Ndb_table_guard ndbtab_g(ndb->getDictionary(), share->table_name); - if (ndbtab_g.get_table() && - ndb_get_table_statistics(NULL, FALSE, ndb, - ndbtab_g.get_table(), &stat) == 0) - { -#ifndef DBUG_OFF - char buff[22], buff2[22]; -#endif - DBUG_PRINT("info", - ("Table: %s commit_count: %s rows: %s", - share->key, - llstr(stat.commit_count, buff), - llstr(stat.row_count, buff2))); - } - else - { - DBUG_PRINT("ndb_util_thread", - ("Error: Could not get commit count for table %s", - share->key)); - stat.commit_count= 0; - } - } - loop_next: - mysql_mutex_lock(&share->mutex); - if (share->commit_count_lock == lock) - share->commit_count= stat.commit_count; - mysql_mutex_unlock(&share->mutex); - - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - } -next: - /* Calculate new time to wake up */ - int secs= 0; - int msecs= opt_ndb_cache_check_time; - - struct timeval tick_time; - gettimeofday(&tick_time, 0); - abstime.tv_sec= tick_time.tv_sec; - abstime.tv_nsec= tick_time.tv_usec * 1000; - - if (msecs >= 1000){ - secs= msecs / 1000; - msecs= msecs % 1000; - } - - abstime.tv_sec+= secs; - abstime.tv_nsec+= msecs * 1000000; - if (abstime.tv_nsec >= 1000000000) { - abstime.tv_sec+= 1; - abstime.tv_nsec-= 1000000000; - } - } - - mysql_mutex_lock(&LOCK_ndb_util_thread); - -ndb_util_thread_end: - net_end(&thd->net); -ndb_util_thread_fail: - if (share_list) - delete [] share_list; - thd->cleanup(); - delete thd; - - /* signal termination */ - ndb_util_thread_running= 0; - mysql_cond_signal(&COND_ndb_util_ready); - mysql_mutex_unlock(&LOCK_ndb_util_thread); - DBUG_PRINT("exit", ("ndb_util_thread")); - - DBUG_LEAVE; // Must match DBUG_ENTER() - my_thread_end(); - pthread_exit(0); - return NULL; // Avoid compiler warnings -} - -/* - Condition pushdown -*/ -/** - Push a condition to ndbcluster storage engine for evaluation - during table and index scans. The conditions will be stored on a stack - for possibly storing several conditions. The stack can be popped - by calling cond_pop, handler::extra(HA_EXTRA_RESET) (handler::reset()) - will clear the stack. - The current implementation supports arbitrary AND/OR nested conditions - with comparisons between columns and constants (including constant - expressions and function calls) and the following comparison operators: - =, !=, >, >=, <, <=, "is null", and "is not null". - - @retval - NULL The condition was supported and will be evaluated for each - row found during the scan - @retval - cond The condition was not supported and all rows will be returned from - the scan for evaluation (and thus not saved on stack) -*/ -const -COND* -ha_ndbcluster::cond_push(const COND *cond) -{ - DBUG_ENTER("cond_push"); - if (!m_cond) - m_cond= new ha_ndbcluster_cond; - if (!m_cond) - { - my_errno= HA_ERR_OUT_OF_MEM; - DBUG_RETURN(NULL); - } - DBUG_EXECUTE("where",print_where((COND *)cond, m_tabname, QT_ORDINARY);); - DBUG_RETURN(m_cond->cond_push(cond, table, (NDBTAB *)m_table)); -} - -/** - Pop the top condition from the condition stack of the handler instance. -*/ -void -ha_ndbcluster::cond_pop() -{ - if (m_cond) - m_cond->cond_pop(); -} - - -/* - get table space info for SHOW CREATE TABLE -*/ -char* ha_ndbcluster::get_tablespace_name(THD *thd, char* name, uint name_len) -{ - Ndb *ndb= check_ndb_in_thd(thd); - NDBDICT *ndbdict= ndb->getDictionary(); - NdbError ndberr; - Uint32 id; - ndb->setDatabaseName(m_dbname); - const NDBTAB *ndbtab= m_table; - DBUG_ASSERT(ndbtab != NULL); - if (!ndbtab->getTablespace(&id)) - { - return 0; - } - { - NdbDictionary::Tablespace ts= ndbdict->getTablespace(id); - ndberr= ndbdict->getNdbError(); - if(ndberr.classification != NdbError::NoError) - goto err; - DBUG_PRINT("info", ("Found tablespace '%s'", ts.getName())); - if (name) - { - strxnmov(name, name_len, ts.getName(), NullS); - return name; - } - else - return (my_strdup(ts.getName(), MYF(0))); - } -err: - if (ndberr.status == NdbError::TemporaryError) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_TEMPORARY_ERRMSG, ER(ER_GET_TEMPORARY_ERRMSG), - ndberr.code, ndberr.message, "NDB"); - else - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - ndberr.code, ndberr.message, "NDB"); - return 0; -} - -/* - Implements the SHOW NDB STATUS command. -*/ -bool -ndbcluster_show_status(handlerton *hton, THD* thd, stat_print_fn *stat_print, - enum ha_stat_type stat_type) -{ - char buf[IO_SIZE]; - uint buflen; - DBUG_ENTER("ndbcluster_show_status"); - - if (stat_type != HA_ENGINE_STATUS) - { - DBUG_RETURN(FALSE); - } - - update_status_variables(g_ndb_cluster_connection); - buflen= - my_snprintf(buf, sizeof(buf), - "cluster_node_id=%ld, " - "connected_host=%s, " - "connected_port=%ld, " - "number_of_data_nodes=%ld, " - "number_of_ready_data_nodes=%ld, " - "connect_count=%ld", - ndb_cluster_node_id, - ndb_connected_host, - ndb_connected_port, - ndb_number_of_data_nodes, - ndb_number_of_ready_data_nodes, - ndb_connect_count); - if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length, - STRING_WITH_LEN("connection"), buf, buflen)) - DBUG_RETURN(TRUE); - - if (get_thd_ndb(thd) && get_thd_ndb(thd)->ndb) - { - Ndb* ndb= (get_thd_ndb(thd))->ndb; - Ndb::Free_list_usage tmp; - tmp.m_name= 0; - while (ndb->get_free_list_usage(&tmp)) - { - buflen= - my_snprintf(buf, sizeof(buf), - "created=%u, free=%u, sizeof=%u", - tmp.m_created, tmp.m_free, tmp.m_sizeof); - if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length, - tmp.m_name, strlen(tmp.m_name), buf, buflen)) - DBUG_RETURN(TRUE); - } - } -#ifdef HAVE_NDB_BINLOG - ndbcluster_show_status_binlog(thd, stat_print, stat_type); -#endif - - DBUG_RETURN(FALSE); -} - - -/* - Create a table in NDB Cluster - */ -static uint get_no_fragments(ulonglong max_rows) -{ -#if MYSQL_VERSION_ID >= 50000 - uint acc_row_size= 25 + /*safety margin*/ 2; -#else - uint acc_row_size= pk_length*4; - /* add acc overhead */ - if (pk_length <= 8) /* main page will set the limit */ - acc_row_size+= 25 + /*safety margin*/ 2; - else /* overflow page will set the limit */ - acc_row_size+= 4 + /*safety margin*/ 4; -#endif - ulonglong acc_fragment_size= 512*1024*1024; -#if MYSQL_VERSION_ID >= 50100 - return (max_rows*acc_row_size)/acc_fragment_size+1; -#else - return ((max_rows*acc_row_size)/acc_fragment_size+1 - +1/*correct rounding*/)/2; -#endif -} - - -/* - Routine to adjust default number of partitions to always be a multiple - of number of nodes and never more than 4 times the number of nodes. - -*/ -static bool adjusted_frag_count(uint no_fragments, uint no_nodes, - uint &reported_frags) -{ - uint i= 0; - reported_frags= no_nodes; - while (reported_frags < no_fragments && ++i < 4 && - (reported_frags + no_nodes) < MAX_PARTITIONS) - reported_frags+= no_nodes; - return (reported_frags < no_fragments); -} - -int ha_ndbcluster::get_default_no_partitions(HA_CREATE_INFO *create_info) -{ - ha_rows max_rows, min_rows; - if (create_info) - { - max_rows= create_info->max_rows; - min_rows= create_info->min_rows; - } - else - { - max_rows= table_share->max_rows; - min_rows= table_share->min_rows; - } - uint reported_frags; - uint no_fragments= - get_no_fragments(max_rows >= min_rows ? max_rows : min_rows); - uint no_nodes= g_ndb_cluster_connection->no_db_nodes(); - if (adjusted_frag_count(no_fragments, no_nodes, reported_frags)) - { - push_warning(current_thd, - MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, - "Ndb might have problems storing the max amount of rows specified"); - } - return (int)reported_frags; -} - - -/* - Set-up auto-partitioning for NDB Cluster - - SYNOPSIS - set_auto_partitions() - part_info Partition info struct to set-up - - RETURN VALUE - NONE - - DESCRIPTION - Set-up auto partitioning scheme for tables that didn't define any - partitioning. We'll use PARTITION BY KEY() in this case which - translates into partition by primary key if a primary key exists - and partition by hidden key otherwise. -*/ - - -enum ndb_distribution_enum { ND_KEYHASH= 0, ND_LINHASH= 1 }; -static const char* distribution_names[]= { "KEYHASH", "LINHASH", NullS }; -static ulong default_ndb_distribution= ND_KEYHASH; -static TYPELIB distribution_typelib= { - array_elements(distribution_names) - 1, - "", - distribution_names, - NULL -}; -static MYSQL_SYSVAR_ENUM( - distribution, /* name */ - default_ndb_distribution, /* var */ - PLUGIN_VAR_RQCMDARG, - "Default distribution for new tables in ndb", - NULL, /* check func. */ - NULL, /* update func. */ - ND_KEYHASH, /* default */ - &distribution_typelib /* typelib */ -); - -void ha_ndbcluster::set_auto_partitions(partition_info *part_info) -{ - DBUG_ENTER("ha_ndbcluster::set_auto_partitions"); - part_info->list_of_part_fields= TRUE; - part_info->part_type= HASH_PARTITION; - switch (default_ndb_distribution) - { - case ND_KEYHASH: - part_info->linear_hash_ind= FALSE; - break; - case ND_LINHASH: - part_info->linear_hash_ind= TRUE; - break; - } - DBUG_VOID_RETURN; -} - - -int ha_ndbcluster::set_range_data(void *tab_ref, partition_info *part_info) -{ - NDBTAB *tab= (NDBTAB*)tab_ref; - int32 *range_data= (int32*)my_malloc(part_info->num_parts*sizeof(int32), - MYF(0)); - uint i; - int error= 0; - bool unsigned_flag= part_info->part_expr->unsigned_flag; - DBUG_ENTER("set_range_data"); - - if (!range_data) - { - mem_alloc_error(part_info->num_parts*sizeof(int32)); - DBUG_RETURN(1); - } - for (i= 0; i < part_info->num_parts; i++) - { - longlong range_val= part_info->range_int_array[i]; - if (unsigned_flag) - range_val-= 0x8000000000000000ULL; - if (range_val < INT_MIN32 || range_val >= INT_MAX32) - { - if ((i != part_info->num_parts - 1) || - (range_val != LONGLONG_MAX)) - { - my_error(ER_LIMITED_PART_RANGE, MYF(0), "NDB"); - error= 1; - goto error; - } - range_val= INT_MAX32; - } - range_data[i]= (int32)range_val; - } - tab->setRangeListData(range_data, sizeof(int32)*part_info->num_parts); -error: - my_free(range_data); - DBUG_RETURN(error); -} - -int ha_ndbcluster::set_list_data(void *tab_ref, partition_info *part_info) -{ - NDBTAB *tab= (NDBTAB*)tab_ref; - int32 *list_data= (int32*)my_malloc(part_info->num_list_values * 2 - * sizeof(int32), MYF(0)); - uint32 *part_id, i; - int error= 0; - bool unsigned_flag= part_info->part_expr->unsigned_flag; - DBUG_ENTER("set_list_data"); - - if (!list_data) - { - mem_alloc_error(part_info->num_list_values*2*sizeof(int32)); - DBUG_RETURN(1); - } - for (i= 0; i < part_info->num_list_values; i++) - { - LIST_PART_ENTRY *list_entry= &part_info->list_array[i]; - longlong list_val= list_entry->list_value; - if (unsigned_flag) - list_val-= 0x8000000000000000ULL; - if (list_val < INT_MIN32 || list_val > INT_MAX32) - { - my_error(ER_LIMITED_PART_RANGE, MYF(0), "NDB"); - error= 1; - goto error; - } - list_data[2*i]= (int32)list_val; - part_id= (uint32*)&list_data[2*i+1]; - *part_id= list_entry->partition_id; - } - tab->setRangeListData(list_data, 2*sizeof(int32)*part_info->num_list_values); -error: - my_free(list_data); - DBUG_RETURN(error); -} - -/* - User defined partitioning set-up. We need to check how many fragments the - user wants defined and which node groups to put those into. Later we also - want to attach those partitions to a tablespace. - - All the functionality of the partition function, partition limits and so - forth are entirely handled by the MySQL Server. There is one exception to - this rule for PARTITION BY KEY where NDB handles the hash function and - this type can thus be handled transparently also by NDB API program. - For RANGE, HASH and LIST and subpartitioning the NDB API programs must - implement the function to map to a partition. -*/ - -uint ha_ndbcluster::set_up_partition_info(partition_info *part_info, - TABLE *table, - void *tab_par) -{ - uint16 frag_data[MAX_PARTITIONS]; - char *ts_names[MAX_PARTITIONS]; - ulong fd_index= 0, i, j; - NDBTAB *tab= (NDBTAB*)tab_par; - NDBTAB::FragmentType ftype= NDBTAB::UserDefined; - partition_element *part_elem; - bool first= TRUE; - uint tot_ts_name_len; - List_iterator<partition_element> part_it(part_info->partitions); - int error; - DBUG_ENTER("ha_ndbcluster::set_up_partition_info"); - - if (part_info->part_type == HASH_PARTITION && - part_info->list_of_part_fields == TRUE) - { - Field **fields= part_info->part_field_array; - - if (part_info->linear_hash_ind) - ftype= NDBTAB::DistrKeyLin; - else - ftype= NDBTAB::DistrKeyHash; - - for (i= 0; i < part_info->part_field_list.elements; i++) - { - NDBCOL *col= tab->getColumn(fields[i]->field_index); - DBUG_PRINT("info",("setting dist key on %s", col->getName())); - col->setPartitionKey(TRUE); - } - } - else - { - if (!current_thd->variables.new_mode) - { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_ILLEGAL_HA_CREATE_OPTION, - ER(ER_ILLEGAL_HA_CREATE_OPTION), - ndbcluster_hton_name, - "LIST, RANGE and HASH partition disabled by default," - " use --new option to enable"); - DBUG_RETURN(HA_ERR_UNSUPPORTED); - } - /* - Create a shadow field for those tables that have user defined - partitioning. This field stores the value of the partition - function such that NDB can handle reorganisations of the data - even when the MySQL Server isn't available to assist with - calculation of the partition function value. - */ - NDBCOL col; - DBUG_PRINT("info", ("Generating partition func value field")); - col.setName("$PART_FUNC_VALUE"); - col.setType(NdbDictionary::Column::Int); - col.setLength(1); - col.setNullable(FALSE); - col.setPrimaryKey(FALSE); - col.setAutoIncrement(FALSE); - tab->addColumn(col); - if (part_info->part_type == RANGE_PARTITION) - { - if ((error= set_range_data((void*)tab, part_info))) - { - DBUG_RETURN(error); - } - } - else if (part_info->part_type == LIST_PARTITION) - { - if ((error= set_list_data((void*)tab, part_info))) - { - DBUG_RETURN(error); - } - } - } - tab->setFragmentType(ftype); - i= 0; - tot_ts_name_len= 0; - do - { - uint ng; - part_elem= part_it++; - if (!part_info->is_sub_partitioned()) - { - ng= part_elem->nodegroup_id; - if (first && ng == UNDEF_NODEGROUP) - ng= 0; - ts_names[fd_index]= part_elem->tablespace_name; - frag_data[fd_index++]= ng; - } - else - { - List_iterator<partition_element> sub_it(part_elem->subpartitions); - j= 0; - do - { - part_elem= sub_it++; - ng= part_elem->nodegroup_id; - if (first && ng == UNDEF_NODEGROUP) - ng= 0; - ts_names[fd_index]= part_elem->tablespace_name; - frag_data[fd_index++]= ng; - } while (++j < part_info->num_subparts); - } - first= FALSE; - } while (++i < part_info->num_parts); - tab->setDefaultNoPartitionsFlag(part_info->use_default_num_partitions); - tab->setLinearFlag(part_info->linear_hash_ind); - { - ha_rows max_rows= table_share->max_rows; - ha_rows min_rows= table_share->min_rows; - if (max_rows < min_rows) - max_rows= min_rows; - if (max_rows != (ha_rows)0) /* default setting, don't set fragmentation */ - { - tab->setMaxRows(max_rows); - tab->setMinRows(min_rows); - } - } - tab->setTablespaceNames(ts_names, fd_index*sizeof(char*)); - tab->setFragmentCount(fd_index); - tab->setFragmentData(&frag_data, fd_index*2); - DBUG_RETURN(0); -} - - -bool ha_ndbcluster::check_if_incompatible_data(HA_CREATE_INFO *create_info, - uint table_changes) -{ - DBUG_ENTER("ha_ndbcluster::check_if_incompatible_data"); - uint i; - const NDBTAB *tab= (const NDBTAB *) m_table; - - if (THDVAR(current_thd, use_copying_alter_table)) - { - DBUG_PRINT("info", ("On-line alter table disabled")); - DBUG_RETURN(COMPATIBLE_DATA_NO); - } - - int pk= 0; - int ai= 0; - - if (create_info->tablespace) - create_info->storage_media = HA_SM_DISK; - else - create_info->storage_media = HA_SM_MEMORY; - - for (i= 0; i < table->s->fields; i++) - { - Field *field= table->field[i]; - const NDBCOL *col= tab->getColumn(i); - if ((col->getStorageType() == NDB_STORAGETYPE_MEMORY && create_info->storage_media != HA_SM_MEMORY) || - (col->getStorageType() == NDB_STORAGETYPE_DISK && create_info->storage_media != HA_SM_DISK)) - { - DBUG_PRINT("info", ("Column storage media is changed")); - DBUG_RETURN(COMPATIBLE_DATA_NO); - } - - if (field->flags & FIELD_IS_RENAMED) - { - DBUG_PRINT("info", ("Field has been renamed, copy table")); - DBUG_RETURN(COMPATIBLE_DATA_NO); - } - if ((field->flags & FIELD_IN_ADD_INDEX) && - col->getStorageType() == NdbDictionary::Column::StorageTypeDisk) - { - DBUG_PRINT("info", ("add/drop index not supported for disk stored column")); - DBUG_RETURN(COMPATIBLE_DATA_NO); - } - - if (field->flags & PRI_KEY_FLAG) - pk=1; - if (field->flags & FIELD_IN_ADD_INDEX) - ai=1; - } - - char tablespace_name[FN_LEN + 1]; - if (get_tablespace_name(current_thd, tablespace_name, FN_LEN)) - { - if (create_info->tablespace) - { - if (strcmp(create_info->tablespace, tablespace_name)) - { - DBUG_PRINT("info", ("storage media is changed, old tablespace=%s, new tablespace=%s", - tablespace_name, create_info->tablespace)); - DBUG_RETURN(COMPATIBLE_DATA_NO); - } - } - else - { - DBUG_PRINT("info", ("storage media is changed, old is DISK and tablespace=%s, new is MEM", - tablespace_name)); - DBUG_RETURN(COMPATIBLE_DATA_NO); - } - } - else - { - if (create_info->storage_media != HA_SM_MEMORY) - { - DBUG_PRINT("info", ("storage media is changed, old is MEM, new is DISK and tablespace=%s", - create_info->tablespace)); - DBUG_RETURN(COMPATIBLE_DATA_NO); - } - } - - if (table_changes != IS_EQUAL_YES) - DBUG_RETURN(COMPATIBLE_DATA_NO); - - /* Check that auto_increment value was not changed */ - if ((create_info->used_fields & HA_CREATE_USED_AUTO) && - create_info->auto_increment_value != 0) - { - DBUG_PRINT("info", ("auto_increment value changed")); - DBUG_RETURN(COMPATIBLE_DATA_NO); - } - - /* Check that row format didn't change */ - if ((create_info->used_fields & HA_CREATE_USED_AUTO) && - get_row_type() != create_info->row_type) - { - DBUG_PRINT("info", ("row format changed")); - DBUG_RETURN(COMPATIBLE_DATA_NO); - } - - DBUG_PRINT("info", ("new table seems compatible")); - DBUG_RETURN(COMPATIBLE_DATA_YES); -} - -bool set_up_tablespace(st_alter_tablespace *alter_info, - NdbDictionary::Tablespace *ndb_ts) -{ - ndb_ts->setName(alter_info->tablespace_name); - ndb_ts->setExtentSize(alter_info->extent_size); - ndb_ts->setDefaultLogfileGroup(alter_info->logfile_group_name); - return FALSE; -} - -bool set_up_datafile(st_alter_tablespace *alter_info, - NdbDictionary::Datafile *ndb_df) -{ - if (alter_info->max_size > 0) - { - my_error(ER_TABLESPACE_AUTO_EXTEND_ERROR, MYF(0)); - return TRUE; - } - ndb_df->setPath(alter_info->data_file_name); - ndb_df->setSize(alter_info->initial_size); - ndb_df->setTablespace(alter_info->tablespace_name); - return FALSE; -} - -bool set_up_logfile_group(st_alter_tablespace *alter_info, - NdbDictionary::LogfileGroup *ndb_lg) -{ - ndb_lg->setName(alter_info->logfile_group_name); - ndb_lg->setUndoBufferSize(alter_info->undo_buffer_size); - return FALSE; -} - -bool set_up_undofile(st_alter_tablespace *alter_info, - NdbDictionary::Undofile *ndb_uf) -{ - ndb_uf->setPath(alter_info->undo_file_name); - ndb_uf->setSize(alter_info->initial_size); - ndb_uf->setLogfileGroup(alter_info->logfile_group_name); - return FALSE; -} - -int ndbcluster_alter_tablespace(handlerton *hton, - THD* thd, st_alter_tablespace *alter_info) -{ - int is_tablespace= 0; - NdbError err; - NDBDICT *dict; - int error; - const char *errmsg; - Ndb *ndb; - DBUG_ENTER("ha_ndbcluster::alter_tablespace"); - LINT_INIT(errmsg); - - ndb= check_ndb_in_thd(thd); - if (ndb == NULL) - { - DBUG_RETURN(HA_ERR_NO_CONNECTION); - } - dict= ndb->getDictionary(); - - switch (alter_info->ts_cmd_type){ - case (CREATE_TABLESPACE): - { - error= ER_CREATE_FILEGROUP_FAILED; - - NdbDictionary::Tablespace ndb_ts; - NdbDictionary::Datafile ndb_df; - NdbDictionary::ObjectId objid; - if (set_up_tablespace(alter_info, &ndb_ts)) - { - DBUG_RETURN(1); - } - if (set_up_datafile(alter_info, &ndb_df)) - { - DBUG_RETURN(1); - } - errmsg= "TABLESPACE"; - if (dict->createTablespace(ndb_ts, &objid)) - { - DBUG_PRINT("error", ("createTablespace returned %d", error)); - goto ndberror; - } - DBUG_PRINT("alter_info", ("Successfully created Tablespace")); - errmsg= "DATAFILE"; - if (dict->createDatafile(ndb_df)) - { - err= dict->getNdbError(); - NdbDictionary::Tablespace tmp= dict->getTablespace(ndb_ts.getName()); - if (dict->getNdbError().code == 0 && - tmp.getObjectId() == objid.getObjectId() && - tmp.getObjectVersion() == objid.getObjectVersion()) - { - dict->dropTablespace(tmp); - } - - DBUG_PRINT("error", ("createDatafile returned %d", error)); - goto ndberror2; - } - is_tablespace= 1; - break; - } - case (ALTER_TABLESPACE): - { - error= ER_ALTER_FILEGROUP_FAILED; - if (alter_info->ts_alter_tablespace_type == ALTER_TABLESPACE_ADD_FILE) - { - NdbDictionary::Datafile ndb_df; - if (set_up_datafile(alter_info, &ndb_df)) - { - DBUG_RETURN(1); - } - errmsg= " CREATE DATAFILE"; - if (dict->createDatafile(ndb_df)) - { - goto ndberror; - } - } - else if(alter_info->ts_alter_tablespace_type == ALTER_TABLESPACE_DROP_FILE) - { - NdbDictionary::Tablespace ts= dict->getTablespace(alter_info->tablespace_name); - NdbDictionary::Datafile df= dict->getDatafile(0, alter_info->data_file_name); - NdbDictionary::ObjectId objid; - df.getTablespaceId(&objid); - if (ts.getObjectId() == objid.getObjectId() && - strcmp(df.getPath(), alter_info->data_file_name) == 0) - { - errmsg= " DROP DATAFILE"; - if (dict->dropDatafile(df)) - { - goto ndberror; - } - } - else - { - DBUG_PRINT("error", ("No such datafile")); - my_error(ER_ALTER_FILEGROUP_FAILED, MYF(0), " NO SUCH FILE"); - DBUG_RETURN(1); - } - } - else - { - DBUG_PRINT("error", ("Unsupported alter tablespace: %d", - alter_info->ts_alter_tablespace_type)); - DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); - } - is_tablespace= 1; - break; - } - case (CREATE_LOGFILE_GROUP): - { - error= ER_CREATE_FILEGROUP_FAILED; - NdbDictionary::LogfileGroup ndb_lg; - NdbDictionary::Undofile ndb_uf; - NdbDictionary::ObjectId objid; - if (alter_info->undo_file_name == NULL) - { - /* - REDO files in LOGFILE GROUP not supported yet - */ - DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); - } - if (set_up_logfile_group(alter_info, &ndb_lg)) - { - DBUG_RETURN(1); - } - errmsg= "LOGFILE GROUP"; - if (dict->createLogfileGroup(ndb_lg, &objid)) - { - goto ndberror; - } - DBUG_PRINT("alter_info", ("Successfully created Logfile Group")); - if (set_up_undofile(alter_info, &ndb_uf)) - { - DBUG_RETURN(1); - } - errmsg= "UNDOFILE"; - if (dict->createUndofile(ndb_uf)) - { - err= dict->getNdbError(); - NdbDictionary::LogfileGroup tmp= dict->getLogfileGroup(ndb_lg.getName()); - if (dict->getNdbError().code == 0 && - tmp.getObjectId() == objid.getObjectId() && - tmp.getObjectVersion() == objid.getObjectVersion()) - { - dict->dropLogfileGroup(tmp); - } - goto ndberror2; - } - break; - } - case (ALTER_LOGFILE_GROUP): - { - error= ER_ALTER_FILEGROUP_FAILED; - if (alter_info->undo_file_name == NULL) - { - /* - REDO files in LOGFILE GROUP not supported yet - */ - DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); - } - NdbDictionary::Undofile ndb_uf; - if (set_up_undofile(alter_info, &ndb_uf)) - { - DBUG_RETURN(1); - } - errmsg= "CREATE UNDOFILE"; - if (dict->createUndofile(ndb_uf)) - { - goto ndberror; - } - break; - } - case (DROP_TABLESPACE): - { - error= ER_DROP_FILEGROUP_FAILED; - errmsg= "TABLESPACE"; - if (dict->dropTablespace(dict->getTablespace(alter_info->tablespace_name))) - { - goto ndberror; - } - is_tablespace= 1; - break; - } - case (DROP_LOGFILE_GROUP): - { - error= ER_DROP_FILEGROUP_FAILED; - errmsg= "LOGFILE GROUP"; - if (dict->dropLogfileGroup(dict->getLogfileGroup(alter_info->logfile_group_name))) - { - goto ndberror; - } - break; - } - case (CHANGE_FILE_TABLESPACE): - { - DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); - } - case (ALTER_ACCESS_MODE_TABLESPACE): - { - DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); - } - default: - { - DBUG_RETURN(HA_ADMIN_NOT_IMPLEMENTED); - } - } -#ifdef HAVE_NDB_BINLOG - if (is_tablespace) - ndbcluster_log_schema_op(thd, 0, - thd->query(), thd->query_length(), - "", alter_info->tablespace_name, - 0, 0, - SOT_TABLESPACE, 0, 0); - else - ndbcluster_log_schema_op(thd, 0, - thd->query(), thd->query_length(), - "", alter_info->logfile_group_name, - 0, 0, - SOT_LOGFILE_GROUP, 0, 0); -#endif - DBUG_RETURN(FALSE); - -ndberror: - err= dict->getNdbError(); -ndberror2: - set_ndb_err(thd, err); - ndb_to_mysql_error(&err); - - my_error(error, MYF(0), errmsg); - DBUG_RETURN(1); -} - - -bool ha_ndbcluster::get_no_parts(const char *name, uint *num_parts) -{ - Ndb *ndb; - NDBDICT *dict; - int err; - DBUG_ENTER("ha_ndbcluster::get_no_parts"); - LINT_INIT(err); - - set_dbname(name); - set_tabname(name); - for (;;) - { - if (check_ndb_connection()) - { - err= HA_ERR_NO_CONNECTION; - break; - } - ndb= get_ndb(); - ndb->setDatabaseName(m_dbname); - Ndb_table_guard ndbtab_g(dict= ndb->getDictionary(), m_tabname); - if (!ndbtab_g.get_table()) - ERR_BREAK(dict->getNdbError(), err); - *num_parts= ndbtab_g.get_table()->getFragmentCount(); - DBUG_RETURN(FALSE); - } - - print_error(err, MYF(0)); - DBUG_RETURN(TRUE); -} - -static int ndbcluster_fill_files_table(handlerton *hton, - THD *thd, - TABLE_LIST *tables, - COND *cond) -{ - TABLE* table= tables->table; - Ndb *ndb= check_ndb_in_thd(thd); - NdbDictionary::Dictionary* dict= ndb->getDictionary(); - NdbDictionary::Dictionary::List dflist; - NdbError ndberr; - uint i; - DBUG_ENTER("ndbcluster_fill_files_table"); - - dict->listObjects(dflist, NdbDictionary::Object::Datafile); - ndberr= dict->getNdbError(); - if (ndberr.classification != NdbError::NoError) - ERR_RETURN(ndberr); - - for (i= 0; i < dflist.count; i++) - { - NdbDictionary::Dictionary::List::Element& elt = dflist.elements[i]; - Ndb_cluster_connection_node_iter iter; - uint id; - - g_ndb_cluster_connection->init_get_next_node(iter); - - while ((id= g_ndb_cluster_connection->get_next_node(iter))) - { - init_fill_schema_files_row(table); - NdbDictionary::Datafile df= dict->getDatafile(id, elt.name); - ndberr= dict->getNdbError(); - if(ndberr.classification != NdbError::NoError) - { - if (ndberr.classification == NdbError::SchemaError) - continue; - - if (ndberr.classification == NdbError::UnknownResultError) - continue; - - ERR_RETURN(ndberr); - } - NdbDictionary::Tablespace ts= dict->getTablespace(df.getTablespace()); - ndberr= dict->getNdbError(); - if (ndberr.classification != NdbError::NoError) - { - if (ndberr.classification == NdbError::SchemaError) - continue; - ERR_RETURN(ndberr); - } - table->field[IS_FILES_TABLE_CATALOG]->store(STRING_WITH_LEN("def"), - system_charset_info); - table->field[IS_FILES_FILE_NAME]->set_notnull(); - table->field[IS_FILES_FILE_NAME]->store(elt.name, strlen(elt.name), - system_charset_info); - table->field[IS_FILES_FILE_TYPE]->set_notnull(); - table->field[IS_FILES_FILE_TYPE]->store("DATAFILE",8, - system_charset_info); - table->field[IS_FILES_TABLESPACE_NAME]->set_notnull(); - table->field[IS_FILES_TABLESPACE_NAME]->store(df.getTablespace(), - strlen(df.getTablespace()), - system_charset_info); - table->field[IS_FILES_LOGFILE_GROUP_NAME]->set_notnull(); - table->field[IS_FILES_LOGFILE_GROUP_NAME]-> - store(ts.getDefaultLogfileGroup(), - strlen(ts.getDefaultLogfileGroup()), - system_charset_info); - table->field[IS_FILES_ENGINE]->set_notnull(); - table->field[IS_FILES_ENGINE]->store(ndbcluster_hton_name, - ndbcluster_hton_name_length, - system_charset_info); - - table->field[IS_FILES_FREE_EXTENTS]->set_notnull(); - table->field[IS_FILES_FREE_EXTENTS]->store(df.getFree() - / ts.getExtentSize()); - table->field[IS_FILES_TOTAL_EXTENTS]->set_notnull(); - table->field[IS_FILES_TOTAL_EXTENTS]->store(df.getSize() - / ts.getExtentSize()); - table->field[IS_FILES_EXTENT_SIZE]->set_notnull(); - table->field[IS_FILES_EXTENT_SIZE]->store(ts.getExtentSize()); - table->field[IS_FILES_INITIAL_SIZE]->set_notnull(); - table->field[IS_FILES_INITIAL_SIZE]->store(df.getSize()); - table->field[IS_FILES_MAXIMUM_SIZE]->set_notnull(); - table->field[IS_FILES_MAXIMUM_SIZE]->store(df.getSize()); - table->field[IS_FILES_VERSION]->set_notnull(); - table->field[IS_FILES_VERSION]->store(df.getObjectVersion()); - - table->field[IS_FILES_ROW_FORMAT]->set_notnull(); - table->field[IS_FILES_ROW_FORMAT]->store("FIXED", 5, system_charset_info); - - char extra[30]; - int len= my_snprintf(extra, sizeof(extra), "CLUSTER_NODE=%u", id); - table->field[IS_FILES_EXTRA]->set_notnull(); - table->field[IS_FILES_EXTRA]->store(extra, len, system_charset_info); - schema_table_store_record(thd, table); - } - } - - NdbDictionary::Dictionary::List uflist; - dict->listObjects(uflist, NdbDictionary::Object::Undofile); - ndberr= dict->getNdbError(); - if (ndberr.classification != NdbError::NoError) - ERR_RETURN(ndberr); - - for (i= 0; i < uflist.count; i++) - { - NdbDictionary::Dictionary::List::Element& elt= uflist.elements[i]; - Ndb_cluster_connection_node_iter iter; - unsigned id; - - g_ndb_cluster_connection->init_get_next_node(iter); - - while ((id= g_ndb_cluster_connection->get_next_node(iter))) - { - NdbDictionary::Undofile uf= dict->getUndofile(id, elt.name); - ndberr= dict->getNdbError(); - if (ndberr.classification != NdbError::NoError) - { - if (ndberr.classification == NdbError::SchemaError) - continue; - if (ndberr.classification == NdbError::UnknownResultError) - continue; - ERR_RETURN(ndberr); - } - NdbDictionary::LogfileGroup lfg= - dict->getLogfileGroup(uf.getLogfileGroup()); - ndberr= dict->getNdbError(); - if (ndberr.classification != NdbError::NoError) - { - if (ndberr.classification == NdbError::SchemaError) - continue; - ERR_RETURN(ndberr); - } - - init_fill_schema_files_row(table); - table->field[IS_FILES_FILE_NAME]->set_notnull(); - table->field[IS_FILES_FILE_NAME]->store(elt.name, strlen(elt.name), - system_charset_info); - table->field[IS_FILES_FILE_TYPE]->set_notnull(); - table->field[IS_FILES_FILE_TYPE]->store("UNDO LOG", 8, - system_charset_info); - NdbDictionary::ObjectId objid; - uf.getLogfileGroupId(&objid); - table->field[IS_FILES_LOGFILE_GROUP_NAME]->set_notnull(); - table->field[IS_FILES_LOGFILE_GROUP_NAME]->store(uf.getLogfileGroup(), - strlen(uf.getLogfileGroup()), - system_charset_info); - table->field[IS_FILES_LOGFILE_GROUP_NUMBER]->set_notnull(); - table->field[IS_FILES_LOGFILE_GROUP_NUMBER]->store(objid.getObjectId()); - table->field[IS_FILES_ENGINE]->set_notnull(); - table->field[IS_FILES_ENGINE]->store(ndbcluster_hton_name, - ndbcluster_hton_name_length, - system_charset_info); - - table->field[IS_FILES_TOTAL_EXTENTS]->set_notnull(); - table->field[IS_FILES_TOTAL_EXTENTS]->store(uf.getSize()/4); - table->field[IS_FILES_EXTENT_SIZE]->set_notnull(); - table->field[IS_FILES_EXTENT_SIZE]->store(4); - - table->field[IS_FILES_INITIAL_SIZE]->set_notnull(); - table->field[IS_FILES_INITIAL_SIZE]->store(uf.getSize()); - table->field[IS_FILES_MAXIMUM_SIZE]->set_notnull(); - table->field[IS_FILES_MAXIMUM_SIZE]->store(uf.getSize()); - - table->field[IS_FILES_VERSION]->set_notnull(); - table->field[IS_FILES_VERSION]->store(uf.getObjectVersion()); - - char extra[100]; - int len= my_snprintf(extra,sizeof(extra),"CLUSTER_NODE=%u;UNDO_BUFFER_SIZE=%lu", - id, (ulong) lfg.getUndoBufferSize()); - table->field[IS_FILES_EXTRA]->set_notnull(); - table->field[IS_FILES_EXTRA]->store(extra, len, system_charset_info); - schema_table_store_record(thd, table); - } - } - - // now for LFGs - NdbDictionary::Dictionary::List lfglist; - dict->listObjects(lfglist, NdbDictionary::Object::LogfileGroup); - ndberr= dict->getNdbError(); - if (ndberr.classification != NdbError::NoError) - ERR_RETURN(ndberr); - - for (i= 0; i < lfglist.count; i++) - { - NdbDictionary::Dictionary::List::Element& elt= lfglist.elements[i]; - - NdbDictionary::LogfileGroup lfg= dict->getLogfileGroup(elt.name); - ndberr= dict->getNdbError(); - if (ndberr.classification != NdbError::NoError) - { - if (ndberr.classification == NdbError::SchemaError) - continue; - ERR_RETURN(ndberr); - } - - init_fill_schema_files_row(table); - table->field[IS_FILES_FILE_TYPE]->set_notnull(); - table->field[IS_FILES_FILE_TYPE]->store("UNDO LOG", 8, - system_charset_info); - - table->field[IS_FILES_LOGFILE_GROUP_NAME]->set_notnull(); - table->field[IS_FILES_LOGFILE_GROUP_NAME]->store(elt.name, - strlen(elt.name), - system_charset_info); - table->field[IS_FILES_LOGFILE_GROUP_NUMBER]->set_notnull(); - table->field[IS_FILES_LOGFILE_GROUP_NUMBER]->store(lfg.getObjectId()); - table->field[IS_FILES_ENGINE]->set_notnull(); - table->field[IS_FILES_ENGINE]->store(ndbcluster_hton_name, - ndbcluster_hton_name_length, - system_charset_info); - - table->field[IS_FILES_FREE_EXTENTS]->set_notnull(); - table->field[IS_FILES_FREE_EXTENTS]->store(lfg.getUndoFreeWords()); - table->field[IS_FILES_EXTENT_SIZE]->set_notnull(); - table->field[IS_FILES_EXTENT_SIZE]->store(4); - - table->field[IS_FILES_VERSION]->set_notnull(); - table->field[IS_FILES_VERSION]->store(lfg.getObjectVersion()); - - char extra[100]; - int len= my_snprintf(extra,sizeof(extra), - "UNDO_BUFFER_SIZE=%lu", - (ulong) lfg.getUndoBufferSize()); - table->field[IS_FILES_EXTRA]->set_notnull(); - table->field[IS_FILES_EXTRA]->store(extra, len, system_charset_info); - schema_table_store_record(thd, table); - } - DBUG_RETURN(0); -} - -SHOW_VAR ndb_status_variables_export[]= { - {"Ndb", (char*) &ndb_status_variables, SHOW_ARRAY}, - {NullS, NullS, SHOW_LONG} -}; - -static MYSQL_SYSVAR_ULONG( - cache_check_time, /* name */ - opt_ndb_cache_check_time, /* var */ - PLUGIN_VAR_RQCMDARG, - "A dedicated thread is created to, at the given " - "millisecond interval, invalidate the query cache " - "if another MySQL server in the cluster has changed " - "the data in the database.", - NULL, /* check func. */ - NULL, /* update func. */ - 0, /* default */ - 0, /* min */ - ONE_YEAR_IN_SECONDS, /* max */ - 0 /* block */ -); - - -static MYSQL_SYSVAR_ULONG( - extra_logging, /* name */ - opt_ndb_extra_logging, /* var */ - PLUGIN_VAR_OPCMDARG, - "Turn on more logging in the error log.", - NULL, /* check func. */ - NULL, /* update func. */ - 1, /* default */ - 0, /* min */ - 0, /* max */ - 0 /* block */ -); - - -ulong opt_ndb_report_thresh_binlog_epoch_slip; -static MYSQL_SYSVAR_ULONG( - report_thresh_binlog_epoch_slip, /* name */ - opt_ndb_report_thresh_binlog_epoch_slip,/* var */ - PLUGIN_VAR_RQCMDARG, - "Threshold on number of epochs to be behind before reporting binlog " - "status. E.g. 3 means that if the difference between what epoch has " - "been received from the storage nodes and what has been applied to " - "the binlog is 3 or more, a status message will be sent to the cluster " - "log.", - NULL, /* check func. */ - NULL, /* update func. */ - 3, /* default */ - 0, /* min */ - 256, /* max */ - 0 /* block */ -); - - -ulong opt_ndb_report_thresh_binlog_mem_usage; -static MYSQL_SYSVAR_ULONG( - report_thresh_binlog_mem_usage, /* name */ - opt_ndb_report_thresh_binlog_mem_usage,/* var */ - PLUGIN_VAR_RQCMDARG, - "Threshold on percentage of free memory before reporting binlog " - "status. E.g. 10 means that if amount of available memory for " - "receiving binlog data from the storage nodes goes below 10%, " - "a status message will be sent to the cluster log.", - NULL, /* check func. */ - NULL, /* update func. */ - 10, /* default */ - 0, /* min */ - 100, /* max */ - 0 /* block */ -); - - -static MYSQL_SYSVAR_STR( - connectstring, /* name */ - opt_ndb_connectstring, /* var */ - PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, - "Connect string for ndbcluster.", - NULL, /* check func. */ - NULL, /* update func. */ - NULL /* default */ -); - - -static MYSQL_SYSVAR_STR( - mgmd_host, /* name */ - opt_ndb_mgmd_host, /* var */ - PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, - "Set host and port for ndb_mgmd. Syntax: hostname[:port]", - NULL, /* check func. */ - NULL, /* update func. */ - NULL /* default */ -); - - -static MYSQL_SYSVAR_UINT( - nodeid, /* name */ - opt_ndb_nodeid, /* var */ - PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, - "Nodeid for this mysqld in the cluster.", - NULL, /* check func. */ - NULL, /* update func. */ - 0, /* default */ - 0, /* min */ - 255, /* max */ - 0 /* block */ -); - -static struct st_mysql_sys_var* system_variables[]= { - MYSQL_SYSVAR(cache_check_time), - MYSQL_SYSVAR(extra_logging), - MYSQL_SYSVAR(report_thresh_binlog_mem_usage), - MYSQL_SYSVAR(report_thresh_binlog_epoch_slip), - MYSQL_SYSVAR(distribution), - MYSQL_SYSVAR(autoincrement_prefetch_sz), - MYSQL_SYSVAR(force_send), - MYSQL_SYSVAR(use_exact_count), - MYSQL_SYSVAR(use_transactions), - MYSQL_SYSVAR(use_copying_alter_table), - MYSQL_SYSVAR(optimized_node_selection), - MYSQL_SYSVAR(index_stat_enable), - MYSQL_SYSVAR(index_stat_cache_entries), - MYSQL_SYSVAR(index_stat_update_freq), - MYSQL_SYSVAR(connectstring), - MYSQL_SYSVAR(mgmd_host), - MYSQL_SYSVAR(nodeid), - - NULL -}; - - -struct st_mysql_storage_engine ndbcluster_storage_engine= -{ MYSQL_HANDLERTON_INTERFACE_VERSION }; - -mysql_declare_plugin(ndbcluster) -{ - MYSQL_STORAGE_ENGINE_PLUGIN, - &ndbcluster_storage_engine, - ndbcluster_hton_name, - "MySQL AB", - "Clustered, fault-tolerant tables", - PLUGIN_LICENSE_GPL, - ndbcluster_init, /* Plugin Init */ - NULL, /* Plugin Deinit */ - 0x0100 /* 1.0 */, - ndb_status_variables_export,/* status variables */ - system_variables, /* system variables */ - NULL, /* config options */ - 0, /* flags */ -} -mysql_declare_plugin_end; -maria_declare_plugin(ndbcluster) -{ - MYSQL_STORAGE_ENGINE_PLUGIN, - &ndbcluster_storage_engine, - ndbcluster_hton_name, - "MySQL AB", - "Clustered, fault-tolerant tables", - PLUGIN_LICENSE_GPL, - ndbcluster_init, /* Plugin Init */ - NULL, /* Plugin Deinit */ - 0x0100 /* 1.0 */, - ndb_status_variables_export,/* status variables */ - NULL, /* system variables */ - "1.0", /* string version */ - MariaDB_PLUGIN_MATURITY_GAMMA /* maturity */ -} -maria_declare_plugin_end; - -#else -int Sun_ar_require_a_symbol_here= 0; -#endif diff --git a/sql/ha_ndbcluster.h b/sql/ha_ndbcluster.h deleted file mode 100644 index 70e1e9dc7cf..00000000000 --- a/sql/ha_ndbcluster.h +++ /dev/null @@ -1,599 +0,0 @@ -#ifndef HA_NDBCLUSTER_INCLUDED -#define HA_NDBCLUSTER_INCLUDED - -/* 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-1301 USA */ - -/* - This file defines the NDB Cluster handler: the interface between MySQL and - NDB Cluster -*/ - -/* The class defining a handle to an NDB Cluster table */ - -#ifdef USE_PRAGMA_INTERFACE -#pragma interface /* gcc class implementation */ -#endif - -/* Blob tables and events are internal to NDB and must never be accessed */ -#define IS_NDB_BLOB_PREFIX(A) is_prefix(A, "NDB$BLOB") - -#include <NdbApi.hpp> -#include <ndbapi_limits.h> - -#define NDB_HIDDEN_PRIMARY_KEY_LENGTH 8 - -#ifdef HAVE_PSI_INTERFACE -extern PSI_file_key key_file_ndb; -#endif /* HAVE_PSI_INTERFACE */ - - -class Ndb; // Forward declaration -class NdbOperation; // Forward declaration -class NdbTransaction; // Forward declaration -class NdbRecAttr; // Forward declaration -class NdbScanOperation; -class NdbIndexScanOperation; -class NdbBlob; -class NdbIndexStat; -class NdbEventOperation; -class ha_ndbcluster_cond; - -#include "sql_partition.h" /* part_id_range */ - -// connectstring to cluster if given by mysqld -extern const char *ndbcluster_connectstring; - -typedef enum ndb_index_type { - UNDEFINED_INDEX = 0, - PRIMARY_KEY_INDEX = 1, - PRIMARY_KEY_ORDERED_INDEX = 2, - UNIQUE_INDEX = 3, - UNIQUE_ORDERED_INDEX = 4, - ORDERED_INDEX = 5 -} NDB_INDEX_TYPE; - -typedef enum ndb_index_status { - UNDEFINED = 0, - ACTIVE = 1, - TO_BE_DROPPED = 2 -} NDB_INDEX_STATUS; - -typedef struct ndb_index_data { - NDB_INDEX_TYPE type; - NDB_INDEX_STATUS status; - const NdbDictionary::Index *index; - const NdbDictionary::Index *unique_index; - unsigned char *unique_index_attrid_map; - bool null_in_unique_index; - // In this version stats are not shared between threads - NdbIndexStat* index_stat; - uint index_stat_cache_entries; - // Simple counter mechanism to decide when to connect to db - uint index_stat_update_freq; - uint index_stat_query_count; -} NDB_INDEX_DATA; - -typedef enum ndb_write_op { - NDB_INSERT = 0, - NDB_UPDATE = 1, - NDB_PK_UPDATE = 2 -} NDB_WRITE_OP; - -typedef union { const NdbRecAttr *rec; NdbBlob *blob; void *ptr; } NdbValue; - -int get_ndb_blobs_value(TABLE* table, NdbValue* value_array, - uchar*& buffer, uint& buffer_size, - my_ptrdiff_t ptrdiff); - -typedef enum { - NSS_INITIAL= 0, - NSS_DROPPED, - NSS_ALTERED -} NDB_SHARE_STATE; - -typedef struct st_ndbcluster_share { - NDB_SHARE_STATE state; - MEM_ROOT mem_root; - THR_LOCK lock; - mysql_mutex_t mutex; - char *key; - uint key_length; - THD *util_lock; - uint use_count; - uint commit_count_lock; - ulonglong commit_count; - char *db; - char *table_name; - Ndb::TupleIdRange tuple_id_range; -#ifdef HAVE_NDB_BINLOG - uint32 connect_count; - uint32 flags; - NdbEventOperation *op; - NdbEventOperation *op_old; // for rename table - char *old_names; // for rename table - TABLE_SHARE *table_share; - TABLE *table; - uchar *record[2]; // pointer to allocated records for receiving data - NdbValue *ndb_value[2]; - MY_BITMAP *subscriber_bitmap; -#endif -} NDB_SHARE; - -inline -NDB_SHARE_STATE -get_ndb_share_state(NDB_SHARE *share) -{ - NDB_SHARE_STATE state; - mysql_mutex_lock(&share->mutex); - state= share->state; - mysql_mutex_unlock(&share->mutex); - return state; -} - -inline -void -set_ndb_share_state(NDB_SHARE *share, NDB_SHARE_STATE state) -{ - mysql_mutex_lock(&share->mutex); - share->state= state; - mysql_mutex_unlock(&share->mutex); -} - -struct Ndb_tuple_id_range_guard { - Ndb_tuple_id_range_guard(NDB_SHARE* _share) : - share(_share), - range(share->tuple_id_range) { - mysql_mutex_lock(&share->mutex); - } - ~Ndb_tuple_id_range_guard() { - mysql_mutex_unlock(&share->mutex); - } - NDB_SHARE* share; - Ndb::TupleIdRange& range; -}; - -#ifdef HAVE_NDB_BINLOG -/* NDB_SHARE.flags */ -#define NSF_HIDDEN_PK 1 /* table has hidden primary key */ -#define NSF_BLOB_FLAG 2 /* table has blob attributes */ -#define NSF_NO_BINLOG 4 /* table should not be binlogged */ -#endif - -typedef enum ndb_query_state_bits { - NDB_QUERY_NORMAL = 0, - NDB_QUERY_MULTI_READ_RANGE = 1 -} NDB_QUERY_STATE_BITS; - -/* - Place holder for ha_ndbcluster thread specific data -*/ - -enum THD_NDB_OPTIONS -{ - TNO_NO_LOG_SCHEMA_OP= 1 << 0 -}; - -enum THD_NDB_TRANS_OPTIONS -{ - TNTO_INJECTED_APPLY_STATUS= 1 << 0 - ,TNTO_NO_LOGGING= 1 << 1 -}; - -struct Ndb_local_table_statistics { - int no_uncommitted_rows_count; - ulong last_count; - ha_rows records; -}; - -class Thd_ndb -{ - public: - Thd_ndb(); - ~Thd_ndb(); - - void init_open_tables(); - - Ndb *ndb; - ulong count; - uint lock_count; - uint start_stmt_count; - NdbTransaction *trans; - bool m_error; - bool m_slow_path; - int m_error_code; - uint32 m_query_id; /* query id whn m_error_code was set */ - uint32 options; - uint32 trans_options; - List<NDB_SHARE> changed_tables; - uint query_state; - HASH open_tables; -}; - -class ha_ndbcluster: public handler -{ - public: - ha_ndbcluster(handlerton *hton, TABLE_SHARE *table); - ~ha_ndbcluster(); - - int ha_initialise(); - int open(const char *name, int mode, uint test_if_locked); - int close(void); - - int write_row(uchar *buf); - int update_row(const uchar *old_data, uchar *new_data); - int delete_row(const uchar *buf); - int index_init(uint index, bool sorted); - int index_end(); - int index_read(uchar *buf, const uchar *key, uint key_len, - enum ha_rkey_function find_flag); - int index_next(uchar *buf); - int index_prev(uchar *buf); - int index_first(uchar *buf); - int index_last(uchar *buf); - int index_read_last(uchar * buf, const uchar * key, uint key_len); - int rnd_init(bool scan); - int rnd_end(); - int rnd_next(uchar *buf); - int rnd_pos(uchar *buf, uchar *pos); - void position(const uchar *record); - int read_range_first(const key_range *start_key, - const key_range *end_key, - bool eq_range, bool sorted); - int read_range_first_to_buf(const key_range *start_key, - const key_range *end_key, - bool eq_range, bool sorted, - uchar* buf); - int read_range_next(); - int alter_tablespace(st_alter_tablespace *info); - - /** - * Multi range stuff - */ -#if 0 - /* - MRR/NDB is disabled in MariaDB. This is because in MariaDB, we've - backported - - the latest version of MRR interface (BKA needs this) - - the latest version of DS-MRR implementation - but didn't backport the latest version MRR/NDB implementation. - - */ - int read_multi_range_first(KEY_MULTI_RANGE **found_range_p, - KEY_MULTI_RANGE*ranges, uint range_count, - bool sorted, HANDLER_BUFFER *buffer); - int read_multi_range_next(KEY_MULTI_RANGE **found_range_p); -#endif - bool null_value_index_search(KEY_MULTI_RANGE *ranges, - KEY_MULTI_RANGE *end_range, - HANDLER_BUFFER *buffer); - - bool get_error_message(int error, String *buf); - ha_rows records(); - ha_rows estimate_rows_upper_bound() - { return HA_POS_ERROR; } - int info(uint); - void get_dynamic_partition_info(PARTITION_STATS *stat_info, uint part_id); - int extra(enum ha_extra_function operation); - int extra_opt(enum ha_extra_function operation, ulong cache_size); - int reset(); - int external_lock(THD *thd, int lock_type); - void unlock_row(); - int start_stmt(THD *thd, thr_lock_type lock_type); - void print_error(int error, myf errflag); - const char * table_type() const; - const char ** bas_ext() const; - ulonglong table_flags(void) const; - void prepare_for_alter(); - int add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys); - int prepare_drop_index(TABLE *table_arg, uint *key_num, uint num_of_keys); - int final_drop_index(TABLE *table_arg); - void set_part_info(partition_info *part_info); - ulong index_flags(uint idx, uint part, bool all_parts) const; - uint max_supported_record_length() const; - uint max_supported_keys() const; - uint max_supported_key_parts() const; - uint max_supported_key_length() const; - uint max_supported_key_part_length() const; - - int rename_table(const char *from, const char *to); - int delete_table(const char *name); - int create(const char *name, TABLE *form, HA_CREATE_INFO *info); - int create_handler_files(const char *file, const char *old_name, - int action_flag, HA_CREATE_INFO *info); - int get_default_no_partitions(HA_CREATE_INFO *info); - bool get_no_parts(const char *name, uint *no_parts); - void set_auto_partitions(partition_info *part_info); - virtual bool is_fatal_error(int error, uint flags) - { - if (!handler::is_fatal_error(error, flags) || - error == HA_ERR_NO_PARTITION_FOUND) - return FALSE; - return TRUE; - } - - THR_LOCK_DATA **store_lock(THD *thd, - THR_LOCK_DATA **to, - enum thr_lock_type lock_type); - - bool low_byte_first() const; - - const char* index_type(uint key_number); - - double scan_time(); - ha_rows records_in_range(uint inx, key_range *min_key, key_range *max_key); - void start_bulk_insert(ha_rows rows); - int end_bulk_insert(); - - static Thd_ndb* seize_thd_ndb(); - static void release_thd_ndb(Thd_ndb* thd_ndb); - -static void set_dbname(const char *pathname, char *dbname); -static void set_tabname(const char *pathname, char *tabname); - - /* - Condition pushdown - */ - - /* - Push condition down to the table handler. - SYNOPSIS - cond_push() - cond Condition to be pushed. The condition tree must not be - modified by the by the caller. - RETURN - The 'remainder' condition that caller must use to filter out records. - NULL means the handler will not return rows that do not match the - passed condition. - NOTES - The pushed conditions form a stack (from which one can remove the - last pushed condition using cond_pop). - The table handler filters out rows using (pushed_cond1 AND pushed_cond2 - AND ... AND pushed_condN) - or less restrictive condition, depending on handler's capabilities. - - handler->reset() call empties the condition stack. - Calls to rnd_init/rnd_end, index_init/index_end etc do not affect the - condition stack. - The current implementation supports arbitrary AND/OR nested conditions - with comparisons between columns and constants (including constant - expressions and function calls) and the following comparison operators: - =, !=, >, >=, <, <=, like, "not like", "is null", and "is not null". - Negated conditions are supported by NOT which generate NAND/NOR groups. - */ - const COND *cond_push(const COND *cond); - /* - Pop the top condition from the condition stack of the handler instance. - SYNOPSIS - cond_pop() - Pops the top if condition stack, if stack is not empty - */ - void cond_pop(); - - uint8 table_cache_type(); - - /* - * Internal to ha_ndbcluster, used by C functions - */ - int ndb_err(NdbTransaction*); - - my_bool register_query_cache_table(THD *thd, char *table_key, - uint key_length, - qc_engine_callback *engine_callback, - ulonglong *engine_data); - - bool check_if_incompatible_data(HA_CREATE_INFO *info, - uint table_changes); - -private: - int loc_read_multi_range_next(KEY_MULTI_RANGE **found_range_p); - friend int ndbcluster_drop_database_impl(const char *path); - friend int ndb_handle_schema_change(THD *thd, - Ndb *ndb, NdbEventOperation *pOp, - NDB_SHARE *share); - - static int delete_table(ha_ndbcluster *h, Ndb *ndb, - const char *path, - const char *db, - const char *table_name); - int create_ndb_index(const char *name, KEY *key_info, bool unique); - int create_ordered_index(const char *name, KEY *key_info); - int create_unique_index(const char *name, KEY *key_info); - int create_index(const char *name, KEY *key_info, - NDB_INDEX_TYPE idx_type, uint idx_no); -// Index list management - int create_indexes(Ndb *ndb, TABLE *tab); - int open_indexes(Ndb *ndb, TABLE *tab, bool ignore_error); - void renumber_indexes(Ndb *ndb, TABLE *tab); - int drop_indexes(Ndb *ndb, TABLE *tab); - int add_index_handle(THD *thd, NdbDictionary::Dictionary *dict, - KEY *key_info, const char *index_name, uint index_no); - int get_metadata(const char* path); - void release_metadata(THD *thd, Ndb *ndb); - NDB_INDEX_TYPE get_index_type(uint idx_no) const; - NDB_INDEX_TYPE get_index_type_from_table(uint index_no) const; - NDB_INDEX_TYPE get_index_type_from_key(uint index_no, KEY *key_info, - bool primary) const; - bool has_null_in_unique_index(uint idx_no) const; - bool check_index_fields_not_null(KEY *key_info); - - uint set_up_partition_info(partition_info *part_info, - TABLE *table, - void *tab); - char* get_tablespace_name(THD *thd, char *name, uint name_len); - int set_range_data(void *tab, partition_info* part_info); - int set_list_data(void *tab, partition_info* part_info); - int complemented_read(const uchar *old_data, uchar *new_data, - uint32 old_part_id); - int pk_read(const uchar *key, uint key_len, uchar *buf, uint32 part_id); - int ordered_index_scan(const key_range *start_key, - const key_range *end_key, - bool sorted, bool descending, uchar* buf, - part_id_range *part_spec); - int unique_index_read(const uchar *key, uint key_len, - uchar *buf); - int unique_index_scan(const KEY* key_info, - const uchar *key, - uint key_len, - uchar *buf); - int full_table_scan(uchar * buf); - - bool check_all_operations_for_error(NdbTransaction *trans, - const NdbOperation *first, - const NdbOperation *last, - uint errcode); - int peek_indexed_rows(const uchar *record, NDB_WRITE_OP write_op); - int fetch_next(NdbScanOperation* op); - int set_auto_inc(Field *field); - int next_result(uchar *buf); - int define_read_attrs(uchar* buf, NdbOperation* op); - int filtered_scan(const uchar *key, uint key_len, - uchar *buf, - enum ha_rkey_function find_flag); - int close_scan(); - void unpack_record(uchar *buf); - int get_ndb_lock_type(enum thr_lock_type type); - - void set_dbname(const char *pathname); - void set_tabname(const char *pathname); - - bool set_hidden_key(NdbOperation*, - uint fieldnr, const uchar* field_ptr); - int set_ndb_key(NdbOperation*, Field *field, - uint fieldnr, const uchar* field_ptr); - int set_ndb_value(NdbOperation*, Field *field, uint fieldnr, - int row_offset= 0, bool *set_blob_value= 0); - int get_ndb_value(NdbOperation*, Field *field, uint fieldnr, uchar*); - int get_ndb_partition_id(NdbOperation *); - friend int g_get_ndb_blobs_value(NdbBlob *ndb_blob, void *arg); - int set_primary_key(NdbOperation *op, const uchar *key); - int set_primary_key_from_record(NdbOperation *op, const uchar *record); - bool check_index_fields_in_write_set(uint keyno); - int set_index_key_from_record(NdbOperation *op, const uchar *record, - uint keyno); - int set_bounds(NdbIndexScanOperation*, uint inx, bool rir, - const key_range *keys[2], uint= 0); - int key_cmp(uint keynr, const uchar * old_row, const uchar * new_row); - int set_index_key(NdbOperation *, const KEY *key_info, const uchar *key_ptr); - void print_results(); - - virtual void get_auto_increment(ulonglong offset, ulonglong increment, - ulonglong nb_desired_values, - ulonglong *first_value, - ulonglong *nb_reserved_values); - bool uses_blob_value(); - - char *update_table_comment(const char * comment); - - int write_ndb_file(const char *name); - - int check_ndb_connection(THD* thd= current_thd); - - void set_rec_per_key(); - int records_update(); - void no_uncommitted_rows_execute_failure(); - void no_uncommitted_rows_update(int); - void no_uncommitted_rows_reset(THD *); - - void release_completed_operations(NdbTransaction*, bool); - - friend int execute_commit(ha_ndbcluster*, NdbTransaction*); - friend int execute_no_commit_ignore_no_key(ha_ndbcluster*, NdbTransaction*); - friend int execute_no_commit(ha_ndbcluster*, NdbTransaction*, bool); - friend int execute_no_commit_ie(ha_ndbcluster*, NdbTransaction*, bool); - - void transaction_checks(THD *thd); - int start_statement(THD *thd, Thd_ndb *thd_ndb, Ndb* ndb); - int init_handler_for_statement(THD *thd, Thd_ndb *thd_ndb); - - NdbTransaction *m_active_trans; - NdbScanOperation *m_active_cursor; - const NdbDictionary::Table *m_table; - struct Ndb_local_table_statistics *m_table_info; - struct Ndb_local_table_statistics m_table_info_instance; - char m_dbname[FN_HEADLEN]; - //char m_schemaname[FN_HEADLEN]; - char m_tabname[FN_HEADLEN]; - ulonglong m_table_flags; - THR_LOCK_DATA m_lock; - bool m_lock_tuple; - NDB_SHARE *m_share; - NDB_INDEX_DATA m_index[MAX_KEY]; - // NdbRecAttr has no reference to blob - NdbValue m_value[NDB_MAX_ATTRIBUTES_IN_TABLE]; - uchar m_ref[NDB_HIDDEN_PRIMARY_KEY_LENGTH]; - partition_info *m_part_info; - uint32 m_part_id; - uchar *m_rec0; - Field **m_part_field_array; - bool m_use_partition_function; - bool m_sorted; - bool m_use_write; - bool m_ignore_dup_key; - bool m_has_unique_index; - bool m_primary_key_update; - bool m_write_op; - bool m_ignore_no_key; - ha_rows m_rows_to_insert; // TODO: merge it with handler::estimation_rows_to_insert? - ha_rows m_rows_inserted; - ha_rows m_bulk_insert_rows; - ha_rows m_rows_changed; - bool m_bulk_insert_not_flushed; - bool m_delete_cannot_batch; - bool m_update_cannot_batch; - ha_rows m_ops_pending; - bool m_skip_auto_increment; - bool m_blobs_pending; - bool m_slow_path; - my_ptrdiff_t m_blobs_offset; - // memory for blobs in one tuple - uchar *m_blobs_buffer; - uint32 m_blobs_buffer_size; - uint m_dupkey; - // set from thread variables at external lock - bool m_ha_not_exact_count; - bool m_force_send; - ha_rows m_autoincrement_prefetch; - bool m_transaction_on; - - ha_ndbcluster_cond *m_cond; - bool m_disable_multi_read; - uchar *m_multi_range_result_ptr; - KEY_MULTI_RANGE *m_multi_ranges; - KEY_MULTI_RANGE *m_multi_range_defined; - const NdbOperation *m_current_multi_operation; - NdbIndexScanOperation *m_multi_cursor; - uchar *m_multi_range_cursor_result_ptr; - int setup_recattr(const NdbRecAttr*); - Ndb *get_ndb(); -}; - -extern SHOW_VAR ndb_status_variables[]; - -int ndbcluster_discover(THD* thd, const char* dbname, const char* name, - const void** frmblob, uint* frmlen); -int ndbcluster_find_files(THD *thd,const char *db,const char *path, - const char *wild, bool dir, List<LEX_STRING> *files); -int ndbcluster_table_exists_in_engine(THD* thd, - const char *db, const char *name); -void ndbcluster_print_error(int error, const NdbOperation *error_op); - -static const char ndbcluster_hton_name[]= "ndbcluster"; -static const int ndbcluster_hton_name_length=sizeof(ndbcluster_hton_name)-1; -extern int ndbcluster_terminating; -extern int ndb_util_thread_running; -extern mysql_cond_t COND_ndb_util_ready; - -#endif /* HA_NDBCLUSTER_INCLUDED */ diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc deleted file mode 100644 index ae7bd12f229..00000000000 --- a/sql/ha_ndbcluster_binlog.cc +++ /dev/null @@ -1,4429 +0,0 @@ -/* Copyright (c) 2006, 2015, 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-1301 USA -*/ - -#include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes -#include "sql_show.h" -#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE -#include "ha_ndbcluster.h" - -#ifdef HAVE_NDB_BINLOG -#include "rpl_injector.h" -#include "rpl_filter.h" -#include "slave.h" -#include "ha_ndbcluster_binlog.h" -#include "NdbDictionary.hpp" -#include "ndb_cluster_connection.hpp" -#include <util/NdbAutoPtr.hpp> - -#include "sql_base.h" // close_thread_tables -#include "sql_table.h" // build_table_filename -#include "table.h" // open_table_from_share -#include "discover.h" // readfrm, writefrm -#include "lock.h" // MYSQL_LOCK_IGNORE_FLUSH, - // mysql_unlock_tables -#include "sql_parse.h" // mysql_parse -#include "transaction.h" - -#ifdef ndb_dynamite -#undef assert -#define assert(x) do { if(x) break; ::printf("%s %d: assert failed: %s\n", __FILE__, __LINE__, #x); ::fflush(stdout); ::signal(SIGABRT,SIG_DFL); ::abort(); ::kill(::getpid(),6); ::kill(::getpid(),9); } while (0) -#endif - -extern my_bool opt_ndb_log_binlog_index; -extern ulong opt_ndb_extra_logging; -/* - defines for cluster replication table names -*/ -#include "ha_ndbcluster_tables.h" -#define NDB_APPLY_TABLE_FILE "./" NDB_REP_DB "/" NDB_APPLY_TABLE -#define NDB_SCHEMA_TABLE_FILE "./" NDB_REP_DB "/" NDB_SCHEMA_TABLE - -/* - Timeout for syncing schema events between - mysql servers, and between mysql server and the binlog -*/ -static const int DEFAULT_SYNC_TIMEOUT= 120; - - -/* - Flag showing if the ndb injector thread is running, if so == 1 - -1 if it was started but later stopped for some reason - 0 if never started -*/ -static int ndb_binlog_thread_running= 0; - -/* - Flag showing if the ndb binlog should be created, if so == TRUE - FALSE if not -*/ -my_bool ndb_binlog_running= FALSE; -my_bool ndb_binlog_tables_inited= FALSE; - -/* - Global reference to the ndb injector thread THD oject - - Has one sole purpose, for setting the in_use table member variable - in get_share(...) -*/ -THD *injector_thd= 0; - -/* - Global reference to ndb injector thd object. - - Used mainly by the binlog index thread, but exposed to the client sql - thread for one reason; to setup the events operations for a table - to enable ndb injector thread receiving events. - - Must therefore always be used with a surrounding - mysql_mutex_lock(&injector_mutex), when doing create/dropEventOperation -*/ -static Ndb *injector_ndb= 0; -static Ndb *schema_ndb= 0; - -static int ndbcluster_binlog_inited= 0; -/* - Flag "ndbcluster_binlog_terminating" set when shutting down mysqld. - Server main loop should call handlerton function: - - ndbcluster_hton->binlog_func == - ndbcluster_binlog_func(...,BFN_BINLOG_END,...) == - ndbcluster_binlog_end - - at shutdown, which sets the flag. And then server needs to wait for it - to complete. Otherwise binlog will not be complete. - - ndbcluster_hton->panic == ndbcluster_end() will not return until - ndb binlog is completed -*/ -static int ndbcluster_binlog_terminating= 0; - -/* - Mutex and condition used for interacting between client sql thread - and injector thread -*/ -pthread_t ndb_binlog_thread; -mysql_mutex_t injector_mutex; -mysql_cond_t injector_cond; - -/* NDB Injector thread (used for binlog creation) */ -static ulonglong ndb_latest_applied_binlog_epoch= 0; -static ulonglong ndb_latest_handled_binlog_epoch= 0; -static ulonglong ndb_latest_received_binlog_epoch= 0; - -NDB_SHARE *ndb_apply_status_share= 0; -NDB_SHARE *ndb_schema_share= 0; -mysql_mutex_t ndb_schema_share_mutex; - -extern my_bool opt_log_slave_updates; -static my_bool g_ndb_log_slave_updates; - -/* Schema object distribution handling */ -HASH ndb_schema_objects; -typedef struct st_ndb_schema_object { - mysql_mutex_t mutex; - char *key; - uint key_length; - uint use_count; - MY_BITMAP slock_bitmap; - uint32 slock[256/32]; // 256 bits for lock status of table -} NDB_SCHEMA_OBJECT; -static NDB_SCHEMA_OBJECT *ndb_get_schema_object(const char *key, - my_bool create_if_not_exists, - my_bool have_lock); -static void ndb_free_schema_object(NDB_SCHEMA_OBJECT **ndb_schema_object, - bool have_lock); - -static Uint64 *p_latest_trans_gci= 0; - -/* - Global variables for holding the ndb_binlog_index table reference -*/ -static TABLE *ndb_binlog_index= 0; -static TABLE_LIST binlog_tables; - -/* - Helper functions -*/ - -#ifndef DBUG_OFF -/* purecov: begin deadcode */ -static void print_records(TABLE *table, const uchar *record) -{ - for (uint j= 0; j < table->s->fields; j++) - { - char buf[40]; - int pos= 0; - Field *field= table->field[j]; - const uchar* field_ptr= field->ptr - table->record[0] + record; - int pack_len= field->pack_length(); - int n= pack_len < 10 ? pack_len : 10; - - for (int i= 0; i < n && pos < 20; i++) - { - pos+= sprintf(&buf[pos]," %x", (int) (uchar) field_ptr[i]); - } - buf[pos]= 0; - DBUG_PRINT("info",("[%u]field_ptr[0->%d]: %s", j, n, buf)); - } -} -/* purecov: end */ -#else -#define print_records(a,b) -#endif - - -#ifndef DBUG_OFF -static void dbug_print_table(const char *info, TABLE *table) -{ - if (table == 0) - { - DBUG_PRINT("info",("%s: (null)", info)); - return; - } - DBUG_PRINT("info", - ("%s: %s.%s s->fields: %d " - "reclength: %lu rec_buff_length: %u record[0]: 0x%lx " - "record[1]: 0x%lx", - info, - table->s->db.str, - table->s->table_name.str, - table->s->fields, - table->s->reclength, - table->s->rec_buff_length, - (long) table->record[0], - (long) table->record[1])); - - for (unsigned int i= 0; i < table->s->fields; i++) - { - Field *f= table->field[i]; - DBUG_PRINT("info", - ("[%d] \"%s\"(0x%lx:%s%s%s%s%s%s) type: %d pack_length: %d " - "ptr: 0x%lx[+%d] null_bit: %u null_ptr: 0x%lx[+%d]", - i, - f->field_name, - (long) f->flags, - (f->flags & PRI_KEY_FLAG) ? "pri" : "attr", - (f->flags & NOT_NULL_FLAG) ? "" : ",nullable", - (f->flags & UNSIGNED_FLAG) ? ",unsigned" : ",signed", - (f->flags & ZEROFILL_FLAG) ? ",zerofill" : "", - (f->flags & BLOB_FLAG) ? ",blob" : "", - (f->flags & BINARY_FLAG) ? ",binary" : "", - f->real_type(), - f->pack_length(), - (long) f->ptr, (int) (f->ptr - table->record[0]), - f->null_bit, - (long) f->null_ptr, - (int) ((uchar*) f->null_ptr - table->record[0]))); - if (f->type() == MYSQL_TYPE_BIT) - { - Field_bit *g= (Field_bit*) f; - DBUG_PRINT("MYSQL_TYPE_BIT",("field_length: %d bit_ptr: 0x%lx[+%d] " - "bit_ofs: %d bit_len: %u", - g->field_length, (long) g->bit_ptr, - (int) ((uchar*) g->bit_ptr - - table->record[0]), - g->bit_ofs, g->bit_len)); - } - } -} -#else -#define dbug_print_table(a,b) -#endif - - -/* - Run a query through mysql_parse - - Used to: - - purging the ndb_binlog_index - - creating the ndb_apply_status table -*/ -static void run_query(THD *thd, char *buf, char *end, - const int *no_print_error, my_bool disable_binlog) -{ - ulong save_thd_query_length= thd->query_length(); - char *save_thd_query= thd->query(); - ulong save_thread_id= thd->variables.pseudo_thread_id; - struct system_status_var save_thd_status_var= thd->status_var; - THD_TRANS save_thd_transaction_all= thd->transaction.all; - THD_TRANS save_thd_transaction_stmt= thd->transaction.stmt; - ulonglong save_thd_options= thd->variables.option_bits; - DBUG_ASSERT(sizeof(save_thd_options) == sizeof(thd->variables.option_bits)); - NET save_thd_net= thd->net; - - bzero((char*) &thd->net, sizeof(NET)); - thd->set_query(buf, (uint) (end - buf)); - thd->variables.pseudo_thread_id= thread_id; - thd->transaction.stmt.modified_non_trans_table= FALSE; - if (disable_binlog) - thd->variables.option_bits&= ~OPTION_BIN_LOG; - - DBUG_PRINT("query", ("%s", thd->query())); - - DBUG_ASSERT(!thd->in_sub_stmt); - DBUG_ASSERT(!thd->locked_tables_mode); - - { - Parser_state parser_state; - if (!parser_state.init(thd, thd->query(), thd->query_length())) - mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); - } - - if (no_print_error && thd->is_slave_error) - { - int i; - Thd_ndb *thd_ndb= get_thd_ndb(thd); - for (i= 0; no_print_error[i]; i++) - if ((thd_ndb->m_error_code == no_print_error[i]) || - (thd->stmt_da->sql_errno() == (unsigned) no_print_error[i])) - break; - if (!no_print_error[i]) - sql_print_error("NDB: %s: error %s %d(ndb: %d) %d %d", - buf, - thd->stmt_da->message(), - thd->stmt_da->sql_errno(), - thd_ndb->m_error_code, - (int) thd->is_error(), thd->is_slave_error); - } - /* - XXX: this code is broken. mysql_parse()/mysql_reset_thd_for_next_command() - can not be called from within a statement, and - run_query() can be called from anywhere, including from within - a sub-statement. - This particular reset is a temporary hack to avoid an assert - for double assignment of the diagnostics area when run_query() - is called from ndbcluster_reset_logs(), which is called from - mysql_flush(). - */ - thd->stmt_da->reset_diagnostics_area(); - - thd->variables.option_bits= save_thd_options; - thd->set_query(save_thd_query, save_thd_query_length); - thd->variables.pseudo_thread_id= save_thread_id; - thd->status_var= save_thd_status_var; - thd->transaction.all= save_thd_transaction_all; - thd->transaction.stmt= save_thd_transaction_stmt; - thd->net= save_thd_net; - thd->set_current_stmt_binlog_format_row(); - - if (thd == injector_thd) - { - /* - running the query will close all tables, including the ndb_binlog_index - used in injector_thd - */ - ndb_binlog_index= 0; - } -} - -static void -ndbcluster_binlog_close_table(THD *thd, NDB_SHARE *share) -{ - DBUG_ENTER("ndbcluster_binlog_close_table"); - if (share->table_share) - { - closefrm(share->table, 1); - share->table_share= 0; - share->table= 0; - } - DBUG_ASSERT(share->table == 0); - DBUG_VOID_RETURN; -} - - -/* - Creates a TABLE object for the ndb cluster table - - NOTES - This does not open the underlying table -*/ - -static int -ndbcluster_binlog_open_table(THD *thd, NDB_SHARE *share, - TABLE_SHARE *table_share, TABLE *table, - int reopen) -{ - int error; - DBUG_ENTER("ndbcluster_binlog_open_table"); - - init_tmp_table_share(thd, table_share, share->db, 0, share->table_name, - share->key); - if ((error= open_table_def(thd, table_share, 0))) - { - DBUG_PRINT("error", ("open_table_def failed: %d my_errno: %d", error, my_errno)); - free_table_share(table_share); - DBUG_RETURN(error); - } - if ((error= open_table_from_share(thd, table_share, "", 0 /* fon't allocate buffers */, - (uint) READ_ALL, 0, table, FALSE))) - { - DBUG_PRINT("error", ("open_table_from_share failed %d my_errno: %d", error, my_errno)); - free_table_share(table_share); - DBUG_RETURN(error); - } - mysql_mutex_lock(&LOCK_open); - assign_new_table_id(table_share); - mysql_mutex_unlock(&LOCK_open); - - if (!reopen) - { - // allocate memory on ndb share so it can be reused after online alter table - (void)multi_alloc_root(&share->mem_root, - &(share->record[0]), table->s->rec_buff_length, - &(share->record[1]), table->s->rec_buff_length, - NULL); - } - { - my_ptrdiff_t row_offset= share->record[0] - table->record[0]; - Field **p_field; - for (p_field= table->field; *p_field; p_field++) - (*p_field)->move_field_offset(row_offset); - table->record[0]= share->record[0]; - table->record[1]= share->record[1]; - } - - table->in_use= injector_thd; - - table->s->db.str= share->db; - table->s->db.length= strlen(share->db); - table->s->table_name.str= share->table_name; - table->s->table_name.length= strlen(share->table_name); - - DBUG_ASSERT(share->table_share == 0); - share->table_share= table_share; - DBUG_ASSERT(share->table == 0); - share->table= table; - /* We can't use 'use_all_columns()' as the file object is not setup yet */ - table->column_bitmaps_set_no_signal(&table->s->all_set, &table->s->all_set); -#ifndef DBUG_OFF - dbug_print_table("table", table); -#endif - DBUG_RETURN(0); -} - - -/* - Initialize the binlog part of the NDB_SHARE -*/ -int ndbcluster_binlog_init_share(NDB_SHARE *share, TABLE *_table) -{ - THD *thd= current_thd; - MEM_ROOT *mem_root= &share->mem_root; - int do_event_op= ndb_binlog_running; - int error= 0; - DBUG_ENTER("ndbcluster_binlog_init_share"); - - share->connect_count= g_ndb_cluster_connection->get_connect_count(); - - share->op= 0; - share->table= 0; - - if (!ndb_schema_share && - strcmp(share->db, NDB_REP_DB) == 0 && - strcmp(share->table_name, NDB_SCHEMA_TABLE) == 0) - do_event_op= 1; - else if (!ndb_apply_status_share && - strcmp(share->db, NDB_REP_DB) == 0 && - strcmp(share->table_name, NDB_APPLY_TABLE) == 0) - do_event_op= 1; - - { - int i, no_nodes= g_ndb_cluster_connection->no_db_nodes(); - share->subscriber_bitmap= (MY_BITMAP*) - alloc_root(mem_root, no_nodes * sizeof(MY_BITMAP)); - for (i= 0; i < no_nodes; i++) - { - bitmap_init(&share->subscriber_bitmap[i], - (Uint32*)alloc_root(mem_root, max_ndb_nodes/8), - max_ndb_nodes, FALSE); - bitmap_clear_all(&share->subscriber_bitmap[i]); - } - } - - if (!do_event_op) - { - if (_table) - { - if (_table->s->primary_key == MAX_KEY) - share->flags|= NSF_HIDDEN_PK; - if (_table->s->blob_fields != 0) - share->flags|= NSF_BLOB_FLAG; - } - else - { - share->flags|= NSF_NO_BINLOG; - } - DBUG_RETURN(error); - } - while (1) - { - int error; - TABLE_SHARE *table_share= (TABLE_SHARE *) alloc_root(mem_root, sizeof(*table_share)); - TABLE *table= (TABLE*) alloc_root(mem_root, sizeof(*table)); - if ((error= ndbcluster_binlog_open_table(thd, share, table_share, table, 0))) - break; - /* - ! do not touch the contents of the table - it may be in use by the injector thread - */ - MEM_ROOT *mem_root= &share->mem_root; - share->ndb_value[0]= (NdbValue*) - alloc_root(mem_root, sizeof(NdbValue) * - (table->s->fields + 2 /*extra for hidden key and part key*/)); - share->ndb_value[1]= (NdbValue*) - alloc_root(mem_root, sizeof(NdbValue) * - (table->s->fields + 2 /*extra for hidden key and part key*/)); - - if (table->s->primary_key == MAX_KEY) - share->flags|= NSF_HIDDEN_PK; - if (table->s->blob_fields != 0) - share->flags|= NSF_BLOB_FLAG; - break; - } - DBUG_RETURN(error); -} - -/***************************************************************** - functions called from master sql client threads -****************************************************************/ - -/* - called in mysql_show_binlog_events and reset_logs to make sure we wait for - all events originating from this mysql server to arrive in the binlog - - Wait for the last epoch in which the last transaction is a part of. - - Wait a maximum of 30 seconds. -*/ -static void ndbcluster_binlog_wait(THD *thd) -{ - if (ndb_binlog_running) - { - DBUG_ENTER("ndbcluster_binlog_wait"); - const char *save_info= thd ? thd->proc_info : 0; - ulonglong wait_epoch= *p_latest_trans_gci; - int count= 30; - if (thd) - thd->proc_info= "Waiting for ndbcluster binlog update to " - "reach current position"; - while (count && ndb_binlog_running && - ndb_latest_handled_binlog_epoch < wait_epoch) - { - count--; - sleep(1); - } - if (thd) - thd->proc_info= save_info; - DBUG_VOID_RETURN; - } -} - -/* - Called from MYSQL_BIN_LOG::reset_logs in log.cc when binlog is emptied -*/ -static int ndbcluster_reset_logs(THD *thd) -{ - if (!ndb_binlog_running) - return 0; - - DBUG_ENTER("ndbcluster_reset_logs"); - - /* - Wait for all events orifinating from this mysql server has - reached the binlog before continuing to reset - */ - ndbcluster_binlog_wait(thd); - - char buf[1024]; - char *end= strmov(buf, "DELETE FROM " NDB_REP_DB "." NDB_REP_TABLE); - - run_query(thd, buf, end, NULL, TRUE); - - DBUG_RETURN(0); -} - -/* - Called from MYSQL_BIN_LOG::purge_logs in log.cc when the binlog "file" - is removed -*/ - -static int -ndbcluster_binlog_index_purge_file(THD *thd, const char *file) -{ - if (!ndb_binlog_running || thd->slave_thread) - return 0; - - DBUG_ENTER("ndbcluster_binlog_index_purge_file"); - DBUG_PRINT("enter", ("file: %s", file)); - - char buf[1024]; - char *end= strmov(strmov(strmov(buf, - "DELETE FROM " - NDB_REP_DB "." NDB_REP_TABLE - " WHERE File='"), file), "'"); - - run_query(thd, buf, end, NULL, TRUE); - - DBUG_RETURN(0); -} - -static void -ndbcluster_binlog_log_query(handlerton *hton, THD *thd, enum_binlog_command binlog_command, - const char *query, uint query_length, - const char *db, const char *table_name) -{ - DBUG_ENTER("ndbcluster_binlog_log_query"); - DBUG_PRINT("enter", ("db: %s table_name: %s query: %s", - db, table_name, query)); - enum SCHEMA_OP_TYPE type; - int log= 0; - switch (binlog_command) - { - case LOGCOM_CREATE_TABLE: - type= SOT_CREATE_TABLE; - DBUG_ASSERT(FALSE); - break; - case LOGCOM_ALTER_TABLE: - type= SOT_ALTER_TABLE; - log= 1; - break; - case LOGCOM_RENAME_TABLE: - type= SOT_RENAME_TABLE; - DBUG_ASSERT(FALSE); - break; - case LOGCOM_DROP_TABLE: - type= SOT_DROP_TABLE; - DBUG_ASSERT(FALSE); - break; - case LOGCOM_CREATE_DB: - type= SOT_CREATE_DB; - log= 1; - break; - case LOGCOM_ALTER_DB: - type= SOT_ALTER_DB; - log= 1; - break; - case LOGCOM_DROP_DB: - type= SOT_DROP_DB; - DBUG_ASSERT(FALSE); - break; - } - if (log) - { - ndbcluster_log_schema_op(thd, 0, query, query_length, - db, table_name, 0, 0, type, - 0, 0); - } - DBUG_VOID_RETURN; -} - - -/* - End use of the NDB Cluster binlog - - wait for binlog thread to shutdown -*/ - -static int ndbcluster_binlog_end(THD *thd) -{ - DBUG_ENTER("ndbcluster_binlog_end"); - - if (!ndbcluster_binlog_inited) - DBUG_RETURN(0); - ndbcluster_binlog_inited= 0; - -#ifdef HAVE_NDB_BINLOG - if (ndb_util_thread_running > 0) - { - /* - Wait for util thread to die (as this uses the injector mutex) - There is a very small change that ndb_util_thread dies and the - following mutex is freed before it's accessed. This shouldn't - however be a likely case as the ndbcluster_binlog_end is supposed to - be called before ndb_cluster_end(). - */ - mysql_mutex_lock(&LOCK_ndb_util_thread); - /* Ensure mutex are not freed if ndb_cluster_end is running at same time */ - ndb_util_thread_running++; - ndbcluster_terminating= 1; - mysql_cond_signal(&COND_ndb_util_thread); - while (ndb_util_thread_running > 1) - mysql_cond_wait(&COND_ndb_util_ready, &LOCK_ndb_util_thread); - ndb_util_thread_running--; - mysql_mutex_unlock(&LOCK_ndb_util_thread); - } - - /* wait for injector thread to finish */ - ndbcluster_binlog_terminating= 1; - mysql_mutex_lock(&injector_mutex); - mysql_cond_signal(&injector_cond); - while (ndb_binlog_thread_running > 0) - mysql_cond_wait(&injector_cond, &injector_mutex); - mysql_mutex_unlock(&injector_mutex); - - mysql_mutex_destroy(&injector_mutex); - mysql_cond_destroy(&injector_cond); - mysql_mutex_destroy(&ndb_schema_share_mutex); -#endif - - DBUG_RETURN(0); -} - -/***************************************************************** - functions called from slave sql client threads -****************************************************************/ -static void ndbcluster_reset_slave(THD *thd) -{ - if (!ndb_binlog_running) - return; - - DBUG_ENTER("ndbcluster_reset_slave"); - char buf[1024]; - char *end= strmov(buf, "DELETE FROM " NDB_REP_DB "." NDB_APPLY_TABLE); - run_query(thd, buf, end, NULL, TRUE); - DBUG_VOID_RETURN; -} - -/* - Initialize the binlog part of the ndb handlerton -*/ - -/** - Upon the sql command flush logs, we need to ensure that all outstanding - ndb data to be logged has made it to the binary log to get a deterministic - behavior on the rotation of the log. - */ -static bool ndbcluster_flush_logs(handlerton *hton) -{ - ndbcluster_binlog_wait(current_thd); - return FALSE; -} - -static int ndbcluster_binlog_func(handlerton *hton, THD *thd, - enum_binlog_func fn, - void *arg) -{ - switch(fn) - { - case BFN_RESET_LOGS: - ndbcluster_reset_logs(thd); - break; - case BFN_RESET_SLAVE: - ndbcluster_reset_slave(thd); - break; - case BFN_BINLOG_WAIT: - ndbcluster_binlog_wait(thd); - break; - case BFN_BINLOG_END: - ndbcluster_binlog_end(thd); - break; - case BFN_BINLOG_PURGE_FILE: - ndbcluster_binlog_index_purge_file(thd, (const char *)arg); - break; - } - return 0; -} - -void ndbcluster_binlog_init_handlerton() -{ - handlerton *h= ndbcluster_hton; - h->flush_logs= ndbcluster_flush_logs; - h->binlog_func= ndbcluster_binlog_func; - h->binlog_log_query= ndbcluster_binlog_log_query; -} - - - - - -/* - check the availability af the ndb_apply_status share - - return share, but do not increase refcount - - return 0 if there is no share -*/ -static NDB_SHARE *ndbcluster_check_ndb_apply_status_share() -{ - mysql_mutex_lock(&ndbcluster_mutex); - - void *share= my_hash_search(&ndbcluster_open_tables, - (uchar*) NDB_APPLY_TABLE_FILE, - sizeof(NDB_APPLY_TABLE_FILE) - 1); - DBUG_PRINT("info",("ndbcluster_check_ndb_apply_status_share %s 0x%lx", - NDB_APPLY_TABLE_FILE, (long) share)); - mysql_mutex_unlock(&ndbcluster_mutex); - return (NDB_SHARE*) share; -} - -/* - check the availability af the schema share - - return share, but do not increase refcount - - return 0 if there is no share -*/ -static NDB_SHARE *ndbcluster_check_ndb_schema_share() -{ - mysql_mutex_lock(&ndbcluster_mutex); - - void *share= my_hash_search(&ndbcluster_open_tables, - (uchar*) NDB_SCHEMA_TABLE_FILE, - sizeof(NDB_SCHEMA_TABLE_FILE) - 1); - DBUG_PRINT("info",("ndbcluster_check_ndb_schema_share %s 0x%lx", - NDB_SCHEMA_TABLE_FILE, (long) share)); - mysql_mutex_unlock(&ndbcluster_mutex); - return (NDB_SHARE*) share; -} - -/* - Create the ndb_apply_status table -*/ -static int ndbcluster_create_ndb_apply_status_table(THD *thd) -{ - DBUG_ENTER("ndbcluster_create_ndb_apply_status_table"); - - /* - Check if we already have the apply status table. - If so it should have been discovered at startup - and thus have a share - */ - - if (ndbcluster_check_ndb_apply_status_share()) - DBUG_RETURN(0); - - if (g_ndb_cluster_connection->get_no_ready() <= 0) - DBUG_RETURN(0); - - char buf[1024 + 1], *end; - - if (opt_ndb_extra_logging) - sql_print_information("NDB: Creating " NDB_REP_DB "." NDB_APPLY_TABLE); - - /* - Check if apply status table exists in MySQL "dictionary" - if so, remove it since there is none in Ndb - */ - { - build_table_filename(buf, sizeof(buf) - 1, - NDB_REP_DB, NDB_APPLY_TABLE, reg_ext, 0); - mysql_file_delete(key_file_frm, buf, MYF(0)); - } - - /* - Note, updating this table schema must be reflected in ndb_restore - */ - end= strmov(buf, "CREATE TABLE IF NOT EXISTS " - NDB_REP_DB "." NDB_APPLY_TABLE - " ( server_id INT UNSIGNED NOT NULL," - " epoch BIGINT UNSIGNED NOT NULL, " - " log_name VARCHAR(255) BINARY NOT NULL, " - " start_pos BIGINT UNSIGNED NOT NULL, " - " end_pos BIGINT UNSIGNED NOT NULL, " - " PRIMARY KEY USING HASH (server_id) ) ENGINE=NDB CHARACTER SET latin1"); - - const int no_print_error[6]= {ER_TABLE_EXISTS_ERROR, - 701, - 702, - 721, // Table already exist - 4009, - 0}; // do not print error 701 etc - run_query(thd, buf, end, no_print_error, TRUE); - - DBUG_RETURN(0); -} - - -/* - Create the schema table -*/ -static int ndbcluster_create_schema_table(THD *thd) -{ - DBUG_ENTER("ndbcluster_create_schema_table"); - - /* - Check if we already have the schema table. - If so it should have been discovered at startup - and thus have a share - */ - - if (ndbcluster_check_ndb_schema_share()) - DBUG_RETURN(0); - - if (g_ndb_cluster_connection->get_no_ready() <= 0) - DBUG_RETURN(0); - - char buf[1024 + 1], *end; - - if (opt_ndb_extra_logging) - sql_print_information("NDB: Creating " NDB_REP_DB "." NDB_SCHEMA_TABLE); - - /* - Check if schema table exists in MySQL "dictionary" - if so, remove it since there is none in Ndb - */ - { - build_table_filename(buf, sizeof(buf) - 1, - NDB_REP_DB, NDB_SCHEMA_TABLE, reg_ext, 0); - mysql_file_delete(key_file_frm, buf, MYF(0)); - } - - /* - Update the defines below to reflect the table schema - */ - end= strmov(buf, "CREATE TABLE IF NOT EXISTS " - NDB_REP_DB "." NDB_SCHEMA_TABLE - " ( db VARBINARY(63) NOT NULL," - " name VARBINARY(63) NOT NULL," - " slock BINARY(32) NOT NULL," - " query BLOB NOT NULL," - " node_id INT UNSIGNED NOT NULL," - " epoch BIGINT UNSIGNED NOT NULL," - " id INT UNSIGNED NOT NULL," - " version INT UNSIGNED NOT NULL," - " type INT UNSIGNED NOT NULL," - " PRIMARY KEY USING HASH (db,name) ) ENGINE=NDB CHARACTER SET latin1"); - - const int no_print_error[6]= {ER_TABLE_EXISTS_ERROR, - 701, - 702, - 721, // Table already exist - 4009, - 0}; // do not print error 701 etc - run_query(thd, buf, end, no_print_error, TRUE); - - DBUG_RETURN(0); -} - -int ndbcluster_setup_binlog_table_shares(THD *thd) -{ - if (!ndb_schema_share && - ndbcluster_check_ndb_schema_share() == 0) - { - ndb_create_table_from_engine(thd, NDB_REP_DB, NDB_SCHEMA_TABLE); - if (!ndb_schema_share) - { - ndbcluster_create_schema_table(thd); - // always make sure we create the 'schema' first - if (!ndb_schema_share) - return 1; - } - } - if (!ndb_apply_status_share && - ndbcluster_check_ndb_apply_status_share() == 0) - { - ndb_create_table_from_engine(thd, NDB_REP_DB, NDB_APPLY_TABLE); - if (!ndb_apply_status_share) - { - ndbcluster_create_ndb_apply_status_table(thd); - if (!ndb_apply_status_share) - return 1; - } - } - if (!ndbcluster_find_all_files(thd)) - { - ndb_binlog_tables_inited= TRUE; - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: ndb tables writable"); - close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT); - /* Signal injector thread that all is setup */ - mysql_cond_signal(&injector_cond); - } - return 0; -} - -/* - Defines and struct for schema table. - Should reflect table definition above. -*/ -#define SCHEMA_DB_I 0u -#define SCHEMA_NAME_I 1u -#define SCHEMA_SLOCK_I 2u -#define SCHEMA_QUERY_I 3u -#define SCHEMA_NODE_ID_I 4u -#define SCHEMA_EPOCH_I 5u -#define SCHEMA_ID_I 6u -#define SCHEMA_VERSION_I 7u -#define SCHEMA_TYPE_I 8u -#define SCHEMA_SIZE 9u -#define SCHEMA_SLOCK_SIZE 32u - -struct Cluster_schema -{ - uchar db_length; - char db[64]; - uchar name_length; - char name[64]; - uchar slock_length; - uint32 slock[SCHEMA_SLOCK_SIZE/4]; - unsigned short query_length; - char *query; - Uint64 epoch; - uint32 node_id; - uint32 id; - uint32 version; - uint32 type; - uint32 any_value; -}; - -static void print_could_not_discover_error(THD *thd, - const Cluster_schema *schema) -{ - sql_print_error("NDB Binlog: Could not discover table '%s.%s' from " - "binlog schema event '%s' from node %d. " - "my_errno: %d", - schema->db, schema->name, schema->query, - schema->node_id, my_errno); - List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); - MYSQL_ERROR *err; - while ((err= it++)) - sql_print_warning("NDB Binlog: (%d)%s", err->get_sql_errno(), - err->get_message_text()); -} - -/* - Transfer schema table data into corresponding struct -*/ -static void ndbcluster_get_schema(NDB_SHARE *share, - Cluster_schema *s) -{ - TABLE *table= share->table; - Field **field; - /* unpack blob values */ - uchar* blobs_buffer= 0; - uint blobs_buffer_size= 0; - my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); - { - ptrdiff_t ptrdiff= 0; - int ret= get_ndb_blobs_value(table, share->ndb_value[0], - blobs_buffer, blobs_buffer_size, - ptrdiff); - if (ret != 0) - { - my_free(blobs_buffer); - DBUG_PRINT("info", ("blob read error")); - DBUG_ASSERT(FALSE); - } - } - /* db varchar 1 length uchar */ - field= table->field; - s->db_length= *(uint8*)(*field)->ptr; - DBUG_ASSERT(s->db_length <= (*field)->field_length); - DBUG_ASSERT((*field)->field_length + 1 == sizeof(s->db)); - memcpy(s->db, (*field)->ptr + 1, s->db_length); - s->db[s->db_length]= 0; - /* name varchar 1 length uchar */ - field++; - s->name_length= *(uint8*)(*field)->ptr; - DBUG_ASSERT(s->name_length <= (*field)->field_length); - DBUG_ASSERT((*field)->field_length + 1 == sizeof(s->name)); - memcpy(s->name, (*field)->ptr + 1, s->name_length); - s->name[s->name_length]= 0; - /* slock fixed length */ - field++; - s->slock_length= (*field)->field_length; - DBUG_ASSERT((*field)->field_length == sizeof(s->slock)); - memcpy(s->slock, (*field)->ptr, s->slock_length); - /* query blob */ - field++; - { - Field_blob *field_blob= (Field_blob*)(*field); - uint blob_len= field_blob->get_length((*field)->ptr); - uchar *blob_ptr= 0; - field_blob->get_ptr(&blob_ptr); - DBUG_ASSERT(blob_len == 0 || blob_ptr != 0); - s->query_length= blob_len; - s->query= sql_strmake((char*) blob_ptr, blob_len); - } - /* node_id */ - field++; - s->node_id= ((Field_long *)*field)->val_int(); - /* epoch */ - field++; - s->epoch= ((Field_long *)*field)->val_int(); - /* id */ - field++; - s->id= ((Field_long *)*field)->val_int(); - /* version */ - field++; - s->version= ((Field_long *)*field)->val_int(); - /* type */ - field++; - s->type= ((Field_long *)*field)->val_int(); - /* free blobs buffer */ - my_free(blobs_buffer); - dbug_tmp_restore_column_map(table->read_set, old_map); -} - -/* - helper function to pack a ndb varchar -*/ -char *ndb_pack_varchar(const NDBCOL *col, char *buf, - const char *str, int sz) -{ - switch (col->getArrayType()) - { - case NDBCOL::ArrayTypeFixed: - memcpy(buf, str, sz); - break; - case NDBCOL::ArrayTypeShortVar: - *(uchar*)buf= (uchar)sz; - memcpy(buf + 1, str, sz); - break; - case NDBCOL::ArrayTypeMediumVar: - int2store(buf, sz); - memcpy(buf + 2, str, sz); - break; - } - return buf; -} - -/* - acknowledge handling of schema operation -*/ -static int -ndbcluster_update_slock(THD *thd, - const char *db, - const char *table_name) -{ - DBUG_ENTER("ndbcluster_update_slock"); - if (!ndb_schema_share) - { - DBUG_RETURN(0); - } - - const NdbError *ndb_error= 0; - uint32 node_id= g_ndb_cluster_connection->node_id(); - Ndb *ndb= check_ndb_in_thd(thd); - char save_db[FN_HEADLEN]; - strcpy(save_db, ndb->getDatabaseName()); - - char tmp_buf[FN_REFLEN]; - NDBDICT *dict= ndb->getDictionary(); - ndb->setDatabaseName(NDB_REP_DB); - Ndb_table_guard ndbtab_g(dict, NDB_SCHEMA_TABLE); - const NDBTAB *ndbtab= ndbtab_g.get_table(); - NdbTransaction *trans= 0; - int retries= 100; - int retry_sleep= 10; /* 10 milliseconds, transaction */ - const NDBCOL *col[SCHEMA_SIZE]; - unsigned sz[SCHEMA_SIZE]; - - MY_BITMAP slock; - uint32 bitbuf[SCHEMA_SLOCK_SIZE/4]; - bitmap_init(&slock, bitbuf, sizeof(bitbuf)*8, false); - - if (ndbtab == 0) - { - abort(); - DBUG_RETURN(0); - } - - { - uint i; - for (i= 0; i < SCHEMA_SIZE; i++) - { - col[i]= ndbtab->getColumn(i); - if (i != SCHEMA_QUERY_I) - { - sz[i]= col[i]->getLength(); - DBUG_ASSERT(sz[i] <= sizeof(tmp_buf)); - } - } - } - - while (1) - { - if ((trans= ndb->startTransaction()) == 0) - goto err; - { - NdbOperation *op= 0; - int r= 0; - - /* read the bitmap exlusive */ - r|= (op= trans->getNdbOperation(ndbtab)) == 0; - DBUG_ASSERT(r == 0); - r|= op->readTupleExclusive(); - DBUG_ASSERT(r == 0); - - /* db */ - ndb_pack_varchar(col[SCHEMA_DB_I], tmp_buf, db, strlen(db)); - r|= op->equal(SCHEMA_DB_I, tmp_buf); - DBUG_ASSERT(r == 0); - /* name */ - ndb_pack_varchar(col[SCHEMA_NAME_I], tmp_buf, table_name, - strlen(table_name)); - r|= op->equal(SCHEMA_NAME_I, tmp_buf); - DBUG_ASSERT(r == 0); - /* slock */ - r|= op->getValue(SCHEMA_SLOCK_I, (char*)slock.bitmap) == 0; - DBUG_ASSERT(r == 0); - } - if (trans->execute(NdbTransaction::NoCommit)) - goto err; - bitmap_clear_bit(&slock, node_id); - { - NdbOperation *op= 0; - int r= 0; - - /* now update the tuple */ - r|= (op= trans->getNdbOperation(ndbtab)) == 0; - DBUG_ASSERT(r == 0); - r|= op->updateTuple(); - DBUG_ASSERT(r == 0); - - /* db */ - ndb_pack_varchar(col[SCHEMA_DB_I], tmp_buf, db, strlen(db)); - r|= op->equal(SCHEMA_DB_I, tmp_buf); - DBUG_ASSERT(r == 0); - /* name */ - ndb_pack_varchar(col[SCHEMA_NAME_I], tmp_buf, table_name, - strlen(table_name)); - r|= op->equal(SCHEMA_NAME_I, tmp_buf); - DBUG_ASSERT(r == 0); - /* slock */ - r|= op->setValue(SCHEMA_SLOCK_I, (char*)slock.bitmap); - DBUG_ASSERT(r == 0); - /* node_id */ - r|= op->setValue(SCHEMA_NODE_ID_I, node_id); - DBUG_ASSERT(r == 0); - /* type */ - r|= op->setValue(SCHEMA_TYPE_I, (uint32)SOT_CLEAR_SLOCK); - DBUG_ASSERT(r == 0); - } - if (trans->execute(NdbTransaction::Commit) == 0) - { - dict->forceGCPWait(); - DBUG_PRINT("info", ("node %d cleared lock on '%s.%s'", - node_id, db, table_name)); - break; - } - err: - const NdbError *this_error= trans ? - &trans->getNdbError() : &ndb->getNdbError(); - if (this_error->status == NdbError::TemporaryError) - { - if (retries--) - { - if (trans) - ndb->closeTransaction(trans); - my_sleep(retry_sleep); - continue; // retry - } - } - ndb_error= this_error; - break; - } - - if (ndb_error) - { - char buf[1024]; - my_snprintf(buf, sizeof(buf), "Could not release lock on '%s.%s'", - db, table_name); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - ndb_error->code, ndb_error->message, buf); - } - if (trans) - ndb->closeTransaction(trans); - ndb->setDatabaseName(save_db); - DBUG_RETURN(0); -} - -/* - log query in schema table -*/ -static void ndb_report_waiting(const char *key, - int the_time, - const char *op, - const char *obj) -{ - ulonglong ndb_latest_epoch= 0; - const char *proc_info= "<no info>"; - mysql_mutex_lock(&injector_mutex); - if (injector_ndb) - ndb_latest_epoch= injector_ndb->getLatestGCI(); - if (injector_thd) - proc_info= injector_thd->proc_info; - mysql_mutex_unlock(&injector_mutex); - sql_print_information("NDB %s:" - " waiting max %u sec for %s %s." - " epochs: (%u,%u,%u)" - " injector proc_info: %s" - ,key, the_time, op, obj - ,(uint)ndb_latest_handled_binlog_epoch - ,(uint)ndb_latest_received_binlog_epoch - ,(uint)ndb_latest_epoch - ,proc_info - ); -} - -int ndbcluster_log_schema_op(THD *thd, NDB_SHARE *share, - const char *query, int query_length, - const char *db, const char *table_name, - uint32 ndb_table_id, - uint32 ndb_table_version, - enum SCHEMA_OP_TYPE type, - const char *new_db, const char *new_table_name) -{ - DBUG_ENTER("ndbcluster_log_schema_op"); - Thd_ndb *thd_ndb= get_thd_ndb(thd); - if (!thd_ndb) - { - if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb())) - { - sql_print_error("Could not allocate Thd_ndb object"); - DBUG_RETURN(1); - } - set_thd_ndb(thd, thd_ndb); - } - - DBUG_PRINT("enter", - ("query: %s db: %s table_name: %s thd_ndb->options: %d", - query, db, table_name, thd_ndb->options)); - if (!ndb_schema_share || thd_ndb->options & TNO_NO_LOG_SCHEMA_OP) - { - DBUG_RETURN(0); - } - - char tmp_buf2_mem[FN_REFLEN]; - String tmp_buf2(tmp_buf2_mem, sizeof(tmp_buf2_mem), system_charset_info); - tmp_buf2.length(0); - const char *type_str; - switch (type) - { - case SOT_DROP_TABLE: - /* drop database command, do not log at drop table */ - if (thd->lex->sql_command == SQLCOM_DROP_DB) - DBUG_RETURN(0); - /* redo the drop table query as is may contain several tables */ - tmp_buf2.append(STRING_WITH_LEN("drop table ")); - append_identifier(thd, &tmp_buf2, table_name, strlen(table_name)); - query= tmp_buf2.c_ptr_safe(); - query_length= tmp_buf2.length(); - type_str= "drop table"; - break; - case SOT_RENAME_TABLE: - /* redo the rename table query as is may contain several tables */ - tmp_buf2.append(STRING_WITH_LEN("rename table ")); - append_identifier(thd, &tmp_buf2, db, strlen(db)); - tmp_buf2.append(STRING_WITH_LEN(".")); - append_identifier(thd, &tmp_buf2, table_name, strlen(table_name)); - tmp_buf2.append(STRING_WITH_LEN(" to ")); - append_identifier(thd, &tmp_buf2, new_db, strlen(new_db)); - tmp_buf2.append(STRING_WITH_LEN(".")); - append_identifier(thd, &tmp_buf2, new_table_name, strlen(new_table_name)); - query= tmp_buf2.c_ptr_safe(); - query_length= tmp_buf2.length(); - type_str= "rename table"; - break; - case SOT_CREATE_TABLE: - type_str= "create table"; - break; - case SOT_ALTER_TABLE: - type_str= "alter table"; - break; - case SOT_DROP_DB: - type_str= "drop db"; - break; - case SOT_CREATE_DB: - type_str= "create db"; - break; - case SOT_ALTER_DB: - type_str= "alter db"; - break; - case SOT_TABLESPACE: - type_str= "tablespace"; - break; - case SOT_LOGFILE_GROUP: - type_str= "logfile group"; - break; - case SOT_TRUNCATE_TABLE: - type_str= "truncate table"; - break; - default: - abort(); /* should not happen, programming error */ - } - - NDB_SCHEMA_OBJECT *ndb_schema_object; - { - char key[FN_REFLEN + 1]; - build_table_filename(key, sizeof(key) - 1, db, table_name, "", 0); - ndb_schema_object= ndb_get_schema_object(key, TRUE, FALSE); - } - - const NdbError *ndb_error= 0; - uint32 node_id= g_ndb_cluster_connection->node_id(); - Uint64 epoch= 0; - MY_BITMAP schema_subscribers; - uint32 bitbuf[sizeof(ndb_schema_object->slock)/4]; - char bitbuf_e[sizeof(bitbuf)]; - bzero(bitbuf_e, sizeof(bitbuf_e)); - { - int i, updated= 0; - int no_storage_nodes= g_ndb_cluster_connection->no_db_nodes(); - bitmap_init(&schema_subscribers, bitbuf, sizeof(bitbuf)*8, FALSE); - bitmap_set_all(&schema_subscribers); - - /* begin protect ndb_schema_share */ - mysql_mutex_lock(&ndb_schema_share_mutex); - if (ndb_schema_share == 0) - { - mysql_mutex_unlock(&ndb_schema_share_mutex); - if (ndb_schema_object) - ndb_free_schema_object(&ndb_schema_object, FALSE); - DBUG_RETURN(0); - } - mysql_mutex_lock(&ndb_schema_share->mutex); - for (i= 0; i < no_storage_nodes; i++) - { - MY_BITMAP *table_subscribers= &ndb_schema_share->subscriber_bitmap[i]; - if (!bitmap_is_clear_all(table_subscribers)) - { - bitmap_intersect(&schema_subscribers, - table_subscribers); - updated= 1; - } - } - mysql_mutex_unlock(&ndb_schema_share->mutex); - mysql_mutex_unlock(&ndb_schema_share_mutex); - /* end protect ndb_schema_share */ - - if (updated) - { - bitmap_clear_bit(&schema_subscribers, node_id); - /* - if setting own acknowledge bit it is important that - no other mysqld's are registred, as subsequent code - will cause the original event to be hidden (by blob - merge event code) - */ - if (bitmap_is_clear_all(&schema_subscribers)) - bitmap_set_bit(&schema_subscribers, node_id); - } - else - bitmap_clear_all(&schema_subscribers); - - if (ndb_schema_object) - { - mysql_mutex_lock(&ndb_schema_object->mutex); - memcpy(ndb_schema_object->slock, schema_subscribers.bitmap, - sizeof(ndb_schema_object->slock)); - mysql_mutex_unlock(&ndb_schema_object->mutex); - } - - DBUG_DUMP("schema_subscribers", (uchar*)schema_subscribers.bitmap, - no_bytes_in_map(&schema_subscribers)); - DBUG_PRINT("info", ("bitmap_is_clear_all(&schema_subscribers): %d", - bitmap_is_clear_all(&schema_subscribers))); - } - - Ndb *ndb= thd_ndb->ndb; - char save_db[FN_REFLEN]; - strcpy(save_db, ndb->getDatabaseName()); - - char tmp_buf[FN_REFLEN]; - NDBDICT *dict= ndb->getDictionary(); - ndb->setDatabaseName(NDB_REP_DB); - Ndb_table_guard ndbtab_g(dict, NDB_SCHEMA_TABLE); - const NDBTAB *ndbtab= ndbtab_g.get_table(); - NdbTransaction *trans= 0; - int retries= 100; - int retry_sleep= 10; /* 10 milliseconds, transaction */ - const NDBCOL *col[SCHEMA_SIZE]; - unsigned sz[SCHEMA_SIZE]; - - if (ndbtab == 0) - { - if (strcmp(NDB_REP_DB, db) != 0 || - strcmp(NDB_SCHEMA_TABLE, table_name)) - { - ndb_error= &dict->getNdbError(); - } - goto end; - } - - { - uint i; - for (i= 0; i < SCHEMA_SIZE; i++) - { - col[i]= ndbtab->getColumn(i); - if (i != SCHEMA_QUERY_I) - { - sz[i]= col[i]->getLength(); - DBUG_ASSERT(sz[i] <= sizeof(tmp_buf)); - } - } - } - - while (1) - { - const char *log_db= db; - const char *log_tab= table_name; - const char *log_subscribers= (char*)schema_subscribers.bitmap; - uint32 log_type= (uint32)type; - if ((trans= ndb->startTransaction()) == 0) - goto err; - while (1) - { - NdbOperation *op= 0; - int r= 0; - r|= (op= trans->getNdbOperation(ndbtab)) == 0; - DBUG_ASSERT(r == 0); - r|= op->writeTuple(); - DBUG_ASSERT(r == 0); - - /* db */ - ndb_pack_varchar(col[SCHEMA_DB_I], tmp_buf, log_db, strlen(log_db)); - r|= op->equal(SCHEMA_DB_I, tmp_buf); - DBUG_ASSERT(r == 0); - /* name */ - ndb_pack_varchar(col[SCHEMA_NAME_I], tmp_buf, log_tab, - strlen(log_tab)); - r|= op->equal(SCHEMA_NAME_I, tmp_buf); - DBUG_ASSERT(r == 0); - /* slock */ - DBUG_ASSERT(sz[SCHEMA_SLOCK_I] == sizeof(bitbuf)); - r|= op->setValue(SCHEMA_SLOCK_I, log_subscribers); - DBUG_ASSERT(r == 0); - /* query */ - { - NdbBlob *ndb_blob= op->getBlobHandle(SCHEMA_QUERY_I); - DBUG_ASSERT(ndb_blob != 0); - uint blob_len= query_length; - const char* blob_ptr= query; - r|= ndb_blob->setValue(blob_ptr, blob_len); - DBUG_ASSERT(r == 0); - } - /* node_id */ - r|= op->setValue(SCHEMA_NODE_ID_I, node_id); - DBUG_ASSERT(r == 0); - /* epoch */ - r|= op->setValue(SCHEMA_EPOCH_I, epoch); - DBUG_ASSERT(r == 0); - /* id */ - r|= op->setValue(SCHEMA_ID_I, ndb_table_id); - DBUG_ASSERT(r == 0); - /* version */ - r|= op->setValue(SCHEMA_VERSION_I, ndb_table_version); - DBUG_ASSERT(r == 0); - /* type */ - r|= op->setValue(SCHEMA_TYPE_I, log_type); - DBUG_ASSERT(r == 0); - /* any value */ - if (!(thd->variables.option_bits & OPTION_BIN_LOG)) - r|= op->setAnyValue(NDB_ANYVALUE_FOR_NOLOGGING); - else - r|= op->setAnyValue(thd->server_id); - DBUG_ASSERT(r == 0); - if (log_db != new_db && new_db && new_table_name) - { - log_db= new_db; - log_tab= new_table_name; - log_subscribers= bitbuf_e; // no ack expected on this - log_type= (uint32)SOT_RENAME_TABLE_NEW; - continue; - } - break; - } - if (trans->execute(NdbTransaction::Commit) == 0) - { - DBUG_PRINT("info", ("logged: %s", query)); - break; - } -err: - const NdbError *this_error= trans ? - &trans->getNdbError() : &ndb->getNdbError(); - if (this_error->status == NdbError::TemporaryError) - { - if (retries--) - { - if (trans) - ndb->closeTransaction(trans); - my_sleep(retry_sleep); - continue; // retry - } - } - ndb_error= this_error; - break; - } -end: - if (ndb_error) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - ndb_error->code, - ndb_error->message, - "Could not log query '%s' on other mysqld's"); - - if (trans) - ndb->closeTransaction(trans); - ndb->setDatabaseName(save_db); - - /* - Wait for other mysqld's to acknowledge the table operation - */ - if (ndb_error == 0 && - !bitmap_is_clear_all(&schema_subscribers)) - { - /* - if own nodeid is set we are a single mysqld registred - as an optimization we update the slock directly - */ - if (bitmap_is_set(&schema_subscribers, node_id)) - ndbcluster_update_slock(thd, db, table_name); - else - dict->forceGCPWait(); - - int max_timeout= DEFAULT_SYNC_TIMEOUT; - mysql_mutex_lock(&ndb_schema_object->mutex); - while (1) - { - struct timespec abstime; - int i; - int no_storage_nodes= g_ndb_cluster_connection->no_db_nodes(); - set_timespec(abstime, 1); - int ret= mysql_cond_timedwait(&injector_cond, - &ndb_schema_object->mutex, - &abstime); - if (thd->killed) - break; - - /* begin protect ndb_schema_share */ - mysql_mutex_lock(&ndb_schema_share_mutex); - if (ndb_schema_share == 0) - { - mysql_mutex_unlock(&ndb_schema_share_mutex); - break; - } - mysql_mutex_lock(&ndb_schema_share->mutex); - for (i= 0; i < no_storage_nodes; i++) - { - /* remove any unsubscribed from schema_subscribers */ - MY_BITMAP *tmp= &ndb_schema_share->subscriber_bitmap[i]; - if (!bitmap_is_clear_all(tmp)) - bitmap_intersect(&schema_subscribers, tmp); - } - mysql_mutex_unlock(&ndb_schema_share->mutex); - mysql_mutex_unlock(&ndb_schema_share_mutex); - /* end protect ndb_schema_share */ - - /* remove any unsubscribed from ndb_schema_object->slock */ - bitmap_intersect(&ndb_schema_object->slock_bitmap, &schema_subscribers); - - DBUG_DUMP("ndb_schema_object->slock_bitmap.bitmap", - (uchar*)ndb_schema_object->slock_bitmap.bitmap, - no_bytes_in_map(&ndb_schema_object->slock_bitmap)); - - if (bitmap_is_clear_all(&ndb_schema_object->slock_bitmap)) - break; - - if (ret) - { - max_timeout--; - if (max_timeout == 0) - { - sql_print_error("NDB %s: distributing %s timed out. Ignoring...", - type_str, ndb_schema_object->key); - break; - } - if (opt_ndb_extra_logging) - ndb_report_waiting(type_str, max_timeout, - "distributing", ndb_schema_object->key); - } - } - mysql_mutex_unlock(&ndb_schema_object->mutex); - } - - if (ndb_schema_object) - ndb_free_schema_object(&ndb_schema_object, FALSE); - - DBUG_RETURN(0); -} - -/* - Handle _non_ data events from the storage nodes -*/ -int -ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, - NDB_SHARE *share) -{ - DBUG_ENTER("ndb_handle_schema_change"); - TABLE* table= share->table; - TABLE_SHARE *table_share= share->table_share; - const char *dbname= table_share->db.str; - const char *tabname= table_share->table_name.str; - bool do_close_cached_tables= FALSE; - bool is_online_alter_table= FALSE; - bool is_rename_table= FALSE; - bool is_remote_change= - (uint) pOp->getReqNodeId() != g_ndb_cluster_connection->node_id(); - - if (pOp->getEventType() == NDBEVENT::TE_ALTER) - { - if (pOp->tableFrmChanged()) - { - DBUG_PRINT("info", ("NDBEVENT::TE_ALTER: table frm changed")); - is_online_alter_table= TRUE; - } - else - { - DBUG_PRINT("info", ("NDBEVENT::TE_ALTER: name changed")); - DBUG_ASSERT(pOp->tableNameChanged()); - is_rename_table= TRUE; - } - } - - { - ndb->setDatabaseName(dbname); - Ndb_table_guard ndbtab_g(ndb->getDictionary(), tabname); - const NDBTAB *ev_tab= pOp->getTable(); - const NDBTAB *cache_tab= ndbtab_g.get_table(); - if (cache_tab && - cache_tab->getObjectId() == ev_tab->getObjectId() && - cache_tab->getObjectVersion() <= ev_tab->getObjectVersion()) - ndbtab_g.invalidate(); - } - - /* - Refresh local frm file and dictionary cache if - remote on-line alter table - */ - if (is_remote_change && is_online_alter_table) - { - const char *tabname= table_share->table_name.str; - char key[FN_REFLEN + 1]; - uchar *data= 0, *pack_data= 0; - size_t length, pack_length; - int error; - NDBDICT *dict= ndb->getDictionary(); - const NDBTAB *altered_table= pOp->getTable(); - - DBUG_PRINT("info", ("Detected frm change of table %s.%s", - dbname, tabname)); - build_table_filename(key, FN_LEN - 1, dbname, tabname, NullS, 0); - /* - If the there is no local table shadowing the altered table and - it has an frm that is different than the one on disk then - overwrite it with the new table definition - */ - if (!ndbcluster_check_if_local_table(dbname, tabname) && - readfrm(key, &data, &length) == 0 && - packfrm(data, length, &pack_data, &pack_length) == 0 && - cmp_frm(altered_table, pack_data, pack_length)) - { - DBUG_DUMP("frm", (uchar*) altered_table->getFrmData(), - altered_table->getFrmLength()); - Ndb_table_guard ndbtab_g(dict, tabname); - const NDBTAB *old= ndbtab_g.get_table(); - if (!old && - old->getObjectVersion() != altered_table->getObjectVersion()) - dict->putTable(altered_table); - - my_free(data); - data= NULL; - if ((error= unpackfrm(&data, &length, - (const uchar*) altered_table->getFrmData())) || - (error= writefrm(key, data, length))) - { - sql_print_information("NDB: Failed write frm for %s.%s, error %d", - dbname, tabname, error); - } - - // copy names as memory will be freed - NdbAutoPtr<char> a1((char *)(dbname= strdup(dbname))); - NdbAutoPtr<char> a2((char *)(tabname= strdup(tabname))); - ndbcluster_binlog_close_table(thd, share); - - TABLE_LIST table_list; - bzero((char*) &table_list,sizeof(table_list)); - table_list.db= (char *)dbname; - table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT); - - if ((error= ndbcluster_binlog_open_table(thd, share, - table_share, table, 1))) - sql_print_information("NDB: Failed to re-open table %s.%s", - dbname, tabname); - - table= share->table; - table_share= share->table_share; - dbname= table_share->db.str; - tabname= table_share->table_name.str; - } - my_free(data); - my_free(pack_data); - } - - // If only frm was changed continue replicating - if (is_online_alter_table) - { - /* Signal ha_ndbcluster::alter_table that drop is done */ - mysql_cond_signal(&injector_cond); - DBUG_RETURN(0); - } - - mysql_mutex_lock(&share->mutex); - if (is_rename_table && !is_remote_change) - { - DBUG_PRINT("info", ("Detected name change of table %s.%s", - share->db, share->table_name)); - /* ToDo: remove printout */ - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: rename table %s%s/%s -> %s.", - share_prefix, share->table->s->db.str, - share->table->s->table_name.str, - share->key); - { - ndb->setDatabaseName(share->table->s->db.str); - Ndb_table_guard ndbtab_g(ndb->getDictionary(), - share->table->s->table_name.str); - const NDBTAB *ev_tab= pOp->getTable(); - const NDBTAB *cache_tab= ndbtab_g.get_table(); - if (cache_tab && - cache_tab->getObjectId() == ev_tab->getObjectId() && - cache_tab->getObjectVersion() <= ev_tab->getObjectVersion()) - ndbtab_g.invalidate(); - } - /* do the rename of the table in the share */ - share->table->s->db.str= share->db; - share->table->s->db.length= strlen(share->db); - share->table->s->table_name.str= share->table_name; - share->table->s->table_name.length= strlen(share->table_name); - } - DBUG_ASSERT(share->op == pOp || share->op_old == pOp); - if (share->op_old == pOp) - share->op_old= 0; - else - share->op= 0; - // either just us or drop table handling as well - - /* Signal ha_ndbcluster::delete/rename_table that drop is done */ - mysql_mutex_unlock(&share->mutex); - mysql_cond_signal(&injector_cond); - - mysql_mutex_lock(&ndbcluster_mutex); - /* ndb_share reference binlog free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog free use_count: %u", - share->key, share->use_count)); - free_share(&share, TRUE); - if (is_remote_change && share && share->state != NSS_DROPPED) - { - DBUG_PRINT("info", ("remote change")); - share->state= NSS_DROPPED; - if (share->use_count != 1) - { - /* open handler holding reference */ - /* wait with freeing create ndb_share to below */ - do_close_cached_tables= TRUE; - } - else - { - /* ndb_share reference create free */ - DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u", - share->key, share->use_count)); - free_share(&share, TRUE); - share= 0; - } - } - else - share= 0; - mysql_mutex_unlock(&ndbcluster_mutex); - - pOp->setCustomData(0); - - mysql_mutex_lock(&injector_mutex); - ndb->dropEventOperation(pOp); - pOp= 0; - mysql_mutex_unlock(&injector_mutex); - - if (do_close_cached_tables) - { - TABLE_LIST table_list; - bzero((char*) &table_list,sizeof(table_list)); - table_list.db= (char *)dbname; - table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT); - /* ndb_share reference create free */ - DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u", - share->key, share->use_count)); - free_share(&share); - } - DBUG_RETURN(0); -} - -static void ndb_binlog_query(THD *thd, Cluster_schema *schema) -{ - if (schema->any_value & NDB_ANYVALUE_RESERVED) - { - if (schema->any_value != NDB_ANYVALUE_FOR_NOLOGGING) - sql_print_warning("NDB: unknown value for binlog signalling 0x%X, " - "query not logged", - schema->any_value); - return; - } - uint32 thd_server_id_save= thd->server_id; - DBUG_ASSERT(sizeof(thd_server_id_save) == sizeof(thd->server_id)); - char *thd_db_save= thd->db; - if (schema->any_value == 0) - thd->server_id= ::server_id; - else - thd->server_id= schema->any_value; - thd->db= schema->db; - int errcode = query_error_code(thd, thd->killed == NOT_KILLED); - thd->binlog_query(THD::STMT_QUERY_TYPE, schema->query, - schema->query_length, FALSE, TRUE, - schema->name[0] == 0 || thd->db[0] == 0, - errcode); - thd->server_id= thd_server_id_save; - thd->db= thd_db_save; -} - -static int -ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, - NdbEventOperation *pOp, - List<Cluster_schema> - *post_epoch_log_list, - List<Cluster_schema> - *post_epoch_unlock_list, - MEM_ROOT *mem_root) -{ - DBUG_ENTER("ndb_binlog_thread_handle_schema_event"); - NDB_SHARE *tmp_share= (NDB_SHARE *)pOp->getCustomData(); - if (tmp_share && ndb_schema_share == tmp_share) - { - NDBEVENT::TableEvent ev_type= pOp->getEventType(); - DBUG_PRINT("enter", ("%s.%s ev_type: %d", - tmp_share->db, tmp_share->table_name, ev_type)); - if (ev_type == NDBEVENT::TE_UPDATE || - ev_type == NDBEVENT::TE_INSERT) - { - Cluster_schema *schema= (Cluster_schema *) - sql_alloc(sizeof(Cluster_schema)); - MY_BITMAP slock; - bitmap_init(&slock, schema->slock, 8*SCHEMA_SLOCK_SIZE, FALSE); - uint node_id= g_ndb_cluster_connection->node_id(); - { - ndbcluster_get_schema(tmp_share, schema); - schema->any_value= pOp->getAnyValue(); - } - enum SCHEMA_OP_TYPE schema_type= (enum SCHEMA_OP_TYPE)schema->type; - DBUG_PRINT("info", - ("%s.%s: log query_length: %d query: '%s' type: %d", - schema->db, schema->name, - schema->query_length, schema->query, - schema_type)); - if (schema_type == SOT_CLEAR_SLOCK) - { - /* - handle slock after epoch is completed to ensure that - schema events get inserted in the binlog after any data - events - */ - post_epoch_log_list->push_back(schema, mem_root); - DBUG_RETURN(0); - } - if (schema->node_id != node_id) - { - int log_query= 0, post_epoch_unlock= 0; - switch (schema_type) - { - case SOT_DROP_TABLE: - // fall through - case SOT_RENAME_TABLE: - // fall through - case SOT_RENAME_TABLE_NEW: - // fall through - case SOT_ALTER_TABLE: - post_epoch_log_list->push_back(schema, mem_root); - /* acknowledge this query _after_ epoch completion */ - post_epoch_unlock= 1; - break; - case SOT_TRUNCATE_TABLE: - { - char key[FN_REFLEN + 1]; - build_table_filename(key, sizeof(key) - 1, - schema->db, schema->name, "", 0); - /* ndb_share reference temporary, free below */ - NDB_SHARE *share= get_share(key, 0, FALSE, FALSE); - if (share) - { - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - } - // invalidation already handled by binlog thread - if (!share || !share->op) - { - { - injector_ndb->setDatabaseName(schema->db); - Ndb_table_guard ndbtab_g(injector_ndb->getDictionary(), - schema->name); - ndbtab_g.invalidate(); - } - TABLE_LIST table_list; - bzero((char*) &table_list,sizeof(table_list)); - table_list.db= schema->db; - table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT); - } - /* ndb_share reference temporary free */ - if (share) - { - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - } - } - // fall through - case SOT_CREATE_TABLE: - if (ndbcluster_check_if_local_table(schema->db, schema->name)) - { - DBUG_PRINT("info", ("NDB Binlog: Skipping locally defined table '%s.%s'", - schema->db, schema->name)); - sql_print_error("NDB Binlog: Skipping locally defined table '%s.%s' from " - "binlog schema event '%s' from node %d. ", - schema->db, schema->name, schema->query, - schema->node_id); - } - else if (ndb_create_table_from_engine(thd, schema->db, schema->name)) - { - print_could_not_discover_error(thd, schema); - } - log_query= 1; - break; - case SOT_DROP_DB: - /* Drop the database locally if it only contains ndb tables */ - if (! ndbcluster_check_if_local_tables_in_db(thd, schema->db)) - { - const int no_print_error[1]= {0}; - run_query(thd, schema->query, - schema->query + schema->query_length, - no_print_error, /* print error */ - TRUE); /* don't binlog the query */ - /* binlog dropping database after any table operations */ - post_epoch_log_list->push_back(schema, mem_root); - /* acknowledge this query _after_ epoch completion */ - post_epoch_unlock= 1; - } - else - { - /* Database contained local tables, leave it */ - sql_print_error("NDB Binlog: Skipping drop database '%s' since it contained local tables " - "binlog schema event '%s' from node %d. ", - schema->db, schema->query, - schema->node_id); - log_query= 1; - } - break; - case SOT_CREATE_DB: - /* fall through */ - case SOT_ALTER_DB: - { - const int no_print_error[1]= {0}; - run_query(thd, schema->query, - schema->query + schema->query_length, - no_print_error, /* print error */ - TRUE); /* don't binlog the query */ - log_query= 1; - break; - } - case SOT_TABLESPACE: - case SOT_LOGFILE_GROUP: - log_query= 1; - break; - case SOT_CLEAR_SLOCK: - abort(); - } - if (log_query && ndb_binlog_running) - ndb_binlog_query(thd, schema); - /* signal that schema operation has been handled */ - DBUG_DUMP("slock", (uchar*) schema->slock, schema->slock_length); - if (bitmap_is_set(&slock, node_id)) - { - if (post_epoch_unlock) - post_epoch_unlock_list->push_back(schema, mem_root); - else - ndbcluster_update_slock(thd, schema->db, schema->name); - } - } - DBUG_RETURN(0); - } - /* - the normal case of UPDATE/INSERT has already been handled - */ - switch (ev_type) - { - case NDBEVENT::TE_DELETE: - // skip - break; - case NDBEVENT::TE_CLUSTER_FAILURE: - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: cluster failure for %s at epoch %u.", - ndb_schema_share->key, (unsigned) pOp->getGCI()); - // fall through - case NDBEVENT::TE_DROP: - if (opt_ndb_extra_logging && - ndb_binlog_tables_inited && ndb_binlog_running) - sql_print_information("NDB Binlog: ndb tables initially " - "read only on reconnect."); - - /* begin protect ndb_schema_share */ - mysql_mutex_lock(&ndb_schema_share_mutex); - /* ndb_share reference binlog extra free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u", - ndb_schema_share->key, - ndb_schema_share->use_count)); - free_share(&ndb_schema_share); - ndb_schema_share= 0; - ndb_binlog_tables_inited= 0; - mysql_mutex_unlock(&ndb_schema_share_mutex); - /* end protect ndb_schema_share */ - - close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT); - // fall through - case NDBEVENT::TE_ALTER: - ndb_handle_schema_change(thd, ndb, pOp, tmp_share); - break; - case NDBEVENT::TE_NODE_FAILURE: - { - uint8 node_id= g_node_id_map[pOp->getNdbdNodeId()]; - DBUG_ASSERT(node_id != 0xFF); - mysql_mutex_lock(&tmp_share->mutex); - bitmap_clear_all(&tmp_share->subscriber_bitmap[node_id]); - DBUG_PRINT("info",("NODE_FAILURE UNSUBSCRIBE[%d]", node_id)); - if (opt_ndb_extra_logging) - { - sql_print_information("NDB Binlog: Node: %d, down," - " Subscriber bitmask %x%x", - pOp->getNdbdNodeId(), - tmp_share->subscriber_bitmap[node_id].bitmap[1], - tmp_share->subscriber_bitmap[node_id].bitmap[0]); - } - mysql_mutex_unlock(&tmp_share->mutex); - mysql_cond_signal(&injector_cond); - break; - } - case NDBEVENT::TE_SUBSCRIBE: - { - uint8 node_id= g_node_id_map[pOp->getNdbdNodeId()]; - uint8 req_id= pOp->getReqNodeId(); - DBUG_ASSERT(req_id != 0 && node_id != 0xFF); - mysql_mutex_lock(&tmp_share->mutex); - bitmap_set_bit(&tmp_share->subscriber_bitmap[node_id], req_id); - DBUG_PRINT("info",("SUBSCRIBE[%d] %d", node_id, req_id)); - if (opt_ndb_extra_logging) - { - sql_print_information("NDB Binlog: Node: %d, subscribe from node %d," - " Subscriber bitmask %x%x", - pOp->getNdbdNodeId(), - req_id, - tmp_share->subscriber_bitmap[node_id].bitmap[1], - tmp_share->subscriber_bitmap[node_id].bitmap[0]); - } - mysql_mutex_unlock(&tmp_share->mutex); - mysql_cond_signal(&injector_cond); - break; - } - case NDBEVENT::TE_UNSUBSCRIBE: - { - uint8 node_id= g_node_id_map[pOp->getNdbdNodeId()]; - uint8 req_id= pOp->getReqNodeId(); - DBUG_ASSERT(req_id != 0 && node_id != 0xFF); - mysql_mutex_lock(&tmp_share->mutex); - bitmap_clear_bit(&tmp_share->subscriber_bitmap[node_id], req_id); - DBUG_PRINT("info",("UNSUBSCRIBE[%d] %d", node_id, req_id)); - if (opt_ndb_extra_logging) - { - sql_print_information("NDB Binlog: Node: %d, unsubscribe from node %d," - " Subscriber bitmask %x%x", - pOp->getNdbdNodeId(), - req_id, - tmp_share->subscriber_bitmap[node_id].bitmap[1], - tmp_share->subscriber_bitmap[node_id].bitmap[0]); - } - mysql_mutex_unlock(&tmp_share->mutex); - mysql_cond_signal(&injector_cond); - break; - } - default: - sql_print_error("NDB Binlog: unknown non data event %d for %s. " - "Ignoring...", (unsigned) ev_type, tmp_share->key); - } - } - DBUG_RETURN(0); -} - -/* - process any operations that should be done after - the epoch is complete -*/ -static void -ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd, - List<Cluster_schema> - *post_epoch_log_list, - List<Cluster_schema> - *post_epoch_unlock_list) -{ - if (post_epoch_log_list->elements == 0) - return; - DBUG_ENTER("ndb_binlog_thread_handle_schema_event_post_epoch"); - Cluster_schema *schema; - while ((schema= post_epoch_log_list->pop())) - { - DBUG_PRINT("info", - ("%s.%s: log query_length: %d query: '%s' type: %d", - schema->db, schema->name, - schema->query_length, schema->query, - schema->type)); - int log_query= 0; - { - enum SCHEMA_OP_TYPE schema_type= (enum SCHEMA_OP_TYPE)schema->type; - char key[FN_REFLEN + 1]; - build_table_filename(key, sizeof(key) - 1, schema->db, schema->name, "", 0); - if (schema_type == SOT_CLEAR_SLOCK) - { - mysql_mutex_lock(&ndbcluster_mutex); - NDB_SCHEMA_OBJECT *ndb_schema_object= - (NDB_SCHEMA_OBJECT*) my_hash_search(&ndb_schema_objects, - (uchar*) key, strlen(key)); - if (ndb_schema_object) - { - mysql_mutex_lock(&ndb_schema_object->mutex); - memcpy(ndb_schema_object->slock, schema->slock, - sizeof(ndb_schema_object->slock)); - DBUG_DUMP("ndb_schema_object->slock_bitmap.bitmap", - (uchar*)ndb_schema_object->slock_bitmap.bitmap, - no_bytes_in_map(&ndb_schema_object->slock_bitmap)); - mysql_mutex_unlock(&ndb_schema_object->mutex); - mysql_cond_signal(&injector_cond); - } - mysql_mutex_unlock(&ndbcluster_mutex); - continue; - } - /* ndb_share reference temporary, free below */ - NDB_SHARE *share= get_share(key, 0, FALSE, FALSE); - if (share) - { - DBUG_PRINT("NDB_SHARE", ("%s temporary use_count: %u", - share->key, share->use_count)); - } - switch (schema_type) - { - case SOT_DROP_DB: - log_query= 1; - break; - case SOT_DROP_TABLE: - log_query= 1; - // invalidation already handled by binlog thread - if (share && share->op) - { - break; - } - // fall through - case SOT_RENAME_TABLE: - // fall through - case SOT_ALTER_TABLE: - // invalidation already handled by binlog thread - if (!share || !share->op) - { - { - injector_ndb->setDatabaseName(schema->db); - Ndb_table_guard ndbtab_g(injector_ndb->getDictionary(), - schema->name); - ndbtab_g.invalidate(); - } - TABLE_LIST table_list; - bzero((char*) &table_list,sizeof(table_list)); - table_list.db= schema->db; - table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT); - } - if (schema_type != SOT_ALTER_TABLE) - break; - // fall through - case SOT_RENAME_TABLE_NEW: - log_query= 1; - if (ndb_binlog_running && (!share || !share->op)) - { - /* - we need to free any share here as command below - may need to call handle_trailing_share - */ - if (share) - { - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - share= 0; - } - if (ndbcluster_check_if_local_table(schema->db, schema->name)) - { - DBUG_PRINT("info", ("NDB Binlog: Skipping locally defined table '%s.%s'", - schema->db, schema->name)); - sql_print_error("NDB Binlog: Skipping locally defined table '%s.%s' from " - "binlog schema event '%s' from node %d. ", - schema->db, schema->name, schema->query, - schema->node_id); - } - else if (ndb_create_table_from_engine(thd, schema->db, schema->name)) - { - print_could_not_discover_error(thd, schema); - } - } - break; - default: - DBUG_ASSERT(FALSE); - } - if (share) - { - /* ndb_share reference temporary free */ - DBUG_PRINT("NDB_SHARE", ("%s temporary free use_count: %u", - share->key, share->use_count)); - free_share(&share); - share= 0; - } - } - if (ndb_binlog_running && log_query) - ndb_binlog_query(thd, schema); - } - while ((schema= post_epoch_unlock_list->pop())) - { - ndbcluster_update_slock(thd, schema->db, schema->name); - } - DBUG_VOID_RETURN; -} - -/* - Timer class for doing performance measurements -*/ - -/********************************************************************* - Internal helper functions for handeling of the cluster replication tables - - ndb_binlog_index - - ndb_apply_status -*********************************************************************/ - -/* - struct to hold the data to be inserted into the - ndb_binlog_index table -*/ -struct ndb_binlog_index_row { - ulonglong gci; - const char *master_log_file; - ulonglong master_log_pos; - ulonglong n_inserts; - ulonglong n_updates; - ulonglong n_deletes; - ulonglong n_schemaops; -}; - -/* - Open the ndb_binlog_index table -*/ -static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index) -{ - static char repdb[]= NDB_REP_DB; - static char reptable[]= NDB_REP_TABLE; - const char *save_proc_info= thd->proc_info; - TABLE_LIST *tables= &binlog_tables; - - tables->init_one_table(repdb, strlen(repdb), reptable, strlen(reptable), - reptable, TL_WRITE); - thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE; - - tables->required_type= FRMTYPE_TABLE; - thd->clear_error(); - if (open_and_lock_tables(thd, tables, FALSE, 0)) - { - if (thd->killed) - sql_print_error("NDB Binlog: Opening ndb_binlog_index: killed"); - else - sql_print_error("NDB Binlog: Opening ndb_binlog_index: %d, '%s'", - thd->stmt_da->sql_errno(), - thd->stmt_da->message()); - thd->proc_info= save_proc_info; - return -1; - } - *ndb_binlog_index= tables->table; - thd->proc_info= save_proc_info; - (*ndb_binlog_index)->use_all_columns(); - return 0; -} - - -/* - Insert one row in the ndb_binlog_index -*/ - -int ndb_add_ndb_binlog_index(THD *thd, void *_row) -{ - ndb_binlog_index_row &row= *(ndb_binlog_index_row *) _row; - int error= 0; - /* - Turn of binlogging to prevent the table changes to be written to - the binary log. - */ - ulong saved_options= thd->variables.option_bits; - thd->variables.option_bits&= ~OPTION_BIN_LOG; - - if (!ndb_binlog_index && open_ndb_binlog_index(thd, &ndb_binlog_index)) - { - sql_print_error("NDB Binlog: Unable to lock table ndb_binlog_index"); - error= -1; - goto add_ndb_binlog_index_err; - } - - /* - Intialize ndb_binlog_index->record[0] - */ - empty_record(ndb_binlog_index); - - ndb_binlog_index->field[0]->store(row.master_log_pos); - ndb_binlog_index->field[1]->store(row.master_log_file, - strlen(row.master_log_file), - &my_charset_bin); - ndb_binlog_index->field[2]->store(row.gci); - ndb_binlog_index->field[3]->store(row.n_inserts); - ndb_binlog_index->field[4]->store(row.n_updates); - ndb_binlog_index->field[5]->store(row.n_deletes); - ndb_binlog_index->field[6]->store(row.n_schemaops); - - if ((error= ndb_binlog_index->file->ha_write_row(ndb_binlog_index->record[0]))) - { - sql_print_error("NDB Binlog: Writing row to ndb_binlog_index: %d", error); - error= -1; - goto add_ndb_binlog_index_err; - } - -add_ndb_binlog_index_err: - thd->stmt_da->can_overwrite_status= TRUE; - thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); - thd->stmt_da->can_overwrite_status= FALSE; - close_thread_tables(thd); - /* - There should be no need for rolling back transaction due to deadlock - (since ndb_binlog_index is non transactional). - */ - DBUG_ASSERT(! thd->transaction_rollback_request); - - thd->mdl_context.release_transactional_locks(); - ndb_binlog_index= 0; - thd->variables.option_bits= saved_options; - return error; -} - -/********************************************************************* - Functions for start, stop, wait for ndbcluster binlog thread -*********************************************************************/ - -enum Binlog_thread_state -{ - BCCC_running= 0, - BCCC_exit= 1, - BCCC_restart= 2 -}; - -static enum Binlog_thread_state do_ndbcluster_binlog_close_connection= BCCC_restart; - -int ndbcluster_binlog_start() -{ - DBUG_ENTER("ndbcluster_binlog_start"); - - if (::server_id == 0) - { - sql_print_warning("NDB: server id set to zero will cause any other mysqld " - "with bin log to log with wrong server id"); - } - else if (::server_id & 0x1 << 31) - { - sql_print_error("NDB: server id's with high bit set is reserved for internal " - "purposes"); - DBUG_RETURN(-1); - } - - mysql_mutex_init(key_injector_mutex, &injector_mutex, MY_MUTEX_INIT_FAST); - mysql_cond_init(key_injector_cond, &injector_cond, NULL); - mysql_mutex_init(key_ndb_schema_share_mutex, - &ndb_schema_share_mutex, MY_MUTEX_INIT_FAST); - - /* Create injector thread */ - if (mysql_thread_create(key_thread_ndb_binlog, - &ndb_binlog_thread, &connection_attrib, - ndb_binlog_thread_func, 0)) - { - DBUG_PRINT("error", ("Could not create ndb injector thread")); - mysql_cond_destroy(&injector_cond); - mysql_mutex_destroy(&injector_mutex); - DBUG_RETURN(-1); - } - - ndbcluster_binlog_inited= 1; - - /* Wait for the injector thread to start */ - mysql_mutex_lock(&injector_mutex); - while (!ndb_binlog_thread_running) - mysql_cond_wait(&injector_cond, &injector_mutex); - mysql_mutex_unlock(&injector_mutex); - - if (ndb_binlog_thread_running < 0) - DBUG_RETURN(-1); - - DBUG_RETURN(0); -} - - -/************************************************************** - Internal helper functions for creating/dropping ndb events - used by the client sql threads -**************************************************************/ -void -ndb_rep_event_name(String *event_name,const char *db, const char *tbl) -{ - event_name->set_ascii("REPL$", 5); - event_name->append(db); - if (tbl) - { - event_name->append('/'); - event_name->append(tbl); - } -} - -bool -ndbcluster_check_if_local_table(const char *dbname, const char *tabname) -{ - char key[FN_REFLEN + 1]; - char ndb_file[FN_REFLEN + 1]; - - DBUG_ENTER("ndbcluster_check_if_local_table"); - build_table_filename(key, FN_LEN-1, dbname, tabname, reg_ext, 0); - build_table_filename(ndb_file, FN_LEN-1, dbname, tabname, ha_ndb_ext, 0); - /* Check that any defined table is an ndb table */ - DBUG_PRINT("info", ("Looking for file %s and %s", key, ndb_file)); - if ((! my_access(key, F_OK)) && my_access(ndb_file, F_OK)) - { - DBUG_PRINT("info", ("table file %s not on disk, local table", ndb_file)); - - - DBUG_RETURN(true); - } - - DBUG_RETURN(false); -} - -bool -ndbcluster_check_if_local_tables_in_db(THD *thd, const char *dbname) -{ - DBUG_ENTER("ndbcluster_check_if_local_tables_in_db"); - DBUG_PRINT("info", ("Looking for files in directory %s", dbname)); - LEX_STRING *tabname; - List<LEX_STRING> files; - char path[FN_REFLEN + 1]; - - build_table_filename(path, sizeof(path) - 1, dbname, "", "", 0); - if (find_files(thd, &files, dbname, path, NullS, 0, NULL) != - FIND_FILES_OK) - { - DBUG_PRINT("info", ("Failed to find files")); - DBUG_RETURN(true); - } - DBUG_PRINT("info",("found: %d files", files.elements)); - while ((tabname= files.pop())) - { - DBUG_PRINT("info", ("Found table %s", tabname->str)); - if (ndbcluster_check_if_local_table(dbname, tabname->str)) - DBUG_RETURN(true); - } - - DBUG_RETURN(false); -} - -/* - Common function for setting up everything for logging a table at - create/discover. -*/ -int ndbcluster_create_binlog_setup(Ndb *ndb, const char *key, - uint key_len, - const char *db, - const char *table_name, - my_bool share_may_exist) -{ - int do_event_op= ndb_binlog_running; - DBUG_ENTER("ndbcluster_create_binlog_setup"); - DBUG_PRINT("enter",("key: %s key_len: %d %s.%s share_may_exist: %d", - key, key_len, db, table_name, share_may_exist)); - DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(table_name)); - DBUG_ASSERT(strlen(key) == key_len); - - mysql_mutex_lock(&ndbcluster_mutex); - - /* Handle any trailing share */ - NDB_SHARE *share= (NDB_SHARE*) my_hash_search(&ndbcluster_open_tables, - (uchar*) key, key_len); - - if (share && share_may_exist) - { - if (share->flags & NSF_NO_BINLOG || - share->op != 0 || - share->op_old != 0) - { - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_RETURN(0); // replication already setup, or should not - } - } - - if (share) - { - if (share->op || share->op_old) - { - my_errno= HA_ERR_TABLE_EXIST; - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_RETURN(1); - } - if (!share_may_exist || share->connect_count != - g_ndb_cluster_connection->get_connect_count()) - { - handle_trailing_share(share); - share= NULL; - } - } - - /* Create share which is needed to hold replication information */ - if (share) - { - /* ndb_share reference create */ - ++share->use_count; - DBUG_PRINT("NDB_SHARE", ("%s create use_count: %u", - share->key, share->use_count)); - } - /* ndb_share reference create */ - else if (!(share= get_share(key, 0, TRUE, TRUE))) - { - sql_print_error("NDB Binlog: " - "allocating table share for %s failed", key); - } - else - { - DBUG_PRINT("NDB_SHARE", ("%s create use_count: %u", - share->key, share->use_count)); - } - - if (!ndb_schema_share && - strcmp(share->db, NDB_REP_DB) == 0 && - strcmp(share->table_name, NDB_SCHEMA_TABLE) == 0) - do_event_op= 1; - else if (!ndb_apply_status_share && - strcmp(share->db, NDB_REP_DB) == 0 && - strcmp(share->table_name, NDB_APPLY_TABLE) == 0) - do_event_op= 1; - - if (!do_event_op) - { - share->flags|= NSF_NO_BINLOG; - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_RETURN(0); - } - mysql_mutex_unlock(&ndbcluster_mutex); - - while (share && !IS_TMP_PREFIX(table_name)) - { - /* - ToDo make sanity check of share so that the table is actually the same - I.e. we need to do open file from frm in this case - Currently awaiting this to be fixed in the 4.1 tree in the general - case - */ - - /* Create the event in NDB */ - ndb->setDatabaseName(db); - - NDBDICT *dict= ndb->getDictionary(); - Ndb_table_guard ndbtab_g(dict, table_name); - const NDBTAB *ndbtab= ndbtab_g.get_table(); - if (ndbtab == 0) - { - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: Failed to get table %s from ndb: " - "%s, %d", key, dict->getNdbError().message, - dict->getNdbError().code); - break; // error - } - String event_name(INJECTOR_EVENT_LEN); - ndb_rep_event_name(&event_name, db, table_name); - /* - event should have been created by someone else, - but let's make sure, and create if it doesn't exist - */ - const NDBEVENT *ev= dict->getEvent(event_name.c_ptr()); - if (!ev) - { - if (ndbcluster_create_event(ndb, ndbtab, event_name.c_ptr(), share)) - { - sql_print_error("NDB Binlog: " - "FAILED CREATE (DISCOVER) TABLE Event: %s", - event_name.c_ptr()); - break; // error - } - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: " - "CREATE (DISCOVER) TABLE Event: %s", - event_name.c_ptr()); - } - else - { - delete ev; - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: DISCOVER TABLE Event: %s", - event_name.c_ptr()); - } - - /* - create the event operations for receiving logging events - */ - if (ndbcluster_create_event_ops(share, ndbtab, event_name.c_ptr())) - { - sql_print_error("NDB Binlog:" - "FAILED CREATE (DISCOVER) EVENT OPERATIONS Event: %s", - event_name.c_ptr()); - /* a warning has been issued to the client */ - DBUG_RETURN(0); - } - DBUG_RETURN(0); - } - DBUG_RETURN(-1); -} - -int -ndbcluster_create_event(Ndb *ndb, const NDBTAB *ndbtab, - const char *event_name, NDB_SHARE *share, - int push_warning) -{ - THD *thd= current_thd; - DBUG_ENTER("ndbcluster_create_event"); - DBUG_PRINT("info", ("table=%s version=%d event=%s share=%s", - ndbtab->getName(), ndbtab->getObjectVersion(), - event_name, share ? share->key : "(nil)")); - DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(ndbtab->getName())); - if (!share) - { - DBUG_PRINT("info", ("share == NULL")); - DBUG_RETURN(0); - } - if (share->flags & NSF_NO_BINLOG) - { - DBUG_PRINT("info", ("share->flags & NSF_NO_BINLOG, flags: %x %d", - share->flags, share->flags & NSF_NO_BINLOG)); - DBUG_RETURN(0); - } - - NDBDICT *dict= ndb->getDictionary(); - NDBEVENT my_event(event_name); - my_event.setTable(*ndbtab); - my_event.addTableEvent(NDBEVENT::TE_ALL); - if (share->flags & NSF_HIDDEN_PK) - { - if (share->flags & NSF_BLOB_FLAG) - { - sql_print_error("NDB Binlog: logging of table %s " - "with BLOB attribute and no PK is not supported", - share->key); - if (push_warning) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_ILLEGAL_HA_CREATE_OPTION, - ER(ER_ILLEGAL_HA_CREATE_OPTION), - ndbcluster_hton_name, - "Binlog of table with BLOB attribute and no PK"); - - share->flags|= NSF_NO_BINLOG; - DBUG_RETURN(-1); - } - /* No primary key, subscribe for all attributes */ - my_event.setReport(NDBEVENT::ER_ALL); - DBUG_PRINT("info", ("subscription all")); - } - else - { - if (ndb_schema_share || strcmp(share->db, NDB_REP_DB) || - strcmp(share->table_name, NDB_SCHEMA_TABLE)) - { - my_event.setReport(NDBEVENT::ER_UPDATED); - DBUG_PRINT("info", ("subscription only updated")); - } - else - { - my_event.setReport((NDBEVENT::EventReport) - (NDBEVENT::ER_ALL | NDBEVENT::ER_SUBSCRIBE)); - DBUG_PRINT("info", ("subscription all and subscribe")); - } - } - if (share->flags & NSF_BLOB_FLAG) - my_event.mergeEvents(TRUE); - - /* add all columns to the event */ - int n_cols= ndbtab->getNoOfColumns(); - for(int a= 0; a < n_cols; a++) - my_event.addEventColumn(a); - - if (dict->createEvent(my_event)) // Add event to database - { - if (dict->getNdbError().classification != NdbError::SchemaObjectExists) - { - /* - failed, print a warning - */ - if (push_warning > 1) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - dict->getNdbError().code, - dict->getNdbError().message, "NDB"); - sql_print_error("NDB Binlog: Unable to create event in database. " - "Event: %s Error Code: %d Message: %s", event_name, - dict->getNdbError().code, dict->getNdbError().message); - DBUG_RETURN(-1); - } - - /* - try retrieving the event, if table version/id matches, we will get - a valid event. Otherwise we have a trailing event from before - */ - const NDBEVENT *ev; - if ((ev= dict->getEvent(event_name))) - { - delete ev; - DBUG_RETURN(0); - } - - /* - trailing event from before; an error, but try to correct it - */ - if (dict->getNdbError().code == NDB_INVALID_SCHEMA_OBJECT && - dict->dropEvent(my_event.getName())) - { - if (push_warning > 1) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - dict->getNdbError().code, - dict->getNdbError().message, "NDB"); - sql_print_error("NDB Binlog: Unable to create event in database. " - " Attempt to correct with drop failed. " - "Event: %s Error Code: %d Message: %s", - event_name, - dict->getNdbError().code, - dict->getNdbError().message); - DBUG_RETURN(-1); - } - - /* - try to add the event again - */ - if (dict->createEvent(my_event)) - { - if (push_warning > 1) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - dict->getNdbError().code, - dict->getNdbError().message, "NDB"); - sql_print_error("NDB Binlog: Unable to create event in database. " - " Attempt to correct with drop ok, but create failed. " - "Event: %s Error Code: %d Message: %s", - event_name, - dict->getNdbError().code, - dict->getNdbError().message); - DBUG_RETURN(-1); - } -#ifdef NDB_BINLOG_EXTRA_WARNINGS - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - 0, "NDB Binlog: Removed trailing event", - "NDB"); -#endif - } - - DBUG_RETURN(0); -} - -inline int is_ndb_compatible_type(Field *field) -{ - return - !(field->flags & BLOB_FLAG) && - field->type() != MYSQL_TYPE_BIT && - field->pack_length() != 0; -} - -/* - - create eventOperations for receiving log events - - setup ndb recattrs for reception of log event data - - "start" the event operation - - used at create/discover of tables -*/ -int -ndbcluster_create_event_ops(NDB_SHARE *share, const NDBTAB *ndbtab, - const char *event_name) -{ - THD *thd= current_thd; - /* - we are in either create table or rename table so table should be - locked, hence we can work with the share without locks - */ - - DBUG_ENTER("ndbcluster_create_event_ops"); - DBUG_PRINT("enter", ("table: %s event: %s", ndbtab->getName(), event_name)); - DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(ndbtab->getName())); - - DBUG_ASSERT(share != 0); - - if (share->flags & NSF_NO_BINLOG) - { - DBUG_PRINT("info", ("share->flags & NSF_NO_BINLOG, flags: %x", - share->flags)); - DBUG_RETURN(0); - } - - int do_ndb_schema_share= 0, do_ndb_apply_status_share= 0; - if (!ndb_schema_share && strcmp(share->db, NDB_REP_DB) == 0 && - strcmp(share->table_name, NDB_SCHEMA_TABLE) == 0) - do_ndb_schema_share= 1; - else if (!ndb_apply_status_share && strcmp(share->db, NDB_REP_DB) == 0 && - strcmp(share->table_name, NDB_APPLY_TABLE) == 0) - do_ndb_apply_status_share= 1; - else if (!binlog_filter->db_ok(share->db) || !ndb_binlog_running) - { - share->flags|= NSF_NO_BINLOG; - DBUG_RETURN(0); - } - - if (share->op) - { - assert(share->op->getCustomData() == (void *) share); - - DBUG_ASSERT(share->use_count > 1); - sql_print_error("NDB Binlog: discover reusing old ev op"); - /* ndb_share reference ToDo free */ - DBUG_PRINT("NDB_SHARE", ("%s ToDo free use_count: %u", - share->key, share->use_count)); - free_share(&share); // old event op already has reference - DBUG_RETURN(0); - } - - TABLE *table= share->table; - - int retries= 100; - /* - 100 milliseconds, temporary error on schema operation can - take some time to be resolved - */ - int retry_sleep= 100; - while (1) - { - mysql_mutex_lock(&injector_mutex); - Ndb *ndb= injector_ndb; - if (do_ndb_schema_share) - ndb= schema_ndb; - - if (ndb == 0) - { - mysql_mutex_unlock(&injector_mutex); - DBUG_RETURN(-1); - } - - NdbEventOperation* op; - if (do_ndb_schema_share) - op= ndb->createEventOperation(event_name); - else - { - // set injector_ndb database/schema from table internal name - int ret= ndb->setDatabaseAndSchemaName(ndbtab); - assert(ret == 0); - op= ndb->createEventOperation(event_name); - // reset to catch errors - ndb->setDatabaseName(""); - } - if (!op) - { - sql_print_error("NDB Binlog: Creating NdbEventOperation failed for" - " %s",event_name); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - ndb->getNdbError().code, - ndb->getNdbError().message, - "NDB"); - mysql_mutex_unlock(&injector_mutex); - DBUG_RETURN(-1); - } - - if (share->flags & NSF_BLOB_FLAG) - op->mergeEvents(TRUE); // currently not inherited from event - - DBUG_PRINT("info", ("share->ndb_value[0]: 0x%lx share->ndb_value[1]: 0x%lx", - (long) share->ndb_value[0], - (long) share->ndb_value[1])); - int n_columns= ndbtab->getNoOfColumns(); - int n_fields= table ? table->s->fields : 0; // XXX ??? - for (int j= 0; j < n_columns; j++) - { - const char *col_name= ndbtab->getColumn(j)->getName(); - NdbValue attr0, attr1; - if (j < n_fields) - { - Field *f= share->table->field[j]; - if (is_ndb_compatible_type(f)) - { - DBUG_PRINT("info", ("%s compatible", col_name)); - attr0.rec= op->getValue(col_name, (char*) f->ptr); - attr1.rec= op->getPreValue(col_name, - (f->ptr - share->table->record[0]) + - (char*) share->table->record[1]); - } - else if (! (f->flags & BLOB_FLAG)) - { - DBUG_PRINT("info", ("%s non compatible", col_name)); - attr0.rec= op->getValue(col_name); - attr1.rec= op->getPreValue(col_name); - } - else - { - DBUG_PRINT("info", ("%s blob", col_name)); - DBUG_ASSERT(share->flags & NSF_BLOB_FLAG); - attr0.blob= op->getBlobHandle(col_name); - attr1.blob= op->getPreBlobHandle(col_name); - if (attr0.blob == NULL || attr1.blob == NULL) - { - sql_print_error("NDB Binlog: Creating NdbEventOperation" - " blob field %u handles failed (code=%d) for %s", - j, op->getNdbError().code, event_name); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - op->getNdbError().code, - op->getNdbError().message, - "NDB"); - ndb->dropEventOperation(op); - mysql_mutex_unlock(&injector_mutex); - DBUG_RETURN(-1); - } - } - } - else - { - DBUG_PRINT("info", ("%s hidden key", col_name)); - attr0.rec= op->getValue(col_name); - attr1.rec= op->getPreValue(col_name); - } - share->ndb_value[0][j].ptr= attr0.ptr; - share->ndb_value[1][j].ptr= attr1.ptr; - DBUG_PRINT("info", ("&share->ndb_value[0][%d]: 0x%lx " - "share->ndb_value[0][%d]: 0x%lx", - j, (long) &share->ndb_value[0][j], - j, (long) attr0.ptr)); - DBUG_PRINT("info", ("&share->ndb_value[1][%d]: 0x%lx " - "share->ndb_value[1][%d]: 0x%lx", - j, (long) &share->ndb_value[0][j], - j, (long) attr1.ptr)); - } - op->setCustomData((void *) share); // set before execute - share->op= op; // assign op in NDB_SHARE - if (op->execute()) - { - share->op= NULL; - retries--; - if (op->getNdbError().status != NdbError::TemporaryError && - op->getNdbError().code != 1407) - retries= 0; - if (retries == 0) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - op->getNdbError().code, op->getNdbError().message, - "NDB"); - sql_print_error("NDB Binlog: ndbevent->execute failed for %s; %d %s", - event_name, - op->getNdbError().code, op->getNdbError().message); - } - ndb->dropEventOperation(op); - mysql_mutex_unlock(&injector_mutex); - if (retries) - { - my_sleep(retry_sleep); - continue; - } - DBUG_RETURN(-1); - } - mysql_mutex_unlock(&injector_mutex); - break; - } - - /* ndb_share reference binlog */ - get_share(share); - DBUG_PRINT("NDB_SHARE", ("%s binlog use_count: %u", - share->key, share->use_count)); - if (do_ndb_apply_status_share) - { - /* ndb_share reference binlog extra */ - ndb_apply_status_share= get_share(share); - DBUG_PRINT("NDB_SHARE", ("%s binlog extra use_count: %u", - share->key, share->use_count)); - mysql_cond_signal(&injector_cond); - } - else if (do_ndb_schema_share) - { - /* ndb_share reference binlog extra */ - ndb_schema_share= get_share(share); - DBUG_PRINT("NDB_SHARE", ("%s binlog extra use_count: %u", - share->key, share->use_count)); - mysql_cond_signal(&injector_cond); - } - - DBUG_PRINT("info",("%s share->op: 0x%lx share->use_count: %u", - share->key, (long) share->op, share->use_count)); - - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: logging %s", share->key); - DBUG_RETURN(0); -} - -/* - when entering the calling thread should have a share lock id share != 0 - then the injector thread will have one as well, i.e. share->use_count == 0 - (unless it has already dropped... then share->op == 0) -*/ -int -ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name, - NDB_SHARE *share, const char *type_str) -{ - DBUG_ENTER("ndbcluster_handle_drop_table"); - THD *thd= current_thd; - - NDBDICT *dict= ndb->getDictionary(); - if (event_name && dict->dropEvent(event_name)) - { - if (dict->getNdbError().code != 4710) - { - /* drop event failed for some reason, issue a warning */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_GET_ERRMSG, ER(ER_GET_ERRMSG), - dict->getNdbError().code, - dict->getNdbError().message, "NDB"); - /* error is not that the event did not exist */ - sql_print_error("NDB Binlog: Unable to drop event in database. " - "Event: %s Error Code: %d Message: %s", - event_name, - dict->getNdbError().code, - dict->getNdbError().message); - /* ToDo; handle error? */ - if (share && share->op && - share->op->getState() == NdbEventOperation::EO_EXECUTING && - dict->getNdbError().mysql_code != HA_ERR_NO_CONNECTION) - { - DBUG_ASSERT(FALSE); - DBUG_RETURN(-1); - } - } - } - - if (share == 0 || share->op == 0) - { - DBUG_RETURN(0); - } - -/* - Syncronized drop between client thread and injector thread is - neccessary in order to maintain ordering in the binlog, - such that the drop occurs _after_ any inserts/updates/deletes. - - The penalty for this is that the drop table becomes slow. - - This wait is however not strictly neccessary to produce a binlog - that is usable. However the slave does not currently handle - these out of order, thus we are keeping the SYNC_DROP_ defined - for now. -*/ - const char *save_proc_info= thd->proc_info; -#define SYNC_DROP_ -#ifdef SYNC_DROP_ - thd->proc_info= "Syncing ndb table schema operation and binlog"; - mysql_mutex_lock(&share->mutex); - int max_timeout= DEFAULT_SYNC_TIMEOUT; - while (share->op) - { - struct timespec abstime; - set_timespec(abstime, 1); - int ret= mysql_cond_timedwait(&injector_cond, - &share->mutex, - &abstime); - if (thd->killed || - share->op == 0) - break; - if (ret) - { - max_timeout--; - if (max_timeout == 0) - { - sql_print_error("NDB %s: %s timed out. Ignoring...", - type_str, share->key); - break; - } - if (opt_ndb_extra_logging) - ndb_report_waiting(type_str, max_timeout, - type_str, share->key); - } - } - mysql_mutex_unlock(&share->mutex); -#else - mysql_mutex_lock(&share->mutex); - share->op_old= share->op; - share->op= 0; - mysql_mutex_unlock(&share->mutex); -#endif - thd->proc_info= save_proc_info; - - DBUG_RETURN(0); -} - - -/******************************************************************** - Internal helper functions for differentd events from the stoarage nodes - used by the ndb injector thread -********************************************************************/ - -/* - Handle error states on events from the storage nodes -*/ -static int ndb_binlog_thread_handle_error(Ndb *ndb, NdbEventOperation *pOp, - ndb_binlog_index_row &row) -{ - NDB_SHARE *share= (NDB_SHARE *)pOp->getCustomData(); - DBUG_ENTER("ndb_binlog_thread_handle_error"); - - int overrun= pOp->isOverrun(); - if (overrun) - { - /* - ToDo: this error should rather clear the ndb_binlog_index... - and continue - */ - sql_print_error("NDB Binlog: Overrun in event buffer, " - "this means we have dropped events. Cannot " - "continue binlog for %s", share->key); - pOp->clearError(); - DBUG_RETURN(-1); - } - - if (!pOp->isConsistent()) - { - /* - ToDo: this error should rather clear the ndb_binlog_index... - and continue - */ - sql_print_error("NDB Binlog: Not Consistent. Cannot " - "continue binlog for %s. Error code: %d" - " Message: %s", share->key, - pOp->getNdbError().code, - pOp->getNdbError().message); - pOp->clearError(); - DBUG_RETURN(-1); - } - sql_print_error("NDB Binlog: unhandled error %d for table %s", - pOp->hasError(), share->key); - pOp->clearError(); - DBUG_RETURN(0); -} - -static int -ndb_binlog_thread_handle_non_data_event(THD *thd, Ndb *ndb, - NdbEventOperation *pOp, - ndb_binlog_index_row &row) -{ - NDB_SHARE *share= (NDB_SHARE *)pOp->getCustomData(); - NDBEVENT::TableEvent type= pOp->getEventType(); - - switch (type) - { - case NDBEVENT::TE_CLUSTER_FAILURE: - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: cluster failure for %s at epoch %u.", - share->key, (unsigned) pOp->getGCI()); - if (ndb_apply_status_share == share) - { - if (opt_ndb_extra_logging && - ndb_binlog_tables_inited && ndb_binlog_running) - sql_print_information("NDB Binlog: ndb tables initially " - "read only on reconnect."); - /* ndb_share reference binlog extra free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u", - share->key, share->use_count)); - free_share(&ndb_apply_status_share); - ndb_apply_status_share= 0; - ndb_binlog_tables_inited= 0; - } - DBUG_PRINT("error", ("CLUSTER FAILURE EVENT: " - "%s received share: 0x%lx op: 0x%lx share op: 0x%lx " - "op_old: 0x%lx", - share->key, (long) share, (long) pOp, - (long) share->op, (long) share->op_old)); - break; - case NDBEVENT::TE_DROP: - if (ndb_apply_status_share == share) - { - if (opt_ndb_extra_logging && - ndb_binlog_tables_inited && ndb_binlog_running) - sql_print_information("NDB Binlog: ndb tables initially " - "read only on reconnect."); - /* ndb_share reference binlog extra free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u", - share->key, share->use_count)); - free_share(&ndb_apply_status_share); - ndb_apply_status_share= 0; - ndb_binlog_tables_inited= 0; - } - /* ToDo: remove printout */ - if (opt_ndb_extra_logging) - sql_print_information("NDB Binlog: drop table %s.", share->key); - // fall through - case NDBEVENT::TE_ALTER: - row.n_schemaops++; - DBUG_PRINT("info", ("TABLE %s EVENT: %s received share: 0x%lx op: 0x%lx " - "share op: 0x%lx op_old: 0x%lx", - type == NDBEVENT::TE_DROP ? "DROP" : "ALTER", - share->key, (long) share, (long) pOp, - (long) share->op, (long) share->op_old)); - break; - case NDBEVENT::TE_NODE_FAILURE: - /* fall through */ - case NDBEVENT::TE_SUBSCRIBE: - /* fall through */ - case NDBEVENT::TE_UNSUBSCRIBE: - /* ignore */ - return 0; - default: - sql_print_error("NDB Binlog: unknown non data event %d for %s. " - "Ignoring...", (unsigned) type, share->key); - return 0; - } - - ndb_handle_schema_change(thd, ndb, pOp, share); - return 0; -} - -/* - Handle data events from the storage nodes -*/ -static int -ndb_binlog_thread_handle_data_event(Ndb *ndb, NdbEventOperation *pOp, - ndb_binlog_index_row &row, - injector::transaction &trans) -{ - NDB_SHARE *share= (NDB_SHARE*) pOp->getCustomData(); - if (share == ndb_apply_status_share) - return 0; - - uint32 originating_server_id= pOp->getAnyValue(); - if (originating_server_id == 0) - originating_server_id= ::server_id; - else if (originating_server_id & NDB_ANYVALUE_RESERVED) - { - if (originating_server_id != NDB_ANYVALUE_FOR_NOLOGGING) - sql_print_warning("NDB: unknown value for binlog signalling 0x%X, " - "event not logged", - originating_server_id); - return 0; - } - else if (!g_ndb_log_slave_updates) - { - /* - This event comes from a slave applier since it has an originating - server id set. Since option to log slave updates is not set, skip it. - */ - return 0; - } - - TABLE *table= share->table; - DBUG_ASSERT(trans.good()); - DBUG_ASSERT(table != 0); - - dbug_print_table("table", table); - - TABLE_SHARE *table_s= table->s; - uint n_fields= table_s->fields; - MY_BITMAP b; - /* Potential buffer for the bitmap */ - uint32 bitbuf[128 / (sizeof(uint32) * 8)]; - bitmap_init(&b, n_fields <= sizeof(bitbuf) * 8 ? bitbuf : NULL, - n_fields, FALSE); - bitmap_set_all(&b); - - /* - row data is already in table->record[0] - As we told the NdbEventOperation to do this - (saves moving data about many times) - */ - - /* - for now malloc/free blobs buffer each time - TODO if possible share single permanent buffer with handlers - */ - uchar* blobs_buffer[2] = { 0, 0 }; - uint blobs_buffer_size[2] = { 0, 0 }; - - switch(pOp->getEventType()) - { - case NDBEVENT::TE_INSERT: - row.n_inserts++; - DBUG_PRINT("info", ("INSERT INTO %s.%s", - table_s->db.str, table_s->table_name.str)); - { - if (share->flags & NSF_BLOB_FLAG) - { - my_ptrdiff_t ptrdiff= 0; - int ret __attribute__((unused))= get_ndb_blobs_value(table, share->ndb_value[0], - blobs_buffer[0], - blobs_buffer_size[0], - ptrdiff); - DBUG_ASSERT(ret == 0); - } - ndb_unpack_record(table, share->ndb_value[0], &b, table->record[0]); - int ret __attribute__((unused))= trans.write_row(originating_server_id, - injector::transaction::table(table, - TRUE), - &b, n_fields, table->record[0]); - DBUG_ASSERT(ret == 0); - } - break; - case NDBEVENT::TE_DELETE: - row.n_deletes++; - DBUG_PRINT("info",("DELETE FROM %s.%s", - table_s->db.str, table_s->table_name.str)); - { - /* - table->record[0] contains only the primary key in this case - since we do not have an after image - */ - int n; - if (table->s->primary_key != MAX_KEY) - n= 0; /* - use the primary key only as it save time and space and - it is the only thing needed to log the delete - */ - else - n= 1; /* - we use the before values since we don't have a primary key - since the mysql server does not handle the hidden primary - key - */ - - if (share->flags & NSF_BLOB_FLAG) - { - my_ptrdiff_t ptrdiff= table->record[n] - table->record[0]; - int ret __attribute__((unused))= get_ndb_blobs_value(table, share->ndb_value[n], - blobs_buffer[n], - blobs_buffer_size[n], - ptrdiff); - DBUG_ASSERT(ret == 0); - } - ndb_unpack_record(table, share->ndb_value[n], &b, table->record[n]); - DBUG_EXECUTE("info", print_records(table, table->record[n]);); - int ret __attribute__((unused))= trans.delete_row(originating_server_id, - injector::transaction::table(table, - TRUE), - &b, n_fields, table->record[n]); - DBUG_ASSERT(ret == 0); - } - break; - case NDBEVENT::TE_UPDATE: - row.n_updates++; - DBUG_PRINT("info", ("UPDATE %s.%s", - table_s->db.str, table_s->table_name.str)); - { - if (share->flags & NSF_BLOB_FLAG) - { - my_ptrdiff_t ptrdiff= 0; - int ret __attribute__((unused))= get_ndb_blobs_value(table, share->ndb_value[0], - blobs_buffer[0], - blobs_buffer_size[0], - ptrdiff); - DBUG_ASSERT(ret == 0); - } - ndb_unpack_record(table, share->ndb_value[0], - &b, table->record[0]); - DBUG_EXECUTE("info", print_records(table, table->record[0]);); - if (table->s->primary_key != MAX_KEY) - { - /* - since table has a primary key, we can do a write - using only after values - */ - trans.write_row(originating_server_id, - injector::transaction::table(table, TRUE), - &b, n_fields, table->record[0]);// after values - } - else - { - /* - mysql server cannot handle the ndb hidden key and - therefore needs the before image as well - */ - if (share->flags & NSF_BLOB_FLAG) - { - my_ptrdiff_t ptrdiff= table->record[1] - table->record[0]; - int ret __attribute__((unused))= get_ndb_blobs_value(table, share->ndb_value[1], - blobs_buffer[1], - blobs_buffer_size[1], - ptrdiff); - DBUG_ASSERT(ret == 0); - } - ndb_unpack_record(table, share->ndb_value[1], &b, table->record[1]); - DBUG_EXECUTE("info", print_records(table, table->record[1]);); - int ret __attribute__((unused))= trans.update_row(originating_server_id, - injector::transaction::table(table, - TRUE), - &b, n_fields, - table->record[1], // before values - table->record[0]);// after values - DBUG_ASSERT(ret == 0); - } - } - break; - default: - /* We should REALLY never get here. */ - DBUG_PRINT("info", ("default - uh oh, a brain exploded.")); - break; - } - - if (share->flags & NSF_BLOB_FLAG) - { - my_free(blobs_buffer[0]); - my_free(blobs_buffer[1]); - } - - return 0; -} - -//#define RUN_NDB_BINLOG_TIMER -#ifdef RUN_NDB_BINLOG_TIMER -class Timer -{ -public: - Timer() { start(); } - void start() { gettimeofday(&m_start, 0); } - void stop() { gettimeofday(&m_stop, 0); } - ulong elapsed_ms() - { - return (ulong) - (((longlong) m_stop.tv_sec - (longlong) m_start.tv_sec) * 1000 + - ((longlong) m_stop.tv_usec - - (longlong) m_start.tv_usec + 999) / 1000); - } -private: - struct timeval m_start,m_stop; -}; -#endif - -/**************************************************************** - Injector thread main loop -****************************************************************/ - -static uchar * -ndb_schema_objects_get_key(NDB_SCHEMA_OBJECT *schema_object, - size_t *length, - my_bool not_used __attribute__((unused))) -{ - *length= schema_object->key_length; - return (uchar*) schema_object->key; -} - -static NDB_SCHEMA_OBJECT *ndb_get_schema_object(const char *key, - my_bool create_if_not_exists, - my_bool have_lock) -{ - NDB_SCHEMA_OBJECT *ndb_schema_object; - uint length= (uint) strlen(key); - DBUG_ENTER("ndb_get_schema_object"); - DBUG_PRINT("enter", ("key: '%s'", key)); - - if (!have_lock) - mysql_mutex_lock(&ndbcluster_mutex); - while (!(ndb_schema_object= - (NDB_SCHEMA_OBJECT*) my_hash_search(&ndb_schema_objects, - (uchar*) key, - length))) - { - if (!create_if_not_exists) - { - DBUG_PRINT("info", ("does not exist")); - break; - } - if (!(ndb_schema_object= - (NDB_SCHEMA_OBJECT*) my_malloc(sizeof(*ndb_schema_object) + length + 1, - MYF(MY_WME | MY_ZEROFILL)))) - { - DBUG_PRINT("info", ("malloc error")); - break; - } - ndb_schema_object->key= (char *)(ndb_schema_object+1); - memcpy(ndb_schema_object->key, key, length + 1); - ndb_schema_object->key_length= length; - if (my_hash_insert(&ndb_schema_objects, (uchar*) ndb_schema_object)) - { - my_free(ndb_schema_object); - break; - } - mysql_mutex_init(key_ndb_schema_object_mutex, &ndb_schema_object->mutex, MY_MUTEX_INIT_FAST); - bitmap_init(&ndb_schema_object->slock_bitmap, ndb_schema_object->slock, - sizeof(ndb_schema_object->slock)*8, FALSE); - bitmap_clear_all(&ndb_schema_object->slock_bitmap); - break; - } - if (ndb_schema_object) - { - ndb_schema_object->use_count++; - DBUG_PRINT("info", ("use_count: %d", ndb_schema_object->use_count)); - } - if (!have_lock) - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_RETURN(ndb_schema_object); -} - - -static void ndb_free_schema_object(NDB_SCHEMA_OBJECT **ndb_schema_object, - bool have_lock) -{ - DBUG_ENTER("ndb_free_schema_object"); - DBUG_PRINT("enter", ("key: '%s'", (*ndb_schema_object)->key)); - if (!have_lock) - mysql_mutex_lock(&ndbcluster_mutex); - if (!--(*ndb_schema_object)->use_count) - { - DBUG_PRINT("info", ("use_count: %d", (*ndb_schema_object)->use_count)); - my_hash_delete(&ndb_schema_objects, (uchar*) *ndb_schema_object); - mysql_mutex_destroy(&(*ndb_schema_object)->mutex); - my_free(*ndb_schema_object); - *ndb_schema_object= 0; - } - else - { - DBUG_PRINT("info", ("use_count: %d", (*ndb_schema_object)->use_count)); - } - if (!have_lock) - mysql_mutex_unlock(&ndbcluster_mutex); - DBUG_VOID_RETURN; -} - -extern ulong opt_ndb_report_thresh_binlog_epoch_slip; -extern ulong opt_ndb_report_thresh_binlog_mem_usage; - -pthread_handler_t ndb_binlog_thread_func(void *arg) -{ - THD *thd; /* needs to be first for thread_stack */ - Ndb *i_ndb= 0; - Ndb *s_ndb= 0; - Thd_ndb *thd_ndb=0; - int ndb_update_ndb_binlog_index= 1; - injector *inj= injector::instance(); - uint incident_id= 0; - -#ifdef RUN_NDB_BINLOG_TIMER - Timer main_timer; -#endif - - mysql_mutex_lock(&injector_mutex); - /* - Set up the Thread - */ - my_thread_init(); - DBUG_ENTER("ndb_binlog_thread"); - - thd= new THD; /* note that contructor of THD uses DBUG_ */ - THD_CHECK_SENTRY(thd); - thd->set_current_stmt_binlog_format_row(); - - /* We need to set thd->thread_id before thd->store_globals, or it will - set an invalid value for thd->variables.pseudo_thread_id. - */ - mysql_mutex_lock(&LOCK_thread_count); - thd->thread_id= thread_id++; - mysql_mutex_unlock(&LOCK_thread_count); - - mysql_thread_set_psi_id(thd->thread_id); - - thd->thread_stack= (char*) &thd; /* remember where our stack is */ - if (thd->store_globals()) - { - thd->cleanup(); - delete thd; - ndb_binlog_thread_running= -1; - mysql_mutex_unlock(&injector_mutex); - mysql_cond_signal(&injector_cond); - - DBUG_LEAVE; // Must match DBUG_ENTER() - my_thread_end(); - pthread_exit(0); - return NULL; // Avoid compiler warnings - } - - thd->init_for_queries(); - thd->command= COM_DAEMON; - thd->system_thread= SYSTEM_THREAD_NDBCLUSTER_BINLOG; - thd->main_security_ctx.host_or_ip= ""; - thd->client_capabilities= 0; - my_net_init(&thd->net, 0); - thd->main_security_ctx.master_access= ~0; - thd->main_security_ctx.priv_user[0]= 0; - /* Do not use user-supplied timeout value for system threads. */ - thd->variables.lock_wait_timeout= LONG_TIMEOUT; - - /* - Set up ndb binlog - */ - sql_print_information("Starting MySQL Cluster Binlog Thread"); - - pthread_detach_this_thread(); - thd->real_id= pthread_self(); - mysql_mutex_lock(&LOCK_thread_count); - threads.append(thd); - mysql_mutex_unlock(&LOCK_thread_count); - thd->lex->start_transaction_opt= 0; - - if (!(s_ndb= new Ndb(g_ndb_cluster_connection, "")) || - s_ndb->init()) - { - sql_print_error("NDB Binlog: Getting Schema Ndb object failed"); - ndb_binlog_thread_running= -1; - mysql_mutex_unlock(&injector_mutex); - mysql_cond_signal(&injector_cond); - goto err; - } - - // empty database - if (!(i_ndb= new Ndb(g_ndb_cluster_connection, "")) || - i_ndb->init()) - { - sql_print_error("NDB Binlog: Getting Ndb object failed"); - ndb_binlog_thread_running= -1; - mysql_mutex_unlock(&injector_mutex); - mysql_cond_signal(&injector_cond); - goto err; - } - - /* init hash for schema object distribution */ - (void) my_hash_init(&ndb_schema_objects, system_charset_info, 32, 0, 0, - (my_hash_get_key)ndb_schema_objects_get_key, 0, 0); - - /* - Expose global reference to our ndb object. - - Used by both sql client thread and binlog thread to interact - with the storage - mysql_mutex_lock(&injector_mutex); - */ - injector_thd= thd; - injector_ndb= i_ndb; - p_latest_trans_gci= - injector_ndb->get_ndb_cluster_connection().get_latest_trans_gci(); - schema_ndb= s_ndb; - - if (opt_bin_log) - { - ndb_binlog_running= TRUE; - } - - /* Thread start up completed */ - ndb_binlog_thread_running= 1; - mysql_mutex_unlock(&injector_mutex); - mysql_cond_signal(&injector_cond); - - /* - wait for mysql server to start (so that the binlog is started - and thus can receive the first GAP event) - */ - mysql_mutex_lock(&LOCK_server_started); - while (!mysqld_server_started) - { - struct timespec abstime; - set_timespec(abstime, 1); - mysql_cond_timedwait(&COND_server_started, &LOCK_server_started, - &abstime); - if (ndbcluster_terminating) - { - mysql_mutex_unlock(&LOCK_server_started); - goto err; - } - } - mysql_mutex_unlock(&LOCK_server_started); -restart: - /* - Main NDB Injector loop - */ - while (ndb_binlog_running) - { - /* - check if it is the first log, if so we do not insert a GAP event - as there is really no log to have a GAP in - */ - if (incident_id == 0) - { - LOG_INFO log_info; - mysql_bin_log.get_current_log(&log_info); - int len= strlen(log_info.log_file_name); - uint no= 0; - if ((sscanf(log_info.log_file_name + len - 6, "%u", &no) == 1) && - no == 1) - { - /* this is the fist log, so skip GAP event */ - break; - } - } - - /* - Always insert a GAP event as we cannot know what has happened - in the cluster while not being connected. - */ - LEX_STRING const msg[2]= - { - { C_STRING_WITH_LEN("mysqld startup") }, - { C_STRING_WITH_LEN("cluster disconnect")} - }; - int error __attribute__((unused))= - inj->record_incident(thd, INCIDENT_LOST_EVENTS, msg[incident_id]); - DBUG_ASSERT(!error); - break; - } - incident_id= 1; - { - thd->proc_info= "Waiting for ndbcluster to start"; - - mysql_mutex_lock(&injector_mutex); - while (!ndb_schema_share || - (ndb_binlog_running && !ndb_apply_status_share)) - { - /* ndb not connected yet */ - struct timespec abstime; - set_timespec(abstime, 1); - mysql_cond_timedwait(&injector_cond, &injector_mutex, &abstime); - if (ndbcluster_binlog_terminating) - { - mysql_mutex_unlock(&injector_mutex); - goto err; - } - } - mysql_mutex_unlock(&injector_mutex); - - if (thd_ndb == NULL) - { - DBUG_ASSERT(ndbcluster_hton->slot != ~(uint)0); - if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb())) - { - sql_print_error("Could not allocate Thd_ndb object"); - goto err; - } - set_thd_ndb(thd, thd_ndb); - thd_ndb->options|= TNO_NO_LOG_SCHEMA_OP; - thd->query_id= 0; // to keep valgrind quiet - } - } - - { - // wait for the first event - thd->proc_info= "Waiting for first event from ndbcluster"; - int schema_res, res; - Uint64 schema_gci; - do - { - DBUG_PRINT("info", ("Waiting for the first event")); - - if (ndbcluster_binlog_terminating) - goto err; - - schema_res= s_ndb->pollEvents(100, &schema_gci); - } while (schema_gci == 0 || ndb_latest_received_binlog_epoch == schema_gci); - if (ndb_binlog_running) - { - Uint64 gci= i_ndb->getLatestGCI(); - while (gci < schema_gci || gci == ndb_latest_received_binlog_epoch) - { - if (ndbcluster_binlog_terminating) - goto err; - res= i_ndb->pollEvents(10, &gci); - } - if (gci > schema_gci) - { - schema_gci= gci; - } - } - // now check that we have epochs consistant with what we had before the restart - DBUG_PRINT("info", ("schema_res: %d schema_gci: %lu", schema_res, - (long) schema_gci)); - { - i_ndb->flushIncompleteEvents(schema_gci); - s_ndb->flushIncompleteEvents(schema_gci); - if (schema_gci < ndb_latest_handled_binlog_epoch) - { - sql_print_error("NDB Binlog: cluster has been restarted --initial or with older filesystem. " - "ndb_latest_handled_binlog_epoch: %u, while current epoch: %u. " - "RESET MASTER should be issued. Resetting ndb_latest_handled_binlog_epoch.", - (unsigned) ndb_latest_handled_binlog_epoch, (unsigned) schema_gci); - *p_latest_trans_gci= 0; - ndb_latest_handled_binlog_epoch= 0; - ndb_latest_applied_binlog_epoch= 0; - ndb_latest_received_binlog_epoch= 0; - } - else if (ndb_latest_applied_binlog_epoch > 0) - { - sql_print_warning("NDB Binlog: cluster has reconnected. " - "Changes to the database that occured while " - "disconnected will not be in the binlog"); - } - if (opt_ndb_extra_logging) - { - sql_print_information("NDB Binlog: starting log at epoch %u", - (unsigned)schema_gci); - } - } - } - { - static char db[]= ""; - thd->db= db; - } - do_ndbcluster_binlog_close_connection= BCCC_running; - for ( ; !((ndbcluster_binlog_terminating || - do_ndbcluster_binlog_close_connection) && - ndb_latest_handled_binlog_epoch >= *p_latest_trans_gci) && - do_ndbcluster_binlog_close_connection != BCCC_restart; ) - { -#ifndef DBUG_OFF - if (do_ndbcluster_binlog_close_connection) - { - DBUG_PRINT("info", ("do_ndbcluster_binlog_close_connection: %d, " - "ndb_latest_handled_binlog_epoch: %lu, " - "*p_latest_trans_gci: %lu", - do_ndbcluster_binlog_close_connection, - (ulong) ndb_latest_handled_binlog_epoch, - (ulong) *p_latest_trans_gci)); - } -#endif -#ifdef RUN_NDB_BINLOG_TIMER - main_timer.stop(); - sql_print_information("main_timer %ld ms", main_timer.elapsed_ms()); - main_timer.start(); -#endif - - /* - now we don't want any events before next gci is complete - */ - thd->proc_info= "Waiting for event from ndbcluster"; - thd->set_time(); - - /* wait for event or 1000 ms */ - Uint64 gci= 0, schema_gci; - int res= 0, tot_poll_wait= 1000; - if (ndb_binlog_running) - { - res= i_ndb->pollEvents(tot_poll_wait, &gci); - tot_poll_wait= 0; - } - else - { - /* - Just consume any events, not used if no binlogging - e.g. node failure events - */ - Uint64 tmp_gci; - if (i_ndb->pollEvents(0, &tmp_gci)) - while (i_ndb->nextEvent()) - ; - } - int schema_res= s_ndb->pollEvents(tot_poll_wait, &schema_gci); - ndb_latest_received_binlog_epoch= gci; - - while (gci > schema_gci && schema_res >= 0) - { - static char buf[64]; - thd->proc_info= "Waiting for schema epoch"; - my_snprintf(buf, sizeof(buf), "%s %u(%u)", thd->proc_info, (unsigned) schema_gci, (unsigned) gci); - thd->proc_info= buf; - schema_res= s_ndb->pollEvents(10, &schema_gci); - } - - if ((ndbcluster_binlog_terminating || - do_ndbcluster_binlog_close_connection) && - (ndb_latest_handled_binlog_epoch >= *p_latest_trans_gci || - !ndb_binlog_running)) - break; /* Shutting down server */ - - if (ndb_binlog_index && ndb_binlog_index->s->has_old_version()) - { - if (ndb_binlog_index->s->has_old_version()) - { - trans_commit_stmt(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - ndb_binlog_index= 0; - } - } - - MEM_ROOT **root_ptr= - my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC); - MEM_ROOT *old_root= *root_ptr; - MEM_ROOT mem_root; - init_sql_alloc(&mem_root, 4096, 0); - List<Cluster_schema> post_epoch_log_list; - List<Cluster_schema> post_epoch_unlock_list; - *root_ptr= &mem_root; - - if (unlikely(schema_res > 0)) - { - thd->proc_info= "Processing events from schema table"; - s_ndb-> - setReportThreshEventGCISlip(opt_ndb_report_thresh_binlog_epoch_slip); - s_ndb-> - setReportThreshEventFreeMem(opt_ndb_report_thresh_binlog_mem_usage); - NdbEventOperation *pOp= s_ndb->nextEvent(); - while (pOp != NULL) - { - if (!pOp->hasError()) - { - ndb_binlog_thread_handle_schema_event(thd, s_ndb, pOp, - &post_epoch_log_list, - &post_epoch_unlock_list, - &mem_root); - DBUG_PRINT("info", ("s_ndb first: %s", s_ndb->getEventOperation() ? - s_ndb->getEventOperation()->getEvent()->getTable()->getName() : - "<empty>")); - DBUG_PRINT("info", ("i_ndb first: %s", i_ndb->getEventOperation() ? - i_ndb->getEventOperation()->getEvent()->getTable()->getName() : - "<empty>")); - if (i_ndb->getEventOperation() == NULL && - s_ndb->getEventOperation() == NULL && - do_ndbcluster_binlog_close_connection == BCCC_running) - { - DBUG_PRINT("info", ("do_ndbcluster_binlog_close_connection= BCCC_restart")); - do_ndbcluster_binlog_close_connection= BCCC_restart; - if (ndb_latest_received_binlog_epoch < *p_latest_trans_gci && ndb_binlog_running) - { - sql_print_error("NDB Binlog: latest transaction in epoch %lu not in binlog " - "as latest received epoch is %lu", - (ulong) *p_latest_trans_gci, - (ulong) ndb_latest_received_binlog_epoch); - } - } - } - else - sql_print_error("NDB: error %lu (%s) on handling " - "binlog schema event", - (ulong) pOp->getNdbError().code, - pOp->getNdbError().message); - pOp= s_ndb->nextEvent(); - } - } - - if (res > 0) - { - DBUG_PRINT("info", ("pollEvents res: %d", res)); - thd->proc_info= "Processing events"; - NdbEventOperation *pOp= i_ndb->nextEvent(); - ndb_binlog_index_row row; - while (pOp != NULL) - { -#ifdef RUN_NDB_BINLOG_TIMER - Timer gci_timer, write_timer; - int event_count= 0; - gci_timer.start(); -#endif - gci= pOp->getGCI(); - DBUG_PRINT("info", ("Handling gci: %d", (unsigned)gci)); - // sometimes get TE_ALTER with invalid table - DBUG_ASSERT(pOp->getEventType() == NdbDictionary::Event::TE_ALTER || - ! IS_NDB_BLOB_PREFIX(pOp->getEvent()->getTable()->getName())); - DBUG_ASSERT(gci <= ndb_latest_received_binlog_epoch); - - /* initialize some variables for this epoch */ - g_ndb_log_slave_updates= opt_log_slave_updates; - i_ndb-> - setReportThreshEventGCISlip(opt_ndb_report_thresh_binlog_epoch_slip); - i_ndb->setReportThreshEventFreeMem(opt_ndb_report_thresh_binlog_mem_usage); - - bzero((char*) &row, sizeof(row)); - thd->variables.character_set_client= &my_charset_latin1; - injector::transaction trans; - // pass table map before epoch - { - Uint32 iter= 0; - const NdbEventOperation *gci_op; - Uint32 event_types; - while ((gci_op= i_ndb->getGCIEventOperations(&iter, &event_types)) - != NULL) - { - NDB_SHARE *share= (NDB_SHARE*)gci_op->getCustomData(); - DBUG_PRINT("info", ("per gci_op: 0x%lx share: 0x%lx event_types: 0x%x", - (long) gci_op, (long) share, event_types)); - // workaround for interface returning TE_STOP events - // which are normally filtered out below in the nextEvent loop - if ((event_types & ~NdbDictionary::Event::TE_STOP) == 0) - { - DBUG_PRINT("info", ("Skipped TE_STOP on table %s", - gci_op->getEvent()->getTable()->getName())); - continue; - } - // this should not happen - if (share == NULL || share->table == NULL) - { - DBUG_PRINT("info", ("no share or table %s!", - gci_op->getEvent()->getTable()->getName())); - continue; - } - if (share == ndb_apply_status_share) - { - // skip this table, it is handled specially - continue; - } - TABLE *table= share->table; -#ifndef DBUG_OFF - const LEX_STRING &name= table->s->table_name; -#endif - if ((event_types & (NdbDictionary::Event::TE_INSERT | - NdbDictionary::Event::TE_UPDATE | - NdbDictionary::Event::TE_DELETE)) == 0) - { - DBUG_PRINT("info", ("skipping non data event table: %.*s", - (int) name.length, name.str)); - continue; - } - if (!trans.good()) - { - DBUG_PRINT("info", - ("Found new data event, initializing transaction")); - inj->new_trans(thd, &trans); - } - DBUG_PRINT("info", ("use_table: %.*s", - (int) name.length, name.str)); - injector::transaction::table tbl(table, TRUE); - int ret __attribute__((unused))= trans.use_table(::server_id, tbl); - DBUG_ASSERT(ret == 0); - } - } - if (trans.good()) - { - if (ndb_apply_status_share) - { - TABLE *table= ndb_apply_status_share->table; - -#ifndef DBUG_OFF - const LEX_STRING& name= table->s->table_name; - DBUG_PRINT("info", ("use_table: %.*s", - (int) name.length, name.str)); -#endif - injector::transaction::table tbl(table, TRUE); - int ret __attribute__((unused))= trans.use_table(::server_id, tbl); - DBUG_ASSERT(ret == 0); - - /* - Intialize table->record[0] - */ - empty_record(table); - - table->field[0]->store((longlong)::server_id); - table->field[1]->store((longlong)gci); - table->field[2]->store("", 0, &my_charset_bin); - table->field[3]->store((longlong)0); - table->field[4]->store((longlong)0); - trans.write_row(::server_id, - injector::transaction::table(table, TRUE), - &table->s->all_set, table->s->fields, - table->record[0]); - } - else - { - sql_print_error("NDB: Could not get apply status share"); - } - } -#ifdef RUN_NDB_BINLOG_TIMER - write_timer.start(); -#endif - do - { -#ifdef RUN_NDB_BINLOG_TIMER - event_count++; -#endif - if (pOp->hasError() && - ndb_binlog_thread_handle_error(i_ndb, pOp, row) < 0) - goto err; - -#ifndef DBUG_OFF - { - NDB_SHARE *share= (NDB_SHARE*) pOp->getCustomData(); - DBUG_PRINT("info", - ("EVENT TYPE: %d GCI: %ld last applied: %ld " - "share: 0x%lx (%s.%s)", pOp->getEventType(), - (long) gci, - (long) ndb_latest_applied_binlog_epoch, - (long) share, - share ? share->db : "'NULL'", - share ? share->table_name : "'NULL'")); - DBUG_ASSERT(share != 0); - } - // assert that there is consistancy between gci op list - // and event list - { - Uint32 iter= 0; - const NdbEventOperation *gci_op; - Uint32 event_types; - while ((gci_op= i_ndb->getGCIEventOperations(&iter, &event_types)) - != NULL) - { - if (gci_op == pOp) - break; - } - DBUG_ASSERT(gci_op == pOp); - DBUG_ASSERT((event_types & pOp->getEventType()) != 0); - } -#endif - if ((unsigned) pOp->getEventType() < - (unsigned) NDBEVENT::TE_FIRST_NON_DATA_EVENT) - ndb_binlog_thread_handle_data_event(i_ndb, pOp, row, trans); - else - { - // set injector_ndb database/schema from table internal name - int ret __attribute__((unused))= - i_ndb->setDatabaseAndSchemaName(pOp->getEvent()->getTable()); - DBUG_ASSERT(ret == 0); - ndb_binlog_thread_handle_non_data_event(thd, i_ndb, pOp, row); - // reset to catch errors - i_ndb->setDatabaseName(""); - DBUG_PRINT("info", ("s_ndb first: %s", s_ndb->getEventOperation() ? - s_ndb->getEventOperation()->getEvent()->getTable()->getName() : - "<empty>")); - DBUG_PRINT("info", ("i_ndb first: %s", i_ndb->getEventOperation() ? - i_ndb->getEventOperation()->getEvent()->getTable()->getName() : - "<empty>")); - if (i_ndb->getEventOperation() == NULL && - s_ndb->getEventOperation() == NULL && - do_ndbcluster_binlog_close_connection == BCCC_running) - { - DBUG_PRINT("info", ("do_ndbcluster_binlog_close_connection= BCCC_restart")); - do_ndbcluster_binlog_close_connection= BCCC_restart; - if (ndb_latest_received_binlog_epoch < *p_latest_trans_gci && ndb_binlog_running) - { - sql_print_error("NDB Binlog: latest transaction in epoch %lu not in binlog " - "as latest received epoch is %lu", - (ulong) *p_latest_trans_gci, - (ulong) ndb_latest_received_binlog_epoch); - } - } - } - - pOp= i_ndb->nextEvent(); - } while (pOp && pOp->getGCI() == gci); - - /* - note! pOp is not referring to an event in the next epoch - or is == 0 - */ -#ifdef RUN_NDB_BINLOG_TIMER - write_timer.stop(); -#endif - - if (trans.good()) - { - //DBUG_ASSERT(row.n_inserts || row.n_updates || row.n_deletes); - thd->proc_info= "Committing events to binlog"; - injector::transaction::binlog_pos start= trans.start_pos(); - if (int r= trans.commit()) - { - sql_print_error("NDB Binlog: " - "Error during COMMIT of GCI. Error: %d", - r); - /* TODO: Further handling? */ - } - row.gci= gci; - row.master_log_file= start.file_name(); - row.master_log_pos= start.file_pos(); - - DBUG_PRINT("info", ("COMMIT gci: %lu", (ulong) gci)); - if (ndb_update_ndb_binlog_index) - ndb_add_ndb_binlog_index(thd, &row); - ndb_latest_applied_binlog_epoch= gci; - } - ndb_latest_handled_binlog_epoch= gci; -#ifdef RUN_NDB_BINLOG_TIMER - gci_timer.stop(); - sql_print_information("gci %ld event_count %d write time " - "%ld(%d e/s), total time %ld(%d e/s)", - (ulong)gci, event_count, - write_timer.elapsed_ms(), - (1000*event_count) / write_timer.elapsed_ms(), - gci_timer.elapsed_ms(), - (1000*event_count) / gci_timer.elapsed_ms()); -#endif - } - } - - ndb_binlog_thread_handle_schema_event_post_epoch(thd, - &post_epoch_log_list, - &post_epoch_unlock_list); - free_root(&mem_root, MYF(0)); - *root_ptr= old_root; - ndb_latest_handled_binlog_epoch= ndb_latest_received_binlog_epoch; - } - if (do_ndbcluster_binlog_close_connection == BCCC_restart) - { - ndb_binlog_tables_inited= FALSE; - trans_commit_stmt(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - ndb_binlog_index= 0; - goto restart; - } -err: - sql_print_information("Stopping Cluster Binlog"); - DBUG_PRINT("info",("Shutting down cluster binlog thread")); - thd->proc_info= "Shutting down"; - thd->stmt_da->can_overwrite_status= TRUE; - thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); - thd->stmt_da->can_overwrite_status= FALSE; - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - mysql_mutex_lock(&injector_mutex); - /* don't mess with the injector_ndb anymore from other threads */ - injector_thd= 0; - injector_ndb= 0; - p_latest_trans_gci= 0; - schema_ndb= 0; - mysql_mutex_unlock(&injector_mutex); - thd->db= 0; // as not to try to free memory - - if (ndb_apply_status_share) - { - /* ndb_share reference binlog extra free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u", - ndb_apply_status_share->key, - ndb_apply_status_share->use_count)); - free_share(&ndb_apply_status_share); - ndb_apply_status_share= 0; - } - if (ndb_schema_share) - { - /* begin protect ndb_schema_share */ - mysql_mutex_lock(&ndb_schema_share_mutex); - /* ndb_share reference binlog extra free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog extra free use_count: %u", - ndb_schema_share->key, - ndb_schema_share->use_count)); - free_share(&ndb_schema_share); - ndb_schema_share= 0; - ndb_binlog_tables_inited= 0; - mysql_mutex_unlock(&ndb_schema_share_mutex); - /* end protect ndb_schema_share */ - } - - /* remove all event operations */ - if (s_ndb) - { - NdbEventOperation *op; - DBUG_PRINT("info",("removing all event operations")); - while ((op= s_ndb->getEventOperation())) - { - DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(op->getEvent()->getTable()->getName())); - DBUG_PRINT("info",("removing event operation on %s", - op->getEvent()->getName())); - NDB_SHARE *share= (NDB_SHARE*) op->getCustomData(); - DBUG_ASSERT(share != 0); - DBUG_ASSERT(share->op == op || - share->op_old == op); - share->op= share->op_old= 0; - /* ndb_share reference binlog free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog free use_count: %u", - share->key, share->use_count)); - free_share(&share); - s_ndb->dropEventOperation(op); - } - delete s_ndb; - s_ndb= 0; - } - if (i_ndb) - { - NdbEventOperation *op; - DBUG_PRINT("info",("removing all event operations")); - while ((op= i_ndb->getEventOperation())) - { - DBUG_ASSERT(! IS_NDB_BLOB_PREFIX(op->getEvent()->getTable()->getName())); - DBUG_PRINT("info",("removing event operation on %s", - op->getEvent()->getName())); - NDB_SHARE *share= (NDB_SHARE*) op->getCustomData(); - DBUG_ASSERT(share != 0); - DBUG_ASSERT(share->op == op || - share->op_old == op); - share->op= share->op_old= 0; - /* ndb_share reference binlog free */ - DBUG_PRINT("NDB_SHARE", ("%s binlog free use_count: %u", - share->key, share->use_count)); - free_share(&share); - i_ndb->dropEventOperation(op); - } - delete i_ndb; - i_ndb= 0; - } - - my_hash_free(&ndb_schema_objects); - - net_end(&thd->net); - thd->cleanup(); - delete thd; - - ndb_binlog_thread_running= -1; - ndb_binlog_running= FALSE; - mysql_cond_signal(&injector_cond); - - DBUG_PRINT("exit", ("ndb_binlog_thread")); - - DBUG_LEAVE; // Must match DBUG_ENTER() - my_thread_end(); - pthread_exit(0); - return NULL; // Avoid compiler warnings -} - -bool -ndbcluster_show_status_binlog(THD* thd, stat_print_fn *stat_print, - enum ha_stat_type stat_type) -{ - char buf[IO_SIZE]; - uint buflen; - ulonglong ndb_latest_epoch= 0; - DBUG_ENTER("ndbcluster_show_status_binlog"); - - mysql_mutex_lock(&injector_mutex); - if (injector_ndb) - { - char buff1[22],buff2[22],buff3[22],buff4[22],buff5[22]; - ndb_latest_epoch= injector_ndb->getLatestGCI(); - mysql_mutex_unlock(&injector_mutex); - - buflen= - snprintf(buf, sizeof(buf), - "latest_epoch=%s, " - "latest_trans_epoch=%s, " - "latest_received_binlog_epoch=%s, " - "latest_handled_binlog_epoch=%s, " - "latest_applied_binlog_epoch=%s", - llstr(ndb_latest_epoch, buff1), - llstr(*p_latest_trans_gci, buff2), - llstr(ndb_latest_received_binlog_epoch, buff3), - llstr(ndb_latest_handled_binlog_epoch, buff4), - llstr(ndb_latest_applied_binlog_epoch, buff5)); - if (stat_print(thd, ndbcluster_hton_name, ndbcluster_hton_name_length, - "binlog", strlen("binlog"), - buf, buflen)) - DBUG_RETURN(TRUE); - } - else - mysql_mutex_unlock(&injector_mutex); - DBUG_RETURN(FALSE); -} - -#endif /* HAVE_NDB_BINLOG */ -#endif diff --git a/sql/ha_ndbcluster_binlog.h b/sql/ha_ndbcluster_binlog.h deleted file mode 100644 index a02f687d76f..00000000000 --- a/sql/ha_ndbcluster_binlog.h +++ /dev/null @@ -1,239 +0,0 @@ -#ifndef HA_NDBCLUSTER_BINLOG_INCLUDED -#define HA_NDBCLUSTER_BINLOG_INCLUDED - -/* 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-1301 USA -*/ - -#include "sql_class.h" /* THD */ - -// Typedefs for long names -typedef NdbDictionary::Object NDBOBJ; -typedef NdbDictionary::Column NDBCOL; -typedef NdbDictionary::Table NDBTAB; -typedef NdbDictionary::Index NDBINDEX; -typedef NdbDictionary::Dictionary NDBDICT; -typedef NdbDictionary::Event NDBEVENT; - -#define IS_TMP_PREFIX(A) (is_prefix(A, tmp_file_prefix)) - -#define INJECTOR_EVENT_LEN 200 - -#define NDB_INVALID_SCHEMA_OBJECT 241 - -/* server id's with high bit set is reservered */ -#define NDB_ANYVALUE_FOR_NOLOGGING 0xFFFFFFFF -#define NDB_ANYVALUE_RESERVED 0x80000000 - -extern handlerton *ndbcluster_hton; - -/* - The numbers below must not change as they - are passed between mysql servers, and if changed - would break compatablility. Add new numbers to - the end. -*/ -enum SCHEMA_OP_TYPE -{ - SOT_DROP_TABLE= 0, - SOT_CREATE_TABLE= 1, - SOT_RENAME_TABLE_NEW= 2, - SOT_ALTER_TABLE= 3, - SOT_DROP_DB= 4, - SOT_CREATE_DB= 5, - SOT_ALTER_DB= 6, - SOT_CLEAR_SLOCK= 7, - SOT_TABLESPACE= 8, - SOT_LOGFILE_GROUP= 9, - SOT_RENAME_TABLE= 10, - SOT_TRUNCATE_TABLE= 11 -}; - -const uint max_ndb_nodes= 64; /* multiple of 32 */ - -static const char *ha_ndb_ext=".ndb"; -static const char share_prefix[]= "./"; - -class Ndb_table_guard -{ -public: - Ndb_table_guard(NDBDICT *dict, const char *tabname) - : m_dict(dict) - { - DBUG_ENTER("Ndb_table_guard"); - m_ndbtab= m_dict->getTableGlobal(tabname); - m_invalidate= 0; - DBUG_PRINT("info", ("m_ndbtab: %p", m_ndbtab)); - DBUG_VOID_RETURN; - } - ~Ndb_table_guard() - { - DBUG_ENTER("~Ndb_table_guard"); - if (m_ndbtab) - { - DBUG_PRINT("info", ("m_ndbtab: %p m_invalidate: %d", - m_ndbtab, m_invalidate)); - m_dict->removeTableGlobal(*m_ndbtab, m_invalidate); - } - DBUG_VOID_RETURN; - } - const NDBTAB *get_table() { return m_ndbtab; } - void invalidate() { m_invalidate= 1; } - const NDBTAB *release() - { - DBUG_ENTER("Ndb_table_guard::release"); - const NDBTAB *tmp= m_ndbtab; - DBUG_PRINT("info", ("m_ndbtab: %p", m_ndbtab)); - m_ndbtab = 0; - DBUG_RETURN(tmp); - } -private: - const NDBTAB *m_ndbtab; - NDBDICT *m_dict; - int m_invalidate; -}; - -#ifdef HAVE_NDB_BINLOG - -#ifdef HAVE_PSI_INTERFACE -extern PSI_mutex_key key_injector_mutex, key_ndb_schema_share_mutex, - key_ndb_schema_object_mutex; -extern PSI_cond_key key_injector_cond; -extern PSI_thread_key key_thread_ndb_binlog; -#endif /* HAVE_PSI_INTERFACE */ - -extern pthread_t ndb_binlog_thread; -extern mysql_mutex_t injector_mutex; -extern mysql_cond_t injector_cond; - -extern unsigned char g_node_id_map[max_ndb_nodes]; -extern pthread_t ndb_util_thread; -extern mysql_mutex_t LOCK_ndb_util_thread; -extern mysql_cond_t COND_ndb_util_thread; -extern int ndbcluster_util_inited; -extern mysql_mutex_t ndbcluster_mutex; -extern HASH ndbcluster_open_tables; -extern Ndb_cluster_connection* g_ndb_cluster_connection; -extern long ndb_number_of_storage_nodes; - -/* - Initialize the binlog part of the ndb handlerton -*/ -void ndbcluster_binlog_init_handlerton(); -/* - Initialize the binlog part of the NDB_SHARE -*/ -int ndbcluster_binlog_init_share(NDB_SHARE *share, TABLE *table); - -bool ndbcluster_check_if_local_table(const char *dbname, const char *tabname); -bool ndbcluster_check_if_local_tables_in_db(THD *thd, const char *dbname); - -int ndbcluster_create_binlog_setup(Ndb *ndb, const char *key, - uint key_len, - const char *db, - const char *table_name, - my_bool share_may_exist); -int ndbcluster_create_event(Ndb *ndb, const NDBTAB *table, - const char *event_name, NDB_SHARE *share, - int push_warning= 0); -int ndbcluster_create_event_ops(NDB_SHARE *share, - const NDBTAB *ndbtab, - const char *event_name); -int ndbcluster_log_schema_op(THD *thd, NDB_SHARE *share, - const char *query, int query_length, - const char *db, const char *table_name, - uint32 ndb_table_id, - uint32 ndb_table_version, - enum SCHEMA_OP_TYPE type, - const char *new_db, - const char *new_table_name); -int ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name, - NDB_SHARE *share, - const char *type_str); -void ndb_rep_event_name(String *event_name, - const char *db, const char *tbl); -int ndb_create_table_from_engine(THD *thd, const char *db, - const char *table_name); -int ndbcluster_binlog_start(); -pthread_handler_t ndb_binlog_thread_func(void *arg); - -/* - table mysql.ndb_apply_status -*/ -int ndbcluster_setup_binlog_table_shares(THD *thd); -extern NDB_SHARE *ndb_apply_status_share; -extern NDB_SHARE *ndb_schema_share; - -extern THD *injector_thd; -extern my_bool ndb_binlog_running; -extern my_bool ndb_binlog_tables_inited; - -bool -ndbcluster_show_status_binlog(THD* thd, stat_print_fn *stat_print, - enum ha_stat_type stat_type); - -/* - prototypes for ndb handler utility function also needed by - the ndb binlog code -*/ -int cmp_frm(const NDBTAB *ndbtab, const void *pack_data, - uint pack_length); -int ndbcluster_find_all_files(THD *thd); -#endif /* HAVE_NDB_BINLOG */ - -void ndb_unpack_record(TABLE *table, NdbValue *value, - MY_BITMAP *defined, uchar *buf); -char *ndb_pack_varchar(const NDBCOL *col, char *buf, - const char *str, int sz); - -NDB_SHARE *ndbcluster_get_share(const char *key, - TABLE *table, - bool create_if_not_exists, - bool have_lock); -NDB_SHARE *ndbcluster_get_share(NDB_SHARE *share); -void ndbcluster_free_share(NDB_SHARE **share, bool have_lock); -void ndbcluster_real_free_share(NDB_SHARE **share); -int handle_trailing_share(NDB_SHARE *share); -inline NDB_SHARE *get_share(const char *key, - TABLE *table, - bool create_if_not_exists= TRUE, - bool have_lock= FALSE) -{ - return ndbcluster_get_share(key, table, create_if_not_exists, have_lock); -} - -inline NDB_SHARE *get_share(NDB_SHARE *share) -{ - return ndbcluster_get_share(share); -} - -inline void free_share(NDB_SHARE **share, bool have_lock= FALSE) -{ - ndbcluster_free_share(share, have_lock); -} - -inline -Thd_ndb * -get_thd_ndb(THD *thd) -{ return (Thd_ndb *) thd_get_ha_data(thd, ndbcluster_hton); } - -inline -void -set_thd_ndb(THD *thd, Thd_ndb *thd_ndb) -{ thd_set_ha_data(thd, ndbcluster_hton, thd_ndb); } - -Ndb* check_ndb_in_thd(THD* thd); - -#endif /* HA_NDBCLUSTER_BINLOG_INCLUDED */ diff --git a/sql/ha_ndbcluster_cond.cc b/sql/ha_ndbcluster_cond.cc deleted file mode 100644 index e1bd6271866..00000000000 --- a/sql/ha_ndbcluster_cond.cc +++ /dev/null @@ -1,1475 +0,0 @@ -/* Copyright (c) 2000, 2011, 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-1301, USA -*/ - -/* - This file defines the NDB Cluster handler engine_condition_pushdown -*/ - -#ifdef USE_PRAGMA_IMPLEMENTATION -#pragma implementation // gcc: Class implementation -#endif - -#include "sql_priv.h" -#include "sql_class.h" // set_var.h: THD -#include "my_global.h" // WITH_* -#include "log.h" // sql_print_error - -#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE -#include <ndbapi/NdbApi.hpp> -#include "ha_ndbcluster_cond.h" - -// Typedefs for long names -typedef NdbDictionary::Column NDBCOL; -typedef NdbDictionary::Table NDBTAB; - - -/** - Serialize a constant item into a Ndb_cond node. - - @param const_type item's result type - @param item item to be serialized - @param curr_cond Ndb_cond node the item to be serialized into - @param context Traverse context -*/ - -static void ndb_serialize_const(Item_result const_type, const Item *item, - Ndb_cond *curr_cond, - Ndb_cond_traverse_context *context) -{ - DBUG_ASSERT(item->const_item()); - switch (const_type) { - case STRING_RESULT: - { - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::STRING_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(STRING_RESULT); - context->expect_collation(item->collation.collation); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - // Check that string result have correct collation - if (!context->expecting_collation(item->collation.collation)) - { - DBUG_PRINT("info", ("Found non-matching collation %s", - item->collation.collation->name)); - context->supported= FALSE; - } - } - break; - } - case REAL_RESULT: - { - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::REAL_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(REAL_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - break; - } - case INT_RESULT: - { - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::INT_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(INT_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - break; - } - case DECIMAL_RESULT: - { - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::DECIMAL_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(DECIMAL_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - break; - } - default: - break; - } -} -/* - Serialize the item tree into a linked list represented by Ndb_cond - for fast generation of NbdScanFilter. Adds information such as - position of fields that is not directly available in the Item tree. - Also checks if condition is supported. -*/ -void ndb_serialize_cond(const Item *item, void *arg) -{ - Ndb_cond_traverse_context *context= (Ndb_cond_traverse_context *) arg; - DBUG_ENTER("ndb_serialize_cond"); - - // Check if we are skipping arguments to a function to be evaluated - if (context->skip) - { - if (!item) - { - DBUG_PRINT("info", ("Unexpected mismatch of found and expected number of function arguments %u", context->skip)); - sql_print_error("ndb_serialize_cond: Unexpected mismatch of found and " - "expected number of function arguments %u", context->skip); - context->skip= 0; - DBUG_VOID_RETURN; - } - DBUG_PRINT("info", ("Skiping argument %d", context->skip)); - context->skip--; - switch (item->type()) { - case Item::FUNC_ITEM: - { - Item_func *func_item= (Item_func *) item; - context->skip+= func_item->argument_count(); - break; - } - case Item::INT_ITEM: - case Item::REAL_ITEM: - case Item::STRING_ITEM: - case Item::VARBIN_ITEM: - case Item::DECIMAL_ITEM: - break; - default: - context->supported= FALSE; - break; - } - - DBUG_VOID_RETURN; - } - - if (context->supported) - { - Ndb_rewrite_context *rewrite_context2= context->rewrite_stack; - const Item_func *rewrite_func_item; - // Check if we are rewriting some unsupported function call - if (rewrite_context2 && - (rewrite_func_item= rewrite_context2->func_item) && - rewrite_context2->count++ == 0) - { - switch (rewrite_func_item->functype()) { - case Item_func::BETWEEN: - /* - Rewrite - <field>|<const> BETWEEN <const1>|<field1> AND <const2>|<field2> - to <field>|<const> > <const1>|<field1> AND - <field>|<const> < <const2>|<field2> - or actually in prefix format - BEGIN(AND) GT(<field>|<const>, <const1>|<field1>), - LT(<field>|<const>, <const2>|<field2>), END() - */ - case Item_func::IN_FUNC: - { - /* - Rewrite <field>|<const> IN(<const1>|<field1>, <const2>|<field2>,..) - to <field>|<const> = <const1>|<field1> OR - <field> = <const2>|<field2> ... - or actually in prefix format - BEGIN(OR) EQ(<field>|<const>, <const1><field1>), - EQ(<field>|<const>, <const2>|<field2>), ... END() - Each part of the disjunction is added for each call - to ndb_serialize_cond and end of rewrite statement - is wrapped in end of ndb_serialize_cond - */ - if (context->expecting(item->type()) || item->const_item()) - { - // This is the <field>|<const> item, save it in the rewrite context - rewrite_context2->left_hand_item= item; - if (item->type() == Item::FUNC_ITEM) - { - Item_func *func_item= (Item_func *) item; - if ((func_item->functype() == Item_func::UNKNOWN_FUNC || - func_item->functype() == Item_func::NEG_FUNC) && - func_item->const_item()) - { - // Skip any arguments since we will evaluate function instead - DBUG_PRINT("info", ("Skip until end of arguments marker")); - context->skip= func_item->argument_count(); - } - else - { - DBUG_PRINT("info", ("Found unsupported functional expression in BETWEEN|IN")); - context->supported= FALSE; - DBUG_VOID_RETURN; - - } - } - } - else - { - // Non-supported BETWEEN|IN expression - DBUG_PRINT("info", ("Found unexpected item of type %u in BETWEEN|IN", - item->type())); - context->supported= FALSE; - DBUG_VOID_RETURN; - } - break; - } - default: - context->supported= FALSE; - break; - } - DBUG_VOID_RETURN; - } - else - { - Ndb_cond_stack *ndb_stack= context->stack_ptr; - Ndb_cond *prev_cond= context->cond_ptr; - Ndb_cond *curr_cond= context->cond_ptr= new Ndb_cond(); - if (!ndb_stack->ndb_cond) - ndb_stack->ndb_cond= curr_cond; - curr_cond->prev= prev_cond; - if (prev_cond) prev_cond->next= curr_cond; - // Check if we are rewriting some unsupported function call - if (context->rewrite_stack) - { - Ndb_rewrite_context *rewrite_context= context->rewrite_stack; - const Item_func *func_item= rewrite_context->func_item; - switch (func_item->functype()) { - case Item_func::BETWEEN: - { - /* - Rewrite - <field>|<const> BETWEEN <const1>|<field1> AND <const2>|<field2> - to <field>|<const> > <const1>|<field1> AND - <field>|<const> < <const2>|<field2> - or actually in prefix format - BEGIN(AND) GT(<field>|<const>, <const1>|<field1>), - LT(<field>|<const>, <const2>|<field2>), END() - */ - if (rewrite_context->count == 2) - { - // Lower limit of BETWEEN - DBUG_PRINT("info", ("GE_FUNC")); - curr_cond->ndb_item= new Ndb_item(Item_func::GE_FUNC, 2); - } - else if (rewrite_context->count == 3) - { - // Upper limit of BETWEEN - DBUG_PRINT("info", ("LE_FUNC")); - curr_cond->ndb_item= new Ndb_item(Item_func::LE_FUNC, 2); - } - else - { - // Illegal BETWEEN expression - DBUG_PRINT("info", ("Illegal BETWEEN expression")); - context->supported= FALSE; - DBUG_VOID_RETURN; - } - break; - } - case Item_func::IN_FUNC: - { - /* - Rewrite <field>|<const> IN(<const1>|<field1>, <const2>|<field2>,..) - to <field>|<const> = <const1>|<field1> OR - <field> = <const2>|<field2> ... - or actually in prefix format - BEGIN(OR) EQ(<field>|<const>, <const1><field1>), - EQ(<field>|<const>, <const2>|<field2>), ... END() - Each part of the disjunction is added for each call - to ndb_serialize_cond and end of rewrite statement - is wrapped in end of ndb_serialize_cond - */ - DBUG_PRINT("info", ("EQ_FUNC")); - curr_cond->ndb_item= new Ndb_item(Item_func::EQ_FUNC, 2); - break; - } - default: - context->supported= FALSE; - } - // Handle left hand <field>|<const> - context->rewrite_stack= NULL; // Disable rewrite mode - context->expect_only(Item::FIELD_ITEM); - context->expect_field_result(STRING_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(INT_RESULT); - context->expect_field_result(DECIMAL_RESULT); - context->expect(Item::INT_ITEM); - context->expect(Item::STRING_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect(Item::FUNC_ITEM); - ndb_serialize_cond(rewrite_context->left_hand_item, arg); - context->skip= 0; // Any FUNC_ITEM expression has already been parsed - context->rewrite_stack= rewrite_context; // Enable rewrite mode - if (!context->supported) - DBUG_VOID_RETURN; - - prev_cond= context->cond_ptr; - curr_cond= context->cond_ptr= new Ndb_cond(); - prev_cond->next= curr_cond; - } - - // Check for end of AND/OR expression - if (!item) - { - // End marker for condition group - DBUG_PRINT("info", ("End of condition group")); - curr_cond->ndb_item= new Ndb_item(NDB_END_COND); - } - else - { - switch (item->type()) { - case Item::FIELD_ITEM: - { - Item_field *field_item= (Item_field *) item; - Field *field= field_item->field; - enum_field_types type= field->type(); - /* - Check that the field is part of the table of the handler - instance and that we expect a field with of this result type. - */ - if (context->table->s == field->table->s) - { - const NDBTAB *tab= context->ndb_table; - DBUG_PRINT("info", ("FIELD_ITEM")); - DBUG_PRINT("info", ("table %s", tab->getName())); - DBUG_PRINT("info", ("column %s", field->field_name)); - DBUG_PRINT("info", ("type %d", field->type())); - DBUG_PRINT("info", ("result type %d", field->result_type())); - - // Check that we are expecting a field and with the correct - // result type - if (context->expecting(Item::FIELD_ITEM) && - context->expecting_field_type(field->type()) && - (context->expecting_field_result(field->result_type()) || - // Date and year can be written as string or int - ((type == MYSQL_TYPE_TIME || - type == MYSQL_TYPE_DATE || - type == MYSQL_TYPE_YEAR || - type == MYSQL_TYPE_DATETIME) - ? (context->expecting_field_result(STRING_RESULT) || - context->expecting_field_result(INT_RESULT)) - : TRUE)) && - // Bit fields no yet supported in scan filter - type != MYSQL_TYPE_BIT && - // No BLOB support in scan filter - type != MYSQL_TYPE_TINY_BLOB && - type != MYSQL_TYPE_MEDIUM_BLOB && - type != MYSQL_TYPE_LONG_BLOB && - type != MYSQL_TYPE_BLOB) - { - const NDBCOL *col= tab->getColumn(field->field_name); - DBUG_ASSERT(col); - curr_cond->ndb_item= new Ndb_item(field, col->getColumnNo()); - context->dont_expect(Item::FIELD_ITEM); - context->expect_no_field_result(); - if (! context->expecting_nothing()) - { - // We have not seen second argument yet - if (type == MYSQL_TYPE_TIME || - type == MYSQL_TYPE_DATE || - type == MYSQL_TYPE_YEAR || - type == MYSQL_TYPE_DATETIME) - { - context->expect_only(Item::STRING_ITEM); - context->expect(Item::INT_ITEM); - } - else - switch (field->result_type()) { - case STRING_RESULT: - // Expect char string or binary string - context->expect_only(Item::STRING_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect_collation(field_item->collation.collation); - break; - case REAL_RESULT: - context->expect_only(Item::REAL_ITEM); - context->expect(Item::DECIMAL_ITEM); - context->expect(Item::INT_ITEM); - break; - case INT_RESULT: - context->expect_only(Item::INT_ITEM); - context->expect(Item::VARBIN_ITEM); - break; - case DECIMAL_RESULT: - context->expect_only(Item::DECIMAL_ITEM); - context->expect(Item::REAL_ITEM); - context->expect(Item::INT_ITEM); - break; - default: - break; - } - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - // Check that field and string constant collations are the same - if ((field->result_type() == STRING_RESULT) && - !context->expecting_collation(item->collation.collation) - && type != MYSQL_TYPE_TIME - && type != MYSQL_TYPE_DATE - && type != MYSQL_TYPE_YEAR - && type != MYSQL_TYPE_DATETIME) - { - DBUG_PRINT("info", ("Found non-matching collation %s", - item->collation.collation->name)); - context->supported= FALSE; - } - } - break; - } - else - { - DBUG_PRINT("info", ("Was not expecting field of type %u(%u)", - field->result_type(), type)); - context->supported= FALSE; - } - } - else - { - DBUG_PRINT("info", ("Was not expecting field from table %s (%s)", - context->table->s->table_name.str, - field->table->s->table_name.str)); - context->supported= FALSE; - } - break; - } - case Item::FUNC_ITEM: - { - Item_func *func_item= (Item_func *) item; - // Check that we expect a function or functional expression here - if (context->expecting(Item::FUNC_ITEM) || - func_item->functype() == Item_func::UNKNOWN_FUNC || - func_item->functype() == Item_func::NEG_FUNC) - context->expect_nothing(); - else - { - // Did not expect function here - context->supported= FALSE; - break; - } - - switch (func_item->functype()) { - case Item_func::EQ_FUNC: - { - DBUG_PRINT("info", ("EQ_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::STRING_ITEM); - context->expect(Item::INT_ITEM); - context->expect(Item::REAL_ITEM); - context->expect(Item::DECIMAL_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect(Item::FIELD_ITEM); - context->expect_field_result(STRING_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(INT_RESULT); - context->expect_field_result(DECIMAL_RESULT); - break; - } - case Item_func::NE_FUNC: - { - DBUG_PRINT("info", ("NE_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::STRING_ITEM); - context->expect(Item::INT_ITEM); - context->expect(Item::REAL_ITEM); - context->expect(Item::DECIMAL_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect(Item::FIELD_ITEM); - context->expect_field_result(STRING_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(INT_RESULT); - context->expect_field_result(DECIMAL_RESULT); - break; - } - case Item_func::LT_FUNC: - { - DBUG_PRINT("info", ("LT_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::STRING_ITEM); - context->expect(Item::INT_ITEM); - context->expect(Item::REAL_ITEM); - context->expect(Item::DECIMAL_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect(Item::FIELD_ITEM); - context->expect_field_result(STRING_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(INT_RESULT); - context->expect_field_result(DECIMAL_RESULT); - break; - } - case Item_func::LE_FUNC: - { - DBUG_PRINT("info", ("LE_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::STRING_ITEM); - context->expect(Item::INT_ITEM); - context->expect(Item::REAL_ITEM); - context->expect(Item::DECIMAL_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect(Item::FIELD_ITEM); - context->expect_field_result(STRING_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(INT_RESULT); - context->expect_field_result(DECIMAL_RESULT); - break; - } - case Item_func::GE_FUNC: - { - DBUG_PRINT("info", ("GE_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::STRING_ITEM); - context->expect(Item::INT_ITEM); - context->expect(Item::REAL_ITEM); - context->expect(Item::DECIMAL_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect(Item::FIELD_ITEM); - context->expect_field_result(STRING_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(INT_RESULT); - context->expect_field_result(DECIMAL_RESULT); - break; - } - case Item_func::GT_FUNC: - { - DBUG_PRINT("info", ("GT_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::STRING_ITEM); - context->expect(Item::REAL_ITEM); - context->expect(Item::DECIMAL_ITEM); - context->expect(Item::INT_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect(Item::FIELD_ITEM); - context->expect_field_result(STRING_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(INT_RESULT); - context->expect_field_result(DECIMAL_RESULT); - break; - } - case Item_func::LIKE_FUNC: - { - DBUG_PRINT("info", ("LIKE_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::STRING_ITEM); - context->expect(Item::FIELD_ITEM); - context->expect_only_field_type(MYSQL_TYPE_STRING); - context->expect_field_type(MYSQL_TYPE_VAR_STRING); - context->expect_field_type(MYSQL_TYPE_VARCHAR); - context->expect_field_result(STRING_RESULT); - context->expect(Item::FUNC_ITEM); - break; - } - case Item_func::ISNULL_FUNC: - { - DBUG_PRINT("info", ("ISNULL_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::FIELD_ITEM); - context->expect_field_result(STRING_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(INT_RESULT); - context->expect_field_result(DECIMAL_RESULT); - break; - } - case Item_func::ISNOTNULL_FUNC: - { - DBUG_PRINT("info", ("ISNOTNULL_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::FIELD_ITEM); - context->expect_field_result(STRING_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(INT_RESULT); - context->expect_field_result(DECIMAL_RESULT); - break; - } - case Item_func::NOT_FUNC: - { - DBUG_PRINT("info", ("NOT_FUNC")); - curr_cond->ndb_item= new Ndb_item(func_item->functype(), - func_item); - context->expect(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - break; - } - case Item_func::BETWEEN: - { - DBUG_PRINT("info", ("BETWEEN, rewriting using AND")); - Item_func_between *between_func= (Item_func_between *) func_item; - Ndb_rewrite_context *rewrite_context= - new Ndb_rewrite_context(func_item); - rewrite_context->next= context->rewrite_stack; - context->rewrite_stack= rewrite_context; - if (between_func->negated) - { - DBUG_PRINT("info", ("NOT_FUNC")); - curr_cond->ndb_item= new Ndb_item(Item_func::NOT_FUNC, 1); - prev_cond= curr_cond; - curr_cond= context->cond_ptr= new Ndb_cond(); - curr_cond->prev= prev_cond; - prev_cond->next= curr_cond; - } - DBUG_PRINT("info", ("COND_AND_FUNC")); - curr_cond->ndb_item= - new Ndb_item(Item_func::COND_AND_FUNC, - func_item->argument_count() - 1); - context->expect_only(Item::FIELD_ITEM); - context->expect(Item::INT_ITEM); - context->expect(Item::STRING_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect(Item::FUNC_ITEM); - break; - } - case Item_func::IN_FUNC: - { - DBUG_PRINT("info", ("IN_FUNC, rewriting using OR")); - Item_func_in *in_func= (Item_func_in *) func_item; - Ndb_rewrite_context *rewrite_context= - new Ndb_rewrite_context(func_item); - rewrite_context->next= context->rewrite_stack; - context->rewrite_stack= rewrite_context; - if (in_func->negated) - { - DBUG_PRINT("info", ("NOT_FUNC")); - curr_cond->ndb_item= new Ndb_item(Item_func::NOT_FUNC, 1); - prev_cond= curr_cond; - curr_cond= context->cond_ptr= new Ndb_cond(); - curr_cond->prev= prev_cond; - prev_cond->next= curr_cond; - } - DBUG_PRINT("info", ("COND_OR_FUNC")); - curr_cond->ndb_item= new Ndb_item(Item_func::COND_OR_FUNC, - func_item->argument_count() - 1); - context->expect_only(Item::FIELD_ITEM); - context->expect(Item::INT_ITEM); - context->expect(Item::STRING_ITEM); - context->expect(Item::VARBIN_ITEM); - context->expect(Item::FUNC_ITEM); - break; - } - case Item_func::NEG_FUNC: - case Item_func::UNKNOWN_FUNC: - { - DBUG_PRINT("info", ("UNKNOWN_FUNC %s", - func_item->const_item()?"const":"")); - DBUG_PRINT("info", ("result type %d", func_item->result_type())); - if (func_item->const_item()) - { - ndb_serialize_const(func_item->result_type(), item, curr_cond, - context); - - // Skip any arguments since we will evaluate function instead - DBUG_PRINT("info", ("Skip until end of arguments marker")); - context->skip= func_item->argument_count(); - } - else - // Function does not return constant expression - context->supported= FALSE; - break; - } - default: - { - DBUG_PRINT("info", ("Found func_item of type %d", - func_item->functype())); - context->supported= FALSE; - } - } - break; - } - case Item::STRING_ITEM: - DBUG_PRINT("info", ("STRING_ITEM")); - if (context->expecting(Item::STRING_ITEM)) - { -#ifndef DBUG_OFF - char buff[256]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - str.length(0); - Item_string *string_item= (Item_string *) item; - DBUG_PRINT("info", ("value \"%s\"", - string_item->val_str(&str)->ptr())); -#endif - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::STRING_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(STRING_RESULT); - context->expect_collation(item->collation.collation); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - // Check that we are comparing with a field with same collation - if (!context->expecting_collation(item->collation.collation)) - { - DBUG_PRINT("info", ("Found non-matching collation %s", - item->collation.collation->name)); - context->supported= FALSE; - } - } - } - else - context->supported= FALSE; - break; - case Item::INT_ITEM: - DBUG_PRINT("info", ("INT_ITEM")); - if (context->expecting(Item::INT_ITEM)) - { - DBUG_PRINT("info", ("value %ld", - (long) ((Item_int*) item)->value)); - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::INT_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(INT_RESULT); - context->expect_field_result(REAL_RESULT); - context->expect_field_result(DECIMAL_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - } - else - context->supported= FALSE; - break; - case Item::REAL_ITEM: - DBUG_PRINT("info", ("REAL_ITEM")); - if (context->expecting(Item::REAL_ITEM)) - { - DBUG_PRINT("info", ("value %f", ((Item_float*) item)->value)); - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::REAL_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(REAL_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - } - else - context->supported= FALSE; - break; - case Item::VARBIN_ITEM: - DBUG_PRINT("info", ("VARBIN_ITEM")); - if (context->expecting(Item::VARBIN_ITEM)) - { - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::VARBIN_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(STRING_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - } - else - context->supported= FALSE; - break; - case Item::DECIMAL_ITEM: - DBUG_PRINT("info", ("DECIMAL_ITEM")); - if (context->expecting(Item::DECIMAL_ITEM)) - { - DBUG_PRINT("info", ("value %f", - ((Item_decimal*) item)->val_real())); - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::DECIMAL_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(REAL_RESULT); - context->expect_field_result(DECIMAL_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - } - else - context->supported= FALSE; - break; - case Item::COND_ITEM: - { - Item_cond *cond_item= (Item_cond *) item; - - if (context->expecting(Item::COND_ITEM)) - { - switch (cond_item->functype()) { - case Item_func::COND_AND_FUNC: - DBUG_PRINT("info", ("COND_AND_FUNC")); - curr_cond->ndb_item= new Ndb_item(cond_item->functype(), - cond_item); - break; - case Item_func::COND_OR_FUNC: - DBUG_PRINT("info", ("COND_OR_FUNC")); - curr_cond->ndb_item= new Ndb_item(cond_item->functype(), - cond_item); - break; - default: - DBUG_PRINT("info", ("COND_ITEM %d", cond_item->functype())); - context->supported= FALSE; - break; - } - } - else - { - /* Did not expect condition */ - context->supported= FALSE; - } - break; - } - case Item::CACHE_ITEM: - { - DBUG_PRINT("info", ("CACHE_ITEM")); - if (item->const_item()) - { - ndb_serialize_const(((Item_cache*)item)->result_type(), item, - curr_cond, context); - } - else - context->supported= FALSE; - - break; - } - default: - { - DBUG_PRINT("info", ("Found item of type %d", item->type())); - context->supported= FALSE; - } - } - } - if (context->supported && context->rewrite_stack) - { - Ndb_rewrite_context *rewrite_context= context->rewrite_stack; - if (rewrite_context->count == - rewrite_context->func_item->argument_count()) - { - // Rewrite is done, wrap an END() at the en - DBUG_PRINT("info", ("End of condition group")); - prev_cond= curr_cond; - curr_cond= context->cond_ptr= new Ndb_cond(); - curr_cond->prev= prev_cond; - prev_cond->next= curr_cond; - curr_cond->ndb_item= new Ndb_item(NDB_END_COND); - // Pop rewrite stack - context->rewrite_stack= rewrite_context->next; - rewrite_context->next= NULL; - delete(rewrite_context); - } - } - } - } - - DBUG_VOID_RETURN; -} - -/* - Push a condition - */ -const -COND* -ha_ndbcluster_cond::cond_push(const COND *cond, - TABLE *table, const NDBTAB *ndb_table) -{ - DBUG_ENTER("cond_push"); - Ndb_cond_stack *ndb_cond = new Ndb_cond_stack(); - if (ndb_cond == NULL) - { - my_errno= HA_ERR_OUT_OF_MEM; - DBUG_RETURN(NULL); - } - if (m_cond_stack) - ndb_cond->next= m_cond_stack; - else - ndb_cond->next= NULL; - m_cond_stack= ndb_cond; - - if (serialize_cond(cond, ndb_cond, table, ndb_table)) - { - DBUG_RETURN(NULL); - } - else - { - cond_pop(); - } - DBUG_RETURN(cond); -} - -/* - Pop the top condition from the condition stack -*/ -void -ha_ndbcluster_cond::cond_pop() -{ - Ndb_cond_stack *ndb_cond_stack= m_cond_stack; - if (ndb_cond_stack) - { - m_cond_stack= ndb_cond_stack->next; - ndb_cond_stack->next= NULL; - delete ndb_cond_stack; - } -} - -/* - Clear the condition stack -*/ -void -ha_ndbcluster_cond::cond_clear() -{ - DBUG_ENTER("cond_clear"); - while (m_cond_stack) - cond_pop(); - - DBUG_VOID_RETURN; -} - -bool -ha_ndbcluster_cond::serialize_cond(const COND *cond, Ndb_cond_stack *ndb_cond, - TABLE *table, const NDBTAB *ndb_table) -{ - DBUG_ENTER("serialize_cond"); - Item *item= (Item *) cond; - Ndb_cond_traverse_context context(table, ndb_table, ndb_cond); - // Expect a logical expression - context.expect(Item::FUNC_ITEM); - context.expect(Item::COND_ITEM); - item->traverse_cond(&ndb_serialize_cond, (void *) &context, Item::PREFIX); - DBUG_PRINT("info", ("The pushed condition is %ssupported", (context.supported)?"":"not ")); - - DBUG_RETURN(context.supported); -} - -int -ha_ndbcluster_cond::build_scan_filter_predicate(Ndb_cond * &cond, - NdbScanFilter *filter, - bool negated) -{ - DBUG_ENTER("build_scan_filter_predicate"); - switch (cond->ndb_item->type) { - case NDB_FUNCTION: - { - if (!cond->next) - break; - Ndb_item *a= cond->next->ndb_item; - Ndb_item *b, *field, *value= NULL; - - switch (cond->ndb_item->argument_count()) { - case 1: - field= (a->type == NDB_FIELD)? a : NULL; - break; - case 2: - if (!cond->next->next) - { - field= NULL; - break; - } - b= cond->next->next->ndb_item; - value= ((a->type == NDB_VALUE) ? a : - (b->type == NDB_VALUE) ? b : - NULL); - field= ((a->type == NDB_FIELD) ? a : - (b->type == NDB_FIELD) ? b : - NULL); - break; - default: - field= NULL; //Keep compiler happy - DBUG_ASSERT(0); - break; - } - switch ((negated) ? - Ndb_item::negate(cond->ndb_item->qualification.function_type) - : cond->ndb_item->qualification.function_type) { - case NDB_EQ_FUNC: - { - if (!value || !field) break; - // Save value in right format for the field type - value->save_in_field(field); - DBUG_PRINT("info", ("Generating EQ filter")); - if (filter->cmp(NdbScanFilter::COND_EQ, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - cond= cond->next->next->next; - DBUG_RETURN(0); - } - case NDB_NE_FUNC: - { - if (!value || !field) break; - // Save value in right format for the field type - value->save_in_field(field); - DBUG_PRINT("info", ("Generating NE filter")); - if (filter->cmp(NdbScanFilter::COND_NE, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - cond= cond->next->next->next; - DBUG_RETURN(0); - } - case NDB_LT_FUNC: - { - if (!value || !field) break; - // Save value in right format for the field type - value->save_in_field(field); - if (a == field) - { - DBUG_PRINT("info", ("Generating LT filter")); - if (filter->cmp(NdbScanFilter::COND_LT, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - } - else - { - DBUG_PRINT("info", ("Generating GT filter")); - if (filter->cmp(NdbScanFilter::COND_GT, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - } - cond= cond->next->next->next; - DBUG_RETURN(0); - } - case NDB_LE_FUNC: - { - if (!value || !field) break; - // Save value in right format for the field type - value->save_in_field(field); - if (a == field) - { - DBUG_PRINT("info", ("Generating LE filter")); - if (filter->cmp(NdbScanFilter::COND_LE, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - } - else - { - DBUG_PRINT("info", ("Generating GE filter")); - if (filter->cmp(NdbScanFilter::COND_GE, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - } - cond= cond->next->next->next; - DBUG_RETURN(0); - } - case NDB_GE_FUNC: - { - if (!value || !field) break; - // Save value in right format for the field type - value->save_in_field(field); - if (a == field) - { - DBUG_PRINT("info", ("Generating GE filter")); - if (filter->cmp(NdbScanFilter::COND_GE, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - } - else - { - DBUG_PRINT("info", ("Generating LE filter")); - if (filter->cmp(NdbScanFilter::COND_LE, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - } - cond= cond->next->next->next; - DBUG_RETURN(0); - } - case NDB_GT_FUNC: - { - if (!value || !field) break; - // Save value in right format for the field type - value->save_in_field(field); - if (a == field) - { - DBUG_PRINT("info", ("Generating GT filter")); - if (filter->cmp(NdbScanFilter::COND_GT, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - } - else - { - DBUG_PRINT("info", ("Generating LT filter")); - if (filter->cmp(NdbScanFilter::COND_LT, - field->get_field_no(), - field->get_val(), - field->pack_length()) == -1) - DBUG_RETURN(1); - } - cond= cond->next->next->next; - DBUG_RETURN(0); - } - case NDB_LIKE_FUNC: - { - if (!value || !field) break; - if ((value->qualification.value_type != Item::STRING_ITEM) && - (value->qualification.value_type != Item::VARBIN_ITEM)) - break; - // Save value in right format for the field type - value->save_in_field(field); - DBUG_PRINT("info", ("Generating LIKE filter: like(%d,%s,%d)", - field->get_field_no(), value->get_val(), - value->pack_length())); - if (filter->cmp(NdbScanFilter::COND_LIKE, - field->get_field_no(), - value->get_val(), - value->pack_length()) == -1) - DBUG_RETURN(1); - cond= cond->next->next->next; - DBUG_RETURN(0); - } - case NDB_NOTLIKE_FUNC: - { - if (!value || !field) break; - if ((value->qualification.value_type != Item::STRING_ITEM) && - (value->qualification.value_type != Item::VARBIN_ITEM)) - break; - // Save value in right format for the field type - value->save_in_field(field); - DBUG_PRINT("info", ("Generating NOTLIKE filter: notlike(%d,%s,%d)", - field->get_field_no(), value->get_val(), - value->pack_length())); - if (filter->cmp(NdbScanFilter::COND_NOT_LIKE, - field->get_field_no(), - value->get_val(), - value->pack_length()) == -1) - DBUG_RETURN(1); - cond= cond->next->next->next; - DBUG_RETURN(0); - } - case NDB_ISNULL_FUNC: - if (!field) - break; - DBUG_PRINT("info", ("Generating ISNULL filter")); - if (filter->isnull(field->get_field_no()) == -1) - DBUG_RETURN(1); - cond= cond->next->next; - DBUG_RETURN(0); - case NDB_ISNOTNULL_FUNC: - { - if (!field) - break; - DBUG_PRINT("info", ("Generating ISNOTNULL filter")); - if (filter->isnotnull(field->get_field_no()) == -1) - DBUG_RETURN(1); - cond= cond->next->next; - DBUG_RETURN(0); - } - default: - break; - } - break; - } - default: - break; - } - DBUG_PRINT("info", ("Found illegal condition")); - DBUG_RETURN(1); -} - - -int -ha_ndbcluster_cond::build_scan_filter_group(Ndb_cond* &cond, - NdbScanFilter *filter) -{ - uint level=0; - bool negated= FALSE; - DBUG_ENTER("build_scan_filter_group"); - - do - { - if (!cond) - DBUG_RETURN(1); - switch (cond->ndb_item->type) { - case NDB_FUNCTION: - { - switch (cond->ndb_item->qualification.function_type) { - case NDB_COND_AND_FUNC: - { - level++; - DBUG_PRINT("info", ("Generating %s group %u", (negated)?"NAND":"AND", - level)); - if ((negated) ? filter->begin(NdbScanFilter::NAND) - : filter->begin(NdbScanFilter::AND) == -1) - DBUG_RETURN(1); - negated= FALSE; - cond= cond->next; - break; - } - case NDB_COND_OR_FUNC: - { - level++; - DBUG_PRINT("info", ("Generating %s group %u", (negated)?"NOR":"OR", - level)); - if ((negated) ? filter->begin(NdbScanFilter::NOR) - : filter->begin(NdbScanFilter::OR) == -1) - DBUG_RETURN(1); - negated= FALSE; - cond= cond->next; - break; - } - case NDB_NOT_FUNC: - { - DBUG_PRINT("info", ("Generating negated query")); - cond= cond->next; - negated= TRUE; - break; - } - default: - if (build_scan_filter_predicate(cond, filter, negated)) - DBUG_RETURN(1); - negated= FALSE; - break; - } - break; - } - case NDB_END_COND: - DBUG_PRINT("info", ("End of group %u", level)); - level--; - if (cond) cond= cond->next; - if (filter->end() == -1) - DBUG_RETURN(1); - if (!negated) - break; - // else fall through (NOT END is an illegal condition) - default: - { - DBUG_PRINT("info", ("Illegal scan filter")); - } - } - } while (level > 0 || negated); - - DBUG_RETURN(0); -} - - -int -ha_ndbcluster_cond::build_scan_filter(Ndb_cond * &cond, NdbScanFilter *filter) -{ - bool simple_cond= TRUE; - DBUG_ENTER("build_scan_filter"); - - switch (cond->ndb_item->type) { - case NDB_FUNCTION: - switch (cond->ndb_item->qualification.function_type) { - case NDB_COND_AND_FUNC: - case NDB_COND_OR_FUNC: - simple_cond= FALSE; - break; - default: - break; - } - break; - default: - break; - } - if (simple_cond && filter->begin() == -1) - DBUG_RETURN(1); - if (build_scan_filter_group(cond, filter)) - DBUG_RETURN(1); - if (simple_cond && filter->end() == -1) - DBUG_RETURN(1); - - DBUG_RETURN(0); -} - -int -ha_ndbcluster_cond::generate_scan_filter(NdbScanOperation *op) -{ - DBUG_ENTER("generate_scan_filter"); - - if (m_cond_stack) - { - NdbScanFilter filter(op, false); // don't abort on too large - - int ret=generate_scan_filter_from_cond(filter); - if (ret != 0) - { - const NdbError& err=filter.getNdbError(); - if (err.code == NdbScanFilter::FilterTooLarge) - { - // err.message has static storage - DBUG_PRINT("info", ("%s", err.message)); - push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - err.code, err.message); - ret=0; - } - } - if (ret != 0) - DBUG_RETURN(ret); - } - else - { - DBUG_PRINT("info", ("Empty stack")); - } - - DBUG_RETURN(0); -} - - -int -ha_ndbcluster_cond::generate_scan_filter_from_cond(NdbScanFilter& filter) -{ - bool multiple_cond= FALSE; - DBUG_ENTER("generate_scan_filter_from_cond"); - - // Wrap an AND group around multiple conditions - if (m_cond_stack->next) - { - multiple_cond= TRUE; - if (filter.begin() == -1) - DBUG_RETURN(1); - } - for (Ndb_cond_stack *stack= m_cond_stack; - (stack); - stack= stack->next) - { - Ndb_cond *cond= stack->ndb_cond; - - if (build_scan_filter(cond, &filter)) - { - DBUG_PRINT("info", ("build_scan_filter failed")); - DBUG_RETURN(1); - } - } - if (multiple_cond && filter.end() == -1) - DBUG_RETURN(1); - - DBUG_RETURN(0); -} - - -int ha_ndbcluster_cond::generate_scan_filter_from_key(NdbScanOperation *op, - const KEY* key_info, - const uchar *key, - uint key_len, - uchar *buf) -{ - KEY_PART_INFO* key_part= key_info->key_part; - KEY_PART_INFO* end= key_part+key_info->key_parts; - NdbScanFilter filter(op, true); // abort on too large - int res; - DBUG_ENTER("generate_scan_filter_from_key"); - - filter.begin(NdbScanFilter::AND); - for (; key_part != end; key_part++) - { - Field* field= key_part->field; - uint32 pack_len= field->pack_length(); - const uchar* ptr= key; - DBUG_PRINT("info", ("Filtering value for %s", field->field_name)); - DBUG_DUMP("key", ptr, pack_len); - if (key_part->null_bit) - { - DBUG_PRINT("info", ("Generating ISNULL filter")); - if (filter.isnull(key_part->fieldnr-1) == -1) - DBUG_RETURN(1); - } - else - { - DBUG_PRINT("info", ("Generating EQ filter")); - if (filter.cmp(NdbScanFilter::COND_EQ, - key_part->fieldnr-1, - ptr, - pack_len) == -1) - DBUG_RETURN(1); - } - key += key_part->store_length; - } - // Add any pushed condition - if (m_cond_stack && - (res= generate_scan_filter_from_cond(filter))) - DBUG_RETURN(res); - - if (filter.end() == -1) - DBUG_RETURN(1); - - DBUG_RETURN(0); -} - -#endif diff --git a/sql/ha_ndbcluster_cond.h b/sql/ha_ndbcluster_cond.h deleted file mode 100644 index 2387dcaf5ba..00000000000 --- a/sql/ha_ndbcluster_cond.h +++ /dev/null @@ -1,500 +0,0 @@ -#ifndef HA_NDBCLUSTER_COND_INCLUDED -#define HA_NDBCLUSTER_COND_INCLUDED - -/* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */ - -/* - This file defines the data structures used by engine condition pushdown in - the NDB Cluster handler -*/ - -#ifdef USE_PRAGMA_INTERFACE -#pragma interface /* gcc class implementation */ -#endif - -/* - It is necessary to include set_var.h instead of item.h because there - are dependencies on include order for set_var.h and item.h. This - will be resolved later. -*/ -#include "set_var.h" /* Item, Item_field */ - -typedef enum ndb_item_type { - NDB_VALUE = 0, // Qualified more with Item::Type - NDB_FIELD = 1, // Qualified from table definition - NDB_FUNCTION = 2,// Qualified from Item_func::Functype - NDB_END_COND = 3 // End marker for condition group -} NDB_ITEM_TYPE; - -typedef enum ndb_func_type { - NDB_EQ_FUNC = 0, - NDB_NE_FUNC = 1, - NDB_LT_FUNC = 2, - NDB_LE_FUNC = 3, - NDB_GT_FUNC = 4, - NDB_GE_FUNC = 5, - NDB_ISNULL_FUNC = 6, - NDB_ISNOTNULL_FUNC = 7, - NDB_LIKE_FUNC = 8, - NDB_NOTLIKE_FUNC = 9, - NDB_NOT_FUNC = 10, - NDB_UNKNOWN_FUNC = 11, - NDB_COND_AND_FUNC = 12, - NDB_COND_OR_FUNC = 13, - NDB_UNSUPPORTED_FUNC = 14 -} NDB_FUNC_TYPE; - -typedef union ndb_item_qualification { - Item::Type value_type; - enum_field_types field_type; // Instead of Item::FIELD_ITEM - NDB_FUNC_TYPE function_type; // Instead of Item::FUNC_ITEM -} NDB_ITEM_QUALIFICATION; - -typedef struct ndb_item_field_value { - Field* field; - int column_no; -} NDB_ITEM_FIELD_VALUE; - -typedef union ndb_item_value { - const Item *item; - NDB_ITEM_FIELD_VALUE *field_value; - uint arg_count; -} NDB_ITEM_VALUE; - -struct negated_function_mapping -{ - NDB_FUNC_TYPE pos_fun; - NDB_FUNC_TYPE neg_fun; -}; - -/* - Define what functions can be negated in condition pushdown. - Note, these HAVE to be in the same order as in definition enum -*/ -static const negated_function_mapping neg_map[]= -{ - {NDB_EQ_FUNC, NDB_NE_FUNC}, - {NDB_NE_FUNC, NDB_EQ_FUNC}, - {NDB_LT_FUNC, NDB_GE_FUNC}, - {NDB_LE_FUNC, NDB_GT_FUNC}, - {NDB_GT_FUNC, NDB_LE_FUNC}, - {NDB_GE_FUNC, NDB_LT_FUNC}, - {NDB_ISNULL_FUNC, NDB_ISNOTNULL_FUNC}, - {NDB_ISNOTNULL_FUNC, NDB_ISNULL_FUNC}, - {NDB_LIKE_FUNC, NDB_NOTLIKE_FUNC}, - {NDB_NOTLIKE_FUNC, NDB_LIKE_FUNC}, - {NDB_NOT_FUNC, NDB_UNSUPPORTED_FUNC}, - {NDB_UNKNOWN_FUNC, NDB_UNSUPPORTED_FUNC}, - {NDB_COND_AND_FUNC, NDB_UNSUPPORTED_FUNC}, - {NDB_COND_OR_FUNC, NDB_UNSUPPORTED_FUNC}, - {NDB_UNSUPPORTED_FUNC, NDB_UNSUPPORTED_FUNC} -}; - -/* - This class is the construction element for serialization of Item tree - in condition pushdown. - An instance of Ndb_Item represents a constant, table field reference, - unary or binary comparison predicate, and start/end of AND/OR. - Instances of Ndb_Item are stored in a linked list implemented by Ndb_cond - class. - The order of elements produced by Ndb_cond::next corresponds to - breadth-first traversal of the Item (i.e. expression) tree in prefix order. - AND and OR have arbitrary arity, so the end of AND/OR group is marked with - Ndb_item with type == NDB_END_COND. - NOT items represent negated conditions and generate NAND/NOR groups. -*/ -class Ndb_item : public Sql_alloc -{ -public: - Ndb_item(NDB_ITEM_TYPE item_type) : type(item_type) {}; - Ndb_item(NDB_ITEM_TYPE item_type, - NDB_ITEM_QUALIFICATION item_qualification, - const Item *item_value) - : type(item_type), qualification(item_qualification) - { - switch(item_type) { - case(NDB_VALUE): - value.item= item_value; - break; - case(NDB_FIELD): { - NDB_ITEM_FIELD_VALUE *field_value= new NDB_ITEM_FIELD_VALUE(); - Item_field *field_item= (Item_field *) item_value; - field_value->field= field_item->field; - field_value->column_no= -1; // Will be fetched at scan filter generation - value.field_value= field_value; - break; - } - case(NDB_FUNCTION): - value.item= item_value; - value.arg_count= ((Item_func *) item_value)->argument_count(); - break; - case(NDB_END_COND): - break; - } - }; - Ndb_item(Field *field, int column_no) : type(NDB_FIELD) - { - NDB_ITEM_FIELD_VALUE *field_value= new NDB_ITEM_FIELD_VALUE(); - qualification.field_type= field->type(); - field_value->field= field; - field_value->column_no= column_no; - value.field_value= field_value; - }; - Ndb_item(Item_func::Functype func_type, const Item *item_value) - : type(NDB_FUNCTION) - { - qualification.function_type= item_func_to_ndb_func(func_type); - value.item= item_value; - value.arg_count= ((Item_func *) item_value)->argument_count(); - }; - Ndb_item(Item_func::Functype func_type, uint no_args) - : type(NDB_FUNCTION) - { - qualification.function_type= item_func_to_ndb_func(func_type); - value.arg_count= no_args; - }; - ~Ndb_item() - { - if (type == NDB_FIELD) - { - delete value.field_value; - value.field_value= NULL; - } - }; - - uint32 pack_length() - { - switch(type) { - case(NDB_VALUE): - if(qualification.value_type == Item::STRING_ITEM) - return value.item->str_value.length(); - break; - case(NDB_FIELD): - return value.field_value->field->pack_length(); - default: - break; - } - - return 0; - }; - - Field * get_field() { return value.field_value->field; }; - - int get_field_no() { return value.field_value->column_no; }; - - int argument_count() - { - return value.arg_count; - }; - - const char* get_val() - { - switch(type) { - case(NDB_VALUE): - if(qualification.value_type == Item::STRING_ITEM) - return value.item->str_value.ptr(); - break; - case(NDB_FIELD): - return (char*) value.field_value->field->ptr; - default: - break; - } - - return NULL; - }; - - void save_in_field(Ndb_item *field_item) - { - Field *field = field_item->value.field_value->field; - const Item *item= value.item; - - if (item && field) - { - my_bitmap_map *old_map= - dbug_tmp_use_all_columns(field->table, field->table->write_set); - ((Item *)item)->save_in_field(field, FALSE); - dbug_tmp_restore_column_map(field->table->write_set, old_map); - } - }; - - static NDB_FUNC_TYPE item_func_to_ndb_func(Item_func::Functype fun) - { - switch (fun) { - case (Item_func::EQ_FUNC): { return NDB_EQ_FUNC; } - case (Item_func::NE_FUNC): { return NDB_NE_FUNC; } - case (Item_func::LT_FUNC): { return NDB_LT_FUNC; } - case (Item_func::LE_FUNC): { return NDB_LE_FUNC; } - case (Item_func::GT_FUNC): { return NDB_GT_FUNC; } - case (Item_func::GE_FUNC): { return NDB_GE_FUNC; } - case (Item_func::ISNULL_FUNC): { return NDB_ISNULL_FUNC; } - case (Item_func::ISNOTNULL_FUNC): { return NDB_ISNOTNULL_FUNC; } - case (Item_func::LIKE_FUNC): { return NDB_LIKE_FUNC; } - case (Item_func::NOT_FUNC): { return NDB_NOT_FUNC; } - case (Item_func::NEG_FUNC): { return NDB_UNKNOWN_FUNC; } - case (Item_func::UNKNOWN_FUNC): { return NDB_UNKNOWN_FUNC; } - case (Item_func::COND_AND_FUNC): { return NDB_COND_AND_FUNC; } - case (Item_func::COND_OR_FUNC): { return NDB_COND_OR_FUNC; } - default: { return NDB_UNSUPPORTED_FUNC; } - } - }; - - static NDB_FUNC_TYPE negate(NDB_FUNC_TYPE fun) - { - uint i= (uint) fun; - DBUG_ASSERT(fun == neg_map[i].pos_fun); - return neg_map[i].neg_fun; - }; - - NDB_ITEM_TYPE type; - NDB_ITEM_QUALIFICATION qualification; - private: - NDB_ITEM_VALUE value; -}; - -/* - This class implements a linked list used for storing a - serialization of the Item tree for condition pushdown. - */ -class Ndb_cond : public Sql_alloc -{ - public: - Ndb_cond() : ndb_item(NULL), next(NULL), prev(NULL) {}; - ~Ndb_cond() - { - if (ndb_item) delete ndb_item; - ndb_item= NULL; - /* - First item in the linked list deletes all in a loop - Note - doing it recursively causes stack issues for - big IN clauses - */ - Ndb_cond *n= next; - while (n) - { - Ndb_cond *tmp= n; - n= n->next; - tmp->next= NULL; - delete tmp; - } - next= prev= NULL; - }; - Ndb_item *ndb_item; - Ndb_cond *next; - Ndb_cond *prev; -}; - -/* - This class implements a stack for storing several conditions - for pushdown (represented as serialized Item trees using Ndb_cond). - The current implementation only pushes one condition, but is - prepared for handling several (C1 AND C2 ...) if the logic for - pushing conditions is extended in sql_select. -*/ -class Ndb_cond_stack : public Sql_alloc -{ - public: - Ndb_cond_stack() : ndb_cond(NULL), next(NULL) {}; - ~Ndb_cond_stack() - { - if (ndb_cond) delete ndb_cond; - ndb_cond= NULL; - if (next) delete next; - next= NULL; - }; - Ndb_cond *ndb_cond; - Ndb_cond_stack *next; -}; - -class Ndb_rewrite_context : public Sql_alloc -{ -public: - Ndb_rewrite_context(Item_func *func) - : func_item(func), left_hand_item(NULL), count(0) {}; - ~Ndb_rewrite_context() - { - if (next) delete next; - } - const Item_func *func_item; - const Item *left_hand_item; - uint count; - Ndb_rewrite_context *next; -}; - -/* - This class is used for storing the context when traversing - the Item tree. It stores a reference to the table the condition - is defined on, the serialized representation being generated, - if the condition found is supported, and information what is - expected next in the tree inorder for the condition to be supported. -*/ -class Ndb_cond_traverse_context : public Sql_alloc -{ - public: - Ndb_cond_traverse_context(TABLE *tab, const NdbDictionary::Table *ndb_tab, - Ndb_cond_stack* stack) - : table(tab), ndb_table(ndb_tab), - supported(TRUE), stack_ptr(stack), cond_ptr(NULL), - skip(0), collation(NULL), rewrite_stack(NULL) - { - // Allocate type checking bitmaps - bitmap_init(&expect_mask, 0, 512, FALSE); - bitmap_init(&expect_field_type_mask, 0, 512, FALSE); - bitmap_init(&expect_field_result_mask, 0, 512, FALSE); - - if (stack) - cond_ptr= stack->ndb_cond; - }; - ~Ndb_cond_traverse_context() - { - bitmap_free(&expect_mask); - bitmap_free(&expect_field_type_mask); - bitmap_free(&expect_field_result_mask); - if (rewrite_stack) delete rewrite_stack; - } - void expect(Item::Type type) - { - bitmap_set_bit(&expect_mask, (uint) type); - if (type == Item::FIELD_ITEM) expect_all_field_types(); - }; - void dont_expect(Item::Type type) - { - bitmap_clear_bit(&expect_mask, (uint) type); - }; - bool expecting(Item::Type type) - { - return bitmap_is_set(&expect_mask, (uint) type); - }; - void expect_nothing() - { - bitmap_clear_all(&expect_mask); - }; - bool expecting_nothing() - { - return bitmap_is_clear_all(&expect_mask); - } - void expect_only(Item::Type type) - { - expect_nothing(); - expect(type); - }; - - void expect_field_type(enum_field_types type) - { - bitmap_set_bit(&expect_field_type_mask, (uint) type); - }; - void expect_all_field_types() - { - bitmap_set_all(&expect_field_type_mask); - }; - bool expecting_field_type(enum_field_types type) - { - return bitmap_is_set(&expect_field_type_mask, (uint) type); - }; - void expect_no_field_type() - { - bitmap_clear_all(&expect_field_type_mask); - }; - bool expecting_no_field_type() - { - return bitmap_is_clear_all(&expect_field_type_mask); - } - void expect_only_field_type(enum_field_types result) - { - expect_no_field_type(); - expect_field_type(result); - }; - - void expect_field_result(Item_result result) - { - bitmap_set_bit(&expect_field_result_mask, (uint) result); - }; - bool expecting_field_result(Item_result result) - { - return bitmap_is_set(&expect_field_result_mask, (uint) result); - }; - void expect_no_field_result() - { - bitmap_clear_all(&expect_field_result_mask); - }; - bool expecting_no_field_result() - { - return bitmap_is_clear_all(&expect_field_result_mask); - } - void expect_only_field_result(Item_result result) - { - expect_no_field_result(); - expect_field_result(result); - }; - void expect_collation(CHARSET_INFO* col) - { - collation= col; - }; - bool expecting_collation(CHARSET_INFO* col) - { - bool matching= (!collation) ? true : (collation == col); - collation= NULL; - - return matching; - }; - - TABLE* table; - const NdbDictionary::Table *ndb_table; - bool supported; - Ndb_cond_stack* stack_ptr; - Ndb_cond* cond_ptr; - MY_BITMAP expect_mask; - MY_BITMAP expect_field_type_mask; - MY_BITMAP expect_field_result_mask; - uint skip; - CHARSET_INFO* collation; - Ndb_rewrite_context *rewrite_stack; -}; - -class ha_ndbcluster; - -class ha_ndbcluster_cond -{ -public: - ha_ndbcluster_cond() - : m_cond_stack(NULL) - {} - ~ha_ndbcluster_cond() - { if (m_cond_stack) delete m_cond_stack; } - const COND *cond_push(const COND *cond, - TABLE *table, const NdbDictionary::Table *ndb_table); - void cond_pop(); - void cond_clear(); - int generate_scan_filter(NdbScanOperation* op); - int generate_scan_filter_from_cond(NdbScanFilter& filter); - int generate_scan_filter_from_key(NdbScanOperation* op, - const KEY* key_info, - const uchar *key, - uint key_len, - uchar *buf); -private: - bool serialize_cond(const COND *cond, Ndb_cond_stack *ndb_cond, - TABLE *table, const NdbDictionary::Table *ndb_table); - int build_scan_filter_predicate(Ndb_cond* &cond, - NdbScanFilter* filter, - bool negated= false); - int build_scan_filter_group(Ndb_cond* &cond, - NdbScanFilter* filter); - int build_scan_filter(Ndb_cond* &cond, NdbScanFilter* filter); - - Ndb_cond_stack *m_cond_stack; -}; - -#endif /* HA_NDBCLUSTER_COND_INCLUDED */ diff --git a/sql/ha_ndbcluster_tables.h b/sql/ha_ndbcluster_tables.h deleted file mode 100644 index 4d97ca2c254..00000000000 --- a/sql/ha_ndbcluster_tables.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef HA_NDBCLUSTER_TABLES_INCLUDED -#define HA_NDBCLUSTER_TABLES_INCLUDED - -/* Copyright (c) 2000-2003, 2006, 2007 MySQL AB, 2009 Sun Microsystems, Inc. - 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-1301, USA -*/ - -#define NDB_REP_DB "mysql" -#define OLD_NDB_REP_DB "cluster" -#define NDB_REP_TABLE "ndb_binlog_index" -#define NDB_APPLY_TABLE "ndb_apply_status" -#define OLD_NDB_APPLY_TABLE "apply_status" -#define NDB_SCHEMA_TABLE "ndb_schema" -#define OLD_NDB_SCHEMA_TABLE "schema" - -#endif /* HA_NDBCLUSTER_TABLES_INCLUDED */ diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index bc2f597d5b0..2167bea8d7c 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -37,10 +37,6 @@ in the execution of queries. This functionality will grow with later versions of MySQL. - You can enable it in your buld by doing the following during your build - process: - ./configure --with-partition - The partition is setup to use table locks. It implements an partition "SHARE" that is inserted into a hash by table name. You can use this to store information of state that any partition handler object will be able to see @@ -50,10 +46,7 @@ if this file. */ -#ifdef __GNUC__ -#pragma implementation // gcc: Class implementation -#endif - +#include <my_global.h> #include "sql_priv.h" #include "sql_parse.h" // append_file_to_dir #include "create_options.h" @@ -63,14 +56,30 @@ #include "sql_table.h" // tablename_to_filename #include "key.h" #include "sql_plugin.h" -#include "table.h" /* HA_DATA_PARTITION */ #include "sql_show.h" // append_identifier #include "sql_admin.h" // SQL_ADMIN_MSG_TEXT_SIZE #include "debug_sync.h" +/* First 4 bytes in the .par file is the number of 32-bit words in the file */ +#define PAR_WORD_SIZE 4 +/* offset to the .par file checksum */ +#define PAR_CHECKSUM_OFFSET 4 +/* offset to the total number of partitions */ +#define PAR_NUM_PARTS_OFFSET 8 +/* offset to the engines array */ +#define PAR_ENGINES_OFFSET 12 +#define PARTITION_ENABLED_TABLE_FLAGS (HA_FILE_BASED | \ + HA_REC_NOT_IN_SEQ | \ + HA_CAN_REPAIR) +#define PARTITION_DISABLED_TABLE_FLAGS (HA_CAN_GEOMETRY | \ + HA_CAN_FULLTEXT | \ + HA_DUPLICATE_POS | \ + HA_CAN_SQL_HANDLER | \ + HA_CAN_INSERT_DELAYED | \ + HA_READ_BEFORE_WRITE_REMOVAL) static const char *ha_par_ext= ".par"; -#define MI_MAX_MSG_BUF MYSQL_ERRMSG_SIZE + /**************************************************************************** MODULE create/delete handler object ****************************************************************************/ @@ -84,6 +93,35 @@ static uint alter_table_flags(uint flags); extern "C" int cmp_key_part_id(void *key_p, uchar *ref1, uchar *ref2); extern "C" int cmp_key_rowid_part_id(void *ptr, uchar *ref1, uchar *ref2); +/* + If frm_error() is called then we will use this to to find out what file + extensions exist for the storage engine. This is also used by the default + rename_table and delete_table method in handler.cc. +*/ +static const char *ha_partition_ext[]= +{ + ha_par_ext, NullS +}; + + +#ifdef HAVE_PSI_INTERFACE +PSI_mutex_key key_partition_auto_inc_mutex; + +static PSI_mutex_info all_partition_mutexes[]= +{ + { &key_partition_auto_inc_mutex, "Partition_share::auto_inc_mutex", 0} +}; + +static void init_partition_psi_keys(void) +{ + const char* category= "partition"; + int count; + + count= array_elements(all_partition_mutexes); + mysql_mutex_register(category, all_partition_mutexes, count); +} +#endif /* HAVE_PSI_INTERFACE */ + static int partition_initialize(void *p) { @@ -98,10 +136,46 @@ static int partition_initialize(void *p) partition_hton->flags= HTON_NOT_USER_SELECTABLE | HTON_HIDDEN | HTON_TEMPORARY_NOT_SUPPORTED; + partition_hton->tablefile_extensions= ha_partition_ext; +#ifdef HAVE_PSI_INTERFACE + init_partition_psi_keys(); +#endif return 0; } + +/** + Initialize and allocate space for partitions shares. + + @param num_parts Number of partitions to allocate storage for. + + @return Operation status. + @retval true Failure (out of memory). + @retval false Success. +*/ + +bool Partition_share::init(uint num_parts) +{ + DBUG_ENTER("Partition_share::init"); + mysql_mutex_init(key_partition_auto_inc_mutex, + &auto_inc_mutex, + MY_MUTEX_INIT_FAST); + auto_inc_initialized= false; + partition_name_hash_initialized= false; + next_auto_inc_val= 0; + partitions_share_refs= new Parts_share_refs; + if (!partitions_share_refs) + DBUG_RETURN(true); + if (partitions_share_refs->init(num_parts)) + { + delete partitions_share_refs; + DBUG_RETURN(true); + } + DBUG_RETURN(false); +} + + /* Create new partition handler @@ -156,7 +230,7 @@ static uint alter_table_flags(uint flags __attribute__((unused))) HA_FAST_CHANGE_PARTITION); } -const uint ha_partition::NO_CURRENT_PART_ID= 0xFFFFFFFF; +const uint32 ha_partition::NO_CURRENT_PART_ID= NOT_A_PARTITION_ID; /* Constructor method @@ -173,7 +247,7 @@ ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share) :handler(hton, share) { DBUG_ENTER("ha_partition::ha_partition(table)"); - init_alloc_root(&m_mem_root, 512, 512); + init_alloc_root(&m_mem_root, 512, 512, MYF(0)); init_handler_variables(); DBUG_VOID_RETURN; } @@ -195,7 +269,7 @@ ha_partition::ha_partition(handlerton *hton, partition_info *part_info) { DBUG_ENTER("ha_partition::ha_partition(part_info)"); DBUG_ASSERT(part_info); - init_alloc_root(&m_mem_root, 512, 512); + init_alloc_root(&m_mem_root, 512, 512, MYF(0)); init_handler_variables(); m_part_info= part_info; m_create_handler= TRUE; @@ -222,14 +296,16 @@ ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share, :handler(hton, share) { DBUG_ENTER("ha_partition::ha_partition(clone)"); - init_alloc_root(&m_mem_root, 512, 512); + init_alloc_root(&m_mem_root, 512, 512, MYF(0)); init_handler_variables(); m_part_info= part_info_arg; m_create_handler= TRUE; m_is_sub_partitioned= m_part_info->is_sub_partitioned(); m_is_clone_of= clone_arg; m_clone_mem_root= clone_mem_root_arg; - m_pkey_is_clustered= clone_arg->primary_key_is_clustered(); + part_share= clone_arg->part_share; + m_tot_parts= clone_arg->m_tot_parts; + m_pkey_is_clustered= clone_arg->primary_key_is_clustered(); DBUG_VOID_RETURN; } @@ -260,7 +336,6 @@ void ha_partition::init_handler_variables() m_added_file= NULL; m_tot_parts= 0; m_pkey_is_clustered= 0; - m_lock_type= F_UNLCK; m_part_spec.start_part= NO_CURRENT_PART_ID; m_scan_value= 2; m_ref_length= 0; @@ -296,6 +371,8 @@ void ha_partition::init_handler_variables() m_is_sub_partitioned= 0; m_is_clone_of= NULL; m_clone_mem_root= NULL; + part_share= NULL; + m_new_partitions_share_refs.empty(); m_part_ids_sorted_by_num_of_records= NULL; #ifdef DONT_HAVE_TO_BE_INITALIZED @@ -308,7 +385,7 @@ void ha_partition::init_handler_variables() const char *ha_partition::table_type() const { // we can do this since we only support a single engine type - return m_file && m_file[0] ? m_file[0]->table_type() : "Unknown"; + return m_file[0]->table_type(); } @@ -325,6 +402,8 @@ const char *ha_partition::table_type() const ha_partition::~ha_partition() { DBUG_ENTER("ha_partition::~ha_partition()"); + if (m_new_partitions_share_refs.elements) + m_new_partitions_share_refs.delete_elements(); if (m_file != NULL) { uint i; @@ -475,7 +554,7 @@ int ha_partition::delete_table(const char *name) { DBUG_ENTER("ha_partition::delete_table"); - DBUG_RETURN(del_ren_cre_table(name, NULL, NULL, NULL)); + DBUG_RETURN(del_ren_table(name, NULL)); } @@ -505,7 +584,7 @@ int ha_partition::rename_table(const char *from, const char *to) { DBUG_ENTER("ha_partition::rename_table"); - DBUG_RETURN(del_ren_cre_table(from, to, NULL, NULL)); + DBUG_RETURN(del_ren_table(from, to)); } @@ -513,7 +592,7 @@ int ha_partition::rename_table(const char *from, const char *to) Create the handler file (.par-file) SYNOPSIS - create_handler_files() + create_partitioning_metadata() name Full path of table name create_info Create info generated for CREATE TABLE @@ -522,19 +601,18 @@ int ha_partition::rename_table(const char *from, const char *to) 0 Success DESCRIPTION - create_handler_files is called to create any handler specific files + create_partitioning_metadata is called to create any handler specific files before opening the file with openfrm to later call ::create on the file object. In the partition handler this is used to store the names of partitions and types of engines in the partitions. */ -int ha_partition::create_handler_files(const char *path, +int ha_partition::create_partitioning_metadata(const char *path, const char *old_path, - int action_flag, - HA_CREATE_INFO *create_info) + int action_flag) { - DBUG_ENTER("ha_partition::create_handler_files()"); + DBUG_ENTER("ha_partition::create_partitioning_metadata()"); /* We need to update total number of parts since we might write the handler @@ -595,24 +673,88 @@ int ha_partition::create_handler_files(const char *path, int ha_partition::create(const char *name, TABLE *table_arg, HA_CREATE_INFO *create_info) { - char t_name[FN_REFLEN]; + int error; + char name_buff[FN_REFLEN + 1], name_lc_buff[FN_REFLEN]; + char *name_buffer_ptr; + const char *path; + uint i; + List_iterator_fast <partition_element> part_it(m_part_info->partitions); + partition_element *part_elem; + handler **file, **abort_file; DBUG_ENTER("ha_partition::create"); - if (create_info->used_fields & HA_CREATE_USED_CONNECTION) + DBUG_ASSERT(*fn_rext((char*)name) == '\0'); + + /* Not allowed to create temporary partitioned tables */ + if (create_info && create_info->tmp_table()) { - my_error(ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), - "CONNECTION not valid for partition"); - DBUG_RETURN(1); + my_error(ER_PARTITION_NO_TEMPORARY, MYF(0)); + DBUG_RETURN(TRUE); } - strmov(t_name, name); - DBUG_ASSERT(*fn_rext((char*)name) == '\0'); - if (del_ren_cre_table(t_name, NULL, table_arg, create_info)) + if (get_from_handler_file(name, ha_thd()->mem_root, false)) + DBUG_RETURN(TRUE); + DBUG_ASSERT(m_file_buffer); + DBUG_PRINT("enter", ("name: (%s)", name)); + name_buffer_ptr= m_name_buffer_ptr; + file= m_file; + /* + Since ha_partition has HA_FILE_BASED, it must alter underlying table names + if they do not have HA_FILE_BASED and lower_case_table_names == 2. + See Bug#37402, for Mac OS X. + The appended #P#<partname>[#SP#<subpartname>] will remain in current case. + Using the first partitions handler, since mixing handlers is not allowed. + */ + path= get_canonical_filename(*file, name, name_lc_buff); + for (i= 0; i < m_part_info->num_parts; i++) { - handler::delete_table(t_name); - DBUG_RETURN(1); + part_elem= part_it++; + if (m_is_sub_partitioned) + { + uint j; + List_iterator_fast <partition_element> sub_it(part_elem->subpartitions); + for (j= 0; j < m_part_info->num_subparts; j++) + { + part_elem= sub_it++; + if ((error= create_partition_name(name_buff, sizeof(name_buff), path, + name_buffer_ptr, NORMAL_PART_NAME, FALSE))) + goto create_error; + if ((error= set_up_table_before_create(table_arg, name_buff, + create_info, part_elem)) || + ((error= (*file)->ha_create(name_buff, table_arg, create_info)))) + goto create_error; + + name_buffer_ptr= strend(name_buffer_ptr) + 1; + file++; + } + } + else + { + if ((error= create_partition_name(name_buff, sizeof(name_buff), path, + name_buffer_ptr, NORMAL_PART_NAME, FALSE))) + goto create_error; + if ((error= set_up_table_before_create(table_arg, name_buff, + create_info, part_elem)) || + ((error= (*file)->ha_create(name_buff, table_arg, create_info)))) + goto create_error; + + name_buffer_ptr= strend(name_buffer_ptr) + 1; + file++; + } } DBUG_RETURN(0); + +create_error: + name_buffer_ptr= m_name_buffer_ptr; + for (abort_file= file, file= m_file; file < abort_file; file++) + { + if (!create_partition_name(name_buff, sizeof(name_buff), path, + name_buffer_ptr, NORMAL_PART_NAME, FALSE)) + (void) (*file)->ha_delete_table((const char*) name_buff); + name_buffer_ptr= strend(name_buffer_ptr) + 1; + } + handler::delete_table(name); + DBUG_RETURN(error); } @@ -672,7 +814,7 @@ int ha_partition::drop_partitions(const char *path) sizeof(part_name_buff), path, part_elem->partition_name, sub_elem->partition_name, name_variant))) - error= ret_error; + error= ret_error; file= m_file[part]; DBUG_PRINT("info", ("Drop subpartition %s", part_name_buff)); if ((ret_error= file->ha_delete_table(part_name_buff))) @@ -1010,7 +1152,8 @@ int ha_partition::repair(THD *thd, HA_CHECK_OPT *check_opt) { DBUG_ENTER("ha_partition::repair"); - DBUG_RETURN(handle_opt_partitions(thd, check_opt, REPAIR_PARTS)); + int res= handle_opt_partitions(thd, check_opt, REPAIR_PARTS); + DBUG_RETURN(res); } /** @@ -1116,7 +1259,7 @@ int ha_partition::handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, /* - print a message row formatted for ANALYZE/CHECK/OPTIMIZE/REPAIR TABLE + print a message row formatted for ANALYZE/CHECK/OPTIMIZE/REPAIR TABLE (modelled after mi_check_print_msg) TODO: move this into the handler, or rewrite mysql_admin_table. */ @@ -1134,7 +1277,7 @@ static bool print_admin_msg(THD* thd, uint len, Protocol *protocol= thd->protocol; uint length; uint msg_length; - char name[SAFE_NAME_LEN*2+2]; + char name[NAME_LEN*2+2]; char *msgbuf; bool error= true; @@ -1215,7 +1358,7 @@ int ha_partition::handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, when ALTER TABLE <CMD> PARTITION ... it should only do named partitions, otherwise all partitions */ - if (!(thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) || + if (!(thd->lex->alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION) || part_elem->part_state == PART_ADMIN) { if (m_is_sub_partitioned) @@ -1236,7 +1379,7 @@ int ha_partition::handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, error != HA_ADMIN_ALREADY_DONE && error != HA_ADMIN_TRY_ALTER) { - print_admin_msg(thd, MI_MAX_MSG_BUF, "error", + print_admin_msg(thd, MYSQL_ERRMSG_SIZE, "error", table_share->db.str, table->alias, opt_op_name[flag], "Subpartition %s returned error", @@ -1263,9 +1406,9 @@ int ha_partition::handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, error != HA_ADMIN_ALREADY_DONE && error != HA_ADMIN_TRY_ALTER) { - print_admin_msg(thd, MI_MAX_MSG_BUF, "error", + print_admin_msg(thd, MYSQL_ERRMSG_SIZE, "error", table_share->db.str, table->alias, - opt_op_name[flag], "Partition %s returned error", + opt_op_name[flag], "Partition %s returned error", part_elem->partition_name); } /* reset part_state for the remaining partitions */ @@ -1291,6 +1434,8 @@ int ha_partition::handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, @retval TRUE Error/Not supported @retval FALSE Success + + @note Called if open_table_from_share fails and ::is_crashed(). */ bool ha_partition::check_and_repair(THD *thd) @@ -1371,9 +1516,25 @@ int ha_partition::prepare_new_partition(TABLE *tbl, int error; DBUG_ENTER("prepare_new_partition"); - if ((error= set_up_table_before_create(tbl, part_name, create_info, - 0, p_elem))) + /* + This call to set_up_table_before_create() is done for an alter table. + So this may be the second time around for this partition_element, + depending on how many partitions and subpartitions there were before, + and how many there are now. + The first time, on the CREATE, data_file_name and index_file_name + came from the parser. They did not have the file name attached to + the end. But if this partition is less than the total number of + previous partitions, it's data_file_name has the filename attached. + So we need to take the partition filename off if it exists. + That file name may be different from part_name, which will be + attached in append_file_to_dir(). + */ + truncate_partition_filename(p_elem->data_file_name); + truncate_partition_filename(p_elem->index_file_name); + + if ((error= set_up_table_before_create(tbl, part_name, create_info, p_elem))) goto error_create; + tbl->s->connect_string = p_elem->connect_string; if ((error= file->ha_create(part_name, tbl, create_info))) { @@ -1389,7 +1550,8 @@ int ha_partition::prepare_new_partition(TABLE *tbl, goto error_create; } DBUG_PRINT("info", ("partition %s created", part_name)); - if ((error= file->ha_open(tbl, part_name, m_mode, m_open_test_lock))) + if ((error= file->ha_open(tbl, part_name, m_mode, + m_open_test_lock | HA_OPEN_NO_PSI_CALL))) goto error_open; DBUG_PRINT("info", ("partition %s opened", part_name)); @@ -1550,11 +1712,11 @@ int ha_partition::change_partitions(HA_CREATE_INFO *create_info, } while (++i < num_parts); } if (m_reorged_parts && - !(m_reorged_file= (handler**)sql_calloc(sizeof(handler*)* - (m_reorged_parts + 1)))) + !(m_reorged_file= (handler**) thd->calloc(sizeof(handler*)* + (m_reorged_parts + 1)))) { mem_alloc_error(sizeof(handler*)*(m_reorged_parts+1)); - DBUG_RETURN(ER_OUTOFMEMORY); + DBUG_RETURN(HA_ERR_OUT_OF_MEM); } /* @@ -1582,11 +1744,12 @@ int ha_partition::change_partitions(HA_CREATE_INFO *create_info, } } while (++i < num_parts); } - if (!(new_file_array= (handler**)sql_calloc(sizeof(handler*)* - (2*(num_remain_partitions + 1))))) + if (!(new_file_array= ((handler**) + thd->calloc(sizeof(handler*)* + (2*(num_remain_partitions + 1)))))) { mem_alloc_error(sizeof(handler*)*2*(num_remain_partitions+1)); - DBUG_RETURN(ER_OUTOFMEMORY); + DBUG_RETURN(HA_ERR_OUT_OF_MEM); } m_added_file= &new_file_array[num_remain_partitions + 1]; @@ -1656,15 +1819,33 @@ int ha_partition::change_partitions(HA_CREATE_INFO *create_info, part_elem->part_state == PART_TO_BE_ADDED) { uint j= 0; + Parts_share_refs *p_share_refs; + /* + The Handler_shares for each partition's handler can be allocated + within this handler, since there will not be any more instances of the + new partitions, until the table is reopened after the ALTER succeeded. + */ + p_share_refs= new Parts_share_refs; + if (!p_share_refs) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + if (p_share_refs->init(num_subparts)) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + if (m_new_partitions_share_refs.push_back(p_share_refs, thd->mem_root)) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); do { - if (!(new_file_array[part_count++]= + handler **new_file= &new_file_array[part_count++]; + if (!(*new_file= get_new_handler(table->s, thd->mem_root, part_elem->engine_type))) { mem_alloc_error(sizeof(handler)); - DBUG_RETURN(ER_OUTOFMEMORY); + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + } + if ((*new_file)->set_ha_share_ref(&p_share_refs->ha_shares[j])) + { + DBUG_RETURN(HA_ERR_OUT_OF_MEM); } } while (++j < num_subparts); if (part_elem->part_state == PART_CHANGED) @@ -1841,7 +2022,7 @@ int ha_partition::copy_partitions(ulonglong * const copied, late_extra_cache(reorg_part); if ((result= file->ha_rnd_init_with_error(1))) - goto error; + goto init_error; while (TRUE) { if ((result= file->ha_rnd_next(m_rec0))) @@ -1886,10 +2067,10 @@ int ha_partition::copy_partitions(ulonglong * const copied, DBUG_RETURN(FALSE); error: m_reorged_file[reorg_part]->ha_rnd_end(); +init_error: DBUG_RETURN(result); } - /* Update create info as part of ALTER TABLE @@ -1901,11 +2082,16 @@ error: NONE DESCRIPTION - Method empty so far + Forward this handler call to the storage engine foreach + partition handler. The data_file_name for each partition may + need to be reset if the tablespace was moved. Use a dummy + HA_CREATE_INFO structure and transfer necessary data. */ void ha_partition::update_create_info(HA_CREATE_INFO *create_info) { + DBUG_ENTER("ha_partition::update_create_info"); + /* Fix for bug#38751, some engines needs info-calls in ALTER. Archive need this since it flushes in ::info. @@ -1919,13 +2105,110 @@ void ha_partition::update_create_info(HA_CREATE_INFO *create_info) if (!(create_info->used_fields & HA_CREATE_USED_AUTO)) create_info->auto_increment_value= stats.auto_increment_value; + /* + DATA DIRECTORY and INDEX DIRECTORY are never applied to the whole + partitioned table, only its parts. + */ + my_bool from_alter = (create_info->data_file_name == (const char*) -1); create_info->data_file_name= create_info->index_file_name = NULL; - create_info->connect_string.str= NULL; - create_info->connect_string.length= 0; - return; + + create_info->connect_string= null_lex_str; + + /* + We do not need to update the individual partition DATA DIRECTORY settings + since they can be changed by ALTER TABLE ... REORGANIZE PARTITIONS. + */ + if (from_alter) + DBUG_VOID_RETURN; + + /* + send Handler::update_create_info() to the storage engine for each + partition that currently has a handler object. Using a dummy + HA_CREATE_INFO structure to collect DATA and INDEX DIRECTORYs. + */ + + List_iterator<partition_element> part_it(m_part_info->partitions); + partition_element *part_elem, *sub_elem; + uint num_subparts= m_part_info->num_subparts; + uint num_parts = num_subparts ? m_file_tot_parts / num_subparts + : m_file_tot_parts; + HA_CREATE_INFO dummy_info; + memset(&dummy_info, 0, sizeof(dummy_info)); + + /* + Since update_create_info() can be called from mysql_prepare_alter_table() + when not all handlers are set up, we look for that condition first. + If all handlers are not available, do not call update_create_info for any. + */ + uint i, j, part; + for (i= 0; i < num_parts; i++) + { + part_elem= part_it++; + if (!part_elem) + DBUG_VOID_RETURN; + if (m_is_sub_partitioned) + { + List_iterator<partition_element> subpart_it(part_elem->subpartitions); + for (j= 0; j < num_subparts; j++) + { + sub_elem= subpart_it++; + if (!sub_elem) + DBUG_VOID_RETURN; + part= i * num_subparts + j; + if (part >= m_file_tot_parts || !m_file[part]) + DBUG_VOID_RETURN; + } + } + else + { + if (!m_file[i]) + DBUG_VOID_RETURN; + } + } + part_it.rewind(); + + for (i= 0; i < num_parts; i++) + { + part_elem= part_it++; + DBUG_ASSERT(part_elem); + if (m_is_sub_partitioned) + { + List_iterator<partition_element> subpart_it(part_elem->subpartitions); + for (j= 0; j < num_subparts; j++) + { + sub_elem= subpart_it++; + DBUG_ASSERT(sub_elem); + part= i * num_subparts + j; + DBUG_ASSERT(part < m_file_tot_parts && m_file[part]); + dummy_info.data_file_name= dummy_info.index_file_name = NULL; + m_file[part]->update_create_info(&dummy_info); + sub_elem->data_file_name = (char*) dummy_info.data_file_name; + sub_elem->index_file_name = (char*) dummy_info.index_file_name; + } + } + else + { + DBUG_ASSERT(m_file[i]); + dummy_info.data_file_name= dummy_info.index_file_name= NULL; + m_file[i]->update_create_info(&dummy_info); + part_elem->data_file_name = (char*) dummy_info.data_file_name; + part_elem->index_file_name = (char*) dummy_info.index_file_name; + } + } + DBUG_VOID_RETURN; } +/** + Change the internal TABLE_SHARE pointer + + @param table_arg TABLE object + @param share New share to use + + @note Is used in error handling in ha_delete_table. + All handlers should exist (lock_partitions should not be used) +*/ + void ha_partition::change_table_ptr(TABLE *table_arg, TABLE_SHARE *share) { handler **file_array; @@ -1976,72 +2259,52 @@ char *ha_partition::update_table_comment(const char *comment) } +/** + Handle delete and rename table -/* - Handle delete, rename and create table - - SYNOPSIS - del_ren_cre_table() - from Full path of old table - to Full path of new table - table_arg Table object - create_info Create info + @param from Full path of old table + @param to Full path of new table - RETURN VALUE - >0 Error - 0 Success + @return Operation status + @retval >0 Error + @retval 0 Success - DESCRIPTION - Common routine to handle delete_table and rename_table. - The routine uses the partition handler file to get the - names of the partition instances. Both these routines - are called after creating the handler without table - object and thus the file is needed to discover the - names of the partitions and the underlying storage engines. + @note Common routine to handle delete_table and rename_table. + The routine uses the partition handler file to get the + names of the partition instances. Both these routines + are called after creating the handler without table + object and thus the file is needed to discover the + names of the partitions and the underlying storage engines. */ -int ha_partition::del_ren_cre_table(const char *from, - const char *to, - TABLE *table_arg, - HA_CREATE_INFO *create_info) +uint ha_partition::del_ren_table(const char *from, const char *to) { int save_error= 0; - int error= HA_ERR_INTERNAL_ERROR; + int error; char from_buff[FN_REFLEN + 1], to_buff[FN_REFLEN + 1], - from_lc_buff[FN_REFLEN], to_lc_buff[FN_REFLEN], buff[FN_REFLEN]; + from_lc_buff[FN_REFLEN], to_lc_buff[FN_REFLEN]; char *name_buffer_ptr; const char *from_path; const char *to_path= NULL; uint i; handler **file, **abort_file; - DBUG_ENTER("del_ren_cre_table()"); - - /* Not allowed to create temporary partitioned tables */ - if (create_info && create_info->options & HA_LEX_CREATE_TMP_TABLE) - { - my_error(ER_PARTITION_NO_TEMPORARY, MYF(0)); - DBUG_RETURN(error); - } - - fn_format(buff,from, "", ha_par_ext, MY_APPEND_EXT); - /* Check if the par file exists */ - if (my_access(buff,F_OK)) - { - /* - If the .par file does not exist, return HA_ERR_NO_SUCH_TABLE, - This will signal to the caller that it can remove the .frm - file. - */ - error= HA_ERR_NO_SUCH_TABLE; - DBUG_RETURN(error); - } + DBUG_ENTER("ha_partition::del_ren_table"); if (get_from_handler_file(from, ha_thd()->mem_root, false)) - DBUG_RETURN(error); + DBUG_RETURN(TRUE); DBUG_ASSERT(m_file_buffer); DBUG_PRINT("enter", ("from: (%s) to: (%s)", from, to ? to : "(nil)")); name_buffer_ptr= m_name_buffer_ptr; file= m_file; + if (to == NULL) + { + /* + Delete table, start by delete the .par file. If error, break, otherwise + delete as much as possible. + */ + if ((error= handler::delete_table(from))) + DBUG_RETURN(error); + } /* Since ha_partition has HA_FILE_BASED, it must alter underlying table names if they do not have HA_FILE_BASED and lower_case_table_names == 2. @@ -2056,46 +2319,27 @@ int ha_partition::del_ren_cre_table(const char *from, do { if ((error= create_partition_name(from_buff, sizeof(from_buff), from_path, - name_buffer_ptr, NORMAL_PART_NAME, FALSE))) + name_buffer_ptr, NORMAL_PART_NAME, FALSE))) goto rename_error; if (to != NULL) - { // Rename branch + { // Rename branch if ((error= create_partition_name(to_buff, sizeof(to_buff), to_path, - name_buffer_ptr, NORMAL_PART_NAME, FALSE))) + name_buffer_ptr, NORMAL_PART_NAME, FALSE))) goto rename_error; error= (*file)->ha_rename_table(from_buff, to_buff); if (error) goto rename_error; } - else if (table_arg == NULL) // delete branch - error= (*file)->ha_delete_table(from_buff); - else + else // delete branch { - if ((error= set_up_table_before_create(table_arg, from_buff, - create_info, i, NULL)) || - parse_engine_table_options(ha_thd(), (*file)->ht, - (*file)->table_share) || - ((error= (*file)->ha_create(from_buff, table_arg, create_info)))) - goto create_error; + error= (*file)->ha_delete_table(from_buff); } name_buffer_ptr= strend(name_buffer_ptr) + 1; if (error) save_error= error; i++; } while (*(++file)); - - if (to == NULL && table_arg == NULL) - { - DBUG_EXECUTE_IF("crash_before_deleting_par_file", DBUG_SUICIDE();); - - /* Delete the .par file. If error, break.*/ - if ((error= handler::delete_table(from))) - DBUG_RETURN(error); - - DBUG_EXECUTE_IF("crash_after_deleting_par_file", DBUG_SUICIDE();); - } - if (to != NULL) { if ((error= handler::rename_table(from, to))) @@ -2106,72 +2350,24 @@ int ha_partition::del_ren_cre_table(const char *from, } } DBUG_RETURN(save_error); -create_error: - name_buffer_ptr= m_name_buffer_ptr; - for (abort_file= file, file= m_file; file < abort_file; file++) - { - if (!create_partition_name(from_buff, sizeof(from_buff), from_path, - name_buffer_ptr, NORMAL_PART_NAME, FALSE)) - (void) (*file)->ha_delete_table(from_buff); - name_buffer_ptr= strend(name_buffer_ptr) + 1; - } - DBUG_RETURN(error); rename_error: name_buffer_ptr= m_name_buffer_ptr; for (abort_file= file, file= m_file; file < abort_file; file++) { /* Revert the rename, back from 'to' to the original 'from' */ if (!create_partition_name(from_buff, sizeof(from_buff), from_path, - name_buffer_ptr, NORMAL_PART_NAME, FALSE) && - !create_partition_name(to_buff, sizeof(to_buff), to_path, name_buffer_ptr, - NORMAL_PART_NAME, FALSE)) + name_buffer_ptr, NORMAL_PART_NAME, FALSE) && + !create_partition_name(to_buff, sizeof(to_buff), to_path, + name_buffer_ptr, NORMAL_PART_NAME, FALSE)) + { + /* Ignore error here */ (void) (*file)->ha_rename_table(to_buff, from_buff); + } name_buffer_ptr= strend(name_buffer_ptr) + 1; } DBUG_RETURN(error); } -/* - Find partition based on partition id - - SYNOPSIS - find_partition_element() - part_id Partition id of partition looked for - - RETURN VALUE - >0 Reference to partition_element - 0 Partition not found -*/ - -partition_element *ha_partition::find_partition_element(uint part_id) -{ - uint i; - uint curr_part_id= 0; - List_iterator_fast <partition_element> part_it(m_part_info->partitions); - - for (i= 0; i < m_part_info->num_parts; i++) - { - partition_element *part_elem; - part_elem= part_it++; - if (m_is_sub_partitioned) - { - uint j; - List_iterator_fast <partition_element> sub_it(part_elem->subpartitions); - for (j= 0; j < m_part_info->num_subparts; j++) - { - part_elem= sub_it++; - if (part_id == curr_part_id++) - return part_elem; - } - } - else if (part_id == curr_part_id++) - return part_elem; - } - DBUG_ASSERT(0); - my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); - return NULL; -} - uint ha_partition::count_query_cache_dependant_tables(uint8 *tables_type) { DBUG_ENTER("ha_partition::count_query_cache_dependant_tables"); @@ -2188,26 +2384,27 @@ uint ha_partition::count_query_cache_dependant_tables(uint8 *tables_type) DBUG_RETURN(type == HA_CACHE_TBL_ASKTRANSACT ? m_tot_parts : 0); } -my_bool ha_partition::reg_query_cache_dependant_table(THD *thd, - char *key, uint key_len, - uint8 type, - Query_cache *cache, - Query_cache_block_table **block_table, - handler *file, - uint *n) +my_bool ha_partition:: +reg_query_cache_dependant_table(THD *thd, + char *engine_key, uint engine_key_len, + char *cache_key, uint cache_key_len, + uint8 type, + Query_cache *cache, + Query_cache_block_table **block_table, + handler *file, + uint *n) { DBUG_ENTER("ha_partition::reg_query_cache_dependant_table"); qc_engine_callback engine_callback; ulonglong engine_data; /* ask undelying engine */ - if (!file->register_query_cache_table(thd, key, - key_len, + if (!file->register_query_cache_table(thd, engine_key, + engine_key_len, &engine_callback, &engine_data)) { - DBUG_PRINT("qcache", ("Handler does not allow caching for %s.%s", - key, - key + table_share->db.length + 1)); + DBUG_PRINT("qcache", ("Handler does not allow caching for %.*s", + engine_key_len, engine_key)); /* As this can change from call to call, don't reset set thd->lex->safe_to_cache_query @@ -2216,9 +2413,11 @@ my_bool ha_partition::reg_query_cache_dependant_table(THD *thd, DBUG_RETURN(TRUE); } (++(*block_table))->n= ++(*n); - if (!cache->insert_table(key_len, - key, (*block_table), + if (!cache->insert_table(thd, cache_key_len, + cache_key, (*block_table), table_share->db.length, + (uint8) (cache_key_len - + table_share->table_cache_key.length), type, engine_callback, engine_data, FALSE)) @@ -2227,19 +2426,19 @@ my_bool ha_partition::reg_query_cache_dependant_table(THD *thd, } -my_bool ha_partition::register_query_cache_dependant_tables(THD *thd, - Query_cache *cache, - Query_cache_block_table **block_table, - uint *n) +my_bool ha_partition:: +register_query_cache_dependant_tables(THD *thd, + Query_cache *cache, + Query_cache_block_table **block_table, + uint *n) { - char *name; - uint prefix_length= table_share->table_cache_key.length + 3; + char *engine_key_end, *query_cache_key_end; + uint i; uint num_parts= m_part_info->num_parts; uint num_subparts= m_part_info->num_subparts; - uint i= 0; + int diff_length; List_iterator<partition_element> part_it(m_part_info->partitions); - char key[FN_REFLEN]; - + char engine_key[FN_REFLEN], query_cache_key[FN_REFLEN]; DBUG_ENTER("ha_partition::register_query_cache_dependant_tables"); /* see ha_partition::count_query_cache_dependant_tables */ @@ -2247,36 +2446,51 @@ my_bool ha_partition::register_query_cache_dependant_tables(THD *thd, DBUG_RETURN(FALSE); // nothing to register /* prepare static part of the key */ - memmove(key, table_share->table_cache_key.str, - table_share->table_cache_key.length); + memcpy(engine_key, table_share->normalized_path.str, + table_share->normalized_path.length); + memcpy(query_cache_key, table_share->table_cache_key.str, + table_share->table_cache_key.length); + + diff_length= ((int) table_share->table_cache_key.length - + (int) table_share->normalized_path.length -1); - name= key + table_share->table_cache_key.length - 1; - name[0]= name[2]= '#'; - name[1]= 'P'; - name+= 3; + engine_key_end= engine_key + table_share->normalized_path.length; + query_cache_key_end= query_cache_key + table_share->table_cache_key.length -1; + engine_key_end[0]= engine_key_end[2]= query_cache_key_end[0]= + query_cache_key_end[2]= '#'; + query_cache_key_end[1]= engine_key_end[1]= 'P'; + engine_key_end+= 3; + query_cache_key_end+= 3; + + i= 0; do { partition_element *part_elem= part_it++; - uint part_len= strmov(name, part_elem->partition_name) - name; + char *engine_pos= strmov(engine_key_end, part_elem->partition_name); if (m_is_sub_partitioned) { List_iterator<partition_element> subpart_it(part_elem->subpartitions); partition_element *sub_elem; - char *sname= name + part_len; uint j= 0, part; - sname[0]= sname[3]= '#'; - sname[1]= 'S'; - sname[2]= 'P'; - sname += 4; + engine_pos[0]= engine_pos[3]= '#'; + engine_pos[1]= 'S'; + engine_pos[2]= 'P'; + engine_pos += 4; do { + char *end; + uint length; sub_elem= subpart_it++; part= i * num_subparts + j; - uint spart_len= strmov(sname, sub_elem->partition_name) - name + 1; - if (reg_query_cache_dependant_table(thd, key, - prefix_length + part_len + 4 + - spart_len, + /* we store the end \0 as part of the key */ + end= strmov(engine_pos, sub_elem->partition_name); + length= end - engine_key; + /* Copy the suffix also to query cache key */ + memcpy(query_cache_key_end, engine_key_end, (end - engine_key_end)); + if (reg_query_cache_dependant_table(thd, engine_key, length, + query_cache_key, + length + diff_length, m_file[part]->table_cache_type(), cache, block_table, m_file[part], @@ -2286,8 +2500,13 @@ my_bool ha_partition::register_query_cache_dependant_tables(THD *thd, } else { - if (reg_query_cache_dependant_table(thd, key, - prefix_length + part_len + 1, + char *end= engine_pos+1; // copy end \0 + uint length= end - engine_key; + /* Copy the suffix also to query cache key */ + memcpy(query_cache_key_end, engine_key_end, (end - engine_key_end)); + if (reg_query_cache_dependant_table(thd, engine_key, length, + query_cache_key, + length + diff_length, m_file[i]->table_cache_type(), cache, block_table, m_file[i], @@ -2300,31 +2519,28 @@ my_bool ha_partition::register_query_cache_dependant_tables(THD *thd, } -/* - Set up table share object before calling create on underlying handler - - SYNOPSIS - set_up_table_before_create() - table Table object - info Create info - part_id Partition id of partition to set-up +/** + Set up table share object before calling create on underlying handler - RETURN VALUE - TRUE Error - FALSE Success + @param table Table object + @param info Create info + @param part_elem[in,out] Pointer to used partition_element, searched if NULL - DESCRIPTION - Set up - 1) Comment on partition - 2) MAX_ROWS, MIN_ROWS on partition - 3) Index file name on partition - 4) Data file name on partition + @return status + @retval TRUE Error + @retval FALSE Success + + @details + Set up + 1) Comment on partition + 2) MAX_ROWS, MIN_ROWS on partition + 3) Index file name on partition + 4) Data file name on partition */ int ha_partition::set_up_table_before_create(TABLE *tbl, const char *partition_name_with_path, HA_CREATE_INFO *info, - uint part_id, partition_element *part_elem) { int error= 0; @@ -2332,12 +2548,10 @@ int ha_partition::set_up_table_before_create(TABLE *tbl, THD *thd= ha_thd(); DBUG_ENTER("set_up_table_before_create"); + DBUG_ASSERT(part_elem); + if (!part_elem) - { - part_elem= find_partition_element(part_id); - if (!part_elem) - DBUG_RETURN(1); // Fatal error - } + DBUG_RETURN(1); tbl->s->max_rows= part_elem->part_max_rows; tbl->s->min_rows= part_elem->part_min_rows; partition_name= strrchr(partition_name_with_path, FN_LIBCHAR); @@ -2472,10 +2686,8 @@ bool ha_partition::create_handler_file(const char *name) /* 4 static words (tot words, checksum, tot partitions, name length) */ tot_len_words= 4 + tot_partition_words + tot_name_words; tot_len_byte= PAR_WORD_SIZE * tot_len_words; - file_buffer= (uchar *) my_alloca(tot_len_byte); - if (!file_buffer) + if (!(file_buffer= (uchar *) my_malloc(tot_len_byte, MYF(MY_ZEROFILL)))) DBUG_RETURN(TRUE); - bzero(file_buffer, tot_len_byte); engine_array= (file_buffer + PAR_ENGINES_OFFSET); name_buffer_ptr= (char*) (engine_array + tot_partition_words * PAR_WORD_SIZE + PAR_WORD_SIZE); @@ -2556,7 +2768,7 @@ bool ha_partition::create_handler_file(const char *name) } else result= TRUE; - my_afree((char*) file_buffer); + my_free(file_buffer); DBUG_RETURN(result); } @@ -2600,8 +2812,7 @@ bool ha_partition::create_handlers(MEM_ROOT *mem_root) for (i= 0; i < m_tot_parts; i++) { handlerton *hton= plugin_data(m_engine_array[i], handlerton*); - if (!(m_file[i]= get_new_handler(table_share, mem_root, - hton))) + if (!(m_file[i]= get_new_handler(table_share, mem_root, hton))) DBUG_RETURN(TRUE); DBUG_PRINT("info", ("engine_type: %u", hton->db_type)); } @@ -2708,9 +2919,10 @@ error_end: bool ha_partition::read_par_file(const char *name) { - char buff[FN_REFLEN], *tot_name_len_offset; + char buff[FN_REFLEN]; + uchar *tot_name_len_offset; File file; - char *file_buffer; + uchar *file_buffer; uint i, len_bytes, len_words, tot_partition_words, tot_name_words, chksum; DBUG_ENTER("ha_partition::read_par_file"); DBUG_PRINT("enter", ("table name: '%s'", name)); @@ -2729,9 +2941,9 @@ bool ha_partition::read_par_file(const char *name) len_bytes= PAR_WORD_SIZE * len_words; if (mysql_file_seek(file, 0, MY_SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) goto err1; - if (!(file_buffer= (char*) alloc_root(&m_mem_root, len_bytes))) + if (!(file_buffer= (uchar*) alloc_root(&m_mem_root, len_bytes))) goto err1; - if (mysql_file_read(file, (uchar *) file_buffer, len_bytes, MYF(MY_NABP))) + if (mysql_file_read(file, file_buffer, len_bytes, MYF(MY_NABP))) goto err2; chksum= 0; @@ -2754,7 +2966,7 @@ bool ha_partition::read_par_file(const char *name) if (len_words != (tot_partition_words + tot_name_words + 4)) goto err2; m_file_buffer= file_buffer; // Will be freed in clear_handler_file() - m_name_buffer_ptr= tot_name_len_offset + PAR_WORD_SIZE; + m_name_buffer_ptr= (char*) (tot_name_len_offset + PAR_WORD_SIZE); if (!(m_connect_string= (LEX_STRING*) alloc_root(&m_mem_root, m_tot_parts * sizeof(LEX_STRING)))) @@ -2804,7 +3016,8 @@ bool ha_partition::setup_engine_array(MEM_ROOT *mem_root) { uint i; uchar *buff; - handlerton **engine_array; + handlerton **engine_array, *first_engine; + enum legacy_db_type db_type, first_db_type; DBUG_ASSERT(!m_file); DBUG_ENTER("ha_partition::setup_engine_array"); @@ -2813,20 +3026,34 @@ bool ha_partition::setup_engine_array(MEM_ROOT *mem_root) DBUG_RETURN(true); buff= (uchar *) (m_file_buffer + PAR_ENGINES_OFFSET); - for (i= 0; i < m_tot_parts; i++) - { - engine_array[i]= ha_resolve_by_legacy_type(ha_thd(), - (enum legacy_db_type) - *(buff + i)); - if (!engine_array[i]) - goto err; - } + first_db_type= (enum legacy_db_type) buff[0]; + first_engine= ha_resolve_by_legacy_type(ha_thd(), first_db_type); + if (!first_engine) + goto err; + if (!(m_engine_array= (plugin_ref*) alloc_root(&m_mem_root, m_tot_parts * sizeof(plugin_ref)))) goto err; for (i= 0; i < m_tot_parts; i++) - m_engine_array[i]= ha_lock_engine(NULL, engine_array[i]); + { + db_type= (enum legacy_db_type) buff[i]; + if (db_type != first_db_type) + { + DBUG_PRINT("error", ("partition %u engine %d is not same as " + "first partition %d", i, db_type, + (int) first_db_type)); + DBUG_ASSERT(0); + clear_handler_file(); + goto err; + } + m_engine_array[i]= ha_lock_engine(NULL, first_engine); + if (!m_engine_array[i]) + { + clear_handler_file(); + goto err; + } + } my_afree(engine_array); @@ -2882,19 +3109,298 @@ bool ha_partition::get_from_handler_file(const char *name, MEM_ROOT *mem_root, MODULE open/close object ****************************************************************************/ +/** + Get the partition name. + + @param part Struct containing name and length + @param[out] length Length of the name + + @return Partition name +*/ + +static uchar *get_part_name(PART_NAME_DEF *part, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length= part->length; + return part->partition_name; +} + + +/** + Insert a partition name in the partition_name_hash. + + @param name Name of partition + @param part_id Partition id (number) + @param is_subpart Set if the name belongs to a subpartition + + @return Operation status + @retval true Failure + @retval false Sucess +*/ + +bool ha_partition::insert_partition_name_in_hash(const char *name, uint part_id, + bool is_subpart) +{ + PART_NAME_DEF *part_def; + uchar *part_name; + uint part_name_length; + DBUG_ENTER("ha_partition::insert_partition_name_in_hash"); + /* + Calculate and store the length here, to avoid doing it when + searching the hash. + */ + part_name_length= strlen(name); + /* + Must use memory that lives as long as table_share. + Freed in the Partition_share destructor. + Since we use my_multi_malloc, then my_free(part_def) will also free + part_name, as a part of my_hash_free. + */ + if (!my_multi_malloc(MY_WME, + &part_def, sizeof(PART_NAME_DEF), + &part_name, part_name_length + 1, + NULL)) + DBUG_RETURN(true); + memcpy(part_name, name, part_name_length + 1); + part_def->partition_name= part_name; + part_def->length= part_name_length; + part_def->part_id= part_id; + part_def->is_subpart= is_subpart; + if (my_hash_insert(&part_share->partition_name_hash, (uchar *) part_def)) + { + my_free(part_def); + DBUG_RETURN(true); + } + DBUG_RETURN(false); +} + + +/** + Populate the partition_name_hash in part_share. +*/ + +bool ha_partition::populate_partition_name_hash() +{ + List_iterator<partition_element> part_it(m_part_info->partitions); + uint num_parts= m_part_info->num_parts; + uint num_subparts= m_is_sub_partitioned ? m_part_info->num_subparts : 1; + uint tot_names; + uint i= 0; + DBUG_ASSERT(part_share); + + DBUG_ENTER("ha_partition::populate_partition_name_hash"); + + /* + partition_name_hash is only set once and never changed + -> OK to check without locking. + */ + + if (part_share->partition_name_hash_initialized) + DBUG_RETURN(false); + lock_shared_ha_data(); + if (part_share->partition_name_hash_initialized) + { + unlock_shared_ha_data(); + DBUG_RETURN(false); + } + tot_names= m_is_sub_partitioned ? m_tot_parts + num_parts : num_parts; + if (my_hash_init(&part_share->partition_name_hash, + system_charset_info, tot_names, 0, 0, + (my_hash_get_key) get_part_name, + my_free, HASH_UNIQUE)) + { + unlock_shared_ha_data(); + DBUG_RETURN(TRUE); + } + + do + { + partition_element *part_elem= part_it++; + DBUG_ASSERT(part_elem->part_state == PART_NORMAL); + if (part_elem->part_state == PART_NORMAL) + { + if (insert_partition_name_in_hash(part_elem->partition_name, + i * num_subparts, false)) + goto err; + if (m_is_sub_partitioned) + { + List_iterator<partition_element> + subpart_it(part_elem->subpartitions); + partition_element *sub_elem; + uint j= 0; + do + { + sub_elem= subpart_it++; + if (insert_partition_name_in_hash(sub_elem->partition_name, + i * num_subparts + j, true)) + goto err; + + } while (++j < num_subparts); + } + } + } while (++i < num_parts); + + part_share->partition_name_hash_initialized= true; + unlock_shared_ha_data(); + + DBUG_RETURN(FALSE); +err: + my_hash_free(&part_share->partition_name_hash); + unlock_shared_ha_data(); + + DBUG_RETURN(TRUE); +} + + +/** + Set Handler_share pointer and allocate Handler_share pointers + for each partition and set those. + + @param ha_share_arg Where to store/retrieve the Partitioning_share pointer + to be shared by all instances of the same table. + + @return Operation status + @retval true Failure + @retval false Sucess +*/ + +bool ha_partition::set_ha_share_ref(Handler_share **ha_share_arg) +{ + Handler_share **ha_shares; + uint i; + DBUG_ENTER("ha_partition::set_ha_share_ref"); + + DBUG_ASSERT(!part_share); + DBUG_ASSERT(table_share); + DBUG_ASSERT(!m_is_clone_of); + DBUG_ASSERT(m_tot_parts); + if (handler::set_ha_share_ref(ha_share_arg)) + DBUG_RETURN(true); + if (!(part_share= get_share())) + DBUG_RETURN(true); + DBUG_ASSERT(part_share->partitions_share_refs); + DBUG_ASSERT(part_share->partitions_share_refs->num_parts >= m_tot_parts); + ha_shares= part_share->partitions_share_refs->ha_shares; + for (i= 0; i < m_tot_parts; i++) + { + if (m_file[i]->set_ha_share_ref(&ha_shares[i])) + DBUG_RETURN(true); + } + DBUG_RETURN(false); +} + + +/** + Get the PARTITION_SHARE for the table. + + @return Operation status + @retval true Error + @retval false Success + + @note Gets or initializes the Partition_share object used by partitioning. + The Partition_share is used for handling the auto_increment etc. +*/ + +Partition_share *ha_partition::get_share() +{ + Partition_share *tmp_share; + DBUG_ENTER("ha_partition::get_share"); + DBUG_ASSERT(table_share); + + lock_shared_ha_data(); + if (!(tmp_share= static_cast<Partition_share*>(get_ha_share_ptr()))) + { + tmp_share= new Partition_share; + if (!tmp_share) + goto err; + if (tmp_share->init(m_tot_parts)) + { + delete tmp_share; + tmp_share= NULL; + goto err; + } + set_ha_share_ptr(static_cast<Handler_share*>(tmp_share)); + } +err: + unlock_shared_ha_data(); + DBUG_RETURN(tmp_share); +} + + + +/** + Helper function for freeing all internal bitmaps. +*/ + +void ha_partition::free_partition_bitmaps() +{ + /* Initialize the bitmap we use to minimize ha_start_bulk_insert calls */ + my_bitmap_free(&m_bulk_insert_started); + my_bitmap_free(&m_locked_partitions); + my_bitmap_free(&m_partitions_to_reset); + my_bitmap_free(&m_key_not_found_partitions); +} + /** - A destructor for partition-specific TABLE_SHARE data. + Helper function for initializing all internal bitmaps. */ -void ha_data_partition_destroy(HA_DATA_PARTITION* ha_part_data) +bool ha_partition::init_partition_bitmaps() { - if (ha_part_data) + DBUG_ENTER("ha_partition::init_partition_bitmaps"); + /* Initialize the bitmap we use to minimize ha_start_bulk_insert calls */ + if (my_bitmap_init(&m_bulk_insert_started, NULL, m_tot_parts + 1, FALSE)) + DBUG_RETURN(true); + bitmap_clear_all(&m_bulk_insert_started); + + /* Initialize the bitmap we use to keep track of locked partitions */ + if (my_bitmap_init(&m_locked_partitions, NULL, m_tot_parts, FALSE)) + { + my_bitmap_free(&m_bulk_insert_started); + DBUG_RETURN(true); + } + bitmap_clear_all(&m_locked_partitions); + + /* + Initialize the bitmap we use to keep track of partitions which may have + something to reset in ha_reset(). + */ + if (my_bitmap_init(&m_partitions_to_reset, NULL, m_tot_parts, FALSE)) + { + my_bitmap_free(&m_bulk_insert_started); + my_bitmap_free(&m_locked_partitions); + DBUG_RETURN(true); + } + bitmap_clear_all(&m_partitions_to_reset); + + /* + Initialize the bitmap we use to keep track of partitions which returned + HA_ERR_KEY_NOT_FOUND from index_read_map. + */ + if (my_bitmap_init(&m_key_not_found_partitions, NULL, m_tot_parts, FALSE)) + { + my_bitmap_free(&m_bulk_insert_started); + my_bitmap_free(&m_locked_partitions); + my_bitmap_free(&m_partitions_to_reset); + DBUG_RETURN(true); + } + bitmap_clear_all(&m_key_not_found_partitions); + m_key_not_found= false; + /* Initialize the bitmap for read/lock_partitions */ + if (!m_is_clone_of) { - mysql_mutex_destroy(&ha_part_data->LOCK_auto_inc); + DBUG_ASSERT(!m_clone_mem_root); + if (m_part_info->set_partition_bitmaps(NULL)) + { + free_partition_bitmaps(); + DBUG_RETURN(true); + } } + DBUG_RETURN(false); } + /* Open handler object @@ -2924,7 +3430,6 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) int error= HA_ERR_INITIALIZATION; handler **file; char name_buff[FN_REFLEN + 1]; - bool is_not_tmp_table= (table_share->tmp_table == NO_TMP_TABLE); ulonglong check_table_flags; DBUG_ENTER("ha_partition::open"); @@ -2933,9 +3438,13 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) m_mode= mode; m_open_test_lock= test_if_locked; m_part_field_array= m_part_info->full_part_field_array; - if (get_from_handler_file(name, &table->mem_root, test(m_is_clone_of))) + if (get_from_handler_file(name, &table->mem_root, MY_TEST(m_is_clone_of))) DBUG_RETURN(error); name_buffer_ptr= m_name_buffer_ptr; + if (populate_partition_name_hash()) + { + DBUG_RETURN(HA_ERR_INITIALIZATION); + } m_start_key.length= 0; m_rec0= table->record[0]; m_rec_length= table_share->stored_rec_length; @@ -2950,32 +3459,10 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) m_part_ids_sorted_by_num_of_records[i]= i; } - /* Initialize the bitmap we use to minimize ha_start_bulk_insert calls */ - if (bitmap_init(&m_bulk_insert_started, NULL, m_tot_parts + 1, FALSE)) - DBUG_RETURN(error); - bitmap_clear_all(&m_bulk_insert_started); - /* - Initialize the bitmap we use to keep track of partitions which returned - HA_ERR_KEY_NOT_FOUND from index_read_map. - */ - if (bitmap_init(&m_key_not_found_partitions, NULL, m_tot_parts, FALSE)) - { - bitmap_free(&m_bulk_insert_started); + if (init_partition_bitmaps()) DBUG_RETURN(error); - } - bitmap_clear_all(&m_key_not_found_partitions); - m_key_not_found= false; - /* Initialize the bitmap we use to determine what partitions are used */ - if (!m_is_clone_of) - { - DBUG_ASSERT(!m_clone_mem_root); - if (bitmap_init(&(m_part_info->used_partitions), NULL, m_tot_parts, TRUE)) - { - bitmap_free(&m_bulk_insert_started); - DBUG_RETURN(error); - } - bitmap_set_all(&(m_part_info->used_partitions)); - } + + DBUG_ASSERT(m_part_info); if (m_is_clone_of) { @@ -2984,7 +3471,10 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) /* Allocate an array of handler pointers for the partitions handlers. */ alloc_len= (m_tot_parts + 1) * sizeof(handler*); if (!(m_file= (handler **) alloc_root(m_clone_mem_root, alloc_len))) + { + error= HA_ERR_INITIALIZATION; goto err_alloc; + } memset(m_file, 0, alloc_len); /* Populate them by cloning the original partitions. This also opens them. @@ -2994,8 +3484,9 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) for (i= 0; i < m_tot_parts; i++) { if ((error= create_partition_name(name_buff, sizeof(name_buff), name, - name_buffer_ptr, NORMAL_PART_NAME, FALSE))) + name_buffer_ptr, NORMAL_PART_NAME, FALSE))) goto err_handler; + /* ::clone() will also set ha_share from the original. */ if (!(m_file[i]= file[i]->clone(name_buff, m_clone_mem_root))) { error= HA_ERR_INITIALIZATION; @@ -3011,13 +3502,16 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) do { if ((error= create_partition_name(name_buff, sizeof(name_buff), name, - name_buffer_ptr, NORMAL_PART_NAME, FALSE))) + name_buffer_ptr, NORMAL_PART_NAME, FALSE))) goto err_handler; table->s->connect_string = m_connect_string[(uint)(file-m_file)]; - if ((error= (*file)->ha_open(table, name_buff, mode, test_if_locked))) + if ((error= (*file)->ha_open(table, name_buff, mode, + test_if_locked | HA_OPEN_NO_PSI_CALL))) goto err_handler; bzero(&table->s->connect_string, sizeof(LEX_STRING)); - m_num_locks+= (*file)->lock_count(); + if (m_file == file) + m_num_locks= (*file)->lock_count(); + DBUG_ASSERT(m_num_locks == (*file)->lock_count()); name_buffer_ptr+= strlen(name_buffer_ptr) + 1; } while (*(++file)); } @@ -3040,7 +3534,7 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) (PARTITION_ENABLED_TABLE_FLAGS))) { error= HA_ERR_INITIALIZATION; - /* set file to last handler, so all of them is closed */ + /* set file to last handler, so all of them are closed */ file = &m_file[m_tot_parts - 1]; goto err_handler; } @@ -3061,34 +3555,6 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) clear_handler_file(); /* - Use table_share->ha_part_data to share auto_increment_value among - all handlers for the same table. - */ - if (is_not_tmp_table) - mysql_mutex_lock(&table_share->LOCK_ha_data); - if (!table_share->ha_part_data) - { - /* currently only needed for auto_increment */ - table_share->ha_part_data= (HA_DATA_PARTITION*) - alloc_root(&table_share->mem_root, - sizeof(HA_DATA_PARTITION)); - if (!table_share->ha_part_data) - { - if (is_not_tmp_table) - mysql_mutex_unlock(&table_share->LOCK_ha_data); - goto err_handler; - } - DBUG_PRINT("info", ("table_share->ha_part_data 0x%p", - table_share->ha_part_data)); - bzero(table_share->ha_part_data, sizeof(HA_DATA_PARTITION)); - table_share->ha_part_data_destroy= ha_data_partition_destroy; - mysql_mutex_init(key_PARTITION_LOCK_auto_inc, - &table_share->ha_part_data->LOCK_auto_inc, - MY_MUTEX_INIT_FAST); - } - if (is_not_tmp_table) - mysql_mutex_unlock(&table_share->LOCK_ha_data); - /* Some handlers update statistics as part of the open call. This will in some cases corrupt the statistics of the partition handler and thus to ensure we have correct statistics we call info from open after @@ -3108,15 +3574,49 @@ err_handler: while (file-- != m_file) (*file)->ha_close(); err_alloc: - bitmap_free(&m_bulk_insert_started); - bitmap_free(&m_key_not_found_partitions); - if (!m_is_clone_of) - bitmap_free(&(m_part_info->used_partitions)); + free_partition_bitmaps(); DBUG_RETURN(error); } +/* + Disabled since it is not possible to prune yet. + without pruning, it need to rebind/unbind every partition in every + statement which uses a table from the table cache. Will also use + as many PSI_tables as there are partitions. +*/ +#ifdef HAVE_M_PSI_PER_PARTITION +void ha_partition::unbind_psi() +{ + uint i; + + DBUG_ENTER("ha_partition::unbind_psi"); + handler::unbind_psi(); + for (i= 0; i < m_tot_parts; i++) + { + DBUG_ASSERT(m_file[i] != NULL); + m_file[i]->unbind_psi(); + } + DBUG_VOID_RETURN; +} + +void ha_partition::rebind_psi() +{ + uint i; + + DBUG_ENTER("ha_partition::rebind_psi"); + handler::rebind_psi(); + for (i= 0; i < m_tot_parts; i++) + { + DBUG_ASSERT(m_file[i] != NULL); + m_file[i]->rebind_psi(); + } + DBUG_VOID_RETURN; +} +#endif /* HAVE_M_PSI_PER_PARTITION */ + + /** Clone the open and locked partitioning handler. @@ -3140,22 +3640,35 @@ handler *ha_partition::clone(const char *name, MEM_ROOT *mem_root) DBUG_ENTER("ha_partition::clone"); new_handler= new (mem_root) ha_partition(ht, table_share, m_part_info, this, mem_root); + if (!new_handler) + DBUG_RETURN(NULL); + + /* + We will not clone each partition's handler here, it will be done in + ha_partition::open() for clones. Also set_ha_share_ref is not needed + here, since 1) ha_share is copied in the constructor used above + 2) each partition's cloned handler will set it from its original. + */ + /* Allocate new_handler->ref here because otherwise ha_open will allocate it on this->table->mem_root and we will not be able to reclaim that memory when the clone handler object is destroyed. */ - if (new_handler && - !(new_handler->ref= (uchar*) alloc_root(mem_root, + if (!(new_handler->ref= (uchar*) alloc_root(mem_root, ALIGN_SIZE(m_ref_length)*2))) - new_handler= NULL; + goto err; - if (new_handler && - new_handler->ha_open(table, name, - table->db_stat, HA_OPEN_IGNORE_IF_LOCKED)) - new_handler= NULL; + if (new_handler->ha_open(table, name, + table->db_stat, + HA_OPEN_IGNORE_IF_LOCKED | HA_OPEN_NO_PSI_CALL)) + goto err; DBUG_RETURN((handler*) new_handler); + +err: + delete new_handler; + DBUG_RETURN(NULL); } @@ -3185,10 +3698,8 @@ int ha_partition::close(void) DBUG_ASSERT(table->s == table_share); destroy_record_priority_queue(); - bitmap_free(&m_bulk_insert_started); - bitmap_free(&m_key_not_found_partitions); - if (!m_is_clone_of) - bitmap_free(&(m_part_info->used_partitions)); + free_partition_bitmaps(); + DBUG_ASSERT(m_part_info); file= m_file; repeat: @@ -3250,41 +3761,64 @@ repeat: int ha_partition::external_lock(THD *thd, int lock_type) { - bool first= TRUE; uint error; - handler **file; + uint i, first_used_partition; + MY_BITMAP *used_partitions; DBUG_ENTER("ha_partition::external_lock"); DBUG_ASSERT(!auto_increment_lock && !auto_increment_safe_stmt_log_lock); - file= m_file; - m_lock_type= lock_type; -repeat: - do + if (lock_type == F_UNLCK) + used_partitions= &m_locked_partitions; + else + used_partitions= &(m_part_info->lock_partitions); + + first_used_partition= bitmap_get_first_set(used_partitions); + + for (i= first_used_partition; + i < m_tot_parts; + i= bitmap_get_next_set(used_partitions, i)) { - DBUG_PRINT("info", ("external_lock(thd, %d) iteration %d", - lock_type, (int) (file - m_file))); - if ((error= (*file)->ha_external_lock(thd, lock_type))) + DBUG_PRINT("info", ("external_lock(thd, %d) part %d", lock_type, i)); + if ((error= m_file[i]->ha_external_lock(thd, lock_type))) { - if (F_UNLCK != lock_type) + if (lock_type != F_UNLCK) goto err_handler; } - } while (*(++file)); + DBUG_PRINT("info", ("external_lock part %u lock %d", i, lock_type)); + if (lock_type != F_UNLCK) + bitmap_set_bit(&m_locked_partitions, i); + } + if (lock_type == F_UNLCK) + { + bitmap_clear_all(used_partitions); + } + else + { + /* Add touched partitions to be included in reset(). */ + bitmap_union(&m_partitions_to_reset, used_partitions); + } - if (first && m_added_file && m_added_file[0]) + if (m_added_file && m_added_file[0]) { + handler **file= m_added_file; DBUG_ASSERT(lock_type == F_UNLCK); - file= m_added_file; - first= FALSE; - goto repeat; + do + { + (void) (*file)->ha_external_lock(thd, lock_type); + } while (*(++file)); } DBUG_RETURN(0); err_handler: - while (file-- != m_file) + uint j; + for (j= first_used_partition; + j < i; + j= bitmap_get_next_set(&m_locked_partitions, j)) { - (*file)->ha_external_lock(thd, F_UNLCK); + (void) m_file[j]->ha_external_lock(thd, F_UNLCK); } + bitmap_clear_all(&m_locked_partitions); DBUG_RETURN(error); } @@ -3339,14 +3873,35 @@ THR_LOCK_DATA **ha_partition::store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type) { - handler **file; + uint i; DBUG_ENTER("ha_partition::store_lock"); - file= m_file; - do + DBUG_ASSERT(thd == current_thd); + + /* + This can be called from get_lock_data() in mysql_lock_abort_for_thread(), + even when thd != table->in_use. In that case don't use partition pruning, + but use all partitions instead to avoid using another threads structures. + */ + if (thd != table->in_use) { - DBUG_PRINT("info", ("store lock %d iteration", (int) (file - m_file))); - to= (*file)->store_lock(thd, to, lock_type); - } while (*(++file)); + for (i= 0; i < m_tot_parts; i++) + to= m_file[i]->store_lock(thd, to, lock_type); + } + else + { + MY_BITMAP *used_partitions= lock_type == TL_UNLOCK || + lock_type == TL_IGNORE ? + &m_locked_partitions : + &m_part_info->lock_partitions; + + for (i= bitmap_get_first_set(used_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(used_partitions, i)) + { + DBUG_PRINT("info", ("store lock %d iteration", i)); + to= m_file[i]->store_lock(thd, to, lock_type); + } + } DBUG_RETURN(to); } @@ -3370,40 +3925,57 @@ THR_LOCK_DATA **ha_partition::store_lock(THD *thd, int ha_partition::start_stmt(THD *thd, thr_lock_type lock_type) { int error= 0; - handler **file; + uint i; + /* Assert that read_partitions is included in lock_partitions */ + DBUG_ASSERT(bitmap_is_subset(&m_part_info->read_partitions, + &m_part_info->lock_partitions)); + /* + m_locked_partitions is set in previous external_lock/LOCK TABLES. + Current statement's lock requests must not include any partitions + not previously locked. + */ + DBUG_ASSERT(bitmap_is_subset(&m_part_info->lock_partitions, + &m_locked_partitions)); DBUG_ENTER("ha_partition::start_stmt"); - file= m_file; - do + for (i= bitmap_get_first_set(&(m_part_info->lock_partitions)); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->lock_partitions, i)) { - if ((error= (*file)->start_stmt(thd, lock_type))) + if ((error= m_file[i]->start_stmt(thd, lock_type))) break; - } while (*(++file)); + /* Add partition to be called in reset(). */ + bitmap_set_bit(&m_partitions_to_reset, i); + } DBUG_RETURN(error); } -/* +/** Get number of lock objects returned in store_lock - SYNOPSIS - lock_count() - - RETURN VALUE - Number of locks returned in call to store_lock + @returns Number of locks returned in call to store_lock - DESCRIPTION + @desc Returns the number of store locks needed in call to store lock. - We return number of partitions since we call store_lock on each - underlying handler. Assists the above functions in allocating + We return number of partitions we will lock multiplied with number of + locks needed by each partition. Assists the above functions in allocating sufficient space for lock structures. */ uint ha_partition::lock_count() const { DBUG_ENTER("ha_partition::lock_count"); - DBUG_PRINT("info", ("m_num_locks %d", m_num_locks)); - DBUG_RETURN(m_num_locks); + /* + The caller want to know the upper bound, to allocate enough memory. + There is no performance lost if we simply return maximum number locks + needed, only some minor over allocation of memory in get_lock_data(). + + Also notice that this may be called for another thread != table->in_use, + when mysql_lock_abort_for_thread() is called. So this is more safe, then + using number of partitions after pruning. + */ + DBUG_RETURN(m_tot_parts * m_num_locks); } @@ -3455,7 +4027,7 @@ bool ha_partition::was_semi_consistent_read() { DBUG_ENTER("ha_partition::was_semi_consistent_read"); DBUG_ASSERT(m_last_part < m_tot_parts && - bitmap_is_set(&(m_part_info->used_partitions), m_last_part)); + bitmap_is_set(&(m_part_info->read_partitions), m_last_part)); DBUG_RETURN(m_file[m_last_part]->was_semi_consistent_read()); } @@ -3480,13 +4052,16 @@ bool ha_partition::was_semi_consistent_read() */ void ha_partition::try_semi_consistent_read(bool yes) { - handler **file; + uint i; DBUG_ENTER("ha_partition::try_semi_consistent_read"); - for (file= m_file; *file; file++) + i= bitmap_get_first_set(&(m_part_info->read_partitions)); + DBUG_ASSERT(i != MY_BIT_NONE); + for (; + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if (bitmap_is_set(&(m_part_info->used_partitions), (file - m_file))) - (*file)->try_semi_consistent_read(yes); + m_file[i]->try_semi_consistent_read(yes); } DBUG_VOID_RETURN; } @@ -3529,8 +4104,8 @@ void ha_partition::try_semi_consistent_read(bool yes) ADDITIONAL INFO: - We have to set timestamp fields and auto_increment fields, because those - may be used in determining which partition the row should be written to. + We have to set auto_increment fields, because those may be used in + determining which partition the row should be written to. */ int ha_partition::write_row(uchar * buf) @@ -3541,27 +4116,18 @@ int ha_partition::write_row(uchar * buf) bool have_auto_increment= table->next_number_field && buf == table->record[0]; my_bitmap_map *old_map; THD *thd= ha_thd(); - timestamp_auto_set_type saved_timestamp_type= table->timestamp_field_type; - ulonglong saved_sql_mode= thd->variables.sql_mode; + sql_mode_t saved_sql_mode= thd->variables.sql_mode; bool saved_auto_inc_field_not_null= table->auto_increment_field_not_null; -#ifdef NOT_NEEDED - uchar *rec0= m_rec0; -#endif DBUG_ENTER("ha_partition::write_row"); DBUG_ASSERT(buf == m_rec0); - /* If we have a timestamp column, update it to the current time */ - if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) - table->timestamp_field->set_time(); - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - /* If we have an auto_increment column and we are writing a changed row or a new row, then update the auto_increment value in the record. */ if (have_auto_increment) { - if (!table_share->ha_part_data->auto_inc_initialized && + if (!part_share->auto_inc_initialized && !table_share->next_number_keypart) { /* @@ -3598,26 +4164,20 @@ int ha_partition::write_row(uchar * buf) } old_map= dbug_tmp_use_all_columns(table, table->read_set); -#ifdef NOT_NEEDED - if (likely(buf == rec0)) -#endif - error= m_part_info->get_partition_id(m_part_info, &part_id, - &func_value); -#ifdef NOT_NEEDED - else - { - set_field_ptr(m_part_field_array, buf, rec0); - error= m_part_info->get_partition_id(m_part_info, &part_id, - &func_value); - set_field_ptr(m_part_field_array, rec0, buf); - } -#endif + error= m_part_info->get_partition_id(m_part_info, &part_id, &func_value); dbug_tmp_restore_column_map(table->read_set, old_map); if (unlikely(error)) { m_part_info->err_value= func_value; goto exit; } + if (!bitmap_is_set(&(m_part_info->lock_partitions), part_id)) + { + DBUG_PRINT("info", ("Write to non-locked partition %u (func_value: %ld)", + part_id, (long) func_value)); + error= HA_ERR_NOT_IN_LOCK_PARTITIONS; + goto exit; + } m_last_part= part_id; DBUG_PRINT("info", ("Insert in partition %d", part_id)); start_part_bulk_insert(thd, part_id); @@ -3630,7 +4190,6 @@ int ha_partition::write_row(uchar * buf) exit: thd->variables.sql_mode= saved_sql_mode; table->auto_increment_field_not_null= saved_auto_inc_field_not_null; - table->timestamp_field_type= saved_timestamp_type; DBUG_RETURN(error); } @@ -3665,19 +4224,12 @@ int ha_partition::update_row(const uchar *old_data, uchar *new_data) uint32 new_part_id, old_part_id; int error= 0; longlong func_value; - timestamp_auto_set_type orig_timestamp_type= table->timestamp_field_type; DBUG_ENTER("ha_partition::update_row"); m_err_rec= NULL; - /* - We need to set timestamp field once before we calculate - the partition. Then we disable timestamp calculations - inside m_file[*]->update_row() methods - */ - if (orig_timestamp_type & TIMESTAMP_AUTO_SET_ON_UPDATE) - table->timestamp_field->set_time(); - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - + // Need to read partition-related columns, to locate the row's partition: + DBUG_ASSERT(bitmap_is_subset(&m_part_info->full_part_field_set, + table->read_set)); if ((error= get_parts_for_update(old_data, new_data, table->record[0], m_part_info, &old_part_id, &new_part_id, &func_value))) @@ -3685,6 +4237,13 @@ int ha_partition::update_row(const uchar *old_data, uchar *new_data) m_part_info->err_value= func_value; goto exit; } + DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), old_part_id)); + if (!bitmap_is_set(&(m_part_info->lock_partitions), new_part_id)) + { + error= HA_ERR_NOT_IN_LOCK_PARTITIONS; + goto exit; + } + /* The protocol for updating a row is: 1) position the handler (cursor) on the row to be updated, @@ -3698,11 +4257,14 @@ int ha_partition::update_row(const uchar *old_data, uchar *new_data) between partitions! Since we don't check all rows on read, we return an error instead of correcting m_last_part, to make the user aware of the problem! + + Notice that HA_READ_BEFORE_WRITE_REMOVAL does not require this protocol, + so this is not supported for this engine. */ if (old_part_id != m_last_part) { m_err_rec= old_data; - DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND); + DBUG_RETURN(HA_ERR_ROW_IN_WRONG_PARTITION); } m_last_part= new_part_id; @@ -3753,7 +4315,7 @@ int ha_partition::update_row(const uchar *old_data, uchar *new_data) exit: /* if updating an auto_increment column, update - table_share->ha_part_data->next_auto_inc_val if needed. + part_share->next_auto_inc_val if needed. (not to be used if auto_increment on secondary field in a multi-column index) mysql_update does not set table->next_number_field, so we use @@ -3766,11 +4328,10 @@ exit: bitmap_is_set(table->write_set, table->found_next_number_field->field_index)) { - if (!table_share->ha_part_data->auto_inc_initialized) + if (!part_share->auto_inc_initialized) info(HA_STATUS_AUTO); set_auto_increment_if_higher(table->found_next_number_field); } - table->timestamp_field_type= orig_timestamp_type; DBUG_RETURN(error); } @@ -3811,10 +4372,18 @@ int ha_partition::delete_row(const uchar *buf) DBUG_ENTER("ha_partition::delete_row"); m_err_rec= NULL; + DBUG_ASSERT(bitmap_is_subset(&m_part_info->full_part_field_set, + table->read_set)); if ((error= get_part_for_delete(buf, m_rec0, m_part_info, &part_id))) { DBUG_RETURN(error); } + /* Should never call delete_row on a partition which is not read */ + DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), part_id)); + DBUG_ASSERT(bitmap_is_set(&(m_part_info->lock_partitions), part_id)); + if (!bitmap_is_set(&(m_part_info->lock_partitions), part_id)) + DBUG_RETURN(HA_ERR_NOT_IN_LOCK_PARTITIONS); + /* The protocol for deleting a row is: 1) position the handler (cursor) on the row to be deleted, @@ -3828,15 +4397,20 @@ int ha_partition::delete_row(const uchar *buf) between partitions! Since we don't check all rows on read, we return an error instead of forwarding the delete to the correct (m_last_part) partition! + + Notice that HA_READ_BEFORE_WRITE_REMOVAL does not require this protocol, + so this is not supported for this engine. + TODO: change the assert in InnoDB into an error instead and make this one an assert instead and remove the get_part_for_delete()! */ if (part_id != m_last_part) { m_err_rec= buf; - DBUG_RETURN(HA_ERR_NO_PARTITION_FOUND); + DBUG_RETURN(HA_ERR_ROW_IN_WRONG_PARTITION); } + m_last_part= part_id; tmp_disable_binlog(thd); error= m_file[part_id]->ha_delete_row(buf); reenable_binlog(thd); @@ -3860,24 +4434,26 @@ int ha_partition::delete_row(const uchar *buf) removed as a result of a SQL statement. Called from item_sum.cc by Item_func_group_concat::clear(), - Item_sum_count_distinct::clear(), and Item_func_group_concat::clear(). + Item_sum_count::clear(), and Item_func_group_concat::clear(). Called from sql_delete.cc by mysql_delete(). - Called from sql_select.cc by JOIN::reinit(). + Called from sql_select.cc by JOIN::reset(). Called from sql_union.cc by st_select_lex_unit::exec(). */ int ha_partition::delete_all_rows() { int error; - handler **file; + uint i; DBUG_ENTER("ha_partition::delete_all_rows"); - file= m_file; - do + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if ((error= (*file)->ha_delete_all_rows())) + /* Can be pruned, like DELETE FROM t PARTITION (pX) */ + if ((error= m_file[i]->ha_delete_all_rows())) DBUG_RETURN(error); - } while (*(++file)); + } DBUG_RETURN(0); } @@ -3900,8 +4476,8 @@ int ha_partition::truncate() it so that it will be initialized again at the next use. */ lock_auto_increment(); - table_share->ha_part_data->next_auto_inc_val= 0; - table_share->ha_part_data->auto_inc_initialized= FALSE; + part_share->next_auto_inc_val= 0; + part_share->auto_inc_initialized= false; unlock_auto_increment(); file= m_file; @@ -3942,8 +4518,8 @@ int ha_partition::truncate_partition(Alter_info *alter_info, bool *binlog_stmt) it so that it will be initialized again at the next use. */ lock_auto_increment(); - table_share->ha_part_data->next_auto_inc_val= 0; - table_share->ha_part_data->auto_inc_initialized= FALSE; + part_share->next_auto_inc_val= 0; + part_share->auto_inc_initialized= FALSE; unlock_auto_increment(); *binlog_stmt= true; @@ -3957,7 +4533,7 @@ int ha_partition::truncate_partition(Alter_info *alter_info, bool *binlog_stmt) { List_iterator<partition_element> subpart_it(part_elem->subpartitions); - partition_element *sub_elem __attribute__((unused)); + partition_element *sub_elem; uint j= 0, part; do { @@ -3989,6 +4565,7 @@ int ha_partition::truncate_partition(Alter_info *alter_info, bool *binlog_stmt) SYNOPSIS start_bulk_insert() rows Number of rows to insert + flags Flags to control index creation RETURN VALUE NONE @@ -3996,7 +4573,7 @@ int ha_partition::truncate_partition(Alter_info *alter_info, bool *binlog_stmt) DESCRIPTION rows == 0 means we will probably insert many rows */ -void ha_partition::start_bulk_insert(ha_rows rows) +void ha_partition::start_bulk_insert(ha_rows rows, uint flags) { DBUG_ENTER("ha_partition::start_bulk_insert"); @@ -4018,6 +4595,7 @@ void ha_partition::start_part_bulk_insert(THD *thd, uint part_id) if (!bitmap_is_set(&m_bulk_insert_started, part_id) && bitmap_is_set(&m_bulk_insert_started, m_tot_parts)) { + DBUG_ASSERT(bitmap_is_set(&(m_part_info->lock_partitions), part_id)); old_buffer_size= thd->variables.read_buff_size; /* Update read_buffer_size for this partition */ thd->variables.read_buff_size= estimate_read_buffer_size(old_buffer_size); @@ -4125,11 +4703,12 @@ int ha_partition::end_bulk_insert() if (!bitmap_is_set(&m_bulk_insert_started, m_tot_parts)) DBUG_RETURN(error); - for (i= 0; i < m_tot_parts; i++) + for (i= bitmap_get_first_set(&m_bulk_insert_started); + i < m_tot_parts; + i= bitmap_get_next_set(&m_bulk_insert_started, i)) { int tmp; - if (bitmap_is_set(&m_bulk_insert_started, i) && - (tmp= m_file[i]->ha_end_bulk_insert())) + if ((tmp= m_file[i]->ha_end_bulk_insert())) error= tmp; } bitmap_clear_all(&m_bulk_insert_started); @@ -4177,7 +4756,7 @@ int ha_partition::rnd_init(bool scan) For operations that may need to change data, we may need to extend read_set. */ - if (m_lock_type == F_WRLCK) + if (get_lock_type() == F_WRLCK) { /* If write_set contains any of the fields used in partition and @@ -4202,9 +4781,9 @@ int ha_partition::rnd_init(bool scan) } /* Now we see what the index of our first important partition is */ - DBUG_PRINT("info", ("m_part_info->used_partitions: 0x%lx", - (long) m_part_info->used_partitions.bitmap)); - part_id= bitmap_get_first_set(&(m_part_info->used_partitions)); + DBUG_PRINT("info", ("m_part_info->read_partitions: 0x%lx", + (long) m_part_info->read_partitions.bitmap)); + part_id= bitmap_get_first_set(&(m_part_info->read_partitions)); DBUG_PRINT("info", ("m_part_spec.start_part %d", part_id)); if (MY_BIT_NONE == part_id) @@ -4231,13 +4810,12 @@ int ha_partition::rnd_init(bool scan) } else { - for (i= part_id; i < m_tot_parts; i++) + for (i= part_id; + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if (bitmap_is_set(&(m_part_info->used_partitions), i)) - { - if ((error= m_file[i]->ha_rnd_init(scan))) - goto err; - } + if ((error= m_file[i]->ha_rnd_init(scan))) + goto err; } } m_scan_value= scan; @@ -4247,10 +4825,12 @@ int ha_partition::rnd_init(bool scan) DBUG_RETURN(0); err: - while ((int)--i >= (int)part_id) + /* Call rnd_end for all previously inited partitions. */ + for (; + part_id < i; + part_id= bitmap_get_next_set(&m_part_info->read_partitions, part_id)) { - if (bitmap_is_set(&(m_part_info->used_partitions), i)) - m_file[i]->ha_rnd_end(); + m_file[part_id]->ha_rnd_end(); } err1: m_scan_value= 2; @@ -4272,7 +4852,6 @@ err1: int ha_partition::rnd_end() { - handler **file; DBUG_ENTER("ha_partition::rnd_end"); switch (m_scan_value) { case 2: // Error @@ -4285,12 +4864,13 @@ int ha_partition::rnd_end() } break; case 0: - file= m_file; - do + uint i; + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if (bitmap_is_set(&(m_part_info->used_partitions), (file - m_file))) - (*file)->ha_rnd_end(); - } while (*(++file)); + m_file[i]->ha_rnd_end(); + } break; } m_scan_value= 2; @@ -4353,7 +4933,7 @@ int ha_partition::rnd_next(uchar *buf) } /* - if we get here, then the current partition rnd_next returned failure + if we get here, then the current partition ha_rnd_next returned failure */ if (result == HA_ERR_RECORD_DELETED) continue; // Probably MyISAM @@ -4368,9 +4948,7 @@ int ha_partition::rnd_next(uchar *buf) break; /* Shift to next partition */ - while (++part_id < m_tot_parts && - !bitmap_is_set(&(m_part_info->used_partitions), part_id)) - ; + part_id= bitmap_get_next_set(&m_part_info->read_partitions, part_id); if (part_id >= m_tot_parts) { result= HA_ERR_END_OF_FILE; @@ -4388,7 +4966,6 @@ int ha_partition::rnd_next(uchar *buf) end: m_part_spec.start_part= NO_CURRENT_PART_ID; end_dont_reset_start_part: - table->status= STATUS_NOT_FOUND; DBUG_RETURN(result); } @@ -4422,6 +4999,7 @@ void ha_partition::position(const uchar *record) { handler *file= m_file[m_last_part]; uint pad_length; + DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), m_last_part)); DBUG_ENTER("ha_partition::position"); file->position(record); @@ -4435,14 +5013,6 @@ void ha_partition::position(const uchar *record) } -void ha_partition::column_bitmaps_signal() -{ - handler::column_bitmaps_signal(); - /* Must read all partition fields to make position() call possible */ - bitmap_union(table->read_set, &m_part_info->full_part_field_set); -} - - /* Read row using position @@ -4474,8 +5044,9 @@ int ha_partition::rnd_pos(uchar * buf, uchar *pos) part_id= uint2korr((const uchar *) pos); DBUG_ASSERT(part_id < m_tot_parts); file= m_file[part_id]; + DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), part_id)); m_last_part= part_id; - DBUG_RETURN(file->rnd_pos(buf, (pos + PARTITION_BYTES_IN_POS))); + DBUG_RETURN(file->ha_rnd_pos(buf, (pos + PARTITION_BYTES_IN_POS))); } @@ -4505,7 +5076,8 @@ int ha_partition::rnd_pos_by_record(uchar *record) if (unlikely(get_part_for_delete(record, m_rec0, m_part_info, &m_last_part))) DBUG_RETURN(1); - DBUG_RETURN(handler::rnd_pos_by_record(record)); + int err= m_file[m_last_part]->rnd_pos_by_record(record); + DBUG_RETURN(err); } @@ -4543,7 +5115,7 @@ bool ha_partition::init_record_priority_queue() if (!m_ordered_rec_buffer) { uint alloc_len; - uint used_parts= bitmap_bits_set(&m_part_info->used_partitions); + uint used_parts= bitmap_bits_set(&m_part_info->read_partitions); /* Allocate record buffer for each used partition. */ m_priority_queue_rec_len= m_rec_length + PARTITION_BYTES_IN_POS; if (!m_using_extended_keys) @@ -4563,16 +5135,15 @@ bool ha_partition::init_record_priority_queue() setting up the scan. */ char *ptr= (char*) m_ordered_rec_buffer; - uint16 i= 0; - do + uint i; + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if (bitmap_is_set(&m_part_info->used_partitions, i)) - { - DBUG_PRINT("info", ("init rec-buf for part %u", i)); - int2store(ptr, i); - ptr+= m_priority_queue_rec_len; - } - } while (++i < m_tot_parts); + DBUG_PRINT("info", ("init rec-buf for part %u", i)); + int2store(ptr, i); + ptr+= m_priority_queue_rec_len; + } m_start_key.key= (const uchar*)ptr; /* Initialize priority queue, initialized to reading forward. */ @@ -4636,7 +5207,7 @@ void ha_partition::destroy_record_priority_queue() int ha_partition::index_init(uint inx, bool sorted) { int error= 0; - handler **file; + uint i; DBUG_ENTER("ha_partition::index_init"); DBUG_PRINT("info", ("inx %u sorted %u", inx, sorted)); @@ -4673,7 +5244,7 @@ int ha_partition::index_init(uint inx, bool sorted) calculate the partition id to place updated and deleted records. But this is required for operations that may need to change data only. */ - if (m_lock_type == F_WRLCK) + if (get_lock_type() == F_WRLCK) bitmap_union(table->read_set, &m_part_info->full_part_field_set); if (sorted) { @@ -4689,25 +5260,40 @@ int ha_partition::index_init(uint inx, bool sorted) TODO: handle COUNT(*) queries via unordered scan. */ - uint i; KEY **key_info= m_curr_key_info; do { - for (i= 0; i < (*key_info)->key_parts; i++) + for (i= 0; i < (*key_info)->user_defined_key_parts; i++) bitmap_set_bit(table->read_set, (*key_info)->key_part[i].field->field_index); } while (*(++key_info)); } - file= m_file; - do + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if (bitmap_is_set(&(m_part_info->used_partitions), (file - m_file))) - if ((error= (*file)->ha_index_init(inx, sorted))) - { - DBUG_ASSERT(0); // Should never happen - break; - } - } while (*(++file)); + if ((error= m_file[i]->ha_index_init(inx, sorted))) + goto err; + + DBUG_EXECUTE_IF("ha_partition_fail_index_init", { + i++; + error= HA_ERR_NO_PARTITION_FOUND; + goto err; + }); + } +err: + if (error) + { + /* End the previously initialized indexes. */ + uint j; + for (j= bitmap_get_first_set(&m_part_info->read_partitions); + j < i; + j= bitmap_get_next_set(&m_part_info->read_partitions, j)) + { + (void) m_file[j]->ha_index_end(); + } + destroy_record_priority_queue(); + } DBUG_RETURN(error); } @@ -4730,19 +5316,19 @@ int ha_partition::index_init(uint inx, bool sorted) int ha_partition::index_end() { int error= 0; - handler **file; + uint i; DBUG_ENTER("ha_partition::index_end"); active_index= MAX_KEY; m_part_spec.start_part= NO_CURRENT_PART_ID; - file= m_file; - do + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { int tmp; - if (bitmap_is_set(&(m_part_info->used_partitions), (file - m_file))) - if ((tmp= (*file)->ha_index_end())) - error= tmp; - } while (*(++file)); + if ((tmp= m_file[i]->ha_index_end())) + error= tmp; + } destroy_record_priority_queue(); DBUG_RETURN(error); } @@ -5049,17 +5635,20 @@ int ha_partition::index_read_idx_map(uchar *buf, uint index, or no matching partitions (start_part > end_part) */ DBUG_ASSERT(m_part_spec.start_part >= m_part_spec.end_part); - - for (part= m_part_spec.start_part; part <= m_part_spec.end_part; part++) + /* The start part is must be marked as used. */ + DBUG_ASSERT(m_part_spec.start_part > m_part_spec.end_part || + bitmap_is_set(&(m_part_info->read_partitions), + m_part_spec.start_part)); + + for (part= m_part_spec.start_part; + part <= m_part_spec.end_part; + part= bitmap_get_next_set(&m_part_info->read_partitions, part)) { - if (bitmap_is_set(&(m_part_info->used_partitions), part)) - { - error= m_file[part]->index_read_idx_map(buf, index, key, - keypart_map, find_flag); - if (error != HA_ERR_KEY_NOT_FOUND && - error != HA_ERR_END_OF_FILE) - break; - } + error= m_file[part]->ha_index_read_idx_map(buf, index, key, + keypart_map, find_flag); + if (error != HA_ERR_KEY_NOT_FOUND && + error != HA_ERR_END_OF_FILE) + break; } if (part <= m_part_spec.end_part) m_last_part= part; @@ -5201,15 +5790,7 @@ int ha_partition::read_range_first(const key_range *start_key, m_ordered= sorted; eq_range= eq_range_arg; - end_range= 0; - if (end_key) - { - end_range= &save_end_range; - save_end_range= *end_key; - key_compare_result_on_equal= - ((end_key->flag == HA_READ_BEFORE_KEY) ? 1 : - (end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0); - } + set_end_range(end_key); range_key_part= m_curr_key_info[0]->key_part; if (start_key) @@ -5218,7 +5799,7 @@ int ha_partition::read_range_first(const key_range *start_key, m_start_key.key= NULL; m_index_scan_type= partition_read_range; - error= common_index_read(m_rec0, test(start_key)); + error= common_index_read(m_rec0, MY_TEST(start_key)); DBUG_RETURN(error); } @@ -5290,7 +5871,6 @@ int ha_partition::partition_scan_set_up(uchar * buf, bool idx_read_flag) key not found. */ DBUG_PRINT("info", ("scan with no partition to scan")); - table->status= STATUS_NOT_FOUND; DBUG_RETURN(HA_ERR_END_OF_FILE); } if (m_part_spec.start_part == m_part_spec.end_part) @@ -5311,11 +5891,10 @@ int ha_partition::partition_scan_set_up(uchar * buf, bool idx_read_flag) Verify this, also bitmap must have at least one bit set otherwise the result from this table is the empty set. */ - uint start_part= bitmap_get_first_set(&(m_part_info->used_partitions)); + uint start_part= bitmap_get_first_set(&(m_part_info->read_partitions)); if (start_part == MY_BIT_NONE) { DBUG_PRINT("info", ("scan with no partition to scan")); - table->status= STATUS_NOT_FOUND; DBUG_RETURN(HA_ERR_END_OF_FILE); } if (start_part > m_part_spec.start_part) @@ -5428,18 +6007,21 @@ int ha_partition::handle_unordered_next(uchar *buf, bool is_next_same) int ha_partition::handle_unordered_scan_next_partition(uchar * buf) { - uint i; + uint i= m_part_spec.start_part; int saved_error= HA_ERR_END_OF_FILE; DBUG_ENTER("ha_partition::handle_unordered_scan_next_partition"); - for (i= m_part_spec.start_part; i <= m_part_spec.end_part; i++) + if (i) + i= bitmap_get_next_set(&m_part_info->read_partitions, i - 1); + else + i= bitmap_get_first_set(&m_part_info->read_partitions); + + for (; + i <= m_part_spec.end_part; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { int error; - handler *file; - - if (!(bitmap_is_set(&(m_part_info->used_partitions), i))) - continue; - file= m_file[i]; + handler *file= m_file[i]; m_part_spec.start_part= i; switch (m_index_scan_type) { case partition_read_range: @@ -5538,6 +6120,8 @@ int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order) } m_top_entry= NO_CURRENT_PART_ID; queue_remove_all(&m_queue); + DBUG_ASSERT(bitmap_is_set(&m_part_info->read_partitions, + m_part_spec.start_part)); /* Position part_rec_buf_ptr to point to the first used partition >= @@ -5545,18 +6129,18 @@ int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order) but is before start_part. These partitions has allocated record buffers but is dynamically pruned, so those buffers must be skipped. */ - uint first_used_part= bitmap_get_first_set(&m_part_info->used_partitions); - for (; first_used_part < m_part_spec.start_part; first_used_part++) + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_part_spec.start_part; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if (bitmap_is_set(&(m_part_info->used_partitions), first_used_part)) - part_rec_buf_ptr+= m_priority_queue_rec_len; + part_rec_buf_ptr+= m_priority_queue_rec_len; } DBUG_PRINT("info", ("m_part_spec.start_part %u first_used_part %u", - m_part_spec.start_part, first_used_part)); - for (i= first_used_part; i <= m_part_spec.end_part; i++) + m_part_spec.start_part, i)); + for (/* continue from above */ ; + i <= m_part_spec.end_part; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if (!(bitmap_is_set(&(m_part_info->used_partitions), i))) - continue; DBUG_PRINT("info", ("reading from part %u (scan_type: %u)", i, m_index_scan_type)); DBUG_ASSERT(i == uint2korr(part_rec_buf_ptr)); @@ -5564,12 +6148,6 @@ int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order) int error; handler *file= m_file[i]; - /* - Reset null bits (to avoid valgrind warnings) and to give a default - value for not read null fields. - */ - bfill(rec_buf_ptr, table->s->null_bytes, 255); - switch (m_index_scan_type) { case partition_index_read: error= file->ha_index_read_map(rec_buf_ptr, @@ -5690,11 +6268,10 @@ int ha_partition::handle_ordered_index_scan_key_not_found() Loop over all used partitions to get the correct offset into m_ordered_rec_buffer. */ - for (i= 0; i < m_tot_parts; i++) + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if (!bitmap_is_set(&m_part_info->used_partitions, i)) - continue; - if (bitmap_is_set(&m_key_not_found_partitions, i)) { /* @@ -5702,7 +6279,7 @@ int ha_partition::handle_ordered_index_scan_key_not_found() in index_read_map. */ curr_rec_buf= part_buf + PARTITION_BYTES_IN_POS; - error= m_file[i]->index_next(curr_rec_buf); + error= m_file[i]->ha_index_next(curr_rec_buf); /* HA_ERR_KEY_NOT_FOUND is not allowed from index_next! */ DBUG_ASSERT(error != HA_ERR_KEY_NOT_FOUND); if (!error) @@ -5968,27 +6545,36 @@ int ha_partition::info(uint flag) uint extra_var_flag= flag & HA_STATUS_VARIABLE_EXTRA; DBUG_ENTER("ha_partition::info"); +#ifndef DBUG_OFF + if (bitmap_is_set_all(&(m_part_info->read_partitions))) + DBUG_PRINT("info", ("All partitions are used")); +#endif /* DBUG_OFF */ if (flag & HA_STATUS_AUTO) { bool auto_inc_is_first_in_idx= (table_share->next_number_keypart == 0); DBUG_PRINT("info", ("HA_STATUS_AUTO")); if (!table->found_next_number_field) stats.auto_increment_value= 0; - else if (table_share->ha_part_data->auto_inc_initialized) + else if (part_share->auto_inc_initialized) { lock_auto_increment(); - stats.auto_increment_value= table_share->ha_part_data->next_auto_inc_val; + stats.auto_increment_value= part_share->next_auto_inc_val; unlock_auto_increment(); } else { lock_auto_increment(); /* to avoid two concurrent initializations, check again when locked */ - if (table_share->ha_part_data->auto_inc_initialized) - stats.auto_increment_value= - table_share->ha_part_data->next_auto_inc_val; + if (part_share->auto_inc_initialized) + stats.auto_increment_value= part_share->next_auto_inc_val; else { + /* + The auto-inc mutex in the table_share is locked, so we do not need + to have the handlers locked. + HA_STATUS_NO_LOCK is not checked, since we cannot skip locking + the mutex, because it is initialized. + */ handler *file, **file_array; ulonglong auto_increment_value= 0; file_array= m_file; @@ -6006,11 +6592,11 @@ int ha_partition::info(uint flag) stats.auto_increment_value= auto_increment_value; if (auto_inc_is_first_in_idx) { - set_if_bigger(table_share->ha_part_data->next_auto_inc_val, + set_if_bigger(part_share->next_auto_inc_val, auto_increment_value); - table_share->ha_part_data->auto_inc_initialized= TRUE; + part_share->auto_inc_initialized= true; DBUG_PRINT("info", ("initializing next_auto_inc_val to %lu", - (ulong) table_share->ha_part_data->next_auto_inc_val)); + (ulong) part_share->next_auto_inc_val)); } } unlock_auto_increment(); @@ -6018,6 +6604,7 @@ int ha_partition::info(uint flag) } if (flag & HA_STATUS_VARIABLE) { + uint i; DBUG_PRINT("info", ("HA_STATUS_VARIABLE")); /* Calculates statistical variables @@ -6038,29 +6625,27 @@ int ha_partition::info(uint flag) check_time: Time of last check (only applicable to MyISAM) We report last time of all underlying handlers */ - handler *file, **file_array; + handler *file; stats.records= 0; stats.deleted= 0; stats.data_file_length= 0; stats.index_file_length= 0; stats.check_time= 0; stats.delete_length= 0; - file_array= m_file; - do + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - if (bitmap_is_set(&(m_part_info->used_partitions), (file_array - m_file))) - { - file= *file_array; - file->info(HA_STATUS_VARIABLE | no_lock_flag | extra_var_flag); - stats.records+= file->stats.records; - stats.deleted+= file->stats.deleted; - stats.data_file_length+= file->stats.data_file_length; - stats.index_file_length+= file->stats.index_file_length; - stats.delete_length+= file->stats.delete_length; - if (file->stats.check_time > stats.check_time) - stats.check_time= file->stats.check_time; - } - } while (*(++file_array)); + file= m_file[i]; + file->info(HA_STATUS_VARIABLE | no_lock_flag | extra_var_flag); + stats.records+= file->stats.records; + stats.deleted+= file->stats.deleted; + stats.data_file_length+= file->stats.data_file_length; + stats.index_file_length+= file->stats.index_file_length; + stats.delete_length+= file->stats.delete_length; + if (file->stats.check_time > stats.check_time) + stats.check_time= file->stats.check_time; + } if (stats.records && stats.records < 2 && !(m_file[0]->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)) stats.records= 2; @@ -6131,7 +6716,7 @@ int ha_partition::info(uint flag) file= *file_array; /* Get variables if not already done */ if (!(flag & HA_STATUS_VARIABLE) || - !bitmap_is_set(&(m_part_info->used_partitions), + !bitmap_is_set(&(m_part_info->read_partitions), (file_array - m_file))) file->info(HA_STATUS_VARIABLE | no_lock_flag | extra_var_flag); if (file->stats.records > max_records) @@ -6198,6 +6783,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_STATS *stat_info, uint part_id) { handler *file= m_file[part_id]; + DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), part_id)); file->info(HA_STATUS_TIME | HA_STATUS_VARIABLE | HA_STATUS_VARIABLE_EXTRA | HA_STATUS_NO_LOCK); @@ -6221,7 +6807,6 @@ void ha_partition::get_dynamic_partition_info(PARTITION_STATS *stat_info, General function to prepare handler for certain behavior. @param[in] operation operation to execute - operation Operation type for extra call @return status @retval 0 success @@ -6284,6 +6869,10 @@ void ha_partition::get_dynamic_partition_info(PARTITION_STATS *stat_info, ensure disk based tables are flushed at end of query execution. Currently is never used. + HA_EXTRA_FORCE_REOPEN: + Only used by MyISAM and Archive, called when altering table, + closing tables to enforce a reopen of the table files. + 2) Operations used by some non-MyISAM handlers ---------------------------------------------- HA_EXTRA_KEYREAD_PRESERVE_FIELDS: @@ -6408,6 +6997,9 @@ void ha_partition::get_dynamic_partition_info(PARTITION_STATS *stat_info, HA_EXTRA_PREPARE_FOR_RENAME: Informs the handler we are about to attempt a rename of the table. + For handlers that have share open files (MyISAM key-file and + Archive writer) they must close the files before rename is possible + on Windows. HA_EXTRA_READCHECK: HA_EXTRA_NO_READCHECK: @@ -6428,10 +7020,6 @@ void ha_partition::get_dynamic_partition_info(PARTITION_STATS *stat_info, HA_EXTRA_NO_READCHECK=5 No readcheck on update HA_EXTRA_READCHECK=6 Use readcheck (def) - HA_EXTRA_FORCE_REOPEN: - Only used by MyISAM, called when altering table, closing tables to - enforce a reopen of the table files. - 4) Operations only used by temporary tables for query processing ---------------------------------------------------------------- HA_EXTRA_RESET_STATE: @@ -6540,6 +7128,10 @@ int ha_partition::extra(enum ha_extra_function operation) case HA_EXTRA_FLUSH: case HA_EXTRA_PREPARE_FOR_FORCED_CLOSE: DBUG_RETURN(loop_extra(operation)); + case HA_EXTRA_PREPARE_FOR_RENAME: + case HA_EXTRA_FORCE_REOPEN: + DBUG_RETURN(loop_extra_alter(operation)); + break; /* Category 2), used by non-MyISAM handlers */ case HA_EXTRA_IGNORE_DUP_KEY: @@ -6552,9 +7144,6 @@ int ha_partition::extra(enum ha_extra_function operation) } /* Category 3), used by MyISAM handlers */ - case HA_EXTRA_PREPARE_FOR_RENAME: - DBUG_RETURN(prepare_for_rename()); - break; case HA_EXTRA_PREPARE_FOR_UPDATE: /* Needs to be run on the first partition in the range now, and @@ -6571,7 +7160,6 @@ int ha_partition::extra(enum ha_extra_function operation) break; case HA_EXTRA_NORMAL: case HA_EXTRA_QUICK: - case HA_EXTRA_FORCE_REOPEN: case HA_EXTRA_PREPARE_FOR_DROP: case HA_EXTRA_FLUSH_CACHE: { @@ -6676,34 +7264,35 @@ int ha_partition::extra(enum ha_extra_function operation) } -/* +/** Special extra call to reset extra parameters - SYNOPSIS - reset() - - RETURN VALUE - >0 Error code - 0 Success + @return Operation status. + @retval >0 Error code + @retval 0 Success - DESCRIPTION - Called at end of each statement to reset buffers + @note Called at end of each statement to reset buffers. + To avoid excessive calls, the m_partitions_to_reset bitmap keep records + of which partitions that have been used in extra(), external_lock() or + start_stmt() and is needed to be called. */ int ha_partition::reset(void) { - int result= 0, tmp; - handler **file; + int result= 0; + int tmp; + uint i; DBUG_ENTER("ha_partition::reset"); - if (m_part_info) - bitmap_set_all(&m_part_info->used_partitions); - m_extra_prepare_for_update= FALSE; - file= m_file; - do + + for (i= bitmap_get_first_set(&m_partitions_to_reset); + i < m_tot_parts; + i= bitmap_get_next_set(&m_partitions_to_reset, i)) { - if ((tmp= (*file)->ha_reset())) + if ((tmp= m_file[i]->ha_reset())) result= tmp; - } while (*(++file)); + } + bitmap_clear_all(&m_partitions_to_reset); + m_extra_prepare_for_update= FALSE; DBUG_RETURN(result); } @@ -6750,41 +7339,48 @@ void ha_partition::prepare_extra_cache(uint cachesize) m_extra_cache_size= cachesize; if (m_part_spec.start_part != NO_CURRENT_PART_ID) { + DBUG_ASSERT(bitmap_is_set(&m_partitions_to_reset, + m_part_spec.start_part)); + bitmap_set_bit(&m_partitions_to_reset, m_part_spec.start_part); late_extra_cache(m_part_spec.start_part); } DBUG_VOID_RETURN; } -/* - Prepares our new and reorged handlers for rename or delete +/** + Prepares our new and reorged handlers for rename or delete. - SYNOPSIS - prepare_for_delete() + @param operation Operation to forward - RETURN VALUE - >0 Error code - 0 Success + @return Operation status + @retval 0 Success + @retval !0 Error */ -int ha_partition::prepare_for_rename() +int ha_partition::loop_extra_alter(enum ha_extra_function operation) { int result= 0, tmp; handler **file; - DBUG_ENTER("ha_partition::prepare_for_rename()"); - + DBUG_ENTER("ha_partition::loop_extra_alter()"); + DBUG_ASSERT(operation == HA_EXTRA_PREPARE_FOR_RENAME || + operation == HA_EXTRA_FORCE_REOPEN); + if (m_new_file != NULL) { for (file= m_new_file; *file; file++) - if ((tmp= (*file)->extra(HA_EXTRA_PREPARE_FOR_RENAME))) - result= tmp; + if ((tmp= (*file)->extra(operation))) + result= tmp; + } + if (m_reorged_file != NULL) + { for (file= m_reorged_file; *file; file++) - if ((tmp= (*file)->extra(HA_EXTRA_PREPARE_FOR_RENAME))) - result= tmp; - DBUG_RETURN(result); + if ((tmp= (*file)->extra(operation))) + result= tmp; } - - DBUG_RETURN(loop_extra(HA_EXTRA_PREPARE_FOR_RENAME)); + if ((tmp= loop_extra(operation))) + result= tmp; + DBUG_RETURN(result); } /* @@ -6802,20 +7398,18 @@ int ha_partition::prepare_for_rename() int ha_partition::loop_extra(enum ha_extra_function operation) { int result= 0, tmp; - handler **file; - bool is_select; + uint i; DBUG_ENTER("ha_partition::loop_extra()"); - is_select= (thd_sql_command(ha_thd()) == SQLCOM_SELECT); - for (file= m_file; *file; file++) + for (i= bitmap_get_first_set(&m_part_info->lock_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->lock_partitions, i)) { - if (!is_select || - bitmap_is_set(&(m_part_info->used_partitions), file - m_file)) - { - if ((tmp= (*file)->extra(operation))) - result= tmp; - } + if ((tmp= m_file[i]->extra(operation))) + result= tmp; } + /* Add all used partitions to be called in reset(). */ + bitmap_union(&m_partitions_to_reset, &m_part_info->lock_partitions); DBUG_RETURN(result); } @@ -6889,20 +7483,18 @@ void ha_partition::late_extra_no_cache(uint partition_id) MODULE optimiser support ****************************************************************************/ -/* - Get keys to use for scanning +/** + Get keys to use for scanning. - SYNOPSIS - keys_to_use_for_scanning() + @return key_map of keys usable for scanning - RETURN VALUE - key_map of keys usable for scanning + @note No need to use read_partitions here, since it does not depend on + which partitions is used, only which storage engine used. */ const key_map *ha_partition::keys_to_use_for_scanning() { DBUG_ENTER("ha_partition::keys_to_use_for_scanning"); - DBUG_RETURN(m_file[0]->keys_to_use_for_scanning()); } @@ -6916,7 +7508,7 @@ ha_rows ha_partition::min_rows_for_estimate() uint i, max_used_partitions, tot_used_partitions; DBUG_ENTER("ha_partition::min_rows_for_estimate"); - tot_used_partitions= bitmap_bits_set(&m_part_info->used_partitions); + tot_used_partitions= bitmap_bits_set(&m_part_info->read_partitions); /* All partitions might have been left as unused during partition pruning @@ -6979,7 +7571,7 @@ uint ha_partition::get_biggest_used_partition(uint *part_index) while ((*part_index) < m_tot_parts) { part_id= m_part_ids_sorted_by_num_of_records[(*part_index)++]; - if (bitmap_is_set(&m_part_info->used_partitions, part_id)) + if (bitmap_is_set(&m_part_info->read_partitions, part_id)) return part_id; } return NO_CURRENT_PART_ID; @@ -6999,12 +7591,13 @@ uint ha_partition::get_biggest_used_partition(uint *part_index) double ha_partition::scan_time() { double scan_time= 0; - handler **file; + uint i; DBUG_ENTER("ha_partition::scan_time"); - for (file= m_file; *file; file++) - if (bitmap_is_set(&(m_part_info->used_partitions), (file - m_file))) - scan_time+= (*file)->scan_time(); + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) + scan_time+= m_file[i]->scan_time(); DBUG_RETURN(scan_time); } @@ -7090,7 +7683,7 @@ ha_rows ha_partition::estimate_rows_upper_bound() do { - if (bitmap_is_set(&(m_part_info->used_partitions), (file - m_file))) + if (bitmap_is_set(&(m_part_info->read_partitions), (file - m_file))) { rows= (*file)->estimate_rows_upper_bound(); if (rows == HA_POS_ERROR) @@ -7132,27 +7725,24 @@ double ha_partition::read_time(uint index, uint ranges, ha_rows rows) /** Number of rows in table. see handler.h - SYNOPSIS - records() - - RETURN VALUE - Number of total rows in a partitioned table. + @return Number of records in the table (after pruning!) */ ha_rows ha_partition::records() { ha_rows rows, tot_rows= 0; - handler **file; + uint i; DBUG_ENTER("ha_partition::records"); - file= m_file; - do + for (i= bitmap_get_first_set(&m_part_info->read_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->read_partitions, i)) { - rows= (*file)->records(); + rows= m_file[i]->records(); if (rows == HA_POS_ERROR) DBUG_RETURN(HA_POS_ERROR); tot_rows+= rows; - } while (*(++file)); + } DBUG_RETURN(tot_rows); } @@ -7203,37 +7793,161 @@ uint8 ha_partition::table_cache_type() } +/** + Calculate hash value for KEY partitioning using an array of fields. + + @param field_array An array of the fields in KEY partitioning + + @return hash_value calculated + + @note Uses the hash function on the character set of the field. + Integer and floating point fields use the binary character set by default. +*/ + +uint32 ha_partition::calculate_key_hash_value(Field **field_array) +{ + ulong nr1= 1; + ulong nr2= 4; + bool use_51_hash; + use_51_hash= MY_TEST((*field_array)->table->part_info->key_algorithm == + partition_info::KEY_ALGORITHM_51); + + do + { + Field *field= *field_array; + if (use_51_hash) + { + switch (field->real_type()) { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + { + if (field->is_null()) + { + nr1^= (nr1 << 1) | 1; + continue; + } + /* Force this to my_hash_sort_bin, which was used in 5.1! */ + uint len= field->pack_length(); + my_charset_bin.coll->hash_sort(&my_charset_bin, field->ptr, len, + &nr1, &nr2); + /* Done with this field, continue with next one. */ + continue; + } + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_BIT: + /* Not affected, same in 5.1 and 5.5 */ + break; + /* + ENUM/SET uses my_hash_sort_simple in 5.1 (i.e. my_charset_latin1) + and my_hash_sort_bin in 5.5! + */ + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + { + if (field->is_null()) + { + nr1^= (nr1 << 1) | 1; + continue; + } + /* Force this to my_hash_sort_bin, which was used in 5.1! */ + uint len= field->pack_length(); + my_charset_latin1.coll->hash_sort(&my_charset_latin1, field->ptr, + len, &nr1, &nr2); + continue; + } + /* New types in mysql-5.6. */ + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_TIMESTAMP2: + /* Not affected, 5.6+ only! */ + break; + + /* These types should not be allowed for partitioning! */ + case MYSQL_TYPE_NULL: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_GEOMETRY: + /* fall through */ + default: + DBUG_ASSERT(0); // New type? + /* Fall through for default hashing (5.5). */ + } + /* fall through, use collation based hashing. */ + } + field->hash(&nr1, &nr2); + } while (*(++field_array)); + return (uint32) nr1; +} + + /**************************************************************************** MODULE print messages ****************************************************************************/ const char *ha_partition::index_type(uint inx) { + uint first_used_partition; DBUG_ENTER("ha_partition::index_type"); - DBUG_RETURN(m_file[0]->index_type(inx)); + first_used_partition= bitmap_get_first_set(&(m_part_info->read_partitions)); + + if (first_used_partition == MY_BIT_NONE) + { + DBUG_ASSERT(0); // How can this happen? + DBUG_RETURN(handler::index_type(inx)); + } + + DBUG_RETURN(m_file[first_used_partition]->index_type(inx)); } enum row_type ha_partition::get_row_type() const { - handler **file; - enum row_type type= (*m_file)->get_row_type(); + uint i; + enum row_type type; + DBUG_ENTER("ha_partition::get_row_type"); - for (file= m_file, file++; *file; file++) + i= bitmap_get_first_set(&m_part_info->read_partitions); + DBUG_ASSERT(i < m_tot_parts); + if (i >= m_tot_parts) + DBUG_RETURN(ROW_TYPE_NOT_USED); + + type= m_file[i]->get_row_type(); + DBUG_PRINT("info", ("partition %u, row_type: %d", i, type)); + + for (i= bitmap_get_next_set(&m_part_info->lock_partitions, i); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->lock_partitions, i)) { - enum row_type part_type= (*file)->get_row_type(); + enum row_type part_type= m_file[i]->get_row_type(); + DBUG_PRINT("info", ("partition %u, row_type: %d", i, type)); if (part_type != type) - return ROW_TYPE_NOT_USED; + DBUG_RETURN(ROW_TYPE_NOT_USED); } - return type; + DBUG_RETURN(type); } void ha_partition::append_row_to_str(String &str) { - Field **field_ptr; const uchar *rec; bool is_rec0= !m_err_rec || m_err_rec == table->record[0]; if (is_rec0) @@ -7245,7 +7959,7 @@ void ha_partition::append_row_to_str(String &str) { KEY *key= table->key_info + table->s->primary_key; KEY_PART_INFO *key_part= key->key_part; - KEY_PART_INFO *key_part_end= key_part + key->key_parts; + KEY_PART_INFO *key_part_end= key_part + key->user_defined_key_parts; if (!is_rec0) set_key_field_ptr(key, rec, table->record[0]); for (; key_part != key_part_end; key_part++) @@ -7261,6 +7975,7 @@ void ha_partition::append_row_to_str(String &str) } else { + Field **field_ptr; if (!is_rec0) set_field_ptr(m_part_info->full_part_field_array, rec, table->record[0]); @@ -7290,59 +8005,55 @@ void ha_partition::print_error(int error, myf errflag) /* Should probably look for my own errors first */ DBUG_PRINT("enter", ("error: %d", error)); - if (error == HA_ERR_NO_PARTITION_FOUND) + if ((error == HA_ERR_NO_PARTITION_FOUND) && + ! (thd->lex->alter_info.flags & Alter_info::ALTER_TRUNCATE_PARTITION)) { - switch(thd_sql_command(thd)) + m_part_info->print_no_partition_found(table, errflag); + DBUG_VOID_RETURN; + } + else if (error == HA_ERR_ROW_IN_WRONG_PARTITION) + { + /* Should only happen on DELETE or UPDATE! */ + DBUG_ASSERT(thd_sql_command(thd) == SQLCOM_DELETE || + thd_sql_command(thd) == SQLCOM_DELETE_MULTI || + thd_sql_command(thd) == SQLCOM_UPDATE || + thd_sql_command(thd) == SQLCOM_UPDATE_MULTI); + DBUG_ASSERT(m_err_rec); + if (m_err_rec) { - case SQLCOM_DELETE: - case SQLCOM_DELETE_MULTI: - case SQLCOM_UPDATE: - case SQLCOM_UPDATE_MULTI: - if (m_err_rec) - { - uint max_length; - char buf[MAX_KEY_LENGTH]; - const char *msg= "Found a row in wrong partition ("; - String str(buf,sizeof(buf),system_charset_info); - uint32 part_id; - /* Should only happen on DELETE or UPDATE! */ - str.length(0); - str.append_ulonglong(m_last_part); - str.append(" != "); - if (!get_part_for_delete(m_err_rec, m_rec0, m_part_info, &part_id)) - { - str.append_ulonglong(part_id); - } - str.append(")"); - append_row_to_str(str); - /* Log this error, so the DBA can notice it and fix it! */ - sql_print_error("Table '%-192s' corrupted: %s%s\n" - "Please CHECK and REPAIR the table!", - table->s->table_name.str, msg, str.c_ptr_safe()); - - max_length= (MYSQL_ERRMSG_SIZE- - (uint) strlen(msg)); - if (str.length() >= max_length) - { - str.length(max_length-4); - str.append(STRING_WITH_LEN("...")); - } - my_printf_error(ER_NO_PARTITION_FOR_GIVEN_VALUE, "%s%s", MYF(0), - msg, str.c_ptr_safe()); - m_err_rec= NULL; - DBUG_VOID_RETURN; - } - /* fall through */ - default: + uint max_length; + char buf[MAX_KEY_LENGTH]; + String str(buf,sizeof(buf),system_charset_info); + uint32 part_id; + str.length(0); + str.append("("); + str.append_ulonglong(m_last_part); + str.append(" != "); + if (get_part_for_delete(m_err_rec, m_rec0, m_part_info, &part_id)) + str.append("?"); + else + str.append_ulonglong(part_id); + str.append(")"); + append_row_to_str(str); + + /* Log this error, so the DBA can notice it and fix it! */ + sql_print_error("Table '%-192s' corrupted: row in wrong partition: %s\n" + "Please REPAIR the table!", + table->s->table_name.str, + str.c_ptr_safe()); + + max_length= (MYSQL_ERRMSG_SIZE - + (uint) strlen(ER_THD(thd, ER_ROW_IN_WRONG_PARTITION))); + if (str.length() >= max_length) { - if (!(thd->lex->alter_info.flags & ALTER_TRUNCATE_PARTITION)) - { - m_part_info->print_no_partition_found(table, errflag); - DBUG_VOID_RETURN; - } + str.length(max_length-4); + str.append(STRING_WITH_LEN("...")); } - /* fall through to generic error handling. */ + my_error(ER_ROW_IN_WRONG_PARTITION, MYF(0), str.c_ptr_safe()); + m_err_rec= NULL; + DBUG_VOID_RETURN; } + /* fall through to generic error handling. */ } /* In case m_file has not been initialized, like in bug#42438 */ @@ -7376,49 +8087,48 @@ bool ha_partition::get_error_message(int error, String *buf) /**************************************************************************** - MODULE handler characteristics + MODULE in-place ALTER ****************************************************************************/ /** + Get table flags. +*/ + +handler::Table_flags ha_partition::table_flags() const +{ + uint first_used_partition= 0; + DBUG_ENTER("ha_partition::table_flags"); + if (m_handler_status < handler_initialized || + m_handler_status >= handler_closed) + DBUG_RETURN(PARTITION_ENABLED_TABLE_FLAGS); + + if (get_lock_type() != F_UNLCK) + { + /* + The flags are cached after external_lock, and may depend on isolation + level. So we should use a locked partition to get the correct flags. + */ + first_used_partition= bitmap_get_first_set(&m_part_info->lock_partitions); + if (first_used_partition == MY_BIT_NONE) + first_used_partition= 0; + } + DBUG_RETURN((m_file[first_used_partition]->ha_table_flags() & + ~(PARTITION_DISABLED_TABLE_FLAGS)) | + (PARTITION_ENABLED_TABLE_FLAGS)); +} + + +/** alter_table_flags must be on handler/table level, not on hton level due to the ha_partition hton does not know what the underlying hton is. */ uint ha_partition::alter_table_flags(uint flags) { - uint flags_to_return, flags_to_check; + uint flags_to_return; DBUG_ENTER("ha_partition::alter_table_flags"); flags_to_return= ht->alter_table_flags(flags); - flags_to_return|= m_file[0]->alter_table_flags(flags); + flags_to_return|= m_file[0]->alter_table_flags(flags); - /* - If one partition fails we must be able to revert the change for the other, - already altered, partitions. So both ADD and DROP can only be supported in - pairs. - */ - flags_to_check= HA_INPLACE_ADD_INDEX_NO_READ_WRITE; - flags_to_check|= HA_INPLACE_DROP_INDEX_NO_READ_WRITE; - if ((flags_to_return & flags_to_check) != flags_to_check) - flags_to_return&= ~flags_to_check; - flags_to_check= HA_INPLACE_ADD_UNIQUE_INDEX_NO_READ_WRITE; - flags_to_check|= HA_INPLACE_DROP_UNIQUE_INDEX_NO_READ_WRITE; - if ((flags_to_return & flags_to_check) != flags_to_check) - flags_to_return&= ~flags_to_check; - flags_to_check= HA_INPLACE_ADD_PK_INDEX_NO_READ_WRITE; - flags_to_check|= HA_INPLACE_DROP_PK_INDEX_NO_READ_WRITE; - if ((flags_to_return & flags_to_check) != flags_to_check) - flags_to_return&= ~flags_to_check; - flags_to_check= HA_INPLACE_ADD_INDEX_NO_WRITE; - flags_to_check|= HA_INPLACE_DROP_INDEX_NO_WRITE; - if ((flags_to_return & flags_to_check) != flags_to_check) - flags_to_return&= ~flags_to_check; - flags_to_check= HA_INPLACE_ADD_UNIQUE_INDEX_NO_WRITE; - flags_to_check|= HA_INPLACE_DROP_UNIQUE_INDEX_NO_WRITE; - if ((flags_to_return & flags_to_check) != flags_to_check) - flags_to_return&= ~flags_to_check; - flags_to_check= HA_INPLACE_ADD_PK_INDEX_NO_WRITE; - flags_to_check|= HA_INPLACE_DROP_PK_INDEX_NO_WRITE; - if ((flags_to_return & flags_to_check) != flags_to_check) - flags_to_return&= ~flags_to_check; DBUG_RETURN(flags_to_return); } @@ -7429,262 +8139,294 @@ uint ha_partition::alter_table_flags(uint flags) bool ha_partition::check_if_incompatible_data(HA_CREATE_INFO *create_info, uint table_changes) { - handler **file; - bool ret= COMPATIBLE_DATA_YES; - /* The check for any partitioning related changes have already been done in mysql_alter_table (by fix_partition_func), so it is only up to the underlying handlers. */ - for (file= m_file; *file; file++) - if ((ret= (*file)->check_if_incompatible_data(create_info, - table_changes)) != - COMPATIBLE_DATA_YES) - break; - return ret; + List_iterator<partition_element> part_it(m_part_info->partitions); + HA_CREATE_INFO dummy_info= *create_info; + uint i=0; + while (partition_element *part_elem= part_it++) + { + if (m_is_sub_partitioned) + { + List_iterator<partition_element> subpart_it(part_elem->subpartitions); + while (partition_element *sub_elem= subpart_it++) + { + dummy_info.data_file_name= sub_elem->data_file_name; + dummy_info.index_file_name= sub_elem->index_file_name; + if (m_file[i++]->check_if_incompatible_data(&dummy_info, table_changes)) + return COMPATIBLE_DATA_NO; + } + } + else + { + dummy_info.data_file_name= part_elem->data_file_name; + dummy_info.index_file_name= part_elem->index_file_name; + if (m_file[i++]->check_if_incompatible_data(&dummy_info, table_changes)) + return COMPATIBLE_DATA_NO; + } + } + return COMPATIBLE_DATA_YES; } /** - Helper class for [final_]add_index, see handler.h + Support of in-place alter table. +*/ + +/** + Helper class for in-place alter, see handler.h */ -class ha_partition_add_index : public handler_add_index +class ha_partition_inplace_ctx : public inplace_alter_handler_ctx { public: - handler_add_index **add_array; - ha_partition_add_index(TABLE* table_arg, KEY* key_info_arg, - uint num_of_keys_arg) - : handler_add_index(table_arg, key_info_arg, num_of_keys_arg) + inplace_alter_handler_ctx **handler_ctx_array; +private: + uint m_tot_parts; + +public: + ha_partition_inplace_ctx(THD *thd, uint tot_parts) + : inplace_alter_handler_ctx(), + handler_ctx_array(NULL), + m_tot_parts(tot_parts) {} - ~ha_partition_add_index() {} + + ~ha_partition_inplace_ctx() + { + if (handler_ctx_array) + { + for (uint index= 0; index < m_tot_parts; index++) + delete handler_ctx_array[index]; + } + } }; -/** - Support of in-place add/drop index +enum_alter_inplace_result +ha_partition::check_if_supported_inplace_alter(TABLE *altered_table, + Alter_inplace_info *ha_alter_info) +{ + uint index= 0; + enum_alter_inplace_result result= HA_ALTER_INPLACE_NO_LOCK; + ha_partition_inplace_ctx *part_inplace_ctx; + bool first_is_set= false; + THD *thd= ha_thd(); + + DBUG_ENTER("ha_partition::check_if_supported_inplace_alter"); + /* + Support inplace change of KEY () -> KEY ALGORITHM = N (). + Any other change would set partition_changed in + prep_alter_part_table() in mysql_alter_table(). + */ + if (ha_alter_info->alter_info->flags == Alter_info::ALTER_PARTITION) + DBUG_RETURN(HA_ALTER_INPLACE_NO_LOCK); - @param table_arg Table to add index to - @param key_info Struct over the new keys to add - @param num_of_keys Number of keys to add - @param[out] add Data to be submitted with final_add_index + part_inplace_ctx= + new (thd->mem_root) ha_partition_inplace_ctx(thd, m_tot_parts); + if (!part_inplace_ctx) + DBUG_RETURN(HA_ALTER_ERROR); - @return Operation status - @retval 0 Success - @retval != 0 Failure (error code returned, and all operations rollbacked) -*/ + part_inplace_ctx->handler_ctx_array= (inplace_alter_handler_ctx **) + thd->alloc(sizeof(inplace_alter_handler_ctx *) * (m_tot_parts + 1)); + if (!part_inplace_ctx->handler_ctx_array) + DBUG_RETURN(HA_ALTER_ERROR); -int ha_partition::add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys, - handler_add_index **add) -{ - uint i; - int ret= 0; - THD *thd= ha_thd(); - ha_partition_add_index *part_add_index; + /* Set all to NULL, including the terminating one. */ + for (index= 0; index <= m_tot_parts; index++) + part_inplace_ctx->handler_ctx_array[index]= NULL; + + ha_alter_info->handler_flags |= Alter_inplace_info::ALTER_PARTITIONED; + for (index= 0; index < m_tot_parts; index++) + { + enum_alter_inplace_result p_result= + m_file[index]->check_if_supported_inplace_alter(altered_table, + ha_alter_info); + part_inplace_ctx->handler_ctx_array[index]= ha_alter_info->handler_ctx; + + if (index == 0) + { + first_is_set= (ha_alter_info->handler_ctx != NULL); + } + else if (first_is_set != (ha_alter_info->handler_ctx != NULL)) + { + /* Either none or all partitions must set handler_ctx! */ + DBUG_ASSERT(0); + DBUG_RETURN(HA_ALTER_ERROR); + } + if (p_result < result) + result= p_result; + if (result == HA_ALTER_ERROR) + break; + } - DBUG_ENTER("ha_partition::add_index"); + ha_alter_info->handler_ctx= part_inplace_ctx; /* - There has already been a check in fix_partition_func in mysql_alter_table - before this call, which checks for unique/primary key violations of the - partitioning function. So no need for extra check here. + To indicate for future inplace calls that there are several + partitions/handlers that need to be committed together, + we set group_commit_ctx to the NULL terminated array of + the partitions handlers. */ - + ha_alter_info->group_commit_ctx= part_inplace_ctx->handler_ctx_array; + + DBUG_RETURN(result); +} + + +bool ha_partition::prepare_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info) +{ + uint index= 0; + bool error= false; + ha_partition_inplace_ctx *part_inplace_ctx; + + DBUG_ENTER("ha_partition::prepare_inplace_alter_table"); + /* - This will be freed at the end of the statement. - And destroyed at final_add_index. (Sql_alloc does not free in delete). + Changing to similar partitioning, only update metadata. + Non allowed changes would be catched in prep_alter_part_table(). */ - part_add_index= new (thd->mem_root) - ha_partition_add_index(table_arg, key_info, num_of_keys); - if (!part_add_index) - DBUG_RETURN(HA_ERR_OUT_OF_MEM); - part_add_index->add_array= (handler_add_index **) - thd->alloc(sizeof(void *) * m_tot_parts); - if (!part_add_index->add_array) - { - delete part_add_index; - DBUG_RETURN(HA_ERR_OUT_OF_MEM); - } + if (ha_alter_info->alter_info->flags == Alter_info::ALTER_PARTITION) + DBUG_RETURN(false); - for (i= 0; i < m_tot_parts; i++) - { - if ((ret= m_file[i]->add_index(table_arg, key_info, num_of_keys, - &part_add_index->add_array[i]))) - goto err; - } - *add= part_add_index; - DBUG_RETURN(ret); -err: - /* Rollback all prepared partitions. i - 1 .. 0 */ - while (i) + part_inplace_ctx= + static_cast<class ha_partition_inplace_ctx*>(ha_alter_info->handler_ctx); + + for (index= 0; index < m_tot_parts && !error; index++) { - i--; - (void) m_file[i]->final_add_index(part_add_index->add_array[i], false); + ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[index]; + if (m_file[index]->ha_prepare_inplace_alter_table(altered_table, + ha_alter_info)) + error= true; + part_inplace_ctx->handler_ctx_array[index]= ha_alter_info->handler_ctx; } - delete part_add_index; - DBUG_RETURN(ret); -} + ha_alter_info->handler_ctx= part_inplace_ctx; + DBUG_RETURN(error); +} -/** - Second phase of in-place add index. - @param add Info from add_index - @param commit Should we commit or rollback the add_index operation +bool ha_partition::inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info) +{ + uint index= 0; + bool error= false; + ha_partition_inplace_ctx *part_inplace_ctx; - @return Operation status - @retval 0 Success - @retval != 0 Failure (error code returned) + DBUG_ENTER("ha_partition::inplace_alter_table"); - @note If commit is false, index changes are rolled back by dropping the - added indexes. If commit is true, nothing is done as the indexes - were already made active in ::add_index() -*/ + /* + Changing to similar partitioning, only update metadata. + Non allowed changes would be catched in prep_alter_part_table(). + */ + if (ha_alter_info->alter_info->flags == Alter_info::ALTER_PARTITION) + DBUG_RETURN(false); -int ha_partition::final_add_index(handler_add_index *add, bool commit) -{ - ha_partition_add_index *part_add_index; - uint i; - int ret= 0; + part_inplace_ctx= + static_cast<class ha_partition_inplace_ctx*>(ha_alter_info->handler_ctx); - DBUG_ENTER("ha_partition::final_add_index"); - - if (!add) + for (index= 0; index < m_tot_parts && !error; index++) { - DBUG_ASSERT(!commit); - DBUG_RETURN(0); + ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[index]; + if (m_file[index]->ha_inplace_alter_table(altered_table, + ha_alter_info)) + error= true; + part_inplace_ctx->handler_ctx_array[index]= ha_alter_info->handler_ctx; } - part_add_index= static_cast<class ha_partition_add_index*>(add); + ha_alter_info->handler_ctx= part_inplace_ctx; - for (i= 0; i < m_tot_parts; i++) - { - if ((ret= m_file[i]->final_add_index(part_add_index->add_array[i], commit))) - goto err; - DBUG_EXECUTE_IF("ha_partition_fail_final_add_index", { - /* Simulate a failure by rollback the second partition */ - if (m_tot_parts > 1) - { - i++; - m_file[i]->final_add_index(part_add_index->add_array[i], false); - /* Set an error that is specific to ha_partition. */ - ret= HA_ERR_NO_PARTITION_FOUND; - goto err; - } - }); - } - delete part_add_index; - DBUG_RETURN(ret); -err: - uint j; - uint *key_numbers= NULL; - KEY *old_key_info= NULL; - uint num_of_keys= 0; - int error; - - /* How could this happen? Needed to create a covering test case :) */ - DBUG_ASSERT(ret == HA_ERR_NO_PARTITION_FOUND); + DBUG_RETURN(error); +} - if (i > 0) - { - num_of_keys= part_add_index->num_of_keys; - key_numbers= (uint*) ha_thd()->alloc(sizeof(uint) * num_of_keys); - if (!key_numbers) - { - sql_print_error("Failed with error handling of adding index:\n" - "committing index failed, and when trying to revert " - "already committed partitions we failed allocating\n" - "memory for the index for table '%s'", - table_share->table_name.str); - DBUG_RETURN(HA_ERR_OUT_OF_MEM); - } - old_key_info= table->key_info; - /* - Use the newly added key_info as table->key_info to remove them. - Note that this requires the subhandlers to use name lookup of the - index. They must use given table->key_info[key_number], they cannot - use their local view of the keys, since table->key_info only include - the indexes to be removed here. - */ - for (j= 0; j < num_of_keys; j++) - key_numbers[j]= j; - table->key_info= part_add_index->key_info; - } - for (j= 0; j < m_tot_parts; j++) +/* + Note that this function will try rollback failed ADD INDEX by + executing DROP INDEX for the indexes that were committed (if any) + before the error occurred. This means that the underlying storage + engine must be able to drop index in-place with X-lock held. + (As X-lock will be held here if new indexes are to be committed) +*/ +bool ha_partition::commit_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info, + bool commit) +{ + ha_partition_inplace_ctx *part_inplace_ctx; + bool error= false; + + DBUG_ENTER("ha_partition::commit_inplace_alter_table"); + + /* + Changing to similar partitioning, only update metadata. + Non allowed changes would be catched in prep_alter_part_table(). + */ + if (ha_alter_info->alter_info->flags == Alter_info::ALTER_PARTITION) + DBUG_RETURN(false); + + part_inplace_ctx= + static_cast<class ha_partition_inplace_ctx*>(ha_alter_info->handler_ctx); + + if (commit) { - if (j < i) + DBUG_ASSERT(ha_alter_info->group_commit_ctx == + part_inplace_ctx->handler_ctx_array); + ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[0]; + error= m_file[0]->ha_commit_inplace_alter_table(altered_table, + ha_alter_info, commit); + if (error) + goto end; + if (ha_alter_info->group_commit_ctx) { - /* Remove the newly added index */ - error= m_file[j]->prepare_drop_index(table, key_numbers, num_of_keys); - if (error || m_file[j]->final_drop_index(table)) + /* + If ha_alter_info->group_commit_ctx is not set to NULL, + then the engine did only commit the first partition! + The engine is probably new, since both innodb and the default + implementation of handler::commit_inplace_alter_table sets it to NULL + and simply return false, since it allows metadata changes only. + Loop over all other partitions as to follow the protocol! + */ + uint i; + DBUG_ASSERT(0); + for (i= 1; i < m_tot_parts; i++) { - sql_print_error("Failed with error handling of adding index:\n" - "committing index failed, and when trying to revert " - "already committed partitions we failed removing\n" - "the index for table '%s' partition nr %d", - table_share->table_name.str, j); + ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[i]; + error|= m_file[i]->ha_commit_inplace_alter_table(altered_table, + ha_alter_info, + true); } + } } - else if (j > i) + else + { + uint i; + for (i= 0; i < m_tot_parts; i++) { - /* Rollback non finished partitions */ - if (m_file[j]->final_add_index(part_add_index->add_array[j], false)) - { - /* How could this happen? */ - sql_print_error("Failed with error handling of adding index:\n" - "Rollback of add_index failed for table\n" - "'%s' partition nr %d", - table_share->table_name.str, j); + /* Rollback, commit == false, is done for each partition! */ + ha_alter_info->handler_ctx= part_inplace_ctx->handler_ctx_array[i]; + if (m_file[i]->ha_commit_inplace_alter_table(altered_table, + ha_alter_info, false)) + error= true; } } - } - if (i > 0) - table->key_info= old_key_info; - delete part_add_index; - DBUG_RETURN(ret); -} - -int ha_partition::prepare_drop_index(TABLE *table_arg, uint *key_num, - uint num_of_keys) -{ - handler **file; - int ret= 0; +end: + ha_alter_info->handler_ctx= part_inplace_ctx; - /* - DROP INDEX does not affect partitioning. - */ - for (file= m_file; *file; file++) - if ((ret= (*file)->prepare_drop_index(table_arg, key_num, num_of_keys))) - break; - return ret; + DBUG_RETURN(error); } -int ha_partition::final_drop_index(TABLE *table_arg) +void ha_partition::notify_table_changed() { handler **file; - int ret= HA_ERR_WRONG_COMMAND; - - for (file= m_file; *file; file++) - if ((ret= (*file)->final_drop_index(table_arg))) - break; - return ret; -} - -/* - If frm_error() is called then we will use this to to find out what file - extensions exist for the storage engine. This is also used by the default - rename_table and delete_table method in handler.cc. -*/ + DBUG_ENTER("ha_partition::notify_table_changed"); -static const char *ha_partition_ext[]= -{ - ha_par_ext, NullS -}; + for (file= m_file; *file; file++) + (*file)->ha_notify_table_changed(); -const char **ha_partition::bas_ext() const -{ return ha_partition_ext; } + DBUG_VOID_RETURN; +} uint ha_partition::min_of_the_max_uint( @@ -7843,8 +8585,8 @@ int ha_partition::reset_auto_increment(ulonglong value) int res; DBUG_ENTER("ha_partition::reset_auto_increment"); lock_auto_increment(); - table_share->ha_part_data->auto_inc_initialized= FALSE; - table_share->ha_part_data->next_auto_inc_val= 0; + part_share->auto_inc_initialized= false; + part_share->next_auto_inc_val= 0; do { if ((res= (*file)->ha_reset_auto_increment(value)) != 0) @@ -7858,7 +8600,7 @@ int ha_partition::reset_auto_increment(ulonglong value) /** This method is called by update_auto_increment which in turn is called by the individual handlers as part of write_row. We use the - table_share->ha_part_data->next_auto_inc_val, or search all + part_share->next_auto_inc_val, or search all partitions for the highest auto_increment_value if not initialized or if auto_increment field is a secondary part of a key, we must search every partition when holding a mutex to be sure of correctness. @@ -7892,7 +8634,7 @@ void ha_partition::get_auto_increment(ulonglong offset, ulonglong increment, /* Only nb_desired_values = 1 makes sense */ (*file)->get_auto_increment(offset, increment, 1, &first_value_part, &nb_reserved_values_part); - if (first_value_part == ~(ulonglong)(0)) // error in one partition + if (first_value_part == ULONGLONG_MAX) // error in one partition { *first_value= first_value_part; /* log that the error was between table/partition handler */ @@ -7911,9 +8653,9 @@ void ha_partition::get_auto_increment(ulonglong offset, ulonglong increment, /* This is initialized in the beginning of the first write_row call. */ - DBUG_ASSERT(table_share->ha_part_data->auto_inc_initialized); + DBUG_ASSERT(part_share->auto_inc_initialized); /* - Get a lock for handling the auto_increment in table_share->ha_part_data + Get a lock for handling the auto_increment in part_share for avoiding two concurrent statements getting the same number. */ @@ -7940,9 +8682,8 @@ void ha_partition::get_auto_increment(ulonglong offset, ulonglong increment, } /* this gets corrected (for offset/increment) in update_auto_increment */ - *first_value= table_share->ha_part_data->next_auto_inc_val; - table_share->ha_part_data->next_auto_inc_val+= - nb_desired_values * increment; + *first_value= part_share->next_auto_inc_val; + part_share->next_auto_inc_val+= nb_desired_values * increment; unlock_auto_increment(); DBUG_PRINT("info", ("*first_value: %lu", (ulong) *first_value)); @@ -7957,34 +8698,45 @@ void ha_partition::release_auto_increment() if (table->s->next_number_keypart) { - for (uint i= 0; i < m_tot_parts; i++) + uint i; + for (i= bitmap_get_first_set(&m_part_info->lock_partitions); + i < m_tot_parts; + i= bitmap_get_next_set(&m_part_info->lock_partitions, i)) + { m_file[i]->ha_release_auto_increment(); + } } - else if (next_insert_id) + else { - ulonglong next_auto_inc_val; lock_auto_increment(); - next_auto_inc_val= table_share->ha_part_data->next_auto_inc_val; - /* - If the current auto_increment values is lower than the reserved - value, and the reserved value was reserved by this thread, - we can lower the reserved value. - */ - if (next_insert_id < next_auto_inc_val && - auto_inc_interval_for_cur_row.maximum() >= next_auto_inc_val) + if (next_insert_id) { - THD *thd= ha_thd(); + ulonglong next_auto_inc_val= part_share->next_auto_inc_val; /* - Check that we do not lower the value because of a failed insert - with SET INSERT_ID, i.e. forced/non generated values. + If the current auto_increment values is lower than the reserved + value, and the reserved value was reserved by this thread, + we can lower the reserved value. */ - if (thd->auto_inc_intervals_forced.maximum() < next_insert_id) - table_share->ha_part_data->next_auto_inc_val= next_insert_id; + if (next_insert_id < next_auto_inc_val && + auto_inc_interval_for_cur_row.maximum() >= next_auto_inc_val) + { + THD *thd= ha_thd(); + /* + Check that we do not lower the value because of a failed insert + with SET INSERT_ID, i.e. forced/non generated values. + */ + if (thd->auto_inc_intervals_forced.maximum() < next_insert_id) + part_share->next_auto_inc_val= next_insert_id; + } + DBUG_PRINT("info", ("part_share->next_auto_inc_val: %lu", + (ulong) part_share->next_auto_inc_val)); } - DBUG_PRINT("info", ("table_share->ha_part_data->next_auto_inc_val: %lu", - (ulong) table_share->ha_part_data->next_auto_inc_val)); - - /* Unlock the multi row statement lock taken in get_auto_increment */ + /* + Unlock the multi-row statement lock taken in get_auto_increment. + These actions must be performed even if the next_insert_id field + contains zero, otherwise if the update_auto_increment fails then + an unnecessary lock will remain: + */ if (auto_increment_safe_stmt_log_lock) { auto_increment_safe_stmt_log_lock= FALSE; @@ -8006,6 +8758,27 @@ void ha_partition::init_table_handle_for_HANDLER() } +/** + Return the checksum of the table (all partitions) +*/ + +uint ha_partition::checksum() const +{ + ha_checksum sum= 0; + + DBUG_ENTER("ha_partition::checksum"); + if ((table_flags() & (HA_HAS_OLD_CHECKSUM | HA_HAS_NEW_CHECKSUM))) + { + handler **file= m_file; + do + { + sum+= (*file)->checksum(); + } while (*(++file)); + } + DBUG_RETURN(sum); +} + + /**************************************************************************** MODULE enable/disable indexes ****************************************************************************/ @@ -8025,6 +8798,7 @@ int ha_partition::disable_indexes(uint mode) handler **file; int error= 0; + DBUG_ASSERT(bitmap_is_set_all(&(m_part_info->lock_partitions))); for (file= m_file; *file; file++) { if ((error= (*file)->ha_disable_indexes(mode))) @@ -8049,6 +8823,7 @@ int ha_partition::enable_indexes(uint mode) handler **file; int error= 0; + DBUG_ASSERT(bitmap_is_set_all(&(m_part_info->lock_partitions))); for (file= m_file; *file; file++) { if ((error= (*file)->ha_enable_indexes(mode))) @@ -8073,6 +8848,7 @@ int ha_partition::indexes_are_disabled(void) handler **file; int error= 0; + DBUG_ASSERT(bitmap_is_set_all(&(m_part_info->lock_partitions))); for (file= m_file; *file; file++) { if ((error= (*file)->indexes_are_disabled())) @@ -8093,7 +8869,7 @@ int ha_partition::indexes_are_disabled(void) @retval != 0 Error */ -int ha_partition::check_misplaced_rows(uint read_part_id, bool repair) +int ha_partition::check_misplaced_rows(uint read_part_id, bool do_repair) { int result= 0; uint32 correct_part_id; @@ -8104,7 +8880,7 @@ int ha_partition::check_misplaced_rows(uint read_part_id, bool repair) DBUG_ASSERT(m_file); - if (repair) + if (do_repair) { /* We must read the full row, if we need to move it! */ bitmap_set_all(table->read_set); @@ -8121,7 +8897,7 @@ int ha_partition::check_misplaced_rows(uint read_part_id, bool repair) while (true) { - if ((result= m_file[read_part_id]->rnd_next(m_rec0))) + if ((result= m_file[read_part_id]->ha_rnd_next(m_rec0))) { if (result == HA_ERR_RECORD_DELETED) continue; @@ -8130,7 +8906,7 @@ int ha_partition::check_misplaced_rows(uint read_part_id, bool repair) if (num_misplaced_rows > 0) { - print_admin_msg(ha_thd(), MI_MAX_MSG_BUF, "warning", + print_admin_msg(ha_thd(), MYSQL_ERRMSG_SIZE, "warning", table_share->db.str, table->alias, opt_op_name[REPAIR_PARTS], "Moved %lld misplaced rows", @@ -8149,10 +8925,10 @@ int ha_partition::check_misplaced_rows(uint read_part_id, bool repair) if (correct_part_id != read_part_id) { num_misplaced_rows++; - if (!repair) + if (!do_repair) { /* Check. */ - print_admin_msg(ha_thd(), MI_MAX_MSG_BUF, "error", + print_admin_msg(ha_thd(), MYSQL_ERRMSG_SIZE, "error", table_share->db.str, table->alias, opt_op_name[CHECK_PARTS], "Found a misplaced row"); @@ -8200,7 +8976,7 @@ int ha_partition::check_misplaced_rows(uint read_part_id, bool repair) correct_part_id, str.c_ptr_safe()); } - print_admin_msg(ha_thd(), MI_MAX_MSG_BUF, "error", + print_admin_msg(ha_thd(), MYSQL_ERRMSG_SIZE, "error", table_share->db.str, table->alias, opt_op_name[REPAIR_PARTS], "Failed to move/insert a row" @@ -8248,7 +9024,8 @@ int ha_partition::check_misplaced_rows(uint read_part_id, bool repair) #define KEY_PARTITIONING_CHANGED_STR \ - "KEY () partitioning changed, please run:\nALTER TABLE %s.%s %s" + "KEY () partitioning changed, please run:\n" \ + "ALTER TABLE %s.%s ALGORITHM = INPLACE %s" int ha_partition::check_for_upgrade(HA_CHECK_OPT *check_opt) { @@ -8342,7 +9119,7 @@ int ha_partition::check_for_upgrade(HA_CHECK_OPT *check_opt) part_buf)) { /* Error creating admin message (too long string?). */ - print_admin_msg(thd, MI_MAX_MSG_BUF, "error", + print_admin_msg(thd, MYSQL_ERRMSG_SIZE, "error", table_share->db.str, table->alias, opt_op_name[CHECK_PARTS], KEY_PARTITIONING_CHANGED_STR, @@ -8365,6 +9142,56 @@ int ha_partition::check_for_upgrade(HA_CHECK_OPT *check_opt) } +/** + Push an engine condition to the condition stack of the storage engine + for each partition. + + @param cond Pointer to the engine condition to be pushed. + + @return NULL Underlying engine will not return rows that + do not match the passed condition. + <> NULL 'Remainder' condition that the caller must use + to filter out records. +*/ + +const COND *ha_partition::cond_push(const COND *cond) +{ + handler **file= m_file; + COND *res_cond= NULL; + DBUG_ENTER("ha_partition::cond_push"); + + do + { + if ((*file)->pushed_cond != cond) + { + if ((*file)->cond_push(cond)) + res_cond= (COND *) cond; + else + (*file)->pushed_cond= cond; + } + } while (*(++file)); + DBUG_RETURN(res_cond); +} + + +/** + Pop the top condition from the condition stack of the storage engine + for each partition. +*/ + +void ha_partition::cond_pop() +{ + handler **file= m_file; + DBUG_ENTER("ha_partition::cond_pop"); + + do + { + (*file)->cond_pop(); + } while (*(++file)); + DBUG_VOID_RETURN; +} + + struct st_mysql_storage_engine partition_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; diff --git a/sql/ha_partition.h b/sql/ha_partition.h index e8e3858d076..23d02337359 100644 --- a/sql/ha_partition.h +++ b/sql/ha_partition.h @@ -2,24 +2,21 @@ #define HA_PARTITION_INCLUDED /* - Copyright (c) 2005, 2013, Oracle and/or its affiliates. + Copyright (c) 2005, 2012, Oracle and/or its affiliates. + Copyright (c) 2009, 2013, Monty Program Ab & SkySQL Ab. - 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 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. + 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 */ - -#ifdef __GNUC__ -#pragma interface /* gcc class implementation */ -#endif + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sql_partition.h" /* part_id_range, partition_element */ #include "queues.h" /* QUEUE */ @@ -30,24 +27,95 @@ enum partition_keywords PKW_COLUMNS, PKW_ALGORITHM }; + #define PARTITION_BYTES_IN_POS 2 -#define PARTITION_ENABLED_TABLE_FLAGS (HA_FILE_BASED | \ - HA_REC_NOT_IN_SEQ | \ - HA_CAN_REPAIR) -#define PARTITION_DISABLED_TABLE_FLAGS (HA_CAN_GEOMETRY | \ - HA_CAN_FULLTEXT | \ - HA_DUPLICATE_POS | \ - HA_CAN_SQL_HANDLER | \ - HA_CAN_INSERT_DELAYED) - -/* First 4 bytes in the .par file is the number of 32-bit words in the file */ -#define PAR_WORD_SIZE 4 -/* offset to the .par file checksum */ -#define PAR_CHECKSUM_OFFSET 4 -/* offset to the total number of partitions */ -#define PAR_NUM_PARTS_OFFSET 8 -/* offset to the engines array */ -#define PAR_ENGINES_OFFSET 12 + + +/** Struct used for partition_name_hash */ +typedef struct st_part_name_def +{ + uchar *partition_name; + uint length; + uint32 part_id; + my_bool is_subpart; +} PART_NAME_DEF; + +/** class where to save partitions Handler_share's */ +class Parts_share_refs +{ +public: + uint num_parts; /**< Size of ha_share array */ + Handler_share **ha_shares; /**< Storage for each part */ + Parts_share_refs() + { + num_parts= 0; + ha_shares= NULL; + } + ~Parts_share_refs() + { + uint i; + for (i= 0; i < num_parts; i++) + if (ha_shares[i]) + delete ha_shares[i]; + if (ha_shares) + delete [] ha_shares; + } + bool init(uint arg_num_parts) + { + DBUG_ASSERT(!num_parts && !ha_shares); + num_parts= arg_num_parts; + /* Allocate an array of Handler_share pointers */ + ha_shares= new Handler_share *[num_parts]; + if (!ha_shares) + { + num_parts= 0; + return true; + } + memset(ha_shares, 0, sizeof(Handler_share*) * num_parts); + return false; + } +}; + + +/** + Partition specific Handler_share. +*/ +class Partition_share : public Handler_share +{ +public: + bool auto_inc_initialized; + mysql_mutex_t auto_inc_mutex; /**< protecting auto_inc val */ + ulonglong next_auto_inc_val; /**< first non reserved value */ + /** + Hash of partition names. Initialized in the first ha_partition::open() + for the table_share. After that it is read-only, i.e. no locking required. + */ + bool partition_name_hash_initialized; + HASH partition_name_hash; + /** Storage for each partitions Handler_share */ + Parts_share_refs *partitions_share_refs; + Partition_share() {} + ~Partition_share() + { + DBUG_ENTER("Partition_share::~Partition_share"); + mysql_mutex_destroy(&auto_inc_mutex); + if (partition_name_hash_initialized) + my_hash_free(&partition_name_hash); + if (partitions_share_refs) + delete partitions_share_refs; + DBUG_VOID_RETURN; + } + bool init(uint num_parts); + void lock_auto_inc() + { + mysql_mutex_lock(&auto_inc_mutex); + } + void unlock_auto_inc() + { + mysql_mutex_unlock(&auto_inc_mutex); + } +}; + extern "C" int cmp_key_rowid_part_id(void *ptr, uchar *ref1, uchar *ref2); @@ -60,13 +128,14 @@ private: partition_index_first= 1, partition_index_first_unordered= 2, partition_index_last= 3, - partition_read_range = 4, - partition_no_index_scan= 5 + partition_index_read_last= 4, + partition_read_range = 5, + partition_no_index_scan= 6 }; /* Data for the partition handler */ int m_mode; // Open mode uint m_open_test_lock; // Open test_if_locked - char *m_file_buffer; // Content of the .par file + uchar *m_file_buffer; // Content of the .par file char *m_name_buffer_ptr; // Pointer to first partition name MEM_ROOT m_mem_root; plugin_ref *m_engine_array; // Array of types of the handlers @@ -126,8 +195,6 @@ private: uint m_tot_parts; // Total number of partitions; uint m_num_locks; // For engines like ha_blackhole, which needs no locks uint m_last_part; // Last file that we update,write,read - int m_lock_type; // Remembers type of last - // external_lock part_id_range m_part_spec; // Which parts to scan uint m_scan_value; // Value passed in rnd_init // call @@ -184,7 +251,6 @@ private: /* Variables for lock structures. */ - THR_LOCK_DATA lock; /* MySQL lock */ bool auto_increment_lock; /**< lock reading/updating auto_inc */ /** @@ -197,16 +263,25 @@ private: ha_rows m_bulk_inserted_rows; /** used for prediction of start_bulk_insert rows */ enum_monotonicity_info m_part_func_monotonicity_info; + /** keep track of locked partitions */ + MY_BITMAP m_locked_partitions; + /** Stores shared auto_increment etc. */ + Partition_share *part_share; + /** Temporary storage for new partitions Handler_shares during ALTER */ + List<Parts_share_refs> m_new_partitions_share_refs; /** Sorted array of partition ids in descending order of number of rows. */ uint32 *m_part_ids_sorted_by_num_of_records; /* Compare function for my_qsort2, for reversed order. */ static int compare_number_of_records(ha_partition *me, const uint32 *a, const uint32 *b); + /** keep track of partitions to call ha_reset */ + MY_BITMAP m_partitions_to_reset; /** partitions that returned HA_ERR_KEY_NOT_FOUND. */ MY_BITMAP m_key_not_found_partitions; bool m_key_not_found; public: + Partition_share *get_part_share() { return part_share; } handler *clone(const char *name, MEM_ROOT *mem_root); virtual void set_part_info(partition_info *part_info) { @@ -250,7 +325,7 @@ public: chance for the handler to add any interesting comments to the table comments not provided by the users comment. - create_handler_files is called before opening a new handler object + create_partitioning_metadata is called before opening a new handler object with openfrm to call create. It is used to create any local handler object needed in opening the object in openfrm ------------------------------------------------------------------------- @@ -259,9 +334,8 @@ public: virtual int rename_table(const char *from, const char *to); virtual int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info); - virtual int create_handler_files(const char *name, - const char *old_name, int action_flag, - HA_CREATE_INFO *create_info); + virtual int create_partitioning_metadata(const char *name, + const char *old_name, int action_flag); virtual void update_create_info(HA_CREATE_INFO *create_info); virtual char *update_table_comment(const char *comment); virtual int change_partitions(HA_CREATE_INFO *create_info, @@ -281,8 +355,11 @@ public: virtual void change_table_ptr(TABLE *table_arg, TABLE_SHARE *share); virtual bool check_if_incompatible_data(HA_CREATE_INFO *create_info, uint table_changes); + void update_part_create_info(HA_CREATE_INFO *create_info, uint part_id) + { + m_file[part_id]->update_create_info(create_info); + } private: - int prepare_for_rename(); int copy_partitions(ulonglong * const copied, ulonglong * const deleted); void cleanup_new_partition(uint part_count); int prepare_new_partition(TABLE *table, HA_CREATE_INFO *create_info, @@ -290,11 +367,10 @@ private: partition_element *p_elem, uint disable_non_uniq_indexes); /* - delete_table, rename_table and create uses very similar logic which + delete_table and rename_table uses very similar logic which is packed into this routine. */ - int del_ren_cre_table(const char *from, const char *to, - TABLE *table_arg, HA_CREATE_INFO *create_info); + uint del_ren_table(const char *from, const char *to); /* One method to create the table_name.par file containing the names of the underlying partitions, their engine and the number of partitions. @@ -311,9 +387,16 @@ private: int set_up_table_before_create(TABLE *table_arg, const char *partition_name_with_path, HA_CREATE_INFO *info, - uint part_id, partition_element *p_elem); partition_element *find_partition_element(uint part_id); + bool insert_partition_name_in_hash(const char *name, uint part_id, + bool is_subpart); + bool populate_partition_name_hash(); + Partition_share *get_share(); + bool set_ha_share_ref(Handler_share **ha_share); + void fix_data_dir(char* path); + bool init_partition_bitmaps(); + void free_partition_bitmaps(); public: @@ -344,7 +427,6 @@ public: will be handled by any underlying handlers implementing transactions. There is only one call to each handler type involved per transaction and these go directly to the handlers supporting transactions - currently InnoDB, BDB and NDB). ------------------------------------------------------------------------- */ virtual THR_LOCK_DATA **store_lock(THD * thd, THR_LOCK_DATA ** to, @@ -373,6 +455,18 @@ public: virtual void try_semi_consistent_read(bool); /* + NOTE: due to performance and resource issues with many partitions, + we only use the m_psi on the ha_partition handler, excluding all + partitions m_psi. + */ +#ifdef HAVE_M_PSI_PER_PARTITION + /* + Bind the table/handler thread to track table i/o. + */ + virtual void unbind_psi(); + virtual void rebind_psi(); +#endif + /* ------------------------------------------------------------------------- MODULE change record ------------------------------------------------------------------------- @@ -397,7 +491,7 @@ public: virtual int delete_row(const uchar * buf); virtual int delete_all_rows(void); virtual int truncate(); - virtual void start_bulk_insert(ha_rows rows); + virtual void start_bulk_insert(ha_rows rows, uint flags); virtual int end_bulk_insert(); private: ha_rows guess_bulk_insert_rows(); @@ -417,10 +511,13 @@ public: virtual bool is_fatal_error(int error, uint flags) { if (!handler::is_fatal_error(error, flags) || - error == HA_ERR_NO_PARTITION_FOUND) + error == HA_ERR_NO_PARTITION_FOUND || + error == HA_ERR_NOT_IN_LOCK_PARTITIONS) return FALSE; return TRUE; } + + /* ------------------------------------------------------------------------- MODULE full table scan @@ -545,7 +642,6 @@ private: int handle_ordered_next(uchar * buf, bool next_same); int handle_ordered_prev(uchar * buf); void return_top_record(uchar * buf); - void column_bitmaps_signal(); public: /* ------------------------------------------------------------------------- @@ -571,13 +667,17 @@ public: private: my_bool reg_query_cache_dependant_table(THD *thd, - char *key, uint key_len, uint8 type, + char *engine_key, + uint engine_key_len, + char *query_key, uint query_key_len, + uint8 type, Query_cache *cache, Query_cache_block_table **block_table, handler *file, uint *n); static const uint NO_CURRENT_PART_ID; int loop_extra(enum ha_extra_function operation); + int loop_extra_alter(enum ha_extra_function operations); void late_extra_cache(uint partition_id); void late_extra_no_cache(uint partition_id); void prepare_extra_cache(uint cachesize); @@ -646,6 +746,9 @@ public: virtual uint8 table_cache_type(); virtual ha_rows records(); + /* Calculate hash value for PARTITION BY KEY tables. */ + static uint32 calculate_key_hash_value(Field **field_array); + /* ------------------------------------------------------------------------- MODULE print messages @@ -698,14 +801,14 @@ public: the handler always has a primary key (hidden if not defined) and this index is used for scanning rather than a full table scan in all situations. - (InnoDB, BDB, Federated) + (InnoDB, Federated) HA_REC_NOT_IN_SEQ: This flag is set for handlers that cannot guarantee that the rows are returned accroding to incremental positions (0, 1, 2, 3...). This also means that rnd_next() should return HA_ERR_RECORD_DELETED if it finds a deleted row. - (MyISAM (not fixed length row), BDB, HEAP, NDB, InooDB) + (MyISAM (not fixed length row), HEAP, InnoDB) HA_CAN_GEOMETRY: Can the storage engine handle spatial data. @@ -718,13 +821,13 @@ public: finding a row by key as by position. This flag is used in a very special situation in conjunction with filesort's. For further explanation see intro to init_read_record. - (BDB, HEAP, InnoDB) + (HEAP, InnoDB) HA_NULL_IN_KEY: Is NULL values allowed in indexes. If this is not allowed then it is not possible to use an index on a NULLable field. - (BDB, HEAP, MyISAM, NDB, InnoDB) + (HEAP, MyISAM, InnoDB) HA_DUPLICATE_POS: Tells that we can the position for the conflicting duplicate key @@ -735,12 +838,12 @@ public: HA_CAN_INDEX_BLOBS: Is the storage engine capable of defining an index of a prefix on a BLOB attribute. - (BDB, Federated, MyISAM, InnoDB) + (Federated, MyISAM, InnoDB) HA_AUTO_PART_KEY: Auto increment fields can be part of a multi-part key. For second part auto-increment keys, the auto_incrementing is done in handler.cc - (BDB, Federated, MyISAM, NDB) + (Federated, MyISAM) HA_REQUIRE_PRIMARY_KEY: Can't define a table without primary key (and cannot handle a table @@ -770,7 +873,7 @@ public: HA_NO_PREFIX_CHAR_KEYS: Indexes on prefixes of character fields is not allowed. - (NDB) + (Federated) HA_CAN_FULLTEXT: Does the storage engine support fulltext indexes @@ -795,11 +898,11 @@ public: Should file names always be in lower case (used by engines that map table names to file names. Since partition handler has a local file this flag is set. - (BDB, Federated, MyISAM) + (Federated, MyISAM) HA_CAN_BIT_FIELD: Is the storage engine capable of handling bit fields? - (MyISAM, NDB) + (MyISAM) HA_NEED_READ_RANGE_BUFFER: Is Read Multi-Range supported => need multi read range buffer @@ -811,7 +914,7 @@ public: not handle this call. There are methods in handler.cc that will transfer those calls into index_read and other calls in the index scan module. - (NDB) + (No handler defines it) HA_PRIMARY_KEY_REQUIRED_FOR_POSITION: Does the storage engine need a PK for position? @@ -825,17 +928,7 @@ public: HA_CAN_INSERT_DELAYED, HA_PRIMARY_KEY_REQUIRED_FOR_POSITION is disabled until further investigated. */ - virtual Table_flags table_flags() const - { - DBUG_ENTER("ha_partition::table_flags"); - if (m_handler_status < handler_initialized || - m_handler_status >= handler_closed) - DBUG_RETURN(PARTITION_ENABLED_TABLE_FLAGS); - - DBUG_RETURN((m_file[0]->ha_table_flags() & - ~(PARTITION_DISABLED_TABLE_FLAGS)) | - (PARTITION_ENABLED_TABLE_FLAGS)); - } + virtual Table_flags table_flags() const; /* This is a bitmap of flags that says how the storage engine @@ -851,11 +944,11 @@ public: Does the index support read next, this is assumed in the server code and never checked so all indexes must support this. Note that the handler can be used even if it doesn't have any index. - (BDB, HEAP, MyISAM, Federated, NDB, InnoDB) + (HEAP, MyISAM, Federated, InnoDB) HA_READ_PREV: Can the index be used to scan backwards. - (BDB, HEAP, MyISAM, NDB, InnoDB) + (HEAP, MyISAM, InnoDB) HA_READ_ORDER: Can the index deliver its record in index order. Typically true for @@ -869,19 +962,19 @@ public: order all output started by index_read since most engines do this. With read_multi_range calls there is a specific flag setting order or not order so in those cases ordering of index output can be avoided. - (BDB, InnoDB, HEAP, MyISAM, NDB) + (InnoDB, HEAP, MyISAM) HA_READ_RANGE: Specify whether index can handle ranges, typically true for all ordered indexes and not true for hash indexes. Used by optimiser to check if ranges (as key >= 5) can be optimised by index. - (BDB, InnoDB, NDB, MyISAM, HEAP) + (InnoDB, MyISAM, HEAP) HA_ONLY_WHOLE_INDEX: Can't use part key searches. This is typically true for hash indexes and typically not true for ordered indexes. - (Federated, NDB, HEAP) + (Federated, HEAP) HA_KEYREAD_ONLY: Does the storage engine support index-only scans on this index. @@ -891,7 +984,7 @@ public: only have to fill in the columns the key covers. If HA_PRIMARY_KEY_IN_READ_INDEX is set then also the PRIMARY KEY columns must be updated in the row. - (BDB, InnoDB, MyISAM) + (InnoDB, MyISAM) */ virtual ulong index_flags(uint inx, uint part, bool all_parts) const { @@ -908,10 +1001,6 @@ public: */ virtual uint alter_table_flags(uint flags); /* - extensions of table handler files - */ - virtual const char **bas_ext() const; - /* unireg.cc will call the following to make sure that the storage engine can handle the data it is about to send. @@ -980,16 +1069,15 @@ private: /* lock already taken */ if (auto_increment_safe_stmt_log_lock) return; - DBUG_ASSERT(table_share->ha_part_data && !auto_increment_lock); + DBUG_ASSERT(!auto_increment_lock); if(table_share->tmp_table == NO_TMP_TABLE) { auto_increment_lock= TRUE; - mysql_mutex_lock(&table_share->ha_part_data->LOCK_auto_inc); + part_share->lock_auto_inc(); } } virtual void unlock_auto_increment() { - DBUG_ASSERT(table_share->ha_part_data); /* If auto_increment_safe_stmt_log_lock is true, we have to keep the lock. It will be set to false and thus unlocked at the end of the statement by @@ -997,7 +1085,7 @@ private: */ if(auto_increment_lock && !auto_increment_safe_stmt_log_lock) { - mysql_mutex_unlock(&table_share->ha_part_data->LOCK_auto_inc); + part_share->unlock_auto_inc(); auto_increment_lock= FALSE; } } @@ -1006,10 +1094,10 @@ private: ulonglong nr= (((Field_num*) field)->unsigned_flag || field->val_int() > 0) ? field->val_int() : 0; lock_auto_increment(); - DBUG_ASSERT(table_share->ha_part_data->auto_inc_initialized == TRUE); + DBUG_ASSERT(part_share->auto_inc_initialized); /* must check when the mutex is taken */ - if (nr >= table_share->ha_part_data->next_auto_inc_val) - table_share->ha_part_data->next_auto_inc_val= nr + 1; + if (nr >= part_share->next_auto_inc_val) + part_share->next_auto_inc_val= nr + 1; unlock_auto_increment(); } @@ -1075,18 +1163,23 @@ public: /* ------------------------------------------------------------------------- - MODULE on-line ALTER TABLE + MODULE in-place ALTER TABLE ------------------------------------------------------------------------- These methods are in the handler interface. (used by innodb-plugin) - They are used for on-line/fast alter table add/drop index: + They are used for in-place alter table: ------------------------------------------------------------------------- */ - virtual int add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys, - handler_add_index **add); - virtual int final_add_index(handler_add_index *add, bool commit); - virtual int prepare_drop_index(TABLE *table_arg, uint *key_num, - uint num_of_keys); - virtual int final_drop_index(TABLE *table_arg); + virtual enum_alter_inplace_result + check_if_supported_inplace_alter(TABLE *altered_table, + Alter_inplace_info *ha_alter_info); + virtual bool prepare_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info); + virtual bool inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info); + virtual bool commit_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info, + bool commit); + virtual void notify_table_changed(); /* ------------------------------------------------------------------------- @@ -1119,6 +1212,14 @@ public: virtual bool is_crashed() const; virtual int check_for_upgrade(HA_CHECK_OPT *check_opt); + /* + ----------------------------------------------------------------------- + MODULE condition pushdown + ----------------------------------------------------------------------- + */ + virtual const COND *cond_push(const COND *cond); + virtual void cond_pop(); + private: int handle_opt_partitions(THD *thd, HA_CHECK_OPT *check_opt, uint flags); int handle_opt_part(THD *thd, HA_CHECK_OPT *check_opt, uint part_id, @@ -1130,6 +1231,7 @@ public: int check_misplaced_rows(uint read_part_id, bool repair); void append_row_to_str(String &str); public: + /* ------------------------------------------------------------------------- Admin commands not supported currently (almost purely MyISAM routines) @@ -1140,8 +1242,8 @@ public: virtual int restore(THD* thd, HA_CHECK_OPT *check_opt); virtual int dump(THD* thd, int fd = -1); virtual int net_read_dump(NET* net); - virtual uint checksum() const; */ + virtual uint checksum() const; /* Enabled keycache for performance reasons, WL#4571 */ virtual int assign_to_keycache(THD* thd, HA_CHECK_OPT *check_opt); virtual int preload_keys(THD* thd, HA_CHECK_OPT* check_opt); @@ -1191,7 +1293,6 @@ public: return h; } - friend int cmp_key_rowid_part_id(void *ptr, uchar *ref1, uchar *ref2); }; diff --git a/sql/handler.cc b/sql/handler.cc index 8c6270a042e..87331a41db6 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2016, Oracle and/or its affiliates. - Copyright (c) 2009, 2016, MariaDB + Copyright (c) 2009, 2018, 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 @@ -11,8 +11,8 @@ 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, Suite 500, Boston, MA 02110-1335 USA */ + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file handler.cc @@ -20,10 +20,8 @@ Handler-calling-functions */ -#ifdef USE_PRAGMA_IMPLEMENTATION -#pragma implementation // gcc: Class implementation -#endif - +#include <my_global.h> +#include <inttypes.h> #include "sql_priv.h" #include "unireg.h" #include "rpl_handler.h" @@ -34,7 +32,7 @@ #include "sql_parse.h" // check_stack_overrun #include "sql_acl.h" // SUPER_ACL #include "sql_base.h" // free_io_cache -#include "discover.h" // writefrm +#include "discover.h" // extension_based_table_discovery, etc #include "log_event.h" // *_rows_log_event #include "create_options.h" #include "rpl_filter.h" @@ -42,9 +40,9 @@ #include "transaction.h" #include "myisam.h" #include "probes_mysql.h" +#include <mysql/psi/mysql_table.h> #include "debug_sync.h" // DEBUG_SYNC #include "sql_audit.h" -#include "../mysys/my_handler_errors.h" #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" @@ -54,6 +52,10 @@ #include "../storage/maria/ha_maria.h" #endif +#include "wsrep_mysqld.h" +#include "wsrep.h" +#include "wsrep_xid.h" + /* While we have legacy_db_type, we have this array to check for dups and to find handlerton from legacy_db_type. @@ -66,19 +68,26 @@ static handlerton *installed_htons[128]; #define BITMAP_STACKBUF_SIZE (128/8) KEY_CREATE_INFO default_key_create_info= - { HA_KEY_ALG_UNDEF, 0, {NullS, 0}, {NullS, 0} }; +{ HA_KEY_ALG_UNDEF, 0, {NullS, 0}, {NullS, 0}, true }; /* number of entries in handlertons[] */ ulong total_ha= 0; /* number of storage engines (from handlertons[]) that support 2pc */ ulong total_ha_2pc= 0; +#ifndef DBUG_OFF +/* + Number of non-mandatory 2pc handlertons whose initialization failed + to estimate total_ha_2pc value under supposition of the failures + have not occcured. +*/ +ulong failed_ha_2pc= 0; +#endif /* size of savepoint storage area (see ha_init) */ ulong savepoint_alloc_size= 0; static const LEX_STRING sys_table_aliases[]= { { C_STRING_WITH_LEN("INNOBASE") }, { C_STRING_WITH_LEN("INNODB") }, - { C_STRING_WITH_LEN("NDB") }, { C_STRING_WITH_LEN("NDBCLUSTER") }, { C_STRING_WITH_LEN("HEAP") }, { C_STRING_WITH_LEN("MEMORY") }, { C_STRING_WITH_LEN("MERGE") }, { C_STRING_WITH_LEN("MRG_MYISAM") }, { C_STRING_WITH_LEN("Maria") }, { C_STRING_WITH_LEN("Aria") }, @@ -101,6 +110,7 @@ uint known_extensions_id= 0; static int commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans); + static plugin_ref ha_default_plugin(THD *thd) { if (thd->variables.table_plugin) @@ -108,6 +118,15 @@ static plugin_ref ha_default_plugin(THD *thd) return my_plugin_lock(thd, global_system_variables.table_plugin); } +static plugin_ref ha_default_tmp_plugin(THD *thd) +{ + if (thd->variables.tmp_table_plugin) + return thd->variables.tmp_table_plugin; + if (global_system_variables.tmp_table_plugin) + return my_plugin_lock(thd, global_system_variables.tmp_table_plugin); + return ha_default_plugin(thd); +} + /** @brief Return the default storage engine handlerton for thread @@ -123,7 +142,17 @@ handlerton *ha_default_handlerton(THD *thd) { plugin_ref plugin= ha_default_plugin(thd); DBUG_ASSERT(plugin); - handlerton *hton= plugin_data(plugin, handlerton*); + handlerton *hton= plugin_hton(plugin); + DBUG_ASSERT(hton); + return hton; +} + + +handlerton *ha_default_tmp_handlerton(THD *thd) +{ + plugin_ref plugin= ha_default_tmp_plugin(thd); + DBUG_ASSERT(plugin); + handlerton *hton= plugin_hton(plugin); DBUG_ASSERT(hton); return hton; } @@ -140,7 +169,7 @@ handlerton *ha_default_handlerton(THD *thd) RETURN pointer to storage engine plugin handle */ -plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name) +plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name, bool tmp_table) { const LEX_STRING *table_alias; plugin_ref plugin; @@ -150,11 +179,11 @@ redo: if (thd && !my_charset_latin1.coll->strnncoll(&my_charset_latin1, (const uchar *)name->str, name->length, (const uchar *)STRING_WITH_LEN("DEFAULT"), 0)) - return ha_default_plugin(thd); + return tmp_table ? ha_default_tmp_plugin(thd) : ha_default_plugin(thd); if ((plugin= my_plugin_lock_by_name(thd, name, MYSQL_STORAGE_ENGINE_PLUGIN))) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton && !(hton->flags & HTON_NOT_USER_SELECTABLE)) return plugin; @@ -202,7 +231,7 @@ handlerton *ha_resolve_by_legacy_type(THD *thd, enum legacy_db_type db_type) default: if (db_type > DB_TYPE_UNKNOWN && db_type < DB_TYPE_DEFAULT && (plugin= ha_lock_engine(thd, installed_htons[db_type]))) - return plugin_data(plugin, handlerton*); + return plugin_hton(plugin); /* fall through */ case DB_TYPE_UNKNOWN: return NULL; @@ -213,31 +242,13 @@ handlerton *ha_resolve_by_legacy_type(THD *thd, enum legacy_db_type db_type) /** Use other database handler if databasehandler is not compiled in. */ -handlerton *ha_checktype(THD *thd, enum legacy_db_type database_type, - bool no_substitute, bool report_error) +handlerton *ha_checktype(THD *thd, handlerton *hton, bool no_substitute) { - handlerton *hton= ha_resolve_by_legacy_type(thd, database_type); if (ha_storage_engine_is_enabled(hton)) return hton; if (no_substitute) - { - if (report_error) - { - const char *engine_name= ha_resolve_storage_engine_name(hton); - my_error(ER_FEATURE_DISABLED,MYF(0),engine_name,engine_name); - } return NULL; - } - - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); - - switch (database_type) { - case DB_TYPE_MRG_ISAM: - return ha_resolve_by_legacy_type(thd, DB_TYPE_MRG_MYISAM); - default: - break; - } return ha_default_handlerton(thd); } /* ha_checktype */ @@ -261,7 +272,8 @@ handler *get_new_handler(TABLE_SHARE *share, MEM_ROOT *alloc, Here the call to current_thd() is ok as we call this function a lot of times but we enter this branch very seldom. */ - DBUG_RETURN(get_new_handler(share, alloc, ha_default_handlerton(current_thd))); + file= get_new_handler(share, alloc, ha_default_handlerton(current_thd)); + DBUG_RETURN(file); } @@ -293,7 +305,7 @@ handler *get_ha_partition(partition_info *part_info) static const char **handler_errmsgs; C_MODE_START -static const char **get_handler_errmsgs() +static const char **get_handler_errmsgs(void) { return handler_errmsgs; } @@ -322,7 +334,7 @@ int ha_init_errors(void) /* Set the dedicated error messages. */ SETMSG(HA_ERR_KEY_NOT_FOUND, ER_DEFAULT(ER_KEY_NOT_FOUND)); SETMSG(HA_ERR_FOUND_DUPP_KEY, ER_DEFAULT(ER_DUP_KEY)); - SETMSG(HA_ERR_RECORD_CHANGED, "Update wich is recoverable"); + SETMSG(HA_ERR_RECORD_CHANGED, "Update which is recoverable"); SETMSG(HA_ERR_WRONG_INDEX, "Wrong index given to function"); SETMSG(HA_ERR_CRASHED, ER_DEFAULT(ER_NOT_KEYFILE)); SETMSG(HA_ERR_WRONG_IN_RECORD, ER_DEFAULT(ER_CRASHED_ON_USAGE)); @@ -357,15 +369,17 @@ int ha_init_errors(void) SETMSG(HA_ERR_NO_CONNECTION, "Could not connect to storage engine"); SETMSG(HA_ERR_TABLE_DEF_CHANGED, ER_DEFAULT(ER_TABLE_DEF_CHANGED)); SETMSG(HA_ERR_FOREIGN_DUPLICATE_KEY, "FK constraint would lead to duplicate key"); - SETMSG(HA_ERR_TABLE_NEEDS_UPGRADE, ER_DEFAULT(ER_TABLE_NEEDS_UPGRADE)); + SETMSG(HA_ERR_TABLE_NEEDS_UPGRADE, "Table upgrade required. Please do \"REPAIR TABLE %`\" or dump/reload to fix it"); SETMSG(HA_ERR_TABLE_READONLY, ER_DEFAULT(ER_OPEN_AS_READONLY)); SETMSG(HA_ERR_AUTOINC_READ_FAILED, ER_DEFAULT(ER_AUTOINC_READ_FAILED)); SETMSG(HA_ERR_AUTOINC_ERANGE, ER_DEFAULT(ER_WARN_DATA_OUT_OF_RANGE)); SETMSG(HA_ERR_TOO_MANY_CONCURRENT_TRXS, ER_DEFAULT(ER_TOO_MANY_CONCURRENT_TRXS)); SETMSG(HA_ERR_INDEX_COL_TOO_LONG, ER_DEFAULT(ER_INDEX_COLUMN_TOO_LONG)); SETMSG(HA_ERR_INDEX_CORRUPT, ER_DEFAULT(ER_INDEX_CORRUPT)); + SETMSG(HA_FTS_INVALID_DOCID, "Invalid InnoDB FTS Doc ID"); SETMSG(HA_ERR_TABLE_IN_FK_CHECK, ER_DEFAULT(ER_TABLE_IN_FK_CHECK)); SETMSG(HA_ERR_DISK_FULL, ER_DEFAULT(ER_DISK_FULL)); + SETMSG(HA_ERR_FTS_TOO_MANY_WORDS_IN_PHRASE, "Too many words in a FTS phrase or proximity search"); /* Register the error messages for use with my_error(). */ return my_error_register(get_handler_errmsgs, HA_ERR_FIRST, HA_ERR_LAST); @@ -391,6 +405,38 @@ static int ha_finish_errors(void) return 0; } +static volatile int32 need_full_discover_for_existence= 0; +static volatile int32 engines_with_discover_file_names= 0; +static volatile int32 engines_with_discover= 0; + +static int full_discover_for_existence(handlerton *, const char *, const char *) +{ return 0; } + +static int ext_based_existence(handlerton *, const char *, const char *) +{ return 0; } + +static int hton_ext_based_table_discovery(handlerton *hton, LEX_STRING *db, + MY_DIR *dir, handlerton::discovered_list *result) +{ + /* + tablefile_extensions[0] is the metadata file, see + the comment above tablefile_extensions declaration + */ + return extension_based_table_discovery(dir, hton->tablefile_extensions[0], + result); +} + +static void update_discovery_counters(handlerton *hton, int val) +{ + if (hton->discover_table_existence == full_discover_for_existence) + my_atomic_add32(&need_full_discover_for_existence, val); + + if (hton->discover_table_names && hton->tablefile_extensions[0]) + my_atomic_add32(&engines_with_discover_file_names, val); + + if (hton->discover_table) + my_atomic_add32(&engines_with_discover, val); +} int ha_finalize_handlerton(st_plugin_int *plugin) { @@ -428,6 +474,9 @@ int ha_finalize_handlerton(st_plugin_int *plugin) } } + free_sysvar_table_options(hton); + update_discovery_counters(hton, -1); + /* In case a plugin is uninstalled and re-installed later, it should reuse an array slot. Otherwise the number of uninstall/install @@ -451,12 +500,12 @@ int ha_finalize_handlerton(st_plugin_int *plugin) int ha_initialize_handlerton(st_plugin_int *plugin) { handlerton *hton; + static const char *no_exts[]= { 0 }; DBUG_ENTER("ha_initialize_handlerton"); DBUG_PRINT("plugin", ("initialize plugin: '%s'", plugin->name.str)); hton= (handlerton *)my_malloc(sizeof(handlerton), MYF(MY_WME | MY_ZEROFILL)); - if (hton == NULL) { sql_print_error("Unable to allocate memory for plugin '%s' handlerton.", @@ -464,6 +513,9 @@ int ha_initialize_handlerton(st_plugin_int *plugin) goto err_no_hton_memory; } + hton->tablefile_extensions= no_exts; + hton->discover_table_names= hton_ext_based_table_discovery; + hton->slot= HA_SLOT_UNDEF; /* Historical Requirement */ plugin->data= hton; // shortcut for the future @@ -474,10 +526,21 @@ int ha_initialize_handlerton(st_plugin_int *plugin) goto err; } - /* - the switch below and hton->state should be removed when - command-line options for plugins will be implemented - */ + // hton_ext_based_table_discovery() works only when discovery + // is supported and the engine if file-based. + if (hton->discover_table_names == hton_ext_based_table_discovery && + (!hton->discover_table || !hton->tablefile_extensions[0])) + hton->discover_table_names= NULL; + + // default discover_table_existence implementation + if (!hton->discover_table_existence && hton->discover_table) + { + if (hton->tablefile_extensions[0]) + hton->discover_table_existence= ext_based_existence; + else + hton->discover_table_existence= full_discover_for_existence; + } + switch (hton->state) { case SHOW_OPTION_NO: break; @@ -547,7 +610,7 @@ int ha_initialize_handlerton(st_plugin_int *plugin) { total_ha_2pc--; hton->prepare= 0; - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Cannot enable tc-log at run-time. " "XA features of %s are disabled", @@ -581,6 +644,9 @@ int ha_initialize_handlerton(st_plugin_int *plugin) break; }; + resolve_sysvar_table_options(hton); + update_discovery_counters(hton, 1); + DBUG_RETURN(0); err_deinit: @@ -592,6 +658,10 @@ err_deinit: (void) plugin->plugin->deinit(NULL); err: +#ifndef DBUG_OFF + if (hton->prepare && hton->state == SHOW_OPTION_YES) + failed_ha_2pc++; +#endif my_free(hton); err_no_hton_memory: plugin->data= NULL; @@ -634,7 +704,7 @@ int ha_end() static my_bool dropdb_handlerton(THD *unused1, plugin_ref plugin, void *path) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton->state == SHOW_OPTION_YES && hton->drop_database) hton->drop_database(hton, (char *)path); return FALSE; @@ -650,7 +720,7 @@ void ha_drop_database(char* path) static my_bool checkpoint_state_handlerton(THD *unused1, plugin_ref plugin, void *disable) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton->state == SHOW_OPTION_YES && hton->checkpoint_state) hton->checkpoint_state(hton, (int) *(bool*) disable); return FALSE; @@ -663,11 +733,48 @@ void ha_checkpoint_state(bool disable) } +struct st_commit_checkpoint_request { + void *cookie; + void (*pre_hook)(void *); +}; + +static my_bool commit_checkpoint_request_handlerton(THD *unused1, plugin_ref plugin, + void *data) +{ + st_commit_checkpoint_request *st= (st_commit_checkpoint_request *)data; + handlerton *hton= plugin_hton(plugin); + if (hton->state == SHOW_OPTION_YES && hton->commit_checkpoint_request) + { + void *cookie= st->cookie; + if (st->pre_hook) + (*st->pre_hook)(cookie); + (*hton->commit_checkpoint_request)(hton, cookie); + } + return FALSE; +} + + +/* + Invoke commit_checkpoint_request() in all storage engines that implement it. + + If pre_hook is non-NULL, the hook will be called prior to each invocation. +*/ +void +ha_commit_checkpoint_request(void *cookie, void (*pre_hook)(void *)) +{ + st_commit_checkpoint_request st; + st.cookie= cookie; + st.pre_hook= pre_hook; + plugin_foreach(NULL, commit_checkpoint_request_handlerton, + MYSQL_STORAGE_ENGINE_PLUGIN, &st); +} + + static my_bool closecon_handlerton(THD *thd, plugin_ref plugin, void *unused) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); /* there's no need to rollback here as all transactions must be rolled back already @@ -688,13 +795,15 @@ static my_bool closecon_handlerton(THD *thd, plugin_ref plugin, */ void ha_close_connection(THD* thd) { - plugin_foreach(thd, closecon_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, 0); + plugin_foreach_with_mask(thd, closecon_handlerton, + MYSQL_STORAGE_ENGINE_PLUGIN, + PLUGIN_IS_DELETED|PLUGIN_IS_READY, 0); } static my_bool kill_handlerton(THD *thd, plugin_ref plugin, void *level) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton->state == SHOW_OPTION_YES && hton->kill_query && thd_get_ha_data(thd, hton)) @@ -753,7 +862,7 @@ void ha_kill_query(THD* thd, enum thd_kill_levels level) end. Such nested transaction was internally referred to as a "statement transaction" and gave birth to the term. - <Historical note ends> + (Historical note ends) Since then a statement transaction is started for each statement that accesses transactional tables or uses the binary log. If @@ -1027,11 +1136,14 @@ void trans_register_ha(THD *thd, bool all, handlerton *ht_arg) { trans= &thd->transaction.all; thd->server_status|= SERVER_STATUS_IN_TRANS; + if (thd->tx_read_only) + thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY; + DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS")); } else trans= &thd->transaction.stmt; - ha_info= thd->ha_data[ht_arg->slot].ha_info + static_cast<unsigned>(all); + ha_info= thd->ha_data[ht_arg->slot].ha_info + (all ? 1 : 0); if (ha_info->is_started()) DBUG_VOID_RETURN; /* already registered, return */ @@ -1044,6 +1156,24 @@ void trans_register_ha(THD *thd, bool all, handlerton *ht_arg) DBUG_VOID_RETURN; } + +static int prepare_or_error(handlerton *ht, THD *thd, bool all) +{ + int err= ht->prepare(ht, thd, all); + status_var_increment(thd->status_var.ha_prepare_count); + if (err) + { + /* avoid sending error, if we're going to replay the transaction */ +#ifdef WITH_WSREP + if (ht != wsrep_hton || + err == EMSGSIZE || thd->wsrep_conflict_state != MUST_REPLAY) +#endif + my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); + } + return err; +} + + /** @retval 0 ok @@ -1061,14 +1191,11 @@ int ha_prepare(THD *thd) { for (; ha_info; ha_info= ha_info->next()) { - int err; handlerton *ht= ha_info->ht(); - status_var_increment(thd->status_var.ha_prepare_count); if (ht->prepare) { - if ((err= ht->prepare(ht, thd, all))) + if (prepare_or_error(ht, thd, all)) { - my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); ha_rollback_trans(thd, all); error=1; break; @@ -1076,9 +1203,11 @@ int ha_prepare(THD *thd) } else { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_GET_ERRNO, ER_THD(thd, ER_GET_ERRNO), + HA_ERR_WRONG_COMMAND, ha_resolve_storage_engine_name(ht)); + } } } @@ -1176,18 +1305,24 @@ int ha_commit_trans(THD *thd, bool all) the changes are not durable as they might be rolled back if the enclosing 'all' transaction is rolled back. */ - bool is_real_trans= all || thd->transaction.all.ha_list == 0; + bool is_real_trans= ((all || thd->transaction.all.ha_list == 0) && + !(thd->variables.option_bits & OPTION_GTID_BEGIN)); Ha_trx_info *ha_info= trans->ha_list; bool need_prepare_ordered, need_commit_ordered; my_xid xid; DBUG_ENTER("ha_commit_trans"); + DBUG_PRINT("info",("thd: %p option_bits: %lu all: %d", + thd, (ulong) thd->variables.option_bits, all)); /* Just a random warning to test warnings pushed during autocommit. */ DBUG_EXECUTE_IF("warn_during_ha_commit_trans", - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARNING_NOT_COMPLETE_ROLLBACK, - ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));); + ER_THD(thd, ER_WARNING_NOT_COMPLETE_ROLLBACK));); + DBUG_PRINT("info", + ("all: %d thd->in_sub_stmt: %d ha_info: %p is_real_trans: %d", + all, thd->in_sub_stmt, ha_info, is_real_trans)); /* We must not commit the normal transaction if a statement transaction is pending. Otherwise statement transaction @@ -1224,7 +1359,9 @@ int ha_commit_trans(THD *thd, bool all) if (!ha_info) { - /* Free resources and perform other cleanup even for 'empty' transactions. */ + /* + Free resources and perform other cleanup even for 'empty' transactions. + */ if (is_real_trans) thd->transaction.cleanup(); DBUG_RETURN(0); @@ -1241,6 +1378,8 @@ int ha_commit_trans(THD *thd, bool all) bool rw_trans= is_real_trans && (rw_ha_count > !thd->is_current_stmt_binlog_disabled()); MDL_request mdl_request; + DBUG_PRINT("info", ("is_real_trans: %d rw_trans: %d rw_ha_count: %d", + is_real_trans, rw_trans, rw_ha_count)); if (rw_trans) { @@ -1255,8 +1394,9 @@ int ha_commit_trans(THD *thd, bool all) mdl_request.init(MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE, MDL_EXPLICIT); - if (thd->mdl_context.acquire_lock(&mdl_request, - thd->variables.lock_wait_timeout)) + if (!WSREP(thd) && + thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) { ha_rollback_trans(thd, all); DBUG_RETURN(1); @@ -1286,7 +1426,6 @@ int ha_commit_trans(THD *thd, bool all) for (Ha_trx_info *hi= ha_info; hi; hi= hi->next()) { - int err; handlerton *ht= hi->ht(); /* Do not call two-phase commit if this particular @@ -1299,12 +1438,7 @@ int ha_commit_trans(THD *thd, bool all) Sic: we know that prepare() is not NULL since otherwise trans->no_2pc would have been set. */ - err= ht->prepare(ht, thd, all); - status_var_increment(thd->status_var.ha_prepare_count); - if (err) - my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); - - if (err) + if (prepare_or_error(ht, thd, all)) goto err; need_prepare_ordered|= (ht->prepare_ordered != NULL); @@ -1313,17 +1447,25 @@ int ha_commit_trans(THD *thd, bool all) DEBUG_SYNC(thd, "ha_commit_trans_after_prepare"); DBUG_EXECUTE_IF("crash_commit_after_prepare", DBUG_SUICIDE();); + if (!error && WSREP_ON && wsrep_is_wsrep_xid(&thd->transaction.xid_state.xid)) + { + // xid was rewritten by wsrep + xid= wsrep_xid_seqno(thd->transaction.xid_state.xid); + } + if (!is_real_trans) { error= commit_one_phase_2(thd, all, trans, is_real_trans); goto done; } + DEBUG_SYNC(thd, "ha_commit_trans_before_log_and_order"); cookie= tc_log->log_and_order(thd, xid, all, need_prepare_ordered, need_commit_ordered); if (!cookie) goto err; + DEBUG_SYNC(thd, "ha_commit_trans_after_log_and_order"); DBUG_EXECUTE_IF("crash_commit_after_log", DBUG_SUICIDE();); error= commit_one_phase_2(thd, all, trans, is_real_trans) ? 2 : 0; @@ -1337,13 +1479,24 @@ int ha_commit_trans(THD *thd, bool all) done: DBUG_EXECUTE_IF("crash_commit_after", DBUG_SUICIDE();); + + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); + mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock()); + mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync); + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); RUN_HOOK(transaction, after_commit, (thd, FALSE)); goto end; /* Come here if error and we need to rollback. */ err: error= 1; /* Transaction was rolled back */ - ha_rollback_trans(thd, all); + /* + In parallel replication, rollback is delayed, as there is extra replication + book-keeping to be done before rolling back and allowing a conflicting + transaction to continue (MDEV-7458). + */ + if (!(thd->rgi_slave && thd->rgi_slave->is_parallel_exec)) + ha_rollback_trans(thd, all); end: if (rw_trans && mdl_request.ticket) @@ -1386,9 +1539,17 @@ int ha_commit_one_phase(THD *thd, bool all) ha_commit_one_phase() can be called with an empty transaction.all.ha_list, see why in trans_register_ha()). */ - bool is_real_trans=all || thd->transaction.all.ha_list == 0; + bool is_real_trans= ((all || thd->transaction.all.ha_list == 0) && + !(thd->variables.option_bits & OPTION_GTID_BEGIN)); + int res; DBUG_ENTER("ha_commit_one_phase"); - int res= commit_one_phase_2(thd, all, trans, is_real_trans); + if (is_real_trans) + { + DEBUG_SYNC(thd, "ha_commit_one_phase"); + if ((res= thd->wait_for_prior_commit())) + DBUG_RETURN(res); + } + res= commit_one_phase_2(thd, all, trans, is_real_trans); DBUG_RETURN(res); } @@ -1399,6 +1560,8 @@ commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans) int error= 0; Ha_trx_info *ha_info= trans->ha_list, *ha_info_next; DBUG_ENTER("commit_one_phase_2"); + if (is_real_trans) + DEBUG_SYNC(thd, "commit_one_phase_2"); if (ha_info) { for (; ha_info; ha_info= ha_info_next) @@ -1427,7 +1590,10 @@ commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans) } /* Free resources and perform other cleanup even for 'empty' transactions. */ if (is_real_trans) + { + thd->has_waiter= false; thd->transaction.cleanup(); + } DBUG_RETURN(error); } @@ -1461,6 +1627,26 @@ int ha_rollback_trans(THD *thd, bool all) DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL || trans == &thd->transaction.stmt); +#ifdef HAVE_REPLICATION + if (is_real_trans) + { + /* + In parallel replication, if we need to rollback during commit, we must + first inform following transactions that we are going to abort our commit + attempt. Otherwise those following transactions can run too early, and + possibly cause replication to fail. See comments in retry_event_group(). + + There were several bugs with this in the past that were very hard to + track down (MDEV-7458, MDEV-8302). So we add here an assertion for + rollback without signalling following transactions. And in release + builds, we explicitly do the signalling before rolling back. + */ + DBUG_ASSERT(!(thd->rgi_slave && thd->rgi_slave->did_mark_start_commit)); + if (thd->rgi_slave && thd->rgi_slave->did_mark_start_commit) + thd->rgi_slave->unmark_start_commit(); + } +#endif + if (thd->in_sub_stmt) { DBUG_ASSERT(0); @@ -1489,6 +1675,11 @@ int ha_rollback_trans(THD *thd, bool all) { // cannot happen my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err); error=1; +#ifdef WITH_WSREP + WSREP_WARN("handlerton rollback failed, thd %lu %lld conf %d SQL %s", + thd->thread_id, thd->query_id, thd->wsrep_conflict_state, + thd->query()); +#endif /* WITH_WSREP */ } status_var_increment(thd->status_var.ha_rollback_count); ha_info_next= ha_info->next(); @@ -1504,11 +1695,14 @@ int ha_rollback_trans(THD *thd, bool all) */ if (is_real_trans && thd->transaction_rollback_request && thd->transaction.xid_state.xa_state != XA_NOTR) - thd->transaction.xid_state.rm_error= thd->stmt_da->sql_errno(); + thd->transaction.xid_state.rm_error= thd->get_stmt_da()->sql_errno(); /* Always cleanup. Even if nht==0. There may be savepoints. */ if (is_real_trans) + { + thd->has_waiter= false; thd->transaction.cleanup(); + } if (all) thd->transaction_rollback_request= FALSE; @@ -1527,10 +1721,10 @@ int ha_rollback_trans(THD *thd, bool all) */ if (is_real_trans && thd->transaction.all.modified_non_trans_table && !thd->slave_thread && thd->killed < KILL_CONNECTION) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARNING_NOT_COMPLETE_ROLLBACK, - ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + ER_THD(thd, ER_WARNING_NOT_COMPLETE_ROLLBACK)); + (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE)); DBUG_RETURN(error); } @@ -1543,7 +1737,7 @@ struct xahton_st { static my_bool xacommit_handlerton(THD *unused1, plugin_ref plugin, void *arg) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton->state == SHOW_OPTION_YES && hton->recover) { hton->commit_by_xid(hton, ((struct xahton_st *)arg)->xid); @@ -1555,7 +1749,7 @@ static my_bool xacommit_handlerton(THD *unused1, plugin_ref plugin, static my_bool xarollback_handlerton(THD *unused1, plugin_ref plugin, void *arg) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton->state == SHOW_OPTION_YES && hton->recover) { hton->rollback_by_xid(hton, ((struct xahton_st *)arg)->xid); @@ -1661,7 +1855,7 @@ struct xarecover_st static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, void *arg) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); struct xarecover_st *info= (struct xarecover_st *) arg; int got; @@ -1673,12 +1867,14 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, got, hton_name(hton)->str); for (int i=0; i < got; i ++) { - my_xid x=info->list[i].get_my_xid(); + my_xid x= WSREP_ON && wsrep_is_wsrep_xid(&info->list[i]) ? + wsrep_xid_seqno(info->list[i]) : + info->list[i].get_my_xid(); if (!x) // not "mine" - that is generated by external TM { #ifndef DBUG_OFF char buf[XIDDATASIZE*4+6]; // see xid_to_str - sql_print_information("ignore xid %s", xid_to_str(buf, info->list+i)); + DBUG_PRINT("info", ("ignore xid %s", xid_to_str(buf, info->list+i))); #endif xid_cache_insert(info->list+i, XA_PREPARED); info->found_foreign_xids++; @@ -1695,19 +1891,31 @@ static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin, tc_heuristic_recover == TC_HEURISTIC_RECOVER_COMMIT) { #ifndef DBUG_OFF - char buf[XIDDATASIZE*4+6]; // see xid_to_str - sql_print_information("commit xid %s", xid_to_str(buf, info->list+i)); + int rc= +#endif + hton->commit_by_xid(hton, info->list+i); +#ifndef DBUG_OFF + if (rc == 0) + { + char buf[XIDDATASIZE*4+6]; // see xid_to_str + DBUG_PRINT("info", ("commit xid %s", xid_to_str(buf, info->list+i))); + } #endif - hton->commit_by_xid(hton, info->list+i); } else { #ifndef DBUG_OFF - char buf[XIDDATASIZE*4+6]; // see xid_to_str - sql_print_information("rollback xid %s", - xid_to_str(buf, info->list+i)); + int rc= +#endif + hton->rollback_by_xid(hton, info->list+i); +#ifndef DBUG_OFF + if (rc == 0) + { + char buf[XIDDATASIZE*4+6]; // see xid_to_str + DBUG_PRINT("info", ("rollback xid %s", + xid_to_str(buf, info->list+i))); + } #endif - hton->rollback_by_xid(hton, info->list+i); } } if (got < info->len) @@ -1729,7 +1937,8 @@ int ha_recover(HASH *commit_list) /* commit_list and tc_heuristic_recover cannot be set both */ DBUG_ASSERT(info.commit_list==0 || tc_heuristic_recover==0); /* if either is set, total_ha_2pc must be set too */ - DBUG_ASSERT(info.dry_run || total_ha_2pc>(ulong)opt_bin_log); + DBUG_ASSERT(info.dry_run || + (failed_ha_2pc + total_ha_2pc) > (ulong)opt_bin_log); if (total_ha_2pc <= (ulong)opt_bin_log) DBUG_RETURN(0); @@ -1781,47 +1990,66 @@ int ha_recover(HASH *commit_list) so mysql_xa_recover does not filter XID's to ensure uniqueness. It can be easily fixed later, if necessary. */ + +static my_bool xa_recover_callback(XID_STATE *xs, Protocol *protocol) +{ + if (xs->xa_state == XA_PREPARED) + { + protocol->prepare_for_resend(); + protocol->store_longlong((longlong) xs->xid.formatID, FALSE); + protocol->store_longlong((longlong) xs->xid.gtrid_length, FALSE); + protocol->store_longlong((longlong) xs->xid.bqual_length, FALSE); + protocol->store(xs->xid.data, xs->xid.gtrid_length + xs->xid.bqual_length, + &my_charset_bin); + if (protocol->write()) + return TRUE; + } + return FALSE; +} + + bool mysql_xa_recover(THD *thd) { List<Item> field_list; Protocol *protocol= thd->protocol; - int i=0; - XID_STATE *xs; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("mysql_xa_recover"); - field_list.push_back(new Item_int("formatID", 0, MY_INT32_NUM_DECIMAL_DIGITS)); - field_list.push_back(new Item_int("gtrid_length", 0, MY_INT32_NUM_DECIMAL_DIGITS)); - field_list.push_back(new Item_int("bqual_length", 0, MY_INT32_NUM_DECIMAL_DIGITS)); - field_list.push_back(new Item_empty_string("data",XIDDATASIZE)); + field_list.push_back(new (mem_root) + Item_int(thd, "formatID", 0, + MY_INT32_NUM_DECIMAL_DIGITS), mem_root); + field_list.push_back(new (mem_root) + Item_int(thd, "gtrid_length", 0, + MY_INT32_NUM_DECIMAL_DIGITS), mem_root); + field_list.push_back(new (mem_root) + Item_int(thd, "bqual_length", 0, + MY_INT32_NUM_DECIMAL_DIGITS), mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "data", + XIDDATASIZE), mem_root); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(1); - mysql_mutex_lock(&LOCK_xid_cache); - while ((xs= (XID_STATE*) my_hash_element(&xid_cache, i++))) - { - if (xs->xa_state==XA_PREPARED) - { - protocol->prepare_for_resend(); - protocol->store_longlong((longlong)xs->xid.formatID, FALSE); - protocol->store_longlong((longlong)xs->xid.gtrid_length, FALSE); - protocol->store_longlong((longlong)xs->xid.bqual_length, FALSE); - protocol->store(xs->xid.data, xs->xid.gtrid_length+xs->xid.bqual_length, - &my_charset_bin); - if (protocol->write()) - { - mysql_mutex_unlock(&LOCK_xid_cache); - DBUG_RETURN(1); - } - } - } - - mysql_mutex_unlock(&LOCK_xid_cache); + if (xid_cache_iterate(thd, (my_hash_walk_action) xa_recover_callback, + protocol)) + DBUG_RETURN(1); my_eof(thd); DBUG_RETURN(0); } +/* + Called by engine to notify TC that a new commit checkpoint has been reached. + See comments on handlerton method commit_checkpoint_request() for details. +*/ +void +commit_checkpoint_notify_ha(handlerton *hton, void *cookie) +{ + tc_log->commit_checkpoint_notify(cookie); +} + + /** @details This function should be called when MySQL sends rows of a SELECT result set @@ -1860,6 +2088,41 @@ int ha_release_temporary_latches(THD *thd) return 0; } +/** + Check if all storage engines used in transaction agree that after + rollback to savepoint it is safe to release MDL locks acquired after + savepoint creation. + + @param thd The client thread that executes the transaction. + + @return true - It is safe to release MDL locks. + false - If it is not. +*/ +bool ha_rollback_to_savepoint_can_release_mdl(THD *thd) +{ + Ha_trx_info *ha_info; + THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt : + &thd->transaction.all); + + DBUG_ENTER("ha_rollback_to_savepoint_can_release_mdl"); + + /** + Checking whether it is safe to release metadata locks after rollback to + savepoint in all the storage engines that are part of the transaction. + */ + for (ha_info= trans->ha_list; ha_info; ha_info= ha_info->next()) + { + handlerton *ht= ha_info->ht(); + DBUG_ASSERT(ht); + + if (ht->savepoint_rollback_can_release_mdl == 0 || + ht->savepoint_rollback_can_release_mdl(ht, thd) == false) + DBUG_RETURN(false); + } + + DBUG_RETURN(true); +} + int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv) { int error=0; @@ -1938,7 +2201,7 @@ int ha_savepoint(THD *thd, SAVEPOINT *sv) } if ((err= ht->savepoint_set(ht, thd, (uchar *)(sv+1)+ht->savepoint_offset))) { // cannot happen - my_error(ER_GET_ERRNO, MYF(0), err); + my_error(ER_GET_ERRNO, MYF(0), err, hton_name(ht)->str); error=1; } status_var_increment(thd->status_var.ha_savepoint_count); @@ -1969,7 +2232,7 @@ int ha_release_savepoint(THD *thd, SAVEPOINT *sv) if ((err= ht->savepoint_release(ht, thd, (uchar *)(sv+1) + ht->savepoint_offset))) { // cannot happen - my_error(ER_GET_ERRNO, MYF(0), err); + my_error(ER_GET_ERRNO, MYF(0), err, hton_name(ht)->str); error=1; } } @@ -1980,7 +2243,7 @@ int ha_release_savepoint(THD *thd, SAVEPOINT *sv) static my_bool snapshot_handlerton(THD *thd, plugin_ref plugin, void *arg) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton->state == SHOW_OPTION_YES && hton->start_consistent_snapshot) { @@ -2010,7 +2273,7 @@ int ha_start_consistent_snapshot(THD *thd) exist: */ if (warn) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "This MySQL server does not support any " "consistent-read capable storage engine"); return 0; @@ -2020,7 +2283,7 @@ int ha_start_consistent_snapshot(THD *thd) static my_bool flush_handlerton(THD *thd, plugin_ref plugin, void *arg) { - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton->state == SHOW_OPTION_YES && hton->flush_logs && hton->flush_logs(hton)) return TRUE; @@ -2094,44 +2357,11 @@ const char *get_canonical_filename(handler *file, const char *path, } -/** - An interceptor to hijack the text of the error message without - setting an error in the thread. We need the text to present it - in the form of a warning to the user. -*/ +/** delete a table in the engine -struct Ha_delete_table_error_handler: public Internal_error_handler -{ -public: - virtual bool handle_condition(THD *thd, - uint sql_errno, - const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char* msg, - MYSQL_ERROR ** cond_hdl); - char buff[MYSQL_ERRMSG_SIZE]; -}; - - -bool -Ha_delete_table_error_handler:: -handle_condition(THD *, - uint, - const char*, - MYSQL_ERROR::enum_warning_level, - const char* msg, - MYSQL_ERROR ** cond_hdl) -{ - *cond_hdl= NULL; - /* Grab the error message */ - strmake_buf(buff, msg); - return TRUE; -} - - -/** @brief - This should return ENOENT if the file doesn't exists. - The .frm file will be deleted only if we return 0 or ENOENT + @note + ENOENT and HA_ERR_NO_SUCH_TABLE are not considered errors. + The .frm file will be deleted only if we return 0. */ int ha_delete_table(THD *thd, handlerton *table_type, const char *path, const char *db, const char *alias, bool generate_warning) @@ -2143,51 +2373,44 @@ int ha_delete_table(THD *thd, handlerton *table_type, const char *path, TABLE_SHARE dummy_share; DBUG_ENTER("ha_delete_table"); + /* table_type is NULL in ALTER TABLE when renaming only .frm files */ + if (table_type == NULL || table_type == view_pseudo_hton || + ! (file=get_new_handler((TABLE_SHARE*)0, thd->mem_root, table_type))) + DBUG_RETURN(0); + bzero((char*) &dummy_table, sizeof(dummy_table)); bzero((char*) &dummy_share, sizeof(dummy_share)); dummy_table.s= &dummy_share; - /* DB_TYPE_UNKNOWN is used in ALTER TABLE when renaming only .frm files */ - if (table_type == NULL || - ! (file=get_new_handler((TABLE_SHARE*)0, thd->mem_root, table_type))) - DBUG_RETURN(ENOENT); - path= get_canonical_filename(file, path, tmp_path); - if ((error= file->ha_delete_table(path)) && generate_warning) + if ((error= file->ha_delete_table(path))) { /* - Because file->print_error() use my_error() to generate the error message - we use an internal error handler to intercept it and store the text - in a temporary buffer. Later the message will be presented to user - as a warning. + it's not an error if the table doesn't exist in the engine. + warn the user, but still report DROP being a success */ - Ha_delete_table_error_handler ha_delete_table_error_handler; - - /* Fill up strucutures that print_error may need */ - dummy_share.path.str= (char*) path; - dummy_share.path.length= strlen(path); - dummy_share.db.str= (char*) db; - dummy_share.db.length= strlen(db); - dummy_share.table_name.str= (char*) alias; - dummy_share.table_name.length= strlen(alias); - dummy_table.alias.set(alias, dummy_share.table_name.length, - table_alias_charset); - - file->change_table_ptr(&dummy_table, &dummy_share); - - thd->push_internal_handler(&ha_delete_table_error_handler); - file->print_error(error, 0); - - thd->pop_internal_handler(); + bool intercept= error == ENOENT || error == HA_ERR_NO_SUCH_TABLE; - /* - XXX: should we convert *all* errors to warnings here? - What if the error is fatal? - */ - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, error, - ha_delete_table_error_handler.buff); + if (!intercept || generate_warning) + { + /* Fill up strucutures that print_error may need */ + dummy_share.path.str= (char*) path; + dummy_share.path.length= strlen(path); + dummy_share.normalized_path= dummy_share.path; + dummy_share.db.str= (char*) db; + dummy_share.db.length= strlen(db); + dummy_share.table_name.str= (char*) alias; + dummy_share.table_name.length= strlen(alias); + dummy_table.alias.set(alias, dummy_share.table_name.length, + table_alias_charset); + file->change_table_ptr(&dummy_table, &dummy_share); + file->print_error(error, MYF(intercept ? ME_JUST_WARNING : 0)); + } + if (intercept) + error= 0; } delete file; + DBUG_RETURN(error); } @@ -2197,8 +2420,11 @@ int ha_delete_table(THD *thd, handlerton *table_type, const char *path, handler *handler::clone(const char *name, MEM_ROOT *mem_root) { handler *new_handler= get_new_handler(table->s, mem_root, ht); - if (! new_handler) + + if (!new_handler) return NULL; + if (new_handler->set_ha_share_ref(ha_share)) + goto err; /* Allocate handler->ref here because otherwise ha_open will allocate it @@ -2208,7 +2434,7 @@ handler *handler::clone(const char *name, MEM_ROOT *mem_root) if (!(new_handler->ref= (uchar*) alloc_root(mem_root, ALIGN_SIZE(ref_length)*2))) - return NULL; + goto err; /* TODO: Implement a more efficient way to have more than one index open for @@ -2219,9 +2445,13 @@ handler *handler::clone(const char *name, MEM_ROOT *mem_root) */ if (new_handler->ha_open(table, name, table->db_stat, HA_OPEN_IGNORE_IF_LOCKED)) - return NULL; + goto err; return new_handler; + +err: + delete new_handler; + return NULL; } @@ -2255,9 +2485,28 @@ THD *handler::ha_thd(void) const return (table && table->in_use) ? table->in_use : current_thd; } -PSI_table_share *handler::ha_table_share_psi(const TABLE_SHARE *share) const +void handler::unbind_psi() +{ + /* + Notify the instrumentation that this table is not owned + by this thread any more. + */ + PSI_CALL_unbind_table(m_psi); +} + +void handler::rebind_psi() { - return share->m_psi; + /* + Notify the instrumentation that this table is now owned + by this thread. + */ + m_psi= PSI_CALL_rebind_table(ha_table_share_psi(), this, m_psi); +} + + +PSI_table_share *handler::ha_table_share_psi() const +{ + return table_share->m_psi; } /** @brief @@ -2279,6 +2528,8 @@ int handler::ha_open(TABLE *table_arg, const char *name, int mode, table= table_arg; DBUG_ASSERT(table->s == table_share); + DBUG_ASSERT(m_lock_type == F_UNLCK); + DBUG_PRINT("info", ("old m_lock_type: %d F_UNLCK %d", m_lock_type, F_UNLCK)); DBUG_ASSERT(alloc_root_inited(&table->mem_root)); if ((error=open(name,mode,test_if_locked))) @@ -2297,6 +2548,18 @@ int handler::ha_open(TABLE *table_arg, const char *name, int mode, } else { + DBUG_ASSERT(m_psi == NULL); + DBUG_ASSERT(table_share != NULL); + /* + Do not call this for partitions handlers, since it may take too much + resources. + So only use the m_psi on table level, not for individual partitions. + */ + if (!(test_if_locked & HA_OPEN_NO_PSI_CALL)) + { + m_psi= PSI_CALL_open_table(ha_table_share_psi(), this); + } + if (table->s->db_options_in_use & HA_OPTION_READ_ONLY_DATA) table->db_stat|=HA_READ_ONLY; (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL @@ -2305,7 +2568,7 @@ int handler::ha_open(TABLE *table_arg, const char *name, int mode, if (!ref && !(ref= (uchar*) alloc_root(&table->mem_root, ALIGN_SIZE(ref_length)*2))) { - close(); + ha_close(); error=HA_ERR_OUT_OF_MEM; } else @@ -2313,11 +2576,11 @@ int handler::ha_open(TABLE *table_arg, const char *name, int mode, cached_table_flags= table_flags(); } reset_statistics(); - internal_tmp_table= test(test_if_locked & HA_OPEN_INTERNAL_TABLE); + internal_tmp_table= MY_TEST(test_if_locked & HA_OPEN_INTERNAL_TABLE); DBUG_RETURN(error); } -int handler::ha_close() +int handler::ha_close(void) { DBUG_ENTER("ha_close"); /* @@ -2326,9 +2589,197 @@ int handler::ha_close() */ if (table->in_use) status_var_add(table->in_use->status_var.rows_tmp_read, rows_tmp_read); + PSI_CALL_close_table(m_psi); + m_psi= NULL; /* instrumentation handle, invalid after close_table() */ + + /* Detach from ANALYZE tracker */ + tracker= NULL; + + DBUG_ASSERT(m_lock_type == F_UNLCK); + DBUG_ASSERT(inited == NONE); DBUG_RETURN(close()); } + +int handler::ha_rnd_next(uchar *buf) +{ + int result; + DBUG_ENTER("handler::ha_rnd_next"); + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + DBUG_ASSERT(inited == RND); + + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_FETCH_ROW, MAX_KEY, 0, + { result= rnd_next(buf); }) + if (!result) + { + update_rows_read(); + increment_statistics(&SSV::ha_read_rnd_next_count); + } + else if (result == HA_ERR_RECORD_DELETED) + increment_statistics(&SSV::ha_read_rnd_deleted_count); + else + increment_statistics(&SSV::ha_read_rnd_next_count); + + table->status=result ? STATUS_NOT_FOUND: 0; + DBUG_RETURN(result); +} + +int handler::ha_rnd_pos(uchar *buf, uchar *pos) +{ + int result; + DBUG_ENTER("handler::ha_rnd_pos"); + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + /* TODO: Find out how to solve ha_rnd_pos when finding duplicate update. */ + /* DBUG_ASSERT(inited == RND); */ + + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_FETCH_ROW, MAX_KEY, 0, + { result= rnd_pos(buf, pos); }) + increment_statistics(&SSV::ha_read_rnd_count); + if (!result) + update_rows_read(); + table->status=result ? STATUS_NOT_FOUND: 0; + DBUG_RETURN(result); +} + +int handler::ha_index_read_map(uchar *buf, const uchar *key, + key_part_map keypart_map, + enum ha_rkey_function find_flag) +{ + int result; + DBUG_ENTER("handler::ha_index_read_map"); + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + DBUG_ASSERT(inited==INDEX); + + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { result= index_read_map(buf, key, keypart_map, find_flag); }) + increment_statistics(&SSV::ha_read_key_count); + if (!result) + update_index_statistics(); + table->status=result ? STATUS_NOT_FOUND: 0; + DBUG_RETURN(result); +} + +/* + @note: Other index lookup/navigation functions require prior + handler->index_init() call. This function is different, it requires + that the scan is not initialized, and accepts "uint index" as an argument. +*/ + +int handler::ha_index_read_idx_map(uchar *buf, uint index, const uchar *key, + key_part_map keypart_map, + enum ha_rkey_function find_flag) +{ + int result; + DBUG_ASSERT(inited==NONE); + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + DBUG_ASSERT(end_range == NULL); + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_FETCH_ROW, index, 0, + { result= index_read_idx_map(buf, index, key, keypart_map, find_flag); }) + increment_statistics(&SSV::ha_read_key_count); + if (!result) + { + update_rows_read(); + index_rows_read[index]++; + } + table->status=result ? STATUS_NOT_FOUND: 0; + return result; +} + +int handler::ha_index_next(uchar * buf) +{ + int result; + DBUG_ENTER("handler::ha_index_next"); + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + DBUG_ASSERT(inited==INDEX); + + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { result= index_next(buf); }) + increment_statistics(&SSV::ha_read_next_count); + if (!result) + update_index_statistics(); + table->status=result ? STATUS_NOT_FOUND: 0; + DBUG_RETURN(result); +} + +int handler::ha_index_prev(uchar * buf) +{ + int result; + DBUG_ENTER("handler::ha_index_prev"); + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + DBUG_ASSERT(inited==INDEX); + + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { result= index_prev(buf); }) + increment_statistics(&SSV::ha_read_prev_count); + if (!result) + update_index_statistics(); + table->status=result ? STATUS_NOT_FOUND: 0; + DBUG_RETURN(result); +} + +int handler::ha_index_first(uchar * buf) +{ + int result; + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + DBUG_ASSERT(inited==INDEX); + + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { result= index_first(buf); }) + increment_statistics(&SSV::ha_read_first_count); + if (!result) + update_index_statistics(); + table->status=result ? STATUS_NOT_FOUND: 0; + return result; +} + +int handler::ha_index_last(uchar * buf) +{ + int result; + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + DBUG_ASSERT(inited==INDEX); + + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { result= index_last(buf); }) + increment_statistics(&SSV::ha_read_last_count); + if (!result) + update_index_statistics(); + table->status=result ? STATUS_NOT_FOUND: 0; + return result; +} + +int handler::ha_index_next_same(uchar *buf, const uchar *key, uint keylen) +{ + int result; + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + DBUG_ASSERT(inited==INDEX); + + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { result= index_next_same(buf, key, keylen); }) + increment_statistics(&SSV::ha_read_next_count); + if (!result) + update_index_statistics(); + table->status=result ? STATUS_NOT_FOUND: 0; + return result; +} + + +bool handler::ha_was_semi_consistent_read() +{ + bool result= was_semi_consistent_read(); + if (result) + increment_statistics(&SSV::ha_read_retry_count); + return result; +} + /* Initialize handler for random reading, with error handling */ int handler::ha_rnd_init_with_error(bool scan) @@ -2404,11 +2855,17 @@ compute_next_insert_id(ulonglong nr,struct system_variables *variables) nr= nr + 1; // optimization of the formula below else { - nr= (((nr+ variables->auto_increment_increment - - variables->auto_increment_offset)) / - (ulonglong) variables->auto_increment_increment); - nr= (nr* (ulonglong) variables->auto_increment_increment + - variables->auto_increment_offset); + /* + Calculating the number of complete auto_increment_increment extents: + */ + nr= (nr + variables->auto_increment_increment - + variables->auto_increment_offset) / + (ulonglong) variables->auto_increment_increment; + /* + Adding an offset to the auto_increment_increment extent boundary: + */ + nr= nr * (ulonglong) variables->auto_increment_increment + + variables->auto_increment_offset; } if (unlikely(nr <= save_nr)) @@ -2462,8 +2919,14 @@ prev_insert_id(ulonglong nr, struct system_variables *variables) } if (variables->auto_increment_increment == 1) return nr; // optimization of the formula below - nr= (((nr - variables->auto_increment_offset)) / - (ulonglong) variables->auto_increment_increment); + /* + Calculating the number of complete auto_increment_increment extents: + */ + nr= (nr - variables->auto_increment_offset) / + (ulonglong) variables->auto_increment_increment; + /* + Adding an offset to the auto_increment_increment extent boundary: + */ return (nr * (ulonglong) variables->auto_increment_increment + variables->auto_increment_offset); } @@ -2673,7 +3136,8 @@ int handler::update_auto_increment() if (unlikely(nr == ULONGLONG_MAX)) DBUG_RETURN(HA_ERR_AUTOINC_ERANGE); - DBUG_PRINT("info",("auto_increment: %llu",nr)); + DBUG_PRINT("info",("auto_increment: %llu nb_reserved_values: %llu", + nr, append ? nb_reserved_values : 0)); /* Store field without warning (Warning will be printed by insert) */ save_count_cuted_fields= thd->count_cuted_fields; @@ -2684,23 +3148,45 @@ int handler::update_auto_increment() if (unlikely(tmp)) // Out of range value in store { /* - It's better to return an error here than getting a confusing - 'duplicate key error' later. + First, test if the query was aborted due to strict mode constraints + or new field value greater than maximum integer value: */ - result= HA_ERR_AUTOINC_ERANGE; + if (thd->killed == KILL_BAD_DATA || + nr > table->next_number_field->get_max_int_value()) + { + /* + It's better to return an error here than getting a confusing + 'duplicate key error' later. + */ + result= HA_ERR_AUTOINC_ERANGE; + } + else + { + /* + Field refused this value (overflow) and truncated it, use the result + of the truncation (which is going to be inserted); however we try to + decrease it to honour auto_increment_* variables. + That will shift the left bound of the reserved interval, we don't + bother shifting the right bound (anyway any other value from this + interval will cause a duplicate key). + */ + nr= prev_insert_id(table->next_number_field->val_int(), variables); + if (unlikely(table->next_number_field->store((longlong)nr, TRUE))) + nr= table->next_number_field->val_int(); + } } if (append) { - DBUG_PRINT("info",("nb_reserved_values: %llu",nb_reserved_values)); - auto_inc_interval_for_cur_row.replace(nr, nb_reserved_values, variables->auto_increment_increment); auto_inc_intervals_count++; /* Row-based replication does not need to store intervals in binlog */ - if (mysql_bin_log.is_open() && !thd->is_current_stmt_binlog_format_row()) - thd->auto_inc_intervals_in_cur_stmt_for_binlog.append(auto_inc_interval_for_cur_row.minimum(), - auto_inc_interval_for_cur_row.values(), - variables->auto_increment_increment); + if (((WSREP(thd) && wsrep_emulate_bin_log ) || mysql_bin_log.is_open()) + && !thd->is_current_stmt_binlog_format_row()) + thd->auto_inc_intervals_in_cur_stmt_for_binlog. + append(auto_inc_interval_for_cur_row.minimum(), + auto_inc_interval_for_cur_row.values(), + variables->auto_increment_increment); } /* @@ -2789,7 +3275,7 @@ void handler::get_auto_increment(ulonglong offset, ulonglong increment, if (table->s->next_number_keypart == 0) { // Autoincrement at key-start - error=ha_index_last(table->record[1]); + error= ha_index_last(table->record[1]); /* MySQL implicitely assumes such method does locking (as MySQL decides to use nr+increment without checking again with the handler, in @@ -2837,6 +3323,9 @@ void handler::get_auto_increment(ulonglong offset, ulonglong increment, void handler::ha_release_auto_increment() { DBUG_ENTER("ha_release_auto_increment"); + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK || + (!next_insert_id && !insert_id_for_cur_row)); release_auto_increment(); insert_id_for_cur_row= 0; auto_inc_interval_for_cur_row.replace(0, 0, 0); @@ -2854,33 +3343,60 @@ void handler::ha_release_auto_increment() } -void handler::print_keydup_error(uint key_nr, const char *msg, myf errflag) +/** + Construct and emit duplicate key error message using information + from table's record buffer. + + @param table TABLE object which record buffer should be used as + source for column values. + @param key Key description. + @param msg Error message template to which key value should be + added. + @param errflag Flags for my_error() call. +*/ + +void print_keydup_error(TABLE *table, KEY *key, const char *msg, myf errflag) { /* Write the duplicated key in the error message */ - char key[MAX_KEY_LENGTH]; - String str(key,sizeof(key),system_charset_info); + char key_buff[MAX_KEY_LENGTH]; + String str(key_buff,sizeof(key_buff),system_charset_info); - if (key_nr == MAX_KEY) + if (key == NULL) { - /* Key is unknown */ - str.copy("", 0, system_charset_info); - my_printf_error(ER_DUP_ENTRY, msg, errflag, str.c_ptr(), "*UNKNOWN*"); + /* + Key is unknown. Should only happen if storage engine reports wrong + duplicate key number. + */ + my_printf_error(ER_DUP_ENTRY, msg, errflag, "", "*UNKNOWN*"); } else { /* Table is opened and defined at this point */ - key_unpack(&str,table,(uint) key_nr); + key_unpack(&str,table, key); uint max_length=MYSQL_ERRMSG_SIZE-(uint) strlen(msg); if (str.length() >= max_length) { str.length(max_length-4); str.append(STRING_WITH_LEN("...")); } - my_printf_error(ER_DUP_ENTRY, msg, - errflag, str.c_ptr_safe(), table->key_info[key_nr].name); + my_printf_error(ER_DUP_ENTRY, msg, errflag, str.c_ptr_safe(), key->name); } } +/** + Construct and emit duplicate key error message using information + from table's record buffer. + + @sa print_keydup_error(table, key, msg, errflag). +*/ + +void print_keydup_error(TABLE *table, KEY *key, myf errflag) +{ + print_keydup_error(table, key, + ER_THD(table->in_use, ER_DUP_ENTRY_WITH_KEY_NAME), + errflag); +} + /** Print error that we got from handler function. @@ -2900,7 +3416,7 @@ void handler::print_error(int error, myf errflag) DBUG_ENTER("handler::print_error"); DBUG_PRINT("enter",("error: %d",error)); - int textno=ER_GET_ERRNO; + int textno= -1; // impossible value switch (error) { case EACCES: textno=ER_OPEN_AS_READONLY; @@ -2910,6 +3426,7 @@ void handler::print_error(int error, myf errflag) break; case ENOENT: case ENOTDIR: + case ELOOP: textno=ER_FILE_NOT_FOUND; break; case ENOSPC: @@ -2932,8 +3449,8 @@ void handler::print_error(int error, myf errflag) break; case HA_ERR_ABORTED_BY_USER: { - DBUG_ASSERT(table->in_use->killed); - table->in_use->send_kill_message(); + DBUG_ASSERT(ha_thd()->killed); + ha_thd()->send_kill_message(); DBUG_VOID_RETURN; } case HA_ERR_WRONG_MRG_TABLE_DEF: @@ -2944,9 +3461,9 @@ void handler::print_error(int error, myf errflag) if (table) { uint key_nr=get_dup_key(error); - if ((int) key_nr >= 0) + if ((int) key_nr >= 0 && key_nr < table->s->keys) { - print_keydup_error(key_nr, ER(ER_DUP_ENTRY_WITH_KEY_NAME), errflag); + print_keydup_error(table, &table->key_info[key_nr], errflag); DBUG_VOID_RETURN; } } @@ -2955,44 +3472,31 @@ void handler::print_error(int error, myf errflag) } case HA_ERR_FOREIGN_DUPLICATE_KEY: { - uint key_nr= get_dup_key(error); - if ((int) key_nr >= 0) - { - uint max_length; - /* Write the key in the error message */ - char key[MAX_KEY_LENGTH]; - String str(key,sizeof(key),system_charset_info); - /* Table is opened and defined at this point */ + char rec_buf[MAX_KEY_LENGTH]; + String rec(rec_buf, sizeof(rec_buf), system_charset_info); + /* Table is opened and defined at this point */ - /* - Use primary_key instead of key_nr because key_nr is a key - number in the child FK table, not in our 'table'. See - Bug#12661768 UPDATE IGNORE CRASHES SERVER IF TABLE IS INNODB - AND IT IS PARENT FOR OTHER ONE This bug gets a better fix in - MySQL 5.6, but it is too risky to get that in 5.1 and 5.5 - (extending the handler interface and adding new error message - codes) - */ - if (table->s->primary_key < MAX_KEY) - key_unpack(&str,table,table->s->primary_key); - else - { - LEX_CUSTRING tmp= {USTRING_WITH_LEN("Unknown key value")}; - str.set((const char*) tmp.str, tmp.length, system_charset_info); - } - max_length= (MYSQL_ERRMSG_SIZE- - (uint) strlen(ER(ER_FOREIGN_DUPLICATE_KEY))); - if (str.length() >= max_length) - { - str.length(max_length-4); - str.append(STRING_WITH_LEN("...")); + /* + Just print the subset of fields that are part of the first index, + printing the whole row from there is not easy. + */ + key_unpack(&rec, table, &table->key_info[0]); + + char child_table_name[NAME_LEN + 1]; + char child_key_name[NAME_LEN + 1]; + if (get_foreign_dup_key(child_table_name, sizeof(child_table_name), + child_key_name, sizeof(child_key_name))) + { + my_error(ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO, errflag, + table_share->table_name.str, rec.c_ptr_safe(), + child_table_name, child_key_name); } - my_error(ER_FOREIGN_DUPLICATE_KEY, errflag, table_share->table_name.str, - str.c_ptr_safe(), key_nr+1); - DBUG_VOID_RETURN; + else + { + my_error(ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO, errflag, + table_share->table_name.str, rec.c_ptr_safe()); } - textno= ER_DUP_KEY; - break; + DBUG_VOID_RETURN; } case HA_ERR_NULL_IN_SPATIAL: my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, errflag); @@ -3030,7 +3534,9 @@ void handler::print_error(int error, myf errflag) textno=ER_OUT_OF_RESOURCES; break; case HA_ERR_WRONG_COMMAND: - textno=ER_ILLEGAL_HA; + my_error(ER_ILLEGAL_HA, MYF(0), table_type(), table_share->db.str, + table_share->table_name.str); + DBUG_VOID_RETURN; break; case HA_ERR_OLD_FILE: textno=ER_OLD_KEYFILE; @@ -3067,14 +3573,18 @@ void handler::print_error(int error, myf errflag) { String str; get_error_message(error, &str); - my_error(ER_ROW_IS_REFERENCED_2, errflag, str.c_ptr_safe()); + my_printf_error(ER_ROW_IS_REFERENCED_2, + ER(str.length() ? ER_ROW_IS_REFERENCED_2 : ER_ROW_IS_REFERENCED), + errflag, str.c_ptr_safe()); DBUG_VOID_RETURN; } case HA_ERR_NO_REFERENCED_ROW: { String str; get_error_message(error, &str); - my_error(ER_NO_REFERENCED_ROW_2, errflag, str.c_ptr_safe()); + my_printf_error(ER_NO_REFERENCED_ROW_2, + ER(str.length() ? ER_NO_REFERENCED_ROW_2 : ER_NO_REFERENCED_ROW), + errflag, str.c_ptr_safe()); DBUG_VOID_RETURN; } case HA_ERR_TABLE_DEF_CHANGED: @@ -3097,7 +3607,8 @@ void handler::print_error(int error, myf errflag) DBUG_VOID_RETURN; } case HA_ERR_TABLE_NEEDS_UPGRADE: - textno=ER_TABLE_NEEDS_UPGRADE; + my_error(ER_TABLE_NEEDS_UPGRADE, errflag, + "TABLE", table_share->table_name.str); break; case HA_ERR_NO_PARTITION_FOUND: textno=ER_WRONG_PARTITION_NAME; @@ -3111,7 +3622,7 @@ void handler::print_error(int error, myf errflag) case HA_ERR_AUTOINC_ERANGE: textno= error; my_error(textno, errflag, table->next_number_field->field_name, - table->in_use->warning_info->current_row_for_warning()); + table->in_use->get_stmt_da()->current_row_for_warning()); DBUG_VOID_RETURN; break; case HA_ERR_TOO_MANY_CONCURRENT_TRXS: @@ -3120,6 +3631,9 @@ void handler::print_error(int error, myf errflag) case HA_ERR_INDEX_COL_TOO_LONG: textno= ER_INDEX_COLUMN_TOO_LONG; break; + case HA_ERR_NOT_IN_LOCK_PARTITIONS: + textno=ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET; + break; case HA_ERR_INDEX_CORRUPT: textno= ER_INDEX_CORRUPT; break; @@ -3148,21 +3662,12 @@ void handler::print_error(int error, myf errflag) my_error(ER_GET_ERRMSG, errflag, error, str.c_ptr(), engine); } } - else if (error >= HA_ERR_FIRST && error <= HA_ERR_LAST) - { - const char* engine= table_type(); - const char *errmsg= handler_error_messages[error - HA_ERR_FIRST]; - my_error(ER_GET_ERRMSG, errflag, error, errmsg, engine); - SET_FATAL_ERROR; - } else - { - my_error(ER_GET_ERRNO, errflag,error); - /* SET_FATAL_ERROR; */ - } + my_error(ER_GET_ERRNO, errflag, error, table_type()); DBUG_VOID_RETURN; } } + DBUG_ASSERT(textno > 0); if (fatal_error) { /* Ensure this becomes a true error */ @@ -3175,8 +3680,18 @@ void handler::print_error(int error, myf errflag) */ errflag|= ME_NOREFRESH; } - } - my_error(textno, errflag, table_share->table_name.str, error); + } + + /* if we got an OS error from a file-based engine, specify a path of error */ + if (error < HA_ERR_FIRST && bas_ext()[0]) + { + char buff[FN_REFLEN]; + strxnmov(buff, sizeof(buff), + table_share->normalized_path.str, bas_ext()[0], NULL); + my_error(textno, errflag, buff, error); + } + else + my_error(textno, errflag, table_share->table_name.str, error); DBUG_VOID_RETURN; } @@ -3192,10 +3707,11 @@ void handler::print_error(int error, myf errflag) */ bool handler::get_error_message(int error, String* buf) { + DBUG_EXECUTE_IF("external_lock_failure", + buf->set_ascii(STRING_WITH_LEN("KABOOM!"));); return FALSE; } - /** Check for incompatible collation changes. @@ -3216,7 +3732,7 @@ int handler::check_collation_compatibility() for (; key < key_end; key++) { KEY_PART_INFO *key_part= key->key_part; - KEY_PART_INFO *key_part_end= key_part + key->key_parts; + KEY_PART_INFO *key_part_end= key_part + key->user_defined_key_parts; for (; key_part < key_part_end; key_part++) { if (!key_part->fieldnr) @@ -3236,9 +3752,10 @@ int handler::check_collation_compatibility() (cs_number == 33 || /* utf8_general_ci - bug #27877 */ cs_number == 35))) /* ucs2_general_ci - bug #27877 */ return HA_ADMIN_NEEDS_UPGRADE; - } - } - } + } + } + } + return 0; } @@ -3249,6 +3766,9 @@ int handler::ha_check_for_upgrade(HA_CHECK_OPT *check_opt) KEY *keyinfo, *keyend; KEY_PART_INFO *keypart, *keypartend; + if (table->s->incompatible_version) + return HA_ADMIN_NEEDS_ALTER; + if (!table->s->mysql_version) { /* check for blob-in-key error */ @@ -3257,7 +3777,7 @@ int handler::ha_check_for_upgrade(HA_CHECK_OPT *check_opt) for (; keyinfo < keyend; keyinfo++) { keypart= keyinfo->key_part; - keypartend= keypart + keyinfo->key_parts; + keypartend= keypart + keyinfo->user_defined_key_parts; for (; keypart < keypartend; keypart++) { if (!keypart->fieldnr) @@ -3349,6 +3869,8 @@ err: */ uint handler::get_dup_key(int error) { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); DBUG_ENTER("handler::get_dup_key"); table->file->errkey = (uint) -1; if (error == HA_ERR_FOUND_DUPP_KEY || error == HA_ERR_FOREIGN_DUPLICATE_KEY || @@ -3378,7 +3900,12 @@ int handler::delete_table(const char *name) { int saved_error= 0; int error= 0; - int enoent_or_zero= ENOENT; // Error if no file was deleted + int enoent_or_zero; + + if (ht->discover_table) + enoent_or_zero= 0; // the table may not exist in the engine, it's ok + else + enoent_or_zero= ENOENT; // the first file of bas_ext() *must* exist for (const char **ext=bas_ext(); *ext ; ext++) { @@ -3452,6 +3979,8 @@ void handler::drop_table(const char *name) int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt) { int error; + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); if ((table->s->mysql_version >= MYSQL_VERSION_ID) && (check_opt->sql_flags & TT_FOR_UPGRADE)) @@ -3538,6 +4067,8 @@ int handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data, uint *dup_key_found) { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_WRLCK); mark_trx_read_write(); return bulk_update_row(old_data, new_data, dup_key_found); @@ -3553,6 +4084,8 @@ handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data, int handler::ha_delete_all_rows() { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_WRLCK); mark_trx_read_write(); return delete_all_rows(); @@ -3568,6 +4101,8 @@ handler::ha_delete_all_rows() int handler::ha_truncate() { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_WRLCK); mark_trx_read_write(); return truncate(); @@ -3583,6 +4118,8 @@ handler::ha_truncate() int handler::ha_reset_auto_increment(ulonglong value) { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_WRLCK); mark_trx_read_write(); return reset_auto_increment(value); @@ -3598,6 +4135,8 @@ handler::ha_reset_auto_increment(ulonglong value) int handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt) { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_WRLCK); mark_trx_read_write(); return optimize(thd, check_opt); @@ -3613,6 +4152,8 @@ handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt) int handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt) { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); mark_trx_read_write(); return analyze(thd, check_opt); @@ -3628,6 +4169,8 @@ handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt) bool handler::ha_check_and_repair(THD *thd) { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_UNLCK); mark_trx_read_write(); return check_and_repair(thd); @@ -3643,6 +4186,8 @@ handler::ha_check_and_repair(THD *thd) int handler::ha_disable_indexes(uint mode) { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); mark_trx_read_write(); return disable_indexes(mode); @@ -3658,6 +4203,8 @@ handler::ha_disable_indexes(uint mode) int handler::ha_enable_indexes(uint mode) { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); mark_trx_read_write(); return enable_indexes(mode); @@ -3673,26 +4220,106 @@ handler::ha_enable_indexes(uint mode) int handler::ha_discard_or_import_tablespace(my_bool discard) { + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_WRLCK); mark_trx_read_write(); return discard_or_import_tablespace(discard); } -/** - Prepare for alter: public interface. +bool handler::ha_prepare_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info) +{ + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + mark_trx_read_write(); + + return prepare_inplace_alter_table(altered_table, ha_alter_info); +} - Called to prepare an *online* ALTER. - @sa handler::prepare_for_alter() +bool handler::ha_commit_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info, + bool commit) +{ + /* + At this point we should have an exclusive metadata lock on the table. + The exception is if we're about to roll back changes (commit= false). + In this case, we might be rolling back after a failed lock upgrade, + so we could be holding the same lock level as for inplace_alter_table(). + */ + DBUG_ASSERT(ha_thd()->mdl_context.is_lock_owner(MDL_key::TABLE, + table->s->db.str, + table->s->table_name.str, + MDL_EXCLUSIVE) || + !commit); + + return commit_inplace_alter_table(altered_table, ha_alter_info, commit); +} + + +/* + Default implementation to support in-place alter table + and old online add/drop index API */ -void -handler::ha_prepare_for_alter() +enum_alter_inplace_result +handler::check_if_supported_inplace_alter(TABLE *altered_table, + Alter_inplace_info *ha_alter_info) { - mark_trx_read_write(); + DBUG_ENTER("handler::check_if_supported_inplace_alter"); + + HA_CREATE_INFO *create_info= ha_alter_info->create_info; + + Alter_inplace_info::HA_ALTER_FLAGS inplace_offline_operations= + Alter_inplace_info::ALTER_COLUMN_EQUAL_PACK_LENGTH | + Alter_inplace_info::ALTER_COLUMN_NAME | + Alter_inplace_info::ALTER_COLUMN_DEFAULT | + Alter_inplace_info::ALTER_COLUMN_OPTION | + Alter_inplace_info::CHANGE_CREATE_OPTION | + Alter_inplace_info::ALTER_PARTITIONED | + Alter_inplace_info::ALTER_RENAME; + + /* Is there at least one operation that requires copy algorithm? */ + if (ha_alter_info->handler_flags & ~inplace_offline_operations) + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + + /* + ALTER TABLE tbl_name CONVERT TO CHARACTER SET .. and + ALTER TABLE table_name DEFAULT CHARSET = .. most likely + change column charsets and so not supported in-place through + old API. + + Changing of PACK_KEYS, MAX_ROWS and ROW_FORMAT options were + not supported as in-place operations in old API either. + */ + if (create_info->used_fields & (HA_CREATE_USED_CHARSET | + HA_CREATE_USED_DEFAULT_CHARSET | + HA_CREATE_USED_PACK_KEYS | + HA_CREATE_USED_MAX_ROWS) || + (table->s->row_type != create_info->row_type)) + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); - prepare_for_alter(); + uint table_changes= (ha_alter_info->handler_flags & + Alter_inplace_info::ALTER_COLUMN_EQUAL_PACK_LENGTH) ? + IS_EQUAL_PACK_LENGTH : IS_EQUAL_YES; + if (table->file->check_if_incompatible_data(create_info, table_changes) + == COMPATIBLE_DATA_YES) + DBUG_RETURN(HA_ALTER_INPLACE_NO_LOCK); + + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); +} + +void Alter_inplace_info::report_unsupported_error(const char *not_supported, + const char *try_instead) +{ + if (unsupported_reason == NULL) + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0), + not_supported, try_instead); + else + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), + not_supported, unsupported_reason, try_instead); } @@ -3705,6 +4332,7 @@ handler::ha_prepare_for_alter() int handler::ha_rename_table(const char *from, const char *to) { + DBUG_ASSERT(m_lock_type == F_UNLCK); mark_trx_read_write(); return rename_table(from, to); @@ -3737,6 +4365,7 @@ handler::ha_delete_table(const char *name) void handler::ha_drop_table(const char *name) { + DBUG_ASSERT(m_lock_type == F_UNLCK); mark_trx_read_write(); return drop_table(name); @@ -3750,12 +4379,13 @@ handler::ha_drop_table(const char *name) */ int -handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info) +handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info_arg) { + DBUG_ASSERT(m_lock_type == F_UNLCK); mark_trx_read_write(); - int error= create(name, form, info); + int error= create(name, form, info_arg); if (!error && - !(info->options & (HA_LEX_CREATE_TMP_TABLE | HA_CREATE_TMP_ALTER))) + !(info_arg->options & (HA_LEX_CREATE_TMP_TABLE | HA_CREATE_TMP_ALTER))) mysql_audit_create_table(form); return error; } @@ -3764,17 +4394,25 @@ handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info) /** Create handler files for CREATE TABLE: public interface. - @sa handler::create_handler_files() + @sa handler::create_partitioning_metadata() */ int -handler::ha_create_handler_files(const char *name, const char *old_name, - int action_flag, HA_CREATE_INFO *info) +handler::ha_create_partitioning_metadata(const char *name, + const char *old_name, + int action_flag) { - if (!opt_readonly || !info || !(info->options & HA_LEX_CREATE_TMP_TABLE)) - mark_trx_read_write(); + /* + Normally this is done when unlocked, but in fast_alter_partition_table, + it is done on an already locked handler when preparing to alter/rename + partitions. + */ + DBUG_ASSERT(m_lock_type == F_UNLCK || + (!old_name && strcmp(name, table_share->path.str))); + - return create_handler_files(name, old_name, action_flag, info); + mark_trx_read_write(); + return create_partitioning_metadata(name, old_name, action_flag); } @@ -3786,12 +4424,19 @@ handler::ha_create_handler_files(const char *name, const char *old_name, int handler::ha_change_partitions(HA_CREATE_INFO *create_info, - const char *path, - ulonglong * const copied, - ulonglong * const deleted, - const uchar *pack_frm_data, - size_t pack_frm_len) + const char *path, + ulonglong * const copied, + ulonglong * const deleted, + const uchar *pack_frm_data, + size_t pack_frm_len) { + /* + Must have at least RDLCK or be a TMP table. Read lock is needed to read + from current partitions and write lock will be taken on new partitions. + */ + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + mark_trx_read_write(); return change_partitions(create_info, path, copied, deleted, @@ -3808,6 +4453,8 @@ handler::ha_change_partitions(HA_CREATE_INFO *create_info, int handler::ha_drop_partitions(const char *path) { + DBUG_ASSERT(!table->db_stat); + mark_trx_read_write(); return drop_partitions(path); @@ -3823,6 +4470,8 @@ handler::ha_drop_partitions(const char *path) int handler::ha_rename_partitions(const char *path) { + DBUG_ASSERT(!table->db_stat); + mark_trx_read_write(); return rename_partitions(path); @@ -3831,10 +4480,10 @@ handler::ha_rename_partitions(const char *path) /** Tell the storage engine that it is allowed to "disable transaction" in the - handler. It is a hint that ACID is not required - it is used in NDB for + handler. It is a hint that ACID is not required - it was used in NDB for ALTER TABLE, for example, when data are copied to temporary table. A storage engine may treat this hint any way it likes. NDB for example - starts to commit every now and then automatically. + started to commit every now and then automatically. This hint can be safely ignored. */ int ha_enable_transaction(THD *thd, bool on) @@ -3883,7 +4532,7 @@ int handler::index_next_same(uchar *buf, const uchar *key, uint keylen) table->record[0]= buf; key_info= table->key_info + active_index; key_part= key_info->key_part; - key_part_end= key_part + key_info->key_parts; + key_part_end= key_part + key_info->user_defined_key_parts; for (; key_part < key_part_end; key_part++) { DBUG_ASSERT(key_part->field); @@ -4062,132 +4711,68 @@ end: */ int ha_create_table(THD *thd, const char *path, const char *db, const char *table_name, - HA_CREATE_INFO *create_info, - bool update_create_info) + HA_CREATE_INFO *create_info, LEX_CUSTRING *frm) { int error= 1; TABLE table; char name_buff[FN_REFLEN]; const char *name; TABLE_SHARE share; + bool temp_table __attribute__((unused)) = + create_info->options & (HA_LEX_CREATE_TMP_TABLE | HA_CREATE_TMP_ALTER); + DBUG_ENTER("ha_create_table"); - + init_tmp_table_share(thd, &share, db, 0, table_name, path); - if (open_table_def(thd, &share, 0) || - open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, 0, &table, - TRUE)) - goto err; - if (update_create_info) - update_create_info_from_table(create_info, &table); + if (frm) + { + bool write_frm_now= !create_info->db_type->discover_table && + !create_info->tmp_table(); - name= get_canonical_filename(table.file, share.path.str, name_buff); + share.frm_image= frm; - error= table.file->ha_create(name, &table, create_info); - (void) closefrm(&table, 0); - if (error) + // open an frm image + if (share.init_from_binary_frm_image(thd, write_frm_now, + frm->str, frm->length)) + goto err; + } + else { - strxmov(name_buff, db, ".", table_name, NullS); - my_error(ER_CANT_CREATE_TABLE, MYF(ME_BELL+ME_WAITTANG), name_buff, error); + // open an frm file + share.db_plugin= ha_lock_engine(thd, create_info->db_type); + + if (open_table_def(thd, &share)) + goto err; } -err: - free_table_share(&share); - DBUG_RETURN(error != 0); -} -/** - Try to discover table from engine. + share.m_psi= PSI_CALL_get_table_share(temp_table, &share); - @note - If found, write the frm file to disk. + if (open_table_from_share(thd, &share, "", 0, READ_ALL, 0, &table, true)) + goto err; - @retval - -1 Table did not exists - @retval - 0 Table created ok - @retval - > 0 Error, table existed but could not be created -*/ -int ha_create_table_from_engine(THD* thd, const char *db, const char *name) -{ - int error; - uchar *frmblob; - size_t frmlen; - char path[FN_REFLEN + 1]; - HA_CREATE_INFO create_info; - TABLE table; - TABLE_SHARE share; - DBUG_ENTER("ha_create_table_from_engine"); - DBUG_PRINT("enter", ("name '%s'.'%s'", db, name)); + update_create_info_from_table(create_info, &table); - bzero((uchar*) &create_info,sizeof(create_info)); - if ((error= ha_discover(thd, db, name, &frmblob, &frmlen))) - { - /* Table could not be discovered and thus not created */ - DBUG_RETURN(error); - } + name= get_canonical_filename(table.file, share.path.str, name_buff); - /* - Table exists in handler and could be discovered - frmblob and frmlen are set, write the frm to disk - */ + error= table.file->ha_create(name, &table, create_info); - build_table_filename(path, sizeof(path) - 1, db, name, "", 0); - // Save the frm file - error= writefrm(path, frmblob, frmlen); - my_free(frmblob); if (error) - DBUG_RETURN(2); - - init_tmp_table_share(thd, &share, db, 0, name, path); - if (open_table_def(thd, &share, 0)) { - DBUG_RETURN(3); + if (!thd->is_error()) + my_error(ER_CANT_CREATE_TABLE, MYF(0), db, table_name, error); + table.file->print_error(error, MYF(ME_JUST_WARNING)); + PSI_CALL_drop_table_share(temp_table, share.db.str, share.db.length, + share.table_name.str, share.table_name.length); } - if (open_table_from_share(thd, &share, "" ,0, 0, 0, &table, FALSE)) - { - free_table_share(&share); - DBUG_RETURN(3); - } - - update_create_info_from_table(&create_info, &table); - create_info.table_options|= HA_OPTION_CREATE_FROM_ENGINE; - - get_canonical_filename(table.file, path, path); - error=table.file->ha_create(path, &table, &create_info); - (void) closefrm(&table, 1); + (void) closefrm(&table, 0); + +err: + free_table_share(&share); DBUG_RETURN(error != 0); } - -/** - Try to find a table in a storage engine. - - @param db Normalized table schema name - @param name Normalized table name. - @param[out] exists Only valid if the function succeeded. - - @retval TRUE An error is found - @retval FALSE Success, check *exists -*/ - -bool -ha_check_if_table_exists(THD* thd, const char *db, const char *name, - bool *exists) -{ - uchar *frmblob= NULL; - size_t frmlen; - DBUG_ENTER("ha_check_if_table_exists"); - - *exists= ! ha_discover(thd, db, name, &frmblob, &frmlen); - if (*exists) - my_free(frmblob); - - DBUG_RETURN(FALSE); -} - - void st_ha_check_opt::init() { flags= sql_flags= 0; @@ -4221,11 +4806,13 @@ int ha_init_key_cache(const char *name, KEY_CACHE *key_cache, void *unused uint division_limit= (uint)key_cache->param_division_limit; uint age_threshold= (uint)key_cache->param_age_threshold; uint partitions= (uint)key_cache->param_partitions; + uint changed_blocks_hash_size= (uint)key_cache->changed_blocks_hash_size; mysql_mutex_unlock(&LOCK_global_system_variables); DBUG_RETURN(!init_key_cache(key_cache, tmp_block_size, tmp_buff_size, division_limit, age_threshold, + changed_blocks_hash_size, partitions)); } DBUG_RETURN(0); @@ -4246,10 +4833,12 @@ int ha_resize_key_cache(KEY_CACHE *key_cache) long tmp_block_size= (long) key_cache->param_block_size; uint division_limit= (uint)key_cache->param_division_limit; uint age_threshold= (uint)key_cache->param_age_threshold; + uint changed_blocks_hash_size= (uint)key_cache->changed_blocks_hash_size; mysql_mutex_unlock(&LOCK_global_system_variables); DBUG_RETURN(!resize_key_cache(key_cache, tmp_block_size, tmp_buff_size, - division_limit, age_threshold)); + division_limit, age_threshold, + changed_blocks_hash_size)); } DBUG_RETURN(0); } @@ -4289,10 +4878,12 @@ int ha_repartition_key_cache(KEY_CACHE *key_cache) uint division_limit= (uint)key_cache->param_division_limit; uint age_threshold= (uint)key_cache->param_age_threshold; uint partitions= (uint)key_cache->param_partitions; + uint changed_blocks_hash_size= (uint)key_cache->changed_blocks_hash_size; mysql_mutex_unlock(&LOCK_global_system_variables); DBUG_RETURN(!repartition_key_cache(key_cache, tmp_block_size, tmp_buff_size, division_limit, age_threshold, + changed_blocks_hash_size, partitions)); } DBUG_RETURN(0); @@ -4310,286 +4901,418 @@ int ha_change_key_cache(KEY_CACHE *old_key_cache, } -/** - Try to discover one table from handler(s). - - @retval - -1 Table did not exists - @retval - 0 OK. In this case *frmblob and *frmlen are set - @retval - >0 error. frmblob and frmlen may not be set -*/ -struct st_discover_args -{ - const char *db; - const char *name; - uchar **frmblob; - size_t *frmlen; -}; - static my_bool discover_handlerton(THD *thd, plugin_ref plugin, void *arg) { - st_discover_args *vargs= (st_discover_args *)arg; - handlerton *hton= plugin_data(plugin, handlerton *); - if (hton->state == SHOW_OPTION_YES && hton->discover && - (!(hton->discover(hton, thd, vargs->db, vargs->name, - vargs->frmblob, - vargs->frmlen)))) - return TRUE; + TABLE_SHARE *share= (TABLE_SHARE *)arg; + handlerton *hton= plugin_hton(plugin); + if (hton->state == SHOW_OPTION_YES && hton->discover_table) + { + share->db_plugin= plugin; + int error= hton->discover_table(hton, thd, share); + if (error != HA_ERR_NO_SUCH_TABLE) + { + if (error) + { + if (!share->error) + { + share->error= OPEN_FRM_ERROR_ALREADY_ISSUED; + plugin_unlock(0, share->db_plugin); + } - return FALSE; + /* + report an error, unless it is "generic" and a more + specific one was already reported + */ + if (error != HA_ERR_GENERIC || !thd->is_error()) + my_error(ER_GET_ERRNO, MYF(0), error, plugin_name(plugin)->str); + share->db_plugin= 0; + } + else + share->error= OPEN_FRM_OK; + + status_var_increment(thd->status_var.ha_discover_count); + return TRUE; // abort the search + } + share->db_plugin= 0; + } + + DBUG_ASSERT(share->error == OPEN_FRM_OPEN_ERROR); + return FALSE; // continue with the next engine } -int ha_discover(THD *thd, const char *db, const char *name, - uchar **frmblob, size_t *frmlen) +int ha_discover_table(THD *thd, TABLE_SHARE *share) { - int error= -1; // Table does not exist in any handler - DBUG_ENTER("ha_discover"); - DBUG_PRINT("enter", ("db: %s, name: %s", db, name)); - st_discover_args args= {db, name, frmblob, frmlen}; + DBUG_ENTER("ha_discover_table"); + int found; - if (is_prefix(name,tmp_file_prefix)) /* skip temporary tables */ - DBUG_RETURN(error); + DBUG_ASSERT(share->error == OPEN_FRM_OPEN_ERROR); // share is not OK yet - if (plugin_foreach(thd, discover_handlerton, - MYSQL_STORAGE_ENGINE_PLUGIN, &args)) - error= 0; + if (!engines_with_discover) + found= FALSE; + else if (share->db_plugin) + found= discover_handlerton(thd, share->db_plugin, share); + else + found= plugin_foreach(thd, discover_handlerton, + MYSQL_STORAGE_ENGINE_PLUGIN, share); + + if (!found) + open_table_error(share, OPEN_FRM_OPEN_ERROR, ENOENT); // not found - if (!error) - status_var_increment(thd->status_var.ha_discover_count); - DBUG_RETURN(error); + DBUG_RETURN(share->error != OPEN_FRM_OK); } +static my_bool file_ext_exists(char *path, size_t path_len, const char *ext) +{ + strmake(path + path_len, ext, FN_REFLEN - path_len); + return !access(path, F_OK); +} -/** - Call this function in order to give the handler the possiblity - to ask engine if there are any new tables that should be written to disk - or any dropped tables that need to be removed from disk -*/ -struct st_find_files_args +struct st_discover_existence_args { - const char *db; - const char *path; - const char *wild; - bool dir; - List<LEX_STRING> *files; + char *path; + size_t path_len; + const char *db, *table_name; + handlerton *hton; + bool frm_exists; }; -static my_bool find_files_handlerton(THD *thd, plugin_ref plugin, - void *arg) +static my_bool discover_existence(THD *thd, plugin_ref plugin, + void *arg) { - st_find_files_args *vargs= (st_find_files_args *)arg; - handlerton *hton= plugin_data(plugin, handlerton *); + st_discover_existence_args *args= (st_discover_existence_args*)arg; + handlerton *ht= plugin_hton(plugin); + if (ht->state != SHOW_OPTION_YES || !ht->discover_table_existence) + return args->frm_exists; + args->hton= ht; - if (hton->state == SHOW_OPTION_YES && hton->find_files) - if (hton->find_files(hton, thd, vargs->db, vargs->path, vargs->wild, - vargs->dir, vargs->files)) - return TRUE; + if (ht->discover_table_existence == ext_based_existence) + return file_ext_exists(args->path, args->path_len, + ht->tablefile_extensions[0]); - return FALSE; + return ht->discover_table_existence(ht, args->db, args->table_name); } -int -ha_find_files(THD *thd,const char *db,const char *path, - const char *wild, bool dir, List<LEX_STRING> *files) +class Table_exists_error_handler : public Internal_error_handler { - int error= 0; - DBUG_ENTER("ha_find_files"); - DBUG_PRINT("enter", ("db: '%s' path: '%s' wild: '%s' dir: %d", - db, path, wild, dir)); - st_find_files_args args= {db, path, wild, dir, files}; - - plugin_foreach(thd, find_files_handlerton, - MYSQL_STORAGE_ENGINE_PLUGIN, &args); - /* The return value is not currently used */ - DBUG_RETURN(error); -} +public: + Table_exists_error_handler() + : m_handled_errors(0), m_unhandled_errors(0) + {} + + bool handle_condition(THD *thd, + uint sql_errno, + const char* sqlstate, + Sql_condition::enum_warning_level level, + const char* msg, + Sql_condition ** cond_hdl) + { + *cond_hdl= NULL; + if (sql_errno == ER_NO_SUCH_TABLE || + sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE || + sql_errno == ER_WRONG_OBJECT) + { + m_handled_errors++; + return TRUE; + } -/** - Ask handler if the table exists in engine. - @retval - HA_ERR_NO_SUCH_TABLE Table does not exist - @retval - HA_ERR_TABLE_EXIST Table exists - @retval - \# Error code -*/ -struct st_table_exists_in_engine_args -{ - const char *db; - const char *name; - int err; + if (level == Sql_condition::WARN_LEVEL_ERROR) + m_unhandled_errors++; + return FALSE; + } + + bool safely_trapped_errors() + { + return ((m_handled_errors > 0) && (m_unhandled_errors == 0)); + } + +private: + int m_handled_errors; + int m_unhandled_errors; }; -static my_bool table_exists_in_engine_handlerton(THD *thd, plugin_ref plugin, - void *arg) -{ - st_table_exists_in_engine_args *vargs= (st_table_exists_in_engine_args *)arg; - handlerton *hton= plugin_data(plugin, handlerton *); +/** + Check if a given table exists, without doing a full discover, if possible - int err= HA_ERR_NO_SUCH_TABLE; + If the 'hton' is not NULL, it's set to the handlerton of the storage engine + of this table, or to view_pseudo_hton if the frm belongs to a view. - if (hton->state == SHOW_OPTION_YES && hton->table_exists_in_engine) - err = hton->table_exists_in_engine(hton, thd, vargs->db, vargs->name); + This function takes discovery correctly into account. If frm is found, + it discovers the table to make sure it really exists in the engine. + If no frm is found it discovers the table, in case it still exists in + the engine. - vargs->err = err; - if (vargs->err == HA_ERR_TABLE_EXIST) - return TRUE; + While it tries to cut corners (don't open .frm if no discovering engine is + enabled, no full discovery if all discovering engines support + discover_table_existence, etc), it still *may* be quite expensive + and must be used sparingly. - return FALSE; -} + @retval true Table exists (even if the error occurred, like bad frm) + @retval false Table does not exist (one can do CREATE TABLE table_name) -int ha_table_exists_in_engine(THD* thd, const char* db, const char* name) -{ - DBUG_ENTER("ha_table_exists_in_engine"); - DBUG_PRINT("enter", ("db: %s, name: %s", db, name)); - st_table_exists_in_engine_args args= {db, name, HA_ERR_NO_SUCH_TABLE}; - plugin_foreach(thd, table_exists_in_engine_handlerton, - MYSQL_STORAGE_ENGINE_PLUGIN, &args); - DBUG_PRINT("exit", ("error: %d", args.err)); - DBUG_RETURN(args.err); -} + @note if frm exists and the table in engine doesn't, *hton will be set, + but the return value will be false. -#ifdef HAVE_NDB_BINLOG -/* - TODO: change this into a dynamic struct - List<handlerton> does not work as - 1. binlog_end is called when MEM_ROOT is gone - 2. cannot work with thd MEM_ROOT as memory should be freed + @note if frm file exists, but the table cannot be opened (engine not + loaded, frm is invalid), the return value will be true, but + *hton will be NULL. */ -#define MAX_HTON_LIST_ST 63 -struct hton_list_st +bool ha_table_exists(THD *thd, const char *db, const char *table_name, + handlerton **hton) { - handlerton *hton[MAX_HTON_LIST_ST]; - uint sz; -}; + handlerton *dummy; + DBUG_ENTER("ha_table_exists"); -struct binlog_func_st -{ - enum_binlog_func fn; - void *arg; -}; + if (hton) + *hton= 0; + else if (engines_with_discover) + hton= &dummy; -/** @brief - Listing handlertons first to avoid recursive calls and deadlock -*/ -static my_bool binlog_func_list(THD *thd, plugin_ref plugin, void *arg) -{ - hton_list_st *hton_list= (hton_list_st *)arg; - handlerton *hton= plugin_data(plugin, handlerton *); - if (hton->state == SHOW_OPTION_YES && hton->binlog_func) + TDC_element *element= tdc_lock_share(thd, db, table_name); + if (element && element != MY_ERRPTR) { - uint sz= hton_list->sz; - if (sz == MAX_HTON_LIST_ST-1) + if (hton) + *hton= element->share->db_type(); + tdc_unlock_share(element); + DBUG_RETURN(TRUE); + } + + char path[FN_REFLEN + 1]; + size_t path_len = build_table_filename(path, sizeof(path) - 1, + db, table_name, "", 0); + st_discover_existence_args args= {path, path_len, db, table_name, 0, true}; + + if (file_ext_exists(path, path_len, reg_ext)) + { + bool exists= true; + if (hton) { - /* list full */ - return FALSE; + char engine_buf[NAME_CHAR_LEN + 1]; + LEX_STRING engine= { engine_buf, 0 }; + frm_type_enum type; + + if ((type= dd_frm_type(thd, path, &engine)) == FRMTYPE_ERROR) + DBUG_RETURN(0); + + if (type != FRMTYPE_VIEW) + { + plugin_ref p= plugin_lock_by_name(thd, &engine, + MYSQL_STORAGE_ENGINE_PLUGIN); + *hton= p ? plugin_hton(p) : NULL; + if (*hton) + // verify that the table really exists + exists= discover_existence(thd, p, &args); + } + else + *hton= view_pseudo_hton; } - hton_list->hton[sz]= hton; - hton_list->sz= sz+1; + DBUG_RETURN(exists); } - return FALSE; -} -static my_bool binlog_func_foreach(THD *thd, binlog_func_st *bfn) -{ - hton_list_st hton_list; - uint i, sz; + args.frm_exists= false; + if (plugin_foreach(thd, discover_existence, MYSQL_STORAGE_ENGINE_PLUGIN, + &args)) + { + if (hton) + *hton= args.hton; + DBUG_RETURN(TRUE); + } - hton_list.sz= 0; - plugin_foreach(thd, binlog_func_list, - MYSQL_STORAGE_ENGINE_PLUGIN, &hton_list); - for (i= 0, sz= hton_list.sz; i < sz ; i++) - hton_list.hton[i]->binlog_func(hton_list.hton[i], thd, bfn->fn, bfn->arg); - return FALSE; + if (need_full_discover_for_existence) + { + TABLE_LIST table; + uint flags = GTS_TABLE | GTS_VIEW; + + if (!hton) + flags|= GTS_NOLOCK; + + Table_exists_error_handler no_such_table_handler; + thd->push_internal_handler(&no_such_table_handler); + TABLE_SHARE *share= tdc_acquire_share(thd, db, table_name, flags); + thd->pop_internal_handler(); + + if (hton && share) + { + *hton= share->db_type(); + tdc_release_share(share); + } + + // the table doesn't exist if we've caught ER_NO_SUCH_TABLE and nothing else + DBUG_RETURN(!no_such_table_handler.safely_trapped_errors()); + } + + DBUG_RETURN(FALSE); } -int ha_reset_logs(THD *thd) +/** + Discover all table names in a given database +*/ +extern "C" { + +static int cmp_file_names(const void *a, const void *b) { - binlog_func_st bfn= {BFN_RESET_LOGS, 0}; - binlog_func_foreach(thd, &bfn); - return 0; + CHARSET_INFO *cs= character_set_filesystem; + char *aa= ((FILEINFO *)a)->name; + char *bb= ((FILEINFO *)b)->name; + return my_strnncoll(cs, (uchar*)aa, strlen(aa), (uchar*)bb, strlen(bb)); } -void ha_reset_slave(THD* thd) +static int cmp_table_names(LEX_STRING * const *a, LEX_STRING * const *b) { - binlog_func_st bfn= {BFN_RESET_SLAVE, 0}; - binlog_func_foreach(thd, &bfn); + return my_strnncoll(&my_charset_bin, (uchar*)((*a)->str), (*a)->length, + (uchar*)((*b)->str), (*b)->length); } -void ha_binlog_wait(THD* thd) +} + +Discovered_table_list::Discovered_table_list(THD *thd_arg, + Dynamic_array<LEX_STRING*> *tables_arg, + const LEX_STRING *wild_arg) : + thd(thd_arg), with_temps(false), tables(tables_arg) { - binlog_func_st bfn= {BFN_BINLOG_WAIT, 0}; - binlog_func_foreach(thd, &bfn); + if (wild_arg->str && wild_arg->str[0]) + { + wild= wild_arg->str; + wend= wild + wild_arg->length; + } + else + wild= 0; } -int ha_binlog_end(THD* thd) +bool Discovered_table_list::add_table(const char *tname, size_t tlen) { - binlog_func_st bfn= {BFN_BINLOG_END, 0}; - binlog_func_foreach(thd, &bfn); + /* + TODO Check with_temps and filter out temp tables. + Implement the check, when we'll have at least one affected engine (with + custom discover_table_names() method, that calls add_table() directly). + Note: avoid comparing the same name twice (here and in add_file). + */ + if (wild && my_wildcmp(table_alias_charset, tname, tname + tlen, wild, wend, + wild_prefix, wild_one, wild_many)) + return 0; + + LEX_STRING *name= thd->make_lex_string(tname, tlen); + if (!name || tables->append(name)) + return 1; return 0; } -int ha_binlog_index_purge_file(THD *thd, const char *file) +bool Discovered_table_list::add_file(const char *fname) { - binlog_func_st bfn= {BFN_BINLOG_PURGE_FILE, (void *)file}; - binlog_func_foreach(thd, &bfn); - return 0; + bool is_temp= strncmp(fname, STRING_WITH_LEN(tmp_file_prefix)) == 0; + + if (is_temp && !with_temps) + return 0; + + char tname[SAFE_NAME_LEN + 1]; + size_t tlen= filename_to_tablename(fname, tname, sizeof(tname), is_temp); + return add_table(tname, tlen); } -struct binlog_log_query_st + +void Discovered_table_list::sort() { - enum_binlog_command binlog_command; - const char *query; - uint query_length; - const char *db; - const char *table_name; -}; + tables->sort(cmp_table_names); +} -static my_bool binlog_log_query_handlerton2(THD *thd, - handlerton *hton, - void *args) -{ - struct binlog_log_query_st *b= (struct binlog_log_query_st*)args; - if (hton->state == SHOW_OPTION_YES && hton->binlog_log_query) - hton->binlog_log_query(hton, thd, - b->binlog_command, - b->query, - b->query_length, - b->db, - b->table_name); - return FALSE; +void Discovered_table_list::remove_duplicates() +{ + LEX_STRING **src= tables->front(); + LEX_STRING **dst= src; + sort(); + while (++dst <= tables->back()) + { + LEX_STRING *s= *src, *d= *dst; + DBUG_ASSERT(strncmp(s->str, d->str, MY_MIN(s->length, d->length)) <= 0); + if ((s->length != d->length || strncmp(s->str, d->str, d->length))) + { + src++; + if (src != dst) + *src= *dst; + } + } + tables->elements(src - tables->front() + 1); } -static my_bool binlog_log_query_handlerton(THD *thd, - plugin_ref plugin, - void *args) +struct st_discover_names_args +{ + LEX_STRING *db; + MY_DIR *dirp; + Discovered_table_list *result; + uint possible_duplicates; +}; + +static my_bool discover_names(THD *thd, plugin_ref plugin, + void *arg) { - return binlog_log_query_handlerton2(thd, plugin_data(plugin, handlerton *), args); + st_discover_names_args *args= (st_discover_names_args *)arg; + handlerton *ht= plugin_hton(plugin); + + if (ht->state == SHOW_OPTION_YES && ht->discover_table_names) + { + uint old_elements= args->result->tables->elements(); + if (ht->discover_table_names(ht, args->db, args->dirp, args->result)) + return 1; + + /* + hton_ext_based_table_discovery never discovers a table that has + a corresponding .frm file; but custom engine discover methods might + */ + if (ht->discover_table_names != hton_ext_based_table_discovery) + args->possible_duplicates+= args->result->tables->elements() - old_elements; + } + + return 0; } -void ha_binlog_log_query(THD *thd, handlerton *hton, - enum_binlog_command binlog_command, - const char *query, uint query_length, - const char *db, const char *table_name) +/** + Return the list of tables + + @param thd + @param db database to look into + @param dirp list of files in this database (as returned by my_dir()) + @param result the object to return the list of files in + @param reusable if true, on return, 'dirp' will be a valid list of all + non-table files. If false, discovery will work much faster, + but it will leave 'dirp' corrupted and completely unusable, + only good for my_dirend(). + + Normally, reusable=false for SHOW and INFORMATION_SCHEMA, and reusable=true + for DROP DATABASE (as it needs to know and delete non-table files). +*/ + +int ha_discover_table_names(THD *thd, LEX_STRING *db, MY_DIR *dirp, + Discovered_table_list *result, bool reusable) { - struct binlog_log_query_st b; - b.binlog_command= binlog_command; - b.query= query; - b.query_length= query_length; - b.db= db; - b.table_name= table_name; - if (hton == 0) - plugin_foreach(thd, binlog_log_query_handlerton, - MYSQL_STORAGE_ENGINE_PLUGIN, &b); + int error; + DBUG_ENTER("ha_discover_table_names"); + + if (engines_with_discover_file_names == 0 && !reusable) + { + st_discover_names_args args= {db, NULL, result, 0}; + error= ext_table_discovery_simple(dirp, result) || + plugin_foreach(thd, discover_names, + MYSQL_STORAGE_ENGINE_PLUGIN, &args); + } else - binlog_log_query_handlerton2(thd, hton, &b); + { + st_discover_names_args args= {db, dirp, result, 0}; + + /* extension_based_table_discovery relies on dirp being sorted */ + my_qsort(dirp->dir_entry, dirp->number_of_files, + sizeof(FILEINFO), cmp_file_names); + + error= extension_based_table_discovery(dirp, reg_ext, result) || + plugin_foreach(thd, discover_names, + MYSQL_STORAGE_ENGINE_PLUGIN, &args); + if (args.possible_duplicates > 0) + result->remove_duplicates(); + } + + DBUG_RETURN(error); } -#endif /** @@ -4619,14 +5342,7 @@ int handler::read_range_first(const key_range *start_key, DBUG_ENTER("handler::read_range_first"); eq_range= eq_range_arg; - end_range= 0; - if (end_key) - { - end_range= &save_end_range; - save_end_range= *end_key; - key_compare_result_on_equal= ((end_key->flag == HA_READ_BEFORE_KEY) ? 1 : - (end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0); - } + set_end_range(end_key); range_key_part= table->key_info[active_index].key_part; if (!start_key) // Read first record @@ -4702,12 +5418,26 @@ int handler::read_range_next() } +void handler::set_end_range(const key_range *end_key) +{ + end_range= 0; + if (end_key) + { + end_range= &save_end_range; + save_end_range= *end_key; + key_compare_result_on_equal= + ((end_key->flag == HA_READ_BEFORE_KEY) ? 1 : + (end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0); + } +} + + /** Compare if found key (in row) is over max-value. @param range range to compare to row. May be 0 for no range - @seealso + @see also key.cc::key_cmp() @return @@ -4773,8 +5503,7 @@ int handler::index_read_idx_map(uchar * buf, uint index, const uchar * key, key_part_map keypart_map, enum ha_rkey_function find_flag) { - int error, error1; - LINT_INIT(error1); + int error, UNINIT_VAR(error1); error= ha_index_init(index, 0); if (!error) @@ -4800,27 +5529,21 @@ static my_bool exts_handlerton(THD *unused, plugin_ref plugin, void *arg) { List<char> *found_exts= (List<char> *) arg; - handlerton *hton= plugin_data(plugin, handlerton *); - handler *file; - if (hton->state == SHOW_OPTION_YES && hton->create && - (file= hton->create(hton, (TABLE_SHARE*) 0, current_thd->mem_root))) - { - List_iterator_fast<char> it(*found_exts); - const char **ext, *old_ext; + handlerton *hton= plugin_hton(plugin); + List_iterator_fast<char> it(*found_exts); + const char **ext, *old_ext; - for (ext= file->bas_ext(); *ext; ext++) + for (ext= hton->tablefile_extensions; *ext; ext++) + { + while ((old_ext= it++)) { - while ((old_ext= it++)) - { - if (!strcmp(old_ext, *ext)) - break; - } - if (!old_ext) - found_exts->push_back((char *) *ext); - - it.rewind(); + if (!strcmp(old_ext, *ext)) + break; } - delete file; + if (!old_ext) + found_exts->push_back((char *) *ext); + + it.rewind(); } return FALSE; } @@ -4875,7 +5598,7 @@ static my_bool showstat_handlerton(THD *thd, plugin_ref plugin, void *arg) { enum ha_stat_type stat= *(enum ha_stat_type *) arg; - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton->state == SHOW_OPTION_YES && hton->show_status && hton->show_status(hton, thd, stat_print, stat)) return TRUE; @@ -4886,11 +5609,16 @@ bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat) { List<Item> field_list; Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; bool result; - field_list.push_back(new Item_empty_string("Type",10)); - field_list.push_back(new Item_empty_string("Name",FN_REFLEN)); - field_list.push_back(new Item_empty_string("Status",10)); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Type", 10), + mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Name", FN_REFLEN), mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Status", 10), + mem_root); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) @@ -4923,7 +5651,7 @@ bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat) if (!result && !thd->is_error()) my_eof(thd); else if (!thd->is_error()) - my_error(ER_GET_ERRNO, MYF(0), errno); + my_error(ER_GET_ERRNO, MYF(0), errno, hton_name(db_type)->str); return result; } @@ -4945,6 +5673,7 @@ static bool check_table_binlog_row_based(THD *thd, TABLE *table) if (table->s->cached_row_logging_check == -1) { int const check(table->s->tmp_table == NO_TMP_TABLE && + ! table->no_replicate && binlog_filter->db_ok(table->s->db.str)); table->s->cached_row_logging_check= check; } @@ -4954,8 +5683,29 @@ static bool check_table_binlog_row_based(THD *thd, TABLE *table) return (thd->is_current_stmt_binlog_format_row() && table->s->cached_row_logging_check && +#ifdef WITH_WSREP + /* + Wsrep partially enables binary logging if it have not been + explicitly turned on. As a result we return 'true' if we are in + wsrep binlog emulation mode and the current thread is not a wsrep + applier or replayer thread. This decision is not affected by + @@sql_log_bin as we want the events to make into the binlog + cache only to filter them later before they make into binary log + file. + + However, we do return 'false' if binary logging was temporarily + turned off (see tmp_disable_binlog(A)). + + Otherwise, return 'true' if binary logging is on. + */ + (thd->variables.sql_log_bin_off != 1) && + ((WSREP_EMULATE_BINLOG(thd) && (thd->wsrep_exec_mode != REPL_RECV)) || + ((WSREP(thd) || (thd->variables.option_bits & OPTION_BIN_LOG)) && + mysql_bin_log.is_open()))); +#else (thd->variables.option_bits & OPTION_BIN_LOG) && mysql_bin_log.is_open()); +#endif } @@ -5044,55 +5794,61 @@ static int write_locked_table_maps(THD *thd) } -typedef bool Log_func(THD*, TABLE*, bool, MY_BITMAP*, - uint, const uchar*, const uchar*); +typedef bool Log_func(THD*, TABLE*, bool, const uchar*, const uchar*); static int binlog_log_row(TABLE* table, const uchar *before_record, const uchar *after_record, Log_func *log_func) { - if (table->no_replicate) - return 0; bool error= 0; THD *const thd= table->in_use; - if (check_table_binlog_row_based(thd, table)) +#ifdef WITH_WSREP + /* + Only InnoDB tables will be replicated through binlog emulation. Also + updates in mysql.gtid_slave_state table should not be binlogged. + */ + if ((WSREP_EMULATE_BINLOG(thd) && + table->file->partition_ht()->db_type != DB_TYPE_INNODB) || + (thd->wsrep_ignore_table == true)) + return 0; + + /* enforce wsrep_max_ws_rows */ + if (WSREP(thd) && table->s->tmp_table == NO_TMP_TABLE) { - MY_BITMAP cols; - /* Potential buffer on the stack for the bitmap */ - uint32 bitbuf[BITMAP_STACKBUF_SIZE/sizeof(uint32)]; - uint n_fields= table->s->fields; - my_bool use_bitbuf= n_fields <= sizeof(bitbuf)*8; + thd->wsrep_affected_rows++; + if (wsrep_max_ws_rows && + thd->wsrep_exec_mode != REPL_RECV && + thd->wsrep_affected_rows > wsrep_max_ws_rows) + { + trans_rollback_stmt(thd) || trans_rollback(thd); + my_message(ER_ERROR_DURING_COMMIT, "wsrep_max_ws_rows exceeded", MYF(0)); + return ER_ERROR_DURING_COMMIT; + } + } +#endif /* WITH_WSREP */ + if (check_table_binlog_row_based(thd, table)) + { /* If there are no table maps written to the binary log, this is the first row handled in this statement. In that case, we need to write table maps for all locked tables to the binary log. */ - if (likely(!(error= bitmap_init(&cols, - use_bitbuf ? bitbuf : NULL, - (n_fields + 7) & ~7UL, - FALSE)))) + if (likely(!(error= write_locked_table_maps(thd)))) { - bitmap_set_all(&cols); - if (likely(!(error= write_locked_table_maps(thd)))) - { - /* - We need to have a transactional behavior for SQLCOM_CREATE_TABLE - (i.e. CREATE TABLE... SELECT * FROM TABLE) in order to keep a - compatible behavior with the STMT based replication even when - the table is not transactional. In other words, if the operation - fails while executing the insert phase nothing is written to the - binlog. - */ - bool const has_trans= thd->lex->sql_command == SQLCOM_CREATE_TABLE || - table->file->has_transactions(); - error= (*log_func)(thd, table, has_trans, &cols, table->s->fields, - before_record, after_record); - } - if (!use_bitbuf) - bitmap_free(&cols); + /* + We need to have a transactional behavior for SQLCOM_CREATE_TABLE + (i.e. CREATE TABLE... SELECT * FROM TABLE) in order to keep a + compatible behavior with the STMT based replication even when + the table is not transactional. In other words, if the operation + fails while executing the insert phase nothing is written to the + binlog. + */ + bool const has_trans= thd->lex->sql_command == SQLCOM_CREATE_TABLE || + table->file->has_transactions(); + error= (*log_func)(thd, table, has_trans, before_record, after_record); } } return error ? HA_ERR_RBR_LOGGING_FAILED : 0; @@ -5100,6 +5856,7 @@ static int binlog_log_row(TABLE* table, int handler::ha_external_lock(THD *thd, int lock_type) { + int error; DBUG_ENTER("handler::ha_external_lock"); /* Whether this is lock or unlock, this should be true, and is to verify that @@ -5107,6 +5864,12 @@ int handler::ha_external_lock(THD *thd, int lock_type) taken a table lock), ha_release_auto_increment() was too. */ DBUG_ASSERT(next_insert_id == 0); + /* Consecutive calls for lock without unlocking in between is not allowed */ + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + ((lock_type != F_UNLCK && m_lock_type == F_UNLCK) || + lock_type == F_UNLCK)); + /* SQL HANDLER call locks/unlock while scanning (RND/INDEX). */ + DBUG_ASSERT(inited == NONE || table->open_by_handler); if (MYSQL_HANDLER_RDLOCK_START_ENABLED() || MYSQL_HANDLER_WRLOCK_START_ENABLED() || @@ -5129,14 +5892,20 @@ int handler::ha_external_lock(THD *thd, int lock_type) } } + ha_statistic_increment(&SSV::ha_external_lock_count); + /* We cache the table flags if the locking succeeded. Otherwise, we keep them as they were when they were fetched in ha_open(). */ - int error= external_lock(thd, lock_type); + MYSQL_TABLE_LOCK_WAIT(m_psi, PSI_TABLE_EXTERNAL_LOCK, lock_type, + { error= external_lock(thd, lock_type); }) + + DBUG_EXECUTE_IF("external_lock_failure", error= HA_ERR_GENERIC;); - if (error == 0) + if (error == 0 || lock_type == F_UNLCK) { + m_lock_type= lock_type; cached_table_flags= table_flags(); if (table_share->tmp_table == NO_TMP_TABLE) mysql_audit_external_lock(thd, table_share, lock_type); @@ -5182,6 +5951,7 @@ int handler::ha_reset() /* reset the bitmaps to point to defaults */ table->default_column_bitmaps(); pushed_cond= NULL; + tracker= NULL; /* Reset information about pushed engine conditions */ cancel_pushed_idx_cond(); /* Reset information about pushed index conditions */ @@ -5193,16 +5963,18 @@ int handler::ha_write_row(uchar *buf) { int error; Log_func *log_func= Write_rows_log_event::binlog_row_logging_function; + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_WRLCK); DBUG_ENTER("handler::ha_write_row"); DEBUG_SYNC_C("ha_write_row_start"); - DBUG_EXECUTE_IF("inject_error_ha_write_row", - DBUG_RETURN(HA_ERR_INTERNAL_ERROR); ); MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); increment_statistics(&SSV::ha_write_count); - error= write_row(buf); + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0, + { error= write_row(buf); }) + MYSQL_INSERT_ROW_DONE(error); if (unlikely(error)) DBUG_RETURN(error); @@ -5219,6 +5991,8 @@ int handler::ha_update_row(const uchar *old_data, uchar *new_data) { int error; Log_func *log_func= Update_rows_log_event::binlog_row_logging_function; + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_WRLCK); /* Some storage engines require that the new record is in record[0] @@ -5231,7 +6005,9 @@ int handler::ha_update_row(const uchar *old_data, uchar *new_data) mark_trx_read_write(); increment_statistics(&SSV::ha_update_count); - error= update_row(old_data, new_data); + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_UPDATE_ROW, active_index, 0, + { error= update_row(old_data, new_data);}) + MYSQL_UPDATE_ROW_DONE(error); if (unlikely(error)) return error; @@ -5245,19 +6021,20 @@ int handler::ha_delete_row(const uchar *buf) { int error; Log_func *log_func= Delete_rows_log_event::binlog_row_logging_function; + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type == F_WRLCK); /* Normally table->record[0] is used, but sometimes table->record[1] is used. */ DBUG_ASSERT(buf == table->record[0] || buf == table->record[1]); - DBUG_EXECUTE_IF("inject_error_ha_delete_row", - return HA_ERR_INTERNAL_ERROR; ); MYSQL_DELETE_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); increment_statistics(&SSV::ha_delete_count); - error= delete_row(buf); + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_DELETE_ROW, active_index, 0, + { error= delete_row(buf);}) MYSQL_DELETE_ROW_DONE(error); if (unlikely(error)) return error; @@ -5277,10 +6054,81 @@ int handler::ha_delete_row(const uchar *buf) void handler::use_hidden_primary_key() { /* fallback to use all columns in the table to identify row */ - table->use_all_columns(); + table->column_bitmaps_set(&table->s->all_set, table->write_set); } +/** + Get an initialized ha_share. + + @return Initialized ha_share + @retval NULL ha_share is not yet initialized. + @retval != NULL previous initialized ha_share. + + @note + If not a temp table, then LOCK_ha_data must be held. +*/ + +Handler_share *handler::get_ha_share_ptr() +{ + DBUG_ENTER("handler::get_ha_share_ptr"); + DBUG_ASSERT(ha_share && table_share); + +#ifndef DBUG_OFF + if (table_share->tmp_table == NO_TMP_TABLE) + mysql_mutex_assert_owner(&table_share->LOCK_ha_data); +#endif + + DBUG_RETURN(*ha_share); +} + + +/** + Set ha_share to be used by all instances of the same table/partition. + + @param ha_share Handler_share to be shared. + + @note + If not a temp table, then LOCK_ha_data must be held. +*/ + +void handler::set_ha_share_ptr(Handler_share *arg_ha_share) +{ + DBUG_ENTER("handler::set_ha_share_ptr"); + DBUG_ASSERT(ha_share); +#ifndef DBUG_OFF + if (table_share->tmp_table == NO_TMP_TABLE) + mysql_mutex_assert_owner(&table_share->LOCK_ha_data); +#endif + + *ha_share= arg_ha_share; + DBUG_VOID_RETURN; +} + + +/** + Take a lock for protecting shared handler data. +*/ + +void handler::lock_shared_ha_data() +{ + DBUG_ASSERT(table_share); + if (table_share->tmp_table == NO_TMP_TABLE) + mysql_mutex_lock(&table_share->LOCK_ha_data); +} + + +/** + Release lock for protecting ha_share. +*/ + +void handler::unlock_shared_ha_data() +{ + DBUG_ASSERT(table_share); + if (table_share->tmp_table == NO_TMP_TABLE) + mysql_mutex_unlock(&table_share->LOCK_ha_data); +} + /** @brief Dummy function which accept information about log files which is not need by handlers @@ -5297,6 +6145,103 @@ void handler::set_lock_type(enum thr_lock_type lock) table->reginfo.lock_type= lock; } +#ifdef WITH_WSREP +/** + @details + This function makes the storage engine to force the victim transaction + to abort. Currently, only innodb has this functionality, but any SE + implementing the wsrep API should provide this service to support + multi-master operation. + + @note Aborting the transaction does NOT end it, it still has to + be rolled back with hton->rollback(). + + @note It is safe to abort from one thread (bf_thd) the transaction, + running in another thread (victim_thd), because InnoDB's lock_sys and + trx_mutex guarantee the necessary protection. However, its not safe + to access victim_thd->transaction, because it's not protected from + concurrent accesses. And it's an overkill to take LOCK_plugin and + iterate the whole installed_htons[] array every time. + + @param bf_thd brute force THD asking for the abort + @param victim_thd victim THD to be aborted + + @return + always 0 +*/ + +int ha_abort_transaction(THD *bf_thd, THD *victim_thd, my_bool signal) +{ + DBUG_ENTER("ha_abort_transaction"); + if (!WSREP(bf_thd) && + !(bf_thd->variables.wsrep_OSU_method == WSREP_OSU_RSU && + bf_thd->wsrep_exec_mode == TOTAL_ORDER)) { + DBUG_RETURN(0); + } + + handlerton *hton= installed_htons[DB_TYPE_INNODB]; + if (hton && hton->abort_transaction) + { + hton->abort_transaction(hton, bf_thd, victim_thd, signal); + } + else + { + WSREP_WARN("Cannot abort InnoDB transaction"); + } + + DBUG_RETURN(0); +} + +void ha_fake_trx_id(THD *thd) +{ + DBUG_ENTER("ha_fake_trx_id"); + + bool no_fake_trx_id= true; + + if (!WSREP(thd)) + { + DBUG_VOID_RETURN; + } + + if (thd->wsrep_ws_handle.trx_id != WSREP_UNDEFINED_TRX_ID) + { + WSREP_DEBUG("fake trx id skipped: %" PRIu64, thd->wsrep_ws_handle.trx_id); + DBUG_VOID_RETURN; + } + + /* Try statement transaction if standard one is not set. */ + THD_TRANS *trans= (thd->transaction.all.ha_list) ? &thd->transaction.all : + &thd->transaction.stmt; + + Ha_trx_info *ha_info= trans->ha_list, *ha_info_next; + + for (; ha_info; ha_info= ha_info_next) + { + handlerton *hton= ha_info->ht(); + if (hton->fake_trx_id) + { + hton->fake_trx_id(hton, thd); + + /* Got a fake trx id. */ + no_fake_trx_id= false; + + /* + We need transaction ID from just one storage engine providing + fake_trx_id (which will most likely be the case). + */ + break; + } + ha_info_next= ha_info->next(); + } + + if (unlikely(no_fake_trx_id)) + WSREP_WARN("Cannot get fake transaction ID from storage engine."); + + DBUG_VOID_RETURN; +} +#endif /* WITH_WSREP */ + + #ifdef TRANS_LOG_MGM_EXAMPLE_CODE /* Example of transaction log management functions based on assumption that logs @@ -5405,7 +6350,7 @@ fl_log_iterator_buffer_init(struct handler_iterator *iterator) /* to be able to make my_free without crash in case of error */ iterator->buffer= 0; - if (!(dirp = my_dir(fl_dir, MYF(0)))) + if (!(dirp = my_dir(fl_dir, MYF(MY_THREAD_SPECIFIC)))) { return HA_ITERATOR_ERROR; } @@ -5414,7 +6359,7 @@ fl_log_iterator_buffer_init(struct handler_iterator *iterator) sizeof(enum log_status) + + FN_REFLEN + 1) * (uint) dirp->number_off_files), - MYF(0))) == 0) + MYF(MY_THREAD_SPECIFIC))) == 0) { return HA_ITERATOR_ERROR; } @@ -5448,6 +6393,7 @@ fl_log_iterator_buffer_init(struct handler_iterator *iterator) iterator->buffer= buff; iterator->next= &fl_log_iterator_next; iterator->destroy= &fl_log_iterator_destroy; + my_dirend(dirp); return HA_ITERATOR_OK; } @@ -5465,3 +6411,117 @@ fl_create_iterator(enum handler_iterator_type type, } } #endif /*TRANS_LOG_MGM_EXAMPLE_CODE*/ + + +bool HA_CREATE_INFO::check_conflicting_charset_declarations(CHARSET_INFO *cs) +{ + if ((used_fields & HA_CREATE_USED_DEFAULT_CHARSET) && + /* DEFAULT vs explicit, or explicit vs DEFAULT */ + (((default_table_charset == NULL) != (cs == NULL)) || + /* Two different explicit character sets */ + (default_table_charset && cs && + !my_charset_same(default_table_charset, cs)))) + { + my_error(ER_CONFLICTING_DECLARATIONS, MYF(0), + "CHARACTER SET ", default_table_charset ? + default_table_charset->csname : "DEFAULT", + "CHARACTER SET ", cs ? cs->csname : "DEFAULT"); + return true; + } + return false; +} + +/* Remove all indexes for a given table from global index statistics */ + +static +int del_global_index_stats_for_table(THD *thd, uchar* cache_key, uint cache_key_length) +{ + int res = 0; + DBUG_ENTER("del_global_index_stats_for_table"); + + mysql_mutex_lock(&LOCK_global_index_stats); + + for (uint i= 0; i < global_index_stats.records;) + { + INDEX_STATS *index_stats = + (INDEX_STATS*) my_hash_element(&global_index_stats, i); + + /* We search correct db\0table_name\0 string */ + if (index_stats && + index_stats->index_name_length >= cache_key_length && + !memcmp(index_stats->index, cache_key, cache_key_length)) + { + res= my_hash_delete(&global_index_stats, (uchar*)index_stats); + /* + In our HASH implementation on deletion one elements + is moved into a place where a deleted element was, + and the last element is moved into the empty space. + Thus we need to re-examine the current element, but + we don't have to restart the search from the beginning. + */ + } + else + i++; + } + + mysql_mutex_unlock(&LOCK_global_index_stats); + DBUG_RETURN(res); +} + +/* Remove a table from global table statistics */ + +int del_global_table_stat(THD *thd, LEX_STRING *db, LEX_STRING *table) +{ + TABLE_STATS *table_stats; + int res = 0; + uchar *cache_key; + uint cache_key_length; + DBUG_ENTER("del_global_table_stat"); + + cache_key_length= db->length + 1 + table->length + 1; + + if(!(cache_key= (uchar *)my_malloc(cache_key_length, + MYF(MY_WME | MY_ZEROFILL)))) + { + /* Out of memory error already given */ + res = 1; + goto end; + } + + memcpy(cache_key, db->str, db->length); + memcpy(cache_key + db->length + 1, table->str, table->length); + + res= del_global_index_stats_for_table(thd, cache_key, cache_key_length); + + mysql_mutex_lock(&LOCK_global_table_stats); + + if((table_stats= (TABLE_STATS*) my_hash_search(&global_table_stats, + cache_key, + cache_key_length))) + res= my_hash_delete(&global_table_stats, (uchar*)table_stats); + + my_free(cache_key); + mysql_mutex_unlock(&LOCK_global_table_stats); + +end: + DBUG_RETURN(res); +} + +/* Remove a index from global index statistics */ + +int del_global_index_stat(THD *thd, TABLE* table, KEY* key_info) +{ + INDEX_STATS *index_stats; + uint key_length= table->s->table_cache_key.length + key_info->name_length + 1; + int res = 0; + DBUG_ENTER("del_global_index_stat"); + mysql_mutex_lock(&LOCK_global_index_stats); + + if((index_stats= (INDEX_STATS*) my_hash_search(&global_index_stats, + key_info->cache_name, + key_length))) + res= my_hash_delete(&global_index_stats, (uchar*)index_stats); + + mysql_mutex_unlock(&LOCK_global_index_stats); + DBUG_RETURN(res); +} diff --git a/sql/handler.h b/sql/handler.h index 3d6c8dea2bf..af492e75da6 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -2,7 +2,7 @@ #define HANDLER_INCLUDED /* Copyright (c) 2000, 2016, Oracle and/or its affiliates. - Copyright (c) 2009, 2016, MariaDB + Copyright (c) 2009, 2018, MariaDB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -31,14 +31,17 @@ #include "thr_lock.h" /* thr_lock_type, THR_LOCK_DATA */ #include "sql_cache.h" #include "structs.h" /* SHOW_COMP_OPTION */ +#include "sql_array.h" /* Dynamic_array<> */ +#include "mdl.h" + +#include "sql_analyze_stmt.h" // for Exec_time_tracker #include <my_compare.h> #include <ft_global.h> #include <keycache.h> +#include <mysql/psi/mysql_table.h> -#if MAX_KEY > 128 -#error MAX_KEY is too large. Values up to 128 are supported. -#endif +class Alter_info; // the following is for checking tables @@ -57,11 +60,27 @@ #define HA_ADMIN_NEEDS_ALTER -11 #define HA_ADMIN_NEEDS_CHECK -12 +/** + Return values for check_if_supported_inplace_alter(). + + @see check_if_supported_inplace_alter() for description of + the individual values. +*/ +enum enum_alter_inplace_result { + HA_ALTER_ERROR, + HA_ALTER_INPLACE_NOT_SUPPORTED, + HA_ALTER_INPLACE_EXCLUSIVE_LOCK, + HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE, + HA_ALTER_INPLACE_SHARED_LOCK, + HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE, + HA_ALTER_INPLACE_NO_LOCK +}; + /* Bits in table_flags() to show what database can do */ -#define HA_NO_TRANSACTIONS (1 << 0) /* Doesn't support transactions */ -#define HA_PARTIAL_COLUMN_READ (1 << 1) /* read may not return all columns */ -#define HA_TABLE_SCAN_ON_INDEX (1 << 2) /* No separate data/index file */ +#define HA_NO_TRANSACTIONS (1ULL << 0) /* Doesn't support transactions */ +#define HA_PARTIAL_COLUMN_READ (1ULL << 1) /* read may not return all columns */ +#define HA_TABLE_SCAN_ON_INDEX (1ULL << 2) /* No separate data/index file */ /* The following should be set if the following is not true when scanning a table with rnd_next() @@ -70,37 +89,37 @@ If this flag is not set, filesort will do a position() call for each matched row to be able to find the row later. */ -#define HA_REC_NOT_IN_SEQ (1 << 3) -#define HA_CAN_GEOMETRY (1 << 4) +#define HA_REC_NOT_IN_SEQ (1ULL << 3) +#define HA_CAN_GEOMETRY (1ULL << 4) /* Reading keys in random order is as fast as reading keys in sort order (Used in records.cc to decide if we should use a record cache and by filesort to decide if we should sort key + data or key + pointer-to-row */ -#define HA_FAST_KEY_READ (1 << 5) +#define HA_FAST_KEY_READ (1ULL << 5) /* Set the following flag if we on delete should force all key to be read and on update read all keys that changes */ -#define HA_REQUIRES_KEY_COLUMNS_FOR_DELETE (1 << 6) -#define HA_NULL_IN_KEY (1 << 7) /* One can have keys with NULL */ -#define HA_DUPLICATE_POS (1 << 8) /* ha_position() gives dup row */ -#define HA_NO_BLOBS (1 << 9) /* Doesn't support blobs */ -#define HA_CAN_INDEX_BLOBS (1 << 10) -#define HA_AUTO_PART_KEY (1 << 11) /* auto-increment in multi-part key */ -#define HA_REQUIRE_PRIMARY_KEY (1 << 12) /* .. and can't create a hidden one */ -#define HA_STATS_RECORDS_IS_EXACT (1 << 13) /* stats.records is exact */ +#define HA_REQUIRES_KEY_COLUMNS_FOR_DELETE (1ULL << 6) +#define HA_NULL_IN_KEY (1ULL << 7) /* One can have keys with NULL */ +#define HA_DUPLICATE_POS (1ULL << 8) /* ha_position() gives dup row */ +#define HA_NO_BLOBS (1ULL << 9) /* Doesn't support blobs */ +#define HA_CAN_INDEX_BLOBS (1ULL << 10) +#define HA_AUTO_PART_KEY (1ULL << 11) /* auto-increment in multi-part key */ +#define HA_REQUIRE_PRIMARY_KEY (1ULL << 12) /* .. and can't create a hidden one */ +#define HA_STATS_RECORDS_IS_EXACT (1ULL << 13) /* stats.records is exact */ /* INSERT_DELAYED only works with handlers that uses MySQL internal table level locks */ -#define HA_CAN_INSERT_DELAYED (1 << 14) +#define HA_CAN_INSERT_DELAYED (1ULL << 14) /* If we get the primary key columns for free when we do an index read - It also implies that we have to retrive the primary key when using - position() and rnd_pos(). + (usually, it also implies that HA_PRIMARY_KEY_REQUIRED_FOR_POSITION + flag is set). */ -#define HA_PRIMARY_KEY_IN_READ_INDEX (1 << 15) +#define HA_PRIMARY_KEY_IN_READ_INDEX (1ULL << 15) /* If HA_PRIMARY_KEY_REQUIRED_FOR_POSITION is set, it means that to position() uses a primary key given by the record argument. @@ -108,36 +127,36 @@ If not set, the position is returned as the current rows position regardless of what argument is given. */ -#define HA_PRIMARY_KEY_REQUIRED_FOR_POSITION (1 << 16) -#define HA_CAN_RTREEKEYS (1 << 17) -#define HA_NOT_DELETE_WITH_CACHE (1 << 18) +#define HA_PRIMARY_KEY_REQUIRED_FOR_POSITION (1ULL << 16) +#define HA_CAN_RTREEKEYS (1ULL << 17) +#define HA_NOT_DELETE_WITH_CACHE (1ULL << 18) /* unused */ /* The following is we need to a primary key to delete (and update) a row. If there is no primary key, all columns needs to be read on update and delete */ -#define HA_PRIMARY_KEY_REQUIRED_FOR_DELETE (1 << 19) -#define HA_NO_PREFIX_CHAR_KEYS (1 << 20) -#define HA_CAN_FULLTEXT (1 << 21) -#define HA_CAN_SQL_HANDLER (1 << 22) -#define HA_NO_AUTO_INCREMENT (1 << 23) +#define HA_PRIMARY_KEY_REQUIRED_FOR_DELETE (1ULL << 19) +#define HA_NO_PREFIX_CHAR_KEYS (1ULL << 20) +#define HA_CAN_FULLTEXT (1ULL << 21) +#define HA_CAN_SQL_HANDLER (1ULL << 22) +#define HA_NO_AUTO_INCREMENT (1ULL << 23) /* Has automatic checksums and uses the old checksum format */ -#define HA_HAS_OLD_CHECKSUM (1 << 24) +#define HA_HAS_OLD_CHECKSUM (1ULL << 24) /* Table data are stored in separate files (for lower_case_table_names) */ -#define HA_FILE_BASED (1 << 26) -#define HA_NO_VARCHAR (1 << 27) -#define HA_CAN_BIT_FIELD (1 << 28) /* supports bit fields */ -#define HA_NEED_READ_RANGE_BUFFER (1 << 29) /* for read_multi_range */ -#define HA_ANY_INDEX_MAY_BE_UNIQUE (1 << 30) -#define HA_NO_COPY_ON_ALTER (LL(1) << 31) -#define HA_HAS_RECORDS (LL(1) << 32) /* records() gives exact count*/ +#define HA_FILE_BASED (1ULL << 26) +#define HA_NO_VARCHAR (1ULL << 27) /* unused */ +#define HA_CAN_BIT_FIELD (1ULL << 28) /* supports bit fields */ +#define HA_NEED_READ_RANGE_BUFFER (1ULL << 29) /* for read_multi_range */ +#define HA_ANY_INDEX_MAY_BE_UNIQUE (1ULL << 30) +#define HA_NO_COPY_ON_ALTER (1ULL << 31) +#define HA_HAS_RECORDS (1ULL << 32) /* records() gives exact count*/ /* Has it's own method of binlog logging */ -#define HA_HAS_OWN_BINLOGGING (LL(1) << 33) +#define HA_HAS_OWN_BINLOGGING (1ULL << 33) /* Engine is capable of row-format and statement-format logging, respectively */ -#define HA_BINLOG_ROW_CAPABLE (LL(1) << 34) -#define HA_BINLOG_STMT_CAPABLE (LL(1) << 35) +#define HA_BINLOG_ROW_CAPABLE (1ULL << 34) +#define HA_BINLOG_STMT_CAPABLE (1ULL << 35) /* When a multiple key conflict happens in a REPLACE command mysql expects the conflicts to be reported in the ascending order of @@ -160,33 +179,84 @@ This flag helps the underlying SE to inform the server that the keys are not ordered. */ -#define HA_DUPLICATE_KEY_NOT_IN_ORDER (LL(1) << 36) +#define HA_DUPLICATE_KEY_NOT_IN_ORDER (1ULL << 36) /* Engine supports REPAIR TABLE. Used by CHECK TABLE FOR UPGRADE if an incompatible table is detected. If this flag is set, CHECK TABLE FOR UPGRADE will report ER_TABLE_NEEDS_UPGRADE, otherwise ER_TABLE_NEED_REBUILD. */ -#define HA_CAN_REPAIR (LL(1) << 37) +#define HA_CAN_REPAIR (1ULL << 37) /* Has automatic checksums and uses the new checksum format */ -#define HA_HAS_NEW_CHECKSUM (LL(1) << 38) -#define HA_CAN_VIRTUAL_COLUMNS (LL(1) << 39) -#define HA_MRR_CANT_SORT (LL(1) << 40) -#define HA_RECORD_MUST_BE_CLEAN_ON_WRITE (LL(1) << 41) +#define HA_HAS_NEW_CHECKSUM (1ULL << 38) +#define HA_CAN_VIRTUAL_COLUMNS (1ULL << 39) +#define HA_MRR_CANT_SORT (1ULL << 40) +#define HA_RECORD_MUST_BE_CLEAN_ON_WRITE (1ULL << 41) /* - Table condition pushdown must be performed regardless of - 'engine_condition_pushdown' setting. - - This flag is aimed at storage engines that come with "special" predicates - that can only be evaluated inside the storage engine. - For example, when one does - select * from sphinx_table where query='{fulltext_query}' - then the "query=..." condition must be always pushed down into storage - engine. + This storage engine supports condition pushdown */ -#define HA_MUST_USE_TABLE_CONDITION_PUSHDOWN (LL(1) << 42) +#define HA_CAN_TABLE_CONDITION_PUSHDOWN (1ULL << 42) +/* old name for the same flag */ +#define HA_MUST_USE_TABLE_CONDITION_PUSHDOWN HA_CAN_TABLE_CONDITION_PUSHDOWN + +/** + The handler supports read before write removal optimization + + Read before write removal may be used for storage engines which support + write without previous read of the row to be updated. Handler returning + this flag must implement start_read_removal() and end_read_removal(). + The handler may return "fake" rows constructed from the key of the row + asked for. This is used to optimize UPDATE and DELETE by reducing the + numer of roundtrips between handler and storage engine. + + Example: + UPDATE a=1 WHERE pk IN (<keys>) + + mysql_update() + { + if (<conditions for starting read removal>) + start_read_removal() + -> handler returns true if read removal supported for this table/query + + while(read_record("pk=<key>")) + -> handler returns fake row with column "pk" set to <key> + + ha_update_row() + -> handler sends write "a=1" for row with "pk=<key>" + + end_read_removal() + -> handler returns the number of rows actually written + } + + @note This optimization in combination with batching may be used to + remove even more roundtrips. +*/ +#define HA_READ_BEFORE_WRITE_REMOVAL (1LL << 43) + +/* + Engine supports extended fulltext API + */ +#define HA_CAN_FULLTEXT_EXT (1LL << 44) + +/* + Storage engine supports table export using the + FLUSH TABLE <table_list> FOR EXPORT statement + (meaning, after this statement one can copy table files out of the + datadir and later "import" (somehow) in another MariaDB instance) + */ +#define HA_CAN_EXPORT (1LL << 45) + +/* + Storage engine does not require an exclusive metadata lock + on the table during optimize. (TODO and repair?). + It can allow other connections to open the table. + (it does not necessarily mean that other connections can + read or modify the table - this is defined by THR locks and the + ::store_lock() method). +*/ +#define HA_CONCURRENT_OPTIMIZE (1LL << 46) /* Set of all binlog flags. Currently only contain the capabilities @@ -280,9 +350,6 @@ /* Note: the following includes binlog and closing 0. - so: innodb + bdb + ndb + binlog + myisam + myisammrg + archive + - example + csv + heap + blackhole + federated + 0 - (yes, the sum is deliberately inaccurate) TODO remove the limit, use dynarrays */ #define MAX_HA 64 @@ -315,10 +382,15 @@ #define HA_KEY_NULL_LENGTH 1 #define HA_KEY_BLOB_LENGTH 2 +/* Maximum length of any index lookup key, in bytes */ + +#define MAX_KEY_LENGTH (MAX_DATA_LENGTH_FOR_KEY \ + +(MAX_REF_PARTS \ + *(HA_KEY_NULL_LENGTH + HA_KEY_BLOB_LENGTH))) + #define HA_LEX_CREATE_TMP_TABLE 1 -#define HA_LEX_CREATE_IF_NOT_EXISTS 2 -#define HA_LEX_CREATE_TABLE_LIKE 4 -#define HA_CREATE_TMP_ALTER 8 +#define HA_CREATE_TMP_ALTER 8 + #define HA_MAX_REC_LENGTH 65535 /* Table caching type */ @@ -327,8 +399,24 @@ #define HA_CACHE_TBL_ASKTRANSACT 2 #define HA_CACHE_TBL_TRANSACT 4 -/* Options of START TRANSACTION statement (and later of SET TRANSACTION stmt) */ -#define MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT 1 +/** + Options for the START TRANSACTION statement. + + Note that READ ONLY and READ WRITE are logically mutually exclusive. + This is enforced by the parser and depended upon by trans_begin(). + + We need two flags instead of one in order to differentiate between + situation when no READ WRITE/ONLY clause were given and thus transaction + is implicitly READ WRITE and the case when READ WRITE clause was used + explicitly. +*/ + +// WITH CONSISTENT SNAPSHOT option +static const uint MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT = 1; +// READ ONLY option +static const uint MYSQL_START_TRANS_OPT_READ_ONLY = 2; +// READ WRITE option +static const uint MYSQL_START_TRANS_OPT_READ_WRITE = 4; /* Flags for method is_fatal_error */ #define HA_CHECK_DUP_KEY 1 @@ -339,25 +427,21 @@ enum legacy_db_type { - DB_TYPE_UNKNOWN=0,DB_TYPE_DIAB_ISAM=1, - DB_TYPE_HASH,DB_TYPE_MISAM,DB_TYPE_PISAM, - DB_TYPE_RMS_ISAM, DB_TYPE_HEAP, DB_TYPE_ISAM, - DB_TYPE_MRG_ISAM, DB_TYPE_MYISAM, DB_TYPE_MRG_MYISAM, - DB_TYPE_BERKELEY_DB, DB_TYPE_INNODB, - DB_TYPE_GEMINI, DB_TYPE_NDBCLUSTER, - DB_TYPE_EXAMPLE_DB, DB_TYPE_ARCHIVE_DB, DB_TYPE_CSV_DB, - DB_TYPE_FEDERATED_DB, - DB_TYPE_BLACKHOLE_DB, - DB_TYPE_PARTITION_DB, - DB_TYPE_BINLOG, - DB_TYPE_SOLID, - DB_TYPE_PBXT, - DB_TYPE_TABLE_FUNCTION, - DB_TYPE_MEMCACHE, - DB_TYPE_FALCON, - DB_TYPE_MARIA, - /** Performance schema engine. */ - DB_TYPE_PERFORMANCE_SCHEMA, + /* note these numerical values are fixed and can *not* be changed */ + DB_TYPE_UNKNOWN=0, + DB_TYPE_HEAP=6, + DB_TYPE_MYISAM=9, + DB_TYPE_MRG_MYISAM=10, + DB_TYPE_INNODB=12, + DB_TYPE_EXAMPLE_DB=15, + DB_TYPE_ARCHIVE_DB=16, + DB_TYPE_CSV_DB=17, + DB_TYPE_FEDERATED_DB=18, + DB_TYPE_BLACKHOLE_DB=19, + DB_TYPE_PARTITION_DB=20, + DB_TYPE_BINLOG=21, + DB_TYPE_PBXT=23, + DB_TYPE_PERFORMANCE_SCHEMA=28, DB_TYPE_ARIA=42, DB_TYPE_TOKUDB=43, DB_TYPE_FIRST_DYNAMIC=44, @@ -376,6 +460,13 @@ enum row_type { ROW_TYPE_NOT_USED=-1, ROW_TYPE_DEFAULT, ROW_TYPE_FIXED, /* not part of the enum, so that it shouldn't be in switch(row_type) */ #define ROW_TYPE_MAX ((uint)ROW_TYPE_PAGE + 1) +/* Specifies data storage format for individual columns */ +enum column_format_type { + COLUMN_FORMAT_TYPE_DEFAULT= 0, /* Not specified (use engine default) */ + COLUMN_FORMAT_TYPE_FIXED= 1, /* FIXED format */ + COLUMN_FORMAT_TYPE_DYNAMIC= 2 /* DYNAMIC format */ +}; + enum enum_binlog_func { BFN_RESET_LOGS= 1, BFN_RESET_SLAVE= 2, @@ -420,6 +511,45 @@ enum enum_binlog_command { /* The following two are used by Maria engine: */ #define HA_CREATE_USED_TRANSACTIONAL (1L << 20) #define HA_CREATE_USED_PAGE_CHECKSUM (1L << 21) +/** This is set whenever STATS_PERSISTENT=0|1|default has been +specified in CREATE/ALTER TABLE. See also HA_OPTION_STATS_PERSISTENT in +include/my_base.h. It is possible to distinguish whether +STATS_PERSISTENT=default has been specified or no STATS_PERSISTENT= is +given at all. */ +#define HA_CREATE_USED_STATS_PERSISTENT (1L << 22) +/** + This is set whenever STATS_AUTO_RECALC=0|1|default has been + specified in CREATE/ALTER TABLE. See enum_stats_auto_recalc. + It is possible to distinguish whether STATS_AUTO_RECALC=default + has been specified or no STATS_AUTO_RECALC= is given at all. +*/ +#define HA_CREATE_USED_STATS_AUTO_RECALC (1L << 23) +/** + This is set whenever STATS_SAMPLE_PAGES=N|default has been + specified in CREATE/ALTER TABLE. It is possible to distinguish whether + STATS_SAMPLE_PAGES=default has been specified or no STATS_SAMPLE_PAGES= is + given at all. +*/ +#define HA_CREATE_USED_STATS_SAMPLE_PAGES (1L << 24) + + +/* + This is master database for most of system tables. However there + can be other databases which can hold system tables. Respective + storage engines define their own system database names. +*/ +extern const char *mysqld_system_database; + +/* + Structure to hold list of system_database.system_table. + This is used at both mysqld and storage engine layer. +*/ +struct st_system_tablename +{ + const char *db; + const char *tablename; +}; + typedef ulonglong my_xid; // this line is the same as in log_event.h #define MYSQL_XID_PREFIX "MySQLXid" @@ -478,7 +608,7 @@ struct xid_t { bqual_length= b; memcpy(data, d, g+b); } - bool is_null() { return formatID == -1; } + bool is_null() const { return formatID == -1; } void null() { formatID= -1; } my_xid quick_get_my_xid() { @@ -497,11 +627,11 @@ struct xid_t { return sizeof(formatID)+sizeof(gtrid_length)+sizeof(bqual_length)+ gtrid_length+bqual_length; } - uchar *key() + uchar *key() const { return (uchar *)>rid_length; } - uint key_length() + uint key_length() const { return sizeof(gtrid_length)+sizeof(bqual_length)+gtrid_length+bqual_length; } @@ -598,18 +728,20 @@ struct TABLE; */ enum enum_schema_tables { - SCH_CHARSETS= 0, - SCH_CLIENT_STATS, + SCH_ALL_PLUGINS, + SCH_APPLICABLE_ROLES, + SCH_CHARSETS, SCH_COLLATIONS, SCH_COLLATION_CHARACTER_SET_APPLICABILITY, SCH_COLUMNS, SCH_COLUMN_PRIVILEGES, + SCH_ENABLED_ROLES, SCH_ENGINES, SCH_EVENTS, + SCH_EXPLAIN, SCH_FILES, SCH_GLOBAL_STATUS, SCH_GLOBAL_VARIABLES, - SCH_INDEX_STATS, SCH_KEY_CACHES, SCH_KEY_COLUMN_USAGE, SCH_OPEN_TABLES, @@ -625,21 +757,23 @@ enum enum_schema_tables SCH_SESSION_STATUS, SCH_SESSION_VARIABLES, SCH_STATISTICS, - SCH_STATUS, + SCH_SYSTEM_VARIABLES, SCH_TABLES, SCH_TABLESPACES, SCH_TABLE_CONSTRAINTS, SCH_TABLE_NAMES, SCH_TABLE_PRIVILEGES, - SCH_TABLE_STATS, SCH_TRIGGERS, SCH_USER_PRIVILEGES, - SCH_USER_STATS, - SCH_VARIABLES, - SCH_VIEWS + SCH_VIEWS, +#ifdef HAVE_SPATIAL + SCH_GEOMETRY_COLUMNS, + SCH_SPATIAL_REF_SYS, +#endif /*HAVE_SPATIAL*/ }; struct TABLE_SHARE; +struct HA_CREATE_INFO; struct st_foreign_key_info; typedef struct st_foreign_key_info FOREIGN_KEY_INFO; typedef bool (stat_print_fn)(THD *thd, const char *type, uint type_len, @@ -719,22 +853,26 @@ struct ha_index_option_struct; enum ha_option_type { HA_OPTION_TYPE_ULL, /* unsigned long long */ HA_OPTION_TYPE_STRING, /* char * */ HA_OPTION_TYPE_ENUM, /* uint */ - HA_OPTION_TYPE_BOOL}; /* bool */ + HA_OPTION_TYPE_BOOL, /* bool */ + HA_OPTION_TYPE_SYSVAR};/* type of the sysval */ #define HA_xOPTION_NUMBER(name, struc, field, def, min, max, blk_siz) \ { HA_OPTION_TYPE_ULL, name, sizeof(name)-1, \ - offsetof(struc, field), def, min, max, blk_siz, 0 } + offsetof(struc, field), def, min, max, blk_siz, 0, 0 } #define HA_xOPTION_STRING(name, struc, field) \ { HA_OPTION_TYPE_STRING, name, sizeof(name)-1, \ - offsetof(struc, field), 0, 0, 0, 0, 0 } + offsetof(struc, field), 0, 0, 0, 0, 0, 0} #define HA_xOPTION_ENUM(name, struc, field, values, def) \ { HA_OPTION_TYPE_ENUM, name, sizeof(name)-1, \ offsetof(struc, field), def, 0, \ - sizeof(values)-1, 0, values } + sizeof(values)-1, 0, values, 0 } #define HA_xOPTION_BOOL(name, struc, field, def) \ { HA_OPTION_TYPE_BOOL, name, sizeof(name)-1, \ - offsetof(struc, field), def, 0, 1, 0, 0 } -#define HA_xOPTION_END { HA_OPTION_TYPE_ULL, 0, 0, 0, 0, 0, 0, 0, 0 } + offsetof(struc, field), def, 0, 1, 0, 0, 0 } +#define HA_xOPTION_SYSVAR(name, struc, field, sysvar) \ + { HA_OPTION_TYPE_SYSVAR, name, sizeof(name)-1, \ + offsetof(struc, field), 0, 0, 0, 0, 0, MYSQL_SYSVAR(sysvar) } +#define HA_xOPTION_END { HA_OPTION_TYPE_ULL, 0, 0, 0, 0, 0, 0, 0, 0, 0 } #define HA_TOPTION_NUMBER(name, field, def, min, max, blk_siz) \ HA_xOPTION_NUMBER(name, ha_table_option_struct, field, def, min, max, blk_siz) @@ -744,6 +882,8 @@ enum ha_option_type { HA_OPTION_TYPE_ULL, /* unsigned long long */ HA_xOPTION_ENUM(name, ha_table_option_struct, field, values, def) #define HA_TOPTION_BOOL(name, field, def) \ HA_xOPTION_BOOL(name, ha_table_option_struct, field, def) +#define HA_TOPTION_SYSVAR(name, field, sysvar) \ + HA_xOPTION_SYSVAR(name, ha_table_option_struct, field, sysvar) #define HA_TOPTION_END HA_xOPTION_END #define HA_FOPTION_NUMBER(name, field, def, min, max, blk_siz) \ @@ -754,6 +894,8 @@ enum ha_option_type { HA_OPTION_TYPE_ULL, /* unsigned long long */ HA_xOPTION_ENUM(name, ha_field_option_struct, field, values, def) #define HA_FOPTION_BOOL(name, field, def) \ HA_xOPTION_BOOL(name, ha_field_option_struct, field, def) +#define HA_FOPTION_SYSVAR(name, field, sysvar) \ + HA_xOPTION_SYSVAR(name, ha_field_option_struct, field, sysvar) #define HA_FOPTION_END HA_xOPTION_END #define HA_IOPTION_NUMBER(name, field, def, min, max, blk_siz) \ @@ -764,6 +906,8 @@ enum ha_option_type { HA_OPTION_TYPE_ULL, /* unsigned long long */ HA_xOPTION_ENUM(name, ha_index_option_struct, field, values, def) #define HA_IOPTION_BOOL(name, field, def) \ HA_xOPTION_BOOL(name, ha_index_option_struct, field, def) +#define HA_IOPTION_SYSVAR(name, field, sysvar) \ + HA_xOPTION_SYSVAR(name, ha_index_option_struct, field, sysvar) #define HA_IOPTION_END HA_xOPTION_END typedef struct st_ha_create_table_option { @@ -774,6 +918,7 @@ typedef struct st_ha_create_table_option { ulonglong def_value; ulonglong min_value, max_value, block_size; const char *values; + struct st_mysql_sys_var *var; } ha_create_table_option; enum handler_iterator_type @@ -817,6 +962,11 @@ struct handler_iterator { }; class handler; +class group_by_handler; +struct Query; +typedef class st_select_lex SELECT_LEX; +typedef struct st_order ORDER; + /* handlerton is a singleton structure - one instance per storage engine - to provide access to storage engine functionality that works on the @@ -884,6 +1034,13 @@ struct handlerton to the savepoint_set call */ int (*savepoint_rollback)(handlerton *hton, THD *thd, void *sv); + /** + Check if storage engine allows to release metadata locks which were + acquired after the savepoint if rollback to savepoint is done. + @return true - If it is safe to release MDL locks. + false - If it is not. + */ + bool (*savepoint_rollback_can_release_mdl)(handlerton *hton, THD *thd); int (*savepoint_release)(handlerton *hton, THD *thd, void *sv); /* 'all' is true if it's a real commit, that makes persistent changes @@ -986,6 +1143,46 @@ struct handlerton int (*recover)(handlerton *hton, XID *xid_list, uint len); int (*commit_by_xid)(handlerton *hton, XID *xid); int (*rollback_by_xid)(handlerton *hton, XID *xid); + /* + The commit_checkpoint_request() handlerton method is used to checkpoint + the XA recovery process for storage engines that support two-phase + commit. + + The method is optional - an engine that does not implemented is expected + to work the traditional way, where every commit() durably flushes the + transaction to disk in the engine before completion, so XA recovery will + no longer be needed for that transaction. + + An engine that does implement commit_checkpoint_request() is also + expected to implement commit_ordered(), so that ordering of commits is + consistent between 2pc participants. Such engine is no longer required to + durably flush to disk transactions in commit(), provided that the + transaction has been successfully prepare()d and commit_ordered(); thus + potentionally saving one fsync() call. (Engine must still durably flush + to disk in commit() when no prepare()/commit_ordered() steps took place, + at least if durable commits are wanted; this happens eg. if binlog is + disabled). + + The TC will periodically (eg. once per binlog rotation) call + commit_checkpoint_request(). When this happens, the engine must arrange + for all transaction that have completed commit_ordered() to be durably + flushed to disk (this does not include transactions that might be in the + middle of executing commit_ordered()). When such flush has completed, the + engine must call commit_checkpoint_notify_ha(), passing back the opaque + "cookie". + + The flush and call of commit_checkpoint_notify_ha() need not happen + immediately - it can be scheduled and performed asynchroneously (ie. as + part of next prepare(), or sync every second, or whatever), but should + not be postponed indefinitely. It is however also permissible to do it + immediately, before returning from commit_checkpoint_request(). + + When commit_checkpoint_notify_ha() is called, the TC will know that the + transactions are durably committed, and thus no longer require XA + recovery. It uses that to reduce the work needed for any subsequent XA + recovery process. + */ + void (*commit_checkpoint_request)(handlerton *hton, void *cookie); /* "Disable or enable checkpointing internal to the storage engine. This is used for FLUSH TABLES WITH READ LOCK AND DISABLE CHECKPOINT to ensure that @@ -1042,18 +1239,11 @@ struct handlerton enum handler_create_iterator_result (*create_iterator)(handlerton *hton, enum handler_iterator_type type, struct handler_iterator *fill_this_in); - int (*discover)(handlerton *hton, THD* thd, const char *db, - const char *name, - uchar **frmblob, - size_t *frmlen); - int (*find_files)(handlerton *hton, THD *thd, - const char *db, - const char *path, - const char *wild, bool dir, List<LEX_STRING> *files); - int (*table_exists_in_engine)(handlerton *hton, THD* thd, const char *db, - const char *name); - - uint32 license; /* Flag for Engine License */ + int (*abort_transaction)(handlerton *hton, THD *bf_thd, + THD *victim_thd, my_bool signal); + int (*set_checkpoint)(handlerton *hton, const XID* xid); + int (*get_checkpoint)(handlerton *hton, XID* xid); + void (*fake_trx_id)(handlerton *hton, THD *thd); /* Optional clauses in the CREATE/ALTER TABLE */ @@ -1061,14 +1251,145 @@ struct handlerton ha_create_table_option *field_options; // these are specified per field ha_create_table_option *index_options; // these are specified per index + /** + The list of extensions of files created for a single table in the + database directory (datadir/db_name/). + + Used by open_table_error(), by the default rename_table and delete_table + handler methods, and by the default discovery implementation. + + For engines that have more than one file name extentions (separate + metadata, index, and/or data files), the order of elements is relevant. + First element of engine file name extentions array should be metadata + file extention. This is implied by the open_table_error() + and the default discovery implementation. + + Second element - data file extention. This is implied + assumed by REPAIR TABLE ... USE_FRM implementation. + */ + const char **tablefile_extensions; // by default - empty list + + /********************************************************************** + Functions to intercept queries + **********************************************************************/ + + /* + Create and return a group_by_handler, if the storage engine can execute + the summary / group by query. + If the storage engine can't do that, return NULL. + + The server guaranteeds that all tables in the list belong to this + storage engine. + */ + group_by_handler *(*create_group_by)(THD *thd, Query *query); + + /********************************************************************* + Table discovery API. + It allows the server to "discover" tables that exist in the storage + engine, without user issuing an explicit CREATE TABLE statement. + **********************************************************************/ + + /* + This method is required for any engine that supports automatic table + discovery, there is no default implementation. + + Given a TABLE_SHARE discover_table() fills it in with a correct table + structure using one of the TABLE_SHARE::init_from_* methods. + + Returns HA_ERR_NO_SUCH_TABLE if the table did not exist in the engine, + zero if the table was discovered successfully, or any other + HA_ERR_* error code as appropriate if the table existed, but the + discovery failed. + */ + int (*discover_table)(handlerton *hton, THD* thd, TABLE_SHARE *share); + + /* + The discover_table_names method tells the server + about all tables in the specified database that the engine + knows about. Tables (or file names of tables) are added to + the provided discovered_list collector object using + add_table() or add_file() methods. + */ + class discovered_list + { + public: + virtual bool add_table(const char *tname, size_t tlen) = 0; + virtual bool add_file(const char *fname) = 0; + protected: virtual ~discovered_list() {} + }; + + /* + By default (if not implemented by the engine, but the discover_table() is + implemented) it will perform a file-based discovery: + + - if tablefile_extensions[0] is not null, this will discovers all tables + with the tablefile_extensions[0] extension. + + Returns 0 on success and 1 on error. + */ + int (*discover_table_names)(handlerton *hton, LEX_STRING *db, MY_DIR *dir, + discovered_list *result); + + /* + This is a method that allows to server to check if a table exists without + an overhead of the complete discovery. + + By default (if not implemented by the engine, but the discovery_table() is + implemented) it will try to perform a file-based discovery: + + - if tablefile_extensions[0] is not null this will look for a file name + with the tablefile_extensions[0] extension. + + - if tablefile_extensions[0] is null, this will resort to discover_table(). + + Note that resorting to discover_table() is slow and the engine + should probably implement its own discover_table_existence() method, + if its tablefile_extensions[0] is null. + + Returns 1 if the table exists and 0 if it does not. + */ + int (*discover_table_existence)(handlerton *hton, const char *db, + const char *table_name); + + /* + This is the assisted table discovery method. Unlike the fully + automatic discovery as above, here a user is expected to issue an + explicit CREATE TABLE with the appropriate table attributes to + "assist" the discovery of a table. But this "discovering" CREATE TABLE + statement will not specify the table structure - the engine discovers + it using this method. For example, FederatedX uses it in + + CREATE TABLE t1 ENGINE=FEDERATED CONNECTION="mysql://foo/bar/t1"; + + Given a TABLE_SHARE discover_table_structure() fills it in with a correct + table structure using one of the TABLE_SHARE::init_from_* methods. + + Assisted discovery works independently from the automatic discover. + An engine is allowed to support only assisted discovery and not + support automatic one. Or vice versa. + */ + int (*discover_table_structure)(handlerton *hton, THD* thd, + TABLE_SHARE *share, HA_CREATE_INFO *info); }; -inline LEX_STRING *hton_name(const handlerton *hton) +static inline LEX_STRING *hton_name(const handlerton *hton) { return &(hton2plugin[hton->slot]->name); } +static inline handlerton *plugin_hton(plugin_ref plugin) +{ + return plugin_data(plugin, handlerton *); +} + +static inline sys_var *find_hton_sysvar(handlerton *hton, st_mysql_sys_var *var) +{ + return find_plugin_sysvar(hton2plugin[hton->slot], var); +} + +handlerton *ha_default_handlerton(THD *thd); +handlerton *ha_default_tmp_handlerton(THD *thd); /* Possible flags of a handlerton (there can be 32 of them) */ #define HTON_NO_FLAGS 0 @@ -1079,8 +1400,23 @@ inline LEX_STRING *hton_name(const handlerton *hton) #define HTON_NOT_USER_SELECTABLE (1 << 5) #define HTON_TEMPORARY_NOT_SUPPORTED (1 << 6) //Having temporary tables not supported #define HTON_SUPPORT_LOG_TABLES (1 << 7) //Engine supports log tables -#define HTON_NO_PARTITION (1 << 8) //You can not partition these tables -#define HTON_EXTENDED_KEYS (1 << 9) //supports extended keys +#define HTON_NO_PARTITION (1 << 8) //Not partition of these tables + +/* + This flag should be set when deciding that the engine does not allow + row based binary logging (RBL) optimizations. + + Currently, setting this flag, means that table's read/write_set will + be left untouched when logging changes to tables in this engine. In + practice this means that the server will not mess around with + table->write_set and/or table->read_set when using RBL and deciding + whether to log full or minimal rows. + + It's valuable for instance for virtual tables, eg: Performance + Schema which have no meaning for replication. +*/ +#define HTON_NO_BINLOG_ROW_OPT (1 << 9) +#define HTON_SUPPORTS_EXTENDED_KEYS (1 <<10) //supports extended keys // MySQL compatibility. Unused. #define HTON_SUPPORTS_FOREIGN_KEYS (1 << 0) //Foreign key constraint supported. @@ -1123,9 +1459,48 @@ struct THD_TRANS */ bool modified_non_trans_table; - void reset() { no_2pc= FALSE; modified_non_trans_table= FALSE; } + void reset() { + no_2pc= FALSE; + modified_non_trans_table= FALSE; + m_unsafe_rollback_flags= 0; + } bool is_empty() const { return ha_list == NULL; } THD_TRANS() {} /* Remove gcc warning */ + + unsigned int m_unsafe_rollback_flags; + /* + Define the type of statemens which cannot be rolled back safely. + Each type occupies one bit in m_unsafe_rollback_flags. + */ + static unsigned int const MODIFIED_NON_TRANS_TABLE= 0x01; + static unsigned int const CREATED_TEMP_TABLE= 0x02; + static unsigned int const DROPPED_TEMP_TABLE= 0x04; + static unsigned int const DID_WAIT= 0x08; + static unsigned int const DID_DDL= 0x10; + + void mark_created_temp_table() + { + DBUG_PRINT("debug", ("mark_created_temp_table")); + m_unsafe_rollback_flags|= CREATED_TEMP_TABLE; + } + void mark_dropped_temp_table() + { + DBUG_PRINT("debug", ("mark_dropped_temp_table")); + m_unsafe_rollback_flags|= DROPPED_TEMP_TABLE; + } + bool has_created_dropped_temp_table() const { + return + (m_unsafe_rollback_flags & (CREATED_TEMP_TABLE|DROPPED_TEMP_TABLE)) != 0; + } + void mark_trans_did_wait() { m_unsafe_rollback_flags|= DID_WAIT; } + bool trans_did_wait() const { + return (m_unsafe_rollback_flags & DID_WAIT) != 0; + } + void mark_trans_did_ddl() { m_unsafe_rollback_flags|= DID_DDL; } + bool trans_did_ddl() const { + return (m_unsafe_rollback_flags & DID_DDL) != 0; + } + }; @@ -1249,13 +1624,49 @@ struct st_table_log_memory_entry; class partition_info; struct st_partition_iter; -#define NOT_A_PARTITION_ID ((uint32)-1) enum ha_choice { HA_CHOICE_UNDEF, HA_CHOICE_NO, HA_CHOICE_YES, HA_CHOICE_MAX }; -typedef struct st_ha_create_information +enum enum_stats_auto_recalc { HA_STATS_AUTO_RECALC_DEFAULT= 0, + HA_STATS_AUTO_RECALC_ON, + HA_STATS_AUTO_RECALC_OFF }; + +/** + A helper struct for schema DDL statements: + CREATE SCHEMA [IF NOT EXISTS] name [ schema_specification... ] + ALTER SCHEMA name [ schema_specification... ] + + It stores the "schema_specification" part of the CREATE/ALTER statements and + is passed to mysql_create_db() and mysql_alter_db(). + Currently consists only of the schema default character set and collation. +*/ +struct Schema_specification_st { - CHARSET_INFO *table_charset, *default_table_charset; + CHARSET_INFO *default_table_charset; + void init() + { + bzero(this, sizeof(*this)); + } +}; + + +/** + A helper struct for table DDL statements, e.g.: + CREATE [OR REPLACE] [TEMPORARY] + TABLE [IF NOT EXISTS] tbl_name table_contents_source; + + Represents a combinations of: + 1. The scope, i.e. TEMPORARY or not TEMPORARY + 2. The "table_contents_source" part of the table DDL statements, + which can be initialized from either of these: + - table_element_list ... // Explicit definition (column and key list) + - LIKE another_table_name ... // Copy structure from another table + - [AS] SELECT ... // Copy structure from a subquery +*/ +struct Table_scope_and_contents_source_st +{ + CHARSET_INFO *table_charset; + LEX_CUSTRING tabledef_version; LEX_STRING connect_string; const char *password, *tablespace; LEX_STRING comment; @@ -1263,11 +1674,19 @@ typedef struct st_ha_create_information const char *alias; ulonglong max_rows,min_rows; ulonglong auto_increment_value; - ulong table_options; + ulong table_options; ///< HA_OPTION_ values ulong avg_row_length; ulong used_fields; ulong key_block_size; - SQL_I_List<TABLE_LIST> merge_list; + /* + number of pages to sample during + stats estimation, if used, otherwise 0. + */ + uint stats_sample_pages; + uint null_bits; /* NULL bits at start of record */ + uint options; /* OR of HA_CREATE_ options */ + uint merge_insert_method; + uint extra_size; /* length of extra data segment */ handlerton *db_type; /** Row type of the table definition. @@ -1276,24 +1695,439 @@ typedef struct st_ha_create_information For ALTER TABLE defaults to ROW_TYPE_NOT_USED (means "keep the current"). Can be changed either explicitly by the parser. - If nothing speficied inherits the value of the original table (if present). + If nothing specified inherits the value of the original table (if present). */ enum row_type row_type; - uint null_bits; /* NULL bits at start of record */ - uint options; /* OR of HA_CREATE_ options */ - uint merge_insert_method; - uint extra_size; /* length of extra data segment */ enum ha_choice transactional; - bool frm_only; ///< 1 if no ha_create_table() - bool varchar; ///< 1 if table has a VARCHAR enum ha_storage_media storage_media; ///< DEFAULT, DISK or MEMORY enum ha_choice page_checksum; ///< If we have page_checksums engine_option_value *option_list; ///< list of table create options + enum_stats_auto_recalc stats_auto_recalc; + bool varchar; ///< 1 if table has a VARCHAR + /* the following three are only for ALTER TABLE, check_if_incompatible_data() */ ha_table_option_struct *option_struct; ///< structure with parsed table options ha_field_option_struct **fields_option_struct; ///< array of field option structures ha_index_option_struct **indexes_option_struct; ///< array of index option structures -} HA_CREATE_INFO; + + /* The following is used to remember the old state for CREATE OR REPLACE */ + TABLE *table; + TABLE_LIST *pos_in_locked_tables; + MDL_ticket *mdl_ticket; + bool table_was_deleted; + TABLE_LIST *merge_list; + + void init() + { + bzero(this, sizeof(*this)); + } + bool tmp_table() const { return options & HA_LEX_CREATE_TMP_TABLE; } + void use_default_db_type(THD *thd) + { + db_type= tmp_table() ? ha_default_tmp_handlerton(thd) + : ha_default_handlerton(thd); + } +}; + + +/** + This struct is passed to handler table routines, e.g. ha_create(). + It does not include the "OR REPLACE" and "IF NOT EXISTS" parts, as these + parts are handled on the SQL level and are not needed on the handler level. +*/ +struct HA_CREATE_INFO: public Table_scope_and_contents_source_st, + public Schema_specification_st +{ + void init() + { + Table_scope_and_contents_source_st::init(); + Schema_specification_st::init(); + } + bool check_conflicting_charset_declarations(CHARSET_INFO *cs); + bool add_table_option_default_charset(CHARSET_INFO *cs) + { + // cs can be NULL, e.g.: CREATE TABLE t1 (..) CHARACTER SET DEFAULT; + if (check_conflicting_charset_declarations(cs)) + return true; + default_table_charset= cs; + used_fields|= HA_CREATE_USED_DEFAULT_CHARSET; + return false; + } + bool add_alter_list_item_convert_to_charset(CHARSET_INFO *cs) + { + /* + cs cannot be NULL, as sql_yacc.yy translates + CONVERT TO CHARACTER SET DEFAULT + to + CONVERT TO CHARACTER SET <character-set-of-the-current-database> + TODO: Should't we postpone resolution of DEFAULT until the + character set of the table owner database is loaded from its db.opt? + */ + DBUG_ASSERT(cs); + if (check_conflicting_charset_declarations(cs)) + return true; + table_charset= default_table_charset= cs; + used_fields|= (HA_CREATE_USED_CHARSET | HA_CREATE_USED_DEFAULT_CHARSET); + return false; + } + ulong table_options_with_row_type() + { + if (row_type == ROW_TYPE_DYNAMIC || row_type == ROW_TYPE_PAGE) + return table_options | HA_OPTION_PACK_RECORD; + else + return table_options; + } +}; + + +/** + This struct is passed to mysql_create_table() and similar creation functions, + as well as to show_create_table(). +*/ +struct Table_specification_st: public HA_CREATE_INFO, + public DDL_options_st +{ + // Deep initialization + void init() + { + HA_CREATE_INFO::init(); + DDL_options_st::init(); + } + void init(DDL_options_st::Options options_arg) + { + HA_CREATE_INFO::init(); + DDL_options_st::init(options_arg); + } + /* + Quick initialization, for parser. + Most of the HA_CREATE_INFO is left uninitialized. + It gets fully initialized in sql_yacc.yy, only when the parser + scans a related keyword (e.g. CREATE, ALTER). + */ + void lex_start() + { + HA_CREATE_INFO::options= 0; + DDL_options_st::init(); + } +}; + + +/** + In-place alter handler context. + + This is a superclass intended to be subclassed by individual handlers + in order to store handler unique context between in-place alter API calls. + + The handler is responsible for creating the object. This can be done + as early as during check_if_supported_inplace_alter(). + + The SQL layer is responsible for destroying the object. + The class extends Sql_alloc so the memory will be mem root allocated. + + @see Alter_inplace_info +*/ + +class inplace_alter_handler_ctx : public Sql_alloc +{ +public: + inplace_alter_handler_ctx() {} + + virtual ~inplace_alter_handler_ctx() {} +}; + + +/** + Class describing changes to be done by ALTER TABLE. + Instance of this class is passed to storage engine in order + to determine if this ALTER TABLE can be done using in-place + algorithm. It is also used for executing the ALTER TABLE + using in-place algorithm. +*/ + +class Alter_inplace_info +{ +public: + /** + Bits to show in detail what operations the storage engine is + to execute. + + All these operations are supported as in-place operations by the + SQL layer. This means that operations that by their nature must + be performed by copying the table to a temporary table, will not + have their own flags here. + + We generally try to specify handler flags only if there are real + changes. But in cases when it is cumbersome to determine if some + attribute has really changed we might choose to set flag + pessimistically, for example, relying on parser output only. + */ + typedef ulong HA_ALTER_FLAGS; + + // Add non-unique, non-primary index + static const HA_ALTER_FLAGS ADD_INDEX = 1L << 0; + + // Drop non-unique, non-primary index + static const HA_ALTER_FLAGS DROP_INDEX = 1L << 1; + + // Add unique, non-primary index + static const HA_ALTER_FLAGS ADD_UNIQUE_INDEX = 1L << 2; + + // Drop unique, non-primary index + static const HA_ALTER_FLAGS DROP_UNIQUE_INDEX = 1L << 3; + + // Add primary index + static const HA_ALTER_FLAGS ADD_PK_INDEX = 1L << 4; + + // Drop primary index + static const HA_ALTER_FLAGS DROP_PK_INDEX = 1L << 5; + + // Add column + static const HA_ALTER_FLAGS ADD_COLUMN = 1L << 6; + + // Drop column + static const HA_ALTER_FLAGS DROP_COLUMN = 1L << 7; + + // Rename column + static const HA_ALTER_FLAGS ALTER_COLUMN_NAME = 1L << 8; + + // Change column datatype + static const HA_ALTER_FLAGS ALTER_COLUMN_TYPE = 1L << 9; + + /** + Change column datatype in such way that new type has compatible + packed representation with old type, so it is theoretically + possible to perform change by only updating data dictionary + without changing table rows. + */ + static const HA_ALTER_FLAGS ALTER_COLUMN_EQUAL_PACK_LENGTH = 1L << 10; + + // Reorder column + static const HA_ALTER_FLAGS ALTER_COLUMN_ORDER = 1L << 11; + + // Change column from NOT NULL to NULL + static const HA_ALTER_FLAGS ALTER_COLUMN_NULLABLE = 1L << 12; + + // Change column from NULL to NOT NULL + static const HA_ALTER_FLAGS ALTER_COLUMN_NOT_NULLABLE = 1L << 13; + + // Set or remove default column value + static const HA_ALTER_FLAGS ALTER_COLUMN_DEFAULT = 1L << 14; + + // Add foreign key + static const HA_ALTER_FLAGS ADD_FOREIGN_KEY = 1L << 15; + + // Drop foreign key + static const HA_ALTER_FLAGS DROP_FOREIGN_KEY = 1L << 16; + + // table_options changed, see HA_CREATE_INFO::used_fields for details. + static const HA_ALTER_FLAGS CHANGE_CREATE_OPTION = 1L << 17; + + // Table is renamed + static const HA_ALTER_FLAGS ALTER_RENAME = 1L << 18; + + // column's engine options changed, something in field->option_struct + static const HA_ALTER_FLAGS ALTER_COLUMN_OPTION = 1L << 19; + + // MySQL alias for the same thing: + static const HA_ALTER_FLAGS ALTER_COLUMN_STORAGE_TYPE = 1L << 19; + + // Change the column format of column + static const HA_ALTER_FLAGS ALTER_COLUMN_COLUMN_FORMAT = 1L << 20; + + // Add partition + static const HA_ALTER_FLAGS ADD_PARTITION = 1L << 21; + + // Drop partition + static const HA_ALTER_FLAGS DROP_PARTITION = 1L << 22; + + // Changing partition options + static const HA_ALTER_FLAGS ALTER_PARTITION = 1L << 23; + + // Coalesce partition + static const HA_ALTER_FLAGS COALESCE_PARTITION = 1L << 24; + + // Reorganize partition ... into + static const HA_ALTER_FLAGS REORGANIZE_PARTITION = 1L << 25; + + // Reorganize partition + static const HA_ALTER_FLAGS ALTER_TABLE_REORG = 1L << 26; + + // Remove partitioning + static const HA_ALTER_FLAGS ALTER_REMOVE_PARTITIONING = 1L << 27; + + // Partition operation with ALL keyword + static const HA_ALTER_FLAGS ALTER_ALL_PARTITION = 1L << 28; + + /** + Recreate the table for ALTER TABLE FORCE, ALTER TABLE ENGINE + and OPTIMIZE TABLE operations. + */ + static const HA_ALTER_FLAGS RECREATE_TABLE = 1L << 29; + + // Virtual columns changed + static const HA_ALTER_FLAGS ALTER_COLUMN_VCOL = 1L << 30; + + /** + ALTER TABLE for a partitioned table. The engine needs to commit + online alter of all partitions atomically (using group_commit_ctx) + */ + static const HA_ALTER_FLAGS ALTER_PARTITIONED = 1L << 31; + + /** + Create options (like MAX_ROWS) for the new version of table. + + @note The referenced instance of HA_CREATE_INFO object was already + used to create new .FRM file for table being altered. So it + has been processed by mysql_prepare_create_table() already. + For example, this means that it has HA_OPTION_PACK_RECORD + flag in HA_CREATE_INFO::table_options member correctly set. + */ + HA_CREATE_INFO *create_info; + + /** + Alter options, fields and keys for the new version of table. + + @note The referenced instance of Alter_info object was already + used to create new .FRM file for table being altered. So it + has been processed by mysql_prepare_create_table() already. + In particular, this means that in Create_field objects for + fields which were present in some form in the old version + of table, Create_field::field member points to corresponding + Field instance for old version of table. + */ + Alter_info *alter_info; + + /** + Array of KEYs for new version of table - including KEYs to be added. + + @note Currently this array is produced as result of + mysql_prepare_create_table() call. + This means that it follows different convention for + KEY_PART_INFO::fieldnr values than objects in TABLE::key_info + array. + + @todo This is mainly due to the fact that we need to keep compatibility + with removed handler::add_index() call. We plan to switch to + TABLE::key_info numbering later. + + KEYs are sorted - see sort_keys(). + */ + KEY *key_info_buffer; + + /** Size of key_info_buffer array. */ + uint key_count; + + /** Size of index_drop_buffer array. */ + uint index_drop_count; + + /** + Array of pointers to KEYs to be dropped belonging to the TABLE instance + for the old version of the table. + */ + KEY **index_drop_buffer; + + /** Size of index_add_buffer array. */ + uint index_add_count; + + /** + Array of indexes into key_info_buffer for KEYs to be added, + sorted in increasing order. + */ + uint *index_add_buffer; + + /** + Context information to allow handlers to keep context between in-place + alter API calls. + + @see inplace_alter_handler_ctx for information about object lifecycle. + */ + inplace_alter_handler_ctx *handler_ctx; + + /** + If the table uses several handlers, like ha_partition uses one handler + per partition, this contains a Null terminated array of ctx pointers + that should all be committed together. + Or NULL if only handler_ctx should be committed. + Set to NULL if the low level handler::commit_inplace_alter_table uses it, + to signal to the main handler that everything was committed as atomically. + + @see inplace_alter_handler_ctx for information about object lifecycle. + */ + inplace_alter_handler_ctx **group_commit_ctx; + + /** + Flags describing in detail which operations the storage engine is to execute. + */ + HA_ALTER_FLAGS handler_flags; + + /** + Partition_info taking into account the partition changes to be performed. + Contains all partitions which are present in the old version of the table + with partitions to be dropped or changed marked as such + all partitions + to be added in the new version of table marked as such. + */ + partition_info *modified_part_info; + + /** true for ALTER IGNORE TABLE ... */ + const bool ignore; + + /** true for online operation (LOCK=NONE) */ + bool online; + + /** + Can be set by handler to describe why a given operation cannot be done + in-place (HA_ALTER_INPLACE_NOT_SUPPORTED) or why it cannot be done + online (HA_ALTER_INPLACE_NO_LOCK or + HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) + If set, it will be used with ER_ALTER_OPERATION_NOT_SUPPORTED_REASON if + results from handler::check_if_supported_inplace_alter() doesn't match + requirements set by user. If not set, the more generic + ER_ALTER_OPERATION_NOT_SUPPORTED will be used. + + Please set to a properly localized string, for example using + my_get_err_msg(), so that the error message as a whole is localized. + */ + const char *unsupported_reason; + + Alter_inplace_info(HA_CREATE_INFO *create_info_arg, + Alter_info *alter_info_arg, + KEY *key_info_arg, uint key_count_arg, + partition_info *modified_part_info_arg, + bool ignore_arg) + : create_info(create_info_arg), + alter_info(alter_info_arg), + key_info_buffer(key_info_arg), + key_count(key_count_arg), + index_drop_count(0), + index_drop_buffer(NULL), + index_add_count(0), + index_add_buffer(NULL), + handler_ctx(NULL), + group_commit_ctx(NULL), + handler_flags(0), + modified_part_info(modified_part_info_arg), + ignore(ignore_arg), + online(false), + unsupported_reason(NULL) + {} + + ~Alter_inplace_info() + { + delete handler_ctx; + } + + /** + Used after check_if_supported_inplace_alter() to report + error if the result does not match the LOCK/ALGORITHM + requirements set by the user. + + @param not_supported Part of statement that was not supported. + @param try_instead Suggestion as to what the user should + replace not_supported with. + */ + void report_unsupported_error(const char *not_supported, + const char *try_instead); +}; typedef struct st_key_create_information @@ -1302,6 +2136,12 @@ typedef struct st_key_create_information ulong block_size; LEX_STRING parser_name; LEX_STRING comment; + /** + A flag to determine if we will check for duplicate indexes. + This typically means that the key information was specified + directly by the user (set by the parser). + */ + bool check_for_duplicate_indexes; } KEY_CREATE_INFO; @@ -1460,21 +2300,24 @@ typedef struct st_range_seq_if typedef bool (*SKIP_INDEX_TUPLE_FUNC) (range_seq_t seq, range_id_t range_info); -class COST_VECT +class Cost_estimate { public: double io_count; /* number of I/O */ double avg_io_cost; /* cost of an average I/O oper. */ double cpu_cost; /* cost of operations in CPU */ - double mem_cost; /* cost of used memory */ double import_cost; /* cost of remote operations */ + double mem_cost; /* cost of used memory */ enum { IO_COEFF=1 }; enum { CPU_COEFF=1 }; enum { MEM_COEFF=1 }; enum { IMPORT_COEFF=1 }; - COST_VECT() {} // keep gcc happy + Cost_estimate() + { + reset(); + } double total_cost() { @@ -1482,7 +2325,17 @@ public: MEM_COEFF*mem_cost + IMPORT_COEFF*import_cost; } - void zero() + /** + Whether or not all costs in the object are zero + + @return true if all costs are zero, false otherwise + */ + bool is_zero() const + { + return !(io_count || cpu_cost || import_cost || mem_cost); + } + + void reset() { avg_io_cost= 1.0; io_count= cpu_cost= mem_cost= import_cost= 0.0; @@ -1496,13 +2349,14 @@ public: /* Don't multiply mem_cost */ } - void add(const COST_VECT* cost) + void add(const Cost_estimate* cost) { double io_count_sum= io_count + cost->io_count; add_io(cost->io_count, cost->avg_io_cost); io_count= io_count_sum; cpu_cost += cost->cpu_cost; } + void add_io(double add_io_cnt, double add_avg_cost) { /* In edge cases add_io_cnt may be zero */ @@ -1515,20 +2369,28 @@ public: } } + /// Add to CPU cost + void add_cpu(double add_cpu_cost) { cpu_cost+= add_cpu_cost; } + + /// Add to import cost + void add_import(double add_import_cost) { import_cost+= add_import_cost; } + + /// Add to memory cost + void add_mem(double add_mem_cost) { mem_cost+= add_mem_cost; } + /* To be used when we go from old single value-based cost calculations to - the new COST_VECT-based. + the new Cost_estimate-based. */ void convert_from_cost(double cost) { - zero(); - avg_io_cost= 1.0; + reset(); io_count= cost; } }; void get_sweep_read_cost(TABLE *table, ha_rows nrows, bool interrupted, - COST_VECT *cost); + Cost_estimate *cost); /* Indicates that all scanned ranges will be singlepoint (aka equality) ranges. @@ -1681,34 +2543,61 @@ uint calculate_key_len(TABLE *, uint, const uchar *, key_part_map); #define make_prev_keypart_map(N) (((key_part_map)1 << (N)) - 1) -/** - Index creation context. - Created by handler::add_index() and destroyed by handler::final_add_index(). - And finally freed at the end of the statement. - (Sql_alloc does not free in delete). -*/ - -class handler_add_index : public Sql_alloc +/** Base class to be used by handlers different shares */ +class Handler_share { public: - /* Table where the indexes are added */ - TABLE* const table; - /* Indexes being created */ - KEY* const key_info; - /* Size of key_info[] */ - const uint num_of_keys; - handler_add_index(TABLE *table_arg, KEY *key_info_arg, uint num_of_keys_arg) - : table (table_arg), key_info (key_info_arg), num_of_keys (num_of_keys_arg) - {} - virtual ~handler_add_index() {} + Handler_share() {} + virtual ~Handler_share() {} }; -class Query_cache; -struct Query_cache_block_table; + /** The handler class is the interface for dynamically loadable storage engines. Do not add ifdefs and take care when adding or changing virtual functions to avoid vtable confusion + + Functions in this class accept and return table columns data. Two data + representation formats are used: + 1. TableRecordFormat - Used to pass [partial] table records to/from + storage engine + + 2. KeyTupleFormat - used to pass index search tuples (aka "keys") to + storage engine. See opt_range.cc for description of this format. + + TableRecordFormat + ================= + [Warning: this description is work in progress and may be incomplete] + The table record is stored in a fixed-size buffer: + + record: null_bytes, column1_data, column2_data, ... + + The offsets of the parts of the buffer are also fixed: every column has + an offset to its column{i}_data, and if it is nullable it also has its own + bit in null_bytes. + + The record buffer only includes data about columns that are marked in the + relevant column set (table->read_set and/or table->write_set, depending on + the situation). + <not-sure>It could be that it is required that null bits of non-present + columns are set to 1</not-sure> + + VARIOUS EXCEPTIONS AND SPECIAL CASES + + If the table has no nullable columns, then null_bytes is still + present, its length is one byte <not-sure> which must be set to 0xFF + at all times. </not-sure> + + If the table has columns of type BIT, then certain bits from those columns + may be stored in null_bytes as well. Grep around for Field_bit for + details. + + For blob columns (see Field_blob), the record buffer stores length of the + data, following by memory pointer to the blob data. The pointer is owned + by the storage engine and is valid until the next operation. + + If a blob column has NULL value, then its length and blob data pointer + must be set to 0. */ class handler :public Sql_alloc @@ -1761,7 +2650,6 @@ public: uint ref_length; FT_INFO *ft_handler; enum {NONE=0, INDEX, RND} inited; - bool locked; bool implicit_emptied; /* Can be !=0 only if HEAP */ const COND *pushed_cond; /** @@ -1792,6 +2680,13 @@ public: /* One bigger than needed to avoid to test if key == MAX_KEY */ ulonglong index_rows_read[MAX_KEY+1]; +private: + /* ANALYZE time tracker, if present */ + Exec_time_tracker *tracker; +public: + void set_time_tracker(Exec_time_tracker *tracker_arg) { tracker=tracker_arg;} + + Item *pushed_idx_cond; uint pushed_idx_cond_keyno; /* The index which the above condition is for */ @@ -1818,6 +2713,24 @@ public: */ PSI_table *m_psi; + virtual void unbind_psi(); + virtual void rebind_psi(); + +private: + /** + The lock type set by when calling::ha_external_lock(). This is + propagated down to the storage engine. The reason for also storing + it here, is that when doing MRR we need to create/clone a second handler + object. This cloned handler object needs to know about the lock_type used. + */ + int m_lock_type; + /** + Pointer where to store/retrieve the Handler_share pointer. + For non partitioned handlers this is &TABLE_SHARE::ha_share. + */ + Handler_share **ha_share; + +public: handler(handlerton *ht_arg, TABLE_SHARE *share_arg) :table_share(share_arg), table(0), estimation_rows_to_insert(0), ht(ht_arg), @@ -1825,18 +2738,22 @@ public: in_range_check_pushed_down(FALSE), ref_length(sizeof(my_off_t)), ft_handler(0), inited(NONE), - locked(FALSE), implicit_emptied(0), + implicit_emptied(0), pushed_cond(0), next_insert_id(0), insert_id_for_cur_row(0), + tracker(NULL), pushed_idx_cond(NULL), pushed_idx_cond_keyno(MAX_KEY), auto_inc_intervals_count(0), - m_psi(NULL) + m_psi(NULL), m_lock_type(F_UNLCK), ha_share(NULL) { + DBUG_PRINT("info", + ("handler created F_UNLCK %d F_RDLCK %d F_WRLCK %d", + F_UNLCK, F_RDLCK, F_WRLCK)); reset_statistics(); } virtual ~handler(void) { - DBUG_ASSERT(locked == FALSE); + DBUG_ASSERT(m_lock_type == F_UNLCK); DBUG_ASSERT(inited == NONE); } virtual handler *clone(const char *name, MEM_ROOT *mem_root); @@ -1846,7 +2763,7 @@ public: cached_table_flags= table_flags(); } /* ha_ methods: pubilc wrappers for private virtual API */ - + int ha_open(TABLE *table, const char *name, int mode, uint test_if_locked); int ha_index_init(uint idx, bool sorted) { @@ -1929,11 +2846,11 @@ public: /** to be actually called to get 'check()' functionality*/ int ha_check(THD *thd, HA_CHECK_OPT *check_opt); int ha_repair(THD* thd, HA_CHECK_OPT* check_opt); - void ha_start_bulk_insert(ha_rows rows) + void ha_start_bulk_insert(ha_rows rows, uint flags= 0) { DBUG_ENTER("handler::ha_start_bulk_insert"); estimation_rows_to_insert= rows; - start_bulk_insert(rows); + start_bulk_insert(rows, flags); DBUG_VOID_RETURN; } int ha_end_bulk_insert() @@ -1954,15 +2871,14 @@ public: int ha_disable_indexes(uint mode); int ha_enable_indexes(uint mode); int ha_discard_or_import_tablespace(my_bool discard); - void ha_prepare_for_alter(); int ha_rename_table(const char *from, const char *to); int ha_delete_table(const char *name); void ha_drop_table(const char *name); int ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info); - int ha_create_handler_files(const char *name, const char *old_name, - int action_flag, HA_CREATE_INFO *info); + int ha_create_partitioning_metadata(const char *name, const char *old_name, + int action_flag); int ha_change_partitions(HA_CREATE_INFO *create_info, const char *path, @@ -1975,10 +2891,32 @@ public: void adjust_next_insert_id_after_explicit_value(ulonglong nr); int update_auto_increment(); - void print_keydup_error(uint key_nr, const char *msg, myf errflag); virtual void print_error(int error, myf errflag); virtual bool get_error_message(int error, String *buf); uint get_dup_key(int error); + /** + Retrieves the names of the table and the key for which there was a + duplicate entry in the case of HA_ERR_FOREIGN_DUPLICATE_KEY. + + If any of the table or key name is not available this method will return + false and will not change any of child_table_name or child_key_name. + + @param child_table_name[out] Table name + @param child_table_name_len[in] Table name buffer size + @param child_key_name[out] Key name + @param child_key_name_len[in] Key name buffer size + + @retval true table and key names were available + and were written into the corresponding + out parameters. + @retval false table and key names were not available, + the out parameters were not touched. + */ + virtual bool get_foreign_dup_key(char *child_table_name, + uint child_table_name_len, + char *child_key_name, + uint child_key_name_len) + { DBUG_ASSERT(false); return(false); } void reset_statistics() { rows_read= rows_changed= rows_tmp_read= 0; @@ -1992,6 +2930,18 @@ public: } virtual double scan_time() { return ulonglong2double(stats.data_file_length) / IO_SIZE + 2; } + + /** + The cost of reading a set of ranges from the table using an index + to access it. + + @param index The index number. + @param ranges The number of ranges to be read. + @param rows Total number of rows to be read. + + This method can be used to calculate the total cost of scanning a table + using an index by calling it using read_time(index, 1, table_size). + */ virtual double read_time(uint index, uint ranges, ha_rows rows) { return rows2double(ranges+rows); } @@ -2161,18 +3111,17 @@ protected: } public: - /* Similar functions like the above, but does statistics counting */ - inline int ha_index_read_map(uchar * buf, const uchar * key, - key_part_map keypart_map, - enum ha_rkey_function find_flag); - inline int ha_index_read_idx_map(uchar * buf, uint index, const uchar * key, - key_part_map keypart_map, - enum ha_rkey_function find_flag); - inline int ha_index_next(uchar * buf); - inline int ha_index_prev(uchar * buf); - inline int ha_index_first(uchar * buf); - inline int ha_index_last(uchar * buf); - inline int ha_index_next_same(uchar *buf, const uchar *key, uint keylen); + int ha_index_read_map(uchar * buf, const uchar * key, + key_part_map keypart_map, + enum ha_rkey_function find_flag); + int ha_index_read_idx_map(uchar * buf, uint index, const uchar * key, + key_part_map keypart_map, + enum ha_rkey_function find_flag); + int ha_index_next(uchar * buf); + int ha_index_prev(uchar * buf); + int ha_index_first(uchar * buf); + int ha_index_last(uchar * buf); + int ha_index_next_same(uchar *buf, const uchar *key, uint keylen); /* TODO: should we make for those functions non-virtual ha_func_name wrappers, too? @@ -2180,10 +3129,11 @@ public: virtual ha_rows multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq, void *seq_init_param, uint n_ranges, uint *bufsz, - uint *mrr_mode, COST_VECT *cost); + uint *mrr_mode, + Cost_estimate *cost); virtual ha_rows multi_range_read_info(uint keyno, uint n_ranges, uint keys, uint key_parts, uint *bufsz, - uint *mrr_mode, COST_VECT *cost); + uint *mrr_mode, Cost_estimate *cost); virtual int multi_range_read_init(RANGE_SEQ_IF *seq, void *seq_init_param, uint n_ranges, uint mrr_mode, HANDLER_BUFFER *buf); @@ -2218,6 +3168,7 @@ public: const key_range *end_key, bool eq_range, bool sorted); virtual int read_range_next(); + void set_end_range(const key_range *end_key); int compare_key(key_range *range); int compare_key2(key_range *range); virtual int ft_init() { return HA_ERR_WRONG_COMMAND; } @@ -2235,16 +3186,25 @@ private: */ virtual int rnd_pos_by_record(uchar *record) { + int error; + DBUG_ASSERT(table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION); + + error = ha_rnd_init(false); + if (error != 0) + return error; + position(record); - return rnd_pos(record, ref); + error = ha_rnd_pos(record, ref); + ha_rnd_end(); + return error; } virtual int read_first_row(uchar *buf, uint primary_key); public: /* Same as above, but with statistics */ inline int ha_ft_read(uchar *buf); - inline int ha_rnd_next(uchar *buf); - inline int ha_rnd_pos(uchar *buf, uchar *pos); + int ha_rnd_next(uchar *buf); + int ha_rnd_pos(uchar *buf, uchar *pos); inline int ha_rnd_pos_by_record(uchar *buf); inline int ha_read_first_row(uchar *buf, uint primary_key); @@ -2289,6 +3249,7 @@ public: If this method returns nonzero, it will also signal the storage engine that the next read will be a locking re-read of the row. */ + bool ha_was_semi_consistent_read(); virtual bool was_semi_consistent_read() { return 0; } /** Tell the engine whether it should avoid unnecessary lock waits. @@ -2350,10 +3311,15 @@ public: { return FALSE; } virtual char* get_foreign_key_create_info() { return(NULL);} /* gets foreign key create string from InnoDB */ - virtual char* get_tablespace_name(THD *thd, char *name, uint name_len) - { return(NULL);} /* gets tablespace name from handler */ - /** used in ALTER TABLE; 1 if changing storage engine is allowed */ - virtual bool can_switch_engines() { return 1; } + /** + Used in ALTER TABLE to check if changing storage engine is allowed. + + @note Called without holding thr_lock.c lock. + + @retval true Changing storage engine is allowed. + @retval false Changing storage engine not allowed. + */ + virtual bool can_switch_engines() { return true; } virtual int can_continue_handler_scan() { return 0; } /** Get the list of foreign keys in this table. @@ -2388,19 +3354,8 @@ public: { return; } /* prepare InnoDB for HANDLER */ virtual void free_foreign_key_create_info(char* str) {} /** The following can be called without an open handler */ - virtual const char *table_type() const =0; - /** - If frm_error() is called then we will use this to find out what file - extentions exist for the storage engine. This is also used by the default - rename_table and delete_table method in handler.cc. - - For engines that have two file name extentions (separate meta/index file - and data file), the order of elements is relevant. First element of engine - file name extentions array should be meta/index file extention. Second - element - data file extention. This order is assumed by - prepare_for_repair() when REPAIR TABLE ... USE_FRM is issued. - */ - virtual const char **bas_ext() const =0; + const char *table_type() const { return hton_name(ht)->str; } + const char **bas_ext() const { return ht->tablefile_extensions; } virtual int get_default_no_partitions(HA_CREATE_INFO *create_info) { return 1;} @@ -2415,57 +3370,21 @@ public: virtual ulong index_flags(uint idx, uint part, bool all_parts) const =0; -/** - First phase of in-place add index. - Handlers are supposed to create new indexes here but not make them - visible. - - @param table_arg Table to add index to - @param key_info Information about new indexes - @param num_of_key Number of new indexes - @param add[out] Context of handler specific information needed - for final_add_index(). - - @note This function can be called with less than exclusive metadata - lock depending on which flags are listed in alter_table_flags. -*/ - virtual int add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys, - handler_add_index **add) - { return (HA_ERR_WRONG_COMMAND); } - -/** - Second and last phase of in-place add index. - Commit or rollback pending new indexes. - - @param add Context of handler specific information from add_index(). - @param commit If true, commit. If false, rollback index changes. - - @note This function is called with exclusive metadata lock. -*/ - virtual int final_add_index(handler_add_index *add, bool commit) - { return (HA_ERR_WRONG_COMMAND); } - - virtual int prepare_drop_index(TABLE *table_arg, uint *key_num, - uint num_of_keys) - { return (HA_ERR_WRONG_COMMAND); } - virtual int final_drop_index(TABLE *table_arg) - { return (HA_ERR_WRONG_COMMAND); } - uint max_record_length() const - { return min(HA_MAX_REC_LENGTH, max_supported_record_length()); } + { return MY_MIN(HA_MAX_REC_LENGTH, max_supported_record_length()); } uint max_keys() const - { return min(MAX_KEY, max_supported_keys()); } + { return MY_MIN(MAX_KEY, max_supported_keys()); } uint max_key_parts() const - { return min(MAX_REF_PARTS, max_supported_key_parts()); } + { return MY_MIN(MAX_REF_PARTS, max_supported_key_parts()); } uint max_key_length() const - { return min(MAX_KEY_LENGTH, max_supported_key_length()); } + { return MY_MIN(MAX_DATA_LENGTH_FOR_KEY, max_supported_key_length()); } uint max_key_part_length() const - { return min(MAX_KEY_LENGTH, max_supported_key_part_length()); } + { return MY_MIN(MAX_DATA_LENGTH_FOR_KEY, max_supported_key_part_length()); } virtual uint max_supported_record_length() const { return HA_MAX_REC_LENGTH; } virtual uint max_supported_keys() const { return 0; } virtual uint max_supported_key_parts() const { return MAX_REF_PARTS; } - virtual uint max_supported_key_length() const { return MAX_KEY_LENGTH; } + virtual uint max_supported_key_length() const { return MAX_DATA_LENGTH_FOR_KEY; } virtual uint max_supported_key_part_length() const { return 255; } virtual uint min_record_length(uint options) const { return 1; } @@ -2684,10 +3603,274 @@ public: pushed_idx_cond_keyno= MAX_KEY; in_range_check_pushed_down= false; } + /** + Part of old, deprecated in-place ALTER API. + */ virtual bool check_if_incompatible_data(HA_CREATE_INFO *create_info, uint table_changes) { return COMPATIBLE_DATA_NO; } + /* On-line/in-place ALTER TABLE interface. */ + + /* + Here is an outline of on-line/in-place ALTER TABLE execution through + this interface. + + Phase 1 : Initialization + ======================== + During this phase we determine which algorithm should be used + for execution of ALTER TABLE and what level concurrency it will + require. + + *) This phase starts by opening the table and preparing description + of the new version of the table. + *) Then we check if it is impossible even in theory to carry out + this ALTER TABLE using the in-place algorithm. For example, because + we need to change storage engine or the user has explicitly requested + usage of the "copy" algorithm. + *) If in-place ALTER TABLE is theoretically possible, we continue + by compiling differences between old and new versions of the table + in the form of HA_ALTER_FLAGS bitmap. We also build a few + auxiliary structures describing requested changes and store + all these data in the Alter_inplace_info object. + *) Then the handler::check_if_supported_inplace_alter() method is called + in order to find if the storage engine can carry out changes requested + by this ALTER TABLE using the in-place algorithm. To determine this, + the engine can rely on data in HA_ALTER_FLAGS/Alter_inplace_info + passed to it as well as on its own checks. If the in-place algorithm + can be used for this ALTER TABLE, the level of required concurrency for + its execution is also returned. + If any errors occur during the handler call, ALTER TABLE is aborted + and no further handler functions are called. + *) Locking requirements of the in-place algorithm are compared to any + concurrency requirements specified by user. If there is a conflict + between them, we either switch to the copy algorithm or emit an error. + + Phase 2 : Execution + =================== + + In this phase the operations are executed. + + *) As the first step, we acquire a lock corresponding to the concurrency + level which was returned by handler::check_if_supported_inplace_alter() + and requested by the user. This lock is held for most of the + duration of in-place ALTER (if HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE + or HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE were returned we acquire an + exclusive lock for duration of the next step only). + *) After that we call handler::ha_prepare_inplace_alter_table() to give the + storage engine a chance to update its internal structures with a higher + lock level than the one that will be used for the main step of algorithm. + After that we downgrade the lock if it is necessary. + *) After that, the main step of this phase and algorithm is executed. + We call the handler::ha_inplace_alter_table() method, which carries out the + changes requested by ALTER TABLE but does not makes them visible to other + connections yet. + *) We ensure that no other connection uses the table by upgrading our + lock on it to exclusive. + *) a) If the previous step succeeds, handler::ha_commit_inplace_alter_table() is + called to allow the storage engine to do any final updates to its structures, + to make all earlier changes durable and visible to other connections. + b) If we have failed to upgrade lock or any errors have occurred during the + handler functions calls (including commit), we call + handler::ha_commit_inplace_alter_table() + to rollback all changes which were done during previous steps. + + Phase 3 : Final + =============== + + In this phase we: + + *) Update SQL-layer data-dictionary by installing .FRM file for the new version + of the table. + *) Inform the storage engine about this change by calling the + handler::ha_notify_table_changed() method. + *) Destroy the Alter_inplace_info and handler_ctx objects. + + */ + + /** + Check if a storage engine supports a particular alter table in-place + + @param altered_table TABLE object for new version of table. + @param ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used + during in-place alter. + + @retval HA_ALTER_ERROR Unexpected error. + @retval HA_ALTER_INPLACE_NOT_SUPPORTED Not supported, must use copy. + @retval HA_ALTER_INPLACE_EXCLUSIVE_LOCK Supported, but requires X lock. + @retval HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE + Supported, but requires SNW lock + during main phase. Prepare phase + requires X lock. + @retval HA_ALTER_INPLACE_SHARED_LOCK Supported, but requires SNW lock. + @retval HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE + Supported, concurrent reads/writes + allowed. However, prepare phase + requires X lock. + @retval HA_ALTER_INPLACE_NO_LOCK Supported, concurrent + reads/writes allowed. + + @note The default implementation uses the old in-place ALTER API + to determine if the storage engine supports in-place ALTER or not. + + @note Called without holding thr_lock.c lock. + */ + virtual enum_alter_inplace_result + check_if_supported_inplace_alter(TABLE *altered_table, + Alter_inplace_info *ha_alter_info); + + + /** + Public functions wrapping the actual handler call. + @see prepare_inplace_alter_table() + */ + bool ha_prepare_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info); + + + /** + Public function wrapping the actual handler call. + @see inplace_alter_table() + */ + bool ha_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info) + { + return inplace_alter_table(altered_table, ha_alter_info); + } + + + /** + Public function wrapping the actual handler call. + Allows us to enforce asserts regardless of handler implementation. + @see commit_inplace_alter_table() + */ + bool ha_commit_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info, + bool commit); + + + /** + Public function wrapping the actual handler call. + @see notify_table_changed() + */ + void ha_notify_table_changed() + { + notify_table_changed(); + } + + +protected: + /** + Allows the storage engine to update internal structures with concurrent + writes blocked. If check_if_supported_inplace_alter() returns + HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE or + HA_ALTER_INPLACE_SHARED_AFTER_PREPARE, this function is called with + exclusive lock otherwise the same level of locking as for + inplace_alter_table() will be used. + + @note Storage engines are responsible for reporting any errors by + calling my_error()/print_error() + + @note If this function reports error, commit_inplace_alter_table() + will be called with commit= false. + + @note For partitioning, failing to prepare one partition, means that + commit_inplace_alter_table() will be called to roll back changes for + all partitions. This means that commit_inplace_alter_table() might be + called without prepare_inplace_alter_table() having been called first + for a given partition. + + @param altered_table TABLE object for new version of table. + @param ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used + during in-place alter. + + @retval true Error + @retval false Success + */ + virtual bool prepare_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info) + { return false; } + + + /** + Alter the table structure in-place with operations specified using HA_ALTER_FLAGS + and Alter_inplace_info. The level of concurrency allowed during this + operation depends on the return value from check_if_supported_inplace_alter(). + + @note Storage engines are responsible for reporting any errors by + calling my_error()/print_error() + + @note If this function reports error, commit_inplace_alter_table() + will be called with commit= false. + + @param altered_table TABLE object for new version of table. + @param ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used + during in-place alter. + + @retval true Error + @retval false Success + */ + virtual bool inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info) + { return false; } + + + /** + Commit or rollback the changes made during prepare_inplace_alter_table() + and inplace_alter_table() inside the storage engine. + Note that in case of rollback the allowed level of concurrency during + this operation will be the same as for inplace_alter_table() and thus + might be higher than during prepare_inplace_alter_table(). (For example, + concurrent writes were blocked during prepare, but might not be during + rollback). + + @note Storage engines are responsible for reporting any errors by + calling my_error()/print_error() + + @note If this function with commit= true reports error, it will be called + again with commit= false. + + @note In case of partitioning, this function might be called for rollback + without prepare_inplace_alter_table() having been called first. + Also partitioned tables sets ha_alter_info->group_commit_ctx to a NULL + terminated array of the partitions handlers and if all of them are + committed as one, then group_commit_ctx should be set to NULL to indicate + to the partitioning handler that all partitions handlers are committed. + @see prepare_inplace_alter_table(). + + @param altered_table TABLE object for new version of table. + @param ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used + during in-place alter. + @param commit True => Commit, False => Rollback. + + @retval true Error + @retval false Success + */ + virtual bool commit_inplace_alter_table(TABLE *altered_table, + Alter_inplace_info *ha_alter_info, + bool commit) +{ + /* Nothing to commit/rollback, mark all handlers committed! */ + ha_alter_info->group_commit_ctx= NULL; + return false; +} + + + /** + Notify the storage engine that the table structure (.FRM) has been updated. + + @note No errors are allowed during notify_table_changed(). + */ + virtual void notify_table_changed() { } + +public: + /* End of On-line/in-place ALTER TABLE interface. */ + + /** use_hidden_primary_key() is called in case of an update/delete when (table_flags() and HA_PRIMARY_KEY_REQUIRED_FOR_DELETE) is defined @@ -2727,36 +3910,9 @@ protected: /** Acquire the instrumented table information from a table share. - @param share a table share @return an instrumented table share, or NULL. */ - PSI_table_share *ha_table_share_psi(const TABLE_SHARE *share) const; - - inline void psi_open() - { - DBUG_ASSERT(m_psi == NULL); - DBUG_ASSERT(table_share != NULL); -#ifdef HAVE_PSI_INTERFACE - if (PSI_server) - { - PSI_table_share *share_psi= ha_table_share_psi(table_share); - if (share_psi) - m_psi= PSI_server->open_table(share_psi, this); - } -#endif - } - - inline void psi_close() - { -#ifdef HAVE_PSI_INTERFACE - if (PSI_server && m_psi) - { - PSI_server->close_table(m_psi); - m_psi= NULL; /* instrumentation handle, invalid after close_table() */ - } -#endif - DBUG_ASSERT(m_psi == NULL); - } + PSI_table_share *ha_table_share_psi() const; /** Default rename_table() and delete_table() rename/delete files with a @@ -2803,6 +3959,14 @@ private: return HA_ERR_WRONG_COMMAND; } + /** + Update a single row. + + Note: If HA_ERR_FOUND_DUPP_KEY is returned, the handler must read + all columns of the row so MySQL can create an error message. If + the columns required for the error message are not read, the error + message will contain garbage. + */ virtual int update_row(const uchar *old_data __attribute__((unused)), uchar *new_data __attribute__((unused))) { @@ -2864,11 +4028,14 @@ private: DBUG_ASSERT(!(ha_table_flags() & HA_CAN_REPAIR)); return HA_ADMIN_NOT_IMPLEMENTED; } - virtual void start_bulk_insert(ha_rows rows) {} + virtual void start_bulk_insert(ha_rows rows, uint flags) {} virtual int end_bulk_insert() { return 0; } +protected: virtual int index_read(uchar * buf, const uchar * key, uint key_len, enum ha_rkey_function find_flag) { return HA_ERR_WRONG_COMMAND; } + friend class ha_partition; +public: /** This method is similar to update_row, however the handler doesn't need to execute the updates at this point in time. The handler can be certain @@ -2938,8 +4105,8 @@ private: virtual void drop_table(const char *name); virtual int create(const char *name, TABLE *form, HA_CREATE_INFO *info)=0; - virtual int create_handler_files(const char *name, const char *old_name, - int action_flag, HA_CREATE_INFO *info) + virtual int create_partitioning_metadata(const char *name, const char *old_name, + int action_flag) { return FALSE; } virtual int change_partitions(HA_CREATE_INFO *create_info, @@ -2953,7 +4120,16 @@ private: { return HA_ERR_WRONG_COMMAND; } virtual int rename_partitions(const char *path) { return HA_ERR_WRONG_COMMAND; } - friend class ha_partition; + virtual bool set_ha_share_ref(Handler_share **arg_ha_share) + { + DBUG_ASSERT(!ha_share); + DBUG_ASSERT(arg_ha_share); + if (ha_share || !arg_ha_share) + return true; + ha_share= arg_ha_share; + return false; + } + int get_lock_type() const { return m_lock_type; } public: /* XXX to be removed, see ha_partition::partition_ht() */ virtual handlerton *partition_ht() const @@ -2964,9 +4140,15 @@ public: virtual void set_lock_type(enum thr_lock_type lock); friend enum icp_result handler_index_cond_check(void* h_arg); +protected: + Handler_share *get_ha_share_ptr(); + void set_ha_share_ptr(Handler_share *arg_ha_share); + void lock_shared_ha_data(); + void unlock_shared_ha_data(); }; #include "multi_range_read.h" +#include "group_by_handler.h" bool key_uses_partial_cols(TABLE_SHARE *table, uint keyno); @@ -2980,15 +4162,18 @@ extern const char *myisam_stats_method_names[]; extern ulong total_ha, total_ha_2pc; /* lookups */ -handlerton *ha_default_handlerton(THD *thd); -plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name); +plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name, bool tmp_table); plugin_ref ha_lock_engine(THD *thd, const handlerton *hton); handlerton *ha_resolve_by_legacy_type(THD *thd, enum legacy_db_type db_type); handler *get_new_handler(TABLE_SHARE *share, MEM_ROOT *alloc, handlerton *db_type); -handlerton *ha_checktype(THD *thd, enum legacy_db_type database_type, - bool no_substitute, bool report_error); +handlerton *ha_checktype(THD *thd, handlerton *hton, bool no_substitute); +static inline handlerton *ha_checktype(THD *thd, enum legacy_db_type type, + bool no_substitute = 0) +{ + return ha_checktype(thd, ha_resolve_by_legacy_type(thd, type), no_substitute); +} static inline enum legacy_db_type ha_legacy_type(const handlerton *db_type) { @@ -3002,7 +4187,7 @@ static inline const char *ha_resolve_storage_engine_name(const handlerton *db_ty static inline bool ha_check_storage_engine_flag(const handlerton *db_type, uint32 flag) { - return db_type == NULL ? FALSE : test(db_type->flags & flag); + return db_type == NULL ? FALSE : MY_TEST(db_type->flags & flag); } static inline bool ha_storage_engine_is_enabled(const handlerton *db_type) @@ -3011,6 +4196,8 @@ static inline bool ha_storage_engine_is_enabled(const handlerton *db_type) (db_type->state == SHOW_OPTION_YES) : FALSE; } +#define view_pseudo_hton ((handlerton *)1) + /* basic stuff */ int ha_init_errors(void); int ha_init(void); @@ -3025,10 +4212,10 @@ void ha_kill_query(THD* thd, enum thd_kill_levels level); bool ha_flush_logs(handlerton *db_type); void ha_drop_database(char* path); void ha_checkpoint_state(bool disable); +void ha_commit_checkpoint_request(void *cookie, void (*pre_hook)(void *)); int ha_create_table(THD *thd, const char *path, const char *db, const char *table_name, - HA_CREATE_INFO *create_info, - bool update_create_info); + HA_CREATE_INFO *create_info, LEX_CUSTRING *frm); int ha_delete_table(THD *thd, handlerton *db_type, const char *path, const char *db, const char *alias, bool generate_warning); @@ -3036,14 +4223,34 @@ int ha_delete_table(THD *thd, handlerton *db_type, const char *path, bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat); /* discovery */ -int ha_create_table_from_engine(THD* thd, const char *db, const char *name); -bool ha_check_if_table_exists(THD* thd, const char *db, const char *name, - bool *exists); -int ha_discover(THD* thd, const char* dbname, const char* name, - uchar** frmblob, size_t* frmlen); -int ha_find_files(THD *thd,const char *db,const char *path, - const char *wild, bool dir, List<LEX_STRING>* files); -int ha_table_exists_in_engine(THD* thd, const char* db, const char* name); +#ifdef MYSQL_SERVER +class Discovered_table_list: public handlerton::discovered_list +{ + THD *thd; + const char *wild, *wend; + bool with_temps; // whether to include temp tables in the result +public: + Dynamic_array<LEX_STRING*> *tables; + + Discovered_table_list(THD *thd_arg, Dynamic_array<LEX_STRING*> *tables_arg, + const LEX_STRING *wild_arg); + Discovered_table_list(THD *thd_arg, Dynamic_array<LEX_STRING*> *tables_arg) + : thd(thd_arg), wild(NULL), with_temps(true), tables(tables_arg) {} + ~Discovered_table_list() {} + + bool add_table(const char *tname, size_t tlen); + bool add_file(const char *fname); + + void sort(); + void remove_duplicates(); // assumes that the list is sorted +}; + +int ha_discover_table(THD *thd, TABLE_SHARE *share); +int ha_discover_table_names(THD *thd, LEX_STRING *db, MY_DIR *dirp, + Discovered_table_list *result, bool reusable); +bool ha_table_exists(THD *thd, const char *db, const char *table_name, + handlerton **hton= 0); +#endif /* key cache */ extern "C" int ha_init_key_cache(const char *name, KEY_CACHE *key_cache, void *); @@ -3069,8 +4276,15 @@ int ha_enable_transaction(THD *thd, bool on); /* savepoints */ int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv); +bool ha_rollback_to_savepoint_can_release_mdl(THD *thd); int ha_savepoint(THD *thd, SAVEPOINT *sv); int ha_release_savepoint(THD *thd, SAVEPOINT *sv); +#ifdef WITH_WSREP +int ha_abort_transaction(THD *bf_thd, THD *victim_thd, my_bool signal); +void ha_fake_trx_id(THD *thd); +#else +inline void ha_fake_trx_id(THD *thd) { } +#endif /* these are called by storage engines */ void trans_register_ha(THD *thd, bool all, handlerton *ht); @@ -3083,32 +4297,32 @@ void trans_register_ha(THD *thd, bool all, handlerton *ht); #define trans_need_2pc(thd, all) ((total_ha_2pc > 1) && \ !((all ? &thd->transaction.all : &thd->transaction.stmt)->no_2pc)) -#ifdef HAVE_NDB_BINLOG -int ha_reset_logs(THD *thd); -int ha_binlog_index_purge_file(THD *thd, const char *file); -void ha_reset_slave(THD *thd); -void ha_binlog_log_query(THD *thd, handlerton *db_type, - enum_binlog_command binlog_command, - const char *query, uint query_length, - const char *db, const char *table_name); -void ha_binlog_wait(THD *thd); -int ha_binlog_end(THD *thd); -#else -#define ha_reset_logs(a) do {} while (0) -#define ha_binlog_index_purge_file(a,b) do {} while (0) -#define ha_reset_slave(a) do {} while (0) -#define ha_binlog_log_query(a,b,c,d,e,f,g) do {} while (0) -#define ha_binlog_wait(a) do {} while (0) -#define ha_binlog_end(a) do {} while (0) -#endif - const char *get_canonical_filename(handler *file, const char *path, char *tmp_path); bool mysql_xa_recover(THD *thd); +void commit_checkpoint_notify_ha(handlerton *hton, void *cookie); inline const char *table_case_name(HA_CREATE_INFO *info, const char *name) { return ((lower_case_table_names == 2 && info->alias) ? info->alias : name); } + +#define TABLE_IO_WAIT(TRACKER, PSI, OP, INDEX, FLAGS, PAYLOAD) \ + { \ + Exec_time_tracker *this_tracker; \ + if (unlikely((this_tracker= tracker))) \ + tracker->start_tracking(); \ + \ + MYSQL_TABLE_IO_WAIT(PSI, OP, INDEX, FLAGS, PAYLOAD); \ + \ + if (unlikely(this_tracker)) \ + tracker->stop_tracking(); \ + } + +void print_keydup_error(TABLE *table, KEY *key, const char *msg, myf errflag); +void print_keydup_error(TABLE *table, KEY *key, myf errflag); + +int del_global_index_stat(THD *thd, TABLE* table, KEY* key_info); +int del_global_table_stat(THD *thd, LEX_STRING *db, LEX_STRING *table); #endif /* HANDLER_INCLUDED */ diff --git a/sql/hash_filo.cc b/sql/hash_filo.cc index 7c275ffc617..fc89bb83a9d 100644 --- a/sql/hash_filo.cc +++ b/sql/hash_filo.cc @@ -23,6 +23,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include "hash_filo.h" diff --git a/sql/hash_filo.h b/sql/hash_filo.h index dab54928a55..4c8c7575efc 100644 --- a/sql/hash_filo.h +++ b/sql/hash_filo.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2011, 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 @@ -32,17 +32,26 @@ class hash_filo_element { +private: hash_filo_element *next_used,*prev_used; public: hash_filo_element() {} + hash_filo_element *next() + { return next_used; } + hash_filo_element *prev() + { return prev_used; } + friend class hash_filo; }; class hash_filo { - const uint size, key_offset, key_length; +private: + const uint key_offset, key_length; const my_hash_get_key get_key; + /** Size of this hash table. */ + uint m_size; my_hash_free_key free_element; bool init; CHARSET_INFO *hash_charset; @@ -55,9 +64,12 @@ public: hash_filo(uint size_arg, uint key_offset_arg , uint key_length_arg, my_hash_get_key get_key_arg, my_hash_free_key free_element_arg, CHARSET_INFO *hash_charset_arg) - :size(size_arg), key_offset(key_offset_arg), key_length(key_length_arg), - get_key(get_key_arg), free_element(free_element_arg),init(0), - hash_charset(hash_charset_arg) + :key_offset(key_offset_arg), key_length(key_length_arg), + get_key(get_key_arg), m_size(size_arg), + free_element(free_element_arg),init(0), + hash_charset(hash_charset_arg), + first_link(NULL), + last_link(NULL) { bzero((char*) &cache,sizeof(cache)); } @@ -80,32 +92,61 @@ public: } if (!locked) mysql_mutex_lock(&lock); + first_link= NULL; + last_link= NULL; (void) my_hash_free(&cache); - (void) my_hash_init(&cache,hash_charset,size,key_offset, + (void) my_hash_init(&cache,hash_charset,m_size,key_offset, key_length, get_key, free_element,0); if (!locked) mysql_mutex_unlock(&lock); - first_link=last_link=0; + } + + hash_filo_element *first() + { + mysql_mutex_assert_owner(&lock); + return first_link; + } + + hash_filo_element *last() + { + mysql_mutex_assert_owner(&lock); + return last_link; } hash_filo_element *search(uchar* key, size_t length) { + mysql_mutex_assert_owner(&lock); + hash_filo_element *entry=(hash_filo_element*) my_hash_search(&cache,(uchar*) key,length); if (entry) { // Found; link it first + DBUG_ASSERT(first_link != NULL); + DBUG_ASSERT(last_link != NULL); if (entry != first_link) { // Relink used-chain if (entry == last_link) - last_link=entry->prev_used; + { + last_link= last_link->prev_used; + /* + The list must have at least 2 elements, + otherwise entry would be equal to first_link. + */ + DBUG_ASSERT(last_link != NULL); + last_link->next_used= NULL; + } else { + DBUG_ASSERT(entry->next_used != NULL); + DBUG_ASSERT(entry->prev_used != NULL); entry->next_used->prev_used = entry->prev_used; entry->prev_used->next_used = entry->next_used; } - if ((entry->next_used= first_link)) - first_link->prev_used=entry; - first_link=entry; + entry->prev_used= NULL; + entry->next_used= first_link; + + first_link->prev_used= entry; + first_link=entry; } } return entry; @@ -113,10 +154,20 @@ public: bool add(hash_filo_element *entry) { - if (cache.records == size) + if (!m_size) return 1; + if (cache.records == m_size) { hash_filo_element *tmp=last_link; - last_link=last_link->prev_used; + last_link= last_link->prev_used; + if (last_link != NULL) + { + last_link->next_used= NULL; + } + else + { + /* Pathological case, m_size == 1 */ + first_link= NULL; + } my_hash_delete(&cache,(uchar*) tmp); } if (my_hash_insert(&cache,(uchar*) entry)) @@ -125,13 +176,40 @@ public: (*free_element)(entry); // This should never happen return 1; } - if ((entry->next_used=first_link)) - first_link->prev_used=entry; + entry->prev_used= NULL; + entry->next_used= first_link; + if (first_link != NULL) + first_link->prev_used= entry; else - last_link=entry; - first_link=entry; + last_link= entry; + first_link= entry; + return 0; } + + uint size() + { return m_size; } + + void resize(uint new_size) + { + mysql_mutex_lock(&lock); + m_size= new_size; + clear(true); + mysql_mutex_unlock(&lock); + } +}; + +template <class T> class Hash_filo: public hash_filo +{ +public: + Hash_filo(uint size_arg, uint key_offset_arg, uint key_length_arg, + my_hash_get_key get_key_arg, my_hash_free_key free_element_arg, + CHARSET_INFO *hash_charset_arg) : + hash_filo(size_arg, key_offset_arg, key_length_arg, + get_key_arg, free_element_arg, hash_charset_arg) {} + T* first() { return (T*)hash_filo::first(); } + T* last() { return (T*)hash_filo::last(); } + T* search(uchar* key, size_t len) { return (T*)hash_filo::search(key, len); } }; #endif diff --git a/sql/hostname.cc b/sql/hostname.cc index f9a6f4b3fcf..39e4b34d615 100644 --- a/sql/hostname.cc +++ b/sql/hostname.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2011, 2014, SkySQL Ab. This program is free software; you can redistribute it and/or modify @@ -24,10 +24,10 @@ Hostnames are checked with reverse name lookup and checked that they doesn't resemble an IP address. */ - +#include <my_global.h> #include "sql_priv.h" +#include "unireg.h" // SPECIAL_NO_HOST_CACHE #include "hostname.h" -#include "my_global.h" #ifndef __WIN__ #include <netdb.h> // getservbyname, servent #endif @@ -50,60 +50,107 @@ extern "C" { // Because of SCO 3.2V4.2 } #endif -/* - HOST_ENTRY_KEY_SIZE -- size of IP address string in the hash cache. -*/ - -#define HOST_ENTRY_KEY_SIZE INET6_ADDRSTRLEN - -/** - An entry in the hostname hash table cache. - - Host name cache does two things: - - caches host names to save DNS look ups; - - counts connect errors from IP. - - Host name can be NULL (that means DNS look up failed), but connect errors - still are counted. -*/ - -class Host_entry :public hash_filo_element +Host_errors::Host_errors() +: m_connect(0), + m_host_blocked(0), + m_nameinfo_transient(0), + m_nameinfo_permanent(0), + m_format(0), + m_addrinfo_transient(0), + m_addrinfo_permanent(0), + m_FCrDNS(0), + m_host_acl(0), + m_no_auth_plugin(0), + m_auth_plugin(0), + m_handshake(0), + m_proxy_user(0), + m_proxy_user_acl(0), + m_authentication(0), + m_ssl(0), + m_max_user_connection(0), + m_max_user_connection_per_hour(0), + m_default_database(0), + m_init_connect(0), + m_local(0) +{} + +Host_errors::~Host_errors() +{} + +void Host_errors::reset() { -public: - /** - Client IP address. This is the key used with the hash table. - - The client IP address is always expressed in IPv6, even when the - network IPv6 stack is not present. - - This IP address is never used to connect to a socket. - */ - char ip_key[HOST_ENTRY_KEY_SIZE]; - - /** - Number of errors during handshake phase from the IP address. - */ - uint connect_errors; + m_connect= 0; + m_host_blocked= 0; + m_nameinfo_transient= 0; + m_nameinfo_permanent= 0; + m_format= 0; + m_addrinfo_transient= 0; + m_addrinfo_permanent= 0; + m_FCrDNS= 0; + m_host_acl= 0; + m_no_auth_plugin= 0; + m_auth_plugin= 0; + m_handshake= 0; + m_proxy_user= 0; + m_proxy_user_acl= 0; + m_authentication= 0; + m_ssl= 0; + m_max_user_connection= 0; + m_max_user_connection_per_hour= 0; + m_default_database= 0; + m_init_connect= 0; + m_local= 0; +} - /** - One of the host names for the IP address. May be NULL. - */ - const char *hostname; -}; +void Host_errors::aggregate(const Host_errors *errors) +{ + m_connect+= errors->m_connect; + m_host_blocked+= errors->m_host_blocked; + m_nameinfo_transient+= errors->m_nameinfo_transient; + m_nameinfo_permanent+= errors->m_nameinfo_permanent; + m_format+= errors->m_format; + m_addrinfo_transient+= errors->m_addrinfo_transient; + m_addrinfo_permanent+= errors->m_addrinfo_permanent; + m_FCrDNS+= errors->m_FCrDNS; + m_host_acl+= errors->m_host_acl; + m_no_auth_plugin+= errors->m_no_auth_plugin; + m_auth_plugin+= errors->m_auth_plugin; + m_handshake+= errors->m_handshake; + m_proxy_user+= errors->m_proxy_user; + m_proxy_user_acl+= errors->m_proxy_user_acl; + m_authentication+= errors->m_authentication; + m_ssl+= errors->m_ssl; + m_max_user_connection+= errors->m_max_user_connection; + m_max_user_connection_per_hour+= errors->m_max_user_connection_per_hour; + m_default_database+= errors->m_default_database; + m_init_connect+= errors->m_init_connect; + m_local+= errors->m_local; +} -static hash_filo *hostname_cache; +static Hash_filo<Host_entry> *hostname_cache; +ulong host_cache_size; void hostname_cache_refresh() { hostname_cache->clear(); } +uint hostname_cache_size() +{ + return hostname_cache->size(); +} + +void hostname_cache_resize(uint size) +{ + hostname_cache->resize(size); +} + bool hostname_cache_init() { Host_entry tmp; uint key_offset= (uint) ((char*) (&tmp.ip_key) - (char*) &tmp); - if (!(hostname_cache= new hash_filo(HOST_CACHE_SIZE, + if (!(hostname_cache= new Hash_filo<Host_entry>(host_cache_size, key_offset, HOST_ENTRY_KEY_SIZE, NULL, (my_hash_free_key) free, &my_charset_bin))) @@ -120,6 +167,16 @@ void hostname_cache_free() hostname_cache= NULL; } +void hostname_cache_lock() +{ + mysql_mutex_lock(&hostname_cache->lock); +} + +void hostname_cache_unlock() +{ + mysql_mutex_unlock(&hostname_cache->lock); +} + static void prepare_hostname_cache_key(const char *ip_string, char *ip_key) { @@ -130,69 +187,119 @@ static void prepare_hostname_cache_key(const char *ip_string, memcpy(ip_key, ip_string, ip_string_length); } +Host_entry *hostname_cache_first() +{ return hostname_cache->first(); } + static inline Host_entry *hostname_cache_search(const char *ip_key) { - return (Host_entry *) hostname_cache->search((uchar *) ip_key, 0); + return hostname_cache->search((uchar *) ip_key, 0); } -static bool add_hostname_impl(const char *ip_key, const char *hostname) +static void add_hostname_impl(const char *ip_key, const char *hostname, + bool validated, Host_errors *errors, + ulonglong now) { - if (hostname_cache_search(ip_key)) - return FALSE; - - size_t hostname_size= hostname ? strlen(hostname) + 1 : 0; - - Host_entry *entry= (Host_entry *) malloc(sizeof (Host_entry) + hostname_size); - - if (!entry) - return TRUE; - - char *hostname_copy; + Host_entry *entry; + bool need_add= false; - memcpy(&entry->ip_key, ip_key, HOST_ENTRY_KEY_SIZE); + entry= hostname_cache_search(ip_key); - if (hostname_size) + if (likely(entry == NULL)) { - hostname_copy= (char *) (entry + 1); - memcpy(hostname_copy, hostname, hostname_size); - - DBUG_PRINT("info", ("Adding '%s' -> '%s' to the hostname cache...'", - (const char *) ip_key, - (const char *) hostname_copy)); + entry= (Host_entry *) malloc(sizeof (Host_entry)); + if (entry == NULL) + return; + + need_add= true; + memcpy(&entry->ip_key, ip_key, HOST_ENTRY_KEY_SIZE); + entry->m_errors.reset(); + entry->m_hostname_length= 0; + entry->m_host_validated= false; + entry->m_first_seen= now; + entry->m_last_seen= now; + entry->m_first_error_seen= 0; + entry->m_last_error_seen= 0; } else { - hostname_copy= NULL; + entry->m_last_seen= now; + } - DBUG_PRINT("info", ("Adding '%s' -> NULL to the hostname cache...'", - (const char *) ip_key)); + if (validated) + { + if (hostname != NULL) + { + uint len= strlen(hostname); + if (len > sizeof(entry->m_hostname) - 1) + len= sizeof(entry->m_hostname) - 1; + memcpy(entry->m_hostname, hostname, len); + entry->m_hostname[len]= '\0'; + entry->m_hostname_length= len; + + DBUG_PRINT("info", + ("Adding/Updating '%s' -> '%s' (validated) to the hostname cache...'", + (const char *) ip_key, + (const char *) entry->m_hostname)); + } + else + { + entry->m_hostname_length= 0; + DBUG_PRINT("info", + ("Adding/Updating '%s' -> NULL (validated) to the hostname cache...'", + (const char *) ip_key)); + } + entry->m_host_validated= true; + /* + New errors that are considered 'blocking', + that will eventually cause the IP to be black listed and blocked. + */ + errors->sum_connect_errors(); + } + else + { + entry->m_hostname_length= 0; + entry->m_host_validated= false; + /* Do not count new blocking errors during DNS failures. */ + errors->clear_connect_errors(); + DBUG_PRINT("info", + ("Adding/Updating '%s' -> NULL (not validated) to the hostname cache...'", + (const char *) ip_key)); } - entry->hostname= hostname_copy; - entry->connect_errors= 0; + if (errors->has_error()) + entry->set_error_timestamps(now); + + entry->m_errors.aggregate(errors); - return hostname_cache->add(entry); + if (need_add) + hostname_cache->add(entry); + + return; } -static bool add_hostname(const char *ip_key, const char *hostname) +static void add_hostname(const char *ip_key, const char *hostname, + bool validated, Host_errors *errors) { if (specialflag & SPECIAL_NO_HOST_CACHE) - return FALSE; + return; + + ulonglong now= my_hrtime().val; mysql_mutex_lock(&hostname_cache->lock); - bool err_status= add_hostname_impl(ip_key, hostname); + add_hostname_impl(ip_key, hostname, validated, errors, now); mysql_mutex_unlock(&hostname_cache->lock); - return err_status; + return; } -void inc_host_errors(const char *ip_string) +void inc_host_errors(const char *ip_string, Host_errors *errors) { if (!ip_string) return; + ulonglong now= my_hrtime().val; char ip_key[HOST_ENTRY_KEY_SIZE]; prepare_hostname_cache_key(ip_string, ip_key); @@ -201,13 +308,20 @@ void inc_host_errors(const char *ip_string) Host_entry *entry= hostname_cache_search(ip_key); if (entry) - entry->connect_errors++; + { + if (entry->m_host_validated) + errors->sum_connect_errors(); + else + errors->clear_connect_errors(); + + entry->m_errors.aggregate(errors); + entry->set_error_timestamps(now); + } mysql_mutex_unlock(&hostname_cache->lock); } - -void reset_host_errors(const char *ip_string) +void reset_host_connect_errors(const char *ip_string) { if (!ip_string) return; @@ -220,12 +334,11 @@ void reset_host_errors(const char *ip_string) Host_entry *entry= hostname_cache_search(ip_key); if (entry) - entry->connect_errors= 0; + entry->m_errors.clear_connect_errors(); mysql_mutex_unlock(&hostname_cache->lock); } - static inline bool is_ip_loopback(const struct sockaddr *ip) { switch (ip->sa_family) { @@ -277,6 +390,7 @@ static inline bool is_hostname_valid(const char *hostname) - returns host name if IP-address is validated; - set value to out-variable connect_errors -- this variable represents the number of connection errors from the specified IP-address. + - update the host_cache statistics NOTE: connect_errors are counted (are supported) only for the clients where IP-address can be resolved and FCrDNS check is passed. @@ -287,37 +401,43 @@ static inline bool is_hostname_valid(const char *hostname) @param [out] connect_errors @return Error status - @retval FALSE Success - @retval TRUE Error + @retval 0 Success + @retval RC_BLOCKED_HOST The host is blocked. The function does not set/report MySQL server error in case of failure. It's caller's responsibility to handle failures of this function properly. */ -bool ip_to_hostname(struct sockaddr_storage *ip_storage, - const char *ip_string, - char **hostname, uint *connect_errors) +int ip_to_hostname(struct sockaddr_storage *ip_storage, + const char *ip_string, + char **hostname, + uint *connect_errors) { const struct sockaddr *ip= (const sockaddr *) ip_storage; int err_code; - bool err_status; + bool err_status __attribute__((unused)); + Host_errors errors; DBUG_ENTER("ip_to_hostname"); DBUG_PRINT("info", ("IP address: '%s'; family: %d.", (const char *) ip_string, (int) ip->sa_family)); + /* Default output values, for most cases. */ + *hostname= NULL; + *connect_errors= 0; + /* Check if we have loopback address (127.0.0.1 or ::1). */ if (is_ip_loopback(ip)) { DBUG_PRINT("info", ("Loopback address detected.")); - *connect_errors= 0; /* Do not count connect errors from localhost. */ + /* Do not count connect errors from localhost. */ *hostname= (char *) my_localhost; - DBUG_RETURN(FALSE); + DBUG_RETURN(0); } /* Prepare host name cache key. */ @@ -329,27 +449,45 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, if (!(specialflag & SPECIAL_NO_HOST_CACHE)) { + ulonglong now= my_hrtime().val; + mysql_mutex_lock(&hostname_cache->lock); Host_entry *entry= hostname_cache_search(ip_key); if (entry) { - *connect_errors= entry->connect_errors; - *hostname= NULL; + entry->m_last_seen= now; + *connect_errors= entry->m_errors.m_connect; + + if (entry->m_errors.m_connect >= max_connect_errors) + { + entry->m_errors.m_host_blocked++; + entry->set_error_timestamps(now); + mysql_mutex_unlock(&hostname_cache->lock); + DBUG_RETURN(RC_BLOCKED_HOST); + } - if (entry->hostname) - *hostname= my_strdup(entry->hostname, MYF(0)); + /* + If there is an IP -> HOSTNAME association in the cache, + but for a hostname that was not validated, + do not return that hostname: perform the network validation again. + */ + if (entry->m_host_validated) + { + if (entry->m_hostname_length) + *hostname= my_strdup(entry->m_hostname, MYF(0)); - DBUG_PRINT("info",("IP (%s) has been found in the cache. " - "Hostname: '%s'; connect_errors: %d", - (const char *) ip_key, - (const char *) (*hostname? *hostname : "null"), - (int) *connect_errors)); + DBUG_PRINT("info",("IP (%s) has been found in the cache. " + "Hostname: '%s'", + (const char *) ip_key, + (const char *) (*hostname? *hostname : "null") + )); - mysql_mutex_unlock(&hostname_cache->lock); + mysql_mutex_unlock(&hostname_cache->lock); - DBUG_RETURN(FALSE); + DBUG_RETURN(0); + } } mysql_mutex_unlock(&hostname_cache->lock); @@ -367,13 +505,60 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, err_code= vio_getnameinfo(ip, hostname_buffer, NI_MAXHOST, NULL, 0, NI_NAMEREQD); - /* BEGIN : DEBUG */ - DBUG_EXECUTE_IF("addr_fake_ipv4", + /* + =========================================================================== + DEBUG code only (begin) + Simulate various output from vio_getnameinfo(). + =========================================================================== + */ + + DBUG_EXECUTE_IF("getnameinfo_error_noname", + { + strcpy(hostname_buffer, "<garbage>"); + err_code= EAI_NONAME; + } + ); + + DBUG_EXECUTE_IF("getnameinfo_error_again", + { + strcpy(hostname_buffer, "<garbage>"); + err_code= EAI_AGAIN; + } + ); + + DBUG_EXECUTE_IF("getnameinfo_fake_ipv4", { strcpy(hostname_buffer, "santa.claus.ipv4.example.com"); err_code= 0; - };); - /* END : DEBUG */ + } + ); + + DBUG_EXECUTE_IF("getnameinfo_fake_ipv6", + { + strcpy(hostname_buffer, "santa.claus.ipv6.example.com"); + err_code= 0; + } + ); + + DBUG_EXECUTE_IF("getnameinfo_format_ipv4", + { + strcpy(hostname_buffer, "12.12.12.12"); + err_code= 0; + } + ); + + DBUG_EXECUTE_IF("getnameinfo_format_ipv6", + { + strcpy(hostname_buffer, "12:DEAD:BEEF:0"); + err_code= 0; + } + ); + + /* + =========================================================================== + DEBUG code only (end) + =========================================================================== + */ if (err_code) { @@ -387,23 +572,29 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, (const char *) ip_key, (const char *) gai_strerror(err_code)); + bool validated; if (vio_is_no_name_error(err_code)) { /* The no-name error means that there is no reverse address mapping for the IP address. A host name can not be resolved. - + */ + errors.m_nameinfo_permanent= 1; + validated= true; + } + else + { + /* If it is not the no-name error, we should not cache the hostname (or rather its absence), because the failure might be transient. + Only the ip error statistics are cached. */ - - add_hostname(ip_key, NULL); - - *hostname= NULL; - *connect_errors= 0; /* New IP added to the cache. */ + errors.m_nameinfo_transient= 1; + validated= false; } + add_hostname(ip_key, NULL, validated, &errors); - DBUG_RETURN(FALSE); + DBUG_RETURN(0); } DBUG_PRINT("info", ("IP '%s' resolved to '%s'.", @@ -439,24 +630,21 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, (const char *) ip_key, (const char *) hostname_buffer); - err_status= add_hostname(ip_key, NULL); - - *hostname= NULL; - *connect_errors= 0; /* New IP added to the cache. */ + errors.m_format= 1; + add_hostname(ip_key, hostname_buffer, false, &errors); - DBUG_RETURN(err_status); + DBUG_RETURN(false); } - /* - To avoid crashing the server in DBUG_EXECUTE_IF, - Define a variable which depicts state of addr_info_list. - */ - bool free_addr_info_list= false; - /* Get IP-addresses for the resolved host name (FCrDNS technique). */ struct addrinfo hints; struct addrinfo *addr_info_list; + /* + Makes fault injection with DBUG_EXECUTE_IF easier. + Invoking free_addr_info(NULL) crashes on some platforms. + */ + bool free_addr_info_list= false; memset(&hints, 0, sizeof (struct addrinfo)); hints.ai_flags= AI_PASSIVE; @@ -470,8 +658,72 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, if (err_code == 0) free_addr_info_list= true; - /* BEGIN : DEBUG */ - DBUG_EXECUTE_IF("addr_fake_ipv4", + /* + =========================================================================== + DEBUG code only (begin) + Simulate various output from getaddrinfo(). + =========================================================================== + */ + DBUG_EXECUTE_IF("getaddrinfo_error_noname", + { + if (free_addr_info_list) + freeaddrinfo(addr_info_list); + + addr_info_list= NULL; + err_code= EAI_NONAME; + free_addr_info_list= false; + } + ); + + DBUG_EXECUTE_IF("getaddrinfo_error_again", + { + if (free_addr_info_list) + freeaddrinfo(addr_info_list); + + addr_info_list= NULL; + err_code= EAI_AGAIN; + free_addr_info_list= false; + } + ); + + DBUG_EXECUTE_IF("getaddrinfo_fake_bad_ipv4", + { + if (free_addr_info_list) + freeaddrinfo(addr_info_list); + + struct sockaddr_in *debug_addr; + /* + Not thread safe, which is ok. + Only one connection at a time is tested with + fault injection. + */ + static struct sockaddr_in debug_sock_addr[2]; + static struct addrinfo debug_addr_info[2]; + /* Simulating ipv4 192.0.2.126 */ + debug_addr= & debug_sock_addr[0]; + debug_addr->sin_family= AF_INET; + debug_addr->sin_addr.s_addr= inet_addr("192.0.2.126"); + + /* Simulating ipv4 192.0.2.127 */ + debug_addr= & debug_sock_addr[1]; + debug_addr->sin_family= AF_INET; + debug_addr->sin_addr.s_addr= inet_addr("192.0.2.127"); + + debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0]; + debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in); + debug_addr_info[0].ai_next= & debug_addr_info[1]; + + debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1]; + debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in); + debug_addr_info[1].ai_next= NULL; + + addr_info_list= & debug_addr_info[0]; + err_code= 0; + free_addr_info_list= false; + } + ); + + DBUG_EXECUTE_IF("getaddrinfo_fake_good_ipv4", { if (free_addr_info_list) freeaddrinfo(addr_info_list); @@ -500,30 +752,186 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, addr_info_list= & debug_addr_info[0]; err_code= 0; free_addr_info_list= false; - };); + } + ); - /* END : DEBUG */ +#ifdef HAVE_IPV6 + DBUG_EXECUTE_IF("getaddrinfo_fake_bad_ipv6", + { + if (free_addr_info_list) + freeaddrinfo(addr_info_list); - if (err_code == EAI_NONAME) - { - /* - Don't cache responses when the DNS server is down, as otherwise - transient DNS failure may leave any number of clients (those - that attempted to connect during the outage) unable to connect - indefinitely. - */ + struct sockaddr_in6 *debug_addr; + struct in6_addr *ip6; + /* + Not thread safe, which is ok. + Only one connection at a time is tested with + fault injection. + */ + static struct sockaddr_in6 debug_sock_addr[2]; + static struct addrinfo debug_addr_info[2]; + /* Simulating ipv6 2001:DB8::6:7E */ + debug_addr= & debug_sock_addr[0]; + debug_addr->sin6_family= AF_INET6; + ip6= & debug_addr->sin6_addr; + /* inet_pton not available on Windows XP. */ + ip6->s6_addr[ 0] = 0x20; + ip6->s6_addr[ 1] = 0x01; + ip6->s6_addr[ 2] = 0x0d; + ip6->s6_addr[ 3] = 0xb8; + ip6->s6_addr[ 4] = 0x00; + ip6->s6_addr[ 5] = 0x00; + ip6->s6_addr[ 6] = 0x00; + ip6->s6_addr[ 7] = 0x00; + ip6->s6_addr[ 8] = 0x00; + ip6->s6_addr[ 9] = 0x00; + ip6->s6_addr[10] = 0x00; + ip6->s6_addr[11] = 0x00; + ip6->s6_addr[12] = 0x00; + ip6->s6_addr[13] = 0x06; + ip6->s6_addr[14] = 0x00; + ip6->s6_addr[15] = 0x7e; + + /* Simulating ipv6 2001:DB8::6:7F */ + debug_addr= & debug_sock_addr[1]; + debug_addr->sin6_family= AF_INET6; + ip6= & debug_addr->sin6_addr; + ip6->s6_addr[ 0] = 0x20; + ip6->s6_addr[ 1] = 0x01; + ip6->s6_addr[ 2] = 0x0d; + ip6->s6_addr[ 3] = 0xb8; + ip6->s6_addr[ 4] = 0x00; + ip6->s6_addr[ 5] = 0x00; + ip6->s6_addr[ 6] = 0x00; + ip6->s6_addr[ 7] = 0x00; + ip6->s6_addr[ 8] = 0x00; + ip6->s6_addr[ 9] = 0x00; + ip6->s6_addr[10] = 0x00; + ip6->s6_addr[11] = 0x00; + ip6->s6_addr[12] = 0x00; + ip6->s6_addr[13] = 0x06; + ip6->s6_addr[14] = 0x00; + ip6->s6_addr[15] = 0x7f; + + debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0]; + debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in6); + debug_addr_info[0].ai_next= & debug_addr_info[1]; - err_status= add_hostname(ip_key, NULL); + debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1]; + debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in6); + debug_addr_info[1].ai_next= NULL; - *hostname= NULL; - *connect_errors= 0; /* New IP added to the cache. */ + addr_info_list= & debug_addr_info[0]; + err_code= 0; + free_addr_info_list= false; + } + ); - DBUG_RETURN(err_status); - } - else if (err_code) + DBUG_EXECUTE_IF("getaddrinfo_fake_good_ipv6", + { + if (free_addr_info_list) + freeaddrinfo(addr_info_list); + + struct sockaddr_in6 *debug_addr; + struct in6_addr *ip6; + /* + Not thread safe, which is ok. + Only one connection at a time is tested with + fault injection. + */ + static struct sockaddr_in6 debug_sock_addr[2]; + static struct addrinfo debug_addr_info[2]; + /* Simulating ipv6 2001:DB8::6:7 */ + debug_addr= & debug_sock_addr[0]; + debug_addr->sin6_family= AF_INET6; + ip6= & debug_addr->sin6_addr; + ip6->s6_addr[ 0] = 0x20; + ip6->s6_addr[ 1] = 0x01; + ip6->s6_addr[ 2] = 0x0d; + ip6->s6_addr[ 3] = 0xb8; + ip6->s6_addr[ 4] = 0x00; + ip6->s6_addr[ 5] = 0x00; + ip6->s6_addr[ 6] = 0x00; + ip6->s6_addr[ 7] = 0x00; + ip6->s6_addr[ 8] = 0x00; + ip6->s6_addr[ 9] = 0x00; + ip6->s6_addr[10] = 0x00; + ip6->s6_addr[11] = 0x00; + ip6->s6_addr[12] = 0x00; + ip6->s6_addr[13] = 0x06; + ip6->s6_addr[14] = 0x00; + ip6->s6_addr[15] = 0x07; + + /* Simulating ipv6 2001:DB8::6:6 */ + debug_addr= & debug_sock_addr[1]; + debug_addr->sin6_family= AF_INET6; + ip6= & debug_addr->sin6_addr; + ip6->s6_addr[ 0] = 0x20; + ip6->s6_addr[ 1] = 0x01; + ip6->s6_addr[ 2] = 0x0d; + ip6->s6_addr[ 3] = 0xb8; + ip6->s6_addr[ 4] = 0x00; + ip6->s6_addr[ 5] = 0x00; + ip6->s6_addr[ 6] = 0x00; + ip6->s6_addr[ 7] = 0x00; + ip6->s6_addr[ 8] = 0x00; + ip6->s6_addr[ 9] = 0x00; + ip6->s6_addr[10] = 0x00; + ip6->s6_addr[11] = 0x00; + ip6->s6_addr[12] = 0x00; + ip6->s6_addr[13] = 0x06; + ip6->s6_addr[14] = 0x00; + ip6->s6_addr[15] = 0x06; + + debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0]; + debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in6); + debug_addr_info[0].ai_next= & debug_addr_info[1]; + + debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1]; + debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in6); + debug_addr_info[1].ai_next= NULL; + + addr_info_list= & debug_addr_info[0]; + err_code= 0; + free_addr_info_list= false; + } + ); +#endif /* HAVE_IPV6 */ + + /* + =========================================================================== + DEBUG code only (end) + =========================================================================== + */ + + if (err_code != 0) { - DBUG_PRINT("error", ("getaddrinfo() failed with error code %d.", err_code)); - DBUG_RETURN(TRUE); + sql_print_warning("Host name '%s' could not be resolved: %s", + (const char *) hostname_buffer, + (const char *) gai_strerror(err_code)); + + bool validated; + + if (err_code == EAI_NONAME) + { + errors.m_addrinfo_permanent= 1; + validated= true; + } + else + { + /* + Don't cache responses when the DNS server is down, as otherwise + transient DNS failure may leave any number of clients (those + that attempted to connect during the outage) unable to connect + indefinitely. + Only cache error statistics. + */ + errors.m_addrinfo_transient= 1; + validated= false; + } + add_hostname(ip_key, NULL, validated, &errors); + + DBUG_RETURN(false); } /* Check that getaddrinfo() returned the used IP (FCrDNS technique). */ @@ -545,7 +953,7 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, DBUG_PRINT("info", (" - '%s'", (const char *) ip_buffer)); - if (strcmp(ip_key, ip_buffer) == 0) + if (strcasecmp(ip_key, ip_buffer) == 0) { /* Copy host name string to be stored in the cache. */ @@ -557,7 +965,7 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, if (free_addr_info_list) freeaddrinfo(addr_info_list); - DBUG_RETURN(TRUE); + DBUG_RETURN(true); } break; @@ -568,9 +976,11 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, if (!*hostname) { - sql_print_information("Hostname '%s' does not resolve to '%s'.", - (const char *) hostname_buffer, - (const char *) ip_key); + errors.m_FCrDNS= 1; + + sql_print_warning("Hostname '%s' does not resolve to '%s'.", + (const char *) hostname_buffer, + (const char *) ip_key); sql_print_information("Hostname '%s' has the following IP addresses:", (const char *) hostname_buffer); @@ -584,30 +994,16 @@ bool ip_to_hostname(struct sockaddr_storage *ip_storage, ip_buffer, sizeof (ip_buffer)); DBUG_ASSERT(!err_status); - sql_print_information(" - %s\n", (const char *) ip_buffer); + sql_print_information(" - %s", (const char *) ip_buffer); } } - /* Free the result of getaddrinfo(). */ + /* Add an entry for the IP to the cache. */ + add_hostname(ip_key, *hostname, true, &errors); + /* Free the result of getaddrinfo(). */ if (free_addr_info_list) freeaddrinfo(addr_info_list); - /* Add an entry for the IP to the cache. */ - - if (*hostname) - { - err_status= add_hostname(ip_key, *hostname); - *connect_errors= 0; - } - else - { - DBUG_PRINT("error",("Couldn't verify hostname with getaddrinfo().")); - - err_status= add_hostname(ip_key, NULL); - *hostname= NULL; - *connect_errors= 0; - } - - DBUG_RETURN(err_status); + DBUG_RETURN(false); } diff --git a/sql/hostname.h b/sql/hostname.h index 6e9535c2947..81a1d0de88d 100644 --- a/sql/hostname.h +++ b/sql/hostname.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2006, 2011, 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 @@ -17,14 +17,168 @@ #define HOSTNAME_INCLUDED #include "my_global.h" /* uint */ +#include "my_net.h" +#include "hash_filo.h" -bool ip_to_hostname(struct sockaddr_storage *ip_storage, - const char *ip_string, - char **hostname, uint *connect_errors); -void inc_host_errors(const char *ip_string); -void reset_host_errors(const char *ip_string); +struct Host_errors +{ +public: + Host_errors(); + ~Host_errors(); + + void reset(); + void aggregate(const Host_errors *errors); + + /** Number of connect errors. */ + ulong m_connect; + + /** Number of host blocked errors. */ + ulong m_host_blocked; + /** Number of transient errors from getnameinfo(). */ + ulong m_nameinfo_transient; + /** Number of permanent errors from getnameinfo(). */ + ulong m_nameinfo_permanent; + /** Number of errors from is_hostname_valid(). */ + ulong m_format; + /** Number of transient errors from getaddrinfo(). */ + ulong m_addrinfo_transient; + /** Number of permanent errors from getaddrinfo(). */ + ulong m_addrinfo_permanent; + /** Number of errors from Forward-Confirmed reverse DNS checks. */ + ulong m_FCrDNS; + /** Number of errors from host grants. */ + ulong m_host_acl; + /** Number of errors from missing auth plugin. */ + ulong m_no_auth_plugin; + /** Number of errors from auth plugin. */ + ulong m_auth_plugin; + /** Number of errors from authentication plugins. */ + ulong m_handshake; + /** Number of errors from proxy user. */ + ulong m_proxy_user; + /** Number of errors from proxy user acl. */ + ulong m_proxy_user_acl; + /** Number of errors from authentication. */ + ulong m_authentication; + /** Number of errors from ssl. */ + ulong m_ssl; + /** Number of errors from max user connection. */ + ulong m_max_user_connection; + /** Number of errors from max user connection per hour. */ + ulong m_max_user_connection_per_hour; + /** Number of errors from the default database. */ + ulong m_default_database; + /** Number of errors from init_connect. */ + ulong m_init_connect; + /** Number of errors from the server itself. */ + ulong m_local; + + bool has_error() const + { + return ((m_host_blocked != 0) + || (m_nameinfo_transient != 0) + || (m_nameinfo_permanent != 0) + || (m_format != 0) + || (m_addrinfo_transient != 0) + || (m_addrinfo_permanent != 0) + || (m_FCrDNS != 0) + || (m_host_acl != 0) + || (m_no_auth_plugin != 0) + || (m_auth_plugin != 0) + || (m_handshake != 0) + || (m_proxy_user != 0) + || (m_proxy_user_acl != 0) + || (m_authentication != 0) + || (m_ssl != 0) + || (m_max_user_connection != 0) + || (m_max_user_connection_per_hour != 0) + || (m_default_database != 0) + || (m_init_connect != 0) + || (m_local != 0)); + } + + void sum_connect_errors() + { + /* Current (historical) behavior: */ + m_connect= m_handshake; + } + + void clear_connect_errors() + { + m_connect= 0; + } +}; + +/** Size of IP address string in the hash cache. */ +#define HOST_ENTRY_KEY_SIZE INET6_ADDRSTRLEN + +/** + An entry in the hostname hash table cache. + + Host name cache does two things: + - caches host names to save DNS look ups; + - counts errors from IP. + + Host name can be empty (that means DNS look up failed), + but errors still are counted. +*/ +class Host_entry : public hash_filo_element +{ +public: + Host_entry *next() + { return (Host_entry*) hash_filo_element::next(); } + + /** + Client IP address. This is the key used with the hash table. + + The client IP address is always expressed in IPv6, even when the + network IPv6 stack is not present. + + This IP address is never used to connect to a socket. + */ + char ip_key[HOST_ENTRY_KEY_SIZE]; + + /** + One of the host names for the IP address. May be a zero length string. + */ + char m_hostname[HOSTNAME_LENGTH + 1]; + /** Length in bytes of @c m_hostname. */ + uint m_hostname_length; + /** The hostname is validated and used for authorization. */ + bool m_host_validated; + ulonglong m_first_seen; + ulonglong m_last_seen; + ulonglong m_first_error_seen; + ulonglong m_last_error_seen; + /** Error statistics. */ + Host_errors m_errors; + + void set_error_timestamps(ulonglong now) + { + if (m_first_error_seen == 0) + m_first_error_seen= now; + m_last_error_seen= now; + } +}; + +/** The size of the host_cache. */ +extern ulong host_cache_size; + +#define RC_OK 0 +#define RC_BLOCKED_HOST 1 +int ip_to_hostname(struct sockaddr_storage *ip_storage, + const char *ip_string, + char **hostname, uint *connect_errors); + +void inc_host_errors(const char *ip_string, Host_errors *errors); +void reset_host_connect_errors(const char *ip_string); bool hostname_cache_init(); void hostname_cache_free(); void hostname_cache_refresh(void); +uint hostname_cache_size(); +void hostname_cache_resize(uint size); +void hostname_cache_lock(); +void hostname_cache_unlock(); +Host_entry *hostname_cache_first(); #endif /* HOSTNAME_INCLUDED */ diff --git a/sql/init.cc b/sql/init.cc index 86915b7aa01..8001e60b65e 100644 --- a/sql/init.cc +++ b/sql/init.cc @@ -21,6 +21,7 @@ Init and dummy functions for interface with unireg */ +#include <my_global.h> #include "sql_priv.h" #include "init.h" #include "my_sys.h" @@ -41,9 +42,6 @@ void unireg_init(ulong options) current_pid=(ulong) getpid(); /* Save for later ref */ my_init_time(); /* Init time-functions (read zone) */ -#ifndef EMBEDDED_LIBRARY - my_abort_hook=unireg_abort; /* Abort with close of databases */ -#endif (void) strmov(reg_ext,".frm"); reg_ext_length= 4; diff --git a/sql/innodb_priv.h b/sql/innodb_priv.h index 33ba7b0f5b3..27aa9ac8645 100644 --- a/sql/innodb_priv.h +++ b/sql/innodb_priv.h @@ -25,12 +25,12 @@ class THD; int get_quote_char_for_identifier(THD *thd, const char *name, uint length); bool schema_table_store_record(THD *thd, TABLE *table); void localtime_to_TIME(MYSQL_TIME *to, struct tm *from); -bool check_global_access(THD *thd, ulong want_access, bool no_errors=false); -uint strconvert(CHARSET_INFO *from_cs, const char *from, +uint strconvert(CHARSET_INFO *from_cs, const char *from, uint from_length, CHARSET_INFO *to_cs, char *to, uint to_length, uint *errors); -void sql_print_error(const char *format, ...); +void sql_print_error(const char *format, ...); +#define thd_binlog_pos(X, Y, Z) mysql_bin_log_commit_pos(X, Z, Y) #endif /* INNODB_PRIV_INCLUDED */ diff --git a/sql/item.cc b/sql/item.cc index d2e3b847f5c..3bfbdb75c40 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -19,9 +19,8 @@ #ifdef USE_PRAGMA_IMPLEMENTATION #pragma implementation // gcc: Class implementation #endif -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include <mysql.h> #include <m_ctype.h> #include "my_dir.h" @@ -41,13 +40,11 @@ // REPORT_EXCEPT_NOT_FOUND, // find_item_in_list, // RESOLVED_AGAINST_ALIAS, ... -#include "log_event.h" // append_query_string #include "sql_expression_cache.h" const String my_null_string("NULL", 4, default_charset_info); -static int save_field_in_field(Field *from, bool *null_value, - Field *to, bool no_conversions); +static int save_field_in_field(Field *, bool *, Field *, bool); /** @@ -59,133 +56,16 @@ bool cmp_items(Item *a, Item *b) return a->eq(b, FALSE); } -/****************************************************************************/ - -/* Hybrid_type_traits {_real} */ - -void Hybrid_type_traits::fix_length_and_dec(Item *item, Item *arg) const -{ - item->decimals= NOT_FIXED_DEC; - item->max_length= item->float_length(arg->decimals); -} - -static const Hybrid_type_traits real_traits_instance; - -const Hybrid_type_traits *Hybrid_type_traits::instance() -{ - return &real_traits_instance; -} - - -my_decimal * -Hybrid_type_traits::val_decimal(Hybrid_type *val, my_decimal *to) const -{ - double2my_decimal(E_DEC_FATAL_ERROR, val->real, val->dec_buf); - return val->dec_buf; -} - - -String * -Hybrid_type_traits::val_str(Hybrid_type *val, String *to, uint8 decimals) const -{ - to->set_real(val->real, decimals, &my_charset_bin); - return to; -} - -/* Hybrid_type_traits_decimal */ -static const Hybrid_type_traits_decimal decimal_traits_instance; - -const Hybrid_type_traits_decimal *Hybrid_type_traits_decimal::instance() -{ - return &decimal_traits_instance; -} - - -void -Hybrid_type_traits_decimal::fix_length_and_dec(Item *item, Item *arg) const -{ - item->decimals= arg->decimals; - item->max_length= min(arg->max_length + DECIMAL_LONGLONG_DIGITS, - DECIMAL_MAX_STR_LENGTH); -} - - -void Hybrid_type_traits_decimal::set_zero(Hybrid_type *val) const -{ - my_decimal_set_zero(&val->dec_buf[0]); - val->used_dec_buf_no= 0; -} - - -void Hybrid_type_traits_decimal::add(Hybrid_type *val, Field *f) const -{ - my_decimal_add(E_DEC_FATAL_ERROR, - &val->dec_buf[val->used_dec_buf_no ^ 1], - &val->dec_buf[val->used_dec_buf_no], - f->val_decimal(&val->dec_buf[2])); - val->used_dec_buf_no^= 1; -} - /** - @todo - what is '4' for scale? + Set max_sum_func_level if it is needed */ -void Hybrid_type_traits_decimal::div(Hybrid_type *val, ulonglong u) const -{ - int2my_decimal(E_DEC_FATAL_ERROR, u, TRUE, &val->dec_buf[2]); - /* XXX: what is '4' for scale? */ - my_decimal_div(E_DEC_FATAL_ERROR, - &val->dec_buf[val->used_dec_buf_no ^ 1], - &val->dec_buf[val->used_dec_buf_no], - &val->dec_buf[2], 4); - val->used_dec_buf_no^= 1; -} - - -longlong -Hybrid_type_traits_decimal::val_int(Hybrid_type *val, bool unsigned_flag) const -{ - longlong result; - my_decimal2int(E_DEC_FATAL_ERROR, &val->dec_buf[val->used_dec_buf_no], - unsigned_flag, &result); - return result; -} - - -double -Hybrid_type_traits_decimal::val_real(Hybrid_type *val) const -{ - my_decimal2double(E_DEC_FATAL_ERROR, &val->dec_buf[val->used_dec_buf_no], - &val->real); - return val->real; -} - - -String * -Hybrid_type_traits_decimal::val_str(Hybrid_type *val, String *to, - uint8 decimals) const -{ - my_decimal_round(E_DEC_FATAL_ERROR, &val->dec_buf[val->used_dec_buf_no], - decimals, FALSE, &val->dec_buf[2]); - my_decimal2string(E_DEC_FATAL_ERROR, &val->dec_buf[2], 0, 0, 0, to); - return to; -} - -/* Hybrid_type_traits_integer */ -static const Hybrid_type_traits_integer integer_traits_instance; - -const Hybrid_type_traits_integer *Hybrid_type_traits_integer::instance() +inline void set_max_sum_func_level(THD *thd, SELECT_LEX *select) { - return &integer_traits_instance; -} - -void -Hybrid_type_traits_integer::fix_length_and_dec(Item *item, Item *arg) const -{ - item->decimals= 0; - item->max_length= MY_INT64_NUM_DECIMAL_DIGITS; - item->unsigned_flag= 0; + if (thd->lex->in_sum_func && + thd->lex->in_sum_func->nest_level >= select->nest_level) + set_if_bigger(thd->lex->in_sum_func->max_sum_func_level, + select->nest_level - 1); } /***************************************************************************** @@ -198,7 +78,7 @@ Hybrid_type_traits_integer::fix_length_and_dec(Item *item, Item *arg) const void item_init(void) { - item_user_lock_init(); + item_func_sleep_init(); uuid_short_init(); } @@ -226,7 +106,6 @@ bool Item::val_bool() return val_real() != 0.0; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); return 0; // Wrong (but safe) } @@ -234,6 +113,82 @@ bool Item::val_bool() } +/** + Get date/time/datetime. + Optionally extend TIME result to DATETIME. +*/ +bool Item::get_date_with_conversion(MYSQL_TIME *ltime, ulonglong fuzzydate) +{ + THD *thd= current_thd; + + /* + Some TIME type items return error when trying to do get_date() + without TIME_TIME_ONLY set (e.g. Item_field for Field_time). + In the SQL standard time->datetime conversion mode we add TIME_TIME_ONLY. + In the legacy time->datetime conversion mode we do not add TIME_TIME_ONLY + and leave it to get_date() to check date. + */ + ulonglong time_flag= (field_type() == MYSQL_TYPE_TIME && + !(thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST)) ? + TIME_TIME_ONLY : 0; + if (get_date(ltime, fuzzydate | time_flag)) + return true; + if (ltime->time_type == MYSQL_TIMESTAMP_TIME && + !(fuzzydate & TIME_TIME_ONLY)) + { + MYSQL_TIME tmp; + if (time_to_datetime_with_warn(thd, ltime, &tmp, fuzzydate)) + return null_value= true; + *ltime= tmp; + } + return false; +} + + +/** + Get date/time/datetime. + If DATETIME or DATE result is returned, it's converted to TIME. +*/ +bool Item::get_time_with_conversion(THD *thd, MYSQL_TIME *ltime, + ulonglong fuzzydate) +{ + if (get_date(ltime, fuzzydate)) + return true; + if (ltime->time_type != MYSQL_TIMESTAMP_TIME) + { + MYSQL_TIME ltime2; + if ((thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) && + (ltime->year || ltime->day || ltime->month)) + { + /* + Old mode conversion from DATETIME with non-zero YYYYMMDD part + to TIME works very inconsistently. Possible variants: + - truncate the YYYYMMDD part + - add (MM*33+DD)*24 to hours + - add (MM*31+DD)*24 to hours + Let's return TRUE here, to disallow equal field propagation. + Note, If we start to use this method in more pieces of the code other + than equal field propagation, we should probably return + TRUE only if some flag in fuzzydate is set. + */ + return true; + } + if (datetime_to_time_with_warn(thd, ltime, <ime2, TIME_SECOND_PART_DIGITS)) + { + /* + If the time difference between CURRENT_DATE and ltime + did not fit into the supported TIME range, then we set the + difference to the maximum possible value in the supported TIME range + */ + DBUG_ASSERT(0); + return (null_value= true); + } + *ltime= ltime2; + } + return false; +} + + /* For the items which don't have its own fast val_str_ascii() implementation we provide a generic slower version, @@ -313,11 +268,29 @@ String *Item::val_string_from_decimal(String *str) } +/* + All val_xxx_from_date() must call this method, to expose consistent behaviour + regarding SQL_MODE when converting DATE/DATETIME to other data types. +*/ +bool Item::get_temporal_with_sql_mode(MYSQL_TIME *ltime) +{ + return get_date(ltime, field_type() == MYSQL_TYPE_TIME + ? TIME_TIME_ONLY + : sql_mode_for_dates(current_thd)); +} + + +bool Item::is_null_from_temporal() +{ + MYSQL_TIME ltime; + return get_temporal_with_sql_mode(<ime); +} + + String *Item::val_string_from_date(String *str) { MYSQL_TIME ltime; - if (get_date(<ime, - field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0) || + if (get_temporal_with_sql_mode(<ime) || str->alloc(MAX_DATE_STRING_REP_LENGTH)) { null_value= 1; @@ -356,17 +329,7 @@ my_decimal *Item::val_decimal_from_string(my_decimal *decimal_value) if (!(res= val_str(&str_value))) return 0; - if (str2my_decimal(E_DEC_FATAL_ERROR & ~E_DEC_BAD_NUM, - res->ptr(), res->length(), res->charset(), - decimal_value) & E_DEC_BAD_NUM) - { - ErrConvString err(res); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), "DECIMAL", - err.ptr()); - } - return decimal_value; + return decimal_from_string_with_check(decimal_value, res); } @@ -374,7 +337,7 @@ my_decimal *Item::val_decimal_from_date(my_decimal *decimal_value) { DBUG_ASSERT(fixed == 1); MYSQL_TIME ltime; - if (get_date(<ime, 0)) + if (get_temporal_with_sql_mode(<ime)) { my_decimal_set_zero(decimal_value); null_value= 1; // set NULL, stop processing @@ -401,18 +364,26 @@ longlong Item::val_int_from_date() { DBUG_ASSERT(fixed == 1); MYSQL_TIME ltime; - if (get_date(<ime, 0)) + if (get_temporal_with_sql_mode(<ime)) return 0; longlong v= TIME_to_ulonglong(<ime); return ltime.neg ? -v : v; } +longlong Item::val_int_from_real() +{ + DBUG_ASSERT(fixed == 1); + bool error; + return double_to_longlong(val_real(), false /*unsigned_flag*/, &error); +} + + double Item::val_real_from_date() { DBUG_ASSERT(fixed == 1); MYSQL_TIME ltime; - if (get_date(<ime, 0)) + if (get_temporal_with_sql_mode(<ime)) return 0; return TIME_to_double(<ime); } @@ -454,9 +425,7 @@ int Item::save_time_in_field(Field *field) int Item::save_date_in_field(Field *field) { MYSQL_TIME ltime; - if (get_date(<ime, (current_thd->variables.sql_mode & - (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | - MODE_INVALID_DATES)))) + if (get_date(<ime, sql_mode_for_dates(current_thd))) return set_field_to_null_with_conversions(field, 0); field->set_notnull(); return field->store_time_dec(<ime, decimals); @@ -494,23 +463,21 @@ int Item::save_str_value_in_field(Field *field, String *result) } -Item::Item(): +Item::Item(THD *thd): is_expensive_cache(-1), rsize(0), name(0), orig_name(0), name_length(0), - fixed(0), is_autogenerated_name(TRUE), - collation(&my_charset_bin, DERIVATION_COERCIBLE) + fixed(0), is_autogenerated_name(TRUE) { + DBUG_ASSERT(thd); marker= 0; - maybe_null=null_value=with_sum_func=with_field=unsigned_flag=0; + maybe_null=null_value=with_sum_func=with_field=0; in_rollup= 0; - decimals= 0; max_length= 0; with_subselect= 0; with_param= 0; - cmp_context= IMPOSSIBLE_RESULT; - /* Initially this item is not attached to any JOIN_TAB. */ + + /* Initially this item is not attached to any JOIN_TAB. */ join_tab_idx= MAX_TABLES; /* Put item in free list so that we can free all items at end */ - THD *thd= current_thd; next= thd->free_list; thd->free_list= this; /* @@ -536,28 +503,24 @@ Item::Item(): tables. */ Item::Item(THD *thd, Item *item): + Type_std_attributes(item), join_tab_idx(item->join_tab_idx), is_expensive_cache(-1), rsize(0), str_value(item->str_value), name(item->name), orig_name(item->orig_name), - max_length(item->max_length), name_length(item->name_length), - decimals(item->decimals), marker(item->marker), maybe_null(item->maybe_null), in_rollup(item->in_rollup), null_value(item->null_value), - unsigned_flag(item->unsigned_flag), with_sum_func(item->with_sum_func), with_param(item->with_param), with_field(item->with_field), fixed(item->fixed), is_autogenerated_name(item->is_autogenerated_name), - with_subselect(item->has_subquery()), - collation(item->collation), - cmp_context(item->cmp_context) + with_subselect(item->has_subquery()) { next= thd->free_list; // Put in free list thd->free_list= this; @@ -573,47 +536,37 @@ uint Item::decimal_precision() const uint prec= my_decimal_length_to_precision(max_char_length(), decimals, unsigned_flag); - return min(prec, DECIMAL_MAX_PRECISION); + return MY_MIN(prec, DECIMAL_MAX_PRECISION); } - return min(max_char_length(), DECIMAL_MAX_PRECISION); -} - - -#if MARIADB_VERSION_ID < 1000000 -static uint ms_to_precision(uint ms) -{ - uint cut, precision; - for (cut= 10, precision= 6 ; precision > 0 ; cut*= 10, precision--) - { - if (ms % cut) - return precision; - } - return 0; + uint res= max_char_length(); + /* + Return at least one decimal digit, even if Item::max_char_length() + returned 0. This is important to avoid attempts to create fields of types + INT(0) or DECIMAL(0,0) when converting NULL or empty strings to INT/DECIMAL: + CREATE TABLE t1 AS SELECT CONVERT(NULL,SIGNED) AS a; + */ + return res ? MY_MIN(res, DECIMAL_MAX_PRECISION) : 1; } -#else -#error Change the code to use MYSQL_TIME_STATUS::precision instead. -#endif -uint Item::temporal_precision(enum_field_types type) +uint Item::temporal_precision(enum_field_types type_arg) { if (const_item() && result_type() == STRING_RESULT && !is_temporal_type(field_type())) { MYSQL_TIME ltime; String buf, *tmp; - int was_cut; + MYSQL_TIME_STATUS status; DBUG_ASSERT(fixed); if ((tmp= val_str(&buf)) && - (type == MYSQL_TYPE_TIME ? + !(type_arg == MYSQL_TYPE_TIME ? str_to_time(tmp->charset(), tmp->ptr(), tmp->length(), - <ime, TIME_TIME_ONLY, &was_cut) : + <ime, TIME_TIME_ONLY, &status) : str_to_datetime(tmp->charset(), tmp->ptr(), tmp->length(), - <ime, TIME_FUZZY_DATES, &was_cut)) > - MYSQL_TIMESTAMP_ERROR) - return min(ms_to_precision(ltime.second_part), TIME_SECOND_PART_DIGITS); + <ime, TIME_FUZZY_DATES, &status))) + return MY_MIN(status.precision, TIME_SECOND_PART_DIGITS); } - return min(decimals, TIME_SECOND_PART_DIGITS); + return MY_MIN(decimals, TIME_SECOND_PART_DIGITS); } @@ -650,7 +603,6 @@ void Item::print_value(String *str) break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } } @@ -661,7 +613,7 @@ void Item::cleanup() { DBUG_ENTER("Item::cleanup"); DBUG_PRINT("enter", ("this: %p", this)); - fixed=0; + fixed= 0; marker= 0; join_tab_idx= MAX_TABLES; if (orig_name) @@ -731,14 +683,17 @@ Item_result Item::cmp_type() const case MYSQL_TYPE_GEOMETRY: return STRING_RESULT; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: case MYSQL_TYPE_DATE: case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: case MYSQL_TYPE_NEWDATE: return TIME_RESULT; }; DBUG_ASSERT(0); - return IMPOSSIBLE_RESULT; + return STRING_RESULT; } /** @@ -770,11 +725,11 @@ Item_result Item::cmp_type() const pointer to newly allocated item is returned. */ -Item* Item::transform(Item_transformer transformer, uchar *arg) +Item* Item::transform(THD *thd, Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); - return (this->*transformer)(arg); + return (this->*transformer)(thd, arg); } @@ -797,7 +752,7 @@ Item* Item::set_expr_cache(THD *thd) { DBUG_ENTER("Item::set_expr_cache"); Item_cache_wrapper *wrapper; - if ((wrapper= new Item_cache_wrapper(this)) && + if ((wrapper= new (thd->mem_root) Item_cache_wrapper(thd, this)) && !wrapper->fix_fields(thd, (Item**)&wrapper)) { if (wrapper->set_cache(thd)) @@ -808,10 +763,11 @@ Item* Item::set_expr_cache(THD *thd) } -Item_ident::Item_ident(Name_resolution_context *context_arg, +Item_ident::Item_ident(THD *thd, Name_resolution_context *context_arg, const char *db_name_arg,const char *table_name_arg, const char *field_name_arg) - :orig_db_name(db_name_arg), orig_table_name(table_name_arg), + :Item_result_field(thd), orig_db_name(db_name_arg), + orig_table_name(table_name_arg), orig_field_name(field_name_arg), context(context_arg), db_name(db_name_arg), table_name(table_name_arg), field_name(field_name_arg), @@ -823,8 +779,9 @@ Item_ident::Item_ident(Name_resolution_context *context_arg, } -Item_ident::Item_ident(TABLE_LIST *view_arg, const char *field_name_arg) - :orig_db_name(NullS), orig_table_name(view_arg->table_name), +Item_ident::Item_ident(THD *thd, TABLE_LIST *view_arg, const char *field_name_arg) + :Item_result_field(thd), orig_db_name(NullS), + orig_table_name(view_arg->table_name), orig_field_name(field_name_arg), context(&view_arg->view->select_lex.context), db_name(NullS), table_name(view_arg->alias), field_name(field_name_arg), @@ -841,7 +798,7 @@ Item_ident::Item_ident(TABLE_LIST *view_arg, const char *field_name_arg) */ Item_ident::Item_ident(THD *thd, Item_ident *item) - :Item(thd, item), + :Item_result_field(thd, item), orig_db_name(item->orig_db_name), orig_table_name(item->orig_table_name), orig_field_name(item->orig_field_name), @@ -860,7 +817,7 @@ void Item_ident::cleanup() { DBUG_ENTER("Item_ident::cleanup"); bool was_fixed= fixed; - Item::cleanup(); + Item_result_field::cleanup(); db_name= orig_db_name; table_name= orig_table_name; field_name= orig_field_name; @@ -871,7 +828,7 @@ void Item_ident::cleanup() We can trust that depended_from set correctly only if this item was fixed */ - can_be_depended= test(depended_from); + can_be_depended= MY_TEST(depended_from); } DBUG_VOID_RETURN; } @@ -889,10 +846,15 @@ bool Item_ident::remove_dependence_processor(uchar * arg) bool Item_ident::collect_outer_ref_processor(uchar *param) { Collect_deps_prm *prm= (Collect_deps_prm *)param; - if (depended_from && + if (depended_from && depended_from->nest_level_base == prm->nest_level_base && depended_from->nest_level < prm->nest_level) - prm->parameters->add_unique(this, &cmp_items); + { + if (prm->collect) + prm->parameters->add_unique(this, &cmp_items); + else + prm->count++; + } return FALSE; } @@ -1042,10 +1004,15 @@ void Item::set_name(const char *str, uint length, CHARSET_INFO *cs) name_length= 0; return; } - if (cs->ctype) - { - const char *str_start= str; + const char *str_start= str; + if (!cs->ctype || cs->mbminlen > 1) + { + str+= cs->cset->scan(cs, str, str + length, MY_SEQ_SPACES); + length-= str - str_start; + } + else + { /* This will probably need a better implementation in the future: a function in CHARSET_INFO structure. @@ -1055,21 +1022,24 @@ void Item::set_name(const char *str, uint length, CHARSET_INFO *cs) length--; str++; } - if (str != str_start && !is_autogenerated_name) - { - char buff[SAFE_NAME_LEN]; - strmake(buff, str_start, - min(sizeof(buff)-1, length + (int) (str-str_start))); - - if (length == 0) - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_NAME_BECOMES_EMPTY, ER(ER_NAME_BECOMES_EMPTY), - buff); - else - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_REMOVED_SPACES, ER(ER_REMOVED_SPACES), - buff); - } + } + if (str != str_start && !is_autogenerated_name) + { + char buff[SAFE_NAME_LEN]; + THD *thd= current_thd; + + strmake(buff, str_start, + MY_MIN(sizeof(buff)-1, length + (int) (str-str_start))); + + if (length == 0) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NAME_BECOMES_EMPTY, + ER_THD(thd, ER_NAME_BECOMES_EMPTY), + buff); + else + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_REMOVED_SPACES, ER_THD(thd, ER_REMOVED_SPACES), + buff); } if (!my_charset_same(cs, system_charset_info)) { @@ -1080,7 +1050,7 @@ void Item::set_name(const char *str, uint length, CHARSET_INFO *cs) name_length= res_length; } else - name= sql_strmake(str, (name_length= min(length,MAX_ALIAS_NAME))); + name= sql_strmake(str, (name_length= MY_MIN(length,MAX_ALIAS_NAME))); } @@ -1133,9 +1103,11 @@ bool Item::eq(const Item *item, bool binary_cmp) const } -Item *Item::safe_charset_converter(CHARSET_INFO *tocs) +Item *Item::safe_charset_converter(THD *thd, CHARSET_INFO *tocs) { - Item_func_conv_charset *conv= new Item_func_conv_charset(this, tocs, 1); + if (!needs_charset_converter(tocs)) + return this; + Item_func_conv_charset *conv= new (thd->mem_root) Item_func_conv_charset(thd, this, tocs, 1); return conv->safe ? conv : NULL; } @@ -1147,7 +1119,7 @@ Item *Item::safe_charset_converter(CHARSET_INFO *tocs) Example: Item_singlerow_subselect has "Item_cache **row". Creating of Item_func_conv_charset followed by THD::change_item_tree() - should not change row[i] from Item_cache directly to Item_func_conv_charset, because Item_singlerow_subselect + should not change row[i] from Item_cache directly to Item_func_conv_charset, because Item_singlerow_subselect later calls Item_cache-specific methods, e.g. row[i]->store() and row[i]->cache_value(). @@ -1160,18 +1132,18 @@ Item *Item::safe_charset_converter(CHARSET_INFO *tocs) TODO: we should eventually check all other use cases of change_item_tree(). Perhaps some more potentially dangerous substitution examples exist. */ -Item *Item_cache::safe_charset_converter(CHARSET_INFO *tocs) +Item *Item_cache::safe_charset_converter(THD *thd, CHARSET_INFO *tocs) { if (!example) - return Item::safe_charset_converter(tocs); - Item *conv= example->safe_charset_converter(tocs); + return Item::safe_charset_converter(thd, tocs); + Item *conv= example->safe_charset_converter(thd, tocs); if (conv == example) return this; Item_cache *cache; - if (!conv || conv->fix_fields(current_thd, (Item **) NULL) || - !(cache= new Item_cache_str(conv))) + if (!conv || conv->fix_fields(thd, (Item **) NULL) || + !(cache= new (thd->mem_root) Item_cache_str(thd, conv))) return NULL; // Safe conversion is not possible, or OEM - cache->setup(conv); + cache->setup(thd, conv); cache->fixed= false; // Make Item::fix_fields() happy return cache; } @@ -1188,7 +1160,7 @@ Item *Item_cache::safe_charset_converter(CHARSET_INFO *tocs) the latter returns a non-fixed Item, so val_str() crashes afterwards. Override Item_num method, to return a fixed item. */ -Item *Item_num::safe_charset_converter(CHARSET_INFO *tocs) +Item *Item_num::safe_charset_converter(THD *thd, CHARSET_INFO *tocs) { /* Item_num returns pure ASCII result, @@ -1198,114 +1170,59 @@ Item *Item_num::safe_charset_converter(CHARSET_INFO *tocs) if (!(tocs->state & MY_CS_NONASCII)) return this; - Item_string *conv; - uint conv_errors; - char buf[64], buf2[64]; - String tmp(buf, sizeof(buf), &my_charset_bin); - String cstr(buf2, sizeof(buf2), &my_charset_bin); - String *ostr= val_str(&tmp); - char *ptr; - cstr.copy(ostr->ptr(), ostr->length(), ostr->charset(), tocs, &conv_errors); - if (conv_errors || !(conv= new Item_string(cstr.ptr(), cstr.length(), - cstr.charset(), - collation.derivation))) - { - /* - Safe conversion is not possible (or EOM). - We could not convert a string into the requested character set - without data loss. The target charset does not cover all the - characters from the string. Operation cannot be done correctly. - */ - return NULL; - } - if (!(ptr= current_thd->strmake(cstr.ptr(), cstr.length()))) - return NULL; - conv->str_value.set(ptr, cstr.length(), cstr.charset()); - /* Ensure that no one is going to change the result string */ - conv->str_value.mark_as_const(); - conv->fix_char_length(max_char_length()); + Item *conv; + if ((conv= const_charset_converter(thd, tocs, true))) + conv->fix_char_length(max_char_length()); return conv; } -Item *Item_static_float_func::safe_charset_converter(CHARSET_INFO *tocs) -{ - Item_string *conv; - char buf[64]; - String *s, tmp(buf, sizeof(buf), &my_charset_bin); - s= val_str(&tmp); - if ((conv= new Item_static_string_func(func_name, s->ptr(), s->length(), - s->charset()))) - { - conv->str_value.copy(); - conv->str_value.mark_as_const(); - } - return conv; -} +/** + Create character set converter for constant items + using Item_null, Item_string or Item_static_string_func. + @param tocs Character set to to convert the string to. + @param lossless Whether data loss is acceptable. + @param func_name Function name, or NULL. -Item *Item_string::safe_charset_converter(CHARSET_INFO *tocs) + @return this, if conversion is not needed, + NULL, if safe conversion is not possible, or + a new item representing the converted constant. +*/ +Item *Item::const_charset_converter(THD *thd, CHARSET_INFO *tocs, + bool lossless, + const char *func_name) { - Item_string *conv; - uint conv_errors; - char *ptr; - String tmp, cstr, *ostr= val_str(&tmp); - cstr.copy(ostr->ptr(), ostr->length(), ostr->charset(), tocs, &conv_errors); - if (conv_errors || !(conv= new Item_string(cstr.ptr(), cstr.length(), - cstr.charset(), - collation.derivation))) - { - /* - Safe conversion is not possible (or EOM). - We could not convert a string into the requested character set - without data loss. The target charset does not cover all the - characters from the string. Operation cannot be done correctly. - */ - return NULL; - } - if (!(ptr= current_thd->strmake(cstr.ptr(), cstr.length()))) - return NULL; - conv->str_value.set(ptr, cstr.length(), cstr.charset()); - /* Ensure that no one is going to change the result string */ - conv->str_value.mark_as_const(); - return conv; -} + DBUG_ASSERT(const_item()); + DBUG_ASSERT(fixed); + StringBuffer<64>tmp; + String *s= val_str(&tmp); + MEM_ROOT *mem_root= thd->mem_root; + if (!s) + return new (mem_root) Item_null(thd, (char *) func_name, tocs); -Item *Item_param::safe_charset_converter(CHARSET_INFO *tocs) -{ - if (const_item()) + if (!needs_charset_converter(s->length(), tocs)) { - uint cnv_errors; - String *ostr= val_str(&cnvstr); - if (null_value) - { - Item_null *n= new Item_null(); - return n ? n->safe_charset_converter(tocs) : NULL; - } - cnvitem->str_value.copy(ostr->ptr(), ostr->length(), - ostr->charset(), tocs, &cnv_errors); - if (cnv_errors) - return NULL; - cnvitem->str_value.mark_as_const(); - cnvitem->max_length= cnvitem->str_value.numchars() * tocs->mbmaxlen; - return cnvitem; + if (collation.collation == &my_charset_bin && tocs != &my_charset_bin && + !this->check_well_formed_result(s, true)) + return NULL; + return this; } - return Item::safe_charset_converter(tocs); -} - -Item *Item_static_string_func::safe_charset_converter(CHARSET_INFO *tocs) -{ - Item_string *conv; uint conv_errors; - String tmp, cstr, *ostr= val_str(&tmp); - cstr.copy(ostr->ptr(), ostr->length(), ostr->charset(), tocs, &conv_errors); - if (conv_errors || - !(conv= new Item_static_string_func(func_name, - cstr.ptr(), cstr.length(), - cstr.charset(), - collation.derivation))) + Item_string *conv= (func_name ? + new (mem_root) + Item_static_string_func(thd, func_name, + s, tocs, &conv_errors, + collation.derivation, + collation.repertoire) : + new (mem_root) + Item_string(thd, s, tocs, &conv_errors, + collation.derivation, + collation.repertoire)); + + if (!conv || (conv_errors && lossless)) { /* Safe conversion is not possible (or EOM). @@ -1315,23 +1232,28 @@ Item *Item_static_string_func::safe_charset_converter(CHARSET_INFO *tocs) */ return NULL; } - conv->str_value.copy(); - /* Ensure that no one is going to change the result string */ - conv->str_value.mark_as_const(); + if (s->charset() == &my_charset_bin && tocs != &my_charset_bin && + !conv->check_well_formed_result(true)) + return NULL; return conv; } -bool Item_string::eq(const Item *item, bool binary_cmp) const +Item *Item_param::safe_charset_converter(THD *thd, CHARSET_INFO *tocs) { - if (type() == item->type() && item->basic_const_item()) - { - if (binary_cmp) - return !stringcmp(&str_value, &item->str_value); - return (collation.collation == item->collation.collation && - !sortcmp(&str_value, &item->str_value, collation.collation)); - } - return 0; + /* + Return "this" if in prepare. result_type may change at execition time, + to it's possible that the converter will not be needed at all: + + PREPARE stmt FROM 'SELECT * FROM t1 WHERE field = ?'; + SET @arg= 1; + EXECUTE stmt USING @arg; + + In the above example result_type is STRING_RESULT at prepare time, + and INT_RESULT at execution time. + */ + return !const_item() || state == NULL_VALUE ? + this : const_charset_converter(thd, tocs, true); } @@ -1365,7 +1287,7 @@ bool Item::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) ltime, fuzzydate, field_name_or_null())) goto err; - break; + return null_value= false; } case REAL_RESULT: { @@ -1373,7 +1295,7 @@ bool Item::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) if (null_value || double_to_datetime_with_warn(value, ltime, fuzzydate, field_name_or_null())) goto err; - break; + return null_value= false; } case DECIMAL_RESULT: { @@ -1382,7 +1304,7 @@ bool Item::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) decimal_to_datetime_with_warn(res, ltime, fuzzydate, field_name_or_null())) goto err; - break; + return null_value= false; } case STRING_RESULT: { @@ -1390,23 +1312,44 @@ bool Item::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) String tmp(buff,sizeof(buff), &my_charset_bin),*res; if (!(res=val_str(&tmp)) || str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(), - ltime, fuzzydate) <= MYSQL_TIMESTAMP_ERROR) + ltime, fuzzydate)) goto err; - break; + return null_value= false; } default: + null_value= true; DBUG_ASSERT(0); } - return null_value= 0; - err: + return null_value|= make_zero_date(ltime, fuzzydate); +} + + +bool Item::make_zero_date(MYSQL_TIME *ltime, ulonglong fuzzydate) +{ /* if the item was not null and convertion failed, we return a zero date if allowed, otherwise - null. */ bzero((char*) ltime,sizeof(*ltime)); - return null_value|= !(fuzzydate & TIME_FUZZY_DATES); + if (fuzzydate & TIME_TIME_ONLY) + { + /* + In the following scenario: + - The caller expected to get a TIME value + - Item returned a not NULL string or numeric value + - But then conversion from string or number to TIME failed + we need to change the default time_type from MYSQL_TIMESTAMP_DATE + (which was set in bzero) to MYSQL_TIMESTAMP_TIME and therefore + return TIME'00:00:00' rather than DATE'0000-00-00'. + If we don't do this, methods like Item::get_time_with_conversion() + will erroneously subtract CURRENT_DATE from '0000-00-00 00:00:00' + and return TIME'-838:59:59' instead of TIME'00:00:00' as a result. + */ + ltime->time_type= MYSQL_TIMESTAMP_TIME; + } + return !(fuzzydate & TIME_FUZZY_DATES); } bool Item::get_seconds(ulonglong *sec, ulong *sec_part) @@ -1464,9 +1407,9 @@ int Item::save_in_field_no_warnings(Field *field, bool no_conversions) Item_sp_variable methods *****************************************************************************/ -Item_sp_variable::Item_sp_variable(char *sp_var_name_str, - uint sp_var_name_length) - :m_thd(0) +Item_sp_variable::Item_sp_variable(THD *thd, char *sp_var_name_str, + uint sp_var_name_length): + Item(thd), m_thd(0) #ifndef DBUG_OFF , m_sp(0) #endif @@ -1574,17 +1517,17 @@ bool Item_sp_variable::is_null() Item_splocal methods *****************************************************************************/ -Item_splocal::Item_splocal(const LEX_STRING &sp_var_name, +Item_splocal::Item_splocal(THD *thd, const LEX_STRING &sp_var_name, uint sp_var_idx, enum_field_types sp_var_type, - uint pos_in_q, uint len_in_q) - :Item_sp_variable(sp_var_name.str, sp_var_name.length), - m_var_idx(sp_var_idx), - limit_clause_param(FALSE), - pos_in_query(pos_in_q), len_in_query(len_in_q) + uint pos_in_q, uint len_in_q): + Item_sp_variable(thd, sp_var_name.str, sp_var_name.length), + Rewritable_query_parameter(pos_in_q, len_in_q), + m_var_idx(sp_var_idx) { maybe_null= TRUE; + sp_var_type= real_type_to_type(sp_var_type); m_type= sp_map_item_type(sp_var_type); m_field_type= sp_var_type; m_result_type= sp_map_result_type(sp_var_type); @@ -1636,9 +1579,9 @@ bool Item_splocal::set_value(THD *thd, sp_rcontext *ctx, Item **it) Item_case_expr methods *****************************************************************************/ -Item_case_expr::Item_case_expr(uint case_expr_id) - :Item_sp_variable( C_STRING_WITH_LEN("case_expr")), - m_case_expr_id(case_expr_id) +Item_case_expr::Item_case_expr(THD *thd, uint case_expr_id): + Item_sp_variable(thd, C_STRING_WITH_LEN("case_expr")), + m_case_expr_id(case_expr_id) { } @@ -1726,13 +1669,17 @@ bool Item_name_const::is_null() } -Item_name_const::Item_name_const(Item *name_arg, Item *val): - value_item(val), name_item(name_arg) +Item_name_const::Item_name_const(THD *thd, Item *name_arg, Item *val): + Item(thd), value_item(val), name_item(name_arg) { + StringBuffer<128> name_buffer; + String *name_str; Item::maybe_null= TRUE; valid_args= true; - if (!name_item->basic_const_item()) + if (!name_item->basic_const_item() || + !(name_str= name_item->val_str(&name_buffer))) // Can't have a NULL name goto err; + set_name(name_str->ptr(), name_str->length(), name_str->charset()); if (value_item->basic_const_item()) return; // ok @@ -1794,25 +1741,18 @@ Item::Type Item_name_const::type() const bool Item_name_const::fix_fields(THD *thd, Item **ref) { - char buf[128]; - String *item_name; - String s(buf, sizeof(buf), &my_charset_bin); - s.length(0); - if (value_item->fix_fields(thd, &value_item) || name_item->fix_fields(thd, &name_item) || !value_item->const_item() || - !name_item->const_item() || - !(item_name= name_item->val_str(&s))) // Can't have a NULL name + !name_item->const_item()) { my_error(ER_RESERVED_SYNTAX, MYF(0), "NAME_CONST"); return TRUE; } - if (is_autogenerated_name) - { - set_name(item_name->ptr(), (uint) item_name->length(), system_charset_info); - } - collation.set(value_item->collation.collation, DERIVATION_IMPLICIT); + if (value_item->collation.derivation == DERIVATION_NUMERIC) + collation.set_numeric(); + else + collation.set(value_item->collation.collation, DERIVATION_IMPLICIT); max_length= value_item->max_length; decimals= value_item->decimals; fixed= 1; @@ -1838,9 +1778,10 @@ void Item_name_const::print(String *str, enum_query_type query_type) class Item_aggregate_ref : public Item_ref { public: - Item_aggregate_ref(Name_resolution_context *context_arg, Item **item, - const char *table_name_arg, const char *field_name_arg) - :Item_ref(context_arg, item, table_name_arg, field_name_arg) {} + Item_aggregate_ref(THD *thd, Name_resolution_context *context_arg, + Item **item, const char *table_name_arg, + const char *field_name_arg): + Item_ref(thd, context_arg, item, table_name_arg, field_name_arg) {} virtual inline void print (String *str, enum_query_type query_type) { @@ -1858,76 +1799,105 @@ public: @param thd Thread handler @param ref_pointer_array Pointer to array of reference fields - @param fields All fields in select + @param fields All fields in select @param ref Pointer to item - @param skip_registered <=> function be must skipped for registered - SUM items + @param split_flags Zero or more of the following flags + SPLIT_FUNC_SKIP_REGISTERED: + Function be must skipped for registered SUM + SUM items + SPLIT_SUM_SELECT + We are called on the select level and have to + register items operated on sum function @note - This is from split_sum_func2() for items that should be split - All found SUM items are added FIRST in the fields list and we replace the item with a reference. + If this is an item in the SELECT list then we also have to split out + all arguments to functions used together with the sum function. + For example in case of SELECT A*sum(B) we have to split out both + A and sum(B). + This is not needed for ORDER BY, GROUP BY or HAVING as all references + to items in the select list are already of type REF + thd->fatal_error() may be called if we are out of memory */ void Item::split_sum_func2(THD *thd, Item **ref_pointer_array, List<Item> &fields, Item **ref, - bool skip_registered) + uint split_flags) { - /* An item of type Item_sum is registered <=> ref_by != 0 */ - if (type() == SUM_FUNC_ITEM && skip_registered && - ((Item_sum *) this)->ref_by) - return; - if ((type() != SUM_FUNC_ITEM && with_sum_func) || - (type() == FUNC_ITEM && - (((Item_func *) this)->functype() == Item_func::ISNOTNULLTEST_FUNC || - ((Item_func *) this)->functype() == Item_func::TRIG_COND_FUNC))) + if (unlikely(type() == SUM_FUNC_ITEM)) { - /* Will split complicated items and ignore simple ones */ - split_sum_func(thd, ref_pointer_array, fields); + /* An item of type Item_sum is registered if ref_by != 0 */ + if ((split_flags & SPLIT_SUM_SKIP_REGISTERED) && + ((Item_sum *) this)->ref_by) + return; } - else if ((type() == SUM_FUNC_ITEM || (used_tables() & ~PARAM_TABLE_BIT)) && - type() != SUBSELECT_ITEM && - (type() != REF_ITEM || - ((Item_ref*)this)->ref_type() == Item_ref::VIEW_REF)) + else { - /* - Replace item with a reference so that we can easily calculate - it (in case of sum functions) or copy it (in case of fields) - - The test above is to ensure we don't do a reference for things - that are constants (PARAM_TABLE_BIT is in effect a constant) - or already referenced (for example an item in HAVING) - Exception is Item_direct_view_ref which we need to convert to - Item_ref to allow fields from view being stored in tmp table. - */ - Item_aggregate_ref *item_ref; - uint el= fields.elements; - DBUG_ASSERT(fields.elements <= - thd->lex->current_select->ref_pointer_array_size); - /* - If this is an item_ref, get the original item - This is a safety measure if this is called for things that is - already a reference. - */ - Item *real_itm= real_item(); + /* Not a SUM() function */ + if (unlikely((!with_sum_func && !(split_flags & SPLIT_SUM_SELECT)))) + { + /* + This is not a SUM function and there are no SUM functions inside. + Nothing more to do. + */ + return; + } + if (likely(with_sum_func || + (type() == FUNC_ITEM && + (((Item_func *) this)->functype() == + Item_func::ISNOTNULLTEST_FUNC || + ((Item_func *) this)->functype() == + Item_func::TRIG_COND_FUNC)))) + { + /* Will call split_sum_func2() for all items */ + split_sum_func(thd, ref_pointer_array, fields, split_flags); + return; + } - ref_pointer_array[el]= real_itm; - if (!(item_ref= new Item_aggregate_ref(&thd->lex->current_select->context, - ref_pointer_array + el, 0, name))) - return; // fatal_error is set - if (type() == SUM_FUNC_ITEM) - item_ref->depended_from= ((Item_sum *) this)->depended_from(); - fields.push_front(real_itm); - thd->change_item_tree(ref, item_ref); + if (unlikely((!(used_tables() & ~PARAM_TABLE_BIT) || + type() == SUBSELECT_ITEM || + (type() == REF_ITEM && + ((Item_ref*)this)->ref_type() != Item_ref::VIEW_REF)))) + return; } + + /* + Replace item with a reference so that we can easily calculate + it (in case of sum functions) or copy it (in case of fields) + + The test above is to ensure we don't do a reference for things + that are constants (PARAM_TABLE_BIT is in effect a constant) + or already referenced (for example an item in HAVING) + Exception is Item_direct_view_ref which we need to convert to + Item_ref to allow fields from view being stored in tmp table. + */ + Item_aggregate_ref *item_ref; + uint el= fields.elements; + /* + If this is an item_ref, get the original item + This is a safety measure if this is called for things that is + already a reference. + */ + Item *real_itm= real_item(); + + ref_pointer_array[el]= real_itm; + if (!(item_ref= (new (thd->mem_root) + Item_aggregate_ref(thd, + &thd->lex->current_select->context, + ref_pointer_array + el, 0, name)))) + return; // fatal_error is set + if (type() == SUM_FUNC_ITEM) + item_ref->depended_from= ((Item_sum *) this)->depended_from(); + fields.push_front(real_itm); + thd->change_item_tree(ref, item_ref); } static bool -left_is_superset(DTCollation *left, DTCollation *right) +left_is_superset(const DTCollation *left, const DTCollation *right) { /* Allow convert to Unicode */ if (left->collation->state & MY_CS_UNICODE && @@ -1986,7 +1956,7 @@ left_is_superset(DTCollation *left, DTCollation *right) @endcode */ -bool DTCollation::aggregate(DTCollation &dt, uint flags) +bool DTCollation::aggregate(const DTCollation &dt, uint flags) { if (!my_charset_same(collation, dt.collation)) { @@ -2118,8 +2088,9 @@ void my_coll_agg_error(Item** args, uint count, const char *fname, } -bool agg_item_collations(DTCollation &c, const char *fname, - Item **av, uint count, uint flags, int item_sep) +bool Item_func_or_sum::agg_item_collations(DTCollation &c, const char *fname, + Item **av, uint count, + uint flags, int item_sep) { uint i; Item **arg; @@ -2164,17 +2135,14 @@ bool agg_item_collations(DTCollation &c, const char *fname, } -bool agg_item_collations_for_comparison(DTCollation &c, const char *fname, - Item **av, uint count, uint flags) -{ - return (agg_item_collations(c, fname, av, count, - flags | MY_COLL_DISALLOW_NONE, 1)); -} - - -bool agg_item_set_converter(DTCollation &coll, const char *fname, - Item **args, uint nargs, uint flags, int item_sep) +bool Item_func_or_sum::agg_item_set_converter(const DTCollation &coll, + const char *fname, + Item **args, uint nargs, + uint flags, int item_sep) { + THD *thd= current_thd; + if (thd->lex->is_ps_or_view_context_analysis()) + return false; Item **arg, *safe_args[2]= {NULL, NULL}; /* @@ -2190,7 +2158,6 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, safe_args[1]= args[item_sep]; } - THD *thd= current_thd; bool res= FALSE; uint i; @@ -2205,34 +2172,11 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, for (i= 0, arg= args; i < nargs; i++, arg+= item_sep) { - Item* conv; - uint32 dummy_offset; - if (!String::needs_conversion(1, (*arg)->collation.collation, - coll.collation, - &dummy_offset)) + Item* conv= (*arg)->safe_charset_converter(thd, coll.collation); + if (conv == *arg) continue; - - /* - No needs to add converter if an "arg" is NUMERIC or DATETIME - value (which is pure ASCII) and at the same time target DTCollation - is ASCII-compatible. For example, no needs to rewrite: - SELECT * FROM t1 WHERE datetime_field = '2010-01-01'; - to - SELECT * FROM t1 WHERE CONVERT(datetime_field USING cs) = '2010-01-01'; - - TODO: avoid conversion of any values with - repertoire ASCII and 7bit-ASCII-compatible, - not only numeric/datetime origin. - */ - if ((*arg)->collation.derivation == DERIVATION_NUMERIC && - (*arg)->collation.repertoire == MY_REPERTOIRE_ASCII && - !((*arg)->collation.collation->state & MY_CS_NONASCII) && - !(coll.collation->state & MY_CS_NONASCII)) - continue; - - if (!(conv= (*arg)->safe_charset_converter(coll.collation)) && - ((*arg)->collation.repertoire == MY_REPERTOIRE_ASCII)) - conv= new Item_func_conv_charset(*arg, coll.collation, 1); + if (!conv && ((*arg)->collation.repertoire == MY_REPERTOIRE_ASCII)) + conv= new (thd->mem_root) Item_func_conv_charset(thd, *arg, coll.collation, 1); if (!conv) { @@ -2246,8 +2190,6 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, res= TRUE; break; // we cannot return here, we need to restore "arena". } - if ((*arg)->type() == Item::FIELD_ITEM) - ((Item_field *)(*arg))->no_const_subst= 1; /* If in statement prepare, then we create a converter for two constant items, do it once and then reuse it. @@ -2275,46 +2217,6 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, } -/* - Collect arguments' character sets together. - We allow to apply automatic character set conversion in some cases. - The conditions when conversion is possible are: - - arguments A and B have different charsets - - A wins according to coercibility rules - (i.e. a column is stronger than a string constant, - an explicit COLLATE clause is stronger than a column) - - character set of A is either superset for character set of B, - or B is a string constant which can be converted into the - character set of A without data loss. - - If all of the above is true, then it's possible to convert - B into the character set of A, and then compare according - to the collation of A. - - For functions with more than two arguments: - - collect(A,B,C) ::= collect(collect(A,B),C) - - Since this function calls THD::change_item_tree() on the passed Item ** - pointers, it is necessary to pass the original Item **'s, not copies. - Otherwise their values will not be properly restored (see BUG#20769). - If the items are not consecutive (eg. args[2] and args[5]), use the - item_sep argument, ie. - - agg_item_charsets(coll, fname, &args[2], 2, flags, 3) - -*/ - -bool agg_item_charsets(DTCollation &coll, const char *fname, - Item **args, uint nargs, uint flags, int item_sep) -{ - if (agg_item_collations(coll, fname, args, nargs, flags, item_sep)) - return TRUE; - - return agg_item_set_converter(coll, fname, args, nargs, flags, item_sep); -} - - void Item_ident_for_show::make_field(Send_field *tmp_field) { tmp_field->table_name= tmp_field->org_table_name= table_name; @@ -2330,9 +2232,9 @@ void Item_ident_for_show::make_field(Send_field *tmp_field) /**********************************************/ -Item_field::Item_field(Field *f) - :Item_ident(0, NullS, *f->table_name, f->field_name), - item_equal(0), no_const_subst(0), +Item_field::Item_field(THD *thd, Field *f) + :Item_ident(thd, 0, NullS, *f->table_name, f->field_name), + item_equal(0), have_privileges(0), any_privileges(0) { set_field(f); @@ -2354,8 +2256,8 @@ Item_field::Item_field(Field *f) Item_field::Item_field(THD *thd, Name_resolution_context *context_arg, Field *f) - :Item_ident(context_arg, f->table->s->db.str, *f->table_name, f->field_name), - item_equal(0), no_const_subst(0), + :Item_ident(thd, context_arg, f->table->s->db.str, *f->table_name, f->field_name), + item_equal(0), have_privileges(0), any_privileges(0) { /* @@ -2394,14 +2296,14 @@ Item_field::Item_field(THD *thd, Name_resolution_context *context_arg, } -Item_field::Item_field(Name_resolution_context *context_arg, +Item_field::Item_field(THD *thd, Name_resolution_context *context_arg, const char *db_arg,const char *table_name_arg, const char *field_name_arg) - :Item_ident(context_arg, db_arg,table_name_arg,field_name_arg), - field(0), result_field(0), item_equal(0), no_const_subst(0), + :Item_ident(thd, context_arg, db_arg, table_name_arg, field_name_arg), + field(0), item_equal(0), have_privileges(0), any_privileges(0) { - SELECT_LEX *select= current_thd->lex->current_select; + SELECT_LEX *select= thd->lex->current_select; collation.set(DERIVATION_IMPLICIT); if (select && select->parsing_place != IN_HAVING) select->select_n_where_fields++; @@ -2415,9 +2317,7 @@ Item_field::Item_field(Name_resolution_context *context_arg, Item_field::Item_field(THD *thd, Item_field *item) :Item_ident(thd, item), field(item->field), - result_field(item->result_field), item_equal(item->item_equal), - no_const_subst(item->no_const_subst), have_privileges(item->have_privileges), any_privileges(item->any_privileges) { @@ -2489,7 +2389,7 @@ void Item_field::set_field(Field *field_par) field_name= field_par->field_name; db_name= field_par->table->s->db.str; alias_name_used= field_par->table->alias_name_used; - unsigned_flag=test(field_par->flags & UNSIGNED_FLAG); + unsigned_flag= MY_TEST(field_par->flags & UNSIGNED_FLAG); collation.set(field_par->charset(), field_par->derivation(), field_par->repertoire()); fix_char_length(field_par->char_length()); @@ -2529,23 +2429,45 @@ bool Item_field::update_table_bitmaps_processor(uchar *arg) return FALSE; } +static inline void set_field_to_new_field(Field **field, Field **new_field) +{ + if (*field && (*field)->table == new_field[0]->table) + { + Field *newf= new_field[(*field)->field_index]; + if ((*field)->ptr == newf->ptr) + *field= newf; + } +} + +bool Item_field::switch_to_nullable_fields_processor(uchar *arg) +{ + Field **new_fields= (Field **)arg; + set_field_to_new_field(&field, new_fields); + set_field_to_new_field(&result_field, new_fields); + maybe_null= field && field->maybe_null(); + return 0; +} + const char *Item_ident::full_name() const { char *tmp; if (!table_name || !field_name) return field_name ? field_name : name ? name : "tmp_field"; + if (db_name && db_name[0]) { - tmp=(char*) sql_alloc((uint) strlen(db_name)+(uint) strlen(table_name)+ - (uint) strlen(field_name)+3); + THD *thd= current_thd; + tmp=(char*) thd->alloc((uint) strlen(db_name)+(uint) strlen(table_name)+ + (uint) strlen(field_name)+3); strxmov(tmp,db_name,".",table_name,".",field_name,NullS); } else { if (table_name[0]) { - tmp= (char*) sql_alloc((uint) strlen(table_name) + - (uint) strlen(field_name) + 2); + THD *thd= current_thd; + tmp= (char*) thd->alloc((uint) strlen(table_name) + + (uint) strlen(field_name) + 2); strxmov(tmp, table_name, ".", field_name, NullS); } else @@ -2585,7 +2507,14 @@ void Item_ident::print(String *str, enum_query_type query_type) } if (db_name && db_name[0] && !alias_name_used) { - if (!(cached_table && cached_table->belong_to_view && + /* + When printing EXPLAIN, don't print database name when it's the same as + current database. + */ + bool skip_db= (query_type & QT_ITEM_IDENT_SKIP_CURRENT_DATABASE) && + thd->db && !strcmp(thd->db, db_name); + if (!skip_db && + !(cached_table && cached_table->belong_to_view && cached_table->belong_to_view->compact_view_format)) { append_identifier(thd, str, d_name, (uint)strlen(d_name)); @@ -2706,28 +2635,8 @@ my_decimal *Item_field::val_decimal_result(my_decimal *decimal_value) bool Item_field::val_bool_result() { if ((null_value= result_field->is_null())) - return FALSE; - switch (result_field->result_type()) { - case INT_RESULT: - return result_field->val_int() != 0; - case DECIMAL_RESULT: - { - my_decimal decimal_value; - my_decimal *val= result_field->val_decimal(&decimal_value); - if (val) - return !my_decimal_is_zero(val); - return 0; - } - case REAL_RESULT: - case STRING_RESULT: - return result_field->val_real() != 0.0; - case ROW_RESULT: - case TIME_RESULT: - case IMPOSSIBLE_RESULT: - DBUG_ASSERT(0); - return 0; // Shut up compiler - } - return 0; + return false; + return result_field->val_bool(); } @@ -2739,11 +2648,11 @@ bool Item_field::is_null_result() bool Item_field::eq(const Item *item, bool binary_cmp) const { - Item *real_item= ((Item *) item)->real_item(); - if (real_item->type() != FIELD_ITEM) + Item *real_item2= ((Item *) item)->real_item(); + if (real_item2->type() != FIELD_ITEM) return 0; - Item_field *item_field= (Item_field*) real_item; + Item_field *item_field= (Item_field*) real_item2; if (item_field->field && field) return item_field->field == field; /* @@ -2779,7 +2688,8 @@ table_map Item_field::all_used_tables() const return (get_depended_from() ? OUTER_REF_TABLE_BIT : field->table->map); } -void Item_field::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_field::fix_after_pullout(st_select_lex *new_parent, Item **ref, + bool merge) { if (new_parent == get_depended_from()) depended_from= NULL; @@ -2823,6 +2733,19 @@ void Item_field::fix_after_pullout(st_select_lex *new_parent, Item **ref) if (!need_change) return; + if (!merge) + { + /* + It is transformation without merge. + This field was "outer" for the inner SELECT where it was taken and + moved up. + "Outer" fields uses normal SELECT_LEX context of upper SELECTs for + name resolution, so we can switch everything to it safely. + */ + this->context= &new_parent->context; + return; + } + Name_resolution_context *ctx= new Name_resolution_context(); if (context->select_lex == new_parent) { @@ -2859,7 +2782,7 @@ void Item_field::fix_after_pullout(st_select_lex *new_parent, Item **ref) Item *Item_field::get_tmp_table_item(THD *thd) { - Item_field *new_item= new Item_field(thd, this); + Item_field *new_item= new (thd->mem_root) Item_temptable_field(thd, this); if (new_item) new_item->field= new_item->result_field; return new_item; @@ -2877,7 +2800,8 @@ longlong Item_field::val_int_endpoint(bool left_endp, bool *incl_endp) This is always 'signed'. Unsigned values are created with Item_uint() */ -Item_int::Item_int(const char *str_arg, uint length) +Item_int::Item_int(THD *thd, const char *str_arg, uint length): + Item_num(thd) { char *end_ptr= (char*) str_arg + length; int error; @@ -2910,15 +2834,15 @@ void Item_int::print(String *str, enum_query_type query_type) } -Item_uint::Item_uint(const char *str_arg, uint length): - Item_int(str_arg, length) +Item_uint::Item_uint(THD *thd, const char *str_arg, uint length): + Item_int(thd, str_arg, length) { unsigned_flag= 1; } -Item_uint::Item_uint(const char *str_arg, longlong i, uint length): - Item_int(str_arg, i, length) +Item_uint::Item_uint(THD *thd, const char *str_arg, longlong i, uint length): + Item_int(thd, str_arg, i, length) { unsigned_flag= 1; } @@ -2941,8 +2865,9 @@ void Item_uint::print(String *str, enum_query_type query_type) } -Item_decimal::Item_decimal(const char *str_arg, uint length, - CHARSET_INFO *charset) +Item_decimal::Item_decimal(THD *thd, const char *str_arg, uint length, + CHARSET_INFO *charset): + Item_num(thd) { str2my_decimal(E_DEC_FATAL_ERROR, str_arg, length, charset, &decimal_value); name= (char*) str_arg; @@ -2954,7 +2879,8 @@ Item_decimal::Item_decimal(const char *str_arg, uint length, unsigned_flag); } -Item_decimal::Item_decimal(longlong val, bool unsig) +Item_decimal::Item_decimal(THD *thd, longlong val, bool unsig): + Item_num(thd) { int2my_decimal(E_DEC_FATAL_ERROR, val, unsig, &decimal_value); decimals= (uint8) decimal_value.frac; @@ -2966,7 +2892,8 @@ Item_decimal::Item_decimal(longlong val, bool unsig) } -Item_decimal::Item_decimal(double val, int precision, int scale) +Item_decimal::Item_decimal(THD *thd, double val, int precision, int scale): + Item_num(thd) { double2my_decimal(E_DEC_FATAL_ERROR, val, &decimal_value); decimals= (uint8) decimal_value.frac; @@ -2978,8 +2905,9 @@ Item_decimal::Item_decimal(double val, int precision, int scale) } -Item_decimal::Item_decimal(const char *str, const my_decimal *val_arg, - uint decimal_par, uint length) +Item_decimal::Item_decimal(THD *thd, const char *str, const my_decimal *val_arg, + uint decimal_par, uint length): + Item_num(thd) { my_decimal2decimal(val_arg, &decimal_value); name= (char*) str; @@ -2989,7 +2917,8 @@ Item_decimal::Item_decimal(const char *str, const my_decimal *val_arg, } -Item_decimal::Item_decimal(my_decimal *value_par) +Item_decimal::Item_decimal(THD *thd, my_decimal *value_par): + Item_num(thd) { my_decimal2decimal(value_par, &decimal_value); decimals= (uint8) decimal_value.frac; @@ -3001,7 +2930,8 @@ Item_decimal::Item_decimal(my_decimal *value_par) } -Item_decimal::Item_decimal(const uchar *bin, int precision, int scale) +Item_decimal::Item_decimal(THD *thd, const uchar *bin, int precision, int scale): + Item_num(thd) { binary2my_decimal(E_DEC_FATAL_ERROR, bin, &decimal_value, precision, scale); @@ -3070,11 +3000,18 @@ void Item_decimal::set_decimal_value(my_decimal *value_par) } +Item *Item_decimal::clone_item(THD *thd) +{ + return new (thd->mem_root) Item_decimal(thd, name, &decimal_value, decimals, + max_length); +} + + String *Item_float::val_str(String *str) { // following assert is redundant, because fixed=1 assigned in constructor DBUG_ASSERT(fixed == 1); - str->set_real(value,decimals,&my_charset_bin); + str->set_real(value, decimals, &my_charset_numeric); return str; } @@ -3088,6 +3025,13 @@ my_decimal *Item_float::val_decimal(my_decimal *decimal_value) } +Item *Item_float::clone_item(THD *thd) +{ + return new (thd->mem_root) Item_float(thd, name, value, decimals, + max_length); +} + + void Item_string::print(String *str, enum_query_type query_type) { const bool print_introducer= @@ -3121,25 +3065,7 @@ void Item_string::print(String *str, enum_query_type query_type) } else { - if (my_charset_same(str_value.charset(), system_charset_info)) - str_value.print(str); // already in system_charset_info - else // need to convert - { - THD *thd= current_thd; - LEX_STRING utf8_lex_str; - - thd->convert_string(&utf8_lex_str, - system_charset_info, - str_value.c_ptr_safe(), - str_value.length(), - str_value.charset()); - - String utf8_str(utf8_lex_str.str, - utf8_lex_str.length, - system_charset_info); - - utf8_str.print(str); - } + str_value.print(str, system_charset_info); } } else @@ -3152,66 +3078,10 @@ void Item_string::print(String *str, enum_query_type query_type) } -double -double_from_string_with_check(CHARSET_INFO *cs, const char *cptr, - const char *end) -{ - int error; - char *end_of_num= (char*) end; - double tmp; - - tmp= my_strntod(cs, (char*) cptr, end - cptr, &end_of_num, &error); - if (error || (end != end_of_num && - !check_if_only_end_space(cs, end_of_num, end))) - { - ErrConvString err(cptr, end - cptr, cs); - /* - We can use err.ptr() here as ErrConvString is guranteed to put an - end \0 here. - */ - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), "DOUBLE", - err.ptr()); - } - return tmp; -} - - double Item_string::val_real() { DBUG_ASSERT(fixed == 1); - return double_from_string_with_check(str_value.charset(), - str_value.ptr(), - str_value.ptr() + - str_value.length()); -} - - -longlong -longlong_from_string_with_check(CHARSET_INFO *cs, const char *cptr, - const char *end) -{ - int err; - longlong tmp; - char *end_of_num= (char*) end; - - tmp= (*(cs->cset->strtoll10))(cs, cptr, &end_of_num, &err); - /* - TODO: Give error if we wanted a signed integer and we got an unsigned - one - */ - if (!current_thd->no_errors && - (err > 0 || - (end != end_of_num && !check_if_only_end_space(cs, end_of_num, end)))) - { - ErrConvString err(cptr, end - cptr, cs); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), "INTEGER", - err.ptr()); - } - return tmp; + return double_from_string_with_check(&str_value); } @@ -3222,8 +3092,7 @@ longlong_from_string_with_check(CHARSET_INFO *cs, const char *cptr, longlong Item_string::val_int() { DBUG_ASSERT(fixed == 1); - return longlong_from_string_with_check(str_value.charset(), str_value.ptr(), - str_value.ptr()+ str_value.length()); + return longlong_from_string_with_check(&str_value); } @@ -3233,10 +3102,6 @@ my_decimal *Item_string::val_decimal(my_decimal *decimal_value) } -bool Item_null::eq(const Item *item, bool binary_cmp) const -{ return item->type() == type(); } - - double Item_null::val_real() { // following assert is redundant, because fixed=1 assigned in constructor @@ -3266,12 +3131,25 @@ my_decimal *Item_null::val_decimal(my_decimal *decimal_value) } -Item *Item_null::safe_charset_converter(CHARSET_INFO *tocs) +bool Item_null::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) +{ + // following assert is redundant, because fixed=1 assigned in constructor + DBUG_ASSERT(fixed == 1); + make_zero_date(ltime, fuzzydate); + return (null_value= true); +} + + +Item *Item_null::safe_charset_converter(THD *thd, CHARSET_INFO *tocs) { - collation.set(tocs); return this; } +Item *Item_null::clone_item(THD *thd) +{ + return new (thd->mem_root) Item_null(thd, name); +} + /*********************** Item_param related ******************************/ /** @@ -3288,15 +3166,15 @@ default_set_param_func(Item_param *param, } -Item_param::Item_param(uint pos_in_query_arg) : +Item_param::Item_param(THD *thd, uint pos_in_query_arg): + Item_basic_value(thd), + Rewritable_query_parameter(pos_in_query_arg, 1), state(NO_VALUE), item_result_type(STRING_RESULT), /* Don't pretend to be a literal unless value for this item is set. */ item_type(PARAM_ITEM), param_type(MYSQL_TYPE_VARCHAR), - pos_in_query(pos_in_query_arg), set_param_func(default_set_param_func), - limit_clause_param(FALSE), m_out_param_info(NULL) { name= (char*) "?"; @@ -3306,8 +3184,6 @@ Item_param::Item_param(uint pos_in_query_arg) : value is set. */ maybe_null= 1; - cnvitem= new Item_string("", 0, &my_charset_bin, DERIVATION_COERCIBLE); - cnvstr.set(cnvbuf, sizeof(cnvbuf), &my_charset_bin); } @@ -3412,14 +3288,10 @@ void Item_param::set_time(MYSQL_TIME *tm, timestamp_type time_type, value.time= *tm; value.time.time_type= time_type; - if (value.time.year > 9999 || value.time.month > 12 || - value.time.day > 31 || - (time_type != MYSQL_TIMESTAMP_TIME && value.time.hour > 23) || - value.time.minute > 59 || value.time.second > 59 || - value.time.second_part > TIME_MAX_SECOND_PART) + if (check_datetime_range(&value.time)) { ErrConvTime str(&value.time); - make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, &str, time_type, 0); set_zero_time(&value.time, MYSQL_TIMESTAMP_ERROR); } @@ -3523,7 +3395,7 @@ bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry) break; case STRING_RESULT: { - CHARSET_INFO *fromcs= entry->collation.collation; + CHARSET_INFO *fromcs= entry->charset(); CHARSET_INFO *tocs= thd->variables.collation_connection; uint32 dummy_offset; @@ -3563,7 +3435,6 @@ bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry) } case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); set_null(); } @@ -3668,17 +3539,14 @@ double Item_param::val_real() case STRING_VALUE: case LONG_DATA_VALUE: { - int dummy_err; - char *end_not_used; - return my_strntod(str_value.charset(), (char*) str_value.ptr(), - str_value.length(), &end_not_used, &dummy_err); + return double_from_string_with_check(&str_value); } case TIME_VALUE: /* This works for example when user says SELECT ?+0.0 and supplies time value for the placeholder. */ - return ulonglong2double(TIME_to_ulonglong(&value.time)); + return TIME_to_double(&value.time); case NULL_VALUE: return 0.0; default: @@ -3707,9 +3575,7 @@ longlong Item_param::val_int() case STRING_VALUE: case LONG_DATA_VALUE: { - int dummy_err; - return my_strntoll(str_value.charset(), str_value.ptr(), - str_value.length(), 10, (char**) 0, &dummy_err); + return longlong_from_string_with_check(&str_value); } case TIME_VALUE: return (longlong) TIME_to_ulonglong(&value.time); @@ -3735,13 +3601,10 @@ my_decimal *Item_param::val_decimal(my_decimal *dec) return dec; case STRING_VALUE: case LONG_DATA_VALUE: - string2my_decimal(E_DEC_FATAL_ERROR, &str_value, dec); - return dec; + return decimal_from_string_with_check(dec, &str_value); case TIME_VALUE: { - longlong i= (longlong) TIME_to_ulonglong(&value.time); - int2my_decimal(E_DEC_FATAL_ERROR, i, 0, dec); - return dec; + return TIME_to_my_decimal(&value.time, dec); } case NULL_VALUE: return 0; @@ -3835,8 +3698,9 @@ const String *Item_param::query_val_str(THD *thd, String* str) const case LONG_DATA_VALUE: { str->length(0); - append_query_string(thd, value.cs_info.character_set_client, &str_value, - str); + append_query_string(value.cs_info.character_set_client, str, + str_value.ptr(), str_value.length(), + thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES); break; } case NULL_VALUE: @@ -3875,18 +3739,14 @@ bool Item_param::convert_str_value(THD *thd) str_value.set_charset(value.cs_info.final_character_set_of_str_value); /* Here str_value is guaranteed to be in final_character_set_of_str_value */ - max_length= str_value.numchars() * str_value.charset()->mbmaxlen; - - /* For the strings converted to numeric form within some functions */ - decimals= NOT_FIXED_DEC; /* str_value_ptr is returned from val_str(). It must be not alloced to prevent it's modification by val_str() invoker. */ str_value_ptr.set(str_value.ptr(), str_value.length(), str_value.charset()); - /* Synchronize item charset with value charset */ - collation.set(str_value.charset(), DERIVATION_COERCIBLE); + /* Synchronize item charset and length with value charset */ + fix_charset_and_length_from_str_value(DERIVATION_COERCIBLE); } return rc; } @@ -3900,23 +3760,28 @@ bool Item_param::basic_const_item() const } +/* see comments in the header file */ + Item * -Item_param::clone_item() +Item_param::clone_item(THD *thd) { - /* see comments in the header file */ + MEM_ROOT *mem_root= thd->mem_root; switch (state) { case NULL_VALUE: - return new Item_null(name); + return new (mem_root) Item_null(thd, name); case INT_VALUE: return (unsigned_flag ? - new Item_uint(name, value.integer, max_length) : - new Item_int(name, value.integer, max_length)); + new (mem_root) Item_uint(thd, name, value.integer, max_length) : + new (mem_root) Item_int(thd, name, value.integer, max_length)); case REAL_VALUE: - return new Item_float(name, value.real, decimals, max_length); + return new (mem_root) Item_float(thd, name, value.real, decimals, + max_length); case STRING_VALUE: case LONG_DATA_VALUE: - return new Item_string(name, str_value.c_ptr_quick(), str_value.length(), - str_value.charset()); + return new (mem_root) Item_string(thd, name, str_value.c_ptr_quick(), + str_value.length(), str_value.charset(), + collation.derivation, + collation.repertoire); case TIME_VALUE: break; case NO_VALUE: @@ -3928,30 +3793,21 @@ Item_param::clone_item() bool -Item_param::eq(const Item *arg, bool binary_cmp) const +Item_param::eq(const Item *item, bool binary_cmp) const { - Item *item; - if (!basic_const_item() || !arg->basic_const_item() || arg->type() != type()) + if (!basic_const_item()) return FALSE; - /* - We need to cast off const to call val_int(). This should be OK for - a basic constant. - */ - item= (Item*) arg; switch (state) { case NULL_VALUE: - return TRUE; + return null_eq(item); case INT_VALUE: - return value.integer == item->val_int() && - unsigned_flag == item->unsigned_flag; + return int_eq(value.integer, item); case REAL_VALUE: - return value.real == item->val_real(); + return real_eq(value.real, item); case STRING_VALUE: case LONG_DATA_VALUE: - if (binary_cmp) - return !stringcmp(&str_value, &item->str_value); - return !sortcmp(&str_value, &item->str_value, collation.collation); + return str_eq(&str_value, item, binary_cmp); default: break; } @@ -4001,17 +3857,14 @@ void Item_param::print(String *str, enum_query_type query_type) void Item_param::set_param_type_and_swap_value(Item_param *src) { - unsigned_flag= src->unsigned_flag; + Type_std_attributes::set(src); param_type= src->param_type; set_param_func= src->set_param_func; item_type= src->item_type; item_result_type= src->item_result_type; - collation.set(src->collation); maybe_null= src->maybe_null; null_value= src->null_value; - max_length= src->max_length; - decimals= src->decimals; state= src->state; value= src->value; @@ -4169,6 +4022,13 @@ void Item_param::make_field(Send_field *field) field->type= m_out_param_info->type; } +bool Item_param::append_for_log(THD *thd, String *str) +{ + StringBuffer<STRING_BUFFER_USUAL_SIZE> buf; + const String *val= query_val_str(thd, &buf); + return str->append(*val); +} + /**************************************************************************** Item_copy_string @@ -4186,8 +4046,8 @@ double Item_copy_string::val_real() longlong Item_copy_string::val_int() { int err; - return null_value ? LL(0) : my_strntoll(str_value.charset(),str_value.ptr(), - str_value.length(),10, (char**) 0, + return null_value ? 0 : my_strntoll(str_value.charset(),str_value.ptr(), + str_value.length(), 10, (char**) 0, &err); } @@ -4333,12 +4193,13 @@ static bool mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current, resolved_item->db_name : ""); const char *table_name= (resolved_item->table_name ? resolved_item->table_name : ""); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_WARN_FIELD_RESOLVED, ER(ER_WARN_FIELD_RESOLVED), - db_name, (db_name[0] ? "." : ""), - table_name, (table_name [0] ? "." : ""), - resolved_item->field_name, - current->select_number, last->select_number); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_WARN_FIELD_RESOLVED, + ER_THD(thd,ER_WARN_FIELD_RESOLVED), + db_name, (db_name[0] ? "." : ""), + table_name, (table_name [0] ? "." : ""), + resolved_item->field_name, + current->select_number, last->select_number); } DBUG_RETURN(FALSE); } @@ -4595,9 +4456,10 @@ resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select) !((*group_by_ref)->eq(*select_ref, 0))) { ambiguous_fields= TRUE; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_NON_UNIQ_ERROR, - ER(ER_NON_UNIQ_ERROR), ref->full_name(), - current_thd->where); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NON_UNIQ_ERROR, + ER_THD(thd,ER_NON_UNIQ_ERROR), ref->full_name(), + thd->where); } } @@ -4719,7 +4581,7 @@ bool is_outer_table(TABLE_LIST *table, SELECT_LEX *select) @retval 0 column fully fixed and fix_fields() should return FALSE @retval - -1 error occured + -1 error occurred */ int @@ -4806,7 +4668,7 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) if (select->join) { marker= select->cur_pos_in_select_list; - select->join->non_agg_fields.push_back(this); + select->join->non_agg_fields.push_back(this, thd->mem_root); } else { @@ -4842,10 +4704,10 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) fix_inner_refs() function. */ ; - if (!(rf= new Item_outer_ref(context, this))) + if (!(rf= new (thd->mem_root) Item_outer_ref(thd, context, this))) return -1; thd->change_item_tree(reference, rf); - select->inner_refs_list.push_back(rf); + select->inner_refs_list.push_back(rf, thd->mem_root); rf->in_sum_func= thd->lex->in_sum_func; } /* @@ -4872,10 +4734,7 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) else { Item::Type ref_type= (*reference)->type(); - prev_subselect_item->used_tables_cache|= - (*reference)->used_tables(); - prev_subselect_item->const_item_cache&= - (*reference)->const_item(); + prev_subselect_item->used_tables_and_const_cache_join(*reference); mark_as_dependent(thd, last_checked_context->select_lex, context->select_lex, this, ((ref_type == REF_ITEM || ref_type == FIELD_ITEM) ? @@ -4907,8 +4766,7 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) if (ref != not_found_item) { DBUG_ASSERT(*ref && (*ref)->fixed); - prev_subselect_item->used_tables_cache|= (*ref)->used_tables(); - prev_subselect_item->const_item_cache&= (*ref)->const_item(); + prev_subselect_item->used_tables_and_const_cache_join(*ref); break; } } @@ -4959,20 +4817,24 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) save= *ref; *ref= NULL; // Don't call set_properties() rf= (place == IN_HAVING ? - new Item_ref(context, ref, (char*) table_name, - (char*) field_name, alias_name_used) : + new (thd->mem_root) + Item_ref(thd, context, ref, (char*) table_name, + (char*) field_name, alias_name_used) : (!select->group_list.elements ? - new Item_direct_ref(context, ref, (char*) table_name, - (char*) field_name, alias_name_used) : - new Item_outer_ref(context, ref, (char*) table_name, - (char*) field_name, alias_name_used))); + new (thd->mem_root) + Item_direct_ref(thd, context, ref, (char*) table_name, + (char*) field_name, alias_name_used) : + new (thd->mem_root) + Item_outer_ref(thd, context, ref, (char*) table_name, + (char*) field_name, alias_name_used))); *ref= save; if (!rf) return -1; if (place != IN_HAVING && select->group_list.elements) { - outer_context->select_lex->inner_refs_list.push_back((Item_outer_ref*)rf); + outer_context->select_lex->inner_refs_list.push_back((Item_outer_ref*)rf, + thd->mem_root); ((Item_outer_ref*)rf)->in_sum_func= thd->lex->in_sum_func; } thd->change_item_tree(reference, rf); @@ -4984,6 +4846,11 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) if (rf->fix_fields(thd, reference) || rf->check_cols(1)) return -1; + /* + We can not "move" aggregate function in the place where + its arguments are not defined. + */ + set_max_sum_func_level(thd, select); mark_as_dependent(thd, last_checked_context->select_lex, context->select_lex, rf, rf); @@ -4992,13 +4859,18 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) } else { + /* + We can not "move" aggregate function in the place where + its arguments are not defined. + */ + set_max_sum_func_level(thd, select); mark_as_dependent(thd, last_checked_context->select_lex, context->select_lex, this, (Item_ident*)*reference); if (last_checked_context->select_lex->having_fix_field) { Item_ref *rf; - rf= new Item_ref(context, (*from_field)->table->s->db.str, + rf= new (thd->mem_root) Item_ref(thd, context, (*from_field)->table->s->db.str, (*from_field)->table->alias.c_ptr(), (char*) field_name); if (!rf) @@ -5094,7 +4966,8 @@ bool Item_field::fix_fields(THD *thd, Item **reference) { uint counter; enum_resolution_type resolution; - Item** res= find_item_in_list(this, thd->lex->current_select->item_list, + Item** res= find_item_in_list(this, + thd->lex->current_select->item_list, &counter, REPORT_EXCEPT_NOT_FOUND, &resolution); if (!res) @@ -5118,10 +4991,15 @@ bool Item_field::fix_fields(THD *thd, Item **reference) { /* The column to which we link isn't valid. */ my_error(ER_BAD_FIELD_ERROR, MYF(0), (*res)->name, - current_thd->where); + thd->where); return(1); } + /* + We can not "move" aggregate function in the place where + its arguments are not defined. + */ + set_max_sum_func_level(thd, thd->lex->current_select); set_field(new_field); return 0; } @@ -5132,11 +5010,13 @@ bool Item_field::fix_fields(THD *thd, Item **reference) Item_ref to point to the Item in the select list and replace the Item_field created by the parser with the new Item_ref. */ - Item_ref *rf= new Item_ref(context, db_name,table_name,field_name); + Item_ref *rf= new (thd->mem_root) + Item_ref(thd, context, db_name, table_name, + field_name); if (!rf) return 1; - bool ret= rf->fix_fields(thd, (Item **) &rf) || rf->check_cols(1); - if (ret) + bool err= rf->fix_fields(thd, (Item **) &rf) || rf->check_cols(1); + if (err) return TRUE; SELECT_LEX *select= thd->lex->current_select; @@ -5144,6 +5024,11 @@ bool Item_field::fix_fields(THD *thd, Item **reference) select->parsing_place == IN_GROUP_BY && alias_name_used ? *rf->ref : rf); + /* + We can not "move" aggregate function in the place where + its arguments are not defined. + */ + set_max_sum_func_level(thd, thd->lex->current_select); return FALSE; } } @@ -5245,7 +5130,7 @@ bool Item_field::fix_fields(THD *thd, Item **reference) thd->lex->current_select->cur_pos_in_select_list != UNDEF_POS && thd->lex->current_select->join) { - thd->lex->current_select->join->non_agg_fields.push_back(this); + thd->lex->current_select->join->non_agg_fields.push_back(this, thd->mem_root); marker= thd->lex->current_select->cur_pos_in_select_list; } mark_non_agg_field: @@ -5281,7 +5166,7 @@ mark_non_agg_field: else { if (outer_fixed) - thd->lex->in_sum_func->outer_fields.push_back(this); + thd->lex->in_sum_func->outer_fields.push_back(this, thd->mem_root); else if (thd->lex->in_sum_func->nest_level != thd->lex->current_select->nest_level) select_lex->set_non_agg_field_used(true); @@ -5310,13 +5195,6 @@ bool Item_field::vcol_in_partition_func_processor(uchar *int_arg) } -Item *Item_field::safe_charset_converter(CHARSET_INFO *tocs) -{ - no_const_subst= 1; - return Item::safe_charset_converter(tocs); -} - - void Item_field::cleanup() { DBUG_ENTER("Item_field::cleanup"); @@ -5327,7 +5205,7 @@ void Item_field::cleanup() it will be linked correctly next time by name of field and table alias. I.e. we can drop 'field'. */ - field= result_field= 0; + field= 0; item_equal= NULL; null_value= FALSE; DBUG_VOID_RETURN; @@ -5373,75 +5251,6 @@ Item_equal *Item_field::find_item_equal(COND_EQUAL *cond_equal) /** - Check whether a field item can be substituted for an equal item - - @details - The function checks whether a substitution of a field item for - an equal item is valid. - - @param arg *arg != NULL <-> the field is in the context - where substitution for an equal item is valid - - @note - The following statement is not always true: - @n - x=y => F(x)=F(x/y). - @n - This means substitution of an item for an equal item not always - yields an equavalent condition. Here's an example: - @code - 'a'='a ' - (LENGTH('a')=1) != (LENGTH('a ')=2) - @endcode - Such a substitution is surely valid if either the substituted - field is not of a STRING type or if it is an argument of - a comparison predicate. - - @retval - TRUE substitution is valid - @retval - FALSE otherwise -*/ - -bool Item_field::subst_argument_checker(uchar **arg) -{ - return *arg && - (*arg == (uchar *) Item::ANY_SUBST || - result_type() != STRING_RESULT || - (field->flags & BINARY_FLAG)); -} - - -/** - Convert a numeric value to a zero-filled string - - @param[in,out] item the item to operate on - @param field The field that this value is equated to - - This function converts a numeric value to a string. In this conversion - the zero-fill flag of the field is taken into account. - This is required so the resulting string value can be used instead of - the field reference when propagating equalities. -*/ - -static void convert_zerofill_number_to_string(Item **item, Field_num *field) -{ - char buff[MAX_FIELD_WIDTH],*pos; - String tmp(buff,sizeof(buff), field->charset()), *res; - - res= (*item)->val_str(&tmp); - if ((*item)->is_null()) - *item= new Item_null(); - else - { - field->prepend_zeros(res); - pos= (char *) sql_strmake (res->ptr(), res->length()); - *item= new Item_string(pos, res->length(), field->charset()); - } -} - - -/** Set a pointer to the multiple equality the field reference belongs to (if any). @@ -5465,47 +5274,44 @@ static void convert_zerofill_number_to_string(Item **item, Field_num *field) - pointer to the field item, otherwise. */ -Item *Item_field::equal_fields_propagator(uchar *arg) +Item *Item_field::propagate_equal_fields(THD *thd, + const Context &ctx, + COND_EQUAL *arg) { - if (no_const_subst) + if (!(item_equal= find_item_equal(arg))) return this; - item_equal= find_item_equal((COND_EQUAL *) arg); - Item *item= 0; - if (item_equal) - item= item_equal->get_const(); - /* - Disable const propagation for items used in different comparison contexts. - This must be done because, for example, Item_hex_string->val_int() is not - the same as (Item_hex_string->val_str() in BINARY column)->val_int(). - We cannot simply disable the replacement in a particular context ( - e.g. <bin_col> = <int_col> AND <bin_col> = <hex_string>) since - Items don't know the context they are in and there are functions like - IF (<hex_string>, 'yes', 'no'). - */ - if (!item || !has_compatible_context(item)) - item= this; - else if (field && (field->flags & ZEROFILL_FLAG) && IS_NUM(field->type())) + if (!field->can_be_substituted_to_equal_item(ctx, item_equal)) { - if (item && (cmp_context == STRING_RESULT || cmp_context == IMPOSSIBLE_RESULT)) - convert_zerofill_number_to_string(&item, (Field_num *)field); - else - item= this; + item_equal= NULL; + return this; } - return item; -} - - -/** - Mark the item to not be part of substitution if it's not a binary item. - - See comments in Arg_comparator::set_compare_func() for details. -*/ + Item *item= item_equal->get_const(); + if (!item) + { + /* + The found Item_equal is Okey, but it does not have a constant + item yet. Keep this->item_equal point to the found Item_equal. + */ + return this; + } + if (!(item= field->get_equal_const_item(thd, ctx, item))) + { + /* + Could not do safe conversion from the original constant item + to a field-compatible constant item. + For example, we tried to optimize: + WHERE date_column=' garbage ' AND LENGTH(date_column)=8; + to + WHERE date_column=' garbage ' AND LENGTH(DATE'XXXX-YY-ZZ'); + but failed to create a valid DATE literal from the given string literal. -bool Item_field::set_no_const_sub(uchar *arg) -{ - if (field->charset() != &my_charset_bin) - no_const_subst=1; - return FALSE; + Do not do constant propagation in such cases and unlink + "this" from the found Item_equal (as this equality not usefull). + */ + item_equal= NULL; + return this; + } + return item; } @@ -5535,17 +5341,28 @@ bool Item_field::set_no_const_sub(uchar *arg) - this - otherwise. */ -Item *Item_field::replace_equal_field(uchar *arg) +Item *Item_field::replace_equal_field(THD *thd, uchar *arg) { REPLACE_EQUAL_FIELD_ARG* param= (REPLACE_EQUAL_FIELD_ARG*)arg; if (item_equal && item_equal == param->item_equal) { - Item *const_item= item_equal->get_const(); - if (const_item) + Item *const_item2= item_equal->get_const(); + if (const_item2) { - if (!has_compatible_context(const_item)) - return this; - return const_item; + /* + Currently we don't allow to create Item_equal with compare_type() + different from its Item_field's cmp_type(). + Field_xxx::test_if_equality_guarantees_uniqueness() prevent this. + Also, Item_field::propagate_equal_fields() does not allow to assign + this->item_equal to any instances of Item_equal if "this" is used + in a non-native comparison context, or with an incompatible collation. + So the fact that we have (item_equal != NULL) means that the currently + processed function (the owner of "this") uses the field in its native + comparison context, and it's safe to replace it to the constant from + item_equal. + */ + DBUG_ASSERT(cmp_type() == item_equal->compare_type()); + return const_item2; } Item_field *subst= (Item_field *)(item_equal->get_first(param->context_tab, this)); @@ -5584,17 +5401,6 @@ void Item::make_field(Send_field *tmp_field) } -enum_field_types Item::string_field_type() const -{ - enum_field_types f_type= MYSQL_TYPE_VAR_STRING; - if (max_length >= 16777216) - f_type= MYSQL_TYPE_LONG_BLOB; - else if (max_length >= 65536) - f_type= MYSQL_TYPE_MEDIUM_BLOB; - return f_type; -} - - void Item_empty_string::make_field(Send_field *tmp_field) { init_make_field(tmp_field, string_field_type()); @@ -5610,7 +5416,6 @@ enum_field_types Item::field_type() const case REAL_RESULT: return MYSQL_TYPE_DOUBLE; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); return MYSQL_TYPE_VARCHAR; } @@ -5634,10 +5439,7 @@ String *Item::check_well_formed_result(String *str, bool send_error) { /* Check whether we got a well-formed string */ CHARSET_INFO *cs= str->charset(); - int well_formed_error; - uint wlen= cs->cset->well_formed_len(cs, - str->ptr(), str->ptr() + str->length(), - str->length(), &well_formed_error); + uint wlen= str->well_formed_length(); null_value= false; if (wlen < str->length()) { @@ -5652,8 +5454,7 @@ String *Item::check_well_formed_result(String *str, bool send_error) cs->csname, hexbuf); return 0; } - if ((thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))) + if (thd->is_strict_mode()) { null_value= 1; str= 0; @@ -5662,12 +5463,53 @@ String *Item::check_well_formed_result(String *str, bool send_error) { str->length(wlen); } - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_INVALID_CHARACTER_STRING, - ER(ER_INVALID_CHARACTER_STRING), cs->csname, hexbuf); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_INVALID_CHARACTER_STRING, + ER_THD(thd, ER_INVALID_CHARACTER_STRING), cs->csname, + hexbuf); } return str; } + +/** + Copy a string with optional character set conversion. +*/ +bool +String_copier_for_item::copy_with_warn(CHARSET_INFO *dstcs, String *dst, + CHARSET_INFO *srccs, const char *src, + uint32 src_length, uint32 nchars) +{ + if ((dst->copy(dstcs, srccs, src, src_length, nchars, this))) + return true; // EOM + if (const char *pos= well_formed_error_pos()) + { + ErrConvString err(pos, src_length - (pos - src), &my_charset_bin); + push_warning_printf(m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_INVALID_CHARACTER_STRING, + ER_THD(m_thd, ER_INVALID_CHARACTER_STRING), + srccs == &my_charset_bin ? + dstcs->csname : srccs->csname, + err.ptr()); + return false; + } + if (const char *pos= cannot_convert_error_pos()) + { + char buf[16]; + int mblen= srccs->cset->charlen(srccs, (const uchar *) pos, + (const uchar *) src + src_length); + DBUG_ASSERT(mblen > 0 && mblen * 2 + 1 <= (int) sizeof(buf)); + octet2hex(buf, pos, mblen); + push_warning_printf(m_thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANNOT_CONVERT_CHARACTER, + ER_THD(m_thd, ER_CANNOT_CONVERT_CHARACTER), + srccs->csname, buf, dstcs->csname); + return false; + } + return false; +} + + /* Compare two items using a given collation @@ -5725,22 +5567,26 @@ bool Item::eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs) Field *Item::make_string_field(TABLE *table) { Field *field; + MEM_ROOT *mem_root= table->in_use->mem_root; + DBUG_ASSERT(collation.collation); /* Note: the following check is repeated in subquery_types_allow_materialization(): */ if (too_big_for_varchar()) - field= new Field_blob(max_length, maybe_null, name, - collation.collation, TRUE); + field= new (mem_root) + Field_blob(max_length, maybe_null, name, + collation.collation, TRUE); /* Item_type_holder holds the exact type, do not change it */ else if (max_length > 0 && (type() != Item::TYPE_HOLDER || field_type() != MYSQL_TYPE_STRING)) - field= new Field_varstring(max_length, maybe_null, name, table->s, - collation.collation); + field= new (mem_root) + Field_varstring(max_length, maybe_null, name, table->s, + collation.collation); else - field= new Field_string(max_length, maybe_null, name, - collation.collation); + field= new (mem_root) + Field_string(max_length, maybe_null, name, collation.collation); if (field) field->init(table); return field; @@ -5759,83 +5605,94 @@ Field *Item::make_string_field(TABLE *table) \# Created field */ -Field *Item::tmp_table_field_from_field_type(TABLE *table, bool fixed_length) +Field *Item::tmp_table_field_from_field_type(TABLE *table, + bool fixed_length, + bool set_blob_packlength) { /* The field functions defines a field to be not null if null_ptr is not 0 */ uchar *null_ptr= maybe_null ? (uchar*) "" : 0; Field *field; + MEM_ROOT *mem_root= table->in_use->mem_root; switch (field_type()) { case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_NEWDECIMAL: - field= Field_new_decimal::create_from_item(this); + field= Field_new_decimal::create_from_item(mem_root, this); break; case MYSQL_TYPE_TINY: - field= new Field_tiny((uchar*) 0, max_length, null_ptr, 0, Field::NONE, - name, 0, unsigned_flag); + field= new (mem_root) + Field_tiny((uchar*) 0, max_length, null_ptr, 0, Field::NONE, + name, 0, unsigned_flag); break; case MYSQL_TYPE_SHORT: - field= new Field_short((uchar*) 0, max_length, null_ptr, 0, Field::NONE, - name, 0, unsigned_flag); + field= new (mem_root) + Field_short((uchar*) 0, max_length, null_ptr, 0, Field::NONE, + name, 0, unsigned_flag); break; case MYSQL_TYPE_LONG: - field= new Field_long((uchar*) 0, max_length, null_ptr, 0, Field::NONE, - name, 0, unsigned_flag); + field= new (mem_root) + Field_long((uchar*) 0, max_length, null_ptr, 0, Field::NONE, + name, 0, unsigned_flag); break; #ifdef HAVE_LONG_LONG case MYSQL_TYPE_LONGLONG: - field= new Field_longlong((uchar*) 0, max_length, null_ptr, 0, Field::NONE, - name, 0, unsigned_flag); + field= new (mem_root) + Field_longlong((uchar*) 0, max_length, null_ptr, 0, Field::NONE, + name, 0, unsigned_flag); break; #endif case MYSQL_TYPE_FLOAT: - field= new Field_float((uchar*) 0, max_length, null_ptr, 0, Field::NONE, - name, decimals, 0, unsigned_flag); + field= new (mem_root) + Field_float((uchar*) 0, max_length, null_ptr, 0, Field::NONE, + name, decimals, 0, unsigned_flag); break; case MYSQL_TYPE_DOUBLE: - field= new Field_double((uchar*) 0, max_length, null_ptr, 0, Field::NONE, - name, decimals, 0, unsigned_flag); + field= new (mem_root) + Field_double((uchar*) 0, max_length, null_ptr, 0, Field::NONE, + name, decimals, 0, unsigned_flag); break; case MYSQL_TYPE_INT24: - field= new Field_medium((uchar*) 0, max_length, null_ptr, 0, Field::NONE, - name, 0, unsigned_flag); + field= new (mem_root) + Field_medium((uchar*) 0, max_length, null_ptr, 0, Field::NONE, + name, 0, unsigned_flag); break; case MYSQL_TYPE_NEWDATE: case MYSQL_TYPE_DATE: - field= new Field_newdate(0, null_ptr, 0, Field::NONE, name, &my_charset_bin); + field= new (mem_root) + Field_newdate(0, null_ptr, 0, Field::NONE, name); break; case MYSQL_TYPE_TIME: - field= new_Field_time(0, null_ptr, 0, Field::NONE, name, - decimals, &my_charset_bin); + field= new_Field_time(mem_root, 0, null_ptr, 0, Field::NONE, name, + decimals); break; case MYSQL_TYPE_TIMESTAMP: - field= new_Field_timestamp(0, null_ptr, 0, - Field::NONE, name, 0, decimals, &my_charset_bin); + field= new_Field_timestamp(mem_root, 0, null_ptr, 0, + Field::NONE, name, 0, decimals); break; case MYSQL_TYPE_DATETIME: - field= new_Field_datetime(0, null_ptr, 0, Field::NONE, name, - decimals, &my_charset_bin); + field= new_Field_datetime(mem_root, 0, null_ptr, 0, Field::NONE, name, + decimals); break; case MYSQL_TYPE_YEAR: - field= new Field_year((uchar*) 0, max_length, null_ptr, 0, Field::NONE, - name); + field= new (mem_root) + Field_year((uchar*) 0, max_length, null_ptr, 0, Field::NONE, name); break; case MYSQL_TYPE_BIT: - field= new Field_bit_as_char(NULL, max_length, null_ptr, 0, - Field::NONE, name); + field= new (mem_root) + Field_bit_as_char(NULL, max_length, null_ptr, 0, Field::NONE, name); break; default: /* This case should never be chosen */ DBUG_ASSERT(0); /* If something goes awfully wrong, it's better to get a string than die */ - case MYSQL_TYPE_STRING: case MYSQL_TYPE_NULL: + case MYSQL_TYPE_STRING: if (fixed_length && !too_big_for_varchar()) { - field= new Field_string(max_length, maybe_null, name, - collation.collation); + field= new (mem_root) + Field_string(max_length, maybe_null, name, collation.collation); break; } /* fall through */ @@ -5848,16 +5705,14 @@ Field *Item::tmp_table_field_from_field_type(TABLE *table, bool fixed_length) case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: - if (this->type() == Item::TYPE_HOLDER) - field= new Field_blob(max_length, maybe_null, name, collation.collation, - 1); - else - field= new Field_blob(max_length, maybe_null, name, collation.collation); + field= new (mem_root) + Field_blob(max_length, maybe_null, name, + collation.collation, set_blob_packlength); break; // Blob handled outside of case #ifdef HAVE_SPATIAL case MYSQL_TYPE_GEOMETRY: - field= new Field_geom(max_length, maybe_null, - name, table->s, get_geometry_type()); + field= new (mem_root) + Field_geom(max_length, maybe_null, name, table->s, get_geometry_type()); #endif /* HAVE_SPATIAL */ } if (field) @@ -5931,13 +5786,51 @@ static int save_field_in_field(Field *from, bool *null_value, } +static int memcpy_field_value(Field *to, Field *from) +{ + if (to->ptr != from->ptr) + memcpy(to->ptr,from->ptr, to->pack_length()); + return 0; +} + +fast_field_copier Item_field::setup_fast_field_copier(Field *to) +{ + DBUG_ENTER("Item_field::setup_fast_field_copier"); + DBUG_RETURN(memcpy_field_possible(to, field) ? + &memcpy_field_value : + &field_conv_incompatible); +} + + /** Set a field's value from a item. */ -void Item_field::save_org_in_field(Field *to) +void Item_field::save_org_in_field(Field *to, + fast_field_copier fast_field_copier_func) { - save_field_in_field(field, &null_value, to, TRUE); + DBUG_ENTER("Item_field::save_org_in_field"); + DBUG_PRINT("enter", ("setup: 0x%lx data: 0x%lx", + (ulong) to, (ulong) fast_field_copier_func)); + if (fast_field_copier_func) + { + if (field->is_null()) + { + null_value= TRUE; + set_field_to_null_with_conversions(to, TRUE); + DBUG_VOID_RETURN; + } + to->set_notnull(); + if (to == field) + { + null_value= 0; + DBUG_VOID_RETURN; + } + (*fast_field_copier_func)(to, field); + } + else + save_field_in_field(field, &null_value, to, TRUE); + DBUG_VOID_RETURN; } @@ -6052,6 +5945,14 @@ int Item_string::save_in_field(Field *field, bool no_conversions) } +Item *Item_string::clone_item(THD *thd) +{ + return new (thd->mem_root) + Item_string(thd, name, str_value.ptr(), + str_value.length(), collation.collation); +} + + static int save_int_value_in_field (Field *field, longlong nr, bool null_value, bool unsigned_flag) { @@ -6068,6 +5969,12 @@ int Item_int::save_in_field(Field *field, bool no_conversions) } +Item *Item_int::clone_item(THD *thd) +{ + return new (thd->mem_root) Item_int(thd, name, value, max_length); +} + + void Item_datetime::set(longlong packed) { unpack_time(packed, <ime); @@ -6091,25 +5998,7 @@ int Item_decimal::save_in_field(Field *field, bool no_conversions) } -bool Item_int::eq(const Item *arg, bool binary_cmp) const -{ - /* No need to check for null value as basic constant can't be NULL */ - if (arg->basic_const_item() && arg->type() == type()) - { - /* - We need to cast off const to call val_int(). This should be OK for - a basic constant. - */ - Item *item= (Item*) arg; - return (item->val_int() == value && - ((longlong) value >= 0 || - (item->unsigned_flag == unsigned_flag))); - } - return FALSE; -} - - -Item *Item_int_with_ref::clone_item() +Item *Item_int_with_ref::clone_item(THD *thd) { DBUG_ASSERT(ref->const_item()); /* @@ -6117,18 +6006,25 @@ Item *Item_int_with_ref::clone_item() parameter markers. */ return (ref->unsigned_flag ? - new Item_uint(ref->name, ref->val_int(), ref->max_length) : - new Item_int(ref->name, ref->val_int(), ref->max_length)); + new (thd->mem_root) + Item_uint(thd, ref->name, ref->val_int(), ref->max_length) : + new (thd->mem_root) + Item_int(thd, ref->name, ref->val_int(), ref->max_length)); } -Item_num *Item_uint::neg() +Item_num *Item_uint::neg(THD *thd) { - Item_decimal *item= new Item_decimal(value, 1); - return item->neg(); + Item_decimal *item= new (thd->mem_root) Item_decimal(thd, value, 1); + return item->neg(thd); } +Item *Item_uint::clone_item(THD *thd) +{ + return new (thd->mem_root) Item_uint(thd, name, value, max_length); +} + static uint nr_of_decimals(const char *str, const char *end) { const char *decimal_point; @@ -6183,7 +6079,8 @@ static uint nr_of_decimals(const char *str, const char *end) Item->name should be fixed to use LEX_STRING eventually. */ -Item_float::Item_float(const char *str_arg, uint length) +Item_float::Item_float(THD *thd, const char *str_arg, uint length): + Item_num(thd) { int error; char *end_not_used; @@ -6226,27 +6123,6 @@ void Item_float::print(String *str, enum_query_type query_type) } -/* - hex item - In string context this is a binary string. - In number context this is a longlong value. -*/ - -bool Item_float::eq(const Item *arg, bool binary_cmp) const -{ - if (arg->basic_const_item() && arg->type() == type()) - { - /* - We need to cast off const to call val_int(). This should be OK for - a basic constant. - */ - Item *item= (Item*) arg; - return item->val_real() == value; - } - return FALSE; -} - - inline uint char_val(char X) { return (uint) (X >= '0' && X <= '9' ? X-'0' : @@ -6255,10 +6131,11 @@ inline uint char_val(char X) } -void Item_hex_constant::hex_string_init(const char *str, uint str_length) +void Item_hex_constant::hex_string_init(THD *thd, const char *str, + uint str_length) { max_length=(str_length+1)/2; - char *ptr=(char*) sql_alloc(max_length+1); + char *ptr=(char*) thd->alloc(max_length+1); if (!ptr) { str_value.set("", 0, &my_charset_bin); @@ -6284,7 +6161,7 @@ longlong Item_hex_hybrid::val_int() // following assert is redundant, because fixed=1 assigned in constructor DBUG_ASSERT(fixed == 1); char *end=(char*) str_value.ptr()+str_value.length(), - *ptr=end-min(str_value.length(),sizeof(longlong)); + *ptr=end-MY_MIN(str_value.length(),sizeof(longlong)); ulonglong value=0; for (; ptr != end ; ptr++) @@ -6302,8 +6179,6 @@ int Item_hex_hybrid::save_in_field(Field *field, bool no_conversions) ulonglong nr; uint32 length= str_value.length(); - if (!length) - return 1; if (length > 8) { @@ -6320,7 +6195,7 @@ int Item_hex_hybrid::save_in_field(Field *field, bool no_conversions) warn: if (!field->store((longlong) nr, TRUE)) - field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, + field->set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); return 1; } @@ -6328,7 +6203,7 @@ warn: void Item_hex_hybrid::print(String *str, enum_query_type query_type) { - uint32 len= min(str_value.length(), sizeof(longlong)); + uint32 len= MY_MIN(str_value.length(), sizeof(longlong)); const char *ptr= str_value.ptr() + str_value.length() - len; str->append("0x"); str->append_hex(ptr, len); @@ -6343,47 +6218,22 @@ void Item_hex_string::print(String *str, enum_query_type query_type) } -bool Item_hex_constant::eq(const Item *arg, bool binary_cmp) const -{ - if (arg->basic_const_item() && arg->type() == type() && - arg->cast_to_int_type() == cast_to_int_type()) - { - if (binary_cmp) - return !stringcmp(&str_value, &arg->str_value); - return !sortcmp(&str_value, &arg->str_value, collation.collation); - } - return FALSE; -} - - -Item *Item_hex_constant::safe_charset_converter(CHARSET_INFO *tocs) -{ - Item_string *conv; - String tmp, *str= val_str(&tmp); - - if (!(conv= new Item_string(str->ptr(), str->length(), tocs))) - return NULL; - conv->str_value.copy(); - conv->str_value.mark_as_const(); - return conv; -} - - /* bin item. In string context this is a binary string. In number context this is a longlong value. */ -Item_bin_string::Item_bin_string(const char *str, uint str_length) +Item_bin_string::Item_bin_string(THD *thd, const char *str, uint str_length): + Item_hex_hybrid(thd) { const char *end= str + str_length - 1; + char *ptr; uchar bits= 0; uint power= 1; max_length= (str_length + 7) >> 3; - char *ptr= (char*) sql_alloc(max_length + 1); - if (!ptr) + if (!(ptr= (char*) thd->alloc(max_length + 1))) return; str_value.set(ptr, max_length, &my_charset_bin); @@ -6413,6 +6263,96 @@ Item_bin_string::Item_bin_string(const char *str, uint str_length) } +bool Item_temporal_literal::eq(const Item *item, bool binary_cmp) const +{ + return + item->basic_const_item() && type() == item->type() && + field_type() == ((Item_temporal_literal *) item)->field_type() && + !my_time_compare(&cached_time, + &((Item_temporal_literal *) item)->cached_time); +} + + +void Item_date_literal::print(String *str, enum_query_type query_type) +{ + str->append("DATE'"); + char buf[MAX_DATE_STRING_REP_LENGTH]; + my_date_to_str(&cached_time, buf); + str->append(buf); + str->append('\''); +} + + +Item *Item_date_literal::clone_item(THD *thd) +{ + return new (thd->mem_root) Item_date_literal(thd, &cached_time); +} + + +bool Item_date_literal::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) +{ + DBUG_ASSERT(fixed); + fuzzy_date |= sql_mode_for_dates(current_thd); + *ltime= cached_time; + return (null_value= check_date_with_warn(ltime, fuzzy_date, + MYSQL_TIMESTAMP_ERROR)); +} + + +void Item_datetime_literal::print(String *str, enum_query_type query_type) +{ + str->append("TIMESTAMP'"); + char buf[MAX_DATE_STRING_REP_LENGTH]; + my_datetime_to_str(&cached_time, buf, decimals); + str->append(buf); + str->append('\''); +} + + +Item *Item_datetime_literal::clone_item(THD *thd) +{ + return new (thd->mem_root) Item_datetime_literal(thd, &cached_time, decimals); +} + + +bool Item_datetime_literal::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) +{ + DBUG_ASSERT(fixed); + fuzzy_date |= sql_mode_for_dates(current_thd); + *ltime= cached_time; + return (null_value= check_date_with_warn(ltime, fuzzy_date, + MYSQL_TIMESTAMP_ERROR)); +} + + +void Item_time_literal::print(String *str, enum_query_type query_type) +{ + str->append("TIME'"); + char buf[MAX_DATE_STRING_REP_LENGTH]; + my_time_to_str(&cached_time, buf, decimals); + str->append(buf); + str->append('\''); +} + + +Item *Item_time_literal::clone_item(THD *thd) +{ + return new (thd->mem_root) Item_time_literal(thd, &cached_time, decimals); +} + + +bool Item_time_literal::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) +{ + DBUG_ASSERT(fixed); + *ltime= cached_time; + if (fuzzy_date & TIME_TIME_ONLY) + return (null_value= false); + return (null_value= check_date_with_warn(ltime, fuzzy_date, + MYSQL_TIMESTAMP_ERROR)); +} + + + /** Pack data in buffer for sending. */ @@ -6514,7 +6454,7 @@ bool Item::send(Protocol *protocol, String *buffer) case MYSQL_TYPE_TIMESTAMP: { MYSQL_TIME tm; - get_date(&tm, sql_mode_for_dates()); + get_date(&tm, sql_mode_for_dates(current_thd)); if (!null_value) { if (f_type == MYSQL_TYPE_DATE) @@ -6563,6 +6503,7 @@ bool Item::cache_const_expr_analyzer(uchar **arg) item->type() == Item::NULL_ITEM || /* Item_name_const hack */ item->type() == Item::FIELD_ITEM || item->type() == SUBSELECT_ITEM || + item->type() == CACHE_ITEM || /* Do not cache GET_USER_VAR() function as its const_item() may return TRUE for the current thread but it still may change @@ -6586,21 +6527,28 @@ bool Item::cache_const_expr_analyzer(uchar **arg) @return this otherwise. */ -Item* Item::cache_const_expr_transformer(uchar *arg) +Item* Item::cache_const_expr_transformer(THD *thd, uchar *arg) { if (*(bool*)arg) { *((bool*)arg)= FALSE; - Item_cache *cache= Item_cache::get_cache(this); + Item_cache *cache= Item_cache::get_cache(thd, this); if (!cache) return NULL; - cache->setup(this); + cache->setup(thd, this); cache->store(this); return cache; } return this; } +/** + Find Item by reference in the expression +*/ +bool Item::find_item_processor(uchar *arg) +{ + return (this == ((Item *) arg)); +} bool Item_field::send(Protocol *protocol, String *buffer) { @@ -6608,6 +6556,18 @@ bool Item_field::send(Protocol *protocol, String *buffer) } +Item* Item::propagate_equal_fields_and_change_item_tree(THD *thd, + const Context &ctx, + COND_EQUAL *cond, + Item **place) +{ + Item *item= propagate_equal_fields(thd, ctx, cond); + if (item && item != this) + thd->change_item_tree(place, item); + return item; +} + + void Item_field::update_null_value() { /* @@ -6641,12 +6601,12 @@ void Item_field::update_null_value() UPDATE statement. RETURN - 0 if error occured + 0 if error occurred ref if all conditions are met this field otherwise */ -Item *Item_field::update_value_transformer(uchar *select_arg) +Item *Item_field::update_value_transformer(THD *thd, uchar *select_arg) { SELECT_LEX *select= (SELECT_LEX*)select_arg; DBUG_ASSERT(fixed); @@ -6661,9 +6621,10 @@ Item *Item_field::update_value_transformer(uchar *select_arg) Item_ref *ref; ref_pointer_array[el]= (Item*)this; - all_fields->push_front((Item*)this); - ref= new Item_ref(&select->context, ref_pointer_array + el, - table_name, field_name); + all_fields->push_front((Item*)this, thd->mem_root); + ref= new (thd->mem_root) + Item_ref(thd, &select->context, ref_pointer_array + el, + table_name, field_name); return ref; } return this; @@ -6682,12 +6643,22 @@ void Item_field::print(String *str, enum_query_type query_type) } -Item_ref::Item_ref(Name_resolution_context *context_arg, +void Item_temptable_field::print(String *str, enum_query_type query_type) +{ + /* + Item_ident doesn't have references to the underlying Field/TABLE objects, + so it's ok to use the following: + */ + Item_ident::print(str, query_type); +} + + +Item_ref::Item_ref(THD *thd, Name_resolution_context *context_arg, Item **item, const char *table_name_arg, const char *field_name_arg, - bool alias_name_used_arg) - :Item_ident(context_arg, NullS, table_name_arg, field_name_arg), - result_field(0), ref(item), reference_trough_name(0) + bool alias_name_used_arg): + Item_ident(thd, context_arg, NullS, table_name_arg, field_name_arg), + ref(item), reference_trough_name(0) { alias_name_used= alias_name_used_arg; /* @@ -6732,10 +6703,10 @@ public: } }; -Item_ref::Item_ref(TABLE_LIST *view_arg, Item **item, - const char *field_name_arg, bool alias_name_used_arg) - :Item_ident(view_arg, field_name_arg), - result_field(NULL), ref(item), reference_trough_name(0) +Item_ref::Item_ref(THD *thd, TABLE_LIST *view_arg, Item **item, + const char *field_name_arg, bool alias_name_used_arg): + Item_ident(thd, view_arg, field_name_arg), + ref(item), reference_trough_name(0) { alias_name_used= alias_name_used_arg; /* @@ -6868,8 +6839,7 @@ bool Item_ref::fix_fields(THD *thd, Item **reference) if (ref != not_found_item) { DBUG_ASSERT(*ref && (*ref)->fixed); - prev_subselect_item->used_tables_cache|= (*ref)->used_tables(); - prev_subselect_item->const_item_cache&= (*ref)->const_item(); + prev_subselect_item->used_tables_and_const_cache_join(*ref); break; } /* @@ -6913,10 +6883,7 @@ bool Item_ref::fix_fields(THD *thd, Item **reference) if (from_field == view_ref_found) { Item::Type refer_type= (*reference)->type(); - prev_subselect_item->used_tables_cache|= - (*reference)->used_tables(); - prev_subselect_item->const_item_cache&= - (*reference)->const_item(); + prev_subselect_item->used_tables_and_const_cache_join(*reference); DBUG_ASSERT((*reference)->type() == REF_ITEM); mark_as_dependent(thd, last_checked_context->select_lex, context->select_lex, this, @@ -6969,7 +6936,7 @@ bool Item_ref::fix_fields(THD *thd, Item **reference) if (from_field != not_found_field) { Item_field* fld; - if (!(fld= new Item_field(from_field))) + if (!(fld= new (thd->mem_root) Item_field(thd, from_field))) goto error; thd->change_item_tree(reference, fld); mark_as_dependent(thd, last_checked_context->select_lex, @@ -7009,19 +6976,6 @@ bool Item_ref::fix_fields(THD *thd, Item **reference) last_checked_context->select_lex->nest_level); } } - else if (ref_type() != VIEW_REF) - { - /* - It could be that we're referring to something that's in ancestor selects. - We must make an appropriate mark_as_dependent() call for each such - outside reference. - */ - Dependency_marker dep_marker; - dep_marker.current_select= current_sel; - dep_marker.thd= thd; - (*ref)->walk(&Item::enumerate_field_refs_processor, FALSE, - (uchar*)&dep_marker); - } DBUG_ASSERT(*ref); /* @@ -7058,10 +7012,8 @@ error: void Item_ref::set_properties() { - max_length= (*ref)->max_length; + Type_std_attributes::set(*ref); maybe_null= (*ref)->maybe_null; - decimals= (*ref)->decimals; - collation.set((*ref)->collation); /* We have to remember if we refer to a sum function, to ensure that split_sum_func() doesn't try to change the reference. @@ -7069,7 +7021,6 @@ void Item_ref::set_properties() with_sum_func= (*ref)->with_sum_func; with_param= (*ref)->with_param; with_field= (*ref)->with_field; - unsigned_flag= (*ref)->unsigned_flag; fixed= 1; if (alias_name_used) return; @@ -7084,7 +7035,6 @@ void Item_ref::cleanup() { DBUG_ENTER("Item_ref::cleanup"); Item_ident::cleanup(); - result_field= 0; if (reference_trough_name) { /* We have to reset the reference as it may been freed */ @@ -7098,7 +7048,7 @@ void Item_ref::cleanup() Transform an Item_ref object with a transformer callback function. The function first applies the transform method to the item - referenced by this Item_reg object. If this returns a new item the + referenced by this Item_ref object. If this returns a new item the old item is substituted for a new one. After this the transformer is applied to the Item_ref object. @@ -7111,13 +7061,13 @@ void Item_ref::cleanup() @retval NULL Out of memory error */ -Item* Item_ref::transform(Item_transformer transformer, uchar *arg) +Item* Item_ref::transform(THD *thd, Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); DBUG_ASSERT((*ref) != NULL); /* Transform the object we are referencing. */ - Item *new_item= (*ref)->transform(transformer, arg); + Item *new_item= (*ref)->transform(thd, transformer, arg); if (!new_item) return NULL; @@ -7128,10 +7078,10 @@ Item* Item_ref::transform(Item_transformer transformer, uchar *arg) change records at each execution. */ if (*ref != new_item) - current_thd->change_item_tree(ref, new_item); + thd->change_item_tree(ref, new_item); /* Transform the item ref object. */ - return (this->*transformer)(arg); + return (this->*transformer)(thd, arg); } @@ -7157,7 +7107,7 @@ Item* Item_ref::transform(Item_transformer transformer, uchar *arg) @return Item returned as the result of transformation of the Item_ref object */ -Item* Item_ref::compile(Item_analyzer analyzer, uchar **arg_p, +Item* Item_ref::compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, Item_transformer transformer, uchar *arg_t) { /* Analyze this Item object. */ @@ -7169,13 +7119,13 @@ Item* Item_ref::compile(Item_analyzer analyzer, uchar **arg_p, if (*arg_p) { uchar *arg_v= *arg_p; - Item *new_item= (*ref)->compile(analyzer, &arg_v, transformer, arg_t); + Item *new_item= (*ref)->compile(thd, analyzer, &arg_v, transformer, arg_t); if (new_item && *ref != new_item) - current_thd->change_item_tree(ref, new_item); + thd->change_item_tree(ref, new_item); } /* Transform this Item object. */ - return (this->*transformer)(arg_t); + return (this->*transformer)(thd, arg_t); } @@ -7269,26 +7219,8 @@ bool Item_ref::val_bool_result() if (result_field) { if ((null_value= result_field->is_null())) - return 0; - switch (result_field->result_type()) { - case INT_RESULT: - return result_field->val_int() != 0; - case DECIMAL_RESULT: - { - my_decimal decimal_value; - my_decimal *val= result_field->val_decimal(&decimal_value); - if (val) - return !my_decimal_is_zero(val); - return 0; - } - case REAL_RESULT: - case STRING_RESULT: - return result_field->val_real() != 0.0; - case ROW_RESULT: - case TIME_RESULT: - case IMPOSSIBLE_RESULT: - DBUG_ASSERT(0); - } + return false; + return result_field->val_bool(); } return val_bool(); } @@ -7393,9 +7325,9 @@ int Item_ref::save_in_field(Field *to, bool no_conversions) } -void Item_ref::save_org_in_field(Field *field) +void Item_ref::save_org_in_field(Field *field, fast_field_copier optimizer_data) { - (*ref)->save_org_in_field(field); + (*ref)->save_org_in_field(field, optimizer_data); } @@ -7421,7 +7353,7 @@ Item *Item_ref::get_tmp_table_item(THD *thd) if (!result_field) return (*ref)->get_tmp_table_item(thd); - Item_field *item= new Item_field(result_field); + Item_field *item= new (thd->mem_root) Item_field(thd, result_field); if (item) { item->table_name= table_name; @@ -7506,24 +7438,21 @@ Item_cache_wrapper::~Item_cache_wrapper() DBUG_ASSERT(expr_cache == 0); } -Item_cache_wrapper::Item_cache_wrapper(Item *item_arg) -:orig_item(item_arg), expr_cache(NULL), expr_value(NULL) +Item_cache_wrapper::Item_cache_wrapper(THD *thd, Item *item_arg): + Item_result_field(thd), orig_item(item_arg), expr_cache(NULL), expr_value(NULL) { DBUG_ASSERT(orig_item->fixed); - max_length= orig_item->max_length; + Type_std_attributes::set(orig_item); maybe_null= orig_item->maybe_null; - decimals= orig_item->decimals; - collation.set(orig_item->collation); with_sum_func= orig_item->with_sum_func; with_param= orig_item->with_param; with_field= orig_item->with_field; - unsigned_flag= orig_item->unsigned_flag; name= item_arg->name; name_length= item_arg->name_length; with_subselect= orig_item->with_subselect; - if ((expr_value= Item_cache::get_cache(orig_item))) - expr_value->setup(orig_item); + if ((expr_value= Item_cache::get_cache(thd, orig_item))) + expr_value->setup(thd, orig_item); fixed= 1; } @@ -7545,7 +7474,14 @@ void Item_cache_wrapper::init_on_demand() void Item_cache_wrapper::print(String *str, enum_query_type query_type) { - str->append(func_name()); + if (query_type & QT_ITEM_CACHE_WRAPPER_SKIP_DETAILS) + { + /* Don't print the cache in EXPLAIN EXTENDED */ + orig_item->print(str, query_type); + return; + } + + str->append("<expr_cache>"); if (expr_cache) { init_on_demand(); @@ -7621,6 +7557,19 @@ bool Item_cache_wrapper::set_cache(THD *thd) DBUG_RETURN(expr_cache == NULL); } +Expression_cache_tracker* Item_cache_wrapper::init_tracker(MEM_ROOT *mem_root) +{ + if (expr_cache) + { + Expression_cache_tracker* tracker= + new(mem_root) Expression_cache_tracker(expr_cache); + if (tracker) + ((Expression_cache_tmptable *)expr_cache)->set_tracker(tracker); + return tracker; + } + return NULL; +} + /** Check if the current values of the parameters are in the expression cache @@ -7883,11 +7832,11 @@ int Item_cache_wrapper::save_in_field(Field *to, bool no_conversions) } -Item* Item_cache_wrapper::get_tmp_table_item(THD *thd_arg) +Item* Item_cache_wrapper::get_tmp_table_item(THD *thd) { if (!orig_item->with_sum_func && !orig_item->const_item()) - return new Item_field(result_field); - return copy_or_same(thd_arg); + return new (thd->mem_root) Item_temptable_field(thd, result_field); + return copy_or_same(thd); } @@ -7971,18 +7920,20 @@ bool Item_outer_ref::fix_fields(THD *thd, Item **reference) } -void Item_outer_ref::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_outer_ref::fix_after_pullout(st_select_lex *new_parent, + Item **ref_arg, bool merge) { if (get_depended_from() == new_parent) { - *ref= outer_ref; - (*ref)->fix_after_pullout(new_parent, ref); + *ref_arg= outer_ref; + (*ref_arg)->fix_after_pullout(new_parent, ref_arg, merge); } } -void Item_ref::fix_after_pullout(st_select_lex *new_parent, Item **refptr) +void Item_ref::fix_after_pullout(st_select_lex *new_parent, Item **refptr, + bool merge) { - (*ref)->fix_after_pullout(new_parent, ref); + (*ref)->fix_after_pullout(new_parent, ref, merge); if (get_depended_from() == new_parent) depended_from= NULL; } @@ -8010,12 +7961,12 @@ bool Item_outer_ref::check_inner_refs_processor(uchar *arg) { List_iterator_fast<Item_outer_ref> *it= ((List_iterator_fast<Item_outer_ref> *) arg); - Item_outer_ref *ref; - while ((ref= (*it)++)) + Item_outer_ref *tmp_ref; + while ((tmp_ref= (*it)++)) { - if (ref == this) + if (tmp_ref == this) { - ref->found_in_group_by= 1; + tmp_ref->found_in_group_by= 1; break; } } @@ -8066,43 +8017,6 @@ Item_equal *Item_direct_view_ref::find_item_equal(COND_EQUAL *cond_equal) /** - Check whether a reference to field item can be substituted for an equal item - - @details - The function checks whether a substitution of a reference to field item for - an equal item is valid. - - @param arg *arg != NULL <-> the reference is in the context - where substitution for an equal item is valid - - @note - See also the note for Item_field::subst_argument_checker - - @retval - TRUE substitution is valid - @retval - FALSE otherwise -*/ -bool Item_direct_view_ref::subst_argument_checker(uchar **arg) -{ - bool res= FALSE; - if (*arg) - { - Item *item= real_item(); - if (item->type() == FIELD_ITEM && - (*arg == (uchar *) Item::ANY_SUBST || - result_type() != STRING_RESULT || - (((Item_field *) item)->field->flags & BINARY_FLAG))) - res= TRUE; - } - /* Block any substitution into the wrapped object */ - if (*arg) - *arg= NULL; - return res; -} - - -/** Set a pointer to the multiple equality the view field reference belongs to (if any). @@ -8122,7 +8036,7 @@ bool Item_direct_view_ref::subst_argument_checker(uchar **arg) of the compile method. @note - The function calls Item_field::equal_fields_propagator for the field item + The function calls Item_field::propagate_equal_fields() for the field item this->real_item() to do the job. Then it takes the pointer to equal_item from this field item and assigns it to this->item_equal. @@ -8131,12 +8045,14 @@ bool Item_direct_view_ref::subst_argument_checker(uchar **arg) - pointer to the field item, otherwise. */ -Item *Item_direct_view_ref::equal_fields_propagator(uchar *arg) +Item *Item_direct_view_ref::propagate_equal_fields(THD *thd, + const Context &ctx, + COND_EQUAL *cond) { Item *field_item= real_item(); if (field_item->type() != FIELD_ITEM) return this; - Item *item= field_item->equal_fields_propagator(arg); + Item *item= field_item->propagate_equal_fields(thd, ctx, cond); set_item_equal(field_item->get_item_equal()); field_item->set_item_equal(NULL); if (item != field_item) @@ -8156,7 +8072,7 @@ Item *Item_direct_view_ref::equal_fields_propagator(uchar *arg) object belongs to unless item_equal contains a constant item. In this case the function returns this constant item (if the substitution does not require conversion). - If the Item_direct_view_item object does not refer any Item_equal object + If the Item_direct_view_ref object does not refer any Item_equal object 'this' is returned . @param arg NULL or points to so some item of the Item_equal type @@ -8175,13 +8091,13 @@ Item *Item_direct_view_ref::equal_fields_propagator(uchar *arg) - this - otherwise. */ -Item *Item_direct_view_ref::replace_equal_field(uchar *arg) +Item *Item_direct_view_ref::replace_equal_field(THD *thd, uchar *arg) { Item *field_item= real_item(); if (field_item->type() != FIELD_ITEM) return this; field_item->set_item_equal(item_equal); - Item *item= field_item->replace_equal_field(arg); + Item *item= field_item->replace_equal_field(thd, arg); field_item->set_item_equal(0); return item != field_item ? item : this; } @@ -8223,9 +8139,10 @@ bool Item_default_value::fix_fields(THD *thd, Item **items) my_error(ER_NO_DEFAULT_FOR_FIELD, MYF(0), field_arg->field->field_name); goto error; } - if (!(def_field= (Field*) sql_alloc(field_arg->field->size_of()))) + if (!(def_field= (Field*) thd->alloc(field_arg->field->size_of()))) goto error; - memcpy((void *)def_field, (void *)field_arg->field, field_arg->field->size_of()); + memcpy((void *)def_field, (void *)field_arg->field, + field_arg->field->size_of()); def_field->move_field_offset((my_ptrdiff_t) (def_field->table->s->default_values - def_field->table->record[0])); @@ -8255,38 +8172,42 @@ int Item_default_value::save_in_field(Field *field_arg, bool no_conversions) { if (!arg) { + TABLE *table= field_arg->table; + THD *thd= table->in_use; + if (field_arg->flags & NO_DEFAULT_VALUE_FLAG && field_arg->real_type() != MYSQL_TYPE_ENUM) { if (field_arg->reset()) { my_message(ER_CANT_CREATE_GEOMETRY_OBJECT, - ER(ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); + ER_THD(thd, ER_CANT_CREATE_GEOMETRY_OBJECT), MYF(0)); return -1; } if (context->error_processor == &view_error_processor) { - TABLE_LIST *view= field_arg->table->pos_in_table_list->top_table(); - push_warning_printf(field_arg->table->in_use, - MYSQL_ERROR::WARN_LEVEL_WARN, + TABLE_LIST *view= table->pos_in_table_list->top_table(); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NO_DEFAULT_FOR_VIEW_FIELD, - ER(ER_NO_DEFAULT_FOR_VIEW_FIELD), + ER_THD(thd, ER_NO_DEFAULT_FOR_VIEW_FIELD), view->view_db.str, view->view_name.str); } else { - push_warning_printf(field_arg->table->in_use, - MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NO_DEFAULT_FOR_FIELD, - ER(ER_NO_DEFAULT_FOR_FIELD), + ER_THD(thd, ER_NO_DEFAULT_FOR_FIELD), field_arg->field_name); } return 1; } field_arg->set_default(); - return 0; + return + !field_arg->is_null() && + field_arg->validate_value_in_record_with_warn(thd, table->record[0]) && + thd->is_error() ? -1 : 0; } return Item_field::save_in_field(field_arg, no_conversions); } @@ -8297,9 +8218,10 @@ int Item_default_value::save_in_field(Field *field_arg, bool no_conversions) same time it can replace some nodes in the tree. */ -Item *Item_default_value::transform(Item_transformer transformer, uchar *args) +Item *Item_default_value::transform(THD *thd, Item_transformer transformer, + uchar *args) { - DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); /* If the value of arg is NULL, then this object represents a constant, @@ -8308,7 +8230,7 @@ Item *Item_default_value::transform(Item_transformer transformer, uchar *args) if (!arg) return 0; - Item *new_item= arg->transform(transformer, args); + Item *new_item= arg->transform(thd, transformer, args); if (!new_item) return 0; @@ -8319,8 +8241,8 @@ Item *Item_default_value::transform(Item_transformer transformer, uchar *args) change records at each execution. */ if (arg != new_item) - current_thd->change_item_tree(&arg, new_item); - return (this->*transformer)(args); + thd->change_item_tree(&arg, new_item); + return (this->*transformer)(thd, args); } @@ -8358,10 +8280,11 @@ bool Item_insert_value::fix_fields(THD *thd, Item **items) if (field_arg->field->table->insert_values) { - Field *def_field= (Field*) sql_alloc(field_arg->field->size_of()); + Field *def_field= (Field*) thd->alloc(field_arg->field->size_of()); if (!def_field) return TRUE; - memcpy((void *)def_field, (void *)field_arg->field, field_arg->field->size_of()); + memcpy((void *)def_field, (void *)field_arg->field, + field_arg->field->size_of()); def_field->move_field_offset((my_ptrdiff_t) (def_field->table->insert_values - def_field->table->record[0])); @@ -8477,6 +8400,7 @@ bool Item_trigger_field::set_value(THD *thd, sp_rcontext * /*ctx*/, Item **it) int err_code= item->save_in_field(field, 0); field->table->copy_blobs= copy_blobs_saved; + field->set_explicit_default(item); return err_code < 0; } @@ -8563,11 +8487,13 @@ Item_result item_cmp_type(Item_result a,Item_result b) void resolve_const_item(THD *thd, Item **ref, Item *comp_item) { Item *item= *ref; - Item *new_item= NULL; if (item->basic_const_item()) return; // Can't be better - Item_result res_type=item_cmp_type(comp_item->cmp_type(), item->cmp_type()); + + Item *new_item= NULL; + Item_result res_type= item_cmp_type(comp_item, item); char *name=item->name; // Alloced by sql_alloc + MEM_ROOT *mem_root= thd->mem_root; switch (res_type) { case TIME_RESULT: @@ -8575,9 +8501,10 @@ void resolve_const_item(THD *thd, Item **ref, Item *comp_item) bool is_null; Item **ref_copy= ref; /* the following call creates a constant and puts it in new_item */ - get_datetime_value(thd, &ref_copy, &new_item, comp_item, &is_null); + enum_field_types type= item->field_type_for_temporal_comparison(comp_item); + get_datetime_value(thd, &ref_copy, &new_item, type, &is_null); if (is_null) - new_item= new Item_null(name); + new_item= new (mem_root) Item_null(thd, name); break; } case STRING_RESULT: @@ -8586,12 +8513,12 @@ void resolve_const_item(THD *thd, Item **ref, Item *comp_item) String tmp(buff,sizeof(buff),&my_charset_bin),*result; result=item->val_str(&tmp); if (item->null_value) - new_item= new Item_null(name); + new_item= new (mem_root) Item_null(thd, name); else { uint length= result->length(); char *tmp_str= sql_strmake(result->ptr(), length); - new_item= new Item_string(name, tmp_str, length, result->charset()); + new_item= new (mem_root) Item_string(thd, name, tmp_str, length, result->charset()); } break; } @@ -8600,15 +8527,15 @@ void resolve_const_item(THD *thd, Item **ref, Item *comp_item) longlong result=item->val_int(); uint length=item->max_length; bool null_value=item->null_value; - new_item= (null_value ? (Item*) new Item_null(name) : - (Item*) new Item_int(name, result, length)); + new_item= (null_value ? (Item*) new (mem_root) Item_null(thd, name) : + (Item*) new (mem_root) Item_int(thd, name, result, length)); break; } case ROW_RESULT: if (item->type() == Item::ROW_ITEM && comp_item->type() == Item::ROW_ITEM) { /* - Substitute constants only in Item_rows. Don't affect other Items + Substitute constants only in Item_row's. Don't affect other Items with ROW_RESULT (eg Item_singlerow_subselect). For such Items more optimal is to detect if it is constant and replace @@ -8620,7 +8547,7 @@ void resolve_const_item(THD *thd, Item **ref, Item *comp_item) uint col; new_item= 0; /* - If item and comp_item are both Item_rows and have same number of cols + If item and comp_item are both Item_row's and have same number of cols then process items in Item_row one by one. We can't ignore NULL values here as this item may be used with <=>, in which case NULL's are significant. @@ -8639,8 +8566,8 @@ void resolve_const_item(THD *thd, Item **ref, Item *comp_item) double result= item->val_real(); uint length=item->max_length,decimals=item->decimals; bool null_value=item->null_value; - new_item= (null_value ? (Item*) new Item_null(name) : (Item*) - new Item_float(name, result, decimals, length)); + new_item= (null_value ? (Item*) new (mem_root) Item_null(thd, name) : (Item*) + new (mem_root) Item_float(thd, name, result, decimals, length)); break; } case DECIMAL_RESULT: @@ -8650,13 +8577,10 @@ void resolve_const_item(THD *thd, Item **ref, Item *comp_item) uint length= item->max_length, decimals= item->decimals; bool null_value= item->null_value; new_item= (null_value ? - (Item*) new Item_null(name) : - (Item*) new Item_decimal(name, result, length, decimals)); + (Item*) new (mem_root) Item_null(thd, name) : + (Item*) new (mem_root) Item_decimal(thd, name, result, length, decimals)); break; } - case IMPOSSIBLE_RESULT: - DBUG_ASSERT(0); - break; } if (new_item) thd->change_item_tree(ref, new_item); @@ -8689,6 +8613,28 @@ int stored_field_cmp_to_item(THD *thd, Field *field, Item *item) { Item_result res_type=item_cmp_type(field->result_type(), item->result_type()); + /* + We have to check field->cmp_type() instead of res_type, + as result_type() - and thus res_type - can never be TIME_RESULT (yet). + */ + if (field->cmp_type() == TIME_RESULT) + { + MYSQL_TIME field_time, item_time, item_time2, *item_time_cmp= &item_time; + if (field->type() == MYSQL_TYPE_TIME) + { + field->get_time(&field_time); + item->get_time(&item_time); + } + else + { + field->get_date(&field_time, TIME_INVALID_DATES); + item->get_date(&item_time, TIME_INVALID_DATES); + if (item_time.time_type == MYSQL_TIMESTAMP_TIME) + if (time_to_datetime(thd, &item_time, item_time_cmp= &item_time2)) + return 1; + } + return my_time_compare(&field_time, item_time_cmp); + } if (res_type == STRING_RESULT) { char item_buff[MAX_FIELD_WIDTH]; @@ -8704,26 +8650,6 @@ int stored_field_cmp_to_item(THD *thd, Field *field, Item *item) if (item->null_value) return 0; String *field_result= field->val_str(&field_tmp); - - enum_field_types field_type= field->type(); - - if (field_type == MYSQL_TYPE_DATE || field_type == MYSQL_TYPE_DATETIME || - field_type == MYSQL_TYPE_TIMESTAMP) - { - enum_mysql_timestamp_type type= MYSQL_TIMESTAMP_ERROR; - - if (field_type == MYSQL_TYPE_DATE) - type= MYSQL_TIMESTAMP_DATE; - else - type= MYSQL_TIMESTAMP_DATETIME; - - const char *field_name= field->field_name; - MYSQL_TIME field_time, item_time; - get_mysql_time_from_str(thd, field_result, type, field_name, &field_time); - get_mysql_time_from_str(thd, item_result, type, field_name, &item_time); - - return my_time_compare(&field_time, &item_time); - } return sortcmp(field_result, item_result, field->charset()); } if (res_type == INT_RESULT) @@ -8739,25 +8665,6 @@ int stored_field_cmp_to_item(THD *thd, Field *field, Item *item) return my_decimal_cmp(field_val, item_val); } /* - We have to check field->cmp_type() instead of res_type, - as result_type() - and thus res_type - can never be TIME_RESULT (yet). - */ - if (field->cmp_type() == TIME_RESULT) - { - MYSQL_TIME field_time, item_time; - if (field->type() == MYSQL_TYPE_TIME) - { - field->get_time(&field_time); - item->get_time(&item_time); - } - else - { - field->get_date(&field_time, TIME_INVALID_DATES); - item->get_date(&item_time, TIME_INVALID_DATES); - } - return my_time_compare(&field_time, &item_time); - } - /* The patch for Bug#13463415 started using this function for comparing BIGINTs. That uncovered a bug in Visual Studio 32bit optimized mode. Prefixing the auto variables with volatile fixes the problem.... @@ -8773,9 +8680,9 @@ int stored_field_cmp_to_item(THD *thd, Field *field, Item *item) return 0; } -Item_cache* Item_cache::get_cache(const Item *item) +Item_cache* Item_cache::get_cache(THD *thd, const Item *item) { - return get_cache(item, item->cmp_type()); + return get_cache(thd, item, item->cmp_type()); } @@ -8788,24 +8695,23 @@ Item_cache* Item_cache::get_cache(const Item *item) @return cache item */ -Item_cache* Item_cache::get_cache(const Item *item, const Item_result type) +Item_cache* Item_cache::get_cache(THD *thd, const Item *item, + const Item_result type) { + MEM_ROOT *mem_root= thd->mem_root; switch (type) { case INT_RESULT: - return new Item_cache_int(item->field_type()); + return new (mem_root) Item_cache_int(thd, item->field_type()); case REAL_RESULT: - return new Item_cache_real(); + return new (mem_root) Item_cache_real(thd); case DECIMAL_RESULT: - return new Item_cache_decimal(); + return new (mem_root) Item_cache_decimal(thd); case STRING_RESULT: - return new Item_cache_str(item); + return new (mem_root) Item_cache_str(thd, item); case ROW_RESULT: - return new Item_cache_row(); + return new (mem_root) Item_cache_row(thd); case TIME_RESULT: - return new Item_cache_temporal(item->field_type()); - case IMPOSSIBLE_RESULT: - DBUG_ASSERT(0); - break; + return new (mem_root) Item_cache_temporal(thd, item->field_type()); } return 0; // Impossible } @@ -8907,17 +8813,34 @@ int Item_cache_int::save_in_field(Field *field, bool no_conversions) } -Item_cache_temporal::Item_cache_temporal(enum_field_types field_type_arg): - Item_cache_int(field_type_arg) +Item_cache_temporal::Item_cache_temporal(THD *thd, + enum_field_types field_type_arg): + Item_cache_int(thd, field_type_arg) { if (mysql_type_to_time_type(cached_field_type) == MYSQL_TIMESTAMP_ERROR) cached_field_type= MYSQL_TYPE_DATETIME; } -longlong Item_cache_temporal::val_temporal_packed() +longlong Item_cache_temporal::val_datetime_packed() +{ + DBUG_ASSERT(fixed == 1); + if (Item_cache_temporal::field_type() == MYSQL_TYPE_TIME) + return Item::val_datetime_packed(); // TIME-to-DATETIME conversion needed + if ((!value_cached && !cache_value()) || null_value) + { + null_value= TRUE; + return 0; + } + return value; +} + + +longlong Item_cache_temporal::val_time_packed() { DBUG_ASSERT(fixed == 1); + if (Item_cache_temporal::field_type() != MYSQL_TYPE_TIME) + return Item::val_time_packed(); // DATETIME-to-TIME conversion needed if ((!value_cached && !cache_value()) || null_value) { null_value= TRUE; @@ -9025,16 +8948,25 @@ int Item_cache_temporal::save_in_field(Field *field, bool no_conversions) } -void Item_cache_temporal::store_packed(longlong val_arg, Item *example) +void Item_cache_temporal::store_packed(longlong val_arg, Item *example_arg) { /* An explicit values is given, save it. */ - store(example); + store(example_arg); value_cached= true; value= val_arg; null_value= false; } +Item *Item_cache_temporal::clone_item(THD *thd) +{ + Item_cache_temporal *item= new (thd->mem_root) + Item_cache_temporal(thd, cached_field_type); + item->store_packed(value, example); + return item; +} + + bool Item_cache_real::cache_value() { if (!example) @@ -9163,28 +9095,18 @@ bool Item_cache_str::cache_value() double Item_cache_str::val_real() { DBUG_ASSERT(fixed == 1); - int err_not_used; - char *end_not_used; if (!has_value()) return 0.0; - if (value) - return my_strntod(value->charset(), (char*) value->ptr(), - value->length(), &end_not_used, &err_not_used); - return (double) 0; + return value ? double_from_string_with_check(value) : 0.0; } longlong Item_cache_str::val_int() { DBUG_ASSERT(fixed == 1); - int err; if (!has_value()) return 0; - if (value) - return my_strntoll(value->charset(), value->ptr(), - value->length(), 10, (char**) 0, &err); - else - return (longlong)0; + return value ? longlong_from_string_with_check(value) : 0; } @@ -9202,11 +9124,7 @@ my_decimal *Item_cache_str::val_decimal(my_decimal *decimal_val) DBUG_ASSERT(fixed == 1); if (!has_value()) return NULL; - if (value) - string2my_decimal(E_DEC_FATAL_ERROR, value, decimal_val); - else - decimal_val= 0; - return decimal_val; + return value ? decimal_from_string_with_check(decimal_val, value) : 0; } @@ -9220,27 +9138,26 @@ int Item_cache_str::save_in_field(Field *field, bool no_conversions) } -bool Item_cache_row::allocate(uint num) +bool Item_cache_row::allocate(THD *thd, uint num) { item_count= num; - THD *thd= current_thd; return (!(values= (Item_cache **) thd->calloc(sizeof(Item_cache *)*item_count))); } -bool Item_cache_row::setup(Item * item) +bool Item_cache_row::setup(THD *thd, Item *item) { example= item; - if (!values && allocate(item->cols())) + if (!values && allocate(thd, item->cols())) return 1; for (uint i= 0; i < item_count; i++) { Item *el= item->element_index(i); Item_cache *tmp; - if (!(tmp= values[i]= Item_cache::get_cache(el))) + if (!(tmp= values[i]= Item_cache::get_cache(thd, el))) return 1; - tmp->setup(el); + tmp->setup(thd, el); } return 0; } @@ -9342,7 +9259,10 @@ void Item_cache_row::set_null() Item_type_holder::Item_type_holder(THD *thd, Item *item) - :Item(thd, item), enum_set_typelib(0), fld_type(get_real_type(item)) + :Item(thd, item), + enum_set_typelib(0), + fld_type(get_real_type(item)), + geometry_type(Field::GEOM_GEOMETRY) { DBUG_ASSERT(item->fixed); maybe_null= item->maybe_null; @@ -9431,7 +9351,6 @@ enum_field_types Item_type_holder::get_real_type(Item *item) return MYSQL_TYPE_NEWDECIMAL; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); return MYSQL_TYPE_VAR_STRING; } @@ -9473,7 +9392,7 @@ bool Item_type_holder::join_types(THD *thd, Item *item) /* fix variable decimals which always is NOT_FIXED_DEC */ if (Field::result_merge_type(fld_type) == INT_RESULT) item_decimals= 0; - decimals= max(decimals, item_decimals); + decimals= MY_MAX(decimals, item_decimals); } if (fld_type == FIELD_TYPE_GEOMETRY) @@ -9483,10 +9402,10 @@ bool Item_type_holder::join_types(THD *thd, Item *item) if (Field::result_merge_type(fld_type) == DECIMAL_RESULT) { collation.set_numeric(); - decimals= min(max(decimals, item->decimals), DECIMAL_MAX_SCALE); + decimals= MY_MIN(MY_MAX(decimals, item->decimals), DECIMAL_MAX_SCALE); int item_int_part= item->decimal_int_part(); - int item_prec = max(prev_decimal_int_part, item_int_part) + decimals; - int precision= min(item_prec, DECIMAL_MAX_PRECISION); + int item_prec = MY_MAX(prev_decimal_int_part, item_int_part) + decimals; + int precision= MY_MIN(item_prec, DECIMAL_MAX_PRECISION); unsigned_flag&= item->unsigned_flag; max_length= my_decimal_precision_to_length_no_truncation(precision, decimals, @@ -9517,7 +9436,7 @@ bool Item_type_holder::join_types(THD *thd, Item *item) */ if (collation.collation != &my_charset_bin) { - max_length= max(old_max_chars * collation.collation->mbmaxlen, + max_length= MY_MAX(old_max_chars * collation.collation->mbmaxlen, display_length(item) / item->collation.collation->mbmaxlen * collation.collation->mbmaxlen); @@ -9539,7 +9458,7 @@ bool Item_type_holder::join_types(THD *thd, Item *item) { int delta1= max_length_orig - decimals_orig; int delta2= item->max_length - item->decimals; - max_length= max(delta1, delta2) + decimals; + max_length= MY_MAX(delta1, delta2) + decimals; if (fld_type == MYSQL_TYPE_FLOAT && max_length > FLT_DIG + 2) { max_length= MAX_FLOAT_STR_LENGTH; @@ -9557,7 +9476,10 @@ bool Item_type_holder::join_types(THD *thd, Item *item) break; } default: - max_length= max(max_length, display_length(item)); + if (fld_type == MYSQL_TYPE_YEAR) + max_length= MY_MAX(max_length, item->max_length); + else + max_length= MY_MAX(max_length, display_length(item)); }; maybe_null|= item->maybe_null; get_full_info(item); @@ -9670,7 +9592,7 @@ Field *Item_type_holder::make_field_by_type(TABLE *table) default: break; } - return tmp_table_field_from_field_type(table, 0); + return tmp_table_field_from_field_type(table, false, true); } @@ -9844,7 +9766,15 @@ const char *dbug_print_item(Item *item) str.length(0); if (!item) return "(Item*)NULL"; - item->print(&str ,QT_ORDINARY); + + THD *thd= current_thd; + ulonglong save_option_bits= thd->variables.option_bits; + thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE; + + item->print(&str, QT_EXPLAIN); + + thd->variables.option_bits= save_option_bits; + if (str.c_ptr_safe() == buf) return buf; else @@ -9852,15 +9782,3 @@ const char *dbug_print_item(Item *item) } #endif /*DBUG_OFF*/ - -/***************************************************************************** -** Instantiate templates -*****************************************************************************/ - -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class List<Item>; -template class List_iterator<Item>; -template class List_iterator_fast<Item>; -template class List_iterator_fast<Item_field>; -template class List<List_item>; -#endif diff --git a/sql/item.h b/sql/item.h index 38dce1f3b97..5c432887ed9 100644 --- a/sql/item.h +++ b/sql/item.h @@ -25,14 +25,15 @@ #include "sql_priv.h" /* STRING_BUFFER_USUAL_SIZE */ #include "unireg.h" #include "sql_const.h" /* RAND_TABLE_BIT, MAX_FIELD_NAME */ -#include "unireg.h" // REQUIRED: for other includes #include "thr_malloc.h" /* sql_calloc */ #include "field.h" /* Derivation */ +#include "sql_type.h" C_MODE_START #include <ma_dyncol.h> C_MODE_END +#ifndef DBUG_OFF static inline bool trace_unsupported_func(const char *where, const char *processor_name) { @@ -43,6 +44,9 @@ bool trace_unsupported_func(const char *where, const char *processor_name) DBUG_PRINT("info", ("%s", buff)); DBUG_RETURN(TRUE); } +#else +#define trace_unsupported_func(X,Y) TRUE +#endif static inline bool trace_unsupported_by_check_vcol_func_processor(const char *where) @@ -61,6 +65,11 @@ struct TABLE_LIST; void item_init(void); /* Init item functions */ class Item_field; class user_var_entry; +class JOIN; +struct KEY_FIELD; +struct SARGABLE_PARAM; +class RANGE_OPT_PARAM; +class SEL_TREE; static inline uint32 @@ -71,6 +80,10 @@ char_to_byte_length_safe(uint32 char_length_arg, uint32 mbmaxlen_arg) } +/* Bits for the split_sum_func() function */ +#define SPLIT_SUM_SKIP_REGISTERED 1 /* Skip registered funcs */ +#define SPLIT_SUM_SELECT 2 /* SELECT item; Split all parts */ + /* "Declared Type Collation" A combination of collation and its derivation. @@ -120,7 +133,14 @@ public: derivation= derivation_arg; set_repertoire_from_charset(collation_arg); } - void set(DTCollation &dt) + DTCollation(CHARSET_INFO *collation_arg, + Derivation derivation_arg, + uint repertoire_arg) + :collation(collation_arg), + derivation(derivation_arg), + repertoire(repertoire_arg) + { } + void set(const DTCollation &dt) { collation= dt.collation; derivation= dt.derivation; @@ -153,7 +173,7 @@ public: } void set(Derivation derivation_arg) { derivation= derivation_arg; } - bool aggregate(DTCollation &dt, uint flags= 0); + bool aggregate(const DTCollation &dt, uint flags= 0); bool set(DTCollation &dt1, DTCollation &dt2, uint flags= 0) { set(dt1); return aggregate(dt2, flags); } const char *derivation_name() const @@ -170,121 +190,12 @@ public: default: return "UNKNOWN"; } } -}; - -/*************************************************************************/ -/* - A framework to easily handle different return types for hybrid items - (hybrid item is an item whose operand can be of any type, e.g. integer, - real, decimal). -*/ - -struct Hybrid_type_traits; - -struct Hybrid_type -{ - longlong integer; - - double real; - /* - Use two decimal buffers interchangeably to speed up += operation - which has no native support in decimal library. - Hybrid_type+= arg is implemented as dec_buf[1]= dec_buf[0] + arg. - The third decimal is used as a handy temporary storage. - */ - my_decimal dec_buf[3]; - int used_dec_buf_no; - - /* - Traits moved to a separate class to - a) be able to easily change object traits in runtime - b) they work as a differentiator for the union above - */ - const Hybrid_type_traits *traits; - - Hybrid_type() {} - /* XXX: add traits->copy() when needed */ - Hybrid_type(const Hybrid_type &rhs) :traits(rhs.traits) {} -}; - - -/* Hybryd_type_traits interface + default implementation for REAL_RESULT */ - -struct Hybrid_type_traits -{ - virtual Item_result type() const { return REAL_RESULT; } - - virtual void - fix_length_and_dec(Item *item, Item *arg) const; - - /* Hybrid_type operations. */ - virtual void set_zero(Hybrid_type *val) const { val->real= 0.0; } - virtual void add(Hybrid_type *val, Field *f) const - { val->real+= f->val_real(); } - virtual void div(Hybrid_type *val, ulonglong u) const - { val->real/= ulonglong2double(u); } - - virtual longlong val_int(Hybrid_type *val, bool unsigned_flag) const - { return (longlong) rint(val->real); } - virtual double val_real(Hybrid_type *val) const { return val->real; } - virtual my_decimal *val_decimal(Hybrid_type *val, my_decimal *buf) const; - virtual String *val_str(Hybrid_type *val, String *buf, uint8 decimals) const; - static const Hybrid_type_traits *instance(); - Hybrid_type_traits() {} - virtual ~Hybrid_type_traits() {} -}; - - -struct Hybrid_type_traits_decimal: public Hybrid_type_traits -{ - virtual Item_result type() const { return DECIMAL_RESULT; } - - virtual void - fix_length_and_dec(Item *arg, Item *item) const; - - /* Hybrid_type operations. */ - virtual void set_zero(Hybrid_type *val) const; - virtual void add(Hybrid_type *val, Field *f) const; - virtual void div(Hybrid_type *val, ulonglong u) const; - - virtual longlong val_int(Hybrid_type *val, bool unsigned_flag) const; - virtual double val_real(Hybrid_type *val) const; - virtual my_decimal *val_decimal(Hybrid_type *val, my_decimal *buf) const - { return &val->dec_buf[val->used_dec_buf_no]; } - virtual String *val_str(Hybrid_type *val, String *buf, uint8 decimals) const; - static const Hybrid_type_traits_decimal *instance(); - Hybrid_type_traits_decimal() {}; -}; - - -struct Hybrid_type_traits_integer: public Hybrid_type_traits -{ - virtual Item_result type() const { return INT_RESULT; } - - virtual void - fix_length_and_dec(Item *arg, Item *item) const; - - /* Hybrid_type operations. */ - virtual void set_zero(Hybrid_type *val) const - { val->integer= 0; } - virtual void add(Hybrid_type *val, Field *f) const - { val->integer+= f->val_int(); } - virtual void div(Hybrid_type *val, ulonglong u) const - { val->integer/= (longlong) u; } - - virtual longlong val_int(Hybrid_type *val, bool unsigned_flag) const - { return val->integer; } - virtual double val_real(Hybrid_type *val) const - { return (double) val->integer; } - virtual my_decimal *val_decimal(Hybrid_type *val, my_decimal *buf) const - { - int2my_decimal(E_DEC_FATAL_ERROR, val->integer, 0, &val->dec_buf[2]); - return &val->dec_buf[2]; - } - virtual String *val_str(Hybrid_type *val, String *buf, uint8 decimals) const - { buf->set(val->integer, &my_charset_bin); return buf;} - static const Hybrid_type_traits_integer *instance(); - Hybrid_type_traits_integer() {}; + int sortcmp(const String *s, const String *t) const + { + return collation->coll->strnncollsp(collation, + (uchar *) s->ptr(), s->length(), + (uchar *) t->ptr(), t->length(), 0); + } }; @@ -524,7 +435,7 @@ public: RETURN FALSE if parameter value has been set, - TRUE if error has occured. + TRUE if error has occurred. */ virtual bool set_value(THD *thd, sp_rcontext *ctx, Item **it)= 0; @@ -534,10 +445,74 @@ public: { return NULL; } }; +/** + This is used for items in the query that needs to be rewritten + before binlogging + + At the moment this applies to Item_param and Item_splocal +*/ +class Rewritable_query_parameter +{ + public: + /* + Offset inside the query text. + Value of 0 means that this object doesn't have to be replaced + (for example SP variables in control statements) + */ + uint pos_in_query; + + /* + Byte length of parameter name in the statement. This is not + Item::name_length because name_length contains byte length of UTF8-encoded + name, but the query string is in the client charset. + */ + uint len_in_query; + + bool limit_clause_param; + + Rewritable_query_parameter(uint pos_in_q= 0, uint len_in_q= 0) + : pos_in_query(pos_in_q), len_in_query(len_in_q), + limit_clause_param(false) + { } + + virtual ~Rewritable_query_parameter() { } + + virtual bool append_for_log(THD *thd, String *str) = 0; +}; + +class Copy_query_with_rewrite +{ + THD *thd; + const char *src; + size_t src_len, from; + String *dst; + + bool copy_up_to(size_t bytes) + { + DBUG_ASSERT(bytes >= from); + return dst->append(src + from, bytes - from); + } + +public: + + Copy_query_with_rewrite(THD *t, const char *s, size_t l, String *d) + :thd(t), src(s), src_len(l), from(0), dst(d) { } + + bool append(Rewritable_query_parameter *p) + { + if (copy_up_to(p->pos_in_query) || p->append_for_log(thd, dst)) + return true; + from= p->pos_in_query + p->len_in_query; + return false; + } + + bool finalize() + { return copy_up_to(src_len); } +}; struct st_dyncall_create_def { - Item *num, *value; + Item *key, *value; CHARSET_INFO *cs; uint len, frac; DYNAMIC_COLUMN_TYPE type; @@ -559,15 +534,83 @@ typedef bool (Item::*Item_processor) (uchar *arg); */ typedef bool (Item::*Item_analyzer) (uchar **argp); -typedef Item* (Item::*Item_transformer) (uchar *arg); +typedef Item* (Item::*Item_transformer) (THD *thd, uchar *arg); typedef void (*Cond_traverser) (const Item *item, void *arg); +struct st_cond_statistic; + +struct find_selective_predicates_list_processor_data +{ + TABLE *table; + List<st_cond_statistic> list; +}; + class Item_equal; class COND_EQUAL; class st_select_lex_unit; -class Item { +class Item_func_not; +class Item_splocal; + +/** + String_copier that sends Item specific warnings. +*/ +class String_copier_for_item: public String_copier +{ + THD *m_thd; +public: + bool copy_with_warn(CHARSET_INFO *dstcs, String *dst, + CHARSET_INFO *srccs, const char *src, + uint32 src_length, uint32 nchars); + String_copier_for_item(THD *thd): m_thd(thd) { } +}; + + +/** + A class to store type attributes for the standard data types. + Does not include attributes for the extended data types + such as ENUM, SET, GEOMETRY. +*/ +class Type_std_attributes +{ +public: + DTCollation collation; + uint decimals; + /* + The maximum value length in characters multiplied by collation->mbmaxlen. + Almost always it's the maximum value length in bytes. + */ + uint32 max_length; + bool unsigned_flag; + Type_std_attributes() + :collation(&my_charset_bin, DERIVATION_COERCIBLE), + decimals(0), max_length(0), unsigned_flag(false) + { } + Type_std_attributes(const Type_std_attributes *other) + :collation(other->collation), + decimals(other->decimals), + max_length(other->max_length), + unsigned_flag(other->unsigned_flag) + { } + void set(const Type_std_attributes *other) + { + *this= *other; + } + void set(const Field *field) + { + decimals= field->decimals(); + max_length= field->field_length; + collation.set(field->charset()); + unsigned_flag= MY_TEST(field->flags & UNSIGNED_FLAG); + } +}; + + +class Item: public Value_source, + public Type_std_attributes, + public Type_handler +{ Item(const Item &); /* Prevent use of these */ void operator=(Item &); /** @@ -581,9 +624,9 @@ class Item { */ uint join_tab_idx; + static void *operator new(size_t size); + public: - static void *operator new(size_t size) throw () - { return sql_alloc(size); } static void *operator new(size_t size, MEM_ROOT *mem_root) throw () { return alloc_root(mem_root, size); } static void operator delete(void *ptr,size_t size) { TRASH_FREE(ptr, size); } @@ -597,7 +640,8 @@ public: SUBSELECT_ITEM, ROW_ITEM, CACHE_ITEM, TYPE_HOLDER, PARAM_ITEM, TRIGGER_FIELD_ITEM, DECIMAL_ITEM, XPATH_NODESET, XPATH_NODESET_CMP, - VIEW_FIXER_ITEM, EXPR_CACHE_ITEM}; + VIEW_FIXER_ITEM, EXPR_CACHE_ITEM, + DATE_ITEM}; enum cond_result { COND_UNDEF,COND_OK,COND_TRUE,COND_FALSE }; @@ -609,11 +653,69 @@ public: /* Reuse size, only used by SP local variable assignment, otherwize 0 */ uint rsize; +protected: /* str_values's main purpose is to be used to cache the value in save_in_field */ String str_value; + + SEL_TREE *get_mm_tree_for_const(RANGE_OPT_PARAM *param); + + Field *create_tmp_field(bool group, TABLE *table, uint convert_int_length); + /* Helper methods, to get an Item value from another Item */ + double val_real_from_item(Item *item) + { + DBUG_ASSERT(fixed == 1); + double value= item->val_real(); + null_value= item->null_value; + return value; + } + longlong val_int_from_item(Item *item) + { + DBUG_ASSERT(fixed == 1); + longlong value= item->val_int(); + null_value= item->null_value; + return value; + } + String *val_str_from_item(Item *item, String *str) + { + DBUG_ASSERT(fixed == 1); + String *res= item->val_str(str); + if (res) + res->set_charset(collation.collation); + if ((null_value= item->null_value)) + res= NULL; + return res; + } + my_decimal *val_decimal_from_item(Item *item, my_decimal *decimal_value) + { + DBUG_ASSERT(fixed == 1); + my_decimal *value= item->val_decimal(decimal_value); + if ((null_value= item->null_value)) + value= NULL; + return value; + } + bool get_date_from_item(Item *item, MYSQL_TIME *ltime, ulonglong fuzzydate) + { + bool rc= item->get_date(ltime, fuzzydate); + null_value= MY_TEST(rc || item->null_value); + return rc; + } + /* + This method is used if the item was not null but convertion to + TIME/DATE/DATETIME failed. We return a zero date if allowed, + otherwise - null. + */ + bool make_zero_date(MYSQL_TIME *ltime, ulonglong fuzzydate); + +public: + /* + Cache val_str() into the own buffer, e.g. to evaluate constant + expressions with subqueries in the ORDER/GROUP clauses. + */ + String *val_str() { return val_str(&str_value); } + char * name; /* Name from select */ /* Original item name (if it was renamed)*/ char * orig_name; @@ -626,27 +728,20 @@ public: */ Item *next; /* - The maximum value length in characters multiplied by collation->mbmaxlen. - Almost always it's the maximum value length in bytes. - */ - uint32 max_length; - /* TODO: convert name and name_length fields into LEX_STRING to keep them in sync (see bug #11829681/60295 etc). Then also remove some strlen(name) calls. */ uint name_length; /* Length of name */ - uint decimals; int marker; bool maybe_null; /* If item may be null */ bool in_rollup; /* If used in GROUP BY list of a query with ROLLUP */ bool null_value; /* if item is null */ - bool unsigned_flag; bool with_sum_func; /* True if item contains a sum func */ bool with_param; /* True if contains an SP parameter */ /** - True if any item except Item_sum_func contains a field. Set during parsing. + True if any item except Item_sum contains a field. Set during parsing. */ bool with_field; bool fixed; /* If item fixed with fix_fields */ @@ -655,10 +750,8 @@ public: bool with_subselect; /* If this item is a subselect or some of its arguments is or contains a subselect */ - DTCollation collation; - Item_result cmp_context; /* Comparison context */ // alloc & destruct is done as start of select using sql_alloc - Item(); + Item(THD *thd); /* Constructor used by Item_field, Item_ref & aggregate (sum) functions. Used for duplicating lists in processing queries with temporary @@ -688,7 +781,9 @@ public: Fix after some tables has been pulled out. Basically re-calculate all attributes that are dependent on the tables. */ - virtual void fix_after_pullout(st_select_lex *new_parent, Item **ref) {}; + virtual void fix_after_pullout(st_select_lex *new_parent, Item **ref, + bool merge) + {}; /* This method should be used in case where we are sure that we do not need @@ -705,19 +800,26 @@ public: /* Function returns 1 on overflow and -1 on fatal errors */ int save_in_field_no_warnings(Field *field, bool no_conversions); virtual int save_in_field(Field *field, bool no_conversions); - virtual void save_org_in_field(Field *field) + virtual void save_org_in_field(Field *field, + fast_field_copier data + __attribute__ ((__unused__))) { (void) save_in_field(field, 1); } + virtual fast_field_copier setup_fast_field_copier(Field *field) + { return NULL; } virtual int save_safe_in_field(Field *field) { return save_in_field(field, 1); } virtual bool send(Protocol *protocol, String *str); virtual bool eq(const Item *, bool binary_cmp) const; /* result_type() of an item specifies how the value should be returned */ - virtual Item_result result_type() const { return REAL_RESULT; } + Item_result result_type() const { return REAL_RESULT; } /* ... while cmp_type() specifies how it should be compared */ - virtual Item_result cmp_type() const; + Item_result cmp_type() const; virtual Item_result cast_to_int_type() const { return cmp_type(); } - virtual enum_field_types string_field_type() const; - virtual enum_field_types field_type() const; + enum_field_types string_field_type() const + { + return Type_handler::string_type_handler(max_length)->field_type(); + } + enum_field_types field_type() const; virtual enum Type type() const =0; /* real_type() is the type of base item. This is same as type() for @@ -812,25 +914,20 @@ public: store return value of this method. NOTE - Buffer passed via argument should only be used if the item itself - doesn't have an own String buffer. In case when the item maintains - it's own string buffer, it's preferable to return it instead to - minimize number of mallocs/memcpys. - The caller of this method can modify returned string, but only in case - when it was allocated on heap, (is_alloced() is true). This allows - the caller to efficiently use a buffer allocated by a child without - having to allocate a buffer of it's own. The buffer, given to - val_str() as argument, belongs to the caller and is later used by the - caller at it's own choosing. - A few implications from the above: - - unless you return a string object which only points to your buffer - but doesn't manages it you should be ready that it will be - modified. - - even for not allocated strings (is_alloced() == false) the caller - can change charset (see Item_func_{typecast/binary}. XXX: is this - a bug? - - still you should try to minimize data copying and return internal - object whenever possible. + The caller can modify the returned String, if it's not marked + "const" (with the String::mark_as_const() method). That means that + if the item returns its own internal buffer (e.g. tmp_value), it + *must* be marked "const" [1]. So normally it's preferrable to + return the result value in the String, that was passed as an + argument. But, for example, SUBSTR() returns a String that simply + points into the buffer of SUBSTR()'s args[0]->val_str(). Such a + String is always "const", so it's ok to use tmp_value for that and + avoid reallocating/copying of the argument String. + + [1] consider SELECT CONCAT(f, ":", f) FROM (SELECT func() AS f); + here the return value of f() is used twice in the top-level + select, and if they share the same tmp_value buffer, modifying the + first one will implicitly modify the second too. RETURN In case of NULL value return 0 (NULL pointer) and set null_value flag @@ -942,11 +1039,18 @@ public: virtual bool val_bool(); virtual String *val_nodeset(String*) { return 0; } + bool eval_const_cond() + { + DBUG_ASSERT(const_item()); + DBUG_ASSERT(!is_expensive()); + return val_bool(); + } + /* save_val() is method of val_* family which stores value in the given field. */ - virtual void save_val(Field *to) { save_org_in_field(to); } + virtual void save_val(Field *to) { save_org_in_field(to, NULL); } /* save_result() is method of val*result() family which stores value in the given field. @@ -964,16 +1068,22 @@ public: my_decimal *val_decimal_from_time(my_decimal *decimal_value); longlong val_int_from_decimal(); longlong val_int_from_date(); + longlong val_int_from_real(); double val_real_from_decimal(); double val_real_from_date(); + // Get TIME, DATE or DATETIME using proper sql_mode flags for the field type + bool get_temporal_with_sql_mode(MYSQL_TIME *ltime); + // Check NULL value for a TIME, DATE or DATETIME expression + bool is_null_from_temporal(); + int save_time_in_field(Field *field); int save_date_in_field(Field *field); int save_str_value_in_field(Field *field, String *result); virtual Field *get_tmp_table_field() { return 0; } - /* This is also used to create fields in CREATE ... SELECT: */ - virtual Field *tmp_table_field(TABLE *t_arg) { return 0; } + virtual Field *create_field_for_create_select(TABLE *table); + virtual Field *create_field_for_schema(THD *thd, TABLE *table); virtual const char *full_name() const { return name ? name : "???"; } const char *field_name_or_null() { return real_item()->type() == Item::FIELD_ITEM ? name : NULL; } @@ -1021,7 +1131,7 @@ public: */ virtual bool basic_const_item() const { return 0; } /* cloning of constant items (0 if it is not const) */ - virtual Item *clone_item() { return 0; } + virtual Item *clone_item(THD *thd) { return 0; } virtual cond_result eq_cmp_result() const { return COND_OK; } inline uint float_length(uint decimals_par) const { return decimals != NOT_FIXED_DEC ? (DBL_DIG+2+decimals_par) : DBL_DIG+8;} @@ -1040,7 +1150,7 @@ public: return decimals < NOT_FIXED_DEC ? decimals : is_temporal_type_with_time(field_type()) ? TIME_SECOND_PART_DIGITS : - min(max_length, DECIMAL_MAX_SCALE); + MY_MIN(max_length, DECIMAL_MAX_SCALE); } /* Returns how many digits a divisor adds into a division result. @@ -1101,14 +1211,127 @@ public: void print_item_w_name(String *, enum_query_type query_type); void print_value(String *); virtual void update_used_tables() {} + virtual COND *build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref) + { + update_used_tables(); + DBUG_ASSERT(!cond_equal_ref || !cond_equal_ref[0]); + return this; + } + virtual COND *remove_eq_conds(THD *thd, Item::cond_result *cond_value, + bool top_level); + virtual void add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, + table_map usable_tables, + SARGABLE_PARAM **sargables) + { + return; + } + /* + Make a select tree for all keys in a condition or a condition part + @param param Context + @param cond_ptr[OUT] Store a replacement item here if the condition + can be simplified, e.g.: + WHERE part1 OR part2 OR part3 + with one of the partN evalutating to SEL_TREE::ALWAYS. + */ + virtual SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr); + /* + Checks whether the item is: + - a simple equality (field=field_item or field=constant_item), or + - a row equality + and form multiple equality predicates. + */ + virtual bool check_equality(THD *thd, COND_EQUAL *cond, List<Item> *eq_list) + { + return false; + } virtual void split_sum_func(THD *thd, Item **ref_pointer_array, - List<Item> &fields) {} + List<Item> &fields, uint flags) {} /* Called for items that really have to be split */ void split_sum_func2(THD *thd, Item **ref_pointer_array, List<Item> &fields, - Item **ref, bool skip_registered); + Item **ref, uint flags); virtual bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); bool get_time(MYSQL_TIME *ltime) - { return get_date(ltime, TIME_TIME_ONLY); } + { return get_date(ltime, TIME_TIME_ONLY | TIME_INVALID_DATES); } + // Get date with automatic TIME->DATETIME conversion + bool get_date_with_conversion(MYSQL_TIME *ltime, ulonglong fuzzydate); + /* + Get time with automatic DATE/DATETIME to TIME conversion. + + Performce a reserve operation to get_date_with_conversion(). + Suppose: + - we have a set of items (typically with the native MYSQL_TYPE_TIME type) + whose item->get_date() return TIME1 value, and + - item->get_date_with_conversion() for the same Items return DATETIME1, + after applying time-to-datetime conversion to TIME1. + + then all items (typically of the native MYSQL_TYPE_{DATE|DATETIME} types) + whose get_date() return DATETIME1 must also return TIME1 from + get_time_with_conversion() + + @param thd - the thread, its variables.old_mode is checked + to decide if use simple YYYYMMDD truncation (old mode), + or perform full DATETIME-to-TIME conversion with + CURRENT_DATE subtraction. + @param[out] ltime - store the result here + @param fuzzydate - flags to be used for the get_date() call. + Normally, should include TIME_TIME_ONLY, to let + the called low-level routines, e.g. str_to_date(), + know that we prefer TIME rather that DATE/DATETIME + and do less conversion outside of the low-level + routines. + + @returns true - on error, e.g. get_date() returned NULL value, + or get_date() returned DATETIME/DATE with non-zero + YYYYMMDD part. + @returns false - on success + */ + bool get_time_with_conversion(THD *thd, MYSQL_TIME *ltime, + ulonglong fuzzydate); + // Get a DATE or DATETIME value in numeric packed format for comparison + virtual longlong val_datetime_packed() + { + MYSQL_TIME ltime; + uint fuzzydate= TIME_FUZZY_DATES | TIME_INVALID_DATES; + return get_date_with_conversion(<ime, fuzzydate) ? 0 : pack_time(<ime); + } + // Get a TIME value in numeric packed format for comparison + virtual longlong val_time_packed() + { + MYSQL_TIME ltime; + uint fuzzydate= TIME_FUZZY_DATES | TIME_INVALID_DATES | TIME_TIME_ONLY; + return get_date(<ime, fuzzydate) ? 0 : pack_time(<ime); + } + // Get a temporal value in packed DATE/DATETIME or TIME format + longlong val_temporal_packed(enum_field_types f_type) + { + return f_type == MYSQL_TYPE_TIME ? val_time_packed() : + val_datetime_packed(); + } + enum_field_types field_type_for_temporal_comparison(const Item *other) const + { + if (cmp_type() == TIME_RESULT) + { + if (other->cmp_type() == TIME_RESULT) + return Field::field_type_merge(field_type(), other->field_type()); + else + return field_type(); + } + else + { + if (other->cmp_type() == TIME_RESULT) + return other->field_type(); + DBUG_ASSERT(0); // Two non-temporal data types, we should not get to here + return MYSQL_TYPE_DATETIME; + } + } + // Get a temporal value to compare to another Item + longlong val_temporal_packed(const Item *other) + { + return val_temporal_packed(field_type_for_temporal_comparison(other)); + } bool get_seconds(ulonglong *sec, ulong *sec_part); virtual bool get_date_result(MYSQL_TIME *ltime, ulonglong fuzzydate) { return get_date(ltime,fuzzydate); } @@ -1116,7 +1339,7 @@ public: The method allows to determine nullness of a complex expression without fully evaluating it, instead of calling val/result*() then checking null_value. Used in Item_func_isnull/Item_func_isnotnull - and Item_sum_count/Item_sum_count_distinct. + and Item_sum_count. Any new item which can be NULL must implement this method. */ virtual bool is_null() { return 0; } @@ -1124,7 +1347,38 @@ public: /* Make sure the null_value member has a correct value. */ - virtual void update_null_value () { (void) val_int(); } + virtual void update_null_value () + { + switch (cmp_type()) { + case INT_RESULT: + (void) val_int(); + break; + case REAL_RESULT: + (void) val_real(); + break; + case DECIMAL_RESULT: + { + my_decimal tmp; + (void) val_decimal(&tmp); + } + break; + case TIME_RESULT: + { + MYSQL_TIME ltime; + (void) get_temporal_with_sql_mode(<ime); + } + break; + case STRING_RESULT: + { + StringBuffer<MAX_FIELD_WIDTH> tmp; + (void) val_str(&tmp); + } + break; + case ROW_RESULT: + DBUG_ASSERT(0); + null_value= true; + } + } /* Inform the item that there will be no distinction between its result @@ -1143,7 +1397,7 @@ public: */ virtual void set_result_field(Field *field) {} virtual bool is_result_field() { return 0; } - virtual bool is_bool_func() { return 0; } + virtual bool is_bool_type() { return false; } virtual void save_in_result_field(bool no_conversions) {} /* set value of aggregate function in case of no rows for grouping were found @@ -1156,7 +1410,6 @@ public: virtual Item *get_tmp_table_item(THD *thd) { return copy_or_same(thd); } static CHARSET_INFO *default_charset(); - virtual CHARSET_INFO *compare_collation() { return NULL; } /* For backward compatibility, to make numeric @@ -1164,8 +1417,8 @@ public: */ virtual CHARSET_INFO *charset_for_protocol(void) const { - return result_type() == STRING_RESULT ? collation.collation : - &my_charset_bin; + return cmp_type() == STRING_RESULT ? collation.collation : + &my_charset_bin; }; virtual bool walk(Item_processor processor, bool walk_subquery, uchar *arg) @@ -1173,7 +1426,12 @@ public: return (this->*processor)(arg); } - virtual Item* transform(Item_transformer transformer, uchar *arg); + virtual bool walk_top_and(Item_processor processor, uchar *arg) + { + return (this->*processor)(arg); + } + + virtual Item* transform(THD *thd, Item_transformer transformer, uchar *arg); /* This function performs a generic "compilation" of the Item tree. @@ -1191,11 +1449,11 @@ public: i.e. analysis is performed top-down while transformation is done bottom-up. */ - virtual Item* compile(Item_analyzer analyzer, uchar **arg_p, + virtual Item* compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, Item_transformer transformer, uchar *arg_t) { if ((this->*analyzer) (arg_p)) - return ((this->*transformer) (arg_t)); + return ((this->*transformer) (thd, arg_t)); return 0; } @@ -1215,11 +1473,11 @@ public: virtual bool intro_version(uchar *int_arg) { return 0; } virtual bool remove_dependence_processor(uchar * arg) { return 0; } - virtual bool remove_fixed(uchar * arg) { fixed= 0; return 0; } virtual bool cleanup_processor(uchar *arg); virtual bool collect_item_field_processor(uchar * arg) { return 0; } virtual bool add_field_to_set_processor(uchar * arg) { return 0; } virtual bool find_item_in_field_list_processor(uchar *arg) { return 0; } + virtual bool find_item_processor(uchar *arg); virtual bool change_context_processor(uchar *context) { return 0; } virtual bool reset_query_id_processor(uchar *query_id_arg) { return 0; } virtual bool is_expensive_processor(uchar *arg) { return 0; } @@ -1235,9 +1493,12 @@ public: virtual bool is_subquery_processor (uchar *opt_arg) { return 0; } virtual bool count_sargable_conds(uchar *arg) { return 0; } virtual bool limit_index_condition_pushdown_processor(uchar *opt_arg) - { + { return FALSE; } + virtual bool exists2in_processor(uchar *opt_arg) { return 0; } + virtual bool find_selective_predicates_list_processor(uchar *opt_arg) + { return 0; } bool cleanup_is_expensive_cache_processor(uchar *arg) { is_expensive_cache= (int8)(-1); @@ -1259,6 +1520,7 @@ public: return FALSE; } + /* The next function differs from the previous one that a bitmap to be updated is passed as uchar *arg. @@ -1266,7 +1528,7 @@ public: virtual bool register_field_in_bitmap(uchar *arg) { return 0; } bool cache_const_expr_analyzer(uchar **arg); - Item* cache_const_expr_transformer(uchar *arg); + Item* cache_const_expr_transformer(THD *thd, uchar *arg); /* Check if a partition function is allowed @@ -1335,22 +1597,15 @@ public: return FALSE; } - /* - The enumeration Subst_constraint is currently used only in implementations - of the virtual function subst_argument_checker. - */ - enum Subst_constraint - { - NO_SUBST= 0, /* No substitution for a field is allowed */ - ANY_SUBST, /* Any substitution for a field is allowed */ - IDENTITY_SUBST /* Substitution for a field is allowed if any two - different values of the field type are not equal */ + virtual Item* propagate_equal_fields(THD*, const Context &, COND_EQUAL *) + { + return this; }; - virtual bool subst_argument_checker(uchar **arg) - { - return (*arg != NULL); - } + Item* propagate_equal_fields_and_change_item_tree(THD *thd, + const Context &ctx, + COND_EQUAL *cond, + Item **place); /* @brief @@ -1370,10 +1625,8 @@ public: return trace_unsupported_by_check_vcol_func_processor(full_name()); } - virtual Item *equal_fields_propagator(uchar * arg) { return this; } - virtual bool set_no_const_sub(uchar *arg) { return FALSE; } /* arg points to REPLACE_EQUAL_FIELD_ARG object */ - virtual Item *replace_equal_field(uchar * arg) { return this; } + virtual Item *replace_equal_field(THD *thd, uchar *arg) { return this; } /* Check if an expression value has allowed arguments, like DATE/DATETIME for date functions. Also used by partitioning code to reject @@ -1388,7 +1641,9 @@ public: List<Item> *parameters; /* unit from which we count nest_level */ st_select_lex_unit *nest_level_base; + uint count; int nest_level; + bool collect; }; /** Collect outer references @@ -1414,6 +1669,8 @@ public: virtual bool check_inner_refs_processor(uchar *arg) { return FALSE; } + virtual bool switch_to_nullable_fields_processor(uchar *arg) { return FALSE; } + /* For SP local variable returns pointer to Item representing its current value and pointer to current Item otherwise. @@ -1437,21 +1694,78 @@ public: // used in row subselects to get value of elements virtual void bring_value() {} - Field *tmp_table_field_from_field_type(TABLE *table, bool fixed_length); - virtual Item_field *filed_for_view_update() { return 0; } + virtual Field *create_tmp_field(bool group, TABLE *table) + { + /* + Values with MY_INT32_NUM_DECIMAL_DIGITS digits may or may not fit into + Field_long : make them Field_longlong. + */ + return create_tmp_field(false, table, MY_INT32_NUM_DECIMAL_DIGITS - 2); + } + + Field *tmp_table_field_from_field_type(TABLE *table, + bool fixed_length, + bool set_blob_packlength); + virtual Item_field *field_for_view_update() { return 0; } virtual Item *neg_transformer(THD *thd) { return NULL; } - virtual Item *update_value_transformer(uchar *select_arg) { return this; } - virtual Item *expr_cache_insert_transformer(uchar *thd_arg) { return this; } + virtual Item *update_value_transformer(THD *thd, uchar *select_arg) + { return this; } + virtual Item *expr_cache_insert_transformer(THD *thd, uchar *unused) + { return this; } virtual bool expr_cache_is_needed(THD *) { return FALSE; } - virtual Item *safe_charset_converter(CHARSET_INFO *tocs); + virtual Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs); + bool needs_charset_converter(uint32 length, CHARSET_INFO *tocs) const + { + /* + This will return "true" if conversion happens: + - between two non-binary different character sets + - from "binary" to "unsafe" character set + (those that can have non-well-formed string) + - from "binary" to UCS2-alike character set with mbminlen>1, + when prefix left-padding is needed for an incomplete character: + binary 0xFF -> ucs2 0x00FF) + */ + if (!String::needs_conversion_on_storage(length, + collation.collation, tocs)) + return false; + /* + No needs to add converter if an "arg" is NUMERIC or DATETIME + value (which is pure ASCII) and at the same time target DTCollation + is ASCII-compatible. For example, no needs to rewrite: + SELECT * FROM t1 WHERE datetime_field = '2010-01-01'; + to + SELECT * FROM t1 WHERE CONVERT(datetime_field USING cs) = '2010-01-01'; + + TODO: avoid conversion of any values with + repertoire ASCII and 7bit-ASCII-compatible, + not only numeric/datetime origin. + */ + if (collation.derivation == DERIVATION_NUMERIC && + collation.repertoire == MY_REPERTOIRE_ASCII && + !(collation.collation->state & MY_CS_NONASCII) && + !(tocs->state & MY_CS_NONASCII)) + return false; + return true; + } + bool needs_charset_converter(CHARSET_INFO *tocs) + { + // Pass 1 as length to force conversion if tocs->mbminlen>1. + return needs_charset_converter(1, tocs); + } + Item *const_charset_converter(THD *thd, CHARSET_INFO *tocs, bool lossless, + const char *func_name); + Item *const_charset_converter(THD *thd, CHARSET_INFO *tocs, bool lossless) + { return const_charset_converter(thd, tocs, lossless, NULL); } void delete_self() { cleanup(); delete this; } - virtual bool is_splocal() { return 0; } /* Needed for error checking */ + virtual Item_splocal *get_item_splocal() { return 0; } + virtual Rewritable_query_parameter *get_rewritable_query_parameter() + { return 0; } /* Return Settable_routine_parameter interface of the Item. Return 0 @@ -1462,18 +1776,6 @@ public: return 0; } /** - Check whether this and the given item has compatible comparison context. - Used by the equality propagation. See Item_field::equal_fields_propagator. - - @return - TRUE if the context is the same - FALSE otherwise. - */ - inline bool has_compatible_context(Item *item) const - { - return cmp_context == IMPOSSIBLE_RESULT || item->cmp_context == cmp_context; - } - /** Test whether an expression is expensive to compute. Used during optimization to avoid computing expensive expressions during this phase. Also used to force temp tables when sorting on expensive @@ -1493,7 +1795,7 @@ public: { if (is_expensive_cache < 0) is_expensive_cache= walk(&Item::is_expensive_processor, 0, (uchar*)0); - return test(is_expensive_cache); + return MY_TEST(is_expensive_cache); } virtual Field::geometry_type get_geometry_type() const { return Field::GEOM_GEOMETRY; }; @@ -1557,6 +1859,15 @@ public: virtual void get_cache_parameters(List<Item> ¶meters) { }; virtual void mark_as_condition_AND_part(TABLE_LIST *embedding) {}; + + /* how much position should be reserved for Exists2In transformation */ + virtual uint exists2in_reserved_items() { return 0; }; + + /** + Inform the item that it is located under a NOT, which is a top-level item. + */ + virtual void under_not(Item_func_not * upper + __attribute__((unused))) {}; }; @@ -1593,12 +1904,102 @@ public: }; class sp_head; +class Item_string; + -class Item_basic_constant :public Item +/** + A common class for Item_basic_constant and Item_param +*/ +class Item_basic_value :public Item +{ + bool is_basic_value(const Item *item, Type type_arg) const + { + return item->basic_const_item() && item->type() == type_arg; + } + bool is_basic_value(Type type_arg) const + { + return basic_const_item() && type() == type_arg; + } + bool str_eq(const String *value, + const String *other, CHARSET_INFO *cs, bool binary_cmp) const + { + return binary_cmp ? + value->bin_eq(other) : + collation.collation == cs && value->eq(other, collation.collation); + } + +protected: + // Value metadata, e.g. to make string processing easier + class Metadata: private MY_STRING_METADATA + { + public: + Metadata(const String *str) + { + my_string_metadata_get(this, str->charset(), str->ptr(), str->length()); + } + Metadata(const String *str, uint repertoire_arg) + { + MY_STRING_METADATA::repertoire= repertoire_arg; + MY_STRING_METADATA::char_length= str->numchars(); + } + uint repertoire() const { return MY_STRING_METADATA::repertoire; } + size_t char_length() const { return MY_STRING_METADATA::char_length; } + }; + void fix_charset_and_length_from_str_value(Derivation dv, Metadata metadata) + { + /* + We have to have a different max_length than 'length' here to + ensure that we get the right length if we do use the item + to create a new table. In this case max_length must be the maximum + number of chars for a string of this type because we in Create_field:: + divide the max_length with mbmaxlen). + */ + collation.set(str_value.charset(), dv, metadata.repertoire()); + fix_char_length(metadata.char_length()); + decimals= NOT_FIXED_DEC; + } + void fix_charset_and_length_from_str_value(Derivation dv) + { + fix_charset_and_length_from_str_value(dv, Metadata(&str_value)); + } + Item_basic_value(THD *thd): Item(thd) {} + /* + In the xxx_eq() methods below we need to cast off "const" to + call val_xxx(). This is OK for Item_basic_constant and Item_param. + */ + bool null_eq(const Item *item) const + { + DBUG_ASSERT(is_basic_value(NULL_ITEM)); + return item->type() == NULL_ITEM; + } + bool str_eq(const String *value, const Item *item, bool binary_cmp) const + { + DBUG_ASSERT(is_basic_value(STRING_ITEM)); + return is_basic_value(item, STRING_ITEM) && + str_eq(value, ((Item_basic_value*)item)->val_str(NULL), + item->collation.collation, binary_cmp); + } + bool real_eq(double value, const Item *item) const + { + DBUG_ASSERT(is_basic_value(REAL_ITEM)); + return is_basic_value(item, REAL_ITEM) && + value == ((Item_basic_value*)item)->val_real(); + } + bool int_eq(longlong value, const Item *item) const + { + DBUG_ASSERT(is_basic_value(INT_ITEM)); + return is_basic_value(item, INT_ITEM) && + value == ((Item_basic_value*)item)->val_int() && + (value >= 0 || item->unsigned_flag == unsigned_flag); + } +}; + + +class Item_basic_constant :public Item_basic_value { table_map used_table_map; public: - Item_basic_constant(): Item(), used_table_map(0) {}; + Item_basic_constant(THD *thd): Item_basic_value(thd), used_table_map(0) {}; void set_used_tables(table_map map) { used_table_map= map; } table_map used_tables() const { return used_table_map; } /* to prevent drop fixed flag (no need parent cleanup call) */ @@ -1644,7 +2045,7 @@ public: #endif public: - Item_sp_variable(char *sp_var_name_str, uint sp_var_name_length); + Item_sp_variable(THD *thd, char *sp_var_name_str, uint sp_var_name_length); public: bool fix_fields(THD *thd, Item **); @@ -1701,7 +2102,8 @@ inline bool Item_sp_variable::send(Protocol *protocol, String *str) *****************************************************************************/ class Item_splocal :public Item_sp_variable, - private Settable_routine_parameter + private Settable_routine_parameter, + public Rewritable_query_parameter { uint m_var_idx; @@ -1709,39 +2111,10 @@ class Item_splocal :public Item_sp_variable, Item_result m_result_type; enum_field_types m_field_type; public: - /* - If this variable is a parameter in LIMIT clause. - Used only during NAME_CONST substitution, to not append - NAME_CONST to the resulting query and thus not break - the slave. - */ - bool limit_clause_param; - /* - Position of this reference to SP variable in the statement (the - statement itself is in sp_instr_stmt::m_query). - This is valid only for references to SP variables in statements, - excluding DECLARE CURSOR statement. It is used to replace references to SP - variables with NAME_CONST calls when putting statements into the binary - log. - Value of 0 means that this object doesn't corresponding to reference to - SP variable in query text. - */ - uint pos_in_query; - /* - Byte length of SP variable name in the statement (see pos_in_query). - The value of this field may differ from the name_length value because - name_length contains byte length of UTF8-encoded item name, but - the query string (see sp_instr_stmt::m_query) is currently stored with - a charset from the SET NAMES statement. - */ - uint len_in_query; - - Item_splocal(const LEX_STRING &sp_var_name, uint sp_var_idx, + Item_splocal(THD *thd, const LEX_STRING &sp_var_name, uint sp_var_idx, enum_field_types sp_var_type, uint pos_in_q= 0, uint len_in_q= 0); - bool is_splocal() { return 1; } /* Needed for error checking */ - Item *this_item(); const Item *this_item() const; Item **this_item_addr(THD *thd, Item **); @@ -1761,10 +2134,15 @@ private: bool set_value(THD *thd, sp_rcontext *ctx, Item **it); public: + Item_splocal *get_item_splocal() { return this; } + + Rewritable_query_parameter *get_rewritable_query_parameter() + { return this; } + Settable_routine_parameter *get_settable_routine_parameter() - { - return this; - } + { return this; } + + bool append_for_log(THD *thd, String *str); }; /***************************************************************************** @@ -1799,7 +2177,7 @@ inline Item_result Item_splocal::result_type() const class Item_case_expr :public Item_sp_variable { public: - Item_case_expr(uint case_expr_id); + Item_case_expr(THD *thd, uint case_expr_id); public: Item *this_item(); @@ -1856,7 +2234,7 @@ class Item_name_const : public Item Item *name_item; bool valid_args; public: - Item_name_const(Item *name_arg, Item *val); + Item_name_const(THD *thd, Item *name_arg, Item *val); bool fix_fields(THD *, Item **); @@ -1893,54 +2271,12 @@ public: } }; -bool agg_item_collations(DTCollation &c, const char *name, - Item **items, uint nitems, uint flags, int item_sep); -bool agg_item_collations_for_comparison(DTCollation &c, const char *name, - Item **items, uint nitems, uint flags); -bool agg_item_set_converter(DTCollation &coll, const char *fname, - Item **args, uint nargs, uint flags, int item_sep); -bool agg_item_charsets(DTCollation &c, const char *name, - Item **items, uint nitems, uint flags, int item_sep); -inline bool -agg_item_charsets_for_string_result(DTCollation &c, const char *name, - Item **items, uint nitems, - int item_sep= 1) -{ - uint flags= MY_COLL_ALLOW_SUPERSET_CONV | - MY_COLL_ALLOW_COERCIBLE_CONV | - MY_COLL_ALLOW_NUMERIC_CONV; - return agg_item_charsets(c, name, items, nitems, flags, item_sep); -} -inline bool -agg_item_charsets_for_comparison(DTCollation &c, const char *name, - Item **items, uint nitems, - int item_sep= 1) -{ - uint flags= MY_COLL_ALLOW_SUPERSET_CONV | - MY_COLL_ALLOW_COERCIBLE_CONV | - MY_COLL_DISALLOW_NONE; - return agg_item_charsets(c, name, items, nitems, flags, item_sep); -} -inline bool -agg_item_charsets_for_string_result_with_comparison(DTCollation &c, - const char *name, - Item **items, uint nitems, - int item_sep= 1) -{ - uint flags= MY_COLL_ALLOW_SUPERSET_CONV | - MY_COLL_ALLOW_COERCIBLE_CONV | - MY_COLL_ALLOW_NUMERIC_CONV | - MY_COLL_DISALLOW_NONE; - return agg_item_charsets(c, name, items, nitems, flags, item_sep); -} - - class Item_num: public Item_basic_constant { public: - Item_num() { collation.set_numeric(); } /* Remove gcc warning */ - virtual Item_num *neg()= 0; - Item *safe_charset_converter(CHARSET_INFO *tocs); + Item_num(THD *thd): Item_basic_constant(thd) { collation.set_numeric(); } + virtual Item_num *neg(THD *thd)= 0; + Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs); bool check_partition_func_processor(uchar *int_arg) { return FALSE;} bool check_vcol_func_processor(uchar *arg) { return FALSE;} }; @@ -1948,7 +2284,37 @@ public: #define NO_CACHED_FIELD_INDEX ((uint)(-1)) class st_select_lex; -class Item_ident :public Item + + +class Item_result_field :public Item /* Item with result field */ +{ +public: + Field *result_field; /* Save result here */ + Item_result_field(THD *thd): Item(thd), result_field(0) {} + // Constructor used for Item_sum/Item_cond_and/or (see Item comment) + Item_result_field(THD *thd, Item_result_field *item): + Item(thd, item), result_field(item->result_field) + {} + ~Item_result_field() {} /* Required with gcc 2.95 */ + Field *get_tmp_table_field() { return result_field; } + /* + This implementation of used_tables() used by Item_avg_field and + Item_variance_field which work when only temporary table left, so theu + return table map of the temporary table. + */ + table_map used_tables() const { return 1; } + void set_result_field(Field *field) { result_field= field; } + bool is_result_field() { return true; } + void save_in_result_field(bool no_conversions) + { + save_in_field(result_field, no_conversions); + } + void cleanup(); + bool check_vcol_func_processor(uchar *arg) { return FALSE;} +}; + + +class Item_ident :public Item_result_field { protected: /* @@ -1993,11 +2359,11 @@ public: this variable. */ bool can_be_depended; - Item_ident(Name_resolution_context *context_arg, + Item_ident(THD *thd, Name_resolution_context *context_arg, const char *db_name_arg, const char *table_name_arg, const char *field_name_arg); Item_ident(THD *thd, Item_ident *item); - Item_ident(TABLE_LIST *view_arg, const char *field_name_arg); + Item_ident(THD *thd, TABLE_LIST *view_arg, const char *field_name_arg); const char *full_name() const; void cleanup(); st_select_lex *get_depended_from() const; @@ -2023,9 +2389,9 @@ public: const char *db_name; const char *table_name; - Item_ident_for_show(Field *par_field, const char *db_arg, - const char *table_name_arg) - :field(par_field), db_name(db_arg), table_name(table_name_arg) + Item_ident_for_show(THD *thd, Field *par_field, const char *db_arg, + const char *table_name_arg): + Item(thd), field(par_field), db_name(db_arg), table_name(table_name_arg) {} enum Type type() const { return FIELD_ITEM; } @@ -2044,9 +2410,8 @@ class Item_field :public Item_ident protected: void set_field(Field *field); public: - Field *field,*result_field; + Field *field; Item_equal *item_equal; - bool no_const_subst; /* if any_privileges set to TRUE then here real effective privileges will be stored @@ -2054,7 +2419,7 @@ public: uint have_privileges; /* field need any privileges (for VIEW creation) */ bool any_privileges; - Item_field(Name_resolution_context *context_arg, + Item_field(THD *thd, Name_resolution_context *context_arg, const char *db_arg,const char *table_name_arg, const char *field_name_arg); /* @@ -2072,7 +2437,7 @@ public: db_name, table_name and column_name are unknown. It's necessary to call reset_field() before fix_fields() for all fields created this way. */ - Item_field(Field *field); + Item_field(THD *thd, Field *field); enum Type type() const { return FIELD_ITEM; } bool eq(const Item *item, bool binary_cmp) const; double val_real(); @@ -2089,10 +2454,11 @@ public: bool send(Protocol *protocol, String *str_arg); void reset_field(Field *f); bool fix_fields(THD *, Item **); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); void make_field(Send_field *tmp_field); int save_in_field(Field *field,bool no_conversions); - void save_org_in_field(Field *field); + void save_org_in_field(Field *field, fast_field_copier optimizer_data); + fast_field_copier setup_fast_field_copier(Field *field); table_map used_tables() const; table_map all_used_tables() const; enum Item_result result_type () const @@ -2112,8 +2478,6 @@ public: return MONOTONIC_STRICT_INCREASING; } longlong val_int_endpoint(bool left_endp, bool *incl_endp); - Field *get_tmp_table_field() { return result_field; } - Field *tmp_table_field(TABLE *t_arg) { return result_field; } bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); bool get_date_result(MYSQL_TIME *ltime,ulonglong fuzzydate); bool is_null() { return field->is_null(); } @@ -2139,6 +2503,30 @@ public: { update_table_bitmaps(); } + COND *build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref) + { + /* + normilize_cond() replaced all conditions of type + WHERE/HAVING field + to: + WHERE/HAVING field<>0 + By the time of a build_equal_items() call, all such conditions should + already be replaced. No Item_field are possible. + Note, some Item_field derivants are still possible. + Item_insert_value: + SELECT * FROM t1 WHERE VALUES(a); + Item_default_value: + SELECT * FROM t1 WHERE DEFAULT(a); + */ + DBUG_ASSERT(type() != FIELD_ITEM); + return Item_ident::build_equal_items(thd, inherited, link_item_fields, + cond_equal_ref); + } + bool is_result_field() { return false; } + void set_result_field(Field *field_arg) {} + void save_in_result_field(bool no_conversions) { } Item *get_tmp_table_item(THD *thd); bool collect_item_field_processor(uchar * arg); bool add_field_to_set_processor(uchar * arg); @@ -2148,22 +2536,19 @@ public: bool register_field_in_bitmap(uchar *arg); bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool vcol_in_partition_func_processor(uchar *bool_arg); - bool check_vcol_func_processor(uchar *arg) { return FALSE;} bool enumerate_field_refs_processor(uchar *arg); bool update_table_bitmaps_processor(uchar *arg); + bool switch_to_nullable_fields_processor(uchar *arg); void cleanup(); Item_equal *get_item_equal() { return item_equal; } void set_item_equal(Item_equal *item_eq) { item_equal= item_eq; } Item_equal *find_item_equal(COND_EQUAL *cond_equal); - bool subst_argument_checker(uchar **arg); - Item *equal_fields_propagator(uchar *arg); - bool set_no_const_sub(uchar *arg); - Item *replace_equal_field(uchar *arg); + Item* propagate_equal_fields(THD *, const Context &, COND_EQUAL *); + Item *replace_equal_field(THD *thd, uchar *arg); inline uint32 max_disp_length() { return field->max_display_length(); } - Item_field *filed_for_view_update() { return this; } - Item *safe_charset_converter(CHARSET_INFO *tocs); + Item_field *field_for_view_update() { return this; } int fix_outer_field(THD *thd, Field **field, Item **reference); - virtual Item *update_value_transformer(uchar *select_arg); + virtual Item *update_value_transformer(THD *thd, uchar *select_arg); virtual void print(String *str, enum_query_type query_type); bool is_outer_field() const { @@ -2182,30 +2567,73 @@ public: friend class st_select_lex_unit; }; + +/* + @brief + Item_temptable_field is the same as Item_field, except that print() + continues to work even if the table has been dropped. + + @detail + + We need this item for "ANALYZE statement" feature. Query execution has + these steps: + + 1. Run the query. + 2. Cleanup starts. Temporary tables are destroyed + 3. print "ANALYZE statement" output, if needed + 4. Call close_thread_table() for regular tables. + + Step #4 is done after step #3, so "ANALYZE stmt" has no problem printing + Item_field objects that refer to regular tables. + + However, Step #3 is done after Step #2. Attempt to print Item_field objects + that refer to temporary tables will cause access to freed memory. + + To resolve this, we use Item_temptable_field to refer to items in temporary + (work) tables. +*/ + +class Item_temptable_field :public Item_field +{ +public: + Item_temptable_field(THD *thd, Name_resolution_context *context_arg, Field *field) + : Item_field(thd, context_arg, field) {} + + Item_temptable_field(THD *thd, Field *field) + : Item_field(thd, field) {} + + Item_temptable_field(THD *thd, Item_field *item) : Item_field(thd, item) {}; + + virtual void print(String *str, enum_query_type query_type); +}; + + class Item_null :public Item_basic_constant { public: - Item_null(char *name_par=0) + Item_null(THD *thd, char *name_par=0, CHARSET_INFO *cs= &my_charset_bin): + Item_basic_constant(thd) { maybe_null= null_value= TRUE; max_length= 0; name= name_par ? name_par : (char*) "NULL"; fixed= 1; - collation.set(&my_charset_bin, DERIVATION_IGNORABLE, MY_REPERTOIRE_ASCII); + collation.set(cs, DERIVATION_IGNORABLE, MY_REPERTOIRE_ASCII); } enum Type type() const { return NULL_ITEM; } - bool eq(const Item *item, bool binary_cmp) const; + bool eq(const Item *item, bool binary_cmp) const { return null_eq(item); } double val_real(); longlong val_int(); String *val_str(String *str); my_decimal *val_decimal(my_decimal *); + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); int save_in_field(Field *field, bool no_conversions); int save_safe_in_field(Field *field); bool send(Protocol *protocol, String *str); enum Item_result result_type () const { return STRING_RESULT; } enum_field_types field_type() const { return MYSQL_TYPE_NULL; } bool basic_const_item() const { return 1; } - Item *clone_item() { return new Item_null(name); } + Item *clone_item(THD *thd); bool is_null() { return 1; } virtual inline void print(String *str, enum_query_type query_type) @@ -2213,7 +2641,7 @@ public: str->append(STRING_WITH_LEN("NULL")); } - Item *safe_charset_converter(CHARSET_INFO *tocs); + Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs); bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *arg) { return FALSE;} }; @@ -2222,8 +2650,23 @@ class Item_null_result :public Item_null { public: Field *result_field; - Item_null_result() : Item_null(), result_field(0) {} + Item_null_result(THD *thd): Item_null(thd), result_field(0) {} bool is_result_field() { return result_field != 0; } +#if MARIADB_VERSION_ID < 100300 + enum_field_types field_type() const + { + return result_field->type(); + } + CHARSET_INFO *charset_for_protocol(void) const + { + return collation.collation; + } +#else + const Type_handler *type_handler() const + { + return result_field->type_handler(); + } +#endif void save_in_result_field(bool no_conversions) { save_in_field(result_field, no_conversions); @@ -2237,13 +2680,10 @@ public: /* Item represents one placeholder ('?') of prepared statement */ -class Item_param :public Item, - private Settable_routine_parameter +class Item_param :public Item_basic_value, + private Settable_routine_parameter, + public Rewritable_query_parameter { - char cnvbuf[MAX_FIELD_WIDTH]; - String cnvstr; - Item *cnvitem; - public: enum enum_item_param_state { @@ -2302,16 +2742,10 @@ public: supply for this placeholder in mysql_stmt_execute. */ enum enum_field_types param_type; - /* - Offset of placeholder inside statement text. Used to create - no-placeholders version of this statement for the binary log. - */ - uint pos_in_query; - Item_param(uint pos_in_query_arg); + Item_param(THD *thd, uint pos_in_query_arg); enum Item_result result_type () const { return item_result_type; } - enum Item_result cast_to_int_type() const { return item_result_type; } enum Type type() const { return item_type; } enum_field_types field_type() const { return param_type; } @@ -2346,7 +2780,7 @@ public: /* If value for parameter was not set we treat it as non-const - so noone will use parameters value in fix_fields still + so no one will use parameters value in fix_fields still parameter is constant during execution. */ virtual table_map used_tables() const @@ -2365,25 +2799,24 @@ public: constant, assert otherwise. This method is called only if basic_const_item returned TRUE. */ - Item *safe_charset_converter(CHARSET_INFO *tocs); - Item *clone_item(); + Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs); + Item *clone_item(THD *thd); /* Implement by-value equality evaluation if parameter value is set and is a basic constant (integer, real or string). Otherwise return FALSE. */ bool eq(const Item *item, bool binary_cmp) const; - /** Item is a argument to a limit clause. */ - bool limit_clause_param; void set_param_type_and_swap_value(Item_param *from); -private: - virtual inline Settable_routine_parameter * - get_settable_routine_parameter() - { - return this; - } + Rewritable_query_parameter *get_rewritable_query_parameter() + { return this; } + Settable_routine_parameter *get_settable_routine_parameter() + { return this; } + bool append_for_log(THD *thd, String *str); + +private: virtual bool set_value(THD *thd, sp_rcontext *ctx, Item **it); virtual void set_out_param_info(Send_field *info); @@ -2402,18 +2835,19 @@ class Item_int :public Item_num { public: longlong value; - Item_int(int32 i,uint length= MY_INT32_NUM_DECIMAL_DIGITS) - :value((longlong) i) + Item_int(THD *thd, int32 i,uint length= MY_INT32_NUM_DECIMAL_DIGITS): + Item_num(thd), value((longlong) i) { max_length=length; fixed= 1; } - Item_int(longlong i,uint length= MY_INT64_NUM_DECIMAL_DIGITS) - :value(i) + Item_int(THD *thd, longlong i,uint length= MY_INT64_NUM_DECIMAL_DIGITS): + Item_num(thd), value(i) { max_length=length; fixed= 1; } - Item_int(ulonglong i, uint length= MY_INT64_NUM_DECIMAL_DIGITS) - :value((longlong)i) + Item_int(THD *thd, ulonglong i, uint length= MY_INT64_NUM_DECIMAL_DIGITS): + Item_num(thd), value((longlong)i) { max_length=length; fixed= 1; unsigned_flag= 1; } - Item_int(const char *str_arg,longlong i,uint length) :value(i) + Item_int(THD *thd, const char *str_arg,longlong i,uint length): + Item_num(thd), value(i) { max_length=length; name=(char*) str_arg; fixed= 1; } - Item_int(const char *str_arg, uint length=64); + Item_int(THD *thd, const char *str_arg, uint length=64); enum Type type() const { return INT_ITEM; } enum Item_result result_type () const { return INT_RESULT; } enum_field_types field_type() const { return MYSQL_TYPE_LONGLONG; } @@ -2423,12 +2857,13 @@ public: String *val_str(String*); int save_in_field(Field *field, bool no_conversions); bool basic_const_item() const { return 1; } - Item *clone_item() { return new Item_int(name,value,max_length); } + Item *clone_item(THD *thd); virtual void print(String *str, enum_query_type query_type); - Item_num *neg() { value= -value; return this; } + Item_num *neg(THD *thd) { value= -value; return this; } uint decimal_precision() const - { return (uint)(max_length - test(value < 0)); } - bool eq(const Item *, bool binary_cmp) const; + { return (uint) (max_length - MY_TEST(value < 0)); } + bool eq(const Item *item, bool binary_cmp) const + { return int_eq(value, item); } bool check_partition_func_processor(uchar *bool_arg) { return FALSE;} bool check_vcol_func_processor(uchar *arg) { return FALSE;} }; @@ -2437,15 +2872,15 @@ public: class Item_uint :public Item_int { public: - Item_uint(const char *str_arg, uint length); - Item_uint(ulonglong i) :Item_int(i, 10) {} - Item_uint(const char *str_arg, longlong i, uint length); + Item_uint(THD *thd, const char *str_arg, uint length); + Item_uint(THD *thd, ulonglong i): Item_int(thd, i, 10) {} + Item_uint(THD *thd, const char *str_arg, longlong i, uint length); double val_real() { DBUG_ASSERT(fixed == 1); return ulonglong2double((ulonglong)value); } String *val_str(String*); - Item *clone_item() { return new Item_uint(name, value, max_length); } + Item *clone_item(THD *thd); virtual void print(String *str, enum_query_type query_type); - Item_num *neg (); + Item_num *neg(THD *thd); uint decimal_precision() const { return max_length; } }; @@ -2455,7 +2890,7 @@ class Item_datetime :public Item_int protected: MYSQL_TIME ltime; public: - Item_datetime() :Item_int(0) { unsigned_flag=0; } + Item_datetime(THD *thd): Item_int(thd, 0) { unsigned_flag=0; } int save_in_field(Field *field, bool no_conversions); longlong val_int(); double val_real() { return (double)val_int(); } @@ -2469,13 +2904,14 @@ class Item_decimal :public Item_num protected: my_decimal decimal_value; public: - Item_decimal(const char *str_arg, uint length, CHARSET_INFO *charset); - Item_decimal(const char *str, const my_decimal *val_arg, + Item_decimal(THD *thd, const char *str_arg, uint length, + CHARSET_INFO *charset); + Item_decimal(THD *thd, const char *str, const my_decimal *val_arg, uint decimal_par, uint length); - Item_decimal(my_decimal *value_par); - Item_decimal(longlong val, bool unsig); - Item_decimal(double val, int precision, int scale); - Item_decimal(const uchar *bin, int precision, int scale); + Item_decimal(THD *thd, my_decimal *value_par); + Item_decimal(THD *thd, longlong val, bool unsig); + Item_decimal(THD *thd, double val, int precision, int scale); + Item_decimal(THD *thd, const uchar *bin, int precision, int scale); enum Type type() const { return DECIMAL_ITEM; } enum Item_result result_type () const { return DECIMAL_RESULT; } @@ -2486,12 +2922,9 @@ public: my_decimal *val_decimal(my_decimal *val) { return &decimal_value; } int save_in_field(Field *field, bool no_conversions); bool basic_const_item() const { return 1; } - Item *clone_item() - { - return new Item_decimal(name, &decimal_value, decimals, max_length); - } + Item *clone_item(THD *thd); virtual void print(String *str, enum_query_type query_type); - Item_num *neg() + Item_num *neg(THD *thd) { my_decimal_neg(&decimal_value); unsigned_flag= !decimal_value.sign(); @@ -2510,17 +2943,17 @@ class Item_float :public Item_num char *presentation; public: double value; - // Item_real() :value(0) {} - Item_float(const char *str_arg, uint length); - Item_float(const char *str,double val_arg,uint decimal_par,uint length) - :value(val_arg) + Item_float(THD *thd, const char *str_arg, uint length); + Item_float(THD *thd, const char *str, double val_arg, uint decimal_par, + uint length): Item_num(thd), value(val_arg) { presentation= name=(char*) str; decimals=(uint8) decimal_par; max_length=length; fixed= 1; } - Item_float(double value_par, uint decimal_par) :presentation(0), value(value_par) + Item_float(THD *thd, double value_par, uint decimal_par): + Item_num(thd), presentation(0), value(value_par) { decimals= (uint8) decimal_par; fixed= 1; @@ -2545,11 +2978,11 @@ public: String *val_str(String*); my_decimal *val_decimal(my_decimal *); bool basic_const_item() const { return 1; } - Item *clone_item() - { return new Item_float(name, value, decimals, max_length); } - Item_num *neg() { value= -value; return this; } + Item *clone_item(THD *thd); + Item_num *neg(THD *thd) { value= -value; return this; } virtual void print(String *str, enum_query_type query_type); - bool eq(const Item *, bool binary_cmp) const; + bool eq(const Item *item, bool binary_cmp) const + { return real_eq(value, item); } }; @@ -2557,9 +2990,9 @@ class Item_static_float_func :public Item_float { const char *func_name; public: - Item_static_float_func(const char *str, double val_arg, uint decimal_par, - uint length) - :Item_float(NullS, val_arg, decimal_par, length), func_name(str) + Item_static_float_func(THD *thd, const char *str, double val_arg, + uint decimal_par, uint length): + Item_float(thd, NullS, val_arg, decimal_par, length), func_name(str) {} virtual inline void print(String *str, enum_query_type query_type) @@ -2567,70 +3000,91 @@ public: str->append(func_name); } - Item *safe_charset_converter(CHARSET_INFO *tocs); + Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs) + { + return const_charset_converter(thd, tocs, true, func_name); + } }; class Item_string :public Item_basic_constant { -public: - Item_string(const char *str,uint length, - CHARSET_INFO *cs, Derivation dv= DERIVATION_COERCIBLE, - uint repertoire= MY_REPERTOIRE_UNICODE30) - : m_cs_specified(FALSE) +protected: + void fix_from_value(Derivation dv, const Metadata metadata) { - str_value.set_or_copy_aligned(str, length, cs); - collation.set(cs, dv, repertoire); - /* - We have to have a different max_length than 'length' here to - ensure that we get the right length if we do use the item - to create a new table. In this case max_length must be the maximum - number of chars for a string of this type because we in Create_field:: - divide the max_length with mbmaxlen). - */ - max_length= str_value.numchars()*cs->mbmaxlen; - set_name(str, length, cs); - decimals=NOT_FIXED_DEC; + fix_charset_and_length_from_str_value(dv, metadata); // it is constant => can be used without fix_fields (and frequently used) fixed= 1; } + void fix_and_set_name_from_value(Derivation dv, const Metadata metadata) + { + fix_from_value(dv, metadata); + set_name(str_value.ptr(), str_value.length(), str_value.charset()); + } +protected: /* Just create an item and do not fill string representation */ - Item_string(CHARSET_INFO *cs, Derivation dv= DERIVATION_COERCIBLE) - : m_cs_specified(FALSE) + Item_string(THD *thd, CHARSET_INFO *cs, Derivation dv= DERIVATION_COERCIBLE): + Item_basic_constant(thd) { collation.set(cs, dv); max_length= 0; - set_name(NULL, 0, cs); + set_name(NULL, 0, system_charset_info); decimals= NOT_FIXED_DEC; fixed= 1; } - Item_string(const char *name_par, const char *str, uint length, - CHARSET_INFO *cs, Derivation dv= DERIVATION_COERCIBLE, - uint repertoire= MY_REPERTOIRE_UNICODE30) - : m_cs_specified(FALSE) +public: + Item_string(THD *thd, CHARSET_INFO *csi, const char *str_arg, uint length_arg): + Item_basic_constant(thd) { - str_value.set_or_copy_aligned(str, length, cs); - collation.set(cs, dv, repertoire); - max_length= str_value.numchars()*cs->mbmaxlen; - set_name(name_par, 0, cs); - decimals=NOT_FIXED_DEC; - // it is constant => can be used without fix_fields (and frequently used) + collation.set(csi, DERIVATION_COERCIBLE); + set_name(NULL, 0, system_charset_info); + decimals= NOT_FIXED_DEC; fixed= 1; + str_value.copy(str_arg, length_arg, csi); + max_length= str_value.numchars() * csi->mbmaxlen; } - /* - This is used in stored procedures to avoid memory leaks and - does a deep copy of its argument. - */ - void set_str_with_copy(const char *str_arg, uint length_arg) + // Constructors with the item name set from its value + Item_string(THD *thd, const char *str, uint length, CHARSET_INFO *cs, + Derivation dv, uint repertoire): Item_basic_constant(thd) { - str_value.copy(str_arg, length_arg, collation.collation); - max_length= str_value.numchars() * collation.collation->mbmaxlen; + str_value.set_or_copy_aligned(str, length, cs); + fix_and_set_name_from_value(dv, Metadata(&str_value, repertoire)); + } + Item_string(THD *thd, const char *str, uint length, + CHARSET_INFO *cs, Derivation dv= DERIVATION_COERCIBLE): + Item_basic_constant(thd) + { + str_value.set_or_copy_aligned(str, length, cs); + fix_and_set_name_from_value(dv, Metadata(&str_value)); + } + Item_string(THD *thd, const String *str, CHARSET_INFO *tocs, uint *conv_errors, + Derivation dv, uint repertoire): Item_basic_constant(thd) + { + if (str_value.copy(str, tocs, conv_errors)) + str_value.set("", 0, tocs); // EOM ? + str_value.mark_as_const(); + fix_and_set_name_from_value(dv, Metadata(&str_value, repertoire)); + } + // Constructors with an externally provided item name + Item_string(THD *thd, const char *name_par, const char *str, uint length, + CHARSET_INFO *cs, Derivation dv= DERIVATION_COERCIBLE): + Item_basic_constant(thd) + { + str_value.set_or_copy_aligned(str, length, cs); + fix_from_value(dv, Metadata(&str_value)); + set_name(name_par, 0, system_charset_info); + } + Item_string(THD *thd, const char *name_par, const char *str, uint length, + CHARSET_INFO *cs, Derivation dv, uint repertoire): + Item_basic_constant(thd) + { + str_value.set_or_copy_aligned(str, length, cs); + fix_from_value(dv, Metadata(&str_value, repertoire)); + set_name(name_par, 0, system_charset_info); } - void set_repertoire_from_value() + void print_value(String *to) const { - collation.repertoire= my_string_repertoire(str_value.charset(), - str_value.ptr(), - str_value.length()); + str_value.print(to); } enum Type type() const { return STRING_ITEM; } double val_real(); @@ -2645,13 +3099,15 @@ public: enum Item_result result_type () const { return STRING_RESULT; } enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; } bool basic_const_item() const { return 1; } - bool eq(const Item *item, bool binary_cmp) const; - Item *clone_item() + bool eq(const Item *item, bool binary_cmp) const { - return new Item_string(name, str_value.ptr(), - str_value.length(), collation.collation); + return str_eq(&str_value, item, binary_cmp); + } + Item *clone_item(THD *thd); + Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs) + { + return const_charset_converter(thd, tocs, true); } - Item *safe_charset_converter(CHARSET_INFO *tocs); inline void append(char *str, uint length) { str_value.append(str, length); @@ -2680,48 +3136,110 @@ public: the original query. @retval FALSE otherwise. */ - inline bool is_cs_specified() const + virtual bool is_cs_specified() const { - return m_cs_specified; + return false; } - /** - Set the value of m_cs_specified attribute. - - m_cs_specified attribute shows whether character-set-introducer was - explicitly specified in the original query for this text literal or - not. The attribute makes sense (is used) only for views. + String *check_well_formed_result(bool send_error) + { return Item::check_well_formed_result(&str_value, send_error); } - This operation is to be called from the parser during parsing an input - query. - */ - inline void set_cs_specified(bool cs_specified) + enum_field_types odbc_temporal_literal_type(const LEX_STRING *type_str) const { - m_cs_specified= cs_specified; + /* + If string is a reasonably short pure ASCII string literal, + try to parse known ODBC style date, time or timestamp literals, + e.g: + SELECT {d'2001-01-01'}; + SELECT {t'10:20:30'}; + SELECT {ts'2001-01-01 10:20:30'}; + */ + if (collation.repertoire == MY_REPERTOIRE_ASCII && + str_value.length() < MAX_DATE_STRING_REP_LENGTH * 4) + { + if (type_str->length == 1) + { + if (type_str->str[0] == 'd') /* {d'2001-01-01'} */ + return MYSQL_TYPE_DATE; + else if (type_str->str[0] == 't') /* {t'10:20:30'} */ + return MYSQL_TYPE_TIME; + } + else if (type_str->length == 2) /* {ts'2001-01-01 10:20:30'} */ + { + if (type_str->str[0] == 't' && type_str->str[1] == 's') + return MYSQL_TYPE_DATETIME; + } + } + return MYSQL_TYPE_STRING; // Not a temporal literal } -private: - bool m_cs_specified; }; -longlong -longlong_from_string_with_check(CHARSET_INFO *cs, const char *cptr, - const char *end); -double -double_from_string_with_check(CHARSET_INFO *cs, const char *cptr, - const char *end); +class Item_string_with_introducer :public Item_string +{ +public: + Item_string_with_introducer(THD *thd, const char *str, uint length, + CHARSET_INFO *cs): + Item_string(thd, str, length, cs) + { } + Item_string_with_introducer(THD *thd, const char *name_arg, + const char *str, uint length, CHARSET_INFO *tocs): + Item_string(thd, name_arg, str, length, tocs) + { } + virtual bool is_cs_specified() const + { + return true; + } +}; + + +class Item_string_sys :public Item_string +{ +public: + Item_string_sys(THD *thd, const char *str, uint length): + Item_string(thd, str, length, system_charset_info) + { } + Item_string_sys(THD *thd, const char *str): + Item_string(thd, str, strlen(str), system_charset_info) + { } +}; + + +class Item_string_ascii :public Item_string +{ +public: + Item_string_ascii(THD *thd, const char *str, uint length): + Item_string(thd, str, length, &my_charset_latin1, + DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII) + { } + Item_string_ascii(THD *thd, const char *str): + Item_string(thd, str, strlen(str), &my_charset_latin1, + DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII) + { } +}; + class Item_static_string_func :public Item_string { const char *func_name; public: - Item_static_string_func(const char *name_par, const char *str, uint length, - CHARSET_INFO *cs, - Derivation dv= DERIVATION_COERCIBLE) - :Item_string(NullS, str, length, cs, dv), func_name(name_par) + Item_static_string_func(THD *thd, const char *name_par, const char *str, + uint length, CHARSET_INFO *cs, + Derivation dv= DERIVATION_COERCIBLE): + Item_string(thd, NullS, str, length, cs, dv), func_name(name_par) + {} + Item_static_string_func(THD *thd, const char *name_par, + const String *str, + CHARSET_INFO *tocs, uint *conv_errors, + Derivation dv, uint repertoire): + Item_string(thd, str, tocs, conv_errors, dv, repertoire), + func_name(name_par) {} - Item *safe_charset_converter(CHARSET_INFO *tocs); + Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs) + { + return const_charset_converter(thd, tocs, true, func_name); + } virtual inline void print(String *str, enum_query_type query_type) { @@ -2740,9 +3258,9 @@ public: class Item_partition_func_safe_string: public Item_string { public: - Item_partition_func_safe_string(const char *name_arg, uint length, + Item_partition_func_safe_string(THD *thd, const char *name_arg, uint length, CHARSET_INFO *cs= NULL): - Item_string(name_arg, length, cs) + Item_string(thd, name_arg, length, cs) {} bool check_vcol_func_processor(uchar *arg) { @@ -2755,10 +3273,10 @@ class Item_return_date_time :public Item_partition_func_safe_string { enum_field_types date_time_field_type; public: - Item_return_date_time(const char *name_arg, uint length_arg, - enum_field_types field_type_arg) - :Item_partition_func_safe_string(name_arg, length_arg, &my_charset_bin), - date_time_field_type(field_type_arg) + Item_return_date_time(THD *thd, const char *name_arg, uint length_arg, + enum_field_types field_type_arg): + Item_partition_func_safe_string(thd, name_arg, length_arg, &my_charset_bin), + date_time_field_type(field_type_arg) { decimals= 0; } enum_field_types field_type() const { return date_time_field_type; } }; @@ -2767,11 +3285,13 @@ public: class Item_blob :public Item_partition_func_safe_string { public: - Item_blob(const char *name_arg, uint length) : - Item_partition_func_safe_string(name_arg, length, &my_charset_bin) + Item_blob(THD *thd, const char *name_arg, uint length): + Item_partition_func_safe_string(thd, name_arg, strlen(name_arg), &my_charset_bin) { max_length= length; } enum Type type() const { return TYPE_HOLDER; } enum_field_types field_type() const { return MYSQL_TYPE_BLOB; } + Field *create_field_for_schema(THD *thd, TABLE *table) + { return tmp_table_field_from_field_type(table, false, true); } }; @@ -2784,8 +3304,10 @@ public: class Item_empty_string :public Item_partition_func_safe_string { public: - Item_empty_string(const char *header,uint length, CHARSET_INFO *cs= NULL) : - Item_partition_func_safe_string("",0, cs ? cs : &my_charset_utf8_general_ci) + Item_empty_string(THD *thd, const char *header,uint length, + CHARSET_INFO *cs= NULL): + Item_partition_func_safe_string(thd, "", 0, + cs ? cs : &my_charset_utf8_general_ci) { name=(char*) header; max_length= length * collation.collation->mbmaxlen; } void make_field(Send_field *field); }; @@ -2795,9 +3317,9 @@ class Item_return_int :public Item_int { enum_field_types int_field_type; public: - Item_return_int(const char *name_arg, uint length, - enum_field_types field_type_arg, longlong value_arg= 0) - :Item_int(name_arg, value_arg, length), int_field_type(field_type_arg) + Item_return_int(THD *thd, const char *name_arg, uint length, + enum_field_types field_type_arg, longlong value_arg= 0): + Item_int(thd, name_arg, value_arg, length), int_field_type(field_type_arg) { unsigned_flag=1; } @@ -2811,24 +3333,33 @@ public: class Item_hex_constant: public Item_basic_constant { private: - void hex_string_init(const char *str, uint str_length); + void hex_string_init(THD *thd, const char *str, uint str_length); public: - Item_hex_constant() + Item_hex_constant(THD *thd): Item_basic_constant(thd) { - hex_string_init("", 0); + hex_string_init(thd, "", 0); } - Item_hex_constant(const char *str, uint str_length) + Item_hex_constant(THD *thd, const char *str, uint str_length): + Item_basic_constant(thd) { - hex_string_init(str, str_length); + hex_string_init(thd, str, str_length); } enum Type type() const { return VARBIN_ITEM; } enum Item_result result_type () const { return STRING_RESULT; } enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; } - virtual Item *safe_charset_converter(CHARSET_INFO *tocs); + virtual Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs) + { + return const_charset_converter(thd, tocs, true); + } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *arg) { return FALSE;} bool basic_const_item() const { return 1; } - bool eq(const Item *item, bool binary_cmp) const; + bool eq(const Item *item, bool binary_cmp) const + { + return item->basic_const_item() && item->type() == type() && + item->cast_to_int_type() == cast_to_int_type() && + str_value.bin_eq(&((Item_hex_constant*)item)->str_value); + } String *val_str(String*) { DBUG_ASSERT(fixed == 1); return &str_value; } }; @@ -2841,9 +3372,9 @@ public: class Item_hex_hybrid: public Item_hex_constant { public: - Item_hex_hybrid(): Item_hex_constant() {} - Item_hex_hybrid(const char *str, uint str_length): - Item_hex_constant(str, str_length) {} + Item_hex_hybrid(THD *thd): Item_hex_constant(thd) {} + Item_hex_hybrid(THD *thd, const char *str, uint str_length): + Item_hex_constant(thd, str, str_length) {} double val_real() { DBUG_ASSERT(fixed == 1); @@ -2876,24 +3407,18 @@ public: class Item_hex_string: public Item_hex_constant { public: - Item_hex_string(): Item_hex_constant() {} - Item_hex_string(const char *str, uint str_length): - Item_hex_constant(str, str_length) {} + Item_hex_string(THD *thd): Item_hex_constant(thd) {} + Item_hex_string(THD *thd, const char *str, uint str_length): + Item_hex_constant(thd, str, str_length) {} longlong val_int() { DBUG_ASSERT(fixed == 1); - return longlong_from_string_with_check(str_value.charset(), - str_value.ptr(), - str_value.ptr()+ - str_value.length()); + return longlong_from_string_with_check(&str_value); } double val_real() { DBUG_ASSERT(fixed == 1); - return double_from_string_with_check(str_value.charset(), - str_value.ptr(), - str_value.ptr() + - str_value.length()); + return double_from_string_with_check(&str_value); } my_decimal *val_decimal(my_decimal *decimal_value) { @@ -2913,31 +3438,462 @@ public: class Item_bin_string: public Item_hex_hybrid { public: - Item_bin_string(const char *str,uint str_length); + Item_bin_string(THD *thd, const char *str,uint str_length); }; -class Item_result_field :public Item /* Item with result field */ + +class Item_temporal_literal :public Item_basic_constant { +protected: + MYSQL_TIME cached_time; public: - Field *result_field; /* Save result here */ - Item_result_field() :result_field(0) {} - // Constructor used for Item_sum/Item_cond_and/or (see Item comment) - Item_result_field(THD *thd, Item_result_field *item): - Item(thd, item), result_field(item->result_field) - {} - ~Item_result_field() {} /* Required with gcc 2.95 */ - Field *get_tmp_table_field() { return result_field; } - Field *tmp_table_field(TABLE *t_arg) { return result_field; } - table_map used_tables() const { return 1; } - virtual void fix_length_and_dec()=0; - void set_result_field(Field *field) { result_field= field; } - bool is_result_field() { return 1; } - void save_in_result_field(bool no_conversions) + /** + Constructor for Item_date_literal. + @param ltime DATE value. + */ + Item_temporal_literal(THD *thd, MYSQL_TIME *ltime): Item_basic_constant(thd) { - save_in_field(result_field, no_conversions); + collation.set(&my_charset_numeric, DERIVATION_NUMERIC, MY_REPERTOIRE_ASCII); + decimals= 0; + cached_time= *ltime; } - void cleanup(); + Item_temporal_literal(THD *thd, MYSQL_TIME *ltime, uint dec_arg): + Item_basic_constant(thd) + { + collation.set(&my_charset_numeric, DERIVATION_NUMERIC, MY_REPERTOIRE_ASCII); + decimals= dec_arg; + cached_time= *ltime; + } + bool basic_const_item() const { return true; } + bool const_item() const { return true; } + enum Type type() const { return DATE_ITEM; } + bool eq(const Item *item, bool binary_cmp) const; + enum Item_result result_type () const { return STRING_RESULT; } + Item_result cmp_type() const { return TIME_RESULT; } + + bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *arg) { return FALSE;} + + bool is_null() + { return is_null_from_temporal(); } + bool get_date_with_sql_mode(MYSQL_TIME *to); + String *val_str(String *str) + { return val_string_from_date(str); } + longlong val_int() + { return val_int_from_date(); } + double val_real() + { return val_real_from_date(); } + my_decimal *val_decimal(my_decimal *decimal_value) + { return val_decimal_from_date(decimal_value); } + int save_in_field(Field *field, bool no_conversions) + { return save_date_in_field(field); } +}; + + +/** + DATE'2010-01-01' +*/ +class Item_date_literal: public Item_temporal_literal +{ +public: + Item_date_literal(THD *thd, MYSQL_TIME *ltime) + :Item_temporal_literal(thd, ltime) + { + max_length= MAX_DATE_WIDTH; + fixed= 1; + /* + If date has zero month or day, it can return NULL in case of + NO_ZERO_DATE or NO_ZERO_IN_DATE. + We can't just check the current sql_mode here in constructor, + because sql_mode can change in case of prepared statements + between PREPARE and EXECUTE. + */ + maybe_null= !ltime->month || !ltime->day; + } + enum_field_types field_type() const { return MYSQL_TYPE_DATE; } + void print(String *str, enum_query_type query_type); + Item *clone_item(THD *thd); + bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); +}; + + +/** + TIME'10:10:10' +*/ +class Item_time_literal: public Item_temporal_literal +{ +public: + Item_time_literal(THD *thd, MYSQL_TIME *ltime, uint dec_arg): + Item_temporal_literal(thd, ltime, dec_arg) + { + max_length= MIN_TIME_WIDTH + (decimals ? decimals + 1 : 0); + fixed= 1; + } + enum_field_types field_type() const { return MYSQL_TYPE_TIME; } + void print(String *str, enum_query_type query_type); + Item *clone_item(THD *thd); + bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); +}; + + +/** + TIMESTAMP'2001-01-01 10:20:30' +*/ +class Item_datetime_literal: public Item_temporal_literal +{ +public: + Item_datetime_literal(THD *thd, MYSQL_TIME *ltime, uint dec_arg): + Item_temporal_literal(thd, ltime, dec_arg) + { + max_length= MAX_DATETIME_WIDTH + (decimals ? decimals + 1 : 0); + fixed= 1; + // See the comment on maybe_null in Item_date_literal + maybe_null= !ltime->month || !ltime->day; + } + enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; } + void print(String *str, enum_query_type query_type); + Item *clone_item(THD *thd); + bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); +}; + + +/** + An error-safe counterpart for Item_date_literal +*/ +class Item_date_literal_for_invalid_dates: public Item_date_literal +{ + /** + During equal field propagation we can replace non-temporal constants + found in equalities to their native temporal equivalents: + WHERE date_column='2001-01-01' ... -> + WHERE date_column=DATE'2001-01-01' ... + + This is done to make the eqial field propagation code handle mixtures of + different temporal types in the same expressions easier (MDEV-8706), e.g. + WHERE LENGTH(date_column)=10 AND date_column=TIME'00:00:00' + + Item_date_literal_for_invalid_dates::get_date() + (unlike the regular Item_date_literal::get_date()) + does not check the result for NO_ZERO_IN_DATE and NO_ZER_DATE, + always returns success (false), and does not produce error/warning messages. + + We need these _for_invalid_dates classes to be able to rewrite: + SELECT * FROM t1 WHERE date_column='0000-00-00' ... + to: + SELECT * FROM t1 WHERE date_column=DATE'0000-00-00' ... + + to avoid returning NULL value instead of '0000-00-00' even + in sql_mode=TRADITIONAL. + */ +public: + Item_date_literal_for_invalid_dates(THD *thd, MYSQL_TIME *ltime) + :Item_date_literal(thd, ltime) { } + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) + { + *ltime= cached_time; + return (null_value= false); + } +}; + + +/** + An error-safe counterpart for Item_datetime_literal + (see Item_date_literal_for_invalid_dates for comments) +*/ +class Item_datetime_literal_for_invalid_dates: public Item_datetime_literal +{ +public: + Item_datetime_literal_for_invalid_dates(THD *thd, + MYSQL_TIME *ltime, uint dec_arg) + :Item_datetime_literal(thd, ltime, dec_arg) { } + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) + { + *ltime= cached_time; + return (null_value= false); + } +}; + + +/** + Array of items, e.g. function or aggerate function arguments. +*/ +class Item_args +{ +protected: + Item **args, *tmp_arg[2]; + uint arg_count; + void set_arguments(THD *thd, List<Item> &list); + bool walk_args(Item_processor processor, bool walk_subquery, uchar *arg) + { + for (uint i= 0; i < arg_count; i++) + { + if (args[i]->walk(processor, walk_subquery, arg)) + return true; + } + return false; + } + bool transform_args(THD *thd, Item_transformer transformer, uchar *arg); + void propagate_equal_fields(THD *, const Item::Context &, COND_EQUAL *); +public: + Item_args(void) + :args(NULL), arg_count(0) + { } + Item_args(Item *a) + :args(tmp_arg), arg_count(1) + { + args[0]= a; + } + Item_args(Item *a, Item *b) + :args(tmp_arg), arg_count(2) + { + args[0]= a; args[1]= b; + } + Item_args(Item *a, Item *b, Item *c) + { + arg_count= 0; + if ((args= (Item**) sql_alloc(sizeof(Item*) * 3))) + { + arg_count= 3; + args[0]= a; args[1]= b; args[2]= c; + } + } + Item_args(Item *a, Item *b, Item *c, Item *d) + { + arg_count= 0; + if ((args= (Item**) sql_alloc(sizeof(Item*) * 4))) + { + arg_count= 4; + args[0]= a; args[1]= b; args[2]= c; args[3]= d; + } + } + Item_args(Item *a, Item *b, Item *c, Item *d, Item* e) + { + arg_count= 5; + if ((args= (Item**) sql_alloc(sizeof(Item*) * 5))) + { + arg_count= 5; + args[0]= a; args[1]= b; args[2]= c; args[3]= d; args[4]= e; + } + } + Item_args(THD *thd, List<Item> &list) + { + set_arguments(thd, list); + } + Item_args(THD *thd, const Item_args *other); + inline Item **arguments() const { return args; } + inline uint argument_count() const { return arg_count; } + inline void remove_arguments() { arg_count=0; } +}; + + +class Used_tables_and_const_cache +{ +public: + /* + In some cases used_tables_cache is not what used_tables() return + so the method should be used where one need used tables bit map + (even internally in Item_func_* code). + */ + table_map used_tables_cache; + bool const_item_cache; + + Used_tables_and_const_cache() + :used_tables_cache(0), + const_item_cache(true) + { } + Used_tables_and_const_cache(const Used_tables_and_const_cache *other) + :used_tables_cache(other->used_tables_cache), + const_item_cache(other->const_item_cache) + { } + void used_tables_and_const_cache_init() + { + used_tables_cache= 0; + const_item_cache= true; + } + void used_tables_and_const_cache_join(const Item *item) + { + used_tables_cache|= item->used_tables(); + const_item_cache&= item->const_item(); + } + void used_tables_and_const_cache_update_and_join(Item *item) + { + item->update_used_tables(); + used_tables_and_const_cache_join(item); + } + /* + Call update_used_tables() for all "argc" items in the array "argv" + and join with the current cache. + "this" must be initialized with a constructor or + re-initialized with used_tables_and_const_cache_init(). + */ + void used_tables_and_const_cache_update_and_join(uint argc, Item **argv) + { + for (uint i=0 ; i < argc ; i++) + used_tables_and_const_cache_update_and_join(argv[i]); + } + /* + Call update_used_tables() for all items in the list + and join with the current cache. + "this" must be initialized with a constructor or + re-initialized with used_tables_and_const_cache_init(). + */ + void used_tables_and_const_cache_update_and_join(List<Item> &list) + { + List_iterator_fast<Item> li(list); + Item *item; + while ((item=li++)) + used_tables_and_const_cache_update_and_join(item); + } +}; + + +/** + An abstract class representing common features of + regular functions and aggregate functions. +*/ +class Item_func_or_sum: public Item_result_field, + public Item_args, + public Used_tables_and_const_cache +{ + bool agg_item_collations(DTCollation &c, const char *name, + Item **items, uint nitems, + uint flags, int item_sep); + bool agg_item_set_converter(const DTCollation &coll, const char *fname, + Item **args, uint nargs, + uint flags, int item_sep); +protected: + /* + Collect arguments' character sets together. + We allow to apply automatic character set conversion in some cases. + The conditions when conversion is possible are: + - arguments A and B have different charsets + - A wins according to coercibility rules + (i.e. a column is stronger than a string constant, + an explicit COLLATE clause is stronger than a column) + - character set of A is either superset for character set of B, + or B is a string constant which can be converted into the + character set of A without data loss. + + If all of the above is true, then it's possible to convert + B into the character set of A, and then compare according + to the collation of A. + + For functions with more than two arguments: + + collect(A,B,C) ::= collect(collect(A,B),C) + + Since this function calls THD::change_item_tree() on the passed Item ** + pointers, it is necessary to pass the original Item **'s, not copies. + Otherwise their values will not be properly restored (see BUG#20769). + If the items are not consecutive (eg. args[2] and args[5]), use the + item_sep argument, ie. + + agg_item_charsets(coll, fname, &args[2], 2, flags, 3) + */ + bool agg_arg_charsets(DTCollation &c, Item **items, uint nitems, + uint flags, int item_sep) + { + if (agg_item_collations(c, func_name(), items, nitems, flags, item_sep)) + return true; + + return agg_item_set_converter(c, func_name(), items, nitems, + flags, item_sep); + } + /* + Aggregate arguments for string result, e.g: CONCAT(a,b) + - convert to @@character_set_connection if all arguments are numbers + - allow DERIVATION_NONE + */ + bool agg_arg_charsets_for_string_result(DTCollation &c, + Item **items, uint nitems, + int item_sep= 1) + { + uint flags= MY_COLL_ALLOW_SUPERSET_CONV | + MY_COLL_ALLOW_COERCIBLE_CONV | + MY_COLL_ALLOW_NUMERIC_CONV; + return agg_arg_charsets(c, items, nitems, flags, item_sep); + } + /* + Aggregate arguments for string result, when some comparison + is involved internally, e.g: REPLACE(a,b,c) + - convert to @@character_set_connection if all arguments are numbers + - disallow DERIVATION_NONE + */ + bool agg_arg_charsets_for_string_result_with_comparison(DTCollation &c, + Item **items, + uint nitems, + int item_sep= 1) + { + uint flags= MY_COLL_ALLOW_SUPERSET_CONV | + MY_COLL_ALLOW_COERCIBLE_CONV | + MY_COLL_ALLOW_NUMERIC_CONV | + MY_COLL_DISALLOW_NONE; + return agg_arg_charsets(c, items, nitems, flags, item_sep); + } + + /* + Aggregate arguments for comparison, e.g: a=b, a LIKE b, a RLIKE b + - don't convert to @@character_set_connection if all arguments are numbers + - don't allow DERIVATION_NONE + */ + bool agg_arg_charsets_for_comparison(DTCollation &c, + Item **items, uint nitems, + int item_sep= 1) + { + uint flags= MY_COLL_ALLOW_SUPERSET_CONV | + MY_COLL_ALLOW_COERCIBLE_CONV | + MY_COLL_DISALLOW_NONE; + return agg_arg_charsets(c, items, nitems, flags, item_sep); + } + + +public: + // This method is used by Arg_comparator + bool agg_arg_charsets_for_comparison(CHARSET_INFO **cs, Item **a, Item **b) + { + DTCollation tmp; + if (tmp.set((*a)->collation, (*b)->collation, MY_COLL_CMP_CONV) || + tmp.derivation == DERIVATION_NONE) + { + my_error(ER_CANT_AGGREGATE_2COLLATIONS,MYF(0), + (*a)->collation.collation->name, + (*a)->collation.derivation_name(), + (*b)->collation.collation->name, + (*b)->collation.derivation_name(), + func_name()); + return true; + } + if (agg_item_set_converter(tmp, func_name(), + a, 1, MY_COLL_CMP_CONV, 1) || + agg_item_set_converter(tmp, func_name(), + b, 1, MY_COLL_CMP_CONV, 1)) + return true; + *cs= tmp.collation; + return false; + } + +public: + Item_func_or_sum(THD *thd): Item_result_field(thd), Item_args() {} + Item_func_or_sum(THD *thd, Item *a): Item_result_field(thd), Item_args(a) { } + Item_func_or_sum(THD *thd, Item *a, Item *b): + Item_result_field(thd), Item_args(a, b) { } + Item_func_or_sum(THD *thd, Item *a, Item *b, Item *c): + Item_result_field(thd), Item_args(a, b, c) { } + Item_func_or_sum(THD *thd, Item *a, Item *b, Item *c, Item *d): + Item_result_field(thd), Item_args(a, b, c, d) { } + Item_func_or_sum(THD *thd, Item *a, Item *b, Item *c, Item *d, Item *e): + Item_result_field(thd), Item_args(a, b, c, d, e) { } + Item_func_or_sum(THD *thd, Item_func_or_sum *item): + Item_result_field(thd, item), Item_args(thd, item), + Used_tables_and_const_cache(item) { } + Item_func_or_sum(THD *thd, List<Item> &list): + Item_result_field(thd), Item_args(thd, list) { } + bool walk(Item_processor processor, bool walk_subquery, uchar *arg) + { + if (walk_args(processor, walk_subquery, arg)) + return true; + return (this->*processor)(arg); + } /* This method is used for debug purposes to print the name of an item to the debug log. The second use of this method is as @@ -2947,7 +3903,7 @@ public: should not be used for runtime type identification, use enum {Sum}Functype and Item_func::functype()/Item_sum::sum_func() instead. - Added here, to the parent class of both Item_func and Item_sum_func. + Added here, to the parent class of both Item_func and Item_sum. NOTE: for Items inherited from Item_sum, func_name() return part of function name till first argument (including '(') to make difference in @@ -2955,6 +3911,9 @@ public: also to make printing of items inherited from Item_sum uniform. */ virtual const char *func_name() const= 0; + virtual void fix_length_and_dec()= 0; + bool const_item() const { return const_item_cache; } + table_map used_tables() const { return used_tables_cache; } }; @@ -2965,14 +3924,13 @@ protected: bool set_properties_only; // the item doesn't need full fix_fields public: enum Ref_Type { REF, DIRECT_REF, VIEW_REF, OUTER_REF, AGGREGATE_REF }; - Field *result_field; /* Save result here */ Item **ref; bool reference_trough_name; - Item_ref(Name_resolution_context *context_arg, + Item_ref(THD *thd, Name_resolution_context *context_arg, const char *db_arg, const char *table_name_arg, - const char *field_name_arg) - :Item_ident(context_arg, db_arg, table_name_arg, field_name_arg), - set_properties_only(0), result_field(0), ref(0), reference_trough_name(1) {} + const char *field_name_arg): + Item_ident(thd, context_arg, db_arg, table_name_arg, field_name_arg), + set_properties_only(0), ref(0), reference_trough_name(1) {} /* This constructor is used in two scenarios: A) *item = NULL @@ -2987,15 +3945,15 @@ public: TODO we probably fix a superset of problems like in BUG#6658. Check this with Bar, and if we have a more broader set of problems like this. */ - Item_ref(Name_resolution_context *context_arg, Item **item, + Item_ref(THD *thd, Name_resolution_context *context_arg, Item **item, const char *table_name_arg, const char *field_name_arg, bool alias_name_used_arg= FALSE); - Item_ref(TABLE_LIST *view_arg, Item **item, + Item_ref(THD *thd, TABLE_LIST *view_arg, Item **item, const char *field_name_arg, bool alias_name_used_arg= FALSE); /* Constructor need to process subselect with temporary tables (see Item) */ Item_ref(THD *thd, Item_ref *item) - :Item_ident(thd, item), set_properties_only(0), result_field(item->result_field), ref(item->ref) {} + :Item_ident(thd, item), set_properties_only(0), ref(item->ref) {} enum Type type() const { return REF_ITEM; } enum Type real_type() const { return ref ? (*ref)->type() : REF_ITEM; } @@ -3022,9 +3980,17 @@ public: bool send(Protocol *prot, String *tmp); void make_field(Send_field *field); bool fix_fields(THD *, Item **); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); int save_in_field(Field *field, bool no_conversions); - void save_org_in_field(Field *field); + void save_org_in_field(Field *field, fast_field_copier optimizer_data); + fast_field_copier setup_fast_field_copier(Field *field) + { return (*ref)->setup_fast_field_copier(field); } +#if MARIADB_VERSION_ID < 100300 + CHARSET_INFO *charset_for_protocol(void) const + { + return (*ref)->charset_for_protocol(); + } +#endif enum Item_result result_type () const { return (*ref)->result_type(); } enum_field_types field_type() const { return (*ref)->field_type(); } Field *get_tmp_table_field() @@ -3032,6 +3998,22 @@ public: Item *get_tmp_table_item(THD *thd); table_map used_tables() const; void update_used_tables(); + COND *build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref) + { + /* + normilize_cond() replaced all conditions of type + WHERE/HAVING field + to: + WHERE/HAVING field<>0 + By the time of a build_equal_items() call, all such conditions should + already be replaced. No Item_ref referencing to Item_field are possible. + */ + DBUG_ASSERT(real_type() != FIELD_ITEM); + return Item_ident::build_equal_items(thd, inherited, link_item_fields, + cond_equal_ref); + } bool const_item() const { return (*ref)->const_item(); @@ -3040,8 +4022,6 @@ public: { return depended_from ? 0 : (*ref)->not_null_tables(); } - void set_result_field(Field *field) { result_field= field; } - bool is_result_field() { return 1; } void save_in_result_field(bool no_conversions) { (*ref)->save_in_field(result_field, no_conversions); @@ -3058,8 +4038,8 @@ public: else return FALSE; } - Item* transform(Item_transformer, uchar *arg); - Item* compile(Item_analyzer analyzer, uchar **arg_p, + Item* transform(THD *thd, Item_transformer, uchar *arg); + Item* compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, Item_transformer transformer, uchar *arg_t); bool enumerate_field_refs_processor(uchar *arg) { return (*ref)->enumerate_field_refs_processor(arg); } @@ -3073,8 +4053,8 @@ public: } virtual void print(String *str, enum_query_type query_type); void cleanup(); - Item_field *filed_for_view_update() - { return (*ref)->filed_for_view_update(); } + Item_field *field_for_view_update() + { return (*ref)->field_for_view_update(); } virtual Ref_Type ref_type() { return REF; } // Row emulation: forwarding of ROW-related calls to ref @@ -3133,22 +4113,29 @@ public: class Item_direct_ref :public Item_ref { public: - Item_direct_ref(Name_resolution_context *context_arg, Item **item, + Item_direct_ref(THD *thd, Name_resolution_context *context_arg, Item **item, const char *table_name_arg, const char *field_name_arg, - bool alias_name_used_arg= FALSE) - :Item_ref(context_arg, item, table_name_arg, - field_name_arg, alias_name_used_arg) + bool alias_name_used_arg= FALSE): + Item_ref(thd, context_arg, item, table_name_arg, + field_name_arg, alias_name_used_arg) {} /* Constructor need to process subselect with temporary tables (see Item) */ Item_direct_ref(THD *thd, Item_direct_ref *item) : Item_ref(thd, item) {} - Item_direct_ref(TABLE_LIST *view_arg, Item **item, + Item_direct_ref(THD *thd, TABLE_LIST *view_arg, Item **item, const char *field_name_arg, - bool alias_name_used_arg= FALSE) - :Item_ref(view_arg, item, field_name_arg, - alias_name_used_arg) + bool alias_name_used_arg= FALSE): + Item_ref(thd, view_arg, item, field_name_arg, + alias_name_used_arg) {} + bool fix_fields(THD *thd, Item **it) + { + if ((!(*ref)->fixed && (*ref)->fix_fields(thd, ref)) || + (*ref)->check_cols(1)) + return TRUE; + return Item_ref::fix_fields(thd, it); + } void save_val(Field *to); double val_real(); longlong val_int(); @@ -3170,9 +4157,9 @@ class Item_direct_ref_to_ident :public Item_direct_ref { Item_ident *ident; public: - Item_direct_ref_to_ident(Item_ident *item) - :Item_direct_ref(item->context, (Item**)&item, item->table_name, item->field_name, - FALSE) + Item_direct_ref_to_ident(THD *thd, Item_ident *item): + Item_direct_ref(thd, item->context, (Item**)&item, item->table_name, + item->field_name, FALSE) { ident= item; ref= (Item**)&ident; @@ -3196,6 +4183,7 @@ public: class Item_cache; class Expression_cache; +class Expression_cache_tracker; /** The objects of this class can store its values in an expression cache. @@ -3223,19 +4211,20 @@ private: void init_on_demand(); public: - Item_cache_wrapper(Item *item_arg); + Item_cache_wrapper(THD *thd, Item *item_arg); ~Item_cache_wrapper(); - const char *func_name() const { return "<expr_cache>"; } enum Type type() const { return EXPR_CACHE_ITEM; } enum Type real_type() const { return orig_item->type(); } bool set_cache(THD *thd); + Expression_cache_tracker* init_tracker(MEM_ROOT *mem_root); bool fix_fields(THD *thd, Item **it); - void fix_length_and_dec() {} void cleanup(); + Item *get_orig_item() const { return orig_item; } + /* Methods of getting value which should be cached in the cache */ void save_val(Field *to); double val_real(); @@ -3246,7 +4235,8 @@ public: bool is_null(); bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); bool send(Protocol *protocol, String *buffer); - void save_org_in_field(Field *field) + void save_org_in_field(Field *field, + fast_field_copier data __attribute__ ((__unused__))) { save_val(field); } @@ -3266,9 +4256,9 @@ public: Item *it= ((Item *) item)->real_item(); return orig_item->eq(it, binary_cmp); } - void fix_after_pullout(st_select_lex *new_parent, Item **refptr) + void fix_after_pullout(st_select_lex *new_parent, Item **refptr, bool merge) { - orig_item->fix_after_pullout(new_parent, &orig_item); + orig_item->fix_after_pullout(new_parent, &orig_item, merge); } int save_in_field(Field *to, bool no_conversions); enum Item_result result_type () const { return orig_item->result_type(); } @@ -3287,8 +4277,8 @@ public: } bool enumerate_field_refs_processor(uchar *arg) { return orig_item->enumerate_field_refs_processor(arg); } - Item_field *filed_for_view_update() - { return orig_item->filed_for_view_update(); } + Item_field *field_for_view_update() + { return orig_item->field_for_view_update(); } /* Row emulation: forwarding of ROW-related calls to orig_item */ uint cols() @@ -3351,11 +4341,12 @@ class Item_direct_view_ref :public Item_direct_ref } public: - Item_direct_view_ref(Name_resolution_context *context_arg, Item **item, + Item_direct_view_ref(THD *thd, Name_resolution_context *context_arg, + Item **item, const char *table_name_arg, const char *field_name_arg, - TABLE_LIST *view_arg) - :Item_direct_ref(context_arg, item, table_name_arg, field_name_arg), + TABLE_LIST *view_arg): + Item_direct_ref(thd, context_arg, item, table_name_arg, field_name_arg), item_equal(0), view(view_arg), null_ref_table(NULL) { @@ -3367,6 +4358,8 @@ public: bool eq(const Item *item, bool binary_cmp) const; Item *get_tmp_table_item(THD *thd) { + if (const_item()) + return copy_or_same(thd); Item *item= Item_ref::get_tmp_table_item(thd); item->name= name; return item; @@ -3375,9 +4368,8 @@ public: Item_equal *get_item_equal() { return item_equal; } void set_item_equal(Item_equal *item_eq) { item_equal= item_eq; } Item_equal *find_item_equal(COND_EQUAL *cond_equal); - bool subst_argument_checker(uchar **arg); - Item *equal_fields_propagator(uchar *arg); - Item *replace_equal_field(uchar *arg); + Item* propagate_equal_fields(THD *, const Context &, COND_EQUAL *); + Item *replace_equal_field(THD *thd, uchar *arg); table_map used_tables() const; void update_used_tables(); table_map not_null_tables() const; @@ -3453,7 +4445,8 @@ public: return Item_direct_ref::get_date(ltime, fuzzydate); } bool send(Protocol *protocol, String *buffer); - void save_org_in_field(Field *field) + void save_org_in_field(Field *field, + fast_field_copier data __attribute__ ((__unused__))) { if (check_null_ref()) field->set_null(); @@ -3500,30 +4493,30 @@ public: */ bool found_in_select_list; bool found_in_group_by; - Item_outer_ref(Name_resolution_context *context_arg, - Item_field *outer_field_arg) - :Item_direct_ref(context_arg, 0, outer_field_arg->table_name, - outer_field_arg->field_name), + Item_outer_ref(THD *thd, Name_resolution_context *context_arg, + Item_field *outer_field_arg): + Item_direct_ref(thd, context_arg, 0, outer_field_arg->table_name, + outer_field_arg->field_name), outer_ref(outer_field_arg), in_sum_func(0), found_in_select_list(0), found_in_group_by(0) { ref= &outer_ref; set_properties(); - fixed= 0; + fixed= 0; /* reset flag set in set_properties() */ } - Item_outer_ref(Name_resolution_context *context_arg, Item **item, + Item_outer_ref(THD *thd, Name_resolution_context *context_arg, Item **item, const char *table_name_arg, const char *field_name_arg, - bool alias_name_used_arg) - :Item_direct_ref(context_arg, item, table_name_arg, field_name_arg, - alias_name_used_arg), + bool alias_name_used_arg): + Item_direct_ref(thd, context_arg, item, table_name_arg, field_name_arg, + alias_name_used_arg), outer_ref(0), in_sum_func(0), found_in_select_list(1), found_in_group_by(0) {} void save_in_result_field(bool no_conversions) { - outer_ref->save_org_in_field(result_field); + outer_ref->save_org_in_field(result_field, NULL); } bool fix_fields(THD *, Item **); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); table_map used_tables() const { return (*ref)->const_item() ? 0 : OUTER_REF_TABLE_BIT; @@ -3551,11 +4544,11 @@ class Item_ref_null_helper: public Item_ref protected: Item_in_subselect* owner; public: - Item_ref_null_helper(Name_resolution_context *context_arg, + Item_ref_null_helper(THD *thd, Name_resolution_context *context_arg, Item_in_subselect* master, Item **item, - const char *table_name_arg, const char *field_name_arg) - :Item_ref(context_arg, item, table_name_arg, field_name_arg), - owner(master) {} + const char *table_name_arg, const char *field_name_arg): + Item_ref(thd, context_arg, item, table_name_arg, field_name_arg), + owner(master) {} void save_val(Field *to); double val_real(); longlong val_int(); @@ -3580,8 +4573,8 @@ class Item_int_with_ref :public Item_int { Item *ref; public: - Item_int_with_ref(longlong i, Item *ref_arg, bool unsigned_arg) : - Item_int(i), ref(ref_arg) + Item_int_with_ref(THD *thd, longlong i, Item *ref_arg, bool unsigned_arg): + Item_int(thd, i), ref(ref_arg) { unsigned_flag= unsigned_arg; } @@ -3589,7 +4582,7 @@ public: { return ref->save_in_field(field, no_conversions); } - Item *clone_item(); + Item *clone_item(THD *thd); virtual Item *real_item() { return ref; } }; @@ -3653,18 +4646,15 @@ protected: stores metadata information about the original class as well as a pointer to it. */ - Item_copy(Item *i) + Item_copy(THD *thd, Item *i): Item(thd) { item= i; null_value=maybe_null=item->maybe_null; - decimals=item->decimals; - max_length=item->max_length; + Type_std_attributes::set(item); name=item->name; cached_field_type= item->field_type(); cached_result_type= item->result_type(); - unsigned_flag= item->unsigned_flag; fixed= item->fixed; - collation.set(item->collation); } public: @@ -3718,7 +4708,7 @@ public: class Item_copy_string : public Item_copy { public: - Item_copy_string (Item *item) : Item_copy(item) {} + Item_copy_string(THD *thd, Item *item_arg): Item_copy(thd, item_arg) {} String *val_str(String*); my_decimal *val_decimal(my_decimal *); @@ -3820,12 +4810,12 @@ class Item_default_value : public Item_field { public: Item *arg; - Item_default_value(Name_resolution_context *context_arg) - :Item_field(context_arg, (const char *)NULL, (const char *)NULL, + Item_default_value(THD *thd, Name_resolution_context *context_arg) + :Item_field(thd, context_arg, (const char *)NULL, (const char *)NULL, (const char *)NULL), arg(NULL) {} - Item_default_value(Name_resolution_context *context_arg, Item *a) - :Item_field(context_arg, (const char *)NULL, (const char *)NULL, + Item_default_value(THD *thd, Name_resolution_context *context_arg, Item *a) + :Item_field(thd, context_arg, (const char *)NULL, (const char *)NULL, (const char *)NULL), arg(a) {} enum Type type() const { return DEFAULT_VALUE_ITEM; } @@ -3835,13 +4825,17 @@ public: int save_in_field(Field *field_arg, bool no_conversions); table_map used_tables() const { return (table_map)0L; } + Field *get_tmp_table_field() { return 0; } + Item *get_tmp_table_item(THD *thd) { return this; } + Item_field *field_for_view_update() { return 0; } + bool walk(Item_processor processor, bool walk_subquery, uchar *args) { return (arg && arg->walk(processor, walk_subquery, args)) || (this->*processor)(args); } - Item *transform(Item_transformer transformer, uchar *args); + Item *transform(THD *thd, Item_transformer transformer, uchar *args); }; /* @@ -3858,8 +4852,8 @@ class Item_insert_value : public Item_field { public: Item *arg; - Item_insert_value(Name_resolution_context *context_arg, Item *a) - :Item_field(context_arg, (const char *)NULL, (const char *)NULL, + Item_insert_value(THD *thd, Name_resolution_context *context_arg, Item *a) + :Item_field(thd, context_arg, (const char *)NULL, (const char *)NULL, (const char *)NULL), arg(a) {} bool eq(const Item *item, bool binary_cmp) const; @@ -3876,13 +4870,15 @@ public: */ table_map used_tables() const { return RAND_TABLE_BIT; } + Item_field *field_for_view_update() { return 0; } + bool walk(Item_processor processor, bool walk_subquery, uchar *args) { return arg->walk(processor, walk_subquery, args) || (this->*processor)(args); } bool check_partition_func_processor(uchar *int_arg) {return TRUE;} - bool check_vcol_func_processor(uchar *arg) + bool check_vcol_func_processor(uchar *arg_arg) { return trace_unsupported_by_check_vcol_func_processor("values"); } @@ -3916,11 +4912,11 @@ public: /* Pointer to Table_trigger_list object for table of this trigger */ Table_triggers_list *triggers; - Item_trigger_field(Name_resolution_context *context_arg, + Item_trigger_field(THD *thd, Name_resolution_context *context_arg, row_version_type row_ver_arg, const char *field_name_arg, ulong priv, const bool ro) - :Item_field(context_arg, + :Item_field(thd, context_arg, (const char *)NULL, (const char *)NULL, field_name_arg), row_version(row_ver_arg), field_idx((uint)-1), original_privilege(priv), want_privilege(priv), table_grants(NULL), read_only (ro) @@ -3970,7 +4966,8 @@ private: BEFORE INSERT of BEFORE UPDATE trigger. */ bool read_only; - virtual bool check_vcol_func_processor(uchar *arg) +public: + bool check_vcol_func_processor(uchar *arg) { return trace_unsupported_by_check_vcol_func_processor("trigger"); } @@ -4004,7 +5001,8 @@ protected: */ bool value_cached; public: - Item_cache(): + Item_cache(THD *thd): + Item_basic_constant(thd), example(0), cached_field(0), cached_field_type(MYSQL_TYPE_STRING), value_cached(0) @@ -4013,7 +5011,8 @@ public: maybe_null= 1; null_value= 1; } - Item_cache(enum_field_types field_type_arg): + Item_cache(THD *thd, enum_field_types field_type_arg): + Item_basic_constant(thd), example(0), cached_field(0), cached_field_type(field_type_arg), value_cached(0) @@ -4023,22 +5022,19 @@ public: null_value= 1; } - virtual bool allocate(uint i) { return 0; } - virtual bool setup(Item *item) + virtual bool allocate(THD *thd, uint i) { return 0; } + virtual bool setup(THD *thd, Item *item) { example= item; - max_length= item->max_length; - decimals= item->decimals; - collation.set(item->collation); - unsigned_flag= item->unsigned_flag; + Type_std_attributes::set(item); if (item->type() == FIELD_ITEM) cached_field= ((Item_field *)item)->field; return 0; }; enum Type type() const { return CACHE_ITEM; } enum_field_types field_type() const { return cached_field_type; } - static Item_cache* get_cache(const Item *item); - static Item_cache* get_cache(const Item* item, const Item_result type); + static Item_cache* get_cache(THD *thd, const Item *item); + static Item_cache* get_cache(THD *thd, const Item* item, const Item_result type); virtual void keep_array() {} virtual void print(String *str, enum_query_type query_type); bool eq_def(Field *field) @@ -4066,9 +5062,9 @@ public: virtual void store(Item *item); virtual bool cache_value()= 0; bool basic_const_item() const - { return test(example && example->basic_const_item());} + { return MY_TEST(example && example->basic_const_item()); } virtual void clear() { null_value= TRUE; value_cached= FALSE; } - bool is_null() { return null_value; } + bool is_null() { return !has_value(); } virtual bool is_expensive() { if (value_cached) @@ -4089,7 +5085,13 @@ public: return TRUE; return (this->*processor)(arg); } - virtual Item *safe_charset_converter(CHARSET_INFO *tocs); + virtual Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs); + void split_sum_func2_example(THD *thd, Item **ref_pointer_array, + List<Item> &fields, uint flags) + { + example->split_sum_func2(thd, ref_pointer_array, fields, &example, flags); + } + Item *get_example() const { return example; } }; @@ -4098,10 +5100,10 @@ class Item_cache_int: public Item_cache protected: longlong value; public: - Item_cache_int(): Item_cache(MYSQL_TYPE_LONGLONG), + Item_cache_int(THD *thd): Item_cache(thd, MYSQL_TYPE_LONGLONG), value(0) {} - Item_cache_int(enum_field_types field_type_arg): - Item_cache(field_type_arg), value(0) {} + Item_cache_int(THD *thd, enum_field_types field_type_arg): + Item_cache(thd, field_type_arg), value(0) {} double val_real(); longlong val_int(); @@ -4116,11 +5118,12 @@ public: class Item_cache_temporal: public Item_cache_int { public: - Item_cache_temporal(enum_field_types field_type_arg); + Item_cache_temporal(THD *thd, enum_field_types field_type_arg); String* val_str(String *str); my_decimal *val_decimal(my_decimal *); longlong val_int(); - longlong val_temporal_packed(); + longlong val_datetime_packed(); + longlong val_time_packed(); double val_real(); bool cache_value(); bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); @@ -4132,12 +5135,7 @@ public: is a constant and need not be optimized further. Important when storing packed datetime values. */ - Item *clone_item() - { - Item_cache_temporal *item= new Item_cache_temporal(cached_field_type); - item->store_packed(value, example); - return item; - } + Item *clone_item(THD *thd); }; @@ -4145,7 +5143,7 @@ class Item_cache_real: public Item_cache { double value; public: - Item_cache_real(): Item_cache(MYSQL_TYPE_DOUBLE), + Item_cache_real(THD *thd): Item_cache(thd, MYSQL_TYPE_DOUBLE), value(0) {} double val_real(); @@ -4162,7 +5160,7 @@ class Item_cache_decimal: public Item_cache protected: my_decimal decimal_value; public: - Item_cache_decimal(): Item_cache(MYSQL_TYPE_NEWDECIMAL) {} + Item_cache_decimal(THD *thd): Item_cache(thd, MYSQL_TYPE_NEWDECIMAL) {} double val_real(); longlong val_int(); @@ -4180,8 +5178,8 @@ class Item_cache_str: public Item_cache bool is_varbinary; public: - Item_cache_str(const Item *item) : - Item_cache(item->field_type()), value(0), + Item_cache_str(THD *thd, const Item *item): + Item_cache(thd, item->field_type()), value(0), is_varbinary(item->type() == FIELD_ITEM && cached_field_type == MYSQL_TYPE_VARCHAR && !((const Item_field *) item)->field->has_charset()) @@ -4198,26 +5196,50 @@ public: bool cache_value(); }; + +class Item_cache_str_for_nullif: public Item_cache_str +{ +public: + Item_cache_str_for_nullif(THD *thd, const Item *item) + :Item_cache_str(thd, item) + { } + Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs) + { + /** + Item_cache_str::safe_charset_converter() returns a new Item_cache + with Item_func_conv_charset installed on "example". The original + Item_cache is not referenced (neither directly nor recursively) + from the result of Item_cache_str::safe_charset_converter(). + + For NULLIF() purposes we need a different behavior: + we need a new instance of Item_func_conv_charset, + with the original Item_cache referenced in args[0]. See MDEV-9181. + */ + return Item::safe_charset_converter(thd, tocs); + } +}; + + class Item_cache_row: public Item_cache { Item_cache **values; uint item_count; bool save_array; public: - Item_cache_row() - :Item_cache(), values(0), item_count(2), + Item_cache_row(THD *thd): + Item_cache(thd), values(0), item_count(2), save_array(0) {} /* 'allocate' used only in row transformer, to preallocate space for row cache. */ - bool allocate(uint num); + bool allocate(THD *thd, uint num); /* 'setup' is needed only by row => it not called by simple row subselect (only by IN subselect (in subselect optimizer)) */ - bool setup(Item *item); + bool setup(THD *thd, Item *item); void store(Item *item); void illegal_method_call(const char *); void make_field(Send_field *) diff --git a/sql/item_buff.cc b/sql/item_buff.cc index 86e0fd32774..d1134525f7b 100644 --- a/sql/item_buff.cc +++ b/sql/item_buff.cc @@ -22,6 +22,7 @@ Buffers to save and compare item values */ +#include <my_global.h> #include "sql_priv.h" /* It is necessary to include set_var.h instead of item.h because there @@ -71,7 +72,7 @@ Cached_item::~Cached_item() {} Cached_item_str::Cached_item_str(THD *thd, Item *arg) :item(arg), - value_max_length(min(arg->max_length, thd->variables.max_sort_length)), + value_max_length(MY_MIN(arg->max_length, thd->variables.max_sort_length)), value(value_max_length) {} @@ -81,7 +82,7 @@ bool Cached_item_str::cmp(void) bool tmp; if ((res=item->val_str(&tmp_value))) - res->length(min(res->length(), value_max_length)); + res->length(MY_MIN(res->length(), value_max_length)); if (null_value != item->null_value) { if ((null_value= item->null_value)) @@ -173,12 +174,3 @@ bool Cached_item_decimal::cmp() return FALSE; } - -/***************************************************************************** -** Instansiate templates -*****************************************************************************/ - -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class List<Cached_item>; -template class List_iterator<Cached_item>; -#endif diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 3af79c23ee7..4e572b95ea3 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2013, Oracle and/or its affiliates. - Copyright (c) 2009, 2016, MariaDB + Copyright (c) 2009, 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 @@ -26,6 +26,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include <m_ctype.h> #include "sql_select.h" @@ -33,47 +34,6 @@ #include "sql_time.h" // make_truncated_value_warning #include "sql_base.h" // dynamic_column_error_message -static Item_result item_store_type(Item_result a, Item *item, - my_bool unsigned_flag) -{ - Item_result b= item->result_type(); - - if (a == STRING_RESULT || b == STRING_RESULT) - return STRING_RESULT; - else if (a == REAL_RESULT || b == REAL_RESULT) - return REAL_RESULT; - else if (a == DECIMAL_RESULT || b == DECIMAL_RESULT || - unsigned_flag != item->unsigned_flag) - return DECIMAL_RESULT; - else - return INT_RESULT; -} - -static void agg_result_type(Item_result *type, Item **items, uint nitems) -{ - Item **item, **item_end; - my_bool unsigned_flag= 0; - - *type= STRING_RESULT; - /* Skip beginning NULL items */ - for (item= items, item_end= item + nitems; item < item_end; item++) - { - if ((*item)->type() != Item::NULL_ITEM) - { - *type= (*item)->result_type(); - unsigned_flag= (*item)->unsigned_flag; - item++; - break; - } - } - /* Combine result types. Note: NULL items don't affect the result */ - for (; item < item_end; item++) - { - if ((*item)->type() != Item::NULL_ITEM) - *type= item_store_type(*type, *item, unsigned_flag); - } -} - /** find an temporal type (item) that others will be converted to @@ -168,7 +128,7 @@ static int agg_cmp_type(Item_result *type, for (uint i= 1 ; i < nitems ; i++) { unsigned_count+= items[i]->unsigned_flag; - type[0]= item_cmp_type(type[0], items[i]->cmp_type()); + type[0]= item_cmp_type(type[0], items[i]); /* When aggregating types of two row expressions we have to check that they have the same cardinality and that each component @@ -196,6 +156,22 @@ static int agg_cmp_type(Item_result *type, @param[in] items array of items to aggregate the type from @paran[in] nitems number of items in the array + @param[in] treat_bit_as_number - if BIT should be aggregated to a non-BIT + counterpart as a LONGLONG number or as a VARBINARY string. + + Currently behaviour depends on the function: + - LEAST/GREATEST treat BIT as VARBINARY when + aggregating with a non-BIT counterpart. + Note, UNION also works this way. + + - CASE, COALESCE, IF, IFNULL treat BIT as LONGLONG when + aggregating with a non-BIT counterpart; + + This inconsistency may be changed in the future. See MDEV-8867. + + Note, independently from "treat_bit_as_number": + - a single BIT argument gives BIT as a result + - two BIT couterparts give BIT as a result @details This function aggregates field types from the array of items. Found type is supposed to be used later as the result field type @@ -209,14 +185,50 @@ static int agg_cmp_type(Item_result *type, @return aggregated field type. */ -enum_field_types agg_field_type(Item **items, uint nitems) +enum_field_types agg_field_type(Item **items, uint nitems, + bool treat_bit_as_number) { uint i; - if (!nitems || items[0]->result_type() == ROW_RESULT ) - return (enum_field_types)-1; + if (!nitems || items[0]->result_type() == ROW_RESULT) + { + DBUG_ASSERT(0); + return MYSQL_TYPE_NULL; + } enum_field_types res= items[0]->field_type(); + uint unsigned_count= items[0]->unsigned_flag; for (i= 1 ; i < nitems ; i++) - res= Field::field_type_merge(res, items[i]->field_type()); + { + enum_field_types cur= items[i]->field_type(); + if (treat_bit_as_number && + ((res == MYSQL_TYPE_BIT) ^ (cur == MYSQL_TYPE_BIT))) + { + if (res == MYSQL_TYPE_BIT) + res= MYSQL_TYPE_LONGLONG; // BIT + non-BIT + else + cur= MYSQL_TYPE_LONGLONG; // non-BIT + BIT + } + res= Field::field_type_merge(res, cur); + unsigned_count+= items[i]->unsigned_flag; + } + switch (res) { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_BIT: + if (unsigned_count != 0 && unsigned_count != nitems) + { + /* + If all arguments are of INT-alike type but have different + unsigned_flag, then convert to DECIMAL. + */ + return MYSQL_TYPE_NEWDECIMAL; + } + default: + break; + } return res; } @@ -242,98 +254,28 @@ static uint collect_cmp_types(Item **items, uint nitems, bool skip_nulls= FALSE) { uint i; uint found_types; - Item_result left_result= items[0]->cmp_type(); + Item_result left_cmp_type= items[0]->cmp_type(); DBUG_ASSERT(nitems > 1); found_types= 0; for (i= 1; i < nitems ; i++) { if (skip_nulls && items[i]->type() == Item::NULL_ITEM) continue; // Skip NULL constant items - if ((left_result == ROW_RESULT || + if ((left_cmp_type == ROW_RESULT || items[i]->cmp_type() == ROW_RESULT) && cmp_row_type(items[0], items[i])) return 0; - found_types|= 1U << (uint)item_cmp_type(left_result, - items[i]->cmp_type()); + found_types|= 1U << (uint) item_cmp_type(left_cmp_type, items[i]); } /* Even if all right-hand items are NULLs and we are skipping them all, we need at least one type bit in the found_type bitmask. */ if (skip_nulls && !found_types) - found_types= 1U << (uint)left_result; + found_types= 1U << (uint) left_cmp_type; return found_types; } -static void my_coll_agg_error(DTCollation &c1, DTCollation &c2, - const char *fname) -{ - my_error(ER_CANT_AGGREGATE_2COLLATIONS, MYF(0), - c1.collation->name,c1.derivation_name(), - c2.collation->name,c2.derivation_name(), - fname); -} - - -Item_bool_func2* Eq_creator::create(Item *a, Item *b) const -{ - return new Item_func_eq(a, b); -} - -Item_bool_func2* Eq_creator::create_swap(Item *a, Item *b) const -{ - return new Item_func_eq(b, a); -} - -Item_bool_func2* Ne_creator::create(Item *a, Item *b) const -{ - return new Item_func_ne(a, b); -} - -Item_bool_func2* Ne_creator::create_swap(Item *a, Item *b) const -{ - return new Item_func_ne(b, a); -} - -Item_bool_func2* Gt_creator::create(Item *a, Item *b) const -{ - return new Item_func_gt(a, b); -} - -Item_bool_func2* Gt_creator::create_swap(Item *a, Item *b) const -{ - return new Item_func_lt(b, a); -} - -Item_bool_func2* Lt_creator::create(Item *a, Item *b) const -{ - return new Item_func_lt(a, b); -} - -Item_bool_func2* Lt_creator::create_swap(Item *a, Item *b) const -{ - return new Item_func_gt(b, a); -} - -Item_bool_func2* Ge_creator::create(Item *a, Item *b) const -{ - return new Item_func_ge(a, b); -} - -Item_bool_func2* Ge_creator::create_swap(Item *a, Item *b) const -{ - return new Item_func_le(b, a); -} - -Item_bool_func2* Le_creator::create(Item *a, Item *b) const -{ - return new Item_func_le(a, b); -} - -Item_bool_func2* Le_creator::create_swap(Item *a, Item *b) const -{ - return new Item_func_ge(b, a); -} /* Test functions @@ -481,8 +423,7 @@ static bool convert_const_to_int(THD *thd, Item_field *field_item, my_bitmap_map *old_maps[2]; ulonglong UNINIT_VAR(orig_field_val); /* original field value if valid */ - LINT_INIT(old_maps[0]); - LINT_INIT(old_maps[1]); + LINT_INIT_STRUCT(old_maps); /* table->read_set may not be set if we come here from a CREATE TABLE */ if (table && table->read_set) @@ -514,8 +455,8 @@ static bool convert_const_to_int(THD *thd, Item_field *field_item, if (0 == field_cmp) { - Item *tmp= new Item_int_with_ref(field->val_int(), *item, - test(field->flags & UNSIGNED_FLAG)); + Item *tmp= new (thd->mem_root) Item_int_with_ref(thd, field->val_int(), *item, + MY_TEST(field->flags & UNSIGNED_FLAG)); if (tmp) thd->change_item_tree(item, tmp); result= 1; // Item was replaced @@ -537,49 +478,17 @@ static bool convert_const_to_int(THD *thd, Item_field *field_item, } -void Item_bool_func2::fix_length_and_dec() +/* + Make a special case of compare with fields to get nicer comparisons + of bigint numbers with constant string. + This directly contradicts the manual (number and a string should + be compared as doubles), but seems to provide more + "intuitive" behavior in some cases (but less intuitive in others). +*/ +void Item_func::convert_const_compared_to_int_field(THD *thd) { - max_length= 1; // Function returns 0 or 1 - - /* - As some compare functions are generated after sql_yacc, - we have to check for out of memory conditions here - */ - if (!args[0] || !args[1]) - return; - - /* - We allow to convert to Unicode character sets in some cases. - The conditions when conversion is possible are: - - arguments A and B have different charsets - - A wins according to coercibility rules - - character set of A is superset for character set of B - - If all of the above is true, then it's possible to convert - B into the character set of A, and then compare according - to the collation of A. - */ - - DTCollation coll; - if (args[0]->cmp_type() == STRING_RESULT && - args[1]->cmp_type() == STRING_RESULT && - agg_arg_charsets_for_comparison(coll, args, 2)) - return; - - args[0]->cmp_context= args[1]->cmp_context= - item_cmp_type(args[0]->result_type(), args[1]->result_type()); - - /* - Make a special case of compare with fields to get nicer comparisons - of bigint numbers with constant string. - This directly contradicts the manual (number and a string should - be compared as doubles), but seems to provide more - "intuitive" behavior in some cases (but less intuitive in others). - - But disable conversion in case of LIKE function. - */ - THD *thd= current_thd; - if (functype() != LIKE_FUNC && !thd->lex->is_ps_or_view_context_analysis()) + DBUG_ASSERT(arg_count >= 2); // Item_func_nullif has arg_count == 3 + if (!thd->lex->is_ps_or_view_context_analysis()) { int field; if (args[field= 0]->real_item()->type() == FIELD_ITEM || @@ -587,16 +496,48 @@ void Item_bool_func2::fix_length_and_dec() { Item_field *field_item= (Item_field*) (args[field]->real_item()); if ((field_item->field_type() == MYSQL_TYPE_LONGLONG || - field_item->field_type() == MYSQL_TYPE_YEAR) && - convert_const_to_int(thd, field_item, &args[!field])) - args[0]->cmp_context= args[1]->cmp_context= INT_RESULT; + field_item->field_type() == MYSQL_TYPE_YEAR)) + convert_const_to_int(thd, field_item, &args[!field]); } } - set_cmp_func(); } -int Arg_comparator::set_compare_func(Item_result_field *item, Item_result type) +bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp) +{ + DBUG_ASSERT(arg_count >= 2); // Item_func_nullif has arg_count == 3 + + if (args[0]->cmp_type() == STRING_RESULT && + args[1]->cmp_type() == STRING_RESULT) + { + DTCollation tmp; + if (agg_arg_charsets_for_comparison(tmp, args, 2)) + return true; + cmp->m_compare_collation= tmp.collation; + } + // Convert constants when compared to int/year field + DBUG_ASSERT(functype() != LIKE_FUNC); + convert_const_compared_to_int_field(thd); + + return cmp->set_cmp_func(this, &args[0], &args[1], true); +} + + +void Item_bool_rowready_func2::fix_length_and_dec() +{ + max_length= 1; // Function returns 0 or 1 + + /* + As some compare functions are generated after sql_yacc, + we have to check for out of memory conditions here + */ + if (!args[0] || !args[1]) + return; + setup_args_and_comparator(current_thd, &cmp); +} + + +int Arg_comparator::set_compare_func(Item_func_or_sum *item, Item_result type) { owner= item; func= comparator_matrix[type] @@ -604,7 +545,7 @@ int Arg_comparator::set_compare_func(Item_result_field *item, Item_result type) switch (type) { case TIME_RESULT: - cmp_collation.collation= &my_charset_numeric; + m_compare_collation= &my_charset_numeric; break; case ROW_RESULT: { @@ -624,49 +565,12 @@ int Arg_comparator::set_compare_func(Item_result_field *item, Item_result type) my_error(ER_OPERAND_COLUMNS, MYF(0), (*a)->element_index(i)->cols()); return 1; } - if (comparators[i].set_cmp_func(owner, (*a)->addr(i), (*b)->addr(i), - set_null)) + if (comparators[i].set_cmp_func(owner, (*a)->addr(i), + (*b)->addr(i), set_null)) return 1; } break; } - case STRING_RESULT: - { - /* - We must set cmp_charset here as we may be called from for an automatic - generated item, like in natural join - */ - if (cmp_collation.set((*a)->collation, (*b)->collation) || - cmp_collation.derivation == DERIVATION_NONE) - { - my_coll_agg_error((*a)->collation, (*b)->collation, - owner->func_name()); - return 1; - } - if (cmp_collation.collation == &my_charset_bin) - { - /* - We are using BLOB/BINARY/VARBINARY, change to compare byte by byte, - without removing end space - */ - if (func == &Arg_comparator::compare_string) - func= &Arg_comparator::compare_binary_string; - else if (func == &Arg_comparator::compare_e_string) - func= &Arg_comparator::compare_e_binary_string; - - /* - As this is binary compassion, mark all fields that they can't be - transformed. Otherwise we would get into trouble with comparisons - like: - WHERE col= 'j' AND col LIKE BINARY 'j' - which would be transformed to: - WHERE col= 'j' - */ - (*a)->walk(&Item::set_no_const_sub, FALSE, (uchar*) 0); - (*b)->walk(&Item::set_no_const_sub, FALSE, (uchar*) 0); - } - break; - } case INT_RESULT: { if (func == &Arg_comparator::compare_int_signed) @@ -685,13 +589,14 @@ int Arg_comparator::set_compare_func(Item_result_field *item, Item_result type) } break; } + case STRING_RESULT: case DECIMAL_RESULT: break; case REAL_RESULT: { if ((*a)->decimals < NOT_FIXED_DEC && (*b)->decimals < NOT_FIXED_DEC) { - precision= 5 / log_10[max((*a)->decimals, (*b)->decimals) + 1]; + precision= 5 / log_10[MY_MAX((*a)->decimals, (*b)->decimals) + 1]; if (func == &Arg_comparator::compare_real) func= &Arg_comparator::compare_real_fixed; else if (func == &Arg_comparator::compare_e_real) @@ -699,71 +604,10 @@ int Arg_comparator::set_compare_func(Item_result_field *item, Item_result type) } break; } - case IMPOSSIBLE_RESULT: - DBUG_ASSERT(0); - break; } return 0; } -/** - Parse date provided in a string to a MYSQL_TIME. - - @param[in] thd Thread handle - @param[in] str A string to convert - @param[in] warn_type Type of the timestamp for issuing the warning - @param[in] warn_name Field name for issuing the warning - @param[out] l_time The MYSQL_TIME objects is initialized. - - Parses a date provided in the string str into a MYSQL_TIME object. - The date is used for comparison, that is fuzzy dates are allowed - independently of sql_mode. - If the string contains an incorrect date or doesn't correspond to a date at - all then a warning is issued. The warn_type and the warn_name arguments are - used as the name and the type of the field when issuing the warning. If any - input was discarded (trailing or non-timestamp-y characters), return value - will be TRUE. - - @return Status flag - @retval FALSE Success. - @retval True Indicates failure. -*/ - -bool get_mysql_time_from_str(THD *thd, String *str, timestamp_type warn_type, - const char *warn_name, MYSQL_TIME *l_time) -{ - bool value; - int error; - enum_mysql_timestamp_type timestamp_type; - int flags= TIME_FUZZY_DATES | MODE_INVALID_DATES; - ErrConvString err(str); - - if (warn_type == MYSQL_TIMESTAMP_TIME) - flags|= TIME_TIME_ONLY; - - timestamp_type= - str_to_datetime(str->charset(), str->ptr(), str->length(), - l_time, flags, &error); - - if (timestamp_type > MYSQL_TIMESTAMP_ERROR) - /* - Do not return yet, we may still want to throw a "trailing garbage" - warning. - */ - value= FALSE; - else - { - value= TRUE; - error= 1; /* force warning */ - } - - if (error > 0) - make_truncated_value_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - &err, warn_type, warn_name); - - return value; -} - /** Prepare the comparator (set the comparison function) for comparing @@ -778,34 +622,72 @@ bool get_mysql_time_from_str(THD *thd, String *str, timestamp_type warn_type, items, holding the cached converted value of the original (constant) item. */ -int Arg_comparator::set_cmp_func(Item_result_field *owner_arg, - Item **a1, Item **a2, - Item_result type) +int Arg_comparator::set_cmp_func(Item_func_or_sum *owner_arg, + Item **a1, Item **a2) { THD *thd= current_thd; owner= owner_arg; set_null= set_null && owner_arg; a= a1; b= a2; + m_compare_type= item_cmp_type(*a1, *a2); - if (type == STRING_RESULT && + if (m_compare_type == STRING_RESULT && (*a)->result_type() == STRING_RESULT && (*b)->result_type() == STRING_RESULT) { - DTCollation coll; - coll.set((*a)->collation.collation); - if (agg_item_set_converter(coll, owner->func_name(), - b, 1, MY_COLL_CMP_CONV, 1)) + /* + We must set cmp_collation here as we may be called from for an automatic + generated item, like in natural join + */ + if (owner->agg_arg_charsets_for_comparison(&m_compare_collation, a, b)) return 1; } - if (type == INT_RESULT && + + if (m_compare_type == TIME_RESULT) + { + enum_field_types f_type= a[0]->field_type_for_temporal_comparison(b[0]); + if (f_type == MYSQL_TYPE_TIME) + { + func= is_owner_equal_func() ? &Arg_comparator::compare_e_time : + &Arg_comparator::compare_time; + } + else + { + func= is_owner_equal_func() ? &Arg_comparator::compare_e_datetime : + &Arg_comparator::compare_datetime; + } + return 0; + } + + if (m_compare_type == REAL_RESULT && + (((*a)->result_type() == DECIMAL_RESULT && !(*a)->const_item() && + (*b)->result_type() == STRING_RESULT && (*b)->const_item()) || + ((*b)->result_type() == DECIMAL_RESULT && !(*b)->const_item() && + (*a)->result_type() == STRING_RESULT && (*a)->const_item()))) + { + /* + <non-const decimal expression> <cmp> <const string expression> + or + <const string expression> <cmp> <non-const decimal expression> + + Do comparison as decimal rather than float, in order not to lose precision. + */ + m_compare_type= DECIMAL_RESULT; + } + + if (m_compare_type == INT_RESULT && (*a)->field_type() == MYSQL_TYPE_YEAR && (*b)->field_type() == MYSQL_TYPE_YEAR) - type= TIME_RESULT; + { + m_compare_type= TIME_RESULT; + func= is_owner_equal_func() ? &Arg_comparator::compare_e_datetime : + &Arg_comparator::compare_datetime; + } - a= cache_converted_constant(thd, a, &a_cache, type); - b= cache_converted_constant(thd, b, &b_cache, type); - return set_compare_func(owner_arg, type); + a= cache_converted_constant(thd, a, &a_cache, m_compare_type); + b= cache_converted_constant(thd, b, &b_cache, m_compare_type); + return set_compare_func(owner_arg, m_compare_type); } @@ -839,8 +721,8 @@ Item** Arg_comparator::cache_converted_constant(THD *thd_arg, Item **value, (*value)->const_item() && type != (*value)->result_type() && type != TIME_RESULT) { - Item_cache *cache= Item_cache::get_cache(*value, type); - cache->setup(*value); + Item_cache *cache= Item_cache::get_cache(thd_arg, *value, type); + cache->setup(thd_arg, *value); *cache_item= cache; return cache_item; } @@ -848,17 +730,6 @@ Item** Arg_comparator::cache_converted_constant(THD *thd_arg, Item **value, } -void Arg_comparator::set_datetime_cmp_func(Item_result_field *owner_arg, - Item **a1, Item **b1) -{ - owner= owner_arg; - a= a1; - b= b1; - a_cache= 0; - b_cache= 0; - func= comparator_matrix[TIME_RESULT][is_owner_equal_func()]; -} - /** Retrieves correct DATETIME value from given item. @@ -892,32 +763,11 @@ void Arg_comparator::set_datetime_cmp_func(Item_result_field *owner_arg, longlong get_datetime_value(THD *thd, Item ***item_arg, Item **cache_arg, - Item *warn_item, bool *is_null) + enum_field_types f_type, bool *is_null) { longlong UNINIT_VAR(value); Item *item= **item_arg; - enum_field_types f_type= item->cmp_type() == TIME_RESULT ? - item->field_type() : warn_item->field_type(); - - if (item->result_type() == INT_RESULT && - item->cmp_type() == TIME_RESULT && - item->type() == Item::CACHE_ITEM) - { - /* it's our Item_cache_temporal, as created below */ - DBUG_ASSERT(is_temporal_type(((Item_cache *) item)->field_type())); - value= ((Item_cache_temporal*) item)->val_temporal_packed(); - } - else - { - MYSQL_TIME ltime; - uint fuzzydate= TIME_FUZZY_DATES | TIME_INVALID_DATES; - if (f_type == MYSQL_TYPE_TIME) - fuzzydate|= TIME_TIME_ONLY; - if (item->get_date(<ime, fuzzydate)) - value= 0; /* invalid date */ - else - value= pack_time(<ime); - } + value= item->val_temporal_packed(f_type); if ((*is_null= item->null_value)) return ~(ulonglong) 0; if (cache_arg && item->const_item() && @@ -926,7 +776,7 @@ get_datetime_value(THD *thd, Item ***item_arg, Item **cache_arg, if (!thd) thd= current_thd; - Item_cache_temporal *cache= new Item_cache_temporal(f_type); + Item_cache_temporal *cache= new (thd->mem_root) Item_cache_temporal(thd, f_type); cache->store_packed(value, item); *cache_arg= cache; *item_arg= cache_arg; @@ -952,7 +802,7 @@ get_datetime_value(THD *thd, Item ***item_arg, Item **cache_arg, 1 a > b */ -int Arg_comparator::compare_datetime() +int Arg_comparator::compare_temporal(enum_field_types type) { bool a_is_null, b_is_null; longlong a_value, b_value; @@ -961,12 +811,12 @@ int Arg_comparator::compare_datetime() owner->null_value= 1; /* Get DATE/DATETIME/TIME value of the 'a' item. */ - a_value= get_datetime_value(0, &a, &a_cache, *b, &a_is_null); + a_value= get_datetime_value(0, &a, &a_cache, type, &a_is_null); if (a_is_null) return -1; /* Get DATE/DATETIME/TIME value of the 'b' item. */ - b_value= get_datetime_value(0, &b, &b_cache, *a, &b_is_null); + b_value= get_datetime_value(0, &b, &b_cache, type, &b_is_null); if (b_is_null) return -1; @@ -978,16 +828,16 @@ int Arg_comparator::compare_datetime() return a_value < b_value ? -1 : a_value > b_value ? 1 : 0; } -int Arg_comparator::compare_e_datetime() +int Arg_comparator::compare_e_temporal(enum_field_types type) { bool a_is_null, b_is_null; longlong a_value, b_value; /* Get DATE/DATETIME/TIME value of the 'a' item. */ - a_value= get_datetime_value(0, &a, &a_cache, *b, &a_is_null); + a_value= get_datetime_value(0, &a, &a_cache, type, &a_is_null); /* Get DATE/DATETIME/TIME value of the 'b' item. */ - b_value= get_datetime_value(0, &b, &b_cache, *a, &b_is_null); + b_value= get_datetime_value(0, &b, &b_cache, type, &b_is_null); return a_is_null || b_is_null ? a_is_null == b_is_null : a_value == b_value; } @@ -1001,39 +851,7 @@ int Arg_comparator::compare_string() { if (set_null) owner->null_value= 0; - return sortcmp(res1,res2,cmp_collation.collation); - } - } - if (set_null) - owner->null_value= 1; - return -1; -} - - -/** - Compare strings byte by byte. End spaces are also compared. - - @retval - <0 *a < *b - @retval - 0 *b == *b - @retval - >0 *a > *b -*/ - -int Arg_comparator::compare_binary_string() -{ - String *res1,*res2; - if ((res1= (*a)->val_str(&value1))) - { - if ((res2= (*b)->val_str(&value2))) - { - if (set_null) - owner->null_value= 0; - uint res1_length= res1->length(); - uint res2_length= res2->length(); - int cmp= memcmp(res1->ptr(), res2->ptr(), min(res1_length,res2_length)); - return cmp ? cmp : (int) (res1_length - res2_length); + return sortcmp(res1, res2, compare_collation()); } } if (set_null) @@ -1053,19 +871,8 @@ int Arg_comparator::compare_e_string() res1= (*a)->val_str(&value1); res2= (*b)->val_str(&value2); if (!res1 || !res2) - return test(res1 == res2); - return test(sortcmp(res1, res2, cmp_collation.collation) == 0); -} - - -int Arg_comparator::compare_e_binary_string() -{ - String *res1,*res2; - res1= (*a)->val_str(&value1); - res2= (*b)->val_str(&value2); - if (!res1 || !res2) - return test(res1 == res2); - return test(stringcmp(res1, res2) == 0); + return MY_TEST(res1 == res2); + return MY_TEST(sortcmp(res1, res2, compare_collation()) == 0); } @@ -1120,8 +927,8 @@ int Arg_comparator::compare_e_real() double val1= (*a)->val_real(); double val2= (*b)->val_real(); if ((*a)->null_value || (*b)->null_value) - return test((*a)->null_value && (*b)->null_value); - return test(val1 == val2); + return MY_TEST((*a)->null_value && (*b)->null_value); + return MY_TEST(val1 == val2); } int Arg_comparator::compare_e_decimal() @@ -1130,8 +937,8 @@ int Arg_comparator::compare_e_decimal() my_decimal *val1= (*a)->val_decimal(&decimal1); my_decimal *val2= (*b)->val_decimal(&decimal2); if ((*a)->null_value || (*b)->null_value) - return test((*a)->null_value && (*b)->null_value); - return test(my_decimal_cmp(val1, val2) == 0); + return MY_TEST((*a)->null_value && (*b)->null_value); + return MY_TEST(my_decimal_cmp(val1, val2) == 0); } @@ -1169,8 +976,8 @@ int Arg_comparator::compare_e_real_fixed() double val1= (*a)->val_real(); double val2= (*b)->val_real(); if ((*a)->null_value || (*b)->null_value) - return test((*a)->null_value && (*b)->null_value); - return test(val1 == val2 || fabs(val1 - val2) < precision); + return MY_TEST((*a)->null_value && (*b)->null_value); + return MY_TEST(val1 == val2 || fabs(val1 - val2) < precision); } @@ -1281,8 +1088,8 @@ int Arg_comparator::compare_e_int() longlong val1= (*a)->val_int(); longlong val2= (*b)->val_int(); if ((*a)->null_value || (*b)->null_value) - return test((*a)->null_value && (*b)->null_value); - return test(val1 == val2); + return MY_TEST((*a)->null_value && (*b)->null_value); + return MY_TEST(val1 == val2); } /** @@ -1293,8 +1100,8 @@ int Arg_comparator::compare_e_int_diff_signedness() longlong val1= (*a)->val_int(); longlong val2= (*b)->val_int(); if ((*a)->null_value || (*b)->null_value) - return test((*a)->null_value && (*b)->null_value); - return (val1 >= 0) && test(val1 == val2); + return MY_TEST((*a)->null_value && (*b)->null_value); + return (val1 >= 0) && MY_TEST(val1 == val2); } int Arg_comparator::compare_row() @@ -1326,9 +1133,13 @@ int Arg_comparator::compare_row() case Item_func::GT_FUNC: case Item_func::GE_FUNC: return -1; // <, <=, > and >= always fail on NULL - default: // EQ_FUNC - if (((Item_bool_func2*)owner)->abort_on_null) + case Item_func::EQ_FUNC: + if (((Item_func_eq*)owner)->abort_on_null) return -1; // We do not need correct NULL returning + break; + default: + DBUG_ASSERT(0); + break; } was_null= 1; owner->null_value= 0; @@ -1423,11 +1234,12 @@ bool Item_in_optimizer::is_top_level_item() } -void Item_in_optimizer::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_in_optimizer::fix_after_pullout(st_select_lex *new_parent, + Item **ref, bool merge) { DBUG_ASSERT(fixed); /* This will re-calculate attributes of our Item_in_subselect: */ - Item_bool_func::fix_after_pullout(new_parent, ref); + Item_bool_func::fix_after_pullout(new_parent, ref, merge); /* Then, re-calculate not_null_tables_cache: */ eval_not_null_tables(NULL); @@ -1476,7 +1288,7 @@ void Item_in_optimizer::restore_first_argument() } -bool Item_in_optimizer::fix_left(THD *thd, Item **ref) +bool Item_in_optimizer::fix_left(THD *thd) { DBUG_ENTER("Item_in_optimizer::fix_left"); /* @@ -1501,7 +1313,7 @@ bool Item_in_optimizer::fix_left(THD *thd, Item **ref) args[0]= ((Item_in_subselect *)args[1])->left_expr; } if ((!(*ref0)->fixed && (*ref0)->fix_fields(thd, ref0)) || - (!cache && !(cache= Item_cache::get_cache(*ref0)))) + (!cache && !(cache= Item_cache::get_cache(thd, *ref0)))) DBUG_RETURN(1); /* During fix_field() expression could be substituted. @@ -1511,7 +1323,7 @@ bool Item_in_optimizer::fix_left(THD *thd, Item **ref) args[0]= (*ref0); DBUG_PRINT("info", ("actual fix fields")); - cache->setup(args[0]); + cache->setup(thd, args[0]); if (cache->cols() == 1) { DBUG_ASSERT(args[0]->type() != ROW_ITEM); @@ -1558,6 +1370,12 @@ bool Item_in_optimizer::fix_left(THD *thd, Item **ref) cache->store(args[0]); cache->cache_value(); } + if (args[1]->fixed) + { + /* to avoid overriding is called to update left expression */ + used_tables_and_const_cache_join(args[1]); + with_sum_func= with_sum_func || args[1]->with_sum_func; + } DBUG_RETURN(0); } @@ -1575,15 +1393,16 @@ bool Item_in_optimizer::fix_fields(THD *thd, Item **ref) if (args[1]->type() == Item::SUBSELECT_ITEM) sub= (Item_subselect *)args[1]; - if (fix_left(thd, ref)) + if (fix_left(thd)) return TRUE; if (args[0]->maybe_null) maybe_null=1; if (!args[1]->fixed && args[1]->fix_fields(thd, args+1)) return TRUE; - if ((sub && ((col= args[0]->cols()) != sub->engine->cols())) || - (!sub && (args[1]->cols() != (col= 1)))) + if (!invisible_mode() && + ((sub && ((col= args[0]->cols()) != sub->engine->cols())) || + (!sub && (args[1]->cols() != (col= 1))))) { my_error(ER_OPERAND_COLUMNS, MYF(0), col); return TRUE; @@ -1594,12 +1413,35 @@ bool Item_in_optimizer::fix_fields(THD *thd, Item **ref) with_sum_func= with_sum_func || args[1]->with_sum_func; with_field= with_field || args[1]->with_field; with_param= args[0]->with_param || args[1]->with_param; - used_tables_cache|= args[1]->used_tables(); - const_item_cache&= args[1]->const_item(); + used_tables_and_const_cache_join(args[1]); fixed= 1; return FALSE; } +/** + Check if Item_in_optimizer should work as a pass-through item for its + arguments. + + @note + Item_in_optimizer should work as pass-through for + - subqueries that were processed by ALL/ANY->MIN/MAX rewrite + - subqueries taht were originally EXISTS subqueries (and were coverted by + the EXISTS->IN rewrite) + + When Item_in_optimizer is not not working as a pass-through, it + - caches its "left argument", args[0]. + - makes adjustments to subquery item's return value for proper NULL + value handling +*/ + +bool Item_in_optimizer::invisible_mode() +{ + /* MAX/MIN transformed or EXISTS->IN prepared => do nothing */ + return (args[1]->type() != Item::SUBSELECT_ITEM || + ((Item_subselect *)args[1])->substype() == + Item_subselect::EXISTS_SUBS); +} + /** Add an expression cache for this subquery if it is needed @@ -1609,7 +1451,7 @@ bool Item_in_optimizer::fix_fields(THD *thd, Item **ref) @details The function checks whether an expression cache is needed for this item and if if so wraps the item into an item of the class - Item_exp_cache_wrapper with an appropriate expression cache set up there. + Item_cache_wrapper with an appropriate expression cache set up there. @note used from Item::transform() @@ -1619,14 +1461,13 @@ bool Item_in_optimizer::fix_fields(THD *thd, Item **ref) this item - otherwise */ -Item *Item_in_optimizer::expr_cache_insert_transformer(uchar *thd_arg) +Item *Item_in_optimizer::expr_cache_insert_transformer(THD *thd, uchar *unused) { - THD *thd= (THD*) thd_arg; DBUG_ENTER("Item_in_optimizer::expr_cache_insert_transformer"); DBUG_ASSERT(fixed); - if (args[1]->type() != Item::SUBSELECT_ITEM) - DBUG_RETURN(this); // MAX/MIN transformed => do nothing + if (invisible_mode()) + DBUG_RETURN(this); if (expr_cache) DBUG_RETURN(expr_cache); @@ -1650,13 +1491,16 @@ void Item_in_optimizer::get_cache_parameters(List<Item> ¶meters) { DBUG_ASSERT(fixed); /* Add left expression to the list of the parameters of the subquery */ - if (args[0]->cols() == 1) - parameters.add_unique(args[0], &cmp_items); - else + if (!invisible_mode()) { - for (uint i= 0; i < args[0]->cols(); i++) + if (args[0]->cols() == 1) + parameters.add_unique(args[0], &cmp_items); + else { - parameters.add_unique(args[0]->element_index(i), &cmp_items); + for (uint i= 0; i < args[0]->cols(); i++) + { + parameters.add_unique(args[0]->element_index(i), &cmp_items); + } } } args[1]->get_cache_parameters(parameters); @@ -1739,17 +1583,19 @@ longlong Item_in_optimizer::val_int() DBUG_ASSERT(fixed == 1); cache->store(args[0]); cache->cache_value(); + DBUG_ENTER(" Item_in_optimizer::val_int"); - if (args[1]->type() != Item::SUBSELECT_ITEM) + if (invisible_mode()) { - /* MAX/MIN transformed => pass through */ longlong res= args[1]->val_int(); null_value= args[1]->null_value; - return (res); + DBUG_PRINT("info", ("pass trough")); + DBUG_RETURN(res); } if (cache->null_value) { + DBUG_PRINT("info", ("Left NULL...")); /* We're evaluating "<outer_value_list> [NOT] IN (SELECT <inner_value_list>...)" @@ -1821,11 +1667,11 @@ longlong Item_in_optimizer::val_int() for (uint i= 0; i < ncols; i++) item_subs->set_cond_guard_var(i, TRUE); } - return 0; + DBUG_RETURN(0); } tmp= args[1]->val_bool_result(); null_value= args[1]->null_value; - return tmp; + DBUG_RETURN(tmp); } @@ -1876,16 +1722,17 @@ bool Item_in_optimizer::is_null() @retval NULL if an error occurred */ -Item *Item_in_optimizer::transform(Item_transformer transformer, uchar *argument) +Item *Item_in_optimizer::transform(THD *thd, Item_transformer transformer, + uchar *argument) { Item *new_item; DBUG_ASSERT(fixed); - DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); DBUG_ASSERT(arg_count == 2); /* Transform the left IN operand. */ - new_item= (*args)->transform(transformer, argument); + new_item= (*args)->transform(thd, transformer, argument); if (!new_item) return 0; /* @@ -1895,16 +1742,16 @@ Item *Item_in_optimizer::transform(Item_transformer transformer, uchar *argument change records at each execution. */ if ((*args) != new_item) - current_thd->change_item_tree(args, new_item); + thd->change_item_tree(args, new_item); - if (args[1]->type() != Item::SUBSELECT_ITEM) + if (invisible_mode()) { /* MAX/MIN transformed => pass through */ - new_item= args[1]->transform(transformer, argument); + new_item= args[1]->transform(thd, transformer, argument); if (!new_item) return 0; if (args[1] != new_item) - current_thd->change_item_tree(args + 1, new_item); + thd->change_item_tree(args + 1, new_item); } else { @@ -1924,9 +1771,9 @@ Item *Item_in_optimizer::transform(Item_transformer transformer, uchar *argument Item_subselect::ANY_SUBS)); Item_in_subselect *in_arg= (Item_in_subselect*)args[1]; - current_thd->change_item_tree(&in_arg->left_expr, args[0]); + thd->change_item_tree(&in_arg->left_expr, args[0]); } - return (this->*transformer)(argument); + return (this->*transformer)(thd, argument); } @@ -1957,7 +1804,7 @@ longlong Item_func_eq::val_int() void Item_func_equal::fix_length_and_dec() { - Item_bool_func2::fix_length_and_dec(); + Item_bool_rowready_func2::fix_length_and_dec(); maybe_null=null_value=0; } @@ -2009,14 +1856,14 @@ longlong Item_func_lt::val_int() longlong Item_func_strcmp::val_int() { DBUG_ASSERT(fixed == 1); - String *a=args[0]->val_str(&cmp.value1); - String *b=args[1]->val_str(&cmp.value2); + String *a= args[0]->val_str(&value1); + String *b= args[1]->val_str(&value2); if (!a || !b) { null_value=1; return 0; } - int value= sortcmp(a,b,cmp.cmp_collation.collation); + int value= cmp_collation.sortcmp(a, b); null_value=0; return !value ? 0 : (value < 0 ? (longlong) -1 : (longlong) 1); } @@ -2030,7 +1877,7 @@ bool Item_func_opt_neg::eq(const Item *item, bool binary_cmp) const if (item->type() != FUNC_ITEM) return 0; Item_func *item_func=(Item_func*) item; - if (arg_count != item_func->arg_count || + if (arg_count != item_func->argument_count() || functype() != item_func->functype()) return 0; if (negated != ((Item_func_opt_neg *) item_func)->negated) @@ -2112,12 +1959,11 @@ void Item_func_interval::fix_length_and_dec() } maybe_null= 0; max_length= 2; - used_tables_cache|= row->used_tables(); + used_tables_and_const_cache_join(row); not_null_tables_cache= row->not_null_tables(); with_sum_func= with_sum_func || row->with_sum_func; with_param= with_param || row->with_param; with_field= with_field || row->with_field; - const_item_cache&= row->const_item(); } @@ -2242,17 +2088,6 @@ longlong Item_func_interval::val_int() 1 got error */ -bool Item_func_between::fix_fields(THD *thd, Item **ref) -{ - if (Item_func_opt_neg::fix_fields(thd, ref)) - return 1; - - thd->lex->current_select->between_count++; - - - return 0; -} - bool Item_func_between::eval_not_null_tables(uchar *opt_arg) { @@ -2280,10 +2115,11 @@ bool Item_func_between::count_sargable_conds(uchar *arg) } -void Item_func_between::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_func_between::fix_after_pullout(st_select_lex *new_parent, + Item **ref, bool merge) { /* This will re-calculate attributes of the arguments */ - Item_func_opt_neg::fix_after_pullout(new_parent, ref); + Item_func_opt_neg::fix_after_pullout(new_parent, ref, merge); /* Then, re-calculate not_null_tables_cache according to our special rules */ eval_not_null_tables(NULL); } @@ -2300,9 +2136,10 @@ void Item_func_between::fix_length_and_dec() */ if (!args[0] || !args[1] || !args[2]) return; - if (agg_cmp_type(&cmp_type, args, 3, false)) + if (agg_cmp_type(&m_compare_type, args, 3, false)) return; - if (cmp_type == STRING_RESULT && + + if (m_compare_type == STRING_RESULT && agg_arg_charsets_for_comparison(cmp_collation, args, 3)) return; @@ -2314,7 +2151,7 @@ void Item_func_between::fix_length_and_dec() For this to work, we need to know what date/time type we compare strings as. */ - if (cmp_type == TIME_RESULT) + if (m_compare_type == TIME_RESULT) compare_as_dates= find_date_time_item(args, 3, 0); /* See the comment about the similar block in Item_bool_func2 */ @@ -2328,7 +2165,7 @@ void Item_func_between::fix_length_and_dec() const bool cvt_arg1= convert_const_to_int(thd, field_item, &args[1]); const bool cvt_arg2= convert_const_to_int(thd, field_item, &args[2]); if (cvt_arg1 && cvt_arg2) - cmp_type=INT_RESULT; // Works for all types. + m_compare_type= INT_RESULT; // Works for all types. } } } @@ -2429,7 +2266,7 @@ longlong Item_func_between::val_int() { DBUG_ASSERT(fixed == 1); - switch (cmp_type) { + switch (m_compare_type) { case TIME_RESULT: { THD *thd= current_thd; @@ -2438,8 +2275,8 @@ longlong Item_func_between::val_int() bool value_is_null, a_is_null, b_is_null; ptr= &args[0]; - value= get_datetime_value(thd, &ptr, &cache, compare_as_dates, - &value_is_null); + enum_field_types f_type= field_type_for_temporal_comparison(compare_as_dates); + value= get_datetime_value(thd, &ptr, &cache, f_type, &value_is_null); if (ptr != &args[0]) thd->change_item_tree(&args[0], *ptr); @@ -2447,12 +2284,12 @@ longlong Item_func_between::val_int() return 0; ptr= &args[1]; - a= get_datetime_value(thd, &ptr, &cache, compare_as_dates, &a_is_null); + a= get_datetime_value(thd, &ptr, &cache, f_type, &a_is_null); if (ptr != &args[1]) thd->change_item_tree(&args[1], *ptr); ptr= &args[2]; - b= get_datetime_value(thd, &ptr, &cache, compare_as_dates, &b_is_null); + b= get_datetime_value(thd, &ptr, &cache, f_type, &b_is_null); if (ptr != &args[2]) thd->change_item_tree(&args[2], *ptr); @@ -2475,7 +2312,6 @@ longlong Item_func_between::val_int() case REAL_RESULT: return val_int_cmp_real(); case ROW_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); null_value= 1; return 0; @@ -2497,64 +2333,17 @@ void Item_func_between::print(String *str, enum_query_type query_type) str->append(')'); } -void -Item_func_ifnull::fix_length_and_dec() -{ - uint32 char_length; - agg_result_type(&cached_result_type, args, 2); - cached_field_type= agg_field_type(args, 2); - maybe_null=args[1]->maybe_null; - decimals= max(args[0]->decimals, args[1]->decimals); - unsigned_flag= args[0]->unsigned_flag && args[1]->unsigned_flag; - - if (cached_result_type == DECIMAL_RESULT || cached_result_type == INT_RESULT) - { - int len0= args[0]->max_char_length() - args[0]->decimals - - (args[0]->unsigned_flag ? 0 : 1); - - int len1= args[1]->max_char_length() - args[1]->decimals - - (args[1]->unsigned_flag ? 0 : 1); - - char_length= max(len0, len1) + decimals + (unsigned_flag ? 0 : 1); - } - else - char_length= max(args[0]->max_char_length(), args[1]->max_char_length()); - - switch (cached_result_type) { - case STRING_RESULT: - if (count_string_result_length(cached_field_type, args, arg_count)) - return; - break; - case DECIMAL_RESULT: - case REAL_RESULT: - break; - case INT_RESULT: - decimals= 0; - break; - case ROW_RESULT: - case TIME_RESULT: - case IMPOSSIBLE_RESULT: - DBUG_ASSERT(0); - } - fix_char_length(char_length); -} - -uint Item_func_ifnull::decimal_precision() const +uint Item_func_case_abbreviation2::decimal_precision2(Item **args) const { int arg0_int_part= args[0]->decimal_int_part(); int arg1_int_part= args[1]->decimal_int_part(); - int max_int_part= max(arg0_int_part, arg1_int_part); + int max_int_part= MY_MAX(arg0_int_part, arg1_int_part); int precision= max_int_part + decimals; - return min(precision, DECIMAL_MAX_PRECISION); + return MY_MIN(precision, DECIMAL_MAX_PRECISION); } -Field *Item_func_ifnull::tmp_table_field(TABLE *table) -{ - return tmp_table_field_from_field_type(table, 0); -} - double Item_func_ifnull::real_op() { @@ -2626,9 +2415,9 @@ Item_func_ifnull::str_op(String *str) bool Item_func_ifnull::date_op(MYSQL_TIME *ltime, uint fuzzydate) { DBUG_ASSERT(fixed == 1); - if (!args[0]->get_date(ltime, fuzzydate & ~TIME_FUZZY_DATES)) + if (!args[0]->get_date_with_conversion(ltime, fuzzydate & ~TIME_FUZZY_DATES)) return (null_value= false); - return (null_value= args[1]->get_date(ltime, fuzzydate & ~TIME_FUZZY_DATES)); + return (null_value= args[1]->get_date_with_conversion(ltime, fuzzydate & ~TIME_FUZZY_DATES)); } @@ -2684,10 +2473,11 @@ Item_func_if::eval_not_null_tables(uchar *opt_arg) } -void Item_func_if::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_func_if::fix_after_pullout(st_select_lex *new_parent, + Item **ref, bool merge) { /* This will re-calculate attributes of the arguments */ - Item_func::fix_after_pullout(new_parent, ref); + Item_func::fix_after_pullout(new_parent, ref, merge); /* Then, re-calculate not_null_tables_cache according to our special rules */ eval_not_null_tables(NULL); } @@ -2695,13 +2485,9 @@ void Item_func_if::fix_after_pullout(st_select_lex *new_parent, Item **ref) void Item_func_if::cache_type_info(Item *source) { - collation.set(source->collation); - cached_field_type= source->field_type(); - cached_result_type= source->result_type(); - decimals= source->decimals; - max_length= source->max_length; + Type_std_attributes::set(source); + set_handler_by_field_type(source->field_type()); maybe_null= source->maybe_null; - unsigned_flag= source->unsigned_flag; } @@ -2715,7 +2501,7 @@ Item_func_if::fix_length_and_dec() maybe_null= true; // If both arguments are NULL, make resulting type BINARY(0). if (args[2]->type() == NULL_ITEM) - cached_field_type= MYSQL_TYPE_STRING; + set_handler_by_field_type(MYSQL_TYPE_STRING); return; } if (args[2]->type() == NULL_ITEM) @@ -2724,47 +2510,7 @@ Item_func_if::fix_length_and_dec() maybe_null= true; return; } - - agg_result_type(&cached_result_type, args + 1, 2); - cached_field_type= agg_field_type(args + 1, 2); - maybe_null= args[1]->maybe_null || args[2]->maybe_null; - decimals= max(args[1]->decimals, args[2]->decimals); - unsigned_flag=args[1]->unsigned_flag && args[2]->unsigned_flag; - - if (cached_result_type == STRING_RESULT) - { - count_string_result_length(cached_field_type, args + 1, 2); - return; - } - else - { - collation.set_numeric(); // Number - } - - uint32 char_length; - if ((cached_result_type == DECIMAL_RESULT ) - || (cached_result_type == INT_RESULT)) - { - int len1= args[1]->max_length - args[1]->decimals - - (args[1]->unsigned_flag ? 0 : 1); - - int len2= args[2]->max_length - args[2]->decimals - - (args[2]->unsigned_flag ? 0 : 1); - - char_length= max(len1, len2) + decimals + (unsigned_flag ? 0 : 1); - } - else - char_length= max(args[1]->max_char_length(), args[2]->max_char_length()); - fix_char_length(char_length); -} - - -uint Item_func_if::decimal_precision() const -{ - int arg1_prec= args[1]->decimal_int_part(); - int arg2_prec= args[2]->decimal_int_part(); - int precision=max(arg1_prec,arg2_prec) + decimals; - return min(precision, DECIMAL_MAX_PRECISION); + Item_func_case_abbreviation2::fix_length_and_dec2(args + 1); } @@ -2818,28 +2564,346 @@ bool Item_func_if::date_op(MYSQL_TIME *ltime, uint fuzzydate) { DBUG_ASSERT(fixed == 1); Item *arg= args[0]->val_bool() ? args[1] : args[2]; - return (null_value= arg->get_date(ltime, fuzzydate)); + return (null_value= arg->get_date_with_conversion(ltime, fuzzydate)); +} + + +void Item_func_nullif::split_sum_func(THD *thd, Item **ref_pointer_array, + List<Item> &fields, uint flags) +{ + if (m_cache) + { + flags|= SPLIT_SUM_SKIP_REGISTERED; // See Item_func::split_sum_func + m_cache->split_sum_func2_example(thd, ref_pointer_array, fields, flags); + args[1]->split_sum_func2(thd, ref_pointer_array, fields, &args[1], flags); + } + else + { + Item_func::split_sum_func(thd, ref_pointer_array, fields, flags); + } } +bool Item_func_nullif::walk(Item_processor processor, + bool walk_subquery, uchar *arg) +{ + /* + No needs to iterate through args[2] when it's just a copy of args[0]. + See MDEV-9712 Performance degradation of nested NULLIF + */ + uint tmp_count= arg_count == 2 || args[0] == args[2] ? 2 : 3; + for (uint i= 0; i < tmp_count; i++) + { + if (args[i]->walk(processor, walk_subquery, arg)) + return true; + } + return (this->*processor)(arg); +} + + +void Item_func_nullif::update_used_tables() +{ + if (m_cache) + { + used_tables_and_const_cache_init(); + used_tables_and_const_cache_update_and_join(m_cache->get_example()); + used_tables_and_const_cache_update_and_join(arg_count, args); + } + else + { + /* + MDEV-9712 Performance degradation of nested NULLIF + No needs to iterate through args[2] when it's just a copy of args[0]. + */ + DBUG_ASSERT(arg_count == 3); + used_tables_and_const_cache_init(); + used_tables_and_const_cache_update_and_join(args[0] == args[2] ? 2 : 3, + args); + } +} + + + void Item_func_nullif::fix_length_and_dec() { - Item_bool_func2::fix_length_and_dec(); + /* + If this is the first invocation of fix_length_and_dec(), create the + third argument as a copy of the first. This cannot be done before + fix_fields(), because fix_fields() might replace items, + for exampe NOT x --> x==0, or (SELECT 1) --> 1. + See also class Item_func_nullif declaration. + */ + if (arg_count == 2) + args[arg_count++]= m_arg0 ? m_arg0 : args[0]; + + THD *thd= current_thd; + /* + At prepared statement EXECUTE time, args[0] can already + point to a different Item, created during PREPARE time fix_length_and_dec(). + For example, if character set conversion was needed, arguments can look + like this: + + args[0]= > Item_func_conv_charset \ + l_expr + args[2]= >------------------------/ + + Otherwise (during PREPARE or convensional execution), + args[0] and args[2] should still point to the same original l_expr. + */ + DBUG_ASSERT(args[0] == args[2] || thd->stmt_arena->is_stmt_execute()); + if (args[0]->type() == SUM_FUNC_ITEM && + !thd->lex->is_ps_or_view_context_analysis()) + { + /* + NULLIF(l_expr, r_expr) + + is calculated in the way to return a result equal to: + + CASE WHEN l_expr = r_expr THEN NULL ELSE r_expr END. + + There's nothing special with r_expr, because it's referenced + only by args[1] and nothing else. + + l_expr needs a special treatment, as it's referenced by both + args[0] and args[2] initially. + + args[2] is used to return the value. Afrer all transformations + (e.g. in fix_length_and_dec(), equal field propagation, etc) + args[2] points to a an Item which preserves the exact data type and + attributes (e.g. collation) of the original l_expr. + It can point: + - to the original l_expr + - to an Item_cache pointing to l_expr + - to a constant of the same data type with l_expr. + + args[0] is used for comparison. It can be replaced: + + - to Item_func_conv_charset by character set aggregation routines + - to a constant Item by equal field propagation routines + (in case of Item_field) + + The data type and/or the attributes of args[0] can differ from + the data type and the attributes of the original l_expr, to make + it comparable to args[1] (which points to r_expr or its replacement). + + For aggregate functions we have to wrap the original args[0]/args[2] + into Item_cache (see MDEV-9181). In this case the Item_cache + instance becomes the subject to character set conversion instead of + the original args[0]/args[2], while the original args[0]/args[2] get + hidden inside the cache. + + Some examples of what NULLIF can end up with after argument + substitution (we don't mention args[1] in some cases for simplicity): + + 1. l_expr is not an aggragate function: + + a. No conversion happened. + args[0] and args[2] were not replaced to something else + (i.e. neither by character set conversion, nor by propagation): + + args[1] > r_expr + args[0] \ + l_expr + args[2] / + + b. Conversion of args[0] happened: + + CREATE OR REPLACE TABLE t1 ( + a CHAR(10) CHARACTER SET latin1, + b CHAR(10) CHARACTER SET utf8); + SELECT * FROM t1 WHERE NULLIF(a,b); + + args[1] > r_expr (Item_field for t1.b) + args[0] > Item_func_conv_charset\ + l_expr (Item_field for t1.a) + args[2] > ----------------------/ + + c. Conversion of args[1] happened: + + CREATE OR REPLACE TABLE t1 ( + a CHAR(10) CHARACTER SET utf8, + b CHAR(10) CHARACTER SET latin1); + SELECT * FROM t1 WHERE NULLIF(a,b); + + args[1] > Item_func_conv_charset -> r_expr (Item_field for t1.b) + args[0] \ + l_expr (Item_field for t1.a) + args[2] / + + d. Conversion of only args[0] happened (by equal field proparation): + + CREATE OR REPLACE TABLE t1 ( + a CHAR(10), + b CHAR(10)); + SELECT * FROM t1 WHERE NULLIF(a,b) AND a='a'; + + args[1] > r_expr (Item_field for t1.b) + args[0] > Item_string('a') (constant replacement for t1.a) + args[2] > l_expr (Item_field for t1.a) + + e. Conversion of both args[0] and args[2] happened + (by equal field propagation): + + CREATE OR REPLACE TABLE t1 (a INT,b INT); + SELECT * FROM t1 WHERE NULLIF(a,b) AND a=5; + + args[1] > r_expr (Item_field for "b") + args[0] \ + Item_int (5) (constant replacement for "a") + args[2] / + + 2. In case if l_expr is an aggregate function: + + a. No conversion happened: + + args[0] \ + Item_cache > l_expr + args[2] / + + b. Conversion of args[0] happened: + + args[0] > Item_func_conv_charset \ + Item_cache > l_expr + args[2] >------------------------/ + + c. Conversion of both args[0] and args[2] happened. + (e.g. by equal expression propagation) + TODO: check if it's possible (and add an example query if so). + */ + m_cache= args[0]->cmp_type() == STRING_RESULT ? + new (thd->mem_root) Item_cache_str_for_nullif(thd, args[0]) : + Item_cache::get_cache(thd, args[0]); + m_cache->setup(thd, args[0]); + m_cache->store(args[0]); + m_cache->set_used_tables(args[0]->used_tables()); + thd->change_item_tree(&args[0], m_cache); + thd->change_item_tree(&args[2], m_cache); + } + set_handler_by_field_type(args[2]->field_type()); + collation.set(args[2]->collation); + decimals= args[2]->decimals; + unsigned_flag= args[2]->unsigned_flag; + fix_char_length(args[2]->max_char_length()); maybe_null=1; - if (args[0]) // Only false if EOM + m_arg0= args[0]; + setup_args_and_comparator(thd, &cmp); + /* + A special code for EXECUTE..PREPARE. + + If args[0] did not change, then we don't remember it, as it can point + to a temporary Item object which will be destroyed between PREPARE + and EXECUTE. EXECUTE time fix_length_and_dec() will correctly set args[2] + from args[0] again. + + If args[0] changed, then it can be Item_func_conv_charset() for the + original args[0], which was permanently installed during PREPARE time + into the item tree as a wrapper for args[0], using change_item_tree(), i.e. + + NULLIF(latin1_field, 'a' COLLATE utf8_bin) + + was "rewritten" to: + + CASE WHEN CONVERT(latin1_field USING utf8) = 'a' COLLATE utf8_bin + THEN NULL + ELSE latin1_field + + - m_args0 points to Item_field corresponding to latin1_field + - args[0] points to Item_func_conv_charset + - args[0]->args[0] is equal to m_args0 + - args[1] points to Item_func_set_collation + - args[2] points is eqial to m_args0 + + In this case we remember and reuse m_arg0 during EXECUTE time as args[2]. + + QQ: How to make sure that m_args0 does not point + to something temporary which will be destoyed between PREPARE and EXECUTE. + The condition below should probably be more strict and somehow check that: + - change_item_tree() was called for the new args[0] + - m_args0 is referenced from inside args[0], e.g. as a function argument, + and therefore it is also something that won't be destroyed between + PREPARE and EXECUTE. + Any ideas? + */ + if (args[0] == m_arg0) + m_arg0= NULL; +} + + +void Item_func_nullif::print(String *str, enum_query_type query_type) +{ + /* + NULLIF(a,b) is implemented according to the SQL standard as a short for + CASE WHEN a=b THEN NULL ELSE a END + + The constructor of Item_func_nullif sets args[0] and args[2] to the + same item "a", and sets args[1] to "b". + + If "this" is a part of a WHERE or ON condition, then: + - the left "a" is a subject to equal field propagation with ANY_SUBST. + - the right "a" is a subject to equal field propagation with IDENTITY_SUBST. + Therefore, after equal field propagation args[0] and args[2] can point + to different items. + */ + if ((query_type & QT_ITEM_ORIGINAL_FUNC_NULLIF) || + (arg_count == 2) || + (args[0] == args[2])) { - decimals=args[0]->decimals; - unsigned_flag= args[0]->unsigned_flag; - cached_result_type= args[0]->result_type(); - if (cached_result_type == STRING_RESULT && - agg_arg_charsets_for_comparison(collation, args, arg_count)) - return; - fix_char_length(args[0]->max_char_length()); + /* + If QT_ITEM_ORIGINAL_FUNC_NULLIF is requested, + that means we want the original NULLIF() representation, + e.g. when we are in: + SHOW CREATE {VIEW|FUNCTION|PROCEDURE} + + The original representation is possible only if + args[0] and args[2] still point to the same Item. + + The caller must never pass call print() with QT_ITEM_ORIGINAL_FUNC_NULLIF + if an expression has undergone some optimization + (e.g. equal field propagation done in optimize_cond()) already and + NULLIF() potentially has two different representations of "a": + - one "a" for comparison + - another "a" for the returned value! + */ + DBUG_ASSERT(arg_count == 2 || + args[0] == args[2] || current_thd->lex->context_analysis_only); + str->append(func_name()); + str->append('('); + if (arg_count == 2) + args[0]->print(str, query_type); + else + args[2]->print(str, query_type); + str->append(','); + args[1]->print(str, query_type); + str->append(')'); + } + else + { + /* + args[0] and args[2] are different items. + This is possible after WHERE optimization (equal fields propagation etc), + e.g. in EXPLAIN EXTENDED or EXPLAIN FORMAT=JSON. + As it's not possible to print as a function with 2 arguments any more, + do it in the CASE style. + */ + str->append(STRING_WITH_LEN("(case when ")); + args[0]->print(str, query_type); + str->append(STRING_WITH_LEN(" = ")); + args[1]->print(str, query_type); + str->append(STRING_WITH_LEN(" then NULL else ")); + args[2]->print(str, query_type); + str->append(STRING_WITH_LEN(" end)")); } } +int Item_func_nullif::compare() +{ + if (m_cache) + m_cache->cache_value(); + return cmp.compare(); +} + /** @note Note that we have to evaluate the first argument twice as the compare @@ -2851,74 +2915,104 @@ Item_func_nullif::fix_length_and_dec() */ double -Item_func_nullif::val_real() +Item_func_nullif::real_op() { DBUG_ASSERT(fixed == 1); double value; - if (!cmp.compare()) + if (!compare()) { null_value=1; return 0.0; } - value= args[0]->val_real(); - null_value=args[0]->null_value; + value= args[2]->val_real(); + null_value= args[2]->null_value; return value; } longlong -Item_func_nullif::val_int() +Item_func_nullif::int_op() { DBUG_ASSERT(fixed == 1); longlong value; - if (!cmp.compare()) + if (!compare()) { null_value=1; return 0; } - value=args[0]->val_int(); - null_value=args[0]->null_value; + value= args[2]->val_int(); + null_value= args[2]->null_value; return value; } String * -Item_func_nullif::val_str(String *str) +Item_func_nullif::str_op(String *str) { DBUG_ASSERT(fixed == 1); String *res; - if (!cmp.compare()) + if (!compare()) { null_value=1; return 0; } - res=args[0]->val_str(str); - null_value=args[0]->null_value; + res= args[2]->val_str(str); + null_value= args[2]->null_value; return res; } my_decimal * -Item_func_nullif::val_decimal(my_decimal * decimal_value) +Item_func_nullif::decimal_op(my_decimal * decimal_value) { DBUG_ASSERT(fixed == 1); my_decimal *res; - if (!cmp.compare()) + if (!compare()) { null_value=1; return 0; } - res= args[0]->val_decimal(decimal_value); - null_value= args[0]->null_value; + res= args[2]->val_decimal(decimal_value); + null_value= args[2]->null_value; return res; } bool +Item_func_nullif::date_op(MYSQL_TIME *ltime, uint fuzzydate) +{ + DBUG_ASSERT(fixed == 1); + if (!compare()) + return (null_value= true); + return (null_value= args[2]->get_date(ltime, fuzzydate)); +} + + +bool Item_func_nullif::is_null() { - return (null_value= (!cmp.compare() ? 1 : args[0]->null_value)); + return (null_value= (!compare() ? 1 : args[2]->null_value)); } +Item_func_case::Item_func_case(THD *thd, List<Item> &list, + Item *first_expr_arg, Item *else_expr_arg): + Item_func_hybrid_field_type(thd), first_expr_num(-1), else_expr_num(-1), + left_cmp_type(INT_RESULT), case_item(0), m_found_types(0) +{ + ncases= list.elements; + if (first_expr_arg) + { + first_expr_num= list.elements; + list.push_back(first_expr_arg, thd->mem_root); + } + if (else_expr_arg) + { + else_expr_num= list.elements; + list.push_back(else_expr_arg, thd->mem_root); + } + set_arguments(thd, list); + bzero(&cmp_items, sizeof(cmp_items)); +} + /** Find and return matching items for CASE or ELSE item if all compares are failed or NULL if ELSE item isn't defined. @@ -2961,7 +3055,7 @@ Item *Item_func_case::find_item(String *str) { if (args[i]->real_item()->type() == NULL_ITEM) continue; - cmp_type= item_cmp_type(left_result_type, args[i]->cmp_type()); + cmp_type= item_cmp_type(left_cmp_type, args[i]); DBUG_ASSERT(cmp_type != ROW_RESULT); DBUG_ASSERT(cmp_items[(uint)cmp_type]); if (!(value_added_map & (1U << (uint)cmp_type))) @@ -2971,7 +3065,7 @@ Item *Item_func_case::find_item(String *str) return else_expr_num != -1 ? args[else_expr_num] : 0; value_added_map|= 1U << (uint)cmp_type; } - if (!cmp_items[(uint)cmp_type]->cmp(args[i]) && !args[i]->null_value) + if (cmp_items[(uint)cmp_type]->cmp(args[i]) == FALSE) return args[i + 1]; } } @@ -3063,7 +3157,7 @@ bool Item_func_case::date_op(MYSQL_TIME *ltime, uint fuzzydate) Item *item= find_item(&dummy_str); if (!item) return (null_value= true); - return (null_value= item->get_date(ltime, fuzzydate)); + return (null_value= item->get_date_with_conversion(ltime, fuzzydate)); } @@ -3074,6 +3168,10 @@ bool Item_func_case::fix_fields(THD *thd, Item **ref) Item_func_case::val_int() -> Item_func_case::find_item() */ uchar buff[MAX_FIELD_WIDTH*2+sizeof(String)*2+sizeof(String*)*2+sizeof(double)*2+sizeof(longlong)*2]; + + if (!(arg_buffer= (Item**) thd->alloc(sizeof(Item*)*(ncases+1)))) + return TRUE; + bool res= Item_func::fix_fields(thd, ref); /* Call check_stack_overrun after fix_fields to be sure that stack variable @@ -3109,14 +3207,11 @@ static void change_item_tree_if_needed(THD *thd, void Item_func_case::fix_length_and_dec() { - Item **agg; + Item **agg= arg_buffer; uint nagg; - uint found_types= 0; THD *thd= current_thd; - if (!(agg= (Item**) sql_alloc(sizeof(Item*)*(ncases+1)))) - return; - + m_found_types= 0; if (else_expr_num == -1 || args[else_expr_num]->maybe_null) maybe_null= 1; @@ -3131,12 +3226,11 @@ void Item_func_case::fix_length_and_dec() if (else_expr_num != -1) agg[nagg++]= args[else_expr_num]; - agg_result_type(&cached_result_type, agg, nagg); - cached_field_type= agg_field_type(agg, nagg); + set_handler_by_field_type(agg_field_type(agg, nagg, true)); - if (cached_result_type == STRING_RESULT) + if (Item_func_case::result_type() == STRING_RESULT) { - if (count_string_result_length(cached_field_type, agg, nagg)) + if (count_string_result_length(Item_func_case::field_type(), agg, nagg)) return; /* Copy all THEN and ELSE items back to args[] array. @@ -3161,7 +3255,7 @@ void Item_func_case::fix_length_and_dec() { uint i; agg[0]= args[first_expr_num]; - left_result_type= agg[0]->cmp_type(); + left_cmp_type= agg[0]->cmp_type(); /* As the first expression and WHEN expressions @@ -3172,14 +3266,14 @@ void Item_func_case::fix_length_and_dec() for (nagg= 0; nagg < ncases/2 ; nagg++) agg[nagg+1]= args[nagg*2]; nagg++; - if (!(found_types= collect_cmp_types(agg, nagg))) + if (!(m_found_types= collect_cmp_types(agg, nagg))) return; Item *date_arg= 0; - if (found_types & (1U << TIME_RESULT)) + if (m_found_types & (1U << TIME_RESULT)) date_arg= find_date_time_item(args, arg_count, 0); - if (found_types & (1U << STRING_RESULT)) + if (m_found_types & (1U << STRING_RESULT)) { /* If we'll do string comparison, we also need to aggregate @@ -3220,7 +3314,7 @@ void Item_func_case::fix_length_and_dec() for (i= 0; i <= (uint)TIME_RESULT; i++) { - if (found_types & (1U << i) && !cmp_items[i]) + if (m_found_types & (1U << i) && !cmp_items[i]) { DBUG_ASSERT((Item_result)i != ROW_RESULT); @@ -3230,16 +3324,90 @@ void Item_func_case::fix_length_and_dec() return; } } + } +} + + +Item* Item_func_case::propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) +{ + if (first_expr_num == -1) + { + // None of the arguments are in a comparison context + Item_args::propagate_equal_fields(thd, Context_identity(), cond); + return this; + } + + for (uint i= 0; i < arg_count; i++) + { /* - Set cmp_context of all WHEN arguments. This prevents - Item_field::equal_fields_propagator() from transforming a - zerofill argument into a string constant. Such a change would - require rebuilding cmp_items. + Even "i" values cover items that are in a comparison context: + CASE x0 WHEN x1 .. WHEN x2 .. WHEN x3 .. + Odd "i" values cover items that are not in comparison: + CASE ... THEN y1 ... THEN y2 ... THEN y3 ... ELSE y4 END */ - for (i= 0; i < ncases; i+= 2) - args[i]->cmp_context= item_cmp_type(left_result_type, - args[i]->result_type()); + Item *new_item= 0; + if ((int) i == first_expr_num) // Then CASE (the switch) argument + { + /* + Cannot replace the CASE (the switch) argument if + there are multiple comparison types were found, or found a single + comparison type that is not equal to args[0]->cmp_type(). + + - Example: multiple comparison types, can't propagate: + WHERE CASE str_column + WHEN 'string' THEN TRUE + WHEN 1 THEN TRUE + ELSE FALSE END; + + - Example: a single incompatible comparison type, can't propagate: + WHERE CASE str_column + WHEN DATE'2001-01-01' THEN TRUE + ELSE FALSE END; + + - Example: a single incompatible comparison type, can't propagate: + WHERE CASE str_column + WHEN 1 THEN TRUE + ELSE FALSE END; + + - Example: a single compatible comparison type, ok to propagate: + WHERE CASE str_column + WHEN 'str1' THEN TRUE + WHEN 'str2' THEN TRUE + ELSE FALSE END; + */ + if (m_found_types == (1UL << left_cmp_type)) + new_item= args[i]->propagate_equal_fields(thd, + Context( + ANY_SUBST, + left_cmp_type, + cmp_collation.collation), + cond); + } + else if ((i % 2) == 0) // WHEN arguments + { + /* + These arguments are in comparison. + Allow invariants of the same value during propagation. + Note, as we pass ANY_SUBST, none of the WHEN arguments will be + replaced to zero-filled constants (only IDENTITY_SUBST allows this). + Such a change for WHEN arguments would require rebuilding cmp_items. + */ + Item_result tmp_cmp_type= item_cmp_type(args[first_expr_num], args[i]); + new_item= args[i]->propagate_equal_fields(thd, + Context( + ANY_SUBST, + tmp_cmp_type, + cmp_collation.collation), + cond); + } + else // THEN and ELSE arguments (they are not in comparison) + { + new_item= args[i]->propagate_equal_fields(thd, Context_identity(), cond); + } + if (new_item && new_item != args[i]) + thd->change_item_tree(&args[i], new_item); } + return this; } @@ -3251,7 +3419,7 @@ uint Item_func_case::decimal_precision() const if (else_expr_num != -1) set_if_bigger(max_int_part, args[else_expr_num]->decimal_int_part()); - return min(max_int_part + decimals, DECIMAL_MAX_PRECISION); + return MY_MIN(max_int_part + decimals, DECIMAL_MAX_PRECISION); } @@ -3352,7 +3520,7 @@ bool Item_func_coalesce::date_op(MYSQL_TIME *ltime,uint fuzzydate) DBUG_ASSERT(fixed == 1); for (uint i= 0; i < arg_count; i++) { - if (!args[i]->get_date(ltime, fuzzydate & ~TIME_FUZZY_DATES)) + if (!args[i]->get_date_with_conversion(ltime, fuzzydate & ~TIME_FUZZY_DATES)) return (null_value= false); } return (null_value= true); @@ -3374,22 +3542,11 @@ my_decimal *Item_func_coalesce::decimal_op(my_decimal *decimal_value) } -void Item_func_coalesce::fix_length_and_dec() +void Item_hybrid_func::fix_attributes(Item **items, uint nitems) { - cached_field_type= agg_field_type(args, arg_count); - agg_result_type(&cached_result_type, args, arg_count); - fix_attributes(args, arg_count); -} - - -#if MYSQL_VERSION_ID > 100100 -#error Rename this to Item_hybrid_func::fix_attributes() when mering to 10.1 -#endif -void Item_func_hybrid_result_type::fix_attributes(Item **items, uint nitems) -{ - switch (cached_result_type) { + switch (Item_hybrid_func::result_type()) { case STRING_RESULT: - if (count_string_result_length(field_type(), + if (count_string_result_length(Item_hybrid_func::field_type(), items, nitems)) return; break; @@ -3408,7 +3565,6 @@ void Item_func_hybrid_result_type::fix_attributes(Item **items, uint nitems) break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } } @@ -3536,11 +3692,11 @@ static int cmp_decimal(void *cmp_arg, my_decimal *a, my_decimal *b) } -int in_vector::find(Item *item) +bool in_vector::find(Item *item) { uchar *result=get_value(item); if (!result || !used_count) - return 0; // Null value + return false; // Null value uint start,end; start=0; end=used_count-1; @@ -3549,13 +3705,13 @@ int in_vector::find(Item *item) uint mid=(start+end+1)/2; int res; if ((res=(*compare)(collation, base+mid*size, result)) == 0) - return 1; + return true; if (res < 0) start=mid; else end=mid-1; } - return (int) ((*compare)(collation, base+start*size, result) == 0); + return ((*compare)(collation, base+start*size, result) == 0); } in_string::in_string(uint elements,qsort2_cmp cmp_func, CHARSET_INFO *cs) @@ -3601,9 +3757,15 @@ uchar *in_string::get_value(Item *item) return (uchar*) item->val_str(&tmp); } -in_row::in_row(uint elements, Item * item) +Item *in_string::create_item(THD *thd) +{ + return new (thd->mem_root) Item_string_for_in_vector(thd, collation); +} + + +in_row::in_row(THD *thd, uint elements, Item * item) { - base= (char*) new cmp_item_row[count= elements]; + base= (char*) new (thd->mem_root) cmp_item_row[count= elements]; size= sizeof(cmp_item_row); compare= (qsort2_cmp) cmp_row; /* @@ -3632,7 +3794,7 @@ void in_row::set(uint pos, Item *item) { DBUG_ENTER("in_row::set"); DBUG_PRINT("enter", ("pos: %u item: 0x%lx", pos, (ulong) item)); - ((cmp_item_row*) base)[pos].store_value_by_template(&tmp, item); + ((cmp_item_row*) base)[pos].store_value_by_template(current_thd, &tmp, item); DBUG_VOID_RETURN; } @@ -3657,13 +3819,21 @@ uchar *in_longlong::get_value(Item *item) return (uchar*) &tmp; } +Item *in_longlong::create_item(THD *thd) +{ + /* + We're created a signed INT, this may not be correct in + general case (see BUG#19342). + */ + return new (thd->mem_root) Item_int(thd, (longlong)0); +} + + void in_datetime::set(uint pos,Item *item) { - Item **tmp_item= &item; - bool is_null; struct packed_longlong *buff= &((packed_longlong*) base)[pos]; - buff->val= get_datetime_value(0, &tmp_item, 0, warn_item, &is_null); + buff->val= item->val_temporal_packed(warn_item); buff->unsigned_flag= 1L; } @@ -3671,13 +3841,21 @@ uchar *in_datetime::get_value(Item *item) { bool is_null; Item **tmp_item= lval_cache ? &lval_cache : &item; - tmp.val= get_datetime_value(0, &tmp_item, &lval_cache, warn_item, &is_null); + enum_field_types f_type= + tmp_item[0]->field_type_for_temporal_comparison(warn_item); + tmp.val= get_datetime_value(0, &tmp_item, &lval_cache, f_type, &is_null); if (item->null_value) return 0; tmp.unsigned_flag= 1L; return (uchar*) &tmp; } +Item *in_datetime::create_item(THD *thd) +{ + return new (thd->mem_root) Item_datetime(thd); +} + + in_double::in_double(uint elements) :in_vector(elements,sizeof(double),(qsort2_cmp) cmp_double, 0) {} @@ -3695,6 +3873,11 @@ uchar *in_double::get_value(Item *item) return (uchar*) &tmp; } +Item *in_double::create_item(THD *thd) +{ + return new (thd->mem_root) Item_float(thd, 0.0, 0); +} + in_decimal::in_decimal(uint elements) :in_vector(elements, sizeof(my_decimal),(qsort2_cmp) cmp_decimal, 0) @@ -3722,6 +3905,11 @@ uchar *in_decimal::get_value(Item *item) return (uchar *)result; } +Item *in_decimal::create_item(THD *thd) +{ + return new (thd->mem_root) Item_decimal(thd, 0, FALSE); +} + cmp_item* cmp_item::get_comparator(Item_result type, Item *warn_item, CHARSET_INFO *cs) @@ -3740,9 +3928,6 @@ cmp_item* cmp_item::get_comparator(Item_result type, Item *warn_item, case TIME_RESULT: DBUG_ASSERT(warn_item); return new cmp_item_datetime(warn_item); - case IMPOSSIBLE_RESULT: - DBUG_ASSERT(0); - break; } return 0; // to satisfy compiler :) } @@ -3819,7 +4004,7 @@ void cmp_item_row::store_value(Item *item) } -void cmp_item_row::store_value_by_template(cmp_item *t, Item *item) +void cmp_item_row::store_value_by_template(THD *thd, cmp_item *t, Item *item) { cmp_item_row *tmpl= (cmp_item_row*) t; if (tmpl->n != item->cols()) @@ -3828,7 +4013,7 @@ void cmp_item_row::store_value_by_template(cmp_item *t, Item *item) return; } n= tmpl->n; - if ((comparators= (cmp_item **) sql_alloc(sizeof(cmp_item *)*n))) + if ((comparators= (cmp_item **) thd->alloc(sizeof(cmp_item *)*n))) { item->bring_value(); item->null_value= 0; @@ -3836,7 +4021,7 @@ void cmp_item_row::store_value_by_template(cmp_item *t, Item *item) { if (!(comparators[i]= tmpl->comparators[i]->make_same())) break; // new failed - comparators[i]->store_value_by_template(tmpl->comparators[i], + comparators[i]->store_value_by_template(thd, tmpl->comparators[i], item->element_index(i)); item->null_value|= item->element_index(i)->null_value; } @@ -3856,14 +4041,20 @@ int cmp_item_row::cmp(Item *arg) arg->bring_value(); for (uint i=0; i < n; i++) { - if (comparators[i]->cmp(arg->element_index(i))) + const int rc= comparators[i]->cmp(arg->element_index(i)); + switch (rc) { - if (!arg->element_index(i)->null_value) - return 1; - was_null= 1; + case UNKNOWN: + was_null= true; + break; + case TRUE: + return TRUE; + case FALSE: + break; // elements #i are equal } + arg->null_value|= arg->element_index(i)->null_value; } - return (arg->null_value= was_null); + return was_null ? UNKNOWN : FALSE; } @@ -3886,15 +4077,15 @@ void cmp_item_decimal::store_value(Item *item) /* val may be zero if item is nnull */ if (val && val != &value) my_decimal2decimal(val, &value); + m_null_value= item->null_value; } int cmp_item_decimal::cmp(Item *arg) { my_decimal tmp_buf, *tmp= arg->val_decimal(&tmp_buf); - if (arg->null_value) - return 1; - return my_decimal_cmp(&value, tmp); + return (m_null_value || arg->null_value) ? + UNKNOWN : (my_decimal_cmp(&value, tmp) != 0); } @@ -3915,16 +4106,17 @@ void cmp_item_datetime::store_value(Item *item) { bool is_null; Item **tmp_item= lval_cache ? &lval_cache : &item; - value= get_datetime_value(0, &tmp_item, &lval_cache, warn_item, &is_null); + enum_field_types f_type= + tmp_item[0]->field_type_for_temporal_comparison(warn_item); + value= get_datetime_value(0, &tmp_item, &lval_cache, f_type, &is_null); + m_null_value= item->null_value; } int cmp_item_datetime::cmp(Item *arg) { - bool is_null; - Item **tmp_item= &arg; - return value != - get_datetime_value(0, &tmp_item, 0, warn_item, &is_null); + const bool rc= value != arg->val_temporal_packed(warn_item); + return (m_null_value || arg->null_value) ? UNKNOWN : rc; } @@ -3941,10 +4133,17 @@ cmp_item *cmp_item_datetime::make_same() } -bool Item_func_in::nulls_in_row() +bool Item_func_in::count_sargable_conds(uchar *arg) +{ + ((SELECT_LEX*) arg)->cond_count++; + return 0; +} + + +bool Item_func_in::list_contains_null() { Item **arg,**arg_end; - for (arg= args+1, arg_end= args+arg_count; arg != arg_end ; arg++) + for (arg= args + 1, arg_end= args+arg_count; arg != arg_end ; arg++) { if ((*arg)->null_inside()) return 1; @@ -4013,10 +4212,11 @@ Item_func_in::eval_not_null_tables(uchar *opt_arg) } -void Item_func_in::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_func_in::fix_after_pullout(st_select_lex *new_parent, Item **ref, + bool merge) { /* This will re-calculate attributes of the arguments */ - Item_func_opt_neg::fix_after_pullout(new_parent, ref); + Item_func_opt_neg::fix_after_pullout(new_parent, ref, merge); /* Then, re-calculate not_null_tables_cache according to our special rules */ eval_not_null_tables(NULL); } @@ -4037,8 +4237,8 @@ void Item_func_in::fix_length_and_dec() Item *date_arg= 0; uint found_types= 0; uint type_cnt= 0, i; - Item_result cmp_type= STRING_RESULT; - left_result_type= args[0]->cmp_type(); + m_compare_type= STRING_RESULT; + left_cmp_type= args[0]->cmp_type(); if (!(found_types= collect_cmp_types(args, arg_count, true))) return; @@ -4055,30 +4255,56 @@ void Item_func_in::fix_length_and_dec() if (found_types & (1U << i)) { (type_cnt)++; - cmp_type= (Item_result) i; + m_compare_type= (Item_result) i; } } + /* + First conditions for bisection to be possible: + 1. All types are similar, and + 2. All expressions in <in value list> are const + */ + bool bisection_possible= + type_cnt == 1 && // 1 + const_itm; // 2 + if (bisection_possible) + { + /* + In the presence of NULLs, the correct result of evaluating this item + must be UNKNOWN or FALSE. To achieve that: + - If type is scalar, we can use bisection and the "have_null" boolean. + - If type is ROW, we will need to scan all of <in value list> when + searching, so bisection is impossible. Unless: + 3. UNKNOWN and FALSE are equivalent results + 4. Neither left expression nor <in value list> contain any NULL value + */ + + if (m_compare_type == ROW_RESULT && + ((!is_top_level_item() || negated) && // 3 + (list_contains_null() || args[0]->maybe_null))) // 4 + bisection_possible= false; + } + if (type_cnt == 1) { - if (cmp_type == STRING_RESULT && + if (m_compare_type == STRING_RESULT && agg_arg_charsets_for_comparison(cmp_collation, args, arg_count)) return; arg_types_compatible= TRUE; - if (cmp_type == ROW_RESULT) + if (m_compare_type == ROW_RESULT) { uint cols= args[0]->cols(); cmp_item_row *cmp= 0; - if (const_itm && !nulls_in_row()) + if (bisection_possible) { - array= new in_row(arg_count-1, 0); + array= new (thd->mem_root) in_row(thd, arg_count-1, 0); cmp= &((in_row*)array)->tmp; } else { - if (!(cmp= new cmp_item_row)) + if (!(cmp= new (thd->mem_root) cmp_item_row)) return; cmp_items[ROW_RESULT]= cmp; } @@ -4095,16 +4321,13 @@ void Item_func_in::fix_length_and_dec() cmp= ((in_row*)array)->tmp.comparators + col; else cmp= ((cmp_item_row*)cmp_items[ROW_RESULT])->comparators + col; - *cmp= new cmp_item_datetime(date_arg); + *cmp= new (thd->mem_root) cmp_item_datetime(date_arg); } } } } - /* - Row item with NULLs inside can return NULL or FALSE => - they can't be processed as static - */ - if (type_cnt == 1 && const_itm && !nulls_in_row()) + + if (bisection_possible) { /* IN must compare INT columns and constants as int values (the same @@ -4116,32 +4339,41 @@ void Item_func_in::fix_length_and_dec() See the comment about the similar block in Item_bool_func2 */ if (args[0]->real_item()->type() == FIELD_ITEM && - !thd->lex->is_view_context_analysis() && cmp_type != INT_RESULT) + !thd->lex->is_view_context_analysis() && m_compare_type != INT_RESULT) { Item_field *field_item= (Item_field*) (args[0]->real_item()); if (field_item->field_type() == MYSQL_TYPE_LONGLONG || field_item->field_type() == MYSQL_TYPE_YEAR) { - bool all_converted= TRUE; + bool all_converted= true; for (arg=args+1, arg_end=args+arg_count; arg != arg_end ; arg++) { - if (!convert_const_to_int(thd, field_item, &arg[0])) - all_converted= FALSE; + /* + Explicit NULLs should not affect data cmp_type resolution: + - we ignore NULLs when calling collect_cmp_type() + - we ignore NULLs here + So this expression: + year_column IN (DATE'2001-01-01', NULL) + switches from TIME_RESULT to INT_RESULT. + */ + if (arg[0]->type() != Item::NULL_ITEM && + !convert_const_to_int(thd, field_item, &arg[0])) + all_converted= false; } if (all_converted) - cmp_type= INT_RESULT; + m_compare_type= INT_RESULT; } } - switch (cmp_type) { + switch (m_compare_type) { case STRING_RESULT: - array=new in_string(arg_count-1,(qsort2_cmp) srtcmp_in, - cmp_collation.collation); + array=new (thd->mem_root) in_string(arg_count-1,(qsort2_cmp) srtcmp_in, + cmp_collation.collation); break; case INT_RESULT: - array= new in_longlong(arg_count-1); + array= new (thd->mem_root) in_longlong(arg_count-1); break; case REAL_RESULT: - array= new in_double(arg_count-1); + array= new (thd->mem_root) in_double(arg_count-1); break; case ROW_RESULT: /* @@ -4152,30 +4384,32 @@ void Item_func_in::fix_length_and_dec() ((in_row*)array)->tmp.store_value(args[0]); break; case DECIMAL_RESULT: - array= new in_decimal(arg_count - 1); + array= new (thd->mem_root) in_decimal(arg_count - 1); break; case TIME_RESULT: date_arg= find_date_time_item(args, arg_count, 0); - array= new in_datetime(date_arg, arg_count - 1); - break; - case IMPOSSIBLE_RESULT: - DBUG_ASSERT(0); + array= new (thd->mem_root) in_datetime(date_arg, arg_count - 1); break; } - if (array && !(thd->is_fatal_error)) // If not EOM + if (!array || thd->is_fatal_error) // OOM + return; + uint j=0; + for (uint i=1 ; i < arg_count ; i++) { - uint j=0; - for (uint i=1 ; i < arg_count ; i++) + array->set(j,args[i]); + if (!args[i]->null_value) + j++; // include this cell in the array. + else { - array->set(j,args[i]); - if (!args[i]->null_value) // Skip NULL values - j++; - else - have_null= 1; + /* + We don't put NULL values in array, to avoid erronous matches in + bisection. + */ + have_null= 1; } - if ((array->used_count= j)) - array->sort(); } + if ((array->used_count= j)) + array->sort(); } else { @@ -4195,16 +4429,6 @@ void Item_func_in::fix_length_and_dec() } } } - /* - Set cmp_context of all arguments. This prevents - Item_field::equal_fields_propagator() from transforming a zerofill integer - argument into a string constant. Such a change would require rebuilding - cmp_itmes. - */ - for (arg= args + 1, arg_end= args + arg_count; arg != arg_end ; arg++) - { - arg[0]->cmp_context= item_cmp_type(left_result_type, arg[0]->result_type()); - } max_length= 1; } @@ -4253,7 +4477,14 @@ longlong Item_func_in::val_int() uint value_added_map= 0; if (array) { - int tmp=array->find(args[0]); + bool tmp=array->find(args[0]); + /* + NULL on left -> UNKNOWN. + Found no match, and NULL on right -> UNKNOWN. + NULL on right can never give a match, as it is not stored in + array. + See also the 'bisection_possible' variable in fix_length_and_dec(). + */ null_value=args[0]->null_value || (!tmp && have_null); return (longlong) (!null_value && tmp != negated); } @@ -4269,19 +4500,18 @@ longlong Item_func_in::val_int() have_null= TRUE; continue; } - Item_result cmp_type= item_cmp_type(left_result_type, args[i]->cmp_type()); + Item_result cmp_type= item_cmp_type(left_cmp_type, args[i]); in_item= cmp_items[(uint)cmp_type]; DBUG_ASSERT(in_item); if (!(value_added_map & (1U << (uint)cmp_type))) { in_item->store_value(args[0]); - if ((null_value= args[0]->null_value)) - return 0; value_added_map|= 1U << (uint)cmp_type; } - if (!in_item->cmp(args[i]) && !args[i]->null_value) + const int rc= in_item->cmp(args[i]); + if (rc == FALSE) return (longlong) (!negated); - have_null|= args[i]->null_value; + have_null|= (rc == UNKNOWN); } null_value= have_null; @@ -4339,11 +4569,28 @@ Item_cond::Item_cond(THD *thd, Item_cond *item) } +Item_cond::Item_cond(THD *thd, Item *i1, Item *i2): + Item_bool_func(thd), abort_on_null(0) +{ + list.push_back(i1, thd->mem_root); + list.push_back(i2, thd->mem_root); +} + + +Item *Item_cond_and::copy_andor_structure(THD *thd) +{ + Item_cond_and *item; + if ((item= new (thd->mem_root) Item_cond_and(thd, this))) + item->copy_andor_arguments(thd, this); + return item; +} + + void Item_cond::copy_andor_arguments(THD *thd, Item_cond *item) { List_iterator_fast<Item> li(item->list); while (Item *it= li++) - list.push_back(it->copy_andor_structure(thd)); + list.push_back(it->copy_andor_structure(thd), thd->mem_root); } @@ -4355,8 +4602,8 @@ Item_cond::fix_fields(THD *thd, Item **ref) Item *item; uchar buff[sizeof(char*)]; // Max local vars in function bool is_and_cond= functype() == Item_func::COND_AND_FUNC; - not_null_tables_cache= used_tables_cache= 0; - const_item_cache= 1; + not_null_tables_cache= 0; + used_tables_and_const_cache_init(); /* and_table_cache is the value that Item_cond_or() returns for @@ -4399,12 +4646,13 @@ Item_cond::fix_fields(THD *thd, Item **ref) was: <field> become: <field> = 1 */ - if (item->type() == FIELD_ITEM) + Item::Type type= item->type(); + if (type == Item::FIELD_ITEM || type == Item::REF_ITEM) { Query_arena backup, *arena; Item *new_item; arena= thd->activate_stmt_arena_if_needed(&backup); - if ((new_item= new Item_func_ne(item, new Item_int(0, 1)))) + if ((new_item= new (thd->mem_root) Item_func_ne(thd, item, new (thd->mem_root) Item_int(thd, 0, 1)))) li.replace(item= new_item); if (arena) thd->restore_active_arena(arena, &backup); @@ -4419,7 +4667,7 @@ Item_cond::fix_fields(THD *thd, Item **ref) if (item->const_item() && !item->with_param && !item->is_expensive() && !cond_has_datetime_is_null(item)) { - if (item->val_int() == is_and_cond && top_level()) + if (item->eval_const_cond() == is_and_cond && top_level()) { /* a. This is "... AND true_cond AND ..." @@ -4460,7 +4708,6 @@ Item_cond::fix_fields(THD *thd, Item **ref) if (item->maybe_null) maybe_null=1; } - thd->lex->current_select->cond_count+= list.elements; fix_length_and_dec(); fixed= 1; return FALSE; @@ -4481,7 +4728,7 @@ Item_cond::eval_not_null_tables(uchar *opt_arg) if (item->const_item() && !item->with_param && !item->is_expensive() && !cond_has_datetime_is_null(item)) { - if (item->val_int() == is_and_cond && top_level()) + if (item->eval_const_cond() == is_and_cond && top_level()) { /* a. This is "... AND true_cond AND ..." @@ -4515,13 +4762,13 @@ Item_cond::eval_not_null_tables(uchar *opt_arg) } -void Item_cond::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_cond::fix_after_pullout(st_select_lex *new_parent, Item **ref, + bool merge) { List_iterator<Item> li(list); Item *item; - used_tables_cache=0; - const_item_cache=1; + used_tables_and_const_cache_init(); and_tables_cache= ~(table_map) 0; // Here and below we do as fix_fields does not_null_tables_cache= 0; @@ -4529,10 +4776,9 @@ void Item_cond::fix_after_pullout(st_select_lex *new_parent, Item **ref) while ((item=li++)) { table_map tmp_table_map; - item->fix_after_pullout(new_parent, li.ref()); + item->fix_after_pullout(new_parent, li.ref(), merge); item= *li.ref(); - used_tables_cache|= item->used_tables(); - const_item_cache&= item->const_item(); + used_tables_and_const_cache_join(item); if (item->const_item()) and_tables_cache= (table_map) 0; @@ -4557,6 +4803,16 @@ bool Item_cond::walk(Item_processor processor, bool walk_subquery, uchar *arg) return Item_func::walk(processor, walk_subquery, arg); } +bool Item_cond_and::walk_top_and(Item_processor processor, uchar *arg) +{ + List_iterator_fast<Item> li(list); + Item *item; + while ((item= li++)) + if (item->walk_top_and(processor, arg)) + return 1; + return Item_cond::walk_top_and(processor, arg); +} + /** Transform an Item_cond object with a transformer callback function. @@ -4576,15 +4832,15 @@ bool Item_cond::walk(Item_processor processor, bool walk_subquery, uchar *arg) Item returned as the result of transformation of the root node */ -Item *Item_cond::transform(Item_transformer transformer, uchar *arg) +Item *Item_cond::transform(THD *thd, Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); List_iterator<Item> li(list); Item *item; while ((item= li++)) { - Item *new_item= item->transform(transformer, arg); + Item *new_item= item->transform(thd, transformer, arg); if (!new_item) return 0; @@ -4595,9 +4851,9 @@ Item *Item_cond::transform(Item_transformer transformer, uchar *arg) change records at each execution. */ if (new_item != item) - current_thd->change_item_tree(li.ref(), new_item); + thd->change_item_tree(li.ref(), new_item); } - return Item_func::transform(transformer, arg); + return Item_func::transform(thd, transformer, arg); } @@ -4625,7 +4881,7 @@ Item *Item_cond::transform(Item_transformer transformer, uchar *arg) Item returned as the result of transformation of the root node */ -Item *Item_cond::compile(Item_analyzer analyzer, uchar **arg_p, +Item *Item_cond::compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, Item_transformer transformer, uchar *arg_t) { if (!(this->*analyzer)(arg_p)) @@ -4640,11 +4896,34 @@ Item *Item_cond::compile(Item_analyzer analyzer, uchar **arg_p, to analyze any argument of the condition formula. */ uchar *arg_v= *arg_p; - Item *new_item= item->compile(analyzer, &arg_v, transformer, arg_t); + Item *new_item= item->compile(thd, analyzer, &arg_v, transformer, arg_t); if (new_item && new_item != item) - current_thd->change_item_tree(li.ref(), new_item); + thd->change_item_tree(li.ref(), new_item); } - return Item_func::transform(transformer, arg_t); + return Item_func::transform(thd, transformer, arg_t); +} + + +Item *Item_cond::propagate_equal_fields(THD *thd, + const Context &ctx, + COND_EQUAL *cond) +{ + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); + DBUG_ASSERT(arg_count == 0); + List_iterator<Item> li(list); + Item *item; + while ((item= li++)) + { + /* + The exact value of the second parameter to propagate_equal_fields() + is not important at this point. Item_func derivants will create and + pass their own context to the arguments. + */ + Item *new_item= item->propagate_equal_fields(thd, Context_boolean(), cond); + if (new_item && new_item != item) + thd->change_item_tree(li.ref(), new_item); + } + return this; } void Item_cond::traverse_cond(Cond_traverser traverser, @@ -4689,12 +4968,13 @@ void Item_cond::traverse_cond(Cond_traverser traverser, */ void Item_cond::split_sum_func(THD *thd, Item **ref_pointer_array, - List<Item> &fields) + List<Item> &fields, uint flags) { List_iterator<Item> li(list); Item *item; while ((item= li++)) - item->split_sum_func2(thd, ref_pointer_array, fields, li.ref(), TRUE); + item->split_sum_func2(thd, ref_pointer_array, fields, li.ref(), + flags | SPLIT_SUM_SKIP_REGISTERED); } @@ -4705,22 +4985,6 @@ Item_cond::used_tables() const } -void Item_cond::update_used_tables() -{ - List_iterator_fast<Item> li(list); - Item *item; - - used_tables_cache=0; - const_item_cache=1; - while ((item=li++)) - { - item->update_used_tables(); - used_tables_cache|= item->used_tables(); - const_item_cache&= item->const_item(); - } -} - - void Item_cond::print(String *str, enum_query_type query_type) { str->append('('); @@ -4748,7 +5012,7 @@ void Item_cond::neg_arguments(THD *thd) Item *new_item= item->neg_transformer(thd); if (!new_item) { - if (!(new_item= new Item_func_not(item))) + if (!(new_item= new (thd->mem_root) Item_func_not(thd, item))) return; // Fatal OEM error } (void) li.replace(new_item); @@ -4766,7 +5030,6 @@ void Item_cond_and::mark_as_condition_AND_part(TABLE_LIST *embedding) } } - /** Evaluation of AND(expr, expr, expr ...). @@ -4824,6 +5087,15 @@ longlong Item_cond_or::val_int() return 0; } +Item *Item_cond_or::copy_andor_structure(THD *thd) +{ + Item_cond_or *item; + if ((item= new (thd->mem_root) Item_cond_or(thd, this))) + item->copy_andor_arguments(thd, this); + return item; +} + + /** Create an AND expression from two expressions. @@ -4844,21 +5116,21 @@ longlong Item_cond_or::val_int() Item */ -Item *and_expressions(Item *a, Item *b, Item **org_item) +Item *and_expressions(THD *thd, Item *a, Item *b, Item **org_item) { if (!a) return (*org_item= (Item*) b); if (a == *org_item) { Item_cond *res; - if ((res= new Item_cond_and(a, (Item*) b))) + if ((res= new (thd->mem_root) Item_cond_and(thd, a, (Item*) b))) { res->used_tables_cache= a->used_tables() | b->used_tables(); res->not_null_tables_cache= a->not_null_tables() | b->not_null_tables(); } return res; } - if (((Item_cond_and*) a)->add((Item*) b)) + if (((Item_cond_and*) a)->add((Item*) b, thd->mem_root)) return 0; ((Item_cond_and*) a)->used_tables_cache|= b->used_tables(); ((Item_cond_and*) a)->not_null_tables_cache|= b->not_null_tables(); @@ -4866,6 +5138,13 @@ Item *and_expressions(Item *a, Item *b, Item **org_item) } +bool Item_func_null_predicate::count_sargable_conds(uchar *arg) +{ + ((SELECT_LEX*) arg)->cond_count++; + return 0; +} + + void Item_func_isnull::print(String *str, enum_query_type query_type) { str->append(func_name()); @@ -4931,16 +5210,23 @@ void Item_func_isnotnull::print(String *str, enum_query_type query_type) } +bool Item_bool_func2::count_sargable_conds(uchar *arg) +{ + ((SELECT_LEX*) arg)->cond_count++; + return 0; +} + + longlong Item_func_like::val_int() { DBUG_ASSERT(fixed == 1); - String* res = args[0]->val_str(&cmp.value1); + String* res= args[0]->val_str(&cmp_value1); if (args[0]->null_value) { null_value=1; return 0; } - String* res2 = args[1]->val_str(&cmp.value2); + String* res2= args[1]->val_str(&cmp_value2); if (args[1]->null_value) { null_value=1; @@ -4949,7 +5235,7 @@ longlong Item_func_like::val_int() null_value=0; if (canDoTurboBM) return turboBM_matches(res->ptr(), res->length()) ? 1 : 0; - return my_wildcmp(cmp.cmp_collation.collation, + return my_wildcmp(cmp_collation.collation, res->ptr(),res->ptr()+res->length(), res2->ptr(),res2->ptr()+res2->length(), escape,wild_one,wild_many) ? 0 : 1; @@ -4960,23 +5246,33 @@ longlong Item_func_like::val_int() We can optimize a where if first character isn't a wildcard */ -Item_func::optimize_type Item_func_like::select_optimize() const +bool Item_func_like::with_sargable_pattern() const { - if (args[1]->const_item() && !args[1]->is_expensive()) - { - String* res2= args[1]->val_str((String *)&cmp.value2); - const char *ptr2; + if (!args[1]->const_item() || args[1]->is_expensive()) + return false; - if (!res2 || !(ptr2= res2->ptr())) - return OPTIMIZE_NONE; + String* res2= args[1]->val_str((String *) &cmp_value2); + if (!res2) + return false; - if (*ptr2 != wild_many) - { - if (args[0]->result_type() != STRING_RESULT || *ptr2 != wild_one) - return OPTIMIZE_OP; - } - } - return OPTIMIZE_NONE; + if (!res2->length()) // Can optimize empty wildcard: column LIKE '' + return true; + + DBUG_ASSERT(res2->ptr()); + char first= res2->ptr()[0]; + return first != wild_many && first != wild_one; +} + + +SEL_TREE *Item_func_like::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) +{ + MEM_ROOT *tmp_root= param->mem_root; + param->thd->mem_root= param->old_root; + bool sargable_pattern= with_sargable_pattern(); + param->thd->mem_root= tmp_root; + return sargable_pattern ? + Item_bool_func2::get_mm_tree(param, cond_ptr) : + Item_func::get_mm_tree(param, cond_ptr); } @@ -4996,7 +5292,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) if (escape_item->const_item()) { /* If we are on execution stage */ - String *escape_str= escape_item->val_str(&cmp.value1); + String *escape_str= escape_item->val_str(&cmp_value1); if (escape_str) { const char *escape_str_ptr= escape_str->ptr(); @@ -5009,7 +5305,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) return TRUE; } - if (use_mb(cmp.cmp_collation.collation)) + if (use_mb(cmp_collation.collation)) { CHARSET_INFO *cs= escape_str->charset(); my_wc_t wc; @@ -5026,7 +5322,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) code instead of Unicode code as "escape" argument. Convert to "cs" if charset of escape differs. */ - CHARSET_INFO *cs= cmp.cmp_collation.collation; + CHARSET_INFO *cs= cmp_collation.collation; uint32 unused; if (escape_str->needs_conversion(escape_str->length(), escape_str->charset(), cs, &unused)) @@ -5052,7 +5348,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) if (args[1]->const_item() && !use_strnxfrm(collation.collation) && !args[1]->is_expensive()) { - String* res2 = args[1]->val_str(&cmp.value2); + String* res2= args[1]->val_str(&cmp_value2); if (!res2) return FALSE; // Null argument @@ -5086,6 +5382,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) turboBM_compute_bad_character_shifts(); DBUG_PRINT("info",("done")); } + use_sampling= (len > 2 && (*first == wild_many || *first == wild_one)); } } return FALSE; @@ -5098,157 +5395,365 @@ void Item_func_like::cleanup() Item_bool_func2::cleanup(); } + +bool Item_func_like::find_selective_predicates_list_processor(uchar *arg) +{ + find_selective_predicates_list_processor_data *data= + (find_selective_predicates_list_processor_data *) arg; + if (use_sampling && used_tables() == data->table->map) + { + THD *thd= data->table->in_use; + COND_STATISTIC *stat; + Item *arg0; + if (!(stat= (COND_STATISTIC *) thd->alloc(sizeof(COND_STATISTIC)))) + return TRUE; + stat->cond= this; + arg0= args[0]->real_item(); + if (args[1]->const_item() && arg0->type() == FIELD_ITEM) + stat->field_arg= ((Item_field *)arg0)->field; + else + stat->field_arg= NULL; + data->list.push_back(stat, thd->mem_root); + } + return FALSE; +} + + +int Regexp_processor_pcre::default_regex_flags() +{ + return default_regex_flags_pcre(current_thd); +} + +void Regexp_processor_pcre::set_recursion_limit(THD *thd) +{ + long stack_used; + DBUG_ASSERT(thd == current_thd); + stack_used= available_stack_size(thd->thread_stack, &stack_used); + m_pcre_extra.match_limit_recursion= + (my_thread_stack_size - STACK_MIN_SIZE - stack_used)/my_pcre_frame_size; +} + + +/** + Convert string to lib_charset, if needed. +*/ +String *Regexp_processor_pcre::convert_if_needed(String *str, String *converter) +{ + if (m_conversion_is_needed) + { + uint dummy_errors; + if (converter->copy(str->ptr(), str->length(), str->charset(), + m_library_charset, &dummy_errors)) + return NULL; + str= converter; + } + return str; +} + + /** @brief Compile regular expression. + @param[in] pattern the pattern to compile from. @param[in] send_error send error message if any. @details Make necessary character set conversion then compile regular expression passed in the args[1]. - @retval 0 success. - @retval 1 error occurred. - @retval -1 given null regular expression. + @retval false success. + @retval true error occurred. */ -int Item_func_regex::regcomp(bool send_error) +bool Regexp_processor_pcre::compile(String *pattern, bool send_error) { - char buff[MAX_FIELD_WIDTH]; - String tmp(buff,sizeof(buff),&my_charset_bin); - String *res= args[1]->val_str(&tmp); - int error; + const char *pcreErrorStr; + int pcreErrorOffset; - if (args[1]->null_value) - return -1; - - if (regex_compiled) + if (is_compiled()) { - if (!stringcmp(res, &prev_regexp)) - return 0; - prev_regexp.copy(*res); - my_regfree(&preg); - regex_compiled= 0; + if (!stringcmp(pattern, &m_prev_pattern)) + return false; + m_prev_pattern.copy(*pattern); + pcre_free(m_pcre); + m_pcre= NULL; } - if (cmp_collation.collation != regex_lib_charset) - { - /* Convert UCS2 strings to UTF8 */ - uint dummy_errors; - if (conv.copy(res->ptr(), res->length(), res->charset(), - regex_lib_charset, &dummy_errors)) - return 1; - res= &conv; - } + if (!(pattern= convert_if_needed(pattern, &pattern_converter))) + return true; + + m_pcre= pcre_compile(pattern->c_ptr_safe(), m_library_flags, + &pcreErrorStr, &pcreErrorOffset, NULL); - if ((error= my_regcomp(&preg, res->c_ptr_safe(), - regex_lib_flags, regex_lib_charset))) + if (m_pcre == NULL) { if (send_error) { - (void) my_regerror(error, &preg, buff, sizeof(buff)); + char buff[MAX_FIELD_WIDTH]; + my_snprintf(buff, sizeof(buff), "%s at offset %d", pcreErrorStr, pcreErrorOffset); my_error(ER_REGEXP_ERROR, MYF(0), buff); } - return 1; + return true; } - regex_compiled= 1; - return 0; + return false; } -bool -Item_func_regex::fix_fields(THD *thd, Item **ref) +bool Regexp_processor_pcre::compile(Item *item, bool send_error) { - DBUG_ASSERT(fixed == 0); - if ((!args[0]->fixed && - args[0]->fix_fields(thd, args)) || args[0]->check_cols(1) || - (!args[1]->fixed && - args[1]->fix_fields(thd, args + 1)) || args[1]->check_cols(1)) - return TRUE; /* purecov: inspected */ - with_sum_func=args[0]->with_sum_func || args[1]->with_sum_func; - with_param=args[0]->with_param || args[1]->with_param; - with_field= args[0]->with_field || args[1]->with_field; - with_subselect= args[0]->has_subquery() || args[1]->has_subquery(); - max_length= 1; - decimals= 0; + char buff[MAX_FIELD_WIDTH]; + String tmp(buff, sizeof(buff), &my_charset_bin); + String *pattern= item->val_str(&tmp); + if (item->null_value || compile(pattern, send_error)) + return true; + return false; +} - if (agg_arg_charsets_for_comparison(cmp_collation, args, 2)) - return TRUE; - regex_lib_flags= (cmp_collation.collation->state & - (MY_CS_BINSORT | MY_CS_CSSORT)) ? - REG_EXTENDED | REG_NOSUB : - REG_EXTENDED | REG_NOSUB | REG_ICASE; +/** + Send a warning explaining an error code returned by pcre_exec(). +*/ +void Regexp_processor_pcre::pcre_exec_warn(int rc) const +{ + char buf[64]; + const char *errmsg= NULL; + THD *thd= current_thd; + /* - If the case of UCS2 and other non-ASCII character sets, - we will convert patterns and strings to UTF8. + Make a descriptive message only for those pcre_exec() error codes + that can actually happen in MariaDB. */ - regex_lib_charset= (cmp_collation.collation->mbminlen > 1) ? - &my_charset_utf8_general_ci : - cmp_collation.collation; + switch (rc) + { + case PCRE_ERROR_NULL: + errmsg= "pcre_exec: null argument passed"; + break; + case PCRE_ERROR_BADOPTION: + errmsg= "pcre_exec: bad option"; + break; + case PCRE_ERROR_BADMAGIC: + errmsg= "pcre_exec: bad magic - not a compiled regex"; + break; + case PCRE_ERROR_UNKNOWN_OPCODE: + errmsg= "pcre_exec: error in compiled regex"; + break; + case PCRE_ERROR_NOMEMORY: + errmsg= "pcre_exec: Out of memory"; + break; + case PCRE_ERROR_NOSUBSTRING: + errmsg= "pcre_exec: no substring"; + break; + case PCRE_ERROR_MATCHLIMIT: + errmsg= "pcre_exec: match limit exceeded"; + break; + case PCRE_ERROR_CALLOUT: + errmsg= "pcre_exec: callout error"; + break; + case PCRE_ERROR_BADUTF8: + errmsg= "pcre_exec: Invalid utf8 byte sequence in the subject string"; + break; + case PCRE_ERROR_BADUTF8_OFFSET: + errmsg= "pcre_exec: Started at invalid location within utf8 byte sequence"; + break; + case PCRE_ERROR_PARTIAL: + errmsg= "pcre_exec: partial match"; + break; + case PCRE_ERROR_INTERNAL: + errmsg= "pcre_exec: internal error"; + break; + case PCRE_ERROR_BADCOUNT: + errmsg= "pcre_exec: ovesize is negative"; + break; + case PCRE_ERROR_RECURSIONLIMIT: + my_snprintf(buf, sizeof(buf), "pcre_exec: recursion limit of %ld exceeded", + m_pcre_extra.match_limit_recursion); + errmsg= buf; + break; + case PCRE_ERROR_BADNEWLINE: + errmsg= "pcre_exec: bad newline options"; + break; + case PCRE_ERROR_BADOFFSET: + errmsg= "pcre_exec: start offset negative or greater than string length"; + break; + case PCRE_ERROR_SHORTUTF8: + errmsg= "pcre_exec: ended in middle of utf8 sequence"; + break; + case PCRE_ERROR_JIT_STACKLIMIT: + errmsg= "pcre_exec: insufficient stack memory for JIT compile"; + break; + case PCRE_ERROR_RECURSELOOP: + errmsg= "pcre_exec: Recursion loop detected"; + break; + case PCRE_ERROR_BADMODE: + errmsg= "pcre_exec: compiled pattern passed to wrong bit library function"; + break; + case PCRE_ERROR_BADENDIANNESS: + errmsg= "pcre_exec: compiled pattern passed to wrong endianness processor"; + break; + case PCRE_ERROR_JIT_BADOPTION: + errmsg= "pcre_exec: bad jit option"; + break; + case PCRE_ERROR_BADLENGTH: + errmsg= "pcre_exec: negative length"; + break; + default: + /* + As other error codes should normally not happen, + we just report the error code without textual description + of the code. + */ + my_snprintf(buf, sizeof(buf), "pcre_exec: Internal error (%d)", rc); + errmsg= buf; + } + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_REGEXP_ERROR, ER_THD(thd, ER_REGEXP_ERROR), errmsg); +} - used_tables_cache=args[0]->used_tables() | args[1]->used_tables(); - not_null_tables_cache= (args[0]->not_null_tables() | - args[1]->not_null_tables()); - const_item_cache=args[0]->const_item() && args[1]->const_item(); - if (!regex_compiled && args[1]->const_item()) + +/** + Call pcre_exec() and send a warning if pcre_exec() returned with an error. +*/ +int Regexp_processor_pcre::pcre_exec_with_warn(const pcre *code, + const pcre_extra *extra, + const char *subject, + int length, int startoffset, + int options, int *ovector, + int ovecsize) +{ + int rc= pcre_exec(code, extra, subject, length, + startoffset, options, ovector, ovecsize); + DBUG_EXECUTE_IF("pcre_exec_error_123", rc= -123;); + if (rc < PCRE_ERROR_NOMATCH) + pcre_exec_warn(rc); + return rc; +} + + +bool Regexp_processor_pcre::exec(const char *str, int length, int offset) +{ + m_pcre_exec_rc= pcre_exec_with_warn(m_pcre, &m_pcre_extra, str, length, offset, 0, + m_SubStrVec, array_elements(m_SubStrVec)); + return false; +} + + +bool Regexp_processor_pcre::exec(String *str, int offset, + uint n_result_offsets_to_convert) +{ + if (!(str= convert_if_needed(str, &subject_converter))) + return true; + m_pcre_exec_rc= pcre_exec_with_warn(m_pcre, &m_pcre_extra, + str->c_ptr_safe(), str->length(), + offset, 0, + m_SubStrVec, array_elements(m_SubStrVec)); + if (m_pcre_exec_rc > 0) { - int comp_res= regcomp(TRUE); - if (comp_res == -1) - { // Will always return NULL - maybe_null=1; - fixed= 1; - return FALSE; + uint i; + for (i= 0; i < n_result_offsets_to_convert; i++) + { + /* + Convert byte offset into character offset. + */ + m_SubStrVec[i]= (int) str->charset()->cset->numchars(str->charset(), + str->ptr(), + str->ptr() + + m_SubStrVec[i]); } - else if (comp_res) - return TRUE; - regex_is_const= 1; - maybe_null= args[0]->maybe_null; } - else - maybe_null=1; - fixed= 1; - return FALSE; + return false; } -longlong Item_func_regex::val_int() +bool Regexp_processor_pcre::exec(Item *item, int offset, + uint n_result_offsets_to_convert) { - DBUG_ASSERT(fixed == 1); char buff[MAX_FIELD_WIDTH]; String tmp(buff,sizeof(buff),&my_charset_bin); - String *res= args[0]->val_str(&tmp); + String *res= item->val_str(&tmp); + if (item->null_value) + return true; + return exec(res, offset, n_result_offsets_to_convert); +} - if ((null_value= (args[0]->null_value || - (!regex_is_const && regcomp(FALSE))))) - return 0; - if (cmp_collation.collation != regex_lib_charset) +void Regexp_processor_pcre::fix_owner(Item_func *owner, + Item *subject_arg, + Item *pattern_arg) +{ + if (!is_compiled() && pattern_arg->const_item()) { - /* Convert UCS2 strings to UTF8 */ - uint dummy_errors; - if (conv.copy(res->ptr(), res->length(), res->charset(), - regex_lib_charset, &dummy_errors)) + if (compile(pattern_arg, true)) { - null_value= 1; - return 0; + owner->maybe_null= 1; // Will always return NULL + return; } - res= &conv; + set_const(true); + owner->maybe_null= subject_arg->maybe_null; } - return my_regexec(&preg,res->c_ptr_safe(),0,(my_regmatch_t*) 0,0) ? 0 : 1; + else + owner->maybe_null= 1; } -void Item_func_regex::cleanup() +bool Item_func_regex::fix_fields(THD *thd, Item **ref) { - DBUG_ENTER("Item_func_regex::cleanup"); - Item_bool_func::cleanup(); - if (regex_compiled) - { - my_regfree(&preg); - regex_compiled=0; - prev_regexp.length(0); - } - DBUG_VOID_RETURN; + re.set_recursion_limit(thd); + return Item_bool_func::fix_fields(thd, ref); +} + +void +Item_func_regex::fix_length_and_dec() +{ + Item_bool_func::fix_length_and_dec(); + + if (agg_arg_charsets_for_comparison(cmp_collation, args, 2)) + return; + + re.init(cmp_collation.collation, 0); + re.fix_owner(this, args[0], args[1]); +} + + +longlong Item_func_regex::val_int() +{ + DBUG_ASSERT(fixed == 1); + if ((null_value= re.recompile(args[1]))) + return 0; + + if ((null_value= re.exec(args[0], 0, 0))) + return 0; + + return re.match(); +} + + +bool Item_func_regexp_instr::fix_fields(THD *thd, Item **ref) +{ + re.set_recursion_limit(thd); + return Item_int_func::fix_fields(thd, ref); +} + + +void +Item_func_regexp_instr::fix_length_and_dec() +{ + if (agg_arg_charsets_for_comparison(cmp_collation, args, 2)) + return; + + re.init(cmp_collation.collation, 0); + re.fix_owner(this, args[0], args[1]); +} + + +longlong Item_func_regexp_instr::val_int() +{ + DBUG_ASSERT(fixed == 1); + if ((null_value= re.recompile(args[1]))) + return 0; + + if ((null_value= re.exec(args[0], 0, 1))) + return 0; + + return re.match() ? re.subpattern_start(0) + 1 : 0; } @@ -5269,7 +5774,7 @@ void Item_func_like::turboBM_compute_suffixes(int *suff) int f = 0; int g = plm1; int *const splm1 = suff + plm1; - CHARSET_INFO *cs= cmp.cmp_collation.collation; + CHARSET_INFO *cs= cmp_collation.collation; *splm1 = pattern_len; @@ -5284,7 +5789,7 @@ void Item_func_like::turboBM_compute_suffixes(int *suff) else { if (i < g) - g = i; // g = min(i, g) + g = i; // g = MY_MIN(i, g) f = i; while (g >= 0 && pattern[g] == pattern[g + plm1 - f]) g--; @@ -5303,7 +5808,7 @@ void Item_func_like::turboBM_compute_suffixes(int *suff) else { if (i < g) - g = i; // g = min(i, g) + g = i; // g = MY_MIN(i, g) f = i; while (g >= 0 && likeconv(cs, pattern[g]) == likeconv(cs, pattern[g + plm1 - f])) @@ -5369,7 +5874,7 @@ void Item_func_like::turboBM_compute_bad_character_shifts() int *end = bmBc + alphabet_size; int j; const int plm1 = pattern_len - 1; - CHARSET_INFO *cs= cmp.cmp_collation.collation; + CHARSET_INFO *cs= cmp_collation.collation; for (i = bmBc; i < end; i++) *i = pattern_len; @@ -5401,7 +5906,7 @@ bool Item_func_like::turboBM_matches(const char* text, int text_len) const int shift = pattern_len; int j = 0; int u = 0; - CHARSET_INFO *cs= cmp.cmp_collation.collation; + CHARSET_INFO *cs= cmp_collation.collation; const int plm1= pattern_len - 1; const int tlmpl= text_len - pattern_len; @@ -5424,14 +5929,14 @@ bool Item_func_like::turboBM_matches(const char* text, int text_len) const register const int v = plm1 - i; turboShift = u - v; bcShift = bmBc[(uint) (uchar) text[i + j]] - plm1 + i; - shift = max(turboShift, bcShift); - shift = max(shift, bmGs[i]); + shift = MY_MAX(turboShift, bcShift); + shift = MY_MAX(shift, bmGs[i]); if (shift == bmGs[i]) - u = min(pattern_len - shift, v); + u = MY_MIN(pattern_len - shift, v); else { if (turboShift < bcShift) - shift = max(shift, u + 1); + shift = MY_MAX(shift, u + 1); u = 0; } j+= shift; @@ -5455,14 +5960,14 @@ bool Item_func_like::turboBM_matches(const char* text, int text_len) const register const int v = plm1 - i; turboShift = u - v; bcShift = bmBc[(uint) likeconv(cs, text[i + j])] - plm1 + i; - shift = max(turboShift, bcShift); - shift = max(shift, bmGs[i]); + shift = MY_MAX(turboShift, bcShift); + shift = MY_MAX(shift, bmGs[i]); if (shift == bmGs[i]) - u = min(pattern_len - shift, v); + u = MY_MIN(pattern_len - shift, v); else { if (turboShift < bcShift) - shift = max(shift, u + 1); + shift = MY_MAX(shift, u + 1); u = 0; } j+= shift; @@ -5539,14 +6044,15 @@ Item *Item_func_not::neg_transformer(THD *thd) /* NOT(x) -> x */ bool Item_func_not::fix_fields(THD *thd, Item **ref) { + args[0]->under_not(this); if (args[0]->type() == FIELD_ITEM) { - /* replace "NOT <field>" with "<filed> == 0" */ + /* replace "NOT <field>" with "<field> == 0" */ Query_arena backup, *arena; Item *new_item; bool rc= TRUE; arena= thd->activate_stmt_arena_if_needed(&backup); - if ((new_item= new Item_func_eq(args[0], new Item_int(0, 1)))) + if ((new_item= new (thd->mem_root) Item_func_eq(thd, args[0], new (thd->mem_root) Item_int(thd, 0, 1)))) { new_item->name= name; rc= (*ref= new_item)->fix_fields(thd, ref); @@ -5561,7 +6067,7 @@ bool Item_func_not::fix_fields(THD *thd, Item **ref) Item *Item_bool_rowready_func2::neg_transformer(THD *thd) { - Item *item= negated_item(); + Item *item= negated_item(thd); return item; } @@ -5580,14 +6086,14 @@ Item *Item_func_xor::neg_transformer(THD *thd) Item_func_xor *new_item; if ((neg_operand= args[0]->neg_transformer(thd))) // args[0] has neg_tranformer - new_item= new(thd->mem_root) Item_func_xor(neg_operand, args[1]); + new_item= new(thd->mem_root) Item_func_xor(thd, neg_operand, args[1]); else if ((neg_operand= args[1]->neg_transformer(thd))) // args[1] has neg_tranformer - new_item= new(thd->mem_root) Item_func_xor(args[0], neg_operand); + new_item= new(thd->mem_root) Item_func_xor(thd, args[0], neg_operand); else { - neg_operand= new(thd->mem_root) Item_func_not(args[0]); - new_item= new(thd->mem_root) Item_func_xor(neg_operand, args[1]); + neg_operand= new(thd->mem_root) Item_func_not(thd, args[0]); + new_item= new(thd->mem_root) Item_func_xor(thd, neg_operand, args[1]); } return new_item; } @@ -5598,7 +6104,7 @@ Item *Item_func_xor::neg_transformer(THD *thd) */ Item *Item_func_isnull::neg_transformer(THD *thd) { - Item *item= new Item_func_isnotnull(args[0]); + Item *item= new (thd->mem_root) Item_func_isnotnull(thd, args[0]); return item; } @@ -5608,7 +6114,7 @@ Item *Item_func_isnull::neg_transformer(THD *thd) */ Item *Item_func_isnotnull::neg_transformer(THD *thd) { - Item *item= new Item_func_isnull(args[0]); + Item *item= new (thd->mem_root) Item_func_isnull(thd, args[0]); return item; } @@ -5617,7 +6123,7 @@ Item *Item_cond_and::neg_transformer(THD *thd) /* NOT(a AND b AND ...) -> */ /* NOT a OR NOT b OR ... */ { neg_arguments(thd); - Item *item= new Item_cond_or(list); + Item *item= new (thd->mem_root) Item_cond_or(thd, list); return item; } @@ -5626,7 +6132,7 @@ Item *Item_cond_or::neg_transformer(THD *thd) /* NOT(a OR b OR ...) -> */ /* NOT a AND NOT b AND ... */ { neg_arguments(thd); - Item *item= new Item_cond_and(list); + Item *item= new (thd->mem_root) Item_cond_and(thd, list); return item; } @@ -5634,7 +6140,7 @@ Item *Item_cond_or::neg_transformer(THD *thd) /* NOT(a OR b OR ...) -> */ Item *Item_func_nop_all::neg_transformer(THD *thd) { /* "NOT (e $cmp$ ANY (SELECT ...)) -> e $rev_cmp$" ALL (SELECT ...) */ - Item_func_not_all *new_item= new Item_func_not_all(args[0]); + Item_func_not_all *new_item= new (thd->mem_root) Item_func_not_all(thd, args[0]); Item_allany_subselect *allany= (Item_allany_subselect*)args[0]; allany->create_comp_func(FALSE); allany->all= !allany->all; @@ -5645,7 +6151,7 @@ Item *Item_func_nop_all::neg_transformer(THD *thd) Item *Item_func_not_all::neg_transformer(THD *thd) { /* "NOT (e $cmp$ ALL (SELECT ...)) -> e $rev_cmp$" ANY (SELECT ...) */ - Item_func_nop_all *new_item= new Item_func_nop_all(args[0]); + Item_func_nop_all *new_item= new (thd->mem_root) Item_func_nop_all(thd, args[0]); Item_allany_subselect *allany= (Item_allany_subselect*)args[0]; allany->all= !allany->all; allany->create_comp_func(TRUE); @@ -5653,45 +6159,45 @@ Item *Item_func_not_all::neg_transformer(THD *thd) return new_item; } -Item *Item_func_eq::negated_item() /* a = b -> a != b */ +Item *Item_func_eq::negated_item(THD *thd) /* a = b -> a != b */ { - return new Item_func_ne(args[0], args[1]); + return new (thd->mem_root) Item_func_ne(thd, args[0], args[1]); } -Item *Item_func_ne::negated_item() /* a != b -> a = b */ +Item *Item_func_ne::negated_item(THD *thd) /* a != b -> a = b */ { - return new Item_func_eq(args[0], args[1]); + return new (thd->mem_root) Item_func_eq(thd, args[0], args[1]); } -Item *Item_func_lt::negated_item() /* a < b -> a >= b */ +Item *Item_func_lt::negated_item(THD *thd) /* a < b -> a >= b */ { - return new Item_func_ge(args[0], args[1]); + return new (thd->mem_root) Item_func_ge(thd, args[0], args[1]); } -Item *Item_func_ge::negated_item() /* a >= b -> a < b */ +Item *Item_func_ge::negated_item(THD *thd) /* a >= b -> a < b */ { - return new Item_func_lt(args[0], args[1]); + return new (thd->mem_root) Item_func_lt(thd, args[0], args[1]); } -Item *Item_func_gt::negated_item() /* a > b -> a <= b */ +Item *Item_func_gt::negated_item(THD *thd) /* a > b -> a <= b */ { - return new Item_func_le(args[0], args[1]); + return new (thd->mem_root) Item_func_le(thd, args[0], args[1]); } -Item *Item_func_le::negated_item() /* a <= b -> a > b */ +Item *Item_func_le::negated_item(THD *thd) /* a <= b -> a > b */ { - return new Item_func_gt(args[0], args[1]); + return new (thd->mem_root) Item_func_gt(thd, args[0], args[1]); } /** just fake method, should never be called. */ -Item *Item_bool_rowready_func2::negated_item() +Item *Item_bool_rowready_func2::negated_item(THD *thd) { DBUG_ASSERT(0); return 0; @@ -5714,17 +6220,17 @@ Item *Item_bool_rowready_func2::negated_item() of the type Item_field or Item_direct_view_ref(Item_field). */ -Item_equal::Item_equal(Item *f1, Item *f2, bool with_const_item) - : Item_bool_func(), eval_item(0), cond_false(0), cond_true(0), - context_field(NULL) +Item_equal::Item_equal(THD *thd, Item *f1, Item *f2, bool with_const_item): + Item_bool_func(thd), eval_item(0), cond_false(0), cond_true(0), + context_field(NULL), link_equal_fields(FALSE), + m_compare_type(item_cmp_type(f1, f2)), + m_compare_collation(f2->collation.collation) { const_item_cache= 0; with_const= with_const_item; - equal_items.push_back(f1); - equal_items.push_back(f2); - compare_as_dates= with_const_item && f2->cmp_type() == TIME_RESULT; + equal_items.push_back(f1, thd->mem_root); + equal_items.push_back(f2, thd->mem_root); upper_levels= NULL; - sargable= TRUE; } @@ -5740,22 +6246,22 @@ Item_equal::Item_equal(Item *f1, Item *f2, bool with_const_item) outer join). */ -Item_equal::Item_equal(Item_equal *item_equal) - : Item_bool_func(), eval_item(0), cond_false(0), cond_true(0), - context_field(NULL) +Item_equal::Item_equal(THD *thd, Item_equal *item_equal): + Item_bool_func(thd), eval_item(0), cond_false(0), cond_true(0), + context_field(NULL), link_equal_fields(FALSE), + m_compare_type(item_equal->m_compare_type), + m_compare_collation(item_equal->m_compare_collation) { const_item_cache= 0; List_iterator_fast<Item> li(item_equal->equal_items); Item *item; while ((item= li++)) { - equal_items.push_back(item); + equal_items.push_back(item, thd->mem_root); } with_const= item_equal->with_const; - compare_as_dates= item_equal->compare_as_dates; cond_false= item_equal->cond_false; upper_levels= item_equal->upper_levels; - sargable= TRUE; } @@ -5773,41 +6279,87 @@ Item_equal::Item_equal(Item_equal *item_equal) the list. Otherwise the value of c is compared with the value of the constant item from equal_items. If they are not equal cond_false is set to TRUE. This serves as an indicator that this Item_equal is always FALSE. - The optional parameter f is used to adjust the flag compare_as_dates. */ -void Item_equal::add_const(Item *c, Item *f) +void Item_equal::add_const(THD *thd, Item *c) { if (cond_false) return; if (!with_const) { with_const= TRUE; - if (f) - compare_as_dates= f->cmp_type() == TIME_RESULT; - equal_items.push_front(c); + equal_items.push_front(c, thd->mem_root); return; } Item *const_item= get_const(); - if (compare_as_dates) - { - cmp.set_datetime_cmp_func(this, &c, &const_item); - cond_false= cmp.compare(); - } - else - { - Item_func_eq *func= new Item_func_eq(c, const_item); - if (func->set_cmp_func()) + switch (Item_equal::compare_type()) { + case TIME_RESULT: + { + enum_field_types f_type= context_field->field_type(); + longlong value0= c->val_temporal_packed(f_type); + longlong value1= const_item->val_temporal_packed(f_type); + cond_false= c->null_value || const_item->null_value || value0 != value1; + break; + } + case STRING_RESULT: { + String *str1, *str2; /* - Setting a comparison function fails when trying to compare - incompatible charsets. Charset compatibility is checked earlier, - except for constant subqueries where we may do it here. + Suppose we have an expression (with a string type field) like this: + WHERE field=const1 AND field=const2 ... + + For all pairs field=constXXX we know that: + + - Item_func_eq::fix_length_and_dec() performed collation and character + set aggregation and added character set converters when needed. + Note, the case like: + WHERE field=const1 COLLATE latin1_bin AND field=const2 + is not handled here, because the field would be replaced to + Item_func_set_collation, which cannot get into Item_equal. + So all constXXX that are handled by Item_equal + already have compatible character sets with "field". + + - Also, Field_str::test_if_equality_guarantees_uniqueness() guarantees + that the comparison collation of all equalities handled by Item_equal + match the the collation of the field. + + Therefore, at Item_equal::add_const() time all constants constXXX + should be directly comparable to each other without an additional + character set conversion. + It's safe to do val_str() for "const_item" and "c" and compare + them according to the collation of the *field*. + + So in a script like this: + CREATE TABLE t1 (a VARCHAR(10) COLLATE xxx); + INSERT INTO t1 VALUES ('a'),('A'); + SELECT * FROM t1 WHERE a='a' AND a='A'; + Item_equal::add_const() effectively rewrites the condition to: + SELECT * FROM t1 WHERE a='a' AND 'a' COLLATE xxx='A'; + and then to: + SELECT * FROM t1 WHERE a='a'; // if the two constants were equal + // e.g. in case of latin1_swedish_ci + or to: + SELECT * FROM t1 WHERE FALSE; // if the two constants were not equal + // e.g. in case of latin1_bin + + Note, both "const_item" and "c" can return NULL, e.g.: + SELECT * FROM t1 WHERE a=NULL AND a='const'; + SELECT * FROM t1 WHERE a='const' AND a=NULL; + SELECT * FROM t1 WHERE a='const' AND a=(SELECT MAX(a) FROM t2) */ - return; + cond_false= !(str1= const_item->val_str(&cmp_value1)) || + !(str2= c->val_str(&cmp_value2)) || + !str1->eq(str2, compare_collation()); + break; + } + default: + { + Item_func_eq *func= new (thd->mem_root) Item_func_eq(thd, c, const_item); + if (func->set_cmp_func()) + return; + func->quick_fix_field(); + cond_false= !func->val_int(); } - func->quick_fix_field(); - cond_false= !func->val_int(); } if (with_const && equal_items.elements == 1) cond_true= TRUE; @@ -5863,12 +6415,12 @@ bool Item_equal::contains(Field *field) contains a reference to f2->field. */ -void Item_equal::merge(Item_equal *item) +void Item_equal::merge(THD *thd, Item_equal *item) { Item *c= item->get_const(); if (c) item->equal_items.pop(); - equal_items.concat(&item->equal_items); + equal_items.append(&item->equal_items); if (c) { /* @@ -5876,7 +6428,7 @@ void Item_equal::merge(Item_equal *item) the multiple equality already contains a constant and its value is not equal to the value of c. */ - add_const(c); + add_const(thd, c); } cond_false|= item->cond_false; } @@ -5910,7 +6462,7 @@ void Item_equal::merge(Item_equal *item) they have common members. */ -bool Item_equal::merge_with_check(Item_equal *item, bool save_merged) +bool Item_equal::merge_with_check(THD *thd, Item_equal *item, bool save_merged) { bool intersected= FALSE; Item_equal_fields_iterator_slow fi(*item); @@ -5927,12 +6479,12 @@ bool Item_equal::merge_with_check(Item_equal *item, bool save_merged) if (intersected) { if (!save_merged) - merge(item); + merge(thd, item); else { Item *c= item->get_const(); if (c) - add_const(c); + add_const(thd, c); if (!cond_false) { Item *item; @@ -5940,7 +6492,7 @@ bool Item_equal::merge_with_check(Item_equal *item, bool save_merged) while ((item= fi++)) { if (!contains(fi.get_curr_field())) - add(item); + add(item, thd->mem_root); } } } @@ -5969,7 +6521,7 @@ bool Item_equal::merge_with_check(Item_equal *item, bool save_merged) Item_equal is joined to the 'list'. */ -void Item_equal::merge_into_list(List<Item_equal> *list, +void Item_equal::merge_into_list(THD *thd, List<Item_equal> *list, bool save_merged, bool only_intersected) { @@ -5980,17 +6532,17 @@ void Item_equal::merge_into_list(List<Item_equal> *list, { if (!merge_into) { - if (item->merge_with_check(this, save_merged)) + if (item->merge_with_check(thd, this, save_merged)) merge_into= item; } else { - if (merge_into->merge_with_check(item, false)) + if (merge_into->merge_with_check(thd, item, false)) it.remove(); } } if (!only_intersected && !merge_into) - list->push_back(this); + list->push_back(this, thd->mem_root); } @@ -6035,7 +6587,7 @@ void Item_equal::sort(Item_field_cmpfunc compare, void *arg) Currently this function is called only after substitution of constant tables. */ -void Item_equal::update_const() +void Item_equal::update_const(THD *thd) { List_iterator<Item> it(equal_items); if (with_const) @@ -6064,7 +6616,7 @@ void Item_equal::update_const() else { it.remove(); - add_const(item); + add_const(thd, item); } } } @@ -6102,6 +6654,9 @@ bool Item_equal::fix_fields(THD *thd, Item **ref) DBUG_ASSERT(fixed == 0); Item_equal_fields_iterator it(*this); Item *item; + Field *first_equal_field= NULL; + Field *last_equal_field= NULL; + Field *prev_equal_field= NULL; not_null_tables_cache= used_tables_cache= 0; const_item_cache= 0; while ((item= it++)) @@ -6115,7 +6670,18 @@ bool Item_equal::fix_fields(THD *thd, Item **ref) maybe_null= 1; if (!item->get_item_equal()) item->set_item_equal(this); + if (link_equal_fields && item->real_item()->type() == FIELD_ITEM) + { + last_equal_field= ((Item_field *) (item->real_item()))->field; + if (!prev_equal_field) + first_equal_field= last_equal_field; + else + prev_equal_field->next_equal_field= last_equal_field; + prev_equal_field= last_equal_field; + } } + if (prev_equal_field && last_equal_field != first_equal_field) + last_equal_field->next_equal_field= first_equal_field; fix_length_and_dec(); fixed= 1; return FALSE; @@ -6190,10 +6756,11 @@ longlong Item_equal::val_int() while ((item= it++)) { Field *field= it.get_curr_field(); - /* Skip fields of non-const tables. They haven't been read yet */ - if (field->table->const_table) + /* Skip fields of tables that has not been read yet */ + if (!field->table->status || (field->table->status & STATUS_NULL_ROW)) { - if (eval_item->cmp(item) || (null_value= item->null_value)) + const int rc= eval_item->cmp(item); + if ((rc == TRUE) || (null_value= (rc == UNKNOWN))) return 0; } } @@ -6222,15 +6789,15 @@ bool Item_equal::walk(Item_processor processor, bool walk_subquery, uchar *arg) } -Item *Item_equal::transform(Item_transformer transformer, uchar *arg) +Item *Item_equal::transform(THD *thd, Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); Item *item; Item_equal_fields_iterator it(*this); while ((item= it++)) { - Item *new_item= item->transform(transformer, arg); + Item *new_item= item->transform(thd, transformer, arg); if (!new_item) return 0; @@ -6241,9 +6808,9 @@ Item *Item_equal::transform(Item_transformer transformer, uchar *arg) change records at each execution. */ if (new_item != item) - current_thd->change_item_tree((Item **) it.ref(), new_item); + thd->change_item_tree((Item **) it.ref(), new_item); } - return Item_func::transform(transformer, arg); + return Item_func::transform(thd, transformer, arg); } @@ -6270,14 +6837,6 @@ void Item_equal::print(String *str, enum_query_type query_type) } -CHARSET_INFO *Item_equal::compare_collation() -{ - Item_equal_fields_iterator it(*this); - Item *item= it++; - return item->collation.collation; -} - - /* @brief Get the first equal field of multiple equality. @param[in] field the field to get equal field to @@ -6390,23 +6949,87 @@ Item* Item_equal::get_first(JOIN_TAB *context, Item *field_item) } -longlong Item_func_dyncol_exists::val_int() +longlong Item_func_dyncol_check::val_int() { char buff[STRING_BUFFER_USUAL_SIZE]; String tmp(buff, sizeof(buff), &my_charset_bin); DYNAMIC_COLUMN col; String *str; - ulonglong num; enum enum_dyncol_func_result rc; - num= args[1]->val_int(); + str= args[0]->val_str(&tmp); + if (args[0]->null_value) + goto null; + col.length= str->length(); + /* We do not change the string, so could do this trick */ + col.str= (char *)str->ptr(); + rc= mariadb_dyncol_check(&col); + if (rc < 0 && rc != ER_DYNCOL_FORMAT) + { + dynamic_column_error_message(rc); + goto null; + } + null_value= FALSE; + return rc == ER_DYNCOL_OK; + +null: + null_value= TRUE; + return 0; +} + +longlong Item_func_dyncol_exists::val_int() +{ + char buff[STRING_BUFFER_USUAL_SIZE], nmstrbuf[11]; + String tmp(buff, sizeof(buff), &my_charset_bin), + nmbuf(nmstrbuf, sizeof(nmstrbuf), system_charset_info); + DYNAMIC_COLUMN col; + String *str; + LEX_STRING buf, *name= NULL; + ulonglong num= 0; + enum enum_dyncol_func_result rc; + + if (args[1]->result_type() == INT_RESULT) + num= args[1]->val_int(); + else + { + String *nm= args[1]->val_str(&nmbuf); + if (!nm || args[1]->null_value) + { + null_value= 1; + return 1; + } + if (my_charset_same(nm->charset(), DYNCOL_UTF)) + { + buf.str= (char *) nm->ptr(); + buf.length= nm->length(); + } + else + { + uint strlen; + uint dummy_errors; + buf.str= (char *)sql_alloc((strlen= nm->length() * + DYNCOL_UTF->mbmaxlen + 1)); + if (buf.str) + { + buf.length= + copy_and_convert(buf.str, strlen, DYNCOL_UTF, + nm->ptr(), nm->length(), nm->charset(), + &dummy_errors); + } + else + buf.length= 0; + } + name= &buf; + } str= args[0]->val_str(&tmp); if (args[0]->null_value || args[1]->null_value || num > UINT_MAX16) goto null; col.length= str->length(); /* We do not change the string, so could do this trick */ col.str= (char *)str->ptr(); - rc= dynamic_column_exists(&col, (uint) num); + rc= ((name == NULL) ? + mariadb_dyncol_exists_num(&col, (uint) num) : + mariadb_dyncol_exists_named(&col, name)); if (rc < 0) { dynamic_column_error_message(rc); @@ -6419,3 +7042,75 @@ null: null_value= TRUE; return 0; } + + +Item_bool_rowready_func2 *Eq_creator::create(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_eq(thd, a, b); +} + + +Item_bool_rowready_func2* Eq_creator::create_swap(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_eq(thd, b, a); +} + + +Item_bool_rowready_func2* Ne_creator::create(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_ne(thd, a, b); +} + + +Item_bool_rowready_func2* Ne_creator::create_swap(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_ne(thd, b, a); +} + + +Item_bool_rowready_func2* Gt_creator::create(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_gt(thd, a, b); +} + + +Item_bool_rowready_func2* Gt_creator::create_swap(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_lt(thd, b, a); +} + + +Item_bool_rowready_func2* Lt_creator::create(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_lt(thd, a, b); +} + + +Item_bool_rowready_func2* Lt_creator::create_swap(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_gt(thd, b, a); +} + + +Item_bool_rowready_func2* Ge_creator::create(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_ge(thd, a, b); +} + + +Item_bool_rowready_func2* Ge_creator::create_swap(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_le(thd, b, a); +} + + +Item_bool_rowready_func2* Le_creator::create(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_le(thd, a, b); +} + + +Item_bool_rowready_func2* Le_creator::create_swap(THD *thd, Item *a, Item *b) const +{ + return new(thd->mem_root) Item_func_ge(thd, b, a); +} diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 12a12e05845..eb1da504e7c 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -25,9 +25,18 @@ #include "thr_malloc.h" /* sql_calloc */ #include "item_func.h" /* Item_int_func, Item_bool_func */ -#include "my_regex.h" +#define PCRE_STATIC 1 /* Important on Windows */ +#include "pcre.h" /* pcre header file */ extern Item_result item_cmp_type(Item_result a,Item_result b); +inline Item_result item_cmp_type(const Item *a, const Item *b) +{ + return item_cmp_type(a->cmp_type(), b->cmp_type()); +} +inline Item_result item_cmp_type(Item_result a, const Item *b) +{ + return item_cmp_type(a, b->cmp_type()); +} class Item_bool_func2; class Arg_comparator; @@ -38,46 +47,46 @@ typedef int (*Item_field_cmpfunc)(Item *f1, Item *f2, void *arg); class Arg_comparator: public Sql_alloc { Item **a, **b; + Item_result m_compare_type; + CHARSET_INFO *m_compare_collation; arg_cmp_func func; - Item_result_field *owner; + Item_func_or_sum *owner; bool set_null; // TRUE <=> set owner->null_value Arg_comparator *comparators; // used only for compare_row() double precision; /* Fields used in DATE/DATETIME comparison. */ Item *a_cache, *b_cache; // Cached values of a and b items // when one of arguments is NULL. + int set_compare_func(Item_func_or_sum *owner, Item_result type); + int set_cmp_func(Item_func_or_sum *owner_arg, Item **a1, Item **a2); + + int compare_temporal(enum_field_types type); + int compare_e_temporal(enum_field_types type); + public: - DTCollation cmp_collation; /* Allow owner function to use string buffers. */ String value1, value2; - Arg_comparator(): set_null(TRUE), comparators(0), + Arg_comparator(): m_compare_type(STRING_RESULT), + m_compare_collation(&my_charset_bin), + set_null(TRUE), comparators(0), + a_cache(0), b_cache(0) {}; + Arg_comparator(Item **a1, Item **a2): a(a1), b(a2), + m_compare_type(STRING_RESULT), + m_compare_collation(&my_charset_bin), + set_null(TRUE), comparators(0), a_cache(0), b_cache(0) {}; - Arg_comparator(Item **a1, Item **a2): a(a1), b(a2), set_null(TRUE), - comparators(0), a_cache(0), b_cache(0) {}; - - int set_compare_func(Item_result_field *owner, Item_result type); - inline int set_compare_func(Item_result_field *owner_arg) - { - return set_compare_func(owner_arg, item_cmp_type((*a)->result_type(), - (*b)->result_type())); - } - int set_cmp_func(Item_result_field *owner_arg, - Item **a1, Item **a2, - Item_result type); - inline int set_cmp_func(Item_result_field *owner_arg, +public: + inline int set_cmp_func(Item_func_or_sum *owner_arg, Item **a1, Item **a2, bool set_null_arg) { set_null= set_null_arg; - return set_cmp_func(owner_arg, a1, a2, - item_cmp_type((*a1)->cmp_type(), - (*a2)->cmp_type())); + return set_cmp_func(owner_arg, a1, a2); } inline int compare() { return (this->*func)(); } int compare_string(); // compare args[0] & args[1] - int compare_binary_string(); // compare args[0] & args[1] int compare_real(); // compare args[0] & args[1] int compare_decimal(); // compare args[0] & args[1] int compare_int_signed(); // compare args[0] & args[1] @@ -86,7 +95,6 @@ public: int compare_int_unsigned(); int compare_row(); // compare args[0] & args[1] int compare_e_string(); // compare args[0] & args[1] - int compare_e_binary_string(); // compare args[0] & args[1] int compare_e_real(); // compare args[0] & args[1] int compare_e_decimal(); // compare args[0] & args[1] int compare_e_int(); // compare args[0] & args[1] @@ -94,18 +102,22 @@ public: int compare_e_row(); // compare args[0] & args[1] int compare_real_fixed(); int compare_e_real_fixed(); - int compare_datetime(); // compare args[0] & args[1] as DATETIMEs - int compare_e_datetime(); + int compare_datetime() { return compare_temporal(MYSQL_TYPE_DATETIME); } + int compare_e_datetime() { return compare_e_temporal(MYSQL_TYPE_DATETIME); } + int compare_time() { return compare_temporal(MYSQL_TYPE_TIME); } + int compare_e_time() { return compare_e_temporal(MYSQL_TYPE_TIME); } Item** cache_converted_constant(THD *thd, Item **value, Item **cache, Item_result type); - void set_datetime_cmp_func(Item_result_field *owner_arg, Item **a1, Item **b1); static arg_cmp_func comparator_matrix [6][2]; inline bool is_owner_equal_func() { return (owner->type() == Item::FUNC_ITEM && ((Item_func*)owner)->functype() == Item_func::EQUAL_FUNC); } + Item_result compare_type() const { return m_compare_type; } + CHARSET_INFO *compare_collation() const { return m_compare_collation; } + Arg_comparator *subcomparators() const { return comparators; } void cleanup() { delete [] comparators; @@ -114,14 +126,75 @@ public: friend class Item_func; }; + +class SEL_ARG; +struct KEY_PART; + class Item_bool_func :public Item_int_func { -public: - Item_bool_func() :Item_int_func() {} - Item_bool_func(Item *a) :Item_int_func(a) {} - Item_bool_func(Item *a,Item *b) :Item_int_func(a,b) {} +protected: + /* + Build a SEL_TREE for a simple predicate + @param param PARAM from SQL_SELECT::test_quick_select + @param field field in the predicate + @param value constant in the predicate + @return Pointer to the tree built tree + */ + virtual SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *value) + { + DBUG_ENTER("Item_bool_func::get_func_mm_tree"); + DBUG_ASSERT(0); + DBUG_RETURN(0); + } + /* + Return the full select tree for "field_item" and "value": + - a single SEL_TREE if the field is not in a multiple equality, or + - a conjuction of all SEL_TREEs for all fields from + the same multiple equality with "field_item". + */ + SEL_TREE *get_full_func_mm_tree(RANGE_OPT_PARAM *param, + Item_field *field_item, Item *value); + /** + Test if "item" and "value" are suitable for the range optimization + and get their full select tree. + + "Suitable" means: + - "item" is a field or a field reference + - "value" is NULL (e.g. WHERE field IS NULL), or + "value" is an unexpensive item (e.g. WHERE field OP value) + + @param item - the argument that is checked to be a field + @param value - the other argument + @returns - NULL if the arguments are not suitable for the range optimizer. + @returns - the full select tree if the arguments are suitable. + */ + SEL_TREE *get_full_func_mm_tree_for_args(RANGE_OPT_PARAM *param, + Item *item, Item *value) + { + DBUG_ENTER("Item_bool_func::get_full_func_mm_tree_for_args"); + Item *field= item->real_item(); + if (field->type() == Item::FIELD_ITEM && !field->const_item() && + (!value || !value->is_expensive())) + DBUG_RETURN(get_full_func_mm_tree(param, (Item_field *) field, value)); + DBUG_RETURN(NULL); + } + SEL_TREE *get_mm_parts(RANGE_OPT_PARAM *param, Field *field, + Item_func::Functype type, Item *value); + SEL_TREE *get_ne_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *lt_value, Item *gt_value); + virtual SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param, Field *field, + KEY_PART *key_part, + Item_func::Functype type, Item *value); +public: + Item_bool_func(THD *thd): Item_int_func(thd) {} + Item_bool_func(THD *thd, Item *a): Item_int_func(thd, a) {} + Item_bool_func(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} + Item_bool_func(THD *thd, Item *a, Item *b, Item *c): Item_int_func(thd, a, b, c) {} + Item_bool_func(THD *thd, List<Item> &list): Item_int_func(thd, list) { } Item_bool_func(THD *thd, Item_bool_func *item) :Item_int_func(thd, item) {} - bool is_bool_func() { return 1; } + bool is_bool_type() { return true; } + virtual CHARSET_INFO *compare_collation() const { return NULL; } void fix_length_and_dec() { decimals=0; max_length=1; } uint decimal_precision() const { return 1; } }; @@ -141,8 +214,8 @@ public: virtual void print(String *str, enum_query_type query_type); protected: - Item_func_truth(Item *a, bool a_value, bool a_affirmative) - : Item_bool_func(a), value(a_value), affirmative(a_affirmative) + Item_func_truth(THD *thd, Item *a, bool a_value, bool a_affirmative): + Item_bool_func(thd, a), value(a_value), affirmative(a_affirmative) {} ~Item_func_truth() @@ -167,7 +240,7 @@ private: class Item_func_istrue : public Item_func_truth { public: - Item_func_istrue(Item *a) : Item_func_truth(a, true, true) {} + Item_func_istrue(THD *thd, Item *a): Item_func_truth(thd, a, true, true) {} ~Item_func_istrue() {} virtual const char* func_name() const { return "istrue"; } }; @@ -180,7 +253,8 @@ public: class Item_func_isnottrue : public Item_func_truth { public: - Item_func_isnottrue(Item *a) : Item_func_truth(a, true, false) {} + Item_func_isnottrue(THD *thd, Item *a): + Item_func_truth(thd, a, true, false) {} ~Item_func_isnottrue() {} virtual const char* func_name() const { return "isnottrue"; } }; @@ -193,7 +267,7 @@ public: class Item_func_isfalse : public Item_func_truth { public: - Item_func_isfalse(Item *a) : Item_func_truth(a, false, true) {} + Item_func_isfalse(THD *thd, Item *a): Item_func_truth(thd, a, false, true) {} ~Item_func_isfalse() {} virtual const char* func_name() const { return "isfalse"; } }; @@ -206,7 +280,8 @@ public: class Item_func_isnotfalse : public Item_func_truth { public: - Item_func_isnotfalse(Item *a) : Item_func_truth(a, false, false) {} + Item_func_isnotfalse(THD *thd, Item *a): + Item_func_truth(thd, a, false, false) {} ~Item_func_isnotfalse() {} virtual const char* func_name() const { return "isnotfalse"; } }; @@ -245,12 +320,12 @@ protected: */ int result_for_null_param; public: - Item_in_optimizer(Item *a, Item_in_subselect *b): - Item_bool_func(a, reinterpret_cast<Item *>(b)), cache(0), expr_cache(0), + Item_in_optimizer(THD *thd, Item *a, Item *b): + Item_bool_func(thd, a, b), cache(0), expr_cache(0), save_cache(0), result_for_null_param(UNKNOWN) { with_subselect= true; } bool fix_fields(THD *, Item **); - bool fix_left(THD *thd, Item **ref); + bool fix_left(THD *thd); table_map not_null_tables() const { return 0; } bool is_null(); longlong val_int(); @@ -259,8 +334,8 @@ public: const char *func_name() const { return "<in_optimizer>"; } Item_cache **get_cache() { return &cache; } void keep_top_level_cache(); - Item *transform(Item_transformer transformer, uchar *arg); - virtual Item *expr_cache_insert_transformer(uchar *thd_arg); + Item *transform(THD *thd, Item_transformer transformer, uchar *arg); + virtual Item *expr_cache_insert_transformer(THD *thd, uchar *unused); bool is_expensive_processor(uchar *arg); bool is_expensive(); void set_join_tab_idx(uint join_tab_idx_arg) @@ -268,7 +343,9 @@ public: virtual void get_cache_parameters(List<Item> ¶meters); bool is_top_level_item(); bool eval_not_null_tables(uchar *opt_arg); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); + bool invisible_mode(); + void reset_cache() { cache= NULL; } virtual void print(String *str, enum_query_type query_type); void restore_first_argument(); Item* get_wrapped_in_subselect_item() @@ -277,174 +354,189 @@ public: } }; -class Comp_creator -{ -public: - Comp_creator() {} /* Remove gcc warning */ - virtual ~Comp_creator() {} /* Remove gcc warning */ - /** - Create operation with given arguments. - */ - virtual Item_bool_func2* create(Item *a, Item *b) const = 0; - /** - Create operation with given arguments in swap order. - */ - virtual Item_bool_func2* create_swap(Item *a, Item *b) const = 0; - virtual const char* symbol(bool invert) const = 0; - virtual bool eqne_op() const = 0; - virtual bool l_op() const = 0; -}; -class Eq_creator :public Comp_creator -{ +/* + Functions and operators with two arguments that can use range optimizer. +*/ +class Item_bool_func2 :public Item_bool_func +{ /* Bool with 2 string args */ +protected: + void add_key_fields_optimize_op(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables, bool equal_func); public: - Eq_creator() {} /* Remove gcc warning */ - virtual ~Eq_creator() {} /* Remove gcc warning */ - virtual Item_bool_func2* create(Item *a, Item *b) const; - virtual Item_bool_func2* create_swap(Item *a, Item *b) const; - virtual const char* symbol(bool invert) const { return invert? "<>" : "="; } - virtual bool eqne_op() const { return 1; } - virtual bool l_op() const { return 0; } -}; + Item_bool_func2(THD *thd, Item *a, Item *b): + Item_bool_func(thd, a, b) { } -class Ne_creator :public Comp_creator -{ -public: - Ne_creator() {} /* Remove gcc warning */ - virtual ~Ne_creator() {} /* Remove gcc warning */ - virtual Item_bool_func2* create(Item *a, Item *b) const; - virtual Item_bool_func2* create_swap(Item *a, Item *b) const; - virtual const char* symbol(bool invert) const { return invert? "=" : "<>"; } - virtual bool eqne_op() const { return 1; } - virtual bool l_op() const { return 0; } + bool is_null() { return MY_TEST(args[0]->is_null() || args[1]->is_null()); } + COND *remove_eq_conds(THD *thd, Item::cond_result *cond_value, + bool top_level); + bool count_sargable_conds(uchar *arg); + /* + Specifies which result type the function uses to compare its arguments. + This method is used in equal field propagation. + */ + virtual Item_result compare_type() const + { + /* + Have STRING_RESULT by default, which means the function compares + val_str() results of the arguments. This is suitable for Item_func_like + and for Item_func_spatial_rel. + Note, Item_bool_rowready_func2 overrides this default behaviour. + */ + return STRING_RESULT; + } + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) + { + DBUG_ENTER("Item_bool_func2::get_mm_tree"); + DBUG_ASSERT(arg_count == 2); + SEL_TREE *ftree= get_full_func_mm_tree_for_args(param, args[0], args[1]); + if (!ftree) + ftree= Item_func::get_mm_tree(param, cond_ptr); + DBUG_RETURN(ftree); + } }; -class Gt_creator :public Comp_creator -{ -public: - Gt_creator() {} /* Remove gcc warning */ - virtual ~Gt_creator() {} /* Remove gcc warning */ - virtual Item_bool_func2* create(Item *a, Item *b) const; - virtual Item_bool_func2* create_swap(Item *a, Item *b) const; - virtual const char* symbol(bool invert) const { return invert? "<=" : ">"; } - virtual bool eqne_op() const { return 0; } - virtual bool l_op() const { return 0; } -}; -class Lt_creator :public Comp_creator +/** + A class for functions and operators that can use the range optimizer and + have a reverse function/operator that can also use the range optimizer, + so this condition: + WHERE value OP field + can be optimized as equivalent to: + WHERE field REV_OP value + + This class covers: + - scalar comparison predicates: <, <=, =, <=>, >=, > + - MBR and precise spatial relation predicates (e.g. SP_TOUCHES(x,y)) + + For example: + WHERE 10 > field + can be optimized as: + WHERE field < 10 +*/ +class Item_bool_func2_with_rev :public Item_bool_func2 { +protected: + SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *value) + { + DBUG_ENTER("Item_bool_func2_with_rev::get_func_mm_tree"); + Item_func::Functype func_type= + (value != arguments()[0]) ? functype() : rev_functype(); + DBUG_RETURN(get_mm_parts(param, field, func_type, value)); + } public: - Lt_creator() {} /* Remove gcc warning */ - virtual ~Lt_creator() {} /* Remove gcc warning */ - virtual Item_bool_func2* create(Item *a, Item *b) const; - virtual Item_bool_func2* create_swap(Item *a, Item *b) const; - virtual const char* symbol(bool invert) const { return invert? ">=" : "<"; } - virtual bool eqne_op() const { return 0; } - virtual bool l_op() const { return 1; } + Item_bool_func2_with_rev(THD *thd, Item *a, Item *b): + Item_bool_func2(thd, a, b) { } + virtual enum Functype rev_functype() const= 0; + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) + { + DBUG_ENTER("Item_bool_func2_with_rev::get_mm_tree"); + DBUG_ASSERT(arg_count == 2); + SEL_TREE *ftree; + /* + Even if get_full_func_mm_tree_for_args(param, args[0], args[1]) will not + return a range predicate it may still be possible to create one + by reversing the order of the operands. Note that this only + applies to predicates where both operands are fields. Example: A + query of the form + + WHERE t1.a OP t2.b + + In this case, args[0] == t1.a and args[1] == t2.b. + When creating range predicates for t2, + get_full_func_mm_tree_for_args(param, args[0], args[1]) + will return NULL because 'field' belongs to t1 and only + predicates that applies to t2 are of interest. In this case a + call to get_full_func_mm_tree_for_args() with reversed operands + may succeed. + */ + if (!(ftree= get_full_func_mm_tree_for_args(param, args[0], args[1])) && + !(ftree= get_full_func_mm_tree_for_args(param, args[1], args[0]))) + ftree= Item_func::get_mm_tree(param, cond_ptr); + DBUG_RETURN(ftree); + } }; -class Ge_creator :public Comp_creator -{ -public: - Ge_creator() {} /* Remove gcc warning */ - virtual ~Ge_creator() {} /* Remove gcc warning */ - virtual Item_bool_func2* create(Item *a, Item *b) const; - virtual Item_bool_func2* create_swap(Item *a, Item *b) const; - virtual const char* symbol(bool invert) const { return invert? "<" : ">="; } - virtual bool eqne_op() const { return 0; } - virtual bool l_op() const { return 0; } -}; -class Le_creator :public Comp_creator +class Item_bool_rowready_func2 :public Item_bool_func2_with_rev { -public: - Le_creator() {} /* Remove gcc warning */ - virtual ~Le_creator() {} /* Remove gcc warning */ - virtual Item_bool_func2* create(Item *a, Item *b) const; - virtual Item_bool_func2* create_swap(Item *a, Item *b) const; - virtual const char* symbol(bool invert) const { return invert? ">" : "<="; } - virtual bool eqne_op() const { return 0; } - virtual bool l_op() const { return 1; } -}; - -class Item_bool_func2 :public Item_int_func -{ /* Bool with 2 string args */ protected: Arg_comparator cmp; - bool abort_on_null; - public: - Item_bool_func2(Item *a,Item *b) - :Item_int_func(a,b), cmp(tmp_arg, tmp_arg+1), - abort_on_null(FALSE) { sargable= TRUE; } - void fix_length_and_dec(); - int set_cmp_func() + Item_bool_rowready_func2(THD *thd, Item *a, Item *b): + Item_bool_func2_with_rev(thd, a, b), cmp(tmp_arg, tmp_arg + 1) { - return cmp.set_cmp_func(this, tmp_arg, tmp_arg+1, TRUE); + allowed_arg_cols= 0; // Fetch this value from first argument } - optimize_type select_optimize() const { return OPTIMIZE_OP; } - virtual enum Functype rev_functype() const { return UNKNOWN_FUNC; } - bool have_rev_func() const { return rev_functype() != UNKNOWN_FUNC; } - - virtual inline void print(String *str, enum_query_type query_type) + void print(String *str, enum_query_type query_type) { Item_func::print_op(str, query_type); } - - bool is_null() { return test(args[0]->is_null() || args[1]->is_null()); } - bool is_bool_func() { return 1; } - CHARSET_INFO *compare_collation() { return cmp.cmp_collation.collation; } - uint decimal_precision() const { return 1; } - void top_level_item() { abort_on_null= TRUE; } + Item *neg_transformer(THD *thd); + virtual Item *negated_item(THD *thd); + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) + { + Item_args::propagate_equal_fields(thd, + Context(ANY_SUBST, + cmp.compare_type(), + compare_collation()), + cond); + return this; + } + void fix_length_and_dec(); + int set_cmp_func() + { + return cmp.set_cmp_func(this, tmp_arg, tmp_arg + 1, true); + } + CHARSET_INFO *compare_collation() const { return cmp.compare_collation(); } + Item_result compare_type() const { return cmp.compare_type(); } Arg_comparator *get_comparator() { return &cmp; } void cleanup() { - Item_int_func::cleanup(); + Item_bool_func2::cleanup(); cmp.cleanup(); } - - friend class Arg_comparator; -}; - -class Item_bool_rowready_func2 :public Item_bool_func2 -{ -public: - Item_bool_rowready_func2(Item *a, Item *b) :Item_bool_func2(a, b) - { - allowed_arg_cols= 0; // Fetch this value from first argument - } - Item *neg_transformer(THD *thd); - virtual Item *negated_item(); - bool subst_argument_checker(uchar **arg) + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) { - return (*arg != NULL); + return add_key_fields_optimize_op(join, key_fields, and_level, + usable_tables, sargables, false); } }; /** - XOR inherits from Item_bool_func2 because it is not optimized yet. + XOR inherits from Item_bool_func because it is not optimized yet. Later, when XOR is optimized, it needs to inherit from Item_cond instead. See WL#5800. */ -class Item_func_xor :public Item_bool_func2 +class Item_func_xor :public Item_bool_func { public: - Item_func_xor(Item *i1, Item *i2) :Item_bool_func2(i1, i2) {} + Item_func_xor(THD *thd, Item *i1, Item *i2): Item_bool_func(thd, i1, i2) {} enum Functype functype() const { return XOR_FUNC; } const char *func_name() const { return "xor"; } + void print(String *str, enum_query_type query_type) + { Item_func::print_op(str, query_type); } longlong val_int(); - void top_level_item() {} Item *neg_transformer(THD *thd); - bool subst_argument_checker(uchar **arg) + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) { - return (*arg != NULL); + Item_args::propagate_equal_fields(thd, Context_boolean(), cond); + return this; } }; class Item_func_not :public Item_bool_func { + bool abort_on_null; public: - Item_func_not(Item *a) :Item_bool_func(a) {} + Item_func_not(THD *thd, Item *a): + Item_bool_func(thd, a), abort_on_null(FALSE) {} + virtual void top_level_item() { abort_on_null= 1; } + bool is_top_level_item() { return abort_on_null; } longlong val_int(); enum Functype functype() const { return NOT_FUNC; } const char *func_name() const { return "not"; } @@ -488,12 +580,16 @@ class Item_func_trig_cond: public Item_bool_func { bool *trig_var; public: - Item_func_trig_cond(Item *a, bool *f) : Item_bool_func(a) { trig_var= f; } + Item_func_trig_cond(THD *thd, Item *a, bool *f): Item_bool_func(thd, a) + { trig_var= f; } longlong val_int() { return *trig_var ? args[0]->val_int() : 1; } enum Functype functype() const { return TRIG_COND_FUNC; }; const char *func_name() const { return "trigcond"; }; bool const_item() const { return FALSE; } bool *get_trig_var() { return trig_var; } + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables); }; class Item_func_not_all :public Item_func_not @@ -502,16 +598,12 @@ class Item_func_not_all :public Item_func_not Item_sum_hybrid *test_sum_item; Item_maxmin_subselect *test_sub_item; - bool abort_on_null; public: bool show; - Item_func_not_all(Item *a) - :Item_func_not(a), test_sum_item(0), test_sub_item(0), abort_on_null(0), - show(0) + Item_func_not_all(THD *thd, Item *a): + Item_func_not(thd, a), test_sum_item(0), test_sub_item(0), show(0) {} - virtual void top_level_item() { abort_on_null= 1; } - bool is_top_level_item() { return abort_on_null; } table_map not_null_tables() const { return 0; } longlong val_int(); enum Functype functype() const { return NOT_ALL_FUNC; } @@ -530,7 +622,7 @@ class Item_func_nop_all :public Item_func_not_all { public: - Item_func_nop_all(Item *a) :Item_func_not_all(a) {} + Item_func_nop_all(THD *thd, Item *a): Item_func_not_all(thd, a) {} longlong val_int(); const char *func_name() const { return "<nop>"; } Item *neg_transformer(THD *thd); @@ -539,16 +631,30 @@ public: class Item_func_eq :public Item_bool_rowready_func2 { + bool abort_on_null; public: - Item_func_eq(Item *a,Item *b) : - Item_bool_rowready_func2(a,b), in_equality_no(UINT_MAX) + Item_func_eq(THD *thd, Item *a, Item *b): + Item_bool_rowready_func2(thd, a, b), + abort_on_null(false), in_equality_no(UINT_MAX) {} longlong val_int(); enum Functype functype() const { return EQ_FUNC; } enum Functype rev_functype() const { return EQ_FUNC; } cond_result eq_cmp_result() const { return COND_TRUE; } const char *func_name() const { return "="; } - Item *negated_item(); + void top_level_item() { abort_on_null= true; } + Item *negated_item(THD *thd); + COND *build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref); + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) + { + return add_key_fields_optimize_op(join, key_fields, and_level, + usable_tables, sargables, true); + } + bool check_equality(THD *thd, COND_EQUAL *cond, List<Item> *eq_list); /* - If this equality is created from the subquery's IN-equality: number of the item it was created from, e.g. for @@ -557,12 +663,15 @@ public: - Otherwise, UINT_MAX */ uint in_equality_no; + virtual uint exists2in_reserved_items() { return 1; }; + friend class Arg_comparator; }; class Item_func_equal :public Item_bool_rowready_func2 { public: - Item_func_equal(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {}; + Item_func_equal(THD *thd, Item *a, Item *b): + Item_bool_rowready_func2(thd, a, b) {} longlong val_int(); void fix_length_and_dec(); table_map not_null_tables() const { return 0; } @@ -571,71 +680,92 @@ public: cond_result eq_cmp_result() const { return COND_TRUE; } const char *func_name() const { return "<=>"; } Item *neg_transformer(THD *thd) { return 0; } + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) + { + return add_key_fields_optimize_op(join, key_fields, and_level, + usable_tables, sargables, true); + } }; class Item_func_ge :public Item_bool_rowready_func2 { public: - Item_func_ge(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {}; + Item_func_ge(THD *thd, Item *a, Item *b): + Item_bool_rowready_func2(thd, a, b) {}; longlong val_int(); enum Functype functype() const { return GE_FUNC; } enum Functype rev_functype() const { return LE_FUNC; } cond_result eq_cmp_result() const { return COND_TRUE; } const char *func_name() const { return ">="; } - Item *negated_item(); + Item *negated_item(THD *thd); }; class Item_func_gt :public Item_bool_rowready_func2 { public: - Item_func_gt(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {}; + Item_func_gt(THD *thd, Item *a, Item *b): + Item_bool_rowready_func2(thd, a, b) {}; longlong val_int(); enum Functype functype() const { return GT_FUNC; } enum Functype rev_functype() const { return LT_FUNC; } cond_result eq_cmp_result() const { return COND_FALSE; } const char *func_name() const { return ">"; } - Item *negated_item(); + Item *negated_item(THD *thd); }; class Item_func_le :public Item_bool_rowready_func2 { public: - Item_func_le(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {}; + Item_func_le(THD *thd, Item *a, Item *b): + Item_bool_rowready_func2(thd, a, b) {}; longlong val_int(); enum Functype functype() const { return LE_FUNC; } enum Functype rev_functype() const { return GE_FUNC; } cond_result eq_cmp_result() const { return COND_TRUE; } const char *func_name() const { return "<="; } - Item *negated_item(); + Item *negated_item(THD *thd); }; class Item_func_lt :public Item_bool_rowready_func2 { public: - Item_func_lt(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {} + Item_func_lt(THD *thd, Item *a, Item *b): + Item_bool_rowready_func2(thd, a, b) {} longlong val_int(); enum Functype functype() const { return LT_FUNC; } enum Functype rev_functype() const { return GT_FUNC; } cond_result eq_cmp_result() const { return COND_FALSE; } const char *func_name() const { return "<"; } - Item *negated_item(); + Item *negated_item(THD *thd); }; class Item_func_ne :public Item_bool_rowready_func2 { +protected: + SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *value) + { + DBUG_ENTER("Item_func_ne::get_func_mm_tree"); + DBUG_RETURN(get_ne_mm_tree(param, field, value, value)); + } public: - Item_func_ne(Item *a,Item *b) :Item_bool_rowready_func2(a,b) {} + Item_func_ne(THD *thd, Item *a, Item *b): + Item_bool_rowready_func2(thd, a, b) {} longlong val_int(); enum Functype functype() const { return NE_FUNC; } + enum Functype rev_functype() const { return NE_FUNC; } cond_result eq_cmp_result() const { return COND_FALSE; } - optimize_type select_optimize() const { return OPTIMIZE_KEY; } const char *func_name() const { return "<>"; } - Item *negated_item(); + Item *negated_item(THD *thd); + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level, + table_map usable_tables, SARGABLE_PARAM **sargables); }; @@ -648,52 +778,74 @@ public: */ -class Item_func_opt_neg :public Item_int_func +class Item_func_opt_neg :public Item_bool_func { +protected: + /* + The result type that will be used for comparison. + cmp_type() of all arguments are collected to here. + */ + Item_result m_compare_type; + /* + The collation that will be used for comparison in case + when m_compare_type is STRING_RESULT. + */ + DTCollation cmp_collation; public: bool negated; /* <=> the item represents NOT <func> */ bool pred_level; /* <=> [NOT] <func> is used on a predicate level */ public: - Item_func_opt_neg(Item *a, Item *b, Item *c) - :Item_int_func(a, b, c), negated(0), pred_level(0) {} - Item_func_opt_neg(List<Item> &list) - :Item_int_func(list), negated(0), pred_level(0) {} + Item_func_opt_neg(THD *thd, Item *a, Item *b, Item *c): + Item_bool_func(thd, a, b, c), negated(0), pred_level(0) {} + Item_func_opt_neg(THD *thd, List<Item> &list): + Item_bool_func(thd, list), negated(0), pred_level(0) {} public: inline void negate() { negated= !negated; } inline void top_level_item() { pred_level= 1; } + bool is_top_level_item() const { return pred_level; } Item *neg_transformer(THD *thd) { negated= !negated; return this; } bool eq(const Item *item, bool binary_cmp) const; - bool subst_argument_checker(uchar **arg) { return TRUE; } + CHARSET_INFO *compare_collation() const { return cmp_collation.collation; } + Item* propagate_equal_fields(THD *, const Context &, COND_EQUAL *) = 0; }; class Item_func_between :public Item_func_opt_neg { - DTCollation cmp_collation; +protected: + SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *value); public: - Item_result cmp_type; String value0,value1,value2; /* TRUE <=> arguments will be compared as dates. */ Item *compare_as_dates; - Item_func_between(Item *a, Item *b, Item *c) - :Item_func_opt_neg(a, b, c), compare_as_dates(FALSE) { sargable= TRUE; } + Item_func_between(THD *thd, Item *a, Item *b, Item *c): + Item_func_opt_neg(thd, a, b, c), compare_as_dates(FALSE) { } longlong val_int(); - optimize_type select_optimize() const { return OPTIMIZE_KEY; } enum Functype functype() const { return BETWEEN; } const char *func_name() const { return "between"; } - bool fix_fields(THD *, Item **); void fix_length_and_dec(); virtual void print(String *str, enum_query_type query_type); - bool is_bool_func() { return 1; } - CHARSET_INFO *compare_collation() { return cmp_collation.collation; } - uint decimal_precision() const { return 1; } bool eval_not_null_tables(uchar *opt_arg); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); bool count_sargable_conds(uchar *arg); + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables); + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr); + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) + { + Item_args::propagate_equal_fields(thd, + Context(ANY_SUBST, + m_compare_type, + compare_collation()), + cond); + return this; + } longlong val_int_cmp_string(); longlong val_int_cmp_int(); @@ -702,21 +854,19 @@ public: }; -class Item_func_strcmp :public Item_bool_func2 +class Item_func_strcmp :public Item_int_func { + String value1, value2; + DTCollation cmp_collation; public: - Item_func_strcmp(Item *a,Item *b) :Item_bool_func2(a,b) {} + Item_func_strcmp(THD *thd, Item *a, Item *b): + Item_int_func(thd, a, b) {} longlong val_int(); - optimize_type select_optimize() const { return OPTIMIZE_NONE; } + uint decimal_precision() const { return 1; } const char *func_name() const { return "strcmp"; } - - virtual inline void print(String *str, enum_query_type query_type) - { - Item_func::print(str, query_type); - } void fix_length_and_dec() { - Item_bool_func2::fix_length_and_dec(); + agg_arg_charsets_for_comparison(cmp_collation, args, 2); fix_char_length(2); // returns "1" or "0" or "-1" } }; @@ -735,8 +885,8 @@ class Item_func_interval :public Item_int_func bool use_decimal_comparison; interval_range *intervals; public: - Item_func_interval(Item_row *a) - :Item_int_func(a),row(a),intervals(0) + Item_func_interval(THD *thd, Item_row *a): + Item_int_func(thd, a), row(a), intervals(0) { allowed_arg_cols= 0; // Fetch this value from first argument } @@ -745,48 +895,90 @@ public: void fix_length_and_dec(); const char *func_name() const { return "interval"; } uint decimal_precision() const { return 2; } + void print(String *str, enum_query_type query_type) + { + str->append(func_name()); + print_args(str, 0, query_type); + } }; class Item_func_coalesce :public Item_func_hybrid_field_type { public: - Item_func_coalesce(Item *a, Item *b) :Item_func_hybrid_field_type(a, b) {} - Item_func_coalesce(List<Item> &list) :Item_func_hybrid_field_type(list) {} + Item_func_coalesce(THD *thd, Item *a, Item *b): + Item_func_hybrid_field_type(thd, a, b) {} + Item_func_coalesce(THD *thd, List<Item> &list): + Item_func_hybrid_field_type(thd, list) {} double real_op(); longlong int_op(); String *str_op(String *); my_decimal *decimal_op(my_decimal *); bool date_op(MYSQL_TIME *ltime,uint fuzzydate); - void fix_length_and_dec(); + void fix_length_and_dec() + { + set_handler_by_field_type(agg_field_type(args, arg_count, true)); + fix_attributes(args, arg_count); + } const char *func_name() const { return "coalesce"; } table_map not_null_tables() const { return 0; } }; -class Item_func_ifnull :public Item_func_coalesce +/* + Case abbreviations that aggregate its result field type by two arguments: + IFNULL(arg1, arg2) + IF(switch, arg1, arg2) +*/ +class Item_func_case_abbreviation2 :public Item_func_hybrid_field_type { protected: - bool field_type_defined; + void fix_length_and_dec2(Item **items) + { + set_handler_by_field_type(agg_field_type(items, 2, true)); + fix_attributes(items, 2); + } + uint decimal_precision2(Item **args) const; +public: + Item_func_case_abbreviation2(THD *thd, Item *a, Item *b): + Item_func_hybrid_field_type(thd, a, b) { } + Item_func_case_abbreviation2(THD *thd, Item *a, Item *b, Item *c): + Item_func_hybrid_field_type(thd, a, b, c) { } +}; + + +class Item_func_ifnull :public Item_func_case_abbreviation2 +{ public: - Item_func_ifnull(Item *a, Item *b) :Item_func_coalesce(a,b) {} + Item_func_ifnull(THD *thd, Item *a, Item *b): + Item_func_case_abbreviation2(thd, a, b) {} double real_op(); longlong int_op(); String *str_op(String *str); my_decimal *decimal_op(my_decimal *); bool date_op(MYSQL_TIME *ltime,uint fuzzydate); - void fix_length_and_dec(); + void fix_length_and_dec() + { + Item_func_case_abbreviation2::fix_length_and_dec2(args); + maybe_null= args[1]->maybe_null; + } const char *func_name() const { return "ifnull"; } - Field *tmp_table_field(TABLE *table); - uint decimal_precision() const; + Field *create_field_for_create_select(TABLE *table) + { return tmp_table_field_from_field_type(table, false, false); } + + table_map not_null_tables() const { return 0; } + uint decimal_precision() const + { + return Item_func_case_abbreviation2::decimal_precision2(args); + } }; -class Item_func_if :public Item_func_hybrid_field_type +class Item_func_if :public Item_func_case_abbreviation2 { public: - Item_func_if(Item *a,Item *b,Item *c) - :Item_func_hybrid_field_type(a,b,c) + Item_func_if(THD *thd, Item *a, Item *b, Item *c): + Item_func_case_abbreviation2(thd, a, b, c) {} bool date_op(MYSQL_TIME *ltime, uint fuzzydate); longlong int_op(); @@ -795,38 +987,95 @@ public: String *str_op(String *); bool fix_fields(THD *, Item **); void fix_length_and_dec(); - uint decimal_precision() const; + uint decimal_precision() const + { + return Item_func_case_abbreviation2::decimal_precision2(args + 1); + } const char *func_name() const { return "if"; } bool eval_not_null_tables(uchar *opt_arg); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); private: void cache_type_info(Item *source); }; -class Item_func_nullif :public Item_bool_func2 +class Item_func_nullif :public Item_func_hybrid_field_type { - enum Item_result cached_result_type; -public: - Item_func_nullif(Item *a,Item *b) - :Item_bool_func2(a,b), cached_result_type(INT_RESULT) - {} - double val_real(); - longlong val_int(); - String *val_str(String *str); - my_decimal *val_decimal(my_decimal *); - enum Item_result result_type () const { return cached_result_type; } - void fix_length_and_dec(); - uint decimal_precision() const { return args[0]->decimal_precision(); } - const char *func_name() const { return "nullif"; } + Arg_comparator cmp; + /* + NULLIF(a,b) is a short for: + CASE WHEN a=b THEN NULL ELSE a END - virtual inline void print(String *str, enum_query_type query_type) + The left "a" is for comparison purposes. + The right "a" is for return value purposes. + These are two different "a" and they can be replaced to different items. + + The left "a" is in a comparison and can be replaced by: + - Item_func::convert_const_compared_to_int_field() + - agg_item_set_converter() in set_cmp_func() + - Arg_comparator::cache_converted_constant() in set_cmp_func() + + Both "a"s are subject to equal fields propagation and can be replaced by: + - Item_field::propagate_equal_fields(ANY_SUBST) for the left "a" + - Item_field::propagate_equal_fields(IDENTITY_SUBST) for the right "a" + */ + Item_cache *m_cache; + int compare(); + Item *m_arg0; +public: + /* + Here we pass three arguments to the parent constructor, as NULLIF + is a three-argument function, it needs two copies of the first argument + (see above). But fix_fields() will be confused if we try to prepare the + same Item twice (if args[0]==args[2]), so we hide the third argument + (decrementing arg_count) and copy args[2]=args[0] again after fix_fields(). + See also Item_func_nullif::fix_length_and_dec(). + */ + Item_func_nullif(THD *thd, Item *a, Item *b): + Item_func_hybrid_field_type(thd, a, b, a), + m_cache(NULL), + m_arg0(NULL) + { arg_count--; } + void cleanup() { - Item_func::print(str, query_type); + Item_func_hybrid_field_type::cleanup(); + arg_count= 2; // See the comment to the constructor } - + bool date_op(MYSQL_TIME *ltime, uint fuzzydate); + double real_op(); + longlong int_op(); + String *str_op(String *str); + my_decimal *decimal_op(my_decimal *); + void fix_length_and_dec(); + bool walk(Item_processor processor, bool walk_subquery, uchar *arg); + uint decimal_precision() const { return args[2]->decimal_precision(); } + const char *func_name() const { return "nullif"; } + void print(String *str, enum_query_type query_type); + void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields, + uint flags); + void update_used_tables(); table_map not_null_tables() const { return 0; } bool is_null(); + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) + { + Context cmpctx(ANY_SUBST, cmp.compare_type(), cmp.compare_collation()); + const Item *old0= args[0]; + args[0]->propagate_equal_fields_and_change_item_tree(thd, cmpctx, + cond, &args[0]); + args[1]->propagate_equal_fields_and_change_item_tree(thd, cmpctx, + cond, &args[1]); + /* + MDEV-9712 Performance degradation of nested NULLIF + ANY_SUBST is more relaxed than IDENTITY_SUBST. + If ANY_SUBST did not change args[0], + then we can skip propagation for args[2]. + */ + if (old0 != args[0]) + args[2]->propagate_equal_fields_and_change_item_tree(thd, + Context_identity(), + cond, &args[2]); + return this; + } }; @@ -857,7 +1106,7 @@ public: { my_qsort2(base,used_count,size,compare,(void*)collation); } - int find(Item *item); + bool find(Item *item); /* Create an instance of Item_{type} (e.g. Item_decimal) constant object @@ -867,7 +1116,7 @@ public: vector in form of Item_xxx constants without creating Item_xxx object for every array element you get (i.e. this implements "FlyWeight" pattern) */ - virtual Item* create_item() { return NULL; } + virtual Item* create_item(THD *thd) { return NULL; } /* Store the value at position #pos into provided item object @@ -882,7 +1131,7 @@ public: /* Compare values number pos1 and pos2 for equality */ bool compare_elems(uint pos1, uint pos2) { - return test(compare(collation, base + pos1*size, base + pos2*size)); + return MY_TEST(compare(collation, base + pos1 * size, base + pos2 * size)); } virtual Item_result result_type()= 0; }; @@ -891,20 +1140,29 @@ class in_string :public in_vector { char buff[STRING_BUFFER_USUAL_SIZE]; String tmp; + class Item_string_for_in_vector: public Item_string + { + public: + Item_string_for_in_vector(THD *thd, CHARSET_INFO *cs): + Item_string(thd, cs) + { } + void set_value(const String *str) + { + str_value= *str; + collation.set(str->charset()); + } + }; public: in_string(uint elements,qsort2_cmp cmp_func, CHARSET_INFO *cs); ~in_string(); void set(uint pos,Item *item); uchar *get_value(Item *item); - Item* create_item() - { - return new Item_string(collation); - } + Item* create_item(THD *thd); void value_to_item(uint pos, Item *item) { String *str=((String*) base)+pos; - Item_string *to= (Item_string*)item; - to->str_value= *str; + Item_string_for_in_vector *to= (Item_string_for_in_vector*) item; + to->set_value(str); } Item_result result_type() { return STRING_RESULT; } }; @@ -926,15 +1184,7 @@ public: in_longlong(uint elements); void set(uint pos,Item *item); uchar *get_value(Item *item); - - Item* create_item() - { - /* - We're created a signed INT, this may not be correct in - general case (see BUG#19342). - */ - return new Item_int((longlong)0); - } + Item* create_item(THD *thd); void value_to_item(uint pos, Item *item) { ((Item_int*) item)->value= ((packed_longlong*) base)[pos].val; @@ -965,10 +1215,7 @@ public: :in_longlong(elements), warn_item(warn_item_arg), lval_cache(0) {}; void set(uint pos,Item *item); uchar *get_value(Item *item); - Item* create_item() - { - return new Item_datetime(); - } + Item *create_item(THD *thd); void value_to_item(uint pos, Item *item) { packed_longlong *val= reinterpret_cast<packed_longlong*>(base)+pos; @@ -986,10 +1233,7 @@ public: in_double(uint elements); void set(uint pos,Item *item); uchar *get_value(Item *item); - Item *create_item() - { - return new Item_float(0.0, 0); - } + Item *create_item(THD *thd); void value_to_item(uint pos, Item *item) { ((Item_float*)item)->value= ((double*) base)[pos]; @@ -1005,10 +1249,7 @@ public: in_decimal(uint elements); void set(uint pos, Item *item); uchar *get_value(Item *item); - Item *create_item() - { - return new Item_decimal(0, FALSE); - } + Item *create_item(THD *thd); void value_to_item(uint pos, Item *item) { my_decimal *dec= ((my_decimal *)base) + pos; @@ -1031,19 +1272,30 @@ public: cmp_item() { cmp_charset= &my_charset_bin; } virtual ~cmp_item() {} virtual void store_value(Item *item)= 0; + /** + @returns result (TRUE, FALSE or UNKNOWN) of + "stored argument's value <> item's value" + */ virtual int cmp(Item *item)= 0; // for optimized IN with row virtual int compare(cmp_item *item)= 0; static cmp_item* get_comparator(Item_result type, Item * warn_item, CHARSET_INFO *cs); virtual cmp_item *make_same()= 0; - virtual void store_value_by_template(cmp_item *tmpl, Item *item) + virtual void store_value_by_template(THD *thd, cmp_item *tmpl, Item *item) { store_value(item); } }; -class cmp_item_string :public cmp_item +/// cmp_item which stores a scalar (i.e. non-ROW). +class cmp_item_scalar : public cmp_item +{ +protected: + bool m_null_value; ///< If stored value is NULL +}; + +class cmp_item_string : public cmp_item_scalar { protected: String *value_res; @@ -1069,14 +1321,27 @@ public: void store_value(Item *item) { value_res= item->val_str(&value); + m_null_value= item->null_value; + // Make sure to cache the result String inside "value" + if (value_res && value_res != &value) + { + if (value.copy(*value_res)) + value.set("", 0, item->collation.collation); + value_res= &value; + } } int cmp(Item *arg) { char buff[STRING_BUFFER_USUAL_SIZE]; - String tmp(buff, sizeof(buff), cmp_charset), *res; - res= arg->val_str(&tmp); - return (value_res ? (res ? sortcmp(value_res, res, cmp_charset) : 1) : - (res ? -1 : 0)); + String tmp(buff, sizeof(buff), cmp_charset), *res= arg->val_str(&tmp); + if (m_null_value || arg->null_value) + return UNKNOWN; + if (value_res && res) + return sortcmp(value_res, res, cmp_charset) != 0; + else if (!value_res && !res) + return FALSE; + else + return TRUE; } int compare(cmp_item *ci) { @@ -1091,7 +1356,7 @@ public: } }; -class cmp_item_int :public cmp_item +class cmp_item_int : public cmp_item_scalar { longlong value; public: @@ -1099,10 +1364,12 @@ public: void store_value(Item *item) { value= item->val_int(); + m_null_value= item->null_value; } int cmp(Item *arg) { - return value != arg->val_int(); + const bool rc= value != arg->val_int(); + return (m_null_value || arg->null_value) ? UNKNOWN : rc; } int compare(cmp_item *ci) { @@ -1118,7 +1385,7 @@ public: If the left item is a constant one then its value is cached in the lval_cache variable. */ -class cmp_item_datetime :public cmp_item +class cmp_item_datetime : public cmp_item_scalar { longlong value; public: @@ -1135,7 +1402,7 @@ public: cmp_item *make_same(); }; -class cmp_item_real :public cmp_item +class cmp_item_real : public cmp_item_scalar { double value; public: @@ -1143,10 +1410,12 @@ public: void store_value(Item *item) { value= item->val_real(); + m_null_value= item->null_value; } int cmp(Item *arg) { - return value != arg->val_real(); + const bool rc= value != arg->val_real(); + return (m_null_value || arg->null_value) ? UNKNOWN : rc; } int compare(cmp_item *ci) { @@ -1157,7 +1426,7 @@ public: }; -class cmp_item_decimal :public cmp_item +class cmp_item_decimal : public cmp_item_scalar { my_decimal value; public: @@ -1184,12 +1453,13 @@ public: void store_value(Item *item) { value_res= item->val_str(&value); + m_null_value= item->null_value; } int cmp(Item *item) { // Should never be called - DBUG_ASSERT(0); - return 1; + DBUG_ASSERT(false); + return TRUE; } int compare(cmp_item *ci) { @@ -1224,32 +1494,18 @@ public: class Item_func_case :public Item_func_hybrid_field_type { int first_expr_num, else_expr_num; - enum Item_result left_result_type; + enum Item_result left_cmp_type; String tmp_value; uint ncases; Item_result cmp_type; DTCollation cmp_collation; cmp_item *cmp_items[6]; /* For all result types */ cmp_item *case_item; + Item **arg_buffer; + uint m_found_types; public: - Item_func_case(List<Item> &list, Item *first_expr_arg, Item *else_expr_arg) - :Item_func_hybrid_field_type(), first_expr_num(-1), else_expr_num(-1), - left_result_type(INT_RESULT), case_item(0) - { - ncases= list.elements; - if (first_expr_arg) - { - first_expr_num= list.elements; - list.push_back(first_expr_arg); - } - if (else_expr_arg) - { - else_expr_num= list.elements; - list.push_back(else_expr_arg); - } - set_arguments(list); - bzero(&cmp_items, sizeof(cmp_items)); - } + Item_func_case(THD *thd, List<Item> &list, Item *first_expr_arg, + Item *else_expr_arg); double real_op(); longlong int_op(); String *str_op(String *); @@ -1262,54 +1518,65 @@ public: const char *func_name() const { return "case"; } virtual void print(String *str, enum_query_type query_type); Item *find_item(String *str); - CHARSET_INFO *compare_collation() { return cmp_collation.collation; } + CHARSET_INFO *compare_collation() const { return cmp_collation.collation; } void cleanup(); + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond); }; /* - The Item_func_in class implements the in_expr IN(values_list) function. + The Item_func_in class implements + in_expr IN (<in value list>) + and + in_expr NOT IN (<in value list>) The current implementation distinguishes 2 cases: - 1) all items in the value_list are constants and have the same + 1) all items in <in value list> are constants and have the same result type. This case is handled by in_vector class. - 2) items in the value_list have different result types or there is some - non-constant items. - In this case Item_func_in employs several cmp_item objects to performs - comparisons of in_expr and an item from the values_list. One cmp_item + 2) otherwise Item_func_in employs several cmp_item objects to perform + comparisons of in_expr and an item from <in value list>. One cmp_item object for each result type. Different result types are collected in the fix_length_and_dec() member function by means of collect_cmp_types() function. */ class Item_func_in :public Item_func_opt_neg { -public: - /* - an array of values when the right hand arguments of IN - are all SQL constant and there are no nulls + /** + Usable if <in value list> is made only of constants. Returns true if one + of these constants contains a NULL. Example: + IN ( (-5, (12,NULL)), ... ). */ + bool list_contains_null(); +protected: + SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *value); +public: + /// An array of values, created when the bisection lookup method is used in_vector *array; + /** + If there is some NULL among <in value list>, during a val_int() call; for + example + IN ( (1,(3,'col')), ... ), where 'col' is a column which evaluates to + NULL. + */ bool have_null; - /* - true when all arguments of the IN clause are of compatible types + /** + true when all arguments of the IN list are of compatible types and can be used safely as comparisons for key conditions */ bool arg_types_compatible; - Item_result left_result_type; + Item_result left_cmp_type; cmp_item *cmp_items[6]; /* One cmp_item for each result type */ - DTCollation cmp_collation; - Item_func_in(List<Item> &list) - :Item_func_opt_neg(list), array(0), have_null(0), - arg_types_compatible(FALSE) + Item_func_in(THD *thd, List<Item> &list): + Item_func_opt_neg(thd, list), array(0), have_null(0), + arg_types_compatible(FALSE) { bzero(&cmp_items, sizeof(cmp_items)); allowed_arg_cols= 0; // Fetch this value from first argument - sargable= TRUE; } longlong val_int(); bool fix_fields(THD *, Item **); void fix_length_and_dec(); - uint decimal_precision() const { return 1; } void cleanup() { uint i; @@ -1324,16 +1591,32 @@ public: } DBUG_VOID_RETURN; } - optimize_type select_optimize() const - { return OPTIMIZE_KEY; } + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level, + table_map usable_tables, SARGABLE_PARAM **sargables); + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr); + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) + { + /* + Note, we pass ANY_SUBST, this makes sure that non of the args + will be replaced to a zero-filled Item_string. + Such a change would require rebuilding of cmp_items. + */ + Context cmpctx(ANY_SUBST, m_compare_type, + Item_func_in::compare_collation()); + for (uint i= 0; i < arg_count; i++) + { + if (arg_types_compatible || i > 0) + args[i]->propagate_equal_fields_and_change_item_tree(thd, cmpctx, + cond, &args[i]); + } + return this; + } virtual void print(String *str, enum_query_type query_type); enum Functype functype() const { return IN_FUNC; } const char *func_name() const { return " IN "; } - bool nulls_in_row(); - bool is_bool_func() { return 1; } - CHARSET_INFO *compare_collation() { return cmp_collation.collation; } bool eval_not_null_tables(uchar *opt_arg); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); + bool count_sargable_conds(uchar *arg); }; class cmp_item_row :public cmp_item @@ -1348,7 +1631,7 @@ public: int cmp(Item *arg); int compare(cmp_item *arg); cmp_item *make_same(); - void store_value_by_template(cmp_item *tmpl, Item *); + void store_value_by_template(THD *thd, cmp_item *tmpl, Item *); friend void Item_func_in::fix_length_and_dec(); }; @@ -1357,7 +1640,7 @@ class in_row :public in_vector { cmp_item_row tmp; public: - in_row(uint elements, Item *); + in_row(THD *thd, uint elements, Item *); ~in_row(); void set(uint pos,Item *item); uchar *get_value(Item *item); @@ -1366,23 +1649,69 @@ public: }; /* Functions used by where clause */ +class Item_func_null_predicate :public Item_bool_func +{ +protected: + SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *value) + { + DBUG_ENTER("Item_func_null_predicate::get_func_mm_tree"); + DBUG_RETURN(get_mm_parts(param, field, functype(), value)); + } + SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param, Field *field, + KEY_PART *key_part, + Item_func::Functype type, Item *value); +public: + Item_func_null_predicate(THD *thd, Item *a): Item_bool_func(thd, a) { } + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level, + table_map usable_tables, SARGABLE_PARAM **sargables); + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) + { + DBUG_ENTER("Item_func_null_predicate::get_mm_tree"); + SEL_TREE *ftree= get_full_func_mm_tree_for_args(param, args[0], NULL); + if (!ftree) + ftree= Item_func::get_mm_tree(param, cond_ptr); + DBUG_RETURN(ftree); + } + CHARSET_INFO *compare_collation() const + { return args[0]->collation.collation; } + void fix_length_and_dec() { decimals=0; max_length=1; maybe_null=0; } + bool count_sargable_conds(uchar *arg); +}; + -class Item_func_isnull :public Item_bool_func +class Item_func_isnull :public Item_func_null_predicate { public: - Item_func_isnull(Item *a) :Item_bool_func(a) { sargable= TRUE; } + Item_func_isnull(THD *thd, Item *a): Item_func_null_predicate(thd, a) {} longlong val_int(); enum Functype functype() const { return ISNULL_FUNC; } void fix_length_and_dec() { - decimals=0; max_length=1; maybe_null=0; + Item_func_null_predicate::fix_length_and_dec(); update_used_tables(); } const char *func_name() const { return "isnull"; } + + bool arg_is_datetime_notnull_field() + { + Item **args= arguments(); + if (args[0]->real_item()->type() == Item::FIELD_ITEM) + { + Field *field=((Item_field*) args[0]->real_item())->field; + + if (((field->type() == MYSQL_TYPE_DATE) || + (field->type() == MYSQL_TYPE_DATETIME)) && + (field->flags & NOT_NULL_FLAG)) + return true; + } + return false; + } + /* Optimize case of not_null_column IS NULL */ virtual void update_used_tables() { - if (!args[0]->maybe_null) + if (!args[0]->maybe_null && !arg_is_datetime_notnull_field()) { used_tables_cache= 0; /* is always false */ const_item_cache= 1; @@ -1394,11 +1723,11 @@ public: const_item_cache= args[0]->const_item(); } } + COND *remove_eq_conds(THD *thd, Item::cond_result *cond_value, + bool top_level); virtual void print(String *str, enum_query_type query_type); table_map not_null_tables() const { return 0; } - optimize_type select_optimize() const { return OPTIMIZE_NULL; } Item *neg_transformer(THD *thd); - CHARSET_INFO *compare_collation() { return args[0]->collation.collation; } }; /* Functions used by HAVING for rewriting IN subquery */ @@ -1413,8 +1742,8 @@ class Item_is_not_null_test :public Item_func_isnull { Item_in_subselect* owner; public: - Item_is_not_null_test(Item_in_subselect* ow, Item *a) - :Item_func_isnull(a), owner(ow) + Item_is_not_null_test(THD *thd, Item_in_subselect* ow, Item *a): + Item_func_isnull(thd, a), owner(ow) {} enum Functype functype() const { return ISNOTNULLTEST_FUNC; } longlong val_int(); @@ -1429,25 +1758,20 @@ public: }; -class Item_func_isnotnull :public Item_bool_func +class Item_func_isnotnull :public Item_func_null_predicate { bool abort_on_null; public: - Item_func_isnotnull(Item *a) :Item_bool_func(a), abort_on_null(0) - { sargable= TRUE; } + Item_func_isnotnull(THD *thd, Item *a): + Item_func_null_predicate(thd, a), abort_on_null(0) + { } longlong val_int(); enum Functype functype() const { return ISNOTNULL_FUNC; } - void fix_length_and_dec() - { - decimals=0; max_length=1; maybe_null=0; - } const char *func_name() const { return "isnotnull"; } - optimize_type select_optimize() const { return OPTIMIZE_NULL; } table_map not_null_tables() const { return abort_on_null ? not_null_tables_cache : 0; } Item *neg_transformer(THD *thd); virtual void print(String *str, enum_query_type query_type); - CHARSET_INFO *compare_collation() { return args[0]->collation.collation; } void top_level_item() { abort_on_null=1; } }; @@ -1470,43 +1794,226 @@ class Item_func_like :public Item_bool_func2 enum { alphabet_size = 256 }; Item *escape_item; - + bool escape_used_in_parsing; + bool use_sampling; + DTCollation cmp_collation; + String cmp_value1, cmp_value2; + bool with_sargable_pattern() const; +protected: + SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *value) + { + DBUG_ENTER("Item_func_like::get_func_mm_tree"); + DBUG_RETURN(get_mm_parts(param, field, LIKE_FUNC, value)); + } + SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param, Field *field, + KEY_PART *key_part, + Item_func::Functype type, Item *value); public: int escape; - Item_func_like(Item *a,Item *b, Item *escape_arg, bool escape_used) - :Item_bool_func2(a,b), canDoTurboBM(FALSE), pattern(0), pattern_len(0), - bmGs(0), bmBc(0), escape_item(escape_arg), - escape_used_in_parsing(escape_used) {} + Item_func_like(THD *thd, Item *a, Item *b, Item *escape_arg, bool escape_used): + Item_bool_func2(thd, a, b), canDoTurboBM(FALSE), pattern(0), pattern_len(0), + bmGs(0), bmBc(0), escape_item(escape_arg), + escape_used_in_parsing(escape_used), use_sampling(0) {} longlong val_int(); enum Functype functype() const { return LIKE_FUNC; } - optimize_type select_optimize() const; - cond_result eq_cmp_result() const { return COND_TRUE; } + void print(String *str, enum_query_type query_type) + { + Item_func::print_op(str, query_type); + } + CHARSET_INFO *compare_collation() const + { return cmp_collation.collation; } + cond_result eq_cmp_result() const + { + /** + We cannot always rewrite conditions as follows: + from: WHERE expr1=const AND expr1 LIKE expr2 + to: WHERE expr1=const AND const LIKE expr2 + or + from: WHERE expr1=const AND expr2 LIKE expr1 + to: WHERE expr1=const AND expr2 LIKE const + + because LIKE works differently comparing to the regular "=" operator: + + 1. LIKE performs a stricter one-character-to-one-character comparison + and does not recognize contractions and expansions. + Replacing "expr1" to "const in LIKE would make the condition + stricter in case of a complex collation. + + 2. LIKE does not ignore trailing spaces and thus works differently + from the "=" operator in case of "PAD SPACE" collations + (which are the majority in MariaDB). So, for "PAD SPACE" collations: + + - expr1=const - ignores trailing spaces + - const LIKE expr2 - does not ignore trailing spaces + - expr2 LIKE const - does not ignore trailing spaces + + Allow only "binary" for now. + It neither ignores trailing spaces nor has contractions/expansions. + + TODO: + We could still replace "expr1" to "const" in "expr1 LIKE expr2" + in case of a "PAD SPACE" collation, but only if "expr2" has '%' + at the end. + */ + return compare_collation() == &my_charset_bin ? COND_TRUE : COND_OK; + } + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level, + table_map usable_tables, SARGABLE_PARAM **sargables); + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr); + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) + { + /* + LIKE differs from the regular comparison operator ('=') in the following: + - LIKE never ignores trailing spaces (even for PAD SPACE collations) + Propagation of equal fields with a PAD SPACE collation into LIKE + is not safe. + Example: + WHERE a='a ' AND a LIKE 'a' - returns true for 'a' + cannot be rewritten to: + WHERE a='a ' AND 'a ' LIKE 'a' - returns false for 'a' + Note, binary collations in MySQL/MariaDB, e.g. latin1_bin, + still have the PAD SPACE attribute and ignore trailing spaces! + - LIKE does not take into account contractions, expansions, + and ignorable characters. + Propagation of equal fields with contractions/expansions/ignorables + is also not safe. + + It's safe to propagate my_charset_bin (BINARY/VARBINARY/BLOB) values, + because they do not ignore trailing spaces and have one-to-one mapping + between a string and its weights. + The below condition should be true only for my_charset_bin + (as of version 10.1.7). + */ + uint flags= Item_func_like::compare_collation()->state; + if ((flags & MY_CS_NOPAD) && !(flags & MY_CS_NON1TO1)) + Item_args::propagate_equal_fields(thd, + Context(ANY_SUBST, + STRING_RESULT, + compare_collation()), + cond); + return this; + } const char *func_name() const { return "like"; } bool fix_fields(THD *thd, Item **ref); + void fix_length_and_dec() + { + max_length= 1; + agg_arg_charsets_for_comparison(cmp_collation, args, 2); + } void cleanup(); + + bool find_selective_predicates_list_processor(uchar *arg); +}; + + +class Regexp_processor_pcre +{ + pcre *m_pcre; + pcre_extra m_pcre_extra; + bool m_conversion_is_needed; + bool m_is_const; + int m_library_flags; + CHARSET_INFO *m_data_charset; + CHARSET_INFO *m_library_charset; + String m_prev_pattern; + int m_pcre_exec_rc; + int m_SubStrVec[30]; + void pcre_exec_warn(int rc) const; + int pcre_exec_with_warn(const pcre *code, const pcre_extra *extra, + const char *subject, int length, int startoffset, + int options, int *ovector, int ovecsize); +public: + String *convert_if_needed(String *src, String *converter); + String subject_converter; + String pattern_converter; + String replace_converter; + Regexp_processor_pcre() : + m_pcre(NULL), m_conversion_is_needed(true), m_is_const(0), + m_library_flags(0), + m_data_charset(&my_charset_utf8_general_ci), + m_library_charset(&my_charset_utf8_general_ci) + { + m_pcre_extra.flags= PCRE_EXTRA_MATCH_LIMIT_RECURSION; + m_pcre_extra.match_limit_recursion= 100L; + } + int default_regex_flags(); + void set_recursion_limit(THD *); + void init(CHARSET_INFO *data_charset, int extra_flags) + { + m_library_flags= default_regex_flags() | extra_flags | + (data_charset != &my_charset_bin ? + (PCRE_UTF8 | PCRE_UCP) : 0) | + ((data_charset->state & + (MY_CS_BINSORT | MY_CS_CSSORT)) ? 0 : PCRE_CASELESS); + + // Convert text data to utf-8. + m_library_charset= data_charset == &my_charset_bin ? + &my_charset_bin : &my_charset_utf8_general_ci; + + m_conversion_is_needed= (data_charset != &my_charset_bin) && + !my_charset_same(data_charset, m_library_charset); + } + void fix_owner(Item_func *owner, Item *subject_arg, Item *pattern_arg); + bool compile(String *pattern, bool send_error); + bool compile(Item *item, bool send_error); + bool recompile(Item *item) + { + return !m_is_const && compile(item, false); + } + bool exec(const char *str, int length, int offset); + bool exec(String *str, int offset, uint n_result_offsets_to_convert); + bool exec(Item *item, int offset, uint n_result_offsets_to_convert); + bool match() const { return m_pcre_exec_rc < 0 ? 0 : 1; } + int nsubpatterns() const { return m_pcre_exec_rc <= 0 ? 0 : m_pcre_exec_rc; } + int subpattern_start(int n) const + { + return m_pcre_exec_rc <= 0 ? 0 : m_SubStrVec[n * 2]; + } + int subpattern_end(int n) const + { + return m_pcre_exec_rc <= 0 ? 0 : m_SubStrVec[n * 2 + 1]; + } + int subpattern_length(int n) const + { + return subpattern_end(n) - subpattern_start(n); + } + void cleanup() + { + if (m_pcre) + { + pcre_free(m_pcre); + m_pcre= NULL; + } + m_prev_pattern.length(0); + } + bool is_compiled() const { return m_pcre != NULL; } + bool is_const() const { return m_is_const; } + void set_const(bool arg) { m_is_const= arg; } + CHARSET_INFO * library_charset() const { return m_library_charset; } }; class Item_func_regex :public Item_bool_func { - my_regex_t preg; - bool regex_compiled; - bool regex_is_const; - String prev_regexp; + Regexp_processor_pcre re; DTCollation cmp_collation; - CHARSET_INFO *regex_lib_charset; - int regex_lib_flags; - String conv; - int regcomp(bool send_error); public: - Item_func_regex(Item *a,Item *b) :Item_bool_func(a,b), - regex_compiled(0),regex_is_const(0) {} - void cleanup(); + Item_func_regex(THD *thd, Item *a, Item *b): Item_bool_func(thd, a, b) + {} + void cleanup() + { + DBUG_ENTER("Item_func_regex::cleanup"); + Item_bool_func::cleanup(); + re.cleanup(); + DBUG_VOID_RETURN; + } longlong val_int(); bool fix_fields(THD *thd, Item **ref); + void fix_length_and_dec(); const char *func_name() const { return "regexp"; } virtual inline void print(String *str, enum_query_type query_type) @@ -1514,7 +2021,28 @@ public: print_op(str, query_type); } - CHARSET_INFO *compare_collation() { return cmp_collation.collation; } + CHARSET_INFO *compare_collation() const { return cmp_collation.collation; } +}; + + +class Item_func_regexp_instr :public Item_int_func +{ + Regexp_processor_pcre re; + DTCollation cmp_collation; +public: + Item_func_regexp_instr(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) + {} + void cleanup() + { + DBUG_ENTER("Item_func_regexp_instr::cleanup"); + Item_int_func::cleanup(); + re.cleanup(); + DBUG_VOID_RETURN; + } + longlong val_int(); + bool fix_fields(THD *thd, Item **ref); + void fix_length_and_dec(); + const char *func_name() const { return "regexp_instr"; } }; @@ -1529,58 +2057,67 @@ protected: public: /* Item_cond() is only used to create top level items */ - Item_cond(): Item_bool_func(), abort_on_null(1) + Item_cond(THD *thd): Item_bool_func(thd), abort_on_null(1) { const_item_cache=0; } - Item_cond(Item *i1,Item *i2) - :Item_bool_func(), abort_on_null(0) - { - list.push_back(i1); - list.push_back(i2); - } + Item_cond(THD *thd, Item *i1, Item *i2); Item_cond(THD *thd, Item_cond *item); - Item_cond(List<Item> &nlist) - :Item_bool_func(), list(nlist), abort_on_null(0) {} - bool add(Item *item) + Item_cond(THD *thd, List<Item> &nlist): + Item_bool_func(thd), list(nlist), abort_on_null(0) {} + bool add(Item *item, MEM_ROOT *root) { DBUG_ASSERT(item); - return list.push_back(item); + return list.push_back(item, root); } - bool add_at_head(Item *item) + bool add_at_head(Item *item, MEM_ROOT *root) { DBUG_ASSERT(item); - return list.push_front(item); + return list.push_front(item, root); } void add_at_head(List<Item> *nlist) { DBUG_ASSERT(nlist->elements); - list.prepand(nlist); + list.prepend(nlist); } void add_at_end(List<Item> *nlist) { DBUG_ASSERT(nlist->elements); - list.concat(nlist); + list.append(nlist); } bool fix_fields(THD *, Item **ref); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); enum Type type() const { return COND_ITEM; } List<Item>* argument_list() { return &list; } table_map used_tables() const; - void update_used_tables(); + void update_used_tables() + { + used_tables_and_const_cache_init(); + used_tables_and_const_cache_update_and_join(list); + } + COND *build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref); + COND *remove_eq_conds(THD *thd, Item::cond_result *cond_value, + bool top_level); + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables); + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr); virtual void print(String *str, enum_query_type query_type); - void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields); + void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields, + uint flags); friend int setup_conds(THD *thd, TABLE_LIST *tables, TABLE_LIST *leaves, COND **conds); void top_level_item() { abort_on_null=1; } bool top_level() { return abort_on_null; } void copy_andor_arguments(THD *thd, Item_cond *item); bool walk(Item_processor processor, bool walk_subquery, uchar *arg); - Item *transform(Item_transformer transformer, uchar *arg); + Item *transform(THD *thd, Item_transformer transformer, uchar *arg); void traverse_cond(Cond_traverser, void *arg, traverse_order order); void neg_arguments(THD *thd); enum_field_types field_type() const { return MYSQL_TYPE_LONGLONG; } - bool subst_argument_checker(uchar **arg) { return TRUE; } - Item *compile(Item_analyzer analyzer, uchar **arg_p, + Item* propagate_equal_fields(THD *, const Context &, COND_EQUAL *); + Item *compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, Item_transformer transformer, uchar *arg_t); bool eval_not_null_tables(uchar *opt_arg); }; @@ -1702,67 +2239,71 @@ class Item_equal: public Item_bool_func the equal_items should be ignored. */ bool cond_true; - /* - compare_as_dates=TRUE <-> constants equal to fields from equal_items - must be compared as datetimes and not as strings. - compare_as_dates can be TRUE only if with_const=TRUE - */ - bool compare_as_dates; - /* - The comparator used to compare constants equal to fields from equal_items - as datetimes. The comparator is used only if compare_as_dates=TRUE - */ - Arg_comparator cmp; - /* For Item_equal objects inside an OR clause: one of the fields that were used in the original equality. */ Item_field *context_field; + bool link_equal_fields; + + Item_result m_compare_type; + CHARSET_INFO *m_compare_collation; + String cmp_value1, cmp_value2; public: COND_EQUAL *upper_levels; /* multiple equalities of upper and levels */ - inline Item_equal() - : Item_bool_func(), with_const(FALSE), eval_item(0), cond_false(0), - context_field(NULL) - { const_item_cache=0; sargable= TRUE; } - Item_equal(Item *f1, Item *f2, bool with_const_item); - Item_equal(Item_equal *item_equal); + Item_equal(THD *thd, Item *f1, Item *f2, bool with_const_item); + Item_equal(THD *thd, Item_equal *item_equal); /* Currently the const item is always the first in the list of equal items */ inline Item* get_const() { return with_const ? equal_items.head() : NULL; } - void add_const(Item *c, Item *f = NULL); + void add_const(THD *thd, Item *c); /** Add a non-constant item to the multiple equality */ - void add(Item *f) { equal_items.push_back(f); } + void add(Item *f, MEM_ROOT *root) { equal_items.push_back(f, root); } bool contains(Field *field); Item* get_first(struct st_join_table *context, Item *field); /** Get number of field items / references to field items in this object */ - uint n_field_items() { return equal_items.elements-test(with_const); } - void merge(Item_equal *item); - bool merge_with_check(Item_equal *equal_item, bool save_merged); - void merge_into_list(List<Item_equal> *list, bool save_merged, + uint n_field_items() { return equal_items.elements - MY_TEST(with_const); } + void merge(THD *thd, Item_equal *item); + bool merge_with_check(THD *thd, Item_equal *equal_item, bool save_merged); + void merge_into_list(THD *thd, List<Item_equal> *list, bool save_merged, bool only_intersected); - void update_const(); + void update_const(THD *thd); enum Functype functype() const { return MULT_EQUAL_FUNC; } longlong val_int(); const char *func_name() const { return "multiple equal"; } - optimize_type select_optimize() const { return OPTIMIZE_EQUAL; } void sort(Item_field_cmpfunc compare, void *arg); void fix_length_and_dec(); bool fix_fields(THD *thd, Item **ref); + void cleanup() + { + delete eval_item; + eval_item= NULL; + } void update_used_tables(); + COND *build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref); + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables); + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr); bool walk(Item_processor processor, bool walk_subquery, uchar *arg); - Item *transform(Item_transformer transformer, uchar *arg); + Item *transform(THD *thd, Item_transformer transformer, uchar *arg); virtual void print(String *str, enum_query_type query_type); - CHARSET_INFO *compare_collation(); + Item_result compare_type() const { return m_compare_type; } + CHARSET_INFO *compare_collation() const { return m_compare_collation; } void set_context_field(Item_field *ctx_field) { context_field= ctx_field; } + void set_link_equal_fields(bool flag) { link_equal_fields= flag; } + friend class Item_equal_fields_iterator; bool count_sargable_conds(uchar *arg); friend class Item_equal_iterator<List_iterator_fast,Item>; friend class Item_equal_iterator<List_iterator,Item>; - friend Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, - Item_equal *item_equal); + friend Item *eliminate_item_equal(THD *thd, COND *cond, + COND_EQUAL *upper_levels, + Item_equal *item_equal); friend bool setup_sj_materialization_part1(struct st_join_table *tab); friend bool setup_sj_materialization_part2(struct st_join_table *tab); }; @@ -1779,6 +2320,11 @@ public: { upper_levels= 0; } + COND_EQUAL(Item_equal *item, MEM_ROOT *mem_root) + :upper_levels(0) + { + current_level.push_back(item, mem_root); + } void copy(COND_EQUAL &cond_equal) { max_members= cond_equal.max_members; @@ -1873,27 +2419,29 @@ public: class Item_cond_and :public Item_cond { public: - COND_EQUAL cond_equal; /* contains list of Item_equal objects for - the current and level and reference - to multiple equalities of upper and levels */ - Item_cond_and() :Item_cond() {} - Item_cond_and(Item *i1,Item *i2) :Item_cond(i1,i2) {} - Item_cond_and(THD *thd, Item_cond_and *item) :Item_cond(thd, item) {} - Item_cond_and(List<Item> &list_arg): Item_cond(list_arg) {} + COND_EQUAL m_cond_equal; /* contains list of Item_equal objects for + the current and level and reference + to multiple equalities of upper and levels */ + Item_cond_and(THD *thd): Item_cond(thd) {} + Item_cond_and(THD *thd, Item *i1,Item *i2): Item_cond(thd, i1, i2) {} + Item_cond_and(THD *thd, Item_cond_and *item): Item_cond(thd, item) {} + Item_cond_and(THD *thd, List<Item> &list_arg): Item_cond(thd, list_arg) {} enum Functype functype() const { return COND_AND_FUNC; } longlong val_int(); const char *func_name() const { return "and"; } table_map not_null_tables() const { return abort_on_null ? not_null_tables_cache: and_tables_cache; } - Item* copy_andor_structure(THD *thd) - { - Item_cond_and *item; - if ((item= new Item_cond_and(thd, this))) - item->copy_andor_arguments(thd, this); - return item; - } + Item *copy_andor_structure(THD *thd); Item *neg_transformer(THD *thd); void mark_as_condition_AND_part(TABLE_LIST *embedding); + virtual uint exists2in_reserved_items() { return list.elements; }; + bool walk_top_and(Item_processor processor, uchar *arg); + COND *build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref); + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level, + table_map usable_tables, SARGABLE_PARAM **sargables); + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr); }; inline bool is_cond_and(Item *item) @@ -1908,28 +2456,31 @@ inline bool is_cond_and(Item *item) class Item_cond_or :public Item_cond { public: - Item_cond_or() :Item_cond() {} - Item_cond_or(Item *i1,Item *i2) :Item_cond(i1,i2) {} - Item_cond_or(THD *thd, Item_cond_or *item) :Item_cond(thd, item) {} - Item_cond_or(List<Item> &list_arg): Item_cond(list_arg) {} + Item_cond_or(THD *thd): Item_cond(thd) {} + Item_cond_or(THD *thd, Item *i1,Item *i2): Item_cond(thd, i1, i2) {} + Item_cond_or(THD *thd, Item_cond_or *item): Item_cond(thd, item) {} + Item_cond_or(THD *thd, List<Item> &list_arg): Item_cond(thd, list_arg) {} enum Functype functype() const { return COND_OR_FUNC; } longlong val_int(); const char *func_name() const { return "or"; } table_map not_null_tables() const { return and_tables_cache; } - Item* copy_andor_structure(THD *thd) - { - Item_cond_or *item; - if ((item= new Item_cond_or(thd, this))) - item->copy_andor_arguments(thd, this); - return item; - } + Item *copy_andor_structure(THD *thd); Item *neg_transformer(THD *thd); }; +class Item_func_dyncol_check :public Item_bool_func +{ +public: + Item_func_dyncol_check(THD *thd, Item *str): Item_bool_func(thd, str) {} + longlong val_int(); + const char *func_name() const { return "column_check"; } +}; + class Item_func_dyncol_exists :public Item_bool_func { public: - Item_func_dyncol_exists(Item *str, Item *num) :Item_bool_func(str, num) {} + Item_func_dyncol_exists(THD *thd, Item *str, Item *num): + Item_bool_func(thd, str, num) {} longlong val_int(); const char *func_name() const { return "column_exists"; } }; @@ -1943,24 +2494,103 @@ inline bool is_cond_or(Item *item) return (cond_item->functype() == Item_func::COND_OR_FUNC); } -/* Some useful inline functions */ +Item *and_expressions(Item *a, Item *b, Item **org_item); + +longlong get_datetime_value(THD *thd, Item ***item_arg, Item **cache_arg, + enum_field_types f_type, bool *is_null); -inline Item *and_conds(Item *a, Item *b) + +class Comp_creator { - if (!b) return a; - if (!a) return b; - return new Item_cond_and(a, b); -} +public: + Comp_creator() {} /* Remove gcc warning */ + virtual ~Comp_creator() {} /* Remove gcc warning */ + /** + Create operation with given arguments. + */ + virtual Item_bool_rowready_func2* create(THD *thd, Item *a, Item *b) + const = 0; + /** + Create operation with given arguments in swap order. + */ + virtual Item_bool_rowready_func2* create_swap(THD *thd, Item *a, Item *b) + const = 0; + virtual const char* symbol(bool invert) const = 0; + virtual bool eqne_op() const = 0; + virtual bool l_op() const = 0; +}; +class Eq_creator :public Comp_creator +{ +public: + Eq_creator() {} /* Remove gcc warning */ + virtual ~Eq_creator() {} /* Remove gcc warning */ + Item_bool_rowready_func2* create(THD *thd, Item *a, Item *b) const; + Item_bool_rowready_func2* create_swap(THD *thd, Item *a, Item *b) const; + const char* symbol(bool invert) const { return invert? "<>" : "="; } + bool eqne_op() const { return 1; } + bool l_op() const { return 0; } +}; -Item *and_expressions(Item *a, Item *b, Item **org_item); +class Ne_creator :public Comp_creator +{ +public: + Ne_creator() {} /* Remove gcc warning */ + virtual ~Ne_creator() {} /* Remove gcc warning */ + Item_bool_rowready_func2* create(THD *thd, Item *a, Item *b) const; + Item_bool_rowready_func2* create_swap(THD *thd, Item *a, Item *b) const; + const char* symbol(bool invert) const { return invert? "=" : "<>"; } + bool eqne_op() const { return 1; } + bool l_op() const { return 0; } +}; -longlong get_datetime_value(THD *thd, Item ***item_arg, Item **cache_arg, - Item *warn_item, bool *is_null); +class Gt_creator :public Comp_creator +{ +public: + Gt_creator() {} /* Remove gcc warning */ + virtual ~Gt_creator() {} /* Remove gcc warning */ + Item_bool_rowready_func2* create(THD *thd, Item *a, Item *b) const; + Item_bool_rowready_func2* create_swap(THD *thd, Item *a, Item *b) const; + const char* symbol(bool invert) const { return invert? "<=" : ">"; } + bool eqne_op() const { return 0; } + bool l_op() const { return 0; } +}; + +class Lt_creator :public Comp_creator +{ +public: + Lt_creator() {} /* Remove gcc warning */ + virtual ~Lt_creator() {} /* Remove gcc warning */ + Item_bool_rowready_func2* create(THD *thd, Item *a, Item *b) const; + Item_bool_rowready_func2* create_swap(THD *thd, Item *a, Item *b) const; + const char* symbol(bool invert) const { return invert? ">=" : "<"; } + bool eqne_op() const { return 0; } + bool l_op() const { return 1; } +}; +class Ge_creator :public Comp_creator +{ +public: + Ge_creator() {} /* Remove gcc warning */ + virtual ~Ge_creator() {} /* Remove gcc warning */ + Item_bool_rowready_func2* create(THD *thd, Item *a, Item *b) const; + Item_bool_rowready_func2* create_swap(THD *thd, Item *a, Item *b) const; + const char* symbol(bool invert) const { return invert? "<" : ">="; } + bool eqne_op() const { return 0; } + bool l_op() const { return 0; } +}; -bool get_mysql_time_from_str(THD *thd, String *str, timestamp_type warn_type, - const char *warn_name, MYSQL_TIME *l_time); +class Le_creator :public Comp_creator +{ +public: + Le_creator() {} /* Remove gcc warning */ + virtual ~Le_creator() {} /* Remove gcc warning */ + Item_bool_rowready_func2* create(THD *thd, Item *a, Item *b) const; + Item_bool_rowready_func2* create_swap(THD *thd, Item *a, Item *b) const; + const char* symbol(bool invert) const { return invert? ">" : "<="; } + bool eqne_op() const { return 0; } + bool l_op() const { return 1; } +}; /* These need definitions from this file but the variables are defined @@ -1975,4 +2605,3 @@ extern Ge_creator ge_creator; extern Le_creator le_creator; #endif /* ITEM_CMPFUNC_INCLUDED */ - diff --git a/sql/item_create.cc b/sql/item_create.cc index 45de3850fcd..82f6bbd3173 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -22,6 +22,7 @@ Functions to create an item. Used by sql_yac.yy */ +#include <my_global.h> #include "sql_priv.h" /* It is necessary to include set_var.h instead of item.h because there @@ -32,6 +33,8 @@ #include "set_var.h" #include "sp_head.h" #include "sp.h" +#include "item_inetfunc.h" +#include "sql_time.h" /* ============================================================================= @@ -50,13 +53,12 @@ static const char* item_name(Item *a, String *str) static void wrong_precision_error(uint errcode, Item *a, - ulonglong number, ulong maximum) + ulonglong number, uint maximum) { char buff[1024]; String buf(buff, sizeof(buff), system_charset_info); - my_error(errcode, MYF(0), (uint) min(number, UINT_MAX32), - item_name(a, &buf), maximum); + my_error(errcode, MYF(0), number, item_name(a, &buf), maximum); } @@ -84,9 +86,9 @@ bool get_length_and_scale(ulonglong length, ulonglong decimals, return 1; } - *out_length= (ulong) length; *out_decimals= (uint) decimals; - my_decimal_trim(out_length, out_decimals); + my_decimal_trim(&length, out_decimals); + *out_length= (ulong) length; if (*out_length < *out_decimals) { @@ -447,6 +449,19 @@ protected: }; +class Create_func_binlog_gtid_pos : public Create_func_arg2 +{ +public: + virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2); + + static Create_func_binlog_gtid_pos s_singleton; + +protected: + Create_func_binlog_gtid_pos() {} + virtual ~Create_func_binlog_gtid_pos() {} +}; + + class Create_func_bit_count : public Create_func_arg1 { public: @@ -498,7 +513,35 @@ protected: Create_func_centroid() {} virtual ~Create_func_centroid() {} }; -#endif + + +class Create_func_convexhull : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_convexhull s_singleton; + +protected: + Create_func_convexhull() {} + virtual ~Create_func_convexhull() {} +}; + + +class Create_func_pointonsurface : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_pointonsurface s_singleton; + +protected: + Create_func_pointonsurface() {} + virtual ~Create_func_pointonsurface() {} +}; + + +#endif /*HAVE_SPATIAL*/ class Create_func_char_length : public Create_func_arg1 @@ -526,6 +569,54 @@ protected: virtual ~Create_func_coercibility() {} }; +class Create_func_dyncol_check : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_dyncol_check s_singleton; + +protected: + Create_func_dyncol_check() {} + virtual ~Create_func_dyncol_check() {} +}; + +class Create_func_dyncol_exists : public Create_func_arg2 +{ +public: + virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2); + + static Create_func_dyncol_exists s_singleton; + +protected: + Create_func_dyncol_exists() {} + virtual ~Create_func_dyncol_exists() {} +}; + +class Create_func_dyncol_list : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_dyncol_list s_singleton; + +protected: + Create_func_dyncol_list() {} + virtual ~Create_func_dyncol_list() {} +}; + +class Create_func_dyncol_json : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_dyncol_json s_singleton; + +protected: + Create_func_dyncol_json() {} + virtual ~Create_func_dyncol_json() {} +}; + class Create_func_compress : public Create_func_arg1 { @@ -553,6 +644,19 @@ protected: }; +class Create_func_decode_histogram : public Create_func_arg2 +{ +public: + Item *create_2_arg(THD *thd, Item *arg1, Item *arg2); + + static Create_func_decode_histogram s_singleton; + +protected: + Create_func_decode_histogram() {} + virtual ~Create_func_decode_histogram() {} +}; + + class Create_func_concat_ws : public Create_native_func { public: @@ -939,7 +1043,19 @@ protected: Create_func_envelope() {} virtual ~Create_func_envelope() {} }; -#endif + +class Create_func_boundary : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_boundary s_singleton; + +protected: + Create_func_boundary() {} + virtual ~Create_func_boundary() {} +}; +#endif /*HAVE_SPATIAL*/ #ifdef HAVE_SPATIAL @@ -1076,6 +1192,19 @@ protected: }; +class Create_func_from_base64 : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_from_base64 s_singleton; + +protected: + Create_func_from_base64() {} + virtual ~Create_func_from_base64() {} +}; + + class Create_func_from_days : public Create_func_arg1 { public: @@ -1175,6 +1304,21 @@ protected: }; +#if defined(HAVE_SPATIAL) && !defined(DBUG_OFF) +class Create_func_gis_debug : public Create_func_arg1 +{ + public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_gis_debug s_singleton; + + protected: + Create_func_gis_debug() {} + virtual ~Create_func_gis_debug() {} +}; +#endif + + #ifdef HAVE_SPATIAL class Create_func_glength : public Create_func_arg1 { @@ -1255,6 +1399,84 @@ protected: }; +class Create_func_inet6_aton : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_inet6_aton s_singleton; + +protected: + Create_func_inet6_aton() {} + virtual ~Create_func_inet6_aton() {} +}; + + +class Create_func_inet6_ntoa : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_inet6_ntoa s_singleton; + +protected: + Create_func_inet6_ntoa() {} + virtual ~Create_func_inet6_ntoa() {} +}; + + +class Create_func_is_ipv4 : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_is_ipv4 s_singleton; + +protected: + Create_func_is_ipv4() {} + virtual ~Create_func_is_ipv4() {} +}; + + +class Create_func_is_ipv6 : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_is_ipv6 s_singleton; + +protected: + Create_func_is_ipv6() {} + virtual ~Create_func_is_ipv6() {} +}; + + +class Create_func_is_ipv4_compat : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_is_ipv4_compat s_singleton; + +protected: + Create_func_is_ipv4_compat() {} + virtual ~Create_func_is_ipv4_compat() {} +}; + + +class Create_func_is_ipv4_mapped : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_is_ipv4_mapped s_singleton; + +protected: + Create_func_is_ipv4_mapped() {} + virtual ~Create_func_is_ipv4_mapped() {} +}; + + class Create_func_instr : public Create_func_arg2 { public: @@ -1284,6 +1506,19 @@ protected: #ifdef HAVE_SPATIAL +class Create_func_relate : public Create_func_arg3 +{ +public: + virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3); + + static Create_func_relate s_singleton; + +protected: + Create_func_relate() {} + virtual ~Create_func_relate() {} +}; + + class Create_func_mbr_intersects : public Create_func_arg2 { public: @@ -1414,6 +1649,19 @@ protected: Create_func_isclosed() {} virtual ~Create_func_isclosed() {} }; + + +class Create_func_isring : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_isring s_singleton; + +protected: + Create_func_isring() {} + virtual ~Create_func_isring() {} +}; #endif @@ -1709,6 +1957,19 @@ protected: }; +class Create_func_master_gtid_wait : public Create_native_func +{ +public: + virtual Item *create_native(THD *thd, LEX_STRING name, List<Item> *item_list); + + static Create_func_master_gtid_wait s_singleton; + +protected: + Create_func_master_gtid_wait() {} + virtual ~Create_func_master_gtid_wait() {} +}; + + class Create_func_md5 : public Create_func_arg1 { public: @@ -1940,6 +2201,45 @@ protected: }; +class Create_func_regexp_instr : public Create_func_arg2 +{ +public: + virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2); + + static Create_func_regexp_instr s_singleton; + +protected: + Create_func_regexp_instr() {} + virtual ~Create_func_regexp_instr() {} +}; + + +class Create_func_regexp_replace : public Create_func_arg3 +{ +public: + virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3); + + static Create_func_regexp_replace s_singleton; + +protected: + Create_func_regexp_replace() {} + virtual ~Create_func_regexp_replace() {} +}; + + +class Create_func_regexp_substr : public Create_func_arg2 +{ +public: + virtual Item *create_2_arg(THD *thd, Item *arg1, Item *arg2); + + static Create_func_regexp_substr s_singleton; + +protected: + Create_func_regexp_substr() {} + virtual ~Create_func_regexp_substr() {} +}; + + class Create_func_radians : public Create_func_arg1 { public: @@ -2005,19 +2305,6 @@ protected: }; -class Create_func_row_count : public Create_func_arg0 -{ -public: - virtual Item *create_builder(THD *thd); - - static Create_func_row_count s_singleton; - -protected: - Create_func_row_count() {} - virtual ~Create_func_row_count() {} -}; - - class Create_func_rpad : public Create_func_arg3 { public: @@ -2295,6 +2582,19 @@ protected: }; +class Create_func_to_base64 : public Create_func_arg1 +{ +public: + virtual Item *create_1_arg(THD *thd, Item *arg1); + + static Create_func_to_base64 s_singleton; + +protected: + Create_func_to_base64() {} + virtual ~Create_func_to_base64() {} +}; + + class Create_func_to_days : public Create_func_arg1 { public: @@ -2670,16 +2970,16 @@ Create_udf_func::create(THD *thd, udf_func *udf, List<Item> *item_list) if (udf->type == UDFTYPE_FUNCTION) { if (arg_count) - func= new (thd->mem_root) Item_func_udf_str(udf, *item_list); + func= new (thd->mem_root) Item_func_udf_str(thd, udf, *item_list); else - func= new (thd->mem_root) Item_func_udf_str(udf); + func= new (thd->mem_root) Item_func_udf_str(thd, udf); } else { if (arg_count) - func= new (thd->mem_root) Item_sum_udf_str(udf, *item_list); + func= new (thd->mem_root) Item_sum_udf_str(thd, udf, *item_list); else - func= new (thd->mem_root) Item_sum_udf_str(udf); + func= new (thd->mem_root) Item_sum_udf_str(thd, udf); } break; } @@ -2688,16 +2988,16 @@ Create_udf_func::create(THD *thd, udf_func *udf, List<Item> *item_list) if (udf->type == UDFTYPE_FUNCTION) { if (arg_count) - func= new (thd->mem_root) Item_func_udf_float(udf, *item_list); + func= new (thd->mem_root) Item_func_udf_float(thd, udf, *item_list); else - func= new (thd->mem_root) Item_func_udf_float(udf); + func= new (thd->mem_root) Item_func_udf_float(thd, udf); } else { if (arg_count) - func= new (thd->mem_root) Item_sum_udf_float(udf, *item_list); + func= new (thd->mem_root) Item_sum_udf_float(thd, udf, *item_list); else - func= new (thd->mem_root) Item_sum_udf_float(udf); + func= new (thd->mem_root) Item_sum_udf_float(thd, udf); } break; } @@ -2706,16 +3006,16 @@ Create_udf_func::create(THD *thd, udf_func *udf, List<Item> *item_list) if (udf->type == UDFTYPE_FUNCTION) { if (arg_count) - func= new (thd->mem_root) Item_func_udf_int(udf, *item_list); + func= new (thd->mem_root) Item_func_udf_int(thd, udf, *item_list); else - func= new (thd->mem_root) Item_func_udf_int(udf); + func= new (thd->mem_root) Item_func_udf_int(thd, udf); } else { if (arg_count) - func= new (thd->mem_root) Item_sum_udf_int(udf, *item_list); + func= new (thd->mem_root) Item_sum_udf_int(thd, udf, *item_list); else - func= new (thd->mem_root) Item_sum_udf_int(udf); + func= new (thd->mem_root) Item_sum_udf_int(thd, udf); } break; } @@ -2724,16 +3024,16 @@ Create_udf_func::create(THD *thd, udf_func *udf, List<Item> *item_list) if (udf->type == UDFTYPE_FUNCTION) { if (arg_count) - func= new (thd->mem_root) Item_func_udf_decimal(udf, *item_list); + func= new (thd->mem_root) Item_func_udf_decimal(thd, udf, *item_list); else - func= new (thd->mem_root) Item_func_udf_decimal(udf); + func= new (thd->mem_root) Item_func_udf_decimal(thd, udf); } else { if (arg_count) - func= new (thd->mem_root) Item_sum_udf_decimal(udf, *item_list); + func= new (thd->mem_root) Item_sum_udf_decimal(thd, udf, *item_list); else - func= new (thd->mem_root) Item_sum_udf_decimal(udf); + func= new (thd->mem_root) Item_sum_udf_decimal(thd, udf); } break; } @@ -2782,10 +3082,10 @@ Create_sp_func::create_with_db(THD *thd, LEX_STRING db, LEX_STRING name, sp_add_used_routine(lex, thd, qname, TYPE_ENUM_FUNCTION); if (arg_count > 0) - func= new (thd->mem_root) Item_func_sp(lex->current_context(), qname, + func= new (thd->mem_root) Item_func_sp(thd, lex->current_context(), qname, *item_list); else - func= new (thd->mem_root) Item_func_sp(lex->current_context(), qname); + func= new (thd->mem_root) Item_func_sp(thd, lex->current_context(), qname); lex->safe_to_cache_query= 0; return func; @@ -2912,7 +3212,7 @@ Create_func_abs Create_func_abs::s_singleton; Item* Create_func_abs::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_abs(arg1); + return new (thd->mem_root) Item_func_abs(thd, arg1); } @@ -2921,7 +3221,7 @@ Create_func_acos Create_func_acos::s_singleton; Item* Create_func_acos::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_acos(arg1); + return new (thd->mem_root) Item_func_acos(thd, arg1); } @@ -2930,7 +3230,7 @@ Create_func_addtime Create_func_addtime::s_singleton; Item* Create_func_addtime::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_add_time(arg1, arg2, 0, 0); + return new (thd->mem_root) Item_func_add_time(thd, arg1, arg2, 0, 0); } @@ -2939,7 +3239,7 @@ Create_func_aes_encrypt Create_func_aes_encrypt::s_singleton; Item* Create_func_aes_encrypt::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_aes_encrypt(arg1, arg2); + return new (thd->mem_root) Item_func_aes_encrypt(thd, arg1, arg2); } @@ -2948,7 +3248,7 @@ Create_func_aes_decrypt Create_func_aes_decrypt::s_singleton; Item* Create_func_aes_decrypt::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_aes_decrypt(arg1, arg2); + return new (thd->mem_root) Item_func_aes_decrypt(thd, arg1, arg2); } @@ -2958,7 +3258,7 @@ Create_func_area Create_func_area::s_singleton; Item* Create_func_area::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_area(arg1); + return new (thd->mem_root) Item_func_area(thd, arg1); } #endif @@ -2969,7 +3269,7 @@ Create_func_as_wkb Create_func_as_wkb::s_singleton; Item* Create_func_as_wkb::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_as_wkb(arg1); + return new (thd->mem_root) Item_func_as_wkb(thd, arg1); } #endif @@ -2980,7 +3280,7 @@ Create_func_as_wkt Create_func_as_wkt::s_singleton; Item* Create_func_as_wkt::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_as_wkt(arg1); + return new (thd->mem_root) Item_func_as_wkt(thd, arg1); } #endif @@ -2990,7 +3290,7 @@ Create_func_asin Create_func_asin::s_singleton; Item* Create_func_asin::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_asin(arg1); + return new (thd->mem_root) Item_func_asin(thd, arg1); } @@ -3010,14 +3310,14 @@ Create_func_atan::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_atan(param_1); + func= new (thd->mem_root) Item_func_atan(thd, param_1); break; } case 2: { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_atan(param_1, param_2); + func= new (thd->mem_root) Item_func_atan(thd, param_1, param_2); break; } default: @@ -3037,7 +3337,7 @@ Item* Create_func_benchmark::create_2_arg(THD *thd, Item *arg1, Item *arg2) { thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - return new (thd->mem_root) Item_func_benchmark(arg1, arg2); + return new (thd->mem_root) Item_func_benchmark(thd, arg1, arg2); } @@ -3046,9 +3346,26 @@ Create_func_bin Create_func_bin::s_singleton; Item* Create_func_bin::create_1_arg(THD *thd, Item *arg1) { - Item *i10= new (thd->mem_root) Item_int((int32) 10,2); - Item *i2= new (thd->mem_root) Item_int((int32) 2,1); - return new (thd->mem_root) Item_func_conv(arg1, i10, i2); + Item *i10= new (thd->mem_root) Item_int(thd, (int32) 10,2); + Item *i2= new (thd->mem_root) Item_int(thd, (int32) 2,1); + return new (thd->mem_root) Item_func_conv(thd, arg1, i10, i2); +} + + +Create_func_binlog_gtid_pos Create_func_binlog_gtid_pos::s_singleton; + +Item* +Create_func_binlog_gtid_pos::create_2_arg(THD *thd, Item *arg1, Item *arg2) +{ +#ifdef HAVE_REPLICATION + if (!mysql_bin_log.is_open()) +#endif + { + my_error(ER_NO_BINARY_LOGGING, MYF(0)); + return NULL; + } + thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); + return new (thd->mem_root) Item_func_binlog_gtid_pos(thd, arg1, arg2); } @@ -3057,7 +3374,7 @@ Create_func_bit_count Create_func_bit_count::s_singleton; Item* Create_func_bit_count::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_bit_count(arg1); + return new (thd->mem_root) Item_func_bit_count(thd, arg1); } @@ -3066,7 +3383,7 @@ Create_func_bit_length Create_func_bit_length::s_singleton; Item* Create_func_bit_length::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_bit_length(arg1); + return new (thd->mem_root) Item_func_bit_length(thd, arg1); } @@ -3075,7 +3392,7 @@ Create_func_ceiling Create_func_ceiling::s_singleton; Item* Create_func_ceiling::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_ceiling(arg1); + return new (thd->mem_root) Item_func_ceiling(thd, arg1); } @@ -3085,9 +3402,27 @@ Create_func_centroid Create_func_centroid::s_singleton; Item* Create_func_centroid::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_centroid(arg1); + return new (thd->mem_root) Item_func_centroid(thd, arg1); } -#endif + + +Create_func_convexhull Create_func_convexhull::s_singleton; + +Item* +Create_func_convexhull::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_convexhull(thd, arg1); +} + + +Create_func_pointonsurface Create_func_pointonsurface::s_singleton; + +Item* +Create_func_pointonsurface::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_pointonsurface(thd, arg1); +} +#endif /*HAVE_SPATIAL*/ Create_func_char_length Create_func_char_length::s_singleton; @@ -3095,7 +3430,7 @@ Create_func_char_length Create_func_char_length::s_singleton; Item* Create_func_char_length::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_char_length(arg1); + return new (thd->mem_root) Item_func_char_length(thd, arg1); } @@ -3104,9 +3439,41 @@ Create_func_coercibility Create_func_coercibility::s_singleton; Item* Create_func_coercibility::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_coercibility(arg1); + return new (thd->mem_root) Item_func_coercibility(thd, arg1); +} + + +Create_func_dyncol_check Create_func_dyncol_check::s_singleton; + +Item* +Create_func_dyncol_check::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_dyncol_check(thd, arg1); } +Create_func_dyncol_exists Create_func_dyncol_exists::s_singleton; + +Item* +Create_func_dyncol_exists::create_2_arg(THD *thd, Item *arg1, Item *arg2) +{ + return new (thd->mem_root) Item_func_dyncol_exists(thd, arg1, arg2); +} + +Create_func_dyncol_list Create_func_dyncol_list::s_singleton; + +Item* +Create_func_dyncol_list::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_dyncol_list(thd, arg1); +} + +Create_func_dyncol_json Create_func_dyncol_json::s_singleton; + +Item* +Create_func_dyncol_json::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_dyncol_json(thd, arg1); +} Create_func_concat Create_func_concat::s_singleton; @@ -3125,9 +3492,16 @@ Create_func_concat::create_native(THD *thd, LEX_STRING name, return NULL; } - return new (thd->mem_root) Item_func_concat(*item_list); + return new (thd->mem_root) Item_func_concat(thd, *item_list); } +Create_func_decode_histogram Create_func_decode_histogram::s_singleton; + +Item * +Create_func_decode_histogram::create_2_arg(THD *thd, Item *arg1, Item *arg2) +{ + return new (thd->mem_root) Item_func_decode_histogram(thd, arg1, arg2); +} Create_func_concat_ws Create_func_concat_ws::s_singleton; @@ -3147,7 +3521,7 @@ Create_func_concat_ws::create_native(THD *thd, LEX_STRING name, return NULL; } - return new (thd->mem_root) Item_func_concat_ws(*item_list); + return new (thd->mem_root) Item_func_concat_ws(thd, *item_list); } @@ -3156,7 +3530,7 @@ Create_func_compress Create_func_compress::s_singleton; Item* Create_func_compress::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_compress(arg1); + return new (thd->mem_root) Item_func_compress(thd, arg1); } @@ -3166,7 +3540,7 @@ Item* Create_func_connection_id::create_builder(THD *thd) { thd->lex->safe_to_cache_query= 0; - return new (thd->mem_root) Item_func_connection_id(); + return new (thd->mem_root) Item_func_connection_id(thd); } @@ -3176,7 +3550,7 @@ Create_func_mbr_contains Create_func_mbr_contains::s_singleton; Item* Create_func_mbr_contains::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_mbr_rel(thd, arg1, arg2, Item_func::SP_CONTAINS_FUNC); } @@ -3186,8 +3560,8 @@ Create_func_contains Create_func_contains::s_singleton; Item* Create_func_contains::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2, - Item_func::SP_CONTAINS_FUNC); + return new (thd->mem_root) Item_func_spatial_precise_rel(thd, arg1, arg2, + Item_func::SP_CONTAINS_FUNC); } #endif @@ -3197,7 +3571,7 @@ Create_func_conv Create_func_conv::s_singleton; Item* Create_func_conv::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) { - return new (thd->mem_root) Item_func_conv(arg1, arg2, arg3); + return new (thd->mem_root) Item_func_conv(thd, arg1, arg2, arg3); } @@ -3206,7 +3580,7 @@ Create_func_convert_tz Create_func_convert_tz::s_singleton; Item* Create_func_convert_tz::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) { - return new (thd->mem_root) Item_func_convert_tz(arg1, arg2, arg3); + return new (thd->mem_root) Item_func_convert_tz(thd, arg1, arg2, arg3); } @@ -3215,7 +3589,7 @@ Create_func_cos Create_func_cos::s_singleton; Item* Create_func_cos::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_cos(arg1); + return new (thd->mem_root) Item_func_cos(thd, arg1); } @@ -3224,7 +3598,7 @@ Create_func_cot Create_func_cot::s_singleton; Item* Create_func_cot::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_cot(arg1); + return new (thd->mem_root) Item_func_cot(thd, arg1); } @@ -3233,7 +3607,7 @@ Create_func_crc32 Create_func_crc32::s_singleton; Item* Create_func_crc32::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_crc32(arg1); + return new (thd->mem_root) Item_func_crc32(thd, arg1); } @@ -3243,7 +3617,7 @@ Create_func_crosses Create_func_crosses::s_singleton; Item* Create_func_crosses::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_precise_rel(thd, arg1, arg2, Item_func::SP_CROSSES_FUNC); } #endif @@ -3254,7 +3628,7 @@ Create_func_date_format Create_func_date_format::s_singleton; Item* Create_func_date_format::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_date_format(arg1, arg2, 0); + return new (thd->mem_root) Item_func_date_format(thd, arg1, arg2, 0); } @@ -3263,10 +3637,10 @@ Create_func_datediff Create_func_datediff::s_singleton; Item* Create_func_datediff::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - Item *i1= new (thd->mem_root) Item_func_to_days(arg1); - Item *i2= new (thd->mem_root) Item_func_to_days(arg2); + Item *i1= new (thd->mem_root) Item_func_to_days(thd, arg1); + Item *i2= new (thd->mem_root) Item_func_to_days(thd, arg2); - return new (thd->mem_root) Item_func_minus(i1, i2); + return new (thd->mem_root) Item_func_minus(thd, i1, i2); } @@ -3275,7 +3649,7 @@ Create_func_dayname Create_func_dayname::s_singleton; Item* Create_func_dayname::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_dayname(arg1); + return new (thd->mem_root) Item_func_dayname(thd, arg1); } @@ -3284,7 +3658,7 @@ Create_func_dayofmonth Create_func_dayofmonth::s_singleton; Item* Create_func_dayofmonth::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_dayofmonth(arg1); + return new (thd->mem_root) Item_func_dayofmonth(thd, arg1); } @@ -3293,7 +3667,7 @@ Create_func_dayofweek Create_func_dayofweek::s_singleton; Item* Create_func_dayofweek::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_weekday(arg1, 1); + return new (thd->mem_root) Item_func_weekday(thd, arg1, 1); } @@ -3302,7 +3676,7 @@ Create_func_dayofyear Create_func_dayofyear::s_singleton; Item* Create_func_dayofyear::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_dayofyear(arg1); + return new (thd->mem_root) Item_func_dayofyear(thd, arg1); } @@ -3311,7 +3685,7 @@ Create_func_decode Create_func_decode::s_singleton; Item* Create_func_decode::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_decode(arg1, arg2); + return new (thd->mem_root) Item_func_decode(thd, arg1, arg2); } @@ -3320,7 +3694,7 @@ Create_func_degrees Create_func_degrees::s_singleton; Item* Create_func_degrees::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_units((char*) "degrees", arg1, + return new (thd->mem_root) Item_func_units(thd, (char*) "degrees", arg1, 180/M_PI, 0.0); } @@ -3341,14 +3715,14 @@ Create_func_des_decrypt::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_des_decrypt(param_1); + func= new (thd->mem_root) Item_func_des_decrypt(thd, param_1); break; } case 2: { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_des_decrypt(param_1, param_2); + func= new (thd->mem_root) Item_func_des_decrypt(thd, param_1, param_2); break; } default: @@ -3378,14 +3752,14 @@ Create_func_des_encrypt::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_des_encrypt(param_1); + func= new (thd->mem_root) Item_func_des_encrypt(thd, param_1); break; } case 2: { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_des_encrypt(param_1, param_2); + func= new (thd->mem_root) Item_func_des_encrypt(thd, param_1, param_2); break; } default: @@ -3405,7 +3779,7 @@ Create_func_dimension Create_func_dimension::s_singleton; Item* Create_func_dimension::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_dimension(arg1); + return new (thd->mem_root) Item_func_dimension(thd, arg1); } #endif @@ -3416,7 +3790,7 @@ Create_func_mbr_disjoint Create_func_mbr_disjoint::s_singleton; Item* Create_func_mbr_disjoint::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_mbr_rel(thd, arg1, arg2, Item_func::SP_DISJOINT_FUNC); } @@ -3426,8 +3800,8 @@ Create_func_disjoint Create_func_disjoint::s_singleton; Item* Create_func_disjoint::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2, - Item_func::SP_DISJOINT_FUNC); + return new (thd->mem_root) Item_func_spatial_precise_rel(thd, arg1, arg2, + Item_func::SP_DISJOINT_FUNC); } @@ -3436,7 +3810,7 @@ Create_func_distance Create_func_distance::s_singleton; Item* Create_func_distance::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_distance(arg1, arg2); + return new (thd->mem_root) Item_func_distance(thd, arg1, arg2); } #endif @@ -3458,7 +3832,7 @@ Create_func_elt::create_native(THD *thd, LEX_STRING name, return NULL; } - return new (thd->mem_root) Item_func_elt(*item_list); + return new (thd->mem_root) Item_func_elt(thd, *item_list); } @@ -3467,7 +3841,7 @@ Create_func_encode Create_func_encode::s_singleton; Item* Create_func_encode::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_encode(arg1, arg2); + return new (thd->mem_root) Item_func_encode(thd, arg1, arg2); } @@ -3487,7 +3861,7 @@ Create_func_encrypt::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_encrypt(param_1); + func= new (thd->mem_root) Item_func_encrypt(thd, param_1); thd->lex->uncacheable(UNCACHEABLE_RAND); break; } @@ -3495,7 +3869,7 @@ Create_func_encrypt::create_native(THD *thd, LEX_STRING name, { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_encrypt(param_1, param_2); + func= new (thd->mem_root) Item_func_encrypt(thd, param_1, param_2); break; } default: @@ -3515,7 +3889,7 @@ Create_func_endpoint Create_func_endpoint::s_singleton; Item* Create_func_endpoint::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_spatial_decomp(arg1, + return new (thd->mem_root) Item_func_spatial_decomp(thd, arg1, Item_func::SP_ENDPOINT); } #endif @@ -3527,7 +3901,16 @@ Create_func_envelope Create_func_envelope::s_singleton; Item* Create_func_envelope::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_envelope(arg1); + return new (thd->mem_root) Item_func_envelope(thd, arg1); +} + + +Create_func_boundary Create_func_boundary::s_singleton; + +Item* +Create_func_boundary::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_boundary(thd, arg1); } #endif @@ -3538,7 +3921,7 @@ Create_func_mbr_equals Create_func_mbr_equals::s_singleton; Item* Create_func_mbr_equals::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_mbr_rel(thd, arg1, arg2, Item_func::SP_EQUALS_FUNC); } @@ -3548,8 +3931,8 @@ Create_func_equals Create_func_equals::s_singleton; Item* Create_func_equals::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2, - Item_func::SP_EQUALS_FUNC); + return new (thd->mem_root) Item_func_spatial_precise_rel(thd, arg1, arg2, + Item_func::SP_EQUALS_FUNC); } #endif @@ -3559,7 +3942,7 @@ Create_func_exp Create_func_exp::s_singleton; Item* Create_func_exp::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_exp(arg1); + return new (thd->mem_root) Item_func_exp(thd, arg1); } @@ -3581,7 +3964,7 @@ Create_func_export_set::create_native(THD *thd, LEX_STRING name, Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); Item *param_3= item_list->pop(); - func= new (thd->mem_root) Item_func_export_set(param_1, param_2, param_3); + func= new (thd->mem_root) Item_func_export_set(thd, param_1, param_2, param_3); break; } case 4: @@ -3590,7 +3973,7 @@ Create_func_export_set::create_native(THD *thd, LEX_STRING name, Item *param_2= item_list->pop(); Item *param_3= item_list->pop(); Item *param_4= item_list->pop(); - func= new (thd->mem_root) Item_func_export_set(param_1, param_2, param_3, + func= new (thd->mem_root) Item_func_export_set(thd, param_1, param_2, param_3, param_4); break; } @@ -3601,7 +3984,7 @@ Create_func_export_set::create_native(THD *thd, LEX_STRING name, Item *param_3= item_list->pop(); Item *param_4= item_list->pop(); Item *param_5= item_list->pop(); - func= new (thd->mem_root) Item_func_export_set(param_1, param_2, param_3, + func= new (thd->mem_root) Item_func_export_set(thd, param_1, param_2, param_3, param_4, param_5); break; } @@ -3622,7 +4005,7 @@ Create_func_exteriorring Create_func_exteriorring::s_singleton; Item* Create_func_exteriorring::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_spatial_decomp(arg1, + return new (thd->mem_root) Item_func_spatial_decomp(thd, arg1, Item_func::SP_EXTERIORRING); } #endif @@ -3645,7 +4028,7 @@ Create_func_field::create_native(THD *thd, LEX_STRING name, return NULL; } - return new (thd->mem_root) Item_func_field(*item_list); + return new (thd->mem_root) Item_func_field(thd, *item_list); } @@ -3654,7 +4037,7 @@ Create_func_find_in_set Create_func_find_in_set::s_singleton; Item* Create_func_find_in_set::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_find_in_set(arg1, arg2); + return new (thd->mem_root) Item_func_find_in_set(thd, arg1, arg2); } @@ -3663,7 +4046,7 @@ Create_func_floor Create_func_floor::s_singleton; Item* Create_func_floor::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_floor(arg1); + return new (thd->mem_root) Item_func_floor(thd, arg1); } @@ -3681,7 +4064,7 @@ Create_func_format::create_native(THD *thd, LEX_STRING name, { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_format(param_1, param_2); + func= new (thd->mem_root) Item_func_format(thd, param_1, param_2); break; } case 3: @@ -3689,7 +4072,7 @@ Create_func_format::create_native(THD *thd, LEX_STRING name, Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); Item *param_3= item_list->pop(); - func= new (thd->mem_root) Item_func_format(param_1, param_2, param_3); + func= new (thd->mem_root) Item_func_format(thd, param_1, param_2, param_3); break; } default: @@ -3701,6 +4084,16 @@ Create_func_format::create_native(THD *thd, LEX_STRING name, } +Create_func_from_base64 Create_func_from_base64::s_singleton; + + +Item * +Create_func_from_base64::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_from_base64(thd, arg1); +} + + Create_func_found_rows Create_func_found_rows::s_singleton; Item* @@ -3709,7 +4102,7 @@ Create_func_found_rows::create_builder(THD *thd) DBUG_ENTER("Create_func_found_rows::create"); thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); thd->lex->safe_to_cache_query= 0; - DBUG_RETURN(new (thd->mem_root) Item_func_found_rows()); + DBUG_RETURN(new (thd->mem_root) Item_func_found_rows(thd)); } @@ -3718,7 +4111,7 @@ Create_func_from_days Create_func_from_days::s_singleton; Item* Create_func_from_days::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_from_days(arg1); + return new (thd->mem_root) Item_func_from_days(thd, arg1); } @@ -3738,15 +4131,15 @@ Create_func_from_unixtime::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_from_unixtime(param_1); + func= new (thd->mem_root) Item_func_from_unixtime(thd, param_1); break; } case 2: { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - Item *ut= new (thd->mem_root) Item_func_from_unixtime(param_1); - func= new (thd->mem_root) Item_func_date_format(ut, param_2, 0); + Item *ut= new (thd->mem_root) Item_func_from_unixtime(thd, param_1); + func= new (thd->mem_root) Item_func_date_format(thd, ut, param_2, 0); break; } default: @@ -3777,7 +4170,7 @@ Create_func_geometry_from_text::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_geometry_from_text(param_1); + func= new (thd->mem_root) Item_func_geometry_from_text(thd, param_1); thd->lex->uncacheable(UNCACHEABLE_RAND); break; } @@ -3785,7 +4178,7 @@ Create_func_geometry_from_text::create_native(THD *thd, LEX_STRING name, { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_geometry_from_text(param_1, param_2); + func= new (thd->mem_root) Item_func_geometry_from_text(thd, param_1, param_2); break; } default: @@ -3817,7 +4210,7 @@ Create_func_geometry_from_wkb::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_geometry_from_wkb(param_1); + func= new (thd->mem_root) Item_func_geometry_from_wkb(thd, param_1); thd->lex->uncacheable(UNCACHEABLE_RAND); break; } @@ -3825,7 +4218,7 @@ Create_func_geometry_from_wkb::create_native(THD *thd, LEX_STRING name, { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_geometry_from_wkb(param_1, param_2); + func= new (thd->mem_root) Item_func_geometry_from_wkb(thd, param_1, param_2); break; } default: @@ -3846,7 +4239,7 @@ Create_func_geometry_type Create_func_geometry_type::s_singleton; Item* Create_func_geometry_type::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_geometry_type(arg1); + return new (thd->mem_root) Item_func_geometry_type(thd, arg1); } #endif @@ -3857,7 +4250,7 @@ Create_func_geometryn Create_func_geometryn::s_singleton; Item* Create_func_geometryn::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_decomp_n(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_decomp_n(thd, arg1, arg2, Item_func::SP_GEOMETRYN); } #endif @@ -3870,8 +4263,19 @@ Create_func_get_lock::create_2_arg(THD *thd, Item *arg1, Item *arg2) { thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - return new (thd->mem_root) Item_func_get_lock(arg1, arg2); + return new (thd->mem_root) Item_func_get_lock(thd, arg1, arg2); +} + + +#if defined(HAVE_SPATIAL) && !defined(DBUG_OFF) +Create_func_gis_debug Create_func_gis_debug::s_singleton; + +Item* +Create_func_gis_debug::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_gis_debug(thd, arg1); } +#endif #ifdef HAVE_SPATIAL @@ -3880,7 +4284,7 @@ Create_func_glength Create_func_glength::s_singleton; Item* Create_func_glength::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_glength(arg1); + return new (thd->mem_root) Item_func_glength(thd, arg1); } #endif @@ -3902,7 +4306,7 @@ Create_func_greatest::create_native(THD *thd, LEX_STRING name, return NULL; } - return new (thd->mem_root) Item_func_max(*item_list); + return new (thd->mem_root) Item_func_max(thd, *item_list); } @@ -3911,7 +4315,7 @@ Create_func_hex Create_func_hex::s_singleton; Item* Create_func_hex::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_hex(arg1); + return new (thd->mem_root) Item_func_hex(thd, arg1); } @@ -3920,7 +4324,7 @@ Create_func_ifnull Create_func_ifnull::s_singleton; Item* Create_func_ifnull::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_ifnull(arg1, arg2); + return new (thd->mem_root) Item_func_ifnull(thd, arg1, arg2); } @@ -3929,7 +4333,25 @@ Create_func_inet_ntoa Create_func_inet_ntoa::s_singleton; Item* Create_func_inet_ntoa::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_inet_ntoa(arg1); + return new (thd->mem_root) Item_func_inet_ntoa(thd, arg1); +} + + +Create_func_inet6_aton Create_func_inet6_aton::s_singleton; + +Item* +Create_func_inet6_aton::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_inet6_aton(thd, arg1); +} + + +Create_func_inet6_ntoa Create_func_inet6_ntoa::s_singleton; + +Item* +Create_func_inet6_ntoa::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_inet6_ntoa(thd, arg1); } @@ -3938,7 +4360,43 @@ Create_func_inet_aton Create_func_inet_aton::s_singleton; Item* Create_func_inet_aton::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_inet_aton(arg1); + return new (thd->mem_root) Item_func_inet_aton(thd, arg1); +} + + +Create_func_is_ipv4 Create_func_is_ipv4::s_singleton; + +Item* +Create_func_is_ipv4::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_is_ipv4(thd, arg1); +} + + +Create_func_is_ipv6 Create_func_is_ipv6::s_singleton; + +Item* +Create_func_is_ipv6::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_is_ipv6(thd, arg1); +} + + +Create_func_is_ipv4_compat Create_func_is_ipv4_compat::s_singleton; + +Item* +Create_func_is_ipv4_compat::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_is_ipv4_compat(thd, arg1); +} + + +Create_func_is_ipv4_mapped Create_func_is_ipv4_mapped::s_singleton; + +Item* +Create_func_is_ipv4_mapped::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_is_ipv4_mapped(thd, arg1); } @@ -3947,7 +4405,7 @@ Create_func_instr Create_func_instr::s_singleton; Item* Create_func_instr::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_locate(arg1, arg2); + return new (thd->mem_root) Item_func_locate(thd, arg1, arg2); } @@ -3957,19 +4415,28 @@ Create_func_interiorringn Create_func_interiorringn::s_singleton; Item* Create_func_interiorringn::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_decomp_n(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_decomp_n(thd, arg1, arg2, Item_func::SP_INTERIORRINGN); } #endif #ifdef HAVE_SPATIAL +Create_func_relate Create_func_relate::s_singleton; + +Item* +Create_func_relate::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *matrix) +{ + return new (thd->mem_root) Item_func_spatial_relate(thd, arg1, arg2, matrix); +} + + Create_func_mbr_intersects Create_func_mbr_intersects::s_singleton; Item* Create_func_mbr_intersects::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_mbr_rel(thd, arg1, arg2, Item_func::SP_INTERSECTS_FUNC); } @@ -3979,8 +4446,8 @@ Create_func_intersects Create_func_intersects::s_singleton; Item* Create_func_intersects::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2, - Item_func::SP_INTERSECTS_FUNC); + return new (thd->mem_root) Item_func_spatial_precise_rel(thd, arg1, arg2, + Item_func::SP_INTERSECTS_FUNC); } @@ -3989,7 +4456,7 @@ Create_func_intersection Create_func_intersection::s_singleton; Item* Create_func_intersection::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_operation(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_operation(thd, arg1, arg2, Gcalc_function::op_intersection); } @@ -3999,7 +4466,7 @@ Create_func_difference Create_func_difference::s_singleton; Item* Create_func_difference::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_operation(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_operation(thd, arg1, arg2, Gcalc_function::op_difference); } @@ -4009,7 +4476,7 @@ Create_func_union Create_func_union::s_singleton; Item* Create_func_union::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_operation(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_operation(thd, arg1, arg2, Gcalc_function::op_union); } @@ -4019,7 +4486,7 @@ Create_func_symdifference Create_func_symdifference::s_singleton; Item* Create_func_symdifference::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_operation(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_operation(thd, arg1, arg2, Gcalc_function::op_symdifference); } @@ -4029,7 +4496,7 @@ Create_func_buffer Create_func_buffer::s_singleton; Item* Create_func_buffer::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_buffer(arg1, arg2); + return new (thd->mem_root) Item_func_buffer(thd, arg1, arg2); } #endif /*HAVE_SPATAI*/ @@ -4041,7 +4508,7 @@ Create_func_is_free_lock::create_1_arg(THD *thd, Item *arg1) { thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - return new (thd->mem_root) Item_func_is_free_lock(arg1); + return new (thd->mem_root) Item_func_is_free_lock(thd, arg1); } @@ -4052,7 +4519,7 @@ Create_func_is_used_lock::create_1_arg(THD *thd, Item *arg1) { thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - return new (thd->mem_root) Item_func_is_used_lock(arg1); + return new (thd->mem_root) Item_func_is_used_lock(thd, arg1); } @@ -4062,20 +4529,27 @@ Create_func_isclosed Create_func_isclosed::s_singleton; Item* Create_func_isclosed::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_isclosed(arg1); + return new (thd->mem_root) Item_func_isclosed(thd, arg1); +} + + +Create_func_isring Create_func_isring::s_singleton; + +Item* +Create_func_isring::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_isring(thd, arg1); } -#endif -#ifdef HAVE_SPATIAL Create_func_isempty Create_func_isempty::s_singleton; Item* Create_func_isempty::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_isempty(arg1); + return new (thd->mem_root) Item_func_isempty(thd, arg1); } -#endif +#endif /*HAVE_SPATIAL*/ Create_func_isnull Create_func_isnull::s_singleton; @@ -4083,7 +4557,7 @@ Create_func_isnull Create_func_isnull::s_singleton; Item* Create_func_isnull::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_isnull(arg1); + return new (thd->mem_root) Item_func_isnull(thd, arg1); } @@ -4093,7 +4567,7 @@ Create_func_issimple Create_func_issimple::s_singleton; Item* Create_func_issimple::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_issimple(arg1); + return new (thd->mem_root) Item_func_issimple(thd, arg1); } #endif @@ -4103,7 +4577,7 @@ Create_func_last_day Create_func_last_day::s_singleton; Item* Create_func_last_day::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_last_day(arg1); + return new (thd->mem_root) Item_func_last_day(thd, arg1); } @@ -4122,14 +4596,14 @@ Create_func_last_insert_id::create_native(THD *thd, LEX_STRING name, switch (arg_count) { case 0: { - func= new (thd->mem_root) Item_func_last_insert_id(); + func= new (thd->mem_root) Item_func_last_insert_id(thd); thd->lex->safe_to_cache_query= 0; break; } case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_last_insert_id(param_1); + func= new (thd->mem_root) Item_func_last_insert_id(thd, param_1); thd->lex->safe_to_cache_query= 0; break; } @@ -4149,7 +4623,7 @@ Create_func_lcase Create_func_lcase::s_singleton; Item* Create_func_lcase::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_lcase(arg1); + return new (thd->mem_root) Item_func_lcase(thd, arg1); } @@ -4170,7 +4644,7 @@ Create_func_least::create_native(THD *thd, LEX_STRING name, return NULL; } - return new (thd->mem_root) Item_func_min(*item_list); + return new (thd->mem_root) Item_func_min(thd, *item_list); } @@ -4179,7 +4653,7 @@ Create_func_length Create_func_length::s_singleton; Item* Create_func_length::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_length(arg1); + return new (thd->mem_root) Item_func_length(thd, arg1); } @@ -4189,7 +4663,7 @@ Create_func_like_range_min Create_func_like_range_min::s_singleton; Item* Create_func_like_range_min::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_like_range_min(arg1, arg2); + return new (thd->mem_root) Item_func_like_range_min(thd, arg1, arg2); } @@ -4198,7 +4672,7 @@ Create_func_like_range_max Create_func_like_range_max::s_singleton; Item* Create_func_like_range_max::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_like_range_max(arg1, arg2); + return new (thd->mem_root) Item_func_like_range_max(thd, arg1, arg2); } #endif @@ -4208,7 +4682,7 @@ Create_func_ln Create_func_ln::s_singleton; Item* Create_func_ln::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_ln(arg1); + return new (thd->mem_root) Item_func_ln(thd, arg1); } @@ -4220,7 +4694,7 @@ Create_func_load_file::create_1_arg(THD *thd, Item *arg1) DBUG_ENTER("Create_func_load_file::create"); thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - DBUG_RETURN(new (thd->mem_root) Item_load_file(arg1)); + DBUG_RETURN(new (thd->mem_root) Item_load_file(thd, arg1)); } @@ -4242,7 +4716,7 @@ Create_func_locate::create_native(THD *thd, LEX_STRING name, Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); /* Yes, parameters in that order : 2, 1 */ - func= new (thd->mem_root) Item_func_locate(param_2, param_1); + func= new (thd->mem_root) Item_func_locate(thd, param_2, param_1); break; } case 3: @@ -4251,7 +4725,7 @@ Create_func_locate::create_native(THD *thd, LEX_STRING name, Item *param_2= item_list->pop(); Item *param_3= item_list->pop(); /* Yes, parameters in that order : 2, 1, 3 */ - func= new (thd->mem_root) Item_func_locate(param_2, param_1, param_3); + func= new (thd->mem_root) Item_func_locate(thd, param_2, param_1, param_3); break; } default: @@ -4281,14 +4755,14 @@ Create_func_log::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_log(param_1); + func= new (thd->mem_root) Item_func_log(thd, param_1); break; } case 2: { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_log(param_1, param_2); + func= new (thd->mem_root) Item_func_log(thd, param_1, param_2); break; } default: @@ -4307,7 +4781,7 @@ Create_func_log10 Create_func_log10::s_singleton; Item* Create_func_log10::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_log10(arg1); + return new (thd->mem_root) Item_func_log10(thd, arg1); } @@ -4316,7 +4790,7 @@ Create_func_log2 Create_func_log2::s_singleton; Item* Create_func_log2::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_log2(arg1); + return new (thd->mem_root) Item_func_log2(thd, arg1); } @@ -4325,7 +4799,7 @@ Create_func_lpad Create_func_lpad::s_singleton; Item* Create_func_lpad::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) { - return new (thd->mem_root) Item_func_lpad(arg1, arg2, arg3); + return new (thd->mem_root) Item_func_lpad(thd, arg1, arg2, arg3); } @@ -4334,7 +4808,7 @@ Create_func_ltrim Create_func_ltrim::s_singleton; Item* Create_func_ltrim::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_ltrim(arg1); + return new (thd->mem_root) Item_func_ltrim(thd, arg1); } @@ -4343,7 +4817,7 @@ Create_func_makedate Create_func_makedate::s_singleton; Item* Create_func_makedate::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_makedate(arg1, arg2); + return new (thd->mem_root) Item_func_makedate(thd, arg1, arg2); } @@ -4352,7 +4826,7 @@ Create_func_maketime Create_func_maketime::s_singleton; Item* Create_func_maketime::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) { - return new (thd->mem_root) Item_func_maketime(arg1, arg2, arg3); + return new (thd->mem_root) Item_func_maketime(thd, arg1, arg2, arg3); } @@ -4373,7 +4847,7 @@ Create_func_make_set::create_native(THD *thd, LEX_STRING name, return NULL; } - return new (thd->mem_root) Item_func_make_set(*item_list); + return new (thd->mem_root) Item_func_make_set(thd, *item_list); } @@ -4392,27 +4866,75 @@ Create_func_master_pos_wait::create_native(THD *thd, LEX_STRING name, if (item_list != NULL) arg_count= item_list->elements; + if (arg_count < 2 || arg_count > 4) + { + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str); + return func; + } + + thd->lex->safe_to_cache_query= 0; + + Item *param_1= item_list->pop(); + Item *param_2= item_list->pop(); switch (arg_count) { case 2: { - Item *param_1= item_list->pop(); - Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_master_pos_wait(param_1, param_2); - thd->lex->safe_to_cache_query= 0; + func= new (thd->mem_root) Item_master_pos_wait(thd, param_1, param_2); break; } case 3: { - Item *param_1= item_list->pop(); - Item *param_2= item_list->pop(); Item *param_3= item_list->pop(); - func= new (thd->mem_root) Item_master_pos_wait(param_1, param_2, param_3); - thd->lex->safe_to_cache_query= 0; + func= new (thd->mem_root) Item_master_pos_wait(thd, param_1, param_2, param_3); break; } - default: + case 4: + { + Item *param_3= item_list->pop(); + Item *param_4= item_list->pop(); + func= new (thd->mem_root) Item_master_pos_wait(thd, param_1, param_2, param_3, + param_4); + break; + } + } + + return func; +} + + +Create_func_master_gtid_wait Create_func_master_gtid_wait::s_singleton; + +Item* +Create_func_master_gtid_wait::create_native(THD *thd, LEX_STRING name, + List<Item> *item_list) +{ + Item *func= NULL; + int arg_count= 0; + + thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); + + if (item_list != NULL) + arg_count= item_list->elements; + + if (arg_count < 1 || arg_count > 2) { my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name.str); + return func; + } + + thd->lex->safe_to_cache_query= 0; + + Item *param_1= item_list->pop(); + switch (arg_count) { + case 1: + { + func= new (thd->mem_root) Item_master_gtid_wait(thd, param_1); + break; + } + case 2: + { + Item *param_2= item_list->pop(); + func= new (thd->mem_root) Item_master_gtid_wait(thd, param_1, param_2); break; } } @@ -4426,7 +4948,7 @@ Create_func_md5 Create_func_md5::s_singleton; Item* Create_func_md5::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_md5(arg1); + return new (thd->mem_root) Item_func_md5(thd, arg1); } @@ -4435,7 +4957,7 @@ Create_func_monthname Create_func_monthname::s_singleton; Item* Create_func_monthname::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_monthname(arg1); + return new (thd->mem_root) Item_func_monthname(thd, arg1); } @@ -4444,7 +4966,7 @@ Create_func_name_const Create_func_name_const::s_singleton; Item* Create_func_name_const::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_name_const(arg1, arg2); + return new (thd->mem_root) Item_name_const(thd, arg1, arg2); } @@ -4453,7 +4975,7 @@ Create_func_nullif Create_func_nullif::s_singleton; Item* Create_func_nullif::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_nullif(arg1, arg2); + return new (thd->mem_root) Item_func_nullif(thd, arg1, arg2); } @@ -4463,7 +4985,7 @@ Create_func_numgeometries Create_func_numgeometries::s_singleton; Item* Create_func_numgeometries::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_numgeometries(arg1); + return new (thd->mem_root) Item_func_numgeometries(thd, arg1); } #endif @@ -4474,7 +4996,7 @@ Create_func_numinteriorring Create_func_numinteriorring::s_singleton; Item* Create_func_numinteriorring::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_numinteriorring(arg1); + return new (thd->mem_root) Item_func_numinteriorring(thd, arg1); } #endif @@ -4485,7 +5007,7 @@ Create_func_numpoints Create_func_numpoints::s_singleton; Item* Create_func_numpoints::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_numpoints(arg1); + return new (thd->mem_root) Item_func_numpoints(thd, arg1); } #endif @@ -4495,9 +5017,9 @@ Create_func_oct Create_func_oct::s_singleton; Item* Create_func_oct::create_1_arg(THD *thd, Item *arg1) { - Item *i10= new (thd->mem_root) Item_int((int32) 10,2); - Item *i8= new (thd->mem_root) Item_int((int32) 8,1); - return new (thd->mem_root) Item_func_conv(arg1, i10, i8); + Item *i10= new (thd->mem_root) Item_int(thd, (int32) 10,2); + Item *i8= new (thd->mem_root) Item_int(thd, (int32) 8,1); + return new (thd->mem_root) Item_func_conv(thd, arg1, i10, i8); } @@ -4506,7 +5028,7 @@ Create_func_ord Create_func_ord::s_singleton; Item* Create_func_ord::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_ord(arg1); + return new (thd->mem_root) Item_func_ord(thd, arg1); } @@ -4516,7 +5038,7 @@ Create_func_mbr_overlaps Create_func_mbr_overlaps::s_singleton; Item* Create_func_mbr_overlaps::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_mbr_rel(thd, arg1, arg2, Item_func::SP_OVERLAPS_FUNC); } @@ -4526,8 +5048,8 @@ Create_func_overlaps Create_func_overlaps::s_singleton; Item* Create_func_overlaps::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2, - Item_func::SP_OVERLAPS_FUNC); + return new (thd->mem_root) Item_func_spatial_precise_rel(thd, arg1, arg2, + Item_func::SP_OVERLAPS_FUNC); } #endif @@ -4537,7 +5059,7 @@ Create_func_period_add Create_func_period_add::s_singleton; Item* Create_func_period_add::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_period_add(arg1, arg2); + return new (thd->mem_root) Item_func_period_add(thd, arg1, arg2); } @@ -4546,7 +5068,7 @@ Create_func_period_diff Create_func_period_diff::s_singleton; Item* Create_func_period_diff::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_period_diff(arg1, arg2); + return new (thd->mem_root) Item_func_period_diff(thd, arg1, arg2); } @@ -4555,7 +5077,7 @@ Create_func_pi Create_func_pi::s_singleton; Item* Create_func_pi::create_builder(THD *thd) { - return new (thd->mem_root) Item_static_float_func("pi()", M_PI, 6, 8); + return new (thd->mem_root) Item_static_float_func(thd, "pi()", M_PI, 6, 8); } @@ -4565,7 +5087,7 @@ Create_func_pointn Create_func_pointn::s_singleton; Item* Create_func_pointn::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_decomp_n(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_decomp_n(thd, arg1, arg2, Item_func::SP_POINTN); } #endif @@ -4576,7 +5098,7 @@ Create_func_pow Create_func_pow::s_singleton; Item* Create_func_pow::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_pow(arg1, arg2); + return new (thd->mem_root) Item_func_pow(thd, arg1, arg2); } @@ -4585,7 +5107,34 @@ Create_func_quote Create_func_quote::s_singleton; Item* Create_func_quote::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_quote(arg1); + return new (thd->mem_root) Item_func_quote(thd, arg1); +} + + +Create_func_regexp_instr Create_func_regexp_instr::s_singleton; + +Item* +Create_func_regexp_instr::create_2_arg(THD *thd, Item *arg1, Item *arg2) +{ + return new (thd->mem_root) Item_func_regexp_instr(thd, arg1, arg2); +} + + +Create_func_regexp_replace Create_func_regexp_replace::s_singleton; + +Item* +Create_func_regexp_replace::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) +{ + return new (thd->mem_root) Item_func_regexp_replace(thd, arg1, arg2, arg3); +} + + +Create_func_regexp_substr Create_func_regexp_substr::s_singleton; + +Item* +Create_func_regexp_substr::create_2_arg(THD *thd, Item *arg1, Item *arg2) +{ + return new (thd->mem_root) Item_func_regexp_substr(thd, arg1, arg2); } @@ -4594,7 +5143,7 @@ Create_func_radians Create_func_radians::s_singleton; Item* Create_func_radians::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_units((char*) "radians", arg1, + return new (thd->mem_root) Item_func_units(thd, (char*) "radians", arg1, M_PI/180, 0.0); } @@ -4627,14 +5176,14 @@ Create_func_rand::create_native(THD *thd, LEX_STRING name, switch (arg_count) { case 0: { - func= new (thd->mem_root) Item_func_rand(); + func= new (thd->mem_root) Item_func_rand(thd); thd->lex->uncacheable(UNCACHEABLE_RAND); break; } case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_rand(param_1); + func= new (thd->mem_root) Item_func_rand(thd, param_1); thd->lex->uncacheable(UNCACHEABLE_RAND); break; } @@ -4656,7 +5205,7 @@ Create_func_release_lock::create_1_arg(THD *thd, Item *arg1) { thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - return new (thd->mem_root) Item_func_release_lock(arg1); + return new (thd->mem_root) Item_func_release_lock(thd, arg1); } @@ -4665,7 +5214,7 @@ Create_func_reverse Create_func_reverse::s_singleton; Item* Create_func_reverse::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_reverse(arg1); + return new (thd->mem_root) Item_func_reverse(thd, arg1); } @@ -4685,15 +5234,15 @@ Create_func_round::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - Item *i0 = new (thd->mem_root) Item_int((char*)"0", 0, 1); - func= new (thd->mem_root) Item_func_round(param_1, i0, 0); + Item *i0= new (thd->mem_root) Item_int(thd, (char*)"0", 0, 1); + func= new (thd->mem_root) Item_func_round(thd, param_1, i0, 0); break; } case 2: { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_round(param_1, param_2, 0); + func= new (thd->mem_root) Item_func_round(thd, param_1, param_2, 0); break; } default: @@ -4707,24 +5256,12 @@ Create_func_round::create_native(THD *thd, LEX_STRING name, } -Create_func_row_count Create_func_row_count::s_singleton; - -Item* -Create_func_row_count::create_builder(THD *thd) -{ - DBUG_ENTER("Create_func_row_count::create"); - thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); - thd->lex->safe_to_cache_query= 0; - DBUG_RETURN(new (thd->mem_root) Item_func_row_count()); -} - - Create_func_rpad Create_func_rpad::s_singleton; Item* Create_func_rpad::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) { - return new (thd->mem_root) Item_func_rpad(arg1, arg2, arg3); + return new (thd->mem_root) Item_func_rpad(thd, arg1, arg2, arg3); } @@ -4733,7 +5270,7 @@ Create_func_rtrim Create_func_rtrim::s_singleton; Item* Create_func_rtrim::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_rtrim(arg1); + return new (thd->mem_root) Item_func_rtrim(thd, arg1); } @@ -4742,7 +5279,7 @@ Create_func_sec_to_time Create_func_sec_to_time::s_singleton; Item* Create_func_sec_to_time::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_sec_to_time(arg1); + return new (thd->mem_root) Item_func_sec_to_time(thd, arg1); } @@ -4751,7 +5288,7 @@ Create_func_sha Create_func_sha::s_singleton; Item* Create_func_sha::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_sha(arg1); + return new (thd->mem_root) Item_func_sha(thd, arg1); } @@ -4760,7 +5297,7 @@ Create_func_sha2 Create_func_sha2::s_singleton; Item* Create_func_sha2::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_sha2(arg1, arg2); + return new (thd->mem_root) Item_func_sha2(thd, arg1, arg2); } @@ -4769,7 +5306,7 @@ Create_func_sign Create_func_sign::s_singleton; Item* Create_func_sign::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_sign(arg1); + return new (thd->mem_root) Item_func_sign(thd, arg1); } @@ -4778,7 +5315,7 @@ Create_func_sin Create_func_sin::s_singleton; Item* Create_func_sin::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_sin(arg1); + return new (thd->mem_root) Item_func_sin(thd, arg1); } @@ -4789,7 +5326,7 @@ Create_func_sleep::create_1_arg(THD *thd, Item *arg1) { thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); thd->lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - return new (thd->mem_root) Item_func_sleep(arg1); + return new (thd->mem_root) Item_func_sleep(thd, arg1); } @@ -4798,7 +5335,7 @@ Create_func_soundex Create_func_soundex::s_singleton; Item* Create_func_soundex::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_soundex(arg1); + return new (thd->mem_root) Item_func_soundex(thd, arg1); } @@ -4807,26 +5344,7 @@ Create_func_space Create_func_space::s_singleton; Item* Create_func_space::create_1_arg(THD *thd, Item *arg1) { - /** - TODO: Fix Bug#23637 - The parsed item tree should not depend on - <code>thd->variables.collation_connection</code>. - */ - CHARSET_INFO *cs= thd->variables.collation_connection; - Item *sp; - - if (cs->mbminlen > 1) - { - uint dummy_errors; - sp= new (thd->mem_root) Item_string("", 0, cs, DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII); - sp->str_value.copy(" ", 1, &my_charset_latin1, cs, &dummy_errors); - } - else - { - sp= new (thd->mem_root) Item_string(" ", 1, cs, DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII); - } - - return new (thd->mem_root) Item_func_repeat(sp, arg1); + return new (thd->mem_root) Item_func_space(thd, arg1); } @@ -4835,7 +5353,7 @@ Create_func_sqrt Create_func_sqrt::s_singleton; Item* Create_func_sqrt::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_sqrt(arg1); + return new (thd->mem_root) Item_func_sqrt(thd, arg1); } @@ -4845,7 +5363,7 @@ Create_func_srid Create_func_srid::s_singleton; Item* Create_func_srid::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_srid(arg1); + return new (thd->mem_root) Item_func_srid(thd, arg1); } #endif @@ -4856,7 +5374,7 @@ Create_func_startpoint Create_func_startpoint::s_singleton; Item* Create_func_startpoint::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_spatial_decomp(arg1, + return new (thd->mem_root) Item_func_spatial_decomp(thd, arg1, Item_func::SP_STARTPOINT); } #endif @@ -4867,7 +5385,7 @@ Create_func_str_to_date Create_func_str_to_date::s_singleton; Item* Create_func_str_to_date::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_str_to_date(arg1, arg2); + return new (thd->mem_root) Item_func_str_to_date(thd, arg1, arg2); } @@ -4876,7 +5394,7 @@ Create_func_strcmp Create_func_strcmp::s_singleton; Item* Create_func_strcmp::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_strcmp(arg1, arg2); + return new (thd->mem_root) Item_func_strcmp(thd, arg1, arg2); } @@ -4885,7 +5403,7 @@ Create_func_substr_index Create_func_substr_index::s_singleton; Item* Create_func_substr_index::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) { - return new (thd->mem_root) Item_func_substr_index(arg1, arg2, arg3); + return new (thd->mem_root) Item_func_substr_index(thd, arg1, arg2, arg3); } @@ -4894,7 +5412,7 @@ Create_func_subtime Create_func_subtime::s_singleton; Item* Create_func_subtime::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_add_time(arg1, arg2, 0, 1); + return new (thd->mem_root) Item_func_add_time(thd, arg1, arg2, 0, 1); } @@ -4903,7 +5421,7 @@ Create_func_tan Create_func_tan::s_singleton; Item* Create_func_tan::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_tan(arg1); + return new (thd->mem_root) Item_func_tan(thd, arg1); } @@ -4912,7 +5430,7 @@ Create_func_time_format Create_func_time_format::s_singleton; Item* Create_func_time_format::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_date_format(arg1, arg2, 1); + return new (thd->mem_root) Item_func_date_format(thd, arg1, arg2, 1); } @@ -4921,7 +5439,7 @@ Create_func_time_to_sec Create_func_time_to_sec::s_singleton; Item* Create_func_time_to_sec::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_time_to_sec(arg1); + return new (thd->mem_root) Item_func_time_to_sec(thd, arg1); } @@ -4930,7 +5448,16 @@ Create_func_timediff Create_func_timediff::s_singleton; Item* Create_func_timediff::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_timediff(arg1, arg2); + return new (thd->mem_root) Item_func_timediff(thd, arg1, arg2); +} + + +Create_func_to_base64 Create_func_to_base64::s_singleton; + +Item* +Create_func_to_base64::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_to_base64(thd, arg1); } @@ -4939,7 +5466,7 @@ Create_func_to_days Create_func_to_days::s_singleton; Item* Create_func_to_days::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_to_days(arg1); + return new (thd->mem_root) Item_func_to_days(thd, arg1); } @@ -4948,7 +5475,7 @@ Create_func_to_seconds Create_func_to_seconds::s_singleton; Item* Create_func_to_seconds::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_to_seconds(arg1); + return new (thd->mem_root) Item_func_to_seconds(thd, arg1); } @@ -4958,7 +5485,7 @@ Create_func_touches Create_func_touches::s_singleton; Item* Create_func_touches::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_precise_rel(thd, arg1, arg2, Item_func::SP_TOUCHES_FUNC); } #endif @@ -4969,7 +5496,7 @@ Create_func_ucase Create_func_ucase::s_singleton; Item* Create_func_ucase::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_ucase(arg1); + return new (thd->mem_root) Item_func_ucase(thd, arg1); } @@ -4978,7 +5505,7 @@ Create_func_uncompress Create_func_uncompress::s_singleton; Item* Create_func_uncompress::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_uncompress(arg1); + return new (thd->mem_root) Item_func_uncompress(thd, arg1); } @@ -4987,7 +5514,7 @@ Create_func_uncompressed_length Create_func_uncompressed_length::s_singleton; Item* Create_func_uncompressed_length::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_uncompressed_length(arg1); + return new (thd->mem_root) Item_func_uncompressed_length(thd, arg1); } @@ -4996,7 +5523,7 @@ Create_func_unhex Create_func_unhex::s_singleton; Item* Create_func_unhex::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_unhex(arg1); + return new (thd->mem_root) Item_func_unhex(thd, arg1); } @@ -5015,14 +5542,14 @@ Create_func_unix_timestamp::create_native(THD *thd, LEX_STRING name, switch (arg_count) { case 0: { - func= new (thd->mem_root) Item_func_unix_timestamp(); + func= new (thd->mem_root) Item_func_unix_timestamp(thd); thd->lex->safe_to_cache_query= 0; break; } case 1: { Item *param_1= item_list->pop(); - func= new (thd->mem_root) Item_func_unix_timestamp(param_1); + func= new (thd->mem_root) Item_func_unix_timestamp(thd, param_1); break; } default: @@ -5044,7 +5571,7 @@ Create_func_uuid::create_builder(THD *thd) DBUG_ENTER("Create_func_uuid::create"); thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); thd->lex->safe_to_cache_query= 0; - DBUG_RETURN(new (thd->mem_root) Item_func_uuid()); + DBUG_RETURN(new (thd->mem_root) Item_func_uuid(thd)); } @@ -5056,7 +5583,7 @@ Create_func_uuid_short::create_builder(THD *thd) DBUG_ENTER("Create_func_uuid_short::create"); thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); thd->lex->safe_to_cache_query= 0; - DBUG_RETURN(new (thd->mem_root) Item_func_uuid_short()); + DBUG_RETURN(new (thd->mem_root) Item_func_uuid_short(thd)); } @@ -5066,7 +5593,7 @@ Item* Create_func_version::create_builder(THD *thd) { thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); - return new (thd->mem_root) Item_static_string_func("version()", + return new (thd->mem_root) Item_static_string_func(thd, "version()", server_version, (uint) strlen(server_version), system_charset_info, @@ -5079,7 +5606,7 @@ Create_func_weekday Create_func_weekday::s_singleton; Item* Create_func_weekday::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_weekday(arg1, 0); + return new (thd->mem_root) Item_func_weekday(thd, arg1, 0); } @@ -5088,8 +5615,8 @@ Create_func_weekofyear Create_func_weekofyear::s_singleton; Item* Create_func_weekofyear::create_1_arg(THD *thd, Item *arg1) { - Item *i1= new (thd->mem_root) Item_int((char*) "0", 3, 1); - return new (thd->mem_root) Item_func_week(arg1, i1); + Item *i1= new (thd->mem_root) Item_int(thd, (char*) "0", 3, 1); + return new (thd->mem_root) Item_func_week(thd, arg1, i1); } @@ -5099,7 +5626,7 @@ Create_func_mbr_within Create_func_mbr_within::s_singleton; Item* Create_func_mbr_within::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_mbr_rel(arg1, arg2, + return new (thd->mem_root) Item_func_spatial_mbr_rel(thd, arg1, arg2, Item_func::SP_WITHIN_FUNC); } @@ -5109,8 +5636,8 @@ Create_func_within Create_func_within::s_singleton; Item* Create_func_within::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_spatial_rel(arg1, arg2, - Item_func::SP_WITHIN_FUNC); + return new (thd->mem_root) Item_func_spatial_precise_rel(thd, arg1, arg2, + Item_func::SP_WITHIN_FUNC); } #endif @@ -5121,7 +5648,7 @@ Create_func_x Create_func_x::s_singleton; Item* Create_func_x::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_x(arg1); + return new (thd->mem_root) Item_func_x(thd, arg1); } #endif @@ -5131,7 +5658,7 @@ Create_func_xml_extractvalue Create_func_xml_extractvalue::s_singleton; Item* Create_func_xml_extractvalue::create_2_arg(THD *thd, Item *arg1, Item *arg2) { - return new (thd->mem_root) Item_func_xml_extractvalue(arg1, arg2); + return new (thd->mem_root) Item_func_xml_extractvalue(thd, arg1, arg2); } @@ -5140,7 +5667,7 @@ Create_func_xml_update Create_func_xml_update::s_singleton; Item* Create_func_xml_update::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) { - return new (thd->mem_root) Item_func_xml_update(arg1, arg2, arg3); + return new (thd->mem_root) Item_func_xml_update(thd, arg1, arg2, arg3); } @@ -5150,7 +5677,7 @@ Create_func_y Create_func_y::s_singleton; Item* Create_func_y::create_1_arg(THD *thd, Item *arg1) { - return new (thd->mem_root) Item_func_y(arg1); + return new (thd->mem_root) Item_func_y(thd, arg1); } #endif @@ -5171,15 +5698,15 @@ Create_func_year_week::create_native(THD *thd, LEX_STRING name, case 1: { Item *param_1= item_list->pop(); - Item *i0= new (thd->mem_root) Item_int((char*) "0", 0, 1); - func= new (thd->mem_root) Item_func_yearweek(param_1, i0); + Item *i0= new (thd->mem_root) Item_int(thd, (char*) "0", 0, 1); + func= new (thd->mem_root) Item_func_yearweek(thd, param_1, i0); break; } case 2: { Item *param_1= item_list->pop(); Item *param_2= item_list->pop(); - func= new (thd->mem_root) Item_func_yearweek(param_1, param_2); + func= new (thd->mem_root) Item_func_yearweek(thd, param_1, param_2); break; } default: @@ -5235,8 +5762,10 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("ATAN2") }, BUILDER(Create_func_atan)}, { { C_STRING_WITH_LEN("BENCHMARK") }, BUILDER(Create_func_benchmark)}, { { C_STRING_WITH_LEN("BIN") }, BUILDER(Create_func_bin)}, + { { C_STRING_WITH_LEN("BINLOG_GTID_POS") }, BUILDER(Create_func_binlog_gtid_pos)}, { { C_STRING_WITH_LEN("BIT_COUNT") }, BUILDER(Create_func_bit_count)}, { { C_STRING_WITH_LEN("BIT_LENGTH") }, BUILDER(Create_func_bit_length)}, + { { C_STRING_WITH_LEN("BOUNDARY") }, GEOM_BUILDER(Create_func_boundary)}, { { C_STRING_WITH_LEN("BUFFER") }, GEOM_BUILDER(Create_func_buffer)}, { { C_STRING_WITH_LEN("CEIL") }, BUILDER(Create_func_ceiling)}, { { C_STRING_WITH_LEN("CEILING") }, BUILDER(Create_func_ceiling)}, @@ -5244,12 +5773,17 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("CHARACTER_LENGTH") }, BUILDER(Create_func_char_length)}, { { C_STRING_WITH_LEN("CHAR_LENGTH") }, BUILDER(Create_func_char_length)}, { { C_STRING_WITH_LEN("COERCIBILITY") }, BUILDER(Create_func_coercibility)}, + { { C_STRING_WITH_LEN("COLUMN_CHECK") }, BUILDER(Create_func_dyncol_check)}, + { { C_STRING_WITH_LEN("COLUMN_EXISTS") }, BUILDER(Create_func_dyncol_exists)}, + { { C_STRING_WITH_LEN("COLUMN_LIST") }, BUILDER(Create_func_dyncol_list)}, + { { C_STRING_WITH_LEN("COLUMN_JSON") }, BUILDER(Create_func_dyncol_json)}, { { C_STRING_WITH_LEN("COMPRESS") }, BUILDER(Create_func_compress)}, { { C_STRING_WITH_LEN("CONCAT") }, BUILDER(Create_func_concat)}, { { C_STRING_WITH_LEN("CONCAT_WS") }, BUILDER(Create_func_concat_ws)}, { { C_STRING_WITH_LEN("CONNECTION_ID") }, BUILDER(Create_func_connection_id)}, { { C_STRING_WITH_LEN("CONV") }, BUILDER(Create_func_conv)}, { { C_STRING_WITH_LEN("CONVERT_TZ") }, BUILDER(Create_func_convert_tz)}, + { { C_STRING_WITH_LEN("CONVEXHULL") }, GEOM_BUILDER(Create_func_convexhull)}, { { C_STRING_WITH_LEN("COS") }, BUILDER(Create_func_cos)}, { { C_STRING_WITH_LEN("COT") }, BUILDER(Create_func_cot)}, { { C_STRING_WITH_LEN("CRC32") }, BUILDER(Create_func_crc32)}, @@ -5262,6 +5796,7 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("DAYOFYEAR") }, BUILDER(Create_func_dayofyear)}, { { C_STRING_WITH_LEN("DECODE") }, BUILDER(Create_func_decode)}, { { C_STRING_WITH_LEN("DEGREES") }, BUILDER(Create_func_degrees)}, + { { C_STRING_WITH_LEN("DECODE_HISTOGRAM") }, BUILDER(Create_func_decode_histogram)}, { { C_STRING_WITH_LEN("DES_DECRYPT") }, BUILDER(Create_func_des_decrypt)}, { { C_STRING_WITH_LEN("DES_ENCRYPT") }, BUILDER(Create_func_des_encrypt)}, { { C_STRING_WITH_LEN("DIMENSION") }, GEOM_BUILDER(Create_func_dimension)}, @@ -5281,6 +5816,7 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("FLOOR") }, BUILDER(Create_func_floor)}, { { C_STRING_WITH_LEN("FORMAT") }, BUILDER(Create_func_format)}, { { C_STRING_WITH_LEN("FOUND_ROWS") }, BUILDER(Create_func_found_rows)}, + { { C_STRING_WITH_LEN("FROM_BASE64") }, BUILDER(Create_func_from_base64)}, { { C_STRING_WITH_LEN("FROM_DAYS") }, BUILDER(Create_func_from_days)}, { { C_STRING_WITH_LEN("FROM_UNIXTIME") }, BUILDER(Create_func_from_unixtime)}, { { C_STRING_WITH_LEN("GEOMCOLLFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, @@ -5300,12 +5836,19 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("IFNULL") }, BUILDER(Create_func_ifnull)}, { { C_STRING_WITH_LEN("INET_ATON") }, BUILDER(Create_func_inet_aton)}, { { C_STRING_WITH_LEN("INET_NTOA") }, BUILDER(Create_func_inet_ntoa)}, + { { C_STRING_WITH_LEN("INET6_ATON") }, BUILDER(Create_func_inet6_aton)}, + { { C_STRING_WITH_LEN("INET6_NTOA") }, BUILDER(Create_func_inet6_ntoa)}, + { { C_STRING_WITH_LEN("IS_IPV4") }, BUILDER(Create_func_is_ipv4)}, + { { C_STRING_WITH_LEN("IS_IPV6") }, BUILDER(Create_func_is_ipv6)}, + { { C_STRING_WITH_LEN("IS_IPV4_COMPAT") }, BUILDER(Create_func_is_ipv4_compat)}, + { { C_STRING_WITH_LEN("IS_IPV4_MAPPED") }, BUILDER(Create_func_is_ipv4_mapped)}, { { C_STRING_WITH_LEN("INSTR") }, BUILDER(Create_func_instr)}, { { C_STRING_WITH_LEN("INTERIORRINGN") }, GEOM_BUILDER(Create_func_interiorringn)}, { { C_STRING_WITH_LEN("INTERSECTS") }, GEOM_BUILDER(Create_func_mbr_intersects)}, { { C_STRING_WITH_LEN("ISCLOSED") }, GEOM_BUILDER(Create_func_isclosed)}, { { C_STRING_WITH_LEN("ISEMPTY") }, GEOM_BUILDER(Create_func_isempty)}, { { C_STRING_WITH_LEN("ISNULL") }, BUILDER(Create_func_isnull)}, + { { C_STRING_WITH_LEN("ISRING") }, GEOM_BUILDER(Create_func_isring)}, { { C_STRING_WITH_LEN("ISSIMPLE") }, GEOM_BUILDER(Create_func_issimple)}, { { C_STRING_WITH_LEN("IS_FREE_LOCK") }, BUILDER(Create_func_is_free_lock)}, { { C_STRING_WITH_LEN("IS_USED_LOCK") }, BUILDER(Create_func_is_used_lock)}, @@ -5334,6 +5877,7 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("MAKEDATE") }, BUILDER(Create_func_makedate)}, { { C_STRING_WITH_LEN("MAKETIME") }, BUILDER(Create_func_maketime)}, { { C_STRING_WITH_LEN("MAKE_SET") }, BUILDER(Create_func_make_set)}, + { { C_STRING_WITH_LEN("MASTER_GTID_WAIT") }, BUILDER(Create_func_master_gtid_wait)}, { { C_STRING_WITH_LEN("MASTER_POS_WAIT") }, BUILDER(Create_func_master_pos_wait)}, { { C_STRING_WITH_LEN("MBRCONTAINS") }, GEOM_BUILDER(Create_func_mbr_contains)}, { { C_STRING_WITH_LEN("MBRDISJOINT") }, GEOM_BUILDER(Create_func_mbr_disjoint)}, @@ -5371,6 +5915,7 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("POINTFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, { { C_STRING_WITH_LEN("POINTFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, { { C_STRING_WITH_LEN("POINTN") }, GEOM_BUILDER(Create_func_pointn)}, + { { C_STRING_WITH_LEN("POINTONSURFACE") }, GEOM_BUILDER(Create_func_pointonsurface)}, { { C_STRING_WITH_LEN("POLYFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, { { C_STRING_WITH_LEN("POLYFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, { { C_STRING_WITH_LEN("POLYGONFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, @@ -5378,12 +5923,14 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("POW") }, BUILDER(Create_func_pow)}, { { C_STRING_WITH_LEN("POWER") }, BUILDER(Create_func_pow)}, { { C_STRING_WITH_LEN("QUOTE") }, BUILDER(Create_func_quote)}, + { { C_STRING_WITH_LEN("REGEXP_INSTR") }, BUILDER(Create_func_regexp_instr)}, + { { C_STRING_WITH_LEN("REGEXP_REPLACE") }, BUILDER(Create_func_regexp_replace)}, + { { C_STRING_WITH_LEN("REGEXP_SUBSTR") }, BUILDER(Create_func_regexp_substr)}, { { C_STRING_WITH_LEN("RADIANS") }, BUILDER(Create_func_radians)}, { { C_STRING_WITH_LEN("RAND") }, BUILDER(Create_func_rand)}, { { C_STRING_WITH_LEN("RELEASE_LOCK") }, BUILDER(Create_func_release_lock)}, { { C_STRING_WITH_LEN("REVERSE") }, BUILDER(Create_func_reverse)}, { { C_STRING_WITH_LEN("ROUND") }, BUILDER(Create_func_round)}, - { { C_STRING_WITH_LEN("ROW_COUNT") }, BUILDER(Create_func_row_count)}, { { C_STRING_WITH_LEN("RPAD") }, BUILDER(Create_func_rpad)}, { { C_STRING_WITH_LEN("RTRIM") }, BUILDER(Create_func_rtrim)}, { { C_STRING_WITH_LEN("SEC_TO_TIME") }, BUILDER(Create_func_sec_to_time)}, @@ -5405,9 +5952,11 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("ST_ASTEXT") }, GEOM_BUILDER(Create_func_as_wkt)}, { { C_STRING_WITH_LEN("ST_ASWKB") }, GEOM_BUILDER(Create_func_as_wkb)}, { { C_STRING_WITH_LEN("ST_ASWKT") }, GEOM_BUILDER(Create_func_as_wkt)}, + { { C_STRING_WITH_LEN("ST_BOUNDARY") }, GEOM_BUILDER(Create_func_boundary)}, { { C_STRING_WITH_LEN("ST_BUFFER") }, GEOM_BUILDER(Create_func_buffer)}, { { C_STRING_WITH_LEN("ST_CENTROID") }, GEOM_BUILDER(Create_func_centroid)}, { { C_STRING_WITH_LEN("ST_CONTAINS") }, GEOM_BUILDER(Create_func_contains)}, + { { C_STRING_WITH_LEN("ST_CONVEXHULL") }, GEOM_BUILDER(Create_func_convexhull)}, { { C_STRING_WITH_LEN("ST_CROSSES") }, GEOM_BUILDER(Create_func_crosses)}, { { C_STRING_WITH_LEN("ST_DIFFERENCE") }, GEOM_BUILDER(Create_func_difference)}, { { C_STRING_WITH_LEN("ST_DIMENSION") }, GEOM_BUILDER(Create_func_dimension)}, @@ -5427,18 +5976,34 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("ST_GEOMETRYTYPE") }, GEOM_BUILDER(Create_func_geometry_type)}, { { C_STRING_WITH_LEN("ST_GEOMFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, { { C_STRING_WITH_LEN("ST_GEOMFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, +#ifndef DBUG_OFF + { { C_STRING_WITH_LEN("ST_GIS_DEBUG") }, GEOM_BUILDER(Create_func_gis_debug)}, +#endif { { C_STRING_WITH_LEN("ST_EQUALS") }, GEOM_BUILDER(Create_func_equals)}, { { C_STRING_WITH_LEN("ST_INTERIORRINGN") }, GEOM_BUILDER(Create_func_interiorringn)}, { { C_STRING_WITH_LEN("ST_INTERSECTS") }, GEOM_BUILDER(Create_func_intersects)}, { { C_STRING_WITH_LEN("ST_INTERSECTION") }, GEOM_BUILDER(Create_func_intersection)}, { { C_STRING_WITH_LEN("ST_ISCLOSED") }, GEOM_BUILDER(Create_func_isclosed)}, { { C_STRING_WITH_LEN("ST_ISEMPTY") }, GEOM_BUILDER(Create_func_isempty)}, + { { C_STRING_WITH_LEN("ST_ISRING") }, GEOM_BUILDER(Create_func_isring)}, { { C_STRING_WITH_LEN("ST_ISSIMPLE") }, GEOM_BUILDER(Create_func_issimple)}, { { C_STRING_WITH_LEN("ST_LENGTH") }, GEOM_BUILDER(Create_func_glength)}, { { C_STRING_WITH_LEN("ST_LINEFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, { { C_STRING_WITH_LEN("ST_LINEFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, { { C_STRING_WITH_LEN("ST_LINESTRINGFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, { { C_STRING_WITH_LEN("ST_LINESTRINGFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, + { { C_STRING_WITH_LEN("ST_MLINEFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, + { { C_STRING_WITH_LEN("ST_MLINEFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, + { { C_STRING_WITH_LEN("ST_MPOINTFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, + { { C_STRING_WITH_LEN("ST_MPOINTFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, + { { C_STRING_WITH_LEN("ST_MPOLYFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, + { { C_STRING_WITH_LEN("ST_MPOLYFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, + { { C_STRING_WITH_LEN("ST_MULTILINESTRINGFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, + { { C_STRING_WITH_LEN("ST_MULTILINESTRINGFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, + { { C_STRING_WITH_LEN("ST_MULTIPOINTFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, + { { C_STRING_WITH_LEN("ST_MULTIPOINTFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, + { { C_STRING_WITH_LEN("ST_MULTIPOLYGONFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, + { { C_STRING_WITH_LEN("ST_MULTIPOLYGONFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, { { C_STRING_WITH_LEN("ST_NUMGEOMETRIES") }, GEOM_BUILDER(Create_func_numgeometries)}, { { C_STRING_WITH_LEN("ST_NUMINTERIORRINGS") }, GEOM_BUILDER(Create_func_numinteriorring)}, { { C_STRING_WITH_LEN("ST_NUMPOINTS") }, GEOM_BUILDER(Create_func_numpoints)}, @@ -5446,10 +6011,12 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("ST_POINTFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, { { C_STRING_WITH_LEN("ST_POINTFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, { { C_STRING_WITH_LEN("ST_POINTN") }, GEOM_BUILDER(Create_func_pointn)}, + { { C_STRING_WITH_LEN("ST_POINTONSURFACE") }, GEOM_BUILDER(Create_func_pointonsurface)}, { { C_STRING_WITH_LEN("ST_POLYFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, { { C_STRING_WITH_LEN("ST_POLYFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, { { C_STRING_WITH_LEN("ST_POLYGONFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)}, { { C_STRING_WITH_LEN("ST_POLYGONFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)}, + { { C_STRING_WITH_LEN("ST_RELATE") }, GEOM_BUILDER(Create_func_relate)}, { { C_STRING_WITH_LEN("ST_SRID") }, GEOM_BUILDER(Create_func_srid)}, { { C_STRING_WITH_LEN("ST_STARTPOINT") }, GEOM_BUILDER(Create_func_startpoint)}, { { C_STRING_WITH_LEN("ST_SYMDIFFERENCE") }, GEOM_BUILDER(Create_func_symdifference)}, @@ -5465,6 +6032,7 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("TIME_FORMAT") }, BUILDER(Create_func_time_format)}, { { C_STRING_WITH_LEN("TIME_TO_SEC") }, BUILDER(Create_func_time_to_sec)}, { { C_STRING_WITH_LEN("TOUCHES") }, GEOM_BUILDER(Create_func_touches)}, + { { C_STRING_WITH_LEN("TO_BASE64") }, BUILDER(Create_func_to_base64)}, { { C_STRING_WITH_LEN("TO_DAYS") }, BUILDER(Create_func_to_days)}, { { C_STRING_WITH_LEN("TO_SECONDS") }, BUILDER(Create_func_to_seconds)}, { { C_STRING_WITH_LEN("UCASE") }, BUILDER(Create_func_ucase)}, @@ -5597,16 +6165,16 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, switch (cast_type) { case ITEM_CAST_BINARY: - res= new (thd->mem_root) Item_func_binary(a); + res= new (thd->mem_root) Item_func_binary(thd, a); break; case ITEM_CAST_SIGNED_INT: - res= new (thd->mem_root) Item_func_signed(a); + res= new (thd->mem_root) Item_func_signed(thd, a); break; case ITEM_CAST_UNSIGNED_INT: - res= new (thd->mem_root) Item_func_unsigned(a); + res= new (thd->mem_root) Item_func_unsigned(thd, a); break; case ITEM_CAST_DATE: - res= new (thd->mem_root) Item_date_typecast(a); + res= new (thd->mem_root) Item_date_typecast(thd, a); break; case ITEM_CAST_TIME: if (decimals > MAX_DATETIME_PRECISION) @@ -5615,7 +6183,7 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, MAX_DATETIME_PRECISION); return 0; } - res= new (thd->mem_root) Item_time_typecast(a, (uint) decimals); + res= new (thd->mem_root) Item_time_typecast(thd, a, (uint) decimals); break; case ITEM_CAST_DATETIME: if (decimals > MAX_DATETIME_PRECISION) @@ -5624,7 +6192,7 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, MAX_DATETIME_PRECISION); return 0; } - res= new (thd->mem_root) Item_datetime_typecast(a, (uint) decimals); + res= new (thd->mem_root) Item_datetime_typecast(thd, a, (uint) decimals); break; case ITEM_CAST_DECIMAL: { @@ -5634,7 +6202,7 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, DECIMAL_MAX_PRECISION, DECIMAL_MAX_SCALE, a)) return NULL; - res= new (thd->mem_root) Item_decimal_typecast(a, len, dec); + res= new (thd->mem_root) Item_decimal_typecast(thd, a, len, dec); break; } case ITEM_CAST_DOUBLE: @@ -5651,7 +6219,7 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, DECIMAL_MAX_PRECISION, NOT_FIXED_DEC-1, a)) return NULL; - res= new (thd->mem_root) Item_double_typecast(a, (uint) length, + res= new (thd->mem_root) Item_double_typecast(thd, a, (uint) length, (uint) decimals); break; } @@ -5671,7 +6239,7 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, } len= (int) length; } - res= new (thd->mem_root) Item_char_typecast(a, len, real_cs); + res= new (thd->mem_root) Item_char_typecast(thd, a, len, real_cs); break; } default: @@ -5685,6 +6253,84 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, } +static bool +have_important_literal_warnings(const MYSQL_TIME_STATUS *status) +{ + return (status->warnings & ~MYSQL_TIME_NOTE_TRUNCATED) != 0; +} + + +/** + Builder for datetime literals: + TIME'00:00:00', DATE'2001-01-01', TIMESTAMP'2001-01-01 00:00:00'. + @param thd The current thread + @param str Character literal + @param length Length of str + @param type Type of literal (TIME, DATE or DATETIME) + @param send_error Whether to generate an error on failure +*/ + +Item *create_temporal_literal(THD *thd, + const char *str, uint length, + CHARSET_INFO *cs, + enum_field_types type, + bool send_error) +{ + MYSQL_TIME_STATUS status; + MYSQL_TIME ltime; + Item *item= NULL; + ulonglong flags= sql_mode_for_dates(thd); + + switch(type) + { + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_NEWDATE: + if (!str_to_datetime(cs, str, length, <ime, flags, &status) && + ltime.time_type == MYSQL_TIMESTAMP_DATE && !status.warnings) + item= new (thd->mem_root) Item_date_literal(thd, <ime); + break; + case MYSQL_TYPE_DATETIME: + if (!str_to_datetime(cs, str, length, <ime, flags, &status) && + ltime.time_type == MYSQL_TIMESTAMP_DATETIME && + !have_important_literal_warnings(&status)) + item= new (thd->mem_root) Item_datetime_literal(thd, <ime, + status.precision); + break; + case MYSQL_TYPE_TIME: + if (!str_to_time(cs, str, length, <ime, 0, &status) && + ltime.time_type == MYSQL_TIMESTAMP_TIME && + !have_important_literal_warnings(&status)) + item= new (thd->mem_root) Item_time_literal(thd, <ime, + status.precision); + break; + default: + DBUG_ASSERT(0); + } + + if (item) + { + if (status.warnings) // e.g. a note on nanosecond truncation + { + ErrConvString err(str, length, cs); + make_truncated_value_warning(thd, + Sql_condition::time_warn_level(status.warnings), + &err, ltime.time_type, 0); + } + return item; + } + + if (send_error) + { + const char *typestr= + (type == MYSQL_TYPE_DATE) ? "DATE" : + (type == MYSQL_TYPE_TIME) ? "TIME" : "DATETIME"; + ErrConvString err(str, length, thd->variables.character_set_client); + my_error(ER_WRONG_VALUE, MYF(0), typestr, err.ptr()); + } + return NULL; +} + + static List<Item> *create_func_dyncol_prepare(THD *thd, DYNCALL_CREATE_DEF **dfs, List<DYNCALL_CREATE_DEF> &list) @@ -5703,8 +6349,8 @@ static List<Item> *create_func_dyncol_prepare(THD *thd, for (uint i= 0; (def= li++) ;) { dfs[0][i++]= *def; - args->push_back(def->num); - args->push_back(def->value); + args->push_back(def->key, thd->mem_root); + args->push_back(def->value, thd->mem_root); } return args; } @@ -5716,10 +6362,9 @@ Item *create_func_dyncol_create(THD *thd, List<DYNCALL_CREATE_DEF> &list) if (!(args= create_func_dyncol_prepare(thd, &dfs, list))) return NULL; - return new (thd->mem_root) Item_func_dyncol_create(*args, dfs); + return new (thd->mem_root) Item_func_dyncol_create(thd, *args, dfs); } - Item *create_func_dyncol_add(THD *thd, Item *str, List<DYNCALL_CREATE_DEF> &list) { @@ -5729,9 +6374,9 @@ Item *create_func_dyncol_add(THD *thd, Item *str, if (!(args= create_func_dyncol_prepare(thd, &dfs, list))) return NULL; - args->push_back(str); + args->push_back(str, thd->mem_root); - return new (thd->mem_root) Item_func_dyncol_add(*args, dfs); + return new (thd->mem_root) Item_func_dyncol_add(thd, *args, dfs); } @@ -5739,7 +6384,7 @@ Item *create_func_dyncol_add(THD *thd, Item *str, Item *create_func_dyncol_delete(THD *thd, Item *str, List<Item> &nums) { DYNCALL_CREATE_DEF *dfs; - Item *num; + Item *key; List_iterator_fast<Item> it(nums); List<Item> *args= new (thd->mem_root) List<Item>; @@ -5749,18 +6394,18 @@ Item *create_func_dyncol_delete(THD *thd, Item *str, List<Item> &nums) if (!args || !dfs) return NULL; - for (uint i= 0; (num= it++); i++) + for (uint i= 0; (key= it++); i++) { - dfs[i].num= num; - dfs[i].value= new Item_null(); + dfs[i].key= key; + dfs[i].value= new (thd->mem_root) Item_null(thd); dfs[i].type= DYN_COL_INT; - args->push_back(dfs[i].num); - args->push_back(dfs[i].value); + args->push_back(dfs[i].key, thd->mem_root); + args->push_back(dfs[i].value, thd->mem_root); } - args->push_back(str); + args->push_back(str, thd->mem_root); - return new (thd->mem_root) Item_func_dyncol_add(*args, dfs); + return new (thd->mem_root) Item_func_dyncol_add(thd, *args, dfs); } @@ -5771,7 +6416,7 @@ Item *create_func_dyncol_get(THD *thd, Item *str, Item *num, { Item *res; - if (!(res= new (thd->mem_root) Item_dyncol_get(str, num))) + if (!(res= new (thd->mem_root) Item_dyncol_get(thd, str, num))) return res; // Return NULL return create_func_cast(thd, res, cast_type, c_len, c_dec, cs); } diff --git a/sql/item_create.h b/sql/item_create.h index ac6b0f8454f..05fe48f656a 100644 --- a/sql/item_create.h +++ b/sql/item_create.h @@ -168,6 +168,20 @@ create_func_cast(THD *thd, Item *a, Cast_target cast_type, const char *len, const char *dec, CHARSET_INFO *cs); +Item *create_temporal_literal(THD *thd, + const char *str, uint length, + CHARSET_INFO *cs, + enum_field_types type, + bool send_error); +inline +Item *create_temporal_literal(THD *thd, const String *str, + enum_field_types type, + bool send_error) +{ + return create_temporal_literal(thd, + str->ptr(), str->length(), str->charset(), + type, send_error); +} int item_create_init(); void item_create_cleanup(); @@ -180,5 +194,6 @@ Item *create_func_dyncol_get(THD *thd, Item *num, Item *str, Cast_target cast_type, const char *c_len, const char *c_dec, CHARSET_INFO *cs); +Item *create_func_dyncol_json(THD *thd, Item *str); #endif diff --git a/sql/item_func.cc b/sql/item_func.cc index 73fc099252b..a1a2c3f1d1c 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2009, 2015, MariaDB + Copyright (c) 2009, 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 @@ -25,7 +25,7 @@ #pragma implementation // gcc: Class implementation #endif -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" #include "sql_priv.h" /* It is necessary to include set_var.h instead of item.h because there @@ -53,8 +53,6 @@ #include "sp.h" #include "set_var.h" #include "debug_sync.h" -#include <mysql/plugin.h> -#include <mysql/service_thd_wait.h> #ifdef NO_EMBEDDED_ACCESS_CHECKS #define sp_restore_security_context(A,B) while (0) {} @@ -71,18 +69,6 @@ bool check_reserved_words(LEX_STRING *name) /** - @return - TRUE if item is a constant -*/ - -bool -eval_const_cond(COND *cond) -{ - return ((Item_func*) cond)->val_int() ? TRUE : FALSE; -} - - -/** Test if the sum of arguments overflows the ulonglong range. */ static inline bool test_if_sum_overflows_ull(ulonglong arg1, ulonglong arg2) @@ -90,51 +76,52 @@ static inline bool test_if_sum_overflows_ull(ulonglong arg1, ulonglong arg2) return ULONGLONG_MAX - arg1 < arg2; } -void Item_func::set_arguments(List<Item> &list) + +void Item_args::set_arguments(THD *thd, List<Item> &list) { - allowed_arg_cols= 1; - arg_count=list.elements; - args= tmp_arg; // If 2 arguments - if (arg_count <= 2 || (args=(Item**) sql_alloc(sizeof(Item*)*arg_count))) + arg_count= list.elements; + if (arg_count <= 2) { - List_iterator_fast<Item> li(list); - Item *item; - Item **save_args= args; - - while ((item=li++)) - { - *(save_args++)= item; - with_sum_func|=item->with_sum_func; - with_field|= item->with_field; - } + args= tmp_arg; + } + else if (!(args= (Item**) thd->alloc(sizeof(Item*) * arg_count))) + { + arg_count= 0; + return; } - list.empty(); // Fields are used + uint i= 0; + List_iterator_fast<Item> li(list); + Item *item; + while ((item= li++)) + args[i++]= item; } -Item_func::Item_func(List<Item> &list) - :allowed_arg_cols(1) + +Item_args::Item_args(THD *thd, const Item_args *other) + :arg_count(other->arg_count) { - set_arguments(list); + if (arg_count <= 2) + { + args= tmp_arg; + } + else if (!(args= (Item**) thd->alloc(sizeof(Item*) * arg_count))) + { + arg_count= 0; + return; + } + memcpy(args, other->args, sizeof(Item*) * arg_count); } -Item_func::Item_func(THD *thd, Item_func *item) - :Item_result_field(thd, item), - allowed_arg_cols(item->allowed_arg_cols), - arg_count(item->arg_count), - used_tables_cache(item->used_tables_cache), - not_null_tables_cache(item->not_null_tables_cache), - const_item_cache(item->const_item_cache) + +void Item_func::sync_with_sum_func_and_with_field(List<Item> &list) { - if (arg_count) + List_iterator_fast<Item> li(list); + Item *item; + while ((item= li++)) { - if (arg_count <=2) - args= tmp_arg; - else - { - if (!(args=(Item**) thd->alloc(sizeof(Item*)*arg_count))) - return; - } - memcpy((char*) args, (char*) item->args, sizeof(Item*)*arg_count); + with_sum_func|= item->with_sum_func; + with_field|= item->with_field; + with_param|= item->with_param; } } @@ -180,8 +167,14 @@ Item_func::fix_fields(THD *thd, Item **ref) Item **arg,**arg_end; uchar buff[STACK_BUFF_ALLOC]; // Max argument in function - used_tables_cache= not_null_tables_cache= 0; - const_item_cache=1; + /* + The Used_tables_and_const_cache of "this" was initialized by + the constructor, or by Item_func::cleanup(). + */ + DBUG_ASSERT(used_tables_cache == 0); + DBUG_ASSERT(const_item_cache == true); + + not_null_tables_cache= 0; /* Use stack limit of STACK_MIN_SIZE * 2 since @@ -224,13 +217,12 @@ Item_func::fix_fields(THD *thd, Item **ref) with_sum_func= with_sum_func || item->with_sum_func; with_param= with_param || item->with_param; with_field= with_field || item->with_field; - used_tables_cache|= item->used_tables(); - const_item_cache&= item->const_item(); + used_tables_and_const_cache_join(item); with_subselect|= item->has_subquery(); } } fix_length_and_dec(); - if (thd->is_error()) // An error inside fix_length_and_dec occured + if (thd->is_error()) // An error inside fix_length_and_dec occurred return TRUE; fixed= 1; return FALSE; @@ -268,43 +260,28 @@ Item_func::eval_not_null_tables(uchar *opt_arg) } -void Item_func::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_func::fix_after_pullout(st_select_lex *new_parent, Item **ref, + bool merge) { Item **arg,**arg_end; - used_tables_cache= not_null_tables_cache= 0; - const_item_cache=1; + used_tables_and_const_cache_init(); + not_null_tables_cache= 0; if (arg_count) { for (arg=args, arg_end=args+arg_count; arg != arg_end ; arg++) { - (*arg)->fix_after_pullout(new_parent, arg); + (*arg)->fix_after_pullout(new_parent, arg, merge); Item *item= *arg; - used_tables_cache|= item->used_tables(); + used_tables_and_const_cache_join(item); not_null_tables_cache|= item->not_null_tables(); - const_item_cache&= item->const_item(); } } } -bool Item_func::walk(Item_processor processor, bool walk_subquery, - uchar *argument) -{ - if (arg_count) - { - Item **arg,**arg_end; - for (arg= args, arg_end= args+arg_count; arg != arg_end; arg++) - { - if ((*arg)->walk(processor, walk_subquery, argument)) - return 1; - } - } - return (this->*processor)(argument); -} - void Item_func::traverse_cond(Cond_traverser traverser, void *argument, traverse_order order) { @@ -333,6 +310,26 @@ void Item_func::traverse_cond(Cond_traverser traverser, } +bool Item_args::transform_args(THD *thd, Item_transformer transformer, uchar *arg) +{ + for (uint i= 0; i < arg_count; i++) + { + Item *new_item= args[i]->transform(thd, transformer, arg); + if (!new_item) + return true; + /* + THD::change_item_tree() should be called only if the tree was + really transformed, i.e. when a new item has been created. + Otherwise we'll be allocating a lot of unnecessary memory for + change records at each execution. + */ + if (args[i] != new_item) + thd->change_item_tree(&args[i], new_item); + } + return false; +} + + /** Transform an Item_func object with a transformer callback function. @@ -350,30 +347,12 @@ void Item_func::traverse_cond(Cond_traverser traverser, Item returned as the result of transformation of the root node */ -Item *Item_func::transform(Item_transformer transformer, uchar *argument) +Item *Item_func::transform(THD *thd, Item_transformer transformer, uchar *argument) { - DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); - - if (arg_count) - { - Item **arg,**arg_end; - for (arg= args, arg_end= args+arg_count; arg != arg_end; arg++) - { - Item *new_item= (*arg)->transform(transformer, argument); - if (!new_item) - return 0; - - /* - THD::change_item_tree() should be called only if the tree was - really transformed, i.e. when a new item has been created. - Otherwise we'll be allocating a lot of unnecessary memory for - change records at each execution. - */ - if (*arg != new_item) - current_thd->change_item_tree(arg, new_item); - } - } - return (this->*transformer)(argument); + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); + if (transform_args(thd, transformer, argument)) + return 0; + return (this->*transformer)(thd, argument); } @@ -403,7 +382,7 @@ Item *Item_func::transform(Item_transformer transformer, uchar *argument) Item returned as the result of transformation of the root node */ -Item *Item_func::compile(Item_analyzer analyzer, uchar **arg_p, +Item *Item_func::compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, Item_transformer transformer, uchar *arg_t) { if (!(this->*analyzer)(arg_p)) @@ -418,43 +397,38 @@ Item *Item_func::compile(Item_analyzer analyzer, uchar **arg_p, to analyze any argument of the condition formula. */ uchar *arg_v= *arg_p; - Item *new_item= (*arg)->compile(analyzer, &arg_v, transformer, arg_t); + Item *new_item= (*arg)->compile(thd, analyzer, &arg_v, transformer, + arg_t); if (new_item && *arg != new_item) - current_thd->change_item_tree(arg, new_item); + thd->change_item_tree(arg, new_item); } } - return (this->*transformer)(arg_t); + return (this->*transformer)(thd, arg_t); } -/** - See comments in Item_cmp_func::split_sum_func() -*/ -void Item_func::split_sum_func(THD *thd, Item **ref_pointer_array, - List<Item> &fields) +void Item_args::propagate_equal_fields(THD *thd, + const Item::Context &ctx, + COND_EQUAL *cond) { - Item **arg, **arg_end; - for (arg= args, arg_end= args+arg_count; arg != arg_end ; arg++) - (*arg)->split_sum_func2(thd, ref_pointer_array, fields, arg, TRUE); + uint i; + for (i= 0; i < arg_count; i++) + args[i]->propagate_equal_fields_and_change_item_tree(thd, ctx, cond, + &args[i]); } -void Item_func::update_used_tables() -{ - used_tables_cache=0; - const_item_cache=1; - for (uint i=0 ; i < arg_count ; i++) - { - args[i]->update_used_tables(); - used_tables_cache|=args[i]->used_tables(); - const_item_cache&=args[i]->const_item(); - } -} - +/** + See comments in Item_cond::split_sum_func() +*/ -table_map Item_func::used_tables() const +void Item_func::split_sum_func(THD *thd, Item **ref_pointer_array, + List<Item> &fields, uint flags) { - return used_tables_cache; + Item **arg, **arg_end; + for (arg= args, arg_end= args+arg_count; arg != arg_end ; arg++) + (*arg)->split_sum_func2(thd, ref_pointer_array, fields, arg, + flags | SPLIT_SUM_SKIP_REGISTERED); } @@ -504,7 +478,11 @@ bool Item_func::eq(const Item *item, bool binary_cmp) const /* Assume we don't have rtti */ if (this == item) return 1; - if (item->type() != FUNC_ITEM) + /* + Ensure that we are comparing two functions and that the function + is deterministic. + */ + if (item->type() != FUNC_ITEM || (used_tables() & RAND_TABLE_BIT)) return 0; Item_func *item_func=(Item_func*) item; Item_func::Functype func_type; @@ -522,40 +500,6 @@ bool Item_func::eq(const Item *item, bool binary_cmp) const } -Field *Item_func::tmp_table_field(TABLE *table) -{ - Field *field= NULL; - - switch (result_type()) { - case INT_RESULT: - if (max_char_length() > MY_INT32_NUM_DECIMAL_DIGITS) - field= new Field_longlong(max_char_length(), maybe_null, name, - unsigned_flag); - else - field= new Field_long(max_char_length(), maybe_null, name, - unsigned_flag); - break; - case REAL_RESULT: - field= new Field_double(max_char_length(), maybe_null, name, decimals); - break; - case STRING_RESULT: - return make_string_field(table); - case DECIMAL_RESULT: - field= Field_new_decimal::create_from_item(this); - break; - case ROW_RESULT: - case TIME_RESULT: - case IMPOSSIBLE_RESULT: - // This case should never be chosen - DBUG_ASSERT(0); - field= 0; - break; - } - if (field) - field->init(table); - return field; -} - /* bool Item_func::is_expensive_processor(uchar *arg) { @@ -624,18 +568,19 @@ void Item_udf_func::fix_num_length_and_dec() @retval False on success, true on error. */ -void Item_func::count_datetime_length(Item **item, uint nitems) +void Item_func::count_datetime_length(enum_field_types field_type_arg, + Item **item, uint nitems) { unsigned_flag= 0; decimals= 0; - if (field_type() != MYSQL_TYPE_DATE) + if (field_type_arg != MYSQL_TYPE_DATE) { for (uint i= 0; i < nitems; i++) set_if_bigger(decimals, item[i]->decimals); } set_if_smaller(decimals, TIME_SECOND_PART_DIGITS); uint len= decimals ? (decimals + 1) : 0; - len+= mysql_temporal_int_part_length(field_type()); + len+= mysql_temporal_int_part_length(field_type_arg); fix_char_length(len); } @@ -655,7 +600,7 @@ void Item_func::count_decimal_length(Item **item, uint nitems) set_if_bigger(max_int_part, item[i]->decimal_int_part()); set_if_smaller(unsigned_flag, item[i]->unsigned_flag); } - int precision= min(max_int_part + decimals, DECIMAL_MAX_PRECISION); + int precision= MY_MIN(max_int_part + decimals, DECIMAL_MAX_PRECISION); fix_char_length(my_decimal_precision_to_length_no_truncation(precision, decimals, unsigned_flag)); @@ -684,19 +629,20 @@ void Item_func::count_only_length(Item **item, uint nitems) result length/precision depends on argument ones. */ -void Item_func::count_real_length(Item **item, uint nitems) +void Item_func::count_real_length(Item **items, uint nitems) { uint32 length= 0; decimals= 0; max_length= 0; + unsigned_flag= false; for (uint i=0 ; i < nitems ; i++) { if (decimals != NOT_FIXED_DEC) { - set_if_bigger(decimals, item[i]->decimals); - set_if_bigger(length, (item[i]->max_length - item[i]->decimals)); + set_if_bigger(decimals, items[i]->decimals); + set_if_bigger(length, (items[i]->max_length - items[i]->decimals)); } - set_if_bigger(max_length, item[i]->max_length); + set_if_bigger(max_length, items[i]->max_length); } if (decimals != NOT_FIXED_DEC) { @@ -719,17 +665,17 @@ void Item_func::count_real_length(Item **item, uint nitems) @retval False on success, true on error. */ -bool Item_func::count_string_result_length(enum_field_types field_type, +bool Item_func::count_string_result_length(enum_field_types field_type_arg, Item **items, uint nitems) { if (agg_arg_charsets_for_string_result(collation, items, nitems, 1)) return true; - if (is_temporal_type(field_type)) - count_datetime_length(items, nitems); + if (is_temporal_type(field_type_arg)) + count_datetime_length(field_type_arg, items, nitems); else { - decimals= NOT_FIXED_DEC; count_only_length(items, nitems); + decimals= max_length ? NOT_FIXED_DEC : 0; } return false; } @@ -739,8 +685,8 @@ void Item_func::signal_divide_by_null() { THD *thd= current_thd; if (thd->variables.sql_mode & MODE_ERROR_FOR_DIVISION_BY_ZERO) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_DIVISION_BY_ZERO, - ER(ER_DIVISION_BY_ZERO)); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_DIVISION_BY_ZERO, + ER_THD(thd, ER_DIVISION_BY_ZERO)); null_value= 1; } @@ -748,7 +694,7 @@ void Item_func::signal_divide_by_null() Item *Item_func::get_tmp_table_item(THD *thd) { if (!with_sum_func && !const_item()) - return new Item_field(result_field); + return new (thd->mem_root) Item_temptable_field(thd, result_field); return copy_or_same(thd); } @@ -759,16 +705,6 @@ double Item_int_func::val_real() return unsigned_flag ? (double) ((ulonglong) val_int()) : (double) val_int(); } -bool Item_int_func::count_sargable_conds(uchar *arg) -{ - if (sargable) - { - SELECT_LEX *sel= (SELECT_LEX *) arg; - sel->cond_count++; - } - return 0; -} - String *Item_int_func::val_str(String *str) { @@ -816,28 +752,28 @@ void Item_num_op::fix_length_and_dec(void) { count_real_length(args, arg_count); max_length= float_length(decimals); - cached_result_type= REAL_RESULT; + set_handler_by_result_type(REAL_RESULT); } else if (r0 == DECIMAL_RESULT || r1 == DECIMAL_RESULT || r0 == TIME_RESULT || r1 == TIME_RESULT) { - cached_result_type= DECIMAL_RESULT; + set_handler_by_result_type(DECIMAL_RESULT); result_precision(); fix_decimals(); if ((r0 == TIME_RESULT || r1 == TIME_RESULT) && decimals == 0) - cached_result_type= INT_RESULT; + set_handler_by_result_type(INT_RESULT); } else { DBUG_ASSERT(r0 == INT_RESULT && r1 == INT_RESULT); - cached_result_type=INT_RESULT; + set_handler_by_result_type(INT_RESULT); result_precision(); decimals= 0; } DBUG_PRINT("info", ("Type: %s", - (cached_result_type == REAL_RESULT ? "REAL_RESULT" : - cached_result_type == DECIMAL_RESULT ? "DECIMAL_RESULT" : - cached_result_type == INT_RESULT ? "INT_RESULT" : + (result_type() == REAL_RESULT ? "REAL_RESULT" : + result_type() == DECIMAL_RESULT ? "DECIMAL_RESULT" : + result_type() == INT_RESULT ? "INT_RESULT" : "--ILLEGAL!!!--"))); DBUG_VOID_RETURN; } @@ -853,41 +789,41 @@ void Item_func_num1::fix_length_and_dec() { DBUG_ENTER("Item_func_num1::fix_length_and_dec"); DBUG_PRINT("info", ("name %s", func_name())); - switch (cached_result_type= args[0]->cast_to_int_type()) { + // Note, cast_to_int_type() can return TIME_RESULT + switch (args[0]->cast_to_int_type()) { case INT_RESULT: + set_handler_by_result_type(INT_RESULT); max_length= args[0]->max_length; unsigned_flag= args[0]->unsigned_flag; break; case STRING_RESULT: case REAL_RESULT: - cached_result_type= REAL_RESULT; + set_handler_by_result_type(REAL_RESULT); decimals= args[0]->decimals; // Preserve NOT_FIXED_DEC max_length= float_length(decimals); break; case TIME_RESULT: - cached_result_type= DECIMAL_RESULT; - /* fall through */ case DECIMAL_RESULT: + set_handler_by_result_type(DECIMAL_RESULT); decimals= args[0]->decimal_scale(); // Do not preserve NOT_FIXED_DEC max_length= args[0]->max_length; break; case ROW_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } DBUG_PRINT("info", ("Type: %s", - (cached_result_type == REAL_RESULT ? "REAL_RESULT" : - cached_result_type == DECIMAL_RESULT ? "DECIMAL_RESULT" : - cached_result_type == INT_RESULT ? "INT_RESULT" : + (result_type() == REAL_RESULT ? "REAL_RESULT" : + result_type() == DECIMAL_RESULT ? "DECIMAL_RESULT" : + result_type() == INT_RESULT ? "INT_RESULT" : "--ILLEGAL!!!--"))); DBUG_VOID_RETURN; } -String *Item_func_hybrid_result_type::val_str(String *str) +String *Item_func_hybrid_field_type::val_str(String *str) { DBUG_ASSERT(fixed == 1); - switch (cached_result_type) { + switch (Item_func_hybrid_field_type::cmp_type()) { case DECIMAL_RESULT: { my_decimal decimal_value, *val; @@ -914,23 +850,21 @@ String *Item_func_hybrid_result_type::val_str(String *str) str->set_real(nr, decimals, collation.collation); break; } + case TIME_RESULT: + { + MYSQL_TIME ltime; + if (date_op_with_null_check(<ime) || + (null_value= str->alloc(MAX_DATE_STRING_REP_LENGTH))) + return (String *) 0; + ltime.time_type= mysql_type_to_time_type(field_type()); + str->length(my_TIME_to_str(<ime, const_cast<char*>(str->ptr()), decimals)); + str->set_charset(&my_charset_bin); + DBUG_ASSERT(!null_value); + return str; + } case STRING_RESULT: - if (is_temporal_type(field_type())) - { - MYSQL_TIME ltime; - if (date_op_with_null_check(<ime) || - (null_value= str->alloc(MAX_DATE_STRING_REP_LENGTH))) - return (String *) 0; - ltime.time_type= mysql_type_to_time_type(field_type()); - str->length(my_TIME_to_str(<ime, const_cast<char*>(str->ptr()), decimals)); - str->set_charset(&my_charset_bin); - DBUG_ASSERT(!null_value); - return str; - } return str_op_with_null_check(&str_value); - case TIME_RESULT: case ROW_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } DBUG_ASSERT(!null_value || (str == NULL)); @@ -938,10 +872,10 @@ String *Item_func_hybrid_result_type::val_str(String *str) } -double Item_func_hybrid_result_type::val_real() +double Item_func_hybrid_field_type::val_real() { DBUG_ASSERT(fixed == 1); - switch (cached_result_type) { + switch (Item_func_hybrid_field_type::cmp_type()) { case DECIMAL_RESULT: { my_decimal decimal_value, *val; @@ -958,35 +892,30 @@ double Item_func_hybrid_result_type::val_real() } case REAL_RESULT: return real_op(); + case TIME_RESULT: + { + MYSQL_TIME ltime; + if (date_op_with_null_check(<ime)) + return 0; + ltime.time_type= mysql_type_to_time_type(field_type()); + return TIME_to_double(<ime); + } case STRING_RESULT: { - if (is_temporal_type(field_type())) - { - MYSQL_TIME ltime; - if (date_op_with_null_check(<ime)) - return 0; - ltime.time_type= mysql_type_to_time_type(field_type()); - return TIME_to_double(<ime); - } - char *end_not_used; - int err_not_used; String *res= str_op_with_null_check(&str_value); - return (res ? my_strntod(res->charset(), (char*) res->ptr(), res->length(), - &end_not_used, &err_not_used) : 0.0); + return res ? double_from_string_with_check(res) : 0.0; } - case TIME_RESULT: case ROW_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } return 0.0; } -longlong Item_func_hybrid_result_type::val_int() +longlong Item_func_hybrid_field_type::val_int() { DBUG_ASSERT(fixed == 1); - switch (cached_result_type) { + switch (Item_func_hybrid_field_type::cmp_type()) { case DECIMAL_RESULT: { my_decimal decimal_value, *val; @@ -1003,39 +932,31 @@ longlong Item_func_hybrid_result_type::val_int() bool error; return double_to_longlong(real_op(), unsigned_flag, &error); } - case STRING_RESULT: + case TIME_RESULT: { - if (is_temporal_type(field_type())) - { - MYSQL_TIME ltime; - if (date_op_with_null_check(<ime)) - return 0; - ltime.time_type= mysql_type_to_time_type(field_type()); - return TIME_to_ulonglong(<ime); - } - int err_not_used; - String *res; - if (!(res= str_op_with_null_check(&str_value))) + MYSQL_TIME ltime; + if (date_op_with_null_check(<ime)) return 0; - - char *end= (char*) res->ptr() + res->length(); - CHARSET_INFO *cs= res->charset(); - return (*(cs->cset->strtoll10))(cs, res->ptr(), &end, &err_not_used); + ltime.time_type= mysql_type_to_time_type(field_type()); + return TIME_to_ulonglong(<ime); + } + case STRING_RESULT: + { + String *res= str_op_with_null_check(&str_value); + return res ? longlong_from_string_with_check(res) : 0; } - case TIME_RESULT: case ROW_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } return 0; } -my_decimal *Item_func_hybrid_result_type::val_decimal(my_decimal *decimal_value) +my_decimal *Item_func_hybrid_field_type::val_decimal(my_decimal *decimal_value) { my_decimal *val= decimal_value; DBUG_ASSERT(fixed == 1); - switch (cached_result_type) { + switch (Item_func_hybrid_field_type::cmp_type()) { case DECIMAL_RESULT: val= decimal_op_with_null_check(decimal_value); break; @@ -1055,44 +976,34 @@ my_decimal *Item_func_hybrid_result_type::val_decimal(my_decimal *decimal_value) double2my_decimal(E_DEC_FATAL_ERROR, result, decimal_value); break; } - case STRING_RESULT: + case TIME_RESULT: { - if (is_temporal_type(field_type())) - { - MYSQL_TIME ltime; - if (date_op_with_null_check(<ime)) - { - my_decimal_set_zero(decimal_value); - return 0; - } - ltime.time_type= mysql_type_to_time_type(field_type()); - return date2my_decimal(<ime, decimal_value); - } - String *res; - if (!(res= str_op_with_null_check(&str_value))) + MYSQL_TIME ltime; + if (date_op_with_null_check(<ime)) { - null_value= 1; - return NULL; + my_decimal_set_zero(decimal_value); + return 0; } - - str2my_decimal(E_DEC_FATAL_ERROR, (char*) res->ptr(), - res->length(), res->charset(), decimal_value); - break; + ltime.time_type= mysql_type_to_time_type(field_type()); + return date2my_decimal(<ime, decimal_value); + } + case STRING_RESULT: + { + String *res= str_op_with_null_check(&str_value); + return res ? decimal_from_string_with_check(decimal_value, res) : 0; } case ROW_RESULT: - case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } return val; } -bool Item_func_hybrid_result_type::get_date(MYSQL_TIME *ltime, +bool Item_func_hybrid_field_type::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) { DBUG_ASSERT(fixed == 1); - switch (cached_result_type) { + switch (Item_func_hybrid_field_type::cmp_type()) { case DECIMAL_RESULT: { my_decimal value, *res; @@ -1120,22 +1031,21 @@ bool Item_func_hybrid_result_type::get_date(MYSQL_TIME *ltime, goto err; break; } + case TIME_RESULT: + return date_op(ltime, + fuzzydate | + (field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0)); case STRING_RESULT: { - if (is_temporal_type(field_type())) - return date_op(ltime, fuzzydate); char buff[40]; String tmp(buff,sizeof(buff), &my_charset_bin),*res; if (!(res= str_op_with_null_check(&tmp)) || str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(), - ltime, fuzzydate) <= MYSQL_TIMESTAMP_ERROR) + ltime, fuzzydate)) goto err; break; - break; } case ROW_RESULT: - case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } @@ -1158,11 +1068,8 @@ void Item_func_signed::print(String *str, enum_query_type query_type) longlong Item_func_signed::val_int_from_str(int *error) { - char buff[MAX_FIELD_WIDTH], *end, *start; - uint32 length; + char buff[MAX_FIELD_WIDTH]; String tmp(buff,sizeof(buff), &my_charset_bin), *res; - longlong value; - CHARSET_INFO *cs; /* For a string result, we must first get the string and then convert it @@ -1176,23 +1083,10 @@ longlong Item_func_signed::val_int_from_str(int *error) return 0; } null_value= 0; - start= (char *)res->ptr(); - length= res->length(); - cs= res->charset(); - - end= start + length; - value= cs->cset->strtoll10(cs, start, &end, error); - if (*error > 0 || end != start+ length) - { - char err_buff[128]; - String err_tmp(err_buff,(uint32) sizeof(err_buff), system_charset_info); - err_tmp.copy(start, length, system_charset_info); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), "INTEGER", - err_tmp.c_ptr()); - } - return value; + Converter_strtoll10_with_warn cnv(NULL, Warn_filter_all(), + res->charset(), res->ptr(), res->length()); + *error= cnv.error(); + return cnv.result(); } @@ -1224,7 +1118,7 @@ longlong Item_func_signed::val_int() return value; err: - push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_UNKNOWN_ERROR, + push_warning(current_thd, Sql_condition::WARN_LEVEL_NOTE, ER_UNKNOWN_ERROR, "Cast to signed converted positive out-of-range integer to " "it's negative complement"); return value; @@ -1280,7 +1174,7 @@ longlong Item_func_unsigned::val_int() return value; err: - push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_UNKNOWN_ERROR, + push_warning(current_thd, Sql_condition::WARN_LEVEL_NOTE, ER_UNKNOWN_ERROR, "Cast to unsigned converted negative integer to it's " "positive complement"); return value; @@ -1348,9 +1242,10 @@ my_decimal *Item_decimal_typecast::val_decimal(my_decimal *dec) return dec; err: - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, - ER(ER_WARN_DATA_OUT_OF_RANGE), + ER_THD(thd, ER_WARN_DATA_OUT_OF_RANGE), name, 1L); return dec; } @@ -1389,10 +1284,11 @@ double Item_double_typecast::val_real() if ((error= truncate_double(&tmp, max_length, decimals, 0, DBL_MAX))) { - push_warning_printf(current_thd, - MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, - ER(ER_WARN_DATA_OUT_OF_RANGE), + ER_THD(thd, ER_WARN_DATA_OUT_OF_RANGE), name, 1); if (error < 0) { @@ -1528,10 +1424,10 @@ my_decimal *Item_func_plus::decimal_op(my_decimal *decimal_value) */ void Item_func_additive_op::result_precision() { - decimals= max(args[0]->decimal_scale(), args[1]->decimal_scale()); + decimals= MY_MAX(args[0]->decimal_scale(), args[1]->decimal_scale()); int arg1_int= args[0]->decimal_precision() - args[0]->decimal_scale(); int arg2_int= args[1]->decimal_precision() - args[1]->decimal_scale(); - int precision= max(arg1_int, arg2_int) + 1 + decimals; + int precision= MY_MAX(arg1_int, arg2_int) + 1 + decimals; DBUG_ASSERT(arg1_int >= 0); DBUG_ASSERT(arg2_int >= 0); @@ -1772,10 +1668,10 @@ void Item_func_mul::result_precision() unsigned_flag= args[0]->unsigned_flag | args[1]->unsigned_flag; else unsigned_flag= args[0]->unsigned_flag & args[1]->unsigned_flag; - decimals= min(args[0]->decimal_scale() + args[1]->decimal_scale(), + decimals= MY_MIN(args[0]->decimal_scale() + args[1]->decimal_scale(), DECIMAL_MAX_SCALE); uint est_prec = args[0]->decimal_precision() + args[1]->decimal_precision(); - uint precision= min(est_prec, DECIMAL_MAX_PRECISION); + uint precision= MY_MIN(est_prec, DECIMAL_MAX_PRECISION); max_length= my_decimal_precision_to_length_no_truncation(precision, decimals, unsigned_flag); } @@ -1837,9 +1733,9 @@ void Item_func_div::result_precision() args[0]->decimal_precision() + // 3 args[1]->divisor_precision_increment() + // 3 prec_increment // 4 - which gives 10 decimals digits. + which gives 10 decimals digits. */ - uint precision=min(args[0]->decimal_precision() + + uint precision=MY_MIN(args[0]->decimal_precision() + args[1]->divisor_precision_increment() + prec_increment, DECIMAL_MAX_PRECISION); @@ -1848,7 +1744,7 @@ void Item_func_div::result_precision() unsigned_flag= args[0]->unsigned_flag | args[1]->unsigned_flag; else unsigned_flag= args[0]->unsigned_flag & args[1]->unsigned_flag; - decimals= min(args[0]->decimal_scale() + prec_increment, DECIMAL_MAX_SCALE); + decimals= MY_MIN(args[0]->decimal_scale() + prec_increment, DECIMAL_MAX_SCALE); max_length= my_decimal_precision_to_length_no_truncation(precision, decimals, unsigned_flag); } @@ -1859,10 +1755,10 @@ void Item_func_div::fix_length_and_dec() DBUG_ENTER("Item_func_div::fix_length_and_dec"); prec_increment= current_thd->variables.div_precincrement; Item_num_op::fix_length_and_dec(); - switch (cached_result_type) { + switch (Item_func_div::result_type()) { case REAL_RESULT: { - decimals=max(args[0]->decimals,args[1]->decimals)+prec_increment; + decimals=MY_MAX(args[0]->decimals,args[1]->decimals)+prec_increment; set_if_smaller(decimals, NOT_FIXED_DEC); uint tmp=float_length(decimals); if (decimals == NOT_FIXED_DEC) @@ -1875,7 +1771,7 @@ void Item_func_div::fix_length_and_dec() break; } case INT_RESULT: - cached_result_type= DECIMAL_RESULT; + set_handler_by_result_type(DECIMAL_RESULT); DBUG_PRINT("info", ("Type changed: DECIMAL_RESULT")); result_precision(); break; @@ -1886,7 +1782,6 @@ void Item_func_div::fix_length_and_dec() case STRING_RESULT: case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } maybe_null= 1; // devision by zero @@ -2055,8 +1950,8 @@ my_decimal *Item_func_mod::decimal_op(my_decimal *decimal_value) void Item_func_mod::result_precision() { - decimals= max(args[0]->decimal_scale(), args[1]->decimal_scale()); - max_length= max(args[0]->max_length, args[1]->max_length); + decimals= MY_MAX(args[0]->decimal_scale(), args[1]->decimal_scale()); + max_length= MY_MAX(args[0]->max_length, args[1]->max_length); } @@ -2122,9 +2017,9 @@ void Item_func_neg::fix_length_and_dec() If this is in integer context keep the context as integer if possible (This is how multiplication and other integer functions works) Use val() to get value as arg_type doesn't mean that item is - Item_int or Item_real due to existence of Item_param. + Item_int or Item_float due to existence of Item_param. */ - if (cached_result_type == INT_RESULT && args[0]->const_item()) + if (Item_func_neg::result_type() == INT_RESULT && args[0]->const_item()) { longlong val= args[0]->val_int(); if ((ulonglong) val >= (ulonglong) LONGLONG_MIN && @@ -2135,7 +2030,7 @@ void Item_func_neg::fix_length_and_dec() Ensure that result is converted to DECIMAL, as longlong can't hold the negated number */ - cached_result_type= DECIMAL_RESULT; + set_handler_by_result_type(DECIMAL_RESULT); DBUG_PRINT("info", ("Type changed: DECIMAL_RESULT")); } } @@ -2384,7 +2279,7 @@ longlong Item_func_shift_left::val_int() return 0; } null_value=0; - return (shift < sizeof(longlong)*8 ? (longlong) res : LL(0)); + return (shift < sizeof(longlong)*8 ? (longlong) res : 0); } longlong Item_func_shift_right::val_int() @@ -2399,7 +2294,7 @@ longlong Item_func_shift_right::val_int() return 0; } null_value=0; - return (shift < sizeof(longlong)*8 ? (longlong) res : LL(0)); + return (shift < sizeof(longlong)*8 ? (longlong) res : 0); } @@ -2415,15 +2310,6 @@ longlong Item_func_bit_neg::val_int() // Conversion functions -void Item_func_integer::fix_length_and_dec() -{ - max_length=args[0]->max_length - args[0]->decimals+1; - uint tmp=float_length(decimals); - set_if_smaller(max_length,tmp); - decimals=0; -} - - void Item_func_int_val::fix_length_and_dec() { DBUG_ENTER("Item_func_int_val::fix_length_and_dec"); @@ -2437,11 +2323,12 @@ void Item_func_int_val::fix_length_and_dec() set_if_smaller(max_length,tmp); decimals= 0; - switch (cached_result_type= args[0]->cast_to_int_type()) + // Note, cast_to_int_type() can return TIME_RESULT + switch (args[0]->cast_to_int_type()) { case STRING_RESULT: case REAL_RESULT: - cached_result_type= REAL_RESULT; + set_handler_by_result_type(REAL_RESULT); max_length= float_length(decimals); break; case INT_RESULT: @@ -2454,22 +2341,21 @@ void Item_func_int_val::fix_length_and_dec() if ((args[0]->max_length - args[0]->decimals) >= (DECIMAL_LONGLONG_DIGITS - 2)) { - cached_result_type= DECIMAL_RESULT; + set_handler_by_result_type(DECIMAL_RESULT); } else { unsigned_flag= args[0]->unsigned_flag; - cached_result_type= INT_RESULT; + set_handler_by_result_type(INT_RESULT); } break; case ROW_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } DBUG_PRINT("info", ("Type: %s", - (cached_result_type == REAL_RESULT ? "REAL_RESULT" : - cached_result_type == DECIMAL_RESULT ? "DECIMAL_RESULT" : - cached_result_type == INT_RESULT ? "INT_RESULT" : + (result_type() == REAL_RESULT ? "REAL_RESULT" : + result_type() == DECIMAL_RESULT ? "DECIMAL_RESULT" : + result_type() == INT_RESULT ? "INT_RESULT" : "--ILLEGAL!!!--"))); DBUG_VOID_RETURN; @@ -2584,10 +2470,10 @@ void Item_func_round::fix_length_and_dec() if (args[0]->result_type() == DECIMAL_RESULT) { max_length++; - cached_result_type= DECIMAL_RESULT; + set_handler_by_result_type(DECIMAL_RESULT); } else - cached_result_type= REAL_RESULT; + set_handler_by_result_type(REAL_RESULT); return; } @@ -2603,40 +2489,41 @@ void Item_func_round::fix_length_and_dec() if (args[0]->decimals == NOT_FIXED_DEC) { - decimals= min(decimals_to_set, NOT_FIXED_DEC); + decimals= MY_MIN(decimals_to_set, NOT_FIXED_DEC); max_length= float_length(decimals); - cached_result_type= REAL_RESULT; + set_handler_by_result_type(REAL_RESULT); return; } switch (args[0]->result_type()) { case REAL_RESULT: case STRING_RESULT: - cached_result_type= REAL_RESULT; - decimals= min(decimals_to_set, NOT_FIXED_DEC); + set_handler_by_result_type(REAL_RESULT); + decimals= MY_MIN(decimals_to_set, NOT_FIXED_DEC); max_length= float_length(decimals); break; case INT_RESULT: if ((!decimals_to_set && truncate) || (args[0]->decimal_precision() < DECIMAL_LONGLONG_DIGITS)) { - int length_can_increase= test(!truncate && (val1 < 0) && !val1_unsigned); + int length_can_increase= MY_TEST(!truncate && (val1 < 0) && + !val1_unsigned); max_length= args[0]->max_length + length_can_increase; /* Here we can keep INT_RESULT */ - cached_result_type= INT_RESULT; + set_handler_by_result_type(INT_RESULT); decimals= 0; break; } /* fall through */ case DECIMAL_RESULT: { - cached_result_type= DECIMAL_RESULT; - decimals_to_set= min(DECIMAL_MAX_SCALE, decimals_to_set); + set_handler_by_result_type(DECIMAL_RESULT); + decimals_to_set= MY_MIN(DECIMAL_MAX_SCALE, decimals_to_set); int decimals_delta= args[0]->decimals - decimals_to_set; int precision= args[0]->decimal_precision(); int length_increase= ((decimals_delta <= 0) || truncate) ? 0:1; precision-= decimals_delta - length_increase; - decimals= min(decimals_to_set, DECIMAL_MAX_SCALE); + decimals= MY_MIN(decimals_to_set, DECIMAL_MAX_SCALE); max_length= my_decimal_precision_to_length_no_truncation(precision, decimals, unsigned_flag); @@ -2644,7 +2531,6 @@ void Item_func_round::fix_length_and_dec() } case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); /* This result type isn't handled */ } } @@ -2750,7 +2636,7 @@ my_decimal *Item_func_round::decimal_op(my_decimal *decimal_value) my_decimal val, *value= args[0]->val_decimal(&val); longlong dec= args[1]->val_int(); if (dec >= 0 || args[1]->unsigned_flag) - dec= min((ulonglong) dec, decimals); + dec= MY_MIN((ulonglong) dec, decimals); else if (dec < INT_MIN) dec= INT_MIN; @@ -2768,7 +2654,20 @@ void Item_func_rand::seed_random(Item *arg) TODO: do not do reinit 'rand' for every execute of PS/SP if args[0] is a constant. */ - uint32 tmp= (uint32) arg->val_int(); + uint32 tmp; +#ifdef WITH_WSREP + THD *thd= current_thd; + if (WSREP(thd)) + { + if (thd->wsrep_exec_mode==REPL_RECV) + tmp= thd->wsrep_rand; + else + tmp= thd->wsrep_rand= (uint32) arg->val_int(); + } + else +#endif /* WITH_WSREP */ + tmp= (uint32) arg->val_int(); + my_rnd_init(rand, (uint32) (tmp*0x10001L+55555555L), (uint32) (tmp*0x10000001L)); } @@ -2860,46 +2759,117 @@ double Item_func_units::val_real() void Item_func_min_max::fix_length_and_dec() { + uint unsigned_count= 0; int max_int_part=0; decimals=0; max_length=0; maybe_null=0; - cmp_type=args[0]->result_type(); + Item_result tmp_cmp_type= args[0]->cmp_type(); + uint string_type_count= 0; + uint temporal_type_count= 0; + enum_field_types temporal_field_type= MYSQL_TYPE_DATETIME; for (uint i=0 ; i < arg_count ; i++) { set_if_bigger(max_length, args[i]->max_length); set_if_bigger(decimals, args[i]->decimals); set_if_bigger(max_int_part, args[i]->decimal_int_part()); + unsigned_count+= args[i]->unsigned_flag; if (args[i]->maybe_null) maybe_null= 1; - cmp_type= item_cmp_type(cmp_type,args[i]->result_type()); + tmp_cmp_type= item_cmp_type(tmp_cmp_type, args[i]->cmp_type()); + string_type_count+= args[i]->cmp_type() == STRING_RESULT; + if (args[i]->cmp_type() == TIME_RESULT) + { + if (!temporal_type_count) + temporal_field_type= args[i]->field_type(); + else + temporal_field_type= Field::field_type_merge(temporal_field_type, + args[i]->field_type()); + temporal_type_count++; + } } - if (cmp_type == STRING_RESULT) + unsigned_flag= unsigned_count == arg_count; // if all args are unsigned + + switch (tmp_cmp_type) { + case TIME_RESULT: + // At least one temporal argument was found. + if (temporal_type_count < arg_count) + maybe_null= true; // Non-temporal-to-temporal conversion can return NULL + collation.set_numeric(); + set_handler_by_field_type(temporal_field_type); + if (is_temporal_type_with_time(temporal_field_type)) + set_if_smaller(decimals, TIME_SECOND_PART_DIGITS); + else + decimals= 0; + break; + + case STRING_RESULT: + /* + All arguments are of string-alike types: + CHAR, VARCHAR, TEXT, BINARY, VARBINARY, BLOB, SET, ENUM + No numeric and no temporal types were found. + */ agg_arg_charsets_for_string_result_with_comparison(collation, args, arg_count); - else if ((cmp_type == DECIMAL_RESULT) || (cmp_type == INT_RESULT)) - { + set_handler_by_field_type(agg_field_type(args, arg_count, false)); + break; + + case INT_RESULT: + /* + All arguments have INT-alike types: + TINY, SHORT, LONG, LONGLONG, INT24, YEAR, BIT. + */ collation.set_numeric(); fix_char_length(my_decimal_precision_to_length_no_truncation(max_int_part + decimals, decimals, unsigned_flag)); - } - else if (cmp_type == REAL_RESULT) - fix_char_length(float_length(decimals)); - - compare_as_dates= find_date_time_item(args, arg_count, 0); - if (compare_as_dates) - { - cached_field_type= compare_as_dates->field_type(); - if (mysql_type_to_time_type(cached_field_type) == MYSQL_TIMESTAMP_DATE) - decimals= 0; + if (unsigned_count != 0 && unsigned_count != arg_count) + { + /* + If all args are of INT-alike type, but have different unsigned_flag, + then change type to DECIMAL. + */ + set_handler_by_field_type(MYSQL_TYPE_NEWDECIMAL); + } else - set_if_smaller(decimals, TIME_SECOND_PART_DIGITS); + { + /* + There are only INT-alike arguments with equal unsigned_flag. + Aggregate types to get the best covering type. + Treat BIT as LONGLONG when aggregating to non-BIT types. + Possible final type: TINY, SHORT, LONG, LONGLONG, INT24, YEAR, BIT. + */ + set_handler_by_field_type(agg_field_type(args, arg_count, true)); + } + break; + + case DECIMAL_RESULT: + // All arguments are of DECIMAL type + collation.set_numeric(); + fix_char_length(my_decimal_precision_to_length_no_truncation(max_int_part + + decimals, + decimals, + unsigned_flag)); + set_handler_by_field_type(MYSQL_TYPE_NEWDECIMAL); + break; + + case ROW_RESULT: + DBUG_ASSERT(0); + // Pass through + case REAL_RESULT: + collation.set_numeric(); + fix_char_length(float_length(decimals)); + /* + Set type to DOUBLE, as Item_func::create_tmp_field() does not + distinguish between DOUBLE and FLOAT and always creates Field_double. + Perhaps we should eventually change this to use agg_field_type() here, + and fix Item_func::create_tmp_field() to create Field_float when possible. + */ + set_handler_by_field_type(MYSQL_TYPE_DOUBLE); + break; } - else - cached_field_type= agg_field_type(args, arg_count); } @@ -2929,14 +2899,12 @@ bool Item_func_min_max::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) for example, SELECT MONTH(GREATEST("2011-11-21", "2010-10-09")) */ - if (!compare_as_dates) + if (Item_func_min_max::cmp_type() != TIME_RESULT) return Item_func::get_date(ltime, fuzzy_date); for (uint i=0; i < arg_count ; i++) { - Item **arg= args + i; - bool is_null; - longlong res= get_datetime_value(0, &arg, 0, compare_as_dates, &is_null); + longlong res= args[i]->val_temporal_packed(Item_func_min_max::field_type()); /* Check if we need to stop (because of error or KILL) and stop the loop */ if (args[i]->null_value) @@ -2947,19 +2915,18 @@ bool Item_func_min_max::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) } unpack_time(min_max, ltime); - enum_field_types ftype= compare_as_dates->field_type(); - if (ftype == MYSQL_TYPE_DATE || ftype == MYSQL_TYPE_NEWDATE) + if (Item_func_min_max::field_type() == MYSQL_TYPE_DATE) { ltime->time_type= MYSQL_TIMESTAMP_DATE; ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0; } - else if (ftype == MYSQL_TYPE_TIME) + else if (Item_func_min_max::field_type() == MYSQL_TYPE_TIME) { ltime->time_type= MYSQL_TIMESTAMP_TIME; ltime->hour+= (ltime->month * 32 + ltime->day) * 24; ltime->year= ltime->month= ltime->day= 0; if (adjust_time_range_with_warn(ltime, - min(decimals, TIME_SECOND_PART_DIGITS))) + std::min<uint>(decimals, TIME_SECOND_PART_DIGITS))) return (null_value= true); } @@ -2975,9 +2942,9 @@ bool Item_func_min_max::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) String *Item_func_min_max::val_str(String *str) { DBUG_ASSERT(fixed == 1); - if (compare_as_dates) + if (Item_func_min_max::cmp_type() == TIME_RESULT) return val_string_from_date(str); - switch (cmp_type) { + switch (Item_func_min_max::result_type()) { case INT_RESULT: return val_string_from_int(str); case DECIMAL_RESULT: @@ -3010,7 +2977,6 @@ String *Item_func_min_max::val_str(String *str) } case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // This case should never be chosen return 0; } @@ -3022,7 +2988,7 @@ double Item_func_min_max::val_real() { DBUG_ASSERT(fixed == 1); double value=0.0; - if (compare_as_dates) + if (Item_func_min_max::cmp_type() == TIME_RESULT) { MYSQL_TIME ltime; if (get_date(<ime, 0)) @@ -3051,7 +3017,7 @@ longlong Item_func_min_max::val_int() { DBUG_ASSERT(fixed == 1); longlong value=0; - if (compare_as_dates) + if (Item_func_min_max::cmp_type() == TIME_RESULT) { MYSQL_TIME ltime; if (get_date(<ime, 0)) @@ -3081,7 +3047,7 @@ my_decimal *Item_func_min_max::val_decimal(my_decimal *dec) DBUG_ASSERT(fixed == 1); my_decimal tmp_buf, *tmp, *UNINIT_VAR(res); - if (compare_as_dates) + if (Item_func_min_max::cmp_type() == TIME_RESULT) { MYSQL_TIME ltime; if (get_date(<ime, 0)) @@ -3346,7 +3312,7 @@ void Item_func_find_in_set::fix_length_and_dec() find->length(), 0); enum_bit=0; if (enum_value) - enum_bit=LL(1) << (enum_value-1); + enum_bit=1LL << (enum_value-1); } } } @@ -3427,7 +3393,7 @@ longlong Item_func_find_in_set::val_int() wc == (my_wc_t) separator) return (longlong) ++position; else - return LL(0); + return 0; } } return 0; @@ -3473,7 +3439,7 @@ void udf_handler::cleanup() bool -udf_handler::fix_fields(THD *thd, Item_result_field *func, +udf_handler::fix_fields(THD *thd, Item_func_or_sum *func, uint arg_count, Item **arguments) { uchar buff[STACK_BUFF_ALLOC]; // Max argument in function @@ -3494,13 +3460,12 @@ udf_handler::fix_fields(THD *thd, Item_result_field *func, /* Fix all arguments */ func->maybe_null=0; - used_tables_cache=0; - const_item_cache=1; + func->used_tables_and_const_cache_init(); if ((f_args.arg_count=arg_count)) { if (!(f_args.arg_type= (Item_result*) - sql_alloc(f_args.arg_count*sizeof(Item_result)))) + thd->alloc(f_args.arg_count*sizeof(Item_result)))) { free_udf(u_d); @@ -3536,20 +3501,21 @@ udf_handler::fix_fields(THD *thd, Item_result_field *func, func->maybe_null=1; func->with_sum_func= func->with_sum_func || item->with_sum_func; func->with_field= func->with_field || item->with_field; + func->with_param= func->with_param || item->with_param; func->with_subselect|= item->with_subselect; - used_tables_cache|=item->used_tables(); - const_item_cache&=item->const_item(); + func->used_tables_and_const_cache_join(item); f_args.arg_type[i]=item->result_type(); } //TODO: why all following memory is not allocated with 1 call of sql_alloc? if (!(buffers=new String[arg_count]) || - !(f_args.args= (char**) sql_alloc(arg_count * sizeof(char *))) || - !(f_args.lengths= (ulong*) sql_alloc(arg_count * sizeof(long))) || - !(f_args.maybe_null= (char*) sql_alloc(arg_count * sizeof(char))) || - !(num_buffer= (char*) sql_alloc(arg_count * + !(f_args.args= (char**) thd->alloc(arg_count * sizeof(char *))) || + !(f_args.lengths= (ulong*) thd->alloc(arg_count * sizeof(long))) || + !(f_args.maybe_null= (char*) thd->alloc(arg_count * sizeof(char))) || + !(num_buffer= (char*) thd->alloc(arg_count * ALIGN_SIZE(sizeof(double)))) || - !(f_args.attributes= (char**) sql_alloc(arg_count * sizeof(char *))) || - !(f_args.attribute_lengths= (ulong*) sql_alloc(arg_count * + !(f_args.attributes= (char**) thd->alloc(arg_count * + sizeof(char *))) || + !(f_args.attribute_lengths= (ulong*) thd->alloc(arg_count * sizeof(long)))) { free_udf(u_d); @@ -3559,7 +3525,7 @@ udf_handler::fix_fields(THD *thd, Item_result_field *func, func->fix_length_and_dec(); initid.max_length=func->max_length; initid.maybe_null=func->maybe_null; - initid.const_item=const_item_cache; + initid.const_item=func->const_item_cache; initid.decimals=func->decimals; initid.ptr=0; @@ -3609,7 +3575,6 @@ udf_handler::fix_fields(THD *thd, Item_result_field *func, break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -3623,22 +3588,21 @@ udf_handler::fix_fields(THD *thd, Item_result_field *func, free_udf(u_d); DBUG_RETURN(TRUE); } - func->max_length=min(initid.max_length,MAX_BLOB_WIDTH); + func->max_length=MY_MIN(initid.max_length,MAX_BLOB_WIDTH); func->maybe_null=initid.maybe_null; - const_item_cache=initid.const_item; - /* - Keep used_tables_cache in sync with const_item_cache. - See the comment in Item_udf_func::update_used tables. - */ - if (!const_item_cache && !used_tables_cache) - used_tables_cache= RAND_TABLE_BIT; - func->decimals=min(initid.decimals,NOT_FIXED_DEC); + /* + The above call for init() can reset initid.const_item to "false", + e.g. when the UDF function wants to be non-deterministic. + See sequence_init() in udf_example.cc. + */ + func->const_item_cache= initid.const_item; + func->decimals=MY_MIN(initid.decimals,NOT_FIXED_DEC); } initialized=1; if (error) { my_error(ER_CANT_INITIALIZE_UDF, MYF(0), - u_d->name.str, ER(ER_UNKNOWN_ERROR)); + u_d->name.str, ER_THD(thd, ER_UNKNOWN_ERROR)); DBUG_RETURN(TRUE); } DBUG_RETURN(FALSE); @@ -3688,7 +3652,6 @@ bool udf_handler::get_arguments() break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -3931,144 +3894,91 @@ udf_handler::~udf_handler() bool udf_handler::get_arguments() { return 0; } #endif /* HAVE_DLOPEN */ -/* -** User level locks -*/ - -mysql_mutex_t LOCK_user_locks; -static HASH hash_user_locks; -class User_level_lock +longlong Item_master_pos_wait::val_int() { - uchar *key; - size_t key_length; - -public: - int count; - bool locked; - mysql_cond_t cond; - my_thread_id thread_id; - void set_thread(THD *thd) { thread_id= thd->thread_id; } + DBUG_ASSERT(fixed == 1); + THD* thd = current_thd; + String *log_name = args[0]->val_str(&value); + int event_count= 0; - User_level_lock(const uchar *key_arg,uint length, ulong id) - :key_length(length),count(1),locked(1), thread_id(id) + null_value=0; + if (thd->slave_thread || !log_name || !log_name->length()) { - key= (uchar*) my_memdup(key_arg,length,MYF(0)); - mysql_cond_init(key_user_level_lock_cond, &cond, NULL); - if (key) - { - if (my_hash_insert(&hash_user_locks,(uchar*) this)) - { - my_free(key); - key=0; - } - } + null_value = 1; + return 0; } - ~User_level_lock() - { - if (key) +#ifdef HAVE_REPLICATION + longlong pos = (ulong)args[1]->val_int(); + longlong timeout = (arg_count>=3) ? args[2]->val_int() : 0 ; + String connection_name_buff; + LEX_STRING connection_name; + Master_info *mi= NULL; + if (arg_count >= 4) + { + String *con; + if (!(con= args[3]->val_str(&connection_name_buff))) + goto err; + + connection_name.str= (char*) con->ptr(); + connection_name.length= con->length(); + if (check_master_connection_name(&connection_name)) { - my_hash_delete(&hash_user_locks,(uchar*) this); - my_free(key); + my_error(ER_WRONG_ARGUMENTS, MYF(ME_JUST_WARNING), + "MASTER_CONNECTION_NAME"); + goto err; } - mysql_cond_destroy(&cond); } - inline bool initialized() { return key != 0; } - friend void item_user_lock_release(User_level_lock *ull); - friend uchar *ull_get_key(const User_level_lock *ull, size_t *length, - my_bool not_used); -}; - -uchar *ull_get_key(const User_level_lock *ull, size_t *length, - my_bool not_used __attribute__((unused))) -{ - *length= ull->key_length; - return ull->key; -} - -#ifdef HAVE_PSI_INTERFACE -static PSI_mutex_key key_LOCK_user_locks; - -static PSI_mutex_info all_user_mutexes[]= -{ - { &key_LOCK_user_locks, "LOCK_user_locks", PSI_FLAG_GLOBAL} -}; - -static void init_user_lock_psi_keys(void) -{ - const char* category= "sql"; - int count; - - if (PSI_server == NULL) - return; - - count= array_elements(all_user_mutexes); - PSI_server->register_mutex(category, all_user_mutexes, count); -} -#endif + else + connection_name= thd->variables.default_master_connection; -static bool item_user_lock_inited= 0; + if (!(mi= get_master_info(&connection_name, Sql_condition::WARN_LEVEL_WARN))) + goto err; -void item_user_lock_init(void) -{ -#ifdef HAVE_PSI_INTERFACE - init_user_lock_psi_keys(); + if ((event_count = mi->rli.wait_for_pos(thd, log_name, pos, timeout)) == -2) + { + null_value = 1; + event_count=0; + } + mi->release(); #endif + return event_count; - mysql_mutex_init(key_LOCK_user_locks, &LOCK_user_locks, MY_MUTEX_INIT_SLOW); - my_hash_init(&hash_user_locks,system_charset_info, - 16,0,0,(my_hash_get_key) ull_get_key,NULL,0); - item_user_lock_inited= 1; -} - -void item_user_lock_free(void) -{ - if (item_user_lock_inited) +#ifdef HAVE_REPLICATION +err: { - item_user_lock_inited= 0; - my_hash_free(&hash_user_locks); - mysql_mutex_destroy(&LOCK_user_locks); + null_value = 1; + return 0; } +#endif } -void item_user_lock_release(User_level_lock *ull) -{ - ull->locked=0; - ull->thread_id= 0; - if (--ull->count) - mysql_cond_signal(&ull->cond); - else - delete ull; -} - -/** - Wait until we are at or past the given position in the master binlog - on the slave. -*/ -longlong Item_master_pos_wait::val_int() +longlong Item_master_gtid_wait::val_int() { DBUG_ASSERT(fixed == 1); - THD* thd = current_thd; - String *log_name = args[0]->val_str(&value); - int event_count= 0; + longlong result= 0; + String *gtid_pos __attribute__((unused)) = args[0]->val_str(&value); - null_value=0; - if (thd->slave_thread || !log_name || !log_name->length()) + if (args[0]->null_value) { - null_value = 1; + null_value= 1; return 0; } + + null_value=0; #ifdef HAVE_REPLICATION - longlong pos = (ulong)args[1]->val_int(); - longlong timeout = (arg_count==3) ? args[2]->val_int() : 0 ; - if ((event_count = active_mi->rli.wait_for_pos(thd, log_name, pos, timeout)) == -2) - { - null_value = 1; - event_count=0; - } + THD* thd= current_thd; + longlong timeout_us; + + if (arg_count==2 && !args[1]->null_value) + timeout_us= (longlong)(1e6*args[1]->val_real()); + else + timeout_us= (longlong)-1; + + result= rpl_global_gtid_waiting.wait_for_pos(thd, gtid_pos, timeout_us); #endif - return event_count; + return result; } @@ -4114,7 +4024,7 @@ class Interruptible_wait /** Time to wait before polling the connection status. */ -const ulonglong Interruptible_wait::m_interrupt_interval= 5 * ULL(1000000000); +const ulonglong Interruptible_wait::m_interrupt_interval= 5 * 1000000000ULL; /** @@ -4159,7 +4069,140 @@ int Interruptible_wait::wait(mysql_cond_t *cond, mysql_mutex_t *mutex) /** - Get a user level lock. If the thread has an old lock this is first released. + For locks with EXPLICIT duration, MDL returns a new ticket + every time a lock is granted. This allows to implement recursive + locks without extra allocation or additional data structures, such + as below. However, if there are too many tickets in the same + MDL_context, MDL_context::find_ticket() is getting too slow, + since it's using a linear search. + This is why a separate structure is allocated for a user + level lock, and before requesting a new lock from MDL, + GET_LOCK() checks thd->ull_hash if such lock is already granted, + and if so, simply increments a reference counter. +*/ + +class User_level_lock +{ +public: + MDL_ticket *lock; + int refs; +}; + + +/** Extract a hash key from User_level_lock. */ + +uchar *ull_get_key(const uchar *ptr, size_t *length, + my_bool not_used __attribute__((unused))) +{ + User_level_lock *ull = (User_level_lock*) ptr; + MDL_key *key = ull->lock->get_key(); + *length= key->length(); + return (uchar*) key->ptr(); +} + + +/** + Release all user level locks for this THD. +*/ + +void mysql_ull_cleanup(THD *thd) +{ + User_level_lock *ull; + DBUG_ENTER("mysql_ull_cleanup"); + + for (uint i= 0; i < thd->ull_hash.records; i++) + { + ull = (User_level_lock*) my_hash_element(&thd->ull_hash, i); + thd->mdl_context.release_lock(ull->lock); + my_free(ull); + } + + my_hash_free(&thd->ull_hash); + + DBUG_VOID_RETURN; +} + + +/** + Set explicit duration for metadata locks corresponding to + user level locks to protect them from being released at the end + of transaction. +*/ + +void mysql_ull_set_explicit_lock_duration(THD *thd) +{ + User_level_lock *ull; + DBUG_ENTER("mysql_ull_set_explicit_lock_duration"); + + for (uint i= 0; i < thd->ull_hash.records; i++) + { + ull= (User_level_lock*) my_hash_element(&thd->ull_hash, i); + thd->mdl_context.set_lock_duration(ull->lock, MDL_EXPLICIT); + } + DBUG_VOID_RETURN; +} + + +/** + When MDL detects a lock wait timeout, it pushes + an error into the statement diagnostics area. + For GET_LOCK(), lock wait timeout is not an error, + but a special return value (0). + Similarly, killing get_lock wait is not an error either, + but a return value NULL. + Capture and suppress lock wait timeouts and kills. +*/ + +class Lock_wait_timeout_handler: public Internal_error_handler +{ +public: + Lock_wait_timeout_handler() :m_lock_wait_timeout(false) {} + + bool m_lock_wait_timeout; + + bool handle_condition(THD * /* thd */, uint sql_errno, + const char * /* sqlstate */, + Sql_condition::enum_warning_level /* level */, + const char *message, + Sql_condition ** /* cond_hdl */); +}; + +bool +Lock_wait_timeout_handler:: +handle_condition(THD *thd, uint sql_errno, + const char * /* sqlstate */, + Sql_condition::enum_warning_level /* level */, + const char *message, + Sql_condition ** /* cond_hdl */) +{ + if (sql_errno == ER_LOCK_WAIT_TIMEOUT) + { + m_lock_wait_timeout= true; + return true; /* condition handled */ + } + if (thd->is_killed()) + return true; + + return false; +} + + +static int ull_name_ok(String *name) +{ + if (!name || !name->length()) + return 0; + + if (name->length() > NAME_LEN) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), name->c_ptr_safe()); + return 0; + } + return 1; +} + + +/** + Get a user level lock. @retval 1 : Got lock @@ -4172,14 +4215,13 @@ int Interruptible_wait::wait(mysql_cond_t *cond, mysql_mutex_t *mutex) longlong Item_func_get_lock::val_int() { DBUG_ASSERT(fixed == 1); - String *res=args[0]->val_str(&value); - ulonglong timeout= args[1]->val_int(); - THD *thd=current_thd; + String *res= args[0]->val_str(&value); + double timeout= args[1]->val_real(); + THD *thd= current_thd; User_level_lock *ull; - int error; - Interruptible_wait timed_cond(thd); DBUG_ENTER("Item_func_get_lock::val_int"); + null_value= 1; /* In slave thread no need to get locks, everything is serialized. Anyway there is no way to make GET_LOCK() work on slave like it did on master @@ -4201,109 +4243,71 @@ longlong Item_func_get_lock::val_int() strmov(buf, "NULL"); else llstr(((longlong) timeout), buf); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_VALUE_FOR_TYPE, ER(ER_WRONG_VALUE_FOR_TYPE), "timeout", buf, "get_lock"); null_value= 1; DBUG_RETURN(0); } - mysql_mutex_lock(&LOCK_user_locks); - - if (!res || !res->length()) + if (!ull_name_ok(res)) + DBUG_RETURN(0); + DBUG_PRINT("enter", ("lock: %.*s", res->length(), res->ptr())); + /* HASH entries are of type User_level_lock. */ + if (! my_hash_inited(&thd->ull_hash) && + my_hash_init(&thd->ull_hash, &my_charset_bin, + 16 /* small hash */, 0, 0, ull_get_key, NULL, 0)) { - mysql_mutex_unlock(&LOCK_user_locks); - null_value=1; DBUG_RETURN(0); } - DBUG_PRINT("info", ("lock %.*s, thd=%ld", res->length(), res->ptr(), - (long) thd->real_id)); - null_value=0; - if (thd->ull) - { - item_user_lock_release(thd->ull); - thd->ull=0; - } + MDL_request ull_request; + ull_request.init(MDL_key::USER_LOCK, res->c_ptr_safe(), "", + MDL_SHARED_NO_WRITE, MDL_EXPLICIT); + MDL_key *ull_key = &ull_request.key; - if (!(ull= ((User_level_lock *) my_hash_search(&hash_user_locks, - (uchar*) res->ptr(), - (size_t) res->length())))) + + if ((ull= (User_level_lock*) + my_hash_search(&thd->ull_hash, ull_key->ptr(), ull_key->length()))) { - ull= new User_level_lock((uchar*) res->ptr(), (size_t) res->length(), - thd->thread_id); - if (!ull || !ull->initialized()) - { - delete ull; - mysql_mutex_unlock(&LOCK_user_locks); - null_value=1; // Probably out of memory - DBUG_RETURN(0); - } - ull->set_thread(thd); - thd->ull=ull; - mysql_mutex_unlock(&LOCK_user_locks); - DBUG_PRINT("info", ("made new lock")); - DBUG_RETURN(1); // Got new lock + /* Recursive lock */ + ull->refs++; + null_value = 0; + DBUG_PRINT("info", ("recursive lock, ref-count: %d", (int) ull->refs)); + DBUG_RETURN(1); } - ull->count++; - DBUG_PRINT("info", ("ull->count=%d", ull->count)); - - /* - Structure is now initialized. Try to get the lock. - Set up control struct to allow others to abort locks. - */ - thd_proc_info(thd, "User lock"); - thd->mysys_var->current_mutex= &LOCK_user_locks; - thd->mysys_var->current_cond= &ull->cond; - timed_cond.set_timeout(timeout * ULL(1000000000)); - - error= 0; - thd_wait_begin(thd, THD_WAIT_USER_LOCK); - while (ull->locked && !thd->killed) + Lock_wait_timeout_handler lock_wait_timeout_handler; + thd->push_internal_handler(&lock_wait_timeout_handler); + bool error= thd->mdl_context.acquire_lock(&ull_request, timeout); + (void) thd->pop_internal_handler(); + if (error) { - DBUG_PRINT("info", ("waiting on lock")); - error= timed_cond.wait(&ull->cond, &LOCK_user_locks); - if (error == ETIMEDOUT || error == ETIME) - { - DBUG_PRINT("info", ("lock wait timeout")); - break; - } - error= 0; + if (lock_wait_timeout_handler.m_lock_wait_timeout) + null_value= 0; + DBUG_RETURN(0); } - thd_wait_end(thd); - if (ull->locked) + ull= (User_level_lock*) my_malloc(sizeof(User_level_lock), + MYF(MY_WME|MY_THREAD_SPECIFIC)); + if (ull == NULL) { - if (!--ull->count) - { - DBUG_ASSERT(0); - delete ull; // Should never happen - } - if (!error) // Killed (thd->killed != 0) - { - error=1; - null_value=1; // Return NULL - } + thd->mdl_context.release_lock(ull_request.ticket); + DBUG_RETURN(0); } - else // We got the lock + + ull->lock= ull_request.ticket; + ull->refs= 1; + + if (my_hash_insert(&thd->ull_hash, (uchar*) ull)) { - ull->locked=1; - ull->set_thread(thd); - ull->thread_id= thd->thread_id; - thd->ull=ull; - error=0; - DBUG_PRINT("info", ("got the lock")); + thd->mdl_context.release_lock(ull->lock); + my_free(ull); + DBUG_RETURN(0); } - mysql_mutex_unlock(&LOCK_user_locks); - - mysql_mutex_lock(&thd->mysys_var->mutex); - thd_proc_info(thd, 0); - thd->mysys_var->current_mutex= 0; - thd->mysys_var->current_cond= 0; - mysql_mutex_unlock(&thd->mysys_var->mutex); + null_value= 0; - DBUG_RETURN(!error ? 1 : 0); + DBUG_RETURN(1); } @@ -4318,43 +4322,87 @@ longlong Item_func_get_lock::val_int() longlong Item_func_release_lock::val_int() { DBUG_ASSERT(fixed == 1); - String *res=args[0]->val_str(&value); - User_level_lock *ull; - longlong result; - THD *thd=current_thd; + String *res= args[0]->val_str(&value); + THD *thd= current_thd; DBUG_ENTER("Item_func_release_lock::val_int"); - if (!res || !res->length()) - { - null_value=1; + null_value= 1; + + if (!ull_name_ok(res)) DBUG_RETURN(0); - } - DBUG_PRINT("info", ("lock %.*s", res->length(), res->ptr())); - null_value=0; - result=0; - mysql_mutex_lock(&LOCK_user_locks); - if (!(ull= ((User_level_lock*) my_hash_search(&hash_user_locks, - (const uchar*) res->ptr(), - (size_t) res->length())))) + DBUG_PRINT("enter", ("lock: %.*s", res->length(), res->ptr())); + + MDL_key ull_key; + ull_key.mdl_key_init(MDL_key::USER_LOCK, res->c_ptr_safe(), ""); + + User_level_lock *ull; + + if (!(ull= + (User_level_lock*) my_hash_search(&thd->ull_hash, + ull_key.ptr(), ull_key.length()))) { - null_value=1; + null_value= thd->mdl_context.get_lock_owner(&ull_key) == 0; + DBUG_RETURN(0); } - else + DBUG_PRINT("info", ("ref count: %d", (int) ull->refs)); + null_value= 0; + if (--ull->refs == 0) { - DBUG_PRINT("info", ("ull->locked=%d ull->thread=%lu thd=%lu", - (int) ull->locked, - (long)ull->thread_id, - (long)thd->thread_id)); - if (ull->locked && current_thd->thread_id == ull->thread_id) - { - DBUG_PRINT("info", ("release lock")); - result=1; // Release is ok - item_user_lock_release(ull); - thd->ull=0; - } + my_hash_delete(&thd->ull_hash, (uchar*) ull); + thd->mdl_context.release_lock(ull->lock); + my_free(ull); } - mysql_mutex_unlock(&LOCK_user_locks); - DBUG_RETURN(result); + DBUG_RETURN(1); +} + + +/** + Check a user level lock. + + Sets null_value=TRUE on error. + + @retval + 1 Available + @retval + 0 Already taken, or error +*/ + +longlong Item_func_is_free_lock::val_int() +{ + DBUG_ASSERT(fixed == 1); + String *res= args[0]->val_str(&value); + THD *thd= current_thd; + null_value= 1; + + if (!ull_name_ok(res)) + return 0; + + MDL_key ull_key; + ull_key.mdl_key_init(MDL_key::USER_LOCK, res->c_ptr_safe(), ""); + + null_value= 0; + return thd->mdl_context.get_lock_owner(&ull_key) == 0; +} + + +longlong Item_func_is_used_lock::val_int() +{ + DBUG_ASSERT(fixed == 1); + String *res= args[0]->val_str(&value); + THD *thd= current_thd; + null_value= 1; + + if (!ull_name_ok(res)) + return 0; + + MDL_key ull_key; + ull_key.mdl_key_init(MDL_key::USER_LOCK, res->c_ptr_safe(), ""); + ulong thread_id = thd->mdl_context.get_lock_owner(&ull_key); + if (thread_id == 0) + return 0; + + null_value= 0; + return thread_id; } @@ -4397,7 +4445,7 @@ longlong Item_func_benchmark::val_int() char buff[MAX_FIELD_WIDTH]; String tmp(buff,sizeof(buff), &my_charset_bin); my_decimal tmp_decimal; - THD *thd=current_thd; + THD *thd= current_thd; ulonglong loop_count; loop_count= (ulonglong) args[0]->val_int(); @@ -4409,8 +4457,9 @@ longlong Item_func_benchmark::val_int() { char buff[22]; llstr(((longlong) loop_count), buff); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WRONG_VALUE_FOR_TYPE, ER(ER_WRONG_VALUE_FOR_TYPE), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_VALUE_FOR_TYPE, + ER_THD(thd, ER_WRONG_VALUE_FOR_TYPE), "count", buff, "benchmark"); } @@ -4436,7 +4485,6 @@ longlong Item_func_benchmark::val_int() break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // This case should never be chosen return 0; } @@ -4455,6 +4503,54 @@ void Item_func_benchmark::print(String *str, enum_query_type query_type) } +mysql_mutex_t LOCK_item_func_sleep; + +#ifdef HAVE_PSI_INTERFACE +static PSI_mutex_key key_LOCK_item_func_sleep; + +static PSI_mutex_info item_func_sleep_mutexes[]= +{ + { &key_LOCK_item_func_sleep, "LOCK_user_locks", PSI_FLAG_GLOBAL} +}; + + +static void init_item_func_sleep_psi_keys(void) +{ + const char* category= "sql"; + int count; + + if (PSI_server == NULL) + return; + + count= array_elements(item_func_sleep_mutexes); + PSI_server->register_mutex(category, item_func_sleep_mutexes, count); +} +#endif + +static bool item_func_sleep_inited= 0; + + +void item_func_sleep_init(void) +{ +#ifdef HAVE_PSI_INTERFACE + init_item_func_sleep_psi_keys(); +#endif + + mysql_mutex_init(key_LOCK_item_func_sleep, &LOCK_item_func_sleep, MY_MUTEX_INIT_SLOW); + item_func_sleep_inited= 1; +} + + +void item_func_sleep_free(void) +{ + if (item_func_sleep_inited) + { + item_func_sleep_inited= 0; + mysql_mutex_destroy(&LOCK_item_func_sleep); + } +} + + /** This function is just used to create tests with time gaps. */ longlong Item_func_sleep::val_int() @@ -4483,24 +4579,23 @@ longlong Item_func_sleep::val_int() timed_cond.set_timeout((ulonglong) (timeout * 1000000000.0)); mysql_cond_init(key_item_func_sleep_cond, &cond, NULL); - mysql_mutex_lock(&LOCK_user_locks); + mysql_mutex_lock(&LOCK_item_func_sleep); - thd_proc_info(thd, "User sleep"); - thd->mysys_var->current_mutex= &LOCK_user_locks; + THD_STAGE_INFO(thd, stage_user_sleep); + thd->mysys_var->current_mutex= &LOCK_item_func_sleep; thd->mysys_var->current_cond= &cond; error= 0; thd_wait_begin(thd, THD_WAIT_SLEEP); while (!thd->killed) { - error= timed_cond.wait(&cond, &LOCK_user_locks); + error= timed_cond.wait(&cond, &LOCK_item_func_sleep); if (error == ETIMEDOUT || error == ETIME) break; error= 0; } thd_wait_end(thd); - thd_proc_info(thd, 0); - mysql_mutex_unlock(&LOCK_user_locks); + mysql_mutex_unlock(&LOCK_item_func_sleep); mysql_mutex_lock(&thd->mysys_var->mutex); thd->mysys_var->current_mutex= 0; thd->mysys_var->current_cond= 0; @@ -4513,13 +4608,13 @@ longlong Item_func_sleep::val_int() (thd, STRING_WITH_LEN("dispatch_command_end SIGNAL query_done")); };); - return test(!error); // Return 1 killed + return MY_TEST(!error); // Return 1 killed } #define extra_size sizeof(double) -static user_var_entry *get_variable(HASH *hash, LEX_STRING &name, +user_var_entry *get_variable(HASH *hash, LEX_STRING &name, bool create_if_not_exists) { user_var_entry *entry; @@ -4531,7 +4626,9 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name, uint size=ALIGN_SIZE(sizeof(user_var_entry))+name.length+1+extra_size; if (!my_hash_inited(hash)) return 0; - if (!(entry = (user_var_entry*) my_malloc(size,MYF(MY_WME | ME_FATALERROR)))) + if (!(entry = (user_var_entry*) my_malloc(size, + MYF(MY_WME | ME_FATALERROR | + MY_THREAD_SPECIFIC)))) return 0; entry->name.str=(char*) entry+ ALIGN_SIZE(sizeof(user_var_entry))+ extra_size; @@ -4539,7 +4636,7 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name, entry->value=0; entry->length=0; entry->update_query_id=0; - entry->collation.set(NULL, DERIVATION_IMPLICIT, 0); + entry->set_charset(NULL); entry->unsigned_flag= 0; /* If we are here, we were called from a SET or a query which sets a @@ -4567,15 +4664,15 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name, void Item_func_set_user_var::cleanup() { Item_func::cleanup(); - entry= NULL; + m_var_entry= NULL; } bool Item_func_set_user_var::set_entry(THD *thd, bool create_if_not_exists) { - if (entry && thd->thread_id == entry_thread_id) + if (m_var_entry && thd->thread_id == entry_thread_id) goto end; // update entry->update_query_id for PS - if (!(entry= get_variable(&thd->user_vars, name, create_if_not_exists))) + if (!(m_var_entry= get_variable(&thd->user_vars, name, create_if_not_exists))) { entry_thread_id= 0; return TRUE; @@ -4587,7 +4684,7 @@ bool Item_func_set_user_var::set_entry(THD *thd, bool create_if_not_exists) is different from query_id). */ end: - entry->update_query_id= thd->query_id; + m_var_entry->update_query_id= thd->query_id; return FALSE; } @@ -4619,12 +4716,12 @@ bool Item_func_set_user_var::fix_fields(THD *thd, Item **ref) and the variable has previously been initialized. */ null_item= (args[0]->type() == NULL_ITEM); - if (!entry->collation.collation || !null_item) - entry->collation.set(args[0]->collation.derivation == DERIVATION_NUMERIC ? - default_charset() : args[0]->collation.collation, - DERIVATION_IMPLICIT); - collation.set(entry->collation.collation, DERIVATION_IMPLICIT); - cached_result_type= args[0]->result_type(); + if (!m_var_entry->charset() || !null_item) + m_var_entry->set_charset(args[0]->collation.derivation == DERIVATION_NUMERIC ? + default_charset() : args[0]->collation.collation); + collation.set(m_var_entry->charset(), DERIVATION_IMPLICIT); + set_handler_by_result_type(args[0]->result_type(), + max_length, collation.collation); if (thd->lex->current_select) { /* @@ -4721,9 +4818,9 @@ bool Item_func_set_user_var::register_field_in_bitmap(uchar *arg) true failure */ -static bool +bool update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length, - Item_result type, CHARSET_INFO *cs, Derivation dv, + Item_result type, CHARSET_INFO *cs, bool unsigned_arg) { if (set_null) @@ -4759,7 +4856,8 @@ update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length, entry->value=0; entry->value= (char*) my_realloc(entry->value, length, MYF(MY_ALLOW_ZERO_PTR | MY_WME | - ME_FATALERROR)); + ME_FATALERROR | + MY_THREAD_SPECIFIC)); if (!entry->value) return 1; } @@ -4773,7 +4871,7 @@ update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length, if (type == DECIMAL_RESULT) ((my_decimal*)entry->value)->fix_buffer_pointer(); entry->length= length; - entry->collation.set(cs, dv); + entry->set_charset(cs); entry->unsigned_flag= unsigned_arg; } entry->type=type; @@ -4784,7 +4882,7 @@ update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length, bool Item_func_set_user_var::update_hash(void *ptr, uint length, Item_result res_type, - CHARSET_INFO *cs, Derivation dv, + CHARSET_INFO *cs, bool unsigned_arg) { /* @@ -4792,9 +4890,9 @@ Item_func_set_user_var::update_hash(void *ptr, uint length, result type of the variable */ if ((null_value= args[0]->null_value) && null_item) - res_type= entry->type; // Don't change type of item - if (::update_hash(entry, (null_value= args[0]->null_value), - ptr, length, res_type, cs, dv, unsigned_arg)) + res_type= m_var_entry->type; // Don't change type of item + if (::update_hash(m_var_entry, (null_value= args[0]->null_value), + ptr, length, res_type, cs, unsigned_arg)) { null_value= 1; return 1; @@ -4825,7 +4923,6 @@ double user_var_entry::val_real(bool *null_value) return my_atof(value); // This is null terminated case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // Impossible break; } @@ -4838,7 +4935,7 @@ double user_var_entry::val_real(bool *null_value) longlong user_var_entry::val_int(bool *null_value) const { if ((*null_value= (value == 0))) - return LL(0); + return 0; switch (type) { case REAL_RESULT: @@ -4858,11 +4955,10 @@ longlong user_var_entry::val_int(bool *null_value) const } case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // Impossible break; } - return LL(0); // Impossible + return 0; // Impossible } @@ -4876,24 +4972,23 @@ String *user_var_entry::val_str(bool *null_value, String *str, switch (type) { case REAL_RESULT: - str->set_real(*(double*) value, decimals, collation.collation); + str->set_real(*(double*) value, decimals, charset()); break; case INT_RESULT: if (!unsigned_flag) - str->set(*(longlong*) value, collation.collation); + str->set(*(longlong*) value, charset()); else - str->set(*(ulonglong*) value, collation.collation); + str->set(*(ulonglong*) value, charset()); break; case DECIMAL_RESULT: - str_set_decimal((my_decimal *) value, str, collation.collation); + str_set_decimal((my_decimal *) value, str, charset()); break; case STRING_RESULT: - if (str->copy(value, length, collation.collation)) + if (str->copy(value, length, charset())) str= 0; // EOM error break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // Impossible break; } @@ -4918,11 +5013,10 @@ my_decimal *user_var_entry::val_decimal(bool *null_value, my_decimal *val) my_decimal2decimal((my_decimal *) value, val); break; case STRING_RESULT: - str2my_decimal(E_DEC_FATAL_ERROR, value, length, collation.collation, val); + str2my_decimal(E_DEC_FATAL_ERROR, value, length, charset(), val); break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // Impossible break; } @@ -4950,7 +5044,7 @@ Item_func_set_user_var::check(bool use_result_field) if (use_result_field && !result_field) use_result_field= FALSE; - switch (cached_result_type) { + switch (Item_func_set_user_var::result_type()) { case REAL_RESULT: { save_result.vreal= use_result_field ? result_field->val_real() : @@ -4981,7 +5075,6 @@ Item_func_set_user_var::check(bool use_result_field) } case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -5016,7 +5109,6 @@ void Item_func_set_user_var::save_item_result(Item *item) break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -5045,46 +5137,41 @@ Item_func_set_user_var::update() bool res= 0; DBUG_ENTER("Item_func_set_user_var::update"); - switch (cached_result_type) { + switch (Item_func_set_user_var::result_type()) { case REAL_RESULT: { res= update_hash((void*) &save_result.vreal,sizeof(save_result.vreal), - REAL_RESULT, default_charset(), DERIVATION_IMPLICIT, 0); + REAL_RESULT, default_charset(), 0); break; } case INT_RESULT: { res= update_hash((void*) &save_result.vint, sizeof(save_result.vint), - INT_RESULT, default_charset(), DERIVATION_IMPLICIT, - unsigned_flag); + INT_RESULT, default_charset(), unsigned_flag); break; } case STRING_RESULT: { if (!save_result.vstr) // Null value - res= update_hash((void*) 0, 0, STRING_RESULT, &my_charset_bin, - DERIVATION_IMPLICIT, 0); + res= update_hash((void*) 0, 0, STRING_RESULT, &my_charset_bin, 0); else res= update_hash((void*) save_result.vstr->ptr(), save_result.vstr->length(), STRING_RESULT, - save_result.vstr->charset(), - DERIVATION_IMPLICIT, 0); + save_result.vstr->charset(), 0); break; } case DECIMAL_RESULT: { if (!save_result.vdec) // Null value - res= update_hash((void*) 0, 0, DECIMAL_RESULT, &my_charset_bin, - DERIVATION_IMPLICIT, 0); + res= update_hash((void*) 0, 0, DECIMAL_RESULT, &my_charset_bin, 0); else res= update_hash((void*) save_result.vdec, sizeof(my_decimal), DECIMAL_RESULT, - default_charset(), DERIVATION_IMPLICIT, 0); + default_charset(), 0); break; } case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -5097,7 +5184,7 @@ double Item_func_set_user_var::val_real() DBUG_ASSERT(fixed == 1); check(0); update(); // Store expression - return entry->val_real(&null_value); + return m_var_entry->val_real(&null_value); } longlong Item_func_set_user_var::val_int() @@ -5105,7 +5192,7 @@ longlong Item_func_set_user_var::val_int() DBUG_ASSERT(fixed == 1); check(0); update(); // Store expression - return entry->val_int(&null_value); + return m_var_entry->val_int(&null_value); } String *Item_func_set_user_var::val_str(String *str) @@ -5113,7 +5200,7 @@ String *Item_func_set_user_var::val_str(String *str) DBUG_ASSERT(fixed == 1); check(0); update(); // Store expression - return entry->val_str(&null_value, str, decimals); + return m_var_entry->val_str(&null_value, str, decimals); } @@ -5122,7 +5209,7 @@ my_decimal *Item_func_set_user_var::val_decimal(my_decimal *val) DBUG_ASSERT(fixed == 1); check(0); update(); // Store expression - return entry->val_decimal(&null_value, val); + return m_var_entry->val_decimal(&null_value, val); } @@ -5131,7 +5218,7 @@ double Item_func_set_user_var::val_result() DBUG_ASSERT(fixed == 1); check(TRUE); update(); // Store expression - return entry->val_real(&null_value); + return m_var_entry->val_real(&null_value); } longlong Item_func_set_user_var::val_int_result() @@ -5139,7 +5226,7 @@ longlong Item_func_set_user_var::val_int_result() DBUG_ASSERT(fixed == 1); check(TRUE); update(); // Store expression - return entry->val_int(&null_value); + return m_var_entry->val_int(&null_value); } bool Item_func_set_user_var::val_bool_result() @@ -5147,7 +5234,7 @@ bool Item_func_set_user_var::val_bool_result() DBUG_ASSERT(fixed == 1); check(TRUE); update(); // Store expression - return entry->val_int(&null_value) != 0; + return m_var_entry->val_int(&null_value) != 0; } String *Item_func_set_user_var::str_result(String *str) @@ -5155,7 +5242,7 @@ String *Item_func_set_user_var::str_result(String *str) DBUG_ASSERT(fixed == 1); check(TRUE); update(); // Store expression - return entry->val_str(&null_value, str, decimals); + return m_var_entry->val_str(&null_value, str, decimals); } @@ -5164,7 +5251,7 @@ my_decimal *Item_func_set_user_var::val_decimal_result(my_decimal *val) DBUG_ASSERT(fixed == 1); check(TRUE); update(); // Store expression - return entry->val_decimal(&null_value, val); + return m_var_entry->val_decimal(&null_value, val); } @@ -5279,7 +5366,7 @@ int Item_func_set_user_var::save_in_field(Field *field, bool no_conversions, CHARSET_INFO *cs= collation.collation; char buff[MAX_FIELD_WIDTH]; // Alloc buffer for small columns str_value.set_quick(buff, sizeof(buff), cs); - result= entry->val_str(&null_value, &str_value, decimals); + result= m_var_entry->val_str(&null_value, &str_value, decimals); if (null_value) { @@ -5295,7 +5382,7 @@ int Item_func_set_user_var::save_in_field(Field *field, bool no_conversions, } else if (result_type() == REAL_RESULT) { - double nr= entry->val_real(&null_value); + double nr= m_var_entry->val_real(&null_value); if (null_value) return set_field_to_null(field); field->set_notnull(); @@ -5304,7 +5391,7 @@ int Item_func_set_user_var::save_in_field(Field *field, bool no_conversions, else if (result_type() == DECIMAL_RESULT) { my_decimal decimal_value; - my_decimal *val= entry->val_decimal(&null_value, &decimal_value); + my_decimal *val= m_var_entry->val_decimal(&null_value, &decimal_value); if (null_value) return set_field_to_null(field); field->set_notnull(); @@ -5312,7 +5399,7 @@ int Item_func_set_user_var::save_in_field(Field *field, bool no_conversions, } else { - longlong nr= entry->val_int(&null_value); + longlong nr= m_var_entry->val_int(&null_value); if (null_value) return set_field_to_null_with_conversions(field, no_conversions); field->set_notnull(); @@ -5327,36 +5414,36 @@ Item_func_get_user_var::val_str(String *str) { DBUG_ASSERT(fixed == 1); DBUG_ENTER("Item_func_get_user_var::val_str"); - if (!var_entry) + if (!m_var_entry) DBUG_RETURN((String*) 0); // No such variable - DBUG_RETURN(var_entry->val_str(&null_value, str, decimals)); + DBUG_RETURN(m_var_entry->val_str(&null_value, str, decimals)); } double Item_func_get_user_var::val_real() { DBUG_ASSERT(fixed == 1); - if (!var_entry) + if (!m_var_entry) return 0.0; // No such variable - return (var_entry->val_real(&null_value)); + return (m_var_entry->val_real(&null_value)); } my_decimal *Item_func_get_user_var::val_decimal(my_decimal *dec) { DBUG_ASSERT(fixed == 1); - if (!var_entry) + if (!m_var_entry) return 0; - return var_entry->val_decimal(&null_value, dec); + return m_var_entry->val_decimal(&null_value, dec); } longlong Item_func_get_user_var::val_int() { DBUG_ASSERT(fixed == 1); - if (!var_entry) - return LL(0); // No such variable - return (var_entry->val_int(&null_value)); + if (!m_var_entry) + return 0; // No such variable + return (m_var_entry->val_int(&null_value)); } @@ -5425,10 +5512,13 @@ get_var_with_binlog(THD *thd, enum_sql_command sql_command, LEX *sav_lex= thd->lex, lex_tmp; thd->lex= &lex_tmp; lex_start(thd); - tmp_var_list.push_back(new set_var_user(new Item_func_set_user_var(name, - new Item_null()))); + tmp_var_list.push_back(new (thd->mem_root) + set_var_user(new (thd->mem_root) + Item_func_set_user_var(thd, name, + new (thd->mem_root) Item_null(thd))), + thd->mem_root); /* Create the variable */ - if (sql_set_variables(thd, &tmp_var_list)) + if (sql_set_variables(thd, &tmp_var_list, false)) { thd->lex= sav_lex; goto err; @@ -5471,7 +5561,7 @@ get_var_with_binlog(THD *thd, enum_sql_command sql_command, ALIGN_SIZE(sizeof(BINLOG_USER_VAR_EVENT)); user_var_event->user_var_event= var_entry; user_var_event->type= var_entry->type; - user_var_event->charset_number= var_entry->collation.collation->number; + user_var_event->charset_number= var_entry->charset()->number; user_var_event->unsigned_flag= var_entry->unsigned_flag; if (!var_entry->value) { @@ -5506,21 +5596,20 @@ void Item_func_get_user_var::fix_length_and_dec() decimals=NOT_FIXED_DEC; max_length=MAX_BLOB_WIDTH; - error= get_var_with_binlog(thd, thd->lex->sql_command, name, &var_entry); + error= get_var_with_binlog(thd, thd->lex->sql_command, name, &m_var_entry); /* If the variable didn't exist it has been created as a STRING-type. - 'var_entry' is NULL only if there occured an error during the call to + 'm_var_entry' is NULL only if there occurred an error during the call to get_var_with_binlog. */ - if (!error && var_entry) + if (!error && m_var_entry) { - m_cached_result_type= var_entry->type; - unsigned_flag= var_entry->unsigned_flag; - max_length= var_entry->length; - - collation.set(var_entry->collation); - switch (m_cached_result_type) { + unsigned_flag= m_var_entry->unsigned_flag; + max_length= m_var_entry->length; + collation.set(m_var_entry->charset(), DERIVATION_IMPLICIT); + set_handler_by_result_type(m_var_entry->type); + switch (Item_func_get_user_var::result_type()) { case REAL_RESULT: fix_char_length(DBL_DIG + 8); break; @@ -5530,6 +5619,7 @@ void Item_func_get_user_var::fix_length_and_dec() break; case STRING_RESULT: max_length= MAX_BLOB_WIDTH - 1; + set_handler_by_field_type(MYSQL_TYPE_MEDIUM_BLOB); break; case DECIMAL_RESULT: fix_char_length(DECIMAL_MAX_STR_LENGTH); @@ -5537,7 +5627,6 @@ void Item_func_get_user_var::fix_length_and_dec() break; case ROW_RESULT: // Keep compiler happy case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); // This case should never be chosen break; } @@ -5546,7 +5635,7 @@ void Item_func_get_user_var::fix_length_and_dec() { collation.set(&my_charset_bin, DERIVATION_IMPLICIT); null_value= 1; - m_cached_result_type= STRING_RESULT; + set_handler_by_field_type(MYSQL_TYPE_LONG_BLOB); max_length= MAX_BLOB_WIDTH; } } @@ -5554,13 +5643,8 @@ void Item_func_get_user_var::fix_length_and_dec() bool Item_func_get_user_var::const_item() const { - return (!var_entry || current_thd->query_id != var_entry->update_query_id); -} - - -enum Item_result Item_func_get_user_var::result_type() const -{ - return m_cached_result_type; + return (!m_var_entry || + current_thd->query_id != m_var_entry->update_query_id); } @@ -5590,7 +5674,7 @@ bool Item_func_get_user_var::eq(const Item *item, bool binary_cmp) const bool Item_func_get_user_var::set_value(THD *thd, sp_rcontext * /*ctx*/, Item **it) { - Item_func_set_user_var *suv= new Item_func_set_user_var(get_name(), *it); + Item_func_set_user_var *suv= new (thd->mem_root) Item_func_set_user_var(thd, get_name(), *it); /* Item_func_set_user_var is not fixed after construction, call fix_fields(). @@ -5612,9 +5696,9 @@ bool Item_user_var_as_out_param::fix_fields(THD *thd, Item **ref) of fields in LOAD DATA INFILE. (Since Item_user_var_as_out_param is used only there). */ - entry->collation.set(thd->lex->exchange->cs ? - thd->lex->exchange->cs : - thd->variables.collation_database); + entry->set_charset(thd->lex->exchange->cs ? + thd->lex->exchange->cs : + thd->variables.collation_database); entry->update_query_id= thd->query_id; return FALSE; } @@ -5622,8 +5706,7 @@ bool Item_user_var_as_out_param::fix_fields(THD *thd, Item **ref) void Item_user_var_as_out_param::set_null_value(CHARSET_INFO* cs) { - ::update_hash(entry, TRUE, 0, 0, STRING_RESULT, cs, - DERIVATION_IMPLICIT, 0 /* unsigned_arg */); + ::update_hash(entry, TRUE, 0, 0, STRING_RESULT, cs, 0 /* unsigned_arg */); } @@ -5631,7 +5714,7 @@ void Item_user_var_as_out_param::set_value(const char *str, uint length, CHARSET_INFO* cs) { ::update_hash(entry, FALSE, (void*)str, length, STRING_RESULT, cs, - DERIVATION_IMPLICIT, 0 /* unsigned_arg */); + 0 /* unsigned_arg */); } @@ -5671,11 +5754,11 @@ void Item_user_var_as_out_param::print_for_load(THD *thd, String *str) Item_func_get_system_var:: -Item_func_get_system_var(sys_var *var_arg, enum_var_type var_type_arg, +Item_func_get_system_var(THD *thd, sys_var *var_arg, enum_var_type var_type_arg, LEX_STRING *component_arg, const char *name_arg, - size_t name_len_arg) - :var(var_arg), var_type(var_type_arg), orig_var_type(var_type_arg), - component(*component_arg), cache_present(0) + size_t name_len_arg): + Item_func(thd), var(var_arg), var_type(var_type_arg), + orig_var_type(var_type_arg), component(*component_arg), cache_present(0) { /* set_name() will allocate the name */ set_name(name_arg, (uint) name_len_arg, system_charset_info); @@ -5837,28 +5920,18 @@ enum_field_types Item_func_get_system_var::field_type() const } -/* - Uses var, var_type, component, cache_present, used_query_id, thd, - cached_llval, null_value, cached_null_value -*/ -#define get_sys_var_safe(type) \ -do { \ - type value; \ - mysql_mutex_lock(&LOCK_global_system_variables); \ - value= *(type*) var->value_ptr(thd, var_type, &component); \ - mysql_mutex_unlock(&LOCK_global_system_variables); \ - cache_present |= GET_SYS_VAR_CACHE_LONG; \ - used_query_id= thd->query_id; \ - cached_llval= null_value ? 0 : (longlong) value; \ - cached_null_value= null_value; \ - return cached_llval; \ -} while (0) - - longlong Item_func_get_system_var::val_int() { THD *thd= current_thd; + DBUG_EXECUTE_IF("simulate_non_gtid_aware_master", + { + if (0 == strcmp("gtid_domain_id", var->name.str)) + { + my_error(ER_VAR_CANT_BE_READ, MYF(0), var->name.str); + return 0; + } + }); if (cache_present && thd->query_id == used_query_id) { if (cache_present & GET_SYS_VAR_CACHE_LONG) @@ -5877,10 +5950,7 @@ longlong Item_func_get_system_var::val_int() { null_value= cached_null_value; if (!null_value) - cached_llval= longlong_from_string_with_check (cached_strval.charset(), - cached_strval.c_ptr(), - cached_strval.c_ptr() + - cached_strval.length()); + cached_llval= longlong_from_string_with_check(&cached_strval); else cached_llval= 0; cache_present|= GET_SYS_VAR_CACHE_LONG; @@ -5888,51 +5958,11 @@ longlong Item_func_get_system_var::val_int() } } - switch (var->show_type()) - { - case SHOW_SINT: get_sys_var_safe (int); - case SHOW_SLONG: get_sys_var_safe (long); - case SHOW_SLONGLONG:get_sys_var_safe (longlong); - case SHOW_UINT: get_sys_var_safe (uint); - case SHOW_ULONG: get_sys_var_safe (ulong); - case SHOW_ULONGLONG:get_sys_var_safe (ulonglong); - case SHOW_HA_ROWS: get_sys_var_safe (ha_rows); - case SHOW_BOOL: get_sys_var_safe (bool); - case SHOW_MY_BOOL: get_sys_var_safe (my_bool); - case SHOW_DOUBLE: - { - double dval= val_real(); - - used_query_id= thd->query_id; - cached_llval= (longlong) dval; - cache_present|= GET_SYS_VAR_CACHE_LONG; - return cached_llval; - } - case SHOW_CHAR: - case SHOW_CHAR_PTR: - case SHOW_LEX_STRING: - { - String *str_val= val_str(NULL); - - if (str_val && str_val->length()) - cached_llval= longlong_from_string_with_check (system_charset_info, - str_val->c_ptr(), - str_val->c_ptr() + - str_val->length()); - else - { - null_value= TRUE; - cached_llval= 0; - } - - cache_present|= GET_SYS_VAR_CACHE_LONG; - return cached_llval; - } - - default: - my_error(ER_VAR_CANT_BE_READ, MYF(0), var->name.str); - return 0; // keep the compiler happy - } + cached_llval= var->val_int(&null_value, thd, var_type, &component); + cache_present |= GET_SYS_VAR_CACHE_LONG; + used_query_id= thd->query_id; + cached_null_value= null_value; + return cached_llval; } @@ -5965,61 +5995,10 @@ String* Item_func_get_system_var::val_str(String* str) } } - str= &cached_strval; - switch (var->show_type()) - { - case SHOW_CHAR: - case SHOW_CHAR_PTR: - case SHOW_LEX_STRING: - { - mysql_mutex_lock(&LOCK_global_system_variables); - char *cptr= var->show_type() == SHOW_CHAR ? - (char*) var->value_ptr(thd, var_type, &component) : - *(char**) var->value_ptr(thd, var_type, &component); - if (cptr) - { - size_t len= var->show_type() == SHOW_LEX_STRING ? - ((LEX_STRING*)(var->value_ptr(thd, var_type, &component)))->length : - strlen(cptr); - if (str->copy(cptr, len, collation.collation)) - { - null_value= TRUE; - str= NULL; - } - } - else - { - null_value= TRUE; - str= NULL; - } - mysql_mutex_unlock(&LOCK_global_system_variables); - break; - } - - case SHOW_SINT: - case SHOW_SLONG: - case SHOW_SLONGLONG: - case SHOW_UINT: - case SHOW_ULONG: - case SHOW_ULONGLONG: - case SHOW_HA_ROWS: - case SHOW_BOOL: - case SHOW_MY_BOOL: - str->set (val_int(), collation.collation); - break; - case SHOW_DOUBLE: - str->set_real (val_real(), decimals, collation.collation); - break; - - default: - my_error(ER_VAR_CANT_BE_READ, MYF(0), var->name.str); - str= NULL; - break; - } - + str= var->val_str(&cached_strval, thd, var_type, &component); cache_present|= GET_SYS_VAR_CACHE_STRING; used_query_id= thd->query_id; - cached_null_value= null_value; + cached_null_value= null_value= !str; return str; } @@ -6046,10 +6025,7 @@ double Item_func_get_system_var::val_real() { null_value= cached_null_value; if (!null_value) - cached_dval= double_from_string_with_check (cached_strval.charset(), - cached_strval.c_ptr(), - cached_strval.c_ptr() + - cached_strval.length()); + cached_dval= double_from_string_with_check(&cached_strval); else cached_dval= 0; cache_present|= GET_SYS_VAR_CACHE_DOUBLE; @@ -6057,58 +6033,11 @@ double Item_func_get_system_var::val_real() } } - switch (var->show_type()) - { - case SHOW_DOUBLE: - mysql_mutex_lock(&LOCK_global_system_variables); - cached_dval= *(double*) var->value_ptr(thd, var_type, &component); - mysql_mutex_unlock(&LOCK_global_system_variables); - used_query_id= thd->query_id; - cached_null_value= null_value; - if (null_value) - cached_dval= 0; - cache_present|= GET_SYS_VAR_CACHE_DOUBLE; - return cached_dval; - case SHOW_CHAR: - case SHOW_LEX_STRING: - case SHOW_CHAR_PTR: - { - mysql_mutex_lock(&LOCK_global_system_variables); - char *cptr= var->show_type() == SHOW_CHAR ? - (char*) var->value_ptr(thd, var_type, &component) : - *(char**) var->value_ptr(thd, var_type, &component); - if (cptr) - cached_dval= double_from_string_with_check (system_charset_info, - cptr, cptr + strlen (cptr)); - else - { - null_value= TRUE; - cached_dval= 0; - } - mysql_mutex_unlock(&LOCK_global_system_variables); - used_query_id= thd->query_id; - cached_null_value= null_value; - cache_present|= GET_SYS_VAR_CACHE_DOUBLE; - return cached_dval; - } - case SHOW_SINT: - case SHOW_SLONG: - case SHOW_SLONGLONG: - case SHOW_UINT: - case SHOW_ULONG: - case SHOW_ULONGLONG: - case SHOW_HA_ROWS: - case SHOW_BOOL: - case SHOW_MY_BOOL: - cached_dval= (double) val_int(); - cache_present|= GET_SYS_VAR_CACHE_DOUBLE; - used_query_id= thd->query_id; - cached_null_value= null_value; - return cached_dval; - default: - my_error(ER_VAR_CANT_BE_READ, MYF(0), var->name.str); - return 0; - } + cached_dval= var->val_real(&null_value, thd, var_type, &component); + cache_present |= GET_SYS_VAR_CACHE_DOUBLE; + used_query_id= thd->query_id; + cached_null_value= null_value; + return cached_dval; } @@ -6135,62 +6064,7 @@ void Item_func_get_system_var::cleanup() } -longlong Item_func_inet_aton::val_int() -{ - DBUG_ASSERT(fixed == 1); - uint byte_result = 0; - ulonglong result = 0; // We are ready for 64 bit addresses - const char *p,* end; - char c = '.'; // we mark c to indicate invalid IP in case length is 0 - char buff[36]; - int dot_count= 0; - - String *s, tmp(buff, sizeof(buff), &my_charset_latin1); - if (!(s = args[0]->val_str_ascii(&tmp))) // If null value - goto err; - null_value=0; - - end= (p = s->ptr()) + s->length(); - while (p < end) - { - c = *p++; - int digit = (int) (c - '0'); - if (digit >= 0 && digit <= 9) - { - if ((byte_result = byte_result * 10 + digit) > 255) - goto err; // Wrong address - } - else if (c == '.') - { - dot_count++; - result= (result << 8) + (ulonglong) byte_result; - byte_result = 0; - } - else - goto err; // Invalid character - } - if (c != '.') // IP number can't end on '.' - { - /* - Handle short-forms addresses according to standard. Examples: - 127 -> 0.0.0.127 - 127.1 -> 127.0.0.1 - 127.2.1 -> 127.2.0.1 - */ - switch (dot_count) { - case 1: result<<= 8; /* Fall through */ - case 2: result<<= 8; /* Fall through */ - } - return (result << 8) + (ulonglong) byte_result; - } - -err: - null_value=1; - return 0; -} - - -void Item_func_match::init_search(bool no_order) +void Item_func_match::init_search(THD *thd, bool no_order) { DBUG_ENTER("Item_func_match::init_search"); @@ -6208,10 +6082,12 @@ void Item_func_match::init_search(bool no_order) if (key == NO_SUCH_KEY) { List<Item> fields; - fields.push_back(new Item_string(" ", 1, cmp_collation.collation)); + fields.push_back(new (thd->mem_root) + Item_string(thd, " ", 1, cmp_collation.collation), + thd->mem_root); for (uint i= 1; i < arg_count; i++) fields.push_back(args[i]); - concat_ws= new Item_func_concat_ws(fields); + concat_ws= new (thd->mem_root) Item_func_concat_ws(thd, fields); /* Above function used only to get value and do not need fix_fields for it: Item_string - basic constant @@ -6224,7 +6100,7 @@ void Item_func_match::init_search(bool no_order) if (master) { join_key= master->join_key= join_key | master->join_key; - master->init_search(no_order); + master->init_search(thd, no_order); ft_handler= master->ft_handler; join_key= master->join_key; DBUG_VOID_RETURN; @@ -6251,7 +6127,7 @@ void Item_func_match::init_search(bool no_order) flags|=FT_SORTED; if (key != NO_SUCH_KEY) - thd_proc_info(table->in_use, "FULLTEXT initialization"); + THD_STAGE_INFO(table->in_use, stage_fulltext_initialization); ft_handler= table->file->ft_init_ext(flags, key, ft_tmp); @@ -6285,6 +6161,7 @@ bool Item_func_match::fix_fields(THD *thd, Item **ref) return TRUE; } + bool allows_multi_table_search= true; const_item_cache=0; table= 0; for (uint i=1 ; i < arg_count ; i++) @@ -6315,7 +6192,10 @@ bool Item_func_match::fix_fields(THD *thd, Item **ref) */ if (item->type() == Item::FIELD_ITEM) table= ((Item_field *)item)->field->table; + + allows_multi_table_search &= allows_search_on_non_indexed_columns(table); } + /* Check that all columns come from the same table. We've already checked that columns in MATCH are fields so @@ -6324,14 +6204,14 @@ bool Item_func_match::fix_fields(THD *thd, Item **ref) if ((used_tables_cache & ~PARAM_TABLE_BIT) != item->used_tables()) key=NO_SUCH_KEY; - if (key == NO_SUCH_KEY && !(flags & FT_BOOL)) + if (key == NO_SUCH_KEY && !allows_multi_table_search) { my_error(ER_WRONG_ARGUMENTS,MYF(0),"MATCH"); return TRUE; } if (!(table->file->ha_table_flags() & HA_CAN_FULLTEXT)) { - my_error(ER_TABLE_CANT_HANDLE_FT, MYF(0)); + my_error(ER_TABLE_CANT_HANDLE_FT, MYF(0), table->file->table_type()); return 1; } table->fulltext_searched=1; @@ -6381,7 +6261,7 @@ bool Item_func_match::fix_index() for (keynr=0 ; keynr < fts ; keynr++) { KEY *ft_key=&table->key_info[ft_to_key[keynr]]; - uint key_parts=ft_key->key_parts; + uint key_parts=ft_key->user_defined_key_parts; for (uint part=0 ; part < key_parts ; part++) { @@ -6413,7 +6293,7 @@ bool Item_func_match::fix_index() { // partial keys doesn't work if (max_cnt < arg_count-1 || - max_cnt < table->key_info[ft_to_key[keynr]].key_parts) + max_cnt < table->key_info[ft_to_key[keynr]].user_defined_key_parts) continue; key=ft_to_key[keynr]; @@ -6422,7 +6302,7 @@ bool Item_func_match::fix_index() } err: - if (flags & FT_BOOL) + if (allows_search_on_non_indexed_columns(table)) { key=NO_SUCH_KEY; return 0; @@ -6555,73 +6435,11 @@ Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name, set_if_smaller(component_name->length, MAX_SYS_VAR_LENGTH); - return new Item_func_get_system_var(var, var_type, component_name, + return new (thd->mem_root) Item_func_get_system_var(thd, var, var_type, component_name, NULL, 0); } -/** - Check a user level lock. - - Sets null_value=TRUE on error. - - @retval - 1 Available - @retval - 0 Already taken, or error -*/ - -longlong Item_func_is_free_lock::val_int() -{ - DBUG_ASSERT(fixed == 1); - String *res=args[0]->val_str(&value); - User_level_lock *ull; - longlong ret_val= 0LL; - - null_value=0; - if (!res || !res->length()) - { - null_value=1; - return ret_val; - } - - mysql_mutex_lock(&LOCK_user_locks); - ull= (User_level_lock *) my_hash_search(&hash_user_locks, (uchar*) res->ptr(), - (size_t) res->length()); - if (!ull || !ull->locked) - ret_val= 1; - mysql_mutex_unlock(&LOCK_user_locks); - DEBUG_SYNC(current_thd, "after_getting_user_level_lock_info"); - - return ret_val; -} - -longlong Item_func_is_used_lock::val_int() -{ - DBUG_ASSERT(fixed == 1); - String *res=args[0]->val_str(&value); - User_level_lock *ull; - my_thread_id thread_id= 0UL; - - null_value=1; - if (!res || !res->length()) - return 0; - - mysql_mutex_lock(&LOCK_user_locks); - ull= (User_level_lock *) my_hash_search(&hash_user_locks, (uchar*) res->ptr(), - (size_t) res->length()); - if ((ull != NULL) && ull->locked) - { - null_value= 0; - thread_id= ull->thread_id; - } - mysql_mutex_unlock(&LOCK_user_locks); - DEBUG_SYNC(current_thd, "after_getting_user_level_lock_info"); - - return thread_id; -} - - longlong Item_func_row_count::val_int() { DBUG_ASSERT(fixed == 1); @@ -6633,23 +6451,25 @@ longlong Item_func_row_count::val_int() -Item_func_sp::Item_func_sp(Name_resolution_context *context_arg, sp_name *name) - :Item_func(), context(context_arg), m_name(name), m_sp(NULL), sp_result_field(NULL) +Item_func_sp::Item_func_sp(THD *thd, Name_resolution_context *context_arg, + sp_name *name): + Item_func(thd), context(context_arg), m_name(name), m_sp(NULL), sp_result_field(NULL) { maybe_null= 1; - m_name->init_qname(current_thd); - dummy_table= (TABLE*) sql_calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE)); + m_name->init_qname(thd); + dummy_table= (TABLE*) thd->calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE)); dummy_table->s= (TABLE_SHARE*) (dummy_table+1); } -Item_func_sp::Item_func_sp(Name_resolution_context *context_arg, - sp_name *name, List<Item> &list) - :Item_func(list), context(context_arg), m_name(name), m_sp(NULL),sp_result_field(NULL) +Item_func_sp::Item_func_sp(THD *thd, Name_resolution_context *context_arg, + sp_name *name_arg, List<Item> &list): + Item_func(thd, list), context(context_arg), m_name(name_arg), m_sp(NULL), + sp_result_field(NULL) { maybe_null= 1; - m_name->init_qname(current_thd); - dummy_table= (TABLE*) sql_calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE)); + m_name->init_qname(thd); + dummy_table= (TABLE*) thd->calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE)); dummy_table->s= (TABLE_SHARE*) (dummy_table+1); } @@ -6761,7 +6581,7 @@ Item_func_sp::init_result_field(THD *thd) if (sp_result_field->pack_length() > sizeof(result_buf)) { void *tmp; - if (!(tmp= sql_alloc(sp_result_field->pack_length()))) + if (!(tmp= thd->alloc(sp_result_field->pack_length()))) DBUG_RETURN(TRUE); sp_result_field->move_field((uchar*) tmp); } @@ -6799,11 +6619,8 @@ void Item_func_sp::fix_length_and_dec() DBUG_ENTER("Item_func_sp::fix_length_and_dec"); DBUG_ASSERT(sp_result_field); - decimals= sp_result_field->decimals(); - max_length= sp_result_field->field_length; - collation.set(sp_result_field->charset()); + Type_std_attributes::set(sp_result_field); maybe_null= 1; - unsigned_flag= test(sp_result_field->flags & UNSIGNED_FLAG); DBUG_VOID_RETURN; } @@ -6856,22 +6673,18 @@ Item_func_sp::execute_impl(THD *thd) { bool err_status= TRUE; Sub_statement_state statement_state; -#ifndef NO_EMBEDDED_ACCESS_CHECKS Security_context *save_security_ctx= thd->security_ctx; -#endif enum enum_sp_data_access access= (m_sp->m_chistics->daccess == SP_DEFAULT_ACCESS) ? SP_DEFAULT_ACCESS_MAPPING : m_sp->m_chistics->daccess; DBUG_ENTER("Item_func_sp::execute_impl"); -#ifndef NO_EMBEDDED_ACCESS_CHECKS if (context->security_ctx) { /* Set view definer security context */ thd->security_ctx= context->security_ctx; } -#endif if (sp_check_access(thd)) goto error; @@ -6899,9 +6712,7 @@ Item_func_sp::execute_impl(THD *thd) thd->restore_sub_statement_state(&statement_state); error: -#ifndef NO_EMBEDDED_ACCESS_CHECKS thd->security_ctx= save_security_ctx; -#endif DBUG_RETURN(err_status); } @@ -6943,16 +6754,6 @@ longlong Item_func_found_rows::val_int() } -Field * -Item_func_sp::tmp_table_field(TABLE *t_arg) -{ - DBUG_ENTER("Item_func_sp::tmp_table_field"); - - DBUG_ASSERT(sp_result_field); - DBUG_RETURN(sp_result_field); -} - - /** @brief Checks if requested access to function can be granted to user. If function isn't found yet, it searches function first. @@ -6972,11 +6773,9 @@ Item_func_sp::sp_check_access(THD *thd) { DBUG_ENTER("Item_func_sp::sp_check_access"); DBUG_ASSERT(m_sp); -#ifndef NO_EMBEDDED_ACCESS_CHECKS if (check_routine_access(thd, EXECUTE_ACL, m_sp->m_db.str, m_sp->m_name.str, 0, FALSE)) DBUG_RETURN(TRUE); -#endif DBUG_RETURN(FALSE); } @@ -6988,7 +6787,29 @@ Item_func_sp::fix_fields(THD *thd, Item **ref) bool res; DBUG_ENTER("Item_func_sp::fix_fields"); DBUG_ASSERT(fixed == 0); - + + /* + Checking privileges to execute the function while creating view and + executing the function of select. + */ + if (!(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW) || + (thd->lex->sql_command == SQLCOM_CREATE_VIEW)) + { + Security_context *save_security_ctx= thd->security_ctx; + if (context->security_ctx) + thd->security_ctx= context->security_ctx; + + res= check_routine_access(thd, EXECUTE_ACL, m_name->m_db.str, + m_name->m_name.str, 0, FALSE); + thd->security_ctx= save_security_ctx; + + if (res) + { + context->process_error(thd); + DBUG_RETURN(res); + } + } + /* We must call init_result_field before Item_func::fix_fields() to make m_sp and result_field members available to fix_length_and_dec(), @@ -7073,7 +6894,7 @@ ulonglong uuid_value; void uuid_short_init() { - uuid_value= ((((ulonglong) server_id) << 56) + + uuid_value= ((((ulonglong) global_system_variables.server_id) << 56) + (((ulonglong) server_start_time) << 24)); } @@ -7139,9 +6960,6 @@ my_decimal *Item_func_last_value::val_decimal(my_decimal *decimal_value) void Item_func_last_value::fix_length_and_dec() { last_value= args[arg_count -1]; - decimals= last_value->decimals; - max_length= last_value->max_length; - collation.set(last_value->collation.collation); + Type_std_attributes::set(last_value); maybe_null= last_value->maybe_null; - unsigned_flag= last_value->unsigned_flag; } diff --git a/sql/item_func.h b/sql/item_func.h index 64b3b5cfb75..36a2f94b31d 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -30,10 +30,11 @@ extern "C" /* Bug in BSDI include file */ } #endif -class Item_func :public Item_result_field + +class Item_func :public Item_func_or_sum { + void sync_with_sum_func_and_with_field(List<Item> &list); protected: - Item **args, *tmp_arg[2]; /* Allowed numbers of columns in result (usually 1, which means scalar value) 0 means get this number from first argument @@ -44,13 +45,13 @@ protected: void count_only_length(Item **item, uint nitems); void count_real_length(Item **item, uint nitems); void count_decimal_length(Item **item, uint nitems); - void count_datetime_length(Item **item, uint nitems); + void count_datetime_length(enum_field_types field_type, + Item **item, uint nitems); bool count_string_result_length(enum_field_types field_type, Item **item, uint nitems); public: - uint arg_count; - table_map used_tables_cache, not_null_tables_cache; - bool const_item_cache; + table_map not_null_tables_cache; + enum Functype { UNKNOWN_FUNC,EQ_FUNC,EQUAL_FUNC,NE_FUNC,LT_FUNC,LE_FUNC, GE_FUNC,GT_FUNC,FT_FUNC, LIKE_FUNC,ISNULL_FUNC,ISNOTNULL_FUNC, @@ -61,109 +62,111 @@ public: SP_TOUCHES_FUNC,SP_CROSSES_FUNC,SP_WITHIN_FUNC, SP_CONTAINS_FUNC,SP_OVERLAPS_FUNC, SP_STARTPOINT,SP_ENDPOINT,SP_EXTERIORRING, - SP_POINTN,SP_GEOMETRYN,SP_INTERIORRINGN, + SP_POINTN,SP_GEOMETRYN,SP_INTERIORRINGN, SP_RELATE_FUNC, NOT_FUNC, NOT_ALL_FUNC, NOW_FUNC, TRIG_COND_FUNC, SUSERVAR_FUNC, GUSERVAR_FUNC, COLLATE_FUNC, EXTRACT_FUNC, CHAR_TYPECAST_FUNC, FUNC_SP, UDF_FUNC, - NEG_FUNC, GSYSVAR_FUNC, IN_OPTIMIZER_FUNC }; - enum optimize_type { OPTIMIZE_NONE,OPTIMIZE_KEY,OPTIMIZE_OP, OPTIMIZE_NULL, - OPTIMIZE_EQUAL }; + NEG_FUNC, GSYSVAR_FUNC, IN_OPTIMIZER_FUNC, DYNCOL_FUNC }; enum Type type() const { return FUNC_ITEM; } virtual enum Functype functype() const { return UNKNOWN_FUNC; } - Item_func(void): - allowed_arg_cols(1), arg_count(0) + Item_func(THD *thd): Item_func_or_sum(thd), allowed_arg_cols(1) { with_sum_func= 0; with_field= 0; + with_param= 0; } - Item_func(Item *a): - allowed_arg_cols(1), arg_count(1) + Item_func(THD *thd, Item *a): Item_func_or_sum(thd, a), allowed_arg_cols(1) { - args= tmp_arg; - args[0]= a; with_sum_func= a->with_sum_func; with_param= a->with_param; with_field= a->with_field; } - Item_func(Item *a,Item *b): - allowed_arg_cols(1), arg_count(2) + Item_func(THD *thd, Item *a, Item *b): + Item_func_or_sum(thd, a, b), allowed_arg_cols(1) { - args= tmp_arg; - args[0]= a; args[1]= b; with_sum_func= a->with_sum_func || b->with_sum_func; with_param= a->with_param || b->with_param; with_field= a->with_field || b->with_field; } - Item_func(Item *a,Item *b,Item *c): - allowed_arg_cols(1) + Item_func(THD *thd, Item *a, Item *b, Item *c): + Item_func_or_sum(thd, a, b, c), allowed_arg_cols(1) { - arg_count= 0; - if ((args= (Item**) sql_alloc(sizeof(Item*)*3))) - { - arg_count= 3; - args[0]= a; args[1]= b; args[2]= c; - with_sum_func= a->with_sum_func || b->with_sum_func || c->with_sum_func; - with_param= a->with_param || b->with_param || c->with_param; - with_field= a->with_field || b->with_field || c->with_field; - } + with_sum_func= a->with_sum_func || b->with_sum_func || c->with_sum_func; + with_field= a->with_field || b->with_field || c->with_field; + with_param= a->with_param || b->with_param || c->with_param; } - Item_func(Item *a,Item *b,Item *c,Item *d): - allowed_arg_cols(1) + Item_func(THD *thd, Item *a, Item *b, Item *c, Item *d): + Item_func_or_sum(thd, a, b, c, d), allowed_arg_cols(1) { - arg_count= 0; - if ((args= (Item**) sql_alloc(sizeof(Item*)*4))) - { - arg_count= 4; - args[0]= a; args[1]= b; args[2]= c; args[3]= d; - with_sum_func= a->with_sum_func || b->with_sum_func || - c->with_sum_func || d->with_sum_func; - with_param= a->with_param || b->with_param || - c->with_param || d->with_param; - with_field= a->with_field || b->with_field || - c->with_field || d->with_field; - } + with_sum_func= a->with_sum_func || b->with_sum_func || + c->with_sum_func || d->with_sum_func; + with_field= a->with_field || b->with_field || + c->with_field || d->with_field; + with_param= a->with_param || b->with_param || + c->with_param || d->with_param; } - Item_func(Item *a,Item *b,Item *c,Item *d,Item* e): - allowed_arg_cols(1) + Item_func(THD *thd, Item *a, Item *b, Item *c, Item *d, Item* e): + Item_func_or_sum(thd, a, b, c, d, e), allowed_arg_cols(1) { - arg_count= 5; - if ((args= (Item**) sql_alloc(sizeof(Item*)*5))) - { - args[0]= a; args[1]= b; args[2]= c; args[3]= d; args[4]= e; - with_sum_func= a->with_sum_func || b->with_sum_func || - c->with_sum_func || d->with_sum_func || e->with_sum_func ; - with_param= a->with_param || b->with_param || - c->with_param || d->with_param || e->with_param; - with_field= a->with_field || b->with_field || - c->with_field || d->with_field || e->with_field; - } + with_sum_func= a->with_sum_func || b->with_sum_func || + c->with_sum_func || d->with_sum_func || e->with_sum_func; + with_field= a->with_field || b->with_field || + c->with_field || d->with_field || e->with_field; + with_param= a->with_param || b->with_param || + c->with_param || d->with_param || e->with_param; + } + Item_func(THD *thd, List<Item> &list): + Item_func_or_sum(thd, list), allowed_arg_cols(1) + { + set_arguments(thd, list); } - Item_func(List<Item> &list); // Constructor used for Item_cond_and/or (see Item comment) - Item_func(THD *thd, Item_func *item); + Item_func(THD *thd, Item_func *item): + Item_func_or_sum(thd, item), + allowed_arg_cols(item->allowed_arg_cols), + not_null_tables_cache(item->not_null_tables_cache) + { + } bool fix_fields(THD *, Item **ref); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void cleanup() + { + Item_func_or_sum::cleanup(); + used_tables_and_const_cache_init(); + } + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); void quick_fix_field(); - table_map used_tables() const; table_map not_null_tables() const; - void update_used_tables(); + void update_used_tables() + { + used_tables_and_const_cache_init(); + used_tables_and_const_cache_update_and_join(arg_count, args); + } + COND *build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref); + SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) + { + DBUG_ENTER("Item_func::get_mm_tree"); + DBUG_RETURN(const_item() ? get_mm_tree_for_const(param) : NULL); + } bool eq(const Item *item, bool binary_cmp) const; - virtual optimize_type select_optimize() const { return OPTIMIZE_NONE; } - virtual bool have_rev_func() const { return 0; } virtual Item *key_item() const { return args[0]; } - virtual bool const_item() const { return const_item_cache; } - inline Item **arguments() const { return args; } - void set_arguments(List<Item> &list); - inline uint argument_count() const { return arg_count; } - inline void remove_arguments() { arg_count=0; } - void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields); + void set_arguments(THD *thd, List<Item> &list) + { + allowed_arg_cols= 1; + Item_args::set_arguments(thd, list); + sync_with_sum_func_and_with_field(list); + list.empty(); // Fields are used + } + void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields, + uint flags); virtual void print(String *str, enum_query_type query_type); void print_op(String *str, enum_query_type query_type); void print_args(String *str, uint from, enum_query_type query_type); inline bool get_arg0_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { - return (null_value=args[0]->get_date(ltime, fuzzy_date)); + return (null_value=args[0]->get_date_with_conversion(ltime, fuzzy_date)); } inline bool get_arg0_time(MYSQL_TIME *ltime) { @@ -178,8 +181,12 @@ public: } void signal_divide_by_null(); friend class udf_handler; - Field *tmp_table_field() { return result_field; } - Field *tmp_table_field(TABLE *t_arg); + Field *create_field_for_create_select(TABLE *table) + { + return result_type() != STRING_RESULT ? + create_tmp_field(false, table, MY_INT32_NUM_DECIMAL_DIGITS) : + tmp_table_field_from_field_type(table, false, false); + } Item *get_tmp_table_item(THD *thd); my_decimal *val_decimal(my_decimal *); @@ -196,53 +203,8 @@ public: else max_length= (uint32) max_result_length; } - bool agg_arg_charsets(DTCollation &c, Item **items, uint nitems, - uint flags, int item_sep) - { - return agg_item_charsets(c, func_name(), items, nitems, flags, item_sep); - } - /* - Aggregate arguments for string result, e.g: CONCAT(a,b) - - convert to @@character_set_connection if all arguments are numbers - - allow DERIVATION_NONE - */ - bool agg_arg_charsets_for_string_result(DTCollation &c, - Item **items, uint nitems, - int item_sep= 1) - { - return agg_item_charsets_for_string_result(c, func_name(), - items, nitems, item_sep); - } - /* - Aggregate arguments for comparison, e.g: a=b, a LIKE b, a RLIKE b - - don't convert to @@character_set_connection if all arguments are numbers - - don't allow DERIVATION_NONE - */ - bool agg_arg_charsets_for_comparison(DTCollation &c, - Item **items, uint nitems, - int item_sep= 1) - { - return agg_item_charsets_for_comparison(c, func_name(), - items, nitems, item_sep); - } - /* - Aggregate arguments for string result, when some comparison - is involved internally, e.g: REPLACE(a,b,c) - - convert to @@character_set_connection if all arguments are numbers - - disallow DERIVATION_NONE - */ - bool agg_arg_charsets_for_string_result_with_comparison(DTCollation &c, - Item **items, - uint nitems, - int item_sep= 1) - { - return agg_item_charsets_for_string_result_with_comparison(c, func_name(), - items, nitems, - item_sep); - } - bool walk(Item_processor processor, bool walk_subquery, uchar *arg); - Item *transform(Item_transformer transformer, uchar *arg); - Item* compile(Item_analyzer analyzer, uchar **arg_p, + Item *transform(THD *thd, Item_transformer transformer, uchar *arg); + Item* compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, Item_transformer transformer, uchar *arg_t); void traverse_cond(Cond_traverser traverser, void * arg, traverse_order order); @@ -351,19 +313,15 @@ public: return FALSE; } - /* - By default only substitution for a field whose two different values - are never equal is allowed in the arguments of a function. - This is overruled for the direct arguments of comparison functions. - */ - bool subst_argument_checker(uchar **arg) - { - if (*arg) - { - *arg= (uchar *) Item::IDENTITY_SUBST; - return TRUE; - } - return FALSE; + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) + { + /* + By default only substitution for a field whose two different values + are never equal is allowed in the arguments of a function. + This is overruled for the direct arguments of comparison functions. + */ + Item_args::propagate_equal_fields(thd, Context_identity(), cond); + return this; } /* @@ -398,16 +356,29 @@ public: args[i]->no_rows_in_result(); } } + void convert_const_compared_to_int_field(THD *thd); + /** + Prepare arguments and setup a comparator. + Used in Item_func_xxx with two arguments and a comparator, + e.g. Item_bool_func2 and Item_func_nullif. + args[0] or args[1] can be modified: + - converted to character set and collation of the operation + - or replaced to an Item_int_with_ref + */ + bool setup_args_and_comparator(THD *thd, Arg_comparator *cmp); }; class Item_real_func :public Item_func { public: - Item_real_func() :Item_func() { collation.set_numeric(); } - Item_real_func(Item *a) :Item_func(a) { collation.set_numeric(); } - Item_real_func(Item *a,Item *b) :Item_func(a,b) { collation.set_numeric(); } - Item_real_func(List<Item> &list) :Item_func(list) { collation.set_numeric(); } + Item_real_func(THD *thd): Item_func(thd) { collation.set_numeric(); } + Item_real_func(THD *thd, Item *a): Item_func(thd, a) + { collation.set_numeric(); } + Item_real_func(THD *thd, Item *a, Item *b): Item_func(thd, a, b) + { collation.set_numeric(); } + Item_real_func(THD *thd, List<Item> &list): Item_func(thd, list) + { collation.set_numeric(); } String *val_str(String*str); my_decimal *val_decimal(my_decimal *decimal_value); longlong val_int() @@ -422,7 +393,49 @@ public: }; -class Item_func_hybrid_result_type: public Item_func +/** + Functions whose returned field type is determined at fix_fields() time. +*/ +class Item_hybrid_func: public Item_func, + public Type_handler_hybrid_field_type +{ +protected: + void fix_attributes(Item **item, uint nitems); +public: + Item_hybrid_func(THD *thd): Item_func(thd) { } + Item_hybrid_func(THD *thd, Item *a): Item_func(thd, a) { } + Item_hybrid_func(THD *thd, Item *a, Item *b): Item_func(thd, a, b) { } + Item_hybrid_func(THD *thd, Item *a, Item *b, Item *c): + Item_func(thd, a, b, c) { } + Item_hybrid_func(THD *thd, List<Item> &list): Item_func(thd, list) { } + Item_hybrid_func(THD *thd, Item_hybrid_func *item) + :Item_func(thd, item), Type_handler_hybrid_field_type(item) { } + enum_field_types field_type() const + { return Type_handler_hybrid_field_type::field_type(); } + enum Item_result result_type () const + { return Type_handler_hybrid_field_type::result_type(); } + enum Item_result cmp_type () const + { return Type_handler_hybrid_field_type::cmp_type(); } +}; + + +/** + Functions that at fix_fields() time determine the returned field type, + trying to preserve the exact data type of the arguments. + + The descendants have to implement "native" value methods, + i.e. str_op(), date_op(), int_op(), real_op(), decimal_op(). + fix_fields() chooses which of the above value methods will be + used during execution time, according to the returned field type. + + For example, if fix_fields() determines that the returned value type + is MYSQL_TYPE_LONG, then: + - int_op() is chosen as the execution time native method. + - val_int() returns the result of int_op() as is. + - all other methods, i.e. val_real(), val_decimal(), val_str(), get_date(), + call int_op() first, then convert the result to the requested data type. +*/ +class Item_func_hybrid_field_type: public Item_hybrid_func { /* Helper methods to make sure that the result of @@ -449,24 +462,23 @@ class Item_func_hybrid_result_type: public Item_func } protected: Item_result cached_result_type; - void fix_attributes(Item **item, uint nitems); public: - Item_func_hybrid_result_type() :Item_func(), cached_result_type(REAL_RESULT) + Item_func_hybrid_field_type(THD *thd): + Item_hybrid_func(thd) { collation.set_numeric(); } - Item_func_hybrid_result_type(Item *a) :Item_func(a), cached_result_type(REAL_RESULT) + Item_func_hybrid_field_type(THD *thd, Item *a): + Item_hybrid_func(thd, a) { collation.set_numeric(); } - Item_func_hybrid_result_type(Item *a,Item *b) - :Item_func(a,b), cached_result_type(REAL_RESULT) + Item_func_hybrid_field_type(THD *thd, Item *a, Item *b): + Item_hybrid_func(thd, a, b) { collation.set_numeric(); } - Item_func_hybrid_result_type(Item *a,Item *b,Item *c) - :Item_func(a,b,c), cached_result_type(REAL_RESULT) + Item_func_hybrid_field_type(THD *thd, Item *a, Item *b, Item *c): + Item_hybrid_func(thd, a, b, c) { collation.set_numeric(); } - Item_func_hybrid_result_type(List<Item> &list) - :Item_func(list), cached_result_type(REAL_RESULT) + Item_func_hybrid_field_type(THD *thd, List<Item> &list): + Item_hybrid_func(thd, list) { collation.set_numeric(); } - enum Item_result result_type () const { return cached_result_type; } - double val_real(); longlong val_int(); my_decimal *val_decimal(my_decimal *); @@ -519,32 +531,7 @@ public: }; - -class Item_func_hybrid_field_type :public Item_func_hybrid_result_type -{ -protected: - enum_field_types cached_field_type; -public: - Item_func_hybrid_field_type() - :Item_func_hybrid_result_type(), cached_field_type(MYSQL_TYPE_DOUBLE) - {} - Item_func_hybrid_field_type(Item *a, Item *b) - :Item_func_hybrid_result_type(a, b), cached_field_type(MYSQL_TYPE_DOUBLE) - {} - Item_func_hybrid_field_type(Item *a, Item *b, Item *c) - :Item_func_hybrid_result_type(a, b, c), - cached_field_type(MYSQL_TYPE_DOUBLE) - {} - Item_func_hybrid_field_type(List<Item> &list) - :Item_func_hybrid_result_type(list), - cached_field_type(MYSQL_TYPE_DOUBLE) - {} - enum_field_types field_type() const { return cached_field_type; } -}; - - - -class Item_func_numhybrid: public Item_func_hybrid_result_type +class Item_func_numhybrid: public Item_func_hybrid_field_type { protected: @@ -556,18 +543,18 @@ protected: } public: - Item_func_numhybrid() :Item_func_hybrid_result_type() + Item_func_numhybrid(THD *thd): Item_func_hybrid_field_type(thd) { } - Item_func_numhybrid(Item *a) :Item_func_hybrid_result_type(a) + Item_func_numhybrid(THD *thd, Item *a): Item_func_hybrid_field_type(thd, a) { } - Item_func_numhybrid(Item *a,Item *b) - :Item_func_hybrid_result_type(a,b) + Item_func_numhybrid(THD *thd, Item *a, Item *b): + Item_func_hybrid_field_type(thd, a, b) { } - Item_func_numhybrid(Item *a,Item *b,Item *c) - :Item_func_hybrid_result_type(a,b,c) + Item_func_numhybrid(THD *thd, Item *a, Item *b, Item *c): + Item_func_hybrid_field_type(thd, a, b, c) { } - Item_func_numhybrid(List<Item> &list) - :Item_func_hybrid_result_type(list) + Item_func_numhybrid(THD *thd, List<Item> &list): + Item_func_hybrid_field_type(thd, list) { } String *str_op(String *str) { DBUG_ASSERT(0); return 0; } bool date_op(MYSQL_TIME *ltime, uint fuzzydate) { DBUG_ASSERT(0); return true; } @@ -578,8 +565,8 @@ public: class Item_func_num1: public Item_func_numhybrid { public: - Item_func_num1(Item *a) :Item_func_numhybrid(a) {} - Item_func_num1(Item *a, Item *b) :Item_func_numhybrid(a, b) {} + Item_func_num1(THD *thd, Item *a): Item_func_numhybrid(thd, a) {} + Item_func_num1(THD *thd, Item *a, Item *b): Item_func_numhybrid(thd, a, b) {} void fix_length_and_dec(); }; @@ -588,7 +575,7 @@ public: class Item_num_op :public Item_func_numhybrid { public: - Item_num_op(Item *a,Item *b) :Item_func_numhybrid(a, b) {} + Item_num_op(THD *thd, Item *a, Item *b): Item_func_numhybrid(thd, a, b) {} virtual void result_precision()= 0; virtual inline void print(String *str, enum_query_type query_type) @@ -602,26 +589,26 @@ class Item_num_op :public Item_func_numhybrid class Item_int_func :public Item_func { -protected: - bool sargable; -public: - Item_int_func() :Item_func() - { collation.set_numeric(); fix_char_length(21); sargable= false; } - Item_int_func(Item *a) :Item_func(a) - { collation.set_numeric(); fix_char_length(21); sargable= false; } - Item_int_func(Item *a,Item *b) :Item_func(a,b) - { collation.set_numeric(); fix_char_length(21); sargable= false; } - Item_int_func(Item *a,Item *b,Item *c) :Item_func(a,b,c) - { collation.set_numeric(); fix_char_length(21); sargable= false; } - Item_int_func(List<Item> &list) :Item_func(list) - { collation.set_numeric(); fix_char_length(21); sargable= false; } +public: + Item_int_func(THD *thd): Item_func(thd) + { collation.set_numeric(); fix_char_length(21); } + Item_int_func(THD *thd, Item *a): Item_func(thd, a) + { collation.set_numeric(); fix_char_length(21); } + Item_int_func(THD *thd, Item *a, Item *b): Item_func(thd, a, b) + { collation.set_numeric(); fix_char_length(21); } + Item_int_func(THD *thd, Item *a, Item *b, Item *c): Item_func(thd, a, b, c) + { collation.set_numeric(); fix_char_length(21); } + Item_int_func(THD *thd, Item *a, Item *b, Item *c, Item *d): + Item_func(thd, a, b, c, d) + { collation.set_numeric(); fix_char_length(21); } + Item_int_func(THD *thd, List<Item> &list): Item_func(thd, list) + { collation.set_numeric(); fix_char_length(21); } Item_int_func(THD *thd, Item_int_func *item) :Item_func(thd, item) - { collation.set_numeric(); sargable= false; } + { collation.set_numeric(); } double val_real(); String *val_str(String*str); enum Item_result result_type () const { return INT_RESULT; } void fix_length_and_dec() {} - bool count_sargable_conds(uchar *arg); }; @@ -630,7 +617,7 @@ class Item_func_connection_id :public Item_int_func longlong value; public: - Item_func_connection_id() {} + Item_func_connection_id(THD *thd): Item_int_func(thd) {} const char *func_name() const { return "connection_id"; } void fix_length_and_dec(); bool fix_fields(THD *thd, Item **ref); @@ -642,7 +629,7 @@ public: class Item_func_signed :public Item_int_func { public: - Item_func_signed(Item *a) :Item_int_func(a) + Item_func_signed(THD *thd, Item *a): Item_int_func(thd, a) { unsigned_flag= 0; } @@ -651,8 +638,15 @@ public: longlong val_int_from_str(int *error); void fix_length_and_dec() { - fix_char_length(min(args[0]->max_char_length(), - MY_INT64_NUM_DECIMAL_DIGITS)); + uint32 char_length= MY_MIN(args[0]->max_char_length(), + MY_INT64_NUM_DECIMAL_DIGITS); + /* + args[0]->max_char_length() can return 0. + Reserve max_length to fit at least one character for one digit, + plus one character for the sign (if signed). + */ + set_if_bigger(char_length, 1U + (unsigned_flag ? 0 : 1)); + fix_char_length(char_length); } virtual void print(String *str, enum_query_type query_type); uint decimal_precision() const { return args[0]->decimal_precision(); } @@ -662,7 +656,7 @@ public: class Item_func_unsigned :public Item_func_signed { public: - Item_func_unsigned(Item *a) :Item_func_signed(a) + Item_func_unsigned(THD *thd, Item *a): Item_func_signed(thd, a) { unsigned_flag= 1; } @@ -676,7 +670,7 @@ class Item_decimal_typecast :public Item_func { my_decimal decimal_value; public: - Item_decimal_typecast(Item *a, int len, int dec) :Item_func(a) + Item_decimal_typecast(THD *thd, Item *a, int len, int dec): Item_func(thd, a) { decimals= (uint8) dec; collation.set_numeric(); @@ -698,7 +692,8 @@ public: class Item_double_typecast :public Item_real_func { public: - Item_double_typecast(Item *a, int len, int dec) :Item_real_func(a) + Item_double_typecast(THD *thd, Item *a, int len, int dec): + Item_real_func(thd, a) { decimals= (uint8) dec; max_length= (uint32) len; @@ -715,7 +710,7 @@ public: class Item_func_additive_op :public Item_num_op { public: - Item_func_additive_op(Item *a,Item *b) :Item_num_op(a,b) {} + Item_func_additive_op(THD *thd, Item *a, Item *b): Item_num_op(thd, a, b) {} void result_precision(); bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -725,7 +720,8 @@ public: class Item_func_plus :public Item_func_additive_op { public: - Item_func_plus(Item *a,Item *b) :Item_func_additive_op(a,b) {} + Item_func_plus(THD *thd, Item *a, Item *b): + Item_func_additive_op(thd, a, b) {} const char *func_name() const { return "+"; } longlong int_op(); double real_op(); @@ -735,7 +731,8 @@ public: class Item_func_minus :public Item_func_additive_op { public: - Item_func_minus(Item *a,Item *b) :Item_func_additive_op(a,b) {} + Item_func_minus(THD *thd, Item *a, Item *b): + Item_func_additive_op(thd, a, b) {} const char *func_name() const { return "-"; } longlong int_op(); double real_op(); @@ -747,7 +744,8 @@ public: class Item_func_mul :public Item_num_op { public: - Item_func_mul(Item *a,Item *b) :Item_num_op(a,b) {} + Item_func_mul(THD *thd, Item *a, Item *b): + Item_num_op(thd, a, b) {} const char *func_name() const { return "*"; } longlong int_op(); double real_op(); @@ -762,7 +760,7 @@ class Item_func_div :public Item_num_op { public: uint prec_increment; - Item_func_div(Item *a,Item *b) :Item_num_op(a,b) {} + Item_func_div(THD *thd, Item *a, Item *b): Item_num_op(thd, a, b) {} longlong int_op() { DBUG_ASSERT(0); return 0; } double real_op(); my_decimal *decimal_op(my_decimal *); @@ -775,7 +773,7 @@ public: class Item_func_int_div :public Item_int_func { public: - Item_func_int_div(Item *a,Item *b) :Item_int_func(a,b) + Item_func_int_div(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} longlong val_int(); const char *func_name() const { return "DIV"; } @@ -794,7 +792,7 @@ public: class Item_func_mod :public Item_num_op { public: - Item_func_mod(Item *a,Item *b) :Item_num_op(a,b) {} + Item_func_mod(THD *thd, Item *a, Item *b): Item_num_op(thd, a, b) {} longlong int_op(); double real_op(); my_decimal *decimal_op(my_decimal *); @@ -809,7 +807,7 @@ public: class Item_func_neg :public Item_func_num1 { public: - Item_func_neg(Item *a) :Item_func_num1(a) {} + Item_func_neg(THD *thd, Item *a): Item_func_num1(thd, a) {} double real_op(); longlong int_op(); my_decimal *decimal_op(my_decimal *); @@ -825,7 +823,7 @@ public: class Item_func_abs :public Item_func_num1 { public: - Item_func_abs(Item *a) :Item_func_num1(a) {} + Item_func_abs(THD *thd, Item *a): Item_func_num1(thd, a) {} double real_op(); longlong int_op(); my_decimal *decimal_op(my_decimal *); @@ -840,8 +838,8 @@ public: class Item_dec_func :public Item_real_func { public: - Item_dec_func(Item *a) :Item_real_func(a) {} - Item_dec_func(Item *a,Item *b) :Item_real_func(a,b) {} + Item_dec_func(THD *thd, Item *a): Item_real_func(thd, a) {} + Item_dec_func(THD *thd, Item *a, Item *b): Item_real_func(thd, a, b) {} void fix_length_and_dec() { decimals=NOT_FIXED_DEC; max_length=float_length(decimals); @@ -852,7 +850,7 @@ class Item_dec_func :public Item_real_func class Item_func_exp :public Item_dec_func { public: - Item_func_exp(Item *a) :Item_dec_func(a) {} + Item_func_exp(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "exp"; } }; @@ -861,7 +859,7 @@ public: class Item_func_ln :public Item_dec_func { public: - Item_func_ln(Item *a) :Item_dec_func(a) {} + Item_func_ln(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "ln"; } }; @@ -870,8 +868,8 @@ public: class Item_func_log :public Item_dec_func { public: - Item_func_log(Item *a) :Item_dec_func(a) {} - Item_func_log(Item *a,Item *b) :Item_dec_func(a,b) {} + Item_func_log(THD *thd, Item *a): Item_dec_func(thd, a) {} + Item_func_log(THD *thd, Item *a, Item *b): Item_dec_func(thd, a, b) {} double val_real(); const char *func_name() const { return "log"; } }; @@ -880,7 +878,7 @@ public: class Item_func_log2 :public Item_dec_func { public: - Item_func_log2(Item *a) :Item_dec_func(a) {} + Item_func_log2(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "log2"; } }; @@ -889,7 +887,7 @@ public: class Item_func_log10 :public Item_dec_func { public: - Item_func_log10(Item *a) :Item_dec_func(a) {} + Item_func_log10(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "log10"; } }; @@ -898,7 +896,7 @@ public: class Item_func_sqrt :public Item_dec_func { public: - Item_func_sqrt(Item *a) :Item_dec_func(a) {} + Item_func_sqrt(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "sqrt"; } }; @@ -907,7 +905,7 @@ public: class Item_func_pow :public Item_dec_func { public: - Item_func_pow(Item *a,Item *b) :Item_dec_func(a,b) {} + Item_func_pow(THD *thd, Item *a, Item *b): Item_dec_func(thd, a, b) {} double val_real(); const char *func_name() const { return "pow"; } }; @@ -916,7 +914,7 @@ public: class Item_func_acos :public Item_dec_func { public: - Item_func_acos(Item *a) :Item_dec_func(a) {} + Item_func_acos(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "acos"; } }; @@ -924,7 +922,7 @@ public: class Item_func_asin :public Item_dec_func { public: - Item_func_asin(Item *a) :Item_dec_func(a) {} + Item_func_asin(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "asin"; } }; @@ -932,8 +930,8 @@ public: class Item_func_atan :public Item_dec_func { public: - Item_func_atan(Item *a) :Item_dec_func(a) {} - Item_func_atan(Item *a,Item *b) :Item_dec_func(a,b) {} + Item_func_atan(THD *thd, Item *a): Item_dec_func(thd, a) {} + Item_func_atan(THD *thd, Item *a, Item *b): Item_dec_func(thd, a, b) {} double val_real(); const char *func_name() const { return "atan"; } }; @@ -941,7 +939,7 @@ public: class Item_func_cos :public Item_dec_func { public: - Item_func_cos(Item *a) :Item_dec_func(a) {} + Item_func_cos(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "cos"; } }; @@ -949,7 +947,7 @@ public: class Item_func_sin :public Item_dec_func { public: - Item_func_sin(Item *a) :Item_dec_func(a) {} + Item_func_sin(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "sin"; } }; @@ -957,7 +955,7 @@ public: class Item_func_tan :public Item_dec_func { public: - Item_func_tan(Item *a) :Item_dec_func(a) {} + Item_func_tan(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "tan"; } }; @@ -965,23 +963,16 @@ public: class Item_func_cot :public Item_dec_func { public: - Item_func_cot(Item *a) :Item_dec_func(a) {} + Item_func_cot(THD *thd, Item *a): Item_dec_func(thd, a) {} double val_real(); const char *func_name() const { return "cot"; } }; -class Item_func_integer :public Item_int_func -{ -public: - inline Item_func_integer(Item *a) :Item_int_func(a) {} - void fix_length_and_dec(); -}; - class Item_func_int_val :public Item_func_num1 { public: - Item_func_int_val(Item *a) :Item_func_num1(a) {} + Item_func_int_val(THD *thd, Item *a): Item_func_num1(thd, a) {} void fix_length_and_dec(); }; @@ -989,7 +980,7 @@ public: class Item_func_ceiling :public Item_func_int_val { public: - Item_func_ceiling(Item *a) :Item_func_int_val(a) {} + Item_func_ceiling(THD *thd, Item *a): Item_func_int_val(thd, a) {} const char *func_name() const { return "ceiling"; } longlong int_op(); double real_op(); @@ -1002,7 +993,7 @@ public: class Item_func_floor :public Item_func_int_val { public: - Item_func_floor(Item *a) :Item_func_int_val(a) {} + Item_func_floor(THD *thd, Item *a): Item_func_int_val(thd, a) {} const char *func_name() const { return "floor"; } longlong int_op(); double real_op(); @@ -1017,8 +1008,8 @@ class Item_func_round :public Item_func_num1 { bool truncate; public: - Item_func_round(Item *a, Item *b, bool trunc_arg) - :Item_func_num1(a,b), truncate(trunc_arg) {} + Item_func_round(THD *thd, Item *a, Item *b, bool trunc_arg) + :Item_func_num1(thd, a, b), truncate(trunc_arg) {} const char *func_name() const { return truncate ? "truncate" : "round"; } double real_op(); longlong int_op(); @@ -1032,8 +1023,9 @@ class Item_func_rand :public Item_real_func struct my_rnd_struct *rand; bool first_eval; // TRUE if val_real() is called 1st time public: - Item_func_rand(Item *a) :Item_real_func(a), rand(0), first_eval(TRUE) {} - Item_func_rand() :Item_real_func() {} + Item_func_rand(THD *thd, Item *a): + Item_real_func(thd, a), rand(0), first_eval(TRUE) {} + Item_func_rand(THD *thd): Item_real_func(thd) {} double val_real(); const char *func_name() const { return "rand"; } bool const_item() const { return 0; } @@ -1052,7 +1044,7 @@ private: class Item_func_sign :public Item_int_func { public: - Item_func_sign(Item *a) :Item_int_func(a) {} + Item_func_sign(THD *thd, Item *a): Item_int_func(thd, a) {} const char *func_name() const { return "sign"; } longlong val_int(); }; @@ -1063,8 +1055,9 @@ class Item_func_units :public Item_real_func char *name; double mul,add; public: - Item_func_units(char *name_arg,Item *a,double mul_arg,double add_arg) - :Item_real_func(a),name(name_arg),mul(mul_arg),add(add_arg) {} + Item_func_units(THD *thd, char *name_arg, Item *a, double mul_arg, + double add_arg): + Item_real_func(thd, a), name(name_arg), mul(mul_arg), add(add_arg) {} double val_real(); const char *func_name() const { return name; } void fix_length_and_dec() @@ -1072,39 +1065,44 @@ public: }; -class Item_func_min_max :public Item_func +/** + Item_func_min_max does not derive from Item_func_hybrid_field_type + because the way how its methods val_xxx() and get_date() work depend + not only by its arguments, but also on the context in which + LEAST() and GREATEST() appear. + For example, using Item_func_min_max in a CAST like this: + CAST(LEAST('11','2') AS SIGNED) + forces Item_func_min_max to compare the arguments as numbers rather + than strings. + Perhaps this should be changed eventually (see MDEV-5893). +*/ +class Item_func_min_max :public Item_hybrid_func { - Item_result cmp_type; String tmp_value; int cmp_sign; - /* An item used for issuing warnings while string to DATETIME conversion. */ - Item *compare_as_dates; -protected: - enum_field_types cached_field_type; public: - Item_func_min_max(List<Item> &list,int cmp_sign_arg) :Item_func(list), - cmp_type(INT_RESULT), cmp_sign(cmp_sign_arg), compare_as_dates(0) {} + Item_func_min_max(THD *thd, List<Item> &list, int cmp_sign_arg): + Item_hybrid_func(thd, list), cmp_sign(cmp_sign_arg) + {} double val_real(); longlong val_int(); String *val_str(String *); my_decimal *val_decimal(my_decimal *); bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); void fix_length_and_dec(); - enum Item_result result_type () const { return cmp_type; } - enum_field_types field_type() const { return cached_field_type; } }; class Item_func_min :public Item_func_min_max { public: - Item_func_min(List<Item> &list) :Item_func_min_max(list,1) {} + Item_func_min(THD *thd, List<Item> &list): Item_func_min_max(thd, list, 1) {} const char *func_name() const { return "least"; } }; class Item_func_max :public Item_func_min_max { public: - Item_func_max(List<Item> &list) :Item_func_min_max(list,-1) {} + Item_func_max(THD *thd, List<Item> &list): Item_func_min_max(thd, list, -1) {} const char *func_name() const { return "greatest"; } }; @@ -1117,15 +1115,18 @@ public: class Item_func_rollup_const :public Item_func { public: - Item_func_rollup_const(Item *a) :Item_func(a) + Item_func_rollup_const(THD *thd, Item *a): Item_func(thd, a) { name= a->name; name_length= a->name_length; } - double val_real() { return args[0]->val_real(); } - longlong val_int() { return args[0]->val_int(); } - String *val_str(String *str) { return args[0]->val_str(str); } - my_decimal *val_decimal(my_decimal *dec) { return args[0]->val_decimal(dec); } + double val_real() { return val_real_from_item(args[0]); } + longlong val_int() { return val_int_from_item(args[0]); } + String *val_str(String *str) { return val_str_from_item(args[0], str); } + my_decimal *val_decimal(my_decimal *dec) + { return val_decimal_from_item(args[0], dec); } + bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) + { return get_date_from_item(args[0], ltime, fuzzydate); } const char *func_name() const { return "rollup_const"; } bool const_item() const { return 0; } Item_result result_type() const { return args[0]->result_type(); } @@ -1144,7 +1145,7 @@ class Item_func_length :public Item_int_func { String value; public: - Item_func_length(Item *a) :Item_int_func(a) {} + Item_func_length(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "length"; } void fix_length_and_dec() { max_length=10; } @@ -1153,7 +1154,7 @@ public: class Item_func_bit_length :public Item_func_length { public: - Item_func_bit_length(Item *a) :Item_func_length(a) {} + Item_func_bit_length(THD *thd, Item *a): Item_func_length(thd, a) {} longlong val_int() { DBUG_ASSERT(fixed == 1); return Item_func_length::val_int()*8; } const char *func_name() const { return "bit_length"; } @@ -1163,7 +1164,7 @@ class Item_func_char_length :public Item_int_func { String value; public: - Item_func_char_length(Item *a) :Item_int_func(a) {} + Item_func_char_length(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "char_length"; } void fix_length_and_dec() { max_length=10; } @@ -1172,15 +1173,18 @@ public: class Item_func_coercibility :public Item_int_func { public: - Item_func_coercibility(Item *a) :Item_int_func(a) {} + Item_func_coercibility(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "coercibility"; } void fix_length_and_dec() { max_length=10; maybe_null= 0; } - bool eval_not_null_tables(uchar *opt_arg) + bool eval_not_null_tables(uchar *) { not_null_tables_cache= 0; - return 0; + return false; } + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) + { return this; } + bool const_item() const { return true; } }; class Item_func_locate :public Item_int_func @@ -1188,8 +1192,8 @@ class Item_func_locate :public Item_int_func String value1,value2; DTCollation cmp_collation; public: - Item_func_locate(Item *a,Item *b) :Item_int_func(a,b) {} - Item_func_locate(Item *a,Item *b,Item *c) :Item_int_func(a,b,c) {} + Item_func_locate(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} + Item_func_locate(THD *thd, Item *a, Item *b, Item *c): Item_int_func(thd, a, b, c) {} const char *func_name() const { return "locate"; } longlong val_int(); void fix_length_and_dec(); @@ -1203,7 +1207,7 @@ class Item_func_field :public Item_int_func Item_result cmp_type; DTCollation cmp_collation; public: - Item_func_field(List<Item> &list) :Item_int_func(list) {} + Item_func_field(THD *thd, List<Item> &list): Item_int_func(thd, list) {} longlong val_int(); const char *func_name() const { return "field"; } void fix_length_and_dec(); @@ -1214,7 +1218,7 @@ class Item_func_ascii :public Item_int_func { String value; public: - Item_func_ascii(Item *a) :Item_int_func(a) {} + Item_func_ascii(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "ascii"; } void fix_length_and_dec() { max_length=3; } @@ -1224,7 +1228,7 @@ class Item_func_ord :public Item_int_func { String value; public: - Item_func_ord(Item *a) :Item_int_func(a) {} + Item_func_ord(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "ord"; } }; @@ -1236,7 +1240,8 @@ class Item_func_find_in_set :public Item_int_func ulonglong enum_bit; DTCollation cmp_collation; public: - Item_func_find_in_set(Item *a,Item *b) :Item_int_func(a,b),enum_value(0) {} + Item_func_find_in_set(THD *thd, Item *a, Item *b): + Item_int_func(thd, a, b), enum_value(0) {} longlong val_int(); const char *func_name() const { return "find_in_set"; } void fix_length_and_dec(); @@ -1247,8 +1252,8 @@ public: class Item_func_bit: public Item_int_func { public: - Item_func_bit(Item *a, Item *b) :Item_int_func(a, b) {} - Item_func_bit(Item *a) :Item_int_func(a) {} + Item_func_bit(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} + Item_func_bit(THD *thd, Item *a): Item_int_func(thd, a) {} void fix_length_and_dec() { unsigned_flag= 1; } virtual inline void print(String *str, enum_query_type query_type) @@ -1260,7 +1265,7 @@ public: class Item_func_bit_or :public Item_func_bit { public: - Item_func_bit_or(Item *a, Item *b) :Item_func_bit(a, b) {} + Item_func_bit_or(THD *thd, Item *a, Item *b): Item_func_bit(thd, a, b) {} longlong val_int(); const char *func_name() const { return "|"; } }; @@ -1268,7 +1273,7 @@ public: class Item_func_bit_and :public Item_func_bit { public: - Item_func_bit_and(Item *a, Item *b) :Item_func_bit(a, b) {} + Item_func_bit_and(THD *thd, Item *a, Item *b): Item_func_bit(thd, a, b) {} longlong val_int(); const char *func_name() const { return "&"; } }; @@ -1276,7 +1281,7 @@ public: class Item_func_bit_count :public Item_int_func { public: - Item_func_bit_count(Item *a) :Item_int_func(a) {} + Item_func_bit_count(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "bit_count"; } void fix_length_and_dec() { max_length=2; } @@ -1285,7 +1290,7 @@ public: class Item_func_shift_left :public Item_func_bit { public: - Item_func_shift_left(Item *a, Item *b) :Item_func_bit(a, b) {} + Item_func_shift_left(THD *thd, Item *a, Item *b): Item_func_bit(thd, a, b) {} longlong val_int(); const char *func_name() const { return "<<"; } }; @@ -1293,7 +1298,7 @@ public: class Item_func_shift_right :public Item_func_bit { public: - Item_func_shift_right(Item *a, Item *b) :Item_func_bit(a, b) {} + Item_func_shift_right(THD *thd, Item *a, Item *b): Item_func_bit(thd, a, b) {} longlong val_int(); const char *func_name() const { return ">>"; } }; @@ -1301,7 +1306,7 @@ public: class Item_func_bit_neg :public Item_func_bit { public: - Item_func_bit_neg(Item *a) :Item_func_bit(a) {} + Item_func_bit_neg(THD *thd, Item *a): Item_func_bit(thd, a) {} longlong val_int(); const char *func_name() const { return "~"; } @@ -1315,8 +1320,8 @@ public: class Item_func_last_insert_id :public Item_int_func { public: - Item_func_last_insert_id() :Item_int_func() {} - Item_func_last_insert_id(Item *a) :Item_int_func(a) {} + Item_func_last_insert_id(THD *thd): Item_int_func(thd) {} + Item_func_last_insert_id(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "last_insert_id"; } void fix_length_and_dec() @@ -1337,8 +1342,8 @@ public: class Item_func_benchmark :public Item_int_func { public: - Item_func_benchmark(Item *count_expr, Item *expr) - :Item_int_func(count_expr, expr) + Item_func_benchmark(THD *thd, Item *count_expr, Item *expr): + Item_int_func(thd, count_expr, expr) {} longlong val_int(); const char *func_name() const { return "benchmark"; } @@ -1351,17 +1356,20 @@ public: }; +void item_func_sleep_init(void); +void item_func_sleep_free(void); + class Item_func_sleep :public Item_int_func { public: - Item_func_sleep(Item *a) :Item_int_func(a) {} + Item_func_sleep(THD *thd, Item *a): Item_int_func(thd, a) {} bool const_item() const { return 0; } const char *func_name() const { return "sleep"; } - void update_used_tables() + table_map used_tables() const { - Item_int_func::update_used_tables(); - used_tables_cache|= RAND_TABLE_BIT; + return used_tables_cache | RAND_TABLE_BIT; } + bool is_expensive() { return 1; } longlong val_int(); bool check_vcol_func_processor(uchar *int_arg) { @@ -1375,23 +1383,31 @@ public: class Item_udf_func :public Item_func { + /** + Mark "this" as non-deterministic if it uses no tables + and is not a constant at the same time. + */ + void set_non_deterministic_if_needed() + { + if (!const_item_cache && !used_tables_cache) + used_tables_cache= RAND_TABLE_BIT; + } protected: udf_handler udf; bool is_expensive_processor(uchar *arg) { return TRUE; } public: - Item_udf_func(udf_func *udf_arg) - :Item_func(), udf(udf_arg) {} - Item_udf_func(udf_func *udf_arg, List<Item> &list) - :Item_func(list), udf(udf_arg) {} + Item_udf_func(THD *thd, udf_func *udf_arg): + Item_func(thd), udf(udf_arg) {} + Item_udf_func(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_func(thd, list), udf(udf_arg) {} const char *func_name() const { return udf.name(); } enum Functype functype() const { return UDF_FUNC; } bool fix_fields(THD *thd, Item **ref) { DBUG_ASSERT(fixed == 0); bool res= udf.fix_fields(thd, this, arg_count, args); - used_tables_cache= udf.used_tables_cache; - const_item_cache= udf.const_item_cache; + set_non_deterministic_if_needed(); fixed= 1; return res; } @@ -1442,8 +1458,7 @@ public: !(used_tables_cache & RAND_TABLE_BIT)) { Item_func::update_used_tables(); - if (!const_item_cache && !used_tables_cache) - used_tables_cache= RAND_TABLE_BIT; + set_non_deterministic_if_needed(); } } void cleanup(); @@ -1461,11 +1476,11 @@ public: class Item_func_udf_float :public Item_udf_func { public: - Item_func_udf_float(udf_func *udf_arg) - :Item_udf_func(udf_arg) {} - Item_func_udf_float(udf_func *udf_arg, - List<Item> &list) - :Item_udf_func(udf_arg, list) {} + Item_func_udf_float(THD *thd, udf_func *udf_arg): + Item_udf_func(thd, udf_arg) {} + Item_func_udf_float(THD *thd, udf_func *udf_arg, + List<Item> &list): + Item_udf_func(thd, udf_arg, list) {} longlong val_int() { DBUG_ASSERT(fixed == 1); @@ -1490,11 +1505,11 @@ class Item_func_udf_float :public Item_udf_func class Item_func_udf_int :public Item_udf_func { public: - Item_func_udf_int(udf_func *udf_arg) - :Item_udf_func(udf_arg) {} - Item_func_udf_int(udf_func *udf_arg, - List<Item> &list) - :Item_udf_func(udf_arg, list) {} + Item_func_udf_int(THD *thd, udf_func *udf_arg): + Item_udf_func(thd, udf_arg) {} + Item_func_udf_int(THD *thd, udf_func *udf_arg, + List<Item> &list): + Item_udf_func(thd, udf_arg, list) {} longlong val_int(); double val_real() { return (double) Item_func_udf_int::val_int(); } String *val_str(String *str); @@ -1506,10 +1521,10 @@ public: class Item_func_udf_decimal :public Item_udf_func { public: - Item_func_udf_decimal(udf_func *udf_arg) - :Item_udf_func(udf_arg) {} - Item_func_udf_decimal(udf_func *udf_arg, List<Item> &list) - :Item_udf_func(udf_arg, list) {} + Item_func_udf_decimal(THD *thd, udf_func *udf_arg): + Item_udf_func(thd, udf_arg) {} + Item_func_udf_decimal(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_udf_func(thd, udf_arg, list) {} longlong val_int(); double val_real(); my_decimal *val_decimal(my_decimal *); @@ -1522,10 +1537,10 @@ public: class Item_func_udf_str :public Item_udf_func { public: - Item_func_udf_str(udf_func *udf_arg) - :Item_udf_func(udf_arg) {} - Item_func_udf_str(udf_func *udf_arg, List<Item> &list) - :Item_udf_func(udf_arg, list) {} + Item_func_udf_str(THD *thd, udf_func *udf_arg): + Item_udf_func(thd, udf_arg) {} + Item_func_udf_str(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_udf_func(thd, udf_arg, list) {} String *val_str(String *); double val_real() { @@ -1560,10 +1575,10 @@ public: class Item_func_udf_float :public Item_real_func { public: - Item_func_udf_float(udf_func *udf_arg) - :Item_real_func() {} - Item_func_udf_float(udf_func *udf_arg, List<Item> &list) - :Item_real_func(list) {} + Item_func_udf_float(THD *thd, udf_func *udf_arg): + Item_real_func(thd) {} + Item_func_udf_float(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_real_func(thd, list) {} double val_real() { DBUG_ASSERT(fixed == 1); return 0.0; } }; @@ -1571,10 +1586,10 @@ class Item_func_udf_float :public Item_real_func class Item_func_udf_int :public Item_int_func { public: - Item_func_udf_int(udf_func *udf_arg) - :Item_int_func() {} - Item_func_udf_int(udf_func *udf_arg, List<Item> &list) - :Item_int_func(list) {} + Item_func_udf_int(THD *thd, udf_func *udf_arg): + Item_int_func(thd) {} + Item_func_udf_int(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_int_func(thd, list) {} longlong val_int() { DBUG_ASSERT(fixed == 1); return 0; } }; @@ -1582,10 +1597,10 @@ public: class Item_func_udf_decimal :public Item_int_func { public: - Item_func_udf_decimal(udf_func *udf_arg) - :Item_int_func() {} - Item_func_udf_decimal(udf_func *udf_arg, List<Item> &list) - :Item_int_func(list) {} + Item_func_udf_decimal(THD *thd, udf_func *udf_arg): + Item_int_func(thd) {} + Item_func_udf_decimal(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_int_func(thd, list) {} my_decimal *val_decimal(my_decimal *) { DBUG_ASSERT(fixed == 1); return 0; } }; @@ -1593,10 +1608,10 @@ public: class Item_func_udf_str :public Item_func { public: - Item_func_udf_str(udf_func *udf_arg) - :Item_func() {} - Item_func_udf_str(udf_func *udf_arg, List<Item> &list) - :Item_func(list) {} + Item_func_udf_str(THD *thd, udf_func *udf_arg): + Item_func(thd) {} + Item_func_udf_str(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_func(thd, list) {} String *val_str(String *) { DBUG_ASSERT(fixed == 1); null_value=1; return 0; } double val_real() { DBUG_ASSERT(fixed == 1); null_value= 1; return 0.0; } @@ -1607,23 +1622,23 @@ public: #endif /* HAVE_DLOPEN */ -/* -** User level locks -*/ - -class User_level_lock; -void item_user_lock_init(void); -void item_user_lock_release(User_level_lock *ull); -void item_user_lock_free(void); +void mysql_ull_cleanup(THD *thd); +void mysql_ull_set_explicit_lock_duration(THD *thd); class Item_func_get_lock :public Item_int_func { String value; public: - Item_func_get_lock(Item *a,Item *b) :Item_int_func(a,b) {} + Item_func_get_lock(THD *thd, Item *a, Item *b) :Item_int_func(thd, a, b) {} longlong val_int(); const char *func_name() const { return "get_lock"; } void fix_length_and_dec() { max_length=1; maybe_null=1;} + table_map used_tables() const + { + return used_tables_cache | RAND_TABLE_BIT; + } + bool const_item() const { return 0; } + bool is_expensive() { return 1; } bool check_vcol_func_processor(uchar *int_arg) { return trace_unsupported_by_check_vcol_func_processor(func_name()); @@ -1634,10 +1649,16 @@ class Item_func_release_lock :public Item_int_func { String value; public: - Item_func_release_lock(Item *a) :Item_int_func(a) {} + Item_func_release_lock(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "release_lock"; } - void fix_length_and_dec() { max_length=1; maybe_null=1;} + void fix_length_and_dec() { max_length= 1; maybe_null= 1;} + table_map used_tables() const + { + return used_tables_cache | RAND_TABLE_BIT; + } + bool const_item() const { return 0; } + bool is_expensive() { return 1; } bool check_vcol_func_processor(uchar *int_arg) { return trace_unsupported_by_check_vcol_func_processor(func_name()); @@ -1650,8 +1671,11 @@ class Item_master_pos_wait :public Item_int_func { String value; public: - Item_master_pos_wait(Item *a,Item *b) :Item_int_func(a,b) {} - Item_master_pos_wait(Item *a,Item *b,Item *c) :Item_int_func(a,b,c) {} + Item_master_pos_wait(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} + Item_master_pos_wait(THD *thd, Item *a, Item *b, Item *c): + Item_int_func(thd, a, b, c) {} + Item_master_pos_wait(THD *thd, Item *a, Item *b, Item *c, Item *d): + Item_int_func(thd, a, b, c, d) {} longlong val_int(); const char *func_name() const { return "master_pos_wait"; } void fix_length_and_dec() { max_length=21; maybe_null=1;} @@ -1662,14 +1686,49 @@ public: }; +class Item_master_gtid_wait :public Item_int_func +{ + String value; +public: + Item_master_gtid_wait(THD *thd, Item *a): Item_int_func(thd, a) {} + Item_master_gtid_wait(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} + longlong val_int(); + const char *func_name() const { return "master_gtid_wait"; } + void fix_length_and_dec() { max_length=2; } + bool check_vcol_func_processor(uchar *int_arg) + { + return trace_unsupported_by_check_vcol_func_processor(func_name()); + } +}; + + /* Handling of user definable variables */ class user_var_entry; -class Item_func_set_user_var :public Item_func + +/** + A class to set and get user variables +*/ +class Item_func_user_var :public Item_hybrid_func +{ +protected: + user_var_entry *m_var_entry; +public: + LEX_STRING name; // keep it public + Item_func_user_var(THD *thd, LEX_STRING a) + :Item_hybrid_func(thd), m_var_entry(NULL), name(a) { } + Item_func_user_var(THD *thd, LEX_STRING a, Item *b) + :Item_hybrid_func(thd, b), m_var_entry(NULL), name(a) { } + Item_func_user_var(THD *thd, Item_func_user_var *item) + :Item_hybrid_func(thd, item), + m_var_entry(item->m_var_entry), name(item->name) { } + bool check_vcol_func_processor(uchar *int_arg) { return true; } +}; + + +class Item_func_set_user_var :public Item_func_user_var { - enum Item_result cached_result_type; - user_var_entry *entry; /* The entry_thread_id variable is used: 1) to skip unnecessary updates of the entry field (see above); @@ -1681,7 +1740,6 @@ class Item_func_set_user_var :public Item_func user variable it the first connection context). */ my_thread_id entry_thread_id; - char buffer[MAX_FIELD_WIDTH]; String value; my_decimal decimal_buff; bool null_item; @@ -1694,17 +1752,15 @@ class Item_func_set_user_var :public Item_func } save_result; public: - LEX_STRING name; // keep it public - Item_func_set_user_var(LEX_STRING a,Item *b) - :Item_func(b), cached_result_type(INT_RESULT), - entry(NULL), entry_thread_id(0), name(a) + Item_func_set_user_var(THD *thd, LEX_STRING a, Item *b): + Item_func_user_var(thd, a, b), + entry_thread_id(0) {} Item_func_set_user_var(THD *thd, Item_func_set_user_var *item) - :Item_func(thd, item), cached_result_type(item->cached_result_type), - entry(item->entry), entry_thread_id(item->entry_thread_id), + :Item_func_user_var(thd, item), + entry_thread_id(item->entry_thread_id), value(item->value), decimal_buff(item->decimal_buff), - null_item(item->null_item), save_result(item->save_result), - name(item->name) + null_item(item->null_item), save_result(item->save_result) {} enum Functype functype() const { return SUSERVAR_FUNC; } @@ -1719,15 +1775,20 @@ public: my_decimal *val_decimal_result(my_decimal *); bool is_null_result(); bool update_hash(void *ptr, uint length, enum Item_result type, - CHARSET_INFO *cs, Derivation dv, bool unsigned_arg); + CHARSET_INFO *cs, bool unsigned_arg); bool send(Protocol *protocol, String *str_arg); void make_field(Send_field *tmp_field); bool check(bool use_result_field); void save_item_result(Item *item); bool update(); - enum Item_result result_type () const { return cached_result_type; } bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec(); + Field *create_field_for_create_select(TABLE *table) + { + return result_type() != STRING_RESULT ? + create_tmp_field(false, table, MY_INT32_NUM_DECIMAL_DIGITS) : + tmp_table_field_from_field_type(table, false, true); + } virtual void print(String *str, enum_query_type query_type); void print_as_stmt(String *str, enum_query_type query_type); const char *func_name() const { return "set_user_var"; } @@ -1737,25 +1798,22 @@ public: { return save_in_field(field, no_conversions, 1); } - void save_org_in_field(Field *field) { (void)save_in_field(field, 1, 0); } + void save_org_in_field(Field *field, + fast_field_copier data __attribute__ ((__unused__))) + { (void)save_in_field(field, 1, 0); } bool register_field_in_read_map(uchar *arg); bool register_field_in_bitmap(uchar *arg); bool set_entry(THD *thd, bool create_if_not_exists); void cleanup(); - bool check_vcol_func_processor(uchar *int_arg) {return TRUE;} }; -class Item_func_get_user_var :public Item_func, +class Item_func_get_user_var :public Item_func_user_var, private Settable_routine_parameter { - user_var_entry *var_entry; - Item_result m_cached_result_type; - public: - LEX_STRING name; // keep it public - Item_func_get_user_var(LEX_STRING a): - Item_func(), m_cached_result_type(STRING_RESULT), name(a) {} + Item_func_get_user_var(THD *thd, LEX_STRING a): + Item_func_user_var(thd, a) {} enum Functype functype() const { return GUSERVAR_FUNC; } LEX_STRING get_name() { return name; } double val_real(); @@ -1764,7 +1822,6 @@ public: String *val_str(String* str); void fix_length_and_dec(); virtual void print(String *str, enum_query_type query_type); - enum Item_result result_type() const; /* We must always return variables as strings to guard against selects of type select @t1:=1,@t1,@t:="hello",@t from foo where (@t1:= t2.b) @@ -1782,7 +1839,6 @@ public: { return this; } - bool check_vcol_func_processor(uchar *int_arg) { return TRUE;} }; @@ -1800,7 +1856,7 @@ class Item_user_var_as_out_param :public Item LEX_STRING name; user_var_entry *entry; public: - Item_user_var_as_out_param(LEX_STRING a) : name(a) + Item_user_var_as_out_param(THD *thd, LEX_STRING a): Item(thd), name(a) { set_name(a.str, 0, system_charset_info); } /* We should return something different from FIELD_ITEM here */ enum Type type() const { return STRING_ITEM;} @@ -1835,7 +1891,8 @@ class Item_func_get_system_var :public Item_func uchar cache_present; public: - Item_func_get_system_var(sys_var *var_arg, enum_var_type var_type_arg, + Item_func_get_system_var(THD *thd, sys_var *var_arg, + enum_var_type var_type_arg, LEX_STRING *component_arg, const char *name_arg, size_t name_len_arg); enum Functype functype() const { return GSYSVAR_FUNC; } @@ -1869,16 +1926,6 @@ public: }; -class Item_func_inet_aton : public Item_int_func -{ -public: - Item_func_inet_aton(Item *a) :Item_int_func(a) {} - longlong val_int(); - const char *func_name() const { return "inet_aton"; } - void fix_length_and_dec() { decimals= 0; max_length= 21; maybe_null= 1; unsigned_flag= 1;} -}; - - /* for fulltext search */ class Item_func_match :public Item_real_func @@ -1894,8 +1941,9 @@ public: String value; // value of concat_ws String search_value; // key_item()'s value converted to cmp_collation - Item_func_match(List<Item> &a, uint b): Item_real_func(a), key(0), flags(b), - join_key(0), ft_handler(0), table(0), master(0), concat_ws(0) { } + Item_func_match(THD *thd, List<Item> &a, uint b): + Item_real_func(thd, a), key(0), flags(b), join_key(0), ft_handler(0), + table(0), master(0), concat_ws(0) { } void cleanup() { DBUG_ENTER("Item_func_match::cleanup"); @@ -1923,19 +1971,54 @@ public: virtual void print(String *str, enum_query_type query_type); bool fix_index(); - void init_search(bool no_order); + void init_search(THD *thd, bool no_order); bool check_vcol_func_processor(uchar *int_arg) { /* TODO: consider adding in support for the MATCH-based virtual columns */ return trace_unsupported_by_check_vcol_func_processor(func_name()); } +private: + /** + Check whether storage engine for given table, + allows FTS Boolean search on non-indexed columns. + + @todo A flag should be added to the extended fulltext API so that + it may be checked whether search on non-indexed columns are + supported. Currently, it is not possible to check for such a + flag since @c this->ft_handler is not yet set when this function is + called. The current hack is to assume that search on non-indexed + columns are supported for engines that does not support the extended + fulltext API (e.g., MyISAM), while it is not supported for other + engines (e.g., InnoDB) + + @param table_arg Table for which storage engine to check + + @retval true if BOOLEAN search on non-indexed columns is supported + @retval false otherwise + */ + bool allows_search_on_non_indexed_columns(TABLE* table_arg) + { + // Only Boolean search may support non_indexed columns + if (!(flags & FT_BOOL)) + return false; + + DBUG_ASSERT(table_arg && table_arg->file); + + // Assume that if extended fulltext API is not supported, + // non-indexed columns are allowed. This will be true for MyISAM. + if ((table_arg->file->ha_table_flags() & HA_CAN_FULLTEXT_EXT) == 0) + return true; + + return false; + } + }; class Item_func_bit_xor : public Item_func_bit { public: - Item_func_bit_xor(Item *a, Item *b) :Item_func_bit(a, b) {} + Item_func_bit_xor(THD *thd, Item *a, Item *b): Item_func_bit(thd, a, b) {} longlong val_int(); const char *func_name() const { return "^"; } }; @@ -1944,7 +2027,7 @@ class Item_func_is_free_lock :public Item_int_func { String value; public: - Item_func_is_free_lock(Item *a) :Item_int_func(a) {} + Item_func_is_free_lock(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "is_free_lock"; } void fix_length_and_dec() { decimals=0; max_length=1; maybe_null=1;} @@ -1958,7 +2041,7 @@ class Item_func_is_used_lock :public Item_int_func { String value; public: - Item_func_is_used_lock(Item *a) :Item_int_func(a) {} + Item_func_is_used_lock(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "is_used_lock"; } void fix_length_and_dec() { decimals=0; max_length=10; maybe_null=1;} @@ -1981,7 +2064,7 @@ enum Cast_target class Item_func_row_count :public Item_int_func { public: - Item_func_row_count() :Item_int_func() {} + Item_func_row_count(THD *thd): Item_int_func(thd) {} longlong val_int(); const char *func_name() const { return "row_count"; } void fix_length_and_dec() { decimals= 0; maybe_null=0; } @@ -2026,9 +2109,9 @@ protected: public: - Item_func_sp(Name_resolution_context *context_arg, sp_name *name); + Item_func_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name); - Item_func_sp(Name_resolution_context *context_arg, + Item_func_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name, List<Item> &list); virtual ~Item_func_sp() @@ -2042,8 +2125,12 @@ public: enum enum_field_types field_type() const; - Field *tmp_table_field(TABLE *t_arg); - + Field *create_field_for_create_select(TABLE *table) + { + return result_type() != STRING_RESULT ? + sp_result_field : + tmp_table_field_from_field_type(table, false, false); + } void make_field(Send_field *tmp_field); Item_result result_type() const; @@ -2127,7 +2214,7 @@ public: class Item_func_found_rows :public Item_int_func { public: - Item_func_found_rows() :Item_int_func() {} + Item_func_found_rows(THD *thd): Item_int_func(thd) {} longlong val_int(); const char *func_name() const { return "found_rows"; } void fix_length_and_dec() { decimals= 0; maybe_null=0; } @@ -2143,7 +2230,7 @@ void uuid_short_init(); class Item_func_uuid_short :public Item_int_func { public: - Item_func_uuid_short() :Item_int_func() {} + Item_func_uuid_short(THD *thd): Item_int_func(thd) {} const char *func_name() const { return "uuid_short"; } longlong val_int(); bool const_item() const { return false; } @@ -2162,7 +2249,7 @@ class Item_func_last_value :public Item_func protected: Item *last_value; public: - Item_func_last_value(List<Item> &list) :Item_func(list) {} + Item_func_last_value(THD *thd, List<Item> &list): Item_func(thd, list) {} double val_real(); longlong val_int(); String *val_str(String *); @@ -2189,12 +2276,16 @@ public: Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name, LEX_STRING component); extern bool check_reserved_words(LEX_STRING *name); -extern enum_field_types agg_field_type(Item **items, uint nitems); +extern enum_field_types agg_field_type(Item **items, uint nitems, + bool treat_bit_as_number); Item *find_date_time_item(Item **args, uint nargs, uint col); double my_double_round(double value, longlong dec, bool dec_unsigned, bool truncate); -bool eval_const_cond(COND *cond); extern bool volatile mqh_used; +bool update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length, + Item_result type, CHARSET_INFO *cs, + bool unsigned_arg); + #endif /* ITEM_FUNC_INCLUDED */ diff --git a/sql/item_geofunc.cc b/sql/item_geofunc.cc index 131b54bef8e..51a4636df1f 100644 --- a/sql/item_geofunc.cc +++ b/sql/item_geofunc.cc @@ -26,6 +26,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" /* It is necessary to include set_var.h instead of item.h because there @@ -36,9 +37,10 @@ #include "set_var.h" #ifdef HAVE_SPATIAL #include <m_ctype.h> +#include "opt_range.h" -Field *Item_geometry_func::tmp_table_field(TABLE *t_arg) +Field *Item_geometry_func::create_field_for_create_select(TABLE *t_arg) { Field *result; if ((result= new Field_geom(max_length, maybe_null, name, t_arg->s, @@ -217,6 +219,130 @@ String *Item_func_envelope::val_str(String *str) } +int Item_func_boundary::Transporter::single_point(double x, double y) +{ + return 0; +} + + +int Item_func_boundary::Transporter::start_line() +{ + n_points= 0; + current_type= Gcalc_function::shape_line; + return 0; +} + + +int Item_func_boundary::Transporter::complete_line() +{ + current_type= (Gcalc_function::shape_type) 0; + if (n_points > 1) + return m_receiver->single_point(last_x, last_y); + return 0; +} + + +int Item_func_boundary::Transporter::start_poly() +{ + current_type= Gcalc_function::shape_polygon; + return 0; +} + + +int Item_func_boundary::Transporter::complete_poly() +{ + current_type= (Gcalc_function::shape_type) 0; + return 0; +} + + +int Item_func_boundary::Transporter::start_ring() +{ + n_points= 0; + return m_receiver->start_shape(Gcalc_function::shape_line); +} + + +int Item_func_boundary::Transporter::complete_ring() +{ + if (n_points > 1) + { + m_receiver->add_point(last_x, last_y); + } + m_receiver->complete_shape(); + return 0; +} + + +int Item_func_boundary::Transporter::add_point(double x, double y) +{ + ++n_points; + if (current_type== Gcalc_function::shape_polygon) + { + /* Polygon's ring case */ + if (n_points == 1) + { + last_x= x; + last_y= y; + } + return m_receiver->add_point(x, y); + } + + if (current_type== Gcalc_function::shape_line) + { + /* Line's case */ + last_x= x; + last_y= y; + if (n_points == 1) + return m_receiver->single_point(x, y); + } + return 0; +} + + +int Item_func_boundary::Transporter::start_collection(int n_objects) +{ + return 0; +} + + +String *Item_func_boundary::val_str(String *str_value) +{ + DBUG_ENTER("Item_func_boundary::val_str"); + DBUG_ASSERT(fixed == 1); + String arg_val; + String *swkb= args[0]->val_str(&arg_val); + Geometry_buffer buffer; + Geometry *g; + uint32 srid= 0; + Transporter trn(&res_receiver); + + if ((null_value= + args[0]->null_value || + !(g= Geometry::construct(&buffer, swkb->ptr(), swkb->length())))) + DBUG_RETURN(0); + + if (g->store_shapes(&trn)) + goto mem_error; + + str_value->set_charset(&my_charset_bin); + if (str_value->reserve(SRID_SIZE, 512)) + goto mem_error; + str_value->length(0); + str_value->q_append(srid); + + if (!Geometry::create_from_opresult(&buffer, str_value, res_receiver)) + goto mem_error; + + res_receiver.reset(); + DBUG_RETURN(str_value); + +mem_error: + null_value= 1; + DBUG_RETURN(0); +} + + Field::geometry_type Item_func_centroid::get_geometry_type() const { return Field::GEOM_POINT; @@ -243,9 +369,292 @@ String *Item_func_centroid::val_str(String *str) srid= uint4korr(swkb->ptr()); str->q_append(srid); - return (null_value= test(geom->centroid(str))) ? 0 : str; + return (null_value= MY_TEST(geom->centroid(str))) ? 0 : str; +} + + +int Item_func_convexhull::add_node_to_line(ch_node **p_cur, int dir, + const Gcalc_heap::Info *pi) +{ + ch_node *new_node; + ch_node *cur= *p_cur; + + while (cur->prev) + { + int v_sign= Gcalc_scan_iterator::point::cmp_dx_dy( + cur->prev->pi, cur->pi, cur->pi, pi); + if (v_sign*dir <0) + break; + new_node= cur; + cur= cur->prev; + res_heap.free_item(new_node); + } + if (!(new_node= new_ch_node())) + return 1; + cur->next= new_node; + new_node->prev= cur; + new_node->pi= pi; + *p_cur= new_node; + return 0; +} + + +#ifndef HEAVY_CONVEX_HULL +String *Item_func_convexhull::val_str(String *str_value) +{ + Geometry_buffer buffer; + Geometry *geom= NULL; + MBR mbr; + const char *c_end; + Gcalc_operation_transporter trn(&func, &collector); + uint32 srid= 0; + ch_node *left_first, *left_cur, *right_first, *right_cur; + Gcalc_heap::Info *cur_pi; + + DBUG_ENTER("Item_func_convexhull::val_str"); + DBUG_ASSERT(fixed == 1); + String *swkb= args[0]->val_str(&tmp_value); + + if ((null_value= + args[0]->null_value || + !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length())))) + DBUG_RETURN(0); + + geom->get_mbr(&mbr, &c_end); + collector.set_extent(mbr.xmin, mbr.xmax, mbr.ymin, mbr.ymax); + if ((null_value= geom->store_shapes(&trn))) + { + str_value= 0; + goto mem_error; + } + + collector.prepare_operation(); + if (!(cur_pi= collector.get_first())) + goto build_result; /* An EMPTY GEOMETRY */ + + if (!cur_pi->get_next()) + { + /* Single point. */ + if (res_receiver.single_point(cur_pi->node.shape.x, cur_pi->node.shape.y)) + goto mem_error; + goto build_result; + } + + left_cur= left_first= new_ch_node(); + right_cur= right_first= new_ch_node(); + right_first->prev= left_first->prev= 0; + right_first->pi= left_first->pi= cur_pi; + + while ((cur_pi= cur_pi->get_next())) + { + /* Handle left part of the hull, then the right part. */ + if (add_node_to_line(&left_cur, 1, cur_pi)) + goto mem_error; + if (add_node_to_line(&right_cur, -1, cur_pi)) + goto mem_error; + } + + left_cur->next= 0; + if (left_first->get_next()->get_next() == NULL && + right_cur->prev->prev == NULL) + { + /* We only have 2 nodes in the result, so we create a polyline. */ + if (res_receiver.start_shape(Gcalc_function::shape_line) || + res_receiver.add_point(left_first->pi->node.shape.x, left_first->pi->node.shape.y) || + res_receiver.add_point(left_cur->pi->node.shape.x, left_cur->pi->node.shape.y) || + res_receiver.complete_shape()) + + goto mem_error; + + goto build_result; + } + + if (res_receiver.start_shape(Gcalc_function::shape_polygon)) + goto mem_error; + + while (left_first) + { + if (res_receiver.add_point(left_first->pi->node.shape.x, left_first->pi->node.shape.y)) + goto mem_error; + left_first= left_first->get_next(); + } + + /* Skip last point in the right part as it coincides */ + /* with the last one in the left. */ + right_cur= right_cur->prev; + while (right_cur->prev) + { + if (res_receiver.add_point(right_cur->pi->node.shape.x, right_cur->pi->node.shape.y)) + goto mem_error; + right_cur= right_cur->prev; + } + res_receiver.complete_shape(); + +build_result: + str_value->set_charset(&my_charset_bin); + if (str_value->reserve(SRID_SIZE, 512)) + goto mem_error; + str_value->length(0); + str_value->q_append(srid); + + if (!Geometry::create_from_opresult(&buffer, str_value, res_receiver)) + goto mem_error; + +mem_error: + collector.reset(); + func.reset(); + res_receiver.reset(); + res_heap.reset(); + DBUG_RETURN(str_value); } +#else /*HEAVY_CONVEX_HULL*/ +String *Item_func_convexhull::val_str(String *str_value) +{ + Geometry_buffer buffer; + Geometry *geom= NULL; + MBR mbr; + const char *c_end; + Gcalc_operation_transporter trn(&func, &collector); + const Gcalc_scan_iterator::event_point *ev; + uint32 srid= 0; + ch_node *left_first, *left_cur, *right_first, *right_cur; + + DBUG_ENTER("Item_func_convexhull::val_str"); + DBUG_ASSERT(fixed == 1); + String *swkb= args[0]->val_str(&tmp_value); + + if ((null_value= + args[0]->null_value || + !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length())))) + DBUG_RETURN(0); + + geom->get_mbr(&mbr, &c_end); + collector.set_extent(mbr.xmin, mbr.xmax, mbr.ymin, mbr.ymax); + if ((null_value= geom->store_shapes(&trn))) + { + str_value= 0; + goto mem_error; + } + + collector.prepare_operation(); + scan_it.init(&collector); + scan_it.killed= (int *) &(current_thd->killed); + + if (!scan_it.more_points()) + goto build_result; /* An EMPTY GEOMETRY */ + + if (scan_it.step()) + goto mem_error; + + if (!scan_it.more_points()) + { + /* Single point. */ + if (res_receiver.single_point(scan_it.get_events()->pi->x, + scan_it.get_events()->pi->y)) + goto mem_error; + goto build_result; + } + + left_cur= left_first= new_ch_node(); + right_cur= right_first= new_ch_node(); + right_first->prev= left_first->prev= 0; + right_first->pi= left_first->pi= scan_it.get_events()->pi; + + while (scan_it.more_points()) + { + if (scan_it.step()) + goto mem_error; + ev= scan_it.get_events(); + + /* Skip the intersections-only events. */ + while (ev->event == scev_intersection) + { + ev= ev->get_next(); + if (!ev) + goto skip_point; + } + + { + Gcalc_point_iterator pit(&scan_it); + if (!pit.point() || scan_it.get_event_position() == pit.point()) + { + /* Handle left part of the hull. */ + if (add_node_to_line(&left_cur, 1, ev->pi)) + goto mem_error; + } + if (pit.point()) + { + /* Check the rightmost point */ + for(; pit.point()->c_get_next(); ++pit) + ; + } + if (!pit.point() || pit.point()->event || + scan_it.get_event_position() == pit.point()->c_get_next()) + { + /* Handle right part of the hull. */ + if (add_node_to_line(&right_cur, -1, ev->pi)) + goto mem_error; + } + } +skip_point:; + } + + left_cur->next= 0; + if (left_first->get_next()->get_next() == NULL && + right_cur->prev->prev == NULL) + { + /* We only have 2 nodes in the result, so we create a polyline. */ + if (res_receiver.start_shape(Gcalc_function::shape_line) || + res_receiver.add_point(left_first->pi->x, left_first->pi->y) || + res_receiver.add_point(left_cur->pi->x, left_cur->pi->y) || + res_receiver.complete_shape()) + + goto mem_error; + + goto build_result; + } + + if (res_receiver.start_shape(Gcalc_function::shape_polygon)) + goto mem_error; + + while (left_first) + { + if (res_receiver.add_point(left_first->pi->x, left_first->pi->y)) + goto mem_error; + left_first= left_first->get_next(); + } + + /* Skip last point in the right part as it coincides */ + /* with the last one in the left. */ + right_cur= right_cur->prev; + while (right_cur->prev) + { + if (res_receiver.add_point(right_cur->pi->x, right_cur->pi->y)) + goto mem_error; + right_cur= right_cur->prev; + } + res_receiver.complete_shape(); + +build_result: + str_value->set_charset(&my_charset_bin); + if (str_value->reserve(SRID_SIZE, 512)) + goto mem_error; + str_value->length(0); + str_value->q_append(srid); + + if (!Geometry::create_from_opresult(&buffer, str_value, res_receiver)) + goto mem_error; + +mem_error: + collector.reset(); + func.reset(); + res_receiver.reset(); + res_heap.reset(); + DBUG_RETURN(str_value); +} +#endif /*HEAVY_CONVEX_HULL*/ + /* Spatial decomposition functions @@ -499,10 +908,11 @@ String *Item_func_spatial_collection::val_str(String *str) } if (str->length() > current_thd->variables.max_allowed_packet) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - func_name(), current_thd->variables.max_allowed_packet); + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), thd->variables.max_allowed_packet); goto err; } @@ -519,6 +929,78 @@ err: Functions for spatial relations */ +static SEL_ARG sel_arg_impossible(SEL_ARG::IMPOSSIBLE); + +SEL_ARG * +Item_func_spatial_rel::get_mm_leaf(RANGE_OPT_PARAM *param, + Field *field, KEY_PART *key_part, + Item_func::Functype type, Item *value) +{ + DBUG_ENTER("Item_func_spatial_rel::get_mm_leaf"); + if (key_part->image_type != Field::itMBR) + DBUG_RETURN(0); + if (value->cmp_type() != STRING_RESULT) + DBUG_RETURN(&sel_arg_impossible); + + if (param->using_real_indexes && + !field->optimize_range(param->real_keynr[key_part->key], + key_part->part)) + DBUG_RETURN(0); + + if (value->save_in_field_no_warnings(field, 1)) + DBUG_RETURN(&sel_arg_impossible); // Bad GEOMETRY value + + DBUG_ASSERT(!field->real_maybe_null()); // SPATIAL keys do not support NULL + + uchar *str= (uchar*) alloc_root(param->mem_root, key_part->store_length + 1); + if (!str) + DBUG_RETURN(0); // out of memory + field->get_key_image(str, key_part->length, key_part->image_type); + SEL_ARG *tree; + if (!(tree= new (param->mem_root) SEL_ARG(field, str, str))) + DBUG_RETURN(0); // out of memory + + switch (type) { + case SP_EQUALS_FUNC: + tree->min_flag= GEOM_FLAG | HA_READ_MBR_EQUAL;// NEAR_MIN;//512; + tree->max_flag= NO_MAX_RANGE; + break; + case SP_DISJOINT_FUNC: + tree->min_flag= GEOM_FLAG | HA_READ_MBR_DISJOINT;// NEAR_MIN;//512; + tree->max_flag= NO_MAX_RANGE; + break; + case SP_INTERSECTS_FUNC: + tree->min_flag= GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; + tree->max_flag= NO_MAX_RANGE; + break; + case SP_TOUCHES_FUNC: + tree->min_flag= GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; + tree->max_flag= NO_MAX_RANGE; + break; + case SP_CROSSES_FUNC: + tree->min_flag= GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; + tree->max_flag= NO_MAX_RANGE; + break; + case SP_WITHIN_FUNC: + tree->min_flag= GEOM_FLAG | HA_READ_MBR_WITHIN;// NEAR_MIN;//512; + tree->max_flag= NO_MAX_RANGE; + break; + case SP_CONTAINS_FUNC: + tree->min_flag= GEOM_FLAG | HA_READ_MBR_CONTAIN;// NEAR_MIN;//512; + tree->max_flag= NO_MAX_RANGE; + break; + case SP_OVERLAPS_FUNC: + tree->min_flag= GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; + tree->max_flag= NO_MAX_RANGE; + break; + default: + DBUG_ASSERT(0); + break; + } + DBUG_RETURN(tree); +} + + const char *Item_func_spatial_mbr_rel::func_name() const { switch (spatial_rel) { @@ -548,8 +1030,8 @@ const char *Item_func_spatial_mbr_rel::func_name() const longlong Item_func_spatial_mbr_rel::val_int() { DBUG_ASSERT(fixed == 1); - String *res1= args[0]->val_str(&cmp.value1); - String *res2= args[1]->val_str(&cmp.value2); + String *res1= args[0]->val_str(&tmp_value1); + String *res2= args[1]->val_str(&tmp_value2); Geometry_buffer buffer1, buffer2; Geometry *g1, *g2; MBR mbr1, mbr2; @@ -590,20 +1072,7 @@ longlong Item_func_spatial_mbr_rel::val_int() } -Item_func_spatial_rel::Item_func_spatial_rel(Item *a,Item *b, - enum Functype sp_rel) : - Item_bool_func2(a,b), collector() -{ - spatial_rel = sp_rel; -} - - -Item_func_spatial_rel::~Item_func_spatial_rel() -{ -} - - -const char *Item_func_spatial_rel::func_name() const +const char *Item_func_spatial_precise_rel::func_name() const { switch (spatial_rel) { case SP_CONTAINS_FUNC: @@ -660,91 +1129,225 @@ static double distance_points(const Gcalc_heap::Info *a, } +static Gcalc_function::op_type op_matrix(int n) +{ + switch (n) + { + case 0: + return Gcalc_function::op_internals; + case 1: + return Gcalc_function::op_border; + case 2: + return (Gcalc_function::op_type) + ((int) Gcalc_function::op_not | (int) Gcalc_function::op_union); + }; + GCALC_DBUG_ASSERT(FALSE); + return Gcalc_function::op_any; +} + + +static int setup_relate_func(Geometry *g1, Geometry *g2, + Gcalc_operation_transporter *trn, Gcalc_function *func, + const char *mask) +{ + int do_store_shapes=1; + uint UNINIT_VAR(shape_a), UNINIT_VAR(shape_b); + uint n_operands= 0; + int last_shape_pos; + + last_shape_pos= func->get_next_expression_pos(); + if (func->reserve_op_buffer(1)) + return 1; + func->add_operation(Gcalc_function::op_intersection, 0); + for (int nc=0; nc<9; nc++) + { + uint cur_op; + + cur_op= Gcalc_function::op_intersection; + switch (mask[nc]) + { + case '*': + continue; + case 'T': + case '0': + case '1': + case '2': + cur_op|= Gcalc_function::v_find_t; + break; + case 'F': + cur_op|= (Gcalc_function::op_not | Gcalc_function::v_find_f); + break; + default: + return 1; + }; + ++n_operands; + if (func->reserve_op_buffer(3)) + return 1; + func->add_operation(cur_op, 2); + + func->add_operation(op_matrix(nc/3), 1); + if (do_store_shapes) + { + shape_a= func->get_next_expression_pos(); + if (g1->store_shapes(trn)) + return 1; + } + else + func->repeat_expression(shape_a); + if (func->reserve_op_buffer(1)) + return 1; + func->add_operation(op_matrix(nc%3), 1); + if (do_store_shapes) + { + shape_b= func->get_next_expression_pos(); + if (g2->store_shapes(trn)) + return 1; + do_store_shapes= 0; + } + else + func->repeat_expression(shape_b); + } + + func->add_operands_to_op(last_shape_pos, n_operands); + return 0; +} + + #define GIS_ZERO 0.00000000001 -longlong Item_func_spatial_rel::val_int() +class Geometry_ptr_with_buffer_and_mbr { - DBUG_ENTER("Item_func_spatial_rel::val_int"); +public: + Geometry *geom; + Geometry_buffer buffer; + MBR mbr; + bool construct(Item *item, String *tmp_value) + { + const char *c_end; + String *res= item->val_str(tmp_value); + return + item->null_value || + !(geom= Geometry::construct(&buffer, res->ptr(), res->length())) || + geom->get_mbr(&mbr, &c_end) || !mbr.valid(); + } + int store_shapes(Gcalc_shape_transporter *trn) const + { return geom->store_shapes(trn); } +}; + + +longlong Item_func_spatial_relate::val_int() +{ + DBUG_ENTER("Item_func_spatial_relate::val_int"); DBUG_ASSERT(fixed == 1); - String *res1; - String *res2; - Geometry_buffer buffer1, buffer2; - Geometry *g1, *g2; + Geometry_ptr_with_buffer_and_mbr g1, g2; int result= 0; - int mask= 0; - uint shape_a, shape_b; - MBR umbr, mbr1, mbr2; - const char *c_end; - res1= args[0]->val_str(&tmp_value1); - res2= args[1]->val_str(&tmp_value2); + if ((null_value= (g1.construct(args[0], &tmp_value1) || + g2.construct(args[1], &tmp_value2) || + func.reserve_op_buffer(1)))) + DBUG_RETURN(0); + + MBR umbr(g1.mbr, g2.mbr); + collector.set_extent(umbr.xmin, umbr.xmax, umbr.ymin, umbr.ymax); + g1.mbr.buffer(1e-5); Gcalc_operation_transporter trn(&func, &collector); - if (func.reserve_op_buffer(1)) + String *matrix= args[2]->val_str(&tmp_matrix); + if ((null_value= args[2]->null_value || matrix->length() != 9 || + setup_relate_func(g1.geom, g2.geom, + &trn, &func, matrix->ptr()))) + goto exit; + + collector.prepare_operation(); + scan_it.init(&collector); + scan_it.killed= (int *) &(current_thd->killed); + if (!func.alloc_states()) + result= func.check_function(scan_it); + +exit: + collector.reset(); + func.reset(); + scan_it.reset(); + DBUG_RETURN(result); +} + + +longlong Item_func_spatial_precise_rel::val_int() +{ + DBUG_ENTER("Item_func_spatial_precise_rel::val_int"); + DBUG_ASSERT(fixed == 1); + Geometry_ptr_with_buffer_and_mbr g1, g2; + int result= 0; + uint shape_a, shape_b; + + if ((null_value= (g1.construct(args[0], &tmp_value1) || + g2.construct(args[1], &tmp_value2) || + func.reserve_op_buffer(1)))) DBUG_RETURN(0); - if ((null_value= - (args[0]->null_value || args[1]->null_value || - !(g1= Geometry::construct(&buffer1, res1->ptr(), res1->length())) || - !(g2= Geometry::construct(&buffer2, res2->ptr(), res2->length())) || - g1->get_mbr(&mbr1, &c_end) || !mbr1.valid() || - g2->get_mbr(&mbr2, &c_end) || !mbr2.valid()))) - goto exit; + Gcalc_operation_transporter trn(&func, &collector); - umbr= mbr1; - umbr.add_mbr(&mbr2); + MBR umbr(g1.mbr, g2.mbr); collector.set_extent(umbr.xmin, umbr.xmax, umbr.ymin, umbr.ymax); - mbr1.buffer(1e-5); + g1.mbr.buffer(1e-5); switch (spatial_rel) { case SP_CONTAINS_FUNC: - if (!mbr1.contains(&mbr2)) + if (!g1.mbr.contains(&g2.mbr)) goto exit; - mask= 1; - func.add_operation(Gcalc_function::op_difference, 2); + func.add_operation(Gcalc_function::v_find_f | + Gcalc_function::op_not | + Gcalc_function::op_difference, 2); /* Mind the g2 goes first. */ - null_value= g2->store_shapes(&trn) || g1->store_shapes(&trn); + null_value= g2.store_shapes(&trn) || g1.store_shapes(&trn); break; case SP_WITHIN_FUNC: - mbr2.buffer(2e-5); - if (!mbr1.within(&mbr2)) + g2.mbr.buffer(2e-5); + if (!g1.mbr.within(&g2.mbr)) goto exit; - mask= 1; - func.add_operation(Gcalc_function::op_difference, 2); - null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn); + func.add_operation(Gcalc_function::v_find_f | + Gcalc_function::op_not | + Gcalc_function::op_difference, 2); + null_value= g1.store_shapes(&trn) || g2.store_shapes(&trn); break; case SP_EQUALS_FUNC: - if (!mbr1.contains(&mbr2)) + if (!g1.mbr.contains(&g2.mbr)) goto exit; - mask= 1; - func.add_operation(Gcalc_function::op_symdifference, 2); - null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn); + func.add_operation(Gcalc_function::v_find_f | + Gcalc_function::op_not | + Gcalc_function::op_symdifference, 2); + null_value= g1.store_shapes(&trn) || g2.store_shapes(&trn); break; case SP_DISJOINT_FUNC: - mask= 1; - func.add_operation(Gcalc_function::op_intersection, 2); - null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn); + func.add_operation(Gcalc_function::v_find_f | + Gcalc_function::op_not | + Gcalc_function::op_intersection, 2); + null_value= g1.store_shapes(&trn) || g2.store_shapes(&trn); break; case SP_INTERSECTS_FUNC: - if (!mbr1.intersects(&mbr2)) + if (!g1.mbr.intersects(&g2.mbr)) goto exit; - func.add_operation(Gcalc_function::op_intersection, 2); - null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn); + func.add_operation(Gcalc_function::v_find_t | + Gcalc_function::op_intersection, 2); + null_value= g1.store_shapes(&trn) || g2.store_shapes(&trn); break; case SP_OVERLAPS_FUNC: case SP_CROSSES_FUNC: func.add_operation(Gcalc_function::op_intersection, 2); + if (func.reserve_op_buffer(3)) + break; func.add_operation(Gcalc_function::v_find_t | Gcalc_function::op_intersection, 2); shape_a= func.get_next_expression_pos(); - if ((null_value= g1->store_shapes(&trn))) + if ((null_value= g1.store_shapes(&trn))) break; shape_b= func.get_next_expression_pos(); - if ((null_value= g2->store_shapes(&trn))) + if ((null_value= g2.store_shapes(&trn))) break; - func.add_operation(Gcalc_function::v_find_t | - Gcalc_function::op_intersection, 2); + if (func.reserve_op_buffer(7)) + break; + func.add_operation(Gcalc_function::op_intersection, 2); func.add_operation(Gcalc_function::v_find_t | Gcalc_function::op_difference, 2); func.repeat_expression(shape_a); @@ -755,23 +1358,25 @@ longlong Item_func_spatial_rel::val_int() func.repeat_expression(shape_a); break; case SP_TOUCHES_FUNC: + if (func.reserve_op_buffer(5)) + break; func.add_operation(Gcalc_function::op_intersection, 2); func.add_operation(Gcalc_function::v_find_f | Gcalc_function::op_not | Gcalc_function::op_intersection, 2); func.add_operation(Gcalc_function::op_internals, 1); shape_a= func.get_next_expression_pos(); - if ((null_value= g1->store_shapes(&trn))) + if ((null_value= g1.store_shapes(&trn)) || + func.reserve_op_buffer(1)) break; func.add_operation(Gcalc_function::op_internals, 1); shape_b= func.get_next_expression_pos(); - if ((null_value= g2->store_shapes(&trn))) + if ((null_value= g2.store_shapes(&trn)) || + func.reserve_op_buffer(1)) break; func.add_operation(Gcalc_function::v_find_t | Gcalc_function::op_intersection, 2); - func.add_operation(Gcalc_function::op_border, 1); func.repeat_expression(shape_a); - func.add_operation(Gcalc_function::op_border, 1); func.repeat_expression(shape_b); break; default: @@ -789,7 +1394,7 @@ longlong Item_func_spatial_rel::val_int() if (func.alloc_states()) goto exit; - result= func.check_function(scan_it) ^ mask; + result= func.check_function(scan_it); exit: collector.reset(); @@ -808,34 +1413,25 @@ String *Item_func_spatial_operation::val_str(String *str_value) { DBUG_ENTER("Item_func_spatial_operation::val_str"); DBUG_ASSERT(fixed == 1); - String *res1= args[0]->val_str(&tmp_value1); - String *res2= args[1]->val_str(&tmp_value2); - Geometry_buffer buffer1, buffer2; - Geometry *g1, *g2; + Geometry_ptr_with_buffer_and_mbr g1, g2; uint32 srid= 0; Gcalc_operation_transporter trn(&func, &collector); - MBR mbr1, mbr2; - const char *c_end; if (func.reserve_op_buffer(1)) DBUG_RETURN(0); func.add_operation(spatial_op, 2); - if ((null_value= - (args[0]->null_value || args[1]->null_value || - !(g1= Geometry::construct(&buffer1, res1->ptr(), res1->length())) || - !(g2= Geometry::construct(&buffer2, res2->ptr(), res2->length())) || - g1->get_mbr(&mbr1, &c_end) || !mbr1.valid() || - g2->get_mbr(&mbr2, &c_end) || !mbr2.valid()))) + if ((null_value= (g1.construct(args[0], &tmp_value1) || + g2.construct(args[1], &tmp_value2)))) { str_value= 0; goto exit; } - mbr1.add_mbr(&mbr2); - collector.set_extent(mbr1.xmin, mbr1.xmax, mbr1.ymin, mbr1.ymax); + g1.mbr.add_mbr(&g2.mbr); + collector.set_extent(g1.mbr.xmin, g1.mbr.xmax, g1.mbr.ymin, g1.mbr.ymax); - if ((null_value= g1->store_shapes(&trn) || g2->store_shapes(&trn))) + if ((null_value= g1.store_shapes(&trn) || g2.store_shapes(&trn))) { str_value= 0; goto exit; @@ -858,7 +1454,7 @@ String *Item_func_spatial_operation::val_str(String *str_value) str_value->length(0); str_value->q_append(srid); - if (Geometry::create_from_opresult(&buffer1, str_value, res_receiver)) + if (!Geometry::create_from_opresult(&g1.buffer, str_value, res_receiver)) goto exit; exit: @@ -1011,6 +1607,8 @@ int Item_func_buffer::Transporter::single_point(double x, double y) { if (buffer_op == Gcalc_function::op_difference) { + if (m_fn->reserve_op_buffer(1)) + return 1; m_fn->add_operation(Gcalc_function::op_false, 0); return 0; } @@ -1267,7 +1865,7 @@ String *Item_func_buffer::val_str(String *str_value) { DBUG_ENTER("Item_func_buffer::val_str"); DBUG_ASSERT(fixed == 1); - String *obj= args[0]->val_str(&tmp_value); + String *obj= args[0]->val_str(str_value); double dist= args[1]->val_real(); Geometry_buffer buffer; Geometry *g; @@ -1326,7 +1924,7 @@ return_empty_result: str_value->length(0); str_value->q_append(srid); - if (Geometry::create_from_opresult(&buffer, str_value, res_receiver)) + if (!Geometry::create_from_opresult(&buffer, str_value, res_receiver)) goto mem_error; null_value= 0; @@ -1359,17 +1957,20 @@ longlong Item_func_issimple::val_int() Gcalc_operation_transporter trn(&func, &collector); Geometry *g; int result= 1; - const Gcalc_scan_iterator::event_point *ev; MBR mbr; const char *c_end; DBUG_ENTER("Item_func_issimple::val_int"); DBUG_ASSERT(fixed == 1); - if ((null_value= (args[0]->null_value || - !(g= Geometry::construct(&buffer, swkb->ptr(), swkb->length())) || - g->get_mbr(&mbr, &c_end)))) - DBUG_RETURN(0); + null_value= 0; + if ((args[0]->null_value || + !(g= Geometry::construct(&buffer, swkb->ptr(), swkb->length())) || + g->get_mbr(&mbr, &c_end))) + { + /* We got NULL as an argument. Have to return -1 */ + DBUG_RETURN(-1); + } collector.set_extent(mbr.xmin, mbr.xmax, mbr.ymin, mbr.ymax); @@ -1384,6 +1985,8 @@ longlong Item_func_issimple::val_int() while (scan_it.more_points()) { + const Gcalc_scan_iterator::event_point *ev, *next_ev; + if (scan_it.step()) goto mem_error; @@ -1391,11 +1994,18 @@ longlong Item_func_issimple::val_int() if (ev->simple_event()) continue; - if ((ev->event == scev_thread || ev->event == scev_single_point) && - !ev->get_next()) + next_ev= ev->get_next(); + if ((ev->event & (scev_thread | scev_single_point)) && !next_ev) + continue; + + if ((ev->event == scev_two_threads) && !next_ev->get_next()) continue; - if (ev->event == scev_two_threads && !ev->get_next()->get_next()) + /* If the first and last points of a curve coincide - that is */ + /* an exception to the rule and the line is considered as simple. */ + if ((next_ev && !next_ev->get_next()) && + (ev->event & (scev_thread | scev_end)) && + (next_ev->event & (scev_thread | scev_end))) continue; result= 0; @@ -1421,15 +2031,47 @@ longlong Item_func_isclosed::val_int() Geometry *geom; int isclosed= 0; // In case of error - null_value= (!swkb || - args[0]->null_value || - !(geom= - Geometry::construct(&buffer, swkb->ptr(), swkb->length())) || - geom->is_closed(&isclosed)); + null_value= 0; + if (!swkb || + args[0]->null_value || + !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length())) || + geom->is_closed(&isclosed)) + { + /* IsClosed(NULL) should return -1 */ + return -1; + } return (longlong) isclosed; } + +longlong Item_func_isring::val_int() +{ + /* It's actually a combination of two functions - IsClosed and IsSimple */ + DBUG_ASSERT(fixed == 1); + String tmp; + String *swkb= args[0]->val_str(&tmp); + Geometry_buffer buffer; + Geometry *geom; + int isclosed= 0; // In case of error + + null_value= 0; + if (!swkb || + args[0]->null_value || + !(geom= Geometry::construct(&buffer, swkb->ptr(), swkb->length())) || + geom->is_closed(&isclosed)) + { + /* IsRing(NULL) should return -1 */ + return -1; + } + + if (!isclosed) + return 0; + + return Item_func_issimple::val_int(); +} + + /* Numerical functions */ @@ -1746,4 +2388,126 @@ mem_error: } +String *Item_func_pointonsurface::val_str(String *str) +{ + Gcalc_operation_transporter trn(&func, &collector); + + DBUG_ENTER("Item_func_pointonsurface::val_real"); + DBUG_ASSERT(fixed == 1); + String *res= args[0]->val_str(&tmp_value); + Geometry_buffer buffer; + Geometry *g; + MBR mbr; + const char *c_end; + double UNINIT_VAR(px), UNINIT_VAR(py), x0, y0; + String *result= 0; + const Gcalc_scan_iterator::point *pprev= NULL; + uint32 srid; + + null_value= 1; + if ((args[0]->null_value || + !(g= Geometry::construct(&buffer, res->ptr(), res->length())) || + g->get_mbr(&mbr, &c_end))) + goto mem_error; + + collector.set_extent(mbr.xmin, mbr.xmax, mbr.ymin, mbr.ymax); + + if (g->store_shapes(&trn)) + goto mem_error; + + collector.prepare_operation(); + scan_it.init(&collector); + + while (scan_it.more_points()) + { + if (scan_it.step()) + goto mem_error; + + if (scan_it.get_h() > GIS_ZERO) + { + y0= scan_it.get_y(); + break; + } + } + + if (!scan_it.more_points()) + { + goto exit; + } + + if (scan_it.step()) + goto mem_error; + + for (Gcalc_point_iterator pit(&scan_it); pit.point(); ++pit) + { + if (pprev == NULL) + { + pprev= pit.point(); + continue; + } + x0= scan_it.get_sp_x(pprev); + px= scan_it.get_sp_x(pit.point()); + if (px - x0 > GIS_ZERO) + { + if (scan_it.get_h() > GIS_ZERO) + { + px= (px + x0) / 2.0; + py= scan_it.get_y(); + } + else + { + px= (px + x0) / 2.0; + py= (y0 + scan_it.get_y()) / 2.0; + } + null_value= 0; + break; + } + pprev= NULL; + } + + if (null_value) + goto exit; + + str->set_charset(&my_charset_bin); + if (str->reserve(SRID_SIZE, 512)) + goto mem_error; + + str->length(0); + srid= uint4korr(res->ptr()); + str->q_append(srid); + + if (Geometry::create_point(str, px, py)) + goto mem_error; + + result= str; + +exit: + collector.reset(); + func.reset(); + scan_it.reset(); + DBUG_RETURN(result); + +mem_error: + collector.reset(); + func.reset(); + scan_it.reset(); + null_value= 1; + DBUG_RETURN(0); +} + + +Field::geometry_type Item_func_pointonsurface::get_geometry_type() const +{ + return Field::GEOM_POINT; +} + + +#ifndef DBUG_OFF +longlong Item_func_gis_debug::val_int() +{ + /* For now this is just a stub. TODO: implement the internal GIS debuggign */ + return 0; +} +#endif + #endif /*HAVE_SPATIAL*/ diff --git a/sql/item_geofunc.h b/sql/item_geofunc.h index 922593f8b91..251cae1121e 100644 --- a/sql/item_geofunc.h +++ b/sql/item_geofunc.h @@ -32,22 +32,23 @@ class Item_geometry_func: public Item_str_func { public: - Item_geometry_func() :Item_str_func() {} - Item_geometry_func(Item *a) :Item_str_func(a) {} - Item_geometry_func(Item *a,Item *b) :Item_str_func(a,b) {} - Item_geometry_func(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {} - Item_geometry_func(List<Item> &list) :Item_str_func(list) {} + Item_geometry_func(THD *thd): Item_str_func(thd) {} + Item_geometry_func(THD *thd, Item *a): Item_str_func(thd, a) {} + Item_geometry_func(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) {} + Item_geometry_func(THD *thd, Item *a, Item *b, Item *c): + Item_str_func(thd, a, b, c) {} + Item_geometry_func(THD *thd, List<Item> &list): Item_str_func(thd, list) {} void fix_length_and_dec(); enum_field_types field_type() const { return MYSQL_TYPE_GEOMETRY; } - Field *tmp_table_field(TABLE *t_arg); - bool is_null() { (void) val_int(); return null_value; } + Field *create_field_for_create_select(TABLE *table); }; class Item_func_geometry_from_text: public Item_geometry_func { public: - Item_func_geometry_from_text(Item *a) :Item_geometry_func(a) {} - Item_func_geometry_from_text(Item *a, Item *srid) :Item_geometry_func(a, srid) {} + Item_func_geometry_from_text(THD *thd, Item *a): Item_geometry_func(thd, a) {} + Item_func_geometry_from_text(THD *thd, Item *a, Item *srid): + Item_geometry_func(thd, a, srid) {} const char *func_name() const { return "st_geometryfromtext"; } String *val_str(String *); }; @@ -55,8 +56,9 @@ public: class Item_func_geometry_from_wkb: public Item_geometry_func { public: - Item_func_geometry_from_wkb(Item *a): Item_geometry_func(a) {} - Item_func_geometry_from_wkb(Item *a, Item *srid): Item_geometry_func(a, srid) {} + Item_func_geometry_from_wkb(THD *thd, Item *a): Item_geometry_func(thd, a) {} + Item_func_geometry_from_wkb(THD *thd, Item *a, Item *srid): + Item_geometry_func(thd, a, srid) {} const char *func_name() const { return "st_geometryfromwkb"; } String *val_str(String *); }; @@ -64,7 +66,7 @@ public: class Item_func_as_wkt: public Item_str_ascii_func { public: - Item_func_as_wkt(Item *a): Item_str_ascii_func(a) {} + Item_func_as_wkt(THD *thd, Item *a): Item_str_ascii_func(thd, a) {} const char *func_name() const { return "st_astext"; } String *val_str_ascii(String *); void fix_length_and_dec(); @@ -73,7 +75,7 @@ public: class Item_func_as_wkb: public Item_geometry_func { public: - Item_func_as_wkb(Item *a): Item_geometry_func(a) {} + Item_func_as_wkb(THD *thd, Item *a): Item_geometry_func(thd, a) {} const char *func_name() const { return "st_aswkb"; } String *val_str(String *); enum_field_types field_type() const { return MYSQL_TYPE_BLOB; } @@ -82,7 +84,7 @@ public: class Item_func_geometry_type: public Item_str_ascii_func { public: - Item_func_geometry_type(Item *a): Item_str_ascii_func(a) {} + Item_func_geometry_type(THD *thd, Item *a): Item_str_ascii_func(thd, a) {} String *val_str_ascii(String *); const char *func_name() const { return "st_geometrytype"; } void fix_length_and_dec() @@ -93,10 +95,43 @@ public: }; }; + +// #define HEAVY_CONVEX_HULL +class Item_func_convexhull: public Item_geometry_func +{ + class ch_node: public Gcalc_dyn_list::Item + { + public: + const Gcalc_heap::Info *pi; + ch_node *prev; + Gcalc_dyn_list::Item *next; + ch_node *get_next() { return (ch_node *) next; } + }; + + Gcalc_heap collector; + Gcalc_function func; + Gcalc_dyn_list res_heap; + + Gcalc_result_receiver res_receiver; + String tmp_value; +#ifdef HEAVY_CONVEX_HULL + Gcalc_scan_iterator scan_it; +#endif /*HEAVY_CONVEX_HULL*/ + ch_node *new_ch_node() { return (ch_node *) res_heap.new_item(); } + int add_node_to_line(ch_node **p_cur, int dir, const Gcalc_heap::Info *pi); +public: + Item_func_convexhull(THD *thd, Item *a): Item_geometry_func(thd, a), + res_heap(8192, sizeof(ch_node)) + {} + const char *func_name() const { return "st_convexhull"; } + String *val_str(String *); +}; + + class Item_func_centroid: public Item_geometry_func { public: - Item_func_centroid(Item *a): Item_geometry_func(a) {} + Item_func_centroid(THD *thd, Item *a): Item_geometry_func(thd, a) {} const char *func_name() const { return "st_centroid"; } String *val_str(String *); Field::geometry_type get_geometry_type() const; @@ -105,17 +140,50 @@ public: class Item_func_envelope: public Item_geometry_func { public: - Item_func_envelope(Item *a): Item_geometry_func(a) {} + Item_func_envelope(THD *thd, Item *a): Item_geometry_func(thd, a) {} const char *func_name() const { return "st_envelope"; } String *val_str(String *); Field::geometry_type get_geometry_type() const; }; + +class Item_func_boundary: public Item_geometry_func +{ + class Transporter : public Gcalc_shape_transporter + { + Gcalc_result_receiver *m_receiver; + uint n_points; + Gcalc_function::shape_type current_type; + double last_x, last_y; + public: + Transporter(Gcalc_result_receiver *receiver) : + Gcalc_shape_transporter(NULL), m_receiver(receiver) + {} + int single_point(double x, double y); + int start_line(); + int complete_line(); + int start_poly(); + int complete_poly(); + int start_ring(); + int complete_ring(); + int add_point(double x, double y); + + int start_collection(int n_objects); + }; + Gcalc_result_receiver res_receiver; +public: + Item_func_boundary(THD *thd, Item *a): Item_geometry_func(thd, a) {} + const char *func_name() const { return "st_boundary"; } + String *val_str(String *); +}; + + class Item_func_point: public Item_geometry_func { public: - Item_func_point(Item *a, Item *b): Item_geometry_func(a, b) {} - Item_func_point(Item *a, Item *b, Item *srid): Item_geometry_func(a, b, srid) {} + Item_func_point(THD *thd, Item *a, Item *b): Item_geometry_func(thd, a, b) {} + Item_func_point(THD *thd, Item *a, Item *b, Item *srid): + Item_geometry_func(thd, a, b, srid) {} const char *func_name() const { return "point"; } String *val_str(String *); Field::geometry_type get_geometry_type() const; @@ -125,8 +193,8 @@ class Item_func_spatial_decomp: public Item_geometry_func { enum Functype decomp_func; public: - Item_func_spatial_decomp(Item *a, Item_func::Functype ft) : - Item_geometry_func(a) { decomp_func = ft; } + Item_func_spatial_decomp(THD *thd, Item *a, Item_func::Functype ft): + Item_geometry_func(thd, a) { decomp_func = ft; } const char *func_name() const { switch (decomp_func) @@ -149,8 +217,8 @@ class Item_func_spatial_decomp_n: public Item_geometry_func { enum Functype decomp_func_n; public: - Item_func_spatial_decomp_n(Item *a, Item *b, Item_func::Functype ft): - Item_geometry_func(a, b) { decomp_func_n = ft; } + Item_func_spatial_decomp_n(THD *thd, Item *a, Item *b, Item_func::Functype ft): + Item_geometry_func(thd, a, b) { decomp_func_n = ft; } const char *func_name() const { switch (decomp_func_n) @@ -171,13 +239,12 @@ public: class Item_func_spatial_collection: public Item_geometry_func { - String tmp_value; enum Geometry::wkbType coll_type; enum Geometry::wkbType item_type; public: - Item_func_spatial_collection( + Item_func_spatial_collection(THD *thd, List<Item> &list, enum Geometry::wkbType ct, enum Geometry::wkbType it): - Item_geometry_func(list) + Item_geometry_func(thd, list) { coll_type=ct; item_type=it; @@ -207,52 +274,70 @@ public: Spatial relations */ -class Item_func_spatial_mbr_rel: public Item_bool_func2 +class Item_func_spatial_rel: public Item_bool_func2_with_rev { +protected: enum Functype spatial_rel; + String tmp_value1, tmp_value2; + SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param, Field *field, + KEY_PART *key_part, + Item_func::Functype type, Item *value); public: - Item_func_spatial_mbr_rel(Item *a,Item *b, enum Functype sp_rel) : - Item_bool_func2(a,b) { spatial_rel = sp_rel; } - longlong val_int(); - enum Functype functype() const - { - return spatial_rel; + Item_func_spatial_rel(THD *thd, Item *a, Item *b, enum Functype sp_rel): + Item_bool_func2_with_rev(thd, a, b), spatial_rel(sp_rel) + { + maybe_null= true; } + enum Functype functype() const { return spatial_rel; } enum Functype rev_functype() const { return spatial_rel; } - const char *func_name() const; - virtual inline void print(String *str, enum_query_type query_type) + bool is_null() { (void) val_int(); return null_value; } + void add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) { - Item_func::print(str, query_type); + return add_key_fields_optimize_op(join, key_fields, and_level, + usable_tables, sargables, false); } - void fix_length_and_dec() { maybe_null= 1; } - bool is_null() { (void) val_int(); return null_value; } }; -class Item_func_spatial_rel: public Item_bool_func2 +class Item_func_spatial_mbr_rel: public Item_func_spatial_rel +{ +public: + Item_func_spatial_mbr_rel(THD *thd, Item *a, Item *b, enum Functype sp_rel): + Item_func_spatial_rel(thd, a, b, sp_rel) + { } + longlong val_int(); + const char *func_name() const; +}; + + +class Item_func_spatial_precise_rel: public Item_func_spatial_rel { - enum Functype spatial_rel; Gcalc_heap collector; Gcalc_scan_iterator scan_it; Gcalc_function func; - String tmp_value1,tmp_value2; public: - Item_func_spatial_rel(Item *a,Item *b, enum Functype sp_rel); - virtual ~Item_func_spatial_rel(); + Item_func_spatial_precise_rel(THD *thd, Item *a, Item *b, enum Functype sp_rel): + Item_func_spatial_rel(thd, a, b, sp_rel), collector() + { } longlong val_int(); - enum Functype functype() const - { - return spatial_rel; - } - enum Functype rev_functype() const { return spatial_rel; } const char *func_name() const; - virtual inline void print(String *str, enum_query_type query_type) - { - Item_func::print(str, query_type); - } +}; - void fix_length_and_dec() { maybe_null= 1; } - bool is_null() { (void) val_int(); return null_value; } + +class Item_func_spatial_relate: public Item_bool_func +{ + Gcalc_heap collector; + Gcalc_scan_iterator scan_it; + Gcalc_function func; + String tmp_value1, tmp_value2, tmp_matrix; +public: + Item_func_spatial_relate(THD *thd, Item *a, Item *b, Item *matrix): + Item_bool_func(thd, a, b, matrix) + { } + longlong val_int(); + const char *func_name() const { return "st_relate"; } }; @@ -271,8 +356,9 @@ public: Gcalc_operation_reducer operation; String tmp_value1,tmp_value2; public: - Item_func_spatial_operation(Item *a,Item *b, Gcalc_function::op_type sp_op) : - Item_geometry_func(a, b), spatial_op(sp_op) + Item_func_spatial_operation(THD *thd, Item *a,Item *b, + Gcalc_function::op_type sp_op): + Item_geometry_func(thd, a, b), spatial_op(sp_op) {} virtual ~Item_func_spatial_operation(); String *val_str(String *); @@ -325,11 +411,10 @@ protected: Gcalc_result_receiver res_receiver; Gcalc_operation_reducer operation; - String tmp_value; public: - Item_func_buffer(Item *obj, Item *distance): - Item_geometry_func(obj, distance) {} + Item_func_buffer(THD *thd, Item *obj, Item *distance): + Item_geometry_func(thd, obj, distance) {} const char *func_name() const { return "st_buffer"; } String *val_str(String *); }; @@ -338,42 +423,49 @@ public: class Item_func_isempty: public Item_bool_func { public: - Item_func_isempty(Item *a): Item_bool_func(a) {} + Item_func_isempty(THD *thd, Item *a): Item_bool_func(thd, a) {} longlong val_int(); - optimize_type select_optimize() const { return OPTIMIZE_NONE; } const char *func_name() const { return "st_isempty"; } void fix_length_and_dec() { maybe_null= 1; } }; -class Item_func_issimple: public Item_bool_func +class Item_func_issimple: public Item_int_func { Gcalc_heap collector; Gcalc_function func; Gcalc_scan_iterator scan_it; String tmp; public: - Item_func_issimple(Item *a): Item_bool_func(a) {} + Item_func_issimple(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); - optimize_type select_optimize() const { return OPTIMIZE_NONE; } const char *func_name() const { return "st_issimple"; } - void fix_length_and_dec() { maybe_null= 1; } + void fix_length_and_dec() { decimals=0; max_length=2; } + uint decimal_precision() const { return 1; } }; -class Item_func_isclosed: public Item_bool_func +class Item_func_isclosed: public Item_int_func { public: - Item_func_isclosed(Item *a): Item_bool_func(a) {} + Item_func_isclosed(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); - optimize_type select_optimize() const { return OPTIMIZE_NONE; } const char *func_name() const { return "st_isclosed"; } - void fix_length_and_dec() { maybe_null= 1; } + void fix_length_and_dec() { decimals=0; max_length=2; } + uint decimal_precision() const { return 1; } +}; + +class Item_func_isring: public Item_func_issimple +{ +public: + Item_func_isring(THD *thd, Item *a): Item_func_issimple(thd, a) {} + longlong val_int(); + const char *func_name() const { return "st_isring"; } }; class Item_func_dimension: public Item_int_func { String value; public: - Item_func_dimension(Item *a): Item_int_func(a) {} + Item_func_dimension(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "st_dimension"; } void fix_length_and_dec() { max_length= 10; maybe_null= 1; } @@ -383,7 +475,7 @@ class Item_func_x: public Item_real_func { String value; public: - Item_func_x(Item *a): Item_real_func(a) {} + Item_func_x(THD *thd, Item *a): Item_real_func(thd, a) {} double val_real(); const char *func_name() const { return "st_x"; } void fix_length_and_dec() @@ -398,7 +490,7 @@ class Item_func_y: public Item_real_func { String value; public: - Item_func_y(Item *a): Item_real_func(a) {} + Item_func_y(THD *thd, Item *a): Item_real_func(thd, a) {} double val_real(); const char *func_name() const { return "st_y"; } void fix_length_and_dec() @@ -413,7 +505,7 @@ class Item_func_numgeometries: public Item_int_func { String value; public: - Item_func_numgeometries(Item *a): Item_int_func(a) {} + Item_func_numgeometries(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "st_numgeometries"; } void fix_length_and_dec() { max_length= 10; maybe_null= 1; } @@ -424,7 +516,7 @@ class Item_func_numinteriorring: public Item_int_func { String value; public: - Item_func_numinteriorring(Item *a): Item_int_func(a) {} + Item_func_numinteriorring(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "st_numinteriorrings"; } void fix_length_and_dec() { max_length= 10; maybe_null= 1; } @@ -435,7 +527,7 @@ class Item_func_numpoints: public Item_int_func { String value; public: - Item_func_numpoints(Item *a): Item_int_func(a) {} + Item_func_numpoints(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "st_numpoints"; } void fix_length_and_dec() { max_length= 10; maybe_null= 1; } @@ -446,7 +538,7 @@ class Item_func_area: public Item_real_func { String value; public: - Item_func_area(Item *a): Item_real_func(a) {} + Item_func_area(THD *thd, Item *a): Item_real_func(thd, a) {} double val_real(); const char *func_name() const { return "st_area"; } void fix_length_and_dec() @@ -461,7 +553,7 @@ class Item_func_glength: public Item_real_func { String value; public: - Item_func_glength(Item *a): Item_real_func(a) {} + Item_func_glength(THD *thd, Item *a): Item_real_func(thd, a) {} double val_real(); const char *func_name() const { return "st_length"; } void fix_length_and_dec() @@ -476,7 +568,7 @@ class Item_func_srid: public Item_int_func { String value; public: - Item_func_srid(Item *a): Item_int_func(a) {} + Item_func_srid(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "srid"; } void fix_length_and_dec() { max_length= 10; maybe_null= 1; } @@ -491,11 +583,38 @@ class Item_func_distance: public Item_real_func Gcalc_function func; Gcalc_scan_iterator scan_it; public: - Item_func_distance(Item *a, Item *b): Item_real_func(a, b) {} + Item_func_distance(THD *thd, Item *a, Item *b): Item_real_func(thd, a, b) {} double val_real(); const char *func_name() const { return "st_distance"; } }; + +class Item_func_pointonsurface: public Item_geometry_func +{ + String tmp_value; + Gcalc_heap collector; + Gcalc_function func; + Gcalc_scan_iterator scan_it; +public: + Item_func_pointonsurface(THD *thd, Item *a): Item_geometry_func(thd, a) {} + const char *func_name() const { return "st_pointonsurface"; } + String *val_str(String *); + Field::geometry_type get_geometry_type() const; +}; + + +#ifndef DBUG_OFF +class Item_func_gis_debug: public Item_int_func +{ + public: + Item_func_gis_debug(THD *thd, Item *a): Item_int_func(thd, a) + { null_value= false; } + const char *func_name() const { return "st_gis_debug"; } + longlong val_int(); +}; +#endif + + #define GEOM_NEW(thd, obj_constructor) new (thd->mem_root) obj_constructor #else /*HAVE_SPATIAL*/ diff --git a/sql/item_inetfunc.cc b/sql/item_inetfunc.cc new file mode 100644 index 00000000000..4c4dfa4497b --- /dev/null +++ b/sql/item_inetfunc.cc @@ -0,0 +1,832 @@ +/* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2014 MariaDB Foundation + + 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 */ + +#include <my_global.h> +#include "item_inetfunc.h" + +#include "my_net.h" + +/////////////////////////////////////////////////////////////////////////// + +static const int IN_ADDR_SIZE= sizeof (in_addr); +static const int IN6_ADDR_SIZE= sizeof (in6_addr); +static const int IN6_ADDR_NUM_WORDS= IN6_ADDR_SIZE / 2; + +static const char HEX_DIGITS[]= "0123456789abcdef"; + +/////////////////////////////////////////////////////////////////////////// + +longlong Item_func_inet_aton::val_int() +{ + DBUG_ASSERT(fixed); + + uint byte_result= 0; + ulonglong result= 0; // We are ready for 64 bit addresses + const char *p,* end; + char c= '.'; // we mark c to indicate invalid IP in case length is 0 + int dot_count= 0; + + StringBuffer<36> tmp; + String *s= args[0]->val_str_ascii(&tmp); + + if (!s) // If null value + goto err; + + null_value= 0; + + end= (p = s->ptr()) + s->length(); + while (p < end) + { + c= *p++; + int digit= (int) (c - '0'); + if (digit >= 0 && digit <= 9) + { + if ((byte_result= byte_result * 10 + digit) > 255) + goto err; // Wrong address + } + else if (c == '.') + { + dot_count++; + result= (result << 8) + (ulonglong) byte_result; + byte_result= 0; + } + else + goto err; // Invalid character + } + if (c != '.') // IP number can't end on '.' + { + /* + Attempt to support short forms of IP-addresses. It's however pretty + basic one comparing to the BSD support. + Examples: + 127 -> 0.0.0.127 + 127.255 -> 127.0.0.255 + 127.256 -> NULL (should have been 127.0.1.0) + 127.2.1 -> 127.2.0.1 + */ + switch (dot_count) { + case 1: result<<= 8; /* Fall through */ + case 2: result<<= 8; /* Fall through */ + } + return (result << 8) + (ulonglong) byte_result; + } + +err: + null_value=1; + return 0; +} + +/////////////////////////////////////////////////////////////////////////// + +String* Item_func_inet_ntoa::val_str(String* str) +{ + DBUG_ASSERT(fixed); + + ulonglong n= (ulonglong) args[0]->val_int(); + + /* + We do not know if args[0] is NULL until we have called + some val function on it if args[0] is not a constant! + + Also return null if n > 255.255.255.255 + */ + if ((null_value= (args[0]->null_value || n > 0xffffffff))) + return 0; // Null value + + str->set_charset(collation.collation); + str->length(0); + + uchar buf[8]; + int4store(buf, n); + + /* Now we can assume little endian. */ + + char num[4]; + num[3]= '.'; + + for (uchar *p= buf + 4; p-- > buf;) + { + uint c= *p; + uint n1, n2; // Try to avoid divisions + n1= c / 100; // 100 digits + c-= n1 * 100; + n2= c / 10; // 10 digits + c-= n2 * 10; // last digit + num[0]= (char) n1 + '0'; + num[1]= (char) n2 + '0'; + num[2]= (char) c + '0'; + uint length= (n1 ? 4 : n2 ? 3 : 2); // Remove pre-zero + uint dot_length= (p <= buf) ? 1 : 0; + (void) str->append(num + 4 - length, length - dot_length, + &my_charset_latin1); + } + + return str; +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Check the function argument, handle errors properly. + + @return The function value. +*/ + +longlong Item_func_inet_bool_base::val_int() +{ + DBUG_ASSERT(fixed); + + if (args[0]->result_type() != STRING_RESULT) // String argument expected + return 0; + + String buffer; + String *arg_str= args[0]->val_str(&buffer); + + if (!arg_str) // Out-of memory happened. The error has been reported. + return 0; // Or: the underlying field is NULL + + return calc_value(arg_str) ? 1 : 0; +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Check the function argument, handle errors properly. + + @param [out] buffer Buffer for string operations. + + @return The function value. +*/ + +String *Item_func_inet_str_base::val_str_ascii(String *buffer) +{ + DBUG_ASSERT(fixed); + + if (args[0]->result_type() != STRING_RESULT) // String argument expected + { + null_value= true; + return NULL; + } + + StringBuffer<STRING_BUFFER_USUAL_SIZE> tmp; + String *arg_str= args[0]->val_str(&tmp); + if (!arg_str) // Out-of memory happened. The error has been reported. + { // Or: the underlying field is NULL + null_value= true; + return NULL; + } + + null_value= !calc_value(arg_str, buffer); + + return null_value ? NULL : buffer; +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Tries to convert given string to binary IPv4-address representation. + This is a portable alternative to inet_pton(AF_INET). + + @param str String to convert. + @param str_len String length. + @param[out] ipv4_address Buffer to store IPv4-address. + + @return Completion status. + @retval false Given string does not represent an IPv4-address. + @retval true The string has been converted sucessfully. + + @note The problem with inet_pton() is that it treats leading zeros in + IPv4-part differently on different platforms. +*/ + +static bool str_to_ipv4(const char *str, int str_length, in_addr *ipv4_address) +{ + if (str_length < 7) + { + DBUG_PRINT("error", ("str_to_ipv4(%.*s): " + "invalid IPv4 address: too short.", + str_length, str)); + return false; + } + + if (str_length > 15) + { + DBUG_PRINT("error", ("str_to_ipv4(%.*s): " + "invalid IPv4 address: too long.", + str_length, str)); + return false; + } + + unsigned char *ipv4_bytes= (unsigned char *) ipv4_address; + const char *p= str; + int byte_value= 0; + int chars_in_group= 0; + int dot_count= 0; + char c= 0; + + while (((p - str) < str_length) && *p) + { + c= *p++; + + if (my_isdigit(&my_charset_latin1, c)) + { + ++chars_in_group; + + if (chars_in_group > 3) + { + DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: " + "too many characters in a group.", + str_length, str)); + return false; + } + + byte_value= byte_value * 10 + (c - '0'); + + if (byte_value > 255) + { + DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: " + "invalid byte value.", + str_length, str)); + return false; + } + } + else if (c == '.') + { + if (chars_in_group == 0) + { + DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: " + "too few characters in a group.", + str_length, str)); + return false; + } + + ipv4_bytes[dot_count]= (unsigned char) byte_value; + + ++dot_count; + byte_value= 0; + chars_in_group= 0; + + if (dot_count > 3) + { + DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: " + "too many dots.", str_length, str)); + return false; + } + } + else + { + DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: " + "invalid character at pos %d.", + str_length, str, (int) (p - str))); + return false; + } + } + + if (c == '.') + { + DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: " + "ending at '.'.", str_length, str)); + return false; + } + + if (dot_count != 3) + { + DBUG_PRINT("error", ("str_to_ipv4(%.*s): invalid IPv4 address: " + "too few groups.", + str_length, str)); + return false; + } + + ipv4_bytes[3]= (unsigned char) byte_value; + + DBUG_PRINT("info", ("str_to_ipv4(%.*s): valid IPv4 address: %d.%d.%d.%d", + str_length, str, + ipv4_bytes[0], ipv4_bytes[1], + ipv4_bytes[2], ipv4_bytes[3])); + return true; +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Tries to convert given string to binary IPv6-address representation. + This is a portable alternative to inet_pton(AF_INET6). + + @param str String to convert. + @param str_len String length. + @param[out] ipv6_address Buffer to store IPv6-address. + + @return Completion status. + @retval false Given string does not represent an IPv6-address. + @retval true The string has been converted sucessfully. + + @note The problem with inet_pton() is that it treats leading zeros in + IPv4-part differently on different platforms. +*/ + +static bool str_to_ipv6(const char *str, int str_length, in6_addr *ipv6_address) +{ + if (str_length < 2) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: too short.", + str_length, str)); + return false; + } + + if (str_length > 8 * 4 + 7) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: too long.", + str_length, str)); + return false; + } + + memset(ipv6_address, 0, IN6_ADDR_SIZE); + + const char *p= str; + + if (*p == ':') + { + ++p; + + if (*p != ':') + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "can not start with ':x'.", str_length, str)); + return false; + } + } + + char *ipv6_bytes= (char *) ipv6_address; + char *ipv6_bytes_end= ipv6_bytes + IN6_ADDR_SIZE; + char *dst= ipv6_bytes; + char *gap_ptr= NULL; + const char *group_start_ptr= p; + int chars_in_group= 0; + int group_value= 0; + + while (((p - str) < str_length) && *p) + { + char c= *p++; + + if (c == ':') + { + group_start_ptr= p; + + if (!chars_in_group) + { + if (gap_ptr) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "too many gaps(::).", str_length, str)); + return false; + } + + gap_ptr= dst; + continue; + } + + if (!*p || ((p - str) >= str_length)) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "ending at ':'.", str_length, str)); + return false; + } + + if (dst + 2 > ipv6_bytes_end) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "too many groups (1).", str_length, str)); + return false; + } + + dst[0]= (unsigned char) (group_value >> 8) & 0xff; + dst[1]= (unsigned char) group_value & 0xff; + dst += 2; + + chars_in_group= 0; + group_value= 0; + } + else if (c == '.') + { + if (dst + IN_ADDR_SIZE > ipv6_bytes_end) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "unexpected IPv4-part.", str_length, str)); + return false; + } + + if (!str_to_ipv4(group_start_ptr, + str + str_length - group_start_ptr, + (in_addr *) dst)) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "invalid IPv4-part.", str_length, str)); + return false; + } + + dst += IN_ADDR_SIZE; + chars_in_group= 0; + + break; + } + else + { + const char *hdp= strchr(HEX_DIGITS, my_tolower(&my_charset_latin1, c)); + + if (!hdp) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "invalid character at pos %d.", + str_length, str, (int) (p - str))); + return false; + } + + if (chars_in_group >= 4) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "too many digits in group.", + str_length, str)); + return false; + } + + group_value <<= 4; + group_value |= hdp - HEX_DIGITS; + + DBUG_ASSERT(group_value <= 0xffff); + + ++chars_in_group; + } + } + + if (chars_in_group > 0) + { + if (dst + 2 > ipv6_bytes_end) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "too many groups (2).", str_length, str)); + return false; + } + + dst[0]= (unsigned char) (group_value >> 8) & 0xff; + dst[1]= (unsigned char) group_value & 0xff; + dst += 2; + } + + if (gap_ptr) + { + if (dst == ipv6_bytes_end) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "no room for a gap (::).", str_length, str)); + return false; + } + + int bytes_to_move= dst - gap_ptr; + + for (int i= 1; i <= bytes_to_move; ++i) + { + ipv6_bytes_end[-i]= gap_ptr[bytes_to_move - i]; + gap_ptr[bytes_to_move - i]= 0; + } + + dst= ipv6_bytes_end; + } + + if (dst < ipv6_bytes_end) + { + DBUG_PRINT("error", ("str_to_ipv6(%.*s): invalid IPv6 address: " + "too few groups.", str_length, str)); + return false; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Converts IPv4-binary-address to a string. This function is a portable + alternative to inet_ntop(AF_INET). + + @param[in] ipv4 IPv4-address data (byte array) + @param[out] str A buffer to store string representation of IPv4-address. + It must be at least of INET_ADDRSTRLEN. + + @note The problem with inet_ntop() is that it is available starting from + Windows Vista, but the minimum supported version is Windows 2000. +*/ + +static void ipv4_to_str(const in_addr *ipv4, char *str) +{ + const unsigned char *ipv4_bytes= (const unsigned char *) ipv4; + + sprintf(str, "%d.%d.%d.%d", + ipv4_bytes[0], ipv4_bytes[1], ipv4_bytes[2], ipv4_bytes[3]); +} +/////////////////////////////////////////////////////////////////////////// + +/** + Converts IPv6-binary-address to a string. This function is a portable + alternative to inet_ntop(AF_INET6). + + @param[in] ipv6 IPv6-address data (byte array) + @param[out] str A buffer to store string representation of IPv6-address. + It must be at least of INET6_ADDRSTRLEN. + + @note The problem with inet_ntop() is that it is available starting from + Windows Vista, but out the minimum supported version is Windows 2000. +*/ + +static void ipv6_to_str(const in6_addr *ipv6, char *str) +{ + struct Region + { + int pos; + int length; + }; + + const unsigned char *ipv6_bytes= (const unsigned char *) ipv6; + + // 1. Translate IPv6-address bytes to words. + // We can't just cast to short, because it's not guaranteed + // that sizeof (short) == 2. So, we have to make a copy. + + uint16 ipv6_words[IN6_ADDR_NUM_WORDS]; + + for (int i= 0; i < IN6_ADDR_NUM_WORDS; ++i) + ipv6_words[i]= (ipv6_bytes[2 * i] << 8) + ipv6_bytes[2 * i + 1]; + + // 2. Find "the gap" -- longest sequence of zeros in IPv6-address. + + Region gap= { -1, -1 }; + + { + Region rg= { -1, -1 }; + + for (int i = 0; i < IN6_ADDR_NUM_WORDS; ++i) + { + if (ipv6_words[i] != 0) + { + if (rg.pos >= 0) + { + if (rg.length > gap.length) + gap= rg; + + rg.pos= -1; + rg.length= -1; + } + } + else + { + if (rg.pos >= 0) + { + ++rg.length; + } + else + { + rg.pos= i; + rg.length= 1; + } + } + } + + if (rg.pos >= 0) + { + if (rg.length > gap.length) + gap= rg; + } + } + + // 3. Convert binary data to string. + + char *p= str; + + for (int i = 0; i < IN6_ADDR_NUM_WORDS; ++i) + { + if (i == gap.pos) + { + // We're at the gap position. We should put trailing ':' and jump to + // the end of the gap. + + if (i == 0) + { + // The gap starts from the beginning of the data -- leading ':' + // should be put additionally. + + *p= ':'; + ++p; + } + + *p= ':'; + ++p; + + i += gap.length - 1; + } + else if (i == 6 && gap.pos == 0 && + (gap.length == 6 || // IPv4-compatible + (gap.length == 5 && ipv6_words[5] == 0xffff) // IPv4-mapped + )) + { + // The data represents either IPv4-compatible or IPv4-mapped address. + // The IPv6-part (zeros or zeros + ffff) has been already put into + // the string (str). Now it's time to dump IPv4-part. + + ipv4_to_str((const in_addr *) (ipv6_bytes + 12), p); + return; + } + else + { + // Usual IPv6-address-field. Print it out using lower-case + // hex-letters without leading zeros (recommended IPv6-format). + // + // If it is not the last field, append closing ':'. + + p += sprintf(p, "%x", ipv6_words[i]); + + if (i != IN6_ADDR_NUM_WORDS - 1) + { + *p= ':'; + ++p; + } + } + } + + *p= 0; +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Converts IP-address-string to IP-address-data. + + @param arg IP-address-string. + @param [out] buffer Buffer to store IP-address-data. + + @return Completion status. + @retval false Given string does not represent an IP-address. + @retval true The string has been converted sucessfully. +*/ + +bool Item_func_inet6_aton::calc_value(const String *arg, String *buffer) +{ + // ipv4-string -> varbinary(4) + // ipv6-string -> varbinary(16) + + in_addr ipv4_address; + in6_addr ipv6_address; + + if (str_to_ipv4(arg->ptr(), arg->length(), &ipv4_address)) + { + buffer->length(0); + buffer->append((char *) &ipv4_address, sizeof (in_addr), &my_charset_bin); + + return true; + } + + if (str_to_ipv6(arg->ptr(), arg->length(), &ipv6_address)) + { + buffer->length(0); + buffer->append((char *) &ipv6_address, sizeof (in6_addr), &my_charset_bin); + + return true; + } + + return false; +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Converts IP-address-data to IP-address-string. + + @param arg IP-address-data. + @param [out] buffer Buffer to store IP-address-string. + + @return Completion status. + @retval false The argument does not correspond to IP-address. + @retval true The string has been converted sucessfully. +*/ + +bool Item_func_inet6_ntoa::calc_value(const String *arg, String *buffer) +{ + if (arg->charset() != &my_charset_bin) + return false; + + if ((int) arg->length() == IN_ADDR_SIZE) + { + char str[INET_ADDRSTRLEN]; + + ipv4_to_str((const in_addr *) arg->ptr(), str); + + buffer->length(0); + buffer->append(str, (uint32) strlen(str), &my_charset_latin1); + + return true; + } + else if ((int) arg->length() == IN6_ADDR_SIZE) + { + char str[INET6_ADDRSTRLEN]; + + ipv6_to_str((const in6_addr *) arg->ptr(), str); + + buffer->length(0); + buffer->append(str, (uint32) strlen(str), &my_charset_latin1); + + return true; + } + + DBUG_PRINT("info", + ("INET6_NTOA(): varbinary(4) or varbinary(16) expected.")); + return false; +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Checks if the passed string represents an IPv4-address. + + @param arg The string to check. + + @return Check status. + @retval false The passed string does not represent an IPv4-address. + @retval true The passed string represents an IPv4-address. +*/ + +bool Item_func_is_ipv4::calc_value(const String *arg) +{ + in_addr ipv4_address; + + return str_to_ipv4(arg->ptr(), arg->length(), &ipv4_address); +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Checks if the passed string represents an IPv6-address. + + @param arg The string to check. + + @return Check status. + @retval false The passed string does not represent an IPv6-address. + @retval true The passed string represents an IPv6-address. +*/ + +bool Item_func_is_ipv6::calc_value(const String *arg) +{ + in6_addr ipv6_address; + + return str_to_ipv6(arg->ptr(), arg->length(), &ipv6_address); +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Checks if the passed IPv6-address is an IPv4-compat IPv6-address. + + @param arg The IPv6-address to check. + + @return Check status. + @retval false The passed IPv6-address is not an IPv4-compatible IPv6-address. + @retval true The passed IPv6-address is an IPv4-compatible IPv6-address. +*/ + +bool Item_func_is_ipv4_compat::calc_value(const String *arg) +{ + if ((int) arg->length() != IN6_ADDR_SIZE || arg->charset() != &my_charset_bin) + return false; + + return IN6_IS_ADDR_V4COMPAT((struct in6_addr *) arg->ptr()); +} + +/////////////////////////////////////////////////////////////////////////// + +/** + Checks if the passed IPv6-address is an IPv4-mapped IPv6-address. + + @param arg The IPv6-address to check. + + @return Check status. + @retval false The passed IPv6-address is not an IPv4-mapped IPv6-address. + @retval true The passed IPv6-address is an IPv4-mapped IPv6-address. +*/ + +bool Item_func_is_ipv4_mapped::calc_value(const String *arg) +{ + if ((int) arg->length() != IN6_ADDR_SIZE || arg->charset() != &my_charset_bin) + return false; + + return IN6_IS_ADDR_V4MAPPED((struct in6_addr *) arg->ptr()); +} diff --git a/sql/item_inetfunc.h b/sql/item_inetfunc.h new file mode 100644 index 00000000000..eaafd005f91 --- /dev/null +++ b/sql/item_inetfunc.h @@ -0,0 +1,243 @@ +#ifndef ITEM_INETFUNC_INCLUDED +#define ITEM_INETFUNC_INCLUDED + +/* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2014 MariaDB Foundation + + 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 */ + + +#include "item.h" + +/************************************************************************* + Item_func_inet_aton implements INET_ATON() SQL-function. +*************************************************************************/ + +class Item_func_inet_aton : public Item_int_func +{ +public: + Item_func_inet_aton(THD *thd, Item *a): Item_int_func(thd, a) {} + longlong val_int(); + const char *func_name() const { return "inet_aton"; } + void fix_length_and_dec() + { + decimals= 0; + max_length= 21; + maybe_null= 1; + unsigned_flag= 1; + } +}; + + +/************************************************************************* + Item_func_inet_ntoa implements INET_NTOA() SQL-function. +*************************************************************************/ + +class Item_func_inet_ntoa : public Item_str_func +{ +public: + Item_func_inet_ntoa(THD *thd, Item *a): Item_str_func(thd, a) + { } + String* val_str(String* str); + const char *func_name() const { return "inet_ntoa"; } + void fix_length_and_dec() + { + decimals= 0; + fix_length_and_charset(3 * 8 + 7, default_charset()); + maybe_null= 1; + } +}; + + +/************************************************************************* + Item_func_inet_bool_base implements common code for INET6/IP-related + functions returning boolean value. +*************************************************************************/ + +class Item_func_inet_bool_base : public Item_bool_func +{ +public: + inline Item_func_inet_bool_base(THD *thd, Item *ip_addr): + Item_bool_func(thd, ip_addr) + { + null_value= false; + } + +public: + virtual longlong val_int(); + +protected: + virtual bool calc_value(const String *arg) = 0; +}; + + +/************************************************************************* + Item_func_inet_str_base implements common code for INET6/IP-related + functions returning string value. +*************************************************************************/ + +class Item_func_inet_str_base : public Item_str_ascii_func +{ +public: + inline Item_func_inet_str_base(THD *thd, Item *arg): + Item_str_ascii_func(thd, arg) + { } + +public: + virtual String *val_str_ascii(String *buffer); + +protected: + virtual bool calc_value(const String *arg, String *buffer) = 0; +}; + + +/************************************************************************* + Item_func_inet6_aton implements INET6_ATON() SQL-function. +*************************************************************************/ + +class Item_func_inet6_aton : public Item_func_inet_str_base +{ +public: + inline Item_func_inet6_aton(THD *thd, Item *ip_addr): + Item_func_inet_str_base(thd, ip_addr) + { } + +public: + virtual const char *func_name() const + { return "inet6_aton"; } + + virtual void fix_length_and_dec() + { + decimals= 0; + fix_length_and_charset(16, &my_charset_bin); + maybe_null= 1; + } + +protected: + virtual bool calc_value(const String *arg, String *buffer); +}; + + +/************************************************************************* + Item_func_inet6_ntoa implements INET6_NTOA() SQL-function. +*************************************************************************/ + +class Item_func_inet6_ntoa : public Item_func_inet_str_base +{ +public: + inline Item_func_inet6_ntoa(THD *thd, Item *ip_addr): + Item_func_inet_str_base(thd, ip_addr) + { } + +public: + virtual const char *func_name() const + { return "inet6_ntoa"; } + + virtual void fix_length_and_dec() + { + decimals= 0; + + // max length: IPv6-address -- 16 bytes + // 16 bytes / 2 bytes per group == 8 groups => 7 delimiter + // 4 symbols per group + fix_length_and_charset(8 * 4 + 7, default_charset()); + + maybe_null= 1; + } + +protected: + virtual bool calc_value(const String *arg, String *buffer); +}; + + +/************************************************************************* + Item_func_is_ipv4 implements IS_IPV4() SQL-function. +*************************************************************************/ + +class Item_func_is_ipv4 : public Item_func_inet_bool_base +{ +public: + inline Item_func_is_ipv4(THD *thd, Item *ip_addr): + Item_func_inet_bool_base(thd, ip_addr) + { } + +public: + virtual const char *func_name() const + { return "is_ipv4"; } + +protected: + virtual bool calc_value(const String *arg); +}; + + +/************************************************************************* + Item_func_is_ipv6 implements IS_IPV6() SQL-function. +*************************************************************************/ + +class Item_func_is_ipv6 : public Item_func_inet_bool_base +{ +public: + inline Item_func_is_ipv6(THD *thd, Item *ip_addr): + Item_func_inet_bool_base(thd, ip_addr) + { } + +public: + virtual const char *func_name() const + { return "is_ipv6"; } + +protected: + virtual bool calc_value(const String *arg); +}; + + +/************************************************************************* + Item_func_is_ipv4_compat implements IS_IPV4_COMPAT() SQL-function. +*************************************************************************/ + +class Item_func_is_ipv4_compat : public Item_func_inet_bool_base +{ +public: + inline Item_func_is_ipv4_compat(THD *thd, Item *ip_addr): + Item_func_inet_bool_base(thd, ip_addr) + { } + +public: + virtual const char *func_name() const + { return "is_ipv4_compat"; } + +protected: + virtual bool calc_value(const String *arg); +}; + + +/************************************************************************* + Item_func_is_ipv4_mapped implements IS_IPV4_MAPPED() SQL-function. +*************************************************************************/ + +class Item_func_is_ipv4_mapped : public Item_func_inet_bool_base +{ +public: + inline Item_func_is_ipv4_mapped(THD *thd, Item *ip_addr): + Item_func_inet_bool_base(thd, ip_addr) + { } + +public: + virtual const char *func_name() const + { return "is_ipv4_mapped"; } + +protected: + virtual bool calc_value(const String *arg); +}; + +#endif // ITEM_INETFUNC_INCLUDED diff --git a/sql/item_row.cc b/sql/item_row.cc index 9fe34dd00fd..8c6edacad7f 100644 --- a/sql/item_row.cc +++ b/sql/item_row.cc @@ -14,6 +14,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" /* It is necessary to include set_var.h instead of item.h because there @@ -23,68 +24,6 @@ #include "sql_class.h" // THD, set_var.h: THD #include "set_var.h" -/** - Row items used for comparing rows and IN operations on rows: - - @verbatim - (a, b, c) > (10, 10, 30) - (a, b, c) = (select c, d, e, from t1 where x=12) - (a, b, c) IN ((1,2,2), (3,4,5), (6,7,8) - (a, b, c) IN (select c, d, e, from t1) - @endverbatim - - @todo - think placing 2-3 component items in item (as it done for function -*/ - -Item_row::Item_row(List<Item> &arg): - Item(), used_tables_cache(0), not_null_tables_cache(0), - const_item_cache(1), with_null(0) -{ - - //TODO: think placing 2-3 component items in item (as it done for function) - if ((arg_count= arg.elements)) - { - items= (Item**) sql_alloc(sizeof(Item*)*arg_count); - if (!items) - { - arg_count= 0; - return; - } - } - else - items= 0; - List_iterator<Item> li(arg); - uint i= 0; - Item *item; - while ((item= li++)) - { - items[i]= item; - i++; - } -} - - -Item_row::Item_row(Item *item): - Item(), - used_tables_cache(0), - not_null_tables_cache(0), - arg_count(item->cols()), - const_item_cache(1), - with_null(0) -{ - items= (Item**) sql_alloc(sizeof(Item*) * arg_count); - if (!items) - { - arg_count= 0; - return; - } - for (uint i= 0; i < arg_count; i++) - { - items[i]= item->element_index(i); - } -} - void Item_row::illegal_method_call(const char *method) { DBUG_ENTER("Item_row::illegal_method_call"); @@ -100,7 +39,7 @@ bool Item_row::fix_fields(THD *thd, Item **ref) null_value= 0; maybe_null= 0; Item **arg, **arg_end; - for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++) + for (arg= args, arg_end= args + arg_count; arg != arg_end ; arg++) { if (!(*arg)->fixed && (*arg)->fix_fields(thd, arg)) @@ -139,7 +78,7 @@ Item_row::eval_not_null_tables(uchar *opt_arg) not_null_tables_cache= 0; if (arg_count) { - for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++) + for (arg= args, arg_end= args + arg_count; arg != arg_end ; arg++) { not_null_tables_cache|= (*arg)->not_null_tables(); } @@ -154,8 +93,7 @@ void Item_row::cleanup() Item::cleanup(); /* Reset to the original values */ - used_tables_cache= 0; - const_item_cache= 1; + used_tables_and_const_cache_init(); with_null= 0; DBUG_VOID_RETURN; @@ -163,38 +101,25 @@ void Item_row::cleanup() void Item_row::split_sum_func(THD *thd, Item **ref_pointer_array, - List<Item> &fields) + List<Item> &fields, uint flags) { Item **arg, **arg_end; - for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++) - (*arg)->split_sum_func2(thd, ref_pointer_array, fields, arg, TRUE); -} - - -void Item_row::update_used_tables() -{ - used_tables_cache= 0; - const_item_cache= 1; - for (uint i= 0; i < arg_count; i++) - { - items[i]->update_used_tables(); - used_tables_cache|= items[i]->used_tables(); - const_item_cache&= items[i]->const_item(); - } + for (arg= args, arg_end= args + arg_count; arg != arg_end ; arg++) + (*arg)->split_sum_func2(thd, ref_pointer_array, fields, arg, + flags | SPLIT_SUM_SKIP_REGISTERED); } -void Item_row::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_row::fix_after_pullout(st_select_lex *new_parent, Item **ref, + bool merge) { - used_tables_cache= 0; - const_item_cache= 1; + used_tables_and_const_cache_init(); not_null_tables_cache= 0; for (uint i= 0; i < arg_count; i++) { - items[i]->fix_after_pullout(new_parent, &items[i]); - used_tables_cache|= items[i]->used_tables(); - const_item_cache&= items[i]->const_item(); - not_null_tables_cache|= items[i]->not_null_tables(); + args[i]->fix_after_pullout(new_parent, &args[i], merge); + used_tables_and_const_cache_join(args[i]); + not_null_tables_cache|= args[i]->not_null_tables(); } } @@ -216,47 +141,23 @@ void Item_row::print(String *str, enum_query_type query_type) { if (i) str->append(','); - items[i]->print(str, query_type); + args[i]->print(str, query_type); } str->append(')'); } -bool Item_row::walk(Item_processor processor, bool walk_subquery, uchar *arg) -{ - for (uint i= 0; i < arg_count; i++) - { - if (items[i]->walk(processor, walk_subquery, arg)) - return 1; - } - return (this->*processor)(arg); -} - - -Item *Item_row::transform(Item_transformer transformer, uchar *arg) +Item *Item_row::transform(THD *thd, Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); - for (uint i= 0; i < arg_count; i++) - { - Item *new_item= items[i]->transform(transformer, arg); - if (!new_item) - return 0; - - /* - THD::change_item_tree() should be called only if the tree was - really transformed, i.e. when a new item has been created. - Otherwise we'll be allocating a lot of unnecessary memory for - change records at each execution. - */ - if (items[i] != new_item) - current_thd->change_item_tree(&items[i], new_item); - } - return (this->*transformer)(arg); + if (transform_args(thd, transformer, arg)) + return 0; + return (this->*transformer)(thd, arg); } void Item_row::bring_value() { for (uint i= 0; i < arg_count; i++) - items[i]->bring_value(); + args[i]->bring_value(); } diff --git a/sql/item_row.h b/sql/item_row.h index 4d5c20711b5..5e8071ec495 100644 --- a/sql/item_row.h +++ b/sql/item_row.h @@ -2,7 +2,7 @@ #define ITEM_ROW_INCLUDED /* - Copyright (c) 2002, 2010, Oracle and/or its affiliates. + Copyright (c) 2002, 2013, 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 @@ -17,25 +17,40 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -class Item_row: public Item +/** + Row items used for comparing rows and IN operations on rows: + + @verbatim + (a, b, c) > (10, 10, 30) + (a, b, c) = (select c, d, e, from t1 where x=12) + (a, b, c) IN ((1,2,2), (3,4,5), (6,7,8) + (a, b, c) IN (select c, d, e, from t1) + @endverbatim +*/ + + +/** + Item which stores (x,y,...) and ROW(x,y,...). + Note that this can be recursive: ((x,y),(z,t)) is a ROW of ROWs. +*/ +class Item_row: public Item, + private Item_args, + private Used_tables_and_const_cache { - Item **items; - table_map used_tables_cache, not_null_tables_cache; - uint arg_count; - bool const_item_cache; + table_map not_null_tables_cache; + /** + If elements are made only of constants, of which one or more are + NULL. For example, this item is (1,2,NULL), or ( (1,NULL), (2,3) ). + */ bool with_null; public: - Item_row(List<Item> &); - Item_row(Item_row *item): - Item(), - items(item->items), - used_tables_cache(item->used_tables_cache), - not_null_tables_cache(0), - arg_count(item->arg_count), - const_item_cache(item->const_item_cache), - with_null(0) - {} - Item_row(Item *item); + Item_row(THD *thd, List<Item> &list): + Item(thd), Item_args(thd, list), not_null_tables_cache(0), with_null(0) + { } + Item_row(THD *thd, Item_row *row): + Item(thd), Item_args(thd, static_cast<Item_args*>(row)), Used_tables_and_const_cache(), + not_null_tables_cache(0), with_null(0) + { } enum Type type() const { return ROW_ITEM; }; void illegal_method_call(const char *); @@ -65,24 +80,34 @@ public: return 0; }; bool fix_fields(THD *thd, Item **ref); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); void cleanup(); - void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields); + void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields, + uint flags); table_map used_tables() const { return used_tables_cache; }; bool const_item() const { return const_item_cache; }; enum Item_result result_type() const { return ROW_RESULT; } Item_result cmp_type() const { return ROW_RESULT; } - void update_used_tables(); + void update_used_tables() + { + used_tables_and_const_cache_init(); + used_tables_and_const_cache_update_and_join(arg_count, args); + } table_map not_null_tables() const { return not_null_tables_cache; } virtual void print(String *str, enum_query_type query_type); - bool walk(Item_processor processor, bool walk_subquery, uchar *arg); - Item *transform(Item_transformer transformer, uchar *arg); + bool walk(Item_processor processor, bool walk_subquery, uchar *arg) + { + if (walk_args(processor, walk_subquery, arg)) + return true; + return (this->*processor)(arg); + } + Item *transform(THD *thd, Item_transformer transformer, uchar *arg); bool eval_not_null_tables(uchar *opt_arg); uint cols() { return arg_count; } - Item* element_index(uint i) { return items[i]; } - Item** addr(uint i) { return items + i; } + Item* element_index(uint i) { return args[i]; } + Item** addr(uint i) { return args + i; } bool check_cols(uint c); bool null_inside() { return with_null; }; void bring_value(); diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index a7ca21b79de..de13999bab8 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -31,10 +31,7 @@ #pragma implementation // gcc: Class implementation #endif -/* May include caustic 3rd-party defs. Use early, so it can override nothing. */ -#include "sha2.h" -#include "my_global.h" // HAVE_* - +#include <my_global.h> // HAVE_* #include "sql_priv.h" /* @@ -51,15 +48,16 @@ #include "password.h" // my_make_scrambled_password, // my_make_scrambled_password_323 #include <m_ctype.h> -#include "my_md5.h" -#include "sha1.h" -#include "my_aes.h" +#include <my_md5.h> #include <zlib.h> C_MODE_START #include "../mysys/my_static.h" // For soundex_map C_MODE_END +#include "sql_show.h" // append_identifier +#include <sql_repl.h> +#include "sql_statistics.h" -size_t username_char_length= 16; +size_t username_char_length= 80; /* For the Items which have only val_str_ascii() method @@ -69,8 +67,14 @@ size_t username_char_length= 16; Conversion happens only in case of "tricky" Item character set (e.g. UCS2). Normally conversion does not happen, and val_str_ascii() is immediately returned instead. + + No matter if conversion is needed or not needed, + the result is always returned in "str" (see MDEV-10306 why). + + @param [OUT] str - Store the result here + @param [IN] ascii_buffer - Use this temporary buffer to call val_str_ascii() */ -String *Item_func::val_str_from_val_str_ascii(String *str, String *str2) +String *Item_func::val_str_from_val_str_ascii(String *str, String *ascii_buffer) { DBUG_ASSERT(fixed == 1); @@ -82,35 +86,19 @@ String *Item_func::val_str_from_val_str_ascii(String *str, String *str2) return res; } - DBUG_ASSERT(str != str2); + DBUG_ASSERT(str != ascii_buffer); uint errors; - String *res= val_str_ascii(str); + String *res= val_str_ascii(ascii_buffer); if (!res) return 0; - if ((null_value= str2->copy(res->ptr(), res->length(), - &my_charset_latin1, collation.collation, - &errors))) + if ((null_value= str->copy(res->ptr(), res->length(), + &my_charset_latin1, collation.collation, + &errors))) return 0; - return str2; -} - - -/* - Convert an array of bytes to a hexadecimal representation. - - Used to generate a hexadecimal representation of a message digest. -*/ -static void array_to_hex(char *to, const unsigned char *str, uint len) -{ - const unsigned char *str_end= str + len; - for (; str != str_end; ++str) - { - *to++= _dig_vec_lower[((uchar) *str) >> 4]; - *to++= _dig_vec_lower[((uchar) *str) & 0x0F]; - } + return str; } @@ -121,9 +109,7 @@ bool Item_str_func::fix_fields(THD *thd, Item **ref) In Item_str_func::check_well_formed_result() we may set null_value flag on the same condition as in test() below. */ - maybe_null= (maybe_null || - test(thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))); + maybe_null= maybe_null || thd->is_strict_mode(); return res; } @@ -131,40 +117,27 @@ bool Item_str_func::fix_fields(THD *thd, Item **ref) my_decimal *Item_str_func::val_decimal(my_decimal *decimal_value) { DBUG_ASSERT(fixed == 1); - char buff[64]; - String *res, tmp(buff,sizeof(buff), &my_charset_bin); - res= val_str(&tmp); - if (!res) - return 0; - (void)str2my_decimal(E_DEC_FATAL_ERROR, (char*) res->ptr(), - res->length(), res->charset(), decimal_value); - return decimal_value; + StringBuffer<64> tmp; + String *res= val_str(&tmp); + return res ? decimal_from_string_with_check(decimal_value, res) : 0; } double Item_str_func::val_real() { DBUG_ASSERT(fixed == 1); - int err_not_used; - char *end_not_used, buff[64]; - String *res, tmp(buff,sizeof(buff), &my_charset_bin); - res= val_str(&tmp); - return res ? my_strntod(res->charset(), (char*) res->ptr(), res->length(), - &end_not_used, &err_not_used) : 0.0; + StringBuffer<64> tmp; + String *res= val_str(&tmp); + return res ? double_from_string_with_check(res) : 0.0; } longlong Item_str_func::val_int() { DBUG_ASSERT(fixed == 1); - int err; - char buff[22]; - String *res, tmp(buff,sizeof(buff), &my_charset_bin); - res= val_str(&tmp); - return (res ? - my_strntoll(res->charset(), res->ptr(), res->length(), 10, NULL, - &err) : - (longlong) 0); + StringBuffer<22> tmp; + String *res= val_str(&tmp); + return res ? longlong_from_string_with_check(res) : 0; } @@ -177,7 +150,7 @@ String *Item_func_md5::val_str_ascii(String *str) uchar digest[16]; null_value=0; - MY_MD5_HASH(digest,(uchar *) sptr->ptr(), sptr->length()); + compute_md5_hash(digest, (const char *) sptr->ptr(), sptr->length()); if (str->alloc(32)) // Ensure that memory is free { null_value=1; @@ -193,52 +166,21 @@ String *Item_func_md5::val_str_ascii(String *str) } -/* - The MD5()/SHA() functions treat their parameter as being a case sensitive. - Thus we set binary collation on it so different instances of MD5() will be - compared properly. -*/ -static CHARSET_INFO *get_checksum_charset(const char *csname) -{ - CHARSET_INFO *cs= get_charset_by_csname(csname, MY_CS_BINSORT, MYF(0)); - if (!cs) - { - // Charset has no binary collation: use my_charset_bin. - cs= &my_charset_bin; - } - return cs; -} - - -void Item_func_md5::fix_length_and_dec() -{ - CHARSET_INFO *cs= get_checksum_charset(args[0]->collation.collation->csname); - args[0]->collation.set(cs, DERIVATION_COERCIBLE); - fix_length_and_charset(32, default_charset()); -} - - String *Item_func_sha::val_str_ascii(String *str) { DBUG_ASSERT(fixed == 1); String * sptr= args[0]->val_str(str); if (sptr) /* If we got value different from NULL */ { - SHA1_CONTEXT context; /* Context used to generate SHA1 hash */ /* Temporary buffer to store 160bit digest */ - uint8 digest[SHA1_HASH_SIZE]; - mysql_sha1_reset(&context); /* We do not have to check for error here */ - /* No need to check error as the only case would be too long message */ - mysql_sha1_input(&context, - (const uchar *) sptr->ptr(), sptr->length()); - + uint8 digest[MY_SHA1_HASH_SIZE]; + my_sha1(digest, (const char *) sptr->ptr(), sptr->length()); /* Ensure that memory is free and we got result */ - if (!( str->alloc(SHA1_HASH_SIZE*2) || - (mysql_sha1_result(&context,digest)))) + if (!str->alloc(MY_SHA1_HASH_SIZE*2)) { - array_to_hex((char *) str->ptr(), digest, SHA1_HASH_SIZE); + array_to_hex((char *) str->ptr(), digest, MY_SHA1_HASH_SIZE); str->set_charset(&my_charset_numeric); - str->length((uint) SHA1_HASH_SIZE*2); + str->length((uint) MY_SHA1_HASH_SIZE*2); null_value=0; return str; } @@ -249,21 +191,17 @@ String *Item_func_sha::val_str_ascii(String *str) void Item_func_sha::fix_length_and_dec() { - CHARSET_INFO *cs= get_checksum_charset(args[0]->collation.collation->csname); - args[0]->collation.set(cs, DERIVATION_COERCIBLE); // size of hex representation of hash - fix_length_and_charset(SHA1_HASH_SIZE * 2, default_charset()); + fix_length_and_charset(MY_SHA1_HASH_SIZE * 2, default_charset()); } String *Item_func_sha2::val_str_ascii(String *str) { DBUG_ASSERT(fixed == 1); -#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) - unsigned char digest_buf[SHA512_DIGEST_LENGTH]; + unsigned char digest_buf[512/8]; // enough for SHA512 String *input_string; - unsigned char *input_ptr; + const char *input_ptr; size_t input_len; - uint digest_length= 0; input_string= args[0]->val_str(str); str->set_charset(&my_charset_bin); @@ -278,40 +216,40 @@ String *Item_func_sha2::val_str_ascii(String *str) if (null_value) return (String *) NULL; - input_ptr= (unsigned char *) input_string->ptr(); + input_ptr= input_string->ptr(); input_len= input_string->length(); - switch ((uint) args[1]->val_int()) { -#ifndef OPENSSL_NO_SHA512 + longlong digest_length= args[1]->val_int(); + switch (digest_length) { case 512: - digest_length= SHA512_DIGEST_LENGTH; - (void) SHA512(input_ptr, input_len, digest_buf); + my_sha512(digest_buf, input_ptr, input_len); break; case 384: - digest_length= SHA384_DIGEST_LENGTH; - (void) SHA384(input_ptr, input_len, digest_buf); + my_sha384(digest_buf, input_ptr, input_len); break; -#endif -#ifndef OPENSSL_NO_SHA256 case 224: - digest_length= SHA224_DIGEST_LENGTH; - (void) SHA224(input_ptr, input_len, digest_buf); + my_sha224(digest_buf, input_ptr, input_len); break; - case 256: case 0: // SHA-256 is the default - digest_length= SHA256_DIGEST_LENGTH; - (void) SHA256(input_ptr, input_len, digest_buf); + digest_length= 256; + /* fall through */ + case 256: + my_sha256(digest_buf, input_ptr, input_len); break; -#endif default: if (!args[1]->const_item()) - push_warning_printf(current_thd, - MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WRONG_PARAMETERS_TO_NATIVE_FCT, - ER(ER_WRONG_PARAMETERS_TO_NATIVE_FCT), "sha2"); + { + THD *thd= current_thd; + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_PARAMETERS_TO_NATIVE_FCT, + ER_THD(thd, ER_WRONG_PARAMETERS_TO_NATIVE_FCT), + "sha2"); + } null_value= TRUE; return NULL; } + digest_length/= 8; /* bits to bytes */ /* Since we're subverting the usual String methods, we must make sure that @@ -327,16 +265,6 @@ String *Item_func_sha2::val_str_ascii(String *str) null_value= FALSE; return str; - -#else - push_warning_printf(current_thd, - MYSQL_ERROR::WARN_LEVEL_WARN, - ER_FEATURE_DISABLED, - ER(ER_FEATURE_DISABLED), - "sha2", "--with-ssl"); - null_value= TRUE; - return (String *) NULL; -#endif /* defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) */ } @@ -345,70 +273,71 @@ void Item_func_sha2::fix_length_and_dec() maybe_null= 1; max_length = 0; -#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) int sha_variant= args[1]->const_item() ? args[1]->val_int() : 512; switch (sha_variant) { -#ifndef OPENSSL_NO_SHA512 + case 0: // SHA-256 is the default + sha_variant= 256; + /* fall through */ case 512: - fix_length_and_charset(SHA512_DIGEST_LENGTH * 2, default_charset()); - break; case 384: - fix_length_and_charset(SHA384_DIGEST_LENGTH * 2, default_charset()); - break; -#endif -#ifndef OPENSSL_NO_SHA256 case 256: - case 0: // SHA-256 is the default - fix_length_and_charset(SHA256_DIGEST_LENGTH * 2, default_charset()); - break; case 224: - fix_length_and_charset(SHA224_DIGEST_LENGTH * 2, default_charset()); + fix_length_and_charset(sha_variant/8 * 2, default_charset()); break; -#endif default: - push_warning_printf(current_thd, - MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WRONG_PARAMETERS_TO_NATIVE_FCT, - ER(ER_WRONG_PARAMETERS_TO_NATIVE_FCT), "sha2"); + THD *thd= current_thd; + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_PARAMETERS_TO_NATIVE_FCT, + ER_THD(thd, ER_WRONG_PARAMETERS_TO_NATIVE_FCT), + "sha2"); } +} - CHARSET_INFO *cs= get_checksum_charset(args[0]->collation.collation->csname); - args[0]->collation.set(cs, DERIVATION_COERCIBLE); +/* Implementation of AES encryption routines */ +void Item_aes_crypt::create_key(String *user_key, uchar *real_key) +{ + uchar *real_key_end= real_key + AES_KEY_LENGTH / 8; + uchar *ptr; + const char *sptr= user_key->ptr(); + const char *key_end= sptr + user_key->length(); -#else - push_warning_printf(current_thd, - MYSQL_ERROR::WARN_LEVEL_WARN, - ER_FEATURE_DISABLED, - ER(ER_FEATURE_DISABLED), - "sha2", "--with-ssl"); -#endif /* defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) */ + bzero(real_key, AES_KEY_LENGTH / 8); + + for (ptr= real_key; sptr < key_end; ptr++, sptr++) + { + if (ptr == real_key_end) + ptr= real_key; + *ptr ^= (uchar) *sptr; + } } -/* Implementation of AES encryption routines */ -String *Item_func_aes_encrypt::val_str(String *str) +String *Item_aes_crypt::val_str(String *str2) { DBUG_ASSERT(fixed == 1); - char key_buff[80]; - String tmp_key_value(key_buff, sizeof(key_buff), system_charset_info); - String *sptr= args[0]->val_str(str); // String to encrypt - String *key= args[1]->val_str(&tmp_key_value); // key - int aes_length; - if (sptr && key) // we need both arguments to be not NULL + StringBuffer<80> user_key_buf; + String *sptr= args[0]->val_str(&tmp_value); + String *user_key= args[1]->val_str(&user_key_buf); + uint32 aes_length; + + if (sptr && user_key) // we need both arguments to be not NULL { null_value=0; - aes_length=my_aes_get_size(sptr->length()); // Calculate result length + aes_length=my_aes_get_size(MY_AES_ECB, sptr->length()); - if (!str_value.alloc(aes_length)) // Ensure that memory is free + if (!str2->alloc(aes_length)) // Ensure that memory is free { - // finally encrypt directly to allocated buffer. - if (my_aes_encrypt(sptr->ptr(),sptr->length(), (char*) str_value.ptr(), - key->ptr(), key->length()) == aes_length) + uchar rkey[AES_KEY_LENGTH / 8]; + create_key(user_key, rkey); + + if (!my_aes_crypt(MY_AES_ECB, what, (uchar*)sptr->ptr(), sptr->length(), + (uchar*)str2->ptr(), &aes_length, + rkey, AES_KEY_LENGTH / 8, 0, 0)) { - // We got the expected result length - str_value.length((uint) aes_length); - return &str_value; + str2->length((uint) aes_length); + return str2; } } } @@ -416,185 +345,285 @@ String *Item_func_aes_encrypt::val_str(String *str) return 0; } - void Item_func_aes_encrypt::fix_length_and_dec() { - max_length=my_aes_get_size(args[0]->max_length); + max_length=my_aes_get_size(MY_AES_ECB, args[0]->max_length); + what= ENCRYPTION_FLAG_ENCRYPT; } -String *Item_func_aes_decrypt::val_str(String *str) + +void Item_func_aes_decrypt::fix_length_and_dec() { - DBUG_ASSERT(fixed == 1); - char key_buff[80]; - String tmp_key_value(key_buff, sizeof(key_buff), system_charset_info); - String *sptr, *key; - DBUG_ENTER("Item_func_aes_decrypt::val_str"); + max_length=args[0]->max_length; + maybe_null= 1; + what= ENCRYPTION_FLAG_DECRYPT; +} - sptr= args[0]->val_str(str); // String to decrypt - key= args[1]->val_str(&tmp_key_value); // Key - if (sptr && key) // Need to have both arguments not NULL + +void Item_func_to_base64::fix_length_and_dec() +{ + maybe_null= args[0]->maybe_null; + collation.set(default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII); + if (args[0]->max_length > (uint) base64_encode_max_arg_length()) { - null_value=0; - if (!str_value.alloc(sptr->length())) // Ensure that memory is free + maybe_null= 1; + fix_char_length_ulonglong((ulonglong) base64_encode_max_arg_length()); + } + else + { + int length= base64_needed_encoded_length((int) args[0]->max_length); + DBUG_ASSERT(length > 0); + fix_char_length_ulonglong((ulonglong) length - 1); + } +} + + +String *Item_func_to_base64::val_str_ascii(String *str) +{ + String *res= args[0]->val_str(&tmp_value); + bool too_long= false; + int length; + if (!res || + res->length() > (uint) base64_encode_max_arg_length() || + (too_long= + ((uint) (length= base64_needed_encoded_length((int) res->length())) > + current_thd->variables.max_allowed_packet)) || + str->alloc((uint) length)) + { + null_value= 1; // NULL input, too long input, or OOM. + if (too_long) { - // finally decrypt directly to allocated buffer. - int length; - length=my_aes_decrypt(sptr->ptr(), sptr->length(), - (char*) str_value.ptr(), - key->ptr(), key->length()); - if (length >= 0) // if we got correct data data - { - str_value.length((uint) length); - DBUG_RETURN(&str_value); - } + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), + thd->variables.max_allowed_packet); } + return 0; } - // Bad parameters. No memory or bad data will all go here - null_value=1; - DBUG_RETURN(0); + base64_encode(res->ptr(), (int) res->length(), (char*) str->ptr()); + DBUG_ASSERT(length > 0); + str->length((uint) length - 1); // Without trailing '\0' + null_value= 0; + return str; } -void Item_func_aes_decrypt::fix_length_and_dec() +void Item_func_from_base64::fix_length_and_dec() { - max_length=args[0]->max_length; - maybe_null= 1; + if (args[0]->max_length > (uint) base64_decode_max_arg_length()) + { + fix_char_length_ulonglong((ulonglong) base64_decode_max_arg_length()); + } + else + { + int length= base64_needed_decoded_length((int) args[0]->max_length); + fix_char_length_ulonglong((ulonglong) length); + } + maybe_null= 1; // Can be NULL, e.g. in case of badly formed input string +} + + +String *Item_func_from_base64::val_str(String *str) +{ + String *res= args[0]->val_str_ascii(&tmp_value); + int length; + const char *end_ptr; + + if (!res) + goto err; + + if (res->length() > (uint) base64_decode_max_arg_length() || + ((uint) (length= base64_needed_decoded_length((int) res->length())) > + current_thd->variables.max_allowed_packet)) + { + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), + thd->variables.max_allowed_packet); + goto err; + } + + if (str->alloc((uint) length)) + goto err; + + if ((length= base64_decode(res->ptr(), (int) res->length(), + (char *) str->ptr(), &end_ptr, 0)) < 0 || + end_ptr < res->ptr() + res->length()) + { + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_BAD_BASE64_DATA, ER_THD(thd, ER_BAD_BASE64_DATA), + end_ptr - res->ptr()); + goto err; + } + + str->length((uint) length); + null_value= 0; + return str; +err: + null_value= 1; // NULL input, too long input, OOM, or badly formed input + return 0; +} +/////////////////////////////////////////////////////////////////////////////// + + +const char *histogram_types[] = + {"SINGLE_PREC_HB", "DOUBLE_PREC_HB", 0}; +static TYPELIB hystorgam_types_typelib= + { array_elements(histogram_types), + "histogram_types", + histogram_types, NULL}; +const char *representation_by_type[]= {"%.3f", "%.5f"}; + +String *Item_func_decode_histogram::val_str(String *str) +{ + DBUG_ASSERT(fixed == 1); + char buff[STRING_BUFFER_USUAL_SIZE]; + String *res, tmp(buff, sizeof(buff), &my_charset_bin); + int type; + + tmp.length(0); + if (!(res= args[0]->val_str(&tmp)) || + (type= find_type(res->c_ptr_safe(), + &hystorgam_types_typelib, MYF(0))) <= 0) + { + null_value= 1; + return 0; + } + type--; + + tmp.length(0); + if (!(res= args[1]->val_str(&tmp))) + { + null_value= 1; + return 0; + } + if (type == DOUBLE_PREC_HB && res->length() % 2 != 0) + res->length(res->length() - 1); // one byte is unused + + double prev= 0.0; + uint i; + str->length(0); + char numbuf[32]; + const uchar *p= (uchar*)res->c_ptr(); + for (i= 0; i < res->length(); i++) + { + double val; + switch (type) + { + case SINGLE_PREC_HB: + val= p[i] / ((double)((1 << 8) - 1)); + break; + case DOUBLE_PREC_HB: + val= uint2korr(p + i) / ((double)((1 << 16) - 1)); + i++; + break; + default: + val= 0; + DBUG_ASSERT(0); + } + /* show delta with previous value */ + int size= my_snprintf(numbuf, sizeof(numbuf), + representation_by_type[type], val - prev); + str->append(numbuf, size); + str->append(","); + prev= val; + } + /* show delta with max */ + int size= my_snprintf(numbuf, sizeof(numbuf), + representation_by_type[type], 1.0 - prev); + str->append(numbuf, size); + + null_value=0; + return str; +} + + +/////////////////////////////////////////////////////////////////////////////// + +/* + Realloc the result buffer. + NOTE: We should be prudent in the initial allocation unit -- the + size of the arguments is a function of data distribution, which + can be any. Instead of overcommitting at the first row, we grow + the allocated amount by the factor of 2. This ensures that no + more than 25% of memory will be overcommitted on average. + + @param IN/OUT str - the result string + @param IN length - new total space required in "str" + @retval false - on success + @retval true - on error +*/ + +bool Item_func_concat::realloc_result(String *str, uint length) const +{ + if (str->alloced_length() >= length) + return false; // Alloced space is big enough, nothing to do. + + if (str->alloced_length() == 0) + return str->alloc(length); + + /* + Item_func_concat::val_str() makes sure the result length does not grow + higher than max_allowed_packet. So "length" is limited to 1G here. + We can't say anything about the current value of str->alloced_length(), + as str was initially set by args[0]->val_str(str). + So multiplication by 2 can overflow, if args[0] for some reasons + did not limit the result to max_alloced_packet. But it's not harmful, + "str" will be realloced exactly to "length" bytes in case of overflow. + */ + uint new_length= MY_MAX(str->alloced_length() * 2, length); + return str->realloc(new_length); } /** Concatenate args with the following premises: If only one arg (which is ok), return value of arg; - Don't reallocate val_str() if not absolute necessary. */ String *Item_func_concat::val_str(String *str) { DBUG_ASSERT(fixed == 1); - String *res,*res2,*use_as_buff; - uint i; - bool is_const= 0; + THD *thd= current_thd; + String *res; null_value=0; - if (!(res=args[0]->val_str(str))) + if (!(res= args[0]->val_str(str))) goto null; - use_as_buff= &tmp_value; - /* Item_subselect in --ps-protocol mode will state it as a non-const */ - is_const= args[0]->const_item() || !args[0]->used_tables(); - for (i=1 ; i < arg_count ; i++) - { - if (res->length() == 0) - { - if (!(res=args[i]->val_str(str))) - goto null; - /* - CONCAT accumulates its result in the result of its the first - non-empty argument. Because of this we need is_const to be - evaluated only for it. - */ - is_const= args[i]->const_item() || !args[i]->used_tables(); - } - else - { - if (!(res2=args[i]->val_str(use_as_buff))) - goto null; - if (res2->length() == 0) - continue; - if (res->length()+res2->length() > - current_thd->variables.max_allowed_packet) - { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(), - current_thd->variables.max_allowed_packet); - goto null; - } - if (!is_const && res->alloced_length() >= res->length()+res2->length()) - { // Use old buffer - res->append(*res2); - } - else if (str->alloced_length() >= res->length()+res2->length()) - { - if (str->ptr() == res2->ptr()) - str->replace(0,0,*res); - else - { - str->copy(*res); - str->append(*res2); - } - res= str; - use_as_buff= &tmp_value; - } - else if (res == &tmp_value) - { - if (res->append(*res2)) // Must be a blob - goto null; - } - else if (res2 == &tmp_value) - { // This can happend only 1 time - if (tmp_value.replace(0,0,*res)) - goto null; - res= &tmp_value; - use_as_buff=str; // Put next arg here - } - else if (tmp_value.is_alloced() && res2->ptr() >= tmp_value.ptr() && - res2->ptr() <= tmp_value.ptr() + tmp_value.alloced_length()) - { - /* - This happens really seldom: - In this case res2 is sub string of tmp_value. We will - now work in place in tmp_value to set it to res | res2 - */ - /* Chop the last characters in tmp_value that isn't in res2 */ - tmp_value.length((uint32) (res2->ptr() - tmp_value.ptr()) + - res2->length()); - /* Place res2 at start of tmp_value, remove chars before res2 */ - if (tmp_value.replace(0,(uint32) (res2->ptr() - tmp_value.ptr()), - *res)) - goto null; - res= &tmp_value; - use_as_buff=str; // Put next arg here - } - else - { // Two big const strings - /* - NOTE: We should be prudent in the initial allocation unit -- the - size of the arguments is a function of data distribution, which - can be any. Instead of overcommitting at the first row, we grow - the allocated amount by the factor of 2. This ensures that no - more than 25% of memory will be overcommitted on average. - */ - uint concat_len= res->length() + res2->length(); + if (res != str) + str->copy(res->ptr(), res->length(), res->charset()); - if (tmp_value.alloced_length() < concat_len) - { - if (tmp_value.alloced_length() == 0) - { - if (tmp_value.alloc(concat_len)) - goto null; - } - else - { - uint new_len = max(tmp_value.alloced_length() * 2, concat_len); - - if (tmp_value.realloc(new_len)) - goto null; - } - } - - if (tmp_value.copy(*res) || tmp_value.append(*res2)) - goto null; - - res= &tmp_value; - use_as_buff=str; - } - is_const= 0; + for (uint i= 1 ; i < arg_count ; i++) + { + uint concat_len; + if (!(res= args[i]->val_str(&tmp_value))) + goto null; + if (res->length() == 0) + continue; + if ((concat_len= str->length() + res->length()) > + thd->variables.max_allowed_packet) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(), + thd->variables.max_allowed_packet); + goto null; } + DBUG_ASSERT(!res->uses_buffer_owned_by(str)); + DBUG_ASSERT(!str->uses_buffer_owned_by(res)); + if (realloc_result(str, concat_len) || str->append(*res)) + goto null; } - res->set_charset(collation.collation); - return res; + + str->set_charset(collation.collation); + return str; null: null_value=1; @@ -636,7 +665,7 @@ String *Item_func_des_encrypt::val_str(String *str) struct st_des_keyschedule keyschedule; const char *append_str="********"; uint key_number, res_length, tail; - String *res= args[0]->val_str(str); + String *res= args[0]->val_str(&tmp_value); if ((null_value= args[0]->null_value)) return 0; // ENCRYPT(NULL) == NULL @@ -660,16 +689,17 @@ String *Item_func_des_encrypt::val_str(String *str) } else { - String *keystr=args[1]->val_str(&tmp_value); + String *keystr= args[1]->val_str(str); if (!keystr) goto error; key_number=127; // User key string /* We make good 24-byte (168 bit) key from given plaintext key with MD5 */ bzero((char*) &ivec,sizeof(ivec)); - EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL, + if (!EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL, (uchar*) keystr->ptr(), (int) keystr->length(), - 1, (uchar*) &keyblock,ivec); + 1, (uchar*) &keyblock,ivec)) + goto error; DES_set_key_unchecked(&keyblock.key1,&keyschedule.ks1); DES_set_key_unchecked(&keyblock.key2,&keyschedule.ks2); DES_set_key_unchecked(&keyblock.key3,&keyschedule.ks3); @@ -691,31 +721,33 @@ String *Item_func_des_encrypt::val_str(String *str) tmp_arg.length(0); tmp_arg.append(res->ptr(), res->length()); code= ER_OUT_OF_RESOURCES; - if (tmp_arg.append(append_str, tail) || tmp_value.alloc(res_length+1)) + if (tmp_arg.append(append_str, tail) || str->alloc(res_length+1)) goto error; tmp_arg[res_length-1]=tail; // save extra length - tmp_value.realloc(res_length+1); - tmp_value.length(res_length+1); - tmp_value.set_charset(&my_charset_bin); - tmp_value[0]=(char) (128 | key_number); + str->realloc(res_length+1); + str->length(res_length+1); + str->set_charset(&my_charset_bin); + (*str)[0]=(char) (128 | key_number); // Real encryption bzero((char*) &ivec,sizeof(ivec)); DES_ede3_cbc_encrypt((const uchar*) (tmp_arg.ptr()), - (uchar*) (tmp_value.ptr()+1), + (uchar*) (str->ptr()+1), res_length, &keyschedule.ks1, &keyschedule.ks2, &keyschedule.ks3, &ivec, TRUE); - return &tmp_value; + return str; error: - push_warning_printf(current_thd,MYSQL_ERROR::WARN_LEVEL_WARN, - code, ER(code), - "des_encrypt"); + THD *thd= current_thd; + push_warning_printf(thd,Sql_condition::WARN_LEVEL_WARN, + code, ER_THD(thd, code), + "des_encrypt"); #else - push_warning_printf(current_thd,MYSQL_ERROR::WARN_LEVEL_WARN, - ER_FEATURE_DISABLED, ER(ER_FEATURE_DISABLED), + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_FEATURE_DISABLED, ER_THD(thd, ER_FEATURE_DISABLED), "des_encrypt", "--with-ssl"); #endif /* defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) */ null_value=1; @@ -731,7 +763,7 @@ String *Item_func_des_decrypt::val_str(String *str) DES_cblock ivec; struct st_des_keyblock keyblock; struct st_des_keyschedule keyschedule; - String *res= args[0]->val_str(str); + String *res= args[0]->val_str(&tmp_value); uint length,tail; if ((null_value= args[0]->null_value)) @@ -755,47 +787,54 @@ String *Item_func_des_decrypt::val_str(String *str) else { // We make good 24-byte (168 bit) key from given plaintext key with MD5 - String *keystr=args[1]->val_str(&tmp_value); + String *keystr= args[1]->val_str(str); if (!keystr) goto error; bzero((char*) &ivec,sizeof(ivec)); - EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL, + if (!EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL, (uchar*) keystr->ptr(),(int) keystr->length(), - 1,(uchar*) &keyblock,ivec); + 1,(uchar*) &keyblock,ivec)) + goto error; // Here we set all 64-bit keys (56 effective) one by one DES_set_key_unchecked(&keyblock.key1,&keyschedule.ks1); DES_set_key_unchecked(&keyblock.key2,&keyschedule.ks2); DES_set_key_unchecked(&keyblock.key3,&keyschedule.ks3); } code= ER_OUT_OF_RESOURCES; - if (tmp_value.alloc(length-1)) + if (str->alloc(length-1)) goto error; bzero((char*) &ivec,sizeof(ivec)); DES_ede3_cbc_encrypt((const uchar*) res->ptr()+1, - (uchar*) (tmp_value.ptr()), + (uchar*) (str->ptr()), length-1, &keyschedule.ks1, &keyschedule.ks2, &keyschedule.ks3, &ivec, FALSE); /* Restore old length of key */ - if ((tail=(uint) (uchar) tmp_value[length-2]) > 8) + if ((tail=(uint) (uchar) (*str)[length-2]) > 8) goto wrong_key; // Wrong key - tmp_value.length(length-1-tail); - tmp_value.set_charset(&my_charset_bin); - return &tmp_value; + str->length(length-1-tail); + str->set_charset(&my_charset_bin); + return str; error: - push_warning_printf(current_thd,MYSQL_ERROR::WARN_LEVEL_WARN, - code, ER(code), - "des_decrypt"); + { + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + code, ER_THD(thd, code), + "des_decrypt"); + } wrong_key: #else - push_warning_printf(current_thd,MYSQL_ERROR::WARN_LEVEL_WARN, - ER_FEATURE_DISABLED, ER(ER_FEATURE_DISABLED), - "des_decrypt", "--with-ssl"); + { + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_FEATURE_DISABLED, ER_THD(thd, ER_FEATURE_DISABLED), + "des_decrypt", "--with-ssl"); + } #endif /* defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) */ null_value=1; return 0; @@ -815,6 +854,7 @@ String *Item_func_concat_ws::val_str(String *str) *sep_str, *res, *res2,*use_as_buff; uint i; bool is_const= 0; + THD *thd= 0; null_value=0; if (!(sep_str= args[0]->val_str(&tmp_sep_str))) @@ -829,7 +869,7 @@ String *Item_func_concat_ws::val_str(String *str) for (i=1; i < arg_count; i++) if ((res= args[i]->val_str(str))) { - is_const= args[i]->const_item() || !args[i]->used_tables(); + is_const= args[i]->const_item(); break; } @@ -841,13 +881,16 @@ String *Item_func_concat_ws::val_str(String *str) if (!(res2= args[i]->val_str(use_as_buff))) continue; // Skip NULL + if (!thd) + thd= current_thd; if (res->length() + sep_str->length() + res2->length() > - current_thd->variables.max_allowed_packet) + thd->variables.max_allowed_packet) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(), - current_thd->variables.max_allowed_packet); + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), + thd->variables.max_allowed_packet); goto null; } if (!is_const && res->alloced_length() >= @@ -926,7 +969,7 @@ String *Item_func_concat_ws::val_str(String *str) } else { - uint new_len = max(tmp_value.alloced_length() * 2, concat_len); + uint new_len = MY_MAX(tmp_value.alloced_length() * 2, concat_len); if (tmp_value.realloc(new_len)) goto null; @@ -973,25 +1016,26 @@ void Item_func_concat_ws::fix_length_and_dec() String *Item_func_reverse::val_str(String *str) { DBUG_ASSERT(fixed == 1); - String *res = args[0]->val_str(str); - char *ptr, *end, *tmp; + String *res= args[0]->val_str(&tmp_value); + const char *ptr, *end; + char *tmp; if ((null_value=args[0]->null_value)) return 0; /* An empty string is a special case as the string pointer may be null */ if (!res->length()) return make_empty_result(); - if (tmp_value.alloced_length() < res->length() && - tmp_value.realloc(res->length())) + if (str->alloced_length() < res->length() && + str->realloc(res->length())) { null_value= 1; return 0; } - tmp_value.length(res->length()); - tmp_value.set_charset(res->charset()); - ptr= (char *) res->ptr(); - end= ptr + res->length(); - tmp= (char *) tmp_value.ptr() + tmp_value.length(); + str->length(res->length()); + str->set_charset(res->charset()); + ptr= res->ptr(); + end= res->end(); + tmp= (char *) str->end(); #ifdef USE_MB if (use_mb(res->charset())) { @@ -1001,7 +1045,7 @@ String *Item_func_reverse::val_str(String *str) if ((l= my_ismbchar(res->charset(),ptr,end))) { tmp-= l; - DBUG_ASSERT(tmp >= tmp_value.ptr()); + DBUG_ASSERT(tmp >= str->ptr()); memcpy(tmp,ptr,l); ptr+= l; } @@ -1015,7 +1059,7 @@ String *Item_func_reverse::val_str(String *str) while (ptr < end) *--tmp= *ptr++; } - return &tmp_value; + return str; } @@ -1047,6 +1091,7 @@ String *Item_func_replace::val_str(String *str) register uint32 l; bool binary_cmp; #endif + THD *thd= 0; null_value=0; res=args[0]->val_str(str); @@ -1094,49 +1139,56 @@ redo: end= strend ? strend - from_length + 1 : NULL; while (ptr < end) { - if (*ptr == *search) + if (*ptr == *search) + { + register char *i,*j; + i=(char*) ptr+1; j=(char*) search+1; + while (j != search_end) + if (*i++ != *j++) goto skip; + offset= (int) (ptr-res->ptr()); + + if (!thd) + thd= current_thd; + + if (res->length()-from_length + to_length > + thd->variables.max_allowed_packet) { - register char *i,*j; - i=(char*) ptr+1; j=(char*) search+1; - while (j != search_end) - if (*i++ != *j++) goto skip; - offset= (int) (ptr-res->ptr()); - if (res->length()-from_length + to_length > - current_thd->variables.max_allowed_packet) - { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - func_name(), - current_thd->variables.max_allowed_packet); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), + thd->variables.max_allowed_packet); - goto null; - } - if (!alloced) - { - alloced=1; - res=copy_if_not_alloced(str,res,res->length()+to_length); - } - res->replace((uint) offset,from_length,*res3); - offset+=(int) to_length; - goto redo; + goto null; } -skip: - if ((l=my_ismbchar(res->charset(), ptr,strend))) ptr+=l; - else ++ptr; + if (!alloced) + { + alloced=1; + res=copy_if_not_alloced(str,res,res->length()+to_length); + } + res->replace((uint) offset,from_length,*res3); + offset+=(int) to_length; + goto redo; + } + skip: + if ((l=my_ismbchar(res->charset(), ptr,strend))) ptr+=l; + else ++ptr; } } else #endif /* USE_MB */ + { + thd= current_thd; do { if (res->length()-from_length + to_length > - current_thd->variables.max_allowed_packet) + thd->variables.max_allowed_packet) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(), - current_thd->variables.max_allowed_packet); + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), + thd->variables.max_allowed_packet); goto null; } if (!alloced) @@ -1148,6 +1200,7 @@ skip: offset+=(int) to_length; } while ((offset=res->strstr(*res2,(uint) offset)) >= 0); + } return res; null: @@ -1172,6 +1225,204 @@ void Item_func_replace::fix_length_and_dec() } +/*********************************************************************/ +bool Item_func_regexp_replace::fix_fields(THD *thd, Item **ref) +{ + re.set_recursion_limit(thd); + return Item_str_func::fix_fields(thd, ref); +} + + +void Item_func_regexp_replace::fix_length_and_dec() +{ + if (agg_arg_charsets_for_string_result_with_comparison(collation, args, 3)) + return; + max_length= MAX_BLOB_WIDTH; + re.init(collation.collation, 0); + re.fix_owner(this, args[0], args[1]); +} + + +/* + Traverse through the replacement string and append to "str". + Sub-pattern references \0 .. \9 are recognized, which are replaced + to the chunks of the source string. +*/ +bool Item_func_regexp_replace::append_replacement(String *str, + const LEX_CSTRING *source, + const LEX_CSTRING *replace) +{ + const char *beg= replace->str; + const char *end= beg + replace->length; + CHARSET_INFO *cs= re.library_charset(); + + for ( ; ; ) + { + my_wc_t wc; + int cnv, n; + + if ((cnv= cs->cset->mb_wc(cs, &wc, (const uchar *) beg, + (const uchar *) end)) < 1) + break; /* End of line */ + beg+= cnv; + + if (wc != '\\') + { + if (str->append(beg - cnv, cnv, cs)) + return true; + continue; + } + + if ((cnv= cs->cset->mb_wc(cs, &wc, (const uchar *) beg, + (const uchar *) end)) < 1) + break; /* End of line */ + beg+= cnv; + + if ((n= ((int) wc) - '0') >= 0 && n <= 9) + { + if (n < re.nsubpatterns()) + { + /* A valid sub-pattern reference found */ + int pbeg= re.subpattern_start(n), plength= re.subpattern_end(n) - pbeg; + if (str->append(source->str + pbeg, plength, cs)) + return true; + } + } + else + { + /* + A non-digit character following after '\'. + Just add the character itself. + */ + if (str->append(beg - cnv, cnv, cs)) + return false; + } + } + return false; +} + + +String *Item_func_regexp_replace::val_str(String *str) +{ + DBUG_ASSERT(fixed == 1); + char buff0[MAX_FIELD_WIDTH]; + char buff2[MAX_FIELD_WIDTH]; + String tmp0(buff0,sizeof(buff0),&my_charset_bin); + String tmp2(buff2,sizeof(buff2),&my_charset_bin); + String *source= args[0]->val_str(&tmp0); + String *replace= args[2]->val_str(&tmp2); + LEX_CSTRING src, rpl; + int startoffset= 0; + + if ((null_value= (args[0]->null_value || args[2]->null_value || + re.recompile(args[1])))) + return (String *) 0; + + if (!(source= re.convert_if_needed(source, &re.subject_converter)) || + !(replace= re.convert_if_needed(replace, &re.replace_converter))) + goto err; + + src= source->lex_cstring(); + rpl= replace->lex_cstring(); + + str->length(0); + str->set_charset(collation.collation); + + for ( ; ; ) // Iterate through all matches + { + + if (re.exec(src.str, src.length, startoffset)) + goto err; + + if (!re.match() || re.subpattern_length(0) == 0) + { + /* + No match or an empty match. + Append the rest of the source string + starting from startoffset until the end of the source. + */ + if (str->append(src.str + startoffset, src.length - startoffset, re.library_charset())) + goto err; + return str; + } + + /* + Append prefix, the part before the matching pattern. + starting from startoffset until the next match + */ + if (str->append(src.str + startoffset, re.subpattern_start(0) - startoffset, re.library_charset())) + goto err; + + // Append replacement + if (append_replacement(str, &src, &rpl)) + goto err; + + // Set the new start point as the end of previous match + startoffset= re.subpattern_end(0); + } + return str; + +err: + null_value= true; + return (String *) 0; +} + + +bool Item_func_regexp_substr::fix_fields(THD *thd, Item **ref) +{ + re.set_recursion_limit(thd); + return Item_str_func::fix_fields(thd, ref); +} + + +void Item_func_regexp_substr::fix_length_and_dec() +{ + if (agg_arg_charsets_for_string_result_with_comparison(collation, args, 2)) + return; + fix_char_length(args[0]->max_char_length()); + re.init(collation.collation, 0); + re.fix_owner(this, args[0], args[1]); +} + + +String *Item_func_regexp_substr::val_str(String *str) +{ + DBUG_ASSERT(fixed == 1); + char buff0[MAX_FIELD_WIDTH]; + String tmp0(buff0,sizeof(buff0),&my_charset_bin); + String *source= args[0]->val_str(&tmp0); + + if ((null_value= (args[0]->null_value || re.recompile(args[1])))) + return (String *) 0; + + if (!(source= re.convert_if_needed(source, &re.subject_converter))) + goto err; + + str->length(0); + str->set_charset(collation.collation); + + if (re.exec(source->ptr(), source->length(), 0)) + goto err; + + if (!re.match()) + return str; + + if (str->append(source->ptr() + re.subpattern_start(0), + re.subpattern_end(0) - re.subpattern_start(0), + re.library_charset())) + goto err; + + return str; + +err: + null_value= true; + return (String *) 0; +} + + +/************************************************************************/ + + String *Item_func_insert::val_str(String *str) { DBUG_ASSERT(fixed == 1); @@ -1217,14 +1468,17 @@ String *Item_func_insert::val_str(String *str) if (length > res->length() - start) length= res->length() - start; - if ((ulonglong) (res->length() - length + res2->length()) > - (ulonglong) current_thd->variables.max_allowed_packet) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - func_name(), current_thd->variables.max_allowed_packet); - goto null; + THD *thd= current_thd; + if ((ulonglong) (res->length() - length + res2->length()) > + (ulonglong) thd->variables.max_allowed_packet) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), thd->variables.max_allowed_packet); + goto null; + } } res=copy_if_not_alloced(str,res,res->length()); res->replace((uint32) start,(uint32) length,*res2); @@ -1252,32 +1506,18 @@ String *Item_str_conv::val_str(String *str) { DBUG_ASSERT(fixed == 1); String *res; - if (!(res=args[0]->val_str(str))) - { - null_value=1; /* purecov: inspected */ - return 0; /* purecov: inspected */ - } - null_value=0; - if (multiply == 1) - { - uint len; - res= copy_if_not_alloced(&tmp_value, res, res->length()); - len= converter(collation.collation, (char*) res->ptr(), res->length(), - (char*) res->ptr(), res->length()); - DBUG_ASSERT(len <= res->length()); - res->length(len); - } - else - { - uint len= res->length() * multiply; - tmp_value.alloc(len); - tmp_value.set_charset(collation.collation); - len= converter(collation.collation, (char*) res->ptr(), res->length(), - (char*) tmp_value.ptr(), len); - tmp_value.length(len); - res= &tmp_value; - } - return res; + uint alloced_length, len; + + if ((null_value= (!(res= args[0]->val_str(&tmp_value)) || + str->alloc((alloced_length= res->length() * multiply))))) + return 0; + + len= converter(collation.collation, (char*) res->ptr(), res->length(), + (char*) str->ptr(), alloced_length); + DBUG_ASSERT(len <= alloced_length); + str->set_charset(collation.collation); + str->length(len); + return str; } @@ -1419,7 +1659,7 @@ String *Item_func_substr::val_str(String *str) length= res->charpos((int) length, (uint32) start); tmp_length= res->length() - start; - length= min(length, tmp_length); + length= MY_MIN(length, tmp_length); if (!start && (longlong) res->length() == length) return res; @@ -1442,7 +1682,7 @@ void Item_func_substr::fix_length_and_dec() else if (start < 0) max_length= ((uint)(-start) > max_length) ? 0 : (uint)(-start); else - max_length-= min((uint)(start - 1), max_length); + max_length-= MY_MIN((uint)(start - 1), max_length); } if (arg_count == 3 && args[2]->const_item()) { @@ -1469,7 +1709,7 @@ String *Item_func_substr_index::val_str(String *str) DBUG_ASSERT(fixed == 1); char buff[MAX_FIELD_WIDTH]; String tmp(buff,sizeof(buff),system_charset_info); - String *res= args[0]->val_str(str); + String *res= args[0]->val_str(&tmp_value); String *delimiter= args[1]->val_str(&tmp); int32 count= (int32) args[2]->val_int(); uint offset; @@ -1518,20 +1758,31 @@ String *Item_func_substr_index::val_str(String *str) if (pass == 0) /* count<0 */ { c+=n+1; - if (c<=0) return res; /* not found, return original string */ + if (c<=0) + { + str->copy(res->ptr(), res->length(), collation.collation); + return str; // not found, return the original string + } ptr=res->ptr(); } else { - if (c) return res; /* Not found, return original string */ + if (c) + { + str->copy(res->ptr(), res->length(), collation.collation); + return str; // not found, return the original string + } if (count>0) /* return left part */ { - tmp_value.set(*res,0,(ulong) (ptr-res->ptr())); + str->copy(res->ptr(), (uint32) (ptr-res->ptr()), collation.collation); + return str; } else /* return right part */ { - ptr+= delimiter_length; - tmp_value.set(*res,(ulong) (ptr-res->ptr()), (ulong) (strend-ptr)); + ptr+= delimiter_length; + str->copy(res->ptr() + (ptr-res->ptr()), (uint32) (strend - ptr), + collation.collation); + return str; } } } @@ -1543,13 +1794,16 @@ String *Item_func_substr_index::val_str(String *str) { // start counting from the beginning for (offset=0; ; offset+= delimiter_length) { - if ((int) (offset= res->strstr(*delimiter, offset)) < 0) - return res; // Didn't find, return org string - if (!--count) - { - tmp_value.set(*res,0,offset); - break; - } + if ((int) (offset= res->strstr(*delimiter, offset)) < 0) + { + str->copy(res->ptr(), res->length(), collation.collation); + return str; // not found, return the original string + } + if (!--count) + { + str->copy(res->ptr(), offset, collation.collation); + return str; + } } } else @@ -1564,30 +1818,32 @@ String *Item_func_substr_index::val_str(String *str) address space less than where the found substring is located in res */ - if ((int) (offset= res->strrstr(*delimiter, offset)) < 0) - return res; // Didn't find, return org string + if ((int) (offset= res->strrstr(*delimiter, offset)) < 0) + { + str->copy(res->ptr(), res->length(), collation.collation); + return str; // not found, return the original string + } /* At this point, we've searched for the substring the number of times as supplied by the index value */ - if (!++count) - { - offset+= delimiter_length; - tmp_value.set(*res,offset,res->length()- offset); - break; - } + if (!++count) + { + offset+= delimiter_length; + str->copy(res->ptr() + offset, res->length() - offset, + collation.collation); + return str; + } } if (count) - return res; // Didn't find, return org string + { + str->copy(res->ptr(), res->length(), collation.collation); + return str; // not found, return the original string + } } } - /* - We always mark tmp_value as const so that if val_str() is called again - on this object, we don't disrupt the contents of tmp_value when it was - derived from another String. - */ - tmp_value.mark_as_const(); - return (&tmp_value); + DBUG_ASSERT(0); + return NULL; } /* @@ -1604,8 +1860,7 @@ String *Item_func_ltrim::val_str(String *str) char buff[MAX_FIELD_WIDTH], *ptr, *end; String tmp(buff,sizeof(buff),system_charset_info); String *res, *remove_str; - uint remove_length; - LINT_INIT(remove_length); + uint UNINIT_VAR(remove_length); res= args[0]->val_str(str); if ((null_value=args[0]->null_value)) @@ -1650,8 +1905,7 @@ String *Item_func_rtrim::val_str(String *str) char buff[MAX_FIELD_WIDTH], *ptr, *end; String tmp(buff, sizeof(buff), system_charset_info); String *res, *remove_str; - uint remove_length; - LINT_INIT(remove_length); + uint UNINIT_VAR(remove_length); res= args[0]->val_str(str); if ((null_value=args[0]->null_value)) @@ -1731,8 +1985,7 @@ String *Item_func_trim::val_str(String *str) const char *r_ptr; String tmp(buff, sizeof(buff), system_charset_info); String *res, *remove_str; - uint remove_length; - LINT_INIT(remove_length); + uint UNINIT_VAR(remove_length); res= args[0]->val_str(str); if ((null_value=args[0]->null_value)) @@ -1827,53 +2080,62 @@ void Item_func_trim::print(String *str, enum_query_type query_type) /* Item_func_password */ +bool Item_func_password::fix_fields(THD *thd, Item **ref) +{ + if (deflt) + alg= (thd->variables.old_passwords ? OLD : NEW); + return Item_str_ascii_func::fix_fields(thd, ref); +} + String *Item_func_password::val_str_ascii(String *str) { DBUG_ASSERT(fixed == 1); String *res= args[0]->val_str(str); - if ((null_value=args[0]->null_value)) - return 0; - if (res->length() == 0) - return make_empty_result(); - my_make_scrambled_password(tmp_value, res->ptr(), res->length()); - str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH, &my_charset_latin1); + switch (alg){ + case NEW: + if (args[0]->null_value || res->length() == 0) + return make_empty_result(); + my_make_scrambled_password(tmp_value, res->ptr(), res->length()); + str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH, &my_charset_latin1); + break; + case OLD: + if ((null_value=args[0]->null_value)) + return 0; + if (res->length() == 0) + return make_empty_result(); + my_make_scrambled_password_323(tmp_value, res->ptr(), res->length()); + str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH_323, &my_charset_latin1); + break; + default: + DBUG_ASSERT(0); + } return str; } char *Item_func_password::alloc(THD *thd, const char *password, - size_t pass_len) + size_t pass_len, enum PW_Alg al) { - char *buff= (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1); - if (buff) - my_make_scrambled_password(buff, password, pass_len); - return buff; -} - -/* Item_func_old_password */ - -String *Item_func_old_password::val_str_ascii(String *str) -{ - DBUG_ASSERT(fixed == 1); - String *res= args[0]->val_str(str); - if ((null_value=args[0]->null_value)) - return 0; - if (res->length() == 0) - return make_empty_result(); - my_make_scrambled_password_323(tmp_value, res->ptr(), res->length()); - str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH_323, &my_charset_latin1); - return str; -} + char *buff= (char *) thd->alloc((al==NEW)? + SCRAMBLED_PASSWORD_CHAR_LENGTH + 1: + SCRAMBLED_PASSWORD_CHAR_LENGTH_323 + 1); + if (!buff) + return NULL; -char *Item_func_old_password::alloc(THD *thd, const char *password, - size_t pass_len) -{ - char *buff= (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1); - if (buff) + switch (al) { + case NEW: + my_make_scrambled_password(buff, password, pass_len); + break; + case OLD: my_make_scrambled_password_323(buff, password, pass_len); + break; + default: + DBUG_ASSERT(0); + } return buff; } + #define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.') String *Item_func_encrypt::val_str(String *str) @@ -1982,32 +2244,6 @@ void Item_func_decode::crypto_transform(String *res) } -Item *Item_func_sysconst::safe_charset_converter(CHARSET_INFO *tocs) -{ - Item_string *conv; - uint conv_errors; - String tmp, cstr, *ostr= val_str(&tmp); - if (null_value) - { - Item *null_item= new Item_null((char *) fully_qualified_func_name()); - null_item->collation.set (tocs); - return null_item; - } - cstr.copy(ostr->ptr(), ostr->length(), ostr->charset(), tocs, &conv_errors); - if (conv_errors || - !(conv= new Item_static_string_func(fully_qualified_func_name(), - cstr.ptr(), cstr.length(), - cstr.charset(), - collation.derivation))) - { - return NULL; - } - conv->str_value.copy(); - conv->str_value.mark_as_const(); - return conv; -} - - String *Item_func_database::val_str(String *str) { DBUG_ASSERT(fixed == 1); @@ -2019,6 +2255,7 @@ String *Item_func_database::val_str(String *str) } else str->copy(thd->db, thd->db_length, system_charset_info); + null_value= 0; return str; } @@ -2053,6 +2290,28 @@ bool Item_func_user::init(const char *user, const char *host) } +Item *Item_func_sysconst::safe_charset_converter(THD *thd, CHARSET_INFO *tocs) +{ + /* + During view or prepared statement creation, the item should not + make use of const_charset_converter as it would imply substitution + with constant items which is not correct. Functions can have different + values during view creation and view execution based on context. + + Return the identical item during view creation and prepare. + */ + if (thd->lex->is_ps_or_view_context_analysis()) + return this; + return const_charset_converter(thd, tocs, true, fully_qualified_func_name()); +} + +bool Item_func_sysconst::const_item() const +{ + if (current_thd->lex->is_ps_or_view_context_analysis()) + return false; + return true; +} + bool Item_func_user::fix_fields(THD *thd, Item **ref) { return (Item_func_sysconst::fix_fields(thd, ref) || @@ -2066,16 +2325,30 @@ bool Item_func_current_user::fix_fields(THD *thd, Item **ref) if (Item_func_sysconst::fix_fields(thd, ref)) return TRUE; - Security_context *ctx= -#ifndef NO_EMBEDDED_ACCESS_CHECKS - (context->security_ctx - ? context->security_ctx : thd->security_ctx); -#else - thd->security_ctx; -#endif /*NO_EMBEDDED_ACCESS_CHECKS*/ + Security_context *ctx= context->security_ctx + ? context->security_ctx : thd->security_ctx; return init(ctx->priv_user, ctx->priv_host); } +bool Item_func_current_role::fix_fields(THD *thd, Item **ref) +{ + if (Item_func_sysconst::fix_fields(thd, ref)) + return 1; + + Security_context *ctx= context->security_ctx + ? context->security_ctx : thd->security_ctx; + if (ctx->priv_role[0]) + { + if (str_value.copy(ctx->priv_role, strlen(ctx->priv_role), + system_charset_info)) + return 1; + str_value.mark_as_const(); + null_value= maybe_null= 0; + return 0; + } + null_value= maybe_null= 1; + return 0; +} void Item_func_soundex::fix_length_and_dec() { @@ -2084,7 +2357,6 @@ void Item_func_soundex::fix_length_and_dec() DBUG_ASSERT(collation.collation != NULL); set_if_bigger(char_length, 4); fix_char_length(char_length); - tmp_value.set_charset(collation.collation); } @@ -2129,7 +2401,7 @@ static bool my_uni_isalpha(int wc) String *Item_func_soundex::val_str(String *str) { DBUG_ASSERT(fixed == 1); - String *res =args[0]->val_str(str); + String *res= args[0]->val_str(&tmp_value); char last_ch,ch; CHARSET_INFO *cs= collation.collation; my_wc_t wc; @@ -2139,10 +2411,11 @@ String *Item_func_soundex::val_str(String *str) if ((null_value= args[0]->null_value)) return 0; /* purecov: inspected */ - if (tmp_value.alloc(max(res->length(), 4 * cs->mbminlen))) - return str; /* purecov: inspected */ - char *to= (char *) tmp_value.ptr(); - char *to_end= to + tmp_value.alloced_length(); + if (str->alloc(MY_MAX(res->length(), 4 * cs->mbminlen))) + return &tmp_value; /* purecov: inspected */ + str->set_charset(collation.collation); + char *to= (char *) str->ptr(); + char *to_end= to + str->alloced_length(); char *from= (char *) res->ptr(), *end= from + res->length(); for ( ; ; ) /* Skip pre-space */ @@ -2227,8 +2500,8 @@ String *Item_func_soundex::val_str(String *str) to+= nbytes; } - tmp_value.length((uint) (to-tmp_value.ptr())); - return &tmp_value; + str->length((uint) (to - str->ptr())); + return str; } @@ -2249,9 +2522,10 @@ MY_LOCALE *Item_func_format::get_locale(Item *item) if (!locale_name || !(lc= my_locale_by_name(locale_name->c_ptr_safe()))) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_LOCALE, - ER(ER_UNKNOWN_LOCALE), + ER_THD(thd, ER_UNKNOWN_LOCALE), locale_name ? locale_name->c_ptr_safe() : "NULL"); lc= &my_locale_en_US; } @@ -2522,6 +2796,20 @@ String *Item_func_make_set::val_str(String *str) } +void Item_func_char::print(String *str, enum_query_type query_type) +{ + str->append(Item_func_char::func_name()); + str->append('('); + print_args(str, 0, query_type); + if (collation.collation != &my_charset_bin) + { + str->append(C_STRING_WITH_LEN(" using ")); + str->append(collation.collation->csname); + } + str->append(')'); +} + + String *Item_func_char::val_str(String *str) { DBUG_ASSERT(fixed == 1); @@ -2636,14 +2924,18 @@ String *Item_func_repeat::val_str(String *str) if (count == 1) // To avoid reallocs return res; length=res->length(); + // Safe length check - if (length > current_thd->variables.max_allowed_packet / (uint) count) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - func_name(), current_thd->variables.max_allowed_packet); - goto err; + THD *thd= current_thd; + if (length > thd->variables.max_allowed_packet / (uint) count) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), thd->variables.max_allowed_packet); + goto err; + } } tot_length= length*(uint) count; if (!(res= alloc_buffer(res,str,&tmp_value,tot_length))) @@ -2663,8 +2955,123 @@ err: } -void Item_func_rpad::fix_length_and_dec() +void Item_func_space::fix_length_and_dec() +{ + collation.set(default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII); + if (args[0]->const_item()) + { + /* must be longlong to avoid truncation */ + longlong count= args[0]->val_int(); + if (args[0]->null_value) + goto end; + /* + Assumes that the maximum length of a String is < INT_MAX32. + Set here so that rest of code sees out-of-bound value as such. + */ + if (count > INT_MAX32) + count= INT_MAX32; + fix_char_length_ulonglong(count); + return; + } + +end: + max_length= MAX_BLOB_WIDTH; + maybe_null= 1; +} + + +String *Item_func_space::val_str(String *str) +{ + uint tot_length; + longlong count= args[0]->val_int(); + CHARSET_INFO *cs= collation.collation; + + if (args[0]->null_value) + goto err; // string and/or delim are null + null_value= 0; + + if (count <= 0 && (count == 0 || !args[0]->unsigned_flag)) + return make_empty_result(); + /* + Assumes that the maximum length of a String is < INT_MAX32. + Bounds check on count: If this is triggered, we will error. + */ + if ((ulonglong) count > INT_MAX32) + count= INT_MAX32; + + // Safe length check + tot_length= (uint) count * cs->mbminlen; + { + THD *thd= current_thd; + if (tot_length > thd->variables.max_allowed_packet) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), + thd->variables.max_allowed_packet); + goto err; + } + } + if (str->alloc(tot_length)) + goto err; + str->length(tot_length); + str->set_charset(cs); + cs->cset->fill(cs, (char*) str->ptr(), tot_length, ' '); + return str; + +err: + null_value= 1; + return 0; +} + + +void Item_func_binlog_gtid_pos::fix_length_and_dec() +{ + collation.set(system_charset_info); + max_length= MAX_BLOB_WIDTH; + maybe_null= 1; +} + + +String *Item_func_binlog_gtid_pos::val_str(String *str) { + DBUG_ASSERT(fixed == 1); +#ifndef HAVE_REPLICATION + null_value= 0; + str->copy("", 0, system_charset_info); + return str; +#else + String name_str, *name; + longlong pos; + + if (args[0]->null_value || args[1]->null_value) + goto err; + + name= args[0]->val_str(&name_str); + pos= args[1]->val_int(); + + if (pos < 0 || pos > UINT_MAX32) + goto err; + + if (gtid_state_from_binlog_pos(name->c_ptr_safe(), (uint32)pos, str)) + goto err; + null_value= 0; + return str; + +err: + null_value= 1; + return NULL; +#endif /* !HAVE_REPLICATION */ +} + + +void Item_func_pad::fix_length_and_dec() +{ + String *str; + if (!args[2]->basic_const_item() || !(str= args[2]->val_str(&pad_str)) || !str->length()) + maybe_null= true; + // Handle character set for args[0] and args[2]. if (agg_arg_charsets_for_string_result(collation, &args[0], 2, 2)) return; @@ -2698,7 +3105,7 @@ String *Item_func_rpad::val_str(String *str) longlong count= args[1]->val_int(); longlong byte_count; String *res= args[0]->val_str(str); - String *rpad= args[2]->val_str(&rpad_str); + String *rpad= args[2]->val_str(&pad_str); if (!res || args[1]->null_value || !rpad || ((count < 0) && !args[1]->unsigned_flag)) @@ -2721,19 +3128,6 @@ String *Item_func_rpad::val_str(String *str) res->set_charset(&my_charset_bin); rpad->set_charset(&my_charset_bin); } -#if MARIADB_VERSION_ID < 1000000 - /* - Well-formedness is handled on a higher level in 10.0, - no needs to check it here again. - */ - else - { - // This will chop off any trailing illegal characters from rpad. - String *well_formed_pad= args[2]->check_well_formed_result(rpad, false); - if (!well_formed_pad) - goto err; - } -#endif if (count <= (res_char_length= res->numchars())) { // String to pad is big enough @@ -2743,13 +3137,16 @@ String *Item_func_rpad::val_str(String *str) pad_char_length= rpad->numchars(); byte_count= count * collation.collation->mbmaxlen; - if ((ulonglong) byte_count > current_thd->variables.max_allowed_packet) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - func_name(), current_thd->variables.max_allowed_packet); - goto err; + THD *thd= current_thd; + if ((ulonglong) byte_count > thd->variables.max_allowed_packet) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), thd->variables.max_allowed_packet); + goto err; + } } if (args[2]->null_value || !pad_char_length) goto err; @@ -2781,32 +3178,6 @@ String *Item_func_rpad::val_str(String *str) } -void Item_func_lpad::fix_length_and_dec() -{ - // Handle character set for args[0] and args[2]. - if (agg_arg_charsets_for_string_result(collation, &args[0], 2, 2)) - return; - - if (args[1]->const_item()) - { - ulonglong char_length= (ulonglong) args[1]->val_int(); - DBUG_ASSERT(collation.collation->mbmaxlen > 0); - /* Assumes that the maximum length of a String is < INT_MAX32. */ - /* Set here so that rest of code sees out-of-bound value as such. */ - if (args[1]->null_value) - char_length= 0; - else if (char_length > INT_MAX32) - char_length= INT_MAX32; - fix_char_length_ulonglong(char_length); - } - else - { - max_length= MAX_BLOB_WIDTH; - maybe_null= 1; - } -} - - String *Item_func_lpad::val_str(String *str) { DBUG_ASSERT(fixed == 1); @@ -2815,7 +3186,7 @@ String *Item_func_lpad::val_str(String *str) longlong count= args[1]->val_int(); longlong byte_count; String *res= args[0]->val_str(&tmp_value); - String *pad= args[2]->val_str(&lpad_str); + String *pad= args[2]->val_str(&pad_str); if (!res || args[1]->null_value || !pad || ((count < 0) && !args[1]->unsigned_flag)) @@ -2839,18 +3210,6 @@ String *Item_func_lpad::val_str(String *str) res->set_charset(&my_charset_bin); pad->set_charset(&my_charset_bin); } -#if MARIADB_VERSION_ID < 1000000 - /* - Well-formedness is handled on a higher level in 10.0, - no needs to check it here again. - */ else - { - // This will chop off any trailing illegal characters from pad. - String *well_formed_pad= args[2]->check_well_formed_result(pad, false); - if (!well_formed_pad) - goto err; - } -#endif res_char_length= res->numchars(); @@ -2863,13 +3222,16 @@ String *Item_func_lpad::val_str(String *str) pad_char_length= pad->numchars(); byte_count= count * collation.collation->mbmaxlen; - if ((ulonglong) byte_count > current_thd->variables.max_allowed_packet) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - func_name(), current_thd->variables.max_allowed_packet); - goto err; + THD *thd= current_thd; + if ((ulonglong) byte_count > thd->variables.max_allowed_packet) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), thd->variables.max_allowed_packet); + goto err; + } } if (args[2]->null_value || !pad_char_length || @@ -2953,21 +3315,18 @@ String *Item_func_conv_charset::val_str(String *str) DBUG_ASSERT(fixed == 1); if (use_cached_value) return null_value ? 0 : &str_value; - String *arg= args[0]->val_str(str); - uint dummy_errors; - if (args[0]->null_value) - { - null_value=1; - return 0; - } - null_value= tmp_value.copy(arg->ptr(), arg->length(), arg->charset(), - conv_charset, &dummy_errors); - return null_value ? 0 : check_well_formed_result(&tmp_value); + String *arg= args[0]->val_str(&tmp_value); + String_copier_for_item copier(current_thd); + return ((null_value= args[0]->null_value || + copier.copy_with_warn(collation.collation, str, + arg->charset(), arg->ptr(), + arg->length(), arg->length()))) ? + 0 : str; } void Item_func_conv_charset::fix_length_and_dec() { - collation.set(conv_charset, DERIVATION_IMPLICIT); + DBUG_ASSERT(collation.derivation == DERIVATION_IMPLICIT); fix_char_length(args[0]->max_char_length()); } @@ -2976,7 +3335,7 @@ void Item_func_conv_charset::print(String *str, enum_query_type query_type) str->append(STRING_WITH_LEN("convert(")); args[0]->print(str, query_type); str->append(STRING_WITH_LEN(" using ")); - str->append(conv_charset->csname); + str->append(collation.collation->csname); str->append(')'); } @@ -3001,11 +3360,8 @@ void Item_func_set_collation::fix_length_and_dec() MY_CS_BINSORT,MYF(0)); else { - if (!(set_collation= get_charset_by_name(colname,MYF(0)))) - { - my_error(ER_UNKNOWN_COLLATION, MYF(0), colname); + if (!(set_collation= mysqld_collation_get_by_name(colname))) return; - } } if (!set_collation || @@ -3029,7 +3385,7 @@ bool Item_func_set_collation::eq(const Item *item, bool binary_cmp) const if (item->type() != FUNC_ITEM) return 0; Item_func *item_func=(Item_func*) item; - if (arg_count != item_func->arg_count || + if (arg_count != item_func->argument_count() || functype() != item_func->functype()) return 0; Item_func_set_collation *item_func_sc=(Item_func_set_collation*) item; @@ -3049,7 +3405,7 @@ void Item_func_set_collation::print(String *str, enum_query_type query_type) str->append(STRING_WITH_LEN(" collate ")); DBUG_ASSERT(args[1]->basic_const_item() && args[1]->type() == Item::STRING_ITEM); - args[1]->str_value.print(str); + ((Item_string *)args[1])->print_value(str); str->append(')'); } @@ -3078,6 +3434,107 @@ String *Item_func_collation::val_str(String *str) } +void Item_func_weight_string::fix_length_and_dec() +{ + CHARSET_INFO *cs= args[0]->collation.collation; + collation.set(&my_charset_bin, args[0]->collation.derivation); + flags= my_strxfrm_flag_normalize(flags, cs->levels_for_order); + /* + Use result_length if it was given explicitly in constructor, + otherwise calculate max_length using argument's max_length + and "nweights". + */ + if (!(max_length= result_length)) + { + uint char_length; + char_length= ((cs->state & MY_CS_STRNXFRM_BAD_NWEIGHTS) || !nweights) ? + args[0]->max_char_length() : nweights * cs->levels_for_order; + max_length= cs->coll->strnxfrmlen(cs, char_length * cs->mbmaxlen); + } + maybe_null= 1; +} + + +/* Return a weight_string according to collation */ +String *Item_func_weight_string::val_str(String *str) +{ + String *res; + CHARSET_INFO *cs= args[0]->collation.collation; + uint tmp_length, frm_length; + DBUG_ASSERT(fixed == 1); + + if (args[0]->result_type() != STRING_RESULT || + !(res= args[0]->val_str(&tmp_value))) + goto nl; + + /* + Use result_length if it was given in constructor + explicitly, otherwise calculate result length + from argument and "nweights". + */ + if (!(tmp_length= result_length)) + { + uint char_length; + if (cs->state & MY_CS_STRNXFRM_BAD_NWEIGHTS) + { + /* + latin2_czech_cs and cp1250_czech_cs do not support + the "nweights" limit in strnxfrm(). Use the full length. + */ + char_length= res->length(); + } + else + { + /* + If we don't need to pad the result with spaces, then it should be + OK to calculate character length of the argument approximately: + "res->length() / cs->mbminlen" can return a number that is + bigger than the real number of characters in the string, so + we'll allocate a little bit more memory but avoid calling + the slow res->numchars(). + In case if we do need to pad with spaces, we call res->numchars() + to know the true number of characters. + */ + if (!(char_length= nweights)) + char_length= (flags & MY_STRXFRM_PAD_WITH_SPACE) ? + res->numchars() : (res->length() / cs->mbminlen); + } + tmp_length= cs->coll->strnxfrmlen(cs, char_length * cs->mbmaxlen); + } + + { + THD *thd= current_thd; + if (tmp_length > current_thd->variables.max_allowed_packet) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), + thd->variables.max_allowed_packet); + goto nl; + } + } + + if (str->alloc(tmp_length)) + goto nl; + + frm_length= cs->coll->strnxfrm(cs, + (uchar *) str->ptr(), tmp_length, + nweights ? nweights : tmp_length, + (const uchar *) res->ptr(), res->length(), + flags); + DBUG_ASSERT(frm_length <= tmp_length); + + str->length(frm_length); + null_value= 0; + return str; + +nl: + null_value= 1; + return 0; +} + + String *Item_func_hex::val_str_ascii(String *str) { String *res; @@ -3111,18 +3568,18 @@ String *Item_func_hex::val_str_ascii(String *str) } /* Convert given string to a hex string, character by character */ - res= args[0]->val_str(str); - if (!res || tmp_value.alloc(res->length()*2+1)) + res= args[0]->val_str(&tmp_value); + if (!res || str->alloc(res->length()*2+1)) { null_value=1; return 0; } null_value=0; - tmp_value.length(res->length()*2); - tmp_value.set_charset(&my_charset_latin1); + str->length(res->length()*2); + str->set_charset(&my_charset_latin1); - octet2hex((char*) tmp_value.ptr(), res->ptr(), res->length()); - return &tmp_value; + octet2hex((char*) str->ptr(), res->ptr(), res->length()); + return str; } /** Convert given hex string to a binary string. */ @@ -3135,8 +3592,8 @@ String *Item_func_unhex::val_str(String *str) uint length; DBUG_ASSERT(fixed == 1); - res= args[0]->val_str(str); - if (!res || tmp_value.alloc(length= (1+res->length())/2)) + res= args[0]->val_str(&tmp_value); + if (!res || str->alloc(length= (1+res->length())/2)) { null_value=1; return 0; @@ -3144,8 +3601,8 @@ String *Item_func_unhex::val_str(String *str) from= res->ptr(); null_value= 0; - tmp_value.length(length); - to= (char*) tmp_value.ptr(); + str->length(length); + to= (char*) str->ptr(); if (res->length() % 2) { int hex_char; @@ -3163,7 +3620,7 @@ String *Item_func_unhex::val_str(String *str) if ((null_value= (hex_char == -1))) return 0; } - return &tmp_value; + return str; } @@ -3243,13 +3700,17 @@ String *Item_load_file::val_str(String *str) /* my_error(ER_TEXTFILE_NOT_READABLE, MYF(0), file_name->c_ptr()); */ goto err; } - if (stat_info.st_size > (long) current_thd->variables.max_allowed_packet) + { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - func_name(), current_thd->variables.max_allowed_packet); - goto err; + THD *thd= current_thd; + if (stat_info.st_size > (long) thd->variables.max_allowed_packet) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + func_name(), thd->variables.max_allowed_packet); + goto err; + } } if (tmp_value.alloc((size_t)stat_info.st_size)) goto err; @@ -3328,17 +3789,18 @@ String* Item_func_export_set::val_str(String* str) } null_value= false; - const ulong max_allowed_packet= current_thd->variables.max_allowed_packet; + THD *thd= current_thd; + const ulong max_allowed_packet= thd->variables.max_allowed_packet; const uint num_separators= num_set_values > 0 ? num_set_values - 1 : 0; const ulonglong max_total_length= - num_set_values * max(yes->length(), no->length()) + + num_set_values * MY_MAX(yes->length(), no->length()) + num_separators * sep->length(); if (unlikely(max_total_length > max_allowed_packet)) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(), max_allowed_packet); null_value= true; return NULL; @@ -3360,57 +3822,15 @@ String* Item_func_export_set::val_str(String* str) void Item_func_export_set::fix_length_and_dec() { - uint32 length= max(args[1]->max_char_length(), args[2]->max_char_length()); + uint32 length= MY_MAX(args[1]->max_char_length(), args[2]->max_char_length()); uint32 sep_length= (arg_count > 3 ? args[3]->max_char_length() : 1); if (agg_arg_charsets_for_string_result(collation, - args + 1, min(4, arg_count) - 1)) + args + 1, MY_MIN(4, arg_count) - 1)) return; fix_char_length(length * 64 + sep_length * 63); } -String* Item_func_inet_ntoa::val_str(String* str) -{ - DBUG_ASSERT(fixed == 1); - uchar buf[8], *p; - ulonglong n = (ulonglong) args[0]->val_int(); - char num[4]; - - /* - We do not know if args[0] is NULL until we have called - some val function on it if args[0] is not a constant! - - Also return null if n > 255.255.255.255 - */ - if ((null_value= (args[0]->null_value || n > (ulonglong) LL(4294967295)))) - return 0; // Null value - - str->set_charset(collation.collation); - str->length(0); - int4store(buf,n); - - /* Now we can assume little endian. */ - - num[3]='.'; - for (p=buf+4 ; p-- > buf ; ) - { - uint c = *p; - uint n1,n2; // Try to avoid divisions - n1= c / 100; // 100 digits - c-= n1*100; - n2= c / 10; // 10 digits - c-=n2*10; // last digit - num[0]=(char) n1+'0'; - num[1]=(char) n2+'0'; - num[2]=(char) c+'0'; - uint length= (n1 ? 4 : n2 ? 3 : 2); // Remove pre-zero - uint dot_length= (p <= buf) ? 1 : 0; - (void) str->append(num + 4 - length, length - dot_length, - &my_charset_latin1); - } - return str; -} - #define get_esc_bit(mask, num) (1 & (*((mask) + ((num) >> 3))) >> ((num) & 7)) @@ -3451,7 +3871,7 @@ String *Item_func_quote::val_str(String *str) ulong max_allowed_packet= current_thd->variables.max_allowed_packet; char *from, *to, *end, *start; - String *arg= args[0]->val_str(str); + String *arg= args[0]->val_str(&tmp_value); uint arg_length, new_length; if (!arg) // Null argument { @@ -3478,7 +3898,7 @@ String *Item_func_quote::val_str(String *str) set_if_smaller(new_length, max_allowed_packet); } - if (tmp_value.alloc(new_length)) + if (str->alloc(new_length)) goto null; if (collation.collation->mbmaxlen > 1) @@ -3486,7 +3906,7 @@ String *Item_func_quote::val_str(String *str) CHARSET_INFO *cs= collation.collation; int mblen; uchar *to_end; - to= (char*) tmp_value.ptr(); + to= (char*) str->ptr(); to_end= (uchar*) to + new_length; /* Put leading quote */ @@ -3523,14 +3943,14 @@ String *Item_func_quote::val_str(String *str) if ((mblen= cs->cset->wc_mb(cs, '\'', (uchar *) to, to_end)) <= 0) goto toolong; to+= mblen; - new_length= to - tmp_value.ptr(); + new_length= to - str->ptr(); goto ret; } /* We replace characters from the end to the beginning */ - to= (char*) tmp_value.ptr() + new_length - 1; + to= (char*) str->ptr() + new_length - 1; *to--= '\''; for (start= (char*) arg->ptr(),end= start + arg_length; end-- != start; to--) { @@ -3560,13 +3980,13 @@ String *Item_func_quote::val_str(String *str) *to= '\''; ret: - tmp_value.length(new_length); - tmp_value.set_charset(collation.collation); + str->length(new_length); + str->set_charset(collation.collation); null_value= 0; - return &tmp_value; + return str; toolong: - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_ALLOWED_PACKET_OVERFLOWED, ER_THD(current_thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), func_name(), max_allowed_packet); @@ -3593,9 +4013,10 @@ longlong Item_func_uncompressed_length::val_int() */ if (res->length() <= 4) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_ZLIB_Z_DATA_ERROR, - ER(ER_ZLIB_Z_DATA_ERROR)); + ER_THD(thd, ER_ZLIB_Z_DATA_ERROR)); null_value= 1; return 0; } @@ -3636,7 +4057,7 @@ String *Item_func_compress::val_str(String *str) char *tmp, *last_char; DBUG_ASSERT(fixed == 1); - if (!(res= args[0]->val_str(str))) + if (!(res= args[0]->val_str(&tmp_value))) { null_value= 1; return 0; @@ -3657,25 +4078,27 @@ String *Item_func_compress::val_str(String *str) // Check new_size overflow: new_size <= res->length() if (((uint32) (new_size+5) <= res->length()) || - buffer.realloc((uint32) new_size + 4 + 1)) + str->realloc((uint32) new_size + 4 + 1)) { null_value= 1; return 0; } - body= ((Byte*)buffer.ptr()) + 4; + body= ((Byte*)str->ptr()) + 4; // As far as we have checked res->is_empty() we can use ptr() if ((err= my_compress_buffer(body, &new_size, (const uchar *)res->ptr(), res->length())) != Z_OK) { + THD *thd= current_thd; code= err==Z_MEM_ERROR ? ER_ZLIB_Z_MEM_ERROR : ER_ZLIB_Z_BUF_ERROR; - push_warning(current_thd,MYSQL_ERROR::WARN_LEVEL_WARN,code,ER(code)); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, code, + ER_THD(thd, code)); null_value= 1; return 0; } - tmp= (char*)buffer.ptr(); // int4store is a macro; avoid side effects + tmp= (char*) str->ptr(); // int4store is a macro; avoid side effects int4store(tmp, res->length() & 0x3FFFFFFF); /* This is to ensure that things works for CHAR fields, which trim ' ': */ @@ -3686,15 +4109,15 @@ String *Item_func_compress::val_str(String *str) new_size++; } - buffer.length((uint32)new_size + 4); - return &buffer; + str->length((uint32)new_size + 4); + return str; } String *Item_func_uncompress::val_str(String *str) { DBUG_ASSERT(fixed == 1); - String *res= args[0]->val_str(str); + String *res= args[0]->val_str(&tmp_value); ulong new_size; int err; uint code; @@ -3708,9 +4131,10 @@ String *Item_func_uncompress::val_str(String *str) /* If length is less than 4 bytes, data is corrupt */ if (res->length() <= 4) { - push_warning_printf(current_thd,MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_ZLIB_Z_DATA_ERROR, - ER(ER_ZLIB_Z_DATA_ERROR)); + ER_THD(thd, ER_ZLIB_Z_DATA_ERROR)); goto err; } @@ -3718,26 +4142,30 @@ String *Item_func_uncompress::val_str(String *str) new_size= uint4korr(res->ptr()) & 0x3FFFFFFF; if (new_size > current_thd->variables.max_allowed_packet) { - push_warning_printf(current_thd,MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd,Sql_condition::WARN_LEVEL_WARN, ER_TOO_BIG_FOR_UNCOMPRESS, - ER(ER_TOO_BIG_FOR_UNCOMPRESS), - static_cast<int>(current_thd->variables. + ER_THD(thd, ER_TOO_BIG_FOR_UNCOMPRESS), + static_cast<int>(thd->variables. max_allowed_packet)); goto err; } - if (buffer.realloc((uint32)new_size)) + if (str->realloc((uint32)new_size)) goto err; - if ((err= uncompress((Byte*)buffer.ptr(), &new_size, + if ((err= uncompress((Byte*)str->ptr(), &new_size, ((const Bytef*)res->ptr())+4,res->length()-4)) == Z_OK) { - buffer.length((uint32) new_size); - return &buffer; + str->length((uint32) new_size); + return str; } code= ((err == Z_BUF_ERROR) ? ER_ZLIB_Z_BUF_ERROR : ((err == Z_MEM_ERROR) ? ER_ZLIB_Z_MEM_ERROR : ER_ZLIB_Z_DATA_ERROR)); - push_warning(current_thd,MYSQL_ERROR::WARN_LEVEL_WARN,code,ER(code)); + { + THD *thd= current_thd; + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, code, ER_THD(thd, code)); + } err: null_value= 1; @@ -3761,9 +4189,10 @@ String *Item_func_uuid::val_str(String *str) } -Item_func_dyncol_create::Item_func_dyncol_create(List<Item> &args, - DYNCALL_CREATE_DEF *dfs) - : Item_str_func(args), defs(dfs), vals(0), nums(0) +Item_func_dyncol_create::Item_func_dyncol_create(THD *thd, List<Item> &args, + DYNCALL_CREATE_DEF *dfs): + Item_str_func(thd, args), defs(dfs), vals(0), keys_num(NULL), keys_str(NULL), + names(FALSE), force_names(FALSE) { DBUG_ASSERT((args.elements & 0x1) == 0); // even number of arguments } @@ -3771,31 +4200,85 @@ Item_func_dyncol_create::Item_func_dyncol_create(List<Item> &args, bool Item_func_dyncol_create::fix_fields(THD *thd, Item **ref) { + uint i; bool res= Item_func::fix_fields(thd, ref); // no need Item_str_func here - vals= (DYNAMIC_COLUMN_VALUE *) alloc_root(thd->mem_root, - sizeof(DYNAMIC_COLUMN_VALUE) * - (arg_count / 2)); - nums= (uint *) alloc_root(thd->mem_root, - sizeof(uint) * (arg_count / 2)); - status_var_increment(thd->status_var.feature_dynamic_columns); - return res || vals == 0 || nums == 0; + if (!res) + { + vals= (DYNAMIC_COLUMN_VALUE *) alloc_root(thd->mem_root, + sizeof(DYNAMIC_COLUMN_VALUE) * + (arg_count / 2)); + for (i= 0; + i + 1 < arg_count && args[i]->result_type() == INT_RESULT; + i+= 2) + ; + if (i + 1 < arg_count) + { + names= TRUE; + } + + keys_num= (uint *) alloc_root(thd->mem_root, + (sizeof(LEX_STRING) > sizeof(uint) ? + sizeof(LEX_STRING) : + sizeof(uint)) * + (arg_count / 2)); + keys_str= (LEX_STRING *) keys_num; + status_var_increment(thd->status_var.feature_dynamic_columns); + } + return res || vals == 0 || keys_num == 0; } void Item_func_dyncol_create::fix_length_and_dec() { + max_length= MAX_BLOB_WIDTH; maybe_null= TRUE; collation.set(&my_charset_bin); decimals= 0; } -void Item_func_dyncol_create::prepare_arguments() +bool Item_func_dyncol_create::prepare_arguments(bool force_names_arg) { char buff[STRING_BUFFER_USUAL_SIZE]; String *res, tmp(buff, sizeof(buff), &my_charset_bin); uint column_count= (arg_count / 2); uint i; my_decimal dtmp, *dres; + force_names= force_names_arg; + + if (!(names || force_names)) + { + for (i= 0; i < column_count; i++) + { + uint valpos= i * 2 + 1; + DYNAMIC_COLUMN_TYPE type= defs[i].type; + if (type == DYN_COL_NULL) + switch (args[valpos]->field_type()) + { + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_GEOMETRY: + type= DYN_COL_STRING; + break; + default: + break; + } + + if (type == DYN_COL_STRING && + args[valpos]->type() == Item::FUNC_ITEM && + ((Item_func *)args[valpos])->functype() == DYNCOL_FUNC) + { + force_names= 1; + break; + } + } + } /* get values */ for (i= 0; i < column_count; i++) @@ -3830,7 +4313,9 @@ void Item_func_dyncol_create::prepare_arguments() type= DYN_COL_NULL; break; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: type= DYN_COL_DATETIME; break; case MYSQL_TYPE_DATE: @@ -3838,6 +4323,7 @@ void Item_func_dyncol_create::prepare_arguments() type= DYN_COL_DATE; break; case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: type= DYN_COL_TIME; break; case MYSQL_TYPE_VARCHAR: @@ -3854,7 +4340,59 @@ void Item_func_dyncol_create::prepare_arguments() break; } } - nums[i]= (uint) args[i * 2]->val_int(); + if (type == DYN_COL_STRING && + args[valpos]->type() == Item::FUNC_ITEM && + ((Item_func *)args[valpos])->functype() == DYNCOL_FUNC) + { + DBUG_ASSERT(names || force_names); + type= DYN_COL_DYNCOL; + } + if (names || force_names) + { + res= args[i * 2]->val_str(&tmp); + if (res) + { + // guaranty UTF-8 string for names + if (my_charset_same(res->charset(), DYNCOL_UTF)) + { + keys_str[i].length= res->length(); + keys_str[i].str= sql_strmake(res->ptr(), res->length()); + } + else + { + uint strlen; + uint dummy_errors; + char *str= + (char *)sql_alloc((strlen= res->length() * + DYNCOL_UTF->mbmaxlen + 1)); + if (str) + { + keys_str[i].length= + copy_and_convert(str, strlen, DYNCOL_UTF, + res->ptr(), res->length(), res->charset(), + &dummy_errors); + keys_str[i].str= str; + } + else + keys_str[i].length= 0; + + } + } + else + { + keys_str[i].length= 0; + keys_str[i].str= NULL; + } + } + else + keys_num[i]= (uint) args[i * 2]->val_int(); + if (args[i * 2]->null_value) + { + /* to make cleanup possible */ + for (; i < column_count; i++) + vals[i].type= DYN_COL_NULL; + return 1; + } vals[i].type= type; switch (type) { case DYN_COL_NULL: @@ -3869,11 +4407,13 @@ void Item_func_dyncol_create::prepare_arguments() case DYN_COL_DOUBLE: vals[i].x.double_value= args[valpos]->val_real(); break; + case DYN_COL_DYNCOL: case DYN_COL_STRING: res= args[valpos]->val_str(&tmp); + if (res && defs[i].cs) + res->set_charset(defs[i].cs); if (res && - (vals[i].x.string.value.str= my_strndup(res->ptr(), res->length(), - MYF(MY_WME)))) + (vals[i].x.string.value.str= sql_strmake(res->ptr(), res->length()))) { vals[i].x.string.value.length= res->length(); vals[i].x.string.charset= res->charset(); @@ -3888,7 +4428,7 @@ void Item_func_dyncol_create::prepare_arguments() case DYN_COL_DECIMAL: if ((dres= args[valpos]->val_decimal(&dtmp))) { - dynamic_column_prepare_decimal(&vals[i]); + mariadb_dyncol_prepare_decimal(&vals[i]); DBUG_ASSERT(vals[i].x.decimal.value.len == dres->len); vals[i].x.decimal.value.intg= dres->intg; vals[i].x.decimal.value.frac= dres->frac; @@ -3898,15 +4438,14 @@ void Item_func_dyncol_create::prepare_arguments() } else { - dynamic_column_prepare_decimal(&vals[i]); // just to be safe + mariadb_dyncol_prepare_decimal(&vals[i]); // just to be safe DBUG_ASSERT(args[valpos]->null_value); } break; case DYN_COL_DATETIME: - args[valpos]->get_date(&vals[i].x.time_value, 0); - break; case DYN_COL_DATE: - args[valpos]->get_date(&vals[i].x.time_value, 0); + args[valpos]->get_date(&vals[i].x.time_value, + sql_mode_for_dates(current_thd)); break; case DYN_COL_TIME: args[valpos]->get_time(&vals[i].x.time_value); @@ -3917,24 +4456,12 @@ void Item_func_dyncol_create::prepare_arguments() } if (vals[i].type != DYN_COL_NULL && args[valpos]->null_value) { - if (vals[i].type == DYN_COL_STRING) - my_free(vals[i].x.string.value.str); vals[i].type= DYN_COL_NULL; } } + return FALSE; } -void Item_func_dyncol_create::cleanup_arguments() -{ - uint column_count= (arg_count / 2); - uint i; - - for (i= 0; i < column_count; i++) - { - if (vals[i].type == DYN_COL_STRING) - my_free(vals[i].x.string.value.str); - } -} String *Item_func_dyncol_create::val_str(String *str) { @@ -3944,30 +4471,36 @@ String *Item_func_dyncol_create::val_str(String *str) enum enum_dyncol_func_result rc; DBUG_ASSERT((arg_count & 0x1) == 0); // even number of arguments - prepare_arguments(); - - if ((rc= dynamic_column_create_many(&col, column_count, nums, vals))) + if (prepare_arguments(FALSE)) { - dynamic_column_error_message(rc); - dynamic_column_column_free(&col); res= NULL; - null_value= TRUE; + null_value= 1; } else { - /* Move result from DYNAMIC_COLUMN to str_value */ - char *ptr; - size_t length, alloc_length; - dynamic_column_reassociate(&col, &ptr, &length, &alloc_length); - str_value.reassociate(ptr, (uint32) length, (uint32) alloc_length, - &my_charset_bin); - res= &str_value; - null_value= FALSE; + if ((rc= ((names || force_names) ? + mariadb_dyncol_create_many_named(&col, column_count, keys_str, + vals, TRUE) : + mariadb_dyncol_create_many_num(&col, column_count, keys_num, + vals, TRUE)))) + { + dynamic_column_error_message(rc); + mariadb_dyncol_free(&col); + res= NULL; + null_value= TRUE; + } + else + { + /* Move result from DYNAMIC_COLUMN to str_value */ + char *ptr; + size_t length, alloc_length; + dynstr_reassociate(&col, &ptr, &length, &alloc_length); + str_value.reset(ptr, length, alloc_length, &my_charset_bin); + res= &str_value; + null_value= FALSE; + } } - /* cleanup */ - cleanup_arguments(); - return res; } @@ -3993,6 +4526,7 @@ void Item_func_dyncol_create::print_arguments(String *str, case DYN_COL_DOUBLE: str->append(STRING_WITH_LEN(" AS double")); break; + case DYN_COL_DYNCOL: case DYN_COL_STRING: str->append(STRING_WITH_LEN(" AS char")); if (defs[i].cs) @@ -4030,6 +4564,40 @@ void Item_func_dyncol_create::print(String *str, str->append(')'); } +String *Item_func_dyncol_json::val_str(String *str) +{ + DYNAMIC_STRING json, col; + String *res; + enum enum_dyncol_func_result rc; + + res= args[0]->val_str(str); + if (args[0]->null_value) + goto null; + + col.str= (char *)res->ptr(); + col.length= res->length(); + if ((rc= mariadb_dyncol_json(&col, &json))) + { + dynamic_column_error_message(rc); + goto null; + } + bzero(&col, sizeof(col)); + { + /* Move result from DYNAMIC_COLUMN to str */ + char *ptr; + size_t length, alloc_length; + dynstr_reassociate(&json, &ptr, &length, &alloc_length); + str->reset(ptr, length, alloc_length, DYNCOL_UTF); + null_value= FALSE; + } + str->set_charset(DYNCOL_UTF); + return str; + +null: + bzero(&col, sizeof(col)); + null_value= TRUE; + return NULL; +} String *Item_func_dyncol_add::val_str(String *str) { @@ -4041,21 +4609,25 @@ String *Item_func_dyncol_add::val_str(String *str) /* We store the packed data last */ res= args[arg_count - 1]->val_str(str); - if (args[arg_count - 1]->null_value) + if (args[arg_count - 1]->null_value || + init_dynamic_string(&col, NULL, res->length() + STRING_BUFFER_USUAL_SIZE, + STRING_BUFFER_USUAL_SIZE)) goto null; - init_dynamic_string(&col, NULL, res->length() + STRING_BUFFER_USUAL_SIZE, - STRING_BUFFER_USUAL_SIZE); col.length= res->length(); memcpy(col.str, res->ptr(), col.length); - prepare_arguments(); + if (prepare_arguments(mariadb_dyncol_has_names(&col))) + goto null; - if ((rc= dynamic_column_update_many(&col, column_count, nums, vals))) + if ((rc= ((names || force_names) ? + mariadb_dyncol_update_many_named(&col, column_count, + keys_str, vals) : + mariadb_dyncol_update_many_num(&col, column_count, + keys_num, vals)))) { dynamic_column_error_message(rc); - dynamic_column_column_free(&col); - cleanup_arguments(); + mariadb_dyncol_free(&col); goto null; } @@ -4063,16 +4635,11 @@ String *Item_func_dyncol_add::val_str(String *str) /* Move result from DYNAMIC_COLUMN to str */ char *ptr; size_t length, alloc_length; - dynamic_column_reassociate(&col, &ptr, &length, &alloc_length); - str->reassociate(ptr, (uint32) length, (uint32) alloc_length, - &my_charset_bin); + dynstr_reassociate(&col, &ptr, &length, &alloc_length); + str->reset(ptr, length, alloc_length, &my_charset_bin); null_value= FALSE; } - /* cleanup */ - dynamic_column_column_free(&col); - cleanup_arguments(); - return str; null: @@ -4104,10 +4671,48 @@ bool Item_dyncol_get::get_dyn_value(DYNAMIC_COLUMN_VALUE *val, String *tmp) { DYNAMIC_COLUMN dyn_str; String *res; - longlong num; + longlong num= 0; + LEX_STRING buf, *name= NULL; + char nmstrbuf[11]; + String nmbuf(nmstrbuf, sizeof(nmstrbuf), system_charset_info); enum enum_dyncol_func_result rc; - num= args[1]->val_int(); + if (args[1]->result_type() == INT_RESULT) + num= args[1]->val_int(); + else + { + String *nm= args[1]->val_str(&nmbuf); + if (!nm || args[1]->null_value) + { + null_value= 1; + return 1; + } + + if (my_charset_same(nm->charset(), DYNCOL_UTF)) + { + buf.str= (char *) nm->ptr(); + buf.length= nm->length(); + } + else + { + uint strlen; + uint dummy_errors; + buf.str= (char *)sql_alloc((strlen= nm->length() * + DYNCOL_UTF->mbmaxlen + 1)); + if (buf.str) + { + buf.length= + copy_and_convert(buf.str, strlen, DYNCOL_UTF, + nm->ptr(), nm->length(), nm->charset(), + &dummy_errors); + } + else + buf.length= 0; + } + name= &buf; + } + + if (args[1]->null_value || num < 0 || num > INT_MAX) { null_value= 1; @@ -4123,7 +4728,9 @@ bool Item_dyncol_get::get_dyn_value(DYNAMIC_COLUMN_VALUE *val, String *tmp) dyn_str.str= (char*) res->ptr(); dyn_str.length= res->length(); - if ((rc= dynamic_column_get(&dyn_str, (uint) num, val))) + if ((rc= ((name == NULL) ? + mariadb_dyncol_get_num(&dyn_str, (uint) num, val) : + mariadb_dyncol_get_named(&dyn_str, name, val)))) { dynamic_column_error_message(rc); null_value= 1; @@ -4149,12 +4756,13 @@ String *Item_dyncol_get::val_str(String *str_result) goto null; case DYN_COL_INT: case DYN_COL_UINT: - str_result->set_int(val.x.long_value, test(val.type == DYN_COL_UINT), + str_result->set_int(val.x.long_value, MY_TEST(val.type == DYN_COL_UINT), &my_charset_latin1); break; case DYN_COL_DOUBLE: str_result->set_real(val.x.double_value, NOT_FIXED_DEC, &my_charset_latin1); break; + case DYN_COL_DYNCOL: case DYN_COL_STRING: if ((char*) tmp.ptr() <= val.x.string.value.str && (char*) tmp.ptr() + tmp.length() >= val.x.string.value.str) @@ -4230,6 +4838,7 @@ longlong Item_dyncol_get::val_int() return 0; switch (val.type) { + case DYN_COL_DYNCOL: case DYN_COL_NULL: goto null; case DYN_COL_UINT: @@ -4246,11 +4855,12 @@ longlong Item_dyncol_get::val_int() num= double_to_longlong(val.x.double_value, unsigned_flag, &error); if (error) { + THD *thd= current_thd; char buff[30]; sprintf(buff, "%lg", val.x.double_value); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_DATA_OVERFLOW, - ER(ER_DATA_OVERFLOW), + ER_THD(thd, ER_DATA_OVERFLOW), buff, unsigned_flag ? "UNSIGNED INT" : "INT"); } @@ -4265,13 +4875,13 @@ longlong Item_dyncol_get::val_int() num= my_strtoll10(val.x.string.value.str, &end, &error); if (end != org_end || error > 0) { - char buff[80]; - strmake(buff, val.x.string.value.str, min(sizeof(buff)-1, - val.x.string.value.length)); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BAD_DATA, - ER(ER_BAD_DATA), - buff, + ER_THD(thd, ER_BAD_DATA), + ErrConvString(val.x.string.value.str, + val.x.string.value.length, + val.x.string.charset).ptr(), unsigned_flag ? "UNSIGNED INT" : "INT"); } unsigned_flag= error >= 0; @@ -4310,6 +4920,7 @@ double Item_dyncol_get::val_real() return 0.0; switch (val.type) { + case DYN_COL_DYNCOL: case DYN_COL_NULL: goto null; case DYN_COL_UINT: @@ -4328,13 +4939,14 @@ double Item_dyncol_get::val_real() if (end != (char*) val.x.string.value.str + val.x.string.value.length || error) { - char buff[80]; - strmake(buff, val.x.string.value.str, min(sizeof(buff)-1, - val.x.string.value.length)); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BAD_DATA, - ER(ER_BAD_DATA), - buff, "DOUBLE"); + ER_THD(thd, ER_BAD_DATA), + ErrConvString(val.x.string.value.str, + val.x.string.value.length, + val.x.string.charset).ptr(), + "DOUBLE"); } return res; } @@ -4367,6 +4979,7 @@ my_decimal *Item_dyncol_get::val_decimal(my_decimal *decimal_value) return NULL; switch (val.type) { + case DYN_COL_DYNCOL: case DYN_COL_NULL: goto null; case DYN_COL_UINT: @@ -4380,18 +4993,21 @@ my_decimal *Item_dyncol_get::val_decimal(my_decimal *decimal_value) break; case DYN_COL_STRING: { + const char *end; int rc; rc= str2my_decimal(0, val.x.string.value.str, val.x.string.value.length, - val.x.string.charset, decimal_value); - char buff[80]; - strmake(buff, val.x.string.value.str, min(sizeof(buff)-1, - val.x.string.value.length)); - if (rc != E_DEC_OK) + val.x.string.charset, decimal_value, &end); + if (rc != E_DEC_OK || + end != val.x.string.value.str + val.x.string.value.length) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BAD_DATA, - ER(ER_BAD_DATA), - buff, "DECIMAL"); + ER_THD(thd, ER_BAD_DATA), + ErrConvString(val.x.string.value.str, + val.x.string.value.length, + val.x.string.charset).ptr(), + "DECIMAL"); } break; } @@ -4401,10 +5017,7 @@ my_decimal *Item_dyncol_get::val_decimal(my_decimal *decimal_value) case DYN_COL_DATETIME: case DYN_COL_DATE: case DYN_COL_TIME: - decimal_value= seconds2my_decimal(val.x.time_value.neg, - TIME_to_ulonglong(&val.x.time_value), - val.x.time_value.second_part, - decimal_value); + decimal_value= TIME_to_my_decimal(&val.x.time_value, decimal_value); break; } return decimal_value; @@ -4426,6 +5039,7 @@ bool Item_dyncol_get::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) return 1; // Error switch (val.type) { + case DYN_COL_DYNCOL: case DYN_COL_NULL: goto null; case DYN_COL_INT: @@ -4458,7 +5072,7 @@ bool Item_dyncol_get::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) if (str_to_datetime_with_warn(&my_charset_numeric, val.x.string.value.str, val.x.string.value.length, - ltime, fuzzy_date) <= MYSQL_TIMESTAMP_ERROR) + ltime, fuzzy_date)) goto null; return 0; case DYN_COL_DATETIME: @@ -4473,7 +5087,6 @@ null: return 1; } - void Item_dyncol_get::print(String *str, enum_query_type query_type) { /* @@ -4503,7 +5116,8 @@ String *Item_func_dyncol_list::val_str(String *str) { uint i; enum enum_dyncol_func_result rc; - DYNAMIC_ARRAY arr; + LEX_STRING *names= 0; + uint count; DYNAMIC_COLUMN col; String *res= args[0]->val_str(str); @@ -4512,33 +5126,37 @@ String *Item_func_dyncol_list::val_str(String *str) col.length= res->length(); /* We do not change the string, so could do this trick */ col.str= (char *)res->ptr(); - if ((rc= dynamic_column_list(&col, &arr))) + if ((rc= mariadb_dyncol_list_named(&col, &count, &names))) { + bzero(&col, sizeof(col)); dynamic_column_error_message(rc); - delete_dynamic(&arr); goto null; } + bzero(&col, sizeof(col)); /* - We support elements from 0 - 65536, so max size for one element is - 6 (including ,). + We estimate average name length as 10 */ - if (str->alloc(arr.elements * 6)) + if (str->alloc(count * 13)) goto null; str->length(0); - for (i= 0; i < arr.elements; i++) + for (i= 0; i < count; i++) { - str->qs_append(*dynamic_element(&arr, i, uint*)); - if (i < arr.elements - 1) + append_identifier(current_thd, str, names[i].str, names[i].length); + if (i < count - 1) str->qs_append(','); } - null_value= FALSE; - delete_dynamic(&arr); + if (names) + my_free(names); + str->set_charset(DYNCOL_UTF); return str; null: null_value= TRUE; + if (names) + my_free(names); return NULL; } + diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index c1138c2a930..ace246bc271 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -3,7 +3,7 @@ /* Copyright (c) 2000, 2011, Oracle and/or its affiliates. - Copyright (c) 2009, 2013, Monty Program Ab. + Copyright (c) 2009, 2015, 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 @@ -49,19 +49,29 @@ protected: return &str_value; } public: - Item_str_func() :Item_func() { decimals=NOT_FIXED_DEC; } - Item_str_func(Item *a) :Item_func(a) {decimals=NOT_FIXED_DEC; } - Item_str_func(Item *a,Item *b) :Item_func(a,b) { decimals=NOT_FIXED_DEC; } - Item_str_func(Item *a,Item *b,Item *c) :Item_func(a,b,c) { decimals=NOT_FIXED_DEC; } - Item_str_func(Item *a,Item *b,Item *c,Item *d) :Item_func(a,b,c,d) {decimals=NOT_FIXED_DEC; } - Item_str_func(Item *a,Item *b,Item *c,Item *d, Item* e) :Item_func(a,b,c,d,e) {decimals=NOT_FIXED_DEC; } - Item_str_func(List<Item> &list) :Item_func(list) {decimals=NOT_FIXED_DEC; } + Item_str_func(THD *thd): Item_func(thd) { decimals=NOT_FIXED_DEC; } + Item_str_func(THD *thd, Item *a): Item_func(thd, a) {decimals=NOT_FIXED_DEC; } + Item_str_func(THD *thd, Item *a, Item *b): + Item_func(thd, a, b) { decimals=NOT_FIXED_DEC; } + Item_str_func(THD *thd, Item *a, Item *b, Item *c): + Item_func(thd, a, b, c) { decimals=NOT_FIXED_DEC; } + Item_str_func(THD *thd, Item *a, Item *b, Item *c, Item *d): + Item_func(thd, a, b, c, d) { decimals=NOT_FIXED_DEC; } + Item_str_func(THD *thd, Item *a, Item *b, Item *c, Item *d, Item* e): + Item_func(thd, a, b, c, d, e) { decimals=NOT_FIXED_DEC; } + Item_str_func(THD *thd, List<Item> &list): + Item_func(thd, list) { decimals=NOT_FIXED_DEC; } longlong val_int(); double val_real(); my_decimal *val_decimal(my_decimal *); enum Item_result result_type () const { return STRING_RESULT; } void left_right_max_length(); bool fix_fields(THD *thd, Item **ref); + void update_null_value() + { + StringBuffer<MAX_FIELD_WIDTH> tmp; + (void) val_str(&tmp); + } }; @@ -73,10 +83,11 @@ class Item_str_ascii_func :public Item_str_func { String ascii_buf; public: - Item_str_ascii_func() :Item_str_func() {} - Item_str_ascii_func(Item *a) :Item_str_func(a) {} - Item_str_ascii_func(Item *a,Item *b) :Item_str_func(a,b) {} - Item_str_ascii_func(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {} + Item_str_ascii_func(THD *thd): Item_str_func(thd) {} + Item_str_ascii_func(THD *thd, Item *a): Item_str_func(thd, a) {} + Item_str_ascii_func(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) {} + Item_str_ascii_func(THD *thd, Item *a, Item *b, Item *c): + Item_str_func(thd, a, b, c) {} String *val_str(String *str) { return val_str_from_val_str_ascii(str, &ascii_buf); @@ -85,49 +96,133 @@ public: }; -class Item_func_md5 :public Item_str_ascii_func +/** + Functions that return a checksum or a hash of the argument, + or somehow else encode or decode the argument, + returning an ASCII-repertoire string. +*/ +class Item_str_ascii_checksum_func: public Item_str_ascii_func +{ +public: + Item_str_ascii_checksum_func(THD *thd, Item *a) + :Item_str_ascii_func(thd, a) { } + Item_str_ascii_checksum_func(THD *thd, Item *a, Item *b) + :Item_str_ascii_func(thd, a, b) { } + bool eq(const Item *item, bool binary_cmp) const + { + // Always use binary argument comparison: MD5('x') != MD5('X') + return Item_func::eq(item, true); + } +}; + + +/** + Functions that return a checksum or a hash of the argument, + or somehow else encode or decode the argument, + returning a binary string. +*/ +class Item_str_binary_checksum_func: public Item_str_func { - String tmp_value; public: - Item_func_md5(Item *a) :Item_str_ascii_func(a) {} + Item_str_binary_checksum_func(THD *thd, Item *a) + :Item_str_func(thd, a) { } + Item_str_binary_checksum_func(THD *thd, Item *a, Item *b) + :Item_str_func(thd, a, b) { } + bool eq(const Item *item, bool binary_cmp) const + { + /* + Always use binary argument comparison: + FROM_BASE64('test') != FROM_BASE64('TEST') + */ + return Item_func::eq(item, true); + } +}; + + +class Item_func_md5 :public Item_str_ascii_checksum_func +{ +public: + Item_func_md5(THD *thd, Item *a): Item_str_ascii_checksum_func(thd, a) {} String *val_str_ascii(String *); - void fix_length_and_dec(); + void fix_length_and_dec() + { + fix_length_and_charset(32, default_charset()); + } const char *func_name() const { return "md5"; } }; -class Item_func_sha :public Item_str_ascii_func +class Item_func_sha :public Item_str_ascii_checksum_func { public: - Item_func_sha(Item *a) :Item_str_ascii_func(a) {} + Item_func_sha(THD *thd, Item *a): Item_str_ascii_checksum_func(thd, a) {} String *val_str_ascii(String *); void fix_length_and_dec(); const char *func_name() const { return "sha"; } }; -class Item_func_sha2 :public Item_str_ascii_func +class Item_func_sha2 :public Item_str_ascii_checksum_func { public: - Item_func_sha2(Item *a, Item *b) :Item_str_ascii_func(a, b) {} + Item_func_sha2(THD *thd, Item *a, Item *b) + :Item_str_ascii_checksum_func(thd, a, b) {} String *val_str_ascii(String *); void fix_length_and_dec(); const char *func_name() const { return "sha2"; } }; -class Item_func_aes_encrypt :public Item_str_func +class Item_func_to_base64 :public Item_str_ascii_checksum_func +{ + String tmp_value; +public: + Item_func_to_base64(THD *thd, Item *a) + :Item_str_ascii_checksum_func(thd, a) {} + String *val_str_ascii(String *); + void fix_length_and_dec(); + const char *func_name() const { return "to_base64"; } +}; + +class Item_func_from_base64 :public Item_str_binary_checksum_func { + String tmp_value; public: - Item_func_aes_encrypt(Item *a, Item *b) :Item_str_func(a,b) {} + Item_func_from_base64(THD *thd, Item *a) + :Item_str_binary_checksum_func(thd, a) { } String *val_str(String *); void fix_length_and_dec(); - const char *func_name() const { return "aes_encrypt"; } + const char *func_name() const { return "from_base64"; } }; -class Item_func_aes_decrypt :public Item_str_func +#include <my_crypt.h> + +class Item_aes_crypt :public Item_str_binary_checksum_func { + enum { AES_KEY_LENGTH = 128 }; + void create_key(String *user_key, uchar* key); + +protected: + int what; + String tmp_value; public: - Item_func_aes_decrypt(Item *a, Item *b) :Item_str_func(a,b) {} + Item_aes_crypt(THD *thd, Item *a, Item *b) + :Item_str_binary_checksum_func(thd, a, b) {} String *val_str(String *); +}; + +class Item_func_aes_encrypt :public Item_aes_crypt +{ +public: + Item_func_aes_encrypt(THD *thd, Item *a, Item *b) + :Item_aes_crypt(thd, a, b) {} + void fix_length_and_dec(); + const char *func_name() const { return "aes_encrypt"; } +}; + +class Item_func_aes_decrypt :public Item_aes_crypt +{ +public: + Item_func_aes_decrypt(THD *thd, Item *a, Item *b): + Item_aes_crypt(thd, a, b) {} void fix_length_and_dec(); const char *func_name() const { return "aes_decrypt"; } }; @@ -136,19 +231,35 @@ public: class Item_func_concat :public Item_str_func { String tmp_value; + bool realloc_result(String *str, uint length) const; public: - Item_func_concat(List<Item> &list) :Item_str_func(list) {} - Item_func_concat(Item *a,Item *b) :Item_str_func(a,b) {} + Item_func_concat(THD *thd, List<Item> &list): Item_str_func(thd, list) {} + Item_func_concat(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "concat"; } }; +class Item_func_decode_histogram :public Item_str_func +{ +public: + Item_func_decode_histogram(THD *thd, Item *a, Item *b): + Item_str_func(thd, a, b) {} + String *val_str(String *); + void fix_length_and_dec() + { + collation.set(system_charset_info); + max_length= MAX_BLOB_WIDTH; + maybe_null= 1; + } + const char *func_name() const { return "decode_histogram"; } +}; + class Item_func_concat_ws :public Item_str_func { String tmp_value; public: - Item_func_concat_ws(List<Item> &list) :Item_str_func(list) {} + Item_func_concat_ws(THD *thd, List<Item> &list): Item_str_func(thd, list) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "concat_ws"; } @@ -159,7 +270,7 @@ class Item_func_reverse :public Item_str_func { String tmp_value; public: - Item_func_reverse(Item *a) :Item_str_func(a) {} + Item_func_reverse(THD *thd, Item *a): Item_str_func(thd, a) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "reverse"; } @@ -170,20 +281,66 @@ class Item_func_replace :public Item_str_func { String tmp_value,tmp_value2; public: - Item_func_replace(Item *org,Item *find,Item *replace) - :Item_str_func(org,find,replace) {} + Item_func_replace(THD *thd, Item *org, Item *find, Item *replace): + Item_str_func(thd, org, find, replace) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "replace"; } }; +class Item_func_regexp_replace :public Item_str_func +{ + Regexp_processor_pcre re; + bool append_replacement(String *str, + const LEX_CSTRING *source, + const LEX_CSTRING *replace); +public: + Item_func_regexp_replace(THD *thd, Item *a, Item *b, Item *c): + Item_str_func(thd, a, b, c) + {} + void cleanup() + { + DBUG_ENTER("Item_func_regex::cleanup"); + Item_str_func::cleanup(); + re.cleanup(); + DBUG_VOID_RETURN; + } + String *val_str(String *str); + bool fix_fields(THD *thd, Item **ref); + void fix_length_and_dec(); + const char *func_name() const { return "regexp_replace"; } +}; + + +class Item_func_regexp_substr :public Item_str_func +{ + Regexp_processor_pcre re; +public: + Item_func_regexp_substr(THD *thd, Item *a, Item *b): + Item_str_func(thd, a, b) + {} + void cleanup() + { + DBUG_ENTER("Item_func_regex::cleanup"); + Item_str_func::cleanup(); + re.cleanup(); + DBUG_VOID_RETURN; + } + String *val_str(String *str); + bool fix_fields(THD *thd, Item **ref); + void fix_length_and_dec(); + const char *func_name() const { return "regexp_substr"; } +}; + + class Item_func_insert :public Item_str_func { String tmp_value; public: - Item_func_insert(Item *org,Item *start,Item *length,Item *new_str) - :Item_str_func(org,start,length,new_str) {} + Item_func_insert(THD *thd, Item *org, Item *start, Item *length, + Item *new_str): + Item_str_func(thd, org, start, length, new_str) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "insert"; } @@ -197,7 +354,7 @@ protected: my_charset_conv_case converter; String tmp_value; public: - Item_str_conv(Item *item) :Item_str_func(item) {} + Item_str_conv(THD *thd, Item *item): Item_str_func(thd, item) {} String *val_str(String *); }; @@ -205,7 +362,7 @@ public: class Item_func_lcase :public Item_str_conv { public: - Item_func_lcase(Item *item) :Item_str_conv(item) {} + Item_func_lcase(THD *thd, Item *item): Item_str_conv(thd, item) {} const char *func_name() const { return "lcase"; } void fix_length_and_dec(); }; @@ -213,7 +370,7 @@ public: class Item_func_ucase :public Item_str_conv { public: - Item_func_ucase(Item *item) :Item_str_conv(item) {} + Item_func_ucase(THD *thd, Item *item): Item_str_conv(thd, item) {} const char *func_name() const { return "ucase"; } void fix_length_and_dec(); }; @@ -223,7 +380,7 @@ class Item_func_left :public Item_str_func { String tmp_value; public: - Item_func_left(Item *a,Item *b) :Item_str_func(a,b) {} + Item_func_left(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "left"; } @@ -234,7 +391,7 @@ class Item_func_right :public Item_str_func { String tmp_value; public: - Item_func_right(Item *a,Item *b) :Item_str_func(a,b) {} + Item_func_right(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "right"; } @@ -245,8 +402,8 @@ class Item_func_substr :public Item_str_func { String tmp_value; public: - Item_func_substr(Item *a,Item *b) :Item_str_func(a,b) {} - Item_func_substr(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {} + Item_func_substr(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) {} + Item_func_substr(THD *thd, Item *a, Item *b, Item *c): Item_str_func(thd, a, b, c) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "substr"; } @@ -257,7 +414,8 @@ class Item_func_substr_index :public Item_str_func { String tmp_value; public: - Item_func_substr_index(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {} + Item_func_substr_index(THD *thd, Item *a,Item *b,Item *c): + Item_str_func(thd, a, b, c) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "substring_index"; } @@ -285,8 +443,8 @@ protected: return trimmed_value(res, 0, res->length()); } public: - Item_func_trim(Item *a,Item *b) :Item_str_func(a,b) {} - Item_func_trim(Item *a) :Item_str_func(a) {} + Item_func_trim(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) {} + Item_func_trim(THD *thd, Item *a): Item_str_func(thd, a) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "trim"; } @@ -298,8 +456,8 @@ public: class Item_func_ltrim :public Item_func_trim { public: - Item_func_ltrim(Item *a,Item *b) :Item_func_trim(a,b) {} - Item_func_ltrim(Item *a) :Item_func_trim(a) {} + Item_func_ltrim(THD *thd, Item *a, Item *b): Item_func_trim(thd, a, b) {} + Item_func_ltrim(THD *thd, Item *a): Item_func_trim(thd, a) {} String *val_str(String *); const char *func_name() const { return "ltrim"; } const char *mode_name() const { return "leading"; } @@ -309,8 +467,8 @@ public: class Item_func_rtrim :public Item_func_trim { public: - Item_func_rtrim(Item *a,Item *b) :Item_func_trim(a,b) {} - Item_func_rtrim(Item *a) :Item_func_trim(a) {} + Item_func_rtrim(THD *thd, Item *a, Item *b): Item_func_trim(thd, a, b) {} + Item_func_rtrim(THD *thd, Item *a): Item_func_trim(thd, a) {} String *val_str(String *); const char *func_name() const { return "rtrim"; } const char *mode_name() const { return "trailing"; } @@ -325,49 +483,44 @@ public: authentication procedure works, see comments in password.c. */ -class Item_func_password :public Item_str_ascii_func +class Item_func_password :public Item_str_ascii_checksum_func { +public: + enum PW_Alg {OLD, NEW}; +private: char tmp_value[SCRAMBLED_PASSWORD_CHAR_LENGTH+1]; + enum PW_Alg alg; + bool deflt; public: - Item_func_password(Item *a) :Item_str_ascii_func(a) {} + Item_func_password(THD *thd, Item *a): + Item_str_ascii_checksum_func(thd, a), alg(NEW), deflt(1) {} + Item_func_password(THD *thd, Item *a, PW_Alg al): + Item_str_ascii_checksum_func(thd, a), alg(al), deflt(0) {} String *val_str_ascii(String *str); + bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec() { - fix_length_and_charset(SCRAMBLED_PASSWORD_CHAR_LENGTH, default_charset()); + fix_length_and_charset((alg == 1 ? + SCRAMBLED_PASSWORD_CHAR_LENGTH : + SCRAMBLED_PASSWORD_CHAR_LENGTH_323), + default_charset()); } - const char *func_name() const { return "password"; } - static char *alloc(THD *thd, const char *password, size_t pass_len); + const char *func_name() const { return ((deflt || alg == 1) ? + "password" : "old_password"); } + static char *alloc(THD *thd, const char *password, size_t pass_len, + enum PW_Alg al); }; -/* - Item_func_old_password -- PASSWORD() implementation used in MySQL 3.21 - 4.0 - compatibility mode. This item is created in sql_yacc.yy when - 'old_passwords' session variable is set, and to handle OLD_PASSWORD() - function. -*/ -class Item_func_old_password :public Item_str_ascii_func -{ - char tmp_value[SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1]; -public: - Item_func_old_password(Item *a) :Item_str_ascii_func(a) {} - String *val_str_ascii(String *str); - void fix_length_and_dec() - { - fix_length_and_charset(SCRAMBLED_PASSWORD_CHAR_LENGTH_323, default_charset()); - } - const char *func_name() const { return "old_password"; } - static char *alloc(THD *thd, const char *password, size_t pass_len); -}; - - -class Item_func_des_encrypt :public Item_str_func +class Item_func_des_encrypt :public Item_str_binary_checksum_func { String tmp_value,tmp_arg; public: - Item_func_des_encrypt(Item *a) :Item_str_func(a) {} - Item_func_des_encrypt(Item *a, Item *b): Item_str_func(a,b) {} + Item_func_des_encrypt(THD *thd, Item *a) + :Item_str_binary_checksum_func(thd, a) {} + Item_func_des_encrypt(THD *thd, Item *a, Item *b) + :Item_str_binary_checksum_func(thd, a, b) {} String *val_str(String *); void fix_length_and_dec() { @@ -378,12 +531,14 @@ public: const char *func_name() const { return "des_encrypt"; } }; -class Item_func_des_decrypt :public Item_str_func +class Item_func_des_decrypt :public Item_str_binary_checksum_func { String tmp_value; public: - Item_func_des_decrypt(Item *a) :Item_str_func(a) {} - Item_func_des_decrypt(Item *a, Item *b): Item_str_func(a,b) {} + Item_func_des_decrypt(THD *thd, Item *a) + :Item_str_binary_checksum_func(thd, a) {} + Item_func_des_decrypt(THD *thd, Item *a, Item *b) + :Item_str_binary_checksum_func(thd, a, b) {} String *val_str(String *); void fix_length_and_dec() { @@ -396,7 +551,13 @@ public: const char *func_name() const { return "des_decrypt"; } }; -class Item_func_encrypt :public Item_str_func + +/** + QQ: Item_func_encrypt should derive from Item_str_ascii_checksum_func. + However, it should be fixed to handle UCS2, UTF16, UTF32 properly first, + as the underlying crypt() call expects a null-terminated input string. +*/ +class Item_func_encrypt :public Item_str_binary_checksum_func { String tmp_value; @@ -406,11 +567,12 @@ class Item_func_encrypt :public Item_str_func collation.set(&my_charset_bin); } public: - Item_func_encrypt(Item *a) :Item_str_func(a) + Item_func_encrypt(THD *thd, Item *a): Item_str_binary_checksum_func(thd, a) { constructor_helper(); } - Item_func_encrypt(Item *a, Item *b): Item_str_func(a,b) + Item_func_encrypt(THD *thd, Item *a, Item *b) + :Item_str_binary_checksum_func(thd, a, b) { constructor_helper(); } @@ -426,7 +588,7 @@ public: #include "sql_crypt.h" -class Item_func_encode :public Item_str_func +class Item_func_encode :public Item_str_binary_checksum_func { private: /** Whether the PRNG has already been seeded. */ @@ -434,8 +596,8 @@ private: protected: SQL_CRYPT sql_crypt; public: - Item_func_encode(Item *a, Item *seed): - Item_str_func(a, seed) {} + Item_func_encode(THD *thd, Item *a, Item *seed_arg): + Item_str_binary_checksum_func(thd, a, seed_arg) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "encode"; } @@ -450,7 +612,7 @@ private: class Item_func_decode :public Item_func_encode { public: - Item_func_decode(Item *a, Item *seed): Item_func_encode(a, seed) {} + Item_func_decode(THD *thd, Item *a, Item *seed_arg): Item_func_encode(thd, a, seed_arg) {} const char *func_name() const { return "decode"; } protected: void crypto_transform(String *); @@ -460,9 +622,9 @@ protected: class Item_func_sysconst :public Item_str_func { public: - Item_func_sysconst() + Item_func_sysconst(THD *thd): Item_str_func(thd) { collation.set(system_charset_info,DERIVATION_SYSCONST); } - Item *safe_charset_converter(CHARSET_INFO *tocs); + Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs); /* Used to create correct Item name in new converted item in safe_charset_converter, return string representation of this function @@ -474,13 +636,14 @@ public: return trace_unsupported_by_check_vcol_func_processor( fully_qualified_func_name()); } + bool const_item() const; }; class Item_func_database :public Item_func_sysconst { public: - Item_func_database() :Item_func_sysconst() {} + Item_func_database(THD *thd): Item_func_sysconst(thd) {} String *val_str(String *); void fix_length_and_dec() { @@ -498,7 +661,7 @@ protected: bool init (const char *user, const char *host); public: - Item_func_user() + Item_func_user(THD *thd): Item_func_sysconst(thd) { str_value.set("", 0, system_charset_info); } @@ -527,19 +690,41 @@ class Item_func_current_user :public Item_func_user Name_resolution_context *context; public: - Item_func_current_user(Name_resolution_context *context_arg) - : context(context_arg) {} + Item_func_current_user(THD *thd, Name_resolution_context *context_arg): + Item_func_user(thd), context(context_arg) {} bool fix_fields(THD *thd, Item **ref); const char *func_name() const { return "current_user"; } const char *fully_qualified_func_name() const { return "current_user()"; } }; +class Item_func_current_role :public Item_func_sysconst +{ + Name_resolution_context *context; + +public: + Item_func_current_role(THD *thd, Name_resolution_context *context_arg): + Item_func_sysconst(thd), context(context_arg) {} + bool fix_fields(THD *thd, Item **ref); + void fix_length_and_dec() + { max_length= username_char_length * SYSTEM_CHARSET_MBMAXLEN; } + int save_in_field(Field *field, bool no_conversions) + { return save_str_value_in_field(field, &str_value); } + const char *func_name() const { return "current_role"; } + const char *fully_qualified_func_name() const { return "current_role()"; } + String *val_str(String *) + { + DBUG_ASSERT(fixed == 1); + return null_value ? NULL : &str_value; + } +}; + + class Item_func_soundex :public Item_str_func { String tmp_value; public: - Item_func_soundex(Item *a) :Item_str_func(a) {} + Item_func_soundex(THD *thd, Item *a): Item_str_func(thd, a) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "soundex"; } @@ -549,7 +734,7 @@ public: class Item_func_elt :public Item_str_func { public: - Item_func_elt(List<Item> &list) :Item_str_func(list) {} + Item_func_elt(THD *thd, List<Item> &list): Item_str_func(thd, list) {} double val_real(); longlong val_int(); String *val_str(String *str); @@ -563,7 +748,7 @@ class Item_func_make_set :public Item_str_func String tmp_str; public: - Item_func_make_set(List<Item> &list) :Item_str_func(list) {} + Item_func_make_set(THD *thd, List<Item> &list): Item_str_func(thd, list) {} String *val_str(String *str); void fix_length_and_dec(); const char *func_name() const { return "make_set"; } @@ -572,13 +757,13 @@ public: class Item_func_format :public Item_str_ascii_func { - String tmp_str; MY_LOCALE *locale; public: - Item_func_format(Item *org, Item *dec): Item_str_ascii_func(org, dec) {} - Item_func_format(Item *org, Item *dec, Item *lang): - Item_str_ascii_func(org, dec, lang) {} - + Item_func_format(THD *thd, Item *org, Item *dec): + Item_str_ascii_func(thd, org, dec) {} + Item_func_format(THD *thd, Item *org, Item *dec, Item *lang): + Item_str_ascii_func(thd, org, dec, lang) {} + MY_LOCALE *get_locale(Item *item); String *val_str_ascii(String *); void fix_length_and_dec(); @@ -590,16 +775,18 @@ public: class Item_func_char :public Item_str_func { public: - Item_func_char(List<Item> &list) :Item_str_func(list) + Item_func_char(THD *thd, List<Item> &list): Item_str_func(thd, list) { collation.set(&my_charset_bin); } - Item_func_char(List<Item> &list, CHARSET_INFO *cs) :Item_str_func(list) - { collation.set(cs); } + Item_func_char(THD *thd, List<Item> &list, CHARSET_INFO *cs): + Item_str_func(thd, list) + { collation.set(cs); } String *val_str(String *); void fix_length_and_dec() { max_length= arg_count * 4; } const char *func_name() const { return "char"; } + void print(String *str, enum_query_type query_type); }; @@ -607,33 +794,62 @@ class Item_func_repeat :public Item_str_func { String tmp_value; public: - Item_func_repeat(Item *arg1,Item *arg2) :Item_str_func(arg1,arg2) {} + Item_func_repeat(THD *thd, Item *arg1, Item *arg2): + Item_str_func(thd, arg1, arg2) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "repeat"; } }; -class Item_func_rpad :public Item_str_func +class Item_func_space :public Item_str_func { - String tmp_value, rpad_str; public: - Item_func_rpad(Item *arg1,Item *arg2,Item *arg3) - :Item_str_func(arg1,arg2,arg3) {} + Item_func_space(THD *thd, Item *arg1): Item_str_func(thd, arg1) {} String *val_str(String *); void fix_length_and_dec(); - const char *func_name() const { return "rpad"; } + const char *func_name() const { return "space"; } }; -class Item_func_lpad :public Item_str_func +class Item_func_binlog_gtid_pos :public Item_str_func { - String tmp_value, lpad_str; public: - Item_func_lpad(Item *arg1,Item *arg2,Item *arg3) - :Item_str_func(arg1,arg2,arg3) {} + Item_func_binlog_gtid_pos(THD *thd, Item *arg1, Item *arg2): + Item_str_func(thd, arg1, arg2) {} String *val_str(String *); void fix_length_and_dec(); + const char *func_name() const { return "binlog_gtid_pos"; } +}; + + +class Item_func_pad: public Item_str_func +{ +protected: + String tmp_value, pad_str; +public: + Item_func_pad(THD *thd, Item *arg1, Item *arg2, Item *arg3): + Item_str_func(thd, arg1, arg2, arg3) {} + void fix_length_and_dec(); +}; + + +class Item_func_rpad :public Item_func_pad +{ +public: + Item_func_rpad(THD *thd, Item *arg1, Item *arg2, Item *arg3): + Item_func_pad(thd, arg1, arg2, arg3) {} + String *val_str(String *); + const char *func_name() const { return "rpad"; } +}; + + +class Item_func_lpad :public Item_func_pad +{ +public: + Item_func_lpad(THD *thd, Item *arg1, Item *arg2, Item *arg3): + Item_func_pad(thd, arg1, arg2, arg3) {} + String *val_str(String *); const char *func_name() const { return "lpad"; } }; @@ -641,7 +857,8 @@ public: class Item_func_conv :public Item_str_func { public: - Item_func_conv(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {} + Item_func_conv(THD *thd, Item *a, Item *b, Item *c): + Item_str_func(thd, a, b, c) {} const char *func_name() const { return "conv"; } String *val_str(String *); void fix_length_and_dec() @@ -653,11 +870,12 @@ public: }; -class Item_func_hex :public Item_str_ascii_func +class Item_func_hex :public Item_str_ascii_checksum_func { String tmp_value; public: - Item_func_hex(Item *a) :Item_str_ascii_func(a) {} + Item_func_hex(THD *thd, Item *a): + Item_str_ascii_checksum_func(thd, a) {} const char *func_name() const { return "hex"; } String *val_str_ascii(String *); void fix_length_and_dec() @@ -672,10 +890,10 @@ class Item_func_unhex :public Item_str_func { String tmp_value; public: - Item_func_unhex(Item *a) :Item_str_func(a) - { + Item_func_unhex(THD *thd, Item *a): Item_str_func(thd, a) + { /* there can be bad hex strings */ - maybe_null= 1; + maybe_null= 1; } const char *func_name() const { return "unhex"; } String *val_str(String *); @@ -696,8 +914,8 @@ protected: String max_str; const bool is_min; public: - Item_func_like_range(Item *a, Item *b, bool is_min_arg) - :Item_str_func(a, b), is_min(is_min_arg) + Item_func_like_range(THD *thd, Item *a, Item *b, bool is_min_arg): + Item_str_func(thd, a, b), is_min(is_min_arg) { maybe_null= 1; } String *val_str(String *); void fix_length_and_dec() @@ -712,8 +930,8 @@ public: class Item_func_like_range_min :public Item_func_like_range { public: - Item_func_like_range_min(Item *a, Item *b) - :Item_func_like_range(a, b, true) { } + Item_func_like_range_min(THD *thd, Item *a, Item *b): + Item_func_like_range(thd, a, b, true) { } const char *func_name() const { return "like_range_min"; } }; @@ -721,8 +939,8 @@ public: class Item_func_like_range_max :public Item_func_like_range { public: - Item_func_like_range_max(Item *a, Item *b) - :Item_func_like_range(a, b, false) { } + Item_func_like_range_max(THD *thd, Item *a, Item *b): + Item_func_like_range(thd, a, b, false) { } const char *func_name() const { return "like_range_max"; } }; #endif @@ -731,7 +949,7 @@ public: class Item_func_binary :public Item_str_func { public: - Item_func_binary(Item *a) :Item_str_func(a) {} + Item_func_binary(THD *thd, Item *a): Item_str_func(thd, a) {} String *val_str(String *a) { DBUG_ASSERT(fixed == 1); @@ -755,7 +973,7 @@ class Item_load_file :public Item_str_func { String tmp_value; public: - Item_load_file(Item *a) :Item_str_func(a) {} + Item_load_file(THD *thd, Item *a): Item_str_func(thd, a) {} String *val_str(String *); const char *func_name() const { return "load_file"; } void fix_length_and_dec() @@ -774,35 +992,23 @@ public: class Item_func_export_set: public Item_str_func { public: - Item_func_export_set(Item *a,Item *b,Item* c) :Item_str_func(a,b,c) {} - Item_func_export_set(Item *a,Item *b,Item* c,Item* d) :Item_str_func(a,b,c,d) {} - Item_func_export_set(Item *a,Item *b,Item* c,Item* d,Item* e) :Item_str_func(a,b,c,d,e) {} + Item_func_export_set(THD *thd, Item *a, Item *b, Item* c): + Item_str_func(thd, a, b, c) {} + Item_func_export_set(THD *thd, Item *a, Item *b, Item* c, Item* d): + Item_str_func(thd, a, b, c, d) {} + Item_func_export_set(THD *thd, Item *a, Item *b, Item* c, Item* d, Item* e): + Item_str_func(thd, a, b, c, d, e) {} String *val_str(String *str); void fix_length_and_dec(); const char *func_name() const { return "export_set"; } }; -class Item_func_inet_ntoa : public Item_str_func -{ -public: - Item_func_inet_ntoa(Item *a) :Item_str_func(a) - { - } - String* val_str(String* str); - const char *func_name() const { return "inet_ntoa"; } - void fix_length_and_dec() - { - decimals= 0; - fix_length_and_charset(3 * 8 + 7, default_charset()); - maybe_null= 1; - } -}; class Item_func_quote :public Item_str_func { String tmp_value; public: - Item_func_quote(Item *a) :Item_str_func(a) {} + Item_func_quote(THD *thd, Item *a): Item_str_func(thd, a) {} const char *func_name() const { return "quote"; } String *val_str(String *); void fix_length_and_dec() @@ -810,7 +1016,7 @@ public: collation.set(args[0]->collation); ulonglong max_result_length= (ulonglong) args[0]->max_length * 2 + 2 * collation.collation->mbmaxlen; - max_length= (uint32) min(max_result_length, MAX_BLOB_WIDTH); + max_length= (uint32) MY_MIN(max_result_length, MAX_BLOB_WIDTH); } }; @@ -820,19 +1026,22 @@ class Item_func_conv_charset :public Item_str_func String tmp_value; public: bool safe; - CHARSET_INFO *conv_charset; // keep it public - Item_func_conv_charset(Item *a, CHARSET_INFO *cs) :Item_str_func(a) - { conv_charset= cs; use_cached_value= 0; safe= 0; } - Item_func_conv_charset(Item *a, CHARSET_INFO *cs, bool cache_if_const) - :Item_str_func(a) + Item_func_conv_charset(THD *thd, Item *a, CHARSET_INFO *cs): + Item_str_func(thd, a) { - conv_charset= cs; + collation.set(cs, DERIVATION_IMPLICIT); + use_cached_value= 0; safe= 0; + } + Item_func_conv_charset(THD *thd, Item *a, CHARSET_INFO *cs, bool cache_if_const): + Item_str_func(thd, a) + { + collation.set(cs, DERIVATION_IMPLICIT); if (cache_if_const && args[0]->const_item() && !args[0]->is_expensive()) { uint errors= 0; String tmp, *str= args[0]->val_str(&tmp); if (!str || str_value.copy(str->ptr(), str->length(), - str->charset(), conv_charset, &errors)) + str->charset(), cs, &errors)) null_value= 1; use_cached_value= 1; str_value.mark_as_const(); @@ -896,26 +1105,26 @@ public: class Item_func_set_collation :public Item_str_func { public: - Item_func_set_collation(Item *a, Item *b) :Item_str_func(a,b) {}; + Item_func_set_collation(THD *thd, Item *a, Item *b): + Item_str_func(thd, a, b) {} String *val_str(String *); void fix_length_and_dec(); bool eq(const Item *item, bool binary_cmp) const; const char *func_name() const { return "collate"; } enum Functype functype() const { return COLLATE_FUNC; } virtual void print(String *str, enum_query_type query_type); - Item_field *filed_for_view_update() + Item_field *field_for_view_update() { /* this function is transparent for view updating */ - return args[0]->filed_for_view_update(); + return args[0]->field_for_view_update(); } }; -class Item_func_charset :public Item_str_func + +class Item_func_expr_str_metadata :public Item_str_func { public: - Item_func_charset(Item *a) :Item_str_func(a) {} - String *val_str(String *); - const char *func_name() const { return "charset"; } + Item_func_expr_str_metadata(THD *thd, Item *a): Item_str_func(thd, a) { } void fix_length_and_dec() { collation.set(system_charset_info); @@ -923,28 +1132,69 @@ public: maybe_null= 0; }; table_map not_null_tables() const { return 0; } + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) + { return this; } + bool const_item() const { return true; } }; -class Item_func_collation :public Item_str_func + +class Item_func_charset :public Item_func_expr_str_metadata { public: - Item_func_collation(Item *a) :Item_str_func(a) {} + Item_func_charset(THD *thd, Item *a) + :Item_func_expr_str_metadata(thd, a) { } + String *val_str(String *); + const char *func_name() const { return "charset"; } +}; + + +class Item_func_collation :public Item_func_expr_str_metadata +{ +public: + Item_func_collation(THD *thd, Item *a) + :Item_func_expr_str_metadata(thd, a) {} String *val_str(String *); const char *func_name() const { return "collation"; } - void fix_length_and_dec() +}; + + +class Item_func_weight_string :public Item_str_func +{ + String tmp_value; + uint flags; + uint nweights; + uint result_length; +public: + Item_func_weight_string(THD *thd, Item *a, uint result_length_arg, + uint nweights_arg, uint flags_arg): + Item_str_func(thd, a) { - collation.set(system_charset_info); - max_length= 64 * collation.collation->mbmaxlen; // should be enough - maybe_null= 0; - }; - table_map not_null_tables() const { return 0; } + nweights= nweights_arg; + flags= flags_arg; + result_length= result_length_arg; + } + const char *func_name() const { return "weight_string"; } + String *val_str(String *); + void fix_length_and_dec(); + bool eq(const Item *item, bool binary_cmp) const + { + if (!Item_str_func::eq(item, binary_cmp)) + return false; + Item_func_weight_string *that= (Item_func_weight_string *)item; + return this->flags == that->flags && + this->nweights == that->nweights && + this->result_length == that->result_length; + } + Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) + { return this; } }; class Item_func_crc32 :public Item_int_func { String value; public: - Item_func_crc32(Item *a) :Item_int_func(a) { unsigned_flag= 1; } + Item_func_crc32(THD *thd, Item *a): Item_int_func(thd, a) + { unsigned_flag= 1; } const char *func_name() const { return "crc32"; } void fix_length_and_dec() { max_length=10; } longlong val_int(); @@ -954,7 +1204,7 @@ class Item_func_uncompressed_length : public Item_int_func { String value; public: - Item_func_uncompressed_length(Item *a):Item_int_func(a){} + Item_func_uncompressed_length(THD *thd, Item *a): Item_int_func(thd, a) {} const char *func_name() const{return "uncompressed_length";} void fix_length_and_dec() { max_length=10; maybe_null= true; } longlong val_int(); @@ -966,21 +1216,23 @@ public: #define ZLIB_DEPENDED_FUNCTION { null_value=1; return 0; } #endif -class Item_func_compress: public Item_str_func +class Item_func_compress: public Item_str_binary_checksum_func { - String buffer; + String tmp_value; public: - Item_func_compress(Item *a):Item_str_func(a){} + Item_func_compress(THD *thd, Item *a) + :Item_str_binary_checksum_func(thd, a) {} void fix_length_and_dec(){max_length= (args[0]->max_length*120)/100+12;} const char *func_name() const{return "compress";} String *val_str(String *) ZLIB_DEPENDED_FUNCTION }; -class Item_func_uncompress: public Item_str_func +class Item_func_uncompress: public Item_str_binary_checksum_func { - String buffer; + String tmp_value; public: - Item_func_uncompress(Item *a): Item_str_func(a){} + Item_func_uncompress(THD *thd, Item *a) + :Item_str_binary_checksum_func(thd, a) {} void fix_length_and_dec(){ maybe_null= 1; max_length= MAX_BLOB_WIDTH; } const char *func_name() const{return "uncompress";} String *val_str(String *) ZLIB_DEPENDED_FUNCTION @@ -990,7 +1242,7 @@ public: class Item_func_uuid: public Item_str_func { public: - Item_func_uuid(): Item_str_func() {} + Item_func_uuid(THD *thd): Item_str_func(thd) {} void fix_length_and_dec() { collation.set(system_charset_info, @@ -1013,31 +1265,47 @@ class Item_func_dyncol_create: public Item_str_func protected: DYNCALL_CREATE_DEF *defs; DYNAMIC_COLUMN_VALUE *vals; - uint *nums; - void prepare_arguments(); - void cleanup_arguments(); + uint *keys_num; + LEX_STRING *keys_str; + bool names, force_names; + bool prepare_arguments(bool force_names); void print_arguments(String *str, enum_query_type query_type); public: - Item_func_dyncol_create(List<Item> &args, DYNCALL_CREATE_DEF *dfs); + Item_func_dyncol_create(THD *thd, List<Item> &args, DYNCALL_CREATE_DEF *dfs); bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec(); const char *func_name() const{ return "column_create"; } String *val_str(String *); virtual void print(String *str, enum_query_type query_type); + virtual enum Functype functype() const { return DYNCOL_FUNC; } }; class Item_func_dyncol_add: public Item_func_dyncol_create { public: - Item_func_dyncol_add(List<Item> &args, DYNCALL_CREATE_DEF *dfs) - :Item_func_dyncol_create(args, dfs) + Item_func_dyncol_add(THD *thd, List<Item> &args_arg, DYNCALL_CREATE_DEF *dfs): + Item_func_dyncol_create(thd, args_arg, dfs) {} const char *func_name() const{ return "column_add"; } String *val_str(String *); virtual void print(String *str, enum_query_type query_type); }; +class Item_func_dyncol_json: public Item_str_func +{ +public: + Item_func_dyncol_json(THD *thd, Item *str): Item_str_func(thd, str) + {collation.set(DYNCOL_UTF);} + const char *func_name() const{ return "column_json"; } + String *val_str(String *); + void fix_length_and_dec() + { + max_length= MAX_BLOB_WIDTH; + maybe_null= 1; + decimals= 0; + } +}; /* The following functions is always called from an Item_cast function @@ -1046,13 +1314,10 @@ public: class Item_dyncol_get: public Item_str_func { public: - Item_dyncol_get(Item *str, Item *num) - :Item_str_func(str, num) - { - max_length= MAX_DYNAMIC_COLUMN_LENGTH; - } + Item_dyncol_get(THD *thd, Item *str, Item *num): Item_str_func(thd, str, num) + {} void fix_length_and_dec() - { maybe_null= 1; } + { maybe_null= 1;; max_length= MAX_BLOB_WIDTH; } /* Mark that collation can change between calls */ bool dynamic_result() { return 1; } @@ -1070,10 +1335,12 @@ public: class Item_func_dyncol_list: public Item_str_func { public: - Item_func_dyncol_list(Item *str) :Item_str_func(str) {}; + Item_func_dyncol_list(THD *thd, Item *str): Item_str_func(thd, str) + {collation.set(DYNCOL_UTF);} void fix_length_and_dec() { maybe_null= 1; max_length= MAX_BLOB_WIDTH; }; const char *func_name() const{ return "column_list"; } String *val_str(String *); }; #endif /* ITEM_STRFUNC_INCLUDED */ + diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 57dcbd4f540..b275f749f25 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -29,6 +29,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" /* It is necessary to include set_var.h instead of item.h because there @@ -43,10 +44,14 @@ double get_post_group_estimate(JOIN* join, double join_op_rows); +const char *exists_outer_expr_name= "<exists outer expr>"; -Item_subselect::Item_subselect(): - Item_result_field(), value_assigned(0), own_engine(0), thd(0), old_engine(0), - used_tables_cache(0), have_to_be_excluded(0), const_item_cache(1), +int check_and_do_in_subquery_rewrites(JOIN *join); + +Item_subselect::Item_subselect(THD *thd_arg): + Item_result_field(thd_arg), Used_tables_and_const_cache(), + value_assigned(0), own_engine(0), thd(0), old_engine(0), + have_to_be_excluded(0), inside_first_fix_fields(0), done_first_fix_fields(FALSE), expr_cache(0), forced_const(FALSE), substitution(0), engine(0), eliminated(FALSE), changed(0), is_correlated(FALSE) @@ -82,15 +87,24 @@ void Item_subselect::init(st_select_lex *select_lex, if (unit->item) { - /* - Item can be changed in JOIN::prepare while engine in JOIN::optimize - => we do not copy old_engine here - */ engine= unit->item->engine; - own_engine= FALSE; parsing_place= unit->item->parsing_place; - unit->thd->change_item_tree((Item**)&unit->item, this); - engine->change_result(this, result, TRUE); + if (unit->item->substype() == EXISTS_SUBS && + ((Item_exists_subselect *)unit->item)->exists_transformed) + { + /* it is permanent transformation of EXISTS to IN */ + unit->item= this; + engine->change_result(this, result, FALSE); + } + else + { + /* + Item can be changed in JOIN::prepare while engine in JOIN::optimize + => we do not copy old_engine here + */ + unit->thd->change_item_tree((Item**)&unit->item, this); + engine->change_result(this, result, TRUE); + } } else { @@ -433,7 +447,8 @@ bool Item_subselect::mark_as_dependent(THD *thd, st_select_lex *select, OUTER_REF_TABLE_BIT. */ -void Item_subselect::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_subselect::fix_after_pullout(st_select_lex *new_parent, + Item **ref, bool merge) { recalc_used_tables(new_parent, TRUE); parent_select= new_parent; @@ -467,7 +482,7 @@ public: void Item_subselect::recalc_used_tables(st_select_lex *new_parent, bool after_pullout) { - List_iterator<Ref_to_outside> it(upper_refs); + List_iterator_fast<Ref_to_outside> it(upper_refs); Ref_to_outside *upper; DBUG_ENTER("recalc_used_tables"); @@ -548,26 +563,57 @@ void Item_subselect::recalc_used_tables(st_select_lex *new_parent, bool Item_subselect::is_expensive() { double examined_rows= 0; + bool all_are_simple= true; + + /* check extremely simple select */ + if (!unit->first_select()->next_select()) // no union + { + /* + such single selects works even without optimization because + can not makes loops + */ + SELECT_LEX *sl= unit->first_select(); + JOIN *join = sl->join; + if (join && !join->tables_list && !sl->first_inner_unit()) + return false; + } + for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) { JOIN *cur_join= sl->join; + + /* not optimized subquery */ if (!cur_join) + return true; + + /* + If the subquery is not optimised or in the process of optimization + it supposed to be expensive + */ + if (cur_join->optimization_state != JOIN::OPTIMIZATION_DONE) + return true; + + if (!cur_join->tables_list && !sl->first_inner_unit()) continue; /* Subqueries whose result is known after optimization are not expensive. Such subqueries have all tables optimized away, thus have no join plan. */ - if (cur_join->optimized && - (cur_join->zero_result_cause || !cur_join->tables_list)) - return false; + if ((cur_join->zero_result_cause || !cur_join->tables_list)) + continue; + + /* + This is not simple SELECT in union so we can not go by simple condition + */ + all_are_simple= false; /* If a subquery is not optimized we cannot estimate its cost. A subquery is considered optimized if it has a join plan. */ - if (!(cur_join->optimized && cur_join->join_tab)) + if (!cur_join->join_tab) return true; if (sl->first_inner_unit()) @@ -581,8 +627,10 @@ bool Item_subselect::is_expensive() examined_rows+= cur_join->get_examined_rows(); } + // here we are sure that subquery is optimized so thd is set - return (examined_rows > thd->variables.expensive_subquery_limit); + return !all_are_simple && + (examined_rows > thd->variables.expensive_subquery_limit); } @@ -681,9 +729,12 @@ bool Item_subselect::exec() void Item_subselect::get_cache_parameters(List<Item> ¶meters) { - Collect_deps_prm prm= {¶meters, - unit->first_select()->nest_level_base, - unit->first_select()->nest_level}; + Collect_deps_prm prm= {¶meters, // parameters + unit->first_select()->nest_level_base, // nest_level_base + 0, // count + unit->first_select()->nest_level, // nest_level + TRUE // collect + }; walk(&Item::collect_outer_ref_processor, TRUE, (uchar*)&prm); } @@ -784,7 +835,7 @@ inline bool Item_in_subselect::left_expr_has_null() @note This method allows many columns in the subquery because it is supported by - Item_in optimizer and result of the IN subquery will be scalar in this + Item_in_optimizer and result of the IN subquery will be scalar in this case. @retval TRUE cache is needed @@ -870,7 +921,7 @@ bool Item_subselect::const_item() const Item *Item_subselect::get_tmp_table_item(THD *thd_arg) { if (!with_sum_func && !const_item()) - return new Item_field(result_field); + return new (thd->mem_root) Item_temptable_field(thd_arg, result_field); return copy_or_same(thd_arg); } @@ -891,6 +942,21 @@ void Item_subselect::update_used_tables() void Item_subselect::print(String *str, enum_query_type query_type) { + if (query_type & QT_ITEM_SUBSELECT_ID_ONLY) + { + str->append("(subquery#"); + if (unit && unit->first_select()) + { + char buf[64]; + ll2str(unit->first_select()->select_number, buf, 10, 0); + str->append(buf); + } + else + str->append("NULL"); // TODO: what exactly does this mean? + + str->append(")"); + return; + } if (engine) { str->append('('); @@ -902,11 +968,11 @@ void Item_subselect::print(String *str, enum_query_type query_type) } -Item_singlerow_subselect::Item_singlerow_subselect(st_select_lex *select_lex) - :Item_subselect(), value(0) +Item_singlerow_subselect::Item_singlerow_subselect(THD *thd, st_select_lex *select_lex): + Item_subselect(thd), value(0) { DBUG_ENTER("Item_singlerow_subselect::Item_singlerow_subselect"); - init(select_lex, new select_singlerow_subselect(this)); + init(select_lex, new (thd->mem_root) select_singlerow_subselect(thd, this)); maybe_null= 1; max_columns= UINT_MAX; DBUG_VOID_RETURN; @@ -932,18 +998,17 @@ Item_singlerow_subselect::invalidate_and_restore_select_lex() DBUG_RETURN(result); } -Item_maxmin_subselect::Item_maxmin_subselect(THD *thd_param, +Item_maxmin_subselect::Item_maxmin_subselect(THD *thd, Item_subselect *parent, st_select_lex *select_lex, - bool max_arg) - :Item_singlerow_subselect(), was_values(TRUE) + bool max_arg): + Item_singlerow_subselect(thd), was_values(TRUE) { DBUG_ENTER("Item_maxmin_subselect::Item_maxmin_subselect"); max= max_arg; init(select_lex, - new select_max_min_finder_subselect(this, max_arg, - parent->substype() == - Item_subselect::ALL_SUBS)); + new (thd->mem_root) select_max_min_finder_subselect(thd, + this, max_arg, parent->substype() == Item_subselect::ALL_SUBS)); max_columns= 1; maybe_null= 1; max_columns= 1; @@ -955,12 +1020,6 @@ Item_maxmin_subselect::Item_maxmin_subselect(THD *thd_param, used_tables_cache= parent->get_used_tables_cache(); const_item_cache= parent->const_item(); - /* - this subquery always creates during preparation, so we can assign - thd here - */ - thd= thd_param; - DBUG_VOID_RETURN; } @@ -1000,7 +1059,7 @@ void Item_maxmin_subselect::no_rows_in_result() */ if (parsing_place != SELECT_LIST || const_item()) return; - value= Item_cache::get_cache(new Item_null()); + value= Item_cache::get_cache(thd, new (thd->mem_root) Item_null(thd)); null_value= 0; was_values= 0; make_const(); @@ -1018,7 +1077,7 @@ void Item_singlerow_subselect::no_rows_in_result() */ if (parsing_place != SELECT_LIST || const_item()) return; - value= Item_cache::get_cache(new Item_null()); + value= Item_cache::get_cache(thd, new (thd->mem_root) Item_null(thd)); reset(); make_const(); } @@ -1086,15 +1145,17 @@ Item_singlerow_subselect::select_transformer(JOIN *join) if (thd->lex->describe) { char warn_buff[MYSQL_ERRMSG_SIZE]; - sprintf(warn_buff, ER(ER_SELECT_REDUCED), select_lex->select_number); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + sprintf(warn_buff, ER_THD(thd, ER_SELECT_REDUCED), + select_lex->select_number); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SELECT_REDUCED, warn_buff); } substitution= select_lex->item_list.head(); /* as far as we moved content to upper level we have to fix dependences & Co */ - substitution->fix_after_pullout(select_lex->outer_select(), &substitution); + substitution->fix_after_pullout(select_lex->outer_select(), + &substitution, TRUE); } DBUG_RETURN(false); } @@ -1162,7 +1223,7 @@ void Item_singlerow_subselect::fix_length_and_dec() @details The function checks whether an expression cache is needed for this item and if if so wraps the item into an item of the class - Item_exp_cache_wrapper with an appropriate expression cache set up there. + Item_cache_wrapper with an appropriate expression cache set up there. @note used from Item::transform() @@ -1172,17 +1233,22 @@ void Item_singlerow_subselect::fix_length_and_dec() this item - otherwise */ -Item* Item_singlerow_subselect::expr_cache_insert_transformer(uchar *thd_arg) +Item* Item_singlerow_subselect::expr_cache_insert_transformer(THD *tmp_thd, + uchar *unused) { - THD *thd= (THD*) thd_arg; DBUG_ENTER("Item_singlerow_subselect::expr_cache_insert_transformer"); + DBUG_ASSERT(thd == tmp_thd); + if (expr_cache) DBUG_RETURN(expr_cache); - if (expr_cache_is_needed(thd) && - (expr_cache= set_expr_cache(thd))) + if (expr_cache_is_needed(tmp_thd) && + (expr_cache= set_expr_cache(tmp_thd))) + { + init_expr_cache_tracker(tmp_thd); DBUG_RETURN(expr_cache); + } DBUG_RETURN(this); } @@ -1326,12 +1392,13 @@ bool Item_singlerow_subselect::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) } -Item_exists_subselect::Item_exists_subselect(st_select_lex *select_lex): - Item_subselect() +Item_exists_subselect::Item_exists_subselect(THD *thd, + st_select_lex *select_lex): + Item_subselect(thd), upper_not(NULL), abort_on_null(0), + emb_on_expr_nest(NULL), optimizer(0), exists_transformed(0) { DBUG_ENTER("Item_exists_subselect::Item_exists_subselect"); - bool val_bool(); - init(select_lex, new select_exists_subselect(this)); + init(select_lex, new (thd->mem_root) select_exists_subselect(thd, this)); max_columns= UINT_MAX; null_value= FALSE; //can't be NULL maybe_null= 0; //can't be NULL @@ -1362,26 +1429,26 @@ bool Item_in_subselect::test_limit(st_select_lex_unit *unit_arg) return(0); } -Item_in_subselect::Item_in_subselect(Item * left_exp, +Item_in_subselect::Item_in_subselect(THD *thd, Item * left_exp, st_select_lex *select_lex): - Item_exists_subselect(), - left_expr_cache(0), first_execution(TRUE), in_strategy(SUBS_NOT_TRANSFORMED), - optimizer(0), pushed_cond_guards(NULL), emb_on_expr_nest(NULL), - do_not_convert_to_sj(FALSE), is_jtbm_merged(FALSE), is_jtbm_const_tab(FALSE), - is_flattenable_semijoin(FALSE), - is_registered_semijoin(FALSE), + Item_exists_subselect(thd), left_expr_cache(0), first_execution(TRUE), + in_strategy(SUBS_NOT_TRANSFORMED), + pushed_cond_guards(NULL), do_not_convert_to_sj(FALSE), is_jtbm_merged(FALSE), + is_jtbm_const_tab(FALSE), is_flattenable_semijoin(FALSE), + is_registered_semijoin(FALSE), upper_item(0) { DBUG_ENTER("Item_in_subselect::Item_in_subselect"); + DBUG_PRINT("info", ("in_strategy: %u", (uint)in_strategy)); left_expr_orig= left_expr= left_exp; /* prepare to possible disassembling the item in convert_subq_to_sj() */ if (left_exp->type() == Item::ROW_ITEM) - left_expr_orig= new Item_row(left_exp); + left_expr_orig= new (thd->mem_root) + Item_row(thd, static_cast<Item_row*>(left_exp)); func= &eq_creator; - init(select_lex, new select_exists_subselect(this)); + init(select_lex, new (thd->mem_root) select_exists_subselect(thd, this)); max_columns= UINT_MAX; maybe_null= 1; - abort_on_null= 0; reset(); //if test_limit will fail then error will be reported to client test_limit(select_lex->master_unit()); @@ -1393,19 +1460,20 @@ int Item_in_subselect::get_identifier() return engine->get_identifier(); } -Item_allany_subselect::Item_allany_subselect(Item * left_exp, +Item_allany_subselect::Item_allany_subselect(THD *thd, Item * left_exp, chooser_compare_func_creator fc, st_select_lex *select_lex, - bool all_arg) - :Item_in_subselect(), func_creator(fc), all(all_arg) + bool all_arg): + Item_in_subselect(thd), func_creator(fc), all(all_arg) { DBUG_ENTER("Item_allany_subselect::Item_allany_subselect"); left_expr_orig= left_expr= left_exp; /* prepare to possible disassembling the item in convert_subq_to_sj() */ if (left_exp->type() == Item::ROW_ITEM) - left_expr_orig= new Item_row(left_exp); + left_expr_orig= new (thd->mem_root) + Item_row(thd, static_cast<Item_row*>(left_exp)); func= func_creator(all_arg); - init(select_lex, new select_exists_subselect(this)); + init(select_lex, new (thd->mem_root) select_exists_subselect(thd, this)); max_columns= 1; abort_on_null= 0; reset(); @@ -1436,8 +1504,8 @@ void Item_exists_subselect::fix_length_and_dec() We need only 1 row to determine existence (i.e. any EXISTS that is not an IN always requires LIMIT 1) */ - thd->change_item_tree(&unit->global_parameters->select_limit, - new Item_int((int32) 1)); + thd->change_item_tree(&unit->global_parameters()->select_limit, + new (thd->mem_root) Item_int(thd, (int32) 1)); DBUG_PRINT("info", ("Set limit to 1")); DBUG_VOID_RETURN; } @@ -1463,7 +1531,7 @@ void Item_in_subselect::fix_length_and_dec() @details The function checks whether an expression cache is needed for this item and if if so wraps the item into an item of the class - Item_exp_cache_wrapper with an appropriate expression cache set up there. + Item_cache_wrapper with an appropriate expression cache set up there. @note used from Item::transform() @@ -1473,17 +1541,21 @@ void Item_in_subselect::fix_length_and_dec() this item - otherwise */ -Item* Item_exists_subselect::expr_cache_insert_transformer(uchar *thd_arg) +Item* Item_exists_subselect::expr_cache_insert_transformer(THD *tmp_thd, + uchar *unused) { - THD *thd= (THD*) thd_arg; DBUG_ENTER("Item_exists_subselect::expr_cache_insert_transformer"); + DBUG_ASSERT(thd == tmp_thd); if (expr_cache) DBUG_RETURN(expr_cache); - if (substype() == EXISTS_SUBS && expr_cache_is_needed(thd) && - (expr_cache= set_expr_cache(thd))) + if (substype() == EXISTS_SUBS && expr_cache_is_needed(tmp_thd) && + (expr_cache= set_expr_cache(tmp_thd))) + { + init_expr_cache_tracker(tmp_thd); DBUG_RETURN(expr_cache); + } DBUG_RETURN(this); } @@ -1766,13 +1838,14 @@ Item_in_subselect::single_value_transformer(JOIN *join) of the statement. Thus one of 'substitution' arguments can be broken in case of PS. */ - substitution= func->create(left_expr, where_item); + substitution= func->create(thd, left_expr, where_item); have_to_be_excluded= 1; if (thd->lex->describe) { char warn_buff[MYSQL_ERRMSG_SIZE]; - sprintf(warn_buff, ER(ER_SELECT_REDUCED), select_lex->select_number); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + sprintf(warn_buff, ER_THD(thd, ER_SELECT_REDUCED), + select_lex->select_number); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SELECT_REDUCED, warn_buff); } DBUG_RETURN(false); @@ -1790,8 +1863,7 @@ Item_in_subselect::single_value_transformer(JOIN *join) SELECT_LEX *current= thd->lex->current_select; thd->lex->current_select= current->return_after_parsing(); - //optimizer never use Item **ref => we can pass 0 as parameter - if (!optimizer || optimizer->fix_left(thd, 0)) + if (!optimizer || optimizer->fix_left(thd)) { thd->lex->current_select= current; DBUG_RETURN(true); @@ -1802,10 +1874,10 @@ Item_in_subselect::single_value_transformer(JOIN *join) optimizer->keep_top_level_cache(); /* - As far as Item_ref_in_optimizer do not substitute itself on fix_fields + As far as Item_in_optimizer does not substitute itself on fix_fields we can use same item for all selects. */ - expr= new Item_direct_ref(&select_lex->context, + expr= new (thd->mem_root) Item_direct_ref(thd, &select_lex->context, (Item**)optimizer->get_cache(), (char *)"<no matter>", (char *)in_left_expr_name); @@ -1874,7 +1946,7 @@ bool Item_allany_subselect::transform_into_max_min(JOIN *join) (ALL && (> || =>)) || (ANY && (< || =<)) for ALL condition is inverted */ - item= new Item_sum_max(*select_lex->ref_pointer_array); + item= new (thd->mem_root) Item_sum_max(thd, *select_lex->ref_pointer_array); } else { @@ -1882,7 +1954,7 @@ bool Item_allany_subselect::transform_into_max_min(JOIN *join) (ALL && (< || =<)) || (ANY && (> || =>)) for ALL condition is inverted */ - item= new Item_sum_min(*select_lex->ref_pointer_array); + item= new (thd->mem_root) Item_sum_min(thd, *select_lex->ref_pointer_array); } if (upper_item) upper_item->set_sum_test(item); @@ -1912,7 +1984,7 @@ bool Item_allany_subselect::transform_into_max_min(JOIN *join) 0); if (join->prepare_stage2()) DBUG_RETURN(true); - subs= new Item_singlerow_subselect(select_lex); + subs= new (thd->mem_root) Item_singlerow_subselect(thd, select_lex); /* Remove other strategies if any (we already changed the query and @@ -1923,7 +1995,7 @@ bool Item_allany_subselect::transform_into_max_min(JOIN *join) else { Item_maxmin_subselect *item; - subs= item= new Item_maxmin_subselect(thd, this, select_lex, func->l_op()); + subs= item= new (thd->mem_root) Item_maxmin_subselect(thd, this, select_lex, func->l_op()); if (upper_item) upper_item->set_sub_test(item); /* @@ -1936,7 +2008,7 @@ bool Item_allany_subselect::transform_into_max_min(JOIN *join) The swap is needed for expressions of type 'f1 < ALL ( SELECT ....)' where we want to evaluate the sub query even if f1 would be null. */ - subs= func->create_swap(*(optimizer->get_cache()), subs); + subs= func->create_swap(thd, *(optimizer->get_cache()), subs); thd->change_item_tree(place, subs); if (subs->fix_fields(thd, &subs)) DBUG_RETURN(true); @@ -1969,7 +2041,7 @@ bool Item_allany_subselect::is_maxmin_applicable(JOIN *join) WHERE condition. */ return (abort_on_null || (upper_item && upper_item->is_top_level_item())) && - !join->select_lex->master_unit()->uncacheable && !func->eqne_op(); + !(join->select_lex->master_unit()->uncacheable & ~UNCACHEABLE_EXPLAIN) && !func->eqne_op(); } @@ -2029,8 +2101,10 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN *join, if (join_having || select_lex->with_sum_func || select_lex->group_list.elements) { - Item *item= func->create(expr, - new Item_ref_null_helper(&select_lex->context, + Item *item= func->create(thd, expr, + new (thd->mem_root) Item_ref_null_helper( + thd, + &select_lex->context, this, select_lex-> ref_pointer_array, @@ -2043,7 +2117,7 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN *join, within a trig_cond. */ disable_cond_guard_for_const_null_left_expr(0); - item= new Item_func_trig_cond(item, get_cond_guard(0)); + item= new (thd->mem_root) Item_func_trig_cond(thd, item, get_cond_guard(0)); } if (!join_having) @@ -2054,22 +2128,25 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN *join, } else { - Item *item= (Item*) select_lex->item_list.head()->real_item(); + Item *item= (Item*) select_lex->item_list.head(); + if (item->type() != REF_ITEM || + ((Item_ref*)item)->ref_type() != Item_ref::VIEW_REF) + item= item->real_item(); if (select_lex->table_list.elements) { Item *having= item; Item *orig_item= item; - item= func->create(expr, item); + item= func->create(thd, expr, item); if (!abort_on_null && orig_item->maybe_null) { - having= new Item_is_not_null_test(this, having); + having= new (thd->mem_root) Item_is_not_null_test(thd, this, having); if (left_expr->maybe_null) { disable_cond_guard_for_const_null_left_expr(0); - if (!(having= new Item_func_trig_cond(having, - get_cond_guard(0)))) + if (!(having= new (thd->mem_root) Item_func_trig_cond(thd, having, + get_cond_guard(0)))) DBUG_RETURN(true); } having->name= (char*) in_having_cond; @@ -2077,8 +2154,8 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN *join, DBUG_RETURN(true); *having_item= having; - item= new Item_cond_or(item, - new Item_func_isnull(orig_item)); + item= new (thd->mem_root) Item_cond_or(thd, item, + new (thd->mem_root) Item_func_isnull(thd, orig_item)); } /* If we may encounter NULL IN (SELECT ...) and care whether subquery @@ -2087,7 +2164,8 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN *join, if (!abort_on_null && left_expr->maybe_null) { disable_cond_guard_for_const_null_left_expr(0); - if (!(item= new Item_func_trig_cond(item, get_cond_guard(0)))) + if (!(item= new (thd->mem_root) Item_func_trig_cond(thd, item, + get_cond_guard(0)))) DBUG_RETURN(true); } @@ -2106,16 +2184,18 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN *join, if (select_lex->master_unit()->is_union()) { Item *new_having= - func->create(expr, - new Item_ref_null_helper(&select_lex->context, this, - select_lex->ref_pointer_array, - (char *)"<no matter>", - (char *)"<result>")); + func->create(thd, expr, + new (thd->mem_root) Item_ref_null_helper(thd, + &select_lex->context, + this, + select_lex->ref_pointer_array, + (char *)"<no matter>", + (char *)"<result>")); if (!abort_on_null && left_expr->maybe_null) { disable_cond_guard_for_const_null_left_expr(0); - if (!(new_having= new Item_func_trig_cond(new_having, - get_cond_guard(0)))) + if (!(new_having= new (thd->mem_root) Item_func_trig_cond(thd, new_having, + get_cond_guard(0)))) DBUG_RETURN(true); } @@ -2177,8 +2257,7 @@ Item_in_subselect::row_value_transformer(JOIN *join) SELECT_LEX *current= thd->lex->current_select; thd->lex->current_select= current->return_after_parsing(); - //optimizer never use Item **ref => we can pass 0 as parameter - if (!optimizer || optimizer->fix_left(thd, 0)) + if (!optimizer || optimizer->fix_left(thd)) { thd->lex->current_select= current; DBUG_RETURN(true); @@ -2290,50 +2369,58 @@ Item_in_subselect::create_row_in_to_exists_cond(JOIN * join, check_cols(left_expr->element_index(i)->cols())) DBUG_RETURN(true); Item *item_eq= - new Item_func_eq(new - Item_direct_ref(&select_lex->context, - (*optimizer->get_cache())-> - addr(i), - (char *)"<no matter>", - (char *)in_left_expr_name), - new - Item_ref(&select_lex->context, - select_lex->ref_pointer_array + i, + new (thd->mem_root) + Item_func_eq(thd, new (thd->mem_root) + Item_direct_ref(thd, &select_lex->context, + (*optimizer->get_cache())-> + addr(i), + (char *)"<no matter>", + (char *)in_left_expr_name), + new (thd->mem_root) + Item_ref(thd, &select_lex->context, + select_lex->ref_pointer_array + i, + (char *)"<no matter>", + (char *)"<list ref>")); + Item *item_isnull= + new (thd->mem_root) + Item_func_isnull(thd, + new (thd->mem_root) + Item_ref(thd, &select_lex->context, + select_lex->ref_pointer_array+i, (char *)"<no matter>", (char *)"<list ref>")); - Item *item_isnull= - new Item_func_isnull(new - Item_ref(&select_lex->context, - select_lex->ref_pointer_array+i, - (char *)"<no matter>", - (char *)"<list ref>")); - Item *col_item= new Item_cond_or(item_eq, item_isnull); + Item *col_item= new (thd->mem_root) + Item_cond_or(thd, item_eq, item_isnull); if (!abort_on_null && left_expr->element_index(i)->maybe_null) { disable_cond_guard_for_const_null_left_expr(i); - if (!(col_item= new Item_func_trig_cond(col_item, get_cond_guard(i)))) + if (!(col_item= new (thd->mem_root) + Item_func_trig_cond(thd, col_item, get_cond_guard(i)))) DBUG_RETURN(true); } - *having_item= and_items(*having_item, col_item); + *having_item= and_items(thd, *having_item, col_item); Item *item_nnull_test= - new Item_is_not_null_test(this, - new Item_ref(&select_lex->context, - select_lex-> - ref_pointer_array + i, - (char *)"<no matter>", - (char *)"<list ref>")); + new (thd->mem_root) + Item_is_not_null_test(thd, this, + new (thd->mem_root) + Item_ref(thd, &select_lex->context, + select_lex-> + ref_pointer_array + i, + (char *)"<no matter>", + (char *)"<list ref>")); if (!abort_on_null && left_expr->element_index(i)->maybe_null) { disable_cond_guard_for_const_null_left_expr(i); if (!(item_nnull_test= - new Item_func_trig_cond(item_nnull_test, get_cond_guard(i)))) + new (thd->mem_root) + Item_func_trig_cond(thd, item_nnull_test, get_cond_guard(i)))) DBUG_RETURN(true); } - item_having_part2= and_items(item_having_part2, item_nnull_test); + item_having_part2= and_items(thd, item_having_part2, item_nnull_test); item_having_part2->top_level_item(); } - *having_item= and_items(*having_item, item_having_part2); + *having_item= and_items(thd, *having_item, item_having_part2); } else { @@ -2348,54 +2435,60 @@ Item_in_subselect::create_row_in_to_exists_cond(JOIN * join, if (select_lex->ref_pointer_array[i]-> check_cols(left_expr->element_index(i)->cols())) DBUG_RETURN(true); - item= - new Item_func_eq(new - Item_direct_ref(&select_lex->context, - (*optimizer->get_cache())-> - addr(i), - (char *)"<no matter>", - (char *)in_left_expr_name), - new - Item_direct_ref(&select_lex->context, - select_lex-> - ref_pointer_array+i, - (char *)"<no matter>", - (char *)"<list ref>")); - if (!abort_on_null) + item= new (thd->mem_root) + Item_func_eq(thd, + new (thd->mem_root) + Item_direct_ref(thd, &select_lex->context, + (*optimizer->get_cache())-> + addr(i), + (char *)"<no matter>", + (char *)in_left_expr_name), + new (thd->mem_root) + Item_direct_ref(thd, &select_lex->context, + select_lex-> + ref_pointer_array+i, + (char *)"<no matter>", + (char *)"<list ref>")); + if (!abort_on_null && select_lex->ref_pointer_array[i]->maybe_null) { Item *having_col_item= - new Item_is_not_null_test(this, - new - Item_ref(&select_lex->context, - select_lex->ref_pointer_array + i, - (char *)"<no matter>", - (char *)"<list ref>")); + new (thd->mem_root) + Item_is_not_null_test(thd, this, + new (thd->mem_root) + Item_ref(thd, &select_lex->context, + select_lex->ref_pointer_array + i, + (char *)"<no matter>", + (char *)"<list ref>")); - item_isnull= new - Item_func_isnull(new - Item_direct_ref(&select_lex->context, + item_isnull= new (thd->mem_root) + Item_func_isnull(thd, + new (thd->mem_root) + Item_direct_ref(thd, &select_lex->context, select_lex-> ref_pointer_array+i, (char *)"<no matter>", (char *)"<list ref>")); - item= new Item_cond_or(item, item_isnull); - /* - TODO: why we create the above for cases where the right part - cant be NULL? - */ + item= new (thd->mem_root) Item_cond_or(thd, item, item_isnull); if (left_expr->element_index(i)->maybe_null) { disable_cond_guard_for_const_null_left_expr(i); - if (!(item= new Item_func_trig_cond(item, get_cond_guard(i)))) + if (!(item= new (thd->mem_root) + Item_func_trig_cond(thd, item, get_cond_guard(i)))) DBUG_RETURN(true); - if (!(having_col_item= - new Item_func_trig_cond(having_col_item, get_cond_guard(i)))) + if (!(having_col_item= new (thd->mem_root) + Item_func_trig_cond(thd, having_col_item, get_cond_guard(i)))) DBUG_RETURN(true); } - *having_item= and_items(*having_item, having_col_item); + *having_item= and_items(thd, *having_item, having_col_item); + } + if (!abort_on_null && left_expr->element_index(i)->maybe_null) + { + if (!(item= new (thd->mem_root) + Item_func_trig_cond(thd, item, get_cond_guard(i)))) + DBUG_RETURN(true); } - *where_item= and_items(*where_item, item); + *where_item= and_items(thd, *where_item, item); } } @@ -2425,6 +2518,12 @@ Item_in_subselect::select_transformer(JOIN *join) return select_in_like_transformer(join); } +bool +Item_exists_subselect::select_transformer(JOIN *join) +{ + return select_prepare_to_be_in(); +} + /** Create the predicates needed to transform an IN/ALL/ANY subselect into a @@ -2541,7 +2640,7 @@ bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) and_args->disjoin((List<Item> *) &join_arg->cond_equal->current_level); } - where_item= and_items(join_arg->conds, where_item); + where_item= and_items(thd, join_arg->conds, where_item); if (!where_item->fixed && where_item->fix_fields(thd, 0)) DBUG_RETURN(true); // TIMOUR TODO: call optimize_cond() for the new where clause @@ -2558,7 +2657,7 @@ bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) Item_equal *elem; while ((elem= li++)) { - and_args->push_back(elem); + and_args->push_back(elem, thd->mem_root); } } } @@ -2566,7 +2665,7 @@ bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) if (having_item) { Item* join_having= join_arg->having ? join_arg->having:join_arg->tmp_having; - having_item= and_items(join_having, having_item); + having_item= and_items(thd, join_having, having_item); if (fix_having(having_item, select_lex)) DBUG_RETURN(true); // TIMOUR TODO: call optimize_cond() for the new having clause @@ -2574,14 +2673,454 @@ bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) select_lex->having->top_level_item(); join_arg->having= select_lex->having; } - join_arg->thd->change_item_tree(&unit->global_parameters->select_limit, - new Item_int((int32) 1)); + join_arg->thd->change_item_tree(&unit->global_parameters()->select_limit, + new (thd->mem_root) + Item_int(thd, (int32) 1)); unit->select_limit_cnt= 1; DBUG_RETURN(false); } +/* + If this select can potentially be converted by EXISTS->IN conversion, wrap it + in an Item_in_optimizer object. Final decision whether to do the conversion + is done at a later phase. +*/ + +bool Item_exists_subselect::select_prepare_to_be_in() +{ + bool trans_res= FALSE; + DBUG_ENTER("Item_exists_subselect::select_prepare_to_be_in"); + if (!optimizer && + thd->lex->sql_command == SQLCOM_SELECT && + !unit->first_select()->is_part_of_union() && + optimizer_flag(thd, OPTIMIZER_SWITCH_EXISTS_TO_IN) && + (is_top_level_item() || + (upper_not && upper_not->is_top_level_item()))) + { + Query_arena *arena, backup; + bool result; + arena= thd->activate_stmt_arena_if_needed(&backup); + result= (!(optimizer= new (thd->mem_root) Item_in_optimizer(thd, new (thd->mem_root) Item_int(thd, 1), this))); + if (arena) + thd->restore_active_arena(arena, &backup); + if (result) + trans_res= TRUE; + else + substitution= optimizer; + } + DBUG_RETURN(trans_res); +} + +/** + Check if 'func' is an equality in form "inner_table.column = outer_expr" + + @param func Expression to check + @param local_field OUT Return "inner_table.column" here + @param outer_expr OUT Return outer_expr here + + @return true - 'func' is an Equality. +*/ + +static bool check_equality_for_exist2in(Item_func *func, + Item_ident **local_field, + Item **outer_exp) +{ + Item **args; + if (func->functype() != Item_func::EQ_FUNC) + return FALSE; + DBUG_ASSERT(func->argument_count() == 2); + args= func->arguments(); + if (args[0]->real_type() == Item::FIELD_ITEM && + args[0]->all_used_tables() != OUTER_REF_TABLE_BIT && + args[1]->all_used_tables() == OUTER_REF_TABLE_BIT) + { + /* It is Item_field or Item_direct_view_ref) */ + DBUG_ASSERT(args[0]->type() == Item::FIELD_ITEM || + args[0]->type() == Item::REF_ITEM); + *local_field= (Item_ident *)args[0]; + *outer_exp= args[1]; + return TRUE; + } + else if (args[1]->real_type() == Item::FIELD_ITEM && + args[1]->all_used_tables() != OUTER_REF_TABLE_BIT && + args[0]->all_used_tables() == OUTER_REF_TABLE_BIT) + { + /* It is Item_field or Item_direct_view_ref) */ + DBUG_ASSERT(args[1]->type() == Item::FIELD_ITEM || + args[1]->type() == Item::REF_ITEM); + *local_field= (Item_ident *)args[1]; + *outer_exp= args[0]; + return TRUE; + } + + return FALSE; +} + +typedef struct st_eq_field_outer +{ + Item **eq_ref; + Item_ident *local_field; + Item *outer_exp; +} EQ_FIELD_OUTER; + + +/** + Check if 'conds' is a set of AND-ed outer_expr=inner_table.col equalities + + @detail + Check if 'conds' has form + + outer1=inner_tbl1.col1 AND ... AND outer2=inner_tbl1.col2 AND remainder_cond + + @param conds Condition to be checked + @parm result Array to collect EQ_FIELD_OUTER elements describing + inner-vs-outer equalities the function has found. + @return + false - some inner-vs-outer equalities were found + true - otherwise. +*/ + +static bool find_inner_outer_equalities(Item **conds, + Dynamic_array<EQ_FIELD_OUTER> &result) +{ + bool found= FALSE; + EQ_FIELD_OUTER element; + if (is_cond_and(*conds)) + { + List_iterator<Item> li(*((Item_cond*)*conds)->argument_list()); + Item *item; + while ((item= li++)) + { + if (item->type() == Item::FUNC_ITEM && + check_equality_for_exist2in((Item_func *)item, + &element.local_field, + &element.outer_exp)) + { + found= TRUE; + element.eq_ref= li.ref(); + if (result.append(element)) + goto alloc_err; + } + } + } + else if ((*conds)->type() == Item::FUNC_ITEM && + check_equality_for_exist2in((Item_func *)*conds, + &element.local_field, + &element.outer_exp)) + { + found= TRUE; + element.eq_ref= conds; + if (result.append(element)) + goto alloc_err; + } + + return !found; +alloc_err: + return TRUE; +} + +/** + Converts EXISTS subquery to IN subquery if it is possible and has sense + + @param opt_arg Pointer on THD + + @return TRUE in case of error and FALSE otherwise. +*/ + +bool Item_exists_subselect::exists2in_processor(uchar *opt_arg) +{ + THD *thd= (THD *)opt_arg; + SELECT_LEX *first_select=unit->first_select(), *save_select; + JOIN *join= first_select->join; + Item **eq_ref= NULL; + Item_ident *local_field= NULL; + Item *outer_exp= NULL; + Item *left_exp= NULL; Item_in_subselect *in_subs; + Query_arena *arena= NULL, backup; + int res= FALSE; + List<Item> outer; + Dynamic_array<EQ_FIELD_OUTER> eqs(5, 5); + bool will_be_correlated; + DBUG_ENTER("Item_exists_subselect::exists2in_processor"); + + if (!optimizer || + !optimizer_flag(thd, OPTIMIZER_SWITCH_EXISTS_TO_IN) || + (!is_top_level_item() && (!upper_not || + !upper_not->is_top_level_item())) || + first_select->is_part_of_union() || + first_select->group_list.elements || + first_select->order_list.elements || + join->having || + first_select->with_sum_func || + !first_select->leaf_tables.elements|| + !join->conds) + DBUG_RETURN(FALSE); + + DBUG_ASSERT(first_select->order_list.elements == 0 && + first_select->group_list.elements == 0 && + first_select->having == NULL); + + if (find_inner_outer_equalities(&join->conds, eqs)) + DBUG_RETURN(FALSE); + + DBUG_ASSERT(eqs.elements() != 0); + + save_select= thd->lex->current_select; + thd->lex->current_select= first_select; + + /* check that the subquery has only dependencies we are going pull out */ + { + List<Item> unused; + Collect_deps_prm prm= {&unused, // parameters + unit->first_select()->nest_level_base, // nest_level_base + 0, // count + unit->first_select()->nest_level, // nest_level + FALSE // collect + }; + walk(&Item::collect_outer_ref_processor, TRUE, (uchar*)&prm); + DBUG_ASSERT(prm.count > 0); + DBUG_ASSERT(prm.count >= (uint)eqs.elements()); + will_be_correlated= prm.count > (uint)eqs.elements(); + if (upper_not && will_be_correlated) + goto out; + } + + if ((uint)eqs.elements() > (first_select->item_list.elements + + first_select->select_n_reserved)) + goto out; + /* It is simple query */ + DBUG_ASSERT(first_select->join->all_fields.elements == + first_select->item_list.elements); + + arena= thd->activate_stmt_arena_if_needed(&backup); + + while (first_select->item_list.elements > (uint)eqs.elements()) + { + first_select->item_list.pop(); + first_select->join->all_fields.elements--; + } + { + List_iterator<Item> it(first_select->item_list); + + for (uint i= 0; i < (uint)eqs.elements(); i++) + { + Item *item= it++; + eq_ref= eqs.at(i).eq_ref; + local_field= eqs.at(i).local_field; + outer_exp= eqs.at(i).outer_exp; + /* Add the field to the SELECT_LIST */ + if (item) + it.replace(local_field); + else + { + first_select->item_list.push_back(local_field, thd->mem_root); + first_select->join->all_fields.elements++; + } + first_select->ref_pointer_array[i]= (Item *)local_field; + + /* remove the parts from condition */ + if (!upper_not || !local_field->maybe_null) + *eq_ref= new (thd->mem_root) Item_int(thd, 1); + else + { + *eq_ref= new (thd->mem_root) + Item_func_isnotnull(thd, + new (thd->mem_root) + Item_field(thd, + ((Item_field*)(local_field-> + real_item()))->context, + ((Item_field*)(local_field-> + real_item()))->field)); + if((*eq_ref)->fix_fields(thd, (Item **)eq_ref)) + { + res= TRUE; + goto out; + } + } + outer_exp->fix_after_pullout(unit->outer_select(), &outer_exp, FALSE); + outer_exp->update_used_tables(); + outer.push_back(outer_exp, thd->mem_root); + } + } + + join->conds->update_used_tables(); + + /* make IN SUBQUERY and put outer_exp as left part */ + if (eqs.elements() == 1) + left_exp= outer_exp; + else + { + if (!(left_exp= new (thd->mem_root) Item_row(thd, outer))) + { + res= TRUE; + goto out; + } + } + + /* make EXISTS->IN permanet (see Item_subselect::init()) */ + set_exists_transformed(); + + first_select->select_limit= NULL; + if (!(in_subs= new (thd->mem_root) Item_in_subselect(thd, left_exp, + first_select))) + { + res= TRUE; + goto out; + } + in_subs->set_exists_transformed(); + optimizer->arguments()[0]= left_exp; + optimizer->arguments()[1]= in_subs; + in_subs->optimizer= optimizer; + DBUG_ASSERT(is_top_level_item() || + (upper_not && upper_not->is_top_level_item())); + in_subs->top_level_item(); + { + SELECT_LEX *current= thd->lex->current_select; + optimizer->reset_cache(); // renew cache, and we will not keep it + thd->lex->current_select= unit->outer_select(); + DBUG_ASSERT(optimizer); + if (optimizer->fix_left(thd)) + { + res= TRUE; + /* + We should not restore thd->lex->current_select because it will be + reset on exit from this procedure + */ + goto out; + } + /* + As far as Item_ref_in_optimizer do not substitute itself on fix_fields + we can use same item for all selects. + */ + in_subs->expr= new (thd->mem_root) + Item_direct_ref(thd, &first_select->context, + (Item**)optimizer->get_cache(), + (char *)"<no matter>", + (char *)in_left_expr_name); + if (in_subs->fix_fields(thd, optimizer->arguments() + 1)) + { + res= TRUE; + /* + We should not restore thd->lex->current_select because it will be + reset on exit from this procedure + */ + goto out; + } + { + /* Move dependence list */ + List_iterator_fast<Ref_to_outside> it(upper_refs); + Ref_to_outside *upper; + while ((upper= it++)) + { + uint i; + for (i= 0; i < (uint)eqs.elements(); i++) + if (eqs.at(i).outer_exp-> + walk(&Item::find_item_processor, TRUE, (uchar*)upper->item)) + break; + if (i == (uint)eqs.elements() && + (in_subs->upper_refs.push_back(upper, thd->stmt_arena->mem_root))) + goto out; + } + } + in_subs->update_used_tables(); + /* + The engine of the subquery is fixed so above fix_fields() is not + complete and should be fixed + */ + in_subs->upper_refs= upper_refs; + upper_refs.empty(); + thd->lex->current_select= current; + } + + DBUG_ASSERT(unit->item == in_subs); + DBUG_ASSERT(join == first_select->join); + /* + Fix dependency info + */ + in_subs->is_correlated= will_be_correlated; + if (!will_be_correlated) + { + first_select->uncacheable&= ~UNCACHEABLE_DEPENDENT_GENERATED; + unit->uncacheable&= ~UNCACHEABLE_DEPENDENT_GENERATED; + } + /* + set possible optimization strategies + */ + in_subs->emb_on_expr_nest= emb_on_expr_nest; + res= check_and_do_in_subquery_rewrites(join); + first_select->join->prepare_stage2(); + + first_select->fix_prepare_information(thd, &join->conds, &join->having); + + if (upper_not) + { + Item *exp; + if (eqs.elements() == 1) + { + exp= (optimizer->arguments()[0]->maybe_null ? + (Item*) new (thd->mem_root) + Item_cond_and(thd, + new (thd->mem_root) + Item_func_isnotnull(thd, + new (thd->mem_root) + Item_direct_ref(thd, + &unit->outer_select()->context, + optimizer->arguments(), + (char *)"<no matter>", + (char *)exists_outer_expr_name)), + optimizer) : + (Item *)optimizer); + } + else + { + List<Item> *and_list= new List<Item>; + if (!and_list) + { + res= TRUE; + goto out; + } + for (size_t i= 0; i < eqs.elements(); i++) + { + if (optimizer->arguments()[0]->maybe_null) + { + and_list-> + push_front(new (thd->mem_root) + Item_func_isnotnull(thd, + new (thd->mem_root) + Item_direct_ref(thd, + &unit->outer_select()->context, + optimizer->arguments()[0]->addr(i), + (char *)"<no matter>", + (char *)exists_outer_expr_name)), + thd->mem_root); + } + } + if (and_list->elements > 0) + { + and_list->push_front(optimizer, thd->mem_root); + exp= new (thd->mem_root) Item_cond_and(thd, *and_list); + } + else + exp= optimizer; + } + upper_not->arguments()[0]= exp; + if (!exp->fixed && exp->fix_fields(thd, upper_not->arguments())) + { + res= TRUE; + goto out; + } + } + +out: + thd->lex->current_select= save_select; + if (arena) + thd->restore_active_arena(arena, &backup); + DBUG_RETURN(res); +} + + /** Prepare IN/ALL/ANY/SOME subquery transformation and call the appropriate transformation function. @@ -2637,13 +3176,13 @@ Item_in_subselect::select_in_like_transformer(JOIN *join) arena= thd->activate_stmt_arena_if_needed(&backup); if (!optimizer) { - result= (!(optimizer= new Item_in_optimizer(left_expr_orig, this))); - if (result) + optimizer= new (thd->mem_root) Item_in_optimizer(thd, left_expr_orig, this); + if ((result= !optimizer)) goto out; } thd->lex->current_select= current->return_after_parsing(); - result= optimizer->fix_left(thd, optimizer->arguments()); + result= optimizer->fix_left(thd); thd->lex->current_select= current; if (changed) @@ -2697,18 +3236,27 @@ void Item_in_subselect::print(String *str, enum_query_type query_type) Item_subselect::print(str, query_type); } +bool Item_exists_subselect::fix_fields(THD *thd, Item **ref) +{ + DBUG_ENTER("Item_exists_subselect::fix_fields"); + if (exists_transformed) + DBUG_RETURN( !( (*ref)= new (thd->mem_root) Item_int(thd, 1))); + DBUG_RETURN(Item_subselect::fix_fields(thd, ref)); +} + bool Item_in_subselect::fix_fields(THD *thd_arg, Item **ref) { uint outer_cols_num; List<Item> *inner_cols; char const *save_where= thd_arg->where; + DBUG_ENTER("Item_in_subselect::fix_fields"); thd= thd_arg; DBUG_ASSERT(unit->thd == thd); if (test_strategy(SUBS_SEMI_JOIN)) - return !( (*ref)= new Item_int(1)); + DBUG_RETURN( !( (*ref)= new (thd->mem_root) Item_int(thd, 1)) ); thd->where= "IN/ALL/ANY subquery"; /* @@ -2760,22 +3308,23 @@ bool Item_in_subselect::fix_fields(THD *thd_arg, Item **ref) left_expr->fix_fields(thd_arg, &left_expr)) goto err; else - if (Item_subselect::fix_fields(thd_arg, ref)) - goto err; + if (Item_subselect::fix_fields(thd_arg, ref)) + goto err; fixed= TRUE; thd->where= save_where; - return FALSE; + DBUG_RETURN(FALSE); err: thd->where= save_where; - return TRUE; + DBUG_RETURN(TRUE); } -void Item_in_subselect::fix_after_pullout(st_select_lex *new_parent, Item **ref) +void Item_in_subselect::fix_after_pullout(st_select_lex *new_parent, + Item **ref, bool merge) { - left_expr->fix_after_pullout(new_parent, &left_expr); - Item_subselect::fix_after_pullout(new_parent, ref); + left_expr->fix_after_pullout(new_parent, &left_expr, merge); + Item_subselect::fix_after_pullout(new_parent, ref, merge); used_tables_cache |= left_expr->used_tables(); } @@ -2867,7 +3416,8 @@ bool Item_in_subselect::init_left_expr_cache() Cached_item *cur_item_cache= new_Cached_item(thd, left_expr->element_index(i), FALSE); - if (!cur_item_cache || left_expr_cache->push_front(cur_item_cache)) + if (!cur_item_cache || left_expr_cache->push_front(cur_item_cache, + thd->mem_root)) return TRUE; } return FALSE; @@ -3005,12 +3555,16 @@ bool subselect_union_engine::is_executed() const bool subselect_union_engine::no_rows() { - bool rows_present= false; - /* Check if we got any rows when reading UNION result from temp. table: */ - if (unit->fake_select_lex->join) - rows_present= test(!unit->fake_select_lex->join->send_records); - return rows_present; + if (unit->fake_select_lex) + { + JOIN *join= unit->fake_select_lex->join; + if (join) + return MY_TEST(!join->send_records); + return false; + } + return MY_TEST(!(((select_union_direct *)(unit->get_union_result())) + ->send_records)); } @@ -3152,9 +3706,9 @@ void subselect_engine::set_row(List<Item> &item_list, Item_cache **row) item->decimals= sel_item->decimals; item->unsigned_flag= sel_item->unsigned_flag; maybe_null= sel_item->maybe_null; - if (!(row[i]= Item_cache::get_cache(sel_item, sel_item->cmp_type()))) + if (!(row[i]= Item_cache::get_cache(thd, sel_item, sel_item->cmp_type()))) return; - row[i]->setup(sel_item); + row[i]->setup(thd, sel_item); //psergey-backport-timours: row[i]->store(sel_item); } if (item_list.elements > 1) @@ -3206,11 +3760,11 @@ int subselect_single_select_engine::exec() SELECT_LEX *save_select= thd->lex->current_select; thd->lex->current_select= select_lex; - if (!join->optimized) + if (join->optimization_state == JOIN::NOT_OPTIMIZED) { SELECT_LEX_UNIT *unit= select_lex->master_unit(); - unit->set_limit(unit->global_parameters); + unit->set_limit(unit->global_parameters()); if (join->optimize()) { thd->where= save_where; @@ -3849,7 +4403,7 @@ void subselect_uniquesubquery_engine::print(String *str) { KEY *key_info= tab->table->key_info + tab->ref.key; str->append(STRING_WITH_LEN("<primary_index_lookup>(")); - for (uint i= 0; i < key_info->key_parts; i++) + for (uint i= 0; i < key_info->user_defined_key_parts; i++) tab->ref.items[i]->print(str); str->append(STRING_WITH_LEN(" in ")); str->append(tab->table->s->table_name.str, tab->table->s->table_name.length); @@ -3907,6 +4461,7 @@ subselect_single_select_engine::change_result(Item_subselect *si, select_result_interceptor *res, bool temp) { + DBUG_ENTER("subselect_single_select_engine::change_result"); item= si; if (temp) { @@ -3927,7 +4482,7 @@ subselect_single_select_engine::change_result(Item_subselect *si, that would not require a lot of extra code that would be harder to manage than the current code. */ - return select_lex->join->change_result(res); + DBUG_RETURN(select_lex->join->change_result(res, NULL)); } @@ -4169,7 +4724,7 @@ subselect_hash_sj_engine::get_strategy_using_data() void subselect_hash_sj_engine::choose_partial_match_strategy( bool has_non_null_key, bool has_covering_null_row, - MY_BITMAP *partial_match_key_parts) + MY_BITMAP *partial_match_key_parts_arg) { ulonglong pm_buff_size; @@ -4220,7 +4775,7 @@ subselect_hash_sj_engine::choose_partial_match_strategy( { pm_buff_size= rowid_merge_buff_size(has_non_null_key, has_covering_null_row, - partial_match_key_parts); + partial_match_key_parts_arg); if (pm_buff_size > thd->variables.rowid_merge_buff_size) strategy= PARTIAL_MATCH_SCAN; } @@ -4295,13 +4850,13 @@ ulonglong subselect_hash_sj_engine::rowid_merge_buff_size( */ static my_bool -bitmap_init_memroot(MY_BITMAP *map, uint n_bits, MEM_ROOT *mem_root) +my_bitmap_init_memroot(MY_BITMAP *map, uint n_bits, MEM_ROOT *mem_root) { my_bitmap_map *bitmap_buf; if (!(bitmap_buf= (my_bitmap_map*) alloc_root(mem_root, bitmap_buffer_size(n_bits))) || - bitmap_init(map, bitmap_buf, n_bits, FALSE)) + my_bitmap_init(map, bitmap_buf, n_bits, FALSE)) return TRUE; bitmap_clear_all(map); return FALSE; @@ -4340,9 +4895,9 @@ bool subselect_hash_sj_engine::init(List<Item> *tmp_columns, uint subquery_id) DBUG_ENTER("subselect_hash_sj_engine::init"); - if (bitmap_init_memroot(&non_null_key_parts, tmp_columns->elements, + if (my_bitmap_init_memroot(&non_null_key_parts, tmp_columns->elements, thd->mem_root) || - bitmap_init_memroot(&partial_match_key_parts, tmp_columns->elements, + my_bitmap_init_memroot(&partial_match_key_parts, tmp_columns->elements, thd->mem_root)) DBUG_RETURN(TRUE); @@ -4367,7 +4922,7 @@ bool subselect_hash_sj_engine::init(List<Item> *tmp_columns, uint subquery_id) DBUG_RETURN(TRUE); } */ - if (!(result_sink= new select_materialize_with_stats)) + if (!(result_sink= new (thd->mem_root) select_materialize_with_stats(thd))) DBUG_RETURN(TRUE); char buf[32]; @@ -4407,7 +4962,8 @@ bool subselect_hash_sj_engine::init(List<Item> *tmp_columns, uint subquery_id) DBUG_ASSERT( tmp_table->s->uniques || tmp_table->key_info->key_length >= tmp_table->file->max_key_length() || - tmp_table->key_info->key_parts > tmp_table->file->max_key_parts()); + tmp_table->key_info->user_defined_key_parts > + tmp_table->file->max_key_parts()); free_tmp_table(thd, tmp_table); tmp_table= NULL; delete result; @@ -4421,7 +4977,7 @@ bool subselect_hash_sj_engine::init(List<Item> *tmp_columns, uint subquery_id) */ DBUG_ASSERT(tmp_table->s->keys == 1 && ((Item_in_subselect *) item)->left_expr->cols() == - tmp_table->key_info->key_parts); + tmp_table->key_info->user_defined_key_parts); if (make_semi_join_conds() || /* A unique_engine is used both for complete and partial matching. */ @@ -4437,7 +4993,7 @@ bool subselect_hash_sj_engine::init(List<Item> *tmp_columns, uint subquery_id) DBUG_RETURN(TRUE); /* Let our engine reuse this query plan for materialization. */ materialize_join= materialize_engine->join; - materialize_join->change_result(result); + materialize_join->change_result(result, NULL); DBUG_RETURN(FALSE); } @@ -4474,7 +5030,7 @@ bool subselect_hash_sj_engine::make_semi_join_conds() DBUG_ENTER("subselect_hash_sj_engine::make_semi_join_conds"); DBUG_ASSERT(semi_join_conds == NULL); - if (!(semi_join_conds= new Item_cond_and)) + if (!(semi_join_conds= new (thd->mem_root) Item_cond_and(thd))) DBUG_RETURN(TRUE); if (!(tmp_table_ref= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST)))) @@ -4494,14 +5050,17 @@ bool subselect_hash_sj_engine::make_semi_join_conds() for (uint i= 0; i < item_in->left_expr->cols(); i++) { - Item_func_eq *eq_cond; /* New equi-join condition for the current column. */ + /* New equi-join condition for the current column. */ + Item_func_eq *eq_cond; /* Item for the corresponding field from the materialized temp table. */ Item_field *right_col_item; - if (!(right_col_item= new Item_field(thd, context, tmp_table->field[i])) || - !(eq_cond= new Item_func_eq(item_in->left_expr->element_index(i), - right_col_item)) || - (((Item_cond_and*)semi_join_conds)->add(eq_cond))) + if (!(right_col_item= new (thd->mem_root) + Item_temptable_field(thd, context, tmp_table->field[i])) || + !(eq_cond= new (thd->mem_root) + Item_func_eq(thd, item_in->left_expr->element_index(i), + right_col_item)) || + (((Item_cond_and*)semi_join_conds)->add(eq_cond, thd->mem_root))) { delete semi_join_conds; semi_join_conds= NULL; @@ -4665,7 +5224,7 @@ double get_fanout_with_deps(JOIN *join, table_map tset) !tab->emb_sj_nest && tab->records_read != 0) { - fanout *= rows2double(tab->records_read); + fanout *= tab->records_read; } } return fanout; @@ -4780,12 +5339,13 @@ double get_post_group_estimate(JOIN* join, double join_op_rows) for (ORDER *order= join->group_list; order; order= order->next) { Item *item= order->item[0]; - if (item->used_tables() & RAND_TABLE_BIT) + table_map item_used_tables= item->used_tables(); + if (item_used_tables & RAND_TABLE_BIT) { /* Each join output record will be in its own group */ return join_op_rows; } - tables_in_group_list|= item->used_tables(); + tables_in_group_list|= item_used_tables; } tables_in_group_list &= ~PSEUDO_TABLE_BITS; @@ -4850,10 +5410,11 @@ int subselect_hash_sj_engine::exec() */ thd->lex->current_select= materialize_engine->select_lex; /* The subquery should be optimized, and materialized only once. */ - DBUG_ASSERT(materialize_join->optimized && !is_materialized); + DBUG_ASSERT(materialize_join->optimization_state == JOIN::OPTIMIZATION_DONE && + !is_materialized); materialize_join->exec(); - if ((res= test(materialize_join->error || thd->is_fatal_error || - thd->is_error()))) + if ((res= MY_TEST(materialize_join->error || thd->is_fatal_error || + thd->is_error()))) goto err; /* @@ -4879,6 +5440,7 @@ int subselect_hash_sj_engine::exec() item_in->reset(); item_in->make_const(); item_in->set_first_execution(); + thd->lex->current_select= save_select; DBUG_RETURN(FALSE); } @@ -4922,6 +5484,7 @@ int subselect_hash_sj_engine::exec() item_in->null_value= 1; item_in->make_const(); item_in->set_first_execution(); + thd->lex->current_select= save_select; DBUG_RETURN(FALSE); } @@ -4936,7 +5499,7 @@ int subselect_hash_sj_engine::exec() count_pm_keys= count_partial_match_columns - count_null_only_columns + (nn_key_parts ? 1 : 0); - choose_partial_match_strategy(test(nn_key_parts), + choose_partial_match_strategy(MY_TEST(nn_key_parts), has_covering_null_row, &partial_match_key_parts); DBUG_ASSERT(strategy == PARTIAL_MATCH_MERGE || @@ -5066,7 +5629,7 @@ Ordered_key::Ordered_key(uint keyid_arg, TABLE *tbl_arg, Item *search_key_arg, Ordered_key::~Ordered_key() { my_free(key_buff); - bitmap_free(&null_key); + my_bitmap_free(&null_key); } @@ -5109,9 +5672,9 @@ bool Ordered_key::init(MY_BITMAP *columns_to_index) { if (!bitmap_is_set(columns_to_index, i)) continue; - cur_tmp_field= new Item_field(tbl->field[i]); + cur_tmp_field= new (thd->mem_root) Item_field(thd, tbl->field[i]); /* Create the predicate (tmp_column[i] < outer_ref[i]). */ - fn_less_than= new Item_func_lt(cur_tmp_field, + fn_less_than= new (thd->mem_root) Item_func_lt(thd, cur_tmp_field, search_key->element_index(i)); fn_less_than->fix_fields(thd, (Item**) &fn_less_than); key_columns[cur_key_col]= cur_tmp_field; @@ -5143,9 +5706,9 @@ bool Ordered_key::init(int col_idx) key_columns= (Item_field**) thd->alloc(sizeof(Item_field*)); compare_pred= (Item_func_lt**) thd->alloc(sizeof(Item_func_lt*)); - key_columns[0]= new Item_field(tbl->field[col_idx]); + key_columns[0]= new (thd->mem_root) Item_field(thd, tbl->field[col_idx]); /* Create the predicate (tmp_column[i] < outer_ref[i]). */ - compare_pred[0]= new Item_func_lt(key_columns[0], + compare_pred[0]= new (thd->mem_root) Item_func_lt(thd, key_columns[0], search_key->element_index(col_idx)); compare_pred[0]->fix_fields(thd, (Item**)&compare_pred[0]); @@ -5167,7 +5730,7 @@ bool Ordered_key::alloc_keys_buffers() DBUG_ASSERT(key_buff_elements > 0); if (!(key_buff= (rownum_t*) my_malloc((size_t)(key_buff_elements * - sizeof(rownum_t)), MYF(MY_WME)))) + sizeof(rownum_t)), MYF(MY_WME | MY_THREAD_SPECIFIC)))) return TRUE; /* @@ -5176,7 +5739,7 @@ bool Ordered_key::alloc_keys_buffers() lookup offset. */ /* Notice that max_null_row is max array index, we need count, so +1. */ - if (bitmap_init(&null_key, NULL, (uint)(max_null_row + 1), FALSE)) + if (my_bitmap_init(&null_key, NULL, (uint)(max_null_row + 1), FALSE)) return TRUE; cur_key_idx= HA_POS_ERROR; @@ -5198,7 +5761,8 @@ int Ordered_key::cmp_keys_by_row_data(ha_rows a, ha_rows b) { uchar *rowid_a, *rowid_b; - int error, cmp_res; + int __attribute__((unused)) error; + int cmp_res; /* The length in bytes of the rowids (positions) of tmp_table. */ uint rowid_length= tbl->file->ref_length; @@ -5294,7 +5858,8 @@ int Ordered_key::cmp_key_with_search_key(rownum_t row_num) /* The length in bytes of the rowids (positions) of tmp_table. */ uint rowid_length= tbl->file->ref_length; uchar *cur_rowid= row_num_to_rowid + row_num * rowid_length; - int error, cmp_res; + int __attribute__((unused)) error; + int cmp_res; if ((error= tbl->file->ha_rnd_pos(tbl->record[0], cur_rowid))) { @@ -5464,7 +6029,7 @@ int subselect_partial_match_engine::exec() /* Search for a complete match. */ if ((lookup_res= lookup_engine->index_lookup())) { - /* An error occured during lookup(). */ + /* An error occurred during lookup(). */ item_in->value= 0; item_in->null_value= 0; return lookup_res; @@ -5593,7 +6158,7 @@ subselect_rowid_merge_engine::init(MY_BITMAP *non_null_key_parts, !(null_bitmaps= (MY_BITMAP**) thd->alloc(merge_keys_count * sizeof(MY_BITMAP*))) || !(row_num_to_rowid= (uchar*) my_malloc((size_t)(row_count * rowid_length), - MYF(MY_WME)))) + MYF(MY_WME | MY_THREAD_SPECIFIC)))) return TRUE; /* Create the only non-NULL key if there is any. */ @@ -5614,8 +6179,8 @@ subselect_rowid_merge_engine::init(MY_BITMAP *non_null_key_parts, */ if (!has_covering_null_columns) { - if (bitmap_init_memroot(&matching_keys, merge_keys_count, thd->mem_root) || - bitmap_init_memroot(&matching_outer_cols, merge_keys_count, thd->mem_root)) + if (my_bitmap_init_memroot(&matching_keys, merge_keys_count, thd->mem_root) || + my_bitmap_init_memroot(&matching_outer_cols, merge_keys_count, thd->mem_root)) return TRUE; /* @@ -5912,7 +6477,7 @@ bool subselect_rowid_merge_engine::partial_match() Do not add the non_null_key, since it was already processed above. */ bitmap_clear_all(&matching_outer_cols); - for (uint i= test(non_null_key); i < merge_keys_count; i++) + for (uint i= MY_TEST(non_null_key); i < merge_keys_count; i++) { DBUG_ASSERT(merge_keys[i]->get_column_count() == 1); if (merge_keys[i]->get_search_key(0)->null_value) @@ -5929,7 +6494,7 @@ bool subselect_rowid_merge_engine::partial_match() nullable columns (above we guarantee there is a match for the non-null coumns), the result is UNKNOWN. */ - if (count_nulls_in_search_key == merge_keys_count - test(non_null_key)) + if (count_nulls_in_search_key == merge_keys_count - MY_TEST(non_null_key)) { res= TRUE; goto end; @@ -6145,3 +6710,23 @@ end: void subselect_table_scan_engine::cleanup() { } + + +/* + Create an execution tracker for the expression cache we're using for this + subselect; add the tracker to the query plan. +*/ + +void Item_subselect::init_expr_cache_tracker(THD *thd) +{ + if(!expr_cache) + return; + + Explain_query *qw= thd->lex->explain; + DBUG_ASSERT(qw); + Explain_node *node= qw->get_node(unit->first_select()->select_number); + if (!node) + return; + DBUG_ASSERT(expr_cache->type() == Item::EXPR_CACHE_ITEM); + node->cache_tracker= ((Item_cache_wrapper *)expr_cache)->init_tracker(qw->mem_root); +} diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 75822ff8c6b..424ea6f0512 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -44,7 +44,8 @@ class Cached_item; /* base class for subselects */ -class Item_subselect :public Item_result_field +class Item_subselect :public Item_result_field, + protected Used_tables_and_const_cache { bool value_assigned; /* value already assigned to subselect */ bool own_engine; /* the engine was not taken from other Item_subselect */ @@ -53,16 +54,12 @@ protected: THD *thd; /* old engine if engine was changed */ subselect_engine *old_engine; - /* cache of used external tables */ - table_map used_tables_cache; /* allowed number of columns (1 for single value subqueries) */ uint max_columns; /* where subquery is placed */ enum_parsing_place parsing_place; /* work with 'substitution' */ bool have_to_be_excluded; - /* cache of constant state */ - bool const_item_cache; bool inside_first_fix_fields; bool done_first_fix_fields; @@ -131,7 +128,7 @@ public: enum subs_type {UNKNOWN_SUBS, SINGLEROW_SUBS, EXISTS_SUBS, IN_SUBS, ALL_SUBS, ANY_SUBS}; - Item_subselect(); + Item_subselect(THD *thd); virtual subs_type substype() { return UNKNOWN_SUBS; } bool is_in_predicate() @@ -172,7 +169,7 @@ public: } bool fix_fields(THD *thd, Item **ref); bool mark_as_dependent(THD *thd, st_select_lex *select, Item *item); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); void recalc_used_tables(st_select_lex *new_parent, bool after_pullout); virtual bool exec(); /* @@ -240,14 +237,17 @@ public: @return the SELECT_LEX structure associated with this Item */ st_select_lex* get_select_lex(); - const char *func_name() const { DBUG_ASSERT(0); return "subselect"; } virtual bool expr_cache_is_needed(THD *); virtual void get_cache_parameters(List<Item> ¶meters); virtual bool is_subquery_processor (uchar *opt_arg) { return 1; } + bool exists2in_processor(uchar *opt_arg) { return 0; } bool limit_index_condition_pushdown_processor(uchar *opt_arg) { return TRUE; - } + } + + void init_expr_cache_tracker(THD *thd); + friend class select_result_interceptor; friend class Item_in_optimizer; @@ -268,8 +268,8 @@ class Item_singlerow_subselect :public Item_subselect protected: Item_cache *value, **row; public: - Item_singlerow_subselect(st_select_lex *select_lex); - Item_singlerow_subselect() :Item_subselect(), value(0), row (0) + Item_singlerow_subselect(THD *thd_arg, st_select_lex *select_lex); + Item_singlerow_subselect(THD *thd_arg): Item_subselect(thd_arg), value(0), row (0) {} void cleanup(); @@ -311,7 +311,7 @@ public: */ st_select_lex* invalidate_and_restore_select_lex(); - Item* expr_cache_insert_transformer(uchar *thd_arg); + Item* expr_cache_insert_transformer(THD *thd, uchar *unused); friend class select_singlerow_subselect; }; @@ -339,13 +339,35 @@ public: class Item_exists_subselect :public Item_subselect { protected: + Item_func_not *upper_not; bool value; /* value of this item (boolean: exists/not-exists) */ + bool abort_on_null; void init_length_and_dec(); + bool select_prepare_to_be_in(); public: - Item_exists_subselect(st_select_lex *select_lex); - Item_exists_subselect(): Item_subselect() {} + /* + Used by subquery optimizations to keep track about in which clause this + subquery predicate is located: + NO_JOIN_NEST - the predicate is an AND-part of the WHERE + join nest pointer - the predicate is an AND-part of ON expression + of a join nest + NULL - for all other locations + */ + TABLE_LIST *emb_on_expr_nest; + /** + Reference on the Item_in_optimizer wrapper of this subquery + */ + Item_in_optimizer *optimizer; + /* true if we got this from EXISTS or to IN */ + bool exists_transformed; + + Item_exists_subselect(THD *thd_arg, st_select_lex *select_lex); + Item_exists_subselect(THD *thd_arg): + Item_subselect(thd_arg), upper_not(NULL), abort_on_null(0), + emb_on_expr_nest(NULL), optimizer(0), exists_transformed(0) + {} subs_type substype() { return EXISTS_SUBS; } void reset() @@ -361,10 +383,23 @@ public: String *val_str(String*); my_decimal *val_decimal(my_decimal *); bool val_bool(); + bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec(); virtual void print(String *str, enum_query_type query_type); + bool select_transformer(JOIN *join); + void top_level_item() { abort_on_null=1; } + inline bool is_top_level_item() { return abort_on_null; } + bool exists2in_processor(uchar *opt_arg); - Item* expr_cache_insert_transformer(uchar *thd_arg); + Item* expr_cache_insert_transformer(THD *thd, uchar *unused); + + void mark_as_condition_AND_part(TABLE_LIST *embedding) + { + emb_on_expr_nest= embedding; + } + virtual void under_not(Item_func_not *upper) { upper_not= upper; }; + + void set_exists_transformed() { exists_transformed= TRUE; } friend class select_exists_subselect; friend class subselect_uniquesubquery_engine; @@ -425,11 +460,8 @@ protected: */ Item *expr; bool was_null; - bool abort_on_null; /* A bitmap of possible execution strategies for an IN predicate. */ uchar in_strategy; -public: - Item_in_optimizer *optimizer; protected: /* Used to trigger on/off conditions that were pushed down to subselect */ bool *pushed_cond_guards; @@ -457,16 +489,7 @@ public: Item *left_expr_orig; /* Priority of this predicate in the convert-to-semi-join-nest process. */ int sj_convert_priority; - /* - Used by subquery optimizations to keep track about in which clause this - subquery predicate is located: - NO_JOIN_NEST - the predicate is an AND-part of the WHERE - join nest pointer - the predicate is an AND-part of ON expression - of a join nest - NULL - for all other locations - */ - TABLE_LIST *emb_on_expr_nest; - /* May be TRUE only for the candidates to semi-join conversion */ + /* May be TRUE only for the candidates to semi-join conversion */ bool do_not_convert_to_sj; /* Types of left_expr and subquery's select list allow to perform subquery @@ -536,7 +559,9 @@ public: */ Item *original_item() { - return is_flattenable_semijoin ? (Item*)this : (Item*)optimizer; + return (is_flattenable_semijoin && !exists_transformed ? + (Item*)this : + (Item*)optimizer); } bool *get_cond_guard(int i) @@ -548,18 +573,16 @@ public: if ( pushed_cond_guards) pushed_cond_guards[i]= v; } - bool have_guarded_conds() { return test(pushed_cond_guards); } + bool have_guarded_conds() { return MY_TEST(pushed_cond_guards); } Item_func_not_all *upper_item; // point on NOT/NOP before ALL/SOME subquery - Item_in_subselect(Item * left_expr, st_select_lex *select_lex); - Item_in_subselect() - :Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE), - abort_on_null(0), in_strategy(SUBS_NOT_TRANSFORMED), optimizer(0), - pushed_cond_guards(NULL), func(NULL), emb_on_expr_nest(NULL), - do_not_convert_to_sj(FALSE), is_jtbm_merged(FALSE), - is_jtbm_const_tab(FALSE), upper_item(0) - {} + Item_in_subselect(THD *thd_arg, Item * left_expr, st_select_lex *select_lex); + Item_in_subselect(THD *thd_arg): + Item_exists_subselect(thd_arg), left_expr_cache(0), first_execution(TRUE), + in_strategy(SUBS_NOT_TRANSFORMED), + pushed_cond_guards(NULL), func(NULL), do_not_convert_to_sj(FALSE), + is_jtbm_merged(FALSE), is_jtbm_const_tab(FALSE), upper_item(0) {} void cleanup(); subs_type substype() { return IN_SUBS; } void reset() @@ -580,13 +603,11 @@ public: my_decimal *val_decimal(my_decimal *); void update_null_value () { (void) val_bool(); } bool val_bool(); - void top_level_item() { abort_on_null=1; } - inline bool is_top_level_item() { return abort_on_null; } bool test_limit(st_select_lex_unit *unit); virtual void print(String *str, enum_query_type query_type); bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec(); - void fix_after_pullout(st_select_lex *new_parent, Item **ref); + void fix_after_pullout(st_select_lex *new_parent, Item **ref, bool merge); bool const_item() const { return Item_subselect::const_item() && left_expr->const_item(); @@ -598,6 +619,7 @@ public: void set_first_execution() { if (first_execution) first_execution= FALSE; } bool expr_cache_is_needed(THD *thd); inline bool left_expr_has_null(); + void disable_cond_guard_for_const_null_left_expr(int i) { if (left_expr->const_item() && !left_expr->is_expensive()) @@ -606,23 +628,18 @@ public: set_cond_guard_var(i,FALSE); } } - + int optimize(double *out_rows, double *cost); - /* + /* Return the identifier that we could use to identify the subquery for the user. */ int get_identifier(); - void mark_as_condition_AND_part(TABLE_LIST *embedding) - { - emb_on_expr_nest= embedding; - } - void block_conversion_to_sj () { do_not_convert_to_sj= TRUE; } bool test_strategy(uchar strategy) - { return test(in_strategy & strategy); } + { return MY_TEST(in_strategy & strategy); } /** Test that the IN strategy was chosen for execution. This is so @@ -642,13 +659,16 @@ public: } bool is_set_strategy() - { return test(in_strategy & SUBS_STRATEGY_CHOSEN); } + { return MY_TEST(in_strategy & SUBS_STRATEGY_CHOSEN); } bool has_strategy() { return in_strategy != SUBS_NOT_TRANSFORMED; } void add_strategy (uchar strategy) { + DBUG_ENTER("Item_in_subselect::add_strategy"); + DBUG_PRINT("enter", ("current: %u add: %u", + (uint) in_strategy, (uint) strategy)); DBUG_ASSERT(strategy != SUBS_NOT_TRANSFORMED); DBUG_ASSERT(!(strategy & SUBS_STRATEGY_CHOSEN)); /* @@ -658,16 +678,25 @@ public: DBUG_ASSERT(!(in_strategy & SUBS_STRATEGY_CHOSEN)); */ in_strategy|= strategy; + DBUG_VOID_RETURN; } void reset_strategy(uchar strategy) { + DBUG_ENTER("Item_in_subselect::reset_strategy"); + DBUG_PRINT("enter", ("current: %u new: %u", + (uint) in_strategy, (uint) strategy)); DBUG_ASSERT(strategy != SUBS_NOT_TRANSFORMED); in_strategy= strategy; + DBUG_VOID_RETURN; } void set_strategy(uchar strategy) { + DBUG_ENTER("Item_in_subselect::set_strategy"); + DBUG_PRINT("enter", ("current: %u set: %u", + (uint) in_strategy, + (uint) (SUBS_STRATEGY_CHOSEN | strategy))); /* Check that only one strategy is set for execution. */ DBUG_ASSERT(strategy == SUBS_SEMI_JOIN || strategy == SUBS_IN_TO_EXISTS || @@ -677,14 +706,27 @@ public: strategy == SUBS_MAXMIN_INJECTED || strategy == SUBS_MAXMIN_ENGINE); in_strategy= (SUBS_STRATEGY_CHOSEN | strategy); + DBUG_VOID_RETURN; + } + + bool walk(Item_processor processor, bool walk_subquery, uchar *arg) + { + return left_expr->walk(processor, walk_subquery, arg) || + Item_subselect::walk(processor, walk_subquery, arg); } + bool exists2in_processor(uchar *opt_arg __attribute__((unused))) + { + return 0; + }; + friend class Item_ref_null_helper; friend class Item_is_not_null_test; friend class Item_in_optimizer; friend class subselect_indexsubquery_engine; friend class subselect_hash_sj_engine; friend class subselect_partial_match_engine; + friend class Item_exists_subselect; }; @@ -695,7 +737,8 @@ public: chooser_compare_func_creator func_creator; bool all; - Item_allany_subselect(Item * left_expr, chooser_compare_func_creator fc, + Item_allany_subselect(THD *thd_arg, Item * left_expr, + chooser_compare_func_creator fc, st_select_lex *select_lex, bool all); void cleanup(); @@ -969,7 +1012,7 @@ public: This function is actually defined in sql_parse.cc, but it depends on chooser_compare_func_creator defined in this file. */ -Item * all_any_subquery_creator(Item *left_expr, +Item * all_any_subquery_creator(THD *thd, Item *left_expr, chooser_compare_func_creator cmp, bool all, SELECT_LEX *select_lex); @@ -1022,9 +1065,9 @@ public: Name_resolution_context *semi_join_conds_context; - subselect_hash_sj_engine(THD *thd, Item_subselect *in_predicate, + subselect_hash_sj_engine(THD *thd_arg, Item_subselect *in_predicate, subselect_single_select_engine *old_engine) - : subselect_engine(in_predicate, NULL), + : subselect_engine(in_predicate, NULL), tmp_table(NULL), is_materialized(FALSE), materialize_engine(old_engine), materialize_join(NULL), semi_join_conds(NULL), lookup_engine(NULL), count_partial_match_columns(0), count_null_only_columns(0), diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 22fbe2bacfe..8ba5579646d 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -26,6 +26,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include "sql_select.h" @@ -36,7 +37,7 @@ ulonglong Item_sum::ram_limitation(THD *thd) { - return min(thd->variables.tmp_table_size, + return MY_MIN(thd->variables.tmp_table_size, thd->variables.max_heap_table_size); } @@ -75,7 +76,7 @@ bool Item_sum::init_sum_func_check(THD *thd) } if (!(thd->lex->allow_sum_func & curr_sel->name_visibility_map)) { - my_message(ER_INVALID_GROUP_FUNC_USE, ER(ER_INVALID_GROUP_FUNC_USE), + my_message(ER_INVALID_GROUP_FUNC_USE, ER_THD(thd, ER_INVALID_GROUP_FUNC_USE), MYF(0)); return TRUE; } @@ -199,7 +200,8 @@ bool Item_sum::check_sum_func(THD *thd, Item **ref) invalid= aggr_level <= max_sum_func_level; if (invalid) { - my_message(ER_INVALID_GROUP_FUNC_USE, ER(ER_INVALID_GROUP_FUNC_USE), + my_message(ER_INVALID_GROUP_FUNC_USE, + ER_THD(thd, ER_INVALID_GROUP_FUNC_USE), MYF(0)); return TRUE; } @@ -270,7 +272,7 @@ bool Item_sum::check_sum_func(THD *thd, Item **ref) Let upper function decide whether this field is a non aggregated one. */ - in_sum_func->outer_fields.push_back(field); + in_sum_func->outer_fields.push_back(field, thd->mem_root); } else sel->set_non_agg_field_used(true); @@ -280,7 +282,7 @@ bool Item_sum::check_sum_func(THD *thd, Item **ref) !sel->group_list.elements) { my_message(ER_MIX_OF_GROUP_FUNC_AND_FIELDS, - ER(ER_MIX_OF_GROUP_FUNC_AND_FIELDS), MYF(0)); + ER_THD(thd, ER_MIX_OF_GROUP_FUNC_AND_FIELDS), MYF(0)); return TRUE; } } @@ -378,6 +380,16 @@ bool Item_sum::register_sum_func(THD *thd, Item **ref) sl->master_unit()->item->with_sum_func= 1; } thd->lex->current_select->mark_as_dependent(thd, aggr_sel, NULL); + + if ((thd->lex->describe & DESCRIBE_EXTENDED) && aggr_sel) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_WARN_AGGFUNC_DEPENDENCE, + ER_THD(thd, ER_WARN_AGGFUNC_DEPENDENCE), + func_name(), + thd->lex->current_select->select_number, + aggr_sel->select_number); + } return FALSE; } @@ -389,26 +401,19 @@ bool Item_sum::collect_outer_ref_processor(uchar *param) if ((ds= depended_from()) && ds->nest_level_base == prm->nest_level_base && ds->nest_level < prm->nest_level) - prm->parameters->add_unique(this, &cmp_items); + { + if (prm->collect) + prm->parameters->add_unique(this, &cmp_items); + else + prm->count++; + } return FALSE; } -Item_sum::Item_sum(List<Item> &list) :arg_count(list.elements), - forced_const(FALSE) +Item_sum::Item_sum(THD *thd, List<Item> &list): Item_func_or_sum(thd, list) { - if ((args=(Item**) sql_alloc(sizeof(Item*)*arg_count))) - { - uint i=0; - List_iterator_fast<Item> li(list); - Item *item; - - while ((item=li++)) - { - args[i++]= item; - } - } - if (!(orig_args= (Item **) sql_alloc(sizeof(Item *) * arg_count))) + if (!(orig_args= (Item **) thd->alloc(sizeof(Item *) * arg_count))) { args= NULL; } @@ -423,27 +428,21 @@ Item_sum::Item_sum(List<Item> &list) :arg_count(list.elements), */ Item_sum::Item_sum(THD *thd, Item_sum *item): - Item_result_field(thd, item), + Item_func_or_sum(thd, item), aggr_sel(item->aggr_sel), nest_level(item->nest_level), aggr_level(item->aggr_level), quick_group(item->quick_group), - arg_count(item->arg_count), orig_args(NULL), - used_tables_cache(item->used_tables_cache), - forced_const(item->forced_const) + orig_args(NULL) { if (arg_count <= 2) { - args=tmp_args; orig_args=tmp_orig_args; } else { - if (!(args= (Item**) thd->alloc(sizeof(Item*)*arg_count))) - return; if (!(orig_args= (Item**) thd->alloc(sizeof(Item*)*arg_count))) return; } - memcpy(args, item->args, sizeof(Item*)*arg_count); memcpy(orig_args, item->orig_args, sizeof(Item*)*arg_count); init_aggregator(); with_distinct= item->with_distinct; @@ -457,6 +456,7 @@ void Item_sum::mark_as_sum_func() SELECT_LEX *cur_select= current_thd->lex->current_select; cur_select->n_sum_items++; cur_select->with_sum_func= 1; + const_item_cache= false; with_sum_func= 1; with_field= 0; } @@ -498,7 +498,7 @@ Item *Item_sum::get_tmp_table_item(THD *thd) if (arg->type() == Item::FIELD_ITEM) ((Item_field*) arg)->field= result_field_tmp++; else - sum_item->args[i]= new Item_field(result_field_tmp++); + sum_item->args[i]= new (thd->mem_root) Item_temptable_field(thd, result_field_tmp++); } } } @@ -506,47 +506,27 @@ Item *Item_sum::get_tmp_table_item(THD *thd) } -bool Item_sum::walk (Item_processor processor, bool walk_subquery, - uchar *argument) -{ - if (arg_count) - { - Item **arg,**arg_end; - for (arg= args, arg_end= args+arg_count; arg != arg_end; arg++) - { - if ((*arg)->walk(processor, walk_subquery, argument)) - return 1; - } - } - return (this->*processor)(argument); -} - - -Field *Item_sum::create_tmp_field(bool group, TABLE *table, - uint convert_blob_length) +Field *Item_sum::create_tmp_field(bool group, TABLE *table) { Field *UNINIT_VAR(field); + MEM_ROOT *mem_root= table->in_use->mem_root; + switch (result_type()) { case REAL_RESULT: - field= new Field_double(max_length, maybe_null, name, decimals, TRUE); + field= new (mem_root) + Field_double(max_length, maybe_null, name, decimals, TRUE); break; case INT_RESULT: - field= new Field_longlong(max_length, maybe_null, name, unsigned_flag); + field= new (mem_root) + Field_longlong(max_length, maybe_null, name, unsigned_flag); break; case STRING_RESULT: - if (max_length/collation.collation->mbmaxlen <= 255 || - convert_blob_length > Field_varstring::MAX_SIZE || - !convert_blob_length) - return make_string_field(table); - field= new Field_varstring(convert_blob_length, maybe_null, - name, table->s, collation.collation); - break; + return make_string_field(table); case DECIMAL_RESULT: - field= Field_new_decimal::create_from_item(this); + field= Field_new_decimal::create_from_item(mem_root, this); break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: // This case should never be choosen DBUG_ASSERT(0); return 0; @@ -559,7 +539,7 @@ Field *Item_sum::create_tmp_field(bool group, TABLE *table, void Item_sum::update_used_tables () { - if (!forced_const) + if (!Item_sum::const_item()) { used_tables_cache= 0; for (uint i=0 ; i < arg_count ; i++) @@ -631,9 +611,13 @@ void Item_sum::cleanup() aggr= NULL; } Item_result_field::cleanup(); - forced_const= FALSE; + const_item_cache= false; } +Item *Item_sum::result_item(THD *thd, Field *field) +{ + return new (thd->mem_root) Item_field(thd, field); +} /** Compare keys consisting of single field that cannot be compared as binary. @@ -650,13 +634,24 @@ void Item_sum::cleanup() @retval > 0 if key1 > key2 */ -static int simple_str_key_cmp(void* arg, uchar* key1, uchar* key2) +int simple_str_key_cmp(void* arg, uchar* key1, uchar* key2) { Field *f= (Field*) arg; return f->cmp(key1, key2); } +C_MODE_START + +int count_distinct_walk(void *elem, element_count count, void *arg) +{ + (*((ulonglong*)arg))++; + return 0; +} + +C_MODE_END + + /** Correctly compare composite keys. @@ -724,7 +719,7 @@ C_MODE_START /* Declarations for auxilary C-callbacks */ -static int simple_raw_key_cmp(void* arg, const void* key1, const void* key2) +int simple_raw_key_cmp(void* arg, const void* key1, const void* key2) { return memcmp(key1, key2, *(uint *) arg); } @@ -786,7 +781,7 @@ bool Aggregator_distinct::setup(THD *thd) for (uint i=0; i < item_sum->get_arg_count() ; i++) { Item *item=item_sum->get_arg(i); - if (list.push_back(item)) + if (list.push_back(item, thd->mem_root)) return TRUE; // End of memory if (item->const_item() && item->is_null()) always_null= true; @@ -907,7 +902,7 @@ bool Aggregator_distinct::setup(THD *thd) PS/SP. Hence all further allocations are performed in the runtime mem_root. */ - if (field_list.push_back(&field_def)) + if (field_list.push_back(&field_def, thd->mem_root)) DBUG_RETURN(TRUE); item_sum->null_value= item_sum->maybe_null= 1; @@ -918,7 +913,7 @@ bool Aggregator_distinct::setup(THD *thd) arg= item_sum->get_arg(0); if (arg->const_item()) { - (void) arg->val_int(); + (void) arg->is_null(); if (arg->null_value) always_null= true; } @@ -1194,36 +1189,37 @@ Item_sum_hybrid::fix_fields(THD *thd, Item **ref) if ((!item->fixed && item->fix_fields(thd, args)) || (item= args[0])->check_cols(1)) return TRUE; - decimals=item->decimals; + Type_std_attributes::set(args[0]); with_subselect= args[0]->with_subselect; with_param= args[0]->with_param; - switch (hybrid_type= item->result_type()) { + Item *item2= item->real_item(); + if (item2->type() == Item::FIELD_ITEM) + set_handler_by_field_type(((Item_field*) item2)->field->type()); + else if (item->cmp_type() == TIME_RESULT) + set_handler_by_field_type(item2->field_type()); + else + set_handler_by_result_type(item2->result_type(), + max_length, collation.collation); + + switch (Item_sum_hybrid::result_type()) { case INT_RESULT: case DECIMAL_RESULT: case STRING_RESULT: - max_length= item->max_length; break; case REAL_RESULT: max_length= float_length(decimals); break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); }; - setup_hybrid(args[0], NULL); + setup_hybrid(thd, args[0], NULL); /* MIN/MAX can return NULL for empty set indepedent of the used column */ maybe_null= 1; - unsigned_flag=item->unsigned_flag; result_field=0; null_value=1; fix_length_and_dec(); - item= item->real_item(); - if (item->type() == Item::FIELD_ITEM) - hybrid_field_type= ((Item_field*) item)->field->type(); - else - hybrid_field_type= Item::field_type(); if (check_sum_func(thd, ref)) return TRUE; @@ -1251,18 +1247,18 @@ Item_sum_hybrid::fix_fields(THD *thd, Item **ref) and Item_sum_min::add() to use different values! */ -void Item_sum_hybrid::setup_hybrid(Item *item, Item *value_arg) +void Item_sum_hybrid::setup_hybrid(THD *thd, Item *item, Item *value_arg) { - if (!(value= Item_cache::get_cache(item, item->cmp_type()))) + if (!(value= Item_cache::get_cache(thd, item, item->cmp_type()))) return; - value->setup(item); + value->setup(thd, item); value->store(value_arg); /* Don't cache value, as it will change */ if (!item->const_item()) value->set_used_tables(RAND_TABLE_BIT); - if (!(arg_cache= Item_cache::get_cache(item, item->cmp_type()))) + if (!(arg_cache= Item_cache::get_cache(thd, item, item->cmp_type()))) return; - arg_cache->setup(item); + arg_cache->setup(thd, item); /* Don't cache value, as it will change */ if (!item->const_item()) arg_cache->set_used_tables(RAND_TABLE_BIT); @@ -1273,40 +1269,43 @@ void Item_sum_hybrid::setup_hybrid(Item *item, Item *value_arg) } -Field *Item_sum_hybrid::create_tmp_field(bool group, TABLE *table, - uint convert_blob_length) +Field *Item_sum_hybrid::create_tmp_field(bool group, TABLE *table) { Field *field; + MEM_ROOT *mem_root; + if (args[0]->type() == Item::FIELD_ITEM) { field= ((Item_field*) args[0])->field; - - if ((field= create_tmp_field_from_field(current_thd, field, name, table, - NULL, convert_blob_length))) + + if ((field= create_tmp_field_from_field(table->in_use, field, name, table, + NULL))) field->flags&= ~NOT_NULL_FLAG; return field; } + /* DATE/TIME fields have STRING_RESULT result types. In order to preserve field type, it's needed to handle DATE/TIME fields creations separately. */ + mem_root= table->in_use->mem_root; switch (args[0]->field_type()) { case MYSQL_TYPE_DATE: - field= new Field_newdate(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE, - name, collation.collation); + field= new (mem_root) + Field_newdate(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE, name); break; case MYSQL_TYPE_TIME: - field= new_Field_time(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE, - name, decimals, collation.collation); + field= new_Field_time(mem_root, 0, maybe_null ? (uchar*)"" : 0, 0, + Field::NONE, name, decimals); break; case MYSQL_TYPE_TIMESTAMP: case MYSQL_TYPE_DATETIME: - field= new_Field_datetime(0, maybe_null ? (uchar*)"" : 0, 0, Field::NONE, - name, decimals, collation.collation); + field= new_Field_datetime(mem_root, 0, maybe_null ? (uchar*)"" : 0, 0, + Field::NONE, name, decimals); break; default: - return Item_sum::create_tmp_field(group, table, convert_blob_length); + return Item_sum::create_tmp_field(group, table); } if (field) field->init(table); @@ -1383,7 +1382,6 @@ void Item_sum_sum::fix_length_and_dec() break; } case ROW_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } DBUG_PRINT("info", ("Type: %s (%d, %d)", @@ -1434,8 +1432,7 @@ longlong Item_sum_sum::val_int() &result); return result; } - bool error; - return double_to_longlong(val_real(), unsigned_flag, &error); + return val_int_from_real(); } @@ -1643,18 +1640,18 @@ void Item_sum_avg::fix_length_and_dec() if (hybrid_type == DECIMAL_RESULT) { int precision= args[0]->decimal_precision() + prec_increment; - decimals= min(args[0]->decimals + prec_increment, DECIMAL_MAX_SCALE); + decimals= MY_MIN(args[0]->decimals + prec_increment, DECIMAL_MAX_SCALE); max_length= my_decimal_precision_to_length_no_truncation(precision, decimals, unsigned_flag); - f_precision= min(precision+DECIMAL_LONGLONG_DIGITS, DECIMAL_MAX_PRECISION); + f_precision= MY_MIN(precision+DECIMAL_LONGLONG_DIGITS, DECIMAL_MAX_PRECISION); f_scale= args[0]->decimals; dec_bin_size= my_decimal_get_binary_size(f_precision, f_scale); } else { - decimals= min(args[0]->decimals + prec_increment, NOT_FIXED_DEC); - max_length= min(args[0]->max_length + prec_increment, float_length(decimals)); + decimals= MY_MIN(args[0]->decimals + prec_increment, NOT_FIXED_DEC); + max_length= MY_MIN(args[0]->max_length + prec_increment, float_length(decimals)); } } @@ -1665,10 +1662,11 @@ Item *Item_sum_avg::copy_or_same(THD* thd) } -Field *Item_sum_avg::create_tmp_field(bool group, TABLE *table, - uint convert_blob_len) +Field *Item_sum_avg::create_tmp_field(bool group, TABLE *table) { Field *field; + MEM_ROOT *mem_root= table->in_use->mem_root; + if (group) { /* @@ -1676,14 +1674,16 @@ Field *Item_sum_avg::create_tmp_field(bool group, TABLE *table, The easiest way is to do this is to store both value in a string and unpack on access. */ - field= new Field_string(((hybrid_type == DECIMAL_RESULT) ? - dec_bin_size : sizeof(double)) + sizeof(longlong), - 0, name, &my_charset_bin); + field= new (mem_root) + Field_string(((hybrid_type == DECIMAL_RESULT) ? + dec_bin_size : sizeof(double)) + sizeof(longlong), + 0, name, &my_charset_bin); } else if (hybrid_type == DECIMAL_RESULT) - field= Field_new_decimal::create_from_item(this); + field= Field_new_decimal::create_from_item(mem_root, this); else - field= new Field_double(max_length, maybe_null, name, decimals, TRUE); + field= new (mem_root) Field_double(max_length, maybe_null, name, decimals, + TRUE); if (field) field->init(table); return field; @@ -1765,6 +1765,18 @@ double Item_sum_std::val_real() { DBUG_ASSERT(fixed == 1); double nr= Item_sum_variance::val_real(); + if (isnan(nr)) + { + /* + variance_fp_recurrence_next() can overflow in some cases and return "nan": + + CREATE OR REPLACE TABLE t1 (a DOUBLE); + INSERT INTO t1 VALUES (1.7e+308), (-1.7e+308), (0); + SELECT STDDEV_SAMP(a) FROM t1; + */ + null_value= true; // Convert "nan" to NULL + return 0; + } if (my_isinf(nr)) return DBL_MAX; DBUG_ASSERT(nr >= 0.0); @@ -1777,6 +1789,12 @@ Item *Item_sum_std::copy_or_same(THD* thd) } +Item *Item_sum_std::result_item(THD *thd, Field *field) +{ + return new (thd->mem_root) Item_std_field(thd, this); +} + + /* Variance */ @@ -1806,8 +1824,9 @@ static void variance_fp_recurrence_next(double *m, double *s, ulonglong *count, else { double m_kminusone= *m; - *m= m_kminusone + (nr - m_kminusone) / (double) *count; - *s= *s + (nr - m_kminusone) * (nr - *m); + volatile double diff= nr - m_kminusone; + *m= m_kminusone + diff / (double) *count; + *s= *s + diff * (nr - *m); } } @@ -1826,7 +1845,7 @@ static double variance_fp_recurrence_result(double s, ulonglong count, bool is_s Item_sum_variance::Item_sum_variance(THD *thd, Item_sum_variance *item): - Item_sum_num(thd, item), hybrid_type(item->hybrid_type), + Item_sum_num(thd, item), count(item->count), sample(item->sample), prec_increment(item->prec_increment) { @@ -1847,18 +1866,17 @@ void Item_sum_variance::fix_length_and_dec() type of the result is an implementation-defined aproximate numeric type. */ - hybrid_type= REAL_RESULT; switch (args[0]->result_type()) { case REAL_RESULT: case STRING_RESULT: - decimals= min(args[0]->decimals + 4, NOT_FIXED_DEC); + decimals= MY_MIN(args[0]->decimals + 4, NOT_FIXED_DEC); break; case INT_RESULT: case DECIMAL_RESULT: { int precision= args[0]->decimal_precision()*2 + prec_increment; - decimals= min(args[0]->decimals + prec_increment, DECIMAL_MAX_SCALE); + decimals= MY_MIN(args[0]->decimals + prec_increment, DECIMAL_MAX_SCALE); max_length= my_decimal_precision_to_length_no_truncation(precision, decimals, unsigned_flag); @@ -1867,7 +1885,6 @@ void Item_sum_variance::fix_length_and_dec() } case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } DBUG_PRINT("info", ("Type: REAL_RESULT (%d, %d)", max_length, (int)decimals)); @@ -1886,8 +1903,7 @@ Item *Item_sum_variance::copy_or_same(THD* thd) If we're grouping, then we need some space to serialize variables into, to pass around. */ -Field *Item_sum_variance::create_tmp_field(bool group, TABLE *table, - uint convert_blob_len) +Field *Item_sum_variance::create_tmp_field(bool group, TABLE *table) { Field *field; if (group) @@ -2007,6 +2023,11 @@ void Item_sum_variance::update_field() } +Item *Item_sum_variance::result_item(THD *thd, Field *field) +{ + return new (thd->mem_root) Item_variance_field(thd, this); +} + /* min & max */ void Item_sum_hybrid::clear() @@ -2067,7 +2088,6 @@ void Item_sum_hybrid::cleanup() { DBUG_ENTER("Item_sum_hybrid::cleanup"); Item_sum::cleanup(); - forced_const= FALSE; if (cmp) delete cmp; cmp= 0; @@ -2106,7 +2126,7 @@ void Item_sum_hybrid::restore_to_before_no_rows_in_result() Item *Item_sum_min::copy_or_same(THD* thd) { Item_sum_min *item= new (thd->mem_root) Item_sum_min(thd, this); - item->setup_hybrid(args[0], value); + item->setup_hybrid(thd, args[0], value); return item; } @@ -2129,7 +2149,7 @@ bool Item_sum_min::add() Item *Item_sum_max::copy_or_same(THD* thd) { Item_sum_max *item= new (thd->mem_root) Item_sum_max(thd, this); - item->setup_hybrid(args[0], value); + item->setup_hybrid(thd, args[0], value); return item; } @@ -2230,7 +2250,7 @@ void Item_sum_num::reset_field() void Item_sum_hybrid::reset_field() { - switch(hybrid_type) { + switch(Item_sum_hybrid::result_type()) { case STRING_RESULT: { char buff[MAX_FIELD_WIDTH]; @@ -2305,7 +2325,6 @@ void Item_sum_hybrid::reset_field() } case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } } @@ -2495,9 +2514,18 @@ void Item_sum_avg::update_field() } +Item *Item_sum_avg::result_item(THD *thd, Field *field) +{ + return + hybrid_type == DECIMAL_RESULT ? + (Item_avg_field*) new (thd->mem_root) Item_avg_field_decimal(thd, this) : + (Item_avg_field*) new (thd->mem_root) Item_avg_field_double(thd, this); +} + + void Item_sum_hybrid::update_field() { - switch (hybrid_type) { + switch (Item_sum_hybrid::result_type()) { case STRING_RESULT: min_max_update_str_field(); break; @@ -2609,34 +2637,13 @@ Item_sum_hybrid::min_max_update_decimal_field() } -Item_avg_field::Item_avg_field(Item_result res_type, Item_sum_avg *item) -{ - name=item->name; - decimals=item->decimals; - max_length= item->max_length; - unsigned_flag= item->unsigned_flag; - field=item->result_field; - maybe_null=1; - hybrid_type= res_type; - prec_increment= item->prec_increment; - if (hybrid_type == DECIMAL_RESULT) - { - f_scale= item->f_scale; - f_precision= item->f_precision; - dec_bin_size= item->dec_bin_size; - } -} - -double Item_avg_field::val_real() +double Item_avg_field_double::val_real() { // fix_fields() never calls for this Item double nr; longlong count; uchar *res; - if (hybrid_type == DECIMAL_RESULT) - return val_real_from_decimal(); - float8get(nr,field->ptr); res= (field->ptr+sizeof(double)); count= sint8korr(res); @@ -2647,19 +2654,9 @@ double Item_avg_field::val_real() } -longlong Item_avg_field::val_int() -{ - bool error; - return double_to_longlong(val_real(), unsigned_flag, &error); -} - - -my_decimal *Item_avg_field::val_decimal(my_decimal *dec_buf) +my_decimal *Item_avg_field_decimal::val_decimal(my_decimal *dec_buf) { // fix_fields() never calls for this Item - if (hybrid_type == REAL_RESULT) - return val_decimal_from_real(dec_buf); - longlong count= sint8korr(field->ptr + dec_bin_size); if ((null_value= !count)) return 0; @@ -2674,21 +2671,6 @@ my_decimal *Item_avg_field::val_decimal(my_decimal *dec_buf) } -String *Item_avg_field::val_str(String *str) -{ - // fix_fields() never calls for this Item - if (hybrid_type == DECIMAL_RESULT) - return val_string_from_decimal(str); - return val_string_from_real(str); -} - - -Item_std_field::Item_std_field(Item_sum_std *item) - : Item_variance_field(item) -{ -} - - double Item_std_field::val_real() { double nr; @@ -2699,57 +2681,9 @@ double Item_std_field::val_real() } -my_decimal *Item_std_field::val_decimal(my_decimal *dec_buf) -{ - /* - We can't call val_decimal_from_real() for DECIMAL_RESULT as - Item_variance_field::val_real() would cause an infinite loop - */ - my_decimal tmp_dec, *dec; - double nr; - if (hybrid_type == REAL_RESULT) - return val_decimal_from_real(dec_buf); - - dec= Item_variance_field::val_decimal(dec_buf); - if (!dec) - return 0; - my_decimal2double(E_DEC_FATAL_ERROR, dec, &nr); - DBUG_ASSERT(nr >= 0.0); - nr= sqrt(nr); - double2my_decimal(E_DEC_FATAL_ERROR, nr, &tmp_dec); - my_decimal_round(E_DEC_FATAL_ERROR, &tmp_dec, decimals, FALSE, dec_buf); - return dec_buf; -} - - -Item_variance_field::Item_variance_field(Item_sum_variance *item) -{ - name=item->name; - decimals=item->decimals; - max_length=item->max_length; - unsigned_flag= item->unsigned_flag; - field=item->result_field; - maybe_null=1; - sample= item->sample; - prec_increment= item->prec_increment; - if ((hybrid_type= item->hybrid_type) == DECIMAL_RESULT) - { - f_scale0= item->f_scale0; - f_precision0= item->f_precision0; - dec_bin_size0= item->dec_bin_size0; - f_scale1= item->f_scale1; - f_precision1= item->f_precision1; - dec_bin_size1= item->dec_bin_size1; - } -} - - double Item_variance_field::val_real() { // fix_fields() never calls for this Item - if (hybrid_type == DECIMAL_RESULT) - return val_real_from_decimal(); - double recurrence_s; ulonglong count; float8get(recurrence_s, (field->ptr + sizeof(double))); @@ -3132,6 +3066,7 @@ int dump_leaf_key(void* key_arg, element_count count __attribute__((unused)), CHARSET_INFO *cs= item->collation.collation; const char *ptr= result->ptr(); uint add_length; + THD *thd= current_thd; /* It's ok to use item->result.length() as the fourth argument as this is never used to limit the length of the data. @@ -3144,10 +3079,16 @@ int dump_leaf_key(void* key_arg, element_count count __attribute__((unused)), &well_formed_error); result->length(old_length + add_length); item->warning_for_row= TRUE; - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_CUT_VALUE_GROUP_CONCAT, ER(ER_CUT_VALUE_GROUP_CONCAT), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CUT_VALUE_GROUP_CONCAT, + ER_THD(thd, ER_CUT_VALUE_GROUP_CONCAT), item->row_count); + /** + To avoid duplicated warnings in Item_func_group_concat::val_str() + */ + if (table && table->blob_storage) + table->blob_storage->set_truncated_value(false); return 1; } return 0; @@ -3164,11 +3105,11 @@ int dump_leaf_key(void* key_arg, element_count count __attribute__((unused)), */ Item_func_group_concat:: -Item_func_group_concat(Name_resolution_context *context_arg, +Item_func_group_concat(THD *thd, Name_resolution_context *context_arg, bool distinct_arg, List<Item> *select_list, const SQL_I_List<ORDER> &order_list, String *separator_arg) - :tmp_table_param(0), separator(separator_arg), tree(0), + :Item_sum(thd), tmp_table_param(0), separator(separator_arg), tree(0), unique_filter(NULL), table(0), order(0), context(context_arg), arg_count_order(order_list.elements), @@ -3190,8 +3131,8 @@ Item_func_group_concat(Name_resolution_context *context_arg, (for possible order items in temporary tables) order - arg_count_order */ - if (!(args= (Item**) sql_alloc(sizeof(Item*) * arg_count * 2 + - sizeof(ORDER*)*arg_count_order))) + if (!(args= (Item**) thd->alloc(sizeof(Item*) * arg_count * 2 + + sizeof(ORDER*)*arg_count_order))) return; order= (ORDER**)(args + arg_count); @@ -3285,6 +3226,8 @@ void Item_func_group_concat::cleanup() if (table) { THD *thd= table->in_use; + if (table->blob_storage) + delete table->blob_storage; free_tmp_table(thd, table); table= 0; if (tree) @@ -3317,7 +3260,7 @@ void Item_func_group_concat::cleanup() } -Field *Item_func_group_concat::make_string_field(TABLE *table) +Field *Item_func_group_concat::make_string_field(TABLE *table_arg) { Field *field; DBUG_ASSERT(collation.collation); @@ -3326,10 +3269,11 @@ Field *Item_func_group_concat::make_string_field(TABLE *table) maybe_null, name, collation.collation, TRUE); else field= new Field_varstring(max_length, - maybe_null, name, table->s, collation.collation); + maybe_null, name, table_arg->s, + collation.collation); if (field) - field->init(table); + field->init(table_arg); return field; } @@ -3351,6 +3295,8 @@ void Item_func_group_concat::clear() reset_tree(tree); if (unique_filter) unique_filter->reset(); + if (table && table->blob_storage) + table->blob_storage->reset(); /* No need to reset the table as we never call write_row */ } @@ -3389,12 +3335,8 @@ bool Item_func_group_concat::add() TREE_ELEMENT *el= 0; // Only for safety if (row_eligible && tree) { - DBUG_EXECUTE_IF("trigger_OOM_in_gconcat_add", - DBUG_SET("+d,simulate_persistent_out_of_memory");); el= tree_insert(tree, table->record[0] + table->s->null_bytes, 0, tree->custom_arg); - DBUG_EXECUTE_IF("trigger_OOM_in_gconcat_add", - DBUG_SET("-d,simulate_persistent_out_of_memory");); /* check if there was enough memory to insert the row */ if (!el) return 1; @@ -3438,8 +3380,8 @@ Item_func_group_concat::fix_fields(THD *thd, Item **ref) } /* skip charset aggregation for order columns */ - if (agg_item_charsets_for_string_result(collation, func_name(), - args, arg_count - arg_count_order)) + if (agg_arg_charsets_for_string_result(collation, + args, arg_count - arg_count_order)) return 1; result.set_charset(collation.collation); @@ -3482,6 +3424,7 @@ bool Item_func_group_concat::setup(THD *thd) { List<Item> list; SELECT_LEX *select_lex= thd->lex->current_select; + const bool order_or_distinct= MY_TEST(arg_count_order > 0 || distinct); DBUG_ENTER("Item_func_group_concat::setup"); /* @@ -3494,15 +3437,12 @@ bool Item_func_group_concat::setup(THD *thd) if (!(tmp_table_param= new TMP_TABLE_PARAM)) DBUG_RETURN(TRUE); - /* We'll convert all blobs to varchar fields in the temporary table */ - tmp_table_param->convert_blob_length= max_length * - collation.collation->mbmaxlen; /* Push all not constant fields to the list and create a temp table */ always_null= 0; for (uint i= 0; i < arg_count_field; i++) { Item *item= args[i]; - if (list.push_back(item)) + if (list.push_back(item, thd->mem_root)) DBUG_RETURN(TRUE); if (item->const_item()) { @@ -3536,18 +3476,9 @@ bool Item_func_group_concat::setup(THD *thd) count_field_types(select_lex, tmp_table_param, all_fields, 0); tmp_table_param->force_copy_fields= force_copy_fields; DBUG_ASSERT(table == 0); - if (arg_count_order > 0 || distinct) + if (order_or_distinct) { /* - Currently we have to force conversion of BLOB values to VARCHAR's - if we are to store them in TREE objects used for ORDER BY and - DISTINCT. This leads to truncation if the BLOB's size exceeds - Field_varstring::MAX_SIZE. - */ - set_if_smaller(tmp_table_param->convert_blob_length, - Field_varstring::MAX_SIZE); - - /* Force the create_tmp_table() to convert BIT columns to INT as we cannot compare two table records containg BIT fields stored in the the tree used for distinct/order by. @@ -3580,6 +3511,13 @@ bool Item_func_group_concat::setup(THD *thd) table->file->extra(HA_EXTRA_NO_ROWS); table->no_rows= 1; + /** + Initialize blob_storage if GROUP_CONCAT is used + with ORDER BY | DISTINCT and BLOB field count > 0. + */ + if (order_or_distinct && table->s->blob_fields) + table->blob_storage= new Blob_mem_storage(); + /* Need sorting or uniqueness: init tree and choose a function to sort. Don't reserve space for NULLs: if any of gconcat arguments is NULL, @@ -3595,10 +3533,11 @@ bool Item_func_group_concat::setup(THD *thd) syntax of this function). If there is no ORDER BY clause, we don't create this tree. */ - init_tree(tree, (uint) min(thd->variables.max_heap_table_size, + init_tree(tree, (uint) MY_MIN(thd->variables.max_heap_table_size, thd->variables.sortbuff_size/16), 0, tree_key_length, - group_concat_key_cmp_with_order , 0, NULL, (void*) this); + group_concat_key_cmp_with_order, NULL, (void*) this, + MYF(MY_THREAD_SPECIFIC)); } if (distinct) @@ -3631,6 +3570,16 @@ String* Item_func_group_concat::val_str(String* str) if (no_appended && tree) /* Tree is used for sorting as in ORDER BY */ tree_walk(tree, &dump_leaf_key, this, left_root_right); + + if (table && table->blob_storage && + table->blob_storage->is_truncated_value()) + { + warning_for_row= true; + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_CUT_VALUE_GROUP_CONCAT, ER(ER_CUT_VALUE_GROUP_CONCAT), + row_count); + } + return &result; } diff --git a/sql/item_sum.h b/sql/item_sum.h index b0bca5e7ad2..d6ccfeb8529 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -302,10 +302,16 @@ class st_select_lex; We assume that the nesting level of subquries does not exceed 127. TODO: to catch queries where the limit is exceeded to make the code clean here. - + + @note + The implementation takes into account the used strategy: + - Items resolved at optimization phase return 0 from Item_sum::used_tables(). + - Items that depend on the number of join output records, but not columns of + any particular table (like COUNT(*)), returm 0 from Item_sum::used_tables(), + but still return false from Item_sum::const_item(). */ -class Item_sum :public Item_result_field +class Item_sum :public Item_func_or_sum { friend class Aggregator_distinct; friend class Aggregator_simple; @@ -362,47 +368,36 @@ public: List<Item_field> outer_fields; protected: - uint arg_count; - Item **args, *tmp_args[2]; /* Copy of the arguments list to hold the original set of arguments. Used in EXPLAIN EXTENDED instead of the current argument list because the current argument list can be altered by usage of temporary tables. */ Item **orig_args, *tmp_orig_args[2]; - table_map used_tables_cache; - /* - TRUE <=> We've managed to calculate the value of this Item in - opt_sum_query(), hence it can be considered constant at all subsequent - steps. - */ - bool forced_const; static ulonglong ram_limitation(THD *thd); public: void mark_as_sum_func(); - Item_sum() :quick_group(1), arg_count(0), forced_const(FALSE) + Item_sum(THD *thd): Item_func_or_sum(thd), quick_group(1) { mark_as_sum_func(); init_aggregator(); } - Item_sum(Item *a) :quick_group(1), arg_count(1), args(tmp_args), - orig_args(tmp_orig_args), forced_const(FALSE) + Item_sum(THD *thd, Item *a): Item_func_or_sum(thd, a), quick_group(1), + orig_args(tmp_orig_args) { - args[0]=a; mark_as_sum_func(); init_aggregator(); } - Item_sum( Item *a, Item *b ) :quick_group(1), arg_count(2), args(tmp_args), - orig_args(tmp_orig_args), forced_const(FALSE) + Item_sum(THD *thd, Item *a, Item *b): Item_func_or_sum(thd, a, b), + quick_group(1), orig_args(tmp_orig_args) { - args[0]=a; args[1]=b; mark_as_sum_func(); init_aggregator(); } - Item_sum(List<Item> &list); + Item_sum(THD *thd, List<Item> &list); //Copy constructor, need to perform subselects with temporary tables Item_sum(THD *thd, Item_sum *item); enum Type type() const { return SUM_FUNC_ITEM; } @@ -435,27 +430,35 @@ public: virtual void update_field()=0; virtual bool keep_field_type(void) const { return 0; } virtual void fix_length_and_dec() { maybe_null=1; null_value=1; } - virtual Item *result_item(Field *field) - { return new Item_field(field); } - /* - Return bitmap of tables that are needed to evaluate the item. + virtual Item *result_item(THD *thd, Field *field); - The implementation takes into account the used strategy: items resolved - at optimization phase will report 0. - Items that depend on the number of join output records, but not columns - of any particular table (like COUNT(*)) will report 0 from used_tables(), - but will still return false from const_item(). - */ - table_map used_tables() const { return used_tables_cache; } void update_used_tables (); + COND *build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref) + { + /* + Item_sum (and derivants) of the original WHERE/HAVING clauses + should already be replaced to Item_aggregate_ref by the time when + build_equal_items() is called. See Item::split_sum_func2(). + */ + DBUG_ASSERT(0); + return Item::build_equal_items(thd, inherited, link_item_fields, + cond_equal_ref); + } bool is_null() { return null_value; } + /** + make_const() + Called if we've managed to calculate the value of this Item in + opt_sum_query(), hence it can be considered constant at all subsequent + steps. + */ void make_const () { used_tables_cache= 0; - forced_const= TRUE; + const_item_cache= true; } - void reset_forced_const() { forced_const= FALSE; } - virtual bool const_item() const { return forced_const; } + void reset_forced_const() { const_item_cache= false; } virtual bool const_during_execution() const { return false; } virtual void print(String *str, enum_query_type query_type); void fix_num_length_and_dec(); @@ -479,9 +482,7 @@ public: } virtual void make_unique() { force_copy_fields= TRUE; } Item *get_tmp_table_item(THD *thd); - virtual Field *create_tmp_field(bool group, TABLE *table, - uint convert_blob_length); - bool walk(Item_processor processor, bool walk_subquery, uchar *argument); + Field *create_tmp_field(bool group, TABLE *table); virtual bool collect_outer_ref_processor(uchar *param); bool init_sum_func_check(THD *thd); bool check_sum_func(THD *thd, Item **ref); @@ -552,9 +553,9 @@ class Unique; /** The distinct aggregator. Implements AGGFN (DISTINCT ..) - Collects all the data into an Unique (similarly to what Item_sum_distinct - does currently) and then (if applicable) iterates over the list of - unique values and pumps them back into its object + Collects all the data into an Unique (similarly to what Item_sum + does currently when with_distinct=true) and then (if applicable) iterates over + the list of unique values and pumps them back into its object */ class Aggregator_distinct : public Aggregator @@ -687,22 +688,17 @@ protected: */ bool is_evaluated; public: - Item_sum_num() :Item_sum(),is_evaluated(FALSE) {} - Item_sum_num(Item *item_par) - :Item_sum(item_par), is_evaluated(FALSE) {} - Item_sum_num(Item *a, Item* b) :Item_sum(a,b),is_evaluated(FALSE) {} - Item_sum_num(List<Item> &list) - :Item_sum(list), is_evaluated(FALSE) {} - Item_sum_num(THD *thd, Item_sum_num *item) - :Item_sum(thd, item),is_evaluated(item->is_evaluated) {} + Item_sum_num(THD *thd): Item_sum(thd), is_evaluated(FALSE) {} + Item_sum_num(THD *thd, Item *item_par): + Item_sum(thd, item_par), is_evaluated(FALSE) {} + Item_sum_num(THD *thd, Item *a, Item* b): + Item_sum(thd, a, b), is_evaluated(FALSE) {} + Item_sum_num(THD *thd, List<Item> &list): + Item_sum(thd, list), is_evaluated(FALSE) {} + Item_sum_num(THD *thd, Item_sum_num *item): + Item_sum(thd, item),is_evaluated(item->is_evaluated) {} bool fix_fields(THD *, Item **); - longlong val_int() - { - DBUG_ASSERT(fixed == 1); - // Real as default - bool error; - return double_to_longlong(val_real(), unsigned_flag, &error); - } + longlong val_int() { return val_int_from_real(); /* Real as default */ } String *val_str(String*str); my_decimal *val_decimal(my_decimal *); void reset_field(); @@ -712,8 +708,8 @@ public: class Item_sum_int :public Item_sum_num { public: - Item_sum_int(Item *item_par) :Item_sum_num(item_par) {} - Item_sum_int(List<Item> &list) :Item_sum_num(list) {} + Item_sum_int(THD *thd, Item *item_par): Item_sum_num(thd, item_par) {} + Item_sum_int(THD *thd, List<Item> &list): Item_sum_num(thd, list) {} Item_sum_int(THD *thd, Item_sum_int *item) :Item_sum_num(thd, item) {} double val_real() { DBUG_ASSERT(fixed == 1); return (double) val_int(); } String *val_str(String*str); @@ -734,7 +730,8 @@ protected: void fix_length_and_dec(); public: - Item_sum_sum(Item *item_par, bool distinct) :Item_sum_num(item_par) + Item_sum_sum(THD *thd, Item *item_par, bool distinct): + Item_sum_num(thd, item_par) { set_distinct(distinct); } @@ -772,8 +769,8 @@ class Item_sum_count :public Item_sum_int void cleanup(); public: - Item_sum_count(Item *item_par) - :Item_sum_int(item_par),count(0) + Item_sum_count(THD *thd, Item *item_par): + Item_sum_int(thd, item_par), count(0) {} /** @@ -784,13 +781,13 @@ class Item_sum_count :public Item_sum_int This constructor is called by the parser only for COUNT (DISTINCT). */ - Item_sum_count(List<Item> &list) - :Item_sum_int(list),count(0) + Item_sum_count(THD *thd, List<Item> &list): + Item_sum_int(thd, list), count(0) { set_distinct(TRUE); } - Item_sum_count(THD *thd, Item_sum_count *item) - :Item_sum_int(thd, item), count(item->count) + Item_sum_count(THD *thd, Item_sum_count *item): + Item_sum_int(thd, item), count(item->count) {} enum Sumfunctype sum_func () const { @@ -813,39 +810,6 @@ class Item_sum_count :public Item_sum_int }; -/* Item to get the value of a stored sum function */ - -class Item_sum_avg; - -class Item_avg_field :public Item_result_field -{ -public: - Field *field; - Item_result hybrid_type; - uint f_precision, f_scale, dec_bin_size; - uint prec_increment; - Item_avg_field(Item_result res_type, Item_sum_avg *item); - enum Type type() const { return FIELD_AVG_ITEM; } - double val_real(); - longlong val_int(); - my_decimal *val_decimal(my_decimal *); - bool is_null() { update_null_value(); return null_value; } - String *val_str(String*); - enum_field_types field_type() const - { - return hybrid_type == DECIMAL_RESULT ? - MYSQL_TYPE_NEWDECIMAL : MYSQL_TYPE_DOUBLE; - } - void fix_length_and_dec() {} - enum Item_result result_type () const { return hybrid_type; } - bool check_vcol_func_processor(uchar *int_arg) - { - return trace_unsupported_by_check_vcol_func_processor("avg_field"); - } - const char *func_name() const { DBUG_ASSERT(0); return "avg_field"; } -}; - - class Item_sum_avg :public Item_sum_sum { public: @@ -853,8 +817,8 @@ public: uint prec_increment; uint f_precision, f_scale, dec_bin_size; - Item_sum_avg(Item *item_par, bool distinct) - :Item_sum_sum(item_par, distinct), count(0) + Item_sum_avg(THD *thd, Item *item_par, bool distinct): + Item_sum_sum(thd, item_par, distinct), count(0) {} Item_sum_avg(THD *thd, Item_sum_avg *item) :Item_sum_sum(thd, item), count(item->count), @@ -869,24 +833,19 @@ public: bool add(); double val_real(); // In SPs we might force the "wrong" type with select into a declare variable - longlong val_int() - { - bool error; - return double_to_longlong(val_real(), unsigned_flag, &error); - } + longlong val_int() { return val_int_from_real(); } my_decimal *val_decimal(my_decimal *); String *val_str(String *str); void reset_field(); void update_field(); - Item *result_item(Field *field) - { return new Item_avg_field(hybrid_type, this); } + Item *result_item(THD *thd, Field *field); void no_rows_in_result() {} const char *func_name() const { return has_with_distinct() ? "avg(distinct " : "avg("; } Item *copy_or_same(THD* thd); - Field *create_tmp_field(bool group, TABLE *table, uint convert_blob_length); + Field *create_tmp_field(bool group, TABLE *table); void cleanup() { count= 0; @@ -894,45 +853,6 @@ public: } }; -class Item_sum_variance; - -class Item_variance_field :public Item_result_field -{ -public: - Field *field; - Item_result hybrid_type; - uint f_precision0, f_scale0; - uint f_precision1, f_scale1; - uint dec_bin_size0, dec_bin_size1; - uint sample; - uint prec_increment; - Item_variance_field(Item_sum_variance *item); - enum Type type() const {return FIELD_VARIANCE_ITEM; } - double val_real(); - longlong val_int() - { /* can't be fix_fields()ed */ - bool error; - return double_to_longlong(val_real(), unsigned_flag, &error); - } - String *val_str(String *str) - { return val_string_from_real(str); } - my_decimal *val_decimal(my_decimal *dec_buf) - { return val_decimal_from_real(dec_buf); } - bool is_null() { update_null_value(); return null_value; } - enum_field_types field_type() const - { - return hybrid_type == DECIMAL_RESULT ? - MYSQL_TYPE_NEWDECIMAL : MYSQL_TYPE_DOUBLE; - } - void fix_length_and_dec() {} - enum Item_result result_type () const { return hybrid_type; } - bool check_vcol_func_processor(uchar *int_arg) - { - return trace_unsupported_by_check_vcol_func_processor("var_field"); - } - const char *func_name() const { DBUG_ASSERT(0); return "variance_field"; } -}; - /* variance(a) = @@ -959,18 +879,14 @@ class Item_sum_variance : public Item_sum_num void fix_length_and_dec(); public: - Item_result hybrid_type; - int cur_dec; double recurrence_m, recurrence_s; /* Used in recurrence relation. */ ulonglong count; - uint f_precision0, f_scale0; - uint f_precision1, f_scale1; - uint dec_bin_size0, dec_bin_size1; uint sample; uint prec_increment; - Item_sum_variance(Item *item_par, uint sample_arg) :Item_sum_num(item_par), - hybrid_type(REAL_RESULT), count(0), sample(sample_arg) + Item_sum_variance(THD *thd, Item *item_par, uint sample_arg): + Item_sum_num(thd, item_par), count(0), + sample(sample_arg) {} Item_sum_variance(THD *thd, Item_sum_variance *item); enum Sumfunctype sum_func () const { return VARIANCE_FUNC; } @@ -980,14 +896,14 @@ public: my_decimal *val_decimal(my_decimal *); void reset_field(); void update_field(); - Item *result_item(Field *field) - { return new Item_variance_field(this); } + Item *result_item(THD *thd, Field *field); void no_rows_in_result() {} const char *func_name() const { return sample ? "var_samp(" : "variance("; } Item *copy_or_same(THD* thd); - Field *create_tmp_field(bool group, TABLE *table, uint convert_blob_length); + Field *create_tmp_field(bool group, TABLE *table); enum Item_result result_type () const { return REAL_RESULT; } + enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE;} void cleanup() { count= 0; @@ -995,20 +911,6 @@ public: } }; -class Item_sum_std; - -class Item_std_field :public Item_variance_field -{ -public: - Item_std_field(Item_sum_std *item); - enum Type type() const { return FIELD_STD_ITEM; } - double val_real(); - my_decimal *val_decimal(my_decimal *); - enum Item_result result_type () const { return REAL_RESULT; } - enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE;} - const char *func_name() const { DBUG_ASSERT(0); return "std_field"; } -}; - /* standard_deviation(a) = sqrt(variance(a)) */ @@ -1016,48 +918,45 @@ public: class Item_sum_std :public Item_sum_variance { public: - Item_sum_std(Item *item_par, uint sample_arg) - :Item_sum_variance(item_par, sample_arg) {} + Item_sum_std(THD *thd, Item *item_par, uint sample_arg): + Item_sum_variance(thd, item_par, sample_arg) {} Item_sum_std(THD *thd, Item_sum_std *item) :Item_sum_variance(thd, item) {} enum Sumfunctype sum_func () const { return STD_FUNC; } double val_real(); - Item *result_item(Field *field) - { return new Item_std_field(this); } + Item *result_item(THD *thd, Field *field); const char *func_name() const { return "std("; } Item *copy_or_same(THD* thd); - enum Item_result result_type () const { return REAL_RESULT; } - enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE;} }; // This class is a string or number function depending on num_func class Arg_comparator; class Item_cache; -class Item_sum_hybrid :public Item_sum +class Item_sum_hybrid :public Item_sum, public Type_handler_hybrid_field_type { protected: Item_cache *value, *arg_cache; Arg_comparator *cmp; - Item_result hybrid_type; - enum_field_types hybrid_field_type; int cmp_sign; bool was_values; // Set if we have found at least one row (for max/min only) bool was_null_value; public: - Item_sum_hybrid(Item *item_par,int sign) - :Item_sum(item_par), value(0), arg_cache(0), cmp(0), - hybrid_type(INT_RESULT), hybrid_field_type(MYSQL_TYPE_LONGLONG), + Item_sum_hybrid(THD *thd, Item *item_par,int sign): + Item_sum(thd, item_par), + Type_handler_hybrid_field_type(MYSQL_TYPE_LONGLONG), + value(0), arg_cache(0), cmp(0), cmp_sign(sign), was_values(TRUE) { collation.set(&my_charset_bin); } Item_sum_hybrid(THD *thd, Item_sum_hybrid *item) - :Item_sum(thd, item), value(item->value), arg_cache(0), - hybrid_type(item->hybrid_type), hybrid_field_type(item->hybrid_field_type), + :Item_sum(thd, item), + Type_handler_hybrid_field_type(item), + value(item->value), arg_cache(0), cmp_sign(item->cmp_sign), was_values(item->was_values) { } bool fix_fields(THD *, Item **); - void setup_hybrid(Item *item, Item *value_arg); + void setup_hybrid(THD *thd, Item *item, Item *value_arg); void clear(); double val_real(); longlong val_int(); @@ -1065,8 +964,12 @@ protected: void reset_field(); String *val_str(String *); bool keep_field_type(void) const { return 1; } - enum Item_result result_type () const { return hybrid_type; } - enum enum_field_types field_type() const { return hybrid_field_type; } + enum Item_result result_type () const + { return Type_handler_hybrid_field_type::result_type(); } + enum Item_result cmp_type () const + { return Type_handler_hybrid_field_type::cmp_type(); } + enum enum_field_types field_type() const + { return Type_handler_hybrid_field_type::field_type(); } void update_field(); void min_max_update_str_field(); void min_max_update_real_field(); @@ -1076,15 +979,14 @@ protected: bool any_value() { return was_values; } void no_rows_in_result(); void restore_to_before_no_rows_in_result(); - Field *create_tmp_field(bool group, TABLE *table, - uint convert_blob_length); + Field *create_tmp_field(bool group, TABLE *table); }; class Item_sum_min :public Item_sum_hybrid { public: - Item_sum_min(Item *item_par) :Item_sum_hybrid(item_par,1) {} + Item_sum_min(THD *thd, Item *item_par): Item_sum_hybrid(thd, item_par, 1) {} Item_sum_min(THD *thd, Item_sum_min *item) :Item_sum_hybrid(thd, item) {} enum Sumfunctype sum_func () const {return MIN_FUNC;} @@ -1097,7 +999,7 @@ public: class Item_sum_max :public Item_sum_hybrid { public: - Item_sum_max(Item *item_par) :Item_sum_hybrid(item_par,-1) {} + Item_sum_max(THD *thd, Item *item_par): Item_sum_hybrid(thd, item_par, -1) {} Item_sum_max(THD *thd, Item_sum_max *item) :Item_sum_hybrid(thd, item) {} enum Sumfunctype sum_func () const {return MAX_FUNC;} @@ -1113,8 +1015,8 @@ protected: ulonglong reset_bits,bits; public: - Item_sum_bit(Item *item_par,ulonglong reset_arg) - :Item_sum_int(item_par),reset_bits(reset_arg),bits(reset_arg) {} + Item_sum_bit(THD *thd, Item *item_par, ulonglong reset_arg): + Item_sum_int(thd, item_par), reset_bits(reset_arg), bits(reset_arg) {} Item_sum_bit(THD *thd, Item_sum_bit *item): Item_sum_int(thd, item), reset_bits(item->reset_bits), bits(item->bits) {} enum Sumfunctype sum_func () const {return SUM_BIT_FUNC;} @@ -1135,7 +1037,7 @@ public: class Item_sum_or :public Item_sum_bit { public: - Item_sum_or(Item *item_par) :Item_sum_bit(item_par,LL(0)) {} + Item_sum_or(THD *thd, Item *item_par): Item_sum_bit(thd, item_par, 0) {} Item_sum_or(THD *thd, Item_sum_or *item) :Item_sum_bit(thd, item) {} bool add(); const char *func_name() const { return "bit_or("; } @@ -1146,7 +1048,8 @@ public: class Item_sum_and :public Item_sum_bit { public: - Item_sum_and(Item *item_par) :Item_sum_bit(item_par, ULONGLONG_MAX) {} + Item_sum_and(THD *thd, Item *item_par): + Item_sum_bit(thd, item_par, ULONGLONG_MAX) {} Item_sum_and(THD *thd, Item_sum_and *item) :Item_sum_bit(thd, item) {} bool add(); const char *func_name() const { return "bit_and("; } @@ -1156,7 +1059,7 @@ class Item_sum_and :public Item_sum_bit class Item_sum_xor :public Item_sum_bit { public: - Item_sum_xor(Item *item_par) :Item_sum_bit(item_par,LL(0)) {} + Item_sum_xor(THD *thd, Item *item_par): Item_sum_bit(thd, item_par, 0) {} Item_sum_xor(THD *thd, Item_sum_xor *item) :Item_sum_bit(thd, item) {} bool add(); const char *func_name() const { return "bit_xor("; } @@ -1164,6 +1067,114 @@ class Item_sum_xor :public Item_sum_bit }; +/* Items to get the value of a stored sum function */ + +class Item_sum_field :public Item +{ +protected: + Field *field; +public: + Item_sum_field(THD *thd, Item_sum *item) + :Item(thd), field(item->result_field) + { + name= item->name; + maybe_null= true; + decimals= item->decimals; + max_length= item->max_length; + unsigned_flag= item->unsigned_flag; + fixed= true; + } + table_map used_tables() const { return (table_map) 1L; } + void save_in_result_field(bool no_conversions) { DBUG_ASSERT(0); } +}; + + +class Item_avg_field :public Item_sum_field +{ +protected: + uint prec_increment; +public: + Item_avg_field(THD *thd, Item_sum_avg *item) + :Item_sum_field(thd, item), prec_increment(item->prec_increment) + { } + enum Type type() const { return FIELD_AVG_ITEM; } + bool is_null() { update_null_value(); return null_value; } + bool check_vcol_func_processor(uchar *int_arg) + { + return trace_unsupported_by_check_vcol_func_processor("avg_field"); + } +}; + + +class Item_avg_field_double :public Item_avg_field +{ +public: + Item_avg_field_double(THD *thd, Item_sum_avg *item) + :Item_avg_field(thd, item) + { } + enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE; } + enum Item_result result_type () const { return REAL_RESULT; } + longlong val_int() { return val_int_from_real(); } + my_decimal *val_decimal(my_decimal *dec) { return val_decimal_from_real(dec); } + String *val_str(String *str) { return val_string_from_real(str); } + double val_real(); +}; + + +class Item_avg_field_decimal :public Item_avg_field +{ + uint f_precision, f_scale, dec_bin_size; +public: + Item_avg_field_decimal(THD *thd, Item_sum_avg *item) + :Item_avg_field(thd, item), + f_precision(item->f_precision), + f_scale(item->f_scale), + dec_bin_size(item->dec_bin_size) + { } + enum_field_types field_type() const { return MYSQL_TYPE_NEWDECIMAL; } + enum Item_result result_type () const { return DECIMAL_RESULT; } + double val_real() { return val_real_from_decimal(); } + longlong val_int() { return val_int_from_decimal(); } + String *val_str(String *str) { return val_string_from_decimal(str); } + my_decimal *val_decimal(my_decimal *); +}; + + +class Item_variance_field :public Item_sum_field +{ + uint sample; +public: + Item_variance_field(THD *thd, Item_sum_variance *item) + :Item_sum_field(thd, item), sample(item->sample) + { } + enum Type type() const {return FIELD_VARIANCE_ITEM; } + double val_real(); + longlong val_int() { return val_int_from_real(); } + String *val_str(String *str) + { return val_string_from_real(str); } + my_decimal *val_decimal(my_decimal *dec_buf) + { return val_decimal_from_real(dec_buf); } + bool is_null() { update_null_value(); return null_value; } + enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE; } + enum Item_result result_type () const { return REAL_RESULT; } + bool check_vcol_func_processor(uchar *int_arg) + { + return trace_unsupported_by_check_vcol_func_processor("var_field"); + } +}; + + +class Item_std_field :public Item_variance_field +{ +public: + Item_std_field(THD *thd, Item_sum_std *item) + :Item_variance_field(thd, item) + { } + enum Type type() const { return FIELD_STD_ITEM; } + double val_real(); +}; + + /* User defined aggregates */ @@ -1176,11 +1187,11 @@ protected: udf_handler udf; public: - Item_udf_sum(udf_func *udf_arg) - :Item_sum(), udf(udf_arg) + Item_udf_sum(THD *thd, udf_func *udf_arg): + Item_sum(thd), udf(udf_arg) { quick_group=0; } - Item_udf_sum(udf_func *udf_arg, List<Item> &list) - :Item_sum(list), udf(udf_arg) + Item_udf_sum(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_sum(thd, list), udf(udf_arg) { quick_group=0;} Item_udf_sum(THD *thd, Item_udf_sum *item) :Item_sum(thd, item), udf(item->udf) @@ -1194,9 +1205,27 @@ public: return TRUE; fixed= 1; + /* + We set const_item_cache to false in constructors. + It can be later changed to "true", in a Item_sum::make_const() call. + No make_const() calls should have happened so far. + */ + DBUG_ASSERT(!const_item_cache); if (udf.fix_fields(thd, this, this->arg_count, this->args)) return TRUE; - + /** + The above call for udf.fix_fields() updates + the Used_tables_and_const_cache part of "this" as if it was a regular + non-aggregate UDF function and can change both const_item_cache and + used_tables_cache members. + - The used_tables_cache will be re-calculated in update_used_tables() + which is called from check_sum_func() below. So we don't care about + its current value. + - The const_item_cache must stay "false" until a Item_sum::make_const() + call happens, if ever. So we need to reset const_item_cache back to + "false" here. + */ + const_item_cache= false; memcpy (orig_args, args, sizeof (Item *) * arg_count); return check_sum_func(thd, ref); } @@ -1215,19 +1244,13 @@ public: class Item_sum_udf_float :public Item_udf_sum { public: - Item_sum_udf_float(udf_func *udf_arg) - :Item_udf_sum(udf_arg) {} - Item_sum_udf_float(udf_func *udf_arg, List<Item> &list) - :Item_udf_sum(udf_arg, list) {} + Item_sum_udf_float(THD *thd, udf_func *udf_arg): + Item_udf_sum(thd, udf_arg) {} + Item_sum_udf_float(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_udf_sum(thd, udf_arg, list) {} Item_sum_udf_float(THD *thd, Item_sum_udf_float *item) :Item_udf_sum(thd, item) {} - longlong val_int() - { - DBUG_ASSERT(fixed == 1); - bool error; - return double_to_longlong(Item_sum_udf_float::val_real(), - unsigned_flag, &error); - } + longlong val_int() { return val_int_from_real(); } double val_real(); String *val_str(String*str); my_decimal *val_decimal(my_decimal *); @@ -1239,10 +1262,10 @@ class Item_sum_udf_float :public Item_udf_sum class Item_sum_udf_int :public Item_udf_sum { public: - Item_sum_udf_int(udf_func *udf_arg) - :Item_udf_sum(udf_arg) {} - Item_sum_udf_int(udf_func *udf_arg, List<Item> &list) - :Item_udf_sum(udf_arg, list) {} + Item_sum_udf_int(THD *thd, udf_func *udf_arg): + Item_udf_sum(thd, udf_arg) {} + Item_sum_udf_int(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_udf_sum(thd, udf_arg, list) {} Item_sum_udf_int(THD *thd, Item_sum_udf_int *item) :Item_udf_sum(thd, item) {} longlong val_int(); @@ -1259,10 +1282,10 @@ public: class Item_sum_udf_str :public Item_udf_sum { public: - Item_sum_udf_str(udf_func *udf_arg) - :Item_udf_sum(udf_arg) {} - Item_sum_udf_str(udf_func *udf_arg, List<Item> &list) - :Item_udf_sum(udf_arg,list) {} + Item_sum_udf_str(THD *thd, udf_func *udf_arg): + Item_udf_sum(thd, udf_arg) {} + Item_sum_udf_str(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_udf_sum(thd, udf_arg, list) {} Item_sum_udf_str(THD *thd, Item_sum_udf_str *item) :Item_udf_sum(thd, item) {} String *val_str(String *); @@ -1298,10 +1321,10 @@ public: class Item_sum_udf_decimal :public Item_udf_sum { public: - Item_sum_udf_decimal(udf_func *udf_arg) - :Item_udf_sum(udf_arg) {} - Item_sum_udf_decimal(udf_func *udf_arg, List<Item> &list) - :Item_udf_sum(udf_arg, list) {} + Item_sum_udf_decimal(THD *thd, udf_func *udf_arg): + Item_udf_sum(thd, udf_arg) {} + Item_sum_udf_decimal(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_udf_sum(thd, udf_arg, list) {} Item_sum_udf_decimal(THD *thd, Item_sum_udf_decimal *item) :Item_udf_sum(thd, item) {} String *val_str(String *); @@ -1318,9 +1341,10 @@ public: class Item_sum_udf_float :public Item_sum_num { public: - Item_sum_udf_float(udf_func *udf_arg) - :Item_sum_num() {} - Item_sum_udf_float(udf_func *udf_arg, List<Item> &list) :Item_sum_num() {} + Item_sum_udf_float(THD *thd, udf_func *udf_arg): + Item_sum_num(thd) {} + Item_sum_udf_float(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_sum_num(thd) {} Item_sum_udf_float(THD *thd, Item_sum_udf_float *item) :Item_sum_num(thd, item) {} enum Sumfunctype sum_func () const { return UDF_SUM_FUNC; } @@ -1334,9 +1358,10 @@ class Item_sum_udf_float :public Item_sum_num class Item_sum_udf_int :public Item_sum_num { public: - Item_sum_udf_int(udf_func *udf_arg) - :Item_sum_num() {} - Item_sum_udf_int(udf_func *udf_arg, List<Item> &list) :Item_sum_num() {} + Item_sum_udf_int(THD *thd, udf_func *udf_arg): + Item_sum_num(thd) {} + Item_sum_udf_int(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_sum_num(thd) {} Item_sum_udf_int(THD *thd, Item_sum_udf_int *item) :Item_sum_num(thd, item) {} enum Sumfunctype sum_func () const { return UDF_SUM_FUNC; } @@ -1351,10 +1376,10 @@ public: class Item_sum_udf_decimal :public Item_sum_num { public: - Item_sum_udf_decimal(udf_func *udf_arg) - :Item_sum_num() {} - Item_sum_udf_decimal(udf_func *udf_arg, List<Item> &list) - :Item_sum_num() {} + Item_sum_udf_decimal(THD *thd, udf_func *udf_arg): + Item_sum_num(thd) {} + Item_sum_udf_decimal(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_sum_num(thd) {} Item_sum_udf_decimal(THD *thd, Item_sum_udf_float *item) :Item_sum_num(thd, item) {} enum Sumfunctype sum_func () const { return UDF_SUM_FUNC; } @@ -1369,10 +1394,10 @@ class Item_sum_udf_decimal :public Item_sum_num class Item_sum_udf_str :public Item_sum_num { public: - Item_sum_udf_str(udf_func *udf_arg) - :Item_sum_num() {} - Item_sum_udf_str(udf_func *udf_arg, List<Item> &list) - :Item_sum_num() {} + Item_sum_udf_str(THD *thd, udf_func *udf_arg): + Item_sum_num(thd) {} + Item_sum_udf_str(THD *thd, udf_func *udf_arg, List<Item> &list): + Item_sum_num(thd) {} Item_sum_udf_str(THD *thd, Item_sum_udf_str *item) :Item_sum_num(thd, item) {} String *val_str(String *) @@ -1444,7 +1469,7 @@ class Item_func_group_concat : public Item_sum void* item_arg); public: - Item_func_group_concat(Name_resolution_context *context_arg, + Item_func_group_concat(THD *thd, Name_resolution_context *context_arg, bool is_distinct, List<Item> *is_select, const SQL_I_List<ORDER> &is_order, String *is_separator); diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 2dc704f6873..e8440803295 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -30,6 +30,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" /* It is necessary to include set_var.h instead of item.h because there @@ -147,14 +148,14 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, switch (*++ptr) { /* Year */ case 'Y': - tmp= (char*) val + min(4, val_len); + tmp= (char*) val + MY_MIN(4, val_len); l_time->year= (int) my_strtoll10(val, &tmp, &error); if ((int) (tmp-val) <= 2) l_time->year= year_2000_handling(l_time->year); val= tmp; break; case 'y': - tmp= (char*) val + min(2, val_len); + tmp= (char*) val + MY_MIN(2, val_len); l_time->year= (int) my_strtoll10(val, &tmp, &error); val= tmp; l_time->year= year_2000_handling(l_time->year); @@ -163,7 +164,7 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, /* Month */ case 'm': case 'c': - tmp= (char*) val + min(2, val_len); + tmp= (char*) val + MY_MIN(2, val_len); l_time->month= (int) my_strtoll10(val, &tmp, &error); val= tmp; break; @@ -180,15 +181,15 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, /* Day */ case 'd': case 'e': - tmp= (char*) val + min(2, val_len); + tmp= (char*) val + MY_MIN(2, val_len); l_time->day= (int) my_strtoll10(val, &tmp, &error); val= tmp; break; case 'D': - tmp= (char*) val + min(2, val_len); + tmp= (char*) val + MY_MIN(2, val_len); l_time->day= (int) my_strtoll10(val, &tmp, &error); /* Skip 'st, 'nd, 'th .. */ - val= tmp + min((int) (val_end-tmp), 2); + val= tmp + MY_MIN((int) (val_end-tmp), 2); break; /* Hour */ @@ -199,14 +200,14 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, /* fall through */ case 'k': case 'H': - tmp= (char*) val + min(2, val_len); + tmp= (char*) val + MY_MIN(2, val_len); l_time->hour= (int) my_strtoll10(val, &tmp, &error); val= tmp; break; /* Minute */ case 'i': - tmp= (char*) val + min(2, val_len); + tmp= (char*) val + MY_MIN(2, val_len); l_time->minute= (int) my_strtoll10(val, &tmp, &error); val= tmp; break; @@ -214,7 +215,7 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, /* Second */ case 's': case 'S': - tmp= (char*) val + min(2, val_len); + tmp= (char*) val + MY_MIN(2, val_len); l_time->second= (int) my_strtoll10(val, &tmp, &error); val= tmp; break; @@ -266,7 +267,7 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, val= tmp; break; case 'j': - tmp= (char*) val + min(val_len, 3); + tmp= (char*) val + MY_MIN(val_len, 3); yearday= (int) my_strtoll10(val, &tmp, &error); val= tmp; break; @@ -278,7 +279,7 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, case 'u': sunday_first_n_first_week_non_iso= (*ptr=='U' || *ptr== 'V'); strict_week_number= (*ptr=='V' || *ptr=='v'); - tmp= (char*) val + min(val_len, 2); + tmp= (char*) val + MY_MIN(val_len, 2); if ((week_number= (int) my_strtoll10(val, &tmp, &error)) < 0 || (strict_week_number && !week_number) || week_number > 53) @@ -290,7 +291,7 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, case 'X': case 'x': strict_week_number_year_type= (*ptr=='X'); - tmp= (char*) val + min(4, val_len); + tmp= (char*) val + MY_MIN(4, val_len); strict_week_number_year= (int) my_strtoll10(val, &tmp, &error); val= tmp; break; @@ -426,7 +427,8 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, { if (!my_isspace(&my_charset_latin1,*val)) { - make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + make_truncated_value_warning(current_thd, + Sql_condition::WARN_LEVEL_WARN, val_begin, length, cached_timestamp_type, NullS); break; @@ -437,10 +439,12 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, err: { + THD *thd= current_thd; char buff[128]; - strmake(buff, val_begin, min(length, sizeof(buff)-1)); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WRONG_VALUE_FOR_TYPE, ER(ER_WRONG_VALUE_FOR_TYPE), + strmake(buff, val_begin, MY_MIN(length, sizeof(buff)-1)); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_VALUE_FOR_TYPE, + ER_THD(thd, ER_WRONG_VALUE_FOR_TYPE), date_time_type, buff, "str_to_date"); } DBUG_RETURN(1); @@ -451,7 +455,7 @@ err: Create a formated date/time value in a string. */ -static bool make_date_time(DATE_TIME_FORMAT *format, MYSQL_TIME *l_time, +static bool make_date_time(const LEX_CSTRING &format, MYSQL_TIME *l_time, timestamp_type type, MY_LOCALE *locale, String *str) { char intbuff[15]; @@ -465,7 +469,7 @@ static bool make_date_time(DATE_TIME_FORMAT *format, MYSQL_TIME *l_time, if (l_time->neg) str->append('-'); - end= (ptr= format->format.str) + format->format.length; + end= (ptr= format.str) + format.length; for (; ptr != end ; ptr++) { if (*ptr != '%' || ptr+1 == end) @@ -573,7 +577,7 @@ static bool make_date_time(DATE_TIME_FORMAT *format, MYSQL_TIME *l_time, str->append_with_prefill(intbuff, length, 2, '0'); break; case 'j': - if (type == MYSQL_TIMESTAMP_TIME) + if (type == MYSQL_TIMESTAMP_TIME || !l_time->month || !l_time->year) return 1; length= (uint) (int10_to_str(calc_daynr(l_time->year,l_time->month, l_time->day) - @@ -699,7 +703,7 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs, { const char *end=str+length; uint i; - long msec_length= 0; + long field_length= 0; while (str != end && !my_isdigit(cs,*str)) str++; @@ -708,9 +712,10 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs, { longlong value; const char *start= str; - for (value=0; str != end && my_isdigit(cs,*str) ; str++) - value= value*LL(10) + (longlong) (*str - '0'); - msec_length= 6 - (str - start); + for (value= 0; str != end && my_isdigit(cs, *str); str++) + value= value*10 + *str - '0'; + if ((field_length= str - start) >= 20) + return true; values[i]= value; while (str != end && !my_isdigit(cs,*str)) str++; @@ -725,8 +730,13 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs, } } - if (transform_msec && msec_length > 0) - values[count - 1] *= (long) log_10_int[msec_length]; + if (transform_msec && field_length > 0) + { + if (field_length < 6) + values[count - 1] *= log_10_int[6 - field_length]; + else if (field_length > 6) + values[count - 1] /= log_10_int[field_length - 6]; + } return (str != end); } @@ -1075,7 +1085,7 @@ longlong Item_func_weekday::val_int() return (longlong) calc_weekday(calc_daynr(ltime.year, ltime.month, ltime.day), - odbc_type) + test(odbc_type); + odbc_type) + MY_TEST(odbc_type); } void Item_func_dayname::fix_length_and_dec() @@ -1093,7 +1103,7 @@ void Item_func_dayname::fix_length_and_dec() String* Item_func_dayname::val_str(String* str) { DBUG_ASSERT(fixed == 1); - uint weekday=(uint) val_int(); // Always Item_func_daynr() + uint weekday=(uint) val_int(); // Always Item_func_weekday() const char *day_name; uint err; @@ -1297,13 +1307,12 @@ bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval) interval->neg= my_decimal2seconds(val, &second, &second_part); if (second == LONGLONG_MAX) { - char buff[DECIMAL_MAX_STR_LENGTH]; - int length= sizeof(buff); - decimal2string(val, buff, &length, 0, 0, 0); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + THD *thd= current_thd; + ErrConvDecimal err(val); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), "DECIMAL", - buff); + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), "DECIMAL", + err.ptr()); return true; } @@ -1477,7 +1486,9 @@ void Item_temporal_func::fix_length_and_dec() (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE); collation.set(field_type() == MYSQL_TYPE_STRING ? default_charset() : &my_charset_numeric, - DERIVATION_NUMERIC, MY_REPERTOIRE_ASCII); + field_type() == MYSQL_TYPE_STRING ? + DERIVATION_COERCIBLE : DERIVATION_NUMERIC, + MY_REPERTOIRE_ASCII); fix_char_length(char_length); } @@ -1488,12 +1499,67 @@ String *Item_temporal_func::val_str(String *str) } +bool Item_temporal_hybrid_func::fix_temporal_type(MYSQL_TIME *ltime) +{ + if (ltime->time_type < 0) /* MYSQL_TIMESTAMP_NONE, MYSQL_TIMESTAMP_ERROR */ + return false; + + if (ltime->time_type != MYSQL_TIMESTAMP_TIME) + goto date_or_datetime_value; + + /* Convert TIME to DATE or DATETIME */ + switch (field_type()) + { + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + { + MYSQL_TIME tmp; + if (time_to_datetime_with_warn(current_thd, ltime, &tmp, 0)) + return (null_value= true); + *ltime= tmp; + if (field_type() == MYSQL_TYPE_DATE) + datetime_to_date(ltime); + return false; + } + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_STRING: /* DATE_ADD, ADDTIME can return VARCHAR */ + return false; + default: + DBUG_ASSERT(0); + return (null_value= true); + } + +date_or_datetime_value: + /* Convert DATE or DATETIME to TIME, DATE, or DATETIME */ + switch (field_type()) + { + case MYSQL_TYPE_TIME: + datetime_to_time(ltime); + return false; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + date_to_datetime(ltime); + return false; + case MYSQL_TYPE_DATE: + datetime_to_date(ltime); + return false; + case MYSQL_TYPE_STRING: /* DATE_ADD, ADDTIME can return VARCHAR */ + return false; + default: + DBUG_ASSERT(0); + return (null_value= true); + } + return false; +} + + String *Item_temporal_hybrid_func::val_str_ascii(String *str) { DBUG_ASSERT(fixed == 1); MYSQL_TIME ltime; - if (get_date(<ime, 0) || + if (get_date(<ime, 0) || fix_temporal_type(<ime) || (null_value= my_TIME_to_str(<ime, str, decimals))) return (String *) 0; @@ -1571,8 +1637,8 @@ bool Item_func_curtime::fix_fields(THD *thd, Item **items) { if (decimals > TIME_SECOND_PART_DIGITS) { - my_error(ER_TOO_BIG_PRECISION, MYF(0), decimals, func_name(), - TIME_SECOND_PART_DIGITS); + my_error(ER_TOO_BIG_PRECISION, MYF(0), static_cast<ulonglong>(decimals), + func_name(), TIME_SECOND_PART_DIGITS); return 1; } return Item_timefunc::fix_fields(thd, items); @@ -1593,7 +1659,7 @@ static void set_sec_part(ulong sec_part, MYSQL_TIME *ltime, Item *item) { ltime->second_part= sec_part; if (item->decimals < TIME_SECOND_PART_DIGITS) - ltime->second_part= sec_part_truncate(ltime->second_part, item->decimals); + my_time_trunc(ltime, item->decimals); } } @@ -1633,8 +1699,8 @@ bool Item_func_now::fix_fields(THD *thd, Item **items) { if (decimals > TIME_SECOND_PART_DIGITS) { - my_error(ER_TOO_BIG_PRECISION, MYF(0), decimals, func_name(), - TIME_SECOND_PART_DIGITS); + my_error(ER_TOO_BIG_PRECISION, MYF(0), static_cast<ulonglong>(decimals), + func_name(), TIME_SECOND_PART_DIGITS); return 1; } return Item_temporal_func::fix_fields(thd, items); @@ -1737,13 +1803,13 @@ overflow: if (!err) { ErrConvInteger err2(sec, unsigned_flag); - make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, &err2, MYSQL_TIMESTAMP_TIME, NullS); } else { ErrConvString err2(err); - make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, &err2, MYSQL_TIMESTAMP_TIME, NullS); } return 0; @@ -1769,13 +1835,13 @@ void Item_func_date_format::fix_length_and_dec() if (arg1->type() == STRING_ITEM) { // Optimize the normal case fixed_length=1; - max_length= format_length(&arg1->str_value) * + max_length= format_length(arg1->val_str(NULL)) * collation.collation->mbmaxlen; } else { fixed_length=0; - max_length=min(arg1->max_length, MAX_BLOB_WIDTH) * 10 * + max_length=MY_MIN(arg1->max_length, MAX_BLOB_WIDTH) * 10 * collation.collation->mbmaxlen; set_if_smaller(max_length,MAX_BLOB_WIDTH); } @@ -1883,6 +1949,7 @@ uint Item_func_date_format::format_length(const String *format) String *Item_func_date_format::val_str(String *str) { + StringBuffer<64> format_buffer; String *format; MYSQL_TIME l_time; uint size; @@ -1892,7 +1959,7 @@ String *Item_func_date_format::val_str(String *str) if (get_arg0_date(&l_time, is_time_flag)) return 0; - if (!(format = args[1]->val_str(str)) || !format->length()) + if (!(format= args[1]->val_str(&format_buffer)) || !format->length()) goto null_date; if (fixed_length) @@ -1903,18 +1970,13 @@ String *Item_func_date_format::val_str(String *str) if (size < MAX_DATE_STRING_REP_LENGTH) size= MAX_DATE_STRING_REP_LENGTH; - if (format == str) - str= &value; // Save result here + DBUG_ASSERT(format != str); if (str->alloc(size)) goto null_date; - DATE_TIME_FORMAT date_time_format; - date_time_format.format.str= (char*) format->ptr(); - date_time_format.format.length= format->length(); - /* Create the result string */ str->set_charset(collation.collation); - if (!make_date_time(&date_time_format, &l_time, + if (!make_date_time(format->lex_cstring(), &l_time, is_time_format ? MYSQL_TIMESTAMP_TIME : MYSQL_TIMESTAMP_DATE, locale, str)) @@ -2016,7 +2078,7 @@ void Item_date_add_interval::fix_length_and_dec() enum_field_types arg0_field_type; /* - The field type for the result of an Item_date function is defined as + The field type for the result of an Item_datefunc is defined as follows: - If first arg is a MYSQL_TYPE_DATETIME result is MYSQL_TYPE_DATETIME @@ -2038,12 +2100,12 @@ void Item_date_add_interval::fix_length_and_dec() int_type <= INTERVAL_SECOND_MICROSECOND)) interval_dec= TIME_SECOND_PART_DIGITS; else if (int_type == INTERVAL_SECOND && args[1]->decimals > 0) - interval_dec= min(args[1]->decimals, TIME_SECOND_PART_DIGITS); + interval_dec= MY_MIN(args[1]->decimals, TIME_SECOND_PART_DIGITS); if (arg0_field_type == MYSQL_TYPE_DATETIME || arg0_field_type == MYSQL_TYPE_TIMESTAMP) { - decimals= max(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), interval_dec); + decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), interval_dec); cached_field_type= MYSQL_TYPE_DATETIME; } else if (arg0_field_type == MYSQL_TYPE_DATE) @@ -2058,20 +2120,18 @@ void Item_date_add_interval::fix_length_and_dec() } else if (arg0_field_type == MYSQL_TYPE_TIME) { - decimals= max(args[0]->temporal_precision(MYSQL_TYPE_TIME), interval_dec); + decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME), interval_dec); if (int_type >= INTERVAL_DAY && int_type != INTERVAL_YEAR_MONTH) cached_field_type= arg0_field_type; else cached_field_type= MYSQL_TYPE_DATETIME; } else - decimals= max(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), interval_dec); + decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), interval_dec); Item_temporal_func::fix_length_and_dec(); } -/* Here arg[1] is a Item_interval object */ - bool Item_date_add_interval::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { INTERVAL interval; @@ -2145,26 +2205,26 @@ void Item_extract::fix_length_and_dec() { maybe_null=1; // If wrong date switch (int_type) { - case INTERVAL_YEAR: max_length=4; date_value=1; break; - case INTERVAL_YEAR_MONTH: max_length=6; date_value=1; break; - case INTERVAL_QUARTER: max_length=2; date_value=1; break; - case INTERVAL_MONTH: max_length=2; date_value=1; break; - case INTERVAL_WEEK: max_length=2; date_value=1; break; - case INTERVAL_DAY: max_length=2; date_value=1; break; - case INTERVAL_DAY_HOUR: max_length=9; date_value=0; break; - case INTERVAL_DAY_MINUTE: max_length=11; date_value=0; break; - case INTERVAL_DAY_SECOND: max_length=13; date_value=0; break; - case INTERVAL_HOUR: max_length=2; date_value=0; break; - case INTERVAL_HOUR_MINUTE: max_length=4; date_value=0; break; - case INTERVAL_HOUR_SECOND: max_length=6; date_value=0; break; - case INTERVAL_MINUTE: max_length=2; date_value=0; break; - case INTERVAL_MINUTE_SECOND: max_length=4; date_value=0; break; - case INTERVAL_SECOND: max_length=2; date_value=0; break; - case INTERVAL_MICROSECOND: max_length=2; date_value=0; break; - case INTERVAL_DAY_MICROSECOND: max_length=20; date_value=0; break; - case INTERVAL_HOUR_MICROSECOND: max_length=13; date_value=0; break; - case INTERVAL_MINUTE_MICROSECOND: max_length=11; date_value=0; break; - case INTERVAL_SECOND_MICROSECOND: max_length=9; date_value=0; break; + case INTERVAL_YEAR: set_date_length(4); break; // YYYY + case INTERVAL_YEAR_MONTH: set_date_length(6); break; // YYYYMM + case INTERVAL_QUARTER: set_date_length(2); break; // 1..4 + case INTERVAL_MONTH: set_date_length(2); break; // MM + case INTERVAL_WEEK: set_date_length(2); break; // 0..52 + case INTERVAL_DAY: set_date_length(2); break; // DD + case INTERVAL_DAY_HOUR: set_time_length(4); break; // DDhh + case INTERVAL_DAY_MINUTE: set_time_length(6); break; // DDhhmm + case INTERVAL_DAY_SECOND: set_time_length(8); break; // DDhhmmss + case INTERVAL_HOUR: set_time_length(2); break; // hh + case INTERVAL_HOUR_MINUTE: set_time_length(4); break; // hhmm + case INTERVAL_HOUR_SECOND: set_time_length(6); break; // hhmmss + case INTERVAL_MINUTE: set_time_length(2); break; // mm + case INTERVAL_MINUTE_SECOND: set_time_length(4); break; // mmss + case INTERVAL_SECOND: set_time_length(2); break; // ss + case INTERVAL_MICROSECOND: set_time_length(6); break; // ffffff + case INTERVAL_DAY_MICROSECOND: set_time_length(14); break; // DDhhmmssffffff + case INTERVAL_HOUR_MICROSECOND: set_time_length(12); break; // hhmmssffffff + case INTERVAL_MINUTE_MICROSECOND: set_time_length(10); break; // mmssffffff + case INTERVAL_SECOND_MICROSECOND: set_time_length(8); break; // ssffffff case INTERVAL_LAST: DBUG_ASSERT(0); break; /* purecov: deadcode */ } } @@ -2179,8 +2239,10 @@ longlong Item_extract::val_int() long neg; int is_time_flag = date_value ? 0 : TIME_TIME_ONLY; - if (get_arg0_date(<ime, is_time_flag)) + // Not using get_arg0_date to avoid automatic TIME to DATETIME conversion + if ((null_value= args[0]->get_date(<ime, is_time_flag))) return 0; + neg= ltime.neg ? -1 : 1; DBUG_ASSERT(ltime.time_type != MYSQL_TIMESTAMP_TIME || ltime.day == 0); @@ -2309,105 +2371,124 @@ void Item_char_typecast::print(String *str, enum_query_type query_type) str->append(')'); } + +void Item_char_typecast::check_truncation_with_warn(String *src, uint dstlen) +{ + if (dstlen < src->length()) + { + THD *thd= current_thd; + char char_type[40]; + ErrConvString err(src); + my_snprintf(char_type, sizeof(char_type), "%s(%lu)", + cast_cs == &my_charset_bin ? "BINARY" : "CHAR", + (ulong) cast_length); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_TRUNCATED_WRONG_VALUE, + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), char_type, + err.ptr()); + } +} + + +String *Item_char_typecast::reuse(String *src, uint32 length) +{ + DBUG_ASSERT(length <= src->length()); + check_truncation_with_warn(src, length); + tmp_value.set(src->ptr(), length, cast_cs); + return &tmp_value; +} + + +/* + Make a copy, to handle conversion or fix bad bytes. +*/ +String *Item_char_typecast::copy(String *str, CHARSET_INFO *strcs) +{ + String_copier_for_item copier(current_thd); + if (copier.copy_with_warn(cast_cs, &tmp_value, strcs, + str->ptr(), str->length(), cast_length)) + { + null_value= 1; // EOM + return 0; + } + check_truncation_with_warn(str, copier.source_end_pos() - str->ptr()); + return &tmp_value; +} + + +uint Item_char_typecast::adjusted_length_with_warn(uint length) +{ + if (length <= current_thd->variables.max_allowed_packet) + return length; + + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED), + cast_cs == &my_charset_bin ? + "cast_as_binary" : func_name(), + thd->variables.max_allowed_packet); + return thd->variables.max_allowed_packet; +} + + String *Item_char_typecast::val_str(String *str) { DBUG_ASSERT(fixed == 1); String *res; - uint32 length; - if (cast_length != ~0U && - cast_length > current_thd->variables.max_allowed_packet) + if (has_explicit_length()) + cast_length= adjusted_length_with_warn(cast_length); + + if (!(res= args[0]->val_str(str))) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - cast_cs == &my_charset_bin ? - "cast_as_binary" : func_name(), - current_thd->variables.max_allowed_packet); - cast_length= current_thd->variables.max_allowed_packet; + null_value= 1; + return 0; } - if (!charset_conversion) + if (cast_cs == &my_charset_bin && + has_explicit_length() && + cast_length > res->length()) { - if (!(res= args[0]->val_str(str))) + // Special case: pad binary value with trailing 0x00 + DBUG_ASSERT(cast_length <= current_thd->variables.max_allowed_packet); + if (res->alloced_length() < cast_length) { - null_value= 1; - return 0; + str_value.alloc(cast_length); + str_value.copy(*res); + res= &str_value; } + bzero((char*) res->ptr() + res->length(), cast_length - res->length()); + res->length(cast_length); + res->set_charset(&my_charset_bin); } else { /* - Convert character set if differ from_cs is 0 in the case where the result set may vary between calls, for example with dynamic columns. */ - uint dummy_errors; - if (!(res= args[0]->val_str(str)) || - tmp_value.copy(res->ptr(), res->length(), - from_cs ? from_cs : res->charset(), - cast_cs, &dummy_errors)) - { - null_value= 1; - return 0; - } - res= &tmp_value; - } - - res->set_charset(cast_cs); - - /* - Cut the tail if cast with length - and the result is longer than cast length, e.g. - CAST('string' AS CHAR(1)) - */ - if (cast_length != ~0U) - { - if (res->length() > (length= (uint32) res->charpos(cast_length))) - { // Safe even if const arg - char char_type[40]; - my_snprintf(char_type, sizeof(char_type), "%s(%lu)", - cast_cs == &my_charset_bin ? "BINARY" : "CHAR", - (ulong) length); - - if (!res->alloced_length()) - { // Don't change const str - str_value= *res; // Not malloced string - res= &str_value; - } - ErrConvString err(res); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), char_type, - err.ptr()); - res->length((uint) length); - } - else if (cast_cs == &my_charset_bin && res->length() < cast_length) + CHARSET_INFO *cs= from_cs ? from_cs : res->charset(); + if (!charset_conversion) { - if (res->alloced_length() < cast_length) + // Try to reuse the original string (if well formed). + MY_STRCOPY_STATUS status; + cs->cset->well_formed_char_length(cs, res->ptr(), res->end(), + cast_length, &status); + if (!status.m_well_formed_error_pos) { - str_value.alloc(cast_length); - str_value.copy(*res); - res= &str_value; + res= reuse(res, status.m_source_end_pos - res->ptr()); } - bzero((char*) res->ptr() + res->length(), cast_length - res->length()); - res->length(cast_length); + goto end; } + // Character set conversion, or bad bytes were found. + if (!(res= copy(res, cs))) + return 0; } - null_value= 0; - if (res->length() > current_thd->variables.max_allowed_packet) - { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_ALLOWED_PACKET_OVERFLOWED, - ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), - cast_cs == &my_charset_bin ? - "cast_as_binary" : func_name(), - current_thd->variables.max_allowed_packet); - null_value= 1; - return 0; - } - return res; +end: + return ((null_value= (res->length() > + adjusted_length_with_warn(res->length())))) ? 0 : res; } @@ -2465,7 +2546,7 @@ bool Item_time_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) if (get_arg0_time(ltime)) return 1; if (decimals < TIME_SECOND_PART_DIGITS) - ltime->second_part= sec_part_truncate(ltime->second_part, decimals); + my_time_trunc(ltime, decimals); /* MYSQL_TIMESTAMP_TIME value can have non-zero day part, which we should not lose. @@ -2481,6 +2562,7 @@ bool Item_time_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) bool Item_date_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { + fuzzy_date |= sql_mode_for_dates(current_thd); if (get_arg0_date(ltime, fuzzy_date & ~TIME_TIME_ONLY)) return 1; @@ -2493,15 +2575,15 @@ bool Item_date_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) bool Item_datetime_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { + fuzzy_date |= sql_mode_for_dates(current_thd); if (get_arg0_date(ltime, fuzzy_date & ~TIME_TIME_ONLY)) return 1; if (decimals < TIME_SECOND_PART_DIGITS) - ltime->second_part= sec_part_truncate(ltime->second_part, decimals); - - if (make_date_with_warn(ltime, fuzzy_date, MYSQL_TIMESTAMP_DATETIME)) - return (null_value= 1); + my_time_trunc(ltime, decimals); + DBUG_ASSERT(ltime->time_type != MYSQL_TIMESTAMP_TIME); + ltime->time_type= MYSQL_TIMESTAMP_DATETIME; return 0; } @@ -2547,7 +2629,7 @@ err: void Item_func_add_time::fix_length_and_dec() { enum_field_types arg0_field_type; - decimals= max(args[0]->decimals, args[1]->decimals); + decimals= MY_MAX(args[0]->decimals, args[1]->decimals); /* The field type for the result of an Item_func_add_time function is defined @@ -2567,14 +2649,14 @@ void Item_func_add_time::fix_length_and_dec() is_date) { cached_field_type= MYSQL_TYPE_DATETIME; - decimals= max(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), - args[1]->temporal_precision(MYSQL_TYPE_TIME)); + decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), + args[1]->temporal_precision(MYSQL_TYPE_TIME)); } else if (arg0_field_type == MYSQL_TYPE_TIME) { cached_field_type= MYSQL_TYPE_TIME; - decimals= max(args[0]->temporal_precision(MYSQL_TYPE_TIME), - args[1]->temporal_precision(MYSQL_TYPE_TIME)); + decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME), + args[1]->temporal_precision(MYSQL_TYPE_TIME)); } Item_temporal_func::fix_length_and_dec(); } @@ -2598,16 +2680,18 @@ bool Item_func_add_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) longlong seconds; int l_sign= sign; - if (is_date) // TIMESTAMP function + if (cached_field_type == MYSQL_TYPE_DATETIME) { + // TIMESTAMP function OR the first argument is DATE/DATETIME/TIMESTAMP if (get_arg0_date(&l_time1, 0) || args[1]->get_time(&l_time2) || l_time1.time_type == MYSQL_TIMESTAMP_TIME || l_time2.time_type != MYSQL_TIMESTAMP_TIME) return (null_value= 1); } - else // ADDTIME function + else { + // ADDTIME function AND the first argument is TIME if (args[0]->get_time(&l_time1) || args[1]->get_time(&l_time2) || l_time2.time_type == MYSQL_TIMESTAMP_DATETIME) @@ -2632,9 +2716,9 @@ bool Item_func_add_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) if (!is_time && ltime->neg) return (null_value= 1); - days= (long)(seconds/86400L); + days= (long) (seconds / SECONDS_IN_24H); - calc_time_from_sec(ltime, (long)(seconds%86400L), microseconds); + calc_time_from_sec(ltime, (long)(seconds % SECONDS_IN_24H), microseconds); ltime->time_type= is_time ? MYSQL_TIMESTAMP_TIME : MYSQL_TIMESTAMP_DATETIME; @@ -2683,8 +2767,6 @@ void Item_func_add_time::print(String *str, enum_query_type query_type) bool Item_func_timediff::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { DBUG_ASSERT(fixed == 1); - longlong seconds; - long microseconds; int l_sign= 1; MYSQL_TIME l_time1,l_time2,l_time3; ErrConvTime str(&l_time3); @@ -2701,36 +2783,11 @@ bool Item_func_timediff::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) if (l_time1.neg != l_time2.neg) l_sign= -l_sign; - bzero((char *)&l_time3, sizeof(l_time3)); - - l_time3.neg= calc_time_diff(&l_time1, &l_time2, l_sign, - &seconds, µseconds); - - /* - For MYSQL_TIMESTAMP_TIME only: - If first argument was negative and diff between arguments - is non-zero we need to swap sign to get proper result. - */ - if (l_time1.neg && (seconds || microseconds)) - l_time3.neg= 1-l_time3.neg; // Swap sign of result - - /* - seconds is longlong, when casted to long it may become a small number - even if the original seconds value was too large and invalid. - as a workaround we limit seconds by a large invalid long number - ("invalid" means > TIME_MAX_SECOND) - */ - set_if_smaller(seconds, INT_MAX32); - - calc_time_from_sec(&l_time3, (long) seconds, microseconds); - - if ((fuzzy_date & TIME_NO_ZERO_DATE) && (seconds == 0) && - (microseconds == 0)) + if (calc_time_diff(&l_time1, &l_time2, l_sign, &l_time3, fuzzy_date)) return (null_value= 1); *ltime= l_time3; return (null_value= adjust_time_range_with_warn(ltime, decimals)); - } /** @@ -2772,7 +2829,7 @@ bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) char buf[28]; char *ptr= longlong10_to_str(hour.value(), buf, hour.is_unsigned() ? 10 : -10); int len = (int)(ptr - buf) + sprintf(ptr, ":%02u:%02u", (uint)minute, (uint)second); - make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, buf, len, MYSQL_TIMESTAMP_TIME, NullS); } @@ -2808,8 +2865,12 @@ longlong Item_func_timestamp_diff::val_int() int neg= 1; null_value= 0; - if (args[0]->get_date(<ime1, TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE) || - args[1]->get_date(<ime2, TIME_NO_ZERO_DATE | TIME_NO_ZERO_IN_DATE)) + if (args[0]->get_date_with_conversion(<ime1, + TIME_NO_ZERO_DATE | + TIME_NO_ZERO_IN_DATE) || + args[1]->get_date_with_conversion(<ime2, + TIME_NO_ZERO_DATE | + TIME_NO_ZERO_IN_DATE)) goto null_date; if (calc_time_diff(<ime2,<ime1, 1, @@ -2879,9 +2940,9 @@ longlong Item_func_timestamp_diff::val_int() case INTERVAL_MONTH: return months*neg; case INTERVAL_WEEK: - return seconds/86400L/7L*neg; + return seconds / SECONDS_IN_24H / 7L * neg; case INTERVAL_DAY: - return seconds/86400L*neg; + return seconds / SECONDS_IN_24H * neg; case INTERVAL_HOUR: return seconds/3600L*neg; case INTERVAL_MINUTE: @@ -3090,7 +3151,7 @@ void Item_func_str_to_date::fix_length_and_dec() } cached_field_type= MYSQL_TYPE_DATETIME; - decimals= NOT_FIXED_DEC; + decimals= TIME_SECOND_PART_DIGITS; if ((const_item= args[1]->const_item())) { char format_buff[64]; @@ -3143,7 +3204,7 @@ bool Item_func_str_to_date::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) date_time_format.format.length= format->length(); if (extract_date_time(&date_time_format, val->ptr(), val->length(), ltime, cached_timestamp_type, 0, "datetime", - fuzzy_date)) + fuzzy_date | sql_mode_for_dates(current_thd))) return (null_value=1); if (cached_timestamp_type == MYSQL_TIMESTAMP_TIME && ltime->day) { diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index 5be9c5cacbd..927ce12f079 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -46,7 +46,7 @@ bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval); class Item_func_period_add :public Item_int_func { public: - Item_func_period_add(Item *a,Item *b) :Item_int_func(a,b) {} + Item_func_period_add(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} longlong val_int(); const char *func_name() const { return "period_add"; } void fix_length_and_dec() @@ -59,7 +59,7 @@ public: class Item_func_period_diff :public Item_int_func { public: - Item_func_period_diff(Item *a,Item *b) :Item_int_func(a,b) {} + Item_func_period_diff(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} longlong val_int(); const char *func_name() const { return "period_diff"; } void fix_length_and_dec() @@ -73,7 +73,7 @@ public: class Item_func_to_days :public Item_int_func { public: - Item_func_to_days(Item *a) :Item_int_func(a) {} + Item_func_to_days(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "to_days"; } void fix_length_and_dec() @@ -96,7 +96,7 @@ public: class Item_func_to_seconds :public Item_int_func { public: - Item_func_to_seconds(Item *a) :Item_int_func(a) {} + Item_func_to_seconds(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "to_seconds"; } void fix_length_and_dec() @@ -113,7 +113,7 @@ public: { int *input_version= (int*)int_arg; /* This function was introduced in 5.5 */ - int output_version= max(*input_version, 50500); + int output_version= MY_MAX(*input_version, 50500); *input_version= output_version; return 0; } @@ -129,7 +129,7 @@ public: class Item_func_dayofmonth :public Item_int_func { public: - Item_func_dayofmonth(Item *a) :Item_int_func(a) {} + Item_func_dayofmonth(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "dayofmonth"; } void fix_length_and_dec() @@ -150,7 +150,8 @@ public: class Item_func_month :public Item_func { public: - Item_func_month(Item *a) :Item_func(a) { collation.set_numeric(); } + Item_func_month(THD *thd, Item *a): Item_func(thd, a) + { collation.set_numeric(); } longlong val_int(); double val_real() { DBUG_ASSERT(fixed == 1); return (double) Item_func_month::val_int(); } @@ -183,7 +184,7 @@ class Item_func_monthname :public Item_str_func { MY_LOCALE *locale; public: - Item_func_monthname(Item *a) :Item_str_func(a) {} + Item_func_monthname(THD *thd, Item *a): Item_str_func(thd, a) {} const char *func_name() const { return "monthname"; } String *val_str(String *str); void fix_length_and_dec(); @@ -199,7 +200,7 @@ public: class Item_func_dayofyear :public Item_int_func { public: - Item_func_dayofyear(Item *a) :Item_int_func(a) {} + Item_func_dayofyear(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "dayofyear"; } void fix_length_and_dec() @@ -220,7 +221,7 @@ public: class Item_func_hour :public Item_int_func { public: - Item_func_hour(Item *a) :Item_int_func(a) {} + Item_func_hour(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "hour"; } void fix_length_and_dec() @@ -241,7 +242,7 @@ public: class Item_func_minute :public Item_int_func { public: - Item_func_minute(Item *a) :Item_int_func(a) {} + Item_func_minute(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "minute"; } void fix_length_and_dec() @@ -262,7 +263,7 @@ public: class Item_func_quarter :public Item_int_func { public: - Item_func_quarter(Item *a) :Item_int_func(a) {} + Item_func_quarter(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "quarter"; } void fix_length_and_dec() @@ -283,7 +284,7 @@ public: class Item_func_second :public Item_int_func { public: - Item_func_second(Item *a) :Item_int_func(a) {} + Item_func_second(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "second"; } void fix_length_and_dec() @@ -304,7 +305,7 @@ public: class Item_func_week :public Item_int_func { public: - Item_func_week(Item *a,Item *b) :Item_int_func(a,b) {} + Item_func_week(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} longlong val_int(); const char *func_name() const { return "week"; } void fix_length_and_dec() @@ -318,7 +319,7 @@ public: class Item_func_yearweek :public Item_int_func { public: - Item_func_yearweek(Item *a,Item *b) :Item_int_func(a,b) {} + Item_func_yearweek(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {} longlong val_int(); const char *func_name() const { return "yearweek"; } void fix_length_and_dec() @@ -339,7 +340,7 @@ public: class Item_func_year :public Item_int_func { public: - Item_func_year(Item *a) :Item_int_func(a) {} + Item_func_year(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "year"; } enum_monotonicity_info get_monotonicity_info() const; @@ -363,8 +364,8 @@ class Item_func_weekday :public Item_func { bool odbc_type; public: - Item_func_weekday(Item *a,bool type_arg) - :Item_func(a), odbc_type(type_arg) { collation.set_numeric(); } + Item_func_weekday(THD *thd, Item *a, bool type_arg): + Item_func(thd, a), odbc_type(type_arg) { collation.set_numeric(); } longlong val_int(); double val_real() { DBUG_ASSERT(fixed == 1); return (double) val_int(); } String *val_str(String *str) @@ -396,7 +397,7 @@ class Item_func_dayname :public Item_func_weekday { MY_LOCALE *locale; public: - Item_func_dayname(Item *a) :Item_func_weekday(a,0) {} + Item_func_dayname(THD *thd, Item *a): Item_func_weekday(thd, a, 0) {} const char *func_name() const { return "dayname"; } String *val_str(String *str); enum Item_result result_type () const { return STRING_RESULT; } @@ -411,8 +412,8 @@ class Item_func_seconds_hybrid: public Item_func_numhybrid protected: virtual enum_field_types arg0_expected_type() const = 0; public: - Item_func_seconds_hybrid() :Item_func_numhybrid() {} - Item_func_seconds_hybrid(Item *a) :Item_func_numhybrid(a) {} + Item_func_seconds_hybrid(THD *thd): Item_func_numhybrid(thd) {} + Item_func_seconds_hybrid(THD *thd, Item *a): Item_func_numhybrid(thd, a) {} void fix_length_and_dec() { if (arg_count) @@ -420,7 +421,7 @@ public: set_if_smaller(decimals, TIME_SECOND_PART_DIGITS); max_length=17 + (decimals ? decimals + 1 : 0); maybe_null= true; - cached_result_type= decimals ? DECIMAL_RESULT : INT_RESULT; + set_handler_by_result_type(decimals ? DECIMAL_RESULT : INT_RESULT); } double real_op() { DBUG_ASSERT(0); return 0; } String *str_op(String *str) { DBUG_ASSERT(0); return 0; } @@ -434,8 +435,9 @@ class Item_func_unix_timestamp :public Item_func_seconds_hybrid protected: enum_field_types arg0_expected_type() const { return MYSQL_TYPE_DATETIME; } public: - Item_func_unix_timestamp() :Item_func_seconds_hybrid() {} - Item_func_unix_timestamp(Item *a) :Item_func_seconds_hybrid(a) {} + Item_func_unix_timestamp(THD *thd): Item_func_seconds_hybrid(thd) {} + Item_func_unix_timestamp(THD *thd, Item *a): + Item_func_seconds_hybrid(thd, a) {} const char *func_name() const { return "unix_timestamp"; } enum_monotonicity_info get_monotonicity_info() const; longlong val_int_endpoint(bool left_endp, bool *incl_endp); @@ -467,7 +469,8 @@ class Item_func_time_to_sec :public Item_func_seconds_hybrid protected: enum_field_types arg0_expected_type() const { return MYSQL_TYPE_TIME; } public: - Item_func_time_to_sec(Item *item) :Item_func_seconds_hybrid(item) {} + Item_func_time_to_sec(THD *thd, Item *item): + Item_func_seconds_hybrid(thd, item) {} const char *func_name() const { return "time_to_sec"; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -484,12 +487,11 @@ class Item_temporal_func: public Item_func { ulonglong sql_mode; public: - Item_temporal_func() :Item_func() {} - Item_temporal_func(Item *a) :Item_func(a) {} - Item_temporal_func(Item *a, Item *b) :Item_func(a,b) {} - Item_temporal_func(Item *a, Item *b, Item *c) :Item_func(a,b,c) {} + Item_temporal_func(THD *thd): Item_func(thd) {} + Item_temporal_func(THD *thd, Item *a): Item_func(thd, a) {} + Item_temporal_func(THD *thd, Item *a, Item *b): Item_func(thd, a, b) {} + Item_temporal_func(THD *thd, Item *a, Item *b, Item *c): Item_func(thd, a, b, c) {} enum Item_result result_type () const { return STRING_RESULT; } - CHARSET_INFO *charset_for_protocol(void) const { return &my_charset_bin; } enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; } Item_result cmp_type() const { return TIME_RESULT; } String *val_str(String *str); @@ -498,8 +500,8 @@ public: bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date) { DBUG_ASSERT(0); return 1; } my_decimal *val_decimal(my_decimal *decimal_value) { return val_decimal_from_date(decimal_value); } - Field *tmp_table_field(TABLE *table) - { return tmp_table_field_from_field_type(table, 0); } + Field *create_field_for_create_select(TABLE *table) + { return tmp_table_field_from_field_type(table, false, false); } #if MARIADB_VERSION_ID > 100300 #error This code should be removed in 10.3, to use the derived save_in_field() #else @@ -524,15 +526,15 @@ protected: enum_field_types cached_field_type; // TIME, DATE, DATETIME or STRING String ascii_buf; // Conversion buffer public: - Item_temporal_hybrid_func(Item *a,Item *b) - :Item_temporal_func(a,b) {} + Item_temporal_hybrid_func(THD *thd, Item *a, Item *b): + Item_temporal_func(thd, a, b) {} enum_field_types field_type() const { return cached_field_type; } Item_result cmp_type() const { return cached_field_type == MYSQL_TYPE_STRING ? STRING_RESULT : TIME_RESULT; } - const CHARSET_INFO *charset_for_protocol() const + CHARSET_INFO *charset_for_protocol() const { /* Can return TIME, DATE, DATETIME or VARCHAR depending on arguments. @@ -545,6 +547,11 @@ public: collation.collation : &my_charset_bin; } /** + Fix the returned timestamp to match field_type(), + which is important for val_str(). + */ + bool fix_temporal_type(MYSQL_TIME *ltime); + /** Return string value in ASCII character set. */ String *val_str_ascii(String *str); @@ -561,8 +568,8 @@ public: class Item_datefunc :public Item_temporal_func { public: - Item_datefunc() :Item_temporal_func() { } - Item_datefunc(Item *a) :Item_temporal_func(a) { } + Item_datefunc(THD *thd): Item_temporal_func(thd) { } + Item_datefunc(THD *thd, Item *a): Item_temporal_func(thd, a) { } enum_field_types field_type() const { return MYSQL_TYPE_DATE; } }; @@ -570,10 +577,11 @@ public: class Item_timefunc :public Item_temporal_func { public: - Item_timefunc() :Item_temporal_func() {} - Item_timefunc(Item *a) :Item_temporal_func(a) {} - Item_timefunc(Item *a,Item *b) :Item_temporal_func(a,b) {} - Item_timefunc(Item *a, Item *b, Item *c) :Item_temporal_func(a, b ,c) {} + Item_timefunc(THD *thd): Item_temporal_func(thd) {} + Item_timefunc(THD *thd, Item *a): Item_temporal_func(thd, a) {} + Item_timefunc(THD *thd, Item *a, Item *b): Item_temporal_func(thd, a, b) {} + Item_timefunc(THD *thd, Item *a, Item *b, Item *c): + Item_temporal_func(thd, a, b ,c) {} enum_field_types field_type() const { return MYSQL_TYPE_TIME; } }; @@ -584,7 +592,7 @@ class Item_func_curtime :public Item_timefunc { MYSQL_TIME ltime; public: - Item_func_curtime(uint dec) :Item_timefunc() { decimals= dec; } + Item_func_curtime(THD *thd, uint dec): Item_timefunc(thd) { decimals= dec; } bool fix_fields(THD *, Item **); void fix_length_and_dec() { @@ -609,7 +617,7 @@ public: class Item_func_curtime_local :public Item_func_curtime { public: - Item_func_curtime_local(uint dec) :Item_func_curtime(dec) {} + Item_func_curtime_local(THD *thd, uint dec): Item_func_curtime(thd, dec) {} const char *func_name() const { return "curtime"; } virtual void store_now_in_TIME(MYSQL_TIME *now_time); }; @@ -618,7 +626,7 @@ public: class Item_func_curtime_utc :public Item_func_curtime { public: - Item_func_curtime_utc(uint dec) :Item_func_curtime(dec) {} + Item_func_curtime_utc(THD *thd, uint dec): Item_func_curtime(thd, dec) {} const char *func_name() const { return "utc_time"; } virtual void store_now_in_TIME(MYSQL_TIME *now_time); }; @@ -630,7 +638,7 @@ class Item_func_curdate :public Item_datefunc { MYSQL_TIME ltime; public: - Item_func_curdate() :Item_datefunc() {} + Item_func_curdate(THD *thd): Item_datefunc(thd) {} void fix_length_and_dec(); bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); virtual void store_now_in_TIME(MYSQL_TIME *now_time)=0; @@ -644,7 +652,7 @@ public: class Item_func_curdate_local :public Item_func_curdate { public: - Item_func_curdate_local() :Item_func_curdate() {} + Item_func_curdate_local(THD *thd): Item_func_curdate(thd) {} const char *func_name() const { return "curdate"; } void store_now_in_TIME(MYSQL_TIME *now_time); }; @@ -653,7 +661,7 @@ public: class Item_func_curdate_utc :public Item_func_curdate { public: - Item_func_curdate_utc() :Item_func_curdate() {} + Item_func_curdate_utc(THD *thd): Item_func_curdate(thd) {} const char *func_name() const { return "utc_date"; } void store_now_in_TIME(MYSQL_TIME *now_time); }; @@ -666,7 +674,7 @@ class Item_func_now :public Item_temporal_func { MYSQL_TIME ltime; public: - Item_func_now(uint dec) :Item_temporal_func() { decimals= dec; } + Item_func_now(THD *thd, uint dec): Item_temporal_func(thd) { decimals= dec; } bool fix_fields(THD *, Item **); void fix_length_and_dec() { @@ -686,7 +694,7 @@ public: class Item_func_now_local :public Item_func_now { public: - Item_func_now_local(uint dec) :Item_func_now(dec) {} + Item_func_now_local(THD *thd, uint dec): Item_func_now(thd, dec) {} const char *func_name() const { return "now"; } virtual void store_now_in_TIME(MYSQL_TIME *now_time); virtual enum Functype functype() const { return NOW_FUNC; } @@ -696,7 +704,7 @@ public: class Item_func_now_utc :public Item_func_now { public: - Item_func_now_utc(uint dec) :Item_func_now(dec) {} + Item_func_now_utc(THD *thd, uint dec): Item_func_now(thd, dec) {} const char *func_name() const { return "utc_timestamp"; } virtual void store_now_in_TIME(MYSQL_TIME *now_time); }; @@ -709,7 +717,7 @@ public: class Item_func_sysdate_local :public Item_func_now { public: - Item_func_sysdate_local(uint dec) :Item_func_now(dec) {} + Item_func_sysdate_local(THD *thd, uint dec): Item_func_now(thd, dec) {} bool const_item() const { return 0; } const char *func_name() const { return "sysdate"; } void store_now_in_TIME(MYSQL_TIME *now_time); @@ -726,7 +734,7 @@ public: class Item_func_from_days :public Item_datefunc { public: - Item_func_from_days(Item *a) :Item_datefunc(a) {} + Item_func_from_days(THD *thd, Item *a): Item_datefunc(thd, a) {} const char *func_name() const { return "from_days"; } bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); bool check_partition_func_processor(uchar *int_arg) {return FALSE;} @@ -745,8 +753,8 @@ class Item_func_date_format :public Item_str_func const bool is_time_format; String value; public: - Item_func_date_format(Item *a,Item *b,bool is_time_format_arg) - :Item_str_func(a,b),is_time_format(is_time_format_arg) {} + Item_func_date_format(THD *thd, Item *a, Item *b, bool is_time_format_arg): + Item_str_func(thd, a, b), is_time_format(is_time_format_arg) {} String *val_str(String *str); const char *func_name() const { return is_time_format ? "time_format" : "date_format"; } @@ -760,7 +768,7 @@ class Item_func_from_unixtime :public Item_temporal_func { Time_zone *tz; public: - Item_func_from_unixtime(Item *a) :Item_temporal_func(a) {} + Item_func_from_unixtime(THD *thd, Item *a): Item_temporal_func(thd, a) {} const char *func_name() const { return "from_unixtime"; } void fix_length_and_dec(); bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); @@ -792,8 +800,8 @@ class Item_func_convert_tz :public Item_temporal_func bool from_tz_cached, to_tz_cached; Time_zone *from_tz, *to_tz; public: - Item_func_convert_tz(Item *a, Item *b, Item *c): - Item_temporal_func(a, b, c), from_tz_cached(0), to_tz_cached(0) {} + Item_func_convert_tz(THD *thd, Item *a, Item *b, Item *c): + Item_temporal_func(thd, a, b, c), from_tz_cached(0), to_tz_cached(0) {} const char *func_name() const { return "convert_tz"; } void fix_length_and_dec(); bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); @@ -804,11 +812,11 @@ class Item_func_convert_tz :public Item_temporal_func class Item_func_sec_to_time :public Item_timefunc { public: - Item_func_sec_to_time(Item *item) :Item_timefunc(item) {} + Item_func_sec_to_time(THD *thd, Item *item): Item_timefunc(thd, item) {} bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); void fix_length_and_dec() { - decimals= args[0]->decimals; + decimals= MY_MIN(args[0]->decimals, TIME_SECOND_PART_DIGITS); Item_timefunc::fix_length_and_dec(); } const char *func_name() const { return "sec_to_time"; } @@ -820,8 +828,10 @@ class Item_date_add_interval :public Item_temporal_hybrid_func public: const interval_type int_type; // keep it public const bool date_sub_interval; // keep it public - Item_date_add_interval(Item *a,Item *b,interval_type type_arg,bool neg_arg) - :Item_temporal_hybrid_func(a,b),int_type(type_arg), date_sub_interval(neg_arg) {} + Item_date_add_interval(THD *thd, Item *a, Item *b, interval_type type_arg, + bool neg_arg): + Item_temporal_hybrid_func(thd, a, b),int_type(type_arg), + date_sub_interval(neg_arg) {} const char *func_name() const { return "date_add_interval"; } void fix_length_and_dec(); bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); @@ -833,10 +843,57 @@ public: class Item_extract :public Item_int_func { bool date_value; + void set_date_length(uint32 length) + { + /* + Although DATE components (e.g. YEAR, YEAR_MONTH, QUARTER, MONTH, WEEK) + cannot have a sign, we should probably still add +1, + because all around the code we assume that max_length is sign inclusive. + Another options is to set unsigned_flag to "true". + */ + max_length= length; //QQ: see above + date_value= true; + } + void set_time_length(uint32 length) + { + max_length= length + 1/*sign*/; + date_value= false; + } public: const interval_type int_type; // keep it public - Item_extract(interval_type type_arg, Item *a) - :Item_int_func(a), int_type(type_arg) {} + Item_extract(THD *thd, interval_type type_arg, Item *a): + Item_int_func(thd, a), int_type(type_arg) {} + enum_field_types field_type() const + { + switch (int_type) { + case INTERVAL_YEAR: + case INTERVAL_YEAR_MONTH: + case INTERVAL_QUARTER: + case INTERVAL_MONTH: + case INTERVAL_WEEK: + case INTERVAL_DAY: + case INTERVAL_DAY_HOUR: + case INTERVAL_DAY_MINUTE: + case INTERVAL_DAY_SECOND: + case INTERVAL_HOUR: + case INTERVAL_HOUR_MINUTE: + case INTERVAL_HOUR_SECOND: + case INTERVAL_MINUTE: + case INTERVAL_MINUTE_SECOND: + case INTERVAL_SECOND: + case INTERVAL_MICROSECOND: + case INTERVAL_SECOND_MICROSECOND: + return MYSQL_TYPE_LONG; + case INTERVAL_DAY_MICROSECOND: + case INTERVAL_HOUR_MICROSECOND: + case INTERVAL_MINUTE_MICROSECOND: + return MYSQL_TYPE_LONGLONG; + case INTERVAL_LAST: + break; + } + DBUG_ASSERT(0); + return MYSQL_TYPE_LONGLONG; + } longlong val_int(); enum Functype functype() const { return EXTRACT_FUNC; } const char *func_name() const { return "extract"; } @@ -881,6 +938,8 @@ class Item_extract :public Item_int_func } return true; } + Field *create_field_for_create_select(TABLE *table) + { return tmp_table_field_from_field_type(table, false, false); } }; @@ -890,9 +949,14 @@ class Item_char_typecast :public Item_str_func CHARSET_INFO *cast_cs, *from_cs; bool charset_conversion; String tmp_value; + bool has_explicit_length() const { return cast_length != ~0U; } + String *reuse(String *src, uint32 length); + String *copy(String *src, CHARSET_INFO *cs); + uint adjusted_length_with_warn(uint length); + void check_truncation_with_warn(String *src, uint dstlen); public: - Item_char_typecast(Item *a, uint length_arg, CHARSET_INFO *cs_arg) - :Item_str_func(a), cast_length(length_arg), cast_cs(cs_arg) {} + Item_char_typecast(THD *thd, Item *a, uint length_arg, CHARSET_INFO *cs_arg): + Item_str_func(thd, a), cast_length(length_arg), cast_cs(cs_arg) {} enum Functype functype() const { return CHAR_TYPECAST_FUNC; } bool eq(const Item *item, bool binary_cmp) const; const char *func_name() const { return "cast_as_char"; } @@ -905,7 +969,7 @@ public: class Item_temporal_typecast: public Item_temporal_func { public: - Item_temporal_typecast(Item *a) :Item_temporal_func(a) {} + Item_temporal_typecast(THD *thd, Item *a): Item_temporal_func(thd, a) {} virtual const char *cast_type() const = 0; void print(String *str, enum_query_type query_type); void fix_length_and_dec() @@ -919,7 +983,7 @@ public: class Item_date_typecast :public Item_temporal_typecast { public: - Item_date_typecast(Item *a) :Item_temporal_typecast(a) {} + Item_date_typecast(THD *thd, Item *a): Item_temporal_typecast(thd, a) {} const char *func_name() const { return "cast_as_date"; } bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date); const char *cast_type() const { return "date"; } @@ -930,8 +994,8 @@ public: class Item_time_typecast :public Item_temporal_typecast { public: - Item_time_typecast(Item *a, uint dec_arg) - :Item_temporal_typecast(a) { decimals= dec_arg; } + Item_time_typecast(THD *thd, Item *a, uint dec_arg): + Item_temporal_typecast(thd, a) { decimals= dec_arg; } const char *func_name() const { return "cast_as_time"; } bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date); const char *cast_type() const { return "time"; } @@ -942,8 +1006,8 @@ public: class Item_datetime_typecast :public Item_temporal_typecast { public: - Item_datetime_typecast(Item *a, uint dec_arg) - :Item_temporal_typecast(a) { decimals= dec_arg; } + Item_datetime_typecast(THD *thd, Item *a, uint dec_arg): + Item_temporal_typecast(thd, a) { decimals= dec_arg; } const char *func_name() const { return "cast_as_datetime"; } const char *cast_type() const { return "datetime"; } enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; } @@ -954,7 +1018,8 @@ public: class Item_func_makedate :public Item_temporal_func { public: - Item_func_makedate(Item *a,Item *b) :Item_temporal_func(a,b) {} + Item_func_makedate(THD *thd, Item *a, Item *b): + Item_temporal_func(thd, a, b) {} const char *func_name() const { return "makedate"; } enum_field_types field_type() const { return MYSQL_TYPE_DATE; } bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date); @@ -967,8 +1032,9 @@ class Item_func_add_time :public Item_temporal_hybrid_func int sign; public: - Item_func_add_time(Item *a, Item *b, bool type_arg, bool neg_arg) - :Item_temporal_hybrid_func(a, b), is_date(type_arg) { sign= neg_arg ? -1 : 1; } + Item_func_add_time(THD *thd, Item *a, Item *b, bool type_arg, bool neg_arg): + Item_temporal_hybrid_func(thd, a, b), is_date(type_arg) + { sign= neg_arg ? -1 : 1; } void fix_length_and_dec(); bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date); void print(String *str, enum_query_type query_type); @@ -978,13 +1044,12 @@ public: class Item_func_timediff :public Item_timefunc { public: - Item_func_timediff(Item *a, Item *b) - :Item_timefunc(a, b) {} + Item_func_timediff(THD *thd, Item *a, Item *b): Item_timefunc(thd, a, b) {} const char *func_name() const { return "timediff"; } void fix_length_and_dec() { - decimals= max(args[0]->temporal_precision(MYSQL_TYPE_TIME), - args[1]->temporal_precision(MYSQL_TYPE_TIME)); + decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME), + args[1]->temporal_precision(MYSQL_TYPE_TIME)); Item_timefunc::fix_length_and_dec(); } bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date); @@ -993,12 +1058,12 @@ public: class Item_func_maketime :public Item_timefunc { public: - Item_func_maketime(Item *a, Item *b, Item *c) - :Item_timefunc(a, b, c) + Item_func_maketime(THD *thd, Item *a, Item *b, Item *c): + Item_timefunc(thd, a, b, c) {} void fix_length_and_dec() { - decimals= min(args[2]->decimals, TIME_SECOND_PART_DIGITS); + decimals= MY_MIN(args[2]->decimals, TIME_SECOND_PART_DIGITS); Item_timefunc::fix_length_and_dec(); } const char *func_name() const { return "maketime"; } @@ -1009,7 +1074,7 @@ public: class Item_func_microsecond :public Item_int_func { public: - Item_func_microsecond(Item *a) :Item_int_func(a) {} + Item_func_microsecond(THD *thd, Item *a): Item_int_func(thd, a) {} longlong val_int(); const char *func_name() const { return "microsecond"; } void fix_length_and_dec() @@ -1030,8 +1095,8 @@ class Item_func_timestamp_diff :public Item_int_func { const interval_type int_type; public: - Item_func_timestamp_diff(Item *a,Item *b,interval_type type_arg) - :Item_int_func(a,b), int_type(type_arg) {} + Item_func_timestamp_diff(THD *thd, Item *a, Item *b, interval_type type_arg): + Item_int_func(thd, a, b), int_type(type_arg) {} const char *func_name() const { return "timestampdiff"; } longlong val_int(); void fix_length_and_dec() @@ -1052,8 +1117,8 @@ class Item_func_get_format :public Item_str_ascii_func { public: const timestamp_type type; // keep it public - Item_func_get_format(timestamp_type type_arg, Item *a) - :Item_str_ascii_func(a), type(type_arg) + Item_func_get_format(THD *thd, timestamp_type type_arg, Item *a): + Item_str_ascii_func(thd, a), type(type_arg) {} String *val_str_ascii(String *str); const char *func_name() const { return "get_format"; } @@ -1075,8 +1140,8 @@ class Item_func_str_to_date :public Item_temporal_hybrid_func String format_converter; CHARSET_INFO *internal_charset; public: - Item_func_str_to_date(Item *a, Item *b) - :Item_temporal_hybrid_func(a, b), const_item(false), + Item_func_str_to_date(THD *thd, Item *a, Item *b): + Item_temporal_hybrid_func(thd, a, b), const_item(false), internal_charset(NULL) {} bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date); @@ -1088,7 +1153,7 @@ public: class Item_func_last_day :public Item_datefunc { public: - Item_func_last_day(Item *a) :Item_datefunc(a) {} + Item_func_last_day(THD *thd, Item *a): Item_datefunc(thd, a) {} const char *func_name() const { return "last_day"; } bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); }; diff --git a/sql/item_xmlfunc.cc b/sql/item_xmlfunc.cc index a49b5749f88..c9e6df52de9 100644 --- a/sql/item_xmlfunc.cc +++ b/sql/item_xmlfunc.cc @@ -18,6 +18,7 @@ #pragma implementation #endif +#include <my_global.h> #include "sql_priv.h" /* It is necessary to include set_var.h instead of item.h because there @@ -99,6 +100,7 @@ typedef struct my_xpath_function_names_st /* XPath query parser */ typedef struct my_xpath_st { + THD *thd; int debug; MY_XPATH_LEX query; /* Whole query */ MY_XPATH_LEX lasttok; /* last scanned token */ @@ -166,13 +168,14 @@ protected: public: String *pxml; String context_cache; - Item_nodeset_func(String *pxml_arg) :Item_str_func(), pxml(pxml_arg) {} - Item_nodeset_func(Item *a, String *pxml_arg) - :Item_str_func(a), pxml(pxml_arg) {} - Item_nodeset_func(Item *a, Item *b, String *pxml_arg) - :Item_str_func(a, b), pxml(pxml_arg) {} - Item_nodeset_func(Item *a, Item *b, Item *c, String *pxml_arg) - :Item_str_func(a,b,c), pxml(pxml_arg) {} + Item_nodeset_func(THD *thd, String *pxml_arg): + Item_str_func(thd), pxml(pxml_arg) {} + Item_nodeset_func(THD *thd, Item *a, String *pxml_arg): + Item_str_func(thd, a), pxml(pxml_arg) {} + Item_nodeset_func(THD *thd, Item *a, Item *b, String *pxml_arg): + Item_str_func(thd, a, b), pxml(pxml_arg) {} + Item_nodeset_func(THD *thd, Item *a, Item *b, Item *c, String *pxml_arg): + Item_str_func(thd, a, b, c), pxml(pxml_arg) {} void prepare_nodes() { nodebeg= (MY_XML_NODE*) pxml->ptr(); @@ -244,7 +247,8 @@ public: class Item_nodeset_func_rootelement :public Item_nodeset_func { public: - Item_nodeset_func_rootelement(String *pxml): Item_nodeset_func(pxml) {} + Item_nodeset_func_rootelement(THD *thd, String *pxml): + Item_nodeset_func(thd, pxml) {} const char *func_name() const { return "xpath_rootelement"; } String *val_nodeset(String *nodeset); }; @@ -254,8 +258,8 @@ public: class Item_nodeset_func_union :public Item_nodeset_func { public: - Item_nodeset_func_union(Item *a, Item *b, String *pxml) - :Item_nodeset_func(a, b, pxml) {} + Item_nodeset_func_union(THD *thd, Item *a, Item *b, String *pxml): + Item_nodeset_func(thd, a, b, pxml) {} const char *func_name() const { return "xpath_union"; } String *val_nodeset(String *nodeset); }; @@ -267,9 +271,9 @@ class Item_nodeset_func_axisbyname :public Item_nodeset_func const char *node_name; uint node_namelen; public: - Item_nodeset_func_axisbyname(Item *a, const char *n_arg, uint l_arg, - String *pxml): - Item_nodeset_func(a, pxml), node_name(n_arg), node_namelen(l_arg) { } + Item_nodeset_func_axisbyname(THD *thd, Item *a, const char *n_arg, uint l_arg, + String *pxml): + Item_nodeset_func(thd, a, pxml), node_name(n_arg), node_namelen(l_arg) { } const char *func_name() const { return "xpath_axisbyname"; } bool validname(MY_XML_NODE *n) { @@ -285,9 +289,9 @@ public: class Item_nodeset_func_selfbyname: public Item_nodeset_func_axisbyname { public: - Item_nodeset_func_selfbyname(Item *a, const char *n_arg, uint l_arg, - String *pxml): - Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml) {} + Item_nodeset_func_selfbyname(THD *thd, Item *a, const char *n_arg, uint l_arg, + String *pxml): + Item_nodeset_func_axisbyname(thd, a, n_arg, l_arg, pxml) {} const char *func_name() const { return "xpath_selfbyname"; } String *val_nodeset(String *nodeset); }; @@ -297,9 +301,9 @@ public: class Item_nodeset_func_childbyname: public Item_nodeset_func_axisbyname { public: - Item_nodeset_func_childbyname(Item *a, const char *n_arg, uint l_arg, + Item_nodeset_func_childbyname(THD *thd, Item *a, const char *n_arg, uint l_arg, String *pxml): - Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml) {} + Item_nodeset_func_axisbyname(thd, a, n_arg, l_arg, pxml) {} const char *func_name() const { return "xpath_childbyname"; } String *val_nodeset(String *nodeset); }; @@ -310,9 +314,9 @@ class Item_nodeset_func_descendantbyname: public Item_nodeset_func_axisbyname { bool need_self; public: - Item_nodeset_func_descendantbyname(Item *a, const char *n_arg, uint l_arg, - String *pxml, bool need_self_arg): - Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml), + Item_nodeset_func_descendantbyname(THD *thd, Item *a, const char *n_arg, uint l_arg, + String *pxml, bool need_self_arg): + Item_nodeset_func_axisbyname(thd, a, n_arg, l_arg, pxml), need_self(need_self_arg) {} const char *func_name() const { return "xpath_descendantbyname"; } String *val_nodeset(String *nodeset); @@ -324,9 +328,9 @@ class Item_nodeset_func_ancestorbyname: public Item_nodeset_func_axisbyname { bool need_self; public: - Item_nodeset_func_ancestorbyname(Item *a, const char *n_arg, uint l_arg, - String *pxml, bool need_self_arg): - Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml), + Item_nodeset_func_ancestorbyname(THD *thd, Item *a, const char *n_arg, uint l_arg, + String *pxml, bool need_self_arg): + Item_nodeset_func_axisbyname(thd, a, n_arg, l_arg, pxml), need_self(need_self_arg) {} const char *func_name() const { return "xpath_ancestorbyname"; } String *val_nodeset(String *nodeset); @@ -337,9 +341,9 @@ public: class Item_nodeset_func_parentbyname: public Item_nodeset_func_axisbyname { public: - Item_nodeset_func_parentbyname(Item *a, const char *n_arg, uint l_arg, - String *pxml): - Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml) {} + Item_nodeset_func_parentbyname(THD *thd, Item *a, const char *n_arg, uint l_arg, + String *pxml): + Item_nodeset_func_axisbyname(thd, a, n_arg, l_arg, pxml) {} const char *func_name() const { return "xpath_parentbyname"; } String *val_nodeset(String *nodeset); }; @@ -349,9 +353,9 @@ public: class Item_nodeset_func_attributebyname: public Item_nodeset_func_axisbyname { public: - Item_nodeset_func_attributebyname(Item *a, const char *n_arg, uint l_arg, - String *pxml): - Item_nodeset_func_axisbyname(a, n_arg, l_arg, pxml) {} + Item_nodeset_func_attributebyname(THD *thd, Item *a, const char *n_arg, + uint l_arg, String *pxml): + Item_nodeset_func_axisbyname(thd, a, n_arg, l_arg, pxml) {} const char *func_name() const { return "xpath_attributebyname"; } String *val_nodeset(String *nodeset); }; @@ -365,8 +369,8 @@ public: class Item_nodeset_func_predicate :public Item_nodeset_func { public: - Item_nodeset_func_predicate(Item *a, Item *b, String *pxml): - Item_nodeset_func(a, b, pxml) {} + Item_nodeset_func_predicate(THD *thd, Item *a, Item *b, String *pxml): + Item_nodeset_func(thd, a, b, pxml) {} const char *func_name() const { return "xpath_predicate"; } String *val_nodeset(String *nodeset); }; @@ -376,8 +380,8 @@ public: class Item_nodeset_func_elementbyindex :public Item_nodeset_func { public: - Item_nodeset_func_elementbyindex(Item *a, Item *b, String *pxml): - Item_nodeset_func(a, b, pxml) { } + Item_nodeset_func_elementbyindex(THD *thd, Item *a, Item *b, String *pxml): + Item_nodeset_func(thd, a, b, pxml) { } const char *func_name() const { return "xpath_elementbyindex"; } String *val_nodeset(String *nodeset); }; @@ -390,9 +394,9 @@ public: class Item_bool :public Item_int { public: - Item_bool(int32 i): Item_int(i) {} + Item_bool(THD *thd, int32 i): Item_int(thd, i) {} const char *func_name() const { return "xpath_bool"; } - bool is_bool_func() { return 1; } + bool is_bool_type() { return true; } }; @@ -402,15 +406,14 @@ public: * a node-set is true if and only if it is non-empty * a string is true if and only if its length is non-zero */ -class Item_xpath_cast_bool :public Item_int_func +class Item_xpath_cast_bool :public Item_bool_func { String *pxml; String tmp_value; public: - Item_xpath_cast_bool(Item *a, String *pxml_arg) - :Item_int_func(a), pxml(pxml_arg) {} + Item_xpath_cast_bool(THD *thd, Item *a, String *pxml_arg): + Item_bool_func(thd, a), pxml(pxml_arg) {} const char *func_name() const { return "xpath_cast_bool"; } - bool is_bool_func() { return 1; } longlong val_int() { if (args[0]->type() == XPATH_NODESET) @@ -429,7 +432,7 @@ public: class Item_xpath_cast_number :public Item_real_func { public: - Item_xpath_cast_number(Item *a): Item_real_func(a) {} + Item_xpath_cast_number(THD *thd, Item *a): Item_real_func(thd, a) {} const char *func_name() const { return "xpath_cast_number"; } virtual double val_real() { return args[0]->val_real(); } }; @@ -442,8 +445,8 @@ class Item_nodeset_context_cache :public Item_nodeset_func { public: String *string_cache; - Item_nodeset_context_cache(String *str_arg, String *pxml): - Item_nodeset_func(pxml), string_cache(str_arg) { } + Item_nodeset_context_cache(THD *thd, String *str_arg, String *pxml): + Item_nodeset_func(thd, pxml), string_cache(str_arg) { } String *val_nodeset(String *res) { return string_cache; } void fix_length_and_dec() { max_length= MAX_BLOB_WIDTH; } @@ -455,8 +458,8 @@ class Item_func_xpath_position :public Item_int_func String *pxml; String tmp_value; public: - Item_func_xpath_position(Item *a, String *p) - :Item_int_func(a), pxml(p) {} + Item_func_xpath_position(THD *thd, Item *a, String *p): + Item_int_func(thd, a), pxml(p) {} const char *func_name() const { return "xpath_position"; } void fix_length_and_dec() { max_length=10; } longlong val_int() @@ -474,8 +477,8 @@ class Item_func_xpath_count :public Item_int_func String *pxml; String tmp_value; public: - Item_func_xpath_count(Item *a, String *p) - :Item_int_func(a), pxml(p) {} + Item_func_xpath_count(THD *thd, Item *a, String *p): + Item_int_func(thd, a), pxml(p) {} const char *func_name() const { return "xpath_count"; } void fix_length_and_dec() { max_length=10; } longlong val_int() @@ -495,8 +498,8 @@ class Item_func_xpath_sum :public Item_real_func String *pxml; String tmp_value; public: - Item_func_xpath_sum(Item *a, String *p) - :Item_real_func(a), pxml(p) {} + Item_func_xpath_sum(THD *thd, Item *a, String *p): + Item_real_func(thd, a), pxml(p) {} const char *func_name() const { return "xpath_sum"; } double val_real() @@ -533,16 +536,43 @@ public: }; +/** + A string whose value may be changed during execution. +*/ +class Item_string_xml_non_const: public Item_string +{ +public: + Item_string_xml_non_const(THD *thd, const char *str, uint length, + CHARSET_INFO *cs): + Item_string(thd, str, length, cs) + { } + bool const_item() const { return false ; } + bool basic_const_item() const { return false; } + void set_value(const char *str, uint length, CHARSET_INFO *cs) + { + str_value.set(str, length, cs); + } + Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs) + { + /* + Item_string::safe_charset_converter() does not accept non-constants. + Note, conversion is not really needed here anyway. + */ + return this; + } +}; + + class Item_nodeset_to_const_comparator :public Item_bool_func { String *pxml; String tmp_nodeset; public: - Item_nodeset_to_const_comparator(Item *nodeset, Item *cmpfunc, String *p) - :Item_bool_func(nodeset,cmpfunc), pxml(p) {} + Item_nodeset_to_const_comparator(THD *thd, Item *nodeset, Item *cmpfunc, + String *p): + Item_bool_func(thd, nodeset, cmpfunc), pxml(p) {} enum Type type() const { return XPATH_NODESET_CMP; }; const char *func_name() const { return "xpath_nodeset_to_const_comparator"; } - bool is_bool_func() { return 1; } bool check_vcol_func_processor(uchar *int_arg) { return trace_unsupported_by_check_vcol_func_processor(func_name()); @@ -551,7 +581,8 @@ public: longlong val_int() { Item_func *comp= (Item_func*)args[1]; - Item_string *fake= (Item_string*)(comp->arguments()[0]); + Item_string_xml_non_const *fake= + (Item_string_xml_non_const*)(comp->arguments()[0]); String *res= args[0]->val_nodeset(&tmp_nodeset); MY_XPATH_FLT *fltbeg= (MY_XPATH_FLT*) res->ptr(); MY_XPATH_FLT *fltend= (MY_XPATH_FLT*) (res->ptr() + res->length()); @@ -569,8 +600,8 @@ public: if ((node->parent == flt->num) && (node->type == MY_XML_NODE_TEXT)) { - fake->str_value.set(node->beg, node->end - node->beg, - collation.collation); + fake->set_value(node->beg, node->end - node->beg, + collation.collation); if (args[1]->val_int()) return 1; } @@ -796,7 +827,7 @@ String *Item_nodeset_func_elementbyindex::val_nodeset(String *nodeset) flt->pos, size); int index= (int) (args[1]->val_int()) - 1; - if (index >= 0 && (flt->pos == (uint) index || args[1]->is_bool_func())) + if (index >= 0 && (flt->pos == (uint) index || args[1]->is_bool_type())) ((XPathFilter*)nodeset)->append_element(flt->num, pos++); } return nodeset; @@ -810,7 +841,8 @@ String *Item_nodeset_func_elementbyindex::val_nodeset(String *nodeset) static Item* nodeset2bool(MY_XPATH *xpath, Item *item) { if (item->type() == Item::XPATH_NODESET) - return new Item_xpath_cast_bool(item, xpath->pxml); + return new (xpath->thd->mem_root) + Item_xpath_cast_bool(xpath->thd, item, xpath->pxml); return item; } @@ -880,16 +912,17 @@ static Item* nodeset2bool(MY_XPATH *xpath, Item *item) RETURN The newly created item. */ -static Item *eq_func(int oper, Item *a, Item *b) +static Item *eq_func(THD *thd, int oper, Item *a, Item *b) { + MEM_ROOT *mem_root= thd->mem_root; switch (oper) { - case '=': return new Item_func_eq(a, b); - case '!': return new Item_func_ne(a, b); - case MY_XPATH_LEX_GE: return new Item_func_ge(a, b); - case MY_XPATH_LEX_LE: return new Item_func_le(a, b); - case MY_XPATH_LEX_GREATER: return new Item_func_gt(a, b); - case MY_XPATH_LEX_LESS: return new Item_func_lt(a, b); + case '=': return new (mem_root) Item_func_eq(thd, a, b); + case '!': return new (mem_root) Item_func_ne(thd, a, b); + case MY_XPATH_LEX_GE: return new (mem_root) Item_func_ge(thd, a, b); + case MY_XPATH_LEX_LE: return new (mem_root) Item_func_le(thd, a, b); + case MY_XPATH_LEX_GREATER: return new (mem_root) Item_func_gt(thd, a, b); + case MY_XPATH_LEX_LESS: return new (mem_root) Item_func_lt(thd, a, b); } return 0; } @@ -907,16 +940,17 @@ static Item *eq_func(int oper, Item *a, Item *b) RETURN The newly created item. */ -static Item *eq_func_reverse(int oper, Item *a, Item *b) +static Item *eq_func_reverse(THD *thd, int oper, Item *a, Item *b) { + MEM_ROOT *mem_root= thd->mem_root; switch (oper) { - case '=': return new Item_func_eq(a, b); - case '!': return new Item_func_ne(a, b); - case MY_XPATH_LEX_GE: return new Item_func_le(a, b); - case MY_XPATH_LEX_LE: return new Item_func_ge(a, b); - case MY_XPATH_LEX_GREATER: return new Item_func_lt(a, b); - case MY_XPATH_LEX_LESS: return new Item_func_gt(a, b); + case '=': return new (mem_root) Item_func_eq(thd, a, b); + case '!': return new (mem_root) Item_func_ne(thd, a, b); + case MY_XPATH_LEX_GE: return new (mem_root) Item_func_le(thd, a, b); + case MY_XPATH_LEX_LE: return new (mem_root) Item_func_ge(thd, a, b); + case MY_XPATH_LEX_GREATER: return new (mem_root) Item_func_lt(thd, a, b); + case MY_XPATH_LEX_LESS: return new (mem_root) Item_func_gt(thd, a, b); } return 0; } @@ -939,7 +973,7 @@ static Item *create_comparator(MY_XPATH *xpath, if (a->type() != Item::XPATH_NODESET && b->type() != Item::XPATH_NODESET) { - return eq_func(oper, a, b); // two scalar arguments + return eq_func(xpath->thd, oper, a, b); // two scalar arguments } else if (a->type() == Item::XPATH_NODESET && b->type() == Item::XPATH_NODESET) @@ -957,29 +991,30 @@ static Item *create_comparator(MY_XPATH *xpath, { /* Compare a node set to a scalar value. - We just create a fake Item_string() argument, + We just create a fake Item_string_xml_non_const() argument, which will be filled to the partular value in a loop through all of the nodes in the node set. */ - Item_string *fake= new Item_string("", 0, xpath->cs); - /* Don't cache fake because its value will be changed during comparison.*/ - fake->set_used_tables(RAND_TABLE_BIT); + THD *thd= xpath->thd; + Item_string *fake= (new (thd->mem_root) + Item_string_xml_non_const(thd, "", 0, xpath->cs)); Item_nodeset_func *nodeset; Item *scalar, *comp; if (a->type() == Item::XPATH_NODESET) { nodeset= (Item_nodeset_func*) a; scalar= b; - comp= eq_func(oper, (Item*)fake, scalar); + comp= eq_func(thd, oper, (Item*)fake, scalar); } else { nodeset= (Item_nodeset_func*) b; scalar= a; - comp= eq_func_reverse(oper, fake, scalar); + comp= eq_func_reverse(thd, oper, fake, scalar); } - return new Item_nodeset_to_const_comparator(nodeset, comp, xpath->pxml); + return (new (thd->mem_root) + Item_nodeset_to_const_comparator(thd, nodeset, comp, xpath->pxml)); } } @@ -996,6 +1031,9 @@ static Item *create_comparator(MY_XPATH *xpath, static Item* nametestfunc(MY_XPATH *xpath, int type, Item *arg, const char *beg, uint len) { + THD *thd= xpath->thd; + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ASSERT(arg != 0); DBUG_ASSERT(arg->type() == Item::XPATH_NODESET); DBUG_ASSERT(beg != 0); @@ -1005,28 +1043,36 @@ static Item* nametestfunc(MY_XPATH *xpath, switch (type) { case MY_XPATH_AXIS_ANCESTOR: - res= new Item_nodeset_func_ancestorbyname(arg, beg, len, xpath->pxml, 0); + res= new (mem_root) Item_nodeset_func_ancestorbyname(thd, arg, beg, len, + xpath->pxml, 0); break; case MY_XPATH_AXIS_ANCESTOR_OR_SELF: - res= new Item_nodeset_func_ancestorbyname(arg, beg, len, xpath->pxml, 1); + res= new (mem_root) Item_nodeset_func_ancestorbyname(thd, arg, beg, len, + xpath->pxml, 1); break; case MY_XPATH_AXIS_PARENT: - res= new Item_nodeset_func_parentbyname(arg, beg, len, xpath->pxml); + res= new (mem_root) Item_nodeset_func_parentbyname(thd, arg, beg, len, + xpath->pxml); break; case MY_XPATH_AXIS_DESCENDANT: - res= new Item_nodeset_func_descendantbyname(arg, beg, len, xpath->pxml, 0); + res= new (mem_root) Item_nodeset_func_descendantbyname(thd, arg, beg, len, + xpath->pxml, 0); break; case MY_XPATH_AXIS_DESCENDANT_OR_SELF: - res= new Item_nodeset_func_descendantbyname(arg, beg, len, xpath->pxml, 1); + res= new (mem_root) Item_nodeset_func_descendantbyname(thd, arg, beg, len, + xpath->pxml, 1); break; case MY_XPATH_AXIS_ATTRIBUTE: - res= new Item_nodeset_func_attributebyname(arg, beg, len, xpath->pxml); + res= new (mem_root) Item_nodeset_func_attributebyname(thd, arg, beg, len, + xpath->pxml); break; case MY_XPATH_AXIS_SELF: - res= new Item_nodeset_func_selfbyname(arg, beg, len, xpath->pxml); + res= new (mem_root) Item_nodeset_func_selfbyname(thd, arg, beg, len, + xpath->pxml); break; default: - res= new Item_nodeset_func_childbyname(arg, beg, len, xpath->pxml); + res= new (mem_root) Item_nodeset_func_childbyname(thd, arg, beg, len, + xpath->pxml); } return res; } @@ -1041,7 +1087,7 @@ static char simpletok[128]= /* ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ - ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ € + ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ \200 */ 0,1,0,0,1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,0, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0, @@ -1134,101 +1180,117 @@ my_xpath_keyword(MY_XPATH *x, */ static Item *create_func_true(MY_XPATH *xpath, Item **args, uint nargs) -{ - return new Item_bool(1); +{ + return new (xpath->thd->mem_root) Item_bool(xpath->thd, 1); } static Item *create_func_false(MY_XPATH *xpath, Item **args, uint nargs) -{ - return new Item_bool(0); +{ + return new (xpath->thd->mem_root) Item_bool(xpath->thd, 0); } static Item *create_func_not(MY_XPATH *xpath, Item **args, uint nargs) -{ - return new Item_func_not(nodeset2bool(xpath, args[0])); +{ + return new (xpath->thd->mem_root) + Item_func_not(xpath->thd, nodeset2bool(xpath, args[0])); } static Item *create_func_ceiling(MY_XPATH *xpath, Item **args, uint nargs) { - return new Item_func_ceiling(args[0]); + return new (xpath->thd->mem_root) Item_func_ceiling(xpath->thd, args[0]); } static Item *create_func_floor(MY_XPATH *xpath, Item **args, uint nargs) { - return new Item_func_floor(args[0]); + return new (xpath->thd->mem_root) Item_func_floor(xpath->thd, args[0]); } static Item *create_func_bool(MY_XPATH *xpath, Item **args, uint nargs) { - return new Item_xpath_cast_bool(args[0], xpath->pxml); + return new (xpath->thd->mem_root) + Item_xpath_cast_bool(xpath->thd, args[0], xpath->pxml); } static Item *create_func_number(MY_XPATH *xpath, Item **args, uint nargs) { - return new Item_xpath_cast_number(args[0]); + return new (xpath->thd->mem_root) + Item_xpath_cast_number(xpath->thd, args[0]); } -static Item *create_func_string_length(MY_XPATH *xpath, Item **args, uint nargs) +static Item *create_func_string_length(MY_XPATH *xpath, Item **args, + uint nargs) { Item *arg= nargs ? args[0] : xpath->context; - return arg ? new Item_func_char_length(arg) : 0; + return arg ? new (xpath->thd->mem_root) + Item_func_char_length(xpath->thd, arg) : 0; } static Item *create_func_round(MY_XPATH *xpath, Item **args, uint nargs) { - return new Item_func_round(args[0], new Item_int((char*)"0",0,1),0); + return new (xpath->thd->mem_root) + Item_func_round(xpath->thd, args[0], + new (xpath->thd->mem_root) + Item_int(xpath->thd, (char *) "0", 0, 1), 0); } static Item *create_func_last(MY_XPATH *xpath, Item **args, uint nargs) { - return xpath->context ? - new Item_func_xpath_count(xpath->context, xpath->pxml) : NULL; + return (xpath->context ? + new (xpath->thd->mem_root) + Item_func_xpath_count(xpath->thd, xpath->context, xpath->pxml) : + NULL); } static Item *create_func_position(MY_XPATH *xpath, Item **args, uint nargs) { - return xpath->context ? - new Item_func_xpath_position(xpath->context, xpath->pxml) : NULL; + return (xpath->context ? + new (xpath->thd->mem_root) + Item_func_xpath_position(xpath->thd, xpath->context, xpath->pxml) : + NULL); } static Item *create_func_contains(MY_XPATH *xpath, Item **args, uint nargs) { - return new Item_xpath_cast_bool(new Item_func_locate(args[0], args[1]), - xpath->pxml); + return (new (xpath->thd->mem_root) + Item_xpath_cast_bool(xpath->thd, + new (xpath->thd->mem_root) + Item_func_locate(xpath->thd, args[0], args[1]), + xpath->pxml)); } static Item *create_func_concat(MY_XPATH *xpath, Item **args, uint nargs) -{ - return new Item_func_concat(args[0], args[1]); +{ + return new (xpath->thd->mem_root) + Item_func_concat(xpath->thd, args[0], args[1]); } static Item *create_func_substr(MY_XPATH *xpath, Item **args, uint nargs) { + THD *thd= xpath->thd; if (nargs == 2) - return new Item_func_substr(args[0], args[1]); - else - return new Item_func_substr(args[0], args[1], args[2]); + return new (thd->mem_root) Item_func_substr(thd, args[0], args[1]); + return new (thd->mem_root) Item_func_substr(thd, args[0], args[1], args[2]); } static Item *create_func_count(MY_XPATH *xpath, Item **args, uint nargs) -{ +{ if (args[0]->type() != Item::XPATH_NODESET) return 0; - return new Item_func_xpath_count(args[0], xpath->pxml); + return new (xpath->thd->mem_root) Item_func_xpath_count(xpath->thd, args[0], xpath->pxml); } @@ -1236,7 +1298,8 @@ static Item *create_func_sum(MY_XPATH *xpath, Item **args, uint nargs) { if (args[0]->type() != Item::XPATH_NODESET) return 0; - return new Item_func_xpath_sum(args[0], xpath->pxml); + return new (xpath->thd->mem_root) + Item_func_xpath_sum(xpath->thd, args[0], xpath->pxml); } @@ -1609,9 +1672,11 @@ static int my_xpath_parse_AbsoluteLocationPath(MY_XPATH *xpath) if (my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH)) { - xpath->context= new Item_nodeset_func_descendantbyname(xpath->context, - "*", 1, - xpath->pxml, 1); + xpath->context= new (xpath->thd->mem_root) + Item_nodeset_func_descendantbyname(xpath->thd, + xpath->context, + "*", 1, + xpath->pxml, 1); return my_xpath_parse_RelativeLocationPath(xpath); } @@ -1650,9 +1715,11 @@ static int my_xpath_parse_RelativeLocationPath(MY_XPATH *xpath) while (my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH)) { if (my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH)) - xpath->context= new Item_nodeset_func_descendantbyname(xpath->context, - "*", 1, - xpath->pxml, 1); + xpath->context= new (xpath->thd->mem_root) + Item_nodeset_func_descendantbyname(xpath->thd, + xpath->context, + "*", 1, + xpath->pxml, 1); if (!my_xpath_parse_Step(xpath)) { xpath->error= 1; @@ -1690,7 +1757,8 @@ my_xpath_parse_AxisSpecifier_NodeTest_opt_Predicate_list(MY_XPATH *xpath) Item *prev_context= xpath->context; String *context_cache; context_cache= &((Item_nodeset_func*)xpath->context)->context_cache; - xpath->context= new Item_nodeset_context_cache(context_cache, xpath->pxml); + xpath->context= new (xpath->thd->mem_root) + Item_nodeset_context_cache(xpath->thd, context_cache, xpath->pxml); xpath->context_cache= context_cache; if(!my_xpath_parse_PredicateExpr(xpath)) @@ -1707,17 +1775,20 @@ my_xpath_parse_AxisSpecifier_NodeTest_opt_Predicate_list(MY_XPATH *xpath) xpath->item= nodeset2bool(xpath, xpath->item); - if (xpath->item->is_bool_func()) + if (xpath->item->is_bool_type()) { - xpath->context= new Item_nodeset_func_predicate(prev_context, - xpath->item, - xpath->pxml); + xpath->context= new (xpath->thd->mem_root) + Item_nodeset_func_predicate(xpath->thd, prev_context, + xpath->item, + xpath->pxml); } else { - xpath->context= new Item_nodeset_func_elementbyindex(prev_context, - xpath->item, - xpath->pxml); + xpath->context= new (xpath->thd->mem_root) + Item_nodeset_func_elementbyindex(xpath->thd, + prev_context, + xpath->item, + xpath->pxml); } } return 1; @@ -1725,7 +1796,7 @@ my_xpath_parse_AxisSpecifier_NodeTest_opt_Predicate_list(MY_XPATH *xpath) static int my_xpath_parse_Step(MY_XPATH *xpath) -{ +{ return my_xpath_parse_AxisSpecifier_NodeTest_opt_Predicate_list(xpath) || my_xpath_parse_AbbreviatedStep(xpath); @@ -1839,8 +1910,10 @@ static int my_xpath_parse_AbbreviatedStep(MY_XPATH *xpath) if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_DOT)) return 0; if (my_xpath_parse_term(xpath, MY_XPATH_LEX_DOT)) - xpath->context= new Item_nodeset_func_parentbyname(xpath->context, "*", 1, - xpath->pxml); + xpath->context= new (xpath->thd->mem_root) + Item_nodeset_func_parentbyname(xpath->thd, + xpath->context, "*", + 1, xpath->pxml); return 1; } @@ -1869,9 +1942,10 @@ static int my_xpath_parse_PrimaryExpr_literal(MY_XPATH *xpath) { if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_STRING)) return 0; - xpath->item= new Item_string(xpath->prevtok.beg + 1, - xpath->prevtok.end - xpath->prevtok.beg - 2, - xpath->cs); + xpath->item= new (xpath->thd->mem_root) + Item_string(xpath->thd, xpath->prevtok.beg + 1, + xpath->prevtok.end - xpath->prevtok.beg - 2, + xpath->cs); return 1; } static int my_xpath_parse_PrimaryExpr(MY_XPATH *xpath) @@ -1964,7 +2038,9 @@ static int my_xpath_parse_UnionExpr(MY_XPATH *xpath) xpath->error= 1; return 0; } - xpath->item= new Item_nodeset_func_union(prev, xpath->item, xpath->pxml); + xpath->item= new (xpath->thd->mem_root) + Item_nodeset_func_union(xpath->thd, prev, xpath->item, + xpath->pxml); } return 1; } @@ -2010,8 +2086,11 @@ my_xpath_parse_FilterExpr_opt_slashes_RelativeLocationPath(MY_XPATH *xpath) /* treat double slash (//) as /descendant-or-self::node()/ */ if (my_xpath_parse_term(xpath, MY_XPATH_LEX_SLASH)) - xpath->context= new Item_nodeset_func_descendantbyname(xpath->context, - "*", 1, xpath->pxml, 1); + xpath->context= new (xpath->thd->mem_root) + Item_nodeset_func_descendantbyname(xpath->thd, + xpath->context, + "*", 1, + xpath->pxml, 1); rc= my_xpath_parse_RelativeLocationPath(xpath); /* push back the context and restore the item */ @@ -2073,8 +2152,9 @@ static int my_xpath_parse_OrExpr(MY_XPATH *xpath) xpath->error= 1; return 0; } - xpath->item= new Item_cond_or(nodeset2bool(xpath, prev), - nodeset2bool(xpath, xpath->item)); + xpath->item= new (xpath->thd->mem_root) + Item_cond_or(xpath->thd, nodeset2bool(xpath, prev), + nodeset2bool(xpath, xpath->item)); } return 1; } @@ -2105,8 +2185,9 @@ static int my_xpath_parse_AndExpr(MY_XPATH *xpath) return 0; } - xpath->item= new Item_cond_and(nodeset2bool(xpath,prev), - nodeset2bool(xpath,xpath->item)); + xpath->item= new (xpath->thd->mem_root) + Item_cond_and(xpath->thd, nodeset2bool(xpath, prev), + nodeset2bool(xpath, xpath->item)); } return 1; } @@ -2268,6 +2349,8 @@ static int my_xpath_parse_AdditiveExpr(MY_XPATH *xpath) { int oper= xpath->prevtok.term; Item *prev= xpath->item; + THD *thd= xpath->thd; + if (!my_xpath_parse_MultiplicativeExpr(xpath)) { xpath->error= 1; @@ -2275,9 +2358,11 @@ static int my_xpath_parse_AdditiveExpr(MY_XPATH *xpath) } if (oper == MY_XPATH_LEX_PLUS) - xpath->item= new Item_func_plus(prev, xpath->item); + xpath->item= new (thd->mem_root) + Item_func_plus(thd, prev, xpath->item); else - xpath->item= new Item_func_minus(prev, xpath->item); + xpath->item= new (thd->mem_root) + Item_func_minus(thd, prev, xpath->item); }; return 1; } @@ -2312,6 +2397,7 @@ static int my_xpath_parse_MultiplicativeExpr(MY_XPATH *xpath) if (!my_xpath_parse_UnaryExpr(xpath)) return 0; + THD *thd= xpath->thd; while (my_xpath_parse_MultiplicativeOperator(xpath)) { int oper= xpath->prevtok.term; @@ -2324,13 +2410,13 @@ static int my_xpath_parse_MultiplicativeExpr(MY_XPATH *xpath) switch (oper) { case MY_XPATH_LEX_ASTERISK: - xpath->item= new Item_func_mul(prev, xpath->item); + xpath->item= new (thd->mem_root) Item_func_mul(thd, prev, xpath->item); break; case MY_XPATH_LEX_DIV: - xpath->item= new Item_func_int_div(prev, xpath->item); + xpath->item= new (thd->mem_root) Item_func_int_div(thd, prev, xpath->item); break; case MY_XPATH_LEX_MOD: - xpath->item= new Item_func_mod(prev, xpath->item); + xpath->item= new (thd->mem_root) Item_func_mod(thd, prev, xpath->item); break; } } @@ -2355,7 +2441,8 @@ static int my_xpath_parse_UnaryExpr(MY_XPATH *xpath) return my_xpath_parse_UnionExpr(xpath); if (!my_xpath_parse_UnaryExpr(xpath)) return 0; - xpath->item= new Item_func_neg(xpath->item); + xpath->item= new (xpath->thd->mem_root) + Item_func_neg(xpath->thd, xpath->item); return 1; } @@ -2387,18 +2474,21 @@ static int my_xpath_parse_UnaryExpr(MY_XPATH *xpath) static int my_xpath_parse_Number(MY_XPATH *xpath) { const char *beg; + THD *thd; if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_DIGITS)) return 0; beg= xpath->prevtok.beg; + thd= xpath->thd; if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_DOT)) { - xpath->item= new Item_int(xpath->prevtok.beg, + xpath->item= new (thd->mem_root) Item_int(thd, xpath->prevtok.beg, xpath->prevtok.end - xpath->prevtok.beg); return 1; } my_xpath_parse_term(xpath, MY_XPATH_LEX_DIGITS); - xpath->item= new Item_float(beg, xpath->prevtok.end - beg); + xpath->item= new (thd->mem_root) Item_float(thd, beg, + xpath->prevtok.end - beg); return 1; } @@ -2491,6 +2581,7 @@ my_xpath_parse_VariableReference(MY_XPATH *xpath) LEX_STRING name; int user_var; const char *dollar_pos; + THD *thd= xpath->thd; if (!my_xpath_parse_term(xpath, MY_XPATH_LEX_DOLLAR) || (!(dollar_pos= xpath->prevtok.beg)) || (!((user_var= my_xpath_parse_term(xpath, MY_XPATH_LEX_AT) && @@ -2502,17 +2593,19 @@ my_xpath_parse_VariableReference(MY_XPATH *xpath) name.str= (char*) xpath->prevtok.beg; if (user_var) - xpath->item= new Item_func_get_user_var(name); + xpath->item= new (thd->mem_root) Item_func_get_user_var(thd, name); else { - sp_variable_t *spv; + sp_variable *spv; sp_pcontext *spc; LEX *lex; - if ((lex= current_thd->lex) && + if ((lex= thd->lex) && (spc= lex->spcont) && - (spv= spc->find_variable(&name))) + (spv= spc->find_variable(name, false))) { - Item_splocal *splocal= new Item_splocal(name, spv->offset, spv->type, 0); + Item_splocal *splocal= new (thd->mem_root) + Item_splocal(thd, name, spv->offset, + spv->type, 0); #ifndef DBUG_OFF if (splocal) splocal->m_sp= lex->sphead; @@ -2591,26 +2684,35 @@ my_xpath_parse(MY_XPATH *xpath, const char *str, const char *strend) my_xpath_lex_init(&xpath->prevtok, str, strend); my_xpath_lex_scan(xpath, &xpath->lasttok, str, strend); - xpath->rootelement= new Item_nodeset_func_rootelement(xpath->pxml); + xpath->rootelement= new (xpath->thd->mem_root) + Item_nodeset_func_rootelement(xpath->thd, + xpath->pxml); - return - my_xpath_parse_Expr(xpath) && - my_xpath_parse_term(xpath, MY_XPATH_LEX_EOF); + return (my_xpath_parse_Expr(xpath) && + my_xpath_parse_term(xpath, MY_XPATH_LEX_EOF)); } void Item_xml_str_func::fix_length_and_dec() { - String *xp, tmp; + max_length= MAX_BLOB_WIDTH; + agg_arg_charsets_for_comparison(collation, args, arg_count); +} + + +bool Item_xml_str_func::fix_fields(THD *thd, Item **ref) +{ + String *xp; MY_XPATH xpath; int rc; + if (Item_str_func::fix_fields(thd, ref)) + return true; + status_var_increment(current_thd->status_var.feature_xml); nodeset_func= 0; - if (agg_arg_charsets_for_comparison(collation, args, arg_count)) - return; if (collation.collation->mbminlen > 1) { @@ -2618,23 +2720,30 @@ void Item_xml_str_func::fix_length_and_dec() my_printf_error(ER_UNKNOWN_ERROR, "Character set '%s' is not supported by XPATH", MYF(0), collation.collation->csname); - return; + return true; } if (!args[1]->const_item()) { my_printf_error(ER_UNKNOWN_ERROR, "Only constant XPATH queries are supported", MYF(0)); - return; + return true; } - if (!(xp= args[1]->val_str(&tmp))) - return; + /* + Get the XPath query text from args[1] and cache it in m_xpath_query. + Its fragments will be referenced by items created during my_xpath_parse(), + e.g. by Item_nodeset_func_axisbyname::node_name. + */ + if (!(xp= args[1]->val_str(&m_xpath_query)) || + (xp != &m_xpath_query && m_xpath_query.copy(*xp))) + return false; // Will return NULL my_xpath_init(&xpath); + xpath.thd= thd; xpath.cs= collation.collation; xpath.debug= 0; - xpath.pxml= &pxml; - pxml.set_charset(collation.collation); + xpath.pxml= xml.parsed(); + xml.set_charset(collation.collation); rc= my_xpath_parse(&xpath, xp->ptr(), xp->ptr() + xp->length()); @@ -2644,13 +2753,24 @@ void Item_xml_str_func::fix_length_and_dec() set_if_smaller(clen, 32); my_printf_error(ER_UNKNOWN_ERROR, "XPATH syntax error: '%.*s'", MYF(0), clen, xpath.lasttok.beg); - return; + return true; } - nodeset_func= xpath.item; - if (nodeset_func) - nodeset_func->fix_fields(current_thd, &nodeset_func); - max_length= MAX_BLOB_WIDTH; + /* + Parsing XML is a heavy operation, so if the first argument is constant, + then parse XML only one time and cache the parsed representation + together with raw text representation. + + Note, we cannot cache the entire function result even if + the first and the second arguments are constants, because + the XPath expression may have user and SP variable references, + so the function result can vary between executions. + */ + if ((args[0]->const_item() && get_xml(&xml, true)) || + !(nodeset_func= xpath.item)) + return false; // Will return NULL + + return nodeset_func->fix_fields(thd, &nodeset_func); } @@ -2781,25 +2901,24 @@ int xml_leave(MY_XML_PARSER *st,const char *attr, size_t len) Parse raw XML SYNOPSYS - RETURN - Currently pointer to parsed XML on success - 0 on parse error + false on success + true on error */ -String *Item_xml_str_func::parse_xml(String *raw_xml, String *parsed_xml_buf) +bool Item_xml_str_func::XML::parse() { MY_XML_PARSER p; MY_XML_USER_DATA user_data; int rc; - parsed_xml_buf->length(0); + m_parsed_buf.length(0); /* Prepare XML parser */ my_xml_parser_create(&p); p.flags= MY_XML_FLAG_RELATIVE_NAMES | MY_XML_FLAG_SKIP_TEXT_NORMALIZATION; user_data.level= 0; - user_data.pxml= parsed_xml_buf; + user_data.pxml= &m_parsed_buf; user_data.parent= 0; my_xml_set_enter_handler(&p, xml_enter); my_xml_set_value_handler(&p, xml_value); @@ -2808,23 +2927,55 @@ String *Item_xml_str_func::parse_xml(String *raw_xml, String *parsed_xml_buf) /* Add root node */ p.current_node_type= MY_XML_NODE_TAG; - xml_enter(&p, raw_xml->ptr(), 0); + xml_enter(&p, m_raw_ptr->ptr(), 0); /* Execute XML parser */ - if ((rc= my_xml_parse(&p, raw_xml->ptr(), raw_xml->length())) != MY_XML_OK) + if ((rc= my_xml_parse(&p, m_raw_ptr->ptr(), m_raw_ptr->length())) != MY_XML_OK) { + THD *thd= current_thd; char buf[128]; my_snprintf(buf, sizeof(buf)-1, "parse error at line %d pos %lu: %s", my_xml_error_lineno(&p) + 1, (ulong) my_xml_error_pos(&p) + 1, my_xml_error_string(&p)); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_VALUE, - ER(ER_WRONG_VALUE), "XML", buf); + ER_THD(thd, ER_WRONG_VALUE), "XML", buf); + m_raw_ptr= (String *) 0; } my_xml_parser_free(&p); - return rc == MY_XML_OK ? parsed_xml_buf : 0; + return rc != MY_XML_OK; +} + + +/* + Parse the raw XML from the given source, + optionally cache the raw XML, + remember the pointer to the raw XML. +*/ +bool Item_xml_str_func::XML::parse(String *raw_xml, bool cache) +{ + m_raw_ptr= raw_xml; + if (cache) + { + m_cached= true; + if (m_raw_ptr != &m_raw_buf && m_raw_buf.copy(*m_raw_ptr)) + { + m_raw_ptr= (String *) 0; + return true; + } + m_raw_ptr= &m_raw_buf; + } + return parse(); +} + + +const MY_XML_NODE *Item_xml_str_func::XML::node(uint idx) +{ + const MY_XML_NODE *nodebeg= (MY_XML_NODE*) m_parsed_buf.ptr(); + DBUG_ASSERT(idx < m_parsed_buf.length() / sizeof (MY_XML_NODE)); + return nodebeg + idx; } @@ -2832,10 +2983,8 @@ String *Item_func_xml_extractvalue::val_str(String *str) { String *res; null_value= 0; - if (!nodeset_func || - !(res= args[0]->val_str(str)) || - !parse_xml(res, &pxml) || - !(res= nodeset_func->val_str(&tmp_value))) + if (!nodeset_func || get_xml(&xml) || + !(res= nodeset_func->val_str(str))) { null_value= 1; return 0; @@ -2844,22 +2993,37 @@ String *Item_func_xml_extractvalue::val_str(String *str) } +bool Item_func_xml_update::collect_result(String *str, + const MY_XML_NODE *cut, + const String *replace) +{ + uint offs= cut->type == MY_XML_NODE_TAG ? 1 : 0; + const char *end= cut->tagend + offs; + str->length(0); + str->set_charset(collation.collation); + return + /* Put the XML part preceding the replaced piece */ + str->append(xml.raw()->ptr(), cut->beg - xml.raw()->ptr() - offs) || + /* Put the replacement */ + str->append(replace->ptr(), replace->length()) || + /* Put the XML part following the replaced piece */ + str->append(end, xml.raw()->ptr() + xml.raw()->length() - end); +} + + String *Item_func_xml_update::val_str(String *str) { - String *res, *nodeset, *rep; + String *nodeset, *rep; null_value= 0; - if (!nodeset_func || - !(res= args[0]->val_str(str)) || + if (!nodeset_func || get_xml(&xml) || !(rep= args[2]->val_str(&tmp_value3)) || - !parse_xml(res, &pxml) || !(nodeset= nodeset_func->val_nodeset(&tmp_value2))) { null_value= 1; return 0; } - MY_XML_NODE *nodebeg= (MY_XML_NODE*) pxml.ptr(); MY_XPATH_FLT *fltbeg= (MY_XPATH_FLT*) nodeset->ptr(); MY_XPATH_FLT *fltend= (MY_XPATH_FLT*) (nodeset->ptr() + nodeset->length()); @@ -2867,10 +3031,10 @@ String *Item_func_xml_update::val_str(String *str) if (fltend - fltbeg != 1) { /* TODO: perhaps add a warning that more than one tag selected */ - return res; + return xml.raw(); } - nodebeg+= fltbeg->num; + const MY_XML_NODE *nodebeg= xml.node(fltbeg->num); if (!nodebeg->level) { @@ -2882,12 +3046,5 @@ String *Item_func_xml_update::val_str(String *str) return rep; } - tmp_value.length(0); - tmp_value.set_charset(collation.collation); - uint offs= nodebeg->type == MY_XML_NODE_TAG ? 1 : 0; - tmp_value.append(res->ptr(), nodebeg->beg - res->ptr() - offs); - tmp_value.append(rep->ptr(), rep->length()); - const char *end= nodebeg->tagend + offs; - tmp_value.append(end, res->ptr() + res->length() - end); - return &tmp_value; + return collect_result(str, nodebeg, rep) ? (String *) NULL : str; } diff --git a/sql/item_xmlfunc.h b/sql/item_xmlfunc.h index 800cf6ed760..92a8f757822 100644 --- a/sql/item_xmlfunc.h +++ b/sql/item_xmlfunc.h @@ -21,29 +21,77 @@ /* This file defines all XML functions */ -#ifdef __GNUC__ +#ifdef USE_PRAGMA_INTERFACE #pragma interface /* gcc class implementation */ #endif +typedef struct my_xml_node_st MY_XML_NODE; + + class Item_xml_str_func: public Item_str_func { protected: - String tmp_value, pxml; + /* + A helper class to store raw and parsed XML. + */ + class XML + { + bool m_cached; + String *m_raw_ptr; // Pointer to text representation + String m_raw_buf; // Cached text representation + String m_parsed_buf; // Array of MY_XML_NODEs, pointing to raw_buffer + bool parse(); + void reset() + { + m_cached= false; + m_raw_ptr= (String *) 0; + } + public: + XML() { reset(); } + void set_charset(CHARSET_INFO *cs) { m_parsed_buf.set_charset(cs); } + String *raw() { return m_raw_ptr; } + String *parsed() { return &m_parsed_buf; } + const MY_XML_NODE *node(uint idx); + bool cached() { return m_cached; } + bool parse(String *raw, bool cache); + bool parse(Item *item, bool cache) + { + String *res; + if (!(res= item->val_str(&m_raw_buf))) + { + m_raw_ptr= (String *) 0; + m_cached= cache; + return true; + } + return parse(res, cache); + } + }; + String m_xpath_query; // XPath query text Item *nodeset_func; + XML xml; + bool get_xml(XML *xml_arg, bool cache= false) + { + if (!cache && xml_arg->cached()) + return xml_arg->raw() == 0; + return xml_arg->parse(args[0], cache); + } public: - Item_xml_str_func(Item *a, Item *b): - Item_str_func(a,b) + Item_xml_str_func(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) { maybe_null= TRUE; } - Item_xml_str_func(Item *a, Item *b, Item *c): - Item_str_func(a,b,c) + Item_xml_str_func(THD *thd, Item *a, Item *b, Item *c): + Item_str_func(thd, a, b, c) { maybe_null= TRUE; } + bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec(); - String *parse_xml(String *raw_xml, String *parsed_xml_buf); + bool const_item() const + { + return const_item_cache && (!nodeset_func || nodeset_func->const_item()); + } bool check_vcol_func_processor(uchar *int_arg) { return trace_unsupported_by_check_vcol_func_processor(func_name()); @@ -54,7 +102,8 @@ public: class Item_func_xml_extractvalue: public Item_xml_str_func { public: - Item_func_xml_extractvalue(Item *a,Item *b) :Item_xml_str_func(a,b) {} + Item_func_xml_extractvalue(THD *thd, Item *a, Item *b): + Item_xml_str_func(thd, a, b) {} const char *func_name() const { return "extractvalue"; } String *val_str(String *); }; @@ -63,8 +112,12 @@ public: class Item_func_xml_update: public Item_xml_str_func { String tmp_value2, tmp_value3; + bool collect_result(String *str, + const MY_XML_NODE *cut, + const String *replace); public: - Item_func_xml_update(Item *a,Item *b,Item *c) :Item_xml_str_func(a,b,c) {} + Item_func_xml_update(THD *thd, Item *a, Item *b, Item *c): + Item_xml_str_func(thd, a, b, c) {} const char *func_name() const { return "updatexml"; } String *val_str(String *); }; diff --git a/sql/key.cc b/sql/key.cc index 7e5a3309b10..19b96522b2c 100644 --- a/sql/key.cc +++ b/sql/key.cc @@ -16,11 +16,14 @@ /* Functions to handle keys and fields in forms */ +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED: by includes later #include "key.h" // key_rec_cmp #include "field.h" // Field +using std::min; +using std::max; + /* Search after a key that starts with 'field' @@ -79,7 +82,7 @@ int find_ref_key(KEY *key, uint key_count, uchar *record, Field *field, KEY_PART_INFO *key_part; *key_length=0; for (j=0, key_part=key_info->key_part ; - j < key_info->key_parts ; + j < key_info->user_defined_key_parts ; j++, key_part++) { if (key_part->offset == fieldpos && @@ -125,8 +128,8 @@ void key_copy(uchar *to_key, uchar *from_record, KEY *key_info, { if (key_part->null_bit) { - *to_key++= test(from_record[key_part->null_offset] & - key_part->null_bit); + *to_key++= MY_TEST(from_record[key_part->null_offset] & + key_part->null_bit); key_length--; if (to_key[-1]) { @@ -134,7 +137,7 @@ void key_copy(uchar *to_key, uchar *from_record, KEY *key_info, Don't copy data for null values The -1 below is to subtract the null byte which is already handled */ - length= min(key_length, (uint) key_part->store_length-1); + length= min<uint>(key_length, key_part->store_length-1); if (with_zerofill) bzero((char*) to_key, length); continue; @@ -144,7 +147,7 @@ void key_copy(uchar *to_key, uchar *from_record, KEY *key_info, key_part->key_part_flag & HA_VAR_LENGTH_PART) { key_length-= HA_KEY_BLOB_LENGTH; - length= min(key_length, key_part->length); + length= min<uint>(key_length, key_part->length); uint bytes= key_part->field->get_key_image(to_key, length, key_info->flags & HA_SPATIAL ? Field::itMBR : Field::itRAW); if (with_zerofill && bytes < length) @@ -153,7 +156,7 @@ void key_copy(uchar *to_key, uchar *from_record, KEY *key_info, } else { - length= min(key_length, key_part->length); + length= min<uint>(key_length, key_part->length); Field *field= key_part->field; CHARSET_INFO *cs= field->charset(); uint bytes= field->get_key_image(to_key, length, Field::itRAW); @@ -205,7 +208,7 @@ void key_restore(uchar *to_record, uchar *from_key, KEY *key_info, Don't copy data for null bytes The -1 below is to subtract the null byte which is already handled */ - length= min(key_length, (uint) key_part->store_length-1); + length= min<uint>(key_length, key_part->store_length-1); continue; } } @@ -247,7 +250,7 @@ void key_restore(uchar *to_record, uchar *from_key, KEY *key_info, my_ptrdiff_t ptrdiff= to_record - field->table->record[0]; field->move_field_offset(ptrdiff); key_length-= HA_KEY_BLOB_LENGTH; - length= min(key_length, key_part->length); + length= min<uint>(key_length, key_part->length); old_map= dbug_tmp_use_all_columns(field->table, field->table->write_set); field->set_key_image(from_key, length); dbug_tmp_restore_column_map(field->table->write_set, old_map); @@ -256,7 +259,7 @@ void key_restore(uchar *to_record, uchar *from_key, KEY *key_info, } else { - length= min(key_length, key_part->length); + length= min<uint>(key_length, key_part->length); /* skip the byte with 'uneven' bits, if used */ memcpy(to_record + key_part->offset, from_key + used_uneven_bits , (size_t) length - used_uneven_bits); @@ -300,8 +303,8 @@ bool key_cmp_if_same(TABLE *table,const uchar *key,uint idx,uint key_length) if (key_part->null_bit) { - if (*key != test(table->record[0][key_part->null_offset] & - key_part->null_bit)) + if (*key != MY_TEST(table->record[0][key_part->null_offset] & + key_part->null_bit)) return 1; if (*key) continue; @@ -413,18 +416,17 @@ void field_unpack(String *to, Field *field, const uchar *rec, uint max_length, @param table Table to use @param - idx Key number + key Key */ -void key_unpack(String *to,TABLE *table,uint idx) +void key_unpack(String *to, TABLE *table, KEY *key) { - KEY_PART_INFO *key_part,*key_part_end; my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); DBUG_ENTER("key_unpack"); to->length(0); - for (key_part=table->key_info[idx].key_part,key_part_end=key_part+ - table->key_info[idx].key_parts ; + KEY_PART_INFO *key_part_end= key->key_part + key->user_defined_key_parts; + for (KEY_PART_INFO *key_part= key->key_part; key_part < key_part_end; key_part++) { @@ -434,12 +436,12 @@ void key_unpack(String *to,TABLE *table,uint idx) { if (table->record[0][key_part->null_offset] & key_part->null_bit) { - to->append(STRING_WITH_LEN("NULL")); - continue; + to->append(STRING_WITH_LEN("NULL")); + continue; } } field_unpack(to, key_part->field, table->record[0], key_part->length, - test(key_part->key_part_flag & HA_PART_KEY_SEG)); + MY_TEST(key_part->key_part_flag & HA_PART_KEY_SEG)); } dbug_tmp_restore_column_map(table->read_set, old_map); DBUG_VOID_RETURN; @@ -577,7 +579,7 @@ int key_rec_cmp(void *key_p, uchar *first_rec, uchar *second_rec) /* loop over all given keys */ do { - key_parts= key_info->key_parts; + key_parts= key_info->user_defined_key_parts; key_part= key_info->key_part; key_part_num= 0; @@ -589,8 +591,8 @@ int key_rec_cmp(void *key_p, uchar *first_rec, uchar *second_rec) if (key_part->null_bit) { /* The key_part can contain NULL values */ - bool first_is_null= field->is_null_in_record_with_offset(first_diff); - bool sec_is_null= field->is_null_in_record_with_offset(sec_diff); + bool first_is_null= field->is_real_null(first_diff); + bool sec_is_null= field->is_real_null(sec_diff); /* NULL is smaller then everything so if first is NULL and the other not then we know that we should return -1 and for the opposite @@ -656,9 +658,8 @@ int key_tuple_cmp(KEY_PART_INFO *part, uchar *key1, uchar *key2, uint tuple_length) { uchar *key1_end= key1 + tuple_length; - int len; + int UNINIT_VAR(len); int res; - LINT_INIT(len); for (;key1 < key1_end; key1 += len, key2 += len, part++) { len= part->store_length; @@ -709,12 +710,9 @@ ulong key_hashnr(KEY *key_info, uint used_key_parts, const uchar *key) for (; key_part < end_key_part; key_part++) { uchar *pos= (uchar*)key; - CHARSET_INFO *cs; - uint length, pack_length; + CHARSET_INFO *UNINIT_VAR(cs); + uint UNINIT_VAR(length), UNINIT_VAR(pack_length); bool is_string= TRUE; - LINT_INIT(cs); - LINT_INIT(length); - LINT_INIT(pack_length); key+= key_part->length; if (key_part->null_bit) @@ -816,13 +814,9 @@ bool key_buf_cmp(KEY *key_info, uint used_key_parts, { uchar *pos1= (uchar*)key1; uchar *pos2= (uchar*)key2; - CHARSET_INFO *cs; - uint length1, length2, pack_length; + CHARSET_INFO *UNINIT_VAR(cs); + uint UNINIT_VAR(length1), UNINIT_VAR(length2), UNINIT_VAR(pack_length); bool is_string= TRUE; - LINT_INIT(cs); - LINT_INIT(length1); - LINT_INIT(length2); - LINT_INIT(pack_length); key1+= key_part->length; key2+= key_part->length; diff --git a/sql/key.h b/sql/key.h index f5f9a19178c..47b981f5298 100644 --- a/sql/key.h +++ b/sql/key.h @@ -32,7 +32,7 @@ void key_copy(uchar *to_key, uchar *from_record, KEY *key_info, uint key_length, void key_restore(uchar *to_record, uchar *from_key, KEY *key_info, uint key_length); bool key_cmp_if_same(TABLE *form,const uchar *key,uint index,uint key_length); -void key_unpack(String *to,TABLE *form,uint index); +void key_unpack(String *to, TABLE *table, KEY *key); void field_unpack(String *to, Field *field, const uchar *rec, uint max_length, bool prefix_key); bool is_key_used(TABLE *table, uint idx, const MY_BITMAP *fields); diff --git a/sql/keycaches.cc b/sql/keycaches.cc index 26a39808c56..78e64ebac72 100644 --- a/sql/keycaches.cc +++ b/sql/keycaches.cc @@ -20,6 +20,7 @@ ****************************************************************************/ NAMED_ILIST key_caches; +NAMED_ILIST rpl_filters; /** ilink (intrusive list element) with a name @@ -66,6 +67,23 @@ uchar* find_named(I_List<NAMED_ILINK> *list, const char *name, uint length, } +bool NAMED_ILIST::delete_element(const char *name, uint length, void (*free_element)(const char *name, uchar*)) +{ + I_List_iterator<NAMED_ILINK> it(*this); + NAMED_ILINK *element; + DBUG_ENTER("NAMED_ILIST::delete_element"); + while ((element= it++)) + { + if (element->cmp(name, length)) + { + (*free_element)(element->name, element->data); + delete element; + DBUG_RETURN(0); + } + } + DBUG_RETURN(1); +} + void NAMED_ILIST::delete_elements(void (*free_element)(const char *name, uchar*)) { NAMED_ILINK *element; @@ -85,7 +103,7 @@ LEX_STRING default_key_cache_base= {C_STRING_WITH_LEN("default")}; KEY_CACHE zero_key_cache; ///< @@nonexistent_cache.param->value_ptr() points here -KEY_CACHE *get_key_cache(LEX_STRING *cache_name) +KEY_CACHE *get_key_cache(const LEX_STRING *cache_name) { if (!cache_name || ! cache_name->length) cache_name= &default_key_cache_base; @@ -159,7 +177,56 @@ bool process_key_caches(process_key_cache_t func, void *param) return res != 0; } -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class I_List_iterator<NAMED_ILINK>; -#endif +/* Rpl_filter functions */ + +LEX_STRING default_rpl_filter_base= {C_STRING_WITH_LEN("")}; + +Rpl_filter *get_rpl_filter(LEX_STRING *filter_name) +{ + if (!filter_name->length) + filter_name= &default_rpl_filter_base; + return ((Rpl_filter*) find_named(&rpl_filters, + filter_name->str, filter_name->length, 0)); +} + +Rpl_filter *create_rpl_filter(const char *name, uint length) +{ + Rpl_filter *filter; + DBUG_ENTER("create_rpl_filter"); + DBUG_PRINT("enter",("name: %.*s", length, name)); + + filter= new Rpl_filter; + if (filter) + { + if (!new NAMED_ILINK(&rpl_filters, name, length, (uchar*) filter)) + { + delete filter; + filter= 0; + } + } + DBUG_RETURN(filter); +} + +Rpl_filter *get_or_create_rpl_filter(const char *name, uint length) +{ + LEX_STRING rpl_filter_name; + Rpl_filter *filter; + + rpl_filter_name.str= (char *) name; + rpl_filter_name.length= length; + if (!(filter= get_rpl_filter(&rpl_filter_name))) + filter= create_rpl_filter(name, length); + return filter; +} + +void free_rpl_filter(const char *name, Rpl_filter *filter) +{ + delete filter; + filter= 0; +} + +void free_all_rpl_filters() +{ + rpl_filters.delete_elements((void (*)(const char*, uchar*)) free_rpl_filter); +} diff --git a/sql/keycaches.h b/sql/keycaches.h index 04d3f6145e7..fff48d51c6f 100644 --- a/sql/keycaches.h +++ b/sql/keycaches.h @@ -18,6 +18,7 @@ #include "sql_list.h" #include <keycache.h> +#include <rpl_filter.h> extern "C" { @@ -30,16 +31,28 @@ class NAMED_ILIST: public I_List<NAMED_ILINK> { public: void delete_elements(void (*free_element)(const char*, uchar*)); + bool delete_element(const char *name, uint length, void (*free_element)(const char*, uchar*)); }; +/* For key cache */ extern LEX_STRING default_key_cache_base; extern KEY_CACHE zero_key_cache; extern NAMED_ILIST key_caches; KEY_CACHE *create_key_cache(const char *name, uint length); -KEY_CACHE *get_key_cache(LEX_STRING *cache_name); +KEY_CACHE *get_key_cache(const LEX_STRING *cache_name); KEY_CACHE *get_or_create_key_cache(const char *name, uint length); void free_key_cache(const char *name, KEY_CACHE *key_cache); bool process_key_caches(process_key_cache_t func, void *param); +/* For Rpl_filter */ +extern LEX_STRING default_rpl_filter_base; +extern NAMED_ILIST rpl_filters; + +Rpl_filter *create_rpl_filter(const char *name, uint length); +Rpl_filter *get_rpl_filter(LEX_STRING *filter_name); +Rpl_filter *get_or_create_rpl_filter(const char *name, uint length); +void free_rpl_filter(const char *name, Rpl_filter *filter); +void free_all_rpl_filters(void); + #endif /* KEYCACHES_INCLUDED */ diff --git a/sql/lex.h b/sql/lex.h index aec2ec29dca..87c87d03fb3 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -1,7 +1,8 @@ #ifndef LEX_INCLUDED #define LEX_INCLUDED -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. + Copyright (c) 2009, 2015, 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 @@ -56,6 +57,7 @@ static SYMBOL symbols[] = { { "ACCESSIBLE", SYM(ACCESSIBLE_SYM)}, { "ACTION", SYM(ACTION)}, { "ADD", SYM(ADD)}, + { "ADMIN", SYM(ADMIN_SYM)}, { "AFTER", SYM(AFTER_SYM)}, { "AGAINST", SYM(AGAINST)}, { "AGGREGATE", SYM(AGGREGATE_SYM)}, @@ -71,9 +73,11 @@ static SYMBOL symbols[] = { { "ASCII", SYM(ASCII_SYM)}, { "ASENSITIVE", SYM(ASENSITIVE_SYM)}, { "AT", SYM(AT_SYM)}, + { "ATOMIC", SYM(ATOMIC_SYM)}, { "AUTHORS", SYM(AUTHORS_SYM)}, { "AUTO_INCREMENT", SYM(AUTO_INC)}, { "AUTOEXTEND_SIZE", SYM(AUTOEXTEND_SIZE_SYM)}, + { "AUTO", SYM(AUTO_SYM)}, { "AVG", SYM(AVG_SYM)}, { "AVG_ROW_LENGTH", SYM(AVG_ROW_LENGTH)}, { "BACKUP", SYM(BACKUP_SYM)}, @@ -110,7 +114,6 @@ static SYMBOL symbols[] = { { "CIPHER", SYM(CIPHER_SYM)}, { "CLASS_ORIGIN", SYM(CLASS_ORIGIN_SYM)}, { "CLIENT", SYM(CLIENT_SYM)}, - { "CLIENT_STATISTICS", SYM(CLIENT_STATS_SYM)}, { "CLOSE", SYM(CLOSE_SYM)}, { "COALESCE", SYM(COALESCE)}, { "CODE", SYM(CODE_SYM)}, @@ -120,11 +123,10 @@ static SYMBOL symbols[] = { { "COLUMN_NAME", SYM(COLUMN_NAME_SYM)}, { "COLUMNS", SYM(COLUMNS)}, { "COLUMN_ADD", SYM(COLUMN_ADD_SYM)}, + { "COLUMN_CHECK", SYM(COLUMN_CHECK_SYM)}, { "COLUMN_CREATE", SYM(COLUMN_CREATE_SYM)}, { "COLUMN_DELETE", SYM(COLUMN_DELETE_SYM)}, - { "COLUMN_EXISTS", SYM(COLUMN_EXISTS_SYM)}, { "COLUMN_GET", SYM(COLUMN_GET_SYM)}, - { "COLUMN_LIST", SYM(COLUMN_LIST_SYM)}, { "COMMENT", SYM(COMMENT_SYM)}, { "COMMIT", SYM(COMMIT_SYM)}, { "COMMITTED", SYM(COMMITTED_SYM)}, @@ -148,7 +150,10 @@ static SYMBOL symbols[] = { { "CREATE", SYM(CREATE)}, { "CROSS", SYM(CROSS)}, { "CUBE", SYM(CUBE_SYM)}, + { "CURRENT", SYM(CURRENT_SYM)}, { "CURRENT_DATE", SYM(CURDATE)}, + { "CURRENT_POS", SYM(CURRENT_POS_SYM)}, + { "CURRENT_ROLE", SYM(CURRENT_ROLE)}, { "CURRENT_TIME", SYM(CURTIME)}, { "CURRENT_TIMESTAMP", SYM(NOW_SYM)}, { "CURRENT_USER", SYM(CURRENT_USER)}, @@ -174,10 +179,12 @@ static SYMBOL symbols[] = { { "DELAYED", SYM(DELAYED_SYM)}, { "DELAY_KEY_WRITE", SYM(DELAY_KEY_WRITE_SYM)}, { "DELETE", SYM(DELETE_SYM)}, + { "DELETE_DOMAIN_ID", SYM(DELETE_DOMAIN_ID_SYM)}, { "DESC", SYM(DESC)}, { "DESCRIBE", SYM(DESCRIBE)}, { "DES_KEY_FILE", SYM(DES_KEY_FILE)}, { "DETERMINISTIC", SYM(DETERMINISTIC_SYM)}, + { "DIAGNOSTICS", SYM(DIAGNOSTICS_SYM)}, { "DIRECTORY", SYM(DIRECTORY_SYM)}, { "DISABLE", SYM(DISABLE_SYM)}, { "DISCARD", SYM(DISCARD)}, @@ -187,6 +194,7 @@ static SYMBOL symbols[] = { { "DIV", SYM(DIV_SYM)}, { "DO", SYM(DO_SYM)}, { "DOUBLE", SYM(DOUBLE_SYM)}, + { "DO_DOMAIN_IDS", SYM(DO_DOMAIN_IDS_SYM)}, { "DROP", SYM(DROP)}, { "DUAL", SYM(DUAL_SYM)}, { "DUMPFILE", SYM(DUMPFILE)}, @@ -210,10 +218,12 @@ static SYMBOL symbols[] = { { "EVENTS", SYM(EVENTS_SYM)}, { "EVERY", SYM(EVERY_SYM)}, { "EXAMINED", SYM(EXAMINED_SYM)}, + { "EXCHANGE", SYM(EXCHANGE_SYM)}, { "EXECUTE", SYM(EXECUTE_SYM)}, { "EXISTS", SYM(EXISTS)}, { "EXIT", SYM(EXIT_SYM)}, { "EXPANSION", SYM(EXPANSION_SYM)}, + { "EXPORT", SYM(EXPORT_SYM)}, { "EXPLAIN", SYM(DESCRIBE)}, { "EXTENDED", SYM(EXTENDED_SYM)}, { "EXTENT_SIZE", SYM(EXTENT_SIZE_SYM)}, @@ -232,6 +242,7 @@ static SYMBOL symbols[] = { { "FOR", SYM(FOR_SYM)}, { "FORCE", SYM(FORCE_SYM)}, { "FOREIGN", SYM(FOREIGN)}, + { "FORMAT", SYM(FORMAT_SYM)}, { "FOUND", SYM(FOUND_SYM)}, { "FROM", SYM(FROM)}, { "FULL", SYM(FULL)}, @@ -242,6 +253,7 @@ static SYMBOL symbols[] = { { "GEOMETRY", SYM(GEOMETRY_SYM)}, { "GEOMETRYCOLLECTION",SYM(GEOMETRYCOLLECTION)}, { "GET_FORMAT", SYM(GET_FORMAT)}, + { "GET", SYM(GET_SYM)}, { "GLOBAL", SYM(GLOBAL_SYM)}, { "GRANT", SYM(GRANT)}, { "GRANTS", SYM(GRANTS)}, @@ -258,15 +270,16 @@ static SYMBOL symbols[] = { { "HOUR_MICROSECOND", SYM(HOUR_MICROSECOND_SYM)}, { "HOUR_MINUTE", SYM(HOUR_MINUTE_SYM)}, { "HOUR_SECOND", SYM(HOUR_SECOND_SYM)}, + { "ID", SYM(ID_SYM)}, { "IDENTIFIED", SYM(IDENTIFIED_SYM)}, - { "IF", SYM(IF)}, + { "IF", SYM(IF_SYM)}, { "IGNORE", SYM(IGNORE_SYM)}, + { "IGNORE_DOMAIN_IDS", SYM(IGNORE_DOMAIN_IDS_SYM)}, { "IGNORE_SERVER_IDS", SYM(IGNORE_SERVER_IDS_SYM)}, { "IMPORT", SYM(IMPORT)}, { "IN", SYM(IN_SYM)}, { "INDEX", SYM(INDEX_SYM)}, { "INDEXES", SYM(INDEXES)}, - { "INDEX_STATISTICS", SYM(INDEX_STATS_SYM)}, { "INFILE", SYM(INFILE)}, { "INITIAL_SIZE", SYM(INITIAL_SIZE_SYM)}, { "INNER", SYM(INNER_SYM)}, @@ -327,6 +340,7 @@ static SYMBOL symbols[] = { { "LOW_PRIORITY", SYM(LOW_PRIORITY)}, { "MASTER", SYM(MASTER_SYM)}, { "MASTER_CONNECT_RETRY", SYM(MASTER_CONNECT_RETRY_SYM)}, + { "MASTER_GTID_POS", SYM(MASTER_GTID_POS_SYM)}, { "MASTER_HOST", SYM(MASTER_HOST_SYM)}, { "MASTER_LOG_FILE", SYM(MASTER_LOG_FILE_SYM)}, { "MASTER_LOG_POS", SYM(MASTER_LOG_POS_SYM)}, @@ -338,15 +352,19 @@ static SYMBOL symbols[] = { { "MASTER_SSL_CAPATH",SYM(MASTER_SSL_CAPATH_SYM)}, { "MASTER_SSL_CERT", SYM(MASTER_SSL_CERT_SYM)}, { "MASTER_SSL_CIPHER",SYM(MASTER_SSL_CIPHER_SYM)}, + { "MASTER_SSL_CRL", SYM(MASTER_SSL_CRL_SYM)}, + { "MASTER_SSL_CRLPATH",SYM(MASTER_SSL_CRLPATH_SYM)}, { "MASTER_SSL_KEY", SYM(MASTER_SSL_KEY_SYM)}, { "MASTER_SSL_VERIFY_SERVER_CERT", SYM(MASTER_SSL_VERIFY_SERVER_CERT_SYM)}, { "MASTER_USER", SYM(MASTER_USER_SYM)}, + { "MASTER_USE_GTID", SYM(MASTER_USE_GTID_SYM)}, { "MASTER_HEARTBEAT_PERIOD", SYM(MASTER_HEARTBEAT_PERIOD_SYM)}, { "MATCH", SYM(MATCH)}, { "MAX_CONNECTIONS_PER_HOUR", SYM(MAX_CONNECTIONS_PER_HOUR)}, { "MAX_QUERIES_PER_HOUR", SYM(MAX_QUERIES_PER_HOUR)}, { "MAX_ROWS", SYM(MAX_ROWS)}, { "MAX_SIZE", SYM(MAX_SIZE_SYM)}, + { "MAX_STATEMENT_TIME", SYM(MAX_STATEMENT_TIME_SYM)}, { "MAX_UPDATES_PER_HOUR", SYM(MAX_UPDATES_PER_HOUR)}, { "MAX_USER_CONNECTIONS", SYM(MAX_USER_CONNECTIONS_SYM)}, { "MAXVALUE", SYM(MAX_VALUE_SYM)}, @@ -379,8 +397,6 @@ static SYMBOL symbols[] = { { "NAMES", SYM(NAMES_SYM)}, { "NATIONAL", SYM(NATIONAL_SYM)}, { "NATURAL", SYM(NATURAL)}, - { "NDB", SYM(NDBCLUSTER_SYM)}, - { "NDBCLUSTER", SYM(NDBCLUSTER_SYM)}, { "NCHAR", SYM(NCHAR_SYM)}, { "NEW", SYM(NEW_SYM)}, { "NEXT", SYM(NEXT_SYM)}, @@ -391,14 +407,15 @@ static SYMBOL symbols[] = { { "NOT", SYM(NOT_SYM)}, { "NO_WRITE_TO_BINLOG", SYM(NO_WRITE_TO_BINLOG)}, { "NULL", SYM(NULL_SYM)}, + { "NUMBER", SYM(NUMBER_SYM)}, { "NUMERIC", SYM(NUMERIC_SYM)}, { "NVARCHAR", SYM(NVARCHAR_SYM)}, { "OFFSET", SYM(OFFSET_SYM)}, - { "OLD_PASSWORD", SYM(OLD_PASSWORD)}, + { "OLD_PASSWORD", SYM(OLD_PASSWORD_SYM)}, { "ON", SYM(ON)}, { "ONE", SYM(ONE_SYM)}, - { "ONE_SHOT", SYM(ONE_SHOT_SYM)}, { "ONLINE", SYM(ONLINE_SYM)}, + { "ONLY", SYM(ONLY_SYM)}, { "OPEN", SYM(OPEN_SYM)}, { "OPTIMIZE", SYM(OPTIMIZE)}, { "OPTIONS", SYM(OPTIONS_SYM)}, @@ -419,7 +436,7 @@ static SYMBOL symbols[] = { { "PARTITION", SYM(PARTITION_SYM)}, { "PARTITIONING", SYM(PARTITIONING_SYM)}, { "PARTITIONS", SYM(PARTITIONS_SYM)}, - { "PASSWORD", SYM(PASSWORD)}, + { "PASSWORD", SYM(PASSWORD_SYM)}, { "PERSISTENT", SYM(PERSISTENT_SYM)}, { "PHASE", SYM(PHASE_SYM)}, { "PLUGIN", SYM(PLUGIN_SYM)}, @@ -477,15 +494,20 @@ static SYMBOL symbols[] = { { "RESTORE", SYM(RESTORE_SYM)}, { "RESTRICT", SYM(RESTRICT)}, { "RESUME", SYM(RESUME_SYM)}, + { "RETURNED_SQLSTATE",SYM(RETURNED_SQLSTATE_SYM)}, { "RETURN", SYM(RETURN_SYM)}, + { "RETURNING", SYM(RETURNING_SYM)}, { "RETURNS", SYM(RETURNS_SYM)}, + { "REVERSE", SYM(REVERSE_SYM)}, { "REVOKE", SYM(REVOKE)}, { "RIGHT", SYM(RIGHT)}, { "RLIKE", SYM(REGEXP)}, /* Like in mSQL2 */ + { "ROLE", SYM(ROLE_SYM)}, { "ROLLBACK", SYM(ROLLBACK_SYM)}, { "ROLLUP", SYM(ROLLUP_SYM)}, { "ROUTINE", SYM(ROUTINE_SYM)}, { "ROW", SYM(ROW_SYM)}, + { "ROW_COUNT", SYM(ROW_COUNT_SYM)}, { "ROWS", SYM(ROWS_SYM)}, { "ROW_FORMAT", SYM(ROW_FORMAT_SYM)}, { "RTREE", SYM(RTREE_SYM)}, @@ -512,6 +534,8 @@ static SYMBOL symbols[] = { { "SIGNED", SYM(SIGNED_SYM)}, { "SIMPLE", SYM(SIMPLE_SYM)}, { "SLAVE", SYM(SLAVE)}, + { "SLAVES", SYM(SLAVES)}, + { "SLAVE_POS", SYM(SLAVE_POS_SYM)}, { "SLOW", SYM(SLOW)}, { "SNAPSHOT", SYM(SNAPSHOT_SYM)}, { "SMALLINT", SYM(SMALLINT)}, @@ -523,6 +547,7 @@ static SYMBOL symbols[] = { { "SOURCE", SYM(SOURCE_SYM)}, { "SPATIAL", SYM(SPATIAL_SYM)}, { "SPECIFIC", SYM(SPECIFIC_SYM)}, + { "REF_SYSTEM_ID", SYM(REF_SYSTEM_ID_SYM)}, { "SQL", SYM(SQL_SYM)}, { "SQLEXCEPTION", SYM(SQLEXCEPTION_SYM)}, { "SQLSTATE", SYM(SQLSTATE_SYM)}, @@ -546,6 +571,10 @@ static SYMBOL symbols[] = { { "START", SYM(START_SYM)}, { "STARTING", SYM(STARTING)}, { "STARTS", SYM(STARTS_SYM)}, + { "STATEMENT", SYM(STATEMENT_SYM)}, + { "STATS_AUTO_RECALC",SYM(STATS_AUTO_RECALC_SYM)}, + { "STATS_PERSISTENT", SYM(STATS_PERSISTENT_SYM)}, + { "STATS_SAMPLE_PAGES",SYM(STATS_SAMPLE_PAGES_SYM)}, { "STATUS", SYM(STATUS_SYM)}, { "STOP", SYM(STOP_SYM)}, { "STORAGE", SYM(STORAGE_SYM)}, @@ -563,7 +592,6 @@ static SYMBOL symbols[] = { { "TABLE_NAME", SYM(TABLE_NAME_SYM)}, { "TABLES", SYM(TABLES)}, { "TABLESPACE", SYM(TABLESPACE)}, - { "TABLE_STATISTICS", SYM(TABLE_STATS_SYM)}, { "TABLE_CHECKSUM", SYM(TABLE_CHECKSUM_SYM)}, { "TEMPORARY", SYM(TEMPORARY)}, { "TEMPTABLE", SYM(TEMPTABLE_SYM)}, @@ -605,9 +633,8 @@ static SYMBOL symbols[] = { { "UPGRADE", SYM(UPGRADE_SYM)}, { "USAGE", SYM(USAGE)}, { "USE", SYM(USE_SYM)}, - { "USER", SYM(USER)}, + { "USER", SYM(USER_SYM)}, { "USER_RESOURCES", SYM(RESOURCES)}, - { "USER_STATISTICS", SYM(USER_STATS_SYM)}, { "USE_FRM", SYM(USE_FRM)}, { "USING", SYM(USING)}, { "UTC_DATE", SYM(UTC_DATE_SYM)}, @@ -626,6 +653,7 @@ static SYMBOL symbols[] = { { "WAIT", SYM(WAIT_SYM)}, { "WARNINGS", SYM(WARNINGS)}, { "WEEK", SYM(WEEK_SYM)}, + { "WEIGHT_STRING", SYM(WEIGHT_STRING_SYM)}, { "WHEN", SYM(WHEN_SYM)}, { "WHERE", SYM(WHERE)}, { "WHILE", SYM(WHILE_SYM)}, @@ -662,7 +690,7 @@ static SYMBOL sql_functions[] = { { "MIN", SYM(MIN_SYM)}, { "NOW", SYM(NOW_SYM)}, { "POSITION", SYM(POSITION_SYM)}, - { "SESSION_USER", SYM(USER)}, + { "SESSION_USER", SYM(USER_SYM)}, { "STD", SYM(STD_SYM)}, { "STDDEV", SYM(STD_SYM)}, { "STDDEV_POP", SYM(STD_SYM)}, @@ -672,7 +700,7 @@ static SYMBOL sql_functions[] = { { "SUBSTRING", SYM(SUBSTRING)}, { "SUM", SYM(SUM_SYM)}, { "SYSDATE", SYM(SYSDATE)}, - { "SYSTEM_USER", SYM(USER)}, + { "SYSTEM_USER", SYM(USER_SYM)}, { "TRIM", SYM(TRIM)}, { "VARIANCE", SYM(VARIANCE_SYM)}, { "VAR_POP", SYM(VARIANCE_SYM)}, diff --git a/sql/lock.cc b/sql/lock.cc index 31ef1314f5a..f3445e3b38a 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -11,8 +11,8 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ /** @@ -68,21 +68,17 @@ table_handler->external_lock(thd, F_UNLCK) for each table that was locked, excluding one that caused failure. That means handler must cleanup itself in case external_lock() fails. - - @todo - Change to use my_malloc() ONLY when using LOCK TABLES command or when - we are forced to use mysql_lock_merge. */ +#include <my_global.h> #include "sql_priv.h" #include "debug_sync.h" -#include "unireg.h" // REQUIRED: for other includes #include "lock.h" #include "sql_base.h" // close_tables_for_reopen #include "sql_parse.h" // is_log_table_write_query #include "sql_acl.h" // SUPER_ACL #include <hash.h> -#include <assert.h> +#include "wsrep_mysqld.h" /** @defgroup Locking Locking @@ -93,7 +89,6 @@ extern HASH open_cache; static int lock_external(THD *thd, TABLE **table,uint count); static int unlock_external(THD *thd, TABLE **table,uint count); -static void print_lock_error(int error, const char *); /* Map the return value of thr_lock to an error from errmsg.txt */ static int thr_lock_errno_to_mysql[]= @@ -265,19 +260,24 @@ void reset_lock_data(MYSQL_LOCK *sql_lock, bool unlock) MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags) { MYSQL_LOCK *sql_lock; + uint gld_flags= GET_LOCK_STORE_LOCKS; DBUG_ENTER("mysql_lock_tables(tables)"); if (lock_tables_check(thd, tables, count, flags)) DBUG_RETURN(NULL); - if (! (sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS))) + if (!(thd->variables.option_bits & OPTION_TABLE_LOCK)) + gld_flags|= GET_LOCK_ON_THD; + + if (! (sql_lock= get_lock_data(thd, tables, count, gld_flags))) DBUG_RETURN(NULL); if (mysql_lock_tables(thd, sql_lock, flags)) { /* Clear the lock type of all lock data to avoid reusage. */ reset_lock_data(sql_lock, 1); - my_free(sql_lock); + if (!(gld_flags & GET_LOCK_ON_THD)) + my_free(sql_lock); sql_lock= 0; } DBUG_RETURN(sql_lock); @@ -301,19 +301,21 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, uint flags) int rc= 1; ulong timeout= (flags & MYSQL_LOCK_IGNORE_TIMEOUT) ? LONG_TIMEOUT : thd->variables.lock_wait_timeout; - + PSI_stage_info org_stage; DBUG_ENTER("mysql_lock_tables(sql_lock)"); - thd_proc_info(thd, "System lock"); + thd->backup_stage(&org_stage); + THD_STAGE_INFO(thd, stage_system_lock); if (sql_lock->table_count && lock_external(thd, sql_lock->table, sql_lock->table_count)) goto end; - thd_proc_info(thd, "Table lock"); + THD_STAGE_INFO(thd, stage_table_lock); /* Copy the lock data array. thr_multi_lock() reorders its contents. */ - memcpy(sql_lock->locks + sql_lock->lock_count, sql_lock->locks, - sql_lock->lock_count * sizeof(*sql_lock->locks)); + memmove(sql_lock->locks + sql_lock->lock_count, sql_lock->locks, + sql_lock->lock_count * sizeof(*sql_lock->locks)); + /* Lock on the copied half of the lock data array. */ rc= thr_lock_errno_to_mysql[(int) thr_multi_lock(sql_lock->locks + sql_lock->lock_count, @@ -323,13 +325,16 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, uint flags) (void) unlock_external(thd, sql_lock->table, sql_lock->table_count); end: - thd_proc_info(thd, "After table lock"); + THD_STAGE_INFO(thd, org_stage); if (thd->killed) { thd->send_kill_message(); if (!rc) + { mysql_unlock_tables(thd, sql_lock, 0); + THD_STAGE_INFO(thd, stage_after_table_lock); + } rc= 1; } else if (rc > 1) @@ -358,7 +363,7 @@ static int lock_external(THD *thd, TABLE **tables, uint count) if ((error=(*tables)->file->ha_external_lock(thd,lock_type))) { - print_lock_error(error, (*tables)->file->table_type()); + (*tables)->file->print_error(error, MYF(0)); while (--i) { tables--; @@ -377,15 +382,27 @@ static int lock_external(THD *thd, TABLE **tables, uint count) } +void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock) +{ + mysql_unlock_tables(thd, sql_lock, + thd->variables.option_bits & OPTION_TABLE_LOCK); +} + + void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock, bool free_lock) { DBUG_ENTER("mysql_unlock_tables"); + bool errors= thd->is_error(); + THD_STAGE_INFO(thd, stage_unlocking_tables); + if (sql_lock->table_count) unlock_external(thd, sql_lock->table, sql_lock->table_count); if (sql_lock->lock_count) thr_multi_unlock(sql_lock->locks, sql_lock->lock_count, 0); if (free_lock) my_free(sql_lock); + if (!errors) + thd->clear_error(); DBUG_VOID_RETURN; } @@ -397,9 +414,10 @@ void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock, bool free_lock) void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count) { - MYSQL_LOCK *sql_lock; - if ((sql_lock= get_lock_data(thd, table, count, GET_LOCK_UNLOCK))) - mysql_unlock_tables(thd, sql_lock, 1); + MYSQL_LOCK *sql_lock= + get_lock_data(thd, table, count, GET_LOCK_UNLOCK | GET_LOCK_ON_THD); + if (sql_lock) + mysql_unlock_tables(thd, sql_lock, 0); } @@ -539,23 +557,6 @@ void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table) } -/** Abort all other threads waiting to get lock in table. */ - -void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock) -{ - MYSQL_LOCK *locked; - DBUG_ENTER("mysql_lock_abort"); - - if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK))) - { - for (uint i=0; i < locked->lock_count; i++) - thr_abort_locks(locked->locks[i]->lock, upgrade_lock); - my_free(locked); - } - DBUG_VOID_RETURN; -} - - /** Abort one thread / table combination. @@ -574,7 +575,7 @@ bool mysql_lock_abort_for_thread(THD *thd, TABLE *table) bool result= FALSE; DBUG_ENTER("mysql_lock_abort_for_thread"); - if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK))) + if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK | GET_LOCK_ON_THD))) { for (uint i=0; i < locked->lock_count; i++) { @@ -582,7 +583,6 @@ bool mysql_lock_abort_for_thread(THD *thd, TABLE *table) table->in_use->thread_id)) result= TRUE; } - my_free(locked); } DBUG_RETURN(result); } @@ -672,8 +672,8 @@ static int unlock_external(THD *thd, TABLE **table,uint count) (*table)->current_lock = F_UNLCK; if ((error=(*table)->file->ha_external_lock(thd, F_UNLCK))) { - error_code=error; - print_lock_error(error_code, (*table)->file->table_type()); + error_code= error; + (*table)->file->print_error(error, MYF(0)); } } table++; @@ -694,25 +694,23 @@ static int unlock_external(THD *thd, TABLE **table,uint count) MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags) { - uint i,tables,lock_count; + uint i,lock_count,table_count; MYSQL_LOCK *sql_lock; THR_LOCK_DATA **locks, **locks_buf; TABLE **to, **table_buf; DBUG_ENTER("get_lock_data"); - DBUG_ASSERT((flags == GET_LOCK_UNLOCK) || (flags == GET_LOCK_STORE_LOCKS)); DBUG_PRINT("info", ("count %d", count)); - for (i=tables=lock_count=0 ; i < count ; i++) + for (i=lock_count=table_count=0 ; i < count ; i++) { TABLE *t= table_ptr[i]; - if (t->s->tmp_table != NON_TRANSACTIONAL_TMP_TABLE && t->s->tmp_table != INTERNAL_TMP_TABLE) { - tables+= t->file->lock_count(); - lock_count++; + lock_count+= t->file->lock_count(); + table_count++; } } @@ -722,15 +720,16 @@ MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags) update the table values. So the second part of the array is copied from the first part immediately before calling thr_multi_lock(). */ - if (!(sql_lock= (MYSQL_LOCK*) - my_malloc(sizeof(*sql_lock) + - sizeof(THR_LOCK_DATA*) * tables * 2 + - sizeof(table_ptr) * lock_count, - MYF(0)))) + size_t amount= sizeof(*sql_lock) + + sizeof(THR_LOCK_DATA*) * lock_count * 2 + + sizeof(table_ptr) * table_count; + if (!(sql_lock= (MYSQL_LOCK*) (flags & GET_LOCK_ON_THD ? + thd->alloc(amount) : + my_malloc(amount, MYF(0))))) DBUG_RETURN(0); locks= locks_buf= sql_lock->locks= (THR_LOCK_DATA**) (sql_lock + 1); - to= table_buf= sql_lock->table= (TABLE**) (locks + tables * 2); - sql_lock->table_count=lock_count; + to= table_buf= sql_lock->table= (TABLE**) (locks + lock_count * 2); + sql_lock->table_count= table_count; for (i=0 ; i < count ; i++) { @@ -745,9 +744,9 @@ MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags) DBUG_ASSERT(lock_type != TL_WRITE_DEFAULT && lock_type != TL_READ_DEFAULT); locks_start= locks; locks= table->file->store_lock(thd, locks, - (flags & GET_LOCK_UNLOCK) ? TL_IGNORE : - lock_type); - if (flags & GET_LOCK_STORE_LOCKS) + (flags & GET_LOCK_ACTION_MASK) == GET_LOCK_UNLOCK ? TL_IGNORE : + lock_type); + if ((flags & GET_LOCK_ACTION_MASK) == GET_LOCK_STORE_LOCKS) { table->lock_position= (uint) (to - table_buf); table->lock_data_start= (uint) (locks_start - locks_buf); @@ -759,13 +758,14 @@ MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags) for ( ; locks_start != locks ; locks_start++) { (*locks_start)->debug_print_param= (void *) table; + (*locks_start)->m_psi= table->file->m_psi; (*locks_start)->lock->name= table->alias.c_ptr(); (*locks_start)->org_type= (*locks_start)->type; } } } /* - We do not use 'tables', because there are cases where store_lock() + We do not use 'lock_count', because there are cases where store_lock() returns less locks than lock_count() claimed. This can happen when a FLUSH TABLES tries to abort locks from a MERGE table of another thread. When that thread has just opened the table, but not yet @@ -779,6 +779,7 @@ MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags) And in the FLUSH case, the memory is released quickly anyway. */ sql_lock->lock_count= locks - locks_buf; + DBUG_ASSERT(sql_lock->lock_count <= lock_count); DBUG_PRINT("info", ("sql_lock->table_count %d sql_lock->lock_count %d", sql_lock->table_count, sql_lock->lock_count)); DBUG_RETURN(sql_lock); @@ -791,7 +792,6 @@ MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags) @param thd Thread handle. @param db The database name. - This function cannot be called while holding LOCK_open mutex. To avoid deadlocks, we do not try to obtain exclusive metadata locks in LOCK TABLES mode, since in this mode there may be other metadata locks already taken by the current connection, @@ -811,7 +811,7 @@ bool lock_schema_name(THD *thd, const char *db) if (thd->locked_tables_mode) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + ER_THD(thd, ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); return TRUE; } @@ -843,9 +843,7 @@ bool lock_schema_name(THD *thd, const char *db) @param name Object name in the schema. This function assumes that no metadata locks were acquired - before calling it. Additionally, it cannot be called while - holding LOCK_open mutex. Both these invariants are enforced by - asserts in MDL_context::acquire_locks(). + before calling it. It is enforced by asserts in MDL_context::acquire_locks(). To avoid deadlocks, we do not try to obtain exclusive metadata locks in LOCK TABLES mode, since in this mode there may be other metadata locks already taken by the current connection, @@ -864,10 +862,12 @@ bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type, MDL_request schema_request; MDL_request mdl_request; + DBUG_ASSERT(ok_for_lower_case_names(db)); + if (thd->locked_tables_mode) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + ER_THD(thd, ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); return TRUE; } @@ -895,38 +895,6 @@ bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type, } -static void print_lock_error(int error, const char *table) -{ - int textno; - DBUG_ENTER("print_lock_error"); - - switch (error) { - case HA_ERR_LOCK_WAIT_TIMEOUT: - textno=ER_LOCK_WAIT_TIMEOUT; - break; - case HA_ERR_READ_ONLY_TRANSACTION: - textno=ER_READ_ONLY_TRANSACTION; - break; - case HA_ERR_LOCK_DEADLOCK: - textno=ER_LOCK_DEADLOCK; - break; - case HA_ERR_WRONG_COMMAND: - textno=ER_ILLEGAL_HA; - break; - default: - textno=ER_CANT_LOCK; - break; - } - - if ( textno == ER_ILLEGAL_HA ) - my_error(textno, MYF(ME_BELL+ME_OLDWIN+ME_WAITTANG), table); - else - my_error(textno, MYF(ME_BELL+ME_OLDWIN+ME_WAITTANG), error); - - DBUG_VOID_RETURN; -} - - /**************************************************************************** Handling of global read locks @@ -1054,6 +1022,24 @@ void Global_read_lock::unlock_global_read_lock(THD *thd) { thd->mdl_context.release_lock(m_mdl_blocks_commits_lock); m_mdl_blocks_commits_lock= NULL; +#ifdef WITH_WSREP + if (WSREP(thd) || wsrep_node_is_donor()) + { + wsrep_locked_seqno= WSREP_SEQNO_UNDEFINED; + wsrep->resume(wsrep); + /* resync here only if we did implicit desync earlier */ + if (!wsrep_desync && wsrep_node_is_synced()) + { + int ret = wsrep->resync(wsrep); + if (ret != WSREP_OK) + { + WSREP_WARN("resync failed %d for FTWRL: db: %s, query: %s", ret, + (thd->db ? thd->db : "(null)"), thd->query()); + DBUG_VOID_RETURN; + } + } + } +#endif /* WITH_WSREP */ } thd->mdl_context.release_lock(m_mdl_global_shared_lock); m_mdl_global_shared_lock= NULL; @@ -1086,9 +1072,19 @@ bool Global_read_lock::make_global_read_lock_block_commit(THD *thd) If we didn't succeed lock_global_read_lock(), or if we already suceeded make_global_read_lock_block_commit(), do nothing. */ + if (m_state != GRL_ACQUIRED) DBUG_RETURN(0); +#ifdef WITH_WSREP + if (WSREP(thd) && m_mdl_blocks_commits_lock) + { + WSREP_DEBUG("GRL was in block commit mode when entering " + "make_global_read_lock_block_commit"); + DBUG_RETURN(FALSE); + } +#endif /* WITH_WSREP */ + mdl_request.init(MDL_key::COMMIT, "", "", MDL_SHARED, MDL_EXPLICIT); if (thd->mdl_context.acquire_lock(&mdl_request, @@ -1098,6 +1094,61 @@ bool Global_read_lock::make_global_read_lock_block_commit(THD *thd) m_mdl_blocks_commits_lock= mdl_request.ticket; m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT; +#ifdef WITH_WSREP + /* Native threads should bail out before wsrep oprations to follow. + Donor servicing thread is an exception, it should pause provider but not desync, + as it is already desynced in donor state + */ + if (!WSREP(thd) && !wsrep_node_is_donor()) + { + DBUG_RETURN(FALSE); + } + + /* if already desynced or donor, avoid double desyncing + if not in PC and synced, desyncing is not possible either + */ + if (wsrep_desync || !wsrep_node_is_synced()) + { + WSREP_DEBUG("desync set upfont, skipping implicit desync for FTWRL: %d", + wsrep_desync); + } + else + { + int rcode; + WSREP_DEBUG("running implicit desync for node"); + rcode = wsrep->desync(wsrep); + if (rcode != WSREP_OK) + { + WSREP_WARN("FTWRL desync failed %d for schema: %s, query: %s", + rcode, (thd->db ? thd->db : "(null)"), thd->query()); + my_message(ER_LOCK_DEADLOCK, "wsrep desync failed for FTWRL", MYF(0)); + DBUG_RETURN(TRUE); + } + } + + long long ret = wsrep->pause(wsrep); + if (ret >= 0) + { + wsrep_locked_seqno= ret; + } + else if (ret != -ENOSYS) /* -ENOSYS - no provider */ + { + long long ret = wsrep->pause(wsrep); + if (ret >= 0) + { + wsrep_locked_seqno= ret; + } + else if (ret != -ENOSYS) /* -ENOSYS - no provider */ + { + WSREP_ERROR("Failed to pause provider: %lld (%s)", -ret, strerror(-ret)); + + DBUG_ASSERT(m_mdl_blocks_commits_lock == NULL); + wsrep_locked_seqno= WSREP_SEQNO_UNDEFINED; + my_error(ER_LOCK_DEADLOCK, MYF(0)); + DBUG_RETURN(TRUE); + } + } +#endif /* WITH_WSREP */ DBUG_RETURN(FALSE); } diff --git a/sql/lock.h b/sql/lock.h index a4833cdc38e..b2de4f60b38 100644 --- a/sql/lock.h +++ b/sql/lock.h @@ -28,11 +28,11 @@ typedef struct st_mysql_lock MYSQL_LOCK; MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags); bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, uint flags); -void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock, bool free_lock= 1); +void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock, bool free_lock); +void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count); void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table); -void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock); bool mysql_lock_abort_for_thread(THD *thd, TABLE *table); MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b); /* Lock based on name */ @@ -42,8 +42,10 @@ bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type, const char *db, const char *name); /* flags for get_lock_data */ -#define GET_LOCK_UNLOCK 1 -#define GET_LOCK_STORE_LOCKS 2 +#define GET_LOCK_UNLOCK 0 +#define GET_LOCK_STORE_LOCKS 1 +#define GET_LOCK_ACTION_MASK 1 +#define GET_LOCK_ON_THD (1 << 1) MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags); void reset_lock_data(MYSQL_LOCK *sql_lock, bool unlock); diff --git a/sql/log.cc b/sql/log.cc index 7db4985ad48..bab250223f3 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -25,7 +25,7 @@ Abort logging when we get an error in reading or writing log files */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "log.h" #include "sql_base.h" // open_log_table @@ -40,6 +40,7 @@ #include "rpl_rli.h" #include "sql_audit.h" #include "log_slow.h" +#include "mysqld.h" #include <my_dir.h> #include <stdarg.h> @@ -53,6 +54,8 @@ #include "rpl_handler.h" #include "debug_sync.h" #include "sql_show.h" +#include "my_pthread.h" +#include "wsrep_mysqld.h" /* max size of the log message */ #define MAX_LOG_BUFFER_SIZE 1024 @@ -61,8 +64,12 @@ #define FLAGSTR(V,F) ((V)&(F)?#F" ":"") +handlerton *binlog_hton; LOGGER logger; +const char *log_bin_index= 0; +const char *log_bin_basename= 0; + MYSQL_BIN_LOG mysql_bin_log(&sync_binlog_period); static bool test_if_number(const char *str, @@ -71,6 +78,8 @@ static int binlog_init(void *p); static int binlog_close_connection(handlerton *hton, THD *thd); static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv); static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv); +static bool binlog_savepoint_rollback_can_release_mdl(handlerton *hton, + THD *thd); static int binlog_commit(handlerton *hton, THD *thd, bool all); static int binlog_rollback(handlerton *hton, THD *thd, bool all); static int binlog_prepare(handlerton *hton, THD *thd, bool all); @@ -86,10 +95,15 @@ ulong opt_binlog_dbug_fsync_sleep= 0; #endif mysql_mutex_t LOCK_prepare_ordered; +mysql_cond_t COND_prepare_ordered; +mysql_mutex_t LOCK_after_binlog_sync; mysql_mutex_t LOCK_commit_ordered; static ulonglong binlog_status_var_num_commits; static ulonglong binlog_status_var_num_group_commits; +static ulonglong binlog_status_group_commit_trigger_count; +static ulonglong binlog_status_group_commit_trigger_lock_wait; +static ulonglong binlog_status_group_commit_trigger_timeout; static char binlog_snapshot_file[FN_REFLEN]; static ulonglong binlog_snapshot_position; @@ -99,6 +113,12 @@ static SHOW_VAR binlog_status_vars_detail[]= (char *)&binlog_status_var_num_commits, SHOW_LONGLONG}, {"group_commits", (char *)&binlog_status_var_num_group_commits, SHOW_LONGLONG}, + {"group_commit_trigger_count", + (char *)&binlog_status_group_commit_trigger_count, SHOW_LONGLONG}, + {"group_commit_trigger_lock_wait", + (char *)&binlog_status_group_commit_trigger_lock_wait, SHOW_LONGLONG}, + {"group_commit_trigger_timeout", + (char *)&binlog_status_group_commit_trigger_timeout, SHOW_LONGLONG}, {"snapshot_file", (char *)&binlog_snapshot_file, SHOW_CHAR}, {"snapshot_position", @@ -106,6 +126,18 @@ static SHOW_VAR binlog_status_vars_detail[]= {NullS, NullS, SHOW_LONG} }; +/* + Variables for the binlog background thread. + Protected by the MYSQL_BIN_LOG::LOCK_binlog_background_thread mutex. + */ +static bool binlog_background_thread_started= false; +static bool binlog_background_thread_stop= false; +static MYSQL_BIN_LOG::xid_count_per_binlog * + binlog_background_thread_queue= NULL; + +static bool start_binlog_background_thread(); + +static rpl_binlog_state rpl_global_gtid_binlog_state; /** purge logs, master and slave sides both, related error code @@ -157,9 +189,9 @@ public: virtual bool handle_condition(THD *thd, uint sql_errno, const char* sql_state, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl); + Sql_condition ** cond_hdl); const char *message() const { return m_message; } }; @@ -167,9 +199,9 @@ bool Silence_log_table_errors::handle_condition(THD *, uint, const char*, - MYSQL_ERROR::enum_warning_level, + Sql_condition::enum_warning_level, const char* msg, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { *cond_hdl= NULL; strmake_buf(m_message, msg); @@ -234,9 +266,9 @@ public: return m_pending; } - void set_pending(Rows_log_event *const pending) + void set_pending(Rows_log_event *const pending_arg) { - m_pending= pending; + m_pending= pending_arg; } void set_incident(void) @@ -270,11 +302,12 @@ public: incident= FALSE; before_stmt_pos= MY_OFF_T_UNDEF; /* - The truncate function calls reinit_io_cache that calls my_b_flush_io_cache - which may increase disk_writes. This breaks the disk_writes use by the - binary log which aims to compute the ratio between in-memory cache usage - and disk cache usage. To avoid this undesirable behavior, we reset the - variable after truncating the cache. + The truncate function calls reinit_io_cache that calls + my_b_flush_io_cache which may increase disk_writes. This breaks + the disk_writes use by the binary log which aims to compute the + ratio between in-memory cache usage and disk cache usage. To + avoid this undesirable behavior, we reset the variable after + truncating the cache. */ cache_log.disk_writes= 0; DBUG_ASSERT(empty()); @@ -482,6 +515,14 @@ public: */ bool using_xa; my_xid xa_xid; + bool need_unlog; + /* + Id of binlog that transaction was written to; only needed if need_unlog is + true. + */ + ulong binlog_id; + /* Set if we get an error during commit that must be returned from unlog(). */ + bool delayed_error; private: @@ -489,13 +530,11 @@ private: binlog_cache_mngr(const binlog_cache_mngr& info); }; -handlerton *binlog_hton; - bool LOGGER::is_log_table_enabled(uint log_table_type) { switch (log_table_type) { case QUERY_LOG_SLOW: - return (table_log_handler != NULL) && opt_slow_log; + return (table_log_handler != NULL) && global_system_variables.sql_log_slow; case QUERY_LOG_GENERAL: return (table_log_handler != NULL) && opt_log ; default: @@ -504,36 +543,51 @@ bool LOGGER::is_log_table_enabled(uint log_table_type) } } +/** + Check if a given table is opened log table + + @param table Table to check + @param check_if_opened Only fail if it's a log table in use + @param error_msg String to put in error message if not ok. + No error message if 0 + @return 0 ok + @return # Type of log file + */ -/* Check if a given table is opened log table */ -int check_if_log_table(size_t db_len, const char *db, size_t table_name_len, - const char *table_name, bool check_if_opened) +int check_if_log_table(const TABLE_LIST *table, + bool check_if_opened, + const char *error_msg) { - if (db_len == 5 && - !(lower_case_table_names ? - my_strcasecmp(system_charset_info, db, "mysql") : - strcmp(db, "mysql"))) + int result= 0; + if (table->db_length == 5 && + !my_strcasecmp(table_alias_charset, table->db, "mysql")) { - if (table_name_len == 11 && !(lower_case_table_names ? - my_strcasecmp(system_charset_info, - table_name, "general_log") : - strcmp(table_name, "general_log"))) + const char *table_name= table->table_name; + + if (table->table_name_length == 11 && + !my_strcasecmp(table_alias_charset, table_name, "general_log")) { - if (!check_if_opened || logger.is_log_table_enabled(QUERY_LOG_GENERAL)) - return QUERY_LOG_GENERAL; - return 0; + result= QUERY_LOG_GENERAL; + goto end; } - if (table_name_len == 8 && !(lower_case_table_names ? - my_strcasecmp(system_charset_info, table_name, "slow_log") : - strcmp(table_name, "slow_log"))) + if (table->table_name_length == 8 && + !my_strcasecmp(table_alias_charset, table_name, "slow_log")) { - if (!check_if_opened || logger.is_log_table_enabled(QUERY_LOG_SLOW)) - return QUERY_LOG_SLOW; - return 0; + result= QUERY_LOG_SLOW; + goto end; } } return 0; + +end: + if (!check_if_opened || logger.is_log_table_enabled(result)) + { + if (error_msg) + my_error(ER_BAD_LOG_STATEMENT, MYF(0), error_msg); + return result; + } + return 0; } @@ -581,12 +635,12 @@ void Log_to_csv_event_handler::cleanup() indicated in the return value. @retval FALSE OK - @retval TRUE error occured + @retval TRUE error occurred */ bool Log_to_csv_event_handler:: log_general(THD *thd, my_hrtime_t event_time, const char *user_host, - uint user_host_len, int thread_id, + uint user_host_len, int thread_id_arg, const char *command_type, uint command_type_len, const char *sql_text, uint sql_text_len, CHARSET_INFO *client_cs) @@ -667,8 +721,9 @@ bool Log_to_csv_event_handler:: /* do a write */ if (table->field[1]->store(user_host, user_host_len, client_cs) || - table->field[2]->store((longlong) thread_id, TRUE) || - table->field[3]->store((longlong) server_id, TRUE) || + table->field[2]->store((longlong) thread_id_arg, TRUE) || + table->field[3]->store((longlong) global_system_variables.server_id, + TRUE) || table->field[4]->store(command_type, command_type_len, client_cs)) goto err; @@ -745,7 +800,7 @@ err: RETURN FALSE - OK - TRUE - error occured + TRUE - error occurred */ bool Log_to_csv_event_handler:: @@ -763,8 +818,8 @@ bool Log_to_csv_event_handler:: Open_tables_backup open_tables_backup; CHARSET_INFO *client_cs= thd->variables.character_set_client; bool save_time_zone_used; - long query_time= (long) min(query_utime/1000000, TIME_MAX_VALUE_SECONDS); - long lock_time= (long) min(lock_utime/1000000, TIME_MAX_VALUE_SECONDS); + long query_time= (long) MY_MIN(query_utime/1000000, TIME_MAX_VALUE_SECONDS); + long lock_time= (long) MY_MIN(lock_utime/1000000, TIME_MAX_VALUE_SECONDS); long query_time_micro= (long) (query_utime % 1000000); long lock_time_micro= (long) (lock_utime % 1000000); @@ -799,7 +854,7 @@ bool Log_to_csv_event_handler:: restore_record(table, s->default_values); // Get empty record /* check that all columns exist */ - if (table->s->fields < 11) + if (table->s->fields < 13) goto err; /* store the time and user values */ @@ -826,10 +881,10 @@ bool Log_to_csv_event_handler:: if (table->field[3]->store_time(&t)) goto err; /* rows_sent */ - if (table->field[4]->store((longlong) thd->sent_row_count, TRUE)) + if (table->field[4]->store((longlong) thd->get_sent_row_count(), TRUE)) goto err; /* rows_examined */ - if (table->field[5]->store((longlong) thd->examined_row_count, TRUE)) + if (table->field[5]->store((longlong) thd->get_examined_row_count(), TRUE)) goto err; /* fill database field */ @@ -865,7 +920,7 @@ bool Log_to_csv_event_handler:: table->field[8]->set_notnull(); } - if (table->field[9]->store((longlong) server_id, TRUE)) + if (table->field[9]->store((longlong)global_system_variables.server_id, TRUE)) goto err; table->field[9]->set_notnull(); @@ -877,6 +932,15 @@ bool Log_to_csv_event_handler:: if (table->field[10]->store(sql_text, sql_text_len, client_cs) < 0) goto err; + if (table->field[11]->store((longlong) thd->thread_id, TRUE)) + goto err; + + /* Rows_affected */ + if (table->field[12]->store(thd->get_stmt_da()->is_ok() ? + (longlong) thd->get_stmt_da()->affected_rows() : + 0, TRUE)) + goto err; + /* log table entries are not replicated */ if (table->file->ha_write_row(table->record[0])) goto err; @@ -986,7 +1050,7 @@ bool Log_to_file_event_handler:: bool Log_to_file_event_handler:: log_general(THD *thd, my_hrtime_t event_time, const char *user_host, - uint user_host_len, int thread_id, + uint user_host_len, int thread_id_arg, const char *command_type, uint command_type_len, const char *sql_text, uint sql_text_len, CHARSET_INFO *client_cs) @@ -995,7 +1059,7 @@ bool Log_to_file_event_handler:: thd->push_internal_handler(&error_handler); bool retval= mysql_log.write(hrtime_to_time(event_time), user_host, user_host_len, - thread_id, command_type, command_type_len, + thread_id_arg, command_type, command_type_len, sql_text, sql_text_len); thd->pop_internal_handler(); return retval; @@ -1006,7 +1070,7 @@ bool Log_to_file_event_handler::init() { if (!is_initialized) { - if (opt_slow_log) + if (global_system_variables.sql_log_slow) mysql_slow_log.open_slow_log(opt_slow_logname); if (opt_log) @@ -1030,7 +1094,7 @@ void Log_to_file_event_handler::flush() /* reopen log files */ if (opt_log) mysql_log.reopen_file(); - if (opt_slow_log) + if (global_system_variables.sql_log_slow) mysql_slow_log.reopen_file(); } @@ -1047,7 +1111,7 @@ void Log_to_file_event_handler::flush() RETURN FALSE - OK - TRUE - error occured + TRUE - error occurred */ bool LOGGER::error_log_print(enum loglevel level, const char *format, @@ -1158,7 +1222,7 @@ bool LOGGER::flush_slow_log() logger.lock_exclusive(); /* Reopen slow log file */ - if (opt_slow_log) + if (global_system_variables.sql_log_slow) file_log_handler->get_mysql_slow_log()->reopen_file(); /* End of log flush */ @@ -1205,7 +1269,7 @@ bool LOGGER::flush_general_log() RETURN FALSE OK - TRUE error occured + TRUE error occurred */ bool LOGGER::slow_log_print(THD *thd, const char *query, uint query_length, @@ -1228,11 +1292,11 @@ bool LOGGER::slow_log_print(THD *thd, const char *query, uint query_length, if (*slow_log_handler_list) { /* do not log slow queries from replication threads */ - if (thd->slave_thread && !opt_log_slow_slave_statements) + if (!thd->variables.sql_log_slow) return 0; lock_shared(); - if (!opt_slow_log) + if (!global_system_variables.sql_log_slow) { unlock(); return 0; @@ -1240,7 +1304,7 @@ bool LOGGER::slow_log_print(THD *thd, const char *query, uint query_length, /* fill in user_host value: the format is "%s[%s] @ %s [%s]" */ user_host_len= (strxnmov(user_host_buff, MAX_USER_HOST_SIZE, - sctx->priv_user ? sctx->priv_user : "", "[", + sctx->priv_user, "[", sctx->user ? sctx->user : (thd->slave_thread ? "SQL_SLAVE" : ""), "] @ ", sctx->host ? sctx->host : "", " [", sctx->ip ? sctx->ip : "", "]", NullS) - @@ -1256,8 +1320,8 @@ bool LOGGER::slow_log_print(THD *thd, const char *query, uint query_length, if (!query) { is_command= TRUE; - query= command_name[thd->command].str; - query_length= command_name[thd->command].length; + query= command_name[thd->get_command()].str; + query_length= command_name[thd->get_command()].length; } for (current_handler= slow_log_handler_list; *current_handler ;) @@ -1406,7 +1470,7 @@ bool LOGGER::activate_log_handler(THD* thd, uint log_type) lock_exclusive(); switch (log_type) { case QUERY_LOG_SLOW: - if (!opt_slow_log) + if (!global_system_variables.sql_log_slow) { file_log= file_log_handler->get_mysql_slow_log(); @@ -1420,7 +1484,7 @@ bool LOGGER::activate_log_handler(THD* thd, uint log_type) else { init_slow_log(log_output_options); - opt_slow_log= TRUE; + global_system_variables.sql_log_slow= TRUE; } } break; @@ -1454,12 +1518,11 @@ bool LOGGER::activate_log_handler(THD* thd, uint log_type) void LOGGER::deactivate_log_handler(THD *thd, uint log_type) { my_bool *tmp_opt= 0; - MYSQL_LOG *file_log; - LINT_INIT(file_log); + MYSQL_LOG *UNINIT_VAR(file_log); switch (log_type) { case QUERY_LOG_SLOW: - tmp_opt= &opt_slow_log; + tmp_opt= &global_system_variables.sql_log_slow; file_log= file_log_handler->get_mysql_slow_log(); break; case QUERY_LOG_GENERAL: @@ -1535,7 +1598,7 @@ binlog_trans_log_savepos(THD *thd, my_off_t *pos) DBUG_ENTER("binlog_trans_log_savepos"); DBUG_ASSERT(pos != NULL); binlog_cache_mngr *const cache_mngr= thd->binlog_setup_trx_data(); - DBUG_ASSERT(mysql_bin_log.is_open()); + DBUG_ASSERT((WSREP(thd) && wsrep_emulate_bin_log) || mysql_bin_log.is_open()); *pos= cache_mngr->trx_cache.get_byte_position(); DBUG_PRINT("return", ("*pos: %lu", (ulong) *pos)); DBUG_VOID_RETURN; @@ -1583,12 +1646,15 @@ binlog_trans_log_truncate(THD *thd, my_off_t pos) int binlog_init(void *p) { binlog_hton= (handlerton *)p; - binlog_hton->state=opt_bin_log ? SHOW_OPTION_YES : SHOW_OPTION_NO; + binlog_hton->state= (WSREP_ON || opt_bin_log) ? SHOW_OPTION_YES + : SHOW_OPTION_NO; binlog_hton->db_type=DB_TYPE_BINLOG; binlog_hton->savepoint_offset= sizeof(my_off_t); binlog_hton->close_connection= binlog_close_connection; binlog_hton->savepoint_set= binlog_savepoint_set; binlog_hton->savepoint_rollback= binlog_savepoint_rollback; + binlog_hton->savepoint_rollback_can_release_mdl= + binlog_savepoint_rollback_can_release_mdl; binlog_hton->commit= binlog_commit; binlog_hton->rollback= binlog_rollback; binlog_hton->prepare= binlog_prepare; @@ -1597,15 +1663,36 @@ int binlog_init(void *p) return 0; } +#ifdef WITH_WSREP +#include "wsrep_binlog.h" +#endif /* WITH_WSREP */ static int binlog_close_connection(handlerton *hton, THD *thd) { + DBUG_ENTER("binlog_close_connection"); binlog_cache_mngr *const cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton); +#ifdef WITH_WSREP + if (cache_mngr && !cache_mngr->trx_cache.empty()) { + IO_CACHE* cache= get_trans_log(thd); + uchar *buf; + size_t len=0; + wsrep_write_cache_buf(cache, &buf, &len); + WSREP_WARN("binlog trx cache not empty (%zu bytes) @ connection close %lu", + len, thd->thread_id); + if (len > 0) wsrep_dump_rbr_buf(thd, buf, len); + + cache = cache_mngr->get_binlog_cache_log(false); + wsrep_write_cache_buf(cache, &buf, &len); + WSREP_WARN("binlog stmt cache not empty (%zu bytes) @ connection close %lu", + len, thd->thread_id); + if (len > 0) wsrep_dump_rbr_buf(thd, buf, len); + } +#endif /* WITH_WSREP */ DBUG_ASSERT(cache_mngr->trx_cache.empty() && cache_mngr->stmt_cache.empty()); thd_set_ha_data(thd, binlog_hton, NULL); cache_mngr->~binlog_cache_mngr(); my_free(cache_mngr); - return 0; + DBUG_RETURN(0); } /* @@ -1633,6 +1720,7 @@ static int binlog_close_connection(handlerton *hton, THD *thd) contain updates to non-transactional tables. Or it can be a flush of a statement cache. */ + static int binlog_flush_cache(THD *thd, binlog_cache_mngr *cache_mngr, Log_event *end_ev, bool all, bool using_stmt, @@ -1640,6 +1728,7 @@ binlog_flush_cache(THD *thd, binlog_cache_mngr *cache_mngr, { int error= 0; DBUG_ENTER("binlog_flush_cache"); + DBUG_PRINT("enter", ("end_ev: %p", end_ev)); if ((using_stmt && !cache_mngr->stmt_cache.empty()) || (using_trx && !cache_mngr->trx_cache.empty())) @@ -1663,6 +1752,20 @@ binlog_flush_cache(THD *thd, binlog_cache_mngr *cache_mngr, end_ev, all, using_stmt, using_trx); } + else + { + /* + This can happen in row-format binlog with something like + BEGIN; INSERT INTO nontrans_table; INSERT IGNORE INTO trans_table; + The nontrans_table is written directly into the binlog before commit, + and if the trans_table is ignored there will be no rows to write when + we get here. + + So there is no work to do. Therefore, we will not increment any XID + count, so we must not decrement any XID count in unlog(). + */ + cache_mngr->need_unlog= 0; + } cache_mngr->reset(using_stmt, using_trx); DBUG_ASSERT((!using_stmt || cache_mngr->stmt_cache.empty()) && @@ -1684,9 +1787,20 @@ static inline int binlog_commit_flush_stmt_cache(THD *thd, bool all, binlog_cache_mngr *cache_mngr) { + DBUG_ENTER("binlog_commit_flush_stmt_cache"); +#ifdef WITH_WSREP + if (thd->wsrep_mysql_replicated > 0) + { + DBUG_ASSERT(WSREP_ON); + WSREP_DEBUG("avoiding binlog_commit_flush_trx_cache: %d", + thd->wsrep_mysql_replicated); + return 0; + } +#endif + Query_log_event end_evt(thd, STRING_WITH_LEN("COMMIT"), FALSE, TRUE, TRUE, 0); - return (binlog_flush_cache(thd, cache_mngr, &end_evt, all, TRUE, FALSE)); + DBUG_RETURN(binlog_flush_cache(thd, cache_mngr, &end_evt, all, TRUE, FALSE)); } /** @@ -1701,9 +1815,10 @@ binlog_commit_flush_stmt_cache(THD *thd, bool all, static inline int binlog_commit_flush_trx_cache(THD *thd, bool all, binlog_cache_mngr *cache_mngr) { + DBUG_ENTER("binlog_commit_flush_trx_cache"); Query_log_event end_evt(thd, STRING_WITH_LEN("COMMIT"), TRUE, TRUE, TRUE, 0); - return (binlog_flush_cache(thd, cache_mngr, &end_evt, all, FALSE, TRUE)); + DBUG_RETURN(binlog_flush_cache(thd, cache_mngr, &end_evt, all, FALSE, TRUE)); } /** @@ -1821,6 +1936,32 @@ static int binlog_prepare(handlerton *hton, THD *thd, bool all) return 0; } +/* + We flush the cache wrapped in a beging/rollback if: + . aborting a single or multi-statement transaction and; + . the OPTION_KEEP_LOG is active or; + . the format is STMT and a non-trans table was updated or; + . the format is MIXED and a temporary non-trans table was + updated or; + . the format is MIXED, non-trans table was updated and + aborting a single statement transaction; +*/ +static bool trans_cannot_safely_rollback(THD *thd, bool all) +{ + binlog_cache_mngr *const cache_mngr= + (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton); + + return ((thd->variables.option_bits & OPTION_KEEP_LOG) || + (trans_has_updated_non_trans_table(thd) && + thd->wsrep_binlog_format() == BINLOG_FORMAT_STMT) || + (cache_mngr->trx_cache.changes_to_non_trans_temp_table() && + thd->wsrep_binlog_format() == BINLOG_FORMAT_MIXED) || + (trans_has_updated_non_trans_table(thd) && + ending_single_stmt_trans(thd,all) && + thd->wsrep_binlog_format() == BINLOG_FORMAT_MIXED)); +} + + /** This function is called once after each statement. @@ -1840,6 +1981,12 @@ static int binlog_commit(handlerton *hton, THD *thd, bool all) binlog_cache_mngr *const cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton); + if (!cache_mngr) + { + DBUG_ASSERT(WSREP(thd)); + DBUG_RETURN(0); + } + DBUG_PRINT("debug", ("all: %d, in_transaction: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s", all, @@ -1896,6 +2043,12 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) binlog_cache_mngr *const cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton); + if (!cache_mngr) + { + DBUG_ASSERT(WSREP(thd)); + DBUG_RETURN(0); + } + DBUG_PRINT("debug", ("all: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s", YESNO(all), YESNO(thd->transaction.all.modified_non_trans_table), @@ -1923,8 +2076,7 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) cache_mngr->reset(false, true); DBUG_RETURN(error); } - - if (mysql_bin_log.check_write_error(thd)) + if (!wsrep_emulate_bin_log && mysql_bin_log.check_write_error(thd)) { /* "all == true" means that a "rollback statement" triggered the error and @@ -1941,25 +2093,7 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) } else if (!error) { - /* - We flush the cache wrapped in a beging/rollback if: - . aborting a single or multi-statement transaction and; - . the OPTION_KEEP_LOG is active or; - . the format is STMT and a non-trans table was updated or; - . the format is MIXED and a temporary non-trans table was - updated or; - . the format is MIXED, non-trans table was updated and - aborting a single statement transaction; - */ - if (ending_trans(thd, all) && - ((thd->variables.option_bits & OPTION_KEEP_LOG) || - (trans_has_updated_non_trans_table(thd) && - thd->variables.binlog_format == BINLOG_FORMAT_STMT) || - (cache_mngr->trx_cache.changes_to_non_trans_temp_table() && - thd->variables.binlog_format == BINLOG_FORMAT_MIXED) || - (trans_has_updated_non_trans_table(thd) && - ending_single_stmt_trans(thd,all) && - thd->variables.binlog_format == BINLOG_FORMAT_MIXED))) + if (ending_trans(thd, all) && trans_cannot_safely_rollback(thd, all)) error= binlog_rollback_flush_trx_cache(thd, all, cache_mngr); /* Truncate the cache if: @@ -1973,9 +2107,9 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) else if (ending_trans(thd, all) || (!(thd->variables.option_bits & OPTION_KEEP_LOG) && (!stmt_has_updated_non_trans_table(thd) || - thd->variables.binlog_format != BINLOG_FORMAT_STMT) && + thd->wsrep_binlog_format() != BINLOG_FORMAT_STMT) && (!cache_mngr->trx_cache.changes_to_non_trans_temp_table() || - thd->variables.binlog_format != BINLOG_FORMAT_MIXED))) + thd->wsrep_binlog_format() != BINLOG_FORMAT_MIXED))) error= binlog_truncate_trx_cache(thd, cache_mngr, all); } @@ -1988,6 +2122,21 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) DBUG_RETURN(error); } + +void binlog_reset_cache(THD *thd) +{ + binlog_cache_mngr *const cache_mngr= opt_bin_log ? + (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton) : 0; + DBUG_ENTER("binlog_reset_cache"); + if (cache_mngr) + { + thd->binlog_remove_pending_rows_event(TRUE, TRUE); + cache_mngr->reset(true, true); + } + DBUG_VOID_RETURN; +} + + void MYSQL_BIN_LOG::set_write_error(THD *thd, bool is_transactional) { DBUG_ENTER("MYSQL_BIN_LOG::set_write_error"); @@ -2001,11 +2150,11 @@ void MYSQL_BIN_LOG::set_write_error(THD *thd, bool is_transactional) { if (is_transactional) { - my_message(ER_TRANS_CACHE_FULL, ER(ER_TRANS_CACHE_FULL), MYF(MY_WME)); + my_message(ER_TRANS_CACHE_FULL, ER_THD(thd, ER_TRANS_CACHE_FULL), MYF(MY_WME)); } else { - my_message(ER_STMT_CACHE_FULL, ER(ER_STMT_CACHE_FULL), MYF(MY_WME)); + my_message(ER_STMT_CACHE_FULL, ER_THD(thd, ER_STMT_CACHE_FULL), MYF(MY_WME)); } } else @@ -2025,7 +2174,7 @@ bool MYSQL_BIN_LOG::check_write_error(THD *thd) if (!thd->is_error()) DBUG_RETURN(checked); - switch (thd->stmt_da->sql_errno()) + switch (thd->get_stmt_da()->sql_errno()) { case ER_TRANS_CACHE_FULL: case ER_STMT_CACHE_FULL: @@ -2065,28 +2214,48 @@ bool MYSQL_BIN_LOG::check_write_error(THD *thd) static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv) { + int error= 1; DBUG_ENTER("binlog_savepoint_set"); - binlog_trans_log_savepos(thd, (my_off_t*) sv); - /* Write it to the binary log */ + if (wsrep_emulate_bin_log) + DBUG_RETURN(0); char buf[1024]; + String log_query(buf, sizeof(buf), &my_charset_bin); if (log_query.copy(STRING_WITH_LEN("SAVEPOINT "), &my_charset_bin) || append_identifier(thd, &log_query, thd->lex->ident.str, thd->lex->ident.length)) DBUG_RETURN(1); int errcode= query_error_code(thd, thd->killed == NOT_KILLED); - Query_log_event qinfo(thd, log_query.ptr(), log_query.length(), + Query_log_event qinfo(thd, log_query.c_ptr_safe(), log_query.length(), TRUE, FALSE, TRUE, errcode); - int ret= mysql_bin_log.write(&qinfo); - DBUG_RETURN(ret); + /* + We cannot record the position before writing the statement + because a rollback to a savepoint (.e.g. consider it "S") would + prevent the savepoint statement (i.e. "SAVEPOINT S") from being + written to the binary log despite the fact that the server could + still issue other rollback statements to the same savepoint (i.e. + "S"). + Given that the savepoint is valid until the server releases it, + ie, until the transaction commits or it is released explicitly, + we need to log it anyway so that we don't have "ROLLBACK TO S" + or "RELEASE S" without the preceding "SAVEPOINT S" in the binary + log. + */ + if (!(error= mysql_bin_log.write(&qinfo))) + binlog_trans_log_savepos(thd, (my_off_t*) sv); + + DBUG_RETURN(error); } static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv) { DBUG_ENTER("binlog_savepoint_rollback"); + if (wsrep_emulate_bin_log) + DBUG_RETURN(0); + /* Write ROLLBACK TO SAVEPOINT to the binlog cache if we have updated some non-transactional table. Otherwise, truncate the binlog cache starting @@ -2106,11 +2275,48 @@ static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv) TRUE, FALSE, TRUE, errcode); DBUG_RETURN(mysql_bin_log.write(&qinfo)); } + binlog_trans_log_truncate(thd, *(my_off_t*)sv); + + /* + When a SAVEPOINT is executed inside a stored function/trigger we force the + pending event to be flushed with a STMT_END_F flag and clear the table maps + as well to ensure that following DMLs will have a clean state to start + with. ROLLBACK inside a stored routine has to finalize possibly existing + current row-based pending event with cleaning up table maps. That ensures + that following DMLs will have a clean state to start with. + */ + if (thd->in_sub_stmt) + thd->clear_binlog_table_maps(); + DBUG_RETURN(0); } +/** + Check whether binlog state allows to safely release MDL locks after + rollback to savepoint. + + @param hton The binlog handlerton. + @param thd The client thread that executes the transaction. + + @return true - It is safe to release MDL locks. + false - If it is not. +*/ +static bool binlog_savepoint_rollback_can_release_mdl(handlerton *hton, + THD *thd) +{ + DBUG_ENTER("binlog_savepoint_rollback_can_release_mdl"); + /* + If we have not updated any non-transactional tables rollback + to savepoint will simply truncate binlog cache starting from + SAVEPOINT command. So it should be safe to release MDL acquired + after SAVEPOINT command in this case. + */ + DBUG_RETURN(!trans_cannot_safely_rollback(thd, true)); +} + + int check_binlog_magic(IO_CACHE* log, const char** errmsg) { uchar magic[4]; @@ -2219,13 +2425,13 @@ static void setup_windows_event_source() nonzero if not possible to get unique filename. */ -static int find_uniq_filename(char *name) +static int find_uniq_filename(char *name, ulong next_log_number) { uint i; char buff[FN_REFLEN], ext_buf[FN_REFLEN]; struct st_my_dir *dir_info; reg1 struct fileinfo *file_info; - ulong max_found= 0, next= 0, number= 0; + ulong max_found, next, number; size_t buf_length, length; char *start, *end; int error= 0; @@ -2245,7 +2451,8 @@ static int find_uniq_filename(char *name) DBUG_RETURN(1); } file_info= dir_info->dir_entry; - for (i= dir_info->number_off_files ; i-- ; file_info++) + max_found= next_log_number ? next_log_number-1 : 0; + for (i= dir_info->number_of_files ; i-- ; file_info++) { if (strncmp(file_info->name, start, length) == 0 && test_if_number(file_info->name+length, &number,0)) @@ -2256,7 +2463,7 @@ static int find_uniq_filename(char *name) my_dirend(dir_info); /* check if reached the maximum possible extension number */ - if (max_found == MAX_LOG_UNIQUE_FN_EXT) + if (max_found >= MAX_LOG_UNIQUE_FN_EXT) { sql_print_error("Log filename extension number exhausted: %06lu. \ Please fix this by archiving old logs and \ @@ -2317,14 +2524,18 @@ void MYSQL_LOG::init(enum_log_type log_type_arg, bool MYSQL_LOG::init_and_set_log_file_name(const char *log_name, const char *new_name, + ulong next_log_number, enum_log_type log_type_arg, enum cache_type io_cache_type_arg) { init(log_type_arg, io_cache_type_arg); - if (new_name && !strmov(log_file_name, new_name)) - return TRUE; - else if (!new_name && generate_new_name(log_file_name, log_name)) + if (new_name) + { + strmov(log_file_name, new_name); + } + else if (!new_name && generate_new_name(log_file_name, log_name, + next_log_number)) return TRUE; return FALSE; @@ -2357,14 +2568,15 @@ bool MYSQL_LOG::open( PSI_file_key log_file_key, #endif const char *log_name, enum_log_type log_type_arg, - const char *new_name, enum cache_type io_cache_type_arg) + const char *new_name, ulong next_log_number, + enum cache_type io_cache_type_arg) { char buff[FN_REFLEN]; MY_STAT f_stat; File file= -1; my_off_t seek_offset; bool is_fifo = false; - int open_flags= O_CREAT | O_BINARY; + int open_flags= O_CREAT | O_BINARY | O_CLOEXEC; DBUG_ENTER("MYSQL_LOG::open"); DBUG_PRINT("enter", ("log_type: %d", (int) log_type_arg)); @@ -2376,7 +2588,13 @@ bool MYSQL_LOG::open( goto err; } - if (init_and_set_log_file_name(name, new_name, + /* + log_type is LOG_UNKNOWN if we should not generate a new name + This is only used when called from MYSQL_BINARY_LOG::open, which + has already updated log_file_name. + */ + if (log_type_arg != LOG_UNKNOWN && + init_and_set_log_file_name(name, new_name, next_log_number, log_type_arg, io_cache_type_arg)) goto err; @@ -2531,17 +2749,21 @@ void MYSQL_LOG::cleanup() } -int MYSQL_LOG::generate_new_name(char *new_name, const char *log_name) +int MYSQL_LOG::generate_new_name(char *new_name, const char *log_name, + ulong next_log_number) { fn_format(new_name, log_name, mysql_data_home, "", 4); if (log_type == LOG_BIN) { if (!fn_ext(log_name)[0]) { - if (find_uniq_filename(new_name)) + if (DBUG_EVALUATE_IF("binlog_inject_new_name_error", TRUE, FALSE) || + find_uniq_filename(new_name, next_log_number)) { - if (current_thd) - my_printf_error(ER_NO_UNIQUE_LOGFILE, ER(ER_NO_UNIQUE_LOGFILE), + THD *thd= current_thd; + if (thd) + my_printf_error(ER_NO_UNIQUE_LOGFILE, + ER_THD(thd, ER_NO_UNIQUE_LOGFILE), MYF(ME_FATALERROR), log_name); sql_print_error(ER_DEFAULT(ER_NO_UNIQUE_LOGFILE), log_name); return 1; @@ -2567,16 +2789,16 @@ int MYSQL_LOG::generate_new_name(char *new_name, const char *log_name) void MYSQL_QUERY_LOG::reopen_file() { char *save_name; - DBUG_ENTER("MYSQL_LOG::reopen_file"); + + mysql_mutex_lock(&LOCK_log); if (!is_open()) { DBUG_PRINT("info",("log is closed")); + mysql_mutex_unlock(&LOCK_log); DBUG_VOID_RETURN; } - mysql_mutex_lock(&LOCK_log); - save_name= name; name= 0; // Don't free name close(LOG_CLOSE_TO_BE_OPENED); @@ -2589,7 +2811,7 @@ void MYSQL_QUERY_LOG::reopen_file() #ifdef HAVE_PSI_INTERFACE m_log_file_key, #endif - save_name, log_type, 0, io_cache_type); + save_name, log_type, 0, 0, io_cache_type); my_free(save_name); mysql_mutex_unlock(&LOCK_log); @@ -2620,11 +2842,11 @@ void MYSQL_QUERY_LOG::reopen_file() RETURN FASE - OK - TRUE - error occured + TRUE - error occurred */ bool MYSQL_QUERY_LOG::write(time_t event_time, const char *user_host, - uint user_host_len, int thread_id, + uint user_host_len, int thread_id_arg, const char *command_type, uint command_type_len, const char *sql_text, uint sql_text_len) { @@ -2663,7 +2885,7 @@ bool MYSQL_QUERY_LOG::write(time_t event_time, const char *user_host, goto err; /* command_type, thread_id */ - length= my_snprintf(buff, 32, "%5ld ", (long) thread_id); + length= my_snprintf(buff, 32, "%5ld ", (long) thread_id_arg); if (my_b_write(&log_file, (uchar*) buff, length)) goto err; @@ -2722,7 +2944,7 @@ err: RETURN FALSE - OK - TRUE - error occured + TRUE - error occurred */ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, @@ -2735,13 +2957,6 @@ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, DBUG_ENTER("MYSQL_QUERY_LOG::write"); mysql_mutex_lock(&LOCK_log); - - if (!is_open()) - { - mysql_mutex_unlock(&LOCK_log); - DBUG_RETURN(0); - } - if (is_open()) { // Safety agains reopen int tmp_errno= 0; @@ -2782,12 +2997,16 @@ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, sprintf(lock_time_buff, "%.6f", ulonglong2double(lock_utime)/1000000.0); if (my_b_printf(&log_file, "# Thread_id: %lu Schema: %s QC_hit: %s\n" \ - "# Query_time: %s Lock_time: %s Rows_sent: %lu Rows_examined: %lu\n", + "# Query_time: %s Lock_time: %s Rows_sent: %lu Rows_examined: %lu\n" \ + "# Rows_affected: %lu\n", (ulong) thd->thread_id, (thd->db ? thd->db : ""), ((thd->query_plan_flags & QPLAN_QC) ? "Yes" : "No"), query_time_buff, lock_time_buff, - (ulong) thd->sent_row_count, - (ulong) thd->examined_row_count) == (size_t) -1) + (ulong) thd->get_sent_row_count(), + (ulong) thd->get_examined_row_count(), + thd->get_stmt_da()->is_ok() ? + (ulong) thd->get_stmt_da()->affected_rows() : + 0) == (size_t) -1) tmp_errno= errno; if ((thd->variables.log_slow_verbosity & LOG_SLOW_VERBOSITY_QUERY_PLAN) && (thd->query_plan_flags & @@ -2796,7 +3015,8 @@ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, my_b_printf(&log_file, "# Full_scan: %s Full_join: %s " "Tmp_table: %s Tmp_table_on_disk: %s\n" - "# Filesort: %s Filesort_on_disk: %s Merge_passes: %lu\n", + "# Filesort: %s Filesort_on_disk: %s Merge_passes: %lu " + "Priority_queue: %s\n", ((thd->query_plan_flags & QPLAN_FULL_SCAN) ? "Yes" : "No"), ((thd->query_plan_flags & QPLAN_FULL_JOIN) ? "Yes" : "No"), ((thd->query_plan_flags & QPLAN_TMP_TABLE) ? "Yes" : "No"), @@ -2804,8 +3024,20 @@ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, ((thd->query_plan_flags & QPLAN_FILESORT) ? "Yes" : "No"), ((thd->query_plan_flags & QPLAN_FILESORT_DISK) ? "Yes" : "No"), - thd->query_plan_fsort_passes) == (size_t) -1) + thd->query_plan_fsort_passes, + ((thd->query_plan_flags & QPLAN_FILESORT_PRIORITY_QUEUE) ? + "Yes" : "No") + ) == (size_t) -1) tmp_errno= errno; + if (thd->variables.log_slow_verbosity & LOG_SLOW_VERBOSITY_EXPLAIN && + thd->lex->explain) + { + StringBuffer<128> buf; + DBUG_ASSERT(!thd->free_list); + if (!print_explain_for_slow_log(thd->lex, thd, &buf)) + my_b_printf(&log_file, "%s", buf.c_ptr_safe()); + thd->free_items(); + } if (thd->db && strcmp(thd->db, db)) { // Database changed if (my_b_printf(&log_file,"use %s;\n",thd->db) == (size_t) -1) @@ -2881,8 +3113,8 @@ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, first change fn_format() to cut the file name if it's too long. */ const char *MYSQL_LOG::generate_name(const char *log_name, - const char *suffix, - bool strip_ext, char *buff) + const char *suffix, + bool strip_ext, char *buff) { if (!log_name || !log_name[0]) { @@ -2895,24 +3127,44 @@ const char *MYSQL_LOG::generate_name(const char *log_name, { char *p= fn_ext(log_name); uint length= (uint) (p - log_name); - strmake(buff, log_name, min(length, FN_REFLEN-1)); + strmake(buff, log_name, MY_MIN(length, FN_REFLEN-1)); return (const char*)buff; } return log_name; } +/* + Print some additional information about addition/removal of + XID list entries. + TODO: Remove once MDEV-9510 is fixed. +*/ +#ifdef WITH_WSREP +#define WSREP_XID_LIST_ENTRY(X, Y) \ + if (wsrep_debug) \ + { \ + char buf[FN_REFLEN]; \ + strmake(buf, Y->binlog_name, Y->binlog_name_len); \ + WSREP_DEBUG(X, buf, Y->binlog_id); \ + } +#else +#define WSREP_XID_LIST_ENTRY(X, Y) do { } while(0) +#endif MYSQL_BIN_LOG::MYSQL_BIN_LOG(uint *sync_period) - :bytes_written(0), prepared_xids(0), file_id(1), open_count(1), - need_start_event(TRUE), + :reset_master_pending(0), mark_xid_done_waiting(0), + bytes_written(0), file_id(1), open_count(1), group_commit_queue(0), group_commit_queue_busy(FALSE), num_commits(0), num_group_commits(0), + group_commit_trigger_count(0), group_commit_trigger_timeout(0), + group_commit_trigger_lock_wait(0), sync_period_ptr(sync_period), sync_counter(0), + state_file_deleted(false), binlog_state_recover_done(false), is_relay_log(0), signal_cnt(0), checksum_alg_reset(BINLOG_CHECKSUM_ALG_UNDEF), relay_log_checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF), - description_event_for_exec(0), description_event_for_queue(0) + description_event_for_exec(0), description_event_for_queue(0), + current_binlog_id(0) { /* We don't want to initialize locks here as such initialization depends on @@ -2932,23 +3184,68 @@ void MYSQL_BIN_LOG::cleanup() DBUG_ENTER("cleanup"); if (inited) { + xid_count_per_binlog *b; + + /* Wait for the binlog background thread to stop. */ + if (!is_relay_log && binlog_background_thread_started) + { + mysql_mutex_lock(&LOCK_binlog_background_thread); + binlog_background_thread_stop= true; + mysql_cond_signal(&COND_binlog_background_thread); + while (binlog_background_thread_stop) + mysql_cond_wait(&COND_binlog_background_thread_end, + &LOCK_binlog_background_thread); + mysql_mutex_unlock(&LOCK_binlog_background_thread); + binlog_background_thread_started= false; + } + inited= 0; + mysql_mutex_lock(&LOCK_log); close(LOG_CLOSE_INDEX|LOG_CLOSE_STOP_EVENT); + mysql_mutex_unlock(&LOCK_log); delete description_event_for_queue; delete description_event_for_exec; + + while ((b= binlog_xid_count_list.get())) + { + /* + There should be no pending XIDs at shutdown, and only one entry (for + the active binlog file) in the list. + */ + DBUG_ASSERT(b->xid_count == 0); + DBUG_ASSERT(!binlog_xid_count_list.head()); + WSREP_XID_LIST_ENTRY("MYSQL_BIN_LOG::cleanup(): Removing xid_list_entry " + "for %s (%lu)", b); + my_free(b); + } + mysql_mutex_destroy(&LOCK_log); mysql_mutex_destroy(&LOCK_index); + mysql_mutex_destroy(&LOCK_xid_list); + mysql_mutex_destroy(&LOCK_binlog_background_thread); + mysql_mutex_destroy(&LOCK_binlog_end_pos); mysql_cond_destroy(&update_cond); + mysql_cond_destroy(&COND_queue_busy); + mysql_cond_destroy(&COND_xid_list); + mysql_cond_destroy(&COND_binlog_background_thread); + mysql_cond_destroy(&COND_binlog_background_thread_end); } + + /* + Free data for global binlog state. + We can't do that automaticly as we need to do this before + safemalloc is shut down + */ + if (!is_relay_log) + rpl_global_gtid_binlog_state.free(); DBUG_VOID_RETURN; } /* Init binlog-specific vars */ -void MYSQL_BIN_LOG::init(bool no_auto_events_arg, ulong max_size_arg) +void MYSQL_BIN_LOG::init(ulong max_size_arg) { DBUG_ENTER("MYSQL_BIN_LOG::init"); - no_auto_events= no_auto_events_arg; max_size= max_size_arg; DBUG_PRINT("info",("max_size: %lu", max_size)); DBUG_VOID_RETURN; @@ -2960,8 +3257,21 @@ void MYSQL_BIN_LOG::init_pthread_objects() MYSQL_LOG::init_pthread_objects(); mysql_mutex_init(m_key_LOCK_index, &LOCK_index, MY_MUTEX_INIT_SLOW); mysql_mutex_setflags(&LOCK_index, MYF_NO_DEADLOCK_DETECTION); + mysql_mutex_init(key_BINLOG_LOCK_xid_list, + &LOCK_xid_list, MY_MUTEX_INIT_FAST); mysql_cond_init(m_key_update_cond, &update_cond, 0); mysql_cond_init(m_key_COND_queue_busy, &COND_queue_busy, 0); + mysql_cond_init(key_BINLOG_COND_xid_list, &COND_xid_list, 0); + + mysql_mutex_init(key_BINLOG_LOCK_binlog_background_thread, + &LOCK_binlog_background_thread, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_BINLOG_COND_binlog_background_thread, + &COND_binlog_background_thread, 0); + mysql_cond_init(key_BINLOG_COND_binlog_background_thread_end, + &COND_binlog_background_thread_end, 0); + + mysql_mutex_init(m_key_LOCK_binlog_end_pos, &LOCK_binlog_end_pos, + MY_MUTEX_INIT_SLOW); } @@ -2986,7 +3296,7 @@ bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg, ".index", opt); if ((index_file_nr= mysql_file_open(m_key_file_log_index, index_file_name, - O_RDWR | O_CREAT | O_BINARY, + O_RDWR | O_CREAT | O_BINARY | O_CLOEXEC, MYF(MY_WME))) < 0 || mysql_file_sync(index_file_nr, MYF(MY_WME)) || init_io_cache(&index_file, index_file_nr, @@ -3048,19 +3358,36 @@ bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg, bool MYSQL_BIN_LOG::open(const char *log_name, enum_log_type log_type_arg, const char *new_name, + ulong next_log_number, enum cache_type io_cache_type_arg, - bool no_auto_events_arg, ulong max_size_arg, bool null_created_arg, bool need_mutex) { File file= -1; - + xid_count_per_binlog *new_xid_list_entry= NULL, *b; DBUG_ENTER("MYSQL_BIN_LOG::open"); DBUG_PRINT("enter",("log_type: %d",(int) log_type_arg)); - if (init_and_set_log_file_name(log_name, new_name, log_type_arg, - io_cache_type_arg)) + mysql_mutex_assert_owner(&LOCK_log); + + if (!is_relay_log) + { + if (!binlog_state_recover_done) + { + binlog_state_recover_done= true; + if (do_binlog_recovery(opt_bin_logname, false)) + DBUG_RETURN(1); + } + + if (!binlog_background_thread_started && + start_binlog_background_thread()) + DBUG_RETURN(1); + } + + /* We need to calculate new log file name for purge to delete old */ + if (init_and_set_log_file_name(log_name, new_name, next_log_number, + log_type_arg, io_cache_type_arg)) { sql_print_error("MYSQL_BIN_LOG::open failed to generate new file name."); DBUG_RETURN(1); @@ -3073,13 +3400,15 @@ bool MYSQL_BIN_LOG::open(const char *log_name, DBUG_EVALUATE_IF("fault_injection_registering_index", 1, 0)) { /** - TODO: although this was introduced to appease valgrind - when injecting emulated faults using fault_injection_registering_index - it may be good to consider what actually happens when - open_purge_index_file succeeds but register or sync fails. - - Perhaps we might need the code below in MYSQL_LOG_BIN::cleanup - for "real life" purposes as well? + TODO: + Although this was introduced to appease valgrind when + injecting emulated faults using + fault_injection_registering_index it may be good to consider + what actually happens when open_purge_index_file succeeds but + register or sync fails. + + Perhaps we might need the code below in MYSQL_LOG_BIN::cleanup + for "real life" purposes as well? */ DBUG_EXECUTE_IF("fault_injection_registering_index", { if (my_b_inited(&purge_index_file)) @@ -3102,7 +3431,9 @@ bool MYSQL_BIN_LOG::open(const char *log_name, #ifdef HAVE_PSI_INTERFACE m_key_file_log, #endif - log_name, log_type_arg, new_name, io_cache_type_arg)) + log_name, + LOG_UNKNOWN, /* Don't generate new name */ + 0, 0, io_cache_type_arg)) { #ifdef HAVE_REPLICATION close_purge_index_file(); @@ -3110,7 +3441,7 @@ bool MYSQL_BIN_LOG::open(const char *log_name, DBUG_RETURN(1); /* all warnings issued */ } - init(no_auto_events_arg, max_size_arg); + init(max_size_arg); open_count++; @@ -3134,11 +3465,10 @@ bool MYSQL_BIN_LOG::open(const char *log_name, write_file_name_to_index_file= 1; } - if (need_start_event && !no_auto_events) { /* - In 4.x we set need_start_event=0 here, but in 5.0 we want a Start event - even if this is not the very first binlog. + In 4.x we put Start event only in the first binlog. But from 5.0 we + want a Start event even if this is not the very first binlog. */ Format_description_log_event s(BINLOG_VERSION); /* @@ -3147,24 +3477,146 @@ bool MYSQL_BIN_LOG::open(const char *log_name, */ if (io_cache_type == WRITE_CACHE) s.flags |= LOG_EVENT_BINLOG_IN_USE_F; - s.checksum_alg= is_relay_log ? - /* relay-log */ - /* inherit master's A descriptor if one has been received */ - (relay_log_checksum_alg= - (relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) ? - relay_log_checksum_alg : - /* otherwise use slave's local preference of RL events verification */ - (opt_slave_sql_verify_checksum == 0) ? - (uint8) BINLOG_CHECKSUM_ALG_OFF : (uint8) binlog_checksum_options): - /* binlog */ - (uint8) binlog_checksum_options; + + if (is_relay_log) + { + if (relay_log_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF) + relay_log_checksum_alg= + opt_slave_sql_verify_checksum ? (enum_binlog_checksum_alg) binlog_checksum_options + : BINLOG_CHECKSUM_ALG_OFF; + s.checksum_alg= relay_log_checksum_alg; + } + else + s.checksum_alg= (enum_binlog_checksum_alg)binlog_checksum_options; + + crypto.scheme = 0; DBUG_ASSERT(s.checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF); if (!s.is_valid()) goto err; s.dont_set_created= null_created_arg; - if (s.write(&log_file)) + if (write_event(&s)) goto err; bytes_written+= s.data_written; + + if (encrypt_binlog) + { + uint key_version= encryption_key_get_latest_version(ENCRYPTION_KEY_SYSTEM_DATA); + if (key_version == ENCRYPTION_KEY_VERSION_INVALID) + { + sql_print_error("Failed to enable encryption of binary logs"); + goto err; + } + + if (key_version != ENCRYPTION_KEY_NOT_ENCRYPTED) + { + if (my_random_bytes(crypto.nonce, sizeof(crypto.nonce))) + goto err; + + Start_encryption_log_event sele(1, key_version, crypto.nonce); + sele.checksum_alg= s.checksum_alg; + if (write_event(&sele)) + goto err; + + // Start_encryption_log_event is written, enable the encryption + if (crypto.init(sele.crypto_scheme, key_version)) + goto err; + } + } + + if (!is_relay_log) + { + char buf[FN_REFLEN]; + + /* + Output a Gtid_list_log_event at the start of the binlog file. + + This is used to quickly determine which GTIDs are found in binlog + files earlier than this one, and which are found in this (or later) + binlogs. + + The list gives a mapping from (domain_id, server_id) -> seq_no (so + this means that there is at most one entry for every unique pair + (domain_id, server_id) in the list). It indicates that this seq_no is + the last one found in an earlier binlog file for this (domain_id, + server_id) combination - so any higher seq_no should be search for + from this binlog file, or a later one. + + This allows to locate the binlog file containing a given GTID by + scanning backwards, reading just the Gtid_list_log_event at the + start of each file, and scanning only the relevant binlog file when + found, not all binlog files. + + The existence of a given entry (domain_id, server_id, seq_no) + guarantees only that this seq_no will not be found in this or any + later binlog file. It does not guarantee that it can be found it an + earlier binlog file, for example the file may have been purged. + + If there is no entry for a given (domain_id, server_id) pair, then + it means that no such GTID exists in any earlier binlog. It is + permissible to remove such pair from future Gtid_list_log_events + if all previous binlog files containing such GTIDs have been purged + (though such optimization is not performed at the time of this + writing). So if there is no entry for given GTID it means that such + GTID should be search for in this or later binlog file, same as if + there had been an entry (domain_id, server_id, 0). + */ + + Gtid_list_log_event gl_ev(&rpl_global_gtid_binlog_state, 0); + if (write_event(&gl_ev)) + goto err; + + /* Output a binlog checkpoint event at the start of the binlog file. */ + + /* + Construct an entry in the binlog_xid_count_list for the new binlog + file (we will not link it into the list until we know the new file + is successfully created; otherwise we would have to remove it again + if creation failed, which gets tricky since other threads may have + seen the entry in the meantime - and we do not want to hold + LOCK_xid_list for long periods of time). + + Write the current binlog checkpoint into the log, so XA recovery will + know from where to start recovery. + */ + uint off= dirname_length(log_file_name); + uint len= strlen(log_file_name) - off; + char *entry_mem, *name_mem; + if (!(new_xid_list_entry = (xid_count_per_binlog *) + my_multi_malloc(MYF(MY_WME), + &entry_mem, sizeof(xid_count_per_binlog), + &name_mem, len, + NULL))) + goto err; + memcpy(name_mem, log_file_name+off, len); + new_xid_list_entry->binlog_name= name_mem; + new_xid_list_entry->binlog_name_len= len; + new_xid_list_entry->xid_count= 0; + + /* + Find the name for the Initial binlog checkpoint. + + Normally this will just be the first entry, as we delete entries + when their count drops to zero. But we scan the list to handle any + corner case, eg. for the first binlog file opened after startup, the + list will be empty. + */ + mysql_mutex_lock(&LOCK_xid_list); + I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list); + while ((b= it++) && b->xid_count == 0) + ; + mysql_mutex_unlock(&LOCK_xid_list); + if (!b) + b= new_xid_list_entry; + strmake(buf, b->binlog_name, b->binlog_name_len); + Binlog_checkpoint_log_event ev(buf, len); + DBUG_EXECUTE_IF("crash_before_write_checkpoint_event", + flush_io_cache(&log_file); + mysql_file_sync(log_file.file, MYF(MY_WME)); + DBUG_SUICIDE();); + if (write_event(&ev)) + goto err; + bytes_written+= ev.data_written; + } } if (description_event_for_queue && description_event_for_queue->binlog_version>=4) @@ -3193,17 +3645,26 @@ bool MYSQL_BIN_LOG::open(const char *log_name, /* Don't set log_pos in event header */ description_event_for_queue->set_artificial_event(); - if (description_event_for_queue->write(&log_file)) + if (write_event(description_event_for_queue)) goto err; bytes_written+= description_event_for_queue->data_written; } if (flush_io_cache(&log_file) || mysql_file_sync(log_file.file, MYF(MY_WME|MY_SYNC_FILESIZE))) goto err; - mysql_mutex_lock(&LOCK_commit_ordered); - strmake_buf(last_commit_pos_file, log_file_name); - last_commit_pos_offset= my_b_tell(&log_file); - mysql_mutex_unlock(&LOCK_commit_ordered); + + my_off_t offset= my_b_tell(&log_file); + + if (!is_relay_log) + { + /* update binlog_end_pos so that it can be read by after sync hook */ + reset_binlog_end_pos(log_file_name, offset); + + mysql_mutex_lock(&LOCK_commit_ordered); + strmake_buf(last_commit_pos_file, log_file_name); + last_commit_pos_offset= offset; + mysql_mutex_unlock(&LOCK_commit_ordered); + } if (write_file_name_to_index_file) { @@ -3235,6 +3696,49 @@ bool MYSQL_BIN_LOG::open(const char *log_name, #endif } } + + if (!is_relay_log) + { + /* + Now the file was created successfully, so we can link in the entry for + the new binlog file in binlog_xid_count_list. + */ + mysql_mutex_lock(&LOCK_xid_list); + ++current_binlog_id; + new_xid_list_entry->binlog_id= current_binlog_id; + /* Remove any initial entries with no pending XIDs. */ + while ((b= binlog_xid_count_list.head()) && b->xid_count == 0) + { + WSREP_XID_LIST_ENTRY("MYSQL_BIN_LOG::open(): Removing xid_list_entry for " + "%s (%lu)", b); + my_free(binlog_xid_count_list.get()); + } + mysql_cond_broadcast(&COND_xid_list); + WSREP_XID_LIST_ENTRY("MYSQL_BIN_LOG::open(): Adding new xid_list_entry for " + "%s (%lu)", new_xid_list_entry); + binlog_xid_count_list.push_back(new_xid_list_entry); + mysql_mutex_unlock(&LOCK_xid_list); + + /* + Now that we have synced a new binlog file with an initial Gtid_list + event, it is safe to delete the binlog state file. We will write out + a new, updated file at shutdown, and if we crash before we can recover + the state from the newly written binlog file. + + Since the state file will contain out-of-date data as soon as the first + new GTID is binlogged, it is better to remove it, to avoid any risk of + accidentally reading incorrect data later. + */ + if (!state_file_deleted) + { + char buf[FN_REFLEN]; + fn_format(buf, opt_bin_logname, mysql_data_home, ".state", + MY_UNPACK_FILENAME); + my_delete(buf, MY_SYNC_DIR); + state_file_deleted= true; + } + } + log_state= LOG_OPENED; #ifdef HAVE_REPLICATION @@ -3253,6 +3757,8 @@ err: Turning logging off for the whole duration of the MySQL server process. \ To turn it on again: fix the cause, \ shutdown the MySQL server and restart it.", name, errno); + if (new_xid_list_entry) + my_free(new_xid_list_entry); if (file >= 0) mysql_file_close(file, MYF(0)); close(LOG_CLOSE_INDEX); @@ -3270,6 +3776,7 @@ int MYSQL_BIN_LOG::get_current_log(LOG_INFO* linfo) int MYSQL_BIN_LOG::raw_get_current_log(LOG_INFO* linfo) { + mysql_mutex_assert_owner(&LOCK_log); strmake_buf(linfo->log_file_name, log_file_name); linfo->pos = my_b_tell(&log_file); return 0; @@ -3312,7 +3819,8 @@ static bool copy_up_file_and_fill(IO_CACHE *index_file, my_off_t offset) if (!bytes_read) break; // end of file mysql_file_seek(file, offset-init_offset, MY_SEEK_SET, MYF(0)); - if (mysql_file_write(file, io_buf, bytes_read, MYF(MY_WME | MY_NABP))) + if (mysql_file_write(file, io_buf, bytes_read, + MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL))) goto err; } /* The following will either truncate the file or fill the end with \n' */ @@ -3401,7 +3909,10 @@ int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name, error= !index_file.error ? LOG_INFO_EOF : LOG_INFO_IO; break; } - + if (fname[length-1] != '\n') + continue; // Not a log entry + fname[length-1]= 0; // Remove end \n + // extend relative paths and match against full path if (normalize_binlog_name(full_fname, fname, is_relay_log)) { @@ -3412,11 +3923,10 @@ int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name, // if the log entry matches, null string matching anything if (!log_name || - (log_name_len == fname_len-1 && full_fname[log_name_len] == '\n' && + (log_name_len == fname_len && !strncmp(full_fname, full_log_name, log_name_len))) { DBUG_PRINT("info", ("Found log file entry")); - full_fname[fname_len-1]= 0; // remove last \n linfo->index_file_start_offset= offset; linfo->index_file_offset = my_b_tell(&index_file); break; @@ -3498,11 +4008,13 @@ err: /** Delete all logs refered to in the index file. - Start writing to a new log file. The new index file will only contain this file. - @param thd Thread + @param thd Thread id. This can be zero in case of resetting + relay logs + @param create_new_log 1 if we should start writing to a new log file + @param next_log_number min number of next log file to use, if possible. @note If not called from slave thread, write start event to new log @@ -3513,7 +4025,9 @@ err: 1 error */ -bool MYSQL_BIN_LOG::reset_logs(THD* thd) +bool MYSQL_BIN_LOG::reset_logs(THD *thd, bool create_new_log, + rpl_gtid *init_state, uint32 init_state_len, + ulong next_log_number) { LOG_INFO linfo; bool error=0; @@ -3521,7 +4035,32 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd) const char* save_name; DBUG_ENTER("reset_logs"); - ha_reset_logs(thd); + if (!is_relay_log) + { + if (init_state && !is_empty_state()) + { + my_error(ER_BINLOG_MUST_BE_EMPTY, MYF(0)); + DBUG_RETURN(1); + } + + /* + Mark that a RESET MASTER is in progress. + This ensures that a binlog checkpoint will not try to write binlog + checkpoint events, which would be useless (as we are deleting the binlog + anyway) and could deadlock, as we are holding LOCK_log. + + Wait for any mark_xid_done() calls that might be already running to + complete (mark_xid_done_waiting counter to drop to zero); we need to + do this before we take the LOCK_log to not deadlock. + */ + mysql_mutex_lock(&LOCK_xid_list); + reset_master_pending++; + while (mark_xid_done_waiting > 0) + mysql_cond_wait(&COND_xid_list, &LOCK_xid_list); + mysql_mutex_unlock(&LOCK_xid_list); + } + + DEBUG_SYNC_C_IF_THD(thd, "reset_logs_after_set_reset_master_pending"); /* We need to get both locks to be sure that no one is trying to write to the index log file. @@ -3529,6 +4068,54 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd) mysql_mutex_lock(&LOCK_log); mysql_mutex_lock(&LOCK_index); + if (!is_relay_log) + { + /* + We are going to nuke all binary log files. + Without binlog, we cannot XA recover prepared-but-not-committed + transactions in engines. So force a commit checkpoint first. + + Note that we take and immediately + release LOCK_after_binlog_sync/LOCK_commit_ordered. This has + the effect to ensure that any on-going group commit (in + trx_group_commit_leader()) has completed before we request the checkpoint, + due to the chaining of LOCK_log and LOCK_commit_ordered in that function. + (We are holding LOCK_log, so no new group commit can start). + + Without this, it is possible (though perhaps unlikely) that the RESET + MASTER could run in-between the write to the binlog and the + commit_ordered() in the engine of some transaction, and then a crash + later would leave such transaction not recoverable. + */ + + mysql_mutex_lock(&LOCK_after_binlog_sync); + mysql_mutex_lock(&LOCK_commit_ordered); + mysql_mutex_unlock(&LOCK_after_binlog_sync); + mysql_mutex_unlock(&LOCK_commit_ordered); + + mark_xids_active(current_binlog_id, 1); + do_checkpoint_request(current_binlog_id); + + /* Now wait for all checkpoint requests and pending unlog() to complete. */ + mysql_mutex_lock(&LOCK_xid_list); + for (;;) + { + if (is_xidlist_idle_nolock()) + break; + /* + Wait until signalled that one more binlog dropped to zero, then check + again. + */ + mysql_cond_wait(&COND_xid_list, &LOCK_xid_list); + } + + /* + Now all XIDs are fully flushed to disk, and we are holding LOCK_log so + no new ones will be written. So we can proceed to delete the logs. + */ + mysql_mutex_unlock(&LOCK_xid_list); + } + /* The following mutex is needed to ensure that no threads call 'delete thd' as we would then risk missing a 'rollback' from this @@ -3556,7 +4143,7 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd) { uint errcode= purge_log_get_error_code(err); sql_print_error("Failed to locate old binlog or relay log files"); - my_message(errcode, ER(errcode), MYF(0)); + my_message(errcode, ER_THD_OR_DEFAULT(thd, errcode), MYF(0)); error= 1; goto err; } @@ -3567,9 +4154,12 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd) { if (my_errno == ENOENT) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE), - linfo.log_file_name); + if (thd) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_LOG_PURGE_NO_FILE, + ER_THD(thd, ER_LOG_PURGE_NO_FILE), + linfo.log_file_name); + sql_print_information("Failed to delete file '%s'", linfo.log_file_name); my_errno= 0; @@ -3577,13 +4167,14 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd) } else { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_BINLOG_PURGE_FATAL_ERR, - "a problem with deleting %s; " - "consider examining correspondence " - "of your binlog index file " - "to the actual binlog files", - linfo.log_file_name); + if (thd) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_BINLOG_PURGE_FATAL_ERR, + "a problem with deleting %s; " + "consider examining correspondence " + "of your binlog index file " + "to the actual binlog files", + linfo.log_file_name); error= 1; goto err; } @@ -3592,15 +4183,25 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd) break; } + if (!is_relay_log) + { + if (init_state) + rpl_global_gtid_binlog_state.load(init_state, init_state_len); + else + rpl_global_gtid_binlog_state.reset(); + } + /* Start logging with a new file */ close(LOG_CLOSE_INDEX | LOG_CLOSE_TO_BE_OPENED); if ((error= my_delete(index_file_name, MYF(0)))) // Reset (open will update) { if (my_errno == ENOENT) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE), - index_file_name); + if (thd) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_LOG_PURGE_NO_FILE, + ER_THD(thd, ER_LOG_PURGE_NO_FILE), + index_file_name); sql_print_information("Failed to delete file '%s'", index_file_name); my_errno= 0; @@ -3608,21 +4209,21 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd) } else { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_BINLOG_PURGE_FATAL_ERR, - "a problem with deleting %s; " - "consider examining correspondence " - "of your binlog index file " - "to the actual binlog files", - index_file_name); + if (thd) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_BINLOG_PURGE_FATAL_ERR, + "a problem with deleting %s; " + "consider examining correspondence " + "of your binlog index file " + "to the actual binlog files", + index_file_name); error= 1; goto err; } } - if (!thd->slave_thread) - need_start_event=1; - if (!open_index_file(index_file_name, 0, FALSE)) - if ((error= open(save_name, log_type, 0, io_cache_type, no_auto_events, max_size, 0, FALSE))) + if (create_new_log && !open_index_file(index_file_name, 0, FALSE)) + if ((error= open(save_name, log_type, 0, next_log_number, + io_cache_type, max_size, 0, FALSE))) goto err; my_free((void *) save_name); @@ -3630,12 +4231,60 @@ err: if (error == 1) name= const_cast<char*>(save_name); mysql_mutex_unlock(&LOCK_thread_count); + + if (!is_relay_log) + { + xid_count_per_binlog *b; + /* + Remove all entries in the xid_count list except the last. + Normally we will just be deleting all the entries that we waited for to + drop to zero above. But if we fail during RESET MASTER for some reason + then we will not have created any new log file, and we may keep the last + of the old entries. + */ + mysql_mutex_lock(&LOCK_xid_list); + for (;;) + { + b= binlog_xid_count_list.head(); + DBUG_ASSERT(b /* List can never become empty. */); + if (b->binlog_id == current_binlog_id) + break; + DBUG_ASSERT(b->xid_count == 0); + WSREP_XID_LIST_ENTRY("MYSQL_BIN_LOG::reset_logs(): Removing " + "xid_list_entry for %s (%lu)", b); + my_free(binlog_xid_count_list.get()); + } + mysql_cond_broadcast(&COND_xid_list); + reset_master_pending--; + mysql_mutex_unlock(&LOCK_xid_list); + } + mysql_mutex_unlock(&LOCK_index); mysql_mutex_unlock(&LOCK_log); DBUG_RETURN(error); } +void MYSQL_BIN_LOG::wait_for_last_checkpoint_event() +{ + mysql_mutex_lock(&LOCK_xid_list); + for (;;) + { + if (binlog_xid_count_list.is_last(binlog_xid_count_list.head())) + break; + mysql_cond_wait(&COND_xid_list, &LOCK_xid_list); + } + mysql_mutex_unlock(&LOCK_xid_list); + + /* + LOCK_xid_list and LOCK_log are chained, so the LOCK_log will only be + obtained after mark_xid_done() has written the last checkpoint event. + */ + mysql_mutex_lock(&LOCK_log); + mysql_mutex_unlock(&LOCK_log); +} + + /** Delete relay log files prior to rli->group_relay_log_name (i.e. all logs which are not involved in a non-finished group @@ -3677,17 +4326,41 @@ err: int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included) { - int error; + int error, errcode; char *to_purge_if_included= NULL; + inuse_relaylog *ir; ulonglong log_space_reclaimed= 0; DBUG_ENTER("purge_first_log"); DBUG_ASSERT(is_open()); - DBUG_ASSERT(rli->slave_running == 1); + DBUG_ASSERT(rli->slave_running == MYSQL_SLAVE_RUN_NOT_CONNECT); DBUG_ASSERT(!strcmp(rli->linfo.log_file_name,rli->event_relay_log_name)); mysql_mutex_lock(&LOCK_index); - to_purge_if_included= my_strdup(rli->group_relay_log_name, MYF(0)); + + ir= rli->inuse_relaylog_list; + while (ir) + { + inuse_relaylog *next= ir->next; + if (!ir->completed || ir->dequeued_count < ir->queued_count) + { + included= false; + break; + } + if (!included && !strcmp(ir->name, rli->group_relay_log_name)) + break; + if (!next) + { + rli->last_inuse_relaylog= NULL; + included= 1; + to_purge_if_included= my_strdup(ir->name, MYF(0)); + } + rli->free_inuse_relaylog(ir); + ir= next; + } + rli->inuse_relaylog_list= ir; + if (ir) + to_purge_if_included= my_strdup(ir->name, MYF(0)); /* Read the next log file name from the index file and pass it back to @@ -3696,12 +4369,9 @@ int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included) if((error=find_log_pos(&rli->linfo, rli->event_relay_log_name, 0)) || (error=find_next_log(&rli->linfo, 0))) { - char buff[22]; - sql_print_error("next log error: %d offset: %s log: %s included: %d", - error, - llstr(rli->linfo.index_file_offset,buff), - rli->event_relay_log_name, - included); + sql_print_error("next log error: %d offset: %llu log: %s included: %d", + error, rli->linfo.index_file_offset, + rli->event_relay_log_name, included); goto err; } @@ -3724,7 +4394,8 @@ int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included) } /* Store where we are in the new file for the execution thread */ - flush_relay_log_info(rli); + if (flush_relay_log_info(rli)) + error= LOG_INFO_IO; DBUG_EXECUTE_IF("crash_before_purge_logs", DBUG_SUICIDE();); @@ -3740,14 +4411,11 @@ int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included) * Need to update the log pos because purge logs has been called * after fetching initially the log pos at the begining of the method. */ - if((error=find_log_pos(&rli->linfo, rli->event_relay_log_name, 0))) - { - char buff[22]; - sql_print_error("next log error: %d offset: %s log: %s included: %d", - error, - llstr(rli->linfo.index_file_offset,buff), - rli->group_relay_log_name, - included); + if ((errcode= find_log_pos(&rli->linfo, rli->event_relay_log_name, 0))) + { + sql_print_error("next log error: %d offset: %llu log: %s included: %d", + errcode, rli->linfo.index_file_offset, + rli->group_relay_log_name, included); goto err; } @@ -3834,8 +4502,7 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, if ((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/))) goto err; while ((strcmp(to_log,log_info.log_file_name) || (exit_loop=included)) && - !is_active(log_info.log_file_name) && - !log_in_use(log_info.log_file_name)) + can_purge_log(log_info.log_file_name)) { if ((error= register_purge_index_entry(log_info.log_file_name))) { @@ -4023,8 +4690,8 @@ int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *reclaimed_space, */ if (thd) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_LOG_PURGE_NO_FILE, ER_THD(thd, ER_LOG_PURGE_NO_FILE), log_info.log_file_name); } sql_print_information("Failed to execute mysql_file_stat on file '%s'", @@ -4038,7 +4705,7 @@ int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *reclaimed_space, */ if (thd) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BINLOG_PURGE_FATAL_ERR, "a problem with getting info on being purged %s; " "consider examining correspondence " @@ -4066,7 +4733,7 @@ int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *reclaimed_space, { if (thd) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BINLOG_PURGE_FATAL_ERR, "a problem with deleting %s and " "reading the binlog index file", @@ -4082,13 +4749,6 @@ int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *reclaimed_space, } error= 0; - if (!need_mutex) - { - /* - This is to avoid triggering an error in NDB. - */ - ha_binlog_index_purge_file(current_thd, log_info.log_file_name); - } DBUG_PRINT("info",("purging %s",log_info.log_file_name)); if (!my_delete(log_info.log_file_name, MYF(0))) @@ -4102,8 +4762,8 @@ int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *reclaimed_space, { if (thd) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_LOG_PURGE_NO_FILE, ER_THD(thd, ER_LOG_PURGE_NO_FILE), log_info.log_file_name); } sql_print_information("Failed to delete file '%s'", @@ -4114,7 +4774,7 @@ int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *reclaimed_space, { if (thd) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BINLOG_PURGE_FATAL_ERR, "a problem with deleting %s; " "consider examining correspondence " @@ -4175,7 +4835,6 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) LOG_INFO log_info; MY_STAT stat_area; THD *thd= current_thd; - DBUG_ENTER("purge_logs_before_date"); mysql_mutex_lock(&LOCK_index); @@ -4185,8 +4844,7 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) goto err; while (strcmp(log_file_name, log_info.log_file_name) && - !is_active(log_info.log_file_name) && - !log_in_use(log_info.log_file_name)) + can_purge_log(log_info.log_file_name)) { if (!mysql_file_stat(m_key_file_log, log_info.log_file_name, &stat_area, MYF(0))) @@ -4205,7 +4863,7 @@ int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time) */ if (thd) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_BINLOG_PURGE_FATAL_ERR, "a problem with getting info on being purged %s; " "consider examining correspondence " @@ -4239,9 +4897,105 @@ err: mysql_mutex_unlock(&LOCK_index); DBUG_RETURN(error); } + + +bool +MYSQL_BIN_LOG::can_purge_log(const char *log_file_name_arg) +{ + xid_count_per_binlog *b; + + if (is_active(log_file_name_arg)) + return false; + mysql_mutex_lock(&LOCK_xid_list); + { + I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list); + while ((b= it++) && + 0 != strncmp(log_file_name_arg+dirname_length(log_file_name_arg), + b->binlog_name, b->binlog_name_len)) + ; + } + mysql_mutex_unlock(&LOCK_xid_list); + if (b) + return false; + return !log_in_use(log_file_name_arg); +} #endif /* HAVE_REPLICATION */ +bool +MYSQL_BIN_LOG::is_xidlist_idle() +{ + bool res; + mysql_mutex_lock(&LOCK_xid_list); + res= is_xidlist_idle_nolock(); + mysql_mutex_unlock(&LOCK_xid_list); + return res; +} + + +bool +MYSQL_BIN_LOG::is_xidlist_idle_nolock() +{ + xid_count_per_binlog *b; + + I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list); + while ((b= it++)) + { + if (b->xid_count > 0) + return false; + } + return true; +} + +#ifdef WITH_WSREP +inline bool +is_gtid_cached_internal(IO_CACHE *file) +{ + uchar data[EVENT_TYPE_OFFSET+1]; + bool result= false; + my_off_t write_pos= my_b_tell(file); + if (reinit_io_cache(file, READ_CACHE, 0, 0, 0)) + return false; + /* + In the cache we have gtid event if , below condition is true, + */ + my_b_read(file, data, sizeof(data)); + uint event_type= (uchar)data[EVENT_TYPE_OFFSET]; + if (event_type == GTID_LOG_EVENT) + result= true; + /* + Cleanup , Why because we have not read the full buffer + and this will cause next to next reinit_io_cache(called in write_cache) + to make cache empty. + */ + file->read_pos= file->read_end; + if (reinit_io_cache(file, WRITE_CACHE, write_pos, 0, 0)) + return false; + return result; +} +#endif + +#ifdef WITH_WSREP +inline bool +MYSQL_BIN_LOG::is_gtid_cached(THD *thd) +{ + binlog_cache_mngr *mngr= (binlog_cache_mngr *) thd_get_ha_data( + thd, binlog_hton); + if (!mngr) + return false; + binlog_cache_data *cache_trans= mngr->get_binlog_cache_data( + use_trans_cache(thd, true)); + binlog_cache_data *cache_stmt= mngr->get_binlog_cache_data( + use_trans_cache(thd, false)); + if (cache_trans && !cache_trans->empty() && + is_gtid_cached_internal(&cache_trans->cache_log)) + return true; + if (cache_stmt && !cache_stmt->empty() && + is_gtid_cached_internal(&cache_stmt->cache_log)) + return true; + return false; +} +#endif /** Create a new log file name. @@ -4267,6 +5021,20 @@ void MYSQL_BIN_LOG::make_log_name(char* buf, const char* log_ident) bool MYSQL_BIN_LOG::is_active(const char *log_file_name_arg) { + /** + * there should/must be mysql_mutex_assert_owner(&LOCK_log) here... + * but code violates this! (scary monsters and super creeps!) + * + * example stacktrace: + * #8 MYSQL_BIN_LOG::is_active + * #9 MYSQL_BIN_LOG::can_purge_log + * #10 MYSQL_BIN_LOG::purge_logs + * #11 MYSQL_BIN_LOG::purge_first_log + * #12 next_event + * #13 exec_relay_log_event + * + * I didn't investigate if this is ligit...(i.e if my comment is wrong) + */ return !strcmp(log_file_name, log_file_name_arg); } @@ -4315,43 +5083,22 @@ int MYSQL_BIN_LOG::new_file_impl(bool need_lock) char new_name[FN_REFLEN], *new_name_ptr, *old_name, *file_to_open; uint close_flag; bool delay_close= false; - File old_file; - LINT_INIT(old_file); - + File UNINIT_VAR(old_file); DBUG_ENTER("MYSQL_BIN_LOG::new_file_impl"); - if (!is_open()) - { - DBUG_PRINT("info",("log is closed")); - DBUG_RETURN(error); - } if (need_lock) mysql_mutex_lock(&LOCK_log); - mysql_mutex_lock(&LOCK_index); - mysql_mutex_assert_owner(&LOCK_log); - mysql_mutex_assert_owner(&LOCK_index); - /* - if binlog is used as tc log, be sure all xids are "unlogged", - so that on recover we only need to scan one - latest - binlog file - for prepared xids. As this is expected to be a rare event, - simple wait strategy is enough. We're locking LOCK_log to be sure no - new Xid_log_event's are added to the log (and prepared_xids is not - increased), and waiting on COND_prep_xids for late threads to - catch up. - */ - if (prepared_xids) + if (!is_open()) { - tc_log_page_waits++; - mysql_mutex_lock(&LOCK_prep_xids); - while (prepared_xids) { - DBUG_PRINT("info", ("prepared_xids=%lu", prepared_xids)); - mysql_cond_wait(&COND_prep_xids, &LOCK_prep_xids); - } - mysql_mutex_unlock(&LOCK_prep_xids); + DBUG_PRINT("info",("log is closed")); + mysql_mutex_unlock(&LOCK_log); + DBUG_RETURN(error); } + mysql_mutex_lock(&LOCK_index); + /* Reuse old name if not binlog and not update log */ new_name_ptr= name; @@ -4360,13 +5107,12 @@ int MYSQL_BIN_LOG::new_file_impl(bool need_lock) We have to do this here and not in open as we want to store the new file name in the current binary log file. */ - if ((error= generate_new_name(new_name, name))) + if ((error= generate_new_name(new_name, name, 0))) goto end; new_name_ptr=new_name; if (log_type == LOG_BIN) { - if (!no_auto_events) { /* We log the whole file name for log file as the user may decide @@ -4382,11 +5128,13 @@ int MYSQL_BIN_LOG::new_file_impl(bool need_lock) r.checksum_alg= relay_log_checksum_alg; DBUG_ASSERT(!is_relay_log || relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF); if(DBUG_EVALUATE_IF("fault_injection_new_file_rotate_event", (error=close_on_error=TRUE), FALSE) || - (error= r.write(&log_file))) + (error= write_event(&r))) { DBUG_EXECUTE_IF("fault_injection_new_file_rotate_event", errno=2;); close_on_error= TRUE; - my_printf_error(ER_ERROR_ON_WRITE, ER(ER_CANT_OPEN_FILE), MYF(ME_FATALERROR), name, errno); + my_printf_error(ER_ERROR_ON_WRITE, + ER_THD_OR_DEFAULT(current_thd, ER_CANT_OPEN_FILE), + MYF(ME_FATALERROR), name, errno); goto end; } bytes_written += r.data_written; @@ -4421,14 +5169,15 @@ int MYSQL_BIN_LOG::new_file_impl(bool need_lock) binlog_checksum_options= checksum_alg_reset; } /* - Note that at this point, log_state != LOG_CLOSED (important for is_open()). + Note that at this point, log_state != LOG_CLOSED + (important for is_open()). */ /* new_file() is only used for rotation (in FLUSH LOGS or because size > max_binlog_size or max_relay_log_size). - If this is a binary log, the Format_description_log_event at the beginning of - the new file should have created=0 (to distinguish with the + If this is a binary log, the Format_description_log_event at the + beginning of the new file should have created=0 (to distinguish with the Format_description_log_event written at server startup, which should trigger temp tables deletion on slaves. */ @@ -4440,14 +5189,15 @@ int MYSQL_BIN_LOG::new_file_impl(bool need_lock) { /* reopen the binary log file. */ file_to_open= new_name_ptr; - error= open(old_name, log_type, new_name_ptr, io_cache_type, - no_auto_events, max_size, 1, FALSE); + error= open(old_name, log_type, new_name_ptr, 0, io_cache_type, + max_size, 1, FALSE); } /* handle reopening errors */ if (error) { - my_printf_error(ER_CANT_OPEN_FILE, ER(ER_CANT_OPEN_FILE), + my_printf_error(ER_CANT_OPEN_FILE, + ER_THD_OR_DEFAULT(current_thd, ER_CANT_OPEN_FILE), MYF(ME_FATALERROR), file_to_open, error); close_on_error= TRUE; } @@ -4485,26 +5235,41 @@ end: new_name_ptr, errno); } + mysql_mutex_unlock(&LOCK_index); if (need_lock) mysql_mutex_unlock(&LOCK_log); - mysql_mutex_unlock(&LOCK_index); DBUG_RETURN(error); } +bool MYSQL_BIN_LOG::write_event(Log_event *ev, IO_CACHE *file) +{ + Log_event_writer writer(file, &crypto); + if (crypto.scheme && file == &log_file) + writer.ctx= alloca(crypto.ctx_size); + + return writer.write(ev); +} + +bool MYSQL_BIN_LOG::append(Log_event *ev) +{ + bool res; + mysql_mutex_lock(&LOCK_log); + res= append_no_lock(ev); + mysql_mutex_unlock(&LOCK_log); + return res; +} -bool MYSQL_BIN_LOG::append(Log_event* ev) + +bool MYSQL_BIN_LOG::append_no_lock(Log_event* ev) { bool error = 0; - mysql_mutex_lock(&LOCK_log); DBUG_ENTER("MYSQL_BIN_LOG::append"); + mysql_mutex_assert_owner(&LOCK_log); DBUG_ASSERT(log_file.type == SEQ_READ_APPEND); - /* - Log_event::write() is smart enough to use my_b_write() or - my_b_append() depending on the kind of cache we have. - */ - if (ev->write(&log_file)) + + if (write_event(ev)) { error=1; goto err; @@ -4513,40 +5278,69 @@ bool MYSQL_BIN_LOG::append(Log_event* ev) DBUG_PRINT("info",("max_size: %lu",max_size)); if (flush_and_sync(0)) goto err; - if ((uint) my_b_append_tell(&log_file) > max_size) + if (my_b_append_tell(&log_file) > max_size) error= new_file_without_locking(); err: - mysql_mutex_unlock(&LOCK_log); signal_update(); // Safe as we don't call close DBUG_RETURN(error); } - -bool MYSQL_BIN_LOG::appendv(const char* buf, uint len,...) +bool MYSQL_BIN_LOG::write_event_buffer(uchar* buf, uint len) { - bool error= 0; - DBUG_ENTER("MYSQL_BIN_LOG::appendv"); - va_list(args); - va_start(args,len); + bool error= 1; + uchar *ebuf= 0; + DBUG_ENTER("MYSQL_BIN_LOG::write_event_buffer"); DBUG_ASSERT(log_file.type == SEQ_READ_APPEND); mysql_mutex_assert_owner(&LOCK_log); - do + + if (crypto.scheme != 0) { - if (my_b_append(&log_file,(uchar*) buf,len)) - { - error= 1; + DBUG_ASSERT(crypto.scheme == 1); + + uint elen; + uchar iv[BINLOG_IV_LENGTH]; + + ebuf= (uchar*)my_safe_alloca(len); + if (!ebuf) goto err; - } - bytes_written += len; - } while ((buf=va_arg(args,const char*)) && (len=va_arg(args,uint))); + + crypto.set_iv(iv, my_b_append_tell(&log_file)); + + /* + we want to encrypt everything, excluding the event length: + massage the data before the encryption + */ + memcpy(buf + EVENT_LEN_OFFSET, buf, 4); + + if (encryption_crypt(buf + 4, len - 4, + ebuf + 4, &elen, + crypto.key, crypto.key_length, iv, sizeof(iv), + ENCRYPTION_FLAG_ENCRYPT | ENCRYPTION_FLAG_NOPAD, + ENCRYPTION_KEY_SYSTEM_DATA, crypto.key_version)) + goto err; + + DBUG_ASSERT(elen == len - 4); + + /* massage the data after the encryption */ + memcpy(ebuf, ebuf + EVENT_LEN_OFFSET, 4); + int4store(ebuf + EVENT_LEN_OFFSET, len); + + buf= ebuf; + } + if (my_b_append(&log_file, buf, len)) + goto err; + bytes_written+= len; + + error= 0; DBUG_PRINT("info",("max_size: %lu",max_size)); if (flush_and_sync(0)) goto err; - if ((uint) my_b_append_tell(&log_file) > max_size) + if (my_b_append_tell(&log_file) > max_size) error= new_file_without_locking(); err: + my_safe_afree(ebuf, len); if (!error) signal_update(); DBUG_RETURN(error); @@ -4798,7 +5592,37 @@ THD::binlog_start_trans_and_stmt() cache_mngr->trx_cache.get_prev_position() == MY_OFF_T_UNDEF) { this->binlog_set_stmt_begin(); - if (in_multi_stmt_transaction_mode()) + bool mstmt_mode= in_multi_stmt_transaction_mode(); +#ifdef WITH_WSREP + /* Write Gtid + Get domain id only when gtid mode is set + If this event is replicate through a master then , + we will forward the same gtid another nodes + We have to do this only one time in mysql transaction. + Since this function is called multiple times , We will check for + ha_info->is_started() + */ + Ha_trx_info *ha_info; + ha_info= this->ha_data[binlog_hton->slot].ha_info + (mstmt_mode ? 1 : 0); + + if (!ha_info->is_started() && wsrep_gtid_mode + && this->variables.gtid_seq_no) + { + binlog_cache_mngr *const cache_mngr= + (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton); + + IO_CACHE *file= + cache_mngr->get_binlog_cache_log(use_trans_cache(this, true)); + Log_event_writer writer(file); + Gtid_log_event gtid_event(this, this->variables.gtid_seq_no, + this->variables.gtid_domain_id, + true, LOG_EVENT_SUPPRESS_USE_F, + true, 0); + gtid_event.server_id= this->variables.server_id; + writer.write(>id_event); + } +#endif + if (mstmt_mode) trans_register_ha(this, TRUE, binlog_hton); trans_register_ha(this, FALSE, binlog_hton); /* @@ -4840,6 +5664,7 @@ binlog_start_consistent_snapshot(handlerton *hton, THD *thd) binlog_cache_mngr *const cache_mngr= thd->binlog_setup_trx_data(); /* Server layer calls us with LOCK_commit_ordered locked, so this is safe. */ + mysql_mutex_assert_owner(&LOCK_commit_ordered); strmake_buf(cache_mngr->last_commit_pos_file, mysql_bin_log.last_commit_pos_file); cache_mngr->last_commit_pos_offset= mysql_bin_log.last_commit_pos_offset; @@ -4872,8 +5697,13 @@ int THD::binlog_write_table_map(TABLE *table, bool is_transactional, (long) table, table->s->table_name.str, table->s->table_map_id)); + /* Ensure that all events in a GTID group are in the same cache */ + if (variables.option_bits & OPTION_GTID_BEGIN) + is_transactional= 1; + /* Pre-conditions */ - DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open()); + DBUG_ASSERT(is_current_stmt_binlog_format_row()); + DBUG_ASSERT(WSREP_EMULATE_BINLOG(this) || mysql_bin_log.is_open()); DBUG_ASSERT(table->s->table_map_id != ULONG_MAX); Table_map_log_event @@ -4887,22 +5717,23 @@ int THD::binlog_write_table_map(TABLE *table, bool is_transactional, IO_CACHE *file= cache_mngr->get_binlog_cache_log(use_trans_cache(this, is_transactional)); + Log_event_writer writer(file); binlog_cache_data *cache_data= cache_mngr->get_binlog_cache_data(use_trans_cache(this, is_transactional)); if (with_annotate && *with_annotate) { - Annotate_rows_log_event anno(current_thd, is_transactional, false); + Annotate_rows_log_event anno(table->in_use, is_transactional, false); /* Annotate event should be written not more than once */ *with_annotate= 0; - if ((error= anno.write(file))) + if ((error= writer.write(&anno))) { if (my_errno == EFBIG) cache_data->set_incident(); DBUG_RETURN(error); } } - if ((error= the_event.write(file))) + if ((error= writer.write(&the_event))) DBUG_RETURN(error); binlog_table_maps++; @@ -5013,7 +5844,7 @@ MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd, bool is_transactional) { DBUG_ENTER("MYSQL_BIN_LOG::flush_and_set_pending_rows_event(event)"); - DBUG_ASSERT(mysql_bin_log.is_open()); + DBUG_ASSERT(WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()); DBUG_PRINT("enter", ("event: 0x%lx", (long) event)); int error= 0; @@ -5029,14 +5860,14 @@ MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd, if (Rows_log_event* pending= cache_data->pending()) { - IO_CACHE *file= &cache_data->cache_log; + Log_event_writer writer(&cache_data->cache_log); /* Write pending event to the cache. */ DBUG_EXECUTE_IF("simulate_disk_full_at_flush_pending", {DBUG_SET("+d,simulate_file_write_error");}); - if (pending->write(file)) + if (writer.write(pending)) { set_write_error(thd, is_transactional); if (check_write_error(thd) && cache_data && @@ -5057,6 +5888,266 @@ MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd, DBUG_RETURN(error); } + +/* Generate a new global transaction ID, and write it to the binlog */ + +bool +MYSQL_BIN_LOG::write_gtid_event(THD *thd, bool standalone, + bool is_transactional, uint64 commit_id) +{ + rpl_gtid gtid; + uint32 domain_id; + uint32 local_server_id; + uint64 seq_no; + int err; + DBUG_ENTER("write_gtid_event"); + DBUG_PRINT("enter", ("standalone: %d", standalone)); + +#ifdef WITH_WSREP + if (WSREP(thd) && thd->wsrep_trx_meta.gtid.seqno != -1 && wsrep_gtid_mode && !thd->variables.gtid_seq_no) + { + domain_id= wsrep_gtid_domain_id; + } else { +#endif /* WITH_WSREP */ + domain_id= thd->variables.gtid_domain_id; +#ifdef WITH_WSREP + } +#endif /* WITH_WSREP */ + local_server_id= thd->variables.server_id; + seq_no= thd->variables.gtid_seq_no; + + if (thd->variables.option_bits & OPTION_GTID_BEGIN) + { + DBUG_PRINT("error", ("OPTION_GTID_BEGIN is set. " + "Master and slave will have different GTID values")); + /* Reset the flag, as we will write out a GTID anyway */ + thd->variables.option_bits&= ~OPTION_GTID_BEGIN; + } + + /* + Reset the session variable gtid_seq_no, to reduce the risk of accidentally + producing a duplicate GTID. + */ + thd->variables.gtid_seq_no= 0; + if (seq_no != 0) + { + /* Use the specified sequence number. */ + gtid.domain_id= domain_id; + gtid.server_id= local_server_id; + gtid.seq_no= seq_no; + err= rpl_global_gtid_binlog_state.update(>id, opt_gtid_strict_mode); + if (err && thd->get_stmt_da()->sql_errno()==ER_GTID_STRICT_OUT_OF_ORDER) + errno= ER_GTID_STRICT_OUT_OF_ORDER; + } + else + { + /* Allocate the next sequence number for the GTID. */ + err= rpl_global_gtid_binlog_state.update_with_next_gtid(domain_id, + local_server_id, >id); + seq_no= gtid.seq_no; + } + if (err) + DBUG_RETURN(true); + thd->last_commit_gtid= gtid; + + Gtid_log_event gtid_event(thd, seq_no, domain_id, standalone, + LOG_EVENT_SUPPRESS_USE_F, is_transactional, + commit_id); + + /* Write the event to the binary log. */ + DBUG_ASSERT(this == &mysql_bin_log); + +#ifdef WITH_WSREP + if (wsrep_gtid_mode && is_gtid_cached(thd)) + DBUG_RETURN(false); +#endif + + if (write_event(>id_event)) + DBUG_RETURN(true); + status_var_add(thd->status_var.binlog_bytes_written, gtid_event.data_written); + + DBUG_RETURN(false); +} + + +int +MYSQL_BIN_LOG::write_state_to_file() +{ + File file_no; + IO_CACHE cache; + char buf[FN_REFLEN]; + int err; + bool opened= false; + bool log_inited= false; + + fn_format(buf, opt_bin_logname, mysql_data_home, ".state", + MY_UNPACK_FILENAME); + if ((file_no= mysql_file_open(key_file_binlog_state, buf, + O_RDWR|O_CREAT|O_TRUNC|O_BINARY, + MYF(MY_WME))) < 0) + { + err= 1; + goto err; + } + opened= true; + if ((err= init_io_cache(&cache, file_no, IO_SIZE, WRITE_CACHE, 0, 0, + MYF(MY_WME|MY_WAIT_IF_FULL)))) + goto err; + log_inited= true; + if ((err= rpl_global_gtid_binlog_state.write_to_iocache(&cache))) + goto err; + log_inited= false; + if ((err= end_io_cache(&cache))) + goto err; + if ((err= mysql_file_sync(file_no, MYF(MY_WME|MY_SYNC_FILESIZE)))) + goto err; + goto end; + +err: + sql_print_error("Error writing binlog state to file '%s'.\n", buf); + if (log_inited) + end_io_cache(&cache); +end: + if (opened) + mysql_file_close(file_no, MYF(0)); + + return err; +} + + +/* + Initialize the binlog state from the master-bin.state file, at server startup. + + Returns: + 0 for success. + 2 for when .state file did not exist. + 1 for other error. +*/ +int +MYSQL_BIN_LOG::read_state_from_file() +{ + File file_no; + IO_CACHE cache; + char buf[FN_REFLEN]; + int err; + bool opened= false; + bool log_inited= false; + + fn_format(buf, opt_bin_logname, mysql_data_home, ".state", + MY_UNPACK_FILENAME); + if ((file_no= mysql_file_open(key_file_binlog_state, buf, + O_RDONLY|O_BINARY, MYF(0))) < 0) + { + if (my_errno != ENOENT) + { + err= 1; + goto err; + } + else + { + /* + If the state file does not exist, this is the first server startup + with GTID enabled. So initialize to empty state. + */ + rpl_global_gtid_binlog_state.reset(); + err= 2; + goto end; + } + } + opened= true; + if ((err= init_io_cache(&cache, file_no, IO_SIZE, READ_CACHE, 0, 0, + MYF(MY_WME|MY_WAIT_IF_FULL)))) + goto err; + log_inited= true; + if ((err= rpl_global_gtid_binlog_state.read_from_iocache(&cache))) + goto err; + goto end; + +err: + sql_print_error("Error reading binlog GTID state from file '%s'.\n", buf); +end: + if (log_inited) + end_io_cache(&cache); + if (opened) + mysql_file_close(file_no, MYF(0)); + + return err; +} + + +int +MYSQL_BIN_LOG::get_most_recent_gtid_list(rpl_gtid **list, uint32 *size) +{ + return rpl_global_gtid_binlog_state.get_most_recent_gtid_list(list, size); +} + + +bool +MYSQL_BIN_LOG::append_state_pos(String *str) +{ + return rpl_global_gtid_binlog_state.append_pos(str); +} + + +bool +MYSQL_BIN_LOG::append_state(String *str) +{ + return rpl_global_gtid_binlog_state.append_state(str); +} + + +bool +MYSQL_BIN_LOG::is_empty_state() +{ + return (rpl_global_gtid_binlog_state.count() == 0); +} + + +bool +MYSQL_BIN_LOG::find_in_binlog_state(uint32 domain_id, uint32 server_id_arg, + rpl_gtid *out_gtid) +{ + rpl_gtid *gtid; + if ((gtid= rpl_global_gtid_binlog_state.find(domain_id, server_id_arg))) + *out_gtid= *gtid; + return gtid != NULL; +} + + +bool +MYSQL_BIN_LOG::lookup_domain_in_binlog_state(uint32 domain_id, + rpl_gtid *out_gtid) +{ + rpl_gtid *found_gtid; + + if ((found_gtid= rpl_global_gtid_binlog_state.find_most_recent(domain_id))) + { + *out_gtid= *found_gtid; + return true; + } + + return false; +} + + +int +MYSQL_BIN_LOG::bump_seq_no_counter_if_needed(uint32 domain_id, uint64 seq_no) +{ + return rpl_global_gtid_binlog_state.bump_seq_no_if_needed(domain_id, seq_no); +} + + +bool +MYSQL_BIN_LOG::check_strict_gtid_sequence(uint32 domain_id, + uint32 server_id_arg, + uint64 seq_no) +{ + return rpl_global_gtid_binlog_state.check_strict_sequence(domain_id, + server_id_arg, + seq_no); +} + + /** Write an event to the binary log. If with_annotate != NULL and *with_annotate = TRUE write also Annotate_rows before the event @@ -5067,11 +6158,30 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) { THD *thd= event_info->thd; bool error= 1; - DBUG_ENTER("MYSQL_BIN_LOG::write(Log_event *)"); binlog_cache_data *cache_data= 0; bool is_trans_cache= FALSE; bool using_trans= event_info->use_trans_cache(); bool direct= event_info->use_direct_logging(); + ulong UNINIT_VAR(prev_binlog_id); + DBUG_ENTER("MYSQL_BIN_LOG::write(Log_event *)"); + + /* + When binary logging is not enabled (--log-bin=0), wsrep-patch partially + enables it without opening the binlog file (MYSQL_BIN_LOG::open(). + So, avoid writing to binlog file. + */ + if (direct && + (wsrep_emulate_bin_log || + (WSREP(thd) && !(thd->variables.option_bits & OPTION_BIN_LOG)))) + DBUG_RETURN(0); + + if (thd->variables.option_bits & OPTION_GTID_BEGIN) + { + DBUG_PRINT("info", ("OPTION_GTID_BEGIN was set")); + /* Wait for commit from binary log before we commit */ + direct= 0; + using_trans= 1; + } if (thd->binlog_evt_union.do_union) { @@ -5087,10 +6197,17 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) /* We only end the statement if we are in a top-level statement. If we are inside a stored function, we do not end the statement since - this will close all tables on the slave. + this will close all tables on the slave. But there can be a special case + where we are inside a stored function/trigger and a SAVEPOINT is being + set in side the stored function/trigger. This SAVEPOINT execution will + force the pending event to be flushed without an STMT_END_F flag. This + will result in a case where following DMLs will be considered as part of + same statement and result in data loss on slave. Hence in this case we + force the end_stmt to be true. */ - bool const end_stmt= - thd->locked_tables_mode && thd->lex->requires_prelocking(); + bool const end_stmt= (thd->in_sub_stmt && thd->lex->sql_command == + SQLCOM_SAVEPOINT) ? true : + (thd->locked_tables_mode && thd->lex->requires_prelocking()); if (thd->binlog_flush_pending_rows_event(end_stmt, using_trans)) DBUG_RETURN(error); @@ -5099,7 +6216,9 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) mostly called if is_open() *was* true a few instructions before, but it could have changed since. */ - if (likely(is_open())) + /* applier and replayer can skip writing binlog events */ + if ((WSREP_EMULATE_BINLOG(thd) && + IF_WSREP(thd->wsrep_exec_mode != REPL_RECV, 0)) || is_open()) { my_off_t UNINIT_VAR(my_org_b_tell); #ifdef HAVE_REPLICATION @@ -5109,7 +6228,17 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) binlog_[wild_]{do|ignore}_table?" (WL#1049)" */ const char *local_db= event_info->get_db(); - if ((!(thd->variables.option_bits & OPTION_BIN_LOG)) || + + bool option_bin_log_flag= (thd->variables.option_bits & OPTION_BIN_LOG); + + /* + Log all updates to binlog cache so that they can get replicated to other + nodes. A check has been added to stop them from getting logged into + binary log files. + */ + if (WSREP(thd)) option_bin_log_flag= true; + + if ((!(option_bin_log_flag)) || (thd->lex->sql_command != SQLCOM_ROLLBACK_TO_SAVEPOINT && thd->lex->sql_command != SQLCOM_SAVEPOINT && !binlog_filter->db_ok(local_db))) @@ -5120,9 +6249,27 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) if (direct) { + int res; + uint64 commit_id= 0; + DBUG_PRINT("info", ("direct is set")); + if ((res= thd->wait_for_prior_commit())) + DBUG_RETURN(res); file= &log_file; my_org_b_tell= my_b_tell(file); mysql_mutex_lock(&LOCK_log); + prev_binlog_id= current_binlog_id; + DBUG_EXECUTE_IF("binlog_force_commit_id", + { + const LEX_STRING commit_name= { C_STRING_WITH_LEN("commit_id") }; + bool null_value; + user_var_entry *entry= + (user_var_entry*) my_hash_search(&thd->user_vars, + (uchar*) commit_name.str, + commit_name.length); + commit_id= entry->val_int(&null_value); + }); + if (write_gtid_event(thd, true, using_trans, commit_id)) + goto err; } else { @@ -5156,7 +6303,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) Annotate_rows_log_event anno(thd, using_trans, direct); /* Annotate event should be written not more than once */ *with_annotate= 0; - if (anno.write(file)) + if (write_event(&anno, file)) goto err; } @@ -5170,7 +6317,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) Intvar_log_event e(thd,(uchar) LAST_INSERT_ID_EVENT, thd->first_successful_insert_id_in_prev_stmt_for_binlog, using_trans, direct); - if (e.write(file)) + if (write_event(&e, file)) goto err; } if (thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements() > 0) @@ -5181,14 +6328,14 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) Intvar_log_event e(thd, (uchar) INSERT_ID_EVENT, thd->auto_inc_intervals_in_cur_stmt_for_binlog. minimum(), using_trans, direct); - if (e.write(file)) + if (write_event(&e, file)) goto err; } if (thd->rand_used) { Rand_log_event e(thd,thd->rand_saved_seed1,thd->rand_saved_seed2, using_trans, direct); - if (e.write(file)) + if (write_event(&e, file)) goto err; } if (thd->user_var_events.elements) @@ -5212,7 +6359,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) flags, using_trans, direct); - if (e.write(file)) + if (write_event(&e, file)) goto err; } } @@ -5222,7 +6369,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) /* Write the event. */ - if (event_info->write(file) || + if (write_event(event_info, file) || DBUG_EVALUATE_IF("injecting_fault_writing", 1, 0)) goto err; @@ -5240,33 +6387,69 @@ err: if ((error= flush_and_sync(&synced))) { } - else if ((error= RUN_HOOK(binlog_storage, after_flush, - (thd, log_file_name, file->pos_in_file, synced)))) - { - sql_print_error("Failed to run 'after_flush' hooks"); - } else { - signal_update(); - if ((error= rotate(false, &check_purge))) - check_purge= false; + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); + mysql_mutex_assert_owner(&LOCK_log); + mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync); + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); + bool first= true; + bool last= true; + if ((error= RUN_HOOK(binlog_storage, after_flush, + (thd, log_file_name, file->pos_in_file, + synced, first, last)))) + { + sql_print_error("Failed to run 'after_flush' hooks"); + error= 1; + } + else + { + /* update binlog_end_pos so it can be read by dump thread + * + * note: must be _after_ the RUN_HOOK(after_flush) or else + * semi-sync-plugin might not have put the transaction into + * it's list before dump-thread tries to send it + */ + update_binlog_end_pos(offset); + + signal_update(); + if ((error= rotate(false, &check_purge))) + check_purge= false; + } } } status_var_add(thd->status_var.binlog_bytes_written, offset - my_org_b_tell); + mysql_mutex_lock(&LOCK_after_binlog_sync); + mysql_mutex_unlock(&LOCK_log); + + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); + mysql_mutex_assert_not_owner(&LOCK_log); + mysql_mutex_assert_owner(&LOCK_after_binlog_sync); + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); + bool first= true; + bool last= true; + if (RUN_HOOK(binlog_storage, after_sync, + (thd, log_file_name, file->pos_in_file, + first, last))) + { + error=1; + /* error is already printed inside hook */ + } + /* Take mutex to protect against a reader seeing partial writes of 64-bit offset on 32-bit CPUs. */ mysql_mutex_lock(&LOCK_commit_ordered); + mysql_mutex_unlock(&LOCK_after_binlog_sync); last_commit_pos_offset= offset; mysql_mutex_unlock(&LOCK_commit_ordered); - mysql_mutex_unlock(&LOCK_log); if (check_purge) - purge(); + checkpoint_and_purge(prev_binlog_id); } if (error) @@ -5351,6 +6534,60 @@ bool general_log_write(THD *thd, enum enum_server_command command, return FALSE; } + +static void +binlog_checkpoint_callback(void *cookie) +{ + MYSQL_BIN_LOG::xid_count_per_binlog *entry= + (MYSQL_BIN_LOG::xid_count_per_binlog *)cookie; + /* + For every supporting engine, we increment the xid_count and issue a + commit_checkpoint_request(). Then we can count when all + commit_checkpoint_notify() callbacks have occurred, and then log a new + binlog checkpoint event. + */ + mysql_bin_log.mark_xids_active(entry->binlog_id, 1); +} + + +/* + Request a commit checkpoint from each supporting engine. + This must be called after each binlog rotate, and after LOCK_log has been + released. The xid_count value in the xid_count_per_binlog entry was + incremented by 1 and will be decremented in this function; this ensures + that the entry will not go away early despite LOCK_log not being held. +*/ +void +MYSQL_BIN_LOG::do_checkpoint_request(ulong binlog_id) +{ + xid_count_per_binlog *entry; + + /* + Find the binlog entry, and invoke commit_checkpoint_request() on it in + each supporting storage engine. + */ + mysql_mutex_lock(&LOCK_xid_list); + I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list); + do { + entry= it++; + DBUG_ASSERT(entry /* binlog_id is always somewhere in the list. */); + } while (entry->binlog_id != binlog_id); + mysql_mutex_unlock(&LOCK_xid_list); + + ha_commit_checkpoint_request(entry, binlog_checkpoint_callback); + /* + When we rotated the binlog, we incremented xid_count to make sure the + entry would not go away until this point, where we have done all necessary + commit_checkpoint_request() calls. + So now we can (and must) decrease the count - when it reaches zero, we + will know that both all pending unlog() and all pending + commit_checkpoint_notify() calls are done, and we can log a new binlog + checkpoint. + */ + mark_xid_done(binlog_id, true); +} + + /** The method executes rotation when LOCK_log is already acquired by the caller. @@ -5359,6 +6596,15 @@ bool general_log_write(THD *thd, enum enum_server_command command, @param check_purge is set to true if rotation took place @note + Caller _must_ check the check_purge variable. If this is set, it means + that the binlog was rotated, and caller _must_ ensure that + do_checkpoint_request() is called later with the binlog_id of the rotated + binlog file. The call to do_checkpoint_request() must happen after + LOCK_log is released (which is why we cannot simply do it here). + Usually, checkpoint_and_purge() is appropriate, as it will both handle + the checkpointing and any needed purging of old logs. + + @note If rotation fails, for instance the server was unable to create a new log file, we still try to write an incident event to the current log. @@ -5371,12 +6617,41 @@ int MYSQL_BIN_LOG::rotate(bool force_rotate, bool* check_purge) int error= 0; DBUG_ENTER("MYSQL_BIN_LOG::rotate"); + if (wsrep_to_isolation) + { + DBUG_ASSERT(WSREP_ON); + *check_purge= false; + WSREP_DEBUG("avoiding binlog rotate due to TO isolation: %d", + wsrep_to_isolation); + DBUG_RETURN(0); + } + //todo: fix the macro def and restore safe_mutex_assert_owner(&LOCK_log); *check_purge= false; if (force_rotate || (my_b_tell(&log_file) >= (my_off_t) max_size)) { + ulong binlog_id= current_binlog_id; + /* + We rotate the binlog, so we need to start a commit checkpoint in all + supporting engines - when it finishes, we can log a new binlog checkpoint + event. + + But we cannot start the checkpoint here - there could be a group commit + still in progress which needs to be included in the checkpoint, and + besides we do not want to do the (possibly expensive) checkpoint while + LOCK_log is held. + + On the other hand, we must be sure that the xid_count entry for the + previous log does not go away until we start the checkpoint - which it + could do as it is no longer the most recent. So we increment xid_count + (to count the pending checkpoint request) - this will fix the entry in + place until we decrement again in do_checkpoint_request(). + */ + mark_xids_active(binlog_id, 1); + if ((error= new_file_without_locking())) + { /** Be conservative... There are possible lost events (eg, failing to log the Execute_load_query_log_event @@ -5389,7 +6664,14 @@ int MYSQL_BIN_LOG::rotate(bool force_rotate, bool* check_purge) if (!write_incident_already_locked(current_thd)) flush_and_sync(0); - *check_purge= true; + /* + We failed to rotate - so we have to decrement the xid_count back that + we incremented before attempting the rotate. + */ + mark_xid_done(binlog_id, false); + } + else + *check_purge= true; } DBUG_RETURN(error); } @@ -5417,6 +6699,127 @@ void MYSQL_BIN_LOG::purge() #endif } + +void MYSQL_BIN_LOG::checkpoint_and_purge(ulong binlog_id) +{ + do_checkpoint_request(binlog_id); + purge(); +} + + +/** + Searches for the first (oldest) binlog file name in in the binlog index. + + @param[in,out] buf_arg pointer to a buffer to hold found + the first binary log file name + @return NULL on success, otherwise error message +*/ +static const char* get_first_binlog(char* buf_arg) +{ + IO_CACHE *index_file; + size_t length; + char fname[FN_REFLEN]; + const char* errmsg= NULL; + + DBUG_ENTER("get_first_binlog"); + + DBUG_ASSERT(mysql_bin_log.is_open()); + + mysql_bin_log.lock_index(); + + index_file=mysql_bin_log.get_index_file(); + if (reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 0)) + { + errmsg= "failed to create a cache on binlog index"; + goto end; + } + /* The file ends with EOF or empty line */ + if ((length=my_b_gets(index_file, fname, sizeof(fname))) <= 1) + { + errmsg= "empty binlog index"; + goto end; + } + else + { + fname[length-1]= 0; // Remove end \n + } + if (normalize_binlog_name(buf_arg, fname, false)) + { + errmsg= "cound not normalize the first file name in the binlog index"; + goto end; + } +end: + mysql_bin_log.unlock_index(); + + DBUG_RETURN(errmsg); +} + +/** + Check weather the gtid binlog state can safely remove gtid + domains passed as the argument. A safety condition is satisfied when + there are no events from the being deleted domains in the currently existing + binlog files. Upon successful check the supplied domains are removed + from @@gtid_binlog_state. The caller is supposed to rotate binlog so that + the active latest file won't have the deleted domains in its Gtid_list header. + + @param domain_drop_lex gtid domain id sequence from lex. + Passed as a pointer to dynamic array must be not empty + unless pointer value NULL. + @retval zero on success + @retval > 0 ineffective call none from the *non* empty + gtid domain sequence is deleted + @retval < 0 on error +*/ +static int do_delete_gtid_domain(DYNAMIC_ARRAY *domain_drop_lex) +{ + int rc= 0; + Gtid_list_log_event *glev= NULL; + char buf[FN_REFLEN]; + File file; + IO_CACHE cache; + const char* errmsg= NULL; + char errbuf[MYSQL_ERRMSG_SIZE]= {0}; + + if (!domain_drop_lex) + return 0; // still "effective" having empty domain sequence to delete + + DBUG_ASSERT(domain_drop_lex->elements > 0); + mysql_mutex_assert_owner(mysql_bin_log.get_log_lock()); + + if ((errmsg= get_first_binlog(buf)) != NULL) + goto end; + bzero((char*) &cache, sizeof(cache)); + if ((file= open_binlog(&cache, buf, &errmsg)) == (File) -1) + goto end; + errmsg= get_gtid_list_event(&cache, &glev); + end_io_cache(&cache); + mysql_file_close(file, MYF(MY_WME)); + + DBUG_EXECUTE_IF("inject_binlog_delete_domain_init_error", + errmsg= "injected error";); + if (errmsg) + goto end; + errmsg= rpl_global_gtid_binlog_state.drop_domain(domain_drop_lex, + glev, errbuf); + +end: + if (errmsg) + { + if (strlen(errmsg) > 0) + { + my_error(ER_BINLOG_CANT_DELETE_GTID_DOMAIN, MYF(0), errmsg); + rc= -1; + } + else + { + rc= 1; + } + } + delete glev; + + return rc; +} + /** The method is a shortcut of @c rotate() and @c purge(). LOCK_log is acquired prior to rotate and is released after it. @@ -5426,15 +6829,25 @@ void MYSQL_BIN_LOG::purge() @retval nonzero - error in rotating routine. */ -int MYSQL_BIN_LOG::rotate_and_purge(bool force_rotate) +int MYSQL_BIN_LOG::rotate_and_purge(bool force_rotate, + DYNAMIC_ARRAY *domain_drop_lex) { - int error= 0; + int err_gtid=0, error= 0; + ulong prev_binlog_id; DBUG_ENTER("MYSQL_BIN_LOG::rotate_and_purge"); bool check_purge= false; //todo: fix the macro def and restore safe_mutex_assert_not_owner(&LOCK_log); mysql_mutex_lock(&LOCK_log); - if ((error= rotate(force_rotate, &check_purge))) + prev_binlog_id= current_binlog_id; + + if ((err_gtid= do_delete_gtid_domain(domain_drop_lex))) + { + // inffective attempt to delete merely skips rotate and purge + if (err_gtid < 0) + error= 1; // otherwise error is propagated the user + } + else if ((error= rotate(force_rotate, &check_purge))) check_purge= false; /* NOTE: Run purge_logs wo/ holding LOCK_log because it does not need @@ -5443,7 +6856,7 @@ int MYSQL_BIN_LOG::rotate_and_purge(bool force_rotate) mysql_mutex_unlock(&LOCK_log); if (check_purge) - purge(); + checkpoint_and_purge(prev_binlog_id); DBUG_RETURN(error); } @@ -5457,33 +6870,35 @@ uint MYSQL_BIN_LOG::next_file_id() return res; } +class CacheWriter: public Log_event_writer +{ +public: + ulong remains; -/** - Calculate checksum of possibly a part of an event containing at least - the whole common header. - - @param buf the pointer to trans cache's buffer - @param off the offset of the beginning of the event in the buffer - @param event_len no-checksum length of the event - @param length the current size of the buffer - - @param crc [in-out] the checksum + CacheWriter(THD *thd_arg, IO_CACHE *file_arg, bool do_checksum, + Binlog_crypt_data *cr) + : Log_event_writer(file_arg, cr), remains(0), thd(thd_arg), first(true) + { checksum_len= do_checksum ? BINLOG_CHECKSUM_LEN : 0; } - Event size in incremented by @c BINLOG_CHECKSUM_LEN. + ~CacheWriter() + { status_var_add(thd->status_var.binlog_bytes_written, bytes_written); } - @return 0 or number of unprocessed yet bytes of the event excluding - the checksum part. -*/ - static ulong fix_log_event_crc(uchar *buf, uint off, uint event_len, - uint length, ha_checksum *crc) -{ - ulong ret; - uchar *event_begin= buf + off; + int write(uchar* pos, size_t len) + { + if (first) + write_header(pos, len); + else + write_data(pos, len); - ret= length >= off + event_len ? 0 : off + event_len - length; - *crc= my_checksum(*crc, event_begin, event_len - ret); - return ret; -} + remains -= len; + if ((first= !remains)) + write_footer(); + return 0; + } +private: + THD *thd; + bool first; +}; /* Write the contents of a cache to the binary log. @@ -5504,20 +6919,22 @@ uint MYSQL_BIN_LOG::next_file_id() int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache) { + DBUG_ENTER("MYSQL_BIN_LOG::write_cache"); + mysql_mutex_assert_owner(&LOCK_log); if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0)) - return ER_ERROR_ON_WRITE; + DBUG_RETURN(ER_ERROR_ON_WRITE); uint length= my_b_bytes_in_cache(cache), group, carry, hdr_offs; - ulong remains= 0; // part of unprocessed yet netto length of the event long val; ulong end_log_pos_inc= 0; // each event processed adds BINLOG_CHECKSUM_LEN 2 t uchar header[LOG_EVENT_HEADER_LEN]; - ha_checksum crc= 0, crc_0= 0; // assignments to keep compiler happy - my_bool do_checksum= (binlog_checksum_options != BINLOG_CHECKSUM_ALG_OFF); - uchar buf[BINLOG_CHECKSUM_LEN]; + CacheWriter writer(thd, &log_file, binlog_checksum_options, &crypto); + + if (crypto.scheme) + writer.ctx= alloca(crypto.ctx_size); // while there is just one alg the following must hold: - DBUG_ASSERT(!do_checksum || + DBUG_ASSERT(binlog_checksum_options == BINLOG_CHECKSUM_ALG_OFF || binlog_checksum_options == BINLOG_CHECKSUM_ALG_CRC32); /* @@ -5536,8 +6953,6 @@ int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache) group= (uint)my_b_tell(&log_file); hdr_offs= carry= 0; - if (do_checksum) - crc= crc_0= my_checksum(0L, NULL, 0); do { @@ -5548,53 +6963,40 @@ int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache) if (unlikely(carry > 0)) { DBUG_ASSERT(carry < LOG_EVENT_HEADER_LEN); + uint tail= LOG_EVENT_HEADER_LEN - carry; /* assemble both halves */ - memcpy(&header[carry], (char *)cache->read_pos, - LOG_EVENT_HEADER_LEN - carry); + memcpy(&header[carry], (char *)cache->read_pos, tail); + + ulong len= uint4korr(header + EVENT_LEN_OFFSET); + writer.remains= len; /* fix end_log_pos */ - val= uint4korr(&header[LOG_POS_OFFSET]) + group + - (end_log_pos_inc+= (do_checksum ? BINLOG_CHECKSUM_LEN : 0)); - int4store(&header[LOG_POS_OFFSET], val); + end_log_pos_inc += writer.checksum_len; + val= uint4korr(header + LOG_POS_OFFSET) + group + end_log_pos_inc; + int4store(header + LOG_POS_OFFSET, val); - if (do_checksum) - { - ulong len= uint4korr(&header[EVENT_LEN_OFFSET]); - /* fix len */ - int4store(&header[EVENT_LEN_OFFSET], len + BINLOG_CHECKSUM_LEN); - } + /* fix len */ + len+= writer.checksum_len; + int4store(header + EVENT_LEN_OFFSET, len); - /* write the first half of the split header */ - if (my_b_write(&log_file, header, carry)) - return ER_ERROR_ON_WRITE; - status_var_add(thd->status_var.binlog_bytes_written, carry); + if (writer.write(header, LOG_EVENT_HEADER_LEN)) + DBUG_RETURN(ER_ERROR_ON_WRITE); - /* - copy fixed second half of header to cache so the correct - version will be written later. - */ - memcpy((char *)cache->read_pos, &header[carry], - LOG_EVENT_HEADER_LEN - carry); + cache->read_pos+= tail; + length-= tail; + carry= 0; /* next event header at ... */ - hdr_offs= uint4korr(&header[EVENT_LEN_OFFSET]) - carry - - (do_checksum ? BINLOG_CHECKSUM_LEN : 0); - - if (do_checksum) - { - DBUG_ASSERT(crc == crc_0 && remains == 0); - crc= my_checksum(crc, header, carry); - remains= uint4korr(header + EVENT_LEN_OFFSET) - carry - - BINLOG_CHECKSUM_LEN; - } - carry= 0; + hdr_offs= len - LOG_EVENT_HEADER_LEN - writer.checksum_len; } /* if there is anything to write, process it. */ if (likely(length > 0)) { + DBUG_EXECUTE_IF("fail_binlog_write_1", + errno= 28; DBUG_RETURN(ER_ERROR_ON_WRITE);); /* process all event-headers in this (partial) cache. if next header is beyond current read-buffer, @@ -5602,52 +7004,28 @@ int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache) very next iteration, just "eventually"). */ - /* crc-calc the whole buffer */ - if (do_checksum && hdr_offs >= length) + if (hdr_offs >= length) { - - DBUG_ASSERT(remains != 0 && crc != crc_0); - - crc= my_checksum(crc, cache->read_pos, length); - remains -= length; - if (my_b_write(&log_file, cache->read_pos, length)) - return ER_ERROR_ON_WRITE; - if (remains == 0) - { - int4store(buf, crc); - if (my_b_write(&log_file, buf, BINLOG_CHECKSUM_LEN)) - return ER_ERROR_ON_WRITE; - crc= crc_0; - } + if (writer.write(cache->read_pos, length)) + DBUG_RETURN(ER_ERROR_ON_WRITE); } while (hdr_offs < length) { /* - partial header only? save what we can get, process once - we get the rest. + finish off with remains of the last event that crawls + from previous into the current buffer */ - - if (do_checksum) + if (writer.remains != 0) { - if (remains != 0) - { - /* - finish off with remains of the last event that crawls - from previous into the current buffer - */ - DBUG_ASSERT(crc != crc_0); - crc= my_checksum(crc, cache->read_pos, hdr_offs); - int4store(buf, crc); - remains -= hdr_offs; - DBUG_ASSERT(remains == 0); - if (my_b_write(&log_file, cache->read_pos, hdr_offs) || - my_b_write(&log_file, buf, BINLOG_CHECKSUM_LEN)) - return ER_ERROR_ON_WRITE; - crc= crc_0; - } + if (writer.write(cache->read_pos, hdr_offs)) + DBUG_RETURN(ER_ERROR_ON_WRITE); } + /* + partial header only? save what we can get, process once + we get the rest. + */ if (hdr_offs + LOG_EVENT_HEADER_LEN > length) { carry= length - hdr_offs; @@ -5658,37 +7036,25 @@ int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache) { /* we've got a full event-header, and it came in one piece */ uchar *ev= (uchar *)cache->read_pos + hdr_offs; - uint event_len= uint4korr(ev + EVENT_LEN_OFFSET); // netto len + uint ev_len= uint4korr(ev + EVENT_LEN_OFFSET); // netto len uchar *log_pos= ev + LOG_POS_OFFSET; + end_log_pos_inc += writer.checksum_len; /* fix end_log_pos */ - val= uint4korr(log_pos) + group + - (end_log_pos_inc += (do_checksum ? BINLOG_CHECKSUM_LEN : 0)); + val= uint4korr(log_pos) + group + end_log_pos_inc; int4store(log_pos, val); - /* fix CRC */ - if (do_checksum) - { - /* fix length */ - int4store(ev + EVENT_LEN_OFFSET, event_len + BINLOG_CHECKSUM_LEN); - remains= fix_log_event_crc(cache->read_pos, hdr_offs, event_len, - length, &crc); - if (my_b_write(&log_file, ev, - remains == 0 ? event_len : length - hdr_offs)) - return ER_ERROR_ON_WRITE; - if (remains == 0) - { - int4store(buf, crc); - if (my_b_write(&log_file, buf, BINLOG_CHECKSUM_LEN)) - return ER_ERROR_ON_WRITE; - crc= crc_0; // crc is complete - } - } + /* fix length */ + int4store(ev + EVENT_LEN_OFFSET, ev_len + writer.checksum_len); + + writer.remains= ev_len; + if (writer.write(ev, std::min<uint>(ev_len, length - hdr_offs))) + DBUG_RETURN(ER_ERROR_ON_WRITE); /* next event header at ... */ - hdr_offs += event_len; // incr by the netto len + hdr_offs += ev_len; // incr by the netto len - DBUG_ASSERT(!do_checksum || remains == 0 || hdr_offs >= length); + DBUG_ASSERT(!writer.checksum_len || writer.remains == 0 || hdr_offs >= length); } } @@ -5702,23 +7068,12 @@ int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache) */ hdr_offs -= length; } - - /* Write data to the binary log file */ - DBUG_EXECUTE_IF("fail_binlog_write_1", - errno= 28; return ER_ERROR_ON_WRITE;); - if (!do_checksum) - if (my_b_write(&log_file, cache->read_pos, length)) - return ER_ERROR_ON_WRITE; - status_var_add(thd->status_var.binlog_bytes_written, length); - - cache->read_pos=cache->read_end; // Mark buffer used up } while ((length= my_b_fill(cache))); DBUG_ASSERT(carry == 0); - DBUG_ASSERT(!do_checksum || remains == 0); - DBUG_ASSERT(!do_checksum || crc == crc_0); + DBUG_ASSERT(!writer.checksum_len || writer.remains == 0); - return 0; // All OK + DBUG_RETURN(0); // All OK } /* @@ -5730,9 +7085,9 @@ int query_error_code(THD *thd, bool not_killed) if (not_killed || (killed_mask_hard(thd->killed) == KILL_BAD_DATA)) { - error= thd->is_error() ? thd->stmt_da->sql_errno() : 0; + error= thd->is_error() ? thd->get_stmt_da()->sql_errno() : 0; - /* thd->stmt_da->sql_errno() might be ER_SERVER_SHUTDOWN or + /* thd->get_get_stmt_da()->sql_errno() might be ER_SERVER_SHUTDOWN or ER_QUERY_INTERRUPTED, So here we need to make sure that error is not set to these errors when specified not_killed by the caller. @@ -5761,7 +7116,7 @@ bool MYSQL_BIN_LOG::write_incident_already_locked(THD *thd) if (likely(is_open())) { - error= ev.write(&log_file); + error= write_event(&ev); status_var_add(thd->status_var.binlog_bytes_written, ev.data_written); } @@ -5774,11 +7129,13 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) uint error= 0; my_off_t offset; bool check_purge= false; + ulong prev_binlog_id; DBUG_ENTER("MYSQL_BIN_LOG::write_incident"); mysql_mutex_lock(&LOCK_log); if (likely(is_open())) { + prev_binlog_id= current_binlog_id; if (!(error= write_incident_already_locked(thd)) && !(error= flush_and_sync(0))) { @@ -5788,6 +7145,9 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) } offset= my_b_tell(&log_file); + + update_binlog_end_pos(offset); + /* Take mutex to protect against a reader seeing partial writes of 64-bit offset on 32-bit CPUs. @@ -5798,7 +7158,7 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) mysql_mutex_unlock(&LOCK_log); if (check_purge) - purge(); + checkpoint_and_purge(prev_binlog_id); } else { @@ -5808,6 +7168,47 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd) DBUG_RETURN(error); } +void +MYSQL_BIN_LOG::write_binlog_checkpoint_event_already_locked(const char *name_arg, uint len) +{ + my_off_t offset; + Binlog_checkpoint_log_event ev(name_arg, len); + /* + Note that we must sync the binlog checkpoint to disk. + Otherwise a subsequent log purge could delete binlogs that XA recovery + thinks are needed (even though they are not really). + */ + if (!write_event(&ev) && !flush_and_sync(0)) + { + signal_update(); + } + else + { + /* + If we fail to write the checkpoint event, something is probably really + bad with the binlog. We complain in the error log. + + Note that failure to write binlog checkpoint does not compromise the + ability to do crash recovery - crash recovery will just have to scan a + bit more of the binlog than strictly necessary. + */ + sql_print_error("Failed to write binlog checkpoint event to binary log\n"); + } + + offset= my_b_tell(&log_file); + + update_binlog_end_pos(offset); + + /* + Take mutex to protect against a reader seeing partial writes of 64-bit + offset on 32-bit CPUs. + */ + mysql_mutex_lock(&LOCK_commit_ordered); + last_commit_pos_offset= offset; + mysql_mutex_unlock(&LOCK_commit_ordered); +} + + /** Write a cached log entry to the binary log. - To support transaction over replication, we wrap the transaction @@ -5840,28 +7241,41 @@ MYSQL_BIN_LOG::write_transaction_to_binlog(THD *thd, bool using_trx_cache) { group_commit_entry entry; + Ha_trx_info *ha_info; DBUG_ENTER("MYSQL_BIN_LOG::write_transaction_to_binlog"); + /* + Control should not be allowed beyond this point in wsrep_emulate_bin_log + mode. Also, do not write the cached updates to binlog if binary logging is + disabled (log-bin/sql_log_bin). + */ + if (wsrep_emulate_bin_log) + { + DBUG_RETURN(0); + } + else if (!(thd->variables.option_bits & OPTION_BIN_LOG)) + { + cache_mngr->need_unlog= false; + DBUG_RETURN(0); + } + entry.thd= thd; entry.cache_mngr= cache_mngr; entry.error= 0; entry.all= all; entry.using_stmt_cache= using_stmt_cache; entry.using_trx_cache= using_trx_cache; + entry.need_unlog= false; + ha_info= all ? thd->transaction.all.ha_list : thd->transaction.stmt.ha_list; - /* - Log "BEGIN" at the beginning of every transaction. Here, a transaction is - either a BEGIN..COMMIT block or a single statement in autocommit mode. - - Create the necessary events here, where we have the correct THD (and - thread context). + for (; ha_info; ha_info= ha_info->next()) + { + if (ha_info->is_started() && ha_info->ht() != binlog_hton && + !ha_info->ht()->commit_checkpoint_request) + entry.need_unlog= true; + break; + } - Due to group commit the actual writing to binlog may happen in a different - thread. - */ - Query_log_event qinfo(thd, STRING_WITH_LEN("BEGIN"), using_trx_cache, TRUE, - TRUE, 0); - entry.begin_event= &qinfo; entry.end_event= end_ev; if (cache_mngr->stmt_cache.has_incident() || cache_mngr->trx_cache.has_incident()) @@ -5877,45 +7291,344 @@ MYSQL_BIN_LOG::write_transaction_to_binlog(THD *thd, } } -bool -MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) + +/* + Put a transaction that is ready to commit in the group commit queue. + The transaction is identified by the ENTRY object passed into this function. + + To facilitate group commit for the binlog, we first queue up ourselves in + this function. Then later the first thread to enter the queue waits for + the LOCK_log mutex, and commits for everyone in the queue once it gets the + lock. Any other threads in the queue just wait for the first one to finish + the commit and wake them up. This way, all transactions in the queue get + committed in a single disk operation. + + The main work in this function is when the commit in one transaction has + been marked to wait for the commit of another transaction to happen + first. This is used to support in-order parallel replication, where + transactions can execute out-of-order but need to be committed in-order with + how they happened on the master. The waiting of one commit on another needs + to be integrated with the group commit queue, to ensure that the waiting + transaction can participate in the same group commit as the waited-for + transaction. + + So when we put a transaction in the queue, we check if there were other + transactions already prepared to commit but just waiting for the first one + to commit. If so, we add those to the queue as well, transitively for all + waiters. + + And if a transaction is marked to wait for a prior transaction, but that + prior transaction is already queued for group commit, then we can queue the + new transaction directly to participate in the group commit. + + @retval < 0 Error + @retval > 0 If queued as the first entry in the queue (meaning this + is the leader) + @retval 0 Otherwise (queued as participant, leader handles the commit) +*/ + +int +MYSQL_BIN_LOG::queue_for_group_commit(group_commit_entry *orig_entry) { + group_commit_entry *entry, *orig_queue, *last; + wait_for_commit *cur; + wait_for_commit *wfc; + DBUG_ENTER("MYSQL_BIN_LOG::queue_for_group_commit"); + + /* + Check if we need to wait for another transaction to commit before us. + + It is safe to do a quick check without lock first in the case where we do + not have to wait. But if the quick check shows we need to wait, we must do + another safe check under lock, to avoid the race where the other + transaction wakes us up between the check and the wait. + */ + wfc= orig_entry->thd->wait_for_commit_ptr; + orig_entry->queued_by_other= false; + if (wfc && wfc->waitee) + { + mysql_mutex_lock(&wfc->LOCK_wait_commit); + /* + Do an extra check here, this time safely under lock. + + If waitee->commit_started is set, it means that the transaction we need + to wait for has already queued up for group commit. In this case it is + safe for us to queue up immediately as well, increasing the opprtunities + for group commit. Because waitee has taken the LOCK_prepare_ordered + before setting the flag, so there is no risk that we can queue ahead of + it. + */ + if (wfc->waitee && !wfc->waitee->commit_started) + { + PSI_stage_info old_stage; + wait_for_commit *loc_waitee; + + /* + By setting wfc->opaque_pointer to our own entry, we mark that we are + ready to commit, but waiting for another transaction to commit before + us. + + This other transaction may then take over the commit process for us to + get us included in its own group commit. If this happens, the + queued_by_other flag is set. + + Setting this flag may or may not be seen by the other thread, but we + are safe in any case: The other thread will set queued_by_other under + its LOCK_wait_commit, and we will not check queued_by_other only after + we have been woken up. + */ + wfc->opaque_pointer= orig_entry; + DEBUG_SYNC(orig_entry->thd, "group_commit_waiting_for_prior"); + orig_entry->thd->ENTER_COND(&wfc->COND_wait_commit, + &wfc->LOCK_wait_commit, + &stage_waiting_for_prior_transaction_to_commit, + &old_stage); + while ((loc_waitee= wfc->waitee) && !orig_entry->thd->check_killed()) + mysql_cond_wait(&wfc->COND_wait_commit, &wfc->LOCK_wait_commit); + wfc->opaque_pointer= NULL; + DBUG_PRINT("info", ("After waiting for prior commit, queued_by_other=%d", + orig_entry->queued_by_other)); + + if (loc_waitee) + { + /* Wait terminated due to kill. */ + mysql_mutex_lock(&loc_waitee->LOCK_wait_commit); + if (loc_waitee->wakeup_subsequent_commits_running || + orig_entry->queued_by_other) + { + /* Our waitee is already waking us up, so ignore the kill. */ + mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit); + do + { + mysql_cond_wait(&wfc->COND_wait_commit, &wfc->LOCK_wait_commit); + } while (wfc->waitee); + } + else + { + /* We were killed, so remove us from the list of waitee. */ + wfc->remove_from_list(&loc_waitee->subsequent_commits_list); + mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit); + wfc->waitee= NULL; + + orig_entry->thd->EXIT_COND(&old_stage); + /* Interrupted by kill. */ + DEBUG_SYNC(orig_entry->thd, "group_commit_waiting_for_prior_killed"); + wfc->wakeup_error= orig_entry->thd->killed_errno(); + if (!wfc->wakeup_error) + wfc->wakeup_error= ER_QUERY_INTERRUPTED; + my_message(wfc->wakeup_error, + ER_THD(orig_entry->thd, wfc->wakeup_error), MYF(0)); + DBUG_RETURN(-1); + } + } + orig_entry->thd->EXIT_COND(&old_stage); + } + else + mysql_mutex_unlock(&wfc->LOCK_wait_commit); + } /* - To facilitate group commit for the binlog, we first queue up ourselves in - the group commit queue. Then the first thread to enter the queue waits for - the LOCK_log mutex, and commits for everyone in the queue once it gets the - lock. Any other threads in the queue just wait for the first one to finish - the commit and wake them up. + If the transaction we were waiting for has already put us into the group + commit queue (and possibly already done the entire binlog commit for us), + then there is nothing else to do. */ + if (orig_entry->queued_by_other) + DBUG_RETURN(0); + + if (wfc && wfc->wakeup_error) + { + my_error(ER_PRIOR_COMMIT_FAILED, MYF(0)); + DBUG_RETURN(-1); + } - entry->thd->clear_wakeup_ready(); + /* Now enqueue ourselves in the group commit queue. */ + DEBUG_SYNC(orig_entry->thd, "commit_before_enqueue"); + orig_entry->thd->clear_wakeup_ready(); mysql_mutex_lock(&LOCK_prepare_ordered); - group_commit_entry *orig_queue= group_commit_queue; - entry->next= orig_queue; - group_commit_queue= entry; + orig_queue= group_commit_queue; + + /* + Iteratively process everything added to the queue, looking for waiters, + and their waiters, and so on. If a waiter is ready to commit, we + immediately add it to the queue, and mark it as queued_by_other. + + This would be natural to do with recursion, but we want to avoid + potentially unbounded recursion blowing the C stack, so we use the list + approach instead. + + We keep a list of the group_commit_entry of all the waiters that need to + be processed. Initially this list contains only the entry passed into this + function. + + We process entries in the list one by one. The element currently being + processed is pointed to by `entry`, and the element at the end of the list + is pointed to by `last` (we do not use NULL to terminate the list). + + As we process an entry, any waiters for that entry are added at the end of + the list, to be processed in subsequent iterations. The the entry is added + to the group_commit_queue. This continues until the list is exhausted, + with all entries ever added eventually processed. + + The end result is a breath-first traversal of the tree of waiters, + re-using the `next' pointers of the group_commit_entry objects in place of + extra stack space in a recursive traversal. + + The temporary list linked through these `next' pointers is not used by the + caller or any other function; it only exists while doing the iterative + tree traversal. After, all the processed entries are linked into the + group_commit_queue. + */ - if (entry->cache_mngr->using_xa) + cur= wfc; + last= orig_entry; + entry= orig_entry; + for (;;) { - DEBUG_SYNC(entry->thd, "commit_before_prepare_ordered"); - run_prepare_ordered(entry->thd, entry->all); - DEBUG_SYNC(entry->thd, "commit_after_prepare_ordered"); + group_commit_entry *next_entry; + + if (entry->cache_mngr->using_xa) + { + DEBUG_SYNC(entry->thd, "commit_before_prepare_ordered"); + run_prepare_ordered(entry->thd, entry->all); + DEBUG_SYNC(entry->thd, "commit_after_prepare_ordered"); + } + + if (cur) + { + /* + Now that we have taken LOCK_prepare_ordered and will queue up in the + group commit queue, it is safe for following transactions to queue + themselves. We will grab here any transaction that is now ready to + queue up, but after that, more transactions may become ready while the + leader is waiting to start the group commit. So set the flag + `commit_started', so that later transactions can still participate in + the group commit.. + */ + cur->commit_started= true; + + /* + Check if this transaction has other transaction waiting for it to + commit. + + If so, process the waiting transactions, and their waiters and so on, + transitively. + */ + if (cur->subsequent_commits_list) + { + wait_for_commit *waiter, **waiter_ptr; + + mysql_mutex_lock(&cur->LOCK_wait_commit); + /* + Grab the list, now safely under lock, and process it if still + non-empty. + */ + waiter= cur->subsequent_commits_list; + waiter_ptr= &cur->subsequent_commits_list; + while (waiter) + { + wait_for_commit *next_waiter= waiter->next_subsequent_commit; + group_commit_entry *entry2= + (group_commit_entry *)waiter->opaque_pointer; + if (entry2) + { + /* + This is another transaction ready to be written to the binary + log. We can put it into the queue directly, without needing a + separate context switch to the other thread. We just set a flag + so that the other thread will know when it wakes up that it was + already processed. + + So remove it from the list of our waiters, and instead put it at + the end of the list to be processed in a subsequent iteration of + the outer loop. + */ + *waiter_ptr= next_waiter; + entry2->queued_by_other= true; + last->next= entry2; + last= entry2; + /* + As a small optimisation, we do not actually need to set + entry2->next to NULL, as we can use the pointer `last' to check + for end-of-list. + */ + } + else + { + /* + This transaction is not ready to participate in the group commit + yet, so leave it in the waiter list. It might join the group + commit later, if it completes soon enough to do so (it will see + our wfc->commit_started flag set), or it might commit later in a + later group commit. + */ + waiter_ptr= &waiter->next_subsequent_commit; + } + waiter= next_waiter; + } + mysql_mutex_unlock(&cur->LOCK_wait_commit); + } + } + + /* + Handle the heuristics that if another transaction is waiting for this + transaction (or if it does so later), then we want to trigger group + commit immediately, without waiting for the binlog_commit_wait_usec + timeout to expire. + */ + entry->thd->waiting_on_group_commit= true; + + /* Add the entry to the group commit queue. */ + next_entry= entry->next; + entry->next= group_commit_queue; + group_commit_queue= entry; + if (entry == last) + break; + /* + Move to the next entry in the flattened list of waiting transactions + that still need to be processed transitively. + */ + entry= next_entry; + DBUG_ASSERT(entry != NULL); + cur= entry->thd->wait_for_commit_ptr; } + + if (opt_binlog_commit_wait_count > 0 && orig_queue != NULL) + mysql_cond_signal(&COND_prepare_ordered); mysql_mutex_unlock(&LOCK_prepare_ordered); - DEBUG_SYNC(entry->thd, "commit_after_release_LOCK_prepare_ordered"); + DEBUG_SYNC(orig_entry->thd, "commit_after_release_LOCK_prepare_ordered"); + + DBUG_PRINT("info", ("Queued for group commit as %s\n", + (orig_queue == NULL) ? "leader" : "participant")); + DBUG_RETURN(orig_queue == NULL); +} + +bool +MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) +{ + int is_leader= queue_for_group_commit(entry); /* - The first in the queue handle group commit for all; the others just wait + The first in the queue handles group commit for all; the others just wait to be signalled when group commit is done. */ - if (orig_queue != NULL) + if (is_leader < 0) + return true; /* Error */ + else if (is_leader) + trx_group_commit_leader(entry); + else if (!entry->queued_by_other) entry->thd->wait_for_wakeup_ready(); else - trx_group_commit_leader(entry); + { + /* + If we were queued by another prior commit, then we are woken up + only when the leader has already completed the commit for us. + So nothing to do here then. + */ + } if (!opt_optimize_thread_scheduling) { /* For the leader, trx_group_commit_leader() already took the lock. */ - if (orig_queue != NULL) + if (!is_leader) mysql_mutex_lock(&LOCK_commit_ordered); DEBUG_SYNC(entry->thd, "commit_loop_entry_commit_ordered"); @@ -5931,15 +7644,41 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) DEBUG_SYNC(entry->thd, "commit_after_group_run_commit_ordered"); } mysql_mutex_unlock(&LOCK_commit_ordered); + entry->thd->wakeup_subsequent_commits(entry->error); if (next) { - next->thd->signal_wakeup_ready(); + /* + Wake up the next thread in the group commit. + + The next thread can be waiting in two different ways, depending on + whether it put itself in the queue, or if it was put in queue by us + because it had to wait for us to commit first. + + So execute the appropriate wakeup, identified by the queued_by_other + field. + */ + if (next->queued_by_other) + next->thd->wait_for_commit_ptr->wakeup(entry->error); + else + next->thd->signal_wakeup_ready(); + } + else + { + /* + If we rotated the binlog, and if we are using the unoptimized thread + scheduling where every thread runs its own commit_ordered(), then we + must do the commit checkpoint and log purge here, after all + commit_ordered() calls have finished, and locks have been released. + */ + if (entry->check_purge) + checkpoint_and_purge(entry->binlog_id); } + } if (likely(!entry->error)) - return 0; + return entry->thd->wait_for_prior_commit(); switch (entry->error) { @@ -5966,8 +7705,9 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) we need to mark it as not needed for recovery (unlog() is not called for a transaction if log_xid() fails). */ - if (entry->cache_mngr->using_xa && entry->cache_mngr->xa_xid) - mark_xid_done(); + if (entry->cache_mngr->using_xa && entry->cache_mngr->xa_xid && + entry->cache_mngr->need_unlog) + mark_xid_done(entry->cache_mngr->binlog_id, true); return 1; } @@ -5987,32 +7727,48 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) { uint xid_count= 0; my_off_t UNINIT_VAR(commit_offset); - group_commit_entry *current; - group_commit_entry *last_in_queue; + group_commit_entry *current, *last_in_queue; group_commit_entry *queue= NULL; bool check_purge= false; + ulong UNINIT_VAR(binlog_id); + uint64 commit_id; DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_leader"); - DBUG_ASSERT(is_open()); - if (likely(is_open())) // Should always be true { + DBUG_EXECUTE_IF("inject_binlog_commit_before_get_LOCK_log", + DBUG_ASSERT(!debug_sync_set_action(leader->thd, STRING_WITH_LEN + ("commit_before_get_LOCK_log SIGNAL waiting WAIT_FOR cont TIMEOUT 1"))); + ); /* Lock the LOCK_log(), and once we get it, collect any additional writes that queued up while we were waiting. */ + DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_log"); mysql_mutex_lock(&LOCK_log); DEBUG_SYNC(leader->thd, "commit_after_get_LOCK_log"); mysql_mutex_lock(&LOCK_prepare_ordered); + if (opt_binlog_commit_wait_count) + wait_for_sufficient_commits(); + /* + Note that wait_for_sufficient_commits() may have released and + re-acquired the LOCK_log and LOCK_prepare_ordered if it needed to wait. + */ current= group_commit_queue; group_commit_queue= NULL; mysql_mutex_unlock(&LOCK_prepare_ordered); + binlog_id= current_binlog_id; /* As the queue is in reverse order of entering, reverse it. */ last_in_queue= current; while (current) { group_commit_entry *next= current->next; + /* + Now that group commit is started, we can clear the flag; there is no + longer any use in waiters on this commit trying to trigger it early. + */ + current->thd->waiting_on_group_commit= false; current->next= queue; queue= current; current= next; @@ -6020,7 +7776,22 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) DBUG_ASSERT(leader == queue /* the leader should be first in queue */); /* Now we have in queue the list of transactions to be committed in order. */ + } + DBUG_ASSERT(is_open()); + if (likely(is_open())) // Should always be true + { + commit_id= (last_in_queue == leader ? 0 : (uint64)leader->thd->query_id); + DBUG_EXECUTE_IF("binlog_force_commit_id", + { + const LEX_STRING commit_name= { C_STRING_WITH_LEN("commit_id") }; + bool null_value; + user_var_entry *entry= + (user_var_entry*) my_hash_search(&leader->thd->user_vars, + (uchar*) commit_name.str, + commit_name.length); + commit_id= entry->val_int(&null_value); + }); /* Commit every transaction in the queue. @@ -6041,13 +7812,31 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) */ DBUG_ASSERT(!cache_mngr->stmt_cache.empty() || !cache_mngr->trx_cache.empty()); - current->error= write_transaction_or_stmt(current); + if ((current->error= write_transaction_or_stmt(current, commit_id))) + current->commit_errno= errno; strmake_buf(cache_mngr->last_commit_pos_file, log_file_name); commit_offset= my_b_write_tell(&log_file); cache_mngr->last_commit_pos_offset= commit_offset; if (cache_mngr->using_xa && cache_mngr->xa_xid) - xid_count++; + { + /* + If all storage engines support commit_checkpoint_request(), then we + do not need to keep track of when this XID is durably committed. + Instead we will just ask the storage engine to durably commit all its + XIDs when we rotate a binlog file. + */ + if (current->need_unlog) + { + xid_count++; + cache_mngr->need_unlog= true; + cache_mngr->binlog_id= binlog_id; + } + else + cache_mngr->need_unlog= false; + + cache_mngr->delayed_error= false; + } } bool synced= 0; @@ -6067,12 +7856,21 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) { bool any_error= false; bool all_error= true; + + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); + mysql_mutex_assert_owner(&LOCK_log); + mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync); + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); + bool first= true, last; for (current= queue; current != NULL; current= current->next) { + last= current->next == NULL; if (!current->error && RUN_HOOK(binlog_storage, after_flush, - (current->thd, log_file_name, - current->cache_mngr->last_commit_pos_offset, synced))) + (current->thd, + current->cache_mngr->last_commit_pos_file, + current->cache_mngr->last_commit_pos_offset, synced, + first, last))) { current->error= ER_ERROR_ON_WRITE; current->commit_errno= -1; @@ -6081,8 +7879,17 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) } else all_error= false; + first= false; } + /* update binlog_end_pos so it can be read by dump thread + * + * note: must be _after_ the RUN_HOOK(after_flush) or else + * semi-sync-plugin might not have put the transaction into + * it's list before dump-thread tries to send it + */ + update_binlog_end_pos(commit_offset); + if (any_error) sql_print_error("Failed to run 'after_flush' hooks"); if (!all_error) @@ -6090,50 +7897,87 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) } /* - if any commit_events are Xid_log_event, increase the number of - prepared_xids (it's decreased in ::unlog()). Binlog cannot be rotated - if there're prepared xids in it - see the comment in new_file() for - an explanation. - If no Xid_log_events (then it's all Query_log_event) rotate binlog, - if necessary. + If any commit_events are Xid_log_event, increase the number of pending + XIDs in current binlog (it's decreased in ::unlog()). When the count in + a (not active) binlog file reaches zero, we know that it is no longer + needed in XA recovery, and we can log a new binlog checkpoint event. */ if (xid_count > 0) { - mark_xids_active(xid_count); + mark_xids_active(binlog_id, xid_count); } - else + + if (rotate(false, &check_purge)) { - if (rotate(false, &check_purge)) - { - /* - If we fail to rotate, which thread should get the error? - We give the error to the *last* transaction thread; that seems to - make the most sense, as it was the last to write to the log. - */ - last_in_queue->error= ER_ERROR_ON_WRITE; - last_in_queue->commit_errno= errno; - check_purge= false; - } - /* In case of binlog rotate, update the correct current binlog offset. */ - commit_offset= my_b_write_tell(&log_file); + /* + If we fail to rotate, which thread should get the error? + We give the error to the leader, as any my_error() thrown inside + rotate() will have been registered for the leader THD. + + However we must not return error from here - that would cause + ha_commit_trans() to abort and rollback the transaction, which would + leave an inconsistent state with the transaction committed in the + binlog but rolled back in the engine. + + Instead set a flag so that we can return error later, from unlog(), + when the transaction has been safely committed in the engine. + */ + leader->cache_mngr->delayed_error= true; + my_error(ER_ERROR_ON_WRITE, MYF(ME_NOREFRESH), name, errno); + check_purge= false; } + /* In case of binlog rotate, update the correct current binlog offset. */ + commit_offset= my_b_write_tell(&log_file); } - DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_commit_ordered"); - mysql_mutex_lock(&LOCK_commit_ordered); - last_commit_pos_offset= commit_offset; + DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_after_binlog_sync"); + mysql_mutex_lock(&LOCK_after_binlog_sync); /* - We cannot unlock LOCK_log until we have locked LOCK_commit_ordered; + We cannot unlock LOCK_log until we have locked LOCK_after_binlog_sync; otherwise scheduling could allow the next group commit to run ahead of us, messing up the order of commit_ordered() calls. But as soon as - LOCK_commit_ordered is obtained, we can let the next group commit start. + LOCK_after_binlog_sync is obtained, we can let the next group commit start. */ mysql_mutex_unlock(&LOCK_log); - if (check_purge) - purge(); - DEBUG_SYNC(leader->thd, "commit_after_release_LOCK_log"); + + /* + Loop through threads and run the binlog_sync hook + */ + { + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); + mysql_mutex_assert_not_owner(&LOCK_log); + mysql_mutex_assert_owner(&LOCK_after_binlog_sync); + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); + + bool first= true, last; + for (current= queue; current != NULL; current= current->next) + { + last= current->next == NULL; + if (!current->error && + RUN_HOOK(binlog_storage, after_sync, + (current->thd, current->cache_mngr->last_commit_pos_file, + current->cache_mngr->last_commit_pos_offset, + first, last))) + { + /* error is already printed inside hook */ + } + first= false; + } + } + + DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_commit_ordered"); + mysql_mutex_lock(&LOCK_commit_ordered); + last_commit_pos_offset= commit_offset; + + /* + Unlock LOCK_after_binlog_sync only *after* LOCK_commit_ordered has been + acquired so that groups can not reorder for the different stages of + the group commit procedure. + */ + mysql_mutex_unlock(&LOCK_after_binlog_sync); + DEBUG_SYNC(leader->thd, "commit_after_release_LOCK_after_binlog_sync"); ++num_group_commits; if (!opt_optimize_thread_scheduling) @@ -6150,6 +7994,15 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) mysql_cond_wait(&COND_queue_busy, &LOCK_commit_ordered); group_commit_queue_busy= TRUE; + /* + Set these so parent can run checkpoint_and_purge() in last thread. + (When using optimized thread scheduling, we run checkpoint_and_purge() + in this function, so parent does not need to and we need not set these + values). + */ + last_in_queue->check_purge= check_purge; + last_in_queue->binlog_id= binlog_id; + /* Note that we return with LOCK_commit_ordered locked! */ DBUG_VOID_RETURN; } @@ -6165,8 +8018,10 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) DEBUG_SYNC(leader->thd, "commit_loop_entry_commit_ordered"); ++num_commits; - if (current->cache_mngr->using_xa && !current->error) + if (current->cache_mngr->using_xa && !current->error && + DBUG_EVALUATE_IF("skip_commit_ordered", 0, 1)) run_commit_ordered(current->thd, current->all); + current->thd->wakeup_subsequent_commits(current->error); /* Careful not to access current->next after waking up the other thread! As @@ -6174,32 +8029,40 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) */ next= current->next; if (current != leader) // Don't wake up ourself - current->thd->signal_wakeup_ready(); + { + if (current->queued_by_other) + current->thd->wait_for_commit_ptr->wakeup(current->error); + else + current->thd->signal_wakeup_ready(); + } current= next; } DEBUG_SYNC(leader->thd, "commit_after_group_run_commit_ordered"); mysql_mutex_unlock(&LOCK_commit_ordered); + DEBUG_SYNC(leader->thd, "commit_after_group_release_commit_ordered"); + + if (check_purge) + checkpoint_and_purge(binlog_id); DBUG_VOID_RETURN; } int -MYSQL_BIN_LOG::write_transaction_or_stmt(group_commit_entry *entry) +MYSQL_BIN_LOG::write_transaction_or_stmt(group_commit_entry *entry, + uint64 commit_id) { binlog_cache_mngr *mngr= entry->cache_mngr; + DBUG_ENTER("MYSQL_BIN_LOG::write_transaction_or_stmt"); - if (entry->begin_event->write(&log_file)) - return ER_ERROR_ON_WRITE; - status_var_add(entry->thd->status_var.binlog_bytes_written, - entry->begin_event->data_written); + if (write_gtid_event(entry->thd, false, entry->using_trx_cache, commit_id)) + DBUG_RETURN(ER_ERROR_ON_WRITE); if (entry->using_stmt_cache && !mngr->stmt_cache.empty() && write_cache(entry->thd, mngr->get_binlog_cache_log(FALSE))) { entry->error_cache= &mngr->stmt_cache.cache_log; - entry->commit_errno= errno; - return ER_ERROR_ON_WRITE; + DBUG_RETURN(ER_ERROR_ON_WRITE); } if (entry->using_trx_cache && !mngr->trx_cache.empty()) @@ -6219,46 +8082,180 @@ MYSQL_BIN_LOG::write_transaction_or_stmt(group_commit_entry *entry) if (write_cache(entry->thd, mngr->get_binlog_cache_log(TRUE))) { entry->error_cache= &mngr->trx_cache.cache_log; - entry->commit_errno= errno; - return ER_ERROR_ON_WRITE; + DBUG_RETURN(ER_ERROR_ON_WRITE); } } - if (entry->end_event->write(&log_file)) + DBUG_EXECUTE_IF("inject_error_writing_xid", + { + entry->error_cache= NULL; + errno= 28; + DBUG_RETURN(ER_ERROR_ON_WRITE); + }); + + if (write_event(entry->end_event)) { entry->error_cache= NULL; - entry->commit_errno= errno; - return ER_ERROR_ON_WRITE; + DBUG_RETURN(ER_ERROR_ON_WRITE); } status_var_add(entry->thd->status_var.binlog_bytes_written, entry->end_event->data_written); if (entry->incident_event) { - if (entry->incident_event->write(&log_file)) + if (write_event(entry->incident_event)) { entry->error_cache= NULL; - entry->commit_errno= errno; - return ER_ERROR_ON_WRITE; + DBUG_RETURN(ER_ERROR_ON_WRITE); } } if (mngr->get_binlog_cache_log(FALSE)->error) // Error on read { entry->error_cache= &mngr->stmt_cache.cache_log; - entry->commit_errno= errno; - return ER_ERROR_ON_READ; + DBUG_RETURN(ER_ERROR_ON_WRITE); } if (mngr->get_binlog_cache_log(TRUE)->error) // Error on read { entry->error_cache= &mngr->trx_cache.cache_log; - entry->commit_errno= errno; - return ER_ERROR_ON_READ; + DBUG_RETURN(ER_ERROR_ON_WRITE); } - return 0; + DBUG_RETURN(0); +} + + +/* + Wait for sufficient commits to queue up for group commit, according to the + values of binlog_commit_wait_count and binlog_commit_wait_usec. + + Note that this function may release and re-acquire LOCK_log and + LOCK_prepare_ordered if it needs to wait. +*/ + +void +MYSQL_BIN_LOG::wait_for_sufficient_commits() +{ + size_t count; + group_commit_entry *e; + group_commit_entry *last_head; + struct timespec wait_until; + + mysql_mutex_assert_owner(&LOCK_log); + mysql_mutex_assert_owner(&LOCK_prepare_ordered); + + for (e= last_head= group_commit_queue, count= 0; e; e= e->next) + { + if (++count >= opt_binlog_commit_wait_count) + { + group_commit_trigger_count++; + return; + } + if (unlikely(e->thd->has_waiter)) + { + group_commit_trigger_lock_wait++; + return; + } + } + + mysql_mutex_unlock(&LOCK_log); + set_timespec_nsec(wait_until, (ulonglong)1000*opt_binlog_commit_wait_usec); + + for (;;) + { + int err; + group_commit_entry *head; + + err= mysql_cond_timedwait(&COND_prepare_ordered, &LOCK_prepare_ordered, + &wait_until); + if (err == ETIMEDOUT) + { + group_commit_trigger_timeout++; + break; + } + if (unlikely(last_head->thd->has_waiter)) + { + group_commit_trigger_lock_wait++; + break; + } + head= group_commit_queue; + for (e= head; e && e != last_head; e= e->next) + { + ++count; + if (unlikely(e->thd->has_waiter)) + { + group_commit_trigger_lock_wait++; + goto after_loop; + } + } + if (count >= opt_binlog_commit_wait_count) + { + group_commit_trigger_count++; + break; + } + last_head= head; + } +after_loop: + + /* + We must not wait for LOCK_log while holding LOCK_prepare_ordered. + LOCK_log can be held for long periods (eg. we do I/O under it), while + LOCK_prepare_ordered must only be held for short periods. + + In addition, waiting for LOCK_log while holding LOCK_prepare_ordered would + violate locking order of LOCK_log-before-LOCK_prepare_ordered. This could + cause SAFEMUTEX warnings (even if it cannot actually deadlock with current + code, as there can be at most one group commit leader thread at a time). + + So release and re-acquire LOCK_prepare_ordered if we need to wait for the + LOCK_log. + */ + if (mysql_mutex_trylock(&LOCK_log)) + { + mysql_mutex_unlock(&LOCK_prepare_ordered); + mysql_mutex_lock(&LOCK_log); + mysql_mutex_lock(&LOCK_prepare_ordered); + } +} + + +void +MYSQL_BIN_LOG::binlog_trigger_immediate_group_commit() +{ + group_commit_entry *head; + mysql_mutex_assert_owner(&LOCK_prepare_ordered); + head= group_commit_queue; + if (head) + { + head->thd->has_waiter= true; + mysql_cond_signal(&COND_prepare_ordered); + } +} + + +/* + This function is called when a transaction T1 goes to wait for another + transaction T2. It is used to cut short any binlog group commit delay from + --binlog-commit-wait-count in the case where another transaction is stalled + on the wait due to conflicting row locks. + + If T2 is already ready to group commit, any waiting group commit will be + signalled to proceed immediately. Otherwise, a flag will be set in T2, and + when T2 later becomes ready, immediate group commit will be triggered. +*/ +void +binlog_report_wait_for(THD *thd1, THD *thd2) +{ + if (opt_binlog_commit_wait_count == 0) + return; + mysql_mutex_lock(&LOCK_prepare_ordered); + thd2->has_waiter= true; + if (thd2->waiting_on_group_commit) + mysql_bin_log.binlog_trigger_immediate_group_commit(); + mysql_mutex_unlock(&LOCK_prepare_ordered); } + /** Wait until we get a signal that the relay log has been updated. @@ -6272,15 +8269,15 @@ MYSQL_BIN_LOG::write_transaction_or_stmt(group_commit_entry *entry) void MYSQL_BIN_LOG::wait_for_update_relay_log(THD* thd) { - const char *old_msg; + PSI_stage_info old_stage; DBUG_ENTER("wait_for_update_relay_log"); - old_msg= thd->enter_cond(&update_cond, &LOCK_log, - "Slave has read all relay log; " - "waiting for the slave I/O " - "thread to update it" ); + mysql_mutex_assert_owner(&LOCK_log); + thd->ENTER_COND(&update_cond, &LOCK_log, + &stage_slave_has_read_all_relay_log, + &old_stage); mysql_cond_wait(&update_cond, &LOCK_log); - thd->exit_cond(old_msg); + thd->EXIT_COND(&old_stage); DBUG_VOID_RETURN; } @@ -6307,6 +8304,7 @@ int MYSQL_BIN_LOG::wait_for_update_bin_log(THD* thd, DBUG_ENTER("wait_for_update_bin_log"); thd_wait_begin(thd, THD_WAIT_BINLOG); + mysql_mutex_assert_owner(&LOCK_log); if (!timeout) mysql_cond_wait(&update_cond, &LOCK_log); else @@ -6316,6 +8314,23 @@ int MYSQL_BIN_LOG::wait_for_update_bin_log(THD* thd, DBUG_RETURN(ret); } +int MYSQL_BIN_LOG::wait_for_update_binlog_end_pos(THD* thd, + struct timespec *timeout) +{ + int ret= 0; + DBUG_ENTER("wait_for_update_binlog_end_pos"); + + thd_wait_begin(thd, THD_WAIT_BINLOG); + mysql_mutex_assert_owner(get_binlog_end_pos_lock()); + if (!timeout) + mysql_cond_wait(&update_cond, get_binlog_end_pos_lock()); + else + ret= mysql_cond_timedwait(&update_cond, get_binlog_end_pos_lock(), + timeout); + thd_wait_end(thd); + DBUG_RETURN(ret); +} + /** Close the log file. @@ -6335,23 +8350,48 @@ int MYSQL_BIN_LOG::wait_for_update_bin_log(THD* thd, void MYSQL_BIN_LOG::close(uint exiting) { // One can't set log_type here! + bool failed_to_save_state= false; DBUG_ENTER("MYSQL_BIN_LOG::close"); DBUG_PRINT("enter",("exiting: %d", (int) exiting)); + + mysql_mutex_assert_owner(&LOCK_log); + if (log_state == LOG_OPENED) { #ifdef HAVE_REPLICATION - if (log_type == LOG_BIN && !no_auto_events && + if (log_type == LOG_BIN && (exiting & LOG_CLOSE_STOP_EVENT)) { Stop_log_event s; // the checksumming rule for relay-log case is similar to Rotate - s.checksum_alg= is_relay_log ? - (uint8) relay_log_checksum_alg : (uint8) binlog_checksum_options; + s.checksum_alg= is_relay_log ? relay_log_checksum_alg + : (enum_binlog_checksum_alg)binlog_checksum_options; DBUG_ASSERT(!is_relay_log || relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF); - s.write(&log_file); + write_event(&s); bytes_written+= s.data_written; signal_update(); + + /* + When we shut down server, write out the binlog state to a separate + file so we do not have to scan an entire binlog file to recover it + at next server start. + + Note that this must be written and synced to disk before marking the + last binlog file as "not crashed". + */ + if (!is_relay_log && write_state_to_file()) + { + sql_print_error("Failed to save binlog GTID state during shutdown. " + "Binlog will be marked as crashed, so that crash " + "recovery can recover the state at next server " + "startup."); + /* + Leave binlog file marked as crashed, so we can recover state by + scanning it now that we failed to write out the state properly. + */ + failed_to_save_state= true; + } } #endif /* HAVE_REPLICATION */ @@ -6360,7 +8400,8 @@ void MYSQL_BIN_LOG::close(uint exiting) && !(exiting & LOG_CLOSE_DELAYED_CLOSE)) { my_off_t org_position= mysql_file_tell(log_file.file, MYF(0)); - clear_inuse_flag_when_closing(log_file.file); + if (!failed_to_save_state) + clear_inuse_flag_when_closing(log_file.file); /* Restore position so that anything we have in the IO_cache is written to the correct position. @@ -6564,7 +8605,7 @@ static void print_buffer_to_nt_eventlog(enum loglevel level, char *buff, DBUG_ENTER("print_buffer_to_nt_eventlog"); /* Add ending CR/LF's to string, overwrite last chars if necessary */ - strmov(buffptr+min(length, buffLen-5), "\r\n\r\n"); + strmov(buffptr+MY_MIN(length, buffLen-5), "\r\n\r\n"); setup_windows_event_source(); if ((event= RegisterEventSource(NULL,"MySQL"))) @@ -6598,24 +8639,44 @@ static void print_buffer_to_file(enum loglevel level, const char *buffer, time_t skr; struct tm tm_tmp; struct tm *start; + THD *thd; + int tag_length= 0; + char tag[NAME_LEN]; DBUG_ENTER("print_buffer_to_file"); DBUG_PRINT("enter",("buffer: %s", buffer)); + if (mysqld_server_initialized && (thd= current_thd)) + { + if (thd->connection_name.length) + { + /* + Add tag for slaves so that the user can see from which connection + the error originates. + */ + tag_length= my_snprintf(tag, sizeof(tag), + ER_THD(thd, ER_MASTER_LOG_PREFIX), + (int) thd->connection_name.length, + thd->connection_name.str); + } + } + mysql_mutex_lock(&LOCK_error_log); skr= my_time(0); localtime_r(&skr, &tm_tmp); start=&tm_tmp; - fprintf(stderr, "%02d%02d%02d %2d:%02d:%02d [%s] %.*s\n", - start->tm_year % 100, + fprintf(stderr, "%d-%02d-%02d %2d:%02d:%02d %lu [%s] %.*s%.*s\n", + start->tm_year + 1900, start->tm_mon+1, start->tm_mday, start->tm_hour, start->tm_min, start->tm_sec, + (unsigned long) pthread_self(), (level == ERROR_LEVEL ? "ERROR" : level == WARNING_LEVEL ? "Warning" : "Note"), + tag_length, tag, (int) length, buffer); fflush(stderr); @@ -6689,6 +8750,9 @@ void sql_print_information(const char *format, ...) va_list args; DBUG_ENTER("sql_print_information"); + if (disable_log_notes) + DBUG_VOID_RETURN; // Skip notes during start/shutdown + va_start(args, format); error_log_print(INFORMATION_LEVEL, format, args); va_end(args); @@ -6738,8 +8802,7 @@ int TC_LOG_MMAP::log_and_order(THD *thd, my_xid xid, bool all, { int cookie; struct commit_entry entry; - bool is_group_commit_leader; - LINT_INIT(is_group_commit_leader); + bool UNINIT_VAR(is_group_commit_leader); if (need_prepare_ordered) { @@ -6761,6 +8824,9 @@ int TC_LOG_MMAP::log_and_order(THD *thd, my_xid xid, bool all, mysql_mutex_unlock(&LOCK_prepare_ordered); } + if (thd->wait_for_prior_commit()) + return 0; + cookie= 0; if (xid) cookie= log_one_transaction(xid); @@ -6885,7 +8951,7 @@ ulong tc_log_page_waits= 0; static const uchar tc_log_magic[]={(uchar) 254, 0x23, 0x05, 0x74}; -ulong opt_tc_log_size= TC_LOG_MIN_SIZE; +ulong opt_tc_log_size; ulong tc_log_max_pages_used=0, tc_log_page_size=0, tc_log_cur_pages_used=0; int TC_LOG_MMAP::open(const char *opt_name) @@ -6898,17 +8964,16 @@ int TC_LOG_MMAP::open(const char *opt_name) DBUG_ASSERT(opt_name && opt_name[0]); tc_log_page_size= my_getpagesize(); - DBUG_ASSERT(TC_LOG_PAGE_SIZE % tc_log_page_size == 0); fn_format(logname,opt_name,mysql_data_home,"",MY_UNPACK_FILENAME); - if ((fd= mysql_file_open(key_file_tclog, logname, O_RDWR, MYF(0))) < 0) + if ((fd= mysql_file_open(key_file_tclog, logname, O_RDWR | O_CLOEXEC, MYF(0))) < 0) { if (my_errno != ENOENT) goto err; if (using_heuristic_recover()) return 1; if ((fd= mysql_file_create(key_file_tclog, logname, CREATE_MODE, - O_RDWR, MYF(MY_WME))) < 0) + O_RDWR | O_CLOEXEC, MYF(MY_WME))) < 0) goto err; inited=1; file_length= opt_tc_log_size; @@ -6974,6 +9039,8 @@ int TC_LOG_MMAP::open(const char *opt_name) mysql_mutex_init(key_LOCK_sync, &LOCK_sync, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_active, &LOCK_active, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_pool, &LOCK_pool, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_pending_checkpoint, &LOCK_pending_checkpoint, + MY_MUTEX_INIT_FAST); mysql_cond_init(key_COND_active, &COND_active, 0); mysql_cond_init(key_COND_pool, &COND_pool, 0); mysql_cond_init(key_TC_LOG_MMAP_COND_queue_busy, &COND_queue_busy, 0); @@ -7224,17 +9291,93 @@ int TC_LOG_MMAP::sync() return err; } +static void +mmap_do_checkpoint_callback(void *data) +{ + TC_LOG_MMAP::pending_cookies *pending= + static_cast<TC_LOG_MMAP::pending_cookies *>(data); + ++pending->pending_count; +} + +int TC_LOG_MMAP::unlog(ulong cookie, my_xid xid) +{ + pending_cookies *full_buffer= NULL; + uint32 ncookies= tc_log_page_size / sizeof(my_xid); + DBUG_ASSERT(*(my_xid *)(data+cookie) == xid); + + /* + Do not delete the entry immediately, as there may be participating storage + engines which implement commit_checkpoint_request(), and thus have not yet + flushed the commit durably to disk. + + Instead put it in a queue - and periodically, we will request a checkpoint + from all engines and delete a whole batch at once. + */ + mysql_mutex_lock(&LOCK_pending_checkpoint); + if (pending_checkpoint == NULL) + { + uint32 size= sizeof(*pending_checkpoint) + sizeof(ulong) * (ncookies - 1); + if (!(pending_checkpoint= + (pending_cookies *)my_malloc(size, MYF(MY_ZEROFILL)))) + { + my_error(ER_OUTOFMEMORY, MYF(0), size); + mysql_mutex_unlock(&LOCK_pending_checkpoint); + return 1; + } + } + + pending_checkpoint->cookies[pending_checkpoint->count++]= cookie; + if (pending_checkpoint->count == ncookies) + { + full_buffer= pending_checkpoint; + pending_checkpoint= NULL; + } + mysql_mutex_unlock(&LOCK_pending_checkpoint); + + if (full_buffer) + { + /* + We do an extra increment and notify here - this ensures that + things work also if there are no engines at all that support + commit_checkpoint_request. + */ + ++full_buffer->pending_count; + ha_commit_checkpoint_request(full_buffer, mmap_do_checkpoint_callback); + commit_checkpoint_notify(full_buffer); + } + return 0; +} + + +void +TC_LOG_MMAP::commit_checkpoint_notify(void *cookie) +{ + uint count; + pending_cookies *pending= static_cast<pending_cookies *>(cookie); + mysql_mutex_lock(&LOCK_pending_checkpoint); + DBUG_ASSERT(pending->pending_count > 0); + count= --pending->pending_count; + mysql_mutex_unlock(&LOCK_pending_checkpoint); + if (count == 0) + { + uint i; + for (i= 0; i < tc_log_page_size / sizeof(my_xid); ++i) + delete_entry(pending->cookies[i]); + my_free(pending); + } +} + + /** erase xid from the page, update page free space counters/pointers. cookie points directly to the memory where xid was logged. */ -int TC_LOG_MMAP::unlog(ulong cookie, my_xid xid) +int TC_LOG_MMAP::delete_entry(ulong cookie) { PAGE *p=pages+(cookie/tc_log_page_size); my_xid *x=(my_xid *)(data+cookie); - DBUG_ASSERT(*x == xid); DBUG_ASSERT(x >= p->start && x < p->end); mysql_mutex_lock(&p->lock); @@ -7258,6 +9401,7 @@ void TC_LOG_MMAP::close() mysql_mutex_destroy(&LOCK_sync); mysql_mutex_destroy(&LOCK_active); mysql_mutex_destroy(&LOCK_pool); + mysql_mutex_destroy(&LOCK_pending_checkpoint); mysql_cond_destroy(&COND_pool); mysql_cond_destroy(&COND_active); mysql_cond_destroy(&COND_queue_busy); @@ -7285,9 +9429,12 @@ void TC_LOG_MMAP::close() } if (inited>=5) // cannot do in the switch because of Windows mysql_file_delete(key_file_tclog, logname, MYF(MY_WME)); + if (pending_checkpoint) + my_free(pending_checkpoint); inited=0; } + int TC_LOG_MMAP::recover() { HASH xids; @@ -7303,12 +9450,10 @@ int TC_LOG_MMAP::recover() the first byte after magic signature is set to current number of storage engines on startup */ - if (data[sizeof(tc_log_magic)] != total_ha_2pc) + if (data[sizeof(tc_log_magic)] > total_ha_2pc) { sql_print_error("Recovery failed! You must enable " - "exactly %d storage engines that support " - "two-phase commit protocol", - data[sizeof(tc_log_magic)]); + "all engines that were enabled at the moment of the crash"); goto err1; } @@ -7373,26 +9518,13 @@ int TC_LOG::using_heuristic_recover() /****** transaction coordinator log for 2pc - binlog() based solution ******/ #define TC_LOG_BINLOG MYSQL_BIN_LOG -/** - @todo - keep in-memory list of prepared transactions - (add to list in log(), remove on unlog()) - and copy it to the new binlog if rotated - but let's check the behaviour of tc_log_page_waits first! -*/ - int TC_LOG_BINLOG::open(const char *opt_name) { - LOG_INFO log_info; int error= 1; DBUG_ASSERT(total_ha_2pc > 1); DBUG_ASSERT(opt_name && opt_name[0]); - mysql_mutex_init(key_BINLOG_LOCK_prep_xids, - &LOCK_prep_xids, MY_MUTEX_INIT_FAST); - mysql_cond_init(key_BINLOG_COND_prep_xids, &COND_prep_xids, 0); - if (!my_b_inited(&index_file)) { /* There was a failure to open the index file, can't open the binlog */ @@ -7402,78 +9534,22 @@ int TC_LOG_BINLOG::open(const char *opt_name) if (using_heuristic_recover()) { + mysql_mutex_lock(&LOCK_log); /* generate a new binlog to mask a corrupted one */ - open(opt_name, LOG_BIN, 0, WRITE_CACHE, 0, max_binlog_size, 0, TRUE); + open(opt_name, LOG_BIN, 0, 0, WRITE_CACHE, max_binlog_size, 0, TRUE); + mysql_mutex_unlock(&LOCK_log); cleanup(); return 1; } - if ((error= find_log_pos(&log_info, NullS, 1))) - { - if (error != LOG_INFO_EOF) - sql_print_error("find_log_pos() failed (error: %d)", error); - else - error= 0; - goto err; - } - - { - const char *errmsg; - IO_CACHE log; - File file; - Log_event *ev=0; - Format_description_log_event fdle(BINLOG_VERSION); - char log_name[FN_REFLEN]; - - if (! fdle.is_valid()) - goto err; - - do - { - strmake_buf(log_name, log_info.log_file_name); - } while (!(error= find_next_log(&log_info, 1))); - - if (error != LOG_INFO_EOF) - { - sql_print_error("find_log_pos() failed (error: %d)", error); - goto err; - } - - if ((file= open_binlog(&log, log_name, &errmsg)) < 0) - { - sql_print_error("%s", errmsg); - goto err; - } - - if ((ev= Log_event::read_log_event(&log, 0, &fdle, - opt_master_verify_checksum)) && - ev->get_type_code() == FORMAT_DESCRIPTION_EVENT && - ev->flags & LOG_EVENT_BINLOG_IN_USE_F) - { - sql_print_information("Recovering after a crash using %s", opt_name); - error= recover(&log, (Format_description_log_event *)ev); - } - else - error=0; - - delete ev; - end_io_cache(&log); - mysql_file_close(file, MYF(MY_WME)); - - if (error) - goto err; - } - -err: + error= do_binlog_recovery(opt_name, true); + binlog_state_recover_done= true; return error; } /** This is called on shutdown, after ha_panic. */ void TC_LOG_BINLOG::close() { - DBUG_ASSERT(prepared_xids==0); - mysql_mutex_destroy(&LOCK_prep_xids); - mysql_cond_destroy(&COND_prep_xids); } /* @@ -7490,7 +9566,10 @@ TC_LOG_BINLOG::log_and_order(THD *thd, my_xid xid, bool all, binlog_cache_mngr *cache_mngr= thd->binlog_setup_trx_data(); if (!cache_mngr) + { + WSREP_DEBUG("Skipping empty log_xid: %s", thd->query()); DBUG_RETURN(0); + } cache_mngr->using_xa= TRUE; cache_mngr->xa_xid= xid; @@ -7498,7 +9577,25 @@ TC_LOG_BINLOG::log_and_order(THD *thd, my_xid xid, bool all, DEBUG_SYNC(thd, "binlog_after_log_and_order"); - DBUG_RETURN(!err); + if (err) + DBUG_RETURN(0); + + bool need_unlog= cache_mngr->need_unlog; + /* + The transaction won't need the flag anymore. + Todo/fixme: consider to move the statement into cache_mngr->reset() + relocated to the current or later point. + */ + cache_mngr->need_unlog= false; + /* + If using explicit user XA, we will not have XID. We must still return a + non-zero cookie (as zero cookie signals error). + */ + if (!xid || !need_unlog) + DBUG_RETURN(BINLOG_COOKIE_DUMMY(cache_mngr->delayed_error)); + + DBUG_RETURN(BINLOG_COOKIE_MAKE(cache_mngr->binlog_id, + cache_mngr->delayed_error)); } /* @@ -7513,92 +9610,552 @@ TC_LOG_BINLOG::log_and_order(THD *thd, my_xid xid, bool all, binary log. */ void -TC_LOG_BINLOG::mark_xids_active(uint xid_count) +TC_LOG_BINLOG::mark_xids_active(ulong binlog_id, uint xid_count) { + xid_count_per_binlog *b; + DBUG_ENTER("TC_LOG_BINLOG::mark_xids_active"); - DBUG_PRINT("info", ("xid_count=%u", xid_count)); - mysql_mutex_lock(&LOCK_prep_xids); - prepared_xids+= xid_count; - mysql_mutex_unlock(&LOCK_prep_xids); + DBUG_PRINT("info", ("binlog_id=%lu xid_count=%u", binlog_id, xid_count)); + + mysql_mutex_lock(&LOCK_xid_list); + I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list); + while ((b= it++)) + { + if (b->binlog_id == binlog_id) + { + b->xid_count += xid_count; + break; + } + } + /* + As we do not delete elements until count reach zero, elements should always + be found. + */ + DBUG_ASSERT(b); + mysql_mutex_unlock(&LOCK_xid_list); DBUG_VOID_RETURN; } /* - Once an XID is committed, it is safe to rotate the binary log, as it can no - longer be needed during crash recovery. + Once an XID is committed, it can no longer be needed during crash recovery, + as it has been durably recorded on disk as "committed". This function is called to mark an XID this way. It needs to decrease the - count of pending XIDs, and signal the log rotator thread when it reaches zero. + count of pending XIDs in the corresponding binlog. When the count reaches + zero (for an "old" binlog that is not the active one), that binlog file no + longer need to be scanned during crash recovery, so we can log a new binlog + checkpoint. */ void -TC_LOG_BINLOG::mark_xid_done() +TC_LOG_BINLOG::mark_xid_done(ulong binlog_id, bool write_checkpoint) { - my_bool send_signal; + xid_count_per_binlog *b; + bool first; + ulong current; DBUG_ENTER("TC_LOG_BINLOG::mark_xid_done"); - mysql_mutex_lock(&LOCK_prep_xids); - // prepared_xids can be 0 if the transaction had ignorable errors. - DBUG_ASSERT(prepared_xids >= 0); - if (prepared_xids > 0) - prepared_xids--; - send_signal= (prepared_xids == 0); - mysql_mutex_unlock(&LOCK_prep_xids); - if (send_signal) { - DBUG_PRINT("info", ("prepared_xids=%lu", prepared_xids)); - mysql_cond_signal(&COND_prep_xids); + + mysql_mutex_lock(&LOCK_xid_list); + current= current_binlog_id; + I_List_iterator<xid_count_per_binlog> it(binlog_xid_count_list); + first= true; + while ((b= it++)) + { + if (b->binlog_id == binlog_id) + { + --b->xid_count; + + DBUG_ASSERT(b->xid_count >= 0); // catch unmatched (++) decrement + + break; + } + first= false; + } + /* Binlog is always found, as we do not remove until count reaches 0 */ + DBUG_ASSERT(b); + /* + If a RESET MASTER is pending, we are about to remove all log files, and + the RESET MASTER thread is waiting for all pending unlog() calls to + complete while holding LOCK_log. In this case we should not log a binlog + checkpoint event (it would be deleted immediately anyway and we would + deadlock on LOCK_log) but just signal the thread. + */ + if (unlikely(reset_master_pending)) + { + mysql_cond_broadcast(&COND_xid_list); + mysql_mutex_unlock(&LOCK_xid_list); + DBUG_VOID_RETURN; + } + + if (likely(binlog_id == current) || b->xid_count != 0 || !first || + !write_checkpoint) + { + /* No new binlog checkpoint reached yet. */ + mysql_mutex_unlock(&LOCK_xid_list); + DBUG_VOID_RETURN; + } + + /* + Now log a binlog checkpoint for the first binlog file with a non-zero count. + + Note that it is possible (though perhaps unlikely) that when count of + binlog (N-2) drops to zero, binlog (N-1) is already at zero. So we may + need to skip several entries before we find the one to log in the binlog + checkpoint event. + + We chain the locking of LOCK_xid_list and LOCK_log, so that we ensure that + Binlog_checkpoint_events are logged in order. This simplifies recovery a + bit, as it can just take the last binlog checkpoint in the log, rather + than compare all found against each other to find the one pointing to the + most recent binlog. + + Note also that we need to first release LOCK_xid_list, then aquire + LOCK_log, then re-aquire LOCK_xid_list. If we were to take LOCK_log while + holding LOCK_xid_list, we might deadlock with other threads that take the + locks in the opposite order. + */ + + ++mark_xid_done_waiting; + mysql_mutex_unlock(&LOCK_xid_list); + mysql_mutex_lock(&LOCK_log); + mysql_mutex_lock(&LOCK_xid_list); + --mark_xid_done_waiting; + mysql_cond_broadcast(&COND_xid_list); + /* We need to reload current_binlog_id due to release/re-take of lock. */ + current= current_binlog_id; + + for (;;) + { + /* Remove initial element(s) with zero count. */ + b= binlog_xid_count_list.head(); + /* + We must not remove all elements in the list - the entry for the current + binlog must be present always. + */ + DBUG_ASSERT(b); + if (b->binlog_id == current || b->xid_count > 0) + break; + WSREP_XID_LIST_ENTRY("TC_LOG_BINLOG::mark_xid_done(): Removing " + "xid_list_entry for %s (%lu)", b); + my_free(binlog_xid_count_list.get()); } + + mysql_mutex_unlock(&LOCK_xid_list); + write_binlog_checkpoint_event_already_locked(b->binlog_name, + b->binlog_name_len); + mysql_mutex_unlock(&LOCK_log); DBUG_VOID_RETURN; } int TC_LOG_BINLOG::unlog(ulong cookie, my_xid xid) { DBUG_ENTER("TC_LOG_BINLOG::unlog"); - if (xid) - mark_xid_done(); - /* As ::write_transaction_to_binlog() did not rotate, do it here. */ - DBUG_RETURN(rotate_and_purge(0)); + if (!xid) + DBUG_RETURN(0); + + if (!BINLOG_COOKIE_IS_DUMMY(cookie)) + mark_xid_done(BINLOG_COOKIE_GET_ID(cookie), true); + /* + See comment in trx_group_commit_leader() - if rotate() gave a failure, + we delay the return of error code to here. + */ + DBUG_RETURN(BINLOG_COOKIE_GET_ERROR_FLAG(cookie)); +} + +void +TC_LOG_BINLOG::commit_checkpoint_notify(void *cookie) +{ + xid_count_per_binlog *entry= static_cast<xid_count_per_binlog *>(cookie); + mysql_mutex_lock(&LOCK_binlog_background_thread); + entry->next_in_queue= binlog_background_thread_queue; + binlog_background_thread_queue= entry; + mysql_cond_signal(&COND_binlog_background_thread); + mysql_mutex_unlock(&LOCK_binlog_background_thread); +} + +/* + Binlog background thread. + + This thread is used to log binlog checkpoints in the background, rather than + in the context of random storage engine threads that happen to call + commit_checkpoint_notify_ha() and may not like the delays while syncing + binlog to disk or may not be setup with all my_thread_init() and other + necessary stuff. + + In the future, this thread could also be used to do log rotation in the + background, which could elimiate all stalls around binlog rotations. +*/ +pthread_handler_t +binlog_background_thread(void *arg __attribute__((unused))) +{ + bool stop; + MYSQL_BIN_LOG::xid_count_per_binlog *queue, *next; + THD *thd; + my_thread_init(); + DBUG_ENTER("binlog_background_thread"); + + thd= new THD; + thd->system_thread= SYSTEM_THREAD_BINLOG_BACKGROUND; + thd->thread_stack= (char*) &thd; /* Set approximate stack start */ + mysql_mutex_lock(&LOCK_thread_count); + thd->thread_id= thread_id++; + mysql_mutex_unlock(&LOCK_thread_count); + thd->store_globals(); + thd->security_ctx->skip_grants(); + thd->set_command(COM_DAEMON); + + /* + Load the slave replication GTID state from the mysql.gtid_slave_pos + table. + + This is mostly so that we can start our seq_no counter from the highest + seq_no seen by a slave. This way, we have a way to tell if a transaction + logged by ourselves as master is newer or older than a replicated + transaction. + */ +#ifdef HAVE_REPLICATION + if (rpl_load_gtid_slave_state(thd)) + sql_print_warning("Failed to load slave replication state from table " + "%s.%s: %u: %s", "mysql", + rpl_gtid_slave_state_table_name.str, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); +#endif + + mysql_mutex_lock(&mysql_bin_log.LOCK_binlog_background_thread); + binlog_background_thread_started= true; + mysql_cond_signal(&mysql_bin_log.COND_binlog_background_thread_end); + mysql_mutex_unlock(&mysql_bin_log.LOCK_binlog_background_thread); + + for (;;) + { + /* + Wait until there is something in the queue to process, or we are asked + to shut down. + */ + THD_STAGE_INFO(thd, stage_binlog_waiting_background_tasks); + mysql_mutex_lock(&mysql_bin_log.LOCK_binlog_background_thread); + for (;;) + { + stop= binlog_background_thread_stop; + queue= binlog_background_thread_queue; + if (stop && !mysql_bin_log.is_xidlist_idle()) + { + /* + Delay stop until all pending binlog checkpoints have been processed. + */ + stop= false; + } + if (stop || queue) + break; + mysql_cond_wait(&mysql_bin_log.COND_binlog_background_thread, + &mysql_bin_log.LOCK_binlog_background_thread); + } + /* Grab the queue, if any. */ + binlog_background_thread_queue= NULL; + mysql_mutex_unlock(&mysql_bin_log.LOCK_binlog_background_thread); + + /* Process any incoming commit_checkpoint_notify() calls. */ + DBUG_EXECUTE_IF("inject_binlog_background_thread_before_mark_xid_done", + DBUG_ASSERT(!debug_sync_set_action( + thd, + STRING_WITH_LEN("binlog_background_thread_before_mark_xid_done " + "SIGNAL injected_binlog_background_thread " + "WAIT_FOR something_that_will_never_happen " + "TIMEOUT 2"))); + ); + while (queue) + { + THD_STAGE_INFO(thd, stage_binlog_processing_checkpoint_notify); + DEBUG_SYNC(thd, "binlog_background_thread_before_mark_xid_done"); + /* Set the thread start time */ + thd->set_time(); + /* Grab next pointer first, as mark_xid_done() may free the element. */ + next= queue->next_in_queue; + mysql_bin_log.mark_xid_done(queue->binlog_id, true); + queue= next; + + DBUG_EXECUTE_IF("binlog_background_checkpoint_processed", + DBUG_ASSERT(!debug_sync_set_action( + thd, + STRING_WITH_LEN("now SIGNAL binlog_background_checkpoint_processed"))); + ); + } + + if (stop) + break; + } + + THD_STAGE_INFO(thd, stage_binlog_stopping_background_thread); + + delete thd; + + my_thread_end(); + + /* Signal that we are (almost) stopped. */ + mysql_mutex_lock(&mysql_bin_log.LOCK_binlog_background_thread); + binlog_background_thread_stop= false; + mysql_cond_signal(&mysql_bin_log.COND_binlog_background_thread_end); + mysql_mutex_unlock(&mysql_bin_log.LOCK_binlog_background_thread); + + DBUG_RETURN(0); +} + +#ifdef HAVE_PSI_INTERFACE +static PSI_thread_key key_thread_binlog; + +static PSI_thread_info all_binlog_threads[]= +{ + { &key_thread_binlog, "binlog_background", PSI_FLAG_GLOBAL}, +}; +#endif /* HAVE_PSI_INTERFACE */ + +static bool +start_binlog_background_thread() +{ + pthread_t th; + +#ifdef HAVE_PSI_INTERFACE + if (PSI_server) + PSI_server->register_thread("sql", all_binlog_threads, + array_elements(all_binlog_threads)); +#endif + + if (mysql_thread_create(key_thread_binlog, &th, &connection_attrib, + binlog_background_thread, NULL)) + return 1; + + /* + Wait for the thread to have started (so we know that the slave replication + state is loaded and we have correct global_gtid_counter). + */ + mysql_mutex_lock(&mysql_bin_log.LOCK_binlog_background_thread); + while (!binlog_background_thread_started) + mysql_cond_wait(&mysql_bin_log.COND_binlog_background_thread_end, + &mysql_bin_log.LOCK_binlog_background_thread); + mysql_mutex_unlock(&mysql_bin_log.LOCK_binlog_background_thread); + + return 0; } -int TC_LOG_BINLOG::recover(IO_CACHE *log, Format_description_log_event *fdle) + +int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, + IO_CACHE *first_log, + Format_description_log_event *fdle, bool do_xa) { - Log_event *ev; + Log_event *ev= NULL; HASH xids; MEM_ROOT mem_root; + char binlog_checkpoint_name[FN_REFLEN]; + bool binlog_checkpoint_found; + bool first_round; + IO_CACHE log; + File file= -1; + const char *errmsg; +#ifdef HAVE_REPLICATION + rpl_gtid last_gtid; + bool last_gtid_standalone= false; + bool last_gtid_valid= false; +#endif if (! fdle->is_valid() || - my_hash_init(&xids, &my_charset_bin, TC_LOG_PAGE_SIZE/3, 0, - sizeof(my_xid), 0, 0, MYF(0))) + (do_xa && my_hash_init(&xids, &my_charset_bin, TC_LOG_PAGE_SIZE/3, 0, + sizeof(my_xid), 0, 0, MYF(0)))) goto err1; - init_alloc_root(&mem_root, TC_LOG_PAGE_SIZE, TC_LOG_PAGE_SIZE); + if (do_xa) + init_alloc_root(&mem_root, TC_LOG_PAGE_SIZE, TC_LOG_PAGE_SIZE, MYF(0)); fdle->flags&= ~LOG_EVENT_BINLOG_IN_USE_F; // abort on the first error - while ((ev= Log_event::read_log_event(log, 0, fdle, - opt_master_verify_checksum)) - && ev->is_valid()) + /* + Scan the binlog for XIDs that need to be committed if still in the + prepared stage. + + Start with the latest binlog file, then continue with any other binlog + files if the last found binlog checkpoint indicates it is needed. + */ + + binlog_checkpoint_found= false; + first_round= true; + for (;;) { - if (ev->get_type_code() == XID_EVENT) + while ((ev= Log_event::read_log_event(first_round ? first_log : &log, + 0, fdle, opt_master_verify_checksum)) + && ev->is_valid()) { - Xid_log_event *xev=(Xid_log_event *)ev; - uchar *x= (uchar *) memdup_root(&mem_root, (uchar*) &xev->xid, - sizeof(xev->xid)); - if (!x || my_hash_insert(&xids, x)) + enum Log_event_type typ= ev->get_type_code(); + switch (typ) + { + case XID_EVENT: + { + if (do_xa) + { + Xid_log_event *xev=(Xid_log_event *)ev; + uchar *x= (uchar *) memdup_root(&mem_root, (uchar*) &xev->xid, + sizeof(xev->xid)); + if (!x || my_hash_insert(&xids, x)) + goto err2; + } + break; + } + case BINLOG_CHECKPOINT_EVENT: + if (first_round && do_xa) + { + uint dir_len; + Binlog_checkpoint_log_event *cev= (Binlog_checkpoint_log_event *)ev; + if (cev->binlog_file_len >= FN_REFLEN) + sql_print_warning("Incorrect binlog checkpoint event with too " + "long file name found."); + else + { + /* + Note that we cannot use make_log_name() here, as we have not yet + initialised MYSQL_BIN_LOG::log_file_name. + */ + dir_len= dirname_length(last_log_name); + strmake(strnmov(binlog_checkpoint_name, last_log_name, dir_len), + cev->binlog_file_name, FN_REFLEN - 1 - dir_len); + binlog_checkpoint_found= true; + } + } + break; + case GTID_LIST_EVENT: + if (first_round) + { + Gtid_list_log_event *glev= (Gtid_list_log_event *)ev; + + /* Initialise the binlog state from the Gtid_list event. */ + if (rpl_global_gtid_binlog_state.load(glev->list, glev->count)) + goto err2; + } + break; + +#ifdef HAVE_REPLICATION + case GTID_EVENT: + if (first_round) + { + Gtid_log_event *gev= (Gtid_log_event *)ev; + + /* Update the binlog state with any GTID logged after Gtid_list. */ + last_gtid.domain_id= gev->domain_id; + last_gtid.server_id= gev->server_id; + last_gtid.seq_no= gev->seq_no; + last_gtid_standalone= + ((gev->flags2 & Gtid_log_event::FL_STANDALONE) ? true : false); + last_gtid_valid= true; + } + break; +#endif + + case START_ENCRYPTION_EVENT: + { + if (fdle->start_decryption((Start_encryption_log_event*) ev)) + goto err2; + } + break; + + default: + /* Nothing. */ + break; + } + +#ifdef HAVE_REPLICATION + if (last_gtid_valid && + ((last_gtid_standalone && !ev->is_part_of_group(typ)) || + (!last_gtid_standalone && + (typ == XID_EVENT || + (typ == QUERY_EVENT && + (((Query_log_event *)ev)->is_commit() || + ((Query_log_event *)ev)->is_rollback())))))) + { + if (rpl_global_gtid_binlog_state.update_nolock(&last_gtid, false)) + goto err2; + last_gtid_valid= false; + } +#endif + + delete ev; + ev= NULL; + } + + if (!do_xa) + break; + /* + If the last binlog checkpoint event points to an older log, we have to + scan all logs from there also, to get all possible XIDs to recover. + + If there was no binlog checkpoint event at all, this means the log was + written by an older version of MariaDB (or MySQL) - these always have an + (implicit) binlog checkpoint event at the start of the last binlog file. + */ + if (first_round) + { + if (!binlog_checkpoint_found) + break; + first_round= false; + DBUG_EXECUTE_IF("xa_recover_expect_master_bin_000004", + if (0 != strcmp("./master-bin.000004", binlog_checkpoint_name) && + 0 != strcmp(".\\master-bin.000004", binlog_checkpoint_name)) + DBUG_SUICIDE(); + ); + if (find_log_pos(linfo, binlog_checkpoint_name, 1)) + { + sql_print_error("Binlog file '%s' not found in binlog index, needed " + "for recovery. Aborting.", binlog_checkpoint_name); goto err2; + } } - delete ev; + else + { + end_io_cache(&log); + mysql_file_close(file, MYF(MY_WME)); + file= -1; + } + + if (!strcmp(linfo->log_file_name, last_log_name)) + break; // No more files to do + if ((file= open_binlog(&log, linfo->log_file_name, &errmsg)) < 0) + { + sql_print_error("%s", errmsg); + goto err2; + } + /* + We do not need to read the Format_description_log_event of other binlog + files. It is not possible for a binlog checkpoint to span multiple + binlog files written by different versions of the server. So we can use + the first one read for reading from all binlog files. + */ + if (find_next_log(linfo, 1)) + { + sql_print_error("Error reading binlog files during recovery. Aborting."); + goto err2; + } + fdle->reset_crypto(); } - if (ha_recover(&xids)) - goto err2; + if (do_xa) + { + if (ha_recover(&xids)) + goto err2; - free_root(&mem_root, MYF(0)); - my_hash_free(&xids); + free_root(&mem_root, MYF(0)); + my_hash_free(&xids); + } return 0; err2: - free_root(&mem_root, MYF(0)); - my_hash_free(&xids); + delete ev; + if (file >= 0) + { + end_io_cache(&log); + mysql_file_close(file, MYF(MY_WME)); + } + if (do_xa) + { + free_root(&mem_root, MYF(0)); + my_hash_free(&xids); + } err1: sql_print_error("Crash recovery failed. Either correct the problem " "(if it's, for example, out of memory error) and restart, " @@ -7608,6 +10165,110 @@ err1: } +int +MYSQL_BIN_LOG::do_binlog_recovery(const char *opt_name, bool do_xa_recovery) +{ + LOG_INFO log_info; + const char *errmsg; + IO_CACHE log; + File file; + Log_event *ev= 0; + Format_description_log_event fdle(BINLOG_VERSION); + char log_name[FN_REFLEN]; + int error; + + if ((error= find_log_pos(&log_info, NullS, 1))) + { + /* + If there are no binlog files (LOG_INFO_EOF), then we still try to read + the .state file to restore the binlog state. This allows to copy a server + to provision a new one without copying the binlog files (except the + master-bin.state file) and still preserve the correct binlog state. + */ + if (error != LOG_INFO_EOF) + sql_print_error("find_log_pos() failed (error: %d)", error); + else + { + error= read_state_from_file(); + if (error == 2) + { + /* + No binlog files and no binlog state is not an error (eg. just initial + server start after fresh installation). + */ + error= 0; + } + } + return error; + } + + if (! fdle.is_valid()) + return 1; + + do + { + strmake_buf(log_name, log_info.log_file_name); + } while (!(error= find_next_log(&log_info, 1))); + + if (error != LOG_INFO_EOF) + { + sql_print_error("find_log_pos() failed (error: %d)", error); + return error; + } + + if ((file= open_binlog(&log, log_name, &errmsg)) < 0) + { + sql_print_error("%s", errmsg); + return 1; + } + + if ((ev= Log_event::read_log_event(&log, 0, &fdle, + opt_master_verify_checksum)) && + ev->get_type_code() == FORMAT_DESCRIPTION_EVENT) + { + if (ev->flags & LOG_EVENT_BINLOG_IN_USE_F) + { + sql_print_information("Recovering after a crash using %s", opt_name); + error= recover(&log_info, log_name, &log, + (Format_description_log_event *)ev, do_xa_recovery); + } + else + { + error= read_state_from_file(); + if (error == 2) + { + /* + The binlog exists, but the .state file is missing. This is normal if + this is the first master start after a major upgrade to 10.0 (with + GTID support). + + However, it could also be that the .state file was lost somehow, and + in this case it could be a serious issue, as we would set the wrong + binlog state in the next binlog file to be created, and GTID + processing would be corrupted. A common way would be copying files + from an old server to a new one and forgetting the .state file. + + So in this case, we want to try to recover the binlog state by + scanning the last binlog file (but we do not need any XA recovery). + + ToDo: We could avoid one scan at first start after major upgrade, by + detecting that there is no GTID_LIST event at the start of the + binlog file, and stopping the scan in that case. + */ + error= recover(&log_info, log_name, &log, + (Format_description_log_event *)ev, false); + } + } + } + + delete ev; + end_io_cache(&log); + mysql_file_close(file, MYF(MY_WME)); + + return error; +} + + #ifdef INNODB_COMPATIBILITY_HOOKS /** Get the file name of the MySQL binlog. @@ -7662,12 +10323,14 @@ binlog_checksum_update(MYSQL_THD thd, struct st_mysql_sys_var *var, { ulong value= *((ulong *)save); bool check_purge= false; + ulong UNINIT_VAR(prev_binlog_id); mysql_mutex_lock(mysql_bin_log.get_log_lock()); if(mysql_bin_log.is_open()) { + prev_binlog_id= mysql_bin_log.current_binlog_id; if (binlog_checksum_options != value) - mysql_bin_log.checksum_alg_reset= (uint8) value; + mysql_bin_log.checksum_alg_reset= (enum_binlog_checksum_alg)value; if (mysql_bin_log.rotate(true, &check_purge)) check_purge= false; } @@ -7679,7 +10342,7 @@ binlog_checksum_update(MYSQL_THD thd, struct st_mysql_sys_var *var, mysql_bin_log.checksum_alg_reset= BINLOG_CHECKSUM_ALG_UNDEF; mysql_mutex_unlock(mysql_bin_log.get_log_lock()); if (check_purge) - mysql_bin_log.purge(); + mysql_bin_log.checkpoint_and_purge(prev_binlog_id); } @@ -7714,8 +10377,7 @@ static MYSQL_SYSVAR_ENUM( binlog_checksum_options, PLUGIN_VAR_RQCMDARG, "Type of BINLOG_CHECKSUM_ALG. Include checksum for " - "log events in the binary log. Possible values are NONE and CRC32; " - "default is NONE.", + "log events in the binary log", NULL, binlog_checksum_update, BINLOG_CHECKSUM_ALG_OFF, @@ -7745,7 +10407,7 @@ set_binlog_snapshot_file(const char *src) Copy out current values of status variables, for SHOW STATUS or information_schema.global_status. - This is called only under LOCK_status, so we can fill in a static array. + This is called only under LOCK_show_status, so we can fill in a static array. */ void TC_LOG_BINLOG::set_status_variables(THD *thd) @@ -7767,6 +10429,11 @@ TC_LOG_BINLOG::set_status_variables(THD *thd) binlog_snapshot_position= last_commit_pos_offset; } mysql_mutex_unlock(&LOCK_commit_ordered); + mysql_mutex_lock(&LOCK_prepare_ordered); + binlog_status_group_commit_trigger_count= this->group_commit_trigger_count; + binlog_status_group_commit_trigger_timeout= this->group_commit_trigger_timeout; + binlog_status_group_commit_trigger_lock_wait= this->group_commit_trigger_lock_wait; + mysql_mutex_unlock(&LOCK_prepare_ordered); if (have_snapshot) { @@ -7775,6 +10442,73 @@ TC_LOG_BINLOG::set_status_variables(THD *thd) } } + +/* + Find the Gtid_list_log_event at the start of a binlog. + + NULL for ok, non-NULL error message for error. + + If ok, then the event is returned in *out_gtid_list. This can be NULL if we + get back to binlogs written by old server version without GTID support. If + so, it means we have reached the point to start from, as no GTID events can + exist in earlier binlogs. +*/ +const char * +get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list) +{ + Format_description_log_event init_fdle(BINLOG_VERSION); + Format_description_log_event *fdle; + Log_event *ev; + const char *errormsg = NULL; + + *out_gtid_list= NULL; + + if (!(ev= Log_event::read_log_event(cache, 0, &init_fdle, + opt_master_verify_checksum)) || + ev->get_type_code() != FORMAT_DESCRIPTION_EVENT) + { + if (ev) + delete ev; + return "Could not read format description log event while looking for " + "GTID position in binlog"; + } + + fdle= static_cast<Format_description_log_event *>(ev); + + for (;;) + { + Log_event_type typ; + + ev= Log_event::read_log_event(cache, 0, fdle, opt_master_verify_checksum); + if (!ev) + { + errormsg= "Could not read GTID list event while looking for GTID " + "position in binlog"; + break; + } + typ= ev->get_type_code(); + if (typ == GTID_LIST_EVENT) + break; /* Done, found it */ + if (typ == START_ENCRYPTION_EVENT) + { + if (fdle->start_decryption((Start_encryption_log_event*) ev)) + errormsg= "Could not set up decryption for binlog."; + } + delete ev; + if (typ == ROTATE_EVENT || typ == STOP_EVENT || + typ == FORMAT_DESCRIPTION_EVENT || typ == START_ENCRYPTION_EVENT) + continue; /* Continue looking */ + + /* We did not find any Gtid_list_log_event, must be old binlog. */ + ev= NULL; + break; + } + + delete fdle; + *out_gtid_list= static_cast<Gtid_list_log_event *>(ev); + return errormsg; +} + struct st_mysql_storage_engine binlog_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; @@ -7795,3 +10529,58 @@ maria_declare_plugin(binlog) MariaDB_PLUGIN_MATURITY_STABLE /* maturity */ } maria_declare_plugin_end; + +#ifdef WITH_WSREP +IO_CACHE * get_trans_log(THD * thd) +{ + DBUG_ASSERT(binlog_hton->slot != HA_SLOT_UNDEF); + binlog_cache_mngr *cache_mngr = (binlog_cache_mngr*) + thd_get_ha_data(thd, binlog_hton); + if (cache_mngr) + return cache_mngr->get_binlog_cache_log(true); + + WSREP_DEBUG("binlog cache not initialized, conn :%ld", thd->thread_id); + return NULL; +} + + +bool wsrep_trans_cache_is_empty(THD *thd) +{ + binlog_cache_mngr *const cache_mngr= + (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton); + return (!cache_mngr || cache_mngr->trx_cache.empty()); +} + + +void thd_binlog_trx_reset(THD * thd) +{ + /* + todo: fix autocommit select to not call the caller + */ + if (thd_get_ha_data(thd, binlog_hton) != NULL) + { + binlog_cache_mngr *const cache_mngr= + (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton); + if (cache_mngr) + { + cache_mngr->reset(false, true); + if (!cache_mngr->stmt_cache.empty()) + { + WSREP_DEBUG("pending events in stmt cache, sql: %s", thd->query()); + cache_mngr->stmt_cache.reset(); + } + } + } + thd->clear_binlog_table_maps(); +} + + +void thd_binlog_rollback_stmt(THD * thd) +{ + WSREP_DEBUG("thd_binlog_rollback_stmt :%ld", thd->thread_id); + binlog_cache_mngr *const cache_mngr= + (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton); + if (cache_mngr) + cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF); +} +#endif /* WITH_WSREP */ diff --git a/sql/log.h b/sql/log.h index c6e5c135b25..2118bd7a059 100644 --- a/sql/log.h +++ b/sql/log.h @@ -17,8 +17,10 @@ #ifndef LOG_H #define LOG_H -#include "unireg.h" // REQUIRED: for other includes #include "handler.h" /* my_xid */ +#include "wsrep.h" +#include "wsrep_mysqld.h" +#include "rpl_constants.h" class Relay_log_info; @@ -45,10 +47,20 @@ class TC_LOG virtual int open(const char *opt_name)=0; virtual void close()=0; + /* + Transaction coordinator 2-phase commit. + + Must invoke the run_prepare_ordered and run_commit_ordered methods, as + described below for these methods. + + In addition, must invoke THD::wait_for_prior_commit(), or equivalent + wait, to ensure that one commit waits for another if registered to do so. + */ virtual int log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered) = 0; virtual int unlog(ulong cookie, my_xid xid)=0; + virtual void commit_checkpoint_notify(void *cookie)= 0; protected: /* @@ -75,9 +87,13 @@ protected: prepare_ordered() or commit_ordered() methods. */ extern mysql_mutex_t LOCK_prepare_ordered; +extern mysql_cond_t COND_prepare_ordered; +extern mysql_mutex_t LOCK_after_binlog_sync; extern mysql_mutex_t LOCK_commit_ordered; #ifdef HAVE_PSI_INTERFACE extern PSI_mutex_key key_LOCK_prepare_ordered, key_LOCK_commit_ordered; +extern PSI_mutex_key key_LOCK_after_binlog_sync; +extern PSI_cond_key key_COND_prepare_ordered; #endif class TC_LOG_DUMMY: public TC_LOG // use it to disable the logging @@ -94,12 +110,15 @@ public: int log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered) { - DBUG_ASSERT(0 /* Internal error - TC_LOG_DUMMY::log_and_order() called */); + DBUG_ASSERT(0); return 1; } int unlog(ulong cookie, my_xid xid) { return 0; } + void commit_checkpoint_notify(void *cookie) { DBUG_ASSERT(0); }; }; +#define TC_LOG_PAGE_SIZE 8192 + #ifdef HAVE_MMAP class TC_LOG_MMAP: public TC_LOG { @@ -110,6 +129,12 @@ class TC_LOG_MMAP: public TC_LOG PS_DIRTY // new xids added since last sync } PAGE_STATE; + struct pending_cookies { + uint count; + uint pending_count; + ulong cookies[1]; + }; + private: typedef struct st_page { struct st_page *next; // page a linked in a fifo queue @@ -141,7 +166,7 @@ class TC_LOG_MMAP: public TC_LOG one has to use active->lock. Same for LOCK_pool and LOCK_sync */ - mysql_mutex_t LOCK_active, LOCK_pool, LOCK_sync; + mysql_mutex_t LOCK_active, LOCK_pool, LOCK_sync, LOCK_pending_checkpoint; mysql_cond_t COND_pool, COND_active; /* Queue of threads that need to call commit_ordered(). @@ -163,14 +188,16 @@ class TC_LOG_MMAP: public TC_LOG */ mysql_cond_t COND_queue_busy; my_bool commit_ordered_queue_busy; + pending_cookies* pending_checkpoint; public: - TC_LOG_MMAP(): inited(0) {} + TC_LOG_MMAP(): inited(0), pending_checkpoint(0) {} int open(const char *opt_name); void close(); int log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered); int unlog(ulong cookie, my_xid xid); + void commit_checkpoint_notify(void *cookie); int recover(); private: @@ -178,6 +205,7 @@ class TC_LOG_MMAP: public TC_LOG void get_active_from_pool(); int sync(); int overflow(); + int delete_entry(ulong cookie); }; #else #define TC_LOG_MMAP TC_LOG_DUMMY @@ -285,19 +313,22 @@ public: #endif const char *log_name, enum_log_type log_type, - const char *new_name, + const char *new_name, ulong next_file_number, enum cache_type io_cache_type_arg); bool init_and_set_log_file_name(const char *log_name, const char *new_name, + ulong next_log_number, enum_log_type log_type_arg, enum cache_type io_cache_type_arg); void init(enum_log_type log_type_arg, enum cache_type io_cache_type_arg); void close(uint exiting); inline bool is_open() { return log_state != LOG_CLOSED; } - const char *generate_name(const char *log_name, const char *suffix, + const char *generate_name(const char *log_name, + const char *suffix, bool strip_ext, char *buff); - int generate_new_name(char *new_name, const char *log_name); + int generate_new_name(char *new_name, const char *log_name, + ulong next_log_number); protected: /* LOCK_log is inited by init_pthread_objects() */ mysql_mutex_t LOCK_log; @@ -314,6 +345,7 @@ public: /** Instrumentation key to use for file io in @c log_file */ PSI_file_key m_log_file_key; #endif + /* for documentation of mutexes held in various places in code */ }; class MYSQL_QUERY_LOG: public MYSQL_LOG @@ -337,7 +369,7 @@ public: key_file_slow_log, #endif generate_name(log_name, "-slow.log", 0, buf), - LOG_NORMAL, 0, WRITE_CACHE); + LOG_NORMAL, 0, 0, WRITE_CACHE); } bool open_query_log(const char *log_name) { @@ -347,14 +379,43 @@ public: key_file_query_log, #endif generate_name(log_name, ".log", 0, buf), - LOG_NORMAL, 0, WRITE_CACHE); + LOG_NORMAL, 0, 0, WRITE_CACHE); } private: time_t last_time; }; +/* + We assign each binlog file an internal ID, used to identify them for unlog(). + The IDs start from 0 and increment for each new binlog created. + + In unlog() we need to know the ID of the binlog file that the corresponding + transaction was written into. We also need a special value for a corner + case where there is no corresponding binlog id (since nothing was logged). + And we need an error flag to mark that unlog() must return failure. + + We use the following macros to pack all of this information into the single + ulong available with log_and_order() / unlog(). + + Note that we cannot use the value 0 for cookie, as that is reserved as error + return value from log_and_order(). + */ +#define BINLOG_COOKIE_ERROR_RETURN 0 +#define BINLOG_COOKIE_DUMMY_ID 1 +#define BINLOG_COOKIE_BASE 2 +#define BINLOG_COOKIE_DUMMY(error_flag) \ + ( (BINLOG_COOKIE_DUMMY_ID<<1) | ((error_flag)&1) ) +#define BINLOG_COOKIE_MAKE(id, error_flag) \ + ( (((id)+BINLOG_COOKIE_BASE)<<1) | ((error_flag)&1) ) +#define BINLOG_COOKIE_GET_ERROR_FLAG(c) ((c) & 1) +#define BINLOG_COOKIE_GET_ID(c) ( ((ulong)(c)>>1) - BINLOG_COOKIE_BASE ) +#define BINLOG_COOKIE_IS_DUMMY(c) \ + ( ((ulong)(c)>>1) == BINLOG_COOKIE_DUMMY_ID ) + class binlog_cache_mngr; +struct rpl_gtid; +struct wait_for_commit; class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG { private: @@ -379,11 +440,10 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG bool using_stmt_cache; bool using_trx_cache; /* - Extra events (BEGIN, COMMIT/ROLLBACK/XID, and possibly INCIDENT) to be + Extra events (COMMIT/ROLLBACK/XID, and possibly INCIDENT) to be written during group commit. The incident_event is only valid if trx_data->has_incident() is true. */ - Log_event *begin_event; Log_event *end_event; Log_event *incident_event; /* Set during group commit to record any per-thread error. */ @@ -392,12 +452,39 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG IO_CACHE *error_cache; /* This is the `all' parameter for ha_commit_ordered(). */ bool all; + /* + True if we need to increment xid_count in trx_group_commit_leader() and + decrement in unlog() (this is needed if there is a participating engine + that does not implement the commit_checkpoint_request() handlerton + method). + */ + bool need_unlog; + /* + Fields used to pass the necessary information to the last thread in a + group commit, only used when opt_optimize_thread_scheduling is not set. + */ + bool check_purge; + /* Flag used to optimise around wait_for_prior_commit. */ + bool queued_by_other; + ulong binlog_id; }; + /* + When this is set, a RESET MASTER is in progress. + + Then we should not write any binlog checkpoints into the binlog (that + could result in deadlock on LOCK_log, and we will delete all binlog files + anyway). Instead we should signal COND_xid_list whenever a new binlog + checkpoint arrives - when all have arrived, RESET MASTER will complete. + */ + uint reset_master_pending; + ulong mark_xid_done_waiting; + /* LOCK_log and LOCK_index are inited by init_pthread_objects() */ mysql_mutex_t LOCK_index; - mysql_mutex_t LOCK_prep_xids; - mysql_cond_t COND_prep_xids; + mysql_mutex_t LOCK_binlog_end_pos; + mysql_mutex_t LOCK_xid_list; + mysql_cond_t COND_xid_list; mysql_cond_t update_cond; ulonglong bytes_written; IO_CACHE index_file; @@ -414,27 +501,14 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG The max size before rotation (usable only if log_type == LOG_BIN: binary logs and relay logs). For a binlog, max_size should be max_binlog_size. - For a relay log, it should be max_relay_log_size if this is non-zero, - max_binlog_size otherwise. max_size is set in init(), and dynamically changed (when one does SET - GLOBAL MAX_BINLOG_SIZE|MAX_RELAY_LOG_SIZE) by fix_max_binlog_size and - fix_max_relay_log_size). + GLOBAL MAX_BINLOG_SIZE|MAX_RELAY_LOG_SIZE) from sys_vars.cc */ ulong max_size; - long prepared_xids; /* for tc log - number of xids to remember */ // current file sequence number for load data infile binary logging uint file_id; uint open_count; // For replication int readers_count; - bool need_start_event; - /* - no_auto_events means we don't want any of these automatic events : - Start/Rotate/Stop. That is, in 4.x when we rotate a relay log, we don't - want a Rotate_log event to be written to the relay log. When we start a - relay log etc. So in 4.x this is 1 for relay logs, 0 for binlogs. - In 5.0 it's 0 for relay logs too! - */ - bool no_auto_events; /* Queue of transactions queued up to participate in group commit. */ group_commit_entry *group_commit_queue; /* @@ -449,6 +523,12 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG ulonglong num_commits; /* Number of group commits done. */ ulonglong num_group_commits; + /* The reason why the group commit was grouped */ + ulonglong group_commit_trigger_count, group_commit_trigger_timeout; + ulonglong group_commit_trigger_lock_wait; + + /* binlog encryption data */ + struct Binlog_crypt_data crypto; /* pointer to the sync period variable, for binlog this will be sync_binlog_period, for relay log this will be @@ -456,6 +536,8 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG */ uint *sync_period_ptr; uint sync_counter; + bool state_file_deleted; + bool binlog_state_recover_done; inline uint get_sync_period() { @@ -470,20 +552,55 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG */ int new_file_without_locking(); int new_file_impl(bool need_lock); - int write_transaction_or_stmt(group_commit_entry *entry); + void do_checkpoint_request(ulong binlog_id); + void purge(); + int write_transaction_or_stmt(group_commit_entry *entry, uint64 commit_id); + int queue_for_group_commit(group_commit_entry *entry); bool write_transaction_to_binlog_events(group_commit_entry *entry); void trx_group_commit_leader(group_commit_entry *leader); - void mark_xid_done(); - void mark_xids_active(uint xid_count); - + bool is_xidlist_idle_nolock(); +#ifdef WITH_WSREP + /* + When this mariadb node is slave and galera enabled. So in this case + we write the gtid in wsrep_run_commit itself. + */ + inline bool is_gtid_cached(THD *thd); +#endif public: + /* + A list of struct xid_count_per_binlog is used to keep track of how many + XIDs are in prepared, but not committed, state in each binlog. And how + many commit_checkpoint_request()'s are pending. + + When count drops to zero in a binlog after rotation, it means that there + are no more XIDs in prepared state, so that binlog is no longer needed + for XA crash recovery, and we can log a new binlog checkpoint event. + + The list is protected against simultaneous access from multiple + threads by LOCK_xid_list. + */ + struct xid_count_per_binlog : public ilink { + char *binlog_name; + uint binlog_name_len; + ulong binlog_id; + /* Total prepared XIDs and pending checkpoint requests in this binlog. */ + long xid_count; + /* For linking in requests to the binlog background thread. */ + xid_count_per_binlog *next_in_queue; + xid_count_per_binlog(); /* Give link error if constructor used. */ + }; + I_List<xid_count_per_binlog> binlog_xid_count_list; + mysql_mutex_t LOCK_binlog_background_thread; + mysql_cond_t COND_binlog_background_thread; + mysql_cond_t COND_binlog_background_thread_end; + using MYSQL_LOG::generate_name; using MYSQL_LOG::is_open; /* This is relay log */ bool is_relay_log; ulong signal_cnt; // update of the counter is checked by heartbeat - uint8 checksum_alg_reset; // to contain a new value when binlog is rotated + enum enum_binlog_checksum_alg checksum_alg_reset; // to contain a new value when binlog is rotated /* Holds the last seen in Relay-Log FD's checksum alg value. The initial value comes from the slave's local FD that heads @@ -517,7 +634,7 @@ public: (A) - checksum algorithm descriptor value FD.(A) - the value of (A) in FD */ - uint8 relay_log_checksum_alg; + enum enum_binlog_checksum_alg relay_log_checksum_alg; /* These describe the log's format. This is used only for relay logs. _for_exec is used by the SQL thread, _for_queue by the I/O thread. It's @@ -534,6 +651,7 @@ public: */ char last_commit_pos_file[FN_REFLEN]; my_off_t last_commit_pos_offset; + ulong current_binlog_id; MYSQL_BIN_LOG(uint *sync_period); /* @@ -562,7 +680,10 @@ public: int log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered); int unlog(ulong cookie, my_xid xid); - int recover(IO_CACHE *log, Format_description_log_event *fdle); + void commit_checkpoint_notify(void *cookie); + int recover(LOG_INFO *linfo, const char *last_log_name, IO_CACHE *first_log, + Format_description_log_event *fdle, bool do_xa); + int do_binlog_recovery(const char *opt_name, bool do_xa_recovery); #if !defined(MYSQL_CLIENT) int flush_and_set_pending_rows_event(THD *thd, Rows_log_event* event, @@ -588,17 +709,19 @@ public: } void set_max_size(ulong max_size_arg); void signal_update(); + void wait_for_sufficient_commits(); + void binlog_trigger_immediate_group_commit(); void wait_for_update_relay_log(THD* thd); int wait_for_update_bin_log(THD* thd, const struct timespec * timeout); - void set_need_start_event() { need_start_event = 1; } - void init(bool no_auto_events_arg, ulong max_size); + void init(ulong max_size); void init_pthread_objects(); void cleanup(); bool open(const char *log_name, enum_log_type log_type, const char *new_name, + ulong next_log_number, enum cache_type io_cache_type_arg, - bool no_auto_events_arg, ulong max_size, + ulong max_size, bool null_created, bool need_mutex); bool open_index_file(const char *index_file_name_arg, @@ -614,6 +737,7 @@ public: bool write_incident_already_locked(THD *thd); bool write_incident(THD *thd); + void write_binlog_checkpoint_event_already_locked(const char *name, uint len); int write_cache(THD *thd, IO_CACHE *cache); void set_write_error(THD *thd, bool is_transactional); bool check_write_error(THD *thd); @@ -622,19 +746,22 @@ public: void stop_union_events(THD *thd); bool is_query_in_union(THD *thd, query_id_t query_id_param); - /* - v stands for vector - invoked as appendv(buf1,len1,buf2,len2,...,bufn,lenn,0) - */ - bool appendv(const char* buf,uint len,...); + bool write_event(Log_event *ev, IO_CACHE *file); + bool write_event(Log_event *ev) { return write_event(ev, &log_file); } + + bool write_event_buffer(uchar* buf,uint len); bool append(Log_event* ev); + bool append_no_lock(Log_event* ev); + void mark_xids_active(ulong cookie, uint xid_count); + void mark_xid_done(ulong cookie, bool write_checkpoint); void make_log_name(char* buf, const char* log_ident); bool is_active(const char* log_file_name); + bool can_purge_log(const char *log_file_name); int update_log_index(LOG_INFO* linfo, bool need_update_threads); int rotate(bool force_rotate, bool* check_purge); - void purge(); - int rotate_and_purge(bool force_rotate); + void checkpoint_and_purge(ulong binlog_id); + int rotate_and_purge(bool force_rotate, DYNAMIC_ARRAY* drop_gtid_domain= NULL); /** Flush binlog cache and synchronize to disk. @@ -664,7 +791,10 @@ public: int register_create_index_entry(const char* entry); int purge_index_entry(THD *thd, ulonglong *decrease_log_space, bool need_mutex); - bool reset_logs(THD* thd); + bool reset_logs(THD* thd, bool create_new_log, + rpl_gtid *init_state, uint32 init_state_len, + ulong next_log_number); + void wait_for_last_checkpoint_event(); void close(uint exiting); void clear_inuse_flag_when_closing(File file); @@ -687,6 +817,82 @@ public: inline IO_CACHE *get_index_file() { return &index_file;} inline uint32 get_open_count() { return open_count; } void set_status_variables(THD *thd); + bool is_xidlist_idle(); + bool write_gtid_event(THD *thd, bool standalone, bool is_transactional, + uint64 commit_id); + int read_state_from_file(); + int write_state_to_file(); + int get_most_recent_gtid_list(rpl_gtid **list, uint32 *size); + bool append_state_pos(String *str); + bool append_state(String *str); + bool is_empty_state(); + bool find_in_binlog_state(uint32 domain_id, uint32 server_id, + rpl_gtid *out_gtid); + bool lookup_domain_in_binlog_state(uint32 domain_id, rpl_gtid *out_gtid); + int bump_seq_no_counter_if_needed(uint32 domain_id, uint64 seq_no); + bool check_strict_gtid_sequence(uint32 domain_id, uint32 server_id, + uint64 seq_no); + + + void update_binlog_end_pos(my_off_t pos) + { + mysql_mutex_assert_owner(&LOCK_log); + mysql_mutex_assert_not_owner(&LOCK_binlog_end_pos); + lock_binlog_end_pos(); + /** + * note: it would make more sense to assert(pos > binlog_end_pos) + * but there are two places triggered by mtr that has pos == binlog_end_pos + * i didn't investigate but accepted as it should do no harm + */ + DBUG_ASSERT(pos >= binlog_end_pos); + binlog_end_pos= pos; + signal_update(); + unlock_binlog_end_pos(); + } + + /** + * used when opening new file, and binlog_end_pos moves backwards + */ + void reset_binlog_end_pos(const char file_name[FN_REFLEN], my_off_t pos) + { + mysql_mutex_assert_owner(&LOCK_log); + mysql_mutex_assert_not_owner(&LOCK_binlog_end_pos); + lock_binlog_end_pos(); + binlog_end_pos= pos; + strcpy(binlog_end_pos_file, file_name); + signal_update(); + unlock_binlog_end_pos(); + } + + /* + It is called by the threads(e.g. dump thread) which want to read + log without LOCK_log protection. + */ + my_off_t get_binlog_end_pos(char file_name_buf[FN_REFLEN]) const + { + mysql_mutex_assert_not_owner(&LOCK_log); + mysql_mutex_assert_owner(&LOCK_binlog_end_pos); + strcpy(file_name_buf, binlog_end_pos_file); + return binlog_end_pos; + } + void lock_binlog_end_pos() { mysql_mutex_lock(&LOCK_binlog_end_pos); } + void unlock_binlog_end_pos() { mysql_mutex_unlock(&LOCK_binlog_end_pos); } + mysql_mutex_t* get_binlog_end_pos_lock() { return &LOCK_binlog_end_pos; } + + int wait_for_update_binlog_end_pos(THD* thd, struct timespec * timeout); + + /* + Binlog position of end of the binlog. + Access to this is protected by LOCK_binlog_end_pos + + The difference between this and last_commit_pos_{file,offset} is that + the commit position is updated later. If semi-sync wait point is set + to WAIT_AFTER_SYNC, the commit pos is update after semi-sync-ack has + been received and the end point is updated after the write as it's needed + for the dump threads to be able to semi-sync the event. + */ + my_off_t binlog_end_pos; + char binlog_end_pos_file[FN_REFLEN]; }; class Log_event_handler @@ -712,8 +918,8 @@ public: }; -int check_if_log_table(size_t db_len, const char *db, size_t table_name_len, - const char *table_name, bool check_if_opened); +int check_if_log_table(const TABLE_LIST *table, bool check_if_opened, + const char *errmsg); class Log_to_csv_event_handler: public Log_event_handler { @@ -881,6 +1087,7 @@ bool general_log_print(THD *thd, enum enum_server_command command, bool general_log_write(THD *thd, enum enum_server_command command, const char *query, uint query_length); +void binlog_report_wait_for(THD *thd, THD *other_thd); void sql_perror(const char *message); bool flush_error_log(); @@ -888,10 +1095,13 @@ File open_binlog(IO_CACHE *log, const char *log_file_name, const char **errmsg); void make_default_log_name(char **out, const char* log_ext, bool once); +void binlog_reset_cache(THD *thd); extern MYSQL_PLUGIN_IMPORT MYSQL_BIN_LOG mysql_bin_log; extern LOGGER logger; +extern const char *log_bin_index; +extern const char *log_bin_basename; /** Turns a relative log binary log path into a full path, based on the @@ -961,4 +1171,9 @@ static inline TC_LOG *get_tc_log_implementation() return &tc_log_mmap; } + +class Gtid_list_log_event; +const char * +get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list); + #endif /* LOG_H */ diff --git a/sql/log_event.cc b/sql/log_event.cc index f84cfc94f6f..bb373a4ed84 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -16,20 +16,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifdef MYSQL_CLIENT - +#include <my_global.h> #include "sql_priv.h" #include "mysqld_error.h" -#else - -#ifdef USE_PRAGMA_IMPLEMENTATION -#pragma implementation // gcc: Class implementation -#endif - -#include "sql_priv.h" +#ifndef MYSQL_CLIENT #include "unireg.h" -#include "my_global.h" // REQUIRED by log_event.h > m_string.h > my_bitmap.h #include "log_event.h" #include "sql_base.h" // close_thread_tables #include "sql_cache.h" // QUERY_CACHE_FLAGS_SIZE @@ -48,13 +40,20 @@ #include "transaction.h" #include <my_dir.h> #include "sql_show.h" // append_identifier - +#include <mysql/psi/mysql_statement.h> +#include <strfunc.h> +#include "compat56.h" +#include "wsrep_mysqld.h" #endif /* MYSQL_CLIENT */ -#include <base64.h> #include <my_bitmap.h> #include "rpl_utility.h" +#include "rpl_constants.h" +#include "sql_digest.h" +#define my_b_write_string(A, B) my_b_write((A), (uchar*)(B), (uint) (sizeof(B) - 1)) + +using std::max; /** BINLOG_CHECKSUM variable. @@ -84,7 +83,6 @@ TYPELIB binlog_checksum_typelib= #define FLAGSTR(V,F) ((V)&(F)?#F" ":"") - /* Size of buffer for printing a double in format %.<PREC>g @@ -93,23 +91,6 @@ TYPELIB binlog_checksum_typelib= */ #define FMT_G_BUFSIZE(PREC) (3 + (PREC) + 5 + 1) -/* - Explicit instantiation to unsigned int of template available_buffer - function. -*/ -template unsigned int available_buffer<unsigned int>(const char*, - const char*, - unsigned int); - -/* - Explicit instantiation to unsigned int of template valid_buffer_range - function. -*/ -template bool valid_buffer_range<unsigned int>(unsigned int, - const char*, - const char*, - unsigned int); - /* replication event checksum is introduced in the following "checksum-home" version. The checksum-aware servers extract FD's version to decide whether the FD event @@ -130,7 +111,7 @@ const ulong checksum_version_product_mariadb= checksum_version_split_mariadb[2]; #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD* thd); +static int rows_event_stmt_cleanup(rpl_group_info *rgi, THD* thd); static const char *HA_ERR(int i) { @@ -194,6 +175,30 @@ static const char *HA_ERR(int i) return "No Error!"; } + +/* + Return true if an error caught during event execution is a temporary error + that will cause automatic retry of the event group during parallel + replication, false otherwise. + + In parallel replication, conflicting transactions can occasionally cause + deadlocks; such errors are handled automatically by rolling back re-trying + the transactions, so should not pollute the error log. +*/ +static bool +is_parallel_retry_error(rpl_group_info *rgi, int err) +{ + if (!rgi->is_parallel_exec) + return false; + if (rgi->speculation == rpl_group_info::SPECULATE_OPTIMISTIC) + return true; + if (rgi->killed_for_retry && + (err == ER_QUERY_INTERRUPTED || err == ER_CONNECTION_KILLED)) + return true; + return has_temporary_error(rgi->thd); +} + + /** Error reporting facility for Rows_log_event::do_apply_event @@ -208,7 +213,7 @@ static const char *HA_ERR(int i) */ static void inline slave_rows_error_report(enum loglevel level, int ha_error, - Relay_log_info const *rli, THD *thd, + rpl_group_info *rgi, THD *thd, TABLE *table, const char * type, const char *log_name, ulong pos) { @@ -216,9 +221,21 @@ static void inline slave_rows_error_report(enum loglevel level, int ha_error, char buff[MAX_SLAVE_ERRMSG], *slider; const char *buff_end= buff + sizeof(buff); uint len; - List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); - MYSQL_ERROR *err; + Diagnostics_area::Sql_condition_iterator it= + thd->get_stmt_da()->sql_conditions(); + Relay_log_info const *rli= rgi->rli; + const Sql_condition *err; buff[0]= 0; + int errcode= thd->is_error() ? thd->get_stmt_da()->sql_errno() : 0; + + /* + In parallel replication, deadlocks or other temporary errors can happen + occasionally in normal operation, they will be handled correctly and + automatically by re-trying the transactions. So do not pollute the error + log with messages about them. + */ + if (is_parallel_retry_error(rgi, errcode)) + return; for (err= it++, slider= buff; err && slider < buff_end - 1; slider += len, err= it++) @@ -229,7 +246,7 @@ static void inline slave_rows_error_report(enum loglevel level, int ha_error, } if (ha_error != 0) - rli->report(level, thd->is_error() ? thd->stmt_da->sql_errno() : 0, + rli->report(level, errcode, rgi->gtid_info(), "Could not execute %s event on table %s.%s;" "%s handler error %s; " "the event's master log %s, end_log_pos %lu", @@ -237,7 +254,7 @@ static void inline slave_rows_error_report(enum loglevel level, int ha_error, buff, handler_error == NULL ? "<unknown>" : handler_error, log_name, pos); else - rli->report(level, thd->is_error() ? thd->stmt_da->sql_errno() : 0, + rli->report(level, errcode, rgi->gtid_info(), "Could not execute %s event on table %s.%s;" "%s the event's master log %s, end_log_pos %lu", type, table->s->db.str, table->s->table_name.str, @@ -246,7 +263,8 @@ static void inline slave_rows_error_report(enum loglevel level, int ha_error, #endif #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -static void set_thd_db(THD *thd,const char *db, uint32 db_len) +static void set_thd_db(THD *thd, Rpl_filter *rpl_filter, + const char *db, uint32 db_len) { char lcase_db_buf[NAME_LEN +1]; LEX_STRING new_db; @@ -261,8 +279,7 @@ static void set_thd_db(THD *thd,const char *db, uint32 db_len) new_db.str= (char*) db; /* TODO WARNING this makes rewrite_db respect lower_case_table_names values * for more info look MDEV-17446 */ - new_db.str= (char*) rpl_filter->get_rewrite_db(new_db.str, - &new_db.length); + new_db.str= (char*) rpl_filter->get_rewrite_db(new_db.str, &new_db.length); thd->set_db(new_db.str, new_db.length); } #endif @@ -311,7 +328,7 @@ public: ~Write_on_release_cache() { copy_event_cache_to_file_and_reinit(m_cache, m_file); - if (m_flags | FLUSH_F) + if (m_flags & FLUSH_F) fflush(m_file); } @@ -344,10 +361,6 @@ private: flag_set m_flags; }; -#ifndef DBUG_OFF -uint debug_not_change_ts_if_art_event= 1; // bug#29309 simulation -#endif - /* pretty_print_str() */ @@ -356,36 +369,29 @@ uint debug_not_change_ts_if_art_event= 1; // bug#29309 simulation static void pretty_print_str(IO_CACHE* cache, const char* str, int len) { const char* end = str + len; - my_b_printf(cache, "\'"); + my_b_write_byte(cache, '\''); while (str < end) { char c; switch ((c=*str++)) { - case '\n': my_b_printf(cache, "\\n"); break; - case '\r': my_b_printf(cache, "\\r"); break; - case '\\': my_b_printf(cache, "\\\\"); break; - case '\b': my_b_printf(cache, "\\b"); break; - case '\t': my_b_printf(cache, "\\t"); break; - case '\'': my_b_printf(cache, "\\'"); break; - case 0 : my_b_printf(cache, "\\0"); break; + case '\n': my_b_write(cache, (uchar*)"\\n", 2); break; + case '\r': my_b_write(cache, (uchar*)"\\r", 2); break; + case '\\': my_b_write(cache, (uchar*)"\\\\", 2); break; + case '\b': my_b_write(cache, (uchar*)"\\b", 2); break; + case '\t': my_b_write(cache, (uchar*)"\\t", 2); break; + case '\'': my_b_write(cache, (uchar*)"\\'", 2); break; + case 0 : my_b_write(cache, (uchar*)"\\0", 2); break; default: - my_b_printf(cache, "%c", c); + my_b_write_byte(cache, c); break; } } - my_b_printf(cache, "\'"); + my_b_write_byte(cache, '\''); } #endif /* MYSQL_CLIENT */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -static void clear_all_errors(THD *thd, Relay_log_info *rli) -{ - thd->is_slave_error = 0; - thd->clear_error(); - rli->clear_error(); -} - inline int idempotent_error_code(int err_code) { int ret= 0; @@ -434,22 +440,12 @@ inline int idempotent_error_code(int err_code) inline int ignored_error_code(int err_code) { -#ifdef HAVE_NDB_BINLOG - /* - The following error codes are hard-coded and will always be ignored. - */ - switch (err_code) + if (use_slave_mask && bitmap_is_set(&slave_error_mask, err_code)) { - case ER_DB_CREATE_EXISTS: - case ER_DB_DROP_EXISTS: + statistic_increment(slave_skipped_errors, LOCK_status); return 1; - default: - /* Nothing to do */ - break; } -#endif - return ((err_code == ER_SLAVE_IGNORED_TABLE) || - (use_slave_mask && bitmap_is_set(&slave_error_mask, err_code))); + return err_code == ER_SLAVE_IGNORED_TABLE; } /* @@ -462,13 +458,13 @@ inline int ignored_error_code(int err_code) */ int convert_handler_error(int error, THD* thd, TABLE *table) { - uint actual_error= (thd->is_error() ? thd->stmt_da->sql_errno() : + uint actual_error= (thd->is_error() ? thd->get_stmt_da()->sql_errno() : 0); if (actual_error == 0) { table->file->print_error(error, MYF(0)); - actual_error= (thd->is_error() ? thd->stmt_da->sql_errno() : + actual_error= (thd->is_error() ? thd->get_stmt_da()->sql_errno() : ER_UNKNOWN_ERROR); if (actual_error == ER_UNKNOWN_ERROR) if (global_system_variables.log_warnings) @@ -498,6 +494,7 @@ inline bool unexpected_error_code(int unexpected_error) case ER_NET_READ_ERROR: case ER_NET_ERROR_ON_WRITE: case ER_QUERY_INTERRUPTED: + case ER_STATEMENT_TIMEOUT: case ER_CONNECTION_KILLED: case ER_SERVER_SHUTDOWN: case ER_NEW_ABORTING_CONNECTION: @@ -536,15 +533,84 @@ pretty_print_str(String *packet, const char *str, int len) } #endif /* !MYSQL_CLIENT */ +#ifndef DBUG_OFF +#define DBUG_DUMP_EVENT_BUF(B,L) \ + do { \ + const uchar *_buf=(uchar*)(B); \ + size_t _len=(L); \ + if (_len >= LOG_EVENT_MINIMAL_HEADER_LEN) \ + { \ + DBUG_PRINT("data", ("header: timestamp:%u type:%u server_id:%u len:%u log_pos:%u flags:%u", \ + uint4korr(_buf), _buf[EVENT_TYPE_OFFSET], \ + uint4korr(_buf+SERVER_ID_OFFSET), \ + uint4korr(_buf+EVENT_LEN_OFFSET), \ + uint4korr(_buf+LOG_POS_OFFSET), \ + uint4korr(_buf+FLAGS_OFFSET))); \ + DBUG_DUMP("data", _buf+LOG_EVENT_MINIMAL_HEADER_LEN, \ + _len-LOG_EVENT_MINIMAL_HEADER_LEN); \ + } \ + else \ + DBUG_DUMP("data", _buf, _len); \ + } while(0) +#else +#define DBUG_DUMP_EVENT_BUF(B,L) do { } while(0) +#endif #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) /** - Creates a temporary name for load data infile:. + Create a prefix for the temporary files that is to be used for + load data file name for this master + + @param name Store prefix of name here + @param connection_name Connection name + + @return pointer to end of name + + @description + We assume that FN_REFLEN is big enough to hold + MAX_CONNECTION_NAME * MAX_FILENAME_MBWIDTH characters + 2 numbers + + a short extension. + + The resulting file name has the following parts, each separated with a '-' + - PREFIX_SQL_LOAD (SQL_LOAD-) + - If a connection name is given (multi-master setup): + - Add an extra '-' to mark that this is a multi-master file + - connection name in lower case, converted to safe file characters. + (see create_logfile_name_with_suffix()). + - server_id + - A last '-' (after server_id). +*/ + +static char *load_data_tmp_prefix(char *name, + LEX_STRING *connection_name) +{ + name= strmov(name, PREFIX_SQL_LOAD); + if (connection_name->length) + { + uint buf_length; + uint errors; + /* Add marker that this is a multi-master-file */ + *name++='-'; + /* Convert connection_name to a safe filename */ + buf_length= strconvert(system_charset_info, connection_name->str, FN_REFLEN, + &my_charset_filename, name, FN_REFLEN, &errors); + name+= buf_length; + *name++= '-'; + } + name= int10_to_str(global_system_variables.server_id, name, 10); + *name++ = '-'; + *name= '\0'; // For testing prefixes + return name; +} + + +/** + Creates a temporary name for LOAD DATA INFILE @param buf Store new filename here @param file_id File_id (part of file name) - @param event_server_id Event_id (part of file name) + @param event_server_id Event_id (part of file name) @param ext Extension for file name @return @@ -552,16 +618,14 @@ pretty_print_str(String *packet, const char *str, int len) */ static char *slave_load_file_stem(char *buf, uint file_id, - int event_server_id, const char *ext) + int event_server_id, const char *ext, + LEX_STRING *connection_name) { char *res; - fn_format(buf,PREFIX_SQL_LOAD,slave_load_tmpdir, "", MY_UNPACK_FILENAME); + res= buf+ unpack_dirname(buf, slave_load_tmpdir); to_unix_path(buf); - - buf = strend(buf); - buf = int10_to_str(::server_id, buf, 10); - *buf++ = '-'; - buf = int10_to_str(event_server_id, buf, 10); + buf= load_data_tmp_prefix(res, connection_name); + buf= int10_to_str(event_server_id, buf, 10); *buf++ = '-'; res= int10_to_str(file_id, buf, 10); strmov(res, ext); // Add extension last @@ -576,14 +640,17 @@ static char *slave_load_file_stem(char *buf, uint file_id, Delete all temporary files used for SQL_LOAD. */ -static void cleanup_load_tmpdir() +static void cleanup_load_tmpdir(LEX_STRING *connection_name) { MY_DIR *dirp; FILEINFO *file; uint i; - char fname[FN_REFLEN], prefbuf[31], *p; + char dir[FN_REFLEN], fname[FN_REFLEN]; + char prefbuf[31 + MAX_CONNECTION_NAME* MAX_FILENAME_MBWIDTH + 1]; + DBUG_ENTER("cleanup_load_tmpdir"); - if (!(dirp=my_dir(slave_load_tmpdir,MYF(0)))) + unpack_dirname(dir, slave_load_tmpdir); + if (!(dirp=my_dir(dir, MYF(MY_WME)))) return; /* @@ -594,12 +661,11 @@ static void cleanup_load_tmpdir() we cannot meet Start_log event in the middle of events from one LOAD DATA. */ - p= strmake(prefbuf, STRING_WITH_LEN(PREFIX_SQL_LOAD)); - p= int10_to_str(::server_id, p, 10); - *(p++)= '-'; - *p= 0; - for (i=0 ; i < (uint)dirp->number_off_files; i++) + load_data_tmp_prefix(prefbuf, connection_name); + DBUG_PRINT("enter", ("dir: '%s' prefix: '%s'", dir, prefbuf)); + + for (i=0 ; i < (uint)dirp->number_of_files; i++) { file=dirp->dir_entry+i; if (is_prefix(file->name, prefbuf)) @@ -610,24 +676,12 @@ static void cleanup_load_tmpdir() } my_dirend(dirp); + DBUG_VOID_RETURN; } #endif /* - write_str() -*/ - -static bool write_str(IO_CACHE *file, const char *str, uint length) -{ - uchar tmp[1]; - tmp[0]= (uchar) length; - return (my_b_safe_write(file, tmp, sizeof(tmp)) || - my_b_safe_write(file, (uchar*) str, length)); -} - - -/* read_str() */ @@ -665,37 +719,35 @@ char *str_to_hex(char *to, const char *from, uint len) #ifndef MYSQL_CLIENT /** - Append a version of the 'from' string suitable for use in a query to + Append a version of the 'str' string suitable for use in a query to the 'to' string. To generate a correct escaping, the character set information in 'csinfo' is used. */ -int -append_query_string(THD *thd, CHARSET_INFO *csinfo, - String const *from, String *to) +int append_query_string(CHARSET_INFO *csinfo, String *to, + const char *str, size_t len, bool no_backslash) { char *beg, *ptr; uint32 const orig_len= to->length(); - if (to->reserve(orig_len + from->length() * 2 + 4)) + if (to->reserve(orig_len + len * 2 + 4)) return 1; beg= (char*) to->ptr() + to->length(); ptr= beg; if (csinfo->escape_with_backslash_is_dangerous) - ptr= str_to_hex(ptr, from->ptr(), from->length()); + ptr= str_to_hex(ptr, str, len); else { *ptr++= '\''; - if (!(thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)) + if (!no_backslash) { - ptr+= escape_string_for_mysql(csinfo, ptr, 0, - from->ptr(), from->length()); + ptr+= escape_string_for_mysql(csinfo, ptr, 0, str, len); } else { - const char *frm_str= from->ptr(); + const char *frm_str= str; - for (; frm_str < (from->ptr() + from->length()); frm_str++) + for (; frm_str < (str + len); frm_str++) { /* Using '' way to represent "'" */ if (*frm_str == '\'') @@ -727,8 +779,8 @@ static void print_set_option(IO_CACHE* file, uint32 bits_changed, if (bits_changed & option) { if (*need_comma) - my_b_printf(file,", "); - my_b_printf(file,"%s=%d", name, test(flags & option)); + my_b_write(file, (uchar*)", ", 2); + my_b_printf(file, "%s=%d", name, MY_TEST(flags & option)); *need_comma= 1; } } @@ -765,6 +817,9 @@ const char* Log_event::get_type_str(Log_event_type type) case PRE_GA_WRITE_ROWS_EVENT: return "Write_rows_event_old"; case PRE_GA_UPDATE_ROWS_EVENT: return "Update_rows_event_old"; case PRE_GA_DELETE_ROWS_EVENT: return "Delete_rows_event_old"; + case WRITE_ROWS_EVENT_V1: return "Write_rows_v1"; + case UPDATE_ROWS_EVENT_V1: return "Update_rows_v1"; + case DELETE_ROWS_EVENT_V1: return "Delete_rows_v1"; case WRITE_ROWS_EVENT: return "Write_rows"; case UPDATE_ROWS_EVENT: return "Update_rows"; case DELETE_ROWS_EVENT: return "Delete_rows"; @@ -772,6 +827,22 @@ const char* Log_event::get_type_str(Log_event_type type) case EXECUTE_LOAD_QUERY_EVENT: return "Execute_load_query"; case INCIDENT_EVENT: return "Incident"; case ANNOTATE_ROWS_EVENT: return "Annotate_rows"; + case BINLOG_CHECKPOINT_EVENT: return "Binlog_checkpoint"; + case GTID_EVENT: return "Gtid"; + case GTID_LIST_EVENT: return "Gtid_list"; + case START_ENCRYPTION_EVENT: return "Start_encryption"; + + /* The following is only for mysqlbinlog */ + case IGNORABLE_LOG_EVENT: return "Ignorable log event"; + case ROWS_QUERY_LOG_EVENT: return "MySQL Rows_query"; + case GTID_LOG_EVENT: return "MySQL Gtid"; + case ANONYMOUS_GTID_LOG_EVENT: return "MySQL Anonymous_Gtid"; + case PREVIOUS_GTIDS_LOG_EVENT: return "MySQL Previous_gtids"; + case HEARTBEAT_LOG_EVENT: return "Heartbeat"; + case TRANSACTION_CONTEXT_EVENT: return "Transaction_context"; + case VIEW_CHANGE_EVENT: return "View_change"; + case XA_PREPARE_LOG_EVENT: return "XA_prepare"; + default: return "Unknown"; /* impossible */ } } @@ -788,11 +859,10 @@ const char* Log_event::get_type_str() #ifndef MYSQL_CLIENT Log_event::Log_event(THD* thd_arg, uint16 flags_arg, bool using_trans) - :log_pos(0), temp_buf(0), exec_time(0), - crc(0), thd(thd_arg), + :log_pos(0), temp_buf(0), exec_time(0), thd(thd_arg), checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF) { - server_id= thd->server_id; + server_id= thd->variables.server_id; when= thd->start_time; when_sec_part=thd->start_time_sec_part; @@ -813,11 +883,10 @@ Log_event::Log_event(THD* thd_arg, uint16 flags_arg, bool using_trans) */ Log_event::Log_event() - :temp_buf(0), exec_time(0), flags(0), - cache_type(Log_event::EVENT_INVALID_CACHE), crc(0), + :temp_buf(0), exec_time(0), flags(0), cache_type(EVENT_INVALID_CACHE), thd(0), checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF) { - server_id= ::server_id; + server_id= global_system_variables.server_id; /* We can't call my_time() here as this would cause a call before my_init() is called @@ -835,8 +904,8 @@ Log_event::Log_event() Log_event::Log_event(const char* buf, const Format_description_log_event* description_event) - :temp_buf(0), cache_type(Log_event::EVENT_INVALID_CACHE), - crc(0), checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF) + :temp_buf(0), exec_time(0), cache_type(Log_event::EVENT_INVALID_CACHE), + checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF) { #ifndef MYSQL_CLIENT thd = 0; @@ -906,8 +975,11 @@ Log_event::Log_event(const char* buf, #ifndef MYSQL_CLIENT #ifdef HAVE_REPLICATION -int Log_event::do_update_pos(Relay_log_info *rli) +int Log_event::do_update_pos(rpl_group_info *rgi) { + Relay_log_info *rli= rgi->rli; + DBUG_ENTER("Log_event::do_update_pos"); + /* rli is null when (as far as I (Guilhem) know) the caller is Load_log_event::do_apply_event *and* that one is called from @@ -923,36 +995,30 @@ int Log_event::do_update_pos(Relay_log_info *rli) if (rli) { /* - bug#29309 simulation: resetting the flag to force - wrong behaviour of artificial event to update - rli->last_master_timestamp for only one time - - the first FLUSH LOGS in the test. + In parallel execution, delay position update for the events that are + not part of event groups (format description, rotate, and such) until + the actual event execution reaches that point. */ - DBUG_EXECUTE_IF("let_first_flush_log_change_timestamp", - if (debug_not_change_ts_if_art_event == 1 - && is_artificial_event()) - debug_not_change_ts_if_art_event= 0; ); - rli->stmt_done(log_pos, is_artificial_event() && - IF_DBUG(debug_not_change_ts_if_art_event > 0, 1) ? - 0 : when); - DBUG_EXECUTE_IF("let_first_flush_log_change_timestamp", - if (debug_not_change_ts_if_art_event == 0) - debug_not_change_ts_if_art_event= 2; ); + if (!rgi->is_parallel_exec || is_group_event(get_type_code())) + rli->stmt_done(log_pos, thd, rgi); } - return 0; // Cannot fail currently + DBUG_RETURN(0); // Cannot fail currently } Log_event::enum_skip_reason -Log_event::do_shall_skip(Relay_log_info *rli) -{ - DBUG_PRINT("info", ("ev->server_id=%lu, ::server_id=%lu," - " rli->replicate_same_server_id=%d," - " rli->slave_skip_counter=%d", - (ulong) server_id, (ulong) ::server_id, +Log_event::do_shall_skip(rpl_group_info *rgi) +{ + Relay_log_info *rli= rgi->rli; + DBUG_PRINT("info", ("ev->server_id: %lu, ::server_id: %lu," + " rli->replicate_same_server_id: %d," + " rli->slave_skip_counter: %llu", + (ulong) server_id, + (ulong) global_system_variables.server_id, rli->replicate_same_server_id, rli->slave_skip_counter)); - if ((server_id == ::server_id && !rli->replicate_same_server_id) || + if ((server_id == global_system_variables.server_id && + !rli->replicate_same_server_id) || (rli->slave_skip_counter == 1 && rli->is_in_group()) || (flags & LOG_EVENT_SKIP_REPLICATION_F && opt_replicate_events_marked_for_skip != RPL_SKIP_REPLICATE)) @@ -967,7 +1033,7 @@ Log_event::do_shall_skip(Relay_log_info *rli) Log_event::pack_info() */ -void Log_event::pack_info(THD *thd, Protocol *protocol) +void Log_event::pack_info(Protocol *protocol) { protocol->store("", &my_charset_bin); } @@ -976,8 +1042,7 @@ void Log_event::pack_info(THD *thd, Protocol *protocol) /** Only called by SHOW BINLOG EVENTS */ -int Log_event::net_send(THD *thd, Protocol *protocol, const char* log_name, - my_off_t pos) +int Log_event::net_send(Protocol *protocol, const char* log_name, my_off_t pos) { const char *p= strrchr(log_name, FN_LIBCHAR); const char *event_type; @@ -991,7 +1056,7 @@ int Log_event::net_send(THD *thd, Protocol *protocol, const char* log_name, protocol->store(event_type, strlen(event_type), &my_charset_bin); protocol->store((uint32) server_id); protocol->store((ulonglong) log_pos); - pack_info(thd, protocol); + pack_info(protocol); return protocol->write(); } #endif /* HAVE_REPLICATION */ @@ -1003,18 +1068,31 @@ int Log_event::net_send(THD *thd, Protocol *protocol, const char* log_name, EVENTS. */ -void Log_event::init_show_field_list(List<Item>* field_list) -{ - field_list->push_back(new Item_empty_string("Log_name", 20)); - field_list->push_back(new Item_return_int("Pos", MY_INT32_NUM_DECIMAL_DIGITS, - MYSQL_TYPE_LONGLONG)); - field_list->push_back(new Item_empty_string("Event_type", 20)); - field_list->push_back(new Item_return_int("Server_id", 10, - MYSQL_TYPE_LONG)); - field_list->push_back(new Item_return_int("End_log_pos", - MY_INT32_NUM_DECIMAL_DIGITS, - MYSQL_TYPE_LONGLONG)); - field_list->push_back(new Item_empty_string("Info", 20)); +void Log_event::init_show_field_list(THD *thd, List<Item>* field_list) +{ + MEM_ROOT *mem_root= thd->mem_root; + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Log_name", 20), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Pos", + MY_INT32_NUM_DECIMAL_DIGITS, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Event_type", 20), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Server_id", 10, + MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "End_log_pos", + MY_INT32_NUM_DECIMAL_DIGITS, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) Item_empty_string(thd, "Info", 20), + mem_root); } /** @@ -1045,12 +1123,14 @@ my_bool Log_event::need_checksum() and Stop event) provides their checksum alg preference through Log_event::checksum_alg. */ - ret= ((checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) ? - (checksum_alg != BINLOG_CHECKSUM_ALG_OFF) : - ((binlog_checksum_options != BINLOG_CHECKSUM_ALG_OFF) && - (cache_type == Log_event::EVENT_NO_CACHE)) ? - test(binlog_checksum_options) : FALSE); - + if (checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) + ret= checksum_alg != BINLOG_CHECKSUM_ALG_OFF; + else + { + ret= binlog_checksum_options && cache_type == Log_event::EVENT_NO_CACHE; + checksum_alg= ret ? (enum_binlog_checksum_alg)binlog_checksum_options + : BINLOG_CHECKSUM_ALG_OFF; + } /* FD calls the methods before data_written has been calculated. The following invariant claims if the current is not the first @@ -1061,10 +1141,6 @@ my_bool Log_event::need_checksum() DBUG_ASSERT(get_type_code() != FORMAT_DESCRIPTION_EVENT || ret || data_written == 0); - if (checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF) - checksum_alg= ret ? // calculated value stored - (uint8) binlog_checksum_options : (uint8) BINLOG_CHECKSUM_ALG_OFF; - DBUG_ASSERT(!ret || ((checksum_alg == binlog_checksum_options || /* @@ -1079,8 +1155,9 @@ my_bool Log_event::need_checksum() the local RL's Rotate and the master's Rotate which IO thread instantiates via queue_binlog_ver_3_event. */ - get_type_code() == ROTATE_EVENT - || /* FD is always checksummed */ + get_type_code() == ROTATE_EVENT || + get_type_code() == START_ENCRYPTION_EVENT || + /* FD is always checksummed */ get_type_code() == FORMAT_DESCRIPTION_EVENT) && checksum_alg != BINLOG_CHECKSUM_ALG_OFF)); @@ -1094,51 +1171,145 @@ my_bool Log_event::need_checksum() DBUG_RETURN(ret); } -bool Log_event::wrapper_my_b_safe_write(IO_CACHE* file, const uchar* buf, ulong size) +int Log_event_writer::write_internal(const uchar *pos, size_t len) +{ + if (my_b_safe_write(file, pos, len)) + return 1; + bytes_written+= len; + return 0; +} + +/* + as soon as encryption produces the first output block, write event_len + where it should be in a valid event header +*/ +int Log_event_writer::maybe_write_event_len(uchar *pos, size_t len) +{ + if (len && event_len) + { + DBUG_ASSERT(len >= EVENT_LEN_OFFSET); + if (write_internal(pos + EVENT_LEN_OFFSET - 4, 4)) + return 1; + int4store(pos + EVENT_LEN_OFFSET - 4, event_len); + event_len= 0; + } + return 0; +} + +int Log_event_writer::encrypt_and_write(const uchar *pos, size_t len) { - if (need_checksum() && size != 0) - crc= my_checksum(crc, buf, size); + uchar *dst= 0; + size_t dstsize= 0; + + if (ctx) + { + dstsize= encryption_encrypted_length(len, ENCRYPTION_KEY_SYSTEM_DATA, + crypto->key_version); + if (!(dst= (uchar*)my_safe_alloca(dstsize))) + return 1; + + uint dstlen; + if (encryption_ctx_update(ctx, pos, len, dst, &dstlen)) + goto err; + if (maybe_write_event_len(dst, dstlen)) + return 1; + pos= dst; + len= dstlen; + } + if (write_internal(pos, len)) + goto err; - return my_b_safe_write(file, buf, size); + my_safe_afree(dst, dstsize); + return 0; +err: + my_safe_afree(dst, dstsize); + return 1; } -bool Log_event::write_footer(IO_CACHE* file) +int Log_event_writer::write_header(uchar *pos, size_t len) { + DBUG_ENTER("Log_event_writer::write_header"); /* - footer contains the checksum-algorithm descriptor - followed by the checksum value + recording checksum of FD event computed with dropped + possibly active LOG_EVENT_BINLOG_IN_USE_F flag. + Similar step at verication: the active flag is dropped before + checksum computing. */ - if (need_checksum()) + if (checksum_len) { - uchar buf[BINLOG_CHECKSUM_LEN]; - int4store(buf, crc); - return (my_b_safe_write(file, (uchar*) buf, sizeof(buf))); + uchar save=pos[FLAGS_OFFSET]; + pos[FLAGS_OFFSET]&= ~LOG_EVENT_BINLOG_IN_USE_F; + crc= my_checksum(0, pos, len); + pos[FLAGS_OFFSET]= save; } - return 0; + + if (ctx) + { + uchar iv[BINLOG_IV_LENGTH]; + crypto->set_iv(iv, my_b_safe_tell(file)); + if (encryption_ctx_init(ctx, crypto->key, crypto->key_length, + iv, sizeof(iv), ENCRYPTION_FLAG_ENCRYPT | ENCRYPTION_FLAG_NOPAD, + ENCRYPTION_KEY_SYSTEM_DATA, crypto->key_version)) + DBUG_RETURN(1); + + DBUG_ASSERT(len >= LOG_EVENT_HEADER_LEN); + event_len= uint4korr(pos + EVENT_LEN_OFFSET); + DBUG_ASSERT(event_len >= len); + memcpy(pos + EVENT_LEN_OFFSET, pos, 4); + pos+= 4; + len-= 4; + } + DBUG_RETURN(encrypt_and_write(pos, len)); +} + +int Log_event_writer::write_data(const uchar *pos, size_t len) +{ + DBUG_ENTER("Log_event_writer::write_data"); + if (checksum_len) + crc= my_checksum(crc, pos, len); + + DBUG_RETURN(encrypt_and_write(pos, len)); +} + +int Log_event_writer::write_footer() +{ + DBUG_ENTER("Log_event_writer::write_footer"); + if (checksum_len) + { + uchar checksum_buf[BINLOG_CHECKSUM_LEN]; + int4store(checksum_buf, crc); + if (encrypt_and_write(checksum_buf, BINLOG_CHECKSUM_LEN)) + DBUG_RETURN(ER_ERROR_ON_WRITE); + } + if (ctx) + { + uint dstlen; + uchar dst[MY_AES_BLOCK_SIZE*2]; + if (encryption_ctx_finish(ctx, dst, &dstlen)) + DBUG_RETURN(1); + if (maybe_write_event_len(dst, dstlen) || write_internal(dst, dstlen)) + DBUG_RETURN(ER_ERROR_ON_WRITE); + } + DBUG_RETURN(0); } /* - Log_event::write() + Log_event::write_header() */ -bool Log_event::write_header(IO_CACHE* file, ulong event_data_length) +bool Log_event::write_header(ulong event_data_length) { uchar header[LOG_EVENT_HEADER_LEN]; ulong now; - bool ret; DBUG_ENTER("Log_event::write_header"); DBUG_PRINT("enter", ("filepos: %lld length: %lu type: %d", - (longlong) my_b_tell(file), event_data_length, + (longlong) writer->pos(), event_data_length, (int) get_type_code())); - /* Store number of bytes that will be written by this event */ - data_written= event_data_length + sizeof(header); + writer->checksum_len= need_checksum() ? BINLOG_CHECKSUM_LEN : 0; - if (need_checksum()) - { - crc= my_checksum(0L, NULL, 0); - data_written += BINLOG_CHECKSUM_LEN; - } + /* Store number of bytes that will be written by this event */ + data_written= event_data_length + sizeof(header) + writer->checksum_len; /* log_pos != 0 if this is relay-log event. In this case we should not @@ -1156,32 +1327,13 @@ bool Log_event::write_header(IO_CACHE* file, ulong event_data_length) else if (!log_pos) { /* - Calculate position of end of event - - Note that with a SEQ_READ_APPEND cache, my_b_tell() does not - work well. So this will give slightly wrong positions for the - Format_desc/Rotate/Stop events which the slave writes to its - relay log. For example, the initial Format_desc will have - end_log_pos=91 instead of 95. Because after writing the first 4 - bytes of the relay log, my_b_tell() still reports 0. Because - my_b_append() does not update the counter which my_b_tell() - later uses (one should probably use my_b_append_tell() to work - around this). To get right positions even when writing to the - relay log, we use the (new) my_b_safe_tell(). - - Note that this raises a question on the correctness of all these - DBUG_ASSERT(my_b_tell()=rli->event_relay_log_pos). - - If in a transaction, the log_pos which we calculate below is not - very good (because then my_b_safe_tell() returns start position - of the BEGIN, so it's like the statement was at the BEGIN's - place), but it's not a very serious problem (as the slave, when - it is in a transaction, does not take those end_log_pos into - account (as it calls inc_event_relay_log_pos()). To be fixed - later, so that it looks less strange. But not bug. + Calculate the position of where the next event will start + (end of this event, that is). */ - log_pos= my_b_safe_tell(file)+data_written; + log_pos= writer->pos() + data_written; + + DBUG_EXECUTE_IF("dbug_master_binlog_over_2GB", log_pos += (1ULL <<31);); } now= get_time(); // Query start time @@ -1198,61 +1350,33 @@ bool Log_event::write_header(IO_CACHE* file, ulong event_data_length) int4store(header+ SERVER_ID_OFFSET, server_id); int4store(header+ EVENT_LEN_OFFSET, data_written); int4store(header+ LOG_POS_OFFSET, log_pos); - /* - recording checksum of FD event computed with dropped - possibly active LOG_EVENT_BINLOG_IN_USE_F flag. - Similar step at verication: the active flag is dropped before - checksum computing. - */ - if (header[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT || - !need_checksum() || !(flags & LOG_EVENT_BINLOG_IN_USE_F)) - { - int2store(header+ FLAGS_OFFSET, flags); - ret= wrapper_my_b_safe_write(file, header, sizeof(header)) != 0; - } - else - { - ret= (wrapper_my_b_safe_write(file, header, FLAGS_OFFSET) != 0); - if (!ret) - { - flags &= ~LOG_EVENT_BINLOG_IN_USE_F; - int2store(header + FLAGS_OFFSET, flags); - crc= my_checksum(crc, header + FLAGS_OFFSET, sizeof(flags)); - flags |= LOG_EVENT_BINLOG_IN_USE_F; - int2store(header + FLAGS_OFFSET, flags); - ret= (my_b_safe_write(file, header + FLAGS_OFFSET, sizeof(flags)) != 0); - } - if (!ret) - ret= (wrapper_my_b_safe_write(file, header + FLAGS_OFFSET + sizeof(flags), - sizeof(header) - - (FLAGS_OFFSET + sizeof(flags))) != 0); - } - DBUG_RETURN( ret); + int2store(header + FLAGS_OFFSET, flags); + + bool ret= writer->write_header(header, sizeof(header)); + DBUG_RETURN(ret); } +#endif /* !MYSQL_CLIENT */ /** - This needn't be format-tolerant, because we only read - LOG_EVENT_MINIMAL_HEADER_LEN (we just want to read the event's length). + This needn't be format-tolerant, because we only parse the first + LOG_EVENT_MINIMAL_HEADER_LEN bytes (just need the event's length). */ int Log_event::read_log_event(IO_CACHE* file, String* packet, - mysql_mutex_t* log_lock, - uint8 checksum_alg_arg, - const char *log_file_name_arg, - bool* is_binlog_active) + const Format_description_log_event *fdle, + enum enum_binlog_checksum_alg checksum_alg_arg) { ulong data_len; - int result=0; char buf[LOG_EVENT_MINIMAL_HEADER_LEN]; uchar ev_offset= packet->length(); - DBUG_ENTER("Log_event::read_log_event"); - - if (log_lock) - mysql_mutex_lock(log_lock); - - if (log_file_name_arg) - *is_binlog_active= mysql_bin_log.is_active(log_file_name_arg); +#ifndef max_allowed_packet + THD *thd=current_thd; + ulong max_allowed_packet= thd ? thd->slave_thread ? slave_max_allowed_packet + : thd->variables.max_allowed_packet + : ~(uint)0; +#endif + DBUG_ENTER("Log_event::read_log_event(IO_CACHE*,String*...)"); if (my_b_read(file, (uchar*) buf, sizeof(buf))) { @@ -1262,185 +1386,161 @@ int Log_event::read_log_event(IO_CACHE* file, String* packet, update to the log. */ DBUG_PRINT("error",("file->error: %d", file->error)); - if (!file->error) - result= LOG_READ_EOF; - else - result= (file->error > 0 ? LOG_READ_TRUNC : LOG_READ_IO); - goto end; + DBUG_RETURN(file->error == 0 ? LOG_READ_EOF : + file->error > 0 ? LOG_READ_TRUNC : LOG_READ_IO); } data_len= uint4korr(buf + EVENT_LEN_OFFSET); - if (data_len < LOG_EVENT_MINIMAL_HEADER_LEN || - data_len > max(current_thd->variables.max_allowed_packet, - opt_binlog_rows_event_max_size + MAX_LOG_EVENT_HEADER)) - { - DBUG_PRINT("error",("data_len: %lu", data_len)); - result= ((data_len < LOG_EVENT_MINIMAL_HEADER_LEN) ? LOG_READ_BOGUS : - LOG_READ_TOO_LARGE); - goto end; - } /* Append the log event header to packet */ if (packet->append(buf, sizeof(buf))) - { - /* Failed to allocate packet */ - result= LOG_READ_MEM; - goto end; - } - data_len-= LOG_EVENT_MINIMAL_HEADER_LEN; - if (data_len) + DBUG_RETURN(LOG_READ_MEM); + + if (data_len < LOG_EVENT_MINIMAL_HEADER_LEN) + DBUG_RETURN(LOG_READ_BOGUS); + + if (data_len > max(max_allowed_packet, + opt_binlog_rows_event_max_size + MAX_LOG_EVENT_HEADER)) + DBUG_RETURN(LOG_READ_TOO_LARGE); + + if (likely(data_len > LOG_EVENT_MINIMAL_HEADER_LEN)) { /* Append rest of event, read directly from file into packet */ - if (packet->append(file, data_len)) + if (packet->append(file, data_len - LOG_EVENT_MINIMAL_HEADER_LEN)) { /* - Fatal error occured when appending rest of the event + Fatal error occurred when appending rest of the event to packet, possible failures: - 1. EOF occured when reading from file, it's really an error - as data_len is >=0 there's supposed to be more bytes available. + 1. EOF occurred when reading from file, it's really an error + as there's supposed to be more bytes available. file->error will have been set to number of bytes left to read 2. Read was interrupted, file->error would normally be set to -1 3. Failed to allocate memory for packet, my_errno - will be ENOMEM(file->error shuold be 0, but since the + will be ENOMEM(file->error should be 0, but since the memory allocation occurs before the call to read it might be uninitialized) */ - result= (my_errno == ENOMEM ? LOG_READ_MEM : - (file->error >= 0 ? LOG_READ_TRUNC: LOG_READ_IO)); - /* Implicit goto end; */ - } - else - { - /* Corrupt the event for Dump thread*/ - DBUG_EXECUTE_IF("corrupt_read_log_event2", - uchar *debug_event_buf_c = (uchar*) packet->ptr() + ev_offset; - if (debug_event_buf_c[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT) - { - int debug_cor_pos = rand() % (data_len + sizeof(buf) - BINLOG_CHECKSUM_LEN); - debug_event_buf_c[debug_cor_pos] =~ debug_event_buf_c[debug_cor_pos]; - DBUG_PRINT("info", ("Corrupt the event at Log_event::read_log_event: byte on position %d", debug_cor_pos)); - DBUG_SET("-d,corrupt_read_log_event2"); - } - ); - /* - CRC verification of the Dump thread - */ - if (opt_master_verify_checksum && - event_checksum_test((uchar*) packet->ptr() + ev_offset, - data_len + sizeof(buf), - checksum_alg_arg)) - { - result= LOG_READ_CHECKSUM_FAILURE; - goto end; - } + DBUG_RETURN(my_errno == ENOMEM ? LOG_READ_MEM : + (file->error >= 0 ? LOG_READ_TRUNC: LOG_READ_IO)); } } -end: - if (log_lock) - mysql_mutex_unlock(log_lock); - DBUG_RETURN(result); -} -#endif /* !MYSQL_CLIENT */ + if (fdle->crypto_data.scheme) + { + uchar iv[BINLOG_IV_LENGTH]; + fdle->crypto_data.set_iv(iv, my_b_tell(file) - data_len); -#ifndef MYSQL_CLIENT -#define UNLOCK_MUTEX if (log_lock) mysql_mutex_unlock(log_lock); -#define LOCK_MUTEX if (log_lock) mysql_mutex_lock(log_lock); -#else -#define UNLOCK_MUTEX -#define LOCK_MUTEX -#endif + char *newpkt= (char*)my_malloc(data_len + ev_offset + 1, MYF(MY_WME)); + if (!newpkt) + DBUG_RETURN(LOG_READ_MEM); + memcpy(newpkt, packet->ptr(), ev_offset); + + uint dstlen; + uchar *src= (uchar*)packet->ptr() + ev_offset; + uchar *dst= (uchar*)newpkt + ev_offset; + memcpy(src + EVENT_LEN_OFFSET, src, 4); + if (encryption_crypt(src + 4, data_len - 4, dst + 4, &dstlen, + fdle->crypto_data.key, fdle->crypto_data.key_length, iv, + sizeof(iv), ENCRYPTION_FLAG_DECRYPT | ENCRYPTION_FLAG_NOPAD, + ENCRYPTION_KEY_SYSTEM_DATA, fdle->crypto_data.key_version)) + { + my_free(newpkt); + DBUG_RETURN(LOG_READ_DECRYPT); + } + DBUG_ASSERT(dstlen == data_len - 4); + memcpy(dst, dst + EVENT_LEN_OFFSET, 4); + int4store(dst + EVENT_LEN_OFFSET, data_len); + packet->reset(newpkt, data_len + ev_offset, data_len + ev_offset + 1, + &my_charset_bin); + } -#ifndef MYSQL_CLIENT -/** - @note - Allocates memory; The caller is responsible for clean-up. -*/ -Log_event* Log_event::read_log_event(IO_CACHE* file, - mysql_mutex_t* log_lock, - const Format_description_log_event - *description_event, - my_bool crc_check) -#else -Log_event* Log_event::read_log_event(IO_CACHE* file, - const Format_description_log_event - *description_event, - my_bool crc_check) -#endif -{ - DBUG_ENTER("Log_event::read_log_event"); - DBUG_ASSERT(description_event != 0); - char head[LOG_EVENT_MINIMAL_HEADER_LEN]; /* - First we only want to read at most LOG_EVENT_MINIMAL_HEADER_LEN, just to - check the event for sanity and to know its length; no need to really parse - it. We say "at most" because this could be a 3.23 master, which has header - of 13 bytes, whereas LOG_EVENT_MINIMAL_HEADER_LEN is 19 bytes (it's - "minimal" over the set {MySQL >=4.0}). + CRC verification of the Dump thread */ - uint header_size= min(description_event->common_header_len, - LOG_EVENT_MINIMAL_HEADER_LEN); - - LOCK_MUTEX; - DBUG_PRINT("info", ("my_b_tell: %lu", (ulong) my_b_tell(file))); - if (my_b_read(file, (uchar *) head, header_size)) + if (data_len > LOG_EVENT_MINIMAL_HEADER_LEN) { - DBUG_PRINT("info", ("Log_event::read_log_event(IO_CACHE*,Format_desc*) \ -failed my_b_read")); - UNLOCK_MUTEX; - /* - No error here; it could be that we are at the file's end. However - if the next my_b_read() fails (below), it will be an error as we - were able to read the first bytes. - */ - DBUG_RETURN(0); + /* Corrupt the event for Dump thread*/ + DBUG_EXECUTE_IF("corrupt_read_log_event2", + uchar *debug_event_buf_c = (uchar*) packet->ptr() + ev_offset; + if (debug_event_buf_c[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT) + { + int debug_cor_pos = rand() % (data_len - BINLOG_CHECKSUM_LEN); + debug_event_buf_c[debug_cor_pos] =~ debug_event_buf_c[debug_cor_pos]; + DBUG_PRINT("info", ("Corrupt the event at Log_event::read_log_event: byte on position %d", debug_cor_pos)); + DBUG_SET("-d,corrupt_read_log_event2"); + } + ); + if (event_checksum_test((uchar*) packet->ptr() + ev_offset, + data_len, checksum_alg_arg)) + DBUG_RETURN(LOG_READ_CHECKSUM_FAILURE); } - ulong data_len = uint4korr(head + EVENT_LEN_OFFSET); - char *buf= 0; + DBUG_RETURN(0); +} + +Log_event* Log_event::read_log_event(IO_CACHE* file, mysql_mutex_t* log_lock, + const Format_description_log_event *fdle, + my_bool crc_check) +{ + DBUG_ENTER("Log_event::read_log_event(IO_CACHE*,Format_description_log_event*...)"); + DBUG_ASSERT(fdle != 0); + String event; const char *error= 0; - Log_event *res= 0; -#ifndef max_allowed_packet - THD *thd=current_thd; - uint max_allowed_packet= thd ? slave_max_allowed_packet:~(uint)0; -#endif + Log_event *res= 0; - if (data_len > max(max_allowed_packet, - opt_binlog_rows_event_max_size + MAX_LOG_EVENT_HEADER)) - { - error = "Event too big"; - goto err; - } + if (log_lock) + mysql_mutex_lock(log_lock); - if (data_len < header_size) + switch (read_log_event(file, &event, fdle, BINLOG_CHECKSUM_ALG_OFF)) { - error = "Event too small"; - goto err; + case 0: + break; + case LOG_READ_EOF: // no error here; we are at the file's end + goto err; + case LOG_READ_BOGUS: + error= "Event invalid"; + goto err; + case LOG_READ_IO: + error= "read error"; + goto err; + case LOG_READ_MEM: + error= "Out of memory"; + goto err; + case LOG_READ_TRUNC: + error= "Event truncated"; + goto err; + case LOG_READ_TOO_LARGE: + error= "Event too big"; + goto err; + case LOG_READ_DECRYPT: + error= "Event decryption failure"; + goto err; + case LOG_READ_CHECKSUM_FAILURE: + default: + DBUG_ASSERT(0); + error= "internal error"; + goto err; } - // some events use the extra byte to null-terminate strings - if (!(buf = (char*) my_malloc(data_len+1, MYF(MY_WME)))) - { - error = "Out of memory"; - goto err; - } - buf[data_len] = 0; - memcpy(buf, head, header_size); - if (my_b_read(file, (uchar*) buf + header_size, data_len - header_size)) - { - error = "read error"; - goto err; - } - if ((res= read_log_event(buf, data_len, &error, description_event, crc_check))) - res->register_temp_buf(buf, TRUE); + if ((res= read_log_event(event.ptr(), event.length(), + &error, fdle, crc_check))) + res->register_temp_buf(event.release(), true); err: - UNLOCK_MUTEX; - if (!res) + if (log_lock) + mysql_mutex_unlock(log_lock); + if (error) { - DBUG_ASSERT(error != 0); - sql_print_error("Error in Log_event::read_log_event(): " - "'%s', data_len: %lu, event_type: %d", - error,data_len,head[EVENT_TYPE_OFFSET]); - my_free(buf); + DBUG_ASSERT(!res); +#ifdef MYSQL_CLIENT + if (force_opt) + DBUG_RETURN(new Unknown_log_event()); +#endif + if (event.length() >= OLD_HEADER_LEN) + sql_print_error("Error in Log_event::read_log_event(): '%s'," + " data_len: %lu, event_type: %d", error, + uint4korr(&event[EVENT_LEN_OFFSET]), + (uchar)event[EVENT_TYPE_OFFSET]); + else + sql_print_error("Error in Log_event::read_log_event(): '%s'", error); /* The SQL slave thread will check if file->error<0 to know if there was an I/O error. Even if there is no "low-level" I/O errors @@ -1456,26 +1556,27 @@ err: /** - Binlog format tolerance is in (buf, event_len, description_event) + Binlog format tolerance is in (buf, event_len, fdle) constructors. */ Log_event* Log_event::read_log_event(const char* buf, uint event_len, const char **error, - const Format_description_log_event *description_event, + const Format_description_log_event *fdle, my_bool crc_check) { Log_event* ev; - uint8 alg; + enum enum_binlog_checksum_alg alg; DBUG_ENTER("Log_event::read_log_event(char*,...)"); - DBUG_ASSERT(description_event != 0); - DBUG_PRINT("info", ("binlog_version: %d", description_event->binlog_version)); - DBUG_DUMP("data", (unsigned char*) buf, event_len); + DBUG_ASSERT(fdle != 0); + DBUG_PRINT("info", ("binlog_version: %d", fdle->binlog_version)); + DBUG_DUMP_EVENT_BUF(buf, event_len); - /* Check the integrity */ - if (event_len < EVENT_LEN_OFFSET || - (uchar)buf[EVENT_TYPE_OFFSET] >= ENUM_END_EVENT || - (uint) event_len != uint4korr(buf+EVENT_LEN_OFFSET)) + /* + Check the integrity; This is needed because handle_slave_io() doesn't + check if packet is of proper length. + */ + if (event_len < EVENT_LEN_OFFSET) { *error="Sanity check failed"; // Needed to free buffer DBUG_RETURN(NULL); // general sanity check - will fail on a partial read @@ -1484,16 +1585,16 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, uint event_type= (uchar)buf[EVENT_TYPE_OFFSET]; // all following START events in the current file are without checksum if (event_type == START_EVENT_V3) - (const_cast< Format_description_log_event *>(description_event))->checksum_alg= BINLOG_CHECKSUM_ALG_OFF; + (const_cast< Format_description_log_event *>(fdle))->checksum_alg= BINLOG_CHECKSUM_ALG_OFF; /* CRC verification by SQL and Show-Binlog-Events master side. - The caller has to provide @description_event->checksum_alg to + The caller has to provide @fdle->checksum_alg to be the last seen FD's (A) descriptor. If event is FD the descriptor is in it. Notice, FD of the binlog can be only in one instance and therefore Show-Binlog-Events executing master side thread needs just to know the only FD's (A) value - whereas RL can contain more. - In the RL case, the alg is kept in FD_e (@description_event) which is reset + In the RL case, the alg is kept in FD_e (@fdle) which is reset to the newer read-out event after its execution with possibly new alg descriptor. Therefore in a typical sequence of RL: {FD_s^0, FD_m, E_m^1} E_m^1 @@ -1505,7 +1606,7 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, Notice, a pre-checksum FD version forces alg := BINLOG_CHECKSUM_ALG_UNDEF. */ alg= (event_type != FORMAT_DESCRIPTION_EVENT) ? - description_event->checksum_alg : get_checksum_alg(buf, event_len); + fdle->checksum_alg : get_checksum_alg(buf, event_len); // Emulate the corruption during reading an event DBUG_EXECUTE_IF("corrupt_read_log_event_char", if (event_type != FORMAT_DESCRIPTION_EVENT) @@ -1524,29 +1625,29 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, *error= "Event crc check failed! Most likely there is event corruption."; if (force_opt) { - ev= new Unknown_log_event(buf, description_event); + ev= new Unknown_log_event(buf, fdle); DBUG_RETURN(ev); } else DBUG_RETURN(NULL); #else *error= ER(ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE); - sql_print_error("%s", ER(ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE)); + sql_print_error("%s", *error); DBUG_RETURN(NULL); #endif } - if (event_type > description_event->number_of_event_types && + if (event_type > fdle->number_of_event_types && event_type != FORMAT_DESCRIPTION_EVENT) { /* - It is unsafe to use the description_event if its post_header_len + It is unsafe to use the fdle if its post_header_len array does not include the event type. */ DBUG_PRINT("error", ("event type %d found, but the current " "Format_description_log_event supports only %d event " "types", event_type, - description_event->number_of_event_types)); + fdle->number_of_event_types)); ev= NULL; } else @@ -1561,9 +1662,9 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, array, which was set up when the Format_description_log_event was read. */ - if (description_event->event_type_permutation) + if (fdle->event_type_permutation) { - int new_event_type= description_event->event_type_permutation[event_type]; + int new_event_type= fdle->event_type_permutation[event_type]; DBUG_PRINT("info", ("converting event type %d to %d (%s)", event_type, new_event_type, get_type_str((Log_event_type)new_event_type))); @@ -1574,112 +1675,148 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, (event_type == FORMAT_DESCRIPTION_EVENT || alg != BINLOG_CHECKSUM_ALG_OFF)) event_len= event_len - BINLOG_CHECKSUM_LEN; - + switch(event_type) { case QUERY_EVENT: - ev = new Query_log_event(buf, event_len, description_event, QUERY_EVENT); + ev = new Query_log_event(buf, event_len, fdle, QUERY_EVENT); break; case LOAD_EVENT: - ev = new Load_log_event(buf, event_len, description_event); + ev = new Load_log_event(buf, event_len, fdle); break; case NEW_LOAD_EVENT: - ev = new Load_log_event(buf, event_len, description_event); + ev = new Load_log_event(buf, event_len, fdle); break; case ROTATE_EVENT: - ev = new Rotate_log_event(buf, event_len, description_event); + ev = new Rotate_log_event(buf, event_len, fdle); break; -#ifdef HAVE_REPLICATION - case SLAVE_EVENT: /* can never happen (unused event) */ - ev = new Slave_log_event(buf, event_len, description_event); + case BINLOG_CHECKPOINT_EVENT: + ev = new Binlog_checkpoint_log_event(buf, event_len, fdle); + break; + case GTID_EVENT: + ev = new Gtid_log_event(buf, event_len, fdle); + break; + case GTID_LIST_EVENT: + ev = new Gtid_list_log_event(buf, event_len, fdle); break; -#endif /* HAVE_REPLICATION */ case CREATE_FILE_EVENT: - ev = new Create_file_log_event(buf, event_len, description_event); + ev = new Create_file_log_event(buf, event_len, fdle); break; case APPEND_BLOCK_EVENT: - ev = new Append_block_log_event(buf, event_len, description_event); + ev = new Append_block_log_event(buf, event_len, fdle); break; case DELETE_FILE_EVENT: - ev = new Delete_file_log_event(buf, event_len, description_event); + ev = new Delete_file_log_event(buf, event_len, fdle); break; case EXEC_LOAD_EVENT: - ev = new Execute_load_log_event(buf, event_len, description_event); + ev = new Execute_load_log_event(buf, event_len, fdle); break; case START_EVENT_V3: /* this is sent only by MySQL <=4.x */ - ev = new Start_log_event_v3(buf, event_len, description_event); + ev = new Start_log_event_v3(buf, event_len, fdle); break; case STOP_EVENT: - ev = new Stop_log_event(buf, description_event); + ev = new Stop_log_event(buf, fdle); break; case INTVAR_EVENT: - ev = new Intvar_log_event(buf, description_event); + ev = new Intvar_log_event(buf, fdle); break; case XID_EVENT: - ev = new Xid_log_event(buf, description_event); + ev = new Xid_log_event(buf, fdle); break; case RAND_EVENT: - ev = new Rand_log_event(buf, description_event); + ev = new Rand_log_event(buf, fdle); break; case USER_VAR_EVENT: - ev = new User_var_log_event(buf, event_len, description_event); + ev = new User_var_log_event(buf, event_len, fdle); break; case FORMAT_DESCRIPTION_EVENT: - ev = new Format_description_log_event(buf, event_len, description_event); + ev = new Format_description_log_event(buf, event_len, fdle); break; #if defined(HAVE_REPLICATION) case PRE_GA_WRITE_ROWS_EVENT: - ev = new Write_rows_log_event_old(buf, event_len, description_event); + ev = new Write_rows_log_event_old(buf, event_len, fdle); break; case PRE_GA_UPDATE_ROWS_EVENT: - ev = new Update_rows_log_event_old(buf, event_len, description_event); + ev = new Update_rows_log_event_old(buf, event_len, fdle); break; case PRE_GA_DELETE_ROWS_EVENT: - ev = new Delete_rows_log_event_old(buf, event_len, description_event); + ev = new Delete_rows_log_event_old(buf, event_len, fdle); break; + case WRITE_ROWS_EVENT_V1: case WRITE_ROWS_EVENT: - ev = new Write_rows_log_event(buf, event_len, description_event); + ev = new Write_rows_log_event(buf, event_len, fdle); break; + case UPDATE_ROWS_EVENT_V1: case UPDATE_ROWS_EVENT: - ev = new Update_rows_log_event(buf, event_len, description_event); + ev = new Update_rows_log_event(buf, event_len, fdle); break; + case DELETE_ROWS_EVENT_V1: case DELETE_ROWS_EVENT: - ev = new Delete_rows_log_event(buf, event_len, description_event); + ev = new Delete_rows_log_event(buf, event_len, fdle); break; + + /* MySQL GTID events are ignored */ + case GTID_LOG_EVENT: + case ANONYMOUS_GTID_LOG_EVENT: + case PREVIOUS_GTIDS_LOG_EVENT: + case TRANSACTION_CONTEXT_EVENT: + case VIEW_CHANGE_EVENT: + case XA_PREPARE_LOG_EVENT: + ev= new Ignorable_log_event(buf, fdle, + get_type_str((Log_event_type) event_type)); + break; + case TABLE_MAP_EVENT: - ev = new Table_map_log_event(buf, event_len, description_event); + ev = new Table_map_log_event(buf, event_len, fdle); break; #endif case BEGIN_LOAD_QUERY_EVENT: - ev = new Begin_load_query_log_event(buf, event_len, description_event); + ev = new Begin_load_query_log_event(buf, event_len, fdle); break; case EXECUTE_LOAD_QUERY_EVENT: - ev= new Execute_load_query_log_event(buf, event_len, description_event); + ev= new Execute_load_query_log_event(buf, event_len, fdle); break; case INCIDENT_EVENT: - ev = new Incident_log_event(buf, event_len, description_event); + ev = new Incident_log_event(buf, event_len, fdle); break; case ANNOTATE_ROWS_EVENT: - ev = new Annotate_rows_log_event(buf, event_len, description_event); + ev = new Annotate_rows_log_event(buf, event_len, fdle); break; - default: - DBUG_PRINT("error",("Unknown event code: %d", - (int) buf[EVENT_TYPE_OFFSET])); - ev= NULL; + case START_ENCRYPTION_EVENT: + ev = new Start_encryption_log_event(buf, event_len, fdle); break; + default: + /* + Create an object of Ignorable_log_event for unrecognized sub-class. + So that SLAVE SQL THREAD will only update the position and continue. + */ + if (uint2korr(buf + FLAGS_OFFSET) & LOG_EVENT_IGNORABLE_F) + { + ev= new Ignorable_log_event(buf, fdle, + get_type_str((Log_event_type) event_type)); + } + else + { + DBUG_PRINT("error",("Unknown event code: %d", + (int) buf[EVENT_TYPE_OFFSET])); + ev= NULL; + break; + } } } if (ev) { ev->checksum_alg= alg; +#ifdef MYSQL_CLIENT if (ev->checksum_alg != BINLOG_CHECKSUM_ALG_OFF && ev->checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) ev->crc= uint4korr(buf + (event_len)); +#endif } - DBUG_PRINT("read_event", ("%s(type_code: %d; event_len: %d)", + DBUG_PRINT("read_event", ("%s(type_code: %u; event_len: %u)", ev ? ev->get_type_str() : "<unknown>", - buf[EVENT_TYPE_OFFSET], + (uchar)buf[EVENT_TYPE_OFFSET], event_len)); /* is_valid() are small event-specific sanity tests which are @@ -1703,7 +1840,7 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, *error= "Found invalid event in binary log"; DBUG_RETURN(0); } - ev= new Unknown_log_event(buf, description_event); + ev= new Unknown_log_event(buf, fdle); #else *error= "Found invalid event in binary log"; DBUG_RETURN(0); @@ -1714,6 +1851,165 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, #ifdef MYSQL_CLIENT +static void hexdump_minimal_header_to_io_cache(IO_CACHE *file, + my_off_t offset, + uchar *ptr) +{ + DBUG_ASSERT(LOG_EVENT_MINIMAL_HEADER_LEN == 19); + + /* + Pretty-print the first LOG_EVENT_MINIMAL_HEADER_LEN (19) bytes of the + common header, which contains the basic information about the log event. + Every event will have at least this much header, but events could contain + more headers (which must be printed by other methods, if desired). + */ + char emit_buf[120]; // Enough for storing one line + my_b_printf(file, + "# " + "|Timestamp " + "|Type " + "|Master ID " + "|Size " + "|Master Pos " + "|Flags\n"); + size_t const emit_buf_written= + my_snprintf(emit_buf, sizeof(emit_buf), + "# %8llx " /* Position */ + "|%02x %02x %02x %02x " /* Timestamp */ + "|%02x " /* Type */ + "|%02x %02x %02x %02x " /* Master ID */ + "|%02x %02x %02x %02x " /* Size */ + "|%02x %02x %02x %02x " /* Master Pos */ + "|%02x %02x\n", /* Flags */ + (ulonglong) offset, /* Position */ + ptr[0], ptr[1], ptr[2], ptr[3], /* Timestamp */ + ptr[4], /* Type */ + ptr[5], ptr[6], ptr[7], ptr[8], /* Master ID */ + ptr[9], ptr[10], ptr[11], ptr[12], /* Size */ + ptr[13], ptr[14], ptr[15], ptr[16], /* Master Pos */ + ptr[17], ptr[18]); /* Flags */ + + DBUG_ASSERT(static_cast<size_t>(emit_buf_written) < sizeof(emit_buf)); + my_b_write(file, reinterpret_cast<uchar*>(emit_buf), emit_buf_written); + my_b_write(file, (uchar*)"#\n", 2); +} + + +/* + The number of bytes to print per line. Should be an even number, + and "hexdump -C" uses 16, so we'll duplicate that here. +*/ +#define HEXDUMP_BYTES_PER_LINE 16 + +static void format_hex_line(char *emit_buff) +{ + memset(emit_buff + 1, ' ', + 1 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 + + HEXDUMP_BYTES_PER_LINE); + emit_buff[0]= '#'; + emit_buff[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 1]= '|'; + emit_buff[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 + + HEXDUMP_BYTES_PER_LINE]= '|'; + emit_buff[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 + + HEXDUMP_BYTES_PER_LINE + 1]= '\n'; + emit_buff[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 + + HEXDUMP_BYTES_PER_LINE + 2]= '\0'; +} + +static void hexdump_data_to_io_cache(IO_CACHE *file, + my_off_t offset, + uchar *ptr, + my_off_t size) +{ + /* + 2 = '# ' + 8 = address + 2 = ' ' + (HEXDUMP_BYTES_PER_LINE * 3 + 1) = Each byte prints as two hex digits, + plus a space + 2 = ' |' + HEXDUMP_BYTES_PER_LINE = text representation + 2 = '|\n' + 1 = '\0' + */ + char emit_buffer[2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2 + + HEXDUMP_BYTES_PER_LINE + 2 + 1 ]; + char *h,*c; + my_off_t i; + + if (size == 0) + return; + + format_hex_line(emit_buffer); + /* + Print the rest of the event (without common header) + */ + my_off_t starting_offset = offset; + for (i= 0, + c= emit_buffer + 2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2, + h= emit_buffer + 2 + 8 + 2; + i < size; + i++, ptr++) + { + my_snprintf(h, 4, "%02x ", *ptr); + h+= 3; + + *c++= my_isprint(&my_charset_bin, *ptr) ? *ptr : '.'; + + /* Print in groups of HEXDUMP_BYTES_PER_LINE characters. */ + if ((i % HEXDUMP_BYTES_PER_LINE) == (HEXDUMP_BYTES_PER_LINE - 1)) + { + /* remove \0 left after printing hex byte representation */ + *h= ' '; + /* prepare space to print address */ + memset(emit_buffer + 2, ' ', 8); + /* print address */ + size_t const emit_buf_written= my_snprintf(emit_buffer + 2, 9, "%8llx", + (ulonglong) starting_offset); + /* remove \0 left after printing address */ + emit_buffer[2 + emit_buf_written]= ' '; + my_b_write(file, reinterpret_cast<uchar*>(emit_buffer), + sizeof(emit_buffer) - 1); + c= emit_buffer + 2 + 8 + 2 + (HEXDUMP_BYTES_PER_LINE * 3 + 1) + 2; + h= emit_buffer + 2 + 8 + 2; + format_hex_line(emit_buffer); + starting_offset+= HEXDUMP_BYTES_PER_LINE; + } + else if ((i % (HEXDUMP_BYTES_PER_LINE / 2)) + == ((HEXDUMP_BYTES_PER_LINE / 2) - 1)) + { + /* + In the middle of the group of HEXDUMP_BYTES_PER_LINE, emit an extra + space in the hex string, to make two groups. + */ + *h++= ' '; + } + + } + + /* + There is still data left in our buffer, which means that the previous + line was not perfectly HEXDUMP_BYTES_PER_LINE characters, so write an + incomplete line, with spaces to pad out to the same length as a full + line would be, to make things more readable. + */ + if (h != emit_buffer + 2 + 8 + 2) + { + *h= ' '; + *c++= '|'; *c++= '\n'; + memset(emit_buffer + 2, ' ', 8); + size_t const emit_buf_written= my_snprintf(emit_buffer + 2, 9, "%8llx", + (ulonglong) starting_offset); + emit_buffer[2 + emit_buf_written]= ' '; + /* pad unprinted area */ + memset(h, ' ', + (HEXDUMP_BYTES_PER_LINE * 3 + 1) - (h - (emit_buffer + 2 + 8 + 2))); + my_b_write(file, reinterpret_cast<uchar*>(emit_buffer), + c - emit_buffer); + } + my_b_write(file, (uchar*)"#\n", 2); +} + /* Log_event::print_header() */ @@ -1726,7 +2022,7 @@ void Log_event::print_header(IO_CACHE* file, my_off_t hexdump_from= print_event_info->hexdump_from; DBUG_ENTER("Log_event::print_header"); - my_b_printf(file, "#"); + my_b_write_byte(file, '#'); print_timestamp(file); my_b_printf(file, " server id %lu end_log_pos %s ", (ulong) server_id, llstr(log_pos,llbuff)); @@ -1746,88 +2042,29 @@ void Log_event::print_header(IO_CACHE* file, /* mysqlbinlog --hexdump */ if (print_event_info->hexdump_from) { - my_b_printf(file, "\n"); + my_b_write_byte(file, '\n'); uchar *ptr= (uchar*)temp_buf; - my_off_t size= - uint4korr(ptr + EVENT_LEN_OFFSET) - LOG_EVENT_MINIMAL_HEADER_LEN; - my_off_t i; - - /* Header len * 4 >= header len * (2 chars + space + extra space) */ - char *h, hex_string[LOG_EVENT_MINIMAL_HEADER_LEN*4]= {0}; - char *c, char_string[16+1]= {0}; - - /* Pretty-print event common header if header is exactly 19 bytes */ - if (print_event_info->common_header_len == LOG_EVENT_MINIMAL_HEADER_LEN) - { - char emit_buf[256]; // Enough for storing one line - my_b_printf(file, "# Position Timestamp Type Master ID " - "Size Master Pos Flags \n"); - size_t const bytes_written= - my_snprintf(emit_buf, sizeof(emit_buf), - "# %8.8lx %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x\n", - (unsigned long) hexdump_from, - ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], - ptr[7], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], - ptr[14], ptr[15], ptr[16], ptr[17], ptr[18]); - DBUG_ASSERT(static_cast<size_t>(bytes_written) < sizeof(emit_buf)); - my_b_write(file, (uchar*) emit_buf, bytes_written); - ptr += LOG_EVENT_MINIMAL_HEADER_LEN; - hexdump_from += LOG_EVENT_MINIMAL_HEADER_LEN; - } - - /* Rest of event (without common header) */ - for (i= 0, c= char_string, h=hex_string; - i < size; - i++, ptr++) - { - my_snprintf(h, 4, "%02x ", *ptr); - h += 3; - - *c++= my_isalnum(&my_charset_bin, *ptr) ? *ptr : '.'; - - if (i % 16 == 15) - { - /* - my_b_printf() does not support full printf() formats, so we - have to do it this way. + my_off_t size= uint4korr(ptr + EVENT_LEN_OFFSET); + my_off_t hdr_len= get_header_len(print_event_info->common_header_len); - TODO: Rewrite my_b_printf() to support full printf() syntax. - */ - char emit_buf[256]; - size_t const bytes_written= - my_snprintf(emit_buf, sizeof(emit_buf), - "# %8.8lx %-48.48s |%16s|\n", - (unsigned long) (hexdump_from + (i & 0xfffffff0)), - hex_string, char_string); - DBUG_ASSERT(static_cast<size_t>(bytes_written) < sizeof(emit_buf)); - my_b_write(file, (uchar*) emit_buf, bytes_written); - hex_string[0]= 0; - char_string[0]= 0; - c= char_string; - h= hex_string; - } - else if (i % 8 == 7) *h++ = ' '; - } - *c= '\0'; + size-= hdr_len; + + my_b_printf(file, "# Position\n"); + + /* Write the header, nicely formatted by field. */ + hexdump_minimal_header_to_io_cache(file, hexdump_from, ptr); + + ptr+= hdr_len; + hexdump_from+= hdr_len; + + /* Print the rest of the data, mimicking "hexdump -C" output. */ + hexdump_data_to_io_cache(file, hexdump_from, ptr, size); - if (hex_string[0]) - { - char emit_buf[256]; - size_t const bytes_written= - my_snprintf(emit_buf, sizeof(emit_buf), - "# %8.8lx %-48.48s |%s|\n", - (unsigned long) (hexdump_from + (i & 0xfffffff0)), - hex_string, char_string); - DBUG_ASSERT(static_cast<size_t>(bytes_written) < sizeof(emit_buf)); - my_b_write(file, (uchar*) emit_buf, bytes_written); - } /* - need a # to prefix the rest of printouts for example those of - Rows_log_event::print_helper(). + Prefix the next line so that the output from print_helper() + will appear as a comment. */ - my_b_write(file, reinterpret_cast<const uchar*>("# "), 2); + my_b_write(file, (uchar*)"# Event: ", 9); } DBUG_VOID_RETURN; } @@ -1847,15 +2084,15 @@ static void my_b_write_quoted(IO_CACHE *file, const uchar *ptr, uint length) { const uchar *s; - my_b_printf(file, "'"); + my_b_write_byte(file, '\''); for (s= ptr; length > 0 ; s++, length--) { if (*s > 0x1F) - my_b_write(file, s, 1); + my_b_write_byte(file, *s); else if (*s == '\'') - my_b_write(file, "\\'", 2); + my_b_write(file, (uchar*)"\\'", 2); else if (*s == '\\') - my_b_write(file, "\\\\", 2); + my_b_write(file, (uchar*)"\\\\", 2); else { uchar hex[10]; @@ -1863,7 +2100,7 @@ my_b_write_quoted(IO_CACHE *file, const uchar *ptr, uint length) my_b_write(file, hex, len); } } - my_b_printf(file, "'"); + my_b_write_byte(file, '\''); } @@ -1878,13 +2115,13 @@ static void my_b_write_bit(IO_CACHE *file, const uchar *ptr, uint nbits) { uint bitnum, nbits8= ((nbits + 7) / 8) * 8, skip_bits= nbits8 - nbits; - my_b_printf(file, "b'"); + my_b_write(file, (uchar*)"b'", 2); for (bitnum= skip_bits ; bitnum < nbits8; bitnum++) { int is_set= (ptr[(bitnum) / 8] >> (7 - bitnum % 8)) & 0x01; - my_b_write(file, (const uchar*) (is_set ? "1" : "0"), 1); + my_b_write_byte(file, (is_set ? '1' : '0')); } - my_b_printf(file, "'"); + my_b_write_byte(file, '\''); } @@ -1944,6 +2181,7 @@ my_b_write_sint32_and_uint32(IO_CACHE *file, int32 si, uint32 ui) @param[out] typestr_length Size of typestr @retval - number of bytes scanned from ptr. + Except in case of NULL, in which case we return 1 to indicate ok */ static size_t @@ -1976,52 +2214,68 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, switch (type) { case MYSQL_TYPE_LONG: { + strmake(typestr, "INT", typestr_length); + if (!ptr) + goto return_null; + int32 si= sint4korr(ptr); uint32 ui= uint4korr(ptr); my_b_write_sint32_and_uint32(file, si, ui); - my_snprintf(typestr, typestr_length, "INT"); return 4; } case MYSQL_TYPE_TINY: { + strmake(typestr, "TINYINT", typestr_length); + if (!ptr) + goto return_null; + my_b_write_sint32_and_uint32(file, (int) (signed char) *ptr, (uint) (unsigned char) *ptr); - my_snprintf(typestr, typestr_length, "TINYINT"); return 1; } case MYSQL_TYPE_SHORT: { + strmake(typestr, "SHORTINT", typestr_length); + if (!ptr) + goto return_null; + int32 si= (int32) sint2korr(ptr); uint32 ui= (uint32) uint2korr(ptr); my_b_write_sint32_and_uint32(file, si, ui); - my_snprintf(typestr, typestr_length, "SHORTINT"); return 2; } case MYSQL_TYPE_INT24: { + strmake(typestr, "MEDIUMINT", typestr_length); + if (!ptr) + goto return_null; + int32 si= sint3korr(ptr); uint32 ui= uint3korr(ptr); my_b_write_sint32_and_uint32(file, si, ui); - my_snprintf(typestr, typestr_length, "MEDIUMINT"); return 3; } case MYSQL_TYPE_LONGLONG: { + strmake(typestr, "LONGINT", typestr_length); + if (!ptr) + goto return_null; + char tmp[64]; + size_t length; longlong si= sint8korr(ptr); - longlong10_to_str(si, tmp, -10); - my_b_printf(file, "%s", tmp); + length= (longlong10_to_str(si, tmp, -10) - tmp); + my_b_write(file, (uchar*)tmp, length); if (si < 0) { ulonglong ui= uint8korr(ptr); longlong10_to_str((longlong) ui, tmp, 10); my_b_printf(file, " (%s)", tmp); } - my_snprintf(typestr, typestr_length, "LONGINT"); return 8; } @@ -2029,6 +2283,11 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, { uint precision= meta >> 8; uint decimals= meta & 0xFF; + my_snprintf(typestr, typestr_length, "DECIMAL(%d,%d)", + precision, decimals); + if (!ptr) + goto return_null; + uint bin_size= my_decimal_get_binary_size(precision, decimals); my_decimal dec; binary2my_decimal(E_DEC_FATAL_ERROR, (uchar*) ptr, &dec, @@ -2037,30 +2296,34 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, char buff[DECIMAL_MAX_STR_LENGTH + 1]; decimal2string(&dec, buff, &length, 0, 0, 0); my_b_write(file, (uchar*)buff, length); - my_snprintf(typestr, typestr_length, "DECIMAL(%d,%d)", - precision, decimals); return bin_size; } case MYSQL_TYPE_FLOAT: { + strmake(typestr, "FLOAT", typestr_length); + if (!ptr) + goto return_null; + float fl; float4get(fl, ptr); char tmp[320]; sprintf(tmp, "%-20g", (double) fl); my_b_printf(file, "%s", tmp); /* my_snprintf doesn't support %-20g */ - my_snprintf(typestr, typestr_length, "FLOAT"); return 4; } case MYSQL_TYPE_DOUBLE: { double dbl; + strmake(typestr, "DOUBLE", typestr_length); + if (!ptr) + goto return_null; + float8get(dbl, ptr); char tmp[320]; - sprintf(tmp, "%-.20g", dbl); /* my_snprintf doesn't support %-20g */ - my_b_printf(file, "%s", tmp); - strcpy(typestr, "DOUBLE"); + sprintf(tmp, "%-.20g", dbl); /* strmake doesn't support %-20g */ + my_b_printf(file, tmp, "%s"); return 8; } @@ -2068,22 +2331,46 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, { /* Meta-data: bit_len, bytes_in_rec, 2 bytes */ uint nbits= ((meta >> 8) * 8) + (meta & 0xFF); + my_snprintf(typestr, typestr_length, "BIT(%d)", nbits); + if (!ptr) + goto return_null; + length= (nbits + 7) / 8; my_b_write_bit(file, ptr, nbits); - my_snprintf(typestr, typestr_length, "BIT(%d)", nbits); return length; } case MYSQL_TYPE_TIMESTAMP: { + strmake(typestr, "TIMESTAMP", typestr_length); + if (!ptr) + goto return_null; + uint32 i32= uint4korr(ptr); my_b_printf(file, "%d", i32); - my_snprintf(typestr, typestr_length, "TIMESTAMP"); return 4; } + case MYSQL_TYPE_TIMESTAMP2: + { + my_snprintf(typestr, typestr_length, "TIMESTAMP(%d)", meta); + if (!ptr) + goto return_null; + + char buf[MAX_DATE_STRING_REP_LENGTH]; + struct timeval tm; + my_timestamp_from_binary(&tm, ptr, meta); + int buflen= my_timeval_to_str(&tm, buf, meta); + my_b_write(file, (uchar*)buf, buflen); + return my_timestamp_binary_length(meta); + } + case MYSQL_TYPE_DATETIME: { + strmake(typestr, "DATETIME", typestr_length); + if (!ptr) + goto return_null; + ulong d, t; uint64 i64= uint8korr(ptr); /* YYYYMMDDhhmmss */ d= (ulong) (i64 / 1000000); @@ -2092,21 +2379,59 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, my_b_printf(file, "%04d-%02d-%02d %02d:%02d:%02d", (int) (d / 10000), (int) (d % 10000) / 100, (int) (d % 100), (int) (t / 10000), (int) (t % 10000) / 100, (int) t % 100); - my_snprintf(typestr, typestr_length, "DATETIME"); return 8; } + case MYSQL_TYPE_DATETIME2: + { + my_snprintf(typestr, typestr_length, "DATETIME(%d)", meta); + if (!ptr) + goto return_null; + + char buf[MAX_DATE_STRING_REP_LENGTH]; + MYSQL_TIME ltime; + longlong packed= my_datetime_packed_from_binary(ptr, meta); + TIME_from_longlong_datetime_packed(<ime, packed); + int buflen= my_datetime_to_str(<ime, buf, meta); + my_b_write_quoted(file, (uchar *) buf, buflen); + return my_datetime_binary_length(meta); + } + case MYSQL_TYPE_TIME: { - uint32 i32= uint3korr(ptr); - my_b_printf(file, "'%02d:%02d:%02d'", - i32 / 10000, (i32 % 10000) / 100, i32 % 100); - my_snprintf(typestr, typestr_length, "TIME"); + strmake(typestr, "TIME", typestr_length); + if (!ptr) + goto return_null; + + int32 tmp= sint3korr(ptr); + int32 i32= tmp >= 0 ? tmp : - tmp; + const char *sign= tmp < 0 ? "-" : ""; + my_b_printf(file, "'%s%02d:%02d:%02d'", + sign, i32 / 10000, (i32 % 10000) / 100, i32 % 100, i32); return 3; } - + + case MYSQL_TYPE_TIME2: + { + my_snprintf(typestr, typestr_length, "TIME(%d)", meta); + if (!ptr) + goto return_null; + + char buf[MAX_DATE_STRING_REP_LENGTH]; + MYSQL_TIME ltime; + longlong packed= my_time_packed_from_binary(ptr, meta); + TIME_from_longlong_time_packed(<ime, packed); + int buflen= my_time_to_str(<ime, buf, meta); + my_b_write_quoted(file, (uchar *) buf, buflen); + return my_time_binary_length(meta); + } + case MYSQL_TYPE_NEWDATE: { + strmake(typestr, "DATE", typestr_length); + if (!ptr) + goto return_null; + uint32 tmp= uint3korr(ptr); int part; char buf[11]; @@ -2128,38 +2453,50 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, *pos--= (char) ('0'+part%10); part/=10; *pos= (char) ('0'+part); my_b_printf(file , "'%s'", buf); - my_snprintf(typestr, typestr_length, "DATE"); return 3; } case MYSQL_TYPE_DATE: { + strmake(typestr, "DATE", typestr_length); + if (!ptr) + goto return_null; + uint i32= uint3korr(ptr); my_b_printf(file , "'%04d:%02d:%02d'", - (i32 / (16L * 32L)), (i32 / 32L % 16L), (i32 % 32L)); - my_snprintf(typestr, typestr_length, "DATE"); + (int)(i32 / (16L * 32L)), (int)(i32 / 32L % 16L), + (int)(i32 % 32L)); return 3; } case MYSQL_TYPE_YEAR: { + strmake(typestr, "YEAR", typestr_length); + if (!ptr) + goto return_null; + uint32 i32= *ptr; my_b_printf(file, "%04d", i32+ 1900); - my_snprintf(typestr, typestr_length, "YEAR"); return 1; } case MYSQL_TYPE_ENUM: switch (meta & 0xFF) { case 1: + strmake(typestr, "ENUM(1 byte)", typestr_length); + if (!ptr) + goto return_null; + my_b_printf(file, "%d", (int) *ptr); - my_snprintf(typestr, typestr_length, "ENUM(1 byte)"); return 1; case 2: { + strmake(typestr, "ENUM(2 bytes)", typestr_length); + if (!ptr) + goto return_null; + int32 i32= uint2korr(ptr); my_b_printf(file, "%d", i32); - my_snprintf(typestr, typestr_length, "ENUM(2 bytes)"); return 2; } default: @@ -2169,31 +2506,46 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, break; case MYSQL_TYPE_SET: - my_b_write_bit(file, ptr , (meta & 0xFF) * 8); my_snprintf(typestr, typestr_length, "SET(%d bytes)", meta & 0xFF); + if (!ptr) + goto return_null; + + my_b_write_bit(file, ptr , (meta & 0xFF) * 8); return meta & 0xFF; case MYSQL_TYPE_BLOB: switch (meta) { case 1: + strmake(typestr, "TINYBLOB/TINYTEXT", typestr_length); + if (!ptr) + goto return_null; + length= *ptr; my_b_write_quoted(file, ptr + 1, length); - my_snprintf(typestr, typestr_length, "TINYBLOB/TINYTEXT"); return length + 1; case 2: + strmake(typestr, "BLOB/TEXT", typestr_length); + if (!ptr) + goto return_null; + length= uint2korr(ptr); my_b_write_quoted(file, ptr + 2, length); - my_snprintf(typestr, typestr_length, "BLOB/TEXT"); return length + 2; case 3: + strmake(typestr, "MEDIUMBLOB/MEDIUMTEXT", typestr_length); + if (!ptr) + goto return_null; + length= uint3korr(ptr); my_b_write_quoted(file, ptr + 3, length); - my_snprintf(typestr, typestr_length, "MEDIUMBLOB/MEDIUMTEXT"); return length + 3; case 4: + strmake(typestr, "LONGBLOB/LONGTEXT", typestr_length); + if (!ptr) + goto return_null; + length= uint4korr(ptr); my_b_write_quoted(file, ptr + 4, length); - my_snprintf(typestr, typestr_length, "LONGBLOB/LONGTEXT"); return length + 4; default: my_b_printf(file, "!! Unknown BLOB packlen=%d", length); @@ -2204,10 +2556,16 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, case MYSQL_TYPE_VAR_STRING: length= meta; my_snprintf(typestr, typestr_length, "VARSTRING(%d)", length); + if (!ptr) + goto return_null; + return my_b_write_quoted_with_length(file, ptr, length); case MYSQL_TYPE_STRING: my_snprintf(typestr, typestr_length, "STRING(%d)", length); + if (!ptr) + goto return_null; + return my_b_write_quoted_with_length(file, ptr, length); case MYSQL_TYPE_DECIMAL: @@ -2228,6 +2586,9 @@ log_event_print_value(IO_CACHE *file, const uchar *ptr, } *typestr= 0; return 0; + +return_null: + return my_b_write(file, (uchar*) "NULL", 4) ? 0 : 1; } @@ -2256,25 +2617,27 @@ Rows_log_event::print_verbose_one_row(IO_CACHE *file, table_def *td, uint null_bit_index= 0; char typestr[64]= ""; - value+= (m_width + 7) / 8; + /* + Skip metadata bytes which gives the information about nullabity of master + columns. Master writes one bit for each affected column. + */ + + value+= (bitmap_bits_set(cols_bitmap) + 7) / 8; my_b_printf(file, "%s", prefix); for (size_t i= 0; i < td->size(); i ++) { + size_t size; int is_null= (null_bits[null_bit_index / 8] >> (null_bit_index % 8)) & 0x01; if (bitmap_is_set(cols_bitmap, i) == 0) continue; - if (is_null) + my_b_printf(file, "### @%d=", static_cast<int>(i + 1)); + if (!is_null) { - my_b_printf(file, "### @%d=NULL", i + 1); - } - else - { - my_b_printf(file, "### @%d=", i + 1); size_t fsize= td->calc_field_size((uint)i, (uchar*) value); if (value + fsize > m_rows_end) { @@ -2283,31 +2646,28 @@ Rows_log_event::print_verbose_one_row(IO_CACHE *file, table_def *td, value+= fsize; return 0; } - size_t size= log_event_print_value(file, value, - td->type(i), td->field_metadata(i), - typestr, sizeof(typestr)); - if (!size) - return 0; + } + if (!(size= log_event_print_value(file,is_null? NULL: value, + td->type(i), td->field_metadata(i), + typestr, sizeof(typestr)))) + return 0; + if (!is_null) value+= size; - } if (print_event_info->verbose > 1) { - my_b_printf(file, " /* "); + my_b_write(file, (uchar*)" /* ", 4); - if (typestr[0]) - my_b_printf(file, "%s ", typestr); - else - my_b_printf(file, "type=%d ", td->type(i)); + my_b_printf(file, "%s ", typestr); my_b_printf(file, "meta=%d nullable=%d is_null=%d ", td->field_metadata(i), td->maybe_null(i), is_null); - my_b_printf(file, "*/"); + my_b_write(file, (uchar*)"*/", 2); } - my_b_printf(file, "\n"); + my_b_write_byte(file, '\n'); null_bit_index++; } @@ -2327,9 +2687,33 @@ void Rows_log_event::print_verbose(IO_CACHE *file, Table_map_log_event *map; table_def *td; const char *sql_command, *sql_clause1, *sql_clause2; - Log_event_type type_code= get_type_code(); + Log_event_type general_type_code= get_general_type_code(); - switch (type_code) { + if (m_extra_row_data) + { + uint8 extra_data_len= m_extra_row_data[EXTRA_ROW_INFO_LEN_OFFSET]; + uint8 extra_payload_len= extra_data_len - EXTRA_ROW_INFO_HDR_BYTES; + assert(extra_data_len >= EXTRA_ROW_INFO_HDR_BYTES); + + my_b_printf(file, "### Extra row data format: %u, len: %u :", + m_extra_row_data[EXTRA_ROW_INFO_FORMAT_OFFSET], + extra_payload_len); + if (extra_payload_len) + { + /* + Buffer for hex view of string, including '0x' prefix, + 2 hex chars / byte and trailing 0 + */ + const int buff_len= 2 + (256 * 2) + 1; + char buff[buff_len]; + str_to_hex(buff, (const char*) &m_extra_row_data[EXTRA_ROW_INFO_HDR_BYTES], + extra_payload_len); + my_b_printf(file, "%s", buff); + } + my_b_printf(file, "\n"); + } + + switch (general_type_code) { case WRITE_ROWS_EVENT: sql_command= "INSERT INTO"; sql_clause1= "### SET\n"; @@ -2353,10 +2737,19 @@ void Rows_log_event::print_verbose(IO_CACHE *file, if (!(map= print_event_info->m_table_map.get_table(m_table_id)) || !(td= map->create_table_def())) { - my_b_printf(file, "### Row event for unknown table #%d", m_table_id); + my_b_printf(file, "### Row event for unknown table #%lu", + (ulong) m_table_id); return; } + /* If the write rows event contained no values for the AI */ + if (((general_type_code == WRITE_ROWS_EVENT) && (m_rows_buf==m_rows_end))) + { + my_b_printf(file, "### INSERT INTO %`s.%`s VALUES ()\n", + map->get_db_name(), map->get_table_name()); + goto end; + } + for (const uchar *value= m_rows_buf; value < m_rows_end; ) { size_t length; @@ -2390,9 +2783,23 @@ void free_table_map_log_event(Table_map_log_event *event) delete event; } +/** + Encode the event, optionally per 'do_print_encoded' arg store the + result into the argument cache; optionally per event_info's + 'verbose' print into the cache a verbose representation of the event. + Note, no extra wrapping is done to the being io-cached data, like + to producing a BINLOG query. It's left for a routine that extracts from + the cache. + + @param file pointer to IO_CACHE + @param print_event_info pointer to print_event_info specializing + what out of and how to print the event + @param do_print_encoded whether to store base64-encoded event + into @file. +*/ void Log_event::print_base64(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info, - bool more) + bool do_print_encoded) { const uchar *ptr= (const uchar *)temp_buf; uint32 size= uint4korr(ptr + EVENT_LEN_OFFSET); @@ -2411,45 +2818,51 @@ void Log_event::print_base64(IO_CACHE* file, DBUG_ASSERT(0); } - if (print_event_info->base64_output_mode != BASE64_OUTPUT_DECODE_ROWS) - { - if (my_b_tell(file) == 0) - my_b_printf(file, "\nBINLOG '\n"); - + if (do_print_encoded) my_b_printf(file, "%s\n", tmp_str); - if (!more) - my_b_printf(file, "'%s\n", print_event_info->delimiter); - } - if (print_event_info->verbose) { Rows_log_event *ev= NULL; + Log_event_type et= (Log_event_type) ptr[EVENT_TYPE_OFFSET]; + if (checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF && checksum_alg != BINLOG_CHECKSUM_ALG_OFF) size-= BINLOG_CHECKSUM_LEN; // checksum is displayed through the header - if (ptr[4] == TABLE_MAP_EVENT) + switch (et) + { + case TABLE_MAP_EVENT: { Table_map_log_event *map; map= new Table_map_log_event((const char*) ptr, size, glob_description_event); print_event_info->m_table_map.set_table(map->get_table_id(), map); + break; } - else if (ptr[4] == WRITE_ROWS_EVENT) + case WRITE_ROWS_EVENT: + case WRITE_ROWS_EVENT_V1: { ev= new Write_rows_log_event((const char*) ptr, size, glob_description_event); + break; } - else if (ptr[4] == DELETE_ROWS_EVENT) + case DELETE_ROWS_EVENT: + case DELETE_ROWS_EVENT_V1: { ev= new Delete_rows_log_event((const char*) ptr, size, glob_description_event); + break; } - else if (ptr[4] == UPDATE_ROWS_EVENT) + case UPDATE_ROWS_EVENT: + case UPDATE_ROWS_EVENT_V1: { ev= new Update_rows_log_event((const char*) ptr, size, glob_description_event); + break; + } + default: + break; } if (ev) @@ -2492,11 +2905,11 @@ void Log_event::print_timestamp(IO_CACHE* file, time_t* ts) #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) inline Log_event::enum_skip_reason -Log_event::continue_group(Relay_log_info *rli) +Log_event::continue_group(rpl_group_info *rgi) { - if (rli->slave_skip_counter == 1) + if (rgi->rli->slave_skip_counter == 1) return Log_event::EVENT_SKIP_IGNORE; - return Log_event::do_shall_skip(rli); + return Log_event::do_shall_skip(rgi); } #endif @@ -2515,7 +2928,7 @@ Log_event::continue_group(Relay_log_info *rli) show the catalog ?? */ -void Query_log_event::pack_info(THD *thd, Protocol *protocol) +void Query_log_event::pack_info(Protocol *protocol) { // TODO: show the catalog ?? char buf_mem[1024]; @@ -2525,7 +2938,7 @@ void Query_log_event::pack_info(THD *thd, Protocol *protocol) && db && db_len) { buf.append(STRING_WITH_LEN("use ")); - append_identifier(thd, &buf, db, db_len); + append_identifier(protocol->thd, &buf, db, db_len); buf.append(STRING_WITH_LEN("; ")); } if (query && q_len) @@ -2539,7 +2952,7 @@ void Query_log_event::pack_info(THD *thd, Protocol *protocol) /** Utility function for the next method (Query_log_event::write()) . */ -static void write_str_with_code_and_len(uchar **dst, const char *src, +static void store_str_with_code_and_len(uchar **dst, const char *src, uint len, uint code) { /* @@ -2564,7 +2977,7 @@ static void write_str_with_code_and_len(uchar **dst, const char *src, will print! */ -bool Query_log_event::write(IO_CACHE* file) +bool Query_log_event::write() { uchar buf[QUERY_HEADER_LEN + MAX_SIZE_LOG_EVENT_STATUS]; uchar *start, *start_of_status; @@ -2635,7 +3048,7 @@ bool Query_log_event::write(IO_CACHE* file) } if (catalog_len) // i.e. this var is inited (false for 4.0 events) { - write_str_with_code_and_len(&start, + store_str_with_code_and_len(&start, catalog, catalog_len, Q_CATALOG_NZ_CODE); /* In 5.0.x where x<4 masters we used to store the end zero here. This was @@ -2673,7 +3086,7 @@ bool Query_log_event::write(IO_CACHE* file) { /* In the TZ sys table, column Name is of length 64 so this should be ok */ DBUG_ASSERT(time_zone_len <= MAX_TIME_ZONE_NAME_LENGTH); - write_str_with_code_and_len(&start, + store_str_with_code_and_len(&start, time_zone_str, time_zone_len, Q_TIME_ZONE_CODE); } if (lc_time_names_number) @@ -2721,17 +3134,22 @@ bool Query_log_event::write(IO_CACHE* file) user= thd->get_invoker_user(); host= thd->get_invoker_host(); } - else if (thd->security_ctx->priv_user) + else { Security_context *ctx= thd->security_ctx; - user.length= strlen(ctx->priv_user); - user.str= ctx->priv_user; - if (ctx->priv_host[0] != '\0') + if (thd->need_binlog_invoker() == THD::INVOKER_USER) { + user.str= ctx->priv_user; host.str= ctx->priv_host; - host.length= strlen(ctx->priv_host); + host.length= strlen(host.str); + } + else + { + user.str= ctx->priv_role; + host= empty_lex_str; } + user.length= strlen(user.str); } if (user.length > 0) @@ -2788,14 +3206,13 @@ bool Query_log_event::write(IO_CACHE* file) */ event_length= (uint) (start-buf) + get_post_header_size_for_derived() + db_len + 1 + q_len; - return (write_header(file, event_length) || - wrapper_my_b_safe_write(file, (uchar*) buf, QUERY_HEADER_LEN) || - write_post_header_for_derived(file) || - wrapper_my_b_safe_write(file, (uchar*) start_of_status, - (uint) (start-start_of_status)) || - wrapper_my_b_safe_write(file, (db) ? (uchar*) db : (uchar*)"", db_len + 1) || - wrapper_my_b_safe_write(file, (uchar*) query, q_len) || - write_footer(file)) ? 1 : 0; + return write_header(event_length) || + write_data(buf, QUERY_HEADER_LEN) || + write_post_header_for_derived() || + write_data(start_of_status, (uint) (start-start_of_status)) || + write_data(safe_str(db), db_len + 1) || + write_data(query, q_len) || + write_footer(); } /** @@ -2818,6 +3235,7 @@ Query_log_event::Query_log_event() query_arg - array of char representing the query query_length - size of the `query_arg' array using_trans - there is a modified transactional table + direct - Don't cache statement suppress_use - suppress the generation of 'USE' statements errcode - the error code of the query @@ -2850,6 +3268,15 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg, { time_t end_time; +#ifdef WITH_WSREP + /* + If Query_log_event will contain non trans keyword (not BEGIN, COMMIT, + SAVEPOINT or ROLLBACK) we disable PA for this transaction. + */ + if (WSREP_ON && !is_trans_keyword()) + thd->wsrep_PA_safe= false; +#endif /* WITH_WSREP */ + memset(&user, 0, sizeof(user)); memset(&host, 0, sizeof(host)); @@ -2932,17 +3359,27 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg, switch (lex->sql_command) { case SQLCOM_DROP_TABLE: - use_cache= (lex->drop_temporary && thd->in_multi_stmt_transaction_mode()); + use_cache= (lex->tmp_table() && thd->in_multi_stmt_transaction_mode()); break; case SQLCOM_CREATE_TABLE: + /* + If we are using CREATE ... SELECT or if we are a slave + executing BEGIN...COMMIT (generated by CREATE...SELECT) we + have to use the transactional cache to ensure we don't + calculate any checksum for the CREATE part. + */ trx_cache= (lex->select_lex.item_list.elements && - thd->is_current_stmt_binlog_format_row()); - use_cache= ((lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) && - thd->in_multi_stmt_transaction_mode()) || trx_cache; + thd->is_current_stmt_binlog_format_row()) || + (thd->variables.option_bits & OPTION_GTID_BEGIN); + use_cache= (lex->tmp_table() && + thd->in_multi_stmt_transaction_mode()) || trx_cache; break; case SQLCOM_SET_OPTION: - use_cache= trx_cache= (lex->autocommit ? FALSE : TRUE); + if (lex->autocommit) + use_cache= trx_cache= FALSE; + else + use_cache= TRUE; break; case SQLCOM_RELEASE_SAVEPOINT: case SQLCOM_ROLLBACK_TO_SAVEPOINT: @@ -2967,8 +3404,8 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg, else cache_type= Log_event::EVENT_STMT_CACHE; DBUG_ASSERT(cache_type != Log_event::EVENT_INVALID_CACHE); - DBUG_PRINT("info",("Query_log_event has flags2: %lu sql_mode: %llu", - (ulong) flags2, sql_mode)); + DBUG_PRINT("info",("Query_log_event has flags2: %lu sql_mode: %llu cache_tye: %d", + (ulong) flags2, sql_mode, cache_type)); } #endif /* MYSQL_CLIENT */ @@ -3132,7 +3569,7 @@ Query_log_event::Query_log_event(const char* buf, uint event_len, be even bigger, but this will suffice to catch most corruption errors that can lead to a crash. */ - if (status_vars_len > min(data_len, MAX_SIZE_LOG_EVENT_STATUS)) + if (status_vars_len > MY_MIN(data_len, MAX_SIZE_LOG_EVENT_STATUS)) { DBUG_PRINT("info", ("status_vars_len (%u) > data_len (%lu); query= 0", status_vars_len, data_len)); @@ -3178,14 +3615,10 @@ Query_log_event::Query_log_event(const char* buf, uint event_len, break; case Q_SQL_MODE_CODE: { -#ifndef DBUG_OFF - char buff[22]; -#endif CHECK_SPACE(pos, end, 8); sql_mode_inited= 1; sql_mode= (ulong) uint8korr(pos); // QQ: Fix when sql_mode is ulonglong - DBUG_PRINT("info",("In Query_log_event, read sql_mode: %s", - llstr(sql_mode, buff))); + DBUG_PRINT("info",("In Query_log_event, read sql_mode: %llu", sql_mode)); pos+= 8; break; } @@ -3336,10 +3769,25 @@ Query_log_event::Query_log_event(const char* buf, uint event_len, if (time_zone_len) copy_str_and_move(&time_zone_str, &start, time_zone_len); - if (user.length > 0) + if (user.length) + { copy_str_and_move((const char **)&(user.str), &start, user.length); - if (host.length > 0) + } + else + { + user.str= (char *) start++; + user.str[0]= '\0'; + } + + if (host.length) + { copy_str_and_move((const char **)&(host.str), &start, host.length); + } + else + { + host.str= (char *) start++; + host.str[0]= '\0'; + } /** if time_zone_len or catalog_len are 0, then time_zone and catalog @@ -3364,9 +3812,8 @@ Query_log_event::Query_log_event(const char* buf, uint event_len, DBUG_VOID_RETURN; } - unsigned int max_length; - max_length= (event_len - ((const char*)(end + db_len + 1) - - (buf - common_header_len))); + uint32 max_length= uint32(event_len - ((const char*)(end + db_len + 1) - + (buf - common_header_len))); if (q_len != max_length) { q_len= 0; @@ -3385,6 +3832,180 @@ Query_log_event::Query_log_event(const char* buf, uint event_len, } +/* + Replace a binlog event read into a packet with a dummy event. Either a + Query_log_event that has just a comment, or if that will not fit in the + space used for the event to be replaced, then a NULL user_var event. + + This is used when sending binlog data to a slave which does not understand + this particular event and which is too old to support informational events + or holes in the event stream. + + This allows to write such events into the binlog on the master and still be + able to replicate against old slaves without them breaking. + + Clears the flag LOG_EVENT_THREAD_SPECIFIC_F and set LOG_EVENT_SUPPRESS_USE_F. + Overwrites the type with QUERY_EVENT (or USER_VAR_EVENT), and replaces the + body with a minimal query / NULL user var. + + Returns zero on success, -1 if error due to too little space in original + event. A minimum of 25 bytes (19 bytes fixed header + 6 bytes in the body) + is needed in any event to be replaced with a dummy event. +*/ +int +Query_log_event::dummy_event(String *packet, ulong ev_offset, + enum enum_binlog_checksum_alg checksum_alg) +{ + uchar *p= (uchar *)packet->ptr() + ev_offset; + size_t data_len= packet->length() - ev_offset; + uint16 flags; + static const size_t min_user_var_event_len= + LOG_EVENT_HEADER_LEN + UV_NAME_LEN_SIZE + 1 + UV_VAL_IS_NULL; // 25 + static const size_t min_query_event_len= + LOG_EVENT_HEADER_LEN + QUERY_HEADER_LEN + 1 + 1; // 34 + + if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + data_len-= BINLOG_CHECKSUM_LEN; + else + DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || + checksum_alg == BINLOG_CHECKSUM_ALG_OFF); + + if (data_len < min_user_var_event_len) + /* Cannot replace with dummy, event too short. */ + return -1; + + flags= uint2korr(p + FLAGS_OFFSET); + flags&= ~LOG_EVENT_THREAD_SPECIFIC_F; + flags|= LOG_EVENT_SUPPRESS_USE_F; + int2store(p + FLAGS_OFFSET, flags); + + if (data_len < min_query_event_len) + { + /* + Have to use dummy user_var event for such a short packet. + + This works, but the event will be considered part of an event group with + the following event. So for example @@global.sql_slave_skip_counter=1 + will skip not only the dummy event, but also the immediately following + event. + + We write a NULL user var with the name @`!dummyvar` (or as much + as that as will fit within the size of the original event - so + possibly just @`!`). + */ + static const char var_name[]= "!dummyvar"; + uint name_len= data_len - (min_user_var_event_len - 1); + + p[EVENT_TYPE_OFFSET]= USER_VAR_EVENT; + int4store(p + LOG_EVENT_HEADER_LEN, name_len); + memcpy(p + LOG_EVENT_HEADER_LEN + UV_NAME_LEN_SIZE, var_name, name_len); + p[LOG_EVENT_HEADER_LEN + UV_NAME_LEN_SIZE + name_len]= 1; // indicates NULL + } + else + { + /* + Use a dummy query event, just a comment. + */ + static const char message[]= + "# Dummy event replacing event type %u that slave cannot handle."; + char buf[sizeof(message)+1]; /* +1, as %u can expand to 3 digits. */ + uchar old_type= p[EVENT_TYPE_OFFSET]; + uchar *q= p + LOG_EVENT_HEADER_LEN; + size_t comment_len, len; + + p[EVENT_TYPE_OFFSET]= QUERY_EVENT; + int4store(q + Q_THREAD_ID_OFFSET, 0); + int4store(q + Q_EXEC_TIME_OFFSET, 0); + q[Q_DB_LEN_OFFSET]= 0; + int2store(q + Q_ERR_CODE_OFFSET, 0); + int2store(q + Q_STATUS_VARS_LEN_OFFSET, 0); + q[Q_DATA_OFFSET]= 0; /* Zero terminator for empty db */ + q+= Q_DATA_OFFSET + 1; + len= my_snprintf(buf, sizeof(buf), message, old_type); + comment_len= data_len - (min_query_event_len - 1); + if (comment_len <= len) + memcpy(q, buf, comment_len); + else + { + memcpy(q, buf, len); + memset(q+len, ' ', comment_len - len); + } + } + + if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + { + ha_checksum crc= my_checksum(0, p, data_len); + int4store(p + data_len, crc); + } + return 0; +} + +/* + Replace an event (GTID event) with a BEGIN query event, to be compatible + with an old slave. +*/ +int +Query_log_event::begin_event(String *packet, ulong ev_offset, + enum enum_binlog_checksum_alg checksum_alg) +{ + uchar *p= (uchar *)packet->ptr() + ev_offset; + uchar *q= p + LOG_EVENT_HEADER_LEN; + size_t data_len= packet->length() - ev_offset; + uint16 flags; + + if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + data_len-= BINLOG_CHECKSUM_LEN; + else + DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || + checksum_alg == BINLOG_CHECKSUM_ALG_OFF); + + /* + Currently we only need to replace GTID event. + The length of GTID differs depending on whether it contains commit id. + */ + DBUG_ASSERT(data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN || + data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN + 2); + if (data_len != LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN && + data_len != LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN + 2) + return 1; + + flags= uint2korr(p + FLAGS_OFFSET); + flags&= ~LOG_EVENT_THREAD_SPECIFIC_F; + flags|= LOG_EVENT_SUPPRESS_USE_F; + int2store(p + FLAGS_OFFSET, flags); + + p[EVENT_TYPE_OFFSET]= QUERY_EVENT; + int4store(q + Q_THREAD_ID_OFFSET, 0); + int4store(q + Q_EXEC_TIME_OFFSET, 0); + q[Q_DB_LEN_OFFSET]= 0; + int2store(q + Q_ERR_CODE_OFFSET, 0); + if (data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN) + { + int2store(q + Q_STATUS_VARS_LEN_OFFSET, 0); + q[Q_DATA_OFFSET]= 0; /* Zero terminator for empty db */ + q+= Q_DATA_OFFSET + 1; + } + else + { + DBUG_ASSERT(data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN + 2); + /* Put in an empty time_zone_str to take up the extra 2 bytes. */ + int2store(q + Q_STATUS_VARS_LEN_OFFSET, 2); + q[Q_DATA_OFFSET]= Q_TIME_ZONE_CODE; + q[Q_DATA_OFFSET+1]= 0; /* Zero length for empty time_zone_str */ + q[Q_DATA_OFFSET+2]= 0; /* Zero terminator for empty db */ + q+= Q_DATA_OFFSET + 3; + } + memcpy(q, "BEGIN", 5); + + if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + { + ha_checksum crc= my_checksum(0, p, data_len); + int4store(p + data_len, crc); + } + return 0; +} + + #ifdef MYSQL_CLIENT /** Query_log_event::print(). @@ -3463,7 +4084,7 @@ void Query_log_event::print_query_header(IO_CACHE* file, if (unlikely(tmp)) /* some bits have changed */ { bool need_comma= 0; - my_b_printf(file, "SET "); + my_b_write_string(file, "SET "); print_set_option(file, tmp, OPTION_NO_FOREIGN_KEY_CHECKS, ~flags2, "@@session.foreign_key_checks", &need_comma); print_set_option(file, tmp, OPTION_AUTO_IS_NULL, flags2, @@ -3586,9 +4207,9 @@ void Query_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -int Query_log_event::do_apply_event(Relay_log_info const *rli) +int Query_log_event::do_apply_event(rpl_group_info *rgi) { - return do_apply_event(rli, query, q_len); + return do_apply_event(rgi, query, q_len); } /** @@ -3610,9 +4231,16 @@ bool test_if_equal_repl_errors(int expected_error, int actual_error) return 1; switch (expected_error) { case ER_DUP_ENTRY: + case ER_DUP_ENTRY_WITH_KEY_NAME: + case ER_DUP_KEY: case ER_AUTOINC_READ_FAILED: - return (actual_error == ER_AUTOINC_READ_FAILED || + return (actual_error == ER_DUP_ENTRY || + actual_error == ER_DUP_ENTRY_WITH_KEY_NAME || + actual_error == ER_DUP_KEY || + actual_error == ER_AUTOINC_READ_FAILED || actual_error == HA_ERR_AUTOINC_ERANGE); + case ER_UNKNOWN_TABLE: + return actual_error == ER_IT_IS_A_VIEW; default: break; } @@ -3637,11 +4265,16 @@ bool test_if_equal_repl_errors(int expected_error, int actual_error) mismatch. This mismatch could be implemented with a new ER_ code, and to ignore it you would use --slave-skip-errors... */ -int Query_log_event::do_apply_event(Relay_log_info const *rli, - const char *query_arg, uint32 q_len_arg) +int Query_log_event::do_apply_event(rpl_group_info *rgi, + const char *query_arg, uint32 q_len_arg) { int expected_error,actual_error= 0; - HA_CREATE_INFO db_options; + Schema_specification_st db_options; + uint64 sub_id= 0; + rpl_gtid gtid; + Relay_log_info const *rli= rgi->rli; + Rpl_filter *rpl_filter= rli->mi->rpl_filter; + bool current_stmt_is_commit; DBUG_ENTER("Query_log_event::do_apply_event"); /* @@ -3666,7 +4299,7 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, goto end; } - set_thd_db(thd, db, db_len); + set_thd_db(thd, rpl_filter, db, db_len); /* Setting the character set and collation of the current database thd->db. @@ -3677,51 +4310,13 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, thd->variables.auto_increment_increment= auto_increment_increment; thd->variables.auto_increment_offset= auto_increment_offset; - /* - InnoDB internally stores the master log position it has executed so far, - i.e. the position just after the COMMIT event. - When InnoDB will want to store, the positions in rli won't have - been updated yet, so group_master_log_* will point to old BEGIN - and event_master_log* will point to the beginning of current COMMIT. - But log_pos of the COMMIT Query event is what we want, i.e. the pos of the - END of the current log event (COMMIT). We save it in rli so that InnoDB can - access it. - */ - const_cast<Relay_log_info*>(rli)->future_group_master_log_pos= log_pos; DBUG_PRINT("info", ("log_pos: %lu", (ulong) log_pos)); - clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); - if (strcmp("COMMIT", query) == 0 && rli->tables_to_lock) - { - /* - Cleaning-up the last statement context: - the terminal event of the current statement flagged with - STMT_END_F got filtered out in ndb circular replication. - */ - int error; - char llbuff[22]; - if ((error= rows_event_stmt_cleanup(const_cast<Relay_log_info*>(rli), thd))) - { - const_cast<Relay_log_info*>(rli)->report(ERROR_LEVEL, error, - "Error in cleaning up after an event preceding the commit; " - "the group log file/position: %s %s", - const_cast<Relay_log_info*>(rli)->group_master_log_name, - llstr(const_cast<Relay_log_info*>(rli)->group_master_log_pos, - llbuff)); - } - /* - Executing a part of rli->stmt_done() logics that does not deal - with group position change. The part is redundant now but is - future-change-proof addon, e.g if COMMIT handling will start checking - invariants like IN_STMT flag must be off at committing the transaction. - */ - const_cast<Relay_log_info*>(rli)->inc_event_relay_log_pos(); - const_cast<Relay_log_info*>(rli)->clear_flag(Relay_log_info::IN_STMT); - } - else - { - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); - } + thd->clear_error(1); + current_stmt_is_commit= is_commit(); + + DBUG_ASSERT(!current_stmt_is_commit || !rgi->tables_to_lock); + rgi->slave_close_thread_tables(thd); /* Note: We do not need to execute reset_one_shot_variables() if this @@ -3741,9 +4336,11 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, thd->variables.pseudo_thread_id= thread_id; // for temp tables DBUG_PRINT("query",("%s", thd->query())); - if (ignored_error_code((expected_error= error_code)) || - !unexpected_error_code(expected_error)) + if (!(expected_error= error_code) || + ignored_error_code(expected_error) || + !unexpected_error_code(expected_error)) { + thd->slave_expected_error= expected_error; if (flags2_inited) /* all bits of thd->variables.option_bits which are 1 in OPTIONS_WRITTEN_TO_BIN_LOG @@ -3756,11 +4353,11 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, nothing to do. */ /* - We do not replicate IGNORE_DIR_IN_CREATE. That is, if the master is a - slave which runs with SQL_MODE=IGNORE_DIR_IN_CREATE, this should not + We do not replicate MODE_NO_DIR_IN_CREATE. That is, if the master is a + slave which runs with SQL_MODE=MODE_NO_DIR_IN_CREATE, this should not force us to ignore the dir too. Imagine you are a ring of machines, and one has a disk problem so that you temporarily need - IGNORE_DIR_IN_CREATE on this machine; you don't want it to propagate + MODE_NO_DIR_IN_CREATE on this machine; you don't want it to propagate elsewhere (you don't want all slaves to start ignoring the dirs). */ if (sql_mode_inited) @@ -3769,7 +4366,8 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, (sql_mode & ~(ulong) MODE_NO_DIR_IN_CREATE)); if (charset_inited) { - if (rli->cached_charset_compare(charset)) + rpl_sql_thread_info *sql_info= thd->system_thread_info.rpl_sql_info; + if (sql_info->cached_charset_compare(charset)) { /* Verify that we support the charsets found in the event. */ if (!(thd->variables.character_set_client= @@ -3785,7 +4383,7 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, stop with EE_UNKNOWN_CHARSET in compare_errors (unless set to ignore this error). */ - set_slave_thread_default_charset(thd, rli); + set_slave_thread_default_charset(thd, rgi); goto compare_errors; } thd->update_charset(); // for the charset change to take effect @@ -3857,6 +4455,38 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, } } + /* + Record any GTID in the same transaction, so slave state is + transactionally consistent. + */ + if (current_stmt_is_commit) + { + thd->variables.option_bits&= ~OPTION_GTID_BEGIN; + if (rgi->gtid_pending) + { + sub_id= rgi->gtid_sub_id; + rgi->gtid_pending= false; + + gtid= rgi->current_gtid; + if (rpl_global_gtid_slave_state->record_gtid(thd, >id, sub_id, + rgi, false)) + { + int errcode= thd->get_stmt_da()->sql_errno(); + if (!is_parallel_retry_error(rgi, errcode)) + rli->report(ERROR_LEVEL, ER_CANNOT_UPDATE_GTID_STATE, + rgi->gtid_info(), + "Error during COMMIT: failed to update GTID state in " + "%s.%s: %d: %s", + "mysql", rpl_gtid_slave_state_table_name.str, + errcode, + thd->get_stmt_da()->message()); + sub_id= 0; + thd->is_slave_error= 1; + goto end; + } + } + } + thd->table_map_for_update= (table_map)table_map_for_update; thd->set_invoker(&user, &host); /* @@ -3871,30 +4501,42 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, concurrency_error_code(expected_error))) { thd->variables.option_bits|= OPTION_MASTER_SQL_ERROR; + thd->variables.option_bits&= ~OPTION_GTID_BEGIN; } /* Execute the query (note that we bypass dispatch_command()) */ Parser_state parser_state; if (!parser_state.init(thd, thd->query(), thd->query_length())) { + DBUG_ASSERT(thd->m_digest == NULL); + thd->m_digest= & thd->m_digest_state; + DBUG_ASSERT(thd->m_statement_psi == NULL); + thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state, + stmt_info_rpl.m_key, + thd->db, thd->db_length, + thd->charset()); + THD_STAGE_INFO(thd, stage_init); + MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, thd->query(), thd->query_length()); + if (thd->m_digest != NULL) + thd->m_digest->reset(thd->m_token_array, max_digest_length); + + if (thd->slave_thread) + { + /* + The opt_log_slow_slave_statements variable can be changed + dynamically, so we have to set the sql_log_slow respectively. + */ + thd->variables.sql_log_slow= opt_log_slow_slave_statements; + } + + thd->enable_slow_log= true; mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); /* Finalize server status flags after executing a statement. */ thd->update_server_status(); log_slow_statement(thd); + thd->lex->restore_set_statement_var(); } thd->variables.option_bits&= ~OPTION_MASTER_SQL_ERROR; - - /* - Resetting the enable_slow_log thd variable. - - We need to reset it back to the opt_log_slow_slave_statements - value after the statement execution (and slow logging - is done). It might have changed if the statement was an - admin statement (in which case, down in mysql_parse execution - thd->enable_slow_log is set to the value of - opt_log_slow_admin_statements). - */ - thd->enable_slow_log= opt_log_slow_slave_statements; } else { @@ -3906,10 +4548,10 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, to check/fix it. */ if (mysql_test_parse_for_slave(thd, thd->query(), thd->query_length())) - clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); /* Can ignore query */ + thd->clear_error(1); else { - rli->report(ERROR_LEVEL, expected_error, + rli->report(ERROR_LEVEL, expected_error, rgi->gtid_info(), "\ Query partially completed on the master (error on master: %d) \ and was aborted. There is a chance that your master is inconsistent at this \ @@ -3922,7 +4564,8 @@ START SLAVE; . Query: '%s'", expected_error, thd->query()); } /* If the query was not ignored, it is printed to the general log */ - if (!thd->is_error() || thd->stmt_da->sql_errno() != ER_SLAVE_IGNORED_TABLE) + if (!thd->is_error() || + thd->get_stmt_da()->sql_errno() != ER_SLAVE_IGNORED_TABLE) general_log_write(thd, COM_QUERY, thd->query(), thd->query_length()); else { @@ -3946,15 +4589,16 @@ compare_errors: has already been dropped. To ignore such irrelevant "table does not exist errors", we silently clear the error if TEMPORARY was used. */ - if (thd->lex->sql_command == SQLCOM_DROP_TABLE && thd->lex->drop_temporary && - thd->is_error() && thd->stmt_da->sql_errno() == ER_BAD_TABLE_ERROR && + if (thd->lex->sql_command == SQLCOM_DROP_TABLE && + thd->lex->tmp_table() && + thd->is_error() && thd->get_stmt_da()->sql_errno() == ER_BAD_TABLE_ERROR && !expected_error) - thd->stmt_da->reset_diagnostics_area(); + thd->get_stmt_da()->reset_diagnostics_area(); /* If we expected a non-zero error code, and we don't get the same error code, and it should be ignored or is related to a concurrency issue. */ - actual_error= thd->is_error() ? thd->stmt_da->sql_errno() : 0; + actual_error= thd->is_error() ? thd->get_stmt_da()->sql_errno() : 0; DBUG_PRINT("info",("expected_error: %d sql_errno: %d", expected_error, actual_error)); @@ -3964,17 +4608,16 @@ compare_errors: !ignored_error_code(actual_error) && !ignored_error_code(expected_error)) { - rli->report(ERROR_LEVEL, 0, - "\ -Query caused different errors on master and slave. \ -Error on master: message (format)='%s' error code=%d ; \ -Error on slave: actual message='%s', error code=%d. \ -Default database: '%s'. Query: '%s'", - ER_SAFE(expected_error), - expected_error, - actual_error ? thd->stmt_da->message() : "no error", - actual_error, - print_slave_db_safe(db), query_arg); + rli->report(ERROR_LEVEL, 0, rgi->gtid_info(), + "Query caused different errors on master and slave. " + "Error on master: message (format)='%s' error code=%d ; " + "Error on slave: actual message='%s', error code=%d. " + "Default database: '%s'. Query: '%s'", + ER_SAFE_THD(thd, expected_error), + expected_error, + actual_error ? thd->get_stmt_da()->message() : "no error", + actual_error, + print_slave_db_safe(db), query_arg); thd->is_slave_error= 1; } /* @@ -3986,19 +4629,22 @@ Default database: '%s'. Query: '%s'", ignored_error_code(actual_error)) { DBUG_PRINT("info",("error ignored")); - clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); - thd->reset_killed(); + thd->clear_error(1); + if (actual_error == ER_QUERY_INTERRUPTED || + actual_error == ER_CONNECTION_KILLED) + thd->reset_killed(); } /* Other cases: mostly we expected no error and get one. */ else if (thd->is_slave_error || thd->is_fatal_error) { - rli->report(ERROR_LEVEL, actual_error, - "Error '%s' on query. Default database: '%s'. Query: '%s'", - (actual_error ? thd->stmt_da->message() : - "unexpected success or fatal error"), - print_slave_db_safe(thd->db), query_arg); + if (!is_parallel_retry_error(rgi, actual_error)) + rli->report(ERROR_LEVEL, actual_error, rgi->gtid_info(), + "Error '%s' on query. Default database: '%s'. Query: '%s'", + (actual_error ? thd->get_stmt_da()->message() : + "unexpected success or fatal error"), + print_slave_db_safe(thd->db), query_arg); thd->is_slave_error= 1; } @@ -4033,8 +4679,7 @@ Default database: '%s'. Query: '%s'", to shutdown trying to finish incomplete events group. */ DBUG_EXECUTE_IF("stop_slave_middle_group", - if (strcmp("COMMIT", query) != 0 && - strcmp("BEGIN", query) != 0) + if (!current_stmt_is_commit && is_begin() == 0) { if (thd->transaction.all.modified_non_trans_table) const_cast<Relay_log_info*>(rli)->abort_slave= 1; @@ -4042,6 +4687,9 @@ Default database: '%s'. Query: '%s'", } end: + if (sub_id && !thd->is_slave_error) + rpl_global_gtid_slave_state->update_state_hash(sub_id, >id, rgi); + /* Probably we have set thd->query, thd->db, thd->catalog to point to places in the data_buf of this event. Now the event is going to be deleted @@ -4056,6 +4704,12 @@ end: thd->set_db(NULL, 0); /* will free the current database */ thd->reset_query(); DBUG_PRINT("info", ("end: query= 0")); + + /* Mark the statement completed. */ + MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da()); + thd->m_statement_psi= NULL; + thd->m_digest= NULL; + /* As a disk space optimization, future masters will not log an event for LAST_INSERT_ID() if that function returned 0 (and thus they will be able @@ -4070,29 +4724,14 @@ end: DBUG_RETURN(thd->is_slave_error); } -int Query_log_event::do_update_pos(Relay_log_info *rli) -{ - /* - Note that we will not increment group* positions if we are just - after a SET ONE_SHOT, because SET ONE_SHOT should not be separated - from its following updating query. - */ - if (thd->one_shot_set) - { - rli->inc_event_relay_log_pos(); - return 0; - } - else - return Log_event::do_update_pos(rli); -} - - Log_event::enum_skip_reason -Query_log_event::do_shall_skip(Relay_log_info *rli) +Query_log_event::do_shall_skip(rpl_group_info *rgi) { + Relay_log_info *rli= rgi->rli; DBUG_ENTER("Query_log_event::do_shall_skip"); - DBUG_PRINT("debug", ("query: %s; q_len: %d", query, q_len)); + DBUG_PRINT("debug", ("query: '%s' q_len: %d", query, q_len)); DBUG_ASSERT(query && q_len > 0); + DBUG_ASSERT(thd == rgi->thd); /* An event skipped due to @@skip_replication must not be counted towards the @@ -4104,19 +4743,58 @@ Query_log_event::do_shall_skip(Relay_log_info *rli) if (rli->slave_skip_counter > 0) { - if (strcmp("BEGIN", query) == 0) + if (is_begin()) { - thd->variables.option_bits|= OPTION_BEGIN; - DBUG_RETURN(Log_event::continue_group(rli)); + thd->variables.option_bits|= OPTION_BEGIN | OPTION_GTID_BEGIN; + DBUG_RETURN(Log_event::continue_group(rgi)); } - if (strcmp("COMMIT", query) == 0 || strcmp("ROLLBACK", query) == 0) + if (is_commit() || is_rollback()) { - thd->variables.option_bits&= ~OPTION_BEGIN; + thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_GTID_BEGIN); DBUG_RETURN(Log_event::EVENT_SKIP_COUNT); } } - DBUG_RETURN(Log_event::do_shall_skip(rli)); +#ifdef WITH_WSREP + else if (WSREP_ON && wsrep_mysql_replication_bundle && opt_slave_domain_parallel_threads == 0 && + thd->wsrep_mysql_replicated > 0 && + (is_begin() || is_commit())) + { + if (++thd->wsrep_mysql_replicated < (int)wsrep_mysql_replication_bundle) + { + WSREP_DEBUG("skipping wsrep commit %d", thd->wsrep_mysql_replicated); + DBUG_RETURN(Log_event::EVENT_SKIP_IGNORE); + } + else + { + thd->wsrep_mysql_replicated = 0; + } + } +#endif + DBUG_RETURN(Log_event::do_shall_skip(rgi)); +} + + +bool +Query_log_event::peek_is_commit_rollback(const char *event_start, + size_t event_len, + enum enum_binlog_checksum_alg checksum_alg) +{ + if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + { + if (event_len > BINLOG_CHECKSUM_LEN) + event_len-= BINLOG_CHECKSUM_LEN; + else + event_len= 0; + } + else + DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || + checksum_alg == BINLOG_CHECKSUM_ALG_OFF); + + if (event_len < LOG_EVENT_HEADER_LEN + QUERY_HEADER_LEN || event_len < 9) + return false; + return !memcmp(event_start + (event_len-7), "\0COMMIT", 7) || + !memcmp(event_start + (event_len-9), "\0ROLLBACK", 9); } #endif @@ -4140,7 +4818,7 @@ Start_log_event_v3::Start_log_event_v3() */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Start_log_event_v3::pack_info(THD *thd, Protocol *protocol) +void Start_log_event_v3::pack_info(Protocol *protocol) { char buf[12 + ST_SERVER_VER_LEN + 14 + 22], *pos; pos= strmov(buf, "Server ver: "); @@ -4195,9 +4873,17 @@ void Start_log_event_v3::print(FILE* file, PRINT_EVENT_INFO* print_event_info) print_event_info->base64_output_mode != BASE64_OUTPUT_NEVER && !print_event_info->short_form) { - if (print_event_info->base64_output_mode != BASE64_OUTPUT_DECODE_ROWS) + /* BINLOG is matched with the delimiter below on the same level */ + bool do_print_encoded= + print_event_info->base64_output_mode != BASE64_OUTPUT_DECODE_ROWS; + if (do_print_encoded) my_b_printf(&cache, "BINLOG '\n"); - print_base64(&cache, print_event_info, FALSE); + + print_base64(&cache, print_event_info, do_print_encoded); + + if (do_print_encoded) + my_b_printf(&cache, "'%s\n", print_event_info->delimiter); + print_event_info->printed_fd_event= TRUE; } DBUG_VOID_RETURN; @@ -4213,13 +4899,12 @@ Start_log_event_v3::Start_log_event_v3(const char* buf, uint event_len, *description_event) :Log_event(buf, description_event), binlog_version(BINLOG_VERSION) { - if (event_len < (uint)description_event->common_header_len + - ST_COMMON_HEADER_LEN_OFFSET) + if (event_len < LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET) { server_version[0]= 0; return; } - buf+= description_event->common_header_len; + buf+= LOG_EVENT_MINIMAL_HEADER_LEN; binlog_version= uint2korr(buf+ST_BINLOG_VER_OFFSET); memcpy(server_version, buf+ST_SERVER_VER_OFFSET, ST_SERVER_VER_LEN); @@ -4235,7 +4920,7 @@ Start_log_event_v3::Start_log_event_v3(const char* buf, uint event_len, */ #ifndef MYSQL_CLIENT -bool Start_log_event_v3::write(IO_CACHE* file) +bool Start_log_event_v3::write() { char buff[START_V3_HEADER_LEN]; int2store(buff + ST_BINLOG_VER_OFFSET,binlog_version); @@ -4243,9 +4928,9 @@ bool Start_log_event_v3::write(IO_CACHE* file) if (!dont_set_created) created= get_time(); // this sets when and when_sec_part as a side effect int4store(buff + ST_CREATED_OFFSET,created); - return (write_header(file, sizeof(buff)) || - wrapper_my_b_safe_write(file, (uchar*) buff, sizeof(buff)) || - write_footer(file)); + return write_header(sizeof(buff)) || + write_data(buff, sizeof(buff)) || + write_footer(); } #endif @@ -4270,10 +4955,12 @@ bool Start_log_event_v3::write(IO_CACHE* file) other words, no deadlock problem. */ -int Start_log_event_v3::do_apply_event(Relay_log_info const *rli) +int Start_log_event_v3::do_apply_event(rpl_group_info *rgi) { DBUG_ENTER("Start_log_event_v3::do_apply_event"); int error= 0; + Relay_log_info *rli= rgi->rli; + switch (binlog_version) { case 3: @@ -4286,19 +4973,13 @@ int Start_log_event_v3::do_apply_event(Relay_log_info const *rli) */ if (created) { - error= close_temporary_tables(thd); - cleanup_load_tmpdir(); - } - else - { + rli->close_temporary_tables(); + /* - Set all temporary tables thread references to the current thread - as they may point to the "old" SQL slave thread in case of its - restart. + The following is only false if we get here with a BINLOG statement */ - TABLE *table; - for (table= thd->temporary_tables; table; table= table->next) - table->in_use= thd; + if (rli->mi) + cleanup_load_tmpdir(&rli->mi->cmp_connection_name); } break; @@ -4314,7 +4995,7 @@ int Start_log_event_v3::do_apply_event(Relay_log_info const *rli) Can distinguish, based on the value of 'created': this event was generated at master startup. */ - error= close_temporary_tables(thd); + rli->close_temporary_tables(); } /* Otherwise, can't distinguish a Start_log_event generated at @@ -4415,10 +5096,10 @@ Format_description_log_event(uint8 binlog_ver, const char* server_ver) post_header_len[PRE_GA_UPDATE_ROWS_EVENT-1] = 0; post_header_len[PRE_GA_DELETE_ROWS_EVENT-1] = 0; - post_header_len[TABLE_MAP_EVENT-1]= TABLE_MAP_HEADER_LEN; - post_header_len[WRITE_ROWS_EVENT-1]= ROWS_HEADER_LEN; - post_header_len[UPDATE_ROWS_EVENT-1]= ROWS_HEADER_LEN; - post_header_len[DELETE_ROWS_EVENT-1]= ROWS_HEADER_LEN; + post_header_len[TABLE_MAP_EVENT-1]= TABLE_MAP_HEADER_LEN; + post_header_len[WRITE_ROWS_EVENT_V1-1]= ROWS_HEADER_LEN_V1; + post_header_len[UPDATE_ROWS_EVENT_V1-1]= ROWS_HEADER_LEN_V1; + post_header_len[DELETE_ROWS_EVENT_V1-1]= ROWS_HEADER_LEN_V1; /* We here have the possibility to simulate a master of before we changed the table map id to be stored in 6 bytes: when it was stored in 4 @@ -4431,11 +5112,22 @@ Format_description_log_event(uint8 binlog_ver, const char* server_ver) */ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master", post_header_len[TABLE_MAP_EVENT-1]= - post_header_len[WRITE_ROWS_EVENT-1]= - post_header_len[UPDATE_ROWS_EVENT-1]= - post_header_len[DELETE_ROWS_EVENT-1]= 6;); + post_header_len[WRITE_ROWS_EVENT_V1-1]= + post_header_len[UPDATE_ROWS_EVENT_V1-1]= + post_header_len[DELETE_ROWS_EVENT_V1-1]= 6;); post_header_len[INCIDENT_EVENT-1]= INCIDENT_HEADER_LEN; post_header_len[HEARTBEAT_LOG_EVENT-1]= 0; + post_header_len[IGNORABLE_LOG_EVENT-1]= 0; + post_header_len[ROWS_QUERY_LOG_EVENT-1]= 0; + post_header_len[GTID_LOG_EVENT-1]= 0; + post_header_len[ANONYMOUS_GTID_LOG_EVENT-1]= 0; + post_header_len[PREVIOUS_GTIDS_LOG_EVENT-1]= 0; + post_header_len[TRANSACTION_CONTEXT_EVENT-1]= 0; + post_header_len[VIEW_CHANGE_EVENT-1]= 0; + post_header_len[XA_PREPARE_LOG_EVENT-1]= 0; + post_header_len[WRITE_ROWS_EVENT-1]= ROWS_HEADER_LEN_V2; + post_header_len[UPDATE_ROWS_EVENT-1]= ROWS_HEADER_LEN_V2; + post_header_len[DELETE_ROWS_EVENT-1]= ROWS_HEADER_LEN_V2; // Set header length of the reserved events to 0 memset(post_header_len + MYSQL_EVENTS_END - 1, 0, @@ -4443,6 +5135,11 @@ Format_description_log_event(uint8 binlog_ver, const char* server_ver) // Set header lengths of Maria events post_header_len[ANNOTATE_ROWS_EVENT-1]= ANNOTATE_ROWS_HEADER_LEN; + post_header_len[BINLOG_CHECKPOINT_EVENT-1]= + BINLOG_CHECKPOINT_HEADER_LEN; + post_header_len[GTID_EVENT-1]= GTID_HEADER_LEN; + post_header_len[GTID_LIST_EVENT-1]= GTID_LIST_HEADER_LEN; + post_header_len[START_ENCRYPTION_EVENT-1]= START_ENCRYPTION_HEADER_LEN; // Sanity-check that all post header lengths are initialized. int i; @@ -4496,7 +5193,8 @@ Format_description_log_event(uint8 binlog_ver, const char* server_ver) break; } calc_server_version_split(); - checksum_alg= (uint8) BINLOG_CHECKSUM_ALG_UNDEF; + checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; + reset_crypto(); } @@ -4548,121 +5246,19 @@ Format_description_log_event(const char* buf, { /* the last bytes are the checksum alg desc and value (or value's room) */ number_of_event_types -= BINLOG_CHECKSUM_ALG_DESC_LEN; - checksum_alg= post_header_len[number_of_event_types]; + checksum_alg= (enum_binlog_checksum_alg)post_header_len[number_of_event_types]; } else { - checksum_alg= (uint8) BINLOG_CHECKSUM_ALG_UNDEF; + checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; } + reset_crypto(); - /* - In some previous versions, the events were given other event type - id numbers than in the present version. When replicating from such - a version, we therefore set up an array that maps those id numbers - to the id numbers of the present server. - - If post_header_len is null, it means malloc failed, and is_valid - will fail, so there is no need to do anything. - - The trees in which events have wrong id's are: - - mysql-5.1-wl1012.old mysql-5.1-wl2325-5.0-drop6p13-alpha - mysql-5.1-wl2325-5.0-drop6 mysql-5.1-wl2325-5.0 - mysql-5.1-wl2325-no-dd - - (this was found by grepping for two lines in sequence where the - first matches "FORMAT_DESCRIPTION_EVENT," and the second matches - "TABLE_MAP_EVENT," in log_event.h in all trees) - - In these trees, the following server_versions existed since - TABLE_MAP_EVENT was introduced: - - 5.1.1-a_drop5p3 5.1.1-a_drop5p4 5.1.1-alpha - 5.1.2-a_drop5p10 5.1.2-a_drop5p11 5.1.2-a_drop5p12 - 5.1.2-a_drop5p13 5.1.2-a_drop5p14 5.1.2-a_drop5p15 - 5.1.2-a_drop5p16 5.1.2-a_drop5p16b 5.1.2-a_drop5p16c - 5.1.2-a_drop5p17 5.1.2-a_drop5p4 5.1.2-a_drop5p5 - 5.1.2-a_drop5p6 5.1.2-a_drop5p7 5.1.2-a_drop5p8 - 5.1.2-a_drop5p9 5.1.3-a_drop5p17 5.1.3-a_drop5p17b - 5.1.3-a_drop5p17c 5.1.4-a_drop5p18 5.1.4-a_drop5p19 - 5.1.4-a_drop5p20 5.1.4-a_drop6p0 5.1.4-a_drop6p1 - 5.1.4-a_drop6p2 5.1.5-a_drop5p20 5.2.0-a_drop6p3 - 5.2.0-a_drop6p4 5.2.0-a_drop6p5 5.2.0-a_drop6p6 - 5.2.1-a_drop6p10 5.2.1-a_drop6p11 5.2.1-a_drop6p12 - 5.2.1-a_drop6p6 5.2.1-a_drop6p7 5.2.1-a_drop6p8 - 5.2.2-a_drop6p13 5.2.2-a_drop6p13-alpha 5.2.2-a_drop6p13b - 5.2.2-a_drop6p13c - - (this was found by grepping for "mysql," in all historical - versions of configure.in in the trees listed above). - - There are 5.1.1-alpha versions that use the new event id's, so we - do not test that version string. So replication from 5.1.1-alpha - with the other event id's to a new version does not work. - Moreover, we can safely ignore the part after drop[56]. This - allows us to simplify the big list above to the following regexes: - - 5\.1\.[1-5]-a_drop5.* - 5\.1\.4-a_drop6.* - 5\.2\.[0-2]-a_drop6.* - - This is what we test for in the 'if' below. - */ - if (post_header_len && - server_version[0] == '5' && server_version[1] == '.' && - server_version[3] == '.' && - strncmp(server_version + 5, "-a_drop", 7) == 0 && - ((server_version[2] == '1' && - server_version[4] >= '1' && server_version[4] <= '5' && - server_version[12] == '5') || - (server_version[2] == '1' && - server_version[4] == '4' && - server_version[12] == '6') || - (server_version[2] == '2' && - server_version[4] >= '0' && server_version[4] <= '2' && - server_version[12] == '6'))) - { - if (number_of_event_types != 22) - { - DBUG_PRINT("info", (" number_of_event_types=%d", - number_of_event_types)); - /* this makes is_valid() return false. */ - my_free(post_header_len); - post_header_len= NULL; - DBUG_VOID_RETURN; - } - static const uint8 perm[23]= - { - UNKNOWN_EVENT, START_EVENT_V3, QUERY_EVENT, STOP_EVENT, ROTATE_EVENT, - INTVAR_EVENT, LOAD_EVENT, SLAVE_EVENT, CREATE_FILE_EVENT, - APPEND_BLOCK_EVENT, EXEC_LOAD_EVENT, DELETE_FILE_EVENT, - NEW_LOAD_EVENT, - RAND_EVENT, USER_VAR_EVENT, - FORMAT_DESCRIPTION_EVENT, - TABLE_MAP_EVENT, - PRE_GA_WRITE_ROWS_EVENT, - PRE_GA_UPDATE_ROWS_EVENT, - PRE_GA_DELETE_ROWS_EVENT, - XID_EVENT, - BEGIN_LOAD_QUERY_EVENT, - EXECUTE_LOAD_QUERY_EVENT, - }; - event_type_permutation= perm; - /* - Since we use (permuted) event id's to index the post_header_len - array, we need to permute the post_header_len array too. - */ - uint8 post_header_len_temp[23]; - for (int i= 1; i < 23; i++) - post_header_len_temp[perm[i] - 1]= post_header_len[i - 1]; - for (int i= 0; i < 22; i++) - post_header_len[i] = post_header_len_temp[i]; - } DBUG_VOID_RETURN; } #ifndef MYSQL_CLIENT -bool Format_description_log_event::write(IO_CACHE* file) +bool Format_description_log_event::write() { bool ret; bool no_checksum; @@ -4670,16 +5266,15 @@ bool Format_description_log_event::write(IO_CACHE* file) We don't call Start_log_event_v3::write() because this would make 2 my_b_safe_write(). */ - uchar buff[FORMAT_DESCRIPTION_HEADER_LEN + BINLOG_CHECKSUM_ALG_DESC_LEN]; - size_t rec_size= sizeof(buff); + uchar buff[START_V3_HEADER_LEN+1]; + size_t rec_size= sizeof(buff) + BINLOG_CHECKSUM_ALG_DESC_LEN + + number_of_event_types; int2store(buff + ST_BINLOG_VER_OFFSET,binlog_version); memcpy((char*) buff + ST_SERVER_VER_OFFSET,server_version,ST_SERVER_VER_LEN); if (!dont_set_created) created= get_time(); int4store(buff + ST_CREATED_OFFSET,created); - buff[ST_COMMON_HEADER_LEN_OFFSET]= LOG_EVENT_HEADER_LEN; - memcpy((char*) buff+ST_COMMON_HEADER_LEN_OFFSET + 1, (uchar*) post_header_len, - LOG_EVENT_TYPES); + buff[ST_COMMON_HEADER_LEN_OFFSET]= common_header_len; /* if checksum is requested record the checksum-algorithm descriptor next to @@ -4688,12 +5283,12 @@ bool Format_description_log_event::write(IO_CACHE* file) slave does it via marking the event according to FD_queue checksum_alg value. */ - compile_time_assert(sizeof(BINLOG_CHECKSUM_ALG_DESC_LEN == 1)); + compile_time_assert(BINLOG_CHECKSUM_ALG_DESC_LEN == 1); #ifndef DBUG_OFF data_written= 0; // to prepare for need_checksum assert #endif - buff[FORMAT_DESCRIPTION_HEADER_LEN]= need_checksum() ? - checksum_alg : (uint8) BINLOG_CHECKSUM_ALG_OFF; + uint8 checksum_byte= (uint8) + (need_checksum() ? checksum_alg : BINLOG_CHECKSUM_ALG_OFF); /* FD of checksum-aware server is always checksum-equipped, (V) is in, regardless of @@global.binlog_checksum policy. @@ -4711,9 +5306,11 @@ bool Format_description_log_event::write(IO_CACHE* file) { checksum_alg= BINLOG_CHECKSUM_ALG_CRC32; // Forcing (V) room to fill anyway } - ret= (write_header(file, rec_size) || - wrapper_my_b_safe_write(file, buff, rec_size) || - write_footer(file)); + ret= write_header(rec_size) || + write_data(buff, sizeof(buff)) || + write_data(post_header_len, number_of_event_types) || + write_data(&checksum_byte, sizeof(checksum_byte)) || + write_footer(); if (no_checksum) checksum_alg= BINLOG_CHECKSUM_ALG_OFF; return ret; @@ -4721,9 +5318,10 @@ bool Format_description_log_event::write(IO_CACHE* file) #endif #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -int Format_description_log_event::do_apply_event(Relay_log_info const *rli) +int Format_description_log_event::do_apply_event(rpl_group_info *rgi) { int ret= 0; + Relay_log_info *rli= rgi->rli; DBUG_ENTER("Format_description_log_event::do_apply_event"); /* @@ -4740,12 +5338,12 @@ int Format_description_log_event::do_apply_event(Relay_log_info const *rli) if (!is_artificial_event() && created && thd->transaction.all.ha_list) { /* This is not an error (XA is safe), just an information */ - rli->report(INFORMATION_LEVEL, 0, + rli->report(INFORMATION_LEVEL, 0, NULL, "Rolling back unfinished transaction (no COMMIT " "or ROLLBACK in relay log). A probable cause is that " "the master died while writing the transaction to " "its binary log, thus rolled back too."); - const_cast<Relay_log_info*>(rli)->cleanup_context(thd, 1); + rgi->cleanup_context(thd, 1); } /* @@ -4753,7 +5351,7 @@ int Format_description_log_event::do_apply_event(Relay_log_info const *rli) perform, we don't call Start_log_event_v3::do_apply_event() (this was just to update the log's description event). */ - if (server_id != (uint32) ::server_id) + if (server_id != (uint32) global_system_variables.server_id) { /* If the event was not requested by the slave i.e. the master sent @@ -4764,22 +5362,23 @@ int Format_description_log_event::do_apply_event(Relay_log_info const *rli) 0, then 96, then jump to first really asked event (which is >96). So this is ok. */ - ret= Start_log_event_v3::do_apply_event(rli); + ret= Start_log_event_v3::do_apply_event(rgi); } if (!ret) { /* Save the information describing this binlog */ + copy_crypto_data(rli->relay_log.description_event_for_exec); delete rli->relay_log.description_event_for_exec; - const_cast<Relay_log_info *>(rli)->relay_log.description_event_for_exec= this; + rli->relay_log.description_event_for_exec= this; } DBUG_RETURN(ret); } -int Format_description_log_event::do_update_pos(Relay_log_info *rli) +int Format_description_log_event::do_update_pos(rpl_group_info *rgi) { - if (server_id == (uint32) ::server_id) + if (server_id == (uint32) global_system_variables.server_id) { /* We only increase the relay log position if we are skipping @@ -4794,23 +5393,34 @@ int Format_description_log_event::do_update_pos(Relay_log_info *rli) Intvar_log_event instead of starting at a Table_map_log_event or the Intvar_log_event respectively. */ - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); return 0; } else { - return Log_event::do_update_pos(rli); + return Log_event::do_update_pos(rgi); } } Log_event::enum_skip_reason -Format_description_log_event::do_shall_skip(Relay_log_info *rli) +Format_description_log_event::do_shall_skip(rpl_group_info *rgi) { return Log_event::EVENT_SKIP_NOT; } #endif +bool Format_description_log_event::start_decryption(Start_encryption_log_event* sele) +{ + DBUG_ASSERT(crypto_data.scheme == 0); + + if (!sele->is_valid()) + return 1; + + memcpy(crypto_data.nonce, sele->nonce, BINLOG_NONCE_LENGTH); + return crypto_data.init(sele->crypto_scheme, sele->key_version); +} + static inline void do_server_version_split(char* version, Format_description_log_event::master_version_split *split_versions) @@ -4894,31 +5504,83 @@ Format_description_log_event::is_version_before_checksum(const master_version_sp checksum-unaware (effectively no checksum) and the actuall [1-254] range alg descriptor. */ -uint8 get_checksum_alg(const char* buf, ulong len) +enum enum_binlog_checksum_alg get_checksum_alg(const char* buf, ulong len) { - uint8 ret; + enum enum_binlog_checksum_alg ret; char version[ST_SERVER_VER_LEN]; Format_description_log_event::master_version_split version_split; DBUG_ENTER("get_checksum_alg"); DBUG_ASSERT(buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT); - memcpy(version, buf + - buf[LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET] - + ST_SERVER_VER_OFFSET, ST_SERVER_VER_LEN); + memcpy(version, + buf + LOG_EVENT_MINIMAL_HEADER_LEN + ST_SERVER_VER_OFFSET, + ST_SERVER_VER_LEN); version[ST_SERVER_VER_LEN - 1]= 0; do_server_version_split(version, &version_split); - ret= Format_description_log_event::is_version_before_checksum(&version_split) ? - (uint8) BINLOG_CHECKSUM_ALG_UNDEF : - * (uint8*) (buf + len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN); + ret= Format_description_log_event::is_version_before_checksum(&version_split) + ? BINLOG_CHECKSUM_ALG_UNDEF + : (enum_binlog_checksum_alg)buf[len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN]; DBUG_ASSERT(ret == BINLOG_CHECKSUM_ALG_OFF || ret == BINLOG_CHECKSUM_ALG_UNDEF || ret == BINLOG_CHECKSUM_ALG_CRC32); DBUG_RETURN(ret); } - +Start_encryption_log_event::Start_encryption_log_event( + const char* buf, uint event_len, + const Format_description_log_event* description_event) + :Log_event(buf, description_event) +{ + if ((int)event_len == + LOG_EVENT_MINIMAL_HEADER_LEN + Start_encryption_log_event::get_data_size()) + { + buf += LOG_EVENT_MINIMAL_HEADER_LEN; + crypto_scheme = *(uchar*)buf; + key_version = uint4korr(buf + BINLOG_CRYPTO_SCHEME_LENGTH); + memcpy(nonce, + buf + BINLOG_CRYPTO_SCHEME_LENGTH + BINLOG_KEY_VERSION_LENGTH, + BINLOG_NONCE_LENGTH); + } + else + crypto_scheme= ~0; // invalid +} + +#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) +int Start_encryption_log_event::do_apply_event(rpl_group_info* rgi) +{ + return rgi->rli->relay_log.description_event_for_exec->start_decryption(this); +} + +int Start_encryption_log_event::do_update_pos(rpl_group_info *rgi) +{ + /* + master never sends Start_encryption_log_event, any SELE that a slave + might see was created locally in MYSQL_BIN_LOG::open() on the slave + */ + rgi->inc_event_relay_log_pos(); + return 0; +} + +#endif + +#ifndef MYSQL_SERVER +void Start_encryption_log_event::print(FILE* file, + PRINT_EVENT_INFO* print_event_info) +{ + Write_on_release_cache cache(&print_event_info->head_cache, file); + StringBuffer<1024> buf; + buf.append(STRING_WITH_LEN("# Encryption scheme: ")); + buf.append_ulonglong(crypto_scheme); + buf.append(STRING_WITH_LEN(", key_version: ")); + buf.append_ulonglong(key_version); + buf.append(STRING_WITH_LEN(", nonce: ")); + buf.append_hex(nonce, BINLOG_NONCE_LENGTH); + buf.append(STRING_WITH_LEN("\n# The rest of the binlog is encrypted!\n")); + my_b_write(&cache, (uchar*)buf.ptr(), buf.length()); +} +#endif /************************************************************************** Load_log_event methods General note about Load_log_event: the binlogging of LOAD DATA INFILE is @@ -4937,7 +5599,7 @@ uint8 get_checksum_alg(const char* buf, ulong len) **************************************************************************/ /* - Load_log_event::pack_info() + Load_log_event::print_query() */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) @@ -5040,13 +5702,13 @@ void Load_log_event::print_query(THD *thd, bool need_db, const char *cs, } -void Load_log_event::pack_info(THD *thd, Protocol *protocol) +void Load_log_event::pack_info(Protocol *protocol) { char query_buffer[1024]; String query_str(query_buffer, sizeof(query_buffer), system_charset_info); query_str.length(0); - print_query(thd, TRUE, NULL, &query_str, 0, 0, NULL); + print_query(protocol->thd, TRUE, NULL, &query_str, 0, 0, NULL); protocol->store(query_str.ptr(), query_str.length(), &my_charset_bin); } #endif /* defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) */ @@ -5058,7 +5720,7 @@ void Load_log_event::pack_info(THD *thd, Protocol *protocol) Load_log_event::write_data_header() */ -bool Load_log_event::write_data_header(IO_CACHE* file) +bool Load_log_event::write_data_header() { char buf[LOAD_HEADER_LEN]; int4store(buf + L_THREAD_ID_OFFSET, slave_proxy_id); @@ -5067,7 +5729,7 @@ bool Load_log_event::write_data_header(IO_CACHE* file) buf[L_TBL_LEN_OFFSET] = (char)table_name_len; buf[L_DB_LEN_OFFSET] = (char)db_len; int4store(buf + L_NUM_FIELDS_OFFSET, num_fields); - return my_b_safe_write(file, (uchar*)buf, LOAD_HEADER_LEN) != 0; + return write_data(buf, LOAD_HEADER_LEN) != 0; } @@ -5075,19 +5737,19 @@ bool Load_log_event::write_data_header(IO_CACHE* file) Load_log_event::write_data_body() */ -bool Load_log_event::write_data_body(IO_CACHE* file) +bool Load_log_event::write_data_body() { - if (sql_ex.write_data(file)) + if (sql_ex.write_data(writer)) return 1; if (num_fields && fields && field_lens) { - if (my_b_safe_write(file, (uchar*)field_lens, num_fields) || - my_b_safe_write(file, (uchar*)fields, field_block_len)) + if (write_data(field_lens, num_fields) || + write_data(fields, field_block_len)) return 1; } - return (my_b_safe_write(file, (uchar*)table_name, table_name_len + 1) || - my_b_safe_write(file, (uchar*)db, db_len + 1) || - my_b_safe_write(file, (uchar*)fname, fname_len)); + return (write_data(table_name, table_name_len + 1) || + write_data(db, db_len + 1) || + write_data(fname, fname_len)); } @@ -5328,33 +5990,33 @@ void Load_log_event::print(FILE* file_arg, PRINT_EVENT_INFO* print_event_info, my_b_printf(&cache, "%sLOAD DATA ", commented ? "# " : ""); if (check_fname_outside_temp_buf()) - my_b_printf(&cache, "LOCAL "); + my_b_write_string(&cache, "LOCAL "); my_b_printf(&cache, "INFILE '%-*s' ", fname_len, fname); if (sql_ex.opt_flags & REPLACE_FLAG) - my_b_printf(&cache,"REPLACE "); + my_b_write_string(&cache, "REPLACE "); else if (sql_ex.opt_flags & IGNORE_FLAG) - my_b_printf(&cache,"IGNORE "); + my_b_write_string(&cache, "IGNORE "); my_b_printf(&cache, "INTO TABLE `%s`", table_name); - my_b_printf(&cache, " FIELDS TERMINATED BY "); + my_b_write_string(&cache, " FIELDS TERMINATED BY "); pretty_print_str(&cache, sql_ex.field_term, sql_ex.field_term_len); if (sql_ex.opt_flags & OPT_ENCLOSED_FLAG) - my_b_printf(&cache," OPTIONALLY "); - my_b_printf(&cache, " ENCLOSED BY "); + my_b_write_string(&cache, " OPTIONALLY "); + my_b_write_string(&cache, " ENCLOSED BY "); pretty_print_str(&cache, sql_ex.enclosed, sql_ex.enclosed_len); - my_b_printf(&cache, " ESCAPED BY "); + my_b_write_string(&cache, " ESCAPED BY "); pretty_print_str(&cache, sql_ex.escaped, sql_ex.escaped_len); - my_b_printf(&cache," LINES TERMINATED BY "); + my_b_write_string(&cache, " LINES TERMINATED BY "); pretty_print_str(&cache, sql_ex.line_term, sql_ex.line_term_len); if (sql_ex.line_start) { - my_b_printf(&cache," STARTING BY "); + my_b_write_string(&cache," STARTING BY "); pretty_print_str(&cache, sql_ex.line_start, sql_ex.line_start_len); } if ((long) skip_lines > 0) @@ -5364,16 +6026,16 @@ void Load_log_event::print(FILE* file_arg, PRINT_EVENT_INFO* print_event_info, { uint i; const char* field = fields; - my_b_printf(&cache, " ("); + my_b_write_string(&cache, " ("); for (i = 0; i < num_fields; i++) { if (i) - my_b_printf(&cache, ","); + my_b_write_byte(&cache, ','); my_b_printf(&cache, "%`s", field); field += field_lens[i] + 1; } - my_b_printf(&cache, ")"); + my_b_write_byte(&cache, ')'); } my_b_printf(&cache, "%s\n", print_event_info->delimiter); @@ -5401,8 +6063,10 @@ void Load_log_event::set_fields(const char* affected_db, const char* field = fields; for (i= 0; i < num_fields; i++) { - field_list.push_back(new Item_field(context, - affected_db, table_name, field)); + field_list.push_back(new (thd->mem_root) + Item_field(thd, context, affected_db, table_name, + field), + thd->mem_root); field+= field_lens[i] + 1; } } @@ -5439,37 +6103,27 @@ void Load_log_event::set_fields(const char* affected_db, 1 Failure */ -int Load_log_event::do_apply_event(NET* net, Relay_log_info const *rli, +int Load_log_event::do_apply_event(NET* net, rpl_group_info *rgi, bool use_rli_only_for_errors) { + Relay_log_info const *rli= rgi->rli; + Rpl_filter *rpl_filter= rli->mi->rpl_filter; DBUG_ENTER("Load_log_event::do_apply_event"); DBUG_ASSERT(thd->query() == 0); - set_thd_db(thd, db, db_len); - thd->reset_query_inner(); // Should not be needed - thd->is_slave_error= 0; - clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); + set_thd_db(thd, rpl_filter, db, db_len); + thd->clear_error(1); /* see Query_log_event::do_apply_event() and BUG#13360 */ - DBUG_ASSERT(!rli->m_table_map.count()); + DBUG_ASSERT(!rgi->m_table_map.count()); /* Usually lex_start() is called by mysql_parse(), but we need it here as the present method does not call mysql_parse(). */ lex_start(thd); thd->lex->local_file= local_fname; - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(0); // Errors are cleared above - if (!use_rli_only_for_errors) - { - /* - Saved for InnoDB, see comment in - Query_log_event::do_apply_event() - */ - const_cast<Relay_log_info*>(rli)->future_group_master_log_pos= log_pos; - DBUG_PRINT("info", ("log_pos: %lu", (ulong) log_pos)); - } - /* We test replicate_*_db rules. Note that we have already prepared the file to load, even if we are going to ignore and delete it @@ -5497,7 +6151,7 @@ int Load_log_event::do_apply_event(NET* net, Relay_log_info const *rli, { thd->set_time(when, when_sec_part); thd->set_query_id(next_query_id()); - thd->warning_info->opt_clear_warning_info(thd->query_id); + thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); TABLE_LIST tables; if (lower_case_table_names) @@ -5517,7 +6171,6 @@ int Load_log_event::do_apply_event(NET* net, Relay_log_info const *rli, } else { - char llbuff[22]; enum enum_duplicates handle_dup; bool ignore= 0; char query_buffer[1024]; @@ -5610,17 +6263,17 @@ int Load_log_event::do_apply_event(NET* net, Relay_log_info const *rli, update it inside mysql_load(). */ List<Item> tmp_list; - if (mysql_load(thd, &ex, &tables, field_list, tmp_list, tmp_list, + if (open_temporary_tables(thd, &tables) || + mysql_load(thd, &ex, &tables, field_list, tmp_list, tmp_list, handle_dup, ignore, net != 0)) thd->is_slave_error= 1; if (thd->cuted_fields) { /* log_pos is the position of the LOAD event in the master log */ sql_print_warning("Slave: load data infile on table '%s' at " - "log position %s in log '%s' produced %ld " + "log position %llu in log '%s' produced %ld " "warning(s). Default database: '%s'", - (char*) table_name, - llstr(log_pos,llbuff), RPL_LOG_NAME, + (char*) table_name, log_pos, RPL_LOG_NAME, (ulong) thd->cuted_fields, print_slave_db_safe(thd->db)); } @@ -5645,9 +6298,10 @@ error: thd->catalog= 0; thd->set_db(NULL, 0); /* will free the current database */ thd->reset_query(); - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); - thd->stmt_da->can_overwrite_status= FALSE; + thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_GTID_BEGIN); + thd->get_stmt_da()->set_overwrite_status(false); close_thread_tables(thd); /* - If transaction rollback was requested due to deadlock @@ -5681,15 +6335,15 @@ error: int sql_errno; if (thd->is_error()) { - err= thd->stmt_da->message(); - sql_errno= thd->stmt_da->sql_errno(); + err= thd->get_stmt_da()->message(); + sql_errno= thd->get_stmt_da()->sql_errno(); } else { sql_errno=ER_UNKNOWN_ERROR; - err=ER(sql_errno); + err= ER_THD(thd, sql_errno); } - rli->report(ERROR_LEVEL, sql_errno,"\ + rli->report(ERROR_LEVEL, sql_errno, rgi->gtid_info(), "\ Error '%s' running LOAD DATA INFILE on table '%s'. Default database: '%s'", err, (char*)table_name, print_slave_db_safe(remember_db)); free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC)); @@ -5706,12 +6360,12 @@ Error '%s' running LOAD DATA INFILE on table '%s'. Default database: '%s'", (char*)table_name, print_slave_db_safe(remember_db)); - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), buf); + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(), + ER_THD(thd, ER_SLAVE_FATAL_ERROR), buf); DBUG_RETURN(1); } - DBUG_RETURN( use_rli_only_for_errors ? 0 : Log_event::do_apply_event(rli) ); + DBUG_RETURN( use_rli_only_for_errors ? 0 : Log_event::do_apply_event(rgi) ); } #endif @@ -5725,14 +6379,13 @@ Error '%s' running LOAD DATA INFILE on table '%s'. Default database: '%s'", */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Rotate_log_event::pack_info(THD *thd, Protocol *protocol) +void Rotate_log_event::pack_info(Protocol *protocol) { - char buf1[256], buf[22]; - String tmp(buf1, sizeof(buf1), log_cs); + StringBuffer<256> tmp(log_cs); tmp.length(0); tmp.append(new_log_ident, ident_len); tmp.append(STRING_WITH_LEN(";pos=")); - tmp.append(llstr(pos,buf)); + tmp.append_ulonglong(pos); protocol->store(tmp.ptr(), tmp.length(), &my_charset_bin); } #endif @@ -5752,7 +6405,7 @@ void Rotate_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) if (print_event_info->short_form) return; print_header(&cache, print_event_info, FALSE); - my_b_printf(&cache, "\tRotate to "); + my_b_write_string(&cache, "\tRotate to "); if (new_log_ident) my_b_write(&cache, (uchar*) new_log_ident, (uint)ident_len); my_b_printf(&cache, " pos: %s\n", llstr(pos, buf)); @@ -5774,12 +6427,9 @@ Rotate_log_event::Rotate_log_event(const char* new_log_ident_arg, pos(pos_arg),ident_len(ident_len_arg ? ident_len_arg : (uint) strlen(new_log_ident_arg)), flags(flags_arg) { -#ifndef DBUG_OFF - char buff[22]; DBUG_ENTER("Rotate_log_event::Rotate_log_event(...,flags)"); - DBUG_PRINT("enter",("new_log_ident: %s pos: %s flags: %lu", new_log_ident_arg, - llstr(pos_arg, buff), (ulong) flags)); -#endif + DBUG_PRINT("enter",("new_log_ident: %s pos: %llu flags: %lu", new_log_ident_arg, + pos_arg, (ulong) flags)); cache_type= EVENT_NO_CACHE; if (flags & DUP_NAME) new_log_ident= my_strndup(new_log_ident_arg, ident_len, MYF(MY_WME)); @@ -5796,16 +6446,14 @@ Rotate_log_event::Rotate_log_event(const char* buf, uint event_len, { DBUG_ENTER("Rotate_log_event::Rotate_log_event(char*,...)"); // The caller will ensure that event_len is what we have at EVENT_LEN_OFFSET - uint8 header_size= description_event->common_header_len; uint8 post_header_len= description_event->post_header_len[ROTATE_EVENT-1]; uint ident_offset; - if (event_len < header_size) + if (event_len < LOG_EVENT_MINIMAL_HEADER_LEN) DBUG_VOID_RETURN; - buf += header_size; - pos = post_header_len ? uint8korr(buf + R_POS_OFFSET) : 4; - ident_len = (uint)(event_len - - (header_size+post_header_len)); - ident_offset = post_header_len; + buf+= LOG_EVENT_MINIMAL_HEADER_LEN; + pos= post_header_len ? uint8korr(buf + R_POS_OFFSET) : 4; + ident_len= (uint)(event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + post_header_len)); + ident_offset= post_header_len; set_if_smaller(ident_len,FN_REFLEN-1); new_log_ident= my_strndup(buf + ident_offset, (uint) ident_len, MYF(MY_WME)); DBUG_PRINT("debug", ("new_log_ident: '%s'", new_log_ident)); @@ -5818,15 +6466,14 @@ Rotate_log_event::Rotate_log_event(const char* buf, uint event_len, */ #ifndef MYSQL_CLIENT -bool Rotate_log_event::write(IO_CACHE* file) +bool Rotate_log_event::write() { char buf[ROTATE_HEADER_LEN]; int8store(buf + R_POS_OFFSET, pos); - return (write_header(file, ROTATE_HEADER_LEN + ident_len) || - wrapper_my_b_safe_write(file, (uchar*) buf, ROTATE_HEADER_LEN) || - wrapper_my_b_safe_write(file, (uchar*) new_log_ident, - (uint) ident_len) || - write_footer(file)); + return (write_header(ROTATE_HEADER_LEN + ident_len) || + write_data(buf, ROTATE_HEADER_LEN) || + write_data(new_log_ident, (uint) ident_len) || + write_footer()); } #endif @@ -5845,18 +6492,18 @@ bool Rotate_log_event::write(IO_CACHE* file) @retval 0 ok + 1 error */ -int Rotate_log_event::do_update_pos(Relay_log_info *rli) +int Rotate_log_event::do_update_pos(rpl_group_info *rgi) { + int error= 0; + Relay_log_info *rli= rgi->rli; DBUG_ENTER("Rotate_log_event::do_update_pos"); -#ifndef DBUG_OFF - char buf[32]; -#endif DBUG_PRINT("info", ("server_id=%lu; ::server_id=%lu", - (ulong) this->server_id, (ulong) ::server_id)); + (ulong) this->server_id, (ulong) global_system_variables.server_id)); DBUG_PRINT("info", ("new_log_ident: %s", this->new_log_ident)); - DBUG_PRINT("info", ("pos: %s", llstr(this->pos, buf))); + DBUG_PRINT("info", ("pos: %llu", this->pos)); /* If we are in a transaction or in a group: the only normal case is @@ -5873,10 +6520,16 @@ int Rotate_log_event::do_update_pos(Relay_log_info *rli) correspond to the beginning of the transaction. Starting from 5.0.0, there also are some rotates from the slave itself, in the relay log, which shall not change the group positions. + + In parallel replication, rotate event is executed out-of-band with normal + events, so we cannot update group_master_log_name or _pos here, it will + be updated with the next normal event instead. */ - if ((server_id != ::server_id || rli->replicate_same_server_id) && + if ((server_id != global_system_variables.server_id || + rli->replicate_same_server_id) && !is_relay_log_event() && - !rli->is_in_group()) + !rli->is_in_group() && + !rgi->is_parallel_exec) { mysql_mutex_lock(&rli->data_lock); DBUG_PRINT("info", ("old group_master_log_name: '%s' " @@ -5885,39 +6538,39 @@ int Rotate_log_event::do_update_pos(Relay_log_info *rli) (ulong) rli->group_master_log_pos)); memcpy(rli->group_master_log_name, new_log_ident, ident_len+1); rli->notify_group_master_log_name_update(); - rli->inc_group_relay_log_pos(pos, TRUE /* skip_lock */); + rli->inc_group_relay_log_pos(pos, rgi, TRUE /* skip_lock */); DBUG_PRINT("info", ("new group_master_log_name: '%s' " "new group_master_log_pos: %lu", rli->group_master_log_name, (ulong) rli->group_master_log_pos)); mysql_mutex_unlock(&rli->data_lock); - flush_relay_log_info(rli); + rpl_global_gtid_slave_state->record_and_update_gtid(thd, rgi); + error= flush_relay_log_info(rli); /* - Reset thd->variables.option_bits and sql_mode etc, because this could be the signal of - a master's downgrade from 5.0 to 4.0. + Reset thd->variables.option_bits and sql_mode etc, because this could + be the signal of a master's downgrade from 5.0 to 4.0. However, no need to reset description_event_for_exec: indeed, if the next master is 5.0 (even 5.0.1) we will soon get a Format_desc; if the next master is 4.0 then the events are in the slave's format (conversion). */ set_slave_thread_options(thd); - set_slave_thread_default_charset(thd, rli); + set_slave_thread_default_charset(thd, rgi); thd->variables.sql_mode= global_system_variables.sql_mode; thd->variables.auto_increment_increment= thd->variables.auto_increment_offset= 1; } else - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); - - DBUG_RETURN(0); + DBUG_RETURN(error); } Log_event::enum_skip_reason -Rotate_log_event::do_shall_skip(Relay_log_info *rli) +Rotate_log_event::do_shall_skip(rpl_group_info *rgi) { - enum_skip_reason reason= Log_event::do_shall_skip(rli); + enum_skip_reason reason= Log_event::do_shall_skip(rgi); switch (reason) { case Log_event::EVENT_SKIP_NOT: @@ -5935,6 +6588,733 @@ Rotate_log_event::do_shall_skip(Relay_log_info *rli) /************************************************************************** + Binlog_checkpoint_log_event methods +**************************************************************************/ + +#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) +void Binlog_checkpoint_log_event::pack_info(Protocol *protocol) +{ + protocol->store(binlog_file_name, binlog_file_len, &my_charset_bin); +} + + +Log_event::enum_skip_reason +Binlog_checkpoint_log_event::do_shall_skip(rpl_group_info *rgi) +{ + enum_skip_reason reason= Log_event::do_shall_skip(rgi); + if (reason == EVENT_SKIP_COUNT) + reason= EVENT_SKIP_NOT; + return reason; +} +#endif + + +#ifdef MYSQL_CLIENT +void Binlog_checkpoint_log_event::print(FILE *file, + PRINT_EVENT_INFO *print_event_info) +{ + Write_on_release_cache cache(&print_event_info->head_cache, file, + Write_on_release_cache::FLUSH_F); + + if (print_event_info->short_form) + return; + print_header(&cache, print_event_info, FALSE); + my_b_write_string(&cache, "\tBinlog checkpoint "); + my_b_write(&cache, (uchar*)binlog_file_name, binlog_file_len); + my_b_write_byte(&cache, '\n'); +} +#endif /* MYSQL_CLIENT */ + + +#ifdef MYSQL_SERVER +Binlog_checkpoint_log_event::Binlog_checkpoint_log_event( + const char *binlog_file_name_arg, + uint binlog_file_len_arg) + :Log_event(), + binlog_file_name(my_strndup(binlog_file_name_arg, binlog_file_len_arg, + MYF(MY_WME))), + binlog_file_len(binlog_file_len_arg) +{ + cache_type= EVENT_NO_CACHE; +} +#endif /* MYSQL_SERVER */ + + +Binlog_checkpoint_log_event::Binlog_checkpoint_log_event( + const char *buf, uint event_len, + const Format_description_log_event *description_event) + :Log_event(buf, description_event), binlog_file_name(0) +{ + uint8 header_size= description_event->common_header_len; + uint8 post_header_len= + description_event->post_header_len[BINLOG_CHECKPOINT_EVENT-1]; + if (event_len < header_size + post_header_len || + post_header_len < BINLOG_CHECKPOINT_HEADER_LEN) + return; + buf+= header_size; + /* See uint4korr and int4store below */ + compile_time_assert(BINLOG_CHECKPOINT_HEADER_LEN == 4); + binlog_file_len= uint4korr(buf); + if (event_len - (header_size + post_header_len) < binlog_file_len) + return; + binlog_file_name= my_strndup(buf + post_header_len, binlog_file_len, + MYF(MY_WME)); + return; +} + + +#ifndef MYSQL_CLIENT +bool Binlog_checkpoint_log_event::write() +{ + uchar buf[BINLOG_CHECKPOINT_HEADER_LEN]; + int4store(buf, binlog_file_len); + return write_header(BINLOG_CHECKPOINT_HEADER_LEN + binlog_file_len) || + write_data(buf, BINLOG_CHECKPOINT_HEADER_LEN) || + write_data(binlog_file_name, binlog_file_len) || + write_footer(); +} +#endif /* MYSQL_CLIENT */ + + +/************************************************************************** + Global transaction ID stuff +**************************************************************************/ + +Gtid_log_event::Gtid_log_event(const char *buf, uint event_len, + const Format_description_log_event *description_event) + : Log_event(buf, description_event), seq_no(0), commit_id(0) +{ + uint8 header_size= description_event->common_header_len; + uint8 post_header_len= description_event->post_header_len[GTID_EVENT-1]; + if (event_len < header_size + post_header_len || + post_header_len < GTID_HEADER_LEN) + return; + + buf+= header_size; + seq_no= uint8korr(buf); + buf+= 8; + domain_id= uint4korr(buf); + buf+= 4; + flags2= *buf; + if (flags2 & FL_GROUP_COMMIT_ID) + { + if (event_len < (uint)header_size + GTID_HEADER_LEN + 2) + { + seq_no= 0; // So is_valid() returns false + return; + } + ++buf; + commit_id= uint8korr(buf); + } +} + + +#ifdef MYSQL_SERVER + +Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg, + uint32 domain_id_arg, bool standalone, + uint16 flags_arg, bool is_transactional, + uint64 commit_id_arg) + : Log_event(thd_arg, flags_arg, is_transactional), + seq_no(seq_no_arg), commit_id(commit_id_arg), domain_id(domain_id_arg), + flags2((standalone ? FL_STANDALONE : 0) | (commit_id_arg ? FL_GROUP_COMMIT_ID : 0)) +{ + cache_type= Log_event::EVENT_NO_CACHE; + if (thd_arg->transaction.stmt.trans_did_wait() || + thd_arg->transaction.all.trans_did_wait()) + flags2|= FL_WAITED; + if (thd_arg->transaction.stmt.trans_did_ddl() || + thd_arg->transaction.stmt.has_created_dropped_temp_table() || + thd_arg->transaction.all.trans_did_ddl() || + thd_arg->transaction.all.has_created_dropped_temp_table()) + flags2|= FL_DDL; + else if (is_transactional) + flags2|= FL_TRANSACTIONAL; + if (!(thd_arg->variables.option_bits & OPTION_RPL_SKIP_PARALLEL)) + flags2|= FL_ALLOW_PARALLEL; + /* Preserve any DDL or WAITED flag in the slave's binlog. */ + if (thd_arg->rgi_slave) + flags2|= (thd_arg->rgi_slave->gtid_ev_flags2 & (FL_DDL|FL_WAITED)); +} + + +/* + Used to record GTID while sending binlog to slave, without having to + fully contruct every Gtid_log_event() needlessly. +*/ +bool +Gtid_log_event::peek(const char *event_start, size_t event_len, + enum enum_binlog_checksum_alg checksum_alg, + uint32 *domain_id, uint32 *server_id, uint64 *seq_no, + uchar *flags2, const Format_description_log_event *fdev) +{ + const char *p; + + if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + { + if (event_len > BINLOG_CHECKSUM_LEN) + event_len-= BINLOG_CHECKSUM_LEN; + else + event_len= 0; + } + else + DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || + checksum_alg == BINLOG_CHECKSUM_ALG_OFF); + + if (event_len < (uint32)fdev->common_header_len + GTID_HEADER_LEN) + return true; + *server_id= uint4korr(event_start + SERVER_ID_OFFSET); + p= event_start + fdev->common_header_len; + *seq_no= uint8korr(p); + p+= 8; + *domain_id= uint4korr(p); + p+= 4; + *flags2= (uchar)*p; + return false; +} + + +bool +Gtid_log_event::write() +{ + uchar buf[GTID_HEADER_LEN+2]; + size_t write_len; + + int8store(buf, seq_no); + int4store(buf+8, domain_id); + buf[12]= flags2; + if (flags2 & FL_GROUP_COMMIT_ID) + { + int8store(buf+13, commit_id); + write_len= GTID_HEADER_LEN + 2; + } + else + { + bzero(buf+13, GTID_HEADER_LEN-13); + write_len= GTID_HEADER_LEN; + } + return write_header(write_len) || + write_data(buf, write_len) || + write_footer(); +} + + +/* + Replace a GTID event with either a BEGIN event, dummy event, or nothing, as + appropriate to work with old slave that does not know global transaction id. + + The need_dummy_event argument is an IN/OUT argument. It is passed as TRUE + if slave has capability lower than MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES. + It is returned TRUE if we return a BEGIN (or dummy) event to be sent to the + slave, FALSE if event should be skipped completely. +*/ +int +Gtid_log_event::make_compatible_event(String *packet, bool *need_dummy_event, + ulong ev_offset, + enum enum_binlog_checksum_alg checksum_alg) +{ + uchar flags2; + if (packet->length() - ev_offset < LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN) + return 1; + flags2= (*packet)[ev_offset + LOG_EVENT_HEADER_LEN + 12]; + if (flags2 & FL_STANDALONE) + { + if (*need_dummy_event) + return Query_log_event::dummy_event(packet, ev_offset, checksum_alg); + return 0; + } + + *need_dummy_event= true; + return Query_log_event::begin_event(packet, ev_offset, checksum_alg); +} + + +#ifdef HAVE_REPLICATION +void +Gtid_log_event::pack_info(Protocol *protocol) +{ + char buf[6+5+10+1+10+1+20+1+4+20+1]; + char *p; + p = strmov(buf, (flags2 & FL_STANDALONE ? "GTID " : "BEGIN GTID ")); + p= longlong10_to_str(domain_id, p, 10); + *p++= '-'; + p= longlong10_to_str(server_id, p, 10); + *p++= '-'; + p= longlong10_to_str(seq_no, p, 10); + if (flags2 & FL_GROUP_COMMIT_ID) + { + p= strmov(p, " cid="); + p= longlong10_to_str(commit_id, p, 10); + } + + protocol->store(buf, p-buf, &my_charset_bin); +} + +static char gtid_begin_string[] = "BEGIN"; + +int +Gtid_log_event::do_apply_event(rpl_group_info *rgi) +{ + ulonglong bits= thd->variables.option_bits; + thd->variables.server_id= this->server_id; + thd->variables.gtid_domain_id= this->domain_id; + thd->variables.gtid_seq_no= this->seq_no; + rgi->gtid_ev_flags2= flags2; + thd->reset_for_next_command(); + + if (opt_gtid_strict_mode && opt_bin_log && opt_log_slave_updates) + { + if (mysql_bin_log.check_strict_gtid_sequence(this->domain_id, + this->server_id, this->seq_no)) + return 1; + } + + DBUG_ASSERT((bits & OPTION_GTID_BEGIN) == 0); + if (flags2 & FL_STANDALONE) + return 0; + + /* Execute this like a BEGIN query event. */ + bits|= OPTION_GTID_BEGIN; + if (flags2 & FL_ALLOW_PARALLEL) + bits&= ~(ulonglong)OPTION_RPL_SKIP_PARALLEL; + else + bits|= (ulonglong)OPTION_RPL_SKIP_PARALLEL; + thd->variables.option_bits= bits; + DBUG_PRINT("info", ("Set OPTION_GTID_BEGIN")); + thd->set_query_and_id(gtid_begin_string, sizeof(gtid_begin_string)-1, + &my_charset_bin, next_query_id()); + thd->lex->sql_command= SQLCOM_BEGIN; + thd->is_slave_error= 0; + status_var_increment(thd->status_var.com_stat[thd->lex->sql_command]); + if (trans_begin(thd, 0)) + { + DBUG_PRINT("error", ("trans_begin() failed")); + thd->is_slave_error= 1; + } + thd->update_stats(); + + if (likely(!thd->is_slave_error)) + general_log_write(thd, COM_QUERY, thd->query(), thd->query_length()); + + thd->reset_query(); + free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC)); + return thd->is_slave_error; +} + + +int +Gtid_log_event::do_update_pos(rpl_group_info *rgi) +{ + rgi->inc_event_relay_log_pos(); + return 0; +} + + +Log_event::enum_skip_reason +Gtid_log_event::do_shall_skip(rpl_group_info *rgi) +{ + Relay_log_info *rli= rgi->rli; + /* + An event skipped due to @@skip_replication must not be counted towards the + number of events to be skipped due to @@sql_slave_skip_counter. + */ + if (flags & LOG_EVENT_SKIP_REPLICATION_F && + opt_replicate_events_marked_for_skip != RPL_SKIP_REPLICATE) + return Log_event::EVENT_SKIP_IGNORE; + + if (rli->slave_skip_counter > 0) + { + if (!(flags2 & FL_STANDALONE)) + { + thd->variables.option_bits|= OPTION_BEGIN; + DBUG_ASSERT(rgi->rli->get_flag(Relay_log_info::IN_TRANSACTION)); + } + return Log_event::continue_group(rgi); + } + return Log_event::do_shall_skip(rgi); +} + + +#endif /* HAVE_REPLICATION */ + +#else /* !MYSQL_SERVER */ + +void +Gtid_log_event::print(FILE *file, PRINT_EVENT_INFO *print_event_info) +{ + Write_on_release_cache cache(&print_event_info->head_cache, file, + Write_on_release_cache::FLUSH_F); + char buf[21]; + char buf2[21]; + + if (!print_event_info->short_form) + { + print_header(&cache, print_event_info, FALSE); + longlong10_to_str(seq_no, buf, 10); + my_b_printf(&cache, "\tGTID %u-%u-%s", domain_id, server_id, buf); + if (flags2 & FL_GROUP_COMMIT_ID) + { + longlong10_to_str(commit_id, buf2, 10); + my_b_printf(&cache, " cid=%s", buf2); + } + if (flags2 & FL_DDL) + my_b_write_string(&cache, " ddl"); + if (flags2 & FL_TRANSACTIONAL) + my_b_write_string(&cache, " trans"); + if (flags2 & FL_WAITED) + my_b_write_string(&cache, " waited"); + my_b_printf(&cache, "\n"); + + if (!print_event_info->allow_parallel_printed || + print_event_info->allow_parallel != !!(flags2 & FL_ALLOW_PARALLEL)) + { + my_b_printf(&cache, + "/*!100101 SET @@session.skip_parallel_replication=%u*/%s\n", + !(flags2 & FL_ALLOW_PARALLEL), print_event_info->delimiter); + print_event_info->allow_parallel= !!(flags2 & FL_ALLOW_PARALLEL); + print_event_info->allow_parallel_printed= true; + } + + if (!print_event_info->domain_id_printed || + print_event_info->domain_id != domain_id) + { + my_b_printf(&cache, "/*!100001 SET @@session.gtid_domain_id=%u*/%s\n", + domain_id, print_event_info->delimiter); + print_event_info->domain_id= domain_id; + print_event_info->domain_id_printed= true; + } + + if (!print_event_info->server_id_printed || + print_event_info->server_id != server_id) + { + my_b_printf(&cache, "/*!100001 SET @@session.server_id=%u*/%s\n", + server_id, print_event_info->delimiter); + print_event_info->server_id= server_id; + print_event_info->server_id_printed= true; + } + + my_b_printf(&cache, "/*!100001 SET @@session.gtid_seq_no=%s*/%s\n", + buf, print_event_info->delimiter); + } + if (!(flags2 & FL_STANDALONE)) + my_b_printf(&cache, "BEGIN\n%s\n", print_event_info->delimiter); +} + +#endif /* MYSQL_SERVER */ + + +/* GTID list. */ + +Gtid_list_log_event::Gtid_list_log_event(const char *buf, uint event_len, + const Format_description_log_event *description_event) + : Log_event(buf, description_event), count(0), list(0), sub_id_list(0) +{ + uint32 i; + uint32 val; + uint8 header_size= description_event->common_header_len; + uint8 post_header_len= description_event->post_header_len[GTID_LIST_EVENT-1]; + if (event_len < header_size + post_header_len || + post_header_len < GTID_LIST_HEADER_LEN) + return; + + buf+= header_size; + val= uint4korr(buf); + count= val & ((1<<28)-1); + gl_flags= val & ((uint32)0xf << 28); + buf+= 4; + if (event_len - (header_size + post_header_len) < count*element_size || + (!(list= (rpl_gtid *)my_malloc(count*sizeof(*list) + (count == 0), + MYF(MY_WME))))) + return; + + for (i= 0; i < count; ++i) + { + list[i].domain_id= uint4korr(buf); + buf+= 4; + list[i].server_id= uint4korr(buf); + buf+= 4; + list[i].seq_no= uint8korr(buf); + buf+= 8; + } + +#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) + if ((gl_flags & FLAG_IGN_GTIDS)) + { + uint32 i; + if (!(sub_id_list= (uint64 *)my_malloc(count*sizeof(uint64), MYF(MY_WME)))) + { + my_free(list); + list= NULL; + return; + } + for (i= 0; i < count; ++i) + { + if (!(sub_id_list[i]= + rpl_global_gtid_slave_state->next_sub_id(list[i].domain_id))) + { + my_free(list); + my_free(sub_id_list); + list= NULL; + sub_id_list= NULL; + return; + } + } + } +#endif +} + + +#ifdef MYSQL_SERVER + +Gtid_list_log_event::Gtid_list_log_event(rpl_binlog_state *gtid_set, + uint32 gl_flags_) + : count(gtid_set->count()), gl_flags(gl_flags_), list(0), sub_id_list(0) +{ + cache_type= EVENT_NO_CACHE; + /* Failure to allocate memory will be caught by is_valid() returning false. */ + if (count < (1<<28) && + (list = (rpl_gtid *)my_malloc(count * sizeof(*list) + (count == 0), + MYF(MY_WME)))) + gtid_set->get_gtid_list(list, count); +} + + +Gtid_list_log_event::Gtid_list_log_event(slave_connection_state *gtid_set, + uint32 gl_flags_) + : count(gtid_set->count()), gl_flags(gl_flags_), list(0), sub_id_list(0) +{ + cache_type= EVENT_NO_CACHE; + /* Failure to allocate memory will be caught by is_valid() returning false. */ + if (count < (1<<28) && + (list = (rpl_gtid *)my_malloc(count * sizeof(*list) + (count == 0), + MYF(MY_WME)))) + { + gtid_set->get_gtid_list(list, count); +#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) + if (gl_flags & FLAG_IGN_GTIDS) + { + uint32 i; + + if (!(sub_id_list= (uint64 *)my_malloc(count * sizeof(uint64), + MYF(MY_WME)))) + { + my_free(list); + list= NULL; + return; + } + for (i= 0; i < count; ++i) + { + if (!(sub_id_list[i]= + rpl_global_gtid_slave_state->next_sub_id(list[i].domain_id))) + { + my_free(list); + my_free(sub_id_list); + list= NULL; + sub_id_list= NULL; + return; + } + } + } +#endif + } +} + + +#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) +bool +Gtid_list_log_event::to_packet(String *packet) +{ + uint32 i; + uchar *p; + uint32 needed_length; + + DBUG_ASSERT(count < 1<<28); + + needed_length= packet->length() + get_data_size(); + if (packet->reserve(needed_length)) + return true; + p= (uchar *)packet->ptr() + packet->length();; + packet->length(needed_length); + int4store(p, (count & ((1<<28)-1)) | gl_flags); + p += 4; + /* Initialise the padding for empty Gtid_list. */ + if (count == 0) + int2store(p, 0); + for (i= 0; i < count; ++i) + { + int4store(p, list[i].domain_id); + int4store(p+4, list[i].server_id); + int8store(p+8, list[i].seq_no); + p += 16; + } + + return false; +} + + +bool +Gtid_list_log_event::write() +{ + char buf[128]; + String packet(buf, sizeof(buf), system_charset_info); + + packet.length(0); + if (to_packet(&packet)) + return true; + return write_header(get_data_size()) || + write_data(packet.ptr(), packet.length()) || + write_footer(); +} + + +int +Gtid_list_log_event::do_apply_event(rpl_group_info *rgi) +{ + Relay_log_info *rli= const_cast<Relay_log_info*>(rgi->rli); + int ret; + if (gl_flags & FLAG_IGN_GTIDS) + { + uint32 i; + for (i= 0; i < count; ++i) + { + if ((ret= rpl_global_gtid_slave_state->record_gtid(thd, &list[i], + sub_id_list[i], + NULL, false))) + return ret; + rpl_global_gtid_slave_state->update_state_hash(sub_id_list[i], &list[i], + NULL); + } + } + ret= Log_event::do_apply_event(rgi); + if (rli->until_condition == Relay_log_info::UNTIL_GTID && + (gl_flags & FLAG_UNTIL_REACHED)) + { + char str_buf[128]; + String str(str_buf, sizeof(str_buf), system_charset_info); + rli->until_gtid_pos.to_string(&str); + sql_print_information("Slave SQL thread stops because it reached its" + " UNTIL master_gtid_pos %s", str.c_ptr_safe()); + rli->abort_slave= true; + rli->stop_for_until= true; + } + free_root(thd->mem_root, MYF(MY_KEEP_PREALLOC)); + return ret; +} + + +Log_event::enum_skip_reason +Gtid_list_log_event::do_shall_skip(rpl_group_info *rgi) +{ + enum_skip_reason reason= Log_event::do_shall_skip(rgi); + if (reason == EVENT_SKIP_COUNT) + reason= EVENT_SKIP_NOT; + return reason; +} + + +void +Gtid_list_log_event::pack_info(Protocol *protocol) +{ + char buf_mem[1024]; + String buf(buf_mem, sizeof(buf_mem), system_charset_info); + uint32 i; + bool first; + + buf.length(0); + buf.append(STRING_WITH_LEN("[")); + first= true; + for (i= 0; i < count; ++i) + rpl_slave_state_tostring_helper(&buf, &list[i], &first); + buf.append(STRING_WITH_LEN("]")); + + protocol->store(&buf); +} +#endif /* HAVE_REPLICATION */ + +#else /* !MYSQL_SERVER */ + +void +Gtid_list_log_event::print(FILE *file, PRINT_EVENT_INFO *print_event_info) +{ + if (!print_event_info->short_form) + { + Write_on_release_cache cache(&print_event_info->head_cache, file, + Write_on_release_cache::FLUSH_F); + char buf[21]; + uint32 i; + + print_header(&cache, print_event_info, FALSE); + my_b_printf(&cache, "\tGtid list ["); + for (i= 0; i < count; ++i) + { + longlong10_to_str(list[i].seq_no, buf, 10); + my_b_printf(&cache, "%u-%u-%s", list[i].domain_id, + list[i].server_id, buf); + if (i < count-1) + my_b_printf(&cache, ",\n# "); + } + my_b_printf(&cache, "]\n"); + } +} + +#endif /* MYSQL_SERVER */ + + +/* + Used to record gtid_list event while sending binlog to slave, without having to + fully contruct the event object. +*/ +bool +Gtid_list_log_event::peek(const char *event_start, uint32 event_len, + enum enum_binlog_checksum_alg checksum_alg, + rpl_gtid **out_gtid_list, uint32 *out_list_len, + const Format_description_log_event *fdev) +{ + const char *p; + uint32 count_field, count; + rpl_gtid *gtid_list; + + if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + { + if (event_len > BINLOG_CHECKSUM_LEN) + event_len-= BINLOG_CHECKSUM_LEN; + else + event_len= 0; + } + else + DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || + checksum_alg == BINLOG_CHECKSUM_ALG_OFF); + + if (event_len < (uint32)fdev->common_header_len + GTID_LIST_HEADER_LEN) + return true; + p= event_start + fdev->common_header_len; + count_field= uint4korr(p); + p+= 4; + count= count_field & ((1<<28)-1); + if (event_len < (uint32)fdev->common_header_len + GTID_LIST_HEADER_LEN + + 16 * count) + return true; + if (!(gtid_list= (rpl_gtid *)my_malloc(sizeof(rpl_gtid)*count + (count == 0), + MYF(MY_WME)))) + return true; + *out_gtid_list= gtid_list; + *out_list_len= count; + while (count--) + { + gtid_list->domain_id= uint4korr(p); + p+= 4; + gtid_list->server_id= uint4korr(p); + p+= 4; + gtid_list->seq_no= uint8korr(p); + p+= 8; + ++gtid_list; + } + + return false; +} + + +/************************************************************************** Intvar_log_event methods **************************************************************************/ @@ -5943,7 +7323,7 @@ Rotate_log_event::do_shall_skip(Relay_log_info *rli) */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Intvar_log_event::pack_info(THD *thd, Protocol *protocol) +void Intvar_log_event::pack_info(Protocol *protocol) { char buf[256], *pos; pos= strmake(buf, get_var_type_name(), sizeof(buf)-23); @@ -5989,14 +7369,14 @@ const char* Intvar_log_event::get_var_type_name() */ #ifndef MYSQL_CLIENT -bool Intvar_log_event::write(IO_CACHE* file) +bool Intvar_log_event::write() { uchar buf[9]; buf[I_TYPE_OFFSET]= (uchar) type; int8store(buf + I_VAL_OFFSET, val); - return (write_header(file, sizeof(buf)) || - wrapper_my_b_safe_write(file, buf, sizeof(buf)) || - write_footer(file)); + return write_header(sizeof(buf)) || + write_data(buf, sizeof(buf)) || + write_footer(); } #endif @@ -6009,15 +7389,14 @@ bool Intvar_log_event::write(IO_CACHE* file) void Intvar_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) { char llbuff[22]; - const char *msg; - LINT_INIT(msg); + const char *UNINIT_VAR(msg); Write_on_release_cache cache(&print_event_info->head_cache, file, Write_on_release_cache::FLUSH_F); if (!print_event_info->short_form) { print_header(&cache, print_event_info, FALSE); - my_b_printf(&cache, "\tIntvar\n"); + my_b_write_string(&cache, "\tIntvar\n"); } my_b_printf(&cache, "SET "); @@ -6045,19 +7424,13 @@ void Intvar_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) Intvar_log_event::do_apply_event() */ -int Intvar_log_event::do_apply_event(Relay_log_info const *rli) +int Intvar_log_event::do_apply_event(rpl_group_info *rgi) { DBUG_ENTER("Intvar_log_event::do_apply_event"); - /* - We are now in a statement until the associated query log event has - been processed. - */ - const_cast<Relay_log_info*>(rli)->set_flag(Relay_log_info::IN_STMT); - - if (rli->deferred_events_collecting) + if (rgi->deferred_events_collecting) { DBUG_PRINT("info",("deferring event")); - DBUG_RETURN(rli->deferred_events->add(this)); + DBUG_RETURN(rgi->deferred_events->add(this)); } switch (type) { @@ -6072,15 +7445,15 @@ int Intvar_log_event::do_apply_event(Relay_log_info const *rli) DBUG_RETURN(0); } -int Intvar_log_event::do_update_pos(Relay_log_info *rli) +int Intvar_log_event::do_update_pos(rpl_group_info *rgi) { - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); return 0; } Log_event::enum_skip_reason -Intvar_log_event::do_shall_skip(Relay_log_info *rli) +Intvar_log_event::do_shall_skip(rpl_group_info *rgi) { /* It is a common error to set the slave skip counter to 1 instead of @@ -6090,7 +7463,7 @@ Intvar_log_event::do_shall_skip(Relay_log_info *rli) that we do not change the value of the slave skip counter since it will be decreased by the following insert event. */ - return continue_group(rli); + return continue_group(rgi); } #endif @@ -6101,7 +7474,7 @@ Intvar_log_event::do_shall_skip(Relay_log_info *rli) **************************************************************************/ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Rand_log_event::pack_info(THD *thd, Protocol *protocol) +void Rand_log_event::pack_info(Protocol *protocol) { char buf1[256], *pos; pos= strmov(buf1,"rand_seed1="); @@ -6126,14 +7499,14 @@ Rand_log_event::Rand_log_event(const char* buf, #ifndef MYSQL_CLIENT -bool Rand_log_event::write(IO_CACHE* file) +bool Rand_log_event::write() { uchar buf[16]; int8store(buf + RAND_SEED1_OFFSET, seed1); int8store(buf + RAND_SEED2_OFFSET, seed2); - return (write_header(file, sizeof(buf)) || - wrapper_my_b_safe_write(file, buf, sizeof(buf)) || - write_footer(file)); + return write_header(sizeof(buf)) || + write_data(buf, sizeof(buf)) || + write_footer(); } #endif @@ -6148,7 +7521,7 @@ void Rand_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) if (!print_event_info->short_form) { print_header(&cache, print_event_info, FALSE); - my_b_printf(&cache, "\tRand\n"); + my_b_write_string(&cache, "\tRand\n"); } my_b_printf(&cache, "SET @@RAND_SEED1=%s, @@RAND_SEED2=%s%s\n", llstr(seed1, llbuff),llstr(seed2, llbuff2), @@ -6158,31 +7531,25 @@ void Rand_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -int Rand_log_event::do_apply_event(Relay_log_info const *rli) +int Rand_log_event::do_apply_event(rpl_group_info *rgi) { - /* - We are now in a statement until the associated query log event has - been processed. - */ - const_cast<Relay_log_info*>(rli)->set_flag(Relay_log_info::IN_STMT); - - if (rli->deferred_events_collecting) - return rli->deferred_events->add(this); + if (rgi->deferred_events_collecting) + return rgi->deferred_events->add(this); thd->rand.seed1= (ulong) seed1; thd->rand.seed2= (ulong) seed2; return 0; } -int Rand_log_event::do_update_pos(Relay_log_info *rli) +int Rand_log_event::do_update_pos(rpl_group_info *rgi) { - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); return 0; } Log_event::enum_skip_reason -Rand_log_event::do_shall_skip(Relay_log_info *rli) +Rand_log_event::do_shall_skip(rpl_group_info *rgi) { /* It is a common error to set the slave skip counter to 1 instead of @@ -6192,7 +7559,7 @@ Rand_log_event::do_shall_skip(Relay_log_info *rli) that we do not change the value of the slave skip counter since it will be decreased by the following insert event. */ - return continue_group(rli); + return continue_group(rgi); } /** @@ -6206,14 +7573,15 @@ Rand_log_event::do_shall_skip(Relay_log_info *rli) bool slave_execute_deferred_events(THD *thd) { bool res= false; - Relay_log_info *rli= thd->rli_slave; + rpl_group_info *rgi= thd->rgi_slave; - DBUG_ASSERT(rli && (!rli->deferred_events_collecting || rli->deferred_events)); + DBUG_ASSERT(rgi && (!rgi->deferred_events_collecting || rgi->deferred_events)); - if (!rli->deferred_events_collecting || rli->deferred_events->is_empty()) + if (!rgi->deferred_events_collecting || rgi->deferred_events->is_empty()) return res; - res= rli->deferred_events->execute(rli); + res= rgi->deferred_events->execute(rgi); + rgi->deferred_events->rewind(); return res; } @@ -6226,7 +7594,7 @@ bool slave_execute_deferred_events(THD *thd) **************************************************************************/ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Xid_log_event::pack_info(THD *thd, Protocol *protocol) +void Xid_log_event::pack_info(Protocol *protocol) { char buf[128], *pos; pos= strmov(buf, "COMMIT /* xid="); @@ -6258,12 +7626,12 @@ Xid_log_event(const char* buf, #ifndef MYSQL_CLIENT -bool Xid_log_event::write(IO_CACHE* file) +bool Xid_log_event::write() { DBUG_EXECUTE_IF("do_not_write_xid", return 0;); - return (write_header(file, sizeof(xid)) || - wrapper_my_b_safe_write(file, (uchar*) &xid, sizeof(xid)) || - write_footer(file)); + return write_header(sizeof(xid)) || + write_data((uchar*)&xid, sizeof(xid)) || + write_footer(); } #endif @@ -6288,15 +7656,72 @@ void Xid_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -int Xid_log_event::do_apply_event(Relay_log_info const *rli) +int Xid_log_event::do_apply_event(rpl_group_info *rgi) { bool res; + int err; + rpl_gtid gtid; + uint64 sub_id= 0; + Relay_log_info const *rli= rgi->rli; + + /* + XID_EVENT works like a COMMIT statement. And it also updates the + mysql.gtid_slave_pos table with the GTID of the current transaction. + + Therefore, it acts much like a normal SQL statement, so we need to do + THD::reset_for_next_command() as if starting a new statement. + */ + thd->reset_for_next_command(); + /* + Record any GTID in the same transaction, so slave state is transactionally + consistent. + */ +#ifdef WITH_WSREP + thd->wsrep_affected_rows= 0; +#endif + + if (rgi->gtid_pending) + { + sub_id= rgi->gtid_sub_id; + rgi->gtid_pending= false; + + gtid= rgi->current_gtid; + err= rpl_global_gtid_slave_state->record_gtid(thd, >id, sub_id, rgi, + false); + if (err) + { + int ec= thd->get_stmt_da()->sql_errno(); + /* + Do not report an error if this is really a kill due to a deadlock. + In this case, the transaction will be re-tried instead. + */ + if (!is_parallel_retry_error(rgi, ec)) + rli->report(ERROR_LEVEL, ER_CANNOT_UPDATE_GTID_STATE, rgi->gtid_info(), + "Error during XID COMMIT: failed to update GTID state in " + "%s.%s: %d: %s", + "mysql", rpl_gtid_slave_state_table_name.str, ec, + thd->get_stmt_da()->message()); + thd->is_slave_error= 1; + return err; + } + + DBUG_EXECUTE_IF("gtid_fail_after_record_gtid", + { my_error(ER_ERROR_DURING_COMMIT, MYF(0), HA_ERR_WRONG_COMMAND); + thd->is_slave_error= 1; + return 1; + }); + } + /* For a slave Xid_log_event is COMMIT */ general_log_print(thd, COM_QUERY, "COMMIT /* implicit, from Xid_log_event */"); + thd->variables.option_bits&= ~OPTION_GTID_BEGIN; res= trans_commit(thd); /* Automatically rolls back on error. */ thd->mdl_context.release_transactional_locks(); + if (!res && sub_id) + rpl_global_gtid_slave_state->update_state_hash(sub_id, >id, rgi); + /* Increment the global status commit count variable */ @@ -6306,14 +7731,31 @@ int Xid_log_event::do_apply_event(Relay_log_info const *rli) } Log_event::enum_skip_reason -Xid_log_event::do_shall_skip(Relay_log_info *rli) +Xid_log_event::do_shall_skip(rpl_group_info *rgi) { DBUG_ENTER("Xid_log_event::do_shall_skip"); - if (rli->slave_skip_counter > 0) { - thd->variables.option_bits&= ~OPTION_BEGIN; + if (rgi->rli->slave_skip_counter > 0) + { + DBUG_ASSERT(!rgi->rli->get_flag(Relay_log_info::IN_TRANSACTION)); + thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_GTID_BEGIN); DBUG_RETURN(Log_event::EVENT_SKIP_COUNT); } - DBUG_RETURN(Log_event::do_shall_skip(rli)); +#ifdef WITH_WSREP + else if (wsrep_mysql_replication_bundle && WSREP_ON && + opt_slave_domain_parallel_threads == 0) + { + if (++thd->wsrep_mysql_replicated < (int)wsrep_mysql_replication_bundle) + { + WSREP_DEBUG("skipping wsrep commit %d", thd->wsrep_mysql_replicated); + DBUG_RETURN(Log_event::EVENT_SKIP_IGNORE); + } + else + { + thd->wsrep_mysql_replicated = 0; + } + } +#endif + DBUG_RETURN(Log_event::do_shall_skip(rgi)); } #endif /* !MYSQL_CLIENT */ @@ -6332,14 +7774,14 @@ user_var_append_name_part(THD *thd, String *buf, buf->append("="); } -void User_var_log_event::pack_info(THD *thd, Protocol* protocol) +void User_var_log_event::pack_info(Protocol* protocol) { if (is_null) { char buf_mem[FN_REFLEN+7]; String buf(buf_mem, sizeof(buf_mem), system_charset_info); buf.length(0); - if (user_var_append_name_part(thd, &buf, name, name_len) || + if (user_var_append_name_part(protocol->thd, &buf, name, name_len) || buf.append("NULL")) return; protocol->store(buf.ptr(), buf.length(), &my_charset_bin); @@ -6355,7 +7797,7 @@ void User_var_log_event::pack_info(THD *thd, Protocol* protocol) String buf(buf_mem, sizeof(buf_mem), system_charset_info); float8get(real_val, val); buf.length(0); - if (user_var_append_name_part(thd, &buf, name, name_len) || + if (user_var_append_name_part(protocol->thd, &buf, name, name_len) || buf.append(buf2, my_gcvt(real_val, MY_GCVT_ARG_DOUBLE, MY_GCVT_MAX_FIELD_WIDTH, buf2, NULL))) return; @@ -6368,7 +7810,7 @@ void User_var_log_event::pack_info(THD *thd, Protocol* protocol) char buf_mem[FN_REFLEN + 22]; String buf(buf_mem, sizeof(buf_mem), system_charset_info); buf.length(0); - if (user_var_append_name_part(thd, &buf, name, name_len) || + if (user_var_append_name_part(protocol->thd, &buf, name, name_len) || buf.append(buf2, longlong10_to_str(uint8korr(val), buf2, ((flags & User_var_log_event::UNSIGNED_F) ? 10 : -10))-buf2)) @@ -6387,7 +7829,7 @@ void User_var_log_event::pack_info(THD *thd, Protocol* protocol) binary2my_decimal(E_DEC_FATAL_ERROR, (uchar*) (val+2), &dec, val[0], val[1]); my_decimal2string(E_DEC_FATAL_ERROR, &dec, 0, 0, 0, &str); - if (user_var_append_name_part(thd, &buf, name, name_len) || + if (user_var_append_name_part(protocol->thd, &buf, name, name_len) || buf.append(buf2)) return; protocol->store(buf.ptr(), buf.length(), &my_charset_bin); @@ -6409,7 +7851,7 @@ void User_var_log_event::pack_info(THD *thd, Protocol* protocol) { size_t old_len; char *beg, *end; - if (user_var_append_name_part(thd, &buf, name, name_len) || + if (user_var_append_name_part(protocol->thd, &buf, name, name_len) || buf.append("_") || buf.append(cs->csname) || buf.append(" ")) @@ -6447,9 +7889,9 @@ User_var_log_event(const char* buf, uint event_len, #endif { bool error= false; - const char* buf_start= buf; + const char* buf_start= buf, *buf_end= buf + event_len; + /* The Post-Header is empty. The Variable Data part begins immediately. */ - const char *start= buf; buf+= description_event->common_header_len + description_event->post_header_len[USER_VAR_EVENT-1]; name_len= uint4korr(buf); @@ -6467,8 +7909,7 @@ User_var_log_event(const char* buf, uint event_len, may have the bigger value possible, is_null= True and there is no payload for val, or even that name_len is 0. */ - if (!valid_buffer_range<uint>(name_len, buf_start, name, - event_len - UV_VAL_IS_NULL)) + if (name + name_len + UV_VAL_IS_NULL > buf_end) { error= true; goto err; @@ -6486,9 +7927,10 @@ User_var_log_event(const char* buf, uint event_len, } else { - if (!valid_buffer_range<uint>(UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE - + UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE, - buf_start, buf, event_len)) + val= (char *) (buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE + + UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE); + + if (val > buf_end) { error= true; goto err; @@ -6498,15 +7940,8 @@ User_var_log_event(const char* buf, uint event_len, charset_number= uint4korr(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE); val_len= uint4korr(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE + UV_CHARSET_NUMBER_SIZE); - val= (char *) (buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE + - UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE); - - if (!valid_buffer_range<uint>(val_len, buf_start, val, event_len)) - { - error= true; - goto err; - } + /** We need to check if this is from an old server that did not pack information for flags. @@ -6518,12 +7953,7 @@ User_var_log_event(const char* buf, uint event_len, Old events will not have this extra byte, thence, we keep the flags set to UNDEF_F. */ - uint bytes_read= ((val + val_len) - start); - if (bytes_read > event_len) - { - error= true; - goto err; - } + uint bytes_read= ((val + val_len) - buf_start); if ((data_written - bytes_read) > 0) { flags= (uint) *(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE + @@ -6539,12 +7969,12 @@ err: #ifndef MYSQL_CLIENT -bool User_var_log_event::write(IO_CACHE* file) +bool User_var_log_event::write() { char buf[UV_NAME_LEN_SIZE]; char buf1[UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE + UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE]; - uchar buf2[max(8, DECIMAL_MAX_FIELD_SIZE + 2)], *pos= buf2; + uchar buf2[MY_MAX(8, DECIMAL_MAX_FIELD_SIZE + 2)], *pos= buf2; uint unsigned_len= 0; uint buf1_length; ulong event_length; @@ -6594,13 +8024,13 @@ bool User_var_log_event::write(IO_CACHE* file) /* Length of the whole event */ event_length= sizeof(buf)+ name_len + buf1_length + val_len + unsigned_len; - return (write_header(file, event_length) || - wrapper_my_b_safe_write(file, (uchar*) buf, sizeof(buf)) || - wrapper_my_b_safe_write(file, (uchar*) name, name_len) || - wrapper_my_b_safe_write(file, (uchar*) buf1, buf1_length) || - wrapper_my_b_safe_write(file, pos, val_len) || - wrapper_my_b_safe_write(file, &flags, unsigned_len) || - write_footer(file)); + return write_header(event_length) || + write_data(buf, sizeof(buf)) || + write_data(name, name_len) || + write_data(buf1, buf1_length) || + write_data(pos, val_len) || + write_data(&flags, unsigned_len) || + write_footer(); } #endif @@ -6618,10 +8048,10 @@ void User_var_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) if (!print_event_info->short_form) { print_header(&cache, print_event_info, FALSE); - my_b_printf(&cache, "\tUser_var\n"); + my_b_write_string(&cache, "\tUser_var\n"); } - my_b_printf(&cache, "SET @"); + my_b_write_string(&cache, "SET @"); my_b_write_backtick_quote(&cache, name, name_len); if (is_null) @@ -6719,17 +8149,17 @@ void User_var_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -int User_var_log_event::do_apply_event(Relay_log_info const *rli) +int User_var_log_event::do_apply_event(rpl_group_info *rgi) { Item *it= 0; CHARSET_INFO *charset; DBUG_ENTER("User_var_log_event::do_apply_event"); query_id_t sav_query_id= 0; /* memorize orig id when deferred applying */ - if (rli->deferred_events_collecting) + if (rgi->deferred_events_collecting) { set_deferred(current_thd->query_id); - DBUG_RETURN(rli->deferred_events->add(this)); + DBUG_RETURN(rgi->deferred_events->add(this)); } else if (is_deferred()) { @@ -6739,7 +8169,7 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli) if (!(charset= get_charset(charset_number, MYF(MY_WME)))) { - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid character set for User var event"); DBUG_RETURN(1); @@ -6750,15 +8180,9 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli) double real_val; longlong int_val; - /* - We are now in a statement until the associated query log event has - been processed. - */ - const_cast<Relay_log_info*>(rli)->set_flag(Relay_log_info::IN_STMT); - if (is_null) { - it= new Item_null(); + it= new (thd->mem_root) Item_null(thd); } else { @@ -6766,26 +8190,26 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli) case REAL_RESULT: if (val_len != 8) { - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid variable length at User var event"); return 1; } float8get(real_val, val); - it= new Item_float(real_val, 0); + it= new (thd->mem_root) Item_float(thd, real_val, 0); val= (char*) &real_val; // Pointer to value in native format val_len= 8; break; case INT_RESULT: if (val_len != 8) { - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid variable length at User var event"); return 1; } int_val= (longlong) uint8korr(val); - it= new Item_int(int_val); + it= new (thd->mem_root) Item_int(thd, int_val); val= (char*) &int_val; // Pointer to value in native format val_len= 8; break; @@ -6793,19 +8217,19 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli) { if (val_len < 3) { - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + rgi->rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid variable length at User var event"); return 1; } - Item_decimal *dec= new Item_decimal((uchar*) val+2, val[0], val[1]); + Item_decimal *dec= new (thd->mem_root) Item_decimal(thd, (uchar*) val+2, val[0], val[1]); it= dec; val= (char *)dec->val_decimal(NULL); val_len= sizeof(my_decimal); break; } case STRING_RESULT: - it= new Item_string(val, val_len, charset); + it= new (thd->mem_root) Item_string(thd, val, val_len, charset); break; case ROW_RESULT: default: @@ -6814,7 +8238,7 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli) } } - Item_func_set_user_var *e= new Item_func_set_user_var(user_var_name, it); + Item_func_set_user_var *e= new (thd->mem_root) Item_func_set_user_var(thd, user_var_name, it); /* Item_func_set_user_var can't substitute something else on its place => 0 can be passed as last argument (reference on item) @@ -6831,7 +8255,7 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli) a single record and with a single column. Thus, like a column value, it could always have IMPLICIT derivation. */ - e->update_hash(val, val_len, type, charset, DERIVATION_IMPLICIT, + e->update_hash(val, val_len, type, charset, (flags & User_var_log_event::UNSIGNED_F)); if (!is_deferred()) free_root(thd->mem_root, 0); @@ -6841,14 +8265,14 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli) DBUG_RETURN(0); } -int User_var_log_event::do_update_pos(Relay_log_info *rli) +int User_var_log_event::do_update_pos(rpl_group_info *rgi) { - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); return 0; } Log_event::enum_skip_reason -User_var_log_event::do_shall_skip(Relay_log_info *rli) +User_var_log_event::do_shall_skip(rpl_group_info *rgi) { /* It is a common error to set the slave skip counter to 1 instead @@ -6858,15 +8282,10 @@ User_var_log_event::do_shall_skip(Relay_log_info *rli) that we do not change the value of the slave skip counter since it will be decreased by the following insert event. */ - return continue_group(rli); + return continue_group(rgi); } #endif /* !MYSQL_CLIENT */ - -/************************************************************************** - Slave_log_event methods -**************************************************************************/ - #ifdef HAVE_REPLICATION #ifdef MYSQL_CLIENT void Unknown_log_event::print(FILE* file_arg, PRINT_EVENT_INFO* print_event_info) @@ -6875,156 +8294,15 @@ void Unknown_log_event::print(FILE* file_arg, PRINT_EVENT_INFO* print_event_info if (print_event_info->short_form) return; - print_header(&cache, print_event_info, FALSE); - my_b_printf(&cache, "\n# %s", "Unknown event\n"); -} -#endif - -#ifndef MYSQL_CLIENT -void Slave_log_event::pack_info(THD *thd, Protocol *protocol) -{ - char buf[256+HOSTNAME_LENGTH], *pos; - pos= strmov(buf, "host="); - pos= strnmov(pos, master_host, HOSTNAME_LENGTH); - pos= strmov(pos, ",port="); - pos= int10_to_str((long) master_port, pos, 10); - pos= strmov(pos, ",log="); - pos= strmov(pos, master_log); - pos= strmov(pos, ",pos="); - pos= longlong10_to_str(master_pos, pos, 10); - protocol->store(buf, pos-buf, &my_charset_bin); -} -#endif /* !MYSQL_CLIENT */ - - -#ifndef MYSQL_CLIENT -/** - @todo - re-write this better without holding both locks at the same time -*/ -Slave_log_event::Slave_log_event(THD* thd_arg, - Relay_log_info* rli) - :Log_event(thd_arg, 0, 0) , mem_pool(0), master_host(0) -{ - DBUG_ENTER("Slave_log_event"); - if (!rli->inited) // QQ When can this happen ? - DBUG_VOID_RETURN; - - Master_info* mi = rli->mi; - // TODO: re-write this better without holding both locks at the same time - mysql_mutex_lock(&mi->data_lock); - mysql_mutex_lock(&rli->data_lock); - master_host_len = strlen(mi->host); - master_log_len = strlen(rli->group_master_log_name); - // on OOM, just do not initialize the structure and print the error - if ((mem_pool = (char*)my_malloc(get_data_size() + 1, - MYF(MY_WME)))) - { - master_host = mem_pool + SL_MASTER_HOST_OFFSET ; - memcpy(master_host, mi->host, master_host_len + 1); - master_log = master_host + master_host_len + 1; - memcpy(master_log, rli->group_master_log_name, master_log_len + 1); - master_port = mi->port; - master_pos = rli->group_master_log_pos; - DBUG_PRINT("info", ("master_log: %s pos: %lu", master_log, - (ulong) master_pos)); - } - else - sql_print_error("Out of memory while recording slave event"); - mysql_mutex_unlock(&rli->data_lock); - mysql_mutex_unlock(&mi->data_lock); - DBUG_VOID_RETURN; -} -#endif /* !MYSQL_CLIENT */ - - -Slave_log_event::~Slave_log_event() -{ - my_free(mem_pool); -} - - -#ifdef MYSQL_CLIENT -void Slave_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) -{ - Write_on_release_cache cache(&print_event_info->head_cache, file); - - char llbuff[22]; - if (print_event_info->short_form) - return; - print_header(&cache, print_event_info, FALSE); - my_b_printf(&cache, "\n\ -Slave: master_host: '%s' master_port: %d master_log: '%s' master_pos: %s\n", - master_host, master_port, master_log, llstr(master_pos, llbuff)); -} -#endif /* MYSQL_CLIENT */ - - -int Slave_log_event::get_data_size() -{ - return master_host_len + master_log_len + 1 + SL_MASTER_HOST_OFFSET; -} - - -#ifndef MYSQL_CLIENT -bool Slave_log_event::write(IO_CACHE* file) -{ - ulong event_length= get_data_size(); - int8store(mem_pool + SL_MASTER_POS_OFFSET, master_pos); - int2store(mem_pool + SL_MASTER_PORT_OFFSET, master_port); - // log and host are already there - - return (write_header(file, event_length) || - my_b_safe_write(file, (uchar*) mem_pool, event_length)); -} -#endif - - -void Slave_log_event::init_from_mem_pool(int data_size) -{ - master_pos = uint8korr(mem_pool + SL_MASTER_POS_OFFSET); - master_port = uint2korr(mem_pool + SL_MASTER_PORT_OFFSET); - master_host = mem_pool + SL_MASTER_HOST_OFFSET; - master_host_len = (uint) strlen(master_host); - // safety - master_log = master_host + master_host_len + 1; - if (master_log > mem_pool + data_size) + if (what != ENCRYPTED) { - master_host = 0; - return; + print_header(&cache, print_event_info, FALSE); + my_b_printf(&cache, "\n# Unknown event\n"); } - master_log_len = (uint) strlen(master_log); -} - - -/** This code is not used, so has not been updated to be format-tolerant. */ -/* We are using description_event so that slave does not crash on Log_event - constructor */ -Slave_log_event::Slave_log_event(const char* buf, - uint event_len, - const Format_description_log_event* description_event) - :Log_event(buf,description_event),mem_pool(0),master_host(0) -{ - if (event_len < LOG_EVENT_HEADER_LEN) - return; - event_len -= LOG_EVENT_HEADER_LEN; - if (!(mem_pool = (char*) my_malloc(event_len + 1, MYF(MY_WME)))) - return; - memcpy(mem_pool, buf + LOG_EVENT_HEADER_LEN, event_len); - mem_pool[event_len] = 0; - init_from_mem_pool(event_len); -} - - -#ifndef MYSQL_CLIENT -int Slave_log_event::do_apply_event(Relay_log_info const *rli) -{ - if (mysql_bin_log.is_open()) - return mysql_bin_log.write(this); - return 0; + else + my_b_printf(&cache, "# Encrypted event\n"); } -#endif /* !MYSQL_CLIENT */ - +#endif /************************************************************************** Stop_log_event methods @@ -7044,7 +8322,7 @@ void Stop_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) return; print_header(&cache, print_event_info, FALSE); - my_b_printf(&cache, "\tStop\n"); + my_b_write_string(&cache, "\tStop\n"); } #endif /* MYSQL_CLIENT */ @@ -7061,8 +8339,12 @@ void Stop_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) Start_log_event_v3::do_apply_event(), not here. Because if we come here, the master was sane. */ -int Stop_log_event::do_update_pos(Relay_log_info *rli) + +int Stop_log_event::do_update_pos(rpl_group_info *rgi) { + int error= 0; + Relay_log_info *rli= rgi->rli; + DBUG_ENTER("Stop_log_event::do_update_pos"); /* We do not want to update master_log pos because we get a rotate event before stop, so by now group_master_log_name is set to the next log. @@ -7070,14 +8352,16 @@ int Stop_log_event::do_update_pos(Relay_log_info *rli) could give false triggers in MASTER_POS_WAIT() that we have reached the target position when in fact we have not. */ - if (thd->variables.option_bits & OPTION_BEGIN) - rli->inc_event_relay_log_pos(); - else + if (rli->get_flag(Relay_log_info::IN_TRANSACTION)) + rgi->inc_event_relay_log_pos(); + else if (!rgi->is_parallel_exec) { - rli->inc_group_relay_log_pos(0); - flush_relay_log_info(rli); + rpl_global_gtid_slave_state->record_and_update_gtid(thd, rgi); + rli->inc_group_relay_log_pos(0, rgi); + if (flush_relay_log_info(rli)) + error= 1; } - return 0; + DBUG_RETURN(error); } #endif /* !MYSQL_CLIENT */ @@ -7117,13 +8401,13 @@ Create_file_log_event(THD* thd_arg, sql_exchange* ex, Create_file_log_event::write_data_body() */ -bool Create_file_log_event::write_data_body(IO_CACHE* file) +bool Create_file_log_event::write_data_body() { bool res; - if ((res= Load_log_event::write_data_body(file)) || fake_base) + if ((res= Load_log_event::write_data_body()) || fake_base) return res; - return (my_b_safe_write(file, (uchar*) "", 1) || - my_b_safe_write(file, (uchar*) block, block_len)); + return write_data("", 1) || + write_data(block, block_len); } @@ -7131,14 +8415,14 @@ bool Create_file_log_event::write_data_body(IO_CACHE* file) Create_file_log_event::write_data_header() */ -bool Create_file_log_event::write_data_header(IO_CACHE* file) +bool Create_file_log_event::write_data_header() { bool res; uchar buf[CREATE_FILE_HEADER_LEN]; - if ((res= Load_log_event::write_data_header(file)) || fake_base) + if ((res= Load_log_event::write_data_header()) || fake_base) return res; int4store(buf + CF_FILE_ID_OFFSET, file_id); - return my_b_safe_write(file, buf, CREATE_FILE_HEADER_LEN) != 0; + return write_data(buf, CREATE_FILE_HEADER_LEN) != 0; } @@ -7146,11 +8430,11 @@ bool Create_file_log_event::write_data_header(IO_CACHE* file) Create_file_log_event::write_base() */ -bool Create_file_log_event::write_base(IO_CACHE* file) +bool Create_file_log_event::write_base() { bool res; fake_base= 1; // pretend we are Load event - res= write(file); + res= write(); fake_base= 0; return res; } @@ -7244,7 +8528,7 @@ void Create_file_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info That one is for "file_id: etc" below: in mysqlbinlog we want the #, in SHOW BINLOG EVENTS we don't. */ - my_b_printf(&cache, "#"); + my_b_write_byte(&cache, '#'); } my_b_printf(&cache, " file_id: %d block_len: %d\n", file_id, block_len); @@ -7263,7 +8547,7 @@ void Create_file_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Create_file_log_event::pack_info(THD *thd, Protocol *protocol) +void Create_file_log_event::pack_info(Protocol *protocol) { char buf[SAFE_NAME_LEN*2 + 30 + 21*2], *pos; pos= strmov(buf, "db="); @@ -7291,18 +8575,20 @@ void Create_file_log_event::pack_info(THD *thd, Protocol *protocol) */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -int Create_file_log_event::do_apply_event(Relay_log_info const *rli) +int Create_file_log_event::do_apply_event(rpl_group_info *rgi) { - char proc_info[17+FN_REFLEN+10], *fname_buf; + char fname_buf[FN_REFLEN]; char *ext; int fd = -1; IO_CACHE file; + Log_event_writer lew(&file); int error = 1; + Relay_log_info const *rli= rgi->rli; + THD_STAGE_INFO(thd, stage_making_temp_file_create_before_load_data); bzero((char*)&file, sizeof(file)); - fname_buf= strmov(proc_info, "Making temp file "); - ext= slave_load_file_stem(fname_buf, file_id, server_id, ".info"); - thd_proc_info(thd, proc_info); + ext= slave_load_file_stem(fname_buf, file_id, server_id, ".info", + &rli->mi->connection_name); /* old copy may exist already */ mysql_file_delete(key_file_log_event_info, fname_buf, MYF(0)); if ((fd= mysql_file_create(key_file_log_event_info, @@ -7312,7 +8598,7 @@ int Create_file_log_event::do_apply_event(Relay_log_info const *rli) init_io_cache(&file, fd, IO_SIZE, WRITE_CACHE, (my_off_t)0, 0, MYF(MY_WME|MY_NABP))) { - rli->report(ERROR_LEVEL, my_errno, + rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(), "Error in Create_file event: could not open file '%s'", fname_buf); goto err; @@ -7321,10 +8607,11 @@ int Create_file_log_event::do_apply_event(Relay_log_info const *rli) // a trick to avoid allocating another buffer fname= fname_buf; fname_len= (uint) (strmov(ext, ".data") - fname); - if (write_base(&file)) + writer= &lew; + if (write_base()) { strmov(ext, ".info"); // to have it right in the error message - rli->report(ERROR_LEVEL, my_errno, + rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(), "Error in Create_file event: could not write to file '%s'", fname_buf); goto err; @@ -7340,14 +8627,14 @@ int Create_file_log_event::do_apply_event(Relay_log_info const *rli) O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW, MYF(MY_WME))) < 0) { - rli->report(ERROR_LEVEL, my_errno, + rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(), "Error in Create_file event: could not open file '%s'", fname_buf); goto err; } if (mysql_file_write(fd, (uchar*) block, block_len, MYF(MY_WME+MY_NABP))) { - rli->report(ERROR_LEVEL, my_errno, + rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(), "Error in Create_file event: write to '%s' failed", fname_buf); goto err; @@ -7359,7 +8646,6 @@ err: end_io_cache(&file); if (fd >= 0) mysql_file_close(fd, MYF(0)); - thd_proc_info(thd, 0); return error != 0; } #endif /* defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) */ @@ -7413,14 +8699,14 @@ Append_block_log_event::Append_block_log_event(const char* buf, uint len, */ #ifndef MYSQL_CLIENT -bool Append_block_log_event::write(IO_CACHE* file) +bool Append_block_log_event::write() { uchar buf[APPEND_BLOCK_HEADER_LEN]; int4store(buf + AB_FILE_ID_OFFSET, file_id); - return (write_header(file, APPEND_BLOCK_HEADER_LEN + block_len) || - wrapper_my_b_safe_write(file, buf, APPEND_BLOCK_HEADER_LEN) || - wrapper_my_b_safe_write(file, (uchar*) block, block_len) || - write_footer(file)); + return write_header(APPEND_BLOCK_HEADER_LEN + block_len) || + write_data(buf, APPEND_BLOCK_HEADER_LEN) || + write_data(block, block_len) || + write_footer(); } #endif @@ -7449,7 +8735,7 @@ void Append_block_log_event::print(FILE* file, */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Append_block_log_event::pack_info(THD *thd, Protocol *protocol) +void Append_block_log_event::pack_info(Protocol *protocol) { char buf[256]; uint length; @@ -7471,16 +8757,17 @@ int Append_block_log_event::get_create_or_append() const Append_block_log_event::do_apply_event() */ -int Append_block_log_event::do_apply_event(Relay_log_info const *rli) +int Append_block_log_event::do_apply_event(rpl_group_info *rgi) { - char proc_info[17+FN_REFLEN+10], *fname= proc_info+17; + char fname[FN_REFLEN]; int fd; int error = 1; + Relay_log_info const *rli= rgi->rli; DBUG_ENTER("Append_block_log_event::do_apply_event"); - fname= strmov(proc_info, "Making temp file "); - slave_load_file_stem(fname, file_id, server_id, ".data"); - thd_proc_info(thd, proc_info); + THD_STAGE_INFO(thd, stage_making_temp_file_append_before_load_data); + slave_load_file_stem(fname, file_id, server_id, ".data", + &rli->mi->cmp_connection_name); if (get_create_or_append()) { /* @@ -7488,7 +8775,7 @@ int Append_block_log_event::do_apply_event(Relay_log_info const *rli) as the present method does not call mysql_parse(). */ lex_start(thd); - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(); /* old copy may exist already */ mysql_file_delete(key_file_log_event_data, fname, MYF(0)); if ((fd= mysql_file_create(key_file_log_event_data, @@ -7496,7 +8783,7 @@ int Append_block_log_event::do_apply_event(Relay_log_info const *rli) O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW, MYF(MY_WME))) < 0) { - rli->report(ERROR_LEVEL, my_errno, + rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(), "Error in %s event: could not create file '%s'", get_type_str(), fname); goto err; @@ -7507,7 +8794,7 @@ int Append_block_log_event::do_apply_event(Relay_log_info const *rli) O_WRONLY | O_APPEND | O_BINARY | O_NOFOLLOW, MYF(MY_WME))) < 0) { - rli->report(ERROR_LEVEL, my_errno, + rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(), "Error in %s event: could not open file '%s'", get_type_str(), fname); goto err; @@ -7520,7 +8807,7 @@ int Append_block_log_event::do_apply_event(Relay_log_info const *rli) if (mysql_file_write(fd, (uchar*) block, block_len, MYF(MY_WME+MY_NABP))) { - rli->report(ERROR_LEVEL, my_errno, + rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(), "Error in %s event: write to '%s' failed", get_type_str(), fname); goto err; @@ -7530,7 +8817,6 @@ int Append_block_log_event::do_apply_event(Relay_log_info const *rli) err: if (fd >= 0) mysql_file_close(fd, MYF(0)); - thd_proc_info(thd, 0); DBUG_RETURN(error); } #endif @@ -7573,13 +8859,13 @@ Delete_file_log_event::Delete_file_log_event(const char* buf, uint len, */ #ifndef MYSQL_CLIENT -bool Delete_file_log_event::write(IO_CACHE* file) +bool Delete_file_log_event::write() { uchar buf[DELETE_FILE_HEADER_LEN]; int4store(buf + DF_FILE_ID_OFFSET, file_id); - return (write_header(file, sizeof(buf)) || - wrapper_my_b_safe_write(file, buf, sizeof(buf)) || - write_footer(file)); + return write_header(sizeof(buf)) || + write_data(buf, sizeof(buf)) || + write_footer(); } #endif @@ -7606,7 +8892,7 @@ void Delete_file_log_event::print(FILE* file, */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Delete_file_log_event::pack_info(THD *thd, Protocol *protocol) +void Delete_file_log_event::pack_info(Protocol *protocol) { char buf[64]; uint length; @@ -7620,10 +8906,12 @@ void Delete_file_log_event::pack_info(THD *thd, Protocol *protocol) */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -int Delete_file_log_event::do_apply_event(Relay_log_info const *rli) +int Delete_file_log_event::do_apply_event(rpl_group_info *rgi) { char fname[FN_REFLEN+10]; - char *ext= slave_load_file_stem(fname, file_id, server_id, ".data"); + Relay_log_info const *rli= rgi->rli; + char *ext= slave_load_file_stem(fname, file_id, server_id, ".data", + &rli->mi->cmp_connection_name); mysql_file_delete(key_file_log_event_data, fname, MYF(MY_WME)); strmov(ext, ".info"); mysql_file_delete(key_file_log_event_info, fname, MYF(MY_WME)); @@ -7671,13 +8959,13 @@ Execute_load_log_event::Execute_load_log_event(const char* buf, uint len, */ #ifndef MYSQL_CLIENT -bool Execute_load_log_event::write(IO_CACHE* file) +bool Execute_load_log_event::write() { uchar buf[EXEC_LOAD_HEADER_LEN]; int4store(buf + EL_FILE_ID_OFFSET, file_id); - return (write_header(file, sizeof(buf)) || - wrapper_my_b_safe_write(file, buf, sizeof(buf)) || - write_footer(file)); + return write_header(sizeof(buf)) || + write_data(buf, sizeof(buf)) || + write_footer(); } #endif @@ -7705,7 +8993,7 @@ void Execute_load_log_event::print(FILE* file, */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Execute_load_log_event::pack_info(THD *thd, Protocol *protocol) +void Execute_load_log_event::pack_info(Protocol *protocol) { char buf[64]; uint length; @@ -7718,7 +9006,7 @@ void Execute_load_log_event::pack_info(THD *thd, Protocol *protocol) Execute_load_log_event::do_apply_event() */ -int Execute_load_log_event::do_apply_event(Relay_log_info const *rli) +int Execute_load_log_event::do_apply_event(rpl_group_info *rgi) { char fname[FN_REFLEN+10]; char *ext; @@ -7726,15 +9014,17 @@ int Execute_load_log_event::do_apply_event(Relay_log_info const *rli) int error= 1; IO_CACHE file; Load_log_event *lev= 0; + Relay_log_info const *rli= rgi->rli; - ext= slave_load_file_stem(fname, file_id, server_id, ".info"); + ext= slave_load_file_stem(fname, file_id, server_id, ".info", + &rli->mi->cmp_connection_name); if ((fd= mysql_file_open(key_file_log_event_info, fname, O_RDONLY | O_BINARY | O_NOFOLLOW, MYF(MY_WME))) < 0 || init_io_cache(&file, fd, IO_SIZE, READ_CACHE, (my_off_t)0, 0, MYF(MY_WME|MY_NABP))) { - rli->report(ERROR_LEVEL, my_errno, + rli->report(ERROR_LEVEL, my_errno, rgi->gtid_info(), "Error in Exec_load event: could not open file '%s'", fname); goto err; @@ -7746,7 +9036,7 @@ int Execute_load_log_event::do_apply_event(Relay_log_info const *rli) opt_slave_sql_verify_checksum)) || lev->get_type_code() != NEW_LOAD_EVENT) { - rli->report(ERROR_LEVEL, 0, "Error in Exec_load event: " + rli->report(ERROR_LEVEL, 0, rgi->gtid_info(), "Error in Exec_load event: " "file '%s' appears corrupted", fname); goto err; } @@ -7759,8 +9049,7 @@ int Execute_load_log_event::do_apply_event(Relay_log_info const *rli) calls mysql_load()). */ - const_cast<Relay_log_info*>(rli)->future_group_master_log_pos= log_pos; - if (lev->do_apply_event(0,rli,1)) + if (lev->do_apply_event(0,rgi,1)) { /* We want to indicate the name of the file that could not be loaded @@ -7773,7 +9062,7 @@ int Execute_load_log_event::do_apply_event(Relay_log_info const *rli) char *tmp= my_strdup(rli->last_error().message, MYF(MY_WME)); if (tmp) { - rli->report(ERROR_LEVEL, rli->last_error().number, + rli->report(ERROR_LEVEL, rli->last_error().number, rgi->gtid_info(), "%s. Failed executing load from '%s'", tmp, fname); my_free(tmp); } @@ -7841,13 +9130,13 @@ int Begin_load_query_log_event::get_create_or_append() const #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) Log_event::enum_skip_reason -Begin_load_query_log_event::do_shall_skip(Relay_log_info *rli) +Begin_load_query_log_event::do_shall_skip(rpl_group_info *rgi) { /* If the slave skip counter is 1, then we should not start executing on the next event. */ - return continue_group(rli); + return continue_group(rgi); } #endif @@ -7905,14 +9194,14 @@ ulong Execute_load_query_log_event::get_post_header_size_for_derived() #ifndef MYSQL_CLIENT bool -Execute_load_query_log_event::write_post_header_for_derived(IO_CACHE* file) +Execute_load_query_log_event::write_post_header_for_derived() { uchar buf[EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN]; int4store(buf, file_id); int4store(buf + 4, fn_pos_start); int4store(buf + 4 + 4, fn_pos_end); *(buf + 4 + 4 + 4)= (uchar) dup_handling; - return wrapper_my_b_safe_write(file, buf, EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN); + return write_data(buf, EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN); } #endif @@ -7945,12 +9234,12 @@ void Execute_load_query_log_event::print(FILE* file, if (local_fname) { my_b_write(&cache, (uchar*) query, fn_pos_start); - my_b_printf(&cache, " LOCAL INFILE "); + my_b_write_string(&cache, " LOCAL INFILE "); pretty_print_str(&cache, local_fname, strlen(local_fname)); if (dup_handling == LOAD_DUP_REPLACE) - my_b_printf(&cache, " REPLACE"); - my_b_printf(&cache, " INTO"); + my_b_write_string(&cache, " REPLACE"); + my_b_write_string(&cache, " INTO"); my_b_write(&cache, (uchar*) query + fn_pos_end, q_len-fn_pos_end); my_b_printf(&cache, "\n%s\n", print_event_info->delimiter); } @@ -7967,7 +9256,7 @@ void Execute_load_query_log_event::print(FILE* file, #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Execute_load_query_log_event::pack_info(THD *thd, Protocol *protocol) +void Execute_load_query_log_event::pack_info(Protocol *protocol) { char buf_mem[1024]; String buf(buf_mem, sizeof(buf_mem), system_charset_info); @@ -7975,7 +9264,7 @@ void Execute_load_query_log_event::pack_info(THD *thd, Protocol *protocol) if (db && db_len) { if (buf.append(STRING_WITH_LEN("use ")) || - append_identifier(thd, &buf, db, db_len) || + append_identifier(protocol->thd, &buf, db, db_len) || buf.append(STRING_WITH_LEN("; "))) return; } @@ -7989,13 +9278,14 @@ void Execute_load_query_log_event::pack_info(THD *thd, Protocol *protocol) int -Execute_load_query_log_event::do_apply_event(Relay_log_info const *rli) +Execute_load_query_log_event::do_apply_event(rpl_group_info *rgi) { char *p; char *buf; char *fname; char *fname_end; int error; + Relay_log_info const *rli= rgi->rli; buf= (char*) my_malloc(q_len + 1 - (fn_pos_end - fn_pos_start) + (FN_REFLEN + 10) + 10 + 8 + 5, MYF(MY_WME)); @@ -8005,8 +9295,8 @@ Execute_load_query_log_event::do_apply_event(Relay_log_info const *rli) /* Replace filename and LOCAL keyword in query before executing it */ if (buf == NULL) { - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), "Not enough memory"); + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(), + ER_THD(rgi->thd, ER_SLAVE_FATAL_ERROR), "Not enough memory"); return 1; } @@ -8014,7 +9304,8 @@ Execute_load_query_log_event::do_apply_event(Relay_log_info const *rli) memcpy(p, query, fn_pos_start); p+= fn_pos_start; fname= (p= strmake(p, STRING_WITH_LEN(" INFILE \'"))); - p= slave_load_file_stem(p, file_id, server_id, ".data"); + p= slave_load_file_stem(p, file_id, server_id, ".data", + &rli->mi->cmp_connection_name); fname_end= p= strend(p); // Safer than p=p+5 *(p++)='\''; switch (dup_handling) { @@ -8031,7 +9322,7 @@ Execute_load_query_log_event::do_apply_event(Relay_log_info const *rli) p= strmake(p, STRING_WITH_LEN(" INTO ")); p= strmake(p, query+fn_pos_end, q_len-fn_pos_end); - error= Query_log_event::do_apply_event(rli, buf, p-buf); + error= Query_log_event::do_apply_event(rgi, buf, p-buf); /* Forging file name for deletion in same buffer */ *fname_end= 0; @@ -8054,40 +9345,6 @@ Execute_load_query_log_event::do_apply_event(Relay_log_info const *rli) **************************************************************************/ /* - sql_ex_info::write_data() -*/ - -bool sql_ex_info::write_data(IO_CACHE* file) -{ - if (new_format()) - { - return (write_str(file, field_term, (uint) field_term_len) || - write_str(file, enclosed, (uint) enclosed_len) || - write_str(file, line_term, (uint) line_term_len) || - write_str(file, line_start, (uint) line_start_len) || - write_str(file, escaped, (uint) escaped_len) || - my_b_safe_write(file,(uchar*) &opt_flags,1)); - } - else - { - /** - @todo This is sensitive to field padding. We should write a - char[7], not an old_sql_ex. /sven - */ - old_sql_ex old_ex; - old_ex.field_term= *field_term; - old_ex.enclosed= *enclosed; - old_ex.line_term= *line_term; - old_ex.line_start= *line_start; - old_ex.escaped= *escaped; - old_ex.opt_flags= opt_flags; - old_ex.empty_flags=empty_flags; - return my_b_safe_write(file, (uchar*) &old_ex, sizeof(old_ex)) != 0; - } -} - - -/* sql_ex_info::init() */ @@ -8137,23 +9394,68 @@ const char *sql_ex_info::init(const char *buf, const char *buf_end, return buf; } +#ifndef MYSQL_CLIENT +/* + write_str() +*/ + +static bool write_str(Log_event_writer *writer, const char *str, uint length) +{ + uchar tmp[1]; + tmp[0]= (uchar) length; + return (writer->write_data(tmp, sizeof(tmp)) || + writer->write_data((uchar*) str, length)); +} + +/* + sql_ex_info::write_data() +*/ + +bool sql_ex_info::write_data(Log_event_writer *writer) +{ + if (new_format()) + { + return write_str(writer, field_term, field_term_len) || + write_str(writer, enclosed, enclosed_len) || + write_str(writer, line_term, line_term_len) || + write_str(writer, line_start, line_start_len) || + write_str(writer, escaped, escaped_len) || + writer->write_data((uchar*) &opt_flags, 1); + } + else + { + uchar old_ex[7]; + old_ex[0]= *field_term; + old_ex[1]= *enclosed; + old_ex[2]= *line_term; + old_ex[3]= *line_start; + old_ex[4]= *escaped; + old_ex[5]= opt_flags; + old_ex[6]= empty_flags; + return writer->write_data(old_ex, sizeof(old_ex)); + } +} + + /************************************************************************** Rows_log_event member functions **************************************************************************/ -#ifndef MYSQL_CLIENT Rows_log_event::Rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid, - MY_BITMAP const *cols, bool is_transactional) + MY_BITMAP const *cols, bool is_transactional, + Log_event_type event_type) : Log_event(thd_arg, 0, is_transactional), m_row_count(0), m_table(tbl_arg), m_table_id(tid), m_width(tbl_arg ? tbl_arg->s->fields : 1), - m_rows_buf(0), m_rows_cur(0), m_rows_end(0), m_flags(0) + m_rows_buf(0), m_rows_cur(0), m_rows_end(0), m_flags(0), + m_type(event_type), m_extra_row_data(0) #ifdef HAVE_REPLICATION , m_curr_row(NULL), m_curr_row_end(NULL), - m_key(NULL), m_key_info(NULL), m_key_nr(0) + m_key(NULL), m_key_info(NULL), m_key_nr(0), + master_had_triggers(0) #endif { /* @@ -8169,8 +9471,8 @@ Rows_log_event::Rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid, set_flags(NO_FOREIGN_KEY_CHECKS_F); if (thd_arg->variables.option_bits & OPTION_RELAXED_UNIQUE_CHECKS) set_flags(RELAXED_UNIQUE_CHECKS_F); - /* if bitmap_init fails, caught in is_valid() */ - if (likely(!bitmap_init(&m_cols, + /* if my_bitmap_init fails, caught in is_valid() */ + if (likely(!my_bitmap_init(&m_cols, m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL, m_width, false))) @@ -8184,14 +9486,13 @@ Rows_log_event::Rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid, } else { - // Needed because bitmap_init() does not set it to null on failure + // Needed because my_bitmap_init() does not set it to null on failure m_cols.bitmap= 0; } } #endif Rows_log_event::Rows_log_event(const char *buf, uint event_len, - Log_event_type event_type, const Format_description_log_event *description_event) : Log_event(buf, description_event), @@ -8199,14 +9500,19 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len, #ifndef MYSQL_CLIENT m_table(NULL), #endif - m_table_id(0), m_rows_buf(0), m_rows_cur(0), m_rows_end(0) + m_table_id(0), m_rows_buf(0), m_rows_cur(0), m_rows_end(0), + m_extra_row_data(0) #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) , m_curr_row(NULL), m_curr_row_end(NULL), - m_key(NULL), m_key_info(NULL), m_key_nr(0) + m_key(NULL), m_key_info(NULL), m_key_nr(0), + master_had_triggers(0) #endif { DBUG_ENTER("Rows_log_event::Rows_log_event(const char*,...)"); uint8 const common_header_len= description_event->common_header_len; + Log_event_type event_type= (Log_event_type) buf[EVENT_TYPE_OFFSET]; + m_type= event_type; + uint8 const post_header_len= description_event->post_header_len[event_type-1]; DBUG_PRINT("enter",("event_len: %u common_header_len: %d " @@ -8229,25 +9535,69 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len, } m_flags= uint2korr(post_start); + post_start+= 2; + + uint16 var_header_len= 0; + if (post_header_len == ROWS_HEADER_LEN_V2) + { + /* + Have variable length header, check length, + which includes length bytes + */ + var_header_len= uint2korr(post_start); + assert(var_header_len >= 2); + var_header_len-= 2; + + /* Iterate over var-len header, extracting 'chunks' */ + const char* start= post_start + 2; + const char* end= start + var_header_len; + for (const char* pos= start; pos < end;) + { + switch(*pos++) + { + case RW_V_EXTRAINFO_TAG: + { + /* Have an 'extra info' section, read it in */ + assert((end - pos) >= EXTRA_ROW_INFO_HDR_BYTES); + uint8 infoLen= pos[EXTRA_ROW_INFO_LEN_OFFSET]; + assert((end - pos) >= infoLen); + /* Just store/use the first tag of this type, skip others */ + if (likely(!m_extra_row_data)) + { + m_extra_row_data= (uchar*) my_malloc(infoLen, + MYF(MY_WME)); + if (likely(m_extra_row_data != NULL)) + { + memcpy(m_extra_row_data, pos, infoLen); + } + } + pos+= infoLen; + break; + } + default: + /* Unknown code, we will not understand anything further here */ + pos= end; /* Break loop */ + } + } + } uchar const *const var_start= - (const uchar *)buf + common_header_len + post_header_len; + (const uchar *)buf + common_header_len + post_header_len + var_header_len; uchar const *const ptr_width= var_start; uchar *ptr_after_width= (uchar*) ptr_width; DBUG_PRINT("debug", ("Reading from %p", ptr_after_width)); m_width = net_field_length(&ptr_after_width); DBUG_PRINT("debug", ("m_width=%lu", m_width)); + /* Avoid reading out of buffer */ - if (static_cast<unsigned int>((ptr_after_width + - (m_width + 7) / 8) - - (uchar*)buf) > event_len) + if (ptr_after_width + (m_width + 7) / 8 > (uchar*)buf + event_len) { m_cols.bitmap= NULL; DBUG_VOID_RETURN; } - /* if bitmap_init fails, catched in is_valid() */ - if (likely(!bitmap_init(&m_cols, + /* if my_bitmap_init fails, catched in is_valid() */ + if (likely(!my_bitmap_init(&m_cols, m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL, m_width, false))) @@ -8260,19 +9610,20 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len, } else { - // Needed because bitmap_init() does not set it to null on failure + // Needed because my_bitmap_init() does not set it to null on failure m_cols.bitmap= NULL; DBUG_VOID_RETURN; } m_cols_ai.bitmap= m_cols.bitmap; /* See explanation in is_valid() */ - if (event_type == UPDATE_ROWS_EVENT) + if ((event_type == UPDATE_ROWS_EVENT) || + (event_type == UPDATE_ROWS_EVENT_V1)) { DBUG_PRINT("debug", ("Reading from %p", ptr_after_width)); - /* if bitmap_init fails, caught in is_valid() */ - if (likely(!bitmap_init(&m_cols_ai, + /* if my_bitmap_init fails, caught in is_valid() */ + if (likely(!my_bitmap_init(&m_cols_ai, m_width <= sizeof(m_bitbuf_ai)*8 ? m_bitbuf_ai : NULL, m_width, false))) @@ -8286,7 +9637,7 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len, } else { - // Needed because bitmap_init() does not set it to null on failure + // Needed because my_bitmap_init() does not set it to null on failure m_cols_ai.bitmap= 0; DBUG_VOID_RETURN; } @@ -8322,27 +9673,41 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len, Rows_log_event::~Rows_log_event() { if (m_cols.bitmap == m_bitbuf) // no my_malloc happened - m_cols.bitmap= 0; // so no my_free in bitmap_free - bitmap_free(&m_cols); // To pair with bitmap_init(). + m_cols.bitmap= 0; // so no my_free in my_bitmap_free + my_bitmap_free(&m_cols); // To pair with my_bitmap_init(). my_free(m_rows_buf); + my_free(m_extra_row_data); } int Rows_log_event::get_data_size() { - int const type_code= get_type_code(); + int const general_type_code= get_general_type_code(); - uchar buf[sizeof(m_width) + 1]; + uchar buf[MAX_INT_WIDTH]; uchar *end= net_store_length(buf, m_width); DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master", return 6 + no_bytes_in_map(&m_cols) + (end - buf) + - (type_code == UPDATE_ROWS_EVENT ? no_bytes_in_map(&m_cols_ai) : 0) + + (general_type_code == UPDATE_ROWS_EVENT ? no_bytes_in_map(&m_cols_ai) : 0) + (m_rows_cur - m_rows_buf);); - int data_size= ROWS_HEADER_LEN; + + int data_size= 0; + bool is_v2_event= get_type_code() > DELETE_ROWS_EVENT_V1; + if (is_v2_event) + { + data_size= ROWS_HEADER_LEN_V2 + + (m_extra_row_data ? + RW_V_TAG_LEN + m_extra_row_data[EXTRA_ROW_INFO_LEN_OFFSET]: + 0); + } + else + { + data_size= ROWS_HEADER_LEN_V1; + } data_size+= no_bytes_in_map(&m_cols); data_size+= (uint) (end - buf); - if (type_code == UPDATE_ROWS_EVENT) + if (general_type_code == UPDATE_ROWS_EVENT) data_size+= no_bytes_in_map(&m_cols_ai); data_size+= (uint) (m_rows_cur - m_rows_buf); @@ -8361,12 +9726,24 @@ int Rows_log_event::do_add_row_data(uchar *row_data, size_t length) DBUG_ENTER("Rows_log_event::do_add_row_data"); DBUG_PRINT("enter", ("row_data: 0x%lx length: %lu", (ulong) row_data, (ulong) length)); + + /* + If length is zero, there is nothing to write, so we just + return. Note that this is not an optimization, since calling + realloc() with size 0 means free(). + */ + if (length == 0) + { + m_row_count++; + DBUG_RETURN(0); + } + /* Don't print debug messages when running valgrind since they can trigger false warnings. */ #ifndef HAVE_valgrind - DBUG_DUMP("row_data", row_data, min(length, 32)); + DBUG_DUMP("row_data", row_data, MY_MIN(length, 32)); #endif DBUG_ASSERT(m_rows_buf <= m_rows_cur); @@ -8431,9 +9808,28 @@ int Rows_log_event::do_add_row_data(uchar *row_data, size_t length) } #endif -#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -int Rows_log_event::do_apply_event(Relay_log_info const *rli) +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) + +/** + Restores empty table list as it was before trigger processing. + + @note We have a lot of ASSERTS that check the lists when we close tables. + There was the same problem with MERGE MYISAM tables and so here we try to + go the same way. +*/ +static void restore_empty_query_table_list(LEX *lex) { + if (lex->first_not_own_table()) + (*lex->first_not_own_table()->prev_global)= NULL; + lex->query_tables= NULL; + lex->query_tables_last= &lex->query_tables; +} + + +int Rows_log_event::do_apply_event(rpl_group_info *rgi) +{ + Relay_log_info const *rli= rgi->rli; + TABLE* table; DBUG_ENTER("Rows_log_event::do_apply_event(Relay_log_info*)"); int error= 0; /* @@ -8450,7 +9846,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) */ DBUG_ASSERT(get_flags(STMT_END_F)); - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); + rgi->slave_close_thread_tables(thd); thd->clear_error(); DBUG_RETURN(0); } @@ -8460,7 +9856,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) do_apply_event(). We still check here to prevent future coding errors. */ - DBUG_ASSERT(rli->sql_thd == thd); + DBUG_ASSERT(rgi->thd == thd); /* If there is no locks taken, this is the first binrow event seen @@ -8474,19 +9870,21 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) Lock_tables() reads the contents of thd->lex, so they must be initialized. - We also call the mysql_reset_thd_for_next_command(), since this + We also call the THD::reset_for_next_command(), since this is the logical start of the next "statement". Note that this call might reset the value of current_stmt_binlog_format, so we need to do any changes to that value after this function. */ + delete_explain_query(thd->lex); lex_start(thd); - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(); /* The current statement is just about to begin and has not yet modified anything. Note, all.modified is reset - by mysql_reset_thd_for_next_command. + by THD::reset_for_next_command(). */ thd->transaction.stmt.modified_non_trans_table= FALSE; + thd->transaction.stmt.m_unsafe_rollback_flags&= ~THD_TRANS::DID_WAIT; /* This is a row injection, so we flag the "statement" as such. Note that this code is called both when the slave does row @@ -8512,10 +9910,45 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) /* A small test to verify that objects have consistent types */ DBUG_ASSERT(sizeof(thd->variables.option_bits) == sizeof(OPTION_RELAXED_UNIQUE_CHECKS)); - if (open_and_lock_tables(thd, rli->tables_to_lock, FALSE, 0)) + if (slave_run_triggers_for_rbr) { - uint actual_error= thd->stmt_da->sql_errno(); - if (thd->is_slave_error || thd->is_fatal_error) + LEX *lex= thd->lex; + uint8 new_trg_event_map= get_trg_event_map(); + + /* + Trigger's procedures work with global table list. So we have to add + rgi->tables_to_lock content there to get trigger's in the list. + + Then restore_empty_query_table_list() restore the list as it was + */ + DBUG_ASSERT(lex->query_tables == NULL); + if ((lex->query_tables= rgi->tables_to_lock)) + rgi->tables_to_lock->prev_global= &lex->query_tables; + + for (TABLE_LIST *tables= rgi->tables_to_lock; tables; + tables= tables->next_global) + { + tables->trg_event_map= new_trg_event_map; + lex->query_tables_last= &tables->next_global; + } + } + if (open_and_lock_tables(thd, rgi->tables_to_lock, FALSE, 0)) + { + uint actual_error= thd->get_stmt_da()->sql_errno(); +#ifdef WITH_WSREP + if (WSREP(thd)) + { + WSREP_WARN("BF applier failed to open_and_lock_tables: %u, fatal: %d " + "wsrep = (exec_mode: %d conflict_state: %d seqno: %lld)", + thd->get_stmt_da()->sql_errno(), + thd->is_fatal_error, + thd->wsrep_exec_mode, + thd->wsrep_conflict_state, + (long long)wsrep_thd_trx_seqno(thd)); + } +#endif + if ((thd->is_slave_error || thd->is_fatal_error) && + !is_parallel_retry_error(rgi, actual_error)) { /* Error reporting borrowed from Query_log_event with many excessive @@ -8523,14 +9956,15 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) We should not honour --slave-skip-errors at this point as we are having severe errors which should not be skiped. */ - rli->report(ERROR_LEVEL, actual_error, + rli->report(ERROR_LEVEL, actual_error, rgi->gtid_info(), "Error executing row event: '%s'", - (actual_error ? thd->stmt_da->message() : + (actual_error ? thd->get_stmt_da()->message() : "unexpected success or fatal error")); thd->is_slave_error= 1; } - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); - DBUG_RETURN(actual_error); + /* remove trigger's tables */ + error= actual_error; + goto err; } /* @@ -8540,7 +9974,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { DBUG_PRINT("debug", ("Checking compability of tables to lock - tables_to_lock: %p", - rli->tables_to_lock)); + rgi->tables_to_lock)); /** When using RBR and MyISAM MERGE tables the base tables that make @@ -8554,8 +9988,8 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) NOTE: The base tables are added here are removed when close_thread_tables is called. */ - TABLE_LIST *table_list_ptr= rli->tables_to_lock; - for (uint i=0 ; table_list_ptr && (i < rli->tables_to_lock_count); + TABLE_LIST *table_list_ptr= rgi->tables_to_lock; + for (uint i=0 ; table_list_ptr && (i < rgi->tables_to_lock_count); table_list_ptr= table_list_ptr->next_global, i++) { /* @@ -8587,8 +10021,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) RPL_TABLE_LIST *ptr= static_cast<RPL_TABLE_LIST*>(table_list_ptr); DBUG_ASSERT(ptr->m_tabledef_valid); TABLE *conv_table; - if (!ptr->m_tabledef.compatible_with(thd, const_cast<Relay_log_info*>(rli), - ptr->table, &conv_table)) + if (!ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table)) { DBUG_PRINT("debug", ("Table: %s.%s is not compatible with master", ptr->table->s->db.str, @@ -8598,8 +10031,9 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) having severe errors which should not be skiped. */ thd->is_slave_error= 1; - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); - DBUG_RETURN(ERR_BAD_TABLE_DEF); + /* remove trigger's tables */ + error= ERR_BAD_TABLE_DEF; + goto err; } DBUG_PRINT("debug", ("Table: %s.%s is compatible with master" " - conv_table: %p", @@ -8623,8 +10057,8 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) Rows_log_event, we can invalidate the query cache for the associated table. */ - TABLE_LIST *ptr= rli->tables_to_lock; - for (uint i=0 ; ptr && (i < rli->tables_to_lock_count); ptr= ptr->next_global, i++) + TABLE_LIST *ptr= rgi->tables_to_lock; + for (uint i=0 ; ptr && (i < rgi->tables_to_lock_count); ptr= ptr->next_global, i++) { /* Please see comment in above 'for' loop to know the reason @@ -8632,22 +10066,45 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) */ if (ptr->parent_l) continue; - const_cast<Relay_log_info*>(rli)->m_table_map.set_table(ptr->table_id, ptr->table); + rgi->m_table_map.set_table(ptr->table_id, ptr->table); + /* + Following is passing flag about triggers on the server. The problem was + to pass it between table map event and row event. I do it via extended + TABLE_LIST (RPL_TABLE_LIST) but row event uses only TABLE so I need to + find somehow the corresponding TABLE_LIST. + */ + if (m_table_id == ptr->table_id) + { + ptr->table->master_had_triggers= + ((RPL_TABLE_LIST*)ptr)->master_had_triggers; + } } #ifdef HAVE_QUERY_CACHE - query_cache.invalidate_locked_for_write(thd, rli->tables_to_lock); +#ifdef WITH_WSREP + /* + Moved invalidation right before the call to rows_event_stmt_cleanup(), + to avoid query cache being polluted with stale entries. + */ + if (! (WSREP(thd) && (thd->wsrep_exec_mode == REPL_RECV))) + { +#endif /* WITH_WSREP */ + query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock); +#ifdef WITH_WSREP + } +#endif /* WITH_WSREP */ #endif } - TABLE* - table= - m_table= const_cast<Relay_log_info*>(rli)->m_table_map.get_table(m_table_id); - - DBUG_PRINT("debug", ("m_table: 0x%lx, m_table_id: %lu", (ulong) m_table, m_table_id)); + table= m_table= rgi->m_table_map.get_table(m_table_id); + DBUG_PRINT("debug", ("m_table: 0x%lx, m_table_id: %lu%s", + (ulong) m_table, m_table_id, + table && master_had_triggers ? + " (master had triggers)" : "")); if (table) { + master_had_triggers= table->master_had_triggers; bool transactional_table= table->file->has_transactions(); /* table == NULL means that this table should not be replicated @@ -8665,18 +10122,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) */ thd->set_time(when, when_sec_part); - /* - Now we are in a statement and will stay in a statement until we - see a STMT_END_F. - - We set this flag here, before actually applying any rows, in - case the SQL thread is stopped and we need to detect that we're - inside a statement and halting abruptly might cause problems - when restarting. - */ - const_cast<Relay_log_info*>(rli)->set_flag(Relay_log_info::IN_STMT); - - if ( m_width == table->s->fields && bitmap_is_set_all(&m_cols)) + if (m_width == table->s->fields && bitmap_is_set_all(&m_cols)) set_flags(COMPLETE_ROWS_F); /* @@ -8689,12 +10135,20 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) longer if slave has extra columns. */ - DBUG_PRINT_BITSET("debug", "Setting table's write_set from: %s", &m_cols); + DBUG_PRINT_BITSET("debug", "Setting table's read_set from: %s", &m_cols); bitmap_set_all(table->read_set); + if (get_general_type_code() == DELETE_ROWS_EVENT || + get_general_type_code() == UPDATE_ROWS_EVENT) + bitmap_intersect(table->read_set,&m_cols); + bitmap_set_all(table->write_set); - if (!get_flags(COMPLETE_ROWS_F)) - bitmap_intersect(table->write_set,&m_cols); + table->rpl_write_set= table->write_set; + + /* WRITE ROWS EVENTS store the bitmap in m_cols instead of m_cols_ai */ + MY_BITMAP *after_image= ((get_general_type_code() == UPDATE_ROWS_EVENT) ? + &m_cols_ai : &m_cols); + bitmap_intersect(table->write_set, after_image); this->slave_exec_mode= slave_exec_mode_options; // fix the mode @@ -8719,16 +10173,17 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) set the initial time of this ROWS statement if it was not done before in some other ROWS event. */ - const_cast<Relay_log_info*>(rli)->set_row_stmt_start_timestamp(); + rgi->set_row_stmt_start_timestamp(); - while (error == 0 && m_curr_row < m_rows_end) + THD_STAGE_INFO(thd, stage_executing); + do { /* in_use can have been set to NULL in close_tables_for_reopen */ THD* old_thd= table->in_use; if (!table->in_use) table->in_use= thd; - error= do_exec_row(rli); + error= do_exec_row(rgi); if (error) DBUG_PRINT("info", ("error: %s", HA_ERR(error))); @@ -8747,10 +10202,10 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) if (idempotent_error || ignored_error) { if (global_system_variables.log_warnings) - slave_rows_error_report(WARNING_LEVEL, error, rli, thd, table, + slave_rows_error_report(WARNING_LEVEL, error, rgi, thd, table, get_type_str(), RPL_LOG_NAME, (ulong) log_pos); - clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); + thd->clear_error(1); error= 0; if (idempotent_error == 0) break; @@ -8768,19 +10223,15 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) (ulong) m_curr_row, (ulong) m_curr_row_end, (ulong) m_rows_end)); if (!m_curr_row_end && !error) - error= unpack_current_row(rli); - - // at this moment m_curr_row_end should be set - DBUG_ASSERT(error || m_curr_row_end != NULL); - DBUG_ASSERT(error || m_curr_row < m_curr_row_end); - DBUG_ASSERT(error || m_curr_row_end <= m_rows_end); - + error= unpack_current_row(rgi); + m_curr_row= m_curr_row_end; if (error == 0 && !transactional_table) thd->transaction.all.modified_non_trans_table= thd->transaction.stmt.modified_non_trans_table= TRUE; } // row processing loop + while (error == 0 && (m_curr_row != m_rows_end)); /* Restore the sql_mode after the rows event is processed. @@ -8803,10 +10254,10 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { if (global_system_variables.log_warnings) - slave_rows_error_report(WARNING_LEVEL, error, rli, thd, table, + slave_rows_error_report(WARNING_LEVEL, error, rgi, thd, table, get_type_str(), RPL_LOG_NAME, (ulong) log_pos); - clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); + thd->clear_error(1); error= 0; } } // if (table) @@ -8814,7 +10265,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) if (error) { - slave_rows_error_report(ERROR_LEVEL, error, rli, thd, table, + slave_rows_error_report(ERROR_LEVEL, error, rgi, thd, table, get_type_str(), RPL_LOG_NAME, (ulong) log_pos); /* @@ -8826,30 +10277,48 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) */ thd->reset_current_stmt_binlog_format_row(); thd->is_slave_error= 1; - DBUG_RETURN(error); + /* remove trigger's tables */ + goto err; } - if (get_flags(STMT_END_F) && (error= rows_event_stmt_cleanup(rli, thd))) + /* remove trigger's tables */ + if (slave_run_triggers_for_rbr) + restore_empty_query_table_list(thd->lex); + +#if defined(WITH_WSREP) && defined(HAVE_QUERY_CACHE) + if (WSREP(thd) && thd->wsrep_exec_mode == REPL_RECV) + { + query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock); + } +#endif /* WITH_WSREP && HAVE_QUERY_CACHE */ + + if (get_flags(STMT_END_F) && (error= rows_event_stmt_cleanup(rgi, thd))) slave_rows_error_report(ERROR_LEVEL, thd->is_error() ? 0 : error, - rli, thd, table, + rgi, thd, table, get_type_str(), RPL_LOG_NAME, (ulong) log_pos); DBUG_RETURN(error); + +err: + if (slave_run_triggers_for_rbr) + restore_empty_query_table_list(thd->lex); + rgi->slave_close_thread_tables(thd); + DBUG_RETURN(error); } Log_event::enum_skip_reason -Rows_log_event::do_shall_skip(Relay_log_info *rli) +Rows_log_event::do_shall_skip(rpl_group_info *rgi) { /* If the slave skip counter is 1 and this event does not end a statement, then we should not start executing on the next event. Otherwise, we defer the decision to the normal skipping logic. */ - if (rli->slave_skip_counter == 1 && !get_flags(STMT_END_F)) + if (rgi->rli->slave_skip_counter == 1 && !get_flags(STMT_END_F)) return Log_event::EVENT_SKIP_IGNORE; else - return Log_event::do_shall_skip(rli); + return Log_event::do_shall_skip(rgi); } /** @@ -8863,9 +10332,11 @@ Rows_log_event::do_shall_skip(Relay_log_info *rli) @retval non-zero Error at the commit. */ -static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd) +static int rows_event_stmt_cleanup(rpl_group_info *rgi, THD * thd) { int error; + DBUG_ENTER("rows_event_stmt_cleanup"); + { /* This is the end of a statement or transaction, so close (and @@ -8920,9 +10391,19 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd) */ thd->reset_current_stmt_binlog_format_row(); - const_cast<Relay_log_info*>(rli)->cleanup_context(thd, 0); + /* + Reset modified_non_trans_table that we have set in + rows_log_event::do_apply_event() + */ + if (!thd->in_multi_stmt_transaction_mode()) + { + thd->transaction.all.modified_non_trans_table= 0; + thd->transaction.all.m_unsafe_rollback_flags&= ~THD_TRANS::DID_WAIT; + } + + rgi->cleanup_context(thd, 0); } - return error; + DBUG_RETURN(error); } /** @@ -8936,10 +10417,11 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd) @retval non-zero Error in the statement commit */ int -Rows_log_event::do_update_pos(Relay_log_info *rli) +Rows_log_event::do_update_pos(rpl_group_info *rgi) { - DBUG_ENTER("Rows_log_event::do_update_pos"); + Relay_log_info *rli= rgi->rli; int error= 0; + DBUG_ENTER("Rows_log_event::do_update_pos"); DBUG_PRINT("info", ("flags: %s", get_flags(STMT_END_F) ? "STMT_END_F " : "")); @@ -8951,7 +10433,7 @@ Rows_log_event::do_update_pos(Relay_log_info *rli) Step the group log position if we are not in a transaction, otherwise increase the event log position. */ - rli->stmt_done(log_pos, when); + error= rli->stmt_done(log_pos, thd, rgi); /* Clear any errors in thd->net.last_err*. It is not known if this is needed or not. It is believed that any errors that may exist in @@ -8962,7 +10444,7 @@ Rows_log_event::do_update_pos(Relay_log_info *rli) } else { - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); } DBUG_RETURN(error); @@ -8971,51 +10453,50 @@ Rows_log_event::do_update_pos(Relay_log_info *rli) #endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */ #ifndef MYSQL_CLIENT -bool Rows_log_event::write_data_header(IO_CACHE *file) +bool Rows_log_event::write_data_header() { - uchar buf[ROWS_HEADER_LEN]; // No need to init the buffer + uchar buf[ROWS_HEADER_LEN_V2]; // No need to init the buffer DBUG_ASSERT(m_table_id != ~0UL); DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master", { int4store(buf + 0, m_table_id); int2store(buf + 4, m_flags); - return (wrapper_my_b_safe_write(file, buf, 6)); + return (write_data(buf, 6)); }); int6store(buf + RW_MAPID_OFFSET, (ulonglong)m_table_id); int2store(buf + RW_FLAGS_OFFSET, m_flags); - return (wrapper_my_b_safe_write(file, buf, ROWS_HEADER_LEN)); + return write_data(buf, ROWS_HEADER_LEN); } -bool Rows_log_event::write_data_body(IO_CACHE*file) +bool Rows_log_event::write_data_body() { /* Note that this should be the number of *bits*, not the number of bytes. */ - uchar sbuf[sizeof(m_width) + 1]; + uchar sbuf[MAX_INT_WIDTH]; my_ptrdiff_t const data_size= m_rows_cur - m_rows_buf; bool res= false; uchar *const sbuf_end= net_store_length(sbuf, (size_t) m_width); DBUG_ASSERT(static_cast<size_t>(sbuf_end - sbuf) <= sizeof(sbuf)); DBUG_DUMP("m_width", sbuf, (size_t) (sbuf_end - sbuf)); - res= res || wrapper_my_b_safe_write(file, sbuf, (size_t) (sbuf_end - sbuf)); + res= res || write_data(sbuf, (size_t) (sbuf_end - sbuf)); DBUG_DUMP("m_cols", (uchar*) m_cols.bitmap, no_bytes_in_map(&m_cols)); - res= res || wrapper_my_b_safe_write(file, (uchar*) m_cols.bitmap, - no_bytes_in_map(&m_cols)); + res= res || write_data((uchar*)m_cols.bitmap, no_bytes_in_map(&m_cols)); /* TODO[refactor write]: Remove the "down cast" here (and elsewhere). */ - if (get_type_code() == UPDATE_ROWS_EVENT) + if (get_general_type_code() == UPDATE_ROWS_EVENT) { DBUG_DUMP("m_cols_ai", (uchar*) m_cols_ai.bitmap, no_bytes_in_map(&m_cols_ai)); - res= res || wrapper_my_b_safe_write(file, (uchar*) m_cols_ai.bitmap, - no_bytes_in_map(&m_cols_ai)); + res= res || write_data((uchar*)m_cols_ai.bitmap, + no_bytes_in_map(&m_cols_ai)); } DBUG_DUMP("rows", m_rows_buf, data_size); - res= res || wrapper_my_b_safe_write(file, m_rows_buf, (size_t) data_size); + res= res || write_data(m_rows_buf, (size_t) data_size); return res; @@ -9023,7 +10504,7 @@ bool Rows_log_event::write_data_body(IO_CACHE*file) #endif #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Rows_log_event::pack_info(THD *thd, Protocol *protocol) +void Rows_log_event::pack_info(Protocol *protocol) { char buf[256]; char const *const flagstr= @@ -9035,12 +10516,128 @@ void Rows_log_event::pack_info(THD *thd, Protocol *protocol) #endif #ifdef MYSQL_CLIENT +/** + Print an event "body" cache to @c file possibly in two fragments. + Each fragement is optionally per @c do_wrap to produce an SQL statement. + + @param file a file to print to + @param body the "body" IO_CACHE of event + @param do_wrap whether to wrap base64-encoded strings with + SQL cover. + @param delimiter delimiter string + + The function signals on any error through setting @c body->error to -1. +*/ +void copy_cache_to_file_wrapped(FILE *file, + IO_CACHE *body, + bool do_wrap, + const char *delimiter) +{ + const char str_binlog[]= "\nBINLOG '\n"; + const char fmt_delim[]= "'%s\n"; + const char fmt_n_delim[]= "\n'%s"; + const my_off_t cache_size= my_b_tell(body); + + if (reinit_io_cache(body, READ_CACHE, 0L, FALSE, FALSE)) + { + body->error= -1; + goto end; + } + + if (!do_wrap) + { + my_b_copy_to_file(body, file, SIZE_T_MAX); + } + else if (4 + sizeof(str_binlog) + cache_size + sizeof(fmt_delim) > + opt_binlog_rows_event_max_encoded_size) + { + /* + 2 fragments can always represent near 1GB row-based + base64-encoded event as two strings each of size less than + max(max_allowed_packet). Greater number of fragments does not + save from potential need to tweak (increase) @@max_allowed_packet + before to process the fragments. So 2 is safe and enough. + + Split the big query when its packet size's estimation exceeds a + limit. The estimate includes the maximum packet header + contribution of non-compressed packet. + */ + const char fmt_frag[]= "\nSET @binlog_fragment_%d ='\n"; + + my_fprintf(file, fmt_frag, 0); + if (my_b_copy_to_file(body, file, cache_size/2 + 1)) + { + body->error= -1; + goto end; + } + my_fprintf(file, fmt_n_delim, delimiter); + + my_fprintf(file, fmt_frag, 1); + if (my_b_copy_to_file(body, file, SIZE_T_MAX)) + { + body->error= -1; + goto end; + } + my_fprintf(file, fmt_delim, delimiter); + + my_fprintf(file, "BINLOG @binlog_fragment_0, @binlog_fragment_1%s\n", + delimiter); + } + else + { + my_fprintf(file, str_binlog); + if (my_b_copy_to_file(body, file, SIZE_T_MAX)) + { + body->error= -1; + goto end; + } + my_fprintf(file, fmt_delim, delimiter); + } + reinit_io_cache(body, WRITE_CACHE, 0, FALSE, TRUE); + +end: + return; +} + +/** + The function invokes base64 encoder to run on the current + event string and store the result into two caches. + When the event ends the current statement the caches are is copied into + the argument file. + Copying is also concerned how to wrap the event, specifically to produce + a valid SQL syntax. + When the encoded data size is within max(MAX_ALLOWED_PACKET) + a regular BINLOG query is composed. Otherwise it is build as fragmented + + SET @binlog_fragment_0='...'; + SET @binlog_fragment_1='...'; + BINLOG @binlog_fragment_0, @binlog_fragment_1; + + where fragments are represented by a pair of indexed user + "one shot" variables. + + @note + If any changes made don't forget to duplicate them to + Old_rows_log_event as long as it's supported. + + @param file pointer to IO_CACHE + @param print_event_info pointer to print_event_info specializing + what out of and how to print the event + @param name the name of a table that the event operates on + + The function signals on any error of cache access through setting + that cache's @c error to -1. +*/ void Rows_log_event::print_helper(FILE *file, PRINT_EVENT_INFO *print_event_info, char const *const name) { IO_CACHE *const head= &print_event_info->head_cache; IO_CACHE *const body= &print_event_info->body_cache; + bool do_print_encoded= + print_event_info->base64_output_mode != BASE64_OUTPUT_DECODE_ROWS && + !print_event_info->short_form; + if (!print_event_info->short_form) { bool const last_stmt_event= get_flags(STMT_END_F); @@ -9048,13 +10645,18 @@ void Rows_log_event::print_helper(FILE *file, my_b_printf(head, "\t%s: table id %lu%s\n", name, m_table_id, last_stmt_event ? " flags: STMT_END_F" : ""); - print_base64(body, print_event_info, !last_stmt_event); + print_base64(body, print_event_info, do_print_encoded); } if (get_flags(STMT_END_F)) { - copy_event_cache_to_file_and_reinit(head, file); - copy_event_cache_to_file_and_reinit(body, file); + if (copy_event_cache_to_file_and_reinit(head, file)) + { + head->error= -1; + return; + } + copy_cache_to_file_wrapped(file, body, do_print_encoded, + print_event_info->delimiter); } } #endif @@ -9113,21 +10715,21 @@ bool Annotate_rows_log_event::is_valid() const } #ifndef MYSQL_CLIENT -bool Annotate_rows_log_event::write_data_header(IO_CACHE *file) +bool Annotate_rows_log_event::write_data_header() { return 0; } #endif #ifndef MYSQL_CLIENT -bool Annotate_rows_log_event::write_data_body(IO_CACHE *file) +bool Annotate_rows_log_event::write_data_body() { - return wrapper_my_b_safe_write(file, (uchar*) m_query_txt, m_query_len); + return write_data(m_query_txt, m_query_len); } #endif #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -void Annotate_rows_log_event::pack_info(THD *thd, Protocol* protocol) +void Annotate_rows_log_event::pack_info(Protocol* protocol) { if (m_query_txt && m_query_len) protocol->store(m_query_txt, m_query_len, &my_charset_bin); @@ -9174,7 +10776,7 @@ void Annotate_rows_log_event::print(FILE *file, PRINT_EVENT_INFO *pinfo) #endif #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -int Annotate_rows_log_event::do_apply_event(Relay_log_info const *rli) +int Annotate_rows_log_event::do_apply_event(rpl_group_info *rgi) { m_save_thd_query_txt= thd->query(); m_save_thd_query_len= thd->query_length(); @@ -9184,18 +10786,18 @@ int Annotate_rows_log_event::do_apply_event(Relay_log_info const *rli) #endif #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -int Annotate_rows_log_event::do_update_pos(Relay_log_info *rli) +int Annotate_rows_log_event::do_update_pos(rpl_group_info *rgi) { - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); return 0; } #endif #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) Log_event::enum_skip_reason -Annotate_rows_log_event::do_shall_skip(Relay_log_info *rli) +Annotate_rows_log_event::do_shall_skip(rpl_group_info *rgi) { - return continue_group(rli); + return continue_group(rgi); } #endif @@ -9297,8 +10899,9 @@ Table_map_log_event::Table_map_log_event(THD *thd, TABLE *tbl, ulong tid, m_null_bits(0), m_meta_memory(NULL) { - uchar cbuf[sizeof(m_colcnt) + 1]; + uchar cbuf[MAX_INT_WIDTH]; uchar *cbuf_end; + DBUG_ENTER("Table_map_log_event::Table_map_log_event(TABLE)"); DBUG_ASSERT(m_table_id != ~0UL); /* In TABLE_SHARE, "db" and "table_name" are 0-terminated (see this comment in @@ -9319,12 +10922,15 @@ Table_map_log_event::Table_map_log_event(THD *thd, TABLE *tbl, ulong tid, DBUG_ASSERT(static_cast<size_t>(cbuf_end - cbuf) <= sizeof(cbuf)); m_data_size+= (cbuf_end - cbuf) + m_colcnt; // COLCNT and column types + if (tbl->triggers) + m_flags|= TM_BIT_HAS_TRIGGERS_F; + /* If malloc fails, caught in is_valid() */ if ((m_memory= (uchar*) my_malloc(m_colcnt, MYF(MY_WME)))) { m_coltype= reinterpret_cast<uchar*>(m_memory); for (unsigned int i= 0 ; i < m_table->s->fields ; ++i) - m_coltype[i]= m_table->field[i]->type(); + m_coltype[i]= m_table->field[i]->binlog_type(); } /* @@ -9363,6 +10969,7 @@ Table_map_log_event::Table_map_log_event(THD *thd, TABLE *tbl, ulong tid, if (m_table->field[i]->maybe_null()) m_null_bits[(i / 8)]+= 1 << (i % 8); + DBUG_VOID_RETURN; } #endif /* !defined(MYSQL_CLIENT) */ @@ -9510,7 +11117,7 @@ int Table_map_log_event::rewrite_db(const char* new_db, size_t new_len, DBUG_ENTER("Table_map_log_event::rewrite_db"); DBUG_ASSERT(temp_buf); - uint header_len= min(desc->common_header_len, + uint header_len= MY_MIN(desc->common_header_len, LOG_EVENT_MINIMAL_HEADER_LEN) + TABLE_MAP_HEADER_LEN; int len_diff; @@ -9657,19 +11264,20 @@ enum enum_tbl_map_status rli->tables_to_lock. */ static enum_tbl_map_status -check_table_map(Relay_log_info const *rli, RPL_TABLE_LIST *table_list) +check_table_map(rpl_group_info *rgi, RPL_TABLE_LIST *table_list) { DBUG_ENTER("check_table_map"); enum_tbl_map_status res= OK_TO_PROCESS; - - if (rli->sql_thd->slave_thread /* filtering is for slave only */ && - (!rpl_filter->db_ok(table_list->db) || - (rpl_filter->is_on() && !rpl_filter->tables_ok("", table_list)))) + Relay_log_info *rli= rgi->rli; + if ((rgi->thd->slave_thread /* filtering is for slave only */ || + IF_WSREP((WSREP(rgi->thd) && rgi->thd->wsrep_applier), 0)) && + (!rli->mi->rpl_filter->db_ok(table_list->db) || + (rli->mi->rpl_filter->is_on() && !rli->mi->rpl_filter->tables_ok("", table_list)))) res= FILTERED_OUT; else { - RPL_TABLE_LIST *ptr= static_cast<RPL_TABLE_LIST*>(rli->tables_to_lock); - for(uint i=0 ; ptr && (i< rli->tables_to_lock_count); + RPL_TABLE_LIST *ptr= static_cast<RPL_TABLE_LIST*>(rgi->tables_to_lock); + for(uint i=0 ; ptr && (i< rgi->tables_to_lock_count); ptr= static_cast<RPL_TABLE_LIST*>(ptr->next_local), i++) { if (ptr->table_id == table_list->table_id) @@ -9692,14 +11300,15 @@ check_table_map(Relay_log_info const *rli, RPL_TABLE_LIST *table_list) DBUG_RETURN(res); } -int Table_map_log_event::do_apply_event(Relay_log_info const *rli) +int Table_map_log_event::do_apply_event(rpl_group_info *rgi) { RPL_TABLE_LIST *table_list; char *db_mem, *tname_mem, *ptr; size_t dummy_len; void *memory; + Rpl_filter *filter; + Relay_log_info const *rli= rgi->rli; DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)"); - DBUG_ASSERT(rli->sql_thd == thd); /* Step the query id to mark what columns that are actually used. */ thd->set_query_id(next_query_id()); @@ -9719,8 +11328,11 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) my_casedn_str(files_charset_info, (char*)db_mem); } + /* call from mysql_client_binlog_statement() will not set rli->mi */ + filter= rgi->thd->slave_thread ? rli->mi->rpl_filter : global_rpl_filter; + /* rewrite rules changed the database */ - if (((ptr= (char*) rpl_filter->get_rewrite_db(db_mem, &dummy_len)) != db_mem)) + if (((ptr= (char*) filter->get_rewrite_db(db_mem, &dummy_len)) != db_mem)) strmov(db_mem, ptr); table_list->init_one_table(db_mem, strlen(db_mem), @@ -9730,8 +11342,14 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) table_list->table_id= DBUG_EVALUATE_IF("inject_tblmap_same_id_maps_diff_table", 0, m_table_id); table_list->updating= 1; table_list->required_type= FRMTYPE_TABLE; - DBUG_PRINT("debug", ("table: %s is mapped to %u", table_list->table_name, table_list->table_id)); - enum_tbl_map_status tblmap_status= check_table_map(rli, table_list); + + DBUG_PRINT("debug", ("table: %s is mapped to %llu", table_list->table_name, + table_list->table_id)); + table_list->master_had_triggers= ((m_flags & TM_BIT_HAS_TRIGGERS_F) ? 1 : 0); + DBUG_PRINT("debug", ("table->master_had_triggers=%d", + (int)table_list->master_had_triggers)); + + enum_tbl_map_status tblmap_status= check_table_map(rgi, table_list); if (tblmap_status == OK_TO_PROCESS) { DBUG_ASSERT(thd->lex->query_tables != table_list); @@ -9757,9 +11375,9 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) We record in the slave's information that the table should be locked by linking the table into the list of tables to lock. */ - table_list->next_global= table_list->next_local= rli->tables_to_lock; - const_cast<Relay_log_info*>(rli)->tables_to_lock= table_list; - const_cast<Relay_log_info*>(rli)->tables_to_lock_count++; + table_list->next_global= table_list->next_local= rgi->tables_to_lock; + rgi->tables_to_lock= table_list; + rgi->tables_to_lock_count++; /* 'memory' is freed in clear_tables_to_lock */ } else // FILTERED_OUT, SAME_ID_MAPPING_* @@ -9789,14 +11407,15 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) table_list->table_id); if (thd->slave_thread) - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), buf); + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(), + ER_THD(thd, ER_SLAVE_FATAL_ERROR), buf); else /* For the cases in which a 'BINLOG' statement is set to execute in a user session */ - my_printf_error(ER_SLAVE_FATAL_ERROR, ER(ER_SLAVE_FATAL_ERROR), + my_printf_error(ER_SLAVE_FATAL_ERROR, + ER_THD(thd, ER_SLAVE_FATAL_ERROR), MYF(0), buf); } @@ -9807,25 +11426,25 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) } Log_event::enum_skip_reason -Table_map_log_event::do_shall_skip(Relay_log_info *rli) +Table_map_log_event::do_shall_skip(rpl_group_info *rgi) { /* If the slave skip counter is 1, then we should not start executing on the next event. */ - return continue_group(rli); + return continue_group(rgi); } -int Table_map_log_event::do_update_pos(Relay_log_info *rli) +int Table_map_log_event::do_update_pos(rpl_group_info *rgi) { - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); return 0; } #endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */ #ifndef MYSQL_CLIENT -bool Table_map_log_event::write_data_header(IO_CACHE *file) +bool Table_map_log_event::write_data_header() { DBUG_ASSERT(m_table_id != ~0UL); uchar buf[TABLE_MAP_HEADER_LEN]; @@ -9833,43 +11452,43 @@ bool Table_map_log_event::write_data_header(IO_CACHE *file) { int4store(buf + 0, m_table_id); int2store(buf + 4, m_flags); - return (wrapper_my_b_safe_write(file, buf, 6)); + return (write_data(buf, 6)); }); int6store(buf + TM_MAPID_OFFSET, (ulonglong)m_table_id); int2store(buf + TM_FLAGS_OFFSET, m_flags); - return (wrapper_my_b_safe_write(file, buf, TABLE_MAP_HEADER_LEN)); + return write_data(buf, TABLE_MAP_HEADER_LEN); } -bool Table_map_log_event::write_data_body(IO_CACHE *file) +bool Table_map_log_event::write_data_body() { DBUG_ASSERT(m_dbnam != NULL); DBUG_ASSERT(m_tblnam != NULL); /* We use only one byte per length for storage in event: */ - DBUG_ASSERT(m_dblen <= min(NAME_LEN, 255)); - DBUG_ASSERT(m_tbllen <= min(NAME_LEN, 255)); + DBUG_ASSERT(m_dblen <= MY_MIN(NAME_LEN, 255)); + DBUG_ASSERT(m_tbllen <= MY_MIN(NAME_LEN, 255)); uchar const dbuf[]= { (uchar) m_dblen }; uchar const tbuf[]= { (uchar) m_tbllen }; - uchar cbuf[sizeof(m_colcnt) + 1]; + uchar cbuf[MAX_INT_WIDTH]; uchar *const cbuf_end= net_store_length(cbuf, (size_t) m_colcnt); DBUG_ASSERT(static_cast<size_t>(cbuf_end - cbuf) <= sizeof(cbuf)); /* Store the size of the field metadata. */ - uchar mbuf[sizeof(m_field_metadata_size)]; + uchar mbuf[MAX_INT_WIDTH]; uchar *const mbuf_end= net_store_length(mbuf, m_field_metadata_size); - return (wrapper_my_b_safe_write(file, dbuf, sizeof(dbuf)) || - wrapper_my_b_safe_write(file, (const uchar*)m_dbnam, m_dblen+1) || - wrapper_my_b_safe_write(file, tbuf, sizeof(tbuf)) || - wrapper_my_b_safe_write(file, (const uchar*)m_tblnam, m_tbllen+1) || - wrapper_my_b_safe_write(file, cbuf, (size_t) (cbuf_end - cbuf)) || - wrapper_my_b_safe_write(file, m_coltype, m_colcnt) || - wrapper_my_b_safe_write(file, mbuf, (size_t) (mbuf_end - mbuf)) || - wrapper_my_b_safe_write(file, m_field_metadata, m_field_metadata_size), - wrapper_my_b_safe_write(file, m_null_bits, (m_colcnt + 7) / 8)); + return write_data(dbuf, sizeof(dbuf)) || + write_data(m_dbnam, m_dblen+1) || + write_data(tbuf, sizeof(tbuf)) || + write_data(m_tblnam, m_tbllen+1) || + write_data(cbuf, (size_t) (cbuf_end - cbuf)) || + write_data(m_coltype, m_colcnt) || + write_data(mbuf, (size_t) (mbuf_end - mbuf)) || + write_data(m_field_metadata, m_field_metadata_size), + write_data(m_null_bits, (m_colcnt + 7) / 8); } #endif @@ -9881,7 +11500,7 @@ bool Table_map_log_event::write_data_body(IO_CACHE *file) */ #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Table_map_log_event::pack_info(THD *thd, Protocol *protocol) +void Table_map_log_event::pack_info(Protocol *protocol) { char buf[256]; size_t bytes= my_snprintf(buf, sizeof(buf), @@ -9896,15 +11515,20 @@ void Table_map_log_event::pack_info(THD *thd, Protocol *protocol) #ifdef MYSQL_CLIENT -void Table_map_log_event::print(FILE *, PRINT_EVENT_INFO *print_event_info) +void Table_map_log_event::print(FILE *file, PRINT_EVENT_INFO *print_event_info) { if (!print_event_info->short_form) { print_header(&print_event_info->head_cache, print_event_info, TRUE); my_b_printf(&print_event_info->head_cache, - "\tTable_map: %`s.%`s mapped to number %lu\n", - m_dbnam, m_tblnam, m_table_id); - print_base64(&print_event_info->body_cache, print_event_info, TRUE); + "\tTable_map: %`s.%`s mapped to number %lu%s\n", + m_dbnam, m_tblnam, m_table_id, + ((m_flags & TM_BIT_HAS_TRIGGERS_F) ? + " (has triggers)" : "")); + print_base64(&print_event_info->body_cache, print_event_info, + print_event_info->base64_output_mode != + BASE64_OUTPUT_DECODE_ROWS); + copy_event_cache_to_file_and_reinit(&print_event_info->head_cache, file); } } #endif @@ -9919,9 +11543,9 @@ void Table_map_log_event::print(FILE *, PRINT_EVENT_INFO *print_event_info) #if !defined(MYSQL_CLIENT) Write_rows_log_event::Write_rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid_arg, - MY_BITMAP const *cols, bool is_transactional) - : Rows_log_event(thd_arg, tbl_arg, tid_arg, cols, is_transactional) + :Rows_log_event(thd_arg, tbl_arg, tid_arg, tbl_arg->rpl_write_set, + is_transactional, WRITE_ROWS_EVENT_V1) { } #endif @@ -9933,7 +11557,7 @@ Write_rows_log_event::Write_rows_log_event(THD *thd_arg, TABLE *tbl_arg, Write_rows_log_event::Write_rows_log_event(const char *buf, uint event_len, const Format_description_log_event *description_event) -: Rows_log_event(buf, event_len, WRITE_ROWS_EVENT, description_event) +: Rows_log_event(buf, event_len, description_event) { } #endif @@ -9954,8 +11578,7 @@ Write_rows_log_event::do_before_row_operations(const Slave_reporting_capability todo: to introduce a property for the event (handler?) which forces applying the event in the replace (idempotent) fashion. */ - if ((slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT) || - (m_table->s->db_type()->db_type == DB_TYPE_NDBCLUSTER)) + if (slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT) { /* We are using REPLACE semantics and not INSERT IGNORE semantics @@ -9968,8 +11591,7 @@ Write_rows_log_event::do_before_row_operations(const Slave_reporting_capability /* Pretend we're executing a REPLACE command: this is needed for - InnoDB and NDB Cluster since they are not (properly) checking the - lex->duplicates flag. + InnoDB since it is not (properly) checking the lex->duplicates flag. */ thd->lex->sql_command= SQLCOM_REPLACE; /* @@ -9977,40 +11599,14 @@ Write_rows_log_event::do_before_row_operations(const Slave_reporting_capability */ m_table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); /* - NDB specific: update from ndb master wrapped as Write_rows - so that the event should be applied to replace slave's row + The following is needed in case if we have AFTER DELETE triggers. */ m_table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); - /* - NDB specific: if update from ndb master wrapped as Write_rows - does not find the row it's assumed idempotent binlog applying - is taking place; don't raise the error. - */ m_table->file->extra(HA_EXTRA_IGNORE_NO_KEY); - /* - TODO: the cluster team (Tomas?) says that it's better if the engine knows - how many rows are going to be inserted, then it can allocate needed memory - from the start. - */ } + if (slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers ) + m_table->prepare_triggers_for_insert_stmt_or_event(); - /* - We need TIMESTAMP_NO_AUTO_SET otherwise ha_write_row() will not use fill - any TIMESTAMP column with data from the row but instead will use - the event's current time. - As we replicate from TIMESTAMP to TIMESTAMP and slave has no extra - columns, we know that all TIMESTAMP columns on slave will receive explicit - data from the row, so TIMESTAMP_NO_AUTO_SET is ok. - When we allow a table without TIMESTAMP to be replicated to a table having - more columns including a TIMESTAMP column, or when we allow a TIMESTAMP - column to be replicated into a BIGINT column and the slave's table has a - TIMESTAMP column, then the slave's TIMESTAMP column will take its value - from set_time() which we called earlier (consistent with SBR). And then in - some cases we won't want TIMESTAMP_NO_AUTO_SET (will require some code to - analyze if explicit data is provided for slave's TIMESTAMP columns). - */ - m_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - /* Honor next number column if present */ m_table->next_number_field= m_table->found_next_number_field; /* @@ -10057,16 +11653,17 @@ Write_rows_log_event::do_after_row_operations(const Slave_reporting_capability * */ if (is_auto_inc_in_extra_columns()) { - bitmap_clear_bit(m_table->write_set, m_table->next_number_field->field_index); - bitmap_clear_bit( m_table->read_set, m_table->next_number_field->field_index); + bitmap_clear_bit(m_table->rpl_write_set, + m_table->next_number_field->field_index); + bitmap_clear_bit(m_table->read_set, + m_table->next_number_field->field_index); if (get_flags(STMT_END_F)) m_table->file->ha_release_auto_increment(); } m_table->next_number_field=0; m_table->auto_increment_field_not_null= FALSE; - if ((slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT) || - m_table->s->db_type()->db_type == DB_TYPE_NDBCLUSTER) + if (slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT) { m_table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); m_table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); @@ -10087,6 +11684,26 @@ Write_rows_log_event::do_after_row_operations(const Slave_reporting_capability * #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) +bool Rows_log_event::process_triggers(trg_event_type event, + trg_action_time_type time_type, + bool old_row_is_record1) +{ + bool result; + DBUG_ENTER("Rows_log_event::process_triggers"); + m_table->triggers->mark_fields_used(event); + if (slave_run_triggers_for_rbr == SLAVE_RUN_TRIGGERS_FOR_RBR_YES) + { + tmp_disable_binlog(thd); /* Do not replicate the low-level changes. */ + result= m_table->triggers->process_triggers(thd, event, + time_type, old_row_is_record1); + reenable_binlog(thd); + } + else + result= m_table->triggers->process_triggers(thd, event, + time_type, old_row_is_record1); + + DBUG_RETURN(result); +} /* Check if there are more UNIQUE keys after the given key. */ @@ -10160,7 +11777,7 @@ is_duplicate_key_error(int errcode) */ int -Rows_log_event::write_row(const Relay_log_info *const rli, +Rows_log_event::write_row(rpl_group_info *rgi, const bool overwrite) { DBUG_ENTER("write_row"); @@ -10169,22 +11786,39 @@ Rows_log_event::write_row(const Relay_log_info *const rli, TABLE *table= m_table; // pointer to event's table int error; int UNINIT_VAR(keynum); + const bool invoke_triggers= + slave_run_triggers_for_rbr && !master_had_triggers && table->triggers; auto_afree_ptr<char> key(NULL); - prepare_record(table, m_width, - table->file->ht->db_type != DB_TYPE_NDBCLUSTER); + prepare_record(table, m_width, true); /* unpack row into table->record[0] */ - if ((error= unpack_current_row(rli))) + if ((error= unpack_current_row(rgi))) + { + table->file->print_error(error, MYF(0)); DBUG_RETURN(error); + } - if (m_curr_row == m_rows_buf) + if (m_curr_row == m_rows_buf && !invoke_triggers) { + /* + This table has no triggers so we can do bulk insert. + + This is the first row to be inserted, we estimate the rows with + the size of the first row and use that value to initialize + storage engine for bulk insertion. + */ /* this is the first row to be inserted, we estimate the rows with the size of the first row and use that value to initialize storage engine for bulk insertion */ - ulong estimated_rows= (m_rows_end - m_curr_row) / (m_curr_row_end - m_curr_row); - m_table->file->ha_start_bulk_insert(estimated_rows); + DBUG_ASSERT(!(m_curr_row > m_curr_row_end)); + ulong estimated_rows= 0; + if (m_curr_row < m_curr_row_end) + estimated_rows= (m_rows_end - m_curr_row) / (m_curr_row_end - m_curr_row); + else if (m_curr_row == m_curr_row_end) + estimated_rows= 1; + + table->file->ha_start_bulk_insert(estimated_rows); } /* @@ -10194,11 +11828,15 @@ Rows_log_event::write_row(const Relay_log_info *const rli, if (is_auto_inc_in_extra_columns()) m_table->next_number_field->set_null(); -#ifndef DBUG_OFF DBUG_DUMP("record[0]", table->record[0], table->s->reclength); - DBUG_PRINT_BITSET("debug", "write_set = %s", table->write_set); - DBUG_PRINT_BITSET("debug", "read_set = %s", table->read_set); -#endif + DBUG_PRINT_BITSET("debug", "rpl_write_set: %s", table->rpl_write_set); + DBUG_PRINT_BITSET("debug", "read_set: %s", table->read_set); + + if (invoke_triggers && + process_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE, TRUE)) + { + DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet + } /* Try to write record. If a corresponding record already exists in the table, @@ -10298,14 +11936,12 @@ Rows_log_event::write_row(const Relay_log_info *const rli, if (!get_flags(COMPLETE_ROWS_F)) { restore_record(table,record[1]); - error= unpack_current_row(rli); + error= unpack_current_row(rgi); } -#ifndef DBUG_OFF DBUG_PRINT("debug",("preparing for update: before and after image")); DBUG_DUMP("record[1] (before)", table->record[1], table->s->reclength); DBUG_DUMP("record[0] (after)", table->record[0], table->s->reclength); -#endif /* REPLACE is defined as either INSERT or DELETE + INSERT. If @@ -10321,53 +11957,81 @@ Rows_log_event::write_row(const Relay_log_info *const rli, then there might be another key for which the unique check will fail, so we're better off just deleting the row and inserting the correct row. + + Additionally we don't use UPDATE if rbr triggers should be invoked - + when triggers are used we want a simple and predictable execution path. */ - if (last_uniq_key(table, keynum) && + if (last_uniq_key(table, keynum) && !invoke_triggers && !table->file->referenced_by_foreign_key()) { DBUG_PRINT("info",("Updating row using ha_update_row()")); - error=table->file->ha_update_row(table->record[1], + error= table->file->ha_update_row(table->record[1], table->record[0]); switch (error) { - + case HA_ERR_RECORD_IS_THE_SAME: DBUG_PRINT("info",("ignoring HA_ERR_RECORD_IS_THE_SAME error from" " ha_update_row()")); error= 0; - + case 0: break; - - default: + + default: DBUG_PRINT("info",("ha_update_row() returns error %d",error)); table->file->print_error(error, MYF(0)); } - + DBUG_RETURN(error); } else { DBUG_PRINT("info",("Deleting offending row and trying to write new one again")); - if ((error= table->file->ha_delete_row(table->record[1]))) + if (invoke_triggers && + process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, TRUE)) + error= HA_ERR_GENERIC; // in case if error is not set yet + else { - DBUG_PRINT("info",("ha_delete_row() returns error %d",error)); - table->file->print_error(error, MYF(0)); - DBUG_RETURN(error); + if ((error= table->file->ha_delete_row(table->record[1]))) + { + DBUG_PRINT("info",("ha_delete_row() returns error %d",error)); + table->file->print_error(error, MYF(0)); + DBUG_RETURN(error); + } + if (invoke_triggers && + process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, TRUE)) + DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet } /* Will retry ha_write_row() with the offending row removed. */ } } + if (invoke_triggers && + process_triggers(TRG_EVENT_INSERT, TRG_ACTION_AFTER, TRUE)) + error= HA_ERR_GENERIC; // in case if error is not set yet + DBUG_RETURN(error); } #endif int -Write_rows_log_event::do_exec_row(const Relay_log_info *const rli) +Write_rows_log_event::do_exec_row(rpl_group_info *rgi) { DBUG_ASSERT(m_table != NULL); - int error= write_row(rli, slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT); + const char *tmp= thd->get_proc_info(); + const char *message= "Write_rows_log_event::write_row()"; + +#ifdef WSREP_PROC_INFO + my_snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "Write_rows_log_event::write_row(%lld)", + (long long) wsrep_thd_trx_seqno(thd)); + message= thd->wsrep_info; +#endif /* WSREP_PROC_INFO */ + + thd_proc_info(thd, message); + int error= write_row(rgi, slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT); + thd_proc_info(thd, tmp); if (error && !thd->is_error()) { @@ -10389,6 +12053,16 @@ void Write_rows_log_event::print(FILE *file, PRINT_EVENT_INFO* print_event_info) } #endif + +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) +uint8 Write_rows_log_event::get_trg_event_map() +{ + return (static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_INSERT)) | + static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)) | + static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE))); +} +#endif + /************************************************************************** Delete_rows_log_event member functions **************************************************************************/ @@ -10401,53 +12075,7 @@ void Write_rows_log_event::print(FILE *file, PRINT_EVENT_INFO* print_event_info) */ static bool record_compare(TABLE *table) { - /* - Need to set the X bit and the filler bits in both records since - there are engines that do not set it correctly. - - In addition, since MyISAM checks that one hasn't tampered with the - record, it is necessary to restore the old bytes into the record - after doing the comparison. - - TODO[record format ndb]: Remove it once NDB returns correct - records. Check that the other engines also return correct records. - */ - - DBUG_DUMP("record[0]", table->record[0], table->s->reclength); - DBUG_DUMP("record[1]", table->record[1], table->s->reclength); - bool result= FALSE; - uchar saved_x[2]= {0, 0}, saved_filler[2]= {0, 0}; - - if (table->s->null_bytes > 0) - { - for (int i = 0 ; i < 2 ; ++i) - { - /* - If we have an X bit then we need to take care of it. - */ - if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD)) - { - saved_x[i]= table->record[i][0]; - table->record[i][0]|= 1U; - } - - /* - If (last_null_bit_pos == 0 && null_bytes > 1), then: - - X bit (if any) + N nullable fields + M Field_bit fields = 8 bits - - Ie, the entire byte is used. - */ - if (table->s->last_null_bit_pos > 0) - { - saved_filler[i]= table->record[i][table->s->null_bytes - 1]; - table->record[i][table->s->null_bytes - 1]|= - 256U - (1U << table->s->last_null_bit_pos); - } - } - } - /** Compare full record only if: - there are no blob fields (otherwise we would also need @@ -10495,24 +12123,6 @@ static bool record_compare(TABLE *table) } record_compare_exit: - /* - Restore the saved bytes. - - TODO[record format ndb]: Remove this code once NDB returns the - correct record format. - */ - if (table->s->null_bytes > 0) - { - for (int i = 0 ; i < 2 ; ++i) - { - if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD)) - table->record[i][0]= saved_x[i]; - - if (table->s->last_null_bit_pos) - table->record[i][table->s->null_bytes - 1]= saved_filler[i]; - } - } - return result; } @@ -10531,14 +12141,12 @@ record_compare_exit: int Rows_log_event::find_key() { uint i, best_key_nr, last_part; - KEY *key, *best_key; - ulong best_rec_per_key, tmp; + KEY *key, *UNINIT_VAR(best_key); + ulong UNINIT_VAR(best_rec_per_key), tmp; DBUG_ENTER("Rows_log_event::find_key"); DBUG_ASSERT(m_table); best_key_nr= MAX_KEY; - LINT_INIT(best_key); - LINT_INIT(best_rec_per_key); /* Keys are sorted so that any primary key is first, followed by unique keys, @@ -10564,7 +12172,7 @@ int Rows_log_event::find_key() We can only use a non-unique key if it allows range scans (ie. skip FULLTEXT indexes and such). */ - last_part= key->key_parts - 1; + last_part= key->user_defined_key_parts - 1; DBUG_PRINT("info", ("Index %s rec_per_key[%u]= %lu", key->name, last_part, key->rec_per_key[last_part])); if (!(m_table->file->index_flags(i, last_part, 1) & HA_READ_NEXT)) @@ -10612,13 +12220,13 @@ static inline void issue_long_find_row_warning(Log_event_type type, const char *table_name, bool is_index_scan, - const Relay_log_info *rli) + rpl_group_info *rgi) { if ((global_system_variables.log_warnings > 1 && - !const_cast<Relay_log_info*>(rli)->is_long_find_row_note_printed())) + !rgi->is_long_find_row_note_printed())) { time_t now= my_time(0); - time_t stmt_ts= const_cast<Relay_log_info*>(rli)->get_row_stmt_start_timestamp(); + time_t stmt_ts= rgi->get_row_stmt_start_timestamp(); DBUG_EXECUTE_IF("inject_long_find_row_note", stmt_ts-=(LONG_FIND_ROW_THRESHOLD*2);); @@ -10627,7 +12235,7 @@ void issue_long_find_row_warning(Log_event_type type, if (delta > LONG_FIND_ROW_THRESHOLD) { - const_cast<Relay_log_info*>(rli)->set_long_find_row_note_printed(); + rgi->set_long_find_row_note_printed(); const char* evt_type= type == DELETE_ROWS_EVENT ? " DELETE" : "n UPDATE"; const char* scan_type= is_index_scan ? "scanning an index" : "scanning the table"; @@ -10642,6 +12250,16 @@ void issue_long_find_row_warning(Log_event_type type, } +/* + HA_ERR_KEY_NOT_FOUND is a fatal error normally, but it's an expected + error in speculate optimistic mode, so use something non-fatal instead +*/ +static int row_not_found_error(rpl_group_info *rgi) +{ + return rgi->speculation != rpl_group_info::SPECULATE_OPTIMISTIC + ? HA_ERR_KEY_NOT_FOUND : HA_ERR_RECORD_CHANGED; +} + /** Locate the current row in event's table. @@ -10673,7 +12291,7 @@ void issue_long_find_row_warning(Log_event_type type, for any following update/delete command. */ -int Rows_log_event::find_row(const Relay_log_info *rli) +int Rows_log_event::find_row(rpl_group_info *rgi) { DBUG_ENTER("Rows_log_event::find_row"); @@ -10691,12 +12309,10 @@ int Rows_log_event::find_row(const Relay_log_info *rli) */ prepare_record(table, m_width, FALSE); - error= unpack_current_row(rli); + error= unpack_current_row(rgi); -#ifndef DBUG_OFF DBUG_PRINT("info",("looking for the following record")); DBUG_DUMP("record[0]", table->record[0], table->s->reclength); -#endif if ((table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION) && table->s->primary_key < MAX_KEY) @@ -10720,13 +12336,15 @@ int Rows_log_event::find_row(const Relay_log_info *rli) table->s->reclength) == 0); */ + int error; DBUG_PRINT("info",("locating record using primary key (position)")); - int error= table->file->ha_rnd_pos_by_record(table->record[0]); + + error= table->file->ha_rnd_pos_by_record(table->record[0]); if (error) { DBUG_PRINT("info",("rnd_pos returns error %d",error)); - if (error == HA_ERR_RECORD_DELETED) - error= HA_ERR_KEY_NOT_FOUND; + if (error == HA_ERR_RECORD_DELETED || error == HA_ERR_KEY_NOT_FOUND) + error= row_not_found_error(rgi); table->file->print_error(error, MYF(0)); } DBUG_RETURN(error); @@ -10791,8 +12409,8 @@ int Rows_log_event::find_row(const Relay_log_info *rli) HA_READ_KEY_EXACT))) { DBUG_PRINT("info",("no record matching the key found in the table")); - if (error == HA_ERR_RECORD_DELETED) - error= HA_ERR_KEY_NOT_FOUND; + if (error == HA_ERR_RECORD_DELETED || error == HA_ERR_KEY_NOT_FOUND) + error= row_not_found_error(rgi); table->file->print_error(error, MYF(0)); table->file->ha_index_end(); goto end; @@ -10836,7 +12454,7 @@ int Rows_log_event::find_row(const Relay_log_info *rli) field in the BI image that is null and part of UNNI. */ bool null_found= FALSE; - for (uint i=0; i < keyinfo->key_parts && !null_found; i++) + for (uint i=0; i < keyinfo->user_defined_key_parts && !null_found; i++) { uint fieldnr= keyinfo->key_part[i].fieldnr - 1; Field **f= table->field+fieldnr; @@ -10866,21 +12484,6 @@ int Rows_log_event::find_row(const Relay_log_info *rli) while (record_compare(table)) { - /* - We need to set the null bytes to ensure that the filler bit - are all set when returning. There are storage engines that - just set the necessary bits on the bytes and don't set the - filler bits correctly. - - TODO[record format ndb]: Remove this code once NDB returns the - correct record format. - */ - if (table->s->null_bytes > 0) - { - table->record[0][table->s->null_bytes - 1]|= - 256U - (1U << table->s->last_null_bit_pos); - } - while ((error= table->file->ha_index_next(table->record[0]))) { /* We just skip records that has already been deleted */ @@ -10955,8 +12558,8 @@ int Rows_log_event::find_row(const Relay_log_info *rli) end: if (is_table_scan || is_index_scan) - issue_long_find_row_warning(get_type_code(), m_table->alias.c_ptr(), - is_index_scan, rli); + issue_long_find_row_warning(get_general_type_code(), m_table->alias.c_ptr(), + is_index_scan, rgi); table->default_column_bitmaps(); DBUG_RETURN(error); } @@ -10969,9 +12572,9 @@ end: #ifndef MYSQL_CLIENT Delete_rows_log_event::Delete_rows_log_event(THD *thd_arg, TABLE *tbl_arg, - ulong tid, MY_BITMAP const *cols, - bool is_transactional) - : Rows_log_event(thd_arg, tbl_arg, tid, cols, is_transactional) + ulong tid, bool is_transactional) + : Rows_log_event(thd_arg, tbl_arg, tid, tbl_arg->read_set, is_transactional, + DELETE_ROWS_EVENT_V1) { } #endif /* #if !defined(MYSQL_CLIENT) */ @@ -10983,7 +12586,7 @@ Delete_rows_log_event::Delete_rows_log_event(THD *thd_arg, TABLE *tbl_arg, Delete_rows_log_event::Delete_rows_log_event(const char *buf, uint event_len, const Format_description_log_event *description_event) - : Rows_log_event(buf, event_len, DELETE_ROWS_EVENT, description_event) + : Rows_log_event(buf, event_len, description_event) { } #endif @@ -11007,6 +12610,8 @@ Delete_rows_log_event::do_before_row_operations(const Slave_reporting_capability */ return 0; } + if (slave_run_triggers_for_rbr && !master_had_triggers) + m_table->prepare_triggers_for_delete_stmt_or_event(); return find_key(); } @@ -11024,19 +12629,52 @@ Delete_rows_log_event::do_after_row_operations(const Slave_reporting_capability return error; } -int Delete_rows_log_event::do_exec_row(const Relay_log_info *const rli) +int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi) { int error; + const char *tmp= thd->get_proc_info(); + const char *message= "Delete_rows_log_event::find_row()"; + const bool invoke_triggers= + slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers; DBUG_ASSERT(m_table != NULL); - if (!(error= find_row(rli))) +#ifdef WSREP_PROC_INFO + my_snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "Delete_rows_log_event::find_row(%lld)", + (long long) wsrep_thd_trx_seqno(thd)); + message= thd->wsrep_info; +#endif /* WSREP_PROC_INFO */ + + thd_proc_info(thd, message); + if (!(error= find_row(rgi))) { /* Delete the record found, located in record[0] */ - error= m_table->file->ha_delete_row(m_table->record[0]); + message= "Delete_rows_log_event::ha_delete_row()"; +#ifdef WSREP_PROC_INFO + snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "Delete_rows_log_event::ha_delete_row(%lld)", + (long long) wsrep_thd_trx_seqno(thd)); + message= thd->wsrep_info; +#endif + thd_proc_info(thd, message); + + if (invoke_triggers && + process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, FALSE)) + error= HA_ERR_GENERIC; // in case if error is not set yet + if (!error) + { + m_table->mark_columns_per_binlog_row_image(); + error= m_table->file->ha_delete_row(m_table->record[0]); + m_table->default_column_bitmaps(); + } + if (invoke_triggers && !error && + process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, FALSE)) + error= HA_ERR_GENERIC; // in case if error is not set yet m_table->file->ha_index_or_rnd_end(); } + thd_proc_info(thd, tmp); return error; } @@ -11051,6 +12689,13 @@ void Delete_rows_log_event::print(FILE *file, #endif +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) +uint8 Delete_rows_log_event::get_trg_event_map() +{ + return static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE)); +} +#endif + /************************************************************************** Update_rows_log_event member functions **************************************************************************/ @@ -11061,27 +12706,17 @@ void Delete_rows_log_event::print(FILE *file, #if !defined(MYSQL_CLIENT) Update_rows_log_event::Update_rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid, - MY_BITMAP const *cols_bi, - MY_BITMAP const *cols_ai, bool is_transactional) -: Rows_log_event(thd_arg, tbl_arg, tid, cols_bi, is_transactional) +: Rows_log_event(thd_arg, tbl_arg, tid, tbl_arg->read_set, is_transactional, + UPDATE_ROWS_EVENT_V1) { - init(cols_ai); -} - -Update_rows_log_event::Update_rows_log_event(THD *thd_arg, TABLE *tbl_arg, - ulong tid, - MY_BITMAP const *cols, - bool is_transactional) -: Rows_log_event(thd_arg, tbl_arg, tid, cols, is_transactional) -{ - init(cols); + init(tbl_arg->rpl_write_set); } void Update_rows_log_event::init(MY_BITMAP const *cols) { - /* if bitmap_init fails, caught in is_valid() */ - if (likely(!bitmap_init(&m_cols_ai, + /* if my_bitmap_init fails, caught in is_valid() */ + if (likely(!my_bitmap_init(&m_cols_ai, m_width <= sizeof(m_bitbuf_ai)*8 ? m_bitbuf_ai : NULL, m_width, false))) @@ -11100,8 +12735,8 @@ void Update_rows_log_event::init(MY_BITMAP const *cols) Update_rows_log_event::~Update_rows_log_event() { if (m_cols_ai.bitmap == m_bitbuf_ai) // no my_malloc happened - m_cols_ai.bitmap= 0; // so no my_free in bitmap_free - bitmap_free(&m_cols_ai); // To pair with bitmap_init(). + m_cols_ai.bitmap= 0; // so no my_free in my_bitmap_free + my_bitmap_free(&m_cols_ai); // To pair with my_bitmap_init(). } @@ -11113,7 +12748,7 @@ Update_rows_log_event::Update_rows_log_event(const char *buf, uint event_len, const Format_description_log_event *description_event) - : Rows_log_event(buf, event_len, UPDATE_ROWS_EVENT, description_event) + : Rows_log_event(buf, event_len, description_event) { } #endif @@ -11133,7 +12768,8 @@ Update_rows_log_event::do_before_row_operations(const Slave_reporting_capability if ((err= find_key())) return err; - m_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; + if (slave_run_triggers_for_rbr && !master_had_triggers) + m_table->prepare_triggers_for_update_stmt_or_event(); return 0; } @@ -11152,19 +12788,32 @@ Update_rows_log_event::do_after_row_operations(const Slave_reporting_capability } int -Update_rows_log_event::do_exec_row(const Relay_log_info *const rli) +Update_rows_log_event::do_exec_row(rpl_group_info *rgi) { + const bool invoke_triggers= + slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers; + const char *tmp= thd->get_proc_info(); + const char *message= "Update_rows_log_event::find_row()"; DBUG_ASSERT(m_table != NULL); - int error= find_row(rli); +#ifdef WSREP_PROC_INFO + my_snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "Update_rows_log_event::find_row(%lld)", + (long long) wsrep_thd_trx_seqno(thd)); + message= thd->wsrep_info; +#endif /* WSREP_PROC_INFO */ + + thd_proc_info(thd, message); + int error= find_row(rgi); if (error) { /* We need to read the second image in the event of error to be able to skip to the next pair of updates */ - m_curr_row= m_curr_row_end; - unpack_current_row(rli); + if ((m_curr_row= m_curr_row_end)) + unpack_current_row(rgi, &m_cols_ai); + thd_proc_info(thd, tmp); return error; } @@ -11182,8 +12831,17 @@ Update_rows_log_event::do_exec_row(const Relay_log_info *const rli) store_record(m_table,record[1]); m_curr_row= m_curr_row_end; + message= "Update_rows_log_event::unpack_current_row()"; +#ifdef WSREP_PROC_INFO + my_snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "Update_rows_log_event::unpack_current_row(%lld)", + (long long) wsrep_thd_trx_seqno(thd)); + message= thd->wsrep_info; +#endif /* WSREP_PROC_INFO */ + /* this also updates m_curr_row_end */ - if ((error= unpack_current_row(rli))) + thd_proc_info(thd, message); + if ((error= unpack_current_row(rgi, &m_cols_ai))) goto err; /* @@ -11200,9 +12858,37 @@ Update_rows_log_event::do_exec_row(const Relay_log_info *const rli) DBUG_DUMP("new values", m_table->record[0], m_table->s->reclength); #endif + message= "Update_rows_log_event::ha_update_row()"; +#ifdef WSREP_PROC_INFO + my_snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "Update_rows_log_event::ha_update_row(%lld)", + (long long) wsrep_thd_trx_seqno(thd)); + message= thd->wsrep_info; +#endif /* WSREP_PROC_INFO */ + + thd_proc_info(thd, message); + if (invoke_triggers && + process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, TRUE)) + { + error= HA_ERR_GENERIC; // in case if error is not set yet + goto err; + } + + // Temporary fix to find out why it fails [/Matz] + memcpy(m_table->read_set->bitmap, m_cols.bitmap, (m_table->read_set->n_bits + 7) / 8); + memcpy(m_table->write_set->bitmap, m_cols_ai.bitmap, (m_table->write_set->n_bits + 7) / 8); + + m_table->mark_columns_per_binlog_row_image(); error= m_table->file->ha_update_row(m_table->record[1], m_table->record[0]); if (error == HA_ERR_RECORD_IS_THE_SAME) error= 0; + m_table->default_column_bitmaps(); + + if (invoke_triggers && !error && + process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_AFTER, TRUE)) + error= HA_ERR_GENERIC; // in case if error is not set yet + + thd_proc_info(thd, tmp); err: m_table->file->ha_index_or_rnd_end(); @@ -11219,6 +12905,12 @@ void Update_rows_log_event::print(FILE *file, } #endif +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) +uint8 Update_rows_log_event::get_trg_event_map() +{ + return static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)); +} +#endif Incident_log_event::Incident_log_event(const char *buf, uint event_len, const Format_description_log_event *descr_event) @@ -11233,6 +12925,8 @@ Incident_log_event::Incident_log_event(const char *buf, uint event_len, DBUG_PRINT("info",("event_len: %u; common_header_len: %d; post_header_len: %d", event_len, common_header_len, post_header_len)); + m_message.str= NULL; + m_message.length= 0; int incident_number= uint2korr(buf + common_header_len); if (incident_number >= INCIDENT_COUNT || incident_number <= INCIDENT_NONE) @@ -11248,8 +12942,19 @@ Incident_log_event::Incident_log_event(const char *buf, uint event_len, char const *const str_end= buf + event_len; uint8 len= 0; // Assignment to keep compiler happy const char *str= NULL; // Assignment to keep compiler happy - read_str(&ptr, str_end, &str, &len); - m_message.str= const_cast<char*>(str); + if (read_str(&ptr, str_end, &str, &len)) + { + /* Mark this event invalid */ + m_incident= INCIDENT_NONE; + DBUG_VOID_RETURN; + } + if (!(m_message.str= (char*) my_malloc(len+1, MYF(MY_WME)))) + { + /* Mark this event invalid */ + m_incident= INCIDENT_NONE; + DBUG_VOID_RETURN; + } + strmake(m_message.str, str, len); m_message.length= len; DBUG_PRINT("info", ("m_incident: %d", m_incident)); DBUG_VOID_RETURN; @@ -11258,6 +12963,8 @@ Incident_log_event::Incident_log_event(const char *buf, uint event_len, Incident_log_event::~Incident_log_event() { + if (m_message.str) + my_free(m_message.str); } @@ -11275,7 +12982,7 @@ Incident_log_event::description() const #ifndef MYSQL_CLIENT -void Incident_log_event::pack_info(THD *thd, Protocol *protocol) +void Incident_log_event::pack_info(Protocol *protocol) { char buf[256]; size_t bytes; @@ -11287,6 +12994,48 @@ void Incident_log_event::pack_info(THD *thd, Protocol *protocol) m_incident, description(), m_message.str); protocol->store(buf, bytes, &my_charset_bin); } +#endif /* MYSQL_CLIENT */ + + +#if WITH_WSREP && !defined(MYSQL_CLIENT) +/* + read the first event from (*buf). The size of the (*buf) is (*buf_len). + At the end (*buf) is shitfed to point to the following event or NULL and + (*buf_len) will be changed to account just being read bytes of the 1st event. +*/ +#define WSREP_MAX_ALLOWED_PACKET 1024*1024*1024 // current protocol max + +Log_event* wsrep_read_log_event( + char **arg_buf, size_t *arg_buf_len, + const Format_description_log_event *description_event) +{ + char *head= (*arg_buf); + uint data_len = uint4korr(head + EVENT_LEN_OFFSET); + char *buf= (*arg_buf); + const char *error= 0; + Log_event *res= 0; + DBUG_ENTER("wsrep_read_log_event"); + + if (data_len > WSREP_MAX_ALLOWED_PACKET) + { + error = "Event too big"; + goto err; + } + + res= Log_event::read_log_event(buf, data_len, &error, description_event, false); + +err: + if (!res) + { + DBUG_ASSERT(error != 0); + sql_print_error("Error in Log_event::read_log_event(): " + "'%s', data_len: %d, event_type: %d", + error,data_len,head[EVENT_TYPE_OFFSET]); + } + (*arg_buf)+= data_len; + (*arg_buf_len)-= data_len; + DBUG_RETURN(res); +} #endif @@ -11306,46 +13055,92 @@ Incident_log_event::print(FILE *file, #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) int -Incident_log_event::do_apply_event(Relay_log_info const *rli) +Incident_log_event::do_apply_event(rpl_group_info *rgi) { + Relay_log_info const *rli= rgi->rli; DBUG_ENTER("Incident_log_event::do_apply_event"); - rli->report(ERROR_LEVEL, ER_SLAVE_INCIDENT, - ER(ER_SLAVE_INCIDENT), + + if (ignored_error_code(ER_SLAVE_INCIDENT)) + { + DBUG_PRINT("info", ("Ignoring Incident")); + DBUG_RETURN(0); + } + + rli->report(ERROR_LEVEL, ER_SLAVE_INCIDENT, NULL, + ER_THD(rgi->thd, ER_SLAVE_INCIDENT), description(), m_message.length > 0 ? m_message.str : "<none>"); DBUG_RETURN(1); } #endif +#ifdef MYSQL_SERVER bool -Incident_log_event::write_data_header(IO_CACHE *file) +Incident_log_event::write_data_header() { DBUG_ENTER("Incident_log_event::write_data_header"); DBUG_PRINT("enter", ("m_incident: %d", m_incident)); uchar buf[sizeof(int16)]; int2store(buf, (int16) m_incident); -#ifndef MYSQL_CLIENT - DBUG_RETURN(wrapper_my_b_safe_write(file, buf, sizeof(buf))); -#else - DBUG_RETURN(my_b_safe_write(file, buf, sizeof(buf))); -#endif + DBUG_RETURN(write_data(buf, sizeof(buf))); } bool -Incident_log_event::write_data_body(IO_CACHE *file) +Incident_log_event::write_data_body() { uchar tmp[1]; DBUG_ENTER("Incident_log_event::write_data_body"); tmp[0]= (uchar) m_message.length; - crc= my_checksum(crc, (uchar*) tmp, 1); - if (m_message.length > 0) - { - crc= my_checksum(crc, (uchar*) m_message.str, m_message.length); - // todo: report a bug on write_str accepts uint but treats it as uchar - } - DBUG_RETURN(write_str(file, m_message.str, (uint) m_message.length)); + DBUG_RETURN(write_data(tmp, sizeof(tmp)) || + write_data(m_message.str, m_message.length)); +} +#endif + +Ignorable_log_event::Ignorable_log_event(const char *buf, + const Format_description_log_event + *descr_event, + const char *event_name) + :Log_event(buf, descr_event), number((int) (uchar) buf[EVENT_TYPE_OFFSET]), + description(event_name) +{ + DBUG_ENTER("Ignorable_log_event::Ignorable_log_event"); + DBUG_VOID_RETURN; } +Ignorable_log_event::~Ignorable_log_event() +{ +} + +#ifndef MYSQL_CLIENT +/* Pack info for its unrecognized ignorable event */ +void Ignorable_log_event::pack_info(Protocol *protocol) +{ + char buf[256]; + size_t bytes; + bytes= my_snprintf(buf, sizeof(buf), "# Ignorable event type %d (%s)", + number, description); + protocol->store(buf, bytes, &my_charset_bin); +} +#endif + +#ifdef MYSQL_CLIENT +/* Print for its unrecognized ignorable event */ +void +Ignorable_log_event::print(FILE *file, + PRINT_EVENT_INFO *print_event_info) +{ + if (print_event_info->short_form) + return; + + print_header(&print_event_info->head_cache, print_event_info, FALSE); + my_b_printf(&print_event_info->head_cache, "\tIgnorable\n"); + my_b_printf(&print_event_info->head_cache, + "# Ignorable event type %d (%s)\n", number, description); + copy_event_cache_to_file_and_reinit(&print_event_info->head_cache, + file); +} +#endif + #ifdef MYSQL_CLIENT /** @@ -11358,7 +13153,9 @@ st_print_event_info::st_print_event_info() auto_increment_increment(0),auto_increment_offset(0), charset_inited(0), lc_time_names_number(~0), charset_database_number(ILLEGAL_CHARSET_INFO_NUMBER), - thread_id(0), thread_id_printed(false), skip_replication(0), + thread_id(0), thread_id_printed(false), server_id(0), + server_id_printed(false), domain_id(0), domain_id_printed(false), + allow_parallel(true), allow_parallel_printed(false), skip_replication(0), base64_output_mode(BASE64_OUTPUT_UNSPEC), printed_fd_event(FALSE) { /* @@ -11396,6 +13193,9 @@ Heartbeat_log_event::Heartbeat_log_event(const char* buf, uint event_len, There is a dummy replacement for this in the embedded library that returns FALSE; this is used by XtraDB to allow it to access replication stuff while still being able to use the same plugin in both stand-alone and embedded. + + In this function it's ok to use active_mi, as this is only called for + the main replication server. */ bool rpl_get_position_info(const char **log_file_name, ulonglong *log_pos, const char **group_relay_log_name, @@ -11405,12 +13205,46 @@ bool rpl_get_position_info(const char **log_file_name, ulonglong *log_pos, return FALSE; #else const Relay_log_info *rli= &(active_mi->rli); - *log_file_name= rli->group_master_log_name; - *log_pos= rli->group_master_log_pos + - (rli->future_event_relay_log_pos - rli->group_relay_log_pos); - *group_relay_log_name= rli->group_relay_log_name; - *relay_log_pos= rli->future_event_relay_log_pos; + if (!rli->mi->using_parallel()) + { + *log_file_name= rli->group_master_log_name; + *log_pos= rli->group_master_log_pos + + (rli->future_event_relay_log_pos - rli->group_relay_log_pos); + *group_relay_log_name= rli->group_relay_log_name; + *relay_log_pos= rli->future_event_relay_log_pos; + } + else + { + *log_file_name= ""; + *log_pos= 0; + *group_relay_log_name= ""; + *relay_log_pos= 0; + } return TRUE; #endif } + +/** + Check if we should write event to the relay log + + This is used to skip events that is only supported by MySQL + + Return: + 0 ok + 1 Don't write event +*/ + +bool event_that_should_be_ignored(const char *buf) +{ + uint event_type= (uchar)buf[EVENT_TYPE_OFFSET]; + if (event_type == GTID_LOG_EVENT || + event_type == ANONYMOUS_GTID_LOG_EVENT || + event_type == PREVIOUS_GTIDS_LOG_EVENT || + event_type == TRANSACTION_CONTEXT_EVENT || + event_type == VIEW_CHANGE_EVENT || + event_type == XA_PREPARE_LOG_EVENT || + (uint2korr(buf + FLAGS_OFFSET) & LOG_EVENT_IGNORABLE_F)) + return 1; + return 0; +} #endif diff --git a/sql/log_event.h b/sql/log_event.h index 4ae01323b4b..446bd8cb827 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -1,4 +1,5 @@ /* Copyright (c) 2000, 2014, Oracle and/or its affiliates. + Copyright (c) 2009, 2014, Monty Program Ab. 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 @@ -48,6 +49,8 @@ #include "sql_class.h" /* THD */ #endif +#include "rpl_gtid.h" + /* Forward declarations */ class String; @@ -78,6 +81,7 @@ class String; #define LOG_READ_TRUNC -6 #define LOG_READ_TOO_LARGE -7 #define LOG_READ_CHECKSUM_FAILURE -8 +#define LOG_READ_DECRYPT -9 #define LOG_EVENT_OFFSET 4 @@ -142,64 +146,10 @@ class String; #define LINE_START_EMPTY 0x8 #define ESCAPED_EMPTY 0x10 -/***************************************************************************** - - old_sql_ex struct - - ****************************************************************************/ -struct old_sql_ex -{ - char field_term; - char enclosed; - char line_term; - char line_start; - char escaped; - char opt_flags; - char empty_flags; -}; - #define NUM_LOAD_DELIM_STRS 5 /***************************************************************************** - sql_ex_info struct - - ****************************************************************************/ -struct sql_ex_info -{ - sql_ex_info() {} /* Remove gcc warning */ - const char* field_term; - const char* enclosed; - const char* line_term; - const char* line_start; - const char* escaped; - int cached_new_format; - uint8 field_term_len,enclosed_len,line_term_len,line_start_len, escaped_len; - char opt_flags; - char empty_flags; - - // store in new format even if old is possible - void force_new_format() { cached_new_format = 1;} - int data_size() - { - return (new_format() ? - field_term_len + enclosed_len + line_term_len + - line_start_len + escaped_len + 6 : 7); - } - bool write_data(IO_CACHE* file); - const char* init(const char* buf, const char* buf_end, bool use_new_format); - bool new_format() - { - return ((cached_new_format != -1) ? cached_new_format : - (cached_new_format=(field_term_len > 1 || - enclosed_len > 1 || - line_term_len > 1 || line_start_len > 1 || - escaped_len > 1))); - } -}; - -/***************************************************************************** - MySQL Binary Log This log consists of events. Each event has a fixed-length header, @@ -251,13 +201,19 @@ struct sql_ex_info #define FORMAT_DESCRIPTION_HEADER_LEN (START_V3_HEADER_LEN+1+LOG_EVENT_TYPES) #define XID_HEADER_LEN 0 #define BEGIN_LOAD_QUERY_HEADER_LEN APPEND_BLOCK_HEADER_LEN -#define ROWS_HEADER_LEN 8 +#define ROWS_HEADER_LEN_V1 8 #define TABLE_MAP_HEADER_LEN 8 #define EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN (4 + 4 + 4 + 1) #define EXECUTE_LOAD_QUERY_HEADER_LEN (QUERY_HEADER_LEN + EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN) #define INCIDENT_HEADER_LEN 2 #define HEARTBEAT_HEADER_LEN 0 +#define IGNORABLE_HEADER_LEN 0 +#define ROWS_HEADER_LEN_V2 10 #define ANNOTATE_ROWS_HEADER_LEN 0 +#define BINLOG_CHECKPOINT_HEADER_LEN 4 +#define GTID_HEADER_LEN 19 +#define GTID_LIST_HEADER_LEN 4 +#define START_ENCRYPTION_HEADER_LEN 0 /* Max number of possible extra bytes in a replication event compared to a @@ -288,7 +244,7 @@ struct sql_ex_info to the slave. It is used to increase the thd(max_allowed) for both the DUMP thread on the master and the SQL/IO thread on the slave. */ -#define MAX_MAX_ALLOWED_PACKET 1024*1024*1024 +#define MAX_MAX_ALLOWED_PACKET (1024*1024*1024) /* Event header offsets; @@ -409,6 +365,9 @@ struct sql_ex_info /* RW = "RoWs" */ #define RW_MAPID_OFFSET 0 #define RW_FLAGS_OFFSET 6 +#define RW_VHLEN_OFFSET 8 +#define RW_V_TAG_LEN 1 +#define RW_V_EXTRAINFO_TAG 0 /* ELQ = "Execute Load Query" */ #define ELQ_FILE_ID_OFFSET QUERY_HEADER_LEN @@ -510,6 +469,17 @@ struct sql_ex_info #define LOG_EVENT_RELAY_LOG_F 0x40 /** + @def LOG_EVENT_IGNORABLE_F + + For an event, 'e', carrying a type code, that a slave, + 's', does not recognize, 's' will check 'e' for + LOG_EVENT_IGNORABLE_F, and if the flag is set, then 'e' + is ignored. Otherwise, 's' acknowledges that it has + found an unknown event in the relay log. +*/ +#define LOG_EVENT_IGNORABLE_F 0x80 + +/** @def LOG_EVENT_SKIP_REPLICATION_F Flag set by application creating the event (with @@skip_replication); the @@ -549,22 +519,13 @@ struct sql_ex_info /* Shouldn't be defined before */ #define EXPECTED_OPTIONS \ - ((ULL(1) << 14) | (ULL(1) << 26) | (ULL(1) << 27) | (ULL(1) << 19)) + ((1ULL << 14) | (1ULL << 26) | (1ULL << 27) | (1ULL << 19)) #if OPTIONS_WRITTEN_TO_BIN_LOG != EXPECTED_OPTIONS #error OPTIONS_WRITTEN_TO_BIN_LOG must NOT change their values! #endif #undef EXPECTED_OPTIONS /* You shouldn't use this one */ -enum enum_binlog_checksum_alg { - BINLOG_CHECKSUM_ALG_OFF= 0, // Events are without checksum though its generator - // is checksum-capable New Master (NM). - BINLOG_CHECKSUM_ALG_CRC32= 1, // CRC32 of zlib algorithm. - BINLOG_CHECKSUM_ALG_ENUM_END, // the cut line: valid alg range is [1, 0x7f]. - BINLOG_CHECKSUM_ALG_UNDEF= 255 // special value to tag undetermined yet checksum - // or events from checksum-unaware servers -}; - #define CHECKSUM_CRC32_SIGNATURE_LEN 4 /** defined statically while there is just one alg implemented @@ -572,6 +533,40 @@ enum enum_binlog_checksum_alg { #define BINLOG_CHECKSUM_LEN CHECKSUM_CRC32_SIGNATURE_LEN #define BINLOG_CHECKSUM_ALG_DESC_LEN 1 /* 1 byte checksum alg descriptor */ +/* + These are capability numbers for MariaDB slave servers. + + Newer MariaDB slaves set this to inform the master about their capabilities. + This allows the master to decide which events it can send to the slave + without breaking replication on old slaves that maybe do not understand + all events from newer masters. + + As new releases are backwards compatible, a given capability implies also + all capabilities with smaller number. + + Older MariaDB slaves and other MySQL slave servers do not set this, so they + are recorded with capability 0. +*/ + +/* MySQL or old MariaDB slave with no announced capability. */ +#define MARIA_SLAVE_CAPABILITY_UNKNOWN 0 +/* MariaDB >= 5.3, which understands ANNOTATE_ROWS_EVENT. */ +#define MARIA_SLAVE_CAPABILITY_ANNOTATE 1 +/* + MariaDB >= 5.5. This version has the capability to tolerate events omitted + from the binlog stream without breaking replication (MySQL slaves fail + because they mis-compute the offsets into the master's binlog). +*/ +#define MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES 2 +/* MariaDB >= 10.0, which knows about binlog_checkpoint_log_event. */ +#define MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT 3 +/* MariaDB >= 10.0.1, which knows about global transaction id events. */ +#define MARIA_SLAVE_CAPABILITY_GTID 4 + +/* Our capability. */ +#define MARIA_SLAVE_CAPABILITY_MINE MARIA_SLAVE_CAPABILITY_GTID + + /** @enum Log_event_type @@ -619,11 +614,12 @@ enum Log_event_type PRE_GA_DELETE_ROWS_EVENT = 22, /* - These event numbers are used from 5.1.16 and forward + These event numbers are used from 5.1.16 until mysql-5.6.6, + and in MariaDB */ - WRITE_ROWS_EVENT = 23, - UPDATE_ROWS_EVENT = 24, - DELETE_ROWS_EVENT = 25, + WRITE_ROWS_EVENT_V1 = 23, + UPDATE_ROWS_EVENT_V1 = 24, + DELETE_ROWS_EVENT_V1 = 25, /* Something out of the ordinary happened on the master @@ -637,6 +633,32 @@ enum Log_event_type HEARTBEAT_LOG_EVENT= 27, /* + In some situations, it is necessary to send over ignorable + data to the slave: data that a slave can handle in case there + is code for handling it, but which can be ignored if it is not + recognized. + + These mysql-5.6 events are not recognized (and ignored) by MariaDB + */ + IGNORABLE_LOG_EVENT= 28, + ROWS_QUERY_LOG_EVENT= 29, + + /* Version 2 of the Row events, generated only by mysql-5.6.6+ */ + WRITE_ROWS_EVENT = 30, + UPDATE_ROWS_EVENT = 31, + DELETE_ROWS_EVENT = 32, + + /* MySQL 5.6 GTID events, ignored by MariaDB */ + GTID_LOG_EVENT= 33, + ANONYMOUS_GTID_LOG_EVENT= 34, + PREVIOUS_GTIDS_LOG_EVENT= 35, + + /* MySQL 5.7 events, ignored by MariaDB */ + TRANSACTION_CONTEXT_EVENT= 36, + VIEW_CHANGE_EVENT= 37, + XA_PREPARE_LOG_EVENT= 38, + + /* Add new events here - right above this comment! Existing events (except ENUM_END_EVENT) should never change their numbers */ @@ -647,6 +669,28 @@ enum Log_event_type MARIA_EVENTS_BEGIN= 160, /* New Maria event numbers start from here */ ANNOTATE_ROWS_EVENT= 160, + /* + Binlog checkpoint event. Used for XA crash recovery on the master, not used + in replication. + A binlog checkpoint event specifies a binlog file such that XA crash + recovery can start from that file - and it is guaranteed to find all XIDs + that are prepared in storage engines but not yet committed. + */ + BINLOG_CHECKPOINT_EVENT= 161, + /* + Gtid event. For global transaction ID, used to start a new event group, + instead of the old BEGIN query event, and also to mark stand-alone + events. + */ + GTID_EVENT= 162, + /* + Gtid list event. Logged at the start of every binlog, to record the + current replication state. This consists of the last GTID seen for + each replication domain. + */ + GTID_LIST_EVENT= 163, + + START_ENCRYPTION_EVENT= 164, /* Add new MariaDB events here - right above this comment! */ @@ -719,6 +763,13 @@ typedef struct st_print_event_info uint charset_database_number; uint thread_id; bool thread_id_printed; + uint32 server_id; + bool server_id_printed; + uint32 domain_id; + bool domain_id_printed; + bool allow_parallel; + bool allow_parallel_printed; + /* Track when @@skip_replication changes so we need to output a SET statement for it. @@ -764,6 +815,45 @@ typedef struct st_print_event_info #endif /** + This class encapsulates writing of Log_event objects to IO_CACHE. + Automatically calculates the checksum and encrypts the data, if necessary. +*/ +class Log_event_writer +{ +public: + ulonglong bytes_written; + void *ctx; ///< Encryption context or 0 if no encryption is needed + uint checksum_len; + int write(Log_event *ev); + int write_header(uchar *pos, size_t len); + int write_data(const uchar *pos, size_t len); + int write_footer(); + my_off_t pos() { return my_b_safe_tell(file); } + +Log_event_writer(IO_CACHE *file_arg, Binlog_crypt_data *cr= 0) + : bytes_written(0), ctx(0), + file(file_arg), crypto(cr) { } + +private: + IO_CACHE *file; + /** + Placeholder for event checksum while writing to binlog. + */ + ha_checksum crc; + /** + Encryption data (key, nonce). Only used if ctx != 0. + */ + Binlog_crypt_data *crypto; + /** + Event length to be written into the next encrypted block + */ + uint event_len; + int write_internal(const uchar *pos, size_t len); + int encrypt_and_write(const uchar *pos, size_t len); + int maybe_write_event_len(uchar *pos, size_t len); +}; + +/** the struct aggregates two paramenters that identify an event uniquely in scope of communication of a particular master and slave couple. I.e there can not be 2 events from the same staying connected master which @@ -1029,16 +1119,46 @@ public: */ ulong slave_exec_mode; - /** - Placeholder for event checksum while writing to binlog. - */ - ha_checksum crc; + Log_event_writer *writer; #ifdef MYSQL_SERVER THD* thd; Log_event(); Log_event(THD* thd_arg, uint16 flags_arg, bool is_transactional); + + /* + init_show_field_list() prepares the column names and types for the + output of SHOW BINLOG EVENTS; it is used only by SHOW BINLOG + EVENTS. + */ + static void init_show_field_list(THD *thd, List<Item>* field_list); +#ifdef HAVE_REPLICATION + int net_send(Protocol *protocol, const char* log_name, my_off_t pos); + + /* + pack_info() is used by SHOW BINLOG EVENTS; as print() it prepares and sends + a string to display to the user, so it resembles print(). + */ + + virtual void pack_info(Protocol *protocol); + +#endif /* HAVE_REPLICATION */ + virtual const char* get_db() + { + return thd ? thd->db : 0; + } +#else + Log_event() : temp_buf(0), when(0), flags(0) {} + ha_checksum crc; + /* print*() functions are used by mysqlbinlog */ + virtual void print(FILE* file, PRINT_EVENT_INFO* print_event_info) = 0; + void print_timestamp(IO_CACHE* file, time_t *ts = 0); + void print_header(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info, + bool is_more); + void print_base64(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info, + bool do_print_encoded); +#endif /* read_log_event() functions read an event from a binlog or relay log; used by SHOW BINLOG EVENTS, the binlog_dump thread on the @@ -1067,9 +1187,9 @@ public: @param[in] file log file to be read @param[out] packet packet to hold the event - @param[in] lock the lock to be used upon read - @param[in] log_file_name_arg the log's file name - @param[out] is_binlog_active is the current log still active + @param[in] checksum_alg_arg verify the event checksum using this + algorithm (or don't if it's + use BINLOG_CHECKSUM_ALG_OFF) @retval 0 success @retval LOG_READ_EOF end of file, nothing was read @@ -1080,46 +1200,8 @@ public: @retval LOG_READ_TOO_LARGE event too large */ static int read_log_event(IO_CACHE* file, String* packet, - mysql_mutex_t* log_lock, - uint8 checksum_alg_arg, - const char *log_file_name_arg = NULL, - bool* is_binlog_active = NULL); - /* - init_show_field_list() prepares the column names and types for the - output of SHOW BINLOG EVENTS; it is used only by SHOW BINLOG - EVENTS. - */ - static void init_show_field_list(List<Item>* field_list); -#ifdef HAVE_REPLICATION - int net_send(THD *thd, Protocol *protocol, const char* log_name, - my_off_t pos); - - /* - pack_info() is used by SHOW BINLOG EVENTS; as print() it prepares and sends - a string to display to the user, so it resembles print(). - */ - - virtual void pack_info(THD *thd, Protocol *protocol); - -#endif /* HAVE_REPLICATION */ - virtual const char* get_db() - { - return thd ? thd->db : 0; - } -#else - Log_event() : temp_buf(0), flags(0) {} - /* avoid having to link mysqlbinlog against libpthread */ - static Log_event* read_log_event(IO_CACHE* file, - const Format_description_log_event - *description_event, my_bool crc_check); - /* print*() functions are used by mysqlbinlog */ - virtual void print(FILE* file, PRINT_EVENT_INFO* print_event_info) = 0; - void print_timestamp(IO_CACHE* file, time_t *ts = 0); - void print_header(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info, - bool is_more); - void print_base64(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info, - bool is_more); -#endif + const Format_description_log_event *fdle, + enum enum_binlog_checksum_alg checksum_alg_arg); /* The value is set by caller of FD constructor and Log_event::write_header() for the rest. @@ -1128,7 +1210,7 @@ public: On the slave side the value is assigned from post_header_len[last] of the last seen FD event. */ - uint8 checksum_alg; + enum enum_binlog_checksum_alg checksum_alg; static void *operator new(size_t size) { @@ -1143,24 +1225,29 @@ public: /* Placement version of the above operators */ static void *operator new(size_t, void* ptr) { return ptr; } static void operator delete(void*, void*) { } - bool wrapper_my_b_safe_write(IO_CACHE* file, const uchar* buf, ulong data_length); #ifdef MYSQL_SERVER - bool write_header(IO_CACHE* file, ulong data_length); - bool write_footer(IO_CACHE* file); + bool write_header(ulong data_length); + bool write_data(const uchar *buf, ulong data_length) + { return writer->write_data(buf, data_length); } + bool write_data(const char *buf, ulong data_length) + { return write_data((uchar*)buf, data_length); } + bool write_footer() + { return writer->write_footer(); } + my_bool need_checksum(); - virtual bool write(IO_CACHE* file) + virtual bool write() { - return(write_header(file, get_data_size()) || - write_data_header(file) || - write_data_body(file) || - write_footer(file)); + return write_header(get_data_size()) || write_data_header() || + write_data_body() || write_footer(); } - virtual bool write_data_header(IO_CACHE* file) + virtual bool write_data_header() { return 0; } - virtual bool write_data_body(IO_CACHE* file __attribute__((unused))) + virtual bool write_data_body() { return 0; } + + /* Return start of query time or current time */ inline my_time_t get_time() { THD *tmp_thd; @@ -1187,6 +1274,7 @@ public: #endif virtual Log_event_type get_type_code() = 0; virtual bool is_valid() const = 0; + virtual my_off_t get_header_len(my_off_t len) { return len; } void set_artificial_event() { flags |= LOG_EVENT_ARTIFICIAL_F; } void set_relay_log_event() { flags |= LOG_EVENT_RELAY_LOG_F; } bool is_artificial_event() const { return flags & LOG_EVENT_ARTIFICIAL_F; } @@ -1238,10 +1326,7 @@ public: */ const char* get_type_str(); - /* Return start of query time or current time */ - #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) -public: /** Apply the event to the database. @@ -1251,9 +1336,13 @@ public: @see do_apply_event */ - int apply_event(Relay_log_info const *rli) + int apply_event(rpl_group_info *rgi) { - return do_apply_event(rli); + int res; + THD_STAGE_INFO(thd, stage_apply_event); + res= do_apply_event(rgi); + THD_STAGE_INFO(thd, stage_after_apply_event); + return res; } @@ -1265,9 +1354,9 @@ public: @see do_update_pos */ - int update_pos(Relay_log_info *rli) + int update_pos(rpl_group_info *rgi) { - return do_update_pos(rli); + return do_update_pos(rgi); } /** @@ -1276,11 +1365,67 @@ public: @see do_shall_skip */ - enum_skip_reason shall_skip(Relay_log_info *rli) + enum_skip_reason shall_skip(rpl_group_info *rgi) { - return do_shall_skip(rli); + return do_shall_skip(rgi); } + + /* + Check if an event is non-final part of a stand-alone event group, + such as Intvar_log_event (such events should be processed as part + of the following event group, not individually). + See also is_part_of_group() + */ + static bool is_part_of_group(enum Log_event_type ev_type) + { + switch (ev_type) + { + case GTID_EVENT: + case INTVAR_EVENT: + case RAND_EVENT: + case USER_VAR_EVENT: + case TABLE_MAP_EVENT: + case ANNOTATE_ROWS_EVENT: + return true; + case DELETE_ROWS_EVENT: + case UPDATE_ROWS_EVENT: + case WRITE_ROWS_EVENT: + /* + ToDo: also check for non-final Rows_log_event (though such events + are usually in a BEGIN-COMMIT group). + */ + default: + return false; + } + } + /* + Same as above, but works on the object. In addition this is true for all + rows event except the last one. + */ + virtual bool is_part_of_group() { return 0; } + + static bool is_group_event(enum Log_event_type ev_type) + { + switch (ev_type) + { + case START_EVENT_V3: + case STOP_EVENT: + case ROTATE_EVENT: + case SLAVE_EVENT: + case FORMAT_DESCRIPTION_EVENT: + case INCIDENT_EVENT: + case HEARTBEAT_LOG_EVENT: + case BINLOG_CHECKPOINT_EVENT: + case GTID_LIST_EVENT: + case START_ENCRYPTION_EVENT: + return false; + + default: + return true; + } + } + protected: /** @@ -1293,14 +1438,14 @@ protected: A typical usage is: @code - enum_skip_reason do_shall_skip(Relay_log_info *rli) { - return continue_group(rli); + enum_skip_reason do_shall_skip(rpl_group_info *rgi) { + return continue_group(rgi); } @endcode @return Skip reason */ - enum_skip_reason continue_group(Relay_log_info *rli); + enum_skip_reason continue_group(rpl_group_info *rgi); /** Primitive to apply an event to the database. @@ -1317,7 +1462,7 @@ protected: @retval 0 Event applied successfully @retval errno Error code if event application failed */ - virtual int do_apply_event(Relay_log_info const *rli) + virtual int do_apply_event(rpl_group_info *rgi) { return 0; /* Default implementation does nothing */ } @@ -1346,7 +1491,7 @@ protected: 1). Observe that handler errors are returned by the do_apply_event() function, and not by this one. */ - virtual int do_update_pos(Relay_log_info *rli); + virtual int do_update_pos(rpl_group_info *rgi); /** @@ -1378,7 +1523,7 @@ protected: The event shall be skipped because the slave skip counter was non-zero. The caller shall decrease the counter by one. */ - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); #endif }; @@ -1836,7 +1981,7 @@ public: bool using_trans, bool direct, bool suppress_use, int error); const char* get_db() { return db; } #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print_query_header(IO_CACHE* file, PRINT_EVENT_INFO* print_event_info); @@ -1853,14 +1998,16 @@ public: my_free(data_buf); } Log_event_type get_type_code() { return QUERY_EVENT; } + static int dummy_event(String *packet, ulong ev_offset, enum enum_binlog_checksum_alg checksum_alg); + static int begin_event(String *packet, ulong ev_offset, enum enum_binlog_checksum_alg checksum_alg); #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); - virtual bool write_post_header_for_derived(IO_CACHE* file) { return FALSE; } + bool write(); + virtual bool write_post_header_for_derived() { return FALSE; } #endif bool is_valid() const { return query != 0; } /* - Returns number of bytes additionaly written to post header by derived + Returns number of bytes additionally written to post header by derived events (so far it is only Execute_load_query event). */ virtual ulong get_post_header_size_for_derived() { return 0; } @@ -1868,13 +2015,14 @@ public: public: /* !!! Public in this patch to allow old usage */ #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); - virtual int do_apply_event(Relay_log_info const *rli); - virtual int do_update_pos(Relay_log_info *rli); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); + virtual int do_apply_event(rpl_group_info *rgi); - int do_apply_event(Relay_log_info const *rli, + int do_apply_event(rpl_group_info *rgi, const char *query_arg, uint32 q_len_arg); + static bool peek_is_commit_rollback(const char *event_start, + size_t event_len, enum enum_binlog_checksum_alg checksum_alg); #endif /* HAVE_REPLICATION */ /* If true, the event always be applied by slave SQL thread or be printed by @@ -1898,99 +2046,48 @@ public: /* !!! Public in this patch to allow old usage */ !strncasecmp(query, "SAVEPOINT", 9) || !strncasecmp(query, "ROLLBACK", 8); } + bool is_begin() { return !strcmp(query, "BEGIN"); } + bool is_commit() { return !strcmp(query, "COMMIT"); } + bool is_rollback() { return !strcmp(query, "ROLLBACK"); } }; -#ifdef HAVE_REPLICATION - -/** - @class Slave_log_event - - Note that this class is currently not used at all; no code writes a - @c Slave_log_event (though some code in @c repl_failsafe.cc reads @c - Slave_log_event). So it's not a problem if this code is not - maintained. - - @section Slave_log_event_binary_format Binary Format - - This event type has no Post-Header. The Body has the following - four components. - - <table> - <caption>Body for Slave_log_event</caption> - - <tr> - <th>Name</th> - <th>Format</th> - <th>Description</th> - </tr> - - <tr> - <td>master_pos</td> - <td>8 byte integer</td> - <td>???TODO - </td> - </tr> - - <tr> - <td>master_port</td> - <td>2 byte integer</td> - <td>???TODO</td> - </tr> - - <tr> - <td>master_host</td> - <td>null-terminated string</td> - <td>???TODO</td> - </tr> - - <tr> - <td>master_log</td> - <td>null-terminated string</td> - <td>???TODO</td> - </tr> - </table> -*/ -class Slave_log_event: public Log_event +/***************************************************************************** + sql_ex_info struct + ****************************************************************************/ +struct sql_ex_info { -protected: - char* mem_pool; - void init_from_mem_pool(int data_size); -public: - my_off_t master_pos; - char* master_host; - char* master_log; - int master_host_len; - int master_log_len; - uint16 master_port; - -#ifdef MYSQL_SERVER - Slave_log_event(THD* thd_arg, Relay_log_info* rli); - void pack_info(THD *thd, Protocol* protocol); -#else - void print(FILE* file, PRINT_EVENT_INFO* print_event_info); -#endif - - Slave_log_event(const char* buf, - uint event_len, - const Format_description_log_event *description_event); - ~Slave_log_event(); - int get_data_size(); - bool is_valid() const { return master_host != 0; } - Log_event_type get_type_code() { return SLAVE_EVENT; } -#ifdef MYSQL_SERVER - bool write(IO_CACHE* file); -#endif + sql_ex_info() {} /* Remove gcc warning */ + const char* field_term; + const char* enclosed; + const char* line_term; + const char* line_start; + const char* escaped; + int cached_new_format; + uint8 field_term_len,enclosed_len,line_term_len,line_start_len, escaped_len; + char opt_flags; + char empty_flags; -private: -#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const* rli); -#endif + // store in new format even if old is possible + void force_new_format() { cached_new_format = 1;} + int data_size() + { + return (new_format() ? + field_term_len + enclosed_len + line_term_len + + line_start_len + escaped_len + 6 : 7); + } + bool write_data(Log_event_writer *writer); + const char* init(const char* buf, const char* buf_end, bool use_new_format); + bool new_format() + { + return ((cached_new_format != -1) ? cached_new_format : + (cached_new_format=(field_term_len > 1 || + enclosed_len > 1 || + line_term_len > 1 || line_start_len > 1 || + escaped_len > 1))); + } }; -#endif /* HAVE_REPLICATION */ - - /** @class Load_log_event @@ -2262,7 +2359,7 @@ public: Name_resolution_context *context); const char* get_db() { return db; } #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); @@ -2284,8 +2381,8 @@ public: return sql_ex.new_format() ? NEW_LOAD_EVENT: LOAD_EVENT; } #ifdef MYSQL_SERVER - bool write_data_header(IO_CACHE* file); - bool write_data_body(IO_CACHE* file); + bool write_data_header(); + bool write_data_body(); #endif bool is_valid() const { return table_name != 0; } int get_data_size() @@ -2297,12 +2394,12 @@ public: public: /* !!! Public in this patch to allow old usage */ #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const* rli) + virtual int do_apply_event(rpl_group_info *rgi) { - return do_apply_event(thd->slave_net,rli,0); + return do_apply_event(thd->slave_net,rgi,0); } - int do_apply_event(NET *net, Relay_log_info const *rli, + int do_apply_event(NET *net, rpl_group_info *rgi, bool use_rli_only_for_errors); #endif }; @@ -2359,7 +2456,7 @@ public: #ifdef MYSQL_SERVER Start_log_event_v3(); #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else Start_log_event_v3() {} @@ -2370,8 +2467,10 @@ public: const Format_description_log_event* description_event); ~Start_log_event_v3() {} Log_event_type get_type_code() { return START_EVENT_V3;} + my_off_t get_header_len(my_off_t l __attribute__((unused))) + { return LOG_EVENT_MINIMAL_HEADER_LEN; } #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); #endif bool is_valid() const { return server_version[0] != 0; } int get_data_size() @@ -2381,14 +2480,14 @@ public: protected: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info*) + virtual int do_apply_event(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info*) { /* Events from ourself should be skipped, but they should not decrease the slave skip counter. */ - if (this->server_id == ::server_id) + if (this->server_id == global_system_variables.server_id) return Log_event::EVENT_SKIP_IGNORE; else return Log_event::EVENT_SKIP_NOT; @@ -2396,6 +2495,73 @@ protected: #endif }; +/** + @class Start_encryption_log_event + + Start_encryption_log_event marks the beginning of encrypted data (all events + after this event are encrypted). + + It contains the cryptographic scheme used for the encryption as well as any + data required to decrypt (except the actual key). + + For binlog cryptoscheme 1: key version, and nonce for iv generation. +*/ +class Start_encryption_log_event : public Log_event +{ +public: +#ifdef MYSQL_SERVER + Start_encryption_log_event(uint crypto_scheme_arg, uint key_version_arg, + const uchar* nonce_arg) + : crypto_scheme(crypto_scheme_arg), key_version(key_version_arg) + { + cache_type = EVENT_NO_CACHE; + DBUG_ASSERT(crypto_scheme == 1); + memcpy(nonce, nonce_arg, BINLOG_NONCE_LENGTH); + } + + bool write_data_body() + { + uchar scheme_buf= crypto_scheme; + uchar key_version_buf[BINLOG_KEY_VERSION_LENGTH]; + int4store(key_version_buf, key_version); + return write_data(&scheme_buf, sizeof(scheme_buf)) || + write_data(key_version_buf, sizeof(key_version_buf)) || + write_data(nonce, BINLOG_NONCE_LENGTH); + } +#else + void print(FILE* file, PRINT_EVENT_INFO* print_event_info); +#endif + + Start_encryption_log_event( + const char* buf, uint event_len, + const Format_description_log_event* description_event); + + bool is_valid() const { return crypto_scheme == 1; } + + Log_event_type get_type_code() { return START_ENCRYPTION_EVENT; } + + int get_data_size() + { + return BINLOG_CRYPTO_SCHEME_LENGTH + BINLOG_KEY_VERSION_LENGTH + + BINLOG_NONCE_LENGTH; + } + + uint crypto_scheme; + uint key_version; + uchar nonce[BINLOG_NONCE_LENGTH]; + +protected: +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) + virtual int do_apply_event(rpl_group_info* rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info* rgi) + { + return Log_event::EVENT_SKIP_NOT; + } +#endif + +}; + /** @class Format_description_log_event @@ -2441,7 +2607,7 @@ public: } Log_event_type get_type_code() { return FORMAT_DESCRIPTION_EVENT;} #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); #endif bool header_is_valid() const { @@ -2473,13 +2639,24 @@ public: return FORMAT_DESCRIPTION_HEADER_LEN; } + Binlog_crypt_data crypto_data; + bool start_decryption(Start_encryption_log_event* sele); + void copy_crypto_data(const Format_description_log_event* o) + { + crypto_data= o->crypto_data; + } + void reset_crypto() + { + crypto_data.scheme= 0; + } + void calc_server_version_split(); static bool is_version_before_checksum(const master_version_split *version_split); protected: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); - virtual int do_update_pos(Relay_log_info *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual int do_apply_event(rpl_group_info *rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); #endif }; @@ -2537,7 +2714,7 @@ Intvar_log_event(THD* thd_arg,uchar type_arg, ulonglong val_arg, cache_type= Log_event::EVENT_NO_CACHE; } #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); @@ -2550,15 +2727,16 @@ Intvar_log_event(THD* thd_arg,uchar type_arg, ulonglong val_arg, const char* get_var_type_name(); int get_data_size() { return 9; /* sizeof(type) + sizeof(val) */;} #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); #endif bool is_valid() const { return 1; } + bool is_part_of_group() { return 1; } private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); - virtual int do_update_pos(Relay_log_info *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual int do_apply_event(rpl_group_info *rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); #endif }; @@ -2617,7 +2795,7 @@ class Rand_log_event: public Log_event cache_type= Log_event::EVENT_NO_CACHE; } #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); @@ -2629,15 +2807,16 @@ class Rand_log_event: public Log_event Log_event_type get_type_code() { return RAND_EVENT;} int get_data_size() { return 16; /* sizeof(ulonglong) * 2*/ } #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); #endif bool is_valid() const { return 1; } + bool is_part_of_group() { return 1; } private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); - virtual int do_update_pos(Relay_log_info *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual int do_apply_event(rpl_group_info *rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); #endif }; @@ -2666,7 +2845,7 @@ class Xid_log_event: public Log_event cache_type= Log_event::EVENT_NO_CACHE; } #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); @@ -2678,14 +2857,14 @@ class Xid_log_event: public Log_event Log_event_type get_type_code() { return XID_EVENT;} int get_data_size() { return sizeof(xid); } #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); #endif bool is_valid() const { return 1; } private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); - enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual int do_apply_event(rpl_group_info *rgi); + enum_skip_reason do_shall_skip(rpl_group_info *rgi); #endif }; @@ -2729,7 +2908,7 @@ public: if (direct) cache_type= Log_event::EVENT_NO_CACHE; } - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); #endif @@ -2739,7 +2918,7 @@ public: ~User_var_log_event() {} Log_event_type get_type_code() { return USER_VAR_EVENT;} #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); /* Getter and setter for deferred User-event. Returns true if the event is not applied directly @@ -2753,12 +2932,13 @@ public: void set_deferred(query_id_t qid) { deferred= true; query_id= qid; } #endif bool is_valid() const { return name != 0; } + bool is_part_of_group() { return 1; } private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); - virtual int do_update_pos(Relay_log_info *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual int do_apply_event(rpl_group_info *rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); #endif }; @@ -2791,14 +2971,14 @@ public: private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_update_pos(Relay_log_info *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli) + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi) { /* Events from ourself should be skipped, but they should not decrease the slave skip counter. */ - if (this->server_id == ::server_id) + if (this->server_id == global_system_variables.server_id) return Log_event::EVENT_SKIP_IGNORE; else return Log_event::EVENT_SKIP_NOT; @@ -2871,7 +3051,7 @@ public: uint ident_len_arg, ulonglong pos_arg, uint flags); #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); @@ -2885,20 +3065,298 @@ public: my_free((void*) new_log_ident); } Log_event_type get_type_code() { return ROTATE_EVENT;} + my_off_t get_header_len(my_off_t l __attribute__((unused))) + { return LOG_EVENT_MINIMAL_HEADER_LEN; } int get_data_size() { return ident_len + ROTATE_HEADER_LEN;} bool is_valid() const { return new_log_ident != 0; } #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); #endif private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_update_pos(Relay_log_info *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); +#endif +}; + + +class Binlog_checkpoint_log_event: public Log_event +{ +public: + char *binlog_file_name; + uint binlog_file_len; + +#ifdef MYSQL_SERVER + Binlog_checkpoint_log_event(const char *binlog_file_name_arg, + uint binlog_file_len_arg); +#ifdef HAVE_REPLICATION + void pack_info(Protocol *protocol); +#endif +#else + void print(FILE *file, PRINT_EVENT_INFO *print_event_info); +#endif + Binlog_checkpoint_log_event(const char *buf, uint event_len, + const Format_description_log_event *description_event); + ~Binlog_checkpoint_log_event() { my_free(binlog_file_name); } + Log_event_type get_type_code() { return BINLOG_CHECKPOINT_EVENT;} + int get_data_size() { return binlog_file_len + BINLOG_CHECKPOINT_HEADER_LEN;} + bool is_valid() const { return binlog_file_name != 0; } +#ifdef MYSQL_SERVER + bool write(); + enum_skip_reason do_shall_skip(rpl_group_info *rgi); +#endif +}; + + +/** + @class Gtid_log_event + + This event is logged as part of every event group to give the global + transaction id (GTID) of that group. + + It replaces the BEGIN query event used in earlier versions to begin most + event groups, but is also used for events that used to be stand-alone. + + @section Gtid_log_event_binary_format Binary Format + + The binary format for Gtid_log_event has 6 extra reserved bytes to make the + length a total of 19 byte (+ 19 bytes of header in common with all events). + This is just the minimal size for a BEGIN query event, which makes it easy + to replace this event with such BEGIN event to remain compatible with old + slave servers. + + <table> + <caption>Post-Header</caption> + + <tr> + <th>Name</th> + <th>Format</th> + <th>Description</th> + </tr> + + <tr> + <td>seq_no</td> + <td>8 byte unsigned integer</td> + <td>increasing id within one server_id. Starts at 1, holes in the sequence + may occur</td> + </tr> + + <tr> + <td>domain_id</td> + <td>4 byte unsigned integer</td> + <td>Replication domain id, identifying independent replication streams></td> + </tr> + + <tr> + <td>flags</td> + <td>1 byte bitfield</td> + <td>Bit 0 set indicates stand-alone event (no terminating COMMIT)</td> + <td>Bit 1 set indicates group commit, and that commit id exists</td> + <td>Bit 2 set indicates a transactional event group (can be safely rolled + back).</td> + <td>Bit 3 set indicates that user allowed optimistic parallel apply (the + @@SESSION.replicate_allow_parallel value was true at commit).</td> + <td>Bit 4 set indicates that this transaction encountered a row (or other) + lock wait during execution.</td> + </tr> + + <tr> + <td>Reserved (no group commit) / commit id (group commit) (see flags bit 1)</td> + <td>6 bytes / 8 bytes</td> + <td>Reserved bytes, set to 0. Maybe be used for future expansion (no + group commit). OR commit id, same for all GTIDs in the same group + commit (see flags bit 1).</td> + </tr> + </table> + + The Body of Gtid_log_event is empty. The total event size is 19 bytes + + the normal 19 bytes common-header. +*/ + +class Gtid_log_event: public Log_event +{ +public: + uint64 seq_no; + uint64 commit_id; + uint32 domain_id; + uchar flags2; + + /* Flags2. */ + + /* FL_STANDALONE is set when there is no terminating COMMIT event. */ + static const uchar FL_STANDALONE= 1; + /* + FL_GROUP_COMMIT_ID is set when event group is part of a group commit on the + master. Groups with same commit_id are part of the same group commit. + */ + static const uchar FL_GROUP_COMMIT_ID= 2; + /* + FL_TRANSACTIONAL is set for an event group that can be safely rolled back + (no MyISAM, eg.). + */ + static const uchar FL_TRANSACTIONAL= 4; + /* + FL_ALLOW_PARALLEL reflects the (negation of the) value of + @@SESSION.skip_parallel_replication at the time of commit. + */ + static const uchar FL_ALLOW_PARALLEL= 8; + /* + FL_WAITED is set if a row lock wait (or other wait) is detected during the + execution of the transaction. + */ + static const uchar FL_WAITED= 16; + /* FL_DDL is set for event group containing DDL. */ + static const uchar FL_DDL= 32; + +#ifdef MYSQL_SERVER + Gtid_log_event(THD *thd_arg, uint64 seq_no, uint32 domain_id, bool standalone, + uint16 flags, bool is_transactional, uint64 commit_id); +#ifdef HAVE_REPLICATION + void pack_info(Protocol *protocol); + virtual int do_apply_event(rpl_group_info *rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); +#endif +#else + void print(FILE *file, PRINT_EVENT_INFO *print_event_info); +#endif + Gtid_log_event(const char *buf, uint event_len, + const Format_description_log_event *description_event); + ~Gtid_log_event() { } + Log_event_type get_type_code() { return GTID_EVENT; } + int get_data_size() + { + return GTID_HEADER_LEN + ((flags2 & FL_GROUP_COMMIT_ID) ? 2 : 0); + } + bool is_valid() const { return seq_no != 0; } +#ifdef MYSQL_SERVER + bool write(); + static int make_compatible_event(String *packet, bool *need_dummy_event, + ulong ev_offset, enum enum_binlog_checksum_alg checksum_alg); + static bool peek(const char *event_start, size_t event_len, + enum enum_binlog_checksum_alg checksum_alg, + uint32 *domain_id, uint32 *server_id, uint64 *seq_no, + uchar *flags2, const Format_description_log_event *fdev); #endif }; +/** + @class Gtid_list_log_event + + This event is logged at the start of every binlog file to record the + current replication state: the last global transaction id (GTID) applied + on the server within each replication domain. + + It consists of a list of GTIDs, one for each replication domain ever seen + on the server. + + @section Gtid_list_log_event_binary_format Binary Format + + <table> + <caption>Post-Header</caption> + + <tr> + <th>Name</th> + <th>Format</th> + <th>Description</th> + </tr> + + <tr> + <td>count</td> + <td>4 byte unsigned integer</td> + <td>The lower 28 bits are the number of GTIDs. The upper 4 bits are + flags bits.</td> + </tr> + </table> + + <table> + <caption>Body</caption> + + <tr> + <th>Name</th> + <th>Format</th> + <th>Description</th> + </tr> + + <tr> + <td>domain_id</td> + <td>4 byte unsigned integer</td> + <td>Replication domain id of one GTID</td> + </tr> + + <tr> + <td>server_id</td> + <td>4 byte unsigned integer</td> + <td>Server id of one GTID</td> + </tr> + + <tr> + <td>seq_no</td> + <td>8 byte unsigned integer</td> + <td>sequence number of one GTID</td> + </tr> + </table> + + The three elements in the body repeat COUNT times to form the GTID list. + + At the time of writing, only one flag bit is in use. + + Bit 28 of `count' is used for flag FLAG_UNTIL_REACHED, which is sent in a + Gtid_list event from the master to the slave to indicate that the START + SLAVE UNTIL master_gtid_pos=xxx condition has been reached. (This flag is + only sent in "fake" events generated on the fly, it is not written into + the binlog). +*/ + +class Gtid_list_log_event: public Log_event +{ +public: + uint32 count; + uint32 gl_flags; + struct rpl_gtid *list; + uint64 *sub_id_list; + + static const uint element_size= 4+4+8; + static const uint32 FLAG_UNTIL_REACHED= (1<<28); + static const uint32 FLAG_IGN_GTIDS= (1<<29); + +#ifdef MYSQL_SERVER + Gtid_list_log_event(rpl_binlog_state *gtid_set, uint32 gl_flags); + Gtid_list_log_event(slave_connection_state *gtid_set, uint32 gl_flags); +#ifdef HAVE_REPLICATION + void pack_info(Protocol *protocol); +#endif +#else + void print(FILE *file, PRINT_EVENT_INFO *print_event_info); +#endif + Gtid_list_log_event(const char *buf, uint event_len, + const Format_description_log_event *description_event); + ~Gtid_list_log_event() { my_free(list); my_free(sub_id_list); } + Log_event_type get_type_code() { return GTID_LIST_EVENT; } + int get_data_size() { + /* + Replacing with dummy event, needed for older slaves, requires a minimum + of 6 bytes in the body. + */ + return (count==0 ? + GTID_LIST_HEADER_LEN+2 : GTID_LIST_HEADER_LEN+count*element_size); + } + bool is_valid() const { return list != NULL; } +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) + bool to_packet(String *packet); + bool write(); + virtual int do_apply_event(rpl_group_info *rgi); + enum_skip_reason do_shall_skip(rpl_group_info *rgi); +#endif + static bool peek(const char *event_start, uint32 event_len, + enum enum_binlog_checksum_alg checksum_alg, + rpl_gtid **out_gtid_list, uint32 *out_list_len, + const Format_description_log_event *fdev); +}; + + /* the classes below are for the new LOAD DATA INFILE logging */ /** @@ -2932,7 +3390,7 @@ public: uchar* block_arg, uint block_len_arg, bool using_trans); #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); @@ -2959,18 +3417,18 @@ public: } bool is_valid() const { return inited_from_old || block != 0; } #ifdef MYSQL_SERVER - bool write_data_header(IO_CACHE* file); - bool write_data_body(IO_CACHE* file); + bool write_data_header(); + bool write_data_body(); /* Cut out Create_file extentions and write it as Load event - used on the slave */ - bool write_base(IO_CACHE* file); + bool write_base(); #endif private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); + virtual int do_apply_event(rpl_group_info *rgi); #endif }; @@ -3004,7 +3462,7 @@ public: Append_block_log_event(THD* thd, const char* db_arg, uchar* block_arg, uint block_len_arg, bool using_trans); #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); virtual int get_create_or_append() const; #endif /* HAVE_REPLICATION */ #else @@ -3019,13 +3477,13 @@ public: int get_data_size() { return block_len + APPEND_BLOCK_HEADER_LEN ;} bool is_valid() const { return block != 0; } #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); const char* get_db() { return db; } #endif private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); + virtual int do_apply_event(rpl_group_info *rgi); #endif }; @@ -3045,7 +3503,7 @@ public: #ifdef MYSQL_SERVER Delete_file_log_event(THD* thd, const char* db_arg, bool using_trans); #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); @@ -3060,13 +3518,13 @@ public: int get_data_size() { return DELETE_FILE_HEADER_LEN ;} bool is_valid() const { return file_id != 0; } #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); const char* get_db() { return db; } #endif private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); + virtual int do_apply_event(rpl_group_info *rgi); #endif }; @@ -3086,7 +3544,7 @@ public: #ifdef MYSQL_SERVER Execute_load_log_event(THD* thd, const char* db_arg, bool using_trans); #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); @@ -3100,13 +3558,13 @@ public: int get_data_size() { return EXEC_LOAD_HEADER_LEN ;} bool is_valid() const { return file_id != 0; } #ifdef MYSQL_SERVER - bool write(IO_CACHE* file); + bool write(); const char* get_db() { return db; } #endif private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); + virtual int do_apply_event(rpl_group_info *rgi); #endif }; @@ -3139,7 +3597,7 @@ public: Log_event_type get_type_code() { return BEGIN_LOAD_QUERY_EVENT; } private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); #endif }; @@ -3182,7 +3640,7 @@ public: bool using_trans, bool direct, bool suppress_use, int errcode); #ifdef HAVE_REPLICATION - void pack_info(THD *thd, Protocol* protocol); + void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ #else void print(FILE* file, PRINT_EVENT_INFO* print_event_info); @@ -3200,12 +3658,12 @@ public: ulong get_post_header_size_for_derived(); #ifdef MYSQL_SERVER - bool write_post_header_for_derived(IO_CACHE* file); + bool write_post_header_for_derived(); #endif private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); + virtual int do_apply_event(rpl_group_info *rgi); #endif }; @@ -3219,6 +3677,7 @@ private: class Unknown_log_event: public Log_event { public: + enum { UNKNOWN, ENCRYPTED } what; /* Even if this is an unknown event, we still pass description_event to Log_event's ctor, this way we can extract maximum information from the @@ -3226,8 +3685,10 @@ public: */ Unknown_log_event(const char* buf, const Format_description_log_event *description_event): - Log_event(buf, description_event) + Log_event(buf, description_event), what(UNKNOWN) {} + /* constructor for hopelessly corrupted events */ + Unknown_log_event(): Log_event(), what(ENCRYPTED) {} ~Unknown_log_event() {} void print(FILE* file, PRINT_EVENT_INFO* print_event_info); Log_event_type get_type_code() { return UNKNOWN_EVENT;} @@ -3261,14 +3722,15 @@ public: virtual int get_data_size(); virtual Log_event_type get_type_code(); virtual bool is_valid() const; + virtual bool is_part_of_group() { return 1; } #ifndef MYSQL_CLIENT - virtual bool write_data_header(IO_CACHE*); - virtual bool write_data_body(IO_CACHE*); + virtual bool write_data_header(); + virtual bool write_data_body(); #endif #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) - virtual void pack_info(THD *thd, Protocol*); + virtual void pack_info(Protocol*); #endif #ifdef MYSQL_CLIENT @@ -3277,9 +3739,9 @@ public: #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) private: - virtual int do_apply_event(Relay_log_info const*); - virtual int do_update_pos(Relay_log_info*); - virtual enum_skip_reason do_shall_skip(Relay_log_info*); + virtual int do_apply_event(rpl_group_info *rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info*); #endif private: @@ -3642,7 +4104,9 @@ public: enum { TM_NO_FLAGS = 0U, - TM_BIT_LEN_EXACT_F = (1U << 0) + TM_BIT_LEN_EXACT_F = (1U << 0), + // MariaDB flags (we starts from the other end) + TM_BIT_HAS_TRIGGERS_F= (1U << 14) }; flag_set get_flags(flag_set flag) const { return m_flags & flag; } @@ -3672,17 +4136,18 @@ public: virtual Log_event_type get_type_code() { return TABLE_MAP_EVENT; } virtual bool is_valid() const { return m_memory != NULL; /* we check malloc */ } + virtual bool is_part_of_group() { return 1; } virtual int get_data_size() { return (uint) m_data_size; } #ifdef MYSQL_SERVER virtual int save_field_metadata(); - virtual bool write_data_header(IO_CACHE *file); - virtual bool write_data_body(IO_CACHE *file); + virtual bool write_data_header(); + virtual bool write_data_body(); virtual const char *get_db() { return m_dbnam; } #endif #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual void pack_info(THD *thd, Protocol *protocol); + virtual void pack_info(Protocol *protocol); #endif #ifdef MYSQL_CLIENT @@ -3692,9 +4157,9 @@ public: private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); - virtual int do_update_pos(Relay_log_info *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual int do_apply_event(rpl_group_info *rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); #endif #ifdef MYSQL_SERVER @@ -3793,8 +4258,11 @@ public: void clear_flags(flag_set flags_arg) { m_flags &= ~flags_arg; } flag_set get_flags(flag_set flags_arg) const { return m_flags & flags_arg; } + Log_event_type get_type_code() { return m_type; } /* Specific type (_V1 etc) */ + virtual Log_event_type get_general_type_code() = 0; /* General rows op type, no version */ + #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual void pack_info(THD *thd, Protocol *protocol); + virtual void pack_info(Protocol *protocol); #endif #ifdef MYSQL_CLIENT @@ -3819,12 +4287,61 @@ public: virtual int get_data_size(); MY_BITMAP const *get_cols() const { return &m_cols; } + MY_BITMAP const *get_cols_ai() const { return &m_cols_ai; } size_t get_width() const { return m_width; } ulong get_table_id() const { return m_table_id; } +#if defined(MYSQL_SERVER) + /* + This member function compares the table's read/write_set + with this event's m_cols and m_cols_ai. Comparison takes + into account what type of rows event is this: Delete, Write or + Update, therefore it uses the correct m_cols[_ai] according + to the event type code. + + Note that this member function should only be called for the + following events: + - Delete_rows_log_event + - Write_rows_log_event + - Update_rows_log_event + + @param[IN] table The table to compare this events bitmaps + against. + + @return TRUE if sets match, FALSE otherwise. (following + bitmap_cmp return logic). + + */ + bool read_write_bitmaps_cmp(TABLE *table) + { + bool res= FALSE; + + switch (get_general_type_code()) + { + case DELETE_ROWS_EVENT: + res= bitmap_cmp(get_cols(), table->read_set); + break; + case UPDATE_ROWS_EVENT: + res= (bitmap_cmp(get_cols(), table->read_set) && + bitmap_cmp(get_cols_ai(), table->rpl_write_set)); + break; + case WRITE_ROWS_EVENT: + res= bitmap_cmp(get_cols(), table->rpl_write_set); + break; + default: + /* + We should just compare bitmaps for Delete, Write + or Update rows events. + */ + DBUG_ASSERT(0); + } + return res; + } +#endif + #ifdef MYSQL_SERVER - virtual bool write_data_header(IO_CACHE *file); - virtual bool write_data_body(IO_CACHE *file); + virtual bool write_data_header(); + virtual bool write_data_body(); virtual const char *get_db() { return m_table->s->db.str; } #endif /* @@ -3837,9 +4354,16 @@ public: { return m_rows_buf && m_cols.bitmap; } + bool is_part_of_group() { return get_flags(STMT_END_F) != 0; } uint m_row_count; /* The number of rows added to the event */ + const uchar* get_extra_row_data() const { return m_extra_row_data; } + +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) + virtual uint8 get_trg_event_map()= 0; +#endif + protected: /* The constructors are protected since you're supposed to inherit @@ -3847,10 +4371,10 @@ protected: */ #ifdef MYSQL_SERVER Rows_log_event(THD*, TABLE*, ulong table_id, - MY_BITMAP const *cols, bool is_transactional); + MY_BITMAP const *cols, bool is_transactional, + Log_event_type event_type); #endif Rows_log_event(const char *row_data, uint event_len, - Log_event_type event_type, const Format_description_log_event *description_event); #ifdef MYSQL_CLIENT @@ -3888,6 +4412,12 @@ protected: flag_set m_flags; /* Flags for row-level events */ + Log_event_type m_type; /* Actual event type */ + + uchar *m_extra_row_data; /* Pointer to extra row data if any */ + /* If non null, first byte is length */ + + /* helper functions */ #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) @@ -3896,20 +4426,35 @@ protected: uchar *m_key; /* Buffer to keep key value during searches */ KEY *m_key_info; /* Pointer to KEY info for m_key_nr */ uint m_key_nr; /* Key number */ + bool master_had_triggers; /* set after tables opening */ int find_key(); // Find a best key to use in find_row() - int find_row(const Relay_log_info *const); - int write_row(const Relay_log_info *const, const bool); + int find_row(rpl_group_info *); + int write_row(rpl_group_info *, const bool); + + // Unpack the current row into m_table->record[0], but with + // a different columns bitmap. + int unpack_current_row(rpl_group_info *rgi, MY_BITMAP const *cols) + { + DBUG_ASSERT(m_table); + + ASSERT_OR_RETURN_ERROR(m_curr_row <= m_rows_end, HA_ERR_CORRUPT_EVENT); + return ::unpack_row(rgi, m_table, m_width, m_curr_row, cols, + &m_curr_row_end, &m_master_reclength, m_rows_end); + } // Unpack the current row into m_table->record[0] - int unpack_current_row(const Relay_log_info *const rli) + int unpack_current_row(rpl_group_info *rgi) { DBUG_ASSERT(m_table); - ASSERT_OR_RETURN_ERROR(m_curr_row < m_rows_end, HA_ERR_CORRUPT_EVENT); - return ::unpack_row(rli, m_table, m_width, m_curr_row, &m_cols, + ASSERT_OR_RETURN_ERROR(m_curr_row <= m_rows_end, HA_ERR_CORRUPT_EVENT); + return ::unpack_row(rgi, m_table, m_width, m_curr_row, &m_cols, &m_curr_row_end, &m_master_reclength, m_rows_end); } + bool process_triggers(trg_event_type event, + trg_action_time_type time_type, + bool old_row_is_record1); /** Helper function to check whether there is an auto increment @@ -3929,9 +4474,9 @@ protected: private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); - virtual int do_update_pos(Relay_log_info *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual int do_apply_event(rpl_group_info *rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); /* Primitive to prepare for a sequence of row executions. @@ -3982,7 +4527,7 @@ private: 0 if execution succeeded, 1 if execution failed. */ - virtual int do_exec_row(const Relay_log_info *const rli) = 0; + virtual int do_exec_row(rpl_group_info *rli) = 0; #endif /* defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) */ friend class Old_rows_log_event; @@ -4007,8 +4552,8 @@ public: }; #if defined(MYSQL_SERVER) - Write_rows_log_event(THD*, TABLE*, ulong table_id, - MY_BITMAP const *cols, bool is_transactional); + Write_rows_log_event(THD*, TABLE*, ulong table_id, + bool is_transactional); #endif #ifdef HAVE_REPLICATION Write_rows_log_event(const char *buf, uint event_len, @@ -4017,19 +4562,20 @@ public: #if defined(MYSQL_SERVER) static bool binlog_row_logging_function(THD *thd, TABLE *table, bool is_transactional, - MY_BITMAP *cols, - uint fields, const uchar *before_record __attribute__((unused)), const uchar *after_record) { - return thd->binlog_write_row(table, is_transactional, - cols, fields, after_record); + return thd->binlog_write_row(table, is_transactional, after_record); } #endif +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) + uint8 get_trg_event_map(); +#endif + private: - virtual Log_event_type get_type_code() { return (Log_event_type)TYPE_CODE; } + virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; } #ifdef MYSQL_CLIENT void print(FILE *file, PRINT_EVENT_INFO *print_event_info); @@ -4038,7 +4584,7 @@ private: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) virtual int do_before_row_operations(const Slave_reporting_capability *const); virtual int do_after_row_operations(const Slave_reporting_capability *const,int); - virtual int do_exec_row(const Relay_log_info *const); + virtual int do_exec_row(rpl_group_info *); #endif }; @@ -4066,12 +4612,6 @@ public: #ifdef MYSQL_SERVER Update_rows_log_event(THD*, TABLE*, ulong table_id, - MY_BITMAP const *cols_bi, - MY_BITMAP const *cols_ai, - bool is_transactional); - - Update_rows_log_event(THD*, TABLE*, ulong table_id, - MY_BITMAP const *cols, bool is_transactional); void init(MY_BITMAP const *cols); @@ -4087,13 +4627,11 @@ public: #ifdef MYSQL_SERVER static bool binlog_row_logging_function(THD *thd, TABLE *table, bool is_transactional, - MY_BITMAP *cols, - uint fields, const uchar *before_record, const uchar *after_record) { return thd->binlog_update_row(table, is_transactional, - cols, fields, before_record, after_record); + before_record, after_record); } #endif @@ -4102,8 +4640,12 @@ public: return Rows_log_event::is_valid() && m_cols_ai.bitmap; } +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) + uint8 get_trg_event_map(); +#endif + protected: - virtual Log_event_type get_type_code() { return (Log_event_type)TYPE_CODE; } + virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; } #ifdef MYSQL_CLIENT void print(FILE *file, PRINT_EVENT_INFO *print_event_info); @@ -4112,7 +4654,7 @@ protected: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) virtual int do_before_row_operations(const Slave_reporting_capability *const); virtual int do_after_row_operations(const Slave_reporting_capability *const,int); - virtual int do_exec_row(const Relay_log_info *const); + virtual int do_exec_row(rpl_group_info *); #endif /* defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) */ }; @@ -4146,8 +4688,7 @@ public: }; #ifdef MYSQL_SERVER - Delete_rows_log_event(THD*, TABLE*, ulong, - MY_BITMAP const *cols, bool is_transactional); + Delete_rows_log_event(THD*, TABLE*, ulong, bool is_transactional); #endif #ifdef HAVE_REPLICATION Delete_rows_log_event(const char *buf, uint event_len, @@ -4156,19 +4697,21 @@ public: #ifdef MYSQL_SERVER static bool binlog_row_logging_function(THD *thd, TABLE *table, bool is_transactional, - MY_BITMAP *cols, - uint fields, const uchar *before_record, const uchar *after_record __attribute__((unused))) { return thd->binlog_delete_row(table, is_transactional, - cols, fields, before_record); + before_record); } #endif - + +#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) + uint8 get_trg_event_map(); +#endif + protected: - virtual Log_event_type get_type_code() { return (Log_event_type)TYPE_CODE; } + virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; } #ifdef MYSQL_CLIENT void print(FILE *file, PRINT_EVENT_INFO *print_event_info); @@ -4177,7 +4720,7 @@ protected: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) virtual int do_before_row_operations(const Slave_reporting_capability *const); virtual int do_after_row_operations(const Slave_reporting_capability *const,int); - virtual int do_exec_row(const Relay_log_info *const); + virtual int do_exec_row(rpl_group_info *); #endif }; @@ -4241,7 +4784,16 @@ public: { DBUG_ENTER("Incident_log_event::Incident_log_event"); DBUG_PRINT("enter", ("m_incident: %d", m_incident)); - m_message= msg; + m_message.str= NULL; + m_message.length= 0; + if (!(m_message.str= (char*) my_malloc(msg.length+1, MYF(MY_WME)))) + { + /* Mark this event invalid */ + m_incident= INCIDENT_NONE; + DBUG_VOID_RETURN; + } + strmake(m_message.str, msg.str, msg.length); + m_message.length= msg.length; set_direct_logging(); /* Replicate the incident irregardless of @@skip_replication. */ flags&= ~LOG_EVENT_SKIP_REPLICATION_F; @@ -4250,7 +4802,10 @@ public: #endif #ifdef MYSQL_SERVER - void pack_info(THD *thd, Protocol*); + void pack_info(Protocol*); + + virtual bool write_data_header(); + virtual bool write_data_body(); #endif Incident_log_event(const char *buf, uint event_len, @@ -4263,12 +4818,9 @@ public: #endif #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); + virtual int do_apply_event(rpl_group_info *rgi); #endif - virtual bool write_data_header(IO_CACHE *file); - virtual bool write_data_body(IO_CACHE *file); - virtual Log_event_type get_type_code() { return INCIDENT_EVENT; } virtual bool is_valid() const @@ -4286,14 +4838,75 @@ private: LEX_STRING m_message; }; +/** + @class Ignorable_log_event + + Base class for ignorable log events. Events deriving from + this class can be safely ignored by slaves that cannot + recognize them. Newer slaves, will be able to read and + handle them. This has been designed to be an open-ended + architecture, so adding new derived events shall not harm + the old slaves that support ignorable log event mechanism + (they will just ignore unrecognized ignorable events). + + @note The only thing that makes an event ignorable is that it has + the LOG_EVENT_IGNORABLE_F flag set. It is not strictly necessary + that ignorable event types derive from Ignorable_log_event; they may + just as well derive from Log_event and pass LOG_EVENT_IGNORABLE_F as + argument to the Log_event constructor. +**/ + +class Ignorable_log_event : public Log_event { +public: + int number; + const char *description; + +#ifndef MYSQL_CLIENT + Ignorable_log_event(THD *thd_arg) + :Log_event(thd_arg, LOG_EVENT_IGNORABLE_F, FALSE), + number(0), description("internal") + { + DBUG_ENTER("Ignorable_log_event::Ignorable_log_event"); + DBUG_VOID_RETURN; + } +#endif + + Ignorable_log_event(const char *buf, + const Format_description_log_event *descr_event, + const char *event_name); + virtual ~Ignorable_log_event(); + +#ifndef MYSQL_CLIENT + void pack_info(Protocol*); +#endif + +#ifdef MYSQL_CLIENT + virtual void print(FILE *file, PRINT_EVENT_INFO *print_event_info); +#endif + + virtual Log_event_type get_type_code() { return IGNORABLE_LOG_EVENT; } + + virtual bool is_valid() const { return 1; } + + virtual int get_data_size() { return IGNORABLE_HEADER_LEN; } +}; + +#ifdef MYSQL_CLIENT +void copy_cache_to_file_wrapped(FILE *file, + PRINT_EVENT_INFO *print_event_info, + IO_CACHE *body, + bool do_wrap); +#endif + static inline bool copy_event_cache_to_file_and_reinit(IO_CACHE *cache, FILE *file) { - return - my_b_copy_to_file(cache, file) || + return + my_b_copy_all_to_file(cache, file) || reinit_io_cache(cache, WRITE_CACHE, 0, FALSE, TRUE); } + #ifdef MYSQL_SERVER /***************************************************************************** @@ -4329,6 +4942,14 @@ private: uint ident_len; }; +inline int Log_event_writer::write(Log_event *ev) +{ + ev->writer= this; + int res= ev->write(); + IF_DBUG(ev->writer= 0,); // writer must be set before every Log_event::write + return res; +} + /** The function is called by slave applier in case there are active table filtering rules to force gathering events associated @@ -4338,27 +4959,15 @@ private: bool slave_execute_deferred_events(THD *thd); #endif -int append_query_string(THD *thd, CHARSET_INFO *csinfo, - String const *from, String *to); - bool rpl_get_position_info(const char **log_file_name, ulonglong *log_pos, const char **group_relay_log_name, ulonglong *relay_log_pos); -bool event_checksum_test(uchar *buf, ulong event_len, uint8 alg); -uint8 get_checksum_alg(const char* buf, ulong len); +bool event_that_should_be_ignored(const char *buf); +bool event_checksum_test(uchar *buf, ulong event_len, enum_binlog_checksum_alg alg); +enum enum_binlog_checksum_alg get_checksum_alg(const char* buf, ulong len); extern TYPELIB binlog_checksum_typelib; -#ifndef MYSQL_CLIENT -/** - The function is called by slave applier in case there are - active table filtering rules to force gathering events associated - with Query-log-event into an array to execute - them once the fate of the Query is determined for execution. -*/ -bool slave_execute_deferred_events(THD *thd); -#endif - /** @} (end of group Replication) */ diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 3d54ffdf7eb..a6f2ed3f416 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -14,18 +14,17 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #ifndef MYSQL_CLIENT #include "unireg.h" #endif -#include "my_global.h" // REQUIRED by log_event.h > m_string.h > my_bitmap.h #include "log_event.h" #ifndef MYSQL_CLIENT #include "sql_cache.h" // QUERY_CACHE_FLAGS_SIZE #include "sql_base.h" // close_tables_for_reopen #include "key.h" // key_copy #include "lock.h" // mysql_unlock_tables -#include "sql_parse.h" // mysql_reset_thd_for_next_command #include "rpl_rli.h" #include "rpl_utility.h" #endif @@ -37,12 +36,13 @@ // Old implementation of do_apply_event() int -Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info *rli) +Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, rpl_group_info *rgi) { DBUG_ENTER("Old_rows_log_event::do_apply_event(st_relay_log_info*)"); int error= 0; THD *ev_thd= ev->thd; uchar const *row_start= ev->m_rows_buf; + const Relay_log_info *rli= rgi->rli; /* If m_table_id == ~0UL, then we have a dummy event that does not @@ -58,7 +58,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info */ DBUG_ASSERT(ev->get_flags(Old_rows_log_event::STMT_END_F)); - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(ev_thd); + rgi->slave_close_thread_tables(ev_thd); ev_thd->clear_error(); DBUG_RETURN(0); } @@ -68,7 +68,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info do_apply_event(). We still check here to prevent future coding errors. */ - DBUG_ASSERT(rli->sql_thd == ev_thd); + DBUG_ASSERT(rgi->thd == ev_thd); /* If there is no locks taken, this is the first binrow event seen @@ -82,13 +82,14 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info Lock_tables() reads the contents of ev_thd->lex, so they must be initialized. - We also call the mysql_reset_thd_for_next_command(), since this + We also call the THD::reset_for_next_command(), since this is the logical start of the next "statement". Note that this call might reset the value of current_stmt_binlog_format, so we need to do any changes to that value after this function. */ + delete_explain_query(thd->lex); lex_start(ev_thd); - mysql_reset_thd_for_next_command(ev_thd); + ev_thd->reset_for_next_command(); /* This is a row injection, so we flag the "statement" as @@ -98,22 +99,22 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info */ ev_thd->lex->set_stmt_row_injection(); - if (open_and_lock_tables(ev_thd, rli->tables_to_lock, FALSE, 0)) + if (open_and_lock_tables(ev_thd, rgi->tables_to_lock, FALSE, 0)) { - uint actual_error= ev_thd->stmt_da->sql_errno(); + uint actual_error= ev_thd->get_stmt_da()->sql_errno(); if (ev_thd->is_slave_error || ev_thd->is_fatal_error) { /* Error reporting borrowed from Query_log_event with many excessive simplifications (we don't honour --slave-skip-errors) */ - rli->report(ERROR_LEVEL, actual_error, + rli->report(ERROR_LEVEL, actual_error, NULL, "Error '%s' on opening tables", - (actual_error ? ev_thd->stmt_da->message() : + (actual_error ? ev_thd->get_stmt_da()->message() : "unexpected success or fatal error")); ev_thd->is_slave_error= 1; } - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); + rgi->slave_close_thread_tables(thd); DBUG_RETURN(actual_error); } @@ -123,8 +124,8 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info */ { - TABLE_LIST *table_list_ptr= rli->tables_to_lock; - for (uint i=0 ; table_list_ptr&& (i< rli->tables_to_lock_count); + TABLE_LIST *table_list_ptr= rgi->tables_to_lock; + for (uint i=0 ; table_list_ptr&& (i< rgi->tables_to_lock_count); table_list_ptr= table_list_ptr->next_global, i++) { /* @@ -141,11 +142,10 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info RPL_TABLE_LIST *ptr=static_cast<RPL_TABLE_LIST*>(table_list_ptr); DBUG_ASSERT(ptr->m_tabledef_valid); TABLE *conv_table; - if (!ptr->m_tabledef.compatible_with(thd, const_cast<Relay_log_info*>(rli), - ptr->table, &conv_table)) + if (!ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table)) { ev_thd->is_slave_error= 1; - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(ev_thd); + rgi->slave_close_thread_tables(ev_thd); DBUG_RETURN(Old_rows_log_event::ERR_BAD_TABLE_DEF); } DBUG_PRINT("debug", ("Table: %s.%s is compatible with master" @@ -170,8 +170,8 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info Old_rows_log_event, we can invalidate the query cache for the associated table. */ - TABLE_LIST *ptr= rli->tables_to_lock; - for (uint i=0; ptr && (i < rli->tables_to_lock_count); ptr= ptr->next_global, i++) + TABLE_LIST *ptr= rgi->tables_to_lock; + for (uint i=0; ptr && (i < rgi->tables_to_lock_count); ptr= ptr->next_global, i++) { /* Please see comment in log_event.cc-Rows_log_event::do_apply_event() @@ -179,14 +179,14 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info */ if (ptr->parent_l) continue; - const_cast<Relay_log_info*>(rli)->m_table_map.set_table(ptr->table_id, ptr->table); + rgi->m_table_map.set_table(ptr->table_id, ptr->table); } #ifdef HAVE_QUERY_CACHE - query_cache.invalidate_locked_for_write(thd, rli->tables_to_lock); + query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock); #endif } - TABLE* table= const_cast<Relay_log_info*>(rli)->m_table_map.get_table(ev->m_table_id); + TABLE* table= rgi->m_table_map.get_table(ev->m_table_id); if (table) { @@ -222,22 +222,11 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info /* A small test to verify that objects have consistent types */ DBUG_ASSERT(sizeof(ev_thd->variables.option_bits) == sizeof(OPTION_RELAXED_UNIQUE_CHECKS)); - /* - Now we are in a statement and will stay in a statement until we - see a STMT_END_F. - - We set this flag here, before actually applying any rows, in - case the SQL thread is stopped and we need to detect that we're - inside a statement and halting abruptly might cause problems - when restarting. - */ - const_cast<Relay_log_info*>(rli)->set_flag(Relay_log_info::IN_STMT); - error= do_before_row_operations(table); while (error == 0 && row_start < ev->m_rows_end) { uchar const *row_end= NULL; - if ((error= do_prepare_row(ev_thd, rli, table, row_start, &row_end))) + if ((error= do_prepare_row(ev_thd, rgi, table, row_start, &row_end))) break; // We should perform the after-row operation even in // the case of error @@ -261,10 +250,10 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info break; default: - rli->report(ERROR_LEVEL, ev_thd->stmt_da->sql_errno(), + rli->report(ERROR_LEVEL, ev_thd->get_stmt_da()->sql_errno(), NULL, "Error in %s event: row application failed. %s", ev->get_type_str(), - ev_thd->is_error() ? ev_thd->stmt_da->message() : ""); + ev_thd->is_error() ? ev_thd->get_stmt_da()->message() : ""); thd->is_slave_error= 1; break; } @@ -277,13 +266,13 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info } if (error) - { /* error has occured during the transaction */ - rli->report(ERROR_LEVEL, ev_thd->stmt_da->sql_errno(), + { /* error has occurred during the transaction */ + rli->report(ERROR_LEVEL, ev_thd->get_stmt_da()->sql_errno(), NULL, "Error in %s event: error during transaction execution " "on table %s.%s. %s", ev->get_type_str(), table->s->db.str, table->s->table_name.str, - ev_thd->is_error() ? ev_thd->stmt_da->message() : ""); + ev_thd->is_error() ? ev_thd->get_stmt_da()->message() : ""); /* If one day we honour --skip-slave-errors in row-based replication, and @@ -297,7 +286,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info rollback at the caller along with sbr. */ ev_thd->reset_current_stmt_binlog_format_row(); - const_cast<Relay_log_info*>(rli)->cleanup_context(ev_thd, error); + rgi->cleanup_context(ev_thd, error); ev_thd->is_slave_error= 1; DBUG_RETURN(error); } @@ -329,50 +318,7 @@ last_uniq_key(TABLE *table, uint keyno) */ static bool record_compare(TABLE *table) { - /* - Need to set the X bit and the filler bits in both records since - there are engines that do not set it correctly. - - In addition, since MyISAM checks that one hasn't tampered with the - record, it is necessary to restore the old bytes into the record - after doing the comparison. - - TODO[record format ndb]: Remove it once NDB returns correct - records. Check that the other engines also return correct records. - */ - bool result= FALSE; - uchar saved_x[2]= {0, 0}, saved_filler[2]= {0, 0}; - - if (table->s->null_bytes > 0) - { - for (int i = 0 ; i < 2 ; ++i) - { - /* - If we have an X bit then we need to take care of it. - */ - if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD)) - { - saved_x[i]= table->record[i][0]; - table->record[i][0]|= 1U; - } - - /* - If (last_null_bit_pos == 0 && null_bytes > 1), then: - - X bit (if any) + N nullable fields + M Field_bit fields = 8 bits - - Ie, the entire byte is used. - */ - if (table->s->last_null_bit_pos > 0) - { - saved_filler[i]= table->record[i][table->s->null_bytes - 1]; - table->record[i][table->s->null_bytes - 1]|= - 256U - (1U << table->s->last_null_bit_pos); - } - } - } - if (table->s->blob_fields + table->s->varchar_fields == 0) { result= cmp_record(table,record[1]); @@ -399,24 +345,6 @@ static bool record_compare(TABLE *table) } record_compare_exit: - /* - Restore the saved bytes. - - TODO[record format ndb]: Remove this code once NDB returns the - correct record format. - */ - if (table->s->null_bytes > 0) - { - for (int i = 0 ; i < 2 ; ++i) - { - if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD)) - table->record[i][0]= saved_x[i]; - - if (table->s->last_null_bit_pos > 0) - table->record[i][table->s->null_bytes - 1]= saved_filler[i]; - } - } - return result; } @@ -807,21 +735,6 @@ static int find_and_fetch_row(TABLE *table, uchar *key) { int error; - /* - We need to set the null bytes to ensure that the filler bit - are all set when returning. There are storage engines that - just set the necessary bits on the bytes and don't set the - filler bits correctly. - - TODO[record format ndb]: Remove this code once NDB returns the - correct record format. - */ - if (table->s->null_bytes > 0) - { - table->record[1][table->s->null_bytes - 1]|= - 256U - (1U << table->s->last_null_bit_pos); - } - while ((error= table->file->ha_index_next(table->record[1]))) { /* We just skip records that has already been deleted */ @@ -916,51 +829,14 @@ int Write_rows_log_event_old::do_before_row_operations(TABLE *table) /* Tell the storage engine that we are using REPLACE semantics. */ thd->lex->duplicates= DUP_REPLACE; - /* - Pretend we're executing a REPLACE command: this is needed for - InnoDB and NDB Cluster since they are not (properly) checking the - lex->duplicates flag. - */ thd->lex->sql_command= SQLCOM_REPLACE; /* Do not raise the error flag in case of hitting to an unique attribute */ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); - /* - NDB specific: update from ndb master wrapped as Write_rows - */ - /* - so that the event should be applied to replace slave's row - */ table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); - /* - NDB specific: if update from ndb master wrapped as Write_rows - does not find the row it's assumed idempotent binlog applying - is taking place; don't raise the error. - */ table->file->extra(HA_EXTRA_IGNORE_NO_KEY); - /* - TODO: the cluster team (Tomas?) says that it's better if the engine knows - how many rows are going to be inserted, then it can allocate needed memory - from the start. - */ table->file->ha_start_bulk_insert(0); - /* - We need TIMESTAMP_NO_AUTO_SET otherwise ha_write_row() will not use fill - any TIMESTAMP column with data from the row but instead will use - the event's current time. - As we replicate from TIMESTAMP to TIMESTAMP and slave has no extra - columns, we know that all TIMESTAMP columns on slave will receive explicit - data from the row, so TIMESTAMP_NO_AUTO_SET is ok. - When we allow a table without TIMESTAMP to be replicated to a table having - more columns including a TIMESTAMP column, or when we allow a TIMESTAMP - column to be replicated into a BIGINT column and the slave's table has a - TIMESTAMP column, then the slave's TIMESTAMP column will take its value - from set_time() which we called earlier (consistent with SBR). And then in - some cases we won't want TIMESTAMP_NO_AUTO_SET (will require some code to - analyze if explicit data is provided for slave's TIMESTAMP columns). - */ - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; return error; } @@ -986,7 +862,7 @@ int Write_rows_log_event_old::do_after_row_operations(TABLE *table, int error) int Write_rows_log_event_old::do_prepare_row(THD *thd_arg, - Relay_log_info const *rli, + rpl_group_info *rgi, TABLE *table, uchar const *row_start, uchar const **row_end) @@ -995,7 +871,7 @@ Write_rows_log_event_old::do_prepare_row(THD *thd_arg, DBUG_ASSERT(row_start && row_end); int error; - error= unpack_row_old(const_cast<Relay_log_info*>(rli), + error= unpack_row_old(rgi, table, m_width, table->record[0], row_start, m_rows_end, &m_cols, row_end, &m_master_reclength, @@ -1070,7 +946,7 @@ int Delete_rows_log_event_old::do_after_row_operations(TABLE *table, int error) int Delete_rows_log_event_old::do_prepare_row(THD *thd_arg, - Relay_log_info const *rli, + rpl_group_info *rgi, TABLE *table, uchar const *row_start, uchar const **row_end) @@ -1083,7 +959,7 @@ Delete_rows_log_event_old::do_prepare_row(THD *thd_arg, */ DBUG_ASSERT(table->s->fields >= m_width); - error= unpack_row_old(const_cast<Relay_log_info*>(rli), + error= unpack_row_old(rgi, table, m_width, table->record[0], row_start, m_rows_end, &m_cols, row_end, &m_master_reclength, @@ -1149,8 +1025,6 @@ int Update_rows_log_event_old::do_before_row_operations(TABLE *table) if (!m_memory) return HA_ERR_OUT_OF_MEM; - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - return error; } @@ -1169,7 +1043,7 @@ int Update_rows_log_event_old::do_after_row_operations(TABLE *table, int error) int Update_rows_log_event_old::do_prepare_row(THD *thd_arg, - Relay_log_info const *rli, + rpl_group_info *rgi, TABLE *table, uchar const *row_start, uchar const **row_end) @@ -1183,14 +1057,14 @@ int Update_rows_log_event_old::do_prepare_row(THD *thd_arg, DBUG_ASSERT(table->s->fields >= m_width); /* record[0] is the before image for the update */ - error= unpack_row_old(const_cast<Relay_log_info*>(rli), + error= unpack_row_old(rgi, table, m_width, table->record[0], row_start, m_rows_end, &m_cols, row_end, &m_master_reclength, table->read_set, PRE_GA_UPDATE_ROWS_EVENT); row_start = *row_end; /* m_after_image is the after image for the update */ - error= unpack_row_old(const_cast<Relay_log_info*>(rli), + error= unpack_row_old(rgi, table, m_width, m_after_image, row_start, m_rows_end, &m_cols, row_end, &m_master_reclength, @@ -1289,8 +1163,8 @@ Old_rows_log_event::Old_rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid, set_flags(NO_FOREIGN_KEY_CHECKS_F); if (thd_arg->variables.option_bits & OPTION_RELAXED_UNIQUE_CHECKS) set_flags(RELAXED_UNIQUE_CHECKS_F); - /* if bitmap_init fails, caught in is_valid() */ - if (likely(!bitmap_init(&m_cols, + /* if my_bitmap_init fails, caught in is_valid() */ + if (likely(!my_bitmap_init(&m_cols, m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL, m_width, false))) @@ -1304,7 +1178,7 @@ Old_rows_log_event::Old_rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid, } else { - // Needed because bitmap_init() does not set it to null on failure + // Needed because my_bitmap_init() does not set it to null on failure m_cols.bitmap= 0; } } @@ -1359,16 +1233,14 @@ Old_rows_log_event::Old_rows_log_event(const char *buf, uint event_len, m_width = net_field_length(&ptr_after_width); DBUG_PRINT("debug", ("m_width=%lu", m_width)); /* Avoid reading out of buffer */ - if (static_cast<unsigned int>(m_width + - (ptr_after_width - - (const uchar *)buf)) > event_len) + if (ptr_after_width + m_width > (uchar *)buf + event_len) { m_cols.bitmap= NULL; DBUG_VOID_RETURN; } - /* if bitmap_init fails, catched in is_valid() */ - if (likely(!bitmap_init(&m_cols, + /* if my_bitmap_init fails, catched in is_valid() */ + if (likely(!my_bitmap_init(&m_cols, m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL, m_width, false))) @@ -1381,7 +1253,7 @@ Old_rows_log_event::Old_rows_log_event(const char *buf, uint event_len, } else { - // Needed because bitmap_init() does not set it to null on failure + // Needed because my_bitmap_init() does not set it to null on failure m_cols.bitmap= NULL; DBUG_VOID_RETURN; } @@ -1412,15 +1284,15 @@ Old_rows_log_event::Old_rows_log_event(const char *buf, uint event_len, Old_rows_log_event::~Old_rows_log_event() { if (m_cols.bitmap == m_bitbuf) // no my_malloc happened - m_cols.bitmap= 0; // so no my_free in bitmap_free - bitmap_free(&m_cols); // To pair with bitmap_init(). + m_cols.bitmap= 0; // so no my_free in my_bitmap_free + my_bitmap_free(&m_cols); // To pair with my_bitmap_init(). my_free(m_rows_buf); } int Old_rows_log_event::get_data_size() { - uchar buf[sizeof(m_width)+1]; + uchar buf[MAX_INT_WIDTH]; uchar *end= net_store_length(buf, (m_width + 7) / 8); DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master", @@ -1451,7 +1323,7 @@ int Old_rows_log_event::do_add_row_data(uchar *row_data, size_t length) trigger false warnings. */ #ifndef HAVE_valgrind - DBUG_DUMP("row_data", row_data, min(length, 32)); + DBUG_DUMP("row_data", row_data, MY_MIN(length, 32)); #endif DBUG_ASSERT(m_rows_buf <= m_rows_cur); @@ -1495,10 +1367,11 @@ int Old_rows_log_event::do_add_row_data(uchar *row_data, size_t length) #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) +int Old_rows_log_event::do_apply_event(rpl_group_info *rgi) { DBUG_ENTER("Old_rows_log_event::do_apply_event(Relay_log_info*)"); int error= 0; + Relay_log_info const *rli= rgi->rli; /* If m_table_id == ~0UL, then we have a dummy event that does not @@ -1514,7 +1387,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ DBUG_ASSERT(get_flags(STMT_END_F)); - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); + rgi->slave_close_thread_tables(thd); thd->clear_error(); DBUG_RETURN(0); } @@ -1524,7 +1397,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) do_apply_event(). We still check here to prevent future coding errors. */ - DBUG_ASSERT(rli->sql_thd == thd); + DBUG_ASSERT(rgi->thd == thd); /* If there is no locks taken, this is the first binrow event seen @@ -1542,8 +1415,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ lex_start(thd); - if ((error= lock_tables(thd, rli->tables_to_lock, - rli->tables_to_lock_count, 0))) + if ((error= lock_tables(thd, rgi->tables_to_lock, + rgi->tables_to_lock_count, 0))) { if (thd->is_slave_error || thd->is_fatal_error) { @@ -1552,7 +1425,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) simplifications (we don't honour --slave-skip-errors) */ uint actual_error= thd->net.last_errno; - rli->report(ERROR_LEVEL, actual_error, + rli->report(ERROR_LEVEL, actual_error, NULL, "Error '%s' in %s event: when locking tables", (actual_error ? thd->net.last_error : "unexpected success or fatal error"), @@ -1561,11 +1434,11 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) } else { - rli->report(ERROR_LEVEL, error, + rli->report(ERROR_LEVEL, error, NULL, "Error in %s event: when locking tables", get_type_str()); } - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); + rgi->slave_close_thread_tables(thd); DBUG_RETURN(error); } @@ -1575,8 +1448,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ { - TABLE_LIST *table_list_ptr= rli->tables_to_lock; - for (uint i=0; table_list_ptr&& (i< rli->tables_to_lock_count); + TABLE_LIST *table_list_ptr= rgi->tables_to_lock; + for (uint i=0; table_list_ptr&& (i< rgi->tables_to_lock_count); table_list_ptr= static_cast<RPL_TABLE_LIST*>(table_list_ptr->next_global), i++) { /* @@ -1592,11 +1465,10 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ RPL_TABLE_LIST *ptr=static_cast<RPL_TABLE_LIST*>(table_list_ptr); TABLE *conv_table; - if (ptr->m_tabledef.compatible_with(thd, const_cast<Relay_log_info*>(rli), - ptr->table, &conv_table)) + if (ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table)) { thd->is_slave_error= 1; - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); + rgi->slave_close_thread_tables(thd); DBUG_RETURN(ERR_BAD_TABLE_DEF); } ptr->m_conv_table= conv_table; @@ -1618,18 +1490,18 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) Old_rows_log_event, we can invalidate the query cache for the associated table. */ - for (TABLE_LIST *ptr= rli->tables_to_lock ; ptr ; ptr= ptr->next_global) + for (TABLE_LIST *ptr= rgi->tables_to_lock ; ptr ; ptr= ptr->next_global) { - const_cast<Relay_log_info*>(rli)->m_table_map.set_table(ptr->table_id, ptr->table); + rgi->m_table_map.set_table(ptr->table_id, ptr->table); } #ifdef HAVE_QUERY_CACHE - query_cache.invalidate_locked_for_write(thd, rli->tables_to_lock); + query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock); #endif } TABLE* table= - m_table= const_cast<Relay_log_info*>(rli)->m_table_map.get_table(m_table_id); + m_table= rgi->m_table_map.get_table(m_table_id); if (table) { @@ -1665,17 +1537,6 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) /* A small test to verify that objects have consistent types */ DBUG_ASSERT(sizeof(thd->variables.option_bits) == sizeof(OPTION_RELAXED_UNIQUE_CHECKS)); - /* - Now we are in a statement and will stay in a statement until we - see a STMT_END_F. - - We set this flag here, before actually applying any rows, in - case the SQL thread is stopped and we need to detect that we're - inside a statement and halting abruptly might cause problems - when restarting. - */ - const_cast<Relay_log_info*>(rli)->set_flag(Relay_log_info::IN_STMT); - if ( m_width == table->s->fields && bitmap_is_set_all(&m_cols)) set_flags(COMPLETE_ROWS_F); @@ -1695,6 +1556,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) bitmap_set_all(table->write_set); if (!get_flags(COMPLETE_ROWS_F)) bitmap_intersect(table->write_set,&m_cols); + table->rpl_write_set= table->write_set; // Do event specific preparations @@ -1709,7 +1571,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) if (!table->in_use) table->in_use= thd; - error= do_exec_row(rli); + error= do_exec_row(rgi); DBUG_PRINT("info", ("error: %d", error)); DBUG_ASSERT(error != HA_ERR_RECORD_DELETED); @@ -1728,10 +1590,9 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) break; default: - rli->report(ERROR_LEVEL, thd->net.last_errno, + rli->report(ERROR_LEVEL, thd->net.last_errno, NULL, "Error in %s event: row application failed. %s", - get_type_str(), - thd->net.last_error ? thd->net.last_error : ""); + get_type_str(), thd->net.last_error); thd->is_slave_error= 1; break; } @@ -1748,7 +1609,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) (ulong) m_curr_row, (ulong) m_curr_row_end, (ulong) m_rows_end)); if (!m_curr_row_end && !error) - unpack_current_row(rli); + unpack_current_row(rgi); // at this moment m_curr_row_end should be set DBUG_ASSERT(error || m_curr_row_end != NULL); @@ -1765,13 +1626,12 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) } // if (table) if (error) - { /* error has occured during the transaction */ - rli->report(ERROR_LEVEL, thd->net.last_errno, + { /* error has occurred during the transaction */ + rli->report(ERROR_LEVEL, thd->net.last_errno, NULL, "Error in %s event: error during transaction execution " "on table %s.%s. %s", get_type_str(), table->s->db.str, - table->s->table_name.str, - thd->net.last_error ? thd->net.last_error : ""); + table->s->table_name.str, thd->net.last_error); /* If one day we honour --skip-slave-errors in row-based replication, and @@ -1785,7 +1645,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) rollback at the caller along with sbr. */ thd->reset_current_stmt_binlog_format_row(); - const_cast<Relay_log_info*>(rli)->cleanup_context(thd, error); + rgi->cleanup_context(thd, error); thd->is_slave_error= 1; DBUG_RETURN(error); } @@ -1814,7 +1674,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) problem. When WL#2975 is implemented, just remove the member Relay_log_info::last_event_start_time and all its occurrences. */ - const_cast<Relay_log_info*>(rli)->last_event_start_time= my_time(0); + rgi->last_event_start_time= my_time(0); } if (get_flags(STMT_END_F)) @@ -1849,7 +1709,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ DBUG_ASSERT(! thd->transaction_rollback_request); if ((error= (binlog_error ? trans_rollback_stmt(thd) : trans_commit_stmt(thd)))) - rli->report(ERROR_LEVEL, error, + rli->report(ERROR_LEVEL, error, NULL, "Error in %s event: commit of row events failed, " "table `%s`.`%s`", get_type_str(), m_table->s->db.str, @@ -1867,7 +1727,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ thd->reset_current_stmt_binlog_format_row(); - const_cast<Relay_log_info*>(rli)->cleanup_context(thd, 0); + rgi->cleanup_context(thd, 0); } DBUG_RETURN(error); @@ -1875,24 +1735,25 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) Log_event::enum_skip_reason -Old_rows_log_event::do_shall_skip(Relay_log_info *rli) +Old_rows_log_event::do_shall_skip(rpl_group_info *rgi) { /* If the slave skip counter is 1 and this event does not end a statement, then we should not start executing on the next event. Otherwise, we defer the decision to the normal skipping logic. */ - if (rli->slave_skip_counter == 1 && !get_flags(STMT_END_F)) + if (rgi->rli->slave_skip_counter == 1 && !get_flags(STMT_END_F)) return Log_event::EVENT_SKIP_IGNORE; else - return Log_event::do_shall_skip(rli); + return Log_event::do_shall_skip(rgi); } int -Old_rows_log_event::do_update_pos(Relay_log_info *rli) +Old_rows_log_event::do_update_pos(rpl_group_info *rgi) { - DBUG_ENTER("Old_rows_log_event::do_update_pos"); + Relay_log_info *rli= rgi->rli; int error= 0; + DBUG_ENTER("Old_rows_log_event::do_update_pos"); DBUG_PRINT("info", ("flags: %s", get_flags(STMT_END_F) ? "STMT_END_F " : "")); @@ -1904,7 +1765,7 @@ Old_rows_log_event::do_update_pos(Relay_log_info *rli) Step the group log position if we are not in a transaction, otherwise increase the event log position. */ - rli->stmt_done(log_pos, when); + error= rli->stmt_done(log_pos, thd, rgi); /* Clear any errors in thd->net.last_err*. It is not known if this is needed or not. It is believed that any errors that may exist in @@ -1915,7 +1776,7 @@ Old_rows_log_event::do_update_pos(Relay_log_info *rli) } else { - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); } DBUG_RETURN(error); @@ -1925,7 +1786,7 @@ Old_rows_log_event::do_update_pos(Relay_log_info *rli) #ifndef MYSQL_CLIENT -bool Old_rows_log_event::write_data_header(IO_CACHE *file) +bool Old_rows_log_event::write_data_header() { uchar buf[ROWS_HEADER_LEN]; // No need to init the buffer @@ -1937,21 +1798,21 @@ bool Old_rows_log_event::write_data_header(IO_CACHE *file) { int4store(buf + 0, m_table_id); int2store(buf + 4, m_flags); - return (my_b_safe_write(file, buf, 6)); + return write_data(buf, 6); }); int6store(buf + RW_MAPID_OFFSET, (ulonglong)m_table_id); int2store(buf + RW_FLAGS_OFFSET, m_flags); - return (my_b_safe_write(file, buf, ROWS_HEADER_LEN)); + return write_data(buf, ROWS_HEADER_LEN); } -bool Old_rows_log_event::write_data_body(IO_CACHE*file) +bool Old_rows_log_event::write_data_body() { /* Note that this should be the number of *bits*, not the number of bytes. */ - uchar sbuf[sizeof(m_width)]; + uchar sbuf[MAX_INT_WIDTH]; my_ptrdiff_t const data_size= m_rows_cur - m_rows_buf; // This method should not be reached. @@ -1962,13 +1823,12 @@ bool Old_rows_log_event::write_data_body(IO_CACHE*file) DBUG_ASSERT(static_cast<size_t>(sbuf_end - sbuf) <= sizeof(sbuf)); DBUG_DUMP("m_width", sbuf, (size_t) (sbuf_end - sbuf)); - res= res || my_b_safe_write(file, sbuf, (size_t) (sbuf_end - sbuf)); + res= res || write_data(sbuf, (size_t) (sbuf_end - sbuf)); DBUG_DUMP("m_cols", (uchar*) m_cols.bitmap, no_bytes_in_map(&m_cols)); - res= res || my_b_safe_write(file, (uchar*) m_cols.bitmap, - no_bytes_in_map(&m_cols)); + res= res || write_data((uchar*)m_cols.bitmap, no_bytes_in_map(&m_cols)); DBUG_DUMP("rows", m_rows_buf, data_size); - res= res || my_b_safe_write(file, m_rows_buf, (size_t) data_size); + res= res || write_data(m_rows_buf, (size_t) data_size); return res; @@ -1977,7 +1837,7 @@ bool Old_rows_log_event::write_data_body(IO_CACHE*file) #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) -void Old_rows_log_event::pack_info(THD *thd, Protocol *protocol) +void Old_rows_log_event::pack_info(Protocol *protocol) { char buf[256]; char const *const flagstr= @@ -1990,12 +1850,17 @@ void Old_rows_log_event::pack_info(THD *thd, Protocol *protocol) #ifdef MYSQL_CLIENT +/* Method duplicates Rows_log_event's one */ void Old_rows_log_event::print_helper(FILE *file, PRINT_EVENT_INFO *print_event_info, char const *const name) { IO_CACHE *const head= &print_event_info->head_cache; IO_CACHE *const body= &print_event_info->body_cache; + bool do_print_encoded= + print_event_info->base64_output_mode != BASE64_OUTPUT_DECODE_ROWS && + !print_event_info->short_form; + if (!print_event_info->short_form) { bool const last_stmt_event= get_flags(STMT_END_F); @@ -2003,13 +1868,18 @@ void Old_rows_log_event::print_helper(FILE *file, my_b_printf(head, "\t%s: table id %lu%s\n", name, m_table_id, last_stmt_event ? " flags: STMT_END_F" : ""); - print_base64(body, print_event_info, !last_stmt_event); + print_base64(body, print_event_info, do_print_encoded); } if (get_flags(STMT_END_F)) { - copy_event_cache_to_file_and_reinit(head, file); - copy_event_cache_to_file_and_reinit(body, file); + if (copy_event_cache_to_file_and_reinit(head, file)) + { + head->error= -1; + return; + } + copy_cache_to_file_wrapped(file, body, do_print_encoded, + print_event_info->delimiter); } } #endif @@ -2052,8 +1922,7 @@ void Old_rows_log_event::print_helper(FILE *file, */ int -Old_rows_log_event::write_row(const Relay_log_info *const rli, - const bool overwrite) +Old_rows_log_event::write_row(rpl_group_info *rgi, const bool overwrite) { DBUG_ENTER("write_row"); DBUG_ASSERT(m_table != NULL && thd != NULL); @@ -2070,8 +1939,9 @@ Old_rows_log_event::write_row(const Relay_log_info *const rli, DBUG_RETURN(error); /* unpack row into table->record[0] */ - error= unpack_current_row(rli); // TODO: how to handle errors? - + if ((error= unpack_current_row(rgi))) + DBUG_RETURN(error); + #ifndef DBUG_OFF DBUG_DUMP("record[0]", table->record[0], table->s->reclength); DBUG_PRINT_BITSET("debug", "write_set = %s", table->write_set); @@ -2177,7 +2047,7 @@ Old_rows_log_event::write_row(const Relay_log_info *const rli, if (!get_flags(COMPLETE_ROWS_F)) { restore_record(table,record[1]); - error= unpack_current_row(rli); + error= unpack_current_row(rgi); } #ifndef DBUG_OFF @@ -2272,7 +2142,7 @@ Old_rows_log_event::write_row(const Relay_log_info *const rli, for any following update/delete command. */ -int Old_rows_log_event::find_row(const Relay_log_info *rli) +int Old_rows_log_event::find_row(rpl_group_info *rgi) { DBUG_ENTER("find_row"); @@ -2285,7 +2155,7 @@ int Old_rows_log_event::find_row(const Relay_log_info *rli) // TODO: shall we check and report errors here? prepare_record(table, m_width, FALSE /* don't check errors */); - error= unpack_current_row(rli); + error= unpack_current_row(rgi); #ifndef DBUG_OFF DBUG_PRINT("info",("looking for the following record")); @@ -2423,7 +2293,7 @@ int Old_rows_log_event::find_row(const Relay_log_info *rli) field in the BI image that is null and part of UNNI. */ bool null_found= FALSE; - for (uint i=0; i < keyinfo->key_parts && !null_found; i++) + for (uint i=0; i < keyinfo->user_defined_key_parts && !null_found; i++) { uint fieldnr= keyinfo->key_part[i].fieldnr - 1; Field **f= table->field+fieldnr; @@ -2448,21 +2318,6 @@ int Old_rows_log_event::find_row(const Relay_log_info *rli) while (record_compare(table)) { - /* - We need to set the null bytes to ensure that the filler bit - are all set when returning. There are storage engines that - just set the necessary bits on the bytes and don't set the - filler bits correctly. - - TODO[record format ndb]: Remove this code once NDB returns the - correct record format. - */ - if (table->s->null_bytes > 0) - { - table->record[0][table->s->null_bytes - 1]|= - 256U - (1U << table->s->last_null_bit_pos); - } - while ((error= table->file->ha_index_next(table->record[0]))) { /* We just skip records that has already been deleted */ @@ -2602,51 +2457,14 @@ Write_rows_log_event_old::do_before_row_operations(const Slave_reporting_capabil /* Tell the storage engine that we are using REPLACE semantics. */ thd->lex->duplicates= DUP_REPLACE; - /* - Pretend we're executing a REPLACE command: this is needed for - InnoDB and NDB Cluster since they are not (properly) checking the - lex->duplicates flag. - */ thd->lex->sql_command= SQLCOM_REPLACE; /* Do not raise the error flag in case of hitting to an unique attribute */ m_table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); - /* - NDB specific: update from ndb master wrapped as Write_rows - */ - /* - so that the event should be applied to replace slave's row - */ m_table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); - /* - NDB specific: if update from ndb master wrapped as Write_rows - does not find the row it's assumed idempotent binlog applying - is taking place; don't raise the error. - */ m_table->file->extra(HA_EXTRA_IGNORE_NO_KEY); - /* - TODO: the cluster team (Tomas?) says that it's better if the engine knows - how many rows are going to be inserted, then it can allocate needed memory - from the start. - */ m_table->file->ha_start_bulk_insert(0); - /* - We need TIMESTAMP_NO_AUTO_SET otherwise ha_write_row() will not use fill - any TIMESTAMP column with data from the row but instead will use - the event's current time. - As we replicate from TIMESTAMP to TIMESTAMP and slave has no extra - columns, we know that all TIMESTAMP columns on slave will receive explicit - data from the row, so TIMESTAMP_NO_AUTO_SET is ok. - When we allow a table without TIMESTAMP to be replicated to a table having - more columns including a TIMESTAMP column, or when we allow a TIMESTAMP - column to be replicated into a BIGINT column and the slave's table has a - TIMESTAMP column, then the slave's TIMESTAMP column will take its value - from set_time() which we called earlier (consistent with SBR). And then in - some cases we won't want TIMESTAMP_NO_AUTO_SET (will require some code to - analyze if explicit data is provided for slave's TIMESTAMP columns). - */ - m_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; return error; } @@ -2673,10 +2491,10 @@ Write_rows_log_event_old::do_after_row_operations(const Slave_reporting_capabili int -Write_rows_log_event_old::do_exec_row(const Relay_log_info *const rli) +Write_rows_log_event_old::do_exec_row(rpl_group_info *rgi) { DBUG_ASSERT(m_table != NULL); - int error= write_row(rli, TRUE /* overwrite */); + int error= write_row(rgi, TRUE /* overwrite */); if (error && !thd->net.last_errno) thd->net.last_errno= error; @@ -2775,12 +2593,12 @@ Delete_rows_log_event_old::do_after_row_operations(const Slave_reporting_capabil } -int Delete_rows_log_event_old::do_exec_row(const Relay_log_info *const rli) +int Delete_rows_log_event_old::do_exec_row(rpl_group_info *rgi) { int error; DBUG_ASSERT(m_table != NULL); - if (!(error= find_row(rli))) + if (!(error= find_row(rgi))) { /* Delete the record found, located in record[0] @@ -2856,8 +2674,6 @@ Update_rows_log_event_old::do_before_row_operations(const Slave_reporting_capabi return HA_ERR_OUT_OF_MEM; } - m_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - return 0; } @@ -2876,11 +2692,11 @@ Update_rows_log_event_old::do_after_row_operations(const Slave_reporting_capabil int -Update_rows_log_event_old::do_exec_row(const Relay_log_info *const rli) +Update_rows_log_event_old::do_exec_row(rpl_group_info *rgi) { DBUG_ASSERT(m_table != NULL); - int error= find_row(rli); + int error= find_row(rgi); if (error) { /* @@ -2888,7 +2704,7 @@ Update_rows_log_event_old::do_exec_row(const Relay_log_info *const rli) able to skip to the next pair of updates */ m_curr_row= m_curr_row_end; - unpack_current_row(rli); + unpack_current_row(rgi); return error; } @@ -2906,7 +2722,7 @@ Update_rows_log_event_old::do_exec_row(const Relay_log_info *const rli) store_record(m_table,record[1]); m_curr_row= m_curr_row_end; - error= unpack_current_row(rli); // this also updates m_curr_row_end + error= unpack_current_row(rgi); // this also updates m_curr_row_end /* Now we have the right row to update. The old row (the one we're diff --git a/sql/log_event_old.h b/sql/log_event_old.h index d9d9f25737b..40e01d37318 100644 --- a/sql/log_event_old.h +++ b/sql/log_event_old.h @@ -41,6 +41,9 @@ but we keep them this way for now. /Sven */ +/* These classes are based on the v1 RowsHeaderLen */ +#undef ROWS_HEADER_LEN +#define ROWS_HEADER_LEN ROWS_HEADER_LEN_V1 /** @class Old_rows_log_event @@ -108,7 +111,7 @@ public: flag_set get_flags(flag_set flags_arg) const { return m_flags & flags_arg; } #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) - virtual void pack_info(THD *thd, Protocol *protocol); + virtual void pack_info(Protocol *protocol); #endif #ifdef MYSQL_CLIENT @@ -131,8 +134,8 @@ public: ulong get_table_id() const { return m_table_id; } #ifndef MYSQL_CLIENT - virtual bool write_data_header(IO_CACHE *file); - virtual bool write_data_body(IO_CACHE *file); + virtual bool write_data_header(); + virtual bool write_data_body(); virtual const char *get_db() { return m_table->s->db.str; } #endif /* @@ -145,6 +148,7 @@ public: { return m_rows_buf && m_cols.bitmap; } + bool is_part_of_group() { return 1; } uint m_row_count; /* The number of rows added to the event */ @@ -195,15 +199,15 @@ protected: const uchar *m_curr_row_end; /* One-after the end of the current row */ uchar *m_key; /* Buffer to keep key value during searches */ - int find_row(const Relay_log_info *const); - int write_row(const Relay_log_info *const, const bool); + int find_row(rpl_group_info *); + int write_row(rpl_group_info *, const bool); // Unpack the current row into m_table->record[0] - int unpack_current_row(const Relay_log_info *const rli) + int unpack_current_row(rpl_group_info *rgi) { DBUG_ASSERT(m_table); ASSERT_OR_RETURN_ERROR(m_curr_row < m_rows_end, HA_ERR_CORRUPT_EVENT); - return ::unpack_row(rli, m_table, m_width, m_curr_row, &m_cols, + return ::unpack_row(rgi, m_table, m_width, m_curr_row, &m_cols, &m_curr_row_end, &m_master_reclength, m_rows_end); } #endif @@ -211,9 +215,9 @@ protected: private: #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) - virtual int do_apply_event(Relay_log_info const *rli); - virtual int do_update_pos(Relay_log_info *rli); - virtual enum_skip_reason do_shall_skip(Relay_log_info *rli); + virtual int do_apply_event(rpl_group_info *rgi); + virtual int do_update_pos(rpl_group_info *rgi); + virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi); /* Primitive to prepare for a sequence of row executions. @@ -264,7 +268,7 @@ private: 0 if execution succeeded, 1 if execution failed. */ - virtual int do_exec_row(const Relay_log_info *const rli) = 0; + virtual int do_exec_row(rpl_group_info *rgi) = 0; #endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */ /********** END OF CUT & PASTE FROM Rows_log_event **********/ @@ -272,7 +276,7 @@ private: #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) - int do_apply_event(Old_rows_log_event*,const Relay_log_info*); + int do_apply_event(Old_rows_log_event*, rpl_group_info *rgi); /* Primitive to prepare for a sequence of row executions. @@ -321,7 +325,7 @@ private: RETURN VALUE Error code, if something went wrong, 0 otherwise. */ - virtual int do_prepare_row(THD*, Relay_log_info const*, TABLE*, + virtual int do_prepare_row(THD*, rpl_group_info*, TABLE*, uchar const *row_start, uchar const **row_end) = 0; @@ -365,14 +369,11 @@ public: #if !defined(MYSQL_CLIENT) static bool binlog_row_logging_function(THD *thd, TABLE *table, bool is_transactional, - MY_BITMAP *cols, - uint fields, const uchar *before_record __attribute__((unused)), const uchar *after_record) { - return thd->binlog_write_row(table, is_transactional, - cols, fields, after_record); + return thd->binlog_write_row(table, is_transactional, after_record); } #endif @@ -384,7 +385,7 @@ private: #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) virtual int do_before_row_operations(const Slave_reporting_capability *const); virtual int do_after_row_operations(const Slave_reporting_capability *const,int); - virtual int do_exec_row(const Relay_log_info *const); + virtual int do_exec_row(rpl_group_info *); #endif /********** END OF CUT & PASTE FROM Write_rows_log_event **********/ @@ -400,13 +401,13 @@ private: #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) // use old definition of do_apply_event() - virtual int do_apply_event(const Relay_log_info *rli) - { return Old_rows_log_event::do_apply_event(this,rli); } + virtual int do_apply_event(rpl_group_info *rgi) + { return Old_rows_log_event::do_apply_event(this, rgi); } // primitives for old version of do_apply_event() virtual int do_before_row_operations(TABLE *table); virtual int do_after_row_operations(TABLE *table, int error); - virtual int do_prepare_row(THD*, Relay_log_info const*, TABLE*, + virtual int do_prepare_row(THD*, rpl_group_info*, TABLE*, uchar const *row_start, uchar const **row_end); virtual int do_exec_row(TABLE *table); @@ -448,7 +449,7 @@ public: const uchar *after_record) { return thd->binlog_update_row(table, is_transactional, - cols, fields, before_record, after_record); + before_record, after_record); } #endif @@ -460,7 +461,7 @@ protected: #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) virtual int do_before_row_operations(const Slave_reporting_capability *const); virtual int do_after_row_operations(const Slave_reporting_capability *const,int); - virtual int do_exec_row(const Relay_log_info *const); + virtual int do_exec_row(rpl_group_info *); #endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */ /********** END OF CUT & PASTE FROM Update_rows_log_event **********/ @@ -478,13 +479,13 @@ private: #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) // use old definition of do_apply_event() - virtual int do_apply_event(const Relay_log_info *rli) - { return Old_rows_log_event::do_apply_event(this,rli); } + virtual int do_apply_event(rpl_group_info *rgi) + { return Old_rows_log_event::do_apply_event(this, rgi); } // primitives for old version of do_apply_event() virtual int do_before_row_operations(TABLE *table); virtual int do_after_row_operations(TABLE *table, int error); - virtual int do_prepare_row(THD*, Relay_log_info const*, TABLE*, + virtual int do_prepare_row(THD*, rpl_group_info*, TABLE*, uchar const *row_start, uchar const **row_end); virtual int do_exec_row(TABLE *table); #endif /* !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) */ @@ -522,8 +523,7 @@ public: const uchar *after_record __attribute__((unused))) { - return thd->binlog_delete_row(table, is_transactional, - cols, fields, before_record); + return thd->binlog_delete_row(table, is_transactional, before_record); } #endif @@ -535,7 +535,7 @@ protected: #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) virtual int do_before_row_operations(const Slave_reporting_capability *const); virtual int do_after_row_operations(const Slave_reporting_capability *const,int); - virtual int do_exec_row(const Relay_log_info *const); + virtual int do_exec_row(rpl_group_info *); #endif /********** END CUT & PASTE FROM Delete_rows_log_event **********/ @@ -553,13 +553,13 @@ private: #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) // use old definition of do_apply_event() - virtual int do_apply_event(const Relay_log_info *rli) - { return Old_rows_log_event::do_apply_event(this,rli); } + virtual int do_apply_event(rpl_group_info *rgi) + { return Old_rows_log_event::do_apply_event(this, rgi); } // primitives for old version of do_apply_event() virtual int do_before_row_operations(TABLE *table); virtual int do_after_row_operations(TABLE *table, int error); - virtual int do_prepare_row(THD*, Relay_log_info const*, TABLE*, + virtual int do_prepare_row(THD*, rpl_group_info*, TABLE*, uchar const *row_start, uchar const **row_end); virtual int do_exec_row(TABLE *table); #endif diff --git a/sql/log_slow.h b/sql/log_slow.h index 541ef55f9e1..5092e8332ed 100644 --- a/sql/log_slow.h +++ b/sql/log_slow.h @@ -11,13 +11,14 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* Defining what to log to slow log */ #define LOG_SLOW_VERBOSITY_INIT 0 #define LOG_SLOW_VERBOSITY_INNODB (1U << 0) #define LOG_SLOW_VERBOSITY_QUERY_PLAN (1U << 1) +#define LOG_SLOW_VERBOSITY_EXPLAIN (1U << 2) #define QPLAN_INIT QPLAN_QC_NO @@ -30,5 +31,8 @@ #define QPLAN_QC_NO (1U << 6) #define QPLAN_TMP_DISK (1U << 7) #define QPLAN_TMP_TABLE (1U << 8) +#define QPLAN_FILESORT_PRIORITY_QUEUE (1U << 9) + /* ... */ +#define QPLAN_STATUS (1U << 31) /* not in the slow_log_filter */ #define QPLAN_MAX (1U << 31) /* reserved as placeholder */ diff --git a/sql/mdl.cc b/sql/mdl.cc index 415d56887d5..86fc5fa39fc 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2007, 2012, 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 @@ -10,24 +10,27 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ #include "sql_class.h" #include "debug_sync.h" -#include <hash.h> +#include "sql_array.h" +#include "rpl_rli.h" +#include <lf.h> #include <mysqld_error.h> #include <mysql/plugin.h> #include <mysql/service_thd_wait.h> +#include <mysql/psi/mysql_stage.h> +#include "wsrep_mysqld.h" +#include "wsrep_thd.h" #ifdef HAVE_PSI_INTERFACE -static PSI_mutex_key key_MDL_map_mutex; static PSI_mutex_key key_MDL_wait_LOCK_wait_status; static PSI_mutex_info all_mdl_mutexes[]= { - { &key_MDL_map_mutex, "MDL_map::mutex", PSI_FLAG_GLOBAL}, { &key_MDL_wait_LOCK_wait_status, "MDL_wait::LOCK_wait_status", 0} }; @@ -53,20 +56,18 @@ static PSI_cond_info all_mdl_conds[]= */ static void init_mdl_psi_keys(void) { - const char *category= "sql"; int count; - if (PSI_server == NULL) - return; - count= array_elements(all_mdl_mutexes); - PSI_server->register_mutex(category, all_mdl_mutexes, count); + mysql_mutex_register("sql", all_mdl_mutexes, count); count= array_elements(all_mdl_rwlocks); - PSI_server->register_rwlock(category, all_mdl_rwlocks, count); + mysql_rwlock_register("sql", all_mdl_rwlocks, count); count= array_elements(all_mdl_conds); - PSI_server->register_cond(category, all_mdl_conds, count); + mysql_cond_register("sql", all_mdl_conds, count); + + MDL_key::init_psi_keys(); } #endif /* HAVE_PSI_INTERFACE */ @@ -76,29 +77,42 @@ static void init_mdl_psi_keys(void) belonging to certain namespace. */ -const char *MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]= +PSI_stage_info MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]= { - "Waiting for global read lock", - "Waiting for schema metadata lock", - "Waiting for table metadata lock", - "Waiting for stored function metadata lock", - "Waiting for stored procedure metadata lock", - "Waiting for trigger metadata lock", - "Waiting for event metadata lock", - "Waiting for commit lock" + {0, "Waiting for global read lock", 0}, + {0, "Waiting for schema metadata lock", 0}, + {0, "Waiting for table metadata lock", 0}, + {0, "Waiting for stored function metadata lock", 0}, + {0, "Waiting for stored procedure metadata lock", 0}, + {0, "Waiting for trigger metadata lock", 0}, + {0, "Waiting for event metadata lock", 0}, + {0, "Waiting for commit lock", 0}, + {0, "User lock", 0} /* Be compatible with old status. */ }; -static bool mdl_initialized= 0; +#ifdef HAVE_PSI_INTERFACE +void MDL_key::init_psi_keys() +{ + int i; + int count; + PSI_stage_info *info __attribute__((unused)); + count= array_elements(MDL_key::m_namespace_to_wait_state_name); + for (i= 0; i<count; i++) + { + /* mysql_stage_register wants an array of pointers, registering 1 by 1. */ + info= & MDL_key::m_namespace_to_wait_state_name[i]; + mysql_stage_register("sql", &info, 1); + } +} +#endif -class MDL_object_lock; -class MDL_object_lock_cache_adapter; +static bool mdl_initialized= 0; /** A collection of all MDL locks. A singleton, there is only one instance of the map in the server. - Maps MDL_key to MDL_lock instances. */ class MDL_map @@ -106,38 +120,17 @@ class MDL_map public: void init(); void destroy(); - MDL_lock *find_or_insert(const MDL_key *key); - void remove(MDL_lock *lock); -private: - bool move_from_hash_to_lock_mutex(MDL_lock *lock); + MDL_lock *find_or_insert(LF_PINS *pins, const MDL_key *key); + unsigned long get_lock_owner(LF_PINS *pins, const MDL_key *key); + void remove(LF_PINS *pins, MDL_lock *lock); + LF_PINS *get_pins() { return lf_hash_get_pins(&m_locks); } private: - /** All acquired locks in the server. */ - HASH m_locks; - /* Protects access to m_locks hash. */ - mysql_mutex_t m_mutex; - /** - Cache of (unused) MDL_lock objects available for re-use. - - On some systems (e.g. Windows XP) constructing/destructing - MDL_lock objects can be fairly expensive. We use this cache - to avoid these costs in scenarios in which they can have - significant negative effect on performance. For example, when - there is only one thread constantly executing statements in - auto-commit mode and thus constantly causing creation/ - destruction of MDL_lock objects for the tables it uses. - - Note that this cache contains only MDL_object_lock objects. - - Protected by m_mutex mutex. - */ - typedef I_P_List<MDL_object_lock, MDL_object_lock_cache_adapter, - I_P_List_counter> - Lock_cache; - Lock_cache m_unused_locks_cache; + LF_HASH m_locks; /**< All acquired locks in the server. */ /** Pre-allocated MDL_lock object for GLOBAL namespace. */ MDL_lock *m_global_lock; /** Pre-allocated MDL_lock object for COMMIT namespace. */ MDL_lock *m_commit_lock; + friend int mdl_iterate(int (*)(MDL_ticket *, void *), void *); }; @@ -300,7 +293,7 @@ Deadlock_detection_visitor::opt_change_victim_to(MDL_context *new_victim) class MDL_lock { public: - typedef uchar bitmap_t; + typedef unsigned short bitmap_t; class Ticket_list { @@ -330,6 +323,104 @@ public: typedef Ticket_list::List::Iterator Ticket_iterator; + + /** + Helper struct which defines how different types of locks are handled + for a specific MDL_lock. In practice we use only two strategies: "scoped" + lock strategy for locks in GLOBAL, COMMIT and SCHEMA namespaces and + "object" lock strategy for all other namespaces. + */ + struct MDL_lock_strategy + { + virtual const bitmap_t *incompatible_granted_types_bitmap() const = 0; + virtual const bitmap_t *incompatible_waiting_types_bitmap() const = 0; + virtual bool needs_notification(const MDL_ticket *ticket) const = 0; + virtual bool conflicting_locks(const MDL_ticket *ticket) const = 0; + virtual bitmap_t hog_lock_types_bitmap() const = 0; + virtual ~MDL_lock_strategy() {} + }; + + + /** + An implementation of the scoped metadata lock. The only locking modes + which are supported at the moment are SHARED and INTENTION EXCLUSIVE + and EXCLUSIVE + */ + struct MDL_scoped_lock : public MDL_lock_strategy + { + MDL_scoped_lock() {} + virtual const bitmap_t *incompatible_granted_types_bitmap() const + { return m_granted_incompatible; } + virtual const bitmap_t *incompatible_waiting_types_bitmap() const + { return m_waiting_incompatible; } + virtual bool needs_notification(const MDL_ticket *ticket) const + { return (ticket->get_type() == MDL_SHARED); } + + /** + Notify threads holding scoped IX locks which conflict with a pending + S lock. + + Thread which holds global IX lock can be a handler thread for + insert delayed. We need to kill such threads in order to get + global shared lock. We do this my calling code outside of MDL. + */ + virtual bool conflicting_locks(const MDL_ticket *ticket) const + { return ticket->get_type() == MDL_INTENTION_EXCLUSIVE; } + + /* + In scoped locks, only IX lock request would starve because of X/S. But that + is practically very rare case. So just return 0 from this function. + */ + virtual bitmap_t hog_lock_types_bitmap() const + { return 0; } + private: + static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; + static const bitmap_t m_waiting_incompatible[MDL_TYPE_END]; + }; + + + /** + An implementation of a per-object lock. Supports SHARED, SHARED_UPGRADABLE, + SHARED HIGH PRIORITY and EXCLUSIVE locks. + */ + struct MDL_object_lock : public MDL_lock_strategy + { + MDL_object_lock() {} + virtual const bitmap_t *incompatible_granted_types_bitmap() const + { return m_granted_incompatible; } + virtual const bitmap_t *incompatible_waiting_types_bitmap() const + { return m_waiting_incompatible; } + virtual bool needs_notification(const MDL_ticket *ticket) const + { return (ticket->get_type() >= MDL_SHARED_NO_WRITE); } + + /** + Notify threads holding a shared metadata locks on object which + conflict with a pending X, SNW or SNRW lock. + + If thread which holds conflicting lock is waiting on table-level + lock or some other non-MDL resource we might need to wake it up + by calling code outside of MDL. + */ + virtual bool conflicting_locks(const MDL_ticket *ticket) const + { return ticket->get_type() < MDL_SHARED_UPGRADABLE; } + + /* + To prevent starvation, these lock types that are only granted + max_write_lock_count times in a row while other lock types are + waiting. + */ + virtual bitmap_t hog_lock_types_bitmap() const + { + return (MDL_BIT(MDL_SHARED_NO_WRITE) | + MDL_BIT(MDL_SHARED_NO_READ_WRITE) | + MDL_BIT(MDL_EXCLUSIVE)); + } + + private: + static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; + static const bitmap_t m_waiting_incompatible[MDL_TYPE_END]; + }; + public: /** The key of the object (data) being protected. */ MDL_key key; @@ -373,27 +464,52 @@ public: return (m_granted.is_empty() && m_waiting.is_empty()); } - virtual const bitmap_t *incompatible_granted_types_bitmap() const = 0; - virtual const bitmap_t *incompatible_waiting_types_bitmap() const = 0; + const bitmap_t *incompatible_granted_types_bitmap() const + { return m_strategy->incompatible_granted_types_bitmap(); } + const bitmap_t *incompatible_waiting_types_bitmap() const + { return m_strategy->incompatible_waiting_types_bitmap(); } bool has_pending_conflicting_lock(enum_mdl_type type); bool can_grant_lock(enum_mdl_type type, MDL_context *requstor_ctx, bool ignore_lock_priority) const; - inline static MDL_lock *create(const MDL_key *key); + inline unsigned long get_lock_owner() const; void reschedule_waiters(); - void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket); + void remove_ticket(LF_PINS *pins, Ticket_list MDL_lock::*queue, + MDL_ticket *ticket); bool visit_subgraph(MDL_ticket *waiting_ticket, MDL_wait_for_graph_visitor *gvisitor); - virtual bool needs_notification(const MDL_ticket *ticket) const = 0; - virtual void notify_conflicting_locks(MDL_context *ctx) = 0; + bool needs_notification(const MDL_ticket *ticket) const + { return m_strategy->needs_notification(ticket); } + void notify_conflicting_locks(MDL_context *ctx) + { + Ticket_iterator it(m_granted); + MDL_ticket *conflicting_ticket; + while ((conflicting_ticket= it++)) + { + if (conflicting_ticket->get_ctx() != ctx && + m_strategy->conflicting_locks(conflicting_ticket)) + { + MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); + + ctx->get_owner()-> + notify_shared_lock(conflicting_ctx->get_owner(), + conflicting_ctx->get_needs_thr_lock_abort()); + } + } + } - virtual bitmap_t hog_lock_types_bitmap() const = 0; + bitmap_t hog_lock_types_bitmap() const + { return m_strategy->hog_lock_types_bitmap(); } + +#ifndef DBUG_OFF + bool check_if_conflicting_replication_locks(MDL_context *ctx); +#endif /** List of granted tickets for this lock. */ Ticket_list m_granted; @@ -408,191 +524,54 @@ public: public: + MDL_lock() + : m_hog_lock_count(0), + m_strategy(0) + { mysql_prlock_init(key_MDL_lock_rwlock, &m_rwlock); } + MDL_lock(const MDL_key *key_arg) : key(key_arg), m_hog_lock_count(0), - m_ref_usage(0), - m_ref_release(0), - m_is_destroyed(FALSE), - m_version(0) + m_strategy(&m_scoped_lock_strategy) { + DBUG_ASSERT(key_arg->mdl_namespace() == MDL_key::GLOBAL || + key_arg->mdl_namespace() == MDL_key::COMMIT); mysql_prlock_init(key_MDL_lock_rwlock, &m_rwlock); } - virtual ~MDL_lock() - { - mysql_prlock_destroy(&m_rwlock); - } - inline static void destroy(MDL_lock *lock); -public: - /** - These three members are used to make it possible to separate - the mdl_locks.m_mutex mutex and MDL_lock::m_rwlock in - MDL_map::find_or_insert() for increased scalability. - The 'm_is_destroyed' member is only set by destroyers that - have both the mdl_locks.m_mutex and MDL_lock::m_rwlock, thus - holding any of the mutexes is sufficient to read it. - The 'm_ref_usage; is incremented under protection by - mdl_locks.m_mutex, but when 'm_is_destroyed' is set to TRUE, this - member is moved to be protected by the MDL_lock::m_rwlock. - This means that the MDL_map::find_or_insert() which only - holds the MDL_lock::m_rwlock can compare it to 'm_ref_release' - without acquiring mdl_locks.m_mutex again and if equal it can also - destroy the lock object safely. - The 'm_ref_release' is incremented under protection by - MDL_lock::m_rwlock. - Note since we are only interested in equality of these two - counters we don't have to worry about overflows as long as - their size is big enough to hold maximum number of concurrent - threads on the system. - */ - uint m_ref_usage; - uint m_ref_release; - bool m_is_destroyed; - /** - We use the same idea and an additional version counter to support - caching of unused MDL_lock object for further re-use. - This counter is incremented while holding both MDL_map::m_mutex and - MDL_lock::m_rwlock locks each time when a MDL_lock is moved from - the hash to the unused objects list (or destroyed). - A thread, which has found a MDL_lock object for the key in the hash - and then released the MDL_map::m_mutex before acquiring the - MDL_lock::m_rwlock, can determine that this object was moved to the - unused objects list (or destroyed) while it held no locks by comparing - the version value which it read while holding the MDL_map::m_mutex - with the value read after acquiring the MDL_lock::m_rwlock. - Note that since it takes several years to overflow this counter such - theoretically possible overflows should not have any practical effects. - */ - ulonglong m_version; -}; - - -/** - An implementation of the scoped metadata lock. The only locking modes - which are supported at the moment are SHARED and INTENTION EXCLUSIVE - and EXCLUSIVE -*/ - -class MDL_scoped_lock : public MDL_lock -{ -public: - MDL_scoped_lock(const MDL_key *key_arg) - : MDL_lock(key_arg) - { } - - virtual const bitmap_t *incompatible_granted_types_bitmap() const - { - return m_granted_incompatible; - } - virtual const bitmap_t *incompatible_waiting_types_bitmap() const - { - return m_waiting_incompatible; - } - virtual bool needs_notification(const MDL_ticket *ticket) const - { - return (ticket->get_type() == MDL_SHARED); - } - virtual void notify_conflicting_locks(MDL_context *ctx); - - /* - In scoped locks, only IX lock request would starve because of X/S. But that - is practically very rare case. So just return 0 from this function. - */ - virtual bitmap_t hog_lock_types_bitmap() const - { - return 0; - } - -private: - static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; - static const bitmap_t m_waiting_incompatible[MDL_TYPE_END]; -}; - - -/** - An implementation of a per-object lock. Supports SHARED, SHARED_UPGRADABLE, - SHARED HIGH PRIORITY and EXCLUSIVE locks. -*/ + ~MDL_lock() + { mysql_prlock_destroy(&m_rwlock); } -class MDL_object_lock : public MDL_lock -{ -public: - MDL_object_lock(const MDL_key *key_arg) - : MDL_lock(key_arg) - { } + static void lf_alloc_constructor(uchar *arg) + { new (arg + LF_HASH_OVERHEAD) MDL_lock(); } - /** - Reset unused MDL_object_lock object to represent the lock context for a - different object. - */ - void reset(const MDL_key *new_key) - { - /* We need to change only object's key. */ - key.mdl_key_init(new_key); - /* m_granted and m_waiting should be already in the empty/initial state. */ - DBUG_ASSERT(is_empty()); - /* Object should not be marked as destroyed. */ - DBUG_ASSERT(! m_is_destroyed); - /* - Values of the rest of the fields should be preserved between old and - new versions of the object. E.g., m_version and m_ref_usage/release - should be kept intact to properly handle possible remaining references - to the old version of the object. - */ - } + static void lf_alloc_destructor(uchar *arg) + { ((MDL_lock*)(arg + LF_HASH_OVERHEAD))->~MDL_lock(); } - virtual const bitmap_t *incompatible_granted_types_bitmap() const + static void lf_hash_initializer(LF_HASH *hash __attribute__((unused)), + MDL_lock *lock, MDL_key *key_arg) { - return m_granted_incompatible; - } - virtual const bitmap_t *incompatible_waiting_types_bitmap() const - { - return m_waiting_incompatible; - } - virtual bool needs_notification(const MDL_ticket *ticket) const - { - return ticket->is_upgradable_or_exclusive(); - } - virtual void notify_conflicting_locks(MDL_context *ctx); - - /* - To prevent starvation, these lock types that are only granted - max_write_lock_count times in a row while other lock types are - waiting. - */ - virtual bitmap_t hog_lock_types_bitmap() const - { - return (MDL_BIT(MDL_SHARED_NO_WRITE) | - MDL_BIT(MDL_SHARED_NO_READ_WRITE) | - MDL_BIT(MDL_EXCLUSIVE)); + DBUG_ASSERT(key_arg->mdl_namespace() != MDL_key::GLOBAL && + key_arg->mdl_namespace() != MDL_key::COMMIT); + new (&lock->key) MDL_key(key_arg); + if (key_arg->mdl_namespace() == MDL_key::SCHEMA) + lock->m_strategy= &m_scoped_lock_strategy; + else + lock->m_strategy= &m_object_lock_strategy; } + const MDL_lock_strategy *m_strategy; private: - static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; - static const bitmap_t m_waiting_incompatible[MDL_TYPE_END]; - -public: - /** Members for linking the object into the list of unused objects. */ - MDL_object_lock *next_in_cache, **prev_in_cache; + static const MDL_scoped_lock m_scoped_lock_strategy; + static const MDL_object_lock m_object_lock_strategy; }; -/** - Helper class for linking MDL_object_lock objects into the unused objects list. -*/ -class MDL_object_lock_cache_adapter : - public I_P_List_adapter<MDL_object_lock, &MDL_object_lock::next_in_cache, - &MDL_object_lock::prev_in_cache> -{ -}; +const MDL_lock::MDL_scoped_lock MDL_lock::m_scoped_lock_strategy; +const MDL_lock::MDL_object_lock MDL_lock::m_object_lock_strategy; static MDL_map mdl_locks; -/** - Start-up parameter for the maximum size of the unused MDL_lock objects cache. -*/ -ulong mdl_locks_cache_size; extern "C" @@ -649,37 +628,88 @@ void mdl_destroy() } -/** Initialize the global hash containing all MDL locks. */ +struct mdl_iterate_arg +{ + int (*callback)(MDL_ticket *ticket, void *arg); + void *argument; +}; + + +static my_bool mdl_iterate_lock(MDL_lock *lock, mdl_iterate_arg *arg) +{ + int res= FALSE; + /* + We can skip check for m_strategy here, becase m_granted + must be empty for such locks anyway. + */ + mysql_prlock_rdlock(&lock->m_rwlock); + MDL_lock::Ticket_iterator ticket_it(lock->m_granted); + MDL_ticket *ticket; + while ((ticket= ticket_it++) && !(res= arg->callback(ticket, arg->argument))) + /* no-op */; + mysql_prlock_unlock(&lock->m_rwlock); + return MY_TEST(res); +} + + +int mdl_iterate(int (*callback)(MDL_ticket *ticket, void *arg), void *arg) +{ + DBUG_ENTER("mdl_iterate"); + mdl_iterate_arg argument= { callback, arg }; + LF_PINS *pins= mdl_locks.get_pins(); + int res= 1; + + if (pins) + { + res= mdl_iterate_lock(mdl_locks.m_global_lock, &argument) || + mdl_iterate_lock(mdl_locks.m_commit_lock, &argument) || + lf_hash_iterate(&mdl_locks.m_locks, pins, + (my_hash_walk_action) mdl_iterate_lock, &argument); + lf_hash_put_pins(pins); + } + DBUG_RETURN(res); +} + + +my_hash_value_type mdl_hash_function(CHARSET_INFO *cs, + const uchar *key, size_t length) +{ + MDL_key *mdl_key= (MDL_key*) (key - my_offsetof(MDL_key, m_ptr)); + return mdl_key->hash_value(); +} + + +/** Initialize the container for all MDL locks. */ void MDL_map::init() { MDL_key global_lock_key(MDL_key::GLOBAL, "", ""); MDL_key commit_lock_key(MDL_key::COMMIT, "", ""); - mysql_mutex_init(key_MDL_map_mutex, &m_mutex, NULL); - my_hash_init(&m_locks, &my_charset_bin, 16 /* FIXME */, 0, 0, - mdl_locks_key, 0, 0); - m_global_lock= MDL_lock::create(&global_lock_key); - m_commit_lock= MDL_lock::create(&commit_lock_key); + m_global_lock= new (std::nothrow) MDL_lock(&global_lock_key); + m_commit_lock= new (std::nothrow) MDL_lock(&commit_lock_key); + + lf_hash_init(&m_locks, sizeof(MDL_lock), LF_HASH_UNIQUE, 0, 0, + mdl_locks_key, &my_charset_bin); + m_locks.alloc.constructor= MDL_lock::lf_alloc_constructor; + m_locks.alloc.destructor= MDL_lock::lf_alloc_destructor; + m_locks.initializer= (lf_hash_initializer) MDL_lock::lf_hash_initializer; + m_locks.hash_function= mdl_hash_function; } /** - Destroy the global hash containing all MDL locks. + Destroy the container for all MDL locks. @pre It must be empty. */ void MDL_map::destroy() { - DBUG_ASSERT(!m_locks.records); - mysql_mutex_destroy(&m_mutex); - my_hash_free(&m_locks); - MDL_lock::destroy(m_global_lock); - MDL_lock::destroy(m_commit_lock); + delete m_global_lock; + delete m_commit_lock; - MDL_object_lock *lock; - while ((lock= m_unused_locks_cache.pop_front())) - MDL_lock::destroy(lock); + DBUG_ASSERT(!my_atomic_load32(&m_locks.count)); + lf_hash_destroy(&m_locks); } @@ -692,16 +722,15 @@ void MDL_map::destroy() @retval NULL - Failure (OOM). */ -MDL_lock* MDL_map::find_or_insert(const MDL_key *mdl_key) +MDL_lock* MDL_map::find_or_insert(LF_PINS *pins, const MDL_key *mdl_key) { MDL_lock *lock; - my_hash_value_type hash_value; if (mdl_key->mdl_namespace() == MDL_key::GLOBAL || mdl_key->mdl_namespace() == MDL_key::COMMIT) { /* - Avoid locking m_mutex when lock for GLOBAL or COMMIT namespace is + Avoid locking any m_mutex when lock for GLOBAL or COMMIT namespace is requested. Return pointer to pre-allocated MDL_lock instance instead. Such an optimization allows to save one mutex lock/unlock for any statement changing data. @@ -719,140 +748,61 @@ MDL_lock* MDL_map::find_or_insert(const MDL_key *mdl_key) return lock; } - - hash_value= my_calc_hash(&m_locks, mdl_key->ptr(), mdl_key->length()); - retry: - mysql_mutex_lock(&m_mutex); - if (!(lock= (MDL_lock*) my_hash_search_using_hash_value(&m_locks, - hash_value, - mdl_key->ptr(), - mdl_key->length()))) - { - MDL_object_lock *unused_lock= NULL; - - /* - No lock object found so we need to create a new one - or reuse an existing unused object. - */ - if (mdl_key->mdl_namespace() != MDL_key::SCHEMA && - m_unused_locks_cache.elements()) - { - /* - We need a MDL_object_lock type of object and the unused objects - cache has some. Get the first object from the cache and set a new - key for it. - */ - DBUG_ASSERT(mdl_key->mdl_namespace() != MDL_key::GLOBAL && - mdl_key->mdl_namespace() != MDL_key::COMMIT); - - unused_lock= m_unused_locks_cache.pop_front(); - unused_lock->reset(mdl_key); - - lock= unused_lock; - } - else - { - lock= MDL_lock::create(mdl_key); - } - - if (!lock || my_hash_insert(&m_locks, (uchar*)lock)) - { - if (unused_lock) - { - /* - Note that we can't easily destroy an object from cache here as it - still might be referenced by other threads. So we simply put it - back into the cache. - */ - m_unused_locks_cache.push_front(unused_lock); - } - else - { - MDL_lock::destroy(lock); - } - mysql_mutex_unlock(&m_mutex); + while (!(lock= (MDL_lock*) lf_hash_search(&m_locks, pins, mdl_key->ptr(), + mdl_key->length()))) + if (lf_hash_insert(&m_locks, pins, (uchar*) mdl_key) == -1) return NULL; - } - } - if (move_from_hash_to_lock_mutex(lock)) + mysql_prlock_wrlock(&lock->m_rwlock); + if (unlikely(!lock->m_strategy)) + { + mysql_prlock_unlock(&lock->m_rwlock); + lf_hash_search_unpin(pins); goto retry; + } + lf_hash_search_unpin(pins); return lock; } /** - Release mdl_locks.m_mutex mutex and lock MDL_lock::m_rwlock for lock - object from the hash. Handle situation when object was released - while we held no locks. - - @retval FALSE - Success. - @retval TRUE - Object was released while we held no mutex, caller - should re-try looking up MDL_lock object in the hash. -*/ + * Return thread id of the owner of the lock, if it is owned. + */ -bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) +unsigned long +MDL_map::get_lock_owner(LF_PINS *pins, const MDL_key *mdl_key) { - ulonglong version; - - DBUG_ASSERT(! lock->m_is_destroyed); - mysql_mutex_assert_owner(&m_mutex); - - /* - We increment m_ref_usage which is a reference counter protected by - mdl_locks.m_mutex under the condition it is present in the hash and - m_is_destroyed is FALSE. - */ - lock->m_ref_usage++; - /* Read value of the version counter under protection of m_mutex lock. */ - version= lock->m_version; - mysql_mutex_unlock(&m_mutex); - - mysql_prlock_wrlock(&lock->m_rwlock); - lock->m_ref_release++; + MDL_lock *lock; + unsigned long res= 0; - if (unlikely(lock->m_version != version)) + if (mdl_key->mdl_namespace() == MDL_key::GLOBAL || + mdl_key->mdl_namespace() == MDL_key::COMMIT) { - /* - If the current value of version differs from one that was read while - we held m_mutex mutex, this MDL_lock object was moved to the unused - objects list or destroyed while we held no locks. - We should retry our search. But first we should destroy the MDL_lock - object if necessary. - */ - if (unlikely(lock->m_is_destroyed)) - { - /* - Object was released while we held no locks, we need to - release it if no others hold references to it, while our own - reference count ensured that the object as such haven't got - its memory released yet. We can also safely compare - m_ref_usage and m_ref_release since the object is no longer - present in the hash (or unused objects list) so no one will - be able to find it and increment m_ref_usage anymore. - */ - uint ref_usage= lock->m_ref_usage; - uint ref_release= lock->m_ref_release; - mysql_prlock_unlock(&lock->m_rwlock); - if (ref_usage == ref_release) - MDL_lock::destroy(lock); - } - else + lock= (mdl_key->mdl_namespace() == MDL_key::GLOBAL) ? m_global_lock : + m_commit_lock; + mysql_prlock_rdlock(&lock->m_rwlock); + res= lock->get_lock_owner(); + mysql_prlock_unlock(&lock->m_rwlock); + } + else + { + lock= (MDL_lock*) lf_hash_search(&m_locks, pins, mdl_key->ptr(), + mdl_key->length()); + if (lock) { /* - Object was not destroyed but its version has changed. - This means that it was moved to the unused objects list - (and even might be already re-used). So now it might - correspond to a different key, therefore we should simply - retry our search. + We can skip check for m_strategy here, becase m_granted + must be empty for such locks anyway. */ + mysql_prlock_rdlock(&lock->m_rwlock); + res= lock->get_lock_owner(); mysql_prlock_unlock(&lock->m_rwlock); + lf_hash_search_unpin(pins); } - return TRUE; } - return FALSE; + return res; } @@ -862,7 +812,7 @@ bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) it. */ -void MDL_map::remove(MDL_lock *lock) +void MDL_map::remove(LF_PINS *pins, MDL_lock *lock) { if (lock->key.mdl_namespace() == MDL_key::GLOBAL || lock->key.mdl_namespace() == MDL_key::COMMIT) @@ -875,65 +825,9 @@ void MDL_map::remove(MDL_lock *lock) return; } - mysql_mutex_lock(&m_mutex); - my_hash_delete(&m_locks, (uchar*) lock); - /* - To let threads holding references to the MDL_lock object know that it was - moved to the list of unused objects or destroyed, we increment the version - counter under protection of both MDL_map::m_mutex and MDL_lock::m_rwlock - locks. This allows us to read the version value while having either one - of those locks. - */ - lock->m_version++; - - if ((lock->key.mdl_namespace() != MDL_key::SCHEMA) && - (m_unused_locks_cache.elements() < mdl_locks_cache_size)) - { - /* - This is an object of MDL_object_lock type and the cache of unused - objects has not reached its maximum size yet. So instead of destroying - object we move it to the list of unused objects to allow its later - re-use with possibly different key. Any threads holding references to - this object (owning MDL_map::m_mutex or MDL_lock::m_rwlock) will notice - this thanks to the fact that we have changed the MDL_lock::m_version - counter. - */ - DBUG_ASSERT(lock->key.mdl_namespace() != MDL_key::GLOBAL && - lock->key.mdl_namespace() != MDL_key::COMMIT); - - m_unused_locks_cache.push_front((MDL_object_lock*)lock); - mysql_mutex_unlock(&m_mutex); - mysql_prlock_unlock(&lock->m_rwlock); - } - else - { - /* - Destroy the MDL_lock object, but ensure that anyone that is - holding a reference to the object is not remaining, if so he - has the responsibility to release it. - - Setting of m_is_destroyed to TRUE while holding _both_ - mdl_locks.m_mutex and MDL_lock::m_rwlock mutexes transfers the - protection of m_ref_usage from mdl_locks.m_mutex to - MDL_lock::m_rwlock while removal of the object from the hash - (and cache of unused objects) makes it read-only. Therefore - whoever acquires MDL_lock::m_rwlock next will see the most up - to date version of m_ref_usage. - - This means that when m_is_destroyed is TRUE and we hold the - MDL_lock::m_rwlock we can safely read the m_ref_usage - member. - */ - uint ref_usage, ref_release; - - lock->m_is_destroyed= TRUE; - ref_usage= lock->m_ref_usage; - ref_release= lock->m_ref_release; - mysql_mutex_unlock(&m_mutex); - mysql_prlock_unlock(&lock->m_rwlock); - if (ref_usage == ref_release) - MDL_lock::destroy(lock); - } + lock->m_strategy= 0; + mysql_prlock_unlock(&lock->m_rwlock); + lf_hash_delete(&m_locks, pins, lock->key.ptr(), lock->key.length()); } @@ -944,9 +838,11 @@ void MDL_map::remove(MDL_lock *lock) */ MDL_context::MDL_context() - : m_thd(NULL), + : + m_owner(NULL), m_needs_thr_lock_abort(FALSE), - m_waiting_for(NULL) + m_waiting_for(NULL), + m_pins(NULL) { mysql_prlock_init(key_MDL_context_LOCK_waiting_for, &m_LOCK_waiting_for); } @@ -966,11 +862,19 @@ MDL_context::MDL_context() void MDL_context::destroy() { - DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty() && - m_tickets[MDL_TRANSACTION].is_empty() && - m_tickets[MDL_EXPLICIT].is_empty()); + DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty()); + DBUG_ASSERT(m_tickets[MDL_TRANSACTION].is_empty()); + DBUG_ASSERT(m_tickets[MDL_EXPLICIT].is_empty()); mysql_prlock_destroy(&m_LOCK_waiting_for); + if (m_pins) + lf_hash_put_pins(m_pins); +} + + +bool MDL_context::fix_pins() +{ + return m_pins ? false : (m_pins= mdl_locks.get_pins()) == 0; } @@ -1028,32 +932,6 @@ void MDL_request::init(const MDL_key *key_arg, /** - Auxiliary functions needed for creation/destruction of MDL_lock objects. - - @note Also chooses an MDL_lock descendant appropriate for object namespace. -*/ - -inline MDL_lock *MDL_lock::create(const MDL_key *mdl_key) -{ - switch (mdl_key->mdl_namespace()) - { - case MDL_key::GLOBAL: - case MDL_key::SCHEMA: - case MDL_key::COMMIT: - return new MDL_scoped_lock(mdl_key); - default: - return new MDL_object_lock(mdl_key); - } -} - - -void MDL_lock::destroy(MDL_lock *lock) -{ - delete lock; -} - - -/** Auxiliary functions needed for creation/destruction of MDL_ticket objects. @@ -1067,7 +945,8 @@ MDL_ticket *MDL_ticket::create(MDL_context *ctx_arg, enum_mdl_type type_arg #endif ) { - return new MDL_ticket(ctx_arg, type_arg + return new (std::nothrow) + MDL_ticket(ctx_arg, type_arg #ifndef DBUG_OFF , duration_arg #endif @@ -1091,7 +970,7 @@ void MDL_ticket::destroy(MDL_ticket *ticket) uint MDL_ticket::get_deadlock_weight() const { return (m_lock->key.mdl_namespace() == MDL_key::GLOBAL || - m_type >= MDL_SHARED_NO_WRITE ? + m_type >= MDL_SHARED_UPGRADABLE ? DEADLOCK_WEIGHT_DDL : DEADLOCK_WEIGHT_DML); } @@ -1160,6 +1039,7 @@ void MDL_wait::reset_status() /** Wait for the status to be assigned to this wait slot. + @param owner MDL context owner. @param abs_timeout Absolute time after which waiting should stop. @param set_status_on_timeout TRUE - If in case of timeout waiting context should close the wait slot by @@ -1171,27 +1051,43 @@ void MDL_wait::reset_status() */ MDL_wait::enum_wait_status -MDL_wait::timed_wait(THD *thd, struct timespec *abs_timeout, - bool set_status_on_timeout, const char *wait_state_name) +MDL_wait::timed_wait(MDL_context_owner *owner, struct timespec *abs_timeout, + bool set_status_on_timeout, + const PSI_stage_info *wait_state_name) { - const char *old_msg; + PSI_stage_info old_stage; enum_wait_status result; int wait_result= 0; DBUG_ENTER("MDL_wait::timed_wait"); mysql_mutex_lock(&m_LOCK_wait_status); - old_msg= thd_enter_cond(thd, &m_COND_wait_status, &m_LOCK_wait_status, - wait_state_name); - - thd_wait_begin(thd, THD_WAIT_META_DATA_LOCK); - while (!m_wait_status && !thd->killed && + owner->ENTER_COND(&m_COND_wait_status, &m_LOCK_wait_status, + wait_state_name, & old_stage); + thd_wait_begin(NULL, THD_WAIT_META_DATA_LOCK); + while (!m_wait_status && !owner->is_killed() && wait_result != ETIMEDOUT && wait_result != ETIME) { +#ifdef WITH_WSREP + // Allow tests to block the applier thread using the DBUG facilities + DBUG_EXECUTE_IF("sync.wsrep_before_mdl_wait", + { + const char act[]= + "now " + "wait_for signal.wsrep_before_mdl_wait"; + DBUG_ASSERT(!debug_sync_set_action((owner->get_thd()), + STRING_WITH_LEN(act))); + };); + if (wsrep_thd_is_BF(owner->get_thd(), false)) + { + wait_result= mysql_cond_wait(&m_COND_wait_status, &m_LOCK_wait_status); + } + else +#endif /* WITH_WSREP */ wait_result= mysql_cond_timedwait(&m_COND_wait_status, &m_LOCK_wait_status, abs_timeout); } - thd_wait_end(thd); + thd_wait_end(NULL); if (m_wait_status == EMPTY) { @@ -1207,14 +1103,14 @@ MDL_wait::timed_wait(THD *thd, struct timespec *abs_timeout, false, which means that the caller intends to restart the wait. */ - if (thd->killed) + if (owner->is_killed()) m_wait_status= KILLED; else if (set_status_on_timeout) m_wait_status= TIMEOUT; } result= m_wait_status; - thd_exit_cond(thd, old_msg); + owner->EXIT_COND(& old_stage); DBUG_RETURN(result); } @@ -1254,11 +1150,57 @@ void MDL_lock::Ticket_list::add_ticket(MDL_ticket *ticket) called by other threads. */ DBUG_ASSERT(ticket->get_lock()); - /* - Add ticket to the *back* of the queue to ensure fairness - among requests with the same priority. - */ - m_list.push_back(ticket); +#ifdef WITH_WSREP + if ((this == &(ticket->get_lock()->m_waiting)) && + wsrep_thd_is_BF(ticket->get_ctx()->get_thd(), false)) + { + Ticket_iterator itw(ticket->get_lock()->m_waiting); + Ticket_iterator itg(ticket->get_lock()->m_granted); + + DBUG_ASSERT(WSREP_ON); + MDL_ticket *waiting, *granted; + MDL_ticket *prev=NULL; + bool added= false; + + while ((waiting= itw++) && !added) + { + if (!wsrep_thd_is_BF(waiting->get_ctx()->get_thd(), true)) + { + WSREP_DEBUG("MDL add_ticket inserted before: %lu %s", + thd_get_thread_id(waiting->get_ctx()->get_thd()), + wsrep_thd_query(waiting->get_ctx()->get_thd())); + /* Insert the ticket before the first non-BF waiting thd. */ + m_list.insert_after(prev, ticket); + added= true; + } + prev= waiting; + } + + /* Otherwise, insert the ticket at the back of the waiting list. */ + if (!added) m_list.push_back(ticket); + + while ((granted= itg++)) + { + if (granted->get_ctx() != ticket->get_ctx() && + granted->is_incompatible_when_granted(ticket->get_type())) + { + if (!wsrep_grant_mdl_exception(ticket->get_ctx(), granted, + &ticket->get_lock()->key)) + { + WSREP_DEBUG("MDL victim killed at add_ticket"); + } + } + } + } + else +#endif /* WITH_WSREP */ + { + /* + Add ticket to the *back* of the queue to ensure fairness + among requests with the same priority. + */ + m_list.push_back(ticket); + } m_bitmap|= MDL_BIT(ticket->get_type()); } @@ -1423,22 +1365,12 @@ void MDL_lock::reschedule_waiters() lock. Arrays of bitmaps which elements specify which granted/waiting locks are incompatible with type of lock being requested. - Here is how types of individual locks are translated to type of scoped lock: - - ----------------+-------------+ - Type of request | Correspond. | - for indiv. lock | scoped lock | - ----------------+-------------+ - S, SH, SR, SW | IS | - SNW, SNRW, X | IX | - SNW, SNRW -> X | IX (*) | - The first array specifies if particular type of request can be satisfied if there is granted scoped lock of certain type. | Type of active | Request | scoped lock | - type | IS(**) IX S X | + type | IS(*) IX S X | ---------+------------------+ IS | + + + + | IX | + + - - | @@ -1451,7 +1383,7 @@ void MDL_lock::reschedule_waiters() | Pending | Request | scoped lock | - type | IS(**) IX S X | + type | IS(*) IX S X | ---------+-----------------+ IS | + + + + | IX | + + - - | @@ -1461,24 +1393,35 @@ void MDL_lock::reschedule_waiters() Here: "+" -- means that request can be satisfied "-" -- means that request can't be satisfied and should wait - (*) Since for upgradable locks we always take intention exclusive scoped - lock at the same time when obtaining the shared lock, there is no - need to obtain such lock during the upgrade itself. - (**) Since intention shared scoped locks are compatible with all other - type of locks we don't even have any accounting for them. + (*) Since intention shared scoped locks are compatible with all other + type of locks we don't even have any accounting for them. + + Note that relation between scoped locks and objects locks requested + by statement is not straightforward and is therefore fully defined + by SQL-layer. + For example, in order to support global read lock implementation + SQL-layer acquires IX lock in GLOBAL namespace for each statement + that can modify metadata or data (i.e. for each statement that + needs SW, SU, SNW, SNRW or X object locks). OTOH, to ensure that + DROP DATABASE works correctly with concurrent DDL, IX metadata locks + in SCHEMA namespace are acquired for DDL statements which can update + metadata in the schema (i.e. which acquire SU, SNW, SNRW and X locks + on schema objects) and aren't acquired for DML. */ -const MDL_lock::bitmap_t MDL_scoped_lock::m_granted_incompatible[MDL_TYPE_END] = +const MDL_lock::bitmap_t +MDL_lock::MDL_scoped_lock::m_granted_incompatible[MDL_TYPE_END]= { MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED), - MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_INTENTION_EXCLUSIVE), 0, 0, 0, 0, 0, + MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_INTENTION_EXCLUSIVE), 0, 0, 0, 0, 0, 0, MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED) | MDL_BIT(MDL_INTENTION_EXCLUSIVE) }; -const MDL_lock::bitmap_t MDL_scoped_lock::m_waiting_incompatible[MDL_TYPE_END] = +const MDL_lock::bitmap_t +MDL_lock::MDL_scoped_lock::m_waiting_incompatible[MDL_TYPE_END]= { MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED), - MDL_BIT(MDL_EXCLUSIVE), 0, 0, 0, 0, 0, 0 + MDL_BIT(MDL_EXCLUSIVE), 0, 0, 0, 0, 0, 0, 0 }; @@ -1490,35 +1433,39 @@ const MDL_lock::bitmap_t MDL_scoped_lock::m_waiting_incompatible[MDL_TYPE_END] = The first array specifies if particular type of request can be satisfied if there is granted lock of certain type. - Request | Granted requests for lock | - type | S SH SR SW SNW SNRW X | - ----------+------------------------------+ - S | + + + + + + - | - SH | + + + + + + - | - SR | + + + + + - - | - SW | + + + + - - - | - SNW | + + + - - - - | - SNRW | + + - - - - - | - X | - - - - - - - | - SNW -> X | - - - 0 0 0 0 | - SNRW -> X | - - 0 0 0 0 0 | + Request | Granted requests for lock | + type | S SH SR SW SU SNW SNRW X | + ----------+----------------------------------+ + S | + + + + + + + - | + SH | + + + + + + + - | + SR | + + + + + + - - | + SW | + + + + + - - - | + SU | + + + + - - - - | + SNW | + + + - - - - - | + SNRW | + + - - - - - - | + X | - - - - - - - - | + SU -> X | - - - - 0 0 0 0 | + SNW -> X | - - - 0 0 0 0 0 | + SNRW -> X | - - 0 0 0 0 0 0 | The second array specifies if particular type of request can be satisfied if there is waiting request for the same lock of certain type. In other words it specifies what is the priority of different lock types. - Request | Pending requests for lock | - type | S SH SR SW SNW SNRW X | - ----------+-----------------------------+ - S | + + + + + + - | - SH | + + + + + + + | - SR | + + + + + - - | - SW | + + + + - - - | - SNW | + + + + + + - | - SNRW | + + + + + + - | - X | + + + + + + + | - SNW -> X | + + + + + + + | - SNRW -> X | + + + + + + + | + Request | Pending requests for lock | + type | S SH SR SW SU SNW SNRW X | + ----------+---------------------------------+ + S | + + + + + + + - | + SH | + + + + + + + + | + SR | + + + + + + - - | + SW | + + + + + - - - | + SU | + + + + + + + - | + SNW | + + + + + + + - | + SNRW | + + + + + + + - | + X | + + + + + + + + | + SU -> X | + + + + + + + + | + SNW -> X | + + + + + + + + | + SNRW -> X | + + + + + + + + | Here: "+" -- means that request can be satisfied "-" -- means that request can't be satisfied and should wait @@ -1527,10 +1474,13 @@ const MDL_lock::bitmap_t MDL_scoped_lock::m_waiting_incompatible[MDL_TYPE_END] = @note In cases then current context already has "stronger" type of lock on the object it will be automatically granted thanks to usage of the MDL_context::find_ticket() method. + + @note IX locks are excluded since they are not used for per-object + metadata locks. */ const MDL_lock::bitmap_t -MDL_object_lock::m_granted_incompatible[MDL_TYPE_END] = +MDL_lock::MDL_object_lock::m_granted_incompatible[MDL_TYPE_END]= { 0, MDL_BIT(MDL_EXCLUSIVE), @@ -1539,19 +1489,22 @@ MDL_object_lock::m_granted_incompatible[MDL_TYPE_END] = MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | MDL_BIT(MDL_SHARED_NO_WRITE), MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | - MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_WRITE), + MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_UPGRADABLE), + MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | + MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_UPGRADABLE) | + MDL_BIT(MDL_SHARED_WRITE), MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | - MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_WRITE) | - MDL_BIT(MDL_SHARED_READ), + MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_UPGRADABLE) | + MDL_BIT(MDL_SHARED_WRITE) | MDL_BIT(MDL_SHARED_READ), MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | - MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_WRITE) | - MDL_BIT(MDL_SHARED_READ) | MDL_BIT(MDL_SHARED_HIGH_PRIO) | - MDL_BIT(MDL_SHARED) + MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_UPGRADABLE) | + MDL_BIT(MDL_SHARED_WRITE) | MDL_BIT(MDL_SHARED_READ) | + MDL_BIT(MDL_SHARED_HIGH_PRIO) | MDL_BIT(MDL_SHARED) }; const MDL_lock::bitmap_t -MDL_object_lock::m_waiting_incompatible[MDL_TYPE_END] = +MDL_lock::MDL_object_lock::m_waiting_incompatible[MDL_TYPE_END]= { 0, MDL_BIT(MDL_EXCLUSIVE), @@ -1561,6 +1514,7 @@ MDL_object_lock::m_waiting_incompatible[MDL_TYPE_END] = MDL_BIT(MDL_SHARED_NO_WRITE), MDL_BIT(MDL_EXCLUSIVE), MDL_BIT(MDL_EXCLUSIVE), + MDL_BIT(MDL_EXCLUSIVE), 0 }; @@ -1589,6 +1543,7 @@ MDL_lock::can_grant_lock(enum_mdl_type type_arg, bool can_grant= FALSE; bitmap_t waiting_incompat_map= incompatible_waiting_types_bitmap()[type_arg]; bitmap_t granted_incompat_map= incompatible_granted_types_bitmap()[type_arg]; + bool wsrep_can_grant= TRUE; /* New lock request can be satisfied iff: @@ -1611,24 +1566,81 @@ MDL_lock::can_grant_lock(enum_mdl_type type_arg, { if (ticket->get_ctx() != requestor_ctx && ticket->is_incompatible_when_granted(type_arg)) + { +#ifdef WITH_WSREP + if (wsrep_thd_is_BF(requestor_ctx->get_thd(),false) && + key.mdl_namespace() == MDL_key::GLOBAL) + { + WSREP_DEBUG("global lock granted for BF: %lu %s", + thd_get_thread_id(requestor_ctx->get_thd()), + wsrep_thd_query(requestor_ctx->get_thd())); + can_grant = true; + } + else if (!wsrep_grant_mdl_exception(requestor_ctx, ticket, &key)) + { + wsrep_can_grant= FALSE; + if (wsrep_log_conflicts) + { + MDL_lock * lock = ticket->get_lock(); + WSREP_INFO( + "MDL conflict db=%s table=%s ticket=%d solved by %s", + lock->key.db_name(), lock->key.name(), ticket->get_type(), + "abort" ); + } + } + else + can_grant= TRUE; + /* Continue loop */ +#else break; +#endif /* WITH_WSREP */ + } } - if (ticket == NULL) /* Incompatible locks are our own. */ - can_grant= TRUE; + if ((ticket == NULL) && wsrep_can_grant) + can_grant= TRUE; /* Incompatible locks are our own. */ + } + } + else + { + if (wsrep_thd_is_BF(requestor_ctx->get_thd(), false) && + key.mdl_namespace() == MDL_key::GLOBAL) + { + WSREP_DEBUG("global lock granted for BF (waiting queue): %lu %s", + thd_get_thread_id(requestor_ctx->get_thd()), + wsrep_thd_query(requestor_ctx->get_thd())); + can_grant = true; } } return can_grant; } +/** + Return thread id of the thread to which the first ticket was + granted. +*/ + +inline unsigned long +MDL_lock::get_lock_owner() const +{ + Ticket_iterator it(m_granted); + MDL_ticket *ticket; + + if ((ticket= it++)) + return ticket->get_ctx()->get_thread_id(); + return 0; +} + + /** Remove a ticket from waiting or pending queue and wakeup up waiters. */ -void MDL_lock::remove_ticket(Ticket_list MDL_lock::*list, MDL_ticket *ticket) +void MDL_lock::remove_ticket(LF_PINS *pins, Ticket_list MDL_lock::*list, + MDL_ticket *ticket) { mysql_prlock_wrlock(&m_rwlock); (this->*list).remove_ticket(ticket); if (is_empty()) - mdl_locks.remove(this); + mdl_locks.remove(pins, this); else { /* @@ -1664,8 +1676,6 @@ bool MDL_lock::has_pending_conflicting_lock(enum_mdl_type type) { bool result; - mysql_mutex_assert_not_owner(&LOCK_open); - mysql_prlock_rdlock(&m_rwlock); result= (m_waiting.bitmap() & incompatible_granted_types_bitmap()[type]); mysql_prlock_unlock(&m_rwlock); @@ -1747,6 +1757,8 @@ MDL_context::find_ticket(MDL_request *mdl_request, if (mdl_request->key.is_equal(&ticket->m_lock->key) && ticket->has_stronger_or_equal_type(mdl_request->type)) { + DBUG_PRINT("info", ("Adding mdl lock %d to %d", + mdl_request->type, ticket->m_type)); *result_duration= duration; return ticket; } @@ -1838,7 +1850,6 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, /* Don't take chances in production. */ mdl_request->ticket= NULL; - mysql_mutex_assert_not_owner(&LOCK_open); /* Check whether the context already holds a shared lock on the object, @@ -1877,6 +1888,9 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, return FALSE; } + if (fix_pins()) + return TRUE; + if (!(ticket= MDL_ticket::create(this, mdl_request->type #ifndef DBUG_OFF , mdl_request->duration @@ -1885,7 +1899,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, return TRUE; /* The below call implicitly locks MDL_lock::m_rwlock on success. */ - if (!(lock= mdl_locks.find_or_insert(key))) + if (!(lock= mdl_locks.find_or_insert(m_pins, key))) { MDL_ticket::destroy(ticket); return TRUE; @@ -1928,7 +1942,17 @@ MDL_context::clone_ticket(MDL_request *mdl_request) { MDL_ticket *ticket; - mysql_mutex_assert_not_owner(&LOCK_open); + + /* + Since in theory we can clone ticket belonging to a different context + we need to prepare target context for possible attempts to release + lock and thus possible removal of MDL_lock from MDL_map container. + So we allocate pins to be able to work with this container if they + are not allocated already. + */ + if (fix_pins()) + return TRUE; + /* By submitting mdl_request->type to MDL_ticket::create() we effectively downgrade the cloned lock to the level of @@ -1958,69 +1982,52 @@ MDL_context::clone_ticket(MDL_request *mdl_request) /** - Notify threads holding a shared metadata locks on object which - conflict with a pending X, SNW or SNRW lock. + Check if there is any conflicting lock that could cause this thread + to wait for another thread which is not ready to commit. + This is always an error, as the upper level of parallel replication + should not allow a scheduling of a conflicting DDL until all earlier + transactions has commited. - @param ctx MDL_context for current thread. + This function is only called for a slave using parallel replication + and trying to get an exclusive lock for the table. */ -void MDL_object_lock::notify_conflicting_locks(MDL_context *ctx) +#ifndef DBUG_OFF +bool MDL_lock::check_if_conflicting_replication_locks(MDL_context *ctx) { Ticket_iterator it(m_granted); MDL_ticket *conflicting_ticket; + rpl_group_info *rgi_slave= ctx->get_thd()->rgi_slave; - while ((conflicting_ticket= it++)) - { - /* Only try to abort locks on which we back off. */ - if (conflicting_ticket->get_ctx() != ctx && - conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE) - - { - MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); - - /* - If thread which holds conflicting lock is waiting on table-level - lock or some other non-MDL resource we might need to wake it up - by calling code outside of MDL. - */ - mysql_notify_thread_having_shared_lock(ctx->get_thd(), - conflicting_ctx->get_thd(), - conflicting_ctx->get_needs_thr_lock_abort()); - } - } -} - - -/** - Notify threads holding scoped IX locks which conflict with a pending S lock. - - @param ctx MDL_context for current thread. -*/ - -void MDL_scoped_lock::notify_conflicting_locks(MDL_context *ctx) -{ - Ticket_iterator it(m_granted); - MDL_ticket *conflicting_ticket; + if (!rgi_slave->gtid_sub_id) + return 0; while ((conflicting_ticket= it++)) { - if (conflicting_ticket->get_ctx() != ctx && - conflicting_ticket->get_type() == MDL_INTENTION_EXCLUSIVE) - + if (conflicting_ticket->get_ctx() != ctx) { MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); + rpl_group_info *conflicting_rgi_slave; + conflicting_rgi_slave= conflicting_ctx->get_thd()->rgi_slave; /* - Thread which holds global IX lock can be a handler thread for - insert delayed. We need to kill such threads in order to get - global shared lock. We do this my calling code outside of MDL. + If the conflicting thread is another parallel replication + thread for the same master and it's not in commit stage, then + the current transaction has started too early and something is + seriously wrong. */ - mysql_notify_thread_having_shared_lock(ctx->get_thd(), - conflicting_ctx->get_thd(), - conflicting_ctx->get_needs_thr_lock_abort()); + if (conflicting_rgi_slave && + conflicting_rgi_slave->gtid_sub_id && + conflicting_rgi_slave->rli == rgi_slave->rli && + conflicting_rgi_slave->current_gtid.domain_id == + rgi_slave->current_gtid.domain_id && + !conflicting_rgi_slave->did_mark_start_commit) + return 1; // Fatal error } } + return 0; } +#endif /** @@ -2036,16 +2043,13 @@ void MDL_scoped_lock::notify_conflicting_locks(MDL_context *ctx) */ bool -MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) +MDL_context::acquire_lock(MDL_request *mdl_request, double lock_wait_timeout) { MDL_lock *lock; MDL_ticket *ticket; - struct timespec abs_timeout; MDL_wait::enum_wait_status wait_status; DBUG_ENTER("MDL_context::acquire_lock"); - - /* Do some work outside the critical section. */ - set_timespec(abs_timeout, lock_wait_timeout); + DBUG_PRINT("enter", ("lock_type: %d", mdl_request->type)); if (try_acquire_lock_impl(mdl_request, &ticket)) DBUG_RETURN(TRUE); @@ -2057,6 +2061,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) MDL_lock, MDL_context and MDL_request were updated accordingly, so we can simply return success. */ + DBUG_PRINT("info", ("Got lock without waiting")); DBUG_RETURN(FALSE); } @@ -2085,48 +2090,69 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) if (lock->needs_notification(ticket) && lock_wait_timeout) lock->notify_conflicting_locks(this); + /* + Ensure that if we are trying to get an exclusive lock for a slave + running parallel replication, then we are not blocked by another + parallel slave thread that is not committed. This should never happen as + the parallel replication scheduler should never schedule a DDL while + DML's are still running. + */ + DBUG_ASSERT((mdl_request->type != MDL_INTENTION_EXCLUSIVE && + mdl_request->type != MDL_EXCLUSIVE) || + !(get_thd()->rgi_slave && + get_thd()->rgi_slave->is_parallel_exec && + lock->check_if_conflicting_replication_locks(this))); + mysql_prlock_unlock(&lock->m_rwlock); will_wait_for(ticket); /* There is a shared or exclusive lock on the object. */ - DEBUG_SYNC(m_thd, "mdl_acquire_lock_wait"); + DEBUG_SYNC(get_thd(), "mdl_acquire_lock_wait"); find_deadlock(); - if (lock->needs_notification(ticket)) + struct timespec abs_timeout, abs_shortwait; + set_timespec_nsec(abs_timeout, + (ulonglong)(lock_wait_timeout * 1000000000ULL)); + set_timespec(abs_shortwait, 1); + wait_status= MDL_wait::EMPTY; + + while (cmp_timespec(abs_shortwait, abs_timeout) <= 0) { - struct timespec abs_shortwait; - set_timespec(abs_shortwait, 1); - wait_status= MDL_wait::EMPTY; + /* abs_timeout is far away. Wait a short while and notify locks. */ + wait_status= m_wait.timed_wait(m_owner, &abs_shortwait, FALSE, + mdl_request->key.get_wait_state_name()); - while (cmp_timespec(abs_shortwait, abs_timeout) <= 0) + if (wait_status != MDL_wait::EMPTY) + break; + /* Check if the client is gone while we were waiting. */ + if (! thd_is_connected(m_owner->get_thd())) { - /* abs_timeout is far away. Wait a short while and notify locks. */ - wait_status= m_wait.timed_wait(m_thd, &abs_shortwait, FALSE, - mdl_request->key.get_wait_state_name()); - - if (wait_status != MDL_wait::EMPTY) - break; + /* + * The client is disconnected. Don't wait forever: + * assume it's the same as a wait timeout, this + * ensures all error handling is correct. + */ + wait_status= MDL_wait::TIMEOUT; + break; + } - mysql_prlock_wrlock(&lock->m_rwlock); + mysql_prlock_wrlock(&lock->m_rwlock); + if (lock->needs_notification(ticket)) lock->notify_conflicting_locks(this); - mysql_prlock_unlock(&lock->m_rwlock); - set_timespec(abs_shortwait, 1); - } - if (wait_status == MDL_wait::EMPTY) - wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE, - mdl_request->key.get_wait_state_name()); + mysql_prlock_unlock(&lock->m_rwlock); + set_timespec(abs_shortwait, 1); } - else - wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE, + if (wait_status == MDL_wait::EMPTY) + wait_status= m_wait.timed_wait(m_owner, &abs_timeout, TRUE, mdl_request->key.get_wait_state_name()); done_waiting_for(); if (wait_status != MDL_wait::GRANTED) { - lock->remove_ticket(&MDL_lock::m_waiting, ticket); + lock->remove_ticket(m_pins, &MDL_lock::m_waiting, ticket); MDL_ticket::destroy(ticket); switch (wait_status) { @@ -2137,6 +2163,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); break; case MDL_wait::KILLED: + get_thd()->send_kill_message(); break; default: DBUG_ASSERT(0); @@ -2190,7 +2217,7 @@ extern "C" int mdl_request_ptr_cmp(const void* ptr1, const void* ptr2) */ bool MDL_context::acquire_locks(MDL_request_list *mdl_requests, - ulong lock_wait_timeout) + double lock_wait_timeout) { MDL_request_list::Iterator it(*mdl_requests); MDL_request **sort_buf, **p_req; @@ -2240,11 +2267,12 @@ err: /** - Upgrade a shared metadata lock to exclusive. + Upgrade a shared metadata lock. - Used in ALTER TABLE, when a copy of the table with the - new definition has been constructed. + Used in ALTER TABLE. + @param mdl_ticket Lock to upgrade. + @param new_type Lock type to upgrade to. @param lock_wait_timeout Seconds to wait before timeout. @note In case of failure to upgrade lock (e.g. because upgrader @@ -2252,7 +2280,7 @@ err: shared mode). @note There can be only one upgrader for a lock or we will have deadlock. - This invariant is ensured by the fact that upgradeable locks SNW + This invariant is ensured by the fact that upgradeable locks SU, SNW and SNRW are not compatible with each other and themselves. @retval FALSE Success @@ -2260,28 +2288,31 @@ err: */ bool -MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, - ulong lock_wait_timeout) +MDL_context::upgrade_shared_lock(MDL_ticket *mdl_ticket, + enum_mdl_type new_type, + double lock_wait_timeout) { MDL_request mdl_xlock_request; MDL_savepoint mdl_svp= mdl_savepoint(); bool is_new_ticket; - - DBUG_ENTER("MDL_ticket::upgrade_shared_lock_to_exclusive"); - DEBUG_SYNC(get_thd(), "mdl_upgrade_shared_lock_to_exclusive"); + DBUG_ENTER("MDL_context::upgrade_shared_lock"); + DBUG_PRINT("enter",("new_type: %d lock_wait_timeout: %f", new_type, + lock_wait_timeout)); + DEBUG_SYNC(get_thd(), "mdl_upgrade_lock"); /* Do nothing if already upgraded. Used when we FLUSH TABLE under LOCK TABLES and a table is listed twice in LOCK TABLES list. */ - if (mdl_ticket->m_type == MDL_EXCLUSIVE) + if (mdl_ticket->has_stronger_or_equal_type(new_type)) DBUG_RETURN(FALSE); - /* Only allow upgrades from MDL_SHARED_NO_WRITE/NO_READ_WRITE */ - DBUG_ASSERT(mdl_ticket->m_type == MDL_SHARED_NO_WRITE || + /* Only allow upgrades from SHARED_UPGRADABLE/NO_WRITE/NO_READ_WRITE */ + DBUG_ASSERT(mdl_ticket->m_type == MDL_SHARED_UPGRADABLE || + mdl_ticket->m_type == MDL_SHARED_NO_WRITE || mdl_ticket->m_type == MDL_SHARED_NO_READ_WRITE); - mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE, + mdl_xlock_request.init(&mdl_ticket->m_lock->key, new_type, MDL_TRANSACTION); if (acquire_lock(&mdl_xlock_request, lock_wait_timeout)) @@ -2299,7 +2330,7 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, ticket from the granted queue and then include it back. */ mdl_ticket->m_lock->m_granted.remove_ticket(mdl_ticket); - mdl_ticket->m_type= MDL_EXCLUSIVE; + mdl_ticket->m_type= new_type; mdl_ticket->m_lock->m_granted.add_ticket(mdl_ticket); mysql_prlock_unlock(&mdl_ticket->m_lock->m_rwlock); @@ -2573,13 +2604,12 @@ void MDL_context::release_lock(enum_mdl_duration duration, MDL_ticket *ticket) { MDL_lock *lock= ticket->m_lock; DBUG_ENTER("MDL_context::release_lock"); - DBUG_PRINT("enter", ("db=%s name=%s", lock->key.db_name(), - lock->key.name())); + DBUG_PRINT("enter", ("db: '%s' name: '%s'", + lock->key.db_name(), lock->key.name())); DBUG_ASSERT(this == ticket->get_ctx()); - mysql_mutex_assert_not_owner(&LOCK_open); - lock->remove_ticket(&MDL_lock::m_granted, ticket); + lock->remove_ticket(m_pins, &MDL_lock::m_granted, ticket); m_tickets[duration].remove(ticket); MDL_ticket::destroy(ticket); @@ -2612,7 +2642,7 @@ void MDL_context::release_lock(MDL_ticket *ticket) the corresponding lists, i.e. stored in reverse temporal order. This allows to employ this function to: - back off in case of a lock conflict. - - release all locks in the end of a statment or transaction + - release all locks in the end of a statement or transaction - rollback to a savepoint. */ @@ -2663,22 +2693,27 @@ void MDL_context::release_all_locks_for_name(MDL_ticket *name) /** - Downgrade an exclusive lock to shared metadata lock. + Downgrade an EXCLUSIVE or SHARED_NO_WRITE lock to shared metadata lock. @param type Type of lock to which exclusive lock should be downgraded. */ -void MDL_ticket::downgrade_exclusive_lock(enum_mdl_type type) +void MDL_ticket::downgrade_lock(enum_mdl_type type) { - mysql_mutex_assert_not_owner(&LOCK_open); - /* Do nothing if already downgraded. Used when we FLUSH TABLE under LOCK TABLES and a table is listed twice in LOCK TABLES list. + Note that this code might even try to "downgrade" a weak lock + (e.g. SW) to a stronger one (e.g SNRW). So we can't even assert + here that target lock is weaker than existing lock. */ - if (m_type != MDL_EXCLUSIVE) + if (m_type == type || !has_stronger_or_equal_type(type)) return; + /* Only allow downgrade from EXCLUSIVE and SHARED_NO_WRITE. */ + DBUG_ASSERT(m_type == MDL_EXCLUSIVE || + m_type == MDL_SHARED_NO_WRITE); + mysql_prlock_wrlock(&m_lock->m_rwlock); /* To update state of MDL_lock object correctly we need to temporarily @@ -2724,6 +2759,23 @@ MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, /** + Return thread id of the owner of the lock or 0 if + there is no owner. + @note: Lock type is not considered at all, the function + simply checks that there is some lock for the given key. + + @return thread id of the owner of the lock or 0 +*/ + +unsigned long +MDL_context::get_lock_owner(MDL_key *key) +{ + fix_pins(); + return mdl_locks.get_lock_owner(m_pins, key); +} + + +/** Check if we have any pending locks which conflict with existing shared lock. @pre The ticket must match an acquired lock. @@ -2736,6 +2788,11 @@ bool MDL_ticket::has_pending_conflicting_lock() const return m_lock->has_pending_conflicting_lock(m_type); } +/** Return a key identifying this lock. */ +MDL_key *MDL_ticket::get_key() const +{ + return &m_lock->key; +} /** Releases metadata locks that were acquired after a specific savepoint. @@ -2917,3 +2974,57 @@ void MDL_context::set_transaction_duration_for_all_locks() ticket->m_duration= MDL_TRANSACTION; #endif } + + + +void MDL_context::release_explicit_locks() +{ + release_locks_stored_before(MDL_EXPLICIT, NULL); +} + +bool MDL_context::has_explicit_locks() +{ + MDL_ticket *ticket = NULL; + + Ticket_iterator it(m_tickets[MDL_EXPLICIT]); + + while ((ticket = it++)) + { + return true; + } + + return false; +} + +#ifdef WITH_WSREP +void MDL_ticket::wsrep_report(bool debug) +{ + if (debug) + { + const PSI_stage_info *psi_stage = m_lock->key.get_wait_state_name(); + + WSREP_DEBUG("MDL ticket: type: %s space: %s db: %s name: %s (%s)", + (get_type() == MDL_INTENTION_EXCLUSIVE) ? "intention exclusive" : + ((get_type() == MDL_SHARED) ? "shared" : + ((get_type() == MDL_SHARED_HIGH_PRIO ? "shared high prio" : + ((get_type() == MDL_SHARED_READ) ? "shared read" : + ((get_type() == MDL_SHARED_WRITE) ? "shared write" : + ((get_type() == MDL_SHARED_NO_WRITE) ? "shared no write" : + ((get_type() == MDL_SHARED_NO_READ_WRITE) ? "shared no read write" : + ((get_type() == MDL_EXCLUSIVE) ? "exclusive" : + "UNKNOWN")))))))), + (m_lock->key.mdl_namespace() == MDL_key::GLOBAL) ? "GLOBAL" : + ((m_lock->key.mdl_namespace() == MDL_key::SCHEMA) ? "SCHEMA" : + ((m_lock->key.mdl_namespace() == MDL_key::TABLE) ? "TABLE" : + ((m_lock->key.mdl_namespace() == MDL_key::TABLE) ? "FUNCTION" : + ((m_lock->key.mdl_namespace() == MDL_key::TABLE) ? "PROCEDURE" : + ((m_lock->key.mdl_namespace() == MDL_key::TABLE) ? "TRIGGER" : + ((m_lock->key.mdl_namespace() == MDL_key::TABLE) ? "EVENT" : + ((m_lock->key.mdl_namespace() == MDL_key::COMMIT) ? "COMMIT" : + (char *)"UNKNOWN"))))))), + m_lock->key.db_name(), + m_lock->key.name(), + psi_stage->m_name); + } +} +#endif /* WITH_WSREP */ diff --git a/sql/mdl.h b/sql/mdl.h index 68f24a7a0e8..15a1976876b 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -1,6 +1,6 @@ #ifndef MDL_H #define MDL_H -/* Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2009, 2012, 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 @@ -12,8 +12,8 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ #if defined(__IBMC__) || defined(__IBMCPP__) /* Further down, "next_in_lock" and "next_in_context" have the same type, @@ -26,15 +26,97 @@ #include "sql_plist.h" #include <my_sys.h> -#include <my_pthread.h> #include <m_string.h> #include <mysql_com.h> +#include <lf.h> + +#include <algorithm> class THD; class MDL_context; class MDL_lock; class MDL_ticket; +bool ok_for_lower_case_names(const char *name); + +/** + @def ENTER_COND(C, M, S, O) + Start a wait on a condition. + @param C the condition to wait on + @param M the associated mutex + @param S the new stage to enter + @param O the previous stage + @sa EXIT_COND(). +*/ +#define ENTER_COND(C, M, S, O) enter_cond(C, M, S, O, __func__, __FILE__, __LINE__) + +/** + @def EXIT_COND(S) + End a wait on a condition + @param S the new stage to enter +*/ +#define EXIT_COND(S) exit_cond(S, __func__, __FILE__, __LINE__) + +/** + An interface to separate the MDL module from the THD, and the rest of the + server code. + */ + +class MDL_context_owner +{ +public: + virtual ~MDL_context_owner() {} + + /** + Enter a condition wait. + For @c enter_cond() / @c exit_cond() to work the mutex must be held before + @c enter_cond(); this mutex is then released by @c exit_cond(). + Usage must be: lock mutex; enter_cond(); your code; exit_cond(). + @param cond the condition to wait on + @param mutex the associated mutex + @param [in] stage the stage to enter, or NULL + @param [out] old_stage the previous stage, or NULL + @param src_function function name of the caller + @param src_file file name of the caller + @param src_line line number of the caller + @sa ENTER_COND(), THD::enter_cond() + @sa EXIT_COND(), THD::exit_cond() + */ + virtual void enter_cond(mysql_cond_t *cond, mysql_mutex_t *mutex, + const PSI_stage_info *stage, PSI_stage_info *old_stage, + const char *src_function, const char *src_file, + int src_line) = 0; + + /** + @def EXIT_COND(S) + End a wait on a condition + @param [in] stage the new stage to enter + @param src_function function name of the caller + @param src_file file name of the caller + @param src_line line number of the caller + @sa ENTER_COND(), THD::enter_cond() + @sa EXIT_COND(), THD::exit_cond() + */ + virtual void exit_cond(const PSI_stage_info *stage, + const char *src_function, const char *src_file, + int src_line) = 0; + /** + Has the owner thread been killed? + */ + virtual int is_killed() = 0; + + /** + This one is only used for DEBUG_SYNC. + (Do not use it to peek/poke into other parts of THD.) + */ + virtual THD* get_thd() = 0; + + /** + @see THD::notify_shared_lock() + */ + virtual bool notify_shared_lock(MDL_context_owner *in_use, + bool needs_thr_lock_abort) = 0; +}; /** Type of metadata lock request. @@ -114,6 +196,15 @@ enum enum_mdl_type { */ MDL_SHARED_WRITE, /* + An upgradable shared metadata lock for cases when there is an intention + to modify (and not just read) data in the table. + Can be upgraded to MDL_SHARED_NO_WRITE and MDL_EXCLUSIVE. + A connection holding SU lock can read table metadata and modify or read + table data (after acquiring appropriate table and row-level locks). + To be used for the first phase of ALTER TABLE. + */ + MDL_SHARED_UPGRADABLE, + /* An upgradable shared metadata lock which blocks all attempts to update table data, allowing reads. A connection holding this kind of lock can read table metadata and read @@ -189,6 +280,10 @@ enum enum_mdl_duration { class MDL_key { public: +#ifdef HAVE_PSI_INTERFACE + static void init_psi_keys(); +#endif + /** Object namespaces. Sic: when adding a new member to this enum make sure to @@ -212,6 +307,7 @@ public: TRIGGER, EVENT, COMMIT, + USER_LOCK, /* user level locks. */ /* This should be the last ! */ NAMESPACE_END }; @@ -238,24 +334,32 @@ public: @param name Name of of the object @param key Where to store the the MDL key. */ - void mdl_key_init(enum_mdl_namespace mdl_namespace, - const char *db, const char *name) + void mdl_key_init(enum_mdl_namespace mdl_namespace_arg, + const char *db, const char *name_arg) { - m_ptr[0]= (char) mdl_namespace; + m_ptr[0]= (char) mdl_namespace_arg; /* It is responsibility of caller to ensure that db and object names are not longer than NAME_LEN. Still we play safe and try to avoid buffer overruns. */ - m_db_name_length= (uint16) (strmake(m_ptr + 1, db, NAME_LEN) - m_ptr - 1); - m_length= (uint16) (strmake(m_ptr + m_db_name_length + 2, name, NAME_LEN) - - m_ptr + 1); + DBUG_ASSERT(strlen(db) <= NAME_LEN); + DBUG_ASSERT(strlen(name_arg) <= NAME_LEN); + m_db_name_length= static_cast<uint16>(strmake(m_ptr + 1, db, NAME_LEN) - + m_ptr - 1); + m_length= static_cast<uint16>(strmake(m_ptr + m_db_name_length + 2, + name_arg, + NAME_LEN) - m_ptr + 1); + m_hash_value= my_hash_sort(&my_charset_bin, (uchar*) m_ptr + 1, + m_length - 1); + DBUG_ASSERT(mdl_namespace_arg == USER_LOCK || ok_for_lower_case_names(db)); } void mdl_key_init(const MDL_key *rhs) { memcpy(m_ptr, rhs->m_ptr, rhs->m_length); m_length= rhs->m_length; m_db_name_length= rhs->m_db_name_length; + m_hash_value= rhs->m_hash_value; } bool is_equal(const MDL_key *rhs) const { @@ -272,6 +376,7 @@ public: character set is utf-8, we can safely assume that no character starts with a zero byte. */ + using std::min; return memcmp(m_ptr, rhs->m_ptr, min(m_length, rhs->m_length)); } @@ -290,19 +395,30 @@ public: Get thread state name to be used in case when we have to wait on resource identified by key. */ - const char * get_wait_state_name() const + const PSI_stage_info * get_wait_state_name() const + { + return & m_namespace_to_wait_state_name[(int)mdl_namespace()]; + } + my_hash_value_type hash_value() const + { + return m_hash_value + mdl_namespace(); + } + my_hash_value_type tc_hash_value() const { - return m_namespace_to_wait_state_name[(int)mdl_namespace()]; + return m_hash_value; } private: uint16 m_length; uint16 m_db_name_length; + my_hash_value_type m_hash_value; char m_ptr[MAX_MDLKEY_LENGTH]; - static const char * m_namespace_to_wait_state_name[NAMESPACE_END]; + static PSI_stage_info m_namespace_to_wait_state_name[NAMESPACE_END]; private: MDL_key(const MDL_key &); /* not implemented */ MDL_key &operator=(const MDL_key &); /* not implemented */ + friend my_hash_value_type mdl_hash_function(CHARSET_INFO *, + const uchar *, size_t); }; @@ -342,6 +458,7 @@ public: MDL_key key; public: + static void *operator new(size_t size, MEM_ROOT *mem_root) throw () { return alloc_root(mem_root, size); } static void operator delete(void *ptr, MEM_ROOT *mem_root) {} @@ -358,6 +475,16 @@ public: DBUG_ASSERT(ticket == NULL); type= type_arg; } + void move_from(MDL_request &from) + { + type= from.type; + duration= from.duration; + ticket= from.ticket; + next_in_list= from.next_in_list; + prev_in_list= from.prev_in_list; + key.mdl_key_init(&from.key); + from.ticket= NULL; // that's what "move" means + } /* This is to work around the ugliness of TABLE_LIST @@ -406,17 +533,7 @@ public: virtual bool inspect_edge(MDL_context *dest) = 0; virtual ~MDL_wait_for_graph_visitor(); - MDL_wait_for_graph_visitor() :m_lock_open_count(0) {} -public: - /** - XXX, hack: During deadlock search, we may need to - inspect TABLE_SHAREs and acquire LOCK_open. Since - LOCK_open is not a recursive mutex, count here how many - times we "took" it (but only take and release once). - Not using a native recursive mutex or rwlock in 5.5 for - LOCK_open since it has significant performance impacts. - */ - uint m_lock_open_count; + MDL_wait_for_graph_visitor() {} }; /** @@ -481,18 +598,23 @@ public: MDL_ticket *next_in_lock; MDL_ticket **prev_in_lock; public: +#ifdef WITH_WSREP + void wsrep_report(bool debug); +#endif /* WITH_WSREP */ bool has_pending_conflicting_lock() const; MDL_context *get_ctx() const { return m_ctx; } bool is_upgradable_or_exclusive() const { - return m_type == MDL_SHARED_NO_WRITE || + return m_type == MDL_SHARED_UPGRADABLE || + m_type == MDL_SHARED_NO_WRITE || m_type == MDL_SHARED_NO_READ_WRITE || m_type == MDL_EXCLUSIVE; } enum_mdl_type get_type() const { return m_type; } MDL_lock *get_lock() const { return m_lock; } - void downgrade_exclusive_lock(enum_mdl_type type); + MDL_key *get_key() const; + void downgrade_lock(enum_mdl_type type); bool has_stronger_or_equal_type(enum_mdl_type type) const; @@ -598,8 +720,10 @@ public: bool set_status(enum_wait_status result_arg); enum_wait_status get_status(); void reset_status(); - enum_wait_status timed_wait(THD *thd, struct timespec *abs_timeout, - bool signal_timeout, const char *wait_state_name); + enum_wait_status timed_wait(MDL_context_owner *owner, + struct timespec *abs_timeout, + bool signal_timeout, + const PSI_stage_info *wait_state_name); private: /** Condvar which is used for waiting until this context's pending @@ -640,10 +764,11 @@ public: void destroy(); bool try_acquire_lock(MDL_request *mdl_request); - bool acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout); - bool acquire_locks(MDL_request_list *requests, ulong lock_wait_timeout); - bool upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, - ulong lock_wait_timeout); + bool acquire_lock(MDL_request *mdl_request, double lock_wait_timeout); + bool acquire_locks(MDL_request_list *requests, double lock_wait_timeout); + bool upgrade_shared_lock(MDL_ticket *mdl_ticket, + enum_mdl_type new_type, + double lock_wait_timeout); bool clone_ticket(MDL_request *mdl_request); @@ -653,6 +778,7 @@ public: bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, const char *name, enum_mdl_type mdl_type); + unsigned long get_lock_owner(MDL_key *mdl_key); bool has_lock(const MDL_savepoint &mdl_savepoint, MDL_ticket *mdl_ticket); @@ -662,6 +788,10 @@ public: m_tickets[MDL_TRANSACTION].is_empty() && m_tickets[MDL_EXPLICIT].is_empty()); } + inline bool has_transactional_locks() const + { + return !m_tickets[MDL_TRANSACTION].is_empty(); + } MDL_savepoint mdl_savepoint() { @@ -675,9 +805,10 @@ public: void release_statement_locks(); void release_transactional_locks(); + void release_explicit_locks(); void rollback_to_savepoint(const MDL_savepoint &mdl_savepoint); - inline THD *get_thd() const { return m_thd; } + MDL_context_owner *get_owner() { return m_owner; } /** @pre Only valid if we started waiting for lock. */ inline uint get_deadlock_weight() const @@ -690,7 +821,7 @@ public: already has received some signal or closed signal slot. */ - void init(THD *thd_arg) { m_thd= thd_arg; } + void init(MDL_context_owner *arg) { m_owner= arg; } void set_needs_thr_lock_abort(bool needs_thr_lock_abort) { @@ -721,9 +852,9 @@ private: Lists of MDL tickets: --------------------- The entire set of locks acquired by a connection can be separated - in three subsets according to their: locks released at the end of - statement, at the end of transaction and locks are released - explicitly. + in three subsets according to their duration: locks released at + the end of statement, at the end of transaction and locks are + released explicitly. Statement and transactional locks are locks with automatic scope. They are accumulated in the course of a transaction, and released @@ -732,11 +863,12 @@ private: locks). They must not be (and never are) released manually, i.e. with release_lock() call. - Locks with explicit duration are taken for locks that span + Tickets with explicit duration are taken for locks that span multiple transactions or savepoints. These are: HANDLER SQL locks (HANDLER SQL is transaction-agnostic), LOCK TABLES locks (you can COMMIT/etc - under LOCK TABLES, and the locked tables stay locked), and + under LOCK TABLES, and the locked tables stay locked), user level + locks (GET_LOCK()/RELEASE_LOCK() functions) and locks implementing "global read lock". Statement/transactional locks are always prepended to the @@ -745,20 +877,19 @@ private: a savepoint, we start popping and releasing tickets from the front until we reach the last ticket acquired after the savepoint. - Locks with explicit duration stored are not stored in any + Locks with explicit duration are not stored in any particular order, and among each other can be split into - three sets: + four sets: - [LOCK TABLES locks] [HANDLER locks] [GLOBAL READ LOCK locks] + [LOCK TABLES locks] [USER locks] [HANDLER locks] [GLOBAL READ LOCK locks] The following is known about these sets: - * GLOBAL READ LOCK locks are always stored after LOCK TABLES - locks and after HANDLER locks. This is because one can't say - SET GLOBAL read_only=1 or FLUSH TABLES WITH READ LOCK - if one has locked tables. One can, however, LOCK TABLES - after having entered the read only mode. Note, that - subsequent LOCK TABLES statement will unlock the previous + * GLOBAL READ LOCK locks are always stored last. + This is because one can't say SET GLOBAL read_only=1 or + FLUSH TABLES WITH READ LOCK if one has locked tables. One can, + however, LOCK TABLES after having entered the read only mode. + Note, that subsequent LOCK TABLES statement will unlock the previous set of tables, but not the GRL! There are no HANDLER locks after GRL locks because SET GLOBAL read_only performs a FLUSH TABLES WITH @@ -770,7 +901,7 @@ private: involved schemas and global intention exclusive lock. */ Ticket_list m_tickets[MDL_DURATION_END]; - THD *m_thd; + MDL_context_owner *m_owner; /** TRUE - if for this context we will break protocol and try to acquire table-level locks while having only S lock on @@ -798,6 +929,7 @@ private: readily available to the wait-for graph iterator. */ MDL_wait_for_subgraph *m_waiting_for; + LF_PINS *m_pins; private: MDL_ticket *find_ticket(MDL_request *mdl_req, enum_mdl_duration *duration); @@ -805,10 +937,15 @@ private: void release_lock(enum_mdl_duration duration, MDL_ticket *ticket); bool try_acquire_lock_impl(MDL_request *mdl_request, MDL_ticket **out_ticket); + bool fix_pins(); public: + THD *get_thd() const { return m_owner->get_thd(); } + bool has_explicit_locks(); void find_deadlock(); + ulong get_thread_id() const { return thd_get_thread_id(get_thd()); } + bool visit_subgraph(MDL_wait_for_graph_visitor *dvisitor); /** Inform the deadlock detector there is an edge in the wait-for graph. */ @@ -837,34 +974,27 @@ public: private: MDL_context(const MDL_context &rhs); /* not implemented */ MDL_context &operator=(MDL_context &rhs); /* not implemented */ + + /* metadata_lock_info plugin */ + friend int i_s_metadata_lock_info_fill_row(MDL_ticket*, void*); }; void mdl_init(); void mdl_destroy(); +extern "C" unsigned long thd_get_thread_id(const MYSQL_THD thd); -/* - Functions in the server's kernel used by metadata locking subsystem. -*/ - -extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, - bool needs_thr_lock_abort); -extern "C" const char* thd_enter_cond(MYSQL_THD thd, mysql_cond_t *cond, - mysql_mutex_t *mutex, const char *msg); -extern "C" void thd_exit_cond(MYSQL_THD thd, const char *old_msg); - -#ifndef DBUG_OFF -extern mysql_mutex_t LOCK_open; -#endif - +/** + Check if a connection in question is no longer connected. -/* - Start-up parameter for the maximum size of the unused MDL_lock objects cache - and a constant for its default value. + @details + Replication apply thread is always connected. Otherwise, + does a poll on the associated socket to check if the client + is gone. */ -extern ulong mdl_locks_cache_size; -static const ulong MDL_LOCKS_CACHE_SIZE_DEFAULT = 1024; +extern "C" int thd_is_connected(MYSQL_THD thd); + /* Metadata locking subsystem tries not to grant more than @@ -872,4 +1002,7 @@ static const ulong MDL_LOCKS_CACHE_SIZE_DEFAULT = 1024; to avoid starving out weak, low-prio locks. */ extern "C" ulong max_write_lock_count; + +extern MYSQL_PLUGIN_IMPORT +int mdl_iterate(int (*callback)(MDL_ticket *ticket, void *arg), void *arg); #endif diff --git a/sql/mem_root_array.h b/sql/mem_root_array.h index 2dcc475cd7b..5daeedadcba 100644 --- a/sql/mem_root_array.h +++ b/sql/mem_root_array.h @@ -47,12 +47,21 @@ template<typename Element_type, bool has_trivial_destructor> class Mem_root_array { public: + /// Convenience typedef, same typedef name as std::vector + typedef Element_type value_type; + Mem_root_array(MEM_ROOT *root) : m_root(root), m_array(NULL), m_size(0), m_capacity(0) { DBUG_ASSERT(m_root != NULL); } + Mem_root_array(MEM_ROOT *root, size_t n, const value_type &val= value_type()) + : m_root(root), m_array(NULL), m_size(0), m_capacity(0) + { + resize(n, val); + } + ~Mem_root_array() { clear(); @@ -70,6 +79,12 @@ public: return m_array[n]; } + Element_type &operator[](size_t n) { return at(n); } + const Element_type &operator[](size_t n) const { return at(n); } + + Element_type &back() { return at(size() - 1); } + const Element_type &back() const { return at(size() - 1); } + // Returns a pointer to the first element in the array. Element_type *begin() { return &m_array[0]; } @@ -155,6 +170,58 @@ public: return false; } + /** + Removes the last element in the array, effectively reducing the + container size by one. This destroys the removed element. + */ + void pop_back() + { + DBUG_ASSERT(!empty()); + if (!has_trivial_destructor) + back().~Element_type(); + m_size-= 1; + } + + /** + Resizes the container so that it contains n elements. + + If n is smaller than the current container size, the content is + reduced to its first n elements, removing those beyond (and + destroying them). + + If n is greater than the current container size, the content is + expanded by inserting at the end as many elements as needed to + reach a size of n. If val is specified, the new elements are + initialized as copies of val, otherwise, they are + value-initialized. + + If n is also greater than the current container capacity, an automatic + reallocation of the allocated storage space takes place. + + Notice that this function changes the actual content of the + container by inserting or erasing elements from it. + */ + void resize(size_t n, const value_type &val= value_type()) + { + if (n == m_size) + return; + if (n > m_size) + { + if (!reserve(n)) + { + while (n != m_size) + push_back(val); + } + return; + } + if (!has_trivial_destructor) + { + while (n != m_size) + pop_back(); + } + m_size= n; + } + size_t capacity() const { return m_capacity; } size_t element_size() const { return sizeof(Element_type); } bool empty() const { return size() == 0; } diff --git a/sql/mf_iocache.cc b/sql/mf_iocache.cc index d8848c1ee35..6535f16445b 100644 --- a/sql/mf_iocache.cc +++ b/sql/mf_iocache.cc @@ -32,6 +32,7 @@ flush_io_cache(). */ +#include <my_global.h> #include "sql_priv.h" #include "sql_class.h" // THD #ifdef HAVE_REPLICATION @@ -57,7 +58,7 @@ int _my_b_net_read(register IO_CACHE *info, uchar *Buffer, if (!info->end_of_file) DBUG_RETURN(1); /* because my_b_get (no _) takes 1 byte at a time */ - read_length=my_net_read(net); + read_length= my_net_read_packet(net, 0); if (read_length == packet_error) { info->error= -1; diff --git a/sql/mf_iocache_encr.cc b/sql/mf_iocache_encr.cc new file mode 100644 index 00000000000..f26a437a25a --- /dev/null +++ b/sql/mf_iocache_encr.cc @@ -0,0 +1,264 @@ +/* + Copyright (c) 2015, 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 */ + +/************************************************************************* + Limitation of encrypted IO_CACHEs + 1. Designed to support temporary files only (open_cached_file, fd=-1) + 2. Created with WRITE_CACHE, later can be reinit_io_cache'ed to + READ_CACHE and WRITE_CACHE in any order arbitrary number of times. + 3. no seeks for writes, but reinit_io_cache(WRITE_CACHE, seek_offset) + is allowed (there's a special hack in reinit_io_cache() for that) +*/ + +#include "../mysys/mysys_priv.h" +#include "log.h" +#include "mysqld.h" +#include "sql_class.h" + +static uint keyid, keyver; + +#define set_iv(IV, N1, N2) \ + do { \ + compile_time_assert(sizeof(IV) >= sizeof(N1) + sizeof(N2)); \ + memcpy(IV, &(N1), sizeof(N1)); \ + memcpy(IV + sizeof(N1), &(N2), sizeof(N2)); \ + } while(0) + +static int my_b_encr_read(IO_CACHE *info, uchar *Buffer, size_t Count) +{ + my_off_t pos_in_file= info->pos_in_file + (info->read_end - info->buffer); + my_off_t old_pos_in_file= pos_in_file, pos_offset= 0; + IO_CACHE_CRYPT *crypt_data= + (IO_CACHE_CRYPT *)(info->buffer + info->buffer_length + MY_AES_BLOCK_SIZE); + uchar *wbuffer= (uchar*)&(crypt_data->inbuf_counter); + uchar *ebuffer= (uchar*)(crypt_data + 1); + DBUG_ENTER("my_b_encr_read"); + + if (pos_in_file == info->end_of_file) + { + /* reading past EOF should not empty the cache */ + info->read_pos= info->read_end; + info->error= 0; + DBUG_RETURN(MY_TEST(Count)); + } + + if (info->seek_not_done) + { + size_t wpos; + + pos_offset= pos_in_file % info->buffer_length; + pos_in_file-= pos_offset; + + wpos= pos_in_file / info->buffer_length * crypt_data->block_length; + + if ((mysql_file_seek(info->file, wpos, MY_SEEK_SET, MYF(0)) + == MY_FILEPOS_ERROR)) + { + info->error= -1; + DBUG_RETURN(1); + } + info->seek_not_done= 0; + } + + do + { + size_t copied; + uint elength, wlength, length; + uchar iv[MY_AES_BLOCK_SIZE]= {0}; + + DBUG_ASSERT(pos_in_file % info->buffer_length == 0); + + if (info->end_of_file - pos_in_file >= info->buffer_length) + wlength= crypt_data->block_length; + else + wlength= crypt_data->last_block_length; + + if (mysql_file_read(info->file, wbuffer, wlength, info->myflags | MY_NABP)) + { + info->error= -1; + DBUG_RETURN(1); + } + + elength= wlength - (ebuffer - wbuffer); + set_iv(iv, pos_in_file, crypt_data->inbuf_counter); + + if (encryption_crypt(ebuffer, elength, info->buffer, &length, + crypt_data->key, sizeof(crypt_data->key), + iv, sizeof(iv), ENCRYPTION_FLAG_DECRYPT, + keyid, keyver)) + { + my_errno= 1; + DBUG_RETURN(info->error= -1); + } + + DBUG_ASSERT(length <= info->buffer_length); + + copied= MY_MIN(Count, length - pos_offset); + + memcpy(Buffer, info->buffer + pos_offset, copied); + Count-= copied; + Buffer+= copied; + + info->read_pos= info->buffer + pos_offset + copied; + info->read_end= info->buffer + length; + info->pos_in_file= pos_in_file; + pos_in_file+= length; + pos_offset= 0; + + if (wlength < crypt_data->block_length && pos_in_file < info->end_of_file) + { + info->error= pos_in_file - old_pos_in_file; + DBUG_RETURN(1); + } + } while (Count); + + DBUG_RETURN(0); +} + +static int my_b_encr_write(IO_CACHE *info, const uchar *Buffer, size_t Count) +{ + IO_CACHE_CRYPT *crypt_data= + (IO_CACHE_CRYPT *)(info->buffer + info->buffer_length + MY_AES_BLOCK_SIZE); + uchar *wbuffer= (uchar*)&(crypt_data->inbuf_counter); + uchar *ebuffer= (uchar*)(crypt_data + 1); + DBUG_ENTER("my_b_encr_write"); + + if (Buffer != info->write_buffer) + { + Count-= Count % info->buffer_length; + if (!Count) + DBUG_RETURN(0); + } + + if (info->seek_not_done) + { + DBUG_ASSERT(info->pos_in_file % info->buffer_length == 0); + size_t wpos= info->pos_in_file / info->buffer_length * crypt_data->block_length; + + if ((mysql_file_seek(info->file, wpos, MY_SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR)) + { + info->error= -1; + DBUG_RETURN(1); + } + info->seek_not_done= 0; + } + + if (info->pos_in_file == 0) + { + if (my_random_bytes(crypt_data->key, sizeof(crypt_data->key))) + { + my_errno= 1; + DBUG_RETURN(info->error= -1); + } + crypt_data->counter= 0; + + IF_DBUG(crypt_data->block_length= 0,); + } + + do + { + size_t length= MY_MIN(info->buffer_length, Count); + uint elength, wlength; + uchar iv[MY_AES_BLOCK_SIZE]= {0}; + + crypt_data->inbuf_counter= crypt_data->counter; + set_iv(iv, info->pos_in_file, crypt_data->inbuf_counter); + + if (encryption_crypt(Buffer, length, ebuffer, &elength, + crypt_data->key, sizeof(crypt_data->key), + iv, sizeof(iv), ENCRYPTION_FLAG_ENCRYPT, + keyid, keyver)) + { + my_errno= 1; + DBUG_RETURN(info->error= -1); + } + wlength= elength + ebuffer - wbuffer; + + if (length == info->buffer_length) + { + /* + block_length should be always the same. that is, encrypting + buffer_length bytes should *always* produce block_length bytes + */ + DBUG_ASSERT(crypt_data->block_length == 0 || crypt_data->block_length == wlength); + DBUG_ASSERT(elength <= encryption_encrypted_length(length, keyid, keyver)); + crypt_data->block_length= wlength; + } + else + { + /* if we write a partial block, it *must* be the last write */ + IF_DBUG(info->write_function= 0,); + crypt_data->last_block_length= wlength; + } + + if (mysql_file_write(info->file, wbuffer, wlength, info->myflags | MY_NABP)) + DBUG_RETURN(info->error= -1); + + Buffer+= length; + Count-= length; + info->pos_in_file+= length; + crypt_data->counter++; + } while (Count); + DBUG_RETURN(0); +} + +/** + determine what key id and key version to use for IO_CACHE temp files + + First, try key id 2, if it doesn't exist, use key id 1. + + (key id 1 is the default system key id, used pretty much everywhere, it must + exist. key id 2 is for tempfiles, it can be used, for example, to set a + faster encryption algorithm for temporary files) + + This looks like it might have a bug: if an encryption plugin is unloaded when + there's an open IO_CACHE, that IO_CACHE will become unreadable after reinit. + But in fact it is safe, as an encryption plugin can only be unloaded on + server shutdown. + + Note that encrypt_tmp_files variable is read-only. +*/ +int init_io_cache_encryption() +{ + if (encrypt_tmp_files) + { + keyid= ENCRYPTION_KEY_TEMPORARY_DATA; + keyver= encryption_key_get_latest_version(keyid); + if (keyver == ENCRYPTION_KEY_VERSION_INVALID) + { + keyid= ENCRYPTION_KEY_SYSTEM_DATA; + keyver= encryption_key_get_latest_version(keyid); + } + if (keyver == ENCRYPTION_KEY_VERSION_INVALID) + { + sql_print_error("Failed to enable encryption of temporary files"); + return 1; + } + + if (keyver != ENCRYPTION_KEY_NOT_ENCRYPTED) + { + sql_print_information("Using encryption key id %d for temporary files", keyid); + _my_b_encr_read= my_b_encr_read; + _my_b_encr_write= my_b_encr_write; + return 0; + } + } + + _my_b_encr_read= 0; + _my_b_encr_write= 0; + return 0; +} + diff --git a/sql/multi_range_read.cc b/sql/multi_range_read.cc index 2479bc3258c..50918d8dcf2 100644 --- a/sql/multi_range_read.cc +++ b/sql/multi_range_read.cc @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "sql_parse.h" #include <my_bit.h> @@ -56,13 +56,13 @@ ha_rows handler::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq, void *seq_init_param, uint n_ranges_arg, - uint *bufsz, uint *flags, COST_VECT *cost) + uint *bufsz, uint *flags, Cost_estimate *cost) { KEY_MULTI_RANGE range; range_seq_t seq_it; ha_rows rows, total_rows= 0; uint n_ranges=0; - THD *thd= current_thd; + THD *thd= table->in_use; /* Default MRR implementation doesn't need buffer */ *bufsz= 0; @@ -106,7 +106,7 @@ handler::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq, { /* The following calculation is the same as in multi_range_read_info(): */ *flags |= HA_MRR_USE_DEFAULT_IMPL; - cost->zero(); + cost->reset(); cost->avg_io_cost= 1; /* assume random seeks */ if ((*flags & HA_MRR_INDEX_ONLY) && total_rows > 2) cost->io_count= keyread_time(keyno, n_ranges, (uint)total_rows); @@ -154,7 +154,7 @@ handler::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq, ha_rows handler::multi_range_read_info(uint keyno, uint n_ranges, uint n_rows, uint key_parts, uint *bufsz, - uint *flags, COST_VECT *cost) + uint *flags, Cost_estimate *cost) { /* Currently we expect this function to be called only in preparation of scan @@ -165,7 +165,7 @@ ha_rows handler::multi_range_read_info(uint keyno, uint n_ranges, uint n_rows, *bufsz= 0; /* Default implementation doesn't need a buffer */ *flags |= HA_MRR_USE_DEFAULT_IMPL; - cost->zero(); + cost->reset(); cost->avg_io_cost= 1; /* assume random seeks */ /* Produce the same cost as non-MRR code does */ @@ -199,12 +199,6 @@ ha_rows handler::multi_range_read_info(uint keyno, uint n_ranges, uint n_rows, One must have called index_init() before calling this function. Several multi_range_read_init() calls may be made in course of one query. - Until WL#2623 is done (see its text, section 3.2), the following will - also hold: - The caller will guarantee that if "seq->init == mrr_ranges_array_init" - then seq_init_param is an array of n_ranges KEY_MULTI_RANGE structures. - This property will only be used by NDB handler until WL#2623 is done. - Buffer memory management is done according to the following scenario: The caller allocates the buffer and provides it to the callee by filling the members of HANDLER_BUFFER structure. @@ -225,7 +219,7 @@ handler::multi_range_read_init(RANGE_SEQ_IF *seq_funcs, void *seq_init_param, DBUG_ENTER("handler::multi_range_read_init"); mrr_iter= seq_funcs->init(seq_init_param, n_ranges, mode); mrr_funcs= *seq_funcs; - mrr_is_output_sorted= test(mode & HA_MRR_SORTED); + mrr_is_output_sorted= MY_TEST(mode & HA_MRR_SORTED); mrr_have_range= FALSE; DBUG_RETURN(0); } @@ -267,7 +261,7 @@ int handler::multi_range_read_next(range_id_t *range_info) } else { - if (was_semi_consistent_read()) + if (ha_was_semi_consistent_read()) { /* The following assignment is redundant, but for extra safety and to @@ -292,7 +286,7 @@ scan_it_again: &mrr_cur_range.start_key : 0, mrr_cur_range.end_key.keypart_map ? &mrr_cur_range.end_key : 0, - test(mrr_cur_range.range_flag & EQ_RANGE), + MY_TEST(mrr_cur_range.range_flag & EQ_RANGE), mrr_is_output_sorted); if (result != HA_ERR_END_OF_FILE) break; @@ -556,12 +550,12 @@ int Mrr_ordered_index_reader::init(handler *h_arg, RANGE_SEQ_IF *seq_funcs, keypar= *key_par_arg; KEY *key_info= &file->get_table()->key_info[file->active_index]; - keypar.index_ranges_unique= test(key_info->flags & HA_NOSAME && - key_info->key_parts == - my_count_bits(keypar.key_tuple_map)); + keypar.index_ranges_unique= MY_TEST(key_info->flags & HA_NOSAME && + key_info->user_defined_key_parts == + my_count_bits(keypar.key_tuple_map)); mrr_iter= seq_funcs->init(seq_init_param, n_ranges, mode); - is_mrr_assoc= !test(mode & HA_MRR_NO_ASSOCIATION); + is_mrr_assoc= !MY_TEST(mode & HA_MRR_NO_ASSOCIATION); mrr_funcs= *seq_funcs; source_exhausted= FALSE; read_was_interrupted= false; @@ -584,7 +578,7 @@ int Mrr_ordered_rndpos_reader::init(handler *h_arg, file= h_arg; index_reader= index_reader_arg; rowid_buffer= buf; - is_mrr_assoc= !test(mode & HA_MRR_NO_ASSOCIATION); + is_mrr_assoc= !MY_TEST(mode & HA_MRR_NO_ASSOCIATION); index_reader_exhausted= FALSE; index_reader_needs_refill= TRUE; return 0; @@ -820,21 +814,20 @@ int DsMrr_impl::dsmrr_init(handler *h_arg, RANGE_SEQ_IF *seq_funcs, void *seq_init_param, uint n_ranges, uint mode, HANDLER_BUFFER *buf) { - THD *thd= current_thd; + THD *thd= h_arg->get_table()->in_use; int res; Key_parameters keypar; - uint key_buff_elem_size; + uint UNINIT_VAR(key_buff_elem_size); /* set/used when do_sort_keys==TRUE */ handler *h_idx; Mrr_ordered_rndpos_reader *disk_strategy= NULL; bool do_sort_keys= FALSE; DBUG_ENTER("DsMrr_impl::dsmrr_init"); - LINT_INIT(key_buff_elem_size); /* set/used when do_sort_keys==TRUE */ /* index_merge may invoke a scan on an object for which dsmrr_info[_const] has not been called, so set the owner handler here as well. */ primary_file= h_arg; - is_mrr_assoc= !test(mode & HA_MRR_NO_ASSOCIATION); + is_mrr_assoc= !MY_TEST(mode & HA_MRR_NO_ASSOCIATION); strategy_exhausted= FALSE; @@ -884,7 +877,7 @@ int DsMrr_impl::dsmrr_init(handler *h_arg, RANGE_SEQ_IF *seq_funcs, if (do_sort_keys) { /* Pre-calculate some parameters of key sorting */ - keypar.use_key_pointers= test(mode & HA_MRR_MATERIALIZED_KEYS); + keypar.use_key_pointers= MY_TEST(mode & HA_MRR_MATERIALIZED_KEYS); seq_funcs->get_key_info(seq_init_param, &keypar.key_tuple_length, &keypar.key_tuple_map); keypar.key_size_in_keybuf= keypar.use_key_pointers? @@ -1013,7 +1006,7 @@ use_default_impl: so small that it can accomodate one rowid and one index tuple) */ if ((res= primary_file->ha_rnd_end()) || - (res= primary_file->ha_index_init(keyno, test(mode & HA_MRR_SORTED)))) + (res= primary_file->ha_index_init(keyno, MY_TEST(mode & HA_MRR_SORTED)))) { DBUG_RETURN(res); } @@ -1217,9 +1210,9 @@ bool DsMrr_impl::setup_buffer_sharing(uint key_size_in_keybuf, statistics? */ uint parts= my_count_bits(key_tuple_map); - ulong rpc; + ha_rows rpc; ulonglong rowids_size= rowid_buf_elem_size; - if ((rpc= key_info->rec_per_key[parts - 1])) + if ((rpc= (ha_rows) key_info->actual_rec_per_key(parts - 1))) rowids_size= rowid_buf_elem_size * rpc; double fraction_for_rowids= @@ -1231,28 +1224,18 @@ bool DsMrr_impl::setup_buffer_sharing(uint key_size_in_keybuf, ptrdiff_t bytes_for_keys= (full_buf_end - full_buf) - bytes_for_rowids; - if (bytes_for_keys < key_buff_elem_size + 1) - { - ptrdiff_t add= key_buff_elem_size + 1 - bytes_for_keys; - bytes_for_keys= key_buff_elem_size + 1; - bytes_for_rowids -= add; - } - - if (bytes_for_rowids < (ptrdiff_t)rowid_buf_elem_size + 1) - { - ptrdiff_t add= (ptrdiff_t)(rowid_buf_elem_size + 1 - bytes_for_rowids); - bytes_for_rowids= (ptrdiff_t)rowid_buf_elem_size + 1; - bytes_for_keys -= add; - } + if (bytes_for_keys < key_buff_elem_size + 1 || + bytes_for_rowids < (ptrdiff_t)rowid_buf_elem_size + 1) + return TRUE; /* Failed to provide minimum space for one of the buffers */ rowid_buffer_end= full_buf + bytes_for_rowids; rowid_buffer.set_buffer_space(full_buf, rowid_buffer_end); key_buffer= &backward_key_buf; key_buffer->set_buffer_space(rowid_buffer_end, full_buf_end); - if (!key_buffer->have_space_for(key_buff_elem_size) || - !rowid_buffer.have_space_for((size_t)rowid_buf_elem_size)) - return TRUE; /* Failed to provide minimum space for one of the buffers */ + /* The above code guarantees that the buffers are big enough */ + DBUG_ASSERT(key_buffer->have_space_for(key_buff_elem_size) && + rowid_buffer.have_space_for((size_t)rowid_buf_elem_size)); return FALSE; } @@ -1358,8 +1341,14 @@ int Key_value_records_iterator::get_next(range_id_t *range_info) } handler *h= owner->file; + uchar *lookup_key; + if (owner->keypar.use_key_pointers) + memcpy(&lookup_key, identical_key_it.read_ptr1, sizeof(void*)); + else + lookup_key= identical_key_it.read_ptr1; + if ((res= h->ha_index_next_same(h->get_table()->record[0], - identical_key_it.read_ptr1, + lookup_key, owner->keypar.key_tuple_length))) { /* It's either HA_ERR_END_OF_FILE or some other error */ @@ -1420,7 +1409,7 @@ int DsMrr_impl::dsmrr_next(range_id_t *range_info) */ ha_rows DsMrr_impl::dsmrr_info(uint keyno, uint n_ranges, uint rows, uint key_parts, - uint *bufsz, uint *flags, COST_VECT *cost) + uint *bufsz, uint *flags, Cost_estimate *cost) { ha_rows res __attribute__((unused)); uint def_flags= *flags; @@ -1455,7 +1444,7 @@ ha_rows DsMrr_impl::dsmrr_info(uint keyno, uint n_ranges, uint rows, ha_rows DsMrr_impl::dsmrr_info_const(uint keyno, RANGE_SEQ_IF *seq, void *seq_init_param, uint n_ranges, - uint *bufsz, uint *flags, COST_VECT *cost) + uint *bufsz, uint *flags, Cost_estimate *cost) { ha_rows rows; uint def_flags= *flags; @@ -1514,7 +1503,7 @@ ha_rows DsMrr_impl::dsmrr_info_const(uint keyno, RANGE_SEQ_IF *seq, bool key_uses_partial_cols(TABLE_SHARE *share, uint keyno) { KEY_PART_INFO *kp= share->key_info[keyno].key_part; - KEY_PART_INFO *kp_end= kp + share->key_info[keyno].key_parts; + KEY_PART_INFO *kp_end= kp + share->key_info[keyno].user_defined_key_parts; for (; kp != kp_end; kp++) { if (!kp->field->part_of_key.is_set(keyno)) @@ -1538,10 +1527,10 @@ bool key_uses_partial_cols(TABLE_SHARE *share, uint keyno) bool DsMrr_impl::check_cpk_scan(THD *thd, TABLE_SHARE *share, uint keyno, uint mrr_flags) { - return test((mrr_flags & HA_MRR_SINGLE_POINT) && - keyno == share->primary_key && - primary_file->primary_key_is_clustered() && - optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_SORT_KEYS)); + return MY_TEST((mrr_flags & HA_MRR_SINGLE_POINT) && + keyno == share->primary_key && + primary_file->primary_key_is_clustered() && + optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_SORT_KEYS)); } @@ -1570,16 +1559,16 @@ bool DsMrr_impl::check_cpk_scan(THD *thd, TABLE_SHARE *share, uint keyno, bool DsMrr_impl::choose_mrr_impl(uint keyno, ha_rows rows, uint *flags, - uint *bufsz, COST_VECT *cost) + uint *bufsz, Cost_estimate *cost) { - COST_VECT dsmrr_cost; + Cost_estimate dsmrr_cost; bool res; - THD *thd= current_thd; + THD *thd= primary_file->get_table()->in_use; TABLE_SHARE *share= primary_file->get_table_share(); bool doing_cpk_scan= check_cpk_scan(thd, share, keyno, *flags); - bool using_cpk= test(keyno == share->primary_key && - primary_file->primary_key_is_clustered()); + bool using_cpk= MY_TEST(keyno == share->primary_key && + primary_file->primary_key_is_clustered()); *flags &= ~HA_MRR_IMPLEMENTATION_FLAGS; if (!optimizer_flag(thd, OPTIMIZER_SWITCH_MRR) || *flags & HA_MRR_INDEX_ONLY || @@ -1667,7 +1656,7 @@ int DsMrr_impl::dsmrr_explain_info(uint mrr_mode, char *str, size_t size) used_str= rowid_ordered; uint used_str_len= strlen(used_str); - uint copy_len= min(used_str_len, size); + uint copy_len= MY_MIN(used_str_len, size); memcpy(str, used_str, copy_len); return copy_len; } @@ -1675,7 +1664,7 @@ int DsMrr_impl::dsmrr_explain_info(uint mrr_mode, char *str, size_t size) } -static void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, COST_VECT *cost); +static void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, Cost_estimate *cost); /** @@ -1693,7 +1682,7 @@ static void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, COST_VECT *cost */ bool DsMrr_impl::get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags, - uint *buffer_size, COST_VECT *cost) + uint *buffer_size, Cost_estimate *cost) { ulong max_buff_entries, elem_size; ha_rows rows_in_full_step; @@ -1702,7 +1691,7 @@ bool DsMrr_impl::get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags, double index_read_cost; elem_size= primary_file->ref_length + - sizeof(void*) * (!test(flags & HA_MRR_NO_ASSOCIATION)); + sizeof(void*) * (!MY_TEST(flags & HA_MRR_NO_ASSOCIATION)); max_buff_entries = *buffer_size / elem_size; if (!max_buff_entries) @@ -1727,13 +1716,13 @@ bool DsMrr_impl::get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags, } else { - cost->zero(); - *buffer_size= max(*buffer_size, + cost->reset(); + *buffer_size= MY_MAX(*buffer_size, (size_t)(1.2*rows_in_last_step) * elem_size + primary_file->ref_length + table->key_info[keynr].key_length); } - COST_VECT last_step_cost; + Cost_estimate last_step_cost; get_sort_and_sweep_cost(table, rows_in_last_step, &last_step_cost); cost->add(&last_step_cost); @@ -1762,7 +1751,7 @@ bool DsMrr_impl::get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags, */ static -void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, COST_VECT *cost) +void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, Cost_estimate *cost) { if (nrows) { @@ -1774,7 +1763,7 @@ void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, COST_VECT *cost) cost->cpu_cost += cmp_op * log2(cmp_op); } else - cost->zero(); + cost->reset(); } @@ -1822,11 +1811,11 @@ void get_sort_and_sweep_cost(TABLE *table, ha_rows nrows, COST_VECT *cost) */ void get_sweep_read_cost(TABLE *table, ha_rows nrows, bool interrupted, - COST_VECT *cost) + Cost_estimate *cost) { DBUG_ENTER("get_sweep_read_cost"); - cost->zero(); + cost->reset(); if (table->file->primary_key_is_clustered()) { cost->io_count= table->file->read_time(table->s->primary_key, diff --git a/sql/multi_range_read.h b/sql/multi_range_read.h index 0819b6fe948..b8234998f74 100644 --- a/sql/multi_range_read.h +++ b/sql/multi_range_read.h @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /** @defgroup DS-MRR declarations @@ -339,7 +339,7 @@ private: uchar *saved_key_tuple; /* Saved current key tuple */ uchar *saved_primary_key; /* Saved current primary key tuple */ - + /* TRUE<=> saved_key_tuple (and saved_primary_key when applicable) have valid values. @@ -568,11 +568,11 @@ public: int dsmrr_next(range_id_t *range_info); ha_rows dsmrr_info(uint keyno, uint n_ranges, uint keys, uint key_parts, - uint *bufsz, uint *flags, COST_VECT *cost); + uint *bufsz, uint *flags, Cost_estimate *cost); ha_rows dsmrr_info_const(uint keyno, RANGE_SEQ_IF *seq, void *seq_init_param, uint n_ranges, uint *bufsz, - uint *flags, COST_VECT *cost); + uint *flags, Cost_estimate *cost); int dsmrr_explain_info(uint mrr_mode, char *str, size_t size); private: @@ -630,9 +630,9 @@ private: Forward_lifo_buffer rowid_buffer; bool choose_mrr_impl(uint keyno, ha_rows rows, uint *flags, uint *bufsz, - COST_VECT *cost); + Cost_estimate *cost); bool get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags, - uint *buffer_size, COST_VECT *cost); + uint *buffer_size, Cost_estimate *cost); bool check_cpk_scan(THD *thd, TABLE_SHARE *share, uint keyno, uint mrr_flags); bool setup_buffer_sharing(uint key_size_in_keybuf, key_part_map key_tuple_map); diff --git a/sql/my_apc.cc b/sql/my_apc.cc new file mode 100644 index 00000000000..b165a801ce5 --- /dev/null +++ b/sql/my_apc.cc @@ -0,0 +1,231 @@ +/* + Copyright (c) 2011, 2013 Monty Program Ab. + + 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 02111-1301 USA */ + + +#ifndef MY_APC_STANDALONE + +#include "sql_class.h" + +#endif + +/* For standalone testing of APC system, see unittest/sql/my_apc-t.cc */ + +/* + Initialize the target. + + @note + Initialization must be done prior to enabling/disabling the target, or making + any call requests to it. + Initial state after initialization is 'disabled'. +*/ +void Apc_target::init(mysql_mutex_t *target_mutex) +{ + DBUG_ASSERT(!enabled); + LOCK_thd_data_ptr= target_mutex; +#ifndef DBUG_OFF + n_calls_processed= 0; +#endif +} + + +/* [internal] Put request qe into the request list */ + +void Apc_target::enqueue_request(Call_request *qe) +{ + mysql_mutex_assert_owner(LOCK_thd_data_ptr); + if (apc_calls) + { + Call_request *after= apc_calls->prev; + qe->next= apc_calls; + apc_calls->prev= qe; + + qe->prev= after; + after->next= qe; + } + else + { + apc_calls= qe; + qe->next= qe->prev= qe; + } +} + + +/* + [internal] Remove request qe from the request queue. + + The request is not necessarily first in the queue. +*/ + +void Apc_target::dequeue_request(Call_request *qe) +{ + mysql_mutex_assert_owner(LOCK_thd_data_ptr); + if (apc_calls == qe) + { + if ((apc_calls= apc_calls->next) == qe) + { + apc_calls= NULL; + } + } + + qe->prev->next= qe->next; + qe->next->prev= qe->prev; +} + +#ifdef HAVE_PSI_INTERFACE + +/* One key for all conds */ +PSI_cond_key key_show_explain_request_COND; + +static PSI_cond_info show_explain_psi_conds[]= +{ + { &key_show_explain_request_COND, "show_explain", 0 /* not using PSI_FLAG_GLOBAL*/ } +}; + +void init_show_explain_psi_keys(void) +{ + if (PSI_server == NULL) + return; + + PSI_server->register_cond("sql", show_explain_psi_conds, + array_elements(show_explain_psi_conds)); +} +#endif + + +/* + Make an APC (Async Procedure Call) to another thread. + + @detail + Make an APC call: schedule it for execution and wait until the target + thread has executed it. + + - The caller is responsible for making sure he's not posting request + to the thread he's calling this function from. + + - The caller must have locked target_mutex. The function will release it. + + @retval FALSE - Ok, the call has been made + @retval TRUE - Call wasnt made (either the target is in disabled state or + timeout occurred) +*/ + +bool Apc_target::make_apc_call(THD *caller_thd, Apc_call *call, + int timeout_sec, bool *timed_out) +{ + bool res= TRUE; + *timed_out= FALSE; + + if (enabled) + { + /* Create and post the request */ + Call_request apc_request; + apc_request.call= call; + apc_request.processed= FALSE; + mysql_cond_init(key_show_explain_request_COND, &apc_request.COND_request, + NULL); + enqueue_request(&apc_request); + apc_request.what="enqueued by make_apc_call"; + + struct timespec abstime; + const int timeout= timeout_sec; + set_timespec(abstime, timeout); + + int wait_res= 0; + PSI_stage_info old_stage; + caller_thd->ENTER_COND(&apc_request.COND_request, LOCK_thd_data_ptr, + &stage_show_explain, &old_stage); + /* todo: how about processing other errors here? */ + while (!apc_request.processed && (wait_res != ETIMEDOUT)) + { + /* We own LOCK_thd_data_ptr */ + wait_res= mysql_cond_timedwait(&apc_request.COND_request, + LOCK_thd_data_ptr, &abstime); + // &apc_request.LOCK_request, &abstime); + if (caller_thd->killed) + break; + } + + if (!apc_request.processed) + { + /* + The wait has timed out, or this thread was KILLed. + Remove the request from the queue (ok to do because we own + LOCK_thd_data_ptr) + */ + apc_request.processed= TRUE; + dequeue_request(&apc_request); + *timed_out= TRUE; + res= TRUE; + } + else + { + /* Request was successfully executed and dequeued by the target thread */ + res= FALSE; + } + /* + exit_cond() will call mysql_mutex_unlock(LOCK_thd_data_ptr) for us: + */ + caller_thd->EXIT_COND(&old_stage); + + /* Destroy all APC request data */ + mysql_cond_destroy(&apc_request.COND_request); + } + else + { + mysql_mutex_unlock(LOCK_thd_data_ptr); + } + return res; +} + + +/* + Process all APC requests. + This should be called periodically by the APC target thread. +*/ + +void Apc_target::process_apc_requests() +{ + while (1) + { + Call_request *request; + + mysql_mutex_lock(LOCK_thd_data_ptr); + if (!(request= get_first_in_queue())) + { + /* No requests in the queue */ + mysql_mutex_unlock(LOCK_thd_data_ptr); + break; + } + + /* + Remove the request from the queue (we're holding queue lock so we can be + sure that request owner won't try to remove it) + */ + request->what="dequeued by process_apc_requests"; + dequeue_request(request); + request->processed= TRUE; + + request->call->call_in_target_thread(); + request->what="func called by process_apc_requests"; + +#ifndef DBUG_OFF + n_calls_processed++; +#endif + mysql_cond_signal(&request->COND_request); + mysql_mutex_unlock(LOCK_thd_data_ptr); + } +} + diff --git a/sql/my_apc.h b/sql/my_apc.h new file mode 100644 index 00000000000..46c6fbd549d --- /dev/null +++ b/sql/my_apc.h @@ -0,0 +1,161 @@ +#ifndef SQL_MY_APC_INCLUDED +#define SQL_MY_APC_INCLUDED +/* + Copyright (c) 2011, 2013 Monty Program Ab. + + 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 02111-1301 USA */ + +/* + Interface + ~~~~~~~~~ + ( + - This is an APC request queue + - We assume there is a particular owner thread which periodically calls + process_apc_requests() to serve the call requests. + - Other threads can post call requests, and block until they are exectued. + ) + + Implementation + ~~~~~~~~~~~~~~ + - The target has a mutex-guarded request queue. + + - After the request has been put into queue, the requestor waits for request + to be satisfied. The worker satisifes the request and signals the + requestor. +*/ + +class THD; + +/* + Target for asynchronous procedure calls (APCs). + - A target is running in some particular thread, + - One can make calls to it from other threads. +*/ +class Apc_target +{ + mysql_mutex_t *LOCK_thd_data_ptr; +public: + Apc_target() : enabled(0), apc_calls(NULL) {} + ~Apc_target() { DBUG_ASSERT(!enabled && !apc_calls);} + + void init(mysql_mutex_t *target_mutex); + + /* Destroy the target. The target must be disabled when this call is made. */ + void destroy() { DBUG_ASSERT(!enabled); } + + /* Enter ther state where the target is available for serving APC requests */ + void enable() { enabled++; } + + /* + Make the target unavailable for serving APC requests. + + @note + This call will serve all requests that were already enqueued + */ + void disable() + { + DBUG_ASSERT(enabled); + mysql_mutex_lock(LOCK_thd_data_ptr); + bool process= !--enabled && have_apc_requests(); + mysql_mutex_unlock(LOCK_thd_data_ptr); + if (unlikely(process)) + process_apc_requests(); + } + + void process_apc_requests(); + /* + A lightweight function, intended to be used in frequent checks like this: + + if (apc_target.have_requests()) apc_target.process_apc_requests() + */ + inline bool have_apc_requests() + { + return MY_TEST(apc_calls); + } + + inline bool is_enabled() { return enabled; } + + /* Functor class for calls you can schedule */ + class Apc_call + { + public: + /* This function will be called in the target thread */ + virtual void call_in_target_thread()= 0; + virtual ~Apc_call() {} + }; + + /* Make a call in the target thread (see function definition for details) */ + bool make_apc_call(THD *caller_thd, Apc_call *call, int timeout_sec, bool *timed_out); + +#ifndef DBUG_OFF + int n_calls_processed; /* Number of calls served by this target */ +#endif +private: + class Call_request; + + /* + Non-zero value means we're enabled. It's an int, not bool, because one can + call enable() N times (and then needs to call disable() N times before the + target is really disabled) + */ + int enabled; + + /* + Circular, double-linked list of all enqueued call requests. + We use this structure, because we + - process requests sequentially: requests are added at the end of the + list and removed from the front. With circular list, we can keep one + pointer, and access both front an back of the list with it. + - a thread that has posted a request may time out (or be KILLed) and + cancel the request, which means we need a fast request-removal + operation. + */ + Call_request *apc_calls; + + class Call_request + { + public: + Apc_call *call; /* Functor to be called */ + + /* The caller will actually wait for "processed==TRUE" */ + bool processed; + + /* Condition that will be signalled when the request has been served */ + mysql_cond_t COND_request; + + /* Double linked-list linkage */ + Call_request *next; + Call_request *prev; + + const char *what; /* (debug) state of the request */ + }; + + void enqueue_request(Call_request *qe); + void dequeue_request(Call_request *qe); + + /* return the first call request in queue, or NULL if there are none enqueued */ + Call_request *get_first_in_queue() + { + return apc_calls; + } +}; + +#ifdef HAVE_PSI_INTERFACE +void init_show_explain_psi_keys(void); +#else +#define init_show_explain_psi_keys() /* no-op */ +#endif + +#endif //SQL_MY_APC_INCLUDED + diff --git a/sql/my_decimal.cc b/sql/my_decimal.cc index 21611afd87b..be732d4a927 100644 --- a/sql/my_decimal.cc +++ b/sql/my_decimal.cc @@ -41,26 +41,29 @@ int decimal_operation_results(int result, const char *value, const char *type) { + /* Avoid calling current_thd on default path */ + if (likely(result == E_DEC_OK)) + return(result); + + THD *thd= current_thd; switch (result) { - case E_DEC_OK: - break; case E_DEC_TRUNCATED: - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_DATA_TRUNCATED, ER(ER_DATA_TRUNCATED), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_DATA_TRUNCATED, ER_THD(thd, ER_DATA_TRUNCATED), value, type); break; case E_DEC_OVERFLOW: - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_DATA_OVERFLOW, ER(ER_DATA_OVERFLOW), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_DATA_OVERFLOW, ER_THD(thd, ER_DATA_OVERFLOW), value, type); break; case E_DEC_DIV_ZERO: - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_DIVISION_BY_ZERO, ER(ER_DIVISION_BY_ZERO)); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_DIVISION_BY_ZERO, ER_THD(thd, ER_DIVISION_BY_ZERO)); break; case E_DEC_BAD_NUM: - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_BAD_DATA, ER(ER_BAD_DATA), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_BAD_DATA, ER_THD(thd, ER_BAD_DATA), value, type); break; case E_DEC_OOM: @@ -236,33 +239,24 @@ int my_decimal2binary(uint mask, const my_decimal *d, uchar *bin, int prec, */ int str2my_decimal(uint mask, const char *from, uint length, - CHARSET_INFO *charset, my_decimal *decimal_value) + CHARSET_INFO *charset, my_decimal *decimal_value, + const char **end_ptr) { - char *end, *from_end; int err; - char buff[STRING_BUFFER_USUAL_SIZE]; - String tmp(buff, sizeof(buff), &my_charset_bin); if (charset->mbminlen > 1) { + StringBuffer<STRING_BUFFER_USUAL_SIZE> tmp; uint dummy_errors; tmp.copy(from, length, charset, &my_charset_latin1, &dummy_errors); - from= tmp.ptr(); - length= tmp.length(); - charset= &my_charset_bin; + char *end= (char*) tmp.end(); + err= string2decimal(tmp.ptr(), (decimal_t*) decimal_value, &end); + *end_ptr= from + charset->mbminlen * (size_t) (end - tmp.ptr()); } - from_end= end= (char*) from+length; - err= string2decimal((char *)from, (decimal_t*) decimal_value, &end); - if (end != from_end && !err) + else { - /* Give warning if there is something other than end space */ - for ( ; end < from_end; end++) - { - if (!my_isspace(&my_charset_latin1, *end)) - { - err= E_DEC_TRUNCATED; - break; - } - } + char *end= (char*) from + length; + err= string2decimal(from, (decimal_t*) decimal_value, &end); + *end_ptr= end; } check_result_and_overflow(mask, err, decimal_value); return err; @@ -326,7 +320,7 @@ my_decimal *seconds2my_decimal(bool sign, } -my_decimal *date2my_decimal(MYSQL_TIME *ltime, my_decimal *dec) +my_decimal *date2my_decimal(const MYSQL_TIME *ltime, my_decimal *dec) { longlong date= (ltime->year*100L + ltime->month)*100L + ltime->day; if (ltime->time_type > MYSQL_TIMESTAMP_DATE) @@ -335,7 +329,7 @@ my_decimal *date2my_decimal(MYSQL_TIME *ltime, my_decimal *dec) } -void my_decimal_trim(ulong *precision, uint *scale) +void my_decimal_trim(ulonglong *precision, uint *scale) { if (!(*precision) && !(*scale)) { diff --git a/sql/my_decimal.h b/sql/my_decimal.h index 22ae38bef41..265b370a154 100644 --- a/sql/my_decimal.h +++ b/sql/my_decimal.h @@ -343,6 +343,10 @@ bool my_decimal2seconds(const my_decimal *d, ulonglong *sec, ulong *microsec); my_decimal *seconds2my_decimal(bool sign, ulonglong sec, ulong microsec, my_decimal *d); +#define TIME_to_my_decimal(TIME, DECIMAL) \ + seconds2my_decimal((TIME)->neg, TIME_to_ulonglong(TIME), \ + (TIME)->second_part, (DECIMAL)) + int my_decimal2int(uint mask, const decimal_t *d, bool unsigned_flag, longlong *l); @@ -362,17 +366,27 @@ int str2my_decimal(uint mask, const char *str, my_decimal *d, char **end) int str2my_decimal(uint mask, const char *from, uint length, - CHARSET_INFO *charset, my_decimal *decimal_value); + CHARSET_INFO *charset, my_decimal *decimal_value, + const char **end); + +inline int str2my_decimal(uint mask, const char *from, uint length, + CHARSET_INFO *charset, my_decimal *decimal_value) +{ + const char *end; + return str2my_decimal(mask, from, length, charset, decimal_value, &end); +} #if defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) inline int string2my_decimal(uint mask, const String *str, my_decimal *d) { - return str2my_decimal(mask, str->ptr(), str->length(), str->charset(), d); + const char *end; + return str2my_decimal(mask, str->ptr(), str->length(), str->charset(), + d, &end); } -my_decimal *date2my_decimal(MYSQL_TIME *ltime, my_decimal *dec); +my_decimal *date2my_decimal(const MYSQL_TIME *ltime, my_decimal *dec); #endif /*defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) */ @@ -482,7 +496,7 @@ int my_decimal_intg(const my_decimal *a) } -void my_decimal_trim(ulong *precision, uint *scale); +void my_decimal_trim(ulonglong *precision, uint *scale); #endif /*my_decimal_h*/ diff --git a/sql/my_json_writer.cc b/sql/my_json_writer.cc new file mode 100644 index 00000000000..390123fbba9 --- /dev/null +++ b/sql/my_json_writer.cc @@ -0,0 +1,372 @@ +/* Copyright (C) 2014 SkySQL Ab, MariaDB Corporation Ab + + 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 02111-1301 USA */ + +#include <my_global.h> +#include "sql_priv.h" +#include "sql_string.h" + +#include "my_json_writer.h" + +void Json_writer::append_indent() +{ + if (!document_start) + output.append('\n'); + for (int i=0; i< indent_level; i++) + output.append(' '); +} + +void Json_writer::start_object() +{ + fmt_helper.on_start_object(); + + if (!element_started) + start_element(); + + output.append("{"); + indent_level+=INDENT_SIZE; + first_child=true; + element_started= false; + document_start= false; +} + +void Json_writer::start_array() +{ + if (fmt_helper.on_start_array()) + return; + + if (!element_started) + start_element(); + + output.append("["); + indent_level+=INDENT_SIZE; + first_child=true; + element_started= false; + document_start= false; +} + + +void Json_writer::end_object() +{ + indent_level-=INDENT_SIZE; + if (!first_child) + append_indent(); + output.append("}"); +} + + +void Json_writer::end_array() +{ + if (fmt_helper.on_end_array()) + return; + indent_level-=INDENT_SIZE; + if (!first_child) + append_indent(); + output.append("]"); +} + + +Json_writer& Json_writer::add_member(const char *name) +{ + if (fmt_helper.on_add_member(name)) + return *this; // handled + + // assert that we are in an object + DBUG_ASSERT(!element_started); + start_element(); + + output.append('"'); + output.append(name); + output.append("\": "); + return *this; +} + + +/* + Used by formatting helper to print something that is formatted by the helper. + We should only separate it from the previous element. +*/ + +void Json_writer::start_sub_element() +{ + //element_started= true; + if (first_child) + first_child= false; + else + output.append(','); + + append_indent(); +} + + +void Json_writer::start_element() +{ + element_started= true; + + if (first_child) + first_child= false; + else + output.append(','); + + append_indent(); +} + +void Json_writer::add_ll(longlong val) +{ + char buf[64]; + my_snprintf(buf, sizeof(buf), "%lld", val); + add_unquoted_str(buf); +} + + +/* Add a memory size, printing in Kb, Kb, Gb if necessary */ +void Json_writer::add_size(longlong val) +{ + char buf[64]; + if (val < 1024) + my_snprintf(buf, sizeof(buf), "%lld", val); + else if (val < 1024*1024*16) + { + /* Values less than 16MB are specified in KB for precision */ + size_t len= my_snprintf(buf, sizeof(buf), "%lld", val/1024); + strcpy(buf + len, "Kb"); + } + else + { + size_t len= my_snprintf(buf, sizeof(buf), "%lld", val/(1024*1024)); + strcpy(buf + len, "Mb"); + } + add_str(buf); +} + + +void Json_writer::add_double(double val) +{ + char buf[64]; + my_snprintf(buf, sizeof(buf), "%lg", val); + add_unquoted_str(buf); +} + + +void Json_writer::add_bool(bool val) +{ + add_unquoted_str(val? "true" : "false"); +} + + +void Json_writer::add_null() +{ + add_unquoted_str("null"); +} + + +void Json_writer::add_unquoted_str(const char* str) +{ + if (fmt_helper.on_add_str(str)) + return; + + if (!element_started) + start_element(); + + output.append(str); + element_started= false; +} + + +void Json_writer::add_str(const char *str) +{ + if (fmt_helper.on_add_str(str)) + return; + + if (!element_started) + start_element(); + + output.append('"'); + output.append(str); + output.append('"'); + element_started= false; +} + + +void Json_writer::add_str(const String &str) +{ + add_str(str.ptr()); +} + + +bool Single_line_formatting_helper::on_add_member(const char *name) +{ + DBUG_ASSERT(state== INACTIVE || state == DISABLED); + if (state != DISABLED) + { + // remove everything from the array + buf_ptr= buffer; + + //append member name to the array + size_t len= strlen(name); + if (len < MAX_LINE_LEN) + { + memcpy(buf_ptr, name, len); + buf_ptr+=len; + *(buf_ptr++)= 0; + + line_len= owner->indent_level + len + 1; + state= ADD_MEMBER; + return true; // handled + } + } + return false; // not handled +} + + +bool Single_line_formatting_helper::on_start_array() +{ + if (state == ADD_MEMBER) + { + state= IN_ARRAY; + return true; // handled + } + else + { + if (state != DISABLED) + state= INACTIVE; + // TODO: what if we have accumulated some stuff already? shouldn't we + // flush it? + return false; // not handled + } +} + + +bool Single_line_formatting_helper::on_end_array() +{ + if (state == IN_ARRAY) + { + flush_on_one_line(); + state= INACTIVE; + return true; // handled + } + return false; // not handled +} + + +void Single_line_formatting_helper::on_start_object() +{ + // Nested objects will not be printed on one line + disable_and_flush(); +} + + +bool Single_line_formatting_helper::on_add_str(const char *str) +{ + if (state == IN_ARRAY) + { + size_t len= strlen(str); + + // New length will be: + // "$string", + // quote + quote + comma + space = 4 + if (line_len + len + 4 > MAX_LINE_LEN) + { + disable_and_flush(); + return false; // didn't handle the last element + } + + //append string to array + memcpy(buf_ptr, str, len); + buf_ptr+=len; + *(buf_ptr++)= 0; + line_len += len + 4; + return true; // handled + } + + disable_and_flush(); + return false; // not handled +} + + +/* + Append everything accumulated to the output on one line +*/ + +void Single_line_formatting_helper::flush_on_one_line() +{ + owner->start_sub_element(); + char *ptr= buffer; + int nr= 0; + while (ptr < buf_ptr) + { + char *str= ptr; + + if (nr == 0) + { + owner->output.append('"'); + owner->output.append(str); + owner->output.append("\": "); + owner->output.append('['); + } + else + { + if (nr != 1) + owner->output.append(", "); + owner->output.append('"'); + owner->output.append(str); + owner->output.append('"'); + } + nr++; + + while (*ptr!=0) + ptr++; + ptr++; + } + owner->output.append(']'); + /* We've printed out the contents of the buffer, mark it as empty */ + buf_ptr= buffer; +} + + +void Single_line_formatting_helper::disable_and_flush() +{ + if (state == DISABLED) + return; + + bool start_array= (state == IN_ARRAY); + state= DISABLED; + // deactivate ourselves and flush all accumulated calls. + char *ptr= buffer; + int nr= 0; + while (ptr < buf_ptr) + { + char *str= ptr; + if (nr == 0) + { + owner->add_member(str); + if (start_array) + owner->start_array(); + } + else + { + //if (nr == 1) + // owner->start_array(); + owner->add_str(str); + } + + nr++; + while (*ptr!=0) + ptr++; + ptr++; + } + buf_ptr= buffer; + state= INACTIVE; +} + diff --git a/sql/my_json_writer.h b/sql/my_json_writer.h new file mode 100644 index 00000000000..c4b528ae10d --- /dev/null +++ b/sql/my_json_writer.h @@ -0,0 +1,190 @@ +/* Copyright (C) 2014 SkySQL Ab, MariaDB Corporation Ab + + 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 02111-1301 USA */ + +class Json_writer; + +/* + Single_line_formatting_helper is used by Json_writer to do better formatting + of JSON documents. + + The idea is to catch arrays that can be printed on one line: + + arrayName : [ "boo", 123, 456 ] + + and actually print them on one line. Arrrays that occupy too much space on + the line, or have nested members cannot be printed on one line. + + We hook into JSON printing functions and try to detect the pattern. While + detecting the pattern, we will accumulate "boo", 123, 456 as strings. + + Then, + - either the pattern is broken, and we print the elements out, + - or the pattern lasts till the end of the array, and we print the + array on one line. +*/ + +class Single_line_formatting_helper +{ + enum enum_state + { + INACTIVE, + ADD_MEMBER, + IN_ARRAY, + DISABLED + }; + + /* + This works like a finite automaton. + + state=DISABLED means the helper is disabled - all on_XXX functions will + return false (which means "not handled") and do nothing. + + +->-+ + | v + INACTIVE ---> ADD_MEMBER ---> IN_ARRAY--->-+ + ^ | + +------------------<--------------------+ + + For other states: + INACTIVE - initial state, we have nothing. + ADD_MEMBER - add_member() was called, the buffer has "member_name\0". + IN_ARRAY - start_array() was called. + + + */ + enum enum_state state; + enum { MAX_LINE_LEN= 80 }; + char buffer[80]; + + /* The data in the buffer is located between buffer[0] and buf_ptr */ + char *buf_ptr; + uint line_len; + + Json_writer *owner; +public: + Single_line_formatting_helper() : state(INACTIVE), buf_ptr(buffer) {} + + void init(Json_writer *owner_arg) { owner= owner_arg; } + + bool on_add_member(const char *name); + + bool on_start_array(); + bool on_end_array(); + void on_start_object(); + // on_end_object() is not needed. + + bool on_add_str(const char *str); + + void flush_on_one_line(); + void disable_and_flush(); +}; + + +/* + A class to write well-formed JSON documents. The documents are also formatted + for human readability. +*/ + +class Json_writer +{ +public: + /* Add a member. We must be in an object. */ + Json_writer& add_member(const char *name); + + /* Add atomic values */ + void add_str(const char* val); + void add_str(const String &str); + + void add_ll(longlong val); + void add_size(longlong val); + void add_double(double val); + void add_bool(bool val); + void add_null(); + +private: + void add_unquoted_str(const char* val); +public: + /* Start a child object */ + void start_object(); + void start_array(); + + void end_object(); + void end_array(); + + Json_writer() : + indent_level(0), document_start(true), element_started(false), + first_child(true) + { + fmt_helper.init(this); + } +private: + // TODO: a stack of (name, bool is_object_or_array) elements. + int indent_level; + enum { INDENT_SIZE = 2 }; + + friend class Single_line_formatting_helper; + friend class Json_writer_nesting_guard; + bool document_start; + bool element_started; + bool first_child; + + Single_line_formatting_helper fmt_helper; + + void append_indent(); + void start_element(); + void start_sub_element(); + + //const char *new_member_name; +public: + String output; +}; + + +/* + RAII-based helper class to detect incorrect use of Json_writer. + + The idea is that a function typically must leave Json_writer at the same + identation level as it was when it was invoked. Leaving it at a different + level typically means we forgot to close an object or an array + + So, here is a way to guard + void foo(Json_writer *writer) + { + Json_writer_nesting_guard(writer); + .. do something with writer + + // at the end of the function, ~Json_writer_nesting_guard() is called + // and it makes sure that the nesting is the same as when the function was + // entered. + } +*/ + +class Json_writer_nesting_guard +{ + Json_writer* writer; + int indent_level; +public: + Json_writer_nesting_guard(Json_writer *writer_arg) : + writer(writer_arg), + indent_level(writer->indent_level) + {} + + ~Json_writer_nesting_guard() + { + DBUG_ASSERT(indent_level == writer->indent_level); + } +}; + + diff --git a/sql/mysql_install_db.cc b/sql/mysql_install_db.cc index 9d2b261b46c..01d00855aed 100644 --- a/sql/mysql_install_db.cc +++ b/sql/mysql_install_db.cc @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* mysql_install_db creates a new database instance (optionally as service) @@ -24,7 +24,6 @@ #include <m_string.h> #include <windows.h> -#include <assert.h> #include <shellapi.h> #include <accctrl.h> #include <aclapi.h> @@ -39,7 +38,7 @@ struct IUnknown; "Usage: mysql_install_db.exe [OPTIONS]\n" \ "OPTIONS:" -extern "C" const char mysql_bootstrap_sql[]; +extern "C" const char* mysql_bootstrap_sql[]; char default_os_user[]= "NT AUTHORITY\\NetworkService"; static int create_db_instance(); @@ -122,10 +121,10 @@ static void die(const char *fmt, ...) if (verbose_errors) { fprintf(stderr, - "http://kb.askmonty.org/v/installation-issues-on-windows contains some help\n" + "https://mariadb.com/kb/en/installation-issues-on-windows contains some help\n" "for solving the most common problems. If this doesn't help you, please\n" - "leave a comment in the Knowledgebase or file a bug report at\n" - "http://mariadb.org/jira"); + "leave a comment in the Knowledge Base or file a bug report at\n" + "https://jira.mariadb.org"); } fflush(stderr); va_end(args); @@ -198,7 +197,7 @@ int main(int argc, char **argv) die("database creation failed"); } - printf("Creation of the database was successfull"); + printf("Creation of the database was successful"); return 0; } @@ -236,6 +235,20 @@ static void get_basedir(char *basedir, int size, const char *mysqld_path) } } +#define STR(s) _STR(s) +#define _STR(s) #s + +static char *get_plugindir() +{ + static char plugin_dir[2*MAX_PATH]; + get_basedir(plugin_dir, sizeof(plugin_dir), mysqld_path); + strcat(plugin_dir, "/" STR(INSTALL_PLUGINDIR)); + + if (access(plugin_dir, 0) == 0) + return plugin_dir; + + return NULL; +} /** Allocate and initialize command line for mysqld --bootstrap. @@ -252,7 +265,7 @@ static char *init_bootstrap_command_line(char *cmdline, size_t size) "\"\"%s\" --no-defaults %s --bootstrap" " \"--lc-messages-dir=%s/share\"" " --basedir=. --datadir=. --default-storage-engine=myisam" - " --max_allowed_packet=9M --loose-skip-innodb" + " --max_allowed_packet=9M " " --net-buffer-length=16k\"", mysqld_path, opt_verbose_bootstrap?"--console":"", basedir ); return cmdline; @@ -316,6 +329,10 @@ static int create_myini() fprintf(myini,"protocol=pipe\n"); else if (opt_port) fprintf(myini,"port=%d\n",opt_port); + + char *plugin_dir = get_plugindir(); + if (plugin_dir) + fprintf(myini, "plugin-dir=%s\n", plugin_dir); fclose(myini); return 0; } @@ -371,8 +388,8 @@ static int register_service() CloseServiceHandle(sc_manager); die("CreateService failed (%u)", GetLastError()); } - - SERVICE_DESCRIPTION sd= { "MariaDB database server" }; + char description[] = "MariaDB database server"; + SERVICE_DESCRIPTION sd= { description }; ChangeServiceConfig2(sc_service, SERVICE_CONFIG_DESCRIPTION, &sd); CloseServiceHandle(sc_service); CloseServiceHandle(sc_manager); @@ -624,6 +641,10 @@ static int create_db_instance() if (!in) goto end; + if (setvbuf(in, NULL, _IONBF, 0)) + { + verbose("WARNING: Cannot disable buffering on mysqld's stdin"); + } if (fwrite("use mysql;\n",11,1, in) != 1) { verbose("ERROR: Cannot write to mysqld's stdin"); @@ -631,12 +652,16 @@ static int create_db_instance() goto end; } - /* Write the bootstrap script to stdin. */ - if (fwrite(mysql_bootstrap_sql, strlen(mysql_bootstrap_sql), 1, in) != 1) + int i; + for (i=0; mysql_bootstrap_sql[i]; i++) { - verbose("ERROR: Cannot write to mysqld's stdin"); - ret= 1; - goto end; + /* Write the bootstrap script to stdin. */ + if (fwrite(mysql_bootstrap_sql[i], strlen(mysql_bootstrap_sql[i]), 1, in) != 1) + { + verbose("ERROR: Cannot write to mysqld's stdin"); + ret= 1; + goto end; + } } /* Remove default user, if requested. */ @@ -687,6 +712,14 @@ static int create_db_instance() goto end; } + /* + Remove innodb log files if they exist (this works around "different size logs" + error in MSI installation). TODO : remove this with the next Innodb, where + different size is handled gracefully. + */ + DeleteFile("ib_logfile0"); + DeleteFile("ib_logfile1"); + /* Create my.ini file in data directory.*/ ret= create_myini(); if (ret) diff --git a/sql/mysql_upgrade_service.cc b/sql/mysql_upgrade_service.cc index db916101eb1..9bb60809205 100644 --- a/sql/mysql_upgrade_service.cc +++ b/sql/mysql_upgrade_service.cc @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* mysql_upgrade_service upgrades mysql service on Windows. @@ -146,6 +146,11 @@ static void die(const char *fmt, ...) exit(1); } +#define WRITE_LOG(fmt,...) {\ + char log_buf[1024]; \ + snprintf(log_buf,sizeof(log_buf), fmt, __VA_ARGS__);\ + WriteFile(logfile_handle,log_buf, strlen(log_buf), 0 , 0);\ +} /* spawn-like function to run subprocesses. @@ -187,17 +192,22 @@ static intptr_t run_tool(int wait_flag, const char *program,...) { char tmpdir[FN_REFLEN]; GetTempPath(FN_REFLEN, tmpdir); - sprintf_s(logfile_path, "%s\\mysql_upgrade_service.%s.log", tmpdir, + sprintf_s(logfile_path, "%smysql_upgrade_service.%s.log", tmpdir, opt_service); - logfile_handle= CreateFile(logfile_path, GENERIC_WRITE, FILE_SHARE_READ, - NULL, TRUNCATE_EXISTING, 0, NULL); - if (!logfile_handle) + SECURITY_ATTRIBUTES attr= {0}; + attr.nLength= sizeof(SECURITY_ATTRIBUTES); + attr.bInheritHandle= TRUE; + logfile_handle= CreateFile(logfile_path, FILE_APPEND_DATA, + FILE_SHARE_READ|FILE_SHARE_WRITE, &attr, CREATE_ALWAYS, 0, NULL); + if (logfile_handle == INVALID_HANDLE_VALUE) { die("Cannot open log file %s, windows error %u", logfile_path, GetLastError()); } } + WRITE_LOG("Executing %s\r\n", cmdline); + /* Start child process */ STARTUPINFO si= {0}; si.cb= sizeof(si); @@ -458,7 +468,7 @@ int main(int argc, char **argv) log("Phase 3/8: Starting mysqld for upgrade"); mysqld_process= (HANDLE)run_tool(P_NOWAIT, mysqld_path, defaults_file_param, "--skip-networking", "--skip-grant-tables", - "--enable-named-pipe", socket_param, NULL); + "--enable-named-pipe", socket_param,"--skip-slave-start", NULL); if (mysqld_process == INVALID_HANDLE_VALUE) { diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 528c5d8ecfe..8d464ed75e6 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -14,14 +14,14 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" // Includes my_global.h #include "sql_priv.h" #include "unireg.h" #include <signal.h> #ifndef __WIN__ #include <netdb.h> // getservbyname, servent #endif -#include "sql_parse.h" // test_if_data_home_dir +#include "sql_parse.h" // path_starts_from_data_home_dir #include "sql_cache.h" // query_cache, query_cache_* #include "sql_locale.h" // MY_LOCALES, my_locales, my_locale_by_name #include "sql_show.h" // free_status_vars, add_status_vars, @@ -40,9 +40,7 @@ #include "hostname.h" // hostname_cache_free, hostname_cache_init #include "sql_acl.h" // acl_free, grant_free, acl_init, // grant_init -#include "sql_base.h" // table_def_free, table_def_init, - // cached_open_tables, - // cached_table_definitions +#include "sql_base.h" #include "sql_test.h" // mysql_print_status #include "item_create.h" // item_create_cleanup, item_create_init #include "sql_servers.h" // servers_free, servers_init @@ -52,6 +50,7 @@ #include "des_key_file.h" // load_des_key_file #include "sql_manager.h" // stop_handle_manager, start_handle_manager #include "sql_expression_cache.h" // subquery_cache_miss, subquery_cache_hit +#include "sys_vars_shared.h" #include <m_ctype.h> #include <my_dir.h> @@ -72,12 +71,21 @@ #include "scheduler.h" #include <waiting_threads.h> #include "debug_sync.h" +#include "wsrep_mysqld.h" +#include "wsrep_var.h" +#include "wsrep_thd.h" +#include "wsrep_sst.h" + #include "sql_callback.h" #include "threadpool.h" #ifdef WITH_PERFSCHEMA_STORAGE_ENGINE #include "../storage/perfschema/pfs_server.h" #endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */ +#include <mysql/psi/mysql_idle.h> +#include <mysql/psi/mysql_socket.h> +#include <mysql/psi/mysql_statement.h> +#include "mysql_com_server.h" #include "keycaches.h" #include "../storage/myisam/ha_myisam.h" @@ -97,11 +105,14 @@ #include "sp_rcontext.h" #include "sp_cache.h" #include "sql_reload.h" // reload_acl_and_cache +#include "pcre.h" #ifdef HAVE_POLL_H #include <poll.h> #endif +#include <my_service_manager.h> + #define mysqld_charset &my_charset_latin1 /* We have HAVE_valgrind below as this speeds up the shutdown of MySQL */ @@ -111,7 +122,6 @@ #endif extern "C" { // Because of SCO 3.2V4.2 -#include <errno.h> #include <sys/stat.h> #ifndef __GNU_LIBRARY__ #define __GNU_LIBRARY__ // Skip warnings in getopt.h @@ -268,6 +278,8 @@ extern "C" sig_handler handle_fatal_signal(int sig); #define ENABLE_TEMP_POOL 0 #endif +int init_io_cache_encryption(); + /* Constants */ #include <welcome_copyright_notice.h> // ORACLE_WELCOME_COPYRIGHT_NOTICE @@ -303,6 +315,9 @@ arg_cmp_func Arg_comparator::comparator_matrix[6][2] = {&Arg_comparator::compare_decimal, &Arg_comparator::compare_e_decimal}, {&Arg_comparator::compare_datetime, &Arg_comparator::compare_e_datetime}}; +/* Timer info to be used by the SQL layer */ +MY_TIMER_INFO sys_timer_info; + /* static variables */ #ifdef HAVE_PSI_INTERFACE @@ -332,14 +347,22 @@ static PSI_rwlock_key key_rwlock_openssl; volatile sig_atomic_t ld_assume_kernel_is_set= 0; #endif +/** + Statement instrumentation key for replication. +*/ +#ifdef HAVE_PSI_STATEMENT_INTERFACE +PSI_statement_info stmt_info_rpl; +#endif + /* the default log output is log tables */ static bool lower_case_table_names_used= 0; static bool max_long_data_size_used= false; static bool volatile select_thread_in_use, signal_thread_in_use; static volatile bool ready_to_exit; static my_bool opt_debugging= 0, opt_external_locking= 0, opt_console= 0; -static my_bool opt_short_log_format= 0; -static uint kill_cached_threads, wake_thread; +static my_bool opt_short_log_format= 0, opt_silent_startup= 0; +uint kill_cached_threads; +static uint wake_thread; ulong max_used_connections; static volatile ulong cached_thread_count= 0; static char *mysqld_user, *mysqld_chroot; @@ -347,20 +370,24 @@ static char *default_character_set_name; static char *character_set_filesystem_name; static char *lc_messages; static char *lc_time_names_name; -static char *my_bind_addr_str; +char *my_bind_addr_str; static char *default_collation_name; -char *default_storage_engine; +char *default_storage_engine, *default_tmp_storage_engine; +char *enforced_storage_engine=NULL; static char compiled_default_collation_name[]= MYSQL_DEFAULT_COLLATION_NAME; static I_List<THD> thread_cache; static bool binlog_format_used= false; LEX_STRING opt_init_connect, opt_init_slave; -static mysql_cond_t COND_thread_cache, COND_flush_thread_cache; +mysql_cond_t COND_thread_cache; +static mysql_cond_t COND_flush_thread_cache; +mysql_cond_t COND_slave_background; static DYNAMIC_ARRAY all_options; /* Global variables */ bool opt_bin_log, opt_bin_log_used=0, opt_ignore_builtin_innodb= 0; -my_bool opt_log, opt_slow_log, debug_assert_if_crashed_table= 0, opt_help= 0; +my_bool opt_log, debug_assert_if_crashed_table= 0, opt_help= 0; +my_bool disable_log_notes; static my_bool opt_abort; ulonglong log_output_options; my_bool opt_userstat_running; @@ -411,6 +438,8 @@ my_bool opt_safe_user_create = 0; my_bool opt_show_slave_auth_info; my_bool opt_log_slave_updates= 0; my_bool opt_replicate_annotate_row_events= 0; +my_bool opt_mysql56_temporal_format=0, strict_password_validation= 1; +my_bool opt_explicit_defaults_for_timestamp= 0; char *opt_slave_skip_errors; /* @@ -453,24 +482,26 @@ my_bool opt_master_verify_checksum= 0; my_bool opt_slave_sql_verify_checksum= 1; const char *binlog_format_names[]= {"MIXED", "STATEMENT", "ROW", NullS}; volatile sig_atomic_t calling_initgroups= 0; /**< Used in SIGSEGV handler. */ -uint mysqld_port, test_flags, select_errors, dropping_tables, ha_open_options; +uint mysqld_port, select_errors, dropping_tables, ha_open_options; uint mysqld_extra_port; uint mysqld_port_timeout; ulong delay_key_write_options; uint protocol_version; uint lower_case_table_names; ulong tc_heuristic_recover= 0; -uint volatile thread_count; +int32 thread_count, service_thread_count; int32 thread_running; +int32 slave_open_temp_tables; ulong thread_created; ulong back_log, connect_timeout, concurrency, server_id; -ulong table_cache_size, table_def_size; ulong what_to_log; -ulong slow_launch_time, slave_open_temp_tables; -ulong open_files_limit, max_binlog_size, max_relay_log_size; +ulong slow_launch_time; +ulong open_files_limit, max_binlog_size; ulong slave_trans_retries; uint slave_net_timeout; ulong slave_exec_mode_options; +ulong slave_run_triggers_for_rbr= 0; +ulong slave_ddl_exec_mode_options= SLAVE_EXEC_MODE_IDEMPOTENT; ulonglong slave_type_conversions_options; ulong thread_cache_size=0; ulonglong binlog_cache_size=0; @@ -478,12 +509,11 @@ ulonglong max_binlog_cache_size=0; ulong slave_max_allowed_packet= 0; ulonglong binlog_stmt_cache_size=0; ulonglong max_binlog_stmt_cache_size=0; +ulonglong test_flags; ulonglong query_cache_size=0; -ulong refresh_version; /* Increments on each reload */ +ulong query_cache_limit=0; ulong executed_events=0; query_id_t global_query_id; -my_atomic_rwlock_t global_query_id_lock; -my_atomic_rwlock_t thread_running_lock; ulong aborted_threads, aborted_connects; ulong delayed_insert_timeout, delayed_insert_limit, delayed_queue_size; ulong delayed_insert_threads, delayed_insert_writes, delayed_rows_in_use; @@ -493,6 +523,10 @@ ulong binlog_cache_use= 0, binlog_cache_disk_use= 0; ulong binlog_stmt_cache_use= 0, binlog_stmt_cache_disk_use= 0; ulong max_connections, max_connect_errors; ulong extra_max_connections; +uint max_digest_length= 0; +ulong slave_retried_transactions; +ulonglong slave_skipped_errors; +ulong feature_files_opened_with_delayed_keys; ulonglong denied_connections; my_decimal decimal_zero; @@ -502,16 +536,12 @@ my_decimal decimal_zero; */ ulong max_long_data_size; -/* Limits for internal temporary tables (MyISAM or Aria) */ -uint internal_tmp_table_max_key_length; -uint internal_tmp_table_max_key_segments; - bool max_user_connections_checking=0; /** Limit of the total number of prepared statements in the server. Is necessary to protect the server against out-of-memory attacks. */ -ulong max_prepared_stmt_count; +uint max_prepared_stmt_count; /** Current total number of prepared statements in the server. This number is exact, and therefore may not be equal to the difference between @@ -522,7 +552,7 @@ ulong max_prepared_stmt_count; two different connections, this counts as two distinct prepared statements. */ -ulong prepared_stmt_count=0; +uint prepared_stmt_count=0; ulong thread_id=1L,current_pid; ulong slow_launch_threads = 0; uint sync_binlog_period= 0, sync_relaylog_period= 0, @@ -535,6 +565,14 @@ ulong rpl_recovery_rank=0; */ ulong stored_program_cache_size= 0; +ulong opt_slave_parallel_threads= 0; +ulong opt_slave_domain_parallel_threads= 0; +ulong opt_slave_parallel_mode= SLAVE_PARALLEL_CONSERVATIVE; +ulong opt_binlog_commit_wait_count= 0; +ulong opt_binlog_commit_wait_usec= 0; +ulong opt_slave_parallel_max_queued= 131072; +my_bool opt_gtid_ignore_duplicates= FALSE; + const double log_10[] = { 1e000, 1e001, 1e002, 1e003, 1e004, 1e005, 1e006, 1e007, 1e008, 1e009, 1e010, 1e011, 1e012, 1e013, 1e014, 1e015, 1e016, 1e017, 1e018, 1e019, @@ -594,12 +632,28 @@ char server_version[SERVER_VERSION_LENGTH]; char *mysqld_unix_port, *opt_mysql_tmpdir; ulong thread_handling; +my_bool encrypt_binlog; +my_bool encrypt_tmp_disk_tables, encrypt_tmp_files; + /** name of reference on left expression in rewritten IN subquery */ const char *in_left_expr_name= "<left expr>"; /** name of additional condition */ const char *in_additional_cond= "<IN COND>"; const char *in_having_cond= "<IN HAVING>"; +/** Number of connection errors when selecting on the listening port */ +ulong connection_errors_select= 0; +/** Number of connection errors when accepting sockets in the listening port. */ +ulong connection_errors_accept= 0; +/** Number of connection errors from TCP wrappers. */ +ulong connection_errors_tcpwrap= 0; +/** Number of connection errors from internal server errors. */ +ulong connection_errors_internal= 0; +/** Number of connection errors from the server max_connection limit. */ +ulong connection_errors_max_connection= 0; +/** Number of errors when reading the peer address. */ +ulong connection_errors_peer_addr= 0; + /* classes for comparation parsing/processing */ Eq_creator eq_creator; Ne_creator ne_creator; @@ -612,7 +666,8 @@ MYSQL_FILE *bootstrap_file; int bootstrap_error; I_List<THD> threads; -Rpl_filter* rpl_filter; +Rpl_filter* cur_rpl_filter; +Rpl_filter* global_rpl_filter; Rpl_filter* binlog_filter; THD *first_global_thread() @@ -649,23 +704,27 @@ SHOW_COMP_OPTION have_ssl, have_symlink, have_dlopen, have_query_cache; SHOW_COMP_OPTION have_geometry, have_rtree_keys; SHOW_COMP_OPTION have_crypt, have_compress; SHOW_COMP_OPTION have_profiling; +SHOW_COMP_OPTION have_openssl; /* Thread specific variables */ pthread_key(MEM_ROOT**,THR_MALLOC); pthread_key(THD*, THR_THD); -mysql_mutex_t LOCK_thread_count; +mysql_mutex_t LOCK_thread_count, LOCK_thread_cache; mysql_mutex_t - LOCK_status, LOCK_error_log, LOCK_short_uuid_generator, + LOCK_status, LOCK_show_status, LOCK_error_log, LOCK_short_uuid_generator, LOCK_delayed_insert, LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_global_system_variables, - LOCK_user_conn, LOCK_slave_list, LOCK_active_mi, - LOCK_connection_count, LOCK_error_messages; + LOCK_user_conn, LOCK_slave_list, + LOCK_connection_count, LOCK_error_messages, LOCK_slave_background; mysql_mutex_t LOCK_stats, LOCK_global_user_client_stats, LOCK_global_table_stats, LOCK_global_index_stats; +/* This protects against changes in master_info_index */ +mysql_mutex_t LOCK_active_mi; + /** The below lock protects access to two global server variables: max_prepared_stmt_count and prepared_stmt_count. These variables @@ -685,8 +744,7 @@ pthread_attr_t connection_attrib; mysql_mutex_t LOCK_server_started; mysql_cond_t COND_server_started; -int mysqld_server_started= 0; - +int mysqld_server_started=0, mysqld_server_initialized= 0; File_parser_dummy_hook file_parser_dummy_hook; /* replication parameters, if master_host is not NULL, we are a slave */ @@ -696,6 +754,7 @@ char *master_info_file; char *relay_log_info_file, *report_user, *report_password, *report_host; char *opt_relay_logname = 0, *opt_relaylog_index_name=0; char *opt_logname, *opt_slow_logname, *opt_bin_logname; +char *opt_binlog_index_name=0; /* Static variables */ @@ -705,7 +764,6 @@ my_bool opt_expect_abort= 0, opt_bootstrap= 0; static my_bool opt_myisam_log; static int cleanup_done; static ulong opt_specialflag; -static char *opt_binlog_index_name; char *mysql_home_ptr, *pidfile_name_ptr; /** Initial command line arguments (count), after load_defaults().*/ static int defaults_argc; @@ -726,41 +784,126 @@ static char **remaining_argv; int orig_argc; char **orig_argv; +static struct my_option pfs_early_options[]= +{ +#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE + {"performance_schema_instrument", OPT_PFS_INSTRUMENT, + "Default startup value for a performance schema instrument.", + &pfs_param.m_pfs_instrument, &pfs_param.m_pfs_instrument, 0, GET_STR, + OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_events_stages_current", 0, + "Default startup value for the events_stages_current consumer.", + &pfs_param.m_consumer_events_stages_current_enabled, + &pfs_param.m_consumer_events_stages_current_enabled, 0, GET_BOOL, + OPT_ARG, FALSE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_events_stages_history", 0, + "Default startup value for the events_stages_history consumer.", + &pfs_param.m_consumer_events_stages_history_enabled, + &pfs_param.m_consumer_events_stages_history_enabled, 0, + GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_events_stages_history_long", 0, + "Default startup value for the events_stages_history_long consumer.", + &pfs_param.m_consumer_events_stages_history_long_enabled, + &pfs_param.m_consumer_events_stages_history_long_enabled, 0, + GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_events_statements_current", 0, + "Default startup value for the events_statements_current consumer.", + &pfs_param.m_consumer_events_statements_current_enabled, + &pfs_param.m_consumer_events_statements_current_enabled, 0, + GET_BOOL, OPT_ARG, TRUE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_events_statements_history", 0, + "Default startup value for the events_statements_history consumer.", + &pfs_param.m_consumer_events_statements_history_enabled, + &pfs_param.m_consumer_events_statements_history_enabled, 0, + GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_events_statements_history_long", 0, + "Default startup value for the events_statements_history_long consumer.", + &pfs_param.m_consumer_events_statements_history_long_enabled, + &pfs_param.m_consumer_events_statements_history_long_enabled, 0, + GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_events_waits_current", 0, + "Default startup value for the events_waits_current consumer.", + &pfs_param.m_consumer_events_waits_current_enabled, + &pfs_param.m_consumer_events_waits_current_enabled, 0, + GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_events_waits_history", 0, + "Default startup value for the events_waits_history consumer.", + &pfs_param.m_consumer_events_waits_history_enabled, + &pfs_param.m_consumer_events_waits_history_enabled, 0, + GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_events_waits_history_long", 0, + "Default startup value for the events_waits_history_long consumer.", + &pfs_param.m_consumer_events_waits_history_long_enabled, + &pfs_param.m_consumer_events_waits_history_long_enabled, 0, + GET_BOOL, OPT_ARG, FALSE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_global_instrumentation", 0, + "Default startup value for the global_instrumentation consumer.", + &pfs_param.m_consumer_global_instrumentation_enabled, + &pfs_param.m_consumer_global_instrumentation_enabled, 0, + GET_BOOL, OPT_ARG, TRUE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_thread_instrumentation", 0, + "Default startup value for the thread_instrumentation consumer.", + &pfs_param.m_consumer_thread_instrumentation_enabled, + &pfs_param.m_consumer_thread_instrumentation_enabled, 0, + GET_BOOL, OPT_ARG, TRUE, 0, 0, 0, 0, 0}, + {"performance_schema_consumer_statements_digest", 0, + "Default startup value for the statements_digest consumer.", + &pfs_param.m_consumer_statement_digest_enabled, + &pfs_param.m_consumer_statement_digest_enabled, 0, + GET_BOOL, OPT_ARG, TRUE, 0, 0, 0, 0, 0}, +#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */ + {"getopt-prefix-matching", 0, + "Recognize command-line options by their unambiguos prefixes.", + &my_getopt_prefix_matching, &my_getopt_prefix_matching, 0, GET_BOOL, + NO_ARG, 1, 0, 1, 0, 0, 0} +}; + #ifdef HAVE_PSI_INTERFACE #ifdef HAVE_MMAP -PSI_mutex_key key_PAGE_lock, key_LOCK_sync, key_LOCK_active, key_LOCK_pool; +PSI_mutex_key key_PAGE_lock, key_LOCK_sync, key_LOCK_active, key_LOCK_pool, + key_LOCK_pending_checkpoint; #endif /* HAVE_MMAP */ #ifdef HAVE_OPENSSL PSI_mutex_key key_LOCK_des_key_file; #endif /* HAVE_OPENSSL */ -PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids, +PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_xid_list, + key_BINLOG_LOCK_binlog_background_thread, + m_key_LOCK_binlog_end_pos, key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi, key_LOCK_connection_count, key_LOCK_crypt, key_LOCK_delayed_create, key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log, key_LOCK_gdl, key_LOCK_global_system_variables, key_LOCK_manager, key_LOCK_prepared_stmt_count, - key_LOCK_rpl_status, key_LOCK_server_started, key_LOCK_status, - key_LOCK_system_variables_hash, key_LOCK_table_share, key_LOCK_thd_data, + key_LOCK_rpl_status, key_LOCK_server_started, + key_LOCK_status, key_LOCK_show_status, + key_LOCK_system_variables_hash, key_LOCK_thd_data, key_LOCK_thd_kill, key_LOCK_user_conn, key_LOCK_uuid_short_generator, key_LOG_LOCK_log, key_master_info_data_lock, key_master_info_run_lock, - key_master_info_sleep_lock, + key_master_info_sleep_lock, key_master_info_start_stop_lock, key_mutex_slave_reporting_capability_err_lock, key_relay_log_info_data_lock, + key_rpl_group_info_sleep_lock, key_relay_log_info_log_space_lock, key_relay_log_info_run_lock, - key_relay_log_info_sleep_lock, key_structure_guard_mutex, key_TABLE_SHARE_LOCK_ha_data, - key_LOCK_error_messages, key_LOG_INFO_lock, key_LOCK_thread_count, + key_LOCK_error_messages, key_LOG_INFO_lock, + key_LOCK_thread_count, key_LOCK_thread_cache, key_PARTITION_LOCK_auto_inc; PSI_mutex_key key_RELAYLOG_LOCK_index; +PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state, + key_LOCK_rpl_thread, key_LOCK_rpl_thread_pool, key_LOCK_parallel_entry; PSI_mutex_key key_LOCK_stats, key_LOCK_global_user_client_stats, key_LOCK_global_table_stats, key_LOCK_global_index_stats, - key_LOCK_wakeup_ready; + key_LOCK_wakeup_ready, key_LOCK_wait_commit; +PSI_mutex_key key_LOCK_gtid_waiting; -PSI_mutex_key key_LOCK_prepare_ordered, key_LOCK_commit_ordered; +PSI_mutex_key key_LOCK_after_binlog_sync; +PSI_mutex_key key_LOCK_prepare_ordered, key_LOCK_commit_ordered, + key_LOCK_slave_background; +PSI_mutex_key key_TABLE_SHARE_LOCK_share; static PSI_mutex_info all_server_mutexes[]= { @@ -769,6 +912,7 @@ static PSI_mutex_info all_server_mutexes[]= { &key_LOCK_sync, "TC_LOG_MMAP::LOCK_sync", 0}, { &key_LOCK_active, "TC_LOG_MMAP::LOCK_active", 0}, { &key_LOCK_pool, "TC_LOG_MMAP::LOCK_pool", 0}, + { &key_LOCK_pool, "TC_LOG_MMAP::LOCK_pending_checkpoint", 0}, #endif /* HAVE_MMAP */ #ifdef HAVE_OPENSSL @@ -776,7 +920,9 @@ static PSI_mutex_info all_server_mutexes[]= #endif /* HAVE_OPENSSL */ { &key_BINLOG_LOCK_index, "MYSQL_BIN_LOG::LOCK_index", 0}, - { &key_BINLOG_LOCK_prep_xids, "MYSQL_BIN_LOG::LOCK_prep_xids", 0}, + { &key_BINLOG_LOCK_xid_list, "MYSQL_BIN_LOG::LOCK_xid_list", 0}, + { &key_BINLOG_LOCK_binlog_background_thread, "MYSQL_BIN_LOG::LOCK_binlog_background_thread", 0}, + { &m_key_LOCK_binlog_end_pos, "MYSQL_BIN_LOG::LOCK_binlog_end_pos", 0 }, { &key_RELAYLOG_LOCK_index, "MYSQL_RELAY_LOG::LOCK_index", 0}, { &key_delayed_insert_mutex, "Delayed_insert::mutex", 0}, { &key_hash_filo_lock, "hash_filo::lock", 0}, @@ -794,33 +940,46 @@ static PSI_mutex_info all_server_mutexes[]= { &key_LOCK_rpl_status, "LOCK_rpl_status", PSI_FLAG_GLOBAL}, { &key_LOCK_server_started, "LOCK_server_started", PSI_FLAG_GLOBAL}, { &key_LOCK_status, "LOCK_status", PSI_FLAG_GLOBAL}, + { &key_LOCK_show_status, "LOCK_show_status", PSI_FLAG_GLOBAL}, { &key_LOCK_system_variables_hash, "LOCK_system_variables_hash", PSI_FLAG_GLOBAL}, - { &key_LOCK_table_share, "LOCK_table_share", PSI_FLAG_GLOBAL}, { &key_LOCK_stats, "LOCK_stats", PSI_FLAG_GLOBAL}, { &key_LOCK_global_user_client_stats, "LOCK_global_user_client_stats", PSI_FLAG_GLOBAL}, { &key_LOCK_global_table_stats, "LOCK_global_table_stats", PSI_FLAG_GLOBAL}, { &key_LOCK_global_index_stats, "LOCK_global_index_stats", PSI_FLAG_GLOBAL}, { &key_LOCK_wakeup_ready, "THD::LOCK_wakeup_ready", 0}, + { &key_LOCK_wait_commit, "wait_for_commit::LOCK_wait_commit", 0}, + { &key_LOCK_gtid_waiting, "gtid_waiting::LOCK_gtid_waiting", 0}, { &key_LOCK_thd_data, "THD::LOCK_thd_data", 0}, + { &key_LOCK_thd_kill, "THD::LOCK_thd_kill", 0}, { &key_LOCK_user_conn, "LOCK_user_conn", PSI_FLAG_GLOBAL}, { &key_LOCK_uuid_short_generator, "LOCK_uuid_short_generator", PSI_FLAG_GLOBAL}, { &key_LOG_LOCK_log, "LOG::LOCK_log", 0}, { &key_master_info_data_lock, "Master_info::data_lock", 0}, + { &key_master_info_start_stop_lock, "Master_info::start_stop_lock", 0}, { &key_master_info_run_lock, "Master_info::run_lock", 0}, { &key_master_info_sleep_lock, "Master_info::sleep_lock", 0}, { &key_mutex_slave_reporting_capability_err_lock, "Slave_reporting_capability::err_lock", 0}, { &key_relay_log_info_data_lock, "Relay_log_info::data_lock", 0}, { &key_relay_log_info_log_space_lock, "Relay_log_info::log_space_lock", 0}, { &key_relay_log_info_run_lock, "Relay_log_info::run_lock", 0}, - { &key_relay_log_info_sleep_lock, "Relay_log_info::sleep_lock", 0}, + { &key_rpl_group_info_sleep_lock, "Rpl_group_info::sleep_lock", 0}, { &key_structure_guard_mutex, "Query_cache::structure_guard_mutex", 0}, { &key_TABLE_SHARE_LOCK_ha_data, "TABLE_SHARE::LOCK_ha_data", 0}, + { &key_TABLE_SHARE_LOCK_share, "TABLE_SHARE::LOCK_share", 0}, { &key_LOCK_error_messages, "LOCK_error_messages", PSI_FLAG_GLOBAL}, { &key_LOCK_prepare_ordered, "LOCK_prepare_ordered", PSI_FLAG_GLOBAL}, + { &key_LOCK_after_binlog_sync, "LOCK_after_binlog_sync", PSI_FLAG_GLOBAL}, { &key_LOCK_commit_ordered, "LOCK_commit_ordered", PSI_FLAG_GLOBAL}, + { &key_LOCK_slave_background, "LOCK_slave_background", PSI_FLAG_GLOBAL}, { &key_LOG_INFO_lock, "LOG_INFO::lock", 0}, { &key_LOCK_thread_count, "LOCK_thread_count", PSI_FLAG_GLOBAL}, - { &key_PARTITION_LOCK_auto_inc, "HA_DATA_PARTITION::LOCK_auto_inc", 0} + { &key_LOCK_thread_cache, "LOCK_thread_cache", PSI_FLAG_GLOBAL}, + { &key_PARTITION_LOCK_auto_inc, "HA_DATA_PARTITION::LOCK_auto_inc", 0}, + { &key_LOCK_slave_state, "LOCK_slave_state", 0}, + { &key_LOCK_binlog_state, "LOCK_binlog_state", 0}, + { &key_LOCK_rpl_thread, "LOCK_rpl_thread", 0}, + { &key_LOCK_rpl_thread_pool, "LOCK_rpl_thread_pool", 0}, + { &key_LOCK_parallel_entry, "LOCK_parallel_entry", 0} }; PSI_rwlock_key key_rwlock_LOCK_grant, key_rwlock_LOCK_logger, @@ -844,7 +1003,9 @@ static PSI_rwlock_info all_server_rwlocks[]= PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool; #endif /* HAVE_MMAP */ -PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond, +PSI_cond_key key_BINLOG_COND_xid_list, key_BINLOG_update_cond, + key_BINLOG_COND_binlog_background_thread, + key_BINLOG_COND_binlog_background_thread_end, key_COND_cache_status_changed, key_COND_manager, key_COND_rpl_status, key_COND_server_started, key_delayed_insert_cond, key_delayed_insert_cond_client, @@ -853,13 +1014,19 @@ PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond, key_master_info_sleep_cond, key_relay_log_info_data_cond, key_relay_log_info_log_space_cond, key_relay_log_info_start_cond, key_relay_log_info_stop_cond, - key_relay_log_info_sleep_cond, + key_rpl_group_info_sleep_cond, key_TABLE_SHARE_cond, key_user_level_lock_cond, key_COND_thread_count, key_COND_thread_cache, key_COND_flush_thread_cache, key_BINLOG_COND_queue_busy; -PSI_cond_key key_RELAYLOG_update_cond, key_COND_wakeup_ready; +PSI_cond_key key_RELAYLOG_update_cond, key_COND_wakeup_ready, + key_COND_wait_commit; PSI_cond_key key_RELAYLOG_COND_queue_busy; PSI_cond_key key_TC_LOG_MMAP_COND_queue_busy; +PSI_cond_key key_COND_rpl_thread_queue, key_COND_rpl_thread, + key_COND_rpl_thread_stop, key_COND_rpl_thread_pool, + key_COND_parallel_entry, key_COND_group_commit_orderer, + key_COND_prepare_ordered, key_COND_slave_background; +PSI_cond_key key_COND_wait_gtid, key_COND_gtid_ignore_duplicates; static PSI_cond_info all_server_conds[]= { @@ -872,15 +1039,17 @@ static PSI_cond_info all_server_conds[]= { &key_COND_pool, "TC_LOG_MMAP::COND_pool", 0}, { &key_TC_LOG_MMAP_COND_queue_busy, "TC_LOG_MMAP::COND_queue_busy", 0}, #endif /* HAVE_MMAP */ - { &key_BINLOG_COND_prep_xids, "MYSQL_BIN_LOG::COND_prep_xids", 0}, + { &key_BINLOG_COND_xid_list, "MYSQL_BIN_LOG::COND_xid_list", 0}, { &key_BINLOG_update_cond, "MYSQL_BIN_LOG::update_cond", 0}, + { &key_BINLOG_COND_binlog_background_thread, "MYSQL_BIN_LOG::COND_binlog_background_thread", 0}, + { &key_BINLOG_COND_binlog_background_thread_end, "MYSQL_BIN_LOG::COND_binlog_background_thread_end", 0}, { &key_BINLOG_COND_queue_busy, "MYSQL_BIN_LOG::COND_queue_busy", 0}, { &key_RELAYLOG_update_cond, "MYSQL_RELAY_LOG::update_cond", 0}, { &key_RELAYLOG_COND_queue_busy, "MYSQL_RELAY_LOG::COND_queue_busy", 0}, { &key_COND_wakeup_ready, "THD::COND_wakeup_ready", 0}, + { &key_COND_wait_commit, "wait_for_commit::COND_wait_commit", 0}, { &key_COND_cache_status_changed, "Query_cache::COND_cache_status_changed", 0}, { &key_COND_manager, "COND_manager", PSI_FLAG_GLOBAL}, - { &key_COND_rpl_status, "COND_rpl_status", PSI_FLAG_GLOBAL}, { &key_COND_server_started, "COND_server_started", PSI_FLAG_GLOBAL}, { &key_delayed_insert_cond, "Delayed_insert::cond", 0}, { &key_delayed_insert_cond_client, "Delayed_insert::cond_client", 0}, @@ -893,17 +1062,28 @@ static PSI_cond_info all_server_conds[]= { &key_relay_log_info_log_space_cond, "Relay_log_info::log_space_cond", 0}, { &key_relay_log_info_start_cond, "Relay_log_info::start_cond", 0}, { &key_relay_log_info_stop_cond, "Relay_log_info::stop_cond", 0}, - { &key_relay_log_info_sleep_cond, "Relay_log_info::sleep_cond", 0}, + { &key_rpl_group_info_sleep_cond, "Rpl_group_info::sleep_cond", 0}, { &key_TABLE_SHARE_cond, "TABLE_SHARE::cond", 0}, { &key_user_level_lock_cond, "User_level_lock::cond", 0}, { &key_COND_thread_count, "COND_thread_count", PSI_FLAG_GLOBAL}, { &key_COND_thread_cache, "COND_thread_cache", PSI_FLAG_GLOBAL}, - { &key_COND_flush_thread_cache, "COND_flush_thread_cache", PSI_FLAG_GLOBAL} + { &key_COND_flush_thread_cache, "COND_flush_thread_cache", PSI_FLAG_GLOBAL}, + { &key_COND_rpl_thread, "COND_rpl_thread", 0}, + { &key_COND_rpl_thread_queue, "COND_rpl_thread_queue", 0}, + { &key_COND_rpl_thread_stop, "COND_rpl_thread_stop", 0}, + { &key_COND_rpl_thread_pool, "COND_rpl_thread_pool", 0}, + { &key_COND_parallel_entry, "COND_parallel_entry", 0}, + { &key_COND_group_commit_orderer, "COND_group_commit_orderer", 0}, + { &key_COND_prepare_ordered, "COND_prepare_ordered", 0}, + { &key_COND_slave_background, "COND_slave_background", 0}, + { &key_COND_wait_gtid, "COND_wait_gtid", 0}, + { &key_COND_gtid_ignore_duplicates, "COND_gtid_ignore_duplicates", 0} }; PSI_thread_key key_thread_bootstrap, key_thread_delayed_insert, key_thread_handle_manager, key_thread_main, - key_thread_one_connection, key_thread_signal_hand; + key_thread_one_connection, key_thread_signal_hand, + key_thread_slave_background, key_rpl_parallel_thread; static PSI_thread_info all_server_threads[]= { @@ -928,9 +1108,15 @@ static PSI_thread_info all_server_threads[]= { &key_thread_handle_manager, "manager", PSI_FLAG_GLOBAL}, { &key_thread_main, "main", PSI_FLAG_GLOBAL}, { &key_thread_one_connection, "one_connection", 0}, - { &key_thread_signal_hand, "signal_handler", PSI_FLAG_GLOBAL} + { &key_thread_signal_hand, "signal_handler", PSI_FLAG_GLOBAL}, + { &key_thread_slave_background, "slave_background", PSI_FLAG_GLOBAL}, + { &key_rpl_parallel_thread, "rpl_parallel_thread", 0} }; +#ifdef HAVE_MMAP +PSI_file_key key_file_map; +#endif /* HAVE_MMAP */ + PSI_file_key key_file_binlog, key_file_binlog_index, key_file_casetest, key_file_dbopt, key_file_des_key_file, key_file_ERRMSG, key_select_to_file, key_file_fileparser, key_file_frm, key_file_global_ddl_log, key_file_load, @@ -940,75 +1126,84 @@ PSI_file_key key_file_binlog, key_file_binlog_index, key_file_casetest, key_file_trg, key_file_trn, key_file_init; PSI_file_key key_file_query_log, key_file_slow_log; PSI_file_key key_file_relaylog, key_file_relaylog_index; +PSI_file_key key_file_binlog_state; -static PSI_file_info all_server_files[]= -{ - { &key_file_binlog, "binlog", 0}, - { &key_file_binlog_index, "binlog_index", 0}, - { &key_file_relaylog, "relaylog", 0}, - { &key_file_relaylog_index, "relaylog_index", 0}, - { &key_file_casetest, "casetest", 0}, - { &key_file_dbopt, "dbopt", 0}, - { &key_file_des_key_file, "des_key_file", 0}, - { &key_file_ERRMSG, "ERRMSG", 0}, - { &key_select_to_file, "select_to_file", 0}, - { &key_file_fileparser, "file_parser", 0}, - { &key_file_frm, "FRM", 0}, - { &key_file_global_ddl_log, "global_ddl_log", 0}, - { &key_file_load, "load", 0}, - { &key_file_loadfile, "LOAD_FILE", 0}, - { &key_file_log_event_data, "log_event_data", 0}, - { &key_file_log_event_info, "log_event_info", 0}, - { &key_file_master_info, "master_info", 0}, - { &key_file_misc, "misc", 0}, - { &key_file_partition, "partition", 0}, - { &key_file_pid, "pid", 0}, - { &key_file_query_log, "query_log", 0}, - { &key_file_relay_log_info, "relay_log_info", 0}, - { &key_file_send_file, "send_file", 0}, - { &key_file_slow_log, "slow_log", 0}, - { &key_file_tclog, "tclog", 0}, - { &key_file_trg, "trigger_name", 0}, - { &key_file_trn, "trigger", 0}, - { &key_file_init, "init", 0} -}; +#endif /* HAVE_PSI_INTERFACE */ -/** - Initialise all the performance schema instrumentation points - used by the server. -*/ -void init_server_psi_keys(void) +#ifdef HAVE_PSI_STATEMENT_INTERFACE +PSI_statement_info stmt_info_new_packet; +#endif + +#ifndef EMBEDDED_LIBRARY +void net_before_header_psi(struct st_net *net, void *user_data, size_t /* unused: count */) { - const char* category= "sql"; - int count; + THD *thd; + thd= static_cast<THD*> (user_data); + DBUG_ASSERT(thd != NULL); - if (PSI_server == NULL) - return; + /* + We only come where when the server is IDLE, waiting for the next command. + Technically, it is a wait on a socket, which may take a long time, + because the call is blocking. + Disable the socket instrumentation, to avoid recording a SOCKET event. + Instead, start explicitly an IDLE event. + */ + MYSQL_SOCKET_SET_STATE(net->vio->mysql_socket, PSI_SOCKET_STATE_IDLE); + MYSQL_START_IDLE_WAIT(thd->m_idle_psi, &thd->m_idle_state); +} - count= array_elements(all_server_mutexes); - PSI_server->register_mutex(category, all_server_mutexes, count); +void net_after_header_psi(struct st_net *net, void *user_data, + size_t /* unused: count */, my_bool rc) +{ + THD *thd; + thd= static_cast<THD*> (user_data); + DBUG_ASSERT(thd != NULL); - count= array_elements(all_server_rwlocks); - PSI_server->register_rwlock(category, all_server_rwlocks, count); + /* + The server just got data for a network packet header, + from the network layer. + The IDLE event is now complete, since we now have a message to process. + We need to: + - start a new STATEMENT event + - start a new STAGE event, within this statement, + - start recording SOCKET WAITS events, within this stage. + The proper order is critical to get events numbered correctly, + and nested in the proper parent. + */ + MYSQL_END_IDLE_WAIT(thd->m_idle_psi); - count= array_elements(all_server_conds); - PSI_server->register_cond(category, all_server_conds, count); + if (! rc) + { + thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state, + stmt_info_new_packet.m_key, + thd->db, thd->db_length, + thd->charset()); - count= array_elements(all_server_threads); - PSI_server->register_thread(category, all_server_threads, count); + THD_STAGE_INFO(thd, stage_init); + } - count= array_elements(all_server_files); - PSI_server->register_file(category, all_server_files, count); + /* + TODO: consider recording a SOCKET event for the bytes just read, + by also passing count here. + */ + MYSQL_SOCKET_SET_STATE(net->vio->mysql_socket, PSI_SOCKET_STATE_ACTIVE); } -#endif /* HAVE_PSI_INTERFACE */ -/* - Since buffered_option_error_reporter is only used currently - for parsing performance schema options, this code is not needed - when the performance schema is not compiled in. -*/ -#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE +void init_net_server_extension(THD *thd) +{ + /* Start with a clean state for connection events. */ + thd->m_idle_psi= NULL; + thd->m_statement_psi= NULL; + /* Hook up the NET_SERVER callback in the net layer. */ + thd->m_net_server_extension.m_user_data= thd; + thd->m_net_server_extension.m_before_header= net_before_header_psi; + thd->m_net_server_extension.m_after_header= net_after_header_psi; + /* Activate this private extension for the mysqld server. */ + thd->net.extension= & thd->m_net_server_extension; +} +#endif /* EMBEDDED_LIBRARY */ + /** A log message for the error log, buffered in memory. Log messages are temporarily buffered when generated before the error log @@ -1105,7 +1300,7 @@ private: void Buffered_logs::init() { - init_alloc_root(&m_root, 1024, 0); + init_alloc_root(&m_root, 1024, 0, MYF(0)); } void Buffered_logs::cleanup() @@ -1144,6 +1339,9 @@ void Buffered_logs::print() /** Logs reported before a logger is available. */ static Buffered_logs buffered_logs; +static MYSQL_SOCKET unix_sock, base_ip_sock, extra_ip_sock; +struct my_rnd_struct sql_rand; ///< used by sql_class.cc:THD::THD() + #ifndef EMBEDDED_LIBRARY /** Error reporter that buffer log messages. @@ -1162,14 +1360,34 @@ static void buffered_option_error_reporter(enum loglevel level, va_end(args); buffered_logs.buffer(level, buffer); } -C_MODE_END -#endif /* !EMBEDDED_LIBRARY */ -#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */ -static my_socket unix_sock, base_ip_sock, extra_ip_sock; -struct my_rnd_struct sql_rand; ///< used by sql_class.cc:THD::THD() -#ifndef EMBEDDED_LIBRARY +/** + Character set and collation error reporter that prints to sql error log. + @param level log message level + @param format log message format string + + This routine is used to print character set and collation + warnings and errors inside an already running mysqld server, + e.g. when a character set or collation is requested for the very first time + and its initialization does not go well for some reasons. + + Note: At early mysqld initialization stage, + when error log is not yet available, + we use buffered_option_error_reporter() instead, + to print general character set subsystem initialization errors, + such as Index.xml syntax problems, bad XML tag hierarchy, etc. +*/ +static void charset_error_reporter(enum loglevel level, + const char *format, ...) +{ + va_list args; + va_start(args, format); + vprint_msg_to_log(level, format, args); + va_end(args); +} +C_MODE_END + struct passwd *user_info; static pthread_t select_thread; #endif @@ -1207,12 +1425,12 @@ bool mysqld_embedded=0; bool mysqld_embedded=1; #endif -static my_bool plugins_are_initialized= FALSE; +my_bool plugins_are_initialized= FALSE; #ifndef DBUG_OFF static const char* default_dbug_option; #endif -static const char *current_dbug_option="disabled"; +const char *current_dbug_option=""; #ifdef HAVE_LIBWRAP const char *libwrapName= NULL; int allow_severity = LOG_INFO; @@ -1223,14 +1441,16 @@ ulong query_cache_min_res_unit= QUERY_CACHE_MIN_RESULT_DATA_SIZE; Query_cache query_cache; #endif #ifdef HAVE_SMEM -char *shared_memory_base_name= default_shared_memory_base_name; +const char *shared_memory_base_name= default_shared_memory_base_name; my_bool opt_enable_shared_memory; HANDLE smem_event_connect_request= 0; #endif my_bool opt_use_ssl = 0; char *opt_ssl_ca= NULL, *opt_ssl_capath= NULL, *opt_ssl_cert= NULL, - *opt_ssl_cipher= NULL, *opt_ssl_key= NULL; + *opt_ssl_cipher= NULL, *opt_ssl_key= NULL, *opt_ssl_crl= NULL, + *opt_ssl_crlpath= NULL; + static scheduler_functions thread_scheduler_struct, extra_thread_scheduler_struct; scheduler_functions *thread_scheduler= &thread_scheduler_struct, @@ -1262,12 +1482,16 @@ struct st_VioSSLFd *ssl_acceptor_fd; */ uint connection_count= 0, extra_connection_count= 0; +my_bool opt_gtid_strict_mode= FALSE; + + /* Function declarations */ pthread_handler_t signal_hand(void *arg); static int mysql_init_variables(void); static int get_options(int *argc_ptr, char ***argv_ptr); static bool add_terminator(DYNAMIC_ARRAY *options); +static bool add_many_options(DYNAMIC_ARRAY *, my_option *, size_t); extern "C" my_bool mysqld_get_one_option(int, const struct my_option *, char *); static int init_thread_environment(); static char *get_relative_path(const char *path); @@ -1328,8 +1552,7 @@ static void close_connections(void) while (select_thread_in_use) { struct timespec abstime; - int error; - LINT_INIT(error); + int UNINIT_VAR(error); DBUG_PRINT("info",("Waiting for select thread")); #ifndef DONT_USE_THR_ALARM @@ -1358,17 +1581,17 @@ static void close_connections(void) DBUG_PRINT("quit",("Closing sockets")); if (!opt_disable_networking ) { - if (base_ip_sock != INVALID_SOCKET) + if (mysql_socket_getfd(base_ip_sock) != INVALID_SOCKET) { (void) mysql_socket_shutdown(base_ip_sock, SHUT_RDWR); - (void) closesocket(base_ip_sock); - base_ip_sock= INVALID_SOCKET; + (void) mysql_socket_close(base_ip_sock); + base_ip_sock= MYSQL_INVALID_SOCKET; } - if (extra_ip_sock != INVALID_SOCKET) + if (mysql_socket_getfd(extra_ip_sock) != INVALID_SOCKET) { (void) mysql_socket_shutdown(extra_ip_sock, SHUT_RDWR); - (void) closesocket(extra_ip_sock); - extra_ip_sock= INVALID_SOCKET; + (void) mysql_socket_close(extra_ip_sock); + extra_ip_sock= MYSQL_INVALID_SOCKET; } } #ifdef _WIN32 @@ -1396,12 +1619,12 @@ static void close_connections(void) } #endif #ifdef HAVE_SYS_UN_H - if (unix_sock != INVALID_SOCKET) + if (mysql_socket_getfd(unix_sock) != INVALID_SOCKET) { (void) mysql_socket_shutdown(unix_sock, SHUT_RDWR); - (void) closesocket(unix_sock); + (void) mysql_socket_close(unix_sock); (void) unlink(mysqld_unix_port); - unix_sock= INVALID_SOCKET; + unix_sock= MYSQL_INVALID_SOCKET; } #endif end_thr_alarm(0); // Abort old alarms. @@ -1424,7 +1647,12 @@ static void close_connections(void) if (tmp->slave_thread) continue; - tmp->killed= KILL_SERVER_HARD; +#ifdef WITH_WSREP + /* skip wsrep system threads as well */ + if (WSREP(tmp) && (tmp->wsrep_exec_mode==REPL_RECV || tmp->wsrep_applier)) + continue; +#endif + tmp->set_killed(KILL_SERVER_HARD); MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (tmp)); mysql_mutex_lock(&tmp->LOCK_thd_data); if (tmp->mysys_var) @@ -1454,10 +1682,23 @@ static void close_connections(void) mysql_mutex_unlock(&LOCK_thread_count); // For unlink from list Events::deinit(); - end_slave(); + slave_prepare_for_shutdown(); - /* Give threads time to die. */ - for (int i= 0; thread_count && i < 200; i++) + /* + Give threads time to die. + + In 5.5, this was waiting 100 rounds @ 20 milliseconds/round, so as little + as 2 seconds, depending on thread scheduling. + + From 10.0, we increase this to 1000 rounds / 20 seconds. The rationale is + that on a server with heavy I/O load, it is quite possible for eg. an + fsync() of the binlog or whatever to cause something like LOCK_log to be + held for more than 2 seconds. We do not want to force kill threads in + such cases, if it can be avoided. Note that normally, the wait will be + much smaller than even 2 seconds, this is only a safety fallback against + stuck threads so server shutdown is not held up forever. + */ + for (int i= 0; *(volatile int32*) &thread_count && i < 1000; i++) my_sleep(20000); /* @@ -1484,16 +1725,52 @@ static void close_connections(void) tmp->thread_id, (tmp->main_security_ctx.user ? tmp->main_security_ctx.user : "")); + /* + close_connection() might need a valid current_thd + for memory allocation tracking. + */ + THD* save_thd= current_thd; + set_current_thd(tmp); close_connection(tmp,ER_SERVER_SHUTDOWN); + set_current_thd(save_thd); + } +#endif +#ifdef WITH_WSREP + /* + * WSREP_TODO: + * this code block may turn out redundant. wsrep->disconnect() + * should terminate slave threads gracefully, and we don't need + * to signal them here. + * The code here makes sure mysqld will not hang during shutdown + * even if wsrep provider has problems in shutting down. + */ + if (WSREP(tmp) && tmp->wsrep_exec_mode==REPL_RECV) + { + sql_print_information("closing wsrep system thread"); + tmp->set_killed(KILL_CONNECTION); + MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (tmp)); + if (tmp->mysys_var) + { + tmp->mysys_var->abort=1; + mysql_mutex_lock(&tmp->mysys_var->mutex); + if (tmp->mysys_var->current_cond) + { + mysql_mutex_lock(tmp->mysys_var->current_mutex); + mysql_cond_broadcast(tmp->mysys_var->current_cond); + mysql_mutex_unlock(tmp->mysys_var->current_mutex); + } + mysql_mutex_unlock(&tmp->mysys_var->mutex); + } } #endif DBUG_PRINT("quit",("Unlocking LOCK_thread_count")); mysql_mutex_unlock(&LOCK_thread_count); } + end_slave(); /* All threads has now been aborted */ DBUG_PRINT("quit",("Waiting for threads to die (count=%u)",thread_count)); mysql_mutex_lock(&LOCK_thread_count); - while (thread_count) + while (thread_count || service_thread_count) { mysql_cond_wait(&COND_thread_count, &LOCK_thread_count); DBUG_PRINT("quit",("One thread died (count=%u)",thread_count)); @@ -1506,22 +1783,14 @@ static void close_connections(void) #ifdef HAVE_CLOSE_SERVER_SOCK -static void close_socket(my_socket sock, const char *info) +static void close_socket(MYSQL_SOCKET sock, const char *info) { DBUG_ENTER("close_socket"); - if (sock != INVALID_SOCKET) + if (mysql_socket_getfd(sock) != INVALID_SOCKET) { DBUG_PRINT("info", ("calling shutdown on %s socket", info)); (void) mysql_socket_shutdown(sock, SHUT_RDWR); -#if defined(__NETWARE__) - /* - The following code is disabled for normal systems as it causes MySQL - to hang on AIX 4.3 during shutdown - */ - DBUG_PRINT("info", ("calling closesocket on %s socket", info)); - (void) closesocket(tmp_sock); -#endif } DBUG_VOID_RETURN; } @@ -1537,9 +1806,9 @@ static void close_server_sock() close_socket(extra_ip_sock, "TCP/IP"); close_socket(unix_sock, "unix/IP"); - if (unix_sock != INVALID_SOCKET) + if (mysql_socket_getfd(unix_sock) != INVALID_SOCKET) (void) unlink(mysqld_unix_port); - base_ip_sock= extra_ip_sock= unix_sock= INVALID_SOCKET; + base_ip_sock= extra_ip_sock= unix_sock= MYSQL_INVALID_SOCKET; DBUG_VOID_RETURN; #endif @@ -1649,7 +1918,19 @@ static void __cdecl kill_server(int sig_ptr) } #endif + /* Stop wsrep threads in case they are running. */ + if (wsrep_running_threads > 0) + { + wsrep_stop_replication(NULL); + } + close_connections(); + + if (wsrep_inited == 1) + wsrep_deinit(true); + + wsrep_thr_deinit(); + if (sig != MYSQL_KILL_SIGNAL && sig != 0) unireg_abort(1); /* purecov: inspected */ @@ -1728,6 +2009,7 @@ void unireg_end(void) { clean_up(1); my_thread_end(); + sd_notify(0, "STATUS=MariaDB server is down"); #if defined(SIGNALS_DONT_BREAK_READ) exit(0); #else @@ -1744,18 +2026,56 @@ extern "C" void unireg_abort(int exit_code) usage(); if (exit_code) sql_print_error("Aborting\n"); + /* Don't write more notes to the log to not hide error message */ + disable_log_notes= 1; + +#ifdef WITH_WSREP + /* Check if wsrep class is used. If yes, then cleanup wsrep */ + if (wsrep) + { + /* + This is an abort situation, we cannot expect to gracefully close all + wsrep threads here, we can only diconnect from service + */ + wsrep_close_client_connections(FALSE); + shutdown_in_progress= 1; + wsrep->disconnect(wsrep); + WSREP_INFO("Service disconnected."); + wsrep_close_threads(NULL); /* this won't close all threads */ + sleep(1); /* so give some time to exit for those which can */ + WSREP_INFO("Some threads may fail to exit."); + + /* In bootstrap mode we deinitialize wsrep here. */ + if (opt_bootstrap && wsrep_inited) + wsrep_deinit(true); + } +#endif // WITH_WSREP + clean_up(!opt_abort && (exit_code || !opt_bootstrap)); /* purecov: inspected */ DBUG_PRINT("quit",("done with cleanup in unireg_abort")); mysqld_exit(exit_code); } + +static void cleanup_tls() +{ + if (THR_THD) + (void)pthread_key_delete(THR_THD); + if (THR_MALLOC) + (void)pthread_key_delete(THR_MALLOC); +} + + static void mysqld_exit(int exit_code) { + DBUG_ENTER("mysqld_exit"); /* Important note: we wait for the signal thread to end, but if a kill -15 signal was sent, the signal thread did spawn the kill_server_thread thread, which is running concurrently. */ + rpl_deinit_gtid_waiting(); + rpl_deinit_gtid_slave_state(); wait_for_signal_thread_to_end(); mysql_audit_finalize(); clean_up_mutexes(); @@ -1764,6 +2084,9 @@ static void mysqld_exit(int exit_code) #ifdef WITH_PERFSCHEMA_STORAGE_ENGINE shutdown_performance_schema(); // we do it as late as possible #endif + cleanup_tls(); + DBUG_LEAVE; + sd_notify(0, "STATUS=MariaDB server is down"); exit(exit_code); /* purecov: inspected */ } @@ -1775,39 +2098,34 @@ void clean_up(bool print_message) if (cleanup_done++) return; /* purecov: inspected */ - close_active_mi(); +#ifdef HAVE_REPLICATION + // We must call end_slave() as clean_up may have been called during startup + end_slave(); + if (use_slave_mask) + my_bitmap_free(&slave_error_mask); +#endif stop_handle_manager(); release_ddl_log(); - /* - make sure that handlers finish up - what they have that is dependent on the binlog - */ - ha_binlog_end(current_thd); - logger.cleanup_base(); injector::free_instance(); mysql_bin_log.cleanup(); -#ifdef HAVE_REPLICATION - if (use_slave_mask) - bitmap_free(&slave_error_mask); -#endif my_tz_free(); my_dboptions_cache_free(); ignore_db_dirs_free(); -#ifndef NO_EMBEDDED_ACCESS_CHECKS servers_free(1); +#ifndef NO_EMBEDDED_ACCESS_CHECKS acl_free(1); grant_free(); #endif query_cache_destroy(); hostname_cache_free(); - item_user_lock_free(); + item_func_sleep_free(); lex_free(); /* Free some memory */ item_create_cleanup(); - table_def_start_shutdown(); + tdc_start_shutdown(); plugin_shutdown(); udf_free(); ha_end(); @@ -1815,36 +2133,40 @@ void clean_up(bool print_message) tc_log->close(); delegates_destroy(); xid_cache_free(); - table_def_free(); + tdc_deinit(); mdl_destroy(); + dflt_key_cache= 0; key_caches.delete_elements((void (*)(const char*, uchar*)) free_key_cache); wt_end(); multi_keycache_free(); sp_cache_end(); free_status_vars(); end_thr_alarm(1); /* Free allocated memory */ +#ifndef EMBEDDED_LIBRARY + end_thr_timer(); +#endif my_free_open_file_info(); if (defaults_argv) free_defaults(defaults_argv); free_tmpdir(&mysql_tmpdir_list); - bitmap_free(&temp_pool); + my_bitmap_free(&temp_pool); free_max_user_conn(); free_global_user_stats(); free_global_client_stats(); free_global_table_stats(); free_global_index_stats(); delete_dynamic(&all_options); + free_all_rpl_filters(); #ifdef HAVE_REPLICATION end_slave_list(); #endif my_uuid_end(); delete binlog_filter; - delete rpl_filter; + delete global_rpl_filter; end_ssl(); #ifndef EMBEDDED_LIBRARY vio_end(); #endif /*!EMBEDDED_LIBRARY*/ - my_regex_end(); #if defined(ENABLED_DEBUG_SYNC) /* End the debug sync facility. See debug_sync.cc. */ debug_sync_end(); @@ -1856,6 +2178,7 @@ void clean_up(bool print_message) sql_print_information(ER_DEFAULT(ER_SHUTDOWN_COMPLETE),my_progname); cleanup_errmsgs(); MYSQL_CALLBACK(thread_scheduler, end, ()); + thread_scheduler= 0; mysql_library_end(); finish_client_errs(); (void) my_error_unregister(ER_ERROR_FIRST, ER_ERROR_LAST); // finish server errs @@ -1863,8 +2186,6 @@ void clean_up(bool print_message) /* Tell main we are ready */ logger.cleanup_end(); sys_var_end(); - my_atomic_rwlock_destroy(&global_query_id_lock); - my_atomic_rwlock_destroy(&thread_running_lock); free_charsets(); mysql_mutex_lock(&LOCK_thread_count); DBUG_PRINT("quit", ("got thread count lock")); @@ -1873,6 +2194,14 @@ void clean_up(bool print_message) mysql_cond_broadcast(&COND_thread_count); mysql_mutex_unlock(&LOCK_thread_count); + my_free(const_cast<char*>(log_bin_basename)); + my_free(const_cast<char*>(log_bin_index)); +#ifndef EMBEDDED_LIBRARY + my_free(const_cast<char*>(relay_log_basename)); + my_free(const_cast<char*>(relay_log_index)); +#endif + free_list(opt_plugin_load_list_ptr); + /* The following lines may never be executed as the main thread may have killed us @@ -1908,7 +2237,9 @@ static void clean_up_mutexes() DBUG_ENTER("clean_up_mutexes"); mysql_rwlock_destroy(&LOCK_grant); mysql_mutex_destroy(&LOCK_thread_count); + mysql_mutex_destroy(&LOCK_thread_cache); mysql_mutex_destroy(&LOCK_status); + mysql_mutex_destroy(&LOCK_show_status); mysql_mutex_destroy(&LOCK_delayed_insert); mysql_mutex_destroy(&LOCK_delayed_status); mysql_mutex_destroy(&LOCK_delayed_create); @@ -1929,7 +2260,6 @@ static void clean_up_mutexes() #endif /* HAVE_OPENSSL */ #ifdef HAVE_REPLICATION mysql_mutex_destroy(&LOCK_rpl_status); - mysql_cond_destroy(&COND_rpl_status); #endif /* HAVE_REPLICATION */ mysql_mutex_destroy(&LOCK_active_mi); mysql_rwlock_destroy(&LOCK_sys_init_connect); @@ -1945,7 +2275,11 @@ static void clean_up_mutexes() mysql_mutex_destroy(&LOCK_server_started); mysql_cond_destroy(&COND_server_started); mysql_mutex_destroy(&LOCK_prepare_ordered); + mysql_cond_destroy(&COND_prepare_ordered); + mysql_mutex_destroy(&LOCK_after_binlog_sync); mysql_mutex_destroy(&LOCK_commit_ordered); + mysql_mutex_destroy(&LOCK_slave_background); + mysql_cond_destroy(&COND_slave_background); DBUG_VOID_RETURN; } @@ -1954,7 +2288,12 @@ static void clean_up_mutexes() ** Init IP and UNIX socket ****************************************************************************/ -#ifndef EMBEDDED_LIBRARY +#ifdef EMBEDDED_LIBRARY +static void set_ports() +{ +} + +#else static void set_ports() { char *env; @@ -1975,10 +2314,13 @@ static void set_ports() #if MYSQL_PORT_DEFAULT == 0 struct servent *serv_ptr; if ((serv_ptr= getservbyname("mysql", "tcp"))) - mysqld_port= ntohs((u_short) serv_ptr->s_port); /* purecov: inspected */ + SYSVAR_AUTOSIZE(mysqld_port, ntohs((u_short) serv_ptr->s_port)); #endif if ((env = getenv("MYSQL_TCP_PORT"))) - mysqld_port= (uint) atoi(env); /* purecov: inspected */ + { + mysqld_port= (uint) atoi(env); + set_sys_var_value_origin(&mysqld_port, sys_var::ENV); + } } if (!mysqld_unix_port) { @@ -1988,7 +2330,10 @@ static void set_ports() mysqld_unix_port= (char*) MYSQL_UNIX_ADDR; #endif if ((env = getenv("MYSQL_UNIX_PORT"))) - mysqld_unix_port= env; /* purecov: inspected */ + { + mysqld_unix_port= env; + set_sys_var_value_origin(&mysqld_unix_port, sys_var::ENV); + } } } @@ -2075,14 +2420,15 @@ static void set_root(const char *path) Activate usage of a tcp port */ -static my_socket activate_tcp_port(uint port) +static MYSQL_SOCKET activate_tcp_port(uint port) { struct addrinfo *ai, *a; struct addrinfo hints; int error; int arg; char port_buf[NI_MAXSERV]; - my_socket ip_sock= INVALID_SOCKET; + const char *real_bind_addr_str; + MYSQL_SOCKET ip_sock= MYSQL_INVALID_SOCKET; DBUG_ENTER("activate_tcp_port"); DBUG_PRINT("general",("IP Socket is %d",port)); @@ -2090,34 +2436,55 @@ static my_socket activate_tcp_port(uint port) hints.ai_flags= AI_PASSIVE; hints.ai_socktype= SOCK_STREAM; hints.ai_family= AF_UNSPEC; + + if (my_bind_addr_str && strcmp(my_bind_addr_str, "*") == 0) + real_bind_addr_str= NULL; // windows doesn't seem to support * here + else + real_bind_addr_str= my_bind_addr_str; my_snprintf(port_buf, NI_MAXSERV, "%d", port); - error= getaddrinfo(my_bind_addr_str, port_buf, &hints, &ai); + error= getaddrinfo(real_bind_addr_str, port_buf, &hints, &ai); if (error != 0) { DBUG_PRINT("error",("Got error: %d from getaddrinfo()", error)); - sql_perror(ER_DEFAULT(ER_IPSOCK_ERROR)); /* purecov: tested */ + + sql_print_error("%s: %s", ER_DEFAULT(ER_IPSOCK_ERROR), gai_strerror(error)); unireg_abort(1); /* purecov: tested */ } + /* + special case: for wildcard addresses prefer ipv6 over ipv4, + because we later switch off IPV6_V6ONLY, so ipv6 wildcard + addresses will work for ipv4 too + */ + if (!real_bind_addr_str && ai->ai_family == AF_INET && ai->ai_next + && ai->ai_next->ai_family == AF_INET6) + { + a= ai; + ai= ai->ai_next; + a->ai_next= ai->ai_next; + ai->ai_next= a; + } + for (a= ai; a != NULL; a= a->ai_next) { - ip_sock= socket(a->ai_family, a->ai_socktype, a->ai_protocol); - - char ip_addr[INET6_ADDRSTRLEN]; + ip_sock= mysql_socket_socket(key_socket_tcpip, a->ai_family, + a->ai_socktype, a->ai_protocol); + char ip_addr[INET6_ADDRSTRLEN]; if (vio_get_normalized_ip_string(a->ai_addr, a->ai_addrlen, ip_addr, sizeof (ip_addr))) { ip_addr[0]= 0; } - if (ip_sock == INVALID_SOCKET) + if (mysql_socket_getfd(ip_sock) == INVALID_SOCKET) { - sql_print_error("Failed to create a socket for %s '%s': errno: %d.", - (a->ai_family == AF_INET) ? "IPv4" : "IPv6", - (const char *) ip_addr, - (int) socket_errno); + sql_print_message_func func= real_bind_addr_str ? sql_print_error + : sql_print_warning; + func("Failed to create a socket for %s '%s': errno: %d.", + (a->ai_family == AF_INET) ? "IPv4" : "IPv6", + (const char *) ip_addr, (int) socket_errno); } else { @@ -2127,20 +2494,23 @@ static my_socket activate_tcp_port(uint port) } } - if (ip_sock == INVALID_SOCKET) + if (mysql_socket_getfd(ip_sock) == INVALID_SOCKET) { DBUG_PRINT("error",("Got error: %d from socket()",socket_errno)); sql_perror(ER_DEFAULT(ER_IPSOCK_ERROR)); /* purecov: tested */ unireg_abort(1); /* purecov: tested */ } + mysql_socket_set_thread_owner(ip_sock); + #ifndef __WIN__ /* We should not use SO_REUSEADDR on windows as this would enable a user to open two mysqld servers with the same TCP/IP port. */ arg= 1; - (void) setsockopt(ip_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&arg,sizeof(arg)); + (void) mysql_socket_setsockopt(ip_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&arg, + sizeof(arg)); #endif /* __WIN__ */ #ifdef IPV6_V6ONLY @@ -2156,10 +2526,16 @@ static my_socket activate_tcp_port(uint port) if (a->ai_family == AF_INET6) { arg= 0; - (void) setsockopt(ip_sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, - sizeof(arg)); + (void) mysql_socket_setsockopt(ip_sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char*)&arg, sizeof(arg)); } #endif + +#ifdef IP_FREEBIND + arg= 1; + (void) mysql_socket_setsockopt(ip_sock, IPPROTO_IP, IP_FREEBIND, (char*) &arg, + sizeof(arg)); +#endif /* Sometimes the port is not released fast enough when stopping and restarting the server. This happens quite often with the test suite @@ -2172,7 +2548,7 @@ static my_socket activate_tcp_port(uint port) uint waited, retry, this_wait; for (waited= 0, retry= 1; ; retry++, waited+= this_wait) { - if (((ret= bind(ip_sock, a->ai_addr, a->ai_addrlen)) >= 0 ) || + if (((ret= mysql_socket_bind(ip_sock, a->ai_addr, a->ai_addrlen)) >= 0 ) || (socket_errno != SOCKET_EADDRINUSE) || (waited >= mysqld_port_timeout)) break; @@ -2191,13 +2567,18 @@ static my_socket activate_tcp_port(uint port) "port: %u ?", port); unireg_abort(1); } - if (listen(ip_sock,(int) back_log) < 0) + if (mysql_socket_listen(ip_sock,(int) back_log) < 0) { sql_perror("Can't start server: listen() on TCP/IP port"); sql_print_error("listen() on TCP/IP failed with error %d", socket_errno); unireg_abort(1); } + +#ifdef FD_CLOEXEC + (void) fcntl(mysql_socket_getfd(ip_sock), F_SETFD, FD_CLOEXEC); +#endif + DBUG_RETURN(ip_sock); } @@ -2216,7 +2597,7 @@ static void network_init(void) if (report_port == 0) { - report_port= mysqld_port; + SYSVAR_AUTOSIZE(report_port, mysqld_port); } #ifndef DBUG_OFF if (!opt_disable_networking) @@ -2282,21 +2663,26 @@ static void network_init(void) (uint) sizeof(UNIXaddr.sun_path) - 1, mysqld_unix_port); unireg_abort(1); } - if ((unix_sock= socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + unix_sock= mysql_socket_socket(key_socket_unix, AF_UNIX, SOCK_STREAM, 0); + if (mysql_socket_getfd(unix_sock) < 0) { sql_perror("Can't start server : UNIX Socket "); /* purecov: inspected */ unireg_abort(1); /* purecov: inspected */ } + + mysql_socket_set_thread_owner(unix_sock); + bzero((char*) &UNIXaddr, sizeof(UNIXaddr)); UNIXaddr.sun_family = AF_UNIX; strmov(UNIXaddr.sun_path, mysqld_unix_port); (void) unlink(mysqld_unix_port); arg= 1; - (void) setsockopt(unix_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&arg, - sizeof(arg)); + (void) mysql_socket_setsockopt(unix_sock,SOL_SOCKET,SO_REUSEADDR, + (char*)&arg, sizeof(arg)); umask(0); - if (bind(unix_sock, reinterpret_cast<struct sockaddr *>(&UNIXaddr), - sizeof(UNIXaddr)) < 0) + if (mysql_socket_bind(unix_sock, + reinterpret_cast<struct sockaddr *>(&UNIXaddr), + sizeof(UNIXaddr)) < 0) { sql_perror("Can't start server : Bind on unix socket"); /* purecov: tested */ sql_print_error("Do you already have another mysqld server running on socket: %s ?",mysqld_unix_port); @@ -2306,9 +2692,12 @@ static void network_init(void) #if defined(S_IFSOCK) && defined(SECURE_SOCKETS) (void) chmod(mysqld_unix_port,S_IFSOCK); /* Fix solaris 2.6 bug */ #endif - if (listen(unix_sock,(int) back_log) < 0) + if (mysql_socket_listen(unix_sock,(int) back_log) < 0) sql_print_warning("listen() on Unix socket failed with error %d", socket_errno); +#ifdef FD_CLOEXEC + (void) fcntl(mysql_socket_getfd(unix_sock), F_SETFD, FD_CLOEXEC); +#endif } #endif DBUG_PRINT("info",("server started")); @@ -2343,7 +2732,7 @@ void close_connection(THD *thd, uint sql_errno) { sleep(0); /* Workaround to avoid tailcall optimisation */ } - MYSQL_AUDIT_NOTIFY_CONNECTION_DISCONNECT(thd, sql_errno); + mysql_audit_notify_connection_disconnect(thd, sql_errno); DBUG_VOID_RETURN; } #endif /* EMBEDDED_LIBRARY */ @@ -2383,6 +2772,16 @@ void thd_cleanup(THD *thd) void dec_connection_count(THD *thd) { +#ifdef WITH_WSREP + /* + Do not decrement when its wsrep system thread. wsrep_applier is set for + applier as well as rollbacker threads. + */ + if (thd->wsrep_applier) + return; +#endif /* WITH_WSREP */ + + DBUG_ASSERT(*thd->scheduler->connection_count > 0); mysql_mutex_lock(&LOCK_connection_count); (*thd->scheduler->connection_count)--; mysql_mutex_unlock(&LOCK_connection_count); @@ -2390,6 +2789,30 @@ void dec_connection_count(THD *thd) /* + Send a signal to unblock close_conneciton() if there is no more + threads running with a THD attached + + It's safe to check for thread_count and service_thread_count outside + of a mutex as we are only interested to see if they where decremented + to 0 by a previous unlink_thd() call. + + We should only signal COND_thread_count if both variables are 0, + false positives are ok. +*/ + +void signal_thd_deleted() +{ + if (!thread_count && ! service_thread_count) + { + /* Signal close_connections() that all THD's are freed */ + mysql_mutex_lock(&LOCK_thread_count); + mysql_cond_broadcast(&COND_thread_count); + mysql_mutex_unlock(&LOCK_thread_count); + } +} + + +/* Unlink thd from global list of available connections and free thd SYNOPSIS @@ -2418,19 +2841,11 @@ void unlink_thd(THD *thd) sync feature has been shut down at this point. */ DBUG_EXECUTE_IF("sleep_after_lock_thread_count_before_delete_thd", sleep(5);); - if (unlikely(abort_loop)) - { - /* - During shutdown, we have to delete thd inside the mutex - to not refer to mutexes that may be deleted during shutdown - */ - delete thd; - thd= 0; - } - thread_count--; mysql_mutex_unlock(&LOCK_thread_count); delete thd; + thread_safe_decrement32(&thread_count); + DBUG_VOID_RETURN; } @@ -2442,7 +2857,7 @@ void unlink_thd(THD *thd) cache_thread() NOTES - LOCK_thread_count has to be locked + LOCK_thread_cache is used to protect the cache variables RETURN 0 Thread was not put in cache @@ -2453,7 +2868,9 @@ void unlink_thd(THD *thd) static bool cache_thread() { - mysql_mutex_assert_owner(&LOCK_thread_count); + DBUG_ENTER("cache_thread"); + + mysql_mutex_lock(&LOCK_thread_cache); if (cached_thread_count < thread_cache_size && ! abort_loop && !kill_cached_threads) { @@ -2461,17 +2878,16 @@ static bool cache_thread() DBUG_PRINT("info", ("Adding thread to cache")); cached_thread_count++; -#ifdef HAVE_PSI_INTERFACE +#ifdef HAVE_PSI_THREAD_INTERFACE /* Delete the instrumentation for the job that just completed, before parking this pthread in the cache (blocked on COND_thread_cache). */ - if (likely(PSI_server != NULL)) - PSI_server->delete_current_thread(); + PSI_THREAD_CALL(delete_current_thread)(); #endif while (!abort_loop && ! wake_thread && ! kill_cached_threads) - mysql_cond_wait(&COND_thread_cache, &LOCK_thread_count); + mysql_cond_wait(&COND_thread_cache, &LOCK_thread_cache); cached_thread_count--; if (kill_cached_threads) mysql_cond_signal(&COND_flush_thread_cache); @@ -2480,21 +2896,19 @@ static bool cache_thread() THD *thd; wake_thread--; thd= thread_cache.get(); + mysql_mutex_unlock(&LOCK_thread_cache); + thd->thread_stack= (char*) &thd; // For store_globals (void) thd->store_globals(); -#ifdef HAVE_PSI_INTERFACE +#ifdef HAVE_PSI_THREAD_INTERFACE /* Create new instrumentation for the new THD job, and attach it to this running pthread. */ - if (likely(PSI_server != NULL)) - { - PSI_thread *psi= PSI_server->new_thread(key_thread_one_connection, - thd, thd->thread_id); - if (likely(psi != NULL)) - PSI_server->set_thread(psi); - } + PSI_thread *psi= PSI_THREAD_CALL(new_thread)(key_thread_one_connection, + thd, thd->thread_id); + PSI_THREAD_CALL(set_thread)(psi); #endif /* @@ -2505,11 +2919,16 @@ static bool cache_thread() thd->mysys_var->abort= 0; thd->thr_create_utime= microsecond_interval_timer(); thd->start_utime= thd->thr_create_utime; + + /* Link thd into list of all active threads (THD's) */ + mysql_mutex_lock(&LOCK_thread_count); threads.append(thd); - return(1); + mysql_mutex_unlock(&LOCK_thread_count); + DBUG_RETURN(1); } } - return(0); + mysql_mutex_unlock(&LOCK_thread_cache); + DBUG_RETURN(0); } @@ -2535,26 +2954,19 @@ static bool cache_thread() bool one_thread_per_connection_end(THD *thd, bool put_in_cache) { DBUG_ENTER("one_thread_per_connection_end"); + const bool wsrep_applier= IF_WSREP(thd->wsrep_applier, false); + unlink_thd(thd); - /* Mark that current_thd is not valid anymore */ - my_pthread_setspecific_ptr(THR_THD, 0); - if (put_in_cache) - { - mysql_mutex_lock(&LOCK_thread_count); - put_in_cache= cache_thread(); - mysql_mutex_unlock(&LOCK_thread_count); - if (put_in_cache) - DBUG_RETURN(0); // Thread is reused - } - /* It's safe to broadcast outside a lock (COND... is not deleted here) */ - DBUG_PRINT("signal", ("Broadcasting COND_thread_count")); + if (!wsrep_applier && put_in_cache && cache_thread()) + DBUG_RETURN(0); // Thread is reused + + signal_thd_deleted(); DBUG_LEAVE; // Must match DBUG_ENTER() #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) ERR_remove_state(0); #endif my_thread_end(); - mysql_cond_broadcast(&COND_thread_count); pthread_exit(0); return 0; // Avoid compiler warnings @@ -2563,15 +2975,17 @@ bool one_thread_per_connection_end(THD *thd, bool put_in_cache) void flush_thread_cache() { - mysql_mutex_lock(&LOCK_thread_count); + DBUG_ENTER("flush_thread_cache"); + mysql_mutex_lock(&LOCK_thread_cache); kill_cached_threads++; while (cached_thread_count) { mysql_cond_broadcast(&COND_thread_cache); - mysql_cond_wait(&COND_flush_thread_cache, &LOCK_thread_count); + mysql_cond_wait(&COND_flush_thread_cache, &LOCK_thread_cache); } kill_cached_threads--; - mysql_mutex_unlock(&LOCK_thread_count); + mysql_mutex_unlock(&LOCK_thread_cache); + DBUG_VOID_RETURN; } @@ -2695,7 +3109,7 @@ LONG WINAPI my_unhandler_exception_filter(EXCEPTION_POINTERS *ex_pointers) } -static void init_signals(void) +void init_signals(void) { if(opt_console) SetConsoleCtrlHandler(console_event_handler,TRUE); @@ -2826,7 +3240,7 @@ static size_t my_setstacksize(pthread_attr_t *attr, size_t stacksize) #ifndef EMBEDDED_LIBRARY -static void init_signals(void) +void init_signals(void) { sigset_t set; struct sigaction sa; @@ -3009,16 +3423,15 @@ pthread_handler_t signal_hand(void *arg __attribute__((unused))) sql_print_information("Got signal %d to shutdown mysqld",sig); #endif /* switch to the old log message processing */ - logger.set_handlers(LOG_FILE, opt_slow_log ? LOG_FILE:LOG_NONE, + logger.set_handlers(LOG_FILE, global_system_variables.sql_log_slow ? LOG_FILE:LOG_NONE, opt_log ? LOG_FILE:LOG_NONE); DBUG_PRINT("info",("Got signal: %d abort_loop: %d",sig,abort_loop)); if (!abort_loop) { abort_loop=1; // mark abort for threads -#ifdef HAVE_PSI_INTERFACE +#ifdef HAVE_PSI_THREAD_INTERFACE /* Delete the instrumentation for the signal thread */ - if (likely(PSI_server != NULL)) - PSI_server->delete_current_thread(); + PSI_THREAD_CALL(delete_current_thread)(); #endif #ifdef USE_ONE_SIGNAL_HAND pthread_t tmp; @@ -3048,13 +3461,15 @@ pthread_handler_t signal_hand(void *arg __attribute__((unused))) if (log_output_options & LOG_NONE) { logger.set_handlers(LOG_FILE, - opt_slow_log ? LOG_TABLE : LOG_NONE, + global_system_variables.sql_log_slow ? + LOG_TABLE : LOG_NONE, opt_log ? LOG_TABLE : LOG_NONE); } else { logger.set_handlers(LOG_FILE, - opt_slow_log ? log_output_options : LOG_NONE, + global_system_variables.sql_log_slow ? + log_output_options : LOG_NONE, opt_log ? log_output_options : LOG_NONE); } break; @@ -3090,28 +3505,28 @@ extern "C" void my_message_sql(uint error, const char *str, myf MyFlags); void my_message_sql(uint error, const char *str, myf MyFlags) { THD *thd= current_thd; - MYSQL_ERROR::enum_warning_level level; + Sql_condition::enum_warning_level level; sql_print_message_func func; - DBUG_ENTER("my_message_sql"); - DBUG_PRINT("error", ("error: %u message: '%s' Flag: %d", error, str, MyFlags)); + DBUG_PRINT("error", ("error: %u message: '%s' Flag: %lu", error, str, + MyFlags)); DBUG_ASSERT(str != NULL); DBUG_ASSERT(error != 0); if (MyFlags & ME_JUST_INFO) { - level= MYSQL_ERROR::WARN_LEVEL_NOTE; + level= Sql_condition::WARN_LEVEL_NOTE; func= sql_print_information; } else if (MyFlags & ME_JUST_WARNING) { - level= MYSQL_ERROR::WARN_LEVEL_WARN; + level= Sql_condition::WARN_LEVEL_WARN; func= sql_print_warning; } else { - level= MYSQL_ERROR::WARN_LEVEL_ERROR; + level= Sql_condition::WARN_LEVEL_ERROR; func= sql_print_error; } @@ -3133,9 +3548,7 @@ void my_message_sql(uint error, const char *str, myf MyFlags) } -#ifndef EMBEDDED_LIBRARY extern "C" void *my_str_malloc_mysqld(size_t size); -extern "C" void my_str_free_mysqld(void *ptr); void *my_str_malloc_mysqld(size_t size) { @@ -3143,13 +3556,6 @@ void *my_str_malloc_mysqld(size_t size) } -void my_str_free_mysqld(void *ptr) -{ - my_free(ptr); -} -#endif /* EMBEDDED_LIBRARY */ - - #ifdef __WIN__ pthread_handler_t handle_shutdown(void *arg) @@ -3175,28 +3581,56 @@ sizeof(load_default_groups)/sizeof(load_default_groups[0]); #endif -#ifndef EMBEDDED_LIBRARY /** This function is used to check for stack overrun for pathological cases of regular expressions and 'like' expressions. - The call to current_thd is quite expensive, so we try to avoid it - for the normal cases. +*/ +extern "C" int +check_enough_stack_size_slow() +{ + uchar stack_top; + THD *my_thd= current_thd; + if (my_thd != NULL) + return check_stack_overrun(my_thd, STACK_MIN_SIZE * 2, &stack_top); + return 0; +} + + +/* + The call to current_thd in check_enough_stack_size_slow is quite expensive, + so we try to avoid it for the normal cases. The size of each stack frame for the wildcmp() routines is ~128 bytes, so checking *every* recursive call is not necessary. */ extern "C" int check_enough_stack_size(int recurse_level) { - uchar stack_top; if (recurse_level % 16 != 0) return 0; - - THD *my_thd= current_thd; - if (my_thd != NULL) - return check_stack_overrun(my_thd, STACK_MIN_SIZE * 2, &stack_top); - return 0; + return check_enough_stack_size_slow(); } + + +static void init_libstrings() +{ +#ifndef EMBEDDED_LIBRARY + my_string_stack_guard= check_enough_stack_size; #endif +} + +ulonglong my_pcre_frame_size; + +static void init_pcre() +{ + pcre_malloc= pcre_stack_malloc= my_str_malloc_mysqld; + pcre_free= pcre_stack_free= my_free; + pcre_stack_guard= check_enough_stack_size_slow; + /* See http://pcre.org/original/doc/html/pcrestack.html */ + my_pcre_frame_size= -pcre_exec(NULL, NULL, NULL, -999, -999, 0, NULL, 0); + // pcre can underestimate its stack usage. Use a safe value, as in the manual + set_if_bigger(my_pcre_frame_size, 500); + my_pcre_frame_size += 16; // Again, safety margin, see the manual +} /** @@ -3229,175 +3663,385 @@ static bool init_global_datetime_format(timestamp_type format_type, return false; } +#define COM_STATUS(X) (void*) offsetof(STATUS_VAR, X), SHOW_LONG_STATUS +#define STMT_STATUS(X) COM_STATUS(com_stat[(uint) X]) + SHOW_VAR com_status_vars[]= { - {"admin_commands", (char*) offsetof(STATUS_VAR, com_other), SHOW_LONG_STATUS}, - {"alter_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_DB]), SHOW_LONG_STATUS}, - {"alter_db_upgrade", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_DB_UPGRADE]), SHOW_LONG_STATUS}, - {"alter_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_EVENT]), SHOW_LONG_STATUS}, - {"alter_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_FUNCTION]), SHOW_LONG_STATUS}, - {"alter_procedure", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_PROCEDURE]), SHOW_LONG_STATUS}, - {"alter_server", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_SERVER]), SHOW_LONG_STATUS}, - {"alter_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_TABLE]), SHOW_LONG_STATUS}, - {"alter_tablespace", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_TABLESPACE]), SHOW_LONG_STATUS}, - {"analyze", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ANALYZE]), SHOW_LONG_STATUS}, - {"assign_to_keycache", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ASSIGN_TO_KEYCACHE]), SHOW_LONG_STATUS}, - {"begin", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_BEGIN]), SHOW_LONG_STATUS}, - {"binlog", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_BINLOG_BASE64_EVENT]), SHOW_LONG_STATUS}, - {"call_procedure", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CALL]), SHOW_LONG_STATUS}, - {"change_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHANGE_DB]), SHOW_LONG_STATUS}, - {"change_master", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHANGE_MASTER]), SHOW_LONG_STATUS}, - {"check", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHECK]), SHOW_LONG_STATUS}, - {"checksum", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHECKSUM]), SHOW_LONG_STATUS}, - {"commit", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_COMMIT]), SHOW_LONG_STATUS}, - {"create_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_DB]), SHOW_LONG_STATUS}, - {"create_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_EVENT]), SHOW_LONG_STATUS}, - {"create_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_SPFUNCTION]), SHOW_LONG_STATUS}, - {"create_index", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_INDEX]), SHOW_LONG_STATUS}, - {"create_procedure", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_PROCEDURE]), SHOW_LONG_STATUS}, - {"create_server", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_SERVER]), SHOW_LONG_STATUS}, - {"create_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_TABLE]), SHOW_LONG_STATUS}, - {"create_trigger", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_TRIGGER]), SHOW_LONG_STATUS}, - {"create_udf", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_FUNCTION]), SHOW_LONG_STATUS}, - {"create_user", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_USER]), SHOW_LONG_STATUS}, - {"create_view", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_VIEW]), SHOW_LONG_STATUS}, - {"dealloc_sql", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DEALLOCATE_PREPARE]), SHOW_LONG_STATUS}, - {"delete", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DELETE]), SHOW_LONG_STATUS}, - {"delete_multi", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DELETE_MULTI]), SHOW_LONG_STATUS}, - {"do", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DO]), SHOW_LONG_STATUS}, - {"drop_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_DB]), SHOW_LONG_STATUS}, - {"drop_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_EVENT]), SHOW_LONG_STATUS}, - {"drop_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_FUNCTION]), SHOW_LONG_STATUS}, - {"drop_index", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_INDEX]), SHOW_LONG_STATUS}, - {"drop_procedure", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_PROCEDURE]), SHOW_LONG_STATUS}, - {"drop_server", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_SERVER]), SHOW_LONG_STATUS}, - {"drop_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_TABLE]), SHOW_LONG_STATUS}, - {"drop_trigger", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_TRIGGER]), SHOW_LONG_STATUS}, - {"drop_user", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_USER]), SHOW_LONG_STATUS}, - {"drop_view", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_VIEW]), SHOW_LONG_STATUS}, - {"empty_query", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_EMPTY_QUERY]), SHOW_LONG_STATUS}, - {"execute_sql", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_EXECUTE]), SHOW_LONG_STATUS}, - {"flush", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_FLUSH]), SHOW_LONG_STATUS}, - {"grant", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_GRANT]), SHOW_LONG_STATUS}, - {"ha_close", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_HA_CLOSE]), SHOW_LONG_STATUS}, - {"ha_open", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_HA_OPEN]), SHOW_LONG_STATUS}, - {"ha_read", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_HA_READ]), SHOW_LONG_STATUS}, - {"help", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_HELP]), SHOW_LONG_STATUS}, - {"insert", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_INSERT]), SHOW_LONG_STATUS}, - {"insert_select", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_INSERT_SELECT]), SHOW_LONG_STATUS}, - {"install_plugin", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_INSTALL_PLUGIN]), SHOW_LONG_STATUS}, - {"kill", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_KILL]), SHOW_LONG_STATUS}, - {"load", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_LOAD]), SHOW_LONG_STATUS}, - {"lock_tables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_LOCK_TABLES]), SHOW_LONG_STATUS}, - {"optimize", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_OPTIMIZE]), SHOW_LONG_STATUS}, - {"preload_keys", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PRELOAD_KEYS]), SHOW_LONG_STATUS}, - {"prepare_sql", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PREPARE]), SHOW_LONG_STATUS}, - {"purge", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE]), SHOW_LONG_STATUS}, - {"purge_before_date", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_PURGE_BEFORE]), SHOW_LONG_STATUS}, - {"release_savepoint", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RELEASE_SAVEPOINT]), SHOW_LONG_STATUS}, - {"rename_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RENAME_TABLE]), SHOW_LONG_STATUS}, - {"rename_user", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RENAME_USER]), SHOW_LONG_STATUS}, - {"repair", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REPAIR]), SHOW_LONG_STATUS}, - {"replace", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REPLACE]), SHOW_LONG_STATUS}, - {"replace_select", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REPLACE_SELECT]), SHOW_LONG_STATUS}, - {"reset", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RESET]), SHOW_LONG_STATUS}, - {"resignal", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_RESIGNAL]), SHOW_LONG_STATUS}, - {"revoke", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REVOKE]), SHOW_LONG_STATUS}, - {"revoke_all", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REVOKE_ALL]), SHOW_LONG_STATUS}, - {"rollback", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ROLLBACK]), SHOW_LONG_STATUS}, - {"rollback_to_savepoint",(char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ROLLBACK_TO_SAVEPOINT]), SHOW_LONG_STATUS}, - {"savepoint", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SAVEPOINT]), SHOW_LONG_STATUS}, - {"select", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SELECT]), SHOW_LONG_STATUS}, - {"set_option", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SET_OPTION]), SHOW_LONG_STATUS}, - {"show_authors", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_AUTHORS]), SHOW_LONG_STATUS}, - {"show_binlog_events", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_BINLOG_EVENTS]), SHOW_LONG_STATUS}, - {"show_binlogs", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_BINLOGS]), SHOW_LONG_STATUS}, - {"show_charsets", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CHARSETS]), SHOW_LONG_STATUS}, - {"show_client_statistics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CLIENT_STATS]), SHOW_LONG_STATUS}, - {"show_collations", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_COLLATIONS]), SHOW_LONG_STATUS}, - {"show_contributors", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CONTRIBUTORS]), SHOW_LONG_STATUS}, - {"show_create_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_DB]), SHOW_LONG_STATUS}, - {"show_create_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_EVENT]), SHOW_LONG_STATUS}, - {"show_create_func", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_FUNC]), SHOW_LONG_STATUS}, - {"show_create_proc", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_PROC]), SHOW_LONG_STATUS}, - {"show_create_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE]), SHOW_LONG_STATUS}, - {"show_create_trigger", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_TRIGGER]), SHOW_LONG_STATUS}, - {"show_databases", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_DATABASES]), SHOW_LONG_STATUS}, - {"show_engine_logs", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_LOGS]), SHOW_LONG_STATUS}, - {"show_engine_mutex", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_MUTEX]), SHOW_LONG_STATUS}, - {"show_engine_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_STATUS]), SHOW_LONG_STATUS}, - {"show_errors", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ERRORS]), SHOW_LONG_STATUS}, - {"show_events", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_EVENTS]), SHOW_LONG_STATUS}, - {"show_fields", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_FIELDS]), SHOW_LONG_STATUS}, + {"admin_commands", COM_STATUS(com_other)}, + {"alter_db", STMT_STATUS(SQLCOM_ALTER_DB)}, + {"alter_db_upgrade", STMT_STATUS(SQLCOM_ALTER_DB_UPGRADE)}, + {"alter_event", STMT_STATUS(SQLCOM_ALTER_EVENT)}, + {"alter_function", STMT_STATUS(SQLCOM_ALTER_FUNCTION)}, + {"alter_procedure", STMT_STATUS(SQLCOM_ALTER_PROCEDURE)}, + {"alter_server", STMT_STATUS(SQLCOM_ALTER_SERVER)}, + {"alter_table", STMT_STATUS(SQLCOM_ALTER_TABLE)}, + {"alter_tablespace", STMT_STATUS(SQLCOM_ALTER_TABLESPACE)}, + {"analyze", STMT_STATUS(SQLCOM_ANALYZE)}, + {"assign_to_keycache", STMT_STATUS(SQLCOM_ASSIGN_TO_KEYCACHE)}, + {"begin", STMT_STATUS(SQLCOM_BEGIN)}, + {"binlog", STMT_STATUS(SQLCOM_BINLOG_BASE64_EVENT)}, + {"call_procedure", STMT_STATUS(SQLCOM_CALL)}, + {"change_db", STMT_STATUS(SQLCOM_CHANGE_DB)}, + {"change_master", STMT_STATUS(SQLCOM_CHANGE_MASTER)}, + {"check", STMT_STATUS(SQLCOM_CHECK)}, + {"checksum", STMT_STATUS(SQLCOM_CHECKSUM)}, + {"commit", STMT_STATUS(SQLCOM_COMMIT)}, + {"compound_sql", STMT_STATUS(SQLCOM_COMPOUND)}, + {"create_db", STMT_STATUS(SQLCOM_CREATE_DB)}, + {"create_event", STMT_STATUS(SQLCOM_CREATE_EVENT)}, + {"create_function", STMT_STATUS(SQLCOM_CREATE_SPFUNCTION)}, + {"create_index", STMT_STATUS(SQLCOM_CREATE_INDEX)}, + {"create_procedure", STMT_STATUS(SQLCOM_CREATE_PROCEDURE)}, + {"create_role", STMT_STATUS(SQLCOM_CREATE_ROLE)}, + {"create_server", STMT_STATUS(SQLCOM_CREATE_SERVER)}, + {"create_table", STMT_STATUS(SQLCOM_CREATE_TABLE)}, + {"create_temporary_table", COM_STATUS(com_create_tmp_table)}, + {"create_trigger", STMT_STATUS(SQLCOM_CREATE_TRIGGER)}, + {"create_udf", STMT_STATUS(SQLCOM_CREATE_FUNCTION)}, + {"create_user", STMT_STATUS(SQLCOM_CREATE_USER)}, + {"create_view", STMT_STATUS(SQLCOM_CREATE_VIEW)}, + {"dealloc_sql", STMT_STATUS(SQLCOM_DEALLOCATE_PREPARE)}, + {"delete", STMT_STATUS(SQLCOM_DELETE)}, + {"delete_multi", STMT_STATUS(SQLCOM_DELETE_MULTI)}, + {"do", STMT_STATUS(SQLCOM_DO)}, + {"drop_db", STMT_STATUS(SQLCOM_DROP_DB)}, + {"drop_event", STMT_STATUS(SQLCOM_DROP_EVENT)}, + {"drop_function", STMT_STATUS(SQLCOM_DROP_FUNCTION)}, + {"drop_index", STMT_STATUS(SQLCOM_DROP_INDEX)}, + {"drop_procedure", STMT_STATUS(SQLCOM_DROP_PROCEDURE)}, + {"drop_role", STMT_STATUS(SQLCOM_DROP_ROLE)}, + {"drop_server", STMT_STATUS(SQLCOM_DROP_SERVER)}, + {"drop_table", STMT_STATUS(SQLCOM_DROP_TABLE)}, + {"drop_temporary_table", COM_STATUS(com_drop_tmp_table)}, + {"drop_trigger", STMT_STATUS(SQLCOM_DROP_TRIGGER)}, + {"drop_user", STMT_STATUS(SQLCOM_DROP_USER)}, + {"drop_view", STMT_STATUS(SQLCOM_DROP_VIEW)}, + {"empty_query", STMT_STATUS(SQLCOM_EMPTY_QUERY)}, + {"execute_sql", STMT_STATUS(SQLCOM_EXECUTE)}, + {"flush", STMT_STATUS(SQLCOM_FLUSH)}, + {"get_diagnostics", STMT_STATUS(SQLCOM_GET_DIAGNOSTICS)}, + {"grant", STMT_STATUS(SQLCOM_GRANT)}, + {"grant_role", STMT_STATUS(SQLCOM_GRANT_ROLE)}, + {"ha_close", STMT_STATUS(SQLCOM_HA_CLOSE)}, + {"ha_open", STMT_STATUS(SQLCOM_HA_OPEN)}, + {"ha_read", STMT_STATUS(SQLCOM_HA_READ)}, + {"help", STMT_STATUS(SQLCOM_HELP)}, + {"insert", STMT_STATUS(SQLCOM_INSERT)}, + {"insert_select", STMT_STATUS(SQLCOM_INSERT_SELECT)}, + {"install_plugin", STMT_STATUS(SQLCOM_INSTALL_PLUGIN)}, + {"kill", STMT_STATUS(SQLCOM_KILL)}, + {"load", STMT_STATUS(SQLCOM_LOAD)}, + {"lock_tables", STMT_STATUS(SQLCOM_LOCK_TABLES)}, + {"optimize", STMT_STATUS(SQLCOM_OPTIMIZE)}, + {"preload_keys", STMT_STATUS(SQLCOM_PRELOAD_KEYS)}, + {"prepare_sql", STMT_STATUS(SQLCOM_PREPARE)}, + {"purge", STMT_STATUS(SQLCOM_PURGE)}, + {"purge_before_date", STMT_STATUS(SQLCOM_PURGE_BEFORE)}, + {"release_savepoint", STMT_STATUS(SQLCOM_RELEASE_SAVEPOINT)}, + {"rename_table", STMT_STATUS(SQLCOM_RENAME_TABLE)}, + {"rename_user", STMT_STATUS(SQLCOM_RENAME_USER)}, + {"repair", STMT_STATUS(SQLCOM_REPAIR)}, + {"replace", STMT_STATUS(SQLCOM_REPLACE)}, + {"replace_select", STMT_STATUS(SQLCOM_REPLACE_SELECT)}, + {"reset", STMT_STATUS(SQLCOM_RESET)}, + {"resignal", STMT_STATUS(SQLCOM_RESIGNAL)}, + {"revoke", STMT_STATUS(SQLCOM_REVOKE)}, + {"revoke_all", STMT_STATUS(SQLCOM_REVOKE_ALL)}, + {"revoke_role", STMT_STATUS(SQLCOM_REVOKE_ROLE)}, + {"rollback", STMT_STATUS(SQLCOM_ROLLBACK)}, + {"rollback_to_savepoint",STMT_STATUS(SQLCOM_ROLLBACK_TO_SAVEPOINT)}, + {"savepoint", STMT_STATUS(SQLCOM_SAVEPOINT)}, + {"select", STMT_STATUS(SQLCOM_SELECT)}, + {"set_option", STMT_STATUS(SQLCOM_SET_OPTION)}, + {"show_authors", STMT_STATUS(SQLCOM_SHOW_AUTHORS)}, + {"show_binlog_events", STMT_STATUS(SQLCOM_SHOW_BINLOG_EVENTS)}, + {"show_binlogs", STMT_STATUS(SQLCOM_SHOW_BINLOGS)}, + {"show_charsets", STMT_STATUS(SQLCOM_SHOW_CHARSETS)}, + {"show_collations", STMT_STATUS(SQLCOM_SHOW_COLLATIONS)}, + {"show_contributors", STMT_STATUS(SQLCOM_SHOW_CONTRIBUTORS)}, + {"show_create_db", STMT_STATUS(SQLCOM_SHOW_CREATE_DB)}, + {"show_create_event", STMT_STATUS(SQLCOM_SHOW_CREATE_EVENT)}, + {"show_create_func", STMT_STATUS(SQLCOM_SHOW_CREATE_FUNC)}, + {"show_create_proc", STMT_STATUS(SQLCOM_SHOW_CREATE_PROC)}, + {"show_create_table", STMT_STATUS(SQLCOM_SHOW_CREATE)}, + {"show_create_trigger", STMT_STATUS(SQLCOM_SHOW_CREATE_TRIGGER)}, + {"show_databases", STMT_STATUS(SQLCOM_SHOW_DATABASES)}, + {"show_engine_logs", STMT_STATUS(SQLCOM_SHOW_ENGINE_LOGS)}, + {"show_engine_mutex", STMT_STATUS(SQLCOM_SHOW_ENGINE_MUTEX)}, + {"show_engine_status", STMT_STATUS(SQLCOM_SHOW_ENGINE_STATUS)}, + {"show_errors", STMT_STATUS(SQLCOM_SHOW_ERRORS)}, + {"show_events", STMT_STATUS(SQLCOM_SHOW_EVENTS)}, + {"show_explain", STMT_STATUS(SQLCOM_SHOW_EXPLAIN)}, + {"show_fields", STMT_STATUS(SQLCOM_SHOW_FIELDS)}, #ifndef DBUG_OFF - {"show_function_code", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_FUNC_CODE]), SHOW_LONG_STATUS}, -#endif - {"show_function_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_STATUS_FUNC]), SHOW_LONG_STATUS}, - {"show_grants", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_GRANTS]), SHOW_LONG_STATUS}, - {"show_index_statistics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_INDEX_STATS]), SHOW_LONG_STATUS}, - {"show_keys", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_KEYS]), SHOW_LONG_STATUS}, - {"show_master_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_MASTER_STAT]), SHOW_LONG_STATUS}, - {"show_open_tables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_OPEN_TABLES]), SHOW_LONG_STATUS}, - {"show_plugins", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PLUGINS]), SHOW_LONG_STATUS}, - {"show_privileges", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PRIVILEGES]), SHOW_LONG_STATUS}, + {"show_function_code", STMT_STATUS(SQLCOM_SHOW_FUNC_CODE)}, +#endif + {"show_function_status", STMT_STATUS(SQLCOM_SHOW_STATUS_FUNC)}, + {"show_generic", STMT_STATUS(SQLCOM_SHOW_GENERIC)}, + {"show_grants", STMT_STATUS(SQLCOM_SHOW_GRANTS)}, + {"show_keys", STMT_STATUS(SQLCOM_SHOW_KEYS)}, + {"show_master_status", STMT_STATUS(SQLCOM_SHOW_MASTER_STAT)}, + {"show_open_tables", STMT_STATUS(SQLCOM_SHOW_OPEN_TABLES)}, + {"show_plugins", STMT_STATUS(SQLCOM_SHOW_PLUGINS)}, + {"show_privileges", STMT_STATUS(SQLCOM_SHOW_PRIVILEGES)}, #ifndef DBUG_OFF - {"show_procedure_code", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PROC_CODE]), SHOW_LONG_STATUS}, -#endif - {"show_procedure_status",(char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_STATUS_PROC]), SHOW_LONG_STATUS}, - {"show_processlist", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PROCESSLIST]), SHOW_LONG_STATUS}, - {"show_profile", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PROFILE]), SHOW_LONG_STATUS}, - {"show_profiles", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_PROFILES]), SHOW_LONG_STATUS}, - {"show_relaylog_events", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_RELAYLOG_EVENTS]), SHOW_LONG_STATUS}, - {"show_slave_hosts", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_SLAVE_HOSTS]), SHOW_LONG_STATUS}, - {"show_slave_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_SLAVE_STAT]), SHOW_LONG_STATUS}, - {"show_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_STATUS]), SHOW_LONG_STATUS}, - {"show_storage_engines", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_STORAGE_ENGINES]), SHOW_LONG_STATUS}, - {"show_table_statistics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_TABLE_STATS]), SHOW_LONG_STATUS}, - {"show_table_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_TABLE_STATUS]), SHOW_LONG_STATUS}, - {"show_tables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_TABLES]), SHOW_LONG_STATUS}, - {"show_triggers", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_TRIGGERS]), SHOW_LONG_STATUS}, - {"show_user_statistics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_USER_STATS]), SHOW_LONG_STATUS}, - {"show_variables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_VARIABLES]), SHOW_LONG_STATUS}, - {"show_warnings", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_WARNS]), SHOW_LONG_STATUS}, - {"signal", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SIGNAL]), SHOW_LONG_STATUS}, - {"slave_start", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SLAVE_START]), SHOW_LONG_STATUS}, - {"slave_stop", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SLAVE_STOP]), SHOW_LONG_STATUS}, - {"stmt_close", (char*) offsetof(STATUS_VAR, com_stmt_close), SHOW_LONG_STATUS}, - {"stmt_execute", (char*) offsetof(STATUS_VAR, com_stmt_execute), SHOW_LONG_STATUS}, - {"stmt_fetch", (char*) offsetof(STATUS_VAR, com_stmt_fetch), SHOW_LONG_STATUS}, - {"stmt_prepare", (char*) offsetof(STATUS_VAR, com_stmt_prepare), SHOW_LONG_STATUS}, - {"stmt_reprepare", (char*) offsetof(STATUS_VAR, com_stmt_reprepare), SHOW_LONG_STATUS}, - {"stmt_reset", (char*) offsetof(STATUS_VAR, com_stmt_reset), SHOW_LONG_STATUS}, - {"stmt_send_long_data", (char*) offsetof(STATUS_VAR, com_stmt_send_long_data), SHOW_LONG_STATUS}, - {"truncate", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_TRUNCATE]), SHOW_LONG_STATUS}, - {"uninstall_plugin", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_UNINSTALL_PLUGIN]), SHOW_LONG_STATUS}, - {"unlock_tables", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_UNLOCK_TABLES]), SHOW_LONG_STATUS}, - {"update", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_UPDATE]), SHOW_LONG_STATUS}, - {"update_multi", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_UPDATE_MULTI]), SHOW_LONG_STATUS}, - {"xa_commit", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_COMMIT]),SHOW_LONG_STATUS}, - {"xa_end", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_END]),SHOW_LONG_STATUS}, - {"xa_prepare", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_PREPARE]),SHOW_LONG_STATUS}, - {"xa_recover", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_RECOVER]),SHOW_LONG_STATUS}, - {"xa_rollback", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_ROLLBACK]),SHOW_LONG_STATUS}, - {"xa_start", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_XA_START]),SHOW_LONG_STATUS}, + {"show_procedure_code", STMT_STATUS(SQLCOM_SHOW_PROC_CODE)}, +#endif + {"show_procedure_status",STMT_STATUS(SQLCOM_SHOW_STATUS_PROC)}, + {"show_processlist", STMT_STATUS(SQLCOM_SHOW_PROCESSLIST)}, + {"show_profile", STMT_STATUS(SQLCOM_SHOW_PROFILE)}, + {"show_profiles", STMT_STATUS(SQLCOM_SHOW_PROFILES)}, + {"show_relaylog_events", STMT_STATUS(SQLCOM_SHOW_RELAYLOG_EVENTS)}, + {"show_slave_hosts", STMT_STATUS(SQLCOM_SHOW_SLAVE_HOSTS)}, + {"show_slave_status", STMT_STATUS(SQLCOM_SHOW_SLAVE_STAT)}, + {"show_status", STMT_STATUS(SQLCOM_SHOW_STATUS)}, + {"show_storage_engines", STMT_STATUS(SQLCOM_SHOW_STORAGE_ENGINES)}, + {"show_table_status", STMT_STATUS(SQLCOM_SHOW_TABLE_STATUS)}, + {"show_tables", STMT_STATUS(SQLCOM_SHOW_TABLES)}, + {"show_triggers", STMT_STATUS(SQLCOM_SHOW_TRIGGERS)}, + {"show_variables", STMT_STATUS(SQLCOM_SHOW_VARIABLES)}, + {"show_warnings", STMT_STATUS(SQLCOM_SHOW_WARNS)}, + {"shutdown", STMT_STATUS(SQLCOM_SHUTDOWN)}, + {"signal", STMT_STATUS(SQLCOM_SIGNAL)}, + {"start_all_slaves", STMT_STATUS(SQLCOM_SLAVE_ALL_START)}, + {"start_slave", STMT_STATUS(SQLCOM_SLAVE_START)}, + {"stmt_close", COM_STATUS(com_stmt_close)}, + {"stmt_execute", COM_STATUS(com_stmt_execute)}, + {"stmt_fetch", COM_STATUS(com_stmt_fetch)}, + {"stmt_prepare", COM_STATUS(com_stmt_prepare)}, + {"stmt_reprepare", COM_STATUS(com_stmt_reprepare)}, + {"stmt_reset", COM_STATUS(com_stmt_reset)}, + {"stmt_send_long_data", COM_STATUS(com_stmt_send_long_data)}, + {"stop_all_slaves", STMT_STATUS(SQLCOM_SLAVE_ALL_STOP)}, + {"stop_slave", STMT_STATUS(SQLCOM_SLAVE_STOP)}, + {"truncate", STMT_STATUS(SQLCOM_TRUNCATE)}, + {"uninstall_plugin", STMT_STATUS(SQLCOM_UNINSTALL_PLUGIN)}, + {"unlock_tables", STMT_STATUS(SQLCOM_UNLOCK_TABLES)}, + {"update", STMT_STATUS(SQLCOM_UPDATE)}, + {"update_multi", STMT_STATUS(SQLCOM_UPDATE_MULTI)}, + {"xa_commit", STMT_STATUS(SQLCOM_XA_COMMIT)}, + {"xa_end", STMT_STATUS(SQLCOM_XA_END)}, + {"xa_prepare", STMT_STATUS(SQLCOM_XA_PREPARE)}, + {"xa_recover", STMT_STATUS(SQLCOM_XA_RECOVER)}, + {"xa_rollback", STMT_STATUS(SQLCOM_XA_ROLLBACK)}, + {"xa_start", STMT_STATUS(SQLCOM_XA_START)}, {NullS, NullS, SHOW_LONG} }; + +#ifdef HAVE_PSI_STATEMENT_INTERFACE +PSI_statement_info sql_statement_info[(uint) SQLCOM_END + 1]; +PSI_statement_info com_statement_info[(uint) COM_END + 1]; + +/** + Initialize the command names array. + Since we do not want to maintain a separate array, + this is populated from data mined in com_status_vars, + which already has one name for each command. +*/ +void init_sql_statement_info() +{ + size_t first_com= offsetof(STATUS_VAR, com_stat[0]); + size_t last_com= offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_END]); + int record_size= offsetof(STATUS_VAR, com_stat[1]) + - offsetof(STATUS_VAR, com_stat[0]); + size_t ptr; + uint i; + uint com_index; + + static const char* dummy= ""; + for (i= 0; i < ((uint) SQLCOM_END + 1); i++) + { + sql_statement_info[i].m_name= dummy; + sql_statement_info[i].m_flags= 0; + } + + SHOW_VAR *var= &com_status_vars[0]; + while (var->name != NULL) + { + ptr= (size_t)(var->value); + if ((first_com <= ptr) && (ptr < last_com)) + { + com_index= ((int)(ptr - first_com))/record_size; + DBUG_ASSERT(com_index < (uint) SQLCOM_END); + sql_statement_info[com_index].m_name= var->name; + } + var++; + } + + DBUG_ASSERT(strcmp(sql_statement_info[(uint) SQLCOM_SELECT].m_name, "select") == 0); + DBUG_ASSERT(strcmp(sql_statement_info[(uint) SQLCOM_SIGNAL].m_name, "signal") == 0); + + sql_statement_info[(uint) SQLCOM_END].m_name= "error"; +} + +void init_com_statement_info() +{ + uint index; + + for (index= 0; index < (uint) COM_END + 1; index++) + { + com_statement_info[index].m_name= command_name[index].str; + com_statement_info[index].m_flags= 0; + } + + /* "statement/abstract/query" can mutate into "statement/sql/..." */ + com_statement_info[(uint) COM_QUERY].m_flags= PSI_FLAG_MUTABLE; +} +#endif + + +#ifdef SAFEMALLOC +/* + Return the id for the current THD, to allow safemalloc to associate + the memory with the right id. +*/ + +extern "C" my_thread_id mariadb_dbug_id() +{ + THD *thd; + if ((thd= current_thd)) + { + return thd->thread_id; + } + return my_thread_dbug_id(); +} +#endif /* SAFEMALLOC */ + +/* Thread Mem Usage By P.Linux */ +extern "C" { +static void my_malloc_size_cb_func(long long size, my_bool is_thread_specific) +{ + THD *thd= current_thd; + + /* + When thread specific is set, both mysqld_server_initialized and thd + must be set, and we check that with DBUG_ASSERT. + + However, do not crash, if current_thd is NULL, in release version. + */ + DBUG_ASSERT(!is_thread_specific || (mysqld_server_initialized && thd)); + + if (is_thread_specific && likely(thd)) /* If thread specific memory */ + { + DBUG_PRINT("info", ("thd memory_used: %lld size: %lld", + (longlong) thd->status_var.local_memory_used, + size)); + thd->status_var.local_memory_used+= size; + if (size > 0 && + thd->status_var.local_memory_used > (int64)thd->variables.max_mem_used && + !thd->killed && !thd->get_stmt_da()->is_set()) + { + /* Ensure we don't get called here again */ + char buf[50], *buf2; + thd->set_killed(KILL_QUERY); + my_snprintf(buf, sizeof(buf), "--max-thread-mem-used=%llu", + thd->variables.max_mem_used); + if ((buf2= (char*) thd->alloc(256))) + { + my_snprintf(buf2, 256, ER_THD(thd, ER_OPTION_PREVENTS_STATEMENT), buf); + thd->set_killed(KILL_QUERY, ER_OPTION_PREVENTS_STATEMENT, buf2); + } + } + DBUG_ASSERT((longlong) thd->status_var.local_memory_used >= 0); + } + else if (likely(thd)) + { + DBUG_PRINT("info", ("global thd memory_used: %lld size: %lld", + (longlong) thd->status_var.global_memory_used, size)); + thd->status_var.global_memory_used+= size; + } + else + { + update_global_memory_status(size); + } +} +} + +/** + Create a replication file name or base for file names. + + @param[in] opt Value of option, or NULL + @param[in] def Default value if option value is not set. + @param[in] ext Extension to use for the path + + @returns Pointer to string containing the full file path, or NULL if + it was not possible to create the path. + */ +static inline const char * +rpl_make_log_name(const char *opt, + const char *def, + const char *ext) +{ + DBUG_ENTER("rpl_make_log_name"); + DBUG_PRINT("enter", ("opt: %s, def: %s, ext: %s", opt, def, ext)); + char buff[FN_REFLEN]; + const char *base= opt ? opt : def; + unsigned int options= + MY_REPLACE_EXT | MY_UNPACK_FILENAME | MY_SAFE_PATH; + + /* mysql_real_data_home_ptr may be null if no value of datadir has been + specified through command-line or througha cnf file. If that is the + case we make mysql_real_data_home_ptr point to mysql_real_data_home + which, in that case holds the default path for data-dir. + */ + if(mysql_real_data_home_ptr == NULL) + mysql_real_data_home_ptr= mysql_real_data_home; + + if (fn_format(buff, base, mysql_real_data_home_ptr, ext, options)) + DBUG_RETURN(my_strdup(buff, MYF(MY_WME))); + else + DBUG_RETURN(NULL); +} + +/* We have to setup my_malloc_size_cb_func early to catch all mallocs */ + +static int init_early_variables() +{ + if (pthread_key_create(&THR_THD, NULL)) + { + fprintf(stderr, "Fatal error: Can't create thread-keys\n"); + return 1; + } + set_current_thd(0); + set_malloc_size_cb(my_malloc_size_cb_func); + global_status_var.global_memory_used= 0; + return 0; +} + + static int init_common_variables() { umask(((~my_umask) & 0666)); + connection_errors_select= 0; + connection_errors_accept= 0; + connection_errors_tcpwrap= 0; + connection_errors_internal= 0; + connection_errors_max_connection= 0; + connection_errors_peer_addr= 0; my_decimal_set_zero(&decimal_zero); // set decimal_zero constant; + if (pthread_key_create(&THR_MALLOC,NULL)) + { + sql_print_error("Can't create thread-keys"); + return 1; + } + + init_libstrings(); tzset(); // Set tzname sf_leaking_memory= 0; // no memory leaks from now on +#ifdef SAFEMALLOC + sf_malloc_dbug_id= mariadb_dbug_id; +#endif max_system_variables.pseudo_thread_id= (ulong)~0; server_start_time= flush_status_time= my_time(0); my_disable_copystat_in_redel= 1; - rpl_filter= new Rpl_filter; + global_rpl_filter= new Rpl_filter; binlog_filter= new Rpl_filter; - if (!rpl_filter || !binlog_filter) + if (!global_rpl_filter || !binlog_filter) { sql_perror("Could not allocate replication and binlog filters"); return 1; @@ -3439,8 +4083,9 @@ static int init_common_variables() #ifdef HAVE_PSI_INTERFACE /* Complete the mysql_bin_log initialization. - Instrumentation keys are known only after the performance schema initialization, - and can not be set in the MYSQL_BIN_LOG constructor (called before main()). + Instrumentation keys are known only after the performance schema + initialization, and can not be set in the MYSQL_BIN_LOG + constructor (called before main()). */ mysql_bin_log.set_psi_keys(key_BINLOG_LOCK_index, key_BINLOG_update_cond, @@ -3464,6 +4109,8 @@ static int init_common_variables() return 1; } + opt_log_basename= const_cast<char *>("mysql"); + if (gethostname(glob_hostname,sizeof(glob_hostname)) < 0) { /* @@ -3473,16 +4120,14 @@ static int init_common_variables() strmake(glob_hostname, STRING_WITH_LEN("localhost")); sql_print_warning("gethostname failed, using '%s' as hostname", glob_hostname); - opt_log_basename= const_cast<char *>("mysql"); } - else + else if (is_filename_allowed(glob_hostname, strlen(glob_hostname), FALSE)) opt_log_basename= glob_hostname; - if (!*pidfile_name) - { - strmake(pidfile_name, opt_log_basename, sizeof(pidfile_name)-5); - strmov(fn_ext(pidfile_name),".pid"); // Add proper extension - } + strmake(pidfile_name, opt_log_basename, sizeof(pidfile_name)-5); + strmov(fn_ext(pidfile_name),".pid"); // Add proper extension + SYSVAR_AUTOSIZE(pidfile_name_ptr, pidfile_name); + set_sys_var_value_origin(&opt_tc_log_size, sys_var::AUTO); /* The default-storage-engine entry in my_long_options should have a @@ -3501,6 +4146,7 @@ static int init_common_variables() #else default_storage_engine= const_cast<char *>("MyISAM"); #endif + default_tmp_storage_engine= NULL; /* Add server status variables to the dynamic list of @@ -3516,32 +4162,34 @@ static int init_common_variables() We have few debug-only commands in com_status_vars, only visible in debug builds. for simplicity we enable the assert only in debug builds - There are 8 Com_ variables which don't have corresponding SQLCOM_ values: + There are 10 Com_ variables which don't have corresponding SQLCOM_ values: (TODO strictly speaking they shouldn't be here, should not have Com_ prefix that is. Perhaps Stmt_ ? Comstmt_ ? Prepstmt_ ?) - Com_admin_commands => com_other - Com_stmt_close => com_stmt_close - Com_stmt_execute => com_stmt_execute - Com_stmt_fetch => com_stmt_fetch - Com_stmt_prepare => com_stmt_prepare - Com_stmt_reprepare => com_stmt_reprepare - Com_stmt_reset => com_stmt_reset - Com_stmt_send_long_data => com_stmt_send_long_data + Com_admin_commands => com_other + Com_create_temporary_table => com_create_tmp_table + Com_drop_temporary_table => com_drop_tmp_table + Com_stmt_close => com_stmt_close + Com_stmt_execute => com_stmt_execute + Com_stmt_fetch => com_stmt_fetch + Com_stmt_prepare => com_stmt_prepare + Com_stmt_reprepare => com_stmt_reprepare + Com_stmt_reset => com_stmt_reset + Com_stmt_send_long_data => com_stmt_send_long_data With this correction the number of Com_ variables (number of elements in the array, excluding the last element - terminator) must match the number of SQLCOM_ constants. */ compile_time_assert(sizeof(com_status_vars)/sizeof(com_status_vars[0]) - 1 == - SQLCOM_END + 8); + SQLCOM_END + 10); #endif if (get_options(&remaining_argc, &remaining_argv)) return 1; set_server_version(); - if (!opt_help) + if (!opt_abort) sql_print_information("%s (mysqld %s) starting as process %lu ...", my_progname, server_version, (ulong) getpid()); @@ -3555,20 +4203,18 @@ static int init_common_variables() #ifdef HAVE_LARGE_PAGES /* Initialize large page size */ - if (opt_large_pages && (opt_large_page_size= my_get_large_page_size())) + if (opt_large_pages) { + SYSVAR_AUTOSIZE(opt_large_page_size, my_get_large_page_size()); + if (opt_large_page_size) + { DBUG_PRINT("info", ("Large page set, large_page_size = %d", opt_large_page_size)); my_use_large_pages= 1; my_large_page_size= opt_large_page_size; - } - else - { - opt_large_pages= 0; - /* - Either not configured to use large pages or Linux haven't - been compiled with large page support - */ + } + else + SYSVAR_AUTOSIZE(opt_large_pages, 0); } #endif /* HAVE_LARGE_PAGES */ #ifdef HAVE_SOLARIS_LARGE_PAGES @@ -3622,13 +4268,53 @@ static int init_common_variables() } #endif /* HAVE_SOLARIS_LARGE_PAGES */ + +#if defined(HAVE_POOL_OF_THREADS) && !defined(_WIN32) + if (IS_SYSVAR_AUTOSIZE(&threadpool_size)) + SYSVAR_AUTOSIZE(threadpool_size, my_getncpus()); +#endif + + /* Fix host_cache_size. */ + if (IS_SYSVAR_AUTOSIZE(&host_cache_size)) + { + if (max_connections <= 628 - 128) + SYSVAR_AUTOSIZE(host_cache_size, 128 + max_connections); + else if (max_connections <= ((ulong)(2000 - 628)) * 20 + 500) + SYSVAR_AUTOSIZE(host_cache_size, 628 + ((max_connections - 500) / 20)); + else + SYSVAR_AUTOSIZE(host_cache_size, 2000); + } + + /* Fix back_log (back_log == 0 added for MySQL compatibility) */ + if (back_log == 0 || IS_SYSVAR_AUTOSIZE(&back_log)) + { + if ((900 - 50) * 5 >= max_connections) + SYSVAR_AUTOSIZE(back_log, (50 + max_connections / 5)); + else + SYSVAR_AUTOSIZE(back_log, 900); + } + /* connections and databases needs lots of files */ { - uint files, wanted_files, max_open_files; + uint files, wanted_files, max_open_files, min_tc_size, extra_files, + min_connections; + ulong org_max_connections, org_tc_size; + /* Number of files reserved for temporary files */ + extra_files= 30; + min_connections= 10; /* MyISAM requires two file handles per table. */ - wanted_files= (10 + max_connections + extra_max_connections + - table_cache_size*2); + wanted_files= (extra_files + max_connections + extra_max_connections + + tc_size * 2); +#if defined(HAVE_POOL_OF_THREADS) && !defined(__WIN__) + // add epoll or kevent fd for each threadpool group, in case pool of threads is used + wanted_files+= (thread_handling > SCHEDULER_NO_THREADS) ? 0 : threadpool_size; +#endif + + min_tc_size= MY_MIN(tc_size, TABLE_OPEN_CACHE_MIN); + org_max_connections= max_connections; + org_tc_size= tc_size; + /* We are trying to allocate no less than max_connections*5 file handles (i.e. we are trying to set the limit so that they will @@ -3639,42 +4325,53 @@ static int init_common_variables() can't get max_connections*5 but still got no less than was requested (value of wanted_files). */ - max_open_files= max(max(wanted_files, - (max_connections + extra_max_connections)*5), - open_files_limit); + max_open_files= MY_MAX(MY_MAX(wanted_files, + (max_connections + extra_max_connections)*5), + open_files_limit); files= my_set_max_open_files(max_open_files); + SYSVAR_AUTOSIZE_IF_CHANGED(open_files_limit, files, ulong); - if (files < wanted_files) - { - if (!open_files_limit) - { - /* - If we have requested too much file handles than we bring - max_connections in supported bounds. - */ - max_connections= (ulong) min(files-10-TABLE_OPEN_CACHE_MIN*2, - max_connections); - /* - Decrease table_cache_size according to max_connections, but - not below TABLE_OPEN_CACHE_MIN. Outer min() ensures that we - never increase table_cache_size automatically (that could - happen if max_connections is decreased above). - */ - table_cache_size= (ulong) min(max((files-10-max_connections)/2, - TABLE_OPEN_CACHE_MIN), - table_cache_size); - DBUG_PRINT("warning", - ("Changed limits: max_open_files: %u max_connections: %ld table_cache: %ld", - files, max_connections, table_cache_size)); - if (global_system_variables.log_warnings) - sql_print_warning("Changed limits: max_open_files: %u max_connections: %ld table_cache: %ld", - files, max_connections, table_cache_size); - } - else if (global_system_variables.log_warnings) - sql_print_warning("Could not increase number of max_open_files to more than %u (request: %u)", files, wanted_files); - } - open_files_limit= files; + if (files < wanted_files && global_system_variables.log_warnings) + sql_print_warning("Could not increase number of max_open_files to more than %u (request: %u)", files, wanted_files); + + /* + If we have requested too much file handles than we bring + max_connections in supported bounds. Still leave at least + 'min_connections' connections + */ + SYSVAR_AUTOSIZE_IF_CHANGED(max_connections, + (ulong) MY_MAX(MY_MIN(files- extra_files- + min_tc_size*2, + max_connections), + min_connections), + ulong); + + /* + Decrease tc_size according to max_connections, but + not below min_tc_size. Outer MY_MIN() ensures that we + never increase tc_size automatically (that could + happen if max_connections is decreased above). + */ + SYSVAR_AUTOSIZE_IF_CHANGED(tc_size, + (ulong) MY_MIN(MY_MAX((files - extra_files - + max_connections) / 2, + min_tc_size), + tc_size), ulong); + DBUG_PRINT("warning", + ("Current limits: max_open_files: %u max_connections: %ld table_cache: %ld", + files, max_connections, tc_size)); + if (global_system_variables.log_warnings > 1 && + (max_connections < org_max_connections || + tc_size < org_tc_size)) + sql_print_warning("Changed limits: max_open_files: %u max_connections: %lu (was %lu) table_cache: %lu (was %lu)", + files, max_connections, org_max_connections, + tc_size, org_tc_size); } + /* + Max_connections and tc_cache are now set. + Now we can fix other variables depending on this variable. + */ + unireg_init(opt_specialflag); /* Set up extern variabels */ if (!(my_default_lc_messages= my_locale_by_name(lc_messages))) @@ -3682,21 +4379,18 @@ static int init_common_variables() sql_print_error("Unknown locale: '%s'", lc_messages); return 1; } - global_system_variables.lc_messages= my_default_lc_messages; + if (init_errmessage()) /* Read error messages from file */ return 1; + global_system_variables.lc_messages= my_default_lc_messages; + global_system_variables.errmsgs= my_default_lc_messages->errmsgs->errmsgs; init_client_errs(); mysql_library_init(unused,unused,unused); /* for replication */ lex_init(); if (item_create_init()) return 1; item_init(); -#ifndef EMBEDDED_LIBRARY - my_regex_init(&my_charset_latin1, check_enough_stack_size); - my_string_stack_guard= check_enough_stack_size; -#else - my_regex_init(&my_charset_latin1, NULL); -#endif + init_pcre(); /* Process a comma-separated character set list and choose the first available character set. This is mostly for @@ -3730,6 +4424,10 @@ static int init_common_variables() default_collation= get_charset_by_name(default_collation_name, MYF(0)); if (!default_collation) { +#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE + buffered_logs.print(); + buffered_logs.cleanup(); +#endif sql_print_error(ER_DEFAULT(ER_UNKNOWN_COLLATION), default_collation_name); return 1; } @@ -3743,9 +4441,8 @@ static int init_common_variables() default_charset_info= default_collation; } /* Set collactions that depends on the default collation */ - global_system_variables.collation_server= default_charset_info; - global_system_variables.collation_database= default_charset_info; - + global_system_variables.collation_server= default_charset_info; + global_system_variables.collation_database= default_charset_info; if (is_supported_parser_charset(default_charset_info)) { global_system_variables.collation_connection= default_charset_info; @@ -3754,10 +4451,10 @@ static int init_common_variables() } else { - sql_print_information("'%s' can not be used as client character set. " - "'%s' will be used as default client character set.", - default_charset_info->csname, - my_charset_latin1.csname); + sql_print_warning("'%s' can not be used as client character set. " + "'%s' will be used as default client character set.", + default_charset_info->csname, + my_charset_latin1.csname); global_system_variables.collation_connection= &my_charset_latin1; global_system_variables.character_set_results= &my_charset_latin1; global_system_variables.character_set_client= &my_charset_latin1; @@ -3784,7 +4481,8 @@ static int init_common_variables() "--log option, log tables are used. " "To enable logging to files use the --log-output option."); - if (opt_slow_log && opt_slow_logname && *opt_slow_logname && + if (global_system_variables.sql_log_slow && opt_slow_logname && + *opt_slow_logname && !(log_output_options & (LOG_FILE | LOG_NONE))) sql_print_warning("Although a path was specified for the " "--log-slow-queries option, log tables are used. " @@ -3802,7 +4500,7 @@ static int init_common_variables() #endif /* defined(ENABLED_DEBUG_SYNC) */ #if (ENABLE_TEMP_POOL) - if (use_temp_pool && bitmap_init(&temp_pool,0,1024,1)) + if (use_temp_pool && my_bitmap_init(&temp_pool,0,1024,1)) return 1; #else use_temp_pool= 0; @@ -3817,35 +4515,26 @@ static int init_common_variables() get corrupted if accesses with names of different case. */ DBUG_PRINT("info", ("lower_case_table_names: %d", lower_case_table_names)); - lower_case_file_system= test_if_case_insensitive(mysql_real_data_home); + SYSVAR_AUTOSIZE(lower_case_file_system, + test_if_case_insensitive(mysql_real_data_home)); if (!lower_case_table_names && lower_case_file_system == 1) { if (lower_case_table_names_used) { -#if MYSQL_VERSION_ID < 100100 - if (global_system_variables.log_warnings) - sql_print_warning("You have forced lower_case_table_names to 0 through " - "a command-line option, even though your file system " - "'%s' is case insensitive. This means that you can " - "corrupt your tables if you access them using names " - "with different letter case. You should consider " - "changing lower_case_table_names to 1 or 2", - mysql_real_data_home); -#else sql_print_error("The server option 'lower_case_table_names' is " "configured to use case sensitive table names but the " "data directory resides on a case-insensitive file system. " "Please use a case sensitive file system for your data " "directory or switch to a case-insensitive table name " "mode."); -#endif return 1; } else { if (global_system_variables.log_warnings) - sql_print_warning("Setting lower_case_table_names=2 because file system for %s is case insensitive", mysql_real_data_home); - lower_case_table_names= 2; + sql_print_warning("Setting lower_case_table_names=2 because file " + "system for %s is case insensitive", mysql_real_data_home); + SYSVAR_AUTOSIZE(lower_case_table_names, 2); } } else if (lower_case_table_names == 2 && @@ -3856,7 +4545,7 @@ static int init_common_variables() "the file system '%s' is case sensitive. Now setting " "lower_case_table_names to 0 to avoid future problems.", mysql_real_data_home); - lower_case_table_names= 0; + SYSVAR_AUTOSIZE(lower_case_table_names, 0); } else { @@ -3874,14 +4563,31 @@ static int init_common_variables() return 1; } +#ifdef WITH_WSREP + /* + We need to initialize auxiliary variables, that will be + further keep the original values of auto-increment options + as they set by the user. These variables used to restore + user-defined values of the auto-increment options after + setting of the wsrep_auto_increment_control to 'OFF'. + */ + global_system_variables.saved_auto_increment_increment= + global_system_variables.auto_increment_increment; + global_system_variables.saved_auto_increment_offset= + global_system_variables.auto_increment_offset; +#endif /* WITH_WSREP */ + return 0; } static int init_thread_environment() { + DBUG_ENTER("init_thread_environment"); mysql_mutex_init(key_LOCK_thread_count, &LOCK_thread_count, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_thread_cache, &LOCK_thread_cache, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_status, &LOCK_status, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_show_status, &LOCK_show_status, MY_MUTEX_INIT_SLOW); mysql_mutex_init(key_LOCK_delayed_insert, &LOCK_delayed_insert, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_delayed_status, @@ -3893,6 +4599,8 @@ static int init_thread_environment() mysql_mutex_init(key_LOCK_active_mi, &LOCK_active_mi, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_global_system_variables, &LOCK_global_system_variables, MY_MUTEX_INIT_FAST); + mysql_mutex_record_order(&LOCK_active_mi, &LOCK_global_system_variables); + mysql_mutex_record_order(&LOCK_status, &LOCK_thread_count); mysql_rwlock_init(key_rwlock_LOCK_system_variables_hash, &LOCK_system_variables_hash); mysql_mutex_init(key_LOCK_prepared_stmt_count, @@ -3912,8 +4620,14 @@ static int init_thread_environment() &LOCK_global_index_stats, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_prepare_ordered, &LOCK_prepare_ordered, MY_MUTEX_INIT_SLOW); + mysql_cond_init(key_COND_prepare_ordered, &COND_prepare_ordered, NULL); + mysql_mutex_init(key_LOCK_after_binlog_sync, &LOCK_after_binlog_sync, + MY_MUTEX_INIT_SLOW); mysql_mutex_init(key_LOCK_commit_ordered, &LOCK_commit_ordered, MY_MUTEX_INIT_SLOW); + mysql_mutex_init(key_LOCK_slave_background, &LOCK_slave_background, + MY_MUTEX_INIT_SLOW); + mysql_cond_init(key_COND_slave_background, &COND_slave_background, NULL); #ifdef HAVE_OPENSSL mysql_mutex_init(key_LOCK_des_key_file, @@ -3937,7 +4651,6 @@ static int init_thread_environment() mysql_cond_init(key_COND_flush_thread_cache, &COND_flush_thread_cache, NULL); #ifdef HAVE_REPLICATION mysql_mutex_init(key_LOCK_rpl_status, &LOCK_rpl_status, MY_MUTEX_INIT_FAST); - mysql_cond_init(key_COND_rpl_status, &COND_rpl_status, NULL); #endif mysql_mutex_init(key_LOCK_server_started, &LOCK_server_started, MY_MUTEX_INIT_FAST); @@ -3946,19 +4659,19 @@ static int init_thread_environment() #ifdef HAVE_EVENT_SCHEDULER Events::init_mutexes(); #endif + init_show_explain_psi_keys(); /* Parameter for threads created for connections */ (void) pthread_attr_init(&connection_attrib); (void) pthread_attr_setdetachstate(&connection_attrib, PTHREAD_CREATE_DETACHED); pthread_attr_setscope(&connection_attrib, PTHREAD_SCOPE_SYSTEM); - if (pthread_key_create(&THR_THD,NULL) || - pthread_key_create(&THR_MALLOC,NULL)) - { - sql_print_error("Can't create thread-keys"); - return 1; - } - return 0; +#ifdef HAVE_REPLICATION + rpl_init_gtid_slave_state(); + rpl_init_gtid_waiting(); +#endif + + DBUG_RETURN(0); } @@ -4035,9 +4748,9 @@ static void init_ssl() /* having ssl_acceptor_fd != 0 signals the use of SSL */ ssl_acceptor_fd= new_VioSSLAcceptorFd(opt_ssl_key, opt_ssl_cert, opt_ssl_ca, opt_ssl_capath, - opt_ssl_cipher, &error); + opt_ssl_cipher, &error, + opt_ssl_crl, opt_ssl_crlpath); DBUG_PRINT("info",("ssl_acceptor_fd: 0x%lx", (long) ssl_acceptor_fd)); - ERR_remove_state(0); if (!ssl_acceptor_fd) { sql_print_warning("Failed to setup SSL"); @@ -4045,6 +4758,14 @@ static void init_ssl() opt_use_ssl = 0; have_ssl= SHOW_OPTION_DISABLED; } + if (global_system_variables.log_warnings > 0) + { + ulong err; + while ((err= ERR_get_error())) + sql_print_warning("SSL error: %s", ERR_error_string(err, NULL)); + } + else + ERR_remove_state(0); } else { @@ -4096,6 +4817,52 @@ static void add_file_to_crash_report(char *file) } #endif +#define init_default_storage_engine(X,Y) \ + init_default_storage_engine_impl(#X, X, &global_system_variables.Y) + +static int init_default_storage_engine_impl(const char *opt_name, + char *engine_name, plugin_ref *res) +{ + if (!engine_name) + { + *res= 0; + return 0; + } + + LEX_STRING name= { engine_name, strlen(engine_name) }; + plugin_ref plugin; + handlerton *hton; + if ((plugin= ha_resolve_by_name(0, &name, false))) + hton= plugin_hton(plugin); + else + { + sql_print_error("Unknown/unsupported storage engine: %s", engine_name); + return 1; + } + if (!ha_storage_engine_is_enabled(hton)) + { + if (!opt_bootstrap) + { + sql_print_error("%s (%s) is not available", opt_name, engine_name); + return 1; + } + DBUG_ASSERT(*res); + } + else + { + /* + Need to unlock as global_system_variables.table_plugin + was acquired during plugin_init() + */ + mysql_mutex_lock(&LOCK_global_system_variables); + if (*res) + plugin_unlock(0, *res); + *res= plugin; + mysql_mutex_unlock(&LOCK_global_system_variables); + } + return 0; +} + static int init_server_components() { DBUG_ENTER("init_server_components"); @@ -4104,15 +4871,33 @@ static int init_server_components() all things are initialized so that unireg_abort() doesn't fail */ mdl_init(); - if (table_def_init() | hostname_cache_init()) + tdc_init(); + if (hostname_cache_init()) unireg_abort(1); query_cache_set_min_res_unit(query_cache_min_res_unit); + query_cache_result_size_limit(query_cache_limit); + /* if we set size of QC non zero in config then probably we want it ON */ + if (query_cache_size != 0 && + global_system_variables.query_cache_type == 0 && + !IS_SYSVAR_AUTOSIZE(&query_cache_size)) + { + global_system_variables.query_cache_type= 1; + } query_cache_init(); query_cache_resize(query_cache_size); my_rnd_init(&sql_rand,(ulong) server_start_time,(ulong) server_start_time/2); setup_fpu(); init_thr_lock(); + +#ifndef EMBEDDED_LIBRARY + if (init_thr_timer(thread_scheduler->max_threads + extra_max_connections)) + { + fprintf(stderr, "Can't initialize timers\n"); + unireg_abort(1); + } +#endif + my_uuid_init((ulong) (my_rnd(&sql_rand))*12345,12345); #ifdef HAVE_REPLICATION init_slave_list(); @@ -4129,16 +4914,17 @@ static int init_server_components() if (opt_error_log && !opt_abort) { if (!log_error_file_ptr[0]) + { fn_format(log_error_file, pidfile_name, mysql_data_home, ".err", MY_REPLACE_EXT); /* replace '.<domain>' by '.err', bug#4997 */ + SYSVAR_AUTOSIZE(log_error_file_ptr, log_error_file); + } else + { fn_format(log_error_file, log_error_file_ptr, mysql_data_home, ".err", MY_UNPACK_FILENAME | MY_SAFE_PATH); - /* - _ptr may have been set to my_disabled_option or "" if no argument was - passed, but we need to show the real name in SHOW VARIABLES: - */ - log_error_file_ptr= log_error_file; + log_error_file_ptr= log_error_file; + } if (!log_error_file[0]) opt_error_log= 0; // Too long file name else @@ -4162,7 +4948,7 @@ static int init_server_components() /* set up the hook before initializing plugins which may use it */ error_handler_hook= my_message_sql; - proc_info_hook= set_thd_proc_info; + proc_info_hook= set_thd_stage_info; #ifdef WITH_PERFSCHEMA_STORAGE_ENGINE /* @@ -4176,11 +4962,16 @@ static int init_server_components() buffered_logs.cleanup(); #endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */ - if (xid_cache_init()) - { - sql_print_error("Out of memory"); - unireg_abort(1); - } +#ifndef EMBEDDED_LIBRARY + /* + Now that the logger is available, redirect character set + errors directly to the logger + (instead of the buffered_logs used at the server startup time). + */ + my_charset_error_reporter= charset_error_reporter; +#endif + + xid_cache_init(); /* initialize delegates for extension observers, errors have already @@ -4190,7 +4981,7 @@ static int init_server_components() unireg_abort(1); /* need to configure logging before initializing storage engines */ - if (!opt_bin_log_used) + if (!opt_bin_log_used && !WSREP_ON) { if (opt_log_slave_updates) sql_print_warning("You need to use --log-bin to make " @@ -4209,20 +5000,19 @@ static int init_server_components() { if (opt_bin_log) { - sql_print_error("using --replicate-same-server-id in conjunction with \ ---log-slave-updates is impossible, it would lead to infinite loops in this \ -server."); + sql_print_error("using --replicate-same-server-id in conjunction with " + "--log-slave-updates is impossible, it would lead to " + "infinite loops in this server."); unireg_abort(1); } else - sql_print_warning("using --replicate-same-server-id in conjunction with \ ---log-slave-updates would lead to infinite loops in this server. However this \ -will be ignored as the --log-bin option is not defined."); + sql_print_warning("using --replicate-same-server-id in conjunction with " + "--log-slave-updates would lead to infinite loops in " + "this server. However this will be ignored as the " + "--log-bin option is not defined."); } #endif - DBUG_ASSERT(!opt_bin_log || opt_bin_logname); - if (opt_bin_log) { /* Reports an error and aborts, if the --log-bin's path @@ -4230,8 +5020,8 @@ will be ignored as the --log-bin option is not defined."); if (opt_bin_logname[0] && opt_bin_logname[strlen(opt_bin_logname) - 1] == FN_LIBCHAR) { - sql_print_error("Path '%s' is a directory name, please specify \ -a file name for --log-bin option", opt_bin_logname); + sql_print_error("Path '%s' is a directory name, please specify " + "a file name for --log-bin option", opt_bin_logname); unireg_abort(1); } @@ -4241,8 +5031,9 @@ a file name for --log-bin option", opt_bin_logname); opt_binlog_index_name[strlen(opt_binlog_index_name) - 1] == FN_LIBCHAR) { - sql_print_error("Path '%s' is a directory name, please specify \ -a file name for --log-bin-index option", opt_binlog_index_name); + sql_print_error("Path '%s' is a directory name, please specify " + "a file name for --log-bin-index option", + opt_binlog_index_name); unireg_abort(1); } @@ -4267,15 +5058,114 @@ a file name for --log-bin-index option", opt_binlog_index_name); opt_log_basename, ln); } if (ln == buf) - { opt_bin_logname= my_once_strdup(buf, MYF(MY_WME)); + } + + /* + Since some wsrep threads (THDs) are create before plugins are + initialized, LOCK_plugin mutex needs to be initialized here. + */ + plugin_mutex_init(); + + /* + Wsrep initialization must happen at this point, because: + - opt_bin_logname must be known when starting replication + since SST may need it + - SST may modify binlog index file, so it must be opened + after SST has happened + + We also (unconditionally) initialize wsrep LOCKs and CONDs. + It is because they are used while accessing wsrep system + variables even when a wsrep provider is not loaded. + */ + + /* It's now safe to use thread specific memory */ + mysqld_server_initialized= 1; + + wsrep_thr_init(); + + if (WSREP_ON && !wsrep_recovery && !opt_abort) /* WSREP BEFORE SE */ + { + if (opt_bootstrap) // bootsrap option given - disable wsrep functionality + { + wsrep_provider_init(WSREP_NONE); + if (wsrep_init()) + unireg_abort(1); } - if (mysql_bin_log.open_index_file(opt_binlog_index_name, ln, TRUE)) + else // full wsrep initialization + { + // add basedir/bin to PATH to resolve wsrep script names + char* const tmp_path= (char*)my_alloca(strlen(mysql_home) + + strlen("/bin") + 1); + if (tmp_path) + { + strcpy(tmp_path, mysql_home); + strcat(tmp_path, "/bin"); + wsrep_prepend_PATH(tmp_path); + } + else + { + WSREP_ERROR("Could not append %s/bin to PATH", mysql_home); + } + my_afree(tmp_path); + + if (wsrep_before_SE()) + { + set_ports(); // this is also called in network_init() later but we need + // to know mysqld_port now - lp:1071882 + wsrep_init_startup(true); + } + } + } + + if (opt_bin_log) + { + if (mysql_bin_log.open_index_file(opt_binlog_index_name, opt_bin_logname, + TRUE)) { unireg_abort(1); } } + if (opt_bin_log) + { + log_bin_basename= + rpl_make_log_name(opt_bin_logname, pidfile_name, + opt_bin_logname ? "" : "-bin"); + log_bin_index= + rpl_make_log_name(opt_binlog_index_name, log_bin_basename, ".index"); + if (log_bin_basename == NULL || log_bin_index == NULL) + { + sql_print_error("Unable to create replication path names:" + " out of memory or path names too long" + " (path name exceeds " STRINGIFY_ARG(FN_REFLEN) + " or file name exceeds " STRINGIFY_ARG(FN_LEN) ")."); + unireg_abort(1); + } + } + +#ifndef EMBEDDED_LIBRARY + DBUG_PRINT("debug", + ("opt_bin_logname: %s, opt_relay_logname: %s, pidfile_name: %s", + opt_bin_logname, opt_relay_logname, pidfile_name)); + if (opt_relay_logname) + { + relay_log_basename= + rpl_make_log_name(opt_relay_logname, pidfile_name, + opt_relay_logname ? "" : "-relay-bin"); + relay_log_index= + rpl_make_log_name(opt_relaylog_index_name, relay_log_basename, ".index"); + if (relay_log_basename == NULL || relay_log_index == NULL) + { + sql_print_error("Unable to create replication path names:" + " out of memory or path names too long" + " (path name exceeds " STRINGIFY_ARG(FN_REFLEN) + " or file name exceeds " STRINGIFY_ARG(FN_LEN) ")."); + unireg_abort(1); + } + } +#endif /* !EMBEDDED_LIBRARY */ + /* call ha_init_key_cache() on all key caches to init them */ process_key_caches(&ha_init_key_cache, 0); @@ -4297,13 +5187,6 @@ a file name for --log-bin-index option", opt_binlog_index_name); } plugins_are_initialized= TRUE; /* Don't separate from init function */ - have_csv= plugin_status(STRING_WITH_LEN("csv"), - MYSQL_STORAGE_ENGINE_PLUGIN); - have_ndbcluster= plugin_status(STRING_WITH_LEN("ndbcluster"), - MYSQL_STORAGE_ENGINE_PLUGIN); - have_partitioning= plugin_status(STRING_WITH_LEN("partition"), - MYSQL_STORAGE_ENGINE_PLUGIN); - /* we do want to exit if there are any other unknown options */ if (remaining_argc > 1) { @@ -4334,6 +5217,9 @@ a file name for --log-bin-index option", opt_binlog_index_name); } } + if (init_io_cache_encryption()) + unireg_abort(1); + if (opt_abort) unireg_abort(0); @@ -4374,60 +5260,56 @@ a file name for --log-bin-index option", opt_binlog_index_name); /* purecov: begin inspected */ sql_print_error("CSV engine is not present, falling back to the " "log files"); - log_output_options= (log_output_options & ~LOG_TABLE) | LOG_FILE; + SYSVAR_AUTOSIZE(log_output_options, + (log_output_options & ~LOG_TABLE) | LOG_FILE); /* purecov: end */ } - logger.set_handlers(LOG_FILE, opt_slow_log ? log_output_options:LOG_NONE, + logger.set_handlers(LOG_FILE, + global_system_variables.sql_log_slow ? + log_output_options:LOG_NONE, opt_log ? log_output_options:LOG_NONE); } - /* - Set the default storage engine - */ - LEX_STRING name= { default_storage_engine, strlen(default_storage_engine) }; - plugin_ref plugin; - handlerton *hton; - if ((plugin= ha_resolve_by_name(0, &name))) - hton= plugin_data(plugin, handlerton*); - else - { - sql_print_error("Unknown/unsupported storage engine: %s", - default_storage_engine); + if (init_default_storage_engine(default_storage_engine, table_plugin)) unireg_abort(1); - } - if (!ha_storage_engine_is_enabled(hton)) - { - if (!opt_bootstrap) - { - sql_print_error("Default storage engine (%s) is not available", - default_storage_engine); - unireg_abort(1); - } - DBUG_ASSERT(global_system_variables.table_plugin); - } - else - { - /* - Need to unlock as global_system_variables.table_plugin - was acquired during plugin_init() - */ - mysql_mutex_lock(&LOCK_global_system_variables); - plugin_unlock(0, global_system_variables.table_plugin); - global_system_variables.table_plugin= plugin; - mysql_mutex_unlock(&LOCK_global_system_variables); - } -#if defined(WITH_ARIA_STORAGE_ENGINE) && defined(USE_ARIA_FOR_TMP_TABLES) + + if (default_tmp_storage_engine && !*default_tmp_storage_engine) + default_tmp_storage_engine= NULL; + + if (enforced_storage_engine && !*enforced_storage_engine) + enforced_storage_engine= NULL; + + if (init_default_storage_engine(default_tmp_storage_engine, tmp_table_plugin)) + unireg_abort(1); + + if (init_default_storage_engine(enforced_storage_engine, enforced_table_plugin)) + unireg_abort(1); + +#ifdef USE_ARIA_FOR_TMP_TABLES if (!ha_storage_engine_is_enabled(maria_hton) && !opt_bootstrap) { sql_print_error("Aria engine is not enabled or did not start. The Aria engine must be enabled to continue as mysqld was configured with --with-aria-tmp-tables"); unireg_abort(1); } - internal_tmp_table_max_key_length= maria_max_key_length(); - internal_tmp_table_max_key_segments= maria_max_key_segments(); -#else - internal_tmp_table_max_key_length= myisam_max_key_length(); - internal_tmp_table_max_key_segments= myisam_max_key_segments(); +#endif + +#ifdef WITH_WSREP + /* + Now is the right time to initialize members of wsrep startup threads + that rely on plugins and other related global system variables to be + initialized. This initialization was not possible before, as plugins + (and thus some global system variables) are initialized after wsrep + startup threads are created. + Note: This only needs to be done for rsync, xtrabackup based SST methods. + */ + if (wsrep_before_SE()) + wsrep_plugins_post_init(); + + if (WSREP_ON && !opt_bin_log) + { + wsrep_emulate_bin_log= 1; + } #endif tc_log= get_tc_log_implementation(); @@ -4443,9 +5325,17 @@ a file name for --log-bin-index option", opt_binlog_index_name); unireg_abort(1); } - if (opt_bin_log && mysql_bin_log.open(opt_bin_logname, LOG_BIN, 0, - WRITE_CACHE, 0, max_binlog_size, 0, TRUE)) - unireg_abort(1); + if (opt_bin_log) + { + int error; + mysql_mutex_t *log_lock= mysql_bin_log.get_log_lock(); + mysql_mutex_lock(log_lock); + error= mysql_bin_log.open(opt_bin_logname, LOG_BIN, 0, 0, + WRITE_CACHE, max_binlog_size, 0, TRUE); + mysql_mutex_unlock(log_lock); + if (error) + unireg_abort(1); + } #ifdef HAVE_REPLICATION if (opt_bin_log && expire_logs_days) @@ -4460,25 +5350,33 @@ a file name for --log-bin-index option", opt_binlog_index_name); (void) mi_log(1); #if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT) && !defined(EMBEDDED_LIBRARY) - if (locked_in_memory && !getuid()) + if (locked_in_memory) { - if (setreuid((uid_t)-1, 0) == -1) - { // this should never happen - sql_perror("setreuid"); - unireg_abort(1); + int error; + if (user_info) + { + DBUG_ASSERT(!getuid()); + if (setreuid((uid_t) -1, 0) == -1) + { + sql_perror("setreuid"); + unireg_abort(1); + } + error= mlockall(MCL_CURRENT); + set_user(mysqld_user, user_info); } - if (mlockall(MCL_CURRENT)) + else + error= mlockall(MCL_CURRENT); + + if (error) { if (global_system_variables.log_warnings) sql_print_warning("Failed to lock memory. Errno: %d\n",errno); locked_in_memory= 0; } - if (user_info) - set_user(mysqld_user, user_info); } - else +#else + locked_in_memory= 0; #endif - locked_in_memory=0; ft_init_stopwords(); @@ -4486,6 +5384,9 @@ a file name for --log-bin-index option", opt_binlog_index_name); init_update_queries(); init_global_user_stats(); init_global_client_stats(); + if (!opt_bootstrap) + servers_init(0); + init_status_vars(); DBUG_RETURN(0); } @@ -4511,7 +5412,6 @@ static void create_shutdown_thread() #endif /* EMBEDDED_LIBRARY */ - #if (defined(_WIN32) || defined(HAVE_SMEM)) && !defined(EMBEDDED_LIBRARY) static void handle_connections_methods() { @@ -4589,41 +5489,6 @@ void decrement_handler_count() #ifndef EMBEDDED_LIBRARY -LEX_STRING sql_statement_names[(uint) SQLCOM_END + 1]; - -static void init_sql_statement_names() -{ - char *first_com= (char*) offsetof(STATUS_VAR, com_stat[0]); - char *last_com= (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_END]); - int record_size= (char*) offsetof(STATUS_VAR, com_stat[1]) - - (char*) offsetof(STATUS_VAR, com_stat[0]); - char *ptr; - uint i; - uint com_index; - - for (i= 0; i < ((uint) SQLCOM_END + 1); i++) - sql_statement_names[i]= empty_lex_str; - - SHOW_VAR *var= &com_status_vars[0]; - while (var->name != NULL) - { - ptr= var->value; - if ((first_com <= ptr) && (ptr <= last_com)) - { - com_index= ((int)(ptr - first_com))/record_size; - DBUG_ASSERT(com_index < (uint) SQLCOM_END); - sql_statement_names[com_index].str= const_cast<char *>(var->name); - sql_statement_names[com_index].length= strlen(var->name); - } - var++; - } - - DBUG_ASSERT(strcmp(sql_statement_names[(uint) SQLCOM_SELECT].str, "select") == 0); - DBUG_ASSERT(strcmp(sql_statement_names[(uint) SQLCOM_SIGNAL].str, "signal") == 0); - - sql_statement_names[(uint) SQLCOM_END].str= const_cast<char*>("error"); -} - #ifndef DBUG_OFF /* Debugging helper function to keep the locale database @@ -4661,18 +5526,29 @@ static void test_lc_time_sz() } #endif//DBUG_OFF + #ifdef __WIN__ int win_main(int argc, char **argv) #else int mysqld_main(int argc, char **argv) #endif { +#ifndef _WIN32 + /* We can't close stdin just now, because it may be booststrap mode. */ + bool please_close_stdin= fcntl(STDIN_FILENO, F_GETFD) >= 0; +#endif + /* Perform basic thread library and malloc initialization, to be able to read defaults files and parse options. */ my_progname= argv[0]; sf_leaking_memory= 1; // no safemalloc memory leak reports if we exit early + mysqld_server_started= mysqld_server_initialized= 0; + + if (init_early_variables()) + exit(1); + #ifdef HAVE_NPTL ld_assume_kernel_is_set= (getenv("LD_ASSUME_KERNEL") != 0); #endif @@ -4688,8 +5564,7 @@ int mysqld_main(int argc, char **argv) orig_argc= argc; orig_argv= argv; my_getopt_use_args_separator= TRUE; - if (load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv)) - return 1; + load_defaults_or_exit(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv); my_getopt_use_args_separator= FALSE; defaults_argc= argc; defaults_argv= argv; @@ -4699,43 +5574,44 @@ int mysqld_main(int argc, char **argv) /* Must be initialized early for comparison of options name */ system_charset_info= &my_charset_utf8_general_ci; - init_sql_statement_names(); sys_var_init(); #ifdef WITH_PERFSCHEMA_STORAGE_ENGINE /* - The performance schema needs to be initialized as early as possible, - before to-be-instrumented objects of the server are initialized. + Initialize the array of performance schema instrument configurations. */ - int ho_error; - DYNAMIC_ARRAY all_early_options; - - my_getopt_register_get_addr(NULL); - /* Skip unknown options so that they may be processed later */ - my_getopt_skip_unknown= TRUE; - - /* prepare all_early_options array */ - my_init_dynamic_array(&all_early_options, sizeof(my_option), 100, 25); - sys_var_add_options(&all_early_options, sys_var::PARSE_EARLY); - add_terminator(&all_early_options); - + init_pfs_instrument_array(); +#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */ /* Logs generated while parsing the command line options are buffered and printed later. */ buffered_logs.init(); my_getopt_error_reporter= buffered_option_error_reporter; + my_charset_error_reporter= buffered_option_error_reporter; +#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE + pfs_param.m_pfs_instrument= const_cast<char*>(""); +#endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */ + my_timer_init(&sys_timer_info); - ho_error= handle_options(&remaining_argc, &remaining_argv, - (my_option*)(all_early_options.buffer), NULL); - delete_dynamic(&all_early_options); + int ho_error __attribute__((unused))= handle_early_options(); + + /* fix tdc_size */ + if (IS_SYSVAR_AUTOSIZE(&tdc_size)) + { + SYSVAR_AUTOSIZE(tdc_size, MY_MIN(400 + tdc_size / 2, 2000)); + } + +#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE if (ho_error == 0) { - /* Add back the program name handle_options removes */ - remaining_argc++; - remaining_argv--; - if (pfs_param.m_enabled) + if (pfs_param.m_enabled && !opt_help && !opt_bootstrap) { + /* Add sizing hints from the server sizing parameters. */ + pfs_param.m_hints.m_table_definition_cache= tdc_size; + pfs_param.m_hints.m_table_open_cache= tc_size; + pfs_param.m_hints.m_max_connections= max_connections; + pfs_param.m_hints.m_open_files_limit= open_files_limit; PSI_hook= initialize_performance_schema(&pfs_param); if (PSI_hook == NULL) { @@ -4763,27 +5639,29 @@ int mysqld_main(int argc, char **argv) if available. */ if (PSI_hook) - PSI_server= (PSI*) PSI_hook->get_interface(PSI_CURRENT_VERSION); - - if (PSI_server) { - /* - Now that we have parsed the command line arguments, and have initialized - the performance schema itself, the next step is to register all the - server instruments. - */ - init_server_psi_keys(); - /* Instrument the main thread */ - PSI_thread *psi= PSI_server->new_thread(key_thread_main, NULL, 0); - if (psi) - PSI_server->set_thread(psi); + PSI *psi_server= (PSI*) PSI_hook->get_interface(PSI_CURRENT_VERSION); + if (likely(psi_server != NULL)) + { + set_psi_server(psi_server); - /* - Now that some instrumentation is in place, - recreate objects which were initialised early, - so that they are instrumented as well. - */ - my_thread_global_reinit(); + /* + Now that we have parsed the command line arguments, and have + initialized the performance schema itself, the next step is to + register all the server instruments. + */ + init_server_psi_keys(); + /* Instrument the main thread */ + PSI_thread *psi= PSI_THREAD_CALL(new_thread)(key_thread_main, NULL, 0); + PSI_THREAD_CALL(set_thread)(psi); + + /* + Now that some instrumentation is in place, + recreate objects which were initialised early, + so that they are instrumented as well. + */ + my_thread_global_reinit(); + } } #endif /* HAVE_PSI_INTERFACE */ @@ -4833,8 +5711,11 @@ int mysqld_main(int argc, char **argv) init_signals(); - my_thread_stack_size= my_setstacksize(&connection_attrib, - my_thread_stack_size); + ulonglong new_thread_stack_size; + new_thread_stack_size= my_setstacksize(&connection_attrib, + my_thread_stack_size); + if (new_thread_stack_size != my_thread_stack_size) + SYSVAR_AUTOSIZE(my_thread_stack_size, new_thread_stack_size); (void) thr_setconcurrency(concurrency); // 10 by default @@ -4868,9 +5749,12 @@ int mysqld_main(int argc, char **argv) set_user(mysqld_user, user_info); } - if (opt_bin_log && !server_id) + if (WSREP_ON && wsrep_check_opts()) + global_system_variables.wsrep_on= 0; + + if (opt_bin_log && !global_system_variables.server_id) { - server_id= 1; + SYSVAR_AUTOSIZE(global_system_variables.server_id, ::server_id= 1); #ifdef EXTRA_DEBUG sql_print_warning("You have enabled the binary log, but you haven't set " "server-id to a non-zero value: we force server id to 1; " @@ -4896,9 +5780,6 @@ int mysqld_main(int argc, char **argv) #ifdef __WIN__ if (!opt_console) { - if (reopen_fstreams(log_error_file, stdout, stderr)) - unireg_abort(1); - setbuf(stderr, NULL); FreeConsole(); // Remove window } @@ -4909,11 +5790,18 @@ int mysqld_main(int argc, char **argv) } #endif - /* - Initialize my_str_malloc() and my_str_free() - */ - my_str_malloc= &my_str_malloc_mysqld; - my_str_free= &my_str_free_mysqld; +#ifdef WITH_WSREP + // Recover and exit. + if (wsrep_recovery) + { + select_thread_in_use= 0; + if (WSREP_ON) + wsrep_recover(); + else + sql_print_information("WSREP: disabled, skipping position recovery"); + unireg_abort(0); + } +#endif /* init signals & alarm @@ -4931,7 +5819,7 @@ int mysqld_main(int argc, char **argv) delete_pid_file(MYF(MY_WME)); - if (unix_sock != INVALID_SOCKET) + if (mysql_socket_getfd(unix_sock) != INVALID_SOCKET) unlink(mysqld_unix_port); exit(1); } @@ -4939,46 +5827,59 @@ int mysqld_main(int argc, char **argv) if (!opt_noacl) (void) grant_init(); - if (!opt_bootstrap) - servers_init(0); - udf_init(); - init_status_vars(); if (opt_bootstrap) /* If running with bootstrap, do not start replication. */ opt_skip_slave_start= 1; binlog_unsafe_map_init(); - /* - init_slave() must be called after the thread keys are created. - Some parts of the code (e.g. SHOW STATUS LIKE 'slave_running' and other - places) assume that active_mi != 0, so let's fail if it's 0 (out of - memory); a message has already been printed. - */ - if (init_slave() && !active_mi) - { - unireg_abort(1); - } #ifdef WITH_PERFSCHEMA_STORAGE_ENGINE initialize_performance_schema_acl(opt_bootstrap); - /* - Do not check the structure of the performance schema tables - during bootstrap: - - the tables are not supposed to exist yet, bootstrap will create them - - a check would print spurious error messages - */ - if (! opt_bootstrap) - check_performance_schema(); #endif initialize_information_schema_acl(); execute_ddl_log_recovery(); - if (Events::init(opt_noacl || opt_bootstrap)) + /* + Change EVENTS_ORIGINAL to EVENTS_OFF (the default value) as there is no + point in using ORIGINAL during startup + */ + if (Events::opt_event_scheduler == Events::EVENTS_ORIGINAL) + Events::opt_event_scheduler= Events::EVENTS_OFF; + + Events::set_original_state(Events::opt_event_scheduler); + if (Events::init((THD*) 0, opt_noacl || opt_bootstrap)) unireg_abort(1); + if (WSREP_ON) + { + if (opt_bootstrap) + { + /*! bootstrap wsrep init was taken care of above */ + } + else + { + wsrep_SE_initialized(); + + if (wsrep_before_SE()) + { + /*! in case of no SST wsrep waits in view handler callback */ + wsrep_SE_init_grab(); + wsrep_SE_init_done(); + /*! in case of SST wsrep waits for wsrep->sst_received */ + wsrep_sst_continue(); + } + else + { + wsrep_init_startup (false); + } + + wsrep_create_appliers(wsrep_slave_threads - 1); + } + } + if (opt_bootstrap) { select_thread_in_use= 0; // Allow 'kill' to work @@ -4991,31 +5892,58 @@ int mysqld_main(int argc, char **argv) exit(0); } } + + create_shutdown_thread(); + start_handle_manager(); + + /* Copy default global rpl_filter to global_rpl_filter */ + copy_filter_setting(global_rpl_filter, get_or_create_rpl_filter("", 0)); + + /* + init_slave() must be called after the thread keys are created. + Some parts of the code (e.g. SHOW STATUS LIKE 'slave_running' and other + places) assume that active_mi != 0, so let's fail if it's 0 (out of + memory); a message has already been printed. + */ + if (init_slave() && !active_mi) + { + unireg_abort(1); + } + if (opt_init_file && *opt_init_file) { if (read_init_file(opt_init_file)) unireg_abort(1); } - create_shutdown_thread(); - start_handle_manager(); - + disable_log_notes= 0; /* Startup done, now we can give notes again */ sql_print_information(ER_DEFAULT(ER_STARTUP),my_progname,server_version, - ((unix_sock == INVALID_SOCKET) ? (char*) "" - : mysqld_unix_port), + ((mysql_socket_getfd(unix_sock) == INVALID_SOCKET) ? + (char*) "" : mysqld_unix_port), mysqld_port, MYSQL_COMPILATION_COMMENT); + +#ifndef _WIN32 + // try to keep fd=0 busy + if (please_close_stdin && !freopen("/dev/null", "r", stdin)) + { + // fall back on failure + fclose(stdin); + } +#endif + #if defined(_WIN32) && !defined(EMBEDDED_LIBRARY) Service.SetRunning(); #endif - /* Signal threads waiting for server to be started */ mysql_mutex_lock(&LOCK_server_started); mysqld_server_started= 1; mysql_cond_signal(&COND_server_started); mysql_mutex_unlock(&LOCK_server_started); + MYSQL_SET_STAGE(0 ,__FILE__, __LINE__); + #if defined(_WIN32) || defined(HAVE_SMEM) handle_connections_methods(); #else @@ -5030,6 +5958,7 @@ int mysqld_main(int argc, char **argv) #ifdef EXTRA_DEBUG2 sql_print_error("Before Lock_thread_count"); #endif + WSREP_DEBUG("Before Lock_thread_count"); mysql_mutex_lock(&LOCK_thread_count); DBUG_PRINT("quit", ("Got thread_count mutex")); select_thread_in_use=0; // For close_connections @@ -5040,13 +5969,12 @@ int mysqld_main(int argc, char **argv) #endif #endif /* __WIN__ */ -#ifdef HAVE_PSI_INTERFACE +#ifdef HAVE_PSI_THREAD_INTERFACE /* Disable the main thread instrumentation, to avoid recording events during the shutdown. */ - if (PSI_server) - PSI_server->delete_current_thread(); + PSI_THREAD_CALL(delete_current_thread)(); #endif /* Wait until cleanup is done */ @@ -5296,12 +6224,15 @@ static void bootstrap(MYSQL_FILE *file) DBUG_ENTER("bootstrap"); THD *thd= new THD; +#ifdef WITH_WSREP + thd->variables.wsrep_on= 0; +#endif thd->bootstrap=1; - my_net_init(&thd->net,(st_vio*) 0); + my_net_init(&thd->net,(st_vio*) 0, thd, MYF(0)); thd->max_client_packet_length= thd->net.max_packet; thd->security_ctx->master_access= ~(ulong)0; thd->thread_id= thd->variables.pseudo_thread_id= thread_id++; - thread_count++; + thread_count++; // Safe as only one thread running in_bootstrap= TRUE; bootstrap_file=file; @@ -5385,55 +6316,70 @@ void handle_connection_in_main_thread(THD *thd) void create_thread_to_handle_connection(THD *thd) { + DBUG_ENTER("create_thread_to_handle_connection"); + mysql_mutex_assert_owner(&LOCK_thread_count); + + /* Check if we can get thread from the cache */ if (cached_thread_count > wake_thread) { - /* Get thread from cache */ - thread_cache.push_back(thd); - wake_thread++; - mysql_cond_signal(&COND_thread_cache); + mysql_mutex_lock(&LOCK_thread_cache); + /* Recheck condition when we have the lock */ + if (cached_thread_count > wake_thread) + { + mysql_mutex_unlock(&LOCK_thread_count); + /* Get thread from cache */ + thread_cache.push_back(thd); + wake_thread++; + mysql_cond_signal(&COND_thread_cache); + mysql_mutex_unlock(&LOCK_thread_cache); + DBUG_PRINT("info",("Thread created")); + DBUG_VOID_RETURN; + } + mysql_mutex_unlock(&LOCK_thread_cache); } - else + + char error_message_buff[MYSQL_ERRMSG_SIZE]; + /* Create new thread to handle connection */ + int error; + thread_created++; + threads.append(thd); + DBUG_PRINT("info",(("creating thread %lu"), thd->thread_id)); + thd->prior_thr_create_utime= microsecond_interval_timer(); + if ((error= mysql_thread_create(key_thread_one_connection, + &thd->real_id, &connection_attrib, + handle_one_connection, + (void*) thd))) { - char error_message_buff[MYSQL_ERRMSG_SIZE]; - /* Create new thread to handle connection */ - int error; - thread_created++; - threads.append(thd); - DBUG_PRINT("info",(("creating thread %lu"), thd->thread_id)); - thd->prior_thr_create_utime= microsecond_interval_timer(); - if ((error= mysql_thread_create(key_thread_one_connection, - &thd->real_id, &connection_attrib, - handle_one_connection, - (void*) thd))) - { - /* purecov: begin inspected */ - DBUG_PRINT("error", - ("Can't create thread to handle request (error %d)", - error)); + /* purecov: begin inspected */ + DBUG_PRINT("error", + ("Can't create thread to handle request (error %d)", + error)); + thd->set_killed(KILL_CONNECTION); // Safety + mysql_mutex_unlock(&LOCK_thread_count); - thread_count--; - thd->killed= KILL_CONNECTION; // Safety - mysql_mutex_unlock(&LOCK_thread_count); + mysql_mutex_lock(&LOCK_connection_count); + (*thd->scheduler->connection_count)--; + mysql_mutex_unlock(&LOCK_connection_count); - mysql_mutex_lock(&LOCK_connection_count); - (*thd->scheduler->connection_count)--; - mysql_mutex_unlock(&LOCK_connection_count); + statistic_increment(aborted_connects,&LOCK_status); + statistic_increment(connection_errors_internal, &LOCK_status); + /* Can't use my_error() since store_globals has not been called. */ + my_snprintf(error_message_buff, sizeof(error_message_buff), + ER_THD(thd, ER_CANT_CREATE_THREAD), error); + net_send_error(thd, ER_CANT_CREATE_THREAD, error_message_buff, NULL); + close_connection(thd, ER_OUT_OF_RESOURCES); - statistic_increment(aborted_connects,&LOCK_status); - /* Can't use my_error() since store_globals has not been called. */ - my_snprintf(error_message_buff, sizeof(error_message_buff), - ER_THD(thd, ER_CANT_CREATE_THREAD), error); - net_send_error(thd, ER_CANT_CREATE_THREAD, error_message_buff, NULL); - close_connection(thd, ER_OUT_OF_RESOURCES); - mysql_mutex_lock(&LOCK_thread_count); - delete thd; - mysql_mutex_unlock(&LOCK_thread_count); - return; - /* purecov: end */ - } + mysql_mutex_lock(&LOCK_thread_count); + thd->unlink(); + mysql_mutex_unlock(&LOCK_thread_count); + delete thd; + thread_safe_decrement32(&thread_count); + return; + /* purecov: end */ } mysql_mutex_unlock(&LOCK_thread_count); DBUG_PRINT("info",("Thread created")); + DBUG_VOID_RETURN; } @@ -5470,6 +6416,7 @@ static void create_new_thread(THD *thd) close_connection(thd, ER_CON_COUNT_ERROR); statistic_increment(denied_connections, &LOCK_status); delete thd; + statistic_increment(connection_errors_max_connection, &LOCK_status); DBUG_VOID_RETURN; } @@ -5480,10 +6427,10 @@ static void create_new_thread(THD *thd) mysql_mutex_unlock(&LOCK_connection_count); - /* Start a new thread to handle connection. */ + thread_safe_increment32(&thread_count); + /* Start a new thread to handle connection. */ mysql_mutex_lock(&LOCK_thread_count); - /* The initialization of thread_id is done in create_embedded_thd() for the embedded library. @@ -5491,8 +6438,6 @@ static void create_new_thread(THD *thd) */ thd->thread_id= thd->variables.pseudo_thread_id= thread_id++; - thread_count++; - MYSQL_CALLBACK(thd->scheduler, add_connection, (thd)); DBUG_VOID_RETURN; @@ -5504,8 +6449,9 @@ static void create_new_thread(THD *thd) inline void kill_broken_server() { /* hack to get around signals ignored in syscalls for problem OS's */ - if (unix_sock == INVALID_SOCKET || - (!opt_disable_networking && base_ip_sock == INVALID_SOCKET)) + if (mysql_socket_getfd(unix_sock) == INVALID_SOCKET || + (!opt_disable_networking && + mysql_socket_getfd(base_ip_sock) == INVALID_SOCKET)) { select_thread_in_use = 0; /* The following call will never return */ @@ -5524,7 +6470,8 @@ inline void kill_broken_server() void handle_connections_sockets() { - my_socket UNINIT_VAR(sock), UNINIT_VAR(new_sock); + MYSQL_SOCKET sock= mysql_socket_invalid(); + MYSQL_SOCKET new_sock= mysql_socket_invalid(); uint error_count=0; THD *thd; struct sockaddr_storage cAddr; @@ -5533,38 +6480,43 @@ void handle_connections_sockets() int extra_ip_flags __attribute__((unused))=0; int flags=0,retval; st_vio *vio_tmp; + bool is_unix_sock; #ifdef HAVE_POLL int socket_count= 0; struct pollfd fds[3]; // for ip_sock, unix_sock and extra_ip_sock + MYSQL_SOCKET pfs_fds[3]; // for performance schema #define setup_fds(X) \ - fds[socket_count].fd= X; \ + mysql_socket_set_thread_owner(X); \ + pfs_fds[socket_count]= (X); \ + fds[socket_count].fd= mysql_socket_getfd(X); \ fds[socket_count].events= POLLIN; \ socket_count++ #else +#define setup_fds(X) FD_SET(mysql_socket_getfd(X),&clientFDs) fd_set readFDs,clientFDs; - uint max_used_connection= (uint) - max(max(base_ip_sock, unix_sock), extra_ip_sock) + 1; -#define setup_fds(X) FD_SET(X,&clientFDs) FD_ZERO(&clientFDs); #endif DBUG_ENTER("handle_connections_sockets"); - if (base_ip_sock != INVALID_SOCKET) + if (mysql_socket_getfd(base_ip_sock) != INVALID_SOCKET) { setup_fds(base_ip_sock); - ip_flags = fcntl(base_ip_sock, F_GETFL, 0); + ip_flags = fcntl(mysql_socket_getfd(base_ip_sock), F_GETFL, 0); } - if (extra_ip_sock != INVALID_SOCKET) + if (mysql_socket_getfd(extra_ip_sock) != INVALID_SOCKET) { setup_fds(extra_ip_sock); - extra_ip_flags = fcntl(extra_ip_sock, F_GETFL, 0); + extra_ip_flags = fcntl(mysql_socket_getfd(extra_ip_sock), F_GETFL, 0); } #ifdef HAVE_SYS_UN_H setup_fds(unix_sock); - socket_flags=fcntl(unix_sock, F_GETFL, 0); + socket_flags=fcntl(mysql_socket_getfd(unix_sock), F_GETFL, 0); #endif + sd_notify(0, "READY=1\n" + "STATUS=Taking your SQL requests now...\n"); + DBUG_PRINT("general",("Waiting for connections.")); MAYBE_BROKEN_SYSCALL; while (!abort_loop) @@ -5573,14 +6525,19 @@ void handle_connections_sockets() retval= poll(fds, socket_count, -1); #else readFDs=clientFDs; - - retval= select((int) max_used_connection,&readFDs,0,0,0); + retval= select((int) 0,&readFDs,0,0,0); #endif if (retval < 0) { if (socket_errno != SOCKET_EINTR) { + /* + select(2)/poll(2) failed on the listening port. + There is not much details to report about the client, + increment the server global status variable. + */ + statistic_increment(connection_errors_accept, &LOCK_status); if (!select_errors++ && !abort_loop) /* purecov: inspected */ sql_print_error("mysqld: Got error %d from select",socket_errno); /* purecov: inspected */ } @@ -5600,19 +6557,19 @@ void handle_connections_sockets() { if (fds[i].revents & POLLIN) { - sock= fds[i].fd; - flags= fcntl(sock, F_GETFL, 0); + sock= pfs_fds[i]; + flags= fcntl(mysql_socket_getfd(sock), F_GETFL, 0); break; } } #else // HAVE_POLL - if (FD_ISSET(base_ip_sock,&readFDs)) + if (FD_ISSET(mysql_socket_getfd(base_ip_sock),&readFDs)) { sock= base_ip_sock; flags= ip_flags; } else - if (FD_ISSET(extra_ip_sock,&readFDs)) + if (FD_ISSET(mysql_socket_getfd(extra_ip_sock),&readFDs)) { sock= extra_ip_sock; flags= extra_ip_flags; @@ -5628,18 +6585,19 @@ void handle_connections_sockets() if (!(test_flags & TEST_BLOCKING)) { #if defined(O_NONBLOCK) - fcntl(sock, F_SETFL, flags | O_NONBLOCK); + fcntl(mysql_socket_getfd(sock), F_SETFL, flags | O_NONBLOCK); #elif defined(O_NDELAY) - fcntl(sock, F_SETFL, flags | O_NDELAY); + fcntl(mysql_socket_getfd(sock), F_SETFL, flags | O_NDELAY); #endif } #endif /* NO_FCNTL_NONBLOCK */ for (uint retry=0; retry < MAX_ACCEPT_RETRY; retry++) { size_socket length= sizeof(struct sockaddr_storage); - new_sock= accept(sock, (struct sockaddr *)(&cAddr), - &length); - if (new_sock != INVALID_SOCKET || + new_sock= mysql_socket_accept(key_socket_client_connection, sock, + (struct sockaddr *)(&cAddr), + &length); + if (mysql_socket_getfd(new_sock) != INVALID_SOCKET || (socket_errno != SOCKET_EINTR && socket_errno != SOCKET_EAGAIN)) break; MAYBE_BROKEN_SYSCALL; @@ -5647,16 +6605,25 @@ void handle_connections_sockets() if (!(test_flags & TEST_BLOCKING)) { if (retry == MAX_ACCEPT_RETRY - 1) - fcntl(sock, F_SETFL, flags); // Try without O_NONBLOCK + { + // Try without O_NONBLOCK + fcntl(mysql_socket_getfd(sock), F_SETFL, flags); + } } #endif } #if !defined(NO_FCNTL_NONBLOCK) if (!(test_flags & TEST_BLOCKING)) - fcntl(sock, F_SETFL, flags); + fcntl(mysql_socket_getfd(sock), F_SETFL, flags); #endif - if (new_sock == INVALID_SOCKET) + if (mysql_socket_getfd(new_sock) == INVALID_SOCKET) { + /* + accept(2) failed on the listening port, after many retries. + There is not much details to report about the client, + increment the server global status variable. + */ + statistic_increment(connection_errors_accept, &LOCK_status); if ((error_count++ & 255) == 0) // This can happen often sql_perror("Error in accept"); MAYBE_BROKEN_SYSCALL; @@ -5664,14 +6631,19 @@ void handle_connections_sockets() sleep(1); // Give other threads some time continue; } +#ifdef FD_CLOEXEC + (void) fcntl(mysql_socket_getfd(new_sock), F_SETFD, FD_CLOEXEC); +#endif #ifdef HAVE_LIBWRAP { - if (sock == base_ip_sock || sock == extra_ip_sock) + if (mysql_socket_getfd(sock) == mysql_socket_getfd(base_ip_sock) || + mysql_socket_getfd(sock) == mysql_socket_getfd(extra_ip_sock)) { struct request_info req; signal(SIGCHLD, SIG_DFL); - request_init(&req, RQ_DAEMON, libwrapName, RQ_FILE, new_sock, NULL); + request_init(&req, RQ_DAEMON, libwrapName, RQ_FILE, + mysql_socket_getfd(new_sock), NULL); my_fromhost(&req); if (!my_hosts_access(&req)) { @@ -5693,42 +6665,43 @@ void handle_connections_sockets() ((void (*)(int))req.sink)(req.fd); (void) mysql_socket_shutdown(new_sock, SHUT_RDWR); - (void) closesocket(new_sock); + (void) mysql_socket_close(new_sock); + /* + The connection was refused by TCP wrappers. + There are no details (by client IP) available to update the + host_cache. + */ + statistic_increment(connection_errors_tcpwrap, &LOCK_status); continue; } } } #endif /* HAVE_LIBWRAP */ - { - size_socket dummyLen; - struct sockaddr_storage dummy; - dummyLen = sizeof(dummy); - if ( getsockname(new_sock,(struct sockaddr *)&dummy, - (SOCKET_SIZE_TYPE *)&dummyLen) < 0 ) - { - sql_perror("Error on new connection socket"); - (void) mysql_socket_shutdown(new_sock, SHUT_RDWR); - (void) closesocket(new_sock); - continue; - } - } - /* ** Don't allow too many connections */ - if (!(thd= new THD)) + DBUG_PRINT("info", ("Creating THD for new connection")); + if (!(thd= new THD) || thd->is_error()) { (void) mysql_socket_shutdown(new_sock, SHUT_RDWR); - (void) closesocket(new_sock); + (void) mysql_socket_close(new_sock); + statistic_increment(connection_errors_internal, &LOCK_status); + delete thd; continue; } - if (!(vio_tmp=vio_new(new_sock, - sock == unix_sock ? VIO_TYPE_SOCKET : - VIO_TYPE_TCPIP, - sock == unix_sock ? VIO_LOCALHOST: 0)) || - my_net_init(&thd->net,vio_tmp)) + /* Set to get io buffers to be part of THD */ + set_current_thd(thd); + + is_unix_sock= (mysql_socket_getfd(sock) == + mysql_socket_getfd(unix_sock)); + + if (!(vio_tmp= + mysql_socket_vio_new(new_sock, + is_unix_sock ? VIO_TYPE_SOCKET : VIO_TYPE_TCPIP, + is_unix_sock ? VIO_LOCALHOST: 0)) || + my_net_init(&thd->net, vio_tmp, thd, MYF(MY_THREAD_SPECIFIC))) { /* Only delete the temporary vio if we didn't already attach it to the @@ -5740,21 +6713,27 @@ void handle_connections_sockets() else { (void) mysql_socket_shutdown(new_sock, SHUT_RDWR); - (void) closesocket(new_sock); + (void) mysql_socket_close(new_sock); } delete thd; + statistic_increment(connection_errors_internal, &LOCK_status); continue; } - if (sock == unix_sock) + + init_net_server_extension(thd); + if (is_unix_sock) thd->security_ctx->host=(char*) my_localhost; - if (sock == extra_ip_sock) + if (mysql_socket_getfd(sock) == mysql_socket_getfd(extra_ip_sock)) { thd->extra_port= 1; thd->scheduler= extra_thread_scheduler; } create_new_thread(thd); + set_current_thd(0); } + sd_notify(0, "STOPPING=1\n" + "STATUS=Shutdown in progress\n"); DBUG_VOID_RETURN; } @@ -5847,8 +6826,9 @@ pthread_handler_t handle_connections_namedpipes(void *arg) CloseHandle(hConnectedPipe); continue; } + set_current_thd(thd); if (!(thd->net.vio= vio_new_win32pipe(hConnectedPipe)) || - my_net_init(&thd->net, thd->net.vio)) + my_net_init(&thd->net, thd->net.vio, thd, MYF(MY_THREAD_SPECIFIC))) { close_connection(thd, ER_OUT_OF_RESOURCES); delete thd; @@ -5857,6 +6837,7 @@ pthread_handler_t handle_connections_namedpipes(void *arg) /* Host is unknown */ thd->security_ctx->host= my_strdup(my_localhost, MYF(0)); create_new_thread(thd); + set_current_thd(0); } LocalFree(saPipeSecurity.lpSecurityDescriptor); CloseHandle(connectOverlapped.hEvent); @@ -6037,6 +7018,7 @@ pthread_handler_t handle_connections_shared_memory(void *arg) errmsg= "Could not set client to read mode"; goto errorconn; } + set_current_thd(thd); if (!(thd->net.vio= vio_new_win32shared_memory(handle_client_file_map, handle_client_map, event_client_wrote, @@ -6044,7 +7026,7 @@ pthread_handler_t handle_connections_shared_memory(void *arg) event_server_wrote, event_server_read, event_conn_closed)) || - my_net_init(&thd->net, thd->net.vio)) + my_net_init(&thd->net, thd->net.vio, thd, MYF(MY_THREAD_SPECIFIC))) { close_connection(thd, ER_OUT_OF_RESOURCES); errmsg= 0; @@ -6053,6 +7035,7 @@ pthread_handler_t handle_connections_shared_memory(void *arg) thd->security_ctx->host= my_strdup(my_localhost, MYF(0)); /* Host is unknown */ create_new_thread(thd); connect_number++; + set_current_thd(0); continue; errorconn: @@ -6080,6 +7063,7 @@ errorconn: CloseHandle(event_conn_closed); delete thd; } + set_current_thd(0); /* End shared memory handling */ error: @@ -6110,6 +7094,64 @@ error: Handle start options ******************************************************************************/ + +/** + Process command line options flagged as 'early'. + Some components needs to be initialized as early as possible, + because the rest of the server initialization depends on them. + Options that needs to be parsed early includes: + - the performance schema, when compiled in, + - options related to the help, + - options related to the bootstrap + The performance schema needs to be initialized as early as possible, + before to-be-instrumented objects of the server are initialized. +*/ + +int handle_early_options() +{ + int ho_error; + DYNAMIC_ARRAY all_early_options; + + my_getopt_register_get_addr(NULL); + /* Skip unknown options so that they may be processed later */ + my_getopt_skip_unknown= TRUE; + + /* prepare all_early_options array */ + my_init_dynamic_array(&all_early_options, sizeof(my_option), 100, 25, MYF(0)); + add_many_options(&all_early_options, pfs_early_options, + array_elements(pfs_early_options)); + sys_var_add_options(&all_early_options, sys_var::PARSE_EARLY); + add_terminator(&all_early_options); + + ho_error= handle_options(&remaining_argc, &remaining_argv, + (my_option*)(all_early_options.buffer), + mysqld_get_one_option); + if (ho_error == 0) + { + /* Add back the program name handle_options removes */ + remaining_argc++; + remaining_argv--; + } + + delete_dynamic(&all_early_options); + + return ho_error; +} + + +#define MYSQL_COMPATIBILITY_OPTION(option) \ + { option, OPT_MYSQL_COMPATIBILITY, \ + 0, 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 } + +#define MYSQL_TO_BE_IMPLEMENTED_OPTION(option) \ + { option, OPT_MYSQL_TO_BE_IMPLEMENTED, \ + 0, 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 } + +#define MYSQL_SUGGEST_ANALOG_OPTION(option, str) \ + { option, OPT_MYSQL_COMPATIBILITY, \ + 0, 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 } + + /** System variables are automatically command-line options (few exceptions are documented in sys_var.h), so don't need @@ -6138,7 +7180,7 @@ struct my_option my_long_options[]= {"autocommit", 0, "Set default value for autocommit (0 or 1)", &opt_autocommit, &opt_autocommit, 0, GET_BOOL, OPT_ARG, 1, 0, 0, 0, 0, NULL}, - {"bind-address", OPT_BIND_ADDRESS, "IP address to bind to.", + {"bind-address", 0, "IP address to bind to.", &my_bind_addr_str, &my_bind_addr_str, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"binlog-do-db", OPT_BINLOG_DO_DB, @@ -6186,9 +7228,6 @@ struct my_option my_long_options[]= 0, 0, 0}, {"core-file", OPT_WANT_CORE, "Write core on errors.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - /* default-storage-engine should have "MyISAM" as def_value. Instead - of initializing it here it is done in init_common_variables() due - to a compiler bug in Sun Studio compiler. */ #ifdef DBUG_OFF {"debug", '#', "Built in DBUG debugger. Disabled in this build.", ¤t_dbug_option, ¤t_dbug_option, 0, GET_STR, OPT_ARG, @@ -6245,9 +7284,16 @@ struct my_option my_long_options[]= &opt_sporadic_binlog_dump_fail, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, #endif /* HAVE_REPLICATION */ + /* default-storage-engine should have "MyISAM" as def_value. Instead + of initializing it here it is done in init_common_variables() due + to a compiler bug in Sun Studio compiler. */ {"default-storage-engine", 0, "The default storage engine for new tables", &default_storage_engine, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + {"default-tmp-storage-engine", 0, + "The default storage engine for user-created temporary tables", + &default_tmp_storage_engine, 0, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0 }, {"default-time-zone", 0, "Set the default time zone.", &default_tz_name, &default_tz_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, @@ -6269,6 +7315,9 @@ struct my_option my_long_options[]= {"stack-trace", 0 , "Print a symbolic stack trace on failure", &opt_stack_trace, &opt_stack_trace, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, #endif /* HAVE_STACKTRACE */ + {"enforce-storage-engine", 0, "Force the use of a storage engine for new tables", + &enforced_storage_engine, 0, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0 }, {"external-locking", 0, "Use system (external) locking (disabled by " "default). With this option enabled you can run myisamchk to test " "(not repair) tables while the MySQL server is running. Disable with " @@ -6298,9 +7347,6 @@ struct my_option my_long_options[]= "Set the language used for the month names and the days of the week.", &lc_time_names_name, &lc_time_names_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, - {"log", 'l', "Log connections and queries to file (deprecated option, use " - "--general-log/--general-log-file instead).", &opt_logname, &opt_logname, - 0, GET_STR_ALLOC, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"log-basename", OPT_LOG_BASENAME, "Basename for all log files and the .pid file. This sets all log file " "names at once (in 'datadir') and is normally the only option you need " @@ -6322,6 +7368,11 @@ struct my_option my_long_options[]= "File that holds the names for last binary log files.", &opt_binlog_index_name, &opt_binlog_index_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"relay-log-index", 0, + "The location and name to use for the file that keeps a list of the last " + "relay logs", + &opt_relaylog_index_name, &opt_relaylog_index_name, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"log-isam", OPT_ISAM_LOG, "Log all MyISAM changes to file.", &myisam_log_filename, &myisam_log_filename, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, @@ -6329,33 +7380,11 @@ struct my_option my_long_options[]= "Don't log extra information to update and slow-query logs.", &opt_short_log_format, &opt_short_log_format, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"log-slow-admin-statements", 0, - "Log slow OPTIMIZE, ANALYZE, ALTER and other administrative statements to " - "the slow log if it is open.", &opt_log_slow_admin_statements, - &opt_log_slow_admin_statements, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"log-slow-slave-statements", 0, - "Log slow statements executed by slave thread to the slow log if it is open.", - &opt_log_slow_slave_statements, &opt_log_slow_slave_statements, - 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"log-slow-queries", OPT_SLOW_QUERY_LOG, - "Enable logging of slow queries (longer than --long-query-time) to log file " - "or table. Optional argument is a file name for the slow log. If not given, " - "'log-basename'-slow.log will be used. Use --log-output=TABLE if you want " - "to have the log in the table mysql.slow_log. " - "Deprecated option, use --slow-query-log/--slow-query-log-file instead.", - &opt_slow_logname, &opt_slow_logname, 0, GET_STR_ALLOC, OPT_ARG, - 0, 0, 0, 0, 0, 0}, {"log-tc", 0, "Path to transaction coordinator log (used for transactions that affect " "more than one storage engine, when binary log is disabled).", &opt_tc_log_file, &opt_tc_log_file, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, -#ifdef HAVE_MMAP - {"log-tc-size", 0, "Size of transaction coordinator log.", - &opt_tc_log_size, &opt_tc_log_size, 0, GET_ULONG, - REQUIRED_ARG, TC_LOG_MIN_SIZE, TC_LOG_MIN_SIZE, (ulonglong) ULONG_MAX, 0, - TC_LOG_PAGE_SIZE, 0}, -#endif {"master-info-file", 0, "The location and name of the file that remembers the master and where " "the I/O replication thread is in the master's binlogs. Defaults to " @@ -6367,16 +7396,12 @@ struct my_option my_long_options[]= &master_retry_count, &master_retry_count, 0, GET_ULONG, REQUIRED_ARG, 3600*24, 0, 0, 0, 0, 0}, #ifdef HAVE_REPLICATION - {"init-rpl-role", 0, "Set the replication role.", + {"init-rpl-role", 0, "Set the replication role", &rpl_status, &rpl_status, &rpl_role_typelib, GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, #endif /* HAVE_REPLICATION */ {"memlock", 0, "Lock mysqld in memory.", &locked_in_memory, &locked_in_memory, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"one-thread", OPT_ONE_THREAD, - "(Deprecated): Only use one thread (for debugging under Linux). Use " - "thread-handling=no-threads instead.", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"old-style-user-limits", 0, "Enable old-style user limits (before 5.0.3, user resources were counted " "per each user+host vs. per account).", @@ -6394,28 +7419,28 @@ struct my_option my_long_options[]= "while having selected a different or no database. If you need cross " "database updates to work, make sure you have 3.23.28 or later, and use " "replicate-wild-do-table=db_name.%.", - 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"replicate-do-table", OPT_REPLICATE_DO_TABLE, "Tells the slave thread to restrict replication to the specified table. " "To specify more than one table, use the directive multiple times, once " "for each table. This will work for cross-database updates, in contrast " - "to replicate-do-db.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + "to replicate-do-db.", 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"replicate-ignore-db", OPT_REPLICATE_IGNORE_DB, "Tells the slave thread to not replicate to the specified database. To " "specify more than one database to ignore, use the directive multiple " "times, once for each database. This option will not work if you use " "cross database updates. If you need cross database updates to work, " "make sure you have 3.23.28 or later, and use replicate-wild-ignore-" - "table=db_name.%. ", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + "table=db_name.%. ", 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"replicate-ignore-table", OPT_REPLICATE_IGNORE_TABLE, "Tells the slave thread to not replicate to the specified table. To specify " "more than one table to ignore, use the directive multiple times, once for " "each table. This will work for cross-database updates, in contrast to " - "replicate-ignore-db.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + "replicate-ignore-db.", 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"replicate-rewrite-db", OPT_REPLICATE_REWRITE_DB, "Updates to a database with a different name than the original. Example: " "replicate-rewrite-db=master_db_name->slave_db_name.", - 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, #ifdef HAVE_REPLICATION {"replicate-same-server-id", 0, "In replication, if set to 1, do not skip events having our server id. " @@ -6431,7 +7456,7 @@ struct my_option my_long_options[]= "database updates. Example: replicate-wild-do-table=foo%.bar% will " "replicate only updates to tables in all databases that start with foo " "and whose table names start with bar.", - 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"replicate-wild-ignore-table", OPT_REPLICATE_WILD_IGNORE_TABLE, "Tells the slave thread to not replicate to the tables that match the " "given wildcard pattern. To specify more than one table to ignore, use " @@ -6439,7 +7464,7 @@ struct my_option my_long_options[]= "cross-database updates. Example: replicate-wild-ignore-table=foo%.bar% " "will not do updates to tables in databases that start with foo and whose " "table names start with bar.", - 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + 0, 0, 0, GET_STR | GET_ASK_ADDR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"safe-mode", OPT_SAFE, "Skip some optimize stages (for testing). Deprecated.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"safe-user-create", 0, @@ -6450,8 +7475,10 @@ struct my_option my_long_options[]= "Show user and password in SHOW SLAVE HOSTS on this master.", &opt_show_slave_auth_info, &opt_show_slave_auth_info, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"silent-startup", OPT_SILENT, "Don't print [Note] to the error log during startup.", + &opt_silent_startup, &opt_silent_startup, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"skip-bdb", OPT_DEPRECATED_OPTION, - "Deprecated option; Exist only for compatiblity with old my.cnf files", + "Deprecated option; Exist only for compatibility with old my.cnf files", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, #ifndef DISABLE_GRANT_OPTIONS {"skip-grant-tables", 0, @@ -6464,10 +7491,20 @@ struct my_option my_long_options[]= {"skip-slave-start", 0, "If set, slave is not autostarted.", &opt_skip_slave_start, &opt_skip_slave_start, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"skip-thread-priority", OPT_SKIP_PRIOR, - "Don't give threads different priorities. This option is deprecated " - "because it has no effect; the implied behavior is already the default.", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef HAVE_REPLICATION + {"slave-parallel-mode", OPT_SLAVE_PARALLEL_MODE, + "Controls what transactions are applied in parallel when using " + "--slave-parallel-threads. Possible values: \"optimistic\" tries to " + "apply most transactional DML in parallel, and handles any conflicts " + "with rollback and retry. \"conservative\" limits parallelism in an " + "effort to avoid any conflicts. \"aggressive\" tries to maximise the " + "parallelism, possibly at the cost of increased conflict rate. " + "\"minimal\" only parallelizes the commit steps of transactions. " + "\"none\" disables parallel apply completely.", + &opt_slave_parallel_mode, &opt_slave_parallel_mode, + &slave_parallel_mode_typelib, GET_ENUM | GET_ASK_ADDR, REQUIRED_ARG, + SLAVE_PARALLEL_CONSERVATIVE, 0, 0, 0, 0, 0}, +#endif #if defined(_WIN32) && !defined(EMBEDDED_LIBRARY) {"slow-start-timeout", 0, "Maximum number of milliseconds that the service control manager should wait " @@ -6477,7 +7514,7 @@ struct my_option my_long_options[]= #endif #ifdef HAVE_OPENSSL {"ssl", 0, - "Enable SSL for connection (automatically enabled with other flags).", + "Enable SSL for connection (automatically enabled if an ssl option is used).", &opt_use_ssl, &opt_use_ssl, 0, GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0}, #endif @@ -6503,8 +7540,8 @@ struct my_option my_long_options[]= &global_system_variables.sysdate_is_now, 0, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 1, 0}, {"tc-heuristic-recover", 0, - "Decision to use in heuristic recover process. Possible values are COMMIT " - "or ROLLBACK.", &tc_heuristic_recover, &tc_heuristic_recover, + "Decision to use in heuristic recover process", + &tc_heuristic_recover, &tc_heuristic_recover, &tc_heuristic_recover_typelib, GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"temp-pool", 0, #if (ENABLE_TEMP_POOL) @@ -6516,45 +7553,106 @@ struct my_option my_long_options[]= &use_temp_pool, &use_temp_pool, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, {"transaction-isolation", 0, - "Default transaction isolation level.", + "Default transaction isolation level", &global_system_variables.tx_isolation, &global_system_variables.tx_isolation, &tx_isolation_typelib, GET_ENUM, REQUIRED_ARG, ISO_REPEATABLE_READ, 0, 0, 0, 0, 0}, + {"transaction-read-only", 0, + "Default transaction access mode. " + "True if transactions are read-only.", + &global_system_variables.tx_read_only, + &global_system_variables.tx_read_only, 0, + GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"user", 'u', "Run mysqld daemon as user.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"verbose", 'v', "Used with --help option for detailed help.", &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}, - {"plugin-load", 0, + {"plugin-load", OPT_PLUGIN_LOAD, "Semicolon-separated list of plugins to load, where each plugin is " "specified as ether a plugin_name=library_file pair or only a library_file. " "If the latter case, all plugins from a given library_file will be loaded.", - &opt_plugin_load, &opt_plugin_load, 0, + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"plugin-load-add", OPT_PLUGIN_LOAD_ADD, + "Optional semicolon-separated list of plugins to load. This option adds " + "to the list specified by --plugin-load in an incremental way. " + "It can be specified many times, adding more plugins every time.", + 0, 0, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"table_cache", 0, "Deprecated; use --table-open-cache instead.", - &table_cache_size, &table_cache_size, 0, GET_ULONG, + &tc_size, &tc_size, 0, GET_ULONG, REQUIRED_ARG, TABLE_OPEN_CACHE_DEFAULT, 1, 512*1024L, 0, 1, 0}, - {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} -}; +#ifdef WITH_WSREP + {"wsrep-new-cluster", 0, "Bootstrap a cluster. It works by overriding the " + "current value of wsrep_cluster_address. It is recommended not to add this " + "option to the config file as this will trigger bootstrap on every server " + "start.", &wsrep_new_cluster, &wsrep_new_cluster, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, +#endif + /* The following options exist in 5.6 but not in 10.0 */ + MYSQL_COMPATIBILITY_OPTION("log-raw"), + MYSQL_COMPATIBILITY_OPTION("log-bin-use-v1-row-events"), + MYSQL_TO_BE_IMPLEMENTED_OPTION("default-authentication-plugin"), + MYSQL_COMPATIBILITY_OPTION("binlog-max-flush-queue-time"), + MYSQL_COMPATIBILITY_OPTION("master-info-repository"), + MYSQL_COMPATIBILITY_OPTION("relay-log-info-repository"), + MYSQL_SUGGEST_ANALOG_OPTION("binlog-rows-query-log-events", "--binlog-annotate-row-events"), + MYSQL_COMPATIBILITY_OPTION("binlog-order-commits"), + MYSQL_TO_BE_IMPLEMENTED_OPTION("log-throttle-queries-not-using-indexes"), + MYSQL_TO_BE_IMPLEMENTED_OPTION("end-markers-in-json"), + MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace"), // OPTIMIZER_TRACE + MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace-features"), // OPTIMIZER_TRACE + MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace-offset"), // OPTIMIZER_TRACE + MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace-limit"), // OPTIMIZER_TRACE + MYSQL_TO_BE_IMPLEMENTED_OPTION("optimizer-trace-max-mem-size"), // OPTIMIZER_TRACE + MYSQL_TO_BE_IMPLEMENTED_OPTION("eq-range-index-dive-limit"), + MYSQL_COMPATIBILITY_OPTION("server-id-bits"), + MYSQL_TO_BE_IMPLEMENTED_OPTION("slave-rows-search-algorithms"), // HAVE_REPLICATION + MYSQL_COMPATIBILITY_OPTION("table-open-cache-instances"), + MYSQL_TO_BE_IMPLEMENTED_OPTION("slave-allow-batching"), // HAVE_REPLICATION + MYSQL_COMPATIBILITY_OPTION("slave-checkpoint-period"), // HAVE_REPLICATION + MYSQL_COMPATIBILITY_OPTION("slave-checkpoint-group"), // HAVE_REPLICATION + MYSQL_SUGGEST_ANALOG_OPTION("slave-parallel-workers", "--slave-parallel-threads"), // HAVE_REPLICATION + MYSQL_SUGGEST_ANALOG_OPTION("slave-pending-jobs-size-max", "--slave-parallel-max-queued"), // HAVE_REPLICATION + MYSQL_TO_BE_IMPLEMENTED_OPTION("disconnect-on-expired-password"), + MYSQL_TO_BE_IMPLEMENTED_OPTION("sha256-password-private-key-path"), // HAVE_OPENSSL && !HAVE_YASSL + MYSQL_TO_BE_IMPLEMENTED_OPTION("sha256-password-public-key-path"), // HAVE_OPENSSL && !HAVE_YASSL + + /* The following options exist in 5.5 and 5.6 but not in 10.0 */ + MYSQL_SUGGEST_ANALOG_OPTION("abort-slave-event-count", "--debug-abort-slave-event-count"), + MYSQL_SUGGEST_ANALOG_OPTION("disconnect-slave-event-count", "--debug-disconnect-slave-event-count"), + MYSQL_SUGGEST_ANALOG_OPTION("exit-info", "--debug-exit-info"), + MYSQL_SUGGEST_ANALOG_OPTION("max-binlog-dump-events", "--debug-max-binlog-dump-events"), + MYSQL_SUGGEST_ANALOG_OPTION("sporadic-binlog-dump-fail", "--debug-sporadic-binlog-dump-fail"), + MYSQL_COMPATIBILITY_OPTION("new"), + + /* The following options were added after 5.6.10 */ + MYSQL_TO_BE_IMPLEMENTED_OPTION("rpl-stop-slave-timeout"), + MYSQL_TO_BE_IMPLEMENTED_OPTION("validate-user-plugins") // NO_EMBEDDED_ACCESS_CHECKS +}; -static int show_queries(THD *thd, SHOW_VAR *var, char *buff) +static int show_queries(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONGLONG; - var->value= (char *)&thd->query_id; + var->value= &thd->query_id; return 0; } -static int show_net_compression(THD *thd, SHOW_VAR *var, char *buff) +static int show_net_compression(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_MY_BOOL; - var->value= (char *)&thd->net.compress; + var->value= &thd->net.compress; return 0; } -static int show_starttime(THD *thd, SHOW_VAR *var, char *buff) +static int show_starttime(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6563,7 +7661,8 @@ static int show_starttime(THD *thd, SHOW_VAR *var, char *buff) } #ifdef ENABLED_PROFILING -static int show_flushstatustime(THD *thd, SHOW_VAR *var, char *buff) +static int show_flushstatustime(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6573,60 +7672,101 @@ static int show_flushstatustime(THD *thd, SHOW_VAR *var, char *buff) #endif #ifdef HAVE_REPLICATION -static int show_rpl_status(THD *thd, SHOW_VAR *var, char *buff) +static int show_rpl_status(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_CHAR; var->value= const_cast<char*>(rpl_status_type[(int)rpl_status]); return 0; } -static int show_slave_running(THD *thd, SHOW_VAR *var, char *buff) +static int show_slave_running(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { + Master_info *mi= NULL; + bool UNINIT_VAR(tmp); + var->type= SHOW_MY_BOOL; var->value= buff; - *((my_bool *)buff)= (my_bool) (active_mi && - active_mi->slave_running == MYSQL_SLAVE_RUN_CONNECT && - active_mi->rli.slave_running); - return 0; -} -static int show_slave_retried_trans(THD *thd, SHOW_VAR *var, char *buff) -{ - /* - TODO: with multimaster, have one such counter per line in - SHOW SLAVE STATUS, and have the sum over all lines here. - */ - if (active_mi) + if ((mi= get_master_info(&thd->variables.default_master_connection, + Sql_condition::WARN_LEVEL_NOTE))) { - var->type= SHOW_LONG; - var->value= buff; - *((long *)buff)= (long)active_mi->rli.retried_trans; + tmp= (my_bool) (mi->slave_running == MYSQL_SLAVE_RUN_READING && + mi->rli.slave_running != MYSQL_SLAVE_NOT_RUN); + mi->release(); } + if (mi) + *((my_bool *)buff)= tmp; else var->type= SHOW_UNDEF; return 0; } -static int show_slave_received_heartbeats(THD *thd, SHOW_VAR *var, char *buff) + +/* How many slaves are connected to this master */ + +static int show_slaves_connected(THD *thd, SHOW_VAR *var, char *buff) { - if (active_mi) + + var->type= SHOW_LONGLONG; + var->value= buff; + mysql_mutex_lock(&LOCK_slave_list); + + *((longlong *)buff)= slave_list.records; + + mysql_mutex_unlock(&LOCK_slave_list); + return 0; +} + + +/* How many masters this slave is connected to */ + + +static int show_slaves_running(THD *thd, SHOW_VAR *var, char *buff) +{ + var->type= SHOW_LONGLONG; + var->value= buff; + + *((longlong *)buff)= any_slave_sql_running(); + + return 0; +} + + +static int show_slave_received_heartbeats(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) +{ + Master_info *mi; + + var->type= SHOW_LONGLONG; + var->value= buff; + + if ((mi= get_master_info(&thd->variables.default_master_connection, + Sql_condition::WARN_LEVEL_NOTE))) { - var->type= SHOW_LONGLONG; - var->value= buff; - *((longlong *)buff)= active_mi->received_heartbeats; + *((longlong *)buff)= mi->received_heartbeats; + mi->release(); } else var->type= SHOW_UNDEF; return 0; } -static int show_heartbeat_period(THD *thd, SHOW_VAR *var, char *buff) + +static int show_heartbeat_period(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { - if (active_mi) + Master_info *mi; + + var->type= SHOW_CHAR; + var->value= buff; + + if ((mi= get_master_info(&thd->variables.default_master_connection, + Sql_condition::WARN_LEVEL_NOTE))) { - var->type= SHOW_CHAR; - var->value= buff; - sprintf(buff, "%.3f", active_mi->heartbeat_period); + sprintf(buff, "%.3f", mi->heartbeat_period); + mi->release(); } else var->type= SHOW_UNDEF; @@ -6636,15 +7776,17 @@ static int show_heartbeat_period(THD *thd, SHOW_VAR *var, char *buff) #endif /* HAVE_REPLICATION */ -static int show_open_tables(THD *thd, SHOW_VAR *var, char *buff) +static int show_open_tables(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; - *((long *)buff)= (long)cached_open_tables(); + *((long *) buff)= (long) tc_records(); return 0; } -static int show_prepared_stmt_count(THD *thd, SHOW_VAR *var, char *buff) +static int show_prepared_stmt_count(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6654,17 +7796,30 @@ static int show_prepared_stmt_count(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_table_definitions(THD *thd, SHOW_VAR *var, char *buff) +static int show_table_definitions(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) +{ + var->type= SHOW_LONG; + var->value= buff; + *((long *) buff)= (long) tdc_records(); + return 0; +} + + +static int show_flush_commands(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; - *((long *)buff)= (long)cached_table_definitions(); + *((long *) buff)= (long) tdc_refresh_version(); return 0; } + #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) /* Functions relying on CTX */ -static int show_ssl_ctx_sess_accept(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_accept(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6673,7 +7828,8 @@ static int show_ssl_ctx_sess_accept(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_accept_good(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_accept_good(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6682,7 +7838,8 @@ static int show_ssl_ctx_sess_accept_good(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_connect_good(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_connect_good(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6691,7 +7848,9 @@ static int show_ssl_ctx_sess_connect_good(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_accept_renegotiate(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_accept_renegotiate(THD *thd, SHOW_VAR *var, + char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6700,7 +7859,9 @@ static int show_ssl_ctx_sess_accept_renegotiate(THD *thd, SHOW_VAR *var, char *b return 0; } -static int show_ssl_ctx_sess_connect_renegotiate(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_connect_renegotiate(THD *thd, SHOW_VAR *var, + char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6709,7 +7870,8 @@ static int show_ssl_ctx_sess_connect_renegotiate(THD *thd, SHOW_VAR *var, char * return 0; } -static int show_ssl_ctx_sess_cb_hits(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_cb_hits(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6718,7 +7880,8 @@ static int show_ssl_ctx_sess_cb_hits(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_hits(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_hits(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6727,7 +7890,8 @@ static int show_ssl_ctx_sess_hits(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_cache_full(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_cache_full(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6736,7 +7900,8 @@ static int show_ssl_ctx_sess_cache_full(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_misses(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_misses(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6745,7 +7910,8 @@ static int show_ssl_ctx_sess_misses(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_timeouts(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_timeouts(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6754,7 +7920,8 @@ static int show_ssl_ctx_sess_timeouts(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_number(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_number(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6763,7 +7930,8 @@ static int show_ssl_ctx_sess_number(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_connect(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_connect(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6772,7 +7940,9 @@ static int show_ssl_ctx_sess_connect(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_sess_get_cache_size(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_sess_get_cache_size(THD *thd, SHOW_VAR *var, + char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6781,7 +7951,8 @@ static int show_ssl_ctx_sess_get_cache_size(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_get_verify_mode(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_get_verify_mode(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6790,7 +7961,8 @@ static int show_ssl_ctx_get_verify_mode(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_get_verify_depth(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_get_verify_depth(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6799,7 +7971,9 @@ static int show_ssl_ctx_get_verify_depth(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_ctx_get_session_cache_mode(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_ctx_get_session_cache_mode(THD *thd, SHOW_VAR *var, + char *buff, + enum enum_var_type scope) { var->type= SHOW_CHAR; if (!ssl_acceptor_fd) @@ -6832,17 +8006,20 @@ static int show_ssl_ctx_get_session_cache_mode(THD *thd, SHOW_VAR *var, char *bu when session_status or global_status is requested from inside an Event. */ -static int show_ssl_get_version(THD *thd, SHOW_VAR *var, char *buff) + +static int show_ssl_get_version(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_CHAR; if( thd->vio_ok() && thd->net.vio->ssl_arg ) var->value= const_cast<char*>(SSL_get_version((SSL*) thd->net.vio->ssl_arg)); else - var->value= (char *)""; + var->value= const_cast<char*>(""); return 0; } -static int show_ssl_session_reused(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_session_reused(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6853,7 +8030,8 @@ static int show_ssl_session_reused(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_get_default_timeout(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_get_default_timeout(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6864,7 +8042,8 @@ static int show_ssl_get_default_timeout(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_get_verify_mode(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_get_verify_mode(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6875,7 +8054,8 @@ static int show_ssl_get_verify_mode(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_get_verify_depth(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_get_verify_depth(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_LONG; var->value= buff; @@ -6886,17 +8066,19 @@ static int show_ssl_get_verify_depth(THD *thd, SHOW_VAR *var, char *buff) return 0; } -static int show_ssl_get_cipher(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_get_cipher(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_CHAR; if( thd->vio_ok() && thd->net.vio->ssl_arg ) var->value= const_cast<char*>(SSL_get_cipher((SSL*) thd->net.vio->ssl_arg)); else - var->value= (char *)""; + var->value= const_cast<char*>(""); return 0; } -static int show_ssl_get_cipher_list(THD *thd, SHOW_VAR *var, char *buff) +static int show_ssl_get_cipher_list(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_CHAR; var->value= buff; @@ -6918,13 +8100,120 @@ static int show_ssl_get_cipher_list(THD *thd, SHOW_VAR *var, char *buff) return 0; } + +#ifdef HAVE_YASSL + +static char * +my_asn1_time_to_string(ASN1_TIME *time, char *buf, size_t len) +{ + return yaSSL_ASN1_TIME_to_string(time, buf, len); +} + +#else /* openssl */ + +static char * +my_asn1_time_to_string(ASN1_TIME *time, char *buf, size_t len) +{ + int n_read; + char *res= NULL; + BIO *bio= BIO_new(BIO_s_mem()); + + if (bio == NULL) + return NULL; + + if (!ASN1_TIME_print(bio, time)) + goto end; + + n_read= BIO_read(bio, buf, (int) (len - 1)); + + if (n_read > 0) + { + buf[n_read]= 0; + res= buf; + } + +end: + BIO_free(bio); + return res; +} + +#endif + + +/** + Handler function for the 'ssl_get_server_not_before' variable + + @param thd the mysql thread structure + @param var the data for the variable + @param[out] buf the string to put the value of the variable into + + @return status + @retval 0 success +*/ + +static int +show_ssl_get_server_not_before(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) +{ + var->type= SHOW_CHAR; + if(thd->vio_ok() && thd->net.vio->ssl_arg) + { + SSL *ssl= (SSL*) thd->net.vio->ssl_arg; + X509 *cert= SSL_get_certificate(ssl); + ASN1_TIME *not_before= X509_get_notBefore(cert); + + var->value= my_asn1_time_to_string(not_before, buff, + SHOW_VAR_FUNC_BUFF_SIZE); + if (!var->value) + return 1; + var->value= buff; + } + else + var->value= empty_c_string; + return 0; +} + + +/** + Handler function for the 'ssl_get_server_not_after' variable + + @param thd the mysql thread structure + @param var the data for the variable + @param[out] buf the string to put the value of the variable into + + @return status + @retval 0 success +*/ + +static int +show_ssl_get_server_not_after(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) +{ + var->type= SHOW_CHAR; + if(thd->vio_ok() && thd->net.vio->ssl_arg) + { + SSL *ssl= (SSL*) thd->net.vio->ssl_arg; + X509 *cert= SSL_get_certificate(ssl); + ASN1_TIME *not_after= X509_get_notAfter(cert); + + var->value= my_asn1_time_to_string(not_after, buff, + SHOW_VAR_FUNC_BUFF_SIZE); + if (!var->value) + return 1; + } + else + var->value= empty_c_string; + return 0; +} + #endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ -static int show_default_keycache(THD *thd, SHOW_VAR *var, char *buff) +static int show_default_keycache(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { struct st_data { KEY_CACHE_STATISTICS stats; - SHOW_VAR var[8]; + SHOW_VAR var[9]; } *data; SHOW_VAR *v; @@ -6932,14 +8221,14 @@ static int show_default_keycache(THD *thd, SHOW_VAR *var, char *buff) v= data->var; var->type= SHOW_ARRAY; - var->value= (char*)v; + var->value= v; get_key_cache_statistics(dflt_key_cache, 0, &data->stats); #define set_one_keycache_var(X,Y) \ v->name= X; \ v->type= SHOW_LONGLONG; \ - v->value= (char*)&data->stats.Y; \ + v->value= &data->stats.Y; \ v++; set_one_keycache_var("blocks_not_flushed", blocks_changed); @@ -6960,8 +8249,62 @@ static int show_default_keycache(THD *thd, SHOW_VAR *var, char *buff) return 0; } + +static int show_memory_used(THD *thd, SHOW_VAR *var, char *buff, + struct system_status_var *status_var, + enum enum_var_type scope) +{ + var->type= SHOW_LONGLONG; + var->value= buff; + if (scope == OPT_GLOBAL) + *(longlong*) buff= (status_var->global_memory_used + + status_var->local_memory_used); + else + *(longlong*) buff= status_var->local_memory_used; + return 0; +} + + +#ifndef DBUG_OFF +static int debug_status_func(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) +{ +#define add_var(X,Y,Z) \ + v->name= X; \ + v->value= (char*)Y; \ + v->type= Z; \ + v++; + + var->type= SHOW_ARRAY; + var->value= buff; + + SHOW_VAR *v= (SHOW_VAR *)buff; + + if (_db_keyword_(0, "role_merge_stats", 1)) + { + static SHOW_VAR roles[]= { + {"global", &role_global_merges, SHOW_ULONG}, + {"db", &role_db_merges, SHOW_ULONG}, + {"table", &role_table_merges, SHOW_ULONG}, + {"column", &role_column_merges, SHOW_ULONG}, + {"routine", &role_routine_merges, SHOW_ULONG}, + {NullS, NullS, SHOW_LONG} + }; + + add_var("role_merges", roles, SHOW_ARRAY); + } + + v->name= 0; + +#undef add_var + + return 0; +} +#endif + #ifdef HAVE_POOL_OF_THREADS -int show_threadpool_idle_threads(THD *thd, SHOW_VAR *var, char *buff) +int show_threadpool_idle_threads(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) { var->type= SHOW_INT; var->value= buff; @@ -6977,6 +8320,7 @@ int show_threadpool_idle_threads(THD *thd, SHOW_VAR *var, char *buff) SHOW_VAR status_vars[]= { {"Aborted_clients", (char*) &aborted_threads, SHOW_LONG}, {"Aborted_connects", (char*) &aborted_connects, SHOW_LONG}, + {"Acl", (char*) acl_statistics, SHOW_ARRAY}, {"Access_denied_errors", (char*) offsetof(STATUS_VAR, access_denied_errors), SHOW_LONG_STATUS}, {"Binlog_bytes_written", (char*) offsetof(STATUS_VAR, binlog_bytes_written), SHOW_LONGLONG_STATUS}, {"Binlog_cache_disk_use", (char*) &binlog_cache_disk_use, SHOW_LONG}, @@ -6987,41 +8331,54 @@ SHOW_VAR status_vars[]= { {"Bytes_received", (char*) offsetof(STATUS_VAR, bytes_received), SHOW_LONGLONG_STATUS}, {"Bytes_sent", (char*) offsetof(STATUS_VAR, bytes_sent), SHOW_LONGLONG_STATUS}, {"Com", (char*) com_status_vars, SHOW_ARRAY}, - {"Compression", (char*) &show_net_compression, SHOW_FUNC}, + {"Compression", (char*) &show_net_compression, SHOW_SIMPLE_FUNC}, {"Connections", (char*) &thread_id, SHOW_LONG_NOFLUSH}, + {"Connection_errors_accept", (char*) &connection_errors_accept, SHOW_LONG}, + {"Connection_errors_internal", (char*) &connection_errors_internal, SHOW_LONG}, + {"Connection_errors_max_connections", (char*) &connection_errors_max_connection, SHOW_LONG}, + {"Connection_errors_peer_address", (char*) &connection_errors_peer_addr, SHOW_LONG}, + {"Connection_errors_select", (char*) &connection_errors_select, SHOW_LONG}, + {"Connection_errors_tcpwrap", (char*) &connection_errors_tcpwrap, SHOW_LONG}, {"Cpu_time", (char*) offsetof(STATUS_VAR, cpu_time), SHOW_DOUBLE_STATUS}, - {"Created_tmp_disk_tables", (char*) offsetof(STATUS_VAR, created_tmp_disk_tables), SHOW_LONG_STATUS}, + {"Created_tmp_disk_tables", (char*) offsetof(STATUS_VAR, created_tmp_disk_tables_), SHOW_LONG_STATUS}, {"Created_tmp_files", (char*) &my_tmp_file_created, SHOW_LONG}, - {"Created_tmp_tables", (char*) offsetof(STATUS_VAR, created_tmp_tables), SHOW_LONG_STATUS}, + {"Created_tmp_tables", (char*) offsetof(STATUS_VAR, created_tmp_tables_), SHOW_LONG_STATUS}, +#ifndef DBUG_OFF + {"Debug", (char*) &debug_status_func, SHOW_FUNC}, +#endif {"Delayed_errors", (char*) &delayed_insert_errors, SHOW_LONG}, {"Delayed_insert_threads", (char*) &delayed_insert_threads, SHOW_LONG_NOFLUSH}, {"Delayed_writes", (char*) &delayed_insert_writes, SHOW_LONG}, + {"Delete_scan", (char*) offsetof(STATUS_VAR, delete_scan_count), SHOW_LONG_STATUS}, {"Empty_queries", (char*) offsetof(STATUS_VAR, empty_queries), SHOW_LONG_STATUS}, {"Executed_events", (char*) &executed_events, SHOW_LONG_NOFLUSH }, {"Executed_triggers", (char*) offsetof(STATUS_VAR, executed_triggers), SHOW_LONG_STATUS}, + {"Feature_delay_key_write", (char*) &feature_files_opened_with_delayed_keys, SHOW_LONG }, {"Feature_dynamic_columns", (char*) offsetof(STATUS_VAR, feature_dynamic_columns), SHOW_LONG_STATUS}, {"Feature_fulltext", (char*) offsetof(STATUS_VAR, feature_fulltext), SHOW_LONG_STATUS}, {"Feature_gis", (char*) offsetof(STATUS_VAR, feature_gis), SHOW_LONG_STATUS}, {"Feature_locale", (char*) offsetof(STATUS_VAR, feature_locale), SHOW_LONG_STATUS}, {"Feature_subquery", (char*) offsetof(STATUS_VAR, feature_subquery), SHOW_LONG_STATUS}, {"Feature_timezone", (char*) offsetof(STATUS_VAR, feature_timezone), SHOW_LONG_STATUS}, - {"Feature_trigger", (char*) offsetof(STATUS_VAR, feature_trigger), SHOW_LONG_STATUS}, - {"Feature_xml", (char*) offsetof(STATUS_VAR, feature_xml), SHOW_LONG_STATUS}, - {"Flush_commands", (char*) &refresh_version, SHOW_LONG_NOFLUSH}, + {"Feature_trigger", (char*) offsetof(STATUS_VAR, feature_trigger), SHOW_LONG_STATUS}, + {"Feature_xml", (char*) offsetof(STATUS_VAR, feature_xml), SHOW_LONG_STATUS}, + {"Flush_commands", (char*) &show_flush_commands, SHOW_SIMPLE_FUNC}, {"Handler_commit", (char*) offsetof(STATUS_VAR, ha_commit_count), SHOW_LONG_STATUS}, {"Handler_delete", (char*) offsetof(STATUS_VAR, ha_delete_count), SHOW_LONG_STATUS}, {"Handler_discover", (char*) offsetof(STATUS_VAR, ha_discover_count), SHOW_LONG_STATUS}, + {"Handler_external_lock", (char*) offsetof(STATUS_VAR, ha_external_lock_count), SHOW_LONGLONG_STATUS}, {"Handler_icp_attempts", (char*) offsetof(STATUS_VAR, ha_icp_attempts), SHOW_LONG_STATUS}, {"Handler_icp_match", (char*) offsetof(STATUS_VAR, ha_icp_match), SHOW_LONG_STATUS}, {"Handler_mrr_init", (char*) offsetof(STATUS_VAR, ha_mrr_init_count), SHOW_LONG_STATUS}, - {"Handler_mrr_key_refills", (char*) offsetof(STATUS_VAR, ha_mrr_key_refills_count), SHOW_LONG_STATUS}, - {"Handler_mrr_rowid_refills", (char*) offsetof(STATUS_VAR, ha_mrr_rowid_refills_count), SHOW_LONG_STATUS}, + {"Handler_mrr_key_refills", (char*) offsetof(STATUS_VAR, ha_mrr_key_refills_count), SHOW_LONG_STATUS}, + {"Handler_mrr_rowid_refills",(char*) offsetof(STATUS_VAR, ha_mrr_rowid_refills_count), SHOW_LONG_STATUS}, {"Handler_prepare", (char*) offsetof(STATUS_VAR, ha_prepare_count), SHOW_LONG_STATUS}, {"Handler_read_first", (char*) offsetof(STATUS_VAR, ha_read_first_count), SHOW_LONG_STATUS}, {"Handler_read_key", (char*) offsetof(STATUS_VAR, ha_read_key_count), SHOW_LONG_STATUS}, {"Handler_read_last", (char*) offsetof(STATUS_VAR, ha_read_last_count), SHOW_LONG_STATUS}, {"Handler_read_next", (char*) offsetof(STATUS_VAR, ha_read_next_count), SHOW_LONG_STATUS}, {"Handler_read_prev", (char*) offsetof(STATUS_VAR, ha_read_prev_count), SHOW_LONG_STATUS}, + {"Handler_read_retry", (char*) offsetof(STATUS_VAR, ha_read_retry_count), SHOW_LONG_STATUS}, {"Handler_read_rnd", (char*) offsetof(STATUS_VAR, ha_read_rnd_count), SHOW_LONG_STATUS}, {"Handler_read_rnd_deleted", (char*) offsetof(STATUS_VAR, ha_read_rnd_deleted_count), SHOW_LONG_STATUS}, {"Handler_read_rnd_next", (char*) offsetof(STATUS_VAR, ha_read_rnd_next_count), SHOW_LONG_STATUS}, @@ -7034,19 +8391,25 @@ SHOW_VAR status_vars[]= { {"Handler_write", (char*) offsetof(STATUS_VAR, ha_write_count), SHOW_LONG_STATUS}, {"Key", (char*) &show_default_keycache, SHOW_FUNC}, {"Last_query_cost", (char*) offsetof(STATUS_VAR, last_query_cost), SHOW_DOUBLE_STATUS}, + {"Max_statement_time_exceeded", (char*) offsetof(STATUS_VAR, max_statement_time_exceeded), SHOW_LONG_STATUS}, + {"Master_gtid_wait_count", (char*) offsetof(STATUS_VAR, master_gtid_wait_count), SHOW_LONGLONG_STATUS}, + {"Master_gtid_wait_timeouts", (char*) offsetof(STATUS_VAR, master_gtid_wait_timeouts), SHOW_LONGLONG_STATUS}, + {"Master_gtid_wait_time", (char*) offsetof(STATUS_VAR, master_gtid_wait_time), SHOW_LONGLONG_STATUS}, {"Max_used_connections", (char*) &max_used_connections, SHOW_LONG}, + {"Memory_used", (char*) &show_memory_used, SHOW_SIMPLE_FUNC}, {"Not_flushed_delayed_rows", (char*) &delayed_rows_in_use, SHOW_LONG_NOFLUSH}, {"Open_files", (char*) &my_file_opened, SHOW_LONG_NOFLUSH}, {"Open_streams", (char*) &my_stream_opened, SHOW_LONG_NOFLUSH}, - {"Open_table_definitions", (char*) &show_table_definitions, SHOW_FUNC}, - {"Open_tables", (char*) &show_open_tables, SHOW_FUNC}, + {"Open_table_definitions", (char*) &show_table_definitions, SHOW_SIMPLE_FUNC}, + {"Open_tables", (char*) &show_open_tables, SHOW_SIMPLE_FUNC}, {"Opened_files", (char*) &my_file_total_opened, SHOW_LONG_NOFLUSH}, + {"Opened_plugin_libraries", (char*) &dlopen_count, SHOW_LONG}, {"Opened_table_definitions", (char*) offsetof(STATUS_VAR, opened_shares), SHOW_LONG_STATUS}, {"Opened_tables", (char*) offsetof(STATUS_VAR, opened_tables), SHOW_LONG_STATUS}, - {"Opened_views", (char*) offsetof(STATUS_VAR, opened_views), SHOW_LONG_STATUS}, - {"Prepared_stmt_count", (char*) &show_prepared_stmt_count, SHOW_FUNC}, - {"Rows_read", (char*) offsetof(STATUS_VAR, rows_read), SHOW_LONGLONG_STATUS}, + {"Opened_views", (char*) offsetof(STATUS_VAR, opened_views), SHOW_LONG_STATUS}, + {"Prepared_stmt_count", (char*) &show_prepared_stmt_count, SHOW_SIMPLE_FUNC}, {"Rows_sent", (char*) offsetof(STATUS_VAR, rows_sent), SHOW_LONGLONG_STATUS}, + {"Rows_read", (char*) offsetof(STATUS_VAR, rows_read), SHOW_LONGLONG_STATUS}, {"Rows_tmp_read", (char*) offsetof(STATUS_VAR, rows_tmp_read), SHOW_LONGLONG_STATUS}, #ifdef HAVE_QUERY_CACHE {"Qcache_free_blocks", (char*) &query_cache.free_memory_blocks, SHOW_LONG_NOFLUSH}, @@ -7058,54 +8421,61 @@ SHOW_VAR status_vars[]= { {"Qcache_queries_in_cache", (char*) &query_cache.queries_in_cache, SHOW_LONG_NOFLUSH}, {"Qcache_total_blocks", (char*) &query_cache.total_blocks, SHOW_LONG_NOFLUSH}, #endif /*HAVE_QUERY_CACHE*/ - {"Queries", (char*) &show_queries, SHOW_FUNC}, + {"Queries", (char*) &show_queries, SHOW_SIMPLE_FUNC}, {"Questions", (char*) offsetof(STATUS_VAR, questions), SHOW_LONG_STATUS}, #ifdef HAVE_REPLICATION - {"Rpl_status", (char*) &show_rpl_status, SHOW_FUNC}, -#endif - {"Select_full_join", (char*) offsetof(STATUS_VAR, select_full_join_count), SHOW_LONG_STATUS}, - {"Select_full_range_join", (char*) offsetof(STATUS_VAR, select_full_range_join_count), SHOW_LONG_STATUS}, - {"Select_range", (char*) offsetof(STATUS_VAR, select_range_count), SHOW_LONG_STATUS}, - {"Select_range_check", (char*) offsetof(STATUS_VAR, select_range_check_count), SHOW_LONG_STATUS}, - {"Select_scan", (char*) offsetof(STATUS_VAR, select_scan_count), SHOW_LONG_STATUS}, - {"Slave_open_temp_tables", (char*) &slave_open_temp_tables, SHOW_LONG}, + {"Rpl_status", (char*) &show_rpl_status, SHOW_SIMPLE_FUNC}, +#endif + {"Select_full_join", (char*) offsetof(STATUS_VAR, select_full_join_count_), SHOW_LONG_STATUS}, + {"Select_full_range_join", (char*) offsetof(STATUS_VAR, select_full_range_join_count_), SHOW_LONG_STATUS}, + {"Select_range", (char*) offsetof(STATUS_VAR, select_range_count_), SHOW_LONG_STATUS}, + {"Select_range_check", (char*) offsetof(STATUS_VAR, select_range_check_count_), SHOW_LONG_STATUS}, + {"Select_scan", (char*) offsetof(STATUS_VAR, select_scan_count_), SHOW_LONG_STATUS}, + {"Slave_open_temp_tables", (char*) &slave_open_temp_tables, SHOW_INT}, #ifdef HAVE_REPLICATION - {"Slave_heartbeat_period", (char*) &show_heartbeat_period, SHOW_FUNC}, - {"Slave_received_heartbeats",(char*) &show_slave_received_heartbeats, SHOW_FUNC}, - {"Slave_retried_transactions",(char*) &show_slave_retried_trans, SHOW_FUNC}, - {"Slave_running", (char*) &show_slave_running, SHOW_FUNC}, + {"Slaves_connected", (char*) &show_slaves_connected, SHOW_SIMPLE_FUNC }, + {"Slaves_running", (char*) &show_slaves_running, SHOW_SIMPLE_FUNC }, + {"Slave_connections", (char*) offsetof(STATUS_VAR, com_register_slave), SHOW_LONG_STATUS}, + {"Slave_heartbeat_period", (char*) &show_heartbeat_period, SHOW_SIMPLE_FUNC}, + {"Slave_received_heartbeats",(char*) &show_slave_received_heartbeats, SHOW_SIMPLE_FUNC}, + {"Slave_retried_transactions",(char*)&slave_retried_transactions, SHOW_LONG}, + {"Slave_running", (char*) &show_slave_running, SHOW_SIMPLE_FUNC}, + {"Slave_skipped_errors", (char*) &slave_skipped_errors, SHOW_LONGLONG}, #endif {"Slow_launch_threads", (char*) &slow_launch_threads, SHOW_LONG}, {"Slow_queries", (char*) offsetof(STATUS_VAR, long_query_count), SHOW_LONG_STATUS}, - {"Sort_merge_passes", (char*) offsetof(STATUS_VAR, filesort_merge_passes), SHOW_LONG_STATUS}, - {"Sort_range", (char*) offsetof(STATUS_VAR, filesort_range_count), SHOW_LONG_STATUS}, - {"Sort_rows", (char*) offsetof(STATUS_VAR, filesort_rows), SHOW_LONG_STATUS}, - {"Sort_scan", (char*) offsetof(STATUS_VAR, filesort_scan_count), SHOW_LONG_STATUS}, + {"Sort_merge_passes", (char*) offsetof(STATUS_VAR, filesort_merge_passes_), SHOW_LONG_STATUS}, + {"Sort_priority_queue_sorts",(char*) offsetof(STATUS_VAR, filesort_pq_sorts_), SHOW_LONG_STATUS}, + {"Sort_range", (char*) offsetof(STATUS_VAR, filesort_range_count_), SHOW_LONG_STATUS}, + {"Sort_rows", (char*) offsetof(STATUS_VAR, filesort_rows_), SHOW_LONG_STATUS}, + {"Sort_scan", (char*) offsetof(STATUS_VAR, filesort_scan_count_), SHOW_LONG_STATUS}, #ifdef HAVE_OPENSSL #ifndef EMBEDDED_LIBRARY - {"Ssl_accept_renegotiates", (char*) &show_ssl_ctx_sess_accept_renegotiate, SHOW_FUNC}, - {"Ssl_accepts", (char*) &show_ssl_ctx_sess_accept, SHOW_FUNC}, - {"Ssl_callback_cache_hits", (char*) &show_ssl_ctx_sess_cb_hits, SHOW_FUNC}, - {"Ssl_cipher", (char*) &show_ssl_get_cipher, SHOW_FUNC}, - {"Ssl_cipher_list", (char*) &show_ssl_get_cipher_list, SHOW_FUNC}, - {"Ssl_client_connects", (char*) &show_ssl_ctx_sess_connect, SHOW_FUNC}, - {"Ssl_connect_renegotiates", (char*) &show_ssl_ctx_sess_connect_renegotiate, SHOW_FUNC}, - {"Ssl_ctx_verify_depth", (char*) &show_ssl_ctx_get_verify_depth, SHOW_FUNC}, - {"Ssl_ctx_verify_mode", (char*) &show_ssl_ctx_get_verify_mode, SHOW_FUNC}, - {"Ssl_default_timeout", (char*) &show_ssl_get_default_timeout, SHOW_FUNC}, - {"Ssl_finished_accepts", (char*) &show_ssl_ctx_sess_accept_good, SHOW_FUNC}, - {"Ssl_finished_connects", (char*) &show_ssl_ctx_sess_connect_good, SHOW_FUNC}, - {"Ssl_session_cache_hits", (char*) &show_ssl_ctx_sess_hits, SHOW_FUNC}, - {"Ssl_session_cache_misses", (char*) &show_ssl_ctx_sess_misses, SHOW_FUNC}, - {"Ssl_session_cache_mode", (char*) &show_ssl_ctx_get_session_cache_mode, SHOW_FUNC}, - {"Ssl_session_cache_overflows", (char*) &show_ssl_ctx_sess_cache_full, SHOW_FUNC}, - {"Ssl_session_cache_size", (char*) &show_ssl_ctx_sess_get_cache_size, SHOW_FUNC}, - {"Ssl_session_cache_timeouts", (char*) &show_ssl_ctx_sess_timeouts, SHOW_FUNC}, - {"Ssl_sessions_reused", (char*) &show_ssl_session_reused, SHOW_FUNC}, - {"Ssl_used_session_cache_entries",(char*) &show_ssl_ctx_sess_number, SHOW_FUNC}, - {"Ssl_verify_depth", (char*) &show_ssl_get_verify_depth, SHOW_FUNC}, - {"Ssl_verify_mode", (char*) &show_ssl_get_verify_mode, SHOW_FUNC}, - {"Ssl_version", (char*) &show_ssl_get_version, SHOW_FUNC}, + {"Ssl_accept_renegotiates", (char*) &show_ssl_ctx_sess_accept_renegotiate, SHOW_SIMPLE_FUNC}, + {"Ssl_accepts", (char*) &show_ssl_ctx_sess_accept, SHOW_SIMPLE_FUNC}, + {"Ssl_callback_cache_hits", (char*) &show_ssl_ctx_sess_cb_hits, SHOW_SIMPLE_FUNC}, + {"Ssl_cipher", (char*) &show_ssl_get_cipher, SHOW_SIMPLE_FUNC}, + {"Ssl_cipher_list", (char*) &show_ssl_get_cipher_list, SHOW_SIMPLE_FUNC}, + {"Ssl_client_connects", (char*) &show_ssl_ctx_sess_connect, SHOW_SIMPLE_FUNC}, + {"Ssl_connect_renegotiates", (char*) &show_ssl_ctx_sess_connect_renegotiate, SHOW_SIMPLE_FUNC}, + {"Ssl_ctx_verify_depth", (char*) &show_ssl_ctx_get_verify_depth, SHOW_SIMPLE_FUNC}, + {"Ssl_ctx_verify_mode", (char*) &show_ssl_ctx_get_verify_mode, SHOW_SIMPLE_FUNC}, + {"Ssl_default_timeout", (char*) &show_ssl_get_default_timeout, SHOW_SIMPLE_FUNC}, + {"Ssl_finished_accepts", (char*) &show_ssl_ctx_sess_accept_good, SHOW_SIMPLE_FUNC}, + {"Ssl_finished_connects", (char*) &show_ssl_ctx_sess_connect_good, SHOW_SIMPLE_FUNC}, + {"Ssl_server_not_after", (char*) &show_ssl_get_server_not_after, SHOW_SIMPLE_FUNC}, + {"Ssl_server_not_before", (char*) &show_ssl_get_server_not_before, SHOW_SIMPLE_FUNC}, + {"Ssl_session_cache_hits", (char*) &show_ssl_ctx_sess_hits, SHOW_SIMPLE_FUNC}, + {"Ssl_session_cache_misses", (char*) &show_ssl_ctx_sess_misses, SHOW_SIMPLE_FUNC}, + {"Ssl_session_cache_mode", (char*) &show_ssl_ctx_get_session_cache_mode, SHOW_SIMPLE_FUNC}, + {"Ssl_session_cache_overflows", (char*) &show_ssl_ctx_sess_cache_full, SHOW_SIMPLE_FUNC}, + {"Ssl_session_cache_size", (char*) &show_ssl_ctx_sess_get_cache_size, SHOW_SIMPLE_FUNC}, + {"Ssl_session_cache_timeouts", (char*) &show_ssl_ctx_sess_timeouts, SHOW_SIMPLE_FUNC}, + {"Ssl_sessions_reused", (char*) &show_ssl_session_reused, SHOW_SIMPLE_FUNC}, + {"Ssl_used_session_cache_entries",(char*) &show_ssl_ctx_sess_number, SHOW_SIMPLE_FUNC}, + {"Ssl_verify_depth", (char*) &show_ssl_get_verify_depth, SHOW_SIMPLE_FUNC}, + {"Ssl_verify_mode", (char*) &show_ssl_get_verify_mode, SHOW_SIMPLE_FUNC}, + {"Ssl_version", (char*) &show_ssl_get_version, SHOW_SIMPLE_FUNC}, #endif #endif /* HAVE_OPENSSL */ {"Syncs", (char*) &my_sync_count, SHOW_LONG_NOFLUSH}, @@ -7123,26 +8493,39 @@ SHOW_VAR status_vars[]= { {"Tc_log_page_waits", (char*) &tc_log_page_waits, SHOW_LONG}, #endif #ifdef HAVE_POOL_OF_THREADS - {"Threadpool_idle_threads", (char *) &show_threadpool_idle_threads, SHOW_FUNC}, + {"Threadpool_idle_threads", (char *) &show_threadpool_idle_threads, SHOW_SIMPLE_FUNC}, {"Threadpool_threads", (char *) &tp_stats.num_worker_threads, SHOW_INT}, #endif {"Threads_cached", (char*) &cached_thread_count, SHOW_LONG_NOFLUSH}, {"Threads_connected", (char*) &connection_count, SHOW_INT}, {"Threads_created", (char*) &thread_created, SHOW_LONG_NOFLUSH}, {"Threads_running", (char*) &thread_running, SHOW_INT}, - {"Uptime", (char*) &show_starttime, SHOW_FUNC}, + {"Update_scan", (char*) offsetof(STATUS_VAR, update_scan_count), SHOW_LONG_STATUS}, + {"Uptime", (char*) &show_starttime, SHOW_SIMPLE_FUNC}, #ifdef ENABLED_PROFILING - {"Uptime_since_flush_status",(char*) &show_flushstatustime, SHOW_FUNC}, + {"Uptime_since_flush_status",(char*) &show_flushstatustime, SHOW_SIMPLE_FUNC}, +#endif +#ifdef WITH_WSREP + {"wsrep", (char*) &wsrep_show_status, SHOW_FUNC}, #endif {NullS, NullS, SHOW_LONG} }; -bool add_terminator(DYNAMIC_ARRAY *options) +static bool add_terminator(DYNAMIC_ARRAY *options) { my_option empty_element= {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}; return insert_dynamic(options, (uchar *)&empty_element); } +static bool add_many_options(DYNAMIC_ARRAY *options, my_option *list, + size_t elements) +{ + for (my_option *opt= list; opt < list + elements; opt++) + if (insert_dynamic(options, opt)) + return 1; + return 0; +} + #ifndef EMBEDDED_LIBRARY static void print_version(void) { @@ -7174,22 +8557,30 @@ static int option_cmp(my_option *a, my_option *b) return 1; } } - DBUG_ASSERT(a->name == b->name); return 0; } static void print_help() { MEM_ROOT mem_root; - init_alloc_root(&mem_root, 4096, 4096); + init_alloc_root(&mem_root, 4096, 4096, MYF(0)); pop_dynamic(&all_options); + add_many_options(&all_options, pfs_early_options, + array_elements(pfs_early_options)); sys_var_add_options(&all_options, sys_var::PARSE_EARLY); add_plugin_options(&all_options, &mem_root); sort_dynamic(&all_options, (qsort_cmp) option_cmp); + sort_dynamic(&all_options, (qsort_cmp) option_cmp); add_terminator(&all_options); my_print_help((my_option*) all_options.buffer); + + /* Add variables that must be shown but not changed, like version numbers */ + pop_dynamic(&all_options); + sys_var_add_options(&all_options, sys_var::GETOPT_ONLY_HELP); + sort_dynamic(&all_options, (qsort_cmp) option_cmp); + add_terminator(&all_options); my_print_variables((my_option*) all_options.buffer); free_root(&mem_root, MYF(0)); @@ -7213,16 +8604,15 @@ static void usage(void) else { #ifdef __WIN__ - puts("NT and Win32 specific options:\n\ - --install Install the default service (NT).\n\ - --install-manual Install the default service started manually (NT).\n\ - --install service_name Install an optional service (NT).\n\ - --install-manual service_name Install an optional service started manually (NT).\n\ - --remove Remove the default service from the service list (NT).\n\ - --remove service_name Remove the service_name from the service list (NT).\n\ - --enable-named-pipe Only to be used for the default server (NT).\n\ - --standalone Dummy option to start as a standalone server (NT).\ -"); + puts("NT and Win32 specific options:\n" + " --install Install the default service (NT).\n" + " --install-manual Install the default service started manually (NT).\n" + " --install service_name Install an optional service (NT).\n" + " --install-manual service_name Install an optional service started manually (NT).\n" + " --remove Remove the default service from the service list (NT).\n" + " --remove service_name Remove the service_name from the service list (NT).\n" + " --enable-named-pipe Only to be used for the default server (NT).\n" + " --standalone Dummy option to start as a standalone server (NT)."); puts(""); #endif print_defaults(MYSQL_CONFIG_NAME,load_default_groups); @@ -7234,14 +8624,12 @@ static void usage(void) if (! plugins_are_initialized) { - puts("\n\ -Plugins have parameters that are not reflected in this list\n\ -because execution stopped before plugins were initialized."); + puts("\nPlugins have parameters that are not reflected in this list" + "\nbecause execution stopped before plugins were initialized."); } - puts("\n\ -To see what values a running MySQL server is using, type\n\ -'mysqladmin variables' instead of 'mysqld --verbose --help'."); + puts("\nTo see what values a running MySQL server is using, type" + "\n'mysqladmin variables' instead of 'mysqld --verbose --help'."); } DBUG_VOID_RETURN; } @@ -7269,7 +8657,7 @@ static int mysql_init_variables(void) /* We can only test for sub paths if my_symlink.c is using realpath */ mysys_test_invalid_symlink= path_starts_from_data_home_dir; #endif - opt_log= opt_slow_log= 0; + opt_log= 0; opt_bin_log= opt_bin_log_used= 0; opt_disable_networking= opt_skip_show_db=0; opt_skip_name_resolve= 0; @@ -7279,12 +8667,14 @@ static int mysql_init_variables(void) opt_tc_log_file= (char *)"tc.log"; // no hostname in tc_log file name ! opt_secure_auth= 0; opt_bootstrap= opt_myisam_log= 0; + disable_log_notes= 0; mqh_used= 0; kill_in_progress= 0; cleanup_done= 0; server_id_supplied= 0; test_flags= select_errors= dropping_tables= ha_open_options=0; - thread_count= thread_running= kill_cached_threads= wake_thread=0; + thread_count= thread_running= kill_cached_threads= wake_thread= 0; + service_thread_count= 0; slave_open_temp_tables= 0; cached_thread_count= 0; opt_endinfo= using_udf_functions= 0; @@ -7302,7 +8692,9 @@ static int mysql_init_variables(void) prepared_stmt_count= 0; mysqld_unix_port= opt_mysql_tmpdir= my_bind_addr_str= NullS; bzero((uchar*) &mysql_tmpdir_list, sizeof(mysql_tmpdir_list)); - bzero((char *) &global_status_var, sizeof(global_status_var)); + /* Clear all except global_memory_used */ + bzero((char*) &global_status_var, offsetof(STATUS_VAR, + last_cleared_system_status_var)); opt_large_pages= 0; opt_super_large_pages= 0; #if defined(ENABLED_DEBUG_SYNC) @@ -7318,17 +8710,14 @@ static int mysql_init_variables(void) character_set_filesystem= &my_charset_bin; opt_specialflag= SPECIAL_ENGLISH; - unix_sock= base_ip_sock= extra_ip_sock= INVALID_SOCKET; + unix_sock= base_ip_sock= extra_ip_sock= MYSQL_INVALID_SOCKET; mysql_home_ptr= mysql_home; - pidfile_name_ptr= pidfile_name; log_error_file_ptr= log_error_file; protocol_version= PROTOCOL_VERSION; what_to_log= ~ (1L << (uint) COM_TIME); - refresh_version= 2L; /* Increments on each reload. 0 and 1 are reserved */ + denied_connections= 0; executed_events= 0; global_query_id= thread_id= 1L; - my_atomic_rwlock_init(&global_query_id_lock); - my_atomic_rwlock_init(&thread_running_lock); strnmov(server_version, MYSQL_SERVER_VERSION, sizeof(server_version)-1); threads.empty(); thread_cache.empty(); @@ -7352,6 +8741,9 @@ static int mysql_init_variables(void) relay_log_info_file= (char*) "relay-log.info"; report_user= report_password = report_host= 0; /* TO BE DELETED */ opt_relay_logname= opt_relaylog_index_name= 0; + slave_retried_transactions= 0; + log_bin_basename= NULL; + log_bin_index= NULL; /* Variables in libraries */ charsets_dir= 0; @@ -7376,8 +8768,13 @@ static int mysql_init_variables(void) #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) have_ssl=SHOW_OPTION_YES; +#if HAVE_YASSL + have_openssl= SHOW_OPTION_NO; #else - have_ssl=SHOW_OPTION_NO; + have_openssl= SHOW_OPTION_YES; +#endif +#else + have_openssl= have_ssl= SHOW_OPTION_NO; #endif #ifdef HAVE_BROKEN_REALPATH have_symlink=SHOW_OPTION_NO; @@ -7450,15 +8847,29 @@ static int mysql_init_variables(void) if (!(tmpenv = getenv("MY_BASEDIR_VERSION"))) tmpenv = DEFAULT_MYSQL_HOME; strmake_buf(mysql_home, tmpenv); + set_sys_var_value_origin(&mysql_home_ptr, sys_var::ENV); #endif + + if (wsrep_init_vars()) + return 1; + return 0; } my_bool -mysqld_get_one_option(int optid, - const struct my_option *opt __attribute__((unused)), - char *argument) +mysqld_get_one_option(int optid, const struct my_option *opt, char *argument) { + if (opt->app_type) + { + sys_var *var= (sys_var*) opt->app_type; + if (argument == autoset_my_option) + { + var->value_origin= sys_var::AUTO; + return 0; + } + var->value_origin= sys_var::CONFIG; + } + switch(optid) { case '#': #ifndef DBUG_OFF @@ -7473,6 +8884,7 @@ mysqld_get_one_option(int optid, if (argument[0] == '1' && !argument[1]) break; DBUG_SET_INITIAL(argument); + current_dbug_option= argument; opt_endinfo=1; /* unireg: memory allocation */ #else sql_print_warning("'%s' is disabled in this build", opt->name); @@ -7480,12 +8892,20 @@ mysqld_get_one_option(int optid, break; case OPT_DEPRECATED_OPTION: sql_print_warning("'%s' is deprecated. It does nothing and exists only " - "for compatiblity with old my.cnf files.", + "for compatibility with old my.cnf files.", opt->name); break; + case OPT_MYSQL_COMPATIBILITY: + sql_print_warning("'%s' is MySQL 5.6 compatible option. Not used or needed " + "in MariaDB.", opt->name); + break; + case OPT_MYSQL_TO_BE_IMPLEMENTED: + sql_print_warning("'%s' is MySQL 5.6 compatible option. To be implemented " + "in later versions.", opt->name); + break; case 'a': - global_system_variables.sql_mode= MODE_ANSI; - global_system_variables.tx_isolation= ISO_SERIALIZABLE; + SYSVAR_AUTOSIZE(global_system_variables.sql_mode, MODE_ANSI); + SYSVAR_AUTOSIZE(global_system_variables.tx_isolation, ISO_SERIALIZABLE); break; case 'b': strmake_buf(mysql_home, argument); @@ -7494,10 +8914,6 @@ mysqld_get_one_option(int optid, if (default_collation_name == compiled_default_collation_name) default_collation_name= 0; break; - case 'l': - WARN_DEPRECATED(NULL, 7, 0, "--log", "'--general-log'/'--general-log-file'"); - opt_log=1; - break; case 'h': strmake_buf(mysql_real_data_home, argument); /* Correct pointer set by my_getopt (for embedded library) */ @@ -7541,46 +8957,64 @@ mysqld_get_one_option(int optid, opt_myisam_log=1; break; case (int) OPT_BIN_LOG: - opt_bin_log= test(argument != disabled_my_option); + opt_bin_log= MY_TEST(argument != disabled_my_option); opt_bin_log_used= 1; break; case (int) OPT_LOG_BASENAME: { if (opt_log_basename[0] == 0 || strchr(opt_log_basename, FN_EXTCHAR) || - strchr(opt_log_basename,FN_LIBCHAR)) + strchr(opt_log_basename,FN_LIBCHAR) || + !is_filename_allowed(opt_log_basename, strlen(opt_log_basename), FALSE)) { - sql_print_error("Wrong argument for --log-basename. It can't be empty or contain '.' or '" FN_DIRSEP "'"); + sql_print_error("Wrong argument for --log-basename. It can't be empty or contain '.' or '" FN_DIRSEP "'. It must be valid filename."); return 1; } if (log_error_file_ptr != disabled_my_option) - log_error_file_ptr= opt_log_basename; + SYSVAR_AUTOSIZE(log_error_file_ptr, opt_log_basename); + /* General log file */ make_default_log_name(&opt_logname, ".log", false); + /* Slow query log file */ make_default_log_name(&opt_slow_logname, "-slow.log", false); + /* Binary log file */ make_default_log_name(&opt_bin_logname, "-bin", true); + /* Binary log index file */ make_default_log_name(&opt_binlog_index_name, "-bin.index", true); + set_sys_var_value_origin(&opt_logname, sys_var::AUTO); + set_sys_var_value_origin(&opt_slow_logname, sys_var::AUTO); + if (!opt_logname || !opt_slow_logname || !opt_bin_logname || + !opt_binlog_index_name) + return 1; + +#ifdef HAVE_REPLICATION + /* Relay log file */ make_default_log_name(&opt_relay_logname, "-relay-bin", true); + /* Relay log index file */ make_default_log_name(&opt_relaylog_index_name, "-relay-bin.index", true); + set_sys_var_value_origin(&opt_relay_logname, sys_var::AUTO); + if (!opt_relay_logname || !opt_relaylog_index_name) + return 1; +#endif - pidfile_name_ptr= pidfile_name; + SYSVAR_AUTOSIZE(pidfile_name_ptr, pidfile_name); + /* PID file */ strmake(pidfile_name, argument, sizeof(pidfile_name)-5); strmov(fn_ext(pidfile_name),".pid"); /* check for errors */ - if (!opt_bin_logname || !opt_relaylog_index_name || ! opt_logname || - ! opt_slow_logname || !pidfile_name_ptr) + if (!pidfile_name_ptr) return 1; // out of memory error break; } #ifdef HAVE_REPLICATION case (int)OPT_REPLICATE_IGNORE_DB: { - rpl_filter->add_ignore_db(argument); + cur_rpl_filter->add_ignore_db(argument); break; } case (int)OPT_REPLICATE_DO_DB: { - rpl_filter->add_do_db(argument); + cur_rpl_filter->add_do_db(argument); break; } case (int)OPT_REPLICATE_REWRITE_DB: @@ -7612,7 +9046,14 @@ mysqld_get_one_option(int optid, return 1; } - rpl_filter->add_db_rewrite(key, val); + cur_rpl_filter->add_db_rewrite(key, val); + break; + } + case (int)OPT_SLAVE_PARALLEL_MODE: + { + /* Store latest mode for Master::Info */ + cur_rpl_filter->set_parallel_mode + ((enum_slave_parallel_mode)opt_slave_parallel_mode); break; } @@ -7628,7 +9069,7 @@ mysqld_get_one_option(int optid, } case (int)OPT_REPLICATE_DO_TABLE: { - if (rpl_filter->add_do_table(argument)) + if (cur_rpl_filter->add_do_table(argument)) { sql_print_error("Could not add do table rule '%s'!\n", argument); return 1; @@ -7637,7 +9078,7 @@ mysqld_get_one_option(int optid, } case (int)OPT_REPLICATE_WILD_DO_TABLE: { - if (rpl_filter->add_wild_do_table(argument)) + if (cur_rpl_filter->add_wild_do_table(argument)) { sql_print_error("Could not add do table rule '%s'!\n", argument); return 1; @@ -7646,7 +9087,7 @@ mysqld_get_one_option(int optid, } case (int)OPT_REPLICATE_WILD_IGNORE_TABLE: { - if (rpl_filter->add_wild_ignore_table(argument)) + if (cur_rpl_filter->add_wild_ignore_table(argument)) { sql_print_error("Could not add ignore table rule '%s'!\n", argument); return 1; @@ -7655,7 +9096,7 @@ mysqld_get_one_option(int optid, } case (int)OPT_REPLICATE_IGNORE_TABLE: { - if (rpl_filter->add_ignore_table(argument)) + if (cur_rpl_filter->add_ignore_table(argument)) { sql_print_error("Could not add ignore table rule '%s'!\n", argument); return 1; @@ -7663,27 +9104,17 @@ mysqld_get_one_option(int optid, break; } #endif /* HAVE_REPLICATION */ - case (int) OPT_SLOW_QUERY_LOG: - WARN_DEPRECATED(NULL, 7, 0, "--log-slow-queries", "'--slow-query-log'/'--slow-query-log-file'"); - opt_slow_log= 1; - break; case (int) OPT_SAFE: opt_specialflag|= SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC; - delay_key_write_options= (uint) DELAY_KEY_WRITE_NONE; - myisam_recover_options= HA_RECOVER_DEFAULT; + SYSVAR_AUTOSIZE(delay_key_write_options, (uint) DELAY_KEY_WRITE_NONE); + SYSVAR_AUTOSIZE(myisam_recover_options, HA_RECOVER_DEFAULT); ha_open_options&= ~(HA_OPEN_DELAY_KEY_WRITE); #ifdef HAVE_QUERY_CACHE - query_cache_size=0; + SYSVAR_AUTOSIZE(query_cache_size, 0); #endif sql_print_warning("The syntax '--safe-mode' is deprecated and will be " "removed in a future release."); break; - case (int) OPT_SKIP_PRIOR: - opt_specialflag|= SPECIAL_NO_PRIOR; - sql_print_warning("The --skip-thread-priority startup option is deprecated " - "and will be removed in MySQL 7.0. This option has no effect " - "as the implied behavior is already the default."); - break; case (int) OPT_SKIP_HOST_CACHE: opt_specialflag|= SPECIAL_NO_HOST_CACHE; break; @@ -7703,9 +9134,7 @@ mysqld_get_one_option(int optid, break; case OPT_SERVER_ID: server_id_supplied = 1; - break; - case OPT_ONE_THREAD: - thread_handling= SCHEDULER_NO_THREADS; + ::server_id= global_system_variables.server_id; break; case OPT_LOWER_CASE_TABLE_NAMES: lower_case_table_names_used= 1; @@ -7726,18 +9155,6 @@ mysqld_get_one_option(int optid, } break; #endif /* defined(ENABLED_DEBUG_SYNC) */ - case OPT_ENGINE_CONDITION_PUSHDOWN: - /* - The last of --engine-condition-pushdown and --optimizer_switch on - command line wins (see get_options(). - */ - if (global_system_variables.engine_condition_pushdown) - global_system_variables.optimizer_switch|= - OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN; - else - global_system_variables.optimizer_switch&= - ~OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN; - break; case OPT_LOG_ERROR: /* "No --log-error" == "write errors to stderr", @@ -7746,11 +9163,6 @@ mysqld_get_one_option(int optid, if (argument == NULL) /* no argument */ log_error_file_ptr= const_cast<char*>(""); break; - case OPT_MAX_LONG_DATA_SIZE: - max_long_data_size_used= true; - break; - - case OPT_IGNORE_DB_DIRECTORY: opt_ignore_db_dirs= NULL; // will be set in ignore_db_dirs_process_additions if (*argument == 0) @@ -7766,6 +9178,117 @@ mysqld_get_one_option(int optid, } } break; + case OPT_PLUGIN_LOAD: + free_list(opt_plugin_load_list_ptr); + /* fall through */ + case OPT_PLUGIN_LOAD_ADD: + opt_plugin_load_list_ptr->push_back(new i_string(argument)); + break; + case OPT_MAX_LONG_DATA_SIZE: + max_long_data_size_used= true; + break; + case OPT_PFS_INSTRUMENT: + { +#ifdef WITH_PERFSCHEMA_STORAGE_ENGINE +#ifndef EMBEDDED_LIBRARY + /* Parse instrument name and value from argument string */ + char* name = argument,*p, *val; + + /* Assignment required */ + if (!(p= strchr(argument, '='))) + { + my_getopt_error_reporter(WARNING_LEVEL, + "Missing value for performance_schema_instrument " + "'%s'", argument); + return 0; + } + + /* Option value */ + val= p + 1; + if (!*val) + { + my_getopt_error_reporter(WARNING_LEVEL, + "Missing value for performance_schema_instrument " + "'%s'", argument); + return 0; + } + + /* Trim leading spaces from instrument name */ + while (*name && my_isspace(mysqld_charset, *name)) + name++; + + /* Trim trailing spaces and slashes from instrument name */ + while (p > argument && (my_isspace(mysqld_charset, p[-1]) || p[-1] == '/')) + p--; + *p= 0; + + if (!*name) + { + my_getopt_error_reporter(WARNING_LEVEL, + "Invalid instrument name for " + "performance_schema_instrument '%s'", argument); + return 0; + } + + /* Trim leading spaces from option value */ + while (*val && my_isspace(mysqld_charset, *val)) + val++; + + /* Trim trailing spaces from option value */ + if ((p= my_strchr(mysqld_charset, val, val+strlen(val), ' ')) != NULL) + *p= 0; + + if (!*val) + { + my_getopt_error_reporter(WARNING_LEVEL, + "Invalid value for performance_schema_instrument " + "'%s'", argument); + return 0; + } + + /* Add instrument name and value to array of configuration options */ + if (add_pfs_instr_to_array(name, val)) + { + my_getopt_error_reporter(WARNING_LEVEL, + "Invalid value for performance_schema_instrument " + "'%s'", argument); + return 0; + } +#endif /* EMBEDDED_LIBRARY */ +#endif + break; + } +#ifdef WITH_WSREP + case OPT_WSREP_CAUSAL_READS: + { + if (global_system_variables.wsrep_causal_reads) + { + WSREP_WARN("option --wsrep-causal-reads is deprecated"); + if (!(global_system_variables.wsrep_sync_wait & WSREP_SYNC_WAIT_BEFORE_READ)) + { + WSREP_WARN("--wsrep-causal-reads=ON takes precedence over --wsrep-sync-wait=%u. " + "WSREP_SYNC_WAIT_BEFORE_READ is on", + global_system_variables.wsrep_sync_wait); + global_system_variables.wsrep_sync_wait |= WSREP_SYNC_WAIT_BEFORE_READ; + } + } + else + { + if (global_system_variables.wsrep_sync_wait & WSREP_SYNC_WAIT_BEFORE_READ) { + WSREP_WARN("--wsrep-sync-wait=%u takes precedence over --wsrep-causal-reads=OFF. " + "WSREP_SYNC_WAIT_BEFORE_READ is on", + global_system_variables.wsrep_sync_wait); + global_system_variables.wsrep_causal_reads = 1; + } + } + break; + } + case OPT_WSREP_SYNC_WAIT: + global_system_variables.wsrep_causal_reads= + MY_TEST(global_system_variables.wsrep_sync_wait & + WSREP_SYNC_WAIT_BEFORE_READ); + break; +#endif /* WITH_WSREP */ } return 0; } @@ -7776,7 +9299,7 @@ mysqld_get_one_option(int optid, C_MODE_START static void* -mysql_getopt_value(const char *keyname, uint key_length, +mysql_getopt_value(const char *name, uint length, const struct my_option *option, int *error) { if (error) @@ -7787,9 +9310,10 @@ mysql_getopt_value(const char *keyname, uint key_length, case OPT_KEY_CACHE_DIVISION_LIMIT: case OPT_KEY_CACHE_AGE_THRESHOLD: case OPT_KEY_CACHE_PARTITIONS: + case OPT_KEY_CACHE_CHANGED_BLOCKS_HASH_SIZE: { KEY_CACHE *key_cache; - if (!(key_cache= get_or_create_key_cache(keyname, key_length))) + if (!(key_cache= get_or_create_key_cache(name, length))) { if (error) *error= EXIT_OUT_OF_MEMORY; @@ -7806,8 +9330,40 @@ mysql_getopt_value(const char *keyname, uint key_length, return &key_cache->param_age_threshold; case OPT_KEY_CACHE_PARTITIONS: return (uchar**) &key_cache->param_partitions; + case OPT_KEY_CACHE_CHANGED_BLOCKS_HASH_SIZE: + return (uchar**) &key_cache->changed_blocks_hash_size; + } + } + /* We return in all cases above. Let us silence -Wimplicit-fallthrough */ + DBUG_ASSERT(0); +#ifdef HAVE_REPLICATION + /* fall through */ + case OPT_REPLICATE_DO_DB: + case OPT_REPLICATE_DO_TABLE: + case OPT_REPLICATE_IGNORE_DB: + case OPT_REPLICATE_IGNORE_TABLE: + case OPT_REPLICATE_WILD_DO_TABLE: + case OPT_REPLICATE_WILD_IGNORE_TABLE: + case OPT_REPLICATE_REWRITE_DB: + case OPT_SLAVE_PARALLEL_MODE: + { + /* Store current filter for mysqld_get_one_option() */ + if (!(cur_rpl_filter= get_or_create_rpl_filter(name, length))) + { + if (error) + *error= EXIT_OUT_OF_MEMORY; + } + if (option->id == OPT_SLAVE_PARALLEL_MODE) + { + /* + Ensure parallel_mode variable is shown in --help. The other + variables are not easily printable here. + */ + return (char**) &opt_slave_parallel_mode; } + return 0; } +#endif } return option->value; } @@ -7848,11 +9404,8 @@ static int get_options(int *argc_ptr, char ***argv_ptr) /* prepare all_options array */ my_init_dynamic_array(&all_options, sizeof(my_option), array_elements(my_long_options), - array_elements(my_long_options)/4); - for (my_option *opt= my_long_options; - opt < my_long_options + array_elements(my_long_options) - 1; - opt++) - insert_dynamic(&all_options, (uchar*) opt); + array_elements(my_long_options)/4, MYF(0)); + add_many_options(&all_options, my_long_options, array_elements(my_long_options)); sys_var_add_options(&all_options, 0); add_terminator(&all_options); @@ -7872,6 +9425,8 @@ static int get_options(int *argc_ptr, char ***argv_ptr) (*argc_ptr)++; (*argv_ptr)--; + disable_log_notes= opt_silent_startup; + /* Options have been parsed. Now some of them need additional special handling, like custom value checking, checking of incompatibilites @@ -7881,7 +9436,7 @@ static int get_options(int *argc_ptr, char ***argv_ptr) if ((opt_log_slow_admin_statements || opt_log_queries_not_using_indexes || opt_log_slow_slave_statements) && - !opt_slow_log) + !global_system_variables.sql_log_slow) sql_print_warning("options --log-slow-admin-statements, --log-queries-not-using-indexes and --log-slow-slave-statements have no effect if --log_slow_queries is not set"); if (global_system_variables.net_buffer_length > global_system_variables.max_allowed_packet) @@ -7892,6 +9447,17 @@ static int get_options(int *argc_ptr, char ***argv_ptr) global_system_variables.max_allowed_packet); } +#if MYSQL_VERSION_ID > 101001 + /* + TIMESTAMP columns get implicit DEFAULT values when + --explicit_defaults_for_timestamp is not set. + */ + if (!opt_help && !opt_explicit_defaults_for_timestamp) + sql_print_warning("TIMESTAMP with implicit DEFAULT value is deprecated. " + "Please use --explicit_defaults_for_timestamp server " + "option (see documentation for more details)."); +#endif + if (log_error_file_ptr != disabled_my_option) opt_error_log= 1; else @@ -7929,6 +9495,16 @@ static int get_options(int *argc_ptr, char ***argv_ptr) else global_system_variables.option_bits&= ~OPTION_BIG_SELECTS; + if (!opt_bootstrap && WSREP_PROVIDER_EXISTS && WSREP_ON && + global_system_variables.binlog_format != BINLOG_FORMAT_ROW) + { + + WSREP_ERROR ("Only binlog_format = 'ROW' is currently supported. " + "Configured value: '%s'. Please adjust your configuration.", + binlog_format_names[global_system_variables.binlog_format]); + return 1; + } + // Synchronize @@global.autocommit on --autocommit const ulonglong turn_bit_on= opt_autocommit ? OPTION_AUTOCOMMIT : OPTION_NOT_AUTOCOMMIT; @@ -7938,7 +9514,7 @@ static int get_options(int *argc_ptr, char ***argv_ptr) global_system_variables.sql_mode= expand_sql_mode(global_system_variables.sql_mode); -#if defined(HAVE_BROKEN_REALPATH) +#if !defined(HAVE_REALPATH) || defined(HAVE_BROKEN_REALPATH) my_use_symdir=0; my_disable_symlinks=1; have_symlink=SHOW_OPTION_NO; @@ -7963,7 +9539,7 @@ static int get_options(int *argc_ptr, char ***argv_ptr) if (mysqld_chroot) set_root(mysqld_chroot); #else - thread_handling = SCHEDULER_NO_THREADS; + SYSVAR_AUTOSIZE(thread_handling, SCHEDULER_NO_THREADS); max_allowed_packet= global_system_variables.max_allowed_packet; net_buffer_length= global_system_variables.net_buffer_length; #endif @@ -7974,7 +9550,7 @@ static int get_options(int *argc_ptr, char ***argv_ptr) Set some global variables from the global_system_variables In most cases the global variables will not be used */ - my_disable_locking= myisam_single_user= test(opt_external_locking == 0); + my_disable_locking= myisam_single_user= MY_TEST(opt_external_locking == 0); my_default_record_cache_size=global_system_variables.read_buff_size; /* @@ -7991,7 +9567,9 @@ static int get_options(int *argc_ptr, char ***argv_ptr) debug_assert_if_crashed_table= 1; global_system_variables.long_query_time= (ulonglong) - (global_system_variables.long_query_time_double * 1e6); + (global_system_variables.long_query_time_double * 1e6 + 0.1); + global_system_variables.max_statement_time= (ulonglong) + (global_system_variables.max_statement_time_double * 1e6 + 0.1); if (opt_short_log_format) opt_specialflag|= SPECIAL_SHORT_LOG_FORMAT; @@ -8013,7 +9591,7 @@ static int get_options(int *argc_ptr, char ***argv_ptr) /* workaround: disable thread pool on XP */ if (GetProcAddress(GetModuleHandle("kernel32"),"CreateThreadpool") == 0 && thread_handling > SCHEDULER_NO_THREADS) - thread_handling = SCHEDULER_ONE_THREAD_PER_CONNECTION; + SYSVAR_AUTOSIZE(thread_handling, SCHEDULER_ONE_THREAD_PER_CONNECTION); #endif if (thread_handling <= SCHEDULER_ONE_THREAD_PER_CONNECTION) @@ -8030,10 +9608,6 @@ static int get_options(int *argc_ptr, char ***argv_ptr) &extra_connection_count); #endif - global_system_variables.engine_condition_pushdown= - test(global_system_variables.optimizer_switch & - OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN); - opt_readonly= read_only; /* @@ -8041,10 +9615,43 @@ static int get_options(int *argc_ptr, char ***argv_ptr) value of max_allowed_packet. */ if (!max_long_data_size_used) - max_long_data_size= global_system_variables.max_allowed_packet; + SYSVAR_AUTOSIZE(max_long_data_size, + global_system_variables.max_allowed_packet); - /* Rember if max_user_connections was 0 at startup */ + /* Remember if max_user_connections was 0 at startup */ max_user_connections_checking= global_system_variables.max_user_connections != 0; + +#ifdef HAVE_REPLICATION + { + sys_var *max_relay_log_size_var, *max_binlog_size_var; + /* If max_relay_log_size is 0, then set it to max_binlog_size */ + if (!global_system_variables.max_relay_log_size) + SYSVAR_AUTOSIZE(global_system_variables.max_relay_log_size, + max_binlog_size); + + /* + Fix so that DEFAULT and limit checking works with max_relay_log_size + (Yes, this is a hack, but it's required as the definition of + max_relay_log_size allows it to be set to 0). + */ + max_relay_log_size_var= intern_find_sys_var(STRING_WITH_LEN("max_relay_log_size")); + max_binlog_size_var= intern_find_sys_var(STRING_WITH_LEN("max_binlog_size")); + if (max_binlog_size_var && max_relay_log_size_var) + { + max_relay_log_size_var->option.min_value= + max_binlog_size_var->option.min_value; + max_relay_log_size_var->option.def_value= + max_binlog_size_var->option.def_value; + } + } +#endif + + /* Ensure that some variables are not set higher than needed */ + if (back_log > max_connections) + SYSVAR_AUTOSIZE(back_log, max_connections); + if (thread_cache_size > max_connections) + SYSVAR_AUTOSIZE(thread_cache_size, max_connections); + return 0; } @@ -8069,7 +9676,7 @@ void set_server_version(void) if (!strstr(MYSQL_SERVER_SUFFIX_STR, "-debug")) end= strnmov(end, "-debug", (version_end-end)); #endif - if (opt_log || opt_slow_log || opt_bin_log) + if (opt_log || global_system_variables.sql_log_slow || opt_bin_log) strnmov(end, "-log", (version_end-end)); // This may slow down system *end= 0; } @@ -8211,13 +9818,18 @@ static int fix_paths(void) /* If --character-sets-dir isn't given, use shared library dir */ if (charsets_dir) + { strmake_buf(mysql_charsets_dir, charsets_dir); + charsets_dir= mysql_charsets_dir; + } else + { strxnmov(mysql_charsets_dir, sizeof(mysql_charsets_dir)-1, buff, CHARSET_DIR, NullS); + SYSVAR_AUTOSIZE(charsets_dir, mysql_charsets_dir); + } (void) my_load_path(mysql_charsets_dir, mysql_charsets_dir, buff); convert_dirname(mysql_charsets_dir, mysql_charsets_dir, NullS); - charsets_dir=mysql_charsets_dir; if (init_tmpdir(&mysql_tmpdir_list, opt_mysql_tmpdir)) DBUG_RETURN(1); @@ -8225,7 +9837,7 @@ static int fix_paths(void) opt_mysql_tmpdir= mysql_tmpdir; #ifdef HAVE_REPLICATION if (!slave_load_tmpdir) - slave_load_tmpdir= mysql_tmpdir; + SYSVAR_AUTOSIZE(slave_load_tmpdir, mysql_tmpdir); #endif /* HAVE_REPLICATION */ /* Convert the secure-file-priv option to system format, allowing @@ -8274,9 +9886,9 @@ static int test_if_case_insensitive(const char *dir_name) MY_STAT stat_info; DBUG_ENTER("test_if_case_insensitive"); - fn_format(buff, glob_hostname, dir_name, ".lower-test", + fn_format(buff, opt_log_basename, dir_name, ".lower-test", MY_UNPACK_FILENAME | MY_REPLACE_EXT | MY_REPLACE_DIR); - fn_format(buff2, glob_hostname, dir_name, ".LOWER-TEST", + fn_format(buff2, opt_log_basename, dir_name, ".LOWER-TEST", MY_UNPACK_FILENAME | MY_REPLACE_EXT | MY_REPLACE_DIR); mysql_file_delete(key_file_casetest, buff2, MYF(0)); if ((file= mysql_file_create(key_file_casetest, @@ -8352,12 +9964,17 @@ void refresh_status(THD *thd) add_to_status(&global_status_var, &thd->status_var); /* Reset thread's status variables */ - bzero((uchar*) &thd->status_var, sizeof(thd->status_var)); + thd->set_status_var_init(); + thd->status_var.global_memory_used= 0; bzero((uchar*) &thd->org_status_var, sizeof(thd->org_status_var)); thd->start_bytes_received= 0; /* Reset some global variables */ reset_status_vars(); +#ifdef WITH_WSREP + if (WSREP_ON) + wsrep->stats_reset(wsrep); +#endif /* WITH_WSREP */ /* Reset the counters of all key caches (default and named). */ process_key_caches(reset_key_cache_counters, 0); @@ -8366,31 +9983,395 @@ void refresh_status(THD *thd) /* Set max_used_connections to the number of currently open - connections. Lock LOCK_thread_count out of LOCK_status to avoid - deadlocks. Status reset becomes not atomic, but status data is - not exact anyway. + connections. This is not perfect, but status data is not exact anyway. */ - mysql_mutex_lock(&LOCK_thread_count); - max_used_connections= thread_count-delayed_insert_threads; - mysql_mutex_unlock(&LOCK_thread_count); + max_used_connections= connection_count + extra_connection_count; } +#ifdef HAVE_PSI_INTERFACE +static PSI_file_info all_server_files[]= +{ +#ifdef HAVE_MMAP + { &key_file_map, "map", 0}, +#endif /* HAVE_MMAP */ + { &key_file_binlog, "binlog", 0}, + { &key_file_binlog_index, "binlog_index", 0}, + { &key_file_relaylog, "relaylog", 0}, + { &key_file_relaylog_index, "relaylog_index", 0}, + { &key_file_casetest, "casetest", 0}, + { &key_file_dbopt, "dbopt", 0}, + { &key_file_des_key_file, "des_key_file", 0}, + { &key_file_ERRMSG, "ERRMSG", 0}, + { &key_select_to_file, "select_to_file", 0}, + { &key_file_fileparser, "file_parser", 0}, + { &key_file_frm, "FRM", 0}, + { &key_file_global_ddl_log, "global_ddl_log", 0}, + { &key_file_load, "load", 0}, + { &key_file_loadfile, "LOAD_FILE", 0}, + { &key_file_log_event_data, "log_event_data", 0}, + { &key_file_log_event_info, "log_event_info", 0}, + { &key_file_master_info, "master_info", 0}, + { &key_file_misc, "misc", 0}, + { &key_file_partition, "partition", 0}, + { &key_file_pid, "pid", 0}, + { &key_file_query_log, "query_log", 0}, + { &key_file_relay_log_info, "relay_log_info", 0}, + { &key_file_send_file, "send_file", 0}, + { &key_file_slow_log, "slow_log", 0}, + { &key_file_tclog, "tclog", 0}, + { &key_file_trg, "trigger_name", 0}, + { &key_file_trn, "trigger", 0}, + { &key_file_init, "init", 0}, + { &key_file_binlog_state, "binlog_state", 0} +}; +#endif /* HAVE_PSI_INTERFACE */ -/***************************************************************************** - Instantiate variables for missing storage engines - This section should go away soon -*****************************************************************************/ +PSI_stage_info stage_after_apply_event= { 0, "after apply log event", 0}; +PSI_stage_info stage_after_create= { 0, "After create", 0}; +PSI_stage_info stage_after_opening_tables= { 0, "After opening tables", 0}; +PSI_stage_info stage_after_table_lock= { 0, "After table lock", 0}; +PSI_stage_info stage_allocating_local_table= { 0, "allocating local table", 0}; +PSI_stage_info stage_alter_inplace_prepare= { 0, "preparing for alter table", 0}; +PSI_stage_info stage_alter_inplace= { 0, "altering table", 0}; +PSI_stage_info stage_alter_inplace_commit= { 0, "committing alter table to storage engine", 0}; +PSI_stage_info stage_apply_event= { 0, "apply log event", 0}; +PSI_stage_info stage_changing_master= { 0, "Changing master", 0}; +PSI_stage_info stage_checking_master_version= { 0, "Checking master version", 0}; +PSI_stage_info stage_checking_permissions= { 0, "checking permissions", 0}; +PSI_stage_info stage_checking_privileges_on_cached_query= { 0, "checking privileges on cached query", 0}; +PSI_stage_info stage_checking_query_cache_for_query= { 0, "checking query cache for query", 0}; +PSI_stage_info stage_cleaning_up= { 0, "cleaning up", 0}; +PSI_stage_info stage_closing_tables= { 0, "closing tables", 0}; +PSI_stage_info stage_connecting_to_master= { 0, "Connecting to master", 0}; +PSI_stage_info stage_converting_heap_to_myisam= { 0, "converting HEAP to " TMP_ENGINE_NAME, 0}; +PSI_stage_info stage_copying_to_group_table= { 0, "Copying to group table", 0}; +PSI_stage_info stage_copying_to_tmp_table= { 0, "Copying to tmp table", 0}; +PSI_stage_info stage_copy_to_tmp_table= { 0, "copy to tmp table", 0}; +PSI_stage_info stage_creating_delayed_handler= { 0, "Creating delayed handler", 0}; +PSI_stage_info stage_creating_sort_index= { 0, "Creating sort index", 0}; +PSI_stage_info stage_creating_table= { 0, "creating table", 0}; +PSI_stage_info stage_creating_tmp_table= { 0, "Creating tmp table", 0}; +PSI_stage_info stage_deleting_from_main_table= { 0, "deleting from main table", 0}; +PSI_stage_info stage_deleting_from_reference_tables= { 0, "deleting from reference tables", 0}; +PSI_stage_info stage_discard_or_import_tablespace= { 0, "discard_or_import_tablespace", 0}; +PSI_stage_info stage_enabling_keys= { 0, "enabling keys", 0}; +PSI_stage_info stage_end= { 0, "end", 0}; +PSI_stage_info stage_executing= { 0, "executing", 0}; +PSI_stage_info stage_execution_of_init_command= { 0, "Execution of init_command", 0}; +PSI_stage_info stage_explaining= { 0, "explaining", 0}; +PSI_stage_info stage_finding_key_cache= { 0, "Finding key cache", 0}; +PSI_stage_info stage_finished_reading_one_binlog_switching_to_next_binlog= { 0, "Finished reading one binlog; switching to next binlog", 0}; +PSI_stage_info stage_flushing_relay_log_and_master_info_repository= { 0, "Flushing relay log and master info repository.", 0}; +PSI_stage_info stage_flushing_relay_log_info_file= { 0, "Flushing relay-log info file.", 0}; +PSI_stage_info stage_freeing_items= { 0, "freeing items", 0}; +PSI_stage_info stage_fulltext_initialization= { 0, "FULLTEXT initialization", 0}; +PSI_stage_info stage_got_handler_lock= { 0, "got handler lock", 0}; +PSI_stage_info stage_got_old_table= { 0, "got old table", 0}; +PSI_stage_info stage_init= { 0, "init", 0}; +PSI_stage_info stage_insert= { 0, "insert", 0}; +PSI_stage_info stage_invalidating_query_cache_entries_table= { 0, "invalidating query cache entries (table)", 0}; +PSI_stage_info stage_invalidating_query_cache_entries_table_list= { 0, "invalidating query cache entries (table list)", 0}; +PSI_stage_info stage_killing_slave= { 0, "Killing slave", 0}; +PSI_stage_info stage_logging_slow_query= { 0, "logging slow query", 0}; +PSI_stage_info stage_making_temp_file_append_before_load_data= { 0, "Making temporary file (append) before replaying LOAD DATA INFILE.", 0}; +PSI_stage_info stage_making_temp_file_create_before_load_data= { 0, "Making temporary file (create) before replaying LOAD DATA INFILE.", 0}; +PSI_stage_info stage_manage_keys= { 0, "manage keys", 0}; +PSI_stage_info stage_master_has_sent_all_binlog_to_slave= { 0, "Master has sent all binlog to slave; waiting for binlog to be updated", 0}; +PSI_stage_info stage_opening_tables= { 0, "Opening tables", 0}; +PSI_stage_info stage_optimizing= { 0, "optimizing", 0}; +PSI_stage_info stage_preparing= { 0, "preparing", 0}; +PSI_stage_info stage_purging_old_relay_logs= { 0, "Purging old relay logs", 0}; +PSI_stage_info stage_query_end= { 0, "query end", 0}; +PSI_stage_info stage_queueing_master_event_to_the_relay_log= { 0, "Queueing master event to the relay log", 0}; +PSI_stage_info stage_reading_event_from_the_relay_log= { 0, "Reading event from the relay log", 0}; +PSI_stage_info stage_recreating_table= { 0, "recreating table", 0}; +PSI_stage_info stage_registering_slave_on_master= { 0, "Registering slave on master", 0}; +PSI_stage_info stage_removing_duplicates= { 0, "Removing duplicates", 0}; +PSI_stage_info stage_removing_tmp_table= { 0, "removing tmp table", 0}; +PSI_stage_info stage_rename= { 0, "rename", 0}; +PSI_stage_info stage_rename_result_table= { 0, "rename result table", 0}; +PSI_stage_info stage_requesting_binlog_dump= { 0, "Requesting binlog dump", 0}; +PSI_stage_info stage_reschedule= { 0, "reschedule", 0}; +PSI_stage_info stage_searching_rows_for_update= { 0, "Searching rows for update", 0}; +PSI_stage_info stage_sending_binlog_event_to_slave= { 0, "Sending binlog event to slave", 0}; +PSI_stage_info stage_sending_cached_result_to_client= { 0, "sending cached result to client", 0}; +PSI_stage_info stage_sending_data= { 0, "Sending data", 0}; +PSI_stage_info stage_setup= { 0, "setup", 0}; +PSI_stage_info stage_show_explain= { 0, "show explain", 0}; +PSI_stage_info stage_slave_has_read_all_relay_log= { 0, "Slave has read all relay log; waiting for the slave I/O thread to update it", 0}; +PSI_stage_info stage_sorting= { 0, "Sorting", 0}; +PSI_stage_info stage_sorting_for_group= { 0, "Sorting for group", 0}; +PSI_stage_info stage_sorting_for_order= { 0, "Sorting for order", 0}; +PSI_stage_info stage_sorting_result= { 0, "Sorting result", 0}; +PSI_stage_info stage_statistics= { 0, "statistics", 0}; +PSI_stage_info stage_sql_thd_waiting_until_delay= { 0, "Waiting until MASTER_DELAY seconds after master executed event", 0 }; +PSI_stage_info stage_storing_result_in_query_cache= { 0, "storing result in query cache", 0}; +PSI_stage_info stage_storing_row_into_queue= { 0, "storing row into queue", 0}; +PSI_stage_info stage_system_lock= { 0, "System lock", 0}; +PSI_stage_info stage_unlocking_tables= { 0, "Unlocking tables", 0}; +PSI_stage_info stage_table_lock= { 0, "Table lock", 0}; +PSI_stage_info stage_filling_schema_table= { 0, "Filling schema table", 0}; +PSI_stage_info stage_update= { 0, "update", 0}; +PSI_stage_info stage_updating= { 0, "updating", 0}; +PSI_stage_info stage_updating_main_table= { 0, "updating main table", 0}; +PSI_stage_info stage_updating_reference_tables= { 0, "updating reference tables", 0}; +PSI_stage_info stage_upgrading_lock= { 0, "upgrading lock", 0}; +PSI_stage_info stage_user_lock= { 0, "User lock", 0}; +PSI_stage_info stage_user_sleep= { 0, "User sleep", 0}; +PSI_stage_info stage_verifying_table= { 0, "verifying table", 0}; +PSI_stage_info stage_waiting_for_delay_list= { 0, "waiting for delay_list", 0}; +PSI_stage_info stage_waiting_for_gtid_to_be_written_to_binary_log= { 0, "waiting for GTID to be written to binary log", 0}; +PSI_stage_info stage_waiting_for_handler_insert= { 0, "waiting for handler insert", 0}; +PSI_stage_info stage_waiting_for_handler_lock= { 0, "waiting for handler lock", 0}; +PSI_stage_info stage_waiting_for_handler_open= { 0, "waiting for handler open", 0}; +PSI_stage_info stage_waiting_for_insert= { 0, "Waiting for INSERT", 0}; +PSI_stage_info stage_waiting_for_master_to_send_event= { 0, "Waiting for master to send event", 0}; +PSI_stage_info stage_waiting_for_master_update= { 0, "Waiting for master update", 0}; +PSI_stage_info stage_waiting_for_relay_log_space= { 0, "Waiting for the slave SQL thread to free enough relay log space", 0}; +PSI_stage_info stage_waiting_for_slave_mutex_on_exit= { 0, "Waiting for slave mutex on exit", 0}; +PSI_stage_info stage_waiting_for_slave_thread_to_start= { 0, "Waiting for slave thread to start", 0}; +PSI_stage_info stage_waiting_for_table_flush= { 0, "Waiting for table flush", 0}; +PSI_stage_info stage_waiting_for_query_cache_lock= { 0, "Waiting for query cache lock", 0}; +PSI_stage_info stage_waiting_for_the_next_event_in_relay_log= { 0, "Waiting for the next event in relay log", 0}; +PSI_stage_info stage_waiting_for_the_slave_thread_to_advance_position= { 0, "Waiting for the slave SQL thread to advance position", 0}; +PSI_stage_info stage_waiting_to_finalize_termination= { 0, "Waiting to finalize termination", 0}; +PSI_stage_info stage_waiting_to_get_readlock= { 0, "Waiting to get readlock", 0}; +PSI_stage_info stage_binlog_waiting_background_tasks= { 0, "Waiting for background binlog tasks", 0}; +PSI_stage_info stage_binlog_processing_checkpoint_notify= { 0, "Processing binlog checkpoint notification", 0}; +PSI_stage_info stage_binlog_stopping_background_thread= { 0, "Stopping binlog background thread", 0}; +PSI_stage_info stage_waiting_for_work_from_sql_thread= { 0, "Waiting for work from SQL thread", 0}; +PSI_stage_info stage_waiting_for_prior_transaction_to_commit= { 0, "Waiting for prior transaction to commit", 0}; +PSI_stage_info stage_waiting_for_prior_transaction_to_start_commit= { 0, "Waiting for prior transaction to start commit before starting next transaction", 0}; +PSI_stage_info stage_waiting_for_room_in_worker_thread= { 0, "Waiting for room in worker thread event queue", 0}; +PSI_stage_info stage_waiting_for_workers_idle= { 0, "Waiting for worker threads to be idle", 0}; +PSI_stage_info stage_waiting_for_ftwrl= { 0, "Waiting due to global read lock", 0}; +PSI_stage_info stage_waiting_for_ftwrl_threads_to_pause= { 0, "Waiting for worker threads to pause for global read lock", 0}; +PSI_stage_info stage_waiting_for_rpl_thread_pool= { 0, "Waiting while replication worker thread pool is busy", 0}; +PSI_stage_info stage_master_gtid_wait_primary= { 0, "Waiting in MASTER_GTID_WAIT() (primary waiter)", 0}; +PSI_stage_info stage_master_gtid_wait= { 0, "Waiting in MASTER_GTID_WAIT()", 0}; +PSI_stage_info stage_gtid_wait_other_connection= { 0, "Waiting for other master connection to process GTID received on multiple master connections", 0}; +PSI_stage_info stage_slave_background_process_request= { 0, "Processing requests", 0}; +PSI_stage_info stage_slave_background_wait_request= { 0, "Waiting for requests", 0}; +PSI_stage_info stage_waiting_for_deadlock_kill= { 0, "Waiting for parallel replication deadlock handling to complete", 0}; -/***************************************************************************** - Instantiate templates -*****************************************************************************/ +#ifdef HAVE_PSI_INTERFACE -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -/* Used templates */ -template class I_List<THD>; -template class I_List_iterator<THD>; -template class I_List<i_string>; -template class I_List<i_string_pair>; -template class I_List<Statement>; -template class I_List_iterator<Statement>; +PSI_stage_info *all_server_stages[]= +{ + & stage_after_apply_event, + & stage_after_create, + & stage_after_opening_tables, + & stage_after_table_lock, + & stage_allocating_local_table, + & stage_alter_inplace, + & stage_alter_inplace_commit, + & stage_alter_inplace_prepare, + & stage_apply_event, + & stage_binlog_processing_checkpoint_notify, + & stage_binlog_stopping_background_thread, + & stage_binlog_waiting_background_tasks, + & stage_changing_master, + & stage_checking_master_version, + & stage_checking_permissions, + & stage_checking_privileges_on_cached_query, + & stage_checking_query_cache_for_query, + & stage_cleaning_up, + & stage_closing_tables, + & stage_connecting_to_master, + & stage_converting_heap_to_myisam, + & stage_copy_to_tmp_table, + & stage_copying_to_group_table, + & stage_copying_to_tmp_table, + & stage_creating_delayed_handler, + & stage_creating_sort_index, + & stage_creating_table, + & stage_creating_tmp_table, + & stage_deleting_from_main_table, + & stage_deleting_from_reference_tables, + & stage_discard_or_import_tablespace, + & stage_enabling_keys, + & stage_end, + & stage_executing, + & stage_execution_of_init_command, + & stage_explaining, + & stage_finding_key_cache, + & stage_finished_reading_one_binlog_switching_to_next_binlog, + & stage_flushing_relay_log_and_master_info_repository, + & stage_flushing_relay_log_info_file, + & stage_freeing_items, + & stage_fulltext_initialization, + & stage_got_handler_lock, + & stage_got_old_table, + & stage_init, + & stage_insert, + & stage_invalidating_query_cache_entries_table, + & stage_invalidating_query_cache_entries_table_list, + & stage_killing_slave, + & stage_logging_slow_query, + & stage_making_temp_file_append_before_load_data, + & stage_making_temp_file_create_before_load_data, + & stage_manage_keys, + & stage_master_has_sent_all_binlog_to_slave, + & stage_opening_tables, + & stage_optimizing, + & stage_preparing, + & stage_purging_old_relay_logs, + & stage_query_end, + & stage_queueing_master_event_to_the_relay_log, + & stage_reading_event_from_the_relay_log, + & stage_recreating_table, + & stage_registering_slave_on_master, + & stage_removing_duplicates, + & stage_removing_tmp_table, + & stage_rename, + & stage_rename_result_table, + & stage_requesting_binlog_dump, + & stage_reschedule, + & stage_searching_rows_for_update, + & stage_sending_binlog_event_to_slave, + & stage_sending_cached_result_to_client, + & stage_sending_data, + & stage_setup, + & stage_show_explain, + & stage_slave_has_read_all_relay_log, + & stage_sorting, + & stage_sorting_for_group, + & stage_sorting_for_order, + & stage_sorting_result, + & stage_sql_thd_waiting_until_delay, + & stage_statistics, + & stage_storing_result_in_query_cache, + & stage_storing_row_into_queue, + & stage_system_lock, + & stage_unlocking_tables, + & stage_table_lock, + & stage_filling_schema_table, + & stage_update, + & stage_updating, + & stage_updating_main_table, + & stage_updating_reference_tables, + & stage_upgrading_lock, + & stage_user_lock, + & stage_user_sleep, + & stage_verifying_table, + & stage_waiting_for_delay_list, + & stage_waiting_for_gtid_to_be_written_to_binary_log, + & stage_waiting_for_handler_insert, + & stage_waiting_for_handler_lock, + & stage_waiting_for_handler_open, + & stage_waiting_for_insert, + & stage_waiting_for_master_to_send_event, + & stage_waiting_for_master_update, + & stage_waiting_for_prior_transaction_to_commit, + & stage_waiting_for_prior_transaction_to_start_commit, + & stage_waiting_for_query_cache_lock, + & stage_waiting_for_relay_log_space, + & stage_waiting_for_room_in_worker_thread, + & stage_waiting_for_slave_mutex_on_exit, + & stage_waiting_for_slave_thread_to_start, + & stage_waiting_for_table_flush, + & stage_waiting_for_the_next_event_in_relay_log, + & stage_waiting_for_the_slave_thread_to_advance_position, + & stage_waiting_for_work_from_sql_thread, + & stage_waiting_to_finalize_termination, + & stage_waiting_to_get_readlock, + & stage_master_gtid_wait_primary, + & stage_master_gtid_wait, + & stage_gtid_wait_other_connection, + & stage_slave_background_process_request, + & stage_slave_background_wait_request +}; + +PSI_socket_key key_socket_tcpip, key_socket_unix, key_socket_client_connection; + +static PSI_socket_info all_server_sockets[]= +{ + { &key_socket_tcpip, "server_tcpip_socket", PSI_FLAG_GLOBAL}, + { &key_socket_unix, "server_unix_socket", PSI_FLAG_GLOBAL}, + { &key_socket_client_connection, "client_connection", 0} +}; + +/** + Initialise all the performance schema instrumentation points + used by the server. +*/ +void init_server_psi_keys(void) +{ + const char* category= "sql"; + int count; + + count= array_elements(all_server_mutexes); + mysql_mutex_register(category, all_server_mutexes, count); + + count= array_elements(all_server_rwlocks); + mysql_rwlock_register(category, all_server_rwlocks, count); + + count= array_elements(all_server_conds); + mysql_cond_register(category, all_server_conds, count); + + count= array_elements(all_server_threads); + mysql_thread_register(category, all_server_threads, count); + + count= array_elements(all_server_files); + mysql_file_register(category, all_server_files, count); + + count= array_elements(all_server_stages); + mysql_stage_register(category, all_server_stages, count); + + count= array_elements(all_server_sockets); + mysql_socket_register(category, all_server_sockets, count); + +#ifdef HAVE_PSI_STATEMENT_INTERFACE + init_sql_statement_info(); + count= array_elements(sql_statement_info); + mysql_statement_register(category, sql_statement_info, count); + + category= "com"; + init_com_statement_info(); + + /* + Register [0 .. COM_QUERY - 1] as "statement/com/..." + */ + count= (int) COM_QUERY; + mysql_statement_register(category, com_statement_info, count); + + /* + Register [COM_QUERY + 1 .. COM_END] as "statement/com/..." + */ + count= (int) COM_END - (int) COM_QUERY; + mysql_statement_register(category, & com_statement_info[(int) COM_QUERY + 1], count); + + category= "abstract"; + /* + Register [COM_QUERY] as "statement/abstract/com_query" + */ + mysql_statement_register(category, & com_statement_info[(int) COM_QUERY], 1); + + /* + When a new packet is received, + it is instrumented as "statement/abstract/new_packet". + Based on the packet type found, it later mutates to the + proper narrow type, for example + "statement/abstract/query" or "statement/com/ping". + In cases of "statement/abstract/query", SQL queries are given to + the parser, which mutates the statement type to an even more + narrow classification, for example "statement/sql/select". + */ + stmt_info_new_packet.m_key= 0; + stmt_info_new_packet.m_name= "new_packet"; + stmt_info_new_packet.m_flags= PSI_FLAG_MUTABLE; + mysql_statement_register(category, &stmt_info_new_packet, 1); + + /* + Statements processed from the relay log are initially instrumented as + "statement/abstract/relay_log". The parser will mutate the statement type to + a more specific classification, for example "statement/sql/insert". + */ + stmt_info_rpl.m_key= 0; + stmt_info_rpl.m_name= "relay_log"; + stmt_info_rpl.m_flags= PSI_FLAG_MUTABLE; + mysql_statement_register(category, &stmt_info_rpl, 1); #endif +} + +#endif /* HAVE_PSI_INTERFACE */ diff --git a/sql/mysqld.h b/sql/mysqld.h index d0679a3fa82..af519622d97 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -21,9 +21,13 @@ #include "sql_bitmap.h" /* Bitmap */ #include "my_decimal.h" /* my_decimal */ #include "mysql_com.h" /* SERVER_VERSION_LENGTH */ -#include "my_atomic.h" /* my_atomic_rwlock_t */ +#include "my_atomic.h" #include "mysql/psi/mysql_file.h" /* MYSQL_FILE */ #include "sql_list.h" /* I_List */ +#include "sql_cmd.h" +#include <my_rnd.h> +#include "my_pthread.h" +#include "my_rdtsc.h" class THD; struct handlerton; @@ -36,6 +40,8 @@ typedef struct st_mysql_show_var SHOW_VAR; #if MAX_INDEXES <= 64 typedef Bitmap<64> key_map; /* Used for finding keys */ +#elif MAX_INDEXES > 128 +#error "MAX_INDEXES values greater than 128 is not supported." #else typedef Bitmap<((MAX_INDEXES+7)/8*8)> key_map; /* Used for finding keys */ #endif @@ -52,11 +58,32 @@ typedef Bitmap<((MAX_INDEXES+7)/8*8)> key_map; /* Used for finding keys */ #define TEST_SIGINT 1024 /**< Allow sigint on threads */ #define TEST_SYNCHRONIZATION 2048 /**< get server to do sleep in some places */ + +/* Keep things compatible */ +#define OPT_DEFAULT SHOW_OPT_DEFAULT +#define OPT_SESSION SHOW_OPT_SESSION +#define OPT_GLOBAL SHOW_OPT_GLOBAL + +extern MY_TIMER_INFO sys_timer_info; + +/* + Values for --slave-parallel-mode + Must match order in slave_parallel_mode_typelib in sys_vars.cc. +*/ +enum enum_slave_parallel_mode { + SLAVE_PARALLEL_NONE, + SLAVE_PARALLEL_MINIMAL, + SLAVE_PARALLEL_CONSERVATIVE, + SLAVE_PARALLEL_OPTIMISTIC, + SLAVE_PARALLEL_AGGRESSIVE +}; + /* Function prototypes */ void kill_mysql(void); void close_connection(THD *thd, uint sql_errno= 0); void handle_connection_in_main_thread(THD *thd); void create_thread_to_handle_connection(THD *thd); +void signal_thd_deleted(); void unlink_thd(THD *thd); bool one_thread_per_connection_end(THD *thd, bool put_in_cache); void flush_thread_cache(); @@ -78,7 +105,7 @@ extern CHARSET_INFO *character_set_filesystem; extern MY_BITMAP temp_pool; extern bool opt_large_files, server_id_supplied; extern bool opt_update_log, opt_bin_log, opt_error_log; -extern my_bool opt_log, opt_slow_log, opt_bootstrap; +extern my_bool opt_log, opt_bootstrap; extern my_bool opt_backup_history_log; extern my_bool opt_backup_progress_log; extern ulonglong log_output_options; @@ -90,30 +117,35 @@ extern bool opt_ignore_builtin_innodb; extern my_bool opt_character_set_client_handshake; extern bool volatile abort_loop; extern bool in_bootstrap; -extern uint volatile thread_count; extern uint connection_count; extern my_bool opt_safe_user_create; extern my_bool opt_safe_show_db, opt_local_infile, opt_myisam_use_mmap; extern my_bool opt_slave_compressed_protocol, use_temp_pool; -extern ulong slave_exec_mode_options; +extern ulong slave_exec_mode_options, slave_ddl_exec_mode_options; +extern ulong slave_retried_transactions; +extern ulong slave_run_triggers_for_rbr; extern ulonglong slave_type_conversions_options; extern my_bool read_only, opt_readonly; -extern my_bool lower_case_file_system; +extern MYSQL_PLUGIN_IMPORT my_bool lower_case_file_system; extern my_bool opt_enable_named_pipe, opt_sync_frm, opt_allow_suspicious_udfs; extern my_bool opt_secure_auth; +extern const char *current_dbug_option; extern char* opt_secure_file_priv; extern char* opt_secure_backup_file_priv; extern size_t opt_secure_backup_file_priv_len; extern my_bool opt_log_slow_admin_statements, opt_log_slow_slave_statements; extern my_bool sp_automatic_privileges, opt_noacl; +extern ulong use_stat_tables; extern my_bool opt_old_style_user_limits, trust_function_creators; extern uint opt_crash_binlog_innodb; -extern char *shared_memory_base_name, *mysqld_unix_port; +extern const char *shared_memory_base_name; +extern char *mysqld_unix_port; extern my_bool opt_enable_shared_memory; extern ulong opt_replicate_events_marked_for_skip; extern char *default_tz_name; extern Time_zone *default_tz; -extern char *default_storage_engine; +extern char *default_storage_engine, *default_tmp_storage_engine; +extern char *enforced_storage_engine; extern bool opt_endinfo, using_udf_functions; extern my_bool locked_in_memory; extern bool opt_using_transactions; @@ -127,17 +159,20 @@ extern ulong opt_tc_log_size, tc_log_max_pages_used, tc_log_page_size; extern ulong tc_log_page_waits; extern my_bool relay_log_purge, opt_innodb_safe_binlog, opt_innodb; extern my_bool relay_log_recovery; -extern uint test_flags,select_errors,ha_open_options; +extern uint select_errors,ha_open_options; +extern ulonglong test_flags; extern uint protocol_version, mysqld_port, dropping_tables; extern ulong delay_key_write_options; extern char *opt_logname, *opt_slow_logname, *opt_bin_logname, *opt_relay_logname; +extern char *opt_binlog_index_name; extern char *opt_backup_history_logname, *opt_backup_progress_logname, *opt_backup_settings_name; extern const char *log_output_str; extern const char *log_backup_output_str; extern char *mysql_home_ptr, *pidfile_name_ptr; -extern char glob_hostname[FN_REFLEN], mysql_home[FN_REFLEN]; +extern MYSQL_PLUGIN_IMPORT char glob_hostname[FN_REFLEN]; +extern char mysql_home[FN_REFLEN]; extern char pidfile_name[FN_REFLEN], system_time_zone[30], *opt_init_file; extern char default_logfile_name[FN_REFLEN]; extern char log_error_file[FN_REFLEN], *opt_tc_log_file; @@ -152,12 +187,13 @@ extern ulong delayed_insert_timeout; extern ulong delayed_insert_limit, delayed_queue_size; extern ulong delayed_insert_threads, delayed_insert_writes; extern ulong delayed_rows_in_use,delayed_insert_errors; -extern ulong slave_open_temp_tables; +extern int32 slave_open_temp_tables; extern ulonglong query_cache_size; +extern ulong query_cache_limit; extern ulong query_cache_min_res_unit; extern ulong slow_launch_threads, slow_launch_time; -extern ulong table_cache_size, table_def_size; extern MYSQL_PLUGIN_IMPORT ulong max_connections; +extern uint max_digest_length; extern ulong max_connect_errors, connect_timeout; extern my_bool slave_allow_batching; extern my_bool allow_slave_start; @@ -166,15 +202,22 @@ extern ulong slave_trans_retries; extern uint slave_net_timeout; extern int max_user_connections; extern ulong what_to_log,flush_time; -extern ulong max_prepared_stmt_count, prepared_stmt_count; +extern uint max_prepared_stmt_count, prepared_stmt_count; extern ulong open_files_limit; extern ulonglong binlog_cache_size, binlog_stmt_cache_size; extern ulonglong max_binlog_cache_size, max_binlog_stmt_cache_size; -extern ulong max_binlog_size, max_relay_log_size; +extern ulong max_binlog_size; extern ulong slave_max_allowed_packet; extern ulong opt_binlog_rows_event_max_size; extern ulong rpl_recovery_rank, thread_cache_size; extern ulong stored_program_cache_size; +extern ulong opt_slave_parallel_threads; +extern ulong opt_slave_domain_parallel_threads; +extern ulong opt_slave_parallel_max_queued; +extern ulong opt_slave_parallel_mode; +extern ulong opt_binlog_commit_wait_count; +extern ulong opt_binlog_commit_wait_usec; +extern my_bool opt_gtid_ignore_duplicates; extern ulong back_log; extern ulong executed_events; extern char language[FN_REFLEN]; @@ -199,7 +242,8 @@ extern handlerton *myisam_hton; extern handlerton *heap_hton; extern const char *load_default_groups[]; extern struct my_option my_long_options[]; -extern int mysqld_server_started; +int handle_early_options(); +extern int mysqld_server_started, mysqld_server_initialized; extern "C" MYSQL_PLUGIN_IMPORT int orig_argc; extern "C" MYSQL_PLUGIN_IMPORT char **orig_argv; extern pthread_attr_t connection_attrib; @@ -209,6 +253,17 @@ extern LEX_STRING opt_init_connect, opt_init_slave; extern int bootstrap_error; extern I_List<THD> threads; extern char err_shared_dir[]; +extern ulong connection_errors_select; +extern ulong connection_errors_accept; +extern ulong connection_errors_tcpwrap; +extern ulong connection_errors_internal; +extern ulong connection_errors_max_connection; +extern ulong connection_errors_peer_addr; +extern ulong log_warnings; +extern my_bool encrypt_binlog; +extern my_bool encrypt_tmp_disk_tables, encrypt_tmp_files; +extern ulong encryption_algorithm; +extern const char *encryption_algorithm_names[]; /* THR_MALLOC is a key which will be used to set/get MEM_ROOT** for a thread, @@ -219,35 +274,41 @@ extern pthread_key(MEM_ROOT**,THR_MALLOC); #ifdef HAVE_PSI_INTERFACE #ifdef HAVE_MMAP extern PSI_mutex_key key_PAGE_lock, key_LOCK_sync, key_LOCK_active, - key_LOCK_pool; + key_LOCK_pool, key_LOCK_pending_checkpoint; #endif /* HAVE_MMAP */ #ifdef HAVE_OPENSSL extern PSI_mutex_key key_LOCK_des_key_file; #endif -extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids, +extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_xid_list, + key_BINLOG_LOCK_binlog_background_thread, + m_key_LOCK_binlog_end_pos, key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi, key_LOCK_connection_count, key_LOCK_crypt, key_LOCK_delayed_create, key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log, key_LOCK_gdl, key_LOCK_global_system_variables, key_LOCK_logger, key_LOCK_manager, key_LOCK_prepared_stmt_count, - key_LOCK_rpl_status, key_LOCK_server_started, key_LOCK_status, - key_LOCK_table_share, key_LOCK_thd_data, + key_LOCK_rpl_status, key_LOCK_server_started, + key_LOCK_status, key_LOCK_show_status, + key_LOCK_thd_data, key_LOCK_thd_kill, key_LOCK_user_conn, key_LOG_LOCK_log, key_master_info_data_lock, key_master_info_run_lock, - key_master_info_sleep_lock, + key_master_info_sleep_lock, key_master_info_start_stop_lock, key_mutex_slave_reporting_capability_err_lock, key_relay_log_info_data_lock, key_relay_log_info_log_space_lock, key_relay_log_info_run_lock, - key_relay_log_info_sleep_lock, + key_rpl_group_info_sleep_lock, key_structure_guard_mutex, key_TABLE_SHARE_LOCK_ha_data, key_LOCK_error_messages, key_LOCK_thread_count, key_PARTITION_LOCK_auto_inc; extern PSI_mutex_key key_RELAYLOG_LOCK_index; +extern PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state, + key_LOCK_rpl_thread, key_LOCK_rpl_thread_pool, key_LOCK_parallel_entry; -extern PSI_mutex_key key_LOCK_stats, +extern PSI_mutex_key key_TABLE_SHARE_LOCK_share, key_LOCK_stats, key_LOCK_global_user_client_stats, key_LOCK_global_table_stats, - key_LOCK_global_index_stats, key_LOCK_wakeup_ready; + key_LOCK_global_index_stats, key_LOCK_wakeup_ready, key_LOCK_wait_commit; +extern PSI_mutex_key key_LOCK_gtid_waiting; extern PSI_rwlock_key key_rwlock_LOCK_grant, key_rwlock_LOCK_logger, key_rwlock_LOCK_sys_init_connect, key_rwlock_LOCK_sys_init_slave, @@ -257,7 +318,9 @@ extern PSI_rwlock_key key_rwlock_LOCK_grant, key_rwlock_LOCK_logger, extern PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool; #endif /* HAVE_MMAP */ -extern PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond, +extern PSI_cond_key key_BINLOG_COND_xid_list, key_BINLOG_update_cond, + key_BINLOG_COND_binlog_background_thread, + key_BINLOG_COND_binlog_background_thread_end, key_COND_cache_status_changed, key_COND_manager, key_COND_rpl_status, key_COND_server_started, key_delayed_insert_cond, key_delayed_insert_cond_client, @@ -266,16 +329,22 @@ extern PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond, key_master_info_sleep_cond, key_relay_log_info_data_cond, key_relay_log_info_log_space_cond, key_relay_log_info_start_cond, key_relay_log_info_stop_cond, - key_relay_log_info_sleep_cond, + key_rpl_group_info_sleep_cond, key_TABLE_SHARE_cond, key_user_level_lock_cond, key_COND_thread_count, key_COND_thread_cache, key_COND_flush_thread_cache; -extern PSI_cond_key key_RELAYLOG_update_cond, key_COND_wakeup_ready; +extern PSI_cond_key key_RELAYLOG_update_cond, key_COND_wakeup_ready, + key_COND_wait_commit; extern PSI_cond_key key_RELAYLOG_COND_queue_busy; extern PSI_cond_key key_TC_LOG_MMAP_COND_queue_busy; +extern PSI_cond_key key_COND_rpl_thread, key_COND_rpl_thread_queue, + key_COND_rpl_thread_stop, key_COND_rpl_thread_pool, + key_COND_parallel_entry, key_COND_group_commit_orderer; +extern PSI_cond_key key_COND_wait_gtid, key_COND_gtid_ignore_duplicates; extern PSI_thread_key key_thread_bootstrap, key_thread_delayed_insert, key_thread_handle_manager, key_thread_kill_server, key_thread_main, - key_thread_one_connection, key_thread_signal_hand; + key_thread_one_connection, key_thread_signal_hand, + key_thread_slave_background, key_rpl_parallel_thread; extern PSI_file_key key_file_binlog, key_file_binlog_index, key_file_casetest, key_file_dbopt, key_file_des_key_file, key_file_ERRMSG, key_select_to_file, @@ -286,10 +355,167 @@ extern PSI_file_key key_file_binlog, key_file_binlog_index, key_file_casetest, key_file_trg, key_file_trn, key_file_init; extern PSI_file_key key_file_query_log, key_file_slow_log; extern PSI_file_key key_file_relaylog, key_file_relaylog_index; +extern PSI_socket_key key_socket_tcpip, key_socket_unix, + key_socket_client_connection; +extern PSI_file_key key_file_binlog_state; void init_server_psi_keys(); #endif /* HAVE_PSI_INTERFACE */ +/* + MAINTAINER: Please keep this list in order, to limit merge collisions. + Hint: grep PSI_stage_info | sort -u +*/ +extern PSI_stage_info stage_apply_event; +extern PSI_stage_info stage_after_create; +extern PSI_stage_info stage_after_opening_tables; +extern PSI_stage_info stage_after_table_lock; +extern PSI_stage_info stage_allocating_local_table; +extern PSI_stage_info stage_alter_inplace_prepare; +extern PSI_stage_info stage_alter_inplace; +extern PSI_stage_info stage_alter_inplace_commit; +extern PSI_stage_info stage_after_apply_event; +extern PSI_stage_info stage_changing_master; +extern PSI_stage_info stage_checking_master_version; +extern PSI_stage_info stage_checking_permissions; +extern PSI_stage_info stage_checking_privileges_on_cached_query; +extern PSI_stage_info stage_checking_query_cache_for_query; +extern PSI_stage_info stage_cleaning_up; +extern PSI_stage_info stage_closing_tables; +extern PSI_stage_info stage_connecting_to_master; +extern PSI_stage_info stage_converting_heap_to_myisam; +extern PSI_stage_info stage_copying_to_group_table; +extern PSI_stage_info stage_copying_to_tmp_table; +extern PSI_stage_info stage_copy_to_tmp_table; +extern PSI_stage_info stage_creating_delayed_handler; +extern PSI_stage_info stage_creating_sort_index; +extern PSI_stage_info stage_creating_table; +extern PSI_stage_info stage_creating_tmp_table; +extern PSI_stage_info stage_deleting_from_main_table; +extern PSI_stage_info stage_deleting_from_reference_tables; +extern PSI_stage_info stage_discard_or_import_tablespace; +extern PSI_stage_info stage_end; +extern PSI_stage_info stage_enabling_keys; +extern PSI_stage_info stage_executing; +extern PSI_stage_info stage_execution_of_init_command; +extern PSI_stage_info stage_explaining; +extern PSI_stage_info stage_finding_key_cache; +extern PSI_stage_info stage_finished_reading_one_binlog_switching_to_next_binlog; +extern PSI_stage_info stage_flushing_relay_log_and_master_info_repository; +extern PSI_stage_info stage_flushing_relay_log_info_file; +extern PSI_stage_info stage_freeing_items; +extern PSI_stage_info stage_fulltext_initialization; +extern PSI_stage_info stage_got_handler_lock; +extern PSI_stage_info stage_got_old_table; +extern PSI_stage_info stage_init; +extern PSI_stage_info stage_insert; +extern PSI_stage_info stage_invalidating_query_cache_entries_table; +extern PSI_stage_info stage_invalidating_query_cache_entries_table_list; +extern PSI_stage_info stage_killing_slave; +extern PSI_stage_info stage_logging_slow_query; +extern PSI_stage_info stage_making_temp_file_append_before_load_data; +extern PSI_stage_info stage_making_temp_file_create_before_load_data; +extern PSI_stage_info stage_manage_keys; +extern PSI_stage_info stage_master_has_sent_all_binlog_to_slave; +extern PSI_stage_info stage_opening_tables; +extern PSI_stage_info stage_optimizing; +extern PSI_stage_info stage_preparing; +extern PSI_stage_info stage_purging_old_relay_logs; +extern PSI_stage_info stage_query_end; +extern PSI_stage_info stage_queueing_master_event_to_the_relay_log; +extern PSI_stage_info stage_reading_event_from_the_relay_log; +extern PSI_stage_info stage_recreating_table; +extern PSI_stage_info stage_registering_slave_on_master; +extern PSI_stage_info stage_removing_duplicates; +extern PSI_stage_info stage_removing_tmp_table; +extern PSI_stage_info stage_rename; +extern PSI_stage_info stage_rename_result_table; +extern PSI_stage_info stage_requesting_binlog_dump; +extern PSI_stage_info stage_reschedule; +extern PSI_stage_info stage_searching_rows_for_update; +extern PSI_stage_info stage_sending_binlog_event_to_slave; +extern PSI_stage_info stage_sending_cached_result_to_client; +extern PSI_stage_info stage_sending_data; +extern PSI_stage_info stage_setup; +extern PSI_stage_info stage_slave_has_read_all_relay_log; +extern PSI_stage_info stage_show_explain; +extern PSI_stage_info stage_sorting; +extern PSI_stage_info stage_sorting_for_group; +extern PSI_stage_info stage_sorting_for_order; +extern PSI_stage_info stage_sorting_result; +extern PSI_stage_info stage_sql_thd_waiting_until_delay; +extern PSI_stage_info stage_statistics; +extern PSI_stage_info stage_storing_result_in_query_cache; +extern PSI_stage_info stage_storing_row_into_queue; +extern PSI_stage_info stage_system_lock; +extern PSI_stage_info stage_unlocking_tables; +extern PSI_stage_info stage_table_lock; +extern PSI_stage_info stage_filling_schema_table; +extern PSI_stage_info stage_update; +extern PSI_stage_info stage_updating; +extern PSI_stage_info stage_updating_main_table; +extern PSI_stage_info stage_updating_reference_tables; +extern PSI_stage_info stage_upgrading_lock; +extern PSI_stage_info stage_user_lock; +extern PSI_stage_info stage_user_sleep; +extern PSI_stage_info stage_verifying_table; +extern PSI_stage_info stage_waiting_for_delay_list; +extern PSI_stage_info stage_waiting_for_gtid_to_be_written_to_binary_log; +extern PSI_stage_info stage_waiting_for_handler_insert; +extern PSI_stage_info stage_waiting_for_handler_lock; +extern PSI_stage_info stage_waiting_for_handler_open; +extern PSI_stage_info stage_waiting_for_insert; +extern PSI_stage_info stage_waiting_for_master_to_send_event; +extern PSI_stage_info stage_waiting_for_master_update; +extern PSI_stage_info stage_waiting_for_relay_log_space; +extern PSI_stage_info stage_waiting_for_slave_mutex_on_exit; +extern PSI_stage_info stage_waiting_for_slave_thread_to_start; +extern PSI_stage_info stage_waiting_for_query_cache_lock; +extern PSI_stage_info stage_waiting_for_table_flush; +extern PSI_stage_info stage_waiting_for_the_next_event_in_relay_log; +extern PSI_stage_info stage_waiting_for_the_slave_thread_to_advance_position; +extern PSI_stage_info stage_waiting_to_finalize_termination; +extern PSI_stage_info stage_waiting_to_get_readlock; +extern PSI_stage_info stage_binlog_waiting_background_tasks; +extern PSI_stage_info stage_binlog_processing_checkpoint_notify; +extern PSI_stage_info stage_binlog_stopping_background_thread; +extern PSI_stage_info stage_waiting_for_work_from_sql_thread; +extern PSI_stage_info stage_waiting_for_prior_transaction_to_commit; +extern PSI_stage_info stage_waiting_for_prior_transaction_to_start_commit; +extern PSI_stage_info stage_waiting_for_room_in_worker_thread; +extern PSI_stage_info stage_waiting_for_workers_idle; +extern PSI_stage_info stage_waiting_for_ftwrl; +extern PSI_stage_info stage_waiting_for_ftwrl_threads_to_pause; +extern PSI_stage_info stage_waiting_for_rpl_thread_pool; +extern PSI_stage_info stage_master_gtid_wait_primary; +extern PSI_stage_info stage_master_gtid_wait; +extern PSI_stage_info stage_gtid_wait_other_connection; +extern PSI_stage_info stage_slave_background_process_request; +extern PSI_stage_info stage_slave_background_wait_request; +extern PSI_stage_info stage_waiting_for_deadlock_kill; + +#ifdef HAVE_PSI_STATEMENT_INTERFACE +/** + Statement instrumentation keys (sql). + The last entry, at [SQLCOM_END], is for parsing errors. +*/ +extern PSI_statement_info sql_statement_info[(uint) SQLCOM_END + 1]; + +/** + Statement instrumentation keys (com). + The last entry, at [COM_END], is for packet errors. +*/ +extern PSI_statement_info com_statement_info[(uint) COM_END + 1]; + +/** + Statement instrumentation key for replication. +*/ +extern PSI_statement_info stmt_info_rpl; + +void init_sql_statement_info(); +void init_com_statement_info(); +#endif /* HAVE_PSI_STATEMENT_INTERFACE */ + #ifndef __WIN__ extern pthread_t signal_thread; #endif @@ -298,6 +524,8 @@ extern pthread_t signal_thread; extern struct st_VioSSLFd * ssl_acceptor_fd; #endif /* HAVE_OPENSSL */ +extern ulonglong my_pcre_frame_size; + /* The following variables were under INNODB_COMPABILITY_HOOKS */ @@ -329,12 +557,13 @@ extern MYSQL_PLUGIN_IMPORT key_map key_map_full; /* Should be threaded Server mutex locks and condition variables. */ extern mysql_mutex_t - LOCK_user_locks, LOCK_status, + LOCK_item_func_sleep, LOCK_status, LOCK_show_status, LOCK_error_log, LOCK_delayed_insert, LOCK_short_uuid_generator, LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_timezone, LOCK_slave_list, LOCK_active_mi, LOCK_manager, LOCK_global_system_variables, LOCK_user_conn, - LOCK_prepared_stmt_count, LOCK_error_messages, LOCK_connection_count; + LOCK_prepared_stmt_count, LOCK_error_messages, LOCK_connection_count, + LOCK_slave_background; extern MYSQL_PLUGIN_IMPORT mysql_mutex_t LOCK_thread_count; #ifdef HAVE_OPENSSL extern char* des_key_file; @@ -346,11 +575,12 @@ extern mysql_rwlock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave; extern mysql_rwlock_t LOCK_system_variables_hash; extern mysql_cond_t COND_thread_count; extern mysql_cond_t COND_manager; +extern mysql_cond_t COND_slave_background; extern int32 thread_running; -extern my_atomic_rwlock_t thread_running_lock; +extern int32 thread_count, service_thread_count; extern char *opt_ssl_ca, *opt_ssl_capath, *opt_ssl_cert, *opt_ssl_cipher, - *opt_ssl_key; + *opt_ssl_key, *opt_ssl_crl, *opt_ssl_crlpath; extern MYSQL_PLUGIN_IMPORT pthread_key(THD*, THR_THD); @@ -363,7 +593,6 @@ extern MYSQL_PLUGIN_IMPORT pthread_key(THD*, THR_THD); enum options_mysqld { OPT_to_set_the_start_number=256, - OPT_BIND_ADDRESS, OPT_BINLOG_DO_DB, OPT_BINLOG_FORMAT, OPT_BINLOG_IGNORE_DB, @@ -371,9 +600,7 @@ enum options_mysqld OPT_BOOTSTRAP, OPT_CONSOLE, OPT_DEBUG_SYNC_TIMEOUT, - OPT_DELAY_KEY_WRITE_ALL, OPT_DEPRECATED_OPTION, - OPT_ENGINE_CONDITION_PUSHDOWN, OPT_IGNORE_DB_DIRECTORY, OPT_ISAM_LOG, OPT_KEY_BUFFER_SIZE, @@ -381,12 +608,14 @@ enum options_mysqld OPT_KEY_CACHE_BLOCK_SIZE, OPT_KEY_CACHE_DIVISION_LIMIT, OPT_KEY_CACHE_PARTITIONS, + OPT_KEY_CACHE_CHANGED_BLOCKS_HASH_SIZE, OPT_LOG_BASENAME, OPT_LOG_ERROR, OPT_LOWER_CASE_TABLE_NAMES, OPT_MAX_LONG_DATA_SIZE, - OPT_ONE_THREAD, - OPT_POOL_OF_THREADS, + OPT_PLUGIN_LOAD, + OPT_PLUGIN_LOAD_ADD, + OPT_PFS_INSTRUMENT, OPT_REPLICATE_DO_DB, OPT_REPLICATE_DO_TABLE, OPT_REPLICATE_IGNORE_DB, @@ -396,21 +625,25 @@ enum options_mysqld OPT_REPLICATE_WILD_IGNORE_TABLE, OPT_SAFE, OPT_SERVER_ID, + OPT_SILENT, OPT_SKIP_HOST_CACHE, - OPT_SKIP_LOCK, - OPT_SKIP_PRIOR, OPT_SKIP_RESOLVE, - OPT_SKIP_STACK_TRACE, - OPT_SKIP_SYMLINKS, - OPT_SLOW_QUERY_LOG, + OPT_SLAVE_PARALLEL_MODE, OPT_SSL_CA, OPT_SSL_CAPATH, OPT_SSL_CERT, OPT_SSL_CIPHER, + OPT_SSL_CRL, + OPT_SSL_CRLPATH, OPT_SSL_KEY, OPT_THREAD_CONCURRENCY, - OPT_UPDATE_LOG, OPT_WANT_CORE, +#ifdef WITH_WSREP + OPT_WSREP_CAUSAL_READS, + OPT_WSREP_SYNC_WAIT, +#endif /* WITH_WSREP */ + OPT_MYSQL_COMPATIBILITY, + OPT_MYSQL_TO_BE_IMPLEMENTED, OPT_which_is_always_the_last }; #endif @@ -428,38 +661,58 @@ enum enum_query_type QT_WITHOUT_INTRODUCERS= (1 << 1), /// view internal representation (like QT_ORDINARY except ORDER BY clause) QT_VIEW_INTERNAL= (1 << 2), - /** - If an expression is constant, print the expression, not the value - it evaluates to. Should be used for error messages, so that they - don't reveal values. - */ + /// If identifiers should not include database names for the current database + QT_ITEM_IDENT_SKIP_CURRENT_DATABASE= (1 << 3), + /// If Item_cache_wrapper should not print <expr_cache> + QT_ITEM_CACHE_WRAPPER_SKIP_DETAILS= (1 << 4), + /// If Item_subselect should print as just "(subquery#1)" + /// rather than display the subquery body + QT_ITEM_SUBSELECT_ID_ONLY= (1 << 5), + /// If NULLIF(a,b) should print itself as + /// CASE WHEN a_for_comparison=b THEN NULL ELSE a_for_return_value END + /// when "a" was replaced to two different items + /// (e.g. by equal fields propagation in optimize_cond()) + /// or always as NULLIF(a, b). + /// The default behaviour is to use CASE syntax when + /// a_for_return_value is not the same as a_for_comparison. + /// SHOW CREATE {VIEW|PROCEDURE|FUNCTION} and other cases where the + /// original representation is required, should set this flag. + QT_ITEM_ORIGINAL_FUNC_NULLIF= (1 <<6), + + /// This value means focus on readability, not on ability to parse back, etc. + QT_EXPLAIN= QT_TO_SYSTEM_CHARSET | + QT_ITEM_IDENT_SKIP_CURRENT_DATABASE | + QT_ITEM_CACHE_WRAPPER_SKIP_DETAILS | + QT_ITEM_SUBSELECT_ID_ONLY, + + /// This is used for EXPLAIN EXTENDED extra warnings + /// Be more detailed than QT_EXPLAIN. + /// Perhaps we should eventually include QT_ITEM_IDENT_SKIP_CURRENT_DATABASE + /// here, as it would give better readable results + QT_EXPLAIN_EXTENDED= QT_TO_SYSTEM_CHARSET, + + // If an expression is constant, print the expression, not the value + // it evaluates to. Should be used for error messages, so that they + // don't reveal values. QT_NO_DATA_EXPANSION= (1 << 9), }; + /* query_id */ typedef int64 query_id_t; extern query_id_t global_query_id; -extern my_atomic_rwlock_t global_query_id_lock; void unireg_end(void) __attribute__((noreturn)); /* increment query_id and return it. */ inline __attribute__((warn_unused_result)) query_id_t next_query_id() { - query_id_t id; - my_atomic_rwlock_wrlock(&global_query_id_lock); - id= my_atomic_add64(&global_query_id, 1); - my_atomic_rwlock_wrunlock(&global_query_id_lock); - return (id+1); + return my_atomic_add64_explicit(&global_query_id, 1, MY_MEMORY_ORDER_RELAXED); } inline query_id_t get_query_id() { - query_id_t id; - my_atomic_rwlock_wrlock(&global_query_id_lock); - id= my_atomic_load64(&global_query_id); - my_atomic_rwlock_wrunlock(&global_query_id_lock); - return id; + return my_atomic_load64_explicit(&global_query_id, MY_MEMORY_ORDER_RELAXED); } @@ -480,61 +733,43 @@ inline void table_case_convert(char * name, uint length) name, length, name, length); } -inline ulong sql_rnd_with_mutex() +inline void thread_safe_increment32(int32 *value) { - mysql_mutex_lock(&LOCK_thread_count); - ulong tmp=(ulong) (my_rnd(&sql_rand) * 0xffffffff); /* make all bits random */ - mysql_mutex_unlock(&LOCK_thread_count); - return tmp; + (void) my_atomic_add32_explicit(value, 1, MY_MEMORY_ORDER_RELAXED); } -inline int32 -inc_thread_running() +inline void thread_safe_decrement32(int32 *value) { - int32 num_thread_running; - my_atomic_rwlock_wrlock(&thread_running_lock); - num_thread_running= my_atomic_add32(&thread_running, 1); - my_atomic_rwlock_wrunlock(&thread_running_lock); - return (num_thread_running+1); + (void) my_atomic_add32_explicit(value, -1, MY_MEMORY_ORDER_RELAXED); } -inline int32 -dec_thread_running() +inline void thread_safe_increment64(int64 *value) { - int32 num_thread_running; - my_atomic_rwlock_wrlock(&thread_running_lock); - num_thread_running= my_atomic_add32(&thread_running, -1); - my_atomic_rwlock_wrunlock(&thread_running_lock); - return (num_thread_running-1); + (void) my_atomic_add64_explicit(value, 1, MY_MEMORY_ORDER_RELAXED); } -inline int32 -get_thread_running() +inline void thread_safe_decrement64(int64 *value) { - int32 num_thread_running; - my_atomic_rwlock_wrlock(&thread_running_lock); - num_thread_running= my_atomic_load32(&thread_running); - my_atomic_rwlock_wrunlock(&thread_running_lock); - return num_thread_running; + (void) my_atomic_add64_explicit(value, -1, MY_MEMORY_ORDER_RELAXED); } -void set_server_version(void); +inline void inc_thread_running() +{ + thread_safe_increment32(&thread_running); +} -#if defined(MYSQL_DYNAMIC_PLUGIN) && defined(_WIN32) -extern "C" THD *_current_thd_noinline(); -#define _current_thd() _current_thd_noinline() -#else -/* - THR_THD is a key which will be used to set/get THD* for a thread, - using my_pthread_setspecific_ptr()/my_thread_getspecific_ptr(). -*/ -extern pthread_key(THD*, THR_THD); -inline THD *_current_thd(void) +inline void dec_thread_running() { - return my_pthread_getspecific_ptr(THD*,THR_THD); + thread_safe_decrement32(&thread_running); } -#endif + +void set_server_version(void); + #define current_thd _current_thd() +inline int set_current_thd(THD *thd) +{ + return my_pthread_setspecific_ptr(THR_THD, thd); +} /* @todo remove, make it static in ha_maria.cc @@ -543,6 +778,8 @@ inline THD *_current_thd(void) extern handlerton *maria_hton; extern uint extra_connection_count; +extern uint64 global_gtid_counter; +extern my_bool opt_gtid_strict_mode; extern my_bool opt_userstat_running, debug_assert_if_crashed_table; extern uint mysqld_extra_port; extern ulong opt_progress_report_time; @@ -552,16 +789,15 @@ extern ulong thread_created; extern scheduler_functions *thread_scheduler, *extra_thread_scheduler; extern char *opt_log_basename; extern my_bool opt_master_verify_checksum; -extern my_bool opt_stack_trace; +extern my_bool opt_stack_trace, disable_log_notes; extern my_bool opt_expect_abort; extern my_bool opt_slave_sql_verify_checksum; +extern my_bool opt_mysql56_temporal_format, strict_password_validation; +extern my_bool opt_explicit_defaults_for_timestamp; extern ulong binlog_checksum_options; extern bool max_user_connections_checking; extern ulong opt_binlog_dbug_fsync_sleep; -extern uint internal_tmp_table_max_key_length; -extern uint internal_tmp_table_max_key_segments; - extern uint volatile global_disable_checkpoint; extern my_bool opt_help; diff --git a/sql/net_serv.cc b/sql/net_serv.cc index ea66bb53394..6d200f55655 100644 --- a/sql/net_serv.cc +++ b/sql/net_serv.cc @@ -34,6 +34,7 @@ HFTODO this must be hidden if we don't want client capabilities in embedded library */ + #include <my_global.h> #include <mysql.h> #include <mysql_com.h> @@ -43,7 +44,6 @@ #include <my_net.h> #include <violite.h> #include <signal.h> -#include <errno.h> #include "probes_mysql.h" #ifdef EMBEDDED_LIBRARY @@ -58,9 +58,11 @@ #ifdef EXTRA_DEBUG #define EXTRA_DEBUG_fprintf fprintf #define EXTRA_DEBUG_fflush fflush +#define EXTRA_DEBUG_ASSERT DBUG_ASSERT #else static void inline EXTRA_DEBUG_fprintf(...) {} static int inline EXTRA_DEBUG_fflush(...) { return 0; } +#define EXTRA_DEBUG_ASSERT(X) do {} while(0) #endif #ifdef MYSQL_SERVER #define MYSQL_SERVER_my_error my_error @@ -104,30 +106,34 @@ extern uint test_flags; extern ulong bytes_sent, bytes_received, net_big_packet_count; #ifdef HAVE_QUERY_CACHE #define USE_QUERY_CACHE -extern void query_cache_insert(const char *packet, ulong length, +extern void query_cache_insert(void *thd, const char *packet, ulong length, unsigned pkt_nr); #endif // HAVE_QUERY_CACHE #define update_statistics(A) A +extern my_bool thd_net_is_killed(); +/* Additional instrumentation hooks for the server */ +#include "mysql_com_server.h" #else #define update_statistics(A) +#define thd_net_is_killed() 0 #endif #define TEST_BLOCKING 8 #define MAX_PACKET_LENGTH (256L*256L*256L-1) -static my_bool net_write_buff(NET *net,const uchar *packet,ulong len); - +static my_bool net_write_buff(NET *, const uchar *, ulong); /** Init with packet info. */ -my_bool my_net_init(NET *net, Vio* vio) +my_bool my_net_init(NET *net, Vio *vio, void *thd, uint my_flags) { DBUG_ENTER("my_net_init"); + DBUG_PRINT("enter", ("my_flags: %u", my_flags)); net->vio = vio; my_net_local_init(net); /* Set some limits */ if (!(net->buff=(uchar*) my_malloc((size_t) net->max_packet+ NET_HEADER_SIZE + COMP_HEADER_SIZE +1, - MYF(MY_WME)))) + MYF(MY_WME | my_flags)))) DBUG_RETURN(1); net->buff_end=net->buff+net->max_packet; net->error=0; net->return_status=0; @@ -138,11 +144,17 @@ my_bool my_net_init(NET *net, Vio* vio) net->where_b = net->remain_in_buf=0; net->net_skip_rest_factor= 0; net->last_errno=0; - net->unused= 0; + net->thread_specific_malloc= MY_TEST(my_flags & MY_THREAD_SPECIFIC); + net->thd= 0; +#ifdef MYSQL_SERVER + net->extension= NULL; + net->thd= thd; +#endif - if (vio != 0) /* If real connection */ + if (vio) { - net->fd = vio_fd(vio); /* For perl DBI/DBD */ + /* For perl DBI/DBD. */ + net->fd= vio_fd(vio); #if defined(MYSQL_SERVER) && !defined(__WIN__) if (!(test_flags & TEST_BLOCKING)) { @@ -193,7 +205,9 @@ my_bool net_realloc(NET *net, size_t length) */ if (!(buff= (uchar*) my_realloc((char*) net->buff, pkt_length + NET_HEADER_SIZE + COMP_HEADER_SIZE + 1, - MYF(MY_WME)))) + MYF(MY_WME | + (net->thread_specific_malloc ? + MY_THREAD_SPECIFIC : 0))))) { /* @todo: 1 and 2 codes are identical. */ net->error= 1; @@ -255,14 +269,17 @@ static int net_data_is_ready(my_socket sd) if ((res= select((int) (sd + 1), &sfds, NULL, NULL, &tv)) < 0) return 0; else - return test(res ? FD_ISSET(sd, &sfds) : 0); + return MY_TEST(res ? FD_ISSET(sd, &sfds) : 0); #endif /* HAVE_POLL */ } #endif /* EMBEDDED_LIBRARY */ /** - Intialize NET handler for new reads: + Clear (reinitialize) the NET structure for a new command. + + @remark Performs debug checking of the socket buffer to + ensure that the protocol sequence is correct. - Read from socket until there is nothing more to read. Discard what is read. @@ -295,7 +312,7 @@ void net_clear(NET *net, my_bool clear_buffer __attribute__((unused))) { size_t count; int ready; - while ((ready= net_data_is_ready(net->vio->sd)) > 0) + while ((ready= net_data_is_ready(vio_fd(net->vio))) > 0) { /* The socket is ready */ if ((long) (count= vio_read(net->vio, net->buff, @@ -345,9 +362,9 @@ my_bool net_flush(NET *net) DBUG_ENTER("net_flush"); if (net->buff != net->write_pos) { - error=test(net_real_write(net, net->buff, - (size_t) (net->write_pos - net->buff))); - net->write_pos=net->buff; + error= MY_TEST(net_real_write(net, net->buff, + (size_t) (net->write_pos - net->buff))); + net->write_pos= net->buff; } /* Sync packet number if using compression */ if (net->compress) @@ -363,15 +380,13 @@ my_bool net_flush(NET *net) /** Write a logical packet with packet header. - Format: Packet length (3 bytes), packet number(1 byte) - When compression is used a 3 byte compression length is added + Format: Packet length (3 bytes), packet number (1 byte) + When compression is used, a 3 byte compression length is added. - @note - If compression is used the original package is modified! + @note If compression is used, the original packet is modified! */ -my_bool -my_net_write(NET *net,const uchar *packet,size_t len) +my_bool my_net_write(NET *net, const uchar *packet, size_t len) { uchar buff[NET_HEADER_SIZE]; int rc; @@ -411,11 +426,12 @@ my_net_write(NET *net,const uchar *packet,size_t len) #ifndef DEBUG_DATA_PACKETS DBUG_DUMP("packet_header", buff, NET_HEADER_SIZE); #endif - rc= test(net_write_buff(net,packet,len)); + rc= MY_TEST(net_write_buff(net, packet, len)); MYSQL_NET_WRITE_DONE(rc); return rc; } + /** Send a command to the server. @@ -484,9 +500,9 @@ net_write_command(NET *net,uchar command, } int3store(buff,length); buff[3]= (uchar) net->pkt_nr++; - rc= test(net_write_buff(net, buff, header_size) || - (head_len && net_write_buff(net, header, head_len)) || - net_write_buff(net, packet, len) || net_flush(net)); + rc= MY_TEST(net_write_buff(net, buff, header_size) || + (head_len && net_write_buff(net, header, head_len)) || + net_write_buff(net, packet, len) || net_flush(net)); MYSQL_NET_WRITE_DONE(rc); DBUG_RETURN(rc); } @@ -527,7 +543,7 @@ net_write_buff(NET *net, const uchar *packet, ulong len) left_length= (ulong) (net->buff_end - net->write_pos); #ifdef DEBUG_DATA_PACKETS - DBUG_DUMP("data", packet, len); + DBUG_DUMP("data_written", packet, len); #endif if (len > left_length) { @@ -589,7 +605,7 @@ net_real_write(NET *net,const uchar *packet, size_t len) DBUG_ENTER("net_real_write"); #if defined(MYSQL_SERVER) && defined(USE_QUERY_CACHE) - query_cache_insert((char*) packet, len, net->pkt_nr); + query_cache_insert(net->thd, (char*) packet, len, net->pkt_nr); #endif if (net->error == 2) @@ -603,7 +619,10 @@ net_real_write(NET *net,const uchar *packet, size_t len) uchar *b; uint header_length=NET_HEADER_SIZE+COMP_HEADER_SIZE; if (!(b= (uchar*) my_malloc(len + NET_HEADER_SIZE + - COMP_HEADER_SIZE + 1, MYF(MY_WME)))) + COMP_HEADER_SIZE + 1, + MYF(MY_WME | + (net->thread_specific_malloc ? + MY_THREAD_SPECIFIC : 0))))) { net->error= 2; net->last_errno= ER_OUT_OF_RESOURCES; @@ -613,7 +632,8 @@ net_real_write(NET *net,const uchar *packet, size_t len) } memcpy(b+header_length,packet,len); - if (my_compress(b+header_length, &len, &complen)) + /* Don't compress error packets (compress == 2) */ + if (net->compress == 2 || my_compress(b+header_length, &len, &complen)) complen=0; int3store(&b[NET_HEADER_SIZE],complen); int3store(b,len); @@ -624,7 +644,7 @@ net_real_write(NET *net,const uchar *packet, size_t len) #endif /* HAVE_COMPRESS */ #ifdef DEBUG_DATA_PACKETS - DBUG_DUMP("data", packet, len); + DBUG_DUMP("data_written", packet, len); #endif #ifndef NO_ALARM @@ -689,7 +709,7 @@ net_real_write(NET *net,const uchar *packet, size_t len) break; } pos+=length; - update_statistics(thd_increment_bytes_sent(length)); + update_statistics(thd_increment_bytes_sent(net->thd, length)); } #ifndef __WIN__ end: @@ -762,7 +782,7 @@ static my_bool my_net_skip_rest(NET *net, uint32 remain, thr_alarm_t *alarmed, DBUG_PRINT("enter",("bytes_to_skip: %u", (uint) remain)); /* The following is good for debugging */ - update_statistics(thd_increment_net_big_packet_count(1)); + update_statistics(thd_increment_net_big_packet_count(net->thd, 1)); if (!thr_alarm_in_use(alarmed)) { @@ -775,10 +795,10 @@ static my_bool my_net_skip_rest(NET *net, uint32 remain, thr_alarm_t *alarmed, { while (remain > 0) { - size_t length= min(remain, net->max_packet); + size_t length= MY_MIN(remain, net->max_packet); if (net_safe_read(net, net->buff, length, alarmed)) DBUG_RETURN(1); - update_statistics(thd_increment_bytes_received(length)); + update_statistics(thd_increment_bytes_received(net->thd, length)); remain -= (uint32) length; limit-= length; if (limit < 0) @@ -807,12 +827,14 @@ static my_bool my_net_skip_rest(NET *net, uint32 remain, thr_alarm_t *alarmed, */ static ulong -my_real_read(NET *net, size_t *complen) +my_real_read(NET *net, size_t *complen, + my_bool header __attribute__((unused))) { uchar *pos; size_t length; uint i,retry_count=0; ulong len=packet_error; + my_bool expect_error_packet __attribute__((unused))= 0; thr_alarm_t alarmed; #ifndef NO_ALARM ALARM alarm_buff; @@ -820,6 +842,21 @@ my_real_read(NET *net, size_t *complen) my_bool net_blocking=vio_is_blocking(net->vio); uint32 remain= (net->compress ? NET_HEADER_SIZE+COMP_HEADER_SIZE : NET_HEADER_SIZE); +#ifdef MYSQL_SERVER + size_t count= remain; + struct st_net_server *server_extension= 0; + + if (header) + { + server_extension= static_cast<st_net_server*> (net->extension); + if (server_extension != NULL) + { + void *user_data= server_extension->m_user_data; + server_extension->m_before_header(net, user_data, count); + } + } +#endif + *complen = 0; net->reading_or_writing=1; @@ -843,6 +880,17 @@ my_real_read(NET *net, size_t *complen) DBUG_PRINT("info",("vio_read returned %ld errno: %d", (long) length, vio_errno(net->vio))); + + if (i== 0 && thd_net_is_killed()) + { + DBUG_PRINT("info", ("thd is killed")); + len= packet_error; + net->error= 0; + net->last_errno= ER_CONNECTION_KILLED; + MYSQL_SERVER_my_error(net->last_errno, MYF(0)); + goto end; + } + #if !defined(__WIN__) && defined(MYSQL_SERVER) /* We got an error that there was no data on the socket. We now set up @@ -885,7 +933,7 @@ my_real_read(NET *net, size_t *complen) my_progname,vio_errno(net->vio)); } #ifndef MYSQL_SERVER - if ((long)length < 0 && vio_errno(net->vio) == SOCKET_EINTR) + if (length != 0 && vio_errno(net->vio) == SOCKET_EINTR) { DBUG_PRINT("warning",("Interrupted read. Retrying...")); continue; @@ -895,7 +943,7 @@ my_real_read(NET *net, size_t *complen) remain, vio_errno(net->vio), (long) length)); len= packet_error; net->error= 2; /* Close socket */ - net->last_errno= (vio_was_interrupted(net->vio) ? + net->last_errno= (vio_was_timeout(net->vio) ? ER_NET_READ_INTERRUPTED : ER_NET_READ_ERROR); MYSQL_SERVER_my_error(net->last_errno, MYF(0)); @@ -903,41 +951,36 @@ my_real_read(NET *net, size_t *complen) } remain -= (uint32) length; pos+= length; - update_statistics(thd_increment_bytes_received(length)); + update_statistics(thd_increment_bytes_received(net->thd, length)); } + +#ifdef DEBUG_DATA_PACKETS + DBUG_DUMP("data_read", net->buff+net->where_b, length); +#endif if (i == 0) { /* First parts is packet length */ ulong helping; +#ifndef DEBUG_DATA_PACKETS DBUG_DUMP("packet_header", net->buff+net->where_b, NET_HEADER_SIZE); +#endif if (net->buff[net->where_b + 3] != (uchar) net->pkt_nr) - { - if (net->buff[net->where_b] != (uchar) 255) - { - DBUG_PRINT("error", - ("Packets out of order (Found: %d, expected %u)", - (int) net->buff[net->where_b + 3], - net->pkt_nr)); - /* - We don't make noise server side, since the client is expected - to break the protocol for e.g. --send LOAD DATA .. LOCAL where - the server expects the client to send a file, but the client - may reply with a new command instead. - */ + { #ifndef MYSQL_SERVER - EXTRA_DEBUG_fflush(stdout); - EXTRA_DEBUG_fprintf(stderr,"Error: Packets out of order (Found: %d, expected %d)\n", - (int) net->buff[net->where_b + 3], - (uint) (uchar) net->pkt_nr); - EXTRA_DEBUG_fflush(stderr); + if (net->buff[net->where_b + 3] == (uchar) (net->pkt_nr -1)) + { + /* + If the server was killed then the server may have missed the + last sent client packet and the packet numbering may be one off. + */ + DBUG_PRINT("warning", ("Found possible out of order packets")); + expect_error_packet= 1; + } + else #endif - } - len= packet_error; - /* Not a NET error on the client. XXX: why? */ - MYSQL_SERVER_my_error(ER_NET_PACKETS_OUT_OF_ORDER, MYF(0)); - goto end; - } - net->compress_pkt_nr= ++net->pkt_nr; + goto packets_out_of_order; + } + net->compress_pkt_nr= ++net->pkt_nr; #ifdef HAVE_COMPRESS if (net->compress) { @@ -959,7 +1002,7 @@ my_real_read(NET *net, size_t *complen) len=uint3korr(net->buff+net->where_b); if (!len) /* End of big multi-packet */ goto end; - helping = max(len,*complen) + net->where_b; + helping = MY_MAX(len,*complen) + net->where_b; /* The necessary size of net->buff */ if (helping >= net->max_packet) { @@ -976,7 +1019,30 @@ my_real_read(NET *net, size_t *complen) } pos=net->buff + net->where_b; remain = (uint32) len; +#ifdef MYSQL_SERVER + if (server_extension != NULL) + { + void *user_data= server_extension->m_user_data; + server_extension->m_after_header(net, user_data, count, 0); + server_extension= NULL; + } +#endif + } +#ifndef MYSQL_SERVER + else if (expect_error_packet) + { + /* + This check is safe both for compressed and not compressed protocol + as for the compressed protocol errors are not compressed anymore. + */ + if (net->buff[net->where_b] != (uchar) 255) + { + /* Restore pkt_nr to original value */ + net->pkt_nr--; + goto packets_out_of_order; + } } +#endif } end: @@ -990,9 +1056,53 @@ end: net->reading_or_writing=0; #ifdef DEBUG_DATA_PACKETS if (len != packet_error) - DBUG_DUMP("data", net->buff+net->where_b, len); + DBUG_DUMP("data_read", net->buff+net->where_b, len); +#endif +#ifdef MYSQL_SERVER + if (server_extension != NULL) + { + void *user_data= server_extension->m_user_data; + server_extension->m_after_header(net, user_data, count, 1); + DBUG_ASSERT(len == packet_error || len == 0); + } #endif return(len); + +packets_out_of_order: + { + DBUG_PRINT("error", + ("Packets out of order (Found: %d, expected %u)", + (int) net->buff[net->where_b + 3], + net->pkt_nr)); + EXTRA_DEBUG_ASSERT(0); + /* + We don't make noise server side, since the client is expected + to break the protocol for e.g. --send LOAD DATA .. LOCAL where + the server expects the client to send a file, but the client + may reply with a new command instead. + */ +#ifndef MYSQL_SERVER + EXTRA_DEBUG_fflush(stdout); + EXTRA_DEBUG_fprintf(stderr,"Error: Packets out of order (Found: %d, expected %d)\n", + (int) net->buff[net->where_b + 3], + (uint) (uchar) net->pkt_nr); + EXTRA_DEBUG_fflush(stderr); +#endif + len= packet_error; + MYSQL_SERVER_my_error(ER_NET_PACKETS_OUT_OF_ORDER, MYF(0)); + goto end; + } +} + + + +/* Old interface. See my_net_read_packet() for function description */ + +#undef my_net_read + +ulong my_net_read(NET *net) +{ + return my_net_read_packet(net, 0); } @@ -1007,13 +1117,17 @@ end: If the packet was compressed, its uncompressed and the length of the uncompressed packet is returned. + read_from_server is set when the server is reading a new command + from the client. + @return The function returns the length of the found packet or packet_error. net->read_pos points to the read data. */ + ulong -my_net_read(NET *net) +my_net_read_packet(NET *net, my_bool read_from_server) { size_t len, complen; @@ -1023,7 +1137,7 @@ my_net_read(NET *net) if (!net->compress) { #endif - len = my_real_read(net,&complen); + len = my_real_read(net,&complen, read_from_server); if (len == MAX_PACKET_LENGTH) { /* First packet of a multi-packet. Concatenate the packets */ @@ -1033,7 +1147,7 @@ my_net_read(NET *net) { net->where_b += len; total_length += len; - len = my_real_read(net,&complen); + len = my_real_read(net,&complen, 0); } while (len == MAX_PACKET_LENGTH); if (len != packet_error) len+= total_length; @@ -1125,11 +1239,13 @@ my_net_read(NET *net) } net->where_b=buf_length; - if ((packet_len = my_real_read(net,&complen)) == packet_error) + if ((packet_len = my_real_read(net,&complen, read_from_server)) + == packet_error) { MYSQL_NET_READ_DONE(1, 0); return packet_error; } + read_from_server= 0; if (my_uncompress(net->buff + net->where_b, packet_len, &complen)) { @@ -1160,13 +1276,12 @@ void my_net_set_read_timeout(NET *net, uint timeout) { DBUG_ENTER("my_net_set_read_timeout"); DBUG_PRINT("enter", ("timeout: %d", timeout)); - if (net->read_timeout == timeout) - DBUG_VOID_RETURN; - net->read_timeout= timeout; -#ifdef NO_ALARM - if (net->vio) - vio_timeout(net->vio, 0, timeout); -#endif + if (net->read_timeout != timeout) + { + net->read_timeout= timeout; + if (net->vio) + vio_timeout(net->vio, 0, timeout); + } DBUG_VOID_RETURN; } @@ -1175,12 +1290,11 @@ void my_net_set_write_timeout(NET *net, uint timeout) { DBUG_ENTER("my_net_set_write_timeout"); DBUG_PRINT("enter", ("timeout: %d", timeout)); - if (net->write_timeout == timeout) - DBUG_VOID_RETURN; - net->write_timeout= timeout; -#ifdef NO_ALARM - if (net->vio) - vio_timeout(net->vio, 1, timeout); -#endif + if (net->write_timeout != timeout) + { + net->write_timeout= timeout; + if (net->vio) + vio_timeout(net->vio, 1, timeout); + } DBUG_VOID_RETURN; } diff --git a/sql/nt_servc.cc b/sql/nt_servc.cc index d6a8eac7ed5..e05e43a0a59 100644 --- a/sql/nt_servc.cc +++ b/sql/nt_servc.cc @@ -508,7 +508,7 @@ BOOL NTService::IsService(LPCSTR ServiceName) } /* ------------------------------------------------------------------------ -------------------------------------------------------------------------- */ -BOOL NTService::got_service_option(char **argv, char *service_option) +BOOL NTService::got_service_option(char **argv, const char *service_option) { char *option; for (option= argv[1]; *option; option++) diff --git a/sql/nt_servc.h b/sql/nt_servc.h index 949499d8d7f..6781fe0ddfa 100644 --- a/sql/nt_servc.h +++ b/sql/nt_servc.h @@ -61,7 +61,7 @@ class NTService BOOL SeekStatus(LPCSTR szInternName, int OperationType); BOOL Remove(LPCSTR szInternName); BOOL IsService(LPCSTR ServiceName); - BOOL got_service_option(char **argv, char *service_option); + BOOL got_service_option(char **argv, const char *service_option); BOOL is_super_user(); /* diff --git a/sql/opt_index_cond_pushdown.cc b/sql/opt_index_cond_pushdown.cc index df9dae8e442..1dde5228263 100644 --- a/sql/opt_index_cond_pushdown.cc +++ b/sql/opt_index_cond_pushdown.cc @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "sql_select.h" #include "sql_test.h" @@ -117,7 +117,7 @@ bool uses_index_fields_only(Item *item, TABLE *tbl, uint keyno, return FALSE; KEY *key_info= tbl->key_info + keyno; KEY_PART_INFO *key_part= key_info->key_part; - KEY_PART_INFO *key_part_end= key_part + key_info->key_parts; + KEY_PART_INFO *key_part_end= key_part + key_info->user_defined_key_parts; for ( ; key_part < key_part_end; key_part++) { if (field->eq(key_part->field)) @@ -129,7 +129,7 @@ bool uses_index_fields_only(Item *item, TABLE *tbl, uint keyno, { key_info= tbl->key_info + tbl->s->primary_key; key_part= key_info->key_part; - key_part_end= key_part + key_info->key_parts; + key_part_end= key_part + key_info->user_defined_key_parts; for ( ; key_part < key_part_end; key_part++) { /* @@ -181,8 +181,8 @@ bool uses_index_fields_only(Item *item, TABLE *tbl, uint keyno, Index condition, or NULL if no condition could be inferred. */ -Item *make_cond_for_index(Item *cond, TABLE *table, uint keyno, - bool other_tbls_ok) +static Item *make_cond_for_index(THD *thd, Item *cond, TABLE *table, uint keyno, + bool other_tbls_ok) { if (!cond) return NULL; @@ -192,20 +192,20 @@ Item *make_cond_for_index(Item *cond, TABLE *table, uint keyno, if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) { table_map used_tables= 0; - Item_cond_and *new_cond=new Item_cond_and; + Item_cond_and *new_cond= new (thd->mem_root) Item_cond_and(thd); if (!new_cond) return (COND*) 0; List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) { - Item *fix= make_cond_for_index(item, table, keyno, other_tbls_ok); + Item *fix= make_cond_for_index(thd, item, table, keyno, other_tbls_ok); if (fix) { - new_cond->argument_list()->push_back(fix); + new_cond->argument_list()->push_back(fix, thd->mem_root); used_tables|= fix->used_tables(); } - if (test(item->marker == ICP_COND_USES_INDEX_ONLY)) + if (MY_TEST(item->marker == ICP_COND_USES_INDEX_ONLY)) { n_marked++; item->marker= 0; @@ -227,18 +227,18 @@ Item *make_cond_for_index(Item *cond, TABLE *table, uint keyno, } else /* It's OR */ { - Item_cond_or *new_cond=new Item_cond_or; + Item_cond_or *new_cond= new (thd->mem_root) Item_cond_or(thd); if (!new_cond) return (COND*) 0; List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) { - Item *fix= make_cond_for_index(item, table, keyno, other_tbls_ok); + Item *fix= make_cond_for_index(thd, item, table, keyno, other_tbls_ok); if (!fix) return (COND*) 0; - new_cond->argument_list()->push_back(fix); - if (test(item->marker == ICP_COND_USES_INDEX_ONLY)) + new_cond->argument_list()->push_back(fix, thd->mem_root); + if (MY_TEST(item->marker == ICP_COND_USES_INDEX_ONLY)) { n_marked++; item->marker= 0; @@ -260,8 +260,8 @@ Item *make_cond_for_index(Item *cond, TABLE *table, uint keyno, } -Item *make_cond_remainder(Item *cond, TABLE *table, uint keyno, - bool other_tbls_ok, bool exclude_index) +static Item *make_cond_remainder(THD *thd, Item *cond, TABLE *table, uint keyno, + bool other_tbls_ok, bool exclude_index) { if (cond->type() == Item::COND_ITEM) { @@ -269,18 +269,18 @@ Item *make_cond_remainder(Item *cond, TABLE *table, uint keyno, if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) { /* Create new top level AND item */ - Item_cond_and *new_cond=new Item_cond_and; + Item_cond_and *new_cond= new (thd->mem_root) Item_cond_and(thd); if (!new_cond) return (COND*) 0; List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) { - Item *fix= make_cond_remainder(item, table, keyno, + Item *fix= make_cond_remainder(thd, item, table, keyno, other_tbls_ok, exclude_index); if (fix) { - new_cond->argument_list()->push_back(fix); + new_cond->argument_list()->push_back(fix, thd->mem_root); tbl_map |= fix->used_tables(); } } @@ -297,18 +297,18 @@ Item *make_cond_remainder(Item *cond, TABLE *table, uint keyno, } else /* It's OR */ { - Item_cond_or *new_cond=new Item_cond_or; + Item_cond_or *new_cond= new (thd->mem_root) Item_cond_or(thd); if (!new_cond) return (COND*) 0; List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) { - Item *fix= make_cond_remainder(item, table, keyno, + Item *fix= make_cond_remainder(thd, item, table, keyno, other_tbls_ok, FALSE); if (!fix) return (COND*) 0; - new_cond->argument_list()->push_back(fix); + new_cond->argument_list()->push_back(fix, thd->mem_root); tbl_map |= fix->used_tables(); } new_cond->quick_fix_field(); @@ -366,8 +366,8 @@ void push_index_cond(JOIN_TAB *tab, uint keyno) DBUG_EXECUTE("where", print_where(tab->select_cond, "full cond", QT_ORDINARY);); - idx_cond= make_cond_for_index(tab->select_cond, tab->table, keyno, - tab->icp_other_tables_ok); + idx_cond= make_cond_for_index(tab->join->thd, tab->select_cond, tab->table, + keyno, tab->icp_other_tables_ok); DBUG_EXECUTE("where", print_where(idx_cond, "idx cond", QT_ORDINARY);); @@ -406,7 +406,8 @@ void push_index_cond(JOIN_TAB *tab, uint keyno) tab->ref.disable_cache= TRUE; Item *row_cond= tab->idx_cond_fact_out ? - make_cond_remainder(tab->select_cond, tab->table, keyno, + make_cond_remainder(tab->join->thd, tab->select_cond, + tab->table, keyno, tab->icp_other_tables_ok, TRUE) : tab->pre_idx_push_select_cond; @@ -419,7 +420,8 @@ void push_index_cond(JOIN_TAB *tab, uint keyno) tab->select_cond= row_cond; else { - COND *new_cond= new Item_cond_and(row_cond, idx_remainder_cond); + COND *new_cond= new (tab->join->thd->mem_root) + Item_cond_and(tab->join->thd, row_cond, idx_remainder_cond); tab->select_cond= new_cond; tab->select_cond->quick_fix_field(); ((Item_cond_and*)tab->select_cond)->used_tables_cache= diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 04ab8415dfe..2f76f918e34 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -108,15 +108,17 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include "key.h" // is_key_used, key_copy, key_cmp, key_restore #include "sql_parse.h" // check_stack_overrun #include "sql_partition.h" // get_part_id_func, PARTITION_ITERATOR, - // struct partition_info + // struct partition_info, NOT_A_PARTITION_ID #include "sql_base.h" // free_io_cache #include "records.h" // init_read_record, end_read_record #include <m_ctype.h> #include "sql_select.h" +#include "sql_statistics.h" #include "filesort.h" // filesort_free_buffers #ifndef EXTRA_DEBUG @@ -130,8 +132,6 @@ */ #define double2rows(x) ((ha_rows)(x)) -static int sel_cmp(Field *f,uchar *a,uchar *b,uint8 a_flag,uint8 b_flag); - /* this should be long enough so that any memcmp with a string that starts from '\0' won't cross is_null_string boundaries, even @@ -139,542 +139,6 @@ static int sel_cmp(Field *f,uchar *a,uchar *b,uint8 a_flag,uint8 b_flag); */ static uchar is_null_string[20]= {1,0}; -class RANGE_OPT_PARAM; -/* - A construction block of the SEL_ARG-graph. - - The following description only covers graphs of SEL_ARG objects with - sel_arg->type==KEY_RANGE: - - One SEL_ARG object represents an "elementary interval" in form - - min_value <=? table.keypartX <=? max_value - - The interval is a non-empty interval of any kind: with[out] minimum/maximum - bound, [half]open/closed, single-point interval, etc. - - 1. SEL_ARG GRAPH STRUCTURE - - SEL_ARG objects are linked together in a graph. The meaning of the graph - is better demostrated by an example: - - tree->keys[i] - | - | $ $ - | part=1 $ part=2 $ part=3 - | $ $ - | +-------+ $ +-------+ $ +--------+ - | | kp1<1 |--$-->| kp2=5 |--$-->| kp3=10 | - | +-------+ $ +-------+ $ +--------+ - | | $ $ | - | | $ $ +--------+ - | | $ $ | kp3=12 | - | | $ $ +--------+ - | +-------+ $ $ - \->| kp1=2 |--$--------------$-+ - +-------+ $ $ | +--------+ - | $ $ ==>| kp3=11 | - +-------+ $ $ | +--------+ - | kp1=3 |--$--------------$-+ | - +-------+ $ $ +--------+ - | $ $ | kp3=14 | - ... $ $ +--------+ - - The entire graph is partitioned into "interval lists". - - An interval list is a sequence of ordered disjoint intervals over the same - key part. SEL_ARG are linked via "next" and "prev" pointers. Additionally, - all intervals in the list form an RB-tree, linked via left/right/parent - pointers. The RB-tree root SEL_ARG object will be further called "root of the - interval list". - - In the example pic, there are 4 interval lists: - "kp<1 OR kp1=2 OR kp1=3", "kp2=5", "kp3=10 OR kp3=12", "kp3=11 OR kp3=13". - The vertical lines represent SEL_ARG::next/prev pointers. - - In an interval list, each member X may have SEL_ARG::next_key_part pointer - pointing to the root of another interval list Y. The pointed interval list - must cover a key part with greater number (i.e. Y->part > X->part). - - In the example pic, the next_key_part pointers are represented by - horisontal lines. - - 2. SEL_ARG GRAPH SEMANTICS - - It represents a condition in a special form (we don't have a name for it ATM) - The SEL_ARG::next/prev is "OR", and next_key_part is "AND". - - For example, the picture represents the condition in form: - (kp1 < 1 AND kp2=5 AND (kp3=10 OR kp3=12)) OR - (kp1=2 AND (kp3=11 OR kp3=14)) OR - (kp1=3 AND (kp3=11 OR kp3=14)) - - - 3. SEL_ARG GRAPH USE - - Use get_mm_tree() to construct SEL_ARG graph from WHERE condition. - Then walk the SEL_ARG graph and get a list of dijsoint ordered key - intervals (i.e. intervals in form - - (constA1, .., const1_K) < (keypart1,.., keypartK) < (constB1, .., constB_K) - - Those intervals can be used to access the index. The uses are in: - - check_quick_select() - Walk the SEL_ARG graph and find an estimate of - how many table records are contained within all - intervals. - - get_quick_select() - Walk the SEL_ARG, materialize the key intervals, - and create QUICK_RANGE_SELECT object that will - read records within these intervals. - - 4. SPACE COMPLEXITY NOTES - - SEL_ARG graph is a representation of an ordered disjoint sequence of - intervals over the ordered set of index tuple values. - - For multi-part keys, one can construct a WHERE expression such that its - list of intervals will be of combinatorial size. Here is an example: - - (keypart1 IN (1,2, ..., n1)) AND - (keypart2 IN (1,2, ..., n2)) AND - (keypart3 IN (1,2, ..., n3)) - - For this WHERE clause the list of intervals will have n1*n2*n3 intervals - of form - - (keypart1, keypart2, keypart3) = (k1, k2, k3), where 1 <= k{i} <= n{i} - - SEL_ARG graph structure aims to reduce the amount of required space by - "sharing" the elementary intervals when possible (the pic at the - beginning of this comment has examples of such sharing). The sharing may - prevent combinatorial blowup: - - There are WHERE clauses that have combinatorial-size interval lists but - will be represented by a compact SEL_ARG graph. - Example: - (keypartN IN (1,2, ..., n1)) AND - ... - (keypart2 IN (1,2, ..., n2)) AND - (keypart1 IN (1,2, ..., n3)) - - but not in all cases: - - - There are WHERE clauses that do have a compact SEL_ARG-graph - representation but get_mm_tree() and its callees will construct a - graph of combinatorial size. - Example: - (keypart1 IN (1,2, ..., n1)) AND - (keypart2 IN (1,2, ..., n2)) AND - ... - (keypartN IN (1,2, ..., n3)) - - - There are WHERE clauses for which the minimal possible SEL_ARG graph - representation will have combinatorial size. - Example: - By induction: Let's take any interval on some keypart in the middle: - - kp15=c0 - - Then let's AND it with this interval 'structure' from preceding and - following keyparts: - - (kp14=c1 AND kp16=c3) OR keypart14=c2) (*) - - We will obtain this SEL_ARG graph: - - kp14 $ kp15 $ kp16 - $ $ - +---------+ $ +---------+ $ +---------+ - | kp14=c1 |--$-->| kp15=c0 |--$-->| kp16=c3 | - +---------+ $ +---------+ $ +---------+ - | $ $ - +---------+ $ +---------+ $ - | kp14=c2 |--$-->| kp15=c0 | $ - +---------+ $ +---------+ $ - $ $ - - Note that we had to duplicate "kp15=c0" and there was no way to avoid - that. - The induction step: AND the obtained expression with another "wrapping" - expression like (*). - When the process ends because of the limit on max. number of keyparts - we'll have: - - WHERE clause length is O(3*#max_keyparts) - SEL_ARG graph size is O(2^(#max_keyparts/2)) - - (it is also possible to construct a case where instead of 2 in 2^n we - have a bigger constant, e.g. 4, and get a graph with 4^(31/2)= 2^31 - nodes) - - We avoid consuming too much memory by setting a limit on the number of - SEL_ARG object we can construct during one range analysis invocation. -*/ - -class SEL_ARG :public Sql_alloc -{ -public: - uint8 min_flag,max_flag,maybe_flag; - uint8 part; // Which key part - uint8 maybe_null; - /* - The ordinal number the least significant component encountered in - the ranges of the SEL_ARG tree (the first component has number 1) - */ - uint16 max_part_no; - /* - Number of children of this element in the RB-tree, plus 1 for this - element itself. - */ - uint16 elements; - /* - Valid only for elements which are RB-tree roots: Number of times this - RB-tree is referred to (it is referred by SEL_ARG::next_key_part or by - SEL_TREE::keys[i] or by a temporary SEL_ARG* variable) - */ - ulong use_count; - - Field *field; - uchar *min_value,*max_value; // Pointer to range - - /* - eq_tree() requires that left == right == 0 if the type is MAYBE_KEY. - */ - SEL_ARG *left,*right; /* R-B tree children */ - SEL_ARG *next,*prev; /* Links for bi-directional interval list */ - SEL_ARG *parent; /* R-B tree parent */ - SEL_ARG *next_key_part; - enum leaf_color { BLACK,RED } color; - enum Type { IMPOSSIBLE, MAYBE, MAYBE_KEY, KEY_RANGE } type; - - enum { MAX_SEL_ARGS = 16000 }; - - SEL_ARG() {} - SEL_ARG(SEL_ARG &); - SEL_ARG(Field *,const uchar *, const uchar *); - SEL_ARG(Field *field, uint8 part, uchar *min_value, uchar *max_value, - uint8 min_flag, uint8 max_flag, uint8 maybe_flag); - SEL_ARG(enum Type type_arg) - :min_flag(0), max_part_no(0) /* first key part means 1. 0 mean 'no parts'*/, - elements(1),use_count(1),left(0),right(0), - next_key_part(0), color(BLACK), type(type_arg) - {} - /** - returns true if a range predicate is equal. Use all_same() - to check for equality of all the predicates on this keypart. - */ - inline bool is_same(const SEL_ARG *arg) const - { - if (type != arg->type || part != arg->part) - return false; - if (type != KEY_RANGE) - return true; - return cmp_min_to_min(arg) == 0 && cmp_max_to_max(arg) == 0; - } - /** - returns true if all the predicates in the keypart tree are equal - */ - bool all_same(const SEL_ARG *arg) const - { - if (type != arg->type || part != arg->part) - return false; - if (type != KEY_RANGE) - return true; - if (arg == this) - return true; - const SEL_ARG *cmp_arg= arg->first(); - const SEL_ARG *cur_arg= first(); - for (; cur_arg && cmp_arg && cur_arg->is_same(cmp_arg); - cur_arg= cur_arg->next, cmp_arg= cmp_arg->next) ; - if (cur_arg || cmp_arg) - return false; - return true; - } - inline void merge_flags(SEL_ARG *arg) { maybe_flag|=arg->maybe_flag; } - inline void maybe_smaller() { maybe_flag=1; } - /* Return true iff it's a single-point null interval */ - inline bool is_null_interval() { return maybe_null && max_value[0] == 1; } - inline int cmp_min_to_min(const SEL_ARG* arg) const - { - return sel_cmp(field,min_value, arg->min_value, min_flag, arg->min_flag); - } - inline int cmp_min_to_max(const SEL_ARG* arg) const - { - return sel_cmp(field,min_value, arg->max_value, min_flag, arg->max_flag); - } - inline int cmp_max_to_max(const SEL_ARG* arg) const - { - return sel_cmp(field,max_value, arg->max_value, max_flag, arg->max_flag); - } - inline int cmp_max_to_min(const SEL_ARG* arg) const - { - return sel_cmp(field,max_value, arg->min_value, max_flag, arg->min_flag); - } - SEL_ARG *clone_and(SEL_ARG* arg) - { // Get overlapping range - uchar *new_min,*new_max; - uint8 flag_min,flag_max; - if (cmp_min_to_min(arg) >= 0) - { - new_min=min_value; flag_min=min_flag; - } - else - { - new_min=arg->min_value; flag_min=arg->min_flag; /* purecov: deadcode */ - } - if (cmp_max_to_max(arg) <= 0) - { - new_max=max_value; flag_max=max_flag; - } - else - { - new_max=arg->max_value; flag_max=arg->max_flag; - } - return new SEL_ARG(field, part, new_min, new_max, flag_min, flag_max, - test(maybe_flag && arg->maybe_flag)); - } - SEL_ARG *clone_first(SEL_ARG *arg) - { // min <= X < arg->min - return new SEL_ARG(field,part, min_value, arg->min_value, - min_flag, arg->min_flag & NEAR_MIN ? 0 : NEAR_MAX, - maybe_flag | arg->maybe_flag); - } - SEL_ARG *clone_last(SEL_ARG *arg) - { // min <= X <= key_max - return new SEL_ARG(field, part, min_value, arg->max_value, - min_flag, arg->max_flag, maybe_flag | arg->maybe_flag); - } - SEL_ARG *clone(RANGE_OPT_PARAM *param, SEL_ARG *new_parent, SEL_ARG **next); - - bool copy_min(SEL_ARG* arg) - { // Get overlapping range - if (cmp_min_to_min(arg) > 0) - { - min_value=arg->min_value; min_flag=arg->min_flag; - if ((max_flag & (NO_MAX_RANGE | NO_MIN_RANGE)) == - (NO_MAX_RANGE | NO_MIN_RANGE)) - return 1; // Full range - } - maybe_flag|=arg->maybe_flag; - return 0; - } - bool copy_max(SEL_ARG* arg) - { // Get overlapping range - if (cmp_max_to_max(arg) <= 0) - { - max_value=arg->max_value; max_flag=arg->max_flag; - if ((max_flag & (NO_MAX_RANGE | NO_MIN_RANGE)) == - (NO_MAX_RANGE | NO_MIN_RANGE)) - return 1; // Full range - } - maybe_flag|=arg->maybe_flag; - return 0; - } - - void copy_min_to_min(SEL_ARG *arg) - { - min_value=arg->min_value; min_flag=arg->min_flag; - } - void copy_min_to_max(SEL_ARG *arg) - { - max_value=arg->min_value; - max_flag=arg->min_flag & NEAR_MIN ? 0 : NEAR_MAX; - } - void copy_max_to_min(SEL_ARG *arg) - { - min_value=arg->max_value; - min_flag=arg->max_flag & NEAR_MAX ? 0 : NEAR_MIN; - } - /* returns a number of keypart values (0 or 1) appended to the key buffer */ - int store_min(uint length, uchar **min_key,uint min_key_flag) - { - /* "(kp1 > c1) AND (kp2 OP c2) AND ..." -> (kp1 > c1) */ - if ((min_flag & GEOM_FLAG) || - (!(min_flag & NO_MIN_RANGE) && - !(min_key_flag & (NO_MIN_RANGE | NEAR_MIN)))) - { - if (maybe_null && *min_value) - { - **min_key=1; - bzero(*min_key+1,length-1); - } - else - memcpy(*min_key,min_value,length); - (*min_key)+= length; - return 1; - } - return 0; - } - /* returns a number of keypart values (0 or 1) appended to the key buffer */ - int store_max(uint length, uchar **max_key, uint max_key_flag) - { - if (!(max_flag & NO_MAX_RANGE) && - !(max_key_flag & (NO_MAX_RANGE | NEAR_MAX))) - { - if (maybe_null && *max_value) - { - **max_key=1; - bzero(*max_key+1,length-1); - } - else - memcpy(*max_key,max_value,length); - (*max_key)+= length; - return 1; - } - return 0; - } - - /* - Returns a number of keypart values appended to the key buffer - for min key and max key. This function is used by both Range - Analysis and Partition pruning. For partition pruning we have - to ensure that we don't store also subpartition fields. Thus - we have to stop at the last partition part and not step into - the subpartition fields. For Range Analysis we set last_part - to MAX_KEY which we should never reach. - */ - int store_min_key(KEY_PART *key, - uchar **range_key, - uint *range_key_flag, - uint last_part) - { - SEL_ARG *key_tree= first(); - uint res= key_tree->store_min(key[key_tree->part].store_length, - range_key, *range_key_flag); - *range_key_flag|= key_tree->min_flag; - if (key_tree->next_key_part && - key_tree->next_key_part->type == SEL_ARG::KEY_RANGE && - key_tree->part != last_part && - key_tree->next_key_part->part == key_tree->part+1 && - !(*range_key_flag & (NO_MIN_RANGE | NEAR_MIN))) - res+= key_tree->next_key_part->store_min_key(key, - range_key, - range_key_flag, - last_part); - return res; - } - - /* returns a number of keypart values appended to the key buffer */ - int store_max_key(KEY_PART *key, - uchar **range_key, - uint *range_key_flag, - uint last_part) - { - SEL_ARG *key_tree= last(); - uint res=key_tree->store_max(key[key_tree->part].store_length, - range_key, *range_key_flag); - (*range_key_flag)|= key_tree->max_flag; - if (key_tree->next_key_part && - key_tree->next_key_part->type == SEL_ARG::KEY_RANGE && - key_tree->part != last_part && - key_tree->next_key_part->part == key_tree->part+1 && - !(*range_key_flag & (NO_MAX_RANGE | NEAR_MAX))) - res+= key_tree->next_key_part->store_max_key(key, - range_key, - range_key_flag, - last_part); - return res; - } - - SEL_ARG *insert(SEL_ARG *key); - SEL_ARG *tree_delete(SEL_ARG *key); - SEL_ARG *find_range(SEL_ARG *key); - SEL_ARG *rb_insert(SEL_ARG *leaf); - friend SEL_ARG *rb_delete_fixup(SEL_ARG *root,SEL_ARG *key, SEL_ARG *par); -#ifdef EXTRA_DEBUG - friend int test_rb_tree(SEL_ARG *element,SEL_ARG *parent); - void test_use_count(SEL_ARG *root); -#endif - SEL_ARG *first(); - const SEL_ARG *first() const; - SEL_ARG *last(); - void make_root(); - inline bool simple_key() - { - return !next_key_part && elements == 1; - } - void increment_use_count(long count) - { - if (next_key_part) - { - next_key_part->use_count+=count; - count*= (next_key_part->use_count-count); - for (SEL_ARG *pos=next_key_part->first(); pos ; pos=pos->next) - if (pos->next_key_part) - pos->increment_use_count(count); - } - } - void incr_refs() - { - increment_use_count(1); - use_count++; - } - void incr_refs_all() - { - for (SEL_ARG *pos=first(); pos ; pos=pos->next) - { - pos->increment_use_count(1); - } - use_count++; - } - void free_tree() - { - for (SEL_ARG *pos=first(); pos ; pos=pos->next) - if (pos->next_key_part) - { - pos->next_key_part->use_count--; - pos->next_key_part->free_tree(); - } - } - - inline SEL_ARG **parent_ptr() - { - return parent->left == this ? &parent->left : &parent->right; - } - - - /* - Check if this SEL_ARG object represents a single-point interval - - SYNOPSIS - is_singlepoint() - - DESCRIPTION - Check if this SEL_ARG object (not tree) represents a single-point - interval, i.e. if it represents a "keypart = const" or - "keypart IS NULL". - - RETURN - TRUE This SEL_ARG object represents a singlepoint interval - FALSE Otherwise - */ - - bool is_singlepoint() - { - /* - Check for NEAR_MIN ("strictly less") and NO_MIN_RANGE (-inf < field) - flags, and the same for right edge. - */ - if (min_flag || max_flag) - return FALSE; - uchar *min_val= min_value; - uchar *max_val= max_value; - - if (maybe_null) - { - /* First byte is a NULL value indicator */ - if (*min_val != *max_val) - return FALSE; - - if (*min_val) - return TRUE; /* This "x IS NULL" */ - min_val++; - max_val++; - } - return !field->key_cmp(min_val, max_val); - } - SEL_ARG *clone_tree(RANGE_OPT_PARAM *param); -}; - /** Helper function to compare two SEL_ARG's. */ @@ -792,12 +256,19 @@ public: (type == SEL_TREE::IMPOSSIBLE) */ enum Type { IMPOSSIBLE, ALWAYS, MAYBE, KEY, KEY_SMALLER } type; - SEL_TREE(enum Type type_arg) :type(type_arg) {} - SEL_TREE() :type(KEY) + + SEL_TREE(enum Type type_arg, MEM_ROOT *root, size_t num_keys) + : type(type_arg), keys(root, num_keys), n_ror_scans(0) { keys_map.clear_all(); - bzero((char*) keys,sizeof(keys)); } + + SEL_TREE(MEM_ROOT *root, size_t num_keys) : + type(KEY), keys(root, num_keys), n_ror_scans(0) + { + keys_map.clear_all(); + } + SEL_TREE(SEL_TREE *arg, bool without_merges, RANGE_OPT_PARAM *param); /* Note: there may exist SEL_TREE objects with sel_tree->type=KEY and @@ -805,7 +276,8 @@ public: merit in range analyzer functions (e.g. get_mm_parts) returning a pointer to such SEL_TREE instead of NULL) */ - SEL_ARG *keys[MAX_KEY]; + Mem_root_array<SEL_ARG *, true> keys; + key_map keys_map; /* bitmask of non-NULL elements in keys */ /* @@ -829,73 +301,19 @@ public: bool without_imerges() { return merges.is_empty(); } }; -class RANGE_OPT_PARAM -{ -public: - THD *thd; /* Current thread handle */ - TABLE *table; /* Table being analyzed */ - COND *cond; /* Used inside get_mm_tree(). */ - table_map prev_tables; - table_map read_tables; - table_map current_table; /* Bit of the table being analyzed */ - - /* Array of parts of all keys for which range analysis is performed */ - KEY_PART *key_parts; - KEY_PART *key_parts_end; - MEM_ROOT *mem_root; /* Memory that will be freed when range analysis completes */ - MEM_ROOT *old_root; /* Memory that will last until the query end */ - /* - Number of indexes used in range analysis (In SEL_TREE::keys only first - #keys elements are not empty) - */ - uint keys; - - /* - If true, the index descriptions describe real indexes (and it is ok to - call field->optimize_range(real_keynr[...], ...). - Otherwise index description describes fake indexes. - */ - bool using_real_indexes; - - /* - Aggressively remove "scans" that do not have conditions on first - keyparts. Such scans are usable when doing partition pruning but not - regular range optimization. - */ - bool remove_jump_scans; - - /* - used_key_no -> table_key_no translation table. Only makes sense if - using_real_indexes==TRUE - */ - uint real_keynr[MAX_KEY]; - - /* - Used to store 'current key tuples', in both range analysis and - partitioning (list) analysis - */ - uchar min_key[MAX_KEY_LENGTH+MAX_FIELD_WIDTH], - max_key[MAX_KEY_LENGTH+MAX_FIELD_WIDTH]; - - /* Number of SEL_ARG objects allocated by SEL_ARG::clone_tree operations */ - uint alloced_sel_args; - - bool force_default_mrr; - KEY_PART *key[MAX_KEY]; /* First key parts of keys used in the query */ - - bool statement_should_be_aborted() const - { - return - thd->is_fatal_error || - thd->is_error() || - alloced_sel_args > SEL_ARG::MAX_SEL_ARGS; - } -}; class PARAM : public RANGE_OPT_PARAM { public: ha_rows quick_rows[MAX_KEY]; + + /* + This will collect 'possible keys' based on the range optimization. + + Queries with a JOIN object actually use ref optimizer (see add_key_field) + to collect possible_keys. This is used by single table UPDATE/DELETE. + */ + key_map possible_keys; longlong baseflag; uint max_key_part, range_count; @@ -929,19 +347,11 @@ class TABLE_READ_PLAN; struct st_index_scan_info; struct st_ror_scan_info; -static SEL_TREE * get_mm_parts(RANGE_OPT_PARAM *param,COND *cond_func,Field *field, - Item_func::Functype type,Item *value, - Item_result cmp_type); -static SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param,COND *cond_func,Field *field, - KEY_PART *key_part, - Item_func::Functype type,Item *value); -static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param,COND *cond); - static bool is_key_scan_ror(PARAM *param, uint keynr, uint8 nparts); static ha_rows check_quick_select(PARAM *param, uint idx, bool index_only, SEL_ARG *tree, bool update_tbl_stats, uint *mrr_flags, uint *bufsize, - COST_VECT *cost); + Cost_estimate *cost); QUICK_RANGE_SELECT *get_quick_select(PARAM *param,uint index, SEL_ARG *key_tree, uint mrr_flags, @@ -1177,7 +587,7 @@ int SEL_IMERGE::and_sel_tree(RANGE_OPT_PARAM *param, SEL_TREE *tree, { SEL_TREE *res_or_tree= 0; SEL_TREE *and_tree= 0; - if (!(res_or_tree= new SEL_TREE()) || + if (!(res_or_tree= new SEL_TREE(param->mem_root, param->keys)) || !(and_tree= new SEL_TREE(tree, TRUE, param))) return (-1); if (!and_range_trees(param, *or_tree, and_tree, res_or_tree)) @@ -1386,10 +796,15 @@ int SEL_IMERGE::or_sel_imerge_with_checks(RANGE_OPT_PARAM *param, */ SEL_TREE::SEL_TREE(SEL_TREE *arg, bool without_merges, - RANGE_OPT_PARAM *param): Sql_alloc() + RANGE_OPT_PARAM *param) + : Sql_alloc(), + keys(param->mem_root, param->keys), + n_ror_scans(0) { keys_map= arg->keys_map; type= arg->type; + MEM_ROOT *mem_root; + for (uint idx= 0; idx < param->keys; idx++) { if ((keys[idx]= arg->keys[idx])) @@ -1399,16 +814,17 @@ SEL_TREE::SEL_TREE(SEL_TREE *arg, bool without_merges, if (without_merges) return; + mem_root= current_thd->mem_root; List_iterator<SEL_IMERGE> it(arg->merges); for (SEL_IMERGE *el= it++; el; el= it++) { - SEL_IMERGE *merge= new SEL_IMERGE(el, 0, param); + SEL_IMERGE *merge= new (mem_root) SEL_IMERGE(el, 0, param); if (!merge || merge->trees == merge->trees_next) { merges.empty(); return; } - merges.push_back (merge); + merges.push_back(merge, mem_root); } } @@ -1478,7 +894,7 @@ mem_err: inline void imerge_list_and_list(List<SEL_IMERGE> *im1, List<SEL_IMERGE> *im2) { - im1->concat(im2); + im1->append(im2); } @@ -1535,11 +951,12 @@ int imerge_list_or_list(RANGE_OPT_PARAM *param, uint rc; bool is_last_check_pass= FALSE; - SEL_IMERGE *imerge= im1->head(); uint elems= imerge->trees_next-imerge->trees; + MEM_ROOT *mem_root= current_thd->mem_root; + im1->empty(); - im1->push_back(imerge); + im1->push_back(imerge, mem_root); rc= imerge->or_sel_imerge_with_checks(param, elems, im2->head(), TRUE, &is_last_check_pass); @@ -1555,14 +972,14 @@ int imerge_list_or_list(RANGE_OPT_PARAM *param, if (!is_last_check_pass) { - SEL_IMERGE* new_imerge= new SEL_IMERGE(imerge, elems, param); + SEL_IMERGE* new_imerge= new (mem_root) SEL_IMERGE(imerge, elems, param); if (new_imerge) { is_last_check_pass= TRUE; rc= new_imerge->or_sel_imerge_with_checks(param, elems, im2->head(), FALSE, &is_last_check_pass); if (!rc) - im1->push_back(new_imerge); + im1->push_back(new_imerge, mem_root); } } return rc; @@ -1622,17 +1039,17 @@ int imerge_list_or_tree(RANGE_OPT_PARAM *param, List<SEL_IMERGE> *merges, SEL_TREE *tree) { - SEL_IMERGE *imerge; List<SEL_IMERGE> additional_merges; List_iterator<SEL_IMERGE> it(*merges); + MEM_ROOT *mem_root= current_thd->mem_root; while ((imerge= it++)) { bool is_last_check_pass; int rc= 0; int rc1= 0; - SEL_TREE *or_tree= new SEL_TREE (tree, FALSE, param); + SEL_TREE *or_tree= new (mem_root) SEL_TREE (tree, FALSE, param); if (or_tree) { uint elems= imerge->trees_next-imerge->trees; @@ -1640,13 +1057,14 @@ int imerge_list_or_tree(RANGE_OPT_PARAM *param, TRUE, &is_last_check_pass); if (!is_last_check_pass) { - SEL_IMERGE *new_imerge= new SEL_IMERGE(imerge, elems, param); + SEL_IMERGE *new_imerge= new (mem_root) SEL_IMERGE(imerge, elems, + param); if (new_imerge) { rc1= new_imerge->or_sel_tree_with_checks(param, elems, or_tree, FALSE, &is_last_check_pass); if (!rc1) - additional_merges.push_back(new_imerge); + additional_merges.push_back(new_imerge, mem_root); } } } @@ -1654,7 +1072,7 @@ int imerge_list_or_tree(RANGE_OPT_PARAM *param, it.remove(); } - merges->concat(&additional_merges); + merges->append(&additional_merges); return merges->is_empty(); } @@ -1708,11 +1126,12 @@ int imerge_list_and_tree(RANGE_OPT_PARAM *param, SEL_IMERGE *new_imerge= NULL; List<SEL_IMERGE> new_merges; List_iterator<SEL_IMERGE> it(*merges); + MEM_ROOT *mem_root= current_thd->mem_root; while ((imerge= it++)) { if (!new_imerge) - new_imerge= new SEL_IMERGE(); + new_imerge= new (mem_root) SEL_IMERGE(); if (imerge->have_common_keys(param, tree) && new_imerge && !imerge->and_sel_tree(param, tree, new_imerge)) { @@ -1723,7 +1142,7 @@ int imerge_list_and_tree(RANGE_OPT_PARAM *param, if (replace) it.replace(new_imerge); else - new_merges.push_back(new_imerge); + new_merges.push_back(new_imerge, mem_root); new_imerge= NULL; } } @@ -1756,7 +1175,7 @@ SQL_SELECT *make_select(TABLE *head, table_map const_tables, if (!conds && !allow_null_cond) DBUG_RETURN(0); - if (!(select= new SQL_SELECT)) + if (!(select= new (head->in_use->mem_root) SQL_SELECT)) { *error= 1; // out of memory DBUG_RETURN(0); /* purecov: inspected */ @@ -1823,7 +1242,6 @@ QUICK_RANGE_SELECT::QUICK_RANGE_SELECT(THD *thd, TABLE *table, uint key_nr, index= key_nr; head= table; key_part_info= head->key_info[index].key_part; - my_init_dynamic_array(&ranges, sizeof(QUICK_RANGE*), 16, 16); /* 'thd' is not accessible in QUICK_RANGE_SELECT::reset(). */ mrr_buf_size= thd->variables.mrr_buff_size; @@ -1832,7 +1250,8 @@ QUICK_RANGE_SELECT::QUICK_RANGE_SELECT(THD *thd, TABLE *table, uint key_nr, if (!no_alloc && !parent_alloc) { // Allocates everything through the internal memroot - init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); + init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0, + MYF(MY_THREAD_SPECIFIC)); thd->mem_root= &alloc; } else @@ -1840,15 +1259,18 @@ QUICK_RANGE_SELECT::QUICK_RANGE_SELECT(THD *thd, TABLE *table, uint key_nr, file= head->file; record= head->record[0]; - /* Allocate a bitmap for used columns (Q: why not on MEM_ROOT?) */ - if (!(bitmap= (my_bitmap_map*) my_malloc(head->s->column_bitmap_size, - MYF(MY_WME)))) + my_init_dynamic_array2(&ranges, sizeof(QUICK_RANGE*), + thd->alloc(sizeof(QUICK_RANGE*) * 16), 16, 16, + MYF(MY_THREAD_SPECIFIC)); + + /* Allocate a bitmap for used columns */ + if (!(bitmap= (my_bitmap_map*) thd->alloc(head->s->column_bitmap_size))) { column_bitmap.bitmap= 0; *create_error= 1; } else - bitmap_init(&column_bitmap, bitmap, head->s->fields, FALSE); + my_bitmap_init(&column_bitmap, bitmap, head->s->fields, FALSE); DBUG_VOID_RETURN; } @@ -1906,7 +1328,6 @@ QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT() } delete_dynamic(&ranges); /* ranges are allocated in alloc */ free_root(&alloc,MYF(0)); - my_free(column_bitmap.bitmap); } my_free(mrr_buf_desc); DBUG_VOID_RETURN; @@ -1919,15 +1340,15 @@ QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT() - Use rowids from unique to run a disk-ordered sweep */ -QUICK_INDEX_SORT_SELECT::QUICK_INDEX_SORT_SELECT(THD *thd_param, - TABLE *table) +QUICK_INDEX_SORT_SELECT::QUICK_INDEX_SORT_SELECT(THD *thd_param, TABLE *table) :unique(NULL), pk_quick_select(NULL), thd(thd_param) { DBUG_ENTER("QUICK_INDEX_SORT_SELECT::QUICK_INDEX_SORT_SELECT"); index= MAX_KEY; head= table; bzero(&read_record, sizeof(read_record)); - init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); + init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0, + MYF(MY_THREAD_SPECIFIC)); DBUG_VOID_RETURN; } @@ -1966,7 +1387,7 @@ QUICK_INDEX_SORT_SELECT::push_quick_back(QUICK_RANGE_SELECT *quick_sel_range) pk_quick_select= quick_sel_range; DBUG_RETURN(0); } - DBUG_RETURN(quick_selects.push_back(quick_sel_range)); + DBUG_RETURN(quick_selects.push_back(quick_sel_range, thd->mem_root)); } QUICK_INDEX_SORT_SELECT::~QUICK_INDEX_SORT_SELECT() @@ -1998,7 +1419,8 @@ QUICK_ROR_INTERSECT_SELECT::QUICK_ROR_INTERSECT_SELECT(THD *thd_param, head= table; record= head->record[0]; if (!parent_alloc) - init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); + init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0, + MYF(MY_THREAD_SPECIFIC)); else bzero(&alloc, sizeof(MEM_ROOT)); last_rowid= (uchar*) alloc_root(parent_alloc? parent_alloc : &alloc, @@ -2046,7 +1468,8 @@ int QUICK_ROR_INTERSECT_SELECT::init() 1 error */ -int QUICK_RANGE_SELECT::init_ror_merged_scan(bool reuse_handler, MEM_ROOT *alloc) +int QUICK_RANGE_SELECT::init_ror_merged_scan(bool reuse_handler, + MEM_ROOT *local_alloc) { handler *save_file= file, *org_file; my_bool org_key_read; @@ -2074,7 +1497,7 @@ int QUICK_RANGE_SELECT::init_ror_merged_scan(bool reuse_handler, MEM_ROOT *alloc DBUG_RETURN(0); } - if (!(file= head->file->clone(head->s->normalized_path.str, alloc))) + if (!(file= head->file->clone(head->s->normalized_path.str, local_alloc))) { /* Manually set the error flag. Note: there seems to be quite a few @@ -2106,15 +1529,14 @@ end: DBUG_ASSERT(head->read_set == &column_bitmap); /* We are only going to read key fields and call position() on 'file' - The following sets head->tmp_set to only use this key and then updates - head->read_set and head->write_set to use this bitmap. - The now bitmap is stored in 'column_bitmap' which is used in ::get_next() + The following sets head->read_set (== column_bitmap) to only use this + key. The 'column_bitmap' is used in ::get_next() */ org_file= head->file; org_key_read= head->key_read; head->file= file; head->key_read= 0; - head->mark_columns_used_by_index_no_reset(index, head->read_set); + head->mark_columns_used_by_index_no_reset(index, &column_bitmap); if (!head->no_keyread) { @@ -2138,8 +1560,7 @@ end: file->ha_close(); goto failure; } - else - DBUG_RETURN(1); + DBUG_RETURN(1); } DBUG_RETURN(0); @@ -2147,6 +1568,7 @@ failure: head->column_bitmaps_set(save_read_set, save_write_set); delete file; file= save_file; + free_file= false; DBUG_RETURN(1); } @@ -2162,7 +1584,7 @@ failure: other error code */ int QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan(bool reuse_handler, - MEM_ROOT *alloc) + MEM_ROOT *local_alloc) { List_iterator_fast<QUICK_SELECT_WITH_RECORD> quick_it(quick_selects); QUICK_SELECT_WITH_RECORD *cur; @@ -2179,7 +1601,7 @@ int QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan(bool reuse_handler, There is no use of this->file. Use it for the first of merged range selects. */ - int error= quick->init_ror_merged_scan(TRUE, alloc); + int error= quick->init_ror_merged_scan(TRUE, local_alloc); if (error) DBUG_RETURN(error); quick->file->extra(HA_EXTRA_KEYREAD_PRESERVE_FIELDS); @@ -2191,7 +1613,7 @@ int QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan(bool reuse_handler, const MY_BITMAP * const save_read_set= quick->head->read_set; const MY_BITMAP * const save_write_set= quick->head->write_set; #endif - if (quick->init_ror_merged_scan(FALSE, alloc)) + if (quick->init_ror_merged_scan(FALSE, local_alloc)) DBUG_RETURN(1); quick->file->extra(HA_EXTRA_KEYREAD_PRESERVE_FIELDS); @@ -2204,7 +1626,7 @@ int QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan(bool reuse_handler, quick->record= head->record[0]; } - if (need_to_fetch_row && head->file->ha_rnd_init_with_error(1)) + if (need_to_fetch_row && head->file->ha_rnd_init_with_error(false)) { DBUG_PRINT("error", ("ROR index_merge rnd_init call failed")); DBUG_RETURN(1); @@ -2253,11 +1675,13 @@ int QUICK_ROR_INTERSECT_SELECT::reset() */ bool -QUICK_ROR_INTERSECT_SELECT::push_quick_back(MEM_ROOT *alloc, QUICK_RANGE_SELECT *quick) +QUICK_ROR_INTERSECT_SELECT::push_quick_back(MEM_ROOT *local_alloc, + QUICK_RANGE_SELECT *quick) { QUICK_SELECT_WITH_RECORD *qr; if (!(qr= new QUICK_SELECT_WITH_RECORD) || - !(qr->key_tuple= (uchar*)alloc_root(alloc, quick->max_used_key_length))) + !(qr->key_tuple= (uchar*)alloc_root(local_alloc, + quick->max_used_key_length))) return TRUE; qr->quick= quick; return quick_selects.push_back(qr); @@ -2284,7 +1708,8 @@ QUICK_ROR_UNION_SELECT::QUICK_ROR_UNION_SELECT(THD *thd_param, head= table; rowid_length= table->file->ref_length; record= head->record[0]; - init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); + init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0, + MYF(MY_THREAD_SPECIFIC)); thd_param->mem_root= &alloc; } @@ -2385,8 +1810,13 @@ int QUICK_ROR_UNION_SELECT::reset() quick->save_last_pos(); queue_insert(&queue, (uchar*)quick); } - - if ((error= head->file->ha_rnd_init(1))) + /* Prepare for ha_rnd_pos calls. */ + if (head->file->inited && (error= head->file->ha_rnd_end())) + { + DBUG_PRINT("error", ("ROR index_merge rnd_end call failed")); + DBUG_RETURN(error); + } + if ((error= head->file->ha_rnd_init(false))) { DBUG_PRINT("error", ("ROR index_merge rnd_init call failed")); DBUG_RETURN(error); @@ -2550,8 +1980,8 @@ SEL_ARG *SEL_ARG::last() Returns -2 or 2 if the ranges where 'joined' like < 2 and >= 2 */ -static int sel_cmp(Field *field, uchar *a, uchar *b, uint8 a_flag, - uint8 b_flag) +int SEL_ARG::sel_cmp(Field *field, uchar *a, uchar *b, uint8 a_flag, + uint8 b_flag) { int cmp; /* First check if there was a compare to a min or max element */ @@ -2881,7 +2311,7 @@ static int fill_used_fields_bitmap(PARAM *param) param->fields_bitmap_size= table->s->column_bitmap_size; if (!(tmp= (my_bitmap_map*) alloc_root(param->mem_root, param->fields_bitmap_size)) || - bitmap_init(¶m->needed_fields, tmp, table->s->fields, FALSE)) + my_bitmap_init(¶m->needed_fields, tmp, table->s->fields, FALSE)) return 1; bitmap_copy(¶m->needed_fields, table->read_set); @@ -2893,7 +2323,7 @@ static int fill_used_fields_bitmap(PARAM *param) /* The table uses clustered PK and it is not internally generated */ KEY_PART_INFO *key_part= param->table->key_info[pk].key_part; KEY_PART_INFO *key_part_end= key_part + - param->table->key_info[pk].key_parts; + param->table->key_info[pk].user_defined_key_parts; for (;key_part != key_part_end; ++key_part) bitmap_clear_bit(¶m->needed_fields, key_part->fieldnr-1); } @@ -2970,7 +2400,8 @@ static int fill_used_fields_bitmap(PARAM *param) int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, table_map prev_tables, ha_rows limit, bool force_quick_range, - bool ordered_output) + bool ordered_output, + bool remove_false_parts_of_where) { uint idx; double scan_time; @@ -2978,7 +2409,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, DBUG_PRINT("enter",("keys_to_use: %lu prev_tables: %lu const_tables: %lu", (ulong) keys_to_use.to_ulonglong(), (ulong) prev_tables, (ulong) const_tables)); - DBUG_PRINT("info", ("records: %lu", (ulong) head->file->stats.records)); + DBUG_PRINT("info", ("records: %lu", (ulong) head->stat_records())); delete quick; quick=0; needed_reg.clear_all(); @@ -2986,7 +2417,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, DBUG_ASSERT(!head->is_filled_at_execution()); if (keys_to_use.is_clear_all() || head->is_filled_at_execution()) DBUG_RETURN(0); - records= head->file->stats.records; + records= head->stat_records(); if (!records) records++; /* purecov: inspected */ scan_time= (double) records / TIME_FOR_COMPARE + 1; @@ -2995,8 +2426,8 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, scan_time= read_time= DBL_MAX; if (limit < records) read_time= (double) records + scan_time + 1; // Force to use index - else if (read_time <= 2.0 && !force_quick_range) - DBUG_RETURN(0); /* No need for quick select */ + + possible_keys.clear_all(); DBUG_PRINT("info",("Time to scan table: %g", read_time)); @@ -3027,10 +2458,13 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, param.imerge_cost_buff_size= 0; param.using_real_indexes= TRUE; param.remove_jump_scans= TRUE; + param.remove_false_where_parts= remove_false_parts_of_where; param.force_default_mrr= ordered_output; + param.possible_keys.clear_all(); thd->no_errors=1; // Don't warn about NULL - init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); + init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0, + MYF(MY_THREAD_SPECIFIC)); if (!(param.key_parts= (KEY_PART*) alloc_root(&alloc, sizeof(KEY_PART) * @@ -3042,13 +2476,13 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, DBUG_RETURN(0); // Can't use range } key_parts= param.key_parts; - thd->mem_root= &alloc; /* Make an array with description of all key parts of all table keys. This is used in get_mm_parts function. */ key_info= head->key_info; + uint max_key_len= 0; for (idx=0 ; idx < head->s->keys ; idx++, key_info++) { KEY_PART_INFO *key_part_info; @@ -3061,6 +2495,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, param.key[param.keys]=key_parts; key_part_info= key_info->key_part; + uint cur_key_len= 0; for (uint part= 0 ; part < n_key_parts ; part++, key_parts++, key_part_info++) { @@ -3068,6 +2503,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, key_parts->part= part; key_parts->length= key_part_info->length; key_parts->store_length= key_part_info->store_length; + cur_key_len += key_part_info->store_length; key_parts->field= key_part_info->field; key_parts->null_bit= key_part_info->null_bit; key_parts->image_type = @@ -3076,12 +2512,24 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, key_parts->flag= (uint8) key_part_info->key_part_flag; } param.real_keynr[param.keys++]=idx; + if (cur_key_len > max_key_len) + max_key_len= cur_key_len; } param.key_parts_end=key_parts; param.alloced_sel_args= 0; + max_key_len++; /* Take into account the "+1" in QUICK_RANGE::QUICK_RANGE */ + if (!(param.min_key= (uchar*)alloc_root(&alloc,max_key_len)) || + !(param.max_key= (uchar*)alloc_root(&alloc,max_key_len))) + { + thd->no_errors=0; + free_root(&alloc,MYF(0)); // Return memory & allocator + DBUG_RETURN(0); // Can't use range + } + + thd->mem_root= &alloc; /* Calculate cost of full index read for the shortest covering index */ - if (!head->covering_keys.is_clear_all()) + if (!force_quick_range && !head->covering_keys.is_clear_all()) { int key_for_use= find_shortest_key(head, &head->covering_keys); double key_read_time= head->file->keyread_time(key_for_use, 1, records) + @@ -3098,7 +2546,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, if (cond) { - if ((tree= get_mm_tree(¶m,cond))) + if ((tree= cond->get_mm_tree(¶m, &cond))) { if (tree->type == SEL_TREE::IMPOSSIBLE) { @@ -3122,8 +2570,8 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, group_trp= get_best_group_min_max(¶m, tree, best_read_time); if (group_trp) { - param.table->quick_condition_rows= min(group_trp->records, - head->file->stats.records); + param.table->quick_condition_rows= MY_MIN(group_trp->records, + head->stat_records()); if (group_trp->read_cost < best_read_time) { best_trp= group_trp; @@ -3198,12 +2646,13 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, } } - if (optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE)) + if (optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE) && + head->stat_records() != 0) { /* Try creating index_merge/ROR-union scan. */ SEL_IMERGE *imerge; - TABLE_READ_PLAN *best_conj_trp= NULL, *new_conj_trp; - LINT_INIT(new_conj_trp); /* no empty index_merge lists possible */ + TABLE_READ_PLAN *best_conj_trp= NULL, + *UNINIT_VAR(new_conj_trp); /* no empty index_merge lists possible */ DBUG_PRINT("info",("No range reads possible," " trying to construct index_merge")); List_iterator_fast<SEL_IMERGE> it(tree->merges); @@ -3238,6 +2687,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, quick= NULL; } } + possible_keys= param.possible_keys; free_mem: free_root(&alloc,MYF(0)); // Return memory & allocator @@ -3251,12 +2701,571 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, Assume that if the user is using 'limit' we will only need to scan limit rows if we are using a key */ - DBUG_RETURN(records ? test(quick) : -1); + DBUG_RETURN(records ? MY_TEST(quick) : -1); +} + +/**************************************************************************** + * Condition selectivity module + ****************************************************************************/ + + +/* + Build descriptors of pseudo-indexes over columns to perform range analysis + + SYNOPSIS + create_key_parts_for_pseudo_indexes() + param IN/OUT data structure for the descriptors to be built + used_fields bitmap of columns for which the descriptors are to be built + + DESCRIPTION + For each column marked in the bitmap used_fields the function builds + a descriptor of a single-component pseudo-index over this column that + can be used for the range analysis of the predicates over this columns. + The descriptors are created in the memory of param->mem_root. + + RETURN + FALSE in the case of success + TRUE otherwise +*/ + +static +bool create_key_parts_for_pseudo_indexes(RANGE_OPT_PARAM *param, + MY_BITMAP *used_fields) +{ + Field **field_ptr; + TABLE *table= param->table; + uint parts= 0; + + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + Field *field= *field_ptr; + if (bitmap_is_set(used_fields, field->field_index) && + is_eits_usable(field)) + parts++; + } + + KEY_PART *key_part; + uint keys= 0; + + if (!parts) + return TRUE; + + if (!(key_part= (KEY_PART *) alloc_root(param->mem_root, + sizeof(KEY_PART) * parts))) + return TRUE; + + param->key_parts= key_part; + uint max_key_len= 0; + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + Field *field= *field_ptr; + if (bitmap_is_set(used_fields, field->field_index)) + { + if (!is_eits_usable(field)) + continue; + + uint16 store_length; + uint16 max_key_part_length= (uint16) table->file->max_key_part_length(); + key_part->key= keys; + key_part->part= 0; + if (field->flags & BLOB_FLAG) + key_part->length= max_key_part_length; + else + { + key_part->length= (uint16) field->key_length(); + set_if_smaller(key_part->length, max_key_part_length); + } + store_length= key_part->length; + if (field->real_maybe_null()) + store_length+= HA_KEY_NULL_LENGTH; + if (field->real_type() == MYSQL_TYPE_VARCHAR) + store_length+= HA_KEY_BLOB_LENGTH; + if (max_key_len < store_length) + max_key_len= store_length; + key_part->store_length= store_length; + key_part->field= field; + key_part->image_type= Field::itRAW; + key_part->flag= 0; + param->key[keys]= key_part; + keys++; + key_part++; + } + } + + max_key_len++; /* Take into account the "+1" in QUICK_RANGE::QUICK_RANGE */ + if (!(param->min_key= (uchar*)alloc_root(param->mem_root, max_key_len)) || + !(param->max_key= (uchar*)alloc_root(param->mem_root, max_key_len))) + { + return true; + } + param->keys= keys; + param->key_parts_end= key_part; + + return FALSE; +} + + +/* + Estimate the number of rows in all ranges built for a column + by the range optimizer + + SYNOPSIS + records_in_column_ranges() + param the data structure to access descriptors of pseudo indexes + built over columns used in the condition of the processed query + idx the index of the descriptor of interest in param + tree the tree representing ranges built for the interesting column + + DESCRIPTION + This function retrieves the ranges represented by the SEL_ARG 'tree' and + for each of them r it calls the function get_column_range_cardinality() + that estimates the number of expected rows in r. It is assumed that param + is the data structure containing the descriptors of pseudo-indexes that + has been built to perform range analysis of the range conditions imposed + on the columns used in the processed query, while idx is the index of the + descriptor created in 'param' exactly for the column for which 'tree' + has been built by the range optimizer. + + RETURN + the number of rows in the retrieved ranges +*/ + +static +double records_in_column_ranges(PARAM *param, uint idx, + SEL_ARG *tree) +{ + SEL_ARG_RANGE_SEQ seq; + KEY_MULTI_RANGE range; + range_seq_t seq_it; + double rows; + Field *field; + uint flags= 0; + double total_rows= 0; + RANGE_SEQ_IF seq_if = {NULL, sel_arg_range_seq_init, + sel_arg_range_seq_next, 0, 0}; + + /* Handle cases when we don't have a valid non-empty list of range */ + if (!tree) + return HA_POS_ERROR; + if (tree->type == SEL_ARG::IMPOSSIBLE) + return (0L); + + field= tree->field; + + seq.keyno= idx; + seq.real_keyno= MAX_KEY; + seq.param= param; + seq.start= tree; + + seq_it= seq_if.init((void *) &seq, 0, flags); + + while (!seq_if.next(seq_it, &range)) + { + key_range *min_endp, *max_endp; + min_endp= range.start_key.length? &range.start_key : NULL; + max_endp= range.end_key.length? &range.end_key : NULL; + rows= get_column_range_cardinality(field, min_endp, max_endp, + range.range_flag); + if (HA_POS_ERROR == rows) + { + total_rows= HA_POS_ERROR; + break; + } + total_rows += rows; + } + return total_rows; +} + + +/* + Calculate the selectivity of the condition imposed on the rows of a table + + SYNOPSIS + calculate_cond_selectivity_for_table() + thd the context handle + table the table of interest + cond conditions imposed on the rows of the table + + DESCRIPTION + This function calculates the selectivity of range conditions cond imposed + on the rows of 'table' in the processed query. + The calculated selectivity is assigned to the field table->cond_selectivity. + + Selectivity is calculated as a product of selectivities imposed by: + + 1. possible range accesses. (if multiple range accesses use the same + restrictions on the same field, we make adjustments for that) + 2. Sargable conditions on fields for which we have column statistics (if + a field is used in a possible range access, we assume that selectivity + is already provided by the range access' estimates) + 3. Reading a few records from the table pages and checking the condition + selectivity (this is used for conditions like "column LIKE '%val%'" + where approaches #1 and #2 do not provide selectivity data). + + NOTE + Currently the selectivities of range conditions over different columns are + considered independent. + + RETURN + FALSE on success + TRUE otherwise +*/ + +bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item **cond) +{ + uint keynr; + uint max_quick_key_parts= 0; + MY_BITMAP *used_fields= &table->cond_set; + double table_records= table->stat_records(); + DBUG_ENTER("calculate_cond_selectivity_for_table"); + + table->cond_selectivity= 1.0; + + if (table_records == 0) + DBUG_RETURN(FALSE); + + QUICK_SELECT_I *quick; + if ((quick=table->reginfo.join_tab->quick) && + quick->get_type() == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) + { + table->cond_selectivity*= (quick->records/table_records); + DBUG_RETURN(FALSE); + } + + if (!*cond) + DBUG_RETURN(FALSE); + + if (table->pos_in_table_list->schema_table) + DBUG_RETURN(FALSE); + + MY_BITMAP handled_columns; + my_bitmap_map* buf; + if (!(buf= (my_bitmap_map*)thd->alloc(table->s->column_bitmap_size))) + DBUG_RETURN(TRUE); + my_bitmap_init(&handled_columns, buf, table->s->fields, FALSE); + + /* + Calculate the selectivity of the range conditions supported by indexes. + + First, take into account possible range accesses. + range access estimates are the most precise, we prefer them to any other + estimate sources. + */ + + for (keynr= 0; keynr < table->s->keys; keynr++) + { + if (table->quick_keys.is_set(keynr)) + set_if_bigger(max_quick_key_parts, table->quick_key_parts[keynr]); + } + + /* + Walk through all indexes, indexes where range access uses more keyparts + go first. + */ + for (uint quick_key_parts= max_quick_key_parts; + quick_key_parts; quick_key_parts--) + { + for (keynr= 0; keynr < table->s->keys; keynr++) + { + if (table->quick_keys.is_set(keynr) && + table->quick_key_parts[keynr] == quick_key_parts) + { + uint i; + uint used_key_parts= table->quick_key_parts[keynr]; + double quick_cond_selectivity= table->quick_rows[keynr] / + table_records; + KEY *key_info= table->key_info + keynr; + KEY_PART_INFO* key_part= key_info->key_part; + /* + Suppose, there are range conditions on two keys + KEY1 (col1, col2) + KEY2 (col3, col2) + + we don't want to count selectivity of condition on col2 twice. + + First, find the longest key prefix that's made of columns whose + selectivity wasn't already accounted for. + */ + for (i= 0; i < used_key_parts; i++, key_part++) + { + if (bitmap_is_set(&handled_columns, key_part->fieldnr-1)) + break; + bitmap_set_bit(&handled_columns, key_part->fieldnr-1); + } + if (i) + { + double UNINIT_VAR(selectivity_mult); + + /* + There is at least 1-column prefix of columns whose selectivity has + not yet been accounted for. + */ + table->cond_selectivity*= quick_cond_selectivity; + if (i != used_key_parts) + { + /* + Range access got us estimate for #used_key_parts. + We need estimate for #(i-1) key parts. + */ + double f1= key_info->actual_rec_per_key(i-1); + double f2= key_info->actual_rec_per_key(i); + if (f1 > 0 && f2 > 0) + selectivity_mult= f1 / f2; + else + { + /* + No statistics available, assume the selectivity is proportional + to the number of key parts. + (i=0 means 1 keypart, i=1 means 2 keyparts, so use i+1) + */ + selectivity_mult= ((double)(i+1)) / i; + } + table->cond_selectivity*= selectivity_mult; + } + /* + We need to set selectivity for fields supported by indexes. + For single-component indexes and for some first components + of other indexes we do it here. For the remaining fields + we do it later in this function, in the same way as for the + fields not used in any indexes. + */ + if (i == 1) + { + uint fieldnr= key_info->key_part[0].fieldnr; + table->field[fieldnr-1]->cond_selectivity= quick_cond_selectivity; + if (i != used_key_parts) + table->field[fieldnr-1]->cond_selectivity*= selectivity_mult; + bitmap_clear_bit(used_fields, fieldnr-1); + } + } + } + } + } + + /* + Second step: calculate the selectivity of the range conditions not + supported by any index and selectivity of the range condition + over the fields whose selectivity has not been set yet. + */ + + if (thd->variables.optimizer_use_condition_selectivity > 2 && + !bitmap_is_clear_all(used_fields) && + thd->variables.use_stat_tables > 0) + { + PARAM param; + MEM_ROOT alloc; + SEL_TREE *tree; + double rows; + + init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0, + MYF(MY_THREAD_SPECIFIC)); + param.thd= thd; + param.mem_root= &alloc; + param.old_root= thd->mem_root; + param.table= table; + param.is_ror_scan= FALSE; + param.remove_false_where_parts= true; + + if (create_key_parts_for_pseudo_indexes(¶m, used_fields)) + goto free_alloc; + + param.prev_tables= param.read_tables= 0; + param.current_table= table->map; + param.using_real_indexes= FALSE; + param.real_keynr[0]= 0; + param.alloced_sel_args= 0; + + thd->no_errors=1; + + tree= cond[0]->get_mm_tree(¶m, cond); + + if (!tree) + goto free_alloc; + + table->reginfo.impossible_range= 0; + if (tree->type == SEL_TREE::IMPOSSIBLE) + { + rows= 0; + table->reginfo.impossible_range= 1; + goto free_alloc; + } + else if (tree->type == SEL_TREE::ALWAYS) + { + rows= table_records; + goto free_alloc; + } + else if (tree->type == SEL_TREE::MAYBE) + { + rows= table_records; + goto free_alloc; + } + + for (uint idx= 0; idx < param.keys; idx++) + { + SEL_ARG *key= tree->keys[idx]; + if (key) + { + if (key->type == SEL_ARG::IMPOSSIBLE) + { + rows= 0; + table->reginfo.impossible_range= 1; + goto free_alloc; + } + else + { + rows= records_in_column_ranges(¶m, idx, key); + if (rows != HA_POS_ERROR) + key->field->cond_selectivity= rows/table_records; + } + } + } + + for (Field **field_ptr= table->field; *field_ptr; field_ptr++) + { + Field *table_field= *field_ptr; + if (bitmap_is_set(used_fields, table_field->field_index) && + table_field->cond_selectivity < 1.0) + { + if (!bitmap_is_set(&handled_columns, table_field->field_index)) + table->cond_selectivity*= table_field->cond_selectivity; + } + } + + free_alloc: + thd->no_errors= 0; + thd->mem_root= param.old_root; + free_root(&alloc, MYF(0)); + + } + + if (quick && (quick->get_type() == QUICK_SELECT_I::QS_TYPE_ROR_UNION || + quick->get_type() == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)) + { + table->cond_selectivity*= (quick->records/table_records); + } + + bitmap_union(used_fields, &handled_columns); + + /* Check if we can improve selectivity estimates by using sampling */ + ulong check_rows= + MY_MIN(thd->variables.optimizer_selectivity_sampling_limit, + (ulong) (table_records * SELECTIVITY_SAMPLING_SHARE)); + if (*cond && check_rows > SELECTIVITY_SAMPLING_THRESHOLD && + thd->variables.optimizer_use_condition_selectivity > 4) + { + find_selective_predicates_list_processor_data *dt= + (find_selective_predicates_list_processor_data *) + alloc_root(thd->mem_root, + sizeof(find_selective_predicates_list_processor_data)); + if (!dt) + DBUG_RETURN(TRUE); + dt->list.empty(); + dt->table= table; + if ((*cond)->walk(&Item::find_selective_predicates_list_processor, 0, + (uchar*) dt)) + DBUG_RETURN(TRUE); + if (dt->list.elements > 0) + { + check_rows= check_selectivity(thd, check_rows, table, &dt->list); + if (check_rows > SELECTIVITY_SAMPLING_THRESHOLD) + { + COND_STATISTIC *stat; + List_iterator_fast<COND_STATISTIC> it(dt->list); + double examined_rows= check_rows; + while ((stat= it++)) + { + if (!stat->positive) + { + DBUG_PRINT("info", ("To avoid 0 assigned 1 to the counter")); + stat->positive= 1; // avoid 0 + } + DBUG_PRINT("info", ("The predicate selectivity : %g", + (double)stat->positive / examined_rows)); + double selectivity= ((double)stat->positive) / examined_rows; + table->cond_selectivity*= selectivity; + /* + If a field is involved then we register its selectivity in case + there in an equality with the field. + For example in case + t1.a LIKE "%bla%" and t1.a = t2.b + the selectivity we have found could be used also for t2. + */ + if (stat->field_arg) + { + stat->field_arg->cond_selectivity*= selectivity; + + if (stat->field_arg->next_equal_field) + { + for (Field *next_field= stat->field_arg->next_equal_field; + next_field != stat->field_arg; + next_field= next_field->next_equal_field) + { + next_field->cond_selectivity*= selectivity; + next_field->table->cond_selectivity*= selectivity; + } + } + } + } + + } + /* This list and its elements put to mem_root so should not be freed */ + table->cond_selectivity_sampling_explain= &dt->list; + } + } + + DBUG_RETURN(FALSE); } /**************************************************************************** + * Condition selectivity code ends + ****************************************************************************/ + +/**************************************************************************** * Partition pruning module ****************************************************************************/ + +/* + Store field key image to table record + + SYNOPSIS + store_key_image_to_rec() + field Field which key image should be stored + ptr Field value in key format + len Length of the value, in bytes + + ATTENTION + len is the length of the value not counting the NULL-byte (at the same + time, ptr points to the key image, which starts with NULL-byte for + nullable columns) + + DESCRIPTION + Copy the field value from its key image to the table record. The source + is the value in key image format, occupying len bytes in buffer pointed + by ptr. The destination is table record, in "field value in table record" + format. +*/ + +void store_key_image_to_rec(Field *field, uchar *ptr, uint len) +{ + /* Do the same as print_key() does */ + my_bitmap_map *old_map; + + if (field->real_maybe_null()) + { + if (*ptr) + { + field->set_null(); + return; + } + field->set_notnull(); + ptr++; + } + old_map= dbug_tmp_use_all_columns(field->table, + field->table->write_set); + field->set_key_image(ptr, len); + dbug_tmp_restore_column_map(field->table->write_set, old_map); +} + #ifdef WITH_PARTITION_STORAGE_ENGINE /* @@ -3381,8 +3390,8 @@ typedef struct st_part_prune_param int last_subpart_partno; /* Same as above for supartitioning */ /* - is_part_keypart[i] == test(keypart #i in partitioning index is a member - used in partitioning) + is_part_keypart[i] == MY_TEST(keypart #i in partitioning index is a member + used in partitioning) Used to maintain current values of cur_part_fields and cur_subpart_fields */ my_bool *is_part_keypart; @@ -3429,29 +3438,26 @@ static void dbug_print_singlepoint_range(SEL_ARG **start, uint num); #endif -/* +/** Perform partition pruning for a given table and condition. - SYNOPSIS - prune_partitions() - thd Thread handle - table Table to perform partition pruning for - pprune_cond Condition to use for partition pruning + @param thd Thread handle + @param table Table to perform partition pruning for + @param pprune_cond Condition to use for partition pruning - DESCRIPTION - This function assumes that all partitions are marked as unused when it - is invoked. The function analyzes the condition, finds partitions that - need to be used to retrieve the records that match the condition, and - marks them as used by setting appropriate bit in part_info->used_partitions - In the worst case all partitions are marked as used. - - NOTE - This function returns promptly if called for non-partitioned table. - - RETURN - TRUE We've inferred that no partitions need to be used (i.e. no table - records will satisfy pprune_cond) - FALSE Otherwise + @note This function assumes that lock_partitions are setup when it + is invoked. The function analyzes the condition, finds partitions that + need to be used to retrieve the records that match the condition, and + marks them as used by setting appropriate bit in part_info->read_partitions + In the worst case all partitions are marked as used. If the table is not + yet locked, it will also unset bits in part_info->lock_partitions that is + not set in read_partitions. + + This function returns promptly if called for non-partitioned table. + + @return Operation status + @retval true Failure + @retval false Success */ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) @@ -3475,7 +3481,8 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) my_bitmap_map *old_sets[2]; prune_param.part_info= part_info; - init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); + init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0, + MYF(MY_THREAD_SPECIFIC)); range_par->mem_root= &alloc; range_par->old_root= thd->mem_root; @@ -3493,6 +3500,8 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) /* range_par->cond doesn't need initialization */ range_par->prev_tables= range_par->read_tables= 0; range_par->current_table= table->map; + /* It should be possible to switch the following ON: */ + range_par->remove_false_where_parts= false; range_par->keys= 1; // one index range_par->using_real_indexes= FALSE; @@ -3503,13 +3512,13 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) thd->no_errors=1; // Don't warn about NULL thd->mem_root=&alloc; - bitmap_clear_all(&part_info->used_partitions); + bitmap_clear_all(&part_info->read_partitions); prune_param.key= prune_param.range_param.key_parts; SEL_TREE *tree; int res; - tree= get_mm_tree(range_par, pprune_cond); + tree= pprune_cond->get_mm_tree(range_par, &pprune_cond); if (!tree) goto all_used; @@ -3575,7 +3584,7 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) res == 1 => some used partitions => retval=FALSE res == -1 - we jump over this line to all_used: */ - retval= test(!res); + retval= MY_TEST(!res); goto end; all_used: @@ -3586,45 +3595,34 @@ end: thd->no_errors=0; thd->mem_root= range_par->old_root; free_root(&alloc,MYF(0)); // Return memory & allocator - DBUG_RETURN(retval); -} - - -/* - Store field key image to table record - - SYNOPSIS - store_key_image_to_rec() - field Field which key image should be stored - ptr Field value in key format - len Length of the value, in bytes - - DESCRIPTION - Copy the field value from its key image to the table record. The source - is the value in key image format, occupying len bytes in buffer pointed - by ptr. The destination is table record, in "field value in table record" - format. -*/ - -void store_key_image_to_rec(Field *field, uchar *ptr, uint len) -{ - /* Do the same as print_key() does */ - my_bitmap_map *old_map; - - if (field->real_maybe_null()) + /* + Must be a subset of the locked partitions. + lock_partitions contains the partitions marked by explicit partition + selection (... t PARTITION (pX) ...) and we must only use partitions + within that set. + */ + bitmap_intersect(&prune_param.part_info->read_partitions, + &prune_param.part_info->lock_partitions); + /* + If not yet locked, also prune partitions to lock if not UPDATEing + partition key fields. This will also prune lock_partitions if we are under + LOCK TABLES, so prune away calls to start_stmt(). + TODO: enhance this prune locking to also allow pruning of + 'UPDATE t SET part_key = const WHERE cond_is_prunable' so it adds + a lock for part_key partition. + */ + if (table->file->get_lock_type() == F_UNLCK && + !partition_key_modified(table, table->write_set)) { - if (*ptr) - { - field->set_null(); - return; - } - field->set_notnull(); - ptr++; - } - old_map= dbug_tmp_use_all_columns(field->table, - field->table->write_set); - field->set_key_image(ptr, len); - dbug_tmp_restore_column_map(field->table->write_set, old_map); + bitmap_copy(&prune_param.part_info->lock_partitions, + &prune_param.part_info->read_partitions); + } + if (bitmap_is_clear_all(&(prune_param.part_info->read_partitions))) + { + table->all_partitions_pruned_away= true; + retval= TRUE; + } + DBUG_RETURN(retval); } @@ -3661,7 +3659,7 @@ static void mark_full_partition_used_no_parts(partition_info* part_info, { DBUG_ENTER("mark_full_partition_used_no_parts"); DBUG_PRINT("enter", ("Mark partition %u as used", part_id)); - bitmap_set_bit(&part_info->used_partitions, part_id); + bitmap_set_bit(&part_info->read_partitions, part_id); DBUG_VOID_RETURN; } @@ -3677,7 +3675,7 @@ static void mark_full_partition_used_with_parts(partition_info *part_info, for (; start != end; start++) { DBUG_PRINT("info", ("1:Mark subpartition %u as used", start)); - bitmap_set_bit(&part_info->used_partitions, start); + bitmap_set_bit(&part_info->read_partitions, start); } DBUG_VOID_RETURN; } @@ -3705,7 +3703,7 @@ static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar, MY_BITMAP all_merges; uint bitmap_bytes; my_bitmap_map *bitmap_buf; - uint n_bits= ppar->part_info->used_partitions.n_bits; + uint n_bits= ppar->part_info->read_partitions.n_bits; bitmap_bytes= bitmap_buffer_size(n_bits); if (!(bitmap_buf= (my_bitmap_map*) alloc_root(ppar->range_param.mem_root, bitmap_bytes))) @@ -3716,7 +3714,7 @@ static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar, */ return find_used_partitions_imerge(ppar, merges.head()); } - bitmap_init(&all_merges, bitmap_buf, n_bits, FALSE); + my_bitmap_init(&all_merges, bitmap_buf, n_bits, FALSE); bitmap_set_prefix(&all_merges, n_bits); List_iterator<SEL_IMERGE> it(merges); @@ -3731,14 +3729,15 @@ static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar, } if (res != -1) - bitmap_intersect(&all_merges, &ppar->part_info->used_partitions); + bitmap_intersect(&all_merges, &ppar->part_info->read_partitions); + if (bitmap_is_clear_all(&all_merges)) return 0; - bitmap_clear_all(&ppar->part_info->used_partitions); + bitmap_clear_all(&ppar->part_info->read_partitions); } - memcpy(ppar->part_info->used_partitions.bitmap, all_merges.bitmap, + memcpy(ppar->part_info->read_partitions.bitmap, all_merges.bitmap, bitmap_bytes); return 1; } @@ -4107,7 +4106,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) { for (uint i= 0; i < ppar->part_info->num_subparts; i++) if (bitmap_is_set(&ppar->subparts_bitmap, i)) - bitmap_set_bit(&ppar->part_info->used_partitions, + bitmap_set_bit(&ppar->part_info->read_partitions, part_id * ppar->part_info->num_subparts + i); } goto pop_and_go_right; @@ -4169,7 +4168,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) != NOT_A_PARTITION_ID) { - bitmap_set_bit(&part_info->used_partitions, + bitmap_set_bit(&part_info->read_partitions, part_id * part_info->num_subparts + subpart_id); } res= 1; /* Some partitions were marked as used */ @@ -4227,7 +4226,7 @@ process_next_key_part: ppar->mark_full_partition_used(ppar->part_info, part_id); found= TRUE; } - res= test(found); + res= MY_TEST(found); } /* Restore the "used partitions iterator" to the default setting that @@ -4255,7 +4254,8 @@ pop_and_go_right: static void mark_all_partitions_as_used(partition_info *part_info) { - bitmap_set_all(&part_info->used_partitions); + bitmap_copy(&(part_info->read_partitions), + &(part_info->lock_partitions)); } @@ -4370,19 +4370,21 @@ static bool create_partition_index_description(PART_PRUNE_PARAM *ppar) uint32 bufsize= bitmap_buffer_size(ppar->part_info->num_subparts); if (!(buf= (my_bitmap_map*) alloc_root(alloc, bufsize))) return TRUE; - bitmap_init(&ppar->subparts_bitmap, buf, ppar->part_info->num_subparts, + my_bitmap_init(&ppar->subparts_bitmap, buf, ppar->part_info->num_subparts, FALSE); } range_par->key_parts= key_part; Field **field= (ppar->part_fields)? part_info->part_field_array : part_info->subpart_field_array; bool in_subpart_fields= FALSE; + uint total_key_len= 0; for (uint part= 0; part < total_parts; part++, key_part++) { key_part->key= 0; key_part->part= part; key_part->length= (uint16)(*field)->key_length(); key_part->store_length= (uint16)get_partition_field_store_length(*field); + total_key_len += key_part->store_length; DBUG_PRINT("info", ("part %u length %u store_length %u", part, key_part->length, key_part->store_length)); @@ -4412,6 +4414,13 @@ static bool create_partition_index_description(PART_PRUNE_PARAM *ppar) } range_par->key_parts_end= key_part; + total_key_len++; /* Take into account the "+1" in QUICK_RANGE::QUICK_RANGE */ + if (!(range_par->min_key= (uchar*)alloc_root(alloc,total_key_len)) || + !(range_par->max_key= (uchar*)alloc_root(alloc,total_key_len))) + { + return true; + } + DBUG_EXECUTE("info", print_partitioning_index(range_par->key_parts, range_par->key_parts_end);); return FALSE; @@ -4738,7 +4747,7 @@ TABLE_READ_PLAN *get_best_disjunct_quick(PARAM *param, SEL_IMERGE *imerge, DBUG_PRINT("info", ("index_merge scans cost %g", imerge_cost)); if (imerge_too_expensive || (imerge_cost > read_time) || ((non_cpk_scan_records+cpk_scan_records >= - param->table->file->stats.records) && + param->table->stat_records()) && read_time != DBL_MAX)) { /* @@ -4808,8 +4817,8 @@ TABLE_READ_PLAN *get_best_disjunct_quick(PARAM *param, SEL_IMERGE *imerge, { imerge_trp->read_cost= imerge_cost; imerge_trp->records= non_cpk_scan_records + cpk_scan_records; - imerge_trp->records= min(imerge_trp->records, - param->table->file->stats.records); + imerge_trp->records= MY_MIN(imerge_trp->records, + param->table->stat_records()); imerge_trp->range_scans= range_scans; imerge_trp->range_scans_end= range_scans + n_child_scans; read_time= imerge_cost; @@ -4880,7 +4889,7 @@ skip_to_ror_scan: ((TRP_ROR_INTERSECT*)(*cur_roru_plan))->index_scan_costs; roru_total_records += (*cur_roru_plan)->records; roru_intersect_part *= (*cur_roru_plan)->records / - param->table->file->stats.records; + param->table->stat_records(); } /* @@ -4890,7 +4899,7 @@ skip_to_ror_scan: in disjunction do not share key parts. */ roru_total_records -= (ha_rows)(roru_intersect_part* - param->table->file->stats.records); + param->table->stat_records()); /* ok, got a ROR read plan for each of the disjuncts Calculate cost: cost(index_union_scan(scan_1, ... scan_n)) = @@ -4979,8 +4988,8 @@ TABLE_READ_PLAN *merge_same_index_scans(PARAM *param, SEL_IMERGE *imerge, { SEL_TREE **changed_tree= imerge->trees+(*tree_idx_ptr-1); SEL_ARG *key= (*changed_tree)->keys[key_idx]; - bzero((*changed_tree)->keys, - sizeof((*changed_tree)->keys[0])*param->keys); + for (uint i= 0; i < param->keys; i++) + (*changed_tree)->keys[i]= NULL; (*changed_tree)->keys_map.clear_all(); if (key) key->incr_refs(); @@ -5101,6 +5110,16 @@ typedef struct st_partial_index_intersect_info key_map filtered_scans; /* scans to be filtered by cpk conditions */ MY_BITMAP *intersect_fields; /* bitmap of fields used in intersection */ + + void init() + { + common_info= NULL; + intersect_fields= NULL; + records_sent_to_unique= records= length= in_memory= use_cpk_filter= 0; + cost= index_read_cost= in_memory_cost= 0.0; + filtered_scans.init(); + filtered_scans.clear_all(); + } } PARTIAL_INDEX_INTERSECT_INFO; @@ -5130,7 +5149,7 @@ bool create_fields_bitmap(PARAM *param, MY_BITMAP *fields_bitmap) if (!(bitmap_buf= (my_bitmap_map *) alloc_root(param->mem_root, param->fields_bitmap_size))) return TRUE; - if (bitmap_init(fields_bitmap, bitmap_buf, param->table->s->fields, FALSE)) + if (my_bitmap_init(fields_bitmap, bitmap_buf, param->table->s->fields, FALSE)) return TRUE; return FALSE; @@ -5171,12 +5190,12 @@ static inline ha_rows get_table_cardinality_for_index_intersect(TABLE *table) { if (table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT) - return table->file->stats.records; + return table->stat_records(); else { ha_rows d; double q; - for (q= (double)table->file->stats.records, d= 1 ; q >= 10; q/= 10, d*= 10 ) ; + for (q= (double)table->stat_records(), d= 1 ; q >= 10; q/= 10, d*= 10 ) ; return (ha_rows) (floor(q+0.5) * d); } } @@ -5237,7 +5256,7 @@ bool prepare_search_best_index_intersect(PARAM *param, if (!n_index_scans) return 1; - bzero(init, sizeof(*init)); + init->init(); init->common_info= common; init->cost= cutoff_cost; @@ -5268,7 +5287,7 @@ bool prepare_search_best_index_intersect(PARAM *param, } } - i= n_index_scans - test(cpk_scan != NULL) + 1; + i= n_index_scans - MY_TEST(cpk_scan != NULL) + 1; if (!(common->search_scans = (INDEX_SCAN_INFO **) alloc_root (param->mem_root, @@ -5338,7 +5357,7 @@ bool prepare_search_best_index_intersect(PARAM *param, if (!(common->best_intersect= (INDEX_SCAN_INFO **) alloc_root (param->mem_root, sizeof(INDEX_SCAN_INFO *) * - (i + test(cpk_scan != NULL))))) + (i + MY_TEST(cpk_scan != NULL))))) return TRUE; size_t calc_cost_buff_size= @@ -5400,7 +5419,7 @@ bool prepare_search_best_index_intersect(PARAM *param, this number by #r. If we do not make any assumptions then we can only state that - #r<=min(#r1,#r2). + #r<=MY_MIN(#r1,#r2). With this estimate we can't say that the index intersection scan will be cheaper than the cheapest index scan. @@ -5433,7 +5452,7 @@ bool prepare_search_best_index_intersect(PARAM *param, #rt2_0 of the same range for sub-index idx2_0(dept) of the index idx2. The current code does not make an estimate either for #rt1_0, or for #rt2_0, but it can be adjusted to provide those numbers. - Alternatively, min(rec_per_key) for (dept) could be used to get an upper + Alternatively, MY_MIN(rec_per_key) for (dept) could be used to get an upper bound for the value of sel(Rt1&Rt2). Yet this statistics is not provided now. @@ -5444,7 +5463,7 @@ bool prepare_search_best_index_intersect(PARAM *param, sel(Rt1&Rt2)=sel(dept=5)*sel(last_name='Sm5')*sel(first_name='Robert') =sel(Rt2)*sel(dept=5) - Here max(rec_per_key) for (dept) could be used to get an upper bound for + Here MY_MAX(rec_per_key) for (dept) could be used to get an upper bound for the value of sel(Rt1&Rt2). When the intersected indexes have different major columns, but some @@ -5497,9 +5516,9 @@ bool prepare_search_best_index_intersect(PARAM *param, f_1 = rec_per_key[first_name]/rec_per_key[last_name]. The the number of records in the range tree: Rt_0: (first_name='Robert' OR first_name='Bob') - for the sub-index (first_name) is not greater than max(#r*f_1, #t). + for the sub-index (first_name) is not greater than MY_MAX(#r*f_1, #t). Strictly speaking, we can state only that it's not greater than - max(#r*max_f_1, #t), where + MY_MAX(#r*max_f_1, #t), where max_f_1= max_rec_per_key[first_name]/min_rec_per_key[last_name]. Yet, if #r/#t is big enough (and this is the case of an index intersection, because using this index range with a single index scan is cheaper than @@ -5579,9 +5598,8 @@ ha_rows records_in_index_intersect_extension(PARTIAL_INDEX_INTERSECT_INFO *curr, ha_rows ext_records= ext_index_scan->records; if (i < used_key_parts) { - ulong *rec_per_key= key_info->rec_per_key+i-1; - ulong f1= rec_per_key[0] ? rec_per_key[0] : 1; - ulong f2= rec_per_key[1] ? rec_per_key[1] : 1; + double f1= key_info->actual_rec_per_key(i-1); + double f2= key_info->actual_rec_per_key(i); ext_records= (ha_rows) ((double) ext_records / f2 * f1); } if (ext_records < table_cardinality) @@ -5949,14 +5967,14 @@ ROR_SCAN_INFO *make_ror_scan(const PARAM *param, int idx, SEL_ARG *sel_arg) param->fields_bitmap_size))) DBUG_RETURN(NULL); - if (bitmap_init(&ror_scan->covered_fields, bitmap_buf, + if (my_bitmap_init(&ror_scan->covered_fields, bitmap_buf, param->table->s->fields, FALSE)) DBUG_RETURN(NULL); bitmap_clear_all(&ror_scan->covered_fields); KEY_PART_INFO *key_part= param->table->key_info[keynr].key_part; KEY_PART_INFO *key_part_end= key_part + - param->table->key_info[keynr].key_parts; + param->table->key_info[keynr].user_defined_key_parts; for (;key_part != key_part_end; ++key_part) { if (bitmap_is_set(¶m->needed_fields, key_part->fieldnr-1)) @@ -6067,13 +6085,13 @@ ROR_INTERSECT_INFO* ror_intersect_init(const PARAM *param) if (!(buf= (my_bitmap_map*) alloc_root(param->mem_root, param->fields_bitmap_size))) return NULL; - if (bitmap_init(&info->covered_fields, buf, param->table->s->fields, + if (my_bitmap_init(&info->covered_fields, buf, param->table->s->fields, FALSE)) return NULL; info->is_covering= FALSE; info->index_scan_costs= 0.0; info->index_records= 0; - info->out_rows= (double) param->table->file->stats.records; + info->out_rows= (double) param->table->stat_records(); bitmap_clear_all(&info->covered_fields); return info; } @@ -6191,23 +6209,23 @@ static double ror_scan_selectivity(const ROR_INTERSECT_INFO *info, SEL_ARG *sel_arg, *tuple_arg= NULL; key_part_map keypart_map= 0; bool cur_covered; - bool prev_covered= test(bitmap_is_set(&info->covered_fields, - key_part->fieldnr-1)); + bool prev_covered= MY_TEST(bitmap_is_set(&info->covered_fields, + key_part->fieldnr - 1)); key_range min_range; key_range max_range; min_range.key= key_val; min_range.flag= HA_READ_KEY_EXACT; max_range.key= key_val; max_range.flag= HA_READ_AFTER_KEY; - ha_rows prev_records= info->param->table->file->stats.records; + ha_rows prev_records= info->param->table->stat_records(); DBUG_ENTER("ror_scan_selectivity"); for (sel_arg= scan->sel_arg; sel_arg; sel_arg= sel_arg->next_key_part) { DBUG_PRINT("info",("sel_arg step")); - cur_covered= test(bitmap_is_set(&info->covered_fields, - key_part[sel_arg->part].fieldnr-1)); + cur_covered= MY_TEST(bitmap_is_set(&info->covered_fields, + key_part[sel_arg->part].fieldnr - 1)); if (cur_covered != prev_covered) { /* create (part1val, ..., part{n-1}val) tuple. */ @@ -6426,7 +6444,7 @@ TRP_ROR_INTERSECT *get_best_ror_intersect(const PARAM *param, SEL_TREE *tree, double min_cost= DBL_MAX; DBUG_ENTER("get_best_ror_intersect"); - if ((tree->n_ror_scans < 2) || !param->table->file->stats.records || + if ((tree->n_ror_scans < 2) || !param->table->stat_records() || !optimizer_flag(param->thd, OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT)) DBUG_RETURN(NULL); @@ -6548,6 +6566,8 @@ TRP_ROR_INTERSECT *get_best_ror_intersect(const PARAM *param, SEL_TREE *tree, if (ror_intersect_add(intersect, cpk_scan, TRUE) && (intersect->total_cost < min_cost)) intersect_best= intersect; //just set pointer here + else + cpk_scan= 0; // Don't use cpk_scan } else cpk_scan= 0; // Don't use cpk_scan @@ -6629,7 +6649,7 @@ TRP_ROR_INTERSECT *get_best_covering_ror_intersect(PARAM *param, for (ROR_SCAN_INFO **scan= tree->ror_scans; scan != ror_scans_end; ++scan) (*scan)->key_components= - param->table->key_info[(*scan)->keynr].key_parts; + param->table->key_info[(*scan)->keynr].user_defined_key_parts; /* Run covering-ROR-search algorithm. @@ -6644,7 +6664,7 @@ TRP_ROR_INTERSECT *get_best_covering_ror_intersect(PARAM *param, covered_fields->bitmap= (my_bitmap_map*)alloc_root(param->mem_root, param->fields_bitmap_size); if (!covered_fields->bitmap || - bitmap_init(covered_fields, covered_fields->bitmap, + my_bitmap_init(covered_fields, covered_fields->bitmap, param->table->s->fields, FALSE)) DBUG_RETURN(0); bitmap_clear_all(covered_fields); @@ -6757,8 +6777,8 @@ static TRP_RANGE *get_key_scans_params(PARAM *param, SEL_TREE *tree, bool update_tbl_stats, double read_time) { - uint idx; - SEL_ARG **key,**end, **key_to_read= NULL; + uint idx, best_idx; + SEL_ARG *key_to_read= NULL; ha_rows UNINIT_VAR(best_records); /* protected by key_to_read */ uint UNINIT_VAR(best_mrr_flags), /* protected by key_to_read */ UNINIT_VAR(best_buf_size); /* protected by key_to_read */ @@ -6781,24 +6801,25 @@ static TRP_RANGE *get_key_scans_params(PARAM *param, SEL_TREE *tree, sizeof(INDEX_SCAN_INFO *) * param->keys); } tree->index_scans_end= tree->index_scans; - for (idx= 0,key=tree->keys, end=key+param->keys; key != end; key++,idx++) + for (idx= 0; idx < param->keys; idx++) { - if (*key) + SEL_ARG *key= tree->keys[idx]; + if (key) { ha_rows found_records; - COST_VECT cost; + Cost_estimate cost; double found_read_time; uint mrr_flags, buf_size; INDEX_SCAN_INFO *index_scan; uint keynr= param->real_keynr[idx]; - if ((*key)->type == SEL_ARG::MAYBE_KEY || - (*key)->maybe_flag) + if (key->type == SEL_ARG::MAYBE_KEY || + key->maybe_flag) param->needed_reg->set_bit(keynr); bool read_index_only= index_read_must_be_used ? TRUE : (bool) param->table->covering_keys.is_set(keynr); - found_records= check_quick_select(param, idx, read_index_only, *key, + found_records= check_quick_select(param, idx, read_index_only, key, update_tbl_stats, &mrr_flags, &buf_size, &cost); @@ -6812,7 +6833,7 @@ static TRP_RANGE *get_key_scans_params(PARAM *param, SEL_TREE *tree, index_scan->used_key_parts= param->max_key_part+1; index_scan->range_count= param->range_count; index_scan->records= found_records; - index_scan->sel_arg= *key; + index_scan->sel_arg= key; *tree->index_scans_end++= index_scan; } if ((found_records != HA_POS_ERROR) && param->is_ror_scan) @@ -6826,6 +6847,7 @@ static TRP_RANGE *get_key_scans_params(PARAM *param, SEL_TREE *tree, read_time= found_read_time; best_records= found_records; key_to_read= key; + best_idx= idx; best_mrr_flags= mrr_flags; best_buf_size= buf_size; } @@ -6836,17 +6858,16 @@ static TRP_RANGE *get_key_scans_params(PARAM *param, SEL_TREE *tree, "ROR scans");); if (key_to_read) { - idx= key_to_read - tree->keys; - if ((read_plan= new (param->mem_root) TRP_RANGE(*key_to_read, idx, + if ((read_plan= new (param->mem_root) TRP_RANGE(key_to_read, best_idx, best_mrr_flags))) { read_plan->records= best_records; - read_plan->is_ror= tree->ror_scans_map.is_set(idx); + read_plan->is_ror= tree->ror_scans_map.is_set(best_idx); read_plan->read_cost= read_time; read_plan->mrr_buf_size= best_buf_size; DBUG_PRINT("info", ("Returning range plan for key %s, cost %g, records %lu", - param->table->key_info[param->real_keynr[idx]].name, + param->table->key_info[param->real_keynr[best_idx]].name, read_plan->read_cost, (ulong) read_plan->records)); } } @@ -6983,7 +7004,10 @@ QUICK_SELECT_I *TRP_ROR_UNION::make_quick(PARAM *param, { if (!(quick= (*scan)->make_quick(param, FALSE, &quick_roru->alloc)) || quick_roru->push_quick_back(quick)) + { + delete quick_roru; DBUG_RETURN(NULL); + } } quick_roru->records= records; quick_roru->read_time= read_cost; @@ -7002,296 +7026,238 @@ QUICK_SELECT_I *TRP_ROR_UNION::make_quick(PARAM *param, field field in the predicate lt_value constant that field should be smaller gt_value constant that field should be greaterr - cmp_type compare type for the field RETURN # Pointer to tree built tree 0 on error */ -static SEL_TREE *get_ne_mm_tree(RANGE_OPT_PARAM *param, Item_func *cond_func, - Field *field, - Item *lt_value, Item *gt_value, - Item_result cmp_type) +SEL_TREE *Item_bool_func::get_ne_mm_tree(RANGE_OPT_PARAM *param, + Field *field, + Item *lt_value, Item *gt_value) { SEL_TREE *tree; - tree= get_mm_parts(param, cond_func, field, Item_func::LT_FUNC, - lt_value, cmp_type); + tree= get_mm_parts(param, field, Item_func::LT_FUNC, lt_value); if (tree) - { - tree= tree_or(param, tree, get_mm_parts(param, cond_func, field, - Item_func::GT_FUNC, - gt_value, cmp_type)); - } + tree= tree_or(param, tree, get_mm_parts(param, field, Item_func::GT_FUNC, + gt_value)); return tree; } - - -/* - Build a SEL_TREE for a simple predicate - - SYNOPSIS - get_func_mm_tree() - param PARAM from SQL_SELECT::test_quick_select - cond_func item for the predicate - field field in the predicate - value constant in the predicate - cmp_type compare type for the field - inv TRUE <> NOT cond_func is considered - (makes sense only when cond_func is BETWEEN or IN) - RETURN - Pointer to the tree built tree -*/ -static SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, Item_func *cond_func, - Field *field, Item *value, - Item_result cmp_type, bool inv) +SEL_TREE *Item_func_between::get_func_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *value) { - SEL_TREE *tree= 0; - DBUG_ENTER("get_func_mm_tree"); - - switch (cond_func->functype()) { - - case Item_func::NE_FUNC: - tree= get_ne_mm_tree(param, cond_func, field, value, value, cmp_type); - break; - - case Item_func::BETWEEN: + SEL_TREE *tree; + DBUG_ENTER("Item_func_between::get_func_mm_tree"); + if (!value) { - if (!value) + if (negated) { - if (inv) - { - tree= get_ne_mm_tree(param, cond_func, field, cond_func->arguments()[1], - cond_func->arguments()[2], cmp_type); - } - else + tree= get_ne_mm_tree(param, field, args[1], args[2]); + } + else + { + tree= get_mm_parts(param, field, Item_func::GE_FUNC, args[1]); + if (tree) { - tree= get_mm_parts(param, cond_func, field, Item_func::GE_FUNC, - cond_func->arguments()[1],cmp_type); - if (tree) - { - tree= tree_and(param, tree, get_mm_parts(param, cond_func, field, - Item_func::LE_FUNC, - cond_func->arguments()[2], - cmp_type)); - } + tree= tree_and(param, tree, get_mm_parts(param, field, + Item_func::LE_FUNC, + args[2])); } } - else - tree= get_mm_parts(param, cond_func, field, - (inv ? - (value == (Item*)1 ? Item_func::GT_FUNC : - Item_func::LT_FUNC): - (value == (Item*)1 ? Item_func::LE_FUNC : - Item_func::GE_FUNC)), - cond_func->arguments()[0], cmp_type); - break; } - case Item_func::IN_FUNC: + else { - Item_func_in *func=(Item_func_in*) cond_func; + tree= get_mm_parts(param, field, + (negated ? + (value == (Item*)1 ? Item_func::GT_FUNC : + Item_func::LT_FUNC): + (value == (Item*)1 ? Item_func::LE_FUNC : + Item_func::GE_FUNC)), + args[0]); + } + DBUG_RETURN(tree); +} - /* - Array for IN() is constructed when all values have the same result - type. Tree won't be built for values with different result types, - so we check it here to avoid unnecessary work. - */ - if (!func->arg_types_compatible) - break; - if (inv) +SEL_TREE *Item_func_in::get_func_mm_tree(RANGE_OPT_PARAM *param, + Field *field, Item *value) +{ + SEL_TREE *tree= 0; + DBUG_ENTER("Item_func_in::get_func_mm_tree"); + /* + Array for IN() is constructed when all values have the same result + type. Tree won't be built for values with different result types, + so we check it here to avoid unnecessary work. + */ + if (!arg_types_compatible) + DBUG_RETURN(0); + + if (negated) + { + if (array && array->result_type() != ROW_RESULT) { - if (func->array && func->array->result_type() != ROW_RESULT) - { - /* - We get here for conditions in form "t.key NOT IN (c1, c2, ...)", - where c{i} are constants. Our goal is to produce a SEL_TREE that - represents intervals: - - ($MIN<t.key<c1) OR (c1<t.key<c2) OR (c2<t.key<c3) OR ... (*) - - where $MIN is either "-inf" or NULL. - - The most straightforward way to produce it is to convert NOT IN - into "(t.key != c1) AND (t.key != c2) AND ... " and let the range - analyzer to build SEL_TREE from that. The problem is that the - range analyzer will use O(N^2) memory (which is probably a bug), - and people do use big NOT IN lists (e.g. see BUG#15872, BUG#21282), - will run out of memory. - - Another problem with big lists like (*) is that a big list is - unlikely to produce a good "range" access, while considering that - range access will require expensive CPU calculations (and for - MyISAM even index accesses). In short, big NOT IN lists are rarely - worth analyzing. - - Considering the above, we'll handle NOT IN as follows: - * if the number of entries in the NOT IN list is less than - NOT_IN_IGNORE_THRESHOLD, construct the SEL_TREE (*) manually. - * Otherwise, don't produce a SEL_TREE. - */ + /* + We get here for conditions in form "t.key NOT IN (c1, c2, ...)", + where c{i} are constants. Our goal is to produce a SEL_TREE that + represents intervals: + + ($MIN<t.key<c1) OR (c1<t.key<c2) OR (c2<t.key<c3) OR ... (*) + + where $MIN is either "-inf" or NULL. + + The most straightforward way to produce it is to convert NOT IN + into "(t.key != c1) AND (t.key != c2) AND ... " and let the range + analyzer to build SEL_TREE from that. The problem is that the + range analyzer will use O(N^2) memory (which is probably a bug), + and people do use big NOT IN lists (e.g. see BUG#15872, BUG#21282), + will run out of memory. + + Another problem with big lists like (*) is that a big list is + unlikely to produce a good "range" access, while considering that + range access will require expensive CPU calculations (and for + MyISAM even index accesses). In short, big NOT IN lists are rarely + worth analyzing. + + Considering the above, we'll handle NOT IN as follows: + * if the number of entries in the NOT IN list is less than + NOT_IN_IGNORE_THRESHOLD, construct the SEL_TREE (*) manually. + * Otherwise, don't produce a SEL_TREE. + */ #define NOT_IN_IGNORE_THRESHOLD 1000 - MEM_ROOT *tmp_root= param->mem_root; - param->thd->mem_root= param->old_root; - /* - Create one Item_type constant object. We'll need it as - get_mm_parts only accepts constant values wrapped in Item_Type - objects. - We create the Item on param->mem_root which points to - per-statement mem_root (while thd->mem_root is currently pointing - to mem_root local to range optimizer). - */ - Item *value_item= func->array->create_item(); - param->thd->mem_root= tmp_root; + MEM_ROOT *tmp_root= param->mem_root; + param->thd->mem_root= param->old_root; + /* + Create one Item_type constant object. We'll need it as + get_mm_parts only accepts constant values wrapped in Item_Type + objects. + We create the Item on param->mem_root which points to + per-statement mem_root (while thd->mem_root is currently pointing + to mem_root local to range optimizer). + */ + Item *value_item= array->create_item(param->thd); + param->thd->mem_root= tmp_root; + + if (array->count > NOT_IN_IGNORE_THRESHOLD || !value_item) + DBUG_RETURN(0); - if (func->array->count > NOT_IN_IGNORE_THRESHOLD || !value_item) + /* Get a SEL_TREE for "(-inf|NULL) < X < c_0" interval. */ + uint i=0; + do + { + array->value_to_item(i, value_item); + tree= get_mm_parts(param, field, Item_func::LT_FUNC, value_item); + if (!tree) break; + i++; + } while (i < array->count && tree->type == SEL_TREE::IMPOSSIBLE); - /* Get a SEL_TREE for "(-inf|NULL) < X < c_0" interval. */ - uint i=0; - do + if (!tree || tree->type == SEL_TREE::IMPOSSIBLE) + { + /* We get here in cases like "t.unsigned NOT IN (-1,-2,-3) */ + DBUG_RETURN(NULL); + } + SEL_TREE *tree2; + for (; i < array->used_count; i++) + { + if (array->compare_elems(i, i-1)) { - func->array->value_to_item(i, value_item); - tree= get_mm_parts(param, cond_func, field, Item_func::LT_FUNC, - value_item, cmp_type); - if (!tree) + /* Get a SEL_TREE for "-inf < X < c_i" interval */ + array->value_to_item(i, value_item); + tree2= get_mm_parts(param, field, Item_func::LT_FUNC, value_item); + if (!tree2) + { + tree= NULL; break; - i++; - } while (i < func->array->count && tree->type == SEL_TREE::IMPOSSIBLE); + } - if (!tree || tree->type == SEL_TREE::IMPOSSIBLE) - { - /* We get here in cases like "t.unsigned NOT IN (-1,-2,-3) */ - tree= NULL; - break; - } - SEL_TREE *tree2; - for (; i < func->array->count; i++) - { - if (func->array->compare_elems(i, i-1)) + /* Change all intervals to be "c_{i-1} < X < c_i" */ + for (uint idx= 0; idx < param->keys; idx++) { - /* Get a SEL_TREE for "-inf < X < c_i" interval */ - func->array->value_to_item(i, value_item); - tree2= get_mm_parts(param, cond_func, field, Item_func::LT_FUNC, - value_item, cmp_type); - if (!tree2) + SEL_ARG *new_interval, *last_val; + if (((new_interval= tree2->keys[idx])) && + (tree->keys[idx]) && + ((last_val= tree->keys[idx]->last()))) { - tree= NULL; - break; - } - - /* Change all intervals to be "c_{i-1} < X < c_i" */ - for (uint idx= 0; idx < param->keys; idx++) - { - SEL_ARG *new_interval, *last_val; - if (((new_interval= tree2->keys[idx])) && - (tree->keys[idx]) && - ((last_val= tree->keys[idx]->last()))) + new_interval->min_value= last_val->max_value; + new_interval->min_flag= NEAR_MIN; + + /* + If the interval is over a partial keypart, the + interval must be "c_{i-1} <= X < c_i" instead of + "c_{i-1} < X < c_i". Reason: + + Consider a table with a column "my_col VARCHAR(3)", + and an index with definition + "INDEX my_idx my_col(1)". If the table contains rows + with my_col values "f" and "foo", the index will not + distinguish the two rows. + + Note that tree_or() below will effectively merge + this range with the range created for c_{i-1} and + we'll eventually end up with only one range: + "NULL < X". + + Partitioning indexes are never partial. + */ + if (param->using_real_indexes) { - new_interval->min_value= last_val->max_value; - new_interval->min_flag= NEAR_MIN; - - /* - If the interval is over a partial keypart, the - interval must be "c_{i-1} <= X < c_i" instead of - "c_{i-1} < X < c_i". Reason: - - Consider a table with a column "my_col VARCHAR(3)", - and an index with definition - "INDEX my_idx my_col(1)". If the table contains rows - with my_col values "f" and "foo", the index will not - distinguish the two rows. - - Note that tree_or() below will effectively merge - this range with the range created for c_{i-1} and - we'll eventually end up with only one range: - "NULL < X". - - Partitioning indexes are never partial. - */ - if (param->using_real_indexes) - { - const KEY key= - param->table->key_info[param->real_keynr[idx]]; - const KEY_PART_INFO *kpi= key.key_part + new_interval->part; - - if (kpi->key_part_flag & HA_PART_KEY_SEG) - new_interval->min_flag= 0; - } + const KEY key= + param->table->key_info[param->real_keynr[idx]]; + const KEY_PART_INFO *kpi= key.key_part + new_interval->part; + + if (kpi->key_part_flag & HA_PART_KEY_SEG) + new_interval->min_flag= 0; } } - /* - The following doesn't try to allocate memory so no need to - check for NULL. - */ - tree= tree_or(param, tree, tree2); } - } - - if (tree && tree->type != SEL_TREE::IMPOSSIBLE) - { /* - Get the SEL_TREE for the last "c_last < X < +inf" interval - (value_item cotains c_last already) + The following doesn't try to allocate memory so no need to + check for NULL. */ - tree2= get_mm_parts(param, cond_func, field, Item_func::GT_FUNC, - value_item, cmp_type); tree= tree_or(param, tree, tree2); } } - else + + if (tree && tree->type != SEL_TREE::IMPOSSIBLE) { - tree= get_ne_mm_tree(param, cond_func, field, - func->arguments()[1], func->arguments()[1], - cmp_type); - if (tree) - { - Item **arg, **end; - for (arg= func->arguments()+2, end= arg+func->argument_count()-2; - arg < end ; arg++) - { - tree= tree_and(param, tree, get_ne_mm_tree(param, cond_func, field, - *arg, *arg, cmp_type)); - } - } + /* + Get the SEL_TREE for the last "c_last < X < +inf" interval + (value_item cotains c_last already) + */ + tree2= get_mm_parts(param, field, Item_func::GT_FUNC, value_item); + tree= tree_or(param, tree, tree2); } } else - { - tree= get_mm_parts(param, cond_func, field, Item_func::EQ_FUNC, - func->arguments()[1], cmp_type); + { + tree= get_ne_mm_tree(param, field, args[1], args[1]); if (tree) { Item **arg, **end; - for (arg= func->arguments()+2, end= arg+func->argument_count()-2; - arg < end ; arg++) + for (arg= args + 2, end= arg + arg_count - 2; arg < end ; arg++) { - tree= tree_or(param, tree, get_mm_parts(param, cond_func, field, - Item_func::EQ_FUNC, - *arg, cmp_type)); + tree= tree_and(param, tree, get_ne_mm_tree(param, field, + *arg, *arg)); } } } - break; } - default: + else { - /* - Here the function for the following predicates are processed: - <, <=, =, >=, >, LIKE, IS NULL, IS NOT NULL. - If the predicate is of the form (value op field) it is handled - as the equivalent predicate (field rev_op value), e.g. - 2 <= a is handled as a >= 2. - */ - Item_func::Functype func_type= - (value != cond_func->arguments()[0]) ? cond_func->functype() : - ((Item_bool_func2*) cond_func)->rev_functype(); - tree= get_mm_parts(param, cond_func, field, func_type, value, cmp_type); - } + tree= get_mm_parts(param, field, Item_func::EQ_FUNC, args[1]); + if (tree) + { + Item **arg, **end; + for (arg= args + 2, end= arg + arg_count - 2; + arg < end ; arg++) + { + tree= tree_or(param, tree, get_mm_parts(param, field, + Item_func::EQ_FUNC, *arg)); + } + } } - DBUG_RETURN(tree); } @@ -7302,9 +7268,9 @@ static SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, Item_func *cond_func, SYNOPSIS get_full_func_mm_tree() param PARAM from SQL_SELECT::test_quick_select - cond_func item for the predicate field_item field in the predicate - value constant in the predicate + value constant in the predicate (or a field already read from + a table in the case of dynamic range access) (for BETWEEN it contains the number of the field argument, for IN it's always 0) inv TRUE <> NOT cond_func is considered @@ -7366,36 +7332,36 @@ static SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, Item_func *cond_func, Pointer to the tree representing the built conjunction of SEL_TREEs */ -static SEL_TREE *get_full_func_mm_tree(RANGE_OPT_PARAM *param, - Item_func *cond_func, - Item_field *field_item, Item *value, - bool inv) +SEL_TREE *Item_bool_func::get_full_func_mm_tree(RANGE_OPT_PARAM *param, + Item_field *field_item, + Item *value) { + DBUG_ENTER("Item_bool_func::get_full_func_mm_tree"); SEL_TREE *tree= 0; SEL_TREE *ftree= 0; table_map ref_tables= 0; table_map param_comp= ~(param->prev_tables | param->read_tables | param->current_table); - DBUG_ENTER("get_full_func_mm_tree"); - #ifdef HAVE_SPATIAL - if (field_item->field->type() == MYSQL_TYPE_GEOMETRY) + Field::geometry_type sav_geom_type; + const bool geometry= field_item->field->type() == MYSQL_TYPE_GEOMETRY; + if (geometry) { + sav_geom_type= ((Field_geom*) field_item->field)->geom_type; /* We have to be able to store all sorts of spatial features here */ ((Field_geom*) field_item->field)->geom_type= Field::GEOM_GEOMETRY; } #endif /*HAVE_SPATIAL*/ - for (uint i= 0; i < cond_func->arg_count; i++) + for (uint i= 0; i < arg_count; i++) { - Item *arg= cond_func->arguments()[i]->real_item(); + Item *arg= arguments()[i]->real_item(); if (arg != field_item) ref_tables|= arg->used_tables(); } Field *field= field_item->field; - Item_result cmp_type= field->cmp_type(); if (!((ref_tables | field->table->map) & param_comp)) - ftree= get_func_mm_tree(param, cond_func, field, value, cmp_type, inv); + ftree= get_func_mm_tree(param, field, value); Item_equal *item_equal= field_item->item_equal; if (item_equal) { @@ -7407,207 +7373,272 @@ static SEL_TREE *get_full_func_mm_tree(RANGE_OPT_PARAM *param, continue; if (!((ref_tables | f->table->map) & param_comp)) { - tree= get_func_mm_tree(param, cond_func, f, value, cmp_type, inv); + tree= get_func_mm_tree(param, f, value); ftree= !ftree ? tree : tree_and(param, ftree, tree); } } } + +#ifdef HAVE_SPATIAL + if (geometry) + { + ((Field_geom*) field_item->field)->geom_type= sav_geom_type; + } +#endif /*HAVE_SPATIAL*/ DBUG_RETURN(ftree); } - /* make a select tree of all keys in condition */ -static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param,COND *cond) -{ - SEL_TREE *tree=0; - SEL_TREE *ftree= 0; - Item_field *field_item= 0; - bool inv= FALSE; - Item *value= 0; - DBUG_ENTER("get_mm_tree"); +/* + make a select tree of all keys in condition + + @param param Context + @param cond INOUT condition to perform range analysis on. - if (cond->type() == Item::COND_ITEM) - { - List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); + @detail + Range analysis may infer that some conditions are never true. + - If the condition is never true, SEL_TREE(type=IMPOSSIBLE) is returned + - if parts of condition are never true, the function may remove these parts + from the condition 'cond'. Sometimes, this will cause the condition to + be substituted for something else. - if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) - { - tree= NULL; - Item *item; - while ((item=li++)) - { - SEL_TREE *new_tree= get_mm_tree(param,item); - if (param->statement_should_be_aborted()) - DBUG_RETURN(NULL); - tree= tree_and(param,tree,new_tree); - if (tree && tree->type == SEL_TREE::IMPOSSIBLE) - break; - } - } - else - { // COND OR - tree= get_mm_tree(param,li++); - if (param->statement_should_be_aborted()) - DBUG_RETURN(NULL); - if (tree) - { - Item *item; - while ((item=li++)) - { - SEL_TREE *new_tree=get_mm_tree(param,item); - if (new_tree == NULL || param->statement_should_be_aborted()) - DBUG_RETURN(NULL); - tree= tree_or(param,tree,new_tree); - if (tree == NULL || tree->type == SEL_TREE::ALWAYS) - break; - } - } - } - DBUG_RETURN(tree); - } - /* Here when simple cond */ - if (cond->const_item()) - { - if (cond->is_expensive()) - DBUG_RETURN(0); - /* - During the cond->val_int() evaluation we can come across a subselect - item which may allocate memory on the thd->mem_root and assumes - all the memory allocated has the same life span as the subselect - item itself. So we have to restore the thread's mem_root here. - */ - MEM_ROOT *tmp_root= param->mem_root; - param->thd->mem_root= param->old_root; - tree= cond->val_int() ? new(tmp_root) SEL_TREE(SEL_TREE::ALWAYS) : - new(tmp_root) SEL_TREE(SEL_TREE::IMPOSSIBLE); - param->thd->mem_root= tmp_root; - DBUG_RETURN(tree); - } - table_map ref_tables= 0; - table_map param_comp= ~(param->prev_tables | param->read_tables | - param->current_table); - if (cond->type() != Item::FUNC_ITEM) - { // Should be a field - ref_tables= cond->used_tables(); - if ((ref_tables & param->current_table) || - (ref_tables & ~(param->prev_tables | param->read_tables))) - DBUG_RETURN(0); - DBUG_RETURN(new SEL_TREE(SEL_TREE::MAYBE)); - } - - Item_func *cond_func= (Item_func*) cond; - if (cond_func->functype() == Item_func::BETWEEN || - cond_func->functype() == Item_func::IN_FUNC) - inv= ((Item_func_opt_neg *) cond_func)->negated; - else + @return + NULL - Could not infer anything from condition cond. + SEL_TREE with type=IMPOSSIBLE - condition can never be true. +*/ +SEL_TREE *Item_cond_and::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) +{ + DBUG_ENTER("Item_cond_and::get_mm_tree"); + SEL_TREE *tree= NULL; + List_iterator<Item> li(*argument_list()); + Item *item; + while ((item= li++)) { - MEM_ROOT *tmp_root= param->mem_root; - param->thd->mem_root= param->old_root; - Item_func::optimize_type opt_res= cond_func->select_optimize(); - param->thd->mem_root= tmp_root; - if (opt_res == Item_func::OPTIMIZE_NONE) - DBUG_RETURN(0); + SEL_TREE *new_tree= li.ref()[0]->get_mm_tree(param,li.ref()); + if (param->statement_should_be_aborted()) + DBUG_RETURN(NULL); + tree= tree_and(param, tree, new_tree); + if (tree && tree->type == SEL_TREE::IMPOSSIBLE) + { + /* + Do not remove 'item' from 'cond'. We return a SEL_TREE::IMPOSSIBLE + and that is sufficient for the caller to see that the whole + condition is never true. + */ + break; + } } + DBUG_RETURN(tree); +} - param->cond= cond; - switch (cond_func->functype()) { - case Item_func::BETWEEN: - if (cond_func->arguments()[0]->real_item()->type() == Item::FIELD_ITEM) +SEL_TREE *Item_cond::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) +{ + DBUG_ENTER("Item_cond::get_mm_tree"); + List_iterator<Item> li(*argument_list()); + bool replace_cond= false; + Item *replacement_item= li++; + SEL_TREE *tree= li.ref()[0]->get_mm_tree(param, li.ref()); + if (param->statement_should_be_aborted()) + DBUG_RETURN(NULL); + if (tree) + { + if (tree->type == SEL_TREE::IMPOSSIBLE && + param->remove_false_where_parts) { - field_item= (Item_field*) (cond_func->arguments()[0]->real_item()); - ftree= get_full_func_mm_tree(param, cond_func, field_item, NULL, inv); + /* See the other li.remove() call below */ + li.remove(); + if (argument_list()->elements <= 1) + replace_cond= true; } - /* - Concerning the code below see the NOTES section in - the comments for the function get_full_func_mm_tree() - */ - for (uint i= 1 ; i < cond_func->arg_count ; i++) + Item *item; + while ((item= li++)) { - if (cond_func->arguments()[i]->real_item()->type() == Item::FIELD_ITEM) + SEL_TREE *new_tree= li.ref()[0]->get_mm_tree(param, li.ref()); + if (new_tree == NULL || param->statement_should_be_aborted()) + DBUG_RETURN(NULL); + tree= tree_or(param, tree, new_tree); + if (tree == NULL || tree->type == SEL_TREE::ALWAYS) { - field_item= (Item_field*) (cond_func->arguments()[i]->real_item()); - SEL_TREE *tmp= get_full_func_mm_tree(param, cond_func, - field_item, (Item*)(intptr)i, inv); - if (inv) - { - tree= !tree ? tmp : tree_or(param, tree, tmp); - if (tree == NULL) - break; - } - else - tree= tree_and(param, tree, tmp); - } - else if (inv) - { - tree= 0; + replacement_item= *li.ref(); break; } + + if (new_tree && new_tree->type == SEL_TREE::IMPOSSIBLE && + param->remove_false_where_parts) + { + /* + This is a condition in form + + cond = item1 OR ... OR item_i OR ... itemN + + and item_i produces SEL_TREE(IMPOSSIBLE). We should remove item_i + from cond. This may cause 'cond' to become a degenerate, + one-way OR. In that case, we replace 'cond' with the remaining + item_i. + */ + li.remove(); + if (argument_list()->elements <= 1) + replace_cond= true; + } + else + replacement_item= *li.ref(); } - ftree = tree_and(param, ftree, tree); - break; - case Item_func::IN_FUNC: + if (replace_cond) + *cond_ptr= replacement_item; + } + DBUG_RETURN(tree); +} + + +SEL_TREE *Item::get_mm_tree_for_const(RANGE_OPT_PARAM *param) +{ + DBUG_ENTER("get_mm_tree_for_const"); + if (is_expensive()) + DBUG_RETURN(0); + /* + During the cond->val_int() evaluation we can come across a subselect + item which may allocate memory on the thd->mem_root and assumes + all the memory allocated has the same life span as the subselect + item itself. So we have to restore the thread's mem_root here. + */ + MEM_ROOT *tmp_root= param->mem_root; + param->thd->mem_root= param->old_root; + SEL_TREE *tree; + + const SEL_TREE::Type type= val_int()? SEL_TREE::ALWAYS: SEL_TREE::IMPOSSIBLE; + param->thd->mem_root= tmp_root; + + tree= new (tmp_root) SEL_TREE(type, tmp_root, param->keys); + DBUG_RETURN(tree); +} + + +SEL_TREE *Item::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) +{ + DBUG_ENTER("Item::get_mm_tree"); + if (const_item()) + DBUG_RETURN(get_mm_tree_for_const(param)); + + /* + Here we have a not-constant non-function Item. + + Item_field should not appear, as normalize_cond() replaces + "WHERE field" to "WHERE field<>0". + + Item_exists_subselect is possible, e.g. in this query: + SELECT id, st FROM t1 + WHERE st IN ('GA','FL') AND EXISTS (SELECT 1 FROM t2 WHERE t2.id=t1.id) + GROUP BY id; + */ + table_map ref_tables= used_tables(); + if ((ref_tables & param->current_table) || + (ref_tables & ~(param->prev_tables | param->read_tables))) + DBUG_RETURN(0); + DBUG_RETURN(new (param->mem_root) SEL_TREE(SEL_TREE::MAYBE, param->mem_root, + param->keys)); +} + + +SEL_TREE * +Item_func_between::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) +{ + DBUG_ENTER("Item_func_between::get_mm_tree"); + if (const_item()) + DBUG_RETURN(get_mm_tree_for_const(param)); + + SEL_TREE *tree= 0; + SEL_TREE *ftree= 0; + + if (arguments()[0]->real_item()->type() == Item::FIELD_ITEM) { - Item_func_in *func=(Item_func_in*) cond_func; - if (func->key_item()->real_item()->type() != Item::FIELD_ITEM) - DBUG_RETURN(0); - field_item= (Item_field*) (func->key_item()->real_item()); - ftree= get_full_func_mm_tree(param, cond_func, field_item, NULL, inv); - break; + Item_field *field_item= (Item_field*) (arguments()[0]->real_item()); + ftree= get_full_func_mm_tree(param, field_item, NULL); } - case Item_func::MULT_EQUAL_FUNC: + + /* + Concerning the code below see the NOTES section in + the comments for the function get_full_func_mm_tree() + */ + for (uint i= 1 ; i < arg_count ; i++) { - Item_equal *item_equal= (Item_equal *) cond; - if (!(value= item_equal->get_const()) || value->is_expensive()) - DBUG_RETURN(0); - Item_equal_fields_iterator it(*item_equal); - ref_tables= value->used_tables(); - while (it++) + if (arguments()[i]->real_item()->type() == Item::FIELD_ITEM) { - Field *field= it.get_curr_field(); - Item_result cmp_type= field->cmp_type(); - if (!((ref_tables | field->table->map) & param_comp)) + Item_field *field_item= (Item_field*) (arguments()[i]->real_item()); + SEL_TREE *tmp= get_full_func_mm_tree(param, field_item, + (Item*)(intptr) i); + if (negated) { - tree= get_mm_parts(param, cond, field, Item_func::EQ_FUNC, - value,cmp_type); - ftree= !ftree ? tree : tree_and(param, ftree, tree); + tree= !tree ? tmp : tree_or(param, tree, tmp); + if (tree == NULL) + break; } + else + tree= tree_and(param, tree, tmp); } - - DBUG_RETURN(ftree); - } - default: - if (cond_func->arguments()[0]->real_item()->type() == Item::FIELD_ITEM) + else if (negated) { - field_item= (Item_field*) (cond_func->arguments()[0]->real_item()); - value= cond_func->arg_count > 1 ? cond_func->arguments()[1] : 0; + tree= 0; + break; } - else if (cond_func->have_rev_func() && - cond_func->arguments()[1]->real_item()->type() == - Item::FIELD_ITEM) + } + + ftree= tree_and(param, ftree, tree); + DBUG_RETURN(ftree); +} + + +SEL_TREE *Item_func_in::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) +{ + DBUG_ENTER("Item_func_in::get_mm_tree"); + if (const_item()) + DBUG_RETURN(get_mm_tree_for_const(param)); + + if (key_item()->real_item()->type() != Item::FIELD_ITEM) + DBUG_RETURN(0); + Item_field *field= (Item_field*) (key_item()->real_item()); + SEL_TREE *tree= get_full_func_mm_tree(param, field, NULL); + DBUG_RETURN(tree); +} + + +SEL_TREE *Item_equal::get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr) +{ + DBUG_ENTER("Item_equal::get_mm_tree"); + if (const_item()) + DBUG_RETURN(get_mm_tree_for_const(param)); + + SEL_TREE *tree= 0; + SEL_TREE *ftree= 0; + + Item *value; + if (!(value= get_const()) || value->is_expensive()) + DBUG_RETURN(0); + + Item_equal_fields_iterator it(*this); + table_map ref_tables= value->used_tables(); + table_map param_comp= ~(param->prev_tables | param->read_tables | + param->current_table); + while (it++) + { + Field *field= it.get_curr_field(); + if (!((ref_tables | field->table->map) & param_comp)) { - field_item= (Item_field*) (cond_func->arguments()[1]->real_item()); - value= cond_func->arguments()[0]; + tree= get_mm_parts(param, field, Item_func::EQ_FUNC, value); + ftree= !ftree ? tree : tree_and(param, ftree, tree); } - else - DBUG_RETURN(0); - if (value && value->is_expensive()) - DBUG_RETURN(0); - - ftree= get_full_func_mm_tree(param, cond_func, field_item, value, inv); } DBUG_RETURN(ftree); } -static SEL_TREE * -get_mm_parts(RANGE_OPT_PARAM *param, COND *cond_func, Field *field, - Item_func::Functype type, - Item *value, Item_result cmp_type) +SEL_TREE * +Item_bool_func::get_mm_parts(RANGE_OPT_PARAM *param, Field *field, + Item_func::Functype type, Item *value) { DBUG_ENTER("get_mm_parts"); if (field->table != param->table) @@ -7616,20 +7647,34 @@ get_mm_parts(RANGE_OPT_PARAM *param, COND *cond_func, Field *field, KEY_PART *key_part = param->key_parts; KEY_PART *end = param->key_parts_end; SEL_TREE *tree=0; + table_map value_used_tables= 0; if (value && - value->used_tables() & ~(param->prev_tables | param->read_tables)) + (value_used_tables= value->used_tables()) & + ~(param->prev_tables | param->read_tables)) DBUG_RETURN(0); for (; key_part != end ; key_part++) { if (field->eq(key_part->field)) { SEL_ARG *sel_arg=0; - if (!tree && !(tree=new SEL_TREE())) + if (!tree && !(tree=new (param->thd->mem_root) SEL_TREE(param->mem_root, + param->keys))) DBUG_RETURN(0); // OOM - if (!value || !(value->used_tables() & ~param->read_tables)) + if (!value || !(value_used_tables & ~param->read_tables)) { - sel_arg=get_mm_leaf(param,cond_func, - key_part->field,key_part,type,value); + /* + We need to restore the runtime mem_root of the thread in this + function because it evaluates the value of its argument, while + the argument can be any, e.g. a subselect. The subselect + items, in turn, assume that all the memory allocated during + the evaluation has the same life span as the item itself. + TODO: opt_range.cc should not reset thd->mem_root at all. + */ + MEM_ROOT *tmp_root= param->mem_root; + param->thd->mem_root= param->old_root; + sel_arg= get_mm_leaf(param, key_part->field, key_part, type, value); + param->thd->mem_root= tmp_root; + if (!sel_arg) continue; if (sel_arg->type == SEL_ARG::IMPOSSIBLE) @@ -7657,191 +7702,163 @@ get_mm_parts(RANGE_OPT_PARAM *param, COND *cond_func, Field *field, } -static SEL_ARG * -get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, - KEY_PART *key_part, Item_func::Functype type,Item *value) +SEL_ARG * +Item_func_null_predicate::get_mm_leaf(RANGE_OPT_PARAM *param, + Field *field, KEY_PART *key_part, + Item_func::Functype type, + Item *value) { - uint maybe_null=(uint) field->real_maybe_null(); - bool optimize_range; - SEL_ARG *tree= 0; MEM_ROOT *alloc= param->mem_root; - uchar *str; - int err; - DBUG_ENTER("get_mm_leaf"); - + DBUG_ENTER("Item_func_null_predicate::get_mm_leaf"); + DBUG_ASSERT(!value); /* - We need to restore the runtime mem_root of the thread in this - function because it evaluates the value of its argument, while - the argument can be any, e.g. a subselect. The subselect - items, in turn, assume that all the memory allocated during - the evaluation has the same life span as the item itself. - TODO: opt_range.cc should not reset thd->mem_root at all. + No check for field->table->maybe_null. It's perfecly fine to use range + access for cases like + + SELECT * FROM t1 LEFT JOIN t2 ON t2.key IS [NOT] NULL + + ON expression is evaluated before considering NULL-complemented rows, so + IS [NOT] NULL has regular semantics. */ - param->thd->mem_root= param->old_root; - if (!value) // IS NULL or IS NOT NULL + if (!field->real_maybe_null()) + DBUG_RETURN(type == ISNULL_FUNC ? &null_element : NULL); + SEL_ARG *tree; + if (!(tree= new (alloc) SEL_ARG(field, is_null_string, is_null_string))) + DBUG_RETURN(0); + if (type == Item_func::ISNOTNULL_FUNC) { - if (field->table->maybe_null) // Can't use a key on this - goto end; - if (!maybe_null) // Not null field - { - if (type == Item_func::ISNULL_FUNC) - tree= &null_element; - goto end; - } - if (!(tree= new (alloc) SEL_ARG(field,is_null_string,is_null_string))) - goto end; // out of memory - if (type == Item_func::ISNOTNULL_FUNC) - { - tree->min_flag=NEAR_MIN; /* IS NOT NULL -> X > NULL */ - tree->max_flag=NO_MAX_RANGE; - } - goto end; + tree->min_flag=NEAR_MIN; /* IS NOT NULL -> X > NULL */ + tree->max_flag=NO_MAX_RANGE; } + DBUG_RETURN(tree); +} - /* - 1. Usually we can't use an index if the column collation - differ from the operation collation. - 2. However, we can reuse a case insensitive index for - the binary searches: +SEL_ARG * +Item_func_like::get_mm_leaf(RANGE_OPT_PARAM *param, + Field *field, KEY_PART *key_part, + Item_func::Functype type, Item *value) +{ + DBUG_ENTER("Item_func_like::get_mm_leaf"); + DBUG_ASSERT(value); - WHERE latin1_swedish_ci_column = 'a' COLLATE lati1_bin; + if (key_part->image_type != Field::itRAW) + DBUG_RETURN(0); - WHERE latin1_swedish_ci_colimn = BINARY 'a ' + if (param->using_real_indexes && + !field->optimize_range(param->real_keynr[key_part->key], + key_part->part)) + DBUG_RETURN(0); - */ if (field->result_type() == STRING_RESULT && - ((Field_str*) field)->match_collation_to_optimize_range() && - value->result_type() == STRING_RESULT && - key_part->image_type == Field::itRAW && - ((Field_str*)field)->charset() != conf_func->compare_collation() && - !(conf_func->compare_collation()->state & MY_CS_BINSORT && - (type == Item_func::EQUAL_FUNC || type == Item_func::EQ_FUNC))) - goto end; + field->charset() != compare_collation()) + DBUG_RETURN(0); - if (key_part->image_type == Field::itMBR) - { - // @todo: use is_spatial_operator() instead? - switch (type) { - case Item_func::SP_EQUALS_FUNC: - case Item_func::SP_DISJOINT_FUNC: - case Item_func::SP_INTERSECTS_FUNC: - case Item_func::SP_TOUCHES_FUNC: - case Item_func::SP_CROSSES_FUNC: - case Item_func::SP_WITHIN_FUNC: - case Item_func::SP_CONTAINS_FUNC: - case Item_func::SP_OVERLAPS_FUNC: - break; - default: - /* - We cannot involve spatial indexes for queries that - don't use MBREQUALS(), MBRDISJOINT(), etc. functions. - */ - goto end; - } - } + StringBuffer<MAX_FIELD_WIDTH> tmp(value->collation.collation); + String *res; - if (param->using_real_indexes) - optimize_range= field->optimize_range(param->real_keynr[key_part->key], - key_part->part); - else - optimize_range= TRUE; + if (!(res= value->val_str(&tmp))) + DBUG_RETURN(&null_element); + + if (field->cmp_type() != STRING_RESULT) + DBUG_RETURN(0); - if (type == Item_func::LIKE_FUNC) + /* + TODO: + Check if this was a function. This should have be optimized away + in the sql_select.cc + */ + if (res != &tmp) { - bool like_error; - char buff1[MAX_FIELD_WIDTH]; - uchar *min_str,*max_str; - String tmp(buff1,sizeof(buff1),value->collation.collation),*res; - size_t length, offset, min_length, max_length; - uint field_length= field->pack_length()+maybe_null; + tmp.copy(*res); // Get own copy + res= &tmp; + } - if (!optimize_range) - goto end; - if (!(res= value->val_str(&tmp))) - { - tree= &null_element; - goto end; - } + uint maybe_null= (uint) field->real_maybe_null(); + uint field_length= field->pack_length() + maybe_null; + size_t offset= maybe_null; + size_t length= key_part->store_length; - /* - TODO: - Check if this was a function. This should have be optimized away - in the sql_select.cc - */ - if (res != &tmp) + if (length != key_part->length + maybe_null) + { + /* key packed with length prefix */ + offset+= HA_KEY_BLOB_LENGTH; + field_length= length - HA_KEY_BLOB_LENGTH; + } + else + { + if (unlikely(length < field_length)) { - tmp.copy(*res); // Get own copy - res= &tmp; + /* + This can only happen in a table created with UNIREG where one key + overlaps many fields + */ + length= field_length; } - if (field->cmp_type() != STRING_RESULT) - goto end; // Can only optimize strings + else + field_length= length; + } + length+= offset; + uchar *min_str,*max_str; + if (!(min_str= (uchar*) alloc_root(param->mem_root, length*2))) + DBUG_RETURN(0); + max_str= min_str + length; + if (maybe_null) + max_str[0]= min_str[0]=0; - offset=maybe_null; - length=key_part->store_length; + size_t min_length, max_length; + field_length-= maybe_null; + if (my_like_range(field->charset(), + res->ptr(), res->length(), + escape, wild_one, wild_many, + field_length, + (char*) min_str + offset, + (char*) max_str + offset, + &min_length, &max_length)) + DBUG_RETURN(0); // Can't optimize with LIKE - if (length != key_part->length + maybe_null) - { - /* key packed with length prefix */ - offset+= HA_KEY_BLOB_LENGTH; - field_length= length - HA_KEY_BLOB_LENGTH; - } - else - { - if (unlikely(length < field_length)) - { - /* - This can only happen in a table created with UNIREG where one key - overlaps many fields - */ - length= field_length; - } - else - field_length= length; - } - length+=offset; - if (!(min_str= (uchar*) alloc_root(alloc, length*2))) - goto end; + if (offset != maybe_null) // BLOB or VARCHAR + { + int2store(min_str + maybe_null, min_length); + int2store(max_str + maybe_null, max_length); + } + SEL_ARG *tree= new (param->mem_root) SEL_ARG(field, min_str, max_str); + DBUG_RETURN(tree); +} - max_str=min_str+length; - if (maybe_null) - max_str[0]= min_str[0]=0; - field_length-= maybe_null; - like_error= my_like_range(field->charset(), - res->ptr(), res->length(), - ((Item_func_like*)(param->cond))->escape, - wild_one, wild_many, - field_length, - (char*) min_str+offset, (char*) max_str+offset, - &min_length, &max_length); - if (like_error) // Can't optimize with LIKE - goto end; +SEL_ARG * +Item_bool_func::get_mm_leaf(RANGE_OPT_PARAM *param, + Field *field, KEY_PART *key_part, + Item_func::Functype type, Item *value) +{ + uint maybe_null=(uint) field->real_maybe_null(); + SEL_ARG *tree= 0; + MEM_ROOT *alloc= param->mem_root; + uchar *str; + int err; + DBUG_ENTER("Item_bool_func::get_mm_leaf"); + + DBUG_ASSERT(value); // IS NULL and IS NOT NULL are handled separately - if (offset != maybe_null) // BLOB or VARCHAR - { - int2store(min_str+maybe_null,min_length); - int2store(max_str+maybe_null,max_length); - } - tree= new (alloc) SEL_ARG(field, min_str, max_str); - goto end; - } + if (key_part->image_type != Field::itRAW) + DBUG_RETURN(0); // e.g. SPATIAL index - if (!optimize_range && - type != Item_func::EQ_FUNC && - type != Item_func::EQUAL_FUNC) + if (param->using_real_indexes && + !field->optimize_range(param->real_keynr[key_part->key], + key_part->part) && + type != EQ_FUNC && + type != EQUAL_FUNC) goto end; // Can't optimize this - /* - We can't always use indexes when comparing a string index to a number - cmp_type() is checked to allow compare of dates to numbers - */ - if (field->cmp_type() == STRING_RESULT && value->cmp_type() != STRING_RESULT) + if (!field->can_optimize_range(this, value, + type == EQUAL_FUNC || type == EQ_FUNC)) goto end; + err= value->save_in_field_no_warnings(field, 1); if (err == 2 && field->cmp_type() == STRING_RESULT) { - if (type == Item_func::EQ_FUNC) + if (type == EQ_FUNC || type == EQUAL_FUNC) { tree= new (alloc) SEL_ARG(field, 0, 0); tree->type= SEL_ARG::IMPOSSIBLE; @@ -7854,7 +7871,7 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, { if (field->cmp_type() != value->result_type()) { - if ((type == Item_func::EQ_FUNC || type == Item_func::EQUAL_FUNC) && + if ((type == EQ_FUNC || type == EQUAL_FUNC) && value->result_type() == item_cmp_type(field->result_type(), value->result_type())) { @@ -7870,8 +7887,8 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, */ tree= 0; if (err == 3 && field->type() == FIELD_TYPE_DATE && - (type == Item_func::GT_FUNC || type == Item_func::GE_FUNC || - type == Item_func::LT_FUNC || type == Item_func::LE_FUNC) ) + (type == GT_FUNC || type == GE_FUNC || + type == LT_FUNC || type == LE_FUNC) ) { /* We were saving DATETIME into a DATE column, the conversion went ok @@ -7904,14 +7921,14 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, */ else if (err == 1 && field->result_type() == INT_RESULT) { - if (type == Item_func::LT_FUNC && (value->val_int() > 0)) - type = Item_func::LE_FUNC; - else if (type == Item_func::GT_FUNC && + if (type == LT_FUNC && (value->val_int() > 0)) + type= LE_FUNC; + else if (type == GT_FUNC && (field->type() != FIELD_TYPE_BIT) && !((Field_num*)field)->unsigned_flag && !((Item_int*)value)->unsigned_flag && (value->val_int() < 0)) - type = Item_func::GE_FUNC; + type= GE_FUNC; } } else if (err < 0) @@ -7925,7 +7942,7 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, Any sargable predicate except "<=>" involving NULL as a constant is always FALSE */ - if (type != Item_func::EQUAL_FUNC && field->is_real_null()) + if (type != EQUAL_FUNC && field->is_real_null()) { tree= &null_element; goto end; @@ -7961,12 +7978,12 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, longlong item_val= value->val_int(); if (item_val < 0) { - if (type == Item_func::LT_FUNC || type == Item_func::LE_FUNC) + if (type == LT_FUNC || type == LE_FUNC) { tree->type= SEL_ARG::IMPOSSIBLE; goto end; } - if (type == Item_func::GT_FUNC || type == Item_func::GE_FUNC) + if (type == GT_FUNC || type == GE_FUNC) { tree= 0; goto end; @@ -7975,11 +7992,11 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, } switch (type) { - case Item_func::LT_FUNC: + case LT_FUNC: if (stored_field_cmp_to_item(param->thd, field, value) == 0) tree->max_flag=NEAR_MAX; /* fall through */ - case Item_func::LE_FUNC: + case LE_FUNC: if (!maybe_null) tree->min_flag=NO_MIN_RANGE; /* From start */ else @@ -7988,61 +8005,29 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, tree->min_flag=NEAR_MIN; } break; - case Item_func::GT_FUNC: + case GT_FUNC: /* Don't use open ranges for partial key_segments */ if ((!(key_part->flag & HA_PART_KEY_SEG)) && (stored_field_cmp_to_item(param->thd, field, value) <= 0)) tree->min_flag=NEAR_MIN; tree->max_flag= NO_MAX_RANGE; break; - case Item_func::GE_FUNC: + case GE_FUNC: /* Don't use open ranges for partial key_segments */ if ((!(key_part->flag & HA_PART_KEY_SEG)) && (stored_field_cmp_to_item(param->thd, field, value) < 0)) tree->min_flag= NEAR_MIN; tree->max_flag=NO_MAX_RANGE; break; - case Item_func::SP_EQUALS_FUNC: - tree->min_flag=GEOM_FLAG | HA_READ_MBR_EQUAL;// NEAR_MIN;//512; - tree->max_flag=NO_MAX_RANGE; - break; - case Item_func::SP_DISJOINT_FUNC: - tree->min_flag=GEOM_FLAG | HA_READ_MBR_DISJOINT;// NEAR_MIN;//512; - tree->max_flag=NO_MAX_RANGE; - break; - case Item_func::SP_INTERSECTS_FUNC: - tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; - tree->max_flag=NO_MAX_RANGE; - break; - case Item_func::SP_TOUCHES_FUNC: - tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; - tree->max_flag=NO_MAX_RANGE; + case EQ_FUNC: + case EQUAL_FUNC: break; - - case Item_func::SP_CROSSES_FUNC: - tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; - tree->max_flag=NO_MAX_RANGE; - break; - case Item_func::SP_WITHIN_FUNC: - tree->min_flag=GEOM_FLAG | HA_READ_MBR_WITHIN;// NEAR_MIN;//512; - tree->max_flag=NO_MAX_RANGE; - break; - - case Item_func::SP_CONTAINS_FUNC: - tree->min_flag=GEOM_FLAG | HA_READ_MBR_CONTAIN;// NEAR_MIN;//512; - tree->max_flag=NO_MAX_RANGE; - break; - case Item_func::SP_OVERLAPS_FUNC: - tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; - tree->max_flag=NO_MAX_RANGE; - break; - default: + DBUG_ASSERT(0); break; } end: - param->thd->mem_root= alloc; DBUG_RETURN(tree); } @@ -8391,6 +8376,34 @@ bool sel_trees_can_be_ored(RANGE_OPT_PARAM* param, } /* + Check whether the key parts inf_init..inf_end-1 of one index can compose + an infix for the key parts key_init..key_end-1 of another index +*/ + +static +bool is_key_infix(KEY_PART *key_init, KEY_PART *key_end, + KEY_PART *inf_init, KEY_PART *inf_end) +{ + KEY_PART *key_part, *inf_part; + for (key_part= key_init; key_part < key_end; key_part++) + { + if (key_part->field->eq(inf_init->field)) + break; + } + if (key_part == key_end) + return false; + for (key_part++, inf_part= inf_init + 1; + key_part < key_end && inf_part < inf_end; + key_part++, inf_part++) + { + if (!key_part->field->eq(inf_part->field)) + return false; + } + return inf_part == inf_end; +} + + +/* Check whether range parts of two trees must be ored for some indexes SYNOPSIS @@ -8446,14 +8459,9 @@ bool sel_trees_must_be_ored(RANGE_OPT_PARAM* param, KEY_PART *key2_init= param->key[idx2]+tree2->keys[idx2]->part; KEY_PART *key2_end= param->key[idx2]+tree2->keys[idx2]->max_part_no; - KEY_PART *part1, *part2; - for (part1= key1_init, part2= key2_init; - part1 < key1_end && part2 < key2_end; - part1++, part2++) - { - if (!part1->field->eq(part2->field)) - DBUG_RETURN(FALSE); - } + if (!is_key_infix(key1_init, key1_end, key2_init, key2_end) && + !is_key_infix(key2_init, key2_end, key1_init, key1_end)) + DBUG_RETURN(FALSE); } } @@ -8636,14 +8644,31 @@ tree_or(RANGE_OPT_PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) imerge[0]= new SEL_IMERGE(tree1->merges.head(), 0, param); } bool no_imerge_from_ranges= FALSE; - if (!(result= new SEL_TREE())) - DBUG_RETURN(result); /* Build the range part of the tree for the formula (1) */ if (sel_trees_can_be_ored(param, tree1, tree2, &ored_keys)) { bool must_be_ored= sel_trees_must_be_ored(param, tree1, tree2, ored_keys); no_imerge_from_ranges= must_be_ored; + + if (no_imerge_from_ranges && no_merges1 && no_merges2) + { + /* + Reuse tree1 as the result in simple cases. This reduces memory usage + for e.g. "key IN (c1, ..., cN)" which produces a lot of ranges. + */ + result= tree1; + result->keys_map.clear_all(); + } + else + { + if (!(result= new (param->mem_root) SEL_TREE(param->mem_root, + param->keys))) + { + DBUG_RETURN(result); + } + } + key_map::Iterator it(ored_keys); int key_no; while ((key_no= it++) != key_map::Iterator::BITMAP_END) @@ -8660,7 +8685,13 @@ tree_or(RANGE_OPT_PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) } result->type= tree1->type; } - + else + { + if (!result && !(result= new (param->mem_root) SEL_TREE(param->mem_root, + param->keys))) + DBUG_RETURN(result); + } + if (no_imerge_from_ranges && no_merges1 && no_merges2) { if (result->keys_map.is_clear_all()) @@ -8755,7 +8786,7 @@ and_all_keys(RANGE_OPT_PARAM *param, SEL_ARG *key1, SEL_ARG *key2, if (!key1) return &null_element; // Impossible ranges key1->use_count++; - key1->max_part_no= max(key2->max_part_no, key2->part+1); + key1->max_part_no= MY_MAX(key2->max_part_no, key2->part+1); return key1; } @@ -8848,7 +8879,7 @@ key_and(RANGE_OPT_PARAM *param, SEL_ARG *key1, SEL_ARG *key2, uint clone_flag) key1->use_count--; key2->use_count--; SEL_ARG *e1=key1->first(), *e2=key2->first(), *new_tree=0; - uint max_part_no= max(key1->max_part_no, key2->max_part_no); + uint max_part_no= MY_MAX(key1->max_part_no, key2->max_part_no); while (e1 && e2) { @@ -8866,7 +8897,7 @@ key_and(RANGE_OPT_PARAM *param, SEL_ARG *key1, SEL_ARG *key2, uint clone_flag) e2->incr_refs(); if (!next || next->type != SEL_ARG::IMPOSSIBLE) { - SEL_ARG *new_arg= e1->clone_and(e2); + SEL_ARG *new_arg= e1->clone_and(param->thd, e2); if (!new_arg) return &null_element; // End of memory new_arg->next_key_part=next; @@ -9046,7 +9077,7 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG *key1,SEL_ARG *key2) b: [---- */ - uint max_part_no= max(key1->max_part_no, key2->max_part_no); + uint max_part_no= MY_MAX(key1->max_part_no, key2->max_part_no); for (key2=key2->first(); key2; ) { @@ -9256,11 +9287,11 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG *key1,SEL_ARG *key2) are merged into one range by deleting first...last-1 from the key1 tree. In the figure, this applies to first and the two consecutive ranges. The range of last is then extended: - * last.min: Set to min(key2.min, first.min) + * last.min: Set to MY_MIN(key2.min, first.min) * last.max: If there is a last->next that overlaps key2 (i.e., last->next has a different next_key_part): Set adjacent to last->next.min - Otherwise: Set to max(key2.max, last.max) + Otherwise: Set to MY_MAX(key2.max, last.max) Result: key2: [****----------------------*******] @@ -9314,7 +9345,7 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG *key1,SEL_ARG *key2) ^ ^ last different next_key_part - Extend range of last up to max(last.max, key2.max): + Extend range of last up to MY_MAX(last.max, key2.max): key2: [--------*****] key1: [***----------**] [xxxx] */ @@ -10094,7 +10125,7 @@ void SEL_ARG::test_use_count(SEL_ARG *root) static ha_rows check_quick_select(PARAM *param, uint idx, bool index_only, SEL_ARG *tree, bool update_tbl_stats, - uint *mrr_flags, uint *bufsize, COST_VECT *cost) + uint *mrr_flags, uint *bufsize, Cost_estimate *cost) { SEL_ARG_RANGE_SEQ seq; RANGE_SEQ_IF seq_if = {NULL, sel_arg_range_seq_init, sel_arg_range_seq_next, 0, 0}; @@ -10149,14 +10180,16 @@ ha_rows check_quick_select(PARAM *param, uint idx, bool index_only, if (rows != HA_POS_ERROR) { param->quick_rows[keynr]= rows; + param->possible_keys.set_bit(keynr); if (update_tbl_stats) { param->table->quick_keys.set_bit(keynr); param->table->quick_key_parts[keynr]= param->max_key_part+1; param->table->quick_n_ranges[keynr]= param->range_count; param->table->quick_condition_rows= - min(param->table->quick_condition_rows, rows); + MY_MIN(param->table->quick_condition_rows, rows); param->table->quick_rows[keynr]= rows; + param->table->quick_costs[keynr]= cost->total_cost(); } } /* Figure out if the key scan is ROR (returns rows in ROWID order) or not */ @@ -10233,7 +10266,7 @@ static bool is_key_scan_ror(PARAM *param, uint keynr, uint8 nparts) KEY *table_key= param->table->key_info + keynr; KEY_PART_INFO *key_part= table_key->key_part + nparts; KEY_PART_INFO *key_part_end= (table_key->key_part + - table_key->key_parts); + table_key->user_defined_key_parts); uint pk_number; for (KEY_PART_INFO *kp= table_key->key_part; kp < key_part; kp++) @@ -10243,8 +10276,13 @@ static bool is_key_scan_ror(PARAM *param, uint keynr, uint8 nparts) if (param->table->field[fieldnr]->key_length() != kp->length) return FALSE; } - - if (key_part == key_part_end) + + /* + If there are equalities for all key parts, it is a ROR scan. If there are + equalities all keyparts and even some of key parts from "Extended Key" + index suffix, it is a ROR-scan, too. + */ + if (key_part >= key_part_end) return TRUE; key_part= table_key->key_part + nparts; @@ -10254,7 +10292,7 @@ static bool is_key_scan_ror(PARAM *param, uint keynr, uint8 nparts) KEY_PART_INFO *pk_part= param->table->key_info[pk_number].key_part; KEY_PART_INFO *pk_part_end= pk_part + - param->table->key_info[pk_number].key_parts; + param->table->key_info[pk_number].user_defined_key_parts; for (;(key_part!=key_part_end) && (pk_part != pk_part_end); ++key_part, ++pk_part) { @@ -10300,12 +10338,12 @@ get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree, uint mrr_flags, if (param->table->key_info[param->real_keynr[idx]].flags & HA_SPATIAL) quick=new QUICK_RANGE_SELECT_GEOM(param->thd, param->table, param->real_keynr[idx], - test(parent_alloc), + MY_TEST(parent_alloc), parent_alloc, &create_err); else quick=new QUICK_RANGE_SELECT(param->thd, param->table, param->real_keynr[idx], - test(parent_alloc), NULL, &create_err); + MY_TEST(parent_alloc), NULL, &create_err); if (quick) { @@ -10417,7 +10455,7 @@ get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key, flag=EQ_RANGE; if ((table_key->flags & HA_NOSAME) && min_part == key_tree->part && - key_tree->part == table_key->key_parts-1) + key_tree->part == table_key->user_defined_key_parts-1) { DBUG_ASSERT(min_part == max_part); if ((table_key->flags & HA_NULL_PART_KEY) && @@ -10432,7 +10470,9 @@ get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key, } /* Get range for retrieving rows in QUICK_SELECT::get_next */ - if (!(range= new QUICK_RANGE(param->min_key, + if (!(range= new (param->thd->mem_root) QUICK_RANGE( + param->thd, + param->min_key, (uint) (tmp_min_key - param->min_key), min_part >=0 ? make_keypart_map(min_part) : 0, param->max_key, @@ -10586,7 +10626,7 @@ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, QUICK_RANGE *range; uint part; bool create_err= FALSE; - COST_VECT cost; + Cost_estimate cost; uint max_used_key_len; old_root= thd->mem_root; @@ -10600,9 +10640,7 @@ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, */ thd->mem_root= old_root; - if (!quick || create_err) - return 0; /* no ranges found */ - if (quick->init()) + if (!quick || create_err || quick->init()) goto err; quick->records= records; @@ -10630,7 +10668,7 @@ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, key_part->null_bit= key_info->key_part[part].null_bit; key_part->flag= (uint8) key_info->key_part[part].key_part_flag; - max_used_key_len +=key_info->key_part[part].store_length; + max_used_key_len +=key_info->key_part[part].store_length; } quick->max_used_key_length= max_used_key_len; @@ -10650,7 +10688,7 @@ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, *ref->null_ref_key= 1; // Set null byte then create a range if (!(null_range= new (alloc) - QUICK_RANGE(ref->key_buff, ref->key_length, + QUICK_RANGE(thd, ref->key_buff, ref->key_length, make_prev_keypart_map(ref->key_parts), ref->key_buff, ref->key_length, make_prev_keypart_map(ref->key_parts), EQ_RANGE))) @@ -10983,7 +11021,7 @@ int QUICK_ROR_INTERSECT_SELECT::get_next() if ((error= quick->get_next())) { /* On certain errors like deadlock, trx might be rolled back.*/ - if (!current_thd->transaction_rollback_request) + if (!thd->transaction_rollback_request) quick_with_last_rowid->file->unlock_row(); DBUG_RETURN(error); } @@ -11011,7 +11049,7 @@ int QUICK_ROR_INTERSECT_SELECT::get_next() if ((error= quick->get_next())) { /* On certain errors like deadlock, trx might be rolled back.*/ - if (!current_thd->transaction_rollback_request) + if (!thd->transaction_rollback_request) quick_with_last_rowid->file->unlock_row(); DBUG_RETURN(error); } @@ -11135,9 +11173,6 @@ int QUICK_RANGE_SELECT::reset() last_range= NULL; cur_range= (QUICK_RANGE**) ranges.buffer; RANGE_SEQ_IF seq_funcs= {NULL, quick_range_seq_init, quick_range_seq_next, 0, 0}; - - if (in_ror_merged_scan) - head->column_bitmaps_set_no_signal(&column_bitmap, &column_bitmap); if (file->inited == handler::RND) { @@ -11146,6 +11181,9 @@ int QUICK_RANGE_SELECT::reset() DBUG_RETURN(error); } + if (in_ror_merged_scan) + head->column_bitmaps_set_no_signal(&column_bitmap, &column_bitmap); + if (file->inited == handler::NONE) { DBUG_EXECUTE_IF("bug14365043_2", @@ -11176,14 +11214,6 @@ int QUICK_RANGE_SELECT::reset() mrr_buf_desc->buffer= mrange_buff; mrr_buf_desc->buffer_end= mrange_buff + buf_size; mrr_buf_desc->end_of_used_area= mrange_buff; -#ifdef HAVE_valgrind - /* - We need this until ndb will use the buffer efficiently - (Now ndb stores complete row in here, instead of only the used fields - which gives us valgrind warnings in compare_record[]) - */ - bzero((char*) mrange_buff, buf_size); -#endif } if (!mrr_buf_desc) @@ -11219,26 +11249,21 @@ err: int QUICK_RANGE_SELECT::get_next() { range_id_t dummy; - MY_BITMAP * const save_read_set= head->read_set; - MY_BITMAP * const save_write_set= head->write_set; - + int result; DBUG_ENTER("QUICK_RANGE_SELECT::get_next"); - if (in_ror_merged_scan) - { - /* - We don't need to signal the bitmap change as the bitmap is always the - same for this head->file - */ - head->column_bitmaps_set_no_signal(&column_bitmap, &column_bitmap); - } - int result= file->multi_range_read_next(&dummy); + if (!in_ror_merged_scan) + DBUG_RETURN(file->multi_range_read_next(&dummy)); - if (in_ror_merged_scan) - { - /* Restore bitmaps set on entry */ - head->column_bitmaps_set_no_signal(save_read_set, save_write_set); - } + MY_BITMAP * const save_read_set= head->read_set; + MY_BITMAP * const save_write_set= head->write_set; + /* + We don't need to signal the bitmap change as the bitmap is always the + same for this head->file + */ + head->column_bitmaps_set_no_signal(&column_bitmap, &column_bitmap); + result= file->multi_range_read_next(&dummy); + head->column_bitmaps_set_no_signal(save_read_set, save_write_set); DBUG_RETURN(result); } @@ -11309,7 +11334,7 @@ int QUICK_RANGE_SELECT::get_next_prefix(uint prefix_length, result= file->read_range_first(last_range->min_keypart_map ? &start_key : 0, last_range->max_keypart_map ? &end_key : 0, - test(last_range->flag & EQ_RANGE), + MY_TEST(last_range->flag & EQ_RANGE), TRUE); if (last_range->flag == (UNIQUE_RANGE | EQ_RANGE)) last_range= 0; // Stop searching @@ -11461,7 +11486,7 @@ int QUICK_SELECT_DESC::get_next() if (last_range) { // Already read through key result = ((last_range->flag & EQ_RANGE && - used_key_parts <= head->key_info[index].key_parts) ? + used_key_parts <= head->key_info[index].user_defined_key_parts) ? file->ha_index_next_same(record, last_range->min_key, last_range->min_length) : file->ha_index_prev(record)); @@ -11509,7 +11534,7 @@ int QUICK_SELECT_DESC::get_next() } if (last_range->flag & EQ_RANGE && - used_key_parts <= head->key_info[index].key_parts) + used_key_parts <= head->key_info[index].user_defined_key_parts) { result= file->ha_index_read_map(record, last_range->max_key, @@ -11520,7 +11545,7 @@ int QUICK_SELECT_DESC::get_next() { DBUG_ASSERT(last_range->flag & NEAR_MAX || (last_range->flag & EQ_RANGE && - used_key_parts > head->key_info[index].key_parts) || + used_key_parts > head->key_info[index].user_defined_key_parts) || range_reads_after_key(last_range)); result= file->ha_index_read_map(record, last_range->max_key, last_range->max_keypart_map, @@ -11652,78 +11677,139 @@ void QUICK_SELECT_I::add_key_name(String *str, bool *first) } -void QUICK_RANGE_SELECT::add_info_string(String *str) +Explain_quick_select* QUICK_RANGE_SELECT::get_explain(MEM_ROOT *local_alloc) { - bool first= TRUE; - - add_key_name(str, &first); + Explain_quick_select *res; + if ((res= new (local_alloc) Explain_quick_select(QS_TYPE_RANGE))) + res->range.set(local_alloc, &head->key_info[index], max_used_key_length); + return res; } -void QUICK_INDEX_MERGE_SELECT::add_info_string(String *str) + +Explain_quick_select* +QUICK_GROUP_MIN_MAX_SELECT::get_explain(MEM_ROOT *local_alloc) { + Explain_quick_select *res; + if ((res= new (local_alloc) Explain_quick_select(QS_TYPE_GROUP_MIN_MAX))) + res->range.set(local_alloc, &head->key_info[index], max_used_key_length); + return res; +} + + +Explain_quick_select* +QUICK_INDEX_SORT_SELECT::get_explain(MEM_ROOT *local_alloc) +{ + Explain_quick_select *res; + if (!(res= new (local_alloc) Explain_quick_select(get_type()))) + return NULL; + QUICK_RANGE_SELECT *quick; - bool first= TRUE; + Explain_quick_select *child_explain; List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects); - - str->append(STRING_WITH_LEN("sort_union(")); while ((quick= it++)) { - quick->add_key_name(str, &first); + if ((child_explain= quick->get_explain(local_alloc))) + res->children.push_back(child_explain); + else + return NULL; } + if (pk_quick_select) - pk_quick_select->add_key_name(str, &first); - str->append(')'); + { + if ((child_explain= pk_quick_select->get_explain(local_alloc))) + res->children.push_back(child_explain); + else + return NULL; + } + return res; } -void QUICK_INDEX_INTERSECT_SELECT::add_info_string(String *str) + +/* + Same as QUICK_INDEX_SORT_SELECT::get_explain(), but primary key is printed + first +*/ + +Explain_quick_select* +QUICK_INDEX_INTERSECT_SELECT::get_explain(MEM_ROOT *local_alloc) { - QUICK_RANGE_SELECT *quick; - bool first= TRUE; - List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects); + Explain_quick_select *res; + Explain_quick_select *child_explain; + + if (!(res= new (local_alloc) Explain_quick_select(get_type()))) + return NULL; - str->append(STRING_WITH_LEN("sort_intersect(")); if (pk_quick_select) - pk_quick_select->add_key_name(str, &first); + { + if ((child_explain= pk_quick_select->get_explain(local_alloc))) + res->children.push_back(child_explain); + else + return NULL; + } + + QUICK_RANGE_SELECT *quick; + List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects); while ((quick= it++)) { - quick->add_key_name(str, &first); + if ((child_explain= quick->get_explain(local_alloc))) + res->children.push_back(child_explain); + else + return NULL; } - str->append(')'); + return res; } -void QUICK_ROR_INTERSECT_SELECT::add_info_string(String *str) + +Explain_quick_select* +QUICK_ROR_INTERSECT_SELECT::get_explain(MEM_ROOT *local_alloc) { - bool first= TRUE; + Explain_quick_select *res; + Explain_quick_select *child_explain; + + if (!(res= new (local_alloc) Explain_quick_select(get_type()))) + return NULL; + QUICK_SELECT_WITH_RECORD *qr; List_iterator_fast<QUICK_SELECT_WITH_RECORD> it(quick_selects); - - str->append(STRING_WITH_LEN("intersect(")); while ((qr= it++)) { - qr->quick->add_key_name(str, &first); + if ((child_explain= qr->quick->get_explain(local_alloc))) + res->children.push_back(child_explain); + else + return NULL; } + if (cpk_quick) - cpk_quick->add_key_name(str, &first); - str->append(')'); + { + if ((child_explain= cpk_quick->get_explain(local_alloc))) + res->children.push_back(child_explain); + else + return NULL; + } + return res; } -void QUICK_ROR_UNION_SELECT::add_info_string(String *str) +Explain_quick_select* +QUICK_ROR_UNION_SELECT::get_explain(MEM_ROOT *local_alloc) { + Explain_quick_select *res; + Explain_quick_select *child_explain; + + if (!(res= new (local_alloc) Explain_quick_select(get_type()))) + return NULL; + QUICK_SELECT_I *quick; - bool first= TRUE; List_iterator_fast<QUICK_SELECT_I> it(quick_selects); - - str->append(STRING_WITH_LEN("union(")); while ((quick= it++)) { - if (first) - first= FALSE; + if ((child_explain= quick->get_explain(local_alloc))) + res->children.push_back(child_explain); else - str->append(','); - quick->add_info_string(str); + return NULL; } - str->append(')'); + + return res; } @@ -11833,7 +11919,7 @@ void QUICK_RANGE_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set) { uint key_len; KEY_PART *part= key_parts; - for (key_len=0; key_len < max_used_key_length; + for (key_len=0; key_len < max_used_key_length; key_len += (part++)->store_length) { bitmap_set_bit(col_set, part->field->field_index); @@ -11845,7 +11931,7 @@ void QUICK_GROUP_MIN_MAX_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set) { uint key_len; KEY_PART_INFO *part= index_info->key_part; - for (key_len=0; key_len < max_used_key_length; + for (key_len=0; key_len < max_used_key_length; key_len += (part++)->store_length) { bitmap_set_bit(col_set, part->field->field_index); @@ -11894,8 +11980,6 @@ void QUICK_ROR_UNION_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set) *******************************************************************************/ static inline uint get_field_keypart(KEY *index, Field *field); -static inline SEL_ARG * get_index_range_tree(uint index, SEL_TREE* range_tree, - PARAM *param, uint *param_idx); static bool get_sel_arg_for_keypart(Field *field, SEL_ARG *index_range_tree, SEL_ARG **cur_range); static bool get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree, @@ -12044,7 +12128,7 @@ cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts, TODO - What happens if the query groups by the MIN/MAX field, and there is no - other field as in: "select min(a) from t1 group by a" ? + other field as in: "select MY_MIN(a) from t1 group by a" ? - We assume that the general correctness of the GROUP-BY query was checked before this point. Is this correct, or do we have to check it completely? - Lift the limitation in condition (B3), that is, make this access method @@ -12077,6 +12161,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) uint key_infix_len= 0; /* Length of key_infix. */ TRP_GROUP_MIN_MAX *read_plan= NULL; /* The eventually constructed TRP. */ uint key_part_nr; + uint elements_in_group; ORDER *tmp_group; Item *item; Item_field *item_field; @@ -12158,10 +12243,12 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) } /* Check (GA4) - that there are no expressions among the group attributes. */ + elements_in_group= 0; for (tmp_group= join->group_list; tmp_group; tmp_group= tmp_group->next) { if ((*tmp_group->item)->real_item()->type() != Item::FIELD_ITEM) DBUG_RETURN(NULL); + elements_in_group++; } /* @@ -12169,8 +12256,6 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) (GA1,GA2) are all TRUE. If there is more than one such index, select the first one. Here we set the variables: group_prefix_len and index_info. */ - KEY *cur_index_info= table->key_info; - KEY *cur_index_info_end= cur_index_info + table->s->keys; /* Cost-related variables for the best index so far. */ double best_read_cost= DBL_MAX; ha_rows best_records= 0; @@ -12182,11 +12267,12 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) uint max_key_part; SEL_ARG *cur_index_tree= NULL; ha_rows cur_quick_prefix_records= 0; - uint cur_param_idx=MAX_KEY; - for (uint cur_index= 0 ; cur_index_info != cur_index_info_end ; - cur_index_info++, cur_index++) + // We go through allowed indexes + for (uint cur_param_idx= 0; cur_param_idx < param->keys ; ++cur_param_idx) { + const uint cur_index= param->real_keynr[cur_param_idx]; + KEY *const cur_index_info= &table->key_info[cur_index]; KEY_PART_INFO *cur_part; KEY_PART_INFO *end_part; /* Last part for loops. */ /* Last index part. */ @@ -12204,10 +12290,23 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) uchar cur_key_infix[MAX_KEY_LENGTH]; uint cur_used_key_parts; - /* Check (B1) - if current index is covering. */ - if (!table->covering_keys.is_set(cur_index)) - goto next_index; - + /* + Check (B1) - if current index is covering. + (was also: "Exclude UNIQUE indexes ..." but this was removed because + there are cases Loose Scan over a multi-part index is useful). + */ + if (!table->covering_keys.is_set(cur_index) || + !table->keys_in_use_for_group_by.is_set(cur_index)) + continue; + + /* + This function is called on the precondition that the index is covering. + Therefore if the GROUP BY list contains more elements than the index, + these are duplicates. The GROUP BY list cannot be a prefix of the index. + */ + if (elements_in_group > table->actual_n_key_parts(cur_index_info)) + continue; + /* Unless extended keys can be used for cur_index: If the current storage manager is such that it appends the primary key to @@ -12218,7 +12317,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) does not qualify as covering in our case. If this is the case, below we check that all query fields are indeed covered by 'cur_index'. */ - if (cur_index_info->key_parts == table->actual_n_key_parts(cur_index_info) + if (cur_index_info->user_defined_key_parts == table->actual_n_key_parts(cur_index_info) && pk < MAX_KEY && cur_index != pk && (table->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX)) { @@ -12268,13 +12367,6 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) else goto next_index; } - /* - This function is called on the precondition that the index is covering. - Therefore if the GROUP BY list contains more elements than the index, - these are duplicates. The GROUP BY list cannot be a prefix of the index. - */ - if (cur_part == end_part && tmp_group) - goto next_index; } /* Check (GA2) if this is a DISTINCT query. @@ -12284,8 +12376,8 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) Later group_fields_array of ORDER objects is used to convert the query to a GROUP query. */ - if ((!join->group_list && join->select_distinct) || - is_agg_distinct) + if ((!join->group && join->select_distinct) || + is_agg_distinct) { if (!is_agg_distinct) { @@ -12319,7 +12411,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) cur_group_prefix_len+= cur_part->store_length; used_key_parts_map.set_bit(key_part_nr); ++cur_group_key_parts; - max_key_part= max(max_key_part,key_part_nr); + max_key_part= MY_MAX(max_key_part,key_part_nr); } /* Check that used key parts forms a prefix of the index. @@ -12344,6 +12436,16 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) } /* + Aplly a heuristic: there is no point to use loose index scan when we're + using the whole unique index. + */ + if (cur_index_info->flags & HA_NOSAME && + cur_group_key_parts == cur_index_info->user_defined_key_parts) + { + goto next_index; + } + + /* Check (NGA1, NGA2) and extract a sequence of constants to be used as part of all search keys. */ @@ -12373,9 +12475,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) { if (tree) { - uint dummy; - SEL_ARG *index_range_tree= get_index_range_tree(cur_index, tree, param, - &dummy); + SEL_ARG *index_range_tree= tree->keys[cur_param_idx]; if (!get_constant_key_infix(cur_index_info, index_range_tree, first_non_group_part, min_max_arg_part, last_part, thd, cur_key_infix, @@ -12439,9 +12539,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) */ if (tree && min_max_arg_item) { - uint dummy; - SEL_ARG *index_range_tree= get_index_range_tree(cur_index, tree, param, - &dummy); + SEL_ARG *index_range_tree= tree->keys[cur_param_idx]; SEL_ARG *cur_range= NULL; if (get_sel_arg_for_keypart(min_max_arg_part->field, index_range_tree, &cur_range) || @@ -12459,11 +12557,9 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) /* Compute the cost of using this index. */ if (tree) { - /* Find the SEL_ARG sub-tree that corresponds to the chosen index. */ - cur_index_tree= get_index_range_tree(cur_index, tree, param, - &cur_param_idx); + cur_index_tree= tree->keys[cur_param_idx]; /* Check if this range tree can be used for prefix retrieval. */ - COST_VECT dummy_cost; + Cost_estimate dummy_cost; uint mrr_flags= HA_MRR_USE_DEFAULT_IMPL; uint mrr_bufsize=0; cur_quick_prefix_records= check_quick_select(param, cur_param_idx, @@ -12729,24 +12825,23 @@ check_group_min_max_predicates(Item *cond, Item_field *min_max_arg_item, if (!simple_pred(pred, args, &inv)) DBUG_RETURN(FALSE); - /* Check for compatible string comparisons - similar to get_mm_leaf. */ - if (args[0] && args[1] && !args[2] && // this is a binary function - min_max_arg_item->result_type() == STRING_RESULT && - /* - Don't use an index when comparing strings of different collations. - */ - ((args[1]->result_type() == STRING_RESULT && - image_type == Field::itRAW && - ((Field_str*) min_max_arg_item->field)->charset() != - pred->compare_collation()) - || - /* - We can't always use indexes when comparing a string index to a - number. - */ - (args[1]->result_type() != STRING_RESULT && - min_max_arg_item->field->cmp_type() != args[1]->result_type()))) - DBUG_RETURN(FALSE); + if (args[0] && args[1]) // this is a binary function or BETWEEN + { + DBUG_ASSERT(pred->is_bool_type()); + Item_bool_func *bool_func= (Item_bool_func*) pred; + Field *field= min_max_arg_item->field; + if (!args[2]) // this is a binary function + { + if (!field->can_optimize_group_min_max(bool_func, args[1])) + DBUG_RETURN(FALSE); + } + else // this is BETWEEN + { + if (!field->can_optimize_group_min_max(bool_func, args[1]) || + !field->can_optimize_group_min_max(bool_func, args[2])) + DBUG_RETURN(FALSE); + } + } } else has_other= true; @@ -12996,44 +13091,6 @@ get_field_keypart(KEY *index, Field *field) /* - Find the SEL_ARG sub-tree that corresponds to the chosen index. - - SYNOPSIS - get_index_range_tree() - index [in] The ID of the index being looked for - range_tree[in] Tree of ranges being searched - param [in] PARAM from SQL_SELECT::test_quick_select - param_idx [out] Index in the array PARAM::key that corresponds to 'index' - - DESCRIPTION - - A SEL_TREE contains range trees for all usable indexes. This procedure - finds the SEL_ARG sub-tree for 'index'. The members of a SEL_TREE are - ordered in the same way as the members of PARAM::key, thus we first find - the corresponding index in the array PARAM::key. This index is returned - through the variable param_idx, to be used later as argument of - check_quick_select(). - - RETURN - Pointer to the SEL_ARG subtree that corresponds to index. -*/ - -SEL_ARG * get_index_range_tree(uint index, SEL_TREE* range_tree, PARAM *param, - uint *param_idx) -{ - uint idx= 0; /* Index nr in param->key_parts */ - while (idx < param->keys) - { - if (index == param->real_keynr[idx]) - break; - idx++; - } - *param_idx= idx; - return(range_tree->keys[idx]); -} - - -/* Compute the cost of a quick_group_min_max_select for a particular index. SYNOPSIS @@ -13053,7 +13110,7 @@ SEL_ARG * get_index_range_tree(uint index, SEL_TREE* range_tree, PARAM *param, DESCRIPTION This method computes the access cost of a TRP_GROUP_MIN_MAX instance and - the number of rows returned. It updates this->read_cost and this->records. + the number of rows returned. NOTES The cost computation distinguishes several cases: @@ -13100,37 +13157,46 @@ void cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts, double *read_cost, ha_rows *records) { ha_rows table_records; - uint num_groups; - uint num_blocks; - uint keys_per_block; - uint keys_per_group; - uint keys_per_subgroup; /* Average number of keys in sub-groups */ + ha_rows num_groups; + ha_rows num_blocks; + uint keys_per_block; + ha_rows keys_per_group; + ha_rows keys_per_subgroup; /* Average number of keys in sub-groups */ /* formed by a key infix. */ double p_overlap; /* Probability that a sub-group overlaps two blocks. */ double quick_prefix_selectivity; double io_cost; - double cpu_cost= 0; /* TODO: CPU cost of index_read calls? */ DBUG_ENTER("cost_group_min_max"); - table_records= table->file->stats.records; - keys_per_block= (table->file->stats.block_size / 2 / - (index_info->key_length + table->file->ref_length) - + 1); - num_blocks= (uint)(table_records / keys_per_block) + 1; + table_records= table->stat_records(); + keys_per_block= (uint) (table->file->stats.block_size / 2 / + (index_info->key_length + table->file->ref_length) + + 1); + num_blocks= (ha_rows)(table_records / keys_per_block) + 1; /* Compute the number of keys in a group. */ - keys_per_group= index_info->rec_per_key[group_key_parts - 1]; + if (!group_key_parts) + { + /* Summary over the whole table */ + keys_per_group= table_records; + } + else + { + keys_per_group= (ha_rows) index_info->actual_rec_per_key(group_key_parts - + 1); + } + if (keys_per_group == 0) /* If there is no statistics try to guess */ /* each group contains 10% of all records */ - keys_per_group= (uint)(table_records / 10) + 1; - num_groups= (uint)(table_records / keys_per_group) + 1; + keys_per_group= (table_records / 10) + 1; + num_groups= (table_records / keys_per_group) + 1; /* Apply the selectivity of the quick select for group prefixes. */ if (range_tree && (quick_prefix_records != HA_POS_ERROR)) { quick_prefix_selectivity= (double) quick_prefix_records / (double) table_records; - num_groups= (uint) rint(num_groups * quick_prefix_selectivity); + num_groups= (ha_rows) rint(num_groups * quick_prefix_selectivity); set_if_bigger(num_groups, 1); } @@ -13139,16 +13205,16 @@ void cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts, Compute the probability that two ends of a subgroup are inside different blocks. */ - keys_per_subgroup= index_info->rec_per_key[used_key_parts - 1]; + keys_per_subgroup= (ha_rows) index_info->actual_rec_per_key(used_key_parts - 1); if (keys_per_subgroup >= keys_per_block) /* If a subgroup is bigger than */ p_overlap= 1.0; /* a block, it will overlap at least two blocks. */ else { double blocks_per_group= (double) num_blocks / (double) num_groups; p_overlap= (blocks_per_group * (keys_per_subgroup - 1)) / keys_per_group; - p_overlap= min(p_overlap, 1.0); + p_overlap= MY_MIN(p_overlap, 1.0); } - io_cost= (double) min(num_groups * (1 + p_overlap), num_blocks); + io_cost= (double) MY_MIN(num_groups * (1 + p_overlap), num_blocks); } else io_cost= (keys_per_group > keys_per_block) ? @@ -13157,19 +13223,33 @@ void cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts, (double) num_blocks; /* - TODO: If there is no WHERE clause and no other expressions, there should be - no CPU cost. We leave it here to make this cost comparable to that of index - scan as computed in SQL_SELECT::test_quick_select(). + CPU cost must be comparable to that of an index scan as computed + in SQL_SELECT::test_quick_select(). When the groups are small, + e.g. for a unique index, using index scan will be cheaper since it + reads the next record without having to re-position to it on every + group. To make the CPU cost reflect this, we estimate the CPU cost + as the sum of: + 1. Cost for evaluating the condition (similarly as for index scan). + 2. Cost for navigating the index structure (assuming a b-tree). + Note: We only add the cost for one comparision per block. For a + b-tree the number of comparisons will be larger. + TODO: This cost should be provided by the storage engine. */ - cpu_cost= (double) num_groups / TIME_FOR_COMPARE; + const double tree_traversal_cost= + ceil(log(static_cast<double>(table_records))/ + log(static_cast<double>(keys_per_block))) * + 1/double(2*TIME_FOR_COMPARE); + + const double cpu_cost= num_groups * + (tree_traversal_cost + 1/double(TIME_FOR_COMPARE)); *read_cost= io_cost + cpu_cost; *records= num_groups; DBUG_PRINT("info", - ("table rows: %lu keys/block: %u keys/group: %u result rows: %lu blocks: %u", - (ulong)table_records, keys_per_block, keys_per_group, - (ulong) *records, num_blocks)); + ("table rows: %lu keys/block: %u keys/group: %lu result rows: %lu blocks: %lu", + (ulong)table_records, keys_per_block, (ulong) keys_per_group, + (ulong) *records, (ulong) num_blocks)); DBUG_VOID_RETURN; } @@ -13336,7 +13416,8 @@ QUICK_GROUP_MIN_MAX_SELECT(TABLE *table, JOIN *join_arg, bool have_min_arg, DBUG_ASSERT(!parent_alloc); if (!parent_alloc) { - init_sql_alloc(&alloc, join->thd->variables.range_alloc_block_size, 0); + init_sql_alloc(&alloc, join->thd->variables.range_alloc_block_size, 0, + MYF(MY_THREAD_SPECIFIC)); join->thd->mem_root= &alloc; } else @@ -13365,15 +13446,21 @@ int QUICK_GROUP_MIN_MAX_SELECT::init() { if (group_prefix) /* Already initialized. */ return 0; - - if (!(last_prefix= (uchar*) alloc_root(&alloc, group_prefix_len))) + + /* + We allocate one byte more to serve the case when the last field in + the buffer is compared using uint3korr (e.g. a Field_newdate field) + */ + if (!(last_prefix= (uchar*) alloc_root(&alloc, group_prefix_len+1))) return 1; /* We may use group_prefix to store keys with all select fields, so allocate enough space for it. + We allocate one byte more to serve the case when the last field in + the buffer is compared using uint3korr (e.g. a Field_newdate field) */ if (!(group_prefix= (uchar*) alloc_root(&alloc, - real_prefix_len + min_max_arg_len))) + real_prefix_len+min_max_arg_len+1))) return 1; if (key_infix_len > 0) @@ -13391,7 +13478,8 @@ int QUICK_GROUP_MIN_MAX_SELECT::init() if (min_max_arg_part) { - if (my_init_dynamic_array(&min_max_ranges, sizeof(QUICK_RANGE*), 16, 16)) + if (my_init_dynamic_array(&min_max_ranges, sizeof(QUICK_RANGE*), 16, 16, + MYF(MY_THREAD_SPECIFIC))) return 1; if (have_min) @@ -13499,7 +13587,7 @@ bool QUICK_GROUP_MIN_MAX_SELECT::add_range(SEL_ARG *sel_range) min_max_arg_len) == 0) range_flag|= EQ_RANGE; /* equality condition */ } - range= new QUICK_RANGE(sel_range->min_value, min_max_arg_len, + range= new QUICK_RANGE(join->thd, sel_range->min_value, min_max_arg_len, make_keypart_map(sel_range->part), sel_range->max_value, min_max_arg_len, make_keypart_map(sel_range->part), @@ -14003,6 +14091,36 @@ int QUICK_GROUP_MIN_MAX_SELECT::next_prefix() } +/** + Allocate a temporary buffer, populate the buffer using the group prefix key + and the min/max field key, and compare the result to the current key pointed + by index_info. + + @param key - the min or max field key + @param length - length of "key" +*/ +int +QUICK_GROUP_MIN_MAX_SELECT::cmp_min_max_key(const uchar *key, uint16 length) +{ + /* + Allocate a buffer. + Note, we allocate one extra byte, because some of Field_xxx::cmp(), + e.g. Field_newdate::cmp(), use uint3korr() which actually read four bytes + and then bit-and the read value with 0xFFFFFF. + See "MDEV-7920 main.group_min_max fails ... with valgrind" for details. + */ + uchar *buffer= (uchar*) my_alloca(real_prefix_len + min_max_arg_len + 1); + /* Concatenate the group prefix key and the min/max field key */ + memcpy(buffer, group_prefix, real_prefix_len); + memcpy(buffer + real_prefix_len, key, length); + /* Compare the key pointed by key_info to the created key */ + int cmp_res= key_cmp(index_info->key_part, buffer, + real_prefix_len + min_max_arg_len); + my_afree(buffer); + return cmp_res; +} + + /* Find the minimal key in a group that satisfies some range conditions for the min/max argument field. @@ -14104,15 +14222,7 @@ int QUICK_GROUP_MIN_MAX_SELECT::next_min_in_range() /* If there is an upper limit, check if the found key is in the range. */ if ( !(cur_range->flag & NO_MAX_RANGE) ) { - /* Compose the MAX key for the range. */ - uchar *max_key= (uchar*) my_alloca(real_prefix_len + min_max_arg_len); - memcpy(max_key, group_prefix, real_prefix_len); - memcpy(max_key + real_prefix_len, cur_range->max_key, - cur_range->max_length); - /* Compare the found key with max_key. */ - int cmp_res= key_cmp(index_info->key_part, max_key, - real_prefix_len + min_max_arg_len); - my_afree(max_key); + int cmp_res= cmp_min_max_key(cur_range->max_key, cur_range->max_length); /* The key is outside of the range if: the interval is open and the key is equal to the maximum boundry @@ -14230,15 +14340,7 @@ int QUICK_GROUP_MIN_MAX_SELECT::next_max_in_range() /* If there is a lower limit, check if the found key is in the range. */ if ( !(cur_range->flag & NO_MIN_RANGE) ) { - /* Compose the MIN key for the range. */ - uchar *min_key= (uchar*) my_alloca(real_prefix_len + min_max_arg_len); - memcpy(min_key, group_prefix, real_prefix_len); - memcpy(min_key + real_prefix_len, cur_range->min_key, - cur_range->min_length); - /* Compare the found key with min_key. */ - int cmp_res= key_cmp(index_info->key_part, min_key, - real_prefix_len + min_max_arg_len); - my_afree(min_key); + int cmp_res= cmp_min_max_key(cur_range->min_key, cur_range->min_length); /* The key is outside of the range if: the interval is open and the key is equal to the minimum boundry @@ -14350,16 +14452,12 @@ void QUICK_GROUP_MIN_MAX_SELECT::add_keys_and_lengths(String *key_names, static void print_sel_tree(PARAM *param, SEL_TREE *tree, key_map *tree_map, const char *msg) { - SEL_ARG **key,**end; - int idx; char buff[1024]; DBUG_ENTER("print_sel_tree"); String tmp(buff,sizeof(buff),&my_charset_bin); tmp.length(0); - for (idx= 0,key=tree->keys, end=key+param->keys ; - key != end ; - key++,idx++) + for (uint idx= 0; idx < param->keys; idx++) { if (tree_map->is_set(idx)) { @@ -14599,11 +14697,3 @@ void QUICK_GROUP_MIN_MAX_SELECT::dbug_dump(int indent, bool verbose) #endif /* !DBUG_OFF */ -/***************************************************************************** -** Instantiate templates -*****************************************************************************/ - -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class List<QUICK_RANGE>; -template class List_iterator<QUICK_RANGE>; -#endif diff --git a/sql/opt_range.h b/sql/opt_range.h index b8b46ae5ab1..0c495639db6 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -38,7 +38,7 @@ class JOIN; class Item_sum; -typedef struct st_key_part { +struct KEY_PART { uint16 key,part; /* See KEY_PART_INFO for meaning of the next two: */ uint16 store_length, length; @@ -50,9 +50,619 @@ typedef struct st_key_part { uint8 flag; Field *field; Field::imagetype image_type; -} KEY_PART; +}; + + +class RANGE_OPT_PARAM; +/* + A construction block of the SEL_ARG-graph. + + The following description only covers graphs of SEL_ARG objects with + sel_arg->type==KEY_RANGE: + + One SEL_ARG object represents an "elementary interval" in form + + min_value <=? table.keypartX <=? max_value + + The interval is a non-empty interval of any kind: with[out] minimum/maximum + bound, [half]open/closed, single-point interval, etc. + + 1. SEL_ARG GRAPH STRUCTURE + + SEL_ARG objects are linked together in a graph. The meaning of the graph + is better demostrated by an example: + + tree->keys[i] + | + | $ $ + | part=1 $ part=2 $ part=3 + | $ $ + | +-------+ $ +-------+ $ +--------+ + | | kp1<1 |--$-->| kp2=5 |--$-->| kp3=10 | + | +-------+ $ +-------+ $ +--------+ + | | $ $ | + | | $ $ +--------+ + | | $ $ | kp3=12 | + | | $ $ +--------+ + | +-------+ $ $ + \->| kp1=2 |--$--------------$-+ + +-------+ $ $ | +--------+ + | $ $ ==>| kp3=11 | + +-------+ $ $ | +--------+ + | kp1=3 |--$--------------$-+ | + +-------+ $ $ +--------+ + | $ $ | kp3=14 | + ... $ $ +--------+ + + The entire graph is partitioned into "interval lists". + + An interval list is a sequence of ordered disjoint intervals over the same + key part. SEL_ARG are linked via "next" and "prev" pointers. Additionally, + all intervals in the list form an RB-tree, linked via left/right/parent + pointers. The RB-tree root SEL_ARG object will be further called "root of the + interval list". + + In the example pic, there are 4 interval lists: + "kp<1 OR kp1=2 OR kp1=3", "kp2=5", "kp3=10 OR kp3=12", "kp3=11 OR kp3=13". + The vertical lines represent SEL_ARG::next/prev pointers. + + In an interval list, each member X may have SEL_ARG::next_key_part pointer + pointing to the root of another interval list Y. The pointed interval list + must cover a key part with greater number (i.e. Y->part > X->part). + + In the example pic, the next_key_part pointers are represented by + horisontal lines. + + 2. SEL_ARG GRAPH SEMANTICS + + It represents a condition in a special form (we don't have a name for it ATM) + The SEL_ARG::next/prev is "OR", and next_key_part is "AND". + + For example, the picture represents the condition in form: + (kp1 < 1 AND kp2=5 AND (kp3=10 OR kp3=12)) OR + (kp1=2 AND (kp3=11 OR kp3=14)) OR + (kp1=3 AND (kp3=11 OR kp3=14)) + + + 3. SEL_ARG GRAPH USE + + Use get_mm_tree() to construct SEL_ARG graph from WHERE condition. + Then walk the SEL_ARG graph and get a list of dijsoint ordered key + intervals (i.e. intervals in form + + (constA1, .., const1_K) < (keypart1,.., keypartK) < (constB1, .., constB_K) + + Those intervals can be used to access the index. The uses are in: + - check_quick_select() - Walk the SEL_ARG graph and find an estimate of + how many table records are contained within all + intervals. + - get_quick_select() - Walk the SEL_ARG, materialize the key intervals, + and create QUICK_RANGE_SELECT object that will + read records within these intervals. + + 4. SPACE COMPLEXITY NOTES + + SEL_ARG graph is a representation of an ordered disjoint sequence of + intervals over the ordered set of index tuple values. + + For multi-part keys, one can construct a WHERE expression such that its + list of intervals will be of combinatorial size. Here is an example: + + (keypart1 IN (1,2, ..., n1)) AND + (keypart2 IN (1,2, ..., n2)) AND + (keypart3 IN (1,2, ..., n3)) + + For this WHERE clause the list of intervals will have n1*n2*n3 intervals + of form + + (keypart1, keypart2, keypart3) = (k1, k2, k3), where 1 <= k{i} <= n{i} + + SEL_ARG graph structure aims to reduce the amount of required space by + "sharing" the elementary intervals when possible (the pic at the + beginning of this comment has examples of such sharing). The sharing may + prevent combinatorial blowup: + + There are WHERE clauses that have combinatorial-size interval lists but + will be represented by a compact SEL_ARG graph. + Example: + (keypartN IN (1,2, ..., n1)) AND + ... + (keypart2 IN (1,2, ..., n2)) AND + (keypart1 IN (1,2, ..., n3)) + + but not in all cases: + + - There are WHERE clauses that do have a compact SEL_ARG-graph + representation but get_mm_tree() and its callees will construct a + graph of combinatorial size. + Example: + (keypart1 IN (1,2, ..., n1)) AND + (keypart2 IN (1,2, ..., n2)) AND + ... + (keypartN IN (1,2, ..., n3)) + + - There are WHERE clauses for which the minimal possible SEL_ARG graph + representation will have combinatorial size. + Example: + By induction: Let's take any interval on some keypart in the middle: + + kp15=c0 + + Then let's AND it with this interval 'structure' from preceding and + following keyparts: + + (kp14=c1 AND kp16=c3) OR keypart14=c2) (*) + + We will obtain this SEL_ARG graph: + + kp14 $ kp15 $ kp16 + $ $ + +---------+ $ +---------+ $ +---------+ + | kp14=c1 |--$-->| kp15=c0 |--$-->| kp16=c3 | + +---------+ $ +---------+ $ +---------+ + | $ $ + +---------+ $ +---------+ $ + | kp14=c2 |--$-->| kp15=c0 | $ + +---------+ $ +---------+ $ + $ $ + + Note that we had to duplicate "kp15=c0" and there was no way to avoid + that. + The induction step: AND the obtained expression with another "wrapping" + expression like (*). + When the process ends because of the limit on max. number of keyparts + we'll have: + + WHERE clause length is O(3*#max_keyparts) + SEL_ARG graph size is O(2^(#max_keyparts/2)) + + (it is also possible to construct a case where instead of 2 in 2^n we + have a bigger constant, e.g. 4, and get a graph with 4^(31/2)= 2^31 + nodes) + + We avoid consuming too much memory by setting a limit on the number of + SEL_ARG object we can construct during one range analysis invocation. +*/ + +class SEL_ARG :public Sql_alloc +{ + static int sel_cmp(Field *field, uchar *a, uchar *b, uint8 a_flag, + uint8 b_flag); +public: + uint8 min_flag,max_flag,maybe_flag; + uint8 part; // Which key part + uint8 maybe_null; + /* + The ordinal number the least significant component encountered in + the ranges of the SEL_ARG tree (the first component has number 1) + */ + uint16 max_part_no; + /* + Number of children of this element in the RB-tree, plus 1 for this + element itself. + */ + uint16 elements; + /* + Valid only for elements which are RB-tree roots: Number of times this + RB-tree is referred to (it is referred by SEL_ARG::next_key_part or by + SEL_TREE::keys[i] or by a temporary SEL_ARG* variable) + */ + ulong use_count; + + Field *field; + uchar *min_value,*max_value; // Pointer to range + + /* + eq_tree() requires that left == right == 0 if the type is MAYBE_KEY. + */ + SEL_ARG *left,*right; /* R-B tree children */ + SEL_ARG *next,*prev; /* Links for bi-directional interval list */ + SEL_ARG *parent; /* R-B tree parent */ + SEL_ARG *next_key_part; + enum leaf_color { BLACK,RED } color; + enum Type { IMPOSSIBLE, MAYBE, MAYBE_KEY, KEY_RANGE } type; + + enum { MAX_SEL_ARGS = 16000 }; + + SEL_ARG() {} + SEL_ARG(SEL_ARG &); + SEL_ARG(Field *,const uchar *, const uchar *); + SEL_ARG(Field *field, uint8 part, uchar *min_value, uchar *max_value, + uint8 min_flag, uint8 max_flag, uint8 maybe_flag); + SEL_ARG(enum Type type_arg) + :min_flag(0), max_part_no(0) /* first key part means 1. 0 mean 'no parts'*/, + elements(1),use_count(1),left(0),right(0), + next_key_part(0), color(BLACK), type(type_arg) + {} + /** + returns true if a range predicate is equal. Use all_same() + to check for equality of all the predicates on this keypart. + */ + inline bool is_same(const SEL_ARG *arg) const + { + if (type != arg->type || part != arg->part) + return false; + if (type != KEY_RANGE) + return true; + return cmp_min_to_min(arg) == 0 && cmp_max_to_max(arg) == 0; + } + /** + returns true if all the predicates in the keypart tree are equal + */ + bool all_same(const SEL_ARG *arg) const + { + if (type != arg->type || part != arg->part) + return false; + if (type != KEY_RANGE) + return true; + if (arg == this) + return true; + const SEL_ARG *cmp_arg= arg->first(); + const SEL_ARG *cur_arg= first(); + for (; cur_arg && cmp_arg && cur_arg->is_same(cmp_arg); + cur_arg= cur_arg->next, cmp_arg= cmp_arg->next) ; + if (cur_arg || cmp_arg) + return false; + return true; + } + inline void merge_flags(SEL_ARG *arg) { maybe_flag|=arg->maybe_flag; } + inline void maybe_smaller() { maybe_flag=1; } + /* Return true iff it's a single-point null interval */ + inline bool is_null_interval() { return maybe_null && max_value[0] == 1; } + inline int cmp_min_to_min(const SEL_ARG* arg) const + { + return sel_cmp(field,min_value, arg->min_value, min_flag, arg->min_flag); + } + inline int cmp_min_to_max(const SEL_ARG* arg) const + { + return sel_cmp(field,min_value, arg->max_value, min_flag, arg->max_flag); + } + inline int cmp_max_to_max(const SEL_ARG* arg) const + { + return sel_cmp(field,max_value, arg->max_value, max_flag, arg->max_flag); + } + inline int cmp_max_to_min(const SEL_ARG* arg) const + { + return sel_cmp(field,max_value, arg->min_value, max_flag, arg->min_flag); + } + SEL_ARG *clone_and(THD *thd, SEL_ARG* arg) + { // Get overlapping range + uchar *new_min,*new_max; + uint8 flag_min,flag_max; + if (cmp_min_to_min(arg) >= 0) + { + new_min=min_value; flag_min=min_flag; + } + else + { + new_min=arg->min_value; flag_min=arg->min_flag; /* purecov: deadcode */ + } + if (cmp_max_to_max(arg) <= 0) + { + new_max=max_value; flag_max=max_flag; + } + else + { + new_max=arg->max_value; flag_max=arg->max_flag; + } + return new (thd->mem_root) SEL_ARG(field, part, new_min, new_max, flag_min, + flag_max, + MY_TEST(maybe_flag && arg->maybe_flag)); + } + SEL_ARG *clone_first(SEL_ARG *arg) + { // min <= X < arg->min + return new SEL_ARG(field,part, min_value, arg->min_value, + min_flag, arg->min_flag & NEAR_MIN ? 0 : NEAR_MAX, + maybe_flag | arg->maybe_flag); + } + SEL_ARG *clone_last(SEL_ARG *arg) + { // min <= X <= key_max + return new SEL_ARG(field, part, min_value, arg->max_value, + min_flag, arg->max_flag, maybe_flag | arg->maybe_flag); + } + SEL_ARG *clone(RANGE_OPT_PARAM *param, SEL_ARG *new_parent, SEL_ARG **next); + + bool copy_min(SEL_ARG* arg) + { // Get overlapping range + if (cmp_min_to_min(arg) > 0) + { + min_value=arg->min_value; min_flag=arg->min_flag; + if ((max_flag & (NO_MAX_RANGE | NO_MIN_RANGE)) == + (NO_MAX_RANGE | NO_MIN_RANGE)) + return 1; // Full range + } + maybe_flag|=arg->maybe_flag; + return 0; + } + bool copy_max(SEL_ARG* arg) + { // Get overlapping range + if (cmp_max_to_max(arg) <= 0) + { + max_value=arg->max_value; max_flag=arg->max_flag; + if ((max_flag & (NO_MAX_RANGE | NO_MIN_RANGE)) == + (NO_MAX_RANGE | NO_MIN_RANGE)) + return 1; // Full range + } + maybe_flag|=arg->maybe_flag; + return 0; + } + + void copy_min_to_min(SEL_ARG *arg) + { + min_value=arg->min_value; min_flag=arg->min_flag; + } + void copy_min_to_max(SEL_ARG *arg) + { + max_value=arg->min_value; + max_flag=arg->min_flag & NEAR_MIN ? 0 : NEAR_MAX; + } + void copy_max_to_min(SEL_ARG *arg) + { + min_value=arg->max_value; + min_flag=arg->max_flag & NEAR_MAX ? 0 : NEAR_MIN; + } + /* returns a number of keypart values (0 or 1) appended to the key buffer */ + int store_min(uint length, uchar **min_key,uint min_key_flag) + { + /* "(kp1 > c1) AND (kp2 OP c2) AND ..." -> (kp1 > c1) */ + if ((min_flag & GEOM_FLAG) || + (!(min_flag & NO_MIN_RANGE) && + !(min_key_flag & (NO_MIN_RANGE | NEAR_MIN)))) + { + if (maybe_null && *min_value) + { + **min_key=1; + bzero(*min_key+1,length-1); + } + else + memcpy(*min_key,min_value,length); + (*min_key)+= length; + return 1; + } + return 0; + } + /* returns a number of keypart values (0 or 1) appended to the key buffer */ + int store_max(uint length, uchar **max_key, uint max_key_flag) + { + if (!(max_flag & NO_MAX_RANGE) && + !(max_key_flag & (NO_MAX_RANGE | NEAR_MAX))) + { + if (maybe_null && *max_value) + { + **max_key=1; + bzero(*max_key+1,length-1); + } + else + memcpy(*max_key,max_value,length); + (*max_key)+= length; + return 1; + } + return 0; + } + + /* + Returns a number of keypart values appended to the key buffer + for min key and max key. This function is used by both Range + Analysis and Partition pruning. For partition pruning we have + to ensure that we don't store also subpartition fields. Thus + we have to stop at the last partition part and not step into + the subpartition fields. For Range Analysis we set last_part + to MAX_KEY which we should never reach. + */ + int store_min_key(KEY_PART *key, + uchar **range_key, + uint *range_key_flag, + uint last_part) + { + SEL_ARG *key_tree= first(); + uint res= key_tree->store_min(key[key_tree->part].store_length, + range_key, *range_key_flag); + *range_key_flag|= key_tree->min_flag; + if (key_tree->next_key_part && + key_tree->next_key_part->type == SEL_ARG::KEY_RANGE && + key_tree->part != last_part && + key_tree->next_key_part->part == key_tree->part+1 && + !(*range_key_flag & (NO_MIN_RANGE | NEAR_MIN))) + res+= key_tree->next_key_part->store_min_key(key, + range_key, + range_key_flag, + last_part); + return res; + } + /* returns a number of keypart values appended to the key buffer */ + int store_max_key(KEY_PART *key, + uchar **range_key, + uint *range_key_flag, + uint last_part) + { + SEL_ARG *key_tree= last(); + uint res=key_tree->store_max(key[key_tree->part].store_length, + range_key, *range_key_flag); + (*range_key_flag)|= key_tree->max_flag; + if (key_tree->next_key_part && + key_tree->next_key_part->type == SEL_ARG::KEY_RANGE && + key_tree->part != last_part && + key_tree->next_key_part->part == key_tree->part+1 && + !(*range_key_flag & (NO_MAX_RANGE | NEAR_MAX))) + res+= key_tree->next_key_part->store_max_key(key, + range_key, + range_key_flag, + last_part); + return res; + } + SEL_ARG *insert(SEL_ARG *key); + SEL_ARG *tree_delete(SEL_ARG *key); + SEL_ARG *find_range(SEL_ARG *key); + SEL_ARG *rb_insert(SEL_ARG *leaf); + friend SEL_ARG *rb_delete_fixup(SEL_ARG *root,SEL_ARG *key, SEL_ARG *par); +#ifdef EXTRA_DEBUG + friend int test_rb_tree(SEL_ARG *element,SEL_ARG *parent); + void test_use_count(SEL_ARG *root); +#endif + SEL_ARG *first(); + const SEL_ARG *first() const; + SEL_ARG *last(); + void make_root(); + inline bool simple_key() + { + return !next_key_part && elements == 1; + } + void increment_use_count(long count) + { + if (next_key_part) + { + next_key_part->use_count+=count; + count*= (next_key_part->use_count-count); + for (SEL_ARG *pos=next_key_part->first(); pos ; pos=pos->next) + if (pos->next_key_part) + pos->increment_use_count(count); + } + } + void incr_refs() + { + increment_use_count(1); + use_count++; + } + void incr_refs_all() + { + for (SEL_ARG *pos=first(); pos ; pos=pos->next) + { + pos->increment_use_count(1); + } + use_count++; + } + void free_tree() + { + for (SEL_ARG *pos=first(); pos ; pos=pos->next) + if (pos->next_key_part) + { + pos->next_key_part->use_count--; + pos->next_key_part->free_tree(); + } + } + + inline SEL_ARG **parent_ptr() + { + return parent->left == this ? &parent->left : &parent->right; + } + + + /* + Check if this SEL_ARG object represents a single-point interval + + SYNOPSIS + is_singlepoint() + + DESCRIPTION + Check if this SEL_ARG object (not tree) represents a single-point + interval, i.e. if it represents a "keypart = const" or + "keypart IS NULL". + + RETURN + TRUE This SEL_ARG object represents a singlepoint interval + FALSE Otherwise + */ + + bool is_singlepoint() + { + /* + Check for NEAR_MIN ("strictly less") and NO_MIN_RANGE (-inf < field) + flags, and the same for right edge. + */ + if (min_flag || max_flag) + return FALSE; + uchar *min_val= min_value; + uchar *max_val= max_value; + + if (maybe_null) + { + /* First byte is a NULL value indicator */ + if (*min_val != *max_val) + return FALSE; + + if (*min_val) + return TRUE; /* This "x IS NULL" */ + min_val++; + max_val++; + } + return !field->key_cmp(min_val, max_val); + } + SEL_ARG *clone_tree(RANGE_OPT_PARAM *param); +}; + + +class RANGE_OPT_PARAM +{ +public: + THD *thd; /* Current thread handle */ + TABLE *table; /* Table being analyzed */ + table_map prev_tables; + table_map read_tables; + table_map current_table; /* Bit of the table being analyzed */ + + /* Array of parts of all keys for which range analysis is performed */ + KEY_PART *key_parts; + KEY_PART *key_parts_end; + MEM_ROOT *mem_root; /* Memory that will be freed when range analysis completes */ + MEM_ROOT *old_root; /* Memory that will last until the query end */ + /* + Number of indexes used in range analysis (In SEL_TREE::keys only first + #keys elements are not empty) + */ + uint keys; + + /* + If true, the index descriptions describe real indexes (and it is ok to + call field->optimize_range(real_keynr[...], ...). + Otherwise index description describes fake indexes. + */ + bool using_real_indexes; + + /* + Aggressively remove "scans" that do not have conditions on first + keyparts. Such scans are usable when doing partition pruning but not + regular range optimization. + */ + bool remove_jump_scans; + + /* + TRUE <=> Range analyzer should remove parts of condition that are found + to be always FALSE. + */ + bool remove_false_where_parts; + + /* + used_key_no -> table_key_no translation table. Only makes sense if + using_real_indexes==TRUE + */ + uint real_keynr[MAX_KEY]; + + /* + Used to store 'current key tuples', in both range analysis and + partitioning (list) analysis + */ + uchar *min_key; + uchar *max_key; + + /* Number of SEL_ARG objects allocated by SEL_ARG::clone_tree operations */ + uint alloced_sel_args; + + bool force_default_mrr; + KEY_PART *key[MAX_KEY]; /* First key parts of keys used in the query */ + + bool statement_should_be_aborted() const + { + return + thd->is_fatal_error || + thd->is_error() || + alloced_sel_args > SEL_ARG::MAX_SEL_ARGS; + } +}; + + +class Explain_quick_select; /* A "MIN_TUPLE < tbl.key_tuple < MAX_TUPLE" interval. @@ -69,13 +679,13 @@ class QUICK_RANGE :public Sql_alloc { uint16 dummy; /* Avoid warnings on 'flag' */ #endif QUICK_RANGE(); /* Full range */ - QUICK_RANGE(const uchar *min_key_arg, uint min_length_arg, + QUICK_RANGE(THD *thd, const uchar *min_key_arg, uint min_length_arg, key_part_map min_keypart_map_arg, const uchar *max_key_arg, uint max_length_arg, key_part_map max_keypart_map_arg, uint flag_arg) - : min_key((uchar*) sql_memdup(min_key_arg,min_length_arg+1)), - max_key((uchar*) sql_memdup(max_key_arg,max_length_arg+1)), + : min_key((uchar*) thd->memdup(min_key_arg, min_length_arg + 1)), + max_key((uchar*) thd->memdup(max_key_arg, max_length_arg + 1)), min_length((uint16) min_length_arg), max_length((uint16) max_length_arg), flag((uint16) flag_arg), @@ -104,7 +714,7 @@ class QUICK_RANGE :public Sql_alloc { void make_min_endpoint(key_range *kr, uint prefix_length, key_part_map keypart_map) { make_min_endpoint(kr); - kr->length= min(kr->length, prefix_length); + kr->length= MY_MIN(kr->length, prefix_length); kr->keypart_map&= keypart_map; } @@ -142,7 +752,7 @@ class QUICK_RANGE :public Sql_alloc { void make_max_endpoint(key_range *kr, uint prefix_length, key_part_map keypart_map) { make_max_endpoint(kr); - kr->length= min(kr->length, prefix_length); + kr->length= MY_MIN(kr->length, prefix_length); kr->keypart_map&= keypart_map; } @@ -345,13 +955,8 @@ public: void add_key_name(String *str, bool *first); - /* - Append text representation of quick select structure (what and how is - merged) to str. The result is added to "Extra" field in EXPLAIN output. - This function is implemented only by quick selects that merge other quick - selects output and/or can produce output suitable for merging. - */ - virtual void add_info_string(String *str) {} + /* Save information about quick select's query plan */ + virtual Explain_quick_select* get_explain(MEM_ROOT *alloc)= 0; /* Return 1 if any index used by this quick select @@ -394,10 +999,10 @@ public: Returns a QUICK_SELECT with reverse order of to the index. */ virtual QUICK_SELECT_I *make_reverse(uint used_key_parts_arg) { return NULL; } - + /* Add the key columns used by the quick select into table's read set. - + This is used by an optimization in filesort. */ virtual void add_used_key_part_to_set(MY_BITMAP *col_set)=0; @@ -406,7 +1011,6 @@ public: struct st_qsel_param; class PARAM; -class SEL_ARG; /* @@ -485,7 +1089,7 @@ public: { file->position(record); } int get_type() { return QS_TYPE_RANGE; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); + Explain_quick_select *get_explain(MEM_ROOT *alloc); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif @@ -625,6 +1229,7 @@ public: #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif + Explain_quick_select *get_explain(MEM_ROOT *alloc); bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range); @@ -669,13 +1274,12 @@ protected: int read_keys_and_merge(); public: - QUICK_INDEX_MERGE_SELECT(THD *thd, TABLE *table) - :QUICK_INDEX_SORT_SELECT(thd, table) {} + QUICK_INDEX_MERGE_SELECT(THD *thd_arg, TABLE *table) + :QUICK_INDEX_SORT_SELECT(thd_arg, table) {} int get_next(); int get_type() { return QS_TYPE_INDEX_MERGE; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); }; class QUICK_INDEX_INTERSECT_SELECT : public QUICK_INDEX_SORT_SELECT @@ -684,14 +1288,14 @@ protected: int read_keys_and_merge(); public: - QUICK_INDEX_INTERSECT_SELECT(THD *thd, TABLE *table) - :QUICK_INDEX_SORT_SELECT(thd, table) {} + QUICK_INDEX_INTERSECT_SELECT(THD *thd_arg, TABLE *table) + :QUICK_INDEX_SORT_SELECT(thd_arg, table) {} key_map filtered_scans; int get_next(); int get_type() { return QS_TYPE_INDEX_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); + Explain_quick_select *get_explain(MEM_ROOT *alloc); }; @@ -729,7 +1333,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); + Explain_quick_select *get_explain(MEM_ROOT *alloc); bool is_keys_used(const MY_BITMAP *fields); void add_used_key_part_to_set(MY_BITMAP *col_set); #ifndef DBUG_OFF @@ -809,7 +1413,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_UNION; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); + Explain_quick_select *get_explain(MEM_ROOT *alloc); bool is_keys_used(const MY_BITMAP *fields); void add_used_key_part_to_set(MY_BITMAP *col_set); #ifndef DBUG_OFF @@ -932,6 +1536,7 @@ private: int next_max(); void update_min_result(); void update_max_result(); + int cmp_min_max_key(const uchar *key, uint16 length); public: QUICK_GROUP_MIN_MAX_SELECT(TABLE *table, JOIN *join, bool have_min, bool have_max, bool have_agg_distinct, @@ -959,11 +1564,8 @@ public: void dbug_dump(int indent, bool verbose); #endif bool is_agg_distinct() { return have_agg_distinct; } - virtual void append_loose_scan_type(String *str) - { - if (is_index_scan) - str->append(STRING_WITH_LEN(" (scanning)")); - } + bool loose_scan_is_scanning() { return is_index_scan; } + Explain_quick_select *get_explain(MEM_ROOT *alloc); }; @@ -1005,6 +1607,8 @@ class SQL_SELECT :public Sql_alloc { key_map quick_keys; // Possible quick keys key_map needed_reg; // Possible quick keys after prev tables. table_map const_tables,read_tables; + /* See PARAM::possible_keys */ + key_map possible_keys; bool free_cond; /* Currently not used and always FALSE */ SQL_SELECT(); @@ -1015,7 +1619,7 @@ class SQL_SELECT :public Sql_alloc { { key_map tmp; tmp.set_all(); - return test_quick_select(thd, tmp, 0, limit, force_quick_range, FALSE) < 0; + return test_quick_select(thd, tmp, 0, limit, force_quick_range, FALSE, FALSE) < 0; } /* RETURN @@ -1025,14 +1629,14 @@ class SQL_SELECT :public Sql_alloc { */ inline int skip_record(THD *thd) { - int rc= test(!cond || cond->val_int()); + int rc= MY_TEST(!cond || cond->val_int()); if (thd->is_error()) rc= -1; return rc; } int test_quick_select(THD *thd, key_map keys, table_map prev_tables, ha_rows limit, bool force_quick_range, - bool ordered_output); + bool ordered_output, bool remove_false_parts_of_where); }; @@ -1057,11 +1661,20 @@ SQL_SELECT *make_select(TABLE *head, table_map const_tables, table_map read_tables, COND *conds, bool allow_null_cond, int *error); +bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item **cond); + #ifdef WITH_PARTITION_STORAGE_ENGINE bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond); -void store_key_image_to_rec(Field *field, uchar *ptr, uint len); #endif +void store_key_image_to_rec(Field *field, uchar *ptr, uint len); extern String null_string; +/* check this number of rows (default value) */ +#define SELECTIVITY_SAMPLING_LIMIT 100 +/* but no more then this part of table (10%) */ +#define SELECTIVITY_SAMPLING_SHARE 0.10 +/* do not check if we are going check less then this number of records */ +#define SELECTIVITY_SAMPLING_THRESHOLD 10 + #endif diff --git a/sql/opt_range_mrr.cc b/sql/opt_range_mrr.cc index 1f4e36178db..b3350191d13 100644 --- a/sql/opt_range_mrr.cc +++ b/sql/opt_range_mrr.cc @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /**************************************************************************** MRR Range Sequence Interface implementation that walks a SEL_ARG* tree. @@ -266,10 +266,28 @@ walk_up_n_right: range->end_key.flag= (cur->max_key_flag & NEAR_MAX ? HA_READ_BEFORE_KEY : HA_READ_AFTER_KEY); range->end_key.keypart_map= make_prev_keypart_map(cur->max_key_parts); - + + KEY *key_info; + if (seq->real_keyno== MAX_KEY) + key_info= NULL; + else + key_info= &seq->param->table->key_info[seq->real_keyno]; + + /* + Conditions below: + (1) - range analysis is used for estimating condition selectivity + (2) - This is a unique key, and we have conditions for all its + user-defined key parts. + (3) - The table uses extended keys, this key covers all components, + and we have conditions for all key parts. + */ if (!(cur->min_key_flag & ~NULL_RANGE) && !cur->max_key_flag && - (uint)key_tree->part+1 == seq->param->table->key_info[seq->real_keyno].key_parts && - (seq->param->table->key_info[seq->real_keyno].flags & HA_NOSAME) && + (!key_info || // (1) + ((uint)key_tree->part+1 == key_info->user_defined_key_parts && // (2) + key_info->flags & HA_NOSAME) || // (2) + ((key_info->flags & HA_EXT_NOSAME) && // (3) + (uint)key_tree->part+1 == key_info->ext_key_parts) // (3) + ) && range->start_key.length == range->end_key.length && !memcmp(seq->param->min_key,seq->param->max_key,range->start_key.length)) range->range_flag= UNIQUE_RANGE | (cur->min_key_flag & NULL_RANGE); @@ -293,7 +311,7 @@ walk_up_n_right: } } seq->param->range_count++; - seq->param->max_key_part=max(seq->param->max_key_part,key_tree->part); + seq->param->max_key_part=MY_MAX(seq->param->max_key_part,key_tree->part); return 0; } diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index f82d55f8309..852f91efc00 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -1,5 +1,5 @@ /* - Copyright (c) 2010, 2012, Monty Program Ab + Copyright (c) 2010, 2015, 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 @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /** @file @@ -26,6 +26,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_base.h" #include "sql_select.h" #include "filesort.h" @@ -461,7 +462,7 @@ void best_access_path(JOIN *join, JOIN_TAB *s, static Item *create_subq_in_equalities(THD *thd, SJ_MATERIALIZATION_INFO *sjm, Item_in_subselect *subq_pred); -static void remove_sj_conds(Item **tree); +static void remove_sj_conds(THD *thd, Item **tree); static bool is_cond_sj_in_equality(Item *item); static bool sj_table_is_included(JOIN *join, JOIN_TAB *join_tab); static Item *remove_additional_cond(Item* conds); @@ -513,8 +514,6 @@ bool is_materialization_applicable(THD *thd, Item_in_subselect *in_subs, Subquery !contains {GROUP BY, ORDER BY [LIMIT], aggregate functions}) && subquery predicate is not under "NOT IN")) - (*) The subquery must be part of a SELECT or CREATE TABLE ... SELECT statement. - The current condition also excludes multi-table update statements. A note about prepared statements: we want the if-branch to be taken on PREPARE and each EXECUTE. The rewrites are only done once, but we need select_lex->sj_subselects list to be populated for every EXECUTE. @@ -523,9 +522,7 @@ bool is_materialization_applicable(THD *thd, Item_in_subselect *in_subs, if (optimizer_flag(thd, OPTIMIZER_SWITCH_MATERIALIZATION) && // 0 !child_select->is_part_of_union() && // 1 parent_unit->first_select()->leaf_tables.elements && // 2 - (thd->lex->sql_command == SQLCOM_SELECT || // * - thd->lex->sql_command == SQLCOM_CREATE_TABLE) && // * - child_select->outer_select()->leaf_tables.elements && // 2A + child_select->outer_select()->leaf_tables.elements && // 2A subquery_types_allow_materialization(in_subs) && (in_subs->is_top_level_item() || //3 optimizer_flag(thd, @@ -665,6 +662,9 @@ int check_and_do_in_subquery_rewrites(JOIN *join) 8. No execution method was already chosen (by a prepared statement) 9. Parent select is not a table-less select 10. Neither parent nor child select have STRAIGHT_JOIN option. + 11. It is first optimisation (the subquery could be moved from ON + clause during first optimisation and then be considered for SJ + on the second when it is too late) */ if (optimizer_flag(thd, OPTIMIZER_SWITCH_SEMIJOIN) && in_subs && // 1 @@ -678,8 +678,8 @@ int check_and_do_in_subquery_rewrites(JOIN *join) select_lex->outer_select()->table_list.first && // 9 !((join->select_options | // 10 select_lex->outer_select()->join->select_options) // 10 - & SELECT_STRAIGHT_JOIN)) // 10 - + & SELECT_STRAIGHT_JOIN) && // 10 + select_lex->first_cond_optimization) // 11 { DBUG_PRINT("info", ("Subquery is semi-join conversion candidate")); @@ -692,7 +692,8 @@ int check_and_do_in_subquery_rewrites(JOIN *join) { Query_arena *arena, backup; arena= thd->activate_stmt_arena_if_needed(&backup); - select_lex->outer_select()->sj_subselects.push_back(in_subs); + select_lex->outer_select()->sj_subselects.push_back(in_subs, + thd->mem_root); if (arena) thd->restore_active_arena(arena, &backup); in_subs->is_registered_semijoin= TRUE; @@ -735,7 +736,8 @@ int check_and_do_in_subquery_rewrites(JOIN *join) { Query_arena *arena, backup; arena= thd->activate_stmt_arena_if_needed(&backup); - select_lex->outer_select()->sj_subselects.push_back(in_subs); + select_lex->outer_select()->sj_subselects.push_back(in_subs, + thd->mem_root); if (arena) thd->restore_active_arena(arena, &backup); in_subs->is_registered_semijoin= TRUE; @@ -1203,7 +1205,7 @@ bool convert_join_subqueries_to_semijoins(JOIN *join) in_subq->sj_convert_priority= - test(in_subq->do_not_convert_to_sj) * MAX_TABLES * 2 + + MY_TEST(in_subq->do_not_convert_to_sj) * MAX_TABLES * 2 + in_subq->is_correlated * MAX_TABLES + child_join->outer_tables; } @@ -1259,7 +1261,8 @@ bool convert_join_subqueries_to_semijoins(JOIN *join) Item **tree= (in_subq->emb_on_expr_nest == NO_JOIN_NEST)? &join->conds : &(in_subq->emb_on_expr_nest->on_expr); Item *replace_me= in_subq->original_item(); - if (replace_where_subcondition(join, tree, replace_me, new Item_int(1), + if (replace_where_subcondition(join, tree, replace_me, + new (thd->mem_root) Item_int(thd, 1), FALSE)) goto restore_arena_and_fail; } @@ -1609,7 +1612,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) A3: changes in the TABLE_LIST::outer_join will make everything work automatically. */ - if (!(wrap_nest= alloc_join_nest(parent_join->thd))) + if (!(wrap_nest= alloc_join_nest(thd))) { DBUG_RETURN(TRUE); } @@ -1618,7 +1621,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) wrap_nest->alias= (char*) "(sj-wrap)"; wrap_nest->nested_join->join_list.empty(); - wrap_nest->nested_join->join_list.push_back(outer_tbl); + wrap_nest->nested_join->join_list.push_back(outer_tbl, thd->mem_root); outer_tbl->embedding= wrap_nest; outer_tbl->join_list= &wrap_nest->nested_join->join_list; @@ -1654,7 +1657,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) TABLE_LIST *sj_nest; NESTED_JOIN *nested_join; - if (!(sj_nest= alloc_join_nest(parent_join->thd))) + if (!(sj_nest= alloc_join_nest(thd))) { DBUG_RETURN(TRUE); } @@ -1668,7 +1671,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) subq_pred->left_expr->used_tables(); /* Nests do not participate in those 'chains', so: */ /* sj_nest->next_leaf= sj_nest->next_local= sj_nest->next_global == NULL*/ - emb_join_list->push_back(sj_nest); + emb_join_list->push_back(sj_nest, thd->mem_root); /* nested_join->used_tables and nested_join->not_null_tables are @@ -1688,7 +1691,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) { tl->embedding= sj_nest; tl->join_list= &nested_join->join_list; - nested_join->join_list.push_back(tl); + nested_join->join_list.push_back(tl, thd->mem_root); } /* @@ -1698,7 +1701,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) NOTE: We actually insert them at the front! That's because the order is reversed in this list. */ - parent_lex->leaf_tables.concat(&subq_lex->leaf_tables); + parent_lex->leaf_tables.append(&subq_lex->leaf_tables); if (subq_lex->options & OPTION_SCHEMA_TABLE) parent_lex->options |= OPTION_SCHEMA_TABLE; @@ -1730,7 +1733,12 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) { tl->set_tablenr(table_no); if (tl->is_jtbm()) + { tl->jtbm_table_no= table_no; + Item *dummy= tl->jtbm_subselect; + tl->jtbm_subselect->fix_after_pullout(parent_lex, &dummy, true); + DBUG_ASSERT(dummy == tl->jtbm_subselect); + } SELECT_LEX *old_sl= tl->select_lex; tl->select_lex= parent_join->select_lex; for (TABLE_LIST *emb= tl->embedding; @@ -1753,8 +1761,9 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) DBUG_RETURN(TRUE); thd->lex->current_select=save_lex; - sj_nest->nested_join->sj_corr_tables= subq_pred->used_tables(); - sj_nest->nested_join->sj_depends_on= subq_pred->used_tables() | + table_map subq_pred_used_tables= subq_pred->used_tables(); + sj_nest->nested_join->sj_corr_tables= subq_pred_used_tables; + sj_nest->nested_join->sj_depends_on= subq_pred_used_tables | subq_pred->left_expr->used_tables(); sj_nest->sj_on_expr= subq_lex->join->conds; @@ -1780,7 +1789,8 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) if (subq_pred->left_expr->cols() == 1) { /* add left = select_list_element */ - nested_join->sj_outer_expr_list.push_back(&subq_pred->left_expr); + nested_join->sj_outer_expr_list.push_back(&subq_pred->left_expr, + thd->mem_root); /* Create Item_func_eq. Note that 1. this is done on the statement, not execution, arena @@ -1791,13 +1801,14 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) with thd->change_item_tree */ Item_func_eq *item_eq= - new Item_func_eq(subq_pred->left_expr_orig, subq_lex->ref_pointer_array[0]); + new (thd->mem_root) Item_func_eq(thd, subq_pred->left_expr_orig, + subq_lex->ref_pointer_array[0]); if (!item_eq) DBUG_RETURN(TRUE); if (subq_pred->left_expr_orig != subq_pred->left_expr) thd->change_item_tree(item_eq->arguments(), subq_pred->left_expr); item_eq->in_equality_no= 0; - sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq); + sj_nest->sj_on_expr= and_items(thd, sj_nest->sj_on_expr, item_eq); } else if (subq_pred->left_expr->type() == Item::ROW_ITEM) { @@ -1807,11 +1818,12 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) */ for (uint i= 0; i < subq_pred->left_expr->cols(); i++) { - nested_join->sj_outer_expr_list.push_back(subq_pred->left_expr-> - addr(i)); + nested_join->sj_outer_expr_list.push_back(subq_pred->left_expr->addr(i), + thd->mem_root); Item_func_eq *item_eq= - new Item_func_eq(subq_pred->left_expr_orig->element_index(i), - subq_lex->ref_pointer_array[i]); + new (thd->mem_root) + Item_func_eq(thd, subq_pred->left_expr_orig->element_index(i), + subq_lex->ref_pointer_array[i]); if (!item_eq) DBUG_RETURN(TRUE); DBUG_ASSERT(subq_pred->left_expr->element_index(i)->fixed); @@ -1820,7 +1832,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) thd->change_item_tree(item_eq->arguments(), subq_pred->left_expr->element_index(i)); item_eq->in_equality_no= i; - sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq); + sj_nest->sj_on_expr= and_items(thd, sj_nest->sj_on_expr, item_eq); } } else @@ -1829,14 +1841,14 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) add row operation left = (select_list_element1, select_list_element2, ...) */ - Item_row *row= new Item_row(subq_lex->pre_fix); + Item_row *row= new (thd->mem_root) Item_row(thd, subq_lex->pre_fix); /* fix fields on subquery was call so they should be the same */ DBUG_ASSERT(subq_pred->left_expr->cols() == row->cols()); if (!row) DBUG_RETURN(TRUE); nested_join->sj_outer_expr_list.push_back(&subq_pred->left_expr); Item_func_eq *item_eq= - new Item_func_eq(subq_pred->left_expr_orig, row); + new (thd->mem_root) Item_func_eq(thd, subq_pred->left_expr_orig, row); if (!item_eq) DBUG_RETURN(TRUE); for (uint i= 0; i < row->cols(); i++) @@ -1845,7 +1857,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) thd->change_item_tree(row->addr(i), subq_lex->ref_pointer_array[i]); } item_eq->in_equality_no= 0; - sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq); + sj_nest->sj_on_expr= and_items(thd, sj_nest->sj_on_expr, item_eq); } /* Fix the created equality and AND @@ -1857,7 +1869,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) we have in here). */ if (!sj_nest->sj_on_expr->fixed && - sj_nest->sj_on_expr->fix_fields(parent_join->thd, &sj_nest->sj_on_expr)) + sj_nest->sj_on_expr->fix_fields(thd, &sj_nest->sj_on_expr)) { DBUG_RETURN(TRUE); } @@ -1866,7 +1878,8 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) Walk through sj nest's WHERE and ON expressions and call item->fix_table_changes() for all items. */ - sj_nest->sj_on_expr->fix_after_pullout(parent_lex, &sj_nest->sj_on_expr); + sj_nest->sj_on_expr->fix_after_pullout(parent_lex, &sj_nest->sj_on_expr, + TRUE); fix_list_after_tbl_changes(parent_lex, &sj_nest->nested_join->join_list); @@ -1879,11 +1892,11 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) /* Inject sj_on_expr into the parent's WHERE or ON */ if (emb_tbl_nest) { - emb_tbl_nest->on_expr= and_items(emb_tbl_nest->on_expr, + emb_tbl_nest->on_expr= and_items(thd, emb_tbl_nest->on_expr, sj_nest->sj_on_expr); emb_tbl_nest->on_expr->top_level_item(); if (!emb_tbl_nest->on_expr->fixed && - emb_tbl_nest->on_expr->fix_fields(parent_join->thd, + emb_tbl_nest->on_expr->fix_fields(thd, &emb_tbl_nest->on_expr)) { DBUG_RETURN(TRUE); @@ -1892,7 +1905,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) else { /* Inject into the WHERE */ - parent_join->conds= and_items(parent_join->conds, sj_nest->sj_on_expr); + parent_join->conds= and_items(thd, parent_join->conds, sj_nest->sj_on_expr); parent_join->conds->top_level_item(); /* fix_fields must update the properties (e.g. st_select_lex::cond_count of @@ -1901,7 +1914,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) save_lex= thd->lex->current_select; thd->lex->current_select=parent_join->select_lex; if (!parent_join->conds->fixed && - parent_join->conds->fix_fields(parent_join->thd, + parent_join->conds->fix_fields(thd, &parent_join->conds)) { DBUG_RETURN(1); @@ -1915,9 +1928,10 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) Item_func_match *ifm; List_iterator_fast<Item_func_match> li(*(subq_lex->ftfunc_list)); while ((ifm= li++)) - parent_lex->ftfunc_list->push_front(ifm); + parent_lex->ftfunc_list->push_front(ifm, thd->mem_root); } + parent_lex->have_merged_subqueries= TRUE; DBUG_RETURN(FALSE); } @@ -1956,16 +1970,16 @@ static bool convert_subq_to_jtbm(JOIN *parent_join, List<TABLE_LIST> *emb_join_list= &parent_lex->top_join_list; TABLE_LIST *emb_tbl_nest= NULL; // will change when we learn to handle outer joins TABLE_LIST *tl; - DBUG_ENTER("convert_subq_to_jtbm"); bool optimization_delayed= TRUE; - subq_pred->set_strategy(SUBS_MATERIALIZATION); + TABLE_LIST *jtbm; + char *tbl_alias; + DBUG_ENTER("convert_subq_to_jtbm"); + subq_pred->set_strategy(SUBS_MATERIALIZATION); subq_pred->is_jtbm_merged= TRUE; *remove_item= TRUE; - TABLE_LIST *jtbm; - char *tbl_alias; if (!(tbl_alias= (char*)parent_join->thd->calloc(SUBQERY_TEMPTABLE_NAME_MAX_LEN)) || !(jtbm= alloc_join_nest(parent_join->thd))) //todo: this is not a join nest! { @@ -1979,13 +1993,13 @@ static bool convert_subq_to_jtbm(JOIN *parent_join, /* Nests do not participate in those 'chains', so: */ /* jtbm->next_leaf= jtbm->next_local= jtbm->next_global == NULL*/ - emb_join_list->push_back(jtbm); + emb_join_list->push_back(jtbm, parent_join->thd->mem_root); /* Inject the jtbm table into TABLE_LIST::next_leaf list, so that make_join_statistics() and co. can find it. */ - parent_lex->leaf_tables.push_back(jtbm); + parent_lex->leaf_tables.push_back(jtbm, parent_join->thd->mem_root); if (subq_pred->unit->first_select()->options & OPTION_SCHEMA_TABLE) parent_lex->options |= OPTION_SCHEMA_TABLE; @@ -2024,13 +2038,15 @@ static bool convert_subq_to_jtbm(JOIN *parent_join, DBUG_ASSERT(parent_join->table_count < MAX_TABLES); Item *conds= hash_sj_engine->semi_join_conds; - conds->fix_after_pullout(parent_lex, &conds); + conds->fix_after_pullout(parent_lex, &conds, TRUE); DBUG_EXECUTE("where", print_where(conds,"SJ-EXPR", QT_ORDINARY);); create_subquery_temptable_name(tbl_alias, hash_sj_engine->materialize_join-> select_lex->select_number); jtbm->alias= tbl_alias; + + parent_lex->have_merged_subqueries= TRUE; #if 0 /* Inject sj_on_expr into the parent's WHERE or ON */ if (emb_tbl_nest) @@ -2074,7 +2090,7 @@ void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist) while ((table= it++)) { if (table->on_expr) - table->on_expr->fix_after_pullout(new_parent, &table->on_expr); + table->on_expr->fix_after_pullout(new_parent, &table->on_expr, TRUE); if (table->nested_join) fix_list_after_tbl_changes(new_parent, &table->nested_join->join_list); } @@ -2304,7 +2320,7 @@ int pull_out_semijoin_tables(JOIN *join) */ child_li.remove(); sj_nest->nested_join->used_tables &= ~tbl->table->map; - upper_join_list->push_back(tbl); + upper_join_list->push_back(tbl, join->thd->mem_root); tbl->join_list= upper_join_list; tbl->embedding= sj_nest->embedding; } @@ -2452,9 +2468,10 @@ bool optimize_semijoin_nests(JOIN *join, table_map all_table_map) double rows= 1.0; while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END) rows *= join->map2table[tableno]->table->quick_condition_rows; - sjm->rows= min(sjm->rows, rows); + sjm->rows= MY_MIN(sjm->rows, rows); } - memcpy(sjm->positions, join->best_positions + join->const_tables, + memcpy((uchar*) sjm->positions, + (uchar*) (join->best_positions + join->const_tables), sizeof(POSITION) * n_tables); /* @@ -2477,7 +2494,7 @@ bool optimize_semijoin_nests(JOIN *join, table_map all_table_map) Set the cost to do a full scan of the temptable (will need this to consider doing sjm-scan): */ - sjm->scan_cost.zero(); + sjm->scan_cost.reset(); sjm->scan_cost.add_io(sjm->rows, lookup_cost); sjm->lookup_cost.convert_from_cost(lookup_cost); @@ -2632,7 +2649,7 @@ bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables) if (!is_excluded_key) { keyinfo= table->key_info + key; - is_excluded_key= !test(keyinfo->flags & HA_NOSAME); + is_excluded_key= !MY_TEST(keyinfo->flags & HA_NOSAME); } if (!is_excluded_key) { @@ -2647,7 +2664,7 @@ bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables) keyuse++; } while (keyuse->key == key && keyuse->table == table); - if (bound_parts == PREV_BITS(uint, keyinfo->key_parts)) + if (bound_parts == PREV_BITS(uint, keyinfo->user_defined_key_parts)) return TRUE; } else @@ -2719,8 +2736,8 @@ bool is_multiple_semi_joins(JOIN *join, POSITION *prefix, uint idx, table_map in if ((emb_sj_nest= prefix[i].table->emb_sj_nest)) { if (inner_tables & emb_sj_nest->sj_inner_tables) - return !test(inner_tables == (emb_sj_nest->sj_inner_tables & - ~join->const_table_map)); + return !MY_TEST(inner_tables == (emb_sj_nest->sj_inner_tables & + ~join->const_table_map)); } } return FALSE; @@ -2759,8 +2776,8 @@ void advance_sj_state(JOIN *join, table_map remaining_tables, uint idx, LooseScan detector in best_access_path) */ remaining_tables &= ~new_join_tab->table->map; - table_map dups_producing_tables, prev_dups_producing_tables, - prev_sjm_lookup_tables; + table_map dups_producing_tables, prev_dups_producing_tables= 0, + prev_sjm_lookup_tables= 0; if (idx == join->const_tables) dups_producing_tables= 0; @@ -2771,7 +2788,7 @@ void advance_sj_state(JOIN *join, table_map remaining_tables, uint idx, if ((emb_sj_nest= new_join_tab->emb_sj_nest)) dups_producing_tables |= emb_sj_nest->sj_inner_tables; - Semi_join_strategy_picker **strategy, **prev_strategy; + Semi_join_strategy_picker **strategy, **prev_strategy= NULL; if (idx == join->const_tables) { /* First table, initialize pickers */ @@ -2955,12 +2972,12 @@ bool Sj_materialization_picker::check_qep(JOIN *join, else { /* This is SJ-Materialization with lookups */ - COST_VECT prefix_cost; + Cost_estimate prefix_cost; signed int first_tab= (int)idx - mat_info->tables; double prefix_rec_count; if (first_tab < (int)join->const_tables) { - prefix_cost.zero(); + prefix_cost.reset(); prefix_rec_count= 1.0; } else @@ -3491,9 +3508,9 @@ at_sjmat_pos(const JOIN *join, table_map remaining_tables, const JOIN_TAB *tab, if (join->positions[idx - i].table->emb_sj_nest != tab->emb_sj_nest) return NULL; } - *loose_scan= test(remaining_tables & ~tab->table->map & - (emb_sj_nest->sj_inner_tables | - emb_sj_nest->nested_join->sj_depends_on)); + *loose_scan= MY_TEST(remaining_tables & ~tab->table->map & + (emb_sj_nest->sj_inner_tables | + emb_sj_nest->nested_join->sj_depends_on)); if (*loose_scan && !emb_sj_nest->sj_subq_pred->sjm_scan_allowed) return NULL; else @@ -3577,8 +3594,7 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) { POSITION *pos= join->best_positions + tablenr; JOIN_TAB *s= pos->table; - uint first; - LINT_INIT(first); // Set by every branch except SJ_OPT_NONE which doesn't use it + uint UNINIT_VAR(first); // Set by every branch except SJ_OPT_NONE which doesn't use it if ((handled_tabs & s->table->map) || pos->sj_strategy == SJ_OPT_NONE) { @@ -3591,7 +3607,7 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) SJ_MATERIALIZATION_INFO *sjm= s->emb_sj_nest->sj_mat_info; sjm->is_used= TRUE; sjm->is_sj_scan= FALSE; - memcpy(pos - sjm->tables + 1, sjm->positions, + memcpy((uchar*) (pos - sjm->tables + 1), (uchar*) sjm->positions, sizeof(POSITION) * sjm->tables); recalculate_prefix_record_count(join, tablenr - sjm->tables + 1, tablenr); @@ -3608,8 +3624,8 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) sjm->is_used= TRUE; sjm->is_sj_scan= TRUE; first= pos->sjmat_picker.sjm_scan_last_inner - sjm->tables + 1; - memcpy(join->best_positions + first, - sjm->positions, sizeof(POSITION) * sjm->tables); + memcpy((uchar*) (join->best_positions + first), + (uchar*) sjm->positions, sizeof(POSITION) * sjm->tables); recalculate_prefix_record_count(join, first, first + sjm->tables); join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE_SCAN; join->best_positions[first].n_sj_tables= sjm->tables; @@ -3781,16 +3797,19 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) bool setup_sj_materialization_part1(JOIN_TAB *sjm_tab) { - DBUG_ENTER("setup_sj_materialization"); JOIN_TAB *tab= sjm_tab->bush_children->start; TABLE_LIST *emb_sj_nest= tab->table->pos_in_table_list->embedding; + SJ_MATERIALIZATION_INFO *sjm; + THD *thd; + + DBUG_ENTER("setup_sj_materialization"); /* Walk out of outer join nests until we reach the semi-join nest we're in */ while (!emb_sj_nest->sj_mat_info) emb_sj_nest= emb_sj_nest->embedding; - SJ_MATERIALIZATION_INFO *sjm= emb_sj_nest->sj_mat_info; - THD *thd= tab->join->thd; + sjm= emb_sj_nest->sj_mat_info; + thd= tab->join->thd; /* First the calls come to the materialization function */ DBUG_ASSERT(sjm->is_used); @@ -3831,8 +3850,8 @@ bool setup_sj_materialization_part1(JOIN_TAB *sjm_tab) sjm->table->file->extra(HA_EXTRA_WRITE_CACHE); sjm->table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); - tab->join->sj_tmp_tables.push_back(sjm->table); - tab->join->sjm_info_list.push_back(sjm); + tab->join->sj_tmp_tables.push_back(sjm->table, thd->mem_root); + tab->join->sjm_info_list.push_back(sjm, thd->mem_root); sjm->materialized= FALSE; sjm_tab->table= sjm->table; @@ -3861,7 +3880,7 @@ bool setup_sj_materialization_part2(JOIN_TAB *sjm_tab) KEY *tmp_key; /* The only index on the temporary table. */ uint tmp_key_parts; /* Number of keyparts in tmp_key. */ tmp_key= sjm->table->key_info; - tmp_key_parts= tmp_key->key_parts; + tmp_key_parts= tmp_key->user_defined_key_parts; /* Create/initialize everything we will need to index lookups into the @@ -3892,12 +3911,12 @@ bool setup_sj_materialization_part2(JOIN_TAB *sjm_tab) for (i= 0; i < tmp_key_parts; i++, cur_key_part++, ref_key++) { tab_ref->items[i]= emb_sj_nest->sj_subq_pred->left_expr->element_index(i); - int null_count= test(cur_key_part->field->real_maybe_null()); + int null_count= MY_TEST(cur_key_part->field->real_maybe_null()); *ref_key= new store_key_item(thd, cur_key_part->field, /* TODO: the NULL byte is taken into account in cur_key_part->store_length, so instead of - cur_ref_buff + test(maybe_null), we could + cur_ref_buff + MY_TEST(maybe_null), we could use that information instead. */ cur_ref_buff + null_count, @@ -3929,9 +3948,9 @@ bool setup_sj_materialization_part2(JOIN_TAB *sjm_tab) */ for (i= 0; i < sjm->tables; i++) { - remove_sj_conds(&tab[i].select_cond); + remove_sj_conds(thd, &tab[i].select_cond); if (tab[i].select) - remove_sj_conds(&tab[i].select->cond); + remove_sj_conds(thd, &tab[i].select->cond); } if (!(sjm->in_equality= create_subq_in_equalities(thd, sjm, emb_sj_nest->sj_subq_pred))) @@ -4074,8 +4093,8 @@ static Item *create_subq_in_equalities(THD *thd, SJ_MATERIALIZATION_INFO *sjm, Item *res= NULL; if (subq_pred->left_expr->cols() == 1) { - if (!(res= new Item_func_eq(subq_pred->left_expr, - new Item_field(sjm->table->field[0])))) + if (!(res= new (thd->mem_root) Item_func_eq(thd, subq_pred->left_expr, + new (thd->mem_root) Item_field(thd, sjm->table->field[0])))) return NULL; /* purecov: inspected */ } else @@ -4083,9 +4102,9 @@ static Item *create_subq_in_equalities(THD *thd, SJ_MATERIALIZATION_INFO *sjm, Item *conj; for (uint i= 0; i < subq_pred->left_expr->cols(); i++) { - if (!(conj= new Item_func_eq(subq_pred->left_expr->element_index(i), - new Item_field(sjm->table->field[i]))) || - !(res= and_items(res, conj))) + if (!(conj= new (thd->mem_root) Item_func_eq(thd, subq_pred->left_expr->element_index(i), + new (thd->mem_root) Item_field(thd, sjm->table->field[i]))) || + !(res= and_items(thd, res, conj))) return NULL; /* purecov: inspected */ } } @@ -4097,7 +4116,7 @@ static Item *create_subq_in_equalities(THD *thd, SJ_MATERIALIZATION_INFO *sjm, -static void remove_sj_conds(Item **tree) +static void remove_sj_conds(THD *thd, Item **tree) { if (*tree) { @@ -4113,7 +4132,7 @@ static void remove_sj_conds(Item **tree) while ((item= li++)) { if (is_cond_sj_in_equality(item)) - li.replace(new Item_int(1)); + li.replace(new (thd->mem_root) Item_int(thd, 1)); } } } @@ -4126,7 +4145,7 @@ static bool is_cond_sj_in_equality(Item *item) ((Item_func*)item)->functype()== Item_func::EQ_FUNC) { Item_func_eq *item_eq= (Item_func_eq*)item; - return test(item_eq->in_equality_no != UINT_MAX); + return MY_TEST(item_eq->in_equality_no != UINT_MAX); } return FALSE; } @@ -4191,7 +4210,6 @@ SJ_TMP_TABLE::create_sj_weedout_tmp_table(THD *thd) /* STEP 1: Get temporary table name */ - statistic_increment(thd->status_var.created_tmp_tables, &LOCK_status); if (use_temp_pool && !(test_flags & TEST_KEEP_TMP_TABLES)) temp_pool_slot = bitmap_lock_set_next(&temp_pool); @@ -4212,7 +4230,7 @@ SJ_TMP_TABLE::create_sj_weedout_tmp_table(THD *thd) using_unique_constraint= TRUE; /* STEP 3: Allocate memory for temptable description */ - init_sql_alloc(&own_root, TABLE_ALLOC_BLOCK_SIZE, 0); + init_sql_alloc(&own_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC)); if (!multi_alloc_root(&own_root, &table, sizeof(*table), &share, sizeof(*share), @@ -4225,7 +4243,7 @@ SJ_TMP_TABLE::create_sj_weedout_tmp_table(THD *thd) &tmpname, (uint) strlen(path)+1, &group_buff, (!using_unique_constraint ? uniq_tuple_length_arg : 0), - &bitmaps, bitmap_buffer_size(1)*3, + &bitmaps, bitmap_buffer_size(1)*5, NullS)) { if (temp_pool_slot != MY_BIT_NONE) @@ -4259,7 +4277,6 @@ SJ_TMP_TABLE::create_sj_weedout_tmp_table(THD *thd) table->s= share; init_tmp_table_share(thd, share, "", 0, tmpname, tmpname); share->blob_field= blob_field; - share->blob_ptr_size= portable_sizeof_char_ptr; share->table_charset= NULL; share->primary_key= MAX_KEY; // Indicate no primary key share->keys_for_keyread.init(); @@ -4301,17 +4318,23 @@ SJ_TMP_TABLE::create_sj_weedout_tmp_table(THD *thd) share->db_plugin= ha_lock_engine(0, TMP_ENGINE_HTON); table->file= get_new_handler(share, &table->mem_root, share->db_type()); - DBUG_ASSERT(uniq_tuple_length_arg <= table->file->max_key_length()); } else { share->db_plugin= ha_lock_engine(0, heap_hton); table->file= get_new_handler(share, &table->mem_root, share->db_type()); + DBUG_ASSERT(uniq_tuple_length_arg <= table->file->max_key_length()); } if (!table->file) goto err; + if (table->file->set_ha_share_ref(&share->ha_share)) + { + delete table->file; + goto err; + } + null_count=1; null_pack_length= 1; @@ -4381,7 +4404,7 @@ SJ_TMP_TABLE::create_sj_weedout_tmp_table(THD *thd) share->max_rows= ~(ha_rows) 0; else share->max_rows= (ha_rows) (((share->db_type() == heap_hton) ? - min(thd->variables.tmp_table_size, + MY_MIN(thd->variables.tmp_table_size, thd->variables.max_heap_table_size) : thd->variables.tmp_table_size) / share->reclength); @@ -4393,11 +4416,11 @@ SJ_TMP_TABLE::create_sj_weedout_tmp_table(THD *thd) { DBUG_PRINT("info",("Creating group key in temporary table")); share->keys=1; - share->uniques= test(using_unique_constraint); + share->uniques= MY_TEST(using_unique_constraint); table->key_info=keyinfo; keyinfo->key_part=key_part_info; keyinfo->flags=HA_NOSAME; - keyinfo->usable_key_parts= keyinfo->key_parts= 1; + keyinfo->usable_key_parts= keyinfo->user_defined_key_parts= 1; keyinfo->key_length=0; keyinfo->rec_per_key=0; keyinfo->algorithm= HA_KEY_ALG_UNDEF; @@ -4413,6 +4436,7 @@ SJ_TMP_TABLE::create_sj_weedout_tmp_table(THD *thd) { if (!(key_field= field->new_key_field(thd->mem_root, table, group_buff, + key_part_info->length, field->null_ptr, field->null_bit))) goto err; @@ -4610,7 +4634,7 @@ int init_dups_weedout(JOIN *join, uint first_table, int first_fanout_table, uint sjtbl->null_bytes= (jt_null_bits + 7)/8; if (sjtbl->create_sj_weedout_tmp_table(thd)) DBUG_RETURN(TRUE); - join->sj_tmp_tables.push_back(sjtbl->tmp_table); + join->sj_tmp_tables.push_back(sjtbl->tmp_table, thd->mem_root); } else { @@ -4635,6 +4659,74 @@ int init_dups_weedout(JOIN *join, uint first_table, int first_fanout_table, uint /* + @brief + Set up semi-join Loose Scan strategy for execution + + @detail + Other strategies are done in setup_semijoin_dups_elimination(), + however, we need to set up Loose Scan earlier, before make_join_select is + called. This is to prevent make_join_select() from switching full index + scans into quick selects (which will break Loose Scan access). + + @return + 0 OK + 1 Error +*/ + +int setup_semijoin_loosescan(JOIN *join) +{ + uint i; + DBUG_ENTER("setup_semijoin_loosescan"); + + POSITION *pos= join->best_positions + join->const_tables; + for (i= join->const_tables ; i < join->top_join_tab_count; ) + { + JOIN_TAB *tab=join->join_tab + i; + switch (pos->sj_strategy) { + case SJ_OPT_MATERIALIZE: + case SJ_OPT_MATERIALIZE_SCAN: + i+= 1; /* join tabs are embedded in the nest */ + pos += pos->n_sj_tables; + break; + case SJ_OPT_LOOSE_SCAN: + { + /* We jump from the last table to the first one */ + tab->loosescan_match_tab= tab + pos->n_sj_tables - 1; + + /* LooseScan requires records to be produced in order */ + if (tab->select && tab->select->quick) + tab->select->quick->need_sorted_output(); + + for (uint j= i; j < i + pos->n_sj_tables; j++) + join->join_tab[j].inside_loosescan_range= TRUE; + + /* Calculate key length */ + uint keylen= 0; + uint keyno= pos->loosescan_picker.loosescan_key; + for (uint kp=0; kp < pos->loosescan_picker.loosescan_parts; kp++) + keylen += tab->table->key_info[keyno].key_part[kp].store_length; + + tab->loosescan_key= keyno; + tab->loosescan_key_len= keylen; + if (pos->n_sj_tables > 1) + tab[pos->n_sj_tables - 1].do_firstmatch= tab; + i+= pos->n_sj_tables; + pos+= pos->n_sj_tables; + break; + } + default: + { + i++; + pos++; + break; + } + } + } + DBUG_RETURN(FALSE); +} + + +/* Setup the strategies to eliminate semi-join duplicates. SYNOPSIS @@ -4742,8 +4834,6 @@ int setup_semijoin_dups_elimination(JOIN *join, ulonglong options, for (i= join->const_tables ; i < join->top_join_tab_count; ) { JOIN_TAB *tab=join->join_tab + i; - //POSITION *pos= join->best_positions + i; - uint keylen, keyno; switch (pos->sj_strategy) { case SJ_OPT_MATERIALIZE: case SJ_OPT_MATERIALIZE_SCAN: @@ -4753,26 +4843,7 @@ int setup_semijoin_dups_elimination(JOIN *join, ulonglong options, break; case SJ_OPT_LOOSE_SCAN: { - /* We jump from the last table to the first one */ - tab->loosescan_match_tab= tab + pos->n_sj_tables - 1; - - /* LooseScan requires records to be produced in order */ - if (tab->select && tab->select->quick) - tab->select->quick->need_sorted_output(); - - for (uint j= i; j < i + pos->n_sj_tables; j++) - join->join_tab[j].inside_loosescan_range= TRUE; - - /* Calculate key length */ - keylen= 0; - keyno= pos->loosescan_picker.loosescan_key; - for (uint kp=0; kp < pos->loosescan_picker.loosescan_parts; kp++) - keylen += tab->table->key_info[keyno].key_part[kp].store_length; - - tab->loosescan_key= keyno; - tab->loosescan_key_len= keylen; - if (pos->n_sj_tables > 1) - tab[pos->n_sj_tables - 1].do_firstmatch= tab; + /* Setup already handled by setup_semijoin_loosescan */ i+= pos->n_sj_tables; pos+= pos->n_sj_tables; break; @@ -5374,13 +5445,14 @@ TABLE *create_dummy_tmp_table(THD *thd) sjm_table_param.init(); sjm_table_param.field_count= 1; List<Item> sjm_table_cols; - Item *column_item= new Item_int(1); - sjm_table_cols.push_back(column_item); + Item *column_item= new (thd->mem_root) Item_int(thd, 1); + sjm_table_cols.push_back(column_item, thd->mem_root); if (!(table= create_tmp_table(thd, &sjm_table_param, sjm_table_cols, (ORDER*) 0, TRUE /* distinct */, 1, /*save_sum_fields*/ - thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS, + thd->variables.option_bits | + TMP_TABLE_ALL_COLUMNS, HA_POS_ERROR /*rows_limit */, (char*)"dummy", TRUE /* Do not open */))) { @@ -5401,8 +5473,8 @@ TABLE *create_dummy_tmp_table(THD *thd) class select_value_catcher :public select_subselect { public: - select_value_catcher(Item_subselect *item_arg) - :select_subselect(item_arg) + select_value_catcher(THD *thd_arg, Item_subselect *item_arg): + select_subselect(thd_arg, item_arg) {} int send_data(List<Item> &items); int setup(List<Item> *items); @@ -5417,16 +5489,16 @@ int select_value_catcher::setup(List<Item> *items) assigned= FALSE; n_elements= items->elements; - if (!(row= (Item_cache**) sql_alloc(sizeof(Item_cache*)*n_elements))) + if (!(row= (Item_cache**) thd->alloc(sizeof(Item_cache*) * n_elements))) return TRUE; Item *sel_item; List_iterator<Item> li(*items); for (uint i= 0; (sel_item= li++); i++) { - if (!(row[i]= Item_cache::get_cache(sel_item))) + if (!(row[i]= Item_cache::get_cache(thd, sel_item))) return TRUE; - row[i]->setup(sel_item); + row[i]->setup(thd, sel_item); } return FALSE; } @@ -5466,6 +5538,7 @@ bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, TABLE_LIST *table; NESTED_JOIN *nested_join; List_iterator<TABLE_LIST> li(*join_list); + THD *thd= join->thd; DBUG_ENTER("setup_jtbm_semi_joins"); while ((table= li++)) @@ -5497,7 +5570,7 @@ bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, 0 or 1 record. Examples of both cases: select * from ot where col in (select ... from it where 2>3) - select * from ot where col in (select min(it.key) from it) + select * from ot where col in (select MY_MIN(it.key) from it) in this case, the subquery predicate has not been setup for materialization. In particular, there is no materialized temp.table. @@ -5513,10 +5586,11 @@ bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, subselect_single_select_engine *engine= (subselect_single_select_engine*)subq_pred->engine; select_value_catcher *new_sink; - if (!(new_sink= new select_value_catcher(subq_pred))) + if (!(new_sink= + new (thd->mem_root) select_value_catcher(thd, subq_pred))) DBUG_RETURN(TRUE); if (new_sink->setup(&engine->select_lex->join->fields_list) || - engine->select_lex->join->change_result(new_sink) || + engine->select_lex->join->change_result(new_sink, NULL) || engine->exec()) { DBUG_RETURN(TRUE); @@ -5533,13 +5607,14 @@ bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, Item *eq_cond; for (uint i= 0; i < subq_pred->left_expr->cols(); i++) { - eq_cond= new Item_func_eq(subq_pred->left_expr->element_index(i), - new_sink->row[i]); + eq_cond= new (thd->mem_root) + Item_func_eq(thd, subq_pred->left_expr->element_index(i), + new_sink->row[i]); if (!eq_cond) DBUG_RETURN(1); - if (!((*join_where)= and_items(*join_where, eq_cond)) || - (*join_where)->fix_fields(join->thd, join_where)) + if (!((*join_where)= and_items(thd, *join_where, eq_cond)) || + (*join_where)->fix_fields(thd, join_where)) DBUG_RETURN(1); } } @@ -5551,7 +5626,7 @@ bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, /* Set up a dummy TABLE*, optimizer code needs JOIN_TABs to have TABLE */ TABLE *dummy_table; - if (!(dummy_table= create_dummy_tmp_table(join->thd))) + if (!(dummy_table= create_dummy_tmp_table(thd))) DBUG_RETURN(1); table->table= dummy_table; table->table->pos_in_table_list= table; @@ -5577,11 +5652,11 @@ bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, Item *sj_conds= hash_sj_engine->semi_join_conds; - (*join_where)= and_items(*join_where, sj_conds); + (*join_where)= and_items(thd, *join_where, sj_conds); if (!(*join_where)->fixed) - (*join_where)->fix_fields(join->thd, join_where); + (*join_where)->fix_fields(thd, join_where); } - table->table->maybe_null= test(join->mixed_implicit_grouping); + table->table->maybe_null= MY_TEST(join->mixed_implicit_grouping); } if ((nested_join= table->nested_join)) @@ -5768,7 +5843,7 @@ bool JOIN::choose_subquery_plan(table_map join_tables) unmodified, and with injected IN->EXISTS predicates. */ inner_read_time_1= inner_join->best_read; - inner_record_count_1= inner_join->record_count; + inner_record_count_1= inner_join->join_record_count; if (in_to_exists_where && const_tables != table_count) { @@ -5869,8 +5944,8 @@ bool JOIN::choose_subquery_plan(table_map join_tables) Item_in_subselect::test_limit). However, once we allow this, here we should set the correct limit if given in the query. */ - in_subs->unit->global_parameters->select_limit= NULL; - in_subs->unit->set_limit(unit->global_parameters); + in_subs->unit->global_parameters()->select_limit= NULL; + in_subs->unit->set_limit(unit->global_parameters()); /* Set the limit of this JOIN object as well, because normally its being set in the beginning of JOIN::optimize, which was already done. diff --git a/sql/opt_subselect.h b/sql/opt_subselect.h index e4d679fa377..7954becfad4 100644 --- a/sql/opt_subselect.h +++ b/sql/opt_subselect.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2010, 2012, Monty Program Ab + Copyright (c) 2010, 2015, 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 @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* Semi-join subquery optimization code definitions @@ -95,12 +95,13 @@ public: bound_sj_equalities(0), quick_uses_applicable_index(FALSE) { - UNINIT_VAR(quick_max_loose_keypart); /* Protected by quick_uses_applicable_index */ + /* Protected by quick_uses_applicable_index */ + LINT_INIT(quick_max_loose_keypart); /* The following are protected by best_loose_scan_cost!= DBL_MAX */ - UNINIT_VAR(best_loose_scan_key); - UNINIT_VAR(best_loose_scan_records); - UNINIT_VAR(best_max_loose_keypart); - UNINIT_VAR(best_loose_scan_start_key); + LINT_INIT(best_loose_scan_key); + LINT_INIT(best_loose_scan_records); + LINT_INIT(best_max_loose_keypart); + LINT_INIT(best_loose_scan_start_key); } void init(JOIN *join, JOIN_TAB *s, table_map remaining_tables) @@ -122,7 +123,7 @@ public: s->emb_sj_nest->sj_in_exprs < 64 && ((remaining_tables & s->emb_sj_nest->sj_inner_tables) == // (2) s->emb_sj_nest->sj_inner_tables) && // (2) - join->cur_sj_inner_tables == 0 && // (3) + join->cur_sj_inner_tables == 0 && // (3) !(remaining_tables & s->emb_sj_nest->nested_join->sj_corr_tables) && // (4) remaining_tables & s->emb_sj_nest->nested_join->sj_depends_on &&// (5) @@ -168,7 +169,7 @@ public: } } - bool have_a_case() { return test(handled_sj_equalities); } + bool have_a_case() { return MY_TEST(handled_sj_equalities); } void check_ref_access_part1(JOIN_TAB *s, uint key, KEYUSE *start_key, table_map found_part) @@ -194,8 +195,6 @@ public: PREV_BITS(key_part_map, max_loose_keypart+1) && // (3) !key_uses_partial_cols(s->table->s, key)) { - /* Ok, can use the strategy */ - part1_conds_met= TRUE; if (s->quick && s->quick->index == key && s->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE) { @@ -204,6 +203,12 @@ public: } DBUG_PRINT("info", ("Can use LooseScan scan")); + if (found_part & 1) + { + /* Can use LooseScan on ref access if the first key part is bound */ + part1_conds_met= TRUE; + } + /* Check if this is a special case where there are no usable bound IN-equalities, i.e. we have @@ -211,11 +216,13 @@ public: outer_expr IN (SELECT innertbl.key FROM ...) and outer_expr cannot be evaluated yet, so it's actually full - index scan and not a ref access + index scan and not a ref access. + We can do full index scan if it uses index-only. */ if (!(found_part & 1 ) && /* no usable ref access for 1st key part */ s->table->covering_keys.is_set(key)) { + part1_conds_met= TRUE; DBUG_PRINT("info", ("Can use full index scan for LooseScan")); /* Calculate the cost of complete loose index scan. */ @@ -284,6 +291,7 @@ public: { pos->records_read= best_loose_scan_records; pos->key= best_loose_scan_start_key; + pos->cond_selectivity= 1.0; pos->loosescan_picker.loosescan_key= best_loose_scan_key; pos->loosescan_picker.loosescan_parts= best_max_loose_keypart + 1; pos->use_join_buffer= FALSE; @@ -372,8 +380,8 @@ public: These are the members we got from temptable creation code. We'll need them if we'll need to convert table from HEAP to MyISAM/Maria. */ - ENGINE_COLUMNDEF *start_recinfo; - ENGINE_COLUMNDEF *recinfo; + TMP_ENGINE_COLUMNDEF *start_recinfo; + TMP_ENGINE_COLUMNDEF *recinfo; SJ_TMP_TABLE *next_flush_table; @@ -382,6 +390,7 @@ public: bool create_sj_weedout_tmp_table(THD *thd); }; +int setup_semijoin_loosescan(JOIN *join); int setup_semijoin_dups_elimination(JOIN *join, ulonglong options, uint no_jbuf_after); void destroy_sj_tmp_tables(JOIN *join); diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc index f717be5ba3f..5d5132e7fee 100644 --- a/sql/opt_sum.cc +++ b/sql/opt_sum.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2011, Oracle and/or its affiliates. - Copyright (c) 2008-2011 Monty Program Ab + Copyright (c) 2008, 2017, 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 @@ -48,6 +48,7 @@ (assuming a index for column d of table t2 is defined) */ +#include <my_global.h> #include "sql_priv.h" #include "key.h" // key_cmp_if_same #include "sql_select.h" @@ -297,9 +298,9 @@ int opt_sum_query(THD *thd, if (!(tl->table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT) || tl->schema_table) { - maybe_exact_count&= test(!tl->schema_table && - (tl->table->file->ha_table_flags() & - HA_HAS_RECORDS)); + maybe_exact_count&= MY_TEST(!tl->schema_table && + (tl->table->file->ha_table_flags() & + HA_HAS_RECORDS)); is_exact_count= FALSE; count= 1; // ensure count != 0 } @@ -342,7 +343,8 @@ int opt_sum_query(THD *thd, there are no outer joins. */ if (!conds && !((Item_sum_count*) item)->get_arg(0)->maybe_null && - !outer_tables && maybe_exact_count) + !outer_tables && maybe_exact_count && + ((item->used_tables() & OUTER_REF_TABLE_BIT) == 0)) { if (!is_exact_count) { @@ -363,14 +365,15 @@ int opt_sum_query(THD *thd, case Item_sum::MIN_FUNC: case Item_sum::MAX_FUNC: { - int is_max= test(item_sum->sum_func() == Item_sum::MAX_FUNC); + int is_max= MY_TEST(item_sum->sum_func() == Item_sum::MAX_FUNC); /* If MIN/MAX(expr) is the first part of a key or if all previous parts of the key is found in the COND, then we can use indexes to find the key. */ Item *expr=item_sum->get_arg(0); - if (expr->real_item()->type() == Item::FIELD_ITEM) + if (((expr->used_tables() & OUTER_REF_TABLE_BIT) == 0) && + expr->real_item()->type() == Item::FIELD_ITEM) { uchar key_buff[MAX_KEY_LENGTH]; TABLE_REF ref; @@ -473,7 +476,7 @@ int opt_sum_query(THD *thd, } if (thd->is_error()) - DBUG_RETURN(thd->stmt_da->sql_errno()); + DBUG_RETURN(thd->get_stmt_da()->sql_errno()); /* If we have a where clause, we can only ignore searching in the @@ -586,7 +589,7 @@ bool simple_pred(Item_func *func_item, Item **args, bool *inv_order) if (!item->const_item()) return 0; args[i]= item; - if (check_item1_shorter_item2(args[0], args[1])) + if (check_item1_shorter_item2(args[0], args[i])) return 0; } } @@ -662,12 +665,13 @@ static bool matching_cond(bool max_fl, TABLE_REF *ref, KEY *keyinfo, if (!cond) DBUG_RETURN(TRUE); Field *field= field_part->field; - if (cond->used_tables() & OUTER_REF_TABLE_BIT) + table_map cond_used_tables= cond->used_tables(); + if (cond_used_tables & OUTER_REF_TABLE_BIT) { DBUG_RETURN(FALSE); } - if (!(cond->used_tables() & field->table->map) && - test(cond->used_tables() & ~PSEUDO_TABLE_BITS)) + if (!(cond_used_tables & field->table->map) && + MY_TEST(cond_used_tables & ~PSEUDO_TABLE_BITS)) { /* Condition doesn't restrict the used table */ DBUG_RETURN(!cond->const_item()); @@ -820,7 +824,7 @@ static bool matching_cond(bool max_fl, TABLE_REF *ref, KEY *keyinfo, Item *value= args[between && max_fl ? 2 : 1]; value->save_in_field_no_warnings(part->field, 1); if (part->null_bit) - *key_ptr++= (uchar) test(part->field->is_null()); + *key_ptr++= (uchar) MY_TEST(part->field->is_null()); part->field->get_key_image(key_ptr, part->length, Field::itRAW); } if (is_field_part) @@ -840,7 +844,7 @@ static bool matching_cond(bool max_fl, TABLE_REF *ref, KEY *keyinfo, else if (eq_type) { if ((!is_null && !cond->val_int()) || - (is_null && !test(part->field->is_null()))) + (is_null && !MY_TEST(part->field->is_null()))) DBUG_RETURN(FALSE); // Impossible test } else if (is_field_part) diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 9f304ad043b..da1706e630f 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /** @file @@ -328,7 +328,7 @@ const size_t Dep_value_table::iterator_size= ALIGN_SIZE(sizeof(Dep_value_table::Module_iter)); const size_t Dep_value::iterator_size= - max(Dep_value_table::iterator_size, Dep_value_field::iterator_size); + MY_MAX(Dep_value_table::iterator_size, Dep_value_field::iterator_size); /* @@ -356,7 +356,7 @@ public: bound. */ void touch() { unbound_args--; } - bool is_applicable() { return !test(unbound_args); } + bool is_applicable() { return !MY_TEST(unbound_args); } /* Iteration over values that */ typedef char *Iterator; @@ -441,7 +441,7 @@ const size_t Dep_module_key::iterator_size= ALIGN_SIZE(sizeof(Dep_module_key::Value_iter)); const size_t Dep_module::iterator_size= - max(Dep_module_expr::iterator_size, Dep_module_key::iterator_size); + MY_MAX(Dep_module_expr::iterator_size, Dep_module_key::iterator_size); /* @@ -529,12 +529,13 @@ bool check_func_dependency(JOIN *join, TABLE_LIST *oj_tbl, Item* cond); static -void build_eq_mods_for_cond(Dep_analysis_context *dac, +void build_eq_mods_for_cond(THD *thd, Dep_analysis_context *dac, Dep_module_expr **eq_mod, uint *and_level, Item *cond); static void check_equality(Dep_analysis_context *dac, Dep_module_expr **eq_mod, - uint and_level, Item_func *cond, Item *left, Item *right); + uint and_level, Item_bool_func *cond, + Item *left, Item *right); static Dep_module_expr *merge_eq_mods(Dep_module_expr *start, Dep_module_expr *new_fields, @@ -846,7 +847,7 @@ bool check_func_dependency(JOIN *join, Dep_value_field objects for the used fields. */ uint and_level=0; - build_eq_mods_for_cond(&dac, &last_eq_mod, &and_level, cond); + build_eq_mods_for_cond(join->thd, &dac, &last_eq_mod, &and_level, cond); if (!(dac.n_equality_mods= last_eq_mod - dac.equality_mods)) return FALSE; /* No useful conditions */ @@ -1017,6 +1018,7 @@ public: bool Dep_analysis_context::setup_equality_modules_deps(List<Dep_module> *bound_modules) { + THD *thd= current_thd; DBUG_ENTER("setup_equality_modules_deps"); /* @@ -1041,8 +1043,8 @@ bool Dep_analysis_context::setup_equality_modules_deps(List<Dep_module> } void *buf; - if (!(buf= current_thd->alloc(bitmap_buffer_size(offset))) || - bitmap_init(&expr_deps, (my_bitmap_map*)buf, offset, FALSE)) + if (!(buf= thd->alloc(bitmap_buffer_size(offset))) || + my_bitmap_init(&expr_deps, (my_bitmap_map*)buf, offset, FALSE)) { DBUG_RETURN(TRUE); /* purecov: inspected */ } @@ -1072,7 +1074,7 @@ bool Dep_analysis_context::setup_equality_modules_deps(List<Dep_module> else { /* It's a multi-equality */ - eq_mod->unbound_args= !test(eq_mod->expr); + eq_mod->unbound_args= !MY_TEST(eq_mod->expr); List_iterator<Dep_value_field> it(*eq_mod->mult_equal_fields); Dep_value_field* field_val; while ((field_val= it++)) @@ -1083,7 +1085,7 @@ bool Dep_analysis_context::setup_equality_modules_deps(List<Dep_module> } if (!eq_mod->unbound_args) - bound_modules->push_back(eq_mod); + bound_modules->push_back(eq_mod, thd->mem_root); } DBUG_RETURN(FALSE); @@ -1149,7 +1151,7 @@ int compare_field_values(Dep_value_field *a, Dep_value_field *b, void *unused) */ static -void build_eq_mods_for_cond(Dep_analysis_context *ctx, +void build_eq_mods_for_cond(THD *thd, Dep_analysis_context *ctx, Dep_module_expr **eq_mod, uint *and_level, Item *cond) { @@ -1163,7 +1165,7 @@ void build_eq_mods_for_cond(Dep_analysis_context *ctx, { Item *item; while ((item=li++)) - build_eq_mods_for_cond(ctx, eq_mod, and_level, item); + build_eq_mods_for_cond(thd, ctx, eq_mod, and_level, item); for (Dep_module_expr *mod_exp= ctx->equality_mods + orig_offset; mod_exp != *eq_mod ; mod_exp++) @@ -1175,12 +1177,12 @@ void build_eq_mods_for_cond(Dep_analysis_context *ctx, { Item *item; (*and_level)++; - build_eq_mods_for_cond(ctx, eq_mod, and_level, li++); + build_eq_mods_for_cond(thd, ctx, eq_mod, and_level, li++); while ((item=li++)) { Dep_module_expr *start_key_fields= *eq_mod; (*and_level)++; - build_eq_mods_for_cond(ctx, eq_mod, and_level, item); + build_eq_mods_for_cond(thd, ctx, eq_mod, and_level, item); *eq_mod= merge_eq_mods(ctx->equality_mods + orig_offset, start_key_fields, *eq_mod, ++(*and_level)); @@ -1199,27 +1201,30 @@ void build_eq_mods_for_cond(Dep_analysis_context *ctx, case Item_func::BETWEEN: { Item *fld; - if (!((Item_func_between*)cond)->negated && + Item_func_between *func= (Item_func_between *) cond_func; + if (!func->negated && (fld= args[0]->real_item())->type() == Item::FIELD_ITEM && args[1]->eq(args[2], ((Item_field*)fld)->field->binary())) { - check_equality(ctx, eq_mod, *and_level, cond_func, args[0], args[1]); - check_equality(ctx, eq_mod, *and_level, cond_func, args[1], args[0]); + check_equality(ctx, eq_mod, *and_level, func, args[0], args[1]); + check_equality(ctx, eq_mod, *and_level, func, args[1], args[0]); } break; } case Item_func::EQ_FUNC: case Item_func::EQUAL_FUNC: { - check_equality(ctx, eq_mod, *and_level, cond_func, args[0], args[1]); - check_equality(ctx, eq_mod, *and_level, cond_func, args[1], args[0]); + Item_bool_rowready_func2 *func= (Item_bool_rowready_func2*) cond_func; + check_equality(ctx, eq_mod, *and_level, func, args[0], args[1]); + check_equality(ctx, eq_mod, *and_level, func, args[1], args[0]); break; } case Item_func::ISNULL_FUNC: { - Item *tmp=new Item_null; + Item *tmp=new (thd->mem_root) Item_null(thd); if (tmp) - check_equality(ctx, eq_mod, *and_level, cond_func, args[0], tmp); + check_equality(ctx, eq_mod, *and_level, + (Item_func_isnull*) cond_func, args[0], tmp); break; } case Item_func::MULT_EQUAL_FUNC: @@ -1251,7 +1256,7 @@ void build_eq_mods_for_cond(Dep_analysis_context *ctx, { Dep_value_field *field_val; if ((field_val= ctx->get_field_value(equal_field))) - fvl->push_back(field_val); + fvl->push_back(field_val, thd->mem_root); } else { @@ -1398,7 +1403,7 @@ Dep_module_expr *merge_eq_mods(Dep_module_expr *start, } } - if (fv->elements + test(old->expr) > 1) + if (fv->elements + MY_TEST(old->expr) > 1) { old->mult_equal_fields= fv; old->level= and_level; @@ -1479,31 +1484,16 @@ Dep_module_expr *merge_eq_mods(Dep_module_expr *start, static void check_equality(Dep_analysis_context *ctx, Dep_module_expr **eq_mod, - uint and_level, Item_func *cond, Item *left, Item *right) + uint and_level, Item_bool_func *cond, + Item *left, Item *right) { if ((left->used_tables() & ctx->usable_tables) && !(right->used_tables() & RAND_TABLE_BIT) && left->real_item()->type() == Item::FIELD_ITEM) { Field *field= ((Item_field*)left->real_item())->field; - if (field->result_type() == STRING_RESULT) - { - if (right->result_type() != STRING_RESULT) - { - if (field->cmp_type() != right->result_type()) - return; - } - else - { - /* - We can't assume there's a functional dependency if the effective - collation of the operation differ from the field collation. - */ - if (field->cmp_type() == STRING_RESULT && - ((Field_str*)field)->charset() != cond->compare_collation()) - return; - } - } + if (!field->can_optimize_outer_join_table_elimination(cond, right)) + return; Dep_value_field *field_val; if ((field_val= ctx->get_field_value(field))) add_module_expr(ctx, eq_mod, and_level, field_val, right, NULL); @@ -1581,7 +1571,7 @@ Dep_value_table *Dep_analysis_context::create_table_value(TABLE *table) if (key->flags & HA_NOSAME) { Dep_module_key *key_dep; - if (!(key_dep= new Dep_module_key(tbl_dep, i, key->key_parts))) + if (!(key_dep= new Dep_module_key(tbl_dep, i, key->user_defined_key_parts))) return NULL; *key_list= key_dep; key_list= &(key_dep->next_table_key); diff --git a/sql/parse_file.cc b/sql/parse_file.cc index a6e3aa7ed66..f3dab4f7b2f 100644 --- a/sql/parse_file.cc +++ b/sql/parse_file.cc @@ -20,11 +20,11 @@ Text .frm files management routines */ +#include <my_global.h> #include "sql_priv.h" #include "parse_file.h" #include "unireg.h" // CREATE_MODE #include "sql_table.h" // build_table_filename -#include <errno.h> #include <m_ctype.h> #include <my_sys.h> #include <my_dir.h> @@ -59,27 +59,27 @@ write_escaped_string(IO_CACHE *file, LEX_STRING *val_s) */ switch(*ptr) { case '\\': // escape character - if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\\\"))) + if (my_b_write(file, (const uchar *)STRING_WITH_LEN("\\\\"))) return TRUE; break; case '\n': // parameter value delimiter - if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\n"))) + if (my_b_write(file, (const uchar *)STRING_WITH_LEN("\\n"))) return TRUE; break; case '\0': // problem for some string processing utilities - if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\0"))) + if (my_b_write(file, (const uchar *)STRING_WITH_LEN("\\0"))) return TRUE; break; case 26: // problem for windows utilities (Ctrl-Z) - if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\z"))) + if (my_b_write(file, (const uchar *)STRING_WITH_LEN("\\z"))) return TRUE; break; case '\'': // list of string delimiter - if (my_b_append(file, (const uchar *)STRING_WITH_LEN("\\\'"))) + if (my_b_write(file, (const uchar *)STRING_WITH_LEN("\\\'"))) return TRUE; break; default: - if (my_b_append(file, (const uchar *)ptr, 1)) + if (my_b_write(file, (const uchar *)ptr, 1)) return TRUE; } } @@ -147,7 +147,7 @@ write_parameter(IO_CACHE *file, uchar* base, File_option *parameter) case FILE_OPTIONS_STRING: { LEX_STRING *val_s= (LEX_STRING *)(base + parameter->offset); - if (my_b_append(file, (const uchar *)val_s->str, val_s->length)) + if (my_b_write(file, (const uchar *)val_s->str, val_s->length)) DBUG_RETURN(TRUE); break; } @@ -166,7 +166,7 @@ write_parameter(IO_CACHE *file, uchar* base, File_option *parameter) val= view_algo_to_frm(val); num.set(val, &my_charset_bin); - if (my_b_append(file, (const uchar *)num.ptr(), num.length())) + if (my_b_write(file, (const uchar *)num.ptr(), num.length())) DBUG_RETURN(TRUE); break; } @@ -179,7 +179,7 @@ write_parameter(IO_CACHE *file, uchar* base, File_option *parameter) get_date(val_s->str, GETDATE_DATE_TIME|GETDATE_GMT|GETDATE_FIXEDLENGTH, tm); val_s->length= PARSE_FILE_TIMESTAMPLENGTH; - if (my_b_append(file, (const uchar *)val_s->str, + if (my_b_write(file, (const uchar *)val_s->str, PARSE_FILE_TIMESTAMPLENGTH)) DBUG_RETURN(TRUE); break; @@ -193,10 +193,10 @@ write_parameter(IO_CACHE *file, uchar* base, File_option *parameter) while ((str= it++)) { // We need ' ' after string to detect list continuation - if ((!first && my_b_append(file, (const uchar *)STRING_WITH_LEN(" "))) || - my_b_append(file, (const uchar *)STRING_WITH_LEN("\'")) || + if ((!first && my_b_write(file, (const uchar *)STRING_WITH_LEN(" "))) || + my_b_write(file, (const uchar *)STRING_WITH_LEN("\'")) || write_escaped_string(file, str) || - my_b_append(file, (const uchar *)STRING_WITH_LEN("\'"))) + my_b_write(file, (const uchar *)STRING_WITH_LEN("\'"))) { DBUG_RETURN(TRUE); } @@ -214,8 +214,8 @@ write_parameter(IO_CACHE *file, uchar* base, File_option *parameter) { num.set(*val, &my_charset_bin); // We need ' ' after string to detect list continuation - if ((!first && my_b_append(file, (const uchar *)STRING_WITH_LEN(" "))) || - my_b_append(file, (const uchar *)num.ptr(), num.length())) + if ((!first && my_b_write(file, (const uchar *)STRING_WITH_LEN(" "))) || + my_b_write(file, (const uchar *)num.ptr(), num.length())) { DBUG_RETURN(TRUE); } @@ -282,28 +282,28 @@ sql_create_definition_file(const LEX_STRING *dir, const LEX_STRING *file_name, path[path_end+1]= '\0'; if ((handler= mysql_file_create(key_file_fileparser, path, CREATE_MODE, O_RDWR | O_TRUNC, - MYF(MY_WME))) <= 0) + MYF(MY_WME))) < 0) { DBUG_RETURN(TRUE); } - if (init_io_cache(&file, handler, 0, SEQ_READ_APPEND, 0L, 0, MYF(MY_WME))) + if (init_io_cache(&file, handler, 0, WRITE_CACHE, 0L, 0, MYF(MY_WME))) goto err_w_file; // write header (file signature) - if (my_b_append(&file, (const uchar *)STRING_WITH_LEN("TYPE=")) || - my_b_append(&file, (const uchar *)type->str, type->length) || - my_b_append(&file, (const uchar *)STRING_WITH_LEN("\n"))) - goto err_w_file; + if (my_b_write(&file, (const uchar *)STRING_WITH_LEN("TYPE=")) || + my_b_write(&file, (const uchar *)type->str, type->length) || + my_b_write(&file, (const uchar *)STRING_WITH_LEN("\n"))) + goto err_w_cache; // write parameters to temporary file for (param= parameters; param->name.str; param++) { - if (my_b_append(&file, (const uchar *)param->name.str, + if (my_b_write(&file, (const uchar *)param->name.str, param->name.length) || - my_b_append(&file, (const uchar *)STRING_WITH_LEN("=")) || + my_b_write(&file, (const uchar *)STRING_WITH_LEN("=")) || write_parameter(&file, base, param) || - my_b_append(&file, (const uchar *)STRING_WITH_LEN("\n"))) + my_b_write(&file, (const uchar *)STRING_WITH_LEN("\n"))) goto err_w_cache; } @@ -404,7 +404,7 @@ sql_parse_prepare(const LEX_STRING *file_name, MEM_ROOT *mem_root, { MY_STAT stat_info; size_t len; - char *end, *sign; + char *buff, *end, *sign; File_parser *parser; File file; DBUG_ENTER("sql_parse_prepare"); @@ -426,7 +426,7 @@ sql_parse_prepare(const LEX_STRING *file_name, MEM_ROOT *mem_root, DBUG_RETURN(0); } - if (!(parser->buff= (char*) alloc_root(mem_root, (size_t)(stat_info.st_size+1)))) + if (!(buff= (char*) alloc_root(mem_root, (size_t)(stat_info.st_size+1)))) { DBUG_RETURN(0); } @@ -437,9 +437,8 @@ sql_parse_prepare(const LEX_STRING *file_name, MEM_ROOT *mem_root, DBUG_RETURN(0); } - if ((len= mysql_file_read(file, (uchar *)parser->buff, - stat_info.st_size, MYF(MY_WME))) == - MY_FILE_ERROR) + if ((len= mysql_file_read(file, (uchar *)buff, stat_info.st_size, + MYF(MY_WME))) == MY_FILE_ERROR) { mysql_file_close(file, MYF(MY_WME)); DBUG_RETURN(0); @@ -450,20 +449,20 @@ sql_parse_prepare(const LEX_STRING *file_name, MEM_ROOT *mem_root, DBUG_RETURN(0); } - end= parser->end= parser->buff + len; + end= buff + len; *end= '\0'; // barrier for more simple parsing // 7 = 5 (TYPE=) + 1 (letter at least of type name) + 1 ('\n') if (len < 7 || - parser->buff[0] != 'T' || - parser->buff[1] != 'Y' || - parser->buff[2] != 'P' || - parser->buff[3] != 'E' || - parser->buff[4] != '=') + buff[0] != 'T' || + buff[1] != 'Y' || + buff[2] != 'P' || + buff[3] != 'E' || + buff[4] != '=') goto frm_error; // skip signature; - parser->file_type.str= sign= parser->buff + 5; + parser->file_type.str= sign= buff + 5; while (*sign >= 'A' && *sign <= 'Z' && sign < end) sign++; if (*sign != '\n') @@ -472,6 +471,7 @@ sql_parse_prepare(const LEX_STRING *file_name, MEM_ROOT *mem_root, // EOS for file signature just for safety *sign= '\0'; + parser->end= end; parser->start= sign + 1; parser->content_ok= 1; @@ -504,11 +504,12 @@ frm_error: */ -static char * -parse_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str) +static const char * +parse_string(const char *ptr, const char *end, MEM_ROOT *mem_root, + LEX_STRING *str) { // get string length - char *eol= strchr(ptr, '\n'); + const char *eol= strchr(ptr, '\n'); if (eol >= end) return 0; @@ -535,7 +536,7 @@ parse_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str) */ my_bool -read_escaped_string(char *ptr, char *eol, LEX_STRING *str) +read_escaped_string(const char *ptr, const char *eol, LEX_STRING *str) { char *write_pos= str->str; @@ -595,10 +596,11 @@ read_escaped_string(char *ptr, char *eol, LEX_STRING *str) */ -char * -parse_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str) +const char * +parse_escaped_string(const char *ptr, const char *end, MEM_ROOT *mem_root, + LEX_STRING *str) { - char *eol= strchr(ptr, '\n'); + const char *eol= strchr(ptr, '\n'); if (eol == 0 || eol >= end || !(str->str= (char*) alloc_root(mem_root, (eol - ptr) + 1)) || @@ -624,11 +626,11 @@ parse_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str) \# pointer on symbol after string */ -static char * -parse_quoted_escaped_string(char *ptr, char *end, +static const char * +parse_quoted_escaped_string(const char *ptr, const char *end, MEM_ROOT *mem_root, LEX_STRING *str) { - char *eol; + const char *eol; uint result_len= 0; bool escaped= 0; @@ -665,7 +667,8 @@ parse_quoted_escaped_string(char *ptr, char *end, @param[in] mem_root MEM_ROOT for parameters allocation */ -bool get_file_options_ulllist(char *&ptr, char *end, char *line, +bool get_file_options_ulllist(const char *&ptr, const char *end, + const char *line, uchar* base, File_option *parameter, MEM_ROOT *mem_root) { @@ -676,7 +679,7 @@ bool get_file_options_ulllist(char *&ptr, char *end, char *line, while (ptr < end) { int not_used; - char *num_end= end; + char *num_end= const_cast<char *>(end); if (!(num= (ulonglong*)alloc_root(mem_root, sizeof(ulonglong))) || nlist->push_back(num, mem_root)) goto nlist_err; @@ -731,18 +734,18 @@ nlist_err: my_bool File_parser::parse(uchar* base, MEM_ROOT *mem_root, struct File_option *parameters, uint required, - Unknown_key_hook *hook) + Unknown_key_hook *hook) const { uint first_param= 0, found= 0; - char *ptr= start; - char *eol; + const char *ptr= start; + const char *eol; LEX_STRING *str; List<LEX_STRING> *list; DBUG_ENTER("File_parser::parse"); while (ptr < end && found < required) { - char *line= ptr; + const char *line= ptr; if (*ptr == '#') { // it is comment @@ -940,9 +943,9 @@ list_err: */ bool -File_parser_dummy_hook::process_unknown_string(char *&unknown_key, +File_parser_dummy_hook::process_unknown_string(const char *&unknown_key, uchar* base, MEM_ROOT *mem_root, - char *end) + const char *end) { DBUG_ENTER("file_parser_dummy_hook::process_unknown_string"); DBUG_PRINT("info", ("Unknown key: '%60s'", unknown_key)); diff --git a/sql/parse_file.h b/sql/parse_file.h index 83a8eabcf5f..87917dbd71b 100644 --- a/sql/parse_file.h +++ b/sql/parse_file.h @@ -58,8 +58,8 @@ class Unknown_key_hook public: Unknown_key_hook() {} /* Remove gcc warning */ virtual ~Unknown_key_hook() {} /* Remove gcc warning */ - virtual bool process_unknown_string(char *&unknown_key, uchar* base, - MEM_ROOT *mem_root, char *end)= 0; + virtual bool process_unknown_string(const char *&unknown_key, uchar* base, + MEM_ROOT *mem_root, const char *end)= 0; }; @@ -69,18 +69,20 @@ class File_parser_dummy_hook: public Unknown_key_hook { public: File_parser_dummy_hook() {} /* Remove gcc warning */ - virtual bool process_unknown_string(char *&unknown_key, uchar* base, - MEM_ROOT *mem_root, char *end); + virtual bool process_unknown_string(const char *&unknown_key, uchar* base, + MEM_ROOT *mem_root, const char *end); }; extern File_parser_dummy_hook file_parser_dummy_hook; -bool get_file_options_ulllist(char *&ptr, char *end, char *line, - uchar* base, File_option *parameter, +bool get_file_options_ulllist(const char *&ptr, const char *end, + const char *line, uchar* base, + File_option *parameter, MEM_ROOT *mem_root); -char * -parse_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str); +const char * +parse_escaped_string(const char *ptr, const char *end, MEM_ROOT *mem_root, + LEX_STRING *str); class File_parser; File_parser *sql_parse_prepare(const LEX_STRING *file_name, @@ -96,18 +98,18 @@ my_bool rename_in_schema_file(THD *thd, class File_parser: public Sql_alloc { - char *buff, *start, *end; + char *start, *end; LEX_STRING file_type; bool content_ok; public: - File_parser() :buff(0), start(0), end(0), content_ok(0) + File_parser() :start(0), end(0), content_ok(0) { file_type.str= 0; file_type.length= 0; } bool ok() { return content_ok; } - LEX_STRING *type() { return &file_type; } + const LEX_STRING *type() const { return &file_type; } my_bool parse(uchar* base, MEM_ROOT *mem_root, struct File_option *parameters, uint required, - Unknown_key_hook *hook); + Unknown_key_hook *hook) const; friend File_parser *sql_parse_prepare(const LEX_STRING *file_name, MEM_ROOT *mem_root, diff --git a/sql/partition_element.h b/sql/partition_element.h index 75033d3552f..308a4d6ddd2 100644 --- a/sql/partition_element.h +++ b/sql/partition_element.h @@ -115,12 +115,10 @@ public: partition_name(NULL), tablespace_name(NULL), log_entry(NULL), part_comment(NULL), data_file_name(NULL), index_file_name(NULL), - engine_type(NULL), part_state(PART_NORMAL), + engine_type(NULL), connect_string(null_lex_str), part_state(PART_NORMAL), nodegroup_id(UNDEF_NODEGROUP), has_null_value(FALSE), signed_flag(FALSE), max_value(FALSE) { - connect_string.str= 0; - connect_string.length= 0; } partition_element(partition_element *part_elem) : part_max_rows(part_elem->part_max_rows), @@ -131,13 +129,11 @@ public: data_file_name(part_elem->data_file_name), index_file_name(part_elem->index_file_name), engine_type(part_elem->engine_type), - connect_string(part_elem->connect_string), + connect_string(null_lex_str), part_state(part_elem->part_state), nodegroup_id(part_elem->nodegroup_id), has_null_value(FALSE) { - connect_string.str= 0; - connect_string.length= 0; } ~partition_element() {} }; diff --git a/sql/partition_info.cc b/sql/partition_info.cc index 740e5087477..892b7e8bd05 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -20,59 +20,508 @@ #pragma implementation #endif +#include <my_global.h> #include "sql_priv.h" // Required to get server definitions for mysql/plugin.h right #include "sql_plugin.h" -#include "sql_partition.h" /* partition_info.h: LIST_PART_ENTRY */ +#include "sql_partition.h" // partition_info.h: LIST_PART_ENTRY + // NOT_A_PARTITION_ID #include "partition_info.h" -#include "sql_parse.h" // test_if_data_home_dir +#include "sql_parse.h" #include "sql_acl.h" // *_ACL +#include "sql_base.h" // fill_record #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" -partition_info *partition_info::get_clone() +partition_info *partition_info::get_clone(THD *thd) { + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ENTER("partition_info::get_clone"); List_iterator<partition_element> part_it(partitions); partition_element *part; - partition_info *clone= new partition_info(); + partition_info *clone= new (mem_root) partition_info(); if (!clone) { mem_alloc_error(sizeof(partition_info)); - return NULL; + DBUG_RETURN(NULL); } - memcpy(clone, this, sizeof(partition_info)); + *clone= *this; + memset(&(clone->read_partitions), 0, sizeof(clone->read_partitions)); + memset(&(clone->lock_partitions), 0, sizeof(clone->lock_partitions)); + clone->bitmaps_are_initialized= FALSE; clone->partitions.empty(); while ((part= (part_it++))) { List_iterator<partition_element> subpart_it(part->subpartitions); partition_element *subpart; - partition_element *part_clone= new partition_element(); + partition_element *part_clone= new (mem_root) partition_element(); if (!part_clone) { mem_alloc_error(sizeof(partition_element)); - return NULL; + DBUG_RETURN(NULL); } - memcpy(part_clone, part, sizeof(partition_element)); + *part_clone= *part; part_clone->subpartitions.empty(); while ((subpart= (subpart_it++))) { - partition_element *subpart_clone= new partition_element(); + partition_element *subpart_clone= new (mem_root) partition_element(); if (!subpart_clone) { mem_alloc_error(sizeof(partition_element)); - return NULL; + DBUG_RETURN(NULL); } - memcpy(subpart_clone, subpart, sizeof(partition_element)); - part_clone->subpartitions.push_back(subpart_clone); + *subpart_clone= *subpart; + part_clone->subpartitions.push_back(subpart_clone, mem_root); + } + clone->partitions.push_back(part_clone, mem_root); + part_clone->list_val_list.empty(); + List_iterator<part_elem_value> list_val_it(part->list_val_list); + part_elem_value *new_val_arr= + (part_elem_value *)alloc_root(mem_root, sizeof(part_elem_value) * + part->list_val_list.elements); + if (!new_val_arr) + { + mem_alloc_error(sizeof(part_elem_value) * part->list_val_list.elements); + DBUG_RETURN(NULL); + } + p_column_list_val *new_colval_arr= + (p_column_list_val*)alloc_root(mem_root, sizeof(p_column_list_val) * + num_columns * + part->list_val_list.elements); + if (!new_colval_arr) + { + mem_alloc_error(sizeof(p_column_list_val) * num_columns * + part->list_val_list.elements); + DBUG_RETURN(NULL); + } + part_elem_value *val; + while ((val= list_val_it++)) + { + part_elem_value *new_val= new_val_arr++; + memcpy(new_val, val, sizeof(part_elem_value)); + if (!val->null_value) + { + p_column_list_val *new_colval= new_colval_arr; + new_colval_arr+= num_columns; + memcpy(new_colval, val->col_val_array, + sizeof(p_column_list_val) * num_columns); + new_val->col_val_array= new_colval; + } + part_clone->list_val_list.push_back(new_val, mem_root); + } + } + DBUG_RETURN(clone); +} + +/** + Mark named [sub]partition to be used/locked. + + @param part_name Partition name to match. + @param length Partition name length. + + @return Success if partition found + @retval true Partition found + @retval false Partition not found +*/ + +bool partition_info::add_named_partition(const char *part_name, + uint length) +{ + HASH *part_name_hash; + PART_NAME_DEF *part_def; + Partition_share *part_share; + DBUG_ENTER("partition_info::add_named_partition"); + DBUG_ASSERT(table && table->s && table->s->ha_share); + part_share= static_cast<Partition_share*>((table->s->ha_share)); + DBUG_ASSERT(part_share->partition_name_hash_initialized); + part_name_hash= &part_share->partition_name_hash; + DBUG_ASSERT(part_name_hash->records); + + part_def= (PART_NAME_DEF*) my_hash_search(part_name_hash, + (const uchar*) part_name, + length); + if (!part_def) + { + my_error(ER_UNKNOWN_PARTITION, MYF(0), part_name, table->alias.c_ptr()); + DBUG_RETURN(true); + } + + if (part_def->is_subpart) + { + bitmap_set_bit(&read_partitions, part_def->part_id); + } + else + { + if (is_sub_partitioned()) + { + /* Mark all subpartitions in the partition */ + uint j, start= part_def->part_id; + uint end= start + num_subparts; + for (j= start; j < end; j++) + bitmap_set_bit(&read_partitions, j); + } + else + bitmap_set_bit(&read_partitions, part_def->part_id); + } + DBUG_PRINT("info", ("Found partition %u is_subpart %d for name %s", + part_def->part_id, part_def->is_subpart, + part_name)); + DBUG_RETURN(false); +} + + +/** + Mark named [sub]partition to be used/locked. + + @param part_elem Partition element that matched. +*/ + +bool partition_info::set_named_partition_bitmap(const char *part_name, + uint length) +{ + DBUG_ENTER("partition_info::set_named_partition_bitmap"); + bitmap_clear_all(&read_partitions); + if (add_named_partition(part_name, length)) + DBUG_RETURN(true); + bitmap_copy(&lock_partitions, &read_partitions); + DBUG_RETURN(false); +} + + + +/** + Prune away partitions not mentioned in the PARTITION () clause, + if used. + + @param table_list Table list pointing to table to prune. + + @return Operation status + @retval true Failure + @retval false Success +*/ +bool partition_info::prune_partition_bitmaps(TABLE_LIST *table_list) +{ + List_iterator<String> partition_names_it(*(table_list->partition_names)); + uint num_names= table_list->partition_names->elements; + uint i= 0; + DBUG_ENTER("partition_info::prune_partition_bitmaps"); + + if (num_names < 1) + DBUG_RETURN(true); + + /* + TODO: When adding support for FK in partitioned tables, the referenced + table must probably lock all partitions for read, and also write depending + of ON DELETE/UPDATE. + */ + bitmap_clear_all(&read_partitions); + + /* No check for duplicate names or overlapping partitions/subpartitions. */ + + DBUG_PRINT("info", ("Searching through partition_name_hash")); + do + { + String *part_name_str= partition_names_it++; + if (add_named_partition(part_name_str->c_ptr(), part_name_str->length())) + DBUG_RETURN(true); + } while (++i < num_names); + DBUG_RETURN(false); +} + + +/** + Set read/lock_partitions bitmap over non pruned partitions + + @param table_list Possible TABLE_LIST which can contain + list of partition names to query + + @return Operation status + @retval FALSE OK + @retval TRUE Failed to allocate memory for bitmap or list of partitions + did not match + + @note OK to call multiple times without the need for free_bitmaps. +*/ + +bool partition_info::set_partition_bitmaps(TABLE_LIST *table_list) +{ + DBUG_ENTER("partition_info::set_partition_bitmaps"); + + DBUG_ASSERT(bitmaps_are_initialized); + DBUG_ASSERT(table); + is_pruning_completed= false; + if (!bitmaps_are_initialized) + DBUG_RETURN(TRUE); + + if (table_list && + table_list->partition_names && + table_list->partition_names->elements) + { + if (table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) + { + my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(true); } - clone->partitions.push_back(part_clone); + if (prune_partition_bitmaps(table_list)) + DBUG_RETURN(TRUE); + } + else + { + bitmap_set_all(&read_partitions); + DBUG_PRINT("info", ("Set all partitions")); } - return clone; + bitmap_copy(&lock_partitions, &read_partitions); + DBUG_ASSERT(bitmap_get_first_set(&lock_partitions) != MY_BIT_NONE); + DBUG_RETURN(FALSE); } + +/** + Checks if possible to do prune partitions on insert. + + @param thd Thread context + @param duplic How to handle duplicates + @param update In case of ON DUPLICATE UPDATE, default function fields + @param update_fields In case of ON DUPLICATE UPDATE, which fields to update + @param fields Listed fields + @param empty_values True if values is empty (only defaults) + @param[out] prune_needs_default_values Set on return if copying of default + values is needed + @param[out] can_prune_partitions Enum showing if possible to prune + @param[inout] used_partitions If possible to prune the bitmap + is initialized and cleared + + @return Operation status + @retval false Success + @retval true Failure +*/ + +bool partition_info::can_prune_insert(THD* thd, + enum_duplicates duplic, + COPY_INFO &update, + List<Item> &update_fields, + List<Item> &fields, + bool empty_values, + enum_can_prune *can_prune_partitions, + bool *prune_needs_default_values, + MY_BITMAP *used_partitions) +{ + uint32 *bitmap_buf; + uint bitmap_bytes; + uint num_partitions= 0; + *can_prune_partitions= PRUNE_NO; + DBUG_ASSERT(bitmaps_are_initialized); + DBUG_ENTER("partition_info::can_prune_insert"); + + if (table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) + DBUG_RETURN(false); + + /* + If under LOCK TABLES pruning will skip start_stmt instead of external_lock + for unused partitions. + + Cannot prune if there are BEFORE INSERT triggers that changes any + partitioning column, since they may change the row to be in another + partition. + */ + if (table->triggers && + table->triggers->has_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE) && + table->triggers->is_fields_updated_in_trigger(&full_part_field_set, + TRG_EVENT_INSERT, + TRG_ACTION_BEFORE)) + DBUG_RETURN(false); + + if (table->found_next_number_field) + { + /* + If the field is used in the partitioning expression, we cannot prune. + TODO: If all rows have not null values and + is not 0 (with NO_AUTO_VALUE_ON_ZERO sql_mode), then pruning is possible! + */ + if (bitmap_is_set(&full_part_field_set, + table->found_next_number_field->field_index)) + DBUG_RETURN(false); + } + + /* + If updating a field in the partitioning expression, we cannot prune. + + Note: TIMESTAMP_AUTO_SET_ON_INSERT is handled by converting Item_null + to the start time of the statement. Which will be the same as in + write_row(). So pruning of TIMESTAMP DEFAULT CURRENT_TIME will work. + But TIMESTAMP_AUTO_SET_ON_UPDATE cannot be pruned if the timestamp + column is a part of any part/subpart expression. + */ + if (duplic == DUP_UPDATE) + { + /* + TODO: add check for static update values, which can be pruned. + */ + if (is_field_in_part_expr(update_fields)) + DBUG_RETURN(false); + + /* + Cannot prune if there are BEFORE UPDATE triggers that changes any + partitioning column, since they may change the row to be in another + partition. + */ + if (table->triggers && + table->triggers->has_triggers(TRG_EVENT_UPDATE, + TRG_ACTION_BEFORE) && + table->triggers->is_fields_updated_in_trigger(&full_part_field_set, + TRG_EVENT_UPDATE, + TRG_ACTION_BEFORE)) + { + DBUG_RETURN(false); + } + } + + /* + If not all partitioning fields are given, + we also must set all non given partitioning fields + to get correct defaults. + TODO: If any gain, we could enhance this by only copy the needed default + fields by + 1) check which fields needs to be set. + 2) only copy those fields from the default record. + */ + *prune_needs_default_values= false; + if (fields.elements) + { + if (!is_full_part_expr_in_fields(fields)) + *prune_needs_default_values= true; + } + else if (empty_values) + { + *prune_needs_default_values= true; // like 'INSERT INTO t () VALUES ()' + } + else + { + /* + In case of INSERT INTO t VALUES (...) we must get values for + all fields in table from VALUES (...) part, so no defaults + are needed. + */ + } + + /* Pruning possible, have to initialize the used_partitions bitmap. */ + num_partitions= lock_partitions.n_bits; + bitmap_bytes= bitmap_buffer_size(num_partitions); + if (!(bitmap_buf= (uint32*) thd->alloc(bitmap_bytes))) + { + mem_alloc_error(bitmap_bytes); + DBUG_RETURN(true); + } + /* Also clears all bits. */ + if (my_bitmap_init(used_partitions, bitmap_buf, num_partitions, false)) + { + /* purecov: begin deadcode */ + /* Cannot happen, due to pre-alloc. */ + mem_alloc_error(bitmap_bytes); + DBUG_RETURN(true); + /* purecov: end */ + } + /* + If no partitioning field in set (e.g. defaults) check pruning only once. + */ + if (fields.elements && + !is_field_in_part_expr(fields)) + *can_prune_partitions= PRUNE_DEFAULTS; + else + *can_prune_partitions= PRUNE_YES; + + DBUG_RETURN(false); +} + + +/** + Mark the partition, the record belongs to, as used. + + @param fields Fields to set + @param values Values to use + @param info COPY_INFO used for default values handling + @param copy_default_values True if we should copy default values + @param used_partitions Bitmap to set + + @returns Operational status + @retval false Success + @retval true Failure +*/ + +bool partition_info::set_used_partition(List<Item> &fields, + List<Item> &values, + COPY_INFO &info, + bool copy_default_values, + MY_BITMAP *used_partitions) +{ + THD *thd= table->in_use; + uint32 part_id; + longlong func_value; + Dummy_error_handler error_handler; + bool ret= true; + DBUG_ENTER("set_partition"); + DBUG_ASSERT(thd); + + /* Only allow checking of constant values */ + List_iterator_fast<Item> v(values); + Item *item; + thd->push_internal_handler(&error_handler); + while ((item= v++)) + { + if (!item->const_item()) + goto err; + } + + if (copy_default_values) + restore_record(table,s->default_values); + + if (fields.elements || !values.elements) + { + if (fill_record(thd, table, fields, values, false)) + goto err; + } + else + { + if (fill_record(thd, table, table->field, values, false, false)) + goto err; + } + DBUG_ASSERT(!table->auto_increment_field_not_null); + + /* + Evaluate DEFAULT functions like CURRENT_TIMESTAMP. + TODO: avoid setting non partitioning fields default value, to avoid + overhead. Not yet done, since mostly only one DEFAULT function per + table, or at least very few such columns. + */ +// if (info.function_defaults_apply_on_columns(&full_part_field_set)) +// info.set_function_defaults(table); + + { + /* + This function is used in INSERT; 'values' are supplied by user, + or are default values, not values read from a table, so read_set is + irrelevant. + */ + my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); + const int rc= get_partition_id(this, &part_id, &func_value); + dbug_tmp_restore_column_map(table->read_set, old_map); + if (rc) + goto err; + } + + DBUG_PRINT("info", ("Insert into partition %u", part_id)); + bitmap_set_bit(used_partitions, part_id); + ret= false; + +err: + thd->pop_internal_handler(); + DBUG_RETURN(ret); +} + + /* Create a memory area where default partition names are stored and fill it up with the names. @@ -158,8 +607,9 @@ void partition_info::set_show_version_string(String *packet) /* Create a unique name for the subpartition as part_name'sp''subpart_no' + SYNOPSIS - create_subpartition_name() + create_default_subpartition_name() subpart_no Number of subpartition part_name Name of partition RETURN VALUES @@ -167,12 +617,12 @@ void partition_info::set_show_version_string(String *packet) 0 Memory allocation error */ -char *partition_info::create_subpartition_name(uint subpart_no, +char *partition_info::create_default_subpartition_name(uint subpart_no, const char *part_name) { uint size_alloc= strlen(part_name) + MAX_PART_NAME_SIZE; char *ptr= (char*) sql_calloc(size_alloc); - DBUG_ENTER("create_subpartition_name"); + DBUG_ENTER("create_default_subpartition_name"); if (likely(ptr != NULL)) { @@ -318,7 +768,8 @@ bool partition_info::set_up_default_subpartitions(handler *file, if (likely(subpart_elem != 0 && (!part_elem->subpartitions.push_back(subpart_elem)))) { - char *ptr= create_subpartition_name(j, part_elem->partition_name); + char *ptr= create_default_subpartition_name(j, + part_elem->partition_name); if (!ptr) goto end; subpart_elem->engine_type= default_engine_type; @@ -378,7 +829,7 @@ bool partition_info::set_up_defaults_for_partitioning(handler *file, Support routine for check_partition_info SYNOPSIS - has_unique_fields + find_duplicate_field no parameters RETURN VALUE @@ -389,13 +840,13 @@ bool partition_info::set_up_defaults_for_partitioning(handler *file, Check that the user haven't defined the same field twice in key or column list partitioning. */ -char* partition_info::has_unique_fields() +char* partition_info::find_duplicate_field() { char *field_name_outer, *field_name_inner; List_iterator<char> it_outer(part_field_list); uint num_fields= part_field_list.elements; uint i,j; - DBUG_ENTER("partition_info::has_unique_fields"); + DBUG_ENTER("partition_info::find_duplicate_field"); for (i= 0; i < num_fields; i++) { @@ -417,6 +868,154 @@ char* partition_info::has_unique_fields() DBUG_RETURN(NULL); } + +/** + @brief Get part_elem and part_id from partition name + + @param partition_name Name of partition to search for. + @param file_name[out] Partition file name (part after table name, + #P#<part>[#SP#<subpart>]), skipped if NULL. + @param part_id[out] Id of found partition or NOT_A_PARTITION_ID. + + @retval Pointer to part_elem of [sub]partition, if not found NULL + + @note Since names of partitions AND subpartitions must be unique, + this function searches both partitions and subpartitions and if name of + a partition is given for a subpartitioned table, part_elem will be + the partition, but part_id will be NOT_A_PARTITION_ID and file_name not set. +*/ +partition_element *partition_info::get_part_elem(const char *partition_name, + char *file_name, + size_t file_name_size, + uint32 *part_id) +{ + List_iterator<partition_element> part_it(partitions); + uint i= 0; + DBUG_ENTER("partition_info::get_part_elem"); + DBUG_ASSERT(part_id); + *part_id= NOT_A_PARTITION_ID; + do + { + partition_element *part_elem= part_it++; + if (is_sub_partitioned()) + { + List_iterator<partition_element> sub_part_it(part_elem->subpartitions); + uint j= 0; + do + { + partition_element *sub_part_elem= sub_part_it++; + if (!my_strcasecmp(system_charset_info, + sub_part_elem->partition_name, partition_name)) + { + if (file_name) + if (create_subpartition_name(file_name, file_name_size, "", + part_elem->partition_name, + partition_name, NORMAL_PART_NAME)) + DBUG_RETURN(NULL); + *part_id= j + (i * num_subparts); + DBUG_RETURN(sub_part_elem); + } + } while (++j < num_subparts); + + /* Naming a partition (first level) on a subpartitioned table. */ + if (!my_strcasecmp(system_charset_info, + part_elem->partition_name, partition_name)) + DBUG_RETURN(part_elem); + } + else if (!my_strcasecmp(system_charset_info, + part_elem->partition_name, partition_name)) + { + if (file_name) + if (create_partition_name(file_name, file_name_size, "", + partition_name, NORMAL_PART_NAME, TRUE)) + DBUG_RETURN(NULL); + *part_id= i; + DBUG_RETURN(part_elem); + } + } while (++i < num_parts); + DBUG_RETURN(NULL); +} + + +/** + Helper function to find_duplicate_name. +*/ + +static const char *get_part_name_from_elem(const char *name, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length= strlen(name); + return name; +} + +/* + A support function to check partition names for duplication in a + partitioned table + + SYNOPSIS + find_duplicate_name() + + RETURN VALUES + NULL Has unique part and subpart names + !NULL Pointer to duplicated name + + DESCRIPTION + Checks that the list of names in the partitions doesn't contain any + duplicated names. +*/ + +char *partition_info::find_duplicate_name() +{ + HASH partition_names; + uint max_names; + const uchar *curr_name= NULL; + List_iterator<partition_element> parts_it(partitions); + partition_element *p_elem; + + DBUG_ENTER("partition_info::find_duplicate_name"); + + /* + TODO: If table->s->ha_part_data->partition_name_hash.elements is > 0, + then we could just return NULL, but that has not been verified. + And this only happens when in ALTER TABLE with full table copy. + */ + + max_names= num_parts; + if (is_sub_partitioned()) + max_names+= num_parts * num_subparts; + if (my_hash_init(&partition_names, system_charset_info, max_names, 0, 0, + (my_hash_get_key) get_part_name_from_elem, 0, HASH_UNIQUE)) + { + DBUG_ASSERT(0); + curr_name= (const uchar*) "Internal failure"; + goto error; + } + while ((p_elem= (parts_it++))) + { + curr_name= (const uchar*) p_elem->partition_name; + if (my_hash_insert(&partition_names, curr_name)) + goto error; + + if (!p_elem->subpartitions.is_empty()) + { + List_iterator<partition_element> subparts_it(p_elem->subpartitions); + partition_element *subp_elem; + while ((subp_elem= (subparts_it++))) + { + curr_name= (const uchar*) subp_elem->partition_name; + if (my_hash_insert(&partition_names, curr_name)) + goto error; + } + } + } + my_hash_free(&partition_names); + DBUG_RETURN(NULL); +error: + my_hash_free(&partition_names); + DBUG_RETURN((char*) curr_name); +} + + /* A support function to check if a partition element's name is unique @@ -460,49 +1059,6 @@ bool partition_info::has_unique_name(partition_element *element) /* - A support function to check partition names for duplication in a - partitioned table - - SYNOPSIS - has_unique_names() - - RETURN VALUES - TRUE Has unique part and subpart names - FALSE Doesn't - - DESCRIPTION - Checks that the list of names in the partitions doesn't contain any - duplicated names. -*/ - -char *partition_info::has_unique_names() -{ - DBUG_ENTER("partition_info::has_unique_names"); - - List_iterator<partition_element> parts_it(partitions); - - partition_element *el; - while ((el= (parts_it++))) - { - if (! has_unique_name(el)) - DBUG_RETURN(el->partition_name); - - if (!el->subpartitions.is_empty()) - { - List_iterator<partition_element> subparts_it(el->subpartitions); - partition_element *subel; - while ((subel= (subparts_it++))) - { - if (! has_unique_name(subel)) - DBUG_RETURN(subel->partition_name); - } - } - } - DBUG_RETURN(NULL); -} - - -/* Check that the partition/subpartition is setup to use the correct storage engine SYNOPSIS @@ -587,14 +1143,12 @@ static bool check_engine_condition(partition_element *p_elem, Current check verifies only that all handlers are the same. Later this check will be more sophisticated. (specified partition handler ) specified table handler - (NDB, NDB) NDB OK (MYISAM, MYISAM) - OK (MYISAM, -) - NOT OK (MYISAM, -) MYISAM OK (- , MYISAM) - NOT OK (- , -) MYISAM OK (-,-) - OK - (NDB, MYISAM) * NOT OK */ bool partition_info::check_engine_mix(handlerton *engine_type, @@ -703,8 +1257,8 @@ bool partition_info::check_range_constants(THD *thd) part_column_list_val *UNINIT_VAR(current_largest_col_val); uint num_column_values= part_field_list.elements; uint size_entries= sizeof(part_column_list_val) * num_column_values; - range_col_array= (part_column_list_val*)sql_calloc(num_parts * - size_entries); + range_col_array= (part_column_list_val*) thd->calloc(num_parts * + size_entries); if (unlikely(range_col_array == NULL)) { mem_alloc_error(num_parts * size_entries); @@ -741,7 +1295,7 @@ bool partition_info::check_range_constants(THD *thd) longlong part_range_value; bool signed_flag= !part_expr->unsigned_flag; - range_int_array= (longlong*)sql_alloc(num_parts * sizeof(longlong)); + range_int_array= (longlong*) thd->alloc(num_parts * sizeof(longlong)); if (unlikely(range_int_array == NULL)) { mem_alloc_error(num_parts * sizeof(longlong)); @@ -955,7 +1509,7 @@ bool partition_info::check_list_constants(THD *thd) size_entries= column_list ? (num_column_values * sizeof(part_column_list_val)) : sizeof(LIST_PART_ENTRY); - ptr= sql_calloc((num_list_values+1) * size_entries); + ptr= thd->calloc((num_list_values+1) * size_entries); if (unlikely(ptr == NULL)) { mem_alloc_error(num_list_values * size_entries); @@ -1051,17 +1605,17 @@ end: */ static void warn_if_dir_in_part_elem(THD *thd, partition_element *part_elem) { -#ifdef HAVE_READLINK - if (!my_use_symdir || (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)) -#endif + if (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE) { if (part_elem->data_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, + ER_THD(thd, WARN_OPTION_IGNORED), "DATA DIRECTORY"); if (part_elem->index_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, + ER_THD(thd, WARN_OPTION_IGNORED), "INDEX DIRECTORY"); part_elem->data_file_name= part_elem->index_file_name= NULL; } @@ -1193,12 +1747,12 @@ bool partition_info::check_partition_info(THD *thd, handlerton **eng_type, } if (part_field_list.elements > 0 && - (same_name= has_unique_fields())) + (same_name= find_duplicate_field())) { my_error(ER_SAME_NAME_PARTITION_FIELD, MYF(0), same_name); goto end; } - if ((same_name= has_unique_names())) + if ((same_name= find_duplicate_name())) { my_error(ER_SAME_NAME_PARTITION, MYF(0), same_name); goto end; @@ -1341,16 +1895,16 @@ void partition_info::print_no_partition_found(TABLE *table_arg, myf errflag) char buf[100]; char *buf_ptr= (char*)&buf; TABLE_LIST table_list; + THD *thd= current_thd; - bzero(&table_list, sizeof(table_list)); + table_list.reset(); table_list.db= table_arg->s->db.str; table_list.table_name= table_arg->s->table_name.str; - if (check_single_table_access(current_thd, - SELECT_ACL, &table_list, TRUE)) + if (check_single_table_access(thd, SELECT_ACL, &table_list, TRUE)) { my_message(ER_NO_PARTITION_FOR_GIVEN_VALUE, - ER(ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT), errflag); + ER_THD(thd, ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT), errflag); } else { @@ -1432,12 +1986,12 @@ bool partition_info::check_partition_field_length() for (i= 0; i < num_part_fields; i++) store_length+= get_partition_field_store_length(part_field_array[i]); - if (store_length > MAX_KEY_LENGTH) + if (store_length > MAX_DATA_LENGTH_FOR_KEY) DBUG_RETURN(TRUE); store_length= 0; for (i= 0; i < num_subpart_fields; i++) store_length+= get_partition_field_store_length(subpart_field_array[i]); - if (store_length > MAX_KEY_LENGTH) + if (store_length > MAX_DATA_LENGTH_FOR_KEY) DBUG_RETURN(TRUE); DBUG_RETURN(FALSE); } @@ -1459,7 +2013,7 @@ bool partition_info::check_partition_field_length() the binary collation. */ -bool partition_info::set_up_charset_field_preps() +bool partition_info::set_up_charset_field_preps(THD *thd) { Field *field, **ptr; uchar **char_ptrs; @@ -1485,14 +2039,14 @@ bool partition_info::set_up_charset_field_preps() } } size= tot_part_fields * sizeof(char*); - if (!(char_ptrs= (uchar**)sql_calloc(size))) + if (!(char_ptrs= (uchar**)thd->calloc(size))) goto error; part_field_buffers= char_ptrs; - if (!(char_ptrs= (uchar**)sql_calloc(size))) + if (!(char_ptrs= (uchar**)thd->calloc(size))) goto error; restore_part_field_ptrs= char_ptrs; size= (tot_part_fields + 1) * sizeof(Field*); - if (!(char_ptrs= (uchar**)sql_alloc(size))) + if (!(char_ptrs= (uchar**)thd->alloc(size))) goto error; part_charset_field_array= (Field**)char_ptrs; ptr= part_field_array; @@ -1503,7 +2057,7 @@ bool partition_info::set_up_charset_field_preps() { uchar *field_buf; size= field->pack_length(); - if (!(field_buf= (uchar*) sql_calloc(size))) + if (!(field_buf= (uchar*) thd->calloc(size))) goto error; part_charset_field_array[i]= field; part_field_buffers[i++]= field_buf; @@ -1525,27 +2079,26 @@ bool partition_info::set_up_charset_field_preps() } } size= tot_subpart_fields * sizeof(char*); - if (!(char_ptrs= (uchar**) sql_calloc(size))) + if (!(char_ptrs= (uchar**) thd->calloc(size))) goto error; subpart_field_buffers= char_ptrs; - if (!(char_ptrs= (uchar**) sql_calloc(size))) + if (!(char_ptrs= (uchar**) thd->calloc(size))) goto error; restore_subpart_field_ptrs= char_ptrs; size= (tot_subpart_fields + 1) * sizeof(Field*); - if (!(char_ptrs= (uchar**) sql_alloc(size))) + if (!(char_ptrs= (uchar**) thd->alloc(size))) goto error; subpart_charset_field_array= (Field**)char_ptrs; ptr= subpart_field_array; i= 0; while ((field= *(ptr++))) { - uchar *field_buf; - LINT_INIT(field_buf); + uchar *UNINIT_VAR(field_buf); if (!field_is_partition_charset(field)) continue; size= field->pack_length(); - if (!(field_buf= (uchar*) sql_calloc(size))) + if (!(field_buf= (uchar*) thd->calloc(size))) goto error; subpart_charset_field_array[i]= field; subpart_field_buffers[i++]= field_buf; @@ -1587,29 +2140,21 @@ bool check_partition_dirs(partition_info *part_info) partition_element *subpart_elem; while ((subpart_elem= sub_it++)) { - if (test_if_data_home_dir(subpart_elem->data_file_name)) - goto dd_err; - if (test_if_data_home_dir(subpart_elem->index_file_name)) - goto id_err; + if (error_if_data_home_dir(subpart_elem->data_file_name, + "DATA DIRECTORY") || + error_if_data_home_dir(subpart_elem->index_file_name, + "INDEX DIRECTORY")) + return 1; } } else { - if (test_if_data_home_dir(part_elem->data_file_name)) - goto dd_err; - if (test_if_data_home_dir(part_elem->index_file_name)) - goto id_err; + if (error_if_data_home_dir(part_elem->data_file_name, "DATA DIRECTORY") || + error_if_data_home_dir(part_elem->index_file_name, "INDEX DIRECTORY")) + return 1; } } return 0; - -dd_err: - my_error(ER_WRONG_ARGUMENTS,MYF(0),"DATA DIRECTORY"); - return 1; - -id_err: - my_error(ER_WRONG_ARGUMENTS,MYF(0),"INDEX DIRECTORY"); - return 1; } @@ -1658,6 +2203,71 @@ void partition_info::report_part_expr_error(bool use_subpart_expr) } +/** + Check if fields are in the partitioning expression. + + @param fields List of Items (fields) + + @return True if any field in the fields list is used by a partitioning expr. + @retval true At least one field in the field list is found. + @retval false No field is within any partitioning expression. +*/ + +bool partition_info::is_field_in_part_expr(List<Item> &fields) +{ + List_iterator<Item> it(fields); + Item *item; + Item_field *field; + DBUG_ENTER("is_fields_in_part_expr"); + while ((item= it++)) + { + field= item->field_for_view_update(); + DBUG_ASSERT(field->field->table == table); + if (bitmap_is_set(&full_part_field_set, field->field->field_index)) + DBUG_RETURN(true); + } + DBUG_RETURN(false); +} + + +/** + Check if all partitioning fields are included. +*/ + +bool partition_info::is_full_part_expr_in_fields(List<Item> &fields) +{ + Field **part_field= full_part_field_array; + DBUG_ASSERT(*part_field); + DBUG_ENTER("is_full_part_expr_in_fields"); + /* + It is very seldom many fields in full_part_field_array, so it is OK + to loop over all of them instead of creating a bitmap fields argument + to compare with. + */ + do + { + List_iterator<Item> it(fields); + Item *item; + Item_field *field; + bool found= false; + + while ((item= it++)) + { + field= item->field_for_view_update(); + DBUG_ASSERT(field->field->table == table); + if (*part_field == field->field) + { + found= true; + break; + } + } + if (!found) + DBUG_RETURN(false); + } while (*(++part_field)); + DBUG_RETURN(true); +} + + /* Create a new column value in current list with maxvalue Called from parser @@ -1669,12 +2279,12 @@ void partition_info::report_part_expr_error(bool use_subpart_expr) FALSE Success */ -int partition_info::add_max_value() +int partition_info::add_max_value(THD *thd) { DBUG_ENTER("partition_info::add_max_value"); part_column_list_val *col_val; - if (!(col_val= add_column_value())) + if (!(col_val= add_column_value(thd))) { DBUG_RETURN(TRUE); } @@ -1694,7 +2304,7 @@ int partition_info::add_max_value() 0 Memory allocation failure */ -part_column_list_val *partition_info::add_column_value() +part_column_list_val *partition_info::add_column_value(THD *thd) { uint max_val= num_columns ? num_columns : MAX_REF_PARTS; DBUG_ENTER("add_column_value"); @@ -1716,9 +2326,9 @@ part_column_list_val *partition_info::add_column_value() into the structure used for 1 column. After this we call ourselves recursively which should always succeed. */ - if (!reorganize_into_single_field_col_val()) + if (!reorganize_into_single_field_col_val(thd)) { - DBUG_RETURN(add_column_value()); + DBUG_RETURN(add_column_value(thd)); } DBUG_RETURN(NULL); } @@ -1799,7 +2409,7 @@ bool partition_info::add_column_list_value(THD *thd, Item *item) if (part_type == LIST_PARTITION && num_columns == 1U) { - if (init_column_part()) + if (init_column_part(thd)) { DBUG_RETURN(TRUE); } @@ -1828,7 +2438,7 @@ bool partition_info::add_column_list_value(THD *thd, Item *item) } thd->where= save_where; - if (!(col_val= add_column_value())) + if (!(col_val= add_column_value(thd))) { DBUG_RETURN(TRUE); } @@ -1849,7 +2459,7 @@ bool partition_info::add_column_list_value(THD *thd, Item *item) TRUE Failure FALSE Success */ -bool partition_info::init_column_part() +bool partition_info::init_column_part(THD *thd) { partition_element *p_elem= curr_part_elem; part_column_list_val *col_val_array; @@ -1858,8 +2468,8 @@ bool partition_info::init_column_part() DBUG_ENTER("partition_info::init_column_part"); if (!(list_val= - (part_elem_value*)sql_calloc(sizeof(part_elem_value))) || - p_elem->list_val_list.push_back(list_val)) + (part_elem_value*) thd->calloc(sizeof(part_elem_value))) || + p_elem->list_val_list.push_back(list_val, thd->mem_root)) { mem_alloc_error(sizeof(part_elem_value)); DBUG_RETURN(TRUE); @@ -1869,8 +2479,8 @@ bool partition_info::init_column_part() else loc_num_columns= MAX_REF_PARTS; if (!(col_val_array= - (part_column_list_val*)sql_calloc(loc_num_columns * - sizeof(part_column_list_val)))) + (part_column_list_val*) thd->calloc(loc_num_columns * + sizeof(part_column_list_val)))) { mem_alloc_error(loc_num_columns * sizeof(part_elem_value)); DBUG_RETURN(TRUE); @@ -1903,7 +2513,8 @@ bool partition_info::init_column_part() TRUE Failure FALSE Success */ -int partition_info::reorganize_into_single_field_col_val() + +int partition_info::reorganize_into_single_field_col_val(THD *thd) { part_column_list_val *col_val, *new_col_val; part_elem_value *val= curr_list_val; @@ -1919,11 +2530,11 @@ int partition_info::reorganize_into_single_field_col_val() { col_val= &val->col_val_array[i]; DBUG_ASSERT(part_type == LIST_PARTITION); - if (init_column_part()) + if (init_column_part(thd)) { DBUG_RETURN(TRUE); } - if (!(new_col_val= add_column_value())) + if (!(new_col_val= add_column_value(thd))) { DBUG_RETURN(TRUE); } @@ -2116,14 +2727,13 @@ bool partition_info::fix_column_value_functions(THD *thd, } thd->got_warning= save_got_warning; thd->variables.sql_mode= save_sql_mode; - if (!(val_ptr= (uchar*) sql_calloc(len))) + if (!(val_ptr= (uchar*) thd->memdup(field->ptr, len))) { mem_alloc_error(len); result= TRUE; goto end; } col_val->column_value= val_ptr; - memcpy(val_ptr, field->ptr, len); } } col_val->fixed= 2; @@ -2133,26 +2743,11 @@ end: } -bool partition_info::error_if_requires_values() const -{ - switch (part_type) { - case NOT_A_PARTITION: - case HASH_PARTITION: - break; - case RANGE_PARTITION: - my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0), "RANGE", "LESS THAN"); - return true; - case LIST_PARTITION: - my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0), "LIST", "IN"); - return true; - } - return false; -} - +/** + Fix partition data from parser. -/* - The parser generates generic data structures, we need to set them up - as the rest of the code expects to find them. This is in reality part + @details The parser generates generic data structures, we need to set them + up as the rest of the code expects to find them. This is in reality part of the syntax check of the parser code. It is necessary to call this function in the case of a CREATE TABLE @@ -2184,16 +2779,14 @@ bool partition_info::error_if_requires_values() const and number of elements are in synch with each other. So only partitioning using functions need to be set-up to their data structures. - SYNOPSIS - fix_parser_data() - thd Thread object + @param thd Thread object - RETURN VALUES - TRUE Failure - FALSE Success + @return Operation status + @retval TRUE Failure + @retval FALSE Success */ -int partition_info::fix_parser_data(THD *thd) +bool partition_info::fix_parser_data(THD *thd) { List_iterator<partition_element> it(partitions); partition_element *part_elem; @@ -2549,6 +3142,23 @@ void partition_info::print_debug(const char *str, uint *value) DBUG_PRINT("info", ("parser: %s", str)); DBUG_VOID_RETURN; } + +bool partition_info::field_in_partition_expr(Field *field) const +{ + uint i; + for (i= 0; i < num_part_fields; i++) + { + if (field->eq(part_field_array[i])) + return TRUE; + } + for (i= 0; i < num_subpart_fields; i++) + { + if (field->eq(subpart_field_array[i])) + return TRUE; + } + return FALSE; +} + #else /* WITH_PARTITION_STORAGE_ENGINE */ /* For builds without partitioning we need to define these functions @@ -2556,7 +3166,7 @@ void partition_info::print_debug(const char *str, uint *value) remove code parts using ifdef, but the code parts cannot be called so we simply need to add empty functions to make the linker happy. */ -part_column_list_val *partition_info::add_column_value() +part_column_list_val *partition_info::add_column_value(THD *thd) { return NULL; } @@ -2571,12 +3181,12 @@ bool partition_info::set_part_expr(char *start_token, Item *item_ptr, return FALSE; } -int partition_info::reorganize_into_single_field_col_val() +int partition_info::reorganize_into_single_field_col_val(THD *thd) { return 0; } -bool partition_info::init_column_part() +bool partition_info::init_column_part(THD *thd) { return FALSE; } @@ -2585,7 +3195,7 @@ bool partition_info::add_column_list_value(THD *thd, Item *item) { return FALSE; } -int partition_info::add_max_value() +int partition_info::add_max_value(THD *thd) { return 0; } @@ -2594,4 +3204,25 @@ void partition_info::print_debug(const char *str, uint *value) { } +bool check_partition_dirs(partition_info *part_info) +{ + return 0; +} + #endif /* WITH_PARTITION_STORAGE_ENGINE */ + +bool partition_info::error_if_requires_values() const +{ + switch (part_type) { + case NOT_A_PARTITION: + case HASH_PARTITION: + break; + case RANGE_PARTITION: + my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0), "RANGE", "LESS THAN"); + return true; + case LIST_PARTITION: + my_error(ER_PARTITION_REQUIRES_VALUES_ERROR, MYF(0), "LIST", "IN"); + return true; + } + return false; +} diff --git a/sql/partition_info.h b/sql/partition_info.h index 779948eddc9..0f38c87dd4e 100644 --- a/sql/partition_info.h +++ b/sql/partition_info.h @@ -20,10 +20,11 @@ #pragma interface /* gcc class implementation */ #endif +#include "sql_class.h" #include "partition_element.h" class partition_info; - +struct TABLE_LIST; /* Some function typedefs */ typedef int (*get_part_id_func)(partition_info *part_info, uint32 *part_id, @@ -111,14 +112,30 @@ public: struct st_ddl_log_memory_entry *frm_log_entry; /* - A bitmap of partitions used by the current query. + Bitmaps of partitions used by the current query. + * read_partitions - partitions to be used for reading. + * lock_partitions - partitions that must be locked (read or write). + Usually read_partitions is the same set as lock_partitions, but + in case of UPDATE the WHERE clause can limit the read_partitions set, + but not neccesarily the lock_partitions set. Usage pattern: - * The handler->extra(HA_EXTRA_RESET) call at query start/end sets all - partitions to be unused. - * Before index/rnd_init(), partition pruning code sets the bits for used - partitions. + * Initialized in ha_partition::open(). + * read+lock_partitions is set according to explicit PARTITION, + WL#5217, in open_and_lock_tables(). + * Bits in read_partitions can be cleared in prune_partitions() + in the optimizing step. + (WL#4443 is about allowing prune_partitions() to affect lock_partitions + and be done before locking too). + * When the partition enabled handler get an external_lock call it locks + all partitions in lock_partitions (and remembers which partitions it + locked, so that it can unlock them later). In case of LOCK TABLES it will + lock all partitions, and keep them locked while lock_partitions can + change for each statement under LOCK TABLES. + * Freed at the same time item_free_list is freed. */ - MY_BITMAP used_partitions; + MY_BITMAP read_partitions; + MY_BITMAP lock_partitions; + bool bitmaps_are_initialized; union { longlong *range_int_array; @@ -151,12 +168,13 @@ public: char *part_func_string; char *subpart_func_string; - partition_element *curr_part_elem; - partition_element *current_partition; + partition_element *curr_part_elem; // part or sub part + partition_element *current_partition; // partition part_elem_value *curr_list_val; uint curr_list_object; uint num_columns; + TABLE *table; /* These key_map's are used for Partitioning to enable quick decisions on whether we can derive more information about which partition to @@ -175,9 +193,7 @@ public: uint num_parts; uint num_subparts; - uint count_curr_subparts; - - uint part_error_code; + uint count_curr_subparts; // used during parsing uint num_list_values; @@ -206,20 +222,30 @@ public: }; enum_key_algorithm key_algorithm; + /* Only the number of partitions defined (uses default names and options). */ bool use_default_partitions; bool use_default_num_partitions; + /* Only the number of subpartitions defined (uses default names etc.). */ bool use_default_subpartitions; bool use_default_num_subpartitions; bool default_partitions_setup; bool defined_max_value; - bool list_of_part_fields; - bool list_of_subpart_fields; - bool linear_hash_ind; + bool list_of_part_fields; // KEY or COLUMNS PARTITIONING + bool list_of_subpart_fields; // KEY SUBPARTITIONING + bool linear_hash_ind; // LINEAR HASH/KEY bool fixed; bool is_auto_partitioned; - bool from_openfrm; bool has_null_value; - bool column_list; + bool column_list; // COLUMNS PARTITIONING, 5.5+ + /** + True if pruning has been completed and can not be pruned any further, + even if there are subqueries or stored programs in the condition. + + Some times it is needed to run prune_partitions() a second time to prune + read partitions after tables are locked, when subquery and + stored functions might have been evaluated. + */ + bool is_pruning_completed; partition_info() : get_partition_id(NULL), get_part_partition_id(NULL), @@ -232,17 +258,18 @@ public: restore_part_field_ptrs(NULL), restore_subpart_field_ptrs(NULL), part_expr(NULL), subpart_expr(NULL), item_free_list(NULL), first_log_entry(NULL), exec_log_entry(NULL), frm_log_entry(NULL), + bitmaps_are_initialized(FALSE), list_array(NULL), err_value(0), part_info_string(NULL), part_func_string(NULL), subpart_func_string(NULL), curr_part_elem(NULL), current_partition(NULL), - curr_list_object(0), num_columns(0), + curr_list_object(0), num_columns(0), table(NULL), default_engine_type(NULL), part_type(NOT_A_PARTITION), subpart_type(NOT_A_PARTITION), part_info_len(0), part_func_len(0), subpart_func_len(0), num_parts(0), num_subparts(0), - count_curr_subparts(0), part_error_code(0), + count_curr_subparts(0), num_list_values(0), num_part_fields(0), num_subpart_fields(0), num_full_part_fields(0), has_null_part_id(0), linear_hash_mask(0), key_algorithm(KEY_ALGORITHM_NONE), @@ -251,8 +278,8 @@ public: default_partitions_setup(FALSE), defined_max_value(FALSE), list_of_part_fields(FALSE), list_of_subpart_fields(FALSE), linear_hash_ind(FALSE), fixed(FALSE), - is_auto_partitioned(FALSE), from_openfrm(FALSE), - has_null_value(FALSE), column_list(FALSE) + is_auto_partitioned(FALSE), + has_null_value(FALSE), column_list(FALSE), is_pruning_completed(false) { all_fields_in_PF.clear_all(); all_fields_in_PPF.clear_all(); @@ -265,7 +292,9 @@ public: } ~partition_info() {} - partition_info *get_clone(); + partition_info *get_clone(THD *thd); + bool set_named_partition_bitmap(const char *part_name, uint length); + bool set_partition_bitmaps(TABLE_LIST *table_list); /* Answers the question if subpartitioning is used for a certain table */ bool is_sub_partitioned() { @@ -280,8 +309,8 @@ public: bool set_up_defaults_for_partitioning(handler *file, HA_CREATE_INFO *info, uint start_no); - char *has_unique_fields(); - char *has_unique_names(); + char *find_duplicate_field(); + char *find_duplicate_name(); bool check_engine_mix(handlerton *engine_type, bool default_engine); bool check_range_constants(THD *thd); bool check_list_constants(THD *thd); @@ -298,20 +327,46 @@ public: bool fix_column_value_functions(THD *thd, part_elem_value *val, uint part_id); - int fix_parser_data(THD *thd); - int add_max_value(); + bool fix_parser_data(THD *thd); + int add_max_value(THD *thd); void init_col_val(part_column_list_val *col_val, Item *item); - int reorganize_into_single_field_col_val(); - part_column_list_val *add_column_value(); + int reorganize_into_single_field_col_val(THD *thd); + part_column_list_val *add_column_value(THD *thd); bool set_part_expr(char *start_token, Item *item_ptr, char *end_token, bool is_subpart); static int compare_column_values(const void *a, const void *b); - bool set_up_charset_field_preps(); + bool set_up_charset_field_preps(THD *thd); bool check_partition_field_length(); - bool init_column_part(); + bool init_column_part(THD *thd); bool add_column_list_value(THD *thd, Item *item); void set_show_version_string(String *packet); + partition_element *get_part_elem(const char *partition_name, char *file_name, + size_t file_name_size, uint32 *part_id); void report_part_expr_error(bool use_subpart_expr); + bool set_used_partition(List<Item> &fields, + List<Item> &values, + COPY_INFO &info, + bool copy_default_values, + MY_BITMAP *used_partitions); + /** + PRUNE_NO - Unable to prune. + PRUNE_DEFAULTS - Partitioning field is only set to + DEFAULT values, only need to check + pruning for one row where the DEFAULTS + values are set. + PRUNE_YES - Pruning is possible, calculate the used partition set + by evaluate the partition_id on row by row basis. + */ + enum enum_can_prune {PRUNE_NO=0, PRUNE_DEFAULTS, PRUNE_YES}; + bool can_prune_insert(THD *thd, + enum_duplicates duplic, + COPY_INFO &update, + List<Item> &update_fields, + List<Item> &fields, + bool empty_values, + enum_can_prune *can_prune_partitions, + bool *prune_needs_default_values, + MY_BITMAP *used_partitions); bool has_same_partitioning(partition_info *new_part_info); bool error_if_requires_values() const; private: @@ -321,8 +376,15 @@ private: bool set_up_default_subpartitions(handler *file, HA_CREATE_INFO *info); char *create_default_partition_names(uint part_no, uint num_parts, uint start_no); - char *create_subpartition_name(uint subpart_no, const char *part_name); + char *create_default_subpartition_name(uint subpart_no, + const char *part_name); + bool prune_partition_bitmaps(TABLE_LIST *table_list); + bool add_named_partition(const char *part_name, uint length); + bool is_field_in_part_expr(List<Item> &fields); + bool is_full_part_expr_in_fields(List<Item> &fields); +public: bool has_unique_name(partition_element *element); + bool field_in_partition_expr(Field *field) const; }; uint32 get_next_partition_id_range(struct st_partition_iter* part_iter); diff --git a/sql/password.c b/sql/password.c index 947620ddf7a..02e4a0c37c7 100644 --- a/sql/password.c +++ b/sql/password.c @@ -38,7 +38,7 @@ The new authentication is performed in following manner: - SERVER: public_seed=create_random_string() + SERVER: public_seed=thd_create_random_password() send(public_seed) CLIENT: recv(public_seed) @@ -60,12 +60,12 @@ *****************************************************************************/ -#include <password.h> #include <my_global.h> #include <my_sys.h> #include <m_string.h> -#include <sha1.h> -#include "mysql.h" +#include <password.h> +#include <mysql.h> +#include <my_rnd.h> /************ MySQL 3.23-4.0 authentication routines: untouched ***********/ @@ -278,14 +278,14 @@ void make_password_from_salt_323(char *to, const ulong *salt) **************** MySQL 4.1.1 authentication routines ************* */ -/* - Generate string of printable random characters of requested length - SYNOPSIS - create_random_string() - to OUT buffer for generation; must be at least length+1 bytes - long; result string is always null-terminated - length IN how many random characters to put in buffer - rand_st INOUT structure used for number generation +#if MYSQL_VERSION_ID < 0x100200 +/** + Generate string of printable random characters of requested length. + + @param to[out] Buffer for generation; must be at least length+1 bytes + long; result string is always null-terminated + length[in] How many random characters to put in buffer + rand_st Structure used for number generation */ void create_random_string(char *to, uint length, @@ -297,6 +297,9 @@ void create_random_string(char *to, uint length, *to= (char) (my_rnd(rand_st)*94+33); *to= '\0'; } +#else +#error +#endif /* Character to use as version identifier for version 4.1 */ @@ -372,6 +375,30 @@ my_crypt(char *to, const uchar *s1, const uchar *s2, uint len) } +/** + Compute two stage SHA1 hash of the password : + + hash_stage1=sha1("password") + hash_stage2=sha1(hash_stage1) + + @param password [IN] Password string. + @param pass_len [IN] Length of the password. + @param hash_stage1 [OUT] sha1(password) + @param hash_stage2 [OUT] sha1(hash_stage1) +*/ + +inline static +void compute_two_stage_sha1_hash(const char *password, size_t pass_len, + uint8 *hash_stage1, uint8 *hash_stage2) +{ + /* Stage 1: hash password */ + my_sha1(hash_stage1, password, pass_len); + + /* Stage 2 : hash first stage's output. */ + my_sha1(hash_stage2, (const char *) hash_stage1, MY_SHA1_HASH_SIZE); +} + + /* MySQL 4.1.1 password hashing: SHA conversion (see RFC 2289, 3174) twice applied to the password string, and then produced octet sequence is @@ -380,7 +407,7 @@ my_crypt(char *to, const uchar *s1, const uchar *s2, uint len) is stored in the database. SYNOPSIS my_make_scrambled_password() - buf OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string + buf OUT buffer of size 2*MY_SHA1_HASH_SIZE + 2 to store hex string password IN password string pass_len IN length of password string */ @@ -388,21 +415,14 @@ my_crypt(char *to, const uchar *s1, const uchar *s2, uint len) void my_make_scrambled_password(char *to, const char *password, size_t pass_len) { - SHA1_CONTEXT sha1_context; - uint8 hash_stage2[SHA1_HASH_SIZE]; - - mysql_sha1_reset(&sha1_context); - /* stage 1: hash password */ - mysql_sha1_input(&sha1_context, (uint8 *) password, (uint) pass_len); - mysql_sha1_result(&sha1_context, (uint8 *) to); - /* stage 2: hash stage1 output */ - mysql_sha1_reset(&sha1_context); - mysql_sha1_input(&sha1_context, (uint8 *) to, SHA1_HASH_SIZE); - /* separate buffer is used to pass 'to' in octet2hex */ - mysql_sha1_result(&sha1_context, hash_stage2); + uint8 hash_stage2[MY_SHA1_HASH_SIZE]; + + /* Two stage SHA1 hash of the password. */ + compute_two_stage_sha1_hash(password, pass_len, (uint8 *) to, hash_stage2); + /* convert hash_stage2 to hex string */ *to++= PVERSION41_CHAR; - octet2hex(to, (const char*) hash_stage2, SHA1_HASH_SIZE); + octet2hex(to, (const char*) hash_stage2, MY_SHA1_HASH_SIZE); } @@ -413,7 +433,7 @@ void my_make_scrambled_password(char *to, const char *password, avoid strlen(). SYNOPSIS make_scrambled_password() - buf OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string + buf OUT buffer of size 2*MY_SHA1_HASH_SIZE + 2 to store hex string password IN NULL-terminated password string */ @@ -425,7 +445,7 @@ void make_scrambled_password(char *to, const char *password) /* Produce an obscure octet sequence from password and random - string, recieved from the server. This sequence corresponds to the + string, received from the server. This sequence corresponds to the password, but password can not be easily restored from it. The sequence is then sent to the server for validation. Trailing zero is not stored in the buf as it is not needed. @@ -434,7 +454,7 @@ void make_scrambled_password(char *to, const char *password) SYNOPSIS scramble() buf OUT store scrambled string here. The buf must be at least - SHA1_HASH_SIZE bytes long. + MY_SHA1_HASH_SIZE bytes long. message IN random message, must be exactly SCRAMBLE_LENGTH long and NULL-terminated. password IN users' password @@ -443,33 +463,25 @@ void make_scrambled_password(char *to, const char *password) void scramble(char *to, const char *message, const char *password) { - SHA1_CONTEXT sha1_context; - uint8 hash_stage1[SHA1_HASH_SIZE]; - uint8 hash_stage2[SHA1_HASH_SIZE]; - - mysql_sha1_reset(&sha1_context); - /* stage 1: hash password */ - mysql_sha1_input(&sha1_context, (uint8 *) password, (uint) strlen(password)); - mysql_sha1_result(&sha1_context, hash_stage1); - /* stage 2: hash stage 1; note that hash_stage2 is stored in the database */ - mysql_sha1_reset(&sha1_context); - mysql_sha1_input(&sha1_context, hash_stage1, SHA1_HASH_SIZE); - mysql_sha1_result(&sha1_context, hash_stage2); + uint8 hash_stage1[MY_SHA1_HASH_SIZE]; + uint8 hash_stage2[MY_SHA1_HASH_SIZE]; + + /* Two stage SHA1 hash of the password. */ + compute_two_stage_sha1_hash(password, strlen(password), hash_stage1, + hash_stage2); + /* create crypt string as sha1(message, hash_stage2) */; - mysql_sha1_reset(&sha1_context); - mysql_sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH); - mysql_sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE); - /* xor allows 'from' and 'to' overlap: lets take advantage of it */ - mysql_sha1_result(&sha1_context, (uint8 *) to); + my_sha1_multi((uint8 *) to, message, SCRAMBLE_LENGTH, + (const char *) hash_stage2, MY_SHA1_HASH_SIZE, NULL); my_crypt(to, (const uchar *) to, hash_stage1, SCRAMBLE_LENGTH); } /* Check that scrambled message corresponds to the password; the function - is used by server to check that recieved reply is authentic. + is used by server to check that received reply is authentic. This function does not check lengths of given strings: message must be - null-terminated, reply and hash_stage2 must be at least SHA1_HASH_SIZE + null-terminated, reply and hash_stage2 must be at least MY_SHA1_HASH_SIZE long (if not, something fishy is going on). SYNOPSIS check_scramble() @@ -489,50 +501,47 @@ my_bool check_scramble(const uchar *scramble_arg, const char *message, const uint8 *hash_stage2) { - SHA1_CONTEXT sha1_context; - uint8 buf[SHA1_HASH_SIZE]; - uint8 hash_stage2_reassured[SHA1_HASH_SIZE]; + uint8 buf[MY_SHA1_HASH_SIZE]; + uint8 hash_stage2_reassured[MY_SHA1_HASH_SIZE]; - mysql_sha1_reset(&sha1_context); /* create key to encrypt scramble */ - mysql_sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH); - mysql_sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE); - mysql_sha1_result(&sha1_context, buf); + my_sha1_multi(buf, message, SCRAMBLE_LENGTH, + (const char *) hash_stage2, MY_SHA1_HASH_SIZE, NULL); /* encrypt scramble */ - my_crypt((char *) buf, buf, scramble_arg, SCRAMBLE_LENGTH); + my_crypt((char *) buf, buf, scramble_arg, SCRAMBLE_LENGTH); + /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */ - mysql_sha1_reset(&sha1_context); - mysql_sha1_input(&sha1_context, buf, SHA1_HASH_SIZE); - mysql_sha1_result(&sha1_context, hash_stage2_reassured); - return test(memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE)); -} + my_sha1(hash_stage2_reassured, (const char *) buf, MY_SHA1_HASH_SIZE); + return MY_TEST(memcmp(hash_stage2, hash_stage2_reassured, MY_SHA1_HASH_SIZE)); +} /* Convert scrambled password from asciiz hex string to binary form. SYNOPSIS get_salt_from_password() - res OUT buf to hold password. Must be at least SHA1_HASH_SIZE + res OUT buf to hold password. Must be at least MY_SHA1_HASH_SIZE bytes long. password IN 4.1.1 version value of user.password */ void get_salt_from_password(uint8 *hash_stage2, const char *password) { - hex2octet(hash_stage2, password+1 /* skip '*' */, SHA1_HASH_SIZE * 2); + hex2octet(hash_stage2, password+1 /* skip '*' */, MY_SHA1_HASH_SIZE * 2); } /* Convert scrambled password from binary form to asciiz hex string. SYNOPSIS make_password_from_salt() - to OUT store resulting string here, 2*SHA1_HASH_SIZE+2 bytes + to OUT store resulting string here, 2*MY_SHA1_HASH_SIZE+2 bytes salt IN password in salt format */ void make_password_from_salt(char *to, const uint8 *hash_stage2) { *to++= PVERSION41_CHAR; - octet2hex(to, (const char*) hash_stage2, SHA1_HASH_SIZE); + octet2hex(to, (const char*) hash_stage2, MY_SHA1_HASH_SIZE); } + diff --git a/sql/plistsort.c b/sql/plistsort.c index 71d287e7b45..99657410fe0 100644 --- a/sql/plistsort.c +++ b/sql/plistsort.c @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* diff --git a/sql/procedure.cc b/sql/procedure.cc index bdaced20586..8f9d6c0a7f3 100644 --- a/sql/procedure.cc +++ b/sql/procedure.cc @@ -20,6 +20,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include "procedure.h" #include "sql_analyse.h" // Includes procedure diff --git a/sql/procedure.h b/sql/procedure.h index 6870b97de57..a46e8cfc137 100644 --- a/sql/procedure.h +++ b/sql/procedure.h @@ -38,7 +38,7 @@ class Item_proc :public Item { public: - Item_proc(const char *name_par): Item() + Item_proc(THD *thd, const char *name_par): Item(thd) { this->name=(char*) name_par; } @@ -63,7 +63,8 @@ class Item_proc_real :public Item_proc { double value; public: - Item_proc_real(const char *name_par,uint dec) : Item_proc(name_par) + Item_proc_real(THD *thd, const char *name_par, uint dec): + Item_proc(thd, name_par) { decimals=dec; max_length=float_length(dec); } @@ -92,7 +93,7 @@ class Item_proc_int :public Item_proc { longlong value; public: - Item_proc_int(const char *name_par) :Item_proc(name_par) + Item_proc_int(THD *thd, const char *name_par): Item_proc(thd, name_par) { max_length=11; } enum Item_result result_type () const { return INT_RESULT; } enum_field_types field_type() const { return MYSQL_TYPE_LONGLONG; } @@ -111,8 +112,8 @@ public: class Item_proc_string :public Item_proc { public: - Item_proc_string(const char *name_par,uint length) :Item_proc(name_par) - { this->max_length=length; } + Item_proc_string(THD *thd, const char *name_par, uint length): + Item_proc(thd, name_par) { this->max_length=length; } enum Item_result result_type () const { return STRING_RESULT; } enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; } void set(double nr) { str_value.set_real(nr, 2, default_charset()); } @@ -156,7 +157,7 @@ public: virtual void add(void)=0; virtual void end_group(void)=0; virtual int send_row(List<Item> &fields)=0; - virtual bool change_columns(List<Item> &fields)=0; + virtual bool change_columns(THD *thd, List<Item> &fields)= 0; virtual void update_refs(void) {} virtual int end_of_records() { return 0; } }; diff --git a/sql/protocol.cc b/sql/protocol.cc index 8602d9131c1..9c5df04e404 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -25,8 +25,8 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "protocol.h" #include "sql_class.h" // THD #include <stdarg.h> @@ -164,14 +164,14 @@ bool net_send_error(THD *thd, uint sql_errno, const char *err, It's one case when we can push an error even though there is an OK or EOF already. */ - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); /* Abort multi-result sets */ thd->server_status&= ~SERVER_MORE_RESULTS_EXISTS; error= net_send_error_packet(thd, sql_errno, err, sqlstate); - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); DBUG_RETURN(error); } @@ -236,7 +236,7 @@ net_send_ok(THD *thd, pos+=2; /* We can only return up to 65535 warnings in two bytes */ - uint tmp= min(statement_warn_count, 65535); + uint tmp= MY_MIN(statement_warn_count, 65535); int2store(pos, tmp); pos+= 2; } @@ -245,7 +245,7 @@ net_send_ok(THD *thd, int2store(pos, server_status); pos+=2; } - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); if (message && message[0]) pos= net_store_data(pos, (uchar*) message, strlen(message)); @@ -254,7 +254,7 @@ net_send_ok(THD *thd, error= net_flush(net); - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); DBUG_PRINT("info", ("OK sent, so no more error sending allowed")); DBUG_RETURN(error); @@ -294,11 +294,11 @@ net_send_eof(THD *thd, uint server_status, uint statement_warn_count) /* Set to TRUE if no active vio, to work well in case of --init-file */ if (net->vio != 0) { - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); error= write_eof_packet(thd, net, server_status, statement_warn_count); if (!error) error= net_flush(net); - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); DBUG_PRINT("info", ("EOF sent, so no more error sending allowed")); } DBUG_RETURN(error); @@ -332,7 +332,7 @@ static bool write_eof_packet(THD *thd, NET *net, Don't send warn count during SP execution, as the warn_list is cleared between substatements, and mysqltest gets confused */ - uint tmp= min(statement_warn_count, 65535); + uint tmp= MY_MIN(statement_warn_count, 65535); buff[0]= 254; int2store(buff+1, tmp); /* @@ -373,7 +373,8 @@ bool net_send_error_packet(THD *thd, uint sql_errno, const char *err, uint error; char converted_err[MYSQL_ERRMSG_SIZE]; char buff[2+1+SQLSTATE_LENGTH+MYSQL_ERRMSG_SIZE], *pos; - + my_bool ret; + uint8 save_compress; DBUG_ENTER("send_error_packet"); if (net->vio == 0) @@ -401,8 +402,16 @@ bool net_send_error_packet(THD *thd, uint sql_errno, const char *err, /* Converted error message is always null-terminated. */ length= (uint) (strmake(pos, converted_err, MYSQL_ERRMSG_SIZE - 1) - buff); - DBUG_RETURN(net_write_command(net,(uchar) 255, (uchar*) "", 0, (uchar*) buff, - length)); + /* + Ensure that errors are not compressed. This is to ensure we can + detect out of bands error messages in the client + */ + if ((save_compress= net->compress)) + net->compress= 2; + ret= net_write_command(net,(uchar) 255, (uchar*) "", 0, (uchar*) buff, + length); + net->compress= save_compress; + DBUG_RETURN(ret); } #endif /* EMBEDDED_LIBRARY */ @@ -488,31 +497,33 @@ static uchar *net_store_length_fast(uchar *packet, uint length) void Protocol::end_statement() { + /* sanity check*/ + DBUG_ASSERT_IF_WSREP(!(WSREP(thd) && thd->wsrep_conflict_state == REPLAYING)); DBUG_ENTER("Protocol::end_statement"); - DBUG_ASSERT(! thd->stmt_da->is_sent); + DBUG_ASSERT(! thd->get_stmt_da()->is_sent()); bool error= FALSE; /* Can not be true, but do not take chances in production. */ - if (thd->stmt_da->is_sent) + if (thd->get_stmt_da()->is_sent()) DBUG_VOID_RETURN; - switch (thd->stmt_da->status()) { + switch (thd->get_stmt_da()->status()) { case Diagnostics_area::DA_ERROR: /* The query failed, send error to log and abort bootstrap. */ - error= send_error(thd->stmt_da->sql_errno(), - thd->stmt_da->message(), - thd->stmt_da->get_sqlstate()); + error= send_error(thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message(), + thd->get_stmt_da()->get_sqlstate()); break; case Diagnostics_area::DA_EOF: error= send_eof(thd->server_status, - thd->stmt_da->statement_warn_count()); + thd->get_stmt_da()->statement_warn_count()); break; case Diagnostics_area::DA_OK: error= send_ok(thd->server_status, - thd->stmt_da->statement_warn_count(), - thd->stmt_da->affected_rows(), - thd->stmt_da->last_insert_id(), - thd->stmt_da->message()); + thd->get_stmt_da()->statement_warn_count(), + thd->get_stmt_da()->affected_rows(), + thd->get_stmt_da()->last_insert_id(), + thd->get_stmt_da()->message()); break; case Diagnostics_area::DA_DISABLED: break; @@ -523,7 +534,7 @@ void Protocol::end_statement() break; } if (!error) - thd->stmt_da->is_sent= TRUE; + thd->get_stmt_da()->set_is_sent(true); DBUG_VOID_RETURN; } @@ -609,17 +620,17 @@ void net_send_progress_packet(THD *thd) *pos++= (uchar) 1; // Number of strings *pos++= (uchar) thd->progress.stage + 1; /* - We have the max() here to avoid problems if max_stage is not set, + We have the MY_MAX() here to avoid problems if max_stage is not set, which may happen during automatic repair of table */ - *pos++= (uchar) max(thd->progress.max_stage, thd->progress.stage + 1); + *pos++= (uchar) MY_MAX(thd->progress.max_stage, thd->progress.stage + 1); progress= 0; if (thd->progress.max_counter) progress= 100000ULL * thd->progress.counter / thd->progress.max_counter; int3store(pos, progress); // Between 0 & 100000 pos+= 3; pos= net_store_data(pos, (const uchar*) proc_info, - min(length, sizeof(buff)-7)); + MY_MIN(length, sizeof(buff)-7)); net_write_command(&thd->net, (uchar) 255, progress_header, sizeof(progress_header), (uchar*) buff, (uint) (pos - buff)); @@ -691,9 +702,9 @@ bool Protocol::flush() { #ifndef EMBEDDED_LIBRARY bool error; - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); error= net_flush(&thd->net); - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); return error; #else return 0; @@ -779,7 +790,7 @@ bool Protocol::send_result_set_metadata(List<Item> *list, uint flags) pos= (char*) local_packet->ptr()+local_packet->length(); *pos++= 12; // Length of packed fields /* inject a NULL to test the client */ - DBUG_EXECUTE_IF("poison_rs_fields", pos[-1]= 0xfb;); + DBUG_EXECUTE_IF("poison_rs_fields", pos[-1]= (char) 0xfb;); if (item->charset_for_protocol() == &my_charset_bin || thd_charset == NULL) { /* No conversion */ @@ -859,13 +870,13 @@ bool Protocol::send_result_set_metadata(List<Item> *list, uint flags) Send no warning information, as it will be sent at statement end. */ if (write_eof_packet(thd, &thd->net, thd->server_status, - thd->warning_info->statement_warn_count())) + thd->get_stmt_da()->current_statement_warn_count())) DBUG_RETURN(1); } DBUG_RETURN(prepare_for_send(list->elements)); err: - my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), + my_message(ER_OUT_OF_RESOURCES, ER_THD(thd, ER_OUT_OF_RESOURCES), MYF(0)); /* purecov: inspected */ DBUG_RETURN(1); /* purecov: inspected */ } @@ -1245,7 +1256,7 @@ bool Protocol_text::send_out_parameters(List<Item_param> *sp_params) continue; // It's an IN-parameter. Item_func_set_user_var *suv= - new Item_func_set_user_var(*user_var_name, item_param); + new (thd->mem_root) Item_func_set_user_var(thd, *user_var_name, item_param); /* Item_func_set_user_var is not fixed after construction, call fix_fields(). @@ -1430,7 +1441,7 @@ bool Protocol_binary::store(MYSQL_TIME *tm, int decimals) DBUG_ASSERT(decimals == AUTO_SEC_PART_DIGITS || (decimals >= 0 && decimals <= TIME_SECOND_PART_DIGITS)); if (decimals != AUTO_SEC_PART_DIGITS) - tm->second_part= sec_part_truncate(tm->second_part, decimals); + my_time_trunc(tm, decimals); int4store(pos+7, tm->second_part); if (tm->second_part) length=11; @@ -1472,7 +1483,7 @@ bool Protocol_binary::store_time(MYSQL_TIME *tm, int decimals) DBUG_ASSERT(decimals == AUTO_SEC_PART_DIGITS || (decimals >= 0 && decimals <= TIME_SECOND_PART_DIGITS)); if (decimals != AUTO_SEC_PART_DIGITS) - tm->second_part= sec_part_truncate(tm->second_part, decimals); + my_time_trunc(tm, decimals); int4store(pos+8, tm->second_part); if (tm->second_part) length=12; @@ -1517,7 +1528,7 @@ bool Protocol_binary::send_out_parameters(List<Item_param> *sp_params) if (!item_param->get_out_param_info()) continue; // It's an IN-parameter. - if (out_param_lst.push_back(item_param)) + if (out_param_lst.push_back(item_param, thd->mem_root)) return TRUE; } } diff --git a/sql/protocol.h b/sql/protocol.h index 871d6018458..2edad5663e8 100644 --- a/sql/protocol.h +++ b/sql/protocol.h @@ -33,8 +33,8 @@ typedef struct st_mysql_rows MYSQL_ROWS; class Protocol { protected: - THD *thd; String *packet; + /* Used by net_store_data() for charset conversions */ String *convert; uint field_pos; #ifndef DBUG_OFF @@ -53,6 +53,10 @@ protected: MYSQL_FIELD *next_mysql_field; MEM_ROOT *alloc; #endif + /* + The following two are low-level functions that are invoked from + higher-level store_xxx() funcs. The data is stored into this->packet. + */ bool store_string_aux(const char *from, size_t length, CHARSET_INFO *fromcs, CHARSET_INFO *tocs); @@ -66,7 +70,7 @@ protected: const char *sql_state); public: - Protocol() {} + THD *thd; Protocol(THD *thd_arg) { init(thd_arg); } virtual ~Protocol() {} void init(THD* thd_arg); @@ -134,6 +138,10 @@ public: virtual enum enum_protocol_type type()= 0; void end_statement(); + + friend int send_answer_1(Protocol *protocol, String *s1, String *s2, + String *s3); + friend int send_header_2(Protocol *protocol, bool for_category); }; @@ -142,7 +150,6 @@ public: class Protocol_text :public Protocol { public: - Protocol_text() {} Protocol_text(THD *thd_arg) :Protocol(thd_arg) {} virtual void prepare_for_resend(); virtual bool store_null(); @@ -174,7 +181,6 @@ class Protocol_binary :public Protocol private: uint bit_fields; public: - Protocol_binary() {} Protocol_binary(THD *thd_arg) :Protocol(thd_arg) {} virtual bool prepare_for_send(uint num_columns); virtual void prepare_for_resend(); @@ -205,6 +211,52 @@ public: virtual enum enum_protocol_type type() { return PROTOCOL_BINARY; }; }; + +/* + A helper for "ANALYZE $stmt" which looks a real network procotol but doesn't + write results to the network. + + At first glance, class select_send looks like a more appropriate place to + implement the "write nothing" hook. This is not true, because + - we need to evaluate the value of every item, and do it the way + select_send does it (i.e. call item->val_int() or val_real() or...) + - select_send::send_data() has some other code, like telling the storage + engine that the row can be unlocked. We want to keep that also. + as a result, "ANALYZE $stmt" uses a select_send_analyze which still uses + select_send::send_data() & co., and also uses Protocol_discard object. +*/ + +class Protocol_discard : public Protocol_text +{ +public: + Protocol_discard(THD *thd_arg) : Protocol_text(thd_arg) {} + bool write() { return 0; } + bool send_result_set_metadata(List<Item> *, uint) { return 0; } + bool send_eof(uint, uint) { return 0; } + void prepare_for_resend() { IF_DBUG(field_pos= 0,); } + + /* + Provide dummy overrides for any storage methods so that we + avoid allocating and copying of data + */ + bool store_null() { return false; } + bool store_tiny(longlong) { return false; } + bool store_short(longlong) { return false; } + bool store_long(longlong) { return false; } + bool store_longlong(longlong, bool) { return false; } + bool store_decimal(const my_decimal *) { return false; } + bool store(const char *, size_t, CHARSET_INFO *) { return false; } + bool store(const char *, size_t, CHARSET_INFO *, CHARSET_INFO *) { return false; } + bool store(MYSQL_TIME *, int) { return false; } + bool store_date(MYSQL_TIME *) { return false; } + bool store_time(MYSQL_TIME *, int) { return false; } + bool store(float, uint32, String *) { return false; } + bool store(double, uint32, String *) { return false; } + bool store(Field *) { return false; } + +}; + + void send_warning(THD *thd, uint sql_errno, const char *err=0); bool net_send_error(THD *thd, uint sql_errno, const char *err, const char* sqlstate); diff --git a/sql/records.cc b/sql/records.cc index 59ea50bd061..d98c6939e04 100644 --- a/sql/records.cc +++ b/sql/records.cc @@ -26,6 +26,7 @@ Functions for easy reading of records, possible through a cache */ +#include <my_global.h> #include "records.h" #include "sql_priv.h" #include "records.h" @@ -66,10 +67,12 @@ static int rr_index_desc(READ_RECORD *info); @param reverse Scan in the reverse direction */ -void init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table, +bool init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table, bool print_error, uint idx, bool reverse) { - int error; + int error= 0; + DBUG_ENTER("init_read_record_idx"); + empty_record(table); bzero((char*) info,sizeof(*info)); info->thd= thd; @@ -88,6 +91,7 @@ void init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table, /* read_record will be changed to rr_index in rr_index_first */ info->read_record= reverse ? rr_index_last : rr_index_first; + DBUG_RETURN(error != 0); } @@ -288,8 +292,7 @@ bool init_read_record(READ_RECORD *info,THD *thd, TABLE *table, thd->variables.read_buff_size); } /* Condition pushdown to storage engine */ - if ((thd->variables.optimizer_switch & - OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN) && + if ((table->file->ha_table_flags() & HA_CAN_TABLE_CONDITION_PUSHDOWN) && select && select->cond && (select->cond->used_tables() & table->map) && !table->file->pushed_cond) @@ -604,7 +607,7 @@ static int init_rr_cache(THD *thd, READ_RECORD *info) if (info->cache_records <= 2 || !(info->cache=(uchar*) my_malloc_lock(rec_cache_size+info->cache_records* info->struct_length+1, - MYF(0)))) + MYF(MY_THREAD_SPECIFIC)))) DBUG_RETURN(1); #ifdef HAVE_valgrind // Avoid warnings in qsort diff --git a/sql/records.h b/sql/records.h index 57467d665d4..b55fbc22f55 100644 --- a/sql/records.h +++ b/sql/records.h @@ -18,7 +18,6 @@ #ifdef USE_PRAGMA_INTERFACE #pragma interface /* gcc class implementation */ #endif -#include <my_global.h> /* for uint typedefs */ struct st_join_table; class handler; @@ -70,14 +69,12 @@ struct READ_RECORD */ Copy_field *copy_field; Copy_field *copy_field_end; -public: - READ_RECORD() {} }; bool init_read_record(READ_RECORD *info, THD *thd, TABLE *reg_form, SQL_SELECT *select, int use_record_cache, bool print_errors, bool disable_rr_cache); -void init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table, +bool init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table, bool print_error, uint idx, bool reverse); void end_read_record(READ_RECORD *info); diff --git a/sql/repl_failsafe.cc b/sql/repl_failsafe.cc index 89fb1bb27de..4cf7df5227f 100644 --- a/sql/repl_failsafe.cc +++ b/sql/repl_failsafe.cc @@ -24,6 +24,7 @@ functions like register_slave()) are working. */ +#include <my_global.h> #include "sql_priv.h" #include "sql_parse.h" // check_access #ifdef HAVE_REPLICATION @@ -43,7 +44,6 @@ ulong rpl_status=RPL_NULL; mysql_mutex_t LOCK_rpl_status; -mysql_cond_t COND_rpl_status; HASH slave_list; const char *rpl_role_type[] = {"MASTER","SLAVE",NullS}; @@ -69,7 +69,6 @@ void change_rpl_status(ulong from_status, ulong to_status) mysql_mutex_lock(&LOCK_rpl_status); if (rpl_status == from_status || rpl_status == RPL_ANY) rpl_status = to_status; - mysql_cond_signal(&COND_rpl_status); mysql_mutex_unlock(&LOCK_rpl_status); } @@ -89,14 +88,15 @@ void change_rpl_status(ulong from_status, ulong to_status) void unregister_slave(THD* thd, bool only_mine, bool need_mutex) { - if (thd->server_id) + uint32 thd_server_id= thd->variables.server_id; + if (thd_server_id) { if (need_mutex) mysql_mutex_lock(&LOCK_slave_list); SLAVE_INFO* old_si; if ((old_si = (SLAVE_INFO*)my_hash_search(&slave_list, - (uchar*)&thd->server_id, 4)) && + (uchar*)&thd_server_id, 4)) && (!only_mine || old_si->thd == thd)) my_hash_delete(&slave_list, (uchar*)old_si); @@ -127,7 +127,7 @@ int register_slave(THD* thd, uchar* packet, uint packet_length) if (!(si = (SLAVE_INFO*)my_malloc(sizeof(SLAVE_INFO), MYF(MY_WME)))) goto err2; - thd->server_id= si->server_id= uint4korr(p); + thd->variables.server_id= si->server_id= uint4korr(p); p+= 4; get_object(p,si->host, "Failed to register slave: too long 'report-host'"); get_object(p,si->user, "Failed to register slave: too long 'report-user'"); @@ -145,7 +145,7 @@ int register_slave(THD* thd, uchar* packet, uint packet_length) // si->rpl_recovery_rank= uint4korr(p); p += 4; if (!(si->master_id= uint4korr(p))) - si->master_id= server_id; + si->master_id= global_system_variables.server_id; si->thd= thd; mysql_mutex_lock(&LOCK_slave_list); @@ -230,19 +230,29 @@ bool show_slave_hosts(THD* thd) { List<Item> field_list; Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("show_slave_hosts"); - field_list.push_back(new Item_return_int("Server_id", 10, - MYSQL_TYPE_LONG)); - field_list.push_back(new Item_empty_string("Host", 20)); + field_list.push_back(new (mem_root) + Item_return_int(thd, "Server_id", 10, + MYSQL_TYPE_LONG), + thd->mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Host", 20), + thd->mem_root); if (opt_show_slave_auth_info) { - field_list.push_back(new Item_empty_string("User",20)); - field_list.push_back(new Item_empty_string("Password",20)); + field_list.push_back(new (mem_root) Item_empty_string(thd, "User", 20), + thd->mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Password", 20), + thd->mem_root); } - field_list.push_back(new Item_return_int("Port", 7, MYSQL_TYPE_LONG)); - field_list.push_back(new Item_return_int("Master_id", 10, - MYSQL_TYPE_LONG)); + field_list.push_back(new (mem_root) + Item_return_int(thd, "Port", 7, MYSQL_TYPE_LONG), + thd->mem_root); + field_list.push_back(new (mem_root) + Item_return_int(thd, "Master_id", 10, MYSQL_TYPE_LONG), + thd->mem_root); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) diff --git a/sql/replication.h b/sql/replication.h index 9492c54fabd..4731c2246ef 100644 --- a/sql/replication.h +++ b/sql/replication.h @@ -10,8 +10,8 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ #ifndef REPLICATION_H #define REPLICATION_H @@ -23,7 +23,7 @@ is locked on Binlog_transmit_observer::transmit_start and is unlocked after Binlog_transmit_observer::transmit_stop. All other master observable events happen between these two and don't lock the plugin at all. This works well - for the semisync_master plugin. + for the semisync_master plugin. Also a plugin is locked on Binlog_relay_IO_observer::thread_start and unlocked after Binlog_relay_IO_observer::thread_stop. This works well for @@ -81,6 +81,7 @@ typedef struct Trans_observer { succeeded. @note The return value is currently ignored by the server. + @note This hook is called wo/ any global mutex held @param param The parameter for transaction observers @@ -103,6 +104,8 @@ typedef struct Trans_observer { @param param The parameter for transaction observers + @note This hook is called wo/ any global mutex held + @retval 0 Sucess @retval 1 Failure */ @@ -114,7 +117,13 @@ typedef struct Trans_observer { */ enum Binlog_storage_flags { /** Binary log was sync:ed */ - BINLOG_STORAGE_IS_SYNCED = 1 + BINLOG_STORAGE_IS_SYNCED = 1, + + /** First(or alone) in a group commit */ + BINLOG_GROUP_COMMIT_LEADER = 2, + + /** Last(or alone) in a group commit */ + BINLOG_GROUP_COMMIT_TRAILER = 4 }; /** @@ -137,6 +146,8 @@ typedef struct Binlog_storage_observer { binary log file. Whether the binary log file is synchronized to disk is indicated by the bit BINLOG_STORAGE_IS_SYNCED in @a flags. + @note: this hook is called with LOCK_log mutex held + @param param Observer common parameter @param log_file Binlog file name been updated @param log_pos Binlog position after update @@ -148,6 +159,26 @@ typedef struct Binlog_storage_observer { int (*after_flush)(Binlog_storage_param *param, const char *log_file, my_off_t log_pos, uint32 flags); + + /** + This callback is called after binlog has been synced + + This callback is called after events flushed to disk has been sync:ed + ("group committed"). + + @note: this hook is called with LOCK_after_binlog_sync mutex held + + @param param Observer common parameter + @param log_file Binlog file name been updated + @param log_pos Binlog position after update + @param flags flags for binlog storage + + @retval 0 Sucess + @retval 1 Failure + */ + int (*after_sync)(Binlog_storage_param *param, + const char *log_file, my_off_t log_pos, + uint32 flags); } Binlog_storage_observer; /** @@ -474,34 +505,6 @@ int unregister_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void MYSQL *rpl_connect_master(MYSQL *mysql); /** - Set thread entering a condition - - This function should be called before putting a thread to wait for - a condition. @a mutex should be held before calling this - function. After being waken up, @f thd_exit_cond should be called. - - @param thd The thread entering the condition, NULL means current thread - @param cond The condition the thread is going to wait for - @param mutex The mutex associated with the condition, this must be - held before call this function - @param msg The new process message for the thread -*/ -const char* thd_enter_cond(MYSQL_THD thd, mysql_cond_t *cond, - mysql_mutex_t *mutex, const char *msg); - -/** - Set thread leaving a condition - - This function should be called after a thread being waken up for a - condition. - - @param thd The thread entering the condition, NULL means current thread - @param old_msg The process message, ususally this should be the old process - message before calling @f thd_enter_cond -*/ -void thd_exit_cond(MYSQL_THD thd, const char *old_msg); - -/** Get the value of user variable as an integer. This function will return the value of variable @a name as an diff --git a/sql/rpl_constants.h b/sql/rpl_constants.h index 3c605d24563..0a7fde439dd 100644 --- a/sql/rpl_constants.h +++ b/sql/rpl_constants.h @@ -17,6 +17,9 @@ #ifndef RPL_CONSTANTS_H #define RPL_CONSTANTS_H +#include <my_sys.h> +#include <my_crypt.h> + /** Enumeration of the incidents that can occur for the server. */ @@ -31,4 +34,79 @@ enum Incident { INCIDENT_COUNT }; + +/** + Enumeration of the reserved formats of Binlog extra row information +*/ +enum ExtraRowInfoFormat { + + /** Reserved formats 0 -> 63 inclusive */ + ERIF_LASTRESERVED = 63, + + /** + Available / uncontrolled formats + 64 -> 254 inclusive + */ + ERIF_OPEN1 = 64, + ERIF_OPEN2 = 65, + + ERIF_LASTOPEN = 254, + + /** + Multi-payload format 255 + + Length is total length, payload is sequence of + sub-payloads with their own headers containing + length + format. + */ + ERIF_MULTI = 255 +}; + +/* + 1 byte length, 1 byte format + Length is total length in bytes, including 2 byte header + Length values 0 and 1 are currently invalid and reserved. +*/ +#define EXTRA_ROW_INFO_LEN_OFFSET 0 +#define EXTRA_ROW_INFO_FORMAT_OFFSET 1 +#define EXTRA_ROW_INFO_HDR_BYTES 2 +#define EXTRA_ROW_INFO_MAX_PAYLOAD (255 - EXTRA_ROW_INFO_HDR_BYTES) + +enum enum_binlog_checksum_alg { + BINLOG_CHECKSUM_ALG_OFF= 0, // Events are without checksum though its generator + // is checksum-capable New Master (NM). + BINLOG_CHECKSUM_ALG_CRC32= 1, // CRC32 of zlib algorithm. + BINLOG_CHECKSUM_ALG_ENUM_END, // the cut line: valid alg range is [1, 0x7f]. + BINLOG_CHECKSUM_ALG_UNDEF= 255 // special value to tag undetermined yet checksum + // or events from checksum-unaware servers +}; + +#define BINLOG_CRYPTO_SCHEME_LENGTH 1 +#define BINLOG_KEY_VERSION_LENGTH 4 +#define BINLOG_IV_LENGTH MY_AES_BLOCK_SIZE +#define BINLOG_IV_OFFS_LENGTH 4 +#define BINLOG_NONCE_LENGTH (BINLOG_IV_LENGTH - BINLOG_IV_OFFS_LENGTH) + +struct Binlog_crypt_data { + uint scheme; + uint key_version, key_length, ctx_size; + uchar key[MY_AES_MAX_KEY_LENGTH]; + uchar nonce[BINLOG_NONCE_LENGTH]; + + int init(uint sch, uint kv) + { + scheme= sch; + ctx_size= encryption_ctx_size(ENCRYPTION_KEY_SYSTEM_DATA, kv); + key_version= kv; + key_length= sizeof(key); + return encryption_key_get(ENCRYPTION_KEY_SYSTEM_DATA, kv, key, &key_length); + } + + void set_iv(uchar* iv, uint32 offs) const + { + memcpy(iv, nonce, BINLOG_NONCE_LENGTH); + int4store(iv + BINLOG_NONCE_LENGTH, offs); + } +}; + #endif /* RPL_CONSTANTS_H */ diff --git a/sql/rpl_filter.cc b/sql/rpl_filter.cc index 6205c253ffc..b82e7bada45 100644 --- a/sql/rpl_filter.cc +++ b/sql/rpl_filter.cc @@ -13,6 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "mysqld.h" // system_charset_info #include "rpl_filter.h" @@ -23,7 +24,9 @@ #define TABLE_RULE_ARR_SIZE 16 Rpl_filter::Rpl_filter() : - table_rules_on(0), do_table_inited(0), ignore_table_inited(0), + parallel_mode(SLAVE_PARALLEL_CONSERVATIVE), + table_rules_on(0), + do_table_inited(0), ignore_table_inited(0), wild_do_table_inited(0), wild_ignore_table_inited(0) { do_db.empty(); @@ -577,7 +580,7 @@ void Rpl_filter::init_table_rule_array(DYNAMIC_ARRAY* a, bool* a_inited) { my_init_dynamic_array(a, sizeof(TABLE_RULE_ENT*), TABLE_RULE_ARR_SIZE, - TABLE_RULE_ARR_SIZE); + TABLE_RULE_ARR_SIZE, MYF(0)); *a_inited = 1; } @@ -738,6 +741,18 @@ Rpl_filter::get_rewrite_db(const char* db, size_t *new_len) } +void +Rpl_filter::copy_rewrite_db(Rpl_filter *from) +{ + I_List_iterator<i_string_pair> it(from->rewrite_db); + i_string_pair* tmp; + DBUG_ASSERT(rewrite_db.is_empty()); + + /* TODO: Add memory checking here and in all add_xxxx functions ! */ + while ((tmp=it++)) + add_db_rewrite(tmp->key, tmp->val); +} + I_List<i_string>* Rpl_filter::get_do_db() { diff --git a/sql/rpl_filter.h b/sql/rpl_filter.h index 2eb0340b714..f24ece30a80 100644 --- a/sql/rpl_filter.h +++ b/sql/rpl_filter.h @@ -17,6 +17,7 @@ #define RPL_FILTER_H #include "mysql.h" +#include "mysqld.h" #include "sql_list.h" /* I_List */ #include "hash.h" /* HASH */ @@ -76,6 +77,16 @@ public: int set_do_db(const char* db_spec); int set_ignore_db(const char* db_spec); + void set_parallel_mode(enum_slave_parallel_mode mode) + { + parallel_mode= mode; + } + /* Return given parallel mode or if one is not given, the default mode */ + enum_slave_parallel_mode get_parallel_mode() + { + return parallel_mode; + } + void add_db_rewrite(const char* from_db, const char* to_db); /* Getters - to get information about current rules */ @@ -88,6 +99,7 @@ public: bool rewrite_db_is_empty(); const char* get_rewrite_db(const char* db, size_t *new_len); + void copy_rewrite_db(Rpl_filter *from); I_List<i_string>* get_do_db(); I_List<i_string>* get_ignore_db(); @@ -96,7 +108,6 @@ public: void get_ignore_db(String* str); private: - bool table_rules_on; void init_table_rule_hash(HASH* h, bool* h_inited); void init_table_rule_array(DYNAMIC_ARRAY* a, bool* a_inited); @@ -127,7 +138,9 @@ private: HASH ignore_table; DYNAMIC_ARRAY wild_do_table; DYNAMIC_ARRAY wild_ignore_table; + enum_slave_parallel_mode parallel_mode; + bool table_rules_on; bool do_table_inited; bool ignore_table_inited; bool wild_do_table_inited; @@ -139,7 +152,7 @@ private: I_List<i_string_pair> rewrite_db; }; -extern Rpl_filter *rpl_filter; +extern Rpl_filter *global_rpl_filter; extern Rpl_filter *binlog_filter; #endif // RPL_FILTER_H diff --git a/sql/rpl_gtid.cc b/sql/rpl_gtid.cc new file mode 100644 index 00000000000..2d4acde03fc --- /dev/null +++ b/sql/rpl_gtid.cc @@ -0,0 +1,2629 @@ +/* Copyright (c) 2013, Kristian Nielsen and MariaDB Services Ab. + + 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 */ + + +/* Definitions for MariaDB global transaction ID (GTID). */ + +#include <my_global.h> +#include "sql_priv.h" +#include "my_sys.h" +#include "unireg.h" +#include "my_global.h" +#include "sql_base.h" +#include "sql_parse.h" +#include "key.h" +#include "rpl_gtid.h" +#include "rpl_rli.h" +#include "log_event.h" + +const LEX_STRING rpl_gtid_slave_state_table_name= + { C_STRING_WITH_LEN("gtid_slave_pos") }; + + +void +rpl_slave_state::update_state_hash(uint64 sub_id, rpl_gtid *gtid, + rpl_group_info *rgi) +{ + int err; + /* + Add the gtid to the HASH in the replication slave state. + + We must do this only _after_ commit, so that for parallel replication, + there will not be an attempt to delete the corresponding table row before + it is even committed. + */ + mysql_mutex_lock(&LOCK_slave_state); + err= update(gtid->domain_id, gtid->server_id, sub_id, gtid->seq_no, rgi); + mysql_mutex_unlock(&LOCK_slave_state); + if (err) + { + sql_print_warning("Slave: Out of memory during slave state maintenance. " + "Some no longer necessary rows in table " + "mysql.%s may be left undeleted.", + rpl_gtid_slave_state_table_name.str); + /* + Such failure is not fatal. We will fail to delete the row for this + GTID, but it will do no harm and will be removed automatically on next + server restart. + */ + } +} + + +int +rpl_slave_state::record_and_update_gtid(THD *thd, rpl_group_info *rgi) +{ + DBUG_ENTER("rpl_slave_state::record_and_update_gtid"); + + /* + Update the GTID position, if we have it and did not already update + it in a GTID transaction. + */ + if (rgi->gtid_pending) + { + uint64 sub_id= rgi->gtid_sub_id; + rgi->gtid_pending= false; + if (rgi->gtid_ignore_duplicate_state!=rpl_group_info::GTID_DUPLICATE_IGNORE) + { + if (record_gtid(thd, &rgi->current_gtid, sub_id, NULL, false)) + DBUG_RETURN(1); + update_state_hash(sub_id, &rgi->current_gtid, rgi); + } + rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_NULL; + } + DBUG_RETURN(0); +} + + +/* + Check GTID event execution when --gtid-ignore-duplicates. + + The idea with --gtid-ignore-duplicates is that we allow multiple master + connections (in multi-source replication) to all receive the same GTIDs and + event groups. Only one instance of each is applied; we use the sequence + number in the GTID to decide whether a GTID has already been applied. + + So if the seq_no of a GTID (or a higher sequence number) has already been + applied, then the event should be skipped. If not then the event should be + applied. + + To avoid two master connections tring to apply the same event + simultaneously, only one is allowed to work in any given domain at any point + in time. The associated Relay_log_info object is called the owner of the + domain (and there can be multiple parallel worker threads working in that + domain for that Relay_log_info). Any other Relay_log_info/master connection + must wait for the domain to become free, or for their GTID to have been + applied, before being allowed to proceed. + + Returns: + 0 This GTID is already applied, it should be skipped. + 1 The GTID is not yet applied; this rli is now the owner, and must apply + the event and release the domain afterwards. + -1 Error (out of memory to allocate a new element for the domain). +*/ +int +rpl_slave_state::check_duplicate_gtid(rpl_gtid *gtid, rpl_group_info *rgi) +{ + uint32 domain_id= gtid->domain_id; + uint64 seq_no= gtid->seq_no; + rpl_slave_state::element *elem; + int res; + bool did_enter_cond= false; + PSI_stage_info old_stage; + THD *UNINIT_VAR(thd); + Relay_log_info *rli= rgi->rli; + + mysql_mutex_lock(&LOCK_slave_state); + if (!(elem= get_element(domain_id))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + res= -1; + goto err; + } + /* + Note that the elem pointer does not change once inserted in the hash. So + we can re-use the pointer without looking it up again in the hash after + each lock release and re-take. + */ + + for (;;) + { + if (elem->highest_seq_no >= seq_no) + { + /* This sequence number is already applied, ignore it. */ + res= 0; + rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_IGNORE; + break; + } + if (!elem->owner_rli) + { + /* The domain became free, grab it and apply the event. */ + elem->owner_rli= rli; + elem->owner_count= 1; + rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_OWNER; + res= 1; + break; + } + if (elem->owner_rli == rli) + { + /* Already own this domain, increment reference count and apply event. */ + ++elem->owner_count; + rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_OWNER; + res= 1; + break; + } + thd= rgi->thd; + if (thd->check_killed()) + { + thd->send_kill_message(); + res= -1; + break; + } + /* + Someone else is currently processing this GTID (or an earlier one). + Wait for them to complete (or fail), and then check again. + */ + if (!did_enter_cond) + { + thd->ENTER_COND(&elem->COND_gtid_ignore_duplicates, &LOCK_slave_state, + &stage_gtid_wait_other_connection, &old_stage); + did_enter_cond= true; + } + mysql_cond_wait(&elem->COND_gtid_ignore_duplicates, + &LOCK_slave_state); + } + +err: + if (did_enter_cond) + thd->EXIT_COND(&old_stage); + else + mysql_mutex_unlock(&LOCK_slave_state); + return res; +} + + +void +rpl_slave_state::release_domain_owner(rpl_group_info *rgi) +{ + element *elem= NULL; + + mysql_mutex_lock(&LOCK_slave_state); + if (!(elem= get_element(rgi->current_gtid.domain_id))) + { + /* + We cannot really deal with error here, as we are already called in an + error handling case (transaction failure and rollback). + + However, get_element() only fails if the element did not exist already + and could not be allocated due to out-of-memory - and if it did not + exist, then we would not get here in the first place. + */ + mysql_mutex_unlock(&LOCK_slave_state); + return; + } + + if (rgi->gtid_ignore_duplicate_state == rpl_group_info::GTID_DUPLICATE_OWNER) + { + uint32 count= elem->owner_count; + DBUG_ASSERT(count > 0); + DBUG_ASSERT(elem->owner_rli == rgi->rli); + --count; + elem->owner_count= count; + if (count == 0) + { + elem->owner_rli= NULL; + mysql_cond_broadcast(&elem->COND_gtid_ignore_duplicates); + } + } + rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_NULL; + mysql_mutex_unlock(&LOCK_slave_state); +} + + +static void +rpl_slave_state_free_element(void *arg) +{ + struct rpl_slave_state::element *elem= (struct rpl_slave_state::element *)arg; + mysql_cond_destroy(&elem->COND_wait_gtid); + mysql_cond_destroy(&elem->COND_gtid_ignore_duplicates); + my_free(elem); +} + + +rpl_slave_state::rpl_slave_state() + : last_sub_id(0), loaded(false) +{ + mysql_mutex_init(key_LOCK_slave_state, &LOCK_slave_state, + MY_MUTEX_INIT_SLOW); + my_hash_init(&hash, &my_charset_bin, 32, offsetof(element, domain_id), + sizeof(uint32), NULL, rpl_slave_state_free_element, HASH_UNIQUE); + my_init_dynamic_array(>id_sort_array, sizeof(rpl_gtid), 8, 8, MYF(0)); +} + + +rpl_slave_state::~rpl_slave_state() +{ + truncate_hash(); + my_hash_free(&hash); + delete_dynamic(>id_sort_array); + mysql_mutex_destroy(&LOCK_slave_state); +} + + +void +rpl_slave_state::truncate_hash() +{ + uint32 i; + + for (i= 0; i < hash.records; ++i) + { + element *e= (element *)my_hash_element(&hash, i); + list_element *l= e->list; + list_element *next; + while (l) + { + next= l->next; + my_free(l); + l= next; + } + /* The element itself is freed by the hash element free function. */ + } + my_hash_reset(&hash); +} + + +int +rpl_slave_state::update(uint32 domain_id, uint32 server_id, uint64 sub_id, + uint64 seq_no, rpl_group_info *rgi) +{ + element *elem= NULL; + list_element *list_elem= NULL; + + if (!(elem= get_element(domain_id))) + return 1; + + if (seq_no > elem->highest_seq_no) + elem->highest_seq_no= seq_no; + if (elem->gtid_waiter && elem->min_wait_seq_no <= seq_no) + { + /* + Someone was waiting in MASTER_GTID_WAIT() for this GTID to appear. + Signal (and remove) them. The waiter will handle all the processing + of all pending MASTER_GTID_WAIT(), so we do not slow down the + replication SQL thread. + */ + mysql_mutex_assert_owner(&LOCK_slave_state); + elem->gtid_waiter= NULL; + mysql_cond_broadcast(&elem->COND_wait_gtid); + } + + if (rgi) + { + if (rgi->gtid_ignore_duplicate_state==rpl_group_info::GTID_DUPLICATE_OWNER) + { +#ifndef DBUG_OFF + Relay_log_info *rli= rgi->rli; +#endif + uint32 count= elem->owner_count; + DBUG_ASSERT(count > 0); + DBUG_ASSERT(elem->owner_rli == rli); + --count; + elem->owner_count= count; + if (count == 0) + { + elem->owner_rli= NULL; + mysql_cond_broadcast(&elem->COND_gtid_ignore_duplicates); + } + } + rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_NULL; + +#ifdef HAVE_REPLICATION + rgi->pending_gtid_deletes_clear(); +#endif + } + + if (!(list_elem= (list_element *)my_malloc(sizeof(*list_elem), MYF(MY_WME)))) + return 1; + list_elem->server_id= server_id; + list_elem->sub_id= sub_id; + list_elem->seq_no= seq_no; + + elem->add(list_elem); + if (last_sub_id < sub_id) + last_sub_id= sub_id; + + return 0; +} + + +struct rpl_slave_state::element * +rpl_slave_state::get_element(uint32 domain_id) +{ + struct element *elem; + + elem= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0); + if (elem) + return elem; + + if (!(elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME)))) + return NULL; + elem->list= NULL; + elem->domain_id= domain_id; + elem->highest_seq_no= 0; + elem->gtid_waiter= NULL; + elem->owner_rli= NULL; + elem->owner_count= 0; + mysql_cond_init(key_COND_wait_gtid, &elem->COND_wait_gtid, 0); + mysql_cond_init(key_COND_gtid_ignore_duplicates, + &elem->COND_gtid_ignore_duplicates, 0); + if (my_hash_insert(&hash, (uchar *)elem)) + { + my_free(elem); + return NULL; + } + return elem; +} + + +int +rpl_slave_state::put_back_list(uint32 domain_id, list_element *list) +{ + element *e; + int err= 0; + + mysql_mutex_lock(&LOCK_slave_state); + if (!(e= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0))) + { + err= 1; + goto end; + } + while (list) + { + list_element *next= list->next; + e->add(list); + list= next; + } + +end: + mysql_mutex_unlock(&LOCK_slave_state); + return err; +} + + +int +rpl_slave_state::truncate_state_table(THD *thd) +{ + TABLE_LIST tlist; + int err= 0; + + tmp_disable_binlog(thd); + tlist.init_one_table(STRING_WITH_LEN("mysql"), + rpl_gtid_slave_state_table_name.str, + rpl_gtid_slave_state_table_name.length, + NULL, TL_WRITE); + if (!(err= open_and_lock_tables(thd, &tlist, FALSE, 0))) + { + err= tlist.table->file->ha_truncate(); + + if (err) + { + ha_rollback_trans(thd, FALSE); + close_thread_tables(thd); + ha_rollback_trans(thd, TRUE); + } + else + { + ha_commit_trans(thd, FALSE); + close_thread_tables(thd); + ha_commit_trans(thd, TRUE); + } + thd->mdl_context.release_transactional_locks(); + } + + reenable_binlog(thd); + return err; +} + + +static const TABLE_FIELD_TYPE mysql_rpl_slave_state_coltypes[4]= { + { { C_STRING_WITH_LEN("domain_id") }, + { C_STRING_WITH_LEN("int(10) unsigned") }, + {NULL, 0} }, + { { C_STRING_WITH_LEN("sub_id") }, + { C_STRING_WITH_LEN("bigint(20) unsigned") }, + {NULL, 0} }, + { { C_STRING_WITH_LEN("server_id") }, + { C_STRING_WITH_LEN("int(10) unsigned") }, + {NULL, 0} }, + { { C_STRING_WITH_LEN("seq_no") }, + { C_STRING_WITH_LEN("bigint(20) unsigned") }, + {NULL, 0} }, +}; + +static const uint mysql_rpl_slave_state_pk_parts[]= {0, 1}; + +static const TABLE_FIELD_DEF mysql_gtid_slave_pos_tabledef= { + array_elements(mysql_rpl_slave_state_coltypes), + mysql_rpl_slave_state_coltypes, + array_elements(mysql_rpl_slave_state_pk_parts), + mysql_rpl_slave_state_pk_parts +}; + +static Table_check_intact_log_error gtid_table_intact; + +/* + Check that the mysql.gtid_slave_pos table has the correct definition. +*/ +int +gtid_check_rpl_slave_state_table(TABLE *table) +{ + int err; + + if ((err= gtid_table_intact.check(table, &mysql_gtid_slave_pos_tabledef))) + my_error(ER_GTID_OPEN_TABLE_FAILED, MYF(0), "mysql", + rpl_gtid_slave_state_table_name.str); + return err; +} + + +/* + Write a gtid to the replication slave state table. + + gtid The global transaction id for this event group. + sub_id Value allocated within the sub_id when the event group was + read (sub_id must be consistent with commit order in master binlog). + rgi rpl_group_info context, if we are recording the gtid transactionally + as part of replicating a transactional event. NULL if called from + outside of a replicated transaction. + + Note that caller must later ensure that the new gtid and sub_id is inserted + into the appropriate HASH element with rpl_slave_state.add(), so that it can + be deleted later. But this must only be done after COMMIT if in transaction. +*/ +int +rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, + rpl_group_info *rgi, bool in_statement) +{ + TABLE_LIST tlist; + int err= 0; + bool table_opened= false; + TABLE *table; + list_element *elist= 0, *cur, *next; + element *elem; + ulonglong thd_saved_option= thd->variables.option_bits; + Query_tables_list lex_backup; + wait_for_commit* suspended_wfc; + DBUG_ENTER("record_gtid"); + + if (unlikely(!loaded)) + { + /* + Probably the mysql.gtid_slave_pos table is missing (eg. upgrade) or + corrupt. + + We already complained loudly about this, but we can try to continue + until the DBA fixes it. + */ + DBUG_RETURN(0); + } + + if (!in_statement) + thd->reset_for_next_command(); + + DBUG_EXECUTE_IF("gtid_inject_record_gtid", + { + my_error(ER_CANNOT_UPDATE_GTID_STATE, MYF(0)); + DBUG_RETURN(1); + } ); + + /* + If we are applying a non-transactional event group, we will be committing + here a transaction, but that does not imply that the event group has + completed or has been binlogged. So we should not trigger + wakeup_subsequent_commits() here. + + Note: An alternative here could be to put a call to mark_start_commit() in + stmt_done() before the call to record_and_update_gtid(). This would + prevent later calling mark_start_commit() after we have run + wakeup_subsequent_commits() from committing the GTID update transaction + (which must be avoided to avoid accessing freed group_commit_orderer + object). It would also allow following event groups to start slightly + earlier. And in the cases where record_gtid() is called without an active + transaction, the current statement should have been binlogged already, so + binlog order is preserved. + + But this is rather subtle, and potentially fragile. And it does not really + seem worth it; non-transactional loads are unlikely to benefit much from + parallel replication in any case. So for now, we go with the simple + suspend/resume of wakeup_subsequent_commits() here in record_gtid(). + */ + suspended_wfc= thd->suspend_subsequent_commits(); + thd->lex->reset_n_backup_query_tables_list(&lex_backup); + tlist.init_one_table(STRING_WITH_LEN("mysql"), + rpl_gtid_slave_state_table_name.str, + rpl_gtid_slave_state_table_name.length, + NULL, TL_WRITE); + if ((err= open_and_lock_tables(thd, &tlist, FALSE, 0))) + goto end; + table_opened= true; + table= tlist.table; + + if ((err= gtid_check_rpl_slave_state_table(table))) + goto end; + +#ifdef WITH_WSREP + /* + Updates in slave state table should not be appended to galera transaction + writeset. + */ + thd->wsrep_ignore_table= true; +#endif + + if (!rgi) + { + DBUG_PRINT("info", ("resetting OPTION_BEGIN")); + thd->variables.option_bits&= + ~(ulonglong)(OPTION_NOT_AUTOCOMMIT |OPTION_BEGIN |OPTION_BIN_LOG | + OPTION_GTID_BEGIN); + } + else + thd->variables.option_bits&= ~(ulonglong)OPTION_BIN_LOG; + + bitmap_set_all(table->write_set); + table->rpl_write_set= table->write_set; + + table->field[0]->store((ulonglong)gtid->domain_id, true); + table->field[1]->store(sub_id, true); + table->field[2]->store((ulonglong)gtid->server_id, true); + table->field[3]->store(gtid->seq_no, true); + DBUG_EXECUTE_IF("inject_crash_before_write_rpl_slave_state", DBUG_SUICIDE();); + if ((err= table->file->ha_write_row(table->record[0]))) + { + table->file->print_error(err, MYF(0)); + goto end; + } + + if(opt_bin_log && + (err= mysql_bin_log.bump_seq_no_counter_if_needed(gtid->domain_id, + gtid->seq_no))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto end; + } + + mysql_mutex_lock(&LOCK_slave_state); + if ((elem= get_element(gtid->domain_id)) == NULL) + { + mysql_mutex_unlock(&LOCK_slave_state); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + err= 1; + goto end; + } + if ((elist= elem->grab_list()) != NULL) + { + /* Delete any old stuff, but keep around the most recent one. */ + uint64 best_sub_id= elist->sub_id; + list_element **best_ptr_ptr= &elist; + cur= elist; + while ((next= cur->next)) + { + if (next->sub_id > best_sub_id) + { + best_sub_id= next->sub_id; + best_ptr_ptr= &cur->next; + } + cur= next; + } + /* + Delete the highest sub_id element from the old list, and put it back as + the single-element new list. + */ + cur= *best_ptr_ptr; + *best_ptr_ptr= cur->next; + cur->next= NULL; + elem->list= cur; + } + mysql_mutex_unlock(&LOCK_slave_state); + + if (!elist) + goto end; + + /* Now delete any already committed rows. */ + bitmap_set_bit(table->read_set, table->field[0]->field_index); + bitmap_set_bit(table->read_set, table->field[1]->field_index); + + if ((err= table->file->ha_index_init(0, 0))) + { + table->file->print_error(err, MYF(0)); + goto end; + } + cur = elist; + while (cur) + { + uchar key_buffer[4+8]; + + DBUG_EXECUTE_IF("gtid_slave_pos_simulate_failed_delete", + { err= ENOENT; + table->file->print_error(err, MYF(0)); + /* `break' does not work inside DBUG_EXECUTE_IF */ + goto dbug_break; }); + + next= cur->next; + + table->field[1]->store(cur->sub_id, true); + /* domain_id is already set in table->record[0] from write_row() above. */ + key_copy(key_buffer, table->record[0], &table->key_info[0], 0, false); + if (table->file->ha_index_read_map(table->record[1], key_buffer, + HA_WHOLE_KEY, HA_READ_KEY_EXACT)) + /* We cannot find the row, assume it is already deleted. */ + ; + else if ((err= table->file->ha_delete_row(table->record[1]))) + table->file->print_error(err, MYF(0)); + /* + In case of error, we still discard the element from the list. We do + not want to endlessly error on the same element in case of table + corruption or such. + */ + cur= next; + if (err) + break; + } +IF_DBUG(dbug_break:, ) + table->file->ha_index_end(); + +end: + +#ifdef WITH_WSREP + thd->wsrep_ignore_table= false; +#endif + + if (table_opened) + { + if (err || (err= ha_commit_trans(thd, FALSE))) + { + /* + If error, we need to put any remaining elist back into the HASH so we + can do another delete attempt later. + */ + if (elist) + { + put_back_list(gtid->domain_id, elist); + elist = 0; + } + + ha_rollback_trans(thd, FALSE); + } + close_thread_tables(thd); + if (rgi) + { + thd->mdl_context.release_statement_locks(); + /* + Save the list of old gtid entries we deleted. If this transaction + fails later for some reason and is rolled back, the deletion of those + entries will be rolled back as well, and we will need to put them back + on the to-be-deleted list so we can re-do the deletion. Otherwise + redundant rows in mysql.gtid_slave_pos may accumulate if transactions + are rolled back and retried after record_gtid(). + */ +#ifdef HAVE_REPLICATION + rgi->pending_gtid_deletes_save(gtid->domain_id, elist); +#endif + } + else + { + thd->mdl_context.release_transactional_locks(); +#ifdef HAVE_REPLICATION + rpl_group_info::pending_gtid_deletes_free(elist); +#endif + } + } + thd->lex->restore_backup_query_tables_list(&lex_backup); + thd->variables.option_bits= thd_saved_option; + thd->resume_subsequent_commits(suspended_wfc); + DBUG_EXECUTE_IF("inject_record_gtid_serverid_100_sleep", + { + if (gtid->server_id == 100) + my_sleep(500000); + }); + DBUG_RETURN(err); +} + + +uint64 +rpl_slave_state::next_sub_id(uint32 domain_id) +{ + uint64 sub_id= 0; + + mysql_mutex_lock(&LOCK_slave_state); + sub_id= ++last_sub_id; + mysql_mutex_unlock(&LOCK_slave_state); + + return sub_id; +} + +/* A callback used in sorting of gtid list based on domain_id. */ +static int rpl_gtid_cmp_cb(const void *id1, const void *id2) +{ + uint32 d1= ((rpl_gtid *)id1)->domain_id; + uint32 d2= ((rpl_gtid *)id2)->domain_id; + + if (d1 < d2) + return -1; + else if (d1 > d2) + return 1; + return 0; +} + +/* Format the specified gtid and store it in the given string buffer. */ +bool +rpl_slave_state_tostring_helper(String *dest, const rpl_gtid *gtid, bool *first) +{ + if (*first) + *first= false; + else + if (dest->append(",",1)) + return true; + return + dest->append_ulonglong(gtid->domain_id) || + dest->append("-",1) || + dest->append_ulonglong(gtid->server_id) || + dest->append("-",1) || + dest->append_ulonglong(gtid->seq_no); +} + +/* + Sort the given gtid list based on domain_id and store them in the specified + string. +*/ +static bool +rpl_slave_state_tostring_helper(DYNAMIC_ARRAY *gtid_dynarr, String *str) +{ + bool first= true, res= true; + + sort_dynamic(gtid_dynarr, rpl_gtid_cmp_cb); + + for (uint i= 0; i < gtid_dynarr->elements; i ++) + { + rpl_gtid *gtid= dynamic_element(gtid_dynarr, i, rpl_gtid *); + if (rpl_slave_state_tostring_helper(str, gtid, &first)) + goto err; + } + res= false; + +err: + return res; +} + + +/* Sort the given gtid list based on domain_id and call cb for each gtid. */ +static bool +rpl_slave_state_tostring_helper(DYNAMIC_ARRAY *gtid_dynarr, + int (*cb)(rpl_gtid *, void *), + void *data) +{ + rpl_gtid *gtid; + bool res= true; + + sort_dynamic(gtid_dynarr, rpl_gtid_cmp_cb); + + for (uint i= 0; i < gtid_dynarr->elements; i ++) + { + gtid= dynamic_element(gtid_dynarr, i, rpl_gtid *); + if ((*cb)(gtid, data)) + goto err; + } + res= false; + +err: + return res; +} + +int +rpl_slave_state::iterate(int (*cb)(rpl_gtid *, void *), void *data, + rpl_gtid *extra_gtids, uint32 num_extra, + bool sort) +{ + uint32 i; + HASH gtid_hash; + uchar *rec; + rpl_gtid *gtid; + int res= 1; + bool locked= false; + + my_hash_init(>id_hash, &my_charset_bin, 32, offsetof(rpl_gtid, domain_id), + sizeof(uint32), NULL, NULL, HASH_UNIQUE); + for (i= 0; i < num_extra; ++i) + if (extra_gtids[i].server_id == global_system_variables.server_id && + my_hash_insert(>id_hash, (uchar *)(&extra_gtids[i]))) + goto err; + + mysql_mutex_lock(&LOCK_slave_state); + locked= true; + reset_dynamic(>id_sort_array); + + for (i= 0; i < hash.records; ++i) + { + uint64 best_sub_id; + rpl_gtid best_gtid; + element *e= (element *)my_hash_element(&hash, i); + list_element *l= e->list; + + if (!l) + continue; /* Nothing here */ + + best_gtid.domain_id= e->domain_id; + best_gtid.server_id= l->server_id; + best_gtid.seq_no= l->seq_no; + best_sub_id= l->sub_id; + while ((l= l->next)) + { + if (l->sub_id > best_sub_id) + { + best_sub_id= l->sub_id; + best_gtid.server_id= l->server_id; + best_gtid.seq_no= l->seq_no; + } + } + + /* Check if we have something newer in the extra list. */ + rec= my_hash_search(>id_hash, (const uchar *)&best_gtid.domain_id, 0); + if (rec) + { + gtid= (rpl_gtid *)rec; + if (gtid->seq_no > best_gtid.seq_no) + memcpy(&best_gtid, gtid, sizeof(best_gtid)); + if (my_hash_delete(>id_hash, rec)) + { + goto err; + } + } + + if ((res= sort ? insert_dynamic(>id_sort_array, + (const void *) &best_gtid) : + (*cb)(&best_gtid, data))) + { + goto err; + } + } + + /* Also add any remaining extra domain_ids. */ + for (i= 0; i < gtid_hash.records; ++i) + { + gtid= (rpl_gtid *)my_hash_element(>id_hash, i); + if ((res= sort ? insert_dynamic(>id_sort_array, (const void *) gtid) : + (*cb)(gtid, data))) + { + goto err; + } + } + + if (sort && rpl_slave_state_tostring_helper(>id_sort_array, cb, data)) + { + goto err; + } + + res= 0; + +err: + if (locked) mysql_mutex_unlock(&LOCK_slave_state); + my_hash_free(>id_hash); + + return res; +} + + +struct rpl_slave_state_tostring_data { + String *dest; + bool first; +}; +static int +rpl_slave_state_tostring_cb(rpl_gtid *gtid, void *data) +{ + rpl_slave_state_tostring_data *p= (rpl_slave_state_tostring_data *)data; + return rpl_slave_state_tostring_helper(p->dest, gtid, &p->first); +} + + +/* + Prepare the current slave state as a string, suitable for sending to the + master to request to receive binlog events starting from that GTID state. + + The state consists of the most recently applied GTID for each domain_id, + ie. the one with the highest sub_id within each domain_id. + + Optinally, extra_gtids is a list of GTIDs from the binlog. This is used when + a server was previously a master and now needs to connect to a new master as + a slave. For each domain_id, if the GTID in the binlog was logged with our + own server_id _and_ has a higher seq_no than what is in the slave state, + then this should be used as the position to start replicating at. This + allows to promote a slave as new master, and connect the old master as a + slave with MASTER_GTID_POS=AUTO. +*/ +int +rpl_slave_state::tostring(String *dest, rpl_gtid *extra_gtids, uint32 num_extra) +{ + struct rpl_slave_state_tostring_data data; + data.first= true; + data.dest= dest; + + return iterate(rpl_slave_state_tostring_cb, &data, extra_gtids, + num_extra, true); +} + + +/* + Lookup a domain_id in the current replication slave state. + + Returns false if the domain_id has no entries in the slave state. + Otherwise returns true, and fills in out_gtid with the corresponding + GTID. +*/ +bool +rpl_slave_state::domain_to_gtid(uint32 domain_id, rpl_gtid *out_gtid) +{ + element *elem; + list_element *list; + uint64 best_sub_id; + + mysql_mutex_lock(&LOCK_slave_state); + elem= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0); + if (!elem || !(list= elem->list)) + { + mysql_mutex_unlock(&LOCK_slave_state); + return false; + } + + out_gtid->domain_id= domain_id; + out_gtid->server_id= list->server_id; + out_gtid->seq_no= list->seq_no; + best_sub_id= list->sub_id; + + while ((list= list->next)) + { + if (best_sub_id > list->sub_id) + continue; + best_sub_id= list->sub_id; + out_gtid->server_id= list->server_id; + out_gtid->seq_no= list->seq_no; + } + + mysql_mutex_unlock(&LOCK_slave_state); + return true; +} + + +/* + Parse a GTID at the start of a string, and update the pointer to point + at the first character after the parsed GTID. + + Returns 0 on ok, non-zero on parse error. +*/ +static int +gtid_parser_helper(char **ptr, char *end, rpl_gtid *out_gtid) +{ + char *q; + char *p= *ptr; + uint64 v1, v2, v3; + int err= 0; + + q= end; + v1= (uint64)my_strtoll10(p, &q, &err); + if (err != 0 || v1 > (uint32)0xffffffff || q == end || *q != '-') + return 1; + p= q+1; + q= end; + v2= (uint64)my_strtoll10(p, &q, &err); + if (err != 0 || v2 > (uint32)0xffffffff || q == end || *q != '-') + return 1; + p= q+1; + q= end; + v3= (uint64)my_strtoll10(p, &q, &err); + if (err != 0) + return 1; + + out_gtid->domain_id= v1; + out_gtid->server_id= v2; + out_gtid->seq_no= v3; + *ptr= q; + return 0; +} + + +rpl_gtid * +gtid_parse_string_to_list(const char *str, size_t str_len, uint32 *out_len) +{ + char *p= const_cast<char *>(str); + char *end= p + str_len; + uint32 len= 0, alloc_len= 5; + rpl_gtid *list= NULL; + + for (;;) + { + rpl_gtid gtid; + + if (len >= (((uint32)1 << 28)-1) || gtid_parser_helper(&p, end, >id)) + { + my_free(list); + return NULL; + } + if ((!list || len >= alloc_len) && + !(list= + (rpl_gtid *)my_realloc(list, + (alloc_len= alloc_len*2) * sizeof(rpl_gtid), + MYF(MY_FREE_ON_ERROR|MY_ALLOW_ZERO_PTR)))) + return NULL; + list[len++]= gtid; + + if (p == end) + break; + if (*p != ',') + { + my_free(list); + return NULL; + } + ++p; + } + *out_len= len; + return list; +} + + +/* + Update the slave replication state with the GTID position obtained from + master when connecting with old-style (filename,offset) position. + + If RESET is true then all existing entries are removed. Otherwise only + domain_ids mentioned in the STATE_FROM_MASTER are changed. + + Returns 0 if ok, non-zero if error. +*/ +int +rpl_slave_state::load(THD *thd, char *state_from_master, size_t len, + bool reset, bool in_statement) +{ + char *end= state_from_master + len; + + if (reset) + { + if (truncate_state_table(thd)) + return 1; + truncate_hash(); + } + if (state_from_master == end) + return 0; + for (;;) + { + rpl_gtid gtid; + uint64 sub_id; + + if (gtid_parser_helper(&state_from_master, end, >id) || + !(sub_id= next_sub_id(gtid.domain_id)) || + record_gtid(thd, >id, sub_id, NULL, in_statement) || + update(gtid.domain_id, gtid.server_id, sub_id, gtid.seq_no, NULL)) + return 1; + if (state_from_master == end) + break; + if (*state_from_master != ',') + return 1; + ++state_from_master; + } + return 0; +} + + +bool +rpl_slave_state::is_empty() +{ + uint32 i; + bool result= true; + + mysql_mutex_lock(&LOCK_slave_state); + for (i= 0; i < hash.records; ++i) + { + element *e= (element *)my_hash_element(&hash, i); + if (e->list) + { + result= false; + break; + } + } + mysql_mutex_unlock(&LOCK_slave_state); + + return result; +} + + +rpl_binlog_state::rpl_binlog_state() +{ + my_hash_init(&hash, &my_charset_bin, 32, offsetof(element, domain_id), + sizeof(uint32), NULL, my_free, HASH_UNIQUE); + my_init_dynamic_array(>id_sort_array, sizeof(rpl_gtid), 8, 8, MYF(0)); + mysql_mutex_init(key_LOCK_binlog_state, &LOCK_binlog_state, + MY_MUTEX_INIT_SLOW); + initialized= 1; +} + + +void +rpl_binlog_state::reset_nolock() +{ + uint32 i; + + for (i= 0; i < hash.records; ++i) + my_hash_free(&((element *)my_hash_element(&hash, i))->hash); + my_hash_reset(&hash); +} + + +void +rpl_binlog_state::reset() +{ + mysql_mutex_lock(&LOCK_binlog_state); + reset_nolock(); + mysql_mutex_unlock(&LOCK_binlog_state); +} + + +void rpl_binlog_state::free() +{ + if (initialized) + { + initialized= 0; + reset_nolock(); + my_hash_free(&hash); + delete_dynamic(>id_sort_array); + mysql_mutex_destroy(&LOCK_binlog_state); + } +} + + +bool +rpl_binlog_state::load(struct rpl_gtid *list, uint32 count) +{ + uint32 i; + bool res= false; + + mysql_mutex_lock(&LOCK_binlog_state); + reset_nolock(); + for (i= 0; i < count; ++i) + { + if (update_nolock(&(list[i]), false)) + { + res= true; + break; + } + } + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + + +static int rpl_binlog_state_load_cb(rpl_gtid *gtid, void *data) +{ + rpl_binlog_state *self= (rpl_binlog_state *)data; + return self->update_nolock(gtid, false); +} + + +bool +rpl_binlog_state::load(rpl_slave_state *slave_pos) +{ + bool res= false; + + mysql_mutex_lock(&LOCK_binlog_state); + reset_nolock(); + if (slave_pos->iterate(rpl_binlog_state_load_cb, this, NULL, 0, false)) + res= true; + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + + +rpl_binlog_state::~rpl_binlog_state() +{ + free(); +} + + +/* + Update replication state with a new GTID. + + If the (domain_id, server_id) pair already exists, then the new GTID replaces + the old one for that domain id. Else a new entry is inserted. + + Returns 0 for ok, 1 for error. +*/ +int +rpl_binlog_state::update_nolock(const struct rpl_gtid *gtid, bool strict) +{ + element *elem; + + if ((elem= (element *)my_hash_search(&hash, + (const uchar *)(>id->domain_id), 0))) + { + if (strict && elem->last_gtid && elem->last_gtid->seq_no >= gtid->seq_no) + { + my_error(ER_GTID_STRICT_OUT_OF_ORDER, MYF(0), gtid->domain_id, + gtid->server_id, gtid->seq_no, elem->last_gtid->domain_id, + elem->last_gtid->server_id, elem->last_gtid->seq_no); + return 1; + } + if (elem->seq_no_counter < gtid->seq_no) + elem->seq_no_counter= gtid->seq_no; + if (!elem->update_element(gtid)) + return 0; + } + else if (!alloc_element_nolock(gtid)) + return 0; + + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return 1; +} + + +int +rpl_binlog_state::update(const struct rpl_gtid *gtid, bool strict) +{ + int res; + mysql_mutex_lock(&LOCK_binlog_state); + res= update_nolock(gtid, strict); + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + + +/* + Fill in a new GTID, allocating next sequence number, and update state + accordingly. +*/ +int +rpl_binlog_state::update_with_next_gtid(uint32 domain_id, uint32 server_id, + rpl_gtid *gtid) +{ + element *elem; + int res= 0; + + gtid->domain_id= domain_id; + gtid->server_id= server_id; + + mysql_mutex_lock(&LOCK_binlog_state); + if ((elem= (element *)my_hash_search(&hash, (const uchar *)(&domain_id), 0))) + { + gtid->seq_no= ++elem->seq_no_counter; + if (!elem->update_element(gtid)) + goto end; + } + else + { + gtid->seq_no= 1; + if (!alloc_element_nolock(gtid)) + goto end; + } + + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + res= 1; +end: + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + + +/* Helper functions for update. */ +int +rpl_binlog_state::element::update_element(const rpl_gtid *gtid) +{ + rpl_gtid *lookup_gtid; + + /* + By far the most common case is that successive events within same + replication domain have the same server id (it changes only when + switching to a new master). So save a hash lookup in this case. + */ + if (likely(last_gtid && last_gtid->server_id == gtid->server_id)) + { + last_gtid->seq_no= gtid->seq_no; + return 0; + } + + lookup_gtid= (rpl_gtid *) + my_hash_search(&hash, (const uchar *)>id->server_id, 0); + if (lookup_gtid) + { + lookup_gtid->seq_no= gtid->seq_no; + last_gtid= lookup_gtid; + return 0; + } + + /* Allocate a new GTID and insert it. */ + lookup_gtid= (rpl_gtid *)my_malloc(sizeof(*lookup_gtid), MYF(MY_WME)); + if (!lookup_gtid) + return 1; + memcpy(lookup_gtid, gtid, sizeof(*lookup_gtid)); + if (my_hash_insert(&hash, (const uchar *)lookup_gtid)) + { + my_free(lookup_gtid); + return 1; + } + last_gtid= lookup_gtid; + return 0; +} + + +int +rpl_binlog_state::alloc_element_nolock(const rpl_gtid *gtid) +{ + element *elem; + rpl_gtid *lookup_gtid; + + /* First time we see this domain_id; allocate a new element. */ + elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME)); + lookup_gtid= (rpl_gtid *)my_malloc(sizeof(*lookup_gtid), MYF(MY_WME)); + if (elem && lookup_gtid) + { + elem->domain_id= gtid->domain_id; + my_hash_init(&elem->hash, &my_charset_bin, 32, + offsetof(rpl_gtid, server_id), sizeof(uint32), NULL, my_free, + HASH_UNIQUE); + elem->last_gtid= lookup_gtid; + elem->seq_no_counter= gtid->seq_no; + memcpy(lookup_gtid, gtid, sizeof(*lookup_gtid)); + if (0 == my_hash_insert(&elem->hash, (const uchar *)lookup_gtid)) + { + lookup_gtid= NULL; /* Do not free. */ + if (0 == my_hash_insert(&hash, (const uchar *)elem)) + return 0; + } + my_hash_free(&elem->hash); + } + + /* An error. */ + if (elem) + my_free(elem); + if (lookup_gtid) + my_free(lookup_gtid); + return 1; +} + + +/* + Check that a new GTID can be logged without creating an out-of-order + sequence number with existing GTIDs. +*/ +bool +rpl_binlog_state::check_strict_sequence(uint32 domain_id, uint32 server_id, + uint64 seq_no) +{ + element *elem; + bool res= 0; + + mysql_mutex_lock(&LOCK_binlog_state); + if ((elem= (element *)my_hash_search(&hash, + (const uchar *)(&domain_id), 0)) && + elem->last_gtid && elem->last_gtid->seq_no >= seq_no) + { + my_error(ER_GTID_STRICT_OUT_OF_ORDER, MYF(0), domain_id, server_id, seq_no, + elem->last_gtid->domain_id, elem->last_gtid->server_id, + elem->last_gtid->seq_no); + res= 1; + } + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + + +/* + When we see a new GTID that will not be binlogged (eg. slave thread + with --log-slave-updates=0), then we need to remember to allocate any + GTID seq_no of our own within that domain starting from there. + + Returns 0 if ok, non-zero if out-of-memory. +*/ +int +rpl_binlog_state::bump_seq_no_if_needed(uint32 domain_id, uint64 seq_no) +{ + element *elem; + int res; + + mysql_mutex_lock(&LOCK_binlog_state); + if ((elem= (element *)my_hash_search(&hash, (const uchar *)(&domain_id), 0))) + { + if (elem->seq_no_counter < seq_no) + elem->seq_no_counter= seq_no; + res= 0; + goto end; + } + + /* We need to allocate a new, empty element to remember the next seq_no. */ + if (!(elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME)))) + { + res= 1; + goto end; + } + + elem->domain_id= domain_id; + my_hash_init(&elem->hash, &my_charset_bin, 32, + offsetof(rpl_gtid, server_id), sizeof(uint32), NULL, my_free, + HASH_UNIQUE); + elem->last_gtid= NULL; + elem->seq_no_counter= seq_no; + if (0 == my_hash_insert(&hash, (const uchar *)elem)) + { + res= 0; + goto end; + } + + my_hash_free(&elem->hash); + my_free(elem); + res= 1; + +end: + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + + +/* + Write binlog state to text file, so we can read it in again without having + to scan last binlog file (normal shutdown/startup, not crash recovery). + + The most recent GTID within each domain_id is written after any other GTID + within this domain. +*/ +int +rpl_binlog_state::write_to_iocache(IO_CACHE *dest) +{ + ulong i, j; + char buf[21]; + int res= 0; + + mysql_mutex_lock(&LOCK_binlog_state); + for (i= 0; i < hash.records; ++i) + { + size_t res; + element *e= (element *)my_hash_element(&hash, i); + if (!e->last_gtid) + { + DBUG_ASSERT(e->hash.records == 0); + continue; + } + for (j= 0; j <= e->hash.records; ++j) + { + const rpl_gtid *gtid; + if (j < e->hash.records) + { + gtid= (const rpl_gtid *)my_hash_element(&e->hash, j); + if (gtid == e->last_gtid) + continue; + } + else + gtid= e->last_gtid; + + longlong10_to_str(gtid->seq_no, buf, 10); + res= my_b_printf(dest, "%u-%u-%s\n", gtid->domain_id, gtid->server_id, buf); + if (res == (size_t) -1) + { + res= 1; + goto end; + } + } + } + +end: + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + + +int +rpl_binlog_state::read_from_iocache(IO_CACHE *src) +{ + /* 10-digit - 10-digit - 20-digit \n \0 */ + char buf[10+1+10+1+20+1+1]; + char *p, *end; + rpl_gtid gtid; + int res= 0; + + mysql_mutex_lock(&LOCK_binlog_state); + reset_nolock(); + for (;;) + { + size_t len= my_b_gets(src, buf, sizeof(buf)); + if (!len) + break; + p= buf; + end= buf + len; + if (gtid_parser_helper(&p, end, >id) || + update_nolock(>id, false)) + { + res= 1; + break; + } + } + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + + +rpl_gtid * +rpl_binlog_state::find_nolock(uint32 domain_id, uint32 server_id) +{ + element *elem; + if (!(elem= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0))) + return NULL; + return (rpl_gtid *)my_hash_search(&elem->hash, (const uchar *)&server_id, 0); +} + +rpl_gtid * +rpl_binlog_state::find(uint32 domain_id, uint32 server_id) +{ + rpl_gtid *p; + mysql_mutex_lock(&LOCK_binlog_state); + p= find_nolock(domain_id, server_id); + mysql_mutex_unlock(&LOCK_binlog_state); + return p; +} + +rpl_gtid * +rpl_binlog_state::find_most_recent(uint32 domain_id) +{ + element *elem; + rpl_gtid *gtid= NULL; + + mysql_mutex_lock(&LOCK_binlog_state); + elem= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0); + if (elem && elem->last_gtid) + gtid= elem->last_gtid; + mysql_mutex_unlock(&LOCK_binlog_state); + + return gtid; +} + + +uint32 +rpl_binlog_state::count() +{ + uint32 c= 0; + uint32 i; + + mysql_mutex_lock(&LOCK_binlog_state); + for (i= 0; i < hash.records; ++i) + c+= ((element *)my_hash_element(&hash, i))->hash.records; + mysql_mutex_unlock(&LOCK_binlog_state); + + return c; +} + + +int +rpl_binlog_state::get_gtid_list(rpl_gtid *gtid_list, uint32 list_size) +{ + uint32 i, j, pos; + int res= 0; + + mysql_mutex_lock(&LOCK_binlog_state); + pos= 0; + for (i= 0; i < hash.records; ++i) + { + element *e= (element *)my_hash_element(&hash, i); + if (!e->last_gtid) + { + DBUG_ASSERT(e->hash.records==0); + continue; + } + for (j= 0; j <= e->hash.records; ++j) + { + const rpl_gtid *gtid; + if (j < e->hash.records) + { + gtid= (rpl_gtid *)my_hash_element(&e->hash, j); + if (gtid == e->last_gtid) + continue; + } + else + gtid= e->last_gtid; + + if (pos >= list_size) + { + res= 1; + goto end; + } + memcpy(>id_list[pos++], gtid, sizeof(*gtid)); + } + } + +end: + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + + +/* + Get a list of the most recently binlogged GTID, for each domain_id. + + This can be used when switching from being a master to being a slave, + to know where to start replicating from the new master. + + The returned list must be de-allocated with my_free(). + + Returns 0 for ok, non-zero for out-of-memory. +*/ +int +rpl_binlog_state::get_most_recent_gtid_list(rpl_gtid **list, uint32 *size) +{ + uint32 i; + uint32 alloc_size, out_size; + int res= 0; + + out_size= 0; + mysql_mutex_lock(&LOCK_binlog_state); + alloc_size= hash.records; + if (!(*list= (rpl_gtid *)my_malloc(alloc_size * sizeof(rpl_gtid), + MYF(MY_WME)))) + { + res= 1; + goto end; + } + for (i= 0; i < alloc_size; ++i) + { + element *e= (element *)my_hash_element(&hash, i); + if (!e->last_gtid) + continue; + memcpy(&((*list)[out_size++]), e->last_gtid, sizeof(rpl_gtid)); + } + +end: + mysql_mutex_unlock(&LOCK_binlog_state); + *size= out_size; + return res; +} + +bool +rpl_binlog_state::append_pos(String *str) +{ + uint32 i; + + mysql_mutex_lock(&LOCK_binlog_state); + reset_dynamic(>id_sort_array); + + for (i= 0; i < hash.records; ++i) + { + element *e= (element *)my_hash_element(&hash, i); + if (e->last_gtid && + insert_dynamic(>id_sort_array, (const void *) e->last_gtid)) + { + mysql_mutex_unlock(&LOCK_binlog_state); + return true; + } + } + rpl_slave_state_tostring_helper(>id_sort_array, str); + mysql_mutex_unlock(&LOCK_binlog_state); + + return false; +} + + +bool +rpl_binlog_state::append_state(String *str) +{ + uint32 i, j; + bool res= false; + + mysql_mutex_lock(&LOCK_binlog_state); + reset_dynamic(>id_sort_array); + + for (i= 0; i < hash.records; ++i) + { + element *e= (element *)my_hash_element(&hash, i); + if (!e->last_gtid) + { + DBUG_ASSERT(e->hash.records==0); + continue; + } + for (j= 0; j <= e->hash.records; ++j) + { + const rpl_gtid *gtid; + if (j < e->hash.records) + { + gtid= (rpl_gtid *)my_hash_element(&e->hash, j); + if (gtid == e->last_gtid) + continue; + } + else + gtid= e->last_gtid; + + if (insert_dynamic(>id_sort_array, (const void *) gtid)) + { + res= true; + goto end; + } + } + } + + rpl_slave_state_tostring_helper(>id_sort_array, str); + +end: + mysql_mutex_unlock(&LOCK_binlog_state); + return res; +} + +/** + Remove domains supplied by the first argument from binlog state. + Removal is done for any domain whose last gtids (from all its servers) match + ones in Gtid list event of the 2nd argument. + + @param ids gtid domain id sequence, may contain dups + @param glev pointer to Gtid list event describing + the match condition + @param errbuf [out] pointer to possible error message array + + @retval NULL as success when at least one domain is removed + @retval "" empty string to indicate ineffective call + when no domains removed + @retval NOT EMPTY string otherwise an error message +*/ +const char* +rpl_binlog_state::drop_domain(DYNAMIC_ARRAY *ids, + Gtid_list_log_event *glev, + char* errbuf) +{ + DYNAMIC_ARRAY domain_unique; // sequece (unsorted) of unique element*:s + rpl_binlog_state::element* domain_unique_buffer[16]; + ulong k, l; + const char* errmsg= NULL; + + DBUG_ENTER("rpl_binlog_state::drop_domain"); + + my_init_dynamic_array2(&domain_unique, + sizeof(element*), domain_unique_buffer, + sizeof(domain_unique_buffer) / sizeof(element*), 4, 0); + + mysql_mutex_lock(&LOCK_binlog_state); + + /* + Gtid list is supposed to come from a binlog's Gtid_list event and + therefore should be a subset of the current binlog state. That is + for every domain in the list the binlog state contains a gtid with + sequence number not less than that of the list. + Exceptions of this inclusion rule are: + A. the list may still refer to gtids from already deleted domains. + Files containing them must have been purged whereas the file + with the list is not yet. + B. out of order groups were injected + C. manually build list of binlog files violating the inclusion + constraint. + While A is a normal case (not necessarily distinguishable from C though), + B and C may require the user's attention so any (incl the A's suspected) + inconsistency is diagnosed and *warned*. + */ + for (l= 0, errbuf[0]= 0; l < glev->count; l++, errbuf[0]= 0) + { + rpl_gtid* rb_state_gtid= find_nolock(glev->list[l].domain_id, + glev->list[l].server_id); + if (!rb_state_gtid) + sprintf(errbuf, + "missing gtids from the '%u-%u' domain-server pair which is " + "referred to in the gtid list describing an earlier state. Ignore " + "if the domain ('%u') was already explicitly deleted", + glev->list[l].domain_id, glev->list[l].server_id, + glev->list[l].domain_id); + else if (rb_state_gtid->seq_no < glev->list[l].seq_no) + sprintf(errbuf, + "having a gtid '%u-%u-%llu' which is less than " + "the '%u-%u-%llu' of the gtid list describing an earlier state. " + "The state may have been affected by manually injecting " + "a lower sequence number gtid or via replication", + rb_state_gtid->domain_id, rb_state_gtid->server_id, + rb_state_gtid->seq_no, glev->list[l].domain_id, + glev->list[l].server_id, glev->list[l].seq_no); + if (strlen(errbuf)) // use strlen() as cheap flag + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_BINLOG_CANT_DELETE_GTID_DOMAIN, + "The current gtid binlog state is incompatible with " + "a former one %s.", errbuf); + } + + /* + For each domain_id from ids + when no such domain in binlog state + warn && continue + For each domain.server's last gtid + when not locate the last gtid in glev.list + error out binlog state can't change + otherwise continue + */ + for (ulong i= 0; i < ids->elements; i++) + { + rpl_binlog_state::element *elem= NULL; + uint32 *ptr_domain_id; + bool not_match; + + ptr_domain_id= (uint32*) dynamic_array_ptr(ids, i); + elem= (rpl_binlog_state::element *) + my_hash_search(&hash, (const uchar *) ptr_domain_id, 0); + if (!elem) + { + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_BINLOG_CANT_DELETE_GTID_DOMAIN, + "The gtid domain being deleted ('%lu') is not in " + "the current binlog state", *ptr_domain_id); + continue; + } + + for (not_match= true, k= 0; k < elem->hash.records; k++) + { + rpl_gtid *d_gtid= (rpl_gtid *)my_hash_element(&elem->hash, k); + for (ulong l= 0; l < glev->count && not_match; l++) + not_match= !(*d_gtid == glev->list[l]); + } + + if (not_match) + { + sprintf(errbuf, "binlog files may contain gtids from the domain ('%u') " + "being deleted. Make sure to first purge those files", + *ptr_domain_id); + errmsg= errbuf; + goto end; + } + // compose a sequence of unique pointers to domain object + for (k= 0; k < domain_unique.elements; k++) + { + if ((rpl_binlog_state::element*) dynamic_array_ptr(&domain_unique, k) + == elem) + break; // domain_id's elem has been already in + } + if (k == domain_unique.elements) // proven not to have duplicates + insert_dynamic(&domain_unique, (uchar*) &elem); + } + + // Domain removal from binlog state + for (k= 0; k < domain_unique.elements; k++) + { + rpl_binlog_state::element *elem= *(rpl_binlog_state::element**) + dynamic_array_ptr(&domain_unique, k); + my_hash_free(&elem->hash); + my_hash_delete(&hash, (uchar*) elem); + } + + DBUG_ASSERT(strlen(errbuf) == 0); + + if (domain_unique.elements == 0) + errmsg= ""; + +end: + mysql_mutex_unlock(&LOCK_binlog_state); + delete_dynamic(&domain_unique); + + DBUG_RETURN(errmsg); +} + +slave_connection_state::slave_connection_state() +{ + my_hash_init(&hash, &my_charset_bin, 32, + offsetof(entry, gtid) + offsetof(rpl_gtid, domain_id), + sizeof(uint32), NULL, my_free, HASH_UNIQUE); + my_init_dynamic_array(>id_sort_array, sizeof(rpl_gtid), 8, 8, MYF(0)); +} + + +slave_connection_state::~slave_connection_state() +{ + my_hash_free(&hash); + delete_dynamic(>id_sort_array); +} + + +/* + Create a hash from the slave GTID state that is sent to master when slave + connects to start replication. + + The state is sent as <GTID>,<GTID>,...,<GTID>, for example: + + 0-2-112,1-4-1022 + + The state gives for each domain_id the GTID to start replication from for + the corresponding replication stream. So domain_id must be unique. + + Returns 0 if ok, non-zero if error due to malformed input. + + Note that input string is built by slave server, so it will not be incorrect + unless bug/corruption/malicious server. So we just need basic sanity check, + not fancy user-friendly error message. +*/ + +int +slave_connection_state::load(char *slave_request, size_t len) +{ + char *p, *end; + uchar *rec; + rpl_gtid *gtid; + const entry *e; + + reset(); + p= slave_request; + end= slave_request + len; + if (p == end) + return 0; + for (;;) + { + if (!(rec= (uchar *)my_malloc(sizeof(entry), MYF(MY_WME)))) + { + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(*gtid)); + return 1; + } + gtid= &((entry *)rec)->gtid; + if (gtid_parser_helper(&p, end, gtid)) + { + my_free(rec); + my_error(ER_INCORRECT_GTID_STATE, MYF(0)); + return 1; + } + if ((e= (const entry *) + my_hash_search(&hash, (const uchar *)(>id->domain_id), 0))) + { + my_error(ER_DUPLICATE_GTID_DOMAIN, MYF(0), gtid->domain_id, + gtid->server_id, (ulonglong)gtid->seq_no, e->gtid.domain_id, + e->gtid.server_id, (ulonglong)e->gtid.seq_no, gtid->domain_id); + my_free(rec); + return 1; + } + ((entry *)rec)->flags= 0; + if (my_hash_insert(&hash, rec)) + { + my_free(rec); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return 1; + } + if (p == end) + break; /* Finished. */ + if (*p != ',') + { + my_error(ER_INCORRECT_GTID_STATE, MYF(0)); + return 1; + } + ++p; + } + + return 0; +} + + +int +slave_connection_state::load(const rpl_gtid *gtid_list, uint32 count) +{ + uint32 i; + + reset(); + for (i= 0; i < count; ++i) + if (update(>id_list[i])) + return 1; + return 0; +} + + +static int +slave_connection_state_load_cb(rpl_gtid *gtid, void *data) +{ + slave_connection_state *state= (slave_connection_state *)data; + return state->update(gtid); +} + + +/* + Same as rpl_slave_state::tostring(), but populates a slave_connection_state + instead. +*/ +int +slave_connection_state::load(rpl_slave_state *state, + rpl_gtid *extra_gtids, uint32 num_extra) +{ + reset(); + return state->iterate(slave_connection_state_load_cb, this, + extra_gtids, num_extra, false); +} + + +slave_connection_state::entry * +slave_connection_state::find_entry(uint32 domain_id) +{ + return (entry *) my_hash_search(&hash, (const uchar *)(&domain_id), 0); +} + + +rpl_gtid * +slave_connection_state::find(uint32 domain_id) +{ + entry *e= find_entry(domain_id); + if (!e) + return NULL; + return &e->gtid; +} + + +int +slave_connection_state::update(const rpl_gtid *in_gtid) +{ + entry *e; + uchar *rec= my_hash_search(&hash, (const uchar *)(&in_gtid->domain_id), 0); + if (rec) + { + e= (entry *)rec; + e->gtid= *in_gtid; + return 0; + } + + if (!(e= (entry *)my_malloc(sizeof(*e), MYF(MY_WME)))) + return 1; + e->gtid= *in_gtid; + e->flags= 0; + if (my_hash_insert(&hash, (uchar *)e)) + { + my_free(e); + return 1; + } + + return 0; +} + + +void +slave_connection_state::remove(const rpl_gtid *in_gtid) +{ + uchar *rec= my_hash_search(&hash, (const uchar *)(&in_gtid->domain_id), 0); +#ifndef DBUG_OFF + bool err; + rpl_gtid *slave_gtid= &((entry *)rec)->gtid; + DBUG_ASSERT(rec /* We should never try to remove not present domain_id. */); + DBUG_ASSERT(slave_gtid->server_id == in_gtid->server_id); + DBUG_ASSERT(slave_gtid->seq_no == in_gtid->seq_no); +#endif + + IF_DBUG(err=, ) + my_hash_delete(&hash, rec); + DBUG_ASSERT(!err); +} + + +void +slave_connection_state::remove_if_present(const rpl_gtid *in_gtid) +{ + uchar *rec= my_hash_search(&hash, (const uchar *)(&in_gtid->domain_id), 0); + if (rec) + my_hash_delete(&hash, rec); +} + + +int +slave_connection_state::to_string(String *out_str) +{ + out_str->length(0); + return append_to_string(out_str); +} + + +int +slave_connection_state::append_to_string(String *out_str) +{ + uint32 i; + bool first; + + first= true; + for (i= 0; i < hash.records; ++i) + { + const entry *e= (const entry *)my_hash_element(&hash, i); + if (rpl_slave_state_tostring_helper(out_str, &e->gtid, &first)) + return 1; + } + return 0; +} + + +int +slave_connection_state::get_gtid_list(rpl_gtid *gtid_list, uint32 list_size) +{ + uint32 i, pos; + + pos= 0; + for (i= 0; i < hash.records; ++i) + { + entry *e; + if (pos >= list_size) + return 1; + e= (entry *)my_hash_element(&hash, i); + memcpy(>id_list[pos++], &e->gtid, sizeof(e->gtid)); + } + + return 0; +} + + +/* + Check if the GTID position has been reached, for mysql_binlog_send(). + + The position has not been reached if we have anything in the state, unless + it has either the START_ON_EMPTY_DOMAIN flag set (which means it does not + belong to this master at all), or the START_OWN_SLAVE_POS (which means that + we start on an old position from when the server was a slave with + --log-slave-updates=0). +*/ +bool +slave_connection_state::is_pos_reached() +{ + uint32 i; + + for (i= 0; i < hash.records; ++i) + { + entry *e= (entry *)my_hash_element(&hash, i); + if (!(e->flags & (START_OWN_SLAVE_POS|START_ON_EMPTY_DOMAIN))) + return false; + } + + return true; +} + + +/* + Execute a MASTER_GTID_WAIT(). + The position to wait for is in gtid_str in string form. + The timeout in microseconds is in timeout_us, zero means no timeout. + + Returns: + 1 for error. + 0 for wait completed. + -1 for wait timed out. +*/ +int +gtid_waiting::wait_for_pos(THD *thd, String *gtid_str, longlong timeout_us) +{ + int err; + rpl_gtid *wait_pos; + uint32 count, i; + struct timespec wait_until, *wait_until_ptr; + ulonglong before; + + /* Wait for the empty position returns immediately. */ + if (gtid_str->length() == 0) + { + status_var_increment(thd->status_var.master_gtid_wait_count); + return 0; + } + + if (!(wait_pos= gtid_parse_string_to_list(gtid_str->ptr(), gtid_str->length(), + &count))) + { + my_error(ER_INCORRECT_GTID_STATE, MYF(0)); + return 1; + } + status_var_increment(thd->status_var.master_gtid_wait_count); + before= microsecond_interval_timer(); + + if (timeout_us >= 0) + { + set_timespec_nsec(wait_until, (ulonglong)1000*timeout_us); + wait_until_ptr= &wait_until; + } + else + wait_until_ptr= NULL; + err= 0; + for (i= 0; i < count; ++i) + { + if ((err= wait_for_gtid(thd, &wait_pos[i], wait_until_ptr))) + break; + } + switch (err) + { + case -1: + status_var_increment(thd->status_var.master_gtid_wait_timeouts); + /* fall through */ + case 0: + status_var_add(thd->status_var.master_gtid_wait_time, + microsecond_interval_timer() - before); + } + my_free(wait_pos); + return err; +} + + +void +gtid_waiting::promote_new_waiter(gtid_waiting::hash_element *he) +{ + queue_element *qe; + + mysql_mutex_assert_owner(&LOCK_gtid_waiting); + if (queue_empty(&he->queue)) + return; + qe= (queue_element *)queue_top(&he->queue); + qe->do_small_wait= true; + mysql_cond_signal(&qe->thd->COND_wakeup_ready); +} + +void +gtid_waiting::process_wait_hash(uint64 wakeup_seq_no, + gtid_waiting::hash_element *he) +{ + mysql_mutex_assert_owner(&LOCK_gtid_waiting); + + for (;;) + { + queue_element *qe; + + if (queue_empty(&he->queue)) + break; + qe= (queue_element *)queue_top(&he->queue); + if (qe->wait_seq_no > wakeup_seq_no) + break; + DBUG_ASSERT(!qe->done); + queue_remove_top(&he->queue); + qe->done= true;; + mysql_cond_signal(&qe->thd->COND_wakeup_ready); + } +} + + +/* + Execute a MASTER_GTID_WAIT() for one specific domain. + + The implementation is optimised primarily for (1) minimal performance impact + on the slave replication threads, and secondarily for (2) quick performance + of MASTER_GTID_WAIT() on a single GTID, which can be useful for consistent + read to clients in an async replication read-scaleout scenario. + + To achieve (1), we have a "small" wait and a "large" wait. The small wait + contends with the replication threads on the lock on the gtid_slave_pos, so + only minimal processing is done under that lock, and only a single waiter at + a time does the small wait. + + If there is already a small waiter, a new thread will either replace the + small waiter (if it needs to wait for an earlier sequence number), or + instead do a "large" wait. + + Once awoken on the small wait, the waiting thread releases the lock shared + with the SQL threads quickly, and then processes all waiters currently doing + the large wait using a different lock that does not impact replication. + + This way, the SQL threads only need to do a single check + possibly a + pthread_cond_signal() when updating the gtid_slave_state, and the time that + non-SQL threads contend for the lock on gtid_slave_state is minimized. + + There is always at least one thread that has the responsibility to ensure + that there is a small waiter; this thread has queue_element::do_small_wait + set to true. This thread will do the small wait until it is done, at which + point it will make sure to pass on the responsibility to another thread. + Normally only one thread has do_small_wait==true, but it can occasionally + happen that there is more than one, when threads race one another for the + lock on the small wait (this results in slightly increased activity on the + small lock but is otherwise harmless). + + Returns: + 0 Wait completed normally + -1 Wait completed due to timeout + 1 An error (my_error() will have been called to set the error in the da) +*/ +int +gtid_waiting::wait_for_gtid(THD *thd, rpl_gtid *wait_gtid, + struct timespec *wait_until) +{ + bool timed_out= false; +#ifdef HAVE_REPLICATION + queue_element elem; + uint32 domain_id= wait_gtid->domain_id; + uint64 seq_no= wait_gtid->seq_no; + hash_element *he; + rpl_slave_state::element *slave_state_elem= NULL; + PSI_stage_info old_stage; + bool did_enter_cond= false; + + elem.wait_seq_no= seq_no; + elem.thd= thd; + elem.done= false; + + mysql_mutex_lock(&LOCK_gtid_waiting); + if (!(he= get_entry(wait_gtid->domain_id))) + { + mysql_mutex_unlock(&LOCK_gtid_waiting); + return 1; + } + /* + If there is already another waiter with seq_no no larger than our own, + we are sure that there is already a small waiter that will wake us up + (or later pass the small wait responsibility to us). So in this case, we + do not need to touch the small wait lock at all. + */ + elem.do_small_wait= + (queue_empty(&he->queue) || + ((queue_element *)queue_top(&he->queue))->wait_seq_no > seq_no); + + if (register_in_wait_queue(thd, wait_gtid, he, &elem)) + { + mysql_mutex_unlock(&LOCK_gtid_waiting); + return 1; + } + /* + Loop, doing either the small or large wait as appropriate, until either + the position waited for is reached, or we get a kill or timeout. + */ + for (;;) + { + mysql_mutex_assert_owner(&LOCK_gtid_waiting); + + if (elem.do_small_wait) + { + uint64 wakeup_seq_no; + queue_element *cur_waiter; + + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + /* + The elements in the gtid_slave_state_hash are never re-allocated once + they enter the hash, so we do not need to re-do the lookup after releasing + and re-aquiring the lock. + */ + if (!slave_state_elem && + !(slave_state_elem= rpl_global_gtid_slave_state->get_element(domain_id))) + { + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + remove_from_wait_queue(he, &elem); + promote_new_waiter(he); + if (did_enter_cond) + thd->EXIT_COND(&old_stage); + else + mysql_mutex_unlock(&LOCK_gtid_waiting); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return 1; + } + + if ((wakeup_seq_no= slave_state_elem->highest_seq_no) >= seq_no) + { + /* + We do not have to wait. (We will be removed from the wait queue when + we call process_wait_hash() below. + */ + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + } + else if ((cur_waiter= slave_state_elem->gtid_waiter) && + slave_state_elem->min_wait_seq_no <= seq_no) + { + /* + There is already a suitable small waiter, go do the large wait. + (Normally we would not have needed to check the small wait in this + case, but it can happen if we race with another thread for the small + lock). + */ + elem.do_small_wait= false; + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + } + else + { + /* + We have to do the small wait ourselves (stealing it from any thread + that might already be waiting for a later seq_no). + */ + slave_state_elem->gtid_waiter= &elem; + slave_state_elem->min_wait_seq_no= seq_no; + if (cur_waiter) + { + /* We stole the wait, so wake up the old waiting thread. */ + mysql_cond_signal(&slave_state_elem->COND_wait_gtid); + } + + /* Release the large lock, and do the small wait. */ + if (did_enter_cond) + { + thd->EXIT_COND(&old_stage); + did_enter_cond= false; + } + else + mysql_mutex_unlock(&LOCK_gtid_waiting); + thd->ENTER_COND(&slave_state_elem->COND_wait_gtid, + &rpl_global_gtid_slave_state->LOCK_slave_state, + &stage_master_gtid_wait_primary, &old_stage); + do + { + if (thd->check_killed()) + break; + else if (wait_until) + { + int err= + mysql_cond_timedwait(&slave_state_elem->COND_wait_gtid, + &rpl_global_gtid_slave_state->LOCK_slave_state, + wait_until); + if (err == ETIMEDOUT || err == ETIME) + { + timed_out= true; + break; + } + } + else + mysql_cond_wait(&slave_state_elem->COND_wait_gtid, + &rpl_global_gtid_slave_state->LOCK_slave_state); + } while (slave_state_elem->gtid_waiter == &elem); + wakeup_seq_no= slave_state_elem->highest_seq_no; + /* + If we aborted due to timeout or kill, remove us as waiter. + + If we were replaced by another waiter with a smaller seq_no, then we + no longer have responsibility for the small wait. + */ + if ((cur_waiter= slave_state_elem->gtid_waiter)) + { + if (cur_waiter == &elem) + slave_state_elem->gtid_waiter= NULL; + else if (slave_state_elem->min_wait_seq_no <= seq_no) + elem.do_small_wait= false; + } + thd->EXIT_COND(&old_stage); + + mysql_mutex_lock(&LOCK_gtid_waiting); + } + + /* + Note that hash_entry pointers do not change once allocated, so we do + not need to lookup `he' again after re-aquiring LOCK_gtid_waiting. + */ + process_wait_hash(wakeup_seq_no, he); + } + else + { + /* Do the large wait. */ + if (!did_enter_cond) + { + thd->ENTER_COND(&thd->COND_wakeup_ready, &LOCK_gtid_waiting, + &stage_master_gtid_wait, &old_stage); + did_enter_cond= true; + } + while (!elem.done && !thd->check_killed()) + { + thd_wait_begin(thd, THD_WAIT_BINLOG); + if (wait_until) + { + int err= mysql_cond_timedwait(&thd->COND_wakeup_ready, + &LOCK_gtid_waiting, wait_until); + if (err == ETIMEDOUT || err == ETIME) + timed_out= true; + } + else + mysql_cond_wait(&thd->COND_wakeup_ready, &LOCK_gtid_waiting); + thd_wait_end(thd); + if (elem.do_small_wait || timed_out) + break; + } + } + + if ((thd->killed || timed_out) && !elem.done) + { + /* Aborted, so remove ourselves from the hash. */ + remove_from_wait_queue(he, &elem); + elem.done= true; + } + if (elem.done) + { + /* + If our wait is done, but we have (or were passed) responsibility for + the small wait, then we need to pass on that task to someone else. + */ + if (elem.do_small_wait) + promote_new_waiter(he); + break; + } + } + + if (did_enter_cond) + thd->EXIT_COND(&old_stage); + else + mysql_mutex_unlock(&LOCK_gtid_waiting); + if (thd->killed) + thd->send_kill_message(); +#endif /* HAVE_REPLICATION */ + return timed_out ? -1 : 0; +} + + +static void +free_hash_element(void *p) +{ + gtid_waiting::hash_element *e= (gtid_waiting::hash_element *)p; + delete_queue(&e->queue); + my_free(e); +} + + +void +gtid_waiting::init() +{ + my_hash_init(&hash, &my_charset_bin, 32, + offsetof(hash_element, domain_id), sizeof(uint32), NULL, + free_hash_element, HASH_UNIQUE); + mysql_mutex_init(key_LOCK_gtid_waiting, &LOCK_gtid_waiting, 0); +} + + +void +gtid_waiting::destroy() +{ + mysql_mutex_destroy(&LOCK_gtid_waiting); + my_hash_free(&hash); +} + + +static int +cmp_queue_elem(void *, uchar *a, uchar *b) +{ + uint64 seq_no_a= *(uint64 *)a; + uint64 seq_no_b= *(uint64 *)b; + if (seq_no_a < seq_no_b) + return -1; + else if (seq_no_a == seq_no_b) + return 0; + else + return 1; +} + + +gtid_waiting::hash_element * +gtid_waiting::get_entry(uint32 domain_id) +{ + hash_element *e; + + if ((e= (hash_element *)my_hash_search(&hash, (const uchar *)&domain_id, 0))) + return e; + + if (!(e= (hash_element *)my_malloc(sizeof(*e), MYF(MY_WME)))) + { + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(*e)); + return NULL; + } + + if (init_queue(&e->queue, 8, offsetof(queue_element, wait_seq_no), 0, + cmp_queue_elem, NULL, 1+offsetof(queue_element, queue_idx), 1)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + my_free(e); + return NULL; + } + e->domain_id= domain_id; + if (my_hash_insert(&hash, (uchar *)e)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + delete_queue(&e->queue); + my_free(e); + return NULL; + } + return e; +} + + +int +gtid_waiting::register_in_wait_queue(THD *thd, rpl_gtid *wait_gtid, + gtid_waiting::hash_element *he, + gtid_waiting::queue_element *elem) +{ + mysql_mutex_assert_owner(&LOCK_gtid_waiting); + + if (queue_insert_safe(&he->queue, (uchar *)elem)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return 1; + } + + return 0; +} + + +void +gtid_waiting::remove_from_wait_queue(gtid_waiting::hash_element *he, + gtid_waiting::queue_element *elem) +{ + mysql_mutex_assert_owner(&LOCK_gtid_waiting); + + queue_remove(&he->queue, elem->queue_idx); +} diff --git a/sql/rpl_gtid.h b/sql/rpl_gtid.h new file mode 100644 index 00000000000..7bd639b768f --- /dev/null +++ b/sql/rpl_gtid.h @@ -0,0 +1,316 @@ +/* Copyright (c) 2013, Kristian Nielsen and MariaDB Services Ab. + + 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 */ + +#ifndef RPL_GTID_H +#define RPL_GTID_H + +#include "hash.h" +#include "queues.h" + + +/* Definitions for MariaDB global transaction ID (GTID). */ + + +extern const LEX_STRING rpl_gtid_slave_state_table_name; + +class String; + +struct rpl_gtid +{ + uint32 domain_id; + uint32 server_id; + uint64 seq_no; +}; + +inline bool operator==(const rpl_gtid& lhs, const rpl_gtid& rhs) +{ + return + lhs.domain_id == rhs.domain_id && + lhs.server_id == rhs.server_id && + lhs.seq_no == rhs.seq_no; +}; + +enum enum_gtid_skip_type { + GTID_SKIP_NOT, GTID_SKIP_STANDALONE, GTID_SKIP_TRANSACTION +}; + + +/* + Structure to keep track of threads waiting in MASTER_GTID_WAIT(). + + Since replication is (mostly) single-threaded, we want to minimise the + performance impact on that from MASTER_GTID_WAIT(). To achieve this, we + are careful to keep the common lock between replication threads and + MASTER_GTID_WAIT threads held for as short as possible. We keep only + a single thread waiting to be notified by the replication threads; this + thread then handles all the (potentially heavy) lifting of dealing with + all current waiting threads. +*/ +struct gtid_waiting { + /* Elements in the hash, basically a priority queue for each domain. */ + struct hash_element { + QUEUE queue; + uint32 domain_id; + }; + /* A priority queue to handle waiters in one domain in seq_no order. */ + struct queue_element { + uint64 wait_seq_no; + THD *thd; + int queue_idx; + /* + do_small_wait is true if we have responsibility for ensuring that there + is a small waiter. + */ + bool do_small_wait; + /* + The flag `done' is set when the wait is completed (either due to reaching + the position waited for, or due to timeout or kill). The queue_element + is in the queue if and only if `done' is true. + */ + bool done; + }; + + mysql_mutex_t LOCK_gtid_waiting; + HASH hash; + + void init(); + void destroy(); + hash_element *get_entry(uint32 domain_id); + int wait_for_pos(THD *thd, String *gtid_str, longlong timeout_us); + void promote_new_waiter(gtid_waiting::hash_element *he); + int wait_for_gtid(THD *thd, rpl_gtid *wait_gtid, struct timespec *wait_until); + void process_wait_hash(uint64 wakeup_seq_no, gtid_waiting::hash_element *he); + int register_in_wait_queue(THD *thd, rpl_gtid *wait_gtid, hash_element *he, + queue_element *elem); + void remove_from_wait_queue(hash_element *he, queue_element *elem); +}; + + +class Relay_log_info; +struct rpl_group_info; +class Gtid_list_log_event; + +/* + Replication slave state. + + For every independent replication stream (identified by domain_id), this + remembers the last gtid applied on the slave within this domain. + + Since events are always committed in-order within a single domain, this is + sufficient to maintain the state of the replication slave. +*/ +struct rpl_slave_state +{ + /* Elements in the list of GTIDs kept for each domain_id. */ + struct list_element + { + struct list_element *next; + uint64 sub_id; + uint64 seq_no; + uint32 server_id; + }; + + /* Elements in the HASH that hold the state for one domain_id. */ + struct element + { + struct list_element *list; + uint32 domain_id; + /* Highest seq_no seen so far in this domain. */ + uint64 highest_seq_no; + /* + If this is non-NULL, then it is the waiter responsible for the small + wait in MASTER_GTID_WAIT(). + */ + gtid_waiting::queue_element *gtid_waiter; + /* + If gtid_waiter is non-NULL, then this is the seq_no that its + MASTER_GTID_WAIT() is waiting on. When we reach this seq_no, we need to + signal the waiter on COND_wait_gtid. + */ + uint64 min_wait_seq_no; + mysql_cond_t COND_wait_gtid; + + /* + For --gtid-ignore-duplicates. The Relay_log_info that currently owns + this domain, and the number of worker threads that are active in it. + + The idea is that only one of multiple master connections is allowed to + actively apply events for a given domain. Other connections must either + discard the events (if the seq_no in GTID shows they have already been + applied), or wait to see if the current owner will apply it. + */ + const Relay_log_info *owner_rli; + uint32 owner_count; + mysql_cond_t COND_gtid_ignore_duplicates; + + list_element *grab_list() { list_element *l= list; list= NULL; return l; } + void add(list_element *l) + { + l->next= list; + list= l; + } + }; + + /* Mapping from domain_id to its element. */ + HASH hash; + /* Mutex protecting access to the state. */ + mysql_mutex_t LOCK_slave_state; + /* Auxiliary buffer to sort gtid list. */ + DYNAMIC_ARRAY gtid_sort_array; + + uint64 last_sub_id; + bool loaded; + + rpl_slave_state(); + ~rpl_slave_state(); + + void truncate_hash(); + ulong count() const { return hash.records; } + int update(uint32 domain_id, uint32 server_id, uint64 sub_id, + uint64 seq_no, rpl_group_info *rgi); + int truncate_state_table(THD *thd); + int record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, + rpl_group_info *rgi, bool in_statement); + uint64 next_sub_id(uint32 domain_id); + int iterate(int (*cb)(rpl_gtid *, void *), void *data, + rpl_gtid *extra_gtids, uint32 num_extra, + bool sort); + int tostring(String *dest, rpl_gtid *extra_gtids, uint32 num_extra); + bool domain_to_gtid(uint32 domain_id, rpl_gtid *out_gtid); + int load(THD *thd, char *state_from_master, size_t len, bool reset, + bool in_statement); + bool is_empty(); + + element *get_element(uint32 domain_id); + int put_back_list(uint32 domain_id, list_element *list); + + void update_state_hash(uint64 sub_id, rpl_gtid *gtid, rpl_group_info *rgi); + int record_and_update_gtid(THD *thd, struct rpl_group_info *rgi); + int check_duplicate_gtid(rpl_gtid *gtid, rpl_group_info *rgi); + void release_domain_owner(rpl_group_info *rgi); +}; + + +/* + Binlog state. + This keeps the last GTID written to the binlog for every distinct + (domain_id, server_id) pair. + This will be logged at the start of the next binlog file as a + Gtid_list_log_event; this way, it is easy to find the binlog file + containing a given GTID, by simply scanning backwards from the newest + one until a lower seq_no is found in the Gtid_list_log_event at the + start of a binlog for the given domain_id and server_id. + + We also remember the last logged GTID for every domain_id. This is used + to know where to start when a master is changed to a slave. As a side + effect, it also allows to skip a hash lookup in the very common case of + logging a new GTID with same server id as last GTID. +*/ +struct rpl_binlog_state +{ + struct element { + uint32 domain_id; + HASH hash; /* Containing all server_id for one domain_id */ + /* The most recent entry in the hash. */ + rpl_gtid *last_gtid; + /* Counter to allocate next seq_no for this domain. */ + uint64 seq_no_counter; + + int update_element(const rpl_gtid *gtid); + }; + /* Mapping from domain_id to collection of elements. */ + HASH hash; + /* Mutex protecting access to the state. */ + mysql_mutex_t LOCK_binlog_state; + my_bool initialized; + + /* Auxiliary buffer to sort gtid list. */ + DYNAMIC_ARRAY gtid_sort_array; + + rpl_binlog_state(); + ~rpl_binlog_state(); + + void reset_nolock(); + void reset(); + void free(); + bool load(struct rpl_gtid *list, uint32 count); + bool load(rpl_slave_state *slave_pos); + int update_nolock(const struct rpl_gtid *gtid, bool strict); + int update(const struct rpl_gtid *gtid, bool strict); + int update_with_next_gtid(uint32 domain_id, uint32 server_id, + rpl_gtid *gtid); + int alloc_element_nolock(const rpl_gtid *gtid); + bool check_strict_sequence(uint32 domain_id, uint32 server_id, uint64 seq_no); + int bump_seq_no_if_needed(uint32 domain_id, uint64 seq_no); + int write_to_iocache(IO_CACHE *dest); + int read_from_iocache(IO_CACHE *src); + uint32 count(); + int get_gtid_list(rpl_gtid *gtid_list, uint32 list_size); + int get_most_recent_gtid_list(rpl_gtid **list, uint32 *size); + bool append_pos(String *str); + bool append_state(String *str); + rpl_gtid *find_nolock(uint32 domain_id, uint32 server_id); + rpl_gtid *find(uint32 domain_id, uint32 server_id); + rpl_gtid *find_most_recent(uint32 domain_id); + const char* drop_domain(DYNAMIC_ARRAY *ids, Gtid_list_log_event *glev, char*); +}; + + +/* + Represent the GTID state that a slave connection to a master requests + the master to start sending binlog events from. +*/ +struct slave_connection_state +{ + struct entry { + rpl_gtid gtid; + uint32 flags; + }; + static const uint32 START_OWN_SLAVE_POS= 0x1; + static const uint32 START_ON_EMPTY_DOMAIN= 0x2; + + /* Mapping from domain_id to the entry with GTID requested for that domain. */ + HASH hash; + + /* Auxiliary buffer to sort gtid list. */ + DYNAMIC_ARRAY gtid_sort_array; + + slave_connection_state(); + ~slave_connection_state(); + + void reset() { my_hash_reset(&hash); } + int load(char *slave_request, size_t len); + int load(const rpl_gtid *gtid_list, uint32 count); + int load(rpl_slave_state *state, rpl_gtid *extra_gtids, uint32 num_extra); + rpl_gtid *find(uint32 domain_id); + entry *find_entry(uint32 domain_id); + int update(const rpl_gtid *in_gtid); + void remove(const rpl_gtid *gtid); + void remove_if_present(const rpl_gtid *in_gtid); + ulong count() const { return hash.records; } + int to_string(String *out_str); + int append_to_string(String *out_str); + int get_gtid_list(rpl_gtid *gtid_list, uint32 list_size); + bool is_pos_reached(); +}; + + +extern bool rpl_slave_state_tostring_helper(String *dest, const rpl_gtid *gtid, + bool *first); +extern int gtid_check_rpl_slave_state_table(TABLE *table); +extern rpl_gtid *gtid_parse_string_to_list(const char *p, size_t len, + uint32 *out_len); + +#endif /* RPL_GTID_H */ diff --git a/sql/rpl_handler.cc b/sql/rpl_handler.cc index ca3b57edce3..520fb61d8c4 100644 --- a/sql/rpl_handler.cc +++ b/sql/rpl_handler.cc @@ -13,6 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" @@ -164,7 +165,7 @@ void delegates_destroy() delegate. */ #define FOREACH_OBSERVER(r, f, do_lock, args) \ - param.server_id= thd->server_id; \ + param.server_id= thd->variables.server_id; \ read_lock(); \ Observer_info_iterator iter= observer_info_iter(); \ Observer_info *info= iter++; \ @@ -242,7 +243,9 @@ int Trans_delegate::after_rollback(THD *thd, bool all) int Binlog_storage_delegate::after_flush(THD *thd, const char *log_file, my_off_t log_pos, - bool synced) + bool synced, + bool first_in_group, + bool last_in_group) { Binlog_storage_param param; Trans_binlog_info *log_info; @@ -251,6 +254,10 @@ int Binlog_storage_delegate::after_flush(THD *thd, if (synced) flags |= BINLOG_STORAGE_IS_SYNCED; + if (first_in_group) + flags|= BINLOG_GROUP_COMMIT_LEADER; + if (last_in_group) + flags|= BINLOG_GROUP_COMMIT_TRAILER; if (!(log_info= thd->semisync_info)) { @@ -268,6 +275,27 @@ int Binlog_storage_delegate::after_flush(THD *thd, return ret; } +int Binlog_storage_delegate::after_sync(THD *thd, + const char *log_file, + my_off_t log_pos, + bool first_in_group, + bool last_in_group) +{ + Binlog_storage_param param; + uint32 flags=0; + + if (first_in_group) + flags|= BINLOG_GROUP_COMMIT_LEADER; + if (last_in_group) + flags|= BINLOG_GROUP_COMMIT_TRAILER; + + int ret= 0; + FOREACH_OBSERVER(ret, after_sync, false, + (¶m, log_file+dirname_length(log_file), log_pos, flags)); + + return ret; +} + #ifdef HAVE_REPLICATION int Binlog_transmit_delegate::transmit_start(THD *thd, ushort flags, const char *log_file, @@ -304,7 +332,7 @@ int Binlog_transmit_delegate::reserve_header(THD *thd, ushort flags, ulong hlen; Binlog_transmit_param param; param.flags= flags; - param.server_id= thd->server_id; + param.server_id= thd->variables.server_id; int ret= 0; read_lock(); @@ -502,4 +530,24 @@ int unregister_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void { return binlog_relay_io_delegate->remove_observer(observer, (st_plugin_int *)p); } +#else +int register_binlog_transmit_observer(Binlog_transmit_observer *observer, void *p) +{ + return 0; +} + +int unregister_binlog_transmit_observer(Binlog_transmit_observer *observer, void *p) +{ + return 0; +} + +int register_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void *p) +{ + return 0; +} + +int unregister_binlog_relay_io_observer(Binlog_relay_IO_observer *observer, void *p) +{ + return 0; +} #endif /* HAVE_REPLICATION */ diff --git a/sql/rpl_handler.h b/sql/rpl_handler.h index 362f3c74a4b..afcfd9d55b1 100644 --- a/sql/rpl_handler.h +++ b/sql/rpl_handler.h @@ -121,7 +121,7 @@ public: inited= FALSE; if (my_rwlock_init(&lock, NULL)) return; - init_sql_alloc(&memroot, 1024, 0); + init_sql_alloc(&memroot, 1024, 0, MYF(0)); inited= TRUE; } ~Delegate() @@ -153,7 +153,10 @@ class Binlog_storage_delegate public: typedef Binlog_storage_observer Observer; int after_flush(THD *thd, const char *log_file, - my_off_t log_pos, bool synced); + my_off_t log_pos, bool synced, + bool first_in_group, bool last_in_group); + int after_sync(THD *thd, const char *log_file, my_off_t log_pos, + bool first_in_group, bool last_in_group); }; #ifdef HAVE_REPLICATION diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index ec1a96e8a2b..bff0da26862 100644 --- a/sql/rpl_injector.cc +++ b/sql/rpl_injector.cc @@ -13,8 +13,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED by later includes #include "rpl_injector.h" #include "transaction.h" #include "sql_parse.h" // begin_trans, end_trans, COMMIT @@ -108,7 +108,7 @@ int injector::transaction::use_table(server_id_type sid, table tbl) if ((error= check_state(TABLE_STATE))) DBUG_RETURN(error); - server_id_type save_id= m_thd->server_id; + server_id_type save_id= m_thd->variables.server_id; m_thd->set_server_id(sid); error= m_thd->binlog_write_table_map(tbl.get_table(), tbl.is_transactional()); @@ -117,62 +117,6 @@ int injector::transaction::use_table(server_id_type sid, table tbl) } -int injector::transaction::write_row (server_id_type sid, table tbl, - MY_BITMAP const* cols, size_t colcnt, - record_type record) -{ - DBUG_ENTER("injector::transaction::write_row(...)"); - - int error= check_state(ROW_STATE); - if (error) - DBUG_RETURN(error); - - server_id_type save_id= m_thd->server_id; - m_thd->set_server_id(sid); - error= m_thd->binlog_write_row(tbl.get_table(), tbl.is_transactional(), - cols, colcnt, record); - m_thd->set_server_id(save_id); - DBUG_RETURN(error); -} - - -int injector::transaction::delete_row(server_id_type sid, table tbl, - MY_BITMAP const* cols, size_t colcnt, - record_type record) -{ - DBUG_ENTER("injector::transaction::delete_row(...)"); - - int error= check_state(ROW_STATE); - if (error) - DBUG_RETURN(error); - - server_id_type save_id= m_thd->server_id; - m_thd->set_server_id(sid); - error= m_thd->binlog_delete_row(tbl.get_table(), tbl.is_transactional(), - cols, colcnt, record); - m_thd->set_server_id(save_id); - DBUG_RETURN(error); -} - - -int injector::transaction::update_row(server_id_type sid, table tbl, - MY_BITMAP const* cols, size_t colcnt, - record_type before, record_type after) -{ - DBUG_ENTER("injector::transaction::update_row(...)"); - - int error= check_state(ROW_STATE); - if (error) - DBUG_RETURN(error); - - server_id_type save_id= m_thd->server_id; - m_thd->set_server_id(sid); - error= m_thd->binlog_update_row(tbl.get_table(), tbl.is_transactional(), - cols, colcnt, before, after); - m_thd->set_server_id(save_id); - DBUG_RETURN(error); -} - injector::transaction::binlog_pos injector::transaction::start_pos() const { diff --git a/sql/rpl_injector.h b/sql/rpl_injector.h index f4790cc963a..41e1fcf460c 100644 --- a/sql/rpl_injector.h +++ b/sql/rpl_injector.h @@ -94,13 +94,13 @@ public: injector::transaction::table tbl(share->table, true); MY_BITMAP cols; - bitmap_init(&cols, NULL, (i + 7) / 8, false); + my_bitmap_init(&cols, NULL, (i + 7) / 8, false); inj->write_row(::server_id, tbl, &cols, row_data); or MY_BITMAP cols; - bitmap_init(&cols, NULL, (i + 7) / 8, false); + my_bitmap_init(&cols, NULL, (i + 7) / 8, false); inj->write_row(::server_id, injector::transaction::table(share->table, true), &cols, row_data); @@ -117,8 +117,8 @@ public: class table { public: - table(TABLE *table, bool is_transactional) - : m_table(table), m_is_transactional(is_transactional) + table(TABLE *table, bool is_transactional_arg) + : m_table(table), m_is_transactional(is_transactional_arg) { } @@ -181,27 +181,6 @@ public: int use_table(server_id_type sid, table tbl); /* - Add a 'write row' entry to the transaction. - */ - int write_row (server_id_type sid, table tbl, - MY_BITMAP const *cols, size_t colcnt, - record_type record); - - /* - Add a 'delete row' entry to the transaction. - */ - int delete_row(server_id_type sid, table tbl, - MY_BITMAP const *cols, size_t colcnt, - record_type record); - - /* - Add an 'update row' entry to the transaction. - */ - int update_row(server_id_type sid, table tbl, - MY_BITMAP const *cols, size_t colcnt, - record_type before, record_type after); - - /* Commit a transaction. This member function will clean up after a sequence of *_row calls by, diff --git a/sql/rpl_mi.cc b/sql/rpl_mi.cc index adaa7cf864f..ab54f0bfbb7 100644 --- a/sql/rpl_mi.cc +++ b/sql/rpl_mi.cc @@ -17,9 +17,10 @@ #include <my_global.h> // For HAVE_REPLICATION #include "sql_priv.h" #include <my_dir.h> -#include "unireg.h" // REQUIRED by other includes #include "rpl_mi.h" #include "slave.h" // SLAVE_MAX_HEARTBEAT_PERIOD +#include "strfunc.h" +#include "sql_repl.h" #ifdef HAVE_REPLICATION @@ -27,23 +28,63 @@ static void init_master_log_pos(Master_info* mi); -Master_info::Master_info(bool is_slave_recovery) +Master_info::Master_info(LEX_STRING *connection_name_arg, + bool is_slave_recovery) :Slave_reporting_capability("I/O"), ssl(0), ssl_verify_server_cert(1), fd(-1), io_thd(0), rli(is_slave_recovery), port(MYSQL_PORT), checksum_alg_before_fd(BINLOG_CHECKSUM_ALG_UNDEF), connect_retry(DEFAULT_CONNECT_RETRY), inited(0), abort_slave(0), - slave_running(0), slave_run_id(0), sync_counter(0), - heartbeat_period(0), received_heartbeats(0), master_id(0) + slave_running(MYSQL_SLAVE_NOT_RUN), slave_run_id(0), + clock_diff_with_master(0), + sync_counter(0), heartbeat_period(0), received_heartbeats(0), + master_id(0), prev_master_id(0), + using_gtid(USE_GTID_NO), events_queued_since_last_gtid(0), + gtid_reconnect_event_skip_count(0), gtid_event_seen(false), + in_start_all_slaves(0), in_stop_all_slaves(0), in_flush_all_relay_logs(0), + users(0), killed(0) { host[0] = 0; user[0] = 0; password[0] = 0; ssl_ca[0]= 0; ssl_capath[0]= 0; ssl_cert[0]= 0; ssl_cipher[0]= 0; ssl_key[0]= 0; + ssl_crl[0]= 0; ssl_crlpath[0]= 0; - my_init_dynamic_array(&ignore_server_ids, sizeof(::server_id), 16, 16); + /* + Store connection name and lower case connection name + It's safe to ignore any OMM errors as this is checked by error() + */ + connection_name.length= cmp_connection_name.length= + connection_name_arg->length; + if ((connection_name.str= (char*) my_malloc(connection_name_arg->length*2+2, + MYF(MY_WME)))) + { + cmp_connection_name.str= (connection_name.str + + connection_name_arg->length+1); + strmake(connection_name.str, connection_name_arg->str, + connection_name.length); + memcpy(cmp_connection_name.str, connection_name_arg->str, + connection_name.length+1); + my_casedn_str(system_charset_info, cmp_connection_name.str); + } + /* + When MySQL restarted, all Rpl_filter settings which aren't in the my.cnf + will be lost. If you want to lose a setting after restart, you + should add them into my.cnf + */ + rpl_filter= get_or_create_rpl_filter(connection_name.str, + connection_name.length); + copy_filter_setting(rpl_filter, global_rpl_filter); + + parallel_mode= rpl_filter->get_parallel_mode(); + + my_init_dynamic_array(&ignore_server_ids, + sizeof(global_system_variables.server_id), 16, 16, + MYF(0)); bzero((char*) &file, sizeof(file)); mysql_mutex_init(key_master_info_run_lock, &run_lock, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_master_info_data_lock, &data_lock, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_master_info_start_stop_lock, &start_stop_lock, + MY_MUTEX_INIT_SLOW); mysql_mutex_setflags(&run_lock, MYF_NO_DEADLOCK_DETECTION); mysql_mutex_setflags(&data_lock, MYF_NO_DEADLOCK_DETECTION); mysql_mutex_init(key_master_info_sleep_lock, &sleep_lock, MY_MUTEX_INIT_FAST); @@ -53,12 +94,42 @@ Master_info::Master_info(bool is_slave_recovery) mysql_cond_init(key_master_info_sleep_cond, &sleep_cond, NULL); } + +/** + Wait until no one is using Master_info +*/ + +void Master_info::wait_until_free() +{ + mysql_mutex_lock(&sleep_lock); + killed= 1; + while (users) + mysql_cond_wait(&sleep_cond, &sleep_lock); + mysql_mutex_unlock(&sleep_lock); +} + +/** + Delete master_info +*/ + Master_info::~Master_info() { + wait_until_free(); +#ifdef WITH_WSREP + /* + Do not free "wsrep" rpl_filter. It will eventually be freed by + free_all_rpl_filters() when server terminates. + */ + if (strncmp(connection_name.str, STRING_WITH_LEN("wsrep"))) +#endif + rpl_filters.delete_element(connection_name.str, connection_name.length, + (void (*)(const char*, uchar*)) free_rpl_filter); + my_free(connection_name.str); delete_dynamic(&ignore_server_ids); mysql_mutex_destroy(&run_lock); mysql_mutex_destroy(&data_lock); mysql_mutex_destroy(&sleep_lock); + mysql_mutex_destroy(&start_stop_lock); mysql_cond_destroy(&data_cond); mysql_cond_destroy(&start_cond); mysql_cond_destroy(&stop_cond); @@ -72,12 +143,11 @@ Master_info::~Master_info() @return -1 if first argument is less, 0 if it equal to, 1 if it is greater than the second */ -int change_master_server_id_cmp(ulong *id1, ulong *id2) +static int change_master_id_cmp(const void *id1, const void *id2) { - return *id1 < *id2? -1 : (*id1 > *id2? 1 : 0); + return (*(ulong *) id1 - *(ulong *) id2); } - /** Reports if the s_id server has been configured to ignore events it generates with @@ -95,12 +165,11 @@ bool Master_info::shall_ignore_server_id(ulong s_id) { if (likely(ignore_server_ids.elements == 1)) return (* (ulong*) dynamic_array_ptr(&ignore_server_ids, 0)) == s_id; - else + else return bsearch((const ulong *) &s_id, ignore_server_ids.buffer, ignore_server_ids.elements, sizeof(ulong), - (int (*) (const void*, const void*)) change_master_server_id_cmp) - != NULL; + change_master_id_cmp) != NULL; } void Master_info::clear_in_memory_info(bool all) @@ -113,12 +182,34 @@ void Master_info::clear_in_memory_info(bool all) } } + +const char * +Master_info::using_gtid_astext(enum enum_using_gtid arg) +{ + switch (arg) + { + case USE_GTID_NO: + return "No"; + case USE_GTID_SLAVE_POS: + return "Slave_Pos"; + default: + DBUG_ASSERT(arg == USE_GTID_CURRENT_POS); + return "Current_Pos"; + } +} + + void init_master_log_pos(Master_info* mi) { DBUG_ENTER("init_master_log_pos"); mi->master_log_name[0] = 0; mi->master_log_pos = BIN_LOG_HEADER_SIZE; // skip magic number + mi->using_gtid= Master_info::USE_GTID_NO; + mi->gtid_current_pos.reset(); + mi->events_queued_since_last_gtid= 0; + mi->gtid_reconnect_event_skip_count= 0; + mi->gtid_event_seen= false; /* Intentionally init ssl_verify_server_cert to 0, no option available */ mi->ssl_verify_server_cert= 0; @@ -128,7 +219,7 @@ void init_master_log_pos(Master_info* mi) if CHANGE MASTER did not specify it. (no data loss in conversion as hb period has a max) */ - mi->heartbeat_period= (float) min(SLAVE_MAX_HEARTBEAT_PERIOD, + mi->heartbeat_period= (float) MY_MIN(SLAVE_MAX_HEARTBEAT_PERIOD, (slave_net_timeout/2.0)); DBUG_ASSERT(mi->heartbeat_period > (float) 0.001 || mi->heartbeat_period == 0); @@ -136,20 +227,94 @@ void init_master_log_pos(Master_info* mi) DBUG_VOID_RETURN; } +/** + Parses the IO_CACHE for "key=" and returns the "key". + If no '=' found, returns the whole line (for END_MARKER). + + @param key [OUT] Key buffer + @param max_size [IN] Maximum buffer size + @param f [IN] IO_CACHE file + @param found_equal [OUT] Set true if a '=' was found. + + @retval 0 Either "key=" or '\n' found + @retval 1 EOF +*/ +static int +read_mi_key_from_file(char *key, int max_size, IO_CACHE *f, bool *found_equal) +{ + int i= 0, c; + + DBUG_ENTER("read_key_from_file"); + + *found_equal= false; + if (max_size <= 0) + DBUG_RETURN(1); + for (;;) + { + if (i >= max_size-1) + { + key[i] = '\0'; + DBUG_RETURN(0); + } + c= my_b_get(f); + if (c == my_b_EOF) + { + DBUG_RETURN(1); + } + else if (c == '\n') + { + key[i]= '\0'; + DBUG_RETURN(0); + } + else if (c == '=') + { + key[i]= '\0'; + *found_equal= true; + DBUG_RETURN(0); + } + else + { + key[i]= c; + ++i; + } + } + /* NotReached */ +} enum { LINES_IN_MASTER_INFO_WITH_SSL= 14, /* 5.1.16 added value of master_ssl_verify_server_cert */ LINE_FOR_MASTER_SSL_VERIFY_SERVER_CERT= 15, - /* 6.0 added value of master_heartbeat_period */ + + /* 5.5 added value of master_heartbeat_period */ LINE_FOR_MASTER_HEARTBEAT_PERIOD= 16, + /* MySQL Cluster 6.3 added master_bind */ LINE_FOR_MASTER_BIND = 17, + /* 6.0 added value of master_ignore_server_id */ LINE_FOR_REPLICATE_IGNORE_SERVER_IDS= 18, - /* Number of lines currently used when saving master info file */ - LINES_IN_MASTER_INFO= LINE_FOR_REPLICATE_IGNORE_SERVER_IDS + + /* 6.0 added value of master_uuid */ + LINE_FOR_MASTER_UUID= 19, + + /* line for master_retry_count */ + LINE_FOR_MASTER_RETRY_COUNT= 20, + + /* line for ssl_crl */ + LINE_FOR_SSL_CRL= 21, + + /* line for ssl_crl */ + LINE_FOR_SSL_CRLPATH= 22, + + /* MySQL 5.6 fixed-position lines. */ + LINE_FOR_FIRST_MYSQL_5_6=23, + LINE_FOR_LAST_MYSQL_5_6=23, + /* Reserved lines for MySQL future versions. */ + LINE_FOR_LAST_MYSQL_FUTURE=33, + /* Number of (fixed-position) lines used when saving master info file */ + LINES_IN_MASTER_INFO= LINE_FOR_LAST_MYSQL_FUTURE }; int init_master_info(Master_info* mi, const char* master_info_fname, @@ -277,7 +442,7 @@ file '%s')", fname); int ssl= 0, ssl_verify_server_cert= 0; float master_heartbeat_period= 0.0; char *first_non_digit; - char dummy_buf[HOSTNAME_LENGTH+1]; + char buf[HOSTNAME_LENGTH+1]; /* Starting from 4.1.x master.info has new format. Now its @@ -371,7 +536,7 @@ file '%s')", fname); (this is just a reservation to avoid future upgrade problems) */ if (lines >= LINE_FOR_MASTER_BIND && - init_strvar_from_file(dummy_buf, sizeof(dummy_buf), &mi->file, "")) + init_strvar_from_file(buf, sizeof(buf), &mi->file, "")) goto errwithmsg; /* Starting from 6.0 list of server_id of ignorable servers might be @@ -383,6 +548,104 @@ file '%s')", fname); sql_print_error("Failed to initialize master info ignore_server_ids"); goto errwithmsg; } + + /* reserved */ + if (lines >= LINE_FOR_MASTER_UUID && + init_strvar_from_file(buf, sizeof(buf), &mi->file, "")) + goto errwithmsg; + + /* Starting from 5.5 the master_retry_count may be in the repository. */ + if (lines >= LINE_FOR_MASTER_RETRY_COUNT && + init_strvar_from_file(buf, sizeof(buf), &mi->file, "")) + goto errwithmsg; + + if (lines >= LINE_FOR_SSL_CRLPATH && + (init_strvar_from_file(mi->ssl_crl, sizeof(mi->ssl_crl), + &mi->file, "") || + init_strvar_from_file(mi->ssl_crlpath, sizeof(mi->ssl_crlpath), + &mi->file, ""))) + goto errwithmsg; + + /* + Starting with MariaDB 10.0, we use a key=value syntax, which is nicer + in several ways. But we leave a bunch of empty lines to accomodate + any future old-style additions in MySQL (this will make it easier for + users moving from MariaDB to MySQL, to not have MySQL try to + interpret a MariaDB key=value line.) + */ + if (lines >= LINE_FOR_LAST_MYSQL_FUTURE) + { + uint i; + bool got_eq; + bool seen_using_gtid= false; + bool seen_do_domain_ids=false, seen_ignore_domain_ids=false; + + /* Skip lines used by / reserved for MySQL >= 5.6. */ + for (i= LINE_FOR_FIRST_MYSQL_5_6; i <= LINE_FOR_LAST_MYSQL_FUTURE; ++i) + { + if (init_strvar_from_file(buf, sizeof(buf), &mi->file, "")) + goto errwithmsg; + } + + /* + Parse any extra key=value lines. read_key_from_file() parses the file + for "key=" and returns the "key" if found. The "value" can then the + parsed on case by case basis. The "unknown" lines would be ignored to + facilitate downgrades. + 10.0 does not have the END_MARKER before any left-overs at the end + of the file. So ignore any but the first occurrence of a key. + */ + while (!read_mi_key_from_file(buf, sizeof(buf), &mi->file, &got_eq)) + { + if (got_eq && !seen_using_gtid && !strcmp(buf, "using_gtid")) + { + int val; + if (!init_intvar_from_file(&val, &mi->file, 0)) + { + if (val == Master_info::USE_GTID_CURRENT_POS) + mi->using_gtid= Master_info::USE_GTID_CURRENT_POS; + else if (val == Master_info::USE_GTID_SLAVE_POS) + mi->using_gtid= Master_info::USE_GTID_SLAVE_POS; + else + mi->using_gtid= Master_info::USE_GTID_NO; + seen_using_gtid= true; + } else { + sql_print_error("Failed to initialize master info using_gtid"); + goto errwithmsg; + } + } + else if (got_eq && !seen_do_domain_ids && !strcmp(buf, "do_domain_ids")) + { + if (mi->domain_id_filter.init_ids(&mi->file, + Domain_id_filter::DO_DOMAIN_IDS)) + { + sql_print_error("Failed to initialize master info do_domain_ids"); + goto errwithmsg; + } + seen_do_domain_ids= true; + } + else if (got_eq && !seen_ignore_domain_ids && + !strcmp(buf, "ignore_domain_ids")) + { + if (mi->domain_id_filter.init_ids(&mi->file, + Domain_id_filter::IGNORE_DOMAIN_IDS)) + { + sql_print_error("Failed to initialize master info " + "ignore_domain_ids"); + goto errwithmsg; + } + seen_ignore_domain_ids= true; + } + else if (!got_eq && !strcmp(buf, "END_MARKER")) + { + /* + Guard agaist extra left-overs at the end of file, in case a later + update causes the file to shrink compared to earlier contents. + */ + break; + } + } + } } #ifndef HAVE_OPENSSL @@ -401,13 +664,13 @@ file '%s')", fname); mi->connect_retry= (uint) connect_retry; mi->ssl= (my_bool) ssl; mi->ssl_verify_server_cert= ssl_verify_server_cert; - mi->heartbeat_period= min(SLAVE_MAX_HEARTBEAT_PERIOD, master_heartbeat_period); + mi->heartbeat_period= MY_MIN(SLAVE_MAX_HEARTBEAT_PERIOD, master_heartbeat_period); } DBUG_PRINT("master_info",("log_file_name: %s position: %ld", mi->master_log_name, (ulong) mi->master_log_pos)); - mi->rli.mi = mi; + mi->rli.mi= mi; if (init_relay_log_info(&mi->rli, slave_info_fname)) goto err; @@ -415,7 +678,7 @@ file '%s')", fname); mi->rli.is_relay_log_recovery= FALSE; // now change cache READ -> WRITE - must do this before flush_master_info reinit_io_cache(&mi->file, WRITE_CACHE, 0L, 0, 1); - if ((error=test(flush_master_info(mi, TRUE, TRUE)))) + if ((error= MY_TEST(flush_master_info(mi, TRUE, TRUE)))) sql_print_error("Failed to flush master info file"); mysql_mutex_unlock(&mi->data_lock); DBUG_RETURN(error); @@ -481,17 +744,17 @@ int flush_master_info(Master_info* mi, if (err) DBUG_RETURN(2); } - + /* produce a line listing the total number and all the ignored server_id:s */ char* ignore_server_ids_buf; { ignore_server_ids_buf= - (char *) my_malloc((sizeof(::server_id) * 3 + 1) * + (char *) my_malloc((sizeof(global_system_variables.server_id) * 3 + 1) * (1 + mi->ignore_server_ids.elements), MYF(MY_WME)); if (!ignore_server_ids_buf) - DBUG_RETURN(1); + DBUG_RETURN(1); /* error */ ulong cur_len= sprintf(ignore_server_ids_buf, "%u", mi->ignore_server_ids.elements); for (ulong i= 0; i < mi->ignore_server_ids.elements; i++) @@ -502,6 +765,24 @@ int flush_master_info(Master_info* mi, } } + char *do_domain_ids_buf= 0, *ignore_domain_ids_buf= 0; + + do_domain_ids_buf= + mi->domain_id_filter.as_string(Domain_id_filter::DO_DOMAIN_IDS); + if (do_domain_ids_buf == NULL) + { + err= 1; /* error */ + goto done; + } + + ignore_domain_ids_buf= + mi->domain_id_filter.as_string(Domain_id_filter::IGNORE_DOMAIN_IDS); + if (ignore_domain_ids_buf == NULL) + { + err= 1; /* error */ + goto done; + } + /* We flushed the relay log BEFORE the master.info file, because if we crash now, we will get a duplicate event in the relay log at restart. If we @@ -522,23 +803,38 @@ int flush_master_info(Master_info* mi, my_fcvt(mi->heartbeat_period, 3, heartbeat_buf, NULL); my_b_seek(file, 0L); my_b_printf(file, - "%u\n%s\n%s\n%s\n%s\n%s\n%d\n%d\n%d\n%s\n%s\n%s\n%s\n%s\n%d\n%s\n%s\n%s\n", + "%u\n%s\n%s\n%s\n%s\n%s\n%d\n%d\n%d\n%s\n%s\n%s\n%s\n%s\n%d\n%s\n%s\n%s\n%s\n%d\n%s\n%s\n" + "\n\n\n\n\n\n\n\n\n\n\n" + "using_gtid=%d\n" + "do_domain_ids=%s\n" + "ignore_domain_ids=%s\n" + "END_MARKER\n", LINES_IN_MASTER_INFO, mi->master_log_name, llstr(mi->master_log_pos, lbuf), mi->host, mi->user, mi->password, mi->port, mi->connect_retry, (int)(mi->ssl), mi->ssl_ca, mi->ssl_capath, mi->ssl_cert, mi->ssl_cipher, mi->ssl_key, mi->ssl_verify_server_cert, - heartbeat_buf, "", ignore_server_ids_buf); - my_free(ignore_server_ids_buf); + heartbeat_buf, "", ignore_server_ids_buf, + "", 0, + mi->ssl_crl, mi->ssl_crlpath, mi->using_gtid, + do_domain_ids_buf, ignore_domain_ids_buf); err= flush_io_cache(file); - if (sync_masterinfo_period && !err && + if (sync_masterinfo_period && !err && ++(mi->sync_counter) >= sync_masterinfo_period) { err= my_sync(mi->fd, MYF(MY_WME)); mi->sync_counter= 0; } - DBUG_RETURN(-err); + + /* Fix err; flush_io_cache()/my_sync() may return -1 */ + err= (err != 0) ? 1 : 0; + +done: + my_free(ignore_server_ids_buf); + my_free(do_domain_ids_buf); + my_free(ignore_domain_ids_buf); + DBUG_RETURN(err); } @@ -559,5 +855,1178 @@ void end_master_info(Master_info* mi) DBUG_VOID_RETURN; } +/* Multi-Master By P.Linux */ +uchar *get_key_master_info(Master_info *mi, size_t *length, + my_bool not_used __attribute__((unused))) +{ + /* Return lower case name */ + *length= mi->cmp_connection_name.length; + return (uchar*) mi->cmp_connection_name.str; +} + +/* + Delete a master info + + Called from my_hash_delete(&master_info_hash) + Stops associated slave threads and frees master_info +*/ + +void free_key_master_info(Master_info *mi) +{ + DBUG_ENTER("free_key_master_info"); + mysql_mutex_unlock(&LOCK_active_mi); + + /* Ensure that we are not in reset_slave while this is done */ + mi->lock_slave_threads(); + terminate_slave_threads(mi,SLAVE_FORCE_ALL); + /* We use 2 here instead of 1 just to make it easier when debugging */ + mi->killed= 2; + end_master_info(mi); + end_relay_log_info(&mi->rli); + mi->unlock_slave_threads(); + delete mi; + + mysql_mutex_lock(&LOCK_active_mi); + DBUG_VOID_RETURN; +} + +/** + Check if connection name for master_info is valid. + + It's valid if it's a valid system name of length less than + MAX_CONNECTION_NAME. + + @return + 0 ok + 1 error +*/ + +bool check_master_connection_name(LEX_STRING *name) +{ + if (name->length >= MAX_CONNECTION_NAME) + return 1; + return 0; +} + + +/** + Create a log file with a given suffix. + + @param + res_file_name Store result here + length Length of res_file_name buffer + info_file Original file name (prefix) + append 1 if we should add suffix last (not before ext) + suffix Suffix + + @note + The suffix is added before the extension of the file name prefixed with '-'. + The suffix is also converted to lower case and we transform + all not safe character, as we do with MySQL table names. + + If suffix is an empty string, then we don't add any suffix. + This is to allow one to use this function also to generate old + file names without a prefix. +*/ + +void create_logfile_name_with_suffix(char *res_file_name, size_t length, + const char *info_file, bool append, + LEX_STRING *suffix) +{ + char buff[MAX_CONNECTION_NAME+1], + res[MAX_CONNECTION_NAME * MAX_FILENAME_MBWIDTH+1], *p; + + p= strmake(res_file_name, info_file, length); + /* If not empty suffix and there is place left for some part of the suffix */ + if (suffix->length != 0 && p <= res_file_name + length -1) + { + const char *info_file_end= info_file + (p - res_file_name); + const char *ext= append ? info_file_end : fn_ext2(info_file); + size_t res_length, ext_pos, from_length; + uint errors; + + /* Create null terminated string */ + from_length= strmake(buff, suffix->str, suffix->length) - buff; + /* Convert to characters usable in a file name */ + res_length= strconvert(system_charset_info, buff, from_length, + &my_charset_filename, res, sizeof(res), &errors); + + ext_pos= (size_t) (ext - info_file); + length-= (suffix->length - ext_pos); /* Leave place for extension */ + p= res_file_name + ext_pos; + *p++= '-'; /* Add separator */ + p= strmake(p, res, MY_MIN((size_t) (length - (p - res_file_name)), + res_length)); + /* Add back extension. We have checked above that there is space for it */ + strmov(p, ext); + } +} + +void copy_filter_setting(Rpl_filter* dst_filter, Rpl_filter* src_filter) +{ + char buf[256]; + String tmp(buf, sizeof(buf), &my_charset_bin); + + dst_filter->get_do_db(&tmp); + if (tmp.is_empty()) + { + src_filter->get_do_db(&tmp); + if (!tmp.is_empty()) + dst_filter->set_do_db(tmp.ptr()); + } + + dst_filter->get_do_table(&tmp); + if (tmp.is_empty()) + { + src_filter->get_do_table(&tmp); + if (!tmp.is_empty()) + dst_filter->set_do_table(tmp.ptr()); + } + + dst_filter->get_ignore_db(&tmp); + if (tmp.is_empty()) + { + src_filter->get_ignore_db(&tmp); + if (!tmp.is_empty()) + dst_filter->set_ignore_db(tmp.ptr()); + } + + dst_filter->get_ignore_table(&tmp); + if (tmp.is_empty()) + { + src_filter->get_ignore_table(&tmp); + if (!tmp.is_empty()) + dst_filter->set_ignore_table(tmp.ptr()); + } + + dst_filter->get_wild_do_table(&tmp); + if (tmp.is_empty()) + { + src_filter->get_wild_do_table(&tmp); + if (!tmp.is_empty()) + dst_filter->set_wild_do_table(tmp.ptr()); + } + + dst_filter->get_wild_ignore_table(&tmp); + if (tmp.is_empty()) + { + src_filter->get_wild_ignore_table(&tmp); + if (!tmp.is_empty()) + dst_filter->set_wild_ignore_table(tmp.ptr()); + } + + if (dst_filter->rewrite_db_is_empty()) + { + if (!src_filter->rewrite_db_is_empty()) + dst_filter->copy_rewrite_db(src_filter); + } +} + +Master_info_index::Master_info_index() +{ + size_t filename_length, dir_length; + /* + Create the Master_info index file by prepending 'multi-' before + the master_info_file file name. + */ + fn_format(index_file_name, master_info_file, mysql_data_home, + "", MY_UNPACK_FILENAME); + filename_length= strlen(index_file_name) + 1; /* Count 0 byte */ + dir_length= dirname_length(index_file_name); + bmove_upp((uchar*) index_file_name + filename_length + 6, + (uchar*) index_file_name + filename_length, + filename_length - dir_length); + memcpy(index_file_name + dir_length, "multi-", 6); + + bzero((char*) &index_file, sizeof(index_file)); + index_file.file= -1; +} + + +/** + Free all connection threads + + This is done during early stages of shutdown + to give connection threads and slave threads time + to die before ~Master_info_index is called +*/ + +void Master_info_index::free_connections() +{ + mysql_mutex_assert_owner(&LOCK_active_mi); + my_hash_reset(&master_info_hash); +} + + +/** + Free all connection threads and free structures +*/ + +Master_info_index::~Master_info_index() +{ + my_hash_free(&master_info_hash); + end_io_cache(&index_file); + if (index_file.file >= 0) + my_close(index_file.file, MYF(MY_WME)); +} + + +/* Load All Master_info from master.info.index File + * RETURN: + * 0 - All Success + * 1 - All Fail + * 2 - Some Success, Some Fail + */ + +bool Master_info_index::init_all_master_info() +{ + int thread_mask; + int err_num= 0, succ_num= 0; // The number of success read Master_info + char sign[MAX_CONNECTION_NAME+1]; + File index_file_nr; + THD *thd; + DBUG_ENTER("init_all_master_info"); + + DBUG_ASSERT(master_info_index); + + if ((index_file_nr= my_open(index_file_name, + O_RDWR | O_CREAT | O_BINARY , + MYF(MY_WME | ME_NOREFRESH))) < 0 || + my_sync(index_file_nr, MYF(MY_WME)) || + init_io_cache(&index_file, index_file_nr, + IO_SIZE, READ_CACHE, + my_seek(index_file_nr,0L,MY_SEEK_END,MYF(0)), + 0, MYF(MY_WME | MY_WAIT_IF_FULL))) + { + if (index_file_nr >= 0) + my_close(index_file_nr,MYF(0)); + + sql_print_error("Creation of Master_info index file '%s' failed", + index_file_name); + DBUG_RETURN(1); + } + + /* Initialize Master_info Hash Table */ + if (my_hash_init(&master_info_hash, system_charset_info, + MAX_REPLICATION_THREAD, 0, 0, + (my_hash_get_key) get_key_master_info, + (my_hash_free_key)free_key_master_info, HASH_UNIQUE)) + { + sql_print_error("Initializing Master_info hash table failed"); + DBUG_RETURN(1); + } + + thd= new THD; /* Needed by start_slave_threads */ + thd->thread_stack= (char*) &thd; + thd->store_globals(); + + reinit_io_cache(&index_file, READ_CACHE, 0L,0,0); + while (!init_strvar_from_file(sign, sizeof(sign), + &index_file, NULL)) + { + LEX_STRING connection_name; + Master_info *mi; + char buf_master_info_file[FN_REFLEN]; + char buf_relay_log_info_file[FN_REFLEN]; + + connection_name.str= sign; + connection_name.length= strlen(sign); + if (!(mi= new Master_info(&connection_name, relay_log_recovery)) || + mi->error()) + { + delete mi; + goto error; + } + + init_thread_mask(&thread_mask,mi,0 /*not inverse*/); + + create_logfile_name_with_suffix(buf_master_info_file, + sizeof(buf_master_info_file), + master_info_file, 0, + &mi->cmp_connection_name); + create_logfile_name_with_suffix(buf_relay_log_info_file, + sizeof(buf_relay_log_info_file), + relay_log_info_file, 0, + &mi->cmp_connection_name); + if (global_system_variables.log_warnings > 1) + sql_print_information("Reading Master_info: '%s' Relay_info:'%s'", + buf_master_info_file, buf_relay_log_info_file); + + mi->lock_slave_threads(); + if (init_master_info(mi, buf_master_info_file, buf_relay_log_info_file, + 0, thread_mask)) + { + err_num++; + sql_print_error("Initialized Master_info from '%s' failed", + buf_master_info_file); + if (!master_info_index->get_master_info(&connection_name, + Sql_condition::WARN_LEVEL_NOTE)) + { + /* Master_info is not in HASH; Add it */ + if (master_info_index->add_master_info(mi, FALSE)) + goto error; + succ_num++; + mi->unlock_slave_threads(); + } + else + { + /* Master_info already in HASH */ + sql_print_error(ER_THD_OR_DEFAULT(current_thd, + ER_CONNECTION_ALREADY_EXISTS), + (int) connection_name.length, connection_name.str, + (int) connection_name.length, connection_name.str); + mi->unlock_slave_threads(); + delete mi; + } + continue; + } + else + { + /* Initialization of Master_info succeded. Add it to HASH */ + if (global_system_variables.log_warnings > 1) + sql_print_information("Initialized Master_info from '%s'", + buf_master_info_file); + if (master_info_index->get_master_info(&connection_name, + Sql_condition::WARN_LEVEL_NOTE)) + { + /* Master_info was already registered */ + sql_print_error(ER_THD_OR_DEFAULT(current_thd, + ER_CONNECTION_ALREADY_EXISTS), + (int) connection_name.length, connection_name.str, + (int) connection_name.length, connection_name.str); + mi->unlock_slave_threads(); + delete mi; + continue; + } + + /* Master_info was not registered; add it */ + if (master_info_index->add_master_info(mi, FALSE)) + goto error; + succ_num++; + + if (!opt_skip_slave_start) + { + if (start_slave_threads(current_thd, + 1 /* need mutex */, + 1 /* wait for start*/, + mi, + buf_master_info_file, + buf_relay_log_info_file, + SLAVE_IO | SLAVE_SQL)) + { + sql_print_error("Failed to create slave threads for connection '%.*s'", + (int) connection_name.length, + connection_name.str); + continue; + } + if (global_system_variables.log_warnings) + sql_print_information("Started replication for '%.*s'", + (int) connection_name.length, + connection_name.str); + } + mi->unlock_slave_threads(); + } + } + thd->reset_globals(); + delete thd; + + if (!err_num) // No Error on read Master_info + { + if (global_system_variables.log_warnings > 1) + sql_print_information("Reading of all Master_info entries succeded"); + DBUG_RETURN(0); + } + if (succ_num) // Have some Error and some Success + { + sql_print_warning("Reading of some Master_info entries failed"); + DBUG_RETURN(2); + } + + sql_print_error("Reading of all Master_info entries failed!"); + DBUG_RETURN(1); + +error: + thd->reset_globals(); + delete thd; + DBUG_RETURN(1); +} + + +/* Write new master.info to master.info.index File */ +bool Master_info_index::write_master_name_to_index_file(LEX_STRING *name, + bool do_sync) +{ + DBUG_ASSERT(my_b_inited(&index_file) != 0); + DBUG_ENTER("write_master_name_to_index_file"); + + /* Don't write default slave to master_info.index */ + if (name->length == 0) + DBUG_RETURN(0); + + reinit_io_cache(&index_file, WRITE_CACHE, + my_b_filelength(&index_file), 0, 0); + + if (my_b_write(&index_file, (uchar*) name->str, name->length) || + my_b_write(&index_file, (uchar*) "\n", 1) || + flush_io_cache(&index_file) || + (do_sync && my_sync(index_file.file, MYF(MY_WME)))) + { + sql_print_error("Write of new Master_info for '%.*s' to index file failed", + (int) name->length, name->str); + DBUG_RETURN(1); + } + + DBUG_RETURN(0); +} + + +/** + Get Master_info for a connection and lock the object from deletion + + @param + connection_name Connection name + warning WARN_LEVEL_NOTE -> Don't print anything + WARN_LEVEL_WARN -> Issue warning if not exists + WARN_LEVEL_ERROR-> Issue error if not exists +*/ + +Master_info *get_master_info(const LEX_STRING *connection_name, + Sql_condition::enum_warning_level warning) +{ + Master_info *mi; + DBUG_ENTER("get_master_info"); + + /* Protect against inserts into hash */ + mysql_mutex_lock(&LOCK_active_mi); + /* + The following can only be true during shutdown when slave has been killed + but some other threads are still trying to access slave statistics. + */ + if (unlikely(!master_info_index)) + { + if (warning != Sql_condition::WARN_LEVEL_NOTE) + my_error(WARN_NO_MASTER_INFO, + MYF(warning == Sql_condition::WARN_LEVEL_WARN ? + ME_JUST_WARNING : 0), + (int) connection_name->length, connection_name->str); + mysql_mutex_unlock(&LOCK_active_mi); + DBUG_RETURN(0); + } + if ((mi= master_info_index->get_master_info(connection_name, warning))) + { + /* + We have to use sleep_lock here. If we would use LOCK_active_mi + then we would take locks in wrong order in Master_info::release() + */ + mysql_mutex_lock(&mi->sleep_lock); + mi->users++; + DBUG_PRINT("info",("users: %d", mi->users)); + mysql_mutex_unlock(&mi->sleep_lock); + } + mysql_mutex_unlock(&LOCK_active_mi); + DBUG_RETURN(mi); +} + + +/** + Release master info. + Signals ~Master_info that it's now safe to delete it +*/ + +void Master_info::release() +{ + mysql_mutex_lock(&sleep_lock); + if (!--users && killed) + { + /* Signal ~Master_info that it's ok to now free it */ + mysql_cond_signal(&sleep_cond); + } + mysql_mutex_unlock(&sleep_lock); +} + + +/** + Get Master_info for a connection + + @param + connection_name Connection name + warning WARN_LEVEL_NOTE -> Don't print anything + WARN_LEVEL_WARN -> Issue warning if not exists + WARN_LEVEL_ERROR-> Issue error if not exists +*/ + +Master_info * +Master_info_index::get_master_info(const LEX_STRING *connection_name, + Sql_condition::enum_warning_level warning) +{ + Master_info *mi; + char buff[MAX_CONNECTION_NAME+1], *res; + uint buff_length; + DBUG_ENTER("get_master_info"); + DBUG_PRINT("enter", + ("connection_name: '%.*s'", (int) connection_name->length, + connection_name->str)); + + /* Make name lower case for comparison */ + res= strmake(buff, connection_name->str, connection_name->length); + my_casedn_str(system_charset_info, buff); + buff_length= (size_t) (res-buff); + + mi= (Master_info*) my_hash_search(&master_info_hash, + (uchar*) buff, buff_length); + if (!mi && warning != Sql_condition::WARN_LEVEL_NOTE) + { + my_error(WARN_NO_MASTER_INFO, + MYF(warning == Sql_condition::WARN_LEVEL_WARN ? ME_JUST_WARNING : + 0), + (int) connection_name->length, + connection_name->str); + } + DBUG_RETURN(mi); +} + + +/* Check Master_host & Master_port is duplicated or not */ +bool Master_info_index::check_duplicate_master_info(LEX_STRING *name_arg, + const char *host, + uint port) +{ + Master_info *mi; + DBUG_ENTER("check_duplicate_master_info"); + + mysql_mutex_assert_owner(&LOCK_active_mi); + DBUG_ASSERT(master_info_index); + + /* Get full host and port name */ + if ((mi= master_info_index->get_master_info(name_arg, + Sql_condition::WARN_LEVEL_NOTE))) + { + if (!host) + host= mi->host; + if (!port) + port= mi->port; + } + if (!host || !port) + DBUG_RETURN(FALSE); // Not comparable yet + + for (uint i= 0; i < master_info_hash.records; ++i) + { + Master_info *tmp_mi; + tmp_mi= (Master_info *) my_hash_element(&master_info_hash, i); + if (tmp_mi == mi) + continue; // Current connection + if (!strcasecmp(host, tmp_mi->host) && port == tmp_mi->port) + { + my_error(ER_CONNECTION_ALREADY_EXISTS, MYF(0), + (int) name_arg->length, + name_arg->str, + (int) tmp_mi->connection_name.length, + tmp_mi->connection_name.str); + DBUG_RETURN(TRUE); + } + } + DBUG_RETURN(FALSE); +} + + +/* Add a Master_info class to Hash Table */ +bool Master_info_index::add_master_info(Master_info *mi, bool write_to_file) +{ + /* + We have to protect against shutdown to ensure we are not calling + my_hash_insert() while my_hash_free() is in progress + */ + if (unlikely(shutdown_in_progress) || + !my_hash_insert(&master_info_hash, (uchar*) mi)) + { + if (global_system_variables.log_warnings > 1) + sql_print_information("Added new Master_info '%.*s' to hash table", + (int) mi->connection_name.length, + mi->connection_name.str); + if (write_to_file) + return write_master_name_to_index_file(&mi->connection_name, 1); + return FALSE; + } + + /* Impossible error (EOM) ? */ + sql_print_error("Adding new entry '%.*s' to master_info failed", + (int) mi->connection_name.length, + mi->connection_name.str); + return TRUE; +} + + +/** + Remove a Master_info class From Hash Table + + TODO: Change this to use my_rename() to make the file name creation + atomic +*/ + +bool Master_info_index::remove_master_info(Master_info *mi) +{ + DBUG_ENTER("remove_master_info"); + mysql_mutex_assert_owner(&LOCK_active_mi); + + // Delete Master_info and rewrite others to file + if (!my_hash_delete(&master_info_hash, (uchar*) mi)) + { + File index_file_nr; + + // Close IO_CACHE and FILE handler fisrt + end_io_cache(&index_file); + my_close(index_file.file, MYF(MY_WME)); + + // Reopen File and truncate it + if ((index_file_nr= my_open(index_file_name, + O_RDWR | O_CREAT | O_TRUNC | O_BINARY , + MYF(MY_WME))) < 0 || + init_io_cache(&index_file, index_file_nr, + IO_SIZE, WRITE_CACHE, + my_seek(index_file_nr,0L,MY_SEEK_END,MYF(0)), + 0, MYF(MY_WME | MY_WAIT_IF_FULL))) + { + int error= my_errno; + if (index_file_nr >= 0) + my_close(index_file_nr,MYF(0)); + + sql_print_error("Create of Master Info Index file '%s' failed with " + "error: %M", + index_file_name, error); + DBUG_RETURN(TRUE); + } + + // Rewrite Master_info.index + for (uint i= 0; i< master_info_hash.records; ++i) + { + Master_info *tmp_mi; + tmp_mi= (Master_info *) my_hash_element(&master_info_hash, i); + write_master_name_to_index_file(&tmp_mi->connection_name, 0); + } + if (my_sync(index_file_nr, MYF(MY_WME))) + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); +} + + +/** + give_error_if_slave_running() + + @param + already_locked 0 if we need to lock, 1 if we have LOCK_active_mi_locked + + @return + TRUE If some slave is running. An error is printed + FALSE No slave is running +*/ + +bool give_error_if_slave_running(bool already_locked) +{ + bool ret= 0; + DBUG_ENTER("give_error_if_slave_running"); + + if (!already_locked) + mysql_mutex_lock(&LOCK_active_mi); + if (!master_info_index) + { + my_error(ER_SERVER_SHUTDOWN, MYF(0)); + ret= 1; + } + else + { + HASH *hash= &master_info_index->master_info_hash; + for (uint i= 0; i< hash->records; ++i) + { + Master_info *mi; + mi= (Master_info *) my_hash_element(hash, i); + if (mi->rli.slave_running != MYSQL_SLAVE_NOT_RUN) + { + my_error(ER_SLAVE_MUST_STOP, MYF(0), (int) mi->connection_name.length, + mi->connection_name.str); + ret= 1; + break; + } + } + } + if (!already_locked) + mysql_mutex_unlock(&LOCK_active_mi); + DBUG_RETURN(ret); +} + + +/** + any_slave_sql_running() + + @return + 0 No Slave SQL thread is running + # Number of slave SQL thread running + + Note that during shutdown we return 1. This is needed to ensure we + don't try to resize thread pool during shutdown as during shutdown + master_info_hash may be freeing the hash and during that time + hash entries can't be accessed. +*/ + +uint any_slave_sql_running() +{ + uint count= 0; + HASH *hash; + DBUG_ENTER("any_slave_sql_running"); + + mysql_mutex_lock(&LOCK_active_mi); + if (unlikely(shutdown_in_progress || !master_info_index)) + { + mysql_mutex_unlock(&LOCK_active_mi); + DBUG_RETURN(1); + } + hash= &master_info_index->master_info_hash; + for (uint i= 0; i< hash->records; ++i) + { + Master_info *mi= (Master_info *)my_hash_element(hash, i); + if (mi->rli.slave_running != MYSQL_SLAVE_NOT_RUN) + count++; + } + mysql_mutex_unlock(&LOCK_active_mi); + DBUG_RETURN(count); +} + + +/** + Master_info_index::start_all_slaves() + + Start all slaves that was not running. + + @return + TRUE Error + FALSE Everything ok. + + This code is written so that we don't keep LOCK_active_mi active + while we are starting a slave. +*/ + +bool Master_info_index::start_all_slaves(THD *thd) +{ + bool result= FALSE; + DBUG_ENTER("start_all_slaves"); + mysql_mutex_assert_owner(&LOCK_active_mi); + + for (uint i= 0; i< master_info_hash.records; i++) + { + Master_info *mi; + mi= (Master_info *) my_hash_element(&master_info_hash, i); + mi->in_start_all_slaves= 0; + } + + for (uint i= 0; i< master_info_hash.records; ) + { + int error; + Master_info *mi; + mi= (Master_info *) my_hash_element(&master_info_hash, i); + + /* + Try to start all slaves that are configured (host is defined) + and are not already running + */ + if (!((mi->slave_running == MYSQL_SLAVE_NOT_RUN || + !mi->rli.slave_running) && *mi->host) || + mi->in_start_all_slaves) + { + i++; + continue; + } + mi->in_start_all_slaves= 1; + + mysql_mutex_lock(&mi->sleep_lock); + mi->users++; // Mark used + mysql_mutex_unlock(&mi->sleep_lock); + mysql_mutex_unlock(&LOCK_active_mi); + error= start_slave(thd, mi, 1); + mi->release(); + mysql_mutex_lock(&LOCK_active_mi); + if (error) + { + my_error(ER_CANT_START_STOP_SLAVE, MYF(0), + "START", + (int) mi->connection_name.length, + mi->connection_name.str); + result= 1; + if (error < 0) // fatal error + break; + } + else if (thd) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SLAVE_STARTED, ER_THD(thd, ER_SLAVE_STARTED), + (int) mi->connection_name.length, + mi->connection_name.str); + /* Restart from first element as master_info_hash may have changed */ + i= 0; + continue; + } + DBUG_RETURN(result); +} + + +/** + Master_info_index::stop_all_slaves() + + Start all slaves that was not running. + + @param thread id from user + + @return + TRUE Error + FALSE Everything ok. + + This code is written so that we don't keep LOCK_active_mi active + while we are stopping a slave. +*/ + +bool Master_info_index::stop_all_slaves(THD *thd) +{ + bool result= FALSE; + DBUG_ENTER("stop_all_slaves"); + mysql_mutex_assert_owner(&LOCK_active_mi); + DBUG_ASSERT(thd); + + for (uint i= 0; i< master_info_hash.records; i++) + { + Master_info *mi; + mi= (Master_info *) my_hash_element(&master_info_hash, i); + mi->in_stop_all_slaves= 0; + } + + for (uint i= 0; i< master_info_hash.records ;) + { + int error; + Master_info *mi; + mi= (Master_info *) my_hash_element(&master_info_hash, i); + if (!(mi->slave_running != MYSQL_SLAVE_NOT_RUN || + mi->rli.slave_running) || + mi->in_stop_all_slaves) + { + i++; + continue; + } + mi->in_stop_all_slaves= 1; // Protection for loops + + mysql_mutex_lock(&mi->sleep_lock); + mi->users++; // Mark used + mysql_mutex_unlock(&mi->sleep_lock); + mysql_mutex_unlock(&LOCK_active_mi); + error= stop_slave(thd, mi, 1); + mi->release(); + mysql_mutex_lock(&LOCK_active_mi); + if (error) + { + my_error(ER_CANT_START_STOP_SLAVE, MYF(0), + "STOP", + (int) mi->connection_name.length, + mi->connection_name.str); + result= 1; + if (error < 0) // Fatal error + break; + } + else + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SLAVE_STOPPED, ER_THD(thd, ER_SLAVE_STOPPED), + (int) mi->connection_name.length, + mi->connection_name.str); + /* Restart from first element as master_info_hash may have changed */ + i= 0; + continue; + } + DBUG_RETURN(result); +} + +Domain_id_filter::Domain_id_filter() : m_filter(false) +{ + for (int i= DO_DOMAIN_IDS; i <= IGNORE_DOMAIN_IDS; i ++) + { + my_init_dynamic_array(&m_domain_ids[i], sizeof(ulong), 16, 16, MYF(0)); + } +} + +Domain_id_filter::~Domain_id_filter() +{ + for (int i= DO_DOMAIN_IDS; i <= IGNORE_DOMAIN_IDS; i ++) + { + delete_dynamic(&m_domain_ids[i]); + } +} + +/** + Update m_filter flag for the current group by looking up its domain id in the + domain ids list. DO_DOMAIN_IDS list is only looked-up is both (do & ignore) + list are non-empty. +*/ +void Domain_id_filter::do_filter(ulong domain_id) +{ + DYNAMIC_ARRAY *do_domain_ids= &m_domain_ids[DO_DOMAIN_IDS]; + DYNAMIC_ARRAY *ignore_domain_ids= &m_domain_ids[IGNORE_DOMAIN_IDS]; + + if (do_domain_ids->elements > 0) + { + if (likely(do_domain_ids->elements == 1)) + m_filter= ((* (ulong *) dynamic_array_ptr(do_domain_ids, 0)) + != domain_id); + else + m_filter= (bsearch((const ulong *) &domain_id, do_domain_ids->buffer, + do_domain_ids->elements, sizeof(ulong), + change_master_id_cmp) == NULL); + } + else if (ignore_domain_ids->elements > 0) + { + if (likely(ignore_domain_ids->elements == 1)) + m_filter= ((* (ulong *) dynamic_array_ptr(ignore_domain_ids, 0)) == + domain_id); + else + m_filter= (bsearch((const ulong *) &domain_id, ignore_domain_ids->buffer, + ignore_domain_ids->elements, sizeof(ulong), + change_master_id_cmp) != NULL); + } + return; +} + +/** + Reset m_filter. It should be called when IO thread receives COMMIT_EVENT or + XID_EVENT. +*/ +void Domain_id_filter::reset_filter() +{ + m_filter= false; +} + +/** + Update the do/ignore domain id filter lists. + + @param do_ids [IN] domain ids to be kept + @param ignore_ids [IN] domain ids to be filtered out + @param using_gtid [IN] use GTID? + + @retval false Success + true Error +*/ +bool Domain_id_filter::update_ids(DYNAMIC_ARRAY *do_ids, + DYNAMIC_ARRAY *ignore_ids, + bool using_gtid) +{ + bool do_list_empty, ignore_list_empty; + + if (do_ids) + { + do_list_empty= (do_ids->elements > 0) ? false : true; + } else { + do_list_empty= (m_domain_ids[DO_DOMAIN_IDS].elements > 0) ? false : true; + } + + if (ignore_ids) + { + ignore_list_empty= (ignore_ids->elements > 0) ? false : true; + } else { + ignore_list_empty= (m_domain_ids[IGNORE_DOMAIN_IDS].elements > 0) ? false : + true; + } + + if (!do_list_empty && !ignore_list_empty) + { + sql_print_error("Both DO_DOMAIN_IDS & IGNORE_DOMAIN_IDS lists can't be " + "non-empty at the same time"); + return true; + } + + if (using_gtid == Master_info::USE_GTID_NO && + (!do_list_empty || !ignore_list_empty)) + { + sql_print_error("DO_DOMAIN_IDS or IGNORE_DOMAIN_IDS lists can't be " + "non-empty in non-GTID mode (MASTER_USE_GTID=no)"); + return true; + } + + if (do_ids) + update_change_master_ids(do_ids, &m_domain_ids[DO_DOMAIN_IDS]); + + if (ignore_ids) + update_change_master_ids(ignore_ids, &m_domain_ids[IGNORE_DOMAIN_IDS]); + + m_filter= false; + + return false; +} + +/** + Serialize and store the ids from domain id lists into the thd's protocol + buffer. + + @param thd [IN] thread handler + + @retval void +*/ +void Domain_id_filter::store_ids(THD *thd) +{ + for (int i= DO_DOMAIN_IDS; i <= IGNORE_DOMAIN_IDS; i ++) + { + prot_store_ids(thd, &m_domain_ids[i]); + } +} + +/** + Initialize the given domain_id list (DYNAMIC_ARRAY) with the + space-separated list of numbers from the specified IO_CACHE where + the first number represents the total number of entries to follows. + + @param f [IN] IO_CACHE file + @param type [IN] domain id list type + + @retval false Success + true Error +*/ +bool Domain_id_filter::init_ids(IO_CACHE *f, enum_list_type type) +{ + return init_dynarray_intvar_from_file(&m_domain_ids[type], f); +} + +/** + Return the elements of the give domain id list type as string. + + @param type [IN] domain id list type + + @retval a string buffer storing the total number + of elements followed by the individual + elements (space-separated) in the + specified list. + + Note: Its caller's responsibility to free the returned string buffer. +*/ +char *Domain_id_filter::as_string(enum_list_type type) +{ + char *buf; + size_t sz; + DYNAMIC_ARRAY *ids= &m_domain_ids[type]; + + sz= (sizeof(ulong) * 3 + 1) * (1 + ids->elements); + + if (!(buf= (char *) my_malloc(sz, MYF(MY_WME)))) + return NULL; + + // Store the total number of elements followed by the individual elements. + ulong cur_len= sprintf(buf, "%u", ids->elements); + sz-= cur_len; + + for (uint i= 0; i < ids->elements; i++) + { + ulong domain_id; + get_dynamic(ids, (void *) &domain_id, i); + cur_len+= my_snprintf(buf + cur_len, sz, " %u", domain_id); + sz-= cur_len; + } + return buf; +} + +void update_change_master_ids(DYNAMIC_ARRAY *new_ids, DYNAMIC_ARRAY *old_ids) +{ + reset_dynamic(old_ids); + + /* bsearch requires an ordered list. */ + sort_dynamic(new_ids, change_master_id_cmp); + + for (uint i= 0; i < new_ids->elements; i++) + { + ulong id; + get_dynamic(new_ids, (void *) &id, i); + + if (bsearch((const ulong *) &id, old_ids->buffer, old_ids->elements, + sizeof(ulong), change_master_id_cmp) == NULL) + { + insert_dynamic(old_ids, (ulong *) &id); + } + } + return; +} + +/** + Serialize and store the ids from the given ids DYNAMIC_ARRAY into the thd's + protocol buffer. + + @param thd [IN] thread handler + @param ids [IN] ids list + + @retval void +*/ + +void prot_store_ids(THD *thd, DYNAMIC_ARRAY *ids) +{ + char buff[FN_REFLEN]; + uint i, cur_len; + + for (i= 0, buff[0]= 0, cur_len= 0; i < ids->elements; i++) + { + ulong id, len; + char dbuff[FN_REFLEN]; + get_dynamic(ids, (void *) &id, i); + len= sprintf(dbuff, (i == 0 ? "%lu" : ", %lu"), id); + if (cur_len + len + 4 > FN_REFLEN) + { + /* + break the loop whenever remained space could not fit + ellipses on the next cycle + */ + sprintf(dbuff + cur_len, "..."); + break; + } + cur_len += sprintf(buff + cur_len, "%s", dbuff); + } + thd->protocol->store(buff, &my_charset_bin); + return; +} + +bool Master_info_index::flush_all_relay_logs() +{ + DBUG_ENTER("flush_all_relay_logs"); + bool result= false; + int error= 0; + mysql_mutex_lock(&LOCK_active_mi); + for (uint i= 0; i< master_info_hash.records; i++) + { + Master_info *mi; + mi= (Master_info *) my_hash_element(&master_info_hash, i); + mi->in_flush_all_relay_logs= 0; + } + for (uint i=0; i < master_info_hash.records;) + { + Master_info *mi; + mi= (Master_info *)my_hash_element(&master_info_hash, i); + DBUG_ASSERT(mi); + + if (mi->in_flush_all_relay_logs) + { + i++; + continue; + } + mi->in_flush_all_relay_logs= 1; + + mysql_mutex_lock(&mi->sleep_lock); + mi->users++; // Mark used + mysql_mutex_unlock(&mi->sleep_lock); + mysql_mutex_unlock(&LOCK_active_mi); + + mysql_mutex_lock(&mi->data_lock); + error= rotate_relay_log(mi); + mysql_mutex_unlock(&mi->data_lock); + mi->release(); + mysql_mutex_lock(&LOCK_active_mi); + + if (error) + { + result= true; + break; + } + /* Restart from first element as master_info_hash may have changed */ + i= 0; + continue; + } + mysql_mutex_unlock(&LOCK_active_mi); + DBUG_RETURN(result); +} #endif /* HAVE_REPLICATION */ diff --git a/sql/rpl_mi.h b/sql/rpl_mi.h index f9c8c8ea5b2..d0f6171815c 100644 --- a/sql/rpl_mi.h +++ b/sql/rpl_mi.h @@ -21,9 +21,118 @@ #include "rpl_rli.h" #include "rpl_reporting.h" #include "my_sys.h" +#include "rpl_filter.h" +#include "keycaches.h" typedef struct st_mysql MYSQL; +/** + Domain id based filter to handle DO_DOMAIN_IDS and IGNORE_DOMAIN_IDS used to + set filtering on replication slave based on event's GTID domain_id. +*/ +class Domain_id_filter +{ +private: + /* + Flag to tell whether the events in the current GTID group get written to + the relay log. It is set according to the domain_id based filtering rule + on every GTID_EVENT and reset at the end of current GTID event group. + */ + bool m_filter; + + /* + DO_DOMAIN_IDS (0): + Ignore all the events which do not belong to any of the domain ids in the + list. + + IGNORE_DOMAIN_IDS (1): + Ignore the events which belong to one of the domain ids in the list. + */ + DYNAMIC_ARRAY m_domain_ids[2]; + +public: + /* domain id list types */ + enum enum_list_type { + DO_DOMAIN_IDS= 0, + IGNORE_DOMAIN_IDS + }; + + Domain_id_filter(); + + ~Domain_id_filter(); + + /* + Returns whether the current group needs to be filtered. + */ + bool is_group_filtered() { return m_filter; } + + /* + Checks whether the group with the specified domain_id needs to be + filtered and updates m_filter flag accordingly. + */ + void do_filter(ulong domain_id); + + /* + Reset m_filter. It should be called when IO thread receives COMMIT_EVENT or + XID_EVENT. + */ + void reset_filter(); + + /* + Update the do/ignore domain id filter lists. + + @param do_ids [IN] domain ids to be kept + @param ignore_ids [IN] domain ids to be filtered out + @param using_gtid [IN] use GTID? + + @retval false Success + true Error + */ + bool update_ids(DYNAMIC_ARRAY *do_ids, DYNAMIC_ARRAY *ignore_ids, + bool using_gtid); + + /* + Serialize and store the ids from domain id lists into the thd's protocol + buffer. + + @param thd [IN] thread handler + + @retval void + */ + void store_ids(THD *thd); + + /* + Initialize the given domain id list (DYNAMIC_ARRAY) with the + space-separated list of numbers from the specified IO_CACHE where + the first number is the total number of entries to follows. + + @param f [IN] IO_CACHE file + @param type [IN] domain id list type + + @retval false Success + true Error + */ + bool init_ids(IO_CACHE *f, enum_list_type type); + + /* + Return the elements of the give domain id list type as string. + + @param type [IN] domain id list type + + @retval a string buffer storing the total number + of elements followed by the individual + elements (space-separated) in the + specified list. + + Note: Its caller's responsibility to free the returned string buffer. + */ + char *as_string(enum_list_type type); + +}; + + +extern TYPELIB slave_parallel_mode_typelib; + /***************************************************************************** Replication IO Thread @@ -59,41 +168,71 @@ typedef struct st_mysql MYSQL; class Master_info : public Slave_reporting_capability { public: - Master_info(bool is_slave_recovery); + enum enum_using_gtid { + USE_GTID_NO= 0, USE_GTID_CURRENT_POS= 1, USE_GTID_SLAVE_POS= 2 + }; + + Master_info(LEX_STRING *connection_name, bool is_slave_recovery); ~Master_info(); bool shall_ignore_server_id(ulong s_id); void clear_in_memory_info(bool all); + bool error() + { + /* If malloc() in initialization failed */ + return connection_name.str == 0; + } + static const char *using_gtid_astext(enum enum_using_gtid arg); + bool using_parallel() + { + return opt_slave_parallel_threads > 0 && + parallel_mode > SLAVE_PARALLEL_NONE; + } + void release(); + void wait_until_free(); + void lock_slave_threads(); + void unlock_slave_threads(); /* the variables below are needed because we can change masters on the fly */ - char master_log_name[FN_REFLEN]; + char master_log_name[FN_REFLEN+6]; /* Room for multi-*/ char host[HOSTNAME_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1]; char user[USERNAME_LENGTH+1]; char password[MAX_PASSWORD_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1]; + LEX_STRING connection_name; /* User supplied connection name */ + LEX_STRING cmp_connection_name; /* Connection name in lower case */ bool ssl; // enables use of SSL connection if true char ssl_ca[FN_REFLEN], ssl_capath[FN_REFLEN], ssl_cert[FN_REFLEN]; char ssl_cipher[FN_REFLEN], ssl_key[FN_REFLEN]; + char ssl_crl[FN_REFLEN], ssl_crlpath[FN_REFLEN]; bool ssl_verify_server_cert; my_off_t master_log_pos; File fd; // we keep the file open, so we need to remember the file pointer IO_CACHE file; - mysql_mutex_t data_lock, run_lock, sleep_lock; + mysql_mutex_t data_lock, run_lock, sleep_lock, start_stop_lock; mysql_cond_t data_cond, start_cond, stop_cond, sleep_cond; THD *io_thd; MYSQL* mysql; uint32 file_id; /* for 3.23 load data infile */ Relay_log_info rli; uint port; + Rpl_filter* rpl_filter; /* Each replication can set its filter rule*/ /* to hold checksum alg in use until IO thread has received FD. Initialized to novalue, then set to the queried from master @@global.binlog_checksum and deactivated once FD has been received. */ - uint8 checksum_alg_before_fd; + enum enum_binlog_checksum_alg checksum_alg_before_fd; uint connect_retry; #ifndef DBUG_OFF int events_till_disconnect; + + /* + The following are auxiliary DBUG variables used to kill IO thread in the + middle of a group/transaction (see "kill_slave_io_after_2_events"). + */ + bool dbug_do_disconnect; + int dbug_event_counter; #endif bool inited; volatile bool abort_slave; @@ -119,7 +258,61 @@ class Master_info : public Slave_reporting_capability ulonglong received_heartbeats; // counter of received heartbeat events DYNAMIC_ARRAY ignore_server_ids; ulong master_id; + /* + At reconnect and until the first rotate event is seen, prev_master_id is + the value of master_id during the previous connection, used to detect + silent change of master server during reconnects. + */ + ulong prev_master_id; + /* + Which kind of GTID position (if any) is used when connecting to master. + + Note that you can not change the numeric values of these, they are used + in master.info. + */ + enum enum_using_gtid using_gtid; + + /* + This GTID position records how far we have fetched into the relay logs. + This is used to continue fetching when the IO thread reconnects to the + master. + + (Full slave stop/start does not use it, as it resets the relay logs). + */ + slave_connection_state gtid_current_pos; + /* + If events_queued_since_last_gtid is non-zero, it is the number of events + queued so far in the relaylog of a GTID-prefixed event group. + It is zero when no partial event group has been queued at the moment. + */ + uint64 events_queued_since_last_gtid; + /* + The GTID of the partially-queued event group, when + events_queued_since_last_gtid is non-zero. + */ + rpl_gtid last_queued_gtid; + /* Whether last_queued_gtid had the FL_STANDALONE flag set. */ + bool last_queued_gtid_standalone; + /* + When slave IO thread needs to reconnect, gtid_reconnect_event_skip_count + counts number of events to skip from the first GTID-prefixed event group, + to avoid duplicating events in the relay log. + */ + uint64 gtid_reconnect_event_skip_count; + /* gtid_event_seen is false until we receive first GTID event from master. */ + bool gtid_event_seen; + bool in_start_all_slaves, in_stop_all_slaves; + bool in_flush_all_relay_logs; + uint users; /* Active user for object */ + uint killed; + + /* domain-id based filter */ + Domain_id_filter domain_id_filter; + + /* The parallel replication mode. */ + enum_slave_parallel_mode parallel_mode; }; + int init_master_info(Master_info* mi, const char* master_info_fname, const char* slave_info_fname, bool abort_if_no_master_info_file, @@ -128,7 +321,66 @@ void end_master_info(Master_info* mi); int flush_master_info(Master_info* mi, bool flush_relay_log_cache, bool need_lock_relay_log); -int change_master_server_id_cmp(ulong *id1, ulong *id2); +void copy_filter_setting(Rpl_filter* dst_filter, Rpl_filter* src_filter); +void update_change_master_ids(DYNAMIC_ARRAY *new_ids, DYNAMIC_ARRAY *old_ids); +void prot_store_ids(THD *thd, DYNAMIC_ARRAY *ids); + +/* + Multi master are handled trough this struct. + Changes to this needs to be protected by LOCK_active_mi; +*/ + +class Master_info_index +{ +private: + IO_CACHE index_file; + char index_file_name[FN_REFLEN]; + +public: + Master_info_index(); + ~Master_info_index(); + + HASH master_info_hash; + + bool init_all_master_info(); + bool write_master_name_to_index_file(LEX_STRING *connection_name, + bool do_sync); + + bool check_duplicate_master_info(LEX_STRING *connection_name, + const char *host, uint port); + bool add_master_info(Master_info *mi, bool write_to_file); + bool remove_master_info(Master_info *mi); + Master_info *get_master_info(const LEX_STRING *connection_name, + Sql_condition::enum_warning_level warning); + bool start_all_slaves(THD *thd); + bool stop_all_slaves(THD *thd); + void free_connections(); + bool flush_all_relay_logs(); +}; + + +/* + The class rpl_io_thread_info is the THD::system_thread_info for the IO thread. +*/ +class rpl_io_thread_info +{ +public: +}; + + +Master_info *get_master_info(const LEX_STRING *connection_name, + Sql_condition::enum_warning_level warning); +bool check_master_connection_name(LEX_STRING *name); +void create_logfile_name_with_suffix(char *res_file_name, size_t length, + const char *info_file, + bool append, + LEX_STRING *suffix); + +uchar *get_key_master_info(Master_info *mi, size_t *length, + my_bool not_used __attribute__((unused))); +void free_key_master_info(Master_info *mi); +uint any_slave_sql_running(); +bool give_error_if_slave_running(bool already_lock); #endif /* HAVE_REPLICATION */ #endif /* RPL_MI_H */ diff --git a/sql/rpl_parallel.cc b/sql/rpl_parallel.cc new file mode 100644 index 00000000000..8fef2d66635 --- /dev/null +++ b/sql/rpl_parallel.cc @@ -0,0 +1,2842 @@ +#include "my_global.h" +#include "rpl_parallel.h" +#include "slave.h" +#include "rpl_mi.h" +#include "sql_parse.h" +#include "debug_sync.h" + +/* + Code for optional parallel execution of replicated events on the slave. +*/ + + +/* + Maximum number of queued events to accumulate in a local free list, before + moving them to the global free list. There is additional a limit of how much + to accumulate based on opt_slave_parallel_max_queued. +*/ +#define QEV_BATCH_FREE 200 + + +struct rpl_parallel_thread_pool global_rpl_thread_pool; + +static void signal_error_to_sql_driver_thread(THD *thd, rpl_group_info *rgi, + int err); + +static int +rpt_handle_event(rpl_parallel_thread::queued_event *qev, + struct rpl_parallel_thread *rpt) +{ + int err; + rpl_group_info *rgi= qev->rgi; + Relay_log_info *rli= rgi->rli; + THD *thd= rgi->thd; + Log_event *ev; + + DBUG_ASSERT(qev->typ == rpl_parallel_thread::queued_event::QUEUED_EVENT); + ev= qev->ev; + + thd->system_thread_info.rpl_sql_info->rpl_filter = rli->mi->rpl_filter; + ev->thd= thd; + + strcpy(rgi->event_relay_log_name_buf, qev->event_relay_log_name); + rgi->event_relay_log_name= rgi->event_relay_log_name_buf; + rgi->event_relay_log_pos= qev->event_relay_log_pos; + rgi->future_event_relay_log_pos= qev->future_event_relay_log_pos; + strcpy(rgi->future_event_master_log_name, qev->future_event_master_log_name); + if (!(ev->is_artificial_event() || ev->is_relay_log_event() || + (ev->when == 0))) + rgi->last_master_timestamp= ev->when + (time_t)ev->exec_time; + err= apply_event_and_update_pos_for_parallel(ev, thd, rgi); + + thread_safe_increment64(&rli->executed_entries); + /* ToDo: error handling. */ + return err; +} + + +static void +handle_queued_pos_update(THD *thd, rpl_parallel_thread::queued_event *qev) +{ + int cmp; + Relay_log_info *rli; + rpl_parallel_entry *e; + + /* + Events that are not part of an event group, such as Format Description, + Stop, GTID List and such, are executed directly in the driver SQL thread, + to keep the relay log state up-to-date. But the associated position update + is done here, in sync with other normal events as they are queued to + worker threads. + */ + if ((thd->variables.option_bits & OPTION_BEGIN) && + opt_using_transactions) + return; + + /* Do not update position if an earlier event group caused an error abort. */ + DBUG_ASSERT(qev->typ == rpl_parallel_thread::queued_event::QUEUED_POS_UPDATE); + e= qev->entry_for_queued; + if (e->stop_on_error_sub_id < (uint64)ULONGLONG_MAX || e->force_abort) + return; + + rli= qev->rgi->rli; + mysql_mutex_lock(&rli->data_lock); + cmp= strcmp(rli->group_relay_log_name, qev->event_relay_log_name); + if (cmp < 0) + { + rli->group_relay_log_pos= qev->future_event_relay_log_pos; + strmake_buf(rli->group_relay_log_name, qev->event_relay_log_name); + rli->notify_group_relay_log_name_update(); + } else if (cmp == 0 && + rli->group_relay_log_pos < qev->future_event_relay_log_pos) + rli->group_relay_log_pos= qev->future_event_relay_log_pos; + + cmp= strcmp(rli->group_master_log_name, qev->future_event_master_log_name); + if (cmp < 0) + { + strcpy(rli->group_master_log_name, qev->future_event_master_log_name); + rli->group_master_log_pos= qev->future_event_master_log_pos; + } + else if (cmp == 0 + && rli->group_master_log_pos < qev->future_event_master_log_pos) + rli->group_master_log_pos= qev->future_event_master_log_pos; + mysql_mutex_unlock(&rli->data_lock); + mysql_cond_broadcast(&rli->data_cond); +} + + +/* + Wait for any pending deadlock kills. Since deadlock kills happen + asynchronously, we need to be sure they will be completed before starting a + new transaction. Otherwise the new transaction might suffer a spurious kill. +*/ +static void +wait_for_pending_deadlock_kill(THD *thd, rpl_group_info *rgi) +{ + PSI_stage_info old_stage; + + mysql_mutex_lock(&thd->LOCK_wakeup_ready); + thd->ENTER_COND(&thd->COND_wakeup_ready, &thd->LOCK_wakeup_ready, + &stage_waiting_for_deadlock_kill, &old_stage); + while (rgi->killed_for_retry == rpl_group_info::RETRY_KILL_PENDING) + mysql_cond_wait(&thd->COND_wakeup_ready, &thd->LOCK_wakeup_ready); + thd->EXIT_COND(&old_stage); +} + + +static void +finish_event_group(rpl_parallel_thread *rpt, uint64 sub_id, + rpl_parallel_entry *entry, rpl_group_info *rgi) +{ + THD *thd= rpt->thd; + wait_for_commit *wfc= &rgi->commit_orderer; + int err; + + thd->get_stmt_da()->set_overwrite_status(true); + /* + Remove any left-over registration to wait for a prior commit to + complete. Normally, such wait would already have been removed at + this point by wait_for_prior_commit() called from within COMMIT + processing. However, in case of MyISAM and no binlog, we might not + have any commit processing, and so we need to do the wait here, + before waking up any subsequent commits, to preserve correct + order of event execution. Also, in the error case we might have + skipped waiting and thus need to remove it explicitly. + + It is important in the non-error case to do a wait, not just an + unregister. Because we might be last in a group-commit that is + replicated in parallel, and the following event will then wait + for us to complete and rely on this also ensuring that any other + event in the group has completed. + + And in the error case, correct GCO lifetime relies on the fact that once + the last event group in the GCO has executed wait_for_prior_commit(), + all earlier event groups have also committed; this way no more + mark_start_commit() calls can be made and it is safe to de-allocate + the GCO. + */ + err= wfc->wait_for_prior_commit(thd); + if (unlikely(err) && !rgi->worker_error) + signal_error_to_sql_driver_thread(thd, rgi, err); + thd->wait_for_commit_ptr= NULL; + + mysql_mutex_lock(&entry->LOCK_parallel_entry); + /* + We need to mark that this event group started its commit phase, in case we + missed it before (otherwise we would deadlock the next event group that is + waiting for this). In most cases (normal DML), it will be a no-op. + */ + rgi->mark_start_commit_no_lock(); + + if (entry->last_committed_sub_id < sub_id) + { + /* + Record that this event group has finished (eg. transaction is + committed, if transactional), so other event groups will no longer + attempt to wait for us to commit. Once we have increased + entry->last_committed_sub_id, no other threads will execute + register_wait_for_prior_commit() against us. Thus, by doing one + extra (usually redundant) wakeup_subsequent_commits() we can ensure + that no register_wait_for_prior_commit() can ever happen without a + subsequent wakeup_subsequent_commits() to wake it up. + + We can race here with the next transactions, but that is fine, as + long as we check that we do not decrease last_committed_sub_id. If + this commit is done, then any prior commits will also have been + done and also no longer need waiting for. + */ + entry->last_committed_sub_id= sub_id; + if (entry->need_sub_id_signal) + mysql_cond_broadcast(&entry->COND_parallel_entry); + + /* Now free any GCOs in which all transactions have committed. */ + group_commit_orderer *tmp_gco= rgi->gco; + while (tmp_gco && + (!tmp_gco->next_gco || tmp_gco->last_sub_id > sub_id || + tmp_gco->next_gco->wait_count > entry->count_committing_event_groups)) + { + /* + We must not free a GCO before the wait_count of the following GCO has + been reached and wakeup has been sent. Otherwise we will lose the + wakeup and hang (there were several such bugs in the past). + + The intention is that this is ensured already since we only free when + the last event group in the GCO has committed + (tmp_gco->last_sub_id <= sub_id). However, if we have a bug, we have + extra check on next_gco->wait_count to hopefully avoid hanging; we + have here an assertion in debug builds that this check does not in + fact trigger. + */ + DBUG_ASSERT(!tmp_gco->next_gco || tmp_gco->last_sub_id > sub_id); + tmp_gco= tmp_gco->prev_gco; + } + while (tmp_gco) + { + group_commit_orderer *prev_gco= tmp_gco->prev_gco; + tmp_gco->next_gco->prev_gco= NULL; + rpt->loc_free_gco(tmp_gco); + tmp_gco= prev_gco; + } + } + + /* + If this event group got error, then any following event groups that have + not yet started should just skip their group, preparing for stop of the + SQL driver thread. + */ + if (unlikely(rgi->worker_error) && + entry->stop_on_error_sub_id == (uint64)ULONGLONG_MAX) + entry->stop_on_error_sub_id= sub_id; + mysql_mutex_unlock(&entry->LOCK_parallel_entry); + + if (rgi->killed_for_retry == rpl_group_info::RETRY_KILL_PENDING) + wait_for_pending_deadlock_kill(thd, rgi); + thd->clear_error(); + thd->reset_killed(); + /* + Would do thd->get_stmt_da()->set_overwrite_status(false) here, but + reset_diagnostics_area() already does that. + */ + thd->get_stmt_da()->reset_diagnostics_area(); + wfc->wakeup_subsequent_commits(rgi->worker_error); +} + + +static void +signal_error_to_sql_driver_thread(THD *thd, rpl_group_info *rgi, int err) +{ + rgi->worker_error= err; + /* + In case we get an error during commit, inform following transactions that + we aborted our commit. + */ + rgi->unmark_start_commit(); + rgi->cleanup_context(thd, true); + rgi->rli->abort_slave= true; + rgi->rli->stop_for_until= false; + mysql_mutex_lock(rgi->rli->relay_log.get_log_lock()); + mysql_mutex_unlock(rgi->rli->relay_log.get_log_lock()); + rgi->rli->relay_log.signal_update(); +} + + +static void +unlock_or_exit_cond(THD *thd, mysql_mutex_t *lock, bool *did_enter_cond, + PSI_stage_info *old_stage) +{ + if (*did_enter_cond) + { + thd->EXIT_COND(old_stage); + *did_enter_cond= false; + } + else + mysql_mutex_unlock(lock); +} + + +static void +register_wait_for_prior_event_group_commit(rpl_group_info *rgi, + rpl_parallel_entry *entry) +{ + mysql_mutex_assert_owner(&entry->LOCK_parallel_entry); + if (rgi->wait_commit_sub_id > entry->last_committed_sub_id) + { + /* + Register that the commit of this event group must wait for the + commit of the previous event group to complete before it may + complete itself, so that we preserve commit order. + */ + wait_for_commit *waitee= + &rgi->wait_commit_group_info->commit_orderer; + rgi->commit_orderer.register_wait_for_prior_commit(waitee); + } +} + + +/* + Do not start parallel execution of this event group until all prior groups + have reached the commit phase that are not safe to run in parallel with. +*/ +static bool +do_gco_wait(rpl_group_info *rgi, group_commit_orderer *gco, + bool *did_enter_cond, PSI_stage_info *old_stage) +{ + THD *thd= rgi->thd; + rpl_parallel_entry *entry= rgi->parallel_entry; + uint64 wait_count; + + mysql_mutex_assert_owner(&entry->LOCK_parallel_entry); + + if (!gco->installed) + { + group_commit_orderer *prev_gco= gco->prev_gco; + if (prev_gco) + { + prev_gco->last_sub_id= gco->prior_sub_id; + prev_gco->next_gco= gco; + } + gco->installed= true; + } + wait_count= gco->wait_count; + if (wait_count > entry->count_committing_event_groups) + { + DEBUG_SYNC(thd, "rpl_parallel_start_waiting_for_prior"); + thd->ENTER_COND(&gco->COND_group_commit_orderer, + &entry->LOCK_parallel_entry, + &stage_waiting_for_prior_transaction_to_start_commit, + old_stage); + *did_enter_cond= true; + do + { + if (thd->check_killed() && !rgi->worker_error) + { + DEBUG_SYNC(thd, "rpl_parallel_start_waiting_for_prior_killed"); + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + thd->send_kill_message(); + slave_output_error_info(rgi, thd); + signal_error_to_sql_driver_thread(thd, rgi, 1); + /* + Even though we were killed, we need to continue waiting for the + prior event groups to signal that we can continue. Otherwise we + mess up the accounting for ordering. However, now that we have + marked the error, events will just be skipped rather than + executed, and things will progress quickly towards stop. + */ + } + mysql_cond_wait(&gco->COND_group_commit_orderer, + &entry->LOCK_parallel_entry); + } while (wait_count > entry->count_committing_event_groups); + } + + if (entry->force_abort && wait_count > entry->stop_count) + { + /* + We are stopping (STOP SLAVE), and this event group is beyond the point + where we can safely stop. So return a flag that will cause us to skip, + rather than execute, the following events. + */ + return true; + } + else + return false; +} + + +static void +do_ftwrl_wait(rpl_group_info *rgi, + bool *did_enter_cond, PSI_stage_info *old_stage) +{ + THD *thd= rgi->thd; + rpl_parallel_entry *entry= rgi->parallel_entry; + uint64 sub_id= rgi->gtid_sub_id; + DBUG_ENTER("do_ftwrl_wait"); + + mysql_mutex_assert_owner(&entry->LOCK_parallel_entry); + + /* + If a FLUSH TABLES WITH READ LOCK (FTWRL) is pending, check if this + transaction is later than transactions that have priority to complete + before FTWRL. If so, wait here so that FTWRL can proceed and complete + first. + + (entry->pause_sub_id is ULONGLONG_MAX if no FTWRL is pending, which makes + this test false as required). + */ + if (unlikely(sub_id > entry->pause_sub_id)) + { + thd->ENTER_COND(&entry->COND_parallel_entry, &entry->LOCK_parallel_entry, + &stage_waiting_for_ftwrl, old_stage); + *did_enter_cond= true; + do + { + if (entry->force_abort || rgi->worker_error) + break; + if (thd->check_killed()) + { + thd->send_kill_message(); + slave_output_error_info(rgi, thd); + signal_error_to_sql_driver_thread(thd, rgi, 1); + break; + } + mysql_cond_wait(&entry->COND_parallel_entry, &entry->LOCK_parallel_entry); + } while (sub_id > entry->pause_sub_id); + + /* + We do not call EXIT_COND() here, as this will be done later by our + caller (since we set *did_enter_cond to true). + */ + } + + if (sub_id > entry->largest_started_sub_id) + entry->largest_started_sub_id= sub_id; + + DBUG_VOID_RETURN; +} + + +static int +pool_mark_busy(rpl_parallel_thread_pool *pool, THD *thd) +{ + PSI_stage_info old_stage; + int res= 0; + + /* + Wait here while the queue is busy. This is done to make FLUSH TABLES WITH + READ LOCK work correctly, without incuring extra locking penalties in + normal operation. FLUSH TABLES WITH READ LOCK needs to lock threads in the + thread pool, and for this we need to make sure the pool will not go away + during the operation. The LOCK_rpl_thread_pool is not suitable for + this. It is taken by release_thread() while holding LOCK_rpl_thread; so it + must be released before locking any LOCK_rpl_thread lock, or a deadlock + can occur. + + So we protect the infrequent operations of FLUSH TABLES WITH READ LOCK and + pool size changes with this condition wait. + */ + mysql_mutex_lock(&pool->LOCK_rpl_thread_pool); + if (thd) + thd->ENTER_COND(&pool->COND_rpl_thread_pool, &pool->LOCK_rpl_thread_pool, + &stage_waiting_for_rpl_thread_pool, &old_stage); + while (pool->busy) + { + if (thd && thd->check_killed()) + { + thd->send_kill_message(); + res= 1; + break; + } + mysql_cond_wait(&pool->COND_rpl_thread_pool, &pool->LOCK_rpl_thread_pool); + } + if (!res) + pool->busy= true; + if (thd) + thd->EXIT_COND(&old_stage); + else + mysql_mutex_unlock(&pool->LOCK_rpl_thread_pool); + + return res; +} + + +static void +pool_mark_not_busy(rpl_parallel_thread_pool *pool) +{ + mysql_mutex_lock(&pool->LOCK_rpl_thread_pool); + DBUG_ASSERT(pool->busy); + pool->busy= false; + mysql_cond_broadcast(&pool->COND_rpl_thread_pool); + mysql_mutex_unlock(&pool->LOCK_rpl_thread_pool); +} + + +void +rpl_unpause_after_ftwrl(THD *thd) +{ + uint32 i; + rpl_parallel_thread_pool *pool= &global_rpl_thread_pool; + DBUG_ENTER("rpl_unpause_after_ftwrl"); + + DBUG_ASSERT(pool->busy); + + for (i= 0; i < pool->count; ++i) + { + rpl_parallel_entry *e; + rpl_parallel_thread *rpt= pool->threads[i]; + + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + if (!rpt->current_owner) + { + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + continue; + } + e= rpt->current_entry; + mysql_mutex_lock(&e->LOCK_parallel_entry); + rpt->pause_for_ftwrl = false; + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + e->pause_sub_id= (uint64)ULONGLONG_MAX; + mysql_cond_broadcast(&e->COND_parallel_entry); + mysql_mutex_unlock(&e->LOCK_parallel_entry); + } + + pool_mark_not_busy(pool); + DBUG_VOID_RETURN; +} + + +/* + . + + Note: in case of error return, rpl_unpause_after_ftwrl() must _not_ be called. +*/ +int +rpl_pause_for_ftwrl(THD *thd) +{ + uint32 i; + rpl_parallel_thread_pool *pool= &global_rpl_thread_pool; + int err; + DBUG_ENTER("rpl_pause_for_ftwrl"); + + /* + While the count_pending_pause_for_ftwrl counter is non-zero, the pool + cannot be shutdown/resized, so threads are guaranteed to not disappear. + + This is required to safely be able to access the individual threads below. + (We cannot lock an individual thread while holding LOCK_rpl_thread_pool, + as this can deadlock against release_thread()). + */ + if ((err= pool_mark_busy(pool, thd))) + DBUG_RETURN(err); + + for (i= 0; i < pool->count; ++i) + { + PSI_stage_info old_stage; + rpl_parallel_entry *e; + rpl_parallel_thread *rpt= pool->threads[i]; + + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + if (!rpt->current_owner) + { + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + continue; + } + e= rpt->current_entry; + mysql_mutex_lock(&e->LOCK_parallel_entry); + /* + Setting the rpt->pause_for_ftwrl flag makes sure that the thread will not + de-allocate itself until signalled to do so by rpl_unpause_after_ftwrl(). + */ + rpt->pause_for_ftwrl = true; + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + ++e->need_sub_id_signal; + if (e->pause_sub_id == (uint64)ULONGLONG_MAX) + e->pause_sub_id= e->largest_started_sub_id; + thd->ENTER_COND(&e->COND_parallel_entry, &e->LOCK_parallel_entry, + &stage_waiting_for_ftwrl_threads_to_pause, &old_stage); + while (e->pause_sub_id < (uint64)ULONGLONG_MAX && + e->last_committed_sub_id < e->pause_sub_id && + !err) + { + if (thd->check_killed()) + { + thd->send_kill_message(); + err= 1; + break; + } + mysql_cond_wait(&e->COND_parallel_entry, &e->LOCK_parallel_entry); + }; + --e->need_sub_id_signal; + thd->EXIT_COND(&old_stage); + if (err) + break; + } + + if (err) + rpl_unpause_after_ftwrl(thd); + DBUG_RETURN(err); +} + + +#ifndef DBUG_OFF +static int +dbug_simulate_tmp_error(rpl_group_info *rgi, THD *thd) +{ + if (rgi->current_gtid.domain_id == 0 && rgi->current_gtid.seq_no == 100 && + rgi->retry_event_count == 4) + { + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return 1; + } + return 0; +} +#endif + + +/* + If we detect a deadlock due to eg. storage engine locks that conflict with + the fixed commit order, then the later transaction will be killed + asynchroneously to allow the former to complete its commit. + + In this case, we convert the 'killed' error into a deadlock error, and retry + the later transaction. + + If we are doing optimistic parallel apply of transactions not known to be + safe, we convert any error to a deadlock error, but then at retry we will + wait for prior transactions to commit first, so that the retries can be + done non-speculative. +*/ +static void +convert_kill_to_deadlock_error(rpl_group_info *rgi) +{ + THD *thd= rgi->thd; + int err_code; + + if (!thd->get_stmt_da()->is_error()) + return; + err_code= thd->get_stmt_da()->sql_errno(); + if ((rgi->speculation == rpl_group_info::SPECULATE_OPTIMISTIC && + err_code != ER_PRIOR_COMMIT_FAILED) || + ((err_code == ER_QUERY_INTERRUPTED || err_code == ER_CONNECTION_KILLED) && + rgi->killed_for_retry)) + { + thd->clear_error(); + my_error(ER_LOCK_DEADLOCK, MYF(0)); + thd->reset_killed(); + } +} + + +/* + Check if an event marks the end of an event group. Returns non-zero if so, + zero otherwise. + + In addition, returns 1 if the group is committing, 2 if it is rolling back. +*/ +static int +is_group_ending(Log_event *ev, Log_event_type event_type) +{ + if (event_type == XID_EVENT) + return 1; + if (event_type == QUERY_EVENT) + { + Query_log_event *qev = (Query_log_event *)ev; + if (qev->is_commit()) + return 1; + if (qev->is_rollback()) + return 2; + } + return 0; +} + + +static int +retry_event_group(rpl_group_info *rgi, rpl_parallel_thread *rpt, + rpl_parallel_thread::queued_event *orig_qev) +{ + IO_CACHE rlog; + LOG_INFO linfo; + File fd= (File)-1; + const char *errmsg; + inuse_relaylog *ir= rgi->relay_log; + uint64 event_count; + uint64 events_to_execute= rgi->retry_event_count; + Relay_log_info *rli= rgi->rli; + int err; + ulonglong cur_offset, old_offset; + char log_name[FN_REFLEN]; + THD *thd= rgi->thd; + rpl_parallel_entry *entry= rgi->parallel_entry; + ulong retries= 0; + Format_description_log_event *description_event= NULL; + +do_retry: + event_count= 0; + err= 0; + errmsg= NULL; + + /* + If we already started committing before getting the deadlock (or other + error) that caused us to need to retry, we have already signalled + subsequent transactions that we have started committing. This is + potentially a problem, as now we will rollback, and if subsequent + transactions would start to execute now, they could see an unexpected + state of the database and get eg. key not found or duplicate key error. + + However, to get a deadlock in the first place, there must have been + another earlier transaction that is waiting for us. Thus that other + transaction has _not_ yet started to commit, and any subsequent + transactions will still be waiting at this point. + + So here, we decrement back the count of transactions that started + committing (if we already incremented it), undoing the effect of an + earlier mark_start_commit(). Then later, when the retry succeeds and we + commit again, we can do a new mark_start_commit() and eventually wake up + subsequent transactions at the proper time. + + We need to do the unmark before the rollback, to be sure that the + transaction we deadlocked with will not signal that it started to commit + until after the unmark. + */ + DBUG_EXECUTE_IF("inject_mdev8302", { my_sleep(20000);}); + rgi->unmark_start_commit(); + DEBUG_SYNC(thd, "rpl_parallel_retry_after_unmark"); + + /* + We might get the deadlock error that causes the retry during commit, while + sitting in wait_for_prior_commit(). If this happens, we will have a + pending error in the wait_for_commit object. So clear this by + unregistering (and later re-registering) the wait. + */ + if(thd->wait_for_commit_ptr) + thd->wait_for_commit_ptr->unregister_wait_for_prior_commit(); + DBUG_EXECUTE_IF("inject_mdev8031", { + /* Simulate that we get deadlock killed at this exact point. */ + rgi->killed_for_retry= rpl_group_info::RETRY_KILL_KILLED; + thd->set_killed(KILL_CONNECTION); + }); + rgi->cleanup_context(thd, 1); + wait_for_pending_deadlock_kill(thd, rgi); + thd->reset_killed(); + thd->clear_error(); + rgi->killed_for_retry = rpl_group_info::RETRY_KILL_NONE; + + /* + If we retry due to a deadlock kill that occurred during the commit step, we + might have already updated (but not committed) an update of table + mysql.gtid_slave_pos, and cleared the gtid_pending flag. Now we have + rolled back any such update, so we must set the gtid_pending flag back to + true so that we will do a new update when/if we succeed with the retry. + */ + rgi->gtid_pending= true; + + mysql_mutex_lock(&rli->data_lock); + ++rli->retried_trans; + statistic_increment(slave_retried_transactions, LOCK_status); + mysql_mutex_unlock(&rli->data_lock); + + for (;;) + { + mysql_mutex_lock(&entry->LOCK_parallel_entry); + register_wait_for_prior_event_group_commit(rgi, entry); + mysql_mutex_unlock(&entry->LOCK_parallel_entry); + + /* + Let us wait for all prior transactions to complete before trying again. + This way, we avoid repeatedly conflicting with and getting deadlock + killed by the same earlier transaction. + */ + if (!(err= thd->wait_for_prior_commit())) + { + rgi->speculation = rpl_group_info::SPECULATE_WAIT; + break; + } + + convert_kill_to_deadlock_error(rgi); + if (!has_temporary_error(thd)) + goto err; + /* + If we get a temporary error such as a deadlock kill, we can safely + ignore it, as we already rolled back. + + But we still want to retry the wait for the prior transaction to + complete its commit. + */ + thd->clear_error(); + thd->reset_killed(); + if(thd->wait_for_commit_ptr) + thd->wait_for_commit_ptr->unregister_wait_for_prior_commit(); + DBUG_EXECUTE_IF("inject_mdev8031", { + /* Inject a small sleep to give prior transaction a chance to commit. */ + my_sleep(100000); + }); + } + + /* + Let us clear any lingering deadlock kill one more time, here after + wait_for_prior_commit() has completed. This should rule out any + possibility of an old deadlock kill lingering on beyond this point. + */ + thd->reset_killed(); + + strmake_buf(log_name, ir->name); + if ((fd= open_binlog(&rlog, log_name, &errmsg)) <0) + { + err= 1; + goto err; + } + cur_offset= rgi->retry_start_offset; + delete description_event; + description_event= + read_relay_log_description_event(&rlog, cur_offset, &errmsg); + if (!description_event) + { + err= 1; + goto err; + } + DBUG_EXECUTE_IF("inject_mdev8031", { + /* Simulate pending KILL caught in read_relay_log_description_event(). */ + if (thd->check_killed()) { + thd->send_kill_message(); + err= 1; + goto err; + } + }); + my_b_seek(&rlog, cur_offset); + + do + { + Log_event_type event_type; + Log_event *ev; + rpl_parallel_thread::queued_event *qev; + + /* The loop is here so we can try again the next relay log file on EOF. */ + for (;;) + { + old_offset= cur_offset; + ev= Log_event::read_log_event(&rlog, 0, description_event, + opt_slave_sql_verify_checksum); + cur_offset= my_b_tell(&rlog); + + if (ev) + break; + if (rlog.error < 0) + { + errmsg= "slave SQL thread aborted because of I/O error"; + err= 1; + goto check_retry; + } + if (rlog.error > 0) + { + sql_print_error("Slave SQL thread: I/O error reading " + "event(errno: %d cur_log->error: %d)", + my_errno, rlog.error); + errmsg= "Aborting slave SQL thread because of partial event read"; + err= 1; + goto err; + } + /* EOF. Move to the next relay log. */ + end_io_cache(&rlog); + mysql_file_close(fd, MYF(MY_WME)); + fd= (File)-1; + + /* Find the next relay log file. */ + if((err= rli->relay_log.find_log_pos(&linfo, log_name, 1)) || + (err= rli->relay_log.find_next_log(&linfo, 1))) + { + char buff[22]; + sql_print_error("next log error: %d offset: %s log: %s", + err, + llstr(linfo.index_file_offset, buff), + log_name); + goto err; + } + strmake_buf(log_name ,linfo.log_file_name); + + DBUG_EXECUTE_IF("inject_retry_event_group_open_binlog_kill", { + if (retries < 2) + { + /* Simulate that we get deadlock killed during open_binlog(). */ + thd->reset_for_next_command(); + rgi->killed_for_retry= rpl_group_info::RETRY_KILL_KILLED; + thd->set_killed(KILL_CONNECTION); + thd->send_kill_message(); + fd= (File)-1; + err= 1; + goto check_retry; + } + }); + if ((fd= open_binlog(&rlog, log_name, &errmsg)) <0) + { + err= 1; + goto check_retry; + } + description_event->reset_crypto(); + /* Loop to try again on the new log file. */ + } + + event_type= ev->get_type_code(); + if (event_type == FORMAT_DESCRIPTION_EVENT) + { + Format_description_log_event *newde= (Format_description_log_event*)ev; + newde->copy_crypto_data(description_event); + delete description_event; + description_event= newde; + continue; + } + else if (event_type == START_ENCRYPTION_EVENT) + { + description_event->start_decryption((Start_encryption_log_event*)ev); + delete ev; + continue; + } + else if (!Log_event::is_group_event(event_type)) + { + delete ev; + continue; + } + ev->thd= thd; + + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + qev= rpt->retry_get_qev(ev, orig_qev, log_name, old_offset, + cur_offset - old_offset); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + if (!qev) + { + delete ev; + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + err= 1; + goto err; + } + if (is_group_ending(ev, event_type) == 1) + rgi->mark_start_commit(); + + err= rpt_handle_event(qev, rpt); + ++event_count; + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + rpt->free_qev(qev); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + + delete_or_keep_event_post_apply(rgi, event_type, ev); + DBUG_EXECUTE_IF("rpl_parallel_simulate_double_temp_err_gtid_0_x_100", + if (retries == 0) err= dbug_simulate_tmp_error(rgi, thd);); + DBUG_EXECUTE_IF("rpl_parallel_simulate_infinite_temp_err_gtid_0_x_100", + err= dbug_simulate_tmp_error(rgi, thd);); + if (!err) + continue; + +check_retry: + convert_kill_to_deadlock_error(rgi); + if (has_temporary_error(thd)) + { + ++retries; + if (retries < slave_trans_retries) + { + if (fd >= 0) + { + end_io_cache(&rlog); + mysql_file_close(fd, MYF(MY_WME)); + fd= (File)-1; + } + goto do_retry; + } + sql_print_error("Slave worker thread retried transaction %lu time(s) " + "in vain, giving up. Consider raising the value of " + "the slave_transaction_retries variable.", + slave_trans_retries); + } + goto err; + + } while (event_count < events_to_execute); + +err: + + if (description_event) + delete description_event; + if (fd >= 0) + { + end_io_cache(&rlog); + mysql_file_close(fd, MYF(MY_WME)); + } + if (errmsg) + sql_print_error("Error reading relay log event: %s", errmsg); + return err; +} + + +pthread_handler_t +handle_rpl_parallel_thread(void *arg) +{ + THD *thd; + PSI_stage_info old_stage; + struct rpl_parallel_thread::queued_event *events; + bool group_standalone= true; + bool in_event_group= false; + bool skip_event_group= false; + rpl_group_info *group_rgi= NULL; + group_commit_orderer *gco; + uint64 event_gtid_sub_id= 0; + rpl_sql_thread_info sql_info(NULL); + int err; + + struct rpl_parallel_thread *rpt= (struct rpl_parallel_thread *)arg; + + my_thread_init(); + thd = new THD; + thd->thread_stack = (char*)&thd; + mysql_mutex_lock(&LOCK_thread_count); + thd->thread_id= thd->variables.pseudo_thread_id= thread_id++; + threads.append(thd); + mysql_mutex_unlock(&LOCK_thread_count); + set_current_thd(thd); + pthread_detach_this_thread(); + thd->init_for_queries(); + thd->variables.binlog_annotate_row_events= 0; + init_thr_lock(); + thd->store_globals(); + thd->system_thread= SYSTEM_THREAD_SLAVE_SQL; + thd->security_ctx->skip_grants(); + thd->variables.max_allowed_packet= slave_max_allowed_packet; + thd->slave_thread= 1; + thd->variables.sql_log_slow= opt_log_slow_slave_statements; + thd->variables.log_slow_filter= global_system_variables.log_slow_filter; + set_slave_thread_options(thd); + thd->client_capabilities = CLIENT_LOCAL_FILES; + thd->net.reading_or_writing= 0; + thd_proc_info(thd, "Waiting for work from main SQL threads"); + thd->set_time(); + thd->variables.lock_wait_timeout= LONG_TIMEOUT; + thd->system_thread_info.rpl_sql_info= &sql_info; + /* + We need to use (at least) REPEATABLE READ isolation level. Otherwise + speculative parallel apply can run out-of-order and give wrong results + for statement-based replication. + */ + thd->variables.tx_isolation= ISO_REPEATABLE_READ; + + + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + rpt->thd= thd; + + while (rpt->delay_start) + mysql_cond_wait(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread); + + rpt->running= true; + mysql_cond_signal(&rpt->COND_rpl_thread); + + while (!rpt->stop) + { + rpl_parallel_thread::queued_event *qev, *next_qev; + + thd->ENTER_COND(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread, + &stage_waiting_for_work_from_sql_thread, &old_stage); + /* + There are 4 cases that should cause us to wake up: + - Events have been queued for us to handle. + - We have an owner, but no events and not inside event group -> we need + to release ourself to the thread pool + - SQL thread is stopping, and we have an owner but no events, and we are + inside an event group; no more events will be queued to us, so we need + to abort the group (force_abort==1). + - Thread pool shutdown (rpt->stop==1). + */ + while (!( (events= rpt->event_queue) || + (rpt->current_owner && !in_event_group) || + (rpt->current_owner && group_rgi->parallel_entry->force_abort) || + rpt->stop)) + mysql_cond_wait(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread); + rpt->dequeue1(events); + thd->EXIT_COND(&old_stage); + + more_events: + for (qev= events; qev; qev= next_qev) + { + Log_event_type event_type; + rpl_group_info *rgi= qev->rgi; + rpl_parallel_entry *entry= rgi->parallel_entry; + bool end_of_group; + int group_ending; + + next_qev= qev->next; + if (qev->typ == rpl_parallel_thread::queued_event::QUEUED_POS_UPDATE) + { + handle_queued_pos_update(thd, qev); + rpt->loc_free_qev(qev); + continue; + } + else if (qev->typ == + rpl_parallel_thread::queued_event::QUEUED_MASTER_RESTART) + { + if (in_event_group) + { + /* + Master restarted (crashed) in the middle of an event group. + So we need to roll back and discard that event group. + */ + group_rgi->cleanup_context(thd, 1); + in_event_group= false; + finish_event_group(rpt, group_rgi->gtid_sub_id, + qev->entry_for_queued, group_rgi); + + rpt->loc_free_rgi(group_rgi); + thd->rgi_slave= group_rgi= NULL; + } + + rpt->loc_free_qev(qev); + continue; + } + DBUG_ASSERT(qev->typ==rpl_parallel_thread::queued_event::QUEUED_EVENT); + + thd->rgi_slave= rgi; + gco= rgi->gco; + /* Handle a new event group, which will be initiated by a GTID event. */ + if ((event_type= qev->ev->get_type_code()) == GTID_EVENT) + { + bool did_enter_cond= false; + PSI_stage_info old_stage; + + DBUG_EXECUTE_IF("rpl_parallel_scheduled_gtid_0_x_100", { + if (rgi->current_gtid.domain_id == 0 && + rgi->current_gtid.seq_no == 100) { + debug_sync_set_action(thd, + STRING_WITH_LEN("now SIGNAL scheduled_gtid_0_x_100")); + } + }); + + if(unlikely(thd->wait_for_commit_ptr) && group_rgi != NULL) + { + /* + This indicates that we get a new GTID event in the middle of + a not completed event group. This is corrupt binlog (the master + will never write such binlog), so it does not happen unless + someone tries to inject wrong crafted binlog, but let us still + try to handle it somewhat nicely. + */ + group_rgi->cleanup_context(thd, true); + finish_event_group(rpt, group_rgi->gtid_sub_id, + group_rgi->parallel_entry, group_rgi); + rpt->loc_free_rgi(group_rgi); + } + + thd->tx_isolation= (enum_tx_isolation)thd->variables.tx_isolation; + in_event_group= true; + /* + If the standalone flag is set, then this event group consists of a + single statement (possibly preceeded by some Intvar_log_event and + similar), without any terminating COMMIT/ROLLBACK/XID. + */ + group_standalone= + (0 != (static_cast<Gtid_log_event *>(qev->ev)->flags2 & + Gtid_log_event::FL_STANDALONE)); + + event_gtid_sub_id= rgi->gtid_sub_id; + rgi->thd= thd; + + mysql_mutex_lock(&entry->LOCK_parallel_entry); + skip_event_group= do_gco_wait(rgi, gco, &did_enter_cond, &old_stage); + + if (unlikely(entry->stop_on_error_sub_id <= rgi->wait_commit_sub_id)) + skip_event_group= true; + if (likely(!skip_event_group)) + do_ftwrl_wait(rgi, &did_enter_cond, &old_stage); + + /* + Register ourself to wait for the previous commit, if we need to do + such registration _and_ that previous commit has not already + occurred. + */ + register_wait_for_prior_event_group_commit(rgi, entry); + + unlock_or_exit_cond(thd, &entry->LOCK_parallel_entry, + &did_enter_cond, &old_stage); + + thd->wait_for_commit_ptr= &rgi->commit_orderer; + + if (opt_gtid_ignore_duplicates && + rgi->rli->mi->using_gtid != Master_info::USE_GTID_NO) + { + int res= + rpl_global_gtid_slave_state->check_duplicate_gtid(&rgi->current_gtid, + rgi); + if (res < 0) + { + /* Error. */ + slave_output_error_info(rgi, thd); + signal_error_to_sql_driver_thread(thd, rgi, 1); + } + else if (!res) + { + /* GTID already applied by another master connection, skip. */ + skip_event_group= true; + } + else + { + /* We have to apply the event. */ + } + } + /* + If we are optimistically running transactions in parallel, but this + particular event group should not run in parallel with what came + before, then wait now for the prior transaction to complete its + commit. + */ + if (rgi->speculation == rpl_group_info::SPECULATE_WAIT && + (err= thd->wait_for_prior_commit())) + { + slave_output_error_info(rgi, thd); + signal_error_to_sql_driver_thread(thd, rgi, 1); + } + } + + group_rgi= rgi; + group_ending= is_group_ending(qev->ev, event_type); + /* + We do not unmark_start_commit() here in case of an explicit ROLLBACK + statement. Such events should be very rare, there is no real reason + to try to group commit them - on the contrary, it seems best to avoid + running them in parallel with following group commits, as with + ROLLBACK events we are already deep in dangerous corner cases with + mix of transactional and non-transactional tables or the like. And + avoiding the mark_start_commit() here allows us to keep an assertion + in ha_rollback_trans() that we do not rollback after doing + mark_start_commit(). + */ + if (group_ending == 1 && likely(!rgi->worker_error)) + { + /* + Do an extra check for (deadlock) kill here. This helps prevent a + lingering deadlock kill that occurred during normal DML processing to + propagate past the mark_start_commit(). If we detect a deadlock only + after mark_start_commit(), we have to unmark, which has at least a + theoretical possibility of leaving a window where it looks like all + transactions in a GCO have started committing, while in fact one + will need to rollback and retry. This is not supposed to be possible + (since there is a deadlock, at least one transaction should be + blocked from reaching commit), but this seems a fragile ensurance, + and there were historically a number of subtle bugs in this area. + */ + if (!thd->killed) + { + DEBUG_SYNC(thd, "rpl_parallel_before_mark_start_commit"); + rgi->mark_start_commit(); + DEBUG_SYNC(thd, "rpl_parallel_after_mark_start_commit"); + } + } + + /* + If the SQL thread is stopping, we just skip execution of all the + following event groups. We still do all the normal waiting and wakeup + processing between the event groups as a simple way to ensure that + everything is stopped and cleaned up correctly. + */ + if (likely(!rgi->worker_error) && !skip_event_group) + { + ++rgi->retry_event_count; +#ifndef DBUG_OFF + err= 0; + DBUG_EXECUTE_IF("rpl_parallel_simulate_temp_err_xid", + if (event_type == XID_EVENT) + { + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + my_error(ER_LOCK_DEADLOCK, MYF(0)); + err= 1; + DEBUG_SYNC(thd, "rpl_parallel_simulate_temp_err_xid"); + }); + if (!err) +#endif + { + if (thd->check_killed()) + { + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + thd->send_kill_message(); + err= 1; + } + else + err= rpt_handle_event(qev, rpt); + } + delete_or_keep_event_post_apply(rgi, event_type, qev->ev); + DBUG_EXECUTE_IF("rpl_parallel_simulate_temp_err_gtid_0_x_100", + err= dbug_simulate_tmp_error(rgi, thd);); + if (err) + { + convert_kill_to_deadlock_error(rgi); + if (has_temporary_error(thd) && slave_trans_retries > 0) + err= retry_event_group(rgi, rpt, qev); + } + } + else + { + delete qev->ev; + thd->get_stmt_da()->set_overwrite_status(true); + err= thd->wait_for_prior_commit(); + thd->get_stmt_da()->set_overwrite_status(false); + } + + end_of_group= + in_event_group && + ((group_standalone && !Log_event::is_part_of_group(event_type)) || + group_ending); + + rpt->loc_free_qev(qev); + + if (unlikely(err)) + { + if (!rgi->worker_error) + { + slave_output_error_info(rgi, thd); + signal_error_to_sql_driver_thread(thd, rgi, err); + } + thd->reset_killed(); + } + if (end_of_group) + { + in_event_group= false; + finish_event_group(rpt, event_gtid_sub_id, entry, rgi); + rpt->loc_free_rgi(rgi); + thd->rgi_slave= group_rgi= rgi= NULL; + skip_event_group= false; + DEBUG_SYNC(thd, "rpl_parallel_end_of_group"); + } + } + + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + /* + Now that we have the lock, we can move everything from our local free + lists to the real free lists that are also accessible from the SQL + driver thread. + */ + rpt->batch_free(); + + if ((events= rpt->event_queue) != NULL) + { + /* + Take next group of events from the replication pool. + This is faster than having to wakeup the pool manager thread to give + us a new event. + */ + rpt->dequeue1(events); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + goto more_events; + } + + rpt->inuse_relaylog_refcount_update(); + + if (in_event_group && group_rgi->parallel_entry->force_abort) + { + /* + We are asked to abort, without getting the remaining events in the + current event group. + + We have to rollback the current transaction and update the last + sub_id value so that SQL thread will know we are done with the + half-processed event group. + */ + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + signal_error_to_sql_driver_thread(thd, group_rgi, 1); + finish_event_group(rpt, group_rgi->gtid_sub_id, + group_rgi->parallel_entry, group_rgi); + in_event_group= false; + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + rpt->free_rgi(group_rgi); + thd->rgi_slave= group_rgi= NULL; + skip_event_group= false; + } + if (!in_event_group) + { + /* If we are in a FLUSH TABLES FOR READ LOCK, wait for it */ + while (rpt->current_entry && rpt->pause_for_ftwrl) + { + /* + We are currently in the delicate process of pausing parallel + replication while FLUSH TABLES WITH READ LOCK is starting. We must + not de-allocate the thread (setting rpt->current_owner= NULL) until + rpl_unpause_after_ftwrl() has woken us up. + */ + rpl_parallel_entry *e= rpt->current_entry; + /* + Wait for rpl_unpause_after_ftwrl() to wake us up. + Note that rpl_pause_for_ftwrl() may wait for 'e->pause_sub_id' + to change. This should happen eventually in finish_event_group() + */ + mysql_mutex_lock(&e->LOCK_parallel_entry); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + if (rpt->pause_for_ftwrl) + mysql_cond_wait(&e->COND_parallel_entry, &e->LOCK_parallel_entry); + mysql_mutex_unlock(&e->LOCK_parallel_entry); + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + } + + rpt->current_owner= NULL; + /* Tell wait_for_done() that we are done, if it is waiting. */ + if (likely(rpt->current_entry) && + unlikely(rpt->current_entry->force_abort)) + mysql_cond_broadcast(&rpt->COND_rpl_thread_stop); + + rpt->current_entry= NULL; + if (!rpt->stop) + rpt->pool->release_thread(rpt); + } + } + + rpt->thd= NULL; + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + + thd->clear_error(); + thd->catalog= 0; + thd->reset_query(); + thd->reset_db(NULL, 0); + thd_proc_info(thd, "Slave worker thread exiting"); + thd->temporary_tables= 0; + + mysql_mutex_lock(&LOCK_thread_count); + thd->unlink(); + mysql_mutex_unlock(&LOCK_thread_count); + delete thd; + + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + rpt->running= false; + mysql_cond_signal(&rpt->COND_rpl_thread); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + + my_thread_end(); + + return NULL; +} + + +static void +dealloc_gco(group_commit_orderer *gco) +{ + mysql_cond_destroy(&gco->COND_group_commit_orderer); + my_free(gco); +} + +/** + Change thread count for global parallel worker threads + + @param pool parallel thread pool + @param new_count Number of threads to be in pool. 0 in shutdown + @param force Force thread count to new_count even if slave + threads are running + + By default we don't resize pool of there are running threads. + However during shutdown we will always do it. + This is needed as any_slave_sql_running() returns 1 during shutdown + as we don't want to access master_info while + Master_info_index::free_connections are running. +*/ + +static int +rpl_parallel_change_thread_count(rpl_parallel_thread_pool *pool, + uint32 new_count, bool force) +{ + uint32 i; + rpl_parallel_thread **old_list= NULL; + rpl_parallel_thread **new_list= NULL; + rpl_parallel_thread *new_free_list= NULL; + rpl_parallel_thread *rpt_array= NULL; + int res; + + if ((res= pool_mark_busy(pool, current_thd))) + return res; + + /* Protect against parallel pool resizes */ + if (pool->count == new_count) + { + pool_mark_not_busy(pool); + return 0; + } + + /* + If we are about to delete pool, do an extra check that there are no new + slave threads running since we marked pool busy + */ + if (!new_count && !force) + { + if (any_slave_sql_running()) + { + DBUG_PRINT("warning", + ("SQL threads running while trying to reset parallel pool")); + pool_mark_not_busy(pool); + return 0; // Ok to not resize pool + } + } + + /* + Allocate the new list of threads up-front. + That way, if we fail half-way, we only need to free whatever we managed + to allocate, and will not be left with a half-functional thread pool. + */ + if (new_count && + !my_multi_malloc(MYF(MY_WME|MY_ZEROFILL), + &new_list, new_count*sizeof(*new_list), + &rpt_array, new_count*sizeof(*rpt_array), + NULL)) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int(new_count*sizeof(*new_list) + + new_count*sizeof(*rpt_array)))); + goto err; + } + + for (i= 0; i < new_count; ++i) + { + pthread_t th; + + new_list[i]= &rpt_array[i]; + new_list[i]->delay_start= true; + mysql_mutex_init(key_LOCK_rpl_thread, &new_list[i]->LOCK_rpl_thread, + MY_MUTEX_INIT_SLOW); + mysql_cond_init(key_COND_rpl_thread, &new_list[i]->COND_rpl_thread, NULL); + mysql_cond_init(key_COND_rpl_thread_queue, + &new_list[i]->COND_rpl_thread_queue, NULL); + mysql_cond_init(key_COND_rpl_thread_stop, + &new_list[i]->COND_rpl_thread_stop, NULL); + new_list[i]->pool= pool; + if (mysql_thread_create(key_rpl_parallel_thread, &th, &connection_attrib, + handle_rpl_parallel_thread, new_list[i])) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto err; + } + new_list[i]->next= new_free_list; + new_free_list= new_list[i]; + } + + /* + Grab each old thread in turn, and signal it to stop. + + Note that since we require all replication threads to be stopped before + changing the parallel replication worker thread pool, all the threads will + be already idle and will terminate immediately. + */ + for (i= 0; i < pool->count; ++i) + { + rpl_parallel_thread *rpt; + + mysql_mutex_lock(&pool->LOCK_rpl_thread_pool); + while ((rpt= pool->free_list) == NULL) + mysql_cond_wait(&pool->COND_rpl_thread_pool, &pool->LOCK_rpl_thread_pool); + pool->free_list= rpt->next; + mysql_mutex_unlock(&pool->LOCK_rpl_thread_pool); + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + rpt->stop= true; + mysql_cond_signal(&rpt->COND_rpl_thread); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + } + + for (i= 0; i < pool->count; ++i) + { + rpl_parallel_thread *rpt= pool->threads[i]; + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + while (rpt->running) + mysql_cond_wait(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + mysql_mutex_destroy(&rpt->LOCK_rpl_thread); + mysql_cond_destroy(&rpt->COND_rpl_thread); + while (rpt->qev_free_list) + { + rpl_parallel_thread::queued_event *next= rpt->qev_free_list->next; + my_free(rpt->qev_free_list); + rpt->qev_free_list= next; + } + while (rpt->rgi_free_list) + { + rpl_group_info *next= rpt->rgi_free_list->next; + delete rpt->rgi_free_list; + rpt->rgi_free_list= next; + } + while (rpt->gco_free_list) + { + group_commit_orderer *next= rpt->gco_free_list->next_gco; + dealloc_gco(rpt->gco_free_list); + rpt->gco_free_list= next; + } + } + + old_list= pool->threads; + if (new_count < pool->count) + pool->count= new_count; + pool->threads= new_list; + if (new_count > pool->count) + pool->count= new_count; + my_free(old_list); + pool->free_list= new_free_list; + for (i= 0; i < pool->count; ++i) + { + mysql_mutex_lock(&pool->threads[i]->LOCK_rpl_thread); + pool->threads[i]->delay_start= false; + mysql_cond_signal(&pool->threads[i]->COND_rpl_thread); + while (!pool->threads[i]->running) + mysql_cond_wait(&pool->threads[i]->COND_rpl_thread, + &pool->threads[i]->LOCK_rpl_thread); + mysql_mutex_unlock(&pool->threads[i]->LOCK_rpl_thread); + } + + pool_mark_not_busy(pool); + + return 0; + +err: + if (new_list) + { + while (new_free_list) + { + mysql_mutex_lock(&new_free_list->LOCK_rpl_thread); + new_free_list->delay_start= false; + new_free_list->stop= true; + mysql_cond_signal(&new_free_list->COND_rpl_thread); + while (!new_free_list->running) + mysql_cond_wait(&new_free_list->COND_rpl_thread, + &new_free_list->LOCK_rpl_thread); + while (new_free_list->running) + mysql_cond_wait(&new_free_list->COND_rpl_thread, + &new_free_list->LOCK_rpl_thread); + mysql_mutex_unlock(&new_free_list->LOCK_rpl_thread); + new_free_list= new_free_list->next; + } + my_free(new_list); + } + pool_mark_not_busy(pool); + return 1; +} + +/* + Deactivate the parallel replication thread pool, if there are now no more + SQL threads running. +*/ + +int rpl_parallel_resize_pool_if_no_slaves(void) +{ + /* master_info_index is set to NULL on shutdown */ + if (opt_slave_parallel_threads > 0 && !any_slave_sql_running()) + return rpl_parallel_inactivate_pool(&global_rpl_thread_pool); + return 0; +} + + +/** + Pool activation is preceeded by taking a "lock" of pool_mark_busy + which guarantees the number of running slaves drops to zero atomicly + with the number of pool workers. + This resolves race between the function caller thread and one + that may be attempting to deactivate the pool. +*/ +int +rpl_parallel_activate_pool(rpl_parallel_thread_pool *pool) +{ + int rc= 0; + + if ((rc= pool_mark_busy(pool, current_thd))) + return rc; // killed + + if (!pool->count) + { + pool_mark_not_busy(pool); + rc= rpl_parallel_change_thread_count(pool, opt_slave_parallel_threads, + 0); + } + else + { + pool_mark_not_busy(pool); + } + return rc; +} + + +int +rpl_parallel_inactivate_pool(rpl_parallel_thread_pool *pool) +{ + return rpl_parallel_change_thread_count(pool, 0, 0); +} + + +void +rpl_parallel_thread::batch_free() +{ + mysql_mutex_assert_owner(&LOCK_rpl_thread); + if (loc_qev_list) + { + *loc_qev_last_ptr_ptr= qev_free_list; + qev_free_list= loc_qev_list; + loc_qev_list= NULL; + dequeue2(loc_qev_size); + /* Signal that our queue can now accept more events. */ + mysql_cond_signal(&COND_rpl_thread_queue); + loc_qev_size= 0; + qev_free_pending= 0; + } + if (loc_rgi_list) + { + *loc_rgi_last_ptr_ptr= rgi_free_list; + rgi_free_list= loc_rgi_list; + loc_rgi_list= NULL; + } + if (loc_gco_list) + { + *loc_gco_last_ptr_ptr= gco_free_list; + gco_free_list= loc_gco_list; + loc_gco_list= NULL; + } +} + + +void +rpl_parallel_thread::inuse_relaylog_refcount_update() +{ + inuse_relaylog *ir= accumulated_ir_last; + if (ir) + { + my_atomic_add64(&ir->dequeued_count, accumulated_ir_count); + accumulated_ir_count= 0; + accumulated_ir_last= NULL; + } +} + + +rpl_parallel_thread::queued_event * +rpl_parallel_thread::get_qev_common(Log_event *ev, ulonglong event_size) +{ + queued_event *qev; + mysql_mutex_assert_owner(&LOCK_rpl_thread); + if ((qev= qev_free_list)) + qev_free_list= qev->next; + else if(!(qev= (queued_event *)my_malloc(sizeof(*qev), MYF(0)))) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*qev)); + return NULL; + } + qev->typ= rpl_parallel_thread::queued_event::QUEUED_EVENT; + qev->ev= ev; + qev->event_size= event_size; + qev->next= NULL; + return qev; +} + + +rpl_parallel_thread::queued_event * +rpl_parallel_thread::get_qev(Log_event *ev, ulonglong event_size, + Relay_log_info *rli) +{ + queued_event *qev= get_qev_common(ev, event_size); + if (!qev) + return NULL; + strcpy(qev->event_relay_log_name, rli->event_relay_log_name); + qev->event_relay_log_pos= rli->event_relay_log_pos; + qev->future_event_relay_log_pos= rli->future_event_relay_log_pos; + strcpy(qev->future_event_master_log_name, rli->future_event_master_log_name); + return qev; +} + + +rpl_parallel_thread::queued_event * +rpl_parallel_thread::retry_get_qev(Log_event *ev, queued_event *orig_qev, + const char *relay_log_name, + ulonglong event_pos, ulonglong event_size) +{ + queued_event *qev= get_qev_common(ev, event_size); + if (!qev) + return NULL; + qev->rgi= orig_qev->rgi; + strcpy(qev->event_relay_log_name, relay_log_name); + qev->event_relay_log_pos= event_pos; + qev->future_event_relay_log_pos= event_pos+event_size; + strcpy(qev->future_event_master_log_name, + orig_qev->future_event_master_log_name); + return qev; +} + + +void +rpl_parallel_thread::loc_free_qev(rpl_parallel_thread::queued_event *qev) +{ + inuse_relaylog *ir= qev->ir; + inuse_relaylog *last_ir= accumulated_ir_last; + if (ir != last_ir) + { + if (last_ir) + inuse_relaylog_refcount_update(); + accumulated_ir_last= ir; + } + ++accumulated_ir_count; + if (!loc_qev_list) + loc_qev_last_ptr_ptr= &qev->next; + else + qev->next= loc_qev_list; + loc_qev_list= qev; + loc_qev_size+= qev->event_size; + /* + We want to release to the global free list only occasionally, to avoid + having to take the LOCK_rpl_thread muted too many times. + + However, we do need to release regularly. If we let the unreleased part + grow too large, then the SQL driver thread may go to sleep waiting for + the queue to drop below opt_slave_parallel_max_queued, and this in turn + can stall all other worker threads for more stuff to do. + */ + if (++qev_free_pending >= QEV_BATCH_FREE || + loc_qev_size >= opt_slave_parallel_max_queued/3) + { + mysql_mutex_lock(&LOCK_rpl_thread); + batch_free(); + mysql_mutex_unlock(&LOCK_rpl_thread); + } +} + + +void +rpl_parallel_thread::free_qev(rpl_parallel_thread::queued_event *qev) +{ + mysql_mutex_assert_owner(&LOCK_rpl_thread); + qev->next= qev_free_list; + qev_free_list= qev; +} + + +rpl_group_info* +rpl_parallel_thread::get_rgi(Relay_log_info *rli, Gtid_log_event *gtid_ev, + rpl_parallel_entry *e, ulonglong event_size) +{ + rpl_group_info *rgi; + mysql_mutex_assert_owner(&LOCK_rpl_thread); + if ((rgi= rgi_free_list)) + { + rgi_free_list= rgi->next; + rgi->reinit(rli); + } + else + { + if(!(rgi= new rpl_group_info(rli))) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*rgi)); + return NULL; + } + rgi->is_parallel_exec = true; + } + if ((rgi->deferred_events_collecting= rli->mi->rpl_filter->is_on()) && + !rgi->deferred_events) + rgi->deferred_events= new Deferred_log_events(rli); + if (event_group_new_gtid(rgi, gtid_ev)) + { + free_rgi(rgi); + my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME)); + return NULL; + } + rgi->parallel_entry= e; + rgi->relay_log= rli->last_inuse_relaylog; + rgi->retry_start_offset= rli->future_event_relay_log_pos-event_size; + rgi->retry_event_count= 0; + rgi->killed_for_retry= rpl_group_info::RETRY_KILL_NONE; + + return rgi; +} + + +void +rpl_parallel_thread::loc_free_rgi(rpl_group_info *rgi) +{ + DBUG_ASSERT(rgi->commit_orderer.waitee == NULL); + rgi->free_annotate_event(); + if (!loc_rgi_list) + loc_rgi_last_ptr_ptr= &rgi->next; + else + rgi->next= loc_rgi_list; + loc_rgi_list= rgi; +} + + +void +rpl_parallel_thread::free_rgi(rpl_group_info *rgi) +{ + mysql_mutex_assert_owner(&LOCK_rpl_thread); + DBUG_ASSERT(rgi->commit_orderer.waitee == NULL); + rgi->free_annotate_event(); + rgi->next= rgi_free_list; + rgi_free_list= rgi; +} + + +group_commit_orderer * +rpl_parallel_thread::get_gco(uint64 wait_count, group_commit_orderer *prev, + uint64 prior_sub_id) +{ + group_commit_orderer *gco; + mysql_mutex_assert_owner(&LOCK_rpl_thread); + if ((gco= gco_free_list)) + gco_free_list= gco->next_gco; + else if(!(gco= (group_commit_orderer *)my_malloc(sizeof(*gco), MYF(0)))) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*gco)); + return NULL; + } + mysql_cond_init(key_COND_group_commit_orderer, + &gco->COND_group_commit_orderer, NULL); + gco->wait_count= wait_count; + gco->prev_gco= prev; + gco->next_gco= NULL; + gco->prior_sub_id= prior_sub_id; + gco->installed= false; + gco->flags= 0; + return gco; +} + + +void +rpl_parallel_thread::loc_free_gco(group_commit_orderer *gco) +{ + if (!loc_gco_list) + loc_gco_last_ptr_ptr= &gco->next_gco; + else + gco->next_gco= loc_gco_list; + loc_gco_list= gco; +} + + +rpl_parallel_thread_pool::rpl_parallel_thread_pool() + : threads(0), free_list(0), count(0), inited(false), busy(false) +{ +} + + +int +rpl_parallel_thread_pool::init(uint32 size) +{ + threads= NULL; + free_list= NULL; + count= 0; + busy= false; + + mysql_mutex_init(key_LOCK_rpl_thread_pool, &LOCK_rpl_thread_pool, + MY_MUTEX_INIT_SLOW); + mysql_cond_init(key_COND_rpl_thread_pool, &COND_rpl_thread_pool, NULL); + inited= true; + + /* + The pool is initially empty. Threads will be spawned when a slave SQL + thread is started. + */ + + return 0; +} + + +void +rpl_parallel_thread_pool::destroy() +{ + if (!inited) + return; + rpl_parallel_change_thread_count(this, 0, 1); + mysql_mutex_destroy(&LOCK_rpl_thread_pool); + mysql_cond_destroy(&COND_rpl_thread_pool); + inited= false; +} + + +/* + Wait for a worker thread to become idle. When one does, grab the thread for + our use and return it. + + Note that we return with the worker threads's LOCK_rpl_thread mutex locked. +*/ +struct rpl_parallel_thread * +rpl_parallel_thread_pool::get_thread(rpl_parallel_thread **owner, + rpl_parallel_entry *entry) +{ + rpl_parallel_thread *rpt; + + DBUG_ASSERT(count > 0); + mysql_mutex_lock(&LOCK_rpl_thread_pool); + while (unlikely(busy) || !(rpt= free_list)) + mysql_cond_wait(&COND_rpl_thread_pool, &LOCK_rpl_thread_pool); + free_list= rpt->next; + mysql_mutex_unlock(&LOCK_rpl_thread_pool); + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + rpt->current_owner= owner; + rpt->current_entry= entry; + + return rpt; +} + + +/* + Release a thread to the thread pool. + The thread should be locked, and should not have any work queued for it. +*/ +void +rpl_parallel_thread_pool::release_thread(rpl_parallel_thread *rpt) +{ + rpl_parallel_thread *list; + + mysql_mutex_assert_owner(&rpt->LOCK_rpl_thread); + DBUG_ASSERT(rpt->current_owner == NULL); + mysql_mutex_lock(&LOCK_rpl_thread_pool); + list= free_list; + rpt->next= list; + free_list= rpt; + if (!list) + mysql_cond_broadcast(&COND_rpl_thread_pool); + mysql_mutex_unlock(&LOCK_rpl_thread_pool); +} + + +/* + Obtain a worker thread that we can queue an event to. + + Each invocation allocates a new worker thread, to maximise + parallelism. However, only up to a maximum of + --slave-domain-parallel-threads workers can be occupied by a single + replication domain; after that point, we start re-using worker threads that + are still executing events that were queued earlier for this thread. + + We never queue more than --rpl-parallel-wait-queue_max amount of events + for one worker, to avoid the SQL driver thread using up all memory with + queued events while worker threads are stalling. + + Note that this function returns with rpl_parallel_thread::LOCK_rpl_thread + locked. Exception is if we were killed, in which case NULL is returned. + + The *did_enter_cond flag is set true if we had to wait for a worker thread + to become free (with mysql_cond_wait()). If so, old_stage will also be set, + and the LOCK_rpl_thread must be released with THD::EXIT_COND() instead + of mysql_mutex_unlock. + + If the flag `reuse' is set, the last worker thread will be returned again, + if it is still available. Otherwise a new worker thread is allocated. +*/ +rpl_parallel_thread * +rpl_parallel_entry::choose_thread(rpl_group_info *rgi, bool *did_enter_cond, + PSI_stage_info *old_stage, bool reuse) +{ + uint32 idx; + Relay_log_info *rli= rgi->rli; + rpl_parallel_thread *thr; + + idx= rpl_thread_idx; + if (!reuse) + { + ++idx; + if (idx >= rpl_thread_max) + idx= 0; + rpl_thread_idx= idx; + } + thr= rpl_threads[idx]; + if (thr) + { + *did_enter_cond= false; + mysql_mutex_lock(&thr->LOCK_rpl_thread); + for (;;) + { + if (thr->current_owner != &rpl_threads[idx]) + { + /* + The worker thread became idle, and returned to the free list and + possibly was allocated to a different request. So we should allocate + a new worker thread. + */ + unlock_or_exit_cond(rli->sql_driver_thd, &thr->LOCK_rpl_thread, + did_enter_cond, old_stage); + thr= NULL; + break; + } + else if (thr->queued_size <= opt_slave_parallel_max_queued) + { + /* The thread is ready to queue into. */ + break; + } + else if (rli->sql_driver_thd->check_killed()) + { + unlock_or_exit_cond(rli->sql_driver_thd, &thr->LOCK_rpl_thread, + did_enter_cond, old_stage); + my_error(ER_CONNECTION_KILLED, MYF(0)); + DBUG_EXECUTE_IF("rpl_parallel_wait_queue_max", + { + debug_sync_set_action(rli->sql_driver_thd, + STRING_WITH_LEN("now SIGNAL wait_queue_killed")); + };); + slave_output_error_info(rgi, rli->sql_driver_thd); + return NULL; + } + else + { + /* + We have reached the limit of how much memory we are allowed to use + for queuing events, so wait for the thread to consume some of its + queue. + */ + if (!*did_enter_cond) + { + /* + We need to do the debug_sync before ENTER_COND(). + Because debug_sync changes the thd->mysys_var->current_mutex, + and this can cause THD::awake to use the wrong mutex. + */ + DBUG_EXECUTE_IF("rpl_parallel_wait_queue_max", + { + debug_sync_set_action(rli->sql_driver_thd, + STRING_WITH_LEN("now SIGNAL wait_queue_ready")); + };); + rli->sql_driver_thd->ENTER_COND(&thr->COND_rpl_thread_queue, + &thr->LOCK_rpl_thread, + &stage_waiting_for_room_in_worker_thread, + old_stage); + *did_enter_cond= true; + } + mysql_cond_wait(&thr->COND_rpl_thread_queue, &thr->LOCK_rpl_thread); + } + } + } + if (!thr) + rpl_threads[idx]= thr= global_rpl_thread_pool.get_thread(&rpl_threads[idx], + this); + + return thr; +} + +static void +free_rpl_parallel_entry(void *element) +{ + rpl_parallel_entry *e= (rpl_parallel_entry *)element; + while (e->current_gco) + { + group_commit_orderer *prev_gco= e->current_gco->prev_gco; + dealloc_gco(e->current_gco); + e->current_gco= prev_gco; + } + mysql_cond_destroy(&e->COND_parallel_entry); + mysql_mutex_destroy(&e->LOCK_parallel_entry); + my_free(e); +} + + +rpl_parallel::rpl_parallel() : + current(NULL), sql_thread_stopping(false) +{ + my_hash_init(&domain_hash, &my_charset_bin, 32, + offsetof(rpl_parallel_entry, domain_id), sizeof(uint32), + NULL, free_rpl_parallel_entry, HASH_UNIQUE); +} + + +void +rpl_parallel::reset() +{ + my_hash_reset(&domain_hash); + current= NULL; + sql_thread_stopping= false; +} + + +rpl_parallel::~rpl_parallel() +{ + my_hash_free(&domain_hash); +} + + +rpl_parallel_entry * +rpl_parallel::find(uint32 domain_id) +{ + struct rpl_parallel_entry *e; + + if (!(e= (rpl_parallel_entry *)my_hash_search(&domain_hash, + (const uchar *)&domain_id, 0))) + { + /* Allocate a new, empty one. */ + ulong count= opt_slave_domain_parallel_threads; + if (count == 0 || count > opt_slave_parallel_threads) + count= opt_slave_parallel_threads; + rpl_parallel_thread **p; + if (!my_multi_malloc(MYF(MY_WME|MY_ZEROFILL), + &e, sizeof(*e), + &p, count*sizeof(*p), + NULL)) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)(sizeof(*e)+count*sizeof(*p))); + return NULL; + } + e->rpl_threads= p; + e->rpl_thread_max= count; + e->domain_id= domain_id; + e->stop_on_error_sub_id= (uint64)ULONGLONG_MAX; + e->pause_sub_id= (uint64)ULONGLONG_MAX; + if (my_hash_insert(&domain_hash, (uchar *)e)) + { + my_free(e); + return NULL; + } + mysql_mutex_init(key_LOCK_parallel_entry, &e->LOCK_parallel_entry, + MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_parallel_entry, &e->COND_parallel_entry, NULL); + } + else + e->force_abort= false; + + return e; +} + +/** + Wait until all sql worker threads has stopped processing + + This is called when sql thread has been killed/stopped +*/ + +void +rpl_parallel::wait_for_done(THD *thd, Relay_log_info *rli) +{ + struct rpl_parallel_entry *e; + rpl_parallel_thread *rpt; + uint32 i, j; + + /* + First signal all workers that they must force quit; no more events will + be queued to complete any partial event groups executed. + */ + for (i= 0; i < domain_hash.records; ++i) + { + e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i); + mysql_mutex_lock(&e->LOCK_parallel_entry); + /* + We want the worker threads to stop as quickly as is safe. If the slave + SQL threads are behind, we could have significant amount of events + queued for the workers, and we want to stop without waiting for them + all to be applied first. But if any event group has already started + executing in a worker, we want to be sure that all prior event groups + are also executed, so that we stop at a consistent point in the binlog + stream (per replication domain). + + All event groups wait for e->count_committing_event_groups to reach + the value of group_commit_orderer::wait_count before starting to + execute. Thus, at this point we know that any event group with a + strictly larger wait_count are safe to skip, none of them can have + started executing yet. So we set e->stop_count here and use it to + decide in the worker threads whether to continue executing an event + group or whether to skip it, when force_abort is set. + + If we stop due to reaching the START SLAVE UNTIL condition, then we + need to continue executing any queued events up to that point. + */ + e->force_abort= true; + e->stop_count= rli->stop_for_until ? + e->count_queued_event_groups : e->count_committing_event_groups; + mysql_mutex_unlock(&e->LOCK_parallel_entry); + for (j= 0; j < e->rpl_thread_max; ++j) + { + if ((rpt= e->rpl_threads[j])) + { + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + if (rpt->current_owner == &e->rpl_threads[j]) + mysql_cond_signal(&rpt->COND_rpl_thread); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + } + } + } + DBUG_EXECUTE_IF("rpl_parallel_wait_for_done_trigger", + { + debug_sync_set_action(thd, + STRING_WITH_LEN("now SIGNAL wait_for_done_waiting")); + };); + + for (i= 0; i < domain_hash.records; ++i) + { + e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i); + for (j= 0; j < e->rpl_thread_max; ++j) + { + if ((rpt= e->rpl_threads[j])) + { + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + while (rpt->current_owner == &e->rpl_threads[j]) + mysql_cond_wait(&rpt->COND_rpl_thread_stop, &rpt->LOCK_rpl_thread); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + } + } + } +} + + +/* + This function handles the case where the SQL driver thread reached the + START SLAVE UNTIL position; we stop queueing more events but continue + processing remaining, already queued events; then use executes manual + STOP SLAVE; then this function signals to worker threads that they + should stop the processing of any remaining queued events. +*/ +void +rpl_parallel::stop_during_until() +{ + struct rpl_parallel_entry *e; + uint32 i; + + for (i= 0; i < domain_hash.records; ++i) + { + e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i); + mysql_mutex_lock(&e->LOCK_parallel_entry); + if (e->force_abort) + e->stop_count= e->count_committing_event_groups; + mysql_mutex_unlock(&e->LOCK_parallel_entry); + } +} + + +bool +rpl_parallel::workers_idle() +{ + struct rpl_parallel_entry *e; + uint32 i, max_i; + + max_i= domain_hash.records; + for (i= 0; i < max_i; ++i) + { + bool active; + e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i); + mysql_mutex_lock(&e->LOCK_parallel_entry); + active= e->current_sub_id > e->last_committed_sub_id; + mysql_mutex_unlock(&e->LOCK_parallel_entry); + if (active) + break; + } + return (i == max_i); +} + + +int +rpl_parallel_entry::queue_master_restart(rpl_group_info *rgi, + Format_description_log_event *fdev) +{ + uint32 idx; + rpl_parallel_thread *thr; + rpl_parallel_thread::queued_event *qev; + Relay_log_info *rli= rgi->rli; + + /* + We only need to queue the server restart if we still have a thread working + on a (potentially partial) event group. + + If the last thread we queued for has finished, then it cannot have any + partial event group that needs aborting. + + Thus there is no need for the full complexity of choose_thread(). We only + need to check if we have a current worker thread, and queue for it if so. + */ + idx= rpl_thread_idx; + thr= rpl_threads[idx]; + if (!thr) + return 0; + mysql_mutex_lock(&thr->LOCK_rpl_thread); + if (thr->current_owner != &rpl_threads[idx]) + { + /* No active worker thread, so no need to queue the master restart. */ + mysql_mutex_unlock(&thr->LOCK_rpl_thread); + return 0; + } + + if (!(qev= thr->get_qev(fdev, 0, rli))) + { + mysql_mutex_unlock(&thr->LOCK_rpl_thread); + return 1; + } + + qev->rgi= rgi; + qev->typ= rpl_parallel_thread::queued_event::QUEUED_MASTER_RESTART; + qev->entry_for_queued= this; + qev->ir= rli->last_inuse_relaylog; + ++qev->ir->queued_count; + thr->enqueue(qev); + mysql_cond_signal(&thr->COND_rpl_thread); + mysql_mutex_unlock(&thr->LOCK_rpl_thread); + return 0; +} + + +int +rpl_parallel::wait_for_workers_idle(THD *thd) +{ + uint32 i, max_i; + + /* + The domain_hash is only accessed by the SQL driver thread, so it is safe + to iterate over without a lock. + */ + max_i= domain_hash.records; + for (i= 0; i < max_i; ++i) + { + PSI_stage_info old_stage; + struct rpl_parallel_entry *e; + int err= 0; + + e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i); + mysql_mutex_lock(&e->LOCK_parallel_entry); + ++e->need_sub_id_signal; + thd->ENTER_COND(&e->COND_parallel_entry, &e->LOCK_parallel_entry, + &stage_waiting_for_workers_idle, &old_stage); + while (e->current_sub_id > e->last_committed_sub_id) + { + if (thd->check_killed()) + { + thd->send_kill_message(); + err= 1; + break; + } + mysql_cond_wait(&e->COND_parallel_entry, &e->LOCK_parallel_entry); + } + --e->need_sub_id_signal; + thd->EXIT_COND(&old_stage); + if (err) + return err; + } + return 0; +} + + +/* + Handle seeing a GTID during slave restart in GTID mode. If we stopped with + different replication domains having reached different positions in the relay + log, we need to skip event groups in domains that are further progressed. + + Updates the state with the seen GTID, and returns true if this GTID should + be skipped, false otherwise. +*/ +bool +process_gtid_for_restart_pos(Relay_log_info *rli, rpl_gtid *gtid) +{ + slave_connection_state::entry *gtid_entry; + slave_connection_state *state= &rli->restart_gtid_pos; + + if (likely(state->count() == 0) || + !(gtid_entry= state->find_entry(gtid->domain_id))) + return false; + if (gtid->server_id == gtid_entry->gtid.server_id) + { + uint64 seq_no= gtid_entry->gtid.seq_no; + if (gtid->seq_no >= seq_no) + { + /* + This domain has reached its start position. So remove it, so that + further events will be processed normally. + */ + state->remove(>id_entry->gtid); + } + return gtid->seq_no <= seq_no; + } + else + return true; +} + + +/* + This is used when we get an error during processing in do_event(); + We will not queue any event to the thread, but we still need to wake it up + to be sure that it will be returned to the pool. +*/ +static void +abandon_worker_thread(THD *thd, rpl_parallel_thread *cur_thread, + bool *did_enter_cond, PSI_stage_info *old_stage) +{ + unlock_or_exit_cond(thd, &cur_thread->LOCK_rpl_thread, + did_enter_cond, old_stage); + mysql_cond_signal(&cur_thread->COND_rpl_thread); +} + + +/* + do_event() is executed by the sql_driver_thd thread. + It's main purpose is to find a thread that can execute the query. + + @retval 0 ok, event was accepted + @retval 1 error + @retval -1 event should be executed serially, in the sql driver thread +*/ + +int +rpl_parallel::do_event(rpl_group_info *serial_rgi, Log_event *ev, + ulonglong event_size) +{ + rpl_parallel_entry *e; + rpl_parallel_thread *cur_thread; + rpl_parallel_thread::queued_event *qev; + rpl_group_info *rgi= NULL; + Relay_log_info *rli= serial_rgi->rli; + enum Log_event_type typ; + bool is_group_event; + bool did_enter_cond= false; + PSI_stage_info old_stage; + + DBUG_EXECUTE_IF("slave_crash_if_parallel_apply", DBUG_SUICIDE();); + /* Handle master log name change, seen in Rotate_log_event. */ + typ= ev->get_type_code(); + if (unlikely(typ == ROTATE_EVENT)) + { + Rotate_log_event *rev= static_cast<Rotate_log_event *>(ev); + if ((rev->server_id != global_system_variables.server_id || + rli->replicate_same_server_id) && + !rev->is_relay_log_event() && + !rli->is_in_group()) + { + memcpy(rli->future_event_master_log_name, + rev->new_log_ident, rev->ident_len+1); + rli->notify_group_master_log_name_update(); + } + } + + /* + Execute queries non-parallel if slave_skip_counter is set, as it's is + easier to skip queries in single threaded mode. + */ + if (rli->slave_skip_counter) + return -1; + + /* Execute pre-10.0 event, which have no GTID, in single-threaded mode. */ + is_group_event= Log_event::is_group_event(typ); + if (unlikely(!current) && typ != GTID_EVENT && + !(unlikely(rli->gtid_skip_flag != GTID_SKIP_NOT) && is_group_event)) + return -1; + + /* ToDo: what to do with this lock?!? */ + mysql_mutex_unlock(&rli->data_lock); + + if (unlikely(typ == FORMAT_DESCRIPTION_EVENT)) + { + Format_description_log_event *fdev= + static_cast<Format_description_log_event *>(ev); + if (fdev->created) + { + /* + This format description event marks a new binlog after a master server + restart. We are going to close all temporary tables to clean up any + possible left-overs after a prior master crash. + + Thus we need to wait for all prior events to execute to completion, + in case they need access to any of the temporary tables. + + We also need to notify the worker thread running the prior incomplete + event group (if any), as such event group signifies an incompletely + written group cut short by a master crash, and must be rolled back. + */ + if (current->queue_master_restart(serial_rgi, fdev) || + wait_for_workers_idle(rli->sql_driver_thd)) + { + delete ev; + return 1; + } + } + } + else if (unlikely(typ == GTID_LIST_EVENT)) + { + Gtid_list_log_event *glev= static_cast<Gtid_list_log_event *>(ev); + rpl_gtid *list= glev->list; + uint32 count= glev->count; + rli->update_relay_log_state(list, count); + while (count) + { + process_gtid_for_restart_pos(rli, list); + ++list; + --count; + } + } + + /* + Stop queueing additional event groups once the SQL thread is requested to + stop. + + We have to queue any remaining events of any event group that has already + been partially queued, but after that we will just ignore any further + events the SQL driver thread may try to queue, and eventually it will stop. + */ + if ((typ == GTID_EVENT || !is_group_event) && rli->abort_slave) + sql_thread_stopping= true; + if (sql_thread_stopping) + { + delete ev; + /* + Return "no error"; normal stop is not an error, and otherwise the error + has already been recorded. + */ + return 0; + } + + if (unlikely(rli->gtid_skip_flag != GTID_SKIP_NOT) && is_group_event) + { + if (typ == GTID_EVENT) + rli->gtid_skip_flag= GTID_SKIP_NOT; + else + { + if (rli->gtid_skip_flag == GTID_SKIP_STANDALONE) + { + if (!Log_event::is_part_of_group(typ)) + rli->gtid_skip_flag= GTID_SKIP_NOT; + } + else + { + DBUG_ASSERT(rli->gtid_skip_flag == GTID_SKIP_TRANSACTION); + if (typ == XID_EVENT || + (typ == QUERY_EVENT && + (((Query_log_event *)ev)->is_commit() || + ((Query_log_event *)ev)->is_rollback()))) + rli->gtid_skip_flag= GTID_SKIP_NOT; + } + delete_or_keep_event_post_apply(serial_rgi, typ, ev); + return 0; + } + } + + if (typ == GTID_EVENT) + { + rpl_gtid gtid; + Gtid_log_event *gtid_ev= static_cast<Gtid_log_event *>(ev); + uint32 domain_id= (rli->mi->using_gtid == Master_info::USE_GTID_NO || + rli->mi->parallel_mode <= SLAVE_PARALLEL_MINIMAL ? + 0 : gtid_ev->domain_id); + if (!(e= find(domain_id))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME)); + delete ev; + return 1; + } + current= e; + + gtid.domain_id= gtid_ev->domain_id; + gtid.server_id= gtid_ev->server_id; + gtid.seq_no= gtid_ev->seq_no; + rli->update_relay_log_state(>id, 1); + if (process_gtid_for_restart_pos(rli, >id)) + { + /* + This domain has progressed further into the relay log before the last + SQL thread restart. So we need to skip this event group to not doubly + apply it. + */ + rli->gtid_skip_flag= ((gtid_ev->flags2 & Gtid_log_event::FL_STANDALONE) ? + GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION); + delete_or_keep_event_post_apply(serial_rgi, typ, ev); + return 0; + } + } + else + e= current; + + /* + Find a worker thread to queue the event for. + Prefer a new thread, so we maximise parallelism (at least for the group + commit). But do not exceed a limit of --slave-domain-parallel-threads; + instead re-use a thread that we queued for previously. + */ + cur_thread= + e->choose_thread(serial_rgi, &did_enter_cond, &old_stage, + typ != GTID_EVENT); + if (!cur_thread) + { + /* This means we were killed. The error is already signalled. */ + delete ev; + return 1; + } + + if (!(qev= cur_thread->get_qev(ev, event_size, rli))) + { + abandon_worker_thread(rli->sql_driver_thd, cur_thread, + &did_enter_cond, &old_stage); + delete ev; + return 1; + } + + if (typ == GTID_EVENT) + { + Gtid_log_event *gtid_ev= static_cast<Gtid_log_event *>(ev); + bool new_gco; + enum_slave_parallel_mode mode= rli->mi->parallel_mode; + uchar gtid_flags= gtid_ev->flags2; + group_commit_orderer *gco; + uint8 force_switch_flag; + enum rpl_group_info::enum_speculation speculation; + + if (!(rgi= cur_thread->get_rgi(rli, gtid_ev, e, event_size))) + { + cur_thread->free_qev(qev); + abandon_worker_thread(rli->sql_driver_thd, cur_thread, + &did_enter_cond, &old_stage); + delete ev; + return 1; + } + + /* + We queue the event group in a new worker thread, to run in parallel + with previous groups. + + To preserve commit order within the replication domain, we set up + rgi->wait_commit_sub_id to make the new group commit only after the + previous group has committed. + + Event groups that group-committed together on the master can be run + in parallel with each other without restrictions. But one batch of + group-commits may not start before all groups in the previous batch + have initiated their commit phase; we set up rgi->gco to ensure that. + */ + rgi->wait_commit_sub_id= e->current_sub_id; + rgi->wait_commit_group_info= e->current_group_info; + + speculation= rpl_group_info::SPECULATE_NO; + new_gco= true; + force_switch_flag= 0; + gco= e->current_gco; + if (likely(gco)) + { + uint8 flags= gco->flags; + + if (mode <= SLAVE_PARALLEL_MINIMAL || + !(gtid_flags & Gtid_log_event::FL_GROUP_COMMIT_ID) || + e->last_commit_id != gtid_ev->commit_id) + flags|= group_commit_orderer::MULTI_BATCH; + /* Make sure we do not attempt to run DDL in parallel speculatively. */ + if (gtid_flags & Gtid_log_event::FL_DDL) + flags|= (force_switch_flag= group_commit_orderer::FORCE_SWITCH); + + if (!(flags & group_commit_orderer::MULTI_BATCH)) + { + /* + Still the same batch of event groups that group-committed together + on the master, so we can run in parallel. + */ + new_gco= false; + } + else if ((mode >= SLAVE_PARALLEL_OPTIMISTIC) && + !(flags & group_commit_orderer::FORCE_SWITCH)) + { + /* + In transactional parallel mode, we optimistically attempt to run + non-DDL in parallel. In case of conflicts, we catch the conflict as + a deadlock or other error, roll back and retry serially. + + The assumption is that only a few event groups will be + non-transactional or otherwise unsuitable for parallel apply. Those + transactions are still scheduled in parallel, but we set a flag that + will make the worker thread wait for everything before to complete + before starting. + */ + new_gco= false; + if (!(gtid_flags & Gtid_log_event::FL_TRANSACTIONAL) || + ( (!(gtid_flags & Gtid_log_event::FL_ALLOW_PARALLEL) || + (gtid_flags & Gtid_log_event::FL_WAITED)) && + (mode < SLAVE_PARALLEL_AGGRESSIVE))) + { + /* + This transaction should not be speculatively run in parallel with + what came before, either because it cannot safely be rolled back in + case of a conflict, or because it was marked as likely to conflict + and require expensive rollback and retry. + + Here we mark it as such, and then the worker thread will do a + wait_for_prior_commit() before starting it. We do not introduce a + new group_commit_orderer, since we still want following transactions + to run in parallel with transactions prior to this one. + */ + speculation= rpl_group_info::SPECULATE_WAIT; + } + else + speculation= rpl_group_info::SPECULATE_OPTIMISTIC; + } + gco->flags= flags; + } + else + { + if (gtid_flags & Gtid_log_event::FL_DDL) + force_switch_flag= group_commit_orderer::FORCE_SWITCH; + } + rgi->speculation= speculation; + + if (gtid_flags & Gtid_log_event::FL_GROUP_COMMIT_ID) + e->last_commit_id= gtid_ev->commit_id; + else + e->last_commit_id= 0; + + if (new_gco) + { + /* + Do not run this event group in parallel with what came before; instead + wait for everything prior to at least have started its commit phase, to + avoid any risk of performing any conflicting action too early. + + Remember the count that marks the end of the previous batch of event + groups that run in parallel, and allocate a new gco. + */ + uint64 count= e->count_queued_event_groups; + + if (!(gco= cur_thread->get_gco(count, gco, e->current_sub_id))) + { + cur_thread->free_rgi(rgi); + cur_thread->free_qev(qev); + abandon_worker_thread(rli->sql_driver_thd, cur_thread, + &did_enter_cond, &old_stage); + delete ev; + return 1; + } + gco->flags|= force_switch_flag; + e->current_gco= gco; + } + rgi->gco= gco; + + qev->rgi= e->current_group_info= rgi; + e->current_sub_id= rgi->gtid_sub_id; + ++e->count_queued_event_groups; + } + else if (!is_group_event) + { + int err; + bool tmp; + /* + Events like ROTATE and FORMAT_DESCRIPTION. Do not run in worker thread. + Same for events not preceeded by GTID (we should not see those normally, + but they might be from an old master). + */ + qev->rgi= serial_rgi; + + tmp= serial_rgi->is_parallel_exec; + serial_rgi->is_parallel_exec= true; + err= rpt_handle_event(qev, NULL); + serial_rgi->is_parallel_exec= tmp; + if (ev->is_relay_log_event()) + qev->future_event_master_log_pos= 0; + else if (typ == ROTATE_EVENT) + qev->future_event_master_log_pos= + (static_cast<Rotate_log_event *>(ev))->pos; + else + qev->future_event_master_log_pos= ev->log_pos; + delete_or_keep_event_post_apply(serial_rgi, typ, ev); + + if (err) + { + cur_thread->free_qev(qev); + abandon_worker_thread(rli->sql_driver_thd, cur_thread, + &did_enter_cond, &old_stage); + return 1; + } + /* + Queue a position update, so that the position will be updated in a + reasonable way relative to other events: + + - If the currently executing events are queued serially for a single + thread, the position will only be updated when everything before has + completed. + + - If we are executing multiple independent events in parallel, then at + least the position will not be updated until one of them has reached + the current point. + */ + qev->typ= rpl_parallel_thread::queued_event::QUEUED_POS_UPDATE; + qev->entry_for_queued= e; + } + else + { + qev->rgi= e->current_group_info; + } + + /* + Queue the event for processing. + */ + qev->ir= rli->last_inuse_relaylog; + ++qev->ir->queued_count; + cur_thread->enqueue(qev); + unlock_or_exit_cond(rli->sql_driver_thd, &cur_thread->LOCK_rpl_thread, + &did_enter_cond, &old_stage); + mysql_cond_signal(&cur_thread->COND_rpl_thread); + + return 0; +} diff --git a/sql/rpl_parallel.h b/sql/rpl_parallel.h new file mode 100644 index 00000000000..a0faeae815c --- /dev/null +++ b/sql/rpl_parallel.h @@ -0,0 +1,375 @@ +#ifndef RPL_PARALLEL_H +#define RPL_PARALLEL_H + +#include "log_event.h" + + +struct rpl_parallel; +struct rpl_parallel_entry; +struct rpl_parallel_thread_pool; + +class Relay_log_info; +struct inuse_relaylog; + + +/* + Structure used to keep track of the parallel replication of a batch of + event-groups that group-committed together on the master. + + It is used to ensure that every event group in one batch has reached the + commit stage before the next batch starts executing. + + Note the lifetime of this structure: + + - It is allocated when the first event in a new batch of group commits + is queued, from the free list rpl_parallel_entry::gco_free_list. + + - The gco for the batch currently being queued is owned by + rpl_parallel_entry::current_gco. The gco for a previous batch that has + been fully queued is owned by the gco->prev_gco pointer of the gco for + the following batch. + + - The worker thread waits on gco->COND_group_commit_orderer for + rpl_parallel_entry::count_committing_event_groups to reach wait_count + before starting; the first waiter links the gco into the next_gco + pointer of the gco of the previous batch for signalling. + + - When an event group reaches the commit stage, it signals the + COND_group_commit_orderer if its gco->next_gco pointer is non-NULL and + rpl_parallel_entry::count_committing_event_groups has reached + gco->next_gco->wait_count. + + - The gco lives until all its event groups have completed their commit. + This is detected by rpl_parallel_entry::last_committed_sub_id being + greater than or equal gco->last_sub_id. Once this happens, the gco is + freed. Note that since update of last_committed_sub_id can happen + out-of-order, the thread that frees a given gco can be for any later + event group, not necessarily an event group from the gco being freed. +*/ +struct group_commit_orderer { + /* Wakeup condition, used with rpl_parallel_entry::LOCK_parallel_entry. */ + mysql_cond_t COND_group_commit_orderer; + uint64 wait_count; + group_commit_orderer *prev_gco; + group_commit_orderer *next_gco; + /* + The sub_id of last event group in the previous GCO. + Only valid if prev_gco != NULL. + */ + uint64 prior_sub_id; + /* + The sub_id of the last event group in this GCO. Only valid when next_gco + is non-NULL. + */ + uint64 last_sub_id; + /* + This flag is set when this GCO has been installed into the next_gco pointer + of the previous GCO. + */ + bool installed; + + /* + This flag is set for a GCO in which we have event groups with multiple + different commit_id values from the master. This happens when we + optimistically try to execute in parallel transactions not known to be + conflict-free. + + When this flag is set, in case of DDL we need to start a new GCO regardless + of current commit_id, as DDL is not safe to speculatively apply in parallel + with prior event groups. + */ + static const uint8 MULTI_BATCH = 0x01; + /* + This flag is set for a GCO that contains DDL. If set, it forces a switch to + a new GCO upon seeing a new commit_id, as DDL is not safe to speculatively + replicate in parallel with subsequent transactions. + */ + static const uint8 FORCE_SWITCH = 0x02; + uint8 flags; +}; + + +struct rpl_parallel_thread { + bool delay_start; + bool running; + bool stop; + bool pause_for_ftwrl; + mysql_mutex_t LOCK_rpl_thread; + mysql_cond_t COND_rpl_thread; + mysql_cond_t COND_rpl_thread_queue; + mysql_cond_t COND_rpl_thread_stop; + struct rpl_parallel_thread *next; /* For free list. */ + struct rpl_parallel_thread_pool *pool; + THD *thd; + /* + Who owns the thread, if any (it's a pointer into the + rpl_parallel_entry::rpl_threads array. + */ + struct rpl_parallel_thread **current_owner; + /* The rpl_parallel_entry of the owner. */ + rpl_parallel_entry *current_entry; + struct queued_event { + queued_event *next; + /* + queued_event can hold either an event to be executed, or just a binlog + position to be updated without any associated event. + */ + enum queued_event_t { + QUEUED_EVENT, + QUEUED_POS_UPDATE, + QUEUED_MASTER_RESTART + } typ; + union { + Log_event *ev; /* QUEUED_EVENT */ + rpl_parallel_entry *entry_for_queued; /* QUEUED_POS_UPDATE and + QUEUED_MASTER_RESTART */ + }; + rpl_group_info *rgi; + inuse_relaylog *ir; + ulonglong future_event_relay_log_pos; + char event_relay_log_name[FN_REFLEN]; + char future_event_master_log_name[FN_REFLEN]; + ulonglong event_relay_log_pos; + my_off_t future_event_master_log_pos; + size_t event_size; + } *event_queue, *last_in_queue; + uint64 queued_size; + /* These free lists are protected by LOCK_rpl_thread. */ + queued_event *qev_free_list; + rpl_group_info *rgi_free_list; + group_commit_orderer *gco_free_list; + /* + These free lists are local to the thread, so need not be protected by any + lock. They are moved to the global free lists in batches in the function + batch_free(), to reduce LOCK_rpl_thread contention. + + The lists are not NULL-terminated (as we do not need to traverse them). + Instead, if they are non-NULL, the loc_XXX_last_ptr_ptr points to the + `next' pointer of the last element, which is used to link into the front + of the global freelists. + */ + queued_event *loc_qev_list, **loc_qev_last_ptr_ptr; + size_t loc_qev_size; + uint64 qev_free_pending; + rpl_group_info *loc_rgi_list, **loc_rgi_last_ptr_ptr; + group_commit_orderer *loc_gco_list, **loc_gco_last_ptr_ptr; + /* These keep track of batch update of inuse_relaylog refcounts. */ + inuse_relaylog *accumulated_ir_last; + uint64 accumulated_ir_count; + + void enqueue(queued_event *qev) + { + if (last_in_queue) + last_in_queue->next= qev; + else + event_queue= qev; + last_in_queue= qev; + queued_size+= qev->event_size; + } + + void dequeue1(queued_event *list) + { + DBUG_ASSERT(list == event_queue); + event_queue= last_in_queue= NULL; + } + + void dequeue2(size_t dequeue_size) + { + queued_size-= dequeue_size; + } + + queued_event *get_qev_common(Log_event *ev, ulonglong event_size); + queued_event *get_qev(Log_event *ev, ulonglong event_size, + Relay_log_info *rli); + queued_event *retry_get_qev(Log_event *ev, queued_event *orig_qev, + const char *relay_log_name, + ulonglong event_pos, ulonglong event_size); + /* + Put a qev on the local free list, to be later released to the global free + list by batch_free(). + */ + void loc_free_qev(queued_event *qev); + /* + Release an rgi immediately to the global free list. Requires holding the + LOCK_rpl_thread mutex. + */ + void free_qev(queued_event *qev); + rpl_group_info *get_rgi(Relay_log_info *rli, Gtid_log_event *gtid_ev, + rpl_parallel_entry *e, ulonglong event_size); + /* + Put an gco on the local free list, to be later released to the global free + list by batch_free(). + */ + void loc_free_rgi(rpl_group_info *rgi); + /* + Release an rgi immediately to the global free list. Requires holding the + LOCK_rpl_thread mutex. + */ + void free_rgi(rpl_group_info *rgi); + group_commit_orderer *get_gco(uint64 wait_count, group_commit_orderer *prev, + uint64 first_sub_id); + /* + Put a gco on the local free list, to be later released to the global free + list by batch_free(). + */ + void loc_free_gco(group_commit_orderer *gco); + /* + Move all local free lists to the global ones. Requires holding + LOCK_rpl_thread. + */ + void batch_free(); + /* Update inuse_relaylog refcounts with what we have accumulated so far. */ + void inuse_relaylog_refcount_update(); +}; + + +struct rpl_parallel_thread_pool { + struct rpl_parallel_thread **threads; + struct rpl_parallel_thread *free_list; + mysql_mutex_t LOCK_rpl_thread_pool; + mysql_cond_t COND_rpl_thread_pool; + uint32 count; + bool inited; + /* + While FTWRL runs, this counter is incremented to make SQL thread or + STOP/START slave not try to start new activity while that operation + is in progress. + */ + bool busy; + + rpl_parallel_thread_pool(); + int init(uint32 size); + void destroy(); + struct rpl_parallel_thread *get_thread(rpl_parallel_thread **owner, + rpl_parallel_entry *entry); + void release_thread(rpl_parallel_thread *rpt); +}; + + +struct rpl_parallel_entry { + mysql_mutex_t LOCK_parallel_entry; + mysql_cond_t COND_parallel_entry; + uint32 domain_id; + /* + Incremented by wait_for_workers_idle() and rpl_pause_for_ftwrl() to show + that they are waiting, so that finish_event_group knows to signal them + when last_committed_sub_id is increased. + */ + uint32 need_sub_id_signal; + uint64 last_commit_id; + bool active; + /* + Set when SQL thread is shutting down, and no more events can be processed, + so worker threads must force abort any current transactions without + waiting for event groups to complete. + */ + bool force_abort; + /* + At STOP SLAVE (force_abort=true), we do not want to process all events in + the queue (which could unnecessarily delay stop, if a lot of events happen + to be queued). The stop_count provides a safe point at which to stop, so + that everything before becomes committed and nothing after does. The value + corresponds to group_commit_orderer::wait_count; if wait_count is less than + or equal to stop_count, we execute the associated event group, else we + skip it (and all following) and stop. + */ + uint64 stop_count; + + /* + Cyclic array recording the last rpl_thread_max worker threads that we + queued event for. This is used to limit how many workers a single domain + can occupy (--slave-domain-parallel-threads). + + Note that workers are never explicitly deleted from the array. Instead, + we need to check (under LOCK_rpl_thread) that the thread still belongs + to us before re-using (rpl_thread::current_owner). + */ + rpl_parallel_thread **rpl_threads; + uint32 rpl_thread_max; + uint32 rpl_thread_idx; + /* + The sub_id of the last transaction to commit within this domain_id. + Must be accessed under LOCK_parallel_entry protection. + + Event groups commit in order, so the rpl_group_info for an event group + will be alive (at least) as long as + rpl_group_info::gtid_sub_id > last_committed_sub_id. This can be used to + safely refer back to previous event groups if they are still executing, + and ignore them if they completed, without requiring explicit + synchronisation between the threads. + */ + uint64 last_committed_sub_id; + /* + The sub_id of the last event group in this replication domain that was + queued for execution by a worker thread. + */ + uint64 current_sub_id; + /* + The largest sub_id that has started its transaction. Protected by + LOCK_parallel_entry. + + (Transactions can start out-of-order, so this value signifies that no + transactions with larger sub_id have started, but not necessarily that all + transactions with smaller sub_id have started). + */ + uint64 largest_started_sub_id; + rpl_group_info *current_group_info; + /* + If we get an error in some event group, we set the sub_id of that event + group here. Then later event groups (with higher sub_id) can know not to + try to start (event groups that already started will be rolled back when + wait_for_prior_commit() returns error). + The value is ULONGLONG_MAX when no error occurred. + */ + uint64 stop_on_error_sub_id; + /* + During FLUSH TABLES WITH READ LOCK, transactions with sub_id larger than + this value must not start, but wait until the global read lock is released. + The value is set to ULONGLONG_MAX when no FTWRL is pending. + */ + uint64 pause_sub_id; + /* Total count of event groups queued so far. */ + uint64 count_queued_event_groups; + /* + Count of event groups that have started (but not necessarily completed) + the commit phase. We use this to know when every event group in a previous + batch of master group commits have started committing on the slave, so + that it is safe to start executing the events in the following batch. + */ + uint64 count_committing_event_groups; + /* The group_commit_orderer object for the events currently being queued. */ + group_commit_orderer *current_gco; + + rpl_parallel_thread * choose_thread(rpl_group_info *rgi, bool *did_enter_cond, + PSI_stage_info *old_stage, bool reuse); + int queue_master_restart(rpl_group_info *rgi, + Format_description_log_event *fdev); +}; +struct rpl_parallel { + HASH domain_hash; + rpl_parallel_entry *current; + bool sql_thread_stopping; + + rpl_parallel(); + ~rpl_parallel(); + void reset(); + rpl_parallel_entry *find(uint32 domain_id); + void wait_for_done(THD *thd, Relay_log_info *rli); + void stop_during_until(); + bool workers_idle(); + int wait_for_workers_idle(THD *thd); + int do_event(rpl_group_info *serial_rgi, Log_event *ev, ulonglong event_size); +}; + + +extern struct rpl_parallel_thread_pool global_rpl_thread_pool; + + +extern int rpl_parallel_resize_pool_if_no_slaves(void); +extern int rpl_parallel_activate_pool(rpl_parallel_thread_pool *pool); +extern int rpl_parallel_inactivate_pool(rpl_parallel_thread_pool *pool); +extern bool process_gtid_for_restart_pos(Relay_log_info *rli, rpl_gtid *gtid); +extern int rpl_pause_for_ftwrl(THD *thd); +extern void rpl_unpause_after_ftwrl(THD *thd); + +#endif /* RPL_PARALLEL_H */ diff --git a/sql/rpl_record.cc b/sql/rpl_record.cc index db65cfa8757..4f12169fbf3 100644 --- a/sql/rpl_record.cc +++ b/sql/rpl_record.cc @@ -14,6 +14,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "rpl_rli.h" @@ -184,12 +185,12 @@ pack_row(TABLE *table, MY_BITMAP const* cols, @retval HA_ERR_GENERIC A generic, internal, error caused the unpacking to fail. - @retval ER_SLAVE_CORRUPT_EVENT + @retval HA_ERR_CORRUPT_EVENT Found error when trying to unpack fields. */ #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) int -unpack_row(Relay_log_info const *rli, +unpack_row(rpl_group_info *rgi, TABLE *table, uint const colcnt, uchar const *const row_data, MY_BITMAP const *cols, uchar const **const current_row_end, ulong *const master_reclength, @@ -199,7 +200,6 @@ unpack_row(Relay_log_info const *rli, DBUG_ASSERT(row_data); DBUG_ASSERT(table); size_t const master_null_byte_count= (bitmap_bits_set(cols) + 7) / 8; - int error= 0; uchar const *null_ptr= row_data; uchar const *pack_ptr= row_data + master_null_byte_count; @@ -208,6 +208,16 @@ unpack_row(Relay_log_info const *rli, Field **field_ptr; Field **const end_ptr= begin_ptr + colcnt; + if (bitmap_is_clear_all(cols)) + { + /** + There was no data sent from the master, so there is + nothing to unpack. + */ + *current_row_end= pack_ptr; + *master_reclength= 0; + DBUG_RETURN(0); + } DBUG_ASSERT(null_ptr < row_data + master_null_byte_count); // Mask to mask out the correct bit among the null bits @@ -217,18 +227,18 @@ unpack_row(Relay_log_info const *rli, uint i= 0; table_def *tabledef= NULL; TABLE *conv_table= NULL; - bool table_found= rli && rli->get_table_data(table, &tabledef, &conv_table); + bool table_found= rgi && rgi->get_table_data(table, &tabledef, &conv_table); DBUG_PRINT("debug", ("Table data: table_found: %d, tabldef: %p, conv_table: %p", table_found, tabledef, conv_table)); DBUG_ASSERT(table_found); /* - If rli is NULL it means that there is no source table and that the + If rgi is NULL it means that there is no source table and that the row shall just be unpacked without doing any checks. This feature is used by MySQL Backup, but can be used for other purposes as well. */ - if (rli && !table_found) + if (rgi && !table_found) DBUG_RETURN(HA_ERR_GENERIC); for (field_ptr= begin_ptr ; field_ptr < end_ptr && *field_ptr ; ++field_ptr) @@ -289,9 +299,12 @@ unpack_row(Relay_log_info const *rli, } else { + THD *thd= f->table->in_use; + f->set_default(); - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_BAD_NULL_ERROR, ER(ER_BAD_NULL_ERROR), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_BAD_NULL_ERROR, + ER_THD(thd, ER_BAD_NULL_ERROR), f->field_name); } } @@ -305,9 +318,8 @@ unpack_row(Relay_log_info const *rli, normal unpack operation. */ uint16 const metadata= tabledef->field_metadata(i); -#ifndef DBUG_OFF uchar const *const old_pack_ptr= pack_ptr; -#endif + pack_ptr= f->unpack(f->ptr, pack_ptr, row_end, metadata); DBUG_PRINT("debug", ("field: %s; metadata: 0x%x;" " pack_ptr: 0x%lx; pack_ptr': 0x%lx; bytes: %d", @@ -316,11 +328,28 @@ unpack_row(Relay_log_info const *rli, (int) (pack_ptr - old_pack_ptr))); if (!pack_ptr) { - rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT, + if (WSREP_ON) + { + /* + Debug message to troubleshoot bug: + https://mariadb.atlassian.net/browse/MDEV-4404 + Galera Node throws "Could not read field" error and drops out of cluster + */ + WSREP_WARN("ROW event unpack field: %s metadata: 0x%x;" + " pack_ptr: 0x%lx; conv_table %p conv_field %p table %s" + " row_end: 0x%lx", + f->field_name, metadata, + (ulong) old_pack_ptr, conv_table, conv_field, + (table_found) ? "found" : "not found", (ulong)row_end + ); + } + + rgi->rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT, + rgi->gtid_info(), "Could not read field '%s' of table '%s.%s'", f->field_name, table->s->db.str, table->s->table_name.str); - DBUG_RETURN(ER_SLAVE_CORRUPT_EVENT); + DBUG_RETURN(HA_ERR_CORRUPT_EVENT); } } @@ -365,7 +394,7 @@ unpack_row(Relay_log_info const *rli, /* throw away master's extra fields */ - uint max_cols= min(tabledef->size(), cols->n_bits); + uint max_cols= MY_MIN(tabledef->size(), cols->n_bits); for (; i < max_cols; i++) { if (bitmap_is_set(cols, i)) @@ -388,6 +417,12 @@ unpack_row(Relay_log_info const *rli, } /* + Add Extra slave persistent columns + */ + if (int error= fill_extra_persistent_columns(table, cols->n_bits)) + DBUG_RETURN(error); + + /* We should now have read all the null bytes, otherwise something is really wrong. */ @@ -404,7 +439,7 @@ unpack_row(Relay_log_info const *rli, *master_reclength = table->s->reclength; } - DBUG_RETURN(error); + DBUG_RETURN(0); } /** @@ -448,16 +483,42 @@ int prepare_record(TABLE *const table, const uint skip, const bool check) if ((f->flags & NO_DEFAULT_VALUE_FLAG) && (f->real_type() != MYSQL_TYPE_ENUM)) { + THD *thd= f->table->in_use; f->set_default(); - push_warning_printf(current_thd, - MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_WARN, ER_NO_DEFAULT_FOR_FIELD, - ER(ER_NO_DEFAULT_FOR_FIELD), + ER_THD(thd, ER_NO_DEFAULT_FOR_FIELD), f->field_name); } } DBUG_RETURN(0); } +/** + Fills @c table->record[0] with computed values of extra persistent column which are present on slave but not on master. + @param table Table whose record[0] buffer is prepared. + @param master_cols No of columns on master + @returns 0 on success + */ +int fill_extra_persistent_columns(TABLE *table, int master_cols) +{ + int error= 0; + Field **vfield_ptr, *vfield; + if (!table->vfield) + return 0; + for (vfield_ptr= table->vfield; *vfield_ptr; ++vfield_ptr) + { + vfield= *vfield_ptr; + if (vfield->field_index >= master_cols && vfield->stored_in_db) + { + /*Set bitmap for writing*/ + bitmap_set_bit(table->vcol_set, vfield->field_index); + error= vfield->vcol_info->expr_item->save_in_field(vfield,0); + bitmap_clear_bit(table->vcol_set, vfield->field_index); + } + } + return error; +} #endif // HAVE_REPLICATION diff --git a/sql/rpl_record.h b/sql/rpl_record.h index d4eb8986846..be69716d9d5 100644 --- a/sql/rpl_record.h +++ b/sql/rpl_record.h @@ -20,7 +20,7 @@ #include <rpl_reporting.h> #include "my_global.h" /* uchar */ -class Relay_log_info; +struct rpl_group_info; struct TABLE; typedef struct st_bitmap MY_BITMAP; @@ -30,7 +30,7 @@ size_t pack_row(TABLE* table, MY_BITMAP const* cols, #endif #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -int unpack_row(Relay_log_info const *rli, +int unpack_row(rpl_group_info *rgi, TABLE *table, uint const colcnt, uchar const *const row_data, MY_BITMAP const *cols, uchar const **const curr_row_end, ulong *const master_reclength, @@ -38,6 +38,7 @@ int unpack_row(Relay_log_info const *rli, // Fill table's record[0] with default values. int prepare_record(TABLE *const table, const uint skip, const bool check); +int fill_extra_persistent_columns(TABLE *table, int master_cols); #endif #endif diff --git a/sql/rpl_record_old.cc b/sql/rpl_record_old.cc index fa0c49b413c..5b876373b9c 100644 --- a/sql/rpl_record_old.cc +++ b/sql/rpl_record_old.cc @@ -13,8 +13,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED by other includes #include "rpl_rli.h" #include "rpl_record_old.h" #include "log_event.h" // Log_event_type @@ -88,7 +88,7 @@ pack_row_old(TABLE *table, MY_BITMAP const* cols, */ #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) int -unpack_row_old(Relay_log_info *rli, +unpack_row_old(rpl_group_info *rgi, TABLE *table, uint const colcnt, uchar *record, uchar const *row, const uchar *row_buffer_end, MY_BITMAP const *cols, @@ -141,7 +141,7 @@ unpack_row_old(Relay_log_info *rli, f->move_field_offset(-offset); if (!ptr) { - rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT, + rgi->rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT, NULL, "Could not read field `%s` of table `%s`.`%s`", f->field_name, table->s->db.str, table->s->table_name.str); @@ -183,7 +183,7 @@ unpack_row_old(Relay_log_info *rli, if (event_type == WRITE_ROWS_EVENT && ((*field_ptr)->flags & mask) == mask) { - rli->report(ERROR_LEVEL, ER_NO_DEFAULT_FOR_FIELD, + rgi->rli->report(ERROR_LEVEL, ER_NO_DEFAULT_FOR_FIELD, NULL, "Field `%s` of table `%s`.`%s` " "has no default value and cannot be NULL", (*field_ptr)->field_name, table->s->db.str, diff --git a/sql/rpl_record_old.h b/sql/rpl_record_old.h index ea981fb23c3..34ef9f11c47 100644 --- a/sql/rpl_record_old.h +++ b/sql/rpl_record_old.h @@ -23,7 +23,7 @@ size_t pack_row_old(TABLE *table, MY_BITMAP const* cols, uchar *row_data, const uchar *record); #ifdef HAVE_REPLICATION -int unpack_row_old(Relay_log_info *rli, +int unpack_row_old(rpl_group_info *rgi, TABLE *table, uint const colcnt, uchar *record, uchar const *row, uchar const *row_buffer_end, MY_BITMAP const *cols, diff --git a/sql/rpl_reporting.cc b/sql/rpl_reporting.cc index ebb0dbec1cb..ad949402511 100644 --- a/sql/rpl_reporting.cc +++ b/sql/rpl_reporting.cc @@ -14,6 +14,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "rpl_reporting.h" #include "log.h" // sql_print_error, sql_print_warning, @@ -28,6 +29,7 @@ Slave_reporting_capability::Slave_reporting_capability(char const *thread_name) void Slave_reporting_capability::report(loglevel level, int err_code, + const char *extra_info, const char *msg, ...) const { void (*report_function)(const char *, ...); @@ -68,9 +70,10 @@ Slave_reporting_capability::report(loglevel level, int err_code, va_end(args); /* If the msg string ends with '.', do not add a ',' it would be ugly */ - report_function("Slave %s: %s%s Error_code: %d", + report_function("Slave %s: %s%s %s%sInternal MariaDB error code: %d", m_thread_name, pbuff, (pbuff[0] && *(strend(pbuff)-1) == '.') ? "" : ",", + (extra_info ? extra_info : ""), (extra_info ? ", " : ""), err_code); } diff --git a/sql/rpl_reporting.h b/sql/rpl_reporting.h index 2b5e0527b9b..d90b7ad6650 100644 --- a/sql/rpl_reporting.h +++ b/sql/rpl_reporting.h @@ -52,8 +52,9 @@ public: code, but can contain more information), in printf() format. */ - void report(loglevel level, int err_code, const char *msg, ...) const - ATTRIBUTE_FORMAT(printf, 4, 5); + void report(loglevel level, int err_code, const char *extra_info, + const char *msg, ...) const + ATTRIBUTE_FORMAT(printf, 5, 6); /** Clear errors. They will not show up under <code>SHOW SLAVE diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 52e60c237dc..b35130c1505 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -11,9 +11,10 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" // HAVE_* #include "rpl_mi.h" @@ -32,6 +33,15 @@ static int count_relay_log_space(Relay_log_info* rli); +/** + Current replication state (hash of last GTID executed, per replication + domain). +*/ +rpl_slave_state *rpl_global_gtid_slave_state; +/* Object used for MASTER_GTID_WAIT(). */ +gtid_waiting rpl_global_gtid_waiting; + + // Defined in slave.cc int init_intvar_from_file(int* var, IO_CACHE* f, int default_val); int init_strvar_from_file(char *var, int max_size, IO_CACHE *f, @@ -42,24 +52,24 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery) no_storage(FALSE), replicate_same_server_id(::replicate_same_server_id), info_fd(-1), cur_log_fd(-1), relay_log(&sync_relaylog_period), sync_counter(0), is_relay_log_recovery(is_slave_recovery), - save_temporary_tables(0), cur_log_old_open_count(0), - error_on_rli_init_info(false), group_relay_log_pos(0), - event_relay_log_pos(0), + save_temporary_tables(0), mi(0), + inuse_relaylog_list(0), last_inuse_relaylog(0), + cur_log_old_open_count(0), error_on_rli_init_info(false), + group_relay_log_pos(0), event_relay_log_pos(0), #if HAVE_valgrind is_fake(FALSE), #endif group_master_log_pos(0), log_space_total(0), ignore_log_space_limit(0), - last_master_timestamp(0), slave_skip_counter(0), - abort_pos_wait(0), slave_run_id(0), sql_thd(0), - inited(0), abort_slave(0), slave_running(0), until_condition(UNTIL_NONE), - until_log_pos(0), retried_trans(0), - tables_to_lock(0), tables_to_lock_count(0), - last_event_start_time(0), deferred_events(NULL),m_flags(0), - row_stmt_start_timestamp(0), long_find_row_note_printed(false), - m_annotate_event(0) + last_master_timestamp(0), sql_thread_caught_up(true), slave_skip_counter(0), + abort_pos_wait(0), slave_run_id(0), sql_driver_thd(), + gtid_skip_flag(GTID_SKIP_NOT), inited(0), abort_slave(0), stop_for_until(0), + slave_running(MYSQL_SLAVE_NOT_RUN), until_condition(UNTIL_NONE), + until_log_pos(0), retried_trans(0), executed_entries(0), + m_flags(0) { DBUG_ENTER("Relay_log_info::Relay_log_info"); + relay_log.is_relay_log= TRUE; #ifdef HAVE_PSI_INTERFACE relay_log.set_psi_keys(key_RELAYLOG_LOCK_index, key_RELAYLOG_update_cond, @@ -71,20 +81,18 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery) group_relay_log_name[0]= event_relay_log_name[0]= group_master_log_name[0]= 0; until_log_name[0]= ign_master_log_name_end[0]= 0; + max_relay_log_size= global_system_variables.max_relay_log_size; bzero((char*) &info_file, sizeof(info_file)); bzero((char*) &cache_buf, sizeof(cache_buf)); - cached_charset_invalidate(); mysql_mutex_init(key_relay_log_info_run_lock, &run_lock, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_relay_log_info_data_lock, &data_lock, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_relay_log_info_log_space_lock, &log_space_lock, MY_MUTEX_INIT_FAST); - mysql_mutex_init(key_relay_log_info_sleep_lock, &sleep_lock, MY_MUTEX_INIT_FAST); mysql_cond_init(key_relay_log_info_data_cond, &data_cond, NULL); mysql_cond_init(key_relay_log_info_start_cond, &start_cond, NULL); mysql_cond_init(key_relay_log_info_stop_cond, &stop_cond, NULL); mysql_cond_init(key_relay_log_info_log_space_cond, &log_space_cond, NULL); - mysql_cond_init(key_relay_log_info_sleep_cond, &sleep_cond, NULL); relay_log.init_pthread_objects(); DBUG_VOID_RETURN; } @@ -94,17 +102,15 @@ Relay_log_info::~Relay_log_info() { DBUG_ENTER("Relay_log_info::~Relay_log_info"); + reset_inuse_relaylog(); mysql_mutex_destroy(&run_lock); mysql_mutex_destroy(&data_lock); mysql_mutex_destroy(&log_space_lock); - mysql_mutex_destroy(&sleep_lock); mysql_cond_destroy(&data_cond); mysql_cond_destroy(&start_cond); mysql_cond_destroy(&stop_cond); mysql_cond_destroy(&log_space_cond); - mysql_cond_destroy(&sleep_cond); relay_log.cleanup(); - free_annotate_event(); DBUG_VOID_RETURN; } @@ -116,11 +122,14 @@ int init_relay_log_info(Relay_log_info* rli, int info_fd= -1; const char* msg = 0; int error = 0; + mysql_mutex_t *log_lock; DBUG_ENTER("init_relay_log_info"); DBUG_ASSERT(!rli->no_storage); // Don't init if there is no storage if (rli->inited) // Set if this function called DBUG_RETURN(0); + + log_lock= rli->relay_log.get_log_lock(); fn_format(fname, info_fname, mysql_data_home, "", 4+32); mysql_mutex_lock(&rli->data_lock); if (rli->error_on_rli_init_info) @@ -131,8 +140,6 @@ int init_relay_log_info(Relay_log_info* rli, rli->abort_pos_wait=0; rli->log_space_limit= relay_log_space_limit; rli->log_space_total= 0; - rli->tables_to_lock= 0; - rli->tables_to_lock_count= 0; char pattern[FN_REFLEN]; (void) my_realpath(pattern, slave_load_tmpdir, 0); @@ -153,15 +160,6 @@ int init_relay_log_info(Relay_log_info* rli, event, in flush_master_info(mi, 1, ?). */ - /* - For the maximum log size, we choose max_relay_log_size if it is - non-zero, max_binlog_size otherwise. If later the user does SET - GLOBAL on one of these variables, fix_max_binlog_size and - fix_max_relay_log_size will reconsider the choice (for example - if the user changes max_relay_log_size to zero, we have to - switch to using max_binlog_size for the relay log) and update - rli->relay_log.max_size (and mysql_bin_log.max_size). - */ { /* Reports an error and returns, if the --relay-log's path is a directory.*/ @@ -211,21 +209,40 @@ a file name for --relay-log-index option", opt_relaylog_index_name); name_warning_sent= 1; } - rli->relay_log.is_relay_log= TRUE; + /* For multimaster, add connection name to relay log filenames */ + Master_info* mi= rli->mi; + char buf_relay_logname[FN_REFLEN], buf_relaylog_index_name_buff[FN_REFLEN]; + char *buf_relaylog_index_name= opt_relaylog_index_name; + + create_logfile_name_with_suffix(buf_relay_logname, + sizeof(buf_relay_logname), + ln, 1, &mi->cmp_connection_name); + ln= buf_relay_logname; + + if (opt_relaylog_index_name) + { + buf_relaylog_index_name= buf_relaylog_index_name_buff; + create_logfile_name_with_suffix(buf_relaylog_index_name_buff, + sizeof(buf_relaylog_index_name_buff), + opt_relaylog_index_name, 0, + &mi->cmp_connection_name); + } /* note, that if open() fails, we'll still have index file open but a destructor will take care of that */ - if (rli->relay_log.open_index_file(opt_relaylog_index_name, ln, TRUE) || - rli->relay_log.open(ln, LOG_BIN, 0, SEQ_READ_APPEND, 0, - (max_relay_log_size ? max_relay_log_size : - max_binlog_size), 1, TRUE)) + mysql_mutex_lock(log_lock); + if (rli->relay_log.open_index_file(buf_relaylog_index_name, ln, TRUE) || + rli->relay_log.open(ln, LOG_BIN, 0, 0, SEQ_READ_APPEND, + mi->rli.max_relay_log_size, 1, TRUE)) { + mysql_mutex_unlock(log_lock); mysql_mutex_unlock(&rli->data_lock); - sql_print_error("Failed in open_log() called from init_relay_log_info()"); + sql_print_error("Failed when trying to open logs for '%s' in init_relay_log_info(). Error: %M", ln, my_errno); DBUG_RETURN(1); } + mysql_mutex_unlock(log_lock); } /* if file does not exist */ @@ -240,9 +257,9 @@ a file name for --relay-log-index option", opt_relaylog_index_name); if ((info_fd= mysql_file_open(key_file_relay_log_info, fname, O_CREAT|O_RDWR|O_BINARY, MYF(MY_WME))) < 0) { - sql_print_error("Failed to create a new relay log info file (\ -file '%s', errno %d)", fname, my_errno); - msg= current_thd->stmt_da->message(); + sql_print_error("Failed to create a new relay log info file (" + "file '%s', errno %d)", fname, my_errno); + msg= current_thd->get_stmt_da()->message(); goto err; } if (init_io_cache(&rli->info_file, info_fd, IO_SIZE*2, READ_CACHE, 0L,0, @@ -250,7 +267,7 @@ file '%s', errno %d)", fname, my_errno); { sql_print_error("Failed to create a cache on relay log info file '%s'", fname); - msg= current_thd->stmt_da->message(); + msg= current_thd->get_stmt_da()->message(); goto err; } @@ -292,27 +309,89 @@ Failed to open the existing relay log info file '%s' (errno %d)", if (info_fd >= 0) mysql_file_close(info_fd, MYF(0)); rli->info_fd= -1; + mysql_mutex_lock(log_lock); rli->relay_log.close(LOG_CLOSE_INDEX | LOG_CLOSE_STOP_EVENT); + mysql_mutex_unlock(log_lock); mysql_mutex_unlock(&rli->data_lock); DBUG_RETURN(1); } } rli->info_fd = info_fd; - int relay_log_pos, master_log_pos; + int relay_log_pos, master_log_pos, lines; + char *first_non_digit; + /* + In MySQL 5.6, there is a MASTER_DELAY option to CHANGE MASTER. This is + not yet merged into MariaDB (as of 10.0.13). However, we detect the + presense of the new option in relay-log.info, as a placeholder for + possible later merge of the feature, and to maintain file format + compatibility with MySQL 5.6+. + */ + int dummy_sql_delay; + + /* + Starting from MySQL 5.6.x, relay-log.info has a new format. + Now, its first line contains the number of lines in the file. + By reading this number we can determine which version our master.info + comes from. We can't simply count the lines in the file, since + versions before 5.6.x could generate files with more lines than + needed. If first line doesn't contain a number, or if it + contains a number less than LINES_IN_RELAY_LOG_INFO_WITH_DELAY, + then the file is treated like a file from pre-5.6.x version. + There is no ambiguity when reading an old master.info: before + 5.6.x, the first line contained the binlog's name, which is + either empty or has an extension (contains a '.'), so can't be + confused with an integer. + + So we're just reading first line and trying to figure which + version is this. + */ + + /* + The first row is temporarily stored in mi->master_log_name, if + it is line count and not binlog name (new format) it will be + overwritten by the second row later. + */ if (init_strvar_from_file(rli->group_relay_log_name, sizeof(rli->group_relay_log_name), + &rli->info_file, "")) + { + msg="Error reading slave log configuration"; + goto err; + } + + lines= strtoul(rli->group_relay_log_name, &first_non_digit, 10); + + if (rli->group_relay_log_name[0] != '\0' && + *first_non_digit == '\0' && + lines >= LINES_IN_RELAY_LOG_INFO_WITH_DELAY) + { + DBUG_PRINT("info", ("relay_log_info file is in new format.")); + /* Seems to be new format => read relay log name from next line */ + if (init_strvar_from_file(rli->group_relay_log_name, + sizeof(rli->group_relay_log_name), + &rli->info_file, "")) + { + msg="Error reading slave log configuration"; + goto err; + } + } + else + DBUG_PRINT("info", ("relay_log_info file is in old format.")); + + if (init_intvar_from_file(&relay_log_pos, + &rli->info_file, BIN_LOG_HEADER_SIZE) || + init_strvar_from_file(rli->group_master_log_name, + sizeof(rli->group_master_log_name), &rli->info_file, "") || - init_intvar_from_file(&relay_log_pos, - &rli->info_file, BIN_LOG_HEADER_SIZE) || - init_strvar_from_file(rli->group_master_log_name, - sizeof(rli->group_master_log_name), - &rli->info_file, "") || - init_intvar_from_file(&master_log_pos, &rli->info_file, 0)) + init_intvar_from_file(&master_log_pos, &rli->info_file, 0) || + (lines >= LINES_IN_RELAY_LOG_INFO_WITH_DELAY && + init_intvar_from_file(&dummy_sql_delay, &rli->info_file, 0))) { msg="Error reading slave log configuration"; goto err; } + strmake_buf(rli->event_relay_log_name,rli->group_relay_log_name); rli->group_relay_log_pos= rli->event_relay_log_pos= relay_log_pos; rli->group_master_log_pos= master_log_pos; @@ -320,30 +399,23 @@ Failed to open the existing relay log info file '%s' (errno %d)", if (rli->is_relay_log_recovery && init_recovery(rli->mi, &msg)) goto err; + rli->relay_log_state.load(rpl_global_gtid_slave_state); if (init_relay_log_pos(rli, rli->group_relay_log_name, rli->group_relay_log_pos, 0 /* no data lock*/, &msg, 0)) { - char llbuf[22]; - sql_print_error("Failed to open the relay log '%s' (relay_log_pos %s)", - rli->group_relay_log_name, - llstr(rli->group_relay_log_pos, llbuf)); + sql_print_error("Failed to open the relay log '%s' (relay_log_pos %llu)", + rli->group_relay_log_name, rli->group_relay_log_pos); goto err; } } -#ifndef DBUG_OFF - { - char llbuf1[22], llbuf2[22]; - DBUG_PRINT("info", ("my_b_tell(rli->cur_log)=%s rli->event_relay_log_pos=%s", - llstr(my_b_tell(rli->cur_log),llbuf1), - llstr(rli->event_relay_log_pos,llbuf2))); - DBUG_ASSERT(rli->event_relay_log_pos >= BIN_LOG_HEADER_SIZE); - DBUG_ASSERT(my_b_tell(rli->cur_log) == rli->event_relay_log_pos); - } -#endif + DBUG_PRINT("info", ("my_b_tell(rli->cur_log)=%llu rli->event_relay_log_pos=%llu", + my_b_tell(rli->cur_log), rli->event_relay_log_pos)); + DBUG_ASSERT(rli->event_relay_log_pos >= BIN_LOG_HEADER_SIZE); + DBUG_ASSERT(my_b_tell(rli->cur_log) == rli->event_relay_log_pos); /* Now change the cache from READ to WRITE - must do this @@ -363,7 +435,7 @@ Failed to open the existing relay log info file '%s' (errno %d)", rli->inited= 1; rli->error_on_rli_init_info= false; mysql_mutex_unlock(&rli->data_lock); - DBUG_RETURN(error); + DBUG_RETURN(0); err: rli->error_on_rli_init_info= true; @@ -373,8 +445,12 @@ err: if (info_fd >= 0) mysql_file_close(info_fd, MYF(0)); rli->info_fd= -1; + + mysql_mutex_lock(log_lock); rli->relay_log.close(LOG_CLOSE_INDEX | LOG_CLOSE_STOP_EVENT); + mysql_mutex_unlock(log_lock); mysql_mutex_unlock(&rli->data_lock); + DBUG_RETURN(1); } @@ -391,10 +467,7 @@ static inline int add_relay_log(Relay_log_info* rli,LOG_INFO* linfo) DBUG_RETURN(1); } rli->log_space_total += s.st_size; -#ifndef DBUG_OFF - char buf[22]; - DBUG_PRINT("info",("log_space_total: %s", llstr(rli->log_space_total,buf))); -#endif + DBUG_PRINT("info",("log_space_total: %llu", rli->log_space_total)); DBUG_RETURN(0); } @@ -444,6 +517,104 @@ void Relay_log_info::clear_until_condition() /* + Read the correct format description event for starting to replicate from + a given position in a relay log file. +*/ +Format_description_log_event * +read_relay_log_description_event(IO_CACHE *cur_log, ulonglong start_pos, + const char **errmsg) +{ + Log_event *ev; + Format_description_log_event *fdev; + bool found= false; + + /* + By default the relay log is in binlog format 3 (4.0). + Even if format is 4, this will work enough to read the first event + (Format_desc) (remember that format 4 is just lenghtened compared to format + 3; format 3 is a prefix of format 4). + */ + fdev= new Format_description_log_event(3); + + while (!found) + { + Log_event_type typ; + + /* + Read the possible Format_description_log_event; if position + was 4, no need, it will be read naturally. + */ + DBUG_PRINT("info",("looking for a Format_description_log_event")); + + if (my_b_tell(cur_log) >= start_pos) + break; + + if (!(ev= Log_event::read_log_event(cur_log, 0, fdev, + opt_slave_sql_verify_checksum))) + { + DBUG_PRINT("info",("could not read event, cur_log->error=%d", + cur_log->error)); + if (cur_log->error) /* not EOF */ + { + *errmsg= "I/O error reading event at position 4"; + delete fdev; + return NULL; + } + break; + } + typ= ev->get_type_code(); + if (typ == FORMAT_DESCRIPTION_EVENT) + { + Format_description_log_event *old= fdev; + DBUG_PRINT("info",("found Format_description_log_event")); + fdev= (Format_description_log_event*) ev; + fdev->copy_crypto_data(old); + delete old; + + /* + As ev was returned by read_log_event, it has passed is_valid(), so + my_malloc() in ctor worked, no need to check again. + */ + /* + Ok, we found a Format_description event. But it is not sure that this + describes the whole relay log; indeed, one can have this sequence + (starting from position 4): + Format_desc (of slave) + Rotate (of master) + Format_desc (of master) + So the Format_desc which really describes the rest of the relay log + is the 3rd event (it can't be further than that, because we rotate + the relay log when we queue a Rotate event from the master). + But what describes the Rotate is the first Format_desc. + So what we do is: + go on searching for Format_description events, until you exceed the + position (argument 'pos') or until you find another event than Rotate + or Format_desc. + */ + } + else if (typ == START_ENCRYPTION_EVENT) + { + if (fdev->start_decryption((Start_encryption_log_event*) ev)) + { + *errmsg= "Unable to set up decryption of binlog."; + delete ev; + delete fdev; + return NULL; + } + delete ev; + } + else + { + DBUG_PRINT("info",("found event of another type=%d", typ)); + found= (typ != ROTATE_EVENT); + delete ev; + } + } + return fdev; +} + + +/* Open the given relay log SYNOPSIS @@ -518,6 +689,8 @@ int init_relay_log_pos(Relay_log_info* rli,const char* log, } rli->group_relay_log_pos = rli->event_relay_log_pos = pos; + rli->clear_flag(Relay_log_info::IN_STMT); + rli->clear_flag(Relay_log_info::IN_TRANSACTION); /* Test to see if the previous run was with the skip of purging @@ -564,78 +737,17 @@ int init_relay_log_pos(Relay_log_info* rli,const char* log, */ if (pos > BIN_LOG_HEADER_SIZE) /* If pos<=4, we stay at 4 */ { - Log_event* ev; - while (look_for_description_event) + if (look_for_description_event) { - /* - Read the possible Format_description_log_event; if position - was 4, no need, it will be read naturally. - */ - DBUG_PRINT("info",("looking for a Format_description_log_event")); - - if (my_b_tell(rli->cur_log) >= pos) - break; - - /* - Because of we have rli->data_lock and log_lock, we can safely read an - event - */ - if (!(ev= Log_event::read_log_event(rli->cur_log, 0, - rli->relay_log.description_event_for_exec, - opt_slave_sql_verify_checksum))) - { - DBUG_PRINT("info",("could not read event, rli->cur_log->error=%d", - rli->cur_log->error)); - if (rli->cur_log->error) /* not EOF */ - { - *errmsg= "I/O error reading event at position 4"; - goto err; - } - break; - } - else if (ev->get_type_code() == FORMAT_DESCRIPTION_EVENT) - { - DBUG_PRINT("info",("found Format_description_log_event")); - delete rli->relay_log.description_event_for_exec; - rli->relay_log.description_event_for_exec= (Format_description_log_event*) ev; - /* - As ev was returned by read_log_event, it has passed is_valid(), so - my_malloc() in ctor worked, no need to check again. - */ - /* - Ok, we found a Format_description event. But it is not sure that this - describes the whole relay log; indeed, one can have this sequence - (starting from position 4): - Format_desc (of slave) - Rotate (of master) - Format_desc (of master) - So the Format_desc which really describes the rest of the relay log - is the 3rd event (it can't be further than that, because we rotate - the relay log when we queue a Rotate event from the master). - But what describes the Rotate is the first Format_desc. - So what we do is: - go on searching for Format_description events, until you exceed the - position (argument 'pos') or until you find another event than Rotate - or Format_desc. - */ - } - else - { - DBUG_PRINT("info",("found event of another type=%d", - ev->get_type_code())); - look_for_description_event= (ev->get_type_code() == ROTATE_EVENT); - delete ev; - } + Format_description_log_event *fdev; + if (!(fdev= read_relay_log_description_event(rli->cur_log, pos, errmsg))) + goto err; + delete rli->relay_log.description_event_for_exec; + rli->relay_log.description_event_for_exec= fdev; } my_b_seek(rli->cur_log,(off_t)pos); -#ifndef DBUG_OFF - { - char llbuf1[22], llbuf2[22]; - DBUG_PRINT("info", ("my_b_tell(rli->cur_log)=%s rli->event_relay_log_pos=%s", - llstr(my_b_tell(rli->cur_log),llbuf1), - llstr(rli->event_relay_log_pos,llbuf2))); - } -#endif + DBUG_PRINT("info", ("my_b_tell(rli->cur_log)=%llu rli->event_relay_log_pos=%llu", + my_b_tell(rli->cur_log), rli->event_relay_log_pos)); } @@ -692,7 +804,7 @@ int Relay_log_info::wait_for_pos(THD* thd, String* log_name, ulong init_abort_pos_wait; int error=0; struct timespec abstime; // for timeout checking - const char *msg; + PSI_stage_info old_stage; DBUG_ENTER("Relay_log_info::wait_for_pos"); if (!inited) @@ -703,9 +815,9 @@ int Relay_log_info::wait_for_pos(THD* thd, String* log_name, set_timespec(abstime,timeout); mysql_mutex_lock(&data_lock); - msg= thd->enter_cond(&data_cond, &data_lock, - "Waiting for the slave SQL thread to " - "advance position"); + thd->ENTER_COND(&data_cond, &data_lock, + &stage_waiting_for_the_slave_thread_to_advance_position, + &old_stage); /* This function will abort when it notices that some CHANGE MASTER or RESET MASTER has changed the master info. @@ -730,7 +842,7 @@ int Relay_log_info::wait_for_pos(THD* thd, String* log_name, ulong log_name_extension; char log_name_tmp[FN_REFLEN]; //make a char[] from String - strmake(log_name_tmp, log_name->ptr(), min(log_name->length(), FN_REFLEN-1)); + strmake(log_name_tmp, log_name->ptr(), MY_MIN(log_name->length(), FN_REFLEN-1)); char *p= fn_ext(log_name_tmp); char *p_end; @@ -740,7 +852,7 @@ int Relay_log_info::wait_for_pos(THD* thd, String* log_name, goto err; } // Convert 0-3 to 4 - log_pos= max(log_pos, BIN_LOG_HEADER_SIZE); + log_pos= MY_MAX(log_pos, BIN_LOG_HEADER_SIZE); /* p points to '.' */ log_name_extension= strtoul(++p, &p_end, 10); /* @@ -849,7 +961,7 @@ int Relay_log_info::wait_for_pos(THD* thd, String* log_name, } err: - thd->exit_cond(msg); + thd->EXIT_COND(&old_stage); DBUG_PRINT("exit",("killed: %d abort: %d slave_running: %d \ improper_arguments: %d timed_out: %d", thd->killed_errno(), @@ -867,17 +979,65 @@ improper_arguments: %d timed_out: %d", void Relay_log_info::inc_group_relay_log_pos(ulonglong log_pos, - bool skip_lock) + rpl_group_info *rgi, + bool skip_lock) { DBUG_ENTER("Relay_log_info::inc_group_relay_log_pos"); if (!skip_lock) mysql_mutex_lock(&data_lock); - inc_event_relay_log_pos(); - group_relay_log_pos= event_relay_log_pos; - strmake_buf(group_relay_log_name,event_relay_log_name); + rgi->inc_event_relay_log_pos(); + DBUG_PRINT("info", ("log_pos: %lu group_master_log_pos: %lu", + (long) log_pos, (long) group_master_log_pos)); + if (rgi->is_parallel_exec) + { + /* In case of parallel replication, do not update the position backwards. */ + int cmp= strcmp(group_relay_log_name, rgi->event_relay_log_name); + if (cmp < 0) + { + group_relay_log_pos= rgi->future_event_relay_log_pos; + strmake_buf(group_relay_log_name, rgi->event_relay_log_name); + notify_group_relay_log_name_update(); + } else if (cmp == 0 && group_relay_log_pos < rgi->future_event_relay_log_pos) + group_relay_log_pos= rgi->future_event_relay_log_pos; + + /* + In the parallel case we need to update the master_log_name here, rather + than in Rotate_log_event::do_update_pos(). + */ + cmp= strcmp(group_master_log_name, rgi->future_event_master_log_name); + if (cmp <= 0) + { + if (cmp < 0) + { + strcpy(group_master_log_name, rgi->future_event_master_log_name); + group_master_log_pos= log_pos; + } + else if (group_master_log_pos < log_pos) + group_master_log_pos= log_pos; + } - notify_group_relay_log_name_update(); + /* + In the parallel case, we only update the Seconds_Behind_Master at the + end of a transaction. In the non-parallel case, the value is updated as + soon as an event is read from the relay log; however this would be too + confusing for the user, seeing the slave reported as up-to-date when + potentially thousands of events are still queued up for worker threads + waiting for execution. + */ + if (rgi->last_master_timestamp && + rgi->last_master_timestamp > last_master_timestamp) + last_master_timestamp= rgi->last_master_timestamp; + } + else + { + /* Non-parallel case. */ + group_relay_log_pos= event_relay_log_pos; + strmake_buf(group_relay_log_name, event_relay_log_name); + notify_group_relay_log_name_update(); + if (log_pos) // not 3.23 binlogs (no log_pos there) and not Stop_log_event + group_master_log_pos= log_pos; + } /* If the slave does not support transactions and replicates a transaction, @@ -909,12 +1069,6 @@ void Relay_log_info::inc_group_relay_log_pos(ulonglong log_pos, the relay log is not "val". With the end_log_pos solution, we avoid computations involving lengthes. */ - DBUG_PRINT("info", ("log_pos: %lu group_master_log_pos: %lu", - (long) log_pos, (long) group_master_log_pos)); - if (log_pos) // 3.23 binlogs don't have log_posx - { - group_master_log_pos= log_pos; - } mysql_cond_broadcast(&data_cond); if (!skip_lock) mysql_mutex_unlock(&data_lock); @@ -930,6 +1084,9 @@ void Relay_log_info::close_temporary_tables() for (table=save_temporary_tables ; table ; table=next) { next=table->next; + + /* Reset in_use as the table may have been created by another thd */ + table->in_use=0; /* Don't ask for disk deletion. For now, anyway they will be deleted when slave restarts, but it is a better intention to not delete them. @@ -945,6 +1102,9 @@ void Relay_log_info::close_temporary_tables() /* purge_relay_logs() + @param rli Relay log information + @param thd thread id. May be zero during startup + NOTES Assumes to have a run lock on rli and that no slave thread are running. */ @@ -992,14 +1152,17 @@ int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset, "log index file:%s.", rli->relay_log.get_index_fname()); DBUG_RETURN(1); } - if (rli->relay_log.open(ln, LOG_BIN, 0, SEQ_READ_APPEND, 0, - (max_relay_log_size ? max_relay_log_size : + mysql_mutex_lock(rli->relay_log.get_log_lock()); + if (rli->relay_log.open(ln, LOG_BIN, 0, 0, SEQ_READ_APPEND, + (rli->max_relay_log_size ? rli->max_relay_log_size : max_binlog_size), 1, TRUE)) { sql_print_error("Unable to purge relay log files. Failed to open relay " "log file:%s.", rli->relay_log.get_log_fname()); + mysql_mutex_unlock(rli->relay_log.get_log_lock()); DBUG_RETURN(1); } + mysql_mutex_unlock(rli->relay_log.get_log_lock()); } else DBUG_RETURN(0); @@ -1009,7 +1172,6 @@ int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset, DBUG_ASSERT(rli->slave_running == 0); DBUG_ASSERT(rli->mi->slave_running == 0); } - rli->slave_skip_counter=0; mysql_mutex_lock(&rli->data_lock); /* @@ -1025,34 +1187,45 @@ int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset, rli->cur_log_fd= -1; } - if (rli->relay_log.reset_logs(thd)) + if (rli->relay_log.reset_logs(thd, !just_reset, NULL, 0, 0)) { *errmsg = "Failed during log reset"; error=1; goto err; } - /* Save name of used relay log file */ - strmake_buf(rli->group_relay_log_name, rli->relay_log.get_log_fname()); - strmake_buf(rli->event_relay_log_name, rli->relay_log.get_log_fname()); - rli->group_relay_log_pos= rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE; - if (count_relay_log_space(rli)) - { - *errmsg= "Error counting relay log space"; - error=1; - goto err; - } + rli->relay_log_state.load(rpl_global_gtid_slave_state); if (!just_reset) + { + /* Save name of used relay log file */ + strmake_buf(rli->group_relay_log_name, rli->relay_log.get_log_fname()); + strmake_buf(rli->event_relay_log_name, rli->relay_log.get_log_fname()); + rli->group_relay_log_pos= rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE; + rli->log_space_total= 0; + + if (count_relay_log_space(rli)) + { + *errmsg= "Error counting relay log space"; + error=1; + goto err; + } error= init_relay_log_pos(rli, rli->group_relay_log_name, rli->group_relay_log_pos, 0 /* do not need data lock */, errmsg, 0); + } + else + { + /* Ensure relay log names are not used */ + rli->group_relay_log_name[0]= rli->event_relay_log_name[0]= 0; + } if (!rli->inited && rli->error_on_rli_init_info) + { + mysql_mutex_lock(rli->relay_log.get_log_lock()); rli->relay_log.close(LOG_CLOSE_INDEX | LOG_CLOSE_STOP_EVENT); + mysql_mutex_unlock(rli->relay_log.get_log_lock()); + } err: -#ifndef DBUG_OFF - char buf[22]; -#endif - DBUG_PRINT("info",("log_space_total: %s",llstr(rli->log_space_total,buf))); + DBUG_PRINT("info",("log_space_total: %llu",rli->log_space_total)); mysql_mutex_unlock(&rli->data_lock); DBUG_RETURN(error); } @@ -1091,44 +1264,35 @@ err: false - condition not met */ -bool Relay_log_info::is_until_satisfied(THD *thd, Log_event *ev) +bool Relay_log_info::is_until_satisfied(my_off_t master_beg_pos) { const char *log_name; ulonglong log_pos; DBUG_ENTER("Relay_log_info::is_until_satisfied"); - DBUG_ASSERT(until_condition != UNTIL_NONE); - if (until_condition == UNTIL_MASTER_POS) { - if (ev && ev->server_id == (uint32) ::server_id && !replicate_same_server_id) - DBUG_RETURN(FALSE); - log_name= group_master_log_name; - log_pos= (!ev)? group_master_log_pos : - ((thd->variables.option_bits & OPTION_BEGIN || !ev->log_pos) ? - group_master_log_pos : ev->log_pos - ev->data_written); + log_name= (mi->using_parallel() ? future_event_master_log_name + : group_master_log_name); + log_pos= master_beg_pos; } else - { /* until_condition == UNTIL_RELAY_POS */ + { + DBUG_ASSERT(until_condition == UNTIL_RELAY_POS); log_name= group_relay_log_name; log_pos= group_relay_log_pos; } -#ifndef DBUG_OFF - { - char buf[32]; - DBUG_PRINT("info", ("group_master_log_name='%s', group_master_log_pos=%s", - group_master_log_name, llstr(group_master_log_pos, buf))); - DBUG_PRINT("info", ("group_relay_log_name='%s', group_relay_log_pos=%s", - group_relay_log_name, llstr(group_relay_log_pos, buf))); - DBUG_PRINT("info", ("(%s) log_name='%s', log_pos=%s", - until_condition == UNTIL_MASTER_POS ? "master" : "relay", - log_name, llstr(log_pos, buf))); - DBUG_PRINT("info", ("(%s) until_log_name='%s', until_log_pos=%s", - until_condition == UNTIL_MASTER_POS ? "master" : "relay", - until_log_name, llstr(until_log_pos, buf))); - } -#endif + DBUG_PRINT("info", ("group_master_log_name='%s', group_master_log_pos=%llu", + group_master_log_name, group_master_log_pos)); + DBUG_PRINT("info", ("group_relay_log_name='%s', group_relay_log_pos=%llu", + group_relay_log_name, group_relay_log_pos)); + DBUG_PRINT("info", ("(%s) log_name='%s', log_pos=%llu", + until_condition == UNTIL_MASTER_POS ? "master" : "relay", + log_name, log_pos)); + DBUG_PRINT("info", ("(%s) until_log_name='%s', until_log_pos=%llu", + until_condition == UNTIL_MASTER_POS ? "master" : "relay", + until_log_name, until_log_pos)); if (until_log_names_cmp_result == UNTIL_LOG_NAMES_CMP_UNKNOWN) { @@ -1175,43 +1339,22 @@ bool Relay_log_info::is_until_satisfied(THD *thd, Log_event *ev) } -void Relay_log_info::cached_charset_invalidate() -{ - DBUG_ENTER("Relay_log_info::cached_charset_invalidate"); - - /* Full of zeroes means uninitialized. */ - bzero(cached_charset, sizeof(cached_charset)); - DBUG_VOID_RETURN; -} - - -bool Relay_log_info::cached_charset_compare(char *charset) const -{ - DBUG_ENTER("Relay_log_info::cached_charset_compare"); - - if (memcmp(cached_charset, charset, sizeof(cached_charset))) - { - memcpy(const_cast<char*>(cached_charset), charset, sizeof(cached_charset)); - DBUG_RETURN(1); - } - DBUG_RETURN(0); -} - - -void Relay_log_info::stmt_done(my_off_t event_master_log_pos, - time_t event_creation_time) +bool Relay_log_info::stmt_done(my_off_t event_master_log_pos, THD *thd, + rpl_group_info *rgi) { -#ifndef DBUG_OFF - extern uint debug_not_change_ts_if_art_event; -#endif - clear_flag(IN_STMT); + int error= 0; + DBUG_ENTER("Relay_log_info::stmt_done"); + DBUG_ASSERT(rgi->rli == this); /* If in a transaction, and if the slave supports transactions, just inc_event_relay_log_pos(). We only have to check for OPTION_BEGIN (not OPTION_NOT_AUTOCOMMIT) as transactions are logged with BEGIN/COMMIT, not with SET AUTOCOMMIT= . + We can't use rgi->rli->get_flag(IN_TRANSACTION) here as OPTION_BEGIN + is also used for single row transactions. + CAUTION: opt_using_transactions means innodb || bdb ; suppose the master supports InnoDB and BDB, but the slave supports only BDB, problems will arise: - suppose an InnoDB table is created on the @@ -1229,32 +1372,417 @@ void Relay_log_info::stmt_done(my_off_t event_master_log_pos, middle of the "transaction". START SLAVE will resume at BEGIN while the MyISAM table has already been updated. */ - if ((sql_thd->variables.option_bits & OPTION_BEGIN) && opt_using_transactions) - inc_event_relay_log_pos(); + if ((rgi->thd->variables.option_bits & OPTION_BEGIN) && + opt_using_transactions) + rgi->inc_event_relay_log_pos(); else { - inc_group_relay_log_pos(event_master_log_pos); - flush_relay_log_info(this); - /* - Note that Rotate_log_event::do_apply_event() does not call this - function, so there is no chance that a fake rotate event resets - last_master_timestamp. Note that we update without mutex - (probably ok - except in some very rare cases, only consequence - is that value may take some time to display in - Seconds_Behind_Master - not critical). - */ - if (!(event_creation_time == 0 && - IF_DBUG(debug_not_change_ts_if_art_event > 0, 1))) - last_master_timestamp= event_creation_time; + inc_group_relay_log_pos(event_master_log_pos, rgi); + if (rpl_global_gtid_slave_state->record_and_update_gtid(thd, rgi)) + { + report(WARNING_LEVEL, ER_CANNOT_UPDATE_GTID_STATE, rgi->gtid_info(), + "Failed to update GTID state in %s.%s, slave state may become " + "inconsistent: %d: %s", + "mysql", rpl_gtid_slave_state_table_name.str, + thd->get_stmt_da()->sql_errno(), thd->get_stmt_da()->message()); + /* + At this point we are not in a transaction (for example after DDL), + so we can not roll back. Anyway, normally updates to the slave + state table should not fail, and if they do, at least we made the + DBA aware of the problem in the error log. + */ + } + DBUG_EXECUTE_IF("inject_crash_before_flush_rli", DBUG_SUICIDE();); + if (mi->using_gtid == Master_info::USE_GTID_NO) + if (flush_relay_log_info(this)) + error= 1; + DBUG_EXECUTE_IF("inject_crash_after_flush_rli", DBUG_SUICIDE();); } + DBUG_RETURN(error); +} + + +int +Relay_log_info::alloc_inuse_relaylog(const char *name) +{ + inuse_relaylog *ir; + uint32 gtid_count; + rpl_gtid *gtid_list; + + if (!(ir= (inuse_relaylog *)my_malloc(sizeof(*ir), MYF(MY_WME|MY_ZEROFILL)))) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*ir)); + return 1; + } + gtid_count= relay_log_state.count(); + if (!(gtid_list= (rpl_gtid *)my_malloc(sizeof(*gtid_list)*gtid_count, + MYF(MY_WME)))) + { + my_free(ir); + my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*gtid_list)*gtid_count); + return 1; + } + if (relay_log_state.get_gtid_list(gtid_list, gtid_count)) + { + my_free(gtid_list); + my_free(ir); + DBUG_ASSERT(0 /* Should not be possible as we allocated correct length */); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return 1; + } + ir->rli= this; + strmake_buf(ir->name, name); + ir->relay_log_state= gtid_list; + ir->relay_log_state_count= gtid_count; + + if (!inuse_relaylog_list) + inuse_relaylog_list= ir; + else + { + last_inuse_relaylog->completed= true; + last_inuse_relaylog->next= ir; + } + last_inuse_relaylog= ir; + + return 0; +} + + +void +Relay_log_info::free_inuse_relaylog(inuse_relaylog *ir) +{ + my_free(ir->relay_log_state); + my_free(ir); } + +void +Relay_log_info::reset_inuse_relaylog() +{ + inuse_relaylog *cur= inuse_relaylog_list; + while (cur) + { + DBUG_ASSERT(cur->queued_count == cur->dequeued_count); + inuse_relaylog *next= cur->next; + free_inuse_relaylog(cur); + cur= next; + } + inuse_relaylog_list= last_inuse_relaylog= NULL; +} + + +int +Relay_log_info::update_relay_log_state(rpl_gtid *gtid_list, uint32 count) +{ + int res= 0; + while (count) + { + if (relay_log_state.update_nolock(gtid_list, false)) + res= 1; + ++gtid_list; + --count; + } + return res; +} + + #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -void Relay_log_info::cleanup_context(THD *thd, bool error) +int +rpl_load_gtid_slave_state(THD *thd) { - DBUG_ENTER("Relay_log_info::cleanup_context"); + TABLE_LIST tlist; + TABLE *table; + bool table_opened= false; + bool table_scanned= false; + bool array_inited= false; + struct local_element { uint64 sub_id; rpl_gtid gtid; }; + struct local_element tmp_entry, *entry; + HASH hash; + DYNAMIC_ARRAY array; + int err= 0; + uint32 i; + DBUG_ENTER("rpl_load_gtid_slave_state"); + + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + bool loaded= rpl_global_gtid_slave_state->loaded; + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + if (loaded) + DBUG_RETURN(0); + + my_hash_init(&hash, &my_charset_bin, 32, + offsetof(local_element, gtid) + offsetof(rpl_gtid, domain_id), + sizeof(uint32), NULL, my_free, HASH_UNIQUE); + if ((err= my_init_dynamic_array(&array, sizeof(local_element), 0, 0, MYF(0)))) + goto end; + array_inited= true; + + thd->reset_for_next_command(); + + tlist.init_one_table(STRING_WITH_LEN("mysql"), + rpl_gtid_slave_state_table_name.str, + rpl_gtid_slave_state_table_name.length, + NULL, TL_READ); + if ((err= open_and_lock_tables(thd, &tlist, FALSE, 0))) + goto end; + table_opened= true; + table= tlist.table; + + if ((err= gtid_check_rpl_slave_state_table(table))) + goto end; + + bitmap_set_all(table->read_set); + if ((err= table->file->ha_rnd_init_with_error(1))) + { + table->file->print_error(err, MYF(0)); + goto end; + } + table_scanned= true; + for (;;) + { + uint32 domain_id, server_id; + uint64 sub_id, seq_no; + uchar *rec; + + if ((err= table->file->ha_rnd_next(table->record[0]))) + { + if (err == HA_ERR_RECORD_DELETED) + continue; + else if (err == HA_ERR_END_OF_FILE) + break; + else + { + table->file->print_error(err, MYF(0)); + goto end; + } + } + domain_id= (ulonglong)table->field[0]->val_int(); + sub_id= (ulonglong)table->field[1]->val_int(); + server_id= (ulonglong)table->field[2]->val_int(); + seq_no= (ulonglong)table->field[3]->val_int(); + DBUG_PRINT("info", ("Read slave state row: %u-%u-%lu sub_id=%lu\n", + (unsigned)domain_id, (unsigned)server_id, + (ulong)seq_no, (ulong)sub_id)); + + tmp_entry.sub_id= sub_id; + tmp_entry.gtid.domain_id= domain_id; + tmp_entry.gtid.server_id= server_id; + tmp_entry.gtid.seq_no= seq_no; + if ((err= insert_dynamic(&array, (uchar *)&tmp_entry))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto end; + } + + if ((rec= my_hash_search(&hash, (const uchar *)&domain_id, 0))) + { + entry= (struct local_element *)rec; + if (entry->sub_id >= sub_id) + continue; + entry->sub_id= sub_id; + DBUG_ASSERT(entry->gtid.domain_id == domain_id); + entry->gtid.server_id= server_id; + entry->gtid.seq_no= seq_no; + } + else + { + if (!(entry= (struct local_element *)my_malloc(sizeof(*entry), + MYF(MY_WME)))) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*entry)); + err= 1; + goto end; + } + entry->sub_id= sub_id; + entry->gtid.domain_id= domain_id; + entry->gtid.server_id= server_id; + entry->gtid.seq_no= seq_no; + if ((err= my_hash_insert(&hash, (uchar *)entry))) + { + my_free(entry); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto end; + } + } + } + + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + if (rpl_global_gtid_slave_state->loaded) + { + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + goto end; + } + + for (i= 0; i < array.elements; ++i) + { + get_dynamic(&array, (uchar *)&tmp_entry, i); + if ((err= rpl_global_gtid_slave_state->update(tmp_entry.gtid.domain_id, + tmp_entry.gtid.server_id, + tmp_entry.sub_id, + tmp_entry.gtid.seq_no, + NULL))) + { + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto end; + } + } + + for (i= 0; i < hash.records; ++i) + { + entry= (struct local_element *)my_hash_element(&hash, i); + if (opt_bin_log && + mysql_bin_log.bump_seq_no_counter_if_needed(entry->gtid.domain_id, + entry->gtid.seq_no)) + { + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto end; + } + } + + rpl_global_gtid_slave_state->loaded= true; + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); - DBUG_ASSERT(sql_thd == thd); + err= 0; /* Clear HA_ERR_END_OF_FILE */ + +end: + if (table_scanned) + { + table->file->ha_index_or_rnd_end(); + ha_commit_trans(thd, FALSE); + ha_commit_trans(thd, TRUE); + } + if (table_opened) + { + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + } + if (array_inited) + delete_dynamic(&array); + my_hash_free(&hash); + DBUG_RETURN(err); +} + + +void +rpl_group_info::reinit(Relay_log_info *rli) +{ + this->rli= rli; + tables_to_lock= NULL; + tables_to_lock_count= 0; + trans_retries= 0; + last_event_start_time= 0; + gtid_sub_id= 0; + commit_id= 0; + gtid_pending= false; + worker_error= 0; + row_stmt_start_timestamp= 0; + long_find_row_note_printed= false; + did_mark_start_commit= false; + gtid_ev_flags2= 0; + pending_gtid_delete_list= NULL; + last_master_timestamp = 0; + gtid_ignore_duplicate_state= GTID_DUPLICATE_NULL; + speculation= SPECULATE_NO; + commit_orderer.reinit(); +} + +rpl_group_info::rpl_group_info(Relay_log_info *rli) + : thd(0), wait_commit_sub_id(0), + wait_commit_group_info(0), parallel_entry(0), + deferred_events(NULL), m_annotate_event(0), is_parallel_exec(false) +{ + reinit(rli); + bzero(¤t_gtid, sizeof(current_gtid)); + mysql_mutex_init(key_rpl_group_info_sleep_lock, &sleep_lock, + MY_MUTEX_INIT_FAST); + mysql_cond_init(key_rpl_group_info_sleep_cond, &sleep_cond, NULL); +} + + +rpl_group_info::~rpl_group_info() +{ + free_annotate_event(); + delete deferred_events; + mysql_mutex_destroy(&sleep_lock); + mysql_cond_destroy(&sleep_cond); +} + + +int +event_group_new_gtid(rpl_group_info *rgi, Gtid_log_event *gev) +{ + uint64 sub_id= rpl_global_gtid_slave_state->next_sub_id(gev->domain_id); + if (!sub_id) + { + /* Out of memory caused hash insertion to fail. */ + return 1; + } + rgi->gtid_sub_id= sub_id; + rgi->current_gtid.domain_id= gev->domain_id; + rgi->current_gtid.server_id= gev->server_id; + rgi->current_gtid.seq_no= gev->seq_no; + rgi->commit_id= gev->commit_id; + rgi->gtid_pending= true; + return 0; +} + + +void +delete_or_keep_event_post_apply(rpl_group_info *rgi, + Log_event_type typ, Log_event *ev) +{ + /* + ToDo: This needs to work on rpl_group_info, not Relay_log_info, to be + thread-safe for parallel replication. + */ + + switch (typ) { + case FORMAT_DESCRIPTION_EVENT: + /* + Format_description_log_event should not be deleted because it + will be used to read info about the relay log's format; + it will be deleted when the SQL thread does not need it, + i.e. when this thread terminates. + */ + break; + case ANNOTATE_ROWS_EVENT: + /* + Annotate_rows event should not be deleted because after it has + been applied, thd->query points to the string inside this event. + The thd->query will be used to generate new Annotate_rows event + during applying the subsequent Rows events. + */ + rgi->set_annotate_event((Annotate_rows_log_event*) ev); + break; + case DELETE_ROWS_EVENT_V1: + case UPDATE_ROWS_EVENT_V1: + case WRITE_ROWS_EVENT_V1: + case DELETE_ROWS_EVENT: + case UPDATE_ROWS_EVENT: + case WRITE_ROWS_EVENT: + /* + After the last Rows event has been applied, the saved Annotate_rows + event (if any) is not needed anymore and can be deleted. + */ + if (((Rows_log_event*)ev)->get_flags(Rows_log_event::STMT_END_F)) + rgi->free_annotate_event(); + /* fall through */ + default: + DBUG_PRINT("info", ("Deleting the event after it has been executed")); + if (!rgi->is_deferred_event(ev)) + delete ev; + break; + } +} + + +void rpl_group_info::cleanup_context(THD *thd, bool error) +{ + DBUG_ENTER("rpl_group_info::cleanup_context"); + DBUG_PRINT("enter", ("error: %d", (int) error)); + + DBUG_ASSERT(this->thd == thd); /* 1) Instances of Table_map_log_event, if ::do_apply_event() was called on them, may have opened tables, which we cannot be sure have been closed (because @@ -1270,13 +1798,44 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) if (error) { trans_rollback_stmt(thd); // if a "statement transaction" + /* trans_rollback() also resets OPTION_GTID_BEGIN */ trans_rollback(thd); // if a "real transaction" + /* + Now that we have rolled back the transaction, make sure we do not + erroneously update the GTID position. + */ + gtid_pending= false; + + /* + Rollback will have undone any deletions of old rows we might have made + in mysql.gtid_slave_pos. Put those rows back on the list to be deleted. + */ + pending_gtid_deletes_put_back(); } m_table_map.clear_tables(); slave_close_thread_tables(thd); if (error) + { thd->mdl_context.release_transactional_locks(); - clear_flag(IN_STMT); + + if (thd == rli->sql_driver_thd) + { + /* + Reset flags. This is needed to handle incident events and errors in + the relay log noticed by the sql driver thread. + */ + rli->clear_flag(Relay_log_info::IN_STMT); + rli->clear_flag(Relay_log_info::IN_TRANSACTION); + } + + /* + Ensure we always release the domain for others to process, when using + --gtid-ignore-duplicates. + */ + if (gtid_ignore_duplicate_state != GTID_DUPLICATE_NULL) + rpl_global_gtid_slave_state->release_domain_owner(this); + } + /* Cleanup for the flags that have been set at do_apply_event. */ @@ -1291,12 +1850,18 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) reset_row_stmt_start_timestamp(); unset_long_find_row_note_printed(); + DBUG_EXECUTE_IF("inject_sleep_gtid_100_x_x", { + if (current_gtid.domain_id == 100) + my_sleep(50000); + };); + DBUG_VOID_RETURN; } -void Relay_log_info::clear_tables_to_lock() + +void rpl_group_info::clear_tables_to_lock() { - DBUG_ENTER("Relay_log_info::clear_tables_to_lock()"); + DBUG_ENTER("rpl_group_info::clear_tables_to_lock()"); #ifndef DBUG_OFF /** When replicating in RBR and MyISAM Merge tables are involved @@ -1340,12 +1905,13 @@ void Relay_log_info::clear_tables_to_lock() DBUG_VOID_RETURN; } -void Relay_log_info::slave_close_thread_tables(THD *thd) + +void rpl_group_info::slave_close_thread_tables(THD *thd) { - DBUG_ENTER("Relay_log_info::slave_close_thread_tables(THD *thd)"); - thd->stmt_da->can_overwrite_status= TRUE; + DBUG_ENTER("rpl_group_info::slave_close_thread_tables(THD *thd)"); + thd->get_stmt_da()->set_overwrite_status(true); thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); close_thread_tables(thd); /* @@ -1373,4 +1939,200 @@ void Relay_log_info::slave_close_thread_tables(THD *thd) clear_tables_to_lock(); DBUG_VOID_RETURN; } + + + +static void +mark_start_commit_inner(rpl_parallel_entry *e, group_commit_orderer *gco, + rpl_group_info *rgi) +{ + group_commit_orderer *tmp; + uint64 count= ++e->count_committing_event_groups; + /* Signal any following GCO whose wait_count has been reached now. */ + tmp= gco; + while ((tmp= tmp->next_gco)) + { + uint64 wait_count= tmp->wait_count; + if (wait_count > count) + break; + mysql_cond_broadcast(&tmp->COND_group_commit_orderer); + } +} + + +void +rpl_group_info::mark_start_commit_no_lock() +{ + if (did_mark_start_commit) + return; + did_mark_start_commit= true; + mark_start_commit_inner(parallel_entry, gco, this); +} + + +void +rpl_group_info::mark_start_commit() +{ + rpl_parallel_entry *e; + + if (did_mark_start_commit) + return; + did_mark_start_commit= true; + + e= this->parallel_entry; + mysql_mutex_lock(&e->LOCK_parallel_entry); + mark_start_commit_inner(e, gco, this); + mysql_mutex_unlock(&e->LOCK_parallel_entry); +} + + +/* + Format the current GTID as a string suitable for printing in error messages. + + The string is stored in a buffer inside rpl_group_info, so remains valid + until next call to gtid_info() or until destruction of rpl_group_info. + + If no GTID is available, then NULL is returned. +*/ +char * +rpl_group_info::gtid_info() +{ + if (!gtid_sub_id || !current_gtid.seq_no) + return NULL; + my_snprintf(gtid_info_buf, sizeof(gtid_info_buf), "Gtid %u-%u-%llu", + current_gtid.domain_id, current_gtid.server_id, + current_gtid.seq_no); + return gtid_info_buf; +} + + +/* + Undo the effect of a prior mark_start_commit(). + + This is only used for retrying a transaction in parallel replication, after + we have encountered a deadlock or other temporary error. + + When we get such a deadlock, it means that the current group of transactions + did not yet all start committing (else they would not have deadlocked). So + we will not yet have woken up anything in the next group, our rgi->gco is + still live, and we can simply decrement the counter (to be incremented again + later, when the retry succeeds and reaches the commit step). +*/ +void +rpl_group_info::unmark_start_commit() +{ + rpl_parallel_entry *e; + + if (!did_mark_start_commit) + return; + did_mark_start_commit= false; + + e= this->parallel_entry; + mysql_mutex_lock(&e->LOCK_parallel_entry); + --e->count_committing_event_groups; + mysql_mutex_unlock(&e->LOCK_parallel_entry); +} + + +/* + When record_gtid() has deleted any old rows from the table + mysql.gtid_slave_pos as part of a replicated transaction, save the list of + rows deleted here. + + If later the transaction fails (eg. optimistic parallel replication), the + deletes will be undone when the transaction is rolled back. Then we can + put back the list of rows into the rpl_global_gtid_slave_state, so that + we can re-do the deletes and avoid accumulating old rows in the table. +*/ +void +rpl_group_info::pending_gtid_deletes_save(uint32 domain_id, + rpl_slave_state::list_element *list) +{ + /* + We should never get to a state where we try to save a new pending list of + gtid deletes while we still have an old one. But make sure we handle it + anyway just in case, so we avoid leaving stray entries in the + mysql.gtid_slave_pos table. + */ + DBUG_ASSERT(!pending_gtid_delete_list); + if (unlikely(pending_gtid_delete_list)) + pending_gtid_deletes_put_back(); + + pending_gtid_delete_list= list; + pending_gtid_delete_list_domain= domain_id; +} + + +/* + Take the list recorded by pending_gtid_deletes_save() and put it back into + rpl_global_gtid_slave_state. This is needed if deletion of the rows was + rolled back due to transaction failure. +*/ +void +rpl_group_info::pending_gtid_deletes_put_back() +{ + if (pending_gtid_delete_list) + { + rpl_global_gtid_slave_state->put_back_list(pending_gtid_delete_list_domain, + pending_gtid_delete_list); + pending_gtid_delete_list= NULL; + } +} + + +/* + Free the list recorded by pending_gtid_deletes_save(). Done when the deletes + in the list have been permanently committed. +*/ +void +rpl_group_info::pending_gtid_deletes_clear() +{ + pending_gtid_deletes_free(pending_gtid_delete_list); + pending_gtid_delete_list= NULL; +} + + +void +rpl_group_info::pending_gtid_deletes_free(rpl_slave_state::list_element *list) +{ + rpl_slave_state::list_element *next; + + while (list) + { + next= list->next; + my_free(list); + list= next; + } +} + + +rpl_sql_thread_info::rpl_sql_thread_info(Rpl_filter *filter) + : rpl_filter(filter) +{ + cached_charset_invalidate(); +} + + +void rpl_sql_thread_info::cached_charset_invalidate() +{ + DBUG_ENTER("rpl_group_info::cached_charset_invalidate"); + + /* Full of zeroes means uninitialized. */ + bzero(cached_charset, sizeof(cached_charset)); + DBUG_VOID_RETURN; +} + + +bool rpl_sql_thread_info::cached_charset_compare(char *charset) const +{ + DBUG_ENTER("rpl_group_info::cached_charset_compare"); + + if (memcmp(cached_charset, charset, sizeof(cached_charset))) + { + memcpy(const_cast<char*>(cached_charset), charset, sizeof(cached_charset)); + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + #endif diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index 5d31a8d1cdc..b40a34a54e6 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -23,10 +23,17 @@ #include "log.h" /* LOG_INFO, MYSQL_BIN_LOG */ #include "sql_class.h" /* THD */ #include "log_event.h" +#include "rpl_parallel.h" struct RPL_TABLE_LIST; class Master_info; -extern uint sql_slave_skip_counter; +class Rpl_filter; + + +enum { + LINES_IN_RELAY_LOG_INFO_WITH_DELAY= 5 +}; + /**************************************************************************** @@ -54,18 +61,21 @@ extern uint sql_slave_skip_counter; *****************************************************************************/ +struct rpl_group_info; +struct inuse_relaylog; + class Relay_log_info : public Slave_reporting_capability { public: /** - Flags for the state of the replication. - */ + Flags for the state of reading the relay log. Note that these are + bit masks. + */ enum enum_state_flag { - /** The replication thread is inside a statement */ - IN_STMT, - - /** Flag counter. Should always be last */ - STATE_FLAGS_COUNT + /** We are inside a group of events forming a statement */ + IN_STMT=1, + /** We have inside a transaction */ + IN_TRANSACTION=2 }; /* @@ -130,9 +140,14 @@ public: IO_CACHE info_file; /* - When we restart slave thread we need to have access to the previously - created temporary tables. Modified only on init/end and by the SQL - thread, read only by SQL thread. + List of temporary tables used by this connection. + This is updated when a temporary table is created or dropped by + a replication thread. + + Not reset when replication ends, to allow one to access the tables + when replication restarts. + + Protected by data_lock. */ TABLE *save_temporary_tables; @@ -140,17 +155,24 @@ public: standard lock acquisition order to avoid deadlocks: run_lock, data_lock, relay_log.LOCK_log, relay_log.LOCK_index */ - mysql_mutex_t data_lock, run_lock, sleep_lock; + mysql_mutex_t data_lock, run_lock; /* start_cond is broadcast when SQL thread is started stop_cond - when stopped data_cond - when data protected by data_lock changes */ - mysql_cond_t start_cond, stop_cond, data_cond, sleep_cond; + mysql_cond_t start_cond, stop_cond, data_cond; /* parent Master_info structure */ Master_info *mi; /* + List of active relay log files. + (This can be more than one in case of parallel replication). + */ + inuse_relaylog *inuse_relaylog_list; + inuse_relaylog *last_inuse_relaylog; + + /* Needed to deal properly with cur_log getting closed and re-opened with a different log under our feet */ @@ -170,8 +192,8 @@ public: - an autocommiting query + its associated events (INSERT_ID, TIMESTAMP...) We need these rli coordinates : - - relay log name and position of the beginning of the group we currently are - executing. Needed to know where we have to restart when replication has + - relay log name and position of the beginning of the group we currently + are executing. Needed to know where we have to restart when replication has stopped in the middle of a group (which has been rolled back by the slave). - relay log name and position just after the event we have just executed. This event is part of the current group. @@ -186,6 +208,10 @@ public: char event_relay_log_name[FN_REFLEN]; ulonglong event_relay_log_pos; ulonglong future_event_relay_log_pos; + /* + The master log name for current event. Only used in parallel replication. + */ + char future_event_master_log_name[FN_REFLEN]; #ifdef HAVE_valgrind bool is_fake; /* Mark that this is a fake relay log info structure */ @@ -217,19 +243,13 @@ public: */ bool sql_force_rotate_relay; + time_t last_master_timestamp; /* - When it commits, InnoDB internally stores the master log position it has - processed so far; the position to store is the one of the end of the - committing event (the COMMIT query event, or the event if in autocommit - mode). + The SQL driver thread sets this true while it is waiting at the end of the + relay log for more events to arrive. SHOW SLAVE STATUS uses this to report + Seconds_Behind_Master as zero while the SQL thread is so waiting. */ -#if MYSQL_VERSION_ID < 40100 - ulonglong future_master_log_pos; -#else - ulonglong future_group_master_log_pos; -#endif - - time_t last_master_timestamp; + bool sql_thread_caught_up; void clear_until_condition(); @@ -237,17 +257,28 @@ public: Needed for problems when slave stops and we want to restart it skipping one or more events in the master log that have caused errors, and have been manually applied by DBA already. + Must be ulong as it's refered to from set_var.cc */ - volatile uint32 slave_skip_counter; + volatile ulonglong slave_skip_counter; + ulonglong max_relay_log_size; + volatile ulong abort_pos_wait; /* Incremented on change master */ volatile ulong slave_run_id; /* Incremented on slave start */ mysql_mutex_t log_space_lock; mysql_cond_t log_space_cond; - THD * sql_thd; + /* + THD for the main sql thread, the one that starts threads to process + slave requests. If there is only one thread, then this THD is also + used for SQL processing. + A kill sent to this THD will kill the replication. + */ + THD *sql_driver_thd; #ifndef DBUG_OFF int events_till_abort; #endif + enum_gtid_skip_type gtid_skip_flag; + /* inited changes its value within LOCK_active_mi-guarded critical sections at times of start_slave_threads() (0->1) and end_slave() (1->0). @@ -257,6 +288,7 @@ public: */ volatile bool inited; volatile bool abort_slave; + volatile bool stop_for_until; volatile uint slave_running; /* @@ -270,7 +302,9 @@ public: thread is running). */ - enum {UNTIL_NONE= 0, UNTIL_MASTER_POS, UNTIL_RELAY_POS} until_condition; + enum { + UNTIL_NONE= 0, UNTIL_MASTER_POS, UNTIL_RELAY_POS, UNTIL_GTID + } until_condition; char until_log_name[FN_REFLEN]; ulonglong until_log_pos; /* extension extracted from log_name and converted to int */ @@ -284,16 +318,20 @@ public: UNTIL_LOG_NAMES_CMP_UNKNOWN= -2, UNTIL_LOG_NAMES_CMP_LESS= -1, UNTIL_LOG_NAMES_CMP_EQUAL= 0, UNTIL_LOG_NAMES_CMP_GREATER= 1 } until_log_names_cmp_result; + /* Condition for UNTIL master_gtid_pos. */ + slave_connection_state until_gtid_pos; - char cached_charset[6]; /* - trans_retries varies between 0 to slave_transaction_retries and counts how - many times the slave has retried the present transaction; gets reset to 0 - when the transaction finally succeeds. retried_trans is a cumulative - counter: how many times the slave has retried a transaction (any) since - slave started. + retried_trans is a cumulative counter: how many times the slave + has retried a transaction (any) since slave started. + Protected by data_lock. + */ + ulong retried_trans; + /* + Number of executed events for SLAVE STATUS. + Protected by slave_executed_entries_lock */ - ulong trans_retries, retried_trans; + int64 executed_entries; /* If the end of the hot relay log is made of master's events ignored by the @@ -305,6 +343,8 @@ public: */ char ign_master_log_name_end[FN_REFLEN]; ulonglong ign_master_log_pos_end; + /* Similar for ignored GTID events. */ + slave_connection_state ign_gtids; /* Indentifies where the SQL Thread should create temporary files for the @@ -313,6 +353,23 @@ public: char slave_patternload_file[FN_REFLEN]; size_t slave_patternload_file_size; + rpl_parallel parallel; + /* + The relay_log_state keeps track of the current binlog state of the execution + of the relay log. This is used to know where to resume current GTID position + if the slave thread is stopped and restarted. + It is only accessed from the SQL thread, so it does not need any locking. + */ + rpl_binlog_state relay_log_state; + /* + The restart_gtid_state is used when the SQL thread restarts on a relay log + in GTID mode. In multi-domain parallel replication, each domain may have a + separat position, so some events in more progressed domains may need to be + skipped. This keeps track of the domains that have not yet reached their + starting event. + */ + slave_connection_state restart_gtid_pos; + Relay_log_info(bool is_slave_recovery); ~Relay_log_info(); @@ -335,13 +392,9 @@ public: if (until_condition==UNTIL_MASTER_POS) until_log_names_cmp_result= UNTIL_LOG_NAMES_CMP_UNKNOWN; } - - inline void inc_event_relay_log_pos() - { - event_relay_log_pos= future_event_relay_log_pos; - } void inc_group_relay_log_pos(ulonglong log_pos, + rpl_group_info *rgi, bool skip_lock=0); int wait_for_pos(THD* thd, String* log_name, longlong log_pos, @@ -349,89 +402,15 @@ public: void close_temporary_tables(); /* Check if UNTIL condition is satisfied. See slave.cc for more. */ - bool is_until_satisfied(THD *thd, Log_event *ev); + bool is_until_satisfied(my_off_t); inline ulonglong until_pos() { + DBUG_ASSERT(until_condition == UNTIL_MASTER_POS || + until_condition == UNTIL_RELAY_POS); return ((until_condition == UNTIL_MASTER_POS) ? group_master_log_pos : group_relay_log_pos); } - RPL_TABLE_LIST *tables_to_lock; /* RBR: Tables to lock */ - uint tables_to_lock_count; /* RBR: Count of tables to lock */ - table_mapping m_table_map; /* RBR: Mapping table-id to table */ - - bool get_table_data(TABLE *table_arg, table_def **tabledef_var, TABLE **conv_table_var) const - { - DBUG_ASSERT(tabledef_var && conv_table_var); - for (TABLE_LIST *ptr= tables_to_lock ; ptr != NULL ; ptr= ptr->next_global) - if (ptr->table == table_arg) - { - *tabledef_var= &static_cast<RPL_TABLE_LIST*>(ptr)->m_tabledef; - *conv_table_var= static_cast<RPL_TABLE_LIST*>(ptr)->m_conv_table; - DBUG_PRINT("debug", ("Fetching table data for table %s.%s:" - " tabledef: %p, conv_table: %p", - table_arg->s->db.str, table_arg->s->table_name.str, - *tabledef_var, *conv_table_var)); - return true; - } - return false; - } - - /* - Last charset (6 bytes) seen by slave SQL thread is cached here; it helps - the thread save 3 get_charset() per Query_log_event if the charset is not - changing from event to event (common situation). - When the 6 bytes are equal to 0 is used to mean "cache is invalidated". - */ - void cached_charset_invalidate(); - bool cached_charset_compare(char *charset) const; - - void cleanup_context(THD *, bool); - void slave_close_thread_tables(THD *); - void clear_tables_to_lock(); - - /* - Used to defer stopping the SQL thread to give it a chance - to finish up the current group of events. - The timestamp is set and reset in @c sql_slave_killed(). - */ - time_t last_event_start_time; - - /* - A container to hold on Intvar-, Rand-, Uservar- log-events in case - the slave is configured with table filtering rules. - The withhold events are executed when their parent Query destiny is - determined for execution as well. - */ - Deferred_log_events *deferred_events; - - /* - State of the container: true stands for IRU events gathering, - false does for execution, either deferred or direct. - */ - bool deferred_events_collecting; - - /* - Returns true if the argument event resides in the containter; - more specifically, the checking is done against the last added event. - */ - bool is_deferred_event(Log_event * ev) - { - return deferred_events_collecting ? deferred_events->is_last(ev) : false; - }; - /* The general cleanup that slave applier may need at the end of query. */ - inline void cleanup_after_query() - { - if (deferred_events) - deferred_events->rewind(); - }; - /* The general cleanup that slave applier may need at the end of session. */ - void cleanup_after_session() - { - if (deferred_events) - delete deferred_events; - }; - /** Helper function to do after statement completion. @@ -444,15 +423,32 @@ public: Master log position of the event. The position is recorded in the relay log info and used to produce information for <code>SHOW SLAVE STATUS</code>. - - @param event_creation_time - Timestamp for the creation of the event on the master side. The - time stamp is recorded in the relay log info and used to compute - the <code>Seconds_behind_master</code> field. */ - void stmt_done(my_off_t event_log_pos, - time_t event_creation_time); + bool stmt_done(my_off_t event_log_pos, THD *thd, rpl_group_info *rgi); + int alloc_inuse_relaylog(const char *name); + void free_inuse_relaylog(inuse_relaylog *ir); + void reset_inuse_relaylog(); + int update_relay_log_state(rpl_gtid *gtid_list, uint32 count); + + /** + Is the replication inside a group? + + The reader of the relay log is inside a group if either: + - The IN_TRANSACTION flag is set, meaning we're inside a transaction + - The IN_STMT flag is set, meaning we have read at least one row from + a multi-event entry. + + This flag reflects the state of the log 'just now', ie after the last + read event would be executed. + This allow us to test if we can stop replication before reading + the next entry. + @retval true Replication thread is currently inside a group + @retval false Replication thread is currently not inside a group + */ + bool is_in_group() const { + return (m_flags & (IN_STMT | IN_TRANSACTION)); + } /** Set the value of a replication state flag. @@ -461,7 +457,7 @@ public: */ void set_flag(enum_state_flag flag) { - m_flags |= (1UL << flag); + m_flags|= flag; } /** @@ -473,7 +469,7 @@ public: */ bool get_flag(enum_state_flag flag) { - return m_flags & (1UL << flag); + return m_flags & flag; } /** @@ -483,34 +479,297 @@ public: */ void clear_flag(enum_state_flag flag) { - m_flags &= ~(1UL << flag); + m_flags&= ~flag; } - /** - Is the replication inside a group? +private: + + /* + Holds the state of the data in the relay log. + We need this to ensure that we are not in the middle of a + statement or inside BEGIN ... COMMIT when should rotate the + relay log. + */ + uint32 m_flags; +}; - Replication is inside a group if either: - - The OPTION_BEGIN flag is set, meaning we're inside a transaction - - The RLI_IN_STMT flag is set, meaning we're inside a statement - @retval true Replication thread is currently inside a group - @retval false Replication thread is currently not inside a group +/* + In parallel replication, if we need to re-try a transaction due to a + deadlock or other temporary error, we may need to go back and re-read events + out of an earlier relay log. + + This structure keeps track of the relaylogs that are potentially in use. + Each rpl_group_info has a pointer to one of those, corresponding to the + first GTID event. + + A pair of reference count keeps track of how long a relay log is potentially + in use. When the `completed' flag is set, all events have been read out of + the relay log, but the log might still be needed for retry in worker + threads. As worker threads complete an event group, they increment + atomically the `dequeued_count' with number of events queued. Thus, when + completed is set and dequeued_count equals queued_count, the relay log file + is finally done with and can be purged. + + By separating the queued and dequeued count, only the dequeued_count needs + multi-thread synchronisation; the completed flag and queued_count fields + are only accessed by the SQL driver thread and need no synchronisation. +*/ +struct inuse_relaylog { + inuse_relaylog *next; + Relay_log_info *rli; + /* + relay_log_state holds the binlog state corresponding to the start of this + relay log file. It is an array with relay_log_state_count elements. + */ + rpl_gtid *relay_log_state; + uint32 relay_log_state_count; + /* Number of events in this relay log queued for worker threads. */ + int64 queued_count; + /* Number of events completed by worker threads. */ + volatile int64 dequeued_count; + /* Set when all events have been read from a relaylog. */ + bool completed; + char name[FN_REFLEN]; +}; + + +/* + This is data for various state needed to be kept for the processing of + one event group (transaction) during replication. + + In single-threaded replication, there will be one global rpl_group_info and + one global Relay_log_info per master connection. They will be linked + together. + + In parallel replication, there will be one rpl_group_info object for + each running sql thread, each having their own thd. + + All rpl_group_info will share the same Relay_log_info. +*/ + +struct rpl_group_info +{ + rpl_group_info *next; /* For free list in rpl_parallel_thread */ + Relay_log_info *rli; + THD *thd; + /* + Current GTID being processed. + The sub_id gives the binlog order within one domain_id. A zero sub_id + means that there is no active GTID. + */ + uint64 gtid_sub_id; + rpl_gtid current_gtid; + uint64 commit_id; + /* + This is used to keep transaction commit order. + We will signal this when we commit, and can register it to wait for the + commit_orderer of the previous commit to signal us. + */ + wait_for_commit commit_orderer; + /* + If non-zero, the sub_id of a prior event group whose commit we have to wait + for before committing ourselves. Then wait_commit_group_info points to the + event group to wait for. + + Before using this, rpl_parallel_entry::last_committed_sub_id should be + compared against wait_commit_sub_id. Only if last_committed_sub_id is + smaller than wait_commit_sub_id must the wait be done (otherwise the + waited-for transaction is already committed, so we would otherwise wait + for the wrong commit). + */ + uint64 wait_commit_sub_id; + rpl_group_info *wait_commit_group_info; + /* + This holds a pointer to a struct that keeps track of the need to wait + for the previous batch of event groups to reach the commit stage, before + this batch can start to execute. + + (When we execute in parallel the transactions that group committed + together on the master, we still need to wait for any prior transactions + to have reached the commit stage). + + The pointed-to gco is only valid for as long as + gtid_sub_id < parallel_entry->last_committed_sub_id. After that, it can + be freed by another thread. + */ + group_commit_orderer *gco; + + struct rpl_parallel_entry *parallel_entry; + + /* + A container to hold on Intvar-, Rand-, Uservar- log-events in case + the slave is configured with table filtering rules. + The withhold events are executed when their parent Query destiny is + determined for execution as well. + */ + Deferred_log_events *deferred_events; + + /* + State of the container: true stands for IRU events gathering, + false does for execution, either deferred or direct. + */ + bool deferred_events_collecting; + + Annotate_rows_log_event *m_annotate_event; + + RPL_TABLE_LIST *tables_to_lock; /* RBR: Tables to lock */ + uint tables_to_lock_count; /* RBR: Count of tables to lock */ + table_mapping m_table_map; /* RBR: Mapping table-id to table */ + mysql_mutex_t sleep_lock; + mysql_cond_t sleep_cond; + + /* + trans_retries varies between 0 to slave_transaction_retries and counts how + many times the slave has retried the present transaction; gets reset to 0 + when the transaction finally succeeds. + */ + ulong trans_retries; + + /* + Used to defer stopping the SQL thread to give it a chance + to finish up the current group of events. + The timestamp is set and reset in @c sql_slave_killed(). + */ + time_t last_event_start_time; + + char *event_relay_log_name; + char event_relay_log_name_buf[FN_REFLEN]; + ulonglong event_relay_log_pos; + ulonglong future_event_relay_log_pos; + /* + The master log name for current event. Only used in parallel replication. + */ + char future_event_master_log_name[FN_REFLEN]; + bool is_parallel_exec; + /* When gtid_pending is true, we have not yet done record_gtid(). */ + bool gtid_pending; + int worker_error; + /* + Set true when we signalled that we reach the commit phase. Used to avoid + counting one event group twice. + */ + bool did_mark_start_commit; + /* Copy of flags2 from GTID event. */ + uchar gtid_ev_flags2; + enum { + GTID_DUPLICATE_NULL=0, + GTID_DUPLICATE_IGNORE=1, + GTID_DUPLICATE_OWNER=2 + }; + /* + When --gtid-ignore-duplicates, this is set to one of the above three + values: + GTID_DUPLICATE_NULL - Not using --gtid-ignore-duplicates. + GTID_DUPLICATE_IGNORE - This gtid already applied, skip the event group. + GTID_DUPLICATE_OWNER - We are the current owner of the domain, and must + apply the event group and then release the domain. + */ + uint8 gtid_ignore_duplicate_state; + + /* + Runtime state for printing a note when slave is taking + too long while processing a row event. */ - bool is_in_group() const { - return (sql_thd->variables.option_bits & OPTION_BEGIN) || - (m_flags & (1UL << IN_STMT)); - } + time_t row_stmt_start_timestamp; + bool long_find_row_note_printed; + /* Needs room for "Gtid D-S-N\x00". */ + char gtid_info_buf[5+10+1+10+1+20+1]; + + /* List of not yet committed deletions in mysql.gtid_slave_pos. */ + rpl_slave_state::list_element *pending_gtid_delete_list; + /* Domain associated with pending_gtid_delete_list. */ + uint32 pending_gtid_delete_list_domain; + + /* + The timestamp, from the master, of the commit event. + Used to do delayed update of rli->last_master_timestamp, for getting + reasonable values out of Seconds_Behind_Master in SHOW SLAVE STATUS. + */ + time_t last_master_timestamp; + + /* + Information to be able to re-try an event group in case of a deadlock or + other temporary error. + */ + inuse_relaylog *relay_log; + uint64 retry_start_offset; + uint64 retry_event_count; + /* + If `speculation' is != SPECULATE_NO, then we are optimistically running + this transaction in parallel, even though it might not be safe (there may + be a conflict with a prior event group). + + In this case, a conflict can cause other errors than deadlocks (like + duplicate key for example). So in case of _any_ error, we need to roll + back and retry the event group. + */ + enum enum_speculation { + /* + This transaction was group-committed together on the master with the + other transactions with which it is replicated in parallel. + */ + SPECULATE_NO, + /* + We will optimistically try to run this transaction in parallel with + other transactions, even though it is not known to be conflict free. + If we get a conflict, we will detect it as a deadlock, roll back and + retry. + */ + SPECULATE_OPTIMISTIC, + /* + This transaction got a conflict during speculative parallel apply, or + it was marked on the master as likely to cause a conflict or unsafe to + speculate. So it will wait for the prior transaction to commit before + starting to replicate. + */ + SPECULATE_WAIT + } speculation; + enum enum_retry_killed { + RETRY_KILL_NONE = 0, + RETRY_KILL_PENDING, + RETRY_KILL_KILLED + }; + uchar killed_for_retry; + + rpl_group_info(Relay_log_info *rli_); + ~rpl_group_info(); + void reinit(Relay_log_info *rli); + + /* + Returns true if the argument event resides in the containter; + more specifically, the checking is done against the last added event. + */ + bool is_deferred_event(Log_event * ev) + { + return deferred_events_collecting ? deferred_events->is_last(ev) : false; + }; + /* The general cleanup that slave applier may need at the end of query. */ + inline void cleanup_after_query() + { + if (deferred_events) + deferred_events->rewind(); + }; + /* The general cleanup that slave applier may need at the end of session. */ + void cleanup_after_session() + { + if (deferred_events) + { + delete deferred_events; + deferred_events= NULL; + } + }; /** Save pointer to Annotate_rows event and switch on the binlog_annotate_row_events for this sql thread. - To be called when sql thread recieves an Annotate_rows event. + To be called when sql thread receives an Annotate_rows event. */ inline void set_annotate_event(Annotate_rows_log_event *event) { free_annotate_event(); m_annotate_event= event; - sql_thd->variables.binlog_annotate_row_events= 1; + this->thd->variables.binlog_annotate_row_events= 1; } /** @@ -532,12 +791,43 @@ public: { if (m_annotate_event) { - sql_thd->variables.binlog_annotate_row_events= 0; + this->thd->variables.binlog_annotate_row_events= 0; delete m_annotate_event; m_annotate_event= 0; } } + bool get_table_data(TABLE *table_arg, table_def **tabledef_var, TABLE **conv_table_var) const + { + DBUG_ASSERT(tabledef_var && conv_table_var); + for (TABLE_LIST *ptr= tables_to_lock ; ptr != NULL ; ptr= ptr->next_global) + if (ptr->table == table_arg) + { + *tabledef_var= &static_cast<RPL_TABLE_LIST*>(ptr)->m_tabledef; + *conv_table_var= static_cast<RPL_TABLE_LIST*>(ptr)->m_conv_table; + DBUG_PRINT("debug", ("Fetching table data for table %s.%s:" + " tabledef: %p, conv_table: %p", + table_arg->s->db.str, table_arg->s->table_name.str, + *tabledef_var, *conv_table_var)); + return true; + } + return false; + } + + void clear_tables_to_lock(); + void cleanup_context(THD *, bool); + void slave_close_thread_tables(THD *); + void mark_start_commit_no_lock(); + void mark_start_commit(); + char *gtid_info(); + void unmark_start_commit(); + + static void pending_gtid_deletes_free(rpl_slave_state::list_element *list); + void pending_gtid_deletes_save(uint32 domain_id, + rpl_slave_state::list_element *list); + void pending_gtid_deletes_put_back(); + void pending_gtid_deletes_clear(); + time_t get_row_stmt_start_timestamp() { return row_stmt_start_timestamp; @@ -571,18 +861,35 @@ public: return long_find_row_note_printed; } -private: + inline void inc_event_relay_log_pos() + { + if (!is_parallel_exec) + rli->event_relay_log_pos= future_event_relay_log_pos; + } +}; - uint32 m_flags; - /* - Runtime state for printing a note when slave is taking - too long while processing a row event. - */ - time_t row_stmt_start_timestamp; - bool long_find_row_note_printed; +/* + The class rpl_sql_thread_info is the THD::system_thread_info for an SQL + thread; this is either the driver SQL thread or a worker thread for parallel + replication. +*/ +class rpl_sql_thread_info +{ +public: + char cached_charset[6]; + Rpl_filter* rpl_filter; - Annotate_rows_log_event *m_annotate_event; + rpl_sql_thread_info(Rpl_filter *filter); + + /* + Last charset (6 bytes) seen by slave SQL thread is cached here; it helps + the thread save 3 get_charset() per Query_log_event if the charset is not + changing from event to event (common situation). + When the 6 bytes are equal to 0 is used to mean "cache is invalidated". + */ + void cached_charset_invalidate(); + bool cached_charset_compare(char *charset) const; }; @@ -590,4 +897,12 @@ private: int init_relay_log_info(Relay_log_info* rli, const char* info_fname); +extern struct rpl_slave_state *rpl_global_gtid_slave_state; +extern gtid_waiting rpl_global_gtid_waiting; + +int rpl_load_gtid_slave_state(THD *thd); +int event_group_new_gtid(rpl_group_info *rgi, Gtid_log_event *gev); +void delete_or_keep_event_post_apply(rpl_group_info *rgi, + Log_event_type typ, Log_event *ev); + #endif /* RPL_RLI_H */ diff --git a/sql/rpl_tblmap.cc b/sql/rpl_tblmap.cc index b7ac1b2d091..80114f50d62 100644 --- a/sql/rpl_tblmap.cc +++ b/sql/rpl_tblmap.cc @@ -13,8 +13,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" -#include "my_global.h" // HAVE_REPLICATION #ifdef HAVE_REPLICATION @@ -43,10 +43,10 @@ table_mapping::table_mapping() constructor is called at startup only. */ (void) my_hash_init(&m_table_ids,&my_charset_bin,TABLE_ID_HASH_SIZE, - offsetof(entry,table_id),sizeof(ulong), + offsetof(entry,table_id),sizeof(ulonglong), 0,0,0); /* We don't preallocate any block, this is consistent with m_free=0 above */ - init_alloc_root(&m_mem_root, TABLE_ID_HASH_SIZE*sizeof(entry), 0); + init_alloc_root(&m_mem_root, TABLE_ID_HASH_SIZE*sizeof(entry), 0, MYF(0)); DBUG_VOID_RETURN; } @@ -59,20 +59,20 @@ table_mapping::~table_mapping() free_root(&m_mem_root, MYF(0)); } -TABLE* table_mapping::get_table(ulong table_id) +TABLE* table_mapping::get_table(ulonglong table_id) { DBUG_ENTER("table_mapping::get_table(ulong)"); - DBUG_PRINT("enter", ("table_id: %lu", table_id)); + DBUG_PRINT("enter", ("table_id: %llu", table_id)); entry *e= find_entry(table_id); if (e) { - DBUG_PRINT("info", ("tid %lu -> table 0x%lx (%s)", + DBUG_PRINT("info", ("tid %llu -> table 0x%lx (%s)", table_id, (long) e->table, MAYBE_TABLE_NAME(e->table))); DBUG_RETURN(e->table); } - DBUG_PRINT("info", ("tid %lu is not mapped!", table_id)); + DBUG_PRINT("info", ("tid %llu is not mapped!", table_id)); DBUG_RETURN(NULL); } @@ -102,10 +102,10 @@ int table_mapping::expand() return 0; } -int table_mapping::set_table(ulong table_id, TABLE* table) +int table_mapping::set_table(ulonglong table_id, TABLE* table) { DBUG_ENTER("table_mapping::set_table(ulong,TABLE*)"); - DBUG_PRINT("enter", ("table_id: %lu table: 0x%lx (%s)", + DBUG_PRINT("enter", ("table_id: %llu table: 0x%lx (%s)", table_id, (long) table, MAYBE_TABLE_NAME(table))); entry *e= find_entry(table_id); @@ -133,13 +133,13 @@ int table_mapping::set_table(ulong table_id, TABLE* table) DBUG_RETURN(ERR_MEMORY_ALLOCATION); } - DBUG_PRINT("info", ("tid %lu -> table 0x%lx (%s)", + DBUG_PRINT("info", ("tid %llu -> table 0x%lx (%s)", table_id, (long) e->table, MAYBE_TABLE_NAME(e->table))); DBUG_RETURN(0); // All OK } -int table_mapping::remove_table(ulong table_id) +int table_mapping::remove_table(ulonglong table_id) { entry *e= find_entry(table_id); if (e) diff --git a/sql/rpl_tblmap.h b/sql/rpl_tblmap.h index 9fb1c4afbd7..05b298e6053 100644 --- a/sql/rpl_tblmap.h +++ b/sql/rpl_tblmap.h @@ -70,10 +70,10 @@ public: table_mapping(); ~table_mapping(); - TABLE* get_table(ulong table_id); + TABLE* get_table(ulonglong table_id); - int set_table(ulong table_id, TABLE* table); - int remove_table(ulong table_id); + int set_table(ulonglong table_id, TABLE* table); + int remove_table(ulonglong table_id); void clear_tables(); ulong count() const { return m_table_ids.records; } @@ -83,14 +83,14 @@ private: it, which only works for PODs) */ struct entry { - ulong table_id; + ulonglong table_id; union { TABLE *table; entry *next; }; }; - entry *find_entry(ulong table_id) + entry *find_entry(ulonglong table_id) { return (entry *) my_hash_search(&m_table_ids, (uchar*)&table_id, diff --git a/sql/rpl_utility.cc b/sql/rpl_utility.cc index 9a20f71f4f7..ff2cd74c3a7 100644 --- a/sql/rpl_utility.cc +++ b/sql/rpl_utility.cc @@ -14,6 +14,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> +#include <my_bit.h> #include "rpl_utility.h" #include "log_event.h" @@ -22,26 +24,15 @@ #include "sql_select.h" /** - Function to compare two size_t integers for their relative - order. Used below. - */ -int compare(size_t a, size_t b) + Calculate display length for MySQL56 temporal data types from their metadata. + It contains fractional precision in the low 16-bit word. +*/ +static uint32 +max_display_length_for_temporal2_field(uint32 int_display_length, + unsigned int metadata) { - if (a < b) - return -1; - if (b < a) - return 1; - return 0; -} - - -/** - Max value for an unsigned integer of 'bits' bits. - - The somewhat contorted expression is to avoid overflow. - */ -uint32 uint_max(int bits) { - return (((1UL << (bits - 1)) - 1) << 1) | 1; + metadata&= 0x00ff; + return int_display_length + metadata + (metadata ? 1 : 0); } @@ -107,14 +98,25 @@ max_display_length_for_field(enum_field_types sql_type, unsigned int metadata) return 3; case MYSQL_TYPE_DATE: - case MYSQL_TYPE_TIME: return 3; + case MYSQL_TYPE_TIME: + return MIN_TIME_WIDTH; + + case MYSQL_TYPE_TIME2: + return max_display_length_for_temporal2_field(MIN_TIME_WIDTH, metadata); + case MYSQL_TYPE_TIMESTAMP: - return 4; + return MAX_DATETIME_WIDTH; + + case MYSQL_TYPE_TIMESTAMP2: + return max_display_length_for_temporal2_field(MAX_DATETIME_WIDTH, metadata); case MYSQL_TYPE_DATETIME: - return 8; + return MAX_DATETIME_WIDTH; + + case MYSQL_TYPE_DATETIME2: + return max_display_length_for_temporal2_field(MAX_DATETIME_WIDTH, metadata); case MYSQL_TYPE_BIT: /* @@ -137,10 +139,10 @@ max_display_length_for_field(enum_field_types sql_type, unsigned int metadata) */ case MYSQL_TYPE_TINY_BLOB: - return uint_max(1 * 8); + return my_set_bits(1 * 8); case MYSQL_TYPE_MEDIUM_BLOB: - return uint_max(3 * 8); + return my_set_bits(3 * 8); case MYSQL_TYPE_BLOB: /* @@ -148,11 +150,11 @@ max_display_length_for_field(enum_field_types sql_type, unsigned int metadata) blobs are of type MYSQL_TYPE_BLOB. In that case, we have to look at the length instead to decide what the max display size is. */ - return uint_max(metadata * 8); + return my_set_bits(metadata * 8); case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_GEOMETRY: - return uint_max(4 * 8); + return my_set_bits(4 * 8); default: return ~(uint32) 0; @@ -182,7 +184,7 @@ int compare_lengths(Field *field, enum_field_types source_type, uint16 metadata) " target_length: %lu, target_type: %u", (unsigned long) source_length, source_type, (unsigned long) target_length, field->real_type())); - int result= compare(source_length, target_length); + int result= source_length < target_length ? -1 : source_length > target_length; DBUG_PRINT("result", ("%d", result)); DBUG_RETURN(result); } @@ -261,12 +263,21 @@ uint32 table_def::calc_field_size(uint col, uchar *master_data) const case MYSQL_TYPE_TIME: length= 3; break; + case MYSQL_TYPE_TIME2: + length= my_time_binary_length(m_field_metadata[col]); + break; case MYSQL_TYPE_TIMESTAMP: length= 4; break; + case MYSQL_TYPE_TIMESTAMP2: + length= my_timestamp_binary_length(m_field_metadata[col]); + break; case MYSQL_TYPE_DATETIME: length= 8; break; + case MYSQL_TYPE_DATETIME2: + length= my_datetime_binary_length(m_field_metadata[col]); + break; case MYSQL_TYPE_BIT: { /* @@ -362,6 +373,7 @@ void show_sql_type(enum_field_types type, uint16 metadata, String *str, CHARSET_ break; case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: str->set_ascii(STRING_WITH_LEN("timestamp")); break; @@ -379,10 +391,12 @@ void show_sql_type(enum_field_types type, uint16 metadata, String *str, CHARSET_ break; case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: str->set_ascii(STRING_WITH_LEN("time")); break; case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: str->set_ascii(STRING_WITH_LEN("datetime")); break; @@ -508,9 +522,9 @@ bool is_conversion_ok(int order, Relay_log_info *rli) bool allow_non_lossy, allow_lossy; allow_non_lossy = slave_type_conversions_options & - (ULL(1) << SLAVE_TYPE_CONVERSIONS_ALL_NON_LOSSY); + (1ULL << SLAVE_TYPE_CONVERSIONS_ALL_NON_LOSSY); allow_lossy= slave_type_conversions_options & - (ULL(1) << SLAVE_TYPE_CONVERSIONS_ALL_LOSSY); + (1ULL << SLAVE_TYPE_CONVERSIONS_ALL_LOSSY); DBUG_PRINT("enter", ("order: %d, flags:%s%s", order, allow_non_lossy ? " ALL_NON_LOSSY" : "", @@ -601,6 +615,36 @@ can_convert_field_to(Field *field, else DBUG_RETURN(false); } + else if ( + /* + Conversion from MariaDB TIMESTAMP(0), TIME(0), DATETIME(0) + to the corresponding MySQL56 types is non-lossy. + */ + (metadata == 0 && + ((field->real_type() == MYSQL_TYPE_TIMESTAMP2 && + source_type == MYSQL_TYPE_TIMESTAMP) || + (field->real_type() == MYSQL_TYPE_TIME2 && + source_type == MYSQL_TYPE_TIME) || + (field->real_type() == MYSQL_TYPE_DATETIME2 && + source_type == MYSQL_TYPE_DATETIME))) || + /* + Conversion from MySQL56 TIMESTAMP(N), TIME(N), DATETIME(N) + to the corresponding MariaDB or MySQL55 types is non-lossy. + */ + (metadata == field->decimals() && + ((field->real_type() == MYSQL_TYPE_TIMESTAMP && + source_type == MYSQL_TYPE_TIMESTAMP2) || + (field->real_type() == MYSQL_TYPE_TIME && + source_type == MYSQL_TYPE_TIME2) || + (field->real_type() == MYSQL_TYPE_DATETIME && + source_type == MYSQL_TYPE_DATETIME2)))) + { + /* + TS-TODO: conversion from FSP1>FSP2. + */ + *order_var= -1; + DBUG_RETURN(true); + } else if (!slave_type_conversions_options) DBUG_RETURN(false); @@ -725,6 +769,9 @@ can_convert_field_to(Field *field, case MYSQL_TYPE_NULL: case MYSQL_TYPE_ENUM: case MYSQL_TYPE_SET: + case MYSQL_TYPE_TIMESTAMP2: + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_TIME2: DBUG_RETURN(false); } DBUG_RETURN(false); // To keep GCC happy @@ -759,14 +806,15 @@ can_convert_field_to(Field *field, @retval false Master table is not compatible with slave table. */ bool -table_def::compatible_with(THD *thd, Relay_log_info *rli, +table_def::compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, TABLE **conv_table_var) const { /* We only check the initial columns for the tables. */ - uint const cols_to_check= min(table->s->fields, size()); + uint const cols_to_check= MY_MIN(table->s->fields, size()); + Relay_log_info *rli= rgi->rli; TABLE *tmp_table= NULL; for (uint col= 0 ; col < cols_to_check ; ++col) @@ -790,7 +838,7 @@ table_def::compatible_with(THD *thd, Relay_log_info *rli, This will create the full table with all fields. This is necessary to ge the correct field lengths for the record. */ - tmp_table= create_conversion_table(thd, rli, table); + tmp_table= create_conversion_table(thd, rgi, table); if (tmp_table == NULL) return false; /* @@ -810,16 +858,19 @@ table_def::compatible_with(THD *thd, Relay_log_info *rli, col, field->field_name)); DBUG_ASSERT(col < size() && col < table->s->fields); DBUG_ASSERT(table->s->db.str && table->s->table_name.str); + DBUG_ASSERT(table->in_use); const char *db_name= table->s->db.str; const char *tbl_name= table->s->table_name.str; char source_buf[MAX_FIELD_WIDTH]; char target_buf[MAX_FIELD_WIDTH]; String source_type(source_buf, sizeof(source_buf), &my_charset_latin1); String target_type(target_buf, sizeof(target_buf), &my_charset_latin1); + THD *thd= table->in_use; + show_sql_type(type(col), field_metadata(col), &source_type, field->charset()); field->sql_type(target_type); - rli->report(ERROR_LEVEL, ER_SLAVE_CONVERSION_FAILED, - ER(ER_SLAVE_CONVERSION_FAILED), + rli->report(ERROR_LEVEL, ER_SLAVE_CONVERSION_FAILED, rgi->gtid_info(), + ER_THD(thd, ER_SLAVE_CONVERSION_FAILED), col, db_name, tbl_name, source_type.c_ptr_safe(), target_type.c_ptr_safe()); return false; @@ -860,24 +911,27 @@ table_def::compatible_with(THD *thd, Relay_log_info *rli, conversion table. */ -TABLE *table_def::create_conversion_table(THD *thd, Relay_log_info *rli, TABLE *target_table) const +TABLE *table_def::create_conversion_table(THD *thd, rpl_group_info *rgi, + TABLE *target_table) const { DBUG_ENTER("table_def::create_conversion_table"); List<Create_field> field_list; TABLE *conv_table= NULL; + Relay_log_info *rli= rgi->rli; /* At slave, columns may differ. So we should create - min(columns@master, columns@slave) columns in the + MY_MIN(columns@master, columns@slave) columns in the conversion table. */ - uint const cols_to_create= min(target_table->s->fields, size()); + uint const cols_to_create= MY_MIN(target_table->s->fields, size()); for (uint col= 0 ; col < cols_to_create; ++col) { Create_field *field_def= (Create_field*) alloc_root(thd->mem_root, sizeof(Create_field)); + Field *target_field= target_table->field[col]; bool unsigned_flag= 0; - if (field_list.push_back(field_def)) + if (field_list.push_back(field_def, thd->mem_root)) DBUG_RETURN(NULL); uint decimals= 0; @@ -890,7 +944,7 @@ TABLE *table_def::create_conversion_table(THD *thd, Relay_log_info *rli, TABLE * int precision; case MYSQL_TYPE_ENUM: case MYSQL_TYPE_SET: - interval= static_cast<Field_enum*>(target_table->field[col])->typelib; + interval= static_cast<Field_enum*>(target_field)->typelib; pack_length= field_metadata(col) & 0x00ff; break; @@ -914,7 +968,7 @@ TABLE *table_def::create_conversion_table(THD *thd, Relay_log_info *rli, TABLE * " column Name: %s.%s.%s.", target_table->s->db.str, target_table->s->table_name.str, - target_table->field[col]->field_name); + target_field->field_name); goto err; case MYSQL_TYPE_TINY_BLOB: @@ -935,7 +989,18 @@ TABLE *table_def::create_conversion_table(THD *thd, Relay_log_info *rli, TABLE * assume we have same sign on master and slave. This is true when not using conversions so it should be true also when using conversions. */ - unsigned_flag= ((Field_num*) target_table->field[col])->unsigned_flag; + unsigned_flag= static_cast<Field_num*>(target_field)->unsigned_flag; + break; + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + /* + As we don't know the precision of the temporal field on the master, + assume it's the same on master and slave. This is true when not + using conversions so it should be true also when using conversions. + */ + if (target_field->decimals()) + max_length+= target_field->decimals() + 1; break; default: break; @@ -943,7 +1008,7 @@ TABLE *table_def::create_conversion_table(THD *thd, Relay_log_info *rli, TABLE * DBUG_PRINT("debug", ("sql_type: %d, target_field: '%s', max_length: %d, decimals: %d," " maybe_null: %d, unsigned_flag: %d, pack_length: %u", - type(col), target_table->field[col]->field_name, + binlog_type(col), target_field->field_name, max_length, decimals, TRUE, unsigned_flag, pack_length)); field_def->init_for_tmp_table(type(col), @@ -952,7 +1017,7 @@ TABLE *table_def::create_conversion_table(THD *thd, Relay_log_info *rli, TABLE * TRUE, // maybe_null unsigned_flag, pack_length); - field_def->charset= target_table->field[col]->charset(); + field_def->charset= target_field->charset(); field_def->interval= interval; } @@ -960,10 +1025,12 @@ TABLE *table_def::create_conversion_table(THD *thd, Relay_log_info *rli, TABLE * err: if (conv_table == NULL) - rli->report(ERROR_LEVEL, ER_SLAVE_CANT_CREATE_CONVERSION, - ER(ER_SLAVE_CANT_CREATE_CONVERSION), + { + rli->report(ERROR_LEVEL, ER_SLAVE_CANT_CREATE_CONVERSION, rgi->gtid_info(), + ER_THD(thd, ER_SLAVE_CANT_CREATE_CONVERSION), target_table->s->db.str, target_table->s->table_name.str); + } DBUG_RETURN(conv_table); } #endif /* MYSQL_CLIENT */ @@ -1000,7 +1067,7 @@ table_def::table_def(unsigned char *types, ulong size, int index= 0; for (unsigned int i= 0; i < m_size; i++) { - switch (m_type[i]) { + switch (binlog_type(i)) { case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: @@ -1049,6 +1116,11 @@ table_def::table_def(unsigned char *types, ulong size, m_field_metadata[i]= x; break; } + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_TIMESTAMP2: + m_field_metadata[i]= field_metadata[index++]; + break; default: m_field_metadata[i]= 0; break; @@ -1077,7 +1149,7 @@ table_def::~table_def() @return TRUE if test fails FALSE as success */ -bool event_checksum_test(uchar *event_buf, ulong event_len, uint8 alg) +bool event_checksum_test(uchar *event_buf, ulong event_len, enum enum_binlog_checksum_alg alg) { bool res= FALSE; uint16 flags= 0; // to store in FD's buffer flags orig value @@ -1111,26 +1183,24 @@ bool event_checksum_test(uchar *event_buf, ulong event_len, uint8 alg) compile_time_assert(BINLOG_CHECKSUM_ALG_ENUM_END <= 0x80); } incoming= uint4korr(event_buf + event_len - BINLOG_CHECKSUM_LEN); - computed= my_checksum(0L, NULL, 0); - /* checksum the event content but the checksum part itself */ - computed= my_checksum(computed, (const uchar*) event_buf, - event_len - BINLOG_CHECKSUM_LEN); + /* checksum the event content without the checksum part itself */ + computed= my_checksum(0, event_buf, event_len - BINLOG_CHECKSUM_LEN); if (flags != 0) { /* restoring the orig value of flags of FD */ DBUG_ASSERT(event_buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT); event_buf[FLAGS_OFFSET]= (uchar) flags; } - res= !(computed == incoming); + res= DBUG_EVALUATE_IF("simulate_checksum_test_failure", TRUE, computed != incoming); } - return DBUG_EVALUATE_IF("simulate_checksum_test_failure", TRUE, res); + return res; } #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) Deferred_log_events::Deferred_log_events(Relay_log_info *rli) : last_added(NULL) { - my_init_dynamic_array(&array, sizeof(Log_event *), 32, 16); + my_init_dynamic_array(&array, sizeof(Log_event *), 32, 16, MYF(0)); } Deferred_log_events::~Deferred_log_events() @@ -1150,27 +1220,27 @@ bool Deferred_log_events::is_empty() return array.elements == 0; } -bool Deferred_log_events::execute(Relay_log_info *rli) +bool Deferred_log_events::execute(rpl_group_info *rgi) { bool res= false; DBUG_ENTER("Deferred_log_events::execute"); - DBUG_ASSERT(rli->deferred_events_collecting); + DBUG_ASSERT(rgi->deferred_events_collecting); - rli->deferred_events_collecting= false; + rgi->deferred_events_collecting= false; for (uint i= 0; !res && i < array.elements; i++) { Log_event *ev= (* (Log_event **) dynamic_array_ptr(&array, i)); - res= ev->apply_event(rli); + res= ev->apply_event(rgi); } - rli->deferred_events_collecting= true; + rgi->deferred_events_collecting= true; DBUG_RETURN(res); } void Deferred_log_events::rewind() { /* - Reset preceeding Query log event events which execution was + Reset preceding Query log event events which execution was deferred because of slave side filtering. */ if (!is_empty()) @@ -1189,4 +1259,3 @@ void Deferred_log_events::rewind() } #endif - diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h index 79f4517c492..ed0ce16363b 100644 --- a/sql/rpl_utility.h +++ b/sql/rpl_utility.h @@ -30,6 +30,7 @@ class Relay_log_info; class Log_event; +struct rpl_group_info; /** A table definition from the master. @@ -65,6 +66,14 @@ public: ulong size() const { return m_size; } + /** + Returns internal binlog type code for one field, + without translation to real types. + */ + enum_field_types binlog_type(ulong index) const + { + return static_cast<enum_field_types>(m_type[index]); + } /* Return a representation of the type data for one field. @@ -82,7 +91,7 @@ public: either MYSQL_TYPE_STRING, MYSQL_TYPE_ENUM, or MYSQL_TYPE_SET, so we might need to modify the type to get the real type. */ - enum_field_types source_type= static_cast<enum_field_types>(m_type[index]); + enum_field_types source_type= binlog_type(index); uint16 source_metadata= m_field_metadata[index]; switch (source_type) { @@ -179,7 +188,7 @@ public: @retval 0 if the table definition is compatible with @c table */ #ifndef MYSQL_CLIENT - bool compatible_with(THD *thd, Relay_log_info *rli, TABLE *table, + bool compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, TABLE **conv_table_var) const; /** @@ -204,7 +213,8 @@ public: @return A pointer to a temporary table with memory allocated in the thread's memroot, NULL if the table could not be created */ - TABLE *create_conversion_table(THD *thd, Relay_log_info *rli, TABLE *target_table) const; + TABLE *create_conversion_table(THD *thd, rpl_group_info *rgi, + TABLE *target_table) const; #endif @@ -230,6 +240,7 @@ struct RPL_TABLE_LIST bool m_tabledef_valid; table_def m_tabledef; TABLE *m_conv_table; + bool master_had_triggers; }; @@ -275,7 +286,7 @@ public: /* queue for exection at Query-log-event time prior the Query */ int add(Log_event *ev); bool is_empty(); - bool execute(Relay_log_info *rli); + bool execute(struct rpl_group_info *rgi); void rewind(); bool is_last(Log_event *ev) { return ev == last_added; }; }; @@ -287,7 +298,7 @@ public: do { \ char buf[256]; \ uint i; \ - for (i = 0 ; i < min(sizeof(buf) - 1, (BS)->n_bits) ; i++) \ + for (i = 0 ; i < MY_MIN(sizeof(buf) - 1, (BS)->n_bits) ; i++) \ buf[i] = bitmap_is_set((BS), i) ? '1' : '0'; \ buf[i] = '\0'; \ DBUG_PRINT((N), ((FRM), buf)); \ diff --git a/sql/scheduler.cc b/sql/scheduler.cc index 1cdda089a6b..bc3166210b5 100644 --- a/sql/scheduler.cc +++ b/sql/scheduler.cc @@ -27,6 +27,7 @@ #include "mysqld.h" #include "sql_class.h" #include "sql_callback.h" +#include <violite.h> /* End connection, in case when we are using 'no-threads' @@ -61,6 +62,15 @@ static void scheduler_wait_sync_begin(void) { static void scheduler_wait_sync_end(void) { thd_wait_end(NULL); } + +static void scheduler_wait_net_begin(void) { + thd_wait_begin(NULL, THD_WAIT_NET); +} + +static void scheduler_wait_net_end(void) { + thd_wait_end(NULL); +} + }; /**@}*/ @@ -76,6 +86,9 @@ void scheduler_init() { scheduler_wait_lock_end); thr_set_sync_wait_callback(scheduler_wait_sync_begin, scheduler_wait_sync_end); + + vio_set_wait_callback(scheduler_wait_net_begin, + scheduler_wait_net_end); } @@ -122,7 +135,7 @@ void one_thread_per_connection_scheduler(scheduler_functions *func, #endif /* - Initailize scheduler for --thread-handling=no-threads + Initialize scheduler for --thread-handling=no-threads */ void one_thread_scheduler(scheduler_functions *func) @@ -138,52 +151,3 @@ void one_thread_scheduler(scheduler_functions *func) func->end_thread= no_threads_end; } - - -/* - no pluggable schedulers in mariadb. - when we'll want it, we'll do it properly -*/ -#if 0 - -static scheduler_functions *saved_thread_scheduler; -static uint saved_thread_handling; - -extern "C" -int my_thread_scheduler_set(scheduler_functions *scheduler) -{ - DBUG_ASSERT(scheduler != 0); - - if (scheduler == NULL) - return 1; - - saved_thread_scheduler= thread_scheduler; - saved_thread_handling= thread_handling; - thread_scheduler= scheduler; - // Scheduler loaded dynamically - thread_handling= SCHEDULER_TYPES_COUNT; - return 0; -} - - -extern "C" -int my_thread_scheduler_reset() -{ - DBUG_ASSERT(saved_thread_scheduler != NULL); - - if (saved_thread_scheduler == NULL) - return 1; - - thread_scheduler= saved_thread_scheduler; - thread_handling= saved_thread_handling; - saved_thread_scheduler= 0; - return 0; -} -#else -extern "C" int my_thread_scheduler_set(scheduler_functions *scheduler) -{ return 1; } - -extern "C" int my_thread_scheduler_reset() -{ return 1; } -#endif - diff --git a/sql/set_var.cc b/sql/set_var.cc index 9efa29d8041..b5430c56865 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -14,13 +14,9 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifdef USE_PRAGMA_IMPLEMENTATION -#pragma implementation -#endif - /* variable declarations are in sys_vars.cc now !!! */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" // Includes my_global.h #include "sql_class.h" // set_var.h: session_var_ptr #include "set_var.h" #include "sql_priv.h" @@ -39,6 +35,7 @@ #include "tztime.h" // my_tz_find, my_tz_SYSTEM, struct Time_zone #include "sql_acl.h" // SUPER_ACL #include "sql_select.h" // free_underlaid_joins +#include "sql_show.h" #include "sql_view.h" // updatable_views_with_limit_typelib #include "lock.h" // lock_global_read_lock, // make_global_read_lock_block_commit, @@ -146,8 +143,7 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg, on_check_function on_check_func, on_update_function on_update_func, const char *substitute) : - next(0), - binlog_status(binlog_status_arg), + next(0), binlog_status(binlog_status_arg), value_origin(COMPILE_TIME), flags(flags_arg), show_val_type(show_val_type_arg), guard(lock), offset(off), on_check(on_check_func), on_update(on_update_func), deprecation_substitute(substitute), @@ -175,6 +171,8 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg, option.arg_type= getopt_arg_type; option.value= (uchar **)global_var_ptr(); option.def_value= def_val; + option.app_type= this; + option.var_type= flags & AUTO_SET ? GET_AUTO : 0; if (chain->last) chain->last->next= this; @@ -196,6 +194,7 @@ bool sys_var::update(THD *thd, set_var *var) */ AutoWLock lock1(&PLock_global_system_variables); AutoWLock lock2(guard); + value_origin= SQL; return global_update(thd, var) || (on_update && on_update(this, thd, OPT_GLOBAL)); } @@ -204,12 +203,12 @@ bool sys_var::update(THD *thd, set_var *var) (on_update && on_update(this, thd, OPT_SESSION)); } -uchar *sys_var::session_value_ptr(THD *thd, LEX_STRING *base) +uchar *sys_var::session_value_ptr(THD *thd, const LEX_STRING *base) { return session_var_ptr(thd); } -uchar *sys_var::global_value_ptr(THD *thd, LEX_STRING *base) +uchar *sys_var::global_value_ptr(THD *thd, const LEX_STRING *base) { return global_var_ptr(); } @@ -242,8 +241,9 @@ bool sys_var::check(THD *thd, set_var *var) return false; } -uchar *sys_var::value_ptr(THD *thd, enum_var_type type, LEX_STRING *base) +uchar *sys_var::value_ptr(THD *thd, enum_var_type type, const LEX_STRING *base) { + DBUG_ASSERT(base); if (type == OPT_GLOBAL || scope() == GLOBAL) { mysql_mutex_assert_owner(&LOCK_global_system_variables); @@ -264,6 +264,129 @@ bool sys_var::set_default(THD *thd, set_var* var) return check(thd, var) || update(thd, var); } + +#define do_num_val(T,CMD) \ +do { \ + T val= *(T*) value; \ + CMD; \ +} while (0) + +#define case_for_integers(CMD) \ + case SHOW_SINT: do_num_val (int,CMD); \ + case SHOW_SLONG: do_num_val (long,CMD); \ + case SHOW_SLONGLONG:do_num_val (longlong,CMD); \ + case SHOW_UINT: do_num_val (uint,CMD); \ + case SHOW_ULONG: do_num_val (ulong,CMD); \ + case SHOW_ULONGLONG:do_num_val (ulonglong,CMD); \ + case SHOW_HA_ROWS: do_num_val (ha_rows,CMD); + +#define case_for_double(CMD) \ + case SHOW_DOUBLE: do_num_val (double,CMD) + +#define case_get_string_as_lex_string \ + case SHOW_CHAR: \ + sval.str= (char*) value; \ + sval.length= sval.str ? strlen(sval.str) : 0; \ + break; \ + case SHOW_CHAR_PTR: \ + sval.str= *(char**) value; \ + sval.length= sval.str ? strlen(sval.str) : 0; \ + break; \ + case SHOW_LEX_STRING: \ + sval= *(LEX_STRING *) value; \ + break + +longlong sys_var::val_int(bool *is_null, + THD *thd, enum_var_type type, const LEX_STRING *base) +{ + LEX_STRING sval; + AutoWLock lock(&PLock_global_system_variables); + const uchar *value= value_ptr(thd, type, base); + *is_null= false; + + switch (show_type()) + { + case_get_string_as_lex_string; + case_for_integers(return val); + case_for_double(return (longlong) val); + case SHOW_MY_BOOL: return *(my_bool*)value; + default: + my_error(ER_VAR_CANT_BE_READ, MYF(0), name.str); + return 0; + } + + longlong ret= 0; + if (!(*is_null= !sval.str)) + ret= longlong_from_string_with_check(charset(thd), + sval.str, sval.str + sval.length); + return ret; +} + + +String *sys_var::val_str_nolock(String *str, THD *thd, const uchar *value) +{ + static LEX_STRING bools[]= + { + { C_STRING_WITH_LEN("OFF") }, + { C_STRING_WITH_LEN("ON") } + }; + + LEX_STRING sval; + switch (show_type()) + { + case_get_string_as_lex_string; + case_for_integers(return str->set(val, system_charset_info) ? 0 : str); + case_for_double(return str->set_real(val, 6, system_charset_info) ? 0 : str); + case SHOW_MY_BOOL: + sval= bools[(int)*(my_bool*)value]; + break; + default: + my_error(ER_VAR_CANT_BE_READ, MYF(0), name.str); + return 0; + } + + if (!sval.str || str->copy(sval.str, sval.length, charset(thd))) + str= NULL; + return str; +} + + +String *sys_var::val_str(String *str, + THD *thd, enum_var_type type, const LEX_STRING *base) +{ + AutoWLock lock(&PLock_global_system_variables); + const uchar *value= value_ptr(thd, type, base); + return val_str_nolock(str, thd, value); +} + + +double sys_var::val_real(bool *is_null, + THD *thd, enum_var_type type, const LEX_STRING *base) +{ + LEX_STRING sval; + AutoWLock lock(&PLock_global_system_variables); + const uchar *value= value_ptr(thd, type, base); + *is_null= false; + + switch (show_type()) + { + case_get_string_as_lex_string; + case_for_integers(return val); + case_for_double(return val); + case SHOW_MY_BOOL: return *(my_bool*)value; + default: + my_error(ER_VAR_CANT_BE_READ, MYF(0), name.str); + return 0; + } + + double ret= 0; + if (!(*is_null= !sval.str)) + ret= double_from_string_with_check(charset(thd), + sval.str, sval.str + sval.length); + return ret; +} + + void sys_var::do_deprecated_warning(THD *thd) { if (deprecation_substitute != NULL) @@ -279,8 +402,8 @@ void sys_var::do_deprecated_warning(THD *thd) ? ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT : ER_WARN_DEPRECATED_SYNTAX; if (thd) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_DEPRECATED_SYNTAX, ER(errmsg), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_DEPRECATED_SYNTAX, ER_THD(thd, errmsg), buf1, deprecation_substitute); else sql_print_warning(ER_DEFAULT(errmsg), buf1, deprecation_substitute); @@ -311,14 +434,14 @@ bool throw_bounds_warning(THD *thd, const char *name, else llstr(v, buf); - if (thd->variables.sql_mode & MODE_STRICT_ALL_TABLES) + if (thd->is_strict_mode()) { my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name, buf); return true; } - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), name, buf); + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), name, buf); } return false; } @@ -331,14 +454,14 @@ bool throw_bounds_warning(THD *thd, const char *name, bool fixed, double v) my_gcvt(v, MY_GCVT_ARG_DOUBLE, sizeof(buf) - 1, buf, NULL); - if (thd->variables.sql_mode & MODE_STRICT_ALL_TABLES) + if (thd->is_strict_mode()) { my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name, buf); return true; } - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE, - ER(ER_TRUNCATED_WRONG_VALUE), name, buf); + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), name, buf); } return false; } @@ -349,6 +472,7 @@ CHARSET_INFO *sys_var::charset(THD *thd) system_charset_info; } + typedef struct old_names_map_st { const char *old_name; @@ -461,7 +585,7 @@ static int show_cmp(SHOW_VAR *a, SHOW_VAR *b) @param thd current thread @param sorted If TRUE, the system variables should be sorted - @param type OPT_GLOBAL or OPT_SESSION for SHOW GLOBAL|SESSION VARIABLES + @param scope OPT_GLOBAL or OPT_SESSION for SHOW GLOBAL|SESSION VARIABLES @retval pointer Array of SHOW_VAR elements for display @@ -469,7 +593,7 @@ static int show_cmp(SHOW_VAR *a, SHOW_VAR *b) NULL FAILURE */ -SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type) +SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type scope) { int count= system_variable_hash.records, i; int size= sizeof(SHOW_VAR) * (count + 1); @@ -484,7 +608,7 @@ SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type) sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i); // don't show session-only variables in SHOW GLOBAL VARIABLES - if (type == OPT_GLOBAL && var->check_type(type)) + if (scope == OPT_GLOBAL && var->check_type(scope)) continue; show->name= var->name.str; @@ -527,6 +651,7 @@ sys_var *intern_find_sys_var(const char *str, uint length) */ var= (sys_var*) my_hash_search(&system_variable_hash, (uchar*) str, length ? length : strlen(str)); + return var; } @@ -551,9 +676,10 @@ sys_var *intern_find_sys_var(const char *str, uint length) -1 ERROR, message not sent */ -int sql_set_variables(THD *thd, List<set_var_base> *var_list) +int sql_set_variables(THD *thd, List<set_var_base> *var_list, bool free) { - int error; + int error= 0; + bool was_error= thd->is_error(); List_iterator_fast<set_var_base> it(*var_list); DBUG_ENTER("sql_set_variables"); @@ -563,7 +689,7 @@ int sql_set_variables(THD *thd, List<set_var_base> *var_list) if ((error= var->check(thd))) goto err; } - if (!(error= test(thd->is_error()))) + if (was_error || !(error= MY_TEST(thd->is_error()))) { it.rewind(); while ((var= it++)) @@ -571,7 +697,8 @@ int sql_set_variables(THD *thd, List<set_var_base> *var_list) } err: - free_underlaid_joins(thd, &thd->lex->select_lex); + if (free) + free_underlaid_joins(thd, &thd->lex->select_lex); DBUG_RETURN(error); } @@ -612,7 +739,7 @@ int set_var::check(THD *thd) if ((!value->fixed && value->fix_fields(thd, &value)) || value->check_cols(1)) return -1; - if (var->check_update_type(value->result_type())) + if (var->check_update_type(value)) { my_error(ER_WRONG_TYPE_FOR_VAR, MYF(0), var->name.str); return -1; @@ -662,12 +789,33 @@ int set_var::light_check(THD *thd) Consider set_var::check() method if there is a need to return an error due to logics. */ + int set_var::update(THD *thd) { return value ? var->update(thd, this) : var->set_default(thd, this); } +set_var::set_var(THD *thd, enum_var_type type_arg, sys_var *var_arg, + const LEX_STRING *base_name_arg, Item *value_arg) + :var(var_arg), type(type_arg), base(*base_name_arg) +{ + /* + If the set value is a field, change it to a string to allow things like + SET table_type=MYISAM; + */ + if (value_arg && value_arg->type() == Item::FIELD_ITEM) + { + Item_field *item= (Item_field*) value_arg; + // names are utf8 + if (!(value= new (thd->mem_root) Item_string_sys(thd, item->field_name))) + value=value_arg; /* Give error message later */ + } + else + value=value_arg; +} + + /***************************************************************************** Functions to handle SET @user_variable=const_expr *****************************************************************************/ @@ -710,7 +858,8 @@ int set_var_user::update(THD *thd) if (user_var_item->update()) { /* Give an error if it's not given already */ - my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), MYF(0)); + my_message(ER_SET_CONSTANTS_ONLY, ER_THD(thd, ER_SET_CONSTANTS_ONLY), + MYF(0)); return -1; } return 0; @@ -724,29 +873,7 @@ int set_var_user::update(THD *thd) int set_var_password::check(THD *thd) { #ifndef NO_EMBEDDED_ACCESS_CHECKS - if (!user->host.str) - { - DBUG_ASSERT(thd->security_ctx->priv_host); - if (*thd->security_ctx->priv_host != 0) - { - user->host.str= (char *) thd->security_ctx->priv_host; - user->host.length= strlen(thd->security_ctx->priv_host); - } - else - { - user->host.str= (char *)"%"; - user->host.length= 1; - } - } - if (!user->user.str) - { - DBUG_ASSERT(thd->security_ctx->user); - user->user.str= (char *) thd->security_ctx->user; - user->user.length= strlen(thd->security_ctx->user); - } - /* Returns 1 as the function sends error to client */ - return check_change_password(thd, user->host.str, user->user.str, - password, strlen(password)) ? 1 : 0; + return check_change_password(thd, user); #else return 0; #endif @@ -755,9 +882,62 @@ int set_var_password::check(THD *thd) int set_var_password::update(THD *thd) { #ifndef NO_EMBEDDED_ACCESS_CHECKS - /* Returns 1 as the function sends error to client */ - return change_password(thd, user->host.str, user->user.str, password) ? - 1 : 0; + Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; + thd->m_reprepare_observer= 0; + int res= change_password(thd, user); + thd->m_reprepare_observer= save_reprepare_observer; + return res; +#else + return 0; +#endif +} + +/***************************************************************************** + Functions to handle SET ROLE +*****************************************************************************/ + +int set_var_role::check(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + int status= acl_check_setrole(thd, role.str, &access); + return status; +#else + return 0; +#endif +} + +int set_var_role::update(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + return acl_setrole(thd, role.str, access); +#else + return 0; +#endif +} + +/***************************************************************************** + Functions to handle SET DEFAULT ROLE +*****************************************************************************/ + +int set_var_default_role::check(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + real_user= get_current_user(thd, user); + int status= acl_check_set_default_role(thd, real_user->host.str, real_user->user.str); + return status; +#else + return 0; +#endif +} + +int set_var_default_role::update(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; + thd->m_reprepare_observer= 0; + int res= acl_set_default_role(thd, real_user->host.str, real_user->user.str, role.str); + thd->m_reprepare_observer= save_reprepare_observer; + return res; #else return 0; #endif @@ -790,3 +970,250 @@ int set_var_collation_client::update(THD *thd) return 0; } +/***************************************************************************** + INFORMATION_SCHEMA.SYSTEM_VARIABLES +*****************************************************************************/ +static void store_value_ptr(Field *field, sys_var *var, String *str, + uchar *value_ptr) +{ + field->set_notnull(); + str= var->val_str_nolock(str, field->table->in_use, value_ptr); + if (str) + field->store(str->ptr(), str->length(), str->charset()); +} + +static void store_var(Field *field, sys_var *var, enum_var_type scope, + String *str) +{ + if (var->check_type(scope)) + return; + + store_value_ptr(field, var, str, + var->value_ptr(field->table->in_use, scope, &null_lex_str)); +} + +int fill_sysvars(THD *thd, TABLE_LIST *tables, COND *cond) +{ + char name_buffer[NAME_CHAR_LEN]; + enum_check_fields save_count_cuted_fields= thd->count_cuted_fields; + bool res= 1; + CHARSET_INFO *scs= system_charset_info; + StringBuffer<STRING_BUFFER_USUAL_SIZE> strbuf(scs); + const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : 0; + Field **fields=tables->table->field; + + DBUG_ASSERT(tables->table->in_use == thd); + + cond= make_cond_for_info_schema(thd, cond, tables); + thd->count_cuted_fields= CHECK_FIELD_WARN; + mysql_rwlock_rdlock(&LOCK_system_variables_hash); + + for (uint i= 0; i < system_variable_hash.records; i++) + { + sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i); + + strmake_buf(name_buffer, var->name.str); + my_caseup_str(system_charset_info, name_buffer); + + /* this must be done before evaluating cond */ + restore_record(tables->table, s->default_values); + fields[0]->store(name_buffer, strlen(name_buffer), scs); + + if ((wild && wild_case_compare(system_charset_info, name_buffer, wild)) + || (cond && !cond->val_int())) + continue; + + mysql_mutex_lock(&LOCK_global_system_variables); + + // SESSION_VALUE + store_var(fields[1], var, OPT_SESSION, &strbuf); + + // GLOBAL_VALUE + store_var(fields[2], var, OPT_GLOBAL, &strbuf); + + // GLOBAL_VALUE_ORIGIN + static const LEX_CSTRING origins[]= + { + { STRING_WITH_LEN("CONFIG") }, + { STRING_WITH_LEN("AUTO") }, + { STRING_WITH_LEN("SQL") }, + { STRING_WITH_LEN("COMPILE-TIME") }, + { STRING_WITH_LEN("ENVIRONMENT") } + }; + const LEX_CSTRING *origin= origins + var->value_origin; + fields[3]->store(origin->str, origin->length, scs); + + // DEFAULT_VALUE + uchar *def= var->is_readonly() && var->option.id < 0 + ? 0 : var->default_value_ptr(thd); + if (def) + store_value_ptr(fields[4], var, &strbuf, def); + + mysql_mutex_unlock(&LOCK_global_system_variables); + + // VARIABLE_SCOPE + static const LEX_CSTRING scopes[]= + { + { STRING_WITH_LEN("GLOBAL") }, + { STRING_WITH_LEN("SESSION") }, + { STRING_WITH_LEN("SESSION ONLY") } + }; + const LEX_CSTRING *scope= scopes + var->scope(); + fields[5]->store(scope->str, scope->length, scs); + + // VARIABLE_TYPE +#if SIZEOF_LONG == SIZEOF_INT +#define LONG_TYPE "INT" +#else +#define LONG_TYPE "BIGINT" +#endif + + static const LEX_CSTRING types[]= + { + { 0, 0 }, // unused 0 + { 0, 0 }, // GET_NO_ARG 1 + { STRING_WITH_LEN("BOOLEAN") }, // GET_BOOL 2 + { STRING_WITH_LEN("INT") }, // GET_INT 3 + { STRING_WITH_LEN("INT UNSIGNED") }, // GET_UINT 4 + { STRING_WITH_LEN(LONG_TYPE) }, // GET_LONG 5 + { STRING_WITH_LEN(LONG_TYPE " UNSIGNED") }, // GET_ULONG 6 + { STRING_WITH_LEN("BIGINT") }, // GET_LL 7 + { STRING_WITH_LEN("BIGINT UNSIGNED") }, // GET_ULL 8 + { STRING_WITH_LEN("VARCHAR") }, // GET_STR 9 + { STRING_WITH_LEN("VARCHAR") }, // GET_STR_ALLOC 10 + { 0, 0 }, // GET_DISABLED 11 + { STRING_WITH_LEN("ENUM") }, // GET_ENUM 12 + { STRING_WITH_LEN("SET") }, // GET_SET 13 + { STRING_WITH_LEN("DOUBLE") }, // GET_DOUBLE 14 + { STRING_WITH_LEN("FLAGSET") }, // GET_FLAGSET 15 + }; + const ulong vartype= (var->option.var_type & GET_TYPE_MASK); + const LEX_CSTRING *type= types + vartype; + fields[6]->store(type->str, type->length, scs); + + // VARIABLE_COMMENT + fields[7]->store(var->option.comment, strlen(var->option.comment), + scs); + + // NUMERIC_MIN_VALUE + // NUMERIC_MAX_VALUE + // NUMERIC_BLOCK_SIZE + bool is_unsigned= true; + switch (vartype) + { + case GET_INT: + case GET_LONG: + case GET_LL: + is_unsigned= false; + /* fall through */ + case GET_UINT: + case GET_ULONG: + case GET_ULL: + fields[8]->set_notnull(); + fields[9]->set_notnull(); + fields[10]->set_notnull(); + fields[8]->store(var->option.min_value, is_unsigned); + fields[9]->store(var->option.max_value, is_unsigned); + fields[10]->store(var->option.block_size, is_unsigned); + break; + case GET_DOUBLE: + fields[8]->set_notnull(); + fields[9]->set_notnull(); + fields[8]->store(getopt_ulonglong2double(var->option.min_value)); + fields[9]->store(getopt_ulonglong2double(var->option.max_value)); + } + + // ENUM_VALUE_LIST + TYPELIB *tl= var->option.typelib; + if (tl) + { + uint i; + strbuf.length(0); + for (i=0; i + 1 < tl->count; i++) + { + strbuf.append(tl->type_names[i]); + strbuf.append(','); + } + strbuf.append(tl->type_names[i]); + fields[11]->set_notnull(); + fields[11]->store(strbuf.ptr(), strbuf.length(), scs); + } + + // READ_ONLY + static const LEX_CSTRING yesno[]= + { + { STRING_WITH_LEN("NO") }, + { STRING_WITH_LEN("YES") } + }; + const LEX_CSTRING *yn = yesno + var->is_readonly(); + fields[12]->store(yn->str, yn->length, scs); + + // COMMAND_LINE_ARGUMENT + if (var->option.id >= 0) + { + static const LEX_CSTRING args[]= + { + { STRING_WITH_LEN("NONE") }, // NO_ARG + { STRING_WITH_LEN("OPTIONAL") }, // OPT_ARG + { STRING_WITH_LEN("REQUIRED") } // REQUIRED_ARG + }; + const LEX_CSTRING *arg= args + var->option.arg_type; + fields[13]->set_notnull(); + fields[13]->store(arg->str, arg->length, scs); + } + + if (schema_table_store_record(thd, tables->table)) + goto end; + thd->get_stmt_da()->inc_current_row_for_warning(); + } + res= 0; +end: + mysql_rwlock_unlock(&LOCK_system_variables_hash); + thd->count_cuted_fields= save_count_cuted_fields; + return res; +} + +/* + This is a simple and inefficient helper that sets sys_var::value_origin + for a specific sysvar. + It should *only* be used on server startup, if you need to do this later, + get yourself a pointer to your sysvar (see e.g. Sys_autocommit_ptr) + and update it directly. +*/ + +void set_sys_var_value_origin(void *ptr, enum sys_var::where here) +{ + bool found __attribute__((unused))= false; + DBUG_ASSERT(!mysqld_server_started); // only to be used during startup + + for (uint i= 0; i < system_variable_hash.records; i++) + { + sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i); + if (var->option.value == ptr) + { + found= true; + var->value_origin= here; + /* don't break early, search for all matches */ + } + } + + DBUG_ASSERT(found); // variable must have been found +} + +enum sys_var::where get_sys_var_value_origin(void *ptr) +{ + DBUG_ASSERT(!mysqld_server_started); // only to be used during startup + + for (uint i= 0; i < system_variable_hash.records; i++) + { + sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i); + if (var->option.value == ptr) + { + return var->value_origin; //first match + } + } + + DBUG_ASSERT(0); // variable must have been found + return sys_var::CONFIG; +} + diff --git a/sql/set_var.h b/sql/set_var.h index 32207e834d9..203969d6169 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -55,13 +55,17 @@ int mysql_del_sys_var_chain(sys_var *chain); optionally it can be assigned to, optionally it can have a command-line counterpart with the same name. */ -class sys_var +class sys_var: protected Value_source // for double_from_string_with_check { public: sys_var *next; LEX_CSTRING name; enum flag_enum { GLOBAL, SESSION, ONLY_SESSION, SCOPE_MASK=1023, - READONLY=1024, ALLOCATED=2048, PARSE_EARLY=4096 }; + READONLY=1024, ALLOCATED=2048, PARSE_EARLY=4096, + NO_SET_STATEMENT=8192, AUTO_SET=16384}; + enum { NO_GETOPT=-1, GETOPT_ONLY_HELP=-2 }; + enum where { CONFIG, AUTO, SQL, COMPILE_TIME, ENV }; + /** Enumeration type to indicate for a system variable whether it will be written to the binlog or not. @@ -69,13 +73,15 @@ public: enum binlog_status_enum { VARIABLE_NOT_IN_BINLOG, SESSION_VARIABLE_IN_BINLOG } binlog_status; + my_option option; ///< min, max, default values are stored here + enum where value_origin; + protected: typedef bool (*on_check_function)(sys_var *self, THD *thd, set_var *var); typedef bool (*on_update_function)(sys_var *self, THD *thd, enum_var_type type); int flags; ///< or'ed flag_enum values const SHOW_TYPE show_val_type; ///< what value_ptr() returns for sql_show.cc - my_option option; ///< min, max, default values are stored here PolyLock *guard; ///< *second* lock that protects the variable ptrdiff_t offset; ///< offset to the value from global_system_variables on_check_function on_check; @@ -104,7 +110,7 @@ public: virtual sys_var_pluginvar *cast_pluginvar() { return 0; } bool check(THD *thd, set_var *var); - uchar *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base); + uchar *value_ptr(THD *thd, enum_var_type type, const LEX_STRING *base); /** Update the system variable with the default value from either @@ -114,6 +120,11 @@ public: bool set_default(THD *thd, set_var *var); bool update(THD *thd, set_var *var); + String *val_str_nolock(String *str, THD *thd, const uchar *value); + longlong val_int(bool *is_null, THD *thd, enum_var_type type, const LEX_STRING *base); + String *val_str(String *str, THD *thd, enum_var_type type, const LEX_STRING *base); + double val_real(bool *is_null, THD *thd, enum_var_type type, const LEX_STRING *base); + SHOW_TYPE show_type() { return show_val_type; } int scope() const { return flags & SCOPE_MASK; } CHARSET_INFO *charset(THD *thd); @@ -123,9 +134,36 @@ public: that support the syntax @@keycache_name.variable_name */ bool is_struct() { return option.var_type & GET_ASK_ADDR; } + bool is_set_stmt_ok() const { return !(flags & NO_SET_STATEMENT); } bool is_written_to_binlog(enum_var_type type) { return type != OPT_GLOBAL && binlog_status == SESSION_VARIABLE_IN_BINLOG; } - virtual bool check_update_type(Item_result type) = 0; + bool check_update_type(const Item *item) + { + Item_result type= item->result_type(); + switch (option.var_type & GET_TYPE_MASK) { + case GET_INT: + case GET_UINT: + case GET_LONG: + case GET_ULONG: + case GET_LL: + case GET_ULL: + return type != INT_RESULT && + (type != DECIMAL_RESULT || item->decimals != 0); + case GET_STR: + case GET_STR_ALLOC: + return type != STRING_RESULT; + case GET_ENUM: + case GET_BOOL: + case GET_SET: + case GET_FLAGSET: + return type != STRING_RESULT && type != INT_RESULT; + case GET_DOUBLE: + return type != INT_RESULT && type != REAL_RESULT && type != DECIMAL_RESULT; + default: + return true; + } + } + bool check_type(enum_var_type type) { switch (scope()) @@ -138,10 +176,37 @@ public: } bool register_option(DYNAMIC_ARRAY *array, int parse_flags) { - return (option.id != -1) && ((flags & PARSE_EARLY) == parse_flags) && - insert_dynamic(array, (uchar*)&option); + DBUG_ASSERT(parse_flags == GETOPT_ONLY_HELP || + parse_flags == PARSE_EARLY || parse_flags == 0); + if (option.id == NO_GETOPT) + return 0; + if (parse_flags == GETOPT_ONLY_HELP) + { + if (option.id != GETOPT_ONLY_HELP) + return 0; + } + else + { + if (option.id == GETOPT_ONLY_HELP) + return 0; + if ((flags & PARSE_EARLY) != parse_flags) + return 0; + } + return insert_dynamic(array, (uchar*)&option); } void do_deprecated_warning(THD *thd); + /** + whether session value of a sysvar is a default one. + + in this simple implementation we don't distinguish between default + and non-default values. for most variables it's ok, they don't treat + default values specially. this method is overwritten in descendant + classes as necessary. + */ + virtual bool session_is_default(THD *thd) { return false; } + + virtual uchar *default_value_ptr(THD *thd) + { return (uchar*)&option.def_value; } private: virtual bool do_check(THD *thd, set_var *var) = 0; @@ -159,11 +224,11 @@ private: protected: /** A pointer to a value of the variable for SHOW. - It must be of show_val_type type (bool for SHOW_BOOL, int for SHOW_INT, - longlong for SHOW_LONGLONG, etc). + It must be of show_val_type type (my_bool for SHOW_MY_BOOL, + int for SHOW_INT, longlong for SHOW_LONGLONG, etc). */ - virtual uchar *session_value_ptr(THD *thd, LEX_STRING *base); - virtual uchar *global_value_ptr(THD *thd, LEX_STRING *base); + virtual uchar *session_value_ptr(THD *thd, const LEX_STRING *base); + virtual uchar *global_value_ptr(THD *thd, const LEX_STRING *base); /** A pointer to a storage area of the variable, to the raw data. @@ -197,6 +262,7 @@ public: virtual int check(THD *thd)=0; /* To check privileges etc. */ virtual int update(THD *thd)=0; /* To set the value */ virtual int light_check(THD *thd) { return check(thd); } /* for PS */ + virtual bool is_system() { return FALSE; } }; @@ -221,25 +287,9 @@ public: } save_result; LEX_STRING base; /**< for structured variables, like keycache_name.variable_name */ - set_var(enum_var_type type_arg, sys_var *var_arg, - const LEX_STRING *base_name_arg, Item *value_arg) - :var(var_arg), type(type_arg), base(*base_name_arg) - { - /* - If the set value is a field, change it to a string to allow things like - SET table_type=MYISAM; - */ - if (value_arg && value_arg->type() == Item::FIELD_ITEM) - { - Item_field *item= (Item_field*) value_arg; - if (!(value=new Item_string(item->field_name, - (uint) strlen(item->field_name), - system_charset_info))) // names are utf8 - value=value_arg; /* Give error message later */ - } - else - value=value_arg; - } + set_var(THD *thd, enum_var_type type_arg, sys_var *var_arg, + const LEX_STRING *base_name_arg, Item *value_arg); + virtual bool is_system() { return 1; } int check(THD *thd); int update(THD *thd); int light_check(THD *thd); @@ -264,15 +314,37 @@ public: class set_var_password: public set_var_base { LEX_USER *user; - char *password; public: - set_var_password(LEX_USER *user_arg,char *password_arg) - :user(user_arg), password(password_arg) + set_var_password(LEX_USER *user_arg) :user(user_arg) {} int check(THD *thd); int update(THD *thd); }; +/* For SET ROLE */ + +class set_var_role: public set_var_base +{ + LEX_STRING role; + ulonglong access; +public: + set_var_role(LEX_STRING role_arg) : role(role_arg) {} + int check(THD *thd); + int update(THD *thd); +}; + +/* For SET DEFAULT ROLE */ + +class set_var_default_role: public set_var_base +{ + LEX_USER *user, *real_user; + LEX_STRING role; +public: + set_var_default_role(LEX_USER *user_arg, LEX_STRING role_arg) : + user(user_arg), role(role_arg) {} + int check(THD *thd); + int update(THD *thd); +}; /* For SET NAMES and SET CHARACTER SET */ @@ -304,20 +376,48 @@ extern SHOW_COMP_OPTION have_query_cache; extern SHOW_COMP_OPTION have_geometry, have_rtree_keys; extern SHOW_COMP_OPTION have_crypt; extern SHOW_COMP_OPTION have_compress; +extern SHOW_COMP_OPTION have_openssl; /* Prototypes for helper functions */ SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type); +int fill_sysvars(THD *thd, TABLE_LIST *tables, COND *cond); sys_var *find_sys_var(THD *thd, const char *str, uint length=0); -int sql_set_variables(THD *thd, List<set_var_base> *var_list); +int sql_set_variables(THD *thd, List<set_var_base> *var_list, bool free); + +#define SYSVAR_AUTOSIZE(VAR,VAL) \ + do { \ + VAR= (VAL); \ + set_sys_var_value_origin(&VAR, sys_var::AUTO); \ + } while(0) + +#define SYSVAR_AUTOSIZE_IF_CHANGED(VAR,VAL,TYPE) \ + do { \ + TYPE tmp= (VAL); \ + if (VAR != tmp) \ + { \ + VAR= (VAL); \ + set_sys_var_value_origin(&VAR, sys_var::AUTO); \ + } \ + } while(0) + +void set_sys_var_value_origin(void *ptr, enum sys_var::where here); + +enum sys_var::where get_sys_var_value_origin(void *ptr); +inline bool IS_SYSVAR_AUTOSIZE(void *ptr) +{ + enum sys_var::where res= get_sys_var_value_origin(ptr); + return (res == sys_var::AUTO || res == sys_var::COMPILE_TIME); +} bool fix_delay_key_write(sys_var *self, THD *thd, enum_var_type type); ulonglong expand_sql_mode(ulonglong sql_mode); bool sql_mode_string_representation(THD *thd, ulonglong sql_mode, LEX_STRING *ls); +int default_regex_flags_pcre(const THD *thd); extern sys_var *Sys_autocommit_ptr; @@ -326,6 +426,7 @@ CHARSET_INFO *get_old_charset_by_name(const char *old_name); int sys_var_init(); int sys_var_add_options(DYNAMIC_ARRAY *long_options, int parse_flags); void sys_var_end(void); +bool check_has_super(sys_var *self, THD *thd, set_var *var); #endif diff --git a/sql/sha2.cc b/sql/sha2.cc deleted file mode 100644 index f2201974172..00000000000 --- a/sql/sha2.cc +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright (c) 2007, 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-1301 USA */ - - -/** - @file - A compatibility layer to our built-in SSL implementation, to mimic the - oft-used external library, OpenSSL. -*/ - -#include <my_global.h> -#include <sha2.h> - -#ifdef HAVE_YASSL - -/* - If TaoCrypt::SHA512 or ::SHA384 are not defined (but ::SHA256 is), it's - probably that neither of config.h's SIZEOF_LONG or SIZEOF_LONG_LONG are - 64 bits long. At present, both OpenSSL and YaSSL require 64-bit integers - for SHA-512. (The SIZEOF_* definitions come from autoconf's config.h .) -*/ - -# define GEN_YASSL_SHA2_BRIDGE(size) \ -unsigned char* SHA##size(const unsigned char *input_ptr, size_t input_length, \ - char unsigned *output_ptr) { \ - TaoCrypt::SHA##size hasher; \ - \ - hasher.Update(input_ptr, input_length); \ - hasher.Final(output_ptr); \ - return(output_ptr); \ -} - - -/** - @fn SHA512 - @fn SHA384 - @fn SHA256 - @fn SHA224 - - Instantiate an hash object, fill in the cleartext value, compute the digest, - and extract the result from the object. - - (Generate the functions. See similar .h code for the prototypes.) -*/ -# ifndef OPENSSL_NO_SHA512 -GEN_YASSL_SHA2_BRIDGE(512); -GEN_YASSL_SHA2_BRIDGE(384); -# else -# warning Some SHA2 functionality is missing. See OPENSSL_NO_SHA512. -# endif -GEN_YASSL_SHA2_BRIDGE(256); -GEN_YASSL_SHA2_BRIDGE(224); - -# undef GEN_YASSL_SHA2_BRIDGE - -#endif /* HAVE_YASSL */ diff --git a/sql/share/CMakeLists.txt b/sql/share/CMakeLists.txt index e0d5fb6c1a7..4293c0b528c 100644 --- a/sql/share/CMakeLists.txt +++ b/sql/share/CMakeLists.txt @@ -44,12 +44,14 @@ SET(files errmsg-utf8.txt ) -FOREACH (dir ${dirs}) - INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${dir} - DESTINATION ${INSTALL_MYSQLSHAREDIR} COMPONENT Server) -ENDFOREACH() INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/charsets DESTINATION ${INSTALL_MYSQLSHAREDIR} COMPONENT Common PATTERN "languages.html" EXCLUDE ) -INSTALL(FILES ${files} DESTINATION ${INSTALL_MYSQLSHAREDIR} COMPONENT Server) +IF (NOT WITHOUT_SERVER) + FOREACH (dir ${dirs}) + INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${dir} + DESTINATION ${INSTALL_MYSQLSHAREDIR} COMPONENT Server) + ENDFOREACH() + INSTALL(FILES ${files} DESTINATION ${INSTALL_MYSQLSHAREDIR} COMPONENT Server) +ENDIF() diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index ee89d8df7b5..14662c0eb38 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -1,4 +1,4 @@ -languages czech=cze latin2, danish=dan latin1, dutch=nla latin1, english=eng latin1, estonian=est latin7, french=fre latin1, german=ger latin1, greek=greek greek, hungarian=hun latin2, italian=ita latin1, japanese=jpn ujis, japanese-sjis=jps sjis, korean=kor euckr, norwegian-ny=norwegian-ny latin1, norwegian=nor latin1, polish=pol latin2, portuguese=por latin1, romanian=rum latin2, russian=rus koi8r, serbian=serbian cp1250, slovak=slo latin2, spanish=spa latin1, swedish=swe latin1, ukrainian=ukr koi8u; +languages czech=cze latin2, danish=dan latin1, dutch=nla latin1, english=eng latin1, estonian=est latin7, french=fre latin1, german=ger latin1, greek=greek greek, hungarian=hun latin2, italian=ita latin1, japanese=jpn ujis, korean=kor euckr, norwegian-ny=norwegian-ny latin1, norwegian=nor latin1, polish=pol latin2, portuguese=por latin1, romanian=rum latin2, russian=rus koi8r, serbian=serbian cp1250, slovak=slo latin2, spanish=spa latin1, swedish=swe latin1, ukrainian=ukr koi8u, bulgarian=bgn cp1251; default-language eng @@ -51,92 +51,90 @@ ER_YES spa "SI" ukr "ТÐК" ER_CANT_CREATE_FILE - cze "Nemohu vytvo-BÅ™it soubor '%-.200s' (chybový kód: %d)" - dan "Kan ikke oprette filen '%-.200s' (Fejlkode: %d)" - nla "Kan file '%-.200s' niet aanmaken (Errcode: %d)" - eng "Can't create file '%-.200s' (errno: %d)" - est "Ei suuda luua faili '%-.200s' (veakood: %d)" - fre "Ne peut créer le fichier '%-.200s' (Errcode: %d)" - ger "Kann Datei '%-.200s' nicht erzeugen (Fehler: %d)" - greek "ΑδÏνατη η δημιουÏγία του αÏχείου '%-.200s' (κωδικός λάθους: %d)" - hun "A '%-.200s' file nem hozhato letre (hibakod: %d)" - ita "Impossibile creare il file '%-.200s' (errno: %d)" - jpn "'%-.200s' ファイルãŒä½œã‚Œã¾ã›ã‚“ (errno: %d)" - kor "í™”ì¼ '%-.200s'를 만들지 못했습니다. (ì—러번호: %d)" - nor "Kan ikke opprette fila '%-.200s' (Feilkode: %d)" - norwegian-ny "Kan ikkje opprette fila '%-.200s' (Feilkode: %d)" - pol "Nie można stworzyć pliku '%-.200s' (Kod błędu: %d)" - por "Não pode criar o arquivo '%-.200s' (erro no. %d)" - rum "Nu pot sa creez fisierul '%-.200s' (Eroare: %d)" - rus "Ðевозможно Ñоздать файл '%-.200s' (ошибка: %d)" - serbian "Ne mogu da kreiram file '%-.200s' (errno: %d)" - slo "Nemôžem vytvoriÅ¥ súbor '%-.200s' (chybový kód: %d)" - spa "No puedo crear archivo '%-.200s' (Error: %d)" - swe "Kan inte skapa filen '%-.200s' (Felkod: %d)" - ukr "Ðе можу Ñтворити файл '%-.200s' (помилка: %d)" + cze "Nemohu vytvoÅ™it soubor '%-.200s' (chybový kód: %M)" + dan "Kan ikke oprette filen '%-.200s' (Fejlkode: %M)" + nla "Kan file '%-.200s' niet aanmaken (Errcode: %M)" + eng "Can't create file '%-.200s' (errno: %M)" + est "Ei suuda luua faili '%-.200s' (veakood: %M)" + fre "Ne peut créer le fichier '%-.200s' (Errcode: %M)" + ger "Kann Datei '%-.200s' nicht erzeugen (Fehler: %M)" + greek "ΑδÏνατη η δημιουÏγία του αÏχείου '%-.200s' (κωδικός λάθους: %M)" + hun "A '%-.200s' file nem hozhato letre (hibakod: %M)" + ita "Impossibile creare il file '%-.200s' (errno: %M)" + jpn "ファイル '%-.200s' を作æˆã§ãã¾ã›ã‚“。(エラー番å·: %M)" + kor "í™”ì¼ '%-.200s'를 만들지 못했습니다. (ì—러번호: %M)" + nor "Kan ikke opprette fila '%-.200s' (Feilkode: %M)" + norwegian-ny "Kan ikkje opprette fila '%-.200s' (Feilkode: %M)" + pol "Nie można stworzyć pliku '%-.200s' (Kod błędu: %M)" + por "Não pode criar o arquivo '%-.200s' (erro no. %M)" + rum "Nu pot sa creez fisierul '%-.200s' (Eroare: %M)" + rus "Ðевозможно Ñоздать файл '%-.200s' (ошибка: %M)" + serbian "Ne mogu da kreiram file '%-.200s' (errno: %M)" + slo "Nemôžem vytvoriÅ¥ súbor '%-.200s' (chybový kód: %M)" + spa "No puedo crear archivo '%-.200s' (Error: %M)" + swe "Kan inte skapa filen '%-.200s' (Felkod: %M)" + ukr "Ðе можу Ñтворити файл '%-.200s' (помилка: %M)" ER_CANT_CREATE_TABLE - cze "Nemohu vytvo-BÅ™it tabulku '%-.200s' (chybový kód: %d)" - dan "Kan ikke oprette tabellen '%-.200s' (Fejlkode: %d)" - nla "Kan tabel '%-.200s' niet aanmaken (Errcode: %d)" - eng "Can't create table '%-.200s' (errno: %d)" - jps "'%-.200s' テーブルãŒä½œã‚Œã¾ã›ã‚“.(errno: %d)", - est "Ei suuda luua tabelit '%-.200s' (veakood: %d)" - fre "Ne peut créer la table '%-.200s' (Errcode: %d)" - ger "Kann Tabelle '%-.200s' nicht erzeugen (Fehler: %d)" - greek "ΑδÏνατη η δημιουÏγία του πίνακα '%-.200s' (κωδικός λάθους: %d)" - hun "A '%-.200s' tabla nem hozhato letre (hibakod: %d)" - ita "Impossibile creare la tabella '%-.200s' (errno: %d)" - jpn "'%-.200s' テーブルãŒä½œã‚Œã¾ã›ã‚“.(errno: %d)" - kor "í…Œì´ë¸” '%-.200s'를 만들지 못했습니다. (ì—러번호: %d)" - nor "Kan ikke opprette tabellen '%-.200s' (Feilkode: %d)" - norwegian-ny "Kan ikkje opprette tabellen '%-.200s' (Feilkode: %d)" - pol "Nie można stworzyć tabeli '%-.200s' (Kod błędu: %d)" - por "Não pode criar a tabela '%-.200s' (erro no. %d)" - rum "Nu pot sa creez tabla '%-.200s' (Eroare: %d)" - rus "Ðевозможно Ñоздать таблицу '%-.200s' (ошибка: %d)" - serbian "Ne mogu da kreiram tabelu '%-.200s' (errno: %d)" - slo "Nemôžem vytvoriÅ¥ tabuľku '%-.200s' (chybový kód: %d)" - spa "No puedo crear tabla '%-.200s' (Error: %d)" - swe "Kan inte skapa tabellen '%-.200s' (Felkod: %d)" - ukr "Ðе можу Ñтворити таблицю '%-.200s' (помилка: %d)" + cze "Nemohu vytvoÅ™it tabulku %`s.%`s (chybový kód: %M)" + dan "Kan ikke oprette tabellen %`s.%`s (Fejlkode: %M)" + nla "Kan tabel %`s.%`s niet aanmaken (Errcode: %M)" + eng "Can't create table %`s.%`s (errno: %M)" + jps "%`s.%`s テーブルãŒä½œã‚Œã¾ã›ã‚“.(errno: %M)", + est "Ei suuda luua tabelit %`s.%`s (veakood: %M)" + fre "Ne peut créer la table %`s.%`s (Errcode: %M)" + ger "Kann Tabelle %`s.%`s nicht erzeugen (Fehler: %M)" + greek "ΑδÏνατη η δημιουÏγία του πίνακα %`s.%`s (κωδικός λάθους: %M)" + hun "A %`s.%`s tabla nem hozhato letre (hibakod: %M)" + ita "Impossibile creare la tabella %`s.%`s (errno: %M)" + jpn "%`s.%`s テーブルãŒä½œã‚Œã¾ã›ã‚“.(errno: %M)" + kor "í…Œì´ë¸” %`s.%`s를 만들지 못했습니다. (ì—러번호: %M)" + nor "Kan ikke opprette tabellen %`s.%`s (Feilkode: %M)" + norwegian-ny "Kan ikkje opprette tabellen %`s.%`s (Feilkode: %M)" + pol "Nie można stworzyć tabeli %`s.%`s (Kod błędu: %M)" + por "Não pode criar a tabela %`s.%`s (erro no. %M)" + rum "Nu pot sa creez tabla %`s.%`s (Eroare: %M)" + rus "Ðевозможно Ñоздать таблицу %`s.%`s (ошибка: %M)" + serbian "Ne mogu da kreiram tabelu %`s.%`s (errno: %M)" + slo "Nemôžem vytvoriÅ¥ tabuľku %`s.%`s (chybový kód: %M)" + spa "No puedo crear tabla %`s.%`s (Error: %M)" + swe "Kan inte skapa tabellen %`s.%`s (Felkod: %M)" + ukr "Ðе можу Ñтворити таблицю %`s.%`s (помилка: %M)" ER_CANT_CREATE_DB - cze "Nemohu vytvo-BÅ™it databázi '%-.192s' (chybový kód: %d)" - dan "Kan ikke oprette databasen '%-.192s' (Fejlkode: %d)" - nla "Kan database '%-.192s' niet aanmaken (Errcode: %d)" - eng "Can't create database '%-.192s' (errno: %d)" - jps "'%-.192s' データベースãŒä½œã‚Œã¾ã›ã‚“ (errno: %d)", - est "Ei suuda luua andmebaasi '%-.192s' (veakood: %d)" - fre "Ne peut créer la base '%-.192s' (Erreur %d)" - ger "Kann Datenbank '%-.192s' nicht erzeugen (Fehler: %d)" - greek "ΑδÏνατη η δημιουÏγία της βάσης δεδομÎνων '%-.192s' (κωδικός λάθους: %d)" - hun "Az '%-.192s' adatbazis nem hozhato letre (hibakod: %d)" - ita "Impossibile creare il database '%-.192s' (errno: %d)" - jpn "'%-.192s' データベースãŒä½œã‚Œã¾ã›ã‚“ (errno: %d)" - kor "ë°ì´íƒ€ë² ì´ìФ '%-.192s'를 만들지 못했습니다.. (ì—러번호: %d)" - nor "Kan ikke opprette databasen '%-.192s' (Feilkode: %d)" - norwegian-ny "Kan ikkje opprette databasen '%-.192s' (Feilkode: %d)" - pol "Nie można stworzyć bazy danych '%-.192s' (Kod błędu: %d)" - por "Não pode criar o banco de dados '%-.192s' (erro no. %d)" - rum "Nu pot sa creez baza de date '%-.192s' (Eroare: %d)" - rus "Ðевозможно Ñоздать базу данных '%-.192s' (ошибка: %d)" - serbian "Ne mogu da kreiram bazu '%-.192s' (errno: %d)" - slo "Nemôžem vytvoriÅ¥ databázu '%-.192s' (chybový kód: %d)" - spa "No puedo crear base de datos '%-.192s' (Error: %d)" - swe "Kan inte skapa databasen '%-.192s' (Felkod: %d)" - ukr "Ðе можу Ñтворити базу данних '%-.192s' (помилка: %d)" + cze "Nemohu vytvoÅ™it databázi '%-.192s' (chybový kód: %M)" + dan "Kan ikke oprette databasen '%-.192s' (Fejlkode: %M)" + nla "Kan database '%-.192s' niet aanmaken (Errcode: %M)" + eng "Can't create database '%-.192s' (errno: %M)" + est "Ei suuda luua andmebaasi '%-.192s' (veakood: %M)" + fre "Ne peut créer la base '%-.192s' (Erreur %M)" + ger "Kann Datenbank '%-.192s' nicht erzeugen (Fehler: %M)" + greek "ΑδÏνατη η δημιουÏγία της βάσης δεδομÎνων '%-.192s' (κωδικός λάθους: %M)" + hun "Az '%-.192s' adatbazis nem hozhato letre (hibakod: %M)" + ita "Impossibile creare il database '%-.192s' (errno: %M)" + jpn "データベース '%-.192s' を作æˆã§ãã¾ã›ã‚“。(エラー番å·: %M)" + kor "ë°ì´íƒ€ë² ì´ìФ '%-.192s'를 만들지 못했습니다.. (ì—러번호: %M)" + nor "Kan ikke opprette databasen '%-.192s' (Feilkode: %M)" + norwegian-ny "Kan ikkje opprette databasen '%-.192s' (Feilkode: %M)" + pol "Nie można stworzyć bazy danych '%-.192s' (Kod błędu: %M)" + por "Não pode criar o banco de dados '%-.192s' (erro no. %M)" + rum "Nu pot sa creez baza de date '%-.192s' (Eroare: %M)" + rus "Ðевозможно Ñоздать базу данных '%-.192s' (ошибка: %M)" + serbian "Ne mogu da kreiram bazu '%-.192s' (errno: %M)" + slo "Nemôžem vytvoriÅ¥ databázu '%-.192s' (chybový kód: %M)" + spa "No puedo crear base de datos '%-.192s' (Error: %M)" + swe "Kan inte skapa databasen '%-.192s' (Felkod: %M)" + ukr "Ðе можу Ñтворити базу данних '%-.192s' (помилка: %M)" ER_DB_CREATE_EXISTS - cze "Nemohu vytvo-BÅ™it databázi '%-.192s'; databáze již existuje" + cze "Nemohu vytvoÅ™it databázi '%-.192s'; databáze již existuje" dan "Kan ikke oprette databasen '%-.192s'; databasen eksisterer" nla "Kan database '%-.192s' niet aanmaken; database bestaat reeds" eng "Can't create database '%-.192s'; database exists" - jps "'%-.192s' データベースãŒä½œã‚Œã¾ã›ã‚“.æ—¢ã«ãã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãŒå˜åœ¨ã—ã¾ã™", est "Ei suuda luua andmebaasi '%-.192s': andmebaas juba eksisteerib" fre "Ne peut créer la base '%-.192s'; elle existe déjà " ger "Kann Datenbank '%-.192s' nicht erzeugen. Datenbank existiert bereits" greek "ΑδÏνατη η δημιουÏγία της βάσης δεδομÎνων '%-.192s'; Η βάση δεδομÎνων υπάÏχει ήδη" hun "Az '%-.192s' adatbazis nem hozhato letre Az adatbazis mar letezik" ita "Impossibile creare il database '%-.192s'; il database esiste" - jpn "'%-.192s' データベースãŒä½œã‚Œã¾ã›ã‚“.æ—¢ã«ãã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãŒå˜åœ¨ã—ã¾ã™" + jpn "データベース '%-.192s' を作æˆã§ãã¾ã›ã‚“。データベースã¯ã™ã§ã«å˜åœ¨ã—ã¾ã™ã€‚" kor "ë°ì´íƒ€ë² ì´ìФ '%-.192s'를 만들지 못했습니다.. ë°ì´íƒ€ë² ì´ìŠ¤ê°€ 존재함" nor "Kan ikke opprette databasen '%-.192s'; databasen eksisterer" norwegian-ny "Kan ikkje opprette databasen '%-.192s'; databasen eksisterer" @@ -150,18 +148,17 @@ ER_DB_CREATE_EXISTS swe "Databasen '%-.192s' existerar redan" ukr "Ðе можу Ñтворити базу данних '%-.192s'. База данних Ñ–Ñнує" ER_DB_DROP_EXISTS - cze "Nemohu zru-BÅ¡it databázi '%-.192s', databáze neexistuje" + cze "Nemohu zruÅ¡it databázi '%-.192s', databáze neexistuje" dan "Kan ikke slette (droppe) '%-.192s'; databasen eksisterer ikke" nla "Kan database '%-.192s' niet verwijderen; database bestaat niet" eng "Can't drop database '%-.192s'; database doesn't exist" - jps "'%-.192s' ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‚’ç ´æ£„ã§ãã¾ã›ã‚“. ãã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãŒãªã„ã®ã§ã™.", est "Ei suuda kustutada andmebaasi '%-.192s': andmebaasi ei eksisteeri" fre "Ne peut effacer la base '%-.192s'; elle n'existe pas" ger "Kann Datenbank '%-.192s' nicht löschen; Datenbank nicht vorhanden" greek "ΑδÏνατη η διαγÏαφή της βάσης δεδομÎνων '%-.192s'. Η βάση δεδομÎνων δεν υπάÏχει" hun "A(z) '%-.192s' adatbazis nem szuntetheto meg. Az adatbazis nem letezik" ita "Impossibile cancellare '%-.192s'; il database non esiste" - jpn "'%-.192s' ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‚’ç ´æ£„ã§ãã¾ã›ã‚“. ãã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãŒãªã„ã®ã§ã™." + jpn "データベース '%-.192s' を削除ã§ãã¾ã›ã‚“。データベースã¯å˜åœ¨ã—ã¾ã›ã‚“。" kor "ë°ì´íƒ€ë² ì´ìФ '%-.192s'를 ì œê±°í•˜ì§€ 못했습니다. ë°ì´íƒ€ë² ì´ìŠ¤ê°€ 존재하지 ì•ŠìŒ " nor "Kan ikke fjerne (drop) '%-.192s'; databasen eksisterer ikke" norwegian-ny "Kan ikkje fjerne (drop) '%-.192s'; databasen eksisterer ikkje" @@ -175,93 +172,89 @@ ER_DB_DROP_EXISTS swe "Kan inte radera databasen '%-.192s'; databasen finns inte" ukr "Ðе можу видалити базу данних '%-.192s'. База данних не Ñ–Ñнує" ER_DB_DROP_DELETE - cze "Chyba p-BÅ™i ruÅ¡enà databáze (nemohu vymazat '%-.192s', chyba %d)" - dan "Fejl ved sletning (drop) af databasen (kan ikke slette '%-.192s', Fejlkode %d)" - nla "Fout bij verwijderen database (kan '%-.192s' niet verwijderen, Errcode: %d)" - eng "Error dropping database (can't delete '%-.192s', errno: %d)" - jps "ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ç ´æ£„ã‚¨ãƒ©ãƒ¼ ('%-.192s' を削除ã§ãã¾ã›ã‚“, errno: %d)", - est "Viga andmebaasi kustutamisel (ei suuda kustutada faili '%-.192s', veakood: %d)" - fre "Ne peut effacer la base '%-.192s' (erreur %d)" - ger "Fehler beim Löschen der Datenbank ('%-.192s' kann nicht gelöscht werden, Fehler: %d)" - greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κατά τη διαγÏαφή της βάσης δεδομÎνων (αδÏνατη η διαγÏαφή '%-.192s', κωδικός λάθους: %d)" - hun "Adatbazis megszuntetesi hiba ('%-.192s' nem torolheto, hibakod: %d)" - ita "Errore durante la cancellazione del database (impossibile cancellare '%-.192s', errno: %d)" - jpn "ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ç ´æ£„ã‚¨ãƒ©ãƒ¼ ('%-.192s' を削除ã§ãã¾ã›ã‚“, errno: %d)" - kor "ë°ì´íƒ€ë² ì´ìФ ì œê±° ì—러('%-.192s'를 ì‚ì œí• ìˆ˜ ì—†ì니다, ì—러번호: %d)" - nor "Feil ved fjerning (drop) av databasen (kan ikke slette '%-.192s', feil %d)" - norwegian-ny "Feil ved fjerning (drop) av databasen (kan ikkje slette '%-.192s', feil %d)" - pol "BÅ‚?d podczas usuwania bazy danych (nie można usun?ć '%-.192s', bÅ‚?d %d)" - por "Erro ao eliminar banco de dados (não pode eliminar '%-.192s' - erro no. %d)" - rum "Eroare dropuind baza de date (nu pot sa sterg '%-.192s', Eroare: %d)" - rus "Ошибка при удалении базы данных (невозможно удалить '%-.192s', ошибка: %d)" - serbian "Ne mogu da izbriÅ¡em bazu (ne mogu da izbriÅ¡em '%-.192s', errno: %d)" - slo "Chyba pri mazanà databázy (nemôžem zmazaÅ¥ '%-.192s', chybový kód: %d)" - spa "Error eliminando la base de datos(no puedo borrar '%-.192s', error %d)" - swe "Fel vid radering av databasen (Kan inte radera '%-.192s'. Felkod: %d)" - ukr "Ðе можу видалити базу данних (Ðе можу видалити '%-.192s', помилка: %d)" + cze "Chyba pÅ™i ruÅ¡enà databáze (nemohu vymazat '%-.192s', chyba %M)" + dan "Fejl ved sletning (drop) af databasen (kan ikke slette '%-.192s', Fejlkode %M)" + nla "Fout bij verwijderen database (kan '%-.192s' niet verwijderen, Errcode: %M)" + eng "Error dropping database (can't delete '%-.192s', errno: %M)" + est "Viga andmebaasi kustutamisel (ei suuda kustutada faili '%-.192s', veakood: %M)" + fre "Ne peut effacer la base '%-.192s' (erreur %M)" + ger "Fehler beim Löschen der Datenbank ('%-.192s' kann nicht gelöscht werden, Fehler: %M)" + greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κατά τη διαγÏαφή της βάσης δεδομÎνων (αδÏνατη η διαγÏαφή '%-.192s', κωδικός λάθους: %M)" + hun "Adatbazis megszuntetesi hiba ('%-.192s' nem torolheto, hibakod: %M)" + ita "Errore durante la cancellazione del database (impossibile cancellare '%-.192s', errno: %M)" + jpn "データベース削除エラー ('%-.192s' を削除ã§ãã¾ã›ã‚“。エラー番å·: %M)" + kor "ë°ì´íƒ€ë² ì´ìФ ì œê±° ì—러('%-.192s'를 ì‚ì œí• ìˆ˜ ì—†ì니다, ì—러번호: %M)" + nor "Feil ved fjerning (drop) av databasen (kan ikke slette '%-.192s', feil %M)" + norwegian-ny "Feil ved fjerning (drop) av databasen (kan ikkje slette '%-.192s', feil %M)" + pol "BÅ‚?d podczas usuwania bazy danych (nie można usun?ć '%-.192s', bÅ‚?d %M)" + por "Erro ao eliminar banco de dados (não pode eliminar '%-.192s' - erro no. %M)" + rum "Eroare dropuind baza de date (nu pot sa sterg '%-.192s', Eroare: %M)" + rus "Ошибка при удалении базы данных (невозможно удалить '%-.192s', ошибка: %M)" + serbian "Ne mogu da izbriÅ¡em bazu (ne mogu da izbriÅ¡em '%-.192s', errno: %M)" + slo "Chyba pri mazanà databázy (nemôžem zmazaÅ¥ '%-.192s', chybový kód: %M)" + spa "Error eliminando la base de datos(no puedo borrar '%-.192s', error %M)" + swe "Fel vid radering av databasen (Kan inte radera '%-.192s'. Felkod: %M)" + ukr "Ðе можу видалити базу данних (Ðе можу видалити '%-.192s', помилка: %M)" ER_DB_DROP_RMDIR - cze "Chyba p-BÅ™i ruÅ¡enà databáze (nemohu vymazat adresář '%-.192s', chyba %d)" - dan "Fejl ved sletting af database (kan ikke slette folderen '%-.192s', Fejlkode %d)" - nla "Fout bij verwijderen database (kan rmdir '%-.192s' niet uitvoeren, Errcode: %d)" - eng "Error dropping database (can't rmdir '%-.192s', errno: %d)" - jps "ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ç ´æ£„ã‚¨ãƒ©ãƒ¼ ('%-.192s' ã‚’ rmdir ã§ãã¾ã›ã‚“, errno: %d)", - est "Viga andmebaasi kustutamisel (ei suuda kustutada kataloogi '%-.192s', veakood: %d)" - fre "Erreur en effaçant la base (rmdir '%-.192s', erreur %d)" - ger "Fehler beim Löschen der Datenbank (Verzeichnis '%-.192s' kann nicht gelöscht werden, Fehler: %d)" - greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κατά τη διαγÏαφή της βάσης δεδομÎνων (αδÏνατη η διαγÏαφή του φακÎλλου '%-.192s', κωδικός λάθους: %d)" - hun "Adatbazis megszuntetesi hiba ('%-.192s' nem szuntetheto meg, hibakod: %d)" - ita "Errore durante la cancellazione del database (impossibile rmdir '%-.192s', errno: %d)" - jpn "ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ç ´æ£„ã‚¨ãƒ©ãƒ¼ ('%-.192s' ã‚’ rmdir ã§ãã¾ã›ã‚“, errno: %d)" - kor "ë°ì´íƒ€ë² ì´ìФ ì œê±° ì—러(rmdir '%-.192s'를 í• ìˆ˜ ì—†ì니다, ì—러번호: %d)" - nor "Feil ved sletting av database (kan ikke slette katalogen '%-.192s', feil %d)" - norwegian-ny "Feil ved sletting av database (kan ikkje slette katalogen '%-.192s', feil %d)" - pol "BÅ‚?d podczas usuwania bazy danych (nie można wykonać rmdir '%-.192s', bÅ‚?d %d)" - por "Erro ao eliminar banco de dados (não pode remover diretório '%-.192s' - erro no. %d)" - rum "Eroare dropuind baza de date (nu pot sa rmdir '%-.192s', Eroare: %d)" - rus "Ðевозможно удалить базу данных (невозможно удалить каталог '%-.192s', ошибка: %d)" - serbian "Ne mogu da izbriÅ¡em bazu (ne mogu da izbriÅ¡em direktorijum '%-.192s', errno: %d)" - slo "Chyba pri mazanà databázy (nemôžem vymazaÅ¥ adresár '%-.192s', chybový kód: %d)" - spa "Error eliminando la base de datos (No puedo borrar directorio '%-.192s', error %d)" - swe "Fel vid radering av databasen (Kan inte radera biblioteket '%-.192s'. Felkod: %d)" - ukr "Ðе можу видалити базу данних (Ðе можу видалити теку '%-.192s', помилка: %d)" + cze "Chyba pÅ™i ruÅ¡enà databáze (nemohu vymazat adresář '%-.192s', chyba %M)" + dan "Fejl ved sletting af database (kan ikke slette folderen '%-.192s', Fejlkode %M)" + nla "Fout bij verwijderen database (kan rmdir '%-.192s' niet uitvoeren, Errcode: %M)" + eng "Error dropping database (can't rmdir '%-.192s', errno: %M)" + est "Viga andmebaasi kustutamisel (ei suuda kustutada kataloogi '%-.192s', veakood: %M)" + fre "Erreur en effaçant la base (rmdir '%-.192s', erreur %M)" + ger "Fehler beim Löschen der Datenbank (Verzeichnis '%-.192s' kann nicht gelöscht werden, Fehler: %M)" + greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κατά τη διαγÏαφή της βάσης δεδομÎνων (αδÏνατη η διαγÏαφή του φακÎλλου '%-.192s', κωδικός λάθους: %M)" + hun "Adatbazis megszuntetesi hiba ('%-.192s' nem szuntetheto meg, hibakod: %M)" + ita "Errore durante la cancellazione del database (impossibile rmdir '%-.192s', errno: %M)" + jpn "データベース削除エラー (ディレクトリ '%-.192s' を削除ã§ãã¾ã›ã‚“。エラー番å·: %M)" + kor "ë°ì´íƒ€ë² ì´ìФ ì œê±° ì—러(rmdir '%-.192s'를 í• ìˆ˜ ì—†ì니다, ì—러번호: %M)" + nor "Feil ved sletting av database (kan ikke slette katalogen '%-.192s', feil %M)" + norwegian-ny "Feil ved sletting av database (kan ikkje slette katalogen '%-.192s', feil %M)" + pol "BÅ‚?d podczas usuwania bazy danych (nie można wykonać rmdir '%-.192s', bÅ‚?d %M)" + por "Erro ao eliminar banco de dados (não pode remover diretório '%-.192s' - erro no. %M)" + rum "Eroare dropuind baza de date (nu pot sa rmdir '%-.192s', Eroare: %M)" + rus "Ðевозможно удалить базу данных (невозможно удалить каталог '%-.192s', ошибка: %M)" + serbian "Ne mogu da izbriÅ¡em bazu (ne mogu da izbriÅ¡em direktorijum '%-.192s', errno: %M)" + slo "Chyba pri mazanà databázy (nemôžem vymazaÅ¥ adresár '%-.192s', chybový kód: %M)" + spa "Error eliminando la base de datos (No puedo borrar directorio '%-.192s', error %M)" + swe "Fel vid radering av databasen (Kan inte radera biblioteket '%-.192s'. Felkod: %M)" + ukr "Ðе можу видалити базу данних (Ðе можу видалити теку '%-.192s', помилка: %M)" ER_CANT_DELETE_FILE - cze "Chyba p-BÅ™i výmazu '%-.192s' (chybový kód: %d)" - dan "Fejl ved sletning af '%-.192s' (Fejlkode: %d)" - nla "Fout bij het verwijderen van '%-.192s' (Errcode: %d)" - eng "Error on delete of '%-.192s' (errno: %d)" - jps "'%-.192s' ã®å‰Šé™¤ãŒã‚¨ãƒ©ãƒ¼ (errno: %d)", - est "Viga '%-.192s' kustutamisel (veakood: %d)" - fre "Erreur en effaçant '%-.192s' (Errcode: %d)" - ger "Fehler beim Löschen von '%-.192s' (Fehler: %d)" - greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κατά τη διαγÏαφή '%-.192s' (κωδικός λάθους: %d)" - hun "Torlesi hiba: '%-.192s' (hibakod: %d)" - ita "Errore durante la cancellazione di '%-.192s' (errno: %d)" - jpn "'%-.192s' ã®å‰Šé™¤ãŒã‚¨ãƒ©ãƒ¼ (errno: %d)" - kor "'%-.192s' ì‚ì œ 중 ì—러 (ì—러번호: %d)" - nor "Feil ved sletting av '%-.192s' (Feilkode: %d)" - norwegian-ny "Feil ved sletting av '%-.192s' (Feilkode: %d)" - pol "BÅ‚?d podczas usuwania '%-.192s' (Kod błędu: %d)" - por "Erro na remoção de '%-.192s' (erro no. %d)" - rum "Eroare incercind sa delete '%-.192s' (Eroare: %d)" - rus "Ошибка при удалении '%-.192s' (ошибка: %d)" - serbian "GreÅ¡ka pri brisanju '%-.192s' (errno: %d)" - slo "Chyba pri mazanà '%-.192s' (chybový kód: %d)" - spa "Error en el borrado de '%-.192s' (Error: %d)" - swe "Kan inte radera filen '%-.192s' (Felkod: %d)" - ukr "Ðе можу видалити '%-.192s' (помилка: %d)" + cze "Chyba pÅ™i výmazu '%-.192s' (chybový kód: %M)" + dan "Fejl ved sletning af '%-.192s' (Fejlkode: %M)" + nla "Fout bij het verwijderen van '%-.192s' (Errcode: %M)" + eng "Error on delete of '%-.192s' (errno: %M)" + est "Viga '%-.192s' kustutamisel (veakood: %M)" + fre "Erreur en effaçant '%-.192s' (Errcode: %M)" + ger "Fehler beim Löschen von '%-.192s' (Fehler: %M)" + greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κατά τη διαγÏαφή '%-.192s' (κωδικός λάθους: %M)" + hun "Torlesi hiba: '%-.192s' (hibakod: %M)" + ita "Errore durante la cancellazione di '%-.192s' (errno: %M)" + jpn "ファイル '%-.192s' ã®å‰Šé™¤ã‚¨ãƒ©ãƒ¼ (エラー番å·: %M)" + kor "'%-.192s' ì‚ì œ 중 ì—러 (ì—러번호: %M)" + nor "Feil ved sletting av '%-.192s' (Feilkode: %M)" + norwegian-ny "Feil ved sletting av '%-.192s' (Feilkode: %M)" + pol "BÅ‚?d podczas usuwania '%-.192s' (Kod błędu: %M)" + por "Erro na remoção de '%-.192s' (erro no. %M)" + rum "Eroare incercind sa delete '%-.192s' (Eroare: %M)" + rus "Ошибка при удалении '%-.192s' (ошибка: %M)" + serbian "GreÅ¡ka pri brisanju '%-.192s' (errno: %M)" + slo "Chyba pri mazanà '%-.192s' (chybový kód: %M)" + spa "Error en el borrado de '%-.192s' (Error: %M)" + swe "Kan inte radera filen '%-.192s' (Felkod: %M)" + ukr "Ðе можу видалити '%-.192s' (помилка: %M)" ER_CANT_FIND_SYSTEM_REC - cze "Nemohu -BÄÃst záznam v systémové tabulce" + cze "Nemohu ÄÃst záznam v systémové tabulce" dan "Kan ikke læse posten i systemfolderen" nla "Kan record niet lezen in de systeem tabel" eng "Can't read record in system table" - jps "system table ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’èªã‚€äº‹ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ", est "Ei suuda lugeda kirjet süsteemsest tabelist" fre "Ne peut lire un enregistrement de la table 'system'" ger "Datensatz in der Systemtabelle nicht lesbar" greek "ΑδÏνατη η ανάγνωση εγγÏαφής από πίνακα του συστήματος" hun "Nem olvashato rekord a rendszertablaban" ita "Impossibile leggere il record dalla tabella di sistema" - jpn "system table ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’èªã‚€äº‹ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ" + jpn "システム表ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’èªã¿è¾¼ã‚ã¾ã›ã‚“。" kor "system í…Œì´ë¸”ì—서 ë ˆì½”ë“œë¥¼ ì½ì„ 수 없습니다." nor "Kan ikke lese posten i systemkatalogen" norwegian-ny "Kan ikkje lese posten i systemkatalogen" @@ -275,182 +268,175 @@ ER_CANT_FIND_SYSTEM_REC swe "Hittar inte posten i systemregistret" ukr "Ðе можу зчитати Ð·Ð°Ð¿Ð¸Ñ Ð· ÑиÑтемної таблиці" ER_CANT_GET_STAT - cze "Nemohu z-BÃskat stav '%-.200s' (chybový kód: %d)" - dan "Kan ikke læse status af '%-.200s' (Fejlkode: %d)" - nla "Kan de status niet krijgen van '%-.200s' (Errcode: %d)" - eng "Can't get status of '%-.200s' (errno: %d)" - jps "'%-.200s' ã®ã‚¹ãƒ†ã‚¤ã‚¿ã‚¹ãŒå¾—られã¾ã›ã‚“. (errno: %d)", - est "Ei suuda lugeda '%-.200s' olekut (veakood: %d)" - fre "Ne peut obtenir le status de '%-.200s' (Errcode: %d)" - ger "Kann Status von '%-.200s' nicht ermitteln (Fehler: %d)" - greek "ΑδÏνατη η λήψη πληÏοφοÏιών για την κατάσταση του '%-.200s' (κωδικός λάθους: %d)" - hun "A(z) '%-.200s' statusza nem allapithato meg (hibakod: %d)" - ita "Impossibile leggere lo stato di '%-.200s' (errno: %d)" - jpn "'%-.200s' ã®ã‚¹ãƒ†ã‚¤ã‚¿ã‚¹ãŒå¾—られã¾ã›ã‚“. (errno: %d)" - kor "'%-.200s'ì˜ ìƒíƒœë¥¼ 얻지 못했습니다. (ì—러번호: %d)" - nor "Kan ikke lese statusen til '%-.200s' (Feilkode: %d)" - norwegian-ny "Kan ikkje lese statusen til '%-.200s' (Feilkode: %d)" - pol "Nie można otrzymać statusu '%-.200s' (Kod błędu: %d)" - por "Não pode obter o status de '%-.200s' (erro no. %d)" - rum "Nu pot sa obtin statusul lui '%-.200s' (Eroare: %d)" - rus "Ðевозможно получить ÑтатуÑную информацию о '%-.200s' (ошибка: %d)" - serbian "Ne mogu da dobijem stanje file-a '%-.200s' (errno: %d)" - slo "Nemôžem zistiÅ¥ stav '%-.200s' (chybový kód: %d)" - spa "No puedo obtener el estado de '%-.200s' (Error: %d)" - swe "Kan inte läsa filinformationen (stat) frÃ¥n '%-.200s' (Felkod: %d)" - ukr "Ðе можу отримати ÑÑ‚Ð°Ñ‚ÑƒÑ '%-.200s' (помилка: %d)" + cze "Nemohu zÃskat stav '%-.200s' (chybový kód: %M)" + dan "Kan ikke læse status af '%-.200s' (Fejlkode: %M)" + nla "Kan de status niet krijgen van '%-.200s' (Errcode: %M)" + eng "Can't get status of '%-.200s' (errno: %M)" + est "Ei suuda lugeda '%-.200s' olekut (veakood: %M)" + fre "Ne peut obtenir le status de '%-.200s' (Errcode: %M)" + ger "Kann Status von '%-.200s' nicht ermitteln (Fehler: %M)" + greek "ΑδÏνατη η λήψη πληÏοφοÏιών για την κατάσταση του '%-.200s' (κωδικός λάθους: %M)" + hun "A(z) '%-.200s' statusza nem allapithato meg (hibakod: %M)" + ita "Impossibile leggere lo stato di '%-.200s' (errno: %M)" + jpn "'%-.200s' ã®çŠ¶æ…‹ã‚’å–å¾—ã§ãã¾ã›ã‚“。(エラー番å·: %M)" + kor "'%-.200s'ì˜ ìƒíƒœë¥¼ 얻지 못했습니다. (ì—러번호: %M)" + nor "Kan ikke lese statusen til '%-.200s' (Feilkode: %M)" + norwegian-ny "Kan ikkje lese statusen til '%-.200s' (Feilkode: %M)" + pol "Nie można otrzymać statusu '%-.200s' (Kod błędu: %M)" + por "Não pode obter o status de '%-.200s' (erro no. %M)" + rum "Nu pot sa obtin statusul lui '%-.200s' (Eroare: %M)" + rus "Ðевозможно получить ÑтатуÑную информацию о '%-.200s' (ошибка: %M)" + serbian "Ne mogu da dobijem stanje file-a '%-.200s' (errno: %M)" + slo "Nemôžem zistiÅ¥ stav '%-.200s' (chybový kód: %M)" + spa "No puedo obtener el estado de '%-.200s' (Error: %M)" + swe "Kan inte läsa filinformationen (stat) frÃ¥n '%-.200s' (Felkod: %M)" + ukr "Ðе можу отримати ÑÑ‚Ð°Ñ‚ÑƒÑ '%-.200s' (помилка: %M)" ER_CANT_GET_WD - cze "Chyba p-BÅ™i zjišťovánà pracovnà adresář (chybový kód: %d)" - dan "Kan ikke læse aktive folder (Fejlkode: %d)" - nla "Kan de werkdirectory niet krijgen (Errcode: %d)" - eng "Can't get working directory (errno: %d)" - jps "working directory を得る事ãŒã§ãã¾ã›ã‚“ã§ã—㟠(errno: %d)", - est "Ei suuda identifitseerida jooksvat kataloogi (veakood: %d)" - fre "Ne peut obtenir le répertoire de travail (Errcode: %d)" - ger "Kann Arbeitsverzeichnis nicht ermitteln (Fehler: %d)" - greek "Ο φάκελλος εÏγασίας δεν βÏÎθηκε (κωδικός λάθους: %d)" - hun "A munkakonyvtar nem allapithato meg (hibakod: %d)" - ita "Impossibile leggere la directory di lavoro (errno: %d)" - jpn "working directory を得る事ãŒã§ãã¾ã›ã‚“ã§ã—㟠(errno: %d)" - kor "수행 ë””ë ‰í† ë¦¬ë¥¼ 찾지 못했습니다. (ì—러번호: %d)" - nor "Kan ikke lese aktiv katalog(Feilkode: %d)" - norwegian-ny "Kan ikkje lese aktiv katalog(Feilkode: %d)" - pol "Nie można rozpoznać aktualnego katalogu (Kod błędu: %d)" - por "Não pode obter o diretório corrente (erro no. %d)" - rum "Nu pot sa obtin directorul current (working directory) (Eroare: %d)" - rus "Ðевозможно определить рабочий каталог (ошибка: %d)" - serbian "Ne mogu da dobijem trenutni direktorijum (errno: %d)" - slo "Nemôžem zistiÅ¥ pracovný adresár (chybový kód: %d)" - spa "No puedo acceder al directorio (Error: %d)" - swe "Kan inte inte läsa aktivt bibliotek. (Felkod: %d)" - ukr "Ðе можу визначити робочу теку (помилка: %d)" + cze "Chyba pÅ™i zjišťovánà pracovnà adresář (chybový kód: %M)" + dan "Kan ikke læse aktive folder (Fejlkode: %M)" + nla "Kan de werkdirectory niet krijgen (Errcode: %M)" + eng "Can't get working directory (errno: %M)" + est "Ei suuda identifitseerida jooksvat kataloogi (veakood: %M)" + fre "Ne peut obtenir le répertoire de travail (Errcode: %M)" + ger "Kann Arbeitsverzeichnis nicht ermitteln (Fehler: %M)" + greek "Ο φάκελλος εÏγασίας δεν βÏÎθηκε (κωδικός λάθους: %M)" + hun "A munkakonyvtar nem allapithato meg (hibakod: %M)" + ita "Impossibile leggere la directory di lavoro (errno: %M)" + jpn "作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’å–å¾—ã§ãã¾ã›ã‚“。(エラー番å·: %M)" + kor "수행 ë””ë ‰í† ë¦¬ë¥¼ 찾지 못했습니다. (ì—러번호: %M)" + nor "Kan ikke lese aktiv katalog(Feilkode: %M)" + norwegian-ny "Kan ikkje lese aktiv katalog(Feilkode: %M)" + pol "Nie można rozpoznać aktualnego katalogu (Kod błędu: %M)" + por "Não pode obter o diretório corrente (erro no. %M)" + rum "Nu pot sa obtin directorul current (working directory) (Eroare: %M)" + rus "Ðевозможно определить рабочий каталог (ошибка: %M)" + serbian "Ne mogu da dobijem trenutni direktorijum (errno: %M)" + slo "Nemôžem zistiÅ¥ pracovný adresár (chybový kód: %M)" + spa "No puedo acceder al directorio (Error: %M)" + swe "Kan inte inte läsa aktivt bibliotek. (Felkod: %M)" + ukr "Ðе можу визначити робочу теку (помилка: %M)" ER_CANT_LOCK - cze "Nemohu uzamknout soubor (chybov-Bý kód: %d)" - dan "Kan ikke lÃ¥se fil (Fejlkode: %d)" - nla "Kan de file niet blokeren (Errcode: %d)" - eng "Can't lock file (errno: %d)" - jps "ファイルをãƒãƒƒã‚¯ã§ãã¾ã›ã‚“ (errno: %d)", - est "Ei suuda lukustada faili (veakood: %d)" - fre "Ne peut verrouiller le fichier (Errcode: %d)" - ger "Datei kann nicht gesperrt werden (Fehler: %d)" - greek "Το αÏχείο δεν μποÏεί να κλειδωθεί (κωδικός λάθους: %d)" - hun "A file nem zarolhato. (hibakod: %d)" - ita "Impossibile il locking il file (errno: %d)" - jpn "ファイルをãƒãƒƒã‚¯ã§ãã¾ã›ã‚“ (errno: %d)" - kor "í™”ì¼ì„ ìž ê·¸ì§€(lock) 못했습니다. (ì—러번호: %d)" - nor "Kan ikke lÃ¥se fila (Feilkode: %d)" - norwegian-ny "Kan ikkje lÃ¥se fila (Feilkode: %d)" - pol "Nie można zablokować pliku (Kod błędu: %d)" - por "Não pode travar o arquivo (erro no. %d)" - rum "Nu pot sa lock fisierul (Eroare: %d)" - rus "Ðевозможно поÑтавить блокировку на файле (ошибка: %d)" - serbian "Ne mogu da zakljuÄam file (errno: %d)" - slo "Nemôžem zamknúť súbor (chybový kód: %d)" - spa "No puedo bloquear archivo: (Error: %d)" - swe "Kan inte lÃ¥sa filen. (Felkod: %d)" - ukr "Ðе можу заблокувати файл (помилка: %d)" + cze "Nemohu uzamknout soubor (chybový kód: %M)" + dan "Kan ikke lÃ¥se fil (Fejlkode: %M)" + nla "Kan de file niet blokeren (Errcode: %M)" + eng "Can't lock file (errno: %M)" + est "Ei suuda lukustada faili (veakood: %M)" + fre "Ne peut verrouiller le fichier (Errcode: %M)" + ger "Datei kann nicht gesperrt werden (Fehler: %M)" + greek "Το αÏχείο δεν μποÏεί να κλειδωθεί (κωδικός λάθους: %M)" + hun "A file nem zarolhato. (hibakod: %M)" + ita "Impossibile il locking il file (errno: %M)" + jpn "ファイルをãƒãƒƒã‚¯ã§ãã¾ã›ã‚“。(エラー番å·: %M)" + kor "í™”ì¼ì„ ìž ê·¸ì§€(lock) 못했습니다. (ì—러번호: %M)" + nor "Kan ikke lÃ¥se fila (Feilkode: %M)" + norwegian-ny "Kan ikkje lÃ¥se fila (Feilkode: %M)" + pol "Nie można zablokować pliku (Kod błędu: %M)" + por "Não pode travar o arquivo (erro no. %M)" + rum "Nu pot sa lock fisierul (Eroare: %M)" + rus "Ðевозможно поÑтавить блокировку на файле (ошибка: %M)" + serbian "Ne mogu da zakljuÄam file (errno: %M)" + slo "Nemôžem zamknúť súbor (chybový kód: %M)" + spa "No puedo bloquear archivo: (Error: %M)" + swe "Kan inte lÃ¥sa filen. (Felkod: %M)" + ukr "Ðе можу заблокувати файл (помилка: %M)" ER_CANT_OPEN_FILE - cze "Nemohu otev-BÅ™Ãt soubor '%-.200s' (chybový kód: %d)" - dan "Kan ikke Ã¥bne fil: '%-.200s' (Fejlkode: %d)" - nla "Kan de file '%-.200s' niet openen (Errcode: %d)" - eng "Can't open file: '%-.200s' (errno: %d)" - jps "'%-.200s' ファイルを開ã事ãŒã§ãã¾ã›ã‚“ (errno: %d)", - est "Ei suuda avada faili '%-.200s' (veakood: %d)" - fre "Ne peut ouvrir le fichier: '%-.200s' (Errcode: %d)" - ger "Kann Datei '%-.200s' nicht öffnen (Fehler: %d)" - greek "Δεν είναι δυνατό να ανοιχτεί το αÏχείο: '%-.200s' (κωδικός λάθους: %d)" - hun "A '%-.200s' file nem nyithato meg (hibakod: %d)" - ita "Impossibile aprire il file: '%-.200s' (errno: %d)" - jpn "'%-.200s' ファイルを開ã事ãŒã§ãã¾ã›ã‚“ (errno: %d)" - kor "í™”ì¼ì„ ì—´ì§€ 못했습니다.: '%-.200s' (ì—러번호: %d)" - nor "Kan ikke Ã¥pne fila: '%-.200s' (Feilkode: %d)" - norwegian-ny "Kan ikkje Ã¥pne fila: '%-.200s' (Feilkode: %d)" - pol "Nie można otworzyć pliku: '%-.200s' (Kod błędu: %d)" - por "Não pode abrir o arquivo '%-.200s' (erro no. %d)" - rum "Nu pot sa deschid fisierul: '%-.200s' (Eroare: %d)" - rus "Ðевозможно открыть файл: '%-.200s' (ошибка: %d)" - serbian "Ne mogu da otvorim file: '%-.200s' (errno: %d)" - slo "Nemôžem otvoriÅ¥ súbor: '%-.200s' (chybový kód: %d)" - spa "No puedo abrir archivo: '%-.200s' (Error: %d)" - swe "Kan inte använda '%-.200s' (Felkod: %d)" - ukr "Ðе можу відкрити файл: '%-.200s' (помилка: %d)" + cze "Nemohu otevÅ™Ãt soubor '%-.200s' (chybový kód: %M)" + dan "Kan ikke Ã¥bne fil: '%-.200s' (Fejlkode: %M)" + nla "Kan de file '%-.200s' niet openen (Errcode: %M)" + eng "Can't open file: '%-.200s' (errno: %M)" + est "Ei suuda avada faili '%-.200s' (veakood: %M)" + fre "Ne peut ouvrir le fichier: '%-.200s' (Errcode: %M)" + ger "Kann Datei '%-.200s' nicht öffnen (Fehler: %M)" + greek "Δεν είναι δυνατό να ανοιχτεί το αÏχείο: '%-.200s' (κωδικός λάθους: %M)" + hun "A '%-.200s' file nem nyithato meg (hibakod: %M)" + ita "Impossibile aprire il file: '%-.200s' (errno: %M)" + jpn "ファイル '%-.200s' をオープンã§ãã¾ã›ã‚“。(エラー番å·: %M)" + kor "í™”ì¼ì„ ì—´ì§€ 못했습니다.: '%-.200s' (ì—러번호: %M)" + nor "Kan ikke Ã¥pne fila: '%-.200s' (Feilkode: %M)" + norwegian-ny "Kan ikkje Ã¥pne fila: '%-.200s' (Feilkode: %M)" + pol "Nie można otworzyć pliku: '%-.200s' (Kod błędu: %M)" + por "Não pode abrir o arquivo '%-.200s' (erro no. %M)" + rum "Nu pot sa deschid fisierul: '%-.200s' (Eroare: %M)" + rus "Ðевозможно открыть файл: '%-.200s' (ошибка: %M)" + serbian "Ne mogu da otvorim file: '%-.200s' (errno: %M)" + slo "Nemôžem otvoriÅ¥ súbor: '%-.200s' (chybový kód: %M)" + spa "No puedo abrir archivo: '%-.200s' (Error: %M)" + swe "Kan inte använda '%-.200s' (Felkod: %M)" + ukr "Ðе можу відкрити файл: '%-.200s' (помилка: %M)" ER_FILE_NOT_FOUND - cze "Nemohu naj-BÃt soubor '%-.200s' (chybový kód: %d)" - dan "Kan ikke finde fila: '%-.200s' (Fejlkode: %d)" - nla "Kan de file: '%-.200s' niet vinden (Errcode: %d)" - eng "Can't find file: '%-.200s' (errno: %d)" - jps "'%-.200s' ファイルを見付ã‘る事ãŒã§ãã¾ã›ã‚“.(errno: %d)", - est "Ei suuda leida faili '%-.200s' (veakood: %d)" - fre "Ne peut trouver le fichier: '%-.200s' (Errcode: %d)" - ger "Kann Datei '%-.200s' nicht finden (Fehler: %d)" - greek "Δεν βÏÎθηκε το αÏχείο: '%-.200s' (κωδικός λάθους: %d)" - hun "A(z) '%-.200s' file nem talalhato (hibakod: %d)" - ita "Impossibile trovare il file: '%-.200s' (errno: %d)" - jpn "'%-.200s' ファイルを見付ã‘る事ãŒã§ãã¾ã›ã‚“.(errno: %d)" - kor "í™”ì¼ì„ 찾지 못했습니다.: '%-.200s' (ì—러번호: %d)" - nor "Kan ikke finne fila: '%-.200s' (Feilkode: %d)" - norwegian-ny "Kan ikkje finne fila: '%-.200s' (Feilkode: %d)" - pol "Nie można znaleĽć pliku: '%-.200s' (Kod błędu: %d)" - por "Não pode encontrar o arquivo '%-.200s' (erro no. %d)" - rum "Nu pot sa gasesc fisierul: '%-.200s' (Eroare: %d)" - rus "Ðевозможно найти файл: '%-.200s' (ошибка: %d)" - serbian "Ne mogu da pronaÄ‘em file: '%-.200s' (errno: %d)" - slo "Nemôžem nájsÅ¥ súbor: '%-.200s' (chybový kód: %d)" - spa "No puedo encontrar archivo: '%-.200s' (Error: %d)" - swe "Hittar inte filen '%-.200s' (Felkod: %d)" - ukr "Ðе можу знайти файл: '%-.200s' (помилка: %d)" + cze "Nemohu najÃt soubor '%-.200s' (chybový kód: %M)" + dan "Kan ikke finde fila: '%-.200s' (Fejlkode: %M)" + nla "Kan de file: '%-.200s' niet vinden (Errcode: %M)" + eng "Can't find file: '%-.200s' (errno: %M)" + est "Ei suuda leida faili '%-.200s' (veakood: %M)" + fre "Ne peut trouver le fichier: '%-.200s' (Errcode: %M)" + ger "Kann Datei '%-.200s' nicht finden (Fehler: %M)" + greek "Δεν βÏÎθηκε το αÏχείο: '%-.200s' (κωδικός λάθους: %M)" + hun "A(z) '%-.200s' file nem talalhato (hibakod: %M)" + ita "Impossibile trovare il file: '%-.200s' (errno: %M)" + jpn "ファイル '%-.200s' ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。(エラー番å·: %M)" + kor "í™”ì¼ì„ 찾지 못했습니다.: '%-.200s' (ì—러번호: %M)" + nor "Kan ikke finne fila: '%-.200s' (Feilkode: %M)" + norwegian-ny "Kan ikkje finne fila: '%-.200s' (Feilkode: %M)" + pol "Nie można znaleĽć pliku: '%-.200s' (Kod błędu: %M)" + por "Não pode encontrar o arquivo '%-.200s' (erro no. %M)" + rum "Nu pot sa gasesc fisierul: '%-.200s' (Eroare: %M)" + rus "Ðевозможно найти файл: '%-.200s' (ошибка: %M)" + serbian "Ne mogu da pronaÄ‘em file: '%-.200s' (errno: %M)" + slo "Nemôžem nájsÅ¥ súbor: '%-.200s' (chybový kód: %M)" + spa "No puedo encontrar archivo: '%-.200s' (Error: %M)" + swe "Hittar inte filen '%-.200s' (Felkod: %M)" + ukr "Ðе можу знайти файл: '%-.200s' (помилка: %M)" ER_CANT_READ_DIR - cze "Nemohu -BÄÃst adresář '%-.192s' (chybový kód: %d)" - dan "Kan ikke læse folder '%-.192s' (Fejlkode: %d)" - nla "Kan de directory niet lezen van '%-.192s' (Errcode: %d)" - eng "Can't read dir of '%-.192s' (errno: %d)" - jps "'%-.192s' ディレクトリãŒèªã‚ã¾ã›ã‚“.(errno: %d)", - est "Ei suuda lugeda kataloogi '%-.192s' (veakood: %d)" - fre "Ne peut lire le répertoire de '%-.192s' (Errcode: %d)" - ger "Verzeichnis von '%-.192s' nicht lesbar (Fehler: %d)" - greek "Δεν είναι δυνατό να διαβαστεί ο φάκελλος του '%-.192s' (κωδικός λάθους: %d)" - hun "A(z) '%-.192s' konyvtar nem olvashato. (hibakod: %d)" - ita "Impossibile leggere la directory di '%-.192s' (errno: %d)" - jpn "'%-.192s' ディレクトリãŒèªã‚ã¾ã›ã‚“.(errno: %d)" - kor "'%-.192s'ë””ë ‰í† ë¦¬ë¥¼ ì½ì§€ 못했습니다. (ì—러번호: %d)" - nor "Kan ikke lese katalogen '%-.192s' (Feilkode: %d)" - norwegian-ny "Kan ikkje lese katalogen '%-.192s' (Feilkode: %d)" - pol "Nie można odczytać katalogu '%-.192s' (Kod błędu: %d)" - por "Não pode ler o diretório de '%-.192s' (erro no. %d)" - rum "Nu pot sa citesc directorul '%-.192s' (Eroare: %d)" - rus "Ðевозможно прочитать каталог '%-.192s' (ошибка: %d)" - serbian "Ne mogu da proÄitam direktorijum '%-.192s' (errno: %d)" - slo "Nemôžem ÄÃtaÅ¥ adresár '%-.192s' (chybový kód: %d)" - spa "No puedo leer el directorio de '%-.192s' (Error: %d)" - swe "Kan inte läsa frÃ¥n bibliotek '%-.192s' (Felkod: %d)" - ukr "Ðе можу прочитати теку '%-.192s' (помилка: %d)" + cze "Nemohu ÄÃst adresář '%-.192s' (chybový kód: %M)" + dan "Kan ikke læse folder '%-.192s' (Fejlkode: %M)" + nla "Kan de directory niet lezen van '%-.192s' (Errcode: %M)" + eng "Can't read dir of '%-.192s' (errno: %M)" + est "Ei suuda lugeda kataloogi '%-.192s' (veakood: %M)" + fre "Ne peut lire le répertoire de '%-.192s' (Errcode: %M)" + ger "Verzeichnis von '%-.192s' nicht lesbar (Fehler: %M)" + greek "Δεν είναι δυνατό να διαβαστεί ο φάκελλος του '%-.192s' (κωδικός λάθους: %M)" + hun "A(z) '%-.192s' konyvtar nem olvashato. (hibakod: %M)" + ita "Impossibile leggere la directory di '%-.192s' (errno: %M)" + jpn "ディレクトリ '%-.192s' ã‚’èªã¿è¾¼ã‚ã¾ã›ã‚“。(エラー番å·: %M)" + kor "'%-.192s'ë””ë ‰í† ë¦¬ë¥¼ ì½ì§€ 못했습니다. (ì—러번호: %M)" + nor "Kan ikke lese katalogen '%-.192s' (Feilkode: %M)" + norwegian-ny "Kan ikkje lese katalogen '%-.192s' (Feilkode: %M)" + pol "Nie można odczytać katalogu '%-.192s' (Kod błędu: %M)" + por "Não pode ler o diretório de '%-.192s' (erro no. %M)" + rum "Nu pot sa citesc directorul '%-.192s' (Eroare: %M)" + rus "Ðевозможно прочитать каталог '%-.192s' (ошибка: %M)" + serbian "Ne mogu da proÄitam direktorijum '%-.192s' (errno: %M)" + slo "Nemôžem ÄÃtaÅ¥ adresár '%-.192s' (chybový kód: %M)" + spa "No puedo leer el directorio de '%-.192s' (Error: %M)" + swe "Kan inte läsa frÃ¥n bibliotek '%-.192s' (Felkod: %M)" + ukr "Ðе можу прочитати теку '%-.192s' (помилка: %M)" ER_CANT_SET_WD - cze "Nemohu zm-BÄ›nit adresář na '%-.192s' (chybový kód: %d)" - dan "Kan ikke skifte folder til '%-.192s' (Fejlkode: %d)" - nla "Kan de directory niet veranderen naar '%-.192s' (Errcode: %d)" - eng "Can't change dir to '%-.192s' (errno: %d)" - jps "'%-.192s' ディレクトリ㫠chdir ã§ãã¾ã›ã‚“.(errno: %d)", - est "Ei suuda siseneda kataloogi '%-.192s' (veakood: %d)" - fre "Ne peut changer le répertoire pour '%-.192s' (Errcode: %d)" - ger "Kann nicht in das Verzeichnis '%-.192s' wechseln (Fehler: %d)" - greek "ΑδÏνατη η αλλαγή του Ï„ÏÎχοντος καταλόγου σε '%-.192s' (κωδικός λάθους: %d)" - hun "Konyvtarvaltas nem lehetseges a(z) '%-.192s'-ba. (hibakod: %d)" - ita "Impossibile cambiare la directory in '%-.192s' (errno: %d)" - jpn "'%-.192s' ディレクトリ㫠chdir ã§ãã¾ã›ã‚“.(errno: %d)" - kor "'%-.192s'ë””ë ‰í† ë¦¬ë¡œ ì´ë™í• 수 없었습니다. (ì—러번호: %d)" - nor "Kan ikke skifte katalog til '%-.192s' (Feilkode: %d)" - norwegian-ny "Kan ikkje skifte katalog til '%-.192s' (Feilkode: %d)" - pol "Nie można zmienić katalogu na '%-.192s' (Kod błędu: %d)" - por "Não pode mudar para o diretório '%-.192s' (erro no. %d)" - rum "Nu pot sa schimb directorul '%-.192s' (Eroare: %d)" - rus "Ðевозможно перейти в каталог '%-.192s' (ошибка: %d)" - serbian "Ne mogu da promenim direktorijum na '%-.192s' (errno: %d)" - slo "Nemôžem vojsÅ¥ do adresára '%-.192s' (chybový kód: %d)" - spa "No puedo cambiar al directorio de '%-.192s' (Error: %d)" - swe "Kan inte byta till '%-.192s' (Felkod: %d)" - ukr "Ðе можу перейти у теку '%-.192s' (помилка: %d)" + cze "Nemohu zmÄ›nit adresář na '%-.192s' (chybový kód: %M)" + dan "Kan ikke skifte folder til '%-.192s' (Fejlkode: %M)" + nla "Kan de directory niet veranderen naar '%-.192s' (Errcode: %M)" + eng "Can't change dir to '%-.192s' (errno: %M)" + est "Ei suuda siseneda kataloogi '%-.192s' (veakood: %M)" + fre "Ne peut changer le répertoire pour '%-.192s' (Errcode: %M)" + ger "Kann nicht in das Verzeichnis '%-.192s' wechseln (Fehler: %M)" + greek "ΑδÏνατη η αλλαγή του Ï„ÏÎχοντος καταλόγου σε '%-.192s' (κωδικός λάθους: %M)" + hun "Konyvtarvaltas nem lehetseges a(z) '%-.192s'-ba. (hibakod: %M)" + ita "Impossibile cambiare la directory in '%-.192s' (errno: %M)" + jpn "ディレクトリ '%-.192s' ã«ç§»å‹•ã§ãã¾ã›ã‚“。(エラー番å·: %M)" + kor "'%-.192s'ë””ë ‰í† ë¦¬ë¡œ ì´ë™í• 수 없었습니다. (ì—러번호: %M)" + nor "Kan ikke skifte katalog til '%-.192s' (Feilkode: %M)" + norwegian-ny "Kan ikkje skifte katalog til '%-.192s' (Feilkode: %M)" + pol "Nie można zmienić katalogu na '%-.192s' (Kod błędu: %M)" + por "Não pode mudar para o diretório '%-.192s' (erro no. %M)" + rum "Nu pot sa schimb directorul '%-.192s' (Eroare: %M)" + rus "Ðевозможно перейти в каталог '%-.192s' (ошибка: %M)" + serbian "Ne mogu da promenim direktorijum na '%-.192s' (errno: %M)" + slo "Nemôžem vojsÅ¥ do adresára '%-.192s' (chybový kód: %M)" + spa "No puedo cambiar al directorio de '%-.192s' (Error: %M)" + swe "Kan inte byta till '%-.192s' (Felkod: %M)" + ukr "Ðе можу перейти у теку '%-.192s' (помилка: %M)" ER_CHECKREAD - cze "Z-Báznam byl zmÄ›nÄ›n od poslednÃho Ätenà v tabulce '%-.192s'" + cze "Záznam byl zmÄ›nÄ›n od poslednÃho Ätenà v tabulce '%-.192s'" dan "Posten er ændret siden sidste læsning '%-.192s'" nla "Record is veranderd sinds de laatste lees activiteit in de tabel '%-.192s'" eng "Record has changed since last read in table '%-.192s'" @@ -460,6 +446,7 @@ ER_CHECKREAD greek "Η εγγÏαφή Îχει αλλάξει από την τελευταία φοÏά που ανασÏÏθηκε από τον πίνακα '%-.192s'" hun "A(z) '%-.192s' tablaban talalhato rekord megvaltozott az utolso olvasas ota" ita "Il record e` cambiato dall'ultima lettura della tabella '%-.192s'" + jpn "表 '%-.192s' ã®æœ€å¾Œã®èªã¿è¾¼ã¿æ™‚点ã‹ã‚‰ã€ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒå¤‰åŒ–ã—ã¾ã—ãŸã€‚" kor "í…Œì´ë¸” '%-.192s'ì—서 마지막으로 ì½ì€ 후 Recordê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤." nor "Posten har blitt endret siden den ble lest '%-.192s'" norwegian-ny "Posten har vorte endra sidan den sist vart lesen '%-.192s'" @@ -472,44 +459,42 @@ ER_CHECKREAD spa "El registro ha cambiado desde la ultima lectura de la tabla '%-.192s'" swe "Posten har förändrats sedan den lästes i register '%-.192s'" ukr "Ð—Ð°Ð¿Ð¸Ñ Ð±ÑƒÐ»Ð¾ змінено з чаÑу оÑтаннього Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð· таблиці '%-.192s'" -ER_DISK_FULL - cze "Disk je pln-Bý (%s), Äekám na uvolnÄ›nà nÄ›jakého mÃsta ..." - dan "Ikke mere diskplads (%s). Venter pÃ¥ at fÃ¥ frigjort plads..." - nla "Schijf vol (%s). Aan het wachten totdat er ruimte vrij wordt gemaakt..." - eng "Disk full (%s); waiting for someone to free some space..." - jps "Disk full (%s). 誰ã‹ãŒä½•ã‹ã‚’減らã™ã¾ã§ã¾ã£ã¦ãã ã•ã„...", - est "Ketas täis (%s). Ootame kuni tekib vaba ruumi..." - fre "Disque plein (%s). J'attend que quelqu'un libère de l'espace..." - ger "Festplatte voll (%s). Warte, bis jemand Platz schafft ..." - greek "Δεν υπάÏχει χώÏος στο δίσκο (%s). ΠαÏακαλώ, πεÏιμÎνετε να ελευθεÏωθεί χώÏος..." - hun "A lemez megtelt (%s)." - ita "Disco pieno (%s). In attesa che qualcuno liberi un po' di spazio..." - jpn "Disk full (%s). 誰ã‹ãŒä½•ã‹ã‚’減らã™ã¾ã§ã¾ã£ã¦ãã ã•ã„..." - kor "Disk full (%s). 다른 ì‚¬ëžŒì´ ì§€ìš¸ë•Œê¹Œì§€ 기다립니다..." - nor "Ikke mer diskplass (%s). Venter pÃ¥ Ã¥ fÃ¥ frigjort plass..." - norwegian-ny "Ikkje meir diskplass (%s). Ventar pÃ¥ Ã¥ fÃ¥ frigjort plass..." - pol "Dysk peÅ‚ny (%s). Oczekiwanie na zwolnienie miejsca..." - por "Disco cheio (%s). Aguardando alguém liberar algum espaço..." - rum "Hard-disk-ul este plin (%s). Astept sa se elibereze ceva spatiu..." - rus "ДиÑк заполнен. (%s). Ожидаем, пока кто-то не уберет поÑле ÑÐµÐ±Ñ Ð¼ÑƒÑор..." - serbian "Disk je pun (%s). ÄŒekam nekoga da doÄ‘e i oslobodi neÅ¡to mesta..." - slo "Disk je plný (%s), Äakám na uvoľnenie miesta..." - spa "Disco lleno (%s). Esperando para que se libere algo de espacio..." - swe "Disken är full (%s). Väntar tills det finns ledigt utrymme..." - ukr "ДиÑк заповнений (%s). Вичикую, доки звільнитьÑÑ Ñ‚Ñ€Ð¾Ñ…Ð¸ міÑцÑ..." +ER_DISK_FULL + cze "Disk je plný (%s), Äekám na uvolnÄ›nà nÄ›jakého mÃsta ... (chybový kód: %M)" + dan "Ikke mere diskplads (%s). Venter pÃ¥ at fÃ¥ frigjort plads... (Fejlkode: %M)" + nla "Schijf vol (%s). Aan het wachten totdat er ruimte vrij wordt gemaakt... (Errcode: %M)" + eng "Disk full (%s); waiting for someone to free some space... (errno: %M)" + est "Ketas täis (%s). Ootame kuni tekib vaba ruumi... (veakood: %M)" + fre "Disque plein (%s). J'attend que quelqu'un libère de l'espace... (Errcode: %M)" + ger "Festplatte voll (%s). Warte, bis jemand Platz schafft ... (Fehler: %M)" + greek "Δεν υπάÏχει χώÏος στο δίσκο (%s). ΠαÏακαλώ, πεÏιμÎνετε να ελευθεÏωθεί χώÏος... (κωδικός λάθους: %M)" + hun "A lemez megtelt (%s). (hibakod: %M)" + ita "Disco pieno (%s). In attesa che qualcuno liberi un po' di spazio... (errno: %M)" + jpn "ãƒ‡ã‚£ã‚¹ã‚¯é ˜åŸŸä¸è¶³ã§ã™(%s)。(エラー番å·: %M)" + kor "Disk full (%s). 다른 ì‚¬ëžŒì´ ì§€ìš¸ë•Œê¹Œì§€ 기다립니다... (ì—러번호: %M)" + nor "Ikke mer diskplass (%s). Venter pÃ¥ Ã¥ fÃ¥ frigjort plass... (Feilkode: %M)" + norwegian-ny "Ikkje meir diskplass (%s). Ventar pÃ¥ Ã¥ fÃ¥ frigjort plass... (Feilkode: %M)" + pol "Dysk peÅ‚ny (%s). Oczekiwanie na zwolnienie miejsca... (Kod błędu: %M)" + por "Disco cheio (%s). Aguardando alguém liberar algum espaço... (erro no. %M)" + rum "Hard-disk-ul este plin (%s). Astept sa se elibereze ceva spatiu... (Eroare: %M)" + rus "ДиÑк заполнен. (%s). Ожидаем, пока кто-то не уберет поÑле ÑÐµÐ±Ñ Ð¼ÑƒÑор... (ошибка: %M)" + serbian "Disk je pun (%s). ÄŒekam nekoga da doÄ‘e i oslobodi neÅ¡to mesta... (errno: %M)" + slo "Disk je plný (%s), Äakám na uvoľnenie miesta... (chybový kód: %M)" + spa "Disco lleno (%s). Esperando para que se libere algo de espacio... (Error: %M)" + swe "Disken är full (%s). Väntar tills det finns ledigt utrymme... (Felkod: %M)" + ukr "ДиÑк заповнений (%s). Вичикую, доки звільнитьÑÑ Ñ‚Ñ€Ð¾Ñ…Ð¸ міÑцÑ... (помилка: %M)" ER_DUP_KEY 23000 - cze "Nemohu zapsat, zdvojen-Bý klÃÄ v tabulce '%-.192s'" + cze "Nemohu zapsat, zdvojený klÃÄ v tabulce '%-.192s'" dan "Kan ikke skrive, flere ens nøgler i tabellen '%-.192s'" nla "Kan niet schrijven, dubbele zoeksleutel in tabel '%-.192s'" eng "Can't write; duplicate key in table '%-.192s'" - jps "table '%-.192s' ã« key ãŒé‡è¤‡ã—ã¦ã„ã¦æ›¸ãã“ã‚ã¾ã›ã‚“", est "Ei saa kirjutada, korduv võti tabelis '%-.192s'" fre "Ecriture impossible, doublon dans une clé de la table '%-.192s'" ger "Kann nicht speichern, Grund: doppelter Schlüssel in Tabelle '%-.192s'" greek "Δεν είναι δυνατή η καταχώÏηση, η τιμή υπάÏχει ήδη στον πίνακα '%-.192s'" hun "Irasi hiba, duplikalt kulcs a '%-.192s' tablaban." ita "Scrittura impossibile: chiave duplicata nella tabella '%-.192s'" - jpn "table '%-.192s' ã« key ãŒé‡è¤‡ã—ã¦ã„ã¦æ›¸ãã“ã‚ã¾ã›ã‚“" + jpn "書ãè¾¼ã‚ã¾ã›ã‚“。表 '%-.192s' ã«é‡è¤‡ã™ã‚‹ã‚ーãŒã‚りã¾ã™ã€‚" kor "기ë¡í• 수 ì—†ì니다., í…Œì´ë¸” '%-.192s'ì—서 중복 키" nor "Kan ikke skrive, flere like nøkler i tabellen '%-.192s'" norwegian-ny "Kan ikkje skrive, flere like nyklar i tabellen '%-.192s'" @@ -523,116 +508,113 @@ ER_DUP_KEY 23000 swe "Kan inte skriva, dubbel söknyckel i register '%-.192s'" ukr "Ðе можу запиÑати, дублюючийÑÑ ÐºÐ»ÑŽÑ‡ в таблиці '%-.192s'" ER_ERROR_ON_CLOSE - cze "Chyba p-BÅ™i zavÃránà '%-.192s' (chybový kód: %d)" - dan "Fejl ved lukning af '%-.192s' (Fejlkode: %d)" - nla "Fout bij het sluiten van '%-.192s' (Errcode: %d)" - eng "Error on close of '%-.192s' (errno: %d)" - est "Viga faili '%-.192s' sulgemisel (veakood: %d)" - fre "Erreur a la fermeture de '%-.192s' (Errcode: %d)" - ger "Fehler beim Schließen von '%-.192s' (Fehler: %d)" - greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κλείνοντας το '%-.192s' (κωδικός λάθους: %d)" - hun "Hiba a(z) '%-.192s' zarasakor. (hibakod: %d)" - ita "Errore durante la chiusura di '%-.192s' (errno: %d)" - kor "'%-.192s'닫는 중 ì—러 (ì—러번호: %d)" - nor "Feil ved lukking av '%-.192s' (Feilkode: %d)" - norwegian-ny "Feil ved lukking av '%-.192s' (Feilkode: %d)" - pol "BÅ‚?d podczas zamykania '%-.192s' (Kod błędu: %d)" - por "Erro ao fechar '%-.192s' (erro no. %d)" - rum "Eroare inchizind '%-.192s' (errno: %d)" - rus "Ошибка при закрытии '%-.192s' (ошибка: %d)" - serbian "GreÅ¡ka pri zatvaranju '%-.192s' (errno: %d)" - slo "Chyba pri zatváranà '%-.192s' (chybový kód: %d)" - spa "Error en el cierre de '%-.192s' (Error: %d)" - swe "Fick fel vid stängning av '%-.192s' (Felkod: %d)" - ukr "Ðе можу закрити '%-.192s' (помилка: %d)" + cze "Chyba pÅ™i zavÃránà '%-.192s' (chybový kód: %M)" + dan "Fejl ved lukning af '%-.192s' (Fejlkode: %M)" + nla "Fout bij het sluiten van '%-.192s' (Errcode: %M)" + eng "Error on close of '%-.192s' (errno: %M)" + est "Viga faili '%-.192s' sulgemisel (veakood: %M)" + fre "Erreur a la fermeture de '%-.192s' (Errcode: %M)" + ger "Fehler beim Schließen von '%-.192s' (Fehler: %M)" + greek "ΠαÏουσιάστηκε Ï€Ïόβλημα κλείνοντας το '%-.192s' (κωδικός λάθους: %M)" + hun "Hiba a(z) '%-.192s' zarasakor. (hibakod: %M)" + ita "Errore durante la chiusura di '%-.192s' (errno: %M)" + jpn "'%-.192s' ã®ã‚¯ãƒãƒ¼ã‚ºæ™‚エラー (エラー番å·: %M)" + kor "'%-.192s'닫는 중 ì—러 (ì—러번호: %M)" + nor "Feil ved lukking av '%-.192s' (Feilkode: %M)" + norwegian-ny "Feil ved lukking av '%-.192s' (Feilkode: %M)" + pol "BÅ‚?d podczas zamykania '%-.192s' (Kod błędu: %M)" + por "Erro ao fechar '%-.192s' (erro no. %M)" + rum "Eroare inchizind '%-.192s' (errno: %M)" + rus "Ошибка при закрытии '%-.192s' (ошибка: %M)" + serbian "GreÅ¡ka pri zatvaranju '%-.192s' (errno: %M)" + slo "Chyba pri zatváranà '%-.192s' (chybový kód: %M)" + spa "Error en el cierre de '%-.192s' (Error: %M)" + swe "Fick fel vid stängning av '%-.192s' (Felkod: %M)" + ukr "Ðе можу закрити '%-.192s' (помилка: %M)" ER_ERROR_ON_READ - cze "Chyba p-BÅ™i Ätenà souboru '%-.200s' (chybový kód: %d)" - dan "Fejl ved læsning af '%-.200s' (Fejlkode: %d)" - nla "Fout bij het lezen van file '%-.200s' (Errcode: %d)" - eng "Error reading file '%-.200s' (errno: %d)" - jps "'%-.200s' ファイルã®èªã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼ (errno: %d)", - est "Viga faili '%-.200s' lugemisel (veakood: %d)" - fre "Erreur en lecture du fichier '%-.200s' (Errcode: %d)" - ger "Fehler beim Lesen der Datei '%-.200s' (Fehler: %d)" - greek "Î Ïόβλημα κατά την ανάγνωση του αÏχείου '%-.200s' (κωδικός λάθους: %d)" - hun "Hiba a '%-.200s'file olvasasakor. (hibakod: %d)" - ita "Errore durante la lettura del file '%-.200s' (errno: %d)" - jpn "'%-.200s' ファイルã®èªã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼ (errno: %d)" - kor "'%-.200s'í™”ì¼ ì½ê¸° ì—러 (ì—러번호: %d)" - nor "Feil ved lesing av '%-.200s' (Feilkode: %d)" - norwegian-ny "Feil ved lesing av '%-.200s' (Feilkode: %d)" - pol "BÅ‚?d podczas odczytu pliku '%-.200s' (Kod błędu: %d)" - por "Erro ao ler arquivo '%-.200s' (erro no. %d)" - rum "Eroare citind fisierul '%-.200s' (errno: %d)" - rus "Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° '%-.200s' (ошибка: %d)" - serbian "GreÅ¡ka pri Äitanju file-a '%-.200s' (errno: %d)" - slo "Chyba pri ÄÃtanà súboru '%-.200s' (chybový kód: %d)" - spa "Error leyendo el fichero '%-.200s' (Error: %d)" - swe "Fick fel vid läsning av '%-.200s' (Felkod %d)" - ukr "Ðе можу прочитати файл '%-.200s' (помилка: %d)" + cze "Chyba pÅ™i Ätenà souboru '%-.200s' (chybový kód: %M)" + dan "Fejl ved læsning af '%-.200s' (Fejlkode: %M)" + nla "Fout bij het lezen van file '%-.200s' (Errcode: %M)" + eng "Error reading file '%-.200s' (errno: %M)" + est "Viga faili '%-.200s' lugemisel (veakood: %M)" + fre "Erreur en lecture du fichier '%-.200s' (Errcode: %M)" + ger "Fehler beim Lesen der Datei '%-.200s' (Fehler: %M)" + greek "Î Ïόβλημα κατά την ανάγνωση του αÏχείου '%-.200s' (κωδικός λάθους: %M)" + hun "Hiba a '%-.200s'file olvasasakor. (hibakod: %M)" + ita "Errore durante la lettura del file '%-.200s' (errno: %M)" + jpn "ファイル '%-.200s' ã®èªã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼ (エラー番å·: %M)" + kor "'%-.200s'í™”ì¼ ì½ê¸° ì—러 (ì—러번호: %M)" + nor "Feil ved lesing av '%-.200s' (Feilkode: %M)" + norwegian-ny "Feil ved lesing av '%-.200s' (Feilkode: %M)" + pol "BÅ‚?d podczas odczytu pliku '%-.200s' (Kod błędu: %M)" + por "Erro ao ler arquivo '%-.200s' (erro no. %M)" + rum "Eroare citind fisierul '%-.200s' (errno: %M)" + rus "Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° '%-.200s' (ошибка: %M)" + serbian "GreÅ¡ka pri Äitanju file-a '%-.200s' (errno: %M)" + slo "Chyba pri ÄÃtanà súboru '%-.200s' (chybový kód: %M)" + spa "Error leyendo el fichero '%-.200s' (Error: %M)" + swe "Fick fel vid läsning av '%-.200s' (Felkod %M)" + ukr "Ðе можу прочитати файл '%-.200s' (помилка: %M)" ER_ERROR_ON_RENAME - cze "Chyba p-BÅ™i pÅ™ejmenovánà '%-.210s' na '%-.210s' (chybový kód: %d)" - dan "Fejl ved omdøbning af '%-.210s' til '%-.210s' (Fejlkode: %d)" - nla "Fout bij het hernoemen van '%-.210s' naar '%-.210s' (Errcode: %d)" - eng "Error on rename of '%-.210s' to '%-.210s' (errno: %d)" - jps "'%-.210s' ã‚’ '%-.210s' ã« rename ã§ãã¾ã›ã‚“ (errno: %d)", - est "Viga faili '%-.210s' ümbernimetamisel '%-.210s'-ks (veakood: %d)" - fre "Erreur en renommant '%-.210s' en '%-.210s' (Errcode: %d)" - ger "Fehler beim Umbenennen von '%-.210s' in '%-.210s' (Fehler: %d)" - greek "Î Ïόβλημα κατά την μετονομασία του αÏχείου '%-.210s' to '%-.210s' (κωδικός λάθους: %d)" - hun "Hiba a '%-.210s' file atnevezesekor '%-.210s'. (hibakod: %d)" - ita "Errore durante la rinominazione da '%-.210s' a '%-.210s' (errno: %d)" - jpn "'%-.210s' ã‚’ '%-.210s' ã« rename ã§ãã¾ã›ã‚“ (errno: %d)" - kor "'%-.210s'를 '%-.210s'로 ì´ë¦„ 변경중 ì—러 (ì—러번호: %d)" - nor "Feil ved omdøping av '%-.210s' til '%-.210s' (Feilkode: %d)" - norwegian-ny "Feil ved omdøyping av '%-.210s' til '%-.210s' (Feilkode: %d)" - pol "BÅ‚?d podczas zmieniania nazwy '%-.210s' na '%-.210s' (Kod błędu: %d)" - por "Erro ao renomear '%-.210s' para '%-.210s' (erro no. %d)" - rum "Eroare incercind sa renumesc '%-.210s' in '%-.210s' (errno: %d)" - rus "Ошибка при переименовании '%-.210s' в '%-.210s' (ошибка: %d)" - serbian "GreÅ¡ka pri promeni imena '%-.210s' na '%-.210s' (errno: %d)" - slo "Chyba pri premenovávanà '%-.210s' na '%-.210s' (chybový kód: %d)" - spa "Error en el renombrado de '%-.210s' a '%-.210s' (Error: %d)" - swe "Kan inte byta namn frÃ¥n '%-.210s' till '%-.210s' (Felkod: %d)" - ukr "Ðе можу перейменувати '%-.210s' у '%-.210s' (помилка: %d)" + cze "Chyba pÅ™i pÅ™ejmenovánà '%-.210s' na '%-.210s' (chybový kód: %M)" + dan "Fejl ved omdøbning af '%-.210s' til '%-.210s' (Fejlkode: %M)" + nla "Fout bij het hernoemen van '%-.210s' naar '%-.210s' (Errcode: %M)" + eng "Error on rename of '%-.210s' to '%-.210s' (errno: %M)" + est "Viga faili '%-.210s' ümbernimetamisel '%-.210s'-ks (veakood: %M)" + fre "Erreur en renommant '%-.210s' en '%-.210s' (Errcode: %M)" + ger "Fehler beim Umbenennen von '%-.210s' in '%-.210s' (Fehler: %M)" + greek "Î Ïόβλημα κατά την μετονομασία του αÏχείου '%-.210s' to '%-.210s' (κωδικός λάθους: %M)" + hun "Hiba a '%-.210s' file atnevezesekor '%-.210s'. (hibakod: %M)" + ita "Errore durante la rinominazione da '%-.210s' a '%-.210s' (errno: %M)" + jpn "'%-.210s' ã®åå‰ã‚’ '%-.210s' ã«å¤‰æ›´ã§ãã¾ã›ã‚“ (エラー番å·: %M)" + kor "'%-.210s'를 '%-.210s'로 ì´ë¦„ 변경중 ì—러 (ì—러번호: %M)" + nor "Feil ved omdøping av '%-.210s' til '%-.210s' (Feilkode: %M)" + norwegian-ny "Feil ved omdøyping av '%-.210s' til '%-.210s' (Feilkode: %M)" + pol "BÅ‚?d podczas zmieniania nazwy '%-.210s' na '%-.210s' (Kod błędu: %M)" + por "Erro ao renomear '%-.210s' para '%-.210s' (erro no. %M)" + rum "Eroare incercind sa renumesc '%-.210s' in '%-.210s' (errno: %M)" + rus "Ошибка при переименовании '%-.210s' в '%-.210s' (ошибка: %M)" + serbian "GreÅ¡ka pri promeni imena '%-.210s' na '%-.210s' (errno: %M)" + slo "Chyba pri premenovávanà '%-.210s' na '%-.210s' (chybový kód: %M)" + spa "Error en el renombrado de '%-.210s' a '%-.210s' (Error: %M)" + swe "Kan inte byta namn frÃ¥n '%-.210s' till '%-.210s' (Felkod: %M)" + ukr "Ðе можу перейменувати '%-.210s' у '%-.210s' (помилка: %M)" ER_ERROR_ON_WRITE - cze "Chyba p-BÅ™i zápisu do souboru '%-.200s' (chybový kód: %d)" - dan "Fejl ved skriving av filen '%-.200s' (Fejlkode: %d)" - nla "Fout bij het wegschrijven van file '%-.200s' (Errcode: %d)" - eng "Error writing file '%-.200s' (errno: %d)" - jps "'%-.200s' ファイルを書ã事ãŒã§ãã¾ã›ã‚“ (errno: %d)", - est "Viga faili '%-.200s' kirjutamisel (veakood: %d)" - fre "Erreur d'écriture du fichier '%-.200s' (Errcode: %d)" - ger "Fehler beim Speichern der Datei '%-.200s' (Fehler: %d)" - greek "Î Ïόβλημα κατά την αποθήκευση του αÏχείου '%-.200s' (κωδικός λάθους: %d)" - hun "Hiba a '%-.200s' file irasakor. (hibakod: %d)" - ita "Errore durante la scrittura del file '%-.200s' (errno: %d)" - jpn "'%-.200s' ファイルを書ã事ãŒã§ãã¾ã›ã‚“ (errno: %d)" - kor "'%-.200s'í™”ì¼ ê¸°ë¡ ì¤‘ ì—러 (ì—러번호: %d)" - nor "Feil ved skriving av fila '%-.200s' (Feilkode: %d)" - norwegian-ny "Feil ved skriving av fila '%-.200s' (Feilkode: %d)" - pol "BÅ‚?d podczas zapisywania pliku '%-.200s' (Kod błędu: %d)" - por "Erro ao gravar arquivo '%-.200s' (erro no. %d)" - rum "Eroare scriind fisierul '%-.200s' (errno: %d)" - rus "Ошибка запиÑи в файл '%-.200s' (ошибка: %d)" - serbian "GreÅ¡ka pri upisu '%-.200s' (errno: %d)" - slo "Chyba pri zápise do súboru '%-.200s' (chybový kód: %d)" - spa "Error escribiendo el archivo '%-.200s' (Error: %d)" - swe "Fick fel vid skrivning till '%-.200s' (Felkod %d)" - ukr "Ðе можу запиÑати файл '%-.200s' (помилка: %d)" + cze "Chyba pÅ™i zápisu do souboru '%-.200s' (chybový kód: %M)" + dan "Fejl ved skriving av filen '%-.200s' (Fejlkode: %M)" + nla "Fout bij het wegschrijven van file '%-.200s' (Errcode: %M)" + eng "Error writing file '%-.200s' (errno: %M)" + est "Viga faili '%-.200s' kirjutamisel (veakood: %M)" + fre "Erreur d'écriture du fichier '%-.200s' (Errcode: %M)" + ger "Fehler beim Speichern der Datei '%-.200s' (Fehler: %M)" + greek "Î Ïόβλημα κατά την αποθήκευση του αÏχείου '%-.200s' (κωδικός λάθους: %M)" + hun "Hiba a '%-.200s' file irasakor. (hibakod: %M)" + ita "Errore durante la scrittura del file '%-.200s' (errno: %M)" + jpn "ファイル '%-.200s' ã®æ›¸ãè¾¼ã¿ã‚¨ãƒ©ãƒ¼ (エラー番å·: %M)" + kor "'%-.200s'í™”ì¼ ê¸°ë¡ ì¤‘ ì—러 (ì—러번호: %M)" + nor "Feil ved skriving av fila '%-.200s' (Feilkode: %M)" + norwegian-ny "Feil ved skriving av fila '%-.200s' (Feilkode: %M)" + pol "BÅ‚?d podczas zapisywania pliku '%-.200s' (Kod błędu: %M)" + por "Erro ao gravar arquivo '%-.200s' (erro no. %M)" + rum "Eroare scriind fisierul '%-.200s' (errno: %M)" + rus "Ошибка запиÑи в файл '%-.200s' (ошибка: %M)" + serbian "GreÅ¡ka pri upisu '%-.200s' (errno: %M)" + slo "Chyba pri zápise do súboru '%-.200s' (chybový kód: %M)" + spa "Error escribiendo el archivo '%-.200s' (Error: %M)" + swe "Fick fel vid skrivning till '%-.200s' (Felkod %M)" + ukr "Ðе можу запиÑати файл '%-.200s' (помилка: %M)" ER_FILE_USED - cze "'%-.192s' je zam-BÄen proti zmÄ›nám" + cze "'%-.192s' je zamÄen proti zmÄ›nám" dan "'%-.192s' er lÃ¥st mod opdateringer" nla "'%-.192s' is geblokeerd tegen veranderingen" eng "'%-.192s' is locked against change" - jps "'%-.192s' ã¯ãƒãƒƒã‚¯ã•れã¦ã„ã¾ã™", est "'%-.192s' on lukustatud muudatuste vastu" fre "'%-.192s' est verrouillé contre les modifications" ger "'%-.192s' ist für Änderungen gesperrt" greek "'%-.192s' δεν επιτÏÎπονται αλλαγÎÏ‚" hun "'%-.192s' a valtoztatas ellen zarolva" ita "'%-.192s' e` soggetto a lock contro i cambiamenti" - jpn "'%-.192s' ã¯ãƒãƒƒã‚¯ã•れã¦ã„ã¾ã™" + jpn "'%-.192s' ã¯ãƒãƒƒã‚¯ã•れã¦ã„ã¾ã™ã€‚" kor "'%-.192s'ê°€ ë³€ê²½í• ìˆ˜ ì—†ë„ë¡ ìž ê²¨ìžˆì니다." nor "'%-.192s' er lÃ¥st mot oppdateringer" norwegian-ny "'%-.192s' er lÃ¥st mot oppdateringar" @@ -646,18 +628,17 @@ ER_FILE_USED swe "'%-.192s' är lÃ¥st mot användning" ukr "'%-.192s' заблокований на внеÑÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½" ER_FILSORT_ABORT - cze "T-BÅ™ÃdÄ›nà pÅ™eruÅ¡eno" + cze "TÅ™ÃdÄ›nà pÅ™eruÅ¡eno" dan "Sortering afbrudt" nla "Sorteren afgebroken" eng "Sort aborted" - jps "Sort 䏿–", est "Sorteerimine katkestatud" fre "Tri alphabétique abandonné" ger "Sortiervorgang abgebrochen" greek "Η διαδικασία ταξινόμισης ακυÏώθηκε" hun "Sikertelen rendezes" ita "Operazione di ordinamento abbandonata" - jpn "Sort 䏿–" + jpn "ソート処ç†ã‚’䏿–ã—ã¾ã—ãŸã€‚" kor "소트가 중단ë˜ì—ˆìŠµë‹ˆë‹¤." nor "Sortering avbrutt" norwegian-ny "Sortering avbrote" @@ -675,14 +656,13 @@ ER_FORM_NOT_FOUND dan "View '%-.192s' eksisterer ikke for '%-.192s'" nla "View '%-.192s' bestaat niet voor '%-.192s'" eng "View '%-.192s' doesn't exist for '%-.192s'" - jps "View '%-.192s' ㌠'%-.192s' ã«å®šç¾©ã•れã¦ã„ã¾ã›ã‚“", est "Vaade '%-.192s' ei eksisteeri '%-.192s' jaoks" fre "La vue (View) '%-.192s' n'existe pas pour '%-.192s'" ger "View '%-.192s' existiert für '%-.192s' nicht" greek "Το View '%-.192s' δεν υπάÏχει για '%-.192s'" hun "A(z) '%-.192s' nezet nem letezik a(z) '%-.192s'-hoz" ita "La view '%-.192s' non esiste per '%-.192s'" - jpn "View '%-.192s' ㌠'%-.192s' ã«å®šç¾©ã•れã¦ã„ã¾ã›ã‚“" + jpn "ビュー '%-.192s' 㯠'%-.192s' ã«å˜åœ¨ã—ã¾ã›ã‚“。" kor "ë·° '%-.192s'ê°€ '%-.192s'ì—서는 존재하지 않ì니다." nor "View '%-.192s' eksisterer ikke for '%-.192s'" norwegian-ny "View '%-.192s' eksisterar ikkje for '%-.192s'" @@ -696,66 +676,38 @@ ER_FORM_NOT_FOUND swe "Formulär '%-.192s' finns inte i '%-.192s'" ukr "ВиглÑд '%-.192s' не Ñ–Ñнує Ð´Ð»Ñ '%-.192s'" ER_GET_ERRNO - cze "Obsluha tabulky vr-Bátila chybu %d" - dan "Modtog fejl %d fra tabel hÃ¥ndteringen" - nla "Fout %d van tabel handler" - eng "Got error %d from storage engine" - est "Tabeli handler tagastas vea %d" - fre "Reçu l'erreur %d du handler de la table" - ger "Fehler %d (Speicher-Engine)" - greek "Ελήφθη μήνυμα λάθους %d από τον χειÏιστή πίνακα (table handler)" - hun "%d hibajelzes a tablakezelotol" - ita "Rilevato l'errore %d dal gestore delle tabelle" - jpn "Got error %d from table handler" - kor "í…Œì´ë¸” handlerì—서 %d ì—러가 ë°œìƒ í•˜ì˜€ìŠµë‹ˆë‹¤." - nor "Mottok feil %d fra tabell hÃ¥ndterer" - norwegian-ny "Mottok feil %d fra tabell handterar" - pol "Otrzymano bÅ‚?d %d z obsÅ‚ugi tabeli" - por "Obteve erro %d no manipulador de tabelas" - rum "Eroarea %d obtinuta din handlerul tabelei" - rus "Получена ошибка %d от обработчика таблиц" - serbian "Handler tabela je vratio greÅ¡ku %d" - slo "Obsluha tabuľky vrátila chybu %d" - spa "Error %d desde el manejador de la tabla" - swe "Fick felkod %d frÃ¥n databashanteraren" - ukr "Отримано помилку %d від деÑкриптора таблиці" + nla "Fout %M van tabel handler %s" + eng "Got error %M from storage engine %s" + fre "Reçu l'erreur %M du handler de la table %s" + ger "Fehler %M von Speicher-Engine %s" + greek "Ελήφθη μήνυμα λάθους %M από τον χειÏιστή πίνακα (table handler) %s" + ita "Rilevato l'errore %M dal gestore delle tabelle %s" + nor "Mottok feil %M fra tabell hÃ¥ndterer %s" + norwegian-ny "Mottok feil %M fra tabell handterar %s" + pol "Otrzymano bÅ‚?d %M z obsÅ‚ugi tabeli %s" + por "Obteve erro %M no manipulador de tabelas %s" + rum "Eroarea %M obtinuta din handlerul tabelei %s" + rus "Получена ошибка %M от обработчика таблиц %s" + spa "Error %M desde el manejador de la tabla %s" + swe "Fick felkod %M frÃ¥n databashanteraren %s" + ukr "Отримано помилку %M від деÑкриптора таблиці %s" ER_ILLEGAL_HA - cze "Obsluha tabulky '%-.192s' nem-Bá tento parametr" - dan "Denne mulighed eksisterer ikke for tabeltypen '%-.192s'" - nla "Tabel handler voor '%-.192s' heeft deze optie niet" - eng "Table storage engine for '%-.192s' doesn't have this option" - est "Tabeli '%-.192s' handler ei toeta antud operatsiooni" - fre "Le handler de la table '%-.192s' n'a pas cette option" - ger "Diese Option gibt es nicht (Speicher-Engine für '%-.192s')" - greek "Ο χειÏιστής πίνακα (table handler) για '%-.192s' δεν διαθÎτει αυτή την επιλογή" - hun "A(z) '%-.192s' tablakezelonek nincs ilyen opcioja" - ita "Il gestore delle tabelle per '%-.192s' non ha questa opzione" - jpn "Table handler for '%-.192s' doesn't have this option" - kor "'%-.192s'ì˜ í…Œì´ë¸” handler는 ì´ëŸ¬í•œ ì˜µì…˜ì„ ì œê³µí•˜ì§€ 않ì니다." - nor "Tabell hÃ¥ndtereren for '%-.192s' har ikke denne muligheten" - norwegian-ny "Tabell hÃ¥ndteraren for '%-.192s' har ikkje denne moglegheita" - pol "ObsÅ‚uga tabeli '%-.192s' nie posiada tej opcji" - por "Manipulador de tabela para '%-.192s' não tem esta opção" - rum "Handlerul tabelei pentru '%-.192s' nu are aceasta optiune" - rus "Обработчик таблицы '%-.192s' не поддерживает Ñту возможноÑть" - serbian "Handler tabela za '%-.192s' nema ovu opciju" - slo "Obsluha tabuľky '%-.192s' nemá tento parameter" - spa "El manejador de la tabla de '%-.192s' no tiene esta opcion" - swe "Tabellhanteraren for tabell '%-.192s' stödjer ej detta" - ukr "ДеÑкриптор таблиці '%-.192s' не має цієї влаÑтивоÑті" + eng "Storage engine %s of the table %`s.%`s doesn't have this option" + ger "Diese Option gibt es nicht in Speicher-Engine %s für %`s.%`s" + rus "Обработчик %s таблицы %`s.%`s не поддерживает Ñту возможноÑть" + ukr "ДеÑкриптор %s таблиці %`s.%`s не має цієї влаÑтивоÑті" ER_KEY_NOT_FOUND - cze "Nemohu naj-BÃt záznam v '%-.192s'" + cze "Nemohu najÃt záznam v '%-.192s'" dan "Kan ikke finde posten i '%-.192s'" nla "Kan record niet vinden in '%-.192s'" eng "Can't find record in '%-.192s'" - jps "'%-.192s'ã®ãªã‹ã«ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒè¦‹ä»˜ã‹ã‚Šã¾ã›ã‚“", est "Ei suuda leida kirjet '%-.192s'-s" fre "Ne peut trouver l'enregistrement dans '%-.192s'" ger "Kann Datensatz in '%-.192s' nicht finden" greek "ΑδÏνατη η ανεÏÏεση εγγÏαφής στο '%-.192s'" hun "Nem talalhato a rekord '%-.192s'-ben" ita "Impossibile trovare il record in '%-.192s'" - jpn "'%-.192s'ã®ãªã‹ã«ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒè¦‹ä»˜ã‹ã‚Šã¾ã›ã‚“" + jpn "'%-.192s' ã«ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。" kor "'%-.192s'ì—서 ë ˆì½”ë“œë¥¼ ì°¾ì„ ìˆ˜ ì—†ì니다." nor "Kan ikke finne posten i '%-.192s'" norwegian-ny "Kan ikkje finne posten i '%-.192s'" @@ -769,18 +721,17 @@ ER_KEY_NOT_FOUND swe "Hittar inte posten '%-.192s'" ukr "Ðе можу запиÑати у '%-.192s'" ER_NOT_FORM_FILE - cze "Nespr-Bávná informace v souboru '%-.200s'" + cze "Nesprávná informace v souboru '%-.200s'" dan "Forkert indhold i: '%-.200s'" nla "Verkeerde info in file: '%-.200s'" eng "Incorrect information in file: '%-.200s'" - jps "ファイル '%-.200s' ã® info ãŒé–“é•ã£ã¦ã„るよã†ã§ã™", est "Vigane informatsioon failis '%-.200s'" fre "Information erronnée dans le fichier: '%-.200s'" ger "Falsche Information in Datei '%-.200s'" greek "Λάθος πληÏοφοÏίες στο αÏχείο: '%-.200s'" hun "Ervenytelen info a file-ban: '%-.200s'" ita "Informazione errata nel file: '%-.200s'" - jpn "ファイル '%-.200s' ã® info ãŒé–“é•ã£ã¦ã„るよã†ã§ã™" + jpn "ファイル '%-.200s' å†…ã®æƒ…å ±ãŒä¸æ£ã§ã™ã€‚" kor "í™”ì¼ì˜ ë¶€ì •í™•í•œ ì •ë³´: '%-.200s'" nor "Feil informasjon i filen: '%-.200s'" norwegian-ny "Feil informasjon i fila: '%-.200s'" @@ -794,18 +745,17 @@ ER_NOT_FORM_FILE swe "Felaktig fil: '%-.200s'" ukr "Хибна Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ñƒ файлі: '%-.200s'" ER_NOT_KEYFILE - cze "Nespr-Bávný klÃÄ pro tabulku '%-.200s'; pokuste se ho opravit" + cze "Nesprávný klÃÄ pro tabulku '%-.200s'; pokuste se ho opravit" dan "Fejl i indeksfilen til tabellen '%-.200s'; prøv at reparere den" nla "Verkeerde zoeksleutel file voor tabel: '%-.200s'; probeer het te repareren" eng "Incorrect key file for table '%-.200s'; try to repair it" - jps "'%-.200s' テーブル㮠key file ãŒé–“é•ã£ã¦ã„るよã†ã§ã™. 修復をã—ã¦ãã ã•ã„", est "Tabeli '%-.200s' võtmefail on vigane; proovi seda parandada" fre "Index corrompu dans la table: '%-.200s'; essayez de le réparer" ger "Fehlerhafte Index-Datei für Tabelle '%-.200s'; versuche zu reparieren" greek "Λάθος αÏχείο ταξινόμισης (key file) για τον πίνακα: '%-.200s'; ΠαÏακαλώ, διοÏθώστε το!" hun "Ervenytelen kulcsfile a tablahoz: '%-.200s'; probalja kijavitani!" ita "File chiave errato per la tabella : '%-.200s'; prova a riparalo" - jpn "'%-.200s' テーブル㮠key file ãŒé–“é•ã£ã¦ã„るよã†ã§ã™. 修復をã—ã¦ãã ã•ã„" + jpn "表 '%-.200s' ã®ç´¢å¼•ファイル(key file)ã®å†…容ãŒä¸æ£ã§ã™ã€‚修復を試行ã—ã¦ãã ã•ã„。" kor "'%-.200s' í…Œì´ë¸”ì˜ ë¶€ì •í™•í•œ 키 존재. ìˆ˜ì •í•˜ì‹œì˜¤!" nor "Tabellen '%-.200s' har feil i nøkkelfilen; forsøk Ã¥ reparer den" norwegian-ny "Tabellen '%-.200s' har feil i nykkelfila; prøv Ã¥ reparere den" @@ -819,18 +769,17 @@ ER_NOT_KEYFILE swe "Fatalt fel vid hantering av register '%-.200s'; kör en reparation" ukr "Хибний файл ключей Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ–: '%-.200s'; Спробуйте його відновити" ER_OLD_KEYFILE - cze "Star-Bý klÃÄový soubor pro '%-.192s'; opravte ho." + cze "Starý klÃÄový soubor pro '%-.192s'; opravte ho." dan "Gammel indeksfil for tabellen '%-.192s'; reparer den" nla "Oude zoeksleutel file voor tabel '%-.192s'; repareer het!" eng "Old key file for table '%-.192s'; repair it!" - jps "'%-.192s' テーブルã¯å¤ã„å½¢å¼ã® key file ã®ã‚ˆã†ã§ã™; 修復をã—ã¦ãã ã•ã„", est "Tabeli '%-.192s' võtmefail on aegunud; paranda see!" fre "Vieux fichier d'index pour la table '%-.192s'; réparez le!" ger "Alte Index-Datei für Tabelle '%-.192s'. Bitte reparieren" greek "Παλαιό αÏχείο ταξινόμισης (key file) για τον πίνακα '%-.192s'; ΠαÏακαλώ, διοÏθώστε το!" hun "Regi kulcsfile a '%-.192s'tablahoz; probalja kijavitani!" ita "File chiave vecchio per la tabella '%-.192s'; riparalo!" - jpn "'%-.192s' テーブルã¯å¤ã„å½¢å¼ã® key file ã®ã‚ˆã†ã§ã™; 修復をã—ã¦ãã ã•ã„" + jpn "表 '%-.192s' ã®ç´¢å¼•ファイル(key file)ã¯å¤ã„å½¢å¼ã§ã™ã€‚修復ã—ã¦ãã ã•ã„。" kor "'%-.192s' í…Œì´ë¸”ì˜ ì´ì „ë²„ì ¼ì˜ í‚¤ 존재. ìˆ˜ì •í•˜ì‹œì˜¤!" nor "Gammel nøkkelfil for tabellen '%-.192s'; reparer den!" norwegian-ny "Gammel nykkelfil for tabellen '%-.192s'; reparer den!" @@ -844,18 +793,17 @@ ER_OLD_KEYFILE swe "Gammal nyckelfil '%-.192s'; reparera registret" ukr "Старий файл ключей Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ– '%-.192s'; Відновіть його!" ER_OPEN_AS_READONLY - cze "'%-.192s' je jen pro -BÄtenÃ" + cze "'%-.192s' je jen pro ÄtenÃ" dan "'%-.192s' er skrivebeskyttet" nla "'%-.192s' is alleen leesbaar" eng "Table '%-.192s' is read only" - jps "'%-.192s' ã¯èªã¿è¾¼ã¿å°‚用ã§ã™", est "Tabel '%-.192s' on ainult lugemiseks" fre "'%-.192s' est en lecture seulement" ger "Tabelle '%-.192s' ist nur lesbar" greek "'%-.192s' επιτÏÎπεται μόνο η ανάγνωση" hun "'%-.192s' irasvedett" ita "'%-.192s' e` di sola lettura" - jpn "'%-.192s' ã¯èªã¿è¾¼ã¿å°‚用ã§ã™" + jpn "表 '%-.192s' ã¯èªã¿è¾¼ã¿å°‚用ã§ã™ã€‚" kor "í…Œì´ë¸” '%-.192s'는 ì½ê¸°ì „ìš© 입니다." nor "'%-.192s' er skrivebeskyttet" norwegian-ny "'%-.192s' er skrivetryggja" @@ -869,18 +817,17 @@ ER_OPEN_AS_READONLY swe "'%-.192s' är skyddad mot förändring" ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ" ER_OUTOFMEMORY HY001 S1001 - cze "M-Bálo pamÄ›ti. PÅ™estartujte daemona a zkuste znovu (je potÅ™eba %d bytů)" + cze "Málo pamÄ›ti. PÅ™estartujte daemona a zkuste znovu (je potÅ™eba %d bytů)" dan "Ikke mere hukommelse. Genstart serveren og prøv igen (mangler %d bytes)" nla "Geen geheugen meer. Herstart server en probeer opnieuw (%d bytes nodig)" eng "Out of memory; restart server and try again (needed %d bytes)" - jps "Out of memory. デーモンをリスタートã—ã¦ã¿ã¦ãã ã•ã„ (%d bytes å¿…è¦)", est "Mälu sai otsa. Proovi MariaDB uuesti käivitada (puudu jäi %d baiti)" fre "Manque de mémoire. Redémarrez le démon et ré-essayez (%d octets nécessaires)" ger "Kein Speicher vorhanden (%d Bytes benötigt). Bitte Server neu starten" greek "Δεν υπάÏχει διαθÎσιμη μνήμη. Î Ïοσπαθήστε πάλι, επανεκινώντας τη διαδικασία (demon) (χÏειάζονται %d bytes)" hun "Nincs eleg memoria. Inditsa ujra a demont, es probalja ismet. (%d byte szukseges.)" ita "Memoria esaurita. Fai ripartire il demone e riprova (richiesti %d bytes)" - jpn "Out of memory. デーモンをリスタートã—ã¦ã¿ã¦ãã ã•ã„ (%d bytes å¿…è¦)" + jpn "メモリãŒä¸è¶³ã—ã¦ã„ã¾ã™ã€‚サーãƒãƒ¼ã‚’å†èµ·å‹•ã—ã¦ã¿ã¦ãã ã•ã„。(%d ãƒã‚¤ãƒˆã®å‰²ã‚Šå½“ã¦ã«å¤±æ•—)" kor "Out of memory. ë°ëª¬ì„ 재 실행 후 다시 시작하시오 (needed %d bytes)" nor "Ikke mer minne. Star pÃ¥ nytt tjenesten og prøv igjen (trengte %d byter)" norwegian-ny "Ikkje meir minne. Start pÃ¥ nytt tenesten og prøv igjen (trengte %d bytar)" @@ -894,18 +841,17 @@ ER_OUTOFMEMORY HY001 S1001 swe "Oväntat slut pÃ¥ minnet, starta om programmet och försök pÃ¥ nytt (Behövde %d bytes)" ukr "Брак пам'Ñті. РеÑтартуйте Ñервер та Ñпробуйте знову (потрібно %d байтів)" ER_OUT_OF_SORTMEMORY HY001 S1001 - cze "M-Bálo pamÄ›ti pro tÅ™ÃdÄ›nÃ. ZvyÅ¡te velikost tÅ™ÃdÃcÃho bufferu" + cze "Málo pamÄ›ti pro tÅ™ÃdÄ›nÃ. ZvyÅ¡te velikost tÅ™ÃdÃcÃho bufferu" dan "Ikke mere sorteringshukommelse. Øg sorteringshukommelse (sort buffer size) for serveren" nla "Geen geheugen om te sorteren. Verhoog de server sort buffer size" eng "Out of sort memory, consider increasing server sort buffer size" - jps "Out of sort memory. sort buffer size ãŒè¶³ã‚Šãªã„よã†ã§ã™.", est "Mälu sai sorteerimisel otsa. Suurenda MariaDB-i sorteerimispuhvrit" fre "Manque de mémoire pour le tri. Augmentez-la." ger "Kein Speicher zum Sortieren vorhanden. sort_buffer_size sollte im Server erhöht werden" greek "Δεν υπάÏχει διαθÎσιμη μνήμη για ταξινόμιση. Αυξήστε το sort buffer size για τη διαδικασία (demon)" hun "Nincs eleg memoria a rendezeshez. Novelje a rendezo demon puffermeretet" ita "Memoria per gli ordinamenti esaurita. Incrementare il 'sort_buffer' al demone" - jpn "Out of sort memory. sort buffer size ãŒè¶³ã‚Šãªã„よã†ã§ã™." + jpn "ソートメモリãŒä¸è¶³ã—ã¦ã„ã¾ã™ã€‚ソートãƒãƒƒãƒ•ァサイズ(sort buffer size)ã®å¢—åŠ ã‚’æ¤œè¨Žã—ã¦ãã ã•ã„。" kor "Out of sort memory. daemon sort bufferì˜ í¬ê¸°ë¥¼ ì¦ê°€ì‹œí‚¤ì„¸ìš”" nor "Ikke mer sorteringsminne. Vurder Ã¥ øke sorteringsminnet (sort buffer size) for tjenesten" norwegian-ny "Ikkje meir sorteringsminne. Vurder Ã¥ auke sorteringsminnet (sorteringsbuffer storleik) for tenesten" @@ -919,43 +865,41 @@ ER_OUT_OF_SORTMEMORY HY001 S1001 swe "Sorteringsbufferten räcker inte till. Kontrollera startparametrarna" ukr "Брак пам'Ñті Ð´Ð»Ñ ÑортуваннÑ. Треба збільшити розмір буфера ÑÐ¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñƒ Ñервера" ER_UNEXPECTED_EOF - cze "Neo-BÄekávaný konec souboru pÅ™i Ätenà '%-.192s' (chybový kód: %d)" - dan "Uventet afslutning pÃ¥ fil (eof) ved læsning af filen '%-.192s' (Fejlkode: %d)" - nla "Onverwachte eof gevonden tijdens het lezen van file '%-.192s' (Errcode: %d)" - eng "Unexpected EOF found when reading file '%-.192s' (errno: %d)" - jps "'%-.192s' ファイルをèªã¿è¾¼ã¿ä¸ã« EOF ãŒäºˆæœŸã›ã¬æ‰€ã§ç¾ã‚Œã¾ã—ãŸ. (errno: %d)", - est "Ootamatu faililõpumärgend faili '%-.192s' lugemisel (veakood: %d)" - fre "Fin de fichier inattendue en lisant '%-.192s' (Errcode: %d)" - ger "Unerwartetes Ende beim Lesen der Datei '%-.192s' (Fehler: %d)" - greek "Κατά τη διάÏκεια της ανάγνωσης, βÏÎθηκε απÏοσδόκητα το Ï„Îλος του αÏχείου '%-.192s' (κωδικός λάθους: %d)" - hun "Varatlan filevege-jel a '%-.192s'olvasasakor. (hibakod: %d)" - ita "Fine del file inaspettata durante la lettura del file '%-.192s' (errno: %d)" - jpn "'%-.192s' ファイルをèªã¿è¾¼ã¿ä¸ã« EOF ãŒäºˆæœŸã›ã¬æ‰€ã§ç¾ã‚Œã¾ã—ãŸ. (errno: %d)" - kor "'%-.192s' í™”ì¼ì„ ì½ëŠ” ë„중 ìž˜ëª»ëœ eofì„ ë°œê²¬ (ì—러번호: %d)" - nor "Uventet slutt pÃ¥ fil (eof) ved lesing av filen '%-.192s' (Feilkode: %d)" - norwegian-ny "Uventa slutt pÃ¥ fil (eof) ved lesing av fila '%-.192s' (Feilkode: %d)" - pol "Nieoczekiwany 'eof' napotkany podczas czytania z pliku '%-.192s' (Kod błędu: %d)" - por "Encontrado fim de arquivo inesperado ao ler arquivo '%-.192s' (erro no. %d)" - rum "Sfirsit de fisier neasteptat in citirea fisierului '%-.192s' (errno: %d)" - rus "Ðеожиданный конец файла '%-.192s' (ошибка: %d)" - serbian "NeoÄekivani kraj pri Äitanju file-a '%-.192s' (errno: %d)" - slo "NeoÄakávaný koniec súboru pri ÄÃtanà '%-.192s' (chybový kód: %d)" - spa "Inesperado fin de ficheroU mientras leiamos el archivo '%-.192s' (Error: %d)" - swe "Oväntat filslut vid läsning frÃ¥n '%-.192s' (Felkod: %d)" - ukr "Хибний кінець файлу '%-.192s' (помилка: %d)" + cze "NeoÄekávaný konec souboru pÅ™i Ätenà '%-.192s' (chybový kód: %M)" + dan "Uventet afslutning pÃ¥ fil (eof) ved læsning af filen '%-.192s' (Fejlkode: %M)" + nla "Onverwachte eof gevonden tijdens het lezen van file '%-.192s' (Errcode: %M)" + eng "Unexpected EOF found when reading file '%-.192s' (errno: %M)" + est "Ootamatu faililõpumärgend faili '%-.192s' lugemisel (veakood: %M)" + fre "Fin de fichier inattendue en lisant '%-.192s' (Errcode: %M)" + ger "Unerwartetes Ende beim Lesen der Datei '%-.192s' (Fehler: %M)" + greek "Κατά τη διάÏκεια της ανάγνωσης, βÏÎθηκε απÏοσδόκητα το Ï„Îλος του αÏχείου '%-.192s' (κωδικός λάθους: %M)" + hun "Varatlan filevege-jel a '%-.192s'olvasasakor. (hibakod: %M)" + ita "Fine del file inaspettata durante la lettura del file '%-.192s' (errno: %M)" + jpn "ファイル '%-.192s' ã‚’èªã¿è¾¼ã¿ä¸ã«äºˆæœŸã›ãšãƒ•ァイルã®çµ‚端ã«é”ã—ã¾ã—ãŸã€‚(エラー番å·: %M)" + kor "'%-.192s' í™”ì¼ì„ ì½ëŠ” ë„중 ìž˜ëª»ëœ eofì„ ë°œê²¬ (ì—러번호: %M)" + nor "Uventet slutt pÃ¥ fil (eof) ved lesing av filen '%-.192s' (Feilkode: %M)" + norwegian-ny "Uventa slutt pÃ¥ fil (eof) ved lesing av fila '%-.192s' (Feilkode: %M)" + pol "Nieoczekiwany 'eof' napotkany podczas czytania z pliku '%-.192s' (Kod błędu: %M)" + por "Encontrado fim de arquivo inesperado ao ler arquivo '%-.192s' (erro no. %M)" + rum "Sfirsit de fisier neasteptat in citirea fisierului '%-.192s' (errno: %M)" + rus "Ðеожиданный конец файла '%-.192s' (ошибка: %M)" + serbian "NeoÄekivani kraj pri Äitanju file-a '%-.192s' (errno: %M)" + slo "NeoÄakávaný koniec súboru pri ÄÃtanà '%-.192s' (chybový kód: %M)" + spa "Inesperado fin de ficheroU mientras leiamos el archivo '%-.192s' (Error: %M)" + swe "Oväntat filslut vid läsning frÃ¥n '%-.192s' (Felkod: %M)" + ukr "Хибний кінець файлу '%-.192s' (помилка: %M)" ER_CON_COUNT_ERROR 08004 - cze "P-BÅ™ÃliÅ¡ mnoho spojenÃ" + cze "PÅ™ÃliÅ¡ mnoho spojenÃ" dan "For mange forbindelser (connections)" nla "Te veel verbindingen" eng "Too many connections" - jps "接続ãŒå¤šã™ãŽã¾ã™", est "Liiga palju samaaegseid ühendusi" fre "Trop de connexions" ger "Zu viele Verbindungen" greek "ΥπάÏχουν πολλÎÏ‚ συνδÎσεις..." hun "Tul sok kapcsolat" ita "Troppe connessioni" - jpn "接続ãŒå¤šã™ãŽã¾ã™" + jpn "接続ãŒå¤šã™ãŽã¾ã™ã€‚" kor "너무 ë§Žì€ ì—°ê²°... max_connectionì„ ì¦ê°€ 시키시오..." nor "For mange tilkoblinger (connections)" norwegian-ny "For mange tilkoplingar (connections)" @@ -969,18 +913,17 @@ ER_CON_COUNT_ERROR 08004 swe "För mÃ¥nga anslutningar" ukr "Забагато з'єднань" ER_OUT_OF_RESOURCES - cze "M-Bálo prostoru/pamÄ›ti pro thread" + cze "Málo prostoru/pamÄ›ti pro thread" dan "UdgÃ¥et for trÃ¥de/hukommelse" nla "Geen thread geheugen meer; controleer of mysqld of andere processen al het beschikbare geheugen gebruikt. Zo niet, dan moet u wellicht 'ulimit' gebruiken om mysqld toe te laten meer geheugen te benutten, of u kunt extra swap ruimte toevoegen" eng "Out of memory; check if mysqld or some other process uses all available memory; if not, you may have to use 'ulimit' to allow mysqld to use more memory or you can add more swap space" - jps "Out of memory; mysqld ã‹ãã®ä»–ã®ãƒ—ãƒã‚»ã‚¹ãŒãƒ¡ãƒ¢ãƒªãƒ¼ã‚’å…¨ã¦ä½¿ã£ã¦ã„ã‚‹ã‹ç¢ºèªã—ã¦ãã ã•ã„. メモリーを使ã„切ã£ã¦ã„ãªã„å ´åˆã€'ulimit' ã‚’è¨å®šã—㦠mysqld ã®ãƒ¡ãƒ¢ãƒªãƒ¼ä½¿ç”¨é™ç•Œé‡ã‚’多ãã™ã‚‹ã‹ã€swap space を増やã—ã¦ã¿ã¦ãã ã•ã„", est "Mälu sai otsa. Võimalik, et aitab swap-i lisamine või käsu 'ulimit' abil MariaDB-le rohkema mälu kasutamise lubamine" fre "Manque de 'threads'/mémoire" ger "Kein Speicher mehr vorhanden. Prüfen Sie, ob mysqld oder ein anderer Prozess den gesamten Speicher verbraucht. Wenn nicht, sollten Sie mit 'ulimit' dafür sorgen, dass mysqld mehr Speicher benutzen darf, oder mehr Swap-Speicher einrichten" greek "Î Ïόβλημα με τη διαθÎσιμη μνήμη (Out of thread space/memory)" hun "Elfogyott a thread-memoria" ita "Fine dello spazio/memoria per i thread" - jpn "Out of memory; mysqld ã‹ãã®ä»–ã®ãƒ—ãƒã‚»ã‚¹ãŒãƒ¡ãƒ¢ãƒªãƒ¼ã‚’å…¨ã¦ä½¿ã£ã¦ã„ã‚‹ã‹ç¢ºèªã—ã¦ãã ã•ã„. メモリーを使ã„切ã£ã¦ã„ãªã„å ´åˆã€'ulimit' ã‚’è¨å®šã—㦠mysqld ã®ãƒ¡ãƒ¢ãƒªãƒ¼ä½¿ç”¨é™ç•Œé‡ã‚’多ãã™ã‚‹ã‹ã€swap space を増やã—ã¦ã¿ã¦ãã ã•ã„" + jpn "メモリãŒä¸è¶³ã—ã¦ã„ã¾ã™ã€‚mysqld ã‚„ãã®ä»–ã®ãƒ—ãƒã‚»ã‚¹ãŒãƒ¡ãƒ¢ãƒªãƒ¼ã‚’使ã„切ã£ã¦ã„ãªã„ã‹ç¢ºèªã—ã¦ä¸‹ã•ã„。メモリーを使ã„切ã£ã¦ã„ãªã„å ´åˆã€'ulimit'ã®è¨å®šç‰ã§ mysqld ã®ãƒ¡ãƒ¢ãƒªãƒ¼ä½¿ç”¨æœ€å¤§é‡ã‚’多ãã™ã‚‹ã‹ã€ã‚¹ãƒ¯ãƒƒãƒ—é ˜åŸŸã‚’å¢—ã‚„ã™å¿…è¦ãŒã‚ã‚‹ã‹ã‚‚ã—れã¾ã›ã‚“。" # This message failed to convert from euc-kr, skipped nor "Tomt for trÃ¥d plass/minne" norwegian-ny "Tomt for trÃ¥d plass/minne" @@ -994,18 +937,17 @@ ER_OUT_OF_RESOURCES swe "Fick slut pÃ¥ minnet. Kontrollera om mysqld eller nÃ¥gon annan process använder allt tillgängligt minne. Om inte, försök använda 'ulimit' eller allokera mera swap" ukr "Брак пам'Ñті; Перевірте чи mysqld або ÑкіÑÑŒ інші процеÑи викориÑтовують уÑÑŽ доÑтупну пам'Ñть. Як ні, то ви можете ÑкориÑтатиÑÑ 'ulimit', аби дозволити mysqld викориÑтовувати більше пам'Ñті або ви можете додати більше міÑÑ†Ñ Ð¿Ñ–Ð´ Ñвап" ER_BAD_HOST_ERROR 08S01 - cze "Nemohu zjistit jm-Béno stroje pro VaÅ¡i adresu" + cze "Nemohu zjistit jméno stroje pro VaÅ¡i adresu" dan "Kan ikke fÃ¥ værtsnavn for din adresse" nla "Kan de hostname niet krijgen van uw adres" eng "Can't get hostname for your address" - jps "ãã® address ã® hostname ãŒå¼•ã‘ã¾ã›ã‚“.", est "Ei suuda lahendada IP aadressi masina nimeks" fre "Ne peut obtenir de hostname pour votre adresse" ger "Kann Hostnamen für diese Adresse nicht erhalten" greek "Δεν Îγινε γνωστό το hostname για την address σας" hun "A gepnev nem allapithato meg a cimbol" ita "Impossibile risalire al nome dell'host dall'indirizzo (risoluzione inversa)" - jpn "ãã® address ã® hostname ãŒå¼•ã‘ã¾ã›ã‚“." + jpn "IPアドレスã‹ã‚‰ãƒ›ã‚¹ãƒˆåを解決ã§ãã¾ã›ã‚“。" kor "ë‹¹ì‹ ì˜ ì»´í“¨í„°ì˜ í˜¸ìŠ¤íŠ¸ì´ë¦„ì„ ì–»ì„ ìˆ˜ ì—†ì니다." nor "Kan ikke fÃ¥ tak i vertsnavn for din adresse" norwegian-ny "Kan ikkje fÃ¥ tak i vertsnavn for di adresse" @@ -1019,7 +961,7 @@ ER_BAD_HOST_ERROR 08S01 swe "Kan inte hitta 'hostname' för din adress" ukr "Ðе можу визначити ім'Ñ Ñ…Ð¾Ñту Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ñ— адреÑи" ER_HANDSHAKE_ERROR 08S01 - cze "Chyba p-BÅ™i ustavovánà spojenÃ" + cze "Chyba pÅ™i ustavovánà spojenÃ" dan "Forkert hÃ¥ndtryk (handshake)" nla "Verkeerde handshake" eng "Bad handshake" @@ -1029,6 +971,7 @@ ER_HANDSHAKE_ERROR 08S01 greek "Η αναγνώÏιση (handshake) δεν Îγινε σωστά" hun "A kapcsolatfelvetel nem sikerult (Bad handshake)" ita "Negoziazione impossibile" + jpn "ãƒãƒ³ãƒ‰ã‚·ã‚§ã‚¤ã‚¯ã‚¨ãƒ©ãƒ¼" nor "Feil hÃ¥ndtrykk (handshake)" norwegian-ny "Feil handtrykk (handshake)" pol "ZÅ‚y uchwyt(handshake)" @@ -1041,7 +984,7 @@ ER_HANDSHAKE_ERROR 08S01 swe "Fel vid initiering av kommunikationen med klienten" ukr "Ðевірна уÑтановка зв'Ñзку" ER_DBACCESS_DENIED_ERROR 42000 - cze "P-BÅ™Ãstup pro uživatele '%s'@'%s' k databázi '%-.192s' nenà povolen" + cze "PÅ™Ãstup pro uživatele '%s'@'%s' k databázi '%-.192s' nenà povolen" dan "Adgang nægtet bruger: '%s'@'%s' til databasen '%-.192s'" nla "Toegang geweigerd voor gebruiker: '%s'@'%s' naar database '%-.192s'" eng "Access denied for user '%s'@'%s' to database '%-.192s'" @@ -1065,7 +1008,7 @@ ER_DBACCESS_DENIED_ERROR 42000 swe "Användare '%s'@'%s' är ej berättigad att använda databasen %-.192s" ukr "ДоÑтуп заборонено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача: '%s'@'%s' до бази данних '%-.192s'" ER_ACCESS_DENIED_ERROR 28000 - cze "P-BÅ™Ãstup pro uživatele '%s'@'%s' (s heslem %s)" + cze "PÅ™Ãstup pro uživatele '%s'@'%s' (s heslem %s)" dan "Adgang nægtet bruger: '%s'@'%s' (Bruger adgangskode: %s)" nla "Toegang geweigerd voor gebruiker: '%s'@'%s' (Wachtwoord gebruikt: %s)" eng "Access denied for user '%s'@'%s' (using password: %s)" @@ -1089,18 +1032,17 @@ ER_ACCESS_DENIED_ERROR 28000 swe "Användare '%s'@'%s' är ej berättigad att logga in (Använder lösen: %s)" ukr "ДоÑтуп заборонено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача: '%s'@'%s' (ВикориÑтано пароль: %s)" ER_NO_DB_ERROR 3D000 - cze "Nebyla vybr-Bána žádná databáze" + cze "Nebyla vybrána žádná databáze" dan "Ingen database valgt" nla "Geen database geselecteerd" eng "No database selected" - jps "データベースãŒé¸æŠžã•れã¦ã„ã¾ã›ã‚“.", est "Andmebaasi ei ole valitud" fre "Aucune base n'a été sélectionnée" ger "Keine Datenbank ausgewählt" greek "Δεν επιλÎχθηκε βάση δεδομÎνων" hun "Nincs kivalasztott adatbazis" ita "Nessun database selezionato" - jpn "データベースãŒé¸æŠžã•れã¦ã„ã¾ã›ã‚“." + jpn "データベースãŒé¸æŠžã•れã¦ã„ã¾ã›ã‚“。" kor "ì„ íƒëœ ë°ì´íƒ€ë² ì´ìŠ¤ê°€ 없습니다." nor "Ingen database valgt" norwegian-ny "Ingen database vald" @@ -1114,18 +1056,17 @@ ER_NO_DB_ERROR 3D000 swe "Ingen databas i användning" ukr "Базу данних не вибрано" ER_UNKNOWN_COM_ERROR 08S01 - cze "Nezn-Bámý pÅ™Ãkaz" + cze "Neznámý pÅ™Ãkaz" dan "Ukendt kommando" nla "Onbekend commando" eng "Unknown command" - jps "ãã®ã‚³ãƒžãƒ³ãƒ‰ã¯ä½•?", est "Tundmatu käsk" fre "Commande inconnue" ger "Unbekannter Befehl" greek "Αγνωστη εντολή" hun "Ervenytelen parancs" ita "Comando sconosciuto" - jpn "ãã®ã‚³ãƒžãƒ³ãƒ‰ã¯ä½•?" + jpn "䏿˜Žãªã‚³ãƒžãƒ³ãƒ‰ã§ã™ã€‚" kor "ëª…ë ¹ì–´ê°€ ë”ì§€ ëª¨ë¥´ê² ì–´ìš”..." nor "Ukjent kommando" norwegian-ny "Ukjent kommando" @@ -1136,21 +1077,20 @@ ER_UNKNOWN_COM_ERROR 08S01 serbian "Nepoznata komanda" slo "Neznámy prÃkaz" spa "Comando desconocido" - swe "Okänt commando" + swe "Okänt kommando" ukr "Ðевідома команда" ER_BAD_NULL_ERROR 23000 - cze "Sloupec '%-.192s' nem-Bůže být null" + cze "Sloupec '%-.192s' nemůže být null" dan "Kolonne '%-.192s' kan ikke være NULL" nla "Kolom '%-.192s' kan niet null zijn" eng "Column '%-.192s' cannot be null" - jps "Column '%-.192s' 㯠null ã«ã¯ã§ããªã„ã®ã§ã™", est "Tulp '%-.192s' ei saa omada nullväärtust" fre "Le champ '%-.192s' ne peut être vide (null)" ger "Feld '%-.192s' darf nicht NULL sein" greek "Το πεδίο '%-.192s' δεν μποÏεί να είναι κενό (null)" hun "A(z) '%-.192s' oszlop erteke nem lehet nulla" ita "La colonna '%-.192s' non puo` essere nulla" - jpn "Column '%-.192s' 㯠null ã«ã¯ã§ããªã„ã®ã§ã™" + jpn "列 '%-.192s' 㯠null ã«ã§ãã¾ã›ã‚“。" kor "칼럼 '%-.192s'는 ë„(Null)ì´ ë˜ë©´ 안ë©ë‹ˆë‹¤. " nor "Kolonne '%-.192s' kan ikke vere null" norwegian-ny "Kolonne '%-.192s' kan ikkje vere null" @@ -1164,18 +1104,17 @@ ER_BAD_NULL_ERROR 23000 swe "Kolumn '%-.192s' fÃ¥r inte vara NULL" ukr "Стовбець '%-.192s' не може бути нульовим" ER_BAD_DB_ERROR 42000 - cze "Nezn-Bámá databáze '%-.192s'" + cze "Neznámá databáze '%-.192s'" dan "Ukendt database '%-.192s'" nla "Onbekende database '%-.192s'" eng "Unknown database '%-.192s'" - jps "'%-.192s' ãªã‚“ã¦ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã¯çŸ¥ã‚Šã¾ã›ã‚“.", est "Tundmatu andmebaas '%-.192s'" fre "Base '%-.192s' inconnue" ger "Unbekannte Datenbank '%-.192s'" greek "Αγνωστη βάση δεδομÎνων '%-.192s'" hun "Ervenytelen adatbazis: '%-.192s'" ita "Database '%-.192s' sconosciuto" - jpn "'%-.192s' ãªã‚“ã¦ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã¯çŸ¥ã‚Šã¾ã›ã‚“." + jpn "'%-.192s' ã¯ä¸æ˜Žãªãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã§ã™ã€‚" kor "ë°ì´íƒ€ë² ì´ìФ '%-.192s'는 알수 ì—†ìŒ" nor "Ukjent database '%-.192s'" norwegian-ny "Ukjent database '%-.192s'" @@ -1189,18 +1128,17 @@ ER_BAD_DB_ERROR 42000 swe "Okänd databas: '%-.192s'" ukr "Ðевідома база данних '%-.192s'" ER_TABLE_EXISTS_ERROR 42S01 - cze "Tabulka '%-.192s' ji-Bž existuje" + cze "Tabulka '%-.192s' již existuje" dan "Tabellen '%-.192s' findes allerede" nla "Tabel '%-.192s' bestaat al" eng "Table '%-.192s' already exists" - jps "Table '%-.192s' ã¯æ—¢ã«ã‚りã¾ã™", est "Tabel '%-.192s' juba eksisteerib" fre "La table '%-.192s' existe déjà " ger "Tabelle '%-.192s' bereits vorhanden" greek "Ο πίνακας '%-.192s' υπάÏχει ήδη" hun "A(z) '%-.192s' tabla mar letezik" ita "La tabella '%-.192s' esiste gia`" - jpn "Table '%-.192s' ã¯æ—¢ã«ã‚りã¾ã™" + jpn "表 '%-.192s' ã¯ã™ã§ã«å˜åœ¨ã—ã¾ã™ã€‚" kor "í…Œì´ë¸” '%-.192s'는 ì´ë¯¸ 존재함" nor "Tabellen '%-.192s' eksisterer allerede" norwegian-ny "Tabellen '%-.192s' eksisterar allereide" @@ -1214,18 +1152,17 @@ ER_TABLE_EXISTS_ERROR 42S01 swe "Tabellen '%-.192s' finns redan" ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' вже Ñ–Ñнує" ER_BAD_TABLE_ERROR 42S02 - cze "Nezn-Bámá tabulka '%-.100s'" + cze "Neznámá tabulka '%-.100s'" dan "Ukendt tabel '%-.100s'" nla "Onbekende tabel '%-.100s'" eng "Unknown table '%-.100s'" - jps "table '%-.100s' ã¯ã‚りã¾ã›ã‚“.", est "Tundmatu tabel '%-.100s'" fre "Table '%-.100s' inconnue" ger "Unbekannte Tabelle '%-.100s'" greek "Αγνωστος πίνακας '%-.100s'" hun "Ervenytelen tabla: '%-.100s'" ita "Tabella '%-.100s' sconosciuta" - jpn "table '%-.100s' ã¯ã‚りã¾ã›ã‚“." + jpn "'%-.100s' ã¯ä¸æ˜Žãªè¡¨ã§ã™ã€‚" kor "í…Œì´ë¸” '%-.100s'는 알수 ì—†ìŒ" nor "Ukjent tabell '%-.100s'" norwegian-ny "Ukjent tabell '%-.100s'" @@ -1239,7 +1176,7 @@ ER_BAD_TABLE_ERROR 42S02 swe "Okänd tabell '%-.100s'" ukr "Ðевідома Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.100s'" ER_NON_UNIQ_ERROR 23000 - cze "Sloupec '%-.192s' v %-.192s nen-Bà zcela jasný" + cze "Sloupec '%-.192s' v %-.192s nenà zcela jasný" dan "Felt: '%-.192s' i tabel %-.192s er ikke entydigt" nla "Kolom: '%-.192s' in %-.192s is niet eenduidig" eng "Column '%-.192s' in %-.192s is ambiguous" @@ -1249,7 +1186,7 @@ ER_NON_UNIQ_ERROR 23000 greek "Το πεδίο: '%-.192s' σε %-.192s δεν Îχει καθοÏιστεί" hun "A(z) '%-.192s' oszlop %-.192s-ben ketertelmu" ita "Colonna: '%-.192s' di %-.192s e` ambigua" - jpn "Column: '%-.192s' in %-.192s is ambiguous" + jpn "列 '%-.192s' 㯠%-.192s å†…ã§æ›–昧ã§ã™ã€‚" kor "칼럼: '%-.192s' in '%-.192s' ì´ ëª¨í˜¸í•¨" nor "Felt: '%-.192s' i tabell %-.192s er ikke entydig" norwegian-ny "Kolonne: '%-.192s' i tabell %-.192s er ikkje eintydig" @@ -1263,18 +1200,17 @@ ER_NON_UNIQ_ERROR 23000 swe "Kolumn '%-.192s' i %-.192s är inte unik" ukr "Стовбець '%-.192s' у %-.192s визначений неоднозначно" ER_SERVER_SHUTDOWN 08S01 - cze "Prob-BÃhá ukonÄovánà práce serveru" + cze "ProbÃhá ukonÄovánà práce serveru" dan "Database nedlukning er i gang" nla "Bezig met het stoppen van de server" eng "Server shutdown in progress" - jps "Server ã‚’ shutdown ä¸...", est "Serveri seiskamine käib" fre "Arrêt du serveur en cours" ger "Der Server wird heruntergefahren" greek "ΕναÏξη διαδικασίας αποσÏνδεσης του εξυπηÏετητή (server shutdown)" hun "A szerver leallitasa folyamatban" ita "Shutdown del server in corso" - jpn "Server ã‚’ shutdown ä¸..." + jpn "サーãƒãƒ¼ã‚’シャットダウンä¸ã§ã™ã€‚" kor "Serverê°€ 셧다운 중입니다." nor "Database nedkobling er i gang" norwegian-ny "Tenar nedkopling er i gang" @@ -1288,18 +1224,17 @@ ER_SERVER_SHUTDOWN 08S01 swe "Servern gÃ¥r nu ned" ukr "ЗавершуєтьÑÑ Ñ€Ð°Ð±Ð¾Ñ‚Ð° Ñервера" ER_BAD_FIELD_ERROR 42S22 S0022 - cze "Nezn-Bámý sloupec '%-.192s' v %-.192s" + cze "Neznámý sloupec '%-.192s' v %-.192s" dan "Ukendt kolonne '%-.192s' i tabel %-.192s" nla "Onbekende kolom '%-.192s' in %-.192s" eng "Unknown column '%-.192s' in '%-.192s'" - jps "'%-.192s' column 㯠'%-.192s' ã«ã¯ã‚りã¾ã›ã‚“.", est "Tundmatu tulp '%-.192s' '%-.192s'-s" fre "Champ '%-.192s' inconnu dans %-.192s" ger "Unbekanntes Tabellenfeld '%-.192s' in %-.192s" greek "Αγνωστο πεδίο '%-.192s' σε '%-.192s'" hun "A(z) '%-.192s' oszlop ervenytelen '%-.192s'-ben" ita "Colonna sconosciuta '%-.192s' in '%-.192s'" - jpn "'%-.192s' column 㯠'%-.192s' ã«ã¯ã‚りã¾ã›ã‚“." + jpn "列 '%-.192s' 㯠'%-.192s' ã«ã¯ã‚りã¾ã›ã‚“。" kor "Unknown 칼럼 '%-.192s' in '%-.192s'" nor "Ukjent kolonne '%-.192s' i tabell %-.192s" norwegian-ny "Ukjent felt '%-.192s' i tabell %-.192s" @@ -1313,17 +1248,17 @@ ER_BAD_FIELD_ERROR 42S22 S0022 swe "Okänd kolumn '%-.192s' i %-.192s" ukr "Ðевідомий Ñтовбець '%-.192s' у '%-.192s'" ER_WRONG_FIELD_WITH_GROUP 42000 S1009 - cze "Pou-Bžité '%-.192s' nebylo v group by" + cze "Použité '%-.192s' nebylo v group by" dan "Brugte '%-.192s' som ikke var i group by" nla "Opdracht gebruikt '%-.192s' dat niet in de GROUP BY voorkomt" eng "'%-.192s' isn't in GROUP BY" - jps "'%-.192s' isn't in GROUP BY", est "'%-.192s' puudub GROUP BY klauslis" fre "'%-.192s' n'est pas dans 'group by'" ger "'%-.192s' ist nicht in GROUP BY vorhanden" greek "ΧÏησιμοποιήθηκε '%-.192s' που δεν υπήÏχε στο group by" hun "Used '%-.192s' with wasn't in group by" ita "Usato '%-.192s' che non e` nel GROUP BY" + jpn "'%-.192s' ã¯GROUP BYå¥ã§æŒ‡å®šã•れã¦ã„ã¾ã›ã‚“。" kor "'%-.192s'ì€ GROUP BYì†ì— ì—†ìŒ" nor "Brukte '%-.192s' som ikke var i group by" norwegian-ny "Brukte '%-.192s' som ikkje var i group by" @@ -1337,7 +1272,7 @@ ER_WRONG_FIELD_WITH_GROUP 42000 S1009 swe "'%-.192s' finns inte i GROUP BY" ukr "'%-.192s' не Ñ” у GROUP BY" ER_WRONG_GROUP_FIELD 42000 S1009 - cze "Nemohu pou-BžÃt group na '%-.192s'" + cze "Nemohu použÃt group na '%-.192s'" dan "Kan ikke gruppere pÃ¥ '%-.192s'" nla "Kan '%-.192s' niet groeperen" eng "Can't group on '%-.192s'" @@ -1347,6 +1282,7 @@ ER_WRONG_GROUP_FIELD 42000 S1009 greek "ΑδÏνατη η ομαδοποίηση (group on) '%-.192s'" hun "A group nem hasznalhato: '%-.192s'" ita "Impossibile raggruppare per '%-.192s'" + jpn "'%-.192s' ã§ã®ã‚°ãƒ«ãƒ¼ãƒ—化ã¯ã§ãã¾ã›ã‚“。" kor "'%-.192s'를 ê·¸ë£¹í• ìˆ˜ ì—†ìŒ" nor "Kan ikke gruppere pÃ¥ '%-.192s'" norwegian-ny "Kan ikkje gruppere pÃ¥ '%-.192s'" @@ -1360,7 +1296,7 @@ ER_WRONG_GROUP_FIELD 42000 S1009 swe "Kan inte använda GROUP BY med '%-.192s'" ukr "Ðе можу групувати по '%-.192s'" ER_WRONG_SUM_SELECT 42000 S1009 - cze "P-BÅ™Ãkaz obsahuje zároveň funkci sum a sloupce" + cze "PÅ™Ãkaz obsahuje zároveň funkci sum a sloupce" dan "Udtrykket har summer (sum) funktioner og kolonner i samme udtryk" nla "Opdracht heeft totaliseer functies en kolommen in dezelfde opdracht" eng "Statement has sum functions and columns in same statement" @@ -1369,6 +1305,7 @@ ER_WRONG_SUM_SELECT 42000 S1009 ger "Die Verwendung von Summierungsfunktionen und Spalten im selben Befehl ist nicht erlaubt" greek "Η διατÏπωση πεÏιÎχει sum functions και columns στην ίδια διατÏπωση" ita "Il comando ha una funzione SUM e una colonna non specificata nella GROUP BY" + jpn "集計関数ã¨é€šå¸¸ã®åˆ—ãŒåŒæ™‚ã«æŒ‡å®šã•れã¦ã„ã¾ã™ã€‚" kor "Statement ê°€ sumê¸°ëŠ¥ì„ ë™ìž‘중ì´ê³ ì¹¼ëŸ¼ë„ ë™ì¼í•œ statement입니다." nor "Uttrykket har summer (sum) funksjoner og kolonner i samme uttrykk" norwegian-ny "Uttrykket har summer (sum) funksjoner og kolonner i same uttrykk" @@ -1382,7 +1319,7 @@ ER_WRONG_SUM_SELECT 42000 S1009 swe "Kommandot har bÃ¥de sum functions och enkla funktioner" ukr "У виразі викориÑтано підÑумовуючі функції порÑд з іменами Ñтовбців" ER_WRONG_VALUE_COUNT 21S01 - cze "Po-BÄet sloupců neodpovÃdá zadané hodnotÄ›" + cze "PoÄet sloupců neodpovÃdá zadané hodnotÄ›" dan "Kolonne tæller stemmer ikke med antallet af værdier" nla "Het aantal kolommen komt niet overeen met het aantal opgegeven waardes" eng "Column count doesn't match value count" @@ -1391,6 +1328,7 @@ ER_WRONG_VALUE_COUNT 21S01 greek "Το Column count δεν ταιÏιάζει με το value count" hun "Az oszlopban levo ertek nem egyezik meg a szamitott ertekkel" ita "Il numero delle colonne non e` uguale al numero dei valori" + jpn "列数ãŒå€¤ã®å€‹æ•°ã¨ä¸€è‡´ã—ã¾ã›ã‚“。" kor "ì¹¼ëŸ¼ì˜ ì¹´ìš´íŠ¸ê°€ ê°’ì˜ ì¹´ìš´íŠ¸ì™€ ì¼ì¹˜í•˜ì§€ 않습니다." nor "Felt telling stemmer verdi telling" norwegian-ny "Kolonne telling stemmer verdi telling" @@ -1404,18 +1342,17 @@ ER_WRONG_VALUE_COUNT 21S01 swe "Antalet kolumner motsvarar inte antalet värden" ukr "КількіÑть Ñтовбців не Ñпівпадає з кількіÑтю значень" ER_TOO_LONG_IDENT 42000 S1009 - cze "Jm-Béno identifikátoru '%-.100s' je pÅ™ÃliÅ¡ dlouhé" + cze "Jméno identifikátoru '%-.100s' je pÅ™ÃliÅ¡ dlouhé" dan "Navnet '%-.100s' er for langt" nla "Naam voor herkenning '%-.100s' is te lang" eng "Identifier name '%-.100s' is too long" - jps "Identifier name '%-.100s' ã¯é•·ã™ãŽã¾ã™", est "Identifikaatori '%-.100s' nimi on liiga pikk" fre "Le nom de l'identificateur '%-.100s' est trop long" ger "Name des Bezeichners '%-.100s' ist zu lang" greek "Το identifier name '%-.100s' είναι Ï€Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿" hun "A(z) '%-.100s' azonositonev tul hosszu." ita "Il nome dell'identificatore '%-.100s' e` troppo lungo" - jpn "Identifier name '%-.100s' ã¯é•·ã™ãŽã¾ã™" + jpn "è˜åˆ¥åå '%-.100s' ã¯é•·ã™ãŽã¾ã™ã€‚" kor "Identifier '%-.100s'는 너무 길군요." nor "Identifikator '%-.100s' er for lang" norwegian-ny "Identifikator '%-.100s' er for lang" @@ -1429,18 +1366,17 @@ ER_TOO_LONG_IDENT 42000 S1009 swe "Kolumnnamn '%-.100s' är för lÃ¥ngt" ukr "Ім'Ñ Ñ–Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ‚Ð¾Ñ€Ð° '%-.100s' задовге" ER_DUP_FIELDNAME 42S21 S1009 - cze "Zdvojen-Bé jméno sloupce '%-.192s'" + cze "Zdvojené jméno sloupce '%-.192s'" dan "Feltnavnet '%-.192s' findes allerede" nla "Dubbele kolom naam '%-.192s'" eng "Duplicate column name '%-.192s'" - jps "'%-.192s' ã¨ã„ㆠcolumn åã¯é‡è¤‡ã—ã¦ã¾ã™", est "Kattuv tulba nimi '%-.192s'" fre "Nom du champ '%-.192s' déjà utilisé" ger "Doppelter Spaltenname: '%-.192s'" greek "Επανάληψη column name '%-.192s'" hun "Duplikalt oszlopazonosito: '%-.192s'" ita "Nome colonna duplicato '%-.192s'" - jpn "'%-.192s' ã¨ã„ㆠcolumn åã¯é‡è¤‡ã—ã¦ã¾ã™" + jpn "列å '%-.192s' ã¯é‡è¤‡ã—ã¦ã¾ã™ã€‚" kor "ì¤‘ë³µëœ ì¹¼ëŸ¼ ì´ë¦„: '%-.192s'" nor "Feltnavnet '%-.192s' eksisterte fra før" norwegian-ny "Feltnamnet '%-.192s' eksisterte frÃ¥ før" @@ -1454,18 +1390,17 @@ ER_DUP_FIELDNAME 42S21 S1009 swe "Kolumnnamn '%-.192s finns flera gÃ¥nger" ukr "Дублююче ім'Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.192s'" ER_DUP_KEYNAME 42000 S1009 - cze "Zdvojen-Bé jméno klÃÄe '%-.192s'" + cze "Zdvojené jméno klÃÄe '%-.192s'" dan "Indeksnavnet '%-.192s' findes allerede" nla "Dubbele zoeksleutel naam '%-.192s'" eng "Duplicate key name '%-.192s'" - jps "'%-.192s' ã¨ã„ㆠkey ã®åå‰ã¯é‡è¤‡ã—ã¦ã„ã¾ã™", est "Kattuv võtme nimi '%-.192s'" fre "Nom de clef '%-.192s' déjà utilisé" ger "Doppelter Name für Schlüssel vorhanden: '%-.192s'" greek "Επανάληψη key name '%-.192s'" hun "Duplikalt kulcsazonosito: '%-.192s'" ita "Nome chiave duplicato '%-.192s'" - jpn "'%-.192s' ã¨ã„ㆠkey ã®åå‰ã¯é‡è¤‡ã—ã¦ã„ã¾ã™" + jpn "索引å '%-.192s' ã¯é‡è¤‡ã—ã¦ã„ã¾ã™ã€‚" kor "ì¤‘ë³µëœ í‚¤ ì´ë¦„ : '%-.192s'" nor "Nøkkelnavnet '%-.192s' eksisterte fra før" norwegian-ny "Nøkkelnamnet '%-.192s' eksisterte frÃ¥ før" @@ -1481,32 +1416,31 @@ ER_DUP_KEYNAME 42000 S1009 # When using this error code, please use ER(ER_DUP_ENTRY_WITH_KEY_NAME) # for the message string. See, for example, code in handler.cc. ER_DUP_ENTRY 23000 S1009 - cze "Zdvojen-Bý klÃÄ '%-.192s' (ÄÃslo klÃÄe %d)" + cze "Zdvojený klÃÄ '%-.192s' (ÄÃslo klÃÄe %d)" dan "Ens værdier '%-.192s' for indeks %d" nla "Dubbele ingang '%-.192s' voor zoeksleutel %d" eng "Duplicate entry '%-.192s' for key %d" - jps "'%-.192s' 㯠key %d ã«ãŠã„ã¦é‡è¤‡ã—ã¦ã„ã¾ã™", est "Kattuv väärtus '%-.192s' võtmele %d" fre "Duplicata du champ '%-.192s' pour la clef %d" ger "Doppelter Eintrag '%-.192s' für Schlüssel %d" greek "Διπλή εγγÏαφή '%-.192s' για το κλειδί %d" hun "Duplikalt bejegyzes '%-.192s' a %d kulcs szerint." ita "Valore duplicato '%-.192s' per la chiave %d" - jpn "'%-.192s' 㯠key %d ã«ãŠã„ã¦é‡è¤‡ã—ã¦ã„ã¾ã™" + jpn "'%-.192s' ã¯ç´¢å¼• %d ã§é‡è¤‡ã—ã¦ã„ã¾ã™ã€‚" kor "ì¤‘ë³µëœ ìž…ë ¥ ê°’ '%-.192s': key %d" nor "Like verdier '%-.192s' for nøkkel %d" norwegian-ny "Like verdiar '%-.192s' for nykkel %d" - pol "Powtórzone wyst?pienie '%-.192s' dla klucza %d" + pol "Powtórzone wystÄ…pienie '%-.192s' dla klucza %d" por "Entrada '%-.192s' duplicada para a chave %d" rum "Cimpul '%-.192s' e duplicat pentru cheia %d" rus "ДублирующаÑÑÑ Ð·Ð°Ð¿Ð¸ÑÑŒ '%-.192s' по ключу %d" serbian "Dupliran unos '%-.192s' za kljuÄ '%d'" slo "Opakovaný kÄ¾ÃºÄ '%-.192s' (ÄÃslo kľúÄa %d)" spa "Entrada duplicada '%-.192s' para la clave %d" - swe "Dubbel nyckel '%-.192s' för nyckel %d" + swe "Dublett '%-.192s' för nyckel %d" ukr "Дублюючий Ð·Ð°Ð¿Ð¸Ñ '%-.192s' Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð° %d" ER_WRONG_FIELD_SPEC 42000 S1009 - cze "Chybn-Bá specifikace sloupce '%-.192s'" + cze "Chybná specifikace sloupce '%-.192s'" dan "Forkert kolonnespecifikaton for felt '%-.192s'" nla "Verkeerde kolom specificatie voor kolom '%-.192s'" eng "Incorrect column specifier for column '%-.192s'" @@ -1516,6 +1450,7 @@ ER_WRONG_FIELD_SPEC 42000 S1009 greek "ΕσφαλμÎνο column specifier για το πεδίο '%-.192s'" hun "Rossz oszlopazonosito: '%-.192s'" ita "Specifica errata per la colonna '%-.192s'" + jpn "列 '%-.192s' ã®å®šç¾©ãŒä¸æ£ã§ã™ã€‚" kor "칼럼 '%-.192s'ì˜ ë¶€ì •í™•í•œ 칼럼 ì •ì˜ìž" nor "Feil kolonne spesifikator for felt '%-.192s'" norwegian-ny "Feil kolonne spesifikator for kolonne '%-.192s'" @@ -1529,18 +1464,17 @@ ER_WRONG_FIELD_SPEC 42000 S1009 swe "Felaktigt kolumntyp för kolumn '%-.192s'" ukr "Ðевірний Ñпецифікатор ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.192s'" ER_PARSE_ERROR 42000 s1009 - cze "%s bl-BÃzko '%-.80s' na řádku %d" + cze "%s blÃzko '%-.80s' na řádku %d" dan "%s nær '%-.80s' pÃ¥ linje %d" nla "%s bij '%-.80s' in regel %d" eng "%s near '%-.80s' at line %d" - jps "%s : '%-.80s' 付近 : %d 行目", est "%s '%-.80s' ligidal real %d" fre "%s près de '%-.80s' à la ligne %d" ger "%s bei '%-.80s' in Zeile %d" greek "%s πλησίον '%-.80s' στη γÏαμμή %d" hun "A %s a '%-.80s'-hez kozeli a %d sorban" ita "%s vicino a '%-.80s' linea %d" - jpn "%s : '%-.80s' 付近 : %d 行目" + jpn "%s : '%-.80s' 付近 %d 行目" kor "'%s' ì—러 ê°™ì니다. ('%-.80s' ëª…ë ¹ì–´ ë¼ì¸ %d)" nor "%s nær '%-.80s' pÃ¥ linje %d" norwegian-ny "%s attmed '%-.80s' pÃ¥ line %d" @@ -1554,18 +1488,17 @@ ER_PARSE_ERROR 42000 s1009 swe "%s nära '%-.80s' pÃ¥ rad %d" ukr "%s Ð±Ñ–Ð»Ñ '%-.80s' в Ñтроці %d" ER_EMPTY_QUERY 42000 - cze "V-Býsledek dotazu je prázdný" + cze "Výsledek dotazu je prázdný" dan "Forespørgsel var tom" nla "Query was leeg" eng "Query was empty" - jps "Query ãŒç©ºã§ã™.", est "Tühi päring" fre "Query est vide" ger "Leere Abfrage" greek "Το εÏώτημα (query) που θÎσατε ήταν κενό" hun "Ures lekerdezes." ita "La query e` vuota" - jpn "Query ãŒç©ºã§ã™." + jpn "クエリãŒç©ºã§ã™ã€‚" kor "쿼리결과가 없습니다." nor "Forespørsel var tom" norwegian-ny "Førespurnad var tom" @@ -1579,18 +1512,17 @@ ER_EMPTY_QUERY 42000 swe "FrÃ¥gan var tom" ukr "ПуÑтий запит" ER_NONUNIQ_TABLE 42000 S1009 - cze "Nejednozna-BÄná tabulka/alias: '%-.192s'" + cze "NejednoznaÄná tabulka/alias: '%-.192s'" dan "Tabellen/aliaset: '%-.192s' er ikke unikt" nla "Niet unieke waarde tabel/alias: '%-.192s'" eng "Not unique table/alias: '%-.192s'" - jps "'%-.192s' ã¯ä¸€æ„ã® table/alias åã§ã¯ã‚りã¾ã›ã‚“", est "Ei ole unikaalne tabel/alias '%-.192s'" fre "Table/alias: '%-.192s' non unique" ger "Tabellenname/Alias '%-.192s' nicht eindeutig" greek "ΑδÏνατη η ανεÏÏεση unique table/alias: '%-.192s'" hun "Nem egyedi tabla/alias: '%-.192s'" ita "Tabella/alias non unico: '%-.192s'" - jpn "'%-.192s' ã¯ä¸€æ„ã® table/alias åã§ã¯ã‚りã¾ã›ã‚“" + jpn "表åï¼åˆ¥å '%-.192s' ã¯ä¸€æ„ã§ã¯ã‚りã¾ã›ã‚“。" kor "Unique 하지 ì•Šì€ í…Œì´ë¸”/alias: '%-.192s'" nor "Ikke unikt tabell/alias: '%-.192s'" norwegian-ny "Ikkje unikt tabell/alias: '%-.192s'" @@ -1604,7 +1536,7 @@ ER_NONUNIQ_TABLE 42000 S1009 swe "Icke unikt tabell/alias: '%-.192s'" ukr "Ðеунікальна таблицÑ/пÑевдонім: '%-.192s'" ER_INVALID_DEFAULT 42000 S1009 - cze "Chybn-Bá defaultnà hodnota pro '%-.192s'" + cze "Chybná defaultnà hodnota pro '%-.192s'" dan "Ugyldig standardværdi for '%-.192s'" nla "Foutieve standaard waarde voor '%-.192s'" eng "Invalid default value for '%-.192s'" @@ -1614,6 +1546,7 @@ ER_INVALID_DEFAULT 42000 S1009 greek "ΕσφαλμÎνη Ï€ÏοκαθοÏισμÎνη τιμή (default value) για '%-.192s'" hun "Ervenytelen ertek: '%-.192s'" ita "Valore di default non valido per '%-.192s'" + jpn "'%-.192s' ã¸ã®ãƒ‡ãƒ•ォルト値ãŒç„¡åйã§ã™ã€‚" kor "'%-.192s'ì˜ ìœ íš¨í•˜ì§€ 못한 ë””í´íЏ ê°’ì„ ì‚¬ìš©í•˜ì…¨ìŠµë‹ˆë‹¤." nor "Ugyldig standardverdi for '%-.192s'" norwegian-ny "Ugyldig standardverdi for '%-.192s'" @@ -1627,18 +1560,17 @@ ER_INVALID_DEFAULT 42000 S1009 swe "Ogiltigt DEFAULT värde för '%-.192s'" ukr "Ðевірне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾ замовчуванню Ð´Ð»Ñ '%-.192s'" ER_MULTIPLE_PRI_KEY 42000 S1009 - cze "Definov-Báno vÃce primárnÃch klÃÄů" + cze "Definováno vÃce primárnÃch klÃÄů" dan "Flere primærnøgler specificeret" nla "Meerdere primaire zoeksleutels gedefinieerd" eng "Multiple primary key defined" - jps "複数㮠primary key ãŒå®šç¾©ã•れã¾ã—ãŸ", est "Mitut primaarset võtit ei saa olla" fre "Plusieurs clefs primaires définies" ger "Mehrere Primärschlüssel (PRIMARY KEY) definiert" greek "ΠεÏισσότεÏα από Îνα primary key οÏίστηκαν" hun "Tobbszoros elsodleges kulcs definialas." ita "Definite piu` chiave primarie" - jpn "複数㮠primary key ãŒå®šç¾©ã•れã¾ã—ãŸ" + jpn "PRIMARY KEY ãŒè¤‡æ•°å®šç¾©ã•れã¦ã„ã¾ã™ã€‚" kor "Multiple primary keyê°€ ì •ì˜ë˜ì–´ 있슴" nor "Fleire primærnøkle spesifisert" norwegian-ny "Fleire primærnyklar spesifisert" @@ -1652,18 +1584,17 @@ ER_MULTIPLE_PRI_KEY 42000 S1009 swe "Flera PRIMARY KEY använda" ukr "Первинного ключа визначено неодноразово" ER_TOO_MANY_KEYS 42000 S1009 - cze "Zad-Báno pÅ™ÃliÅ¡ mnoho klÃÄů, je povoleno nejvÃce %d klÃÄů" + cze "Zadáno pÅ™ÃliÅ¡ mnoho klÃÄů, je povoleno nejvÃce %d klÃÄů" dan "For mange nøgler specificeret. Kun %d nøgler mÃ¥ bruges" nla "Teveel zoeksleutels gedefinieerd. Maximaal zijn %d zoeksleutels toegestaan" eng "Too many keys specified; max %d keys allowed" - jps "key ã®æŒ‡å®šãŒå¤šã™ãŽã¾ã™. key ã¯æœ€å¤§ %d ã¾ã§ã§ã™", est "Liiga palju võtmeid. Maksimaalselt võib olla %d võtit" fre "Trop de clefs sont définies. Maximum de %d clefs alloué" ger "Zu viele Schlüssel definiert. Maximal %d Schlüssel erlaubt" greek "ΠάÏα πολλά key οÏίσθηκαν. Το Ï€Î¿Î»Ï %d επιτÏÎπονται" hun "Tul sok kulcs. Maximum %d kulcs engedelyezett." ita "Troppe chiavi. Sono ammesse max %d chiavi" - jpn "key ã®æŒ‡å®šãŒå¤šã™ãŽã¾ã™. key ã¯æœ€å¤§ %d ã¾ã§ã§ã™" + jpn "ç´¢å¼•ã®æ•°ãŒå¤šã™ãŽã¾ã™ã€‚最大 %d 個ã¾ã§ã§ã™ã€‚" kor "너무 ë§Žì€ í‚¤ê°€ ì •ì˜ë˜ì–´ 있ì니다.. 최대 %dì˜ í‚¤ê°€ 가능함" nor "For mange nøkler spesifisert. Maks %d nøkler tillatt" norwegian-ny "For mange nykler spesifisert. Maks %d nyklar tillatt" @@ -1677,7 +1608,7 @@ ER_TOO_MANY_KEYS 42000 S1009 swe "För mÃ¥nga nycklar använda. Man fÃ¥r ha högst %d nycklar" ukr "Забагато ключів зазначено. Дозволено не більше %d ключів" ER_TOO_MANY_KEY_PARTS 42000 S1009 - cze "Zad-Báno pÅ™ÃliÅ¡ mnoho Äást klÃÄů, je povoleno nejvÃce %d ÄástÃ" + cze "Zadáno pÅ™ÃliÅ¡ mnoho Äást klÃÄů, je povoleno nejvÃce %d ÄástÃ" dan "For mange nøgledele specificeret. Kun %d dele mÃ¥ bruges" nla "Teveel zoeksleutel onderdelen gespecificeerd. Maximaal %d onderdelen toegestaan" eng "Too many key parts specified; max %d parts allowed" @@ -1687,6 +1618,7 @@ ER_TOO_MANY_KEY_PARTS 42000 S1009 greek "ΠάÏα πολλά key parts οÏίσθηκαν. Το Ï€Î¿Î»Ï %d επιτÏÎπονται" hun "Tul sok kulcsdarabot definialt. Maximum %d resz engedelyezett" ita "Troppe parti di chiave specificate. Sono ammesse max %d parti" + jpn "索引ã®ã‚ー列指定ãŒå¤šã™ãŽã¾ã™ã€‚最大 %d 個ã¾ã§ã§ã™ã€‚" kor "너무 ë§Žì€ í‚¤ 부분(parts)ë“¤ì´ ì •ì˜ë˜ì–´ 있ì니다.. 최대 %d ë¶€ë¶„ì´ ê°€ëŠ¥í•¨" nor "For mange nøkkeldeler spesifisert. Maks %d deler tillatt" norwegian-ny "For mange nykkeldelar spesifisert. Maks %d delar tillatt" @@ -1700,18 +1632,17 @@ ER_TOO_MANY_KEY_PARTS 42000 S1009 swe "För mÃ¥nga nyckeldelar använda. Man fÃ¥r ha högst %d nyckeldelar" ukr "Забагато чаÑтин ключа зазначено. Дозволено не більше %d чаÑтин" ER_TOO_LONG_KEY 42000 S1009 - cze "Zadan-Bý klÃÄ byl pÅ™ÃliÅ¡ dlouhý, nejvÄ›tšà délka klÃÄe je %d" + cze "Zadaný klÃÄ byl pÅ™ÃliÅ¡ dlouhý, nejvÄ›tšà délka klÃÄe je %d" dan "Specificeret nøgle var for lang. Maksimal nøglelængde er %d" nla "Gespecificeerde zoeksleutel was te lang. De maximale lengte is %d" eng "Specified key was too long; max key length is %d bytes" - jps "key ãŒé•·ã™ãŽã¾ã™. key ã®é•·ã•ã¯æœ€å¤§ %d ã§ã™", est "Võti on liiga pikk. Maksimaalne võtmepikkus on %d" fre "La clé est trop longue. Longueur maximale: %d" ger "Schlüssel ist zu lang. Die maximale Schlüssellänge beträgt %d" greek "Το κλειδί που οÏίσθηκε είναι Ï€Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿. Το μÎγιστο μήκος είναι %d" hun "A megadott kulcs tul hosszu. Maximalis kulcshosszusag: %d" ita "La chiave specificata e` troppo lunga. La max lunghezza della chiave e` %d" - jpn "key ãŒé•·ã™ãŽã¾ã™. key ã®é•·ã•ã¯æœ€å¤§ %d ã§ã™" + jpn "索引ã®ã‚ーãŒé•·ã™ãŽã¾ã™ã€‚最大 %d ãƒã‚¤ãƒˆã¾ã§ã§ã™ã€‚" kor "ì •ì˜ëœ 키가 너무 ê¹ë‹ˆë‹¤. 최대 í‚¤ì˜ ê¸¸ì´ëŠ” %d입니다." nor "Spesifisert nøkkel var for lang. Maks nøkkellengde er is %d" norwegian-ny "Spesifisert nykkel var for lang. Maks nykkellengde er %d" @@ -1725,18 +1656,17 @@ ER_TOO_LONG_KEY 42000 S1009 swe "För lÃ¥ng nyckel. Högsta tillÃ¥tna nyckellängd är %d" ukr "Зазначений ключ задовгий. Ðайбільша довжина ключа %d байтів" ER_KEY_COLUMN_DOES_NOT_EXITS 42000 S1009 - cze "Kl-BÃÄový sloupec '%-.192s' v tabulce neexistuje" + cze "KlÃÄový sloupec '%-.192s' v tabulce neexistuje" dan "Nøglefeltet '%-.192s' eksisterer ikke i tabellen" nla "Zoeksleutel kolom '%-.192s' bestaat niet in tabel" eng "Key column '%-.192s' doesn't exist in table" - jps "Key column '%-.192s' ãŒãƒ†ãƒ¼ãƒ–ルã«ã‚りã¾ã›ã‚“.", est "Võtme tulp '%-.192s' puudub tabelis" fre "La clé '%-.192s' n'existe pas dans la table" ger "In der Tabelle gibt es kein Schlüsselfeld '%-.192s'" greek "Το πεδίο κλειδί '%-.192s' δεν υπάÏχει στον πίνακα" hun "A(z) '%-.192s'kulcsoszlop nem letezik a tablaban" ita "La colonna chiave '%-.192s' non esiste nella tabella" - jpn "Key column '%-.192s' ãŒãƒ†ãƒ¼ãƒ–ルã«ã‚りã¾ã›ã‚“." + jpn "ã‚ー列 '%-.192s' ã¯è¡¨ã«ã‚りã¾ã›ã‚“。" kor "Key 칼럼 '%-.192s'는 í…Œì´ë¸”ì— ì¡´ìž¬í•˜ì§€ 않습니다." nor "Nøkkel felt '%-.192s' eksiterer ikke i tabellen" norwegian-ny "Nykkel kolonne '%-.192s' eksiterar ikkje i tabellen" @@ -1750,41 +1680,22 @@ ER_KEY_COLUMN_DOES_NOT_EXITS 42000 S1009 swe "Nyckelkolumn '%-.192s' finns inte" ukr "Ключовий Ñтовбець '%-.192s' не Ñ–Ñнує у таблиці" ER_BLOB_USED_AS_KEY 42000 S1009 - cze "Blob sloupec '%-.192s' nem-Bůže být použit jako klÃÄ" - dan "BLOB feltet '%-.192s' kan ikke bruges ved specifikation af indeks" - nla "BLOB kolom '%-.192s' kan niet gebruikt worden bij zoeksleutel specificatie" - eng "BLOB column '%-.192s' can't be used in key specification with the used table type" - est "BLOB-tüüpi tulpa '%-.192s' ei saa kasutada võtmena" - fre "Champ BLOB '%-.192s' ne peut être utilisé dans une clé" - ger "BLOB-Feld '%-.192s' kann beim verwendeten Tabellentyp nicht als Schlüssel verwendet werden" - greek "Πεδίο Ï„Ïπου Blob '%-.192s' δεν μποÏεί να χÏησιμοποιηθεί στον οÏισμό ενός ÎºÎ»ÎµÎ¹Î´Î¹Î¿Ï (key specification)" - hun "Blob objektum '%-.192s' nem hasznalhato kulcskent" - ita "La colonna BLOB '%-.192s' non puo` essere usata nella specifica della chiave" - kor "BLOB 칼럼 '%-.192s'는 키 ì •ì˜ì—서 사용ë 수 없습니다." - nor "Blob felt '%-.192s' kan ikke brukes ved spesifikasjon av nøkler" - norwegian-ny "Blob kolonne '%-.192s' kan ikkje brukast ved spesifikasjon av nyklar" - pol "Kolumna typu Blob '%-.192s' nie może być użyta w specyfikacji klucza" - por "Coluna BLOB '%-.192s' não pode ser utilizada na especificação de chave para o tipo de tabela usado" - rum "Coloana de tip BLOB '%-.192s' nu poate fi folosita in specificarea cheii cu tipul de tabla folosit" - rus "Столбец типа BLOB '%-.192s' не может быть иÑпользован как значение ключа в таблице такого типа" - serbian "BLOB kolona '%-.192s' ne može biti upotrebljena za navoÄ‘enje kljuÄa sa tipom tabele koji se trenutno koristi" - slo "Blob pole '%-.192s' nemôže byÅ¥ použité ako kľúÄ" - spa "La columna Blob '%-.192s' no puede ser usada en una declaracion de clave" - swe "En BLOB '%-.192s' kan inte vara nyckel med den använda tabelltypen" - ukr "BLOB Ñтовбець '%-.192s' не може бути викориÑтаний у визначенні ключа в цьому типі таблиці" + eng "BLOB column %`s can't be used in key specification in the %s table" + ger "BLOB-Feld %`s kann beim %s Tabellen nicht als Schlüssel verwendet werden" + rus "Столбец типа BLOB %`s не может быть иÑпользован как значение ключа в %s таблице" + ukr "BLOB Ñтовбець %`s не може бути викориÑтаний у визначенні ключа в %s таблиці" ER_TOO_BIG_FIELDLENGTH 42000 S1009 - cze "P-BÅ™ÃliÅ¡ velká délka sloupce '%-.192s' (nejvÃce %lu). Použijte BLOB" + cze "PÅ™ÃliÅ¡ velká délka sloupce '%-.192s' (nejvÃce %lu). Použijte BLOB" dan "For stor feltlængde for kolonne '%-.192s' (maks = %lu). Brug BLOB i stedet" nla "Te grote kolomlengte voor '%-.192s' (max = %lu). Maak hiervoor gebruik van het type BLOB" eng "Column length too big for column '%-.192s' (max = %lu); use BLOB or TEXT instead" - jps "column '%-.192s' ã¯,確ä¿ã™ã‚‹ column ã®å¤§ãã•ãŒå¤šã™ãŽã¾ã™. (最大 %lu ã¾ã§). BLOB ã‚’ã‹ã‚りã«ä½¿ç”¨ã—ã¦ãã ã•ã„." est "Tulba '%-.192s' pikkus on liiga pikk (maksimaalne pikkus: %lu). Kasuta BLOB väljatüüpi" fre "Champ '%-.192s' trop long (max = %lu). Utilisez un BLOB" ger "Feldlänge für Feld '%-.192s' zu groß (maximal %lu). BLOB- oder TEXT-Spaltentyp verwenden!" greek "Î Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿ μήκος για το πεδίο '%-.192s' (max = %lu). ΠαÏακαλώ χÏησιμοποιείστε τον Ï„Ïπο BLOB" hun "A(z) '%-.192s' oszlop tul hosszu. (maximum = %lu). Hasznaljon BLOB tipust inkabb." ita "La colonna '%-.192s' e` troppo grande (max=%lu). Utilizza un BLOB." - jpn "column '%-.192s' ã¯,確ä¿ã™ã‚‹ column ã®å¤§ãã•ãŒå¤šã™ãŽã¾ã™. (最大 %lu ã¾ã§). BLOB ã‚’ã‹ã‚りã«ä½¿ç”¨ã—ã¦ãã ã•ã„." + jpn "列 '%-.192s' ã®ã‚µã‚¤ã‚ºå®šç¾©ãŒå¤§ãã™ãŽã¾ã™ (最大 %lu ã¾ã§)。代ã‚り㫠BLOB ã¾ãŸã¯ TEXT を使用ã—ã¦ãã ã•ã„。" kor "칼럼 '%-.192s'ì˜ ì¹¼ëŸ¼ 길ì´ê°€ 너무 ê¹ë‹ˆë‹¤ (최대 = %lu). ëŒ€ì‹ ì— BLOB를 사용하세요." nor "For stor nøkkellengde for kolonne '%-.192s' (maks = %lu). Bruk BLOB istedenfor" norwegian-ny "For stor nykkellengde for felt '%-.192s' (maks = %lu). Bruk BLOB istadenfor" @@ -1798,18 +1709,17 @@ ER_TOO_BIG_FIELDLENGTH 42000 S1009 swe "För stor kolumnlängd angiven för '%-.192s' (max= %lu). Använd en BLOB instället" ukr "Задовга довжина ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.192s' (max = %lu). ВикориÑтайте тип BLOB" ER_WRONG_AUTO_KEY 42000 S1009 - cze "M-Bůžete mÃt pouze jedno AUTO pole a to musà být definováno jako klÃÄ" + cze "Můžete mÃt pouze jedno AUTO pole a to musà být definováno jako klÃÄ" dan "Der kan kun specificeres eet AUTO_INCREMENT-felt, og det skal være indekseret" nla "Er kan slechts 1 autofield zijn en deze moet als zoeksleutel worden gedefinieerd." eng "Incorrect table definition; there can be only one auto column and it must be defined as a key" - jps "テーブルã®å®šç¾©ãŒé•ã„ã¾ã™; there can be only one auto column and it must be defined as a key", est "Vigane tabelikirjeldus; Tabelis tohib olla üks auto_increment tüüpi tulp ning see peab olema defineeritud võtmena" fre "Un seul champ automatique est permis et il doit être indexé" ger "Falsche Tabellendefinition. Es darf nur eine AUTO_INCREMENT-Spalte geben, und diese muss als Schlüssel definiert werden" greek "ΜποÏεί να υπάÏχει μόνο Îνα auto field και Ï€ÏÎπει να Îχει οÏισθεί σαν key" hun "Csak egy auto mezo lehetseges, es azt kulcskent kell definialni." ita "Puo` esserci solo un campo AUTO e deve essere definito come chiave" - jpn "テーブルã®å®šç¾©ãŒé•ã„ã¾ã™; there can be only one auto column and it must be defined as a key" + jpn "䏿£ãªè¡¨å®šç¾©ã§ã™ã€‚AUTO_INCREMENT列ã¯ï¼‘個ã¾ã§ã§ã€ç´¢å¼•を定義ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" kor "ë¶€ì •í™•í•œ í…Œì´ë¸” ì •ì˜; í…Œì´ë¸”ì€ í•˜ë‚˜ì˜ auto ì¹¼ëŸ¼ì´ ì¡´ìž¬í•˜ê³ í‚¤ë¡œ ì •ì˜ë˜ì–´ì ¸ì•¼ 합니다." nor "Bare ett auto felt kan være definert som nøkkel." norwegian-ny "Bare eitt auto felt kan være definert som nøkkel." @@ -1822,8 +1732,8 @@ ER_WRONG_AUTO_KEY 42000 S1009 spa "Puede ser solamente un campo automatico y este debe ser definido como una clave" swe "Det fÃ¥r finnas endast ett AUTO_INCREMENT-fält och detta mÃ¥ste vara en nyckel" ukr "Ðевірне Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ–; Може бути лише один автоматичний Ñтовбець, що повинен бути визначений Ñк ключ" -ER_UNUSED_2 - eng "You should never see it" +ER_BINLOG_CANT_DELETE_GTID_DOMAIN + eng "Could not delete gtid domain. Reason: %s." ER_NORMAL_SHUTDOWN cze "%s: norm-Bálnà ukonÄenÃ" dan "%s: Normal nedlukning" @@ -1835,6 +1745,7 @@ ER_NORMAL_SHUTDOWN greek "%s: Φυσιολογική διαδικασία shutdown" hun "%s: Normal leallitas" ita "%s: Shutdown normale" + jpn "%s: 通常シャットダウン" kor "%s: ì •ìƒì ì¸ shutdown" nor "%s: Normal avslutning" norwegian-ny "%s: Normal nedkopling" @@ -1848,18 +1759,17 @@ ER_NORMAL_SHUTDOWN swe "%s: Normal avslutning" ukr "%s: Ðормальне завершеннÑ" ER_GOT_SIGNAL - cze "%s: p-BÅ™ijat signal %d, konÄÃm\n" + cze "%s: pÅ™ijat signal %d, konÄÃm\n" dan "%s: Fangede signal %d. Afslutter!!\n" nla "%s: Signaal %d. Systeem breekt af!\n" eng "%s: Got signal %d. Aborting!\n" - jps "%s: Got signal %d. 䏿–!Â¥n", est "%s: sain signaali %d. Lõpetan!\n" fre "%s: Reçu le signal %d. Abandonne!\n" ger "%s: Signal %d erhalten. Abbruch!\n" greek "%s: Ελήφθη το μήνυμα %d. Η διαδικασία εγκαταλείπεται!\n" hun "%s: %d jelzes. Megszakitva!\n" ita "%s: Ricevuto segnale %d. Interruzione!\n" - jpn "%s: Got signal %d. 䏿–!\n" + jpn "%s: シグナル %d ã‚’å—ä¿¡ã—ã¾ã—ãŸã€‚強制終了ã—ã¾ã™ï¼\n" kor "%s: %d ì‹ í˜¸ê°€ 들어왔ìŒ. 중지!\n" nor "%s: Oppdaget signal %d. Avslutter!\n" norwegian-ny "%s: Oppdaga signal %d. Avsluttar!\n" @@ -1873,18 +1783,17 @@ ER_GOT_SIGNAL swe "%s: Fick signal %d. Avslutar!\n" ukr "%s: Отримано Ñигнал %d. ПерериваюÑÑŒ!\n" ER_SHUTDOWN_COMPLETE - cze "%s: ukon-BÄenà práce hotovo\n" + cze "%s: ukonÄenà práce hotovo\n" dan "%s: Server lukket\n" nla "%s: Afsluiten afgerond\n" eng "%s: Shutdown complete\n" - jps "%s: Shutdown 完了¥n", est "%s: Lõpp\n" fre "%s: Arrêt du serveur terminé\n" ger "%s: Herunterfahren beendet\n" greek "%s: Η διαδικασία Shutdown ολοκληÏώθηκε\n" hun "%s: A leallitas kesz\n" ita "%s: Shutdown completato\n" - jpn "%s: Shutdown 完了\n" + jpn "%s: シャットダウン完了\n" kor "%s: Shutdown ì´ ì™„ë£Œë¨!\n" nor "%s: Avslutning komplett\n" norwegian-ny "%s: Nedkopling komplett\n" @@ -1898,18 +1807,17 @@ ER_SHUTDOWN_COMPLETE swe "%s: Avslutning klar\n" ukr "%s: Роботу завершено\n" ER_FORCING_CLOSE 08S01 - cze "%s: n-Básilné uzavÅ™enà threadu %ld uživatele '%-.48s'\n" + cze "%s: násilné uzavÅ™enà threadu %ld uživatele '%-.48s'\n" dan "%s: Forceret nedlukning af trÃ¥d: %ld bruger: '%-.48s'\n" nla "%s: Afsluiten afgedwongen van thread %ld gebruiker: '%-.48s'\n" eng "%s: Forcing close of thread %ld user: '%-.48s'\n" - jps "%s: スレッド %ld 強制終了 user: '%-.48s'Â¥n", est "%s: Sulgen jõuga lõime %ld kasutaja: '%-.48s'\n" fre "%s: Arrêt forcé de la tâche (thread) %ld utilisateur: '%-.48s'\n" ger "%s: Thread %ld zwangsweise beendet. Benutzer: '%-.48s'\n" greek "%s: Το thread θα κλείσει %ld user: '%-.48s'\n" hun "%s: A(z) %ld thread kenyszeritett zarasa. Felhasznalo: '%-.48s'\n" ita "%s: Forzata la chiusura del thread %ld utente: '%-.48s'\n" - jpn "%s: スレッド %ld 強制終了 user: '%-.48s'\n" + jpn "%s: スレッド %ld を強制終了ã—ã¾ã™ (ユーザー: '%-.48s')\n" kor "%s: thread %ldì˜ ê°•ì œ 종료 user: '%-.48s'\n" nor "%s: PÃ¥tvinget avslutning av trÃ¥d %ld bruker: '%-.48s'\n" norwegian-ny "%s: PÃ¥tvinga avslutning av trÃ¥d %ld brukar: '%-.48s'\n" @@ -1923,18 +1831,17 @@ ER_FORCING_CLOSE 08S01 swe "%s: Stänger av trÃ¥d %ld; användare: '%-.48s'\n" ukr "%s: ПриÑкорюю Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð³Ñ–Ð»ÐºÐ¸ %ld кориÑтувача: '%-.48s'\n" ER_IPSOCK_ERROR 08S01 - cze "Nemohu vytvo-BÅ™it IP socket" + cze "Nemohu vytvoÅ™it IP socket" dan "Kan ikke oprette IP socket" nla "Kan IP-socket niet openen" eng "Can't create IP socket" - jps "IP socket ãŒä½œã‚Œã¾ã›ã‚“", est "Ei suuda luua IP socketit" fre "Ne peut créer la connexion IP (socket)" ger "Kann IP-Socket nicht erzeugen" greek "Δεν είναι δυνατή η δημιουÏγία IP socket" hun "Az IP socket nem hozhato letre" ita "Impossibile creare il socket IP" - jpn "IP socket ãŒä½œã‚Œã¾ã›ã‚“" + jpn "IPソケットを作æˆã§ãã¾ã›ã‚“。" kor "IP ì†Œì¼“ì„ ë§Œë“¤ì§€ 못했습니다." nor "Kan ikke opprette IP socket" norwegian-ny "Kan ikkje opprette IP socket" @@ -1948,18 +1855,17 @@ ER_IPSOCK_ERROR 08S01 swe "Kan inte skapa IP-socket" ukr "Ðе можу Ñтворити IP роз'єм" ER_NO_SUCH_INDEX 42S12 S1009 - cze "Tabulka '%-.192s' nem-Bá index odpovÃdajÃcà CREATE INDEX. VytvoÅ™te tabulku znovu" + cze "Tabulka '%-.192s' nemá index odpovÃdajÃcà CREATE INDEX. VytvoÅ™te tabulku znovu" dan "Tabellen '%-.192s' har ikke den nøgle, som blev brugt i CREATE INDEX. Genopret tabellen" nla "Tabel '%-.192s' heeft geen INDEX zoals deze gemaakt worden met CREATE INDEX. Maak de tabel opnieuw" eng "Table '%-.192s' has no index like the one used in CREATE INDEX; recreate the table" - jps "Table '%-.192s' ã¯ãã®ã‚ˆã†ãª index ã‚’æŒã£ã¦ã„ã¾ã›ã‚“(CREATE INDEX å®Ÿè¡Œæ™‚ã«æŒ‡å®šã•れã¦ã„ã¾ã›ã‚“). テーブルを作り直ã—ã¦ãã ã•ã„", est "Tabelil '%-.192s' puuduvad võtmed. Loo tabel uuesti" fre "La table '%-.192s' n'a pas d'index comme celle utilisée dans CREATE INDEX. Recréez la table" ger "Tabelle '%-.192s' besitzt keinen wie den in CREATE INDEX verwendeten Index. Tabelle neu anlegen" greek "Ο πίνακας '%-.192s' δεν Îχει ευÏετήÏιο (index) σαν αυτό που χÏησιμοποιείτε στην CREATE INDEX. ΠαÏακαλώ, ξαναδημιουÏγήστε τον πίνακα" hun "A(z) '%-.192s' tablahoz nincs meg a CREATE INDEX altal hasznalt index. Alakitsa at a tablat" ita "La tabella '%-.192s' non ha nessun indice come quello specificatato dalla CREATE INDEX. Ricrea la tabella" - jpn "Table '%-.192s' ã¯ãã®ã‚ˆã†ãª index ã‚’æŒã£ã¦ã„ã¾ã›ã‚“(CREATE INDEX å®Ÿè¡Œæ™‚ã«æŒ‡å®šã•れã¦ã„ã¾ã›ã‚“). テーブルを作り直ã—ã¦ãã ã•ã„" + jpn "表 '%-.192s' ã«ä»¥å‰CREATE INDEXã§ä½œæˆã•れãŸç´¢å¼•ãŒã‚りã¾ã›ã‚“。表を作り直ã—ã¦ãã ã•ã„。" kor "í…Œì´ë¸” '%-.192s'는 ì¸ë±ìŠ¤ë¥¼ 만들지 않았습니다. alter í…Œì´ë¸”ëª…ë ¹ì„ ì´ìš©í•˜ì—¬ í…Œì´ë¸”ì„ ìˆ˜ì •í•˜ì„¸ìš”..." nor "Tabellen '%-.192s' har ingen index som den som er brukt i CREATE INDEX. Gjenopprett tabellen" norwegian-ny "Tabellen '%-.192s' har ingen index som den som er brukt i CREATE INDEX. Oprett tabellen pÃ¥ nytt" @@ -1973,7 +1879,7 @@ ER_NO_SUCH_INDEX 42S12 S1009 swe "Tabellen '%-.192s' har inget index som motsvarar det angivna i CREATE INDEX. Skapa om tabellen" ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' має індекÑ, що не Ñпівпадає з вказанним у CREATE INDEX. Створіть таблицю знову" ER_WRONG_FIELD_TERMINATORS 42000 S1009 - cze "Argument separ-Bátoru položek nebyl oÄekáván. PÅ™eÄtÄ›te si manuál" + cze "Argument separátoru položek nebyl oÄekáván. PÅ™eÄtÄ›te si manuál" dan "Felt adskiller er ikke som forventet, se dokumentationen" nla "De argumenten om velden te scheiden zijn anders dan verwacht. Raadpleeg de handleiding" eng "Field separator argument is not what is expected; check the manual" @@ -1983,6 +1889,7 @@ ER_WRONG_FIELD_TERMINATORS 42000 S1009 greek "Ο διαχωÏιστής πεδίων δεν είναι αυτός που αναμενόταν. ΠαÏακαλώ ανατÏÎξτε στο manual" hun "A mezoelvalaszto argumentumok nem egyeznek meg a varttal. Nezze meg a kezikonyvben!" ita "L'argomento 'Field separator' non e` quello atteso. Controlla il manuale" + jpn "フィールド区切り文å—ãŒäºˆæœŸã›ã¬ä½¿ã‚れ方をã—ã¦ã„ã¾ã™ã€‚マニュアルを確èªã—ã¦ä¸‹ã•ã„。" kor "필드 êµ¬ë¶„ìž ì¸ìˆ˜ë“¤ì´ ì™„ì „í•˜ì§€ 않습니다. ë©”ë‰´ì–¼ì„ ì°¾ì•„ 보세요." nor "Felt skiller argumentene er ikke som forventet, se dokumentasjonen" norwegian-ny "Felt skiljer argumenta er ikkje som venta, sjÃ¥ dokumentasjonen" @@ -1996,7 +1903,7 @@ ER_WRONG_FIELD_TERMINATORS 42000 S1009 swe "Fältseparatorerna är vad som förväntades. Kontrollera mot manualen" ukr "Хибний розділювач полів. Почитайте документацію" ER_BLOBS_AND_NO_TERMINATED 42000 S1009 - cze "Nen-Bà možné použÃt pevný rowlength s BLOBem. Použijte 'fields terminated by'." + cze "Nenà možné použÃt pevný rowlength s BLOBem. Použijte 'fields terminated by'." dan "Man kan ikke bruge faste feltlængder med BLOB. Brug i stedet 'fields terminated by'." nla "Bij het gebruik van BLOBs is het niet mogelijk om vaste rijlengte te gebruiken. Maak s.v.p. gebruik van 'fields terminated by'." eng "You can't use fixed rowlength with BLOBs; please use 'fields terminated by'" @@ -2006,7 +1913,7 @@ ER_BLOBS_AND_NO_TERMINATED 42000 S1009 greek "Δεν μποÏείτε να χÏησιμοποιήσετε fixed rowlength σε BLOBs. ΠαÏακαλώ χÏησιμοποιείστε 'fields terminated by'." hun "Fix hosszusagu BLOB-ok nem hasznalhatok. Hasznalja a 'mezoelvalaszto jelet' ." ita "Non possono essere usate righe a lunghezza fissa con i BLOB. Usa 'FIELDS TERMINATED BY'." - jpn "You can't use fixed rowlength with BLOBs; please use 'fields terminated by'." + jpn "BLOBã«ã¯å›ºå®šé•·ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒä½¿ç”¨ã§ãã¾ã›ã‚“。'FIELDS TERMINATED BY'å¥ã‚’使用ã—ã¦ä¸‹ã•ã„。" kor "BLOB로는 ê³ ì •ê¸¸ì´ì˜ lowlength를 ì‚¬ìš©í• ìˆ˜ 없습니다. 'fields terminated by'를 사용하세요." nor "En kan ikke bruke faste feltlengder med BLOB. Vennlisgt bruk 'fields terminated by'." norwegian-ny "Ein kan ikkje bruke faste feltlengder med BLOB. Vennlisgt bruk 'fields terminated by'." @@ -2020,18 +1927,17 @@ ER_BLOBS_AND_NO_TERMINATED 42000 S1009 swe "Man kan inte använda fast radlängd med blobs. Använd 'fields terminated by'" ukr "Ðе можна викориÑтовувати Ñталу довжину Ñтроки з BLOB. ЗкориÑтайтеÑÑ 'fields terminated by'" ER_TEXTFILE_NOT_READABLE - cze "Soubor '%-.128s' mus-Bà být v adresáři databáze nebo Äitelný pro vÅ¡echny" + cze "Soubor '%-.128s' musà být v adresáři databáze nebo Äitelný pro vÅ¡echny" dan "Filen '%-.128s' skal være i database-folderen, eller kunne læses af alle" nla "Het bestand '%-.128s' dient in de database directory voor the komen of leesbaar voor iedereen te zijn." eng "The file '%-.128s' must be in the database directory or be readable by all" - jps "ファイル '%-.128s' 㯠databse ã® directory ã«ã‚ã‚‹ã‹å…¨ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒèªã‚るよã†ã«è¨±å¯ã•れã¦ã„ãªã‘れã°ãªã‚Šã¾ã›ã‚“.", est "Fail '%-.128s' peab asuma andmebaasi kataloogis või olema kõigile loetav" fre "Le fichier '%-.128s' doit être dans le répertoire de la base et lisible par tous" ger "Datei '%-.128s' muss im Datenbank-Verzeichnis vorhanden oder lesbar für alle sein" greek "Το αÏχείο '%-.128s' Ï€ÏÎπει να υπάÏχει στο database directory ή να μποÏεί να διαβαστεί από όλους" hun "A(z) '%-.128s'-nak az adatbazis konyvtarban kell lennie, vagy mindenki szamara olvashatonak" ita "Il file '%-.128s' deve essere nella directory del database e deve essere leggibile da tutti" - jpn "ファイル '%-.128s' 㯠databse ã® directory ã«ã‚ã‚‹ã‹å…¨ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒèªã‚るよã†ã«è¨±å¯ã•れã¦ã„ãªã‘れã°ãªã‚Šã¾ã›ã‚“." + jpn "ファイル '%-.128s' ã¯ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«ã‚ã‚‹ã‹ã€å…¨ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‹ã‚‰èªã‚ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" kor "'%-.128s' í™”ì¼ëŠ” ë°ì´íƒ€ë² ì´ìФ ë””ë ‰í† ë¦¬ì— ì¡´ìž¬í•˜ê±°ë‚˜ 모ë‘ì—게 ì½ê¸° 가능하여야 합니다." nor "Filen '%-.128s' mÃ¥ være i database-katalogen for Ã¥ være lesbar for alle" norwegian-ny "Filen '%-.128s' mÃ¥ være i database-katalogen for Ã¥ være lesbar for alle" @@ -2045,18 +1951,17 @@ ER_TEXTFILE_NOT_READABLE swe "Textfilen '%-.128s' mÃ¥ste finnas i databasbiblioteket eller vara läsbar för alla" ukr "Файл '%-.128s' повинен бути у теці бази данних або мати вÑтановлене право на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð´Ð»Ñ ÑƒÑÑ–Ñ…" ER_FILE_EXISTS_ERROR - cze "Soubor '%-.200s' ji-Bž existuje" + cze "Soubor '%-.200s' již existuje" dan "Filen '%-.200s' eksisterer allerede" nla "Het bestand '%-.200s' bestaat reeds" eng "File '%-.200s' already exists" - jps "File '%-.200s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™", est "Fail '%-.200s' juba eksisteerib" fre "Le fichier '%-.200s' existe déjà " ger "Datei '%-.200s' bereits vorhanden" greek "Το αÏχείο '%-.200s' υπάÏχει ήδη" hun "A '%-.200s' file mar letezik." ita "Il file '%-.200s' esiste gia`" - jpn "File '%-.200s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™" + jpn "ファイル '%-.200s' ã¯ã™ã§ã«å˜åœ¨ã—ã¾ã™ã€‚" kor "'%-.200s' í™”ì¼ì€ ì´ë¯¸ 존재합니다." nor "Filen '%-.200s' eksisterte allerede" norwegian-ny "Filen '%-.200s' eksisterte allereide" @@ -2070,18 +1975,17 @@ ER_FILE_EXISTS_ERROR swe "Filen '%-.200s' existerar redan" ukr "Файл '%-.200s' вже Ñ–Ñнує" ER_LOAD_INFO - cze "Z-Báznamů: %ld Vymazáno: %ld PÅ™eskoÄeno: %ld VarovánÃ: %ld" + cze "Záznamů: %ld Vymazáno: %ld PÅ™eskoÄeno: %ld VarovánÃ: %ld" dan "Poster: %ld Fjernet: %ld Sprunget over: %ld Advarsler: %ld" nla "Records: %ld Verwijderd: %ld Overgeslagen: %ld Waarschuwingen: %ld" eng "Records: %ld Deleted: %ld Skipped: %ld Warnings: %ld" - jps "レコード数: %ld 削除: %ld Skipped: %ld Warnings: %ld", est "Kirjeid: %ld Kustutatud: %ld Vahele jäetud: %ld Hoiatusi: %ld" fre "Enregistrements: %ld Effacés: %ld Non traités: %ld Avertissements: %ld" ger "Datensätze: %ld Gelöscht: %ld Ausgelassen: %ld Warnungen: %ld" greek "ΕγγÏαφÎÏ‚: %ld ΔιαγÏαφÎÏ‚: %ld ΠαÏεκάμφθησαν: %ld Î Ïοειδοποιήσεις: %ld" hun "Rekordok: %ld Torolve: %ld Skipped: %ld Warnings: %ld" ita "Records: %ld Cancellati: %ld Saltati: %ld Avvertimenti: %ld" - jpn "レコード数: %ld 削除: %ld Skipped: %ld Warnings: %ld" + jpn "レコード数: %ld 削除: %ld スã‚ップ: %ld è¦å‘Š: %ld" kor "ë ˆì½”ë“œ: %ldê°œ ì‚ì œ: %ldê°œ 스킵: %ldê°œ ê²½ê³ : %ldê°œ" nor "Poster: %ld Fjernet: %ld Hoppet over: %ld Advarsler: %ld" norwegian-ny "Poster: %ld Fjerna: %ld Hoppa over: %ld Ã…tvaringar: %ld" @@ -2095,11 +1999,10 @@ ER_LOAD_INFO swe "Rader: %ld Bortagna: %ld Dubletter: %ld Varningar: %ld" ukr "ЗапиÑів: %ld Видалено: %ld Пропущено: %ld ЗаÑтережень: %ld" ER_ALTER_INFO - cze "Z-Báznamů: %ld Zdvojených: %ld" + cze "Záznamů: %ld Zdvojených: %ld" dan "Poster: %ld Ens: %ld" nla "Records: %ld Dubbel: %ld" eng "Records: %ld Duplicates: %ld" - jps "レコード数: %ld é‡è¤‡: %ld", est "Kirjeid: %ld Kattuvaid: %ld" fre "Enregistrements: %ld Doublons: %ld" ger "Datensätze: %ld Duplikate: %ld" @@ -2120,7 +2023,7 @@ ER_ALTER_INFO swe "Rader: %ld Dubletter: %ld" ukr "ЗапиÑів: %ld Дублікатів: %ld" ER_WRONG_SUB_KEY - cze "Chybn-Bá podÄást klÃÄe -- nenà to Å™etÄ›zec nebo je delšà než délka Äásti klÃÄe" + cze "Chybná podÄást klÃÄe -- nenà to Å™etÄ›zec nebo je delšà než délka Äásti klÃÄe" dan "Forkert indeksdel. Den anvendte nøgledel er ikke en streng eller længden er større end nøglelængden" nla "Foutief sub-gedeelte van de zoeksleutel. De gebruikte zoeksleutel is geen onderdeel van een string of of de gebruikte lengte is langer dan de zoeksleutel" eng "Incorrect prefix key; the used key part isn't a string, the used length is longer than the key part, or the storage engine doesn't support unique prefix keys" @@ -2130,7 +2033,7 @@ ER_WRONG_SUB_KEY greek "ΕσφαλμÎνο sub part key. Το χÏησιμοποιοÏμενο key part δεν είναι string ή το μήκος του είναι μεγαλÏτεÏο" hun "Rossz alkulcs. A hasznalt kulcsresz nem karaktersorozat vagy hosszabb, mint a kulcsresz" ita "Sotto-parte della chiave errata. La parte di chiave utilizzata non e` una stringa o la lunghezza e` maggiore della parte di chiave." - jpn "Incorrect prefix key; the used key part isn't a string or the used length is longer than the key part" + jpn "ã‚ーã®ãƒ—レフィックスãŒä¸æ£ã§ã™ã€‚ã‚ãƒ¼ãŒæ–‡å—列ã§ã¯ãªã„ã‹ã€ãƒ—レフィックス長ãŒã‚ーよりも長ã„ã‹ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ã‚¸ãƒ³ãŒä¸€æ„索引ã®ãƒ—レフィックス指定をサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“。" kor "ë¶€ì •í™•í•œ 서버 파트 키. ì‚¬ìš©ëœ í‚¤ 파트가 스트ë§ì´ 아니거나 키 íŒŒíŠ¸ì˜ ê¸¸ì´ê°€ 너무 ê¹ë‹ˆë‹¤." nor "Feil delnøkkel. Den brukte delnøkkelen er ikke en streng eller den oppgitte lengde er lengre enn nøkkel lengden" norwegian-ny "Feil delnykkel. Den brukte delnykkelen er ikkje ein streng eller den oppgitte lengda er lengre enn nykkellengden" @@ -2144,18 +2047,17 @@ ER_WRONG_SUB_KEY swe "Felaktig delnyckel. Nyckeldelen är inte en sträng eller den angivna längden är längre än kolumnlängden" ukr "Ðевірна чаÑтина ключа. ВикориÑтана чаÑтина ключа не Ñ” Ñтрокою, задовга або вказівник таблиці не підтримує унікальних чаÑтин ключей" ER_CANT_REMOVE_ALL_FIELDS 42000 - cze "Nen-Bà možné vymazat vÅ¡echny položky s ALTER TABLE. Použijte DROP TABLE" + cze "Nenà možné vymazat vÅ¡echny položky s ALTER TABLE. Použijte DROP TABLE" dan "Man kan ikke slette alle felter med ALTER TABLE. Brug DROP TABLE i stedet." nla "Het is niet mogelijk alle velden te verwijderen met ALTER TABLE. Gebruik a.u.b. DROP TABLE hiervoor!" eng "You can't delete all columns with ALTER TABLE; use DROP TABLE instead" - jps "ALTER TABLE ã§å…¨ã¦ã® column ã¯å‰Šé™¤ã§ãã¾ã›ã‚“. DROP TABLE を使用ã—ã¦ãã ã•ã„", est "ALTER TABLE kasutades ei saa kustutada kõiki tulpasid. Kustuta tabel DROP TABLE abil" fre "Vous ne pouvez effacer tous les champs avec ALTER TABLE. Utilisez DROP TABLE" ger "Mit ALTER TABLE können nicht alle Felder auf einmal gelöscht werden. Dafür DROP TABLE verwenden" greek "Δεν είναι δυνατή η διαγÏαφή όλων των πεδίων με ALTER TABLE. ΠαÏακαλώ χÏησιμοποιείστε DROP TABLE" hun "Az osszes mezo nem torolheto az ALTER TABLE-lel. Hasznalja a DROP TABLE-t helyette" ita "Non si possono cancellare tutti i campi con una ALTER TABLE. Utilizzare DROP TABLE" - jpn "ALTER TABLE ã§å…¨ã¦ã® column ã¯å‰Šé™¤ã§ãã¾ã›ã‚“. DROP TABLE を使用ã—ã¦ãã ã•ã„" + jpn "ALTER TABLE ã§ã¯å…¨ã¦ã®åˆ—ã®å‰Šé™¤ã¯ã§ãã¾ã›ã‚“。DROP TABLE を使用ã—ã¦ãã ã•ã„。" kor "ALTER TABLE ëª…ë ¹ìœ¼ë¡œëŠ” ëª¨ë“ ì¹¼ëŸ¼ì„ ì§€ìš¸ 수 없습니다. DROP TABLE ëª…ë ¹ì„ ì´ìš©í•˜ì„¸ìš”." nor "En kan ikke slette alle felt med ALTER TABLE. Bruk DROP TABLE isteden." norwegian-ny "Ein kan ikkje slette alle felt med ALTER TABLE. Bruk DROP TABLE istadenfor." @@ -2169,18 +2071,17 @@ ER_CANT_REMOVE_ALL_FIELDS 42000 swe "Man kan inte radera alla fält med ALTER TABLE. Använd DROP TABLE istället" ukr "Ðе можливо видалити вÑÑ– Ñтовбці за допомогою ALTER TABLE. Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ ÑкориÑтайтеÑÑ DROP TABLE" ER_CANT_DROP_FIELD_OR_KEY 42000 - cze "Nemohu zru-BÅ¡it '%-.192s' (provést DROP). Zkontrolujte, zda neexistujà záznamy/klÃÄe" + cze "Nemohu zruÅ¡it '%-.192s' (provést DROP). Zkontrolujte, zda neexistujà záznamy/klÃÄe" dan "Kan ikke udføre DROP '%-.192s'. Undersøg om feltet/nøglen eksisterer." nla "Kan '%-.192s' niet weggooien. Controleer of het veld of de zoeksleutel daadwerkelijk bestaat." eng "Can't DROP '%-.192s'; check that column/key exists" - jps "'%-.192s' ã‚’ç ´æ£„ã§ãã¾ã›ã‚“ã§ã—ãŸ; check that column/key exists", est "Ei suuda kustutada '%-.192s'. Kontrolli kas tulp/võti eksisteerib" fre "Ne peut effacer (DROP) '%-.192s'. Vérifiez s'il existe" ger "Kann '%-.192s' nicht löschen. Existiert die Spalte oder der Schlüssel?" greek "ΑδÏνατη η διαγÏαφή (DROP) '%-.192s'. ΠαÏακαλώ ελÎγξτε αν το πεδίο/κλειδί υπάÏχει" hun "A DROP '%-.192s' nem lehetseges. Ellenorizze, hogy a mezo/kulcs letezik-e" ita "Impossibile cancellare '%-.192s'. Controllare che il campo chiave esista" - jpn "'%-.192s' ã‚’ç ´æ£„ã§ãã¾ã›ã‚“ã§ã—ãŸ; check that column/key exists" + jpn "'%-.192s' を削除ã§ãã¾ã›ã‚“。列ï¼ç´¢å¼•ã®å˜åœ¨ã‚’確èªã—ã¦ä¸‹ã•ã„。" kor "'%-.192s'를 DROPí• ìˆ˜ 없습니다. 칼럼ì´ë‚˜ 키가 존재하는지 채í¬í•˜ì„¸ìš”." nor "Kan ikke DROP '%-.192s'. Undersøk om felt/nøkkel eksisterer." norwegian-ny "Kan ikkje DROP '%-.192s'. Undersøk om felt/nøkkel eksisterar." @@ -2194,18 +2095,17 @@ ER_CANT_DROP_FIELD_OR_KEY 42000 swe "Kan inte ta bort '%-.192s'. Kontrollera att fältet/nyckel finns" ukr "Ðе можу DROP '%-.192s'. Перевірте, чи цей Ñтовбець/ключ Ñ–Ñнує" ER_INSERT_INFO - cze "Z-Báznamů: %ld Zdvojených: %ld VarovánÃ: %ld" + cze "Záznamů: %ld Zdvojených: %ld VarovánÃ: %ld" dan "Poster: %ld Ens: %ld Advarsler: %ld" nla "Records: %ld Dubbel: %ld Waarschuwing: %ld" eng "Records: %ld Duplicates: %ld Warnings: %ld" - jps "レコード数: %ld é‡è¤‡æ•°: %ld Warnings: %ld", est "Kirjeid: %ld Kattuvaid: %ld Hoiatusi: %ld" fre "Enregistrements: %ld Doublons: %ld Avertissements: %ld" ger "Datensätze: %ld Duplikate: %ld Warnungen: %ld" greek "ΕγγÏαφÎÏ‚: %ld Επαναλήψεις: %ld Î Ïοειδοποιήσεις: %ld" hun "Rekordok: %ld Duplikalva: %ld Warnings: %ld" ita "Records: %ld Duplicati: %ld Avvertimenti: %ld" - jpn "レコード数: %ld é‡è¤‡æ•°: %ld Warnings: %ld" + jpn "レコード数: %ld é‡è¤‡æ•°: %ld è¦å‘Š: %ld" kor "ë ˆì½”ë“œ: %ldê°œ 중복: %ldê°œ ê²½ê³ : %ldê°œ" nor "Poster: %ld Like: %ld Advarsler: %ld" norwegian-ny "Postar: %ld Like: %ld Ã…tvaringar: %ld" @@ -2218,25 +2118,21 @@ ER_INSERT_INFO spa "Registros: %ld Duplicados: %ld Peligros: %ld" swe "Rader: %ld Dubletter: %ld Varningar: %ld" ukr "ЗапиÑів: %ld Дублікатів: %ld ЗаÑтережень: %ld" -ER_UPDATE_TABLE_USED - eng "You can't specify target table '%-.192s' for update in FROM clause" - ger "Die Verwendung der zu aktualisierenden Zieltabelle '%-.192s' ist in der FROM-Klausel nicht zulässig." - rus "Ðе допуÑкаетÑÑ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ðµ таблицы '%-.192s' в ÑпиÑке таблиц FROM Ð´Ð»Ñ Ð²Ð½ÐµÑÐµÐ½Ð¸Ñ Ð² нее изменений" - swe "INSERT-table '%-.192s' fÃ¥r inte finnas i FROM tabell-listan" - ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' що змінюєтьÑÑ Ð½Ðµ дозволена у переліку таблиць FROM" +ER_UPDATE_TABLE_USED + eng "Table '%-.192s' is specified twice, both as a target for '%s' and as a separate source for data" + swe "Table '%-.192s' är använd tvÃ¥ gÃ¥nger. BÃ¥de för '%s' och för att hämta data" ER_NO_SUCH_THREAD - cze "Nezn-Bámá identifikace threadu: %lu" + cze "Neznámá identifikace threadu: %lu" dan "Ukendt trÃ¥d id: %lu" nla "Onbekend thread id: %lu" eng "Unknown thread id: %lu" - jps "thread id: %lu ã¯ã‚りã¾ã›ã‚“", est "Tundmatu lõim: %lu" fre "Numéro de tâche inconnu: %lu" ger "Unbekannte Thread-ID: %lu" greek "Αγνωστο thread id: %lu" hun "Ervenytelen szal (thread) id: %lu" ita "Thread id: %lu sconosciuto" - jpn "thread id: %lu ã¯ã‚りã¾ã›ã‚“" + jpn "䏿˜Žãªã‚¹ãƒ¬ãƒƒãƒ‰IDã§ã™: %lu" kor "알수 없는 ì“°ë ˆë“œ id: %lu" nor "Ukjent trÃ¥d id: %lu" norwegian-ny "Ukjent trÃ¥d id: %lu" @@ -2250,18 +2146,17 @@ ER_NO_SUCH_THREAD swe "Finns ingen trÃ¥d med id %lu" ukr "Ðевідомий ідентифікатор гілки: %lu" ER_KILL_DENIED_ERROR - cze "Nejste vlastn-BÃkem threadu %lu" + cze "Nejste vlastnÃkem threadu %lu" dan "Du er ikke ejer af trÃ¥den %lu" nla "U bent geen bezitter van thread %lu" eng "You are not owner of thread %lu" - jps "thread %lu ã®ã‚ªãƒ¼ãƒŠãƒ¼ã§ã¯ã‚りã¾ã›ã‚“", est "Ei ole lõime %lu omanik" fre "Vous n'êtes pas propriétaire de la tâche no: %lu" ger "Sie sind nicht Eigentümer von Thread %lu" greek "Δεν είσθε owner του thread %lu" hun "A %lu thread-nek mas a tulajdonosa" ita "Utente non proprietario del thread %lu" - jpn "thread %lu ã®ã‚ªãƒ¼ãƒŠãƒ¼ã§ã¯ã‚りã¾ã›ã‚“" + jpn "スレッド %lu ã®ã‚ªãƒ¼ãƒŠãƒ¼ã§ã¯ã‚りã¾ã›ã‚“。" kor "ì“°ë ˆë“œ(Thread) %luì˜ ì†Œìœ ìžê°€ 아닙니다." nor "Du er ikke eier av trÃ¥den %lu" norwegian-ny "Du er ikkje eigar av trÃ¥d %lu" @@ -2275,7 +2170,7 @@ ER_KILL_DENIED_ERROR swe "Du är inte ägare till trÃ¥d %lu" ukr "Ви не володар гілки %lu" ER_NO_TABLES_USED - cze "Nejsou pou-Bžity žádné tabulky" + cze "Nejsou použity žádné tabulky" dan "Ingen tabeller i brug" nla "Geen tabellen gebruikt." eng "No tables used" @@ -2285,6 +2180,7 @@ ER_NO_TABLES_USED greek "Δεν χÏησιμοποιήθηκαν πίνακες" hun "Nincs hasznalt tabla" ita "Nessuna tabella usata" + jpn "è¡¨ãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“。" kor "ì–´ë–¤ í…Œì´ë¸”ë„ ì‚¬ìš©ë˜ì§€ 않았습니다." nor "Ingen tabeller i bruk" norwegian-ny "Ingen tabellar i bruk" @@ -2298,7 +2194,7 @@ ER_NO_TABLES_USED swe "Inga tabeller angivna" ukr "Ðе викориÑтано таблиць" ER_TOO_BIG_SET - cze "P-BÅ™ÃliÅ¡ mnoho Å™etÄ›zců pro sloupec %-.192s a SET" + cze "PÅ™ÃliÅ¡ mnoho Å™etÄ›zců pro sloupec %-.192s a SET" dan "For mange tekststrenge til specifikationen af SET i kolonne %-.192s" nla "Teveel strings voor kolom %-.192s en SET" eng "Too many strings for column %-.192s and SET" @@ -2308,6 +2204,7 @@ ER_TOO_BIG_SET greek "ΠάÏα πολλά strings για το πεδίο %-.192s και SET" hun "Tul sok karakter: %-.192s es SET" ita "Troppe stringhe per la colonna %-.192s e la SET" + jpn "SETåž‹ã®åˆ— '%-.192s' ã®ãƒ¡ãƒ³ãƒãƒ¼ã®æ•°ãŒå¤šã™ãŽã¾ã™ã€‚" kor "칼럼 %-.192s와 SETì—서 스트ë§ì´ 너무 많습니다." nor "For mange tekststrenger kolonne %-.192s og SET" norwegian-ny "For mange tekststrengar felt %-.192s og SET" @@ -2321,7 +2218,7 @@ ER_TOO_BIG_SET swe "För mÃ¥nga alternativ till kolumn %-.192s för SET" ukr "Забагато Ñтрок Ð´Ð»Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ %-.192s та SET" ER_NO_UNIQUE_LOGFILE - cze "Nemohu vytvo-BÅ™it jednoznaÄné jméno logovacÃho souboru %-.200s.(1-999)\n" + cze "Nemohu vytvoÅ™it jednoznaÄné jméno logovacÃho souboru %-.200s.(1-999)\n" dan "Kan ikke lave unikt log-filnavn %-.200s.(1-999)\n" nla "Het is niet mogelijk een unieke naam te maken voor de logfile %-.200s.(1-999)\n" eng "Can't generate a unique log-filename %-.200s.(1-999)\n" @@ -2331,6 +2228,7 @@ ER_NO_UNIQUE_LOGFILE greek "ΑδÏνατη η δημιουÏγία unique log-filename %-.200s.(1-999)\n" hun "Egyedi log-filenev nem generalhato: %-.200s.(1-999)\n" ita "Impossibile generare un nome del file log unico %-.200s.(1-999)\n" + jpn "一æ„ãªãƒã‚°ãƒ•ァイルå %-.200s.(1-999) を生æˆã§ãã¾ã›ã‚“。\n" kor "Unique ë¡œê·¸í™”ì¼ '%-.200s'를 만들수 없습니다.(1-999)\n" nor "Kan ikke lage unikt loggfilnavn %-.200s.(1-999)\n" norwegian-ny "Kan ikkje lage unikt loggfilnavn %-.200s.(1-999)\n" @@ -2344,18 +2242,17 @@ ER_NO_UNIQUE_LOGFILE swe "Kan inte generera ett unikt filnamn %-.200s.(1-999)\n" ukr "Ðе можу згенерувати унікальне ім'Ñ log-файлу %-.200s.(1-999)\n" ER_TABLE_NOT_LOCKED_FOR_WRITE - cze "Tabulka '%-.192s' byla zam-BÄena s READ a nemůže být zmÄ›nÄ›na" + cze "Tabulka '%-.192s' byla zamÄena s READ a nemůže být zmÄ›nÄ›na" dan "Tabellen '%-.192s' var lÃ¥st med READ lÃ¥s og kan ikke opdateres" nla "Tabel '%-.192s' was gelocked met een lock om te lezen. Derhalve kunnen geen wijzigingen worden opgeslagen." eng "Table '%-.192s' was locked with a READ lock and can't be updated" - jps "Table '%-.192s' 㯠READ lock ã«ãªã£ã¦ã„ã¦ã€æ›´æ–°ã¯ã§ãã¾ã›ã‚“", est "Tabel '%-.192s' on lukustatud READ lukuga ning ei ole muudetav" fre "Table '%-.192s' verrouillée lecture (READ): modification impossible" ger "Tabelle '%-.192s' ist mit Lesesperre versehen und kann nicht aktualisiert werden" greek "Ο πίνακας '%-.192s' Îχει κλειδωθεί με READ lock και δεν επιτÏÎπονται αλλαγÎÏ‚" hun "A(z) '%-.192s' tabla zarolva lett (READ lock) es nem lehet frissiteni" ita "La tabella '%-.192s' e` soggetta a lock in lettura e non puo` essere aggiornata" - jpn "Table '%-.192s' 㯠READ lock ã«ãªã£ã¦ã„ã¦ã€æ›´æ–°ã¯ã§ãã¾ã›ã‚“" + jpn "表 '%-.192s' ã¯READãƒãƒƒã‚¯ã•れã¦ã„ã¦ã€æ›´æ–°ã§ãã¾ã›ã‚“。" kor "í…Œì´ë¸” '%-.192s'는 READ ë½ì´ ìž ê²¨ìžˆì–´ì„œ ê°±ì‹ í• ìˆ˜ 없습니다." nor "Tabellen '%-.192s' var lÃ¥st med READ lÃ¥s og kan ikke oppdateres" norwegian-ny "Tabellen '%-.192s' var lÃ¥st med READ lÃ¥s og kan ikkje oppdaterast" @@ -2369,18 +2266,17 @@ ER_TABLE_NOT_LOCKED_FOR_WRITE swe "Tabell '%-.192s' kan inte uppdateras emedan den är lÃ¥st för läsning" ukr "Таблицю '%-.192s' заблоковано тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ, тому Ñ—Ñ— не можна оновити" ER_TABLE_NOT_LOCKED - cze "Tabulka '%-.192s' nebyla zam-BÄena s LOCK TABLES" + cze "Tabulka '%-.192s' nebyla zamÄena s LOCK TABLES" dan "Tabellen '%-.192s' var ikke lÃ¥st med LOCK TABLES" nla "Tabel '%-.192s' was niet gelocked met LOCK TABLES" eng "Table '%-.192s' was not locked with LOCK TABLES" - jps "Table '%-.192s' 㯠LOCK TABLES ã«ã‚ˆã£ã¦ãƒãƒƒã‚¯ã•れã¦ã„ã¾ã›ã‚“", est "Tabel '%-.192s' ei ole lukustatud käsuga LOCK TABLES" fre "Table '%-.192s' non verrouillée: utilisez LOCK TABLES" ger "Tabelle '%-.192s' wurde nicht mit LOCK TABLES gesperrt" greek "Ο πίνακας '%-.192s' δεν Îχει κλειδωθεί με LOCK TABLES" hun "A(z) '%-.192s' tabla nincs zarolva a LOCK TABLES-szel" ita "Non e` stato impostato il lock per la tabella '%-.192s' con LOCK TABLES" - jpn "Table '%-.192s' 㯠LOCK TABLES ã«ã‚ˆã£ã¦ãƒãƒƒã‚¯ã•れã¦ã„ã¾ã›ã‚“" + jpn "表 '%-.192s' 㯠LOCK TABLES ã§ãƒãƒƒã‚¯ã•れã¦ã„ã¾ã›ã‚“。" kor "í…Œì´ë¸” '%-.192s'는 LOCK TABLES ëª…ë ¹ìœ¼ë¡œ ìž ê¸°ì§€ 않았습니다." nor "Tabellen '%-.192s' var ikke lÃ¥st med LOCK TABLES" norwegian-ny "Tabellen '%-.192s' var ikkje lÃ¥st med LOCK TABLES" @@ -2394,7 +2290,7 @@ ER_TABLE_NOT_LOCKED swe "Tabell '%-.192s' är inte lÃ¥st med LOCK TABLES" ukr "Таблицю '%-.192s' не було блоковано з LOCK TABLES" ER_BLOB_CANT_HAVE_DEFAULT 42000 - cze "Blob polo-Bžka '%-.192s' nemůže mÃt defaultnà hodnotu" + cze "Blob položka '%-.192s' nemůže mÃt defaultnà hodnotu" dan "BLOB feltet '%-.192s' kan ikke have en standard værdi" nla "Blob veld '%-.192s' can geen standaardwaarde bevatten" eng "BLOB/TEXT column '%-.192s' can't have a default value" @@ -2404,7 +2300,7 @@ ER_BLOB_CANT_HAVE_DEFAULT 42000 greek "Τα Blob πεδία '%-.192s' δεν μποÏοÏν να Îχουν Ï€ÏοκαθοÏισμÎνες τιμÎÏ‚ (default value)" hun "A(z) '%-.192s' blob objektumnak nem lehet alapertelmezett erteke" ita "Il campo BLOB '%-.192s' non puo` avere un valore di default" - jpn "BLOB column '%-.192s' can't have a default value" + jpn "BLOB/TEXT 列 '%-.192s' ã«ã¯ãƒ‡ãƒ•ォルト値を指定ã§ãã¾ã›ã‚“。" kor "BLOB 칼럼 '%-.192s' 는 ë””í´íЏ ê°’ì„ ê°€ì§ˆ 수 없습니다." nor "Blob feltet '%-.192s' kan ikke ha en standard verdi" norwegian-ny "Blob feltet '%-.192s' kan ikkje ha ein standard verdi" @@ -2418,18 +2314,17 @@ ER_BLOB_CANT_HAVE_DEFAULT 42000 swe "BLOB fält '%-.192s' kan inte ha ett DEFAULT-värde" ukr "Стовбець BLOB '%-.192s' не може мати Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾ замовчуванню" ER_WRONG_DB_NAME 42000 - cze "Nep-BÅ™Ãpustné jméno databáze '%-.100s'" + cze "NepÅ™Ãpustné jméno databáze '%-.100s'" dan "Ugyldigt database navn '%-.100s'" nla "Databasenaam '%-.100s' is niet getoegestaan" eng "Incorrect database name '%-.100s'" - jps "指定ã—㟠database å '%-.100s' ãŒé–“é•ã£ã¦ã„ã¾ã™", est "Vigane andmebaasi nimi '%-.100s'" fre "Nom de base de donnée illégal: '%-.100s'" ger "Unerlaubter Datenbankname '%-.100s'" greek "Λάθος όνομα βάσης δεδομÎνων '%-.100s'" hun "Hibas adatbazisnev: '%-.100s'" ita "Nome database errato '%-.100s'" - jpn "指定ã—㟠database å '%-.100s' ãŒé–“é•ã£ã¦ã„ã¾ã™" + jpn "データベースå '%-.100s' ã¯ä¸æ£ã§ã™ã€‚" kor "'%-.100s' ë°ì´íƒ€ë² ì´ìŠ¤ì˜ ì´ë¦„ì´ ë¶€ì •í™•í•©ë‹ˆë‹¤." nor "Ugyldig database navn '%-.100s'" norwegian-ny "Ugyldig database namn '%-.100s'" @@ -2443,18 +2338,17 @@ ER_WRONG_DB_NAME 42000 swe "Felaktigt databasnamn '%-.100s'" ukr "Ðевірне ім'Ñ Ð±Ð°Ð·Ð¸ данних '%-.100s'" ER_WRONG_TABLE_NAME 42000 - cze "Nep-BÅ™Ãpustné jméno tabulky '%-.100s'" + cze "NepÅ™Ãpustné jméno tabulky '%-.100s'" dan "Ugyldigt tabel navn '%-.100s'" nla "Niet toegestane tabelnaam '%-.100s'" eng "Incorrect table name '%-.100s'" - jps "指定ã—㟠table å '%-.100s' ã¯ã¾ã¡ãŒã£ã¦ã„ã¾ã™", est "Vigane tabeli nimi '%-.100s'" fre "Nom de table illégal: '%-.100s'" ger "Unerlaubter Tabellenname '%-.100s'" greek "Λάθος όνομα πίνακα '%-.100s'" hun "Hibas tablanev: '%-.100s'" ita "Nome tabella errato '%-.100s'" - jpn "指定ã—㟠table å '%-.100s' ã¯ã¾ã¡ãŒã£ã¦ã„ã¾ã™" + jpn "表å '%-.100s' ã¯ä¸æ£ã§ã™ã€‚" kor "'%-.100s' í…Œì´ë¸” ì´ë¦„ì´ ë¶€ì •í™•í•©ë‹ˆë‹¤." nor "Ugyldig tabell navn '%-.100s'" norwegian-ny "Ugyldig tabell namn '%-.100s'" @@ -2468,7 +2362,7 @@ ER_WRONG_TABLE_NAME 42000 swe "Felaktigt tabellnamn '%-.100s'" ukr "Ðевірне ім'Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ– '%-.100s'" ER_TOO_BIG_SELECT 42000 - cze "Zadan-Bý SELECT by procházel pÅ™ÃliÅ¡ mnoho záznamů a trval velmi dlouho. Zkontrolujte tvar WHERE a je-li SELECT v pořádku, použijte SET SQL_BIG_SELECTS=1" + cze "Zadaný SELECT by procházel pÅ™ÃliÅ¡ mnoho záznamů a trval velmi dlouho. Zkontrolujte tvar WHERE a je-li SELECT v pořádku, použijte SET SQL_BIG_SELECTS=1" dan "SELECT ville undersøge for mange poster og ville sandsynligvis tage meget lang tid. Undersøg WHERE delen og brug SET SQL_BIG_SELECTS=1 hvis udtrykket er korrekt" nla "Het SELECT-statement zou te veel records analyseren en dus veel tijd in beslagnemen. Kijk het WHERE-gedeelte van de query na en kies SET SQL_BIG_SELECTS=1 als het stament in orde is." eng "The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET MAX_JOIN_SIZE=# if the SELECT is okay" @@ -2478,6 +2372,7 @@ ER_TOO_BIG_SELECT 42000 greek "Το SELECT θα εξετάσει μεγάλο αÏιθμό εγγÏαφών και πιθανώς θα καθυστεÏήσει. ΠαÏακαλώ εξετάστε τις παÏαμÎÏ„Ïους του WHERE και χÏησιμοποιείστε SET SQL_BIG_SELECTS=1 αν το SELECT είναι σωστό" hun "A SELECT tul sok rekordot fog megvizsgalni es nagyon sokaig fog tartani. Ellenorizze a WHERE-t es hasznalja a SET SQL_BIG_SELECTS=1 beallitast, ha a SELECT okay" ita "La SELECT dovrebbe esaminare troppi record e usare troppo tempo. Controllare la WHERE e usa SET SQL_BIG_SELECTS=1 se e` tutto a posto." + jpn "SELECTãŒMAX_JOIN_SIZEã‚’è¶…ãˆã‚‹è¡Œæ•°ã‚’処ç†ã—ã¾ã—ãŸã€‚WHEREå¥ã‚’確èªã—ã€SELECTæ–‡ã«å•題ãŒãªã‘れã°ã€ SET SQL_BIG_SELECTS=1 ã¾ãŸã¯ SET MAX_JOIN_SIZE=# を使用ã—ã¦ä¸‹ã•ã„。" kor "SELECT ëª…ë ¹ì—서 너무 ë§Žì€ ë ˆì½”ë“œë¥¼ 찾기 ë•Œë¬¸ì— ë§Žì€ ì‹œê°„ì´ ì†Œìš”ë©ë‹ˆë‹¤. ë”°ë¼ì„œ WHERE ë¬¸ì„ ì 검하거나, 만약 SELECTê°€ okë˜ë©´ SET SQL_BIG_SELECTS=1 ì˜µì…˜ì„ ì‚¬ìš©í•˜ì„¸ìš”." nor "SELECT ville undersøke for mange poster og ville sannsynligvis ta veldig lang tid. Undersøk WHERE klausulen og bruk SET SQL_BIG_SELECTS=1 om SELECTen er korrekt" norwegian-ny "SELECT ville undersøkje for mange postar og ville sannsynligvis ta veldig lang tid. Undersøk WHERE klausulen og bruk SET SQL_BIG_SELECTS=1 om SELECTen er korrekt" @@ -2491,7 +2386,7 @@ ER_TOO_BIG_SELECT 42000 swe "Den angivna frÃ¥gan skulle läsa mer än MAX_JOIN_SIZE rader. Kontrollera din WHERE och använd SET SQL_BIG_SELECTS=1 eller SET MAX_JOIN_SIZE=# ifall du vill hantera stora joins" ukr "Запиту SELECT потрібно обробити багато запиÑів, що, певне, займе дуже багато чаÑу. Перевірте ваше WHERE та викориÑтовуйте SET SQL_BIG_SELECTS=1, Ñкщо цей запит SELECT Ñ” вірним" ER_UNKNOWN_ERROR - cze "Nezn-Bámá chyba" + cze "Neznámá chyba" dan "Ukendt fejl" nla "Onbekende Fout" eng "Unknown error" @@ -2501,6 +2396,7 @@ ER_UNKNOWN_ERROR greek "Î ÏοÎκυψε άγνωστο λάθος" hun "Ismeretlen hiba" ita "Errore sconosciuto" + jpn "䏿˜Žãªã‚¨ãƒ©ãƒ¼" kor "알수 없는 ì—러입니다." nor "Ukjent feil" norwegian-ny "Ukjend feil" @@ -2510,10 +2406,10 @@ ER_UNKNOWN_ERROR serbian "Nepoznata greÅ¡ka" slo "Neznámá chyba" spa "Error desconocido" - swe "Oidentifierat fel" + swe "Okänt fel" ukr "Ðевідома помилка" ER_UNKNOWN_PROCEDURE 42000 - cze "Nezn-Bámá procedura %-.192s" + cze "Neznámá procedura %-.192s" dan "Ukendt procedure %-.192s" nla "Onbekende procedure %-.192s" eng "Unknown procedure '%-.192s'" @@ -2523,6 +2419,7 @@ ER_UNKNOWN_PROCEDURE 42000 greek "Αγνωστη διαδικασία '%-.192s'" hun "Ismeretlen eljaras: '%-.192s'" ita "Procedura '%-.192s' sconosciuta" + jpn "'%-.192s' ã¯ä¸æ˜Žãªãƒ—ãƒã‚·ãƒ¼ã‚¸ãƒ£ã§ã™ã€‚" kor "알수 없는 수행문 : '%-.192s'" nor "Ukjent prosedyre %-.192s" norwegian-ny "Ukjend prosedyre %-.192s" @@ -2536,7 +2433,7 @@ ER_UNKNOWN_PROCEDURE 42000 swe "Okänd procedur: %-.192s" ukr "Ðевідома процедура '%-.192s'" ER_WRONG_PARAMCOUNT_TO_PROCEDURE 42000 - cze "Chybn-Bý poÄet parametrů procedury %-.192s" + cze "Chybný poÄet parametrů procedury %-.192s" dan "Forkert antal parametre til proceduren %-.192s" nla "Foutief aantal parameters doorgegeven aan procedure %-.192s" eng "Incorrect parameter count to procedure '%-.192s'" @@ -2546,6 +2443,7 @@ ER_WRONG_PARAMCOUNT_TO_PROCEDURE 42000 greek "Λάθος αÏιθμός παÏαμÎÏ„Ïων στη διαδικασία '%-.192s'" hun "Rossz parameter a(z) '%-.192s'eljaras szamitasanal" ita "Numero di parametri errato per la procedura '%-.192s'" + jpn "プãƒã‚·ãƒ¼ã‚¸ãƒ£ '%-.192s' ã¸ã®ãƒ‘ラメータ数ãŒä¸æ£ã§ã™ã€‚" kor "'%-.192s' ìˆ˜í–‰ë¬¸ì— ëŒ€í•œ ë¶€ì •í™•í•œ 파ë¼ë©”í„°" nor "Feil parameter antall til prosedyren %-.192s" norwegian-ny "Feil parameter tal til prosedyra %-.192s" @@ -2559,7 +2457,7 @@ ER_WRONG_PARAMCOUNT_TO_PROCEDURE 42000 swe "Felaktigt antal parametrar till procedur %-.192s" ukr "Хибна кількіÑть параметрів процедури '%-.192s'" ER_WRONG_PARAMETERS_TO_PROCEDURE - cze "Chybn-Bé parametry procedury %-.192s" + cze "Chybné parametry procedury %-.192s" dan "Forkert(e) parametre til proceduren %-.192s" nla "Foutieve parameters voor procedure %-.192s" eng "Incorrect parameters to procedure '%-.192s'" @@ -2569,6 +2467,7 @@ ER_WRONG_PARAMETERS_TO_PROCEDURE greek "Λάθος παÏάμετÏοι στην διαδικασία '%-.192s'" hun "Rossz parameter a(z) '%-.192s' eljarasban" ita "Parametri errati per la procedura '%-.192s'" + jpn "プãƒã‚·ãƒ¼ã‚¸ãƒ£ '%-.192s' ã¸ã®ãƒ‘ラメータãŒä¸æ£ã§ã™ã€‚" kor "'%-.192s' ìˆ˜í–‰ë¬¸ì— ëŒ€í•œ ë¶€ì •í™•í•œ 파ë¼ë©”í„°" nor "Feil parametre til prosedyren %-.192s" norwegian-ny "Feil parameter til prosedyra %-.192s" @@ -2582,7 +2481,7 @@ ER_WRONG_PARAMETERS_TO_PROCEDURE swe "Felaktiga parametrar till procedur %-.192s" ukr "Хибний параметер процедури '%-.192s'" ER_UNKNOWN_TABLE 42S02 - cze "Nezn-Bámá tabulka '%-.192s' v %-.32s" + cze "Neznámá tabulka '%-.192s' v %-.32s" dan "Ukendt tabel '%-.192s' i %-.32s" nla "Onbekende tabel '%-.192s' in %-.32s" eng "Unknown table '%-.192s' in %-.32s" @@ -2592,7 +2491,7 @@ ER_UNKNOWN_TABLE 42S02 greek "Αγνωστος πίνακας '%-.192s' σε %-.32s" hun "Ismeretlen tabla: '%-.192s' %-.32s-ban" ita "Tabella '%-.192s' sconosciuta in %-.32s" - jpn "Unknown table '%-.192s' in %-.32s" + jpn "'%-.192s' 㯠%-.32s ã§ã¯ä¸æ˜Žãªè¡¨ã§ã™ã€‚" kor "알수 없는 í…Œì´ë¸” '%-.192s' (ë°ì´íƒ€ë² ì´ìФ %-.32s)" nor "Ukjent tabell '%-.192s' i %-.32s" norwegian-ny "Ukjend tabell '%-.192s' i %-.32s" @@ -2606,7 +2505,7 @@ ER_UNKNOWN_TABLE 42S02 swe "Okänd tabell '%-.192s' i '%-.32s'" ukr "Ðевідома Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' у %-.32s" ER_FIELD_SPECIFIED_TWICE 42000 - cze "Polo-Bžka '%-.192s' je zadána dvakrát" + cze "Položka '%-.192s' je zadána dvakrát" dan "Feltet '%-.192s' er anvendt to gange" nla "Veld '%-.192s' is dubbel gespecificeerd" eng "Column '%-.192s' specified twice" @@ -2616,6 +2515,7 @@ ER_FIELD_SPECIFIED_TWICE 42000 greek "Το πεδίο '%-.192s' Îχει οÏισθεί δÏο φοÏÎÏ‚" hun "A(z) '%-.192s' mezot ketszer definialta" ita "Campo '%-.192s' specificato 2 volte" + jpn "列 '%-.192s' ã¯2回指定ã•れã¦ã„ã¾ã™ã€‚" kor "칼럼 '%-.192s'는 ë‘번 ì •ì˜ë˜ì–´ 있ì니다." nor "Feltet '%-.192s' er spesifisert to ganger" norwegian-ny "Feltet '%-.192s' er spesifisert to gangar" @@ -2629,7 +2529,7 @@ ER_FIELD_SPECIFIED_TWICE 42000 swe "Fält '%-.192s' är redan använt" ukr "Стовбець '%-.192s' зазначено двічі" ER_INVALID_GROUP_FUNC_USE - cze "Nespr-Bávné použità funkce group" + cze "Nesprávné použità funkce group" dan "Forkert brug af grupperings-funktion" nla "Ongeldig gebruik van GROUP-functie" eng "Invalid use of group function" @@ -2639,6 +2539,7 @@ ER_INVALID_GROUP_FUNC_USE greek "ΕσφαλμÎνη χÏήση της group function" hun "A group funkcio ervenytelen hasznalata" ita "Uso non valido di una funzione di raggruppamento" + jpn "集計関数ã®ä½¿ç”¨æ–¹æ³•ãŒä¸æ£ã§ã™ã€‚" kor "ìž˜ëª»ëœ ê·¸ë£¹ 함수를 사용하였습니다." por "Uso inválido de função de agrupamento (GROUP)" rum "Folosire incorecta a functiei group" @@ -2649,7 +2550,7 @@ ER_INVALID_GROUP_FUNC_USE swe "Felaktig användning av SQL grupp function" ukr "Хибне викориÑÑ‚Ð°Ð½Ð½Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ— групуваннÑ" ER_UNSUPPORTED_EXTENSION 42000 - cze "Tabulka '%-.192s' pou-BžÃvá rozÅ¡ÃÅ™enÃ, které v této verzi MariaDB nenÃ" + cze "Tabulka '%-.192s' použÃvá rozÅ¡ÃÅ™enÃ, které v této verzi MySQL nenÃ" dan "Tabellen '%-.192s' bruger et filtypenavn som ikke findes i denne MariaDB version" nla "Tabel '%-.192s' gebruikt een extensie, die niet in deze MariaDB-versie voorkomt." eng "Table '%-.192s' uses an extension that doesn't exist in this MariaDB version" @@ -2659,6 +2560,7 @@ ER_UNSUPPORTED_EXTENSION 42000 greek "Ο πίνακς '%-.192s' χÏησιμοποιεί κάποιο extension που δεν υπάÏχει στην Îκδοση αυτή της MariaDB" hun "A(z) '%-.192s' tabla olyan bovitest hasznal, amely nem letezik ebben a MariaDB versioban." ita "La tabella '%-.192s' usa un'estensione che non esiste in questa versione di MariaDB" + jpn "表 '%-.192s' ã¯ã€ã“ã®MySQLãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ã¯ç„¡ã„機能を使用ã—ã¦ã„ã¾ã™ã€‚" kor "í…Œì´ë¸” '%-.192s'는 í™•ìž¥ëª…ë ¹ì„ ì´ìš©í•˜ì§€ë§Œ í˜„ìž¬ì˜ MariaDB ë²„ì ¼ì—서는 존재하지 않습니다." nor "Table '%-.192s' uses a extension that doesn't exist in this MariaDB version" norwegian-ny "Table '%-.192s' uses a extension that doesn't exist in this MariaDB version" @@ -2672,18 +2574,17 @@ ER_UNSUPPORTED_EXTENSION 42000 swe "Tabell '%-.192s' har en extension som inte finns i denna version av MariaDB" ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' викориÑтовує розширеннÑ, що не Ñ–Ñнує у цій верÑÑ–Ñ— MariaDB" ER_TABLE_MUST_HAVE_COLUMNS 42000 - cze "Tabulka mus-Bà mÃt alespoň jeden sloupec" + cze "Tabulka musà mÃt alespoň jeden sloupec" dan "En tabel skal have mindst een kolonne" nla "Een tabel moet minstens 1 kolom bevatten" eng "A table must have at least 1 column" - jps "ãƒ†ãƒ¼ãƒ–ãƒ«ã¯æœ€ä½Ž 1 個㮠column ãŒå¿…è¦ã§ã™", est "Tabelis peab olema vähemalt üks tulp" fre "Une table doit comporter au moins une colonne" ger "Eine Tabelle muss mindestens eine Spalte besitzen" greek "Ενας πίνακας Ï€ÏÎπει να Îχει τουλάχιστον Îνα πεδίο" hun "A tablanak legalabb egy oszlopot tartalmazni kell" ita "Una tabella deve avere almeno 1 colonna" - jpn "ãƒ†ãƒ¼ãƒ–ãƒ«ã¯æœ€ä½Ž 1 個㮠column ãŒå¿…è¦ã§ã™" + jpn "表ã«ã¯æœ€ä½Žã§ã‚‚1個ã®åˆ—ãŒå¿…è¦ã§ã™ã€‚" kor "í•˜ë‚˜ì˜ í…Œì´ë¸”ì—서는 ì ì–´ë„ í•˜ë‚˜ì˜ ì¹¼ëŸ¼ì´ ì¡´ìž¬í•˜ì—¬ì•¼ 합니다." por "Uma tabela tem que ter pelo menos uma (1) coluna" rum "O tabela trebuie sa aiba cel putin o coloana" @@ -2694,18 +2595,17 @@ ER_TABLE_MUST_HAVE_COLUMNS 42000 swe "Tabeller mÃ¥ste ha minst 1 kolumn" ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ Ð¿Ð¾Ð²Ð¸Ð½Ð½Ð° мати хочаб один Ñтовбець" ER_RECORD_FILE_FULL - cze "Tabulka '%-.192s' je pln-Bá" + cze "Tabulka '%-.192s' je plná" dan "Tabellen '%-.192s' er fuld" nla "De tabel '%-.192s' is vol" eng "The table '%-.192s' is full" - jps "table '%-.192s' ã¯ã„ã£ã±ã„ã§ã™", est "Tabel '%-.192s' on täis" fre "La table '%-.192s' est pleine" ger "Tabelle '%-.192s' ist voll" greek "Ο πίνακας '%-.192s' είναι γεμάτος" hun "A '%-.192s' tabla megtelt" ita "La tabella '%-.192s' e` piena" - jpn "table '%-.192s' ã¯ã„ã£ã±ã„ã§ã™" + jpn "表 '%-.192s' ã¯æº€æ¯ã§ã™ã€‚" kor "í…Œì´ë¸” '%-.192s'ê°€ full났습니다. " por "Tabela '%-.192s' está cheia" rum "Tabela '%-.192s' e plina" @@ -2716,18 +2616,17 @@ ER_RECORD_FILE_FULL swe "Tabellen '%-.192s' är full" ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s' заповнена" ER_UNKNOWN_CHARACTER_SET 42000 - cze "Nezn-Bámá znaková sada: '%-.64s'" + cze "Neznámá znaková sada: '%-.64s'" dan "Ukendt tegnsæt: '%-.64s'" nla "Onbekende character set: '%-.64s'" eng "Unknown character set: '%-.64s'" - jps "character set '%-.64s' ã¯ã‚µãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“", est "Vigane kooditabel '%-.64s'" fre "Jeu de caractères inconnu: '%-.64s'" ger "Unbekannter Zeichensatz: '%-.64s'" greek "Αγνωστο character set: '%-.64s'" hun "Ervenytelen karakterkeszlet: '%-.64s'" ita "Set di caratteri '%-.64s' sconosciuto" - jpn "character set '%-.64s' ã¯ã‚µãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“" + jpn "䏿˜Žãªæ–‡å—コードセット: '%-.64s'" kor "알수없는 언어 Set: '%-.64s'" por "Conjunto de caracteres '%-.64s' desconhecido" rum "Set de caractere invalid: '%-.64s'" @@ -2738,18 +2637,17 @@ ER_UNKNOWN_CHARACTER_SET 42000 swe "Okänd teckenuppsättning: '%-.64s'" ukr "Ðевідома кодова таблицÑ: '%-.64s'" ER_TOO_MANY_TABLES - cze "P-BÅ™ÃliÅ¡ mnoho tabulek, MariaDB jich může mÃt v joinu jen %d" + cze "PÅ™ÃliÅ¡ mnoho tabulek, MySQL jich může mÃt v joinu jen %d" dan "For mange tabeller. MariaDB kan kun bruge %d tabeller i et join" nla "Teveel tabellen. MariaDB kan slechts %d tabellen in een join bevatten" eng "Too many tables; MariaDB can only use %d tables in a join" - jps "テーブルãŒå¤šã™ãŽã¾ã™; MariaDB can only use %d tables in a join", est "Liiga palju tabeleid. MariaDB suudab JOINiga ühendada kuni %d tabelit" fre "Trop de tables. MariaDB ne peut utiliser que %d tables dans un JOIN" ger "Zu viele Tabellen. MariaDB kann in einem Join maximal %d Tabellen verwenden" greek "Î Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿Ï‚ αÏιθμός πινάκων. Η MariaDB μποÏεί να χÏησιμοποιήσει %d πίνακες σε διαδικασία join" hun "Tul sok tabla. A MariaDB csak %d tablat tud kezelni osszefuzeskor" ita "Troppe tabelle. MariaDB puo` usare solo %d tabelle in una join" - jpn "テーブルãŒå¤šã™ãŽã¾ã™; MariaDB can only use %d tables in a join" + jpn "表ãŒå¤šã™ãŽã¾ã™ã€‚MySQLãŒJOINã§ãる表㯠%d 個ã¾ã§ã§ã™ã€‚" kor "너무 ë§Žì€ í…Œì´ë¸”ì´ Joinë˜ì—ˆìŠµë‹ˆë‹¤. MariaDBì—서는 JOIN시 %dê°œì˜ í…Œì´ë¸”ë§Œ ì‚¬ìš©í• ìˆ˜ 있습니다." por "Tabelas demais. O MariaDB pode usar somente %d tabelas em uma junção (JOIN)" rum "Prea multe tabele. MariaDB nu poate folosi mai mult de %d tabele intr-un join" @@ -2760,18 +2658,17 @@ ER_TOO_MANY_TABLES swe "För mÃ¥nga tabeller. MariaDB can ha högst %d tabeller i en och samma join" ukr "Забагато таблиць. MariaDB може викориÑтовувати лише %d таблиць у об'єднанні" ER_TOO_MANY_FIELDS - cze "P-BÅ™ÃliÅ¡ mnoho položek" + cze "PÅ™ÃliÅ¡ mnoho položek" dan "For mange felter" nla "Te veel velden" eng "Too many columns" - jps "column ãŒå¤šã™ãŽã¾ã™", est "Liiga palju tulpasid" fre "Trop de champs" ger "Zu viele Felder" greek "Î Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿Ï‚ αÏιθμός πεδίων" hun "Tul sok mezo" ita "Troppi campi" - jpn "column ãŒå¤šã™ãŽã¾ã™" + jpn "列ãŒå¤šã™ãŽã¾ã™ã€‚" kor "ì¹¼ëŸ¼ì´ ë„ˆë¬´ 많습니다." por "Colunas demais" rum "Prea multe coloane" @@ -2782,18 +2679,17 @@ ER_TOO_MANY_FIELDS swe "För mÃ¥nga fält" ukr "Забагато Ñтовбців" ER_TOO_BIG_ROWSIZE 42000 - cze "-BŘádek je pÅ™ÃliÅ¡ velký. Maximálnà velikost řádku, nepoÄÃtaje položky blob, je %ld. MusÃte zmÄ›nit nÄ›které položky na blob" + cze "Řádek je pÅ™ÃliÅ¡ velký. Maximálnà velikost řádku, nepoÄÃtaje položky blob, je %ld. MusÃte zmÄ›nit nÄ›které položky na blob" dan "For store poster. Max post størrelse, uden BLOB's, er %ld. Du mÃ¥ lave nogle felter til BLOB's" nla "Rij-grootte is groter dan toegestaan. Maximale rij grootte, blobs niet meegeteld, is %ld. U dient sommige velden in blobs te veranderen." - eng "Row size too large. The maximum row size for the used table type, not counting BLOBs, is %ld. You have to change some columns to TEXT or BLOBs" - jps "row size ãŒå¤§ãã™ãŽã¾ã™. BLOB ã‚’å«ã¾ãªã„å ´åˆã® row size ã®æœ€å¤§ã¯ %ld ã§ã™. ã„ãã¤ã‹ã® field ã‚’ BLOB ã«å¤‰ãˆã¦ãã ã•ã„.", + eng "Row size too large. The maximum row size for the used table type, not counting BLOBs, is %ld. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs" est "Liiga pikk kirje. Kirje maksimumpikkus arvestamata BLOB-tüüpi välju on %ld. Muuda mõned väljad BLOB-tüüpi väljadeks" fre "Ligne trop grande. Le taille maximale d'une ligne, sauf les BLOBs, est %ld. Changez le type de quelques colonnes en BLOB" ger "Zeilenlänge zu groß. Die maximale Zeilenlänge für den verwendeten Tabellentyp (ohne BLOB-Felder) beträgt %ld. Einige Felder müssen in BLOB oder TEXT umgewandelt werden" greek "Î Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿ μÎγεθος εγγÏαφής. Το μÎγιστο μÎγεθος εγγÏαφής, χωÏίς να υπολογίζονται τα blobs, είναι %ld. Î ÏÎπει να οÏίσετε κάποια πεδία σαν blobs" hun "Tul nagy sormeret. A maximalis sormeret (nem szamolva a blob objektumokat) %ld. Nehany mezot meg kell valtoztatnia" ita "Riga troppo grande. La massima grandezza di una riga, non contando i BLOB, e` %ld. Devi cambiare alcuni campi in BLOB" - jpn "row size ãŒå¤§ãã™ãŽã¾ã™. BLOB ã‚’å«ã¾ãªã„å ´åˆã® row size ã®æœ€å¤§ã¯ %ld ã§ã™. ã„ãã¤ã‹ã® field ã‚’ BLOB ã«å¤‰ãˆã¦ãã ã•ã„." + jpn "行サイズãŒå¤§ãã™ãŽã¾ã™ã€‚ã“ã®è¡¨ã®æœ€å¤§è¡Œã‚µã‚¤ã‚ºã¯ BLOB ã‚’å«ã¾ãšã« %ld ã§ã™ã€‚æ ¼ç´æ™‚ã®ã‚ªãƒ¼ãƒãƒ¼ãƒ˜ãƒƒãƒ‰ã‚‚å«ã¾ã‚Œã¾ã™(マニュアルを確èªã—ã¦ãã ã•ã„)。列をTEXTã¾ãŸã¯BLOBã«å¤‰æ›´ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" kor "너무 í° row 사ì´ì¦ˆìž…니다. BLOB를 계산하지 ì•Šê³ ìµœëŒ€ row 사ì´ì¦ˆëŠ” %ld입니다. ì–¼ë§ˆê°„ì˜ í•„ë“œë“¤ì„ BLOB로 바꾸셔야 ê² êµ°ìš”.." por "Tamanho de linha grande demais. O máximo tamanho de linha, não contando BLOBs, é %ld. Você tem que mudar alguns campos para BLOBs" rum "Marimea liniei (row) prea mare. Marimea maxima a liniei, excluzind BLOB-urile este de %ld. Trebuie sa schimbati unele cimpuri in BLOB-uri" @@ -2804,17 +2700,16 @@ ER_TOO_BIG_ROWSIZE 42000 swe "För stor total radlängd. Den högst tillÃ¥tna radlängden, förutom BLOBs, är %ld. Ändra nÃ¥gra av dina fält till BLOB" ukr "Задовга Ñтрока. Ðайбільшою довжиною Ñтроки, не рахуючи BLOB, Ñ” %ld. Вам потрібно привеÑти деÑкі Ñтовбці до типу BLOB" ER_STACK_OVERRUN - cze "P-BÅ™eteÄenà zásobnÃku threadu: použito %ld z %ld. Použijte 'mysqld --thread_stack=#' k zadánà vÄ›tÅ¡Ãho zásobnÃku" + cze "PÅ™eteÄenà zásobnÃku threadu: použito %ld z %ld. Použijte 'mysqld --thread_stack=#' k zadánà vÄ›tÅ¡Ãho zásobnÃku" dan "Thread stack brugt: Brugt: %ld af en %ld stak. Brug 'mysqld --thread_stack=#' for at allokere en større stak om nødvendigt" nla "Thread stapel overrun: Gebruikte: %ld van een %ld stack. Gebruik 'mysqld --thread_stack=#' om een grotere stapel te definieren (indien noodzakelijk)." eng "Thread stack overrun: Used: %ld of a %ld stack. Use 'mysqld --thread_stack=#' to specify a bigger stack if needed" - jps "Thread stack overrun: Used: %ld of a %ld stack. ã‚¹ã‚¿ãƒƒã‚¯é ˜åŸŸã‚’å¤šãã¨ã‚ŠãŸã„å ´åˆã€'mysqld --thread_stack=#' ã¨æŒ‡å®šã—ã¦ãã ã•ã„", fre "Débordement de la pile des tâches (Thread stack). Utilisées: %ld pour une pile de %ld. Essayez 'mysqld --thread_stack=#' pour indiquer une plus grande valeur" ger "Thread-Stack-Überlauf. Benutzt: %ld von %ld Stack. 'mysqld --thread_stack=#' verwenden, um bei Bedarf einen größeren Stack anzulegen" greek "Stack overrun στο thread: Used: %ld of a %ld stack. ΠαÏακαλώ χÏησιμοποιείστε 'mysqld --thread_stack=#' για να οÏίσετε Îνα μεγαλÏτεÏο stack αν χÏειάζεται" hun "Thread verem tullepes: Used: %ld of a %ld stack. Hasznalja a 'mysqld --thread_stack=#' nagyobb verem definialasahoz" ita "Thread stack overrun: Usati: %ld di uno stack di %ld. Usa 'mysqld --thread_stack=#' per specificare uno stack piu` grande." - jpn "Thread stack overrun: Used: %ld of a %ld stack. ã‚¹ã‚¿ãƒƒã‚¯é ˜åŸŸã‚’å¤šãã¨ã‚ŠãŸã„å ´åˆã€'mysqld --thread_stack=#' ã¨æŒ‡å®šã—ã¦ãã ã•ã„" + jpn "スレッドスタックä¸è¶³ã§ã™(使用: %ld ; サイズ: %ld)。必è¦ã«å¿œã˜ã¦ã€ã‚ˆã‚Šå¤§ãã„値㧠'mysqld --thread_stack=#' ã®æŒ‡å®šã‚’ã—ã¦ãã ã•ã„。" kor "ì“°ë ˆë“œ 스íƒì´ 넘쳤습니다. 사용: %ldê°œ 스íƒ: %ldê°œ. 만약 필요시 ë”í° ìŠ¤íƒì„ ì›í• 때ì—는 'mysqld --thread_stack=#' 를 ì •ì˜í•˜ì„¸ìš”" por "Estouro da pilha do 'thread'. Usados %ld de uma pilha de %ld. Use 'mysqld --thread_stack=#' para especificar uma pilha maior, se necessário" rum "Stack-ul thread-ului a fost depasit (prea mic): Folositi: %ld intr-un stack de %ld. Folositi 'mysqld --thread_stack=#' ca sa specifici un stack mai mare" @@ -2825,7 +2720,7 @@ ER_STACK_OVERRUN swe "TrÃ¥dstacken tog slut: Har använt %ld av %ld bytes. Använd 'mysqld --thread_stack=#' ifall du behöver en större stack" ukr "Стек гілок переповнено: ВикориÑтано: %ld з %ld. ВикориÑтовуйте 'mysqld --thread_stack=#' аби зазначити більший Ñтек, Ñкщо необхідно" ER_WRONG_OUTER_JOIN 42000 - cze "V OUTER JOIN byl nalezen k-BřÞový odkaz. Prověřte ON podmÃnky" + cze "V OUTER JOIN byl nalezen křÞový odkaz. Prověřte ON podmÃnky" dan "Krydsreferencer fundet i OUTER JOIN; check dine ON conditions" nla "Gekruiste afhankelijkheid gevonden in OUTER JOIN. Controleer uw ON-conditions" eng "Cross dependency found in OUTER JOIN; examine your ON conditions" @@ -2835,6 +2730,7 @@ ER_WRONG_OUTER_JOIN 42000 greek "Cross dependency βÏÎθηκε σε OUTER JOIN. ΠαÏακαλώ εξετάστε τις συνθήκες που θÎσατε στο ON" hun "Keresztfuggoseg van az OUTER JOIN-ban. Ellenorizze az ON felteteleket" ita "Trovata una dipendenza incrociata nella OUTER JOIN. Controlla le condizioni ON" + jpn "OUTER JOINã«ç›¸äº’ä¾å˜ãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸã€‚ONå¥ã®æ¡ä»¶ã‚’確èªã—ã¦ä¸‹ã•ã„。" por "Dependência cruzada encontrada em junção externa (OUTER JOIN); examine as condições utilizadas nas cláusulas 'ON'" rum "Dependinta incrucisata (cross dependency) gasita in OUTER JOIN. Examinati conditiile ON" rus "Ð’ OUTER JOIN обнаружена перекреÑÑ‚Ð½Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑть. Внимательно проанализируйте Ñвои уÑÐ»Ð¾Ð²Ð¸Ñ ON" @@ -2847,18 +2743,17 @@ ER_NULL_COLUMN_IN_INDEX 42000 eng "Table handler doesn't support NULL in given index. Please change column '%-.192s' to be NOT NULL or use another handler" swe "Tabell hanteraren kan inte indexera NULL kolumner för den givna index typen. Ändra '%-.192s' till NOT NULL eller använd en annan hanterare" ER_CANT_FIND_UDF - cze "Nemohu na-BÄÃst funkci '%-.192s'" + cze "Nemohu naÄÃst funkci '%-.192s'" dan "Kan ikke læse funktionen '%-.192s'" nla "Kan functie '%-.192s' niet laden" eng "Can't load function '%-.192s'" - jps "function '%-.192s' ã‚’ ãƒãƒ¼ãƒ‰ã§ãã¾ã›ã‚“", est "Ei suuda avada funktsiooni '%-.192s'" fre "Imposible de charger la fonction '%-.192s'" ger "Kann Funktion '%-.192s' nicht laden" greek "Δεν είναι δυνατή η διαδικασία load για τη συνάÏτηση '%-.192s'" hun "A(z) '%-.192s' fuggveny nem toltheto be" ita "Impossibile caricare la funzione '%-.192s'" - jpn "function '%-.192s' ã‚’ ãƒãƒ¼ãƒ‰ã§ãã¾ã›ã‚“" + jpn "関数 '%-.192s' ã‚’ãƒãƒ¼ãƒ‰ã§ãã¾ã›ã‚“。" kor "'%-.192s' 함수를 로드하지 못했습니다." por "Não pode carregar a função '%-.192s'" rum "Nu pot incarca functia '%-.192s'" @@ -2873,14 +2768,13 @@ ER_CANT_INITIALIZE_UDF dan "Kan ikke starte funktionen '%-.192s'; %-.80s" nla "Kan functie '%-.192s' niet initialiseren; %-.80s" eng "Can't initialize function '%-.192s'; %-.80s" - jps "function '%-.192s' ã‚’åˆæœŸåŒ–ã§ãã¾ã›ã‚“; %-.80s", est "Ei suuda algväärtustada funktsiooni '%-.192s'; %-.80s" fre "Impossible d'initialiser la fonction '%-.192s'; %-.80s" ger "Kann Funktion '%-.192s' nicht initialisieren: %-.80s" greek "Δεν είναι δυνατή η ÎναÏξη της συνάÏτησης '%-.192s'; %-.80s" hun "A(z) '%-.192s' fuggveny nem inicializalhato; %-.80s" ita "Impossibile inizializzare la funzione '%-.192s'; %-.80s" - jpn "function '%-.192s' ã‚’åˆæœŸåŒ–ã§ãã¾ã›ã‚“; %-.80s" + jpn "関数 '%-.192s' ã‚’åˆæœŸåŒ–ã§ãã¾ã›ã‚“。; %-.80s" kor "'%-.192s' 함수를 초기화 하지 못했습니다.; %-.80s" por "Não pode inicializar a função '%-.192s' - '%-.80s'" rum "Nu pot initializa functia '%-.192s'; %-.80s" @@ -2891,18 +2785,17 @@ ER_CANT_INITIALIZE_UDF swe "Kan inte initialisera funktionen '%-.192s'; '%-.80s'" ukr "Ðе можу ініціалізувати функцію '%-.192s'; %-.80s" ER_UDF_NO_PATHS - cze "Pro sd-BÃlenou knihovnu nejsou povoleny cesty" + cze "Pro sdÃlenou knihovnu nejsou povoleny cesty" dan "Angivelse af sti ikke tilladt for delt bibliotek" nla "Geen pad toegestaan voor shared library" eng "No paths allowed for shared library" - jps "shared library ã¸ã®ãƒ‘スãŒé€šã£ã¦ã„ã¾ã›ã‚“", est "Teegi nimes ei tohi olla kataloogi" fre "Chemin interdit pour les bibliothèques partagées" ger "Keine Pfade gestattet für Shared Library" greek "Δεν βÏÎθηκαν paths για την shared library" hun "Nincs ut a megosztott konyvtarakhoz (shared library)" ita "Non sono ammessi path per le librerie condivisa" - jpn "shared library ã¸ã®ãƒ‘スãŒé€šã£ã¦ã„ã¾ã›ã‚“" + jpn "共有ライブラリã«ã¯ãƒ‘スを指定ã§ãã¾ã›ã‚“。" kor "ê³µìœ ë¼ì´ë²„러리를 위한 패스가 ì •ì˜ë˜ì–´ 있지 않습니다." por "Não há caminhos (paths) permitidos para biblioteca compartilhada" rum "Nici un paths nu e permis pentru o librarie shared" @@ -2913,18 +2806,17 @@ ER_UDF_NO_PATHS swe "Man fÃ¥r inte ange sökväg för dynamiska bibliotek" ukr "Ðе дозволено викориÑтовувати путі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð´Ñ–Ð»ÑŽÐ²Ð°Ð½Ð¸Ñ… бібліотек" ER_UDF_EXISTS - cze "Funkce '%-.192s' ji-Bž existuje" + cze "Funkce '%-.192s' již existuje" dan "Funktionen '%-.192s' findes allerede" nla "Functie '%-.192s' bestaat reeds" eng "Function '%-.192s' already exists" - jps "Function '%-.192s' ã¯æ—¢ã«å®šç¾©ã•れã¦ã„ã¾ã™", est "Funktsioon '%-.192s' juba eksisteerib" fre "La fonction '%-.192s' existe déjà " ger "Funktion '%-.192s' existiert schon" greek "Η συνάÏτηση '%-.192s' υπάÏχει ήδη" hun "A '%-.192s' fuggveny mar letezik" ita "La funzione '%-.192s' esiste gia`" - jpn "Function '%-.192s' ã¯æ—¢ã«å®šç¾©ã•れã¦ã„ã¾ã™" + jpn "関数 '%-.192s' ã¯ã™ã§ã«å®šç¾©ã•れã¦ã„ã¾ã™ã€‚" kor "'%-.192s' 함수는 ì´ë¯¸ 존재합니다." por "Função '%-.192s' já existe" rum "Functia '%-.192s' exista deja" @@ -2935,18 +2827,17 @@ ER_UDF_EXISTS swe "Funktionen '%-.192s' finns redan" ukr "Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ '%-.192s' вже Ñ–Ñнує" ER_CANT_OPEN_LIBRARY - cze "Nemohu otev-BÅ™Ãt sdÃlenou knihovnu '%-.192s' (errno: %d, %-.128s)" + cze "Nemohu otevÅ™Ãt sdÃlenou knihovnu '%-.192s' (errno: %d, %-.128s)" dan "Kan ikke Ã¥bne delt bibliotek '%-.192s' (errno: %d, %-.128s)" nla "Kan shared library '%-.192s' niet openen (Errcode: %d, %-.128s)" eng "Can't open shared library '%-.192s' (errno: %d, %-.128s)" - jps "shared library '%-.192s' ã‚’é–‹ã事ãŒã§ãã¾ã›ã‚“ (errno: %d, %-.128s)", est "Ei suuda avada jagatud teeki '%-.192s' (veakood: %d, %-.128s)" fre "Impossible d'ouvrir la bibliothèque partagée '%-.192s' (errno: %d, %-.128s)" ger "Kann Shared Library '%-.192s' nicht öffnen (Fehler: %d, %-.128s)" greek "Δεν είναι δυνατή η ανάγνωση της shared library '%-.192s' (κωδικός λάθους: %d, %-.128s)" hun "A(z) '%-.192s' megosztott konyvtar nem hasznalhato (hibakod: %d, %-.128s)" ita "Impossibile aprire la libreria condivisa '%-.192s' (errno: %d, %-.128s)" - jpn "shared library '%-.192s' ã‚’é–‹ã事ãŒã§ãã¾ã›ã‚“ (errno: %d, %-.128s)" + jpn "共有ライブラリ '%-.192s' ã‚’é–‹ã事ãŒã§ãã¾ã›ã‚“。(エラー番å·: %d, %-.128s)" kor "'%-.192s' ê³µìœ ë¼ì´ë²„러리를 열수 없습니다.(ì—러번호: %d, %-.128s)" nor "Can't open shared library '%-.192s' (errno: %d, %-.128s)" norwegian-ny "Can't open shared library '%-.192s' (errno: %d, %-.128s)" @@ -2960,18 +2851,17 @@ ER_CANT_OPEN_LIBRARY swe "Kan inte öppna det dynamiska biblioteket '%-.192s' (Felkod: %d, %-.128s)" ukr "Ðе можу відкрити розділювану бібліотеку '%-.192s' (помилка: %d, %-.128s)" ER_CANT_FIND_DL_ENTRY - cze "Nemohu naj-BÃt funkci '%-.128s' v knihovnÄ›" + cze "Nemohu najÃt funkci '%-.128s' v knihovnÄ›" dan "Kan ikke finde funktionen '%-.128s' i bibliotek" nla "Kan functie '%-.128s' niet in library vinden" eng "Can't find symbol '%-.128s' in library" - jps "function '%-.128s' をライブラリーä¸ã«è¦‹ä»˜ã‘る事ãŒã§ãã¾ã›ã‚“", est "Ei leia funktsiooni '%-.128s' antud teegis" fre "Impossible de trouver la fonction '%-.128s' dans la bibliothèque" ger "Kann Funktion '%-.128s' in der Library nicht finden" greek "Δεν είναι δυνατή η ανεÏÏεση της συνάÏτησης '%-.128s' στην βιβλιοθήκη" hun "A(z) '%-.128s' fuggveny nem talalhato a konyvtarban" ita "Impossibile trovare la funzione '%-.128s' nella libreria" - jpn "function '%-.128s' をライブラリーä¸ã«è¦‹ä»˜ã‘る事ãŒã§ãã¾ã›ã‚“" + jpn "関数 '%-.128s' ã¯å…±æœ‰ãƒ©ã‚¤ãƒ–ラリーä¸ã«ã‚りã¾ã›ã‚“。" kor "ë¼ì´ë²„러리ì—서 '%-.128s' 함수를 ì°¾ì„ ìˆ˜ 없습니다." por "Não pode encontrar a função '%-.128s' na biblioteca" rum "Nu pot gasi functia '%-.128s' in libraria" @@ -2982,18 +2872,17 @@ ER_CANT_FIND_DL_ENTRY swe "Hittar inte funktionen '%-.128s' in det dynamiska biblioteket" ukr "Ðе можу знайти функцію '%-.128s' у бібліотеці" ER_FUNCTION_NOT_DEFINED - cze "Funkce '%-.192s' nen-Bà definována" + cze "Funkce '%-.192s' nenà definována" dan "Funktionen '%-.192s' er ikke defineret" nla "Functie '%-.192s' is niet gedefinieerd" eng "Function '%-.192s' is not defined" - jps "Function '%-.192s' ã¯å®šç¾©ã•れã¦ã„ã¾ã›ã‚“", est "Funktsioon '%-.192s' ei ole defineeritud" fre "La fonction '%-.192s' n'est pas définie" ger "Funktion '%-.192s' ist nicht definiert" greek "Η συνάÏτηση '%-.192s' δεν Îχει οÏισθεί" hun "A '%-.192s' fuggveny nem definialt" ita "La funzione '%-.192s' non e` definita" - jpn "Function '%-.192s' ã¯å®šç¾©ã•れã¦ã„ã¾ã›ã‚“" + jpn "関数 '%-.192s' ã¯å®šç¾©ã•れã¦ã„ã¾ã›ã‚“。" kor "'%-.192s' 함수가 ì •ì˜ë˜ì–´ 있지 않습니다." por "Função '%-.192s' não está definida" rum "Functia '%-.192s' nu e definita" @@ -3004,18 +2893,17 @@ ER_FUNCTION_NOT_DEFINED swe "Funktionen '%-.192s' är inte definierad" ukr "Функцію '%-.192s' не визначено" ER_HOST_IS_BLOCKED - cze "Stroj '%-.64s' je zablokov-Bán kvůli mnoha chybám pÅ™i pÅ™ipojovánÃ. Odblokujete použitÃm 'mysqladmin flush-hosts'" + cze "Stroj '%-.64s' je zablokován kvůli mnoha chybám pÅ™i pÅ™ipojovánÃ. Odblokujete použitÃm 'mysqladmin flush-hosts'" dan "Værten '%-.64s' er blokeret pÃ¥ grund af mange fejlforespørgsler. LÃ¥s op med 'mysqladmin flush-hosts'" nla "Host '%-.64s' is geblokkeeerd vanwege te veel verbindings fouten. Deblokkeer met 'mysqladmin flush-hosts'" eng "Host '%-.64s' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'" - jps "Host '%-.64s' 㯠many connection error ã®ãŸã‚ã€æ‹’å¦ã•れã¾ã—ãŸ. 'mysqladmin flush-hosts' ã§è§£é™¤ã—ã¦ãã ã•ã„", est "Masin '%-.64s' on blokeeritud hulgaliste ühendusvigade tõttu. Blokeeringu saab tühistada 'mysqladmin flush-hosts' käsuga" fre "L'hôte '%-.64s' est bloqué à cause d'un trop grand nombre d'erreur de connexion. Débloquer le par 'mysqladmin flush-hosts'" ger "Host '%-.64s' blockiert wegen zu vieler Verbindungsfehler. Aufheben der Blockierung mit 'mysqladmin flush-hosts'" greek "Ο υπολογιστής '%-.64s' Îχει αποκλεισθεί λόγω πολλαπλών λαθών σÏνδεσης. Î Ïοσπαθήστε να διοÏώσετε με 'mysqladmin flush-hosts'" hun "A '%-.64s' host blokkolodott, tul sok kapcsolodasi hiba miatt. Hasznalja a 'mysqladmin flush-hosts' parancsot" ita "Sistema '%-.64s' bloccato a causa di troppi errori di connessione. Per sbloccarlo: 'mysqladmin flush-hosts'" - jpn "Host '%-.64s' 㯠many connection error ã®ãŸã‚ã€æ‹’å¦ã•れã¾ã—ãŸ. 'mysqladmin flush-hosts' ã§è§£é™¤ã—ã¦ãã ã•ã„" + jpn "接続エラーãŒå¤šã„ãŸã‚ã€ãƒ›ã‚¹ãƒˆ '%-.64s' ã¯æ‹’å¦ã•れã¾ã—ãŸã€‚'mysqladmin flush-hosts' ã§è§£é™¤ã§ãã¾ã™ã€‚" kor "너무 ë§Žì€ ì—°ê²°ì˜¤ë¥˜ë¡œ ì¸í•˜ì—¬ 호스트 '%-.64s'는 블ë½ë˜ì—ˆìŠµë‹ˆë‹¤. 'mysqladmin flush-hosts'를 ì´ìš©í•˜ì—¬ 블ë½ì„ í•´ì œí•˜ì„¸ìš”" por "'Host' '%-.64s' está bloqueado devido a muitos erros de conexão. Desbloqueie com 'mysqladmin flush-hosts'" rum "Host-ul '%-.64s' e blocat din cauza multelor erori de conectie. Poti deploca folosind 'mysqladmin flush-hosts'" @@ -3025,18 +2913,17 @@ ER_HOST_IS_BLOCKED swe "Denna dator, '%-.64s', är blockerad pga mÃ¥nga felaktig paket. Gör 'mysqladmin flush-hosts' för att ta bort alla blockeringarna" ukr "ХоÑÑ‚ '%-.64s' заблоковано з причини великої кількоÑті помилок з'єднаннÑ. Ð”Ð»Ñ Ñ€Ð¾Ð·Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸Ñтовуйте 'mysqladmin flush-hosts'" ER_HOST_NOT_PRIVILEGED - cze "Stroj '%-.64s' nem-Bá povoleno se k tomuto MariaDB serveru pÅ™ipojit" + cze "Stroj '%-.64s' nemá povoleno se k tomuto MySQL serveru pÅ™ipojit" dan "Værten '%-.64s' kan ikke tilkoble denne MariaDB-server" nla "Het is host '%-.64s' is niet toegestaan verbinding te maken met deze MariaDB server" eng "Host '%-.64s' is not allowed to connect to this MariaDB server" - jps "Host '%-.64s' 㯠MariaDB server ã«æŽ¥ç¶šã‚’è¨±å¯ã•れã¦ã„ã¾ã›ã‚“", est "Masinal '%-.64s' puudub ligipääs sellele MariaDB serverile" fre "Le hôte '%-.64s' n'est pas authorisé à se connecter à ce serveur MariaDB" ger "Host '%-.64s' hat keine Berechtigung, sich mit diesem MariaDB-Server zu verbinden" greek "Ο υπολογιστής '%-.64s' δεν Îχει δικαίωμα σÏνδεσης με τον MariaDB server" hun "A '%-.64s' host szamara nem engedelyezett a kapcsolodas ehhez a MariaDB szerverhez" ita "Al sistema '%-.64s' non e` consentita la connessione a questo server MariaDB" - jpn "Host '%-.64s' 㯠MariaDB server ã«æŽ¥ç¶šã‚’è¨±å¯ã•れã¦ã„ã¾ã›ã‚“" + jpn "ホスト '%-.64s' ã‹ã‚‰ã®ã“ã® MySQL server ã¸ã®æŽ¥ç¶šã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“。" kor "'%-.64s' 호스트는 ì´ MariaDBì„œë²„ì— ì ‘ì†í• 허가를 받지 못했습니다." por "'Host' '%-.64s' não tem permissão para se conectar com este servidor MariaDB" rum "Host-ul '%-.64s' nu este permis a se conecta la aceste server MariaDB" @@ -3046,39 +2933,37 @@ ER_HOST_NOT_PRIVILEGED swe "Denna dator, '%-.64s', har inte privileger att använda denna MariaDB server" ukr "ХоÑту '%-.64s' не доволено зв'ÑзуватиÑÑŒ з цим Ñервером MariaDB" ER_PASSWORD_ANONYMOUS_USER 42000 - cze "Pou-BžÃváte MariaDB jako anonymnà uživatel a anonymnà uživatelé nemajà povoleno mÄ›nit hesla" + cze "PoužÃváte MySQL jako anonymnà uživatel a anonymnà uživatelé nemajà povoleno mÄ›nit hesla" dan "Du bruger MariaDB som anonym bruger. Anonyme brugere mÃ¥ ikke ændre adgangskoder" nla "U gebruikt MariaDB als anonieme gebruiker en deze mogen geen wachtwoorden wijzigen" - eng "You are using MariaDB as an anonymous user and anonymous users are not allowed to change passwords" - jps "MariaDB ã‚’ anonymous users ã§ä½¿ç”¨ã—ã¦ã„る状態ã§ã¯ã€ãƒ‘スワードã®å¤‰æ›´ã¯ã§ãã¾ã›ã‚“", + eng "You are using MariaDB as an anonymous user and anonymous users are not allowed to modify user settings" est "Te kasutate MariaDB-i anonüümse kasutajana, kelledel pole parooli muutmise õigust" fre "Vous utilisez un utilisateur anonyme et les utilisateurs anonymes ne sont pas autorisés à changer les mots de passe" ger "Sie benutzen MariaDB als anonymer Benutzer und dürfen daher keine Passwörter ändern" greek "ΧÏησιμοποιείτε την MariaDB σαν anonymous user και Îτσι δεν μποÏείτε να αλλάξετε τα passwords άλλων χÏηστών" hun "Nevtelen (anonymous) felhasznalokent nem negedelyezett a jelszovaltoztatas" ita "Impossibile cambiare la password usando MariaDB come utente anonimo" - jpn "MariaDB ã‚’ anonymous users ã§ä½¿ç”¨ã—ã¦ã„る状態ã§ã¯ã€ãƒ‘スワードã®å¤‰æ›´ã¯ã§ãã¾ã›ã‚“" + jpn "MySQL を匿åユーザーã§ä½¿ç”¨ã—ã¦ã„ã‚‹ã®ã§ã€ãƒ‘スワードã®å¤‰æ›´ã¯ã§ãã¾ã›ã‚“。" kor "ë‹¹ì‹ ì€ MariaDBì„œë²„ì— ìµëª…ì˜ ì‚¬ìš©ìžë¡œ ì ‘ì†ì„ 하셨습니다.ìµëª…ì˜ ì‚¬ìš©ìžëŠ” 암호를 ë³€ê²½í• ìˆ˜ 없습니다." por "Você está usando o MariaDB como usuário anônimo e usuários anônimos não têm permissão para mudar senhas" - rum "Dumneavoastra folositi MariaDB ca un utilizator anonim si utilizatorii anonimi nu au voie sa schime parolele" + rum "Dumneavoastra folositi MariaDB ca un utilizator anonim si utilizatorii anonimi nu au voie sa schimbe setarile utilizatorilor." rus "Ð’Ñ‹ иÑпользуете MariaDB от имени анонимного пользователÑ, а анонимным пользователÑм не разрешаетÑÑ Ð¼ÐµÐ½Ñть пароли" serbian "Vi koristite MariaDB kao anonimni korisnik a anonimnim korisnicima nije dozvoljeno da menjaju lozinke" spa "Tu estás usando MariaDB como un usuario anonimo y usuarios anonimos no tienen permiso para cambiar las claves" swe "Du använder MariaDB som en anonym användare och som sÃ¥dan fÃ¥r du inte ändra ditt lösenord" ukr "Ви викориÑтовуєте MariaDB Ñк анонімний кориÑтувач, тому вам не дозволено змінювати паролі" ER_PASSWORD_NOT_ALLOWED 42000 - cze "Na zm-BÄ›nu hesel ostatnÃm musÃte mÃt právo provést update tabulek v databázi mysql" + cze "Na zmÄ›nu hesel ostatnÃm musÃte mÃt právo provést update tabulek v databázi mysql" dan "Du skal have tilladelse til at opdatere tabeller i MariaDB databasen for at ændre andres adgangskoder" nla "U moet tabel update priveleges hebben in de mysql database om wachtwoorden voor anderen te mogen wijzigen" eng "You must have privileges to update tables in the mysql database to be able to change passwords for others" - jps "ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ‘スワードを変更ã™ã‚‹ãŸã‚ã«ã¯, mysql データベースã«å¯¾ã—㦠update ã®è¨±å¯ãŒãªã‘れã°ãªã‚Šã¾ã›ã‚“.", est "Teiste paroolide muutmiseks on nõutav tabelite muutmisõigus 'mysql' andmebaasis" fre "Vous devez avoir le privilège update sur les tables de la base de donnée mysql pour pouvoir changer les mots de passe des autres" ger "Sie benötigen die Berechtigung zum Aktualisieren von Tabellen in der Datenbank 'mysql', um die Passwörter anderer Benutzer ändern zu können" greek "Î ÏÎπει να Îχετε δικαίωμα διόÏθωσης πινάκων (update) στη βάση δεδομÎνων mysql για να μποÏείτε να αλλάξετε τα passwords άλλων χÏηστών" hun "Onnek tabla-update joggal kell rendelkeznie a mysql adatbazisban masok jelszavanak megvaltoztatasahoz" ita "E` necessario il privilegio di update sulle tabelle del database mysql per cambiare le password per gli altri utenti" - jpn "ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ‘スワードを変更ã™ã‚‹ãŸã‚ã«ã¯, mysql データベースã«å¯¾ã—㦠update ã®è¨±å¯ãŒãªã‘れã°ãªã‚Šã¾ã›ã‚“." + jpn "ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ‘スワードを変更ã™ã‚‹ãŸã‚ã«ã¯ã€mysqlデータベースã®è¡¨ã‚’æ›´æ–°ã™ã‚‹æ¨©é™ãŒå¿…è¦ã§ã™ã€‚" kor "ë‹¹ì‹ ì€ ë‹¤ë¥¸ì‚¬ìš©ìžë“¤ì˜ 암호를 ë³€ê²½í• ìˆ˜ 있ë„ë¡ ë°ì´íƒ€ë² ì´ìФ ë³€ê²½ê¶Œí•œì„ ê°€ì ¸ì•¼ 합니다." por "Você deve ter privilégios para atualizar tabelas no banco de dados mysql para ser capaz de mudar a senha de outros" rum "Trebuie sa aveti privilegii sa actualizati tabelele in bazele de date mysql ca sa puteti sa schimati parolele altora" @@ -3087,8 +2972,8 @@ ER_PASSWORD_NOT_ALLOWED 42000 spa "Tu debes de tener permiso para actualizar tablas en la base de datos mysql para cambiar las claves para otros" swe "För att ändra lösenord för andra mÃ¥ste du ha rättigheter att uppdatera mysql-databasen" ukr "Ви повині мати право на Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†ÑŒ у базі данних mysql, аби мати можливіÑть змінювати пароль іншим" -ER_PASSWORD_NO_MATCH 42000 - cze "V tabulce user nen-Bà žádný odpovÃdajÃcà řádek" +ER_PASSWORD_NO_MATCH 28000 + cze "V tabulce user nenà žádný odpovÃdajÃcà řádek" dan "Kan ikke finde nogen tilsvarende poster i bruger tabellen" nla "Kan geen enkele passende rij vinden in de gebruikers tabel" eng "Can't find any matching row in the user table" @@ -3098,6 +2983,7 @@ ER_PASSWORD_NO_MATCH 42000 greek "Δεν είναι δυνατή η ανεÏÏεση της αντίστοιχης εγγÏαφής στον πίνακα των χÏηστών" hun "Nincs megegyezo sor a user tablaban" ita "Impossibile trovare la riga corrispondente nella tabella user" + jpn "ユーザーテーブルã«è©²å½“ã™ã‚‹ãƒ¬ã‚³ãƒ¼ãƒ‰ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。" kor "ì‚¬ìš©ìž í…Œì´ë¸”ì—서 ì¼ì¹˜í•˜ëŠ” ê²ƒì„ ì°¾ì„ ìˆ˜ ì—†ì니다." por "Não pode encontrar nenhuma linha que combine na tabela usuário (user table)" rum "Nu pot gasi nici o linie corespunzatoare in tabela utilizatorului" @@ -3107,17 +2993,16 @@ ER_PASSWORD_NO_MATCH 42000 swe "Hittade inte användaren i 'user'-tabellen" ukr "Ðе можу знайти відповідних запиÑів у таблиці кориÑтувача" ER_UPDATE_INFO - cze "Nalezen-Bých řádků: %ld ZmÄ›nÄ›no: %ld VarovánÃ: %ld" + cze "Nalezených řádků: %ld ZmÄ›nÄ›no: %ld VarovánÃ: %ld" dan "Poster fundet: %ld Ændret: %ld Advarsler: %ld" nla "Passende rijen: %ld Gewijzigd: %ld Waarschuwingen: %ld" eng "Rows matched: %ld Changed: %ld Warnings: %ld" - jps "一致数(Rows matched): %ld 変更: %ld Warnings: %ld", est "Sobinud kirjeid: %ld Muudetud: %ld Hoiatusi: %ld" fre "Enregistrements correspondants: %ld Modifiés: %ld Warnings: %ld" ger "Datensätze gefunden: %ld Geändert: %ld Warnungen: %ld" hun "Megegyezo sorok szama: %ld Valtozott: %ld Warnings: %ld" ita "Rows riconosciute: %ld Cambiate: %ld Warnings: %ld" - jpn "一致数(Rows matched): %ld 変更: %ld Warnings: %ld" + jpn "該当ã—ãŸè¡Œ: %ld 変更: %ld è¦å‘Š: %ld" kor "ì¼ì¹˜í•˜ëŠ” Rows : %ldê°œ 변경ë¨: %ldê°œ ê²½ê³ : %ldê°œ" por "Linhas que combinaram: %ld - Alteradas: %ld - Avisos: %ld" rum "Linii identificate (matched): %ld Schimbate: %ld Atentionari (warnings): %ld" @@ -3127,30 +3012,29 @@ ER_UPDATE_INFO swe "Rader: %ld Uppdaterade: %ld Varningar: %ld" ukr "ЗапиÑів відповідає: %ld Змінено: %ld ЗаÑтережень: %ld" ER_CANT_CREATE_THREAD - cze "Nemohu vytvo-BÅ™it nový thread (errno %d). Pokud je jeÅ¡tÄ› nÄ›jaká volná paměť, podÃvejte se do manuálu na Äást o chybách specifických pro jednotlivé operaÄnà systémy" - dan "Kan ikke danne en ny trÃ¥d (fejl nr. %d). Hvis computeren ikke er løbet tør for hukommelse, kan du se i brugervejledningen for en mulig operativ-system - afhængig fejl" - nla "Kan geen nieuwe thread aanmaken (Errcode: %d). Indien er geen tekort aan geheugen is kunt u de handleiding consulteren over een mogelijke OS afhankelijke fout" - eng "Can't create a new thread (errno %d); if you are not out of available memory, you can consult the manual for a possible OS-dependent bug" - jps "æ–°è¦ã«ã‚¹ãƒ¬ãƒƒãƒ‰ãŒä½œã‚Œã¾ã›ã‚“ã§ã—㟠(errno %d). ã‚‚ã—æœ€å¤§ä½¿ç”¨è¨±å¯ãƒ¡ãƒ¢ãƒªãƒ¼æ•°ã‚’è¶Šãˆã¦ã„ãªã„ã®ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¦ã„ã‚‹ãªã‚‰, マニュアルã®ä¸ã‹ã‚‰ 'possible OS-dependent bug' ã¨ã„ã†æ–‡å—を探ã—ã¦ãã¿ã¦ã ã•ã„.", - est "Ei suuda luua uut lõime (veakood %d). Kui mälu ei ole otsas, on tõenäoliselt tegemist operatsioonisüsteemispetsiifilise veaga" - fre "Impossible de créer une nouvelle tâche (errno %d). S'il reste de la mémoire libre, consultez le manual pour trouver un éventuel bug dépendant de l'OS" - ger "Kann keinen neuen Thread erzeugen (Fehler: %d). Sollte noch Speicher verfügbar sein, bitte im Handbuch wegen möglicher Fehler im Betriebssystem nachschlagen" - hun "Uj thread letrehozasa nem lehetseges (Hibakod: %d). Amenyiben van meg szabad memoria, olvassa el a kezikonyv operacios rendszerfuggo hibalehetosegekrol szolo reszet" - ita "Impossibile creare un nuovo thread (errno %d). Se non ci sono problemi di memoria disponibile puoi consultare il manuale per controllare possibili problemi dipendenti dal SO" - jpn "æ–°è¦ã«ã‚¹ãƒ¬ãƒƒãƒ‰ãŒä½œã‚Œã¾ã›ã‚“ã§ã—㟠(errno %d). ã‚‚ã—æœ€å¤§ä½¿ç”¨è¨±å¯ãƒ¡ãƒ¢ãƒªãƒ¼æ•°ã‚’è¶Šãˆã¦ã„ãªã„ã®ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¦ã„ã‚‹ãªã‚‰, マニュアルã®ä¸ã‹ã‚‰ 'possible OS-dependent bug' ã¨ã„ã†æ–‡å—を探ã—ã¦ãã¿ã¦ã ã•ã„." - kor "새로운 ì“°ë ˆë“œë¥¼ 만들 수 없습니다.(ì—러번호 %d). 만약 ì—¬ìœ ë©”ëª¨ë¦¬ê°€ 있다면 OS-dependent버그 ì˜ ë©”ë‰´ì–¼ ë¶€ë¶„ì„ ì°¾ì•„ë³´ì‹œì˜¤." - nor "Can't create a new thread (errno %d); if you are not out of available memory you can consult the manual for any possible OS dependent bug" - norwegian-ny "Can't create a new thread (errno %d); if you are not out of available memory you can consult the manual for any possible OS dependent bug" - pol "Can't create a new thread (errno %d); if you are not out of available memory you can consult the manual for any possible OS dependent bug" - por "Não pode criar uma nova 'thread' (erro no. %d). Se você não estiver sem memória disponÃvel, você pode consultar o manual sobre um possÃvel 'bug' dependente do sistema operacional" - rum "Nu pot crea un thread nou (Eroare %d). Daca mai aveti memorie disponibila in sistem, puteti consulta manualul - ar putea exista un potential bug in legatura cu sistemul de operare" - rus "Ðевозможно Ñоздать новый поток (ошибка %d). ЕÑли Ñто не ÑитуациÑ, ÑвÑÐ·Ð°Ð½Ð½Ð°Ñ Ñ Ð½ÐµÑ…Ð²Ð°Ñ‚ÐºÐ¾Ð¹ памÑти, то вам Ñледует изучить документацию на предмет опиÑÐ°Ð½Ð¸Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ð¹ ошибки работы в конкретной ОС" - serbian "Ne mogu da kreiram novi thread (errno %d). Ako imate joÅ¡ slobodne memorije, trebali biste da pogledate u priruÄniku da li je ovo specifiÄna greÅ¡ka vaÅ¡eg operativnog sistema" - spa "No puedo crear un nuevo thread (errno %d). Si tu está con falta de memoria disponible, tu puedes consultar el Manual para posibles problemas con SO" - swe "Kan inte skapa en ny trÃ¥d (errno %d)" - ukr "Ðе можу Ñтворити нову гілку (помилка %d). Якщо ви не викориÑтали уÑÑŽ пам'Ñть, то прочитайте документацію до вашої ОС - можливо це помилка ОС" + cze "Nemohu vytvoÅ™it nový thread (errno %M). Pokud je jeÅ¡tÄ› nÄ›jaká volná paměť, podÃvejte se do manuálu na Äást o chybách specifických pro jednotlivé operaÄnà systémy" + dan "Kan ikke danne en ny trÃ¥d (fejl nr. %M). Hvis computeren ikke er løbet tør for hukommelse, kan du se i brugervejledningen for en mulig operativ-system - afhængig fejl" + nla "Kan geen nieuwe thread aanmaken (Errcode: %M). Indien er geen tekort aan geheugen is kunt u de handleiding consulteren over een mogelijke OS afhankelijke fout" + eng "Can't create a new thread (errno %M); if you are not out of available memory, you can consult the manual for a possible OS-dependent bug" + est "Ei suuda luua uut lõime (veakood %M). Kui mälu ei ole otsas, on tõenäoliselt tegemist operatsioonisüsteemispetsiifilise veaga" + fre "Impossible de créer une nouvelle tâche (errno %M). S'il reste de la mémoire libre, consultez le manual pour trouver un éventuel bug dépendant de l'OS" + ger "Kann keinen neuen Thread erzeugen (Fehler: %M). Sollte noch Speicher verfügbar sein, bitte im Handbuch wegen möglicher Fehler im Betriebssystem nachschlagen" + hun "Uj thread letrehozasa nem lehetseges (Hibakod: %M). Amenyiben van meg szabad memoria, olvassa el a kezikonyv operacios rendszerfuggo hibalehetosegekrol szolo reszet" + ita "Impossibile creare un nuovo thread (errno %M). Se non ci sono problemi di memoria disponibile puoi consultare il manuale per controllare possibili problemi dipendenti dal SO" + jpn "æ–°è¦ã«ã‚¹ãƒ¬ãƒƒãƒ‰ã‚’作æˆã§ãã¾ã›ã‚“。(ã‚¨ãƒ©ãƒ¼ç•ªå· %M) ã‚‚ã—も使用å¯èƒ½ãƒ¡ãƒ¢ãƒªãƒ¼ã®ä¸è¶³ã§ãªã‘れã°ã€OSä¾å˜ã®ãƒã‚°ã§ã‚ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚" + kor "새로운 ì“°ë ˆë“œë¥¼ 만들 수 없습니다.(ì—러번호 %M). 만약 ì—¬ìœ ë©”ëª¨ë¦¬ê°€ 있다면 OS-dependent버그 ì˜ ë©”ë‰´ì–¼ ë¶€ë¶„ì„ ì°¾ì•„ë³´ì‹œì˜¤." + nor "Can't create a new thread (errno %M); if you are not out of available memory you can consult the manual for any possible OS dependent bug" + norwegian-ny "Can't create a new thread (errno %M); if you are not out of available memory you can consult the manual for any possible OS dependent bug" + pol "Can't create a new thread (errno %M); if you are not out of available memory you can consult the manual for any possible OS dependent bug" + por "Não pode criar uma nova 'thread' (erro no. %M). Se você não estiver sem memória disponÃvel, você pode consultar o manual sobre um possÃvel 'bug' dependente do sistema operacional" + rum "Nu pot crea un thread nou (Eroare %M). Daca mai aveti memorie disponibila in sistem, puteti consulta manualul - ar putea exista un potential bug in legatura cu sistemul de operare" + rus "Ðевозможно Ñоздать новый поток (ошибка %M). ЕÑли Ñто не ÑитуациÑ, ÑвÑÐ·Ð°Ð½Ð½Ð°Ñ Ñ Ð½ÐµÑ…Ð²Ð°Ñ‚ÐºÐ¾Ð¹ памÑти, то вам Ñледует изучить документацию на предмет опиÑÐ°Ð½Ð¸Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ð¹ ошибки работы в конкретной ОС" + serbian "Ne mogu da kreiram novi thread (errno %M). Ako imate joÅ¡ slobodne memorije, trebali biste da pogledate u priruÄniku da li je ovo specifiÄna greÅ¡ka vaÅ¡eg operativnog sistema" + spa "No puedo crear un nuevo thread (errno %M). Si tu está con falta de memoria disponible, tu puedes consultar el Manual para posibles problemas con SO" + swe "Kan inte skapa en ny trÃ¥d (errno %M)" + ukr "Ðе можу Ñтворити нову гілку (помилка %M). Якщо ви не викориÑтали уÑÑŽ пам'Ñть, то прочитайте документацію до вашої ОС - можливо це помилка ОС" ER_WRONG_VALUE_COUNT_ON_ROW 21S01 - cze "Po-BÄet sloupců neodpovÃdá poÄtu hodnot na řádku %lu" + cze "PoÄet sloupců neodpovÃdá poÄtu hodnot na řádku %lu" dan "Kolonne antallet stemmer ikke overens med antallet af værdier i post %lu" nla "Kolom aantal komt niet overeen met waarde aantal in rij %lu" eng "Column count doesn't match value count at row %lu" @@ -3158,6 +3042,7 @@ ER_WRONG_VALUE_COUNT_ON_ROW 21S01 ger "Anzahl der Felder stimmt nicht mit der Anzahl der Werte in Zeile %lu überein" hun "Az oszlopban talalhato ertek nem egyezik meg a %lu sorban szamitott ertekkel" ita "Il numero delle colonne non corrisponde al conteggio alla riga %lu" + jpn "%lu 行目ã§ã€åˆ—ã®æ•°ãŒå€¤ã®æ•°ã¨ä¸€è‡´ã—ã¾ã›ã‚“。" kor "Row %luì—서 칼럼 카운트와 value 카운터와 ì¼ì¹˜í•˜ì§€ 않습니다." por "Contagem de colunas não confere com a contagem de valores na linha %lu" rum "Numarul de coloane nu corespunde cu numarul de valori la linia %lu" @@ -3167,7 +3052,7 @@ ER_WRONG_VALUE_COUNT_ON_ROW 21S01 swe "Antalet kolumner motsvarar inte antalet värden pÃ¥ rad: %lu" ukr "КількіÑть Ñтовбців не Ñпівпадає з кількіÑтю значень у Ñтроці %lu" ER_CANT_REOPEN_TABLE - cze "Nemohu znovuotev-BÅ™Ãt tabulku: '%-.192s" + cze "Nemohu znovuotevÅ™Ãt tabulku: '%-.192s" dan "Kan ikke genÃ¥bne tabel '%-.192s" nla "Kan tabel niet opnieuw openen: '%-.192s" eng "Can't reopen table: '%-.192s'" @@ -3176,6 +3061,7 @@ ER_CANT_REOPEN_TABLE ger "Kann Tabelle'%-.192s' nicht erneut öffnen" hun "Nem lehet ujra-megnyitni a tablat: '%-.192s" ita "Impossibile riaprire la tabella: '%-.192s'" + jpn "表をå†ã‚ªãƒ¼ãƒ—ンã§ãã¾ã›ã‚“。: '%-.192s'" kor "í…Œì´ë¸”ì„ ë‹¤ì‹œ 열수 없군요: '%-.192s" nor "Can't reopen table: '%-.192s" norwegian-ny "Can't reopen table: '%-.192s" @@ -3189,17 +3075,16 @@ ER_CANT_REOPEN_TABLE swe "Kunde inte stänga och öppna tabell '%-.192s" ukr "Ðе можу перевідкрити таблицю: '%-.192s'" ER_INVALID_USE_OF_NULL 22004 - cze "Neplatn-Bé užità hodnoty NULL" + cze "Neplatné užità hodnoty NULL" dan "Forkert brug af nulværdi (NULL)" nla "Foutief gebruik van de NULL waarde" eng "Invalid use of NULL value" - jps "NULL 値ã®ä½¿ç”¨æ–¹æ³•ãŒä¸é©åˆ‡ã§ã™", est "NULL väärtuse väärkasutus" fre "Utilisation incorrecte de la valeur NULL" ger "Unerlaubte Verwendung eines NULL-Werts" hun "A NULL ervenytelen hasznalata" ita "Uso scorretto del valore NULL" - jpn "NULL 値ã®ä½¿ç”¨æ–¹æ³•ãŒä¸é©åˆ‡ã§ã™" + jpn "NULL 値ã®ä½¿ç”¨æ–¹æ³•ãŒä¸é©åˆ‡ã§ã™ã€‚" kor "NULL ê°’ì„ ìž˜ëª» 사용하셨군요..." por "Uso inválido do valor NULL" rum "Folosirea unei value NULL e invalida" @@ -3209,7 +3094,7 @@ ER_INVALID_USE_OF_NULL 22004 swe "Felaktig använding av NULL" ukr "Хибне викориÑÑ‚Ð°Ð½Ð½Ñ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ NULL" ER_REGEXP_ERROR 42000 - cze "Regul-Bárnà výraz vrátil chybu '%-.64s'" + cze "Regulárnà výraz vrátil chybu '%-.64s'" dan "Fik fejl '%-.64s' fra regexp" nla "Fout '%-.64s' ontvangen van regexp" eng "Got error '%-.64s' from regexp" @@ -3218,6 +3103,7 @@ ER_REGEXP_ERROR 42000 ger "regexp lieferte Fehler '%-.64s'" hun "'%-.64s' hiba a regularis kifejezes hasznalata soran (regexp)" ita "Errore '%-.64s' da regexp" + jpn "regexp ãŒã‚¨ãƒ©ãƒ¼ '%-.64s' ã‚’è¿”ã—ã¾ã—ãŸã€‚" kor "regexpì—서 '%-.64s'ê°€ 났습니다." por "Obteve erro '%-.64s' em regexp" rum "Eroarea '%-.64s' obtinuta din expresia regulara (regexp)" @@ -3227,7 +3113,7 @@ ER_REGEXP_ERROR 42000 swe "Fick fel '%-.64s' frÃ¥n REGEXP" ukr "Отримано помилку '%-.64s' від регулÑрного виразу" ER_MIX_OF_GROUP_FUNC_AND_FIELDS 42000 - cze "Pokud nen-Bà žádná GROUP BY klauzule, nenà dovoleno souÄasné použità GROUP položek (MIN(),MAX(),COUNT()...) s ne GROUP položkami" + cze "Pokud nenà žádná GROUP BY klauzule, nenà dovoleno souÄasné použità GROUP položek (MIN(),MAX(),COUNT()...) s ne GROUP položkami" dan "Sammenblanding af GROUP kolonner (MIN(),MAX(),COUNT()...) uden GROUP kolonner er ikke tilladt, hvis der ikke er noget GROUP BY prædikat" nla "Het mixen van GROUP kolommen (MIN(),MAX(),COUNT()...) met no-GROUP kolommen is foutief indien er geen GROUP BY clausule is" eng "Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal if there is no GROUP BY clause" @@ -3236,6 +3122,7 @@ ER_MIX_OF_GROUP_FUNC_AND_FIELDS 42000 ger "Das Vermischen von GROUP-Feldern (MIN(),MAX(),COUNT()...) mit Nicht-GROUP-Feldern ist nicht zulässig, wenn keine GROUP-BY-Klausel vorhanden ist" hun "A GROUP mezok (MIN(),MAX(),COUNT()...) kevert hasznalata nem lehetseges GROUP BY hivatkozas nelkul" ita "Il mescolare funzioni di aggregazione (MIN(),MAX(),COUNT()...) e non e` illegale se non c'e` una clausula GROUP BY" + jpn "GROUP BYå¥ãŒç„¡ã„å ´åˆã€é›†è¨ˆé–¢æ•°(MIN(),MAX(),COUNT(),...)ã¨é€šå¸¸ã®åˆ—ã‚’åŒæ™‚ã«ä½¿ç”¨ã§ãã¾ã›ã‚“。" kor "Mixing of GROUP 칼럼s (MIN(),MAX(),COUNT(),...) with no GROUP 칼럼s is illegal if there is no GROUP BY clause" por "Mistura de colunas agrupadas (com MIN(), MAX(), COUNT(), ...) com colunas não agrupadas é ilegal, se não existir uma cláusula de agrupamento (cláusula GROUP BY)" rum "Amestecarea de coloane GROUP (MIN(),MAX(),COUNT()...) fara coloane GROUP este ilegala daca nu exista o clauza GROUP BY" @@ -3245,17 +3132,16 @@ ER_MIX_OF_GROUP_FUNC_AND_FIELDS 42000 swe "Man fÃ¥r ha bÃ¥de GROUP-kolumner (MIN(),MAX(),COUNT()...) och fält i en frÃ¥ga om man inte har en GROUP BY-del" ukr "Ð—Ð¼Ñ–ÑˆÑƒÐ²Ð°Ð½Ð½Ñ GROUP Ñтовбців (MIN(),MAX(),COUNT()...) з не GROUP ÑтовбцÑми Ñ” забороненим, Ñкщо не має GROUP BY" ER_NONEXISTING_GRANT 42000 - cze "Neexistuje odpov-BÃdajÃcà grant pro uživatele '%-.48s' na stroji '%-.64s'" + cze "Neexistuje odpovÃdajÃcà grant pro uživatele '%-.48s' na stroji '%-.64s'" dan "Denne tilladelse findes ikke for brugeren '%-.48s' pÃ¥ vært '%-.64s'" nla "Deze toegang (GRANT) is niet toegekend voor gebruiker '%-.48s' op host '%-.64s'" eng "There is no such grant defined for user '%-.48s' on host '%-.64s'" - jps "ユーザー '%-.48s' (ホスト '%-.64s' ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼) ã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“", est "Sellist õigust ei ole defineeritud kasutajale '%-.48s' masinast '%-.64s'" fre "Un tel droit n'est pas défini pour l'utilisateur '%-.48s' sur l'hôte '%-.64s'" ger "Für Benutzer '%-.48s' auf Host '%-.64s' gibt es keine solche Berechtigung" hun "A '%-.48s' felhasznalonak nincs ilyen joga a '%-.64s' host-on" ita "GRANT non definita per l'utente '%-.48s' dalla macchina '%-.64s'" - jpn "ユーザー '%-.48s' (ホスト '%-.64s' ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼) ã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“" + jpn "ユーザー '%-.48s' (ホスト '%-.64s' 上) ã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“。" kor "ì‚¬ìš©ìž '%-.48s' (호스트 '%-.64s')를 위하여 ì •ì˜ëœ 그런 승ì¸ì€ 없습니다." por "Não existe tal permissão (grant) definida para o usuário '%-.48s' no 'host' '%-.64s'" rum "Nu exista un astfel de grant definit pentru utilzatorul '%-.48s' de pe host-ul '%-.64s'" @@ -3265,47 +3151,47 @@ ER_NONEXISTING_GRANT 42000 swe "Det finns inget privilegium definierat för användare '%-.48s' pÃ¥ '%-.64s'" ukr "Повноважень не визначено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача '%-.48s' з хоÑту '%-.64s'" ER_TABLEACCESS_DENIED_ERROR 42000 - cze "%-.16s p-BÅ™Ãkaz nepÅ™Ãstupný pro uživatele: '%s'@'%s' pro tabulku '%-.192s'" - dan "%-.16s-kommandoen er ikke tilladt for brugeren '%s'@'%s' for tabellen '%-.192s'" - nla "%-.16s commando geweigerd voor gebruiker: '%s'@'%s' voor tabel '%-.192s'" - eng "%-.16s command denied to user '%s'@'%s' for table '%-.192s'" - jps "コマンド %-.16s 㯠ユーザー '%s'@'%s' ,テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“", - est "%-.16s käsk ei ole lubatud kasutajale '%s'@'%s' tabelis '%-.192s'" - fre "La commande '%-.16s' est interdite à l'utilisateur: '%s'@'%s' sur la table '%-.192s'" - ger "%-.16s Befehl nicht erlaubt für Benutzer '%s'@'%s' auf Tabelle '%-.192s'" - hun "%-.16s parancs a '%s'@'%s' felhasznalo szamara nem engedelyezett a '%-.192s' tablaban" - ita "Comando %-.16s negato per l'utente: '%s'@'%s' sulla tabella '%-.192s'" - jpn "コマンド %-.16s 㯠ユーザー '%s'@'%s' ,テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“" - kor "'%-.16s' ëª…ë ¹ì€ ë‹¤ìŒ ì‚¬ìš©ìžì—게 ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤. : '%s'@'%s' for í…Œì´ë¸” '%-.192s'" - por "Comando '%-.16s' negado para o usuário '%s'@'%s' na tabela '%-.192s'" - rum "Comanda %-.16s interzisa utilizatorului: '%s'@'%s' pentru tabela '%-.192s'" - rus "Команда %-.16s запрещена пользователю '%s'@'%s' Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ‹ '%-.192s'" - serbian "%-.16s komanda zabranjena za korisnika '%s'@'%s' za tabelu '%-.192s'" - spa "%-.16s comando negado para usuario: '%s'@'%s' para tabla '%-.192s'" - swe "%-.16s ej tillÃ¥tet för '%s'@'%s' för tabell '%-.192s'" - ukr "%-.16s команда заборонена кориÑтувачу: '%s'@'%s' у таблиці '%-.192s'" + cze "%-.32s pÅ™Ãkaz nepÅ™Ãstupný pro uživatele: '%s'@'%s' pro tabulku '%-.192s'" + dan "%-.32s-kommandoen er ikke tilladt for brugeren '%s'@'%s' for tabellen '%-.192s'" + nla "%-.32s commando geweigerd voor gebruiker: '%s'@'%s' voor tabel '%-.192s'" + eng "%-.32s command denied to user '%s'@'%s' for table '%-.192s'" + jps "コマンド %-.32s 㯠ユーザー '%s'@'%s' ,テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“", + est "%-.32s käsk ei ole lubatud kasutajale '%s'@'%s' tabelis '%-.192s'" + fre "La commande '%-.32s' est interdite à l'utilisateur: '%s'@'%s' sur la table '%-.192s'" + ger "%-.32s Befehl nicht erlaubt für Benutzer '%s'@'%s' auf Tabelle '%-.192s'" + hun "%-.32s parancs a '%s'@'%s' felhasznalo szamara nem engedelyezett a '%-.192s' tablaban" + ita "Comando %-.32s negato per l'utente: '%s'@'%s' sulla tabella '%-.192s'" + jpn "コマンド %-.32s 㯠ユーザー '%s'@'%s' ,テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“" + kor "'%-.32s' ëª…ë ¹ì€ ë‹¤ìŒ ì‚¬ìš©ìžì—게 ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤. : '%s'@'%s' for í…Œì´ë¸” '%-.192s'" + por "Comando '%-.32s' negado para o usuário '%s'@'%s' na tabela '%-.192s'" + rum "Comanda %-.32s interzisa utilizatorului: '%s'@'%s' pentru tabela '%-.192s'" + rus "Команда %-.32s запрещена пользователю '%s'@'%s' Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ‹ '%-.192s'" + serbian "%-.32s komanda zabranjena za korisnika '%s'@'%s' za tabelu '%-.192s'" + spa "%-.32s comando negado para usuario: '%s'@'%s' para tabla '%-.192s'" + swe "%-.32s ej tillÃ¥tet för '%s'@'%s' för tabell '%-.192s'" + ukr "%-.32s команда заборонена кориÑтувачу: '%s'@'%s' у таблиці '%-.192s'" ER_COLUMNACCESS_DENIED_ERROR 42000 - cze "%-.16s p-BÅ™Ãkaz nepÅ™Ãstupný pro uživatele: '%s'@'%s' pro sloupec '%-.192s' v tabulce '%-.192s'" - dan "%-.16s-kommandoen er ikke tilladt for brugeren '%s'@'%s' for kolonne '%-.192s' in tabellen '%-.192s'" - nla "%-.16s commando geweigerd voor gebruiker: '%s'@'%s' voor kolom '%-.192s' in tabel '%-.192s'" - eng "%-.16s command denied to user '%s'@'%s' for column '%-.192s' in table '%-.192s'" - jps "コマンド %-.16s 㯠ユーザー '%s'@'%s'Â¥n カラム'%-.192s' テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“", - est "%-.16s käsk ei ole lubatud kasutajale '%s'@'%s' tulbale '%-.192s' tabelis '%-.192s'" - fre "La commande '%-.16s' est interdite à l'utilisateur: '%s'@'%s' sur la colonne '%-.192s' de la table '%-.192s'" - ger "%-.16s Befehl nicht erlaubt für Benutzer '%s'@'%s' und Feld '%-.192s' in Tabelle '%-.192s'" - hun "%-.16s parancs a '%s'@'%s' felhasznalo szamara nem engedelyezett a '%-.192s' mezo eseten a '%-.192s' tablaban" - ita "Comando %-.16s negato per l'utente: '%s'@'%s' sulla colonna '%-.192s' della tabella '%-.192s'" - jpn "コマンド %-.16s 㯠ユーザー '%s'@'%s'\n カラム'%-.192s' テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“" - kor "'%-.16s' ëª…ë ¹ì€ ë‹¤ìŒ ì‚¬ìš©ìžì—게 ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤. : '%s'@'%s' for 칼럼 '%-.192s' in í…Œì´ë¸” '%-.192s'" - por "Comando '%-.16s' negado para o usuário '%s'@'%s' na coluna '%-.192s', na tabela '%-.192s'" - rum "Comanda %-.16s interzisa utilizatorului: '%s'@'%s' pentru coloana '%-.192s' in tabela '%-.192s'" - rus "Команда %-.16s запрещена пользователю '%s'@'%s' Ð´Ð»Ñ Ñтолбца '%-.192s' в таблице '%-.192s'" - serbian "%-.16s komanda zabranjena za korisnika '%s'@'%s' za kolonu '%-.192s' iz tabele '%-.192s'" - spa "%-.16s comando negado para usuario: '%s'@'%s' para columna '%-.192s' en la tabla '%-.192s'" - swe "%-.16s ej tillÃ¥tet för '%s'@'%s' för kolumn '%-.192s' i tabell '%-.192s'" - ukr "%-.16s команда заборонена кориÑтувачу: '%s'@'%s' Ð´Ð»Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.192s' у таблиці '%-.192s'" + cze "%-.32s pÅ™Ãkaz nepÅ™Ãstupný pro uživatele: '%s'@'%s' pro sloupec '%-.192s' v tabulce '%-.192s'" + dan "%-.32s-kommandoen er ikke tilladt for brugeren '%s'@'%s' for kolonne '%-.192s' in tabellen '%-.192s'" + nla "%-.32s commando geweigerd voor gebruiker: '%s'@'%s' voor kolom '%-.192s' in tabel '%-.192s'" + eng "%-.32s command denied to user '%s'@'%s' for column '%-.192s' in table '%-.192s'" + jps "コマンド %-.32s 㯠ユーザー '%s'@'%s'Â¥n カラム'%-.192s' テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“", + est "%-.32s käsk ei ole lubatud kasutajale '%s'@'%s' tulbale '%-.192s' tabelis '%-.192s'" + fre "La commande '%-.32s' est interdite à l'utilisateur: '%s'@'%s' sur la colonne '%-.192s' de la table '%-.192s'" + ger "%-.32s Befehl nicht erlaubt für Benutzer '%s'@'%s' und Feld '%-.192s' in Tabelle '%-.192s'" + hun "%-.32s parancs a '%s'@'%s' felhasznalo szamara nem engedelyezett a '%-.192s' mezo eseten a '%-.192s' tablaban" + ita "Comando %-.32s negato per l'utente: '%s'@'%s' sulla colonna '%-.192s' della tabella '%-.192s'" + jpn "コマンド %-.32s 㯠ユーザー '%s'@'%s'\n カラム'%-.192s' テーブル '%-.192s' ã«å¯¾ã—ã¦è¨±å¯ã•れã¦ã„ã¾ã›ã‚“" + kor "'%-.32s' ëª…ë ¹ì€ ë‹¤ìŒ ì‚¬ìš©ìžì—게 ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤. : '%s'@'%s' for 칼럼 '%-.192s' in í…Œì´ë¸” '%-.192s'" + por "Comando '%-.32s' negado para o usuário '%s'@'%s' na coluna '%-.192s', na tabela '%-.192s'" + rum "Comanda %-.32s interzisa utilizatorului: '%s'@'%s' pentru coloana '%-.192s' in tabela '%-.192s'" + rus "Команда %-.32s запрещена пользователю '%s'@'%s' Ð´Ð»Ñ Ñтолбца '%-.192s' в таблице '%-.192s'" + serbian "%-.32s komanda zabranjena za korisnika '%s'@'%s' za kolonu '%-.192s' iz tabele '%-.192s'" + spa "%-.32s comando negado para usuario: '%s'@'%s' para columna '%-.192s' en la tabla '%-.192s'" + swe "%-.32s ej tillÃ¥tet för '%s'@'%s' för kolumn '%-.192s' i tabell '%-.192s'" + ukr "%-.32s команда заборонена кориÑтувачу: '%s'@'%s' Ð´Ð»Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.192s' у таблиці '%-.192s'" ER_ILLEGAL_GRANT_FOR_TABLE 42000 - cze "Neplatn-Bý pÅ™Ãkaz GRANT/REVOKE. ProsÃm, pÅ™eÄtÄ›te si v manuálu, jaká privilegia je možné použÃt." + cze "Neplatný pÅ™Ãkaz GRANT/REVOKE. ProsÃm, pÅ™eÄtÄ›te si v manuálu, jaká privilegia je možné použÃt." dan "Forkert GRANT/REVOKE kommando. Se i brugervejledningen hvilke privilegier der kan specificeres." nla "Foutief GRANT/REVOKE commando. Raadpleeg de handleiding welke priveleges gebruikt kunnen worden." eng "Illegal GRANT/REVOKE command; please consult the manual to see which privileges can be used" @@ -3315,7 +3201,7 @@ ER_ILLEGAL_GRANT_FOR_TABLE 42000 greek "Illegal GRANT/REVOKE command; please consult the manual to see which privileges can be used." hun "Ervenytelen GRANT/REVOKE parancs. Kerem, nezze meg a kezikonyvben, milyen jogok lehetsegesek" ita "Comando GRANT/REVOKE illegale. Prego consultare il manuale per sapere quali privilegi possono essere usati." - jpn "Illegal GRANT/REVOKE command; please consult the manual to see which privleges can be used." + jpn "䏿£ãª GRANT/REVOKE コマンドã§ã™ã€‚ã©ã®æ¨©é™ã§åˆ©ç”¨å¯èƒ½ã‹ã¯ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«ã‚’å‚ç…§ã—ã¦ä¸‹ã•ã„。" kor "ìž˜ëª»ëœ GRANT/REVOKE ëª…ë ¹. ì–´ë–¤ 권리와 승ì¸ì´ 사용ë˜ì–´ 질 수 있는지 ë©”ë‰´ì–¼ì„ ë³´ì‹œì˜¤." nor "Illegal GRANT/REVOKE command; please consult the manual to see which privleges can be used." norwegian-ny "Illegal GRANT/REVOKE command; please consult the manual to see which privleges can be used." @@ -3329,7 +3215,7 @@ ER_ILLEGAL_GRANT_FOR_TABLE 42000 swe "Felaktigt GRANT-privilegium använt" ukr "Хибна GRANT/REVOKE команда; прочитайте документацію ÑтоÑовно того, Ñкі права можна викориÑтовувати" ER_GRANT_WRONG_HOST_OR_USER 42000 - cze "Argument p-BÅ™Ãkazu GRANT uživatel nebo stroj je pÅ™ÃliÅ¡ dlouhý" + cze "Argument pÅ™Ãkazu GRANT uživatel nebo stroj je pÅ™ÃliÅ¡ dlouhý" dan "Værts- eller brugernavn for langt til GRANT" nla "De host of gebruiker parameter voor GRANT is te lang" eng "The host or user argument to GRANT is too long" @@ -3338,6 +3224,7 @@ ER_GRANT_WRONG_HOST_OR_USER 42000 ger "Das Host- oder User-Argument für GRANT ist zu lang" hun "A host vagy felhasznalo argumentuma tul hosszu a GRANT parancsban" ita "L'argomento host o utente per la GRANT e` troppo lungo" + jpn "GRANTコマンドã¸ã®ã€ãƒ›ã‚¹ãƒˆåやユーザーåãŒé•·ã™ãŽã¾ã™ã€‚" kor "승ì¸(GRANT)ì„ ìœ„í•˜ì—¬ 사용한 사용ìžë‚˜ í˜¸ìŠ¤íŠ¸ì˜ ê°’ë“¤ì´ ë„ˆë¬´ ê¹ë‹ˆë‹¤." por "Argumento de 'host' ou de usuário para o GRANT é longo demais" rum "Argumentul host-ului sau utilizatorului pentru GRANT e prea lung" @@ -3356,7 +3243,7 @@ ER_NO_SUCH_TABLE 42S02 ger "Tabelle '%-.192s.%-.192s' existiert nicht" hun "A '%-.192s.%-.192s' tabla nem letezik" ita "La tabella '%-.192s.%-.192s' non esiste" - jpn "Table '%-.192s.%-.192s' doesn't exist" + jpn "表 '%-.192s.%-.192s' ã¯å˜åœ¨ã—ã¾ã›ã‚“。" kor "í…Œì´ë¸” '%-.192s.%-.192s' 는 존재하지 않습니다." nor "Table '%-.192s.%-.192s' doesn't exist" norwegian-ny "Table '%-.192s.%-.192s' doesn't exist" @@ -3370,7 +3257,7 @@ ER_NO_SUCH_TABLE 42S02 swe "Det finns ingen tabell som heter '%-.192s.%-.192s'" ukr "Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%-.192s.%-.192s' не Ñ–Ñнує" ER_NONEXISTING_TABLE_GRANT 42000 - cze "Neexistuje odpov-BÃdajÃcà grant pro uživatele '%-.48s' na stroji '%-.64s' pro tabulku '%-.192s'" + cze "Neexistuje odpovÃdajÃcà grant pro uživatele '%-.48s' na stroji '%-.64s' pro tabulku '%-.192s'" dan "Denne tilladelse eksisterer ikke for brugeren '%-.48s' pÃ¥ vært '%-.64s' for tabellen '%-.192s'" nla "Deze toegang (GRANT) is niet toegekend voor gebruiker '%-.48s' op host '%-.64s' op tabel '%-.192s'" eng "There is no such grant defined for user '%-.48s' on host '%-.64s' on table '%-.192s'" @@ -3379,6 +3266,7 @@ ER_NONEXISTING_TABLE_GRANT 42000 ger "Eine solche Berechtigung ist für User '%-.48s' auf Host '%-.64s' an Tabelle '%-.192s' nicht definiert" hun "A '%-.48s' felhasznalo szamara a '%-.64s' host '%-.192s' tablajaban ez a parancs nem engedelyezett" ita "GRANT non definita per l'utente '%-.48s' dalla macchina '%-.64s' sulla tabella '%-.192s'" + jpn "ユーザー '%-.48s' (ホスト '%-.64s' 上) ã®è¡¨ '%-.192s' ã¸ã®æ¨©é™ã¯å®šç¾©ã•れã¦ã„ã¾ã›ã‚“。" kor "ì‚¬ìš©ìž '%-.48s'(호스트 '%-.64s')는 í…Œì´ë¸” '%-.192s'를 사용하기 위하여 ì •ì˜ëœ 승ì¸ì€ 없습니다. " por "Não existe tal permissão (grant) definido para o usuário '%-.48s' no 'host' '%-.64s', na tabela '%-.192s'" rum "Nu exista un astfel de privilegiu (grant) definit pentru utilizatorul '%-.48s' de pe host-ul '%-.64s' pentru tabela '%-.192s'" @@ -3388,7 +3276,7 @@ ER_NONEXISTING_TABLE_GRANT 42000 swe "Det finns inget privilegium definierat för användare '%-.48s' pÃ¥ '%-.64s' för tabell '%-.192s'" ukr "Повноважень не визначено Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача '%-.48s' з хоÑту '%-.64s' Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ– '%-.192s'" ER_NOT_ALLOWED_COMMAND 42000 - cze "Pou-Bžitý pÅ™Ãkaz nenà v této verzi MariaDB povolen" + cze "Použitý pÅ™Ãkaz nenà v této verzi MySQL povolen" dan "Den brugte kommando er ikke tilladt med denne udgave af MariaDB" nla "Het used commando is niet toegestaan in deze MariaDB versie" eng "The used command is not allowed with this MariaDB version" @@ -3397,6 +3285,7 @@ ER_NOT_ALLOWED_COMMAND 42000 ger "Der verwendete Befehl ist in dieser MariaDB-Version nicht zulässig" hun "A hasznalt parancs nem engedelyezett ebben a MariaDB verzioban" ita "Il comando utilizzato non e` supportato in questa versione di MariaDB" + jpn "ã“ã®MySQLãƒãƒ¼ã‚¸ãƒ§ãƒ³ã§ã¯åˆ©ç”¨ã§ããªã„コマンドã§ã™ã€‚" kor "ì‚¬ìš©ëœ ëª…ë ¹ì€ í˜„ìž¬ì˜ MariaDB ë²„ì ¼ì—서는 ì´ìš©ë˜ì§€ 않습니다." por "Comando usado não é permitido para esta versão do MariaDB" rum "Comanda folosita nu este permisa pentru aceasta versiune de MariaDB" @@ -3406,7 +3295,7 @@ ER_NOT_ALLOWED_COMMAND 42000 swe "Du kan inte använda detta kommando med denna MariaDB version" ukr "ВикориÑтовувана команда не дозволена у цій верÑÑ–Ñ— MariaDB" ER_SYNTAX_ERROR 42000 - cze "Va-BÅ¡e syntaxe je nÄ›jaká divná" + cze "VaÅ¡e syntaxe je nÄ›jaká divná" dan "Der er en fejl i SQL syntaksen" nla "Er is iets fout in de gebruikte syntax" eng "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use" @@ -3416,7 +3305,7 @@ ER_SYNTAX_ERROR 42000 greek "You have an error in your SQL syntax" hun "Szintaktikai hiba" ita "Errore di sintassi nella query SQL" - jpn "Something is wrong in your syntax" + jpn "SQL構文エラーã§ã™ã€‚ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«å¯¾å¿œã™ã‚‹ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«ã‚’å‚ç…§ã—ã¦æ£ã—ã„æ§‹æ–‡ã‚’確èªã—ã¦ãã ã•ã„。" kor "SQL êµ¬ë¬¸ì— ì˜¤ë¥˜ê°€ 있습니다." nor "Something is wrong in your syntax" norwegian-ny "Something is wrong in your syntax" @@ -3430,7 +3319,7 @@ ER_SYNTAX_ERROR 42000 swe "Du har nÃ¥got fel i din syntax" ukr "У Ð²Ð°Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° у ÑинтакÑиÑÑ– SQL" ER_DELAYED_CANT_CHANGE_LOCK - cze "Zpo-BždÄ›ný insert threadu nebyl schopen zÃskat požadovaný zámek pro tabulku %-.192s" + cze "ZpoždÄ›ný insert threadu nebyl schopen zÃskat požadovaný zámek pro tabulku %-.192s" dan "Forsinket indsættelse trÃ¥den (delayed insert thread) kunne ikke opnÃ¥ lÃ¥s pÃ¥ tabellen %-.192s" nla "'Delayed insert' thread kon de aangevraagde 'lock' niet krijgen voor tabel %-.192s" eng "Delayed insert thread couldn't get requested lock for table %-.192s" @@ -3439,6 +3328,7 @@ ER_DELAYED_CANT_CHANGE_LOCK ger "Verzögerter (DELAYED) Einfüge-Thread konnte die angeforderte Sperre für Tabelle '%-.192s' nicht erhalten" hun "A kesleltetett beillesztes (delayed insert) thread nem kapott zatolast a %-.192s tablahoz" ita "Il thread di inserimento ritardato non riesce ad ottenere il lock per la tabella %-.192s" + jpn "'Delayed insert'スレッドãŒè¡¨ '%-.192s' ã®ãƒãƒƒã‚¯ã‚’å–å¾—ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" kor "ì§€ì—°ëœ insert ì“°ë ˆë“œê°€ í…Œì´ë¸” %-.192sì˜ ìš”êµ¬ëœ ë½í‚¹ì„ ì²˜ë¦¬í• ìˆ˜ 없었습니다." por "'Thread' de inserção retardada (atrasada) pois não conseguiu obter a trava solicitada para tabela '%-.192s'" rum "Thread-ul pentru inserarea aminata nu a putut obtine lacatul (lock) pentru tabela %-.192s" @@ -3448,7 +3338,7 @@ ER_DELAYED_CANT_CHANGE_LOCK swe "DELAYED INSERT-trÃ¥den kunde inte lÃ¥sa tabell '%-.192s'" ukr "Гілка Ð´Ð»Ñ INSERT DELAYED не може отримати Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ– %-.192s" ER_TOO_MANY_DELAYED_THREADS - cze "P-BÅ™ÃliÅ¡ mnoho zpoždÄ›ných threadů" + cze "PÅ™ÃliÅ¡ mnoho zpoždÄ›ných threadů" dan "For mange slettede trÃ¥de (threads) i brug" nla "Te veel 'delayed' threads in gebruik" eng "Too many delayed threads in use" @@ -3457,6 +3347,7 @@ ER_TOO_MANY_DELAYED_THREADS ger "Zu viele verzögerte (DELAYED) Threads in Verwendung" hun "Tul sok kesletetett thread (delayed)" ita "Troppi threads ritardati in uso" + jpn "'Delayed insert'スレッドãŒå¤šã™ãŽã¾ã™ã€‚" kor "너무 ë§Žì€ ì§€ì—° ì“°ë ˆë“œë¥¼ ì‚¬ìš©í•˜ê³ ìžˆìŠµë‹ˆë‹¤." por "Excesso de 'threads' retardadas (atrasadas) em uso" rum "Prea multe threaduri aminate care sint in uz" @@ -3466,7 +3357,7 @@ ER_TOO_MANY_DELAYED_THREADS swe "Det finns redan 'max_delayed_threads' trÃ¥dar i använding" ukr "Забагато затриманих гілок викориÑтовуєтьÑÑ" ER_ABORTING_CONNECTION 08S01 - cze "Zru-BÅ¡eno spojenà %ld do databáze: '%-.192s' uživatel: '%-.48s' (%-.64s)" + cze "ZruÅ¡eno spojenà %ld do databáze: '%-.192s' uživatel: '%-.48s' (%-.64s)" dan "Afbrudt forbindelse %ld til database: '%-.192s' bruger: '%-.48s' (%-.64s)" nla "Afgebroken verbinding %ld naar db: '%-.192s' gebruiker: '%-.48s' (%-.64s)" eng "Aborted connection %ld to db: '%-.192s' user: '%-.48s' (%-.64s)" @@ -3475,7 +3366,7 @@ ER_ABORTING_CONNECTION 08S01 ger "Abbruch der Verbindung %ld zur Datenbank '%-.192s'. Benutzer: '%-.48s' (%-.64s)" hun "Megszakitott kapcsolat %ld db: '%-.192s' adatbazishoz, felhasznalo: '%-.48s' (%-.64s)" ita "Interrotta la connessione %ld al db: '%-.192s' utente: '%-.48s' (%-.64s)" - jpn "Aborted connection %ld to db: '%-.192s' user: '%-.48s' (%-.64s)" + jpn "接続 %ld ãŒä¸æ–ã•れã¾ã—ãŸã€‚データベース: '%-.192s' ユーザー: '%-.48s' (%-.64s)" kor "ë°ì´íƒ€ë² ì´ìФ ì ‘ì†ì„ 위한 ì—°ê²° %ldê°€ ì¤‘ë‹¨ë¨ : '%-.192s' 사용ìž: '%-.48s' (%-.64s)" nor "Aborted connection %ld to db: '%-.192s' user: '%-.48s' (%-.64s)" norwegian-ny "Aborted connection %ld to db: '%-.192s' user: '%-.48s' (%-.64s)" @@ -3489,7 +3380,7 @@ ER_ABORTING_CONNECTION 08S01 swe "Avbröt länken för trÃ¥d %ld till db '%-.192s', användare '%-.48s' (%-.64s)" ukr "Перервано з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ %ld до бази данних: '%-.192s' кориÑтувача: '%-.48s' (%-.64s)" ER_NET_PACKET_TOO_LARGE 08S01 - cze "Zji-BÅ¡tÄ›n pÅ™Ãchozà packet delšà než 'max_allowed_packet'" + cze "ZjiÅ¡tÄ›n pÅ™Ãchozà packet delšà než 'max_allowed_packet'" dan "Modtog en datapakke som var større end 'max_allowed_packet'" nla "Groter pakket ontvangen dan 'max_allowed_packet'" eng "Got a packet bigger than 'max_allowed_packet' bytes" @@ -3498,6 +3389,7 @@ ER_NET_PACKET_TOO_LARGE 08S01 ger "Empfangenes Paket ist größer als 'max_allowed_packet' Bytes" hun "A kapott csomag nagyobb, mint a maximalisan engedelyezett: 'max_allowed_packet'" ita "Ricevuto un pacchetto piu` grande di 'max_allowed_packet'" + jpn "'max_allowed_packet'よりも大ããªãƒ‘ケットをå—ä¿¡ã—ã¾ã—ãŸã€‚" kor "'max_allowed_packet'보다 ë”í° íŒ¨í‚·ì„ ë°›ì•˜ìŠµë‹ˆë‹¤." por "Obteve um pacote maior do que a taxa máxima de pacotes definida (max_allowed_packet)" rum "Un packet mai mare decit 'max_allowed_packet' a fost primit" @@ -3507,7 +3399,7 @@ ER_NET_PACKET_TOO_LARGE 08S01 swe "Kommunkationspaketet är större än 'max_allowed_packet'" ukr "Отримано пакет більший ніж max_allowed_packet" ER_NET_READ_ERROR_FROM_PIPE 08S01 - cze "Zji-BÅ¡tÄ›na chyba pÅ™i Ätenà z roury spojenÃ" + cze "ZjiÅ¡tÄ›na chyba pÅ™i Ätenà z roury spojenÃ" dan "Fik læsefejl fra forbindelse (connection pipe)" nla "Kreeg leesfout van de verbindings pipe" eng "Got a read error from the connection pipe" @@ -3516,6 +3408,7 @@ ER_NET_READ_ERROR_FROM_PIPE 08S01 ger "Lese-Fehler bei einer Verbindungs-Pipe" hun "Olvasasi hiba a kapcsolat soran" ita "Rilevato un errore di lettura dalla pipe di connessione" + jpn "接続パイプã®èªã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼ã§ã™ã€‚" kor "ì—°ê²° 파ì´í”„로부터 ì—러가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤." por "Obteve um erro de leitura no 'pipe' da conexão" rum "Eroare la citire din cauza lui 'connection pipe'" @@ -3525,7 +3418,7 @@ ER_NET_READ_ERROR_FROM_PIPE 08S01 swe "Fick läsfel frÃ¥n klienten vid läsning frÃ¥n 'PIPE'" ukr "Отримано помилку Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð· комунікаційного каналу" ER_NET_FCNTL_ERROR 08S01 - cze "Zji-BÅ¡tÄ›na chyba fcntl()" + cze "ZjiÅ¡tÄ›na chyba fcntl()" dan "Fik fejlmeddelelse fra fcntl()" nla "Kreeg fout van fcntl()" eng "Got an error from fcntl()" @@ -3534,6 +3427,7 @@ ER_NET_FCNTL_ERROR 08S01 ger "fcntl() lieferte einen Fehler" hun "Hiba a fcntl() fuggvenyben" ita "Rilevato un errore da fcntl()" + jpn "fcntl()ãŒã‚¨ãƒ©ãƒ¼ã‚’è¿”ã—ã¾ã—ãŸã€‚" kor "fcntl() 함수로부터 ì—러가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤." por "Obteve um erro em fcntl()" rum "Eroare obtinuta de la fcntl()" @@ -3543,7 +3437,7 @@ ER_NET_FCNTL_ERROR 08S01 swe "Fick fatalt fel frÃ¥n 'fcntl()'" ukr "Отримано помилкку від fcntl()" ER_NET_PACKETS_OUT_OF_ORDER 08S01 - cze "P-BÅ™Ãchozà packety v chybném poÅ™adÃ" + cze "PÅ™Ãchozà packety v chybném poÅ™adÃ" dan "Modtog ikke datapakker i korrekt rækkefølge" nla "Pakketten in verkeerde volgorde ontvangen" eng "Got packets out of order" @@ -3552,6 +3446,7 @@ ER_NET_PACKETS_OUT_OF_ORDER 08S01 ger "Pakete nicht in der richtigen Reihenfolge empfangen" hun "Helytelen sorrendben erkezett adatcsomagok" ita "Ricevuti pacchetti non in ordine" + jpn "䏿£ãªé †åºã®ãƒ‘ケットをå—ä¿¡ã—ã¾ã—ãŸã€‚" kor "순서가 맞지않는 íŒ¨í‚·ì„ ë°›ì•˜ìŠµë‹ˆë‹¤." por "Obteve pacotes fora de ordem" rum "Packets care nu sint ordonati au fost gasiti" @@ -3561,7 +3456,7 @@ ER_NET_PACKETS_OUT_OF_ORDER 08S01 swe "Kommunikationspaketen kom i fel ordning" ukr "Отримано пакети у неналежному порÑдку" ER_NET_UNCOMPRESS_ERROR 08S01 - cze "Nemohu rozkomprimovat komunika-BÄnà packet" + cze "Nemohu rozkomprimovat komunikaÄnà packet" dan "Kunne ikke dekomprimere kommunikations-pakke (communication packet)" nla "Communicatiepakket kon niet worden gedecomprimeerd" eng "Couldn't uncompress communication packet" @@ -3570,6 +3465,7 @@ ER_NET_UNCOMPRESS_ERROR 08S01 ger "Kommunikationspaket lässt sich nicht entpacken" hun "A kommunikacios adatcsomagok nem tomorithetok ki" ita "Impossibile scompattare i pacchetti di comunicazione" + jpn "圧縮パケットã®å±•é–‹ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" kor "í†µì‹ íŒ¨í‚·ì˜ ì••ì¶•í•´ì œë¥¼ í• ìˆ˜ 없었습니다." por "Não conseguiu descomprimir pacote de comunicação" rum "Nu s-a putut decompresa pachetul de comunicatie (communication packet)" @@ -3579,7 +3475,7 @@ ER_NET_UNCOMPRESS_ERROR 08S01 swe "Kunde inte packa up kommunikationspaketet" ukr "Ðе можу декомпреÑувати комунікаційний пакет" ER_NET_READ_ERROR 08S01 - cze "Zji-BÅ¡tÄ›na chyba pÅ™i Ätenà komunikaÄnÃho packetu" + cze "ZjiÅ¡tÄ›na chyba pÅ™i Ätenà komunikaÄnÃho packetu" dan "Fik fejlmeddelelse ved læsning af kommunikations-pakker (communication packets)" nla "Fout bij het lezen van communicatiepakketten" eng "Got an error reading communication packets" @@ -3588,6 +3484,7 @@ ER_NET_READ_ERROR 08S01 ger "Fehler beim Lesen eines Kommunikationspakets" hun "HIba a kommunikacios adatcsomagok olvasasa soran" ita "Rilevato un errore ricevendo i pacchetti di comunicazione" + jpn "パケットã®å—ä¿¡ã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" kor "í†µì‹ íŒ¨í‚·ì„ ì½ëŠ” 중 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤." por "Obteve um erro na leitura de pacotes de comunicação" rum "Eroare obtinuta citind pachetele de comunicatie (communication packets)" @@ -3597,7 +3494,7 @@ ER_NET_READ_ERROR 08S01 swe "Fick ett fel vid läsning frÃ¥n klienten" ukr "Отримано помилку Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼ÑƒÐ½Ñ–ÐºÐ°Ñ†Ñ–Ð¹Ð½Ð¸Ñ… пакетів" ER_NET_READ_INTERRUPTED 08S01 - cze "Zji-BÅ¡tÄ›n timeout pÅ™i Ätenà komunikaÄnÃho packetu" + cze "ZjiÅ¡tÄ›n timeout pÅ™i Ätenà komunikaÄnÃho packetu" dan "Timeout-fejl ved læsning af kommunukations-pakker (communication packets)" nla "Timeout bij het lezen van communicatiepakketten" eng "Got timeout reading communication packets" @@ -3606,6 +3503,7 @@ ER_NET_READ_INTERRUPTED 08S01 ger "Zeitüberschreitung beim Lesen eines Kommunikationspakets" hun "Idotullepes a kommunikacios adatcsomagok olvasasa soran" ita "Rilevato un timeout ricevendo i pacchetti di comunicazione" + jpn "パケットã®å—ä¿¡ã§ã‚¿ã‚¤ãƒ アウトãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" kor "í†µì‹ íŒ¨í‚·ì„ ì½ëŠ” 중 timeoutì´ ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤." por "Obteve expiração de tempo (timeout) na leitura de pacotes de comunicação" rum "Timeout obtinut citind pachetele de comunicatie (communication packets)" @@ -3615,7 +3513,7 @@ ER_NET_READ_INTERRUPTED 08S01 swe "Fick 'timeout' vid läsning frÃ¥n klienten" ukr "Отримано затримку Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼ÑƒÐ½Ñ–ÐºÐ°Ñ†Ñ–Ð¹Ð½Ð¸Ñ… пакетів" ER_NET_ERROR_ON_WRITE 08S01 - cze "Zji-BÅ¡tÄ›na chyba pÅ™i zápisu komunikaÄnÃho packetu" + cze "ZjiÅ¡tÄ›na chyba pÅ™i zápisu komunikaÄnÃho packetu" dan "Fik fejlmeddelelse ved skrivning af kommunukations-pakker (communication packets)" nla "Fout bij het schrijven van communicatiepakketten" eng "Got an error writing communication packets" @@ -3624,6 +3522,7 @@ ER_NET_ERROR_ON_WRITE 08S01 ger "Fehler beim Schreiben eines Kommunikationspakets" hun "Hiba a kommunikacios csomagok irasa soran" ita "Rilevato un errore inviando i pacchetti di comunicazione" + jpn "パケットã®é€ä¿¡ã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" kor "í†µì‹ íŒ¨í‚·ì„ ê¸°ë¡í•˜ëŠ” 중 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤." por "Obteve um erro na escrita de pacotes de comunicação" rum "Eroare in scrierea pachetelor de comunicatie (communication packets)" @@ -3633,7 +3532,7 @@ ER_NET_ERROR_ON_WRITE 08S01 swe "Fick ett fel vid skrivning till klienten" ukr "Отримано помилку запиÑу комунікаційних пакетів" ER_NET_WRITE_INTERRUPTED 08S01 - cze "Zji-BÅ¡tÄ›n timeout pÅ™i zápisu komunikaÄnÃho packetu" + cze "ZjiÅ¡tÄ›n timeout pÅ™i zápisu komunikaÄnÃho packetu" dan "Timeout-fejl ved skrivning af kommunukations-pakker (communication packets)" nla "Timeout bij het schrijven van communicatiepakketten" eng "Got timeout writing communication packets" @@ -3642,6 +3541,7 @@ ER_NET_WRITE_INTERRUPTED 08S01 ger "Zeitüberschreitung beim Schreiben eines Kommunikationspakets" hun "Idotullepes a kommunikacios csomagok irasa soran" ita "Rilevato un timeout inviando i pacchetti di comunicazione" + jpn "パケットã®é€ä¿¡ã§ã‚¿ã‚¤ãƒ アウトãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" kor "í†µì‹ íŒ¨íŒƒì„ ê¸°ë¡í•˜ëŠ” 중 timeoutì´ ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤." por "Obteve expiração de tempo ('timeout') na escrita de pacotes de comunicação" rum "Timeout obtinut scriind pachetele de comunicatie (communication packets)" @@ -3651,7 +3551,7 @@ ER_NET_WRITE_INTERRUPTED 08S01 swe "Fick 'timeout' vid skrivning till klienten" ukr "Отримано затримку запиÑу комунікаційних пакетів" ER_TOO_LONG_STRING 42000 - cze "V-Býsledný Å™etÄ›zec je delšà než 'max_allowed_packet'" + cze "Výsledný Å™etÄ›zec je delšà než 'max_allowed_packet'" dan "Strengen med resultater er større end 'max_allowed_packet'" nla "Resultaat string is langer dan 'max_allowed_packet'" eng "Result string is longer than 'max_allowed_packet' bytes" @@ -3660,6 +3560,7 @@ ER_TOO_LONG_STRING 42000 ger "Ergebnis-String ist länger als 'max_allowed_packet' Bytes" hun "Ez eredmeny sztring nagyobb, mint a lehetseges maximum: 'max_allowed_packet'" ita "La stringa di risposta e` piu` lunga di 'max_allowed_packet'" + jpn "çµæžœã®æ–‡å—列㌠'max_allowed_packet' よりも大ãã„ã§ã™ã€‚" por "'String' resultante é mais longa do que 'max_allowed_packet'" rum "Sirul rezultat este mai lung decit 'max_allowed_packet'" rus "Ð ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð¸Ñ€ÑƒÑŽÑ‰Ð°Ñ Ñтрока больше, чем 'max_allowed_packet'" @@ -3668,41 +3569,41 @@ ER_TOO_LONG_STRING 42000 swe "Resultatsträngen är längre än max_allowed_packet" ukr "Строка результату довша ніж max_allowed_packet" ER_TABLE_CANT_HANDLE_BLOB 42000 - cze "Typ pou-Bžité tabulky nepodporuje BLOB/TEXT sloupce" - dan "Denne tabeltype understøtter ikke brug af BLOB og TEXT kolonner" - nla "Het gebruikte tabel type ondersteunt geen BLOB/TEXT kolommen" - eng "The used table type doesn't support BLOB/TEXT columns" - est "Valitud tabelitüüp ei toeta BLOB/TEXT tüüpi välju" - fre "Ce type de table ne supporte pas les colonnes BLOB/TEXT" - ger "Der verwendete Tabellentyp unterstützt keine BLOB- und TEXT-Felder" - hun "A hasznalt tabla tipus nem tamogatja a BLOB/TEXT mezoket" - ita "Il tipo di tabella usata non supporta colonne di tipo BLOB/TEXT" - por "Tipo de tabela usado não permite colunas BLOB/TEXT" - rum "Tipul de tabela folosit nu suporta coloane de tip BLOB/TEXT" - rus "ИÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÐµÐ¼Ð°Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð° не поддерживает типы BLOB/TEXT" - serbian "IskoriÅ¡teni tip tabele ne podržava kolone tipa 'BLOB' odnosno 'TEXT'" - spa "El tipo de tabla usada no permite soporte para columnas BLOB/TEXT" - swe "Den använda tabelltypen kan inte hantera BLOB/TEXT-kolumner" - ukr "ВикориÑтаний тип таблиці не підтримує BLOB/TEXT Ñтовбці" + cze "Typ použité tabulky (%s) nepodporuje BLOB/TEXT sloupce" + dan "Denne tabeltype (%s) understøtter ikke brug af BLOB og TEXT kolonner" + nla "Het gebruikte tabel type (%s) ondersteunt geen BLOB/TEXT kolommen" + eng "Storage engine %s doesn't support BLOB/TEXT columns" + est "Valitud tabelitüüp (%s) ei toeta BLOB/TEXT tüüpi välju" + fre "Ce type de table (%s) ne supporte pas les colonnes BLOB/TEXT" + ger "Der verwendete Tabellentyp (%s) unterstützt keine BLOB- und TEXT-Felder" + hun "A hasznalt tabla tipus (%s) nem tamogatja a BLOB/TEXT mezoket" + ita "Il tipo di tabella usata (%s) non supporta colonne di tipo BLOB/TEXT" + por "Tipo de tabela usado (%s) não permite colunas BLOB/TEXT" + rum "Tipul de tabela folosit (%s) nu suporta coloane de tip BLOB/TEXT" + rus "%s таблицы не поддерживают типы BLOB/TEXT" + serbian "IskoriÅ¡teni tip tabele (%s) ne podržava kolone tipa 'BLOB' odnosno 'TEXT'" + spa "El tipo de tabla usada (%s) no permite soporte para columnas BLOB/TEXT" + swe "Den använda tabelltypen (%s) kan inte hantera BLOB/TEXT-kolumner" + ukr "%s таблиці не підтримують BLOB/TEXT Ñтовбці" ER_TABLE_CANT_HANDLE_AUTO_INCREMENT 42000 - cze "Typ pou-Bžité tabulky nepodporuje AUTO_INCREMENT sloupce" - dan "Denne tabeltype understøtter ikke brug af AUTO_INCREMENT kolonner" - nla "Het gebruikte tabel type ondersteunt geen AUTO_INCREMENT kolommen" - eng "The used table type doesn't support AUTO_INCREMENT columns" - est "Valitud tabelitüüp ei toeta AUTO_INCREMENT tüüpi välju" - fre "Ce type de table ne supporte pas les colonnes AUTO_INCREMENT" - ger "Der verwendete Tabellentyp unterstützt keine AUTO_INCREMENT-Felder" - hun "A hasznalt tabla tipus nem tamogatja az AUTO_INCREMENT tipusu mezoket" - ita "Il tipo di tabella usata non supporta colonne di tipo AUTO_INCREMENT" - por "Tipo de tabela usado não permite colunas AUTO_INCREMENT" - rum "Tipul de tabela folosit nu suporta coloane de tip AUTO_INCREMENT" - rus "ИÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÐµÐ¼Ð°Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð° не поддерживает автоинкрементные Ñтолбцы" - serbian "IskoriÅ¡teni tip tabele ne podržava kolone tipa 'AUTO_INCREMENT'" - spa "El tipo de tabla usada no permite soporte para columnas AUTO_INCREMENT" - swe "Den använda tabelltypen kan inte hantera AUTO_INCREMENT-kolumner" - ukr "ВикориÑтаний тип таблиці не підтримує AUTO_INCREMENT Ñтовбці" + cze "Typ použité tabulky (%s) nepodporuje AUTO_INCREMENT sloupce" + dan "Denne tabeltype understøtter (%s) ikke brug af AUTO_INCREMENT kolonner" + nla "Het gebruikte tabel type (%s) ondersteunt geen AUTO_INCREMENT kolommen" + eng "Storage engine %s doesn't support AUTO_INCREMENT columns" + est "Valitud tabelitüüp (%s) ei toeta AUTO_INCREMENT tüüpi välju" + fre "Ce type de table (%s) ne supporte pas les colonnes AUTO_INCREMENT" + ger "Der verwendete Tabellentyp (%s) unterstützt keine AUTO_INCREMENT-Felder" + hun "A hasznalt tabla tipus (%s) nem tamogatja az AUTO_INCREMENT tipusu mezoket" + ita "Il tipo di tabella usata (%s) non supporta colonne di tipo AUTO_INCREMENT" + por "Tipo de tabela usado (%s) não permite colunas AUTO_INCREMENT" + rum "Tipul de tabela folosit (%s) nu suporta coloane de tip AUTO_INCREMENT" + rus "%s таблицы не поддерживают автоинкрементные Ñтолбцы" + serbian "IskoriÅ¡teni tip tabele (%s) ne podržava kolone tipa 'AUTO_INCREMENT'" + spa "El tipo de tabla usada (%s) no permite soporte para columnas AUTO_INCREMENT" + swe "Den använda tabelltypen (%s) kan inte hantera AUTO_INCREMENT-kolumner" + ukr "%s таблиці не підтримують AUTO_INCREMENT Ñтовбці" ER_DELAYED_INSERT_TABLE_LOCKED - cze "INSERT DELAYED nen-Bà možno s tabulkou '%-.192s' použÃt, protože je zamÄená pomocà LOCK TABLES" + cze "INSERT DELAYED nenà možno s tabulkou '%-.192s' použÃt, protože je zamÄená pomocà LOCK TABLES" dan "INSERT DELAYED kan ikke bruges med tabellen '%-.192s', fordi tabellen er lÃ¥st med LOCK TABLES" nla "INSERT DELAYED kan niet worden gebruikt bij table '%-.192s', vanwege een 'lock met LOCK TABLES" eng "INSERT DELAYED can't be used with table '%-.192s' because it is locked with LOCK TABLES" @@ -3712,7 +3613,7 @@ ER_DELAYED_INSERT_TABLE_LOCKED greek "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES" hun "Az INSERT DELAYED nem hasznalhato a '%-.192s' tablahoz, mert a tabla zarolt (LOCK TABLES)" ita "L'inserimento ritardato (INSERT DELAYED) non puo` essere usato con la tabella '%-.192s', perche` soggetta a lock da 'LOCK TABLES'" - jpn "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES" + jpn "表 '%-.192s' ã¯LOCK TABLESã§ãƒãƒƒã‚¯ã•れã¦ã„ã‚‹ãŸã‚ã€INSERT DELAYEDを使用ã§ãã¾ã›ã‚“。" kor "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES" nor "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES" norwegian-ny "INSERT DELAYED can't be used with table '%-.192s', because it is locked with LOCK TABLES" @@ -3726,7 +3627,7 @@ ER_DELAYED_INSERT_TABLE_LOCKED swe "INSERT DELAYED kan inte användas med tabell '%-.192s', emedan den är lÃ¥st med LOCK TABLES" ukr "INSERT DELAYED не може бути викориÑтано з таблицею '%-.192s', тому що Ñ—Ñ— заблоковано з LOCK TABLES" ER_WRONG_COLUMN_NAME 42000 - cze "Nespr-Bávné jméno sloupce '%-.100s'" + cze "Nesprávné jméno sloupce '%-.100s'" dan "Forkert kolonnenavn '%-.100s'" nla "Incorrecte kolom naam '%-.100s'" eng "Incorrect column name '%-.100s'" @@ -3735,6 +3636,7 @@ ER_WRONG_COLUMN_NAME 42000 ger "Falscher Spaltenname '%-.100s'" hun "Ervenytelen mezonev: '%-.100s'" ita "Nome colonna '%-.100s' non corretto" + jpn "列å '%-.100s' ã¯ä¸æ£ã§ã™ã€‚" por "Nome de coluna '%-.100s' incorreto" rum "Nume increct de coloana '%-.100s'" rus "Ðеверное Ð¸Ð¼Ñ Ñтолбца '%-.100s'" @@ -3743,31 +3645,12 @@ ER_WRONG_COLUMN_NAME 42000 swe "Felaktigt kolumnnamn '%-.100s'" ukr "Ðевірне ім'Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%-.100s'" ER_WRONG_KEY_COLUMN 42000 - cze "Handler pou-Bžité tabulky neumà indexovat sloupce '%-.192s'" - dan "Den brugte tabeltype kan ikke indeksere kolonnen '%-.192s'" - nla "De gebruikte tabel 'handler' kan kolom '%-.192s' niet indexeren" - eng "The used storage engine can't index column '%-.192s'" - est "Tabelihandler ei oska indekseerida tulpa '%-.192s'" - fre "Le handler de la table ne peut indexé la colonne '%-.192s'" - ger "Die verwendete Speicher-Engine kann die Spalte '%-.192s' nicht indizieren" - greek "The used table handler can't index column '%-.192s'" - hun "A hasznalt tablakezelo nem tudja a '%-.192s' mezot indexelni" - ita "Il gestore delle tabelle non puo` indicizzare la colonna '%-.192s'" - jpn "The used table handler can't index column '%-.192s'" - kor "The used table handler can't index column '%-.192s'" - nor "The used table handler can't index column '%-.192s'" - norwegian-ny "The used table handler can't index column '%-.192s'" - pol "The used table handler can't index column '%-.192s'" - por "O manipulador de tabela usado não pode indexar a coluna '%-.192s'" - rum "Handler-ul tabelei folosite nu poate indexa coloana '%-.192s'" - rus "ИÑпользованный обработчик таблицы не может проиндекÑировать Ñтолбец '%-.192s'" - serbian "Handler tabele ne može da indeksira kolonu '%-.192s'" - slo "The used table handler can't index column '%-.192s'" - spa "El manipulador de tabla usado no puede indexar columna '%-.192s'" - swe "Den använda tabelltypen kan inte indexera kolumn '%-.192s'" - ukr "ВикориÑтаний вказівник таблиці не може індекÑувати Ñтовбець '%-.192s'" + eng "The storage engine %s can't index column %`s" + ger "Die Speicher-Engine %s kann die Spalte %`s nicht indizieren" + rus "Обработчик таблиц %s не может проиндекÑировать Ñтолбец %`s" + ukr "Вказівник таблиц %s не може індекÑувати Ñтовбець %`s" ER_WRONG_MRG_TABLE - cze "V-BÅ¡echny tabulky v MERGE tabulce nejsou definovány stejnÄ›" + cze "VÅ¡echny tabulky v MERGE tabulce nejsou definovány stejnÄ›" dan "Tabellerne i MERGE er ikke defineret ens" nla "Niet alle tabellen in de MERGE tabel hebben identieke gedefinities" eng "Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist" @@ -3776,7 +3659,7 @@ ER_WRONG_MRG_TABLE ger "Nicht alle Tabellen in der MERGE-Tabelle sind gleich definiert" hun "A MERGE tablaban talalhato tablak definicioja nem azonos" ita "Non tutte le tabelle nella tabella di MERGE sono definite in maniera identica" - jpn "All tables in the MERGE table are not defined identically" + jpn "MERGEè¡¨ã®æ§‹æˆè¡¨ãŒã‚ªãƒ¼ãƒ—ンã§ãã¾ã›ã‚“。列定義ãŒç•°ãªã‚‹ã‹ã€MyISAM表ã§ã¯ãªã„ã‹ã€å˜åœ¨ã—ã¾ã›ã‚“。" kor "All tables in the MERGE table are not defined identically" nor "All tables in the MERGE table are not defined identically" norwegian-ny "All tables in the MERGE table are not defined identically" @@ -3790,7 +3673,7 @@ ER_WRONG_MRG_TABLE swe "Tabellerna i MERGE-tabellen är inte identiskt definierade" ukr "Таблиці у MERGE TABLE мають різну Ñтруктуру" ER_DUP_UNIQUE 23000 - cze "Kv-Bůli unique constraintu nemozu zapsat do tabulky '%-.192s'" + cze "Kvůli unique constraintu nemozu zapsat do tabulky '%-.192s'" dan "Kan ikke skrive til tabellen '%-.192s' fordi det vil bryde CONSTRAINT regler" nla "Kan niet opslaan naar table '%-.192s' vanwege 'unique' beperking" eng "Can't write, because of unique constraint, to table '%-.192s'" @@ -3798,6 +3681,7 @@ ER_DUP_UNIQUE 23000 fre "Écriture impossible à cause d'un index UNIQUE sur la table '%-.192s'" ger "Schreiben in Tabelle '%-.192s' nicht möglich wegen einer Eindeutigkeitsbeschränkung (unique constraint)" hun "A '%-.192s' nem irhato, az egyedi mezok miatt" + jpn "ä¸€æ„æ€§åˆ¶ç´„é•åã®ãŸã‚ã€è¡¨ '%-.192s' ã«æ›¸ãè¾¼ã‚ã¾ã›ã‚“。" ita "Impossibile scrivere nella tabella '%-.192s' per limitazione di unicita`" por "Não pode gravar, devido à restrição UNIQUE, na tabela '%-.192s'" rum "Nu pot scrie pe hard-drive, din cauza constraintului unic (unique constraint) pentru tabela '%-.192s'" @@ -3807,7 +3691,7 @@ ER_DUP_UNIQUE 23000 swe "Kan inte skriva till tabell '%-.192s'; UNIQUE-test" ukr "Ðе можу запиÑати до таблиці '%-.192s', з причини вимог унікальноÑті" ER_BLOB_KEY_WITHOUT_LENGTH 42000 - cze "BLOB sloupec '%-.192s' je pou-Bžit ve specifikaci klÃÄe bez délky" + cze "BLOB sloupec '%-.192s' je použit ve specifikaci klÃÄe bez délky" dan "BLOB kolonnen '%-.192s' brugt i nøglespecifikation uden nøglelængde" nla "BLOB kolom '%-.192s' gebruikt in zoeksleutel specificatie zonder zoeksleutel lengte" eng "BLOB/TEXT column '%-.192s' used in key specification without a key length" @@ -3817,7 +3701,7 @@ ER_BLOB_KEY_WITHOUT_LENGTH 42000 greek "BLOB column '%-.192s' used in key specification without a key length" hun "BLOB mezo '%-.192s' hasznalt a mezo specifikacioban, a mezohossz megadasa nelkul" ita "La colonna '%-.192s' di tipo BLOB e` usata in una chiave senza specificarne la lunghezza" - jpn "BLOB column '%-.192s' used in key specification without a key length" + jpn "BLOB列 '%-.192s' ã‚’ã‚ーã«ä½¿ç”¨ã™ã‚‹ã«ã¯é•·ã•指定ãŒå¿…è¦ã§ã™ã€‚" kor "BLOB column '%-.192s' used in key specification without a key length" nor "BLOB column '%-.192s' used in key specification without a key length" norwegian-ny "BLOB column '%-.192s' used in key specification without a key length" @@ -3831,7 +3715,7 @@ ER_BLOB_KEY_WITHOUT_LENGTH 42000 swe "Du har inte angett nÃ¥gon nyckellängd för BLOB '%-.192s'" ukr "Стовбець BLOB '%-.192s' викориÑтано у визначенні ключа без Ð²ÐºÐ°Ð·Ð°Ð½Ð½Ñ Ð´Ð¾Ð²Ð¶Ð¸Ð½Ð¸ ключа" ER_PRIMARY_CANT_HAVE_NULL 42000 - cze "V-BÅ¡echny Äásti primárnÃho klÃÄe musejà být NOT NULL; pokud potÅ™ebujete NULL, použijte UNIQUE" + cze "VÅ¡echny Äásti primárnÃho klÃÄe musejà být NOT NULL; pokud potÅ™ebujete NULL, použijte UNIQUE" dan "Alle dele af en PRIMARY KEY skal være NOT NULL; Hvis du skal bruge NULL i nøglen, brug UNIQUE istedet" nla "Alle delen van een PRIMARY KEY moeten NOT NULL zijn; Indien u NULL in een zoeksleutel nodig heeft kunt u UNIQUE gebruiken" eng "All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead" @@ -3840,6 +3724,7 @@ ER_PRIMARY_CANT_HAVE_NULL 42000 ger "Alle Teile eines PRIMARY KEY müssen als NOT NULL definiert sein. Wenn NULL in einem Schlüssel benötigt wird, muss ein UNIQUE-Schlüssel verwendet werden" hun "Az elsodleges kulcs teljes egeszeben csak NOT NULL tipusu lehet; Ha NULL mezot szeretne a kulcskent, hasznalja inkabb a UNIQUE-ot" ita "Tutte le parti di una chiave primaria devono essere dichiarate NOT NULL; se necessitano valori NULL nelle chiavi utilizzare UNIQUE" + jpn "PRIMARY KEYã®åˆ—ã¯å…¨ã¦NOT NULLã§ãªã‘れã°ã„ã‘ã¾ã›ã‚“。UNIQUE索引ã§ã‚れã°NULLã‚’å«ã‚€ã“ã¨ãŒå¯èƒ½ã§ã™ã€‚" por "Todas as partes de uma chave primária devem ser não-nulas. Se você precisou usar um valor nulo (NULL) em uma chave, use a cláusula UNIQUE em seu lugar" rum "Toate partile unei chei primare (PRIMARY KEY) trebuie sa fie NOT NULL; Daca aveti nevoie de NULL in vreo cheie, folositi UNIQUE in schimb" rus "Ð’Ñе чаÑти первичного ключа (PRIMARY KEY) должны быть определены как NOT NULL; ЕÑли вам нужна поддержка величин NULL в ключе, воÑпользуйтеÑÑŒ индекÑом UNIQUE" @@ -3848,7 +3733,7 @@ ER_PRIMARY_CANT_HAVE_NULL 42000 swe "Alla delar av en PRIMARY KEY mÃ¥ste vara NOT NULL; Om du vill ha en nyckel med NULL, använd UNIQUE istället" ukr "УÑÑ– чаÑтини PRIMARY KEY повинні бути NOT NULL; Якщо ви потребуєте NULL у ключі, ÑкориÑтайтеÑÑ UNIQUE" ER_TOO_MANY_ROWS 42000 - cze "V-Býsledek obsahuje vÃce než jeden řádek" + cze "Výsledek obsahuje vÃce než jeden řádek" dan "Resultatet bestod af mere end een række" nla "Resultaat bevatte meer dan een rij" eng "Result consisted of more than one row" @@ -3857,6 +3742,7 @@ ER_TOO_MANY_ROWS 42000 ger "Ergebnis besteht aus mehr als einer Zeile" hun "Az eredmeny tobb, mint egy sort tartalmaz" ita "Il risultato consiste di piu` di una riga" + jpn "çµæžœãŒ2行以上ã§ã™ã€‚" por "O resultado consistiu em mais do que uma linha" rum "Resultatul constista din mai multe linii" rus "Ð’ результате возвращена более чем одна Ñтрока" @@ -3865,7 +3751,7 @@ ER_TOO_MANY_ROWS 42000 swe "Resultet bestod av mera än en rad" ukr "Результат знаходитьÑÑ Ñƒ більше ніж одній Ñтроці" ER_REQUIRES_PRIMARY_KEY 42000 - cze "Tento typ tabulky vy-Bžaduje primárnà klÃÄ" + cze "Tento typ tabulky vyžaduje primárnà klÃÄ" dan "Denne tabeltype kræver en primærnøgle" nla "Dit tabel type heeft een primaire zoeksleutel nodig" eng "This table type requires a primary key" @@ -3874,6 +3760,7 @@ ER_REQUIRES_PRIMARY_KEY 42000 ger "Dieser Tabellentyp benötigt einen Primärschlüssel (PRIMARY KEY)" hun "Az adott tablatipushoz elsodleges kulcs hasznalata kotelezo" ita "Questo tipo di tabella richiede una chiave primaria" + jpn "使用ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ã‚¸ãƒ³ã§ã¯ã€PRIMARY KEYãŒå¿…è¦ã§ã™ã€‚" por "Este tipo de tabela requer uma chave primária" rum "Aceast tip de tabela are nevoie de o cheie primara" rus "Ðтот тип таблицы требует Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¿ÐµÑ€Ð²Ð¸Ñ‡Ð½Ð¾Ð³Ð¾ ключа" @@ -3882,7 +3769,7 @@ ER_REQUIRES_PRIMARY_KEY 42000 swe "Denna tabelltyp kräver en PRIMARY KEY" ukr "Цей тип таблиці потребує первинного ключа" ER_NO_RAID_COMPILED - cze "Tato verze MariaDB nen-Bà zkompilována s podporou RAID" + cze "Tato verze MySQL nenà zkompilována s podporou RAID" dan "Denne udgave af MariaDB er ikke oversat med understøttelse af RAID" nla "Deze versie van MariaDB is niet gecompileerd met RAID ondersteuning" eng "This version of MariaDB is not compiled with RAID support" @@ -3891,6 +3778,7 @@ ER_NO_RAID_COMPILED ger "Diese MariaDB-Version ist nicht mit RAID-Unterstützung kompiliert" hun "Ezen leforditott MariaDB verzio nem tartalmaz RAID support-ot" ita "Questa versione di MYSQL non e` compilata con il supporto RAID" + jpn "ã“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®MySQLã¯RAIDサãƒãƒ¼ãƒˆã‚’å«ã‚ã¦ã‚³ãƒ³ãƒ‘イルã•れã¦ã„ã¾ã›ã‚“。" por "Esta versão do MariaDB não foi compilada com suporte a RAID" rum "Aceasta versiune de MariaDB, nu a fost compilata cu suport pentru RAID" rus "Ðта верÑÐ¸Ñ MariaDB Ñкомпилирована без поддержки RAID" @@ -3899,7 +3787,7 @@ ER_NO_RAID_COMPILED swe "Denna version av MariaDB är inte kompilerad med RAID" ukr "Ð¦Ñ Ð²ÐµÑ€ÑÑ–Ñ MariaDB не зкомпільована з підтримкою RAID" ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE - cze "Update tabulky bez WHERE s kl-BÃÄem nenà v módu bezpeÄných update dovoleno" + cze "Update tabulky bez WHERE s klÃÄem nenà v módu bezpeÄných update dovoleno" dan "Du bruger sikker opdaterings modus ('safe update mode') og du forsøgte at opdatere en tabel uden en WHERE klausul, der gør brug af et KEY felt" nla "U gebruikt 'safe update mode' en u probeerde een tabel te updaten zonder een WHERE met een KEY kolom" eng "You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column" @@ -3908,6 +3796,7 @@ ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE ger "MariaDB läuft im sicheren Aktualisierungsmodus (safe update mode). Sie haben versucht, eine Tabelle zu aktualisieren, ohne in der WHERE-Klausel ein KEY-Feld anzugeben" hun "On a biztonsagos update modot hasznalja, es WHERE that uses a KEY column" ita "In modalita` 'safe update' si e` cercato di aggiornare una tabella senza clausola WHERE su una chiave" + jpn "'safe update mode'ã§ã€ç´¢å¼•を利用ã™ã‚‹WHEREå¥ã®ç„¡ã„更新処ç†ã‚’実行ã—よã†ã¨ã—ã¾ã—ãŸã€‚" por "Você está usando modo de atualização seguro e tentou atualizar uma tabela sem uma cláusula WHERE que use uma coluna chave" rus "Ð’Ñ‹ работаете в режиме безопаÑных обновлений (safe update mode) и попробовали изменить таблицу без иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ð¾Ð³Ð¾ Ñтолбца в чаÑти WHERE" serbian "Vi koristite safe update mod servera, a probali ste da promenite podatke bez 'WHERE' komande koja koristi kolonu kljuÄa" @@ -3915,7 +3804,7 @@ ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE swe "Du använder 'säker uppdateringsmod' och försökte uppdatera en tabell utan en WHERE-sats som använder sig av en nyckel" ukr "Ви у режимі безпечного Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ‚Ð° намагаєтеÑÑŒ оновити таблицю без оператора WHERE, що викориÑтовує KEY Ñтовбець" ER_KEY_DOES_NOT_EXITS 42000 S1009 - cze "Kl-BÃÄ '%-.192s' v tabulce '%-.192s' neexistuje" + cze "KlÃÄ '%-.192s' v tabulce '%-.192s' neexistuje" dan "Nøglen '%-.192s' eksisterer ikke i tabellen '%-.192s'" nla "Zoeksleutel '%-.192s' bestaat niet in tabel '%-.192s'" eng "Key '%-.192s' doesn't exist in table '%-.192s'" @@ -3924,6 +3813,7 @@ ER_KEY_DOES_NOT_EXITS 42000 S1009 ger "Schlüssel '%-.192s' existiert in der Tabelle '%-.192s' nicht" hun "A '%-.192s' kulcs nem letezik a '%-.192s' tablaban" ita "La chiave '%-.192s' non esiste nella tabella '%-.192s'" + jpn "索引 '%-.192s' ã¯è¡¨ '%-.192s' ã«ã¯å˜åœ¨ã—ã¾ã›ã‚“。" por "Chave '%-.192s' não existe na tabela '%-.192s'" rus "Ключ '%-.192s' не ÑущеÑтвует в таблице '%-.192s'" serbian "KljuÄ '%-.192s' ne postoji u tabeli '%-.192s'" @@ -3931,7 +3821,7 @@ ER_KEY_DOES_NOT_EXITS 42000 S1009 swe "Nyckel '%-.192s' finns inte in tabell '%-.192s'" ukr "Ключ '%-.192s' не Ñ–Ñнує в таблиці '%-.192s'" ER_CHECK_NO_SUCH_TABLE 42000 - cze "Nemohu otev-BÅ™Ãt tabulku" + cze "Nemohu otevÅ™Ãt tabulku" dan "Kan ikke Ã¥bne tabellen" nla "Kan tabel niet openen" eng "Can't open table" @@ -3940,6 +3830,7 @@ ER_CHECK_NO_SUCH_TABLE 42000 ger "Kann Tabelle nicht öffnen" hun "Nem tudom megnyitni a tablat" ita "Impossibile aprire la tabella" + jpn "表をオープンã§ãã¾ã›ã‚“。" por "Não pode abrir a tabela" rus "Ðевозможно открыть таблицу" serbian "Ne mogu da otvorim tabelu" @@ -3957,7 +3848,7 @@ ER_CHECK_NOT_IMPLEMENTED 42000 greek "The handler for the table doesn't support %s" hun "A tabla kezeloje (handler) nem tamogatja az %s" ita "Il gestore per la tabella non supporta il %s" - jpn "The handler for the table doesn't support %s" + jpn "ã“ã®è¡¨ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ã‚¸ãƒ³ã¯ '%s' を利用ã§ãã¾ã›ã‚“。" kor "The handler for the table doesn't support %s" nor "The handler for the table doesn't support %s" norwegian-ny "The handler for the table doesn't support %s" @@ -3971,7 +3862,7 @@ ER_CHECK_NOT_IMPLEMENTED 42000 swe "Tabellhanteraren för denna tabell kan inte göra %s" ukr "Вказівник таблиці не підтримуе %s" ER_CANT_DO_THIS_DURING_AN_TRANSACTION 25000 - cze "Proveden-Bà tohoto pÅ™Ãkazu nenà v transakci dovoleno" + cze "Provedenà tohoto pÅ™Ãkazu nenà v transakci dovoleno" dan "Du mÃ¥ ikke bruge denne kommando i en transaktion" nla "Het is u niet toegestaan dit commando uit te voeren binnen een transactie" eng "You are not allowed to execute this command in a transaction" @@ -3980,6 +3871,7 @@ ER_CANT_DO_THIS_DURING_AN_TRANSACTION 25000 ger "Sie dürfen diesen Befehl nicht in einer Transaktion ausführen" hun "Az On szamara nem engedelyezett a parancs vegrehajtasa a tranzakcioban" ita "Non puoi eseguire questo comando in una transazione" + jpn "ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ãƒˆãƒ©ãƒ³ã‚¶ã‚¯ã‚·ãƒ§ãƒ³å†…ã§å®Ÿè¡Œã§ãã¾ã›ã‚“。" por "Não lhe é permitido executar este comando em uma transação" rus "Вам не разрешено выполнÑть Ñту команду в транзакции" serbian "Nije Vam dozvoljeno da izvrÅ¡ite ovu komandu u transakciji" @@ -3987,71 +3879,75 @@ ER_CANT_DO_THIS_DURING_AN_TRANSACTION 25000 swe "Du fÃ¥r inte utföra detta kommando i en transaktion" ukr "Вам не дозволено виконувати цю команду в транзакції" ER_ERROR_DURING_COMMIT - cze "Chyba %d p-BÅ™i COMMIT" - dan "Modtog fejl %d mens kommandoen COMMIT blev udført" - nla "Kreeg fout %d tijdens COMMIT" - eng "Got error %d during COMMIT" - est "Viga %d käsu COMMIT täitmisel" - fre "Erreur %d lors du COMMIT" - ger "Fehler %d beim COMMIT" - hun "%d hiba a COMMIT vegrehajtasa soran" - ita "Rilevato l'errore %d durante il COMMIT" - por "Obteve erro %d durante COMMIT" - rus "Получена ошибка %d в процеÑÑе COMMIT" - serbian "GreÅ¡ka %d za vreme izvrÅ¡avanja komande 'COMMIT'" - spa "Obtenido error %d durante COMMIT" - swe "Fick fel %d vid COMMIT" - ukr "Отримано помилку %d під Ñ‡Ð°Ñ COMMIT" + cze "Chyba %M pÅ™i COMMIT" + dan "Modtog fejl %M mens kommandoen COMMIT blev udført" + nla "Kreeg fout %M tijdens COMMIT" + eng "Got error %M during COMMIT" + est "Viga %M käsu COMMIT täitmisel" + fre "Erreur %M lors du COMMIT" + ger "Fehler %M beim COMMIT" + hun "%M hiba a COMMIT vegrehajtasa soran" + ita "Rilevato l'errore %M durante il COMMIT" + jpn "COMMITä¸ã«ã‚¨ãƒ©ãƒ¼ %M ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" + por "Obteve erro %M durante COMMIT" + rus "Получена ошибка %M в процеÑÑе COMMIT" + serbian "GreÅ¡ka %M za vreme izvrÅ¡avanja komande 'COMMIT'" + spa "Obtenido error %M durante COMMIT" + swe "Fick fel %M vid COMMIT" + ukr "Отримано помилку %M під Ñ‡Ð°Ñ COMMIT" ER_ERROR_DURING_ROLLBACK - cze "Chyba %d p-BÅ™i ROLLBACK" - dan "Modtog fejl %d mens kommandoen ROLLBACK blev udført" - nla "Kreeg fout %d tijdens ROLLBACK" - eng "Got error %d during ROLLBACK" - est "Viga %d käsu ROLLBACK täitmisel" - fre "Erreur %d lors du ROLLBACK" - ger "Fehler %d beim ROLLBACK" - hun "%d hiba a ROLLBACK vegrehajtasa soran" - ita "Rilevato l'errore %d durante il ROLLBACK" - por "Obteve erro %d durante ROLLBACK" - rus "Получена ошибка %d в процеÑÑе ROLLBACK" - serbian "GreÅ¡ka %d za vreme izvrÅ¡avanja komande 'ROLLBACK'" - spa "Obtenido error %d durante ROLLBACK" - swe "Fick fel %d vid ROLLBACK" - ukr "Отримано помилку %d під Ñ‡Ð°Ñ ROLLBACK" + cze "Chyba %M pÅ™i ROLLBACK" + dan "Modtog fejl %M mens kommandoen ROLLBACK blev udført" + nla "Kreeg fout %M tijdens ROLLBACK" + eng "Got error %M during ROLLBACK" + est "Viga %M käsu ROLLBACK täitmisel" + fre "Erreur %M lors du ROLLBACK" + ger "Fehler %M beim ROLLBACK" + hun "%M hiba a ROLLBACK vegrehajtasa soran" + ita "Rilevato l'errore %M durante il ROLLBACK" + jpn "ROLLBACKä¸ã«ã‚¨ãƒ©ãƒ¼ %M ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" + por "Obteve erro %M durante ROLLBACK" + rus "Получена ошибка %M в процеÑÑе ROLLBACK" + serbian "GreÅ¡ka %M za vreme izvrÅ¡avanja komande 'ROLLBACK'" + spa "Obtenido error %M durante ROLLBACK" + swe "Fick fel %M vid ROLLBACK" + ukr "Отримано помилку %M під Ñ‡Ð°Ñ ROLLBACK" ER_ERROR_DURING_FLUSH_LOGS - cze "Chyba %d p-BÅ™i FLUSH_LOGS" - dan "Modtog fejl %d mens kommandoen FLUSH_LOGS blev udført" - nla "Kreeg fout %d tijdens FLUSH_LOGS" - eng "Got error %d during FLUSH_LOGS" - est "Viga %d käsu FLUSH_LOGS täitmisel" - fre "Erreur %d lors du FLUSH_LOGS" - ger "Fehler %d bei FLUSH_LOGS" - hun "%d hiba a FLUSH_LOGS vegrehajtasa soran" - ita "Rilevato l'errore %d durante il FLUSH_LOGS" - por "Obteve erro %d durante FLUSH_LOGS" - rus "Получена ошибка %d в процеÑÑе FLUSH_LOGS" - serbian "GreÅ¡ka %d za vreme izvrÅ¡avanja komande 'FLUSH_LOGS'" - spa "Obtenido error %d durante FLUSH_LOGS" - swe "Fick fel %d vid FLUSH_LOGS" - ukr "Отримано помилку %d під Ñ‡Ð°Ñ FLUSH_LOGS" + cze "Chyba %M pÅ™i FLUSH_LOGS" + dan "Modtog fejl %M mens kommandoen FLUSH_LOGS blev udført" + nla "Kreeg fout %M tijdens FLUSH_LOGS" + eng "Got error %M during FLUSH_LOGS" + est "Viga %M käsu FLUSH_LOGS täitmisel" + fre "Erreur %M lors du FLUSH_LOGS" + ger "Fehler %M bei FLUSH_LOGS" + hun "%M hiba a FLUSH_LOGS vegrehajtasa soran" + ita "Rilevato l'errore %M durante il FLUSH_LOGS" + jpn "FLUSH_LOGSä¸ã«ã‚¨ãƒ©ãƒ¼ %M ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" + por "Obteve erro %M durante FLUSH_LOGS" + rus "Получена ошибка %M в процеÑÑе FLUSH_LOGS" + serbian "GreÅ¡ka %M za vreme izvrÅ¡avanja komande 'FLUSH_LOGS'" + spa "Obtenido error %M durante FLUSH_LOGS" + swe "Fick fel %M vid FLUSH_LOGS" + ukr "Отримано помилку %M під Ñ‡Ð°Ñ FLUSH_LOGS" ER_ERROR_DURING_CHECKPOINT - cze "Chyba %d p-BÅ™i CHECKPOINT" - dan "Modtog fejl %d mens kommandoen CHECKPOINT blev udført" - nla "Kreeg fout %d tijdens CHECKPOINT" - eng "Got error %d during CHECKPOINT" - est "Viga %d käsu CHECKPOINT täitmisel" - fre "Erreur %d lors du CHECKPOINT" - ger "Fehler %d bei CHECKPOINT" - hun "%d hiba a CHECKPOINT vegrehajtasa soran" - ita "Rilevato l'errore %d durante il CHECKPOINT" - por "Obteve erro %d durante CHECKPOINT" - rus "Получена ошибка %d в процеÑÑе CHECKPOINT" - serbian "GreÅ¡ka %d za vreme izvrÅ¡avanja komande 'CHECKPOINT'" - spa "Obtenido error %d durante CHECKPOINT" - swe "Fick fel %d vid CHECKPOINT" - ukr "Отримано помилку %d під Ñ‡Ð°Ñ CHECKPOINT" + cze "Chyba %M pÅ™i CHECKPOINT" + dan "Modtog fejl %M mens kommandoen CHECKPOINT blev udført" + nla "Kreeg fout %M tijdens CHECKPOINT" + eng "Got error %M during CHECKPOINT" + est "Viga %M käsu CHECKPOINT täitmisel" + fre "Erreur %M lors du CHECKPOINT" + ger "Fehler %M bei CHECKPOINT" + hun "%M hiba a CHECKPOINT vegrehajtasa soran" + ita "Rilevato l'errore %M durante il CHECKPOINT" + jpn "CHECKPOINTä¸ã«ã‚¨ãƒ©ãƒ¼ %M ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" + por "Obteve erro %M durante CHECKPOINT" + rus "Получена ошибка %M в процеÑÑе CHECKPOINT" + serbian "GreÅ¡ka %M za vreme izvrÅ¡avanja komande 'CHECKPOINT'" + spa "Obtenido error %M durante CHECKPOINT" + swe "Fick fel %M vid CHECKPOINT" + ukr "Отримано помилку %M під Ñ‡Ð°Ñ CHECKPOINT" ER_NEW_ABORTING_CONNECTION 08S01 - cze "Spojen-Bà %ld do databáze: '%-.192s' uživatel: '%-.48s' stroj: '%-.64s' (%-.64s) bylo pÅ™eruÅ¡eno" + cze "Spojenà %ld do databáze: '%-.192s' uživatel: '%-.48s' stroj: '%-.64s' (%-.64s) bylo pÅ™eruÅ¡eno" dan "Afbrød forbindelsen %ld til databasen '%-.192s' bruger: '%-.48s' vært: '%-.64s' (%-.64s)" nla "Afgebroken verbinding %ld naar db: '%-.192s' gebruiker: '%-.48s' host: '%-.64s' (%-.64s)" eng "Aborted connection %ld to db: '%-.192s' user: '%-.48s' host: '%-.64s' (%-.64s)" @@ -4059,37 +3955,25 @@ ER_NEW_ABORTING_CONNECTION 08S01 fre "Connection %ld avortée vers la bd: '%-.192s' utilisateur: '%-.48s' hôte: '%-.64s' (%-.64s)" ger "Abbruch der Verbindung %ld zur Datenbank '%-.192s'. Benutzer: '%-.48s', Host: '%-.64s' (%-.64s)" ita "Interrotta la connessione %ld al db: ''%-.192s' utente: '%-.48s' host: '%-.64s' (%-.64s)" + jpn "接続 %ld ãŒä¸æ–ã•れã¾ã—ãŸã€‚データベース: '%-.192s' ユーザー: '%-.48s' ホスト: '%-.64s' (%-.64s)" por "Conexão %ld abortada para banco de dados '%-.192s' - usuário '%-.48s' - 'host' '%-.64s' ('%-.64s')" rus "Прервано Ñоединение %ld к базе данных '%-.192s' Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%-.48s' Ñ Ñ…Ð¾Ñта '%-.64s' (%-.64s)" serbian "Prekinuta konekcija broj %ld ka bazi: '%-.192s' korisnik je bio: '%-.48s' a host: '%-.64s' (%-.64s)" spa "Abortada conexión %ld para db: '%-.192s' usuario: '%-.48s' servidor: '%-.64s' (%-.64s)" swe "Avbröt länken för trÃ¥d %ld till db '%-.192s', användare '%-.48s', host '%-.64s' (%-.64s)" ukr "Перервано з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ %ld до бази данних: '%-.192s' кориÑтувач: '%-.48s' хоÑÑ‚: '%-.64s' (%-.64s)" -ER_DUMP_NOT_IMPLEMENTED - cze "Handler tabulky nepodporuje bin-Bárnà dump" - dan "Denne tabeltype unserstøtter ikke binært tabeldump" - nla "De 'handler' voor de tabel ondersteund geen binaire tabel dump" - eng "The storage engine for the table does not support binary table dump" - fre "Ce type de table ne supporte pas les copies binaires" - ger "Die Speicher-Engine für die Tabelle unterstützt keinen binären Tabellen-Dump" - ita "Il gestore per la tabella non supporta il dump binario" - jpn "The handler for the table does not support binary table dump" - por "O manipulador de tabela não suporta 'dump' binário de tabela" - rum "The handler for the table does not support binary table dump" - rus "Обработчик Ñтой таблицы не поддерживает двоичного ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ð·Ð° таблицы (dump)" - serbian "Handler tabele ne podržava binarni dump tabele" - spa "El manipulador de tabla no soporta dump para tabla binaria" - swe "Tabellhanteraren klarar inte en binär kopiering av tabellen" - ukr "Цей тип таблиці не підтримує бінарну передачу таблиці" +ER_UNUSED_10 + eng "You should never see it" ER_FLUSH_MASTER_BINLOG_CLOSED eng "Binlog closed, cannot RESET MASTER" ger "Binlog geschlossen. Kann RESET MASTER nicht ausführen" + jpn "ãƒã‚¤ãƒŠãƒªãƒã‚°ãŒã‚¯ãƒãƒ¼ã‚ºã•れã¦ã„ã¾ã™ã€‚RESET MASTER を実行ã§ãã¾ã›ã‚“。" por "Binlog fechado. Não pode fazer RESET MASTER" rus "Двоичный журнал Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð·Ð°ÐºÑ€Ñ‹Ñ‚, невозможно выполнить RESET MASTER" serbian "Binarni log file zatvoren, ne mogu da izvrÅ¡im komandu 'RESET MASTER'" ukr "Реплікаційний лог закрито, не можу виконати RESET MASTER" ER_INDEX_REBUILD - cze "P-BÅ™ebudovánà indexu dumpnuté tabulky '%-.192s' nebylo úspěšné" + cze "PÅ™ebudovánà indexu dumpnuté tabulky '%-.192s' nebylo úspěšné" dan "Kunne ikke genopbygge indekset for den dumpede tabel '%-.192s'" nla "Gefaald tijdens heropbouw index van gedumpte tabel '%-.192s'" eng "Failed rebuilding the index of dumped table '%-.192s'" @@ -4098,6 +3982,7 @@ ER_INDEX_REBUILD greek "Failed rebuilding the index of dumped table '%-.192s'" hun "Failed rebuilding the index of dumped table '%-.192s'" ita "Fallita la ricostruzione dell'indice della tabella copiata '%-.192s'" + jpn "ダンプ表 '%-.192s' ã®ç´¢å¼•冿§‹ç¯‰ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" por "Falhou na reconstrução do Ãndice da tabela 'dumped' '%-.192s'" rus "Ошибка переÑтройки индекÑа Ñохраненной таблицы '%-.192s'" serbian "Izgradnja indeksa dump-ovane tabele '%-.192s' nije uspela" @@ -4111,20 +3996,22 @@ ER_MASTER fre "Erreur reçue du maître: '%-.64s'" ger "Fehler vom Master: '%-.64s'" ita "Errore dal master: '%-.64s" + jpn "マスターã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿ: '%-.64s'" por "Erro no 'master' '%-.64s'" rus "Ошибка от головного Ñервера: '%-.64s'" serbian "GreÅ¡ka iz glavnog servera '%-.64s' u klasteru" spa "Error del master: '%-.64s'" - swe "Fick en master: '%-.64s'" + swe "Fel frÃ¥n master: '%-.64s'" ukr "Помилка від головного: '%-.64s'" ER_MASTER_NET_READ 08S01 - cze "S-BÃÅ¥ová chyba pÅ™i Ätenà z masteru" + cze "SÃÅ¥ová chyba pÅ™i Ätenà z masteru" dan "Netværksfejl ved læsning fra master" nla "Net fout tijdens lezen van master" eng "Net error reading from master" fre "Erreur de lecture réseau reçue du maître" ger "Netzfehler beim Lesen vom Master" ita "Errore di rete durante la ricezione dal master" + jpn "マスターã‹ã‚‰ã®ãƒ‡ãƒ¼ã‚¿å—ä¿¡ä¸ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚¨ãƒ©ãƒ¼" por "Erro de rede lendo do 'master'" rus "Возникла ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð² процеÑÑе коммуникации Ñ Ð³Ð¾Ð»Ð¾Ð²Ð½Ñ‹Ð¼ Ñервером" serbian "GreÅ¡ka u primanju mrežnih paketa sa glavnog servera u klasteru" @@ -4132,13 +4019,14 @@ ER_MASTER_NET_READ 08S01 swe "Fick nätverksfel vid läsning frÃ¥n master" ukr "Мережева помилка Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð²Ñ–Ð´ головного" ER_MASTER_NET_WRITE 08S01 - cze "S-BÃÅ¥ová chyba pÅ™i zápisu na master" + cze "SÃÅ¥ová chyba pÅ™i zápisu na master" dan "Netværksfejl ved skrivning til master" nla "Net fout tijdens schrijven naar master" eng "Net error writing to master" fre "Erreur d'écriture réseau reçue du maître" ger "Netzfehler beim Schreiben zum Master" ita "Errore di rete durante l'invio al master" + jpn "マスターã¸ã®ãƒ‡ãƒ¼ã‚¿é€ä¿¡ä¸ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚¨ãƒ©ãƒ¼" por "Erro de rede gravando no 'master'" rus "Возникла ошибка запиÑи в процеÑÑе коммуникации Ñ Ð³Ð¾Ð»Ð¾Ð²Ð½Ñ‹Ð¼ Ñервером" serbian "GreÅ¡ka u slanju mrežnih paketa na glavni server u klasteru" @@ -4146,7 +4034,7 @@ ER_MASTER_NET_WRITE 08S01 swe "Fick nätverksfel vid skrivning till master" ukr "Мережева помилка запиÑу до головного" ER_FT_MATCHING_KEY_NOT_FOUND - cze "-BŽádný sloupec nemá vytvoÅ™en fulltextový index" + cze "Žádný sloupec nemá vytvoÅ™en fulltextový index" dan "Kan ikke finde en FULLTEXT nøgle som svarer til kolonne listen" nla "Kan geen FULLTEXT index vinden passend bij de kolom lijst" eng "Can't find FULLTEXT index matching the column list" @@ -4154,6 +4042,7 @@ ER_FT_MATCHING_KEY_NOT_FOUND fre "Impossible de trouver un index FULLTEXT correspondant à cette liste de colonnes" ger "Kann keinen FULLTEXT-Index finden, der der Feldliste entspricht" ita "Impossibile trovare un indice FULLTEXT che corrisponda all'elenco delle colonne" + jpn "列リストã«å¯¾å¿œã™ã‚‹å…¨æ–‡ç´¢å¼•(FULLTEXT)ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。" por "Não pode encontrar um Ãndice para o texto todo que combine com a lista de colunas" rus "Ðевозможно отыÑкать полнотекÑтовый (FULLTEXT) индекÑ, ÑоответÑтвующий ÑпиÑку Ñтолбцов" serbian "Ne mogu da pronaÄ‘em 'FULLTEXT' indeks koli odgovara listi kolona" @@ -4161,7 +4050,7 @@ ER_FT_MATCHING_KEY_NOT_FOUND swe "Hittar inte ett FULLTEXT-index i kolumnlistan" ukr "Ðе можу знайти FULLTEXT індекÑ, що відповідає переліку Ñтовбців" ER_LOCK_OR_ACTIVE_TRANSACTION - cze "Nemohu prov-Bést zadaný pÅ™Ãkaz, protože existujà aktivnà zamÄené tabulky nebo aktivnà transakce" + cze "Nemohu provést zadaný pÅ™Ãkaz, protože existujà aktivnà zamÄené tabulky nebo aktivnà transakce" dan "Kan ikke udføre den givne kommando fordi der findes aktive, lÃ¥ste tabeller eller fordi der udføres en transaktion" nla "Kan het gegeven commando niet uitvoeren, want u heeft actieve gelockte tabellen of een actieve transactie" eng "Can't execute the given command because you have active locked tables or an active transaction" @@ -4169,6 +4058,7 @@ ER_LOCK_OR_ACTIVE_TRANSACTION fre "Impossible d'exécuter la commande car vous avez des tables verrouillées ou une transaction active" ger "Kann den angegebenen Befehl wegen einer aktiven Tabellensperre oder einer aktiven Transaktion nicht ausführen" ita "Impossibile eseguire il comando richiesto: tabelle sotto lock o transazione in atto" + jpn "ã™ã§ã«ã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªè¡¨ãƒãƒƒã‚¯ã‚„トランザクションãŒã‚ã‚‹ãŸã‚ã€ã‚³ãƒžãƒ³ãƒ‰ã‚’実行ã§ãã¾ã›ã‚“。" por "Não pode executar o comando dado porque você tem tabelas ativas travadas ou uma transação ativa" rus "Ðевозможно выполнить указанную команду, поÑкольку у Ð²Ð°Ñ Ð¿Ñ€Ð¸ÑутÑтвуют активно заблокированные таблица или Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð°Ñ Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ" serbian "Ne mogu da izvrÅ¡im datu komandu zbog toga Å¡to su tabele zakljuÄane ili je transakcija u toku" @@ -4176,7 +4066,7 @@ ER_LOCK_OR_ACTIVE_TRANSACTION swe "Kan inte utföra kommandot emedan du har en lÃ¥st tabell eller an aktiv transaktion" ukr "Ðе можу виконати подану команду тому, що Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð° або виконуєтьÑÑ Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ñ–Ñ" ER_UNKNOWN_SYSTEM_VARIABLE - cze "Nezn-Bámá systémová promÄ›nná '%-.64s'" + cze "Neznámá systémová promÄ›nná '%-.64s'" dan "Ukendt systemvariabel '%-.64s'" nla "Onbekende systeem variabele '%-.64s'" eng "Unknown system variable '%-.64s'" @@ -4184,6 +4074,7 @@ ER_UNKNOWN_SYSTEM_VARIABLE fre "Variable système '%-.64s' inconnue" ger "Unbekannte Systemvariable '%-.64s'" ita "Variabile di sistema '%-.64s' sconosciuta" + jpn "'%-.64s' ã¯ä¸æ˜Žãªã‚·ã‚¹ãƒ†ãƒ 変数ã§ã™ã€‚" por "Variável de sistema '%-.64s' desconhecida" rus "ÐеизвеÑÑ‚Ð½Ð°Ñ ÑиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s'" serbian "Nepoznata sistemska promenljiva '%-.64s'" @@ -4191,7 +4082,7 @@ ER_UNKNOWN_SYSTEM_VARIABLE swe "Okänd systemvariabel: '%-.64s'" ukr "Ðевідома ÑиÑтемна змінна '%-.64s'" ER_CRASHED_ON_USAGE - cze "Tabulka '%-.192s' je ozna-BÄena jako poruÅ¡ená a mÄ›la by být opravena" + cze "Tabulka '%-.192s' je oznaÄena jako poruÅ¡ená a mÄ›la by být opravena" dan "Tabellen '%-.192s' er markeret med fejl og bør repareres" nla "Tabel '%-.192s' staat als gecrashed gemarkeerd en dient te worden gerepareerd" eng "Table '%-.192s' is marked as crashed and should be repaired" @@ -4199,6 +4090,7 @@ ER_CRASHED_ON_USAGE fre "La table '%-.192s' est marquée 'crashed' et devrait être réparée" ger "Tabelle '%-.192s' ist als defekt markiert und sollte repariert werden" ita "La tabella '%-.192s' e` segnalata come corrotta e deve essere riparata" + jpn "表 '%-.192s' ã¯å£Šã‚Œã¦ã„ã¾ã™ã€‚修復ãŒå¿…è¦ã§ã™ã€‚" por "Tabela '%-.192s' está marcada como danificada e deve ser reparada" rus "Таблица '%-.192s' помечена как иÑÐ¿Ð¾Ñ€Ñ‡ÐµÐ½Ð½Ð°Ñ Ð¸ должна пройти проверку и ремонт" serbian "Tabela '%-.192s' je markirana kao oÅ¡tećena i trebala bi biti popravljena" @@ -4206,7 +4098,7 @@ ER_CRASHED_ON_USAGE swe "Tabell '%-.192s' är trasig och bör repareras med REPAIR TABLE" ukr "Таблицю '%-.192s' марковано Ñк зіпÑовану та Ñ—Ñ— потрібно відновити" ER_CRASHED_ON_REPAIR - cze "Tabulka '%-.192s' je ozna-BÄena jako poruÅ¡ená a poslednà (automatická?) oprava se nezdaÅ™ila" + cze "Tabulka '%-.192s' je oznaÄena jako poruÅ¡ená a poslednà (automatická?) oprava se nezdaÅ™ila" dan "Tabellen '%-.192s' er markeret med fejl og sidste (automatiske?) REPAIR fejlede" nla "Tabel '%-.192s' staat als gecrashed gemarkeerd en de laatste (automatische?) reparatie poging mislukte" eng "Table '%-.192s' is marked as crashed and last (automatic?) repair failed" @@ -4214,6 +4106,7 @@ ER_CRASHED_ON_REPAIR fre "La table '%-.192s' est marquée 'crashed' et le dernier 'repair' a échoué" ger "Tabelle '%-.192s' ist als defekt markiert und der letzte (automatische?) Reparaturversuch schlug fehl" ita "La tabella '%-.192s' e` segnalata come corrotta e l'ultima ricostruzione (automatica?) e` fallita" + jpn "表 '%-.192s' ã¯å£Šã‚Œã¦ã„ã¾ã™ã€‚修復(自動?)ã«ã‚‚失敗ã—ã¦ã„ã¾ã™ã€‚" por "Tabela '%-.192s' está marcada como danificada e a última reparação (automática?) falhou" rus "Таблица '%-.192s' помечена как иÑÐ¿Ð¾Ñ€Ñ‡ÐµÐ½Ð½Ð°Ñ Ð¸ поÑледний (автоматичеÑкий?) ремонт не был уÑпешным" serbian "Tabela '%-.192s' je markirana kao oÅ¡tećena, a zadnja (automatska?) popravka je bila neuspela" @@ -4228,6 +4121,7 @@ ER_WARNING_NOT_COMPLETE_ROLLBACK fre "Attention: certaines tables ne supportant pas les transactions ont été changées et elles ne pourront pas être restituées" ger "Änderungen an einigen nicht transaktionalen Tabellen konnten nicht zurückgerollt werden" ita "Attenzione: Alcune delle modifiche alle tabelle non transazionali non possono essere ripristinate (roll back impossibile)" + jpn "トランザクション対応ã§ã¯ãªã„表ã¸ã®å¤‰æ›´ã¯ãƒãƒ¼ãƒ«ãƒãƒƒã‚¯ã•れã¾ã›ã‚“。" por "Aviso: Algumas tabelas não-transacionais alteradas não puderam ser reconstituÃdas (rolled back)" rus "Внимание: по некоторым измененным нетранзакционным таблицам невозможно будет произвеÑти откат транзакции" serbian "Upozorenje: Neke izmenjene tabele ne podržavaju komandu 'ROLLBACK'" @@ -4242,24 +4136,25 @@ ER_TRANS_CACHE_FULL fre "Cette transaction à commandes multiples nécessite plus de 'max_binlog_cache_size' octets de stockage, augmentez cette variable de mysqld et réessayez" ger "Transaktionen, die aus mehreren Befehlen bestehen, benötigten mehr als 'max_binlog_cache_size' Bytes an Speicher. Btte vergrössern Sie diese Server-Variable versuchen Sie es noch einmal" ita "La transazione a comandi multipli (multi-statement) ha richiesto piu` di 'max_binlog_cache_size' bytes di disco: aumentare questa variabile di mysqld e riprovare" + jpn "複数ステートメントã‹ã‚‰æˆã‚‹ãƒˆãƒ©ãƒ³ã‚¶ã‚¯ã‚·ãƒ§ãƒ³ãŒ 'max_binlog_cache_size' 以上ã®å®¹é‡ã‚’å¿…è¦ã¨ã—ã¾ã—ãŸã€‚ã“ã®ã‚·ã‚¹ãƒ†ãƒ å¤‰æ•°ã‚’å¢—åŠ ã—ã¦ã€å†è©¦è¡Œã—ã¦ãã ã•ã„。" por "Transações multi-declaradas (multi-statement transactions) requeriram mais do que o valor limite (max_binlog_cache_size) de bytes para armazenagem. Aumente o valor desta variável do mysqld e tente novamente" rus "Транзакции, включающей большое количеÑтво команд, потребовалоÑÑŒ более чем 'max_binlog_cache_size' байт. Увеличьте Ñту переменную Ñервера mysqld и попробуйте еще раз" spa "Multipla transición necesita mas que 'max_binlog_cache_size' bytes de almacenamiento. Aumente esta variable mysqld y tente de nuevo" swe "Transaktionen krävde mera än 'max_binlog_cache_size' minne. Öka denna mysqld-variabel och försök pÃ¥ nytt" ukr "Ð¢Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ñ–Ñ Ð· багатьма виразами вимагає більше ніж 'max_binlog_cache_size' байтів Ð´Ð»Ñ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ. Збільште цю змінну mysqld та Ñпробуйте знову" ER_SLAVE_MUST_STOP - dan "Denne handling kunne ikke udføres med kørende slave, brug først kommandoen STOP SLAVE" - nla "Deze operatie kan niet worden uitgevoerd met een actieve slave, doe eerst STOP SLAVE" - eng "This operation cannot be performed with a running slave; run STOP SLAVE first" - fre "Cette opération ne peut être réalisée avec un esclave actif, faites STOP SLAVE d'abord" - ger "Diese Operation kann bei einem aktiven Slave nicht durchgeführt werden. Bitte zuerst STOP SLAVE ausführen" - ita "Questa operazione non puo' essere eseguita con un database 'slave' che gira, lanciare prima STOP SLAVE" - por "Esta operação não pode ser realizada com um 'slave' em execução. Execute STOP SLAVE primeiro" - rus "Ðту операцию невозможно выполнить при работающем потоке подчиненного Ñервера. Сначала выполните STOP SLAVE" - serbian "Ova operacija ne može biti izvrÅ¡ena dok je aktivan podreÄ‘eni server. Zadajte prvo komandu 'STOP SLAVE' da zaustavite podreÄ‘eni server." - spa "Esta operación no puede ser hecha con el esclavo funcionando, primero use STOP SLAVE" - swe "Denna operation kan inte göras under replikering; Gör STOP SLAVE först" - ukr "ÐžÐ¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ðµ може бути виконана з запущеним підлеглим, Ñпочатку виконайте STOP SLAVE" + dan "Denne handling kunne ikke udføres med kørende slave '%2$*1$s', brug først kommandoen STOP SLAVE '%2$*1$s'" + nla "Deze operatie kan niet worden uitgevoerd met een actieve slave '%2$*1$s', doe eerst STOP SLAVE '%2$*1$s'" + eng "This operation cannot be performed as you have a running slave '%2$*1$s'; run STOP SLAVE '%2$*1$s' first" + fre "Cette opération ne peut être réalisée avec un esclave '%2$*1$s' actif, faites STOP SLAVE '%2$*1$s' d'abord" + ger "Diese Operation kann bei einem aktiven Slave '%2$*1$s' nicht durchgeführt werden. Bitte zuerst STOP SLAVE '%2$*1$s' ausführen" + ita "Questa operazione non puo' essere eseguita con un database 'slave' '%2$*1$s' che gira, lanciare prima STOP SLAVE '%2$*1$s'" + por "Esta operação não pode ser realizada com um 'slave' '%2$*1$s' em execução. Execute STOP SLAVE '%2$*1$s' primeiro" + rus "Ðту операцию невозможно выполнить при работающем потоке подчиненного Ñервера %2$*1$s. Сначала выполните STOP SLAVE '%2$*1$s'" + serbian "Ova operacija ne može biti izvrÅ¡ena dok je aktivan podreÄ‘eni '%2$*1$s' server. Zadajte prvo komandu 'STOP SLAVE '%2$*1$s'' da zaustavite podreÄ‘eni server." + spa "Esta operación no puede ser hecha con el esclavo '%2$*1$s' funcionando, primero use STOP SLAVE '%2$*1$s'" + swe "Denna operation kan inte göras under replikering; Du har en aktiv förbindelse till '%2$*1$s'. Gör STOP SLAVE '%2$*1$s' först" + ukr "ÐžÐ¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ðµ може бути виконана з запущеним підлеглим '%2$*1$s', Ñпочатку виконайте STOP SLAVE '%2$*1$s'" ER_SLAVE_NOT_RUNNING dan "Denne handling kræver en kørende slave. Konfigurer en slave og brug kommandoen START SLAVE" nla "Deze operatie vereist een actieve slave, configureer slave en doe dan START SLAVE" @@ -4267,6 +4162,7 @@ ER_SLAVE_NOT_RUNNING fre "Cette opération nécessite un esclave actif, configurez les esclaves et faites START SLAVE" ger "Diese Operation benötigt einen aktiven Slave. Bitte Slave konfigurieren und mittels START SLAVE aktivieren" ita "Questa operaione richiede un database 'slave', configurarlo ed eseguire START SLAVE" + jpn "ã“ã®å‡¦ç†ã¯ã€ç¨¼åƒä¸ã®ã‚¹ãƒ¬ãƒ¼ãƒ–ã§ãªã‘れã°å®Ÿè¡Œã§ãã¾ã›ã‚“。スレーブã®è¨å®šã‚’ã—ã¦START SLAVEコマンドを実行ã—ã¦ãã ã•ã„。" por "Esta operação requer um 'slave' em execução. Configure o 'slave' e execute START SLAVE" rus "Ð”Ð»Ñ Ñтой операции требуетÑÑ Ñ€Ð°Ð±Ð¾Ñ‚Ð°ÑŽÑ‰Ð¸Ð¹ подчиненный Ñервер. Сначала выполните START SLAVE" serbian "Ova operacija zahteva da je aktivan podreÄ‘eni server. KonfiguriÅ¡ite prvo podreÄ‘eni server i onda izvrÅ¡ite komandu 'START SLAVE'" @@ -4280,6 +4176,7 @@ ER_BAD_SLAVE fre "Le server n'est pas configuré comme un esclave, changez le fichier de configuration ou utilisez CHANGE MASTER TO" ger "Der Server ist nicht als Slave konfiguriert. Bitte in der Konfigurationsdatei oder mittels CHANGE MASTER TO beheben" ita "Il server non e' configurato come 'slave', correggere il file di configurazione cambiando CHANGE MASTER TO" + jpn "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¯ã‚¹ãƒ¬ãƒ¼ãƒ–ã¨ã—ã¦è¨å®šã•れã¦ã„ã¾ã›ã‚“。コンフィグファイルã‹CHANGE MASTER TOコマンドã§è¨å®šã—ã¦ä¸‹ã•ã„。" por "O servidor não está configurado como 'slave'. Acerte o arquivo de configuração ou use CHANGE MASTER TO" rus "Ðтот Ñервер не наÑтроен как подчиненный. ВнеÑите иÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² конфигурационном файле или Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ CHANGE MASTER TO" serbian "Server nije konfigurisan kao podreÄ‘eni server, ispravite konfiguracioni file ili na njemu izvrÅ¡ite komandu 'CHANGE MASTER TO'" @@ -4287,18 +4184,20 @@ ER_BAD_SLAVE swe "Servern är inte konfigurerade som en replikationsslav. Ändra konfigurationsfilen eller gör CHANGE MASTER TO" ukr "Сервер не зконфігуровано Ñк підлеглий, виправте це у файлі конфігурації або з CHANGE MASTER TO" ER_MASTER_INFO - eng "Could not initialize master info structure; more error messages can be found in the MariaDB error log" - fre "Impossible d'initialiser les structures d'information de maître, vous trouverez des messages d'erreur supplémentaires dans le journal des erreurs de MariaDB" - ger "Konnte Master-Info-Struktur nicht initialisieren. Weitere Fehlermeldungen können im MariaDB-Error-Log eingesehen werden" - serbian "Nisam mogao da inicijalizujem informacionu strukturu glavnog servera, proverite da li imam privilegije potrebne za pristup file-u 'master.info'" - swe "Kunde inte initialisera replikationsstrukturerna. See MariaDB fel fil för mera information" -ER_SLAVE_THREAD + eng "Could not initialize master info structure for '%.*s'; more error messages can be found in the MariaDB error log" + fre "Impossible d'initialiser les structures d'information de maître '%.*s', vous trouverez des messages d'erreur supplémentaires dans le journal des erreurs de MariaDB" + ger "Konnte Master-Info-Struktur '%.*s' nicht initialisieren. Weitere Fehlermeldungen können im MariaDB-Error-Log eingesehen werden" + jpn "'master info '%.*s''æ§‹é€ ä½“ã®åˆæœŸåŒ–ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚MariaDBエラーãƒã‚°ã§ã‚¨ãƒ©ãƒ¼ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’確èªã—ã¦ãã ã•ã„。" + serbian "Nisam mogao da inicijalizujem informacionu strukturu glavnog servera, proverite da li imam privilegije potrebne za pristup file-u 'master.info' '%.*s'" + swe "Kunde inte initialisera replikationsstrukturerna för '%.*s'. See MariaDB fel fil för mera information" +ER_SLAVE_THREAD dan "Kunne ikke danne en slave-trÃ¥d; check systemressourcerne" nla "Kon slave thread niet aanmaken, controleer systeem resources" eng "Could not create slave thread; check system resources" fre "Impossible de créer une tâche esclave, vérifiez les ressources système" ger "Konnte Slave-Thread nicht starten. Bitte System-Ressourcen überprüfen" ita "Impossibile creare il thread 'slave', controllare le risorse di sistema" + jpn "スレーブスレッドを作æˆã§ãã¾ã›ã‚“。システムリソースを確èªã—ã¦ãã ã•ã„。" por "Não conseguiu criar 'thread' de 'slave'. Verifique os recursos do sistema" rus "Ðевозможно Ñоздать поток подчиненного Ñервера. Проверьте ÑиÑтемные реÑурÑÑ‹" serbian "Nisam mogao da startujem thread za podreÄ‘eni server, proverite sistemske resurse" @@ -4313,6 +4212,7 @@ ER_TOO_MANY_USER_CONNECTIONS 42000 fre "L'utilisateur %-.64s possède déjà plus de 'max_user_connections' connexions actives" ger "Benutzer '%-.64s' hat mehr als 'max_user_connections' aktive Verbindungen" ita "L'utente %-.64s ha gia' piu' di 'max_user_connections' connessioni attive" + jpn "ユーザー '%-.64s' ã¯ã™ã§ã« 'max_user_connections' 以上ã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªæŽ¥ç¶šã‚’è¡Œã£ã¦ã„ã¾ã™ã€‚" por "Usuário '%-.64s' já possui mais que o valor máximo de conexões (max_user_connections) ativas" rus "У Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %-.64s уже больше чем 'max_user_connections' активных Ñоединений" serbian "Korisnik %-.64s već ima viÅ¡e aktivnih konekcija nego Å¡to je to odreÄ‘eno 'max_user_connections' promenljivom" @@ -4322,13 +4222,14 @@ ER_TOO_MANY_USER_CONNECTIONS 42000 ER_SET_CONSTANTS_ONLY dan "Du mÃ¥ kun bruge konstantudtryk med SET" nla "U mag alleen constante expressies gebruiken bij SET" - eng "You may only use constant expressions with SET" + eng "You may only use constant expressions in this statement" est "Ainult konstantsed suurused on lubatud SET klauslis" fre "Seules les expressions constantes sont autorisées avec SET" - ger "Bei SET dürfen nur konstante Ausdrücke verwendet werden" + ger "Bei diesem Befehl dürfen nur konstante Ausdrücke verwendet werden" ita "Si possono usare solo espressioni costanti con SET" + jpn "SET処ç†ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚" por "Você pode usar apenas expressões constantes com SET" - rus "Ð’Ñ‹ можете иÑпользовать в SET только конÑтантные выражениÑ" + rus "С Ñтой командой вы можете иÑпользовать только конÑтантные выражениÑ" serbian "Možete upotrebiti samo konstantan iskaz sa komandom 'SET'" spa "Tu solo debes usar expresiones constantes con SET" swe "Man kan endast använda konstantuttryck med SET" @@ -4341,6 +4242,7 @@ ER_LOCK_WAIT_TIMEOUT fre "Timeout sur l'obtention du verrou" ger "Beim Warten auf eine Sperre wurde die zulässige Wartezeit überschritten. Bitte versuchen Sie, die Transaktion neu zu starten" ita "E' scaduto il timeout per l'attesa del lock" + jpn "ãƒãƒƒã‚¯å¾…ã¡ãŒã‚¿ã‚¤ãƒ アウトã—ã¾ã—ãŸã€‚トランザクションをå†è©¦è¡Œã—ã¦ãã ã•ã„。" por "Tempo de espera (timeout) de travamento excedido. Tente reiniciar a transação." rus "Таймаут Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸ иÑтек; попробуйте перезапуÑтить транзакцию" serbian "Vremenski limit za zakljuÄavanje tabele je istekao; Probajte da ponovo startujete transakciju" @@ -4355,6 +4257,7 @@ ER_LOCK_TABLE_FULL fre "Le nombre total de verrou dépasse la taille de la table des verrous" ger "Die Gesamtzahl der Sperren überschreitet die Größe der Sperrtabelle" ita "Il numero totale di lock e' maggiore della grandezza della tabella di lock" + jpn "ãƒãƒƒã‚¯ã®æ•°ãŒå¤šã™ãŽã¾ã™ã€‚" por "O número total de travamentos excede o tamanho da tabela de travamentos" rus "Общее количеÑтво блокировок превыÑило размеры таблицы блокировок" serbian "Broj totalnih zakljuÄavanja tabele premaÅ¡uje veliÄinu tabele zakljuÄavanja" @@ -4369,6 +4272,7 @@ ER_READ_ONLY_TRANSACTION 25000 fre "Un verrou en update ne peut être acquit pendant une transaction READ UNCOMMITTED" ger "Während einer READ-UNCOMMITTED-Transaktion können keine UPDATE-Sperren angefordert werden" ita "I lock di aggiornamento non possono essere acquisiti durante una transazione 'READ UNCOMMITTED'" + jpn "èªã¿è¾¼ã¿å°‚用トランザクションã§ã™ã€‚" por "Travamentos de atualização não podem ser obtidos durante uma transação de tipo READ UNCOMMITTED" rus "Блокировки обновлений Ð½ÐµÐ»ÑŒÐ·Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ñ‚ÑŒ в процеÑÑе Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð½Ðµ принÑтой (в режиме READ UNCOMMITTED) транзакции" serbian "ZakljuÄavanja izmena ne mogu biti realizovana sve dok traje 'READ UNCOMMITTED' transakcija" @@ -4383,6 +4287,7 @@ ER_DROP_DB_WITH_READ_LOCK fre "DROP DATABASE n'est pas autorisée pendant qu'une tâche possède un verrou global en lecture" ger "DROP DATABASE ist nicht erlaubt, solange der Thread eine globale Lesesperre hält" ita "DROP DATABASE non e' permesso mentre il thread ha un lock globale di lettura" + jpn "ã‚°ãƒãƒ¼ãƒãƒ«ãƒªãƒ¼ãƒ‰ãƒãƒƒã‚¯ã‚’ä¿æŒã—ã¦ã„ã‚‹é–“ã¯ã€DROP DATABASE を実行ã§ãã¾ã›ã‚“。" por "DROP DATABASE não permitido enquanto uma 'thread' está mantendo um travamento global de leitura" rus "Ðе допуÑкаетÑÑ DROP DATABASE, пока поток держит глобальную блокировку чтениÑ" serbian "Komanda 'DROP DATABASE' nije dozvoljena dok thread globalno zakljuÄava Äitanje podataka" @@ -4397,6 +4302,7 @@ ER_CREATE_DB_WITH_READ_LOCK fre "CREATE DATABASE n'est pas autorisée pendant qu'une tâche possède un verrou global en lecture" ger "CREATE DATABASE ist nicht erlaubt, solange der Thread eine globale Lesesperre hält" ita "CREATE DATABASE non e' permesso mentre il thread ha un lock globale di lettura" + jpn "ã‚°ãƒãƒ¼ãƒãƒ«ãƒªãƒ¼ãƒ‰ãƒãƒƒã‚¯ã‚’ä¿æŒã—ã¦ã„ã‚‹é–“ã¯ã€CREATE DATABASE を実行ã§ãã¾ã›ã‚“。" por "CREATE DATABASE não permitido enquanto uma 'thread' está mantendo um travamento global de leitura" rus "Ðе допуÑкаетÑÑ CREATE DATABASE, пока поток держит глобальную блокировку чтениÑ" serbian "Komanda 'CREATE DATABASE' nije dozvoljena dok thread globalno zakljuÄava Äitanje podataka" @@ -4410,6 +4316,7 @@ ER_WRONG_ARGUMENTS fre "Mauvais arguments à %s" ger "Falsche Argumente für %s" ita "Argomenti errati a %s" + jpn "%s ã®å¼•æ•°ãŒä¸æ£ã§ã™" por "Argumentos errados para %s" rus "Ðеверные параметры Ð´Ð»Ñ %s" serbian "PogreÅ¡ni argumenti prosleÄ‘eni na %s" @@ -4436,6 +4343,7 @@ ER_UNION_TABLES_IN_DIFFERENT_DIR fre "Définition de table incorrecte; toutes les tables MERGE doivent être dans la même base de donnée" ger "Falsche Tabellendefinition. Alle MERGE-Tabellen müssen sich in derselben Datenbank befinden" ita "Definizione della tabella errata; tutte le tabelle di tipo MERGE devono essere nello stesso database" + jpn "䏿£ãªè¡¨å®šç¾©ã§ã™ã€‚MERGEè¡¨ã®æ§‹æˆè¡¨ã¯ã™ã¹ã¦åŒã˜ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹å†…ã«ãªã‘れã°ãªã‚Šã¾ã›ã‚“。" por "Definição incorreta da tabela. Todas as tabelas contidas na junção devem estar no mesmo banco de dados." rus "Ðеверное определение таблицы; Ð’Ñе таблицы в MERGE должны принадлежать одной и той же базе данных" serbian "PogreÅ¡na definicija tabele; sve 'MERGE' tabele moraju biti u istoj bazi podataka" @@ -4448,30 +4356,32 @@ ER_LOCK_DEADLOCK 40001 fre "Deadlock découvert en essayant d'obtenir les verrous : essayez de redémarrer la transaction" ger "Beim Versuch, eine Sperre anzufordern, ist ein Deadlock aufgetreten. Versuchen Sie, die Transaktion neu zu starten" ita "Trovato deadlock durante il lock; Provare a far ripartire la transazione" + jpn "ãƒãƒƒã‚¯å–å¾—ä¸ã«ãƒ‡ãƒƒãƒ‰ãƒãƒƒã‚¯ãŒæ¤œå‡ºã•れã¾ã—ãŸã€‚トランザクションをå†è©¦è¡Œã—ã¦ãã ã•ã„。" por "Encontrado um travamento fatal (deadlock) quando tentava obter uma trava. Tente reiniciar a transação." rus "Возникла Ñ‚ÑƒÐ¿Ð¸ÐºÐ¾Ð²Ð°Ñ ÑÐ¸Ñ‚ÑƒÐ°Ñ†Ð¸Ñ Ð² процеÑÑе Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸; Попробуйте перезапуÑтить транзакцию" serbian "Unakrsno zakljuÄavanje pronaÄ‘eno kada sam pokuÅ¡ao da dobijem pravo na zakljuÄavanje; Probajte da restartujete transakciju" spa "Encontrado deadlock cuando tentando obtener el bloqueo; Tente recomenzar la transición" swe "Fick 'DEADLOCK' vid lÃ¥sförsök av block/rad. Försök att starta om transaktionen" ER_TABLE_CANT_HANDLE_FT - nla "Het gebruikte tabel type ondersteund geen FULLTEXT indexen" - eng "The used table type doesn't support FULLTEXT indexes" - est "Antud tabelitüüp ei toeta FULLTEXT indekseid" - fre "Le type de table utilisé ne supporte pas les index FULLTEXT" - ger "Der verwendete Tabellentyp unterstützt keine FULLTEXT-Indizes" - ita "La tabella usata non supporta gli indici FULLTEXT" - por "O tipo de tabela utilizado não suporta Ãndices de texto completo (fulltext indexes)" - rus "ИÑпользуемый тип таблиц не поддерживает полнотекÑтовых индекÑов" - serbian "Upotrebljeni tip tabele ne podržava 'FULLTEXT' indekse" - spa "El tipo de tabla usada no soporta Ãndices FULLTEXT" - swe "Tabelltypen har inte hantering av FULLTEXT-index" - ukr "ВикориÑтаний тип таблиці не підтримує FULLTEXT індекÑів" + nla "Het gebruikte tabel type (%s) ondersteund geen FULLTEXT indexen" + eng "The storage engine %s doesn't support FULLTEXT indexes" + est "Antud tabelitüüp (%s) ei toeta FULLTEXT indekseid" + fre "Le type de table utilisé (%s) ne supporte pas les index FULLTEXT" + ger "Der verwendete Tabellentyp (%s) unterstützt keine FULLTEXT-Indizes" + ita "La tabella usata (%s) non supporta gli indici FULLTEXT" + por "O tipo de tabela utilizado (%s) não suporta Ãndices de texto completo (fulltext indexes)" + rus "ИÑпользуемый тип таблиц (%s) не поддерживает полнотекÑтовых индекÑов" + serbian "Upotrebljeni tip tabele (%s) ne podržava 'FULLTEXT' indekse" + spa "El tipo de tabla usada (%s) no soporta Ãndices FULLTEXT" + swe "Tabelltypen (%s) har inte hantering av FULLTEXT-index" + ukr "ВикориÑтаний тип таблиці (%s) не підтримує FULLTEXT індекÑів" ER_CANNOT_ADD_FOREIGN nla "Kan foreign key beperking niet toevoegen" eng "Cannot add foreign key constraint" fre "Impossible d'ajouter des contraintes d'index externe" ger "Fremdschlüssel-Beschränkung kann nicht hinzugefügt werden" ita "Impossibile aggiungere il vincolo di integrita' referenziale (foreign key constraint)" + jpn "外部ã‚ãƒ¼åˆ¶ç´„ã‚’è¿½åŠ ã§ãã¾ã›ã‚“。" por "Não pode acrescentar uma restrição de chave estrangeira" rus "Ðевозможно добавить Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð²Ð½ÐµÑˆÐ½ÐµÐ³Ð¾ ключа" serbian "Ne mogu da dodam proveru spoljnog kljuÄa" @@ -4485,6 +4395,7 @@ ER_NO_REFERENCED_ROW 23000 greek "Cannot add a child row: a foreign key constraint fails" hun "Cannot add a child row: a foreign key constraint fails" ita "Impossibile aggiungere la riga: un vincolo d'integrita' referenziale non e' soddisfatto" + jpn "親ã‚ーãŒã‚りã¾ã›ã‚“。外部ã‚ー制約é•åã§ã™ã€‚" norwegian-ny "Cannot add a child row: a foreign key constraint fails" por "Não pode acrescentar uma linha filha: uma restrição de chave estrangeira falhou" rus "Ðевозможно добавить или обновить дочернюю Ñтроку: проверка ограничений внешнего ключа не выполнÑетÑÑ" @@ -4497,6 +4408,7 @@ ER_ROW_IS_REFERENCED 23000 greek "Cannot delete a parent row: a foreign key constraint fails" hun "Cannot delete a parent row: a foreign key constraint fails" ita "Impossibile cancellare la riga: un vincolo d'integrita' referenziale non e' soddisfatto" + jpn "åレコードãŒã‚りã¾ã™ã€‚外部ã‚ー制約é•åã§ã™ã€‚" por "Não pode apagar uma linha pai: uma restrição de chave estrangeira falhou" rus "Ðевозможно удалить или обновить родительÑкую Ñтроку: проверка ограничений внешнего ключа не выполнÑетÑÑ" serbian "Ne mogu da izbriÅ¡em roditeljski slog: provera spoljnog kljuÄa je neuspela" @@ -4507,6 +4419,7 @@ ER_CONNECT_TO_MASTER 08S01 eng "Error connecting to master: %-.128s" ger "Fehler bei der Verbindung zum Master: %-.128s" ita "Errore durante la connessione al master: %-.128s" + jpn "マスターã¸ã®æŽ¥ç¶šã‚¨ãƒ©ãƒ¼: %-.128s" por "Erro conectando com o master: %-.128s" rus "Ошибка ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ð³Ð¾Ð»Ð¾Ð²Ð½Ñ‹Ð¼ Ñервером: %-.128s" spa "Error de coneccion a master: %-.128s" @@ -4516,6 +4429,7 @@ ER_QUERY_ON_MASTER eng "Error running query on master: %-.128s" ger "Beim Ausführen einer Abfrage auf dem Master trat ein Fehler auf: %-.128s" ita "Errore eseguendo una query sul master: %-.128s" + jpn "マスターã§ã®ã‚¯ã‚¨ãƒªå®Ÿè¡Œã‚¨ãƒ©ãƒ¼: %-.128s" por "Erro rodando consulta no master: %-.128s" rus "Ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа на головном Ñервере: %-.128s" spa "Error executando el query en master: %-.128s" @@ -4526,6 +4440,7 @@ ER_ERROR_WHEN_EXECUTING_COMMAND est "Viga käsu %s täitmisel: %-.128s" ger "Fehler beim Ausführen des Befehls %s: %-.128s" ita "Errore durante l'esecuzione del comando %s: %-.128s" + jpn "%s コマンドã®å®Ÿè¡Œã‚¨ãƒ©ãƒ¼: %-.128s" por "Erro quando executando comando %s: %-.128s" rus "Ошибка при выполнении команды %s: %-.128s" serbian "GreÅ¡ka pri izvrÅ¡avanju komande %s: %-.128s" @@ -4537,6 +4452,7 @@ ER_WRONG_USAGE est "Vigane %s ja %s kasutus" ger "Falsche Verwendung von %s und %s" ita "Uso errato di %s e %s" + jpn "%s ã® %s ã«é–¢ã™ã‚‹ä¸æ£ãªä½¿ç”¨æ³•ã§ã™ã€‚" por "Uso errado de %s e %s" rus "Ðеверное иÑпользование %s и %s" serbian "PogreÅ¡na upotreba %s i %s" @@ -4549,6 +4465,7 @@ ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT 21000 est "Tulpade arv kasutatud SELECT lausetes ei kattu" ger "Die verwendeten SELECT-Befehle liefern unterschiedliche Anzahlen von Feldern zurück" ita "La SELECT utilizzata ha un numero di colonne differente" + jpn "使用ã®SELECTæ–‡ãŒè¿”ã™åˆ—æ•°ãŒé•ã„ã¾ã™ã€‚" por "Os comandos SELECT usados têm diferente número de colunas" rus "ИÑпользованные операторы выборки (SELECT) дают разное количеÑтво Ñтолбцов" serbian "Upotrebljene 'SELECT' komande adresiraju razliÄit broj kolona" @@ -4560,6 +4477,7 @@ ER_CANT_UPDATE_WITH_READLOCK est "Ei suuda täita päringut konfliktse luku tõttu" ger "Augrund eines READ-LOCK-Konflikts kann die Abfrage nicht ausgeführt werden" ita "Impossibile eseguire la query perche' c'e' un conflitto con in lock di lettura" + jpn "ç«¶åˆã™ã‚‹ãƒªãƒ¼ãƒ‰ãƒãƒƒã‚¯ã‚’ä¿æŒã—ã¦ã„ã‚‹ã®ã§ã€ã‚¯ã‚¨ãƒªã‚’実行ã§ãã¾ã›ã‚“。" por "Não posso executar a consulta porque você tem um conflito de travamento de leitura" rus "Ðевозможно иÑполнить запроÑ, поÑкольку у Ð²Ð°Ñ ÑƒÑтановлены конфликтующие блокировки чтениÑ" serbian "Ne mogu da izvrÅ¡im upit zbog toga Å¡to imate zakljuÄavanja Äitanja podataka u konfliktu" @@ -4571,6 +4489,7 @@ ER_MIXING_NOT_ALLOWED est "Transaktsioone toetavate ning mittetoetavate tabelite kooskasutamine ei ole lubatud" ger "Die gleichzeitige Verwendung von Tabellen mit und ohne Transaktionsunterstützung ist deaktiviert" ita "E' disabilitata la possibilita' di mischiare tabelle transazionali e non-transazionali" + jpn "トランザクション対応ã®è¡¨ã¨éžå¯¾å¿œã®è¡¨ã®åŒæ™‚使用ã¯ç„¡åŠ¹åŒ–ã•れã¦ã„ã¾ã™ã€‚" por "Mistura de tabelas transacional e não-transacional está desabilitada" rus "ИÑпользование транзакционных таблиц нарÑду Ñ Ð½ÐµÑ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ð¾Ð½Ð½Ñ‹Ð¼Ð¸ запрещено" serbian "MeÅ¡anje tabela koje podržavaju transakcije i onih koje ne podržavaju transakcije je iskljuÄeno" @@ -4582,6 +4501,7 @@ ER_DUP_ARGUMENT est "Määrangut '%s' on lauses kasutatud topelt" ger "Option '%s' wird im Befehl zweimal verwendet" ita "L'opzione '%s' e' stata usata due volte nel comando" + jpn "オプション '%s' ãŒ2度使用ã•れã¦ã„ã¾ã™ã€‚" por "Opção '%s' usada duas vezes no comando" rus "ÐžÐ¿Ñ†Ð¸Ñ '%s' дважды иÑпользована в выражении" spa "Opción '%s' usada dos veces en el comando" @@ -4591,6 +4511,7 @@ ER_USER_LIMIT_REACHED 42000 eng "User '%-.64s' has exceeded the '%s' resource (current value: %ld)" ger "Benutzer '%-.64s' hat die Ressourcenbeschränkung '%s' überschritten (aktueller Wert: %ld)" ita "L'utente '%-.64s' ha ecceduto la risorsa '%s' (valore corrente: %ld)" + jpn "ユーザー '%-.64s' ã¯ãƒªã‚½ãƒ¼ã‚¹ã®ä¸Šé™ '%s' ã«é”ã—ã¾ã—ãŸã€‚(ç¾åœ¨å€¤: %ld)" por "Usuário '%-.64s' tem excedido o '%s' recurso (atual valor: %ld)" rus "Пользователь '%-.64s' превыÑил иÑпользование реÑурÑа '%s' (текущее значение: %ld)" spa "Usuario '%-.64s' ha excedido el recurso '%s' (actual valor: %ld)" @@ -4600,6 +4521,7 @@ ER_SPECIFIC_ACCESS_DENIED_ERROR 42000 eng "Access denied; you need (at least one of) the %-.128s privilege(s) for this operation" ger "Kein Zugriff. Hierfür wird die Berechtigung %-.128s benötigt" ita "Accesso non consentito. Serve il privilegio %-.128s per questa operazione" + jpn "ã‚¢ã‚¯ã‚»ã‚¹ã¯æ‹’å¦ã•れã¾ã—ãŸã€‚ã“ã®æ“作ã«ã¯ %-.128s 権é™ãŒ(複数ã®å ´åˆã¯ã©ã‚Œã‹1ã¤)å¿…è¦ã§ã™ã€‚" por "Acesso negado. Você precisa o privilégio %-.128s para essa operação" rus "Ð’ доÑтупе отказано. Вам нужны привилегии %-.128s Ð´Ð»Ñ Ñтой операции" spa "Acceso negado. Usted necesita el privilegio %-.128s para esta operación" @@ -4610,6 +4532,7 @@ ER_LOCAL_VARIABLE eng "Variable '%-.64s' is a SESSION variable and can't be used with SET GLOBAL" ger "Variable '%-.64s' ist eine lokale Variable und kann nicht mit SET GLOBAL verändert werden" ita "La variabile '%-.64s' e' una variabile locale ( SESSION ) e non puo' essere cambiata usando SET GLOBAL" + jpn "変数 '%-.64s' ã¯ã‚»ãƒƒã‚·ãƒ§ãƒ³å¤‰æ•°ã§ã™ã€‚SET GLOBALã§ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“。" por "Variável '%-.64s' é uma SESSION variável e não pode ser usada com SET GLOBAL" rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' ÑвлÑетÑÑ Ð¿Ð¾Ñ‚Ð¾ÐºÐ¾Ð²Ð¾Ð¹ (SESSION) переменной и не может быть изменена Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ SET GLOBAL" spa "Variable '%-.64s' es una SESSION variable y no puede ser usada con SET GLOBAL" @@ -4619,6 +4542,7 @@ ER_GLOBAL_VARIABLE eng "Variable '%-.64s' is a GLOBAL variable and should be set with SET GLOBAL" ger "Variable '%-.64s' ist eine globale Variable und muss mit SET GLOBAL verändert werden" ita "La variabile '%-.64s' e' una variabile globale ( GLOBAL ) e deve essere cambiata usando SET GLOBAL" + jpn "変数 '%-.64s' ã¯ã‚°ãƒãƒ¼ãƒãƒ«å¤‰æ•°ã§ã™ã€‚SET GLOBALを使用ã—ã¦ãã ã•ã„。" por "Variável '%-.64s' é uma GLOBAL variável e deve ser configurada com SET GLOBAL" rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' ÑвлÑетÑÑ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð¾Ð¹ (GLOBAL) переменной, и ее Ñледует изменÑть Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ SET GLOBAL" spa "Variable '%-.64s' es una GLOBAL variable y no puede ser configurada con SET GLOBAL" @@ -4628,6 +4552,7 @@ ER_NO_DEFAULT 42000 eng "Variable '%-.64s' doesn't have a default value" ger "Variable '%-.64s' hat keinen Vorgabewert" ita "La variabile '%-.64s' non ha un valore di default" + jpn "変数 '%-.64s' ã«ã¯ãƒ‡ãƒ•ォルト値ãŒã‚りã¾ã›ã‚“。" por "Variável '%-.64s' não tem um valor padrão" rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' не имеет Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¿Ð¾ умолчанию" spa "Variable '%-.64s' no tiene un valor patrón" @@ -4637,6 +4562,7 @@ ER_WRONG_VALUE_FOR_VAR 42000 eng "Variable '%-.64s' can't be set to the value of '%-.200s'" ger "Variable '%-.64s' kann nicht auf '%-.200s' gesetzt werden" ita "Alla variabile '%-.64s' non puo' essere assegato il valore '%-.200s'" + jpn "変数 '%-.64s' ã«å€¤ '%-.200s' ã‚’è¨å®šã§ãã¾ã›ã‚“。" por "Variável '%-.64s' não pode ser configurada para o valor de '%-.200s'" rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' не может быть уÑтановлена в значение '%-.200s'" spa "Variable '%-.64s' no puede ser configurada para el valor de '%-.200s'" @@ -4646,6 +4572,7 @@ ER_WRONG_TYPE_FOR_VAR 42000 eng "Incorrect argument type to variable '%-.64s'" ger "Falscher Argumenttyp für Variable '%-.64s'" ita "Tipo di valore errato per la variabile '%-.64s'" + jpn "変数 '%-.64s' ã¸ã®å€¤ã®åž‹ãŒä¸æ£ã§ã™ã€‚" por "Tipo errado de argumento para variável '%-.64s'" rus "Ðеверный тип аргумента Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð¹ '%-.64s'" spa "Tipo de argumento equivocado para variable '%-.64s'" @@ -4655,6 +4582,7 @@ ER_VAR_CANT_BE_READ eng "Variable '%-.64s' can only be set, not read" ger "Variable '%-.64s' kann nur verändert, nicht gelesen werden" ita "Alla variabile '%-.64s' e' di sola scrittura quindi puo' essere solo assegnato un valore, non letto" + jpn "変数 '%-.64s' ã¯æ›¸ãè¾¼ã¿å°‚用ã§ã™ã€‚èªã¿è¾¼ã¿ã¯ã§ãã¾ã›ã‚“。" por "Variável '%-.64s' somente pode ser configurada, não lida" rus "ÐŸÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ '%-.64s' может быть только уÑтановлена, но не Ñчитана" spa "Variable '%-.64s' solamente puede ser configurada, no leÃda" @@ -4664,6 +4592,7 @@ ER_CANT_USE_OPTION_HERE 42000 eng "Incorrect usage/placement of '%s'" ger "Falsche Verwendung oder Platzierung von '%s'" ita "Uso/posizione di '%s' sbagliato" + jpn "'%s' ã®ä½¿ç”¨æ³•ã¾ãŸã¯å ´æ‰€ãŒä¸æ£ã§ã™ã€‚" por "Errado uso/colocação de '%s'" rus "Ðеверное иÑпользование или в неверном меÑте указан '%s'" spa "Equivocado uso/colocación de '%s'" @@ -4673,6 +4602,7 @@ ER_NOT_SUPPORTED_YET 42000 eng "This version of MariaDB doesn't yet support '%s'" ger "Diese MariaDB-Version unterstützt '%s' nicht" ita "Questa versione di MariaDB non supporta ancora '%s'" + jpn "ã“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®MariaDBã§ã¯ã€ã¾ã '%s' を利用ã§ãã¾ã›ã‚“。" por "Esta versão de MariaDB não suporta ainda '%s'" rus "Ðта верÑÐ¸Ñ MariaDB пока еще не поддерживает '%s'" spa "Esta versión de MariaDB no soporta todavia '%s'" @@ -4682,6 +4612,7 @@ ER_MASTER_FATAL_ERROR_READING_BINLOG eng "Got fatal error %d from master when reading data from binary log: '%-.320s'" ger "Schwerer Fehler %d: '%-.320s vom Master beim Lesen des binären Logs" ita "Errore fatale %d: '%-.320s' dal master leggendo i dati dal log binario" + jpn "致命的ãªã‚¨ãƒ©ãƒ¼ %d: '%-.320s' ãŒãƒžã‚¹ã‚¿ãƒ¼ã§ãƒã‚¤ãƒŠãƒªãƒã‚°èªã¿è¾¼ã¿ä¸ã«ç™ºç”Ÿã—ã¾ã—ãŸã€‚" por "Obteve fatal erro %d: '%-.320s' do master quando lendo dados do binary log" rus "Получена неиÑÐ¿Ñ€Ð°Ð²Ð¸Ð¼Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° %d: '%-.320s' от головного Ñервера в процеÑÑе выборки данных из двоичного журнала" spa "Recibió fatal error %d: '%-.320s' del master cuando leyendo datos del binary log" @@ -4689,6 +4620,7 @@ ER_MASTER_FATAL_ERROR_READING_BINLOG ER_SLAVE_IGNORED_TABLE eng "Slave SQL thread ignored the query because of replicate-*-table rules" ger "Slave-SQL-Thread hat die Abfrage aufgrund von replicate-*-table-Regeln ignoriert" + jpn "replicate-*-table ルールã«å¾“ã£ã¦ã€ã‚¹ãƒ¬ãƒ¼ãƒ–SQLスレッドã¯ã‚¯ã‚¨ãƒªã‚’無視ã—ã¾ã—ãŸã€‚" nla "Slave SQL thread negeerde de query vanwege replicate-*-table opties" por "Slave SQL thread ignorado a consulta devido à s normas de replicação-*-tabela" spa "Slave SQL thread ignorado el query debido a las reglas de replicación-*-tabla" @@ -4697,12 +4629,14 @@ ER_INCORRECT_GLOBAL_LOCAL_VAR eng "Variable '%-.192s' is a %s variable" serbian "Promenljiva '%-.192s' je %s promenljiva" ger "Variable '%-.192s' ist eine %s-Variable" + jpn "変数 '%-.192s' 㯠%s 変数ã§ã™ã€‚" nla "Variabele '%-.192s' is geen %s variabele" spa "Variable '%-.192s' es una %s variable" swe "Variabel '%-.192s' är av typ %s" ER_WRONG_FK_DEF 42000 eng "Incorrect foreign key definition for '%-.192s': %s" ger "Falsche Fremdschlüssel-Definition für '%-.192s': %s" + jpn "外部ã‚ー '%-.192s' ã®å®šç¾©ã®ä¸æ£: %s" nla "Incorrecte foreign key definitie voor '%-.192s': %s" por "Definição errada da chave estrangeira para '%-.192s': %s" spa "Equivocada definición de llave extranjera para '%-.192s': %s" @@ -4710,6 +4644,7 @@ ER_WRONG_FK_DEF 42000 ER_KEY_REF_DO_NOT_MATCH_TABLE_REF eng "Key reference and table reference don't match" ger "Schlüssel- und Tabellenverweis passen nicht zusammen" + jpn "外部ã‚ーã®å‚照表ã¨å®šç¾©ãŒä¸€è‡´ã—ã¾ã›ã‚“。" nla "Sleutel- en tabelreferentie komen niet overeen" por "Referência da chave e referência da tabela não coincidem" spa "Referencia de llave y referencia de tabla no coinciden" @@ -4717,6 +4652,7 @@ ER_KEY_REF_DO_NOT_MATCH_TABLE_REF ER_OPERAND_COLUMNS 21000 eng "Operand should contain %d column(s)" ger "Operand sollte %d Spalte(n) enthalten" + jpn "オペランド㫠%d 個ã®åˆ—ãŒå¿…è¦ã§ã™ã€‚" nla "Operand behoort %d kolommen te bevatten" rus "Операнд должен Ñодержать %d колонок" spa "Operando debe tener %d columna(s)" @@ -4724,6 +4660,7 @@ ER_OPERAND_COLUMNS 21000 ER_SUBQUERY_NO_1_ROW 21000 eng "Subquery returns more than 1 row" ger "Unterabfrage lieferte mehr als einen Datensatz zurück" + jpn "サブクエリãŒ2行以上ã®çµæžœã‚’è¿”ã—ã¾ã™ã€‚" nla "Subquery retourneert meer dan 1 rij" por "Subconsulta retorna mais que 1 registro" rus "ÐŸÐ¾Ð´Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‰Ð°ÐµÑ‚ более одной запиÑи" @@ -4734,6 +4671,7 @@ ER_UNKNOWN_STMT_HANDLER dan "Unknown prepared statement handler (%.*s) given to %s" eng "Unknown prepared statement handler (%.*s) given to %s" ger "Unbekannter Prepared-Statement-Handler (%.*s) für %s angegeben" + jpn "'%.*s' ã¯ãƒ—リペアードステートメントã®ä¸æ˜Žãªãƒãƒ³ãƒ‰ãƒ«ã§ã™ã€‚(%s ã§æŒ‡å®šã•れã¾ã—ãŸ)" nla "Onebekende prepared statement handler (%.*s) voor %s aangegeven" por "Desconhecido manipulador de declaração preparado (%.*s) determinado para %s" spa "Desconocido preparado comando handler (%.*s) dado para %s" @@ -4742,6 +4680,7 @@ ER_UNKNOWN_STMT_HANDLER ER_CORRUPT_HELP_DB eng "Help database is corrupt or does not exist" ger "Die Hilfe-Datenbank ist beschädigt oder existiert nicht" + jpn "ヘルプデータベースã¯å£Šã‚Œã¦ã„ã‚‹ã‹å˜åœ¨ã—ã¾ã›ã‚“。" nla "Help database is beschadigd of bestaat niet" por "Banco de dado de ajuda corrupto ou não existente" spa "Base de datos Help está corrupto o no existe" @@ -4749,6 +4688,7 @@ ER_CORRUPT_HELP_DB ER_CYCLIC_REFERENCE eng "Cyclic reference on subqueries" ger "Zyklischer Verweis in Unterabfragen" + jpn "サブクエリã®å‚ç…§ãŒãƒ«ãƒ¼ãƒ—ã—ã¦ã„ã¾ã™ã€‚" nla "Cyclische verwijzing in subqueries" por "Referência cÃclica em subconsultas" rus "ЦикличеÑÐºÐ°Ñ ÑÑылка на подзапроÑ" @@ -4758,6 +4698,7 @@ ER_CYCLIC_REFERENCE ER_AUTO_CONVERT eng "Converting column '%s' from %s to %s" ger "Feld '%s' wird von %s nach %s umgewandelt" + jpn "列 '%s' ã‚’ %s ã‹ã‚‰ %s ã¸å¤‰æ›ã—ã¾ã™ã€‚" nla "Veld '%s' wordt van %s naar %s geconverteerd" por "Convertendo coluna '%s' de %s para %s" rus "Преобразование Ð¿Ð¾Ð»Ñ '%s' из %s в %s" @@ -4767,6 +4708,7 @@ ER_AUTO_CONVERT ER_ILLEGAL_REFERENCE 42S22 eng "Reference '%-.64s' not supported (%s)" ger "Verweis '%-.64s' wird nicht unterstützt (%s)" + jpn "'%-.64s' ã®å‚ç…§ã¯ã§ãã¾ã›ã‚“。(%s)" nla "Verwijzing '%-.64s' niet ondersteund (%s)" por "Referência '%-.64s' não suportada (%s)" rus "СÑылка '%-.64s' не поддерживаетÑÑ (%s)" @@ -4776,6 +4718,7 @@ ER_ILLEGAL_REFERENCE 42S22 ER_DERIVED_MUST_HAVE_ALIAS 42000 eng "Every derived table must have its own alias" ger "Für jede abgeleitete Tabelle muss ein eigener Alias angegeben werden" + jpn "導出表ã«ã¯åˆ¥åãŒå¿…é ˆã§ã™ã€‚" nla "Voor elke afgeleide tabel moet een unieke alias worden gebruikt" por "Cada tabela derivada deve ter seu próprio alias" spa "Cada tabla derivada debe tener su propio alias" @@ -4783,6 +4726,7 @@ ER_DERIVED_MUST_HAVE_ALIAS 42000 ER_SELECT_REDUCED 01000 eng "Select %u was reduced during optimization" ger "Select %u wurde während der Optimierung reduziert" + jpn "Select %u ã¯æœ€é©åŒ–ã«ã‚ˆã£ã¦æ¸›ã‚‰ã•れã¾ã—ãŸã€‚" nla "Select %u werd geredureerd tijdens optimtalisatie" por "Select %u foi reduzido durante otimização" rus "Select %u был упразднен в процеÑÑе оптимизации" @@ -4792,6 +4736,7 @@ ER_SELECT_REDUCED 01000 ER_TABLENAME_NOT_ALLOWED_HERE 42000 eng "Table '%-.192s' from one of the SELECTs cannot be used in %-.32s" ger "Tabelle '%-.192s', die in einem der SELECT-Befehle verwendet wurde, kann nicht in %-.32s verwendet werden" + jpn "特定ã®SELECTã®ã¿ã§ä½¿ç”¨ã®è¡¨ '%-.192s' 㯠%-.32s ã§ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“。" nla "Tabel '%-.192s' uit een van de SELECTS kan niet in %-.32s gebruikt worden" por "Tabela '%-.192s' de um dos SELECTs não pode ser usada em %-.32s" spa "Tabla '%-.192s' de uno de los SELECT no puede ser usada en %-.32s" @@ -4799,6 +4744,7 @@ ER_TABLENAME_NOT_ALLOWED_HERE 42000 ER_NOT_SUPPORTED_AUTH_MODE 08004 eng "Client does not support authentication protocol requested by server; consider upgrading MariaDB client" ger "Client unterstützt das vom Server erwartete Authentifizierungsprotokoll nicht. Bitte aktualisieren Sie Ihren MariaDB-Client" + jpn "クライアントã¯ã‚µãƒ¼ãƒãƒ¼ãŒè¦æ±‚ã™ã‚‹èªè¨¼ãƒ—ãƒãƒˆã‚³ãƒ«ã«å¯¾å¿œã§ãã¾ã›ã‚“。MariaDBクライアントã®ã‚¢ãƒƒãƒ—グレードを検討ã—ã¦ãã ã•ã„。" nla "Client ondersteunt het door de server verwachtte authenticatieprotocol niet. Overweeg een nieuwere MariaDB client te gebruiken" por "Cliente não suporta o protocolo de autenticação exigido pelo servidor; considere a atualização do cliente MariaDB" spa "Cliente no soporta protocolo de autenticación solicitado por el servidor; considere actualizar el cliente MariaDB" @@ -4806,6 +4752,7 @@ ER_NOT_SUPPORTED_AUTH_MODE 08004 ER_SPATIAL_CANT_HAVE_NULL 42000 eng "All parts of a SPATIAL index must be NOT NULL" ger "Alle Teile eines SPATIAL-Index müssen als NOT NULL deklariert sein" + jpn "空間索引ã®ã‚ー列㯠NOT NULL ã§ãªã‘れã°ã„ã‘ã¾ã›ã‚“。" nla "Alle delete van een SPATIAL index dienen als NOT NULL gedeclareerd te worden" por "Todas as partes de uma SPATIAL index devem ser NOT NULL" spa "Todas las partes de una SPATIAL index deben ser NOT NULL" @@ -4813,6 +4760,7 @@ ER_SPATIAL_CANT_HAVE_NULL 42000 ER_COLLATION_CHARSET_MISMATCH 42000 eng "COLLATION '%s' is not valid for CHARACTER SET '%s'" ger "COLLATION '%s' ist für CHARACTER SET '%s' ungültig" + jpn "COLLATION '%s' 㯠CHARACTER SET '%s' ã«é©ç”¨ã§ãã¾ã›ã‚“。" nla "COLLATION '%s' is niet geldig voor CHARACTER SET '%s'" por "COLLATION '%s' não é válida para CHARACTER SET '%s'" spa "COLLATION '%s' no es válido para CHARACTER SET '%s'" @@ -4820,6 +4768,7 @@ ER_COLLATION_CHARSET_MISMATCH 42000 ER_SLAVE_WAS_RUNNING eng "Slave is already running" ger "Slave läuft bereits" + jpn "スレーブã¯ã™ã§ã«ç¨¼åƒä¸ã§ã™ã€‚" nla "Slave is reeds actief" por "O slave já está rodando" spa "Slave ya está funcionando" @@ -4827,6 +4776,7 @@ ER_SLAVE_WAS_RUNNING ER_SLAVE_WAS_NOT_RUNNING eng "Slave already has been stopped" ger "Slave wurde bereits angehalten" + jpn "スレーブã¯ã™ã§ã«åœæ¢ã—ã¦ã„ã¾ã™ã€‚" nla "Slave is reeds gestopt" por "O slave já está parado" spa "Slave ya fué parado" @@ -4834,24 +4784,28 @@ ER_SLAVE_WAS_NOT_RUNNING ER_TOO_BIG_FOR_UNCOMPRESS eng "Uncompressed data size too large; the maximum size is %d (probably, length of uncompressed data was corrupted)" ger "Unkomprimierte Daten sind zu groß. Die maximale Größe beträgt %d (wahrscheinlich wurde die Länge der unkomprimierten Daten beschädigt)" + jpn "展開後ã®ãƒ‡ãƒ¼ã‚¿ãŒå¤§ãã™ãŽã¾ã™ã€‚最大サイズ㯠%d ã§ã™ã€‚(展開後データã®é•·ã•æƒ…å ±ãŒå£Šã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ã‚‚ã‚りã¾ã™ã€‚)" nla "Ongecomprimeerder data is te groot; de maximum lengte is %d (waarschijnlijk, de lengte van de gecomprimeerde data was beschadigd)" por "Tamanho muito grande dos dados des comprimidos. O máximo tamanho é %d. (provavelmente, o comprimento dos dados descomprimidos está corrupto)" spa "Tamaño demasiado grande para datos descomprimidos. El máximo tamaño es %d. (probablemente, extensión de datos descomprimidos fué corrompida)" ER_ZLIB_Z_MEM_ERROR eng "ZLIB: Not enough memory" ger "ZLIB: Nicht genug Speicher" + jpn "ZLIB: メモリä¸è¶³ã§ã™ã€‚" nla "ZLIB: Onvoldoende geheugen" por "ZLIB: Não suficiente memória disponÃvel" spa "Z_MEM_ERROR: No suficiente memoria para zlib" ER_ZLIB_Z_BUF_ERROR eng "ZLIB: Not enough room in the output buffer (probably, length of uncompressed data was corrupted)" ger "ZLIB: Im Ausgabepuffer ist nicht genug Platz vorhanden (wahrscheinlich wurde die Länge der unkomprimierten Daten beschädigt)" + jpn "ZLIB: 出力ãƒãƒƒãƒ•ã‚¡ã«å分ãªç©ºããŒã‚りã¾ã›ã‚“。(展開後データã®é•·ã•æƒ…å ±ãŒå£Šã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ã‚‚ã‚りã¾ã™ã€‚)" nla "ZLIB: Onvoldoende ruimte in uitgaande buffer (waarschijnlijk, de lengte van de ongecomprimeerde data was beschadigd)" por "ZLIB: Não suficiente espaço no buffer emissor (provavelmente, o comprimento dos dados descomprimidos está corrupto)" spa "Z_BUF_ERROR: No suficiente espacio en el búfer de salida para zlib (probablemente, extensión de datos descomprimidos fué corrompida)" ER_ZLIB_Z_DATA_ERROR eng "ZLIB: Input data corrupted" ger "ZLIB: Eingabedaten beschädigt" + jpn "ZLIB: 入力データãŒå£Šã‚Œã¦ã„ã¾ã™ã€‚" nla "ZLIB: Invoer data beschadigd" por "ZLIB: Dados de entrada está corrupto" spa "ZLIB: Dato de entrada fué corrompido para zlib" @@ -4860,18 +4814,21 @@ ER_CUT_VALUE_GROUP_CONCAT ER_WARN_TOO_FEW_RECORDS 01000 eng "Row %lu doesn't contain data for all columns" ger "Zeile %lu enthält nicht für alle Felder Daten" + jpn "行 %lu ã¯ã™ã¹ã¦ã®åˆ—ã¸ã®ãƒ‡ãƒ¼ã‚¿ã‚’å«ã‚“ã§ã„ã¾ã›ã‚“。" nla "Rij %lu bevat niet de data voor alle kolommen" por "Conta de registro é menor que a conta de coluna na linha %lu" spa "LÃnea %lu no contiene datos para todas las columnas" ER_WARN_TOO_MANY_RECORDS 01000 eng "Row %lu was truncated; it contained more data than there were input columns" ger "Zeile %lu gekürzt, die Zeile enthielt mehr Daten, als es Eingabefelder gibt" + jpn "行 %lu ã¯ãƒ‡ãƒ¼ã‚¿ã‚’切りæ¨ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚列よりも多ã„データをå«ã‚“ã§ã„ã¾ã—ãŸã€‚" nla "Regel %lu ingekort, bevatte meer data dan invoer kolommen" por "Conta de registro é maior que a conta de coluna na linha %lu" spa "LÃnea %lu fué truncada; La misma contine mas datos que las que existen en las columnas de entrada" ER_WARN_NULL_TO_NOTNULL 22004 eng "Column set to default value; NULL supplied to NOT NULL column '%s' at row %lu" ger "Feld auf Vorgabewert gesetzt, da NULL für NOT-NULL-Feld '%s' in Zeile %lu angegeben" + jpn "列ã«ãƒ‡ãƒ•ォルト値ãŒè¨å®šã•れã¾ã—ãŸã€‚NOT NULLã®åˆ— '%s' 㫠行 %lu ã§ NULL ãŒä¸Žãˆã‚‰ã‚Œã¾ã—ãŸã€‚" por "Dado truncado, NULL fornecido para NOT NULL coluna '%s' na linha %lu" spa "Datos truncado, NULL suministrado para NOT NULL columna '%s' en la lÃnea %lu" ER_WARN_DATA_OUT_OF_RANGE 22003 @@ -4879,17 +4836,20 @@ ER_WARN_DATA_OUT_OF_RANGE 22003 WARN_DATA_TRUNCATED 01000 eng "Data truncated for column '%s' at row %lu" ger "Daten abgeschnitten für Feld '%s' in Zeile %lu" + jpn "列 '%s' 㮠行 %lu ã§ãƒ‡ãƒ¼ã‚¿ãŒåˆ‡ã‚Šæ¨ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚" por "Dado truncado para coluna '%s' na linha %lu" spa "Datos truncados para columna '%s' en la lÃnea %lu" ER_WARN_USING_OTHER_HANDLER eng "Using storage engine %s for table '%s'" ger "Speicher-Engine %s wird für Tabelle '%s' benutzt" + jpn "ストレージエンジン %s ãŒè¡¨ '%s' ã«åˆ©ç”¨ã•れã¦ã„ã¾ã™ã€‚" por "Usando engine de armazenamento %s para tabela '%s'" spa "Usando motor de almacenamiento %s para tabla '%s'" swe "Använder handler %s för tabell '%s'" ER_CANT_AGGREGATE_2COLLATIONS eng "Illegal mix of collations (%s,%s) and (%s,%s) for operation '%s'" ger "Unerlaubte Mischung von Sortierreihenfolgen (%s, %s) und (%s, %s) für Operation '%s'" + jpn "ç…§åˆé †åº (%s,%s) 㨠(%s,%s) ã®æ··åœ¨ã¯æ“作 '%s' ã§ã¯ä¸æ£ã§ã™ã€‚" por "Combinação ilegal de collations (%s,%s) e (%s,%s) para operação '%s'" spa "Ilegal mezcla de collations (%s,%s) y (%s,%s) para operación '%s'" ER_DROP_USER @@ -4898,42 +4858,50 @@ ER_DROP_USER ER_REVOKE_GRANTS eng "Can't revoke all privileges for one or more of the requested users" ger "Kann nicht alle Berechtigungen widerrufen, die für einen oder mehrere Benutzer gewährt wurden" + jpn "指定ã•れãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã‹ã‚‰æŒ‡å®šã•れãŸå…¨ã¦ã®æ¨©é™ã‚’剥奪ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" por "Não pode revocar todos os privilégios, grant para um ou mais dos usuários pedidos" spa "No puede revocar todos los privilegios, derecho para uno o mas de los usuarios solicitados" ER_CANT_AGGREGATE_3COLLATIONS eng "Illegal mix of collations (%s,%s), (%s,%s), (%s,%s) for operation '%s'" ger "Unerlaubte Mischung von Sortierreihenfolgen (%s, %s), (%s, %s), (%s, %s) für Operation '%s'" + jpn "ç…§åˆé †åº (%s,%s), (%s,%s), (%s,%s) ã®æ··åœ¨ã¯æ“作 '%s' ã§ã¯ä¸æ£ã§ã™ã€‚" por "Ilegal combinação de collations (%s,%s), (%s,%s), (%s,%s) para operação '%s'" spa "Ilegal mezcla de collations (%s,%s), (%s,%s), (%s,%s) para operación '%s'" ER_CANT_AGGREGATE_NCOLLATIONS eng "Illegal mix of collations for operation '%s'" ger "Unerlaubte Mischung von Sortierreihenfolgen für Operation '%s'" + jpn "æ“作 '%s' ã§ã¯ä¸æ£ãªç…§åˆé †åºã®æ··åœ¨ã§ã™ã€‚" por "Ilegal combinação de collations para operação '%s'" spa "Ilegal mezcla de collations para operación '%s'" ER_VARIABLE_IS_NOT_STRUCT eng "Variable '%-.64s' is not a variable component (can't be used as XXXX.variable_name)" ger "Variable '%-.64s' ist keine Variablen-Komponente (kann nicht als XXXX.variablen_name verwendet werden)" + jpn "変数 '%-.64s' ã¯æ§‹é€ å¤‰æ•°ã®æ§‹æˆè¦ç´ ã§ã¯ã‚りã¾ã›ã‚“。(XXXX.変数å ã¨ã„ã†æŒ‡å®šã¯ã§ãã¾ã›ã‚“。)" por "Variável '%-.64s' não é uma variável componente (Não pode ser usada como XXXX.variável_nome)" spa "Variable '%-.64s' no es una variable componente (No puede ser usada como XXXX.variable_name)" ER_UNKNOWN_COLLATION eng "Unknown collation: '%-.64s'" ger "Unbekannte Sortierreihenfolge: '%-.64s'" + jpn "䏿˜Žãªç…§åˆé †åº: '%-.64s'" por "Collation desconhecida: '%-.64s'" spa "Collation desconocida: '%-.64s'" ER_SLAVE_IGNORED_SSL_PARAMS eng "SSL parameters in CHANGE MASTER are ignored because this MariaDB slave was compiled without SSL support; they can be used later if MariaDB slave with SSL is started" ger "SSL-Parameter in CHANGE MASTER werden ignoriert, weil dieser MariaDB-Slave ohne SSL-Unterstützung kompiliert wurde. Sie können aber später verwendet werden, wenn ein MariaDB-Slave mit SSL gestartet wird" + jpn "ã“ã®MySQLスレーブã¯SSLサãƒãƒ¼ãƒˆã‚’å«ã‚ã¦ã‚³ãƒ³ãƒ‘イルã•れã¦ã„ãªã„ã®ã§ã€CHANGE MASTER ã®SSLパラメータã¯ç„¡è¦–ã•れã¾ã—ãŸã€‚今後SSLサãƒãƒ¼ãƒˆã‚’æŒã¤MySQLスレーブを起動ã™ã‚‹éš›ã«åˆ©ç”¨ã•れã¾ã™ã€‚" por "SSL parâmetros em CHANGE MASTER são ignorados porque este escravo MariaDB foi compilado sem o SSL suporte. Os mesmos podem ser usados mais tarde quando o escravo MariaDB com SSL seja iniciado." spa "Parametros SSL en CHANGE MASTER son ignorados porque este slave MariaDB fue compilado sin soporte SSL; pueden ser usados despues cuando el slave MariaDB con SSL sea inicializado" ER_SERVER_IS_IN_SECURE_AUTH_MODE eng "Server is running in --secure-auth mode, but '%s'@'%s' has a password in the old format; please change the password to the new format" ger "Server läuft im Modus --secure-auth, aber '%s'@'%s' hat ein Passwort im alten Format. Bitte Passwort ins neue Format ändern" + jpn "サーãƒãƒ¼ã¯ --secure-auth モードã§ç¨¼åƒã—ã¦ã„ã¾ã™ã€‚ã—ã‹ã— '%s'@'%s' ã¯å¤ã„å½¢å¼ã®ãƒ‘スワードを使用ã—ã¦ã„ã¾ã™ã€‚æ–°ã—ã„å½¢å¼ã®ãƒ‘スワードã«å¤‰æ›´ã—ã¦ãã ã•ã„。" por "Servidor está rodando em --secure-auth modo, porêm '%s'@'%s' tem senha no formato antigo; por favor troque a senha para o novo formato" rus "Сервер запущен в режиме --secure-auth (безопаÑной авторизации), но Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%s'@'%s' пароль Ñохранён в Ñтаром формате; необходимо обновить формат паролÑ" spa "Servidor está rodando en modo --secure-auth, pero '%s'@'%s' tiene clave en el antiguo formato; por favor cambie la clave para el nuevo formato" ER_WARN_FIELD_RESOLVED eng "Field or reference '%-.192s%s%-.192s%s%-.192s' of SELECT #%d was resolved in SELECT #%d" ger "Feld oder Verweis '%-.192s%s%-.192s%s%-.192s' im SELECT-Befehl Nr. %d wurde im SELECT-Befehl Nr. %d aufgelöst" + jpn "フィールドã¾ãŸã¯å‚ç…§ '%-.192s%s%-.192s%s%-.192s' 㯠SELECT #%d ã§ã¯ãªãã€SELECT #%d ã§è§£æ±ºã•れã¾ã—ãŸã€‚" por "Campo ou referência '%-.192s%s%-.192s%s%-.192s' de SELECT #%d foi resolvido em SELECT #%d" rus "Поле или ÑÑылка '%-.192s%s%-.192s%s%-.192s' из SELECTа #%d была найдена в SELECTе #%d" spa "Campo o referencia '%-.192s%s%-.192s%s%-.192s' de SELECT #%d fue resolvido en SELECT #%d" @@ -4941,27 +4909,32 @@ ER_WARN_FIELD_RESOLVED ER_BAD_SLAVE_UNTIL_COND eng "Incorrect parameter or combination of parameters for START SLAVE UNTIL" ger "Falscher Parameter oder falsche Kombination von Parametern für START SLAVE UNTIL" + jpn "START SLAVE UNTIL ã¸ã®ãƒ‘ラメータã¾ãŸã¯ãã®çµ„ã¿åˆã‚ã›ãŒä¸æ£ã§ã™ã€‚" por "Parâmetro ou combinação de parâmetros errado para START SLAVE UNTIL" spa "Parametro equivocado o combinación de parametros para START SLAVE UNTIL" ER_MISSING_SKIP_SLAVE eng "It is recommended to use --skip-slave-start when doing step-by-step replication with START SLAVE UNTIL; otherwise, you will get problems if you get an unexpected slave's mysqld restart" ger "Es wird empfohlen, mit --skip-slave-start zu starten, wenn mit START SLAVE UNTIL eine Schritt-für-Schritt-Replikation ausgeführt wird. Ansonsten gibt es Probleme, wenn ein Slave-Server unerwartet neu startet" + jpn "START SLAVE UNTIL ã§æ®µéšŽçš„ã«ãƒ¬ãƒ—リケーションを行ã†éš›ã«ã¯ã€--skip-slave-start オプションを使ã†ã“ã¨ã‚’推奨ã—ã¾ã™ã€‚使ã‚ãªã„å ´åˆã€ã‚¹ãƒ¬ãƒ¼ãƒ–ã®mysqldãŒä¸æ…®ã®å†èµ·å‹•ã‚’ã™ã‚‹ã¨å•題ãŒç™ºç”Ÿã—ã¾ã™ã€‚" por "É recomendado para rodar com --skip-slave-start quando fazendo replicação passo-por-passo com START SLAVE UNTIL, de outra forma você não está seguro em caso de inesperada reinicialição do mysqld escravo" spa "Es recomendado rodar con --skip-slave-start cuando haciendo replicación step-by-step con START SLAVE UNTIL, a menos que usted no esté seguro en caso de inesperada reinicialización del mysqld slave" ER_UNTIL_COND_IGNORED eng "SQL thread is not to be started so UNTIL options are ignored" ger "SQL-Thread soll nicht gestartet werden. Daher werden UNTIL-Optionen ignoriert" + jpn "スレーブSQLスレッドãŒé–‹å§‹ã•れãªã„ãŸã‚ã€UNTILオプションã¯ç„¡è¦–ã•れã¾ã—ãŸã€‚" por "Thread SQL não pode ser inicializado tal que opções UNTIL são ignoradas" spa "SQL thread no es inicializado tal que opciones UNTIL son ignoradas" ER_WRONG_NAME_FOR_INDEX 42000 eng "Incorrect index name '%-.100s'" ger "Falscher Indexname '%-.100s'" + jpn "索引å '%-.100s' ã¯ä¸æ£ã§ã™ã€‚" por "Incorreto nome de Ãndice '%-.100s'" spa "Nombre de Ãndice incorrecto '%-.100s'" swe "Felaktigt index namn '%-.100s'" ER_WRONG_NAME_FOR_CATALOG 42000 eng "Incorrect catalog name '%-.100s'" ger "Falscher Katalogname '%-.100s'" + jpn "ã‚«ã‚¿ãƒã‚°å '%-.100s' ã¯ä¸æ£ã§ã™ã€‚" por "Incorreto nome de catálogo '%-.100s'" spa "Nombre de catalog incorrecto '%-.100s'" swe "Felaktigt katalog namn '%-.100s'" @@ -4976,33 +4949,39 @@ ER_WARN_QC_RESIZE ER_BAD_FT_COLUMN eng "Column '%-.192s' cannot be part of FULLTEXT index" ger "Feld '%-.192s' kann nicht Teil eines FULLTEXT-Index sein" + jpn "列 '%-.192s' ã¯å…¨æ–‡ç´¢å¼•ã®ã‚ーã«ã¯ã§ãã¾ã›ã‚“。" por "Coluna '%-.192s' não pode ser parte de Ãndice FULLTEXT" spa "Columna '%-.192s' no puede ser parte de FULLTEXT index" swe "Kolumn '%-.192s' kan inte vara del av ett FULLTEXT index" ER_UNKNOWN_KEY_CACHE eng "Unknown key cache '%-.100s'" ger "Unbekannter Schlüssel-Cache '%-.100s'" + jpn "'%-.100s' ã¯ä¸æ˜Žãªã‚ーã‚ャッシュã§ã™ã€‚" por "Key cache desconhecida '%-.100s'" spa "Desconocida key cache '%-.100s'" swe "Okänd nyckel cache '%-.100s'" ER_WARN_HOSTNAME_WONT_WORK eng "MariaDB is started in --skip-name-resolve mode; you must restart it without this switch for this grant to work" ger "MariaDB wurde mit --skip-name-resolve gestartet. Diese Option darf nicht verwendet werden, damit diese Rechtevergabe möglich ist" + jpn "MariaDB㯠--skip-name-resolve モードã§èµ·å‹•ã—ã¦ã„ã¾ã™ã€‚ã“ã®ã‚ªãƒ—ションを外ã—ã¦å†èµ·å‹•ã—ãªã‘れã°ã€ã“ã®æ¨©é™æ“ä½œã¯æ©Ÿèƒ½ã—ã¾ã›ã‚“。" por "MariaDB foi inicializado em modo --skip-name-resolve. Você necesita reincializá-lo sem esta opção para este grant funcionar" spa "MariaDB esta inicializado en modo --skip-name-resolve. Usted necesita reinicializarlo sin esta opción para este derecho funcionar" ER_UNKNOWN_STORAGE_ENGINE 42000 eng "Unknown storage engine '%s'" ger "Unbekannte Speicher-Engine '%s'" + jpn "'%s' ã¯ä¸æ˜Žãªã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ã‚¸ãƒ³ã§ã™ã€‚" por "Motor de tabela desconhecido '%s'" spa "Desconocido motor de tabla '%s'" ER_WARN_DEPRECATED_SYNTAX eng "'%s' is deprecated and will be removed in a future release. Please use %s instead" ger "'%s' ist veraltet. Bitte benutzen Sie '%s'" + jpn "'%s' ã¯å°†æ¥ã®ãƒªãƒªãƒ¼ã‚¹ã§å»ƒæ¢äºˆå®šã§ã™ã€‚代ã‚り㫠%s を使用ã—ã¦ãã ã•ã„。" por "'%s' é desatualizado. Use '%s' em seu lugar" spa "'%s' está desaprobado, use '%s' en su lugar" ER_NON_UPDATABLE_TABLE eng "The target table %-.100s of the %s is not updatable" ger "Die Zieltabelle %-.100s von %s ist nicht aktualisierbar" + jpn "対象表 %-.100s ã¯æ›´æ–°å¯èƒ½ã§ã¯ãªã„ã®ã§ã€%s を行ãˆã¾ã›ã‚“。" por "A tabela destino %-.100s do %s não é atualizável" rus "Таблица %-.100s в %s не может изменÑÑ‚ÑÑ" spa "La tabla destino %-.100s del %s no es actualizable" @@ -5011,33 +4990,39 @@ ER_NON_UPDATABLE_TABLE ER_FEATURE_DISABLED eng "The '%s' feature is disabled; you need MariaDB built with '%s' to have it working" ger "Das Feature '%s' ist ausgeschaltet, Sie müssen MariaDB mit '%s' übersetzen, damit es verfügbar ist" + jpn "機能 '%s' ã¯ç„¡åйã§ã™ã€‚利用ã™ã‚‹ãŸã‚ã«ã¯ '%s' ã‚’å«ã‚ã¦ãƒ“ルドã—ãŸMariaDBãŒå¿…è¦ã§ã™ã€‚" por "O recurso '%s' foi desativado; você necessita MariaDB construÃdo com '%s' para ter isto funcionando" spa "El recurso '%s' fue deshabilitado; usted necesita construir MariaDB con '%s' para tener eso funcionando" swe "'%s' är inte aktiverad; För att aktivera detta mÃ¥ste du bygga om MariaDB med '%s' definierad" ER_OPTION_PREVENTS_STATEMENT eng "The MariaDB server is running with the %s option so it cannot execute this statement" ger "Der MariaDB-Server läuft mit der Option %s und kann diese Anweisung deswegen nicht ausführen" + jpn "MariaDBサーãƒãƒ¼ãŒ %s オプションã§å®Ÿè¡Œã•れã¦ã„ã‚‹ã®ã§ã€ã“ã®ã‚¹ãƒ†ãƒ¼ãƒˆãƒ¡ãƒ³ãƒˆã¯å®Ÿè¡Œã§ãã¾ã›ã‚“。" por "O servidor MariaDB está rodando com a opção %s razão pela qual não pode executar esse commando" spa "El servidor MariaDB está rodando con la opción %s tal que no puede ejecutar este comando" swe "MariaDB är startad med %s. Pga av detta kan du inte använda detta kommando" ER_DUPLICATED_VALUE_IN_TYPE eng "Column '%-.100s' has duplicated value '%-.64s' in %s" ger "Feld '%-.100s' hat doppelten Wert '%-.64s' in %s" + jpn "列 '%-.100s' ã§ã€é‡è¤‡ã™ã‚‹å€¤ '%-.64s' ㌠%s ã«æŒ‡å®šã•れã¦ã„ã¾ã™ã€‚" por "Coluna '%-.100s' tem valor duplicado '%-.64s' em %s" spa "Columna '%-.100s' tiene valor doblado '%-.64s' en %s" ER_TRUNCATED_WRONG_VALUE 22007 eng "Truncated incorrect %-.32s value: '%-.128s'" ger "Falscher %-.32s-Wert gekürzt: '%-.128s'" + jpn "䏿£ãª %-.32s ã®å€¤ãŒåˆ‡ã‚Šæ¨ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚: '%-.128s'" por "Truncado errado %-.32s valor: '%-.128s'" spa "Equivocado truncado %-.32s valor: '%-.128s'" ER_TOO_MUCH_AUTO_TIMESTAMP_COLS eng "Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause" ger "Fehlerhafte Tabellendefinition. Es kann nur eine einzige TIMESTAMP-Spalte mit CURRENT_TIMESTAMP als DEFAULT oder in einer ON-UPDATE-Klausel geben" + jpn "䏿£ãªè¡¨å®šç¾©ã§ã™ã€‚DEFAULTå¥ã¾ãŸã¯ON UPDATEå¥ã« CURRENT_TIMESTAMP ã‚’ã¨ã‚‚ãªã†TIMESTAMPåž‹ã®åˆ—ã¯1ã¤ã¾ã§ã§ã™ã€‚" por "Incorreta definição de tabela; Pode ter somente uma coluna TIMESTAMP com CURRENT_TIMESTAMP em DEFAULT ou ON UPDATE cláusula" spa "Incorrecta definición de tabla; Solamente debe haber una columna TIMESTAMP con CURRENT_TIMESTAMP en DEFAULT o ON UPDATE cláusula" ER_INVALID_ON_UPDATE eng "Invalid ON UPDATE clause for '%-.192s' column" ger "Ungültige ON-UPDATE-Klausel für Spalte '%-.192s'" + jpn "列 '%-.192s' ã« ON UPDATEå¥ã¯ç„¡åйã§ã™ã€‚" por "Inválida cláusula ON UPDATE para campo '%-.192s'" spa "Inválido ON UPDATE cláusula para campo '%-.192s'" ER_UNSUPPORTED_PS @@ -5047,11 +5032,13 @@ ER_GET_ERRMSG dan "Modtog fejl %d '%-.200s' fra %s" eng "Got error %d '%-.200s' from %s" ger "Fehler %d '%-.200s' von %s" + jpn "エラー %d '%-.200s' ㌠%s ã‹ã‚‰è¿”ã•れã¾ã—ãŸã€‚" nor "Mottok feil %d '%-.200s' fa %s" norwegian-ny "Mottok feil %d '%-.200s' fra %s" ER_GET_TEMPORARY_ERRMSG dan "Modtog temporary fejl %d '%-.200s' fra %s" eng "Got temporary error %d '%-.200s' from %s" + jpn "一時エラー %d '%-.200s' ㌠%s ã‹ã‚‰è¿”ã•れã¾ã—ãŸã€‚" ger "Temporärer Fehler %d '%-.200s' von %s" nor "Mottok temporary feil %d '%-.200s' fra %s" norwegian-ny "Mottok temporary feil %d '%-.200s' fra %s" @@ -5207,10 +5194,10 @@ ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER rus "Ðеожиданный конец файла при пропуÑке неизвеÑтного параметра '%-.192s'" ukr "ÐеÑподіванний кінець файлу у Ñпробі проминути невідомий параметр '%-.192s'" ER_VIEW_NO_EXPLAIN - eng "EXPLAIN/SHOW can not be issued; lacking privileges for underlying table" - ger "EXPLAIN/SHOW kann nicht verlangt werden. Rechte für zugrunde liegende Tabelle fehlen" - rus "EXPLAIN/SHOW не может быть выполненно; недоÑтаточно прав на таблицы запроÑа" - ukr "EXPLAIN/SHOW не може бути виконано; немає прав на тиблиці запиту" + eng "ANALYZE/EXPLAIN/SHOW can not be issued; lacking privileges for underlying table" + ger "ANALYZE/EXPLAIN/SHOW kann nicht verlangt werden. Rechte für zugrunde liegende Tabelle fehlen" + rus "ANALYZE/EXPLAIN/SHOW не может быть выполнено; недоÑтаточно прав на таблицы запроÑа" + ukr "ANALYZE/EXPLAIN/SHOW не може бути виконано; немає прав на таблиці запиту" ER_FRM_UNKNOWN_TYPE eng "File '%-.192s' has unknown type '%-.64s' in its header" ger "Datei '%-.192s' hat unbekannten Typ '%-.64s' im Header" @@ -5307,8 +5294,8 @@ ER_VIEW_CHECK_FAILED rus "проверка CHECK OPTION Ð´Ð»Ñ VIEW '%-.192s.%-.192s' провалилаÑÑŒ" ukr "Перевірка CHECK OPTION Ð´Ð»Ñ VIEW '%-.192s.%-.192s' не пройшла" ER_PROCACCESS_DENIED_ERROR 42000 - eng "%-.16s command denied to user '%s'@'%s' for routine '%-.192s'" - ger "Befehl %-.16s nicht zulässig für Benutzer '%s'@'%s' in Routine '%-.192s'" + eng "%-.32s command denied to user '%s'@'%s' for routine '%-.192s'" + ger "Befehl %-.32s nicht zulässig für Benutzer '%s'@'%s' in Routine '%-.192s'" ER_RELAY_LOG_FAIL eng "Failed purging old relay logs: %s" ger "Bereinigen alter Relais-Logs fehlgeschlagen: %s" @@ -5361,8 +5348,8 @@ ER_LOGGING_PROHIBIT_CHANGING_OF eng "Binary logging and replication forbid changing the global server %s" ger "Binärlogs und Replikation verhindern Wechsel des globalen Servers %s" ER_NO_FILE_MAPPING - eng "Can't map file: %-.200s, errno: %d" - ger "Kann Datei nicht abbilden: %-.200s, Fehler: %d" + eng "Can't map file: %-.200s, errno: %M" + ger "Kann Datei nicht abbilden: %-.200s, Fehler: %M" ER_WRONG_MAGIC eng "Wrong magic in %-.64s" ger "Falsche magische Zahlen in %-.64s" @@ -5482,11 +5469,11 @@ ER_SP_NO_RECURSION eng "Recursive stored functions and triggers are not allowed." ger "Rekursive gespeicherte Routinen und Triggers sind nicht erlaubt" ER_TOO_BIG_SCALE 42000 S1009 - eng "Too big scale %u specified for '%-.192s'. Maximum is %lu." - ger "Zu großer Skalierungsfaktor %u für '%-.192s' angegeben. Maximum ist %lu" + eng "Too big scale %llu specified for '%-.192s'. Maximum is %u." + ger "Zu großer Skalierungsfaktor %llu für '%-.192s' angegeben. Maximum ist %u" ER_TOO_BIG_PRECISION 42000 S1009 - eng "Too big precision %u specified for '%-.192s'. Maximum is %lu." - ger "Zu große Genauigkeit %u für '%-.192s' angegeben. Maximum ist %lu" + eng "Too big precision %llu specified for '%-.192s'. Maximum is %u." + ger "Zu große Genauigkeit %llu für '%-.192s' angegeben. Maximum ist %u" ER_M_BIGGER_THAN_D 42000 S1009 eng "For float(M,D), double(M,D) or decimal(M,D), M must be >= D (column '%-.192s')." ger "Für FLOAT(M,D), DOUBLE(M,D) oder DECIMAL(M,D) muss M >= D sein (Feld '%-.192s')" @@ -5517,6 +5504,7 @@ ER_TRG_IN_WRONG_SCHEMA ER_STACK_OVERRUN_NEED_MORE eng "Thread stack overrun: %ld bytes used of a %ld byte stack, and %ld bytes needed. Use 'mysqld --thread_stack=#' to specify a bigger stack." ger "Thread-Stack-Überlauf: %ld Bytes eines %ld-Byte-Stacks in Verwendung, und %ld Bytes benötigt. Verwenden Sie 'mysqld --thread_stack=#', um einen größeren Stack anzugeben" + jpn "スレッドスタックä¸è¶³ã§ã™(使用: %ld ; サイズ: %ld ; è¦æ±‚: %ld)。より大ãã„値㧠'mysqld --thread_stack=#' ã®æŒ‡å®šã‚’ã—ã¦ãã ã•ã„。" ER_TOO_LONG_BODY 42000 S1009 eng "Routine body for '%-.100s' is too long" ger "Routinen-Body für '%-.100s' ist zu lang" @@ -5525,7 +5513,7 @@ ER_WARN_CANT_DROP_DEFAULT_KEYCACHE ger "Der vorgabemäßige Schlüssel-Cache kann nicht gelöscht werden" ER_TOO_BIG_DISPLAYWIDTH 42000 S1009 eng "Display width out of range for '%-.192s' (max = %lu)" - ger "Anzeigebreite außerhalb des zulässigen Bereichs für '%-.192s' (Maximum: %lu)" + ger "Anzeigebreite außerhalb des zulässigen Bereichs für '%-.192s' (Maximum = %lu)" ER_XAER_DUPID XAE08 eng "XAER_DUPID: The XID already exists" ger "XAER_DUPID: Die XID existiert bereits" @@ -5544,9 +5532,8 @@ ER_PS_NO_RECURSION ER_SP_CANT_SET_AUTOCOMMIT eng "Not allowed to set autocommit from a stored function or trigger" ger "Es ist nicht erlaubt, innerhalb einer gespeicherten Funktion oder eines Triggers AUTOCOMMIT zu setzen" -ER_MALFORMED_DEFINER - eng "Definer is not fully qualified" - ger "Definierer des View ist nicht vollständig spezifiziert" +ER_MALFORMED_DEFINER 0L000 + eng "Invalid definer" ER_VIEW_FRM_NO_USER eng "View '%-.192s'.'%-.192s' has no definer information (old table format). Current user is used as definer. Please recreate the view!" ger "View '%-.192s'.'%-.192s' hat keine Definierer-Information (altes Tabellenformat). Der aktuelle Benutzer wird als Definierer verwendet. Bitte erstellen Sie den View neu" @@ -5554,8 +5541,8 @@ ER_VIEW_OTHER_USER eng "You need the SUPER privilege for creation view with '%-.192s'@'%-.192s' definer" ger "Sie brauchen die SUPER-Berechtigung, um einen View mit dem Definierer '%-.192s'@'%-.192s' zu erzeugen" ER_NO_SUCH_USER - eng "The user specified as a definer ('%-.64s'@'%-.64s') does not exist" - ger "Der als Definierer angegebene Benutzer ('%-.64s'@'%-.64s') existiert nicht" + eng "The user specified as a definer ('%-.64s'@'%-.64s') does not exist" + ger "Der als Definierer angegebene Benutzer ('%-.64s'@'%-.64s') existiert nicht" ER_FORBID_SCHEMA_CHANGE eng "Changing schema from '%-.192s' to '%-.192s' is not allowed." ger "Wechsel des Schemas von '%-.192s' auf '%-.192s' ist nicht erlaubt" @@ -5584,14 +5571,14 @@ ER_SP_WRONG_NAME 42000 eng "Incorrect routine name '%-.192s'" ger "Ungültiger Routinenname '%-.192s'" ER_TABLE_NEEDS_UPGRADE - eng "Table upgrade required. Please do \"REPAIR TABLE `%-.32s`\" or dump/reload to fix it!" - ger "Tabellenaktualisierung erforderlich. Bitte zum Reparieren \"REPAIR TABLE `%-.32s`\" eingeben!" + eng "Upgrade required. Please do \"REPAIR %s %`s\" or dump/reload to fix it!" + ger "Aktualisierung erforderlich. Bitte zum Reparieren \"REPAIR %s %`s\" eingeben!" ER_SP_NO_AGGREGATE 42000 eng "AGGREGATE is not supported for stored functions" ger "AGGREGATE wird bei gespeicherten Funktionen nicht unterstützt" ER_MAX_PREPARED_STMT_COUNT_REACHED 42000 - eng "Can't create more than max_prepared_stmt_count statements (current value: %lu)" - ger "Kann nicht mehr Anweisungen als max_prepared_stmt_count erzeugen (aktueller Wert: %lu)" + eng "Can't create more than max_prepared_stmt_count statements (current value: %u)" + ger "Kann nicht mehr Anweisungen als max_prepared_stmt_count erzeugen (aktueller Wert: %u)" ER_VIEW_RECURSIVE eng "`%-.192s`.`%-.192s` contains view recursion" ger "`%-.192s`.`%-.192s` enthält View-Rekursion" @@ -5599,8 +5586,8 @@ ER_NON_GROUPING_FIELD_USED 42000 eng "Non-grouping field '%-.192s' is used in %-.64s clause" ger "In der %-.192s-Klausel wird das die Nicht-Gruppierungsspalte '%-.64s' verwendet" ER_TABLE_CANT_HANDLE_SPKEYS - eng "The used table type doesn't support SPATIAL indexes" - ger "Der verwendete Tabellentyp unterstützt keine SPATIAL-Indizes" + eng "The storage engine %s doesn't support SPATIAL indexes" + ger "Der verwendete Tabellentyp (%s) unterstützt keine SPATIAL-Indizes" ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA eng "Triggers can not be created on system tables" ger "Trigger können nicht auf Systemtabellen erzeugt werden" @@ -5622,6 +5609,7 @@ ER_WRONG_STRING_LENGTH ER_NON_INSERTABLE_TABLE eng "The target table %-.100s of the %s is not insertable-into" ger "Die Zieltabelle %-.100s von %s ist nicht einfügbar" + jpn "対象表 %-.100s ã¯æŒ¿å…¥å¯èƒ½ã§ã¯ãªã„ã®ã§ã€%s を行ãˆã¾ã›ã‚“。" ER_ADMIN_WRONG_MRG_TABLE eng "Table '%-.64s' is differently defined or of non-MyISAM type or doesn't exist" ger "Tabelle '%-.64s' ist unterschiedlich definiert, nicht vom Typ MyISAM oder existiert nicht" @@ -5863,8 +5851,8 @@ ER_EVENT_ALREADY_EXISTS eng "Event '%-.192s' already exists" ger "Event '%-.192s' existiert bereits" ER_EVENT_STORE_FAILED - eng "Failed to store event %s. Error code %d from storage engine." - ger "Speichern von Event %s fehlgeschlagen. Fehlercode der Speicher-Engine: %d" + eng "Failed to store event %s. Error code %M from storage engine." + ger "Speichern von Event %s fehlgeschlagen. Fehlercode der Speicher-Engine: %M" ER_EVENT_DOES_NOT_EXIST eng "Unknown event '%-.192s'" ger "Unbekanntes Event '%-.192s'" @@ -5889,12 +5877,11 @@ ER_EVENT_OPEN_TABLE_FAILED ER_EVENT_NEITHER_M_EXPR_NOR_M_AT eng "No datetime expression provided" ger "Kein DATETIME-Ausdruck angegeben" -ER_COL_COUNT_DOESNT_MATCH_CORRUPTED - eng "Column count of mysql.%s is wrong. Expected %d, found %d. The table is probably corrupted" - ger "Spaltenanzahl von mysql.%s falsch. %d erwartet, aber %d gefunden. Tabelle ist wahrscheinlich beschädigt" -ER_CANNOT_LOAD_FROM_TABLE - eng "Cannot load from mysql.%s. The table is probably corrupted" - ger "Kann mysql.%s nicht einlesen. Tabelle ist wahrscheinlich beschädigt" + +ER_UNUSED_2 + eng "You should never see it" +ER_UNUSED_3 + eng "You should never see it" ER_EVENT_CANNOT_DELETE eng "Failed to delete the event from mysql.event" ger "Löschen des Events aus mysql.event fehlgeschlagen" @@ -5921,9 +5908,8 @@ ER_CANT_WRITE_LOCK_LOG_TABLE ER_CANT_LOCK_LOG_TABLE eng "You can't use locks with log tables." ger "Log-Tabellen können nicht gesperrt werden." -ER_FOREIGN_DUPLICATE_KEY 23000 S1009 - eng "Upholding foreign key constraints for table '%.192s', entry '%-.192s', key %d would lead to a duplicate entry" - ger "Aufrechterhalten der Fremdschlüssel-Beschränkungen für Tabelle '%.192s', Eintrag '%-.192s', Schlüssel %d würde zu einem doppelten Eintrag führen" +ER_UNUSED_4 + eng "You should never see it" ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE eng "Column count of mysql.%s is wrong. Expected %d, found %d. Created with MariaDB %d, now running %d. Please use mysql_upgrade to fix this error." ger "Spaltenanzahl von mysql.%s falsch. %d erwartet, aber %d erhalten. Erzeugt mit MariaDB %d, jetzt unter %d. Bitte benutzen Sie mysql_upgrade, um den Fehler zu beheben" @@ -5933,9 +5919,8 @@ ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT eng "Cannot change the binary logging format inside a stored function or trigger" ger "Das Binärlog-Format kann innerhalb einer gespeicherten Funktion oder eines Triggers nicht geändert werden" -ER_NDB_CANT_SWITCH_BINLOG_FORMAT - eng "The NDB cluster engine does not support changing the binlog format on the fly yet" - ger "Die Speicher-Engine NDB Cluster unterstützt das Ändern des Binärlog-Formats zur Laufzeit noch nicht" +ER_UNUSED_13 + eng "You should never see it" ER_PARTITION_NO_TEMPORARY eng "Cannot create temporary table with partitions" ger "Anlegen temporärer Tabellen mit Partitionen nicht möglich" @@ -5958,9 +5943,8 @@ ER_WRONG_PARTITION_NAME eng "Incorrect partition name" ger "Falscher Partitionsname" swe "Felaktigt partitionsnamn" -ER_CANT_CHANGE_TX_ISOLATION 25001 - eng "Transaction isolation level can't be changed while a transaction is in progress" - ger "Transaktionsisolationsebene kann während einer laufenden Transaktion nicht geändert werden" +ER_CANT_CHANGE_TX_CHARACTERISTICS 25001 + eng "Transaction characteristics can't be changed while a transaction is in progress" ER_DUP_ENTRY_AUTOINCREMENT_CASE eng "ALTER TABLE causes auto_increment resequencing, resulting in duplicate entry '%-.192s' for key '%-.192s'" ger "ALTER TABLE führt zur Neusequenzierung von auto_increment, wodurch der doppelte Eintrag '%-.192s' für Schlüssel '%-.192s' auftritt" @@ -5968,8 +5952,8 @@ ER_EVENT_MODIFY_QUEUE_ERROR eng "Internal scheduler error %d" ger "Interner Scheduler-Fehler %d" ER_EVENT_SET_VAR_ERROR - eng "Error during starting/stopping of the scheduler. Error code %u" - ger "Fehler während des Startens oder Anhalten des Schedulers. Fehlercode %u" + eng "Error during starting/stopping of the scheduler. Error code %M" + ger "Fehler während des Startens oder Anhalten des Schedulers. Fehlercode %M" ER_PARTITION_MERGE_ERROR eng "Engine cannot be used in partitioned tables" ger "Engine kann in partitionierten Tabellen nicht verwendet werden" @@ -5994,8 +5978,8 @@ ER_ONLY_INTEGERS_ALLOWED eng "Only integers allowed as number here" ger "An dieser Stelle sind nur Ganzzahlen zulässig" ER_UNSUPORTED_LOG_ENGINE - eng "This storage engine cannot be used for log tables" - ger "Diese Speicher-Engine kann für Logtabellen nicht verwendet werden" + eng "Storage engine %s cannot be used for log tables" + ger "Speicher-Engine %s kann für Logtabellen nicht verwendet werden" ER_BAD_LOG_STATEMENT eng "You cannot '%s' a log table if logging is enabled" ger "Sie können eine Logtabelle nicht '%s', wenn Loggen angeschaltet ist" @@ -6017,29 +6001,28 @@ ER_NATIVE_FCT_NAME_COLLISION # When using this error message, use the ER_DUP_ENTRY error code. See, for # example, code in handler.cc. ER_DUP_ENTRY_WITH_KEY_NAME 23000 S1009 - cze "Zvojen-Bý klÃÄ '%-.64s' (ÄÃslo klÃÄe '%-.192s')" + cze "Zvojený klÃÄ '%-.64s' (ÄÃslo klÃÄe '%-.192s')" dan "Ens værdier '%-.64s' for indeks '%-.192s'" nla "Dubbele ingang '%-.64s' voor zoeksleutel '%-.192s'" eng "Duplicate entry '%-.64s' for key '%-.192s'" - jps "'%-.64s' 㯠key '%-.192s' ã«ãŠã„ã¦é‡è¤‡ã—ã¦ã„ã¾ã™", est "Kattuv väärtus '%-.64s' võtmele '%-.192s'" fre "Duplicata du champ '%-.64s' pour la clef '%-.192s'" ger "Doppelter Eintrag '%-.64s' für Schlüssel '%-.192s'" greek "Διπλή εγγÏαφή '%-.64s' για το κλειδί '%-.192s'" hun "Duplikalt bejegyzes '%-.64s' a '%-.192s' kulcs szerint." ita "Valore duplicato '%-.64s' per la chiave '%-.192s'" - jpn "'%-.64s' 㯠key '%-.192s' ã«ãŠã„ã¦é‡è¤‡ã—ã¦ã„ã¾ã™" + jpn "'%-.64s' ã¯ç´¢å¼• '%-.192s' ã§é‡è¤‡ã—ã¦ã„ã¾ã™ã€‚" kor "ì¤‘ë³µëœ ìž…ë ¥ ê°’ '%-.64s': key '%-.192s'" nor "Like verdier '%-.64s' for nøkkel '%-.192s'" norwegian-ny "Like verdiar '%-.64s' for nykkel '%-.192s'" - pol "Powtórzone wyst?pienie '%-.64s' dla klucza '%-.192s'" + pol "Powtórzone wystÄ…pienie '%-.64s' dla klucza '%-.192s'" por "Entrada '%-.64s' duplicada para a chave '%-.192s'" rum "Cimpul '%-.64s' e duplicat pentru cheia '%-.192s'" rus "ДублирующаÑÑÑ Ð·Ð°Ð¿Ð¸ÑÑŒ '%-.64s' по ключу '%-.192s'" serbian "Dupliran unos '%-.64s' za kljuÄ '%-.192s'" slo "Opakovaný kÄ¾ÃºÄ '%-.64s' (ÄÃslo kľúÄa '%-.192s')" spa "Entrada duplicada '%-.64s' para la clave '%-.192s'" - swe "Dubbel nyckel '%-.64s' för nyckel '%-.192s'" + swe "Dublett '%-.64s' för nyckel '%-.192s'" ukr "Дублюючий Ð·Ð°Ð¿Ð¸Ñ '%-.64s' Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð° '%-.192s'" ER_BINLOG_PURGE_EMFILE eng "Too many files opened, please execute the command again" @@ -6048,10 +6031,10 @@ ER_EVENT_CANNOT_CREATE_IN_THE_PAST eng "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was dropped immediately after creation." ger "Ausführungszeit des Events liegt in der Vergangenheit, und es wurde ON COMPLETION NOT PRESERVE gesetzt. Das Event wurde unmittelbar nach Erzeugung gelöscht." ER_EVENT_CANNOT_ALTER_IN_THE_PAST - eng "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was dropped immediately after creation." - ger "Ausführungszeit des Events liegt in der Vergangenheit, und es wurde ON COMPLETION NOT PRESERVE gesetzt. Das Event wurde unmittelbar nach Erzeugung gelöscht." + eng "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was not changed. Specify a time in the future." + ger "Execution Zeitpunkt des Ereignisses in der Vergangenheit liegt, und es war NACH ABSCHLUSS Set nicht erhalten. Die Veranstaltung wurde nicht verändert. Geben Sie einen Zeitpunkt in der Zukunft." ER_SLAVE_INCIDENT - eng "The incident %s occured on the master. Message: %-.64s" + eng "The incident %s occurred on the master. Message: %-.64s" ger "Der Vorfall %s passierte auf dem Master. Meldung: %-.64s" ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT eng "Table has no partition for some existing values" @@ -6105,9 +6088,8 @@ ER_TRG_CANT_OPEN_TABLE ER_CANT_CREATE_SROUTINE eng "Cannot create stored routine `%-.64s`. Check warnings" ger "Kann gespeicherte Routine `%-.64s` nicht erzeugen. Beachten Sie die Warnungen" -ER_NEVER_USED - eng "Ambiguous slave modes combination. %s" - ger "Mehrdeutige Kombination von Slave-Modi. %s" +ER_UNUSED_11 + eng "You should never see it" ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT eng "The BINLOG statement of type `%s` was not preceded by a format description BINLOG statement." ger "Der BINLOG-Anweisung vom Typ `%s` ging keine BINLOG-Anweisung zur Formatbeschreibung voran." @@ -6133,8 +6115,8 @@ ER_DELAYED_NOT_SUPPORTED eng "DELAYED option not supported for table '%-.192s'" ger "Die DELAYED-Option wird für Tabelle '%-.192s' nicht unterstützt" WARN_NO_MASTER_INFO - eng "The master info structure does not exist" - ger "Die Master-Info-Struktur existiert nicht" + eng "There is no master connection '%.*s'" + ger "Die Master-Info-Struktur existiert nicht '%.*s'" WARN_OPTION_IGNORED eng "<%-.64s> option ignored" ger "Option <%-.64s> ignoriert" @@ -6154,10 +6136,9 @@ ER_SLAVE_HEARTBEAT_FAILURE eng "Unexpected master's heartbeat data: %s" ger "Unerwartete Daten vom Heartbeat des Masters: %s" ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE - eng "The requested value for the heartbeat period is either negative or exceeds the maximum allowed (%s seconds)." -ER_NDB_REPLICATION_SCHEMA_ERROR - eng "Bad schema for mysql.ndb_replication table. Message: %-.64s" - ger "Fehlerhaftes Schema für mysql.ndb_replication table. Meldung: %-.64s" + eng "The requested value for the heartbeat period is either negative or exceeds the maximum allowed (%u seconds)." +ER_UNUSED_14 + eng "You should never see it" ER_CONFLICT_FN_PARSE_ERROR eng "Error in parsing conflict function. Message: %-.64s" ger "Fehler beim Parsen einer Konflikt-Funktion. Meldung: %-.64s" @@ -6165,13 +6146,13 @@ ER_EXCEPTIONS_WRITE_ERROR eng "Write to exceptions table failed. Message: %-.128s"" ger "Schreiben in Ausnahme-Tabelle fehlgeschlagen. Meldung: %-.128s"" ER_TOO_LONG_TABLE_COMMENT - eng "Comment for table '%-.64s' is too long (max = %lu)" - por "Comentário para a tabela '%-.64s' é longo demais (max = %lu)" - ger "Kommentar für Tabelle '%-.64s' ist zu lang (max = %lu)" + eng "Comment for table '%-.64s' is too long (max = %u)" + por "Comentário para a tabela '%-.64s' é longo demais (max = %u)" + ger "Kommentar für Tabelle '%-.64s' ist zu lang (max = %u)" ER_TOO_LONG_FIELD_COMMENT - eng "Comment for field '%-.64s' is too long (max = %lu)" - por "Comentário para o campo '%-.64s' é longo demais (max = %lu)" - ger "Kommentar für Feld '%-.64s' ist zu lang (max = %lu)" + eng "Comment for field '%-.64s' is too long (max = %u)" + por "Comentário para o campo '%-.64s' é longo demais (max = %u)" + ger "Kommentar für Feld '%-.64s' ist zu lang (max = %u)" ER_FUNC_INEXISTENT_NAME_COLLISION 42000 eng "FUNCTION %s does not exist. Check the 'Function Name Parsing and Resolution' section in the Reference Manual" ger "FUNCTION %s existiert nicht. Erläuterungen im Abschnitt 'Function Name Parsing and Resolution' im Referenzhandbuch" @@ -6370,7 +6351,7 @@ ER_VALUES_IS_NOT_INT_TYPE_ERROR swe "Värden i VALUES för partition '%-.64s' mÃ¥ste ha typen INT" ER_ACCESS_DENIED_NO_PASSWORD_ERROR 28000 - cze "P-BÅ™Ãstup pro uživatele '%s'@'%s'" + cze "PÅ™Ãstup pro uživatele '%s'@'%s'" dan "Adgang nægtet bruger: '%s'@'%s'" nla "Toegang geweigerd voor gebruiker: '%s'@'%s'" eng "Access denied for user '%s'@'%s'" @@ -6457,11 +6438,11 @@ ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT ER_BINLOG_UNSAFE_UPDATE_IGNORE eng "UPDATE IGNORE is unsafe because the order in which rows are updated determines which (if any) rows are ignored. This order cannot be predicted and may differ on master and the slave." -ER_PLUGIN_NO_UNINSTALL - eng "Plugin '%s' is marked as not dynamically uninstallable. You have to stop the server to uninstall it." +ER_UNUSED_15 + eng "You should never see it" -ER_PLUGIN_NO_INSTALL - eng "Plugin '%s' is marked as not dynamically installable. You have to stop the server to install it." +ER_UNUSED_16 + eng "You should never see it" ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT eng "Statements writing to a table with an auto-increment column after selecting from another table are unsafe because the order in which rows are retrieved determines what (if any) rows will be written. This order cannot be predicted and may differ on master and the slave." @@ -6485,6 +6466,492 @@ ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST # End of 5.5 error messages. # +ER_CANNOT_LOAD_FROM_TABLE_V2 + eng "Cannot load from %s.%s. The table is probably corrupted" + ger "Kann %s.%s nicht einlesen. Tabelle ist wahrscheinlich beschädigt" + +ER_MASTER_DELAY_VALUE_OUT_OF_RANGE + eng "The requested value %u for the master delay exceeds the maximum %u" +ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT + eng "Only Format_description_log_event and row events are allowed in BINLOG statements (but %s was provided)" + +ER_PARTITION_EXCHANGE_DIFFERENT_OPTION + eng "Non matching attribute '%-.64s' between partition and table" + swe "Attributet '%-.64s' är olika mellan partition och tabell" +ER_PARTITION_EXCHANGE_PART_TABLE + eng "Table to exchange with partition is partitioned: '%-.64s'" + swe "Tabellen att byta ut mot partition är partitionerad: '%-.64s'" +ER_PARTITION_EXCHANGE_TEMP_TABLE + eng "Table to exchange with partition is temporary: '%-.64s'" + swe "Tabellen att byta ut mot partition är temporär: '%-.64s'" +ER_PARTITION_INSTEAD_OF_SUBPARTITION + eng "Subpartitioned table, use subpartition instead of partition" + swe "Subpartitionerad tabell, använd subpartition istället för partition" +ER_UNKNOWN_PARTITION + eng "Unknown partition '%-.64s' in table '%-.64s'" + swe "Okänd partition '%-.64s' i tabell '%-.64s'" +ER_TABLES_DIFFERENT_METADATA + eng "Tables have different definitions" + swe "Tabellerna har olika definitioner" +ER_ROW_DOES_NOT_MATCH_PARTITION + eng "Found a row that does not match the partition" + swe "Hittade en rad som inte passar i partitionen" +ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX + eng "Option binlog_cache_size (%lu) is greater than max_binlog_cache_size (%lu); setting binlog_cache_size equal to max_binlog_cache_size." +ER_WARN_INDEX_NOT_APPLICABLE + eng "Cannot use %-.64s access on index '%-.64s' due to type or collation conversion on field '%-.64s'" + +ER_PARTITION_EXCHANGE_FOREIGN_KEY + eng "Table to exchange with partition has foreign key references: '%-.64s'" + swe "Tabellen att byta ut mot partition har foreign key referenser: '%-.64s'" +ER_NO_SUCH_KEY_VALUE + eng "Key value '%-.192s' was not found in table '%-.192s.%-.192s'" +ER_VALUE_TOO_LONG + eng "Too long value for '%s'" +ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE + eng "Replication event checksum verification failed while reading from network." +ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE + eng "Replication event checksum verification failed while reading from a log file." + +ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX + eng "Option binlog_stmt_cache_size (%lu) is greater than max_binlog_stmt_cache_size (%lu); setting binlog_stmt_cache_size equal to max_binlog_stmt_cache_size." +ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT + eng "Can't update table '%-.192s' while '%-.192s' is being created." + +ER_PARTITION_CLAUSE_ON_NONPARTITIONED + eng "PARTITION () clause on non partitioned table" + swe "PARTITION () klausul för en icke partitionerad tabell" +ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET + eng "Found a row not matching the given partition set" + swe "Hittade en rad som inte passar i nÃ¥gon given partition" + +ER_UNUSED_5 + eng "You should never see it" + +ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE + eng "Failure while changing the type of replication repository: %s." + +ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE + eng "The creation of some temporary tables could not be rolled back." +ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE + eng "Some temporary tables were dropped, but these operations could not be rolled back." + +ER_MTS_FEATURE_IS_NOT_SUPPORTED + eng "%s is not supported in multi-threaded slave mode. %s" +ER_MTS_UPDATED_DBS_GREATER_MAX + eng "The number of modified databases exceeds the maximum %d; the database names will not be included in the replication event metadata." +ER_MTS_CANT_PARALLEL + eng "Cannot execute the current event group in the parallel mode. Encountered event %s, relay-log name %s, position %s which prevents execution of this event group in parallel mode. Reason: %s." +ER_MTS_INCONSISTENT_DATA + eng "%s" + +ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING + eng "FULLTEXT index is not supported for partitioned tables." + swe "FULLTEXT index stöds ej för partitionerade tabeller." + +ER_DA_INVALID_CONDITION_NUMBER 35000 + eng "Invalid condition number" + por "Número de condição inválido" + +ER_INSECURE_PLAIN_TEXT + eng "Sending passwords in plain text without SSL/TLS is extremely insecure." + +ER_INSECURE_CHANGE_MASTER + eng "Storing MySQL user name or password information in the master.info repository is not secure and is therefore not recommended. Please see the MySQL Manual for more about this issue and possible alternatives." + +ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO 23000 S1009 + eng "Foreign key constraint for table '%.192s', record '%-.192s' would lead to a duplicate entry in table '%.192s', key '%.192s'" + ger "Fremdschlüssel-Beschränkung für Tabelle '%.192s', Datensatz '%-.192s' würde zu einem doppelten Eintrag in Tabelle '%.192s', Schlüssel '%.192s' führen" + swe "FOREIGN KEY constraint för tabell '%.192s', posten '%-.192s' kan inte uppdatera barntabell '%.192s' pÃ¥ grund av nyckel '%.192s'" + +ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO 23000 S1009 + eng "Foreign key constraint for table '%.192s', record '%-.192s' would lead to a duplicate entry in a child table" + ger "Fremdschlüssel-Beschränkung für Tabelle '%.192s', Datensatz '%-.192s' würde zu einem doppelten Eintrag in einer Kind-Tabelle führen" + swe "FOREIGN KEY constraint för tabell '%.192s', posten '%-.192s' kan inte uppdatera en barntabell pÃ¥ grund av UNIQUE-test" + +ER_SQLTHREAD_WITH_SECURE_SLAVE + eng "Setting authentication options is not possible when only the Slave SQL Thread is being started." + +ER_TABLE_HAS_NO_FT + eng "The table does not have FULLTEXT index to support this query" + +ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER + eng "The system variable %.200s cannot be set in stored functions or triggers." + +ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION + eng "The system variable %.200s cannot be set when there is an ongoing transaction." + +ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST + eng "The system variable @@SESSION.GTID_NEXT has the value %.200s, which is not listed in @@SESSION.GTID_NEXT_LIST." + +ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION_WHEN_GTID_NEXT_LIST_IS_NULL + eng "When @@SESSION.GTID_NEXT_LIST == NULL, the system variable @@SESSION.GTID_NEXT cannot change inside a transaction." + +ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION + eng "The statement 'SET %.200s' cannot invoke a stored function." + +ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL + eng "The system variable @@SESSION.GTID_NEXT cannot be 'AUTOMATIC' when @@SESSION.GTID_NEXT_LIST is non-NULL." + +ER_SKIPPING_LOGGED_TRANSACTION + eng "Skipping transaction %.200s because it has already been executed and logged." + +ER_MALFORMED_GTID_SET_SPECIFICATION + eng "Malformed GTID set specification '%.200s'." + +ER_MALFORMED_GTID_SET_ENCODING + eng "Malformed GTID set encoding." + +ER_MALFORMED_GTID_SPECIFICATION + eng "Malformed GTID specification '%.200s'." + +ER_GNO_EXHAUSTED + eng "Impossible to generate Global Transaction Identifier: the integer component reached the maximal value. Restart the server with a new server_uuid." + +ER_BAD_SLAVE_AUTO_POSITION + eng "Parameters MASTER_LOG_FILE, MASTER_LOG_POS, RELAY_LOG_FILE and RELAY_LOG_POS cannot be set when MASTER_AUTO_POSITION is active." + +ER_AUTO_POSITION_REQUIRES_GTID_MODE_ON + eng "CHANGE MASTER TO MASTER_AUTO_POSITION = 1 can only be executed when GTID_MODE = ON." + +ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET + eng "Cannot execute statements with implicit commit inside a transaction when GTID_NEXT != AUTOMATIC or GTID_NEXT_LIST != NULL." + +ER_GTID_MODE_2_OR_3_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON + eng "GTID_MODE = ON or GTID_MODE = UPGRADE_STEP_2 requires ENFORCE_GTID_CONSISTENCY = 1." + +ER_GTID_MODE_REQUIRES_BINLOG + eng "GTID_MODE = ON or UPGRADE_STEP_1 or UPGRADE_STEP_2 requires --log-bin and --log-slave-updates." + +ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF + eng "GTID_NEXT cannot be set to UUID:NUMBER when GTID_MODE = OFF." + +ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON + eng "GTID_NEXT cannot be set to ANONYMOUS when GTID_MODE = ON." + +ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF + eng "GTID_NEXT_LIST cannot be set to a non-NULL value when GTID_MODE = OFF." + +ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF + eng "Found a Gtid_log_event or Previous_gtids_log_event when GTID_MODE = OFF." + +ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE + eng "When ENFORCE_GTID_CONSISTENCY = 1, updates to non-transactional tables can only be done in either autocommitted statements or single-statement transactions, and never in the same statement as updates to transactional tables." + +ER_GTID_UNSAFE_CREATE_SELECT + eng "CREATE TABLE ... SELECT is forbidden when ENFORCE_GTID_CONSISTENCY = 1." + +ER_GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION + eng "When ENFORCE_GTID_CONSISTENCY = 1, the statements CREATE TEMPORARY TABLE and DROP TEMPORARY TABLE can be executed in a non-transactional context only, and require that AUTOCOMMIT = 1." + +ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME + eng "The value of GTID_MODE can only change one step at a time: OFF <-> UPGRADE_STEP_1 <-> UPGRADE_STEP_2 <-> ON. Also note that this value must be stepped up or down simultaneously on all servers; see the Manual for instructions." + +ER_MASTER_HAS_PURGED_REQUIRED_GTIDS + eng "The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires." + +ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID + eng "GTID_NEXT cannot be changed by a client that owns a GTID. The client owns %s. Ownership is released on COMMIT or ROLLBACK." + +ER_UNKNOWN_EXPLAIN_FORMAT + eng "Unknown EXPLAIN format name: '%s'" + rus "ÐеизвеÑтное Ð¸Ð¼Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð° команды EXPLAIN: '%s'" + +ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION 25006 + eng "Cannot execute statement in a READ ONLY transaction." + +ER_TOO_LONG_TABLE_PARTITION_COMMENT + eng "Comment for table partition '%-.64s' is too long (max = %lu)" + +ER_SLAVE_CONFIGURATION + eng "Slave is not configured or failed to initialize properly. You must at least set --server-id to enable either a master or a slave. Additional error messages can be found in the MySQL error log." + +ER_INNODB_FT_LIMIT + eng "InnoDB presently supports one FULLTEXT index creation at a time" + +ER_INNODB_NO_FT_TEMP_TABLE + eng "Cannot create FULLTEXT index on temporary InnoDB table" + +ER_INNODB_FT_WRONG_DOCID_COLUMN + eng "Column '%-.192s' is of wrong type for an InnoDB FULLTEXT index" + +ER_INNODB_FT_WRONG_DOCID_INDEX + eng "Index '%-.192s' is of wrong type for an InnoDB FULLTEXT index" + +ER_INNODB_ONLINE_LOG_TOO_BIG + eng "Creating index '%-.192s' required more than 'innodb_online_alter_log_max_size' bytes of modification log. Please try again." + +ER_UNKNOWN_ALTER_ALGORITHM + eng "Unknown ALGORITHM '%s'" + +ER_UNKNOWN_ALTER_LOCK + eng "Unknown LOCK type '%s'" + +ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS + eng "CHANGE MASTER cannot be executed when the slave was stopped with an error or killed in MTS mode. Consider using RESET SLAVE or START SLAVE UNTIL." + +ER_MTS_RECOVERY_FAILURE + eng "Cannot recover after SLAVE errored out in parallel execution mode. Additional error messages can be found in the MySQL error log." + +ER_MTS_RESET_WORKERS + eng "Cannot clean up worker info tables. Additional error messages can be found in the MySQL error log." + +ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 + eng "Column count of %s.%s is wrong. Expected %d, found %d. The table is probably corrupted" + ger "Spaltenanzahl von %s.%s falsch. %d erwartet, aber %d gefunden. Tabelle ist wahrscheinlich beschädigt" + +ER_SLAVE_SILENT_RETRY_TRANSACTION + eng "Slave must silently retry current transaction" + +ER_DISCARD_FK_CHECKS_RUNNING + eng "There is a foreign key check running on table '%-.192s'. Cannot discard the table." + +ER_TABLE_SCHEMA_MISMATCH + eng "Schema mismatch (%s)" + +ER_TABLE_IN_SYSTEM_TABLESPACE + eng "Table '%-.192s' in system tablespace" + +ER_IO_READ_ERROR + eng "IO Read error: (%lu, %s) %s" + +ER_IO_WRITE_ERROR + eng "IO Write error: (%lu, %s) %s" + +ER_TABLESPACE_MISSING + eng "Tablespace is missing for table '%-.192s'" + +ER_TABLESPACE_EXISTS + eng "Tablespace for table '%-.192s' exists. Please DISCARD the tablespace before IMPORT." + +ER_TABLESPACE_DISCARDED + eng "Tablespace has been discarded for table '%-.192s'" + +ER_INTERNAL_ERROR + eng "Internal error: %-.192s" + +ER_INNODB_IMPORT_ERROR + eng "ALTER TABLE '%-.192s' IMPORT TABLESPACE failed with error %lu : '%s'" + +ER_INNODB_INDEX_CORRUPT + eng "Index corrupt: %s" + +ER_INVALID_YEAR_COLUMN_LENGTH + eng "YEAR(%lu) column type is deprecated. Creating YEAR(4) column instead." + rus "Тип YEAR(%lu) более не поддерживаетÑÑ, вмеÑто него будет Ñоздана колонка Ñ Ñ‚Ð¸Ð¿Ð¾Ð¼ YEAR(4)." + +ER_NOT_VALID_PASSWORD + eng "Your password does not satisfy the current policy requirements" + +ER_MUST_CHANGE_PASSWORD + eng "You must SET PASSWORD before executing this statement" + bgn "ТрÑбва първо да Ñи Ñмените паролата ÑÑŠÑ SET PASSWORD за да можете да изпълните тази команда" + +ER_FK_NO_INDEX_CHILD + eng "Failed to add the foreign key constaint. Missing index for constraint '%s' in the foreign table '%s'" + +ER_FK_NO_INDEX_PARENT + eng "Failed to add the foreign key constaint. Missing index for constraint '%s' in the referenced table '%s'" + +ER_FK_FAIL_ADD_SYSTEM + eng "Failed to add the foreign key constraint '%s' to system tables" + +ER_FK_CANNOT_OPEN_PARENT + eng "Failed to open the referenced table '%s'" + +ER_FK_INCORRECT_OPTION + eng "Failed to add the foreign key constraint on table '%s'. Incorrect options in FOREIGN KEY constraint '%s'" + +ER_FK_DUP_NAME + eng "Duplicate foreign key constraint name '%s'" + +ER_PASSWORD_FORMAT + eng "The password hash doesn't have the expected format. Check if the correct password algorithm is being used with the PASSWORD() function." + +ER_FK_COLUMN_CANNOT_DROP + eng "Cannot drop column '%-.192s': needed in a foreign key constraint '%-.192s'" + ger "Kann Spalte '%-.192s' nicht löschen: wird für eine Fremdschlüsselbeschränkung '%-.192s' benötigt" + +ER_FK_COLUMN_CANNOT_DROP_CHILD + eng "Cannot drop column '%-.192s': needed in a foreign key constraint '%-.192s' of table '%-.192s'" + ger "Kann Spalte '%-.192s' nicht löschen: wird für eine Fremdschlüsselbeschränkung '%-.192s' der Tabelle '%-.192s' benötigt" + +ER_FK_COLUMN_NOT_NULL + eng "Column '%-.192s' cannot be NOT NULL: needed in a foreign key constraint '%-.192s' SET NULL" + ger "Spalte '%-.192s' kann nicht NOT NULL sein: wird für eine Fremdschlüsselbeschränkung '%-.192s' SET NULL benötigt" + +ER_DUP_INDEX + eng "Duplicate index %`s. This is deprecated and will be disallowed in a future release." + +ER_FK_COLUMN_CANNOT_CHANGE + eng "Cannot change column '%-.192s': used in a foreign key constraint '%-.192s'" + +ER_FK_COLUMN_CANNOT_CHANGE_CHILD + eng "Cannot change column '%-.192s': used in a foreign key constraint '%-.192s' of table '%-.192s'" + +ER_FK_CANNOT_DELETE_PARENT + eng "Cannot delete rows from table which is parent in a foreign key constraint '%-.192s' of table '%-.192s'" + +ER_MALFORMED_PACKET + eng "Malformed communication packet." + +ER_READ_ONLY_MODE + eng "Running in read-only mode" + +ER_GTID_NEXT_TYPE_UNDEFINED_GROUP + eng "When GTID_NEXT is set to a GTID, you must explicitly set it again after a COMMIT or ROLLBACK. If you see this error message in the slave SQL thread, it means that a table in the current transaction is transactional on the master and non-transactional on the slave. In a client connection, it means that you executed SET GTID_NEXT before a transaction and forgot to set GTID_NEXT to a different identifier or to 'AUTOMATIC' after COMMIT or ROLLBACK. Current GTID_NEXT is '%s'." + +ER_VARIABLE_NOT_SETTABLE_IN_SP + eng "The system variable %.200s cannot be set in stored procedures." + +ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF + eng "GTID_PURGED can only be set when GTID_MODE = ON." + +ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY + eng "GTID_PURGED can only be set when GTID_EXECUTED is empty." + +ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY + eng "GTID_PURGED can only be set when there are no ongoing transactions (not even in other clients)." + +ER_GTID_PURGED_WAS_CHANGED + eng "GTID_PURGED was changed from '%s' to '%s'." + +ER_GTID_EXECUTED_WAS_CHANGED + eng "GTID_EXECUTED was changed from '%s' to '%s'." + +ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES + eng "Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT, and both replicated and non replicated tables are written to." + +ER_ALTER_OPERATION_NOT_SUPPORTED 0A000 + eng "%s is not supported for this operation. Try %s." + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON 0A000 + eng "%s is not supported. Reason: %s. Try %s." + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY + eng "COPY algorithm requires a lock" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION + eng "Partition specific operations do not yet support LOCK/ALGORITHM" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME + eng "Columns participating in a foreign key are renamed" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE + eng "Cannot change column type INPLACE" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK + eng "Adding foreign keys needs foreign_key_checks=OFF" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_IGNORE + eng "Creating unique indexes with IGNORE requires COPY algorithm to remove duplicate rows" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK + eng "Dropping a primary key is not allowed without also adding a new primary key" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC + eng "Adding an auto-increment column requires a lock" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS + eng "Cannot replace hidden FTS_DOC_ID with a user-visible one" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS + eng "Cannot drop or rename FTS_DOC_ID" + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS + eng "Fulltext index creation requires a lock" + +ER_SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE + eng "sql_slave_skip_counter can not be set when the server is running with GTID_MODE = ON. Instead, for each transaction that you want to skip, generate an empty transaction with the same GTID as the transaction" + +ER_DUP_UNKNOWN_IN_INDEX 23000 + cze "Zdvojený klÃÄ (ÄÃslo klÃÄe '%-.192s')" + dan "Flere ens nøgler for indeks '%-.192s'" + nla "Dubbele ingang voor zoeksleutel '%-.192s'" + eng "Duplicate entry for key '%-.192s'" + est "Kattuv väärtus võtmele '%-.192s'" + fre "Duplicata du champ pour la clef '%-.192s'" + ger "Doppelter Eintrag für Schlüssel '%-.192s'" + greek "Διπλή εγγÏαφή για το κλειδί '%-.192s'" + hun "Duplikalt bejegyzes a '%-.192s' kulcs szerint." + ita "Valore duplicato per la chiave '%-.192s'" + jpn "ã¯ç´¢å¼• '%-.192s' ã§é‡è¤‡ã—ã¦ã„ã¾ã™ã€‚" + kor "ì¤‘ë³µëœ ìž…ë ¥ ê°’: key '%-.192s'" + nor "Like verdier for nøkkel '%-.192s'" + norwegian-ny "Like verdiar for nykkel '%-.192s'" + pol "Powtórzone wystÄ…pienie dla klucza '%-.192s'" + por "Entrada duplicada para a chave '%-.192s'" + rum "Cimpul e duplicat pentru cheia '%-.192s'" + rus "ДублирующаÑÑÑ Ð·Ð°Ð¿Ð¸ÑÑŒ по ключу '%-.192s'" + serbian "Dupliran unos za kljuÄ '%-.192s'" + slo "Opakovaný kÄ¾ÃºÄ (ÄÃslo kľúÄa '%-.192s')" + spa "Entrada duplicada para la clave '%-.192s'" + swe "Dublett för nyckel '%-.192s'" + ukr "Дублюючий Ð·Ð°Ð¿Ð¸Ñ Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð° '%-.192s'" + +ER_IDENT_CAUSES_TOO_LONG_PATH + eng "Long database name and identifier for object resulted in path length exceeding %d characters. Path: '%s'." + +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL + eng "cannot silently convert NULL values, as required in this SQL_MODE" + +ER_MUST_CHANGE_PASSWORD_LOGIN + eng "Your password has expired. To log in you must change it using a client that supports expired passwords." + bgn "Паролата ви е изтекла. За да влезете трÑбва да Ñ Ñмените използвайки клиент който поддрържа такива пароли." + +ER_ROW_IN_WRONG_PARTITION + eng "Found a row in wrong partition %s" + swe "Hittade en rad i fel partition %s" + +ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX + eng "Cannot schedule event %s, relay-log name %s, position %s to Worker thread because its size %lu exceeds %lu of slave_pending_jobs_size_max." + +ER_INNODB_NO_FT_USES_PARSER + eng "Cannot CREATE FULLTEXT INDEX WITH PARSER on InnoDB table" +ER_BINLOG_LOGICAL_CORRUPTION + eng "The binary log file '%s' is logically corrupted: %s" + +ER_WARN_PURGE_LOG_IN_USE + eng "file %s was not purged because it was being read by %d thread(s), purged only %d out of %d files." + +ER_WARN_PURGE_LOG_IS_ACTIVE + eng "file %s was not purged because it is the active log file." + +ER_AUTO_INCREMENT_CONFLICT + eng "Auto-increment value in UPDATE conflicts with internally generated values" + +WARN_ON_BLOCKHOLE_IN_RBR + eng "Row events are not logged for %s statements that modify BLACKHOLE tables in row format. Table(s): '%-.192s'" + +ER_SLAVE_MI_INIT_REPOSITORY + eng "Slave failed to initialize master info structure from the repository" + +ER_SLAVE_RLI_INIT_REPOSITORY + eng "Slave failed to initialize relay log info structure from the repository" + +ER_ACCESS_DENIED_CHANGE_USER_ERROR 28000 + eng "Access denied trying to change to user '%-.48s'@'%-.64s' (using password: %s). Disconnecting." + bgn "Отказан доÑтъп при опит за ÑмÑна към потребител %-.48s'@'%-.64s' (използвана парола: %s). ЗатварÑне на връзката." + +ER_INNODB_READ_ONLY + eng "InnoDB is in read only mode." + +ER_STOP_SLAVE_SQL_THREAD_TIMEOUT + eng "STOP SLAVE command execution is incomplete: Slave SQL thread got the stop signal, thread is busy, SQL thread will stop once the current task is complete." + +ER_STOP_SLAVE_IO_THREAD_TIMEOUT + eng "STOP SLAVE command execution is incomplete: Slave IO thread got the stop signal, thread is busy, IO thread will stop once the current task is complete." + +ER_TABLE_CORRUPT + eng "Operation cannot be performed. The table '%-.64s.%-.64s' is missing, corrupt or contains bad data." + +ER_TEMP_FILE_WRITE_FAILURE + eng "Temporary file write failure." + +ER_INNODB_FT_AUX_NOT_HEX_ID + eng "Upgrade index name failed, please use create index(alter table) algorithm copy to rebuild index." + + # # MariaDB error messages section starts here # @@ -6524,12 +6991,12 @@ ER_UNKNOWN_OPTION eng "Unknown option '%-.64s'" ER_BAD_OPTION_VALUE eng "Incorrect value '%-.64s' for option '%-.64s'" -ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE - eng "Replication event checksum verification failed while reading from network." -ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE - eng "Replication event checksum verification failed while reading from a log file." -ER_CANT_DO_ONLINE - eng "Can't execute the given '%s' command as online" +ER_UNUSED_6 + eng "You should never see it" +ER_UNUSED_7 + eng "You should never see it" +ER_UNUSED_8 + eng "You should never see it" ER_DATA_OVERFLOW 22003 eng "Got overflow when converting '%-.128s' to %-.32s. Value truncated." ER_DATA_TRUNCATED 22003 @@ -6554,8 +7021,8 @@ ER_VIEW_ORDERBY_IGNORED eng "View '%-.192s'.'%-.192s' ORDER BY clause ignored because there is other ORDER BY clause already." ER_CONNECTION_KILLED 70100 eng "Connection was killed" -ER_INTERNAL_ERROR - eng "Internal error: '%-.192s'" +ER_UNUSED_12 + eng "You should never see it" ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SKIP_REPLICATION eng "Cannot modify @@session.skip_replication inside a transaction" ER_STORED_FUNCTION_PREVENTS_SWITCH_SKIP_REPLICATION @@ -6565,3 +7032,113 @@ ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT ER_NO_SUCH_TABLE_IN_ENGINE 42S02 eng "Table '%-.192s.%-.192s' doesn't exist in engine" swe "Det finns ingen tabell som heter '%-.192s.%-.192s' i handlern" +ER_TARGET_NOT_EXPLAINABLE + eng "Target is not running an EXPLAINable command" +ER_CONNECTION_ALREADY_EXISTS + eng "Connection '%.*s' conflicts with existing connection '%.*s'" +ER_MASTER_LOG_PREFIX + eng "Master '%.*s': " +ER_CANT_START_STOP_SLAVE + eng "Can't %s SLAVE '%.*s'" +ER_SLAVE_STARTED + eng "SLAVE '%.*s' started" +ER_SLAVE_STOPPED + eng "SLAVE '%.*s' stopped" +ER_SQL_DISCOVER_ERROR + eng "Engine %s failed to discover table %`-.192s.%`-.192s with '%s'" +ER_FAILED_GTID_STATE_INIT + eng "Failed initializing replication GTID state" +ER_INCORRECT_GTID_STATE + eng "Could not parse GTID list" +ER_CANNOT_UPDATE_GTID_STATE + eng "Could not update replication slave gtid state" +ER_DUPLICATE_GTID_DOMAIN + eng "GTID %u-%u-%llu and %u-%u-%llu conflict (duplicate domain id %u)" +ER_GTID_OPEN_TABLE_FAILED + eng "Failed to open %s.%s" + ger "Öffnen von %s.%s fehlgeschlagen" +ER_GTID_POSITION_NOT_FOUND_IN_BINLOG + eng "Connecting slave requested to start from GTID %u-%u-%llu, which is not in the master's binlog" +ER_CANNOT_LOAD_SLAVE_GTID_STATE + eng "Failed to load replication slave GTID position from table %s.%s" +ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG + eng "Specified GTID %u-%u-%llu conflicts with the binary log which contains a more recent GTID %u-%u-%llu. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos." +ER_MASTER_GTID_POS_MISSING_DOMAIN + eng "Specified value for @@gtid_slave_pos contains no value for replication domain %u. This conflicts with the binary log which contains GTID %u-%u-%llu. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos." +ER_UNTIL_REQUIRES_USING_GTID + eng "START SLAVE UNTIL master_gtid_pos requires that slave is using GTID" +ER_GTID_STRICT_OUT_OF_ORDER + eng "An attempt was made to binlog GTID %u-%u-%llu which would create an out-of-order sequence number with existing GTID %u-%u-%llu, and gtid strict mode is enabled." +ER_GTID_START_FROM_BINLOG_HOLE + eng "The binlog on the master is missing the GTID %u-%u-%llu requested by the slave (even though a subsequent sequence number does exist), and GTID strict mode is enabled" +ER_SLAVE_UNEXPECTED_MASTER_SWITCH + eng "Unexpected GTID received from master after reconnect. This normally indicates that the master server was replaced without restarting the slave threads. %s" +ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO + eng "Cannot modify @@session.gtid_domain_id or @@session.gtid_seq_no inside a transaction" +ER_STORED_FUNCTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO + eng "Cannot modify @@session.gtid_domain_id or @@session.gtid_seq_no inside a stored function or trigger" +ER_GTID_POSITION_NOT_FOUND_IN_BINLOG2 + eng "Connecting slave requested to start from GTID %u-%u-%llu, which is not in the master's binlog. Since the master's binlog contains GTIDs with higher sequence numbers, it probably means that the slave has diverged due to executing extra erroneous transactions" +ER_BINLOG_MUST_BE_EMPTY + eng "This operation is not allowed if any GTID has been logged to the binary log. Run RESET MASTER first to erase the log" +ER_NO_SUCH_QUERY + eng "Unknown query id: %lld" + ger "Unbekannte Abfrage-ID: %lld" + rus "ÐеизвеÑтный номер запроÑа: %lld" +ER_BAD_BASE64_DATA + eng "Bad base64 data as position %u" +ER_INVALID_ROLE OP000 + eng "Invalid role specification %`s." + rum "Rolul %`s este invalid." +ER_INVALID_CURRENT_USER 0L000 + eng "The current user is invalid." + rum "Utilizatorul curent este invalid." +ER_CANNOT_GRANT_ROLE + eng "Cannot grant role '%s' to: %s." + rum "Rolul '%s' nu poate fi acordat catre: %s." +ER_CANNOT_REVOKE_ROLE + eng "Cannot revoke role '%s' from: %s." + rum "Rolul '%s' nu poate fi revocat de la: %s." +ER_CHANGE_SLAVE_PARALLEL_THREADS_ACTIVE + eng "Cannot change @@slave_parallel_threads while another change is in progress" +ER_PRIOR_COMMIT_FAILED + eng "Commit failed due to failure of an earlier commit on which this one depends" +ER_IT_IS_A_VIEW 42S02 + eng "'%-.192s' is a view" +ER_SLAVE_SKIP_NOT_IN_GTID + eng "When using parallel replication and GTID with multiple replication domains, @@sql_slave_skip_counter can not be used. Instead, setting @@gtid_slave_pos explicitly can be used to skip to after a given GTID position." +ER_TABLE_DEFINITION_TOO_BIG + eng "The definition for table %`s is too big" +ER_PLUGIN_INSTALLED + eng "Plugin '%-.192s' already installed" + rus "Плагин '%-.192s' уже уÑтановлен" +ER_STATEMENT_TIMEOUT 70100 + eng "Query execution was interrupted (max_statement_time exceeded)" +ER_SUBQUERIES_NOT_SUPPORTED 42000 + eng "%s does not support subqueries or stored functions." +ER_SET_STATEMENT_NOT_SUPPORTED 42000 + eng "The system variable %.200s cannot be set in SET STATEMENT." +ER_UNUSED_9 + eng "You should never see it" +ER_USER_CREATE_EXISTS + eng "Can't create user '%-.64s'@'%-.64s'; it already exists" +ER_USER_DROP_EXISTS + eng "Can't drop user '%-.64s'@'%-.64s'; it doesn't exist" +ER_ROLE_CREATE_EXISTS + eng "Can't create role '%-.64s'; it already exists" +ER_ROLE_DROP_EXISTS + eng "Can't drop role '%-.64s'; it doesn't exist" +ER_CANNOT_CONVERT_CHARACTER + eng "Cannot convert '%s' character 0x%-.64s to '%s'" +ER_INVALID_DEFAULT_VALUE_FOR_FIELD 22007 + eng "Incorrect default value '%-.128s' for column '%.192s'" +ER_KILL_QUERY_DENIED_ERROR + eng "You are not owner of query %lu" + ger "Sie sind nicht Eigentümer von Abfrage %lu" + rus "Ð’Ñ‹ не ÑвлÑетеÑÑŒ владельцем запроÑа %lu" +ER_NO_EIS_FOR_FIELD + eng "Engine-independent statistics are not collected for column '%s'" + ukr "Ðезалежна від типу таблиці ÑтатиÑтика не збираєтьÑÑ Ð´Ð»Ñ ÑÑ‚Ð¾Ð²Ð±Ñ†Ñ '%s'" +ER_WARN_AGGFUNC_DEPENDENCE + eng "Aggregate function '%-.192s)' of SELECT #%d belongs to SELECT #%d" + ukr "Ðгрегатна Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ '%-.192s)' з SELECTу #%d належить до SELECTу #%d" diff --git a/sql/signal_handler.cc b/sql/signal_handler.cc index 81792cc30ac..76af7733fb9 100644 --- a/sql/signal_handler.cc +++ b/sql/signal_handler.cc @@ -64,12 +64,18 @@ extern "C" sig_handler handle_fatal_signal(int sig) struct tm tm; #ifdef HAVE_STACKTRACE THD *thd; + /* + This flag remembers if the query pointer was found invalid. + We will try and print the query at the end of the signal handler, in case + we're wrong. + */ + bool print_invalid_query_pointer= false; #endif if (segfaulted) { my_safe_printf_stderr("Fatal " SIGNAL_FMT " while backtracing\n", sig); - _exit(1); /* Quit without running destructors */ + goto end; } segfaulted = 1; @@ -100,7 +106,7 @@ extern "C" sig_handler handle_fatal_signal(int sig) "or misconfigured. This error can also be caused by malfunctioning hardware.\n\n"); my_safe_printf_stderr("%s", - "To report this bug, see http://kb.askmonty.org/en/reporting-bugs\n\n"); + "To report this bug, see https://mariadb.com/kb/en/reporting-bugs\n\n"); my_safe_printf_stderr("%s", "We will try our best to scrape up some info that will hopefully help\n" @@ -110,8 +116,9 @@ extern "C" sig_handler handle_fatal_signal(int sig) set_server_version(); my_safe_printf_stderr("Server version: %s\n", server_version); - my_safe_printf_stderr("key_buffer_size=%lu\n", - (ulong) dflt_key_cache->key_cache_mem_size); + if (dflt_key_cache) + my_safe_printf_stderr("key_buffer_size=%lu\n", + (ulong) dflt_key_cache->key_cache_mem_size); my_safe_printf_stderr("read_buffer_size=%ld\n", (long) global_system_variables.read_buff_size); @@ -119,24 +126,30 @@ extern "C" sig_handler handle_fatal_signal(int sig) my_safe_printf_stderr("max_used_connections=%lu\n", (ulong) max_used_connections); - my_safe_printf_stderr("max_threads=%u\n", - (uint) thread_scheduler->max_threads + - (uint) extra_max_connections); + if (thread_scheduler) + my_safe_printf_stderr("max_threads=%u\n", + (uint) thread_scheduler->max_threads + + (uint) extra_max_connections); my_safe_printf_stderr("thread_count=%u\n", (uint) thread_count); - my_safe_printf_stderr("It is possible that mysqld could use up to \n" - "key_buffer_size + " - "(read_buffer_size + sort_buffer_size)*max_threads = " - "%lu K bytes of memory\n", - (ulong)(dflt_key_cache->key_cache_mem_size + - (global_system_variables.read_buff_size + - global_system_variables.sortbuff_size) * - (thread_scheduler->max_threads + extra_max_connections) + - (max_connections + extra_max_connections)* sizeof(THD)) / 1024); - - my_safe_printf_stderr("%s", - "Hope that's ok; if not, decrease some variables in the equation.\n\n"); + if (dflt_key_cache && thread_scheduler) + { + my_safe_printf_stderr("It is possible that mysqld could use up to \n" + "key_buffer_size + " + "(read_buffer_size + sort_buffer_size)*max_threads = " + "%lu K bytes of memory\n", + (ulong) + (dflt_key_cache->key_cache_mem_size + + (global_system_variables.read_buff_size + + global_system_variables.sortbuff_size) * + (thread_scheduler->max_threads + extra_max_connections) + + (max_connections + extra_max_connections) * + sizeof(THD)) / 1024); + my_safe_printf_stderr("%s", + "Hope that's ok; if not, decrease some variables in " + "the equation.\n\n"); + } #ifdef HAVE_STACKTRACE thd= current_thd; @@ -172,6 +185,10 @@ extern "C" sig_handler handle_fatal_signal(int sig) case KILL_QUERY_HARD: kreason= "KILL_QUERY"; break; + case KILL_TIMEOUT: + case KILL_TIMEOUT_HARD: + kreason= "KILL_TIMEOUT"; + break; case KILL_SYSTEM_THREAD: case KILL_SYSTEM_THREAD_HARD: kreason= "KILL_SYSTEM_THREAD"; @@ -190,7 +207,12 @@ extern "C" sig_handler handle_fatal_signal(int sig) "Some pointers may be invalid and cause the dump to abort.\n"); my_safe_printf_stderr("Query (%p): ", thd->query()); - my_safe_print_str(thd->query(), min(65536U, thd->query_length())); + if (my_safe_print_str(thd->query(), MY_MIN(65536U, thd->query_length()))) + { + // Query was found invalid. We will try to print it at the end. + print_invalid_query_pointer= true; + } + my_safe_printf_stderr("\nConnection ID (thread ID): %lu\n", (ulong) thd->thread_id); my_safe_printf_stderr("Status: %s\n\n", kreason); @@ -216,7 +238,7 @@ extern "C" sig_handler handle_fatal_signal(int sig) if (calling_initgroups) { my_safe_printf_stderr("%s", "\n" - "This crash occured while the server was calling initgroups(). This is\n" + "This crash occurred while the server was calling initgroups(). This is\n" "often due to the use of a mysqld that is statically linked against \n" "glibc and configured to use LDAP in /etc/nsswitch.conf.\n" "You will need to either upgrade to a version of glibc that does not\n" @@ -254,6 +276,18 @@ extern "C" sig_handler handle_fatal_signal(int sig) "\"mlockall\" bugs.\n"); } +#ifdef HAVE_STACKTRACE + if (print_invalid_query_pointer) + { + my_safe_printf_stderr( + "\nWe think the query pointer is invalid, but we will try " + "to print it anyway. \n" + "Query: "); + my_write_stderr(thd->query(), MY_MIN(65536U, thd->query_length())); + my_safe_printf_stderr("\n\n"); + } +#endif + #ifdef HAVE_WRITE_CORE if (test_flags & TEST_CORE_ON_SIGNAL) { @@ -267,9 +301,11 @@ end: #ifndef __WIN__ /* Quit, without running destructors (etc.) + Use a signal, because the parent (systemd) can check that with WIFSIGNALED On Windows, do not terminate, but pass control to exception filter. */ - _exit(1); // Using _exit(), since exit() is not async signal safe + signal(sig, SIG_DFL); + kill(getpid(), sig); #else return; #endif diff --git a/sql/slave.cc b/sql/slave.cc index dc499fb1fc2..6fe6de6872a 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -25,8 +25,8 @@ replication slave. */ +#include <my_global.h> #include "sql_priv.h" -#include "my_global.h" #include "slave.h" #include "sql_parse.h" // execute_init_command #include "sql_table.h" // mysql_rm_table @@ -52,11 +52,14 @@ #include "log_event.h" // Rotate_log_event, // Create_file_log_event, // Format_description_log_event +#include "wsrep_mysqld.h" #ifdef HAVE_REPLICATION #include "rpl_tblmap.h" #include "debug_sync.h" +#include "rpl_parallel.h" + #define FLAGSTR(V,F) ((V)&(F)?#F" ":"") @@ -71,9 +74,15 @@ char slave_skip_error_names[SHOW_VAR_FUNC_BUFF_SIZE]; char* slave_load_tmpdir = 0; Master_info *active_mi= 0; +Master_info_index *master_info_index; my_bool replicate_same_server_id; ulonglong relay_log_space_limit = 0; +const char *relay_log_index= 0; +const char *relay_log_basename= 0; + +LEX_STRING default_master_connection_name= { (char*) "", 0 }; + /* When slave thread exits, we need to remember the temporary tables so we can re-use them on slave start. @@ -108,11 +117,11 @@ static const char *reconnect_messages[SLAVE_RECON_ACT_MAX][SLAVE_RECON_MSG_MAX]= { { "Waiting to reconnect after a failed registration on master", - "Slave I/O thread killed while waitnig to reconnect after a failed \ + "Slave I/O thread killed while waiting to reconnect after a failed \ registration on master", "Reconnecting after a failed registration on master", "failed registering on master, reconnecting to try again, \ -log '%s' at position %s", +log '%s' at position %llu%s", "COM_REGISTER_SLAVE", "Slave I/O thread killed during or after reconnect" }, @@ -120,7 +129,7 @@ log '%s' at position %s", "Waiting to reconnect after a failed binlog dump request", "Slave I/O thread killed while retrying master dump", "Reconnecting after a failed binlog dump request", - "failed dump request, reconnecting to try again, log '%s' at position %s", + "failed dump request, reconnecting to try again, log '%s' at position %llu%s", "COM_BINLOG_DUMP", "Slave I/O thread killed during or after reconnect" }, @@ -129,7 +138,7 @@ log '%s' at position %s", "Slave I/O thread killed while waiting to reconnect after a failed read", "Reconnecting after a failed master event read", "Slave I/O thread: Failed reading log event, reconnecting to retry, \ -log '%s' at position %s", +log '%s' at position %llu%s", "", "Slave I/O thread killed during or after a reconnect done to recover from \ failed read" @@ -142,23 +151,19 @@ typedef enum { SLAVE_THD_IO, SLAVE_THD_SQL} SLAVE_THD_TYPE; static int process_io_rotate(Master_info* mi, Rotate_log_event* rev); static int process_io_create_file(Master_info* mi, Create_file_log_event* cev); static bool wait_for_relay_log_space(Relay_log_info* rli); -static inline bool io_slave_killed(THD* thd,Master_info* mi); -static inline bool sql_slave_killed(THD* thd,Relay_log_info* rli); -static int init_slave_thread(THD* thd, SLAVE_THD_TYPE thd_type); +static bool io_slave_killed(Master_info* mi); +static bool sql_slave_killed(rpl_group_info *rgi); +static int init_slave_thread(THD*, Master_info *, SLAVE_THD_TYPE); static void print_slave_skip_errors(void); static int safe_connect(THD* thd, MYSQL* mysql, Master_info* mi); -static int safe_reconnect(THD* thd, MYSQL* mysql, Master_info* mi, - bool suppress_warnings); -static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, - bool reconnect, bool suppress_warnings); -static Log_event* next_event(Relay_log_info* rli); +static int safe_reconnect(THD*, MYSQL*, Master_info*, bool); +static int connect_to_master(THD*, MYSQL*, Master_info*, bool, bool); +static Log_event* next_event(rpl_group_info* rgi, ulonglong *event_size); static int queue_event(Master_info* mi,const char* buf,ulong event_len); -static int terminate_slave_thread(THD *thd, - mysql_mutex_t *term_lock, - mysql_cond_t *term_cond, - volatile uint *slave_running, - bool skip_lock); -static bool check_io_slave_killed(THD *thd, Master_info *mi, const char *info); +static int terminate_slave_thread(THD *, mysql_mutex_t *, mysql_cond_t *, + volatile uint *, bool); +static bool check_io_slave_killed(Master_info *mi, const char *info); +static bool send_show_master_info_data(THD *, Master_info *, bool, String *); /* Function to set the slave's max_allowed_packet based on the value of slave_max_allowed_packet. @@ -227,16 +232,14 @@ void init_thread_mask(int* mask,Master_info* mi,bool inverse) /* - lock_slave_threads() + lock_slave_threads() against other threads doing STOP, START or RESET SLAVE + */ -void lock_slave_threads(Master_info* mi) +void Master_info::lock_slave_threads() { DBUG_ENTER("lock_slave_threads"); - - //TODO: see if we can do this without dual mutex - mysql_mutex_lock(&mi->run_lock); - mysql_mutex_lock(&mi->rli.run_lock); + mysql_mutex_lock(&start_stop_lock); DBUG_VOID_RETURN; } @@ -245,13 +248,10 @@ void lock_slave_threads(Master_info* mi) unlock_slave_threads() */ -void unlock_slave_threads(Master_info* mi) +void Master_info::unlock_slave_threads() { DBUG_ENTER("unlock_slave_threads"); - - //TODO: see if we can do this without dual mutex - mysql_mutex_unlock(&mi->rli.run_lock); - mysql_mutex_unlock(&mi->run_lock); + mysql_mutex_unlock(&start_stop_lock); DBUG_VOID_RETURN; } @@ -277,6 +277,174 @@ static void init_slave_psi_keys(void) } #endif /* HAVE_PSI_INTERFACE */ + +static bool slave_background_thread_running; +static bool slave_background_thread_stop; +static bool slave_background_thread_gtid_loaded; + +struct slave_background_kill_t { + slave_background_kill_t *next; + THD *to_kill; +} *slave_background_kill_list; + + +pthread_handler_t +handle_slave_background(void *arg __attribute__((unused))) +{ + THD *thd; + PSI_stage_info old_stage; + bool stop; + + my_thread_init(); + thd= new THD; + thd->thread_stack= (char*) &thd; /* Set approximate stack start */ + mysql_mutex_lock(&LOCK_thread_count); + thd->thread_id= thread_id++; + mysql_mutex_unlock(&LOCK_thread_count); + thd->system_thread = SYSTEM_THREAD_SLAVE_BACKGROUND; + thread_safe_increment32(&service_thread_count); + thd->store_globals(); + thd->security_ctx->skip_grants(); + thd->set_command(COM_DAEMON); + + thd_proc_info(thd, "Loading slave GTID position from table"); + if (rpl_load_gtid_slave_state(thd)) + sql_print_warning("Failed to load slave replication state from table " + "%s.%s: %u: %s", "mysql", + rpl_gtid_slave_state_table_name.str, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + + mysql_mutex_lock(&LOCK_slave_background); + slave_background_thread_gtid_loaded= true; + mysql_cond_broadcast(&COND_slave_background); + + THD_STAGE_INFO(thd, stage_slave_background_process_request); + do + { + slave_background_kill_t *kill_list; + + thd->ENTER_COND(&COND_slave_background, &LOCK_slave_background, + &stage_slave_background_wait_request, + &old_stage); + for (;;) + { + stop= abort_loop || thd->killed || slave_background_thread_stop; + kill_list= slave_background_kill_list; + if (stop || kill_list) + break; + mysql_cond_wait(&COND_slave_background, &LOCK_slave_background); + } + + slave_background_kill_list= NULL; + thd->EXIT_COND(&old_stage); + + while (kill_list) + { + slave_background_kill_t *p = kill_list; + THD *to_kill= p->to_kill; + kill_list= p->next; + + mysql_mutex_lock(&to_kill->LOCK_thd_data); + to_kill->awake(KILL_CONNECTION); + mysql_mutex_unlock(&to_kill->LOCK_thd_data); + mysql_mutex_lock(&to_kill->LOCK_wakeup_ready); + to_kill->rgi_slave->killed_for_retry= + rpl_group_info::RETRY_KILL_KILLED; + mysql_cond_broadcast(&to_kill->COND_wakeup_ready); + mysql_mutex_unlock(&to_kill->LOCK_wakeup_ready); + my_free(p); + } + mysql_mutex_lock(&LOCK_slave_background); + } while (!stop); + + slave_background_thread_running= false; + mysql_cond_broadcast(&COND_slave_background); + mysql_mutex_unlock(&LOCK_slave_background); + + delete thd; + thread_safe_decrement32(&service_thread_count); + signal_thd_deleted(); + my_thread_end(); + + return 0; +} + + + +void +slave_background_kill_request(THD *to_kill) +{ + if (to_kill->rgi_slave->killed_for_retry) + return; // Already deadlock killed. + slave_background_kill_t *p= + (slave_background_kill_t *)my_malloc(sizeof(*p), MYF(MY_WME)); + if (p) + { + p->to_kill= to_kill; + to_kill->rgi_slave->killed_for_retry= + rpl_group_info::RETRY_KILL_PENDING; + mysql_mutex_lock(&LOCK_slave_background); + p->next= slave_background_kill_list; + slave_background_kill_list= p; + mysql_cond_signal(&COND_slave_background); + mysql_mutex_unlock(&LOCK_slave_background); + } +} + + +/* + Start the slave background thread. + + This thread is currently used for two purposes: + + 1. To load the GTID state from mysql.gtid_slave_pos at server start; reading + from table requires valid THD, which is otherwise not available during + server init. + + 2. To kill worker thread transactions during parallel replication, when a + storage engine attempts to take an errorneous conflicting lock that would + cause a deadlock. Killing is done asynchroneously, as the kill may not + be safe within the context of a callback from inside storage engine + locking code. +*/ +static int +start_slave_background_thread() +{ + pthread_t th; + + slave_background_thread_running= true; + slave_background_thread_stop= false; + slave_background_thread_gtid_loaded= false; + if (mysql_thread_create(key_thread_slave_background, + &th, &connection_attrib, handle_slave_background, + NULL)) + { + sql_print_error("Failed to create thread while initialising slave"); + return 1; + } + + mysql_mutex_lock(&LOCK_slave_background); + while (!slave_background_thread_gtid_loaded) + mysql_cond_wait(&COND_slave_background, &LOCK_slave_background); + mysql_mutex_unlock(&LOCK_slave_background); + + return 0; +} + + +static void +stop_slave_background_thread() +{ + mysql_mutex_lock(&LOCK_slave_background); + slave_background_thread_stop= true; + mysql_cond_broadcast(&COND_slave_background); + while (slave_background_thread_running) + mysql_cond_wait(&COND_slave_background, &LOCK_slave_background); + mysql_mutex_unlock(&LOCK_slave_background); +} + + /* Initialize slave structures */ int init_slave() @@ -288,21 +456,44 @@ int init_slave() init_slave_psi_keys(); #endif + if (start_slave_background_thread()) + return 1; + + if (global_rpl_thread_pool.init(opt_slave_parallel_threads)) + return 1; + /* This is called when mysqld starts. Before client connections are accepted. However bootstrap may conflict with us if it does START SLAVE. So it's safer to take the lock. */ - mysql_mutex_lock(&LOCK_active_mi); - /* - TODO: re-write this to interate through the list of files - for multi-master - */ - active_mi= new Master_info(relay_log_recovery); if (pthread_key_create(&RPL_MASTER_INFO, NULL)) goto err; + master_info_index= new Master_info_index; + if (!master_info_index || master_info_index->init_all_master_info()) + { + sql_print_error("Failed to initialize multi master structures"); + DBUG_RETURN(1); + } + if (!(active_mi= new Master_info(&default_master_connection_name, + relay_log_recovery)) || + active_mi->error()) + { + delete active_mi; + active_mi= 0; + sql_print_error("Failed to allocate memory for the Master Info structure"); + goto err; + } + + if (master_info_index->add_master_info(active_mi, FALSE)) + { + delete active_mi; + active_mi= 0; + goto err; + } + /* If --slave-skip-errors=... was not used, the string value for the system variable has not been set up yet. Do it now. @@ -317,18 +508,11 @@ int init_slave() If master_host is specified, create the master_info file if it doesn't exists. */ - if (!active_mi) - { - sql_print_error("Failed to allocate memory for the master info structure"); - error= 1; - goto err; - } if (init_master_info(active_mi,master_info_file,relay_log_info_file, 1, (SLAVE_IO | SLAVE_SQL))) { sql_print_error("Failed to initialize the master info structure"); - error= 1; goto err; } @@ -336,22 +520,34 @@ int init_slave() if (active_mi->host[0] && !opt_skip_slave_start) { - if (start_slave_threads(1 /* need mutex */, - 0 /* no wait for start*/, - active_mi, - master_info_file, - relay_log_info_file, - SLAVE_IO | SLAVE_SQL)) + int error; + THD *thd= new THD; + thd->thread_stack= (char*) &thd; + thd->store_globals(); + + error= start_slave_threads(0, /* No active thd */ + 1 /* need mutex */, + 1 /* wait for start*/, + active_mi, + master_info_file, + relay_log_info_file, + SLAVE_IO | SLAVE_SQL); + + thd->reset_globals(); + delete thd; + if (error) { sql_print_error("Failed to create slave threads"); - error= 1; goto err; } } -err: - mysql_mutex_unlock(&LOCK_active_mi); +end: DBUG_RETURN(error); + +err: + error= 1; + goto end; } /* @@ -388,7 +584,7 @@ int init_recovery(Master_info* mi, const char** errmsg) Relay_log_info *rli= &mi->rli; if (rli->group_master_log_name[0]) { - mi->master_log_pos= max(BIN_LOG_HEADER_SIZE, + mi->master_log_pos= MY_MAX(BIN_LOG_HEADER_SIZE, rli->group_master_log_pos); strmake_buf(mi->master_log_name, rli->group_master_log_name); @@ -403,6 +599,7 @@ int init_recovery(Master_info* mi, const char** errmsg) DBUG_RETURN(0); } + /** Convert slave skip errors bitmap into a printable string. @@ -481,7 +678,7 @@ void init_slave_skip_errors(const char* arg) const char *p; DBUG_ENTER("init_slave_skip_errors"); - if (bitmap_init(&slave_error_mask,0,MAX_SLAVE_ERROR,0)) + if (my_bitmap_init(&slave_error_mask,0,MAX_SLAVE_ERROR,0)) { fprintf(stderr, "Badly out of memory, please check your system status\n"); exit(1); @@ -510,14 +707,6 @@ void init_slave_skip_errors(const char* arg) DBUG_VOID_RETURN; } -static void set_thd_in_use_temporary_tables(Relay_log_info *rli) -{ - TABLE *table; - - for (table= rli->save_temporary_tables ; table ; table= table->next) - table->in_use= rli->sql_thd; -} - int terminate_slave_threads(Master_info* mi,int thread_mask,bool skip_lock) { DBUG_ENTER("terminate_slave_threads"); @@ -525,30 +714,36 @@ int terminate_slave_threads(Master_info* mi,int thread_mask,bool skip_lock) if (!mi->inited) DBUG_RETURN(0); /* successfully do nothing */ int error,force_all = (thread_mask & SLAVE_FORCE_ALL); + int retval= 0; mysql_mutex_t *sql_lock = &mi->rli.run_lock, *io_lock = &mi->run_lock; mysql_mutex_t *log_lock= mi->rli.relay_log.get_log_lock(); if (thread_mask & (SLAVE_SQL|SLAVE_FORCE_ALL)) { DBUG_PRINT("info",("Terminating SQL thread")); - mi->rli.abort_slave=1; - if ((error=terminate_slave_thread(mi->rli.sql_thd, sql_lock, + if (mi->using_parallel() && mi->rli.abort_slave && mi->rli.stop_for_until) + { + mi->rli.stop_for_until= false; + mi->rli.parallel.stop_during_until(); + } + else + mi->rli.abort_slave=1; + if ((error=terminate_slave_thread(mi->rli.sql_driver_thd, sql_lock, &mi->rli.stop_cond, &mi->rli.slave_running, skip_lock)) && !force_all) DBUG_RETURN(error); + retval= error; mysql_mutex_lock(log_lock); DBUG_PRINT("info",("Flushing relay-log info file.")); if (current_thd) - thd_proc_info(current_thd, "Flushing relay-log info file."); - if (flush_relay_log_info(&mi->rli)) - DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS); - - if (my_sync(mi->rli.info_fd, MYF(MY_WME))) - DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS); + THD_STAGE_INFO(current_thd, stage_flushing_relay_log_info_file); + if (flush_relay_log_info(&mi->rli) || + my_sync(mi->rli.info_fd, MYF(MY_WME))) + retval= ER_ERROR_DURING_FLUSH_LOGS; mysql_mutex_unlock(log_lock); } @@ -562,25 +757,26 @@ int terminate_slave_threads(Master_info* mi,int thread_mask,bool skip_lock) skip_lock)) && !force_all) DBUG_RETURN(error); + if (!retval) + retval= error; mysql_mutex_lock(log_lock); DBUG_PRINT("info",("Flushing relay log and master info file.")); if (current_thd) - thd_proc_info(current_thd, "Flushing relay log and master info files."); - if (flush_master_info(mi, TRUE, FALSE)) - DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS); - + THD_STAGE_INFO(current_thd, stage_flushing_relay_log_and_master_info_repository); + if (likely(mi->fd >= 0)) + { + if (flush_master_info(mi, TRUE, FALSE) || my_sync(mi->fd, MYF(MY_WME))) + retval= ER_ERROR_DURING_FLUSH_LOGS; + } if (mi->rli.relay_log.is_open() && my_sync(mi->rli.relay_log.get_log_file()->file, MYF(MY_WME))) - DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS); - - if (my_sync(mi->fd, MYF(MY_WME))) - DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS); + retval= ER_ERROR_DURING_FLUSH_LOGS; mysql_mutex_unlock(log_lock); } - DBUG_RETURN(0); + DBUG_RETURN(retval); } @@ -715,7 +911,7 @@ int start_slave_thread( if (start_lock) mysql_mutex_lock(start_lock); - if (!server_id) + if (!global_system_variables.server_id) { if (start_cond) mysql_cond_broadcast(start_cond); @@ -743,14 +939,25 @@ int start_slave_thread( mysql_mutex_unlock(start_lock); DBUG_RETURN(ER_SLAVE_THREAD); } + + /* + In the following loop we can't check for thd->killed as we have to + wait until THD structures for the slave thread are created + before we can return. + This should be ok as there is no major work done in the slave + threads before they signal that we can stop waiting. + */ + if (start_cond && cond_lock) // caller has cond_lock { THD* thd = current_thd; while (start_id == *slave_run_id) { DBUG_PRINT("sleep",("Waiting for slave thread to start")); - const char *old_msg= thd->enter_cond(start_cond, cond_lock, - "Waiting for slave thread to start"); + PSI_stage_info saved_stage= {0, "", 0}; + thd->ENTER_COND(start_cond, cond_lock, + & stage_waiting_for_slave_thread_to_start, + & saved_stage); /* It is not sufficient to test this at loop bottom. We must test it after registering the mutex in enter_cond(). If the kill @@ -758,16 +965,9 @@ int start_slave_thread( registered, we could otherwise go waiting though thd->killed is set. */ - if (!thd->killed) - mysql_cond_wait(start_cond, cond_lock); - thd->exit_cond(old_msg); + mysql_cond_wait(start_cond, cond_lock); + thd->EXIT_COND(& saved_stage); mysql_mutex_lock(cond_lock); // re-acquire it as exit_cond() released - if (thd->killed) - { - if (start_lock) - mysql_mutex_unlock(start_lock); - DBUG_RETURN(thd->killed_errno()); - } } } if (start_lock) @@ -785,13 +985,15 @@ int start_slave_thread( started the threads that were not previously running */ -int start_slave_threads(bool need_slave_mutex, bool wait_for_start, +int start_slave_threads(THD *thd, + bool need_slave_mutex, bool wait_for_start, Master_info* mi, const char* master_info_fname, const char* slave_info_fname, int thread_mask) { mysql_mutex_t *lock_io=0, *lock_sql=0, *lock_cond_io=0, *lock_cond_sql=0; mysql_cond_t* cond_io=0, *cond_sql=0; int error=0; + const char *errmsg; DBUG_ENTER("start_slave_threads"); if (need_slave_mutex) @@ -807,7 +1009,43 @@ int start_slave_threads(bool need_slave_mutex, bool wait_for_start, lock_cond_sql = &mi->rli.run_lock; } - if (thread_mask & SLAVE_IO) + /* + If we are using GTID and both SQL and IO threads are stopped, then get + rid of all relay logs. + + Relay logs are not very useful when using GTID, except as a buffer + between the fetch in the IO thread and the apply in SQL thread. However + while one of the threads is running, they are in use and cannot be + removed. + */ + if (mi->using_gtid != Master_info::USE_GTID_NO && + !mi->slave_running && !mi->rli.slave_running) + { + /* + purge_relay_logs() clears the mi->rli.group_master_log_pos. + So save and restore them, like we do in CHANGE MASTER. + (We are not going to use them for GTID, but it might be worth to + keep them in case connection with GTID fails and user wants to go + back and continue with previous old-style replication coordinates). + */ + mi->master_log_pos = MY_MAX(BIN_LOG_HEADER_SIZE, + mi->rli.group_master_log_pos); + strmake(mi->master_log_name, mi->rli.group_master_log_name, + sizeof(mi->master_log_name)-1); + purge_relay_logs(&mi->rli, thd, 0, &errmsg); + mi->rli.group_master_log_pos= mi->master_log_pos; + strmake(mi->rli.group_master_log_name, mi->master_log_name, + sizeof(mi->rli.group_master_log_name)-1); + + error= rpl_load_gtid_state(&mi->gtid_current_pos, mi->using_gtid == + Master_info::USE_GTID_CURRENT_POS); + mi->events_queued_since_last_gtid= 0; + mi->gtid_reconnect_event_skip_count= 0; + + mi->rli.restart_gtid_pos.reset(); + } + + if (!error && (thread_mask & SLAVE_IO)) error= start_slave_thread( #ifdef HAVE_PSI_INTERFACE key_thread_slave_io, @@ -834,10 +1072,18 @@ int start_slave_threads(bool need_slave_mutex, bool wait_for_start, /* - Release slave threads at time of executing shutdown. + Kill slaves preparing for shutdown +*/ - SYNOPSIS - end_slave() +void slave_prepare_for_shutdown() +{ + mysql_mutex_lock(&LOCK_active_mi); + master_info_index->free_connections(); + mysql_mutex_unlock(&LOCK_active_mi); +} + +/* + Release slave threads at time of executing shutdown. */ void end_slave() @@ -850,50 +1096,33 @@ void end_slave() running presently. If a START SLAVE was in progress, the mutex lock below will make us wait until slave threads have started, and START SLAVE returns, then we terminate them here. + + We can also be called by cleanup(), which only happens if some + startup parameter to the server was wrong. */ mysql_mutex_lock(&LOCK_active_mi); - if (active_mi) - { - /* - TODO: replace the line below with - list_walk(&master_list, (list_walk_action)end_slave_on_walk,0); - once multi-master code is ready. - */ - terminate_slave_threads(active_mi,SLAVE_FORCE_ALL); - } + /* + master_info_index should not have any threads anymore as they where + killed as part of slave_prepare_for_shutdown() + */ + delete master_info_index; + master_info_index= 0; + active_mi= 0; mysql_mutex_unlock(&LOCK_active_mi); - DBUG_VOID_RETURN; -} - -/** - Free all resources used by slave threads at time of executing shutdown. - The routine must be called after all possible users of @c active_mi - have left. - SYNOPSIS - close_active_mi() + stop_slave_background_thread(); -*/ -void close_active_mi() -{ - mysql_mutex_lock(&LOCK_active_mi); - if (active_mi) - { - end_master_info(active_mi); - end_relay_log_info(&active_mi->rli); - delete active_mi; - active_mi= 0; - } - mysql_mutex_unlock(&LOCK_active_mi); + global_rpl_thread_pool.destroy(); + free_all_rpl_filters(); + DBUG_VOID_RETURN; } -static bool io_slave_killed(THD* thd, Master_info* mi) +static bool io_slave_killed(Master_info* mi) { DBUG_ENTER("io_slave_killed"); - DBUG_ASSERT(mi->io_thd == thd); DBUG_ASSERT(mi->slave_running); // tracking buffer overrun - DBUG_RETURN(mi->abort_slave || abort_loop || thd->killed); + DBUG_RETURN(mi->abort_slave || abort_loop || mi->io_thd->killed); } /** @@ -909,26 +1138,36 @@ static bool io_slave_killed(THD* thd, Master_info* mi) @return TRUE the killed status is recognized, FALSE a possible killed status is deferred. */ -static bool sql_slave_killed(THD* thd, Relay_log_info* rli) +static bool sql_slave_killed(rpl_group_info *rgi) { bool ret= FALSE; + Relay_log_info *rli= rgi->rli; + THD *thd= rgi->thd; DBUG_ENTER("sql_slave_killed"); - DBUG_ASSERT(rli->sql_thd == thd); + DBUG_ASSERT(rli->sql_driver_thd == thd); DBUG_ASSERT(rli->slave_running == 1);// tracking buffer overrun - if (abort_loop || thd->killed || rli->abort_slave) + if (abort_loop || rli->sql_driver_thd->killed || rli->abort_slave) { /* - The transaction should always be binlogged if OPTION_KEEP_LOG is set - (it implies that something can not be rolled back). And such case - should be regarded similarly as modifing a non-transactional table - because retrying of the transaction will lead to an error or inconsistency - as well. - Example: OPTION_KEEP_LOG is set if a temporary table is created or dropped. + The transaction should always be binlogged if OPTION_KEEP_LOG is + set (it implies that something can not be rolled back). And such + case should be regarded similarly as modifing a + non-transactional table because retrying of the transaction will + lead to an error or inconsistency as well. + + Example: OPTION_KEEP_LOG is set if a temporary table is created + or dropped. + + Note that transaction.all.modified_non_trans_table may be 1 + if last statement was a single row transaction without begin/end. + Testing this flag must always be done in connection with + rli->is_in_group(). */ + if ((thd->transaction.all.modified_non_trans_table || - (thd->variables.option_bits & OPTION_KEEP_LOG)) - && rli->is_in_group()) + (thd->variables.option_bits & OPTION_KEEP_LOG)) && + rli->is_in_group()) { char msg_stopped[]= "... Slave SQL Thread stopped with incomplete event group " @@ -938,25 +1177,34 @@ static bool sql_slave_killed(THD* thd, Relay_log_info* rli) "ignores duplicate key, key not found, and similar errors (see " "documentation for details)."; + DBUG_PRINT("info", ("modified_non_trans_table: %d OPTION_BEGIN: %d " + "OPTION_KEEP_LOG: %d is_in_group: %d", + thd->transaction.all.modified_non_trans_table, + MY_TEST(thd->variables.option_bits & OPTION_BEGIN), + MY_TEST(thd->variables.option_bits & OPTION_KEEP_LOG), + rli->is_in_group())); + if (rli->abort_slave) { - DBUG_PRINT("info", ("Request to stop slave SQL Thread received while " - "applying a group that has non-transactional " - "changes; waiting for completion of the group ... ")); + DBUG_PRINT("info", + ("Request to stop slave SQL Thread received while " + "applying a group that has non-transactional " + "changes; waiting for completion of the group ... ")); /* - Slave sql thread shutdown in face of unfinished group modified - Non-trans table is handled via a timer. The slave may eventually - give out to complete the current group and in that case there - might be issues at consequent slave restart, see the error message. - WL#2975 offers a robust solution requiring to store the last exectuted - event's coordinates along with the group's coordianates - instead of waiting with @c last_event_start_time the timer. + Slave sql thread shutdown in face of unfinished group + modified Non-trans table is handled via a timer. The slave + may eventually give out to complete the current group and in + that case there might be issues at consequent slave restart, + see the error message. WL#2975 offers a robust solution + requiring to store the last exectuted event's coordinates + along with the group's coordianates instead of waiting with + @c last_event_start_time the timer. */ - if (rli->last_event_start_time == 0) - rli->last_event_start_time= my_time(0); - ret= difftime(my_time(0), rli->last_event_start_time) <= + if (rgi->last_event_start_time == 0) + rgi->last_event_start_time= my_time(0); + ret= difftime(my_time(0), rgi->last_event_start_time) <= SLAVE_WAIT_GROUP_DONE ? FALSE : TRUE; DBUG_EXECUTE_IF("stop_slave_middle_group", @@ -965,21 +1213,22 @@ static bool sql_slave_killed(THD* thd, Relay_log_info* rli) if (ret == 0) { - rli->report(WARNING_LEVEL, 0, + rli->report(WARNING_LEVEL, 0, rgi->gtid_info(), "Request to stop slave SQL Thread received while " "applying a group that has non-transactional " "changes; waiting for completion of the group ... "); } else { - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), msg_stopped); + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(), + ER_THD(thd, ER_SLAVE_FATAL_ERROR), msg_stopped); } } else { ret= TRUE; - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER(ER_SLAVE_FATAL_ERROR), + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, rgi->gtid_info(), + ER_THD(thd, ER_SLAVE_FATAL_ERROR), msg_stopped); } } @@ -989,7 +1238,7 @@ static bool sql_slave_killed(THD* thd, Relay_log_info* rli) } } if (ret) - rli->last_event_start_time= 0; + rgi->last_event_start_time= 0; DBUG_RETURN(ret); } @@ -1066,7 +1315,6 @@ int init_strvar_from_file(char *var, int max_size, IO_CACHE *f, DBUG_RETURN(1); } - /* when moving these functions to mysys, don't forget to remove slave.cc from libmysqld/CMakeLists.txt @@ -1122,6 +1370,7 @@ int init_floatvar_from_file(float* var, IO_CACHE* f, float default_val) going to ignore (to not log them in the relay log). Items being read are supposed to be decimal output of values of a type shorter or equal of @c long and separated by the single space. + It also used to restore DO_DOMAIN_IDS & IGNORE_DOMAIN_IDS lists. @param arr @c DYNAMIC_ARRAY pointer to storage for servers id @param f @c IO_CACHE pointer to the source file @@ -1142,7 +1391,7 @@ int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f) if ((read_size= my_b_gets(f, buf_act, sizeof(buf))) == 0) { - return 0; // no line in master.info + DBUG_RETURN(0); // no line in master.info } if (read_size + 1 == sizeof(buf) && buf[sizeof(buf) - 2] != '\n') { @@ -1221,6 +1470,10 @@ bool is_network_error(uint errorno) errorno == ER_NET_READ_INTERRUPTED || errorno == ER_SERVER_SHUTDOWN) return TRUE; +#ifdef WITH_WSREP + if (errorno == ER_UNKNOWN_COM_ERROR) + return TRUE; +#endif return FALSE; } @@ -1243,7 +1496,7 @@ bool is_network_error(uint errorno) static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi) { - char err_buff[MAX_SLAVE_ERRMSG]; + char err_buff[MAX_SLAVE_ERRMSG], err_buff2[MAX_SLAVE_ERRMSG]; const char* errmsg= 0; int err_code= 0; MYSQL_RES *master_res= 0; @@ -1260,23 +1513,28 @@ static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi) if (!my_isdigit(&my_charset_bin,*mysql->server_version)) { - errmsg = "Master reported unrecognized MySQL version"; + errmsg= err_buff2; + snprintf(err_buff2, sizeof(err_buff2), + "Master reported unrecognized MySQL version: %s", + mysql->server_version); err_code= ER_SLAVE_FATAL_ERROR; - sprintf(err_buff, ER(err_code), errmsg); + sprintf(err_buff, ER_DEFAULT(err_code), err_buff2); } else { /* Note the following switch will bug when we have MySQL branch 30 ;) */ - switch (version) - { + switch (version) { case 0: case 1: case 2: - errmsg = "Master reported unrecognized MySQL version"; + errmsg= err_buff2; + snprintf(err_buff2, sizeof(err_buff2), + "Master reported unrecognized MySQL version: %s", + mysql->server_version); err_code= ER_SLAVE_FATAL_ERROR; - sprintf(err_buff, ER(err_code), errmsg); + sprintf(err_buff, ER_DEFAULT(err_code), err_buff2); break; case 3: mi->rli.relay_log.description_event_for_queue= new @@ -1316,7 +1574,7 @@ static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi) { errmsg= "default Format_description_log_event"; err_code= ER_SLAVE_CREATE_EVENT_FAILURE; - sprintf(err_buff, ER(err_code), errmsg); + sprintf(err_buff, ER_DEFAULT(err_code), errmsg); goto err; } @@ -1387,11 +1645,11 @@ static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi) mi->clock_diff_with_master= (long) (time((time_t*) 0) - strtoul(master_row[0], 0, 10)); } - else if (check_io_slave_killed(mi->io_thd, mi, NULL)) + else if (check_io_slave_killed(mi, NULL)) goto slave_killed_err; else if (is_network_error(mysql_errno(mysql))) { - mi->report(WARNING_LEVEL, mysql_errno(mysql), + mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL, "Get master clock failed with error: %s", mysql_error(mysql)); goto network_err; } @@ -1437,7 +1695,8 @@ static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi) (master_res= mysql_store_result(mysql)) && (master_row= mysql_fetch_row(master_res))) { - if ((::server_id == (mi->master_id= strtoul(master_row[1], 0, 10))) && + if ((global_system_variables.server_id == + (mi->master_id= strtoul(master_row[1], 0, 10))) && !mi->rli.replicate_same_server_id) { errmsg= "The slave I/O thread stops because master and slave have equal \ @@ -1445,17 +1704,17 @@ MySQL server ids; these ids must be different for replication to work (or \ the --replicate-same-server-id option must be used on slave but this does \ not always make sense; please check the manual before using it)."; err_code= ER_SLAVE_FATAL_ERROR; - sprintf(err_buff, ER(err_code), errmsg); + sprintf(err_buff, ER_DEFAULT(err_code), errmsg); goto err; } } else if (mysql_errno(mysql)) { - if (check_io_slave_killed(mi->io_thd, mi, NULL)) + if (check_io_slave_killed(mi, NULL)) goto slave_killed_err; else if (is_network_error(mysql_errno(mysql))) { - mi->report(WARNING_LEVEL, mysql_errno(mysql), + mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL, "Get master SERVER_ID failed with error: %s", mysql_error(mysql)); goto network_err; } @@ -1468,7 +1727,7 @@ when it try to get the value of SERVER_ID variable from master."; } else if (!master_row && master_res) { - mi->report(WARNING_LEVEL, ER_UNKNOWN_SYSTEM_VARIABLE, + mi->report(WARNING_LEVEL, ER_UNKNOWN_SYSTEM_VARIABLE, NULL, "Unknown system variable 'SERVER_ID' on master, \ maybe it is a *VERY OLD MASTER*."); } @@ -1481,7 +1740,7 @@ maybe it is a *VERY OLD MASTER*."); { errmsg= "Slave configured with server id filtering could not detect the master server id."; err_code= ER_SLAVE_FATAL_ERROR; - sprintf(err_buff, ER(err_code), errmsg); + sprintf(err_buff, ER_DEFAULT(err_code), errmsg); goto err; } @@ -1520,15 +1779,15 @@ maybe it is a *VERY OLD MASTER*."); different values for the COLLATION_SERVER global variable. The values must \ be equal for the Statement-format replication to work"; err_code= ER_SLAVE_FATAL_ERROR; - sprintf(err_buff, ER(err_code), errmsg); + sprintf(err_buff, ER_DEFAULT(err_code), errmsg); goto err; } } - else if (check_io_slave_killed(mi->io_thd, mi, NULL)) + else if (check_io_slave_killed(mi, NULL)) goto slave_killed_err; else if (is_network_error(mysql_errno(mysql))) { - mi->report(WARNING_LEVEL, mysql_errno(mysql), + mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL, "Get master COLLATION_SERVER failed with error: %s", mysql_error(mysql)); goto network_err; } @@ -1542,7 +1801,7 @@ when it try to get the value of COLLATION_SERVER global variable from master."; goto err; } else - mi->report(WARNING_LEVEL, ER_UNKNOWN_SYSTEM_VARIABLE, + mi->report(WARNING_LEVEL, ER_UNKNOWN_SYSTEM_VARIABLE, NULL, "Unknown system variable 'COLLATION_SERVER' on master, \ maybe it is a *VERY OLD MASTER*. *NOTE*: slave may experience \ inconsistency if replicated data deals with collation."); @@ -1583,15 +1842,15 @@ inconsistency if replicated data deals with collation."); different values for the TIME_ZONE global variable. The values must \ be equal for the Statement-format replication to work"; err_code= ER_SLAVE_FATAL_ERROR; - sprintf(err_buff, ER(err_code), errmsg); + sprintf(err_buff, ER_DEFAULT(err_code), errmsg); goto err; } } - else if (check_io_slave_killed(mi->io_thd, mi, NULL)) + else if (check_io_slave_killed(mi, NULL)) goto slave_killed_err; else if (is_network_error(err_code= mysql_errno(mysql))) { - mi->report(ERROR_LEVEL, err_code, + mi->report(ERROR_LEVEL, err_code, NULL, "Get master TIME_ZONE failed with error: %s", mysql_error(mysql)); goto network_err; @@ -1599,7 +1858,7 @@ be equal for the Statement-format replication to work"; else if (err_code == ER_UNKNOWN_SYSTEM_VARIABLE) { /* We use ERROR_LEVEL to get the error logged to file */ - mi->report(ERROR_LEVEL, err_code, + mi->report(ERROR_LEVEL, err_code, NULL, "MySQL master doesn't have a TIME_ZONE variable. Note that" "if your timezone is not same between master and slave, your " @@ -1622,14 +1881,13 @@ when it try to get the value of TIME_ZONE global variable from master."; if (mi->heartbeat_period != 0.0) { - char llbuf[22]; - const char query_format[]= "SET @master_heartbeat_period= %s"; - char query[sizeof(query_format) - 2 + sizeof(llbuf)]; + const char query_format[]= "SET @master_heartbeat_period= %llu"; + char query[sizeof(query_format) + 32]; /* the period is an ulonglong of nano-secs. */ - llstr((ulonglong) (mi->heartbeat_period*1000000000UL), llbuf); - sprintf(query, query_format, llbuf); + my_snprintf(query, sizeof(query), query_format, + (ulonglong) (mi->heartbeat_period*1000000000UL)); DBUG_EXECUTE_IF("simulate_slave_heartbeat_network_error", { static ulong dbug_count= 0; @@ -1638,13 +1896,13 @@ when it try to get the value of TIME_ZONE global variable from master."; }); if (mysql_real_query(mysql, query, strlen(query))) { - if (check_io_slave_killed(mi->io_thd, mi, NULL)) + if (check_io_slave_killed(mi, NULL)) goto slave_killed_err; if (is_network_error(mysql_errno(mysql))) { IF_DBUG(heartbeat_network_error: , ) - mi->report(WARNING_LEVEL, mysql_errno(mysql), + mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL, "SET @master_heartbeat_period to master failed with error: %s", mysql_error(mysql)); mysql_free_result(mysql_store_result(mysql)); @@ -1687,7 +1945,7 @@ when it try to get the value of TIME_ZONE global variable from master."; rc= mysql_real_query(mysql, query, strlen(query)); if (rc != 0) { - if (check_io_slave_killed(mi->io_thd, mi, NULL)) + if (check_io_slave_killed(mi, NULL)) goto slave_killed_err; if (mysql_errno(mysql) == ER_UNKNOWN_SYSTEM_VARIABLE) @@ -1696,7 +1954,7 @@ when it try to get the value of TIME_ZONE global variable from master."; if (global_system_variables.log_warnings > 1) { // this is tolerable as OM -> NS is supported - mi->report(WARNING_LEVEL, mysql_errno(mysql), + mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL, "Notifying master by %s failed with " "error: %s", query, mysql_error(mysql)); } @@ -1705,7 +1963,7 @@ when it try to get the value of TIME_ZONE global variable from master."; { if (is_network_error(mysql_errno(mysql))) { - mi->report(WARNING_LEVEL, mysql_errno(mysql), + mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL, "Notifying master by %s failed with " "error: %s", query, mysql_error(mysql)); mysql_free_result(mysql_store_result(mysql)); @@ -1731,17 +1989,17 @@ when it try to get the value of TIME_ZONE global variable from master."; (master_row= mysql_fetch_row(master_res)) && (master_row[0] != NULL)) { - mi->checksum_alg_before_fd= (uint8) - find_type(master_row[0], &binlog_checksum_typelib, 1) - 1; + mi->checksum_alg_before_fd= (enum_binlog_checksum_alg) + (find_type(master_row[0], &binlog_checksum_typelib, 1) - 1); // valid outcome is either of DBUG_ASSERT(mi->checksum_alg_before_fd == BINLOG_CHECKSUM_ALG_OFF || mi->checksum_alg_before_fd == BINLOG_CHECKSUM_ALG_CRC32); } - else if (check_io_slave_killed(mi->io_thd, mi, NULL)) + else if (check_io_slave_killed(mi, NULL)) goto slave_killed_err; else if (is_network_error(mysql_errno(mysql))) { - mi->report(WARNING_LEVEL, mysql_errno(mysql), + mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL, "Get master BINLOG_CHECKSUM failed with error: %s", mysql_error(mysql)); goto network_err; } @@ -1778,7 +2036,7 @@ past_checksum: err_code= mysql_errno(mysql); if (is_network_error(err_code)) { - mi->report(ERROR_LEVEL, err_code, + mi->report(ERROR_LEVEL, err_code, NULL, "Setting master-side filtering of @@skip_replication failed " "with error: %s", mysql_error(mysql)); goto network_err; @@ -1808,13 +2066,277 @@ past_checksum: } } } + + /* Announce MariaDB slave capabilities. */ + DBUG_EXECUTE_IF("simulate_slave_capability_none", goto after_set_capability;); + { + int rc= DBUG_EVALUATE_IF("simulate_slave_capability_old_53", + mysql_real_query(mysql, STRING_WITH_LEN("SET @mariadb_slave_capability=" + STRINGIFY_ARG(MARIA_SLAVE_CAPABILITY_ANNOTATE))), + mysql_real_query(mysql, STRING_WITH_LEN("SET @mariadb_slave_capability=" + STRINGIFY_ARG(MARIA_SLAVE_CAPABILITY_MINE)))); + if (rc) + { + err_code= mysql_errno(mysql); + if (is_network_error(err_code)) + { + mi->report(ERROR_LEVEL, err_code, NULL, + "Setting @mariadb_slave_capability failed with error: %s", + mysql_error(mysql)); + goto network_err; + } + else + { + /* Fatal error */ + errmsg= "The slave I/O thread stops because a fatal error is " + "encountered when it tries to set @mariadb_slave_capability."; + sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql)); + goto err; + } + } + } +#ifndef DBUG_OFF +after_set_capability: +#endif + + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + /* Request dump to start from slave replication GTID state. */ + int rc; + char str_buf[256]; + String query_str(str_buf, sizeof(str_buf), system_charset_info); + query_str.length(0); + + /* + Read the master @@GLOBAL.gtid_domain_id variable. + This is mostly to check that master is GTID aware, but we could later + perhaps use it to check that different multi-source masters are correctly + configured with distinct domain_id. + */ + if (mysql_real_query(mysql, + STRING_WITH_LEN("SELECT @@GLOBAL.gtid_domain_id")) || + !(master_res= mysql_store_result(mysql)) || + !(master_row= mysql_fetch_row(master_res))) + { + err_code= mysql_errno(mysql); + if (is_network_error(err_code)) + { + mi->report(ERROR_LEVEL, err_code, NULL, + "Get master @@GLOBAL.gtid_domain_id failed with error: %s", + mysql_error(mysql)); + goto network_err; + } + else + { + errmsg= "The slave I/O thread stops because master does not support " + "MariaDB global transaction id. A fatal error is encountered when " + "it tries to SELECT @@GLOBAL.gtid_domain_id."; + sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql)); + goto err; + } + } + mysql_free_result(master_res); + master_res= NULL; + + query_str.append(STRING_WITH_LEN("SET @slave_connect_state='"), + system_charset_info); + if (mi->gtid_current_pos.append_to_string(&query_str)) + { + err_code= ER_OUTOFMEMORY; + errmsg= "The slave I/O thread stops because a fatal out-of-memory " + "error is encountered when it tries to compute @slave_connect_state."; + sprintf(err_buff, "%s Error: Out of memory", errmsg); + goto err; + } + query_str.append(STRING_WITH_LEN("'"), system_charset_info); + + rc= mysql_real_query(mysql, query_str.ptr(), query_str.length()); + if (rc) + { + err_code= mysql_errno(mysql); + if (is_network_error(err_code)) + { + mi->report(ERROR_LEVEL, err_code, NULL, + "Setting @slave_connect_state failed with error: %s", + mysql_error(mysql)); + goto network_err; + } + else + { + /* Fatal error */ + errmsg= "The slave I/O thread stops because a fatal error is " + "encountered when it tries to set @slave_connect_state."; + sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql)); + goto err; + } + } + + query_str.length(0); + if (query_str.append(STRING_WITH_LEN("SET @slave_gtid_strict_mode="), + system_charset_info) || + query_str.append_ulonglong(opt_gtid_strict_mode != false)) + { + err_code= ER_OUTOFMEMORY; + errmsg= "The slave I/O thread stops because a fatal out-of-memory " + "error is encountered when it tries to set @slave_gtid_strict_mode."; + sprintf(err_buff, "%s Error: Out of memory", errmsg); + goto err; + } + + rc= mysql_real_query(mysql, query_str.ptr(), query_str.length()); + if (rc) + { + err_code= mysql_errno(mysql); + if (is_network_error(err_code)) + { + mi->report(ERROR_LEVEL, err_code, NULL, + "Setting @slave_gtid_strict_mode failed with error: %s", + mysql_error(mysql)); + goto network_err; + } + else + { + /* Fatal error */ + errmsg= "The slave I/O thread stops because a fatal error is " + "encountered when it tries to set @slave_gtid_strict_mode."; + sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql)); + goto err; + } + } + + query_str.length(0); + if (query_str.append(STRING_WITH_LEN("SET @slave_gtid_ignore_duplicates="), + system_charset_info) || + query_str.append_ulonglong(opt_gtid_ignore_duplicates != false)) + { + err_code= ER_OUTOFMEMORY; + errmsg= "The slave I/O thread stops because a fatal out-of-memory error " + "is encountered when it tries to set @slave_gtid_ignore_duplicates."; + sprintf(err_buff, "%s Error: Out of memory", errmsg); + goto err; + } + + rc= mysql_real_query(mysql, query_str.ptr(), query_str.length()); + if (rc) + { + err_code= mysql_errno(mysql); + if (is_network_error(err_code)) + { + mi->report(ERROR_LEVEL, err_code, NULL, + "Setting @slave_gtid_ignore_duplicates failed with " + "error: %s", mysql_error(mysql)); + goto network_err; + } + else + { + /* Fatal error */ + errmsg= "The slave I/O thread stops because a fatal error is " + "encountered when it tries to set @slave_gtid_ignore_duplicates."; + sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql)); + goto err; + } + } + + if (mi->rli.until_condition == Relay_log_info::UNTIL_GTID) + { + query_str.length(0); + query_str.append(STRING_WITH_LEN("SET @slave_until_gtid='"), + system_charset_info); + if (mi->rli.until_gtid_pos.append_to_string(&query_str)) + { + err_code= ER_OUTOFMEMORY; + errmsg= "The slave I/O thread stops because a fatal out-of-memory " + "error is encountered when it tries to compute @slave_until_gtid."; + sprintf(err_buff, "%s Error: Out of memory", errmsg); + goto err; + } + query_str.append(STRING_WITH_LEN("'"), system_charset_info); + + rc= mysql_real_query(mysql, query_str.ptr(), query_str.length()); + if (rc) + { + err_code= mysql_errno(mysql); + if (is_network_error(err_code)) + { + mi->report(ERROR_LEVEL, err_code, NULL, + "Setting @slave_until_gtid failed with error: %s", + mysql_error(mysql)); + goto network_err; + } + else + { + /* Fatal error */ + errmsg= "The slave I/O thread stops because a fatal error is " + "encountered when it tries to set @slave_until_gtid."; + sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql)); + goto err; + } + } + } + } + else + { + /* + If we are not using GTID to connect this time, then instead request + the corresponding GTID position from the master, so that the user + can reconnect the next time using MASTER_GTID_POS=AUTO. + */ + char quote_buf[2*sizeof(mi->master_log_name)+1]; + char str_buf[28+2*sizeof(mi->master_log_name)+10]; + String query(str_buf, sizeof(str_buf), system_charset_info); + query.length(0); + + query.append("SELECT binlog_gtid_pos('"); + escape_quotes_for_mysql(&my_charset_bin, quote_buf, sizeof(quote_buf), + mi->master_log_name, strlen(mi->master_log_name)); + query.append(quote_buf); + query.append("',"); + query.append_ulonglong(mi->master_log_pos); + query.append(")"); + + if (!mysql_real_query(mysql, query.c_ptr_safe(), query.length()) && + (master_res= mysql_store_result(mysql)) && + (master_row= mysql_fetch_row(master_res)) && + (master_row[0] != NULL)) + { + rpl_global_gtid_slave_state->load(mi->io_thd, master_row[0], + strlen(master_row[0]), false, false); + } + else if (check_io_slave_killed(mi, NULL)) + goto slave_killed_err; + else if (is_network_error(mysql_errno(mysql))) + { + mi->report(WARNING_LEVEL, mysql_errno(mysql), NULL, + "Get master GTID position failed with error: %s", mysql_error(mysql)); + goto network_err; + } + else + { + /* + ToDo: If the master does not have the binlog_gtid_pos() function, it + just means that it is an old master with no GTID support, so we should + do nothing. + + However, if binlog_gtid_pos() exists, but fails or returns NULL, then + it means that the requested position is not valid. We could use this + to catch attempts to replicate from within the middle of an event, + avoiding strange failures or possible corruption. + */ + } + if (master_res) + { + mysql_free_result(master_res); + master_res= NULL; + } + } + err: if (errmsg) { if (master_res) mysql_free_result(master_res); DBUG_ASSERT(err_code != 0); - mi->report(ERROR_LEVEL, err_code, "%s", err_buff); + mi->report(ERROR_LEVEL, err_code, NULL, "%s", err_buff); DBUG_RETURN(1); } @@ -1835,21 +2357,27 @@ slave_killed_err: static bool wait_for_relay_log_space(Relay_log_info* rli) { bool slave_killed=0; + bool ignore_log_space_limit; Master_info* mi = rli->mi; - const char *save_proc_info; + PSI_stage_info old_stage; THD* thd = mi->io_thd; DBUG_ENTER("wait_for_relay_log_space"); mysql_mutex_lock(&rli->log_space_lock); - save_proc_info= thd->enter_cond(&rli->log_space_cond, - &rli->log_space_lock, - "\ -Waiting for the slave SQL thread to free enough relay log space"); + thd->ENTER_COND(&rli->log_space_cond, + &rli->log_space_lock, + &stage_waiting_for_relay_log_space, + &old_stage); while (rli->log_space_limit < rli->log_space_total && - !(slave_killed=io_slave_killed(thd,mi)) && + !(slave_killed=io_slave_killed(mi)) && !rli->ignore_log_space_limit) mysql_cond_wait(&rli->log_space_cond, &rli->log_space_lock); + ignore_log_space_limit= rli->ignore_log_space_limit; + rli->ignore_log_space_limit= 0; + + thd->EXIT_COND(&old_stage); + /* Makes the IO thread read only one event at a time until the SQL thread is able to purge the relay @@ -1873,33 +2401,28 @@ Waiting for the slave SQL thread to free enough relay log space"); thread sleeps waiting for events. */ - if (rli->ignore_log_space_limit) + + if (ignore_log_space_limit) { #ifndef DBUG_OFF { - char llbuf1[22], llbuf2[22]; - DBUG_PRINT("info", ("log_space_limit=%s " - "log_space_total=%s " + DBUG_PRINT("info", ("log_space_limit=%llu log_space_total=%llu " "ignore_log_space_limit=%d " "sql_force_rotate_relay=%d", - llstr(rli->log_space_limit,llbuf1), - llstr(rli->log_space_total,llbuf2), + rli->log_space_limit, rli->log_space_total, (int) rli->ignore_log_space_limit, (int) rli->sql_force_rotate_relay)); } #endif if (rli->sql_force_rotate_relay) { - mysql_mutex_lock(&active_mi->data_lock); + mysql_mutex_lock(&mi->data_lock); rotate_relay_log(rli->mi); - mysql_mutex_unlock(&active_mi->data_lock); + mysql_mutex_unlock(&mi->data_lock); rli->sql_force_rotate_relay= false; } - - rli->ignore_log_space_limit= false; } - thd->exit_cond(save_proc_info); DBUG_RETURN(slave_killed); } @@ -1925,34 +2448,67 @@ static void write_ignored_events_info_to_relay_log(THD *thd, Master_info *mi) DBUG_ASSERT(thd == mi->io_thd); mysql_mutex_lock(log_lock); - if (rli->ign_master_log_name_end[0]) - { - DBUG_PRINT("info",("writing a Rotate event to track down ignored events")); - Rotate_log_event *ev= new Rotate_log_event(rli->ign_master_log_name_end, - 0, rli->ign_master_log_pos_end, - Rotate_log_event::DUP_NAME); - rli->ign_master_log_name_end[0]= 0; - /* can unlock before writing as slave SQL thd will soon see our Rotate */ + if (rli->ign_master_log_name_end[0] || rli->ign_gtids.count()) + { + Rotate_log_event *rev= NULL; + Gtid_list_log_event *glev= NULL; + if (rli->ign_master_log_name_end[0]) + { + rev= new Rotate_log_event(rli->ign_master_log_name_end, + 0, rli->ign_master_log_pos_end, + Rotate_log_event::DUP_NAME); + rli->ign_master_log_name_end[0]= 0; + if (unlikely(!(bool)rev)) + mi->report(ERROR_LEVEL, ER_SLAVE_CREATE_EVENT_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_CREATE_EVENT_FAILURE), + "Rotate_event (out of memory?)," + " SHOW SLAVE STATUS may be inaccurate"); + } + if (rli->ign_gtids.count()) + { + DBUG_ASSERT(!rli->is_in_group()); // Ensure no active transaction + glev= new Gtid_list_log_event(&rli->ign_gtids, + Gtid_list_log_event::FLAG_IGN_GTIDS); + rli->ign_gtids.reset(); + if (unlikely(!(bool)glev)) + mi->report(ERROR_LEVEL, ER_SLAVE_CREATE_EVENT_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_CREATE_EVENT_FAILURE), + "Gtid_list_event (out of memory?)," + " gtid_slave_pos may be inaccurate"); + } + + /* Can unlock before writing as slave SQL thd will soon see our event. */ mysql_mutex_unlock(log_lock); - if (likely((bool)ev)) + if (rev) { - ev->server_id= 0; // don't be ignored by slave SQL thread - if (unlikely(rli->relay_log.append(ev))) - mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, - ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE), + DBUG_PRINT("info",("writing a Rotate event to track down ignored events")); + rev->server_id= 0; // don't be ignored by slave SQL thread + if (unlikely(rli->relay_log.append(rev))) + mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_RELAY_LOG_WRITE_FAILURE), "failed to write a Rotate event" " to the relay log, SHOW SLAVE STATUS may be" " inaccurate"); + delete rev; + } + if (glev) + { + DBUG_PRINT("info",("writing a Gtid_list event to track down ignored events")); + glev->server_id= 0; // don't be ignored by slave SQL thread + glev->set_artificial_event(); // Don't mess up Exec_Master_Log_Pos + if (unlikely(rli->relay_log.append(glev))) + mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_RELAY_LOG_WRITE_FAILURE), + "failed to write a Gtid_list event to the relay log, " + "gtid_slave_pos may be inaccurate"); + delete glev; + } + if (likely (rev || glev)) + { rli->relay_log.harvest_bytes_written(&rli->log_space_total); if (flush_master_info(mi, TRUE, TRUE)) sql_print_error("Failed to flush master info file"); - delete ev; } - else - mi->report(ERROR_LEVEL, ER_SLAVE_CREATE_EVENT_FAILURE, - ER(ER_SLAVE_CREATE_EVENT_FAILURE), - "Rotate_event (out of memory?)," - " SHOW SLAVE STATUS may be inaccurate"); } else mysql_mutex_unlock(log_lock); @@ -2001,7 +2557,7 @@ int register_slave_on_master(MYSQL* mysql, Master_info *mi, DBUG_RETURN(0); } - int4store(pos, server_id); pos+= 4; + int4store(pos, global_system_variables.server_id); pos+= 4; pos= net_store_data(pos, (uchar*) report_host, report_host_len); pos= net_store_data(pos, (uchar*) report_user, report_user_len); pos= net_store_data(pos, (uchar*) report_password, report_password_len); @@ -2021,12 +2577,12 @@ int register_slave_on_master(MYSQL* mysql, Master_info *mi, { *suppress_warnings= TRUE; // Suppress reconnect warning } - else if (!check_io_slave_killed(mi->io_thd, mi, NULL)) + else if (!check_io_slave_killed(mi, NULL)) { char buf[256]; my_snprintf(buf, sizeof(buf), "%s (Errno: %d)", mysql_error(mysql), mysql_errno(mysql)); - mi->report(ERROR_LEVEL, ER_SLAVE_MASTER_COM_FAILURE, + mi->report(ERROR_LEVEL, ER_SLAVE_MASTER_COM_FAILURE, NULL, ER(ER_SLAVE_MASTER_COM_FAILURE), "COM_REGISTER_SLAVE", buf); } DBUG_RETURN(1); @@ -2046,99 +2602,283 @@ int register_slave_on_master(MYSQL* mysql, Master_info *mi, @retval FALSE success @retval TRUE failure */ -bool show_master_info(THD* thd, Master_info* mi) + +bool show_master_info(THD *thd, Master_info *mi, bool full) { - // TODO: fix this for multi-master - List<Item> field_list; - Protocol *protocol= thd->protocol; DBUG_ENTER("show_master_info"); + String gtid_pos; + List<Item> field_list; - field_list.push_back(new Item_empty_string("Slave_IO_State", - 14)); - field_list.push_back(new Item_empty_string("Master_Host", - sizeof(mi->host))); - field_list.push_back(new Item_empty_string("Master_User", - sizeof(mi->user))); - field_list.push_back(new Item_return_int("Master_Port", 7, - MYSQL_TYPE_LONG)); - field_list.push_back(new Item_return_int("Connect_Retry", 10, - MYSQL_TYPE_LONG)); - field_list.push_back(new Item_empty_string("Master_Log_File", - FN_REFLEN)); - field_list.push_back(new Item_return_int("Read_Master_Log_Pos", 10, - MYSQL_TYPE_LONGLONG)); - field_list.push_back(new Item_empty_string("Relay_Log_File", - FN_REFLEN)); - field_list.push_back(new Item_return_int("Relay_Log_Pos", 10, - MYSQL_TYPE_LONGLONG)); - field_list.push_back(new Item_empty_string("Relay_Master_Log_File", - FN_REFLEN)); - field_list.push_back(new Item_empty_string("Slave_IO_Running", 3)); - field_list.push_back(new Item_empty_string("Slave_SQL_Running", 3)); - field_list.push_back(new Item_empty_string("Replicate_Do_DB", 20)); - field_list.push_back(new Item_empty_string("Replicate_Ignore_DB", 20)); - field_list.push_back(new Item_empty_string("Replicate_Do_Table", 20)); - field_list.push_back(new Item_empty_string("Replicate_Ignore_Table", 23)); - field_list.push_back(new Item_empty_string("Replicate_Wild_Do_Table", 24)); - field_list.push_back(new Item_empty_string("Replicate_Wild_Ignore_Table", - 28)); - field_list.push_back(new Item_return_int("Last_Errno", 4, MYSQL_TYPE_LONG)); - field_list.push_back(new Item_empty_string("Last_Error", 20)); - field_list.push_back(new Item_return_int("Skip_Counter", 10, - MYSQL_TYPE_LONG)); - field_list.push_back(new Item_return_int("Exec_Master_Log_Pos", 10, - MYSQL_TYPE_LONGLONG)); - field_list.push_back(new Item_return_int("Relay_Log_Space", 10, - MYSQL_TYPE_LONGLONG)); - field_list.push_back(new Item_empty_string("Until_Condition", 6)); - field_list.push_back(new Item_empty_string("Until_Log_File", FN_REFLEN)); - field_list.push_back(new Item_return_int("Until_Log_Pos", 10, - MYSQL_TYPE_LONGLONG)); - field_list.push_back(new Item_empty_string("Master_SSL_Allowed", 7)); - field_list.push_back(new Item_empty_string("Master_SSL_CA_File", - sizeof(mi->ssl_ca))); - field_list.push_back(new Item_empty_string("Master_SSL_CA_Path", - sizeof(mi->ssl_capath))); - field_list.push_back(new Item_empty_string("Master_SSL_Cert", - sizeof(mi->ssl_cert))); - field_list.push_back(new Item_empty_string("Master_SSL_Cipher", - sizeof(mi->ssl_cipher))); - field_list.push_back(new Item_empty_string("Master_SSL_Key", - sizeof(mi->ssl_key))); - field_list.push_back(new Item_return_int("Seconds_Behind_Master", 10, - MYSQL_TYPE_LONGLONG)); - field_list.push_back(new Item_empty_string("Master_SSL_Verify_Server_Cert", - 3)); - field_list.push_back(new Item_return_int("Last_IO_Errno", 4, MYSQL_TYPE_LONG)); - field_list.push_back(new Item_empty_string("Last_IO_Error", 20)); - field_list.push_back(new Item_return_int("Last_SQL_Errno", 4, MYSQL_TYPE_LONG)); - field_list.push_back(new Item_empty_string("Last_SQL_Error", 20)); - field_list.push_back(new Item_empty_string("Replicate_Ignore_Server_Ids", - FN_REFLEN)); - field_list.push_back(new Item_return_int("Master_Server_Id", sizeof(ulong), - MYSQL_TYPE_LONG)); - - if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + if (full && rpl_global_gtid_slave_state->tostring(>id_pos, NULL, 0)) + DBUG_RETURN(TRUE); + show_master_info_get_fields(thd, &field_list, full, gtid_pos.length()); + if (thd->protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + if (send_show_master_info_data(thd, mi, full, >id_pos)) DBUG_RETURN(TRUE); + my_eof(thd); + DBUG_RETURN(FALSE); +} + +void show_master_info_get_fields(THD *thd, List<Item> *field_list, + bool full, size_t gtid_pos_length) +{ + Master_info *mi; + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ENTER("show_master_info_get_fields"); + + if (full) + { + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Connection_name", + MAX_CONNECTION_NAME), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Slave_SQL_State", 30), + mem_root); + } + + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Slave_IO_State", 30), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_Host", sizeof(mi->host)), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_User", sizeof(mi->user)), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Master_Port", 7, MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Connect_Retry", 10, + MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_Log_File", FN_REFLEN), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Read_Master_Log_Pos", 10, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Relay_Log_File", FN_REFLEN), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Relay_Log_Pos", 10, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Relay_Master_Log_File", + FN_REFLEN), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Slave_IO_Running", 3), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Slave_SQL_Running", 3), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Replicate_Do_DB", 20), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Replicate_Ignore_DB", 20), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Replicate_Do_Table", 20), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Replicate_Ignore_Table", 23), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Replicate_Wild_Do_Table", 24), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Replicate_Wild_Ignore_Table", + 28), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Last_Errno", 4, MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Last_Error", 20), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Skip_Counter", 10, + MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Exec_Master_Log_Pos", 10, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Relay_Log_Space", 10, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Until_Condition", 6), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Until_Log_File", FN_REFLEN), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Until_Log_Pos", 10, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_SSL_Allowed", 7), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_SSL_CA_File", + sizeof(mi->ssl_ca)), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_SSL_CA_Path", + sizeof(mi->ssl_capath)), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_SSL_Cert", + sizeof(mi->ssl_cert)), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_SSL_Cipher", + sizeof(mi->ssl_cipher)), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_SSL_Key", + sizeof(mi->ssl_key)), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Seconds_Behind_Master", 10, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_SSL_Verify_Server_Cert", + 3), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Last_IO_Errno", 4, + MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Last_IO_Error", 20), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Last_SQL_Errno", 4, + MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Last_SQL_Error", 20), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Replicate_Ignore_Server_Ids", + FN_REFLEN), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Master_Server_Id", sizeof(ulong), + MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_SSL_Crl", + sizeof(mi->ssl_crl)), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Master_SSL_Crlpath", + sizeof(mi->ssl_crlpath)), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Using_Gtid", + sizeof("Current_Pos")-1), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Gtid_IO_Pos", 30), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Replicate_Do_Domain_Ids", + FN_REFLEN), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Replicate_Ignore_Domain_Ids", + FN_REFLEN), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Parallel_Mode", + sizeof("conservative")-1), + mem_root); + if (full) + { + field_list->push_back(new (mem_root) + Item_return_int(thd, "Retried_transactions", 10, + MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Max_relay_log_size", 10, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Executed_log_entries", 10, + MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Slave_received_heartbeats", 10, + MYSQL_TYPE_LONG), + mem_root); + field_list->push_back(new (mem_root) + Item_float(thd, "Slave_heartbeat_period", 0.0, 3, 10), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Gtid_Slave_Pos", + gtid_pos_length), + mem_root); + } + DBUG_VOID_RETURN; +} + +/* Text for Slave_IO_Running */ +static const char *slave_running[]= { "No", "Connecting", "Preparing", "Yes" }; + +static bool send_show_master_info_data(THD *thd, Master_info *mi, bool full, + String *gtid_pos) +{ + DBUG_ENTER("send_show_master_info_data"); if (mi->host[0]) { DBUG_PRINT("info",("host is set: '%s'", mi->host)); String *packet= &thd->packet; + Protocol *protocol= thd->protocol; + Rpl_filter *rpl_filter= mi->rpl_filter; + StringBuffer<256> tmp; + protocol->prepare_for_resend(); /* slave_running can be accessed without run_lock but not other non-volotile members like mi->io_thd, which is guarded by the mutex. */ + if (full) + protocol->store(mi->connection_name.str, mi->connection_name.length, + &my_charset_bin); mysql_mutex_lock(&mi->run_lock); - protocol->store(mi->io_thd ? mi->io_thd->proc_info : "", &my_charset_bin); + if (full) + { + /* + Show what the sql driver replication thread is doing + This is only meaningful if there is only one slave thread. + */ + protocol->store(mi->rli.sql_driver_thd ? + mi->rli.sql_driver_thd->get_proc_info() : "", + &my_charset_bin); + } + protocol->store(mi->io_thd ? mi->io_thd->get_proc_info() : "", &my_charset_bin); mysql_mutex_unlock(&mi->run_lock); mysql_mutex_lock(&mi->data_lock); mysql_mutex_lock(&mi->rli.data_lock); + /* err_lock is to protect mi->last_error() */ mysql_mutex_lock(&mi->err_lock); + /* err_lock is to protect mi->rli.last_error() */ mysql_mutex_lock(&mi->rli.err_lock); protocol->store(mi->host, &my_charset_bin); protocol->store(mi->user, &my_charset_bin); @@ -2151,15 +2891,11 @@ bool show_master_info(THD* thd, Master_info* mi) &my_charset_bin); protocol->store((ulonglong) mi->rli.group_relay_log_pos); protocol->store(mi->rli.group_master_log_name, &my_charset_bin); - protocol->store(mi->slave_running == MYSQL_SLAVE_RUN_CONNECT ? - "Yes" : (mi->slave_running == MYSQL_SLAVE_RUN_NOT_CONNECT ? - "Connecting" : "No"), &my_charset_bin); + protocol->store(slave_running[mi->slave_running], &my_charset_bin); protocol->store(mi->rli.slave_running ? "Yes":"No", &my_charset_bin); protocol->store(rpl_filter->get_do_db()); protocol->store(rpl_filter->get_ignore_db()); - char buf[256]; - String tmp(buf, sizeof(buf), &my_charset_bin); rpl_filter->get_do_table(&tmp); protocol->store(&tmp); rpl_filter->get_ignore_table(&tmp); @@ -2178,7 +2914,8 @@ bool show_master_info(THD* thd, Master_info* mi) protocol->store( mi->rli.until_condition==Relay_log_info::UNTIL_NONE ? "None": ( mi->rli.until_condition==Relay_log_info::UNTIL_MASTER_POS? "Master": - "Relay"), &my_charset_bin); + ( mi->rli.until_condition==Relay_log_info::UNTIL_RELAY_POS? "Relay": + "Gtid")), &my_charset_bin); protocol->store(mi->rli.until_log_name, &my_charset_bin); protocol->store((ulonglong) mi->rli.until_log_pos); @@ -2197,11 +2934,26 @@ bool show_master_info(THD* thd, Master_info* mi) Seconds_Behind_Master: if SQL thread is running and I/O thread is connected, we can compute it otherwise show NULL (i.e. unknown). */ - if ((mi->slave_running == MYSQL_SLAVE_RUN_CONNECT) && + if ((mi->slave_running == MYSQL_SLAVE_RUN_READING) && mi->rli.slave_running) { - long time_diff= ((long)(time(0) - mi->rli.last_master_timestamp) - - mi->clock_diff_with_master); + long time_diff; + bool idle; + time_t stamp= mi->rli.last_master_timestamp; + + if (!stamp) + idle= true; + else + { + idle= mi->rli.sql_thread_caught_up; + if (mi->using_parallel() && idle && !mi->rli.parallel.workers_idle()) + idle= false; + } + if (idle) + time_diff= 0; + else + { + time_diff= ((long)(time(0) - stamp) - mi->clock_diff_with_master); /* Apparently on some systems time_diff can be <0. Here are possible reasons related to MySQL: @@ -2217,13 +2969,15 @@ bool show_master_info(THD* thd, Master_info* mi) slave is 2. At SHOW SLAVE STATUS time, assume that the difference between timestamp of slave and rli->last_master_timestamp is 0 (i.e. they are in the same second), then we get 0-(2-1)=-1 as a result. - This confuses users, so we don't go below 0: hence the max(). + This confuses users, so we don't go below 0. last_master_timestamp == 0 (an "impossible" timestamp 1970) is a special marker to say "consider we have caught up". */ - protocol->store((longlong)(mi->rli.last_master_timestamp ? - max(0, time_diff) : 0)); + if (time_diff < 0) + time_diff= 0; + } + protocol->store((longlong)time_diff); } else { @@ -2240,31 +2994,40 @@ bool show_master_info(THD* thd, Master_info* mi) // Last_SQL_Error protocol->store(mi->rli.last_error().message, &my_charset_bin); // Replicate_Ignore_Server_Ids - { - char buff[FN_REFLEN]; - ulong i, cur_len; - for (i= 0, buff[0]= 0, cur_len= 0; - i < mi->ignore_server_ids.elements; i++) - { - ulong s_id, slen; - char sbuff[FN_REFLEN]; - get_dynamic(&mi->ignore_server_ids, (uchar*) &s_id, i); - slen= sprintf(sbuff, (i==0? "%lu" : ", %lu"), s_id); - if (cur_len + slen + 4 > FN_REFLEN) - { - /* - break the loop whenever remained space could not fit - ellipses on the next cycle - */ - sprintf(buff + cur_len, "..."); - break; - } - cur_len += sprintf(buff + cur_len, "%s", sbuff); - } - protocol->store(buff, &my_charset_bin); - } + prot_store_ids(thd, &mi->ignore_server_ids); // Master_Server_id protocol->store((uint32) mi->master_id); + // Master_Ssl_Crl + protocol->store(mi->ssl_ca, &my_charset_bin); + // Master_Ssl_Crlpath + protocol->store(mi->ssl_capath, &my_charset_bin); + // Using_Gtid + protocol->store(mi->using_gtid_astext(mi->using_gtid), &my_charset_bin); + // Gtid_IO_Pos + { + mi->gtid_current_pos.to_string(&tmp); + protocol->store(tmp.ptr(), tmp.length(), &my_charset_bin); + } + + // Replicate_Do_Domain_Ids & Replicate_Ignore_Domain_Ids + mi->domain_id_filter.store_ids(thd); + + // Parallel_Mode + { + const char *mode_name= get_type(&slave_parallel_mode_typelib, + mi->parallel_mode); + protocol->store(mode_name, strlen(mode_name), &my_charset_bin); + } + + if (full) + { + protocol->store((uint32) mi->rli.retried_trans); + protocol->store((ulonglong) mi->rli.max_relay_log_size); + protocol->store((uint32) mi->rli.executed_entries); + protocol->store((uint32) mi->received_heartbeats); + protocol->store((double) mi->heartbeat_period, 3, &tmp); + protocol->store(gtid_pos->ptr(), gtid_pos->length(), &my_charset_bin); + } mysql_mutex_unlock(&mi->rli.err_lock); mysql_mutex_unlock(&mi->err_lock); @@ -2274,6 +3037,81 @@ bool show_master_info(THD* thd, Master_info* mi) if (my_net_write(&thd->net, (uchar*) thd->packet.ptr(), packet->length())) DBUG_RETURN(TRUE); } + DBUG_RETURN(FALSE); +} + + +/* Used to sort connections by name */ + +static int cmp_mi_by_name(const Master_info **arg1, + const Master_info **arg2) +{ + return my_strcasecmp(system_charset_info, (*arg1)->connection_name.str, + (*arg2)->connection_name.str); +} + + +/** + Execute a SHOW FULL SLAVE STATUS statement. + + @param thd Pointer to THD object for the client thread executing the + statement. + + Elements are sorted according to the original connection_name. + + @retval FALSE success + @retval TRUE failure + + @note + master_info_index is protected by LOCK_active_mi. +*/ + +bool show_all_master_info(THD* thd) +{ + uint i, elements; + String gtid_pos; + Master_info **tmp; + List<Item> field_list; + DBUG_ENTER("show_master_info"); + mysql_mutex_assert_owner(&LOCK_active_mi); + + gtid_pos.length(0); + if (rpl_append_gtid_state(>id_pos, true)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + DBUG_RETURN(TRUE); + } + + show_master_info_get_fields(thd, &field_list, 1, gtid_pos.length()); + if (thd->protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + if (!master_info_index || + !(elements= master_info_index->master_info_hash.records)) + goto end; + + /* + Sort lines to get them into a predicted order + (needed for test cases and to not confuse users) + */ + if (!(tmp= (Master_info**) thd->alloc(sizeof(Master_info*) * elements))) + DBUG_RETURN(TRUE); + + for (i= 0; i < elements; i++) + { + tmp[i]= (Master_info *) my_hash_element(&master_info_index-> + master_info_hash, i); + } + my_qsort(tmp, elements, sizeof(Master_info*), (qsort_cmp) cmp_mi_by_name); + + for (i= 0; i < elements; i++) + { + if (send_show_master_info_data(thd, tmp[i], 1, >id_pos)) + DBUG_RETURN(TRUE); + } + +end: my_eof(thd); DBUG_RETURN(FALSE); } @@ -2301,7 +3139,7 @@ void set_slave_thread_options(THD* thd) DBUG_VOID_RETURN; } -void set_slave_thread_default_charset(THD* thd, Relay_log_info const *rli) +void set_slave_thread_default_charset(THD* thd, rpl_group_info *rgi) { DBUG_ENTER("set_slave_thread_default_charset"); @@ -2313,13 +3151,7 @@ void set_slave_thread_default_charset(THD* thd, Relay_log_info const *rli) global_system_variables.collation_server; thd->update_charset(); - /* - We use a const cast here since the conceptual (and externally - visible) behavior of the function is to set the default charset of - the thread. That the cache has to be invalidated is a secondary - effect. - */ - const_cast<Relay_log_info*>(rli)->cached_charset_invalidate(); + thd->system_thread_info.rpl_sql_info->cached_charset_invalidate(); DBUG_VOID_RETURN; } @@ -2327,39 +3159,43 @@ void set_slave_thread_default_charset(THD* thd, Relay_log_info const *rli) init_slave_thread() */ -static int init_slave_thread(THD* thd, SLAVE_THD_TYPE thd_type) +static int init_slave_thread(THD* thd, Master_info *mi, + SLAVE_THD_TYPE thd_type) { DBUG_ENTER("init_slave_thread"); -#if !defined(DBUG_OFF) - int simulate_error= 0; -#endif - thd->system_thread = (thd_type == SLAVE_THD_SQL) ? - SYSTEM_THREAD_SLAVE_SQL : SYSTEM_THREAD_SLAVE_IO; - thd->security_ctx->skip_grants(); - my_net_init(&thd->net, 0); - thd->slave_thread = 1; - thd->enable_slow_log= opt_log_slow_slave_statements; - thd->variables.log_slow_filter= global_system_variables.log_slow_filter; - set_slave_thread_options(thd); - mysql_mutex_lock(&LOCK_thread_count); - thd->thread_id= thd->variables.pseudo_thread_id= thread_id++; - mysql_mutex_unlock(&LOCK_thread_count); - + int simulate_error __attribute__((unused))= 0; DBUG_EXECUTE_IF("simulate_io_slave_error_on_init", simulate_error|= (1 << SLAVE_THD_IO);); DBUG_EXECUTE_IF("simulate_sql_slave_error_on_init", simulate_error|= (1 << SLAVE_THD_SQL);); + + thd->system_thread = (thd_type == SLAVE_THD_SQL) ? + SYSTEM_THREAD_SLAVE_SQL : SYSTEM_THREAD_SLAVE_IO; + thread_safe_increment32(&service_thread_count); + + /* We must call store_globals() before doing my_net_init() */ if (init_thr_lock() || thd->store_globals() || + my_net_init(&thd->net, 0, thd, MYF(MY_THREAD_SPECIFIC)) || IF_DBUG(simulate_error & (1<< thd_type), 0)) { thd->cleanup(); DBUG_RETURN(-1); } + thd->security_ctx->skip_grants(); + thd->slave_thread= 1; + thd->connection_name= mi->connection_name; + thd->variables.sql_log_slow= opt_log_slow_slave_statements; + thd->variables.log_slow_filter= global_system_variables.log_slow_filter; + set_slave_thread_options(thd); + mysql_mutex_lock(&LOCK_thread_count); + thd->thread_id= thd->variables.pseudo_thread_id= thread_id++; + mysql_mutex_unlock(&LOCK_thread_count); + if (thd_type == SLAVE_THD_SQL) - thd_proc_info(thd, "Waiting for the next event in relay log"); + THD_STAGE_INFO(thd, stage_waiting_for_the_next_event_in_relay_log); else - thd_proc_info(thd, "Waiting for master update"); + THD_STAGE_INFO(thd, stage_waiting_for_master_update); thd->set_time(); /* Do not use user-supplied timeout value for system threads. */ thd->variables.lock_wait_timeout= LONG_TIMEOUT; @@ -2377,13 +3213,12 @@ static int init_slave_thread(THD* thd, SLAVE_THD_TYPE thd_type) @retval True if the thread has been killed, false otherwise. */ template <typename killed_func, typename rpl_info> -static inline bool slave_sleep(THD *thd, time_t seconds, - killed_func func, rpl_info info) +static bool slave_sleep(THD *thd, time_t seconds, + killed_func func, rpl_info info) { bool ret; struct timespec abstime; - const char *old_proc_info; mysql_mutex_t *lock= &info->sleep_lock; mysql_cond_t *cond= &info->sleep_cond; @@ -2391,16 +3226,16 @@ static inline bool slave_sleep(THD *thd, time_t seconds, /* Absolute system time at which the sleep time expires. */ set_timespec(abstime, seconds); mysql_mutex_lock(lock); - old_proc_info= thd->enter_cond(cond, lock, thd->proc_info); + thd->ENTER_COND(cond, lock, NULL, NULL); - while (! (ret= func(thd, info))) + while (! (ret= func(info))) { int error= mysql_cond_timedwait(cond, lock, &abstime); if (error == ETIMEDOUT || error == ETIME) break; } /* Implicitly unlocks the mutex. */ - thd->exit_cond(old_proc_info); + thd->EXIT_COND(NULL); return ret; } @@ -2427,7 +3262,7 @@ static int request_dump(THD *thd, MYSQL* mysql, Master_info* mi, // TODO if big log files: Change next to int8store() int4store(buf, (ulong) mi->master_log_pos); int2store(buf + 4, binlog_flags); - int4store(buf + 6, server_id); + int4store(buf + 6, global_system_variables.server_id); len = (uint) strlen(logname); memcpy(buf + 10, logname,len); if (simple_command(mysql, COM_BINLOG_DUMP, buf, len + 10, 1)) @@ -2495,8 +3330,13 @@ static ulong read_event(MYSQL* mysql, Master_info *mi, bool* suppress_warnings) *suppress_warnings= TRUE; } else - sql_print_error("Error reading packet from server: %s ( server_errno=%d)", - mysql_error(mysql), mysql_errno(mysql)); + { + if (!mi->rli.abort_slave) + { + sql_print_error("Error reading packet from server: %s (server_errno=%d)", + mysql_error(mysql), mysql_errno(mysql)); + } + } DBUG_RETURN(packet_error); } @@ -2517,16 +3357,15 @@ static ulong read_event(MYSQL* mysql, Master_info *mi, bool* suppress_warnings) /* Check if the current error is of temporary nature of not. Some errors are temporary in nature, such as - ER_LOCK_DEADLOCK and ER_LOCK_WAIT_TIMEOUT. Ndb also signals - that the error is temporary by pushing a warning with the error code - ER_GET_TEMPORARY_ERRMSG, if the originating error is temporary. + ER_LOCK_DEADLOCK and ER_LOCK_WAIT_TIMEOUT. */ -static int has_temporary_error(THD *thd) +int +has_temporary_error(THD *thd) { DBUG_ENTER("has_temporary_error"); DBUG_EXECUTE_IF("all_errors_are_temporary_errors", - if (thd->stmt_da->is_error()) + if (thd->get_stmt_da()->is_error()) { thd->clear_error(); my_error(ER_LOCK_DEADLOCK, MYF(0)); @@ -2545,72 +3384,34 @@ static int has_temporary_error(THD *thd) currently, InnoDB deadlock detected by InnoDB or lock wait timeout (innodb_lock_wait_timeout exceeded */ - if (thd->stmt_da->sql_errno() == ER_LOCK_DEADLOCK || - thd->stmt_da->sql_errno() == ER_LOCK_WAIT_TIMEOUT) + if (thd->get_stmt_da()->sql_errno() == ER_LOCK_DEADLOCK || + thd->get_stmt_da()->sql_errno() == ER_LOCK_WAIT_TIMEOUT) DBUG_RETURN(1); -#ifdef HAVE_NDB_BINLOG - /* - currently temporary error set in ndbcluster - */ - List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); - MYSQL_ERROR *err; - while ((err= it++)) - { - DBUG_PRINT("info", ("has condition %d %s", err->get_sql_errno(), - err->get_message_text())); - switch (err->get_sql_errno()) - { - case ER_GET_TEMPORARY_ERRMSG: - DBUG_RETURN(1); - default: - break; - } - } -#endif DBUG_RETURN(0); } -/** - Applies the given event and advances the relay log position. - - In essence, this function does: - - @code - ev->apply_event(rli); - ev->update_pos(rli); - @endcode - - But it also does some maintainance, such as skipping events if - needed and reporting errors. - - If the @c skip flag is set, then it is tested whether the event - should be skipped, by looking at the slave_skip_counter and the - server id. The skip flag should be set when calling this from a - replication thread but not set when executing an explicit BINLOG - statement. - - @retval 0 OK. - - @retval 1 Error calling ev->apply_event(). +/* + First half of apply_event_and_update_pos(), see below. + Setup some THD variables for applying the event. - @retval 2 No error calling ev->apply_event(), but error calling - ev->update_pos(). + Split out so that it can run with rli->data_lock held in non-parallel + replication, but without the mutex held in the parallel case. */ -int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli) +static int +apply_event_and_update_pos_setup(Log_event* ev, THD* thd, rpl_group_info *rgi) { - int exec_res= 0; - - DBUG_ENTER("apply_event_and_update_pos"); + DBUG_ENTER("apply_event_and_update_pos_setup"); DBUG_PRINT("exec_event",("%s(type_code: %d; server_id: %d)", ev->get_type_str(), ev->get_type_code(), ev->server_id)); - DBUG_PRINT("info", ("thd->options: %s%s; rli->last_event_start_time: %lu", + DBUG_PRINT("info", ("thd->options: '%s%s%s' rgi->last_event_start_time: %lu", FLAGSTR(thd->variables.option_bits, OPTION_NOT_AUTOCOMMIT), FLAGSTR(thd->variables.option_bits, OPTION_BEGIN), - (ulong) rli->last_event_start_time)); + FLAGSTR(thd->variables.option_bits, OPTION_GTID_BEGIN), + (ulong) rgi->last_event_start_time)); /* Execute the event to change the database and update the binary @@ -2636,7 +3437,8 @@ int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli) has a Rotate etc). */ - thd->server_id = ev->server_id; // use the original server id for logging + /* Use the original server id for logging. */ + thd->variables.server_id = ev->server_id; thd->set_time(); // time the query thd->lex->current_select= 0; if (!ev->when) @@ -2650,12 +3452,42 @@ int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli) (ev->flags & LOG_EVENT_SKIP_REPLICATION_F ? OPTION_SKIP_REPLICATION : 0); ev->thd = thd; // because up to this point, ev->thd == 0 - int reason= ev->shall_skip(rli); - if (reason == Log_event::EVENT_SKIP_COUNT) - sql_slave_skip_counter= --rli->slave_skip_counter; - mysql_mutex_unlock(&rli->data_lock); + DBUG_RETURN(ev->shall_skip(rgi)); +} + + +/* + Second half of apply_event_and_update_pos(), see below. + + Do the actual event apply (or skip), and position update. + */ +static int +apply_event_and_update_pos_apply(Log_event* ev, THD* thd, rpl_group_info *rgi, + int reason) +{ + int exec_res= 0; + Relay_log_info* rli= rgi->rli; + + DBUG_ENTER("apply_event_and_update_pos_apply"); + DBUG_EXECUTE_IF("inject_slave_sql_before_apply_event", + { + DBUG_ASSERT(!debug_sync_set_action + (thd, STRING_WITH_LEN("now WAIT_FOR continue"))); + DBUG_SET_INITIAL("-d,inject_slave_sql_before_apply_event"); + };); if (reason == Log_event::EVENT_SKIP_NOT) - exec_res= ev->apply_event(rli); + exec_res= ev->apply_event(rgi); + +#ifdef WITH_WSREP + if (exec_res && thd->wsrep_conflict_state != NO_CONFLICT) + { + WSREP_DEBUG("SQL apply failed, res %d conflict state: %d", + exec_res, thd->wsrep_conflict_state); + rli->abort_slave= 1; + rli->report(ERROR_LEVEL, ER_UNKNOWN_COM_ERROR, rgi->gtid_info(), + "Node has dropped from cluster"); + } +#endif #ifndef DBUG_OFF /* @@ -2671,9 +3503,10 @@ int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli) // EVENT_SKIP_COUNT "skipped because event skip counter was non-zero" }; - DBUG_PRINT("info", ("OPTION_BEGIN: %d; IN_STMT: %d", - test(thd->variables.option_bits & OPTION_BEGIN), - rli->get_flag(Relay_log_info::IN_STMT))); + DBUG_PRINT("info", ("OPTION_BEGIN: %d IN_STMT: %d IN_TRANSACTION: %d", + MY_TEST(thd->variables.option_bits & OPTION_BEGIN), + rli->get_flag(Relay_log_info::IN_STMT), + rli->get_flag(Relay_log_info::IN_TRANSACTION))); DBUG_PRINT("skip_event", ("%s event was %s", ev->get_type_str(), explain[reason])); #endif @@ -2681,20 +3514,15 @@ int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli) DBUG_PRINT("info", ("apply_event error = %d", exec_res)); if (exec_res == 0) { - int error= ev->update_pos(rli); + int error= ev->update_pos(rgi); #ifdef HAVE_valgrind if (!rli->is_fake) #endif { -#ifndef DBUG_OFF - char buf[22]; -#endif DBUG_PRINT("info", ("update_pos error = %d", error)); - DBUG_PRINT("info", ("group %s %s", - llstr(rli->group_relay_log_pos, buf), + DBUG_PRINT("info", ("group %llu %s", rli->group_relay_log_pos, rli->group_relay_log_name)); - DBUG_PRINT("info", ("event %s %s", - llstr(rli->event_relay_log_pos, buf), + DBUG_PRINT("info", ("event %llu %s", rli->event_relay_log_pos, rli->event_relay_log_name)); } /* @@ -2706,23 +3534,171 @@ int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli) */ if (error) { - char buf[22]; - rli->report(ERROR_LEVEL, ER_UNKNOWN_ERROR, + rli->report(ERROR_LEVEL, ER_UNKNOWN_ERROR, rgi->gtid_info(), "It was not possible to update the positions" " of the relay log information: the slave may" " be in an inconsistent state." - " Stopped in %s position %s", - rli->group_relay_log_name, - llstr(rli->group_relay_log_pos, buf)); + " Stopped in %s position %llu", + rli->group_relay_log_name, rli->group_relay_log_pos); DBUG_RETURN(2); } } + else + { + /* + Make sure we do not erroneously update gtid_slave_pos with a lingering + GTID from this failed event group (MDEV-4906). + */ + rgi->gtid_pending= false; + } DBUG_RETURN(exec_res ? 1 : 0); } /** + Applies the given event and advances the relay log position. + + In essence, this function does: + + @code + ev->apply_event(rli); + ev->update_pos(rli); + @endcode + + But it also does some maintainance, such as skipping events if + needed and reporting errors. + + If the @c skip flag is set, then it is tested whether the event + should be skipped, by looking at the slave_skip_counter and the + server id. The skip flag should be set when calling this from a + replication thread but not set when executing an explicit BINLOG + statement. + + @retval 0 OK. + + @retval 1 Error calling ev->apply_event(). + + @retval 2 No error calling ev->apply_event(), but error calling + ev->update_pos(). + + This function is only used in non-parallel replication, where it is called + with rli->data_lock held; this lock is released during this function. +*/ +int +apply_event_and_update_pos(Log_event* ev, THD* thd, rpl_group_info *rgi) +{ + Relay_log_info* rli= rgi->rli; + mysql_mutex_assert_owner(&rli->data_lock); + int reason= apply_event_and_update_pos_setup(ev, thd, rgi); + if (reason == Log_event::EVENT_SKIP_COUNT) + { + DBUG_ASSERT(rli->slave_skip_counter > 0); + rli->slave_skip_counter--; + } + mysql_mutex_unlock(&rli->data_lock); + return apply_event_and_update_pos_apply(ev, thd, rgi, reason); +} + + +/* + The version of above apply_event_and_update_pos() used in parallel + replication. Unlike the non-parallel case, this function is called without + rli->data_lock held. +*/ +int +apply_event_and_update_pos_for_parallel(Log_event* ev, THD* thd, + rpl_group_info *rgi) +{ +#ifndef DBUG_OFF + Relay_log_info* rli= rgi->rli; +#endif + mysql_mutex_assert_not_owner(&rli->data_lock); + int reason= apply_event_and_update_pos_setup(ev, thd, rgi); + /* + In parallel replication, sql_slave_skip_counter is handled in the SQL + driver thread, so 23 should never see EVENT_SKIP_COUNT here. + */ + DBUG_ASSERT(reason != Log_event::EVENT_SKIP_COUNT); + return apply_event_and_update_pos_apply(ev, thd, rgi, reason); +} + + +/** + Keep the relay log transaction state up to date. + + The state reflects how things are after the given event, that has just been + read from the relay log, is executed. + + This is only needed to ensure we: + - Don't abort the sql driver thread in the middle of an event group. + - Don't rotate the io thread in the middle of a statement or transaction. + The mechanism is that the io thread, when it needs to rotate the relay + log, will wait until the sql driver has read all the cached events + and then continue reading events one by one from the master until + the sql threads signals that log doesn't have an active group anymore. + + There are two possible cases. We keep them as 2 separate flags mainly + to make debugging easier. + + - IN_STMT is set when we have read an event that should be used + together with the next event. This is for example setting a + variable that is used when executing the next statement. + - IN_TRANSACTION is set when we are inside a BEGIN...COMMIT group + + To test the state one should use the is_in_group() function. +*/ + +inline void update_state_of_relay_log(Relay_log_info *rli, Log_event *ev) +{ + Log_event_type typ= ev->get_type_code(); + + /* check if we are in a multi part event */ + if (ev->is_part_of_group()) + rli->set_flag(Relay_log_info::IN_STMT); + else if (Log_event::is_group_event(typ)) + { + /* + If it was not a is_part_of_group() and not a group event (like + rotate) then we can reset the IN_STMT flag. We have the above + if only to allow us to have a rotate element anywhere. + */ + rli->clear_flag(Relay_log_info::IN_STMT); + } + + /* Check for an event that starts or stops a transaction */ + if (typ == QUERY_EVENT) + { + Query_log_event *qev= (Query_log_event*) ev; + /* + Trivial optimization to avoid the following somewhat expensive + checks. + */ + if (qev->q_len <= sizeof("ROLLBACK")) + { + if (qev->is_begin()) + rli->set_flag(Relay_log_info::IN_TRANSACTION); + if (qev->is_commit() || qev->is_rollback()) + rli->clear_flag(Relay_log_info::IN_TRANSACTION); + } + } + if (typ == XID_EVENT) + rli->clear_flag(Relay_log_info::IN_TRANSACTION); + if (typ == GTID_EVENT && + !(((Gtid_log_event*) ev)->flags2 & Gtid_log_event::FL_STANDALONE)) + { + /* This GTID_EVENT will generate a BEGIN event */ + rli->set_flag(Relay_log_info::IN_TRANSACTION); + } + + DBUG_PRINT("info", ("event: %u IN_STMT: %d IN_TRANSACTION: %d", + (uint) typ, + rli->get_flag(Relay_log_info::IN_STMT), + rli->get_flag(Relay_log_info::IN_TRANSACTION))); +} + + +/** Top-level function for executing the next event from the relay log. This function reads the event from the relay log, executes it, and @@ -2750,22 +3726,23 @@ int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli) @retval 1 The event was not applied. */ -static int exec_relay_log_event(THD* thd, Relay_log_info* rli) + +static int exec_relay_log_event(THD* thd, Relay_log_info* rli, + rpl_group_info *serial_rgi) { + ulonglong event_size; DBUG_ENTER("exec_relay_log_event"); /* - We acquire this mutex since we need it for all operations except - event execution. But we will release it in places where we will - wait for something for example inside of next_event(). - */ + We acquire this mutex since we need it for all operations except + event execution. But we will release it in places where we will + wait for something for example inside of next_event(). + */ mysql_mutex_lock(&rli->data_lock); - Log_event * ev = next_event(rli); - - DBUG_ASSERT(rli->sql_thd==thd); + Log_event *ev= next_event(serial_rgi, &event_size); - if (sql_slave_killed(thd,rli)) + if (sql_slave_killed(serial_rgi)) { mysql_mutex_unlock(&rli->data_lock); delete ev; @@ -2774,22 +3751,48 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) if (ev) { int exec_res; + Log_event_type typ= ev->get_type_code(); + + /* + Even if we don't execute this event, we keep the master timestamp, + so that seconds behind master shows correct delta (there are events + that are not replayed, so we keep falling behind). + + If it is an artificial event, or a relay log event (IO thread generated + event) or ev->when is set to 0, we don't update the + last_master_timestamp. + + In parallel replication, we might queue a large number of events, and + the user might be surprised to see a claim that the slave is up to date + long before those queued events are actually executed. + */ + if (!rli->mi->using_parallel() && + !(ev->is_artificial_event() || ev->is_relay_log_event() || (ev->when == 0))) + { + rli->last_master_timestamp= ev->when + (time_t) ev->exec_time; + DBUG_ASSERT(rli->last_master_timestamp >= 0); + } /* This tests if the position of the beginning of the current event hits the UNTIL barrier. */ - if (rli->until_condition != Relay_log_info::UNTIL_NONE && - rli->is_until_satisfied(thd, ev)) + if ((rli->until_condition == Relay_log_info::UNTIL_MASTER_POS || + rli->until_condition == Relay_log_info::UNTIL_RELAY_POS) && + (ev->server_id != global_system_variables.server_id || + rli->replicate_same_server_id) && + rli->is_until_satisfied((rli->get_flag(Relay_log_info::IN_TRANSACTION) || !ev->log_pos) + ? rli->group_master_log_pos + : ev->log_pos - ev->data_written)) { - char buf[22]; sql_print_information("Slave SQL thread stopped because it reached its" - " UNTIL position %s", llstr(rli->until_pos(), buf)); + " UNTIL position %llu", rli->until_pos()); /* - Setting abort_slave flag because we do not want additional message about - error in query execution to be printed. + Setting abort_slave flag because we do not want additional + message about error in query execution to be printed. */ rli->abort_slave= 1; + rli->stop_for_until= true; mysql_mutex_unlock(&rli->data_lock); delete ev; DBUG_RETURN(1); @@ -2802,56 +3805,90 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) read hanging if the realy log does not have any more events. */ DBUG_EXECUTE_IF("incomplete_group_in_relay_log", - if ((ev->get_type_code() == XID_EVENT) || - ((ev->get_type_code() == QUERY_EVENT) && + if ((typ == XID_EVENT) || + ((typ == QUERY_EVENT) && strcmp("COMMIT", ((Query_log_event *) ev)->query) == 0)) { DBUG_ASSERT(thd->transaction.all.modified_non_trans_table); rli->abort_slave= 1; mysql_mutex_unlock(&rli->data_lock); delete ev; - rli->inc_event_relay_log_pos(); + serial_rgi->inc_event_relay_log_pos(); DBUG_RETURN(0); };); } - exec_res= apply_event_and_update_pos(ev, thd, rli); + update_state_of_relay_log(rli, ev); - switch (ev->get_type_code()) { - case FORMAT_DESCRIPTION_EVENT: - /* - Format_description_log_event should not be deleted because it - will be used to read info about the relay log's format; - it will be deleted when the SQL thread does not need it, - i.e. when this thread terminates. - */ - break; - case ANNOTATE_ROWS_EVENT: - /* - Annotate_rows event should not be deleted because after it has - been applied, thd->query points to the string inside this event. - The thd->query will be used to generate new Annotate_rows event - during applying the subsequent Rows events. - */ - rli->set_annotate_event((Annotate_rows_log_event*) ev); - break; - case DELETE_ROWS_EVENT: - case UPDATE_ROWS_EVENT: - case WRITE_ROWS_EVENT: + if (rli->mi->using_parallel()) + { + int res= rli->parallel.do_event(serial_rgi, ev, event_size); + /* + In parallel replication, we need to update the relay log position + immediately so that it will be the correct position from which to + read the next event. + */ + if (res == 0) + rli->event_relay_log_pos= rli->future_event_relay_log_pos; + if (res >= 0) + DBUG_RETURN(res); + /* + Else we proceed to execute the event non-parallel. + This is the case for pre-10.0 events without GTID, and for handling + slave_skip_counter. + */ + } + + if (typ == GTID_EVENT) + { + Gtid_log_event *gev= static_cast<Gtid_log_event *>(ev); + + /* + For GTID, allocate a new sub_id for the given domain_id. + The sub_id must be allocated in increasing order of binlog order. + */ + if (event_group_new_gtid(serial_rgi, gev)) + { + sql_print_error("Error reading relay log event: %s", "slave SQL thread " + "aborted because of out-of-memory error"); + mysql_mutex_unlock(&rli->data_lock); + delete ev; + DBUG_RETURN(1); + } + + if (opt_gtid_ignore_duplicates && + rli->mi->using_gtid != Master_info::USE_GTID_NO) + { + int res= rpl_global_gtid_slave_state->check_duplicate_gtid + (&serial_rgi->current_gtid, serial_rgi); + if (res < 0) + { + sql_print_error("Error processing GTID event: %s", "slave SQL " + "thread aborted because of out-of-memory error"); + mysql_mutex_unlock(&rli->data_lock); + delete ev; + DBUG_RETURN(1); + } /* - After the last Rows event has been applied, the saved Annotate_rows - event (if any) is not needed anymore and can be deleted. + If we need to skip this event group (because the GTID was already + applied), then do it using the code for slave_skip_counter, which + is able to handle skipping until the end of the event group. */ - if (((Rows_log_event*)ev)->get_flags(Rows_log_event::STMT_END_F)) - rli->free_annotate_event(); - /* fall through */ - default: - DBUG_PRINT("info", ("Deleting the event after it has been executed")); - if (!rli->is_deferred_event(ev)) - delete ev; - break; + if (!res) + rli->slave_skip_counter= 1; + } } + serial_rgi->future_event_relay_log_pos= rli->future_event_relay_log_pos; + serial_rgi->event_relay_log_name= rli->event_relay_log_name; + serial_rgi->event_relay_log_pos= rli->event_relay_log_pos; + exec_res= apply_event_and_update_pos(ev, thd, serial_rgi); + +#ifdef WITH_WSREP + WSREP_DEBUG("apply_event_and_update_pos() result: %d", exec_res); +#endif /* WITH_WSREP */ + + delete_or_keep_event_post_apply(serial_rgi, typ, ev); /* update_log_pos failed: this should not happen, so we don't @@ -2860,13 +3897,19 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) if (exec_res == 2) DBUG_RETURN(1); +#ifdef WITH_WSREP + mysql_mutex_lock(&thd->LOCK_thd_data); + if (thd->wsrep_conflict_state == NO_CONFLICT) + { + mysql_mutex_unlock(&thd->LOCK_thd_data); +#endif /* WITH_WSREP */ if (slave_trans_retries) { - int temp_err; - LINT_INIT(temp_err); + int UNINIT_VAR(temp_err); if (exec_res && (temp_err= has_temporary_error(thd))) { const char *errmsg; + rli->clear_error(); /* We were in a transaction which has been rolled back because of a temporary error; @@ -2874,14 +3917,16 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) Note, if lock wait timeout (innodb_lock_wait_timeout exceeded) there is no rollback since 5.0.13 (ref: manual). We have to not only seek but also - a) init_master_info(), to seek back to hot relay log's start for later - (for when we will come back to this hot log after re-processing the - possibly existing old logs where BEGIN is: check_binlog_magic() will - then need the cache to be at position 0 (see comments at beginning of + + a) init_master_info(), to seek back to hot relay log's start + for later (for when we will come back to this hot log after + re-processing the possibly existing old logs where BEGIN is: + check_binlog_magic() will then need the cache to be at + position 0 (see comments at beginning of init_master_info()). b) init_relay_log_pos(), because the BEGIN may be an older relay log. */ - if (rli->trans_retries < slave_trans_retries) + if (serial_rgi->trans_retries < slave_trans_retries) { if (init_master_info(rli->mi, 0, 0, 0, SLAVE_SQL)) sql_print_error("Failed to initialize the master info structure"); @@ -2894,16 +3939,19 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) else { exec_res= 0; - rli->cleanup_context(thd, 1); + serial_rgi->cleanup_context(thd, 1); /* chance for concurrent connection to get more locks */ - slave_sleep(thd, min(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE), - sql_slave_killed, rli); + slave_sleep(thd, MY_MIN(serial_rgi->trans_retries, + MAX_SLAVE_RETRY_PAUSE), + sql_slave_killed, serial_rgi); + serial_rgi->trans_retries++; mysql_mutex_lock(&rli->data_lock); // because of SHOW STATUS - rli->trans_retries++; rli->retried_trans++; + statistic_increment(slave_retried_transactions, LOCK_status); mysql_mutex_unlock(&rli->data_lock); DBUG_PRINT("info", ("Slave retries transaction " - "rli->trans_retries: %lu", rli->trans_retries)); + "rgi->trans_retries: %lu", + serial_rgi->trans_retries)); } } else @@ -2922,16 +3970,23 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) event, the execution will proceed as usual; in the case of a non-transient error, the slave will stop with an error. */ - rli->trans_retries= 0; // restart from fresh - DBUG_PRINT("info", ("Resetting retry counter, rli->trans_retries: %lu", - rli->trans_retries)); + serial_rgi->trans_retries= 0; // restart from fresh + DBUG_PRINT("info", ("Resetting retry counter, rgi->trans_retries: %lu", + serial_rgi->trans_retries)); } } +#ifdef WITH_WSREP + } + else + mysql_mutex_unlock(&thd->LOCK_thd_data); +#endif /* WITH_WSREP */ + + thread_safe_increment64(&rli->executed_entries); DBUG_RETURN(exec_res); } mysql_mutex_unlock(&rli->data_lock); - rli->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_READ_FAILURE, - ER(ER_SLAVE_RELAY_LOG_READ_FAILURE), "\ + rli->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_READ_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_RELAY_LOG_READ_FAILURE), "\ Could not parse relay log event entry. The possible reasons are: the master's \ binary log is corrupted (you can check this by running 'mysqlbinlog' on the \ binary log), the slave's relay log is corrupted (you can check this by running \ @@ -2944,9 +3999,9 @@ on this slave.\ } -static bool check_io_slave_killed(THD *thd, Master_info *mi, const char *info) +static bool check_io_slave_killed(Master_info *mi, const char *info) { - if (io_slave_killed(thd, mi)) + if (io_slave_killed(mi)) { if (info && global_system_variables.log_warnings) sql_print_information("%s", info); @@ -2997,22 +4052,36 @@ static int try_to_reconnect(THD *thd, MYSQL *mysql, Master_info *mi, return 1; // Don't retry forever slave_sleep(thd, mi->connect_retry, io_slave_killed, mi); } - if (check_io_slave_killed(thd, mi, messages[SLAVE_RECON_MSG_KILLED_WAITING])) + if (check_io_slave_killed(mi, messages[SLAVE_RECON_MSG_KILLED_WAITING])) return 1; thd->proc_info = messages[SLAVE_RECON_MSG_AFTER]; if (!suppress_warnings) { - char buf[256], llbuff[22]; + char buf[256]; + StringBuffer<100> tmp; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + mi->gtid_current_pos.append_to_string(&tmp); + if (mi->events_queued_since_last_gtid == 0) + tmp.append(STRING_WITH_LEN("'")); + else + { + tmp.append(STRING_WITH_LEN("', GTID event skip ")); + tmp.append_ulonglong((ulonglong)mi->events_queued_since_last_gtid); + } + } my_snprintf(buf, sizeof(buf), messages[SLAVE_RECON_MSG_FAILED], - IO_RPL_LOG_NAME, llstr(mi->master_log_pos, llbuff)); + IO_RPL_LOG_NAME, mi->master_log_pos, + tmp.c_ptr_safe()); /* Raise a warining during registering on master/requesting dump. Log a message reading event. */ if (messages[SLAVE_RECON_MSG_COMMAND][0]) { - mi->report(WARNING_LEVEL, ER_SLAVE_MASTER_COM_FAILURE, - ER(ER_SLAVE_MASTER_COM_FAILURE), + mi->report(WARNING_LEVEL, ER_SLAVE_MASTER_COM_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_MASTER_COM_FAILURE), messages[SLAVE_RECON_MSG_COMMAND], buf); } else @@ -3020,7 +4089,7 @@ static int try_to_reconnect(THD *thd, MYSQL *mysql, Master_info *mi, sql_print_information("%s", buf); } } - if (safe_reconnect(thd, mysql, mi, 1) || io_slave_killed(thd, mi)) + if (safe_reconnect(thd, mysql, mi, 1) || io_slave_killed(mi)) { if (global_system_variables.log_warnings) sql_print_information("%s", messages[SLAVE_RECON_MSG_KILLED_AFTER]); @@ -3044,12 +4113,12 @@ pthread_handler_t handle_slave_io(void *arg) MYSQL *mysql; Master_info *mi = (Master_info*)arg; Relay_log_info *rli= &mi->rli; - char llbuff[22]; uint retry_count; bool suppress_warnings; int ret; + rpl_io_thread_info io_info; #ifndef DBUG_OFF - uint retry_count_reg= 0, retry_count_dump= 0, retry_count_event= 0; + mi->dbug_do_disconnect= false; #endif // needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff my_thread_init(); @@ -3075,50 +4144,76 @@ pthread_handler_t handle_slave_io(void *arg) pthread_detach_this_thread(); thd->thread_stack= (char*) &thd; // remember where our stack is mi->clear_error(); - if (init_slave_thread(thd, SLAVE_THD_IO)) + if (init_slave_thread(thd, mi, SLAVE_THD_IO)) { mysql_cond_broadcast(&mi->start_cond); sql_print_error("Failed during slave I/O thread initialization"); goto err_during_init; } + thd->system_thread_info.rpl_io_info= &io_info; mysql_mutex_lock(&LOCK_thread_count); threads.append(thd); mysql_mutex_unlock(&LOCK_thread_count); - mi->slave_running = 1; + mi->slave_running = MYSQL_SLAVE_RUN_NOT_CONNECT; mi->abort_slave = 0; mysql_mutex_unlock(&mi->run_lock); mysql_cond_broadcast(&mi->start_cond); - DBUG_PRINT("master_info",("log_file_name: '%s' position: %s", - mi->master_log_name, - llstr(mi->master_log_pos,llbuff))); + DBUG_PRINT("master_info",("log_file_name: '%s' position: %llu", + mi->master_log_name, mi->master_log_pos)); /* This must be called before run any binlog_relay_io hooks */ my_pthread_setspecific_ptr(RPL_MASTER_INFO, mi); + /* Load the set of seen GTIDs, if we did not already. */ + if (rpl_load_gtid_slave_state(thd)) + { + mi->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), NULL, + "Unable to load replication GTID slave state from mysql.%s: %s", + rpl_gtid_slave_state_table_name.str, + thd->get_stmt_da()->message()); + /* + If we are using old-style replication, we can continue, even though we + then will not be able to record the GTIDs we receive. But if using GTID, + we must give up. + */ + if (mi->using_gtid != Master_info::USE_GTID_NO || opt_gtid_strict_mode) + goto err; + } + + if (RUN_HOOK(binlog_relay_io, thread_start, (thd, mi))) { - mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), "Failed to run 'thread_start' hook"); + mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, + ER_THD(thd, ER_SLAVE_FATAL_ERROR), + "Failed to run 'thread_start' hook"); goto err; } if (!(mi->mysql = mysql = mysql_init(NULL))) { - mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), "error in mysql_init()"); + mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, + ER_THD(thd, ER_SLAVE_FATAL_ERROR), "error in mysql_init()"); goto err; } - thd_proc_info(thd, "Connecting to master"); + THD_STAGE_INFO(thd, stage_connecting_to_master); // we can get killed during safe_connect if (!safe_connect(thd, mysql, mi)) { - sql_print_information("Slave I/O thread: connected to master '%s@%s:%d'," - "replication started in log '%s' at position %s", - mi->user, mi->host, mi->port, - IO_RPL_LOG_NAME, - llstr(mi->master_log_pos,llbuff)); + if (mi->using_gtid == Master_info::USE_GTID_NO) + sql_print_information("Slave I/O thread: connected to master '%s@%s:%d'," + "replication started in log '%s' at position %llu", + mi->user, mi->host, mi->port, + IO_RPL_LOG_NAME, mi->master_log_pos); + else + { + StringBuffer<100> tmp; + mi->gtid_current_pos.to_string(&tmp); + sql_print_information("Slave I/O thread: connected to master '%s@%s:%d'," + "replication starts at GTID position '%s'", + mi->user, mi->host, mi->port, tmp.c_ptr_safe()); + } } else { @@ -3128,6 +4223,25 @@ pthread_handler_t handle_slave_io(void *arg) connected: + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + /* + When the IO thread (re)connects to the master using GTID, it will + connect at the start of an event group. But the IO thread may have + previously logged part of the following event group to the relay + log. + + When the IO and SQL thread are started together, we erase any previous + relay logs, but this is not possible/desirable while the SQL thread is + running. To avoid duplicating partial event groups in the relay logs in + this case, we remember the count of events in any partially logged event + group before the reconnect, and then here at connect we set up a counter + to skip the already-logged part of the group. + */ + mi->gtid_reconnect_event_skip_count= mi->events_queued_since_last_gtid; + mi->gtid_event_seen= false; + } + #ifdef ENABLED_DEBUG_SYNC DBUG_EXECUTE_IF("dbug.before_get_running_status_yes", { @@ -3143,7 +4257,7 @@ connected: // TODO: the assignment below should be under mutex (5.0) mi->slave_running= MYSQL_SLAVE_RUN_CONNECT; thd->slave_net = &mysql->net; - thd_proc_info(thd, "Checking master version"); + THD_STAGE_INFO(thd, stage_checking_master_version); ret= get_master_version_and_clock(mysql, mi); if (ret == 1) /* Fatal error */ @@ -3151,11 +4265,14 @@ connected: if (ret == 2) { - if (check_io_slave_killed(mi->io_thd, mi, "Slave I/O thread killed" + if (check_io_slave_killed(mi, "Slave I/O thread killed " "while calling get_master_version_and_clock(...)")) goto err; suppress_warnings= FALSE; - /* Try to reconnect because the error was caused by a transient network problem */ + /* + Try to reconnect because the error was caused by a transient network + problem + */ if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings, reconnect_messages[SLAVE_RECON_ACT_REG])) goto err; @@ -3167,10 +4284,10 @@ connected: /* Register ourselves with the master. */ - thd_proc_info(thd, "Registering slave on master"); + THD_STAGE_INFO(thd, stage_registering_slave_on_master); if (register_slave_on_master(mysql, mi, &suppress_warnings)) { - if (!check_io_slave_killed(thd, mi, "Slave I/O thread killed " + if (!check_io_slave_killed(mi, "Slave I/O thread killed " "while registering slave on master")) { sql_print_error("Slave I/O thread couldn't register on master"); @@ -3182,46 +4299,27 @@ connected: goto err; goto connected; } - DBUG_EXECUTE_IF("FORCE_SLAVE_TO_RECONNECT_REG", - if (!retry_count_reg) - { - retry_count_reg++; - sql_print_information("Forcing to reconnect slave I/O thread"); - if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings, - reconnect_messages[SLAVE_RECON_ACT_REG])) - goto err; - goto connected; - }); } DBUG_PRINT("info",("Starting reading binary log from master")); - while (!io_slave_killed(thd,mi)) + while (!io_slave_killed(mi)) { - thd_proc_info(thd, "Requesting binlog dump"); + THD_STAGE_INFO(thd, stage_requesting_binlog_dump); if (request_dump(thd, mysql, mi, &suppress_warnings)) { sql_print_error("Failed on request_dump()"); - if (check_io_slave_killed(thd, mi, "Slave I/O thread killed while \ -requesting master dump") || - try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings, - reconnect_messages[SLAVE_RECON_ACT_DUMP])) + if (check_io_slave_killed(mi, NullS) || + try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings, + reconnect_messages[SLAVE_RECON_ACT_DUMP])) goto err; goto connected; } - DBUG_EXECUTE_IF("FORCE_SLAVE_TO_RECONNECT_DUMP", - if (!retry_count_dump) - { - retry_count_dump++; - sql_print_information("Forcing to reconnect slave I/O thread"); - if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings, - reconnect_messages[SLAVE_RECON_ACT_DUMP])) - goto err; - goto connected; - }); + const char *event_buf; + mi->slave_running= MYSQL_SLAVE_RUN_READING; DBUG_ASSERT(mi->last_error().number == 0); - while (!io_slave_killed(thd,mi)) + while (!io_slave_killed(mi)) { ulong event_len; /* @@ -3230,21 +4328,10 @@ requesting master dump") || important thing is to not confuse users by saying "reading" whereas we're in fact receiving nothing. */ - thd_proc_info(thd, "Waiting for master to send event"); + THD_STAGE_INFO(thd, stage_waiting_for_master_to_send_event); event_len= read_event(mysql, mi, &suppress_warnings); - if (check_io_slave_killed(thd, mi, "Slave I/O thread killed while \ -reading event")) + if (check_io_slave_killed(mi, NullS)) goto err; - DBUG_EXECUTE_IF("FORCE_SLAVE_TO_RECONNECT_EVENT", - if (!retry_count_event) - { - retry_count_event++; - sql_print_information("Forcing to reconnect slave I/O thread"); - if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings, - reconnect_messages[SLAVE_RECON_ACT_EVENT])) - goto err; - goto connected; - }); if (event_len == packet_error) { @@ -3256,19 +4343,19 @@ Log entry on master is longer than slave_max_allowed_packet (%lu) on \ slave. If the entry is correct, restart the server with a higher value of \ slave_max_allowed_packet", slave_max_allowed_packet); - mi->report(ERROR_LEVEL, ER_NET_PACKET_TOO_LARGE, + mi->report(ERROR_LEVEL, ER_NET_PACKET_TOO_LARGE, NULL, "%s", "Got a packet bigger than 'slave_max_allowed_packet' bytes"); goto err; case ER_MASTER_FATAL_ERROR_READING_BINLOG: - mi->report(ERROR_LEVEL, ER_MASTER_FATAL_ERROR_READING_BINLOG, - ER(ER_MASTER_FATAL_ERROR_READING_BINLOG), + mi->report(ERROR_LEVEL, ER_MASTER_FATAL_ERROR_READING_BINLOG, NULL, + ER_THD(thd, ER_MASTER_FATAL_ERROR_READING_BINLOG), mysql_error_number, mysql_error(mysql)); goto err; case ER_OUT_OF_RESOURCES: sql_print_error("\ Stopping slave I/O thread due to out-of-memory error from master"); - mi->report(ERROR_LEVEL, ER_OUT_OF_RESOURCES, - "%s", ER(ER_OUT_OF_RESOURCES)); + mi->report(ERROR_LEVEL, ER_OUT_OF_RESOURCES, NULL, + "%s", ER_THD(thd, ER_OUT_OF_RESOURCES)); goto err; } if (try_to_reconnect(thd, mysql, mi, &retry_count, suppress_warnings, @@ -3278,14 +4365,14 @@ Stopping slave I/O thread due to out-of-memory error from master"); } // if (event_len == packet_error) retry_count=0; // ok event, reset retry counter - thd_proc_info(thd, "Queueing master event to the relay log"); + THD_STAGE_INFO(thd, stage_queueing_master_event_to_the_relay_log); event_buf= (const char*)mysql->net.read_pos + 1; if (RUN_HOOK(binlog_relay_io, after_read_event, (thd, mi,(const char*)mysql->net.read_pos + 1, event_len, &event_buf, &event_len))) { - mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), + mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, + ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Failed to run 'after_read_event' hook"); goto err; } @@ -3295,8 +4382,8 @@ Stopping slave I/O thread due to out-of-memory error from master"); bool synced= 0; if (queue_event(mi, event_buf, event_len)) { - mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, - ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE), + mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_RELAY_LOG_WRITE_FAILURE), "could not queue event from master"); goto err; } @@ -3304,13 +4391,14 @@ Stopping slave I/O thread due to out-of-memory error from master"); if (RUN_HOOK(binlog_relay_io, after_queue_event, (thd, mi, event_buf, event_len, synced))) { - mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), + mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, + ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Failed to run 'after_queue_event' hook"); goto err; } - if (flush_master_info(mi, TRUE, TRUE)) + if (mi->using_gtid == Master_info::USE_GTID_NO && + flush_master_info(mi, TRUE, TRUE)) { sql_print_error("Failed to flush master info file"); goto err; @@ -3322,18 +4410,17 @@ Stopping slave I/O thread due to out-of-memory error from master"); - if mi->rli.ignore_log_space_limit is 1 but becomes 0 just after (so the clean value is 0), then we are reading only one more event as we should, and we'll block only at the next event. No big deal. - - if mi->rli.ignore_log_space_limit is 0 but becomes 1 just after (so - the clean value is 1), then we are going into wait_for_relay_log_space() - for no reason, but this function will do a clean read, notice the clean - value and exit immediately. + - if mi->rli.ignore_log_space_limit is 0 but becomes 1 just + after (so the clean value is 1), then we are going into + wait_for_relay_log_space() for no reason, but this function + will do a clean read, notice the clean value and exit + immediately. */ #ifndef DBUG_OFF { - char llbuf1[22], llbuf2[22]; - DBUG_PRINT("info", ("log_space_limit=%s log_space_total=%s \ -ignore_log_space_limit=%d", - llstr(rli->log_space_limit,llbuf1), - llstr(rli->log_space_total,llbuf2), + DBUG_PRINT("info", ("log_space_limit=%llu log_space_total=%llu " + "ignore_log_space_limit=%d", + rli->log_space_limit, rli->log_space_total, (int) rli->ignore_log_space_limit)); } #endif @@ -3353,8 +4440,18 @@ log space"); // error = 0; err: // print the current replication position - sql_print_information("Slave I/O thread exiting, read up to log '%s', position %s", - IO_RPL_LOG_NAME, llstr(mi->master_log_pos,llbuff)); + if (mi->using_gtid == Master_info::USE_GTID_NO) + sql_print_information("Slave I/O thread exiting, read up to log '%s', " + "position %llu", IO_RPL_LOG_NAME, mi->master_log_pos); + else + { + StringBuffer<100> tmp; + mi->gtid_current_pos.to_string(&tmp); + sql_print_information("Slave I/O thread exiting, read up to log '%s', " + "position %llu; GTID position %s", + IO_RPL_LOG_NAME, mi->master_log_pos, + tmp.c_ptr_safe()); + } RUN_HOOK(binlog_relay_io, thread_stop, (thd, mi)); thd->reset_query(); thd->reset_db(NULL, 0); @@ -3375,7 +4472,9 @@ err: mi->mysql=0; } write_ignored_events_info_to_relay_log(thd, mi); - thd_proc_info(thd, "Waiting for slave mutex on exit"); + if (mi->using_gtid != Master_info::USE_GTID_NO) + flush_master_info(mi, TRUE, TRUE); + THD_STAGE_INFO(thd, stage_waiting_for_slave_mutex_on_exit); thd->add_status_to_global(); mysql_mutex_lock(&mi->run_lock); @@ -3385,15 +4484,16 @@ err_during_init: mi->rli.relay_log.description_event_for_queue= 0; // TODO: make rpl_status part of Master_info change_rpl_status(RPL_ACTIVE_SLAVE,RPL_IDLE_SLAVE); - DBUG_ASSERT(thd->net.buff != 0); - net_end(&thd->net); // destructor will not free it, because net.vio is 0 + mysql_mutex_lock(&LOCK_thread_count); thd->unlink(); mysql_mutex_unlock(&LOCK_thread_count); - THD_CHECK_SENTRY(thd); delete thd; + thread_safe_decrement32(&service_thread_count); + signal_thd_deleted(); + mi->abort_slave= 0; - mi->slave_running= 0; + mi->slave_running= MYSQL_SLAVE_NOT_RUN; mi->io_thd= 0; /* Note: the order of the two following calls (first broadcast, then unlock) @@ -3416,17 +4516,34 @@ err_during_init: /* Check the temporary directory used by commands like LOAD DATA INFILE. + + As the directory never changes during a mysqld run, we only + test this once and cache the result. This also resolve a race condition + when this can be run by multiple threads at the same time. */ + +static bool check_temp_dir_run= 0; +static int check_temp_dir_result= 0; + static int check_temp_dir(char* tmp_file) { - int fd; + File fd; + int result= 1; // Assume failure MY_DIR *dirp; char tmp_dir[FN_REFLEN]; size_t tmp_dir_size; - DBUG_ENTER("check_temp_dir"); + mysql_mutex_lock(&LOCK_thread_count); + if (check_temp_dir_run) + { + if ((result= check_temp_dir_result)) + my_message(result, tmp_file, MYF(0)); + goto end; + } + check_temp_dir_run= 1; + /* Get the directory from the temporary file. */ @@ -3436,27 +4553,120 @@ int check_temp_dir(char* tmp_file) Check if the directory exists. */ if (!(dirp=my_dir(tmp_dir,MYF(MY_WME)))) - DBUG_RETURN(1); + goto end; my_dirend(dirp); /* - Check permissions to create a file. + Check permissions to create a file. We use O_TRUNC to ensure that + things works even if we happen to have and old file laying around. */ if ((fd= mysql_file_create(key_file_misc, tmp_file, CREATE_MODE, - O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW, + O_WRONLY | O_BINARY | O_TRUNC | O_NOFOLLOW, MYF(MY_WME))) < 0) - DBUG_RETURN(1); + goto end; + result= 0; // Directory name ok /* Clean up. */ mysql_file_close(fd, MYF(0)); mysql_file_delete(key_file_misc, tmp_file, MYF(0)); - DBUG_RETURN(0); +end: + mysql_mutex_unlock(&LOCK_thread_count); + DBUG_RETURN(result); +} + + +void +slave_output_error_info(rpl_group_info *rgi, THD *thd) +{ + /* + retrieve as much info as possible from the thd and, error + codes and warnings and print this to the error log as to + allow the user to locate the error + */ + Relay_log_info *rli= rgi->rli; + uint32 const last_errno= rli->last_error().number; + + if (thd->is_error()) + { + char const *const errmsg= thd->get_stmt_da()->message(); + + DBUG_PRINT("info", + ("thd->get_stmt_da()->sql_errno()=%d; rli->last_error.number=%d", + thd->get_stmt_da()->sql_errno(), last_errno)); + if (last_errno == 0) + { + /* + This function is reporting an error which was not reported + while executing exec_relay_log_event(). + */ + rli->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), + rgi->gtid_info(), "%s", errmsg); + } + else if (last_errno != thd->get_stmt_da()->sql_errno()) + { + /* + * An error was reported while executing exec_relay_log_event() + * however the error code differs from what is in the thread. + * This function prints out more information to help finding + * what caused the problem. + */ + sql_print_error("Slave (additional info): %s Error_code: %d", + errmsg, thd->get_stmt_da()->sql_errno()); + } + } + + /* Print any warnings issued */ + Diagnostics_area::Sql_condition_iterator it= + thd->get_stmt_da()->sql_conditions(); + const Sql_condition *err; + /* + Added controlled slave thread cancel for replication + of user-defined variables. + */ + bool udf_error = false; + while ((err= it++)) + { + if (err->get_sql_errno() == ER_CANT_OPEN_LIBRARY) + udf_error = true; + sql_print_warning("Slave: %s Error_code: %d", err->get_message_text(), err->get_sql_errno()); + } + if (udf_error) + { + StringBuffer<100> tmp; + if (rli->mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + rpl_append_gtid_state(&tmp, false); + tmp.append(STRING_WITH_LEN("'")); + } + sql_print_error("Error loading user-defined library, slave SQL " + "thread aborted. Install the missing library, and restart the " + "slave SQL thread with \"SLAVE START\". We stopped at log '%s' " + "position %llu%s", RPL_LOG_NAME, rli->group_master_log_pos, + tmp.c_ptr_safe()); + } + else + { + StringBuffer<100> tmp; + if (rli->mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + rpl_append_gtid_state(&tmp, false); + tmp.append(STRING_WITH_LEN("'")); + } + sql_print_error("Error running query, slave SQL thread aborted. " + "Fix the problem, and restart the slave SQL thread " + "with \"SLAVE START\". We stopped at log '%s' position " + "%llu%s", RPL_LOG_NAME, rli->group_master_log_pos, + tmp.c_ptr_safe()); + } } + /** Slave SQL thread entry point. @@ -3468,26 +4678,34 @@ int check_temp_dir(char* tmp_file) pthread_handler_t handle_slave_sql(void *arg) { THD *thd; /* needs to be first for thread_stack */ - char llbuff[22],llbuff1[22]; char saved_log_name[FN_REFLEN]; char saved_master_log_name[FN_REFLEN]; my_off_t UNINIT_VAR(saved_log_pos); my_off_t UNINIT_VAR(saved_master_log_pos); + String saved_skip_gtid_pos; my_off_t saved_skip= 0; - Relay_log_info* rli = &((Master_info*)arg)->rli; + Master_info *mi= ((Master_info*)arg); + Relay_log_info* rli = &mi->rli; + my_bool wsrep_node_dropped __attribute__((unused)) = FALSE; const char *errmsg; + rpl_group_info *serial_rgi; + rpl_sql_thread_info sql_info(mi->rpl_filter); // needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff my_thread_init(); DBUG_ENTER("handle_slave_sql"); - LINT_INIT(saved_master_log_pos); - LINT_INIT(saved_log_pos); +#ifdef WITH_WSREP + wsrep_restart_point: +#endif + serial_rgi= new rpl_group_info(rli); thd = new THD; // note that contructor of THD uses DBUG_ ! thd->thread_stack = (char*)&thd; // remember where our stack is + thd->system_thread_info.rpl_sql_info= &sql_info; DBUG_ASSERT(rli->inited); + DBUG_ASSERT(rli->mi == mi); mysql_mutex_lock(&rli->run_lock); DBUG_ASSERT(!rli->slave_running); errmsg= 0; @@ -3495,36 +4713,52 @@ pthread_handler_t handle_slave_sql(void *arg) rli->events_till_abort = abort_slave_event_count; #endif - rli->sql_thd= thd; + /* + THD for the sql driver thd. In parallel replication this is the thread + that reads things from the relay log and calls rpl_parallel::do_event() + to execute queries. + + In single thread replication this is the THD for the thread that is + executing SQL queries too. + */ + serial_rgi->thd= rli->sql_driver_thd= thd; /* Inform waiting threads that slave has started */ rli->slave_run_id++; - rli->slave_running = 1; + rli->slave_running= MYSQL_SLAVE_RUN_NOT_CONNECT; pthread_detach_this_thread(); - if (init_slave_thread(thd, SLAVE_THD_SQL)) + + if (opt_slave_parallel_threads > 0 && + rpl_parallel_activate_pool(&global_rpl_thread_pool)) + { + mysql_cond_broadcast(&rli->start_cond); + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, + "Failed during parallel slave pool activation"); + goto err_during_init; + } + + if (init_slave_thread(thd, mi, SLAVE_THD_SQL)) { /* TODO: this is currently broken - slave start and change master will be stuck if we fail here */ mysql_cond_broadcast(&rli->start_cond); - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, "Failed during slave thread initialization"); goto err_during_init; } thd->init_for_queries(); - thd->rli_slave= rli; - if ((rli->deferred_events_collecting= rpl_filter->is_on())) + thd->rgi_slave= serial_rgi; + if ((serial_rgi->deferred_events_collecting= mi->rpl_filter->is_on())) { - rli->deferred_events= new Deferred_log_events(rli); + serial_rgi->deferred_events= new Deferred_log_events(rli); } - thd->temporary_tables = rli->save_temporary_tables; // restore temp tables - set_thd_in_use_temporary_tables(rli); // (re)set sql_thd in use for saved temp tables /* binlog_annotate_row_events must be TRUE only after an Annotate_rows event - has been recieved and only till the last corresponding rbr event has been + has been received and only till the last corresponding rbr event has been applied. In all other cases it must be FALSE. */ thd->variables.binlog_annotate_row_events= 0; @@ -3540,6 +4774,7 @@ pthread_handler_t handle_slave_sql(void *arg) Seconds_Behind_Master grows. No big deal. */ rli->abort_slave = 0; + rli->stop_for_until= false; mysql_mutex_unlock(&rli->run_lock); mysql_cond_broadcast(&rli->start_cond); @@ -3554,29 +4789,49 @@ pthread_handler_t handle_slave_sql(void *arg) But the master timestamp is reset by RESET SLAVE & CHANGE MASTER. */ rli->clear_error(); + rli->parallel.reset(); //tell the I/O thread to take relay_log_space_limit into account from now on rli->ignore_log_space_limit= 0; - rli->trans_retries= 0; // start from "no error" - DBUG_PRINT("info", ("rli->trans_retries: %lu", rli->trans_retries)); + serial_rgi->gtid_sub_id= 0; + serial_rgi->gtid_pending= false; + if (mi->using_gtid != Master_info::USE_GTID_NO && mi->using_parallel() && + rli->restart_gtid_pos.count() > 0) + { + /* + With parallel replication in GTID mode, if we have a multi-domain GTID + position, we need to start some way back in the relay log and skip any + GTID that was already applied before. Since event groups can be split + across multiple relay logs, this earlier starting point may be in the + middle of an already applied event group, so we also need to skip any + remaining part of such group. + */ + rli->gtid_skip_flag = GTID_SKIP_TRANSACTION; + } + else + rli->gtid_skip_flag = GTID_SKIP_NOT; if (init_relay_log_pos(rli, rli->group_relay_log_name, rli->group_relay_log_pos, 1 /*need data lock*/, &errmsg, 1 /*look for a description_event*/)) { - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, "Error initializing relay log position: %s", errmsg); - goto err; + goto err_before_start; } + rli->reset_inuse_relaylog(); + if (rli->alloc_inuse_relaylog(rli->group_relay_log_name)) + goto err_before_start; + + strcpy(rli->future_event_master_log_name, rli->group_master_log_name); THD_CHECK_SENTRY(thd); #ifndef DBUG_OFF { - char llbuf1[22], llbuf2[22]; - DBUG_PRINT("info", ("my_b_tell(rli->cur_log)=%s rli->event_relay_log_pos=%s", - llstr(my_b_tell(rli->cur_log),llbuf1), - llstr(rli->event_relay_log_pos,llbuf2))); + DBUG_PRINT("info", ("my_b_tell(rli->cur_log)=%llu " + "rli->event_relay_log_pos=%llu", + my_b_tell(rli->cur_log), rli->event_relay_log_pos)); DBUG_ASSERT(rli->event_relay_log_pos >= BIN_LOG_HEADER_SIZE); /* Wonder if this is correct. I (Guilhem) wonder if my_b_tell() returns the @@ -3595,24 +4850,59 @@ pthread_handler_t handle_slave_sql(void *arg) #endif } #endif - DBUG_ASSERT(rli->sql_thd == thd); - DBUG_PRINT("master_info",("log_file_name: %s position: %s", +#ifdef WITH_WSREP + thd->wsrep_exec_mode= LOCAL_STATE; + /* synchronize with wsrep replication */ + if (WSREP_ON) + wsrep_ready_wait(); +#endif + DBUG_PRINT("master_info",("log_file_name: %s position: %llu", rli->group_master_log_name, - llstr(rli->group_master_log_pos,llbuff))); + rli->group_master_log_pos)); if (global_system_variables.log_warnings) - sql_print_information("Slave SQL thread initialized, starting replication in \ -log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, - llstr(rli->group_master_log_pos,llbuff),rli->group_relay_log_name, - llstr(rli->group_relay_log_pos,llbuff1)); + { + StringBuffer<100> tmp; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + rpl_append_gtid_state(&tmp, + mi->using_gtid==Master_info::USE_GTID_CURRENT_POS); + tmp.append(STRING_WITH_LEN("'")); + } + sql_print_information("Slave SQL thread initialized, starting replication " + "in log '%s' at position %llu, relay log '%s' " + "position: %llu%s", RPL_LOG_NAME, + rli->group_master_log_pos, rli->group_relay_log_name, + rli->group_relay_log_pos, tmp.c_ptr_safe()); + } if (check_temp_dir(rli->slave_patternload_file)) { - rli->report(ERROR_LEVEL, thd->stmt_da->sql_errno(), + check_temp_dir_result= thd->get_stmt_da()->sql_errno(); + rli->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), NULL, "Unable to use slave's temporary directory %s - %s", - slave_load_tmpdir, thd->stmt_da->message()); + slave_load_tmpdir, thd->get_stmt_da()->message()); goto err; } + else + check_temp_dir_result= 0; + + /* Load the set of seen GTIDs, if we did not already. */ + if (rpl_load_gtid_slave_state(thd)) + { + rli->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), NULL, + "Unable to load replication GTID slave state from mysql.%s: %s", + rpl_gtid_slave_state_table_name.str, + thd->get_stmt_da()->message()); + /* + If we are using old-style replication, we can continue, even though we + then will not be able to record the GTIDs we receive. But if using GTID, + we must give up. + */ + if (mi->using_gtid != Master_info::USE_GTID_NO || opt_gtid_strict_mode) + goto err; + } /* execute init_slave variable */ if (opt_init_slave.length) @@ -3620,7 +4910,7 @@ log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, execute_init_command(thd, &opt_init_slave, &LOCK_sys_init_slave); if (thd->is_slave_error) { - rli->report(ERROR_LEVEL, thd->stmt_da->sql_errno(), + rli->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), NULL, "Slave SQL thread aborted. Can't execute init_slave query"); goto err; } @@ -3637,14 +4927,20 @@ log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, strmake_buf(saved_master_log_name, rli->group_master_log_name); saved_log_pos= rli->group_relay_log_pos; saved_master_log_pos= rli->group_master_log_pos; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + saved_skip_gtid_pos.append(STRING_WITH_LEN(", GTID '")); + rpl_append_gtid_state(&saved_skip_gtid_pos, false); + saved_skip_gtid_pos.append(STRING_WITH_LEN("'; ")); + } saved_skip= rli->slave_skip_counter; } - if (rli->until_condition != Relay_log_info::UNTIL_NONE && - rli->is_until_satisfied(thd, NULL)) + if ((rli->until_condition == Relay_log_info::UNTIL_MASTER_POS || + rli->until_condition == Relay_log_info::UNTIL_RELAY_POS) && + rli->is_until_satisfied(rli->group_master_log_pos)) { - char buf[22]; sql_print_information("Slave SQL thread stopped because it reached its" - " UNTIL position %s", llstr(rli->until_pos(), buf)); + " UNTIL position %llu", rli->until_pos()); mysql_mutex_unlock(&rli->data_lock); goto err; } @@ -3652,103 +4948,77 @@ log '%s' at position %s, relay log '%s' position: %s", RPL_LOG_NAME, /* Read queries from the IO/THREAD until this thread is killed */ - while (!sql_slave_killed(thd,rli)) + while (!sql_slave_killed(serial_rgi)) { - thd_proc_info(thd, "Reading event from the relay log"); - DBUG_ASSERT(rli->sql_thd == thd); + THD_STAGE_INFO(thd, stage_reading_event_from_the_relay_log); THD_CHECK_SENTRY(thd); if (saved_skip && rli->slave_skip_counter == 0) { + StringBuffer<100> tmp; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN(", GTID '")); + rpl_append_gtid_state(&tmp, false); + tmp.append(STRING_WITH_LEN("'; ")); + } + sql_print_information("'SQL_SLAVE_SKIP_COUNTER=%ld' executed at " "relay_log_file='%s', relay_log_pos='%ld', master_log_name='%s', " - "master_log_pos='%ld' and new position at " + "master_log_pos='%ld'%s and new position at " "relay_log_file='%s', relay_log_pos='%ld', master_log_name='%s', " - "master_log_pos='%ld' ", + "master_log_pos='%ld'%s ", (ulong) saved_skip, saved_log_name, (ulong) saved_log_pos, saved_master_log_name, (ulong) saved_master_log_pos, + saved_skip_gtid_pos.c_ptr_safe(), rli->group_relay_log_name, (ulong) rli->group_relay_log_pos, - rli->group_master_log_name, (ulong) rli->group_master_log_pos); + rli->group_master_log_name, (ulong) rli->group_master_log_pos, + tmp.c_ptr_safe()); saved_skip= 0; + saved_skip_gtid_pos.free(); } - - if (exec_relay_log_event(thd,rli)) + + if (exec_relay_log_event(thd, rli, serial_rgi)) { +#ifdef WITH_WSREP + if (thd->wsrep_conflict_state != NO_CONFLICT) + { + wsrep_node_dropped= TRUE; + rli->abort_slave= TRUE; + } +#endif /* WITH_WSREP */ + DBUG_PRINT("info", ("exec_relay_log_event() failed")); // do not scare the user if SQL thread was simply killed or stopped - if (!sql_slave_killed(thd,rli)) + if (!sql_slave_killed(serial_rgi)) { - /* - retrieve as much info as possible from the thd and, error - codes and warnings and print this to the error log as to - allow the user to locate the error - */ - uint32 const last_errno= rli->last_error().number; - - if (thd->is_error()) - { - char const *const errmsg= thd->stmt_da->message(); - - DBUG_PRINT("info", - ("thd->stmt_da->sql_errno()=%d; rli->last_error.number=%d", - thd->stmt_da->sql_errno(), last_errno)); - if (last_errno == 0) - { - /* - This function is reporting an error which was not reported - while executing exec_relay_log_event(). - */ - rli->report(ERROR_LEVEL, thd->stmt_da->sql_errno(), "%s", errmsg); - } - else if (last_errno != thd->stmt_da->sql_errno()) - { - /* - * An error was reported while executing exec_relay_log_event() - * however the error code differs from what is in the thread. - * This function prints out more information to help finding - * what caused the problem. - */ - sql_print_error("Slave (additional info): %s Error_code: %d", - errmsg, thd->stmt_da->sql_errno()); - } - } - - /* Print any warnings issued */ - List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); - MYSQL_ERROR *err; - /* - Added controlled slave thread cancel for replication - of user-defined variables. - */ - bool udf_error = false; - while ((err= it++)) - { - if (err->get_sql_errno() == ER_CANT_OPEN_LIBRARY) - udf_error = true; - sql_print_warning("Slave: %s Error_code: %d", err->get_message_text(), err->get_sql_errno()); - } - if (udf_error) - sql_print_error("Error loading user-defined library, slave SQL " - "thread aborted. Install the missing library, and restart the " - "slave SQL thread with \"SLAVE START\". We stopped at log '%s' " - "position %s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, - llbuff)); - else - sql_print_error("\ -Error running query, slave SQL thread aborted. Fix the problem, and restart \ -the slave SQL thread with \"SLAVE START\". We stopped at log \ -'%s' position %s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, llbuff)); + slave_output_error_info(serial_rgi, thd); + if (WSREP_ON && rli->last_error().number == ER_UNKNOWN_COM_ERROR) + wsrep_node_dropped= TRUE; } goto err; } } + err: + if (mi->using_parallel()) + rli->parallel.wait_for_done(thd, rli); + /* Thread stopped. Print the current replication position to the log */ - sql_print_information("Slave SQL thread exiting, replication stopped in log " - "'%s' at position %s", - RPL_LOG_NAME, llstr(rli->group_master_log_pos,llbuff)); + { + StringBuffer<100> tmp; + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + tmp.append(STRING_WITH_LEN("; GTID position '")); + rpl_append_gtid_state(&tmp, false); + tmp.append(STRING_WITH_LEN("'")); + } + sql_print_information("Slave SQL thread exiting, replication stopped in " + "log '%s' at position %llu%s", RPL_LOG_NAME, + rli->group_master_log_pos, tmp.c_ptr_safe()); + } - err: + err_before_start: /* Some events set some playgrounds, which won't be cleared because thread @@ -3757,7 +5027,7 @@ the slave SQL thread with \"SLAVE START\". We stopped at log \ must "proactively" clear playgrounds: */ thd->clear_error(); - rli->cleanup_context(thd, 1); + serial_rgi->cleanup_context(thd, 1); /* Some extra safety, which should not been needed (normally, event deletion should already have done these assignments (each event which sets these @@ -3766,42 +5036,117 @@ the slave SQL thread with \"SLAVE START\". We stopped at log \ thd->catalog= 0; thd->reset_query(); thd->reset_db(NULL, 0); + if (rli->mi->using_gtid != Master_info::USE_GTID_NO) + { + ulong domain_count; + my_bool save_log_all_errors= thd->log_all_errors; + + /* + We don't need to check return value for flush_relay_log_info() + as any errors should be logged to stderr + */ + thd->log_all_errors= 1; + flush_relay_log_info(rli); + thd->log_all_errors= save_log_all_errors; + if (mi->using_parallel()) + { + /* + In parallel replication GTID mode, we may stop with different domains + at different positions in the relay log. + + To handle this when we restart the SQL thread, mark the current + per-domain position in the Relay_log_info. + */ + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + domain_count= rpl_global_gtid_slave_state->count(); + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + if (domain_count > 1) + { + inuse_relaylog *ir; + + /* + Load the starting GTID position, so that we can skip already applied + GTIDs when we restart the SQL thread. And set the start position in + the relay log back to a known safe place to start (prior to any not + yet applied transaction in any domain). + */ + rli->restart_gtid_pos.load(rpl_global_gtid_slave_state, NULL, 0); + if ((ir= rli->inuse_relaylog_list)) + { + rpl_gtid *gtid= ir->relay_log_state; + uint32 count= ir->relay_log_state_count; + while (count > 0) + { + process_gtid_for_restart_pos(rli, gtid); + ++gtid; + --count; + } + strmake_buf(rli->group_relay_log_name, ir->name); + rli->group_relay_log_pos= BIN_LOG_HEADER_SIZE; + rli->relay_log_state.load(ir->relay_log_state, ir->relay_log_state_count); + } + } + } + } + THD_STAGE_INFO(thd, stage_waiting_for_slave_mutex_on_exit); thd->add_status_to_global(); - thd_proc_info(thd, "Waiting for slave mutex on exit"); mysql_mutex_lock(&rli->run_lock); err_during_init: /* We need data_lock, at least to wake up any waiting master_pos_wait() */ mysql_mutex_lock(&rli->data_lock); - DBUG_ASSERT(rli->slave_running == 1); // tracking buffer overrun + DBUG_ASSERT(rli->slave_running == MYSQL_SLAVE_RUN_NOT_CONNECT); // tracking buffer overrun /* When master_pos_wait() wakes up it will check this and terminate */ - rli->slave_running= 0; + rli->slave_running= MYSQL_SLAVE_NOT_RUN; /* Forget the relay log's format */ delete rli->relay_log.description_event_for_exec; rli->relay_log.description_event_for_exec= 0; + rli->reset_inuse_relaylog(); /* Wake up master_pos_wait() */ mysql_mutex_unlock(&rli->data_lock); DBUG_PRINT("info",("Signaling possibly waiting master_pos_wait() functions")); mysql_cond_broadcast(&rli->data_cond); rli->ignore_log_space_limit= 0; /* don't need any lock */ /* we die so won't remember charset - re-update them on next thread start */ - rli->cached_charset_invalidate(); - rli->save_temporary_tables = thd->temporary_tables; + thd->system_thread_info.rpl_sql_info->cached_charset_invalidate(); /* TODO: see if we can do this conditionally in next_event() instead to avoid unneeded position re-init */ thd->temporary_tables = 0; // remove tempation from destructor to close them - DBUG_ASSERT(thd->net.buff != 0); - net_end(&thd->net); // destructor will not free it, because we are weird - DBUG_ASSERT(rli->sql_thd == thd); THD_CHECK_SENTRY(thd); - rli->sql_thd= 0; - set_thd_in_use_temporary_tables(rli); // (re)set sql_thd in use for saved temp tables + rli->sql_driver_thd= 0; mysql_mutex_lock(&LOCK_thread_count); - THD_CHECK_SENTRY(thd); - delete thd; + thd->rgi_fake= thd->rgi_slave= NULL; + delete serial_rgi; mysql_mutex_unlock(&LOCK_thread_count); + +#ifdef WITH_WSREP + /* + If slave stopped due to node going non primary, we set global flag to + trigger automatic restart of slave when node joins back to cluster. + */ + if (WSREP_ON && wsrep_node_dropped && wsrep_restart_slave) + { + if (wsrep_ready_get()) + { + WSREP_INFO("Slave error due to node temporarily non-primary" + "SQL slave will continue"); + wsrep_node_dropped= FALSE; + mysql_mutex_unlock(&rli->run_lock); + WSREP_DEBUG("wsrep_conflict_state now: %d", thd->wsrep_conflict_state); + WSREP_INFO("slave restart: %d", thd->wsrep_conflict_state); + thd->wsrep_conflict_state= NO_CONFLICT; + goto wsrep_restart_point; + } else { + WSREP_INFO("Slave error due to node going non-primary"); + WSREP_INFO("wsrep_restart_slave was set and therefore slave will be " + "automatically restarted when node joins back to cluster."); + wsrep_restart_slave_activated= TRUE; + } + } +#endif /* WITH_WSREP */ + /* Note: the order of the broadcast and unlock calls below (first broadcast, then unlock) is important. Otherwise a killer_thread can execute between the calls and @@ -3811,6 +5156,15 @@ err_during_init: DBUG_EXECUTE_IF("simulate_slave_delay_at_terminate_bug38694", sleep(5);); mysql_mutex_unlock(&rli->run_lock); // tell the world we are done + rpl_parallel_resize_pool_if_no_slaves(); + + mysql_mutex_lock(&LOCK_thread_count); + thd->unlink(); + mysql_mutex_unlock(&LOCK_thread_count); + delete thd; + thread_safe_decrement32(&service_thread_count); + signal_thd_deleted(); + DBUG_LEAVE; // Must match DBUG_ENTER() my_thread_end(); #ifdef HAVE_OPENSSL @@ -3837,14 +5191,14 @@ static int process_io_create_file(Master_info* mi, Create_file_log_event* cev) if (unlikely(!cev->is_valid())) DBUG_RETURN(1); - if (!rpl_filter->db_ok(cev->db)) + if (!mi->rpl_filter->db_ok(cev->db)) { skip_load_data_infile(net); DBUG_RETURN(0); } DBUG_ASSERT(cev->inited_from_old); thd->file_id = cev->file_id = mi->file_id++; - thd->server_id = cev->server_id; + thd->variables.server_id = cev->server_id; cev_not_written = 1; if (unlikely(net_request_file(net,cev->fname))) @@ -3886,8 +5240,8 @@ static int process_io_create_file(Master_info* mi, Create_file_log_event* cev) xev.log_pos = cev->log_pos; if (unlikely(mi->rli.relay_log.append(&xev))) { - mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, - ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE), + mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_RELAY_LOG_WRITE_FAILURE), "error writing Exec_load event to relay log"); goto err; } @@ -3900,8 +5254,8 @@ static int process_io_create_file(Master_info* mi, Create_file_log_event* cev) cev->block_len = num_bytes; if (unlikely(mi->rli.relay_log.append(cev))) { - mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, - ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE), + mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_RELAY_LOG_WRITE_FAILURE), "error writing Create_file event to relay log"); goto err; } @@ -3915,8 +5269,8 @@ static int process_io_create_file(Master_info* mi, Create_file_log_event* cev) aev.log_pos = cev->log_pos; if (unlikely(mi->rli.relay_log.append(&aev))) { - mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, - ER(ER_SLAVE_RELAY_LOG_WRITE_FAILURE), + mi->report(ERROR_LEVEL, ER_SLAVE_RELAY_LOG_WRITE_FAILURE, NULL, + ER_THD(thd, ER_SLAVE_RELAY_LOG_WRITE_FAILURE), "error writing Append_block event to relay log"); goto err; } @@ -4022,7 +5376,7 @@ static int queue_binlog_ver_1_event(Master_info *mi, const char *buf, { if (unlikely(!(tmp_buf=(char*)my_malloc(event_len+1,MYF(MY_WME))))) { - mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, ER(ER_SLAVE_FATAL_ERROR), "Memory allocation failed"); DBUG_RETURN(1); } @@ -4220,22 +5574,28 @@ static int queue_old_event(Master_info *mi, const char *buf, static int queue_event(Master_info* mi,const char* buf, ulong event_len) { int error= 0; - String error_msg; + StringBuffer<1024> error_msg; ulonglong inc_pos; ulonglong event_pos; Relay_log_info *rli= &mi->rli; mysql_mutex_t *log_lock= rli->relay_log.get_log_lock(); ulong s_id; bool unlock_data_lock= TRUE; + bool gtid_skip_enqueue= false; + bool got_gtid_event= false; + rpl_gtid event_gtid; +#ifndef DBUG_OFF + static uint dbug_rows_event_count= 0; +#endif /* FD_q must have been prepared for the first R_a event inside get_master_version_and_clock() Show-up of FD:s affects checksum_alg at once because that changes FD_queue. */ - uint8 checksum_alg= mi->checksum_alg_before_fd != BINLOG_CHECKSUM_ALG_UNDEF ? - mi->checksum_alg_before_fd : - mi->rli.relay_log.relay_log_checksum_alg; + enum enum_binlog_checksum_alg checksum_alg= + mi->checksum_alg_before_fd != BINLOG_CHECKSUM_ALG_UNDEF ? + mi->checksum_alg_before_fd : mi->rli.relay_log.relay_log_checksum_alg; char *save_buf= NULL; // needed for checksumming the fake Rotate event char rot_buf[LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + FN_REFLEN]; @@ -4289,12 +5649,30 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) goto err; } - LINT_INIT(inc_pos); - if (mi->rli.relay_log.description_event_for_queue->binlog_version<4 && (uchar)buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT /* a way to escape */) DBUG_RETURN(queue_old_event(mi,buf,event_len)); +#ifdef ENABLED_DEBUG_SYNC + /* + A (+d,dbug.rows_events_to_delay_relay_logging)-test is supposed to + create a few Write_log_events and after receiving the 1st of them + the IO thread signals to launch the SQL thread, and sets itself to + wait for a release signal. + */ + DBUG_EXECUTE_IF("dbug.rows_events_to_delay_relay_logging", + if ((buf[EVENT_TYPE_OFFSET] == WRITE_ROWS_EVENT_V1 || + buf[EVENT_TYPE_OFFSET] == WRITE_ROWS_EVENT) && + ++dbug_rows_event_count == 2) + { + const char act[]= + "now SIGNAL start_sql_thread " + "WAIT_FOR go_on_relay_logging"; + DBUG_ASSERT(debug_sync_service); + DBUG_ASSERT(!debug_sync_set_action(current_thd, + STRING_WITH_LEN(act))); + };); +#endif mysql_mutex_lock(&mi->data_lock); switch ((uchar)buf[EVENT_TYPE_OFFSET]) { @@ -4318,6 +5696,86 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) event_len - BINLOG_CHECKSUM_LEN : event_len, mi->rli.relay_log.description_event_for_queue); + if (unlikely(mi->gtid_reconnect_event_skip_count) && + unlikely(!mi->gtid_event_seen) && + rev.is_artificial_event() && + (mi->prev_master_id != mi->master_id || + strcmp(rev.new_log_ident, mi->master_log_name) != 0)) + { + /* + Artificial Rotate_log_event is the first event we receive at the start + of each master binlog file. It gives the name of the new binlog file. + + Normally, we already have this name from the real rotate event at the + end of the previous binlog file (unless we are making a new connection + using GTID). But if the master server restarted/crashed, there is no + rotate event at the end of the prior binlog file, so the name is new. + + We use this fact to handle a special case of master crashing. If the + master crashed while writing the binlog, it might end with a partial + event group lacking the COMMIT/XID event, which must be rolled + back. If the slave IO thread happens to get a disconnect in the middle + of exactly this event group, it will try to reconnect at the same GTID + and skip already fetched events. However, that GTID did not commit on + the master before the crash, so it does not really exist, and the + master will connect the slave at the next following GTID starting in + the next binlog. This could confuse the slave and make it mix the + start of one event group with the end of another. + + But we detect this case here, by noticing the change of binlog name + which detects the missing rotate event at the end of the previous + binlog file. In this case, we reset the counters to make us not skip + the next event group, and queue an artificial Format Description + event. The previously fetched incomplete event group will then be + rolled back when the Format Description event is executed by the SQL + thread. + + A similar case is if the reconnect somehow connects to a different + master server (like due to a network proxy or IP address takeover). + We detect this case by noticing a change of server_id and in this + case likewise rollback the partially received event group. + */ + Format_description_log_event fdle(4); + + if (mi->prev_master_id != mi->master_id) + sql_print_warning("The server_id of master server changed in the " + "middle of GTID %u-%u-%llu. Assuming a change of " + "master server, so rolling back the previously " + "received partial transaction. Expected: %lu, " + "received: %lu", mi->last_queued_gtid.domain_id, + mi->last_queued_gtid.server_id, + mi->last_queued_gtid.seq_no, + mi->prev_master_id, mi->master_id); + else if (strcmp(rev.new_log_ident, mi->master_log_name) != 0) + sql_print_warning("Unexpected change of master binlog file name in the " + "middle of GTID %u-%u-%llu, assuming that master has " + "crashed and rolling back the transaction. Expected: " + "'%s', received: '%s'", + mi->last_queued_gtid.domain_id, + mi->last_queued_gtid.server_id, + mi->last_queued_gtid.seq_no, + mi->master_log_name, rev.new_log_ident); + + mysql_mutex_lock(log_lock); + if (likely(!rli->relay_log.write_event(&fdle) && + !rli->relay_log.flush_and_sync(NULL))) + { + rli->relay_log.harvest_bytes_written(&rli->log_space_total); + } + else + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + mysql_mutex_unlock(log_lock); + goto err; + } + rli->relay_log.signal_update(); + mysql_mutex_unlock(log_lock); + + mi->gtid_reconnect_event_skip_count= 0; + mi->events_queued_since_last_gtid= 0; + } + mi->prev_master_id= mi->master_id; + if (unlikely(process_io_rotate(mi, &rev))) { error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; @@ -4339,7 +5797,7 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) if (uint4korr(&buf[0]) == 0 && checksum_alg == BINLOG_CHECKSUM_ALG_OFF && mi->rli.relay_log.relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_OFF) { - ha_checksum rot_crc= my_checksum(0L, NULL, 0); + ha_checksum rot_crc= 0; event_len += BINLOG_CHECKSUM_LEN; memcpy(rot_buf, buf, event_len - BINLOG_CHECKSUM_LEN); int4store(&rot_buf[EVENT_LEN_OFFSET], @@ -4405,6 +5863,7 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; goto err; } + tmp->copy_crypto_data(mi->rli.relay_log.description_event_for_queue); delete mi->rli.relay_log.description_event_for_queue; mi->rli.relay_log.description_event_for_queue= tmp; if (tmp->checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF) @@ -4414,6 +5873,19 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) mi->rli.relay_log.relay_log_checksum_alg= tmp->checksum_alg; /* + Do not queue any format description event that we receive after a + reconnect where we are skipping over a partial event group received + before the reconnect. + + (If we queued such an event, and it was the first format_description + event after master restart, the slave SQL thread would think that + the partial event group before it in the relay log was from a + previous master crash and should be rolled back). + */ + if (unlikely(mi->gtid_reconnect_event_skip_count && !mi->gtid_event_seen)) + gtid_skip_enqueue= true; + + /* Though this does some conversion to the slave's format, this will preserve the master's binlog format version, and number of event types. */ @@ -4433,7 +5905,6 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) /* HB (heartbeat) cannot come before RL (Relay) */ - char llbuf[22]; Heartbeat_log_event hb(buf, mi->rli.relay_log.relay_log_checksum_alg != BINLOG_CHECKSUM_ALG_OFF ? @@ -4446,8 +5917,7 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) error_msg.append(STRING_WITH_LEN("the event's data: log_file_name ")); error_msg.append(hb.get_log_ident(), (uint) strlen(hb.get_log_ident())); error_msg.append(STRING_WITH_LEN(" log_pos ")); - llstr(hb.log_pos, llbuf); - error_msg.append(llbuf, strlen(llbuf)); + error_msg.append_ulonglong(hb.log_pos); goto err; } mi->received_heartbeats++; @@ -4456,16 +5926,17 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) Heartbeat is sent only after an event corresponding to the corrdinates the heartbeat carries. - Slave can not have a difference in coordinates except in the only + Slave can not have a higher coordinate except in the only special case when mi->master_log_name, master_log_pos have never been updated by Rotate event i.e when slave does not have any history with the master (and thereafter mi->master_log_pos is NULL). + Slave can have lower coordinates, if some event from master was omitted. + TODO: handling `when' for SHOW SLAVE STATUS' snds behind */ - if ((memcmp(mi->master_log_name, hb.get_log_ident(), hb.get_ident_len()) - && mi->master_log_name != NULL) - || mi->master_log_pos != hb.log_pos) + if (memcmp(mi->master_log_name, hb.get_log_ident(), hb.get_ident_len()) + || mi->master_log_pos > hb.log_pos) { /* missed events of heartbeat from the past */ error= ER_SLAVE_HEARTBEAT_FAILURE; @@ -4473,15 +5944,197 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) error_msg.append(STRING_WITH_LEN("the event's data: log_file_name ")); error_msg.append(hb.get_log_ident(), (uint) strlen(hb.get_log_ident())); error_msg.append(STRING_WITH_LEN(" log_pos ")); - llstr(hb.log_pos, llbuf); - error_msg.append(llbuf, strlen(llbuf)); + error_msg.append_ulonglong(hb.log_pos); goto err; } + + /* + Heartbeat events doesn't count in the binlog size, so we don't have to + increment mi->master_log_pos + */ goto skip_relay_logging; } break; + case GTID_LIST_EVENT: + { + const char *errmsg; + Gtid_list_log_event *glev; + Log_event *tmp; + uint32 flags; + + if (!(tmp= Log_event::read_log_event(buf, event_len, &errmsg, + mi->rli.relay_log.description_event_for_queue, + opt_slave_sql_verify_checksum))) + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + goto err; + } + glev= static_cast<Gtid_list_log_event *>(tmp); + event_pos= glev->log_pos; + flags= glev->gl_flags; + delete glev; + + /* + We use fake Gtid_list events to update the old-style position (among + other things). + + Early code created fake Gtid_list events with zero log_pos, those should + not modify old-style position. + */ + if (event_pos == 0 || event_pos <= mi->master_log_pos) + inc_pos= 0; + else + inc_pos= event_pos - mi->master_log_pos; + + if (mi->rli.until_condition == Relay_log_info::UNTIL_GTID && + flags & Gtid_list_log_event::FLAG_UNTIL_REACHED) + { + char str_buf[128]; + String str(str_buf, sizeof(str_buf), system_charset_info); + mi->rli.until_gtid_pos.to_string(&str); + sql_print_information("Slave I/O thread stops because it reached its" + " UNTIL master_gtid_pos %s", str.c_ptr_safe()); + mi->abort_slave= true; + } + } + break; + + case GTID_EVENT: + { + DBUG_EXECUTE_IF("kill_slave_io_after_2_events", + { + mi->dbug_do_disconnect= true; + mi->dbug_event_counter= 2; + };); + + uchar gtid_flag; + + if (Gtid_log_event::peek(buf, event_len, checksum_alg, + &event_gtid.domain_id, &event_gtid.server_id, + &event_gtid.seq_no, >id_flag, + rli->relay_log.description_event_for_queue)) + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + goto err; + } + got_gtid_event= true; + if (mi->using_gtid == Master_info::USE_GTID_NO) + goto default_action; + if (unlikely(!mi->gtid_event_seen)) + { + mi->gtid_event_seen= true; + if (mi->gtid_reconnect_event_skip_count) + { + /* + If we are reconnecting, and we need to skip a partial event group + already queued to the relay log before the reconnect, then we check + that we actually get the same event group (same GTID) as before, so + we do not end up with half of one group and half another. + + The only way we should be able to receive a different GTID than what + we expect is if the binlog on the master (or more likely the whole + master server) was replaced with a different one, on the same IP + address, _and_ the new master happens to have domains in a different + order so we get the GTID from a different domain first. Still, it is + best to protect against this case. + */ + if (event_gtid.domain_id != mi->last_queued_gtid.domain_id || + event_gtid.server_id != mi->last_queued_gtid.server_id || + event_gtid.seq_no != mi->last_queued_gtid.seq_no) + { + bool first; + error= ER_SLAVE_UNEXPECTED_MASTER_SWITCH; + error_msg.append(STRING_WITH_LEN("Expected: ")); + first= true; + rpl_slave_state_tostring_helper(&error_msg, &mi->last_queued_gtid, + &first); + error_msg.append(STRING_WITH_LEN(", received: ")); + first= true; + rpl_slave_state_tostring_helper(&error_msg, &event_gtid, &first); + goto err; + } + } + } + + if (unlikely(mi->gtid_reconnect_event_skip_count)) + { + goto default_action; + } + + /* + We have successfully queued to relay log everything before this GTID, so + in case of reconnect we can start from after any previous GTID. + (Normally we would have updated gtid_current_pos earlier at the end of + the previous event group, but better leave an extra check here for + safety). + */ + if (mi->events_queued_since_last_gtid) + { + mi->gtid_current_pos.update(&mi->last_queued_gtid); + mi->events_queued_since_last_gtid= 0; + } + mi->last_queued_gtid= event_gtid; + mi->last_queued_gtid_standalone= + (gtid_flag & Gtid_log_event::FL_STANDALONE) != 0; + + /* Should filter all the subsequent events in the current GTID group? */ + mi->domain_id_filter.do_filter(event_gtid.domain_id); + + ++mi->events_queued_since_last_gtid; + inc_pos= event_len; + } + break; + +#ifndef DBUG_OFF + case XID_EVENT: + DBUG_EXECUTE_IF("slave_discard_xid_for_gtid_0_x_1000", + { + /* Inject an event group that is missing its XID commit event. */ + if (mi->last_queued_gtid.domain_id == 0 && + mi->last_queued_gtid.seq_no == 1000) + goto skip_relay_logging; + }); +#endif + /* fall through */ default: + default_action: + DBUG_EXECUTE_IF("kill_slave_io_after_2_events", + { + if (mi->dbug_do_disconnect && + (((uchar)buf[EVENT_TYPE_OFFSET] == QUERY_EVENT) || + ((uchar)buf[EVENT_TYPE_OFFSET] == TABLE_MAP_EVENT)) + && (--mi->dbug_event_counter == 0)) + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + mi->dbug_do_disconnect= false; /* Safety */ + goto err; + } + };); + + DBUG_EXECUTE_IF("kill_slave_io_before_commit", + { + if ((uchar)buf[EVENT_TYPE_OFFSET] == XID_EVENT || + ((uchar)buf[EVENT_TYPE_OFFSET] == QUERY_EVENT && + Query_log_event::peek_is_commit_rollback(buf, event_len, + checksum_alg))) + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + goto err; + } + };); + + if (mi->using_gtid != Master_info::USE_GTID_NO && mi->gtid_event_seen) + { + if (unlikely(mi->gtid_reconnect_event_skip_count)) + { + --mi->gtid_reconnect_event_skip_count; + gtid_skip_enqueue= true; + } + else if (mi->events_queued_since_last_gtid) + ++mi->events_queued_since_last_gtid; + } + inc_pos= event_len; break; } @@ -4517,7 +6170,52 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) mysql_mutex_lock(log_lock); s_id= uint4korr(buf + SERVER_ID_OFFSET); - if ((s_id == ::server_id && !mi->rli.replicate_same_server_id) || + /* + Write the event to the relay log, unless we reconnected in the middle + of an event group and now need to skip the initial part of the group that + we already wrote before reconnecting. + */ + if (unlikely(gtid_skip_enqueue)) + { + mi->master_log_pos+= inc_pos; + if ((uchar)buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT && + s_id == mi->master_id) + { + /* + If we write this master's description event in the middle of an event + group due to GTID reconnect, SQL thread will think that master crashed + in the middle of the group and roll back the first half, so we must not. + + But we still have to write an artificial copy of the masters description + event, to override the initial slave-version description event so that + SQL thread has the right information for parsing the events it reads. + */ + rli->relay_log.description_event_for_queue->created= 0; + rli->relay_log.description_event_for_queue->set_artificial_event(); + if (rli->relay_log.append_no_lock + (rli->relay_log.description_event_for_queue)) + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + else + rli->relay_log.harvest_bytes_written(&rli->log_space_total); + } + else if (mi->gtid_reconnect_event_skip_count == 0) + { + /* + Add a fake rotate event so that SQL thread can see the old-style + position where we re-connected in the middle of a GTID event group. + */ + Rotate_log_event fake_rev(mi->master_log_name, 0, mi->master_log_pos, 0); + fake_rev.server_id= mi->master_id; + if (rli->relay_log.append_no_lock(&fake_rev)) + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + else + rli->relay_log.harvest_bytes_written(&rli->log_space_total); + } + } + else + if ((s_id == global_system_variables.server_id && + !mi->rli.replicate_same_server_id) || + event_that_should_be_ignored(buf) || /* the following conjunction deals with IGNORE_SERVER_IDS, if set If the master is on the ignore list, execution of @@ -4529,7 +6227,15 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) (s_id != mi->master_id || /* for the master meta information is necessary */ (buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT && - buf[EVENT_TYPE_OFFSET] != ROTATE_EVENT)))) + buf[EVENT_TYPE_OFFSET] != ROTATE_EVENT))) || + + /* + Check whether it needs to be filtered based on domain_id + (DO_DOMAIN_IDS/IGNORE_DOMAIN_IDS). + */ + (mi->domain_id_filter.is_group_filtered() && + Log_event::is_group_event((Log_event_type)(uchar) + buf[EVENT_TYPE_OFFSET]))) { /* Do not write it to the relay log. @@ -4548,7 +6254,8 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) IGNORE_SERVER_IDS it increments mi->master_log_pos as well as rli->group_relay_log_pos. */ - if (!(s_id == ::server_id && !mi->rli.replicate_same_server_id) || + if (!(s_id == global_system_variables.server_id && + !mi->rli.replicate_same_server_id) || (buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT && buf[EVENT_TYPE_OFFSET] != ROTATE_EVENT && buf[EVENT_TYPE_OFFSET] != STOP_EVENT)) @@ -4557,6 +6264,8 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) memcpy(rli->ign_master_log_name_end, mi->master_log_name, FN_REFLEN); DBUG_ASSERT(rli->ign_master_log_name_end[0]); rli->ign_master_log_pos_end= mi->master_log_pos; + if (got_gtid_event) + rli->ign_gtids.update(&event_gtid); } rli->relay_log.signal_update(); // the slave SQL thread needs to re-check DBUG_PRINT("info", ("master_log_pos: %lu, event originating from %u server, ignored", @@ -4564,8 +6273,7 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) } else { - /* write the event to the relay log */ - if (likely(!(rli->relay_log.appendv(buf,event_len,0)))) + if (likely(!rli->relay_log.write_event_buffer((uchar*)buf, event_len))) { mi->master_log_pos+= inc_pos; DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos)); @@ -4576,28 +6284,58 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; } rli->ign_master_log_name_end[0]= 0; // last event is not ignored + if (got_gtid_event) + rli->ign_gtids.remove_if_present(&event_gtid); if (save_buf != NULL) buf= save_buf; } mysql_mutex_unlock(log_lock); + if (!error && + mi->using_gtid != Master_info::USE_GTID_NO && + mi->events_queued_since_last_gtid > 0 && + ( (mi->last_queued_gtid_standalone && + !Log_event::is_part_of_group((Log_event_type)(uchar) + buf[EVENT_TYPE_OFFSET])) || + (!mi->last_queued_gtid_standalone && + ((uchar)buf[EVENT_TYPE_OFFSET] == XID_EVENT || + ((uchar)buf[EVENT_TYPE_OFFSET] == QUERY_EVENT && + Query_log_event::peek_is_commit_rollback(buf, event_len, + checksum_alg)))))) + { + /* + The whole of the current event group is queued. So in case of + reconnect we can start from after the current GTID. + */ + mi->gtid_current_pos.update(&mi->last_queued_gtid); + mi->events_queued_since_last_gtid= 0; + + /* Reset the domain_id_filter flag. */ + mi->domain_id_filter.reset_filter(); + } + skip_relay_logging: - + err: if (unlock_data_lock) mysql_mutex_unlock(&mi->data_lock); DBUG_PRINT("info", ("error: %d", error)); - if (error) - mi->report(ERROR_LEVEL, error, ER(error), - (error == ER_SLAVE_RELAY_LOG_WRITE_FAILURE)? - "could not queue event from master" : + + /* + Do not print ER_SLAVE_RELAY_LOG_WRITE_FAILURE error here, as the caller + handle_slave_io() prints it on return. + */ + if (error && error != ER_SLAVE_RELAY_LOG_WRITE_FAILURE) + mi->report(ERROR_LEVEL, error, NULL, ER_DEFAULT(error), error_msg.ptr()); + DBUG_RETURN(error); } void end_relay_log_info(Relay_log_info* rli) { + mysql_mutex_t *log_lock; DBUG_ENTER("end_relay_log_info"); rli->error_on_rli_init_info= false; @@ -4616,8 +6354,11 @@ void end_relay_log_info(Relay_log_info* rli) rli->cur_log_fd = -1; } rli->inited = 0; + log_lock= rli->relay_log.get_log_lock(); + mysql_mutex_lock(log_lock); rli->relay_log.close(LOG_CLOSE_INDEX | LOG_CLOSE_STOP_EVENT); rli->relay_log.harvest_bytes_written(&rli->log_space_total); + mysql_mutex_unlock(log_lock); /* Delete the slave's temporary tables from memory. In the future there will be other actions than this, to ensure persistance @@ -4689,7 +6430,7 @@ static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, int slave_was_killed; int last_errno= -2; // impossible error ulong err_count=0; - char llbuff[22]; + my_bool my_true= 1; DBUG_ENTER("connect_to_master"); set_slave_max_allowed_packet(thd, mysql); #ifndef DBUG_OFF @@ -4697,10 +6438,12 @@ static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, #endif ulong client_flag= CLIENT_REMEMBER_OPTIONS; if (opt_slave_compressed_protocol) - client_flag=CLIENT_COMPRESS; /* We will use compression */ + client_flag|= CLIENT_COMPRESS; /* We will use compression */ mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *) &slave_net_timeout); mysql_options(mysql, MYSQL_OPT_READ_TIMEOUT, (char *) &slave_net_timeout); + mysql_options(mysql, MYSQL_OPT_USE_THREAD_SPECIFIC_MEMORY, + (char*) &my_true); #ifdef HAVE_OPENSSL if (mi->ssl) @@ -4713,6 +6456,10 @@ static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, mi->ssl_cipher[0]?mi->ssl_cipher:0); mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &mi->ssl_verify_server_cert); + mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH, + mi->ssl_crlpath[0] ? mi->ssl_crlpath : 0); + mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + &mi->ssl_verify_server_cert); } #endif @@ -4741,16 +6488,16 @@ static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir_ptr); /* we disallow empty users */ - if (mi->user == NULL || mi->user[0] == 0) + if (mi->user[0] == 0) { - mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), + mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, + ER_THD(thd, ER_SLAVE_FATAL_ERROR), "Invalid (empty) username when attempting to " "connect to the master server. Connection attempt " "terminated."); DBUG_RETURN(1); } - while (!(slave_was_killed = io_slave_killed(thd,mi)) && + while (!(slave_was_killed = io_slave_killed(mi)) && (reconnect ? mysql_reconnect(mysql) != 0 : mysql_real_connect(mysql, mi->host, mi->user, mi->password, 0, mi->port, 0, client_flag) == 0)) @@ -4760,9 +6507,9 @@ static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, { last_errno=mysql_errno(mysql); suppress_warnings= 0; - mi->report(ERROR_LEVEL, last_errno, + mi->report(ERROR_LEVEL, last_errno, NULL, "error %s to master '%s@%s:%d'" - " - retry-time: %d retries: %lu message: %s", + " - retry-time: %d maximum-retries: %lu message: %s", (reconnect ? "reconnecting" : "connecting"), mi->user, mi->host, mi->port, mi->connect_retry, master_retry_count, @@ -4790,11 +6537,10 @@ static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, if (reconnect) { if (!suppress_warnings && global_system_variables.log_warnings) - sql_print_information("Slave: connected to master '%s@%s:%d',\ -replication resumed in log '%s' at position %s", mi->user, - mi->host, mi->port, - IO_RPL_LOG_NAME, - llstr(mi->master_log_pos,llbuff)); + sql_print_information("Slave: connected to master '%s@%s:%d'," + "replication resumed in log '%s' at " + "position %llu", mi->user, mi->host, mi->port, + IO_RPL_LOG_NAME, mi->master_log_pos); } else { @@ -4828,18 +6574,20 @@ static int safe_reconnect(THD* thd, MYSQL* mysql, Master_info* mi, } +#ifdef NOT_USED MYSQL *rpl_connect_master(MYSQL *mysql) { - THD *thd= current_thd; Master_info *mi= my_pthread_getspecific_ptr(Master_info*, RPL_MASTER_INFO); + bool allocated= false; + my_bool my_true= 1; + THD *thd; + if (!mi) { sql_print_error("'rpl_connect_master' must be called in slave I/O thread context."); return NULL; } - - bool allocated= false; - + thd= mi->io_thd; if (!mysql) { if(!(mysql= mysql_init(NULL))) @@ -4859,6 +6607,8 @@ MYSQL *rpl_connect_master(MYSQL *mysql) */ mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *) &slave_net_timeout); mysql_options(mysql, MYSQL_OPT_READ_TIMEOUT, (char *) &slave_net_timeout); + mysql_options(mysql, MYSQL_OPT_USE_THREAD_SPECIFIC_MEMORY, + (char*) &my_true); #ifdef HAVE_OPENSSL if (mi->ssl) @@ -4880,11 +6630,11 @@ MYSQL *rpl_connect_master(MYSQL *mysql) if (mi->user == NULL || mi->user[0] == 0 - || io_slave_killed(thd, mi) + || io_slave_killed( mi) || !mysql_real_connect(mysql, mi->host, mi->user, mi->password, 0, mi->port, 0, 0)) { - if (!io_slave_killed(thd, mi)) + if (!io_slave_killed( mi)) sql_print_error("rpl_connect_master: error connecting to master: %s (server_error: %d)", mysql_error(mysql), mysql_errno(mysql)); @@ -4894,6 +6644,7 @@ MYSQL *rpl_connect_master(MYSQL *mysql) } return mysql; } +#endif /* Store the file and position where the execute-slave thread are in the @@ -4984,7 +6735,7 @@ static IO_CACHE *reopen_relay_log(Relay_log_info *rli, const char **errmsg) relay_log_pos Current log pos pending Number of bytes already processed from the event */ - rli->event_relay_log_pos= max(rli->event_relay_log_pos, BIN_LOG_HEADER_SIZE); + rli->event_relay_log_pos= MY_MAX(rli->event_relay_log_pos, BIN_LOG_HEADER_SIZE); my_b_seek(cur_log,rli->event_relay_log_pos); DBUG_RETURN(cur_log); } @@ -4999,17 +6750,20 @@ static IO_CACHE *reopen_relay_log(Relay_log_info *rli, const char **errmsg) @return The event read, or NULL on error. If an error occurs, the error is reported through the sql_print_information() or sql_print_error() functions. + + The size of the read event (in bytes) is returned in *event_size. */ -static Log_event* next_event(Relay_log_info* rli) +static Log_event* next_event(rpl_group_info *rgi, ulonglong *event_size) { Log_event* ev; + Relay_log_info *rli= rgi->rli; IO_CACHE* cur_log = rli->cur_log; mysql_mutex_t *log_lock = rli->relay_log.get_log_lock(); const char* errmsg=0; - THD* thd = rli->sql_thd; DBUG_ENTER("next_event"); - DBUG_ASSERT(thd != 0); + DBUG_ASSERT(rgi->thd != 0 && rgi->thd == rli->sql_driver_thd); + *event_size= 0; #ifndef DBUG_OFF if (abort_slave_event_count && !rli->events_till_abort--) @@ -5025,7 +6779,7 @@ static Log_event* next_event(Relay_log_info* rli) */ mysql_mutex_assert_owner(&rli->data_lock); - while (!sql_slave_killed(thd,rli)) + while (!sql_slave_killed(rgi)) { /* We can have two kinds of log reading: @@ -5038,6 +6792,7 @@ static Log_event* next_event(Relay_log_info* rli) The other case is much simpler: We just have a read only log that nobody else will be updating. */ + ulonglong old_pos; bool hot_log; if ((hot_log = (cur_log != &rli->cache_buf))) { @@ -5068,12 +6823,11 @@ static Log_event* next_event(Relay_log_info* rli) #ifndef DBUG_OFF { /* This is an assertion which sometimes fails, let's try to track it */ - char llbuf1[22], llbuf2[22]; - DBUG_PRINT("info", ("my_b_tell(cur_log)=%s rli->event_relay_log_pos=%s", - llstr(my_b_tell(cur_log),llbuf1), - llstr(rli->event_relay_log_pos,llbuf2))); + DBUG_PRINT("info", ("my_b_tell(cur_log)=%llu rli->event_relay_log_pos=%llu", + my_b_tell(cur_log), rli->event_relay_log_pos)); DBUG_ASSERT(my_b_tell(cur_log) >= BIN_LOG_HEADER_SIZE); - DBUG_ASSERT(my_b_tell(cur_log) == rli->event_relay_log_pos); + DBUG_ASSERT(rli->mi->using_parallel() || + my_b_tell(cur_log) == rli->event_relay_log_pos); } #endif /* @@ -5088,22 +6842,24 @@ static Log_event* next_event(Relay_log_info* rli) But if the relay log is created by new_file(): then the solution is: MYSQL_BIN_LOG::open() will write the buffered description event. */ + old_pos= rli->event_relay_log_pos; if ((ev= Log_event::read_log_event(cur_log,0, rli->relay_log.description_event_for_exec, opt_slave_sql_verify_checksum))) { - DBUG_ASSERT(thd==rli->sql_thd); /* read it while we have a lock, to avoid a mutex lock in inc_event_relay_log_pos() */ rli->future_event_relay_log_pos= my_b_tell(cur_log); + *event_size= rli->future_event_relay_log_pos - old_pos; + if (hot_log) mysql_mutex_unlock(log_lock); + rli->sql_thread_caught_up= false; DBUG_RETURN(ev); } - DBUG_ASSERT(thd==rli->sql_thd); if (opt_reckless_slave) // For mysql-test cur_log->error = 0; if (cur_log->error < 0) @@ -5139,12 +6895,10 @@ static Log_event* next_event(Relay_log_info* rli) Seconds_Behind_Master would be zero only when master has no more updates in binlog for slave. The heartbeat can be sent in a (small) fraction of slave_net_timeout. Until it's done - rli->last_master_timestamp is temporarely (for time of - waiting for the following event) reset whenever EOF is - reached. + rli->sql_thread_caught_up is temporarely (for time of waiting for + the following event) set whenever EOF is reached. */ - time_t save_timestamp= rli->last_master_timestamp; - rli->last_master_timestamp= 0; + rli->sql_thread_caught_up= true; DBUG_ASSERT(rli->relay_log.get_open_count() == rli->cur_log_old_open_count); @@ -5168,6 +6922,39 @@ static Log_event* next_event(Relay_log_info* rli) DBUG_RETURN(ev); } + if (rli->ign_gtids.count() && !rli->is_in_group()) + { + /* + We generate and return a Gtid_list, to update gtid_slave_pos, + unless being in the middle of a group. + */ + DBUG_PRINT("info",("seeing ignored end gtids")); + ev= new Gtid_list_log_event(&rli->ign_gtids, + Gtid_list_log_event::FLAG_IGN_GTIDS); + rli->ign_gtids.reset(); + mysql_mutex_unlock(log_lock); + if (unlikely(!ev)) + { + errmsg= "Slave SQL thread failed to create a Gtid_list event " + "(out of memory?), gtid_slave_pos may be inaccurate"; + goto err; + } + ev->server_id= 0; // don't be ignored by slave SQL thread + ev->set_artificial_event(); // Don't mess up Exec_Master_Log_Pos + DBUG_RETURN(ev); + } + + /* + We have to check sql_slave_killed() here an extra time. + Otherwise we may miss a wakeup, since last check was done + without holding LOCK_log. + */ + if (sql_slave_killed(rgi)) + { + mysql_mutex_unlock(log_lock); + break; + } + /* We can, and should release data_lock while we are waiting for update. If we do not, show slave status will block @@ -5191,14 +6978,15 @@ static Log_event* next_event(Relay_log_info* rli) and reads one more event and starts honoring log_space_limit again. If the SQL thread needs more events to be able to rotate the log (it - might need to finish the current group first), then it can ask for one - more at a time. Thus we don't outgrow the relay log indefinitely, + might need to finish the current group first), then it can ask for + one more at a time. Thus we don't outgrow the relay log indefinitely, but rather in a controlled manner, until the next rotate. When the SQL thread starts it sets ignore_log_space_limit to false. We should also reset ignore_log_space_limit to 0 when the user does - RESET SLAVE, but in fact, no need as RESET SLAVE requires that the slave - be stopped, and the SQL thread sets ignore_log_space_limit to 0 when + RESET SLAVE, but in fact, no need as RESET SLAVE requires that the + slave be stopped, and the SQL thread sets ignore_log_space_limit + to 0 when it stops. */ mysql_mutex_lock(&rli->log_space_lock); @@ -5230,10 +7018,10 @@ static Log_event* next_event(Relay_log_info* rli) mysql_cond_broadcast(&rli->log_space_cond); mysql_mutex_unlock(&rli->log_space_lock); // Note that wait_for_update_relay_log unlocks lock_log ! - rli->relay_log.wait_for_update_relay_log(rli->sql_thd); + rli->relay_log.wait_for_update_relay_log(rli->sql_driver_thd); // re-acquire data lock since we released it earlier mysql_mutex_lock(&rli->data_lock); - rli->last_master_timestamp= save_timestamp; + rli->sql_thread_caught_up= false; continue; } /* @@ -5245,6 +7033,8 @@ static Log_event* next_event(Relay_log_info* rli) DBUG_ASSERT(rli->cur_log_fd >= 0); mysql_file_close(rli->cur_log_fd, MYF(MY_WME)); rli->cur_log_fd = -1; + rli->last_inuse_relaylog->completed= true; + rli->relay_log.description_event_for_exec->reset_crypto(); if (relay_log_purge) { @@ -5284,9 +7074,12 @@ static Log_event* next_event(Relay_log_info* rli) } rli->event_relay_log_pos = BIN_LOG_HEADER_SIZE; strmake_buf(rli->event_relay_log_name,rli->linfo.log_file_name); - flush_relay_log_info(rli); + if (flush_relay_log_info(rli)) + { + errmsg= "error flushing relay log"; + goto err; + } } - /* Now we want to open this next log. To know if it's a hot log (the one being written by the I/O thread now) or a cold log, we can use @@ -5302,11 +7095,6 @@ static Log_event* next_event(Relay_log_info* rli) mysql_mutex_lock(log_lock); if (rli->relay_log.is_active(rli->linfo.log_file_name)) { -#ifdef EXTRA_DEBUG - if (global_system_variables.log_warnings) - sql_print_information("next log '%s' is currently active", - rli->linfo.log_file_name); -#endif rli->cur_log= cur_log= rli->relay_log.get_log_file(); rli->cur_log_old_open_count= rli->relay_log.get_open_count(); DBUG_ASSERT(rli->cur_log_fd == -1); @@ -5378,6 +7166,12 @@ static Log_event* next_event(Relay_log_info* rli) mysql_mutex_unlock(log_lock); goto err; } + if (rli->alloc_inuse_relaylog(rli->linfo.log_file_name)) + { + if (!hot_log) + mysql_mutex_unlock(log_lock); + goto err; + } if (!hot_log) mysql_mutex_unlock(log_lock); continue; @@ -5389,15 +7183,12 @@ static Log_event* next_event(Relay_log_info* rli) ourselves. We are sure that the log is still not hot now (a log can get from hot to cold, but not from cold to hot). No need for LOCK_log. */ -#ifdef EXTRA_DEBUG - if (global_system_variables.log_warnings) - sql_print_information("next log '%s' is not active", - rli->linfo.log_file_name); -#endif // open_binlog() will check the magic header if ((rli->cur_log_fd=open_binlog(cur_log,rli->linfo.log_file_name, &errmsg)) <0) goto err; + if (rli->alloc_inuse_relaylog(rli->linfo.log_file_name)) + goto err; } else { @@ -5536,7 +7327,7 @@ bool rpl_master_has_bug(const Relay_log_info *rli, uint bug_id, bool report, " so slave stops; check error log on slave" " for more info", MYF(0), bug_id); // a verbose message for the error log - rli->report(ERROR_LEVEL, ER_UNKNOWN_ERROR, + rli->report(ERROR_LEVEL, ER_UNKNOWN_ERROR, NULL, "According to the master's version ('%s')," " it is probable that master suffers from this bug:" " http://bugs.mysql.com/bug.php?id=%u" @@ -5573,20 +7364,14 @@ bool rpl_master_has_bug(const Relay_log_info *rli, uint bug_id, bool report, */ bool rpl_master_erroneous_autoinc(THD *thd) { - if (active_mi && active_mi->rli.sql_thd == thd) + if (thd->rgi_slave) { - Relay_log_info *rli= &active_mi->rli; DBUG_EXECUTE_IF("simulate_bug33029", return TRUE;); - return rpl_master_has_bug(rli, 33029, FALSE, NULL, NULL); + return rpl_master_has_bug(thd->rgi_slave->rli, 33029, FALSE, NULL, NULL); } return FALSE; } -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class I_List_iterator<i_string>; -template class I_List_iterator<i_string_pair>; -#endif - /** @} (end of group Replication) */ diff --git a/sql/slave.h b/sql/slave.h index ae70f0194fa..58c8106614d 100644 --- a/sql/slave.h +++ b/sql/slave.h @@ -45,9 +45,14 @@ #define MAX_SLAVE_ERROR 2000 +#define MAX_REPLICATION_THREAD 64 + // Forward declarations class Relay_log_info; class Master_info; +class Master_info_index; +struct rpl_group_info; +struct rpl_parallel_thread; int init_intvar_from_file(int* var, IO_CACHE* f, int default_val); int init_strvar_from_file(char *var, int max_size, IO_CACHE *f, @@ -125,13 +130,16 @@ extern my_bool opt_log_slave_updates; extern char *opt_slave_skip_errors; extern my_bool opt_replicate_annotate_row_events; extern ulonglong relay_log_space_limit; +extern ulonglong slave_skipped_errors; +extern const char *relay_log_index; +extern const char *relay_log_basename; /* - 3 possible values for Master_info::slave_running and + 4 possible values for Master_info::slave_running and Relay_log_info::slave_running. - The values 0,1,2 are very important: to keep the diff small, I didn't - substitute places where we use 0/1 with the newly defined symbols. So don't change - these values. + The values 0,1,2,3 are very important: to keep the diff small, I didn't + substitute places where we use 0/1 with the newly defined symbols. + So don't change these values. The same way, code is assuming that in Relay_log_info we use only values 0/1. I started with using an enum, but @@ -140,6 +148,7 @@ extern ulonglong relay_log_space_limit; #define MYSQL_SLAVE_NOT_RUN 0 #define MYSQL_SLAVE_RUN_NOT_CONNECT 1 #define MYSQL_SLAVE_RUN_CONNECT 2 +#define MYSQL_SLAVE_RUN_READING 3 #define RPL_LOG_NAME (rli->group_master_log_name[0] ? rli->group_master_log_name :\ "FIRST") @@ -168,7 +177,8 @@ bool flush_relay_log_info(Relay_log_info* rli); int register_slave_on_master(MYSQL* mysql); int terminate_slave_threads(Master_info* mi, int thread_mask, bool skip_lock = 0); -int start_slave_threads(bool need_slave_mutex, bool wait_for_start, +int start_slave_threads(THD *thd, + bool need_slave_mutex, bool wait_for_start, Master_info* mi, const char* master_info_fname, const char* slave_info_fname, int thread_mask); /* @@ -197,7 +207,11 @@ int mysql_table_dump(THD* thd, const char* db, int fetch_master_table(THD* thd, const char* db_name, const char* table_name, Master_info* mi, MYSQL* mysql, bool overwrite); -bool show_master_info(THD* thd, Master_info* mi); +void show_master_info_get_fields(THD *thd, List<Item> *field_list, + bool full, size_t gtid_pos_length); +bool show_master_info(THD* thd, Master_info* mi, bool full); +bool show_all_master_info(THD* thd); +void show_binlog_info_get_fields(THD *thd, List<Item> *field_list); bool show_binlog_info(THD* thd); bool rpl_master_has_bug(const Relay_log_info *rli, uint bug_id, bool report, bool (*pred)(const void *), const void *param); @@ -206,14 +220,17 @@ bool rpl_master_erroneous_autoinc(THD* thd); const char *print_slave_db_safe(const char *db); void skip_load_data_infile(NET* net); +void slave_prepare_for_shutdown(); void end_slave(); /* release slave threads */ void close_active_mi(); /* clean up slave threads data */ void clear_until_condition(Relay_log_info* rli); void clear_slave_error(Relay_log_info* rli); void end_relay_log_info(Relay_log_info* rli); -void lock_slave_threads(Master_info* mi); -void unlock_slave_threads(Master_info* mi); void init_thread_mask(int* mask,Master_info* mi,bool inverse); +Format_description_log_event * +read_relay_log_description_event(IO_CACHE *cur_log, ulonglong start_pos, + const char **errmsg); + int init_relay_log_pos(Relay_log_info* rli,const char* log,ulonglong pos, bool need_data_lock, const char** errmsg, bool look_for_description_event); @@ -221,16 +238,25 @@ int init_relay_log_pos(Relay_log_info* rli,const char* log,ulonglong pos, int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset, const char** errmsg); void set_slave_thread_options(THD* thd); -void set_slave_thread_default_charset(THD *thd, Relay_log_info const *rli); +void set_slave_thread_default_charset(THD *thd, rpl_group_info *rgi); int rotate_relay_log(Master_info* mi); -int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli); +int has_temporary_error(THD *thd); +int apply_event_and_update_pos(Log_event* ev, THD* thd, + struct rpl_group_info *rgi); +int apply_event_and_update_pos_for_parallel(Log_event* ev, THD* thd, + struct rpl_group_info *rgi); pthread_handler_t handle_slave_io(void *arg); +void slave_output_error_info(rpl_group_info *rgi, THD *thd); pthread_handler_t handle_slave_sql(void *arg); bool net_request_file(NET* net, const char* fname); +void slave_background_kill_request(THD *to_kill); extern bool volatile abort_loop; extern Master_info *active_mi; /* active_mi for multi-master */ +extern Master_info *default_master_info; /* To replace active_mi */ +extern Master_info_index *master_info_index; +extern LEX_STRING default_master_connection_name; extern my_bool replicate_same_server_id; extern int disconnect_slave_event_count, abort_slave_event_count ; diff --git a/sql/sp.cc b/sql/sp.cc index 62ed89f4a02..a5a14ec8d85 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -15,6 +15,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sp.h" @@ -33,25 +34,17 @@ #include <my_user.h> -static bool -create_string(THD *thd, String *buf, - stored_procedure_type sp_type, - const char *db, ulong dblen, - const char *name, ulong namelen, - const char *params, ulong paramslen, - const char *returns, ulong returnslen, - const char *body, ulong bodylen, - st_sp_chistics *chistics, - const LEX_STRING *definer_user, - const LEX_STRING *definer_host, - ulonglong sql_mode); - +/* Used in error handling only */ +#define SP_TYPE_STRING(type) \ + (type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE") + static int db_load_routine(THD *thd, stored_procedure_type type, sp_name *name, sp_head **sphp, ulonglong sql_mode, const char *params, const char *returns, const char *body, st_sp_chistics &chistics, - const char *definer, longlong created, longlong modified, + LEX_STRING *definer_user_name, LEX_STRING *definer_host_name, + longlong created, longlong modified, Stored_program_creation_ctx *creation_ctx); static const @@ -169,7 +162,7 @@ TABLE_FIELD_TYPE proc_table_fields[MYSQL_PROC_FIELD_COUNT] = }; static const TABLE_FIELD_DEF - proc_table_def= {MYSQL_PROC_FIELD_COUNT, proc_table_fields}; +proc_table_def= {MYSQL_PROC_FIELD_COUNT, proc_table_fields, 0, (uint*) 0 }; /*************************************************************************/ @@ -326,9 +319,9 @@ Stored_routine_creation_ctx::load_from_db(THD *thd, if (invalid_creation_ctx) { push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_WARN, + Sql_condition::WARN_LEVEL_WARN, ER_SR_INVALID_CREATION_CTX, - ER(ER_SR_INVALID_CREATION_CTX), + ER_THD(thd, ER_SR_INVALID_CREATION_CTX), (const char *) db_name, (const char *) sr_name); } @@ -375,10 +368,10 @@ void Proc_table_intact::report_error(uint code, const char *fmt, ...) my_vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); - if (code == ER_COL_COUNT_DOESNT_MATCH_CORRUPTED) + if (code) my_message(code, buf, MYF(0)); else - my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "proc"); + my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), "mysql", "proc"); if (m_print_once) { @@ -549,6 +542,10 @@ db_find_routine(THD *thd, stored_procedure_type type, sp_name *name, ulonglong sql_mode, saved_mode= thd->variables.sql_mode; Open_tables_backup open_tables_state_backup; Stored_program_creation_ctx *creation_ctx; + char definer_user_name_holder[USERNAME_LENGTH + 1]; + LEX_STRING definer_user_name= { definer_user_name_holder, USERNAME_LENGTH }; + char definer_host_name_holder[HOSTNAME_LENGTH + 1]; + LEX_STRING definer_host_name= { definer_host_name_holder, HOSTNAME_LENGTH }; DBUG_ENTER("db_find_routine"); DBUG_PRINT("enter", ("type: %d name: %.*s", @@ -658,9 +655,19 @@ db_find_routine(THD *thd, stored_procedure_type type, sp_name *name, close_system_tables(thd, &open_tables_state_backup); table= 0; + if (parse_user(definer, strlen(definer), + definer_user_name.str, &definer_user_name.length, + definer_host_name.str, &definer_host_name.length) && + definer_user_name.length && !definer_host_name.length) + { + // 'user@' -> 'user@%' + definer_host_name= host_not_specified; + } + ret= db_load_routine(thd, type, name, sphp, sql_mode, params, returns, body, chistics, - definer, created, modified, creation_ctx); + &definer_user_name, &definer_host_name, + created, modified, creation_ctx); done: /* Restore the time zone flag as the timezone usage in proc table @@ -684,9 +691,9 @@ public: virtual bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl); + Sql_condition ** cond_hdl); }; bool @@ -694,13 +701,13 @@ Silence_deprecated_warning::handle_condition( THD *, uint sql_errno, const char*, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char*, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { *cond_hdl= NULL; if (sql_errno == ER_WARN_DEPRECATED_SYNTAX && - level == MYSQL_ERROR::WARN_LEVEL_WARN) + level == Sql_condition::WARN_LEVEL_WARN) return TRUE; return FALSE; @@ -773,9 +780,9 @@ public: virtual bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* message, - MYSQL_ERROR ** cond_hdl); + Sql_condition ** cond_hdl); bool error_caught() const { return m_error_caught; } @@ -787,9 +794,9 @@ bool Bad_db_error_handler::handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* message, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { if (sql_errno == ER_BAD_DB_ERROR) { @@ -805,7 +812,8 @@ db_load_routine(THD *thd, stored_procedure_type type, sp_name *name, sp_head **sphp, ulonglong sql_mode, const char *params, const char *returns, const char *body, st_sp_chistics &chistics, - const char *definer, longlong created, longlong modified, + LEX_STRING *definer_user_name, LEX_STRING *definer_host_name, + longlong created, longlong modified, Stored_program_creation_ctx *creation_ctx) { LEX *old_lex= thd->lex, newlex; @@ -815,21 +823,13 @@ db_load_routine(THD *thd, stored_procedure_type type, { saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) }; bool cur_db_changed; Bad_db_error_handler db_not_exists_handler; - char definer_user_name_holder[USERNAME_LENGTH + 1]; - LEX_STRING definer_user_name= { definer_user_name_holder, - USERNAME_LENGTH }; - - char definer_host_name_holder[HOSTNAME_LENGTH + 1]; - LEX_STRING definer_host_name= { definer_host_name_holder, HOSTNAME_LENGTH }; int ret= 0; thd->lex= &newlex; newlex.current_select= NULL; - - parse_user(definer, strlen(definer), - definer_user_name.str, &definer_user_name.length, - definer_host_name.str, &definer_host_name.length); + // Resetting REPLACE and EXIST flags in create_info, for show_create_sp() + newlex.create_info.DDL_options_st::init(); defstr.set_charset(creation_ctx->get_client_cs()); @@ -839,14 +839,14 @@ db_load_routine(THD *thd, stored_procedure_type type, definition for SHOW CREATE PROCEDURE later. */ - if (!create_string(thd, &defstr, + if (!show_create_sp(thd, &defstr, type, NULL, 0, name->m_name.str, name->m_name.length, params, strlen(params), returns, strlen(returns), body, strlen(body), - &chistics, &definer_user_name, &definer_host_name, + &chistics, definer_user_name, definer_host_name, sql_mode)) { ret= SP_INTERNAL_ERROR; @@ -896,7 +896,7 @@ db_load_routine(THD *thd, stored_procedure_type type, goto end; } - (*sphp)->set_definer(&definer_user_name, &definer_host_name); + (*sphp)->set_definer(definer_user_name, definer_host_name); (*sphp)->set_info(created, modified, &chistics, sql_mode); (*sphp)->set_creation_ctx(creation_ctx); (*sphp)->optimize(); @@ -919,7 +919,7 @@ end: } -static void +void sp_returns_type(THD *thd, String &result, sp_head *sp) { TABLE table; @@ -948,6 +948,51 @@ sp_returns_type(THD *thd, String &result, sp_head *sp) /** + Delete the record for the stored routine object from mysql.proc, + which is already opened, locked, and positioned to the record with the + record to be deleted. + + The operation deletes the record for the current record in "table" + and invalidates the stored-routine cache. + + @param thd Thread context. + @param type Stored routine type (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION) + @param name Stored routine name. + @param table A pointer to the opened mysql.proc table + + @returns Error code. + @return SP_OK on success, or SP_DELETE_ROW_FAILED on error. + used to indicate about errors. +*/ +static int +sp_drop_routine_internal(THD *thd, stored_procedure_type type, + sp_name *name, TABLE *table) +{ + DBUG_ENTER("sp_drop_routine_internal"); + + if (table->file->ha_delete_row(table->record[0])) + DBUG_RETURN(SP_DELETE_ROW_FAILED); + + /* Make change permanent and avoid 'table is marked as crashed' errors */ + table->file->extra(HA_EXTRA_FLUSH); + + sp_cache_invalidate(); + /* + A lame workaround for lack of cache flush: + make sure the routine is at least gone from the + local cache. + */ + sp_head *sp; + sp_cache **spc= (type == TYPE_ENUM_FUNCTION ? + &thd->sp_func_cache : &thd->sp_proc_cache); + sp= sp_cache_lookup(spc, name); + if (sp) + sp_cache_flush_obsolete(spc, &sp); + DBUG_RETURN(SP_OK); +} + + +/** Write stored-routine object into mysql.proc. This operation stores attributes of the stored procedure/function into @@ -966,16 +1011,19 @@ sp_returns_type(THD *thd, String &result, sp_head *sp) followed by an implicit grant (sp_grant_privileges()) and this subsequent call opens and closes mysql.procs_priv. - @return Error code. SP_OK is returned on success. Other - SP_ constants are used to indicate about errors. + @return Error status. + @retval FALSE on success + @retval TRUE on error */ -int +bool sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) { - int ret; + LEX *lex= thd->lex; + bool ret= TRUE; TABLE *table; - char definer[USER_HOST_BUFF_SIZE]; + char definer_buf[USER_HOST_BUFF_SIZE]; + LEX_STRING definer; ulonglong saved_mode= thd->variables.sql_mode; MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? MDL_key::FUNCTION : MDL_key::PROCEDURE; @@ -985,9 +1033,6 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) enum_check_fields saved_count_cuted_fields; bool store_failed= FALSE; - - bool save_binlog_row_based; - DBUG_ENTER("sp_create_routine"); DBUG_PRINT("enter", ("type: %d name: %.*s", (int) type, (int) sp->m_name.length, @@ -1000,35 +1045,76 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) /* Grab an exclusive MDL lock. */ if (lock_object_name(thd, mdl_type, sp->m_db.str, sp->m_name.str)) - DBUG_RETURN(SP_OPEN_TABLE_FAILED); - - /* Reset sql_mode during data dictionary operations. */ - thd->variables.sql_mode= 0; + { + my_error(ER_BAD_DB_ERROR, MYF(0), sp->m_db.str); + DBUG_RETURN(TRUE); + } /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. + Check that a database directory with this name + exists. Design note: This won't work on virtual databases + like information_schema. */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); + if (check_db_dir_existence(sp->m_db.str)) + { + my_error(ER_BAD_DB_ERROR, MYF(0), sp->m_db.str); + DBUG_RETURN(TRUE); + } + + + /* Reset sql_mode during data dictionary operations. */ + thd->variables.sql_mode= 0; saved_count_cuted_fields= thd->count_cuted_fields; thd->count_cuted_fields= CHECK_FIELD_WARN; if (!(table= open_proc_table_for_update(thd))) - ret= SP_OPEN_TABLE_FAILED; + { + my_error(ER_SP_STORE_FAILED, MYF(0), SP_TYPE_STRING(type),sp->m_name.str); + goto done; + } else { + /* Checking if the routine already exists */ + if (db_find_routine_aux(thd, type, lex->spname, table) == SP_OK) + { + if (lex->create_info.or_replace()) + { + if ((ret= sp_drop_routine_internal(thd, type, lex->spname, table))) + goto done; + } + else if (lex->create_info.if_not_exists()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SP_ALREADY_EXISTS, + ER_THD(thd, ER_SP_ALREADY_EXISTS), + SP_TYPE_STRING(type), + lex->spname->m_name.str); + + ret= FALSE; + + // Setting retstr as it is used for logging. + if (sp->m_type == TYPE_ENUM_FUNCTION) + sp_returns_type(thd, retstr, sp); + goto log; + } + else + { + my_error(ER_SP_ALREADY_EXISTS, MYF(0), + SP_TYPE_STRING(type), sp->m_name.str); + goto done; + } + } + restore_record(table, s->default_values); // Get default values for fields /* NOTE: all needed privilege checks have been already done. */ - strxnmov(definer, sizeof(definer)-1, thd->lex->definer->user.str, "@", - thd->lex->definer->host.str, NullS); + thd->lex->definer->set_lex_string(&definer, definer_buf); if (table->s->fields < MYSQL_PROC_FIELD_COUNT) { - ret= SP_GET_FIELD_FAILED; + my_error(ER_SP_STORE_FAILED, MYF(0), + SP_TYPE_STRING(type), sp->m_name.str); goto done; } @@ -1037,12 +1123,12 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) sp->m_name.str+sp->m_name.length) > table->field[MYSQL_PROC_FIELD_NAME]->char_length()) { - ret= SP_BAD_IDENTIFIER; + my_error(ER_TOO_LONG_IDENT, MYF(0), sp->m_name.str); goto done; } if (sp->m_body.length > table->field[MYSQL_PROC_FIELD_BODY]->field_length) { - ret= SP_BODY_TOO_LONG; + my_error(ER_TOO_LONG_BODY, MYF(0), sp->m_name.str); goto done; } @@ -1099,7 +1185,7 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) store_failed= store_failed || table->field[MYSQL_PROC_FIELD_DEFINER]-> - store(definer, (uint)strlen(definer), system_charset_info); + store(definer.str, definer.length, system_charset_info); ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_CREATED])->set_time(); ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_MODIFIED])->set_time(); @@ -1131,17 +1217,13 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) if (access == SP_CONTAINS_SQL || access == SP_MODIFIES_SQL_DATA) { - my_message(ER_BINLOG_UNSAFE_ROUTINE, - ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0)); - ret= SP_INTERNAL_ERROR; + my_error(ER_BINLOG_UNSAFE_ROUTINE, MYF(0)); goto done; } } if (!(thd->security_ctx->master_access & SUPER_ACL)) { - my_message(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER, - ER(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER), MYF(0)); - ret= SP_INTERNAL_ERROR; + my_error(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER,MYF(0)); goto done; } } @@ -1172,62 +1254,70 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) if (store_failed) { - ret= SP_FLD_STORE_FAILED; + my_error(ER_CANT_CREATE_SROUTINE, MYF(0), sp->m_name.str); goto done; } - ret= SP_OK; if (table->file->ha_write_row(table->record[0])) - ret= SP_WRITE_ROW_FAILED; - if (ret == SP_OK) - sp_cache_invalidate(); + { + my_error(ER_SP_ALREADY_EXISTS, MYF(0), + SP_TYPE_STRING(type), sp->m_name.str); + goto done; + } + /* Make change permanent and avoid 'table is marked as crashed' errors */ + table->file->extra(HA_EXTRA_FLUSH); + + sp_cache_invalidate(); + } - if (ret == SP_OK && mysql_bin_log.is_open()) +log: + if (mysql_bin_log.is_open()) + { + thd->clear_error(); + + String log_query; + log_query.set_charset(system_charset_info); + + if (!show_create_sp(thd, &log_query, + sp->m_type, + (sp->m_explicit_name ? sp->m_db.str : NULL), + (sp->m_explicit_name ? sp->m_db.length : 0), + sp->m_name.str, sp->m_name.length, + sp->m_params.str, sp->m_params.length, + retstr.ptr(), retstr.length(), + sp->m_body.str, sp->m_body.length, + sp->m_chistics, &(thd->lex->definer->user), + &(thd->lex->definer->host), + saved_mode)) { - thd->clear_error(); - - String log_query; - log_query.set_charset(system_charset_info); - - if (!create_string(thd, &log_query, - sp->m_type, - (sp->m_explicit_name ? sp->m_db.str : NULL), - (sp->m_explicit_name ? sp->m_db.length : 0), - sp->m_name.str, sp->m_name.length, - sp->m_params.str, sp->m_params.length, - retstr.ptr(), retstr.length(), - sp->m_body.str, sp->m_body.length, - sp->m_chistics, &(thd->lex->definer->user), - &(thd->lex->definer->host), - saved_mode)) - { - ret= SP_INTERNAL_ERROR; - goto done; - } - /* restore sql_mode when binloging */ - thd->variables.sql_mode= saved_mode; - /* Such a statement can always go directly to binlog, no trans cache */ - if (thd->binlog_query(THD::STMT_QUERY_TYPE, - log_query.ptr(), log_query.length(), - FALSE, FALSE, FALSE, 0)) - ret= SP_INTERNAL_ERROR; - thd->variables.sql_mode= 0; + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + goto done; + } + /* restore sql_mode when binloging */ + thd->variables.sql_mode= saved_mode; + /* Such a statement can always go directly to binlog, no trans cache */ + if (thd->binlog_query(THD::STMT_QUERY_TYPE, + log_query.ptr(), log_query.length(), + FALSE, FALSE, FALSE, 0)) + { + my_error(ER_ERROR_ON_WRITE, MYF(MY_WME), "binary log", -1); + goto done; } + thd->variables.sql_mode= 0; } + ret= FALSE; done: thd->count_cuted_fields= saved_count_cuted_fields; thd->variables.sql_mode= saved_mode; - /* Restore the state of binlog format */ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(ret); } /** - Delete the record for the stored routine object from mysql.proc. + Delete the record for the stored routine object from mysql.proc + and do binary logging. The operation deletes the record for the stored routine specified by name from the mysql.proc table and invalidates the stored-routine cache. @@ -1246,7 +1336,6 @@ sp_drop_routine(THD *thd, stored_procedure_type type, sp_name *name) { TABLE *table; int ret; - bool save_binlog_row_based; MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? MDL_key::FUNCTION : MDL_key::PROCEDURE; DBUG_ENTER("sp_drop_routine"); @@ -1263,44 +1352,18 @@ sp_drop_routine(THD *thd, stored_procedure_type type, sp_name *name) if (!(table= open_proc_table_for_update(thd))) DBUG_RETURN(SP_OPEN_TABLE_FAILED); + if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK) + ret= sp_drop_routine_internal(thd, type, name, table); + + if (ret == SP_OK && + write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + ret= SP_INTERNAL_ERROR; /* This statement will be replicated as a statement, even when using row-based replication. The flag will be reset at the end of the statement. */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - - if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK) - { - if (table->file->ha_delete_row(table->record[0])) - ret= SP_DELETE_ROW_FAILED; - } - - if (ret == SP_OK) - { - if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - ret= SP_INTERNAL_ERROR; - sp_cache_invalidate(); - - /* - A lame workaround for lack of cache flush: - make sure the routine is at least gone from the - local cache. - */ - { - sp_head *sp; - sp_cache **spc= (type == TYPE_ENUM_FUNCTION ? - &thd->sp_func_cache : &thd->sp_proc_cache); - sp= sp_cache_lookup(spc, name); - if (sp) - sp_cache_flush_obsolete(spc, &sp); - } - } - /* Restore the state of binlog format */ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(ret); } @@ -1328,7 +1391,6 @@ sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name, { TABLE *table; int ret; - bool save_binlog_row_based; MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? MDL_key::FUNCTION : MDL_key::PROCEDURE; DBUG_ENTER("sp_update_routine"); @@ -1346,14 +1408,6 @@ sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name, if (!(table= open_proc_table_for_update(thd))) DBUG_RETURN(SP_OPEN_TABLE_FAILED); - /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK) { if (type == TYPE_ENUM_FUNCTION && ! trust_function_creators && @@ -1374,14 +1428,13 @@ sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name, if (!is_deterministic) { my_message(ER_BINLOG_UNSAFE_ROUTINE, - ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0)); + ER_THD(thd, ER_BINLOG_UNSAFE_ROUTINE), MYF(0)); ret= SP_INTERNAL_ERROR; goto err; } } store_record(table,record[1]); - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_MODIFIED])->set_time(); if (chistics->suid != SP_IS_DEFAULT_SUID) table->field[MYSQL_PROC_FIELD_SECURITY_TYPE]-> @@ -1398,6 +1451,8 @@ sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name, ret= SP_WRITE_ROW_FAILED; else ret= 0; + /* Make change permanent and avoid 'table is marked as crashed' errors */ + table->file->extra(HA_EXTRA_FLUSH); } if (ret == SP_OK) @@ -1407,10 +1462,7 @@ sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name, sp_cache_invalidate(); } err: - /* Restore the state of binlog format */ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(ret); } @@ -1425,15 +1477,15 @@ public: bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { if (sql_errno == ER_NO_SUCH_TABLE || sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE || - sql_errno == ER_CANNOT_LOAD_FROM_TABLE || + sql_errno == ER_CANNOT_LOAD_FROM_TABLE_V2 || sql_errno == ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE || - sql_errno == ER_COL_COUNT_DOESNT_MATCH_CORRUPTED) + sql_errno == ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2) return true; return false; } @@ -1459,6 +1511,8 @@ bool lock_db_routines(THD *thd, char *db) uchar keybuf[MAX_KEY_LENGTH]; DBUG_ENTER("lock_db_routines"); + DBUG_ASSERT(ok_for_lower_case_names(db)); + /* mysql.proc will be re-opened during deletion, so we can ignore errors when opening the table here. The error handler is @@ -1578,7 +1632,11 @@ sp_drop_db_routines(THD *thd, char *db) if (nxtres != HA_ERR_END_OF_FILE) ret= SP_KEY_NOT_FOUND; if (deleted) + { sp_cache_invalidate(); + /* Make change permanent and avoid 'table is marked as crashed' errors */ + table->file->extra(HA_EXTRA_FLUSH); + } } table->file->ha_index_end(); @@ -1686,7 +1744,6 @@ sp_find_routine(THD *thd, stored_procedure_type type, sp_name *name, ulong level; sp_head *new_sp; const char *returns= ""; - char definer[USER_HOST_BUFF_SIZE]; /* String buffer for RETURNS data type must have system charset; @@ -1723,8 +1780,6 @@ sp_find_routine(THD *thd, stored_procedure_type type, sp_name *name, DBUG_RETURN(0); } - strxmov(definer, sp->m_definer_user.str, "@", - sp->m_definer_host.str, NullS); if (type == TYPE_ENUM_FUNCTION) { sp_returns_type(thd, retstr, sp); @@ -1732,7 +1787,8 @@ sp_find_routine(THD *thd, stored_procedure_type type, sp_name *name, } if (db_load_routine(thd, type, name, &new_sp, sp->m_sql_mode, sp->m_params.str, returns, - sp->m_body.str, *sp->m_chistics, definer, + sp->m_body.str, *sp->m_chistics, + &sp->m_definer_user, &sp->m_definer_host, sp->m_created, sp->m_modified, sp->get_creation_ctx()) == SP_OK) { @@ -1798,7 +1854,7 @@ sp_exist_routines(THD *thd, TABLE_LIST *routines, bool is_proc) sp_find_routine(thd, TYPE_ENUM_FUNCTION, name, &thd->sp_func_cache, FALSE) != NULL; - thd->warning_info->clear_warning_info(thd->query_id); + thd->get_stmt_da()->clear_warning_info(thd->query_id); if (! sp_object_found) { my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "FUNCTION or PROCEDURE", @@ -2145,8 +2201,8 @@ int sp_cache_routine(THD *thd, enum stored_procedure_type type, sp_name *name, @return Returns TRUE on success, FALSE on (alloc) failure. */ -static bool -create_string(THD *thd, String *buf, +bool +show_create_sp(THD *thd, String *buf, stored_procedure_type type, const char *db, ulong dblen, const char *name, ulong namelen, @@ -2167,11 +2223,16 @@ create_string(THD *thd, String *buf, thd->variables.sql_mode= sql_mode; buf->append(STRING_WITH_LEN("CREATE ")); + if (thd->lex->create_info.or_replace()) + buf->append(STRING_WITH_LEN("OR REPLACE ")); append_definer(thd, buf, definer_user, definer_host); if (type == TYPE_ENUM_FUNCTION) buf->append(STRING_WITH_LEN("FUNCTION ")); else buf->append(STRING_WITH_LEN("PROCEDURE ")); + if (thd->lex->create_info.if_not_exists()) + buf->append(STRING_WITH_LEN("IF NOT EXISTS ")); + if (dblen > 0) { append_identifier(thd, buf, db, dblen); @@ -2272,7 +2333,7 @@ sp_load_for_information_schema(THD *thd, TABLE *proc_table, String *db, sp_body= (type == TYPE_ENUM_FUNCTION ? "RETURN NULL" : "BEGIN END"); bzero((char*) &sp_chistics, sizeof(sp_chistics)); defstr.set_charset(creation_ctx->get_client_cs()); - if (!create_string(thd, &defstr, type, + if (!show_create_sp(thd, &defstr, type, sp_db_str.str, sp_db_str.length, sp_name_obj.m_name.str, sp_name_obj.m_name.length, params, strlen(params), @@ -2290,3 +2351,4 @@ sp_load_for_information_schema(THD *thd, TABLE *proc_table, String *db, thd->lex= old_lex; return sp; } + @@ -126,7 +126,7 @@ sp_exist_routines(THD *thd, TABLE_LIST *procs, bool is_proc); bool sp_show_create_routine(THD *thd, stored_procedure_type type, sp_name *name); -int +bool sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp); int @@ -214,4 +214,19 @@ bool load_collation(MEM_ROOT *mem_root, CHARSET_INFO *dflt_cl, CHARSET_INFO **cl); +void sp_returns_type(THD *thd, + String &result, + sp_head *sp); + +bool show_create_sp(THD *thd, String *buf, + stored_procedure_type type, + const char *db, ulong dblen, + const char *name, ulong namelen, + const char *params, ulong paramslen, + const char *returns, ulong returnslen, + const char *body, ulong bodylen, + st_sp_chistics *chistics, + const LEX_STRING *definer_user, + const LEX_STRING *definer_host, + ulonglong sql_mode); #endif /* _SP_H_ */ diff --git a/sql/sp_cache.cc b/sql/sp_cache.cc index 14f49ecc077..bafd0f34ab6 100644 --- a/sql/sp_cache.cc +++ b/sql/sp_cache.cc @@ -13,6 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #ifdef USE_PRAGMA_IMPLEMENTATION diff --git a/sql/sp_head.cc b/sql/sp_head.cc index fec7f51eaf0..852ba453090 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2002, 2016, Oracle and/or its affiliates. - Copyright (c) 2011, 2016, MariaDB + Copyright (c) 2011, 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 @@ -15,7 +15,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "unireg.h" #include "sql_prepare.h" @@ -27,7 +27,7 @@ // prepare_create_field #include "sql_acl.h" // *_ACL #include "sql_array.h" // Dynamic_array -#include "log_event.h" // append_query_string, Query_log_event +#include "log_event.h" // Query_log_event #include "sql_derived.h" // mysql_handle_derived #ifdef USE_PRAGMA_IMPLEMENTATION @@ -42,6 +42,8 @@ #include "sql_parse.h" // cleanup_items #include "sql_base.h" // close_thread_tables #include "transaction.h" // trans_commit_stmt +#include "sql_audit.h" +#include "debug_sync.h" /* Sufficient max length of printed destinations and frame offsets (all uints). @@ -148,18 +150,15 @@ sp_get_item_value(THD *thd, Item *item, String *str) return NULL; { - char buf_holder[STRING_BUFFER_USUAL_SIZE]; - String buf(buf_holder, sizeof(buf_holder), result->charset()); + StringBuffer<STRING_BUFFER_USUAL_SIZE> buf(result->charset()); CHARSET_INFO *cs= thd->variables.character_set_client; - /* We must reset length of the buffer, because of String specificity. */ - buf.length(0); - buf.append('_'); buf.append(result->charset()->csname); if (cs->escape_with_backslash_is_dangerous) buf.append(' '); - append_query_string(thd, cs, result, &buf); + append_query_string(cs, &buf, result->ptr(), result->length(), + thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES); buf.append(" COLLATE '"); buf.append(item->collation.collation->name); buf.append('\''); @@ -176,6 +175,28 @@ sp_get_item_value(THD *thd, Item *item, String *str) } +bool Item_splocal::append_for_log(THD *thd, String *str) +{ + if (fix_fields(thd, NULL)) + return true; + + if (limit_clause_param) + return str->append_ulonglong(val_uint()); + + if (str->append(STRING_WITH_LEN(" NAME_CONST('")) || + str->append(&m_name) || + str->append(STRING_WITH_LEN("',"))) + return true; + + StringBuffer<STRING_BUFFER_USUAL_SIZE> str_value_holder(&my_charset_latin1); + String *str_value= sp_get_item_value(thd, this_item(), &str_value_holder); + if (str_value) + return str->append(*str_value) || str->append(')'); + else + return str->append(STRING_WITH_LEN("NULL)")); +} + + /** Returns a combination of: - sp_head::MULTI_RESULTS: added if the 'cmd' is a command that might @@ -219,8 +240,10 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_SHOW_CREATE_TRIGGER: case SQLCOM_SHOW_DATABASES: case SQLCOM_SHOW_ERRORS: + case SQLCOM_SHOW_EXPLAIN: case SQLCOM_SHOW_FIELDS: case SQLCOM_SHOW_FUNC_CODE: + case SQLCOM_SHOW_GENERIC: case SQLCOM_SHOW_GRANTS: case SQLCOM_SHOW_ENGINE_STATUS: case SQLCOM_SHOW_ENGINE_LOGS: @@ -259,13 +282,13 @@ sp_get_flags_for_command(LEX *lex) flags= sp_head::CONTAINS_DYNAMIC_SQL; break; case SQLCOM_CREATE_TABLE: - if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) + if (lex->tmp_table()) flags= 0; else flags= sp_head::HAS_COMMIT_OR_ROLLBACK; break; case SQLCOM_DROP_TABLE: - if (lex->drop_temporary) + if (lex->tmp_table()) flags= 0; else flags= sp_head::HAS_COMMIT_OR_ROLLBACK; @@ -281,9 +304,12 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_CREATE_VIEW: case SQLCOM_CREATE_TRIGGER: case SQLCOM_CREATE_USER: + case SQLCOM_CREATE_ROLE: case SQLCOM_ALTER_TABLE: case SQLCOM_GRANT: + case SQLCOM_GRANT_ROLE: case SQLCOM_REVOKE: + case SQLCOM_REVOKE_ROLE: case SQLCOM_BEGIN: case SQLCOM_RENAME_TABLE: case SQLCOM_RENAME_USER: @@ -291,6 +317,7 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_DROP_DB: case SQLCOM_REVOKE_ALL: case SQLCOM_DROP_USER: + case SQLCOM_DROP_ROLE: case SQLCOM_DROP_VIEW: case SQLCOM_DROP_TRIGGER: case SQLCOM_TRUNCATE: @@ -311,6 +338,35 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_UNINSTALL_PLUGIN: flags= sp_head::HAS_COMMIT_OR_ROLLBACK; break; + case SQLCOM_DELETE: + case SQLCOM_DELETE_MULTI: + { + /* + DELETE normally doesn't return resultset, but there are 3 exceptions: + - DELETE ... RETURNING + - EXPLAIN DELETE ... + - ANALYZE DELETE ... + */ + if (lex->select_lex.item_list.is_empty() && + !lex->describe && !lex->analyze_stmt) + flags= 0; + else + flags= sp_head::MULTI_RESULTS; + break; + } + case SQLCOM_UPDATE: + case SQLCOM_UPDATE_MULTI: + case SQLCOM_INSERT: + case SQLCOM_REPLACE: + case SQLCOM_REPLACE_SELECT: + case SQLCOM_INSERT_SELECT: + { + if (!lex->describe && !lex->analyze_stmt) + flags= 0; + else + flags= sp_head::MULTI_RESULTS; + break; + } default: flags= 0; break; @@ -385,9 +441,7 @@ sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr) */ thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL; - thd->abort_on_warning= - thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES); + thd->abort_on_warning= thd->is_strict_mode(); thd->transaction.stmt.modified_non_trans_table= FALSE; /* Save the value in the field. Convert the value if needed. */ @@ -461,6 +515,7 @@ sp_name::init_qname(THD *thd) (int) m_db.length, (m_db.length ? m_db.str : ""), dot, ".", (int) m_name.length, m_name.str); + DBUG_ASSERT(ok_for_lower_case_names(m_db.str)); } @@ -508,7 +563,7 @@ sp_head::operator new(size_t size) throw() MEM_ROOT own_root; sp_head *sp; - init_sql_alloc(&own_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); + init_sql_alloc(&own_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC, MYF(0)); sp= (sp_head *) alloc_root(&own_root, size); if (sp == NULL) DBUG_RETURN(NULL); @@ -542,13 +597,12 @@ sp_head::sp_head() :Query_arena(&main_mem_root, STMT_INITIALIZED_FOR_SP), m_flags(0), m_sp_cache_version(0), + m_creation_ctx(0), unsafe_flags(0), m_recursion_level(0), m_next_cached_sp(0), m_cont_level(0) { - const LEX_STRING str_reset= { NULL, 0 }; - m_first_instance= this; m_first_free_instance= this; m_last_cached_sp= this; @@ -559,7 +613,7 @@ sp_head::sp_head() be rewritten soon. Remove the else part and replace 'if' with an assert when this is done. */ - m_db= m_name= m_qname= str_reset; + m_db= m_name= m_qname= null_lex_str; DBUG_ENTER("sp_head::sp_head"); @@ -593,7 +647,7 @@ sp_head::init(LEX *lex) types of stored procedures to simplify reset_lex()/restore_lex() code. */ lex->trg_table_fields.empty(); - my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8); + my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8, MYF(0)); m_param_begin= NULL; m_param_end= NULL; @@ -773,7 +827,7 @@ sp_head::~sp_head() for (uint ip = 0 ; (i = get_instr(ip)) ; ip++) delete i; delete_dynamic(&m_instr); - m_pcont->destroy(); + delete m_pcont; free_items(); /* @@ -818,6 +872,7 @@ sp_head::create_result_field(uint field_max_length, const char *field_name, field_max_length : m_return_field_def.length; field= ::make_field(table->s, /* TABLE_SHARE ptr */ + table->in_use->mem_root, (uchar*) 0, /* field ptr */ field_length, /* field [max] length */ (uchar*) "", /* null ptr */ @@ -825,7 +880,7 @@ sp_head::create_result_field(uint field_max_length, const char *field_name, m_return_field_def.pack_flag, m_return_field_def.sql_type, m_return_field_def.charset, - m_return_field_def.geom_type, + m_return_field_def.geom_type, m_return_field_def.srid, Field::NONE, /* unreg check */ m_return_field_def.interval, field_name ? field_name : (const char *) m_name.str); @@ -839,7 +894,8 @@ sp_head::create_result_field(uint field_max_length, const char *field_name, } -int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b) +int cmp_rqp_locations(Rewritable_query_parameter * const *a, + Rewritable_query_parameter * const *b) { return (int)((*a)->pos_in_query - (*b)->pos_in_query); } @@ -945,85 +1001,32 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) { DBUG_ENTER("subst_spvars"); - Dynamic_array<Item_splocal*> sp_vars_uses; - char *pbuf, *cur, buffer[512]; - String qbuf(buffer, sizeof(buffer), &my_charset_bin); - int prev_pos, res, buf_len; + Dynamic_array<Rewritable_query_parameter*> rewritables; + char *pbuf; + StringBuffer<512> qbuf; + Copy_query_with_rewrite acc(thd, query_str->str, query_str->length, &qbuf); - /* Find all instances of Item_splocal used in this statement */ + /* Find rewritable Items used in this statement */ for (Item *item= instr->free_list; item; item= item->next) { - if (item->is_splocal()) - { - Item_splocal *item_spl= (Item_splocal*)item; - if (item_spl->pos_in_query) - sp_vars_uses.append(item_spl); - } + Rewritable_query_parameter *rqp= item->get_rewritable_query_parameter(); + if (rqp && rqp->pos_in_query) + rewritables.append(rqp); } - if (!sp_vars_uses.elements()) + if (!rewritables.elements()) DBUG_RETURN(FALSE); - /* Sort SP var refs by their occurences in the query */ - sp_vars_uses.sort(cmp_splocal_locations); + rewritables.sort(cmp_rqp_locations); - /* - Construct a statement string where SP local var refs are replaced - with "NAME_CONST(name, value)" - */ - qbuf.length(0); - cur= query_str->str; - prev_pos= res= 0; - thd->query_name_consts= 0; + thd->query_name_consts= rewritables.elements(); - for (Item_splocal **splocal= sp_vars_uses.front(); - splocal < sp_vars_uses.back(); splocal++) + for (Rewritable_query_parameter **rqp= rewritables.front(); + rqp <= rewritables.back(); rqp++) { - Item *val; - - char str_buffer[STRING_BUFFER_USUAL_SIZE]; - String str_value_holder(str_buffer, sizeof(str_buffer), - &my_charset_latin1); - String *str_value; - - /* append the text between sp ref occurences */ - res|= qbuf.append(cur + prev_pos, (*splocal)->pos_in_query - prev_pos); - prev_pos= (*splocal)->pos_in_query + (*splocal)->len_in_query; - - res|= (*splocal)->fix_fields(thd, (Item **) splocal); - if (res) - break; - - if ((*splocal)->limit_clause_param) - { - res|= qbuf.append_ulonglong((*splocal)->val_uint()); - if (res) - break; - continue; - } - - /* append the spvar substitute */ - res|= qbuf.append(STRING_WITH_LEN(" NAME_CONST('")); - res|= qbuf.append((*splocal)->m_name.str, (*splocal)->m_name.length); - res|= qbuf.append(STRING_WITH_LEN("',")); - - if (res) - break; - - val= (*splocal)->this_item(); - DBUG_PRINT("info", ("print 0x%lx", (long) val)); - str_value= sp_get_item_value(thd, val, &str_value_holder); - if (str_value) - res|= qbuf.append(*str_value); - else - res|= qbuf.append(STRING_WITH_LEN("NULL")); - res|= qbuf.append(')'); - if (res) - break; - - thd->query_name_consts++; + if (acc.append(*rqp)) + DBUG_RETURN(TRUE); } - if (res || - qbuf.append(cur + prev_pos, query_str->length - prev_pos)) + if (acc.finalize()) DBUG_RETURN(TRUE); /* @@ -1038,8 +1041,8 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) <db_name> Name of current database <flags> Flags struct */ - buf_len= (qbuf.length() + 1 + QUERY_CACHE_DB_LENGTH_SIZE + thd->db_length + - QUERY_CACHE_FLAGS_SIZE + 1); + int buf_len= (qbuf.length() + 1 + QUERY_CACHE_DB_LENGTH_SIZE + + thd->db_length + QUERY_CACHE_FLAGS_SIZE + 1); if ((pbuf= (char *) alloc_root(thd->mem_root, buf_len))) { char *ptr= pbuf + qbuf.length(); @@ -1079,105 +1082,6 @@ void sp_head::recursion_level_error(THD *thd) } -/** - Find an SQL handler for any condition (warning or error) after execution - of a stored routine instruction. Basically, this function looks for an - appropriate SQL handler in RT-contexts. If an SQL handler is found, it is - remembered in the RT-context for future activation (the context can be - inactive at the moment). - - If there is no pending condition, the function just returns. - - If there was an error during the execution, an SQL handler for it will be - searched within the current and outer scopes. - - There might be several errors in the Warning Info (that's possible by using - SIGNAL/RESIGNAL in nested scopes) -- the function is looking for an SQL - handler for the latest (current) error only. - - If there was a warning during the execution, an SQL handler for it will be - searched within the current scope only. - - If several warnings were thrown during the execution and there are different - SQL handlers for them, it is not determined which SQL handler will be chosen. - Only one SQL handler will be executed. - - If warnings and errors were thrown during the execution, the error takes - precedence. I.e. error handler will be executed. If there is no handler - for that error, condition will remain unhandled. - - Once a warning or an error has been handled it is not removed from - Warning Info. - - According to The Standard (quoting PeterG): - - An SQL procedure statement works like this ... - SQL/Foundation 13.5 <SQL procedure statement> - (General Rules) (greatly summarized) says: - (1) Empty diagnostics area, thus clearing the condition. - (2) Execute statement. - During execution, if Exception Condition occurs, - set Condition Area = Exception Condition and stop - statement. - During execution, if No Data occurs, - set Condition Area = No Data Condition and continue - statement. - During execution, if Warning occurs, - and Condition Area is not already full due to - an earlier No Data condition, set Condition Area - = Warning and continue statement. - (3) Finish statement. - At end of execution, if Condition Area is not - already full due to an earlier No Data or Warning, - set Condition Area = Successful Completion. - In effect, this system means there is a precedence: - Exception trumps No Data, No Data trumps Warning, - Warning trumps Successful Completion. - - NB: "Procedure statements" include any DDL or DML or - control statements. So CREATE and DELETE and WHILE - and CALL and RETURN are procedure statements. But - DECLARE and END are not procedure statements. - - @param thd thread handle - @param ctx runtime context of the stored routine -*/ - -static void -find_handler_after_execution(THD *thd, sp_rcontext *ctx) -{ - if (thd->is_error()) - { - ctx->find_handler(thd, - thd->stmt_da->sql_errno(), - thd->stmt_da->get_sqlstate(), - MYSQL_ERROR::WARN_LEVEL_ERROR, - thd->stmt_da->message()); - } - else if (thd->warning_info->statement_warn_count()) - { - List_iterator<MYSQL_ERROR> it(thd->warning_info->warn_list()); - MYSQL_ERROR *err; - while ((err= it++)) - { - if ((err->get_level() != MYSQL_ERROR::WARN_LEVEL_WARN && - err->get_level() != MYSQL_ERROR::WARN_LEVEL_NOTE) || - err->handled()) - continue; - - if (ctx->find_handler(thd, - err->get_sql_errno(), - err->get_sqlstate(), - err->get_level(), - err->get_message_text())) - { - err->mark_handled(); - break; - } - } - } -} - /** Execute the routine. The main instruction jump loop is there. @@ -1226,39 +1130,16 @@ sp_head::execute(THD *thd, bool merge_da_on_success) const uint status_backup_mask= SERVER_STATUS_CURSOR_EXISTS | SERVER_STATUS_LAST_ROW_SENT; Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; - Object_creation_ctx *saved_creation_ctx; - Warning_info *saved_warning_info; - Warning_info warning_info(thd->warning_info->warn_id(), false); + Object_creation_ctx *UNINIT_VAR(saved_creation_ctx); + Diagnostics_area *da= thd->get_stmt_da(); + Warning_info sp_wi(da->warning_info_id(), false, true); - /* - Just reporting a stack overrun error - (@sa check_stack_overrun()) requires stack memory for error - message buffer. Thus, we have to put the below check - relatively close to the beginning of the execution stack, - where available stack margin is still big. As long as the check - has to be fairly high up the call stack, the amount of memory - we "book" for has to stay fairly high as well, and hence - not very accurate. The number below has been calculated - by trial and error, and reflects the amount of memory necessary - to execute a single stored procedure instruction, be it either - an SQL statement, or, heaviest of all, a CALL, which involves - parsing and loading of another stored procedure into the cache - (@sa db_load_routine() and Bug#10100). - At the time of measuring, a recursive SP invocation required - 3232 bytes of stack on 32 bit Linux, 6016 bytes on 64 bit Mac - and 11152 on 64 bit Solaris sparc. - The same with db_load_routine() required circa 7k bytes and - 14k bytes accordingly. Hence, here we book the stack with some - reasonable margin. - - Reverting back to 8 * STACK_MIN_SIZE until further fix. - 8 * STACK_MIN_SIZE is required on some exotic platforms. - */ - if (check_stack_overrun(thd, 8 * STACK_MIN_SIZE, (uchar*)&old_packet)) + /* this 7*STACK_MIN_SIZE is a complex matter with a long history (see it!) */ + if (check_stack_overrun(thd, 7 * STACK_MIN_SIZE, (uchar*)&old_packet)) DBUG_RETURN(TRUE); /* init per-instruction memroot */ - init_sql_alloc(&execute_mem_root, MEM_ROOT_BLOCK_SIZE, 0); + init_sql_alloc(&execute_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0)); DBUG_ASSERT(!(m_flags & IS_INVOKED)); m_flags|= IS_INVOKED; @@ -1299,15 +1180,15 @@ sp_head::execute(THD *thd, bool merge_da_on_success) old_arena= thd->stmt_arena; /* Push a new warning information area. */ - warning_info.append_warning_info(thd, thd->warning_info); - saved_warning_info= thd->warning_info; - thd->warning_info= &warning_info; + da->copy_sql_conditions_to_wi(thd, &sp_wi); + da->push_warning_info(&sp_wi); /* Switch query context. This has to be done early as this is sometimes allocated trough sql_alloc */ - saved_creation_ctx= m_creation_ctx->set_n_backup(thd); + if (m_creation_ctx) + saved_creation_ctx= m_creation_ctx->set_n_backup(thd); /* We have to save/restore this info when we are changing call level to @@ -1377,6 +1258,7 @@ sp_head::execute(THD *thd, bool merge_da_on_success) /* Discard the initial part of executing routines. */ thd->profiling.discard_current_query(); #endif + DEBUG_SYNC(thd, "sp_head_execute_before_loop"); do { sp_instr *i; @@ -1402,7 +1284,7 @@ sp_head::execute(THD *thd, bool merge_da_on_success) } /* Reset number of warnings for this query. */ - thd->warning_info->reset_for_next_command(); + thd->get_stmt_da()->reset_for_next_command(); DBUG_PRINT("execute", ("Instruction %u", ip)); @@ -1429,8 +1311,13 @@ sp_head::execute(THD *thd, bool merge_da_on_success) if (thd->locked_tables_mode <= LTM_LOCK_TABLES) thd->user_var_events_alloc= thd->mem_root; + sql_digest_state *parent_digest= thd->m_digest; + thd->m_digest= NULL; + err_status= i->execute(thd, &ip); + thd->m_digest= parent_digest; + if (i->free_list) cleanup_items(i->free_list); @@ -1453,19 +1340,10 @@ sp_head::execute(THD *thd, bool merge_da_on_success) errors are not catchable by SQL handlers) or the connection has been killed during execution. */ - if (!thd->is_fatal_error && !thd->killed_errno()) + if (!thd->is_fatal_error && !thd->killed_errno() && + ctx->handle_sql_condition(thd, &ip, i)) { - /* - Find SQL handler in the appropriate RT-contexts: - - warnings can be handled by SQL handlers within - the current scope only; - - errors can be handled by any SQL handler from outer scope. - */ - find_handler_after_execution(thd, ctx); - - /* If found, activate handler for the current scope. */ - if (ctx->activate_handler(thd, &ip, i, &execute_arena, &backup_arena)) - err_status= FALSE; + err_status= FALSE; } /* Reset sp_rcontext::end_partial_result_set flag. */ @@ -1480,7 +1358,8 @@ sp_head::execute(THD *thd, bool merge_da_on_success) /* Restore query context. */ - m_creation_ctx->restore_env(thd, saved_creation_ctx); + if (m_creation_ctx) + m_creation_ctx->restore_env(thd, saved_creation_ctx); /* Restore arena. */ @@ -1511,9 +1390,40 @@ sp_head::execute(THD *thd, bool merge_da_on_success) - if there was an exception during execution, warning info should be propagated to the caller in any case. */ + da->pop_warning_info(); + if (err_status || merge_da_on_success) - saved_warning_info->merge_with_routine_info(thd, thd->warning_info); - thd->warning_info= saved_warning_info; + { + /* + If a routine body is empty or if a routine did not generate any warnings, + do not duplicate our own contents by appending the contents of the called + routine. We know that the called routine did not change its warning info. + + On the other hand, if the routine body is not empty and some statement in + the routine generates a warning or uses tables, warning info is guaranteed + to have changed. In this case we know that the routine warning info + contains only new warnings, and thus we perform a copy. + */ + if (da->warning_info_changed(&sp_wi)) + { + /* + If the invocation of the routine was a standalone statement, + rather than a sub-statement, in other words, if it's a CALL + of a procedure, rather than invocation of a function or a + trigger, we need to clear the current contents of the caller's + warning info. + + This is per MySQL rules: if a statement generates a warning, + warnings from the previous statement are flushed. Normally + it's done in push_warning(). However, here we don't use + push_warning() to avoid invocation of condition handlers or + escalation of warnings to errors. + */ + da->opt_clear_warning_info(thd->query_id); + da->copy_sql_conditions_from_wi(thd, &sp_wi); + da->remove_marked_sql_conditions(); + } + } done: DBUG_PRINT("info", ("err_status: %d killed: %d is_slave_error: %d report_error: %d", @@ -1718,11 +1628,10 @@ sp_head::execute_trigger(THD *thd, TODO: we should create sp_rcontext once per command and reuse it on subsequent executions of a trigger. */ - init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0); + init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0)); thd->set_n_backup_active_arena(&call_arena, &backup_arena); - if (!(nctx= new sp_rcontext(m_pcont, 0, octx)) || - nctx->init(thd)) + if (!(nctx= sp_rcontext::create(thd, m_pcont, NULL))) { err_status= TRUE; goto err_with_cleanup; @@ -1792,7 +1701,7 @@ bool sp_head::execute_function(THD *thd, Item **argp, uint argcount, Field *return_value_fld) { - ulonglong binlog_save_options; + ulonglong UNINIT_VAR(binlog_save_options); bool need_binlog_call= FALSE; uint arg_no; sp_rcontext *octx = thd->spcont; @@ -1806,8 +1715,6 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, DBUG_ENTER("sp_head::execute_function"); DBUG_PRINT("info", ("function %s", m_name.str)); - LINT_INIT(binlog_save_options); - /* Check that the function is called with all specified arguments. @@ -1835,11 +1742,10 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, TODO: we should create sp_rcontext once per command and reuse it on subsequent executions of a function/trigger. */ - init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0); + init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0)); thd->set_n_backup_active_arena(&call_arena, &backup_arena); - if (!(nctx= new sp_rcontext(m_pcont, return_value_fld, octx)) || - nctx->init(thd)) + if (!(nctx= sp_rcontext::create(thd, m_pcont, return_value_fld))) { thd->restore_active_arena(&call_arena, &backup_arena); err_status= TRUE; @@ -1933,9 +1839,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, as one select and not resetting THD::user_var_events before each invocation. */ - mysql_mutex_lock(&LOCK_thread_count); - q= global_query_id; - mysql_mutex_unlock(&LOCK_thread_count); + q= get_query_id(); mysql_bin_log.start_union_events(thd, q + 1); binlog_save_options= thd->variables.option_bits; thd->variables.option_bits&= ~OPTION_BIN_LOG; @@ -1967,7 +1871,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, if (mysql_bin_log.write(&qinfo) && thd->binlog_evt_union.unioned_events_trans) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Invoked ROUTINE modified a transactional table but MySQL " "failed to reflect this change in the binary log"); err_status= TRUE; @@ -2056,9 +1960,9 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (! octx) { /* Create a temporary old context. */ - if (!(octx= new sp_rcontext(m_pcont, NULL, octx)) || octx->init(thd)) + if (!(octx= sp_rcontext::create(thd, m_pcont, NULL))) { - delete octx; /* Delete octx if it was init() that failed. */ + DBUG_PRINT("error", ("Could not create octx")); DBUG_RETURN(TRUE); } @@ -2071,8 +1975,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) thd->spcont->callers_arena= thd; } - if (!(nctx= new sp_rcontext(m_pcont, NULL, octx)) || - nctx->init(thd)) + if (!(nctx= sp_rcontext::create(thd, m_pcont, NULL))) { delete nctx; /* Delete nctx if it was init() that failed. */ thd->spcont= save_spcont; @@ -2095,12 +1998,12 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (!arg_item) break; - sp_variable_t *spvar= m_pcont->find_variable(i); + sp_variable *spvar= m_pcont->find_variable(i); if (!spvar) continue; - if (spvar->mode != sp_param_in) + if (spvar->mode != sp_variable::MODE_IN) { Settable_routine_parameter *srp= arg_item->get_settable_routine_parameter(); @@ -2112,17 +2015,18 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) break; } - srp->set_required_privilege(spvar->mode == sp_param_inout); + srp->set_required_privilege(spvar->mode == sp_variable::MODE_INOUT); } - if (spvar->mode == sp_param_out) + if (spvar->mode == sp_variable::MODE_OUT) { - Item_null *null_item= new Item_null(); + Item_null *null_item= new (thd->mem_root) Item_null(thd); Item *tmp_item= null_item; if (!null_item || nctx->set_variable(thd, i, &tmp_item)) { + DBUG_PRINT("error", ("set variable failed")); err_status= TRUE; break; } @@ -2131,6 +2035,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) { if (nctx->set_variable(thd, i, it_args.ref())) { + DBUG_PRINT("error", ("set variable 2 failed")); err_status= TRUE; break; } @@ -2146,9 +2051,9 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (!thd->in_sub_stmt) { - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); } thd_proc_info(thd, "closing tables"); @@ -2195,7 +2100,9 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) #endif if (!err_status) + { err_status= execute(thd, TRUE); + } if (save_log_general) thd->variables.option_bits &= ~OPTION_LOG_OFF; @@ -2223,9 +2130,9 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (!arg_item) break; - sp_variable_t *spvar= m_pcont->find_variable(i); + sp_variable *spvar= m_pcont->find_variable(i); - if (spvar->mode == sp_param_in) + if (spvar->mode == sp_variable::MODE_IN) continue; Settable_routine_parameter *srp= @@ -2235,6 +2142,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (srp->set_value(thd, octx, nctx->get_item_addr(i))) { + DBUG_PRINT("error", ("set value failed")); err_status= TRUE; break; } @@ -2313,16 +2221,6 @@ sp_head::reset_lex(THD *thd) sublex->trg_table_fields.empty(); sublex->sp_lex_in_use= FALSE; - /* Reset type info. */ - - sublex->charset= NULL; - sublex->length= NULL; - sublex->dec= NULL; - sublex->interval_list.empty(); - sublex->type= 0; - sublex->uint_geom_type= 0; - sublex->vcol_info= 0; - /* Reset part of parser state which needs this. */ thd->m_parser_state->m_yacc.reset_before_substatement(); @@ -2366,11 +2264,19 @@ sp_head::restore_lex(THD *thd) */ if (sp_update_sp_used_routines(&m_sroutines, &sublex->sroutines)) DBUG_RETURN(TRUE); + + /* If this substatement is a update query, then mark MODIFIES_DATA */ + if (is_update_query(sublex->sql_command)) + m_flags|= MODIFIES_DATA; + /* Merge tables used by this statement (but not by its functions or procedures) to multiset of tables used by this routine. */ merge_table_list(thd, sublex->query_tables, sublex); + /* Merge lists of PS parameters. */ + oldlex->param_list.append(&sublex->param_list); + if (! sublex->sp_lex_in_use) { sublex->sphead= NULL; @@ -2385,7 +2291,7 @@ sp_head::restore_lex(THD *thd) Put the instruction on the backpatch list, associated with the label. */ int -sp_head::push_backpatch(sp_instr *i, sp_label_t *lab) +sp_head::push_backpatch(sp_instr *i, sp_label *lab) { bp_t *bp= (bp_t *)sql_alloc(sizeof(bp_t)); @@ -2401,7 +2307,7 @@ sp_head::push_backpatch(sp_instr *i, sp_label_t *lab) the current position. */ void -sp_head::backpatch(sp_label_t *lab) +sp_head::backpatch(sp_label *lab) { bp_t *bp; uint dest= instructions(); @@ -2413,7 +2319,7 @@ sp_head::backpatch(sp_label_t *lab) if (bp->lab == lab) { DBUG_PRINT("info", ("backpatch: (m_ip %d, label 0x%lx <%s>) to dest %d", - bp->instr->m_ip, (ulong) lab, lab->name, dest)); + bp->instr->m_ip, (ulong) lab, lab->name.str, dest)); bp->instr->backpatch(dest, lab->ctx); } } @@ -2440,17 +2346,9 @@ sp_head::fill_field_definition(THD *thd, LEX *lex, enum enum_field_types field_type, Create_field *field_def) { - LEX_STRING cmt = { 0, 0 }; uint unused1= 0; - int unused2= 0; - - if (field_def->init(thd, (char*) "", field_type, lex->length, lex->dec, - lex->type, (Item*) 0, (Item*) 0, &cmt, 0, - &lex->interval_list, - lex->charset ? lex->charset : - thd->variables.collation_database, - lex->uint_geom_type, - lex->vcol_info, NULL)) + + if (field_def->check(thd)) return TRUE; if (field_def->interval_list.elements) @@ -2459,8 +2357,7 @@ sp_head::fill_field_definition(THD *thd, LEX *lex, sp_prepare_create_field(thd, field_def); - if (prepare_create_field(field_def, &unused1, &unused2, &unused2, - HA_CAN_GEOMETRY)) + if (prepare_create_field(field_def, &unused1, HA_CAN_GEOMETRY)) { return TRUE; } @@ -2531,8 +2428,13 @@ sp_head::set_definer(const char *definer, uint definerlen) char host_name_holder[HOSTNAME_LENGTH + 1]; LEX_STRING host_name= { host_name_holder, HOSTNAME_LENGTH }; - parse_user(definer, definerlen, user_name.str, &user_name.length, - host_name.str, &host_name.length); + if (parse_user(definer, definerlen, user_name.str, &user_name.length, + host_name.str, &host_name.length) && + user_name.length && !host_name.length) + { + // 'user@' -> 'user@%' + host_name= host_not_specified; + } set_definer(&user_name, &host_name); } @@ -2617,10 +2519,18 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) *full_access= ((!check_table_access(thd, SELECT_ACL, &tables, FALSE, 1, TRUE) && (tables.grant.privilege & SELECT_ACL) != 0) || + /* Check if user owns the routine. */ (!strcmp(sp->m_definer_user.str, thd->security_ctx->priv_user) && !strcmp(sp->m_definer_host.str, - thd->security_ctx->priv_host))); + thd->security_ctx->priv_host)) || + /* Check if current role or any of the sub-granted roles + own the routine. */ + (sp->m_definer_host.length == 0 && + (!strcmp(sp->m_definer_user.str, + thd->security_ctx->priv_role) || + check_role_is_granted(thd->security_ctx->priv_role, NULL, + sp->m_definer_user.str)))); if (!*full_access) return check_some_routine_access(thd, sp->m_db.str, sp->m_name.str, sp->m_type == TYPE_ENUM_PROCEDURE); @@ -2629,6 +2539,69 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) /** + Collect metadata for SHOW CREATE statement for stored routines. + + @param thd Thread context. + @param type Stored routine type + @param type Stored routine type + (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION) + + @return Error status. + @retval FALSE on success + @retval TRUE on error +*/ + +void +sp_head::show_create_routine_get_fields(THD *thd, int type, List<Item> *fields) +{ + const char *col1_caption= type == TYPE_ENUM_PROCEDURE ? + "Procedure" : "Function"; + + const char *col3_caption= type == TYPE_ENUM_PROCEDURE ? + "Create Procedure" : "Create Function"; + + MEM_ROOT *mem_root= thd->mem_root; + + /* Send header. */ + + fields->push_back(new (mem_root) + Item_empty_string(thd, col1_caption, NAME_CHAR_LEN), + mem_root); + fields->push_back(new (mem_root) + Item_empty_string(thd, "sql_mode", 256), + mem_root); + + { + /* + NOTE: SQL statement field must be not less than 1024 in order not to + confuse old clients. + */ + + Item_empty_string *stmt_fld= + new (mem_root) Item_empty_string(thd, col3_caption, 1024); + stmt_fld->maybe_null= TRUE; + + fields->push_back(stmt_fld, mem_root); + } + + fields->push_back(new (mem_root) + Item_empty_string(thd, "character_set_client", + MY_CS_NAME_SIZE), + mem_root); + + fields->push_back(new (mem_root) + Item_empty_string(thd, "collation_connection", + MY_CS_NAME_SIZE), + mem_root); + + fields->push_back(new (mem_root) + Item_empty_string(thd, "Database Collation", + MY_CS_NAME_SIZE), + mem_root); +} + + +/** Implement SHOW CREATE statement for stored routines. @param thd Thread context. @@ -2657,6 +2630,7 @@ sp_head::show_create_routine(THD *thd, int type) LEX_STRING sql_mode; bool full_access; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("sp_head::show_create_routine"); DBUG_PRINT("info", ("routine %s", m_name.str)); @@ -2671,8 +2645,12 @@ sp_head::show_create_routine(THD *thd, int type) /* Send header. */ - fields.push_back(new Item_empty_string(col1_caption, NAME_CHAR_LEN)); - fields.push_back(new Item_empty_string("sql_mode", sql_mode.length)); + fields.push_back(new (mem_root) + Item_empty_string(thd, col1_caption, NAME_CHAR_LEN), + thd->mem_root); + fields.push_back(new (mem_root) + Item_empty_string(thd, "sql_mode", sql_mode.length), + thd->mem_root); { /* @@ -2681,22 +2659,28 @@ sp_head::show_create_routine(THD *thd, int type) */ Item_empty_string *stmt_fld= - new Item_empty_string(col3_caption, - max(m_defstr.length, 1024)); + new (mem_root) Item_empty_string(thd, col3_caption, + MY_MAX(m_defstr.length, 1024)); stmt_fld->maybe_null= TRUE; - fields.push_back(stmt_fld); + fields.push_back(stmt_fld, thd->mem_root); } - fields.push_back(new Item_empty_string("character_set_client", - MY_CS_NAME_SIZE)); + fields.push_back(new (mem_root) + Item_empty_string(thd, "character_set_client", + MY_CS_NAME_SIZE), + thd->mem_root); - fields.push_back(new Item_empty_string("collation_connection", - MY_CS_NAME_SIZE)); + fields.push_back(new (mem_root) + Item_empty_string(thd, "collation_connection", + MY_CS_NAME_SIZE), + thd->mem_root); - fields.push_back(new Item_empty_string("Database Collation", - MY_CS_NAME_SIZE)); + fields.push_back(new (mem_root) + Item_empty_string(thd, "Database Collation", + MY_CS_NAME_SIZE), + thd->mem_root); if (protocol->send_result_set_metadata(&fields, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) @@ -2879,10 +2863,13 @@ sp_head::show_routine_code(THD *thd) if (check_show_routine_access(thd, this, &full_access) || !full_access) DBUG_RETURN(1); - field_list.push_back(new Item_uint("Pos", 9)); + field_list.push_back(new (thd->mem_root) Item_uint(thd, "Pos", 9), + thd->mem_root); // 1024 is for not to confuse old clients - field_list.push_back(new Item_empty_string("Instruction", - max(buffer.length(), 1024))); + field_list.push_back(new (thd->mem_root) + Item_empty_string(thd, "Instruction", + MY_MAX(buffer.length(), 1024)), + thd->mem_root); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(1); @@ -2898,12 +2885,12 @@ sp_head::show_routine_code(THD *thd) const char *format= "Instruction at position %u has m_ip=%u"; char tmp[sizeof(format) + 2*SP_INSTR_UINT_MAXLEN + 1]; - sprintf(tmp, format, ip, i->m_ip); + my_snprintf(tmp, sizeof(tmp), format, ip, i->m_ip); /* Since this is for debugging purposes only, we don't bother to introduce a special error code for it. */ - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, tmp); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, tmp); } protocol->prepare_for_resend(); protocol->store((longlong)ip); @@ -3010,9 +2997,9 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, /* Here we also commit or rollback the current statement. */ if (! thd->in_sub_stmt) { - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); } thd_proc_info(thd, "closing tables"); close_thread_tables(thd); @@ -3031,6 +3018,8 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, thd->mdl_context.release_statement_locks(); } } + //TODO: why is this here if log_slow_query is in sp_instr_stmt_execute? + delete_explain_query(m_lex); if (m_lex->query_tables_own_last) { @@ -3054,10 +3043,10 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, open_tables stage. */ if (!res || !thd->is_error() || - (thd->stmt_da->sql_errno() != ER_CANT_REOPEN_TABLE && - thd->stmt_da->sql_errno() != ER_NO_SUCH_TABLE && - thd->stmt_da->sql_errno() != ER_NO_SUCH_TABLE_IN_ENGINE && - thd->stmt_da->sql_errno() != ER_UPDATE_TABLE_USED)) + (thd->get_stmt_da()->sql_errno() != ER_CANT_REOPEN_TABLE && + thd->get_stmt_da()->sql_errno() != ER_NO_SUCH_TABLE && + thd->get_stmt_da()->sql_errno() != ER_NO_SUCH_TABLE_IN_ENGINE && + thd->get_stmt_da()->sql_errno() != ER_UPDATE_TABLE_USED)) thd->stmt_arena->state= Query_arena::STMT_EXECUTED; /* @@ -3074,6 +3063,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, cleanup_items() is called in sp_head::execute() */ + thd->lex->restore_set_statement_var(); DBUG_RETURN(res || thd->is_error()); } @@ -3090,7 +3080,8 @@ int sp_instr::exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables) Check whenever we have access to tables for this statement and open and lock them before executing instructions core function. */ - if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE) + if (open_temporary_tables(thd, tables) || + check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE) || open_and_lock_tables(thd, tables, TRUE, 0)) result= -1; else @@ -3102,7 +3093,7 @@ int sp_instr::exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables) return result; } -uint sp_instr::get_cont_dest() +uint sp_instr::get_cont_dest() const { return (m_ip+1); } @@ -3146,14 +3137,19 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) bool log_slow= !res && thd->enable_slow_log; /* Finalize server status flags after executing a statement. */ - if (log_slow || thd->stmt_da->is_eof()) + if (log_slow || thd->get_stmt_da()->is_eof()) thd->update_server_status(); - if (thd->stmt_da->is_eof()) + if (thd->get_stmt_da()->is_eof()) thd->protocol->end_statement(); query_cache_end_of_result(thd); + mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS, + thd->get_stmt_da()->is_error() ? + thd->get_stmt_da()->sql_errno() : 0, + command_name[COM_QUERY].str); + if (log_slow) log_slow_statement(thd); } @@ -3171,7 +3167,10 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) thd->query_name_consts= 0; if (!thd->is_error()) - thd->stmt_da->reset_diagnostics_area(); + { + res= 0; + thd->get_stmt_da()->reset_diagnostics_area(); + } } DBUG_RETURN(res || thd->is_error()); } @@ -3254,6 +3253,7 @@ sp_instr_set::exec_core(THD *thd, uint *nextp) my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); } } + delete_explain_query(thd->lex); *nextp = m_ip+1; return res; @@ -3264,7 +3264,7 @@ sp_instr_set::print(String *str) { /* set name@offset ... */ int rsrv = SP_INSTR_UINT_MAXLEN+6; - sp_variable_t *var = m_ctx->find_variable(m_offset); + sp_variable *var = m_ctx->find_variable(m_offset); /* 'var' should always be non-null, but just in case... */ if (var) @@ -3279,7 +3279,8 @@ sp_instr_set::print(String *str) } str->qs_append(m_offset); str->qs_append(' '); - m_value->print(str, QT_ORDINARY); + m_value->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); } @@ -3299,7 +3300,10 @@ sp_instr_set_trigger_field::execute(THD *thd, uint *nextp) int sp_instr_set_trigger_field::exec_core(THD *thd, uint *nextp) { + bool sav_abort_on_warning= thd->abort_on_warning; + thd->abort_on_warning= thd->is_strict_mode() && !thd->lex->ignore; const int res= (trigger_field->set_value(thd, &value) ? -1 : 0); + thd->abort_on_warning= sav_abort_on_warning; *nextp = m_ip+1; return res; } @@ -3308,16 +3312,18 @@ void sp_instr_set_trigger_field::print(String *str) { str->append(STRING_WITH_LEN("set_trigger_field ")); - trigger_field->print(str, QT_ORDINARY); + trigger_field->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); str->append(STRING_WITH_LEN(":=")); - value->print(str, QT_ORDINARY); + value->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); } /* sp_instr_opt_meta */ -uint sp_instr_opt_meta::get_cont_dest() +uint sp_instr_opt_meta::get_cont_dest() const { return m_cont_dest; } @@ -3436,7 +3442,8 @@ sp_instr_jump_if_not::print(String *str) str->qs_append('('); str->qs_append(m_cont_dest); str->qs_append(STRING_WITH_LEN(") ")); - m_expr->print(str, QT_ORDINARY); + m_expr->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); } @@ -3498,6 +3505,14 @@ int sp_instr_freturn::exec_core(THD *thd, uint *nextp) { /* + RETURN is a "procedure statement" (in terms of the SQL standard). + That means, Diagnostics Area should be clean before its execution. + */ + + Diagnostics_area *da= thd->get_stmt_da(); + da->clear_warning_info(da->warning_info_id()); + + /* Change <next instruction pointer>, so that this will be the last instruction in the stored function. */ @@ -3524,7 +3539,8 @@ sp_instr_freturn::print(String *str) str->qs_append(STRING_WITH_LEN("freturn ")); str->qs_append((uint)m_type); str->qs_append(' '); - m_value->print(str, QT_ORDINARY); + m_value->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); } /* @@ -3535,14 +3551,12 @@ int sp_instr_hpush_jump::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_hpush_jump::execute"); - List_iterator_fast<sp_cond_type_t> li(m_cond); - sp_cond_type_t *p; - while ((p= li++)) - thd->spcont->push_handler(p, m_ip+1, m_type); + int ret= thd->spcont->push_handler(m_handler, m_ip + 1); *nextp= m_dest; - DBUG_RETURN(0); + + DBUG_RETURN(ret); } @@ -3552,27 +3566,22 @@ sp_instr_hpush_jump::print(String *str) /* hpush_jump dest fsize type */ if (str->reserve(SP_INSTR_UINT_MAXLEN*2 + 21)) return; + str->qs_append(STRING_WITH_LEN("hpush_jump ")); str->qs_append(m_dest); str->qs_append(' '); str->qs_append(m_frame); - switch (m_type) { - case SP_HANDLER_NONE: - str->qs_append(STRING_WITH_LEN(" NONE")); // This would be a bug - break; - case SP_HANDLER_EXIT: + + switch (m_handler->type) { + case sp_handler::EXIT: str->qs_append(STRING_WITH_LEN(" EXIT")); break; - case SP_HANDLER_CONTINUE: + case sp_handler::CONTINUE: str->qs_append(STRING_WITH_LEN(" CONTINUE")); break; - case SP_HANDLER_UNDO: - str->qs_append(STRING_WITH_LEN(" UNDO")); - break; default: - // This would be a bug as well - str->qs_append(STRING_WITH_LEN(" UNKNOWN:")); - str->qs_append(m_type); + // The handler type must be either CONTINUE or EXIT. + DBUG_ASSERT(0); } } @@ -3600,7 +3609,7 @@ sp_instr_hpush_jump::opt_mark(sp_head *sp, List<sp_instr> *leads) above, so we start on m_dest+1 here. m_opt_hpop is the hpop marking the end of the handler scope. */ - if (m_type == SP_HANDLER_CONTINUE) + if (m_handler->type == sp_handler::CONTINUE) { for (uint scope_ip= m_dest+1; scope_ip <= m_opt_hpop; scope_ip++) sp->add_mark_lead(scope_ip, leads); @@ -3642,13 +3651,11 @@ int sp_instr_hreturn::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_hreturn::execute"); - if (m_dest) - *nextp= m_dest; - else - { - *nextp= thd->spcont->pop_hstack(); - } - thd->spcont->exit_handler(); + + uint continue_ip= thd->spcont->exit_handler(thd->get_stmt_da()); + + *nextp= m_dest ? m_dest : continue_ip; + DBUG_RETURN(0); } @@ -3660,12 +3667,17 @@ sp_instr_hreturn::print(String *str) if (str->reserve(SP_INSTR_UINT_MAXLEN*2 + 9)) return; str->qs_append(STRING_WITH_LEN("hreturn ")); - str->qs_append(m_frame); if (m_dest) { - str->qs_append(' '); + // NOTE: this is legacy: hreturn instruction for EXIT handler + // should print out 0 as frame index. + str->qs_append(STRING_WITH_LEN("0 ")); str->qs_append(m_dest); } + else + { + str->qs_append(m_frame); + } } @@ -3697,41 +3709,32 @@ sp_instr_hreturn::opt_mark(sp_head *sp, List<sp_instr> *leads) int sp_instr_cpush::execute(THD *thd, uint *nextp) { - Query_arena backup_arena; DBUG_ENTER("sp_instr_cpush::execute"); - /* - We should create cursors in the callers arena, as - it could be (and usually is) used in several instructions. - */ - thd->set_n_backup_active_arena(thd->spcont->callers_arena, &backup_arena); - - thd->spcont->push_cursor(&m_lex_keeper, this); - - thd->restore_active_arena(thd->spcont->callers_arena, &backup_arena); + int ret= thd->spcont->push_cursor(thd, &m_lex_keeper, this); *nextp= m_ip+1; - DBUG_RETURN(0); + DBUG_RETURN(ret); } void sp_instr_cpush::print(String *str) { - LEX_STRING n; - my_bool found= m_ctx->find_cursor(m_cursor, &n); + const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor); + /* cpush name@offset */ uint rsrv= SP_INSTR_UINT_MAXLEN+7; - if (found) - rsrv+= n.length; + if (cursor_name) + rsrv+= cursor_name->length; if (str->reserve(rsrv)) return; str->qs_append(STRING_WITH_LEN("cpush ")); - if (found) + if (cursor_name) { - str->qs_append(n.str, n.length); + str->qs_append(cursor_name->str, cursor_name->length); str->qs_append('@'); } str->qs_append(m_cursor); @@ -3819,19 +3822,19 @@ sp_instr_copen::exec_core(THD *thd, uint *nextp) void sp_instr_copen::print(String *str) { - LEX_STRING n; - my_bool found= m_ctx->find_cursor(m_cursor, &n); + const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor); + /* copen name@offset */ uint rsrv= SP_INSTR_UINT_MAXLEN+7; - if (found) - rsrv+= n.length; + if (cursor_name) + rsrv+= cursor_name->length; if (str->reserve(rsrv)) return; str->qs_append(STRING_WITH_LEN("copen ")); - if (found) + if (cursor_name) { - str->qs_append(n.str, n.length); + str->qs_append(cursor_name->str, cursor_name->length); str->qs_append('@'); } str->qs_append(m_cursor); @@ -3861,19 +3864,19 @@ sp_instr_cclose::execute(THD *thd, uint *nextp) void sp_instr_cclose::print(String *str) { - LEX_STRING n; - my_bool found= m_ctx->find_cursor(m_cursor, &n); + const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor); + /* cclose name@offset */ uint rsrv= SP_INSTR_UINT_MAXLEN+8; - if (found) - rsrv+= n.length; + if (cursor_name) + rsrv+= cursor_name->length; if (str->reserve(rsrv)) return; str->qs_append(STRING_WITH_LEN("cclose ")); - if (found) + if (cursor_name) { - str->qs_append(n.str, n.length); + str->qs_append(cursor_name->str, cursor_name->length); str->qs_append('@'); } str->qs_append(m_cursor); @@ -3902,21 +3905,21 @@ sp_instr_cfetch::execute(THD *thd, uint *nextp) void sp_instr_cfetch::print(String *str) { - List_iterator_fast<struct sp_variable> li(m_varlist); - sp_variable_t *pv; - LEX_STRING n; - my_bool found= m_ctx->find_cursor(m_cursor, &n); + List_iterator_fast<sp_variable> li(m_varlist); + sp_variable *pv; + const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor); + /* cfetch name@offset vars... */ uint rsrv= SP_INSTR_UINT_MAXLEN+8; - if (found) - rsrv+= n.length; + if (cursor_name) + rsrv+= cursor_name->length; if (str->reserve(rsrv)) return; str->qs_append(STRING_WITH_LEN("cfetch ")); - if (found) + if (cursor_name) { - str->qs_append(n.str, n.length); + str->qs_append(cursor_name->str, cursor_name->length); str->qs_append('@'); } str->qs_append(m_cursor); @@ -3941,7 +3944,7 @@ sp_instr_error::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_error::execute"); - my_message(m_errcode, ER(m_errcode), MYF(0)); + my_message(m_errcode, ER_THD(thd, m_errcode), MYF(0)); *nextp= m_ip+1; DBUG_RETURN(-1); } @@ -3983,7 +3986,7 @@ sp_instr_set_case_expr::exec_core(THD *thd, uint *nextp) initialized. Set to NULL so we can continue. */ - Item *null_item= new Item_null(); + Item *null_item= new (thd->mem_root) Item_null(thd); if (!null_item || thd->spcont->set_case_expr(thd, m_case_expr_id, &null_item)) @@ -4009,7 +4012,8 @@ sp_instr_set_case_expr::print(String *str) str->qs_append(STRING_WITH_LEN(") ")); str->qs_append(m_case_expr_id); str->qs_append(' '); - m_case_expr->print(str, QT_ORDINARY); + m_case_expr->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); } uint @@ -4096,7 +4100,7 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) SP_TABLE *tab; if (lex_for_tmp_check->sql_command == SQLCOM_DROP_TABLE && - lex_for_tmp_check->drop_temporary) + lex_for_tmp_check->tmp_table()) return TRUE; for (uint i= 0 ; i < m_sptabs.records ; i++) @@ -4161,7 +4165,7 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) return FALSE; if (lex_for_tmp_check->sql_command == SQLCOM_CREATE_TABLE && lex_for_tmp_check->query_tables == table && - lex_for_tmp_check->create_info.options & HA_LEX_CREATE_TMP_TABLE) + lex_for_tmp_check->tmp_table()) { tab->temp= TRUE; tab->qname.length= temp_table_key_length; @@ -4234,7 +4238,7 @@ sp_head::add_used_tables_to_table_list(THD *thd, if (stab->temp) continue; - if (!(tab_buff= (char *)thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST)) * + if (!(tab_buff= (char *)thd->alloc(ALIGN_SIZE(sizeof(TABLE_LIST)) * stab->lock_count)) || !(key_buff= (char*)thd->memdup(stab->qname.str, stab->qname.length))) @@ -4243,32 +4247,11 @@ sp_head::add_used_tables_to_table_list(THD *thd, for (uint j= 0; j < stab->lock_count; j++) { table= (TABLE_LIST *)tab_buff; - - table->db= key_buff; - table->db_length= stab->db_length; - table->table_name= table->db + table->db_length + 1; - table->table_name_length= stab->table_name_length; - table->alias= table->table_name + table->table_name_length + 1; - table->lock_type= stab->lock_type; - table->cacheable_table= 1; - table->prelocking_placeholder= 1; - table->belong_to_view= belong_to_view; - table->trg_event_map= stab->trg_event_map; - /* - Since we don't allow DDL on base tables in prelocked mode it - is safe to infer the type of metadata lock from the type of - table lock. - */ - table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, - table->lock_type >= TL_WRITE_ALLOW_WRITE ? - MDL_SHARED_WRITE : MDL_SHARED_READ, - MDL_TRANSACTION); - - /* Everyting else should be zeroed */ - - **query_tables_last_ptr= table; - table->prev_global= *query_tables_last_ptr; - *query_tables_last_ptr= &table->next_global; + table->init_one_table_for_prelocking(key_buff, stab->db_length, + key_buff + stab->db_length + 1, stab->table_name_length, + key_buff + stab->db_length + stab->table_name_length + 2, + stab->lock_type, true, belong_to_view, stab->trg_event_map, + query_tables_last_ptr); tab_buff+= ALIGN_SIZE(sizeof(TABLE_LIST)); result= TRUE; @@ -4311,4 +4294,3 @@ sp_add_to_query_tables(THD *thd, LEX *lex, lex->add_to_query_tables(table); return table; } - diff --git a/sql/sp_head.h b/sql/sp_head.h index 409db33ef02..604190079cb 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -30,8 +30,9 @@ #include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_class.h" // THD, set_var.h: THD #include "set_var.h" // Item -#include "sp.h" +#include "sp_pcontext.h" // sp_pcontext #include <stddef.h> +#include "sp.h" /** @defgroup Stored_Routines Stored Routines @@ -39,6 +40,11 @@ @{ */ +// Values for the type enum. This reflects the order of the enum declaration +// in the CREATE TABLE command. +//#define TYPE_ENUM_FUNCTION 1 #define TYPE_ENUM_PROCEDURE 2 #define +//TYPE_ENUM_TRIGGER 3 #define TYPE_ENUM_PROXY 4 + Item_result sp_map_result_type(enum enum_field_types type); @@ -48,12 +54,9 @@ sp_map_item_type(enum enum_field_types type); uint sp_get_flags_for_command(LEX *lex); -struct sp_label; class sp_instr; class sp_instr_opt_meta; class sp_instr_jump_if_not; -struct sp_cond_type; -struct sp_variable; /*************************************************************************/ @@ -119,6 +122,8 @@ public: sp_name(LEX_STRING db, LEX_STRING name, bool use_explicit_name) : m_db(db), m_name(name), m_explicit_name(use_explicit_name) { + if (lower_case_table_names && m_db.str) + m_db.length= my_casedn_str(files_charset_info, m_db.str); m_qname.str= 0; m_qname.length= 0; } @@ -156,7 +161,21 @@ public: LOG_SLOW_STATEMENTS= 256, // Used by events LOG_GENERAL_LOG= 512, // Used by events HAS_SQLCOM_RESET= 1024, - HAS_SQLCOM_FLUSH= 2048 + HAS_SQLCOM_FLUSH= 2048, + + /** + Marks routines that directly (i.e. not by calling other routines) + change tables. Note that this flag is set automatically based on + type of statements used in the stored routine and is different + from routine characteristic provided by user in a form of CONTAINS + SQL, READS SQL DATA, MODIFIES SQL DATA clauses. The latter are + accepted by parser but pretty much ignored after that. + We don't rely on them: + a) for compatibility reasons. + b) because in CONTAINS SQL case they don't provide enough + information anyway. + */ + MODIFIES_DATA= 4096 }; stored_procedure_type m_type; @@ -274,6 +293,15 @@ public: */ Security_context m_security_ctx; + /** + List of all items (Item_trigger_field objects) representing fields in + old/new version of row in trigger. We use this list for checking whenever + all such fields are valid at trigger creation time and for binding these + fields to TABLE object at table open (although for latter pointer to table + being opened is probably enough). + */ + SQL_I_List<Item_trigger_field> m_trg_table_fields; + static void * operator new(size_t size) throw (); @@ -312,17 +340,26 @@ public: bool execute_procedure(THD *thd, List<Item> *args); + static void + show_create_routine_get_fields(THD *thd, int type, List<Item> *fields); + bool show_create_routine(THD *thd, int type); int add_instr(sp_instr *instr); - inline uint - instructions() - { - return m_instr.elements; - } + /** + Returns true if any substatement in the routine directly + (not through another routine) modifies data/changes table. + + @sa Comment for MODIFIES_DATA flag. + */ + bool modifies_data() const + { return m_flags & MODIFIES_DATA; } + + inline uint instructions() + { return m_instr.elements; } inline sp_instr * last_instruction() @@ -352,12 +389,12 @@ public: /// Put the instruction on the backpatch list, associated with the label. int - push_backpatch(sp_instr *, struct sp_label *); + push_backpatch(sp_instr *, sp_label *); /// Update all instruction with this label in the backpatch list to /// the current position. void - backpatch(struct sp_label *); + backpatch(sp_label *); /// Start a new cont. backpatch level. If 'i' is NULL, the level is just incr. int @@ -450,9 +487,10 @@ public: else if (m_flags & HAS_SQLCOM_FLUSH) my_error(ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0), "FLUSH"); - return test(m_flags & - (CONTAINS_DYNAMIC_SQL|MULTI_RESULTS|HAS_SET_AUTOCOMMIT_STMT| - HAS_COMMIT_OR_ROLLBACK|HAS_SQLCOM_RESET|HAS_SQLCOM_FLUSH)); + return MY_TEST(m_flags & + (CONTAINS_DYNAMIC_SQL | MULTI_RESULTS | + HAS_SET_AUTOCOMMIT_STMT | HAS_COMMIT_OR_ROLLBACK | + HAS_SQLCOM_RESET | HAS_SQLCOM_FLUSH)); } #ifndef DBUG_OFF @@ -493,7 +531,7 @@ private: DYNAMIC_ARRAY m_instr; ///< The "instructions" typedef struct { - struct sp_label *lab; + sp_label *lab; sp_instr *instr; } bp_t; List<bp_t> m_backpatch; ///< Instructions needing backpatching @@ -573,7 +611,7 @@ public: instruction for CONTINUE error handlers. @retval 0 on success, - @retval other if some error occured + @retval other if some error occurred */ virtual int execute(THD *thd, uint *nextp) = 0; @@ -593,7 +631,7 @@ public: Get the continuation destination of this instruction. @return the continuation destination */ - virtual uint get_cont_dest(); + virtual uint get_cont_dest() const; /* Execute core function of instruction after all preparations (e.g. @@ -865,7 +903,7 @@ public: virtual void set_destination(uint old_dest, uint new_dest) = 0; - virtual uint get_cont_dest(); + virtual uint get_cont_dest() const; protected: @@ -1016,15 +1054,21 @@ class sp_instr_hpush_jump : public sp_instr_jump public: - sp_instr_hpush_jump(uint ip, sp_pcontext *ctx, int htype, uint fp) - : sp_instr_jump(ip, ctx), m_type(htype), m_frame(fp), m_opt_hpop(0) + sp_instr_hpush_jump(uint ip, + sp_pcontext *ctx, + sp_handler *handler) + :sp_instr_jump(ip, ctx), + m_handler(handler), + m_opt_hpop(0), + m_frame(ctx->current_var_count()) { - m_cond.empty(); + DBUG_ASSERT(m_handler->condition_values.elements == 0); } virtual ~sp_instr_hpush_jump() { - m_cond.empty(); + m_handler->condition_values.empty(); + m_handler= NULL; } virtual int execute(THD *thd, uint *nextp); @@ -1048,17 +1092,24 @@ public: m_opt_hpop= dest; } - inline void add_condition(struct sp_cond_type *cond) - { - m_cond.push_front(cond); - } + void add_condition(sp_condition_value *condition_value) + { m_handler->condition_values.push_back(condition_value); } + + sp_handler *get_handler() + { return m_handler; } private: - int m_type; ///< Handler type +private: + /// Handler. + sp_handler *m_handler; + + /// hpop marking end of handler scope. + uint m_opt_hpop; + + // This attribute is needed for SHOW PROCEDURE CODE only (i.e. it's needed in + // debug version only). It's used in print(). uint m_frame; - uint m_opt_hpop; // hpop marking end of handler scope. - List<struct sp_cond_type> m_cond; }; // class sp_instr_hpush_jump : public sp_instr_jump @@ -1095,8 +1146,9 @@ class sp_instr_hreturn : public sp_instr_jump public: - sp_instr_hreturn(uint ip, sp_pcontext *ctx, uint fp) - : sp_instr_jump(ip, ctx), m_frame(fp) + sp_instr_hreturn(uint ip, sp_pcontext *ctx) + :sp_instr_jump(ip, ctx), + m_frame(ctx->current_var_count()) {} virtual ~sp_instr_hreturn() @@ -1251,7 +1303,7 @@ public: virtual void print(String *str); - void add_to_varlist(struct sp_variable *var) + void add_to_varlist(sp_variable *var) { m_varlist.push_back(var); } @@ -1259,7 +1311,7 @@ public: private: uint m_cursor; - List<struct sp_variable> m_varlist; + List<sp_variable> m_varlist; }; // class sp_instr_cfetch : public sp_instr diff --git a/sql/sp_pcontext.cc b/sql/sp_pcontext.cc index 4c5087eaf27..faf5a2de891 100644 --- a/sql/sp_pcontext.cc +++ b/sql/sp_pcontext.cc @@ -13,6 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #ifdef USE_PRAGMA_IMPLEMENTATION @@ -22,133 +23,86 @@ #include "sp_pcontext.h" #include "sp_head.h" -/* Initial size for the dynamic arrays in sp_pcontext */ -#define PCONTEXT_ARRAY_INIT_ALLOC 16 -/* Increment size for the dynamic arrays in sp_pcontext */ -#define PCONTEXT_ARRAY_INCREMENT_ALLOC 8 - -/* - Sanity check for SQLSTATEs. Will not check if it's really an existing - state (there are just too many), but will check length and bad characters. - Returns TRUE if it's ok, FALSE if it's bad. -*/ -bool -sp_cond_check(LEX_STRING *sqlstate) +bool sp_condition_value::equals(const sp_condition_value *cv) const { - int i; - const char *p; + DBUG_ASSERT(cv); - if (sqlstate->length != 5) - return FALSE; - for (p= sqlstate->str, i= 0 ; i < 5 ; i++) + if (this == cv) + return true; + + if (type != cv->type) + return false; + + switch (type) { - char c = p[i]; + case sp_condition_value::ERROR_CODE: + return (mysqlerr == cv->mysqlerr); + + case sp_condition_value::SQLSTATE: + return (strcmp(sql_state, cv->sql_state) == 0); - if ((c < '0' || '9' < c) && - (c < 'A' || 'Z' < c)) - return FALSE; + default: + return true; } - /* SQLSTATE class '00' : completion condition */ - if (strncmp(sqlstate->str, "00", 2) == 0) - return FALSE; - return TRUE; } + +void sp_pcontext::init(uint var_offset, + uint cursor_offset, + int num_case_expressions) +{ + m_var_offset= var_offset; + m_cursor_offset= cursor_offset; + m_num_case_exprs= num_case_expressions; + + m_labels.empty(); +} + + sp_pcontext::sp_pcontext() : Sql_alloc(), - m_max_var_index(0), m_max_cursor_index(0), m_max_handler_index(0), - m_context_handlers(0), m_parent(NULL), m_pboundary(0), - m_label_scope(LABEL_DEFAULT_SCOPE) + m_max_var_index(0), m_max_cursor_index(0), + m_parent(NULL), m_pboundary(0), + m_scope(REGULAR_SCOPE) { - (void) my_init_dynamic_array(&m_vars, sizeof(sp_variable_t *), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - (void) my_init_dynamic_array(&m_case_expr_id_lst, sizeof(int), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - (void) my_init_dynamic_array(&m_conds, sizeof(sp_cond_type_t *), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - (void) my_init_dynamic_array(&m_cursors, sizeof(LEX_STRING), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - (void) my_init_dynamic_array(&m_handlers, sizeof(sp_cond_type_t *), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - m_label.empty(); - m_children.empty(); - - m_var_offset= m_cursor_offset= 0; - m_num_case_exprs= 0; + init(0, 0, 0); } -sp_pcontext::sp_pcontext(sp_pcontext *prev, label_scope_type label_scope) + +sp_pcontext::sp_pcontext(sp_pcontext *prev, sp_pcontext::enum_scope scope) : Sql_alloc(), - m_max_var_index(0), m_max_cursor_index(0), m_max_handler_index(0), - m_context_handlers(0), m_parent(prev), m_pboundary(0), - m_label_scope(label_scope) + m_max_var_index(0), m_max_cursor_index(0), + m_parent(prev), m_pboundary(0), + m_scope(scope) { - (void) my_init_dynamic_array(&m_vars, sizeof(sp_variable_t *), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - (void) my_init_dynamic_array(&m_case_expr_id_lst, sizeof(int), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - (void) my_init_dynamic_array(&m_conds, sizeof(sp_cond_type_t *), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - (void) my_init_dynamic_array(&m_cursors, sizeof(LEX_STRING), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - (void) my_init_dynamic_array(&m_handlers, sizeof(sp_cond_type_t *), - PCONTEXT_ARRAY_INIT_ALLOC, - PCONTEXT_ARRAY_INCREMENT_ALLOC); - m_label.empty(); - m_children.empty(); - - m_var_offset= prev->m_var_offset + prev->m_max_var_index; - m_cursor_offset= prev->current_cursor_count(); - m_num_case_exprs= prev->get_num_case_exprs(); + init(prev->m_var_offset + prev->m_max_var_index, + prev->current_cursor_count(), + prev->get_num_case_exprs()); } -void -sp_pcontext::destroy() + +sp_pcontext::~sp_pcontext() { - List_iterator_fast<sp_pcontext> li(m_children); - sp_pcontext *child; - - while ((child= li++)) - child->destroy(); - - m_children.empty(); - m_label.empty(); - delete_dynamic(&m_vars); - delete_dynamic(&m_case_expr_id_lst); - delete_dynamic(&m_conds); - delete_dynamic(&m_cursors); - delete_dynamic(&m_handlers); + for (size_t i= 0; i < m_children.elements(); ++i) + delete m_children.at(i); } -sp_pcontext * -sp_pcontext::push_context(label_scope_type label_scope) + +sp_pcontext *sp_pcontext::push_context(THD *thd, sp_pcontext::enum_scope scope) { - sp_pcontext *child= new sp_pcontext(this, label_scope); + sp_pcontext *child= new (thd->mem_root) sp_pcontext(this, scope); if (child) - m_children.push_back(child); + m_children.append(child); return child; } -sp_pcontext * -sp_pcontext::pop_context() + +sp_pcontext *sp_pcontext::pop_context() { m_parent->m_max_var_index+= m_max_var_index; - uint submax= max_handler_index(); - if (submax > m_parent->m_max_handler_index) - m_parent->m_max_handler_index= submax; - - submax= max_cursor_index(); + uint submax= max_cursor_index(); if (submax > m_parent->m_max_cursor_index) m_parent->m_max_cursor_index= submax; @@ -158,142 +112,115 @@ sp_pcontext::pop_context() return m_parent; } -uint -sp_pcontext::diff_handlers(sp_pcontext *ctx, bool exclusive) + +uint sp_pcontext::diff_handlers(const sp_pcontext *ctx, bool exclusive) const { uint n= 0; - sp_pcontext *pctx= this; - sp_pcontext *last_ctx= NULL; + const sp_pcontext *pctx= this; + const sp_pcontext *last_ctx= NULL; while (pctx && pctx != ctx) { - n+= pctx->m_context_handlers; + n+= pctx->m_handlers.elements(); last_ctx= pctx; pctx= pctx->parent_context(); } if (pctx) - return (exclusive && last_ctx ? n - last_ctx->m_context_handlers : n); + return (exclusive && last_ctx ? n - last_ctx->m_handlers.elements() : n); return 0; // Didn't find ctx } -uint -sp_pcontext::diff_cursors(sp_pcontext *ctx, bool exclusive) + +uint sp_pcontext::diff_cursors(const sp_pcontext *ctx, bool exclusive) const { uint n= 0; - sp_pcontext *pctx= this; - sp_pcontext *last_ctx= NULL; + const sp_pcontext *pctx= this; + const sp_pcontext *last_ctx= NULL; while (pctx && pctx != ctx) { - n+= pctx->m_cursors.elements; + n+= pctx->m_cursors.elements(); last_ctx= pctx; pctx= pctx->parent_context(); } if (pctx) - return (exclusive && last_ctx ? n - last_ctx->m_cursors.elements : n); + return (exclusive && last_ctx ? n - last_ctx->m_cursors.elements() : n); return 0; // Didn't find ctx } -/* - This does a linear search (from newer to older variables, in case - we have shadowed names). - It's possible to have a more efficient allocation and search method, - but it might not be worth it. The typical number of parameters and - variables will in most cases be low (a handfull). - ...and, this is only called during parsing. -*/ -sp_variable_t * -sp_pcontext::find_variable(LEX_STRING *name, my_bool scoped) + +sp_variable *sp_pcontext::find_variable(LEX_STRING name, + bool current_scope_only) const { - uint i= m_vars.elements - m_pboundary; + uint i= m_vars.elements() - m_pboundary; while (i--) { - sp_variable_t *p; + sp_variable *p= m_vars.at(i); - get_dynamic(&m_vars, (uchar*)&p, i); if (my_strnncoll(system_charset_info, - (const uchar *)name->str, name->length, + (const uchar *)name.str, name.length, (const uchar *)p->name.str, p->name.length) == 0) { return p; } } - if (!scoped && m_parent) - return m_parent->find_variable(name, scoped); - return NULL; + + return (!current_scope_only && m_parent) ? + m_parent->find_variable(name, false) : + NULL; } -/* - Find a variable by offset from the top. - This used for two things: - - When evaluating parameters at the beginning, and setting out parameters - at the end, of invokation. (Top frame only, so no recursion then.) - - For printing of sp_instr_set. (Debug mode only.) -*/ -sp_variable_t * -sp_pcontext::find_variable(uint offset) + +sp_variable *sp_pcontext::find_variable(uint offset) const { - if (m_var_offset <= offset && offset < m_var_offset + m_vars.elements) - { // This frame - sp_variable_t *p; + if (m_var_offset <= offset && offset < m_var_offset + m_vars.elements()) + return m_vars.at(offset - m_var_offset); // This frame - get_dynamic(&m_vars, (uchar*)&p, offset - m_var_offset); - return p; - } - if (m_parent) - return m_parent->find_variable(offset); // Some previous frame - return NULL; // index out of bounds + return m_parent ? + m_parent->find_variable(offset) : // Some previous frame + NULL; // Index out of bounds } -sp_variable_t * -sp_pcontext::push_variable(LEX_STRING *name, enum enum_field_types type, - sp_param_mode_t mode) + +sp_variable *sp_pcontext::add_variable(THD *thd, LEX_STRING name) { - sp_variable_t *p= (sp_variable_t *)sql_alloc(sizeof(sp_variable_t)); + sp_variable *p= + new (thd->mem_root) sp_variable(name, current_var_count()); if (!p) return NULL; ++m_max_var_index; - p->name.str= name->str; - p->name.length= name->length; - p->type= type; - p->mode= mode; - p->offset= current_var_count(); - p->dflt= NULL; - if (insert_dynamic(&m_vars, (uchar*)&p)) - return NULL; - return p; + return m_vars.append(p) ? NULL : p; } -sp_label_t * -sp_pcontext::push_label(char *name, uint ip) +sp_label *sp_pcontext::push_label(THD *thd, LEX_STRING name, uint ip) { - sp_label_t *lab = (sp_label_t *)sql_alloc(sizeof(sp_label_t)); + sp_label *label= + new (thd->mem_root) sp_label(name, ip, sp_label::IMPLICIT, this); - if (lab) - { - lab->name= name; - lab->ip= ip; - lab->type= SP_LAB_IMPL; - lab->ctx= this; - m_label.push_front(lab); - } - return lab; + if (!label) + return NULL; + + m_labels.push_front(label, thd->mem_root); + + return label; } -sp_label_t * -sp_pcontext::find_label(char *name) + +sp_label *sp_pcontext::find_label(LEX_STRING name) { - List_iterator_fast<sp_label_t> li(m_label); - sp_label_t *lab; + List_iterator_fast<sp_label> li(m_labels); + sp_label *lab; while ((lab= li++)) - if (my_strcasecmp(system_charset_info, name, lab->name) == 0) + { + if (my_strcasecmp(system_charset_info, name.str, lab->name.str) == 0) return lab; + } /* Note about exception handlers. @@ -303,159 +230,253 @@ sp_pcontext::find_label(char *name) In short, a DECLARE HANDLER block can not refer to labels from the parent context, as they are out of scope. */ - if (m_parent && (m_label_scope == LABEL_DEFAULT_SCOPE)) - return m_parent->find_label(name); - return NULL; + return (m_parent && (m_scope == REGULAR_SCOPE)) ? + m_parent->find_label(name) : + NULL; } -int -sp_pcontext::push_cond(LEX_STRING *name, sp_cond_type_t *val) + +bool sp_pcontext::add_condition(THD *thd, + LEX_STRING name, + sp_condition_value *value) { - sp_cond_t *p= (sp_cond_t *)sql_alloc(sizeof(sp_cond_t)); + sp_condition *p= new (thd->mem_root) sp_condition(name, value); if (p == NULL) - return 1; - p->name.str= name->str; - p->name.length= name->length; - p->val= val; - return insert_dynamic(&m_conds, (uchar *)&p); + return true; + + return m_conditions.append(p); } -/* - See comment for find_variable() above -*/ -sp_cond_type_t * -sp_pcontext::find_cond(LEX_STRING *name, my_bool scoped) + +sp_condition_value *sp_pcontext::find_condition(LEX_STRING name, + bool current_scope_only) const { - uint i= m_conds.elements; + uint i= m_conditions.elements(); while (i--) { - sp_cond_t *p; + sp_condition *p= m_conditions.at(i); - get_dynamic(&m_conds, (uchar*)&p, i); if (my_strnncoll(system_charset_info, - (const uchar *)name->str, name->length, - (const uchar *)p->name.str, p->name.length) == 0) + (const uchar *) name.str, name.length, + (const uchar *) p->name.str, p->name.length) == 0) { - return p->val; + return p->value; } } - if (!scoped && m_parent) - return m_parent->find_cond(name, scoped); - return NULL; + + return (!current_scope_only && m_parent) ? + m_parent->find_condition(name, false) : + NULL; } -/* - This only searches the current context, for error checking of - duplicates. - Returns TRUE if found. -*/ -bool -sp_pcontext::find_handler(sp_cond_type_t *cond) + +sp_handler *sp_pcontext::add_handler(THD *thd, + sp_handler::enum_type type) { - uint i= m_handlers.elements; + sp_handler *h= new (thd->mem_root) sp_handler(type); - while (i--) + if (!h) + return NULL; + + return m_handlers.append(h) ? NULL : h; +} + + +bool sp_pcontext::check_duplicate_handler( + const sp_condition_value *cond_value) const +{ + for (size_t i= 0; i < m_handlers.elements(); ++i) { - sp_cond_type_t *p; + sp_handler *h= m_handlers.at(i); + + List_iterator_fast<sp_condition_value> li(h->condition_values); + sp_condition_value *cv; - get_dynamic(&m_handlers, (uchar*)&p, i); - if (cond->type == p->type) + while ((cv= li++)) { - switch (p->type) + if (cond_value->equals(cv)) + return true; + } + } + + return false; +} + + +sp_handler* +sp_pcontext::find_handler(const char *sql_state, + uint sql_errno, + Sql_condition::enum_warning_level level) const +{ + sp_handler *found_handler= NULL; + sp_condition_value *found_cv= NULL; + + for (size_t i= 0; i < m_handlers.elements(); ++i) + { + sp_handler *h= m_handlers.at(i); + + List_iterator_fast<sp_condition_value> li(h->condition_values); + sp_condition_value *cv; + + while ((cv= li++)) + { + switch (cv->type) { - case sp_cond_type_t::number: - if (cond->mysqlerr == p->mysqlerr) - return TRUE; - break; - case sp_cond_type_t::state: - if (strcmp(cond->sqlstate, p->sqlstate) == 0) - return TRUE; - break; - default: - return TRUE; + case sp_condition_value::ERROR_CODE: + if (sql_errno == cv->mysqlerr && + (!found_cv || + found_cv->type > sp_condition_value::ERROR_CODE)) + { + found_cv= cv; + found_handler= h; + } + break; + + case sp_condition_value::SQLSTATE: + if (strcmp(sql_state, cv->sql_state) == 0 && + (!found_cv || + found_cv->type > sp_condition_value::SQLSTATE)) + { + found_cv= cv; + found_handler= h; + } + break; + + case sp_condition_value::WARNING: + if ((is_sqlstate_warning(sql_state) || + level == Sql_condition::WARN_LEVEL_WARN) && !found_cv) + { + found_cv= cv; + found_handler= h; + } + break; + + case sp_condition_value::NOT_FOUND: + if (is_sqlstate_not_found(sql_state) && !found_cv) + { + found_cv= cv; + found_handler= h; + } + break; + + case sp_condition_value::EXCEPTION: + if (is_sqlstate_exception(sql_state) && + level == Sql_condition::WARN_LEVEL_ERROR && !found_cv) + { + found_cv= cv; + found_handler= h; + } + break; } } } - return FALSE; + + if (found_handler) + return found_handler; + + + // There is no appropriate handler in this parsing context. We need to look up + // in parent contexts. There might be two cases here: + // + // 1. The current context has REGULAR_SCOPE. That means, it's a simple + // BEGIN..END block: + // ... + // BEGIN + // ... # We're here. + // END + // ... + // In this case we simply call find_handler() on parent's context recursively. + // + // 2. The current context has HANDLER_SCOPE. That means, we're inside an + // SQL-handler block: + // ... + // DECLARE ... HANDLER FOR ... + // BEGIN + // ... # We're here. + // END + // ... + // In this case we can not just call parent's find_handler(), because + // parent's handler don't catch conditions from this scope. Instead, we should + // try to find first parent context (we might have nested handler + // declarations), which has REGULAR_SCOPE (i.e. which is regular BEGIN..END + // block). + + const sp_pcontext *p= this; + + while (p && p->m_scope == HANDLER_SCOPE) + p= p->m_parent; + + if (!p || !p->m_parent) + return NULL; + + return p->m_parent->find_handler(sql_state, sql_errno, level); } -int -sp_pcontext::push_cursor(LEX_STRING *name) + +bool sp_pcontext::add_cursor(LEX_STRING name) { - LEX_STRING n; + if (m_cursors.elements() == m_max_cursor_index) + ++m_max_cursor_index; - if (m_cursors.elements == m_max_cursor_index) - m_max_cursor_index+= 1; - n.str= name->str; - n.length= name->length; - return insert_dynamic(&m_cursors, (uchar *)&n); + return m_cursors.append(name); } -/* - See comment for find_variable() above -*/ -my_bool -sp_pcontext::find_cursor(LEX_STRING *name, uint *poff, my_bool scoped) + +bool sp_pcontext::find_cursor(LEX_STRING name, + uint *poff, + bool current_scope_only) const { - uint i= m_cursors.elements; + uint i= m_cursors.elements(); while (i--) { - LEX_STRING n; + LEX_STRING n= m_cursors.at(i); - get_dynamic(&m_cursors, (uchar*)&n, i); if (my_strnncoll(system_charset_info, - (const uchar *)name->str, name->length, - (const uchar *)n.str, n.length) == 0) + (const uchar *) name.str, name.length, + (const uchar *) n.str, n.length) == 0) { *poff= m_cursor_offset + i; - return TRUE; + return true; } } - if (!scoped && m_parent) - return m_parent->find_cursor(name, poff, scoped); - return FALSE; + + return (!current_scope_only && m_parent) ? + m_parent->find_cursor(name, poff, false) : + false; } -void -sp_pcontext::retrieve_field_definitions(List<Create_field> *field_def_lst) +void sp_pcontext::retrieve_field_definitions( + List<Create_field> *field_def_lst) const { /* Put local/context fields in the result list. */ - for (uint i = 0; i < m_vars.elements; ++i) + for (size_t i= 0; i < m_vars.elements(); ++i) { - sp_variable_t *var_def; - get_dynamic(&m_vars, (uchar*) &var_def, i); + sp_variable *var_def= m_vars.at(i); field_def_lst->push_back(&var_def->field_def); } /* Put the fields of the enclosed contexts in the result list. */ - List_iterator_fast<sp_pcontext> li(m_children); - sp_pcontext *ctx; - - while ((ctx = li++)) - ctx->retrieve_field_definitions(field_def_lst); + for (size_t i= 0; i < m_children.elements(); ++i) + m_children.at(i)->retrieve_field_definitions(field_def_lst); } -/* - Find a cursor by offset from the top. - This is only used for debugging. -*/ -my_bool -sp_pcontext::find_cursor(uint offset, LEX_STRING *n) + +const LEX_STRING *sp_pcontext::find_cursor(uint offset) const { if (m_cursor_offset <= offset && - offset < m_cursor_offset + m_cursors.elements) - { // This frame - get_dynamic(&m_cursors, (uchar*)n, offset - m_cursor_offset); - return TRUE; + offset < m_cursor_offset + m_cursors.elements()) + { + return &m_cursors.at(offset - m_cursor_offset); // This frame } - if (m_parent) - return m_parent->find_cursor(offset, n); // Some previous frame - return FALSE; // index out of bounds + + return m_parent ? + m_parent->find_cursor(offset) : // Some previous frame + NULL; // Index out of bounds } diff --git a/sql/sp_pcontext.h b/sql/sp_pcontext.h index f1d0d250c47..efe9531c3a0 100644 --- a/sql/sp_pcontext.h +++ b/sql/sp_pcontext.h @@ -24,438 +24,535 @@ #include "sql_string.h" // LEX_STRING #include "mysql_com.h" // enum_field_types #include "field.h" // Create_field +#include "sql_array.h" // Dynamic_array -class sp_pcontext; -typedef enum -{ - sp_param_in, - sp_param_out, - sp_param_inout -} sp_param_mode_t; +/// This class represents a stored program variable or a parameter +/// (also referenced as 'SP-variable'). -typedef struct sp_variable +class sp_variable : public Sql_alloc { - LEX_STRING name; - enum enum_field_types type; - sp_param_mode_t mode; - - /* - offset -- this the index to the variable's value in the runtime frame. - This is calculated during parsing and used when creating sp_instr_set - instructions and Item_splocal items. - I.e. values are set/referred by array indexing in runtime. - */ - uint offset; - - Item *dflt; - Create_field field_def; -} sp_variable_t; +public: + enum enum_mode + { + MODE_IN, + MODE_OUT, + MODE_INOUT + }; + /// Name of the SP-variable. + LEX_STRING name; -#define SP_LAB_IMPL 0 // Implicit label generated by parser -#define SP_LAB_BEGIN 1 // Label at BEGIN -#define SP_LAB_ITER 2 // Label at iteration control + /// Field-type of the SP-variable. + enum enum_field_types type; -/* - An SQL/PSM label. Can refer to the identifier used with the - "label_name:" construct which may precede some SQL/PSM statements, or - to an implicit implementation-dependent identifier which the parser - inserts before a high-level flow control statement such as - IF/WHILE/REPEAT/LOOP, when such statement is rewritten into - a combination of low-level jump/jump_if instructions and labels. -*/ + /// Mode of the SP-variable. + enum_mode mode; -typedef struct sp_label -{ - char *name; - uint ip; // Instruction index - int type; // begin/iter or ref/free - sp_pcontext *ctx; // The label's context -} sp_label_t; + /// The index to the variable's value in the runtime frame. + /// + /// It is calculated during parsing and used when creating sp_instr_set + /// instructions and Item_splocal items. I.e. values are set/referred by + /// array indexing in runtime. + uint offset; -typedef struct sp_cond_type -{ - enum { number, state, warning, notfound, exception } type; - char sqlstate[SQLSTATE_LENGTH+1]; - uint mysqlerr; -} sp_cond_type_t; + /// Default value of the SP-variable (if any). + Item *default_value; -/* - Sanity check for SQLSTATEs. Will not check if it's really an existing - state (there are just too many), but will check length bad characters. -*/ -extern bool -sp_cond_check(LEX_STRING *sqlstate); + /// Full type information (field meta-data) of the SP-variable. + Create_field field_def; -typedef struct sp_cond -{ - LEX_STRING name; - sp_cond_type_t *val; -} sp_cond_t; - -/** - The scope of a label in Stored Procedures, - for name resolution of labels in a parsing context. -*/ -enum label_scope_type -{ - /** - The labels declared in a parent context are in scope. - */ - LABEL_DEFAULT_SCOPE, - /** - The labels declared in a parent context are not in scope. - */ - LABEL_HANDLER_SCOPE +public: + sp_variable(LEX_STRING _name, uint _offset) + :Sql_alloc(), + name(_name), + type(MYSQL_TYPE_NULL), + mode(MODE_IN), + offset(_offset), + default_value(NULL) + { } }; -/** - The parse-time context, used to keep track of declared variables/parameters, - conditions, handlers, cursors and labels, during parsing. - sp_contexts are organized as a tree, with one object for each begin-end - block, one object for each exception handler, - plus a root-context for the parameters. - This is used during parsing for looking up defined names (e.g. declared - variables and visible labels), for error checking, and to calculate offsets - to be used at runtime. (During execution variable values, active handlers - and cursors, etc, are referred to by an index in a stack.) - Parsing contexts for exception handlers limit the visibility of labels. - The pcontext tree is also kept during execution and is used for error - checking (e.g. correct number of parameters), and in the future, used by - the debugger. -*/ +/////////////////////////////////////////////////////////////////////////// -class sp_pcontext : public Sql_alloc +/// This class represents an SQL/PSM label. Can refer to the identifier +/// used with the "label_name:" construct which may precede some SQL/PSM +/// statements, or to an implicit implementation-dependent identifier which +/// the parser inserts before a high-level flow control statement such as +/// IF/WHILE/REPEAT/LOOP, when such statement is rewritten into a +/// combination of low-level jump/jump_if instructions and labels. + +class sp_label : public Sql_alloc { public: - - /** - Constructor. - Builds a parsing context root node. - */ - sp_pcontext(); - - // Free memory - void - destroy(); - - /** - Create and push a new context in the tree. - @param label_scope label scope for the new parsing context - @return the node created - */ - sp_pcontext * - push_context(label_scope_type label_scope); - - /** - Pop a node from the parsing context tree. - @return the parent node - */ - sp_pcontext * - pop_context(); - - sp_pcontext * - parent_context() + enum enum_type { - return m_parent; - } + /// Implicit label generated by parser. + IMPLICIT, - /* - Number of handlers/cursors to pop between this context and 'ctx'. - If 'exclusive' is true, don't count the last block we are leaving; - this is used for LEAVE where we will jump to the cpop/hpop instructions. - */ - uint - diff_handlers(sp_pcontext *ctx, bool exclusive); - uint - diff_cursors(sp_pcontext *ctx, bool exclusive); - - - // - // Parameters and variables - // - - /* - The maximum number of variables used in this and all child contexts - In the root, this gives us the number of slots needed for variables - during execution. - */ - inline uint - max_var_index() - { - return m_max_var_index; - } + /// Label at BEGIN. + BEGIN, - /* - The current number of variables used in the parents (from the root), - including this context. - */ - inline uint - current_var_count() - { - return m_var_offset + m_vars.elements; - } + /// Label at iteration control + ITERATION + }; - /* The number of variables in this context alone */ - inline uint - context_var_count() - { - return m_vars.elements; - } + /// Name of the label. + LEX_STRING name; - /* Map index in this pcontext to runtime offset */ - inline uint - var_context2runtime(uint i) - { - return m_var_offset + i; - } + /// Instruction pointer of the label. + uint ip; - /* Set type of variable. 'i' is the offset from the top */ - inline void - set_type(uint i, enum enum_field_types type) - { - sp_variable_t *p= find_variable(i); + /// Type of the label. + enum_type type; - if (p) - p->type= type; - } + /// Scope of the label. + class sp_pcontext *ctx; - /* Set default value of variable. 'i' is the offset from the top */ - inline void - set_default(uint i, Item *it) - { - sp_variable_t *p= find_variable(i); +public: + sp_label(LEX_STRING _name, uint _ip, enum_type _type, sp_pcontext *_ctx) + :Sql_alloc(), + name(_name), + ip(_ip), + type(_type), + ctx(_ctx) + { } +}; - if (p) - p->dflt= it; - } +/////////////////////////////////////////////////////////////////////////// + +/// This class represents condition-value term in DECLARE CONDITION or +/// DECLARE HANDLER statements. sp_condition_value has little to do with +/// SQL-conditions. +/// +/// In some sense, this class is a union -- a set of filled attributes +/// depends on the sp_condition_value::type value. - sp_variable_t * - push_variable(LEX_STRING *name, enum enum_field_types type, - sp_param_mode_t mode); - - /* - Retrieve definitions of fields from the current context and its - children. - */ - void - retrieve_field_definitions(List<Create_field> *field_def_lst); - - // Find by name - sp_variable_t * - find_variable(LEX_STRING *name, my_bool scoped=0); - - // Find by offset (from the top) - sp_variable_t * - find_variable(uint offset); - - /* - Set the current scope boundary (for default values). - The argument is the number of variables to skip. - */ - inline void - declare_var_boundary(uint n) +class sp_condition_value : public Sql_alloc +{ +public: + enum enum_type { - m_pboundary= n; - } + ERROR_CODE, + SQLSTATE, + WARNING, + NOT_FOUND, + EXCEPTION + }; - /* - CASE expressions support. - */ + /// Type of the condition value. + enum_type type; - inline int - register_case_expr() - { - return m_num_case_exprs++; - } + /// SQLSTATE of the condition value. + char sql_state[SQLSTATE_LENGTH+1]; - inline int - get_num_case_exprs() const - { - return m_num_case_exprs; - } + /// MySQL error code of the condition value. + uint mysqlerr; - inline bool - push_case_expr_id(int case_expr_id) +public: + sp_condition_value(uint _mysqlerr) + :Sql_alloc(), + type(ERROR_CODE), + mysqlerr(_mysqlerr) + { } + + sp_condition_value(const char *_sql_state) + :Sql_alloc(), + type(SQLSTATE) { - return insert_dynamic(&m_case_expr_id_lst, (uchar*) &case_expr_id); + memcpy(sql_state, _sql_state, SQLSTATE_LENGTH); + sql_state[SQLSTATE_LENGTH]= 0; } - inline void - pop_case_expr_id() + sp_condition_value(enum_type _type) + :Sql_alloc(), + type(_type) { - pop_dynamic(&m_case_expr_id_lst); + DBUG_ASSERT(type != ERROR_CODE && type != SQLSTATE); } - inline int - get_current_case_expr_id() const - { - int case_expr_id; + /// Check if two instances of sp_condition_value are equal or not. + /// + /// @param cv another instance of sp_condition_value to check. + /// + /// @return true if the instances are equal, false otherwise. + bool equals(const sp_condition_value *cv) const; +}; - get_dynamic((DYNAMIC_ARRAY*)&m_case_expr_id_lst, (uchar*) &case_expr_id, - m_case_expr_id_lst.elements - 1); +/////////////////////////////////////////////////////////////////////////// - return case_expr_id; - } +/// This class represents 'DECLARE CONDITION' statement. +/// sp_condition has little to do with SQL-conditions. - // - // Labels - // +class sp_condition : public Sql_alloc +{ +public: + /// Name of the condition. + LEX_STRING name; - sp_label_t * - push_label(char *name, uint ip); + /// Value of the condition. + sp_condition_value *value; - sp_label_t * - find_label(char *name); +public: + sp_condition(LEX_STRING _name, sp_condition_value *_value) + :Sql_alloc(), + name(_name), + value(_value) + { } +}; - inline sp_label_t * - last_label() - { - sp_label_t *lab= m_label.head(); +/////////////////////////////////////////////////////////////////////////// - if (!lab && m_parent) - lab= m_parent->last_label(); - return lab; - } +/// This class represents 'DECLARE HANDLER' statement. - inline sp_label_t * - pop_label() +class sp_handler : public Sql_alloc +{ +public: + /// Enumeration of possible handler types. + /// Note: UNDO handlers are not (and have never been) supported. + enum enum_type { - return m_label.pop(); - } + EXIT, + CONTINUE + }; - // - // Conditions - // + /// Handler type. + enum_type type; - int - push_cond(LEX_STRING *name, sp_cond_type_t *val); + /// Conditions caught by this handler. + List<sp_condition_value> condition_values; - sp_cond_type_t * - find_cond(LEX_STRING *name, my_bool scoped=0); +public: + /// The constructor. + /// + /// @param _type SQL-handler type. + sp_handler(enum_type _type) + :Sql_alloc(), + type(_type) + { } +}; - // - // Handlers - // +/////////////////////////////////////////////////////////////////////////// + +/// The class represents parse-time context, which keeps track of declared +/// variables/parameters, conditions, handlers, cursors and labels. +/// +/// sp_pcontext objects are organized in a tree according to the following +/// rules: +/// - one sp_pcontext object corresponds for for each BEGIN..END block; +/// - one sp_pcontext object corresponds for each exception handler; +/// - one additional sp_pcontext object is created to contain +/// Stored Program parameters. +/// +/// sp_pcontext objects are used both at parse-time and at runtime. +/// +/// During the parsing stage sp_pcontext objects are used: +/// - to look up defined names (e.g. declared variables and visible +/// labels); +/// - to check for duplicates; +/// - for error checking; +/// - to calculate offsets to be used at runtime. +/// +/// During the runtime phase, a tree of sp_pcontext objects is used: +/// - for error checking (e.g. to check correct number of parameters); +/// - to resolve SQL-handlers. - inline void - push_handler(sp_cond_type_t *cond) +class sp_pcontext : public Sql_alloc +{ +public: + enum enum_scope { - insert_dynamic(&m_handlers, (uchar*)&cond); - } - - bool - find_handler(sp_cond_type *cond); + /// REGULAR_SCOPE designates regular BEGIN ... END blocks. + REGULAR_SCOPE, - inline uint - max_handler_index() - { - return m_max_handler_index + m_context_handlers; - } + /// HANDLER_SCOPE designates SQL-handler blocks. + HANDLER_SCOPE + }; - inline void - add_handlers(uint n) +public: + sp_pcontext(); + ~sp_pcontext(); + + + /// Create and push a new context in the tree. + + /// @param thd thread context. + /// @param scope scope of the new parsing context. + /// @return the node created. + sp_pcontext *push_context(THD *thd, enum_scope scope); + + /// Pop a node from the parsing context tree. + /// @return the parent node. + sp_pcontext *pop_context(); + + sp_pcontext *parent_context() const + { return m_parent; } + + /// Calculate and return the number of handlers to pop between the given + /// context and this one. + /// + /// @param ctx the other parsing context. + /// @param exclusive specifies if the last scope should be excluded. + /// + /// @return the number of handlers to pop between the given context and + /// this one. If 'exclusive' is true, don't count the last scope we are + /// leaving; this is used for LEAVE where we will jump to the hpop + /// instructions. + uint diff_handlers(const sp_pcontext *ctx, bool exclusive) const; + + /// Calculate and return the number of cursors to pop between the given + /// context and this one. + /// + /// @param ctx the other parsing context. + /// @param exclusive specifies if the last scope should be excluded. + /// + /// @return the number of cursors to pop between the given context and + /// this one. If 'exclusive' is true, don't count the last scope we are + /// leaving; this is used for LEAVE where we will jump to the cpop + /// instructions. + uint diff_cursors(const sp_pcontext *ctx, bool exclusive) const; + + ///////////////////////////////////////////////////////////////////////// + // SP-variables (parameters and variables). + ///////////////////////////////////////////////////////////////////////// + + /// @return the maximum number of variables used in this and all child + /// contexts. For the root parsing context, this gives us the number of + /// slots needed for variables during the runtime phase. + uint max_var_index() const + { return m_max_var_index; } + + /// @return the current number of variables used in the parent contexts + /// (from the root), including this context. + uint current_var_count() const + { return m_var_offset + m_vars.elements(); } + + /// @return the number of variables in this context alone. + uint context_var_count() const + { return m_vars.elements(); } + + /// @return map index in this parsing context to runtime offset. + uint var_context2runtime(uint i) const + { return m_var_offset + i; } + + /// Add SP-variable to the parsing context. + /// + /// @param thd Thread context. + /// @param name Name of the SP-variable. + /// + /// @return instance of newly added SP-variable. + sp_variable *add_variable(THD *thd, LEX_STRING name); + + /// Retrieve full type information about SP-variables in this parsing + /// context and its children. + /// + /// @param field_def_lst[out] Container to store type information. + void retrieve_field_definitions(List<Create_field> *field_def_lst) const; + + /// Find SP-variable by name. + /// + /// The function does a linear search (from newer to older variables, + /// in case we have shadowed names). + /// + /// The function is called only at parsing time. + /// + /// @param name Variable name. + /// @param current_scope_only A flag if we search only in current scope. + /// + /// @return instance of found SP-variable, or NULL if not found. + sp_variable *find_variable(LEX_STRING name, bool current_scope_only) const; + + /// Find SP-variable by the offset in the root parsing context. + /// + /// The function is used for two things: + /// - When evaluating parameters at the beginning, and setting out parameters + /// at the end, of invocation. (Top frame only, so no recursion then.) + /// - For printing of sp_instr_set. (Debug mode only.) + /// + /// @param offset Variable offset in the root parsing context. + /// + /// @return instance of found SP-variable, or NULL if not found. + sp_variable *find_variable(uint offset) const; + + /// Set the current scope boundary (for default values). + /// + /// @param n The number of variables to skip. + void declare_var_boundary(uint n) + { m_pboundary= n; } + + ///////////////////////////////////////////////////////////////////////// + // CASE expressions. + ///////////////////////////////////////////////////////////////////////// + + int register_case_expr() + { return m_num_case_exprs++; } + + int get_num_case_exprs() const + { return m_num_case_exprs; } + + bool push_case_expr_id(int case_expr_id) + { return m_case_expr_ids.append(case_expr_id); } + + void pop_case_expr_id() + { m_case_expr_ids.pop(); } + + int get_current_case_expr_id() const + { return *m_case_expr_ids.back(); } + + ///////////////////////////////////////////////////////////////////////// + // Labels. + ///////////////////////////////////////////////////////////////////////// + + sp_label *push_label(THD *thd, LEX_STRING name, uint ip); + + sp_label *find_label(LEX_STRING name); + + sp_label *last_label() { - m_context_handlers+= n; - } - - // - // Cursors - // + sp_label *label= m_labels.head(); - int - push_cursor(LEX_STRING *name); + if (!label && m_parent) + label= m_parent->last_label(); - my_bool - find_cursor(LEX_STRING *name, uint *poff, my_bool scoped=0); - - /* Find by offset (for debugging only) */ - my_bool - find_cursor(uint offset, LEX_STRING *n); - - inline uint - max_cursor_index() - { - return m_max_cursor_index + m_cursors.elements; - } - - inline uint - current_cursor_count() - { - return m_cursor_offset + m_cursors.elements; + return label; } -protected: + sp_label *pop_label() + { return m_labels.pop(); } + + ///////////////////////////////////////////////////////////////////////// + // Conditions. + ///////////////////////////////////////////////////////////////////////// + + bool add_condition(THD *thd, LEX_STRING name, sp_condition_value *value); + + /// See comment for find_variable() above. + sp_condition_value *find_condition(LEX_STRING name, + bool current_scope_only) const; + + ///////////////////////////////////////////////////////////////////////// + // Handlers. + ///////////////////////////////////////////////////////////////////////// + + sp_handler *add_handler(THD* thd, sp_handler::enum_type type); + + /// This is an auxilary parsing-time function to check if an SQL-handler + /// exists in the current parsing context (current scope) for the given + /// SQL-condition. This function is used to check for duplicates during + /// the parsing phase. + /// + /// This function can not be used during the runtime phase to check + /// SQL-handler existence because it searches for the SQL-handler in the + /// current scope only (during runtime, current and parent scopes + /// should be checked according to the SQL-handler resolution rules). + /// + /// @param condition_value the handler condition value + /// (not SQL-condition!). + /// + /// @retval true if such SQL-handler exists. + /// @retval false otherwise. + bool check_duplicate_handler(const sp_condition_value *cond_value) const; + + /// Find an SQL handler for the given SQL condition according to the + /// SQL-handler resolution rules. This function is used at runtime. + /// + /// @param sql_state The SQL condition state + /// @param sql_errno The error code + /// @param level The SQL condition level + /// + /// @return a pointer to the found SQL-handler or NULL. + sp_handler *find_handler(const char *sql_state, + uint sql_errno, + Sql_condition::enum_warning_level level) const; + + ///////////////////////////////////////////////////////////////////////// + // Cursors. + ///////////////////////////////////////////////////////////////////////// + + bool add_cursor(LEX_STRING name); + + /// See comment for find_variable() above. + bool find_cursor(LEX_STRING name, uint *poff, bool current_scope_only) const; + + /// Find cursor by offset (for debugging only). + const LEX_STRING *find_cursor(uint offset) const; + + uint max_cursor_index() const + { return m_max_cursor_index + m_cursors.elements(); } + + uint current_cursor_count() const + { return m_cursor_offset + m_cursors.elements(); } - /** - Constructor for a tree node. - @param prev the parent parsing context - @param label_scope label_scope for this parsing context - */ - sp_pcontext(sp_pcontext *prev, label_scope_type label_scope); - - /* - m_max_var_index -- number of variables (including all types of arguments) - in this context including all children contexts. - - m_max_var_index >= m_vars.elements. +private: + /// Constructor for a tree node. + /// @param prev the parent parsing context + /// @param scope scope of this parsing context + sp_pcontext(sp_pcontext *prev, enum_scope scope); - m_max_var_index of the root parsing context contains number of all - variables (including arguments) in all enclosed contexts. - */ - uint m_max_var_index; + void init(uint var_offset, uint cursor_offset, int num_case_expressions); - // The maximum sub context's framesizes - uint m_max_cursor_index; - uint m_max_handler_index; - uint m_context_handlers; // No. of handlers in this context + /* Prevent use of these */ + sp_pcontext(const sp_pcontext &); + void operator=(sp_pcontext &); private: + /// m_max_var_index -- number of variables (including all types of arguments) + /// in this context including all children contexts. + /// + /// m_max_var_index >= m_vars.elements(). + /// + /// m_max_var_index of the root parsing context contains number of all + /// variables (including arguments) in all enclosed contexts. + uint m_max_var_index; + + /// The maximum sub context's framesizes. + uint m_max_cursor_index; - sp_pcontext *m_parent; // Parent context - - /* - m_var_offset -- this is an index of the first variable in this - parsing context. - - m_var_offset is 0 for root context. + /// Parent context. + sp_pcontext *m_parent; - Since now each variable is stored in separate place, no reuse is done, - so m_var_offset is different for all enclosed contexts. - */ + /// An index of the first SP-variable in this parsing context. The index + /// belongs to a runtime table of SP-variables. + /// + /// Note: + /// - m_var_offset is 0 for root parsing context; + /// - m_var_offset is different for all nested parsing contexts. uint m_var_offset; - uint m_cursor_offset; // Cursor offset for this context + /// Cursor offset for this context. + uint m_cursor_offset; - /* - Boundary for finding variables in this context. This is the number - of variables currently "invisible" to default clauses. - This is normally 0, but will be larger during parsing of - DECLARE ... DEFAULT, to get the scope right for DEFAULT values. - */ + /// Boundary for finding variables in this context. This is the number of + /// variables currently "invisible" to default clauses. This is normally 0, + /// but will be larger during parsing of DECLARE ... DEFAULT, to get the + /// scope right for DEFAULT values. uint m_pboundary; int m_num_case_exprs; - DYNAMIC_ARRAY m_vars; // Parameters/variables - DYNAMIC_ARRAY m_case_expr_id_lst; /* Stack of CASE expression ids. */ - DYNAMIC_ARRAY m_conds; // Conditions - DYNAMIC_ARRAY m_cursors; // Cursors - DYNAMIC_ARRAY m_handlers; // Handlers, for checking for duplicates + /// SP parameters/variables. + Dynamic_array<sp_variable *> m_vars; - List<sp_label_t> m_label; // The label list + /// Stack of CASE expression ids. + Dynamic_array<int> m_case_expr_ids; - List<sp_pcontext> m_children; // Children contexts, used for destruction + /// Stack of SQL-conditions. + Dynamic_array<sp_condition *> m_conditions; - /** - Scope of labels for this parsing context. - */ - label_scope_type m_label_scope; + /// Stack of cursors. + Dynamic_array<LEX_STRING> m_cursors; -private: - sp_pcontext(const sp_pcontext &); /* Prevent use of these */ - void operator=(sp_pcontext &); + /// Stack of SQL-handlers. + Dynamic_array<sp_handler *> m_handlers; + + /// List of labels. + List<sp_label> m_labels; + + /// Children contexts, used for destruction. + Dynamic_array<sp_pcontext *> m_children; + + /// Scope of this parsing context. + enum_scope m_scope; }; // class sp_pcontext : public Sql_alloc diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index 30acfebabb2..4d74d2721f1 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -13,6 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #ifdef USE_PRAGMA_IMPLEMENTATION @@ -26,23 +27,21 @@ #include "sp_pcontext.h" #include "sql_select.h" // create_virtual_tmp_table -sp_rcontext::sp_rcontext(sp_pcontext *root_parsing_ctx, +/////////////////////////////////////////////////////////////////////////// +// sp_rcontext implementation. +/////////////////////////////////////////////////////////////////////////// + + +sp_rcontext::sp_rcontext(const sp_pcontext *root_parsing_ctx, Field *return_value_fld, - sp_rcontext *prev_runtime_ctx) - :end_partial_result_set(FALSE), + bool in_sub_stmt) + :end_partial_result_set(false), m_root_parsing_ctx(root_parsing_ctx), - m_var_table(0), - m_var_items(0), + m_var_table(NULL), m_return_value_fld(return_value_fld), - m_return_value_set(FALSE), - in_sub_stmt(FALSE), - m_hcount(0), - m_hsp(0), - m_ihsp(0), - m_hfound(-1), - m_ccount(0), - m_case_expr_holders(0), - m_prev_runtime_ctx(prev_runtime_ctx) + m_return_value_set(false), + m_in_sub_stmt(in_sub_stmt), + m_ccount(0) { } @@ -51,422 +50,323 @@ sp_rcontext::~sp_rcontext() { if (m_var_table) free_blobs(m_var_table); + + // Leave m_handlers, m_handler_call_stack, m_var_items, m_cstack + // and m_case_expr_holders untouched. + // They are allocated in mem roots and will be freed accordingly. } -/* - Initialize sp_rcontext instance. +sp_rcontext *sp_rcontext::create(THD *thd, + const sp_pcontext *root_parsing_ctx, + Field *return_value_fld) +{ + sp_rcontext *ctx= new (thd->mem_root) sp_rcontext(root_parsing_ctx, + return_value_fld, + thd->in_sub_stmt); - SYNOPSIS - thd Thread handle - RETURN - FALSE on success - TRUE on error -*/ + if (!ctx) + return NULL; -bool sp_rcontext::init(THD *thd) -{ - uint handler_count= m_root_parsing_ctx->max_handler_index(); - - in_sub_stmt= thd->in_sub_stmt; - - if (init_var_table(thd) || init_var_items()) - return TRUE; - - if (!(m_raised_conditions= new (thd->mem_root) Sql_condition_info[handler_count])) - return TRUE; - - return - !(m_handler= - (sp_handler_t*)thd->alloc(handler_count * sizeof(sp_handler_t))) || - !(m_hstack= - (uint*)thd->alloc(handler_count * sizeof(uint))) || - !(m_in_handler= - (sp_active_handler_t*)thd->alloc(handler_count * - sizeof(sp_active_handler_t))) || - !(m_cstack= - (sp_cursor**)thd->alloc(m_root_parsing_ctx->max_cursor_index() * - sizeof(sp_cursor*))) || - !(m_case_expr_holders= - (Item_cache**)thd->calloc(m_root_parsing_ctx->get_num_case_exprs() * - sizeof (Item_cache*))); + if (ctx->alloc_arrays(thd) || + ctx->init_var_table(thd) || + ctx->init_var_items(thd)) + { + delete ctx; + return NULL; + } + + return ctx; } -/* - Create and initialize a table to store SP-vars. +bool sp_rcontext::alloc_arrays(THD *thd) +{ + { + size_t n= m_root_parsing_ctx->max_cursor_index(); + m_cstack.reset( + static_cast<sp_cursor **> ( + thd->alloc(n * sizeof (sp_cursor*))), + n); + } + + { + size_t n= m_root_parsing_ctx->get_num_case_exprs(); + m_case_expr_holders.reset( + static_cast<Item_cache **> ( + thd->calloc(n * sizeof (Item_cache*))), + n); + } + + return !m_cstack.array() || !m_case_expr_holders.array(); +} - SYNOPSIS - thd Thread handler. - RETURN - FALSE on success - TRUE on error -*/ -bool -sp_rcontext::init_var_table(THD *thd) +bool sp_rcontext::init_var_table(THD *thd) { List<Create_field> field_def_lst; if (!m_root_parsing_ctx->max_var_index()) - return FALSE; + return false; m_root_parsing_ctx->retrieve_field_definitions(&field_def_lst); DBUG_ASSERT(field_def_lst.elements == m_root_parsing_ctx->max_var_index()); - + if (!(m_var_table= create_virtual_tmp_table(thd, field_def_lst))) - return TRUE; + return true; - m_var_table->copy_blobs= TRUE; - m_var_table->alias.set("", 0, table_alias_charset); + m_var_table->copy_blobs= true; + m_var_table->alias.set("", 0, m_var_table->alias.charset()); - return FALSE; + return false; } -/* - Create and initialize an Item-adapter (Item_field) for each SP-var field. - - RETURN - FALSE on success - TRUE on error -*/ - -bool -sp_rcontext::init_var_items() +bool sp_rcontext::init_var_items(THD *thd) { - uint idx; uint num_vars= m_root_parsing_ctx->max_var_index(); - if (!(m_var_items= (Item**) sql_alloc(num_vars * sizeof (Item *)))) - return TRUE; + m_var_items.reset( + static_cast<Item **> ( + thd->alloc(num_vars * sizeof (Item *))), + num_vars); + + if (!m_var_items.array()) + return true; - for (idx = 0; idx < num_vars; ++idx) + for (uint idx = 0; idx < num_vars; ++idx) { - if (!(m_var_items[idx]= new Item_field(m_var_table->field[idx]))) - return TRUE; + if (!(m_var_items[idx]= new (thd->mem_root) Item_field(thd, m_var_table->field[idx]))) + return true; } - return FALSE; + return false; } -bool -sp_rcontext::set_return_value(THD *thd, Item **return_value_item) +bool sp_rcontext::set_return_value(THD *thd, Item **return_value_item) { DBUG_ASSERT(m_return_value_fld); - m_return_value_set = TRUE; + m_return_value_set = true; return sp_eval_expr(thd, m_return_value_fld, return_value_item); } -#define IS_WARNING_CONDITION(S) ((S)[0] == '0' && (S)[1] == '1') -#define IS_NOT_FOUND_CONDITION(S) ((S)[0] == '0' && (S)[1] == '2') -#define IS_EXCEPTION_CONDITION(S) ((S)[0] != '0' || (S)[1] > '2') - -/** - Find an SQL handler for the given error. - - SQL handlers are pushed on the stack m_handler, with the latest/innermost - one on the top; we then search for matching handlers from the top and - down. - - We search through all the handlers, looking for the most specific one - (sql_errno more specific than sqlstate more specific than the rest). - Note that mysql error code handlers is a MySQL extension, not part of - the standard. - - SQL handlers for warnings are searched in the current scope only. - - SQL handlers for errors are searched in the current and in outer scopes. - That's why finding and activation of handler must be separated: an errror - handler might be located in the outer scope, which is not active at the - moment. Before such handler can be activated, execution flow should - unwind to that scope. - - Found SQL handler is remembered in m_hfound for future activation. - If no handler is found, m_hfound is -1. - - @param thd Thread handle - @param sql_errno The error code - @param sqlstate The error SQL state - @param level The error level - @param msg The error message - - @retval TRUE if an SQL handler was found - @retval FALSE otherwise -*/ - -bool -sp_rcontext::find_handler(THD *thd, - uint sql_errno, - const char *sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char *msg) +bool sp_rcontext::push_cursor(THD *thd, sp_lex_keeper *lex_keeper, + sp_instr_cpush *i) { - int i= m_hcount; - - /* Reset previously found handler. */ - m_hfound= -1; - /* - If this is a fatal sub-statement error, and this runtime - context corresponds to a sub-statement, no CONTINUE/EXIT - handlers from this context are applicable: try to locate one - in the outer scope. + We should create cursors in the callers arena, as + it could be (and usually is) used in several instructions. */ - if (thd->is_fatal_sub_stmt_error && in_sub_stmt) - i= 0; - - /* Search handlers from the latest (innermost) to the oldest (outermost) */ - while (i--) - { - sp_cond_type_t *cond= m_handler[i].cond; - int j= m_ihsp; - - /* Check active handlers, to avoid invoking one recursively */ - while (j--) - if (m_in_handler[j].ip == m_handler[i].handler) - break; - if (j >= 0) - continue; // Already executing this handler + sp_cursor *c= new (callers_arena->mem_root) sp_cursor(thd, lex_keeper, i); - switch (cond->type) - { - case sp_cond_type_t::number: - if (sql_errno == cond->mysqlerr && - (m_hfound < 0 || m_handler[m_hfound].cond->type > sp_cond_type_t::number)) - m_hfound= i; // Always the most specific - break; - case sp_cond_type_t::state: - if (strcmp(sqlstate, cond->sqlstate) == 0 && - (m_hfound < 0 || m_handler[m_hfound].cond->type > sp_cond_type_t::state)) - m_hfound= i; - break; - case sp_cond_type_t::warning: - if ((IS_WARNING_CONDITION(sqlstate) || - level == MYSQL_ERROR::WARN_LEVEL_WARN) && - m_hfound < 0) - m_hfound= i; - break; - case sp_cond_type_t::notfound: - if (IS_NOT_FOUND_CONDITION(sqlstate) && m_hfound < 0) - m_hfound= i; - break; - case sp_cond_type_t::exception: - if (IS_EXCEPTION_CONDITION(sqlstate) && - level == MYSQL_ERROR::WARN_LEVEL_ERROR && - m_hfound < 0) - m_hfound= i; - break; - } - } - - if (m_hfound >= 0) - { - DBUG_ASSERT((uint) m_hfound < m_root_parsing_ctx->max_handler_index()); - - m_raised_conditions[m_hfound].clear(); - m_raised_conditions[m_hfound].set(sql_errno, sqlstate, level, msg); - - return TRUE; - } + if (c == NULL) + return true; - /* - Only "exception conditions" are propagated to handlers in calling - contexts. If no handler is found locally for a "completion condition" - (warning or "not found") we will simply resume execution. - */ - if (m_prev_runtime_ctx && IS_EXCEPTION_CONDITION(sqlstate) && - level == MYSQL_ERROR::WARN_LEVEL_ERROR) - { - return m_prev_runtime_ctx->find_handler(thd, sql_errno, sqlstate, - level, msg); - } - - return FALSE; + m_cstack[m_ccount++]= c; + return false; } -void -sp_rcontext::push_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i) -{ - DBUG_ENTER("sp_rcontext::push_cursor"); - DBUG_ASSERT(m_ccount < m_root_parsing_ctx->max_cursor_index()); - m_cstack[m_ccount++]= new sp_cursor(lex_keeper, i); - DBUG_PRINT("info", ("m_ccount: %d", m_ccount)); - DBUG_VOID_RETURN; -} -void -sp_rcontext::pop_cursors(uint count) +void sp_rcontext::pop_cursors(uint count) { - DBUG_ENTER("sp_rcontext::pop_cursors"); DBUG_ASSERT(m_ccount >= count); + while (count--) - { delete m_cstack[--m_ccount]; - } - DBUG_PRINT("info", ("m_ccount: %d", m_ccount)); - DBUG_VOID_RETURN; } -void -sp_rcontext::push_handler(struct sp_cond_type *cond, uint h, int type) -{ - DBUG_ENTER("sp_rcontext::push_handler"); - DBUG_ASSERT(m_hcount < m_root_parsing_ctx->max_handler_index()); - - m_handler[m_hcount].cond= cond; - m_handler[m_hcount].handler= h; - m_handler[m_hcount].type= type; - m_hcount+= 1; - DBUG_PRINT("info", ("m_hcount: %d", m_hcount)); - DBUG_VOID_RETURN; -} - -void -sp_rcontext::pop_handlers(uint count) +bool sp_rcontext::push_handler(sp_handler *handler, uint first_ip) { - DBUG_ENTER("sp_rcontext::pop_handlers"); - DBUG_ASSERT(m_hcount >= count); + /* + We should create handler entries in the callers arena, as + they could be (and usually are) used in several instructions. + */ + sp_handler_entry *he= + new (callers_arena->mem_root) sp_handler_entry(handler, first_ip); - m_hcount-= count; + if (he == NULL) + return true; - DBUG_PRINT("info", ("m_hcount: %d", m_hcount)); - DBUG_VOID_RETURN; + return m_handlers.append(he); } -void -sp_rcontext::push_hstack(uint h) -{ - DBUG_ENTER("sp_rcontext::push_hstack"); - DBUG_ASSERT(m_hsp < m_root_parsing_ctx->max_handler_index()); - - m_hstack[m_hsp++]= h; - DBUG_PRINT("info", ("m_hsp: %d", m_hsp)); - DBUG_VOID_RETURN; -} - -uint -sp_rcontext::pop_hstack() +void sp_rcontext::pop_handlers(size_t count) { - uint handler; - DBUG_ENTER("sp_rcontext::pop_hstack"); - DBUG_ASSERT(m_hsp); - - handler= m_hstack[--m_hsp]; + DBUG_ASSERT(m_handlers.elements() >= count); - DBUG_PRINT("info", ("m_hsp: %d", m_hsp)); - DBUG_RETURN(handler); + for (size_t i= 0; i < count; ++i) + m_handlers.pop(); } -/** - Prepare found handler to be executed. - - @retval TRUE if an SQL handler is activated (was found) and IP of the - first handler instruction. - @retval FALSE if there is no active handler -*/ -bool -sp_rcontext::activate_handler(THD *thd, - uint *ip, - sp_instr *instr, - Query_arena *execute_arena, - Query_arena *backup_arena) +bool sp_rcontext::handle_sql_condition(THD *thd, + uint *ip, + const sp_instr *cur_spi) { - if (m_hfound < 0) - return FALSE; + DBUG_ENTER("sp_rcontext::handle_sql_condition"); - switch (m_handler[m_hfound].type) { - case SP_HANDLER_NONE: - break; + /* + If this is a fatal sub-statement error, and this runtime + context corresponds to a sub-statement, no CONTINUE/EXIT + handlers from this context are applicable: try to locate one + in the outer scope. + */ + if (thd->is_fatal_sub_stmt_error && m_in_sub_stmt) + DBUG_RETURN(false); - case SP_HANDLER_CONTINUE: - thd->restore_active_arena(execute_arena, backup_arena); - thd->set_n_backup_active_arena(execute_arena, backup_arena); - push_hstack(instr->get_cont_dest()); + Diagnostics_area *da= thd->get_stmt_da(); + const sp_handler *found_handler= NULL; + const Sql_condition *found_condition= NULL; - /* Fall through */ + if (thd->is_error()) + { + found_handler= + cur_spi->m_ctx->find_handler(da->get_sqlstate(), + da->sql_errno(), + Sql_condition::WARN_LEVEL_ERROR); + + if (found_handler) + found_condition= da->get_error_condition(); + + /* + Found condition can be NULL if the diagnostics area was full + when the error was raised. It can also be NULL if + Diagnostics_area::set_error_status(uint sql_error) was used. + In these cases, make a temporary Sql_condition here so the + error can be handled. + */ + if (!found_condition) + { + Sql_condition *condition= + new (callers_arena->mem_root) Sql_condition(callers_arena->mem_root); + condition->set(da->sql_errno(), da->get_sqlstate(), + Sql_condition::WARN_LEVEL_ERROR, + da->message()); + found_condition= condition; + } + } + else if (da->current_statement_warn_count()) + { + Diagnostics_area::Sql_condition_iterator it= da->sql_conditions(); + const Sql_condition *c; - default: - /* End aborted result set. */ + // Here we need to find the last warning/note from the stack. + // In MySQL most substantial warning is the last one. + // (We could have used a reverse iterator here if one existed) - if (end_partial_result_set) - thd->protocol->end_partial_result_set(thd); + while ((c= it++)) + { + if (c->get_level() == Sql_condition::WARN_LEVEL_WARN || + c->get_level() == Sql_condition::WARN_LEVEL_NOTE) + { + const sp_handler *handler= + cur_spi->m_ctx->find_handler(c->get_sqlstate(), + c->get_sql_errno(), + c->get_level()); + if (handler) + { + found_handler= handler; + found_condition= c; + } + } + } + } - /* Enter handler. */ + if (!found_handler) + DBUG_RETURN(false); - DBUG_ASSERT(m_ihsp < m_root_parsing_ctx->max_handler_index()); - DBUG_ASSERT(m_hfound >= 0); + // At this point, we know that: + // - there is a pending SQL-condition (error or warning); + // - there is an SQL-handler for it. - m_in_handler[m_ihsp].ip= m_handler[m_hfound].handler; - m_in_handler[m_ihsp].index= m_hfound; - m_ihsp++; + DBUG_ASSERT(found_condition); - DBUG_PRINT("info", ("Entering handler...")); - DBUG_PRINT("info", ("m_ihsp: %d", m_ihsp)); + sp_handler_entry *handler_entry= NULL; + for (size_t i= 0; i < m_handlers.elements(); ++i) + { + sp_handler_entry *h= m_handlers.at(i); - /* Reset error state. */ + if (h->handler == found_handler) + { + handler_entry= h; + break; + } + } - thd->clear_error(); - thd->reset_killed(); // Some errors set thd->killed - // (e.g. "bad data"). + /* + handler_entry usually should not be NULL here, as that indicates + that the parser context thinks a HANDLER should be activated, + but the runtime context cannot find it. + + However, this can happen (and this is in line with the Standard) + if SQL-condition has been raised before DECLARE HANDLER instruction + is processed. + + For example: + CREATE PROCEDURE p() + BEGIN + DECLARE v INT DEFAULT 'get'; -- raises SQL-warning here + DECLARE EXIT HANDLER ... -- this handler does not catch the warning + END + */ + if (!handler_entry) + DBUG_RETURN(false); - /* Return IP of the activated SQL handler. */ - *ip= m_handler[m_hfound].handler; + // Mark active conditions so that they can be deleted when the handler exits. + da->mark_sql_conditions_for_removal(); - /* Reset found handler. */ - m_hfound= -1; - } + uint continue_ip= handler_entry->handler->type == sp_handler::CONTINUE ? + cur_spi->get_cont_dest() : 0; - return TRUE; -} + /* End aborted result set. */ + if (end_partial_result_set) + thd->protocol->end_partial_result_set(thd); -void -sp_rcontext::exit_handler() -{ - DBUG_ENTER("sp_rcontext::exit_handler"); - DBUG_ASSERT(m_ihsp); + /* Reset error state. */ + thd->clear_error(); + thd->reset_killed(); // Some errors set thd->killed, (e.g. "bad data"). + + /* Add a frame to handler-call-stack. */ + Sql_condition_info *cond_info= + new (callers_arena->mem_root) Sql_condition_info(found_condition, + callers_arena); + Handler_call_frame *frame= + new (callers_arena->mem_root) Handler_call_frame(cond_info, continue_ip); + m_handler_call_stack.append(frame); - uint hindex= m_in_handler[m_ihsp-1].index; - m_raised_conditions[hindex].clear(); - m_ihsp-= 1; + *ip= handler_entry->first_ip; - DBUG_PRINT("info", ("m_ihsp: %d", m_ihsp)); - DBUG_VOID_RETURN; + DBUG_RETURN(true); } -Sql_condition_info* sp_rcontext::raised_condition() const + +uint sp_rcontext::exit_handler(Diagnostics_area *da) { - if (m_ihsp > 0) - { - uint hindex= m_in_handler[m_ihsp - 1].index; - Sql_condition_info *raised= & m_raised_conditions[hindex]; - return raised; - } + DBUG_ENTER("sp_rcontext::exit_handler"); + DBUG_ASSERT(m_handler_call_stack.elements() > 0); - if (m_prev_runtime_ctx) - return m_prev_runtime_ctx->raised_condition(); + Handler_call_frame *f= m_handler_call_stack.pop(); - return NULL; -} + /* + Remove the SQL conditions that were present in DA when the + handler was activated. + */ + da->remove_marked_sql_conditions(); + uint continue_ip= f->continue_ip; -int -sp_rcontext::set_variable(THD *thd, uint var_idx, Item **value) -{ - return set_variable(thd, m_var_table->field[var_idx], value); + DBUG_RETURN(continue_ip); } -int -sp_rcontext::set_variable(THD *thd, Field *field, Item **value) +int sp_rcontext::set_variable(THD *thd, Field *field, Item **value) { if (!value) { @@ -478,28 +378,51 @@ sp_rcontext::set_variable(THD *thd, Field *field, Item **value) } -Item * -sp_rcontext::get_item(uint var_idx) +Item_cache *sp_rcontext::create_case_expr_holder(THD *thd, + const Item *item) const { - return m_var_items[var_idx]; + Item_cache *holder; + Query_arena current_arena; + + thd->set_n_backup_active_arena(thd->spcont->callers_arena, ¤t_arena); + + holder= Item_cache::get_cache(thd, item); + + thd->restore_active_arena(thd->spcont->callers_arena, ¤t_arena); + + return holder; } -Item ** -sp_rcontext::get_item_addr(uint var_idx) +bool sp_rcontext::set_case_expr(THD *thd, int case_expr_id, + Item **case_expr_item_ptr) { - return m_var_items + var_idx; + Item *case_expr_item= sp_prepare_func_item(thd, case_expr_item_ptr); + if (!case_expr_item) + return true; + + if (!m_case_expr_holders[case_expr_id] || + m_case_expr_holders[case_expr_id]->result_type() != + case_expr_item->result_type()) + { + m_case_expr_holders[case_expr_id]= + create_case_expr_holder(thd, case_expr_item); + } + + m_case_expr_holders[case_expr_id]->store(case_expr_item); + m_case_expr_holders[case_expr_id]->cache_value(); + return false; } -/* - * - * sp_cursor - * - */ +/////////////////////////////////////////////////////////////////////////// +// sp_cursor implementation. +/////////////////////////////////////////////////////////////////////////// + -sp_cursor::sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i) - :m_lex_keeper(lex_keeper), +sp_cursor::sp_cursor(THD *thd_arg, sp_lex_keeper *lex_keeper, sp_instr_cpush *i): + result(thd_arg), + m_lex_keeper(lex_keeper), server_side_cursor(NULL), m_i(i) { @@ -523,12 +446,12 @@ sp_cursor::sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i) 0 in case of success, -1 otherwise */ -int -sp_cursor::open(THD *thd) +int sp_cursor::open(THD *thd) { if (server_side_cursor) { - my_message(ER_SP_CURSOR_ALREADY_OPEN, ER(ER_SP_CURSOR_ALREADY_OPEN), + my_message(ER_SP_CURSOR_ALREADY_OPEN, + ER_THD(thd, ER_SP_CURSOR_ALREADY_OPEN), MYF(0)); return -1; } @@ -538,12 +461,12 @@ sp_cursor::open(THD *thd) } -int -sp_cursor::close(THD *thd) +int sp_cursor::close(THD *thd) { if (! server_side_cursor) { - my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0)); + my_message(ER_SP_CURSOR_NOT_OPEN, ER_THD(thd, ER_SP_CURSOR_NOT_OPEN), + MYF(0)); return -1; } destroy(); @@ -551,33 +474,32 @@ sp_cursor::close(THD *thd) } -void -sp_cursor::destroy() +void sp_cursor::destroy() { delete server_side_cursor; - server_side_cursor= 0; + server_side_cursor= NULL; } -int -sp_cursor::fetch(THD *thd, List<struct sp_variable> *vars) +int sp_cursor::fetch(THD *thd, List<sp_variable> *vars) { if (! server_side_cursor) { - my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0)); + my_message(ER_SP_CURSOR_NOT_OPEN, ER_THD(thd, ER_SP_CURSOR_NOT_OPEN), + MYF(0)); return -1; } if (vars->elements != result.get_field_count()) { my_message(ER_SP_WRONG_NO_OF_FETCH_ARGS, - ER(ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0)); + ER_THD(thd, ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0)); return -1; } DBUG_EXECUTE_IF("bug23032_emit_warning", - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, - ER(ER_UNKNOWN_ERROR));); + ER_THD(thd, ER_UNKNOWN_ERROR));); result.set_spvar_list(vars); @@ -591,7 +513,7 @@ sp_cursor::fetch(THD *thd, List<struct sp_variable> *vars) */ if (! server_side_cursor->is_open()) { - my_message(ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA), MYF(0)); + my_message(ER_SP_FETCH_NO_DATA, ER_THD(thd, ER_SP_FETCH_NO_DATA), MYF(0)); return -1; } @@ -599,108 +521,13 @@ sp_cursor::fetch(THD *thd, List<struct sp_variable> *vars) } -/* - Create an instance of appropriate Item_cache class depending on the - specified type in the callers arena. - - SYNOPSIS - thd thread handler - result_type type of the expression +/////////////////////////////////////////////////////////////////////////// +// sp_cursor::Select_fetch_into_spvars implementation. +/////////////////////////////////////////////////////////////////////////// - RETURN - Pointer to valid object on success - NULL on error - NOTE - We should create cache items in the callers arena, as they are used - between in several instructions. -*/ - -Item_cache * -sp_rcontext::create_case_expr_holder(THD *thd, const Item *item) -{ - Item_cache *holder; - Query_arena current_arena; - - thd->set_n_backup_active_arena(thd->spcont->callers_arena, ¤t_arena); - - holder= Item_cache::get_cache(item); - - thd->restore_active_arena(thd->spcont->callers_arena, ¤t_arena); - - return holder; -} - - -/* - Set CASE expression to the specified value. - - SYNOPSIS - thd thread handler - case_expr_id identifier of the CASE expression - case_expr_item a value of the CASE expression - - RETURN - FALSE on success - TRUE on error - - NOTE - The idea is to reuse Item_cache for the expression of the one CASE - statement. This optimization takes place when there is CASE statement - inside of a loop. So, in other words, we will use the same object on each - iteration instead of creating a new one for each iteration. - - TODO - Hypothetically, a type of CASE expression can be different for each - iteration. For instance, this can happen if the expression contains a - session variable (something like @@VAR) and its type is changed from one - iteration to another. - - In order to cope with this problem, we check type each time, when we use - already created object. If the type does not match, we re-create Item. - This also can (should?) be optimized. -*/ - -int -sp_rcontext::set_case_expr(THD *thd, int case_expr_id, Item **case_expr_item_ptr) -{ - Item *case_expr_item= sp_prepare_func_item(thd, case_expr_item_ptr); - if (!case_expr_item) - return TRUE; - - if (!m_case_expr_holders[case_expr_id] || - m_case_expr_holders[case_expr_id]->result_type() != - case_expr_item->result_type()) - { - m_case_expr_holders[case_expr_id]= - create_case_expr_holder(thd, case_expr_item); - } - - m_case_expr_holders[case_expr_id]->store(case_expr_item); - m_case_expr_holders[case_expr_id]->cache_value(); - return FALSE; -} - - -Item * -sp_rcontext::get_case_expr(int case_expr_id) -{ - return m_case_expr_holders[case_expr_id]; -} - - -Item ** -sp_rcontext::get_case_expr_addr(int case_expr_id) -{ - return (Item**) m_case_expr_holders + case_expr_id; -} - - -/*************************************************************************** - Select_fetch_into_spvars -****************************************************************************/ - -int Select_fetch_into_spvars::prepare(List<Item> &fields, SELECT_LEX_UNIT *u) +int sp_cursor::Select_fetch_into_spvars::prepare(List<Item> &fields, + SELECT_LEX_UNIT *u) { /* Cache the number of columns in the result set in order to easily @@ -711,11 +538,11 @@ int Select_fetch_into_spvars::prepare(List<Item> &fields, SELECT_LEX_UNIT *u) } -int Select_fetch_into_spvars::send_data(List<Item> &items) +int sp_cursor::Select_fetch_into_spvars::send_data(List<Item> &items) { - List_iterator_fast<struct sp_variable> spvar_iter(*spvar_list); + List_iterator_fast<sp_variable> spvar_iter(*spvar_list); List_iterator_fast<Item> item_iter(items); - sp_variable_t *spvar; + sp_variable *spvar; Item *item; /* Must be ensured by the caller */ @@ -728,7 +555,7 @@ int Select_fetch_into_spvars::send_data(List<Item> &items) for (; spvar= spvar_iter++, item= item_iter++; ) { if (thd->spcont->set_variable(thd, spvar->offset, &item)) - return 1; + return true; } - return 0; + return false; } diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index 5008a73d96c..2640490fefa 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -22,80 +22,18 @@ #endif #include "sql_class.h" // select_result_interceptor +#include "sp_pcontext.h" // sp_condition_value + +/////////////////////////////////////////////////////////////////////////// +// sp_rcontext declaration. +/////////////////////////////////////////////////////////////////////////// -struct sp_cond_type; class sp_cursor; -struct sp_variable; class sp_lex_keeper; class sp_instr_cpush; class Query_arena; class sp_head; -class sp_pcontext; class Item_cache; -typedef class st_select_lex_unit SELECT_LEX_UNIT; -class Server_side_cursor; - -#define SP_HANDLER_NONE 0 -#define SP_HANDLER_EXIT 1 -#define SP_HANDLER_CONTINUE 2 -#define SP_HANDLER_UNDO 3 - -typedef struct -{ - /** Condition caught by this HANDLER. */ - struct sp_cond_type *cond; - /** Location (instruction pointer) of the handler code. */ - uint handler; - /** Handler type (EXIT, CONTINUE). */ - int type; -} sp_handler_t; - -typedef struct -{ - /** Instruction pointer of the active handler. */ - uint ip; - /** Handler index of the active handler. */ - uint index; -} sp_active_handler_t; - - -class Sql_condition_info : public Sql_alloc -{ -public: - /** SQL error code. */ - uint m_sql_errno; - - /** Error level. */ - MYSQL_ERROR::enum_warning_level m_level; - - /** SQLSTATE. */ - char m_sql_state[SQLSTATE_LENGTH + 1]; - - /** Text message. */ - char m_message[MYSQL_ERRMSG_SIZE]; - - void set(uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char* msg) - { - m_sql_errno= sql_errno; - m_level= level; - - memcpy(m_sql_state, sqlstate, SQLSTATE_LENGTH); - m_sql_state[SQLSTATE_LENGTH]= '\0'; - - strncpy(m_message, msg, MYSQL_ERRMSG_SIZE); - } - - void clear() - { - m_sql_errno= 0; - m_level= MYSQL_ERROR::WARN_LEVEL_ERROR; - - m_sql_state[0]= '\0'; - m_message[0]= '\0'; - } -}; /* @@ -119,251 +57,412 @@ public: class sp_rcontext : public Sql_alloc { - sp_rcontext(const sp_rcontext &); /* Prevent use of these */ - void operator=(sp_rcontext &); - - public: - - /* - Arena used to (re) allocate items on . E.g. reallocate INOUT/OUT - SP parameters when they don't fit into prealloced items. This - is common situation with String items. It is used mainly in - sp_eval_func_item(). - */ - Query_arena *callers_arena; - - /* - End a open result set before start executing a continue/exit - handler if one is found as otherwise the client will hang - due to a violation of the client/server protocol. - */ - bool end_partial_result_set; - -#ifndef DBUG_OFF - /* - The routine for which this runtime context is created. Used for checking - if correct runtime context is used for variable handling. - */ - sp_head *sp; -#endif - - sp_rcontext(sp_pcontext *root_parsing_ctx, Field *return_value_fld, - sp_rcontext *prev_runtime_ctx); - bool init(THD *thd); +public: + /// Construct and properly initialize a new sp_rcontext instance. The static + /// create-function is needed because we need a way to return an error from + /// the constructor. + /// + /// @param thd Thread handle. + /// @param root_parsing_ctx Top-level parsing context for this stored program. + /// @param return_value_fld Field object to store the return value + /// (for stored functions only). + /// + /// @return valid sp_rcontext object or NULL in case of OOM-error. + static sp_rcontext *create(THD *thd, + const sp_pcontext *root_parsing_ctx, + Field *return_value_fld); ~sp_rcontext(); - int - set_variable(THD *thd, uint var_idx, Item **value); - - Item * - get_item(uint var_idx); +private: + sp_rcontext(const sp_pcontext *root_parsing_ctx, + Field *return_value_fld, + bool in_sub_stmt); - Item ** - get_item_addr(uint var_idx); + // Prevent use of copying constructor and operator. + sp_rcontext(const sp_rcontext &); + void operator=(sp_rcontext &); - bool - set_return_value(THD *thd, Item **return_value_item); +private: + /// This is an auxillary class to store entering instruction pointer for an + /// SQL-handler. + class sp_handler_entry : public Sql_alloc + { + public: + /// Handler definition (from parsing context). + const sp_handler *handler; + + /// Instruction pointer to the first instruction. + uint first_ip; + + /// The constructor. + /// + /// @param _handler sp_handler object. + /// @param _first_ip first instruction pointer. + sp_handler_entry(const sp_handler *_handler, uint _first_ip) + :handler(_handler), first_ip(_first_ip) + { } + }; - inline bool - is_return_value_set() const +public: + /// This class stores basic information about SQL-condition, such as: + /// - SQL error code; + /// - error level; + /// - SQLSTATE; + /// - text message. + /// + /// It's used to organize runtime SQL-handler call stack. + /// + /// Standard Sql_condition class can not be used, because we don't always have + /// an Sql_condition object for an SQL-condition in Diagnostics_area. + /// + /// Eventually, this class should be moved to sql_error.h, and be a part of + /// standard SQL-condition processing (Diagnostics_area should contain an + /// object for active SQL-condition, not just information stored in DA's + /// fields). + class Sql_condition_info : public Sql_alloc { - return m_return_value_set; - } + public: + /// SQL error code. + uint sql_errno; + + /// Error level. + Sql_condition::enum_warning_level level; + + /// SQLSTATE. + char sql_state[SQLSTATE_LENGTH + 1]; + + /// Text message. + char *message; + + /// The constructor. + /// + /// @param _sql_condition The SQL condition. + /// @param arena Query arena for SP + Sql_condition_info(const Sql_condition *_sql_condition, + Query_arena *arena) + :sql_errno(_sql_condition->get_sql_errno()), + level(_sql_condition->get_level()) + { + memcpy(sql_state, _sql_condition->get_sqlstate(), SQLSTATE_LENGTH); + sql_state[SQLSTATE_LENGTH]= '\0'; + + message= strdup_root(arena->mem_root, _sql_condition->get_message_text()); + } + }; - /* - SQL handlers support. - */ +private: + /// This class represents a call frame of SQL-handler (one invocation of a + /// handler). Basically, it's needed to store continue instruction pointer for + /// CONTINUE SQL-handlers. + class Handler_call_frame : public Sql_alloc + { + public: + /// SQL-condition, triggered handler activation. + const Sql_condition_info *sql_condition; + + /// Continue-instruction-pointer for CONTINUE-handlers. + /// The attribute contains 0 for EXIT-handlers. + uint continue_ip; + + /// The constructor. + /// + /// @param _sql_condition SQL-condition, triggered handler activation. + /// @param _continue_ip Continue instruction pointer. + Handler_call_frame(const Sql_condition_info *_sql_condition, + uint _continue_ip) + :sql_condition(_sql_condition), + continue_ip(_continue_ip) + { } + }; - void push_handler(struct sp_cond_type *cond, uint h, int type); +public: + /// Arena used to (re) allocate items on. E.g. reallocate INOUT/OUT + /// SP-variables when they don't fit into prealloced items. This is common + /// situation with String items. It is used mainly in sp_eval_func_item(). + Query_arena *callers_arena; - void pop_handlers(uint count); + /// Flag to end an open result set before start executing an SQL-handler + /// (if one is found). Otherwise the client will hang due to a violation + /// of the client/server protocol. + bool end_partial_result_set; - bool - find_handler(THD *thd, - uint sql_errno, - const char *sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char *msg); +#ifndef DBUG_OFF + /// The stored program for which this runtime context is created. Used for + /// checking if correct runtime context is used for variable handling. + sp_head *sp; +#endif - Sql_condition_info *raised_condition() const; + ///////////////////////////////////////////////////////////////////////// + // SP-variables. + ///////////////////////////////////////////////////////////////////////// - void - push_hstack(uint h); + int set_variable(THD *thd, uint var_idx, Item **value) + { return set_variable(thd, m_var_table->field[var_idx], value); } - uint - pop_hstack(); + Item *get_item(uint var_idx) const + { return m_var_items[var_idx]; } - bool - activate_handler(THD *thd, - uint *ip, - sp_instr *instr, - Query_arena *execute_arena, - Query_arena *backup_arena); + Item **get_item_addr(uint var_idx) const + { return m_var_items.array() + var_idx; } + bool set_return_value(THD *thd, Item **return_value_item); - void - exit_handler(); + bool is_return_value_set() const + { return m_return_value_set; } - void - push_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i); + ///////////////////////////////////////////////////////////////////////// + // SQL-handlers. + ///////////////////////////////////////////////////////////////////////// - void - pop_cursors(uint count); + /// Create a new sp_handler_entry instance and push it to the handler call + /// stack. + /// + /// @param handler SQL-handler object. + /// @param first_ip First instruction pointer of the handler. + /// + /// @return error flag. + /// @retval false on success. + /// @retval true on error. + bool push_handler(sp_handler *handler, uint first_ip); - inline void - pop_all_cursors() - { - pop_cursors(m_ccount); - } + /// Pop and delete given number of sp_handler_entry instances from the handler + /// call stack. + /// + /// @param count Number of handler entries to pop & delete. + void pop_handlers(size_t count); - inline sp_cursor * - get_cursor(uint i) + const Sql_condition_info *raised_condition() const { - return m_cstack[i]; + return m_handler_call_stack.elements() ? + (*m_handler_call_stack.back())->sql_condition : NULL; } - /* - CASE expressions support. - */ + /// Handle current SQL condition (if any). + /// + /// This is the public-interface function to handle SQL conditions in + /// stored routines. + /// + /// @param thd Thread handle. + /// @param ip[out] Instruction pointer to the first handler + /// instruction. + /// @param cur_spi Current SP instruction. + /// + /// @retval true if an SQL-handler has been activated. That means, all of + /// the following conditions are satisfied: + /// - the SP-instruction raised SQL-condition(s), + /// - and there is an SQL-handler to process at least one of those + /// SQL-conditions, + /// - and that SQL-handler has been activated. + /// Note, that the return value has nothing to do with "error flag" + /// semantics. + /// + /// @retval false otherwise. + bool handle_sql_condition(THD *thd, + uint *ip, + const sp_instr *cur_spi); + + /// Remove latest call frame from the handler call stack. + /// + /// @param da Diagnostics area containing handled conditions. + /// + /// @return continue instruction pointer of the removed handler. + uint exit_handler(Diagnostics_area *da); + + ///////////////////////////////////////////////////////////////////////// + // Cursors. + ///////////////////////////////////////////////////////////////////////// + + /// Create a new sp_cursor instance and push it to the cursor stack. + /// + /// @param lex_keeper SP-instruction execution helper. + /// @param i Cursor-push instruction. + /// + /// @return error flag. + /// @retval false on success. + /// @retval true on error. + bool push_cursor(THD *thd, sp_lex_keeper *lex_keeper, sp_instr_cpush *i); + + /// Pop and delete given number of sp_cursor instance from the cursor stack. + /// + /// @param count Number of cursors to pop & delete. + void pop_cursors(uint count); + + void pop_all_cursors() + { pop_cursors(m_ccount); } + + sp_cursor *get_cursor(uint i) const + { return m_cstack[i]; } + + ///////////////////////////////////////////////////////////////////////// + // CASE expressions. + ///////////////////////////////////////////////////////////////////////// + + /// Set CASE expression to the specified value. + /// + /// @param thd Thread handler. + /// @param case_expr_id The CASE expression identifier. + /// @param case_expr_item The CASE expression value + /// + /// @return error flag. + /// @retval false on success. + /// @retval true on error. + /// + /// @note The idea is to reuse Item_cache for the expression of the one + /// CASE statement. This optimization takes place when there is CASE + /// statement inside of a loop. So, in other words, we will use the same + /// object on each iteration instead of creating a new one for each + /// iteration. + /// + /// TODO + /// Hypothetically, a type of CASE expression can be different for each + /// iteration. For instance, this can happen if the expression contains + /// a session variable (something like @@VAR) and its type is changed + /// from one iteration to another. + /// + /// In order to cope with this problem, we check type each time, when we + /// use already created object. If the type does not match, we re-create + /// Item. This also can (should?) be optimized. + bool set_case_expr(THD *thd, int case_expr_id, Item **case_expr_item_ptr); + + Item *get_case_expr(int case_expr_id) const + { return m_case_expr_holders[case_expr_id]; } + + Item ** get_case_expr_addr(int case_expr_id) const + { return (Item**) m_case_expr_holders.array() + case_expr_id; } - int - set_case_expr(THD *thd, int case_expr_id, Item **case_expr_item_ptr); +private: + /// Internal function to allocate memory for arrays. + /// + /// @param thd Thread handle. + /// + /// @return error flag: false on success, true in case of failure. + bool alloc_arrays(THD *thd); + + /// Create and initialize a table to store SP-variables. + /// + /// param thd Thread handle. + /// + /// @return error flag. + /// @retval false on success. + /// @retval true on error. + bool init_var_table(THD *thd); - Item * - get_case_expr(int case_expr_id); + /// Create and initialize an Item-adapter (Item_field) for each SP-var field. + /// + /// param thd Thread handle. + /// + /// @return error flag. + /// @retval false on success. + /// @retval true on error. + bool init_var_items(THD *thd); + + /// Create an instance of appropriate Item_cache class depending on the + /// specified type in the callers arena. + /// + /// @note We should create cache items in the callers arena, as they are + /// used between in several instructions. + /// + /// @param thd Thread handler. + /// @param item Item to get the expression type. + /// + /// @return Pointer to valid object on success, or NULL in case of error. + Item_cache *create_case_expr_holder(THD *thd, const Item *item) const; - Item ** - get_case_expr_addr(int case_expr_id); + int set_variable(THD *thd, Field *field, Item **value); private: - sp_pcontext *m_root_parsing_ctx; + /// Top-level (root) parsing context for this runtime context. + const sp_pcontext *m_root_parsing_ctx; - /* Virtual table for storing variables. */ + /// Virtual table for storing SP-variables. TABLE *m_var_table; - /* - Collection of Item_field proxies, each of them points to the corresponding - field in m_var_table. - */ - Item **m_var_items; + /// Collection of Item_field proxies, each of them points to the + /// corresponding field in m_var_table. + Bounds_checked_array<Item *> m_var_items; - /* - This is a pointer to a field, which should contain return value for stored - functions (only). For stored procedures, this pointer is NULL. - */ + /// This is a pointer to a field, which should contain return value for + /// stored functions (only). For stored procedures, this pointer is NULL. Field *m_return_value_fld; - /* - Indicates whether the return value (in m_return_value_fld) has been set - during execution. - */ + /// Indicates whether the return value (in m_return_value_fld) has been + /// set during execution. bool m_return_value_set; - /** - TRUE if the context is created for a sub-statement. - */ - bool in_sub_stmt; + /// Flag to tell if the runtime context is created for a sub-statement. + bool m_in_sub_stmt; - sp_handler_t *m_handler; // Visible handlers + /// Stack of visible handlers. + Dynamic_array<sp_handler_entry *> m_handlers; - /** - SQL conditions caught by each handler. - This is an array indexed by handler index. - */ - Sql_condition_info *m_raised_conditions; + /// Stack of caught SQL conditions. + Dynamic_array<Handler_call_frame *> m_handler_call_stack; - uint m_hcount; // Stack pointer for m_handler - uint *m_hstack; // Return stack for continue handlers - uint m_hsp; // Stack pointer for m_hstack - /** Active handler stack. */ - sp_active_handler_t *m_in_handler; - uint m_ihsp; // Stack pointer for m_in_handler - int m_hfound; // Set by find_handler; -1 if not found + /// Stack of cursors. + Bounds_checked_array<sp_cursor *> m_cstack; - sp_cursor **m_cstack; + /// Current number of cursors in m_cstack. uint m_ccount; - Item_cache **m_case_expr_holders; - - /* Previous runtime context (NULL if none) */ - sp_rcontext *m_prev_runtime_ctx; - -private: - bool init_var_table(THD *thd); - bool init_var_items(); - - Item_cache *create_case_expr_holder(THD *thd, const Item *item); - - int set_variable(THD *thd, Field *field, Item **value); + /// Array of CASE expression holders. + Bounds_checked_array<Item_cache *> m_case_expr_holders; }; // class sp_rcontext : public Sql_alloc +/////////////////////////////////////////////////////////////////////////// +// sp_cursor declaration. +/////////////////////////////////////////////////////////////////////////// -/* - An interceptor of cursor result set used to implement - FETCH <cname> INTO <varlist>. -*/ - -class Select_fetch_into_spvars: public select_result_interceptor -{ - List<struct sp_variable> *spvar_list; - uint field_count; -public: - Select_fetch_into_spvars() {} /* Remove gcc warning */ - uint get_field_count() { return field_count; } - void set_spvar_list(List<struct sp_variable> *vars) { spvar_list= vars; } - - virtual bool send_eof() { return FALSE; } - virtual int send_data(List<Item> &items); - virtual int prepare(List<Item> &list, SELECT_LEX_UNIT *u); -}; - +class Server_side_cursor; +typedef class st_select_lex_unit SELECT_LEX_UNIT; /* A mediator between stored procedures and server side cursors */ class sp_cursor : public Sql_alloc { -public: +private: + /// An interceptor of cursor result set used to implement + /// FETCH <cname> INTO <varlist>. + class Select_fetch_into_spvars: public select_result_interceptor + { + List<sp_variable> *spvar_list; + uint field_count; + public: + Select_fetch_into_spvars(THD *thd_arg): select_result_interceptor(thd_arg) {} + uint get_field_count() { return field_count; } + void set_spvar_list(List<sp_variable> *vars) { spvar_list= vars; } + + virtual bool send_eof() { return FALSE; } + virtual int send_data(List<Item> &items); + virtual int prepare(List<Item> &list, SELECT_LEX_UNIT *u); +}; - sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i); +public: + sp_cursor(THD *thd_arg, sp_lex_keeper *lex_keeper, sp_instr_cpush *i); virtual ~sp_cursor() - { - destroy(); - } + { destroy(); } - sp_lex_keeper * - get_lex_keeper() { return m_lex_keeper; } + sp_lex_keeper *get_lex_keeper() { return m_lex_keeper; } - int - open(THD *thd); + int open(THD *thd); - int - close(THD *thd); + int close(THD *thd); - inline bool - is_open() - { - return test(server_side_cursor); - } + my_bool is_open() + { return MY_TEST(server_side_cursor); } - int - fetch(THD *, List<struct sp_variable> *vars); + int fetch(THD *, List<sp_variable> *vars); - inline sp_instr_cpush * - get_instr() - { - return m_i; - } + sp_instr_cpush *get_instr() + { return m_i; } private: - Select_fetch_into_spvars result; sp_lex_keeper *m_lex_keeper; Server_side_cursor *server_side_cursor; sp_instr_cpush *m_i; // My push instruction - void - destroy(); + void destroy(); }; // class sp_cursor : public Sql_alloc diff --git a/sql/spatial.cc b/sql/spatial.cc index c5dc0c2140c..e8d2fb42383 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -15,8 +15,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" -#include "my_global.h" // REQUIRED for HAVE_* below #include "spatial.h" #include "gstream.h" // Gis_read_stream #include "sql_string.h" // String @@ -291,19 +291,18 @@ Geometry *Geometry::create_from_wkb(Geometry_buffer *buffer, } -int Geometry::create_from_opresult(Geometry_buffer *g_buf, +Geometry *Geometry::create_from_opresult(Geometry_buffer *g_buf, String *res, Gcalc_result_receiver &rr) { uint32 geom_type= rr.get_result_typeid(); Geometry *obj= create_by_typeid(g_buf, geom_type); if (!obj || res->reserve(WKB_HEADER_SIZE, 512)) - return 1; + return NULL; res->q_append((char) wkb_ndr); res->q_append(geom_type); - return obj->init_from_opresult(res, rr.result(), rr.length()) == 0 && - rr.length(); + return obj->init_from_opresult(res, rr.result(), rr.length()) ? obj : NULL; } @@ -386,7 +385,7 @@ bool Geometry::create_point(String *result, const char *data) const 1 Can't reallocate 'result' */ -bool Geometry::create_point(String *result, double x, double y) const +bool Geometry::create_point(String *result, double x, double y) { if (result->reserve(1 + 4 + POINT_DATA_SIZE)) return 1; @@ -1150,8 +1149,8 @@ int Gis_polygon::centroid_xy(double *x, double *y) const uint32 n_points, org_n_points; double prev_x, prev_y; double cur_area= 0; - double cur_cx= 0; - double cur_cy= 0; + double cur_cx= 0, cur_cy= 0; + double sum_cx= 0, sum_cy= 0; if (no_data(data, 4)) return 1; @@ -1165,17 +1164,32 @@ int Gis_polygon::centroid_xy(double *x, double *y) const while (--n_points) // One point is already read { double tmp_x, tmp_y; + double loc_area; get_point(&tmp_x, &tmp_y, data); data+= POINT_DATA_SIZE; - cur_area+= (prev_x + tmp_x) * (prev_y - tmp_y); + loc_area= prev_x * tmp_y - tmp_x * prev_y; + cur_area+= loc_area; cur_cx+= tmp_x; cur_cy+= tmp_y; + sum_cx+= (prev_x + tmp_x) * loc_area; + sum_cy+= (prev_y + tmp_y) * loc_area; + prev_x= tmp_x; prev_y= tmp_y; } - cur_area= fabs(cur_area) / 2; - cur_cx= cur_cx / (org_n_points - 1); - cur_cy= cur_cy / (org_n_points - 1); + + if (fabs(cur_area) > 1e-10) + { + cur_cx= sum_cx / cur_area / 3.0; + cur_cy= sum_cy / cur_area / 3.0; + } + else + { + cur_cx= cur_cx / (org_n_points - 1); + cur_cy= cur_cy / (org_n_points - 1); + } + + cur_area= fabs(cur_area); if (!first_loop) { @@ -2227,6 +2241,13 @@ uint Gis_geometry_collection::init_from_opresult(String *bin, return 0; bin->q_append(n_objects); + if (res_len == 0) + { + /* Special case of GEOMETRYCOLLECTION EMPTY. */ + opres+= 1; + goto empty_geom; + } + while (res_len) { switch ((Gcalc_function::shape_type) uint4korr(opres)) @@ -2250,6 +2271,7 @@ uint Gis_geometry_collection::init_from_opresult(String *bin, res_len-= g_len; n_objects++; } +empty_geom: bin->write_at_position(no_pos, n_objects); return (uint) (opres - opres_orig); } diff --git a/sql/spatial.h b/sql/spatial.h index ee2d6d5ec8d..6f50acac984 100644 --- a/sql/spatial.h +++ b/sql/spatial.h @@ -71,7 +71,11 @@ struct MBR MBR(const st_point_2d &min, const st_point_2d &max) :xmin(min.x), ymin(min.y), xmax(max.x), ymax(max.y) {} - + + MBR(const MBR &mbr1, const MBR &mbr2) + :xmin(mbr1.xmin), ymin(mbr1.ymin), xmax(mbr1.xmax), ymax(mbr1.ymax) + { add_mbr(&mbr2); } + inline void add_xy(double x, double y) { /* Not using "else" for proper one point MBR calculation */ @@ -196,8 +200,8 @@ struct MBR if (d != mbr->dimension() || d <= 0 || contains(mbr) || within(mbr)) return 0; - MBR intersection(max(xmin, mbr->xmin), max(ymin, mbr->ymin), - min(xmax, mbr->xmax), min(ymax, mbr->ymax)); + MBR intersection(MY_MAX(xmin, mbr->xmin), MY_MAX(ymin, mbr->ymin), + MY_MIN(xmax, mbr->xmax), MY_MIN(ymax, mbr->ymax)); return (d == intersection.dimension()); } @@ -298,7 +302,7 @@ public: bool init_stream=1); static Geometry *create_from_wkb(Geometry_buffer *buffer, const char *wkb, uint32 len, String *res); - static int create_from_opresult(Geometry_buffer *g_buf, + static Geometry *create_from_opresult(Geometry_buffer *g_buf, String *res, Gcalc_result_receiver &rr); int as_wkt(String *wkt, const char **end); @@ -316,6 +320,7 @@ public: bool envelope(String *result) const; static Class_info *ci_collection[wkb_last+1]; + static bool create_point(String *result, double x, double y); protected: static Class_info *find_class(int type_id) { @@ -326,7 +331,6 @@ protected: const char *append_points(String *txt, uint32 n_points, const char *data, uint32 offset) const; bool create_point(String *result, const char *data) const; - bool create_point(String *result, double x, double y) const; const char *get_mbr_for_points(MBR *mbr, const char *data, uint offset) const; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 066e7e4c7f4..c2e5bfd8c11 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -25,32 +25,35 @@ in the relevant fields. Empty strings comes last. */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "sql_acl.h" // MYSQL_DB_FIELD_COUNT, ACL_ACCESS #include "sql_base.h" // close_mysql_tables #include "key.h" // key_copy, key_cmp_if_same, key_restore #include "sql_show.h" // append_identifier -#include "sql_table.h" // build_table_filename +#include "sql_table.h" // write_bin_log #include "hash_filo.h" #include "sql_parse.h" // check_access #include "sql_view.h" // VIEW_ANY_ACL #include "records.h" // READ_RECORD, read_record_info, // init_read_record, end_read_record #include "rpl_filter.h" // rpl_filter +#include "rpl_rli.h" #include <m_ctype.h> #include <stdarg.h> #include "sp_head.h" #include "sp.h" #include "transaction.h" #include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT -#include "records.h" // init_read_record, end_read_record #include <sql_common.h> #include <mysql/plugin_auth.h> +#include <mysql/plugin_password_validation.h> #include "sql_connect.h" #include "hostname.h" #include "sql_db.h" #include "sql_array.h" +#include "sql_hset.h" +#include "password.h" #include "sql_plugin_compat.h" @@ -59,15 +62,15 @@ bool mysql_user_table_is_in_short_password_format= false; static const TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = { { - { C_STRING_WITH_LEN("Host") }, + { C_STRING_WITH_LEN("Host") }, { C_STRING_WITH_LEN("char(60)") }, {NULL, 0} - }, + }, { - { C_STRING_WITH_LEN("Db") }, + { C_STRING_WITH_LEN("Db") }, { C_STRING_WITH_LEN("char(64)") }, {NULL, 0} - }, + }, { { C_STRING_WITH_LEN("User") }, { C_STRING_WITH_LEN("char(") }, @@ -171,19 +174,34 @@ TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = { }; const TABLE_FIELD_DEF - mysql_db_table_def= {MYSQL_DB_FIELD_COUNT, mysql_db_table_fields}; +mysql_db_table_def= {MYSQL_DB_FIELD_COUNT, mysql_db_table_fields, 0, (uint*) 0 }; static LEX_STRING native_password_plugin_name= { C_STRING_WITH_LEN("mysql_native_password") }; - + static LEX_STRING old_password_plugin_name= { C_STRING_WITH_LEN("mysql_old_password") }; - + /// @todo make it configurable LEX_STRING *default_auth_plugin_name= &native_password_plugin_name; +/* + Wildcard host, matches any hostname +*/ +LEX_STRING host_not_specified= { C_STRING_WITH_LEN("%") }; + +/* + Constants, used in the SHOW GRANTS command. + Their actual string values are irrelevant, they're always compared + as pointers to these string constants. +*/ +LEX_STRING current_user= { C_STRING_WITH_LEN("*current_user") }; +LEX_STRING current_role= { C_STRING_WITH_LEN("*current_role") }; +LEX_STRING current_user_and_current_role= { C_STRING_WITH_LEN("*current_user_and_current_role") }; + + #ifndef NO_EMBEDDED_ACCESS_CHECKS static plugin_ref old_password_plugin; #endif @@ -197,6 +215,12 @@ struct acl_host_and_ip long ip, ip_mask; // Used with masked ip:s }; +#ifndef NO_EMBEDDED_ACCESS_CHECKS +static bool compare_hostname(const acl_host_and_ip *, const char *, const char *); +#else +#define compare_hostname(X,Y,Z) 0 +#endif + class ACL_ACCESS { public: ulong sort; @@ -212,19 +236,32 @@ public: char *db; }; -class ACL_USER :public ACL_ACCESS +class ACL_USER_BASE :public ACL_ACCESS +{ + +public: + static void *operator new(size_t size, MEM_ROOT *mem_root) + { return (void*) alloc_root(mem_root, size); } + + uchar flags; // field used to store various state information + LEX_STRING user; + /* list to hold references to granted roles (ACL_ROLE instances) */ + DYNAMIC_ARRAY role_grants; +}; + +class ACL_USER :public ACL_USER_BASE { public: acl_host_and_ip host; uint hostname_length; USER_RESOURCES user_resource; - char *user; uint8 salt[SCRAMBLE_LENGTH + 1]; // scrambled password in binary form - uint8 salt_len; // 0 - no password, 4 - 3.20, 8 - 4.0, 20 - 4.1.1 + uint8 salt_len; // 0 - no password, 4 - 3.20, 8 - 4.0, 20 - 4.1.1 enum SSL_type ssl_type; const char *ssl_cipher, *x509_issuer, *x509_subject; LEX_STRING plugin; LEX_STRING auth_string; + LEX_STRING default_rolename; ACL_USER *copy(MEM_ROOT *root) { @@ -232,7 +269,8 @@ public: if (!dst) return 0; *dst= *this; - dst->user= safe_strdup_root(root, user); + dst->user.str= safe_strdup_root(root, user.str); + dst->user.length= user.length; dst->ssl_cipher= safe_strdup_root(root, ssl_cipher); dst->x509_issuer= safe_strdup_root(root, x509_issuer); dst->x509_subject= safe_strdup_root(root, x509_subject); @@ -243,8 +281,57 @@ public: dst->plugin.str= strmake_root(root, plugin.str, plugin.length); dst->auth_string.str= safe_strdup_root(root, auth_string.str); dst->host.hostname= safe_strdup_root(root, host.hostname); + dst->default_rolename.str= safe_strdup_root(root, default_rolename.str); + dst->default_rolename.length= default_rolename.length; + bzero(&dst->role_grants, sizeof(role_grants)); return dst; } + + int cmp(const char *user2, const char *host2) + { + CHARSET_INFO *cs= system_charset_info; + int res; + res= strcmp(safe_str(user.str), safe_str(user2)); + if (!res) + res= my_strcasecmp(cs, host.hostname, host2); + return res; + } + + bool eq(const char *user2, const char *host2) { return !cmp(user2, host2); } + + bool wild_eq(const char *user2, const char *host2, const char *ip2) + { + if (strcmp(safe_str(user.str), safe_str(user2))) + return false; + + return compare_hostname(&host, host2, ip2 ? ip2 : host2); + } +}; + +class ACL_ROLE :public ACL_USER_BASE +{ +public: + /* + In case of granting a role to a role, the access bits are merged together + via a bit OR operation and placed in the ACL_USER::access field. + + When rebuilding role_grants via the rebuild_role_grant function, + the ACL_USER::access field needs to be reset first. The field + initial_role_access holds initial grants, as granted directly to the role + */ + ulong initial_role_access; + /* + In subgraph traversal, when we need to traverse only a part of the graph + (e.g. all direct and indirect grantees of a role X), the counter holds the + number of affected neighbour nodes. + See also propagate_role_grants() + */ + uint counter; + DYNAMIC_ARRAY parent_grantee; // array of backlinks to elements granted + + ACL_ROLE(ACL_USER * user, MEM_ROOT *mem); + ACL_ROLE(const char * rolename, ulong privileges, MEM_ROOT *mem); + }; class ACL_DB :public ACL_ACCESS @@ -252,16 +339,31 @@ class ACL_DB :public ACL_ACCESS public: acl_host_and_ip host; char *user,*db; + ulong initial_access; /* access bits present in the table */ }; +#ifndef DBUG_OFF +/* status variables, only visible in SHOW STATUS after -#d,role_merge_stats */ +ulong role_global_merges= 0, role_db_merges= 0, role_table_merges= 0, + role_column_merges= 0, role_routine_merges= 0; +#endif #ifndef NO_EMBEDDED_ACCESS_CHECKS +static bool fix_and_copy_user(LEX_USER *to, LEX_USER *from, THD *thd); static void update_hostname(acl_host_and_ip *host, const char *hostname); static ulong get_sort(uint count,...); -static bool compare_hostname(const acl_host_and_ip *host, const char *hostname, - const char *ip); -static bool show_proxy_grants (THD *thd, LEX_USER *user, - char *buff, size_t buffsize); +static bool show_proxy_grants (THD *, const char *, const char *, + char *, size_t); +static bool show_role_grants(THD *, const char *, const char *, + ACL_USER_BASE *, char *, size_t); +static bool show_global_privileges(THD *, ACL_USER_BASE *, + bool, char *, size_t); +static bool show_database_privileges(THD *, const char *, const char *, + char *, size_t); +static bool show_table_and_column_privileges(THD *, const char *, const char *, + char *, size_t); +static int show_routine_grants(THD *, const char *, const char *, HASH *, + const char *, int, char *, int); class ACL_PROXY_USER :public ACL_ACCESS { @@ -271,14 +373,14 @@ class ACL_PROXY_USER :public ACL_ACCESS const char *proxied_user; bool with_grant; - typedef enum { - MYSQL_PROXIES_PRIV_HOST, - MYSQL_PROXIES_PRIV_USER, + typedef enum { + MYSQL_PROXIES_PRIV_HOST, + MYSQL_PROXIES_PRIV_USER, MYSQL_PROXIES_PRIV_PROXIED_HOST, - MYSQL_PROXIES_PRIV_PROXIED_USER, + MYSQL_PROXIES_PRIV_PROXIED_USER, MYSQL_PROXIES_PRIV_WITH_GRANT, MYSQL_PROXIES_PRIV_GRANTOR, - MYSQL_PROXIES_PRIV_TIMESTAMP } old_acl_proxy_users; + MYSQL_PROXIES_PRIV_TIMESTAMP } proxy_table_fields; public: ACL_PROXY_USER () {}; @@ -287,16 +389,14 @@ public: bool with_grant_arg) { user= (user_arg && *user_arg) ? user_arg : NULL; - update_hostname (&host, - (host_arg && *host_arg) ? host_arg : NULL); - proxied_user= (proxied_user_arg && *proxied_user_arg) ? + update_hostname (&host, (host_arg && *host_arg) ? host_arg : NULL); + proxied_user= (proxied_user_arg && *proxied_user_arg) ? proxied_user_arg : NULL; - update_hostname (&proxied_host, + update_hostname (&proxied_host, (proxied_host_arg && *proxied_host_arg) ? proxied_host_arg : NULL); with_grant= with_grant_arg; - sort= get_sort(4, host.hostname, user, - proxied_host.hostname, proxied_user); + sort= get_sort(4, host.hostname, user, proxied_host.hostname, proxied_user); } void init(MEM_ROOT *mem, const char *host_arg, const char *user_arg, @@ -305,9 +405,9 @@ public: { init ((host_arg && *host_arg) ? strdup_root (mem, host_arg) : NULL, (user_arg && *user_arg) ? strdup_root (mem, user_arg) : NULL, - (proxied_host_arg && *proxied_host_arg) ? + (proxied_host_arg && *proxied_host_arg) ? strdup_root (mem, proxied_host_arg) : NULL, - (proxied_user_arg && *proxied_user_arg) ? + (proxied_user_arg && *proxied_user_arg) ? strdup_root (mem, proxied_user_arg) : NULL, with_grant_arg); } @@ -326,29 +426,27 @@ public: const char *get_host() { return host.hostname; } const char *get_proxied_user() { return proxied_user; } const char *get_proxied_host() { return proxied_host.hostname; } - void set_user(MEM_ROOT *mem, const char *user_arg) - { + void set_user(MEM_ROOT *mem, const char *user_arg) + { user= user_arg && *user_arg ? strdup_root(mem, user_arg) : NULL; } - void set_host(MEM_ROOT *mem, const char *host_arg) - { - update_hostname(&host, - (host_arg && *host_arg) ? - strdup_root(mem, host_arg) : NULL); + void set_host(MEM_ROOT *mem, const char *host_arg) + { + update_hostname(&host, safe_strdup_root(mem, host_arg)); } bool check_validity(bool check_no_resolve) { - if (check_no_resolve && + if (check_no_resolve && (hostname_requires_resolving(host.hostname) || hostname_requires_resolving(proxied_host.hostname))) { sql_print_warning("'proxies_priv' entry '%s@%s %s@%s' " "ignored in --skip-name-resolve mode.", - proxied_user ? proxied_user : "", - proxied_host.hostname ? proxied_host.hostname : "", - user ? user : "", - host.hostname ? host.hostname : ""); + safe_str(proxied_user), + safe_str(proxied_host.hostname), + safe_str(user), + safe_str(host.hostname)); return TRUE; } return FALSE; @@ -362,22 +460,15 @@ public: "compare_hostname(%s,%s,%s) &&" "wild_compare (%s,%s) &&" "wild_compare (%s,%s)", - host.hostname ? host.hostname : "<NULL>", - host_arg ? host_arg : "<NULL>", - ip_arg ? ip_arg : "<NULL>", - proxied_host.hostname ? proxied_host.hostname : "<NULL>", - host_arg ? host_arg : "<NULL>", - ip_arg ? ip_arg : "<NULL>", - user_arg ? user_arg : "<NULL>", - user ? user : "<NULL>", - proxied_user_arg ? proxied_user_arg : "<NULL>", - proxied_user ? proxied_user : "<NULL>")); + host.hostname, host_arg, ip_arg, proxied_host.hostname, + host_arg, ip_arg, user_arg, user, + proxied_user_arg, proxied_user)); DBUG_RETURN(compare_hostname(&host, host_arg, ip_arg) && compare_hostname(&proxied_host, host_arg, ip_arg) && (!user || (user_arg && !wild_compare(user_arg, user, TRUE))) && - (!proxied_user || - (proxied_user && !wild_compare(proxied_user_arg, + (!proxied_user || + (proxied_user && !wild_compare(proxied_user_arg, proxied_user, TRUE)))); } @@ -395,21 +486,16 @@ public: "strcmp(%s,%s) &&" "wild_compare (%s,%s) &&" "wild_compare (%s,%s)", - user ? user : "<NULL>", - grant->user ? grant->user : "<NULL>", - proxied_user ? proxied_user : "<NULL>", - grant->proxied_user ? grant->proxied_user : "<NULL>", - host.hostname ? host.hostname : "<NULL>", - grant->host.hostname ? grant->host.hostname : "<NULL>", - proxied_host.hostname ? proxied_host.hostname : "<NULL>", - grant->proxied_host.hostname ? - grant->proxied_host.hostname : "<NULL>")); + user, grant->user, proxied_user, grant->proxied_user, + host.hostname, grant->host.hostname, + proxied_host.hostname, grant->proxied_host.hostname)); - DBUG_RETURN(auth_element_equals(user, grant->user) && - auth_element_equals(proxied_user, grant->proxied_user) && - auth_element_equals(host.hostname, grant->host.hostname) && - auth_element_equals(proxied_host.hostname, - grant->proxied_host.hostname)); + bool res= auth_element_equals(user, grant->user) && + auth_element_equals(proxied_user, grant->proxied_user) && + auth_element_equals(host.hostname, grant->host.hostname) && + auth_element_equals(proxied_host.hostname, + grant->proxied_host.hostname); + DBUG_RETURN(res); } @@ -446,23 +532,21 @@ public: with_grant= grant->with_grant; } - static int store_pk(TABLE *table, - const LEX_STRING *host, + static int store_pk(TABLE *table, + const LEX_STRING *host, const LEX_STRING *user, - const LEX_STRING *proxied_host, + const LEX_STRING *proxied_host, const LEX_STRING *proxied_user) { DBUG_ENTER("ACL_PROXY_USER::store_pk"); DBUG_PRINT("info", ("host=%s, user=%s, proxied_host=%s, proxied_user=%s", - host->str ? host->str : "<NULL>", - user->str ? user->str : "<NULL>", - proxied_host->str ? proxied_host->str : "<NULL>", - proxied_user->str ? proxied_user->str : "<NULL>")); - if (table->field[MYSQL_PROXIES_PRIV_HOST]->store(host->str, + host->str, user->str, + proxied_host->str, proxied_user->str)); + if (table->field[MYSQL_PROXIES_PRIV_HOST]->store(host->str, host->length, system_charset_info)) DBUG_RETURN(TRUE); - if (table->field[MYSQL_PROXIES_PRIV_USER]->store(user->str, + if (table->field[MYSQL_PROXIES_PRIV_USER]->store(user->str, user->length, system_charset_info)) DBUG_RETURN(TRUE); @@ -490,10 +574,10 @@ public: if (store_pk(table, host, user, proxied_host, proxied_user)) DBUG_RETURN(TRUE); DBUG_PRINT("info", ("with_grant=%s", with_grant ? "TRUE" : "FALSE")); - if (table->field[MYSQL_PROXIES_PRIV_WITH_GRANT]->store(with_grant ? 1 : 0, - TRUE)) + if (table->field[MYSQL_PROXIES_PRIV_WITH_GRANT]->store(with_grant ? 1 : 0, + TRUE)) DBUG_RETURN(TRUE); - if (table->field[MYSQL_PROXIES_PRIV_GRANTOR]->store(grantor, + if (table->field[MYSQL_PROXIES_PRIV_GRANTOR]->store(grantor, strlen(grantor), system_charset_info)) DBUG_RETURN(TRUE); @@ -520,6 +604,78 @@ static uchar* acl_entry_get_key(acl_entry *entry, size_t *length, return (uchar*) entry->key; } +static uchar* acl_role_get_key(ACL_ROLE *entry, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length=(uint) entry->user.length; + return (uchar*) entry->user.str; +} + +struct ROLE_GRANT_PAIR : public Sql_alloc +{ + char *u_uname; + char *u_hname; + char *r_uname; + LEX_STRING hashkey; + bool with_admin; + + bool init(MEM_ROOT *mem, char *username, char *hostname, char *rolename, + bool with_admin_option); +}; + +static uchar* acl_role_map_get_key(ROLE_GRANT_PAIR *entry, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length=(uint) entry->hashkey.length; + return (uchar*) entry->hashkey.str; +} + +bool ROLE_GRANT_PAIR::init(MEM_ROOT *mem, char *username, + char *hostname, char *rolename, + bool with_admin_option) +{ + size_t uname_l = safe_strlen(username); + size_t hname_l = safe_strlen(hostname); + size_t rname_l = safe_strlen(rolename); + /* + Create a buffer that holds all 3 NULL terminated strings in succession + To save memory space, the same buffer is used as the hashkey + */ + size_t bufflen = uname_l + hname_l + rname_l + 3; //add the '\0' aswell + char *buff= (char *)alloc_root(mem, bufflen); + if (!buff) + return true; + + /* + Offsets in the buffer for all 3 strings + */ + char *username_pos= buff; + char *hostname_pos= buff + uname_l + 1; + char *rolename_pos= buff + uname_l + hname_l + 2; + + if (username) //prevent undefined behaviour + memcpy(username_pos, username, uname_l); + username_pos[uname_l]= '\0'; //#1 string terminator + u_uname= username_pos; + + if (hostname) //prevent undefined behaviour + memcpy(hostname_pos, hostname, hname_l); + hostname_pos[hname_l]= '\0'; //#2 string terminator + u_hname= hostname_pos; + + if (rolename) //prevent undefined behaviour + memcpy(rolename_pos, rolename, rname_l); + rolename_pos[rname_l]= '\0'; //#3 string terminator + r_uname= rolename_pos; + + hashkey.str = buff; + hashkey.length = bufflen; + + with_admin= with_admin_option; + + return false; +} + #define IP_ADDR_STRLEN (3 + 1 + 3 + 1 + 3 + 1 + 3) #define ACL_KEY_LENGTH (IP_ADDR_STRLEN + 1 + NAME_LEN + \ 1 + USERNAME_LENGTH + 1) @@ -542,46 +698,240 @@ static uchar* acl_entry_get_key(acl_entry *entry, size_t *length, #endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ #define NORMAL_HANDSHAKE_SIZE 6 -static DYNAMIC_ARRAY acl_hosts, acl_users, acl_dbs, acl_proxy_users; -static MEM_ROOT mem, memex; +#define ROLE_ASSIGN_COLUMN_IDX 43 +#define DEFAULT_ROLE_COLUMN_IDX 44 +#define MAX_STATEMENT_TIME_COLUMN_IDX 45 + +/* various flags valid for ACL_USER */ +#define IS_ROLE (1L << 0) +/* Flag to mark that a ROLE is on the recursive DEPTH_FIRST_SEARCH stack */ +#define ROLE_ON_STACK (1L << 1) +/* + Flag to mark that a ROLE and all it's neighbours have + been visited +*/ +#define ROLE_EXPLORED (1L << 2) +/* Flag to mark that on_node was already called for this role */ +#define ROLE_OPENED (1L << 3) + +static DYNAMIC_ARRAY acl_hosts, acl_users, acl_proxy_users; +static Dynamic_array<ACL_DB> acl_dbs(0U,50U); +typedef Dynamic_array<ACL_DB>::CMP_FUNC acl_dbs_cmp; +static HASH acl_roles; +/* + An hash containing mappings user <--> role + + A hash is used so as to make updates quickly + The hashkey used represents all the entries combined +*/ +static HASH acl_roles_mappings; +static MEM_ROOT acl_memroot, grant_memroot; static bool initialized=0; static bool allow_all_hosts=1; static HASH acl_check_hosts, column_priv_hash, proc_priv_hash, func_priv_hash; static DYNAMIC_ARRAY acl_wild_hosts; -static hash_filo *acl_cache; +static Hash_filo<acl_entry> *acl_cache; static uint grant_version=0; /* Version of priv tables. incremented by acl_load */ static ulong get_access(TABLE *form,uint fieldnr, uint *next_field=0); +static bool check_is_role(TABLE *form); static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b); static ulong get_sort(uint count,...); static void init_check_host(void); static void rebuild_check_host(void); -static ACL_USER *find_acl_user(const char *host, const char *user, - my_bool exact); +static void rebuild_role_grants(void); +static ACL_USER *find_user_exact(const char *host, const char *user); +static ACL_USER *find_user_wild(const char *host, const char *user, const char *ip= 0); +static ACL_ROLE *find_acl_role(const char *user); +static ROLE_GRANT_PAIR *find_role_grant_pair(const LEX_STRING *u, const LEX_STRING *h, const LEX_STRING *r); +static ACL_USER_BASE *find_acl_user_base(const char *user, const char *host); static bool update_user_table(THD *, TABLE *, const char *, const char *, const - char *, uint, bool); -static my_bool acl_load(THD *thd, TABLE_LIST *tables); -static my_bool grant_load(THD *thd, TABLE_LIST *tables); + char *, uint); +static bool acl_load(THD *thd, TABLE_LIST *tables); +static bool grant_load(THD *thd, TABLE_LIST *tables); static inline void get_grantor(THD *thd, char* grantor); +static bool add_role_user_mapping(const char *uname, const char *hname, const char *rname); + +#define ROLE_CYCLE_FOUND 2 +static int traverse_role_graph_up(ACL_ROLE *, void *, + int (*) (ACL_ROLE *, void *), + int (*) (ACL_ROLE *, ACL_ROLE *, void *)); + +static int traverse_role_graph_down(ACL_USER_BASE *, void *, + int (*) (ACL_USER_BASE *, void *), + int (*) (ACL_USER_BASE *, ACL_ROLE *, void *)); + +/* + Enumeration of ACL/GRANT tables in the mysql database +*/ +enum enum_acl_tables +{ + USER_TABLE, + DB_TABLE, + TABLES_PRIV_TABLE, + COLUMNS_PRIV_TABLE, +#define FIRST_OPTIONAL_TABLE HOST_TABLE + HOST_TABLE, + PROCS_PRIV_TABLE, + PROXIES_PRIV_TABLE, + ROLES_MAPPING_TABLE, + TABLES_MAX // <== always the last +}; +// bits for open_grant_tables +static const int Table_user= 1 << USER_TABLE; +static const int Table_db= 1 << DB_TABLE; +static const int Table_tables_priv= 1 << TABLES_PRIV_TABLE; +static const int Table_columns_priv= 1 << COLUMNS_PRIV_TABLE; +static const int Table_host= 1 << HOST_TABLE; +static const int Table_procs_priv= 1 << PROCS_PRIV_TABLE; +static const int Table_proxies_priv= 1 << PROXIES_PRIV_TABLE; +static const int Table_roles_mapping= 1 << ROLES_MAPPING_TABLE; + +static int open_grant_tables(THD *, TABLE_LIST *, enum thr_lock_type, int); + +const LEX_STRING acl_table_names[]= // matches enum_acl_tables +{ + { C_STRING_WITH_LEN("user") }, + { C_STRING_WITH_LEN("db") }, + { C_STRING_WITH_LEN("tables_priv") }, + { C_STRING_WITH_LEN("columns_priv") }, + { C_STRING_WITH_LEN("host") }, + { C_STRING_WITH_LEN("procs_priv") }, + { C_STRING_WITH_LEN("proxies_priv") }, + { C_STRING_WITH_LEN("roles_mapping") } +}; + +/** check if the table was opened, issue an error otherwise */ +static int no_such_table(TABLE_LIST *tl) +{ + if (tl->table) + return 0; + + my_error(ER_NO_SUCH_TABLE, MYF(0), tl->db, tl->alias); + return 1; +} + /* Enumeration of various ACL's and Hashes used in handle_grant_struct() */ enum enum_acl_lists { USER_ACL= 0, + ROLE_ACL, DB_ACL, COLUMN_PRIVILEGES_HASH, PROC_PRIVILEGES_HASH, FUNC_PRIVILEGES_HASH, - PROXY_USERS_ACL + PROXY_USERS_ACL, + ROLES_MAPPINGS_HASH }; -/* - Convert scrambled password to binary form, according to scramble type, +ACL_ROLE::ACL_ROLE(ACL_USER *user, MEM_ROOT *root) : counter(0) +{ + + access= user->access; + /* set initial role access the same as the table row privileges */ + initial_role_access= user->access; + this->user.str= safe_strdup_root(root, user->user.str); + this->user.length= user->user.length; + bzero(&role_grants, sizeof(role_grants)); + bzero(&parent_grantee, sizeof(parent_grantee)); + flags= IS_ROLE; +} + +ACL_ROLE::ACL_ROLE(const char * rolename, ulong privileges, MEM_ROOT *root) : + initial_role_access(privileges), counter(0) +{ + this->access= initial_role_access; + this->user.str= safe_strdup_root(root, rolename); + this->user.length= strlen(rolename); + bzero(&role_grants, sizeof(role_grants)); + bzero(&parent_grantee, sizeof(parent_grantee)); + flags= IS_ROLE; +} + + +static bool is_invalid_role_name(const char *str) +{ + if (*str && strcasecmp(str, "PUBLIC") && strcasecmp(str, "NONE")) + return false; + + my_error(ER_INVALID_ROLE, MYF(0), str); + return true; +} + + +static void free_acl_user(ACL_USER *user) +{ + delete_dynamic(&(user->role_grants)); +} + +static void free_acl_role(ACL_ROLE *role) +{ + delete_dynamic(&(role->role_grants)); + delete_dynamic(&(role->parent_grantee)); +} + +static my_bool check_if_exists(THD *, plugin_ref, void *) +{ + return TRUE; +} + +static bool has_validation_plugins() +{ + return plugin_foreach(NULL, check_if_exists, + MariaDB_PASSWORD_VALIDATION_PLUGIN, NULL); +} + +struct validation_data { LEX_STRING *user, *password; }; + +static my_bool do_validate(THD *, plugin_ref plugin, void *arg) +{ + struct validation_data *data= (struct validation_data *)arg; + struct st_mariadb_password_validation *handler= + (st_mariadb_password_validation *)plugin_decl(plugin)->info; + return handler->validate_password(data->user, data->password); +} + + +static bool validate_password(LEX_USER *user, THD *thd) +{ + if (user->pwtext.length || !user->pwhash.length) + { + struct validation_data data= { &user->user, + user->pwtext.str ? &user->pwtext : + const_cast<LEX_STRING *>(&empty_lex_str) }; + if (plugin_foreach(NULL, do_validate, + MariaDB_PASSWORD_VALIDATION_PLUGIN, &data)) + { + my_error(ER_NOT_VALID_PASSWORD, MYF(0)); + return true; + } + } + else + { + if (!thd->slave_thread && + strict_password_validation && has_validation_plugins()) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--strict-password-validation"); + return true; + } + } + return false; +} + +/** + Convert scrambled password to binary form, according to scramble type, Binary form is stored in user.salt. + + @param acl_user The object where to store the salt + @param password The password hash containing the salt + @param password_len The length of the password hash + + Despite the name of the function it is used when loading ACLs from disk + to store the password hash in the ACL_USER object. */ -static -void +static void set_user_salt(ACL_USER *acl_user, const char *password, uint password_len) { if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH) @@ -626,7 +976,6 @@ static char *fix_plugin_ptr(char *name) */ static bool fix_user_plugin_ptr(ACL_USER *user) { - user->salt_len= 0; if (my_strcasecmp(system_charset_info, user->plugin.str, native_password_plugin_name.str) == 0) user->plugin= native_password_plugin_name; @@ -636,11 +985,112 @@ static bool fix_user_plugin_ptr(ACL_USER *user) user->plugin= old_password_plugin_name; else return true; - + set_user_salt(user, user->auth_string.str, user->auth_string.length); return false; } + +/* + Validates the password, calculates password hash, transforms + equivalent LEX_USER representations. + + Upon entering this function: + + - if user->plugin is specified, user->auth is the plugin auth data. + - if user->plugin is mysql_native_password or mysql_old_password, + user->auth is the password hash, and LEX_USER is transformed + to match the next case (that is, user->plugin is cleared). + - if user->plugin is NOT specified, built-in auth is assumed, that is + mysql_native_password or mysql_old_password. In that case, + user->pwhash is the password hash. And user->pwtext is the original + plain-text password. Either one can be set or both. + + Upon exiting this function: + + - user->pwtext is left untouched + - user->pwhash is the password hash, as the mysql.user.password column + - user->plugin is the plugin name, as the mysql.user.plugin column + - user->auth is the plugin auth data, as the mysql.user.authentication_string column +*/ +static bool fix_lex_user(THD *thd, LEX_USER *user) +{ + size_t check_length; + + DBUG_ASSERT(user->plugin.length || !user->auth.length); + DBUG_ASSERT(!(user->plugin.length && (user->pwtext.length || user->pwhash.length))); + + if (my_strcasecmp(system_charset_info, user->plugin.str, + native_password_plugin_name.str) == 0) + check_length= SCRAMBLED_PASSWORD_CHAR_LENGTH; + else + if (my_strcasecmp(system_charset_info, user->plugin.str, + old_password_plugin_name.str) == 0) + check_length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + else + if (user->plugin.length) + return false; // nothing else to do + else if (thd->variables.old_passwords == 1 || + user->pwhash.length == SCRAMBLED_PASSWORD_CHAR_LENGTH_323) + check_length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + else + check_length= SCRAMBLED_PASSWORD_CHAR_LENGTH; + + if (user->plugin.length) + { + user->pwhash= user->auth; + user->plugin= empty_lex_str; + user->auth= empty_lex_str; + } + + if (user->pwhash.length && user->pwhash.length != check_length) + { + my_error(ER_PASSWD_LENGTH, MYF(0), check_length); + return true; + } + + if (user->pwtext.length && !user->pwhash.length) + { + size_t scramble_length; + void (*make_scramble)(char *, const char *, size_t); + + if (thd->variables.old_passwords == 1) + { + scramble_length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + make_scramble= my_make_scrambled_password_323; + } + else + { + scramble_length= SCRAMBLED_PASSWORD_CHAR_LENGTH; + make_scramble= my_make_scrambled_password; + } + + Query_arena *arena, backup; + arena= thd->activate_stmt_arena_if_needed(&backup); + char *buff= (char *) thd->alloc(scramble_length + 1); + if (arena) + thd->restore_active_arena(arena, &backup); + + if (buff == NULL) + return true; + make_scramble(buff, user->pwtext.str, user->pwtext.length); + user->pwhash.str= buff; + user->pwhash.length= scramble_length; + } + + return false; +} + + +static bool get_YN_as_bool(Field *field) +{ + char buff[2]; + String res(buff,sizeof(buff),&my_charset_latin1); + field->val_str(&res); + return res[0] == 'Y' || res[0] == 'y'; +} + + /* Initialize structures responsible for user/db-level privilege checking and load privilege information for them from tables in the 'mysql' database. @@ -659,13 +1109,13 @@ static bool fix_user_plugin_ptr(ACL_USER *user) 1 Could not initialize grant's */ -my_bool acl_init(bool dont_read_acl_tables) +bool acl_init(bool dont_read_acl_tables) { THD *thd; - my_bool return_val; + bool return_val; DBUG_ENTER("acl_init"); - acl_cache= new hash_filo(ACL_CACHE_SIZE, 0, 0, + acl_cache= new Hash_filo<acl_entry>(ACL_CACHE_SIZE, 0, 0, (my_hash_get_key) acl_entry_get_key, (my_hash_free_key) free, &my_charset_utf8_bin); @@ -701,8 +1151,6 @@ my_bool acl_init(bool dont_read_acl_tables) */ return_val= acl_reload(thd); delete thd; - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); DBUG_RETURN(return_val); } @@ -710,10 +1158,9 @@ my_bool acl_init(bool dont_read_acl_tables) Choose from either native or old password plugins when assigning a password */ -static bool -set_user_plugin (ACL_USER *user, int password_len) +static bool set_user_plugin (ACL_USER *user, int password_len) { - switch (password_len) + switch (password_len) { case 0: /* no password */ case SCRAMBLED_PASSWORD_CHAR_LENGTH: @@ -724,8 +1171,8 @@ set_user_plugin (ACL_USER *user, int password_len) return FALSE; default: sql_print_warning("Found invalid password for user: '%s@%s'; " - "Ignoring user", user->user ? user->user : "", - user->host.hostname ? user->host.hostname : ""); + "Ignoring user", safe_str(user->user.str), + safe_str(user->host.hostname)); return TRUE; } } @@ -738,19 +1185,20 @@ set_user_plugin (ACL_USER *user, int password_len) SYNOPSIS acl_load() thd Current thread - tables List containing open "mysql.host", "mysql.user" and - "mysql.db" tables. + tables List containing open "mysql.host", "mysql.user", + "mysql.db", "mysql.proxies_priv" and "mysql.roles_mapping" + tables. RETURN VALUES FALSE Success TRUE Error */ -static my_bool acl_load(THD *thd, TABLE_LIST *tables) +static bool acl_load(THD *thd, TABLE_LIST *tables) { TABLE *table; READ_RECORD read_record_info; - my_bool return_val= TRUE; + bool return_val= TRUE; bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE; char tmp_name[SAFE_NAME_LEN+1]; int password_length; @@ -761,71 +1209,70 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) grant_version++; /* Privileges updated */ - acl_cache->clear(1); // Clear locked hostname cache - - init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0); - if (init_read_record(&read_record_info,thd,table= tables[0].table,NULL,1,0, - FALSE)) - goto end; - - table->use_all_columns(); - while (!(read_record_info.read_record(&read_record_info))) + init_sql_alloc(&acl_memroot, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0)); + if ((table= tables[HOST_TABLE].table)) // "host" table may not exist (e.g. in MySQL 5.6.7+) { - ACL_HOST host; - update_hostname(&host.host,get_field(&mem, table->field[0])); - host.db= get_field(&mem, table->field[1]); - if (lower_case_table_names && host.db) + if (init_read_record(&read_record_info, thd, table, NULL, 1, 1, FALSE)) + goto end; + table->use_all_columns(); + while (!(read_record_info.read_record(&read_record_info))) { - /* - convert db to lower case and give a warning if the db wasn't - already in lower case - */ - char *end = strnmov(tmp_name, host.db, sizeof(tmp_name)); - if (end >= tmp_name + sizeof(tmp_name)) + ACL_HOST host; + update_hostname(&host.host,get_field(&acl_memroot, table->field[0])); + host.db= get_field(&acl_memroot, table->field[1]); + if (lower_case_table_names && host.db) { - sql_print_warning(ER(ER_WRONG_DB_NAME), host.db); + /* + convert db to lower case and give a warning if the db wasn't + already in lower case + */ + char *end = strnmov(tmp_name, host.db, sizeof(tmp_name)); + if (end >= tmp_name + sizeof(tmp_name)) + { + sql_print_warning(ER_THD(thd, ER_WRONG_DB_NAME), host.db); + continue; + } + my_casedn_str(files_charset_info, host.db); + if (strcmp(host.db, tmp_name) != 0) + sql_print_warning("'host' entry '%s|%s' had database in mixed " + "case that has been forced to lowercase because " + "lower_case_table_names is set. It will not be " + "possible to remove this privilege using REVOKE.", + host.host.hostname, host.db); + } + host.access= get_access(table,2); + host.access= fix_rights_for_db(host.access); + host.sort= get_sort(2,host.host.hostname,host.db); + if (check_no_resolve && hostname_requires_resolving(host.host.hostname)) + { + sql_print_warning("'host' entry '%s|%s' " + "ignored in --skip-name-resolve mode.", + safe_str(host.host.hostname), + safe_str(host.db)); continue; } - my_casedn_str(files_charset_info, host.db); - if (strcmp(host.db, tmp_name) != 0) - sql_print_warning("'host' entry '%s|%s' had database in mixed " - "case that has been forced to lowercase because " - "lower_case_table_names is set. It will not be " - "possible to remove this privilege using REVOKE.", - host.host.hostname ? host.host.hostname : "", - host.db ? host.db : ""); - } - host.access= get_access(table,2); - host.access= fix_rights_for_db(host.access); - host.sort= get_sort(2,host.host.hostname,host.db); - if (check_no_resolve && hostname_requires_resolving(host.host.hostname)) - { - sql_print_warning("'host' entry '%s|%s' " - "ignored in --skip-name-resolve mode.", - host.host.hostname ? host.host.hostname : "", - host.db ? host.db : ""); - continue; - } #ifndef TO_BE_REMOVED - if (table->s->fields == 8) - { // Without grant - if (host.access & CREATE_ACL) - host.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL; - } + if (table->s->fields == 8) + { // Without grant + if (host.access & CREATE_ACL) + host.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL; + } #endif - (void) push_dynamic(&acl_hosts,(uchar*) &host); + (void) push_dynamic(&acl_hosts,(uchar*) &host); + } + my_qsort((uchar*) dynamic_element(&acl_hosts,0,ACL_HOST*),acl_hosts.elements, + sizeof(ACL_HOST),(qsort_cmp) acl_compare); + end_read_record(&read_record_info); } - my_qsort((uchar*) dynamic_element(&acl_hosts,0,ACL_HOST*),acl_hosts.elements, - sizeof(ACL_HOST),(qsort_cmp) acl_compare); - end_read_record(&read_record_info); freeze_size(&acl_hosts); - if (init_read_record(&read_record_info,thd,table=tables[1].table,NULL,1,0, - FALSE)) + if (init_read_record(&read_record_info, thd, table=tables[USER_TABLE].table, + NULL, 1, 1, FALSE)) goto end; - table->use_all_columns(); - username_char_length= min(table->field[1]->char_length(), USERNAME_CHAR_LENGTH); + + username_char_length= MY_MIN(table->field[1]->char_length(), + USERNAME_CHAR_LENGTH); password_length= table->field[2]->field_length / table->field[2]->charset()->mbmaxlen; if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH_323) @@ -853,6 +1300,8 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) mysql_mutex_unlock(&LOCK_global_system_variables); else { + extern sys_var *Sys_old_passwords_ptr; + Sys_old_passwords_ptr->value_origin= sys_var::AUTO; global_system_variables.old_passwords= 1; mysql_mutex_unlock(&LOCK_global_system_variables); sql_print_warning("mysql.user table is not updated to new password format; " @@ -871,25 +1320,42 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) while (!(read_record_info.read_record(&read_record_info))) { ACL_USER user; + bool is_role= FALSE; bzero(&user, sizeof(user)); - update_hostname(&user.host, get_field(&mem, table->field[0])); - user.user= get_field(&mem, table->field[1]); - if (check_no_resolve && hostname_requires_resolving(user.host.hostname)) + update_hostname(&user.host, get_field(&acl_memroot, table->field[0])); + char *username= get_field(&acl_memroot, table->field[1]); + user.user.str= username; + user.user.length= safe_strlen(username); + + /* + If the user entry is a role, skip password and hostname checks + A user can not log in with a role so some checks are not necessary + */ + is_role= check_is_role(table); + + if (is_role && is_invalid_role_name(username)) + { + thd->clear_error(); // the warning is still issued + continue; + } + + if (!is_role && check_no_resolve && + hostname_requires_resolving(user.host.hostname)) { sql_print_warning("'user' entry '%s@%s' " "ignored in --skip-name-resolve mode.", - user.user ? user.user : "", - user.host.hostname ? user.host.hostname : ""); + safe_str(user.user.str), + safe_str(user.host.hostname)); continue; } - char *password= get_field(&mem, table->field[2]); - uint password_len= password ? strlen(password) : 0; - user.auth_string.str= password ? password : const_cast<char*>(""); + char *password= get_field(&acl_memroot, table->field[2]); + uint password_len= safe_strlen(password); + user.auth_string.str= safe_str(password); user.auth_string.length= password_len; set_user_salt(&user, password, password_len); - if (set_user_plugin(&user, password_len)) + if (!is_role && set_user_plugin(&user, password_len)) continue; { @@ -931,9 +1397,10 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) if (table->s->fields <= 38 && (user.access & SUPER_ACL)) user.access|= TRIGGER_ACL; - user.sort= get_sort(2,user.host.hostname,user.user); - user.hostname_length= (user.host.hostname ? - (uint) strlen(user.host.hostname) : 0); + user.sort= get_sort(2, user.host.hostname, user.user.str); + user.hostname_length= safe_strlen(user.host.hostname); + user.user_resource.user_conn= 0; + user.user_resource.max_statement_time= 0.0; /* Starting from 4.0.2 we have more fields */ if (table->s->fields >= 31) @@ -948,9 +1415,9 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) else /* !strcmp(ssl_type, "SPECIFIED") */ user.ssl_type=SSL_TYPE_SPECIFIED; - user.ssl_cipher= get_field(&mem, table->field[next_field++]); - user.x509_issuer= get_field(&mem, table->field[next_field++]); - user.x509_subject= get_field(&mem, table->field[next_field++]); + user.ssl_cipher= get_field(&acl_memroot, table->field[next_field++]); + user.x509_issuer= get_field(&acl_memroot, table->field[next_field++]); + user.x509_subject= get_field(&acl_memroot, table->field[next_field++]); char *ptr = get_field(thd->mem_root, table->field[next_field++]); user.user_resource.questions=ptr ? atoi(ptr) : 0; @@ -969,30 +1436,43 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) user.user_resource.user_conn= ptr ? atoi(ptr) : 0; } - if (table->s->fields >= 41) + if (!is_role && table->s->fields >= 41) { /* We may have plugin & auth_String fields */ - char *tmpstr= get_field(&mem, table->field[next_field++]); + char *tmpstr= get_field(&acl_memroot, table->field[next_field++]); if (tmpstr) { user.plugin.str= tmpstr; user.plugin.length= strlen(user.plugin.str); - if (user.auth_string.length) + user.auth_string.str= + safe_str(get_field(&acl_memroot, table->field[next_field++])); + user.auth_string.length= strlen(user.auth_string.str); + + if (user.auth_string.length && password_len) { sql_print_warning("'user' entry '%s@%s' has both a password " "and an authentication plugin specified. The " "password will be ignored.", - user.user ? user.user : "", - user.host.hostname ? user.host.hostname : ""); + safe_str(user.user.str), + safe_str(user.host.hostname)); + } + else if (password_len) + { + user.auth_string.str= password; + user.auth_string.length= password_len; } - user.auth_string.str= get_field(&mem, table->field[next_field++]); - if (!user.auth_string.str) - user.auth_string.str= const_cast<char*>(""); - user.auth_string.length= strlen(user.auth_string.str); fix_user_plugin_ptr(&user); } } + + if (table->s->fields > MAX_STATEMENT_TIME_COLUMN_IDX) + { + /* Starting from 10.1.1 we can have max_statement_time */ + ptr= get_field(thd->mem_root, + table->field[MAX_STATEMENT_TIME_COLUMN_IDX]); + user.user_resource.max_statement_time= ptr ? atof(ptr) : 0.0; + } } else { @@ -1011,7 +1491,34 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) user.access|= SUPER_ACL | EXECUTE_ACL; #endif } - (void) push_dynamic(&acl_users,(uchar*) &user); + + (void) my_init_dynamic_array(&user.role_grants,sizeof(ACL_ROLE *), + 8, 8, MYF(0)); + + /* check default role, if any */ + if (!is_role && table->s->fields > DEFAULT_ROLE_COLUMN_IDX) + { + user.default_rolename.str= + get_field(&acl_memroot, table->field[DEFAULT_ROLE_COLUMN_IDX]); + user.default_rolename.length= safe_strlen(user.default_rolename.str); + } + + if (is_role) + { + DBUG_PRINT("info", ("Found role %s", user.user.str)); + ACL_ROLE *entry= new (&acl_memroot) ACL_ROLE(&user, &acl_memroot); + entry->role_grants = user.role_grants; + (void) my_init_dynamic_array(&entry->parent_grantee, + sizeof(ACL_USER_BASE *), 8, 8, MYF(0)); + my_hash_insert(&acl_roles, (uchar *)entry); + + continue; + } + else + { + DBUG_PRINT("info", ("Found user %s", user.user.str)); + (void) push_dynamic(&acl_users,(uchar*) &user); + } if (!user.host.hostname || (user.host.hostname[0] == wild_many && !user.host.hostname[1])) allow_all_hosts=1; // Anyone can connect @@ -1022,33 +1529,34 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) end_read_record(&read_record_info); freeze_size(&acl_users); - if (init_read_record(&read_record_info,thd,table=tables[2].table,NULL,1,0, - FALSE)) + if (init_read_record(&read_record_info, thd, table=tables[DB_TABLE].table, + NULL, 1, 1, FALSE)) goto end; - table->use_all_columns(); while (!(read_record_info.read_record(&read_record_info))) { ACL_DB db; - update_hostname(&db.host,get_field(&mem, table->field[MYSQL_DB_FIELD_HOST])); - db.db=get_field(&mem, table->field[MYSQL_DB_FIELD_DB]); + db.user=get_field(&acl_memroot, table->field[MYSQL_DB_FIELD_USER]); + const char *hostname= get_field(&acl_memroot, table->field[MYSQL_DB_FIELD_HOST]); + if (!hostname && find_acl_role(db.user)) + hostname= ""; + update_hostname(&db.host, hostname); + db.db=get_field(&acl_memroot, table->field[MYSQL_DB_FIELD_DB]); if (!db.db) { sql_print_warning("Found an entry in the 'db' table with empty database name; Skipped"); continue; } - db.user=get_field(&mem, table->field[MYSQL_DB_FIELD_USER]); if (check_no_resolve && hostname_requires_resolving(db.host.hostname)) { sql_print_warning("'db' entry '%s %s@%s' " "ignored in --skip-name-resolve mode.", - db.db, - db.user ? db.user : "", - db.host.hostname ? db.host.hostname : ""); + db.db, safe_str(db.user), safe_str(db.host.hostname)); continue; } db.access=get_access(table,3); db.access=fix_rights_for_db(db.access); + db.initial_access= db.access; if (lower_case_table_names) { /* @@ -1058,7 +1566,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) char *end = strnmov(tmp_name, db.db, sizeof(tmp_name)); if (end >= tmp_name + sizeof(tmp_name)) { - sql_print_warning(ER(ER_WRONG_DB_NAME), db.db); + sql_print_warning(ER_THD(thd, ER_WRONG_DB_NAME), db.db); continue; } my_casedn_str(files_charset_info, db.db); @@ -1068,9 +1576,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) "case that has been forced to lowercase because " "lower_case_table_names is set. It will not be " "possible to remove this privilege using REVOKE.", - db.db, - db.user ? db.user : "", - db.host.hostname ? db.host.hostname : ""); + db.db, safe_str(db.user), safe_str(db.host.hostname)); } } db.sort=get_sort(3,db.host.hostname,db.db,db.user); @@ -1081,22 +1587,22 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) db.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL; } #endif - (void) push_dynamic(&acl_dbs,(uchar*) &db); + acl_dbs.push(db); } - my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements, - sizeof(ACL_DB),(qsort_cmp) acl_compare); end_read_record(&read_record_info); - freeze_size(&acl_dbs); + acl_dbs.sort((acl_dbs_cmp)acl_compare); + acl_dbs.freeze(); - if (tables[3].table) + if ((table= tables[PROXIES_PRIV_TABLE].table)) { - init_read_record(&read_record_info, thd, table= tables[3].table, NULL, 1, - 0, FALSE); + if (init_read_record(&read_record_info, thd, table, + NULL, 1, 1, FALSE)) + goto end; table->use_all_columns(); while (!(read_record_info.read_record(&read_record_info))) { ACL_PROXY_USER proxy; - proxy.init(table, &mem); + proxy.init(table, &acl_memroot); if (proxy.check_validity(check_no_resolve)) continue; if (push_dynamic(&acl_proxy_users, (uchar*) &proxy)) @@ -1117,6 +1623,44 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) } freeze_size(&acl_proxy_users); + if ((table= tables[ROLES_MAPPING_TABLE].table)) + { + if (init_read_record(&read_record_info, thd, table, NULL, 1, 1, FALSE)) + goto end; + table->use_all_columns(); + + MEM_ROOT temp_root; + init_alloc_root(&temp_root, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0)); + while (!(read_record_info.read_record(&read_record_info))) + { + char *hostname= safe_str(get_field(&temp_root, table->field[0])); + char *username= safe_str(get_field(&temp_root, table->field[1])); + char *rolename= safe_str(get_field(&temp_root, table->field[2])); + bool with_grant_option= get_YN_as_bool(table->field[3]); + + if (add_role_user_mapping(username, hostname, rolename)) { + sql_print_error("Invalid roles_mapping table entry user:'%s@%s', rolename:'%s'", + username, hostname, rolename); + continue; + } + + ROLE_GRANT_PAIR *mapping= new (&acl_memroot) ROLE_GRANT_PAIR; + + if (mapping->init(&acl_memroot, username, hostname, rolename, with_grant_option)) + continue; + + my_hash_insert(&acl_roles_mappings, (uchar*) mapping); + } + + free_root(&temp_root, MYF(0)); + end_read_record(&read_record_info); + } + else + { + sql_print_error("Missing system table mysql.roles_mapping; " + "please run mysql_upgrade to create it"); + } + init_check_host(); initialized=1; @@ -1131,13 +1675,15 @@ end: void acl_free(bool end) { - free_root(&mem,MYF(0)); + my_hash_free(&acl_roles); + free_root(&acl_memroot,MYF(0)); delete_dynamic(&acl_hosts); - delete_dynamic(&acl_users); - delete_dynamic(&acl_dbs); + delete_dynamic_with_callback(&acl_users, (FREE_FUNC) free_acl_user); + acl_dbs.free_memory(); delete_dynamic(&acl_wild_hosts); delete_dynamic(&acl_proxy_users); my_hash_free(&acl_check_hosts); + my_hash_free(&acl_roles_mappings); if (!end) acl_cache->clear(1); /* purecov: inspected */ else @@ -1169,89 +1715,85 @@ void acl_free(bool end) TRUE Failure */ -my_bool acl_reload(THD *thd) +bool acl_reload(THD *thd) { - TABLE_LIST tables[4]; - DYNAMIC_ARRAY old_acl_hosts, old_acl_users, old_acl_dbs, old_acl_proxy_users; + TABLE_LIST tables[TABLES_MAX]; + DYNAMIC_ARRAY old_acl_hosts, old_acl_users, old_acl_proxy_users; + Dynamic_array<ACL_DB> old_acl_dbs(0U,0U); + HASH old_acl_roles, old_acl_roles_mappings; MEM_ROOT old_mem; - bool old_initialized; - my_bool return_val= TRUE; + int result; DBUG_ENTER("acl_reload"); /* To avoid deadlocks we should obtain table locks before obtaining acl_cache->lock mutex. */ - tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("host"), "host", TL_READ); - tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("user"), "user", TL_READ); - tables[2].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("db"), "db", TL_READ); - tables[3].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("proxies_priv"), - "proxies_priv", TL_READ); - tables[0].next_local= tables[0].next_global= tables + 1; - tables[1].next_local= tables[1].next_global= tables + 2; - tables[2].next_local= tables[2].next_global= tables + 3; - tables[0].open_type= tables[1].open_type= tables[2].open_type= - tables[3].open_type= OT_BASE_ONLY; - tables[3].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - - if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) + if ((result= open_grant_tables(thd, tables, TL_READ, Table_host | + Table_user | Table_db | Table_proxies_priv | Table_roles_mapping))) { + DBUG_ASSERT(result <= 0); /* Execution might have been interrupted; only print the error message if an error condition has been raised. */ - if (thd->stmt_da->is_error()) + if (thd->get_stmt_da()->is_error()) sql_print_error("Fatal error: Can't open and lock privilege tables: %s", - thd->stmt_da->message()); + thd->get_stmt_da()->message()); goto end; } - if ((old_initialized=initialized)) - mysql_mutex_lock(&acl_cache->lock); + acl_cache->clear(0); + mysql_mutex_lock(&acl_cache->lock); old_acl_hosts= acl_hosts; old_acl_users= acl_users; + old_acl_roles= acl_roles; + old_acl_roles_mappings= acl_roles_mappings; old_acl_proxy_users= acl_proxy_users; old_acl_dbs= acl_dbs; - my_init_dynamic_array(&acl_hosts, sizeof(ACL_HOST), 20, 50); - my_init_dynamic_array(&acl_users, sizeof(ACL_USER), 50, 100); - my_init_dynamic_array(&acl_dbs, sizeof(ACL_DB), 50, 100); - my_init_dynamic_array(&acl_proxy_users, sizeof(ACL_PROXY_USER), 50, 100); - old_mem= mem; + my_init_dynamic_array(&acl_hosts, sizeof(ACL_HOST), 20, 50, MYF(0)); + my_init_dynamic_array(&acl_users, sizeof(ACL_USER), 50, 100, MYF(0)); + acl_dbs.init(50, 100); + my_init_dynamic_array(&acl_proxy_users, sizeof(ACL_PROXY_USER), 50, 100, MYF(0)); + my_hash_init2(&acl_roles,50, &my_charset_utf8_bin, + 0, 0, 0, (my_hash_get_key) acl_role_get_key, 0, + (void (*)(void *))free_acl_role, 0); + my_hash_init2(&acl_roles_mappings, 50, &my_charset_utf8_bin, 0, 0, 0, + (my_hash_get_key) acl_role_map_get_key, 0, 0, 0); + old_mem= acl_memroot; delete_dynamic(&acl_wild_hosts); my_hash_free(&acl_check_hosts); - if ((return_val= acl_load(thd, tables))) + if ((result= acl_load(thd, tables))) { // Error. Revert to old list DBUG_PRINT("error",("Reverting to old privileges")); acl_free(); /* purecov: inspected */ acl_hosts= old_acl_hosts; acl_users= old_acl_users; + acl_roles= old_acl_roles; + acl_roles_mappings= old_acl_roles_mappings; acl_proxy_users= old_acl_proxy_users; acl_dbs= old_acl_dbs; - mem= old_mem; + old_acl_dbs.init(0,0); + acl_memroot= old_mem; init_check_host(); } else { + my_hash_free(&old_acl_roles); free_root(&old_mem,MYF(0)); delete_dynamic(&old_acl_hosts); - delete_dynamic(&old_acl_users); + delete_dynamic_with_callback(&old_acl_users, (FREE_FUNC) free_acl_user); delete_dynamic(&old_acl_proxy_users); - delete_dynamic(&old_acl_dbs); + my_hash_free(&old_acl_roles_mappings); } - if (old_initialized) - mysql_mutex_unlock(&acl_cache->lock); + mysql_mutex_unlock(&acl_cache->lock); end: close_mysql_tables(thd); - DBUG_RETURN(return_val); + DBUG_RETURN(result); } - /* Get all access bits from table after fieldnr @@ -1283,8 +1825,7 @@ static ulong get_access(TABLE *form, uint fieldnr, uint *next_field) ((Field_enum*) (*pos))->typelib->count == 2 ; pos++, fieldnr++, bit<<=1) { - (*pos)->val_str(&res); - if (my_toupper(&my_charset_latin1, res[0]) == 'Y') + if (get_YN_as_bool(*pos)) access_bits|= bit; } if (next_field) @@ -1292,6 +1833,34 @@ static ulong get_access(TABLE *form, uint fieldnr, uint *next_field) return access_bits; } +/* + Check if a user entry in the user table is marked as being a role entry + + IMPLEMENTATION + Access the coresponding column and check the coresponding ENUM of the form + ENUM('N', 'Y') + + SYNOPSIS + check_is_role() + form an open table to read the entry from. + The record should be already read in table->record[0] + + RETURN VALUE + TRUE if the user is marked as a role + FALSE otherwise +*/ + +static bool check_is_role(TABLE *form) +{ + char buff[2]; + String res(buff, sizeof(buff), &my_charset_latin1); + /* Table version does not support roles */ + if (form->s->fields <= ROLE_ASSIGN_COLUMN_IDX) + return FALSE; + + return get_YN_as_bool(form->field[ROLE_ASSIGN_COLUMN_IDX]); +} + /* Return a number which, if sorted 'desc', puts strings in this order: @@ -1329,7 +1898,7 @@ static ulong get_sort(uint count,...) chars= 128; // Marker that chars existed } } - sort= (sort << 8) + (wild_pos ? min(wild_pos, 127) : chars); + sort= (sort << 8) + (wild_pos ? MY_MIN(wild_pos, 127U) : chars); } va_end(args); return sort; @@ -1371,12 +1940,11 @@ bool acl_getroot(Security_context *sctx, char *user, char *host, DBUG_ENTER("acl_getroot"); DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', db: '%s'", - (host ? host : "(NULL)"), (ip ? ip : "(NULL)"), - user, (db ? db : "(NULL)"))); + host, ip, user, db)); sctx->user= user; sctx->host= host; sctx->ip= ip; - sctx->host_or_ip= host ? host : (ip ? ip : ""); + sctx->host_or_ip= host ? host : (safe_str(ip)); if (!initialized) { @@ -1391,61 +1959,171 @@ bool acl_getroot(Security_context *sctx, char *user, char *host, sctx->master_access= 0; sctx->db_access= 0; - *sctx->priv_user= *sctx->priv_host= 0; + *sctx->priv_user= *sctx->priv_host= *sctx->priv_role= 0; - /* - Find acl entry in user database. - This is specially tailored to suit the check we do for CALL of - a stored procedure; user is set to what is actually a - priv_user, which can be ''. - */ - for (i=0 ; i < acl_users.elements ; i++) + if (host[0]) // User, not Role { - ACL_USER *acl_user_tmp= dynamic_element(&acl_users,i,ACL_USER*); - if ((!acl_user_tmp->user && !user[0]) || - (acl_user_tmp->user && strcmp(user, acl_user_tmp->user) == 0)) + acl_user= find_user_wild(host, user, ip); + + if (acl_user) { - if (compare_hostname(&acl_user_tmp->host, host, ip)) + res= 0; + for (i=0 ; i < acl_dbs.elements() ; i++) { - acl_user= acl_user_tmp; - res= 0; - break; + ACL_DB *acl_db= &acl_dbs.at(i); + if (!acl_db->user || + (user && user[0] && !strcmp(user, acl_db->user))) + { + if (compare_hostname(&acl_db->host, host, ip)) + { + if (!acl_db->db || (db && !wild_compare(db, acl_db->db, 0))) + { + sctx->db_access= acl_db->access; + break; + } + } + } } + sctx->master_access= acl_user->access; + + if (acl_user->user.str) + strmake_buf(sctx->priv_user, user); + + if (acl_user->host.hostname) + strmake_buf(sctx->priv_host, acl_user->host.hostname); } } - - if (acl_user) + else // Role, not User { - for (i=0 ; i < acl_dbs.elements ; i++) + ACL_ROLE *acl_role= find_acl_role(user); + if (acl_role) { - ACL_DB *acl_db= dynamic_element(&acl_dbs, i, ACL_DB*); - if (!acl_db->user || - (user && user[0] && !strcmp(user, acl_db->user))) + res= 0; + for (i=0 ; i < acl_dbs.elements() ; i++) { - if (compare_hostname(&acl_db->host, host, ip)) - { - if (!acl_db->db || (db && !wild_compare(db, acl_db->db, 0))) - { - sctx->db_access= acl_db->access; - break; - } - } + ACL_DB *acl_db= &acl_dbs.at(i); + if (!acl_db->user || + (user && user[0] && !strcmp(user, acl_db->user))) + { + if (compare_hostname(&acl_db->host, "", "")) + { + if (!acl_db->db || (db && !wild_compare(db, acl_db->db, 0))) + { + sctx->db_access= acl_db->access; + break; + } + } + } } + sctx->master_access= acl_role->access; + + if (acl_role->user.str) + strmake_buf(sctx->priv_user, user); + sctx->priv_host[0]= 0; } - sctx->master_access= acl_user->access; + } - if (acl_user->user) - strmake_buf(sctx->priv_user, user); - else - *sctx->priv_user= 0; + mysql_mutex_unlock(&acl_cache->lock); + DBUG_RETURN(res); +} - if (acl_user->host.hostname) - strmake_buf(sctx->priv_host, acl_user->host.hostname); - else - *sctx->priv_host= 0; +static int check_user_can_set_role(const char *user, const char *host, + const char *ip, const char *rolename, ulonglong *access) +{ + ACL_ROLE *role; + ACL_USER_BASE *acl_user_base; + ACL_USER *UNINIT_VAR(acl_user); + bool is_granted= FALSE; + int result= 0; + + /* clear role privileges */ + mysql_mutex_lock(&acl_cache->lock); + + if (!strcasecmp(rolename, "NONE")) + { + /* have to clear the privileges */ + /* get the current user */ + acl_user= find_user_wild(host, user, ip); + if (acl_user == NULL) + { + my_error(ER_INVALID_CURRENT_USER, MYF(0), rolename); + result= -1; + } + else if (access) + *access= acl_user->access; + + goto end; } + + role= find_acl_role(rolename); + + /* According to SQL standard, the same error message must be presented */ + if (role == NULL) { + my_error(ER_INVALID_ROLE, MYF(0), rolename); + result= -1; + goto end; + } + + for (uint i=0 ; i < role->parent_grantee.elements ; i++) + { + acl_user_base= *(dynamic_element(&role->parent_grantee, i, ACL_USER_BASE**)); + if (acl_user_base->flags & IS_ROLE) + continue; + + acl_user= (ACL_USER *)acl_user_base; + if (acl_user->wild_eq(user, host, ip)) + { + is_granted= TRUE; + break; + } + } + + /* According to SQL standard, the same error message must be presented */ + if (!is_granted) + { + my_error(ER_INVALID_ROLE, MYF(0), rolename); + result= 1; + goto end; + } + + if (access) + { + *access = acl_user->access | role->access; + } +end: mysql_mutex_unlock(&acl_cache->lock); - DBUG_RETURN(res); + return result; + +} + +int acl_check_setrole(THD *thd, char *rolename, ulonglong *access) +{ + /* Yes! priv_user@host. Don't ask why - that's what check_access() does. */ + return check_user_can_set_role(thd->security_ctx->priv_user, + thd->security_ctx->host, thd->security_ctx->ip, rolename, access); +} + + +int acl_setrole(THD *thd, char *rolename, ulonglong access) +{ + /* merge the privileges */ + Security_context *sctx= thd->security_ctx; + sctx->master_access= static_cast<ulong>(access); + if (thd->db) + sctx->db_access= acl_get(sctx->host, sctx->ip, sctx->user, thd->db, FALSE); + + if (!strcasecmp(rolename, "NONE")) + { + thd->security_ctx->priv_role[0]= 0; + } + else + { + if (thd->db) + sctx->db_access|= acl_get("", "", rolename, thd->db, FALSE); + /* mark the current role */ + strmake_buf(thd->security_ctx->priv_role, rolename); + } + return 0; } static uchar* check_get_key(ACL_USER *buff, size_t *length, @@ -1456,6 +2134,14 @@ static uchar* check_get_key(ACL_USER *buff, size_t *length, } +static void acl_update_role(const char *rolename, ulong privileges) +{ + ACL_ROLE *role= find_acl_role(rolename); + if (role) + role->initial_role_access= role->access= privileges; +} + + static void acl_update_user(const char *user, const char *host, const char *password, uint password_len, enum SSL_type ssl_type, @@ -1472,57 +2158,68 @@ static void acl_update_user(const char *user, const char *host, for (uint i=0 ; i < acl_users.elements ; i++) { ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); - if ((!acl_user->user && !user[0]) || - (acl_user->user && !strcmp(user,acl_user->user))) + if (acl_user->eq(user, host)) { - if ((!acl_user->host.hostname && !host[0]) || - (acl_user->host.hostname && - !my_strcasecmp(system_charset_info, host, acl_user->host.hostname))) + if (plugin->str[0]) { - if (plugin->str[0]) + acl_user->plugin= *plugin; + acl_user->auth_string.str= auth->str ? + strmake_root(&acl_memroot, auth->str, auth->length) : const_cast<char*>(""); + acl_user->auth_string.length= auth->length; + if (fix_user_plugin_ptr(acl_user)) + acl_user->plugin.str= strmake_root(&acl_memroot, plugin->str, plugin->length); + } + else + if (password[0]) { - acl_user->plugin= *plugin; - acl_user->auth_string.str= auth->str ? - strmake_root(&mem, auth->str, auth->length) : const_cast<char*>(""); - acl_user->auth_string.length= auth->length; - if (fix_user_plugin_ptr(acl_user)) - acl_user->plugin.str= strmake_root(&mem, plugin->str, plugin->length); + acl_user->auth_string.str= strmake_root(&acl_memroot, password, password_len); + acl_user->auth_string.length= password_len; + set_user_salt(acl_user, password, password_len); + set_user_plugin(acl_user, password_len); } - else - if (password[0]) - { - acl_user->auth_string.str= strmake_root(&mem, password, password_len); - acl_user->auth_string.length= password_len; - set_user_salt(acl_user, password, password_len); - set_user_plugin(acl_user, password_len); - } - acl_user->access=privileges; - if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) - acl_user->user_resource.questions=mqh->questions; - if (mqh->specified_limits & USER_RESOURCES::UPDATES_PER_HOUR) - acl_user->user_resource.updates=mqh->updates; - if (mqh->specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR) - acl_user->user_resource.conn_per_hour= mqh->conn_per_hour; - if (mqh->specified_limits & USER_RESOURCES::USER_CONNECTIONS) - acl_user->user_resource.user_conn= mqh->user_conn; - if (ssl_type != SSL_TYPE_NOT_SPECIFIED) - { - acl_user->ssl_type= ssl_type; - acl_user->ssl_cipher= (ssl_cipher ? strdup_root(&mem,ssl_cipher) : - 0); - acl_user->x509_issuer= (x509_issuer ? strdup_root(&mem,x509_issuer) : - 0); - acl_user->x509_subject= (x509_subject ? - strdup_root(&mem,x509_subject) : 0); - } - /* search complete: */ - break; + acl_user->access=privileges; + if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) + acl_user->user_resource.questions=mqh->questions; + if (mqh->specified_limits & USER_RESOURCES::UPDATES_PER_HOUR) + acl_user->user_resource.updates=mqh->updates; + if (mqh->specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR) + acl_user->user_resource.conn_per_hour= mqh->conn_per_hour; + if (mqh->specified_limits & USER_RESOURCES::USER_CONNECTIONS) + acl_user->user_resource.user_conn= mqh->user_conn; + if (mqh->specified_limits & USER_RESOURCES::MAX_STATEMENT_TIME) + acl_user->user_resource.max_statement_time= mqh->max_statement_time; + if (ssl_type != SSL_TYPE_NOT_SPECIFIED) + { + acl_user->ssl_type= ssl_type; + acl_user->ssl_cipher= (ssl_cipher ? strdup_root(&acl_memroot,ssl_cipher) : + 0); + acl_user->x509_issuer= (x509_issuer ? strdup_root(&acl_memroot,x509_issuer) : + 0); + acl_user->x509_subject= (x509_subject ? + strdup_root(&acl_memroot,x509_subject) : 0); } + /* search complete: */ + break; } } } +static void acl_insert_role(const char *rolename, ulong privileges) +{ + ACL_ROLE *entry; + + mysql_mutex_assert_owner(&acl_cache->lock); + entry= new (&acl_memroot) ACL_ROLE(rolename, privileges, &acl_memroot); + (void) my_init_dynamic_array(&entry->parent_grantee, + sizeof(ACL_USER_BASE *), 8, 8, MYF(0)); + (void) my_init_dynamic_array(&entry->role_grants,sizeof(ACL_ROLE *), + 8, 8, MYF(0)); + + my_hash_insert(&acl_roles, (uchar *)entry); +} + + static void acl_insert_user(const char *user, const char *host, const char *password, uint password_len, enum SSL_type ssl_type, @@ -1538,34 +2235,39 @@ static void acl_insert_user(const char *user, const char *host, mysql_mutex_assert_owner(&acl_cache->lock); - acl_user.user=*user ? strdup_root(&mem,user) : 0; - update_hostname(&acl_user.host, *host ? strdup_root(&mem, host): 0); + bzero(&acl_user, sizeof(acl_user)); + acl_user.user.str=*user ? strdup_root(&acl_memroot,user) : 0; + acl_user.user.length= strlen(user); + update_hostname(&acl_user.host, safe_strdup_root(&acl_memroot, host)); if (plugin->str[0]) { acl_user.plugin= *plugin; acl_user.auth_string.str= auth->str ? - strmake_root(&mem, auth->str, auth->length) : const_cast<char*>(""); + strmake_root(&acl_memroot, auth->str, auth->length) : const_cast<char*>(""); acl_user.auth_string.length= auth->length; if (fix_user_plugin_ptr(&acl_user)) - acl_user.plugin.str= strmake_root(&mem, plugin->str, plugin->length); + acl_user.plugin.str= strmake_root(&acl_memroot, plugin->str, plugin->length); } else { - acl_user.auth_string.str= strmake_root(&mem, password, password_len); + acl_user.auth_string.str= strmake_root(&acl_memroot, password, password_len); acl_user.auth_string.length= password_len; set_user_salt(&acl_user, password, password_len); set_user_plugin(&acl_user, password_len); } + acl_user.flags= 0; acl_user.access=privileges; acl_user.user_resource = *mqh; - acl_user.sort=get_sort(2,acl_user.host.hostname,acl_user.user); + acl_user.sort=get_sort(2, acl_user.host.hostname, acl_user.user.str); acl_user.hostname_length=(uint) strlen(host); acl_user.ssl_type= (ssl_type != SSL_TYPE_NOT_SPECIFIED ? ssl_type : SSL_TYPE_NONE); - acl_user.ssl_cipher= ssl_cipher ? strdup_root(&mem,ssl_cipher) : 0; - acl_user.x509_issuer= x509_issuer ? strdup_root(&mem,x509_issuer) : 0; - acl_user.x509_subject=x509_subject ? strdup_root(&mem,x509_subject) : 0; + acl_user.ssl_cipher= ssl_cipher ? strdup_root(&acl_memroot,ssl_cipher) : 0; + acl_user.x509_issuer= x509_issuer ? strdup_root(&acl_memroot,x509_issuer) : 0; + acl_user.x509_subject=x509_subject ? strdup_root(&acl_memroot,x509_subject) : 0; + (void) my_init_dynamic_array(&acl_user.role_grants, sizeof(ACL_USER *), + 8, 8, MYF(0)); (void) push_dynamic(&acl_users,(uchar*) &acl_user); if (!acl_user.host.hostname || @@ -1576,37 +2278,51 @@ static void acl_insert_user(const char *user, const char *host, /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ rebuild_check_host(); + + /* + Rebuild every user's role_grants since 'acl_users' has been sorted + and old pointers to ACL_USER elements are no longer valid + */ + rebuild_role_grants(); } -static void acl_update_db(const char *user, const char *host, const char *db, - ulong privileges) +static bool acl_update_db(const char *user, const char *host, const char *db, + ulong privileges) { mysql_mutex_assert_owner(&acl_cache->lock); - for (uint i=0 ; i < acl_dbs.elements ; i++) + bool updated= false; + + for (uint i=0 ; i < acl_dbs.elements() ; i++) { - ACL_DB *acl_db=dynamic_element(&acl_dbs,i,ACL_DB*); + ACL_DB *acl_db= &acl_dbs.at(i); if ((!acl_db->user && !user[0]) || - (acl_db->user && - !strcmp(user,acl_db->user))) + (acl_db->user && + !strcmp(user,acl_db->user))) { if ((!acl_db->host.hostname && !host[0]) || - (acl_db->host.hostname && - !strcmp(host, acl_db->host.hostname))) + (acl_db->host.hostname && + !strcmp(host, acl_db->host.hostname))) { - if ((!acl_db->db && !db[0]) || - (acl_db->db && !strcmp(db,acl_db->db))) + if ((!acl_db->db && !db[0]) || + (acl_db->db && !strcmp(db,acl_db->db))) - { - if (privileges) - acl_db->access=privileges; - else - delete_dynamic_element(&acl_dbs,i); - } + { + if (privileges) + { + acl_db->access= privileges; + acl_db->initial_access= acl_db->access; + } + else + acl_dbs.del(i); + updated= true; + } } } } + + return updated; } @@ -1625,22 +2341,20 @@ static void acl_update_db(const char *user, const char *host, const char *db, */ static void acl_insert_db(const char *user, const char *host, const char *db, - ulong privileges) + ulong privileges) { ACL_DB acl_db; mysql_mutex_assert_owner(&acl_cache->lock); - acl_db.user=strdup_root(&mem,user); - update_hostname(&acl_db.host, *host ? strdup_root(&mem,host) : 0); - acl_db.db=strdup_root(&mem,db); - acl_db.access=privileges; + acl_db.user=strdup_root(&acl_memroot,user); + update_hostname(&acl_db.host, safe_strdup_root(&acl_memroot, host)); + acl_db.db=strdup_root(&acl_memroot,db); + acl_db.initial_access= acl_db.access= privileges; acl_db.sort=get_sort(3,acl_db.host.hostname,acl_db.db,acl_db.user); - (void) push_dynamic(&acl_dbs,(uchar*) &acl_db); - my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements, - sizeof(ACL_DB),(qsort_cmp) acl_compare); + acl_dbs.push(acl_db); + acl_dbs.sort((acl_dbs_cmp)acl_compare); } - /* Get privilege for a host, user and db combination @@ -1658,7 +2372,7 @@ ulong acl_get(const char *host, const char *ip, acl_entry *entry; DBUG_ENTER("acl_get"); - tmp_db= strmov(strmov(key, ip ? ip : "") + 1, user) + 1; + tmp_db= strmov(strmov(key, safe_str(ip)) + 1, user) + 1; end= strnmov(tmp_db, db, key + sizeof(key) - tmp_db); if (end >= key + sizeof(key)) // db name was truncated @@ -1672,8 +2386,7 @@ ulong acl_get(const char *host, const char *ip, key_length= (size_t) (end-key); mysql_mutex_lock(&acl_cache->lock); - if (!db_is_pattern && (entry=(acl_entry*) acl_cache->search((uchar*) key, - key_length))) + if (!db_is_pattern && (entry=acl_cache->search((uchar*) key, key_length))) { db_access=entry->access; mysql_mutex_unlock(&acl_cache->lock); @@ -1684,19 +2397,22 @@ ulong acl_get(const char *host, const char *ip, /* Check if there are some access rights for database and user */ - for (i=0 ; i < acl_dbs.elements ; i++) + for (i=0 ; i < acl_dbs.elements() ; i++) { - ACL_DB *acl_db=dynamic_element(&acl_dbs,i,ACL_DB*); + ACL_DB *acl_db= &acl_dbs.at(i); if (!acl_db->user || !strcmp(user,acl_db->user)) { if (compare_hostname(&acl_db->host,host,ip)) { - if (!acl_db->db || !wild_compare(db,acl_db->db,db_is_pattern)) - { - db_access=acl_db->access; - if (acl_db->host.hostname) - goto exit; // Fully specified. Take it - break; /* purecov: tested */ + if (!acl_db->db || !wild_compare(db,acl_db->db,db_is_pattern)) + { + db_access=acl_db->access; + if (acl_db->host.hostname) + goto exit; // Fully specified. Take it + /* the host table is not used for roles */ + if ((!host || !host[0]) && !acl_db->host.hostname && find_acl_role(user)) + goto exit; + break; /* purecov: tested */ } } } @@ -1747,7 +2463,7 @@ static void init_check_host(void) { DBUG_ENTER("init_check_host"); (void) my_init_dynamic_array(&acl_wild_hosts,sizeof(struct acl_host_and_ip), - acl_users.elements,1); + acl_users.elements, 1, MYF(0)); (void) my_hash_init(&acl_check_hosts,system_charset_info, acl_users.elements, 0, 0, (my_hash_get_key) check_get_key, 0, 0); @@ -1798,16 +2514,150 @@ static void init_check_host(void) 'acl_user' array, which are invalidated by drop operation, and use ACL_USER::host::hostname as a key, which is changed by rename. */ -void rebuild_check_host(void) +static void rebuild_check_host(void) { delete_dynamic(&acl_wild_hosts); my_hash_free(&acl_check_hosts); init_check_host(); } +/* + Reset a role role_grants dynamic array. + Also, the role's access bits are reset to the ones present in the table. +*/ +static my_bool acl_role_reset_role_arrays(void *ptr, + void * not_used __attribute__((unused))) +{ + ACL_ROLE *role= (ACL_ROLE *)ptr; + reset_dynamic(&role->role_grants); + reset_dynamic(&role->parent_grantee); + role->counter= 0; + return 0; +} -/* Return true if there is no users that can match the given host */ +/* + Add a the coresponding pointers present in the mapping to the entries in + acl_users and acl_roles +*/ +static bool add_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role) +{ + return push_dynamic(&grantee->role_grants, (uchar*) &role) + || push_dynamic(&role->parent_grantee, (uchar*) &grantee); + +} + +/* + Revert the last add_role_user_mapping() action +*/ +static void undo_add_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role) +{ + void *pop __attribute__((unused)); + + pop= pop_dynamic(&grantee->role_grants); + DBUG_ASSERT(role == *(ACL_ROLE**)pop); + + pop= pop_dynamic(&role->parent_grantee); + DBUG_ASSERT(grantee == *(ACL_USER_BASE**)pop); +} +/* + this helper is used when building role_grants and parent_grantee arrays + from scratch. + + this happens either on initial loading of data from tables, in acl_load(). + or in rebuild_role_grants after acl_role_reset_role_arrays(). +*/ +static bool add_role_user_mapping(const char *uname, const char *hname, + const char *rname) +{ + ACL_USER_BASE *grantee= find_acl_user_base(uname, hname); + ACL_ROLE *role= find_acl_role(rname); + + if (grantee == NULL || role == NULL) + return 1; + + /* + because all arrays are rebuilt completely, and counters were also reset, + we can increment them here, and after the rebuild all counters will + have correct values (equal to the number of roles granted). + */ + if (grantee->flags & IS_ROLE) + ((ACL_ROLE*)grantee)->counter++; + return add_role_user_mapping(grantee, role); +} + +/* + This helper function is used to removes roles and grantees + from the corresponding cross-reference arrays. see remove_role_user_mapping(). + as such, it asserts that an element to delete is present in the array, + and is present only once. +*/ +static void remove_ptr_from_dynarray(DYNAMIC_ARRAY *array, void *ptr) +{ + bool found __attribute__((unused))= false; + for (uint i= 0; i < array->elements; i++) + { + if (ptr == *dynamic_element(array, i, void**)) + { + DBUG_ASSERT(!found); + delete_dynamic_element(array, i); + IF_DBUG(found= true, break); + } + } + DBUG_ASSERT(found); +} + +static void remove_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role, + int grantee_idx=-1, int role_idx=-1) +{ + remove_ptr_from_dynarray(&grantee->role_grants, role); + remove_ptr_from_dynarray(&role->parent_grantee, grantee); +} + + +static my_bool add_role_user_mapping_action(void *ptr, void *unused __attribute__((unused))) +{ + ROLE_GRANT_PAIR *pair= (ROLE_GRANT_PAIR*)ptr; + bool status __attribute__((unused)); + status= add_role_user_mapping(pair->u_uname, pair->u_hname, pair->r_uname); + /* + The invariant chosen is that acl_roles_mappings should _always_ + only contain valid entries, referencing correct user and role grants. + If add_role_user_mapping detects an invalid entry, it will not add + the mapping into the ACL_USER::role_grants array. + */ + DBUG_ASSERT(status == 0); + return 0; +} + + +/* + Rebuild the role grants every time the acl_users is modified + + The role grants in the ACL_USER class need to be rebuilt, as they contain + pointers to elements of the acl_users array. +*/ + +static void rebuild_role_grants(void) +{ + DBUG_ENTER("rebuild_role_grants"); + /* + Reset every user's and role's role_grants array + */ + for (uint i=0; i < acl_users.elements; i++) { + ACL_USER *user= dynamic_element(&acl_users, i, ACL_USER *); + reset_dynamic(&user->role_grants); + } + my_hash_iterate(&acl_roles, acl_role_reset_role_arrays, NULL); + + /* Rebuild the direct links between users and roles in ACL_USER::role_grants */ + my_hash_iterate(&acl_roles_mappings, add_role_user_mapping_action, NULL); + + DBUG_VOID_RETURN; +} + + +/* Return true if there is no users that can match the given host */ bool acl_check_host(const char *host, const char *ip) { if (allow_all_hosts) @@ -1830,153 +2680,172 @@ bool acl_check_host(const char *host, const char *ip) } } mysql_mutex_unlock(&acl_cache->lock); + if (ip != NULL) + { + /* Increment HOST_CACHE.COUNT_HOST_ACL_ERRORS. */ + Host_errors errors; + errors.m_host_acl= 1; + inc_host_errors(ip, &errors); + } return 1; // Host is not allowed } +/** + Check if the user is allowed to alter the mysql.user table -/* - Check if the user is allowed to change password - - SYNOPSIS: - check_change_password() - thd THD - host hostname for the user - user user name - new_password new password - - NOTE: - new_password cannot be NULL + @param thd THD + @param host Hostname for the user + @param user User name - RETURN VALUE - 0 OK - 1 ERROR ; In this case the error is sent to the client. + @return Error status + @retval 0 OK + @retval 1 Error */ -int check_change_password(THD *thd, const char *host, const char *user, - char *new_password, uint new_password_len) +static int check_alter_user(THD *thd, const char *host, const char *user) { + int error = 1; if (!initialized) { my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); - return(1); + goto end; } - if (!thd->slave_thread && - (strcmp(thd->security_ctx->user, user) || - my_strcasecmp(system_charset_info, host, - thd->security_ctx->priv_host))) + + if (IF_WSREP((!WSREP(thd) || !thd->wsrep_applier), 1) && + !thd->slave_thread && !thd->security_ctx->priv_user[0] && + !in_bootstrap) { - if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 0)) - return(1); + my_message(ER_PASSWORD_ANONYMOUS_USER, + ER_THD(thd, ER_PASSWORD_ANONYMOUS_USER), + MYF(0)); + goto end; } - if (!thd->slave_thread && !thd->security_ctx->user[0]) + if (!host) // Role { - my_message(ER_PASSWORD_ANONYMOUS_USER, ER(ER_PASSWORD_ANONYMOUS_USER), - MYF(0)); - return(1); + my_error(ER_PASSWORD_NO_MATCH, MYF(0)); + goto end; } - size_t len= strlen(new_password); - if (len && len != SCRAMBLED_PASSWORD_CHAR_LENGTH && - len != SCRAMBLED_PASSWORD_CHAR_LENGTH_323) + + if (!thd->slave_thread && + IF_WSREP((!WSREP(thd) || !thd->wsrep_applier),1) && + (strcmp(thd->security_ctx->priv_user, user) || + my_strcasecmp(system_charset_info, host, + thd->security_ctx->priv_host))) { - my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH); - return -1; + if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 0)) + goto end; } - return(0); -} + error = 0; -/* - Change a password for a user +end: + return error; +} +/** + Check if the user is allowed to change password - SYNOPSIS - change_password() - thd Thread handle - host Hostname - user User name - new_password New password for host@user + @param thd THD + @param user User, hostname, new password or password hash - RETURN VALUES - 0 ok - 1 ERROR; In this case the error is sent to the client. + @return Error status + @retval 0 OK + @retval 1 ERROR; In this case the error is sent to the client. */ -bool change_password(THD *thd, const char *host, const char *user, - char *new_password) +bool check_change_password(THD *thd, LEX_USER *user) { - TABLE_LIST tables; - TABLE *table; + LEX_USER *real_user= get_current_user(thd, user); + + if (fix_and_copy_user(real_user, user, thd) || + validate_password(real_user, thd)) + return true; + + *user= *real_user; + + return check_alter_user(thd, user->host.str, user->user.str); +} + + +/** + Change a password for a user. + + @param thd THD + @param user User, hostname, new password hash + + @return Error code + @retval 0 ok + @retval 1 ERROR; In this case the error is sent to the client. +*/ +bool change_password(THD *thd, LEX_USER *user) +{ + TABLE_LIST tables[TABLES_MAX]; /* Buffer should be extended when password length is extended. */ char buff[512]; - ulong query_length; - bool save_binlog_row_based; - uint new_password_len= (uint) strlen(new_password); - bool result= 1; - bool use_salt= 0; + ulong query_length= 0; + enum_binlog_format save_binlog_format; + int result=0; + const CSET_STRING query_save __attribute__((unused)) = thd->query_string; DBUG_ENTER("change_password"); DBUG_PRINT("enter",("host: '%s' user: '%s' new_password: '%s'", - host,user,new_password)); - DBUG_ASSERT(host != 0); // Ensured by parent - - if (check_change_password(thd, host, user, new_password, new_password_len)) - DBUG_RETURN(1); - - tables.init_one_table("mysql", 5, "user", 4, "user", TL_WRITE); - -#ifdef HAVE_REPLICATION - /* - GRANT and REVOKE are applied the slave in/exclusion rules as they are - some kind of updates to the mysql.% tables. - */ - if (thd->slave_thread && rpl_filter->is_on()) - { - /* - The tables must be marked "updating" so that tables_ok() takes them into - account in tests. It's ok to leave 'updating' set after tables_ok. - */ - tables.updating= 1; - /* Thanks to bzero, tables.next==0 */ - if (!(thd->spcont || rpl_filter->tables_ok(0, &tables))) - DBUG_RETURN(0); - } -#endif - if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT))) - DBUG_RETURN(1); + user->host.str, user->user.str, user->pwhash.str)); + DBUG_ASSERT(user->host.str != 0); // Ensured by parent /* This statement will be replicated as a statement, even when using row-based replication. The flag will be reset at the end of the statement. + This has to be handled here as it's called by set_var.cc, which is + not automaticly handled by sql_parse.cc */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + if (mysql_bin_log.is_open() || + (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0))) + { + query_length= sprintf(buff, "SET PASSWORD FOR '%-.120s'@'%-.120s'='%-.120s'", + safe_str(user->user.str), safe_str(user->host.str), + safe_str(user->pwhash.str)); + } + + if (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0)) + { + thd->set_query(buff, query_length, system_charset_info); + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, (char*)"user", NULL); + } + + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user))) + DBUG_RETURN(result != 1); + + result= 1; mysql_mutex_lock(&acl_cache->lock); ACL_USER *acl_user; - if (!(acl_user= find_acl_user(host, user, TRUE))) + if (!(acl_user= find_user_exact(user->host.str, user->user.str))) { mysql_mutex_unlock(&acl_cache->lock); - my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); + my_message(ER_PASSWORD_NO_MATCH, + ER_THD(thd, ER_PASSWORD_NO_MATCH), MYF(0)); goto end; } /* update loaded acl entry: */ - if (acl_user->plugin.str == native_password_plugin_name.str || + if (acl_user->plugin.str == native_password_plugin_name.str || acl_user->plugin.str == old_password_plugin_name.str) { - acl_user->auth_string.str= strmake_root(&mem, new_password, new_password_len); - acl_user->auth_string.length= new_password_len; - set_user_salt(acl_user, new_password, new_password_len); - set_user_plugin(acl_user, new_password_len); - use_salt= 1; + acl_user->auth_string.str= strmake_root(&acl_memroot, user->pwhash.str, user->pwhash.length); + acl_user->auth_string.length= user->pwhash.length; + set_user_salt(acl_user, user->pwhash.str, user->pwhash.length); + set_user_plugin(acl_user, user->pwhash.length); } else - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_SET_PASSWORD_AUTH_PLUGIN, ER(ER_SET_PASSWORD_AUTH_PLUGIN)); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SET_PASSWORD_AUTH_PLUGIN, + ER_THD(thd, ER_SET_PASSWORD_AUTH_PLUGIN)); - if (update_user_table(thd, table, - acl_user->host.hostname ? acl_user->host.hostname : "", - acl_user->user ? acl_user->user : "", - new_password, new_password_len, use_salt)) + if (update_user_table(thd, tables[USER_TABLE].table, + safe_str(acl_user->host.hostname), + safe_str(acl_user->user.str), + user->pwhash.str, user->pwhash.length)) { mysql_mutex_unlock(&acl_cache->lock); /* purecov: deadcode */ goto end; @@ -1987,11 +2856,160 @@ bool change_password(THD *thd, const char *host, const char *user, result= 0; if (mysql_bin_log.is_open()) { + DBUG_ASSERT(query_length); + thd->clear_error(); + result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length, + FALSE, FALSE, FALSE, 0); + } +end: + close_mysql_tables(thd); + +#ifdef WITH_WSREP +WSREP_ERROR_LABEL: + if (WSREP(thd) && !thd->wsrep_applier) + { + WSREP_TO_ISOLATION_END; + + thd->set_query(query_save); + thd->wsrep_exec_mode = LOCAL_STATE; + } +#endif /* WITH_WSREP */ + thd->restore_stmt_binlog_format(save_binlog_format); + + DBUG_RETURN(result); +} + +int acl_check_set_default_role(THD *thd, const char *host, const char *user) +{ + return check_alter_user(thd, host, user); +} + +int acl_set_default_role(THD *thd, const char *host, const char *user, + const char *rolename) +{ + TABLE_LIST tables[TABLES_MAX]; + TABLE *table; + char user_key[MAX_KEY_LENGTH]; + int result= 1; + int error; + ulong query_length= 0; + bool clear_role= FALSE; + char buff[512]; + enum_binlog_format save_binlog_format= + thd->get_current_stmt_binlog_format(); + const CSET_STRING query_save __attribute__((unused)) = thd->query_string; + + DBUG_ENTER("acl_set_default_role"); + DBUG_PRINT("enter",("host: '%s' user: '%s' rolename: '%s'", + safe_str(user), safe_str(host), safe_str(rolename))); + + if (rolename == current_role.str) { + if (!thd->security_ctx->priv_role[0]) + rolename= "NONE"; + else + rolename= thd->security_ctx->priv_role; + } + + if (check_user_can_set_role(user, host, host, rolename, NULL)) + DBUG_RETURN(result); + + if (!strcasecmp(rolename, "NONE")) + clear_role= TRUE; + + if (mysql_bin_log.is_open() || + (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0))) + { query_length= - sprintf(buff,"SET PASSWORD FOR '%-.120s'@'%-.120s'='%-.120s'", - acl_user->user ? acl_user->user : "", - acl_user->host.hostname ? acl_user->host.hostname : "", - new_password); + sprintf(buff,"SET DEFAULT ROLE '%-.120s' FOR '%-.120s'@'%-.120s'", + safe_str(rolename), safe_str(user), safe_str(host)); + } + + if (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0)) + { + thd->set_query(buff, query_length, system_charset_info); + // Attention!!! here is implicit goto error; + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, (char*)"user", NULL); + } + + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user))) + DBUG_RETURN(result != 1); + + table= tables[USER_TABLE].table; + result= 1; + + /* + This statement will be replicated as a statement, even when using + row-based replication. The flag will be reset at the end of the + statement. + This has to be handled here as it's called by set_var.cc, which is + not automaticly handled by sql_parse.cc + */ + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + mysql_mutex_lock(&acl_cache->lock); + ACL_USER *acl_user; + if (!(acl_user= find_user_exact(host, user))) + { + mysql_mutex_unlock(&acl_cache->lock); + my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH), + MYF(0)); + goto end; + } + + if (!clear_role) { + /* set new default_rolename */ + acl_user->default_rolename.str= safe_strdup_root(&acl_memroot, rolename); + acl_user->default_rolename.length= strlen(rolename); + } + else + { + /* clear the default_rolename */ + acl_user->default_rolename.str = NULL; + acl_user->default_rolename.length = 0; + } + + /* update the mysql.user table with the new default role */ + table->use_all_columns(); + if (table->s->fields <= DEFAULT_ROLE_COLUMN_IDX) + { + my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0), + table->alias.c_ptr(), DEFAULT_ROLE_COLUMN_IDX + 1, table->s->fields, + static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID); + mysql_mutex_unlock(&acl_cache->lock); + goto end; + } + table->field[0]->store(host,(uint) strlen(host), system_charset_info); + table->field[1]->store(user,(uint) strlen(user), system_charset_info); + key_copy((uchar *) user_key, table->record[0], table->key_info, + table->key_info->key_length); + + if (table->file->ha_index_read_idx_map(table->record[0], 0, + (uchar *) user_key, HA_WHOLE_KEY, + HA_READ_KEY_EXACT)) + { + mysql_mutex_unlock(&acl_cache->lock); + my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH), + MYF(0)); + goto end; + } + store_record(table, record[1]); + table->field[DEFAULT_ROLE_COLUMN_IDX]->store(acl_user->default_rolename.str, + acl_user->default_rolename.length, + system_charset_info); + if ((error=table->file->ha_update_row(table->record[1],table->record[0])) && + error != HA_ERR_RECORD_IS_THE_SAME) + { + mysql_mutex_unlock(&acl_cache->lock); + table->file->print_error(error,MYF(0)); /* purecov: deadcode */ + goto end; + } + + acl_cache->clear(1); + mysql_mutex_unlock(&acl_cache->lock); + result= 0; + if (mysql_bin_log.is_open()) + { + DBUG_ASSERT(query_length); thd->clear_error(); result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length, FALSE, FALSE, FALSE, 0); @@ -1999,10 +3017,18 @@ bool change_password(THD *thd, const char *host, const char *user, end: close_mysql_tables(thd); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); +#ifdef WITH_WSREP +WSREP_ERROR_LABEL: + if (WSREP(thd) && !thd->wsrep_applier) + { + WSREP_TO_ISOLATION_END; + + thd->set_query(query_save); + thd->wsrep_exec_mode = LOCAL_STATE; + } +#endif /* WITH_WSREP */ + + thd->restore_stmt_binlog_format(save_binlog_format); DBUG_RETURN(result); } @@ -2018,7 +3044,7 @@ end: RETURN FALSE user not fond - TRUE there are such user + TRUE there is such user */ bool is_acl_user(const char *host, const char *user) @@ -2030,45 +3056,96 @@ bool is_acl_user(const char *host, const char *user) return TRUE; mysql_mutex_lock(&acl_cache->lock); - res= find_acl_user(host, user, TRUE) != NULL; + + if (*host) // User + res= find_user_exact(host, user) != NULL; + else // Role + res= find_acl_role(user) != NULL; + mysql_mutex_unlock(&acl_cache->lock); return res; } /* - Find first entry that matches the current user + unlike find_user_exact and find_user_wild, + this function finds anonymous users too, it's when a + user is not empty, but priv_user (acl_user->user) is empty. */ +static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip) +{ + ACL_USER *result= NULL; + mysql_mutex_assert_owner(&acl_cache->lock); + for (uint i=0; i < acl_users.elements; i++) + { + ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*); + if ((!acl_user_tmp->user.str || + !strcmp(user, acl_user_tmp->user.str)) && + compare_hostname(&acl_user_tmp->host, host, ip)) + { + result= acl_user_tmp; + break; + } + } + return result; +} + -static ACL_USER * -find_acl_user(const char *host, const char *user, my_bool exact) +/* + Find first entry that matches the specified user@host pair +*/ +static ACL_USER * find_user_exact(const char *host, const char *user) { - DBUG_ENTER("find_acl_user"); - DBUG_PRINT("enter",("host: '%s' user: '%s'",host,user)); + mysql_mutex_assert_owner(&acl_cache->lock); + for (uint i=0 ; i < acl_users.elements ; i++) + { + ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); + if (acl_user->eq(user, host)) + return acl_user; + } + return 0; +} + +/* + Find first entry that matches the specified user@host pair +*/ +static ACL_USER * find_user_wild(const char *host, const char *user, const char *ip) +{ mysql_mutex_assert_owner(&acl_cache->lock); for (uint i=0 ; i < acl_users.elements ; i++) { ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); - DBUG_PRINT("info",("strcmp('%s','%s'), compare_hostname('%s','%s'),", - user, acl_user->user ? acl_user->user : "", - host, - acl_user->host.hostname ? acl_user->host.hostname : - "")); - if ((!acl_user->user && !user[0]) || - (acl_user->user && !strcmp(user,acl_user->user))) - { - if (exact ? !my_strcasecmp(system_charset_info, host, - acl_user->host.hostname ? - acl_user->host.hostname : "") : - compare_hostname(&acl_user->host,host,host)) - { - DBUG_RETURN(acl_user); - } - } + if (acl_user->wild_eq(user, host, ip)) + return acl_user; } - DBUG_RETURN(0); + return 0; +} + +/* + Find a role with the specified name +*/ +static ACL_ROLE *find_acl_role(const char *role) +{ + DBUG_ENTER("find_acl_role"); + DBUG_PRINT("enter",("role: '%s'", role)); + DBUG_PRINT("info", ("Hash elements: %ld", acl_roles.records)); + + mysql_mutex_assert_owner(&acl_cache->lock); + + ACL_ROLE *r= (ACL_ROLE *)my_hash_search(&acl_roles, (uchar *)role, + safe_strlen(role)); + DBUG_RETURN(r); +} + + +static ACL_USER_BASE *find_acl_user_base(const char *user, const char *host) +{ + if (*host) + return find_user_exact(host, user); + + return find_acl_role(user); } @@ -2105,10 +3182,11 @@ static const char *calc_ip(const char *ip, long *val, char end) static void update_hostname(acl_host_and_ip *host, const char *hostname) { + // fix historical undocumented convention that empty host is the same as '%' + hostname=const_cast<char*>(hostname ? hostname : host_not_specified.str); host->hostname=(char*) hostname; // This will not be modified! - if (!hostname || - (!(hostname=calc_ip(hostname,&host->ip,'/')) || - !(hostname=calc_ip(hostname+1,&host->ip_mask,'\0')))) + if (!(hostname= calc_ip(hostname,&host->ip,'/')) || + !(hostname= calc_ip(hostname+1,&host->ip_mask,'\0'))) { host->ip= host->ip_mask=0; // Not a masked ip } @@ -2209,23 +3287,22 @@ bool hostname_requires_resolving(const char *hostname) } -/* +/** Update record for user in mysql.user privilege table with new password. - SYNOPSIS - update_user_table() - thd Thread handle - table Pointer to TABLE object for open mysql.user table - host/user Hostname/username pair identifying user for which - new password should be set - new_password New password - new_password_len Length of new password + @param thd THD + @param table Pointer to TABLE object for open mysql.user table + @param host Hostname + @param user Username + @param new_password New password hash + @param new_password_len Length of new password hash + + @see change_password */ static bool update_user_table(THD *thd, TABLE *table, const char *host, const char *user, - const char *new_password, uint new_password_len, - bool reset_plugin) + const char *new_password, uint new_password_len) { char user_key[MAX_KEY_LENGTH]; int error; @@ -2242,17 +3319,12 @@ static bool update_user_table(THD *thd, TABLE *table, (uchar *) user_key, HA_WHOLE_KEY, HA_READ_KEY_EXACT)) { - my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), + my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH), MYF(0)); /* purecov: deadcode */ DBUG_RETURN(1); /* purecov: deadcode */ } store_record(table,record[1]); table->field[2]->store(new_password, new_password_len, system_charset_info); - if (reset_plugin && table->s->fields >= 41) - { - table->field[40]->reset(); - table->field[41]->reset(); - } if ((error=table->file->ha_update_row(table->record[1],table->record[0])) && error != HA_ERR_RECORD_IS_THE_SAME) { @@ -2273,9 +3345,9 @@ static bool update_user_table(THD *thd, TABLE *table, static bool test_if_create_new_users(THD *thd) { Security_context *sctx= thd->security_ctx; - bool create_new_users= test(sctx->master_access & INSERT_ACL) || + bool create_new_users= MY_TEST(sctx->master_access & INSERT_ACL) || (!opt_safe_user_create && - test(sctx->master_access & CREATE_USER_ACL)); + MY_TEST(sctx->master_access & CREATE_USER_ACL)); if (!create_new_users) { TABLE_LIST tl; @@ -2286,6 +3358,8 @@ static bool test_if_create_new_users(THD *thd) db_access=acl_get(sctx->host, sctx->ip, sctx->priv_user, tl.db, 0); + if (sctx->priv_role[0]) + db_access|= acl_get("", "", sctx->priv_role, tl.db, 0); if (!(db_access & INSERT_ACL)) { if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE)) @@ -2302,28 +3376,39 @@ static bool test_if_create_new_users(THD *thd) static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo, ulong rights, bool revoke_grant, - bool can_create_user, bool no_auto_create) + bool can_create_user, bool no_auto_create) { int error = -1; bool old_row_exists=0; char what= (revoke_grant) ? 'N' : 'Y'; uchar user_key[MAX_KEY_LENGTH]; + bool handle_as_role= combo.is_role(); LEX *lex= thd->lex; DBUG_ENTER("replace_user_table"); mysql_mutex_assert_owner(&acl_cache->lock); - if (combo.password.str && combo.password.str[0]) + if (combo.pwhash.str && combo.pwhash.str[0]) { - if (combo.password.length != SCRAMBLED_PASSWORD_CHAR_LENGTH && - combo.password.length != SCRAMBLED_PASSWORD_CHAR_LENGTH_323) + if (combo.pwhash.length != SCRAMBLED_PASSWORD_CHAR_LENGTH && + combo.pwhash.length != SCRAMBLED_PASSWORD_CHAR_LENGTH_323) { + DBUG_ASSERT(0); my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH); DBUG_RETURN(-1); } } else - combo.password= empty_lex_str; + combo.pwhash= empty_lex_str; + + /* if the user table is not up to date, we can't handle role updates */ + if (table->s->fields <= ROLE_ASSIGN_COLUMN_IDX && handle_as_role) + { + my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0), + table->alias.c_ptr(), ROLE_ASSIGN_COLUMN_IDX + 1, table->s->fields, + static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID); + DBUG_RETURN(-1); + } table->use_all_columns(); table->field[0]->store(combo.host.str,combo.host.length, @@ -2356,7 +3441,7 @@ static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo, see also test_if_create_new_users() */ - else if (!combo.password.length && !combo.plugin.length && no_auto_create) + else if (!combo.pwhash.length && !combo.plugin.length && no_auto_create) { my_error(ER_PASSWORD_NO_MATCH, MYF(0)); goto end; @@ -2388,6 +3473,10 @@ static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo, store_record(table,record[1]); // Save copy for update } + if (!old_row_exists || combo.pwtext.length || combo.pwhash.length) + if (!handle_as_role && validate_password(&combo, thd)) + goto end; + /* Update table columns with new privileges */ Field **tmp_field; @@ -2403,8 +3492,8 @@ static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo, } rights= get_access(table, 3, &next_field); DBUG_PRINT("info",("table fields: %d",table->s->fields)); - if (combo.password.str[0]) - table->field[2]->store(combo.password.str, combo.password.length, system_charset_info); + if (combo.pwhash.str[0]) + table->field[2]->store(combo.pwhash.str, combo.pwhash.length, system_charset_info); if (table->s->fields >= 31) /* From 4.0.0 we have more fields */ { /* We write down SSL related ACL stuff */ @@ -2460,8 +3549,6 @@ static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo, if (table->s->fields >= 36 && (mqh.specified_limits & USER_RESOURCES::USER_CONNECTIONS)) table->field[next_field+3]->store((longlong) mqh.user_conn, FALSE); - mqh_used= mqh_used || mqh.questions || mqh.updates || mqh.conn_per_hour; - next_field+= 4; if (table->s->fields >= 41) { @@ -2469,19 +3556,38 @@ static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo, table->field[next_field + 1]->set_notnull(); if (combo.plugin.str[0]) { - DBUG_ASSERT(combo.password.str[0] == 0); + DBUG_ASSERT(combo.pwhash.str[0] == 0); table->field[2]->reset(); table->field[next_field]->store(combo.plugin.str, combo.plugin.length, system_charset_info); table->field[next_field + 1]->store(combo.auth.str, combo.auth.length, system_charset_info); } - if (combo.password.str[0]) + if (combo.pwhash.str[0]) { DBUG_ASSERT(combo.plugin.str[0] == 0); table->field[next_field]->reset(); table->field[next_field + 1]->reset(); } + + if (table->s->fields > MAX_STATEMENT_TIME_COLUMN_IDX) + { + if (mqh.specified_limits & USER_RESOURCES::MAX_STATEMENT_TIME) + table->field[MAX_STATEMENT_TIME_COLUMN_IDX]-> + store(mqh.max_statement_time); + } + } + mqh_used= (mqh_used || mqh.questions || mqh.updates || mqh.conn_per_hour || + mqh.user_conn || mqh.max_statement_time != 0.0); + + /* table format checked earlier */ + if (handle_as_role) + { + if (old_row_exists && !check_is_role(table)) + { + goto end; + } + table->field[ROLE_ASSIGN_COLUMN_IDX]->store("Y", 1, system_charset_info); } } @@ -2496,10 +3602,10 @@ static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo, if ((error= table->file->ha_update_row(table->record[1],table->record[0])) && error != HA_ERR_RECORD_IS_THE_SAME) - { // This should never happen - table->file->print_error(error,MYF(0)); /* purecov: deadcode */ - error= -1; /* purecov: deadcode */ - goto end; /* purecov: deadcode */ + { // This should never happen + table->file->print_error(error,MYF(0)); /* purecov: deadcode */ + error= -1; /* purecov: deadcode */ + goto end; /* purecov: deadcode */ } else error= 0; @@ -2521,27 +3627,37 @@ end: { acl_cache->clear(1); // Clear privilege cache if (old_row_exists) - acl_update_user(combo.user.str, combo.host.str, - combo.password.str, combo.password.length, - lex->ssl_type, - lex->ssl_cipher, - lex->x509_issuer, - lex->x509_subject, - &lex->mqh, - rights, - &combo.plugin, - &combo.auth); + { + if (handle_as_role) + acl_update_role(combo.user.str, rights); + else + acl_update_user(combo.user.str, combo.host.str, + combo.pwhash.str, combo.pwhash.length, + lex->ssl_type, + lex->ssl_cipher, + lex->x509_issuer, + lex->x509_subject, + &lex->mqh, + rights, + &combo.plugin, + &combo.auth); + } else - acl_insert_user(combo.user.str, combo.host.str, - combo.password.str, combo.password.length, - lex->ssl_type, - lex->ssl_cipher, - lex->x509_issuer, - lex->x509_subject, - &lex->mqh, - rights, - &combo.plugin, - &combo.auth); + { + if (handle_as_role) + acl_insert_role(combo.user.str, rights); + else + acl_insert_user(combo.user.str, combo.host.str, + combo.pwhash.str, combo.pwhash.length, + lex->ssl_type, + lex->ssl_cipher, + lex->x509_issuer, + lex->x509_subject, + &lex->mqh, + rights, + &combo.plugin, + &combo.auth); + } } DBUG_RETURN(error); } @@ -2563,17 +3679,16 @@ static int replace_db_table(TABLE *table, const char *db, uchar user_key[MAX_KEY_LENGTH]; DBUG_ENTER("replace_db_table"); - if (!initialized) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); - DBUG_RETURN(-1); - } - /* Check if there is such a user in user table in memory? */ - if (!find_acl_user(combo.host.str,combo.user.str, FALSE)) + if (!find_user_wild(combo.host.str,combo.user.str)) { - my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); - DBUG_RETURN(-1); + /* The user could be a role, check if the user is registered as a role */ + if (!combo.host.length && !find_acl_role(combo.user.str)) + { + my_message(ER_PASSWORD_NO_MATCH, ER_THD(table->in_use, + ER_PASSWORD_NO_MATCH), MYF(0)); + DBUG_RETURN(-1); + } } table->use_all_columns(); @@ -2642,9 +3757,21 @@ static int replace_db_table(TABLE *table, const char *db, acl_cache->clear(1); // Clear privilege cache if (old_row_exists) acl_update_db(combo.user.str,combo.host.str,db,rights); - else - if (rights) - acl_insert_db(combo.user.str,combo.host.str,db,rights); + else if (rights) + { + /* + If we did not have an already existing row, for users, we must always + insert an ACL_DB entry. For roles however, it is possible that one was + already created when DB privileges were propagated from other granted + roles onto the current role. For this case, first try to update the + existing entry, otherwise insert a new one. + */ + if (!combo.is_role() || + !acl_update_db(combo.user.str, combo.host.str, db, rights)) + { + acl_insert_db(combo.user.str,combo.host.str,db,rights); + } + } DBUG_RETURN(0); /* This could only happen if the grant tables got corrupted */ @@ -2655,8 +3782,131 @@ abort: DBUG_RETURN(-1); } +/** + Updates the mysql.roles_mapping table + + @param table TABLE to update + @param user user name of the grantee + @param host host name of the grantee + @param role role name to grant + @param with_admin WITH ADMIN OPTION flag + @param existing the entry in the acl_roles_mappings hash or NULL. + it is never NULL if revoke_grant is true. + it is NULL when a new pair is added, it's not NULL + when an existing pair is updated. + @param revoke_grant true for REVOKE, false for GRANT +*/ +static int +replace_roles_mapping_table(TABLE *table, LEX_STRING *user, LEX_STRING *host, + LEX_STRING *role, bool with_admin, + ROLE_GRANT_PAIR *existing, bool revoke_grant) +{ + DBUG_ENTER("replace_roles_mapping_table"); + + uchar row_key[MAX_KEY_LENGTH]; + int error; + table->use_all_columns(); + restore_record(table, s->default_values); + table->field[0]->store(host->str, host->length, system_charset_info); + table->field[1]->store(user->str, user->length, system_charset_info); + table->field[2]->store(role->str, role->length, system_charset_info); + + DBUG_ASSERT(!revoke_grant || existing); + + if (existing) // delete or update + { + key_copy(row_key, table->record[0], table->key_info, + table->key_info->key_length); + if (table->file->ha_index_read_idx_map(table->record[1], 0, row_key, + HA_WHOLE_KEY, HA_READ_KEY_EXACT)) + { + /* No match */ + DBUG_RETURN(1); + } + if (revoke_grant && !with_admin) + { + if ((error= table->file->ha_delete_row(table->record[1]))) + { + DBUG_PRINT("info", ("error deleting row '%s' '%s' '%s'", + host->str, user->str, role->str)); + goto table_error; + } + } + else if (with_admin) + { + table->field[3]->store(!revoke_grant + 1); + + if ((error= table->file->ha_update_row(table->record[1], table->record[0]))) + { + DBUG_PRINT("info", ("error updating row '%s' '%s' '%s'", + host->str, user->str, role->str)); + goto table_error; + } + } + DBUG_RETURN(0); + } + + table->field[3]->store(with_admin + 1); + + if ((error= table->file->ha_write_row(table->record[0]))) + { + DBUG_PRINT("info", ("error inserting row '%s' '%s' '%s'", + host->str, user->str, role->str)); + goto table_error; + } -static void + /* all ok */ + DBUG_RETURN(0); + +table_error: + DBUG_PRINT("info", ("table error")); + table->file->print_error(error, MYF(0)); + DBUG_RETURN(1); +} + + +/** + Updates the acl_roles_mappings hash + + @param user user name of the grantee + @param host host name of the grantee + @param role role name to grant + @param with_admin WITH ADMIN OPTION flag + @param existing the entry in the acl_roles_mappings hash or NULL. + it is never NULL if revoke_grant is true. + it is NULL when a new pair is added, it's not NULL + when an existing pair is updated. + @param revoke_grant true for REVOKE, false for GRANT +*/ +static int +update_role_mapping(LEX_STRING *user, LEX_STRING *host, LEX_STRING *role, + bool with_admin, ROLE_GRANT_PAIR *existing, bool revoke_grant) +{ + if (revoke_grant) + { + if (with_admin) + { + existing->with_admin= false; + return 0; + } + return my_hash_delete(&acl_roles_mappings, (uchar*)existing); + } + + if (existing) + { + existing->with_admin|= with_admin; + return 0; + } + + /* allocate a new entry that will go in the hash */ + ROLE_GRANT_PAIR *hash_entry= new (&acl_memroot) ROLE_GRANT_PAIR; + if (hash_entry->init(&acl_memroot, user->str, host->str, + role->str, with_admin)) + return 1; + return my_hash_insert(&acl_roles_mappings, (uchar*) hash_entry); +} + +static void acl_update_proxy_user(ACL_PROXY_USER *new_value, bool is_revoke) { mysql_mutex_assert_owner(&acl_cache->lock); @@ -2664,7 +3914,7 @@ acl_update_proxy_user(ACL_PROXY_USER *new_value, bool is_revoke) DBUG_ENTER("acl_update_proxy_user"); for (uint i= 0; i < acl_proxy_users.elements; i++) { - ACL_PROXY_USER *acl_user= + ACL_PROXY_USER *acl_user= dynamic_element(&acl_proxy_users, i, ACL_PROXY_USER *); if (acl_user->pk_equals(new_value)) @@ -2686,7 +3936,7 @@ acl_update_proxy_user(ACL_PROXY_USER *new_value, bool is_revoke) } -static void +static void acl_insert_proxy_user(ACL_PROXY_USER *new_value) { DBUG_ENTER("acl_insert_proxy_user"); @@ -2699,9 +3949,9 @@ acl_insert_proxy_user(ACL_PROXY_USER *new_value) } -static int +static int replace_proxies_priv_table(THD *thd, TABLE *table, const LEX_USER *user, - const LEX_USER *proxied_user, bool with_grant_arg, + const LEX_USER *proxied_user, bool with_grant_arg, bool revoke_grant) { bool old_row_exists= 0; @@ -2712,21 +3962,16 @@ replace_proxies_priv_table(THD *thd, TABLE *table, const LEX_USER *user, DBUG_ENTER("replace_proxies_priv_table"); - if (!initialized) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); - DBUG_RETURN(-1); - } - /* Check if there is such a user in user table in memory? */ - if (!find_acl_user(user->host.str,user->user.str, FALSE)) + if (!find_user_wild(user->host.str,user->user.str)) { - my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); + my_message(ER_PASSWORD_NO_MATCH, + ER_THD(thd, ER_PASSWORD_NO_MATCH), MYF(0)); DBUG_RETURN(-1); } table->use_all_columns(); - ACL_PROXY_USER::store_pk (table, &user->host, &user->user, + ACL_PROXY_USER::store_pk (table, &user->host, &user->user, &proxied_user->host, &proxied_user->user); key_copy(user_key, table->record[0], table->key_info, @@ -2799,7 +4044,7 @@ replace_proxies_priv_table(THD *thd, TABLE *table, const LEX_USER *user, } else { - new_grant.init(&mem, user->host.str, user->user.str, + new_grant.init(&acl_memroot, user->host.str, user->user.str, proxied_user->host.str, proxied_user->user.str, with_grant_arg); acl_insert_proxy_user(&new_grant); @@ -2825,11 +4070,16 @@ class GRANT_COLUMN :public Sql_alloc public: char *column; ulong rights; + ulong init_rights; uint key_length; - GRANT_COLUMN(String &c, ulong y) :rights (y) + GRANT_COLUMN(String &c, ulong y) :rights (y), init_rights(y) { - column= (char*) memdup_root(&memex,c.ptr(), key_length=c.length()); + column= (char*) memdup_root(&grant_memroot,c.ptr(), key_length=c.length()); } + + /* this constructor assumes thas source->column is allocated in grant_memroot */ + GRANT_COLUMN(GRANT_COLUMN *source) : column(source->column), + rights (source->rights), init_rights(0), key_length(source->key_length) { } }; @@ -2840,13 +4090,13 @@ static uchar* get_key_column(GRANT_COLUMN *buff, size_t *length, return (uchar*) buff->column; } - class GRANT_NAME :public Sql_alloc { public: acl_host_and_ip host; char *db, *user, *tname, *hash_key; ulong privs; + ulong init_privs; /* privileges found in physical table */ ulong sort; size_t key_length; GRANT_NAME(const char *h, const char *d,const char *u, @@ -2864,6 +4114,7 @@ class GRANT_TABLE :public GRANT_NAME { public: ulong cols; + ulong init_cols; /* privileges found in physical table */ HASH hash_columns; GRANT_TABLE(const char *h, const char *d,const char *u, @@ -2871,6 +4122,11 @@ public: GRANT_TABLE (TABLE *form, TABLE *col_privs); ~GRANT_TABLE(); bool ok() { return privs != 0 || cols != 0; } + void init_hash() + { + my_hash_init2(&hash_columns, 4, system_charset_info, 0, 0, 0, + (my_hash_get_key) get_key_column, 0, 0, 0); + } }; @@ -2879,29 +4135,29 @@ void GRANT_NAME::set_user_details(const char *h, const char *d, bool is_routine) { /* Host given by user */ - update_hostname(&host, strdup_root(&memex, h)); + update_hostname(&host, strdup_root(&grant_memroot, h)); if (db != d) { - db= strdup_root(&memex, d); + db= strdup_root(&grant_memroot, d); if (lower_case_table_names) my_casedn_str(files_charset_info, db); } - user = strdup_root(&memex,u); + user = strdup_root(&grant_memroot,u); sort= get_sort(3,host.hostname,db,user); if (tname != t) { - tname= strdup_root(&memex, t); + tname= strdup_root(&grant_memroot, t); if (lower_case_table_names || is_routine) my_casedn_str(files_charset_info, tname); } key_length= strlen(d) + strlen(u)+ strlen(t)+3; - hash_key= (char*) alloc_root(&memex,key_length); + hash_key= (char*) alloc_root(&grant_memroot,key_length); strmov(strmov(strmov(hash_key,user)+1,db)+1,tname); } GRANT_NAME::GRANT_NAME(const char *h, const char *d,const char *u, const char *t, ulong p, bool is_routine) - :db(0), tname(0), privs(p) + :db(0), tname(0), privs(p), init_privs(p) { set_user_details(h, d, u, t, is_routine); } @@ -2910,20 +4166,27 @@ GRANT_TABLE::GRANT_TABLE(const char *h, const char *d,const char *u, const char *t, ulong p, ulong c) :GRANT_NAME(h,d,u,t,p, FALSE), cols(c) { - (void) my_hash_init2(&hash_columns,4,system_charset_info, - 0,0,0, (my_hash_get_key) get_key_column,0,0); + init_hash(); } - +/* + create a new GRANT_TABLE entry for role inheritance. init_* fields are set + to 0 +*/ GRANT_NAME::GRANT_NAME(TABLE *form, bool is_routine) { - update_hostname(&host, get_field(&memex, form->field[0])); - db= get_field(&memex,form->field[1]); - user= get_field(&memex,form->field[2]); - if (!user) - user= (char*) ""; + user= safe_str(get_field(&grant_memroot,form->field[2])); + + const char *hostname= get_field(&grant_memroot, form->field[0]); + mysql_mutex_lock(&acl_cache->lock); + if (!hostname && find_acl_role(user)) + hostname= ""; + mysql_mutex_unlock(&acl_cache->lock); + update_hostname(&host, hostname); + + db= get_field(&grant_memroot,form->field[1]); sort= get_sort(3, host.hostname, db, user); - tname= get_field(&memex,form->field[3]); + tname= get_field(&grant_memroot,form->field[3]); if (!db || !tname) { /* Wrong table row; Ignore it */ @@ -2939,10 +4202,11 @@ GRANT_NAME::GRANT_NAME(TABLE *form, bool is_routine) my_casedn_str(files_charset_info, tname); } key_length= (strlen(db) + strlen(user) + strlen(tname) + 3); - hash_key= (char*) alloc_root(&memex, key_length); + hash_key= (char*) alloc_root(&grant_memroot, key_length); strmov(strmov(strmov(hash_key,user)+1,db)+1,tname); privs = (ulong) form->field[6]->val_int(); privs = fix_rights_for_table(privs); + init_privs= privs; } @@ -2959,17 +4223,23 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) return; } cols= (ulong) form->field[7]->val_int(); - cols = fix_rights_for_column(cols); + cols= fix_rights_for_column(cols); + /* + Initial columns privileges are the same as column privileges on creation. + In case of roles, the cols privilege bits can get inherited and thus + cause the cols field to change. The init_cols field is always the same + as the physical table entry + */ + init_cols= cols; + + init_hash(); - (void) my_hash_init2(&hash_columns,4,system_charset_info, - 0,0,0, (my_hash_get_key) get_key_column,0,0); if (cols) { uint key_prefix_len; KEY_PART_INFO *key_part= col_privs->key_info->key_part; col_privs->field[0]->store(host.hostname, - host.hostname ? (uint) strlen(host.hostname) : - 0, + (uint) safe_strlen(host.hostname), system_charset_info); col_privs->field[1]->store(db,(uint) strlen(db), system_charset_info); col_privs->field[2]->store(user,(uint) strlen(user), system_charset_info); @@ -2985,6 +4255,7 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) if (col_privs->file->ha_index_init(0, 1)) { cols= 0; + init_cols= 0; return; } @@ -2992,7 +4263,8 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) (key_part_map)15, HA_READ_KEY_EXACT)) { - cols = 0; /* purecov: deadcode */ + cols= 0; /* purecov: deadcode */ + init_cols= 0; col_privs->file->ha_index_end(); return; } @@ -3007,13 +4279,13 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) fix_rights_for_column(priv)))) { /* Don't use this entry */ - privs = cols = 0; /* purecov: deadcode */ + privs= cols= init_privs= init_cols=0; /* purecov: deadcode */ return; /* purecov: deadcode */ } if (my_hash_insert(&hash_columns, (uchar *) mem_check)) { /* Invalidate this entry */ - privs= cols= 0; + privs= cols= init_privs= init_cols=0; return; } } while (!col_privs->file->ha_index_next(col_privs->record[0]) && @@ -3037,9 +4309,9 @@ static uchar* get_grant_table(GRANT_NAME *buff, size_t *length, } -void free_grant_table(GRANT_TABLE *grant_table) +static void free_grant_table(GRANT_TABLE *grant_table) { - my_hash_free(&grant_table->hash_columns); + grant_table->~GRANT_TABLE(); } @@ -3094,7 +4366,7 @@ static GRANT_NAME *name_hash_search(HASH *name_hash, } -inline GRANT_NAME * +static GRANT_NAME * routine_hash_search(const char *host, const char *ip, const char *db, const char *user, const char *tname, bool proc, bool exact) { @@ -3104,7 +4376,7 @@ routine_hash_search(const char *host, const char *ip, const char *db, } -inline GRANT_TABLE * +static GRANT_TABLE * table_hash_search(const char *host, const char *ip, const char *db, const char *user, const char *tname, bool exact) { @@ -3113,7 +4385,7 @@ table_hash_search(const char *host, const char *ip, const char *db, } -inline GRANT_COLUMN * +static GRANT_COLUMN * column_hash_search(GRANT_TABLE *t, const char *cname, uint length) { return (GRANT_COLUMN*) my_hash_search(&t->hash_columns, @@ -3295,7 +4567,10 @@ static int replace_column_table(GRANT_TABLE *g_t, goto end; /* purecov: deadcode */ } if (grant_column) - grant_column->rights = privileges; // Update hash + { + grant_column->rights = privileges; // Update hash + grant_column->init_rights = privileges; + } } else { @@ -3352,11 +4627,14 @@ static int replace_table_table(THD *thd, GRANT_TABLE *grant_table, The following should always succeed as new users are created before this function is called! */ - if (!find_acl_user(combo.host.str,combo.user.str, FALSE)) + if (!find_user_wild(combo.host.str,combo.user.str)) { - my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), - MYF(0)); /* purecov: deadcode */ - DBUG_RETURN(-1); /* purecov: deadcode */ + if (!combo.host.length && !find_acl_role(combo.user.str)) + { + my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH), + MYF(0)); /* purecov: deadcode */ + DBUG_RETURN(-1); /* purecov: deadcode */ + } } table->use_all_columns(); @@ -3440,6 +4718,9 @@ static int replace_table_table(THD *thd, GRANT_TABLE *grant_table, if (rights | col_rights) { + grant_table->init_privs= rights; + grant_table->init_cols= col_rights; + grant_table->privs= rights; grant_table->cols= col_rights; } @@ -3469,12 +4750,13 @@ static int replace_routine_table(THD *thd, GRANT_NAME *grant_name, int old_row_exists= 1; int error=0; ulong store_proc_rights; + HASH *hash= is_proc ? &proc_priv_hash : &func_priv_hash; DBUG_ENTER("replace_routine_table"); - if (!initialized) + if (revoke_grant && !grant_name->init_privs) // only inherited role privs { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); - DBUG_RETURN(-1); + my_hash_delete(hash, (uchar*) grant_name); + DBUG_RETURN(0); } get_grantor(thd, grantor); @@ -3506,6 +4788,9 @@ static int replace_routine_table(THD *thd, GRANT_NAME *grant_name, The following should never happen as we first check the in memory grant tables for the user. There is however always a small change that the user has modified the grant tables directly. + + Also, there is also a second posibility that this routine entry + is created for a role by being inherited from a granted role. */ if (revoke_grant) { // no row, no revoke @@ -3560,12 +4845,12 @@ static int replace_routine_table(THD *thd, GRANT_NAME *grant_name, if (rights) { + grant_name->init_privs= rights; grant_name->privs= rights; } else { - my_hash_delete(is_proc ? &proc_priv_hash : &func_priv_hash,(uchar*) - grant_name); + my_hash_delete(hash, (uchar*) grant_name); } DBUG_RETURN(0); @@ -3576,6 +4861,905 @@ table_error: } +/***************************************************************** + Role privilege propagation and graph traversal functionality + + According to the SQL standard, a role can be granted to a role, + thus role grants can create an arbitrarily complex directed acyclic + graph (a standard explicitly specifies that cycles are not allowed). + + When a privilege is granted to a role, it becomes available to all grantees. + The code below recursively traverses a DAG of role grants, propagating + privilege changes. + + The traversal function can work both ways, from roles to grantees or + from grantees to roles. The first is used for privilege propagation, + the second - for SHOW GRANTS and I_S.APPLICABLE_ROLES + + The role propagation code is smart enough to propagate only privilege + changes to one specific database, table, or routine, if only they + were changed (like in GRANT ... ON ... TO ...) or it can propagate + everything (on startup or after FLUSH PRIVILEGES). + + It traverses only a subgraph that's accessible from the modified role, + only visiting roles that can be possibly affected by the GRANT statement. + + Additionally, it stops traversal early, if this particular GRANT statement + didn't result in any changes of privileges (e.g. both role1 and role2 + are granted to the role3, both role1 and role2 have SELECT privilege. + if SELECT is revoked from role1 it won't change role3 privileges, + so we won't traverse from role3 to its grantees). +******************************************************************/ +struct PRIVS_TO_MERGE +{ + enum what { ALL, GLOBAL, DB, TABLE_COLUMN, PROC, FUNC } what; + const char *db, *name; +}; + +static int init_role_for_merging(ACL_ROLE *role, void *context) +{ + role->counter= 0; + return 0; +} + +static int count_subgraph_nodes(ACL_ROLE *role, ACL_ROLE *grantee, void *context) +{ + grantee->counter++; + return 0; +} + +static int merge_role_privileges(ACL_ROLE *, ACL_ROLE *, void *); + +/** + rebuild privileges of all affected roles + + entry point into role privilege propagation. after privileges of the + 'role' were changed, this function rebuilds privileges of all affected roles + as necessary. +*/ +static void propagate_role_grants(ACL_ROLE *role, + enum PRIVS_TO_MERGE::what what, + const char *db= 0, const char *name= 0) +{ + + mysql_mutex_assert_owner(&acl_cache->lock); + PRIVS_TO_MERGE data= { what, db, name }; + + /* + Changing privileges of a role causes all other roles that had + this role granted to them to have their rights invalidated. + + We need to rebuild all roles' related access bits. + + This cannot be a simple depth-first search, instead we have to merge + privieges for all roles granted to a specific grantee, *before* + merging privileges for this grantee. In other words, we must visit all + parent nodes of a specific node, before descencing into this node. + + For example, if role1 is granted to role2 and role3, and role3 is + granted to role2, after "GRANT ... role1", we cannot merge privileges + for role2, until role3 is merged. The counter will be 0 for role1, 2 + for role2, 1 for role3. Traversal will start from role1, go to role2, + decrement the counter, backtrack, go to role3, merge it, go to role2 + again, merge it. + + And the counter is not just "all parent nodes", but only parent nodes + that are part of the subgraph we're interested in. For example, if + both roleA and roleB are granted to roleC, then roleC has two parent + nodes. But when granting a privilege to roleA, we're only looking at a + subgraph that includes roleA and roleC (roleB cannot be possibly + affected by that grant statement). In this subgraph roleC has only one + parent. + + (on the other hand, in acl_load we want to update all roles, and + the counter is exactly equal to the number of all parent nodes) + + Thus, we do two graph traversals here. First we only count parents + that are part of the subgraph. On the second traversal we decrement + the counter and actually merge privileges for a node when a counter + drops to zero. + */ + traverse_role_graph_up(role, &data, init_role_for_merging, count_subgraph_nodes); + traverse_role_graph_up(role, &data, NULL, merge_role_privileges); +} + + +// State of a node during a Depth First Search exploration +struct NODE_STATE +{ + ACL_USER_BASE *node_data; /* pointer to the node data */ + uint neigh_idx; /* the neighbour that needs to be evaluated next */ +}; + +/** + Traverse the role grant graph and invoke callbacks at the specified points. + + @param user user or role to start traversal from + @param context opaque parameter to pass to callbacks + @param offset offset to ACL_ROLE::parent_grantee or to + ACL_USER_BASE::role_grants. Depending on this value, + traversal will go from roles to grantees or from + grantees to roles. + @param on_node called when a node is visited for the first time. + Returning a value <0 will abort the traversal. + @param on_edge called for every edge in the graph, when traversal + goes from a node to a neighbour node. + Returning <0 will abort the traversal. Returning >0 + will make the traversal not to follow this edge. + + @note + The traverse method is a DEPTH FIRST SEARCH, but callbacks can influence + that (on_edge returning >0 value). + + @note + This function should not be called directly, use + traverse_role_graph_up() and traverse_role_graph_down() instead. + + @retval 0 traversal finished successfully + @retval ROLE_CYCLE_FOUND traversal aborted, cycle detected + @retval <0 traversal was aborted, because a callback returned + this error code +*/ +static int traverse_role_graph_impl(ACL_USER_BASE *user, void *context, + off_t offset, + int (*on_node) (ACL_USER_BASE *role, void *context), + int (*on_edge) (ACL_USER_BASE *current, ACL_ROLE *neighbour, void *context)) +{ + DBUG_ENTER("traverse_role_graph_impl"); + DBUG_ASSERT(user); + DBUG_PRINT("enter",("role: '%s'", user->user.str)); + /* + The search operation should always leave the ROLE_ON_STACK and + ROLE_EXPLORED flags clean for all nodes involved in the search + */ + DBUG_ASSERT(!(user->flags & ROLE_ON_STACK)); + DBUG_ASSERT(!(user->flags & ROLE_EXPLORED)); + mysql_mutex_assert_owner(&acl_cache->lock); + + /* + Stack used to simulate the recursive calls of DFS. + It uses a Dynamic_array to reduce the number of + malloc calls to a minimum + */ + Dynamic_array<NODE_STATE> stack(20,50); + Dynamic_array<ACL_USER_BASE *> to_clear(20,50); + NODE_STATE state; /* variable used to insert elements in the stack */ + int result= 0; + + state.neigh_idx= 0; + state.node_data= user; + user->flags|= ROLE_ON_STACK; + + stack.push(state); + to_clear.push(user); + + user->flags|= ROLE_OPENED; + if (on_node && ((result= on_node(user, context)) < 0)) + goto end; + + while (stack.elements()) + { + NODE_STATE *curr_state= stack.back(); + + DBUG_ASSERT(curr_state->node_data->flags & ROLE_ON_STACK); + + ACL_USER_BASE *current= curr_state->node_data; + ACL_USER_BASE *neighbour= NULL; + DBUG_PRINT("info", ("Examining role %s", current->user.str)); + /* + Iterate through the neighbours until a first valid jump-to + neighbour is found + */ + bool found= FALSE; + uint i; + DYNAMIC_ARRAY *array= (DYNAMIC_ARRAY *)(((char*)current) + offset); + + DBUG_ASSERT(array == ¤t->role_grants || current->flags & IS_ROLE); + for (i= curr_state->neigh_idx; i < array->elements; i++) + { + neighbour= *(dynamic_element(array, i, ACL_ROLE**)); + if (!(neighbour->flags & IS_ROLE)) + continue; + + DBUG_PRINT("info", ("Examining neighbour role %s", neighbour->user.str)); + + /* check if it forms a cycle */ + if (neighbour->flags & ROLE_ON_STACK) + { + DBUG_PRINT("info", ("Found cycle")); + result= ROLE_CYCLE_FOUND; + goto end; + } + + if (!(neighbour->flags & ROLE_OPENED)) + { + neighbour->flags|= ROLE_OPENED; + to_clear.push(neighbour); + if (on_node && ((result= on_node(neighbour, context)) < 0)) + goto end; + } + + if (on_edge) + { + result= on_edge(current, (ACL_ROLE*)neighbour, context); + if (result < 0) + goto end; + if (result > 0) + continue; + } + + /* Check if it was already explored, in that case, move on */ + if (neighbour->flags & ROLE_EXPLORED) + continue; + + found= TRUE; + break; + } + + /* found states that we have found a node to jump next into */ + if (found) + { + curr_state->neigh_idx= i + 1; + + /* some sanity checks */ + DBUG_ASSERT(!(neighbour->flags & ROLE_ON_STACK)); + + /* add the neighbour on the stack */ + neighbour->flags|= ROLE_ON_STACK; + state.neigh_idx= 0; + state.node_data= neighbour; + stack.push(state); + } + else + { + /* Make sure we got a correct node */ + DBUG_ASSERT(curr_state->node_data->flags & ROLE_ON_STACK); + /* Finished with exploring the current node, pop it off the stack */ + curr_state= &stack.pop(); + curr_state->node_data->flags&= ~ROLE_ON_STACK; /* clear the on-stack bit */ + curr_state->node_data->flags|= ROLE_EXPLORED; + } + } + +end: + /* Cleanup */ + for (uint i= 0; i < to_clear.elements(); i++) + { + ACL_USER_BASE *current= to_clear.at(i); + DBUG_ASSERT(current->flags & (ROLE_EXPLORED | ROLE_ON_STACK | ROLE_OPENED)); + current->flags&= ~(ROLE_EXPLORED | ROLE_ON_STACK | ROLE_OPENED); + } + DBUG_RETURN(result); +} + +/** + Traverse the role grant graph, going from a role to its grantees. + + This is used to propagate changes in privileges, for example, + when GRANT or REVOKE is issued for a role. +*/ + +static int traverse_role_graph_up(ACL_ROLE *role, void *context, + int (*on_node) (ACL_ROLE *role, void *context), + int (*on_edge) (ACL_ROLE *current, ACL_ROLE *neighbour, void *context)) +{ + return traverse_role_graph_impl(role, context, + my_offsetof(ACL_ROLE, parent_grantee), + (int (*)(ACL_USER_BASE *, void *))on_node, + (int (*)(ACL_USER_BASE *, ACL_ROLE *, void *))on_edge); +} + +/** + Traverse the role grant graph, going from a user or a role to granted roles. + + This is used, for example, to print all grants available to a user or a role + (as in SHOW GRANTS). +*/ + +static int traverse_role_graph_down(ACL_USER_BASE *user, void *context, + int (*on_node) (ACL_USER_BASE *role, void *context), + int (*on_edge) (ACL_USER_BASE *current, ACL_ROLE *neighbour, void *context)) +{ + return traverse_role_graph_impl(user, context, + my_offsetof(ACL_USER_BASE, role_grants), + on_node, on_edge); +} + +/* + To find all db/table/routine privilege for a specific role + we need to scan the array of privileges. It can be big. + But the set of privileges granted to a role in question (or + to roles directly granted to the role in question) is supposedly + much smaller. + + We put a role and all roles directly granted to it in a hash, and iterate + the (suposedly long) array of privileges, filtering out "interesting" + entries using the role hash. We put all these "interesting" + entries in a (suposedly small) dynamic array and them use it for merging. +*/ +static uchar* role_key(const ACL_ROLE *role, size_t *klen, my_bool) +{ + *klen= role->user.length; + return (uchar*) role->user.str; +} +typedef Hash_set<ACL_ROLE> role_hash_t; + +static bool merge_role_global_privileges(ACL_ROLE *grantee) +{ + ulong old= grantee->access; + grantee->access= grantee->initial_role_access; + + DBUG_EXECUTE_IF("role_merge_stats", role_global_merges++;); + + for (uint i= 0; i < grantee->role_grants.elements; i++) + { + ACL_ROLE *r= *dynamic_element(&grantee->role_grants, i, ACL_ROLE**); + grantee->access|= r->access; + } + return old != grantee->access; +} + +static int db_name_sort(const int *db1, const int *db2) +{ + return strcmp(acl_dbs.at(*db1).db, acl_dbs.at(*db2).db); +} + +/** + update ACL_DB for given database and a given role with merged privileges + + @param merged ACL_DB of the role in question (or NULL if it wasn't found) + @param first first ACL_DB in an array for the database in question + @param access new privileges for the given role on the gived database + @param role the name of the given role + + @return a bitmap of + 1 - privileges were changed + 2 - ACL_DB was added + 4 - ACL_DB was deleted +*/ +static int update_role_db(int merged, int first, ulong access, char *role) +{ + if (first < 0) + return 0; + + DBUG_EXECUTE_IF("role_merge_stats", role_db_merges++;); + + if (merged < 0) + { + /* + there's no ACL_DB for this role (all db grants come from granted roles) + we need to create it + + Note that we cannot use acl_insert_db() now: + 1. it'll sort elements in the acl_dbs, so the pointers will become invalid + 2. we may need many of them, no need to sort every time + */ + DBUG_ASSERT(access); + ACL_DB acl_db; + acl_db.user= role; + acl_db.host.hostname= const_cast<char*>(""); + acl_db.host.ip= acl_db.host.ip_mask= 0; + acl_db.db= acl_dbs.at(first).db; + acl_db.access= access; + acl_db.initial_access= 0; + acl_db.sort=get_sort(3, "", acl_db.db, role); + acl_dbs.push(acl_db); + return 2; + } + else if (access == 0) + { + /* + there is ACL_DB but the role has no db privileges granted + (all privileges were coming from granted roles, and now those roles + were dropped or had their privileges revoked). + we need to remove this ACL_DB entry + + Note, that we cannot delete now: + 1. it'll shift elements in the acl_dbs, so the pointers will become invalid + 2. it's O(N) operation, and we may need many of them + so we only mark elements deleted and will delete later. + */ + acl_dbs.at(merged).sort= 0; // lower than any valid ACL_DB sort value, will be sorted last + return 4; + } + else if (acl_dbs.at(merged).access != access) + { + /* this is easy */ + acl_dbs.at(merged).access= access; + return 1; + } + return 0; +} + +/** + merges db privileges from roles granted to the role 'grantee'. + + @return true if database privileges of the 'grantee' were changed + +*/ +static bool merge_role_db_privileges(ACL_ROLE *grantee, const char *dbname, + role_hash_t *rhash) +{ + Dynamic_array<int> dbs; + + /* + Supposedly acl_dbs can be huge, but only a handful of db grants + apply to grantee or roles directly granted to grantee. + + Collect these applicable db grants. + */ + for (uint i=0 ; i < acl_dbs.elements() ; i++) + { + ACL_DB *db= &acl_dbs.at(i); + if (db->host.hostname[0]) + continue; + if (dbname && strcmp(db->db, dbname)) + continue; + ACL_ROLE *r= rhash->find(db->user, strlen(db->user)); + if (!r) + continue; + dbs.append(i); + } + dbs.sort(db_name_sort); + + /* + Because dbs array is sorted by the db name, all grants for the same db + (that should be merged) are sorted together. The grantee's ACL_DB element + is not necessarily the first and may be not present at all. + */ + int first= -1, merged= -1; + ulong UNINIT_VAR(access), update_flags= 0; + for (int *p= dbs.front(); p <= dbs.back(); p++) + { + if (first<0 || (!dbname && strcmp(acl_dbs.at(p[0]).db, acl_dbs.at(p[-1]).db))) + { // new db name series + update_flags|= update_role_db(merged, first, access, grantee->user.str); + merged= -1; + access= 0; + first= *p; + } + if (strcmp(acl_dbs.at(*p).user, grantee->user.str) == 0) + access|= acl_dbs.at(merged= *p).initial_access; + else + access|= acl_dbs.at(*p).access; + } + update_flags|= update_role_db(merged, first, access, grantee->user.str); + + /* + to make this code a bit simpler, we sort on deletes, to move + deleted elements to the end of the array. strictly speaking it's + unnecessary, it'd be faster to remove them in one O(N) array scan. + + on the other hand, qsort on almost sorted array is pretty fast anyway... + */ + if (update_flags & (2|4)) + { // inserted or deleted, need to sort + acl_dbs.sort((acl_dbs_cmp)acl_compare); + } + if (update_flags & 4) + { // deleted, trim the end + while (acl_dbs.elements() && acl_dbs.back()->sort == 0) + acl_dbs.pop(); + } + return update_flags; +} + +static int table_name_sort(GRANT_TABLE * const *tbl1, GRANT_TABLE * const *tbl2) +{ + int res = strcmp((*tbl1)->db, (*tbl2)->db); + if (res) return res; + return strcmp((*tbl1)->tname, (*tbl2)->tname); +} + +/** + merges column privileges for the entry 'merged' + + @param merged GRANT_TABLE to merge the privileges into + @param cur first entry in the array of GRANT_TABLE's for a given table + @param last last entry in the array of GRANT_TABLE's for a given table, + all entries between cur and last correspond to the *same* table + + @return 1 if the _set of columns_ in 'merged' was changed + (not if the _set of privileges_ was changed). +*/ +static int update_role_columns(GRANT_TABLE *merged, + GRANT_TABLE **cur, GRANT_TABLE **last) + +{ + ulong rights __attribute__((unused))= 0; + int changed= 0; + if (!merged->cols) + { + changed= merged->hash_columns.records > 0; + my_hash_reset(&merged->hash_columns); + return changed; + } + + DBUG_EXECUTE_IF("role_merge_stats", role_column_merges++;); + + HASH *mh= &merged->hash_columns; + for (uint i=0 ; i < mh->records ; i++) + { + GRANT_COLUMN *col = (GRANT_COLUMN *)my_hash_element(mh, i); + col->rights= col->init_rights; + } + + for (; cur < last; cur++) + { + if (*cur == merged) + continue; + HASH *ch= &cur[0]->hash_columns; + for (uint i=0 ; i < ch->records ; i++) + { + GRANT_COLUMN *ccol = (GRANT_COLUMN *)my_hash_element(ch, i); + GRANT_COLUMN *mcol = (GRANT_COLUMN *)my_hash_search(mh, + (uchar *)ccol->column, ccol->key_length); + if (mcol) + mcol->rights|= ccol->rights; + else + { + changed= 1; + my_hash_insert(mh, (uchar*)new (&grant_memroot) GRANT_COLUMN(ccol)); + } + } + } + + for (uint i=0 ; i < mh->records ; i++) + { + GRANT_COLUMN *col = (GRANT_COLUMN *)my_hash_element(mh, i); + rights|= col->rights; + if (!col->rights) + { + changed= 1; + my_hash_delete(mh, (uchar*)col); + } + } + DBUG_ASSERT(rights == merged->cols); + return changed; +} + +/** + update GRANT_TABLE for a given table and a given role with merged privileges + + @param merged GRANT_TABLE of the role in question (or NULL if it wasn't found) + @param first first GRANT_TABLE in an array for the table in question + @param last last entry in the array of GRANT_TABLE's for a given table, + all entries between first and last correspond to the *same* table + @param privs new table-level privileges for 'merged' + @param cols new OR-ed column-level privileges for 'merged' + @param role the name of the given role + + @return a bitmap of + 1 - privileges were changed + 2 - GRANT_TABLE was added + 4 - GRANT_TABLE was deleted +*/ +static int update_role_table_columns(GRANT_TABLE *merged, + GRANT_TABLE **first, GRANT_TABLE **last, + ulong privs, ulong cols, char *role) +{ + if (!first) + return 0; + + DBUG_EXECUTE_IF("role_merge_stats", role_table_merges++;); + + if (merged == NULL) + { + /* + there's no GRANT_TABLE for this role (all table grants come from granted + roles) we need to create it + */ + DBUG_ASSERT(privs | cols); + merged= new (&grant_memroot) GRANT_TABLE("", first[0]->db, role, first[0]->tname, + privs, cols); + merged->init_privs= merged->init_cols= 0; + update_role_columns(merged, first, last); + my_hash_insert(&column_priv_hash,(uchar*) merged); + return 2; + } + else if ((privs | cols) == 0) + { + /* + there is GRANT_TABLE object but the role has no table or column + privileges granted (all privileges were coming from granted roles, and + now those roles were dropped or had their privileges revoked). + we need to remove this GRANT_TABLE + */ + DBUG_EXECUTE_IF("role_merge_stats", + role_column_merges+= MY_TEST(merged->cols);); + my_hash_delete(&column_priv_hash,(uchar*) merged); + return 4; + } + else + { + bool changed= merged->cols != cols || merged->privs != privs; + merged->cols= cols; + merged->privs= privs; + if (update_role_columns(merged, first, last)) + changed= true; + return changed; + } +} + +/** + merges table privileges from roles granted to the role 'grantee'. + + @return true if table privileges of the 'grantee' were changed + +*/ +static bool merge_role_table_and_column_privileges(ACL_ROLE *grantee, + const char *db, const char *tname, role_hash_t *rhash) +{ + Dynamic_array<GRANT_TABLE *> grants; + DBUG_ASSERT(MY_TEST(db) == MY_TEST(tname)); // both must be set, or neither + + /* + first, collect table/column privileges granted to + roles in question. + */ + for (uint i=0 ; i < column_priv_hash.records ; i++) + { + GRANT_TABLE *grant= (GRANT_TABLE *) my_hash_element(&column_priv_hash, i); + if (grant->host.hostname[0]) + continue; + if (tname && (strcmp(grant->db, db) || strcmp(grant->tname, tname))) + continue; + ACL_ROLE *r= rhash->find(grant->user, strlen(grant->user)); + if (!r) + continue; + grants.append(grant); + } + grants.sort(table_name_sort); + + GRANT_TABLE **first= NULL, *UNINIT_VAR(merged), **cur; + ulong UNINIT_VAR(privs), UNINIT_VAR(cols), update_flags= 0; + for (cur= grants.front(); cur <= grants.back(); cur++) + { + if (!first || + (!tname && (strcmp(cur[0]->db, cur[-1]->db) || + strcmp(cur[0]->tname, cur[-1]->tname)))) + { // new db.tname series + update_flags|= update_role_table_columns(merged, first, cur, + privs, cols, grantee->user.str); + merged= NULL; + privs= cols= 0; + first= cur; + } + if (strcmp(cur[0]->user, grantee->user.str) == 0) + { + merged= cur[0]; + cols|= cur[0]->init_cols; + privs|= cur[0]->init_privs; + } + else + { + cols|= cur[0]->cols; + privs|= cur[0]->privs; + } + } + update_flags|= update_role_table_columns(merged, first, cur, + privs, cols, grantee->user.str); + + return update_flags; +} + +static int routine_name_sort(GRANT_NAME * const *r1, GRANT_NAME * const *r2) +{ + int res= strcmp((*r1)->db, (*r2)->db); + if (res) return res; + return strcmp((*r1)->tname, (*r2)->tname); +} + +/** + update GRANT_NAME for a given routine and a given role with merged privileges + + @param merged GRANT_NAME of the role in question (or NULL if it wasn't found) + @param first first GRANT_NAME in an array for the routine in question + @param privs new routine-level privileges for 'merged' + @param role the name of the given role + @param hash proc_priv_hash or func_priv_hash + + @return a bitmap of + 1 - privileges were changed + 2 - GRANT_NAME was added + 4 - GRANT_NAME was deleted +*/ +static int update_role_routines(GRANT_NAME *merged, GRANT_NAME **first, + ulong privs, char *role, HASH *hash) +{ + if (!first) + return 0; + + DBUG_EXECUTE_IF("role_merge_stats", role_routine_merges++;); + + if (merged == NULL) + { + /* + there's no GRANT_NAME for this role (all routine grants come from granted + roles) we need to create it + */ + DBUG_ASSERT(privs); + merged= new (&grant_memroot) GRANT_NAME("", first[0]->db, role, first[0]->tname, + privs, true); + merged->init_privs= 0; // all privs are inherited + my_hash_insert(hash, (uchar *)merged); + return 2; + } + else if (privs == 0) + { + /* + there is GRANT_NAME but the role has no privileges granted + (all privileges were coming from granted roles, and now those roles + were dropped or had their privileges revoked). + we need to remove this entry + */ + my_hash_delete(hash, (uchar*)merged); + return 4; + } + else if (merged->privs != privs) + { + /* this is easy */ + merged->privs= privs; + return 1; + } + return 0; +} + +/** + merges routine privileges from roles granted to the role 'grantee'. + + @return true if routine privileges of the 'grantee' were changed + +*/ +static bool merge_role_routine_grant_privileges(ACL_ROLE *grantee, + const char *db, const char *tname, role_hash_t *rhash, HASH *hash) +{ + ulong update_flags= 0; + + DBUG_ASSERT(MY_TEST(db) == MY_TEST(tname)); // both must be set, or neither + + Dynamic_array<GRANT_NAME *> grants; + + /* first, collect routine privileges granted to roles in question */ + for (uint i=0 ; i < hash->records ; i++) + { + GRANT_NAME *grant= (GRANT_NAME *) my_hash_element(hash, i); + if (grant->host.hostname[0]) + continue; + if (tname && (strcmp(grant->db, db) || strcmp(grant->tname, tname))) + continue; + ACL_ROLE *r= rhash->find(grant->user, strlen(grant->user)); + if (!r) + continue; + grants.append(grant); + } + grants.sort(routine_name_sort); + + GRANT_NAME **first= NULL, *UNINIT_VAR(merged); + ulong UNINIT_VAR(privs); + for (GRANT_NAME **cur= grants.front(); cur <= grants.back(); cur++) + { + if (!first || + (!tname && (strcmp(cur[0]->db, cur[-1]->db) || + strcmp(cur[0]->tname, cur[-1]->tname)))) + { // new db.tname series + update_flags|= update_role_routines(merged, first, privs, + grantee->user.str, hash); + merged= NULL; + privs= 0; + first= cur; + } + if (strcmp(cur[0]->user, grantee->user.str) == 0) + { + merged= cur[0]; + privs|= cur[0]->init_privs; + } + else + { + privs|= cur[0]->privs; + } + } + update_flags|= update_role_routines(merged, first, privs, + grantee->user.str, hash); + return update_flags; +} + +/** + update privileges of the 'grantee' from all roles, granted to it +*/ +static int merge_role_privileges(ACL_ROLE *role __attribute__((unused)), + ACL_ROLE *grantee, void *context) +{ + PRIVS_TO_MERGE *data= (PRIVS_TO_MERGE *)context; + + DBUG_ASSERT(grantee->counter > 0); + if (--grantee->counter) + return 1; // don't recurse into grantee just yet + + grantee->counter= 1; // Mark the grantee as merged. + + /* if we'll do db/table/routine privileges, create a hash of role names */ + role_hash_t role_hash(role_key); + if (data->what != PRIVS_TO_MERGE::GLOBAL) + { + role_hash.insert(grantee); + for (uint i= 0; i < grantee->role_grants.elements; i++) + role_hash.insert(*dynamic_element(&grantee->role_grants, i, ACL_ROLE**)); + } + + bool all= data->what == PRIVS_TO_MERGE::ALL; + bool changed= false; + if (all || data->what == PRIVS_TO_MERGE::GLOBAL) + changed|= merge_role_global_privileges(grantee); + if (all || data->what == PRIVS_TO_MERGE::DB) + changed|= merge_role_db_privileges(grantee, data->db, &role_hash); + if (all || data->what == PRIVS_TO_MERGE::TABLE_COLUMN) + changed|= merge_role_table_and_column_privileges(grantee, + data->db, data->name, &role_hash); + if (all || data->what == PRIVS_TO_MERGE::PROC) + changed|= merge_role_routine_grant_privileges(grantee, + data->db, data->name, &role_hash, &proc_priv_hash); + if (all || data->what == PRIVS_TO_MERGE::FUNC) + changed|= merge_role_routine_grant_privileges(grantee, + data->db, data->name, &role_hash, &func_priv_hash); + + return !changed; // don't recurse into the subgraph if privs didn't change +} + +static bool merge_one_role_privileges(ACL_ROLE *grantee) +{ + PRIVS_TO_MERGE data= { PRIVS_TO_MERGE::ALL, 0, 0 }; + grantee->counter= 1; + return merge_role_privileges(0, grantee, &data); +} + +/***************************************************************** + End of the role privilege propagation and graph traversal code +******************************************************************/ + +static bool has_auth(LEX_USER *user, LEX *lex) +{ + return user->pwtext.length || user->pwhash.length || user->plugin.length || user->auth.length || + lex->ssl_type != SSL_TYPE_NOT_SPECIFIED || lex->ssl_cipher || + lex->x509_issuer || lex->x509_subject || + lex->mqh.specified_limits; +} + +static bool fix_and_copy_user(LEX_USER *to, LEX_USER *from, THD *thd) +{ + if (to != from) + { + /* preserve authentication information, if LEX_USER was reallocated */ + to->pwtext= from->pwtext; + to->pwhash= from->pwhash; + to->plugin= from->plugin; + to->auth= from->auth; + } + + if (fix_lex_user(thd, to)) + return true; + + return false; +} + +static bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, THD *thd) +{ + if (fix_and_copy_user(to, from, thd)) + return true; + + // if changing auth for an existing user + if (has_auth(to, thd->lex) && find_user_exact(to->host.str, to->user.str)) + { + mysql_mutex_unlock(&acl_cache->lock); + bool res= check_alter_user(thd, to->host.str, to->user.str); + mysql_mutex_lock(&acl_cache->lock); + return res; + } + + return false; +} + + /* Store table level and column level grants in the privilege tables @@ -3599,23 +5783,18 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, bool revoke_grant) { ulong column_priv= 0; + int result; List_iterator <LEX_USER> str_list (user_list); LEX_USER *Str, *tmp_Str; - TABLE_LIST tables[3]; + TABLE_LIST tables[TABLES_MAX]; bool create_new_users=0; char *db_name, *table_name; - bool save_binlog_row_based; DBUG_ENTER("mysql_table_grant"); - if (!initialized) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), - "--skip-grant-tables"); /* purecov: inspected */ - DBUG_RETURN(TRUE); /* purecov: inspected */ - } if (rights & ~TABLE_ACLS) { - my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE), + my_message(ER_ILLEGAL_GRANT_FOR_TABLE, + ER_THD(thd, ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0)); DBUG_RETURN(TRUE); } @@ -3655,12 +5834,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, { if (!(rights & CREATE_ACL)) { - char buf[FN_REFLEN + 1]; - build_table_filename(buf, sizeof(buf) - 1, table_list->db, - table_list->table_name, reg_ext, 0); - fn_format(buf, buf, "", "", MY_UNPACK_FILENAME | MY_RESOLVE_SYMLINKS | - MY_RETURN_REAL_PATH | MY_APPEND_EXT); - if (access(buf,F_OK)) + if (!ha_table_exists(thd, table_list->db, table_list->table_name, 0)) { my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias); DBUG_RETURN(TRUE); @@ -3679,53 +5853,16 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, } } - /* open the mysql.tables_priv and mysql.columns_priv tables */ - - tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("user"), "user", TL_WRITE); - tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("tables_priv"), - "tables_priv", TL_WRITE); - tables[2].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("columns_priv"), - "columns_priv", TL_WRITE); - tables[0].next_local= tables[0].next_global= tables+1; - /* Don't open column table if we don't need it ! */ - if (column_priv || (revoke_grant && ((rights & COL_ACLS) || columns.elements))) - tables[1].next_local= tables[1].next_global= tables+2; - /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. + Open the mysql.user and mysql.tables_priv tables. + Don't open column table if we don't need it ! */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); + int maybe_columns_priv= 0; + if (column_priv || + (revoke_grant && ((rights & COL_ACLS) || columns.elements))) + maybe_columns_priv= Table_columns_priv; -#ifdef HAVE_REPLICATION /* - GRANT and REVOKE are applied the slave in/exclusion rules as they are - some kind of updates to the mysql.% tables. - */ - if (thd->slave_thread && rpl_filter->is_on()) - { - /* - The tables must be marked "updating" so that tables_ok() takes them into - account in tests. - */ - tables[0].updating= tables[1].updating= tables[2].updating= 1; - if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) - { - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); - DBUG_RETURN(FALSE); - } - } -#endif - - /* The lock api is depending on the thd->lex variable which needs to be re-initialized. */ @@ -3737,39 +5874,37 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, this value corresponds to the statement being executed. */ thd->lex->sql_command= backup.sql_command; - if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) - { // Should never happen - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user | + Table_tables_priv | maybe_columns_priv))) + { thd->lex->restore_backup_query_tables_list(&backup); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); - DBUG_RETURN(TRUE); /* purecov: deadcode */ + DBUG_RETURN(result != 1); } if (!revoke_grant) create_new_users= test_if_create_new_users(thd); - bool result= FALSE; mysql_rwlock_wrlock(&LOCK_grant); mysql_mutex_lock(&acl_cache->lock); MEM_ROOT *old_root= thd->mem_root; - thd->mem_root= &memex; + thd->mem_root= &grant_memroot; grant_version++; while ((tmp_Str = str_list++)) { int error; GRANT_TABLE *grant_table; - if (!(Str= get_current_user(thd, tmp_Str))) + if (!(Str= get_current_user(thd, tmp_Str, false))) { result= TRUE; continue; - } + } /* Create user if needed */ - error=replace_user_table(thd, tables[0].table, *Str, - 0, revoke_grant, create_new_users, - test(thd->variables.sql_mode & - MODE_NO_AUTO_CREATE_USER)); + error= copy_and_check_auth(Str, tmp_Str, thd) || + replace_user_table(thd, tables[USER_TABLE].table, *Str, + 0, revoke_grant, create_new_users, + MY_TEST(thd->variables.sql_mode & + MODE_NO_AUTO_CREATE_USER)); if (error) { result= TRUE; // Remember error @@ -3837,24 +5972,27 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, /* update table and columns */ - if (replace_table_table(thd, grant_table, tables[1].table, *Str, - db_name, table_name, + if (replace_table_table(thd, grant_table, tables[TABLES_PRIV_TABLE].table, + *Str, db_name, table_name, rights, column_priv, revoke_grant)) { /* Should only happen if table is crashed */ result= TRUE; /* purecov: deadcode */ } - else if (tables[2].table) + else if (tables[COLUMNS_PRIV_TABLE].table) { - if ((replace_column_table(grant_table, tables[2].table, *Str, - columns, - db_name, table_name, - rights, revoke_grant))) + if (replace_column_table(grant_table, tables[COLUMNS_PRIV_TABLE].table, + *Str, columns, db_name, table_name, rights, + revoke_grant)) { result= TRUE; } } + if (Str->is_role()) + propagate_role_grants(find_acl_role(Str->user.str), + PRIVS_TO_MERGE::TABLE_COLUMN, db_name, table_name); } + thd->mem_root= old_root; mysql_mutex_unlock(&acl_cache->lock); @@ -3868,12 +6006,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, if (!result) /* success */ my_ok(thd); - /* Tables are automatically closed */ thd->lex->restore_backup_query_tables_list(&backup); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -3899,21 +6032,16 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, { List_iterator <LEX_USER> str_list (user_list); LEX_USER *Str, *tmp_Str; - TABLE_LIST tables[2]; - bool create_new_users=0, result=0; + TABLE_LIST tables[TABLES_MAX]; + bool create_new_users= 0; + int result; char *db_name, *table_name; - bool save_binlog_row_based; DBUG_ENTER("mysql_routine_grant"); - if (!initialized) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), - "--skip-grant-tables"); - DBUG_RETURN(TRUE); - } if (rights & ~PROC_ACLS) { - my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE), + my_message(ER_ILLEGAL_GRANT_FOR_TABLE, + ER_THD(thd, ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0)); DBUG_RETURN(TRUE); } @@ -3924,89 +6052,45 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, DBUG_RETURN(TRUE); } - /* open the mysql.user and mysql.procs_priv tables */ - - tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("user"), "user", TL_WRITE); - tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("procs_priv"), "procs_priv", TL_WRITE); - tables[0].next_local= tables[0].next_global= tables+1; - - /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - -#ifdef HAVE_REPLICATION - /* - GRANT and REVOKE are applied the slave in/exclusion rules as they are - some kind of updates to the mysql.% tables. - */ - if (thd->slave_thread && rpl_filter->is_on()) - { - /* - The tables must be marked "updating" so that tables_ok() takes them into - account in tests. - */ - tables[0].updating= tables[1].updating= 1; - if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) - { - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); - DBUG_RETURN(FALSE); - } - } -#endif + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user | + Table_procs_priv))) + DBUG_RETURN(result != 1); - if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) - { // Should never happen - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); - DBUG_RETURN(TRUE); - } + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); if (!revoke_grant) create_new_users= test_if_create_new_users(thd); mysql_rwlock_wrlock(&LOCK_grant); mysql_mutex_lock(&acl_cache->lock); MEM_ROOT *old_root= thd->mem_root; - thd->mem_root= &memex; + thd->mem_root= &grant_memroot; DBUG_PRINT("info",("now time to iterate and add users")); while ((tmp_Str= str_list++)) { - int error; GRANT_NAME *grant_name; - if (!(Str= get_current_user(thd, tmp_Str))) + if (!(Str= get_current_user(thd, tmp_Str, false))) { result= TRUE; continue; - } + } /* Create user if needed */ - error=replace_user_table(thd, tables[0].table, *Str, - 0, revoke_grant, create_new_users, - test(thd->variables.sql_mode & - MODE_NO_AUTO_CREATE_USER)); - if (error) + if (copy_and_check_auth(Str, tmp_Str, thd) || + replace_user_table(thd, tables[USER_TABLE].table, *Str, + 0, revoke_grant, create_new_users, + MY_TEST(thd->variables.sql_mode & + MODE_NO_AUTO_CREATE_USER))) { - result= TRUE; // Remember error - continue; // Add next user + result= TRUE; + continue; } db_name= table_list->db; table_name= table_list->table_name; - grant_name= routine_hash_search(Str->host.str, NullS, db_name, Str->user.str, table_name, is_proc, 1); - if (!grant_name) + if (!grant_name || !grant_name->init_privs) { if (revoke_grant) { @@ -4027,13 +6111,18 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, } } - if (replace_routine_table(thd, grant_name, tables[1].table, *Str, - db_name, table_name, is_proc, rights, + if (no_such_table(tables + PROCS_PRIV_TABLE) || + replace_routine_table(thd, grant_name, tables[PROCS_PRIV_TABLE].table, + *Str, db_name, table_name, is_proc, rights, revoke_grant) != 0) { result= TRUE; continue; } + if (Str->is_role()) + propagate_role_grants(find_acl_role(Str->user.str), + is_proc ? PRIVS_TO_MERGE::PROC : PRIVS_TO_MERGE::FUNC, + db_name, table_name); } thd->mem_root= old_root; mysql_mutex_unlock(&acl_cache->lock); @@ -4045,15 +6134,334 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, } mysql_rwlock_unlock(&LOCK_grant); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); /* Tables are automatically closed */ DBUG_RETURN(result); } +/** + append a user or role name to a buffer that will be later used as an error message +*/ +static void append_user(THD *thd, String *str, + const LEX_STRING *u, const LEX_STRING *h) +{ + if (str->length()) + str->append(','); + append_query_string(system_charset_info, str, u->str, u->length, + thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES); + /* hostname part is not relevant for roles, it is always empty */ + if (u->length == 0 || h->length != 0) + { + str->append('@'); + append_query_string(system_charset_info, str, h->str, h->length, + thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES); + } +} + +static void append_user(THD *thd, String *str, LEX_USER *user) +{ + append_user(thd, str, & user->user, & user->host); +} + +/** + append a string to a buffer that will be later used as an error message + + @note + a string can be either CURRENT_USER or CURRENT_ROLE or NONE, it should be + neither quoted nor escaped. +*/ +static void append_str(String *str, const char *s, size_t l) +{ + if (str->length()) + str->append(','); + str->append(s, l); +} + +static int can_grant_role_callback(ACL_USER_BASE *grantee, + ACL_ROLE *role, void *data) +{ + ROLE_GRANT_PAIR *pair; + + if (role != (ACL_ROLE*)data) + return 0; // keep searching + + if (grantee->flags & IS_ROLE) + pair= find_role_grant_pair(&grantee->user, &empty_lex_str, &role->user); + else + { + ACL_USER *user= (ACL_USER *)grantee; + LEX_STRING host= { user->host.hostname, user->hostname_length }; + pair= find_role_grant_pair(&user->user, &host, &role->user); + } + if (!pair->with_admin) + return 0; // keep searching + + return -1; // abort the traversal +} + + +/* + One can only grant a role if SELECT * FROM I_S.APPLICABLE_ROLES shows this + role as grantable. + + What this really means - we need to traverse role graph for the current user + looking for our role being granted with the admin option. +*/ +static bool can_grant_role(THD *thd, ACL_ROLE *role) +{ + Security_context *sctx= thd->security_ctx; + + if (!sctx->user) // replication + return true; + + ACL_USER *grantee= find_user_exact(sctx->priv_host, sctx->priv_user); + if (!grantee) + return false; + + return traverse_role_graph_down(grantee, role, NULL, + can_grant_role_callback) == -1; +} + + +bool mysql_grant_role(THD *thd, List <LEX_USER> &list, bool revoke) +{ + DBUG_ENTER("mysql_grant_role"); + /* + The first entry in the list is the granted role. Need at least two + entries for the command to be valid + */ + DBUG_ASSERT(list.elements >= 2); + int result; + bool create_new_user, no_auto_create_user; + String wrong_users; + LEX_USER *user, *granted_role; + LEX_STRING rolename; + LEX_STRING username; + LEX_STRING hostname; + ACL_ROLE *role, *role_as_user; + + List_iterator <LEX_USER> user_list(list); + granted_role= user_list++; + if (!(granted_role= get_current_user(thd, granted_role))) + DBUG_RETURN(TRUE); + + DBUG_ASSERT(granted_role->is_role()); + rolename= granted_role->user; + + create_new_user= test_if_create_new_users(thd); + no_auto_create_user= MY_TEST(thd->variables.sql_mode & + MODE_NO_AUTO_CREATE_USER); + + TABLE_LIST tables[TABLES_MAX]; + if ((result= open_grant_tables(thd, tables, TL_WRITE, + Table_user | Table_roles_mapping))) + DBUG_RETURN(result != 1); + + mysql_rwlock_wrlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); + if (!(role= find_acl_role(rolename.str))) + { + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); + my_error(ER_INVALID_ROLE, MYF(0), rolename.str); + DBUG_RETURN(TRUE); + } + + if (!can_grant_role(thd, role)) + { + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); + my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), + thd->security_ctx->priv_user, thd->security_ctx->priv_host); + DBUG_RETURN(TRUE); + } + + while ((user= user_list++)) + { + role_as_user= NULL; + /* current_role is treated slightly different */ + if (user->user.str == current_role.str) + { + /* current_role is NONE */ + if (!thd->security_ctx->priv_role[0]) + { + my_error(ER_INVALID_ROLE, MYF(0), "NONE"); + append_str(&wrong_users, STRING_WITH_LEN("NONE")); + result= 1; + continue; + } + if (!(role_as_user= find_acl_role(thd->security_ctx->priv_role))) + { + LEX_STRING ls= { thd->security_ctx->priv_role, + strlen(thd->security_ctx->priv_role) }; + append_user(thd, &wrong_users, &ls, &empty_lex_str); + result= 1; + continue; + } + + /* can not grant current_role to current_role */ + if (granted_role->user.str == current_role.str) + { + append_user(thd, &wrong_users, &role_as_user->user, &empty_lex_str); + result= 1; + continue; + } + username.str= thd->security_ctx->priv_role; + username.length= strlen(username.str); + hostname= empty_lex_str; + } + else if (user->user.str == current_user.str) + { + username.str= thd->security_ctx->priv_user; + username.length= strlen(username.str); + hostname.str= thd->security_ctx->priv_host; + hostname.length= strlen(hostname.str); + } + else + { + username= user->user; + if (user->host.str) + hostname= user->host; + else + if ((role_as_user= find_acl_role(user->user.str))) + hostname= empty_lex_str; + else + { + if (is_invalid_role_name(username.str)) + { + append_user(thd, &wrong_users, &username, &empty_lex_str); + result= 1; + continue; + } + hostname= host_not_specified; + } + } + + ROLE_GRANT_PAIR *hash_entry= find_role_grant_pair(&username, &hostname, + &rolename); + ACL_USER_BASE *grantee= role_as_user; + + if (has_auth(user, thd->lex)) + DBUG_ASSERT(!grantee); + else if (!grantee) + grantee= find_user_exact(hostname.str, username.str); + + if (!grantee && !revoke) + { + LEX_USER user_combo = *user; + user_combo.host = hostname; + user_combo.user = username; + + if (copy_and_check_auth(&user_combo, &user_combo, thd) || + replace_user_table(thd, tables[USER_TABLE].table, user_combo, 0, + false, create_new_user, + no_auto_create_user)) + { + append_user(thd, &wrong_users, &username, &hostname); + result= 1; + continue; + } + grantee= find_user_exact(hostname.str, username.str); + + /* either replace_user_table failed, or we've added the user */ + DBUG_ASSERT(grantee); + } + + if (!grantee) + { + append_user(thd, &wrong_users, &username, &hostname); + result= 1; + continue; + } + + if (!revoke) + { + if (hash_entry) + { + // perhaps, updating an existing grant, adding WITH ADMIN OPTION + } + else + { + add_role_user_mapping(grantee, role); + + /* + Check if this grant would cause a cycle. It only needs to be run + if we're granting a role to a role + */ + if (role_as_user && + traverse_role_graph_down(role, 0, 0, 0) == ROLE_CYCLE_FOUND) + { + append_user(thd, &wrong_users, &username, &empty_lex_str); + result= 1; + undo_add_role_user_mapping(grantee, role); + continue; + } + } + } + else + { + /* grant was already removed or never existed */ + if (!hash_entry) + { + append_user(thd, &wrong_users, &username, &hostname); + result= 1; + continue; + } + if (thd->lex->with_admin_option) + { + // only revoking an admin option, not the complete grant + } + else + { + /* revoke a role grant */ + remove_role_user_mapping(grantee, role); + } + } + + /* write into the roles_mapping table */ + if (replace_roles_mapping_table(tables[ROLES_MAPPING_TABLE].table, + &username, &hostname, &rolename, + thd->lex->with_admin_option, + hash_entry, revoke)) + { + append_user(thd, &wrong_users, &username, &empty_lex_str); + result= 1; + if (!revoke) + { + /* need to remove the mapping added previously */ + undo_add_role_user_mapping(grantee, role); + } + else + { + /* need to restore the mapping deleted previously */ + add_role_user_mapping(grantee, role); + } + continue; + } + update_role_mapping(&username, &hostname, &rolename, + thd->lex->with_admin_option, hash_entry, revoke); + + /* + Only need to propagate grants when granting/revoking a role to/from + a role + */ + if (role_as_user && merge_one_role_privileges(role_as_user) == 0) + propagate_role_grants(role_as_user, PRIVS_TO_MERGE::ALL); + } + + mysql_mutex_unlock(&acl_cache->lock); + + if (result) + my_error(revoke ? ER_CANNOT_REVOKE_ROLE : ER_CANNOT_GRANT_ROLE, MYF(0), + rolename.str, wrong_users.c_ptr_safe()); + else + result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + + mysql_rwlock_unlock(&LOCK_grant); + + DBUG_RETURN(result); +} + bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, ulong rights, bool revoke_grant, bool is_proxy) @@ -4062,15 +6470,9 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, LEX_USER *Str, *tmp_Str, *proxied_user= NULL; char tmp_db[SAFE_NAME_LEN+1]; bool create_new_users=0; - TABLE_LIST tables[2]; - bool save_binlog_row_based; + int result; + TABLE_LIST tables[TABLES_MAX]; DBUG_ENTER("mysql_grant"); - if (!initialized) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), - "--skip-grant-tables"); /* purecov: tested */ - DBUG_RETURN(TRUE); /* purecov: tested */ - } if (lower_case_table_names && db) { @@ -4090,61 +6492,11 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, proxied_user= str_list++; } - /* open the mysql.user and mysql.db or mysql.proxies_priv tables */ - tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("user"), "user", TL_WRITE); - if (is_proxy) - - tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("proxies_priv"), - "proxies_priv", - TL_WRITE); - else - tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("db"), - "db", - TL_WRITE); - tables[0].next_local= tables[0].next_global= tables+1; - - /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - -#ifdef HAVE_REPLICATION - /* - GRANT and REVOKE are applied the slave in/exclusion rules as they are - some kind of updates to the mysql.% tables. - */ - if (thd->slave_thread && rpl_filter->is_on()) - { - /* - The tables must be marked "updating" so that tables_ok() takes them into - account in tests. - */ - tables[0].updating= tables[1].updating= 1; - if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) - { - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); - DBUG_RETURN(FALSE); - } - } -#endif + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user | + (is_proxy ? Table_proxies_priv : Table_db)))) + DBUG_RETURN(result != 1); - if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) - { // This should never happen - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); - DBUG_RETURN(TRUE); /* purecov: deadcode */ - } + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); if (!revoke_grant) create_new_users= test_if_create_new_users(thd); @@ -4154,48 +6506,55 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, mysql_mutex_lock(&acl_cache->lock); grant_version++; - int result=0; + if (proxied_user) + { + if (!(proxied_user= get_current_user(thd, proxied_user, false))) + DBUG_RETURN(TRUE); + DBUG_ASSERT(proxied_user->host.length); // not a Role + } + while ((tmp_Str = str_list++)) { - if (!(Str= get_current_user(thd, tmp_Str))) + if (!(Str= get_current_user(thd, tmp_Str, false))) { - result= TRUE; + result= true; continue; } - /* - No User, but a password? - They did GRANT ... TO CURRENT_USER() IDENTIFIED BY ... ! - Get the current user, and shallow-copy the new password to them! - */ - if (!tmp_Str->user.str && tmp_Str->password.str) - Str->password= tmp_Str->password; - if (replace_user_table(thd, tables[0].table, *Str, + + if (copy_and_check_auth(Str, tmp_Str, thd) || + replace_user_table(thd, tables[USER_TABLE].table, *Str, (!db ? rights : 0), revoke_grant, create_new_users, - test(thd->variables.sql_mode & - MODE_NO_AUTO_CREATE_USER))) - result= -1; + MY_TEST(thd->variables.sql_mode & + MODE_NO_AUTO_CREATE_USER))) + result= true; else if (db) { ulong db_rights= rights & DB_ACLS; if (db_rights == rights) { - if (replace_db_table(tables[1].table, db, *Str, db_rights, + if (replace_db_table(tables[DB_TABLE].table, db, *Str, db_rights, revoke_grant)) - result= -1; + result= true; } else { my_error(ER_WRONG_USAGE, MYF(0), "DB GRANT", "GLOBAL PRIVILEGES"); - result= -1; + result= true; } } else if (is_proxy) { - if (replace_proxies_priv_table (thd, tables[1].table, Str, proxied_user, - rights & GRANT_ACL ? TRUE : FALSE, - revoke_grant)) - result= -1; + if (no_such_table(tables + PROXIES_PRIV_TABLE) || + replace_proxies_priv_table (thd, tables[PROXIES_PRIV_TABLE].table, + Str, proxied_user, + rights & GRANT_ACL ? TRUE : FALSE, + revoke_grant)) + result= true; } + if (Str->is_role()) + propagate_role_grants(find_acl_role(Str->user.str), + db ? PRIVS_TO_MERGE::DB : PRIVS_TO_MERGE::GLOBAL, + db); } mysql_mutex_unlock(&acl_cache->lock); @@ -4208,10 +6567,6 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, if (!result) my_ok(thd); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -4225,7 +6580,7 @@ void grant_free(void) my_hash_free(&column_priv_hash); my_hash_free(&proc_priv_hash); my_hash_free(&func_priv_hash); - free_root(&memex,MYF(0)); + free_root(&grant_memroot,MYF(0)); DBUG_VOID_RETURN; } @@ -4239,10 +6594,10 @@ void grant_free(void) @retval 1 Could not initialize grant subsystem. */ -my_bool grant_init() +bool grant_init() { THD *thd; - my_bool return_val; + bool return_val; DBUG_ENTER("grant_init"); if (!(thd= new THD)) @@ -4251,107 +6606,6 @@ my_bool grant_init() thd->store_globals(); return_val= grant_reload(thd); delete thd; - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); - DBUG_RETURN(return_val); -} - - -/** - @brief Helper function to grant_reload_procs_priv - - Reads the procs_priv table into memory hash. - - @param table A pointer to the procs_priv table structure. - - @see grant_reload - @see grant_reload_procs_priv - - @return Error state - @retval TRUE An error occurred - @retval FALSE Success -*/ - -static my_bool grant_load_procs_priv(TABLE *p_table) -{ - MEM_ROOT *memex_ptr; - my_bool return_val= 1; - bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE; - MEM_ROOT **save_mem_root_ptr= my_pthread_getspecific_ptr(MEM_ROOT**, - THR_MALLOC); - DBUG_ENTER("grant_load_procs_priv"); - (void) my_hash_init(&proc_priv_hash, &my_charset_utf8_bin, - 0,0,0, (my_hash_get_key) get_grant_table, - 0,0); - (void) my_hash_init(&func_priv_hash, &my_charset_utf8_bin, - 0,0,0, (my_hash_get_key) get_grant_table, - 0,0); - - if (p_table->file->ha_index_init(0, 1)) - DBUG_RETURN(TRUE); - - p_table->use_all_columns(); - - if (!p_table->file->ha_index_first(p_table->record[0])) - { - memex_ptr= &memex; - my_pthread_setspecific_ptr(THR_MALLOC, &memex_ptr); - do - { - GRANT_NAME *mem_check; - HASH *hash; - if (!(mem_check=new (memex_ptr) GRANT_NAME(p_table, TRUE))) - { - /* This could only happen if we are out memory */ - goto end_unlock; - } - - if (check_no_resolve) - { - if (hostname_requires_resolving(mem_check->host.hostname)) - { - sql_print_warning("'procs_priv' entry '%s %s@%s' " - "ignored in --skip-name-resolve mode.", - mem_check->tname, mem_check->user, - mem_check->host.hostname ? - mem_check->host.hostname : ""); - continue; - } - } - if (p_table->field[4]->val_int() == TYPE_ENUM_PROCEDURE) - { - hash= &proc_priv_hash; - } - else - if (p_table->field[4]->val_int() == TYPE_ENUM_FUNCTION) - { - hash= &func_priv_hash; - } - else - { - sql_print_warning("'procs_priv' entry '%s' " - "ignored, bad routine type", - mem_check->tname); - continue; - } - - mem_check->privs= fix_rights_for_procedure(mem_check->privs); - if (! mem_check->ok()) - delete mem_check; - else if (my_hash_insert(hash, (uchar*) mem_check)) - { - delete mem_check; - goto end_unlock; - } - } - while (!p_table->file->ha_index_next(p_table->record[0])); - } - /* Return ok */ - return_val= 0; - -end_unlock: - p_table->file->ha_index_end(); - my_pthread_setspecific_ptr(THR_MALLOC, save_mem_root_ptr); DBUG_RETURN(return_val); } @@ -4371,11 +6625,11 @@ end_unlock: @retval TRUE Error */ -static my_bool grant_load(THD *thd, TABLE_LIST *tables) +static bool grant_load(THD *thd, TABLE_LIST *tables) { MEM_ROOT *memex_ptr; - my_bool return_val= 1; - TABLE *t_table= 0, *c_table= 0; + bool return_val= 1; + TABLE *t_table, *c_table, *p_table; bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE; MEM_ROOT **save_mem_root_ptr= my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC); @@ -4387,9 +6641,15 @@ static my_bool grant_load(THD *thd, TABLE_LIST *tables) (void) my_hash_init(&column_priv_hash, &my_charset_utf8_bin, 0,0,0, (my_hash_get_key) get_grant_table, (my_hash_free_key) free_grant_table,0); + (void) my_hash_init(&proc_priv_hash, &my_charset_utf8_bin, + 0,0,0, (my_hash_get_key) get_grant_table, 0,0); + (void) my_hash_init(&func_priv_hash, &my_charset_utf8_bin, + 0,0,0, (my_hash_get_key) get_grant_table, 0,0); + init_sql_alloc(&grant_memroot, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0)); - t_table = tables[0].table; - c_table = tables[1].table; + t_table= tables[TABLES_PRIV_TABLE].table; + c_table= tables[COLUMNS_PRIV_TABLE].table; + p_table= tables[PROCS_PRIV_TABLE].table; // this can be NULL if (t_table->file->ha_index_init(0, 1)) goto end_index_init; @@ -4397,10 +6657,11 @@ static my_bool grant_load(THD *thd, TABLE_LIST *tables) t_table->use_all_columns(); c_table->use_all_columns(); + memex_ptr= &grant_memroot; + my_pthread_setspecific_ptr(THR_MALLOC, &memex_ptr); + if (!t_table->file->ha_index_first(t_table->record[0])) { - memex_ptr= &memex; - my_pthread_setspecific_ptr(THR_MALLOC, &memex_ptr); do { GRANT_TABLE *mem_check; @@ -4417,9 +6678,8 @@ static my_bool grant_load(THD *thd, TABLE_LIST *tables) sql_print_warning("'tables_priv' entry '%s %s@%s' " "ignored in --skip-name-resolve mode.", mem_check->tname, - mem_check->user ? mem_check->user : "", - mem_check->host.hostname ? - mem_check->host.hostname : ""); + safe_str(mem_check->user), + safe_str(mem_check->host.hostname)); continue; } } @@ -4435,8 +6695,72 @@ static my_bool grant_load(THD *thd, TABLE_LIST *tables) while (!t_table->file->ha_index_next(t_table->record[0])); } - return_val=0; // Return ok + return_val= 0; + + if (p_table) + { + if (p_table->file->ha_index_init(0, 1)) + goto end_unlock; + + p_table->use_all_columns(); + + if (!p_table->file->ha_index_first(p_table->record[0])) + { + do + { + GRANT_NAME *mem_check; + HASH *hash; + if (!(mem_check=new (memex_ptr) GRANT_NAME(p_table, TRUE))) + { + /* This could only happen if we are out memory */ + goto end_unlock_p; + } + if (check_no_resolve) + { + if (hostname_requires_resolving(mem_check->host.hostname)) + { + sql_print_warning("'procs_priv' entry '%s %s@%s' " + "ignored in --skip-name-resolve mode.", + mem_check->tname, mem_check->user, + safe_str(mem_check->host.hostname)); + continue; + } + } + if (p_table->field[4]->val_int() == TYPE_ENUM_PROCEDURE) + { + hash= &proc_priv_hash; + } + else + if (p_table->field[4]->val_int() == TYPE_ENUM_FUNCTION) + { + hash= &func_priv_hash; + } + else + { + sql_print_warning("'procs_priv' entry '%s' " + "ignored, bad routine type", + mem_check->tname); + continue; + } + + mem_check->privs= fix_rights_for_procedure(mem_check->privs); + mem_check->init_privs= mem_check->privs; + if (! mem_check->ok()) + delete mem_check; + else if (my_hash_insert(hash, (uchar*) mem_check)) + { + delete mem_check; + goto end_unlock_p; + } + } + while (!p_table->file->ha_index_next(p_table->record[0])); + } + } + +end_unlock_p: + if (p_table) + p_table->file->ha_index_end(); end_unlock: t_table->file->ha_index_end(); my_pthread_setspecific_ptr(THR_MALLOC, save_mem_root_ptr); @@ -4445,47 +6769,17 @@ end_index_init: DBUG_RETURN(return_val); } - -/** - @brief Helper function to grant_reload. Reloads procs_priv table is it - exists. - - @param thd A pointer to the thread handler object. - @param table A pointer to the table list. - - @see grant_reload - - @return Error state - @retval FALSE Success - @retval TRUE An error has occurred. -*/ - -static my_bool grant_reload_procs_priv(THD *thd, TABLE_LIST *table) +static my_bool propagate_role_grants_action(void *role_ptr, + void *ptr __attribute__((unused))) { - HASH old_proc_priv_hash, old_func_priv_hash; - my_bool return_val= FALSE; - DBUG_ENTER("grant_reload_procs_priv"); - - /* Save a copy of the current hash if we need to undo the grant load */ - old_proc_priv_hash= proc_priv_hash; - old_func_priv_hash= func_priv_hash; - - if ((return_val= grant_load_procs_priv(table->table))) - { - /* Error; Reverting to old hash */ - DBUG_PRINT("error",("Reverting to old privileges")); - my_hash_free(&proc_priv_hash); - my_hash_free(&func_priv_hash); - proc_priv_hash= old_proc_priv_hash; - func_priv_hash= old_func_priv_hash; - } - else - { - my_hash_free(&old_proc_priv_hash); - my_hash_free(&old_func_priv_hash); - } + ACL_ROLE *role= static_cast<ACL_ROLE *>(role_ptr); + if (role->counter) + return 0; - DBUG_RETURN(return_val); + mysql_mutex_assert_owner(&acl_cache->lock); + PRIVS_TO_MERGE data= { PRIVS_TO_MERGE::ALL, 0, 0 }; + traverse_role_graph_up(role, &data, NULL, merge_role_privileges); + return 0; } @@ -4504,112 +6798,61 @@ static my_bool grant_reload_procs_priv(THD *thd, TABLE_LIST *table) @retval TRUE Error */ -my_bool grant_reload(THD *thd) +bool grant_reload(THD *thd) { - TABLE_LIST tables[3]; - HASH old_column_priv_hash; + TABLE_LIST tables[TABLES_MAX]; + HASH old_column_priv_hash, old_proc_priv_hash, old_func_priv_hash; MEM_ROOT old_mem; - my_bool return_val= 1; + int result; DBUG_ENTER("grant_reload"); - /* Don't do anything if running with --skip-grant-tables */ - if (!initialized) - DBUG_RETURN(0); - - tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("tables_priv"), - "tables_priv", TL_READ); - tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("columns_priv"), - "columns_priv", TL_READ); - tables[2].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("procs_priv"), - "procs_priv", TL_READ); - - tables[0].next_local= tables[0].next_global= tables+1; - tables[1].next_local= tables[1].next_global= tables+2; - tables[0].open_type= tables[1].open_type= tables[2].open_type= OT_BASE_ONLY; - - /* - Reload will work in the following manner:- - - proc_priv_hash structure - / \ - not initialized initialized - / \ | - mysql.procs_priv table Server Startup | - is missing \ | - | open_and_lock_tables() - Assume we are working on /success \failure - pre 4.1 system tables. Normal Scenario. An error is thrown. - A warning is printed Reload column privilege. Retain the old hash. - and continue with Reload function and - reloading the column procedure privileges, - privileges. if available. - */ - - if (!(my_hash_inited(&proc_priv_hash))) - tables[2].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - /* To avoid deadlocks we should obtain table locks before obtaining LOCK_grant rwlock. */ - if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) - { - if (thd->stmt_da->is_error()) - { - sql_print_error("Fatal error: Can't open and lock privilege tables: %s", - thd->stmt_da->message()); - } - goto end; - } - if (tables[2].table == NULL) - { - sql_print_warning("Table 'mysql.procs_priv' does not exist. " - "Please run mysql_upgrade."); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_NO_SUCH_TABLE, - ER(ER_NO_SUCH_TABLE), tables[2].db, - tables[2].table_name); - } + if ((result= open_grant_tables(thd, tables, TL_READ, Table_tables_priv | + Table_columns_priv | Table_procs_priv))) + DBUG_RETURN(result != 1); mysql_rwlock_wrlock(&LOCK_grant); + grant_version++; old_column_priv_hash= column_priv_hash; + old_proc_priv_hash= proc_priv_hash; + old_func_priv_hash= func_priv_hash; /* Create a new memory pool but save the current memory pool to make an undo opertion possible in case of failure. */ - old_mem= memex; - init_sql_alloc(&memex, ACL_ALLOC_BLOCK_SIZE, 0); + old_mem= grant_memroot; - /* - tables[2].table i.e. procs_priv can be null if we are working with - pre 4.1 privilage tables - */ - if ((return_val= (grant_load(thd, tables) || - (tables[2].table != NULL && - grant_reload_procs_priv(thd, &tables[2]))) - )) + if ((result= grant_load(thd, tables))) { // Error. Revert to old hash DBUG_PRINT("error",("Reverting to old privileges")); - my_hash_free(&column_priv_hash); - free_root(&memex,MYF(0)); + grant_free(); /* purecov: deadcode */ column_priv_hash= old_column_priv_hash; /* purecov: deadcode */ - memex= old_mem; /* purecov: deadcode */ + proc_priv_hash= old_proc_priv_hash; + func_priv_hash= old_func_priv_hash; + grant_memroot= old_mem; /* purecov: deadcode */ } else { my_hash_free(&old_column_priv_hash); + my_hash_free(&old_proc_priv_hash); + my_hash_free(&old_func_priv_hash); free_root(&old_mem,MYF(0)); - grant_version++; } + + mysql_mutex_lock(&acl_cache->lock); + my_hash_iterate(&acl_roles, propagate_role_grants_action, NULL); + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); -end: close_mysql_tables(thd); - DBUG_RETURN(return_val); + + DBUG_RETURN(result); } @@ -4638,12 +6881,16 @@ end: @see check_access @see check_table_access - @note This functions assumes that either number of tables to be inspected + @note + This functions assumes that either number of tables to be inspected by it is limited explicitly (i.e. is is not UINT_MAX) or table list used and thd->lex->query_tables_own_last value correspond to each other (the latter should be either 0 or point to next_global member of one of elements of this table list). + We delay locking of LOCK_grant until we really need it as we assume that + most privileges be resolved with user or db level accesses. + @return Access status @retval FALSE Access granted; But column privileges might need to be checked. @@ -4660,6 +6907,9 @@ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, Security_context *sctx= thd->security_ctx; uint i; ulong orig_want_access= want_access; + bool locked= 0; + GRANT_TABLE *grant_table; + GRANT_TABLE *grant_table_role= NULL; DBUG_ENTER("check_grant"); DBUG_ASSERT(number > 0); @@ -4683,16 +6933,13 @@ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, */ tl->grant.orig_want_privilege= (want_access & ~SHOW_VIEW_ACL); } + number= i; - mysql_rwlock_rdlock(&LOCK_grant); - for (tl= tables; - tl && number-- && tl != first_not_own_table; - tl= tl->next_global) + for (tl= tables; number-- ; tl= tl->next_global) { TABLE_LIST *const t_ref= tl->correspondent_table ? tl->correspondent_table : tl; - sctx = test(t_ref->security_ctx) ? t_ref->security_ctx : - thd->security_ctx; + sctx= t_ref->security_ctx ? t_ref->security_ctx : thd->security_ctx; const ACL_internal_table_access *access= get_cached_table_access(&t_ref->grant.m_internal, @@ -4743,15 +6990,40 @@ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, } continue; } - GRANT_TABLE *grant_table= table_hash_search(sctx->host, sctx->ip, - t_ref->get_db_name(), - sctx->priv_user, - t_ref->get_table_name(), - FALSE); - if (!grant_table) + if (is_temporary_table(t_ref)) + { + /* + If this table list element corresponds to a pre-opened temporary + table skip checking of all relevant table-level privileges for it. + Note that during creation of temporary table we still need to check + if user has CREATE_TMP_ACL. + */ + t_ref->grant.privilege|= TMP_TABLE_ACLS; + t_ref->grant.want_privilege= 0; + continue; + } + + if (!locked) + { + locked= 1; + mysql_rwlock_rdlock(&LOCK_grant); + } + + grant_table= table_hash_search(sctx->host, sctx->ip, + t_ref->get_db_name(), + sctx->priv_user, + t_ref->get_table_name(), + FALSE); + if (sctx->priv_role[0]) + grant_table_role= table_hash_search("", NULL, t_ref->get_db_name(), + sctx->priv_role, + t_ref->get_table_name(), + TRUE); + + if (!grant_table && !grant_table_role) { - want_access &= ~t_ref->grant.privilege; + want_access&= ~t_ref->grant.privilege; goto err; // No grants } @@ -4762,25 +7034,30 @@ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, if (any_combination_will_do) continue; - t_ref->grant.grant_table= grant_table; // Remember for column test + t_ref->grant.grant_table_user= grant_table; // Remember for column test + t_ref->grant.grant_table_role= grant_table_role; t_ref->grant.version= grant_version; - t_ref->grant.privilege|= grant_table->privs; + t_ref->grant.privilege|= grant_table ? grant_table->privs : 0; + t_ref->grant.privilege|= grant_table_role ? grant_table_role->privs : 0; t_ref->grant.want_privilege= ((want_access & COL_ACLS) & ~t_ref->grant.privilege); if (!(~t_ref->grant.privilege & want_access)) continue; - if (want_access & ~(grant_table->cols | t_ref->grant.privilege)) + if ((want_access&= ~((grant_table ? grant_table->cols : 0) | + (grant_table_role ? grant_table_role->cols : 0) | + t_ref->grant.privilege))) { - want_access &= ~(grant_table->cols | t_ref->grant.privilege); - goto err; // impossible + goto err; // impossible } } - mysql_rwlock_unlock(&LOCK_grant); + if (locked) + mysql_rwlock_unlock(&LOCK_grant); DBUG_RETURN(FALSE); err: - mysql_rwlock_unlock(&LOCK_grant); + if (locked) + mysql_rwlock_unlock(&LOCK_grant); if (!no_errors) // Not a silent skip of table { char command[128]; @@ -4820,6 +7097,7 @@ bool check_grant_column(THD *thd, GRANT_INFO *grant, const char *name, uint length, Security_context *sctx) { GRANT_TABLE *grant_table; + GRANT_TABLE *grant_table_role; GRANT_COLUMN *grant_column; ulong want_access= grant->want_privilege & ~grant->privilege; DBUG_ENTER("check_grant_column"); @@ -4834,17 +7112,40 @@ bool check_grant_column(THD *thd, GRANT_INFO *grant, if (grant->version != grant_version) { - grant->grant_table= + grant->grant_table_user= table_hash_search(sctx->host, sctx->ip, db_name, sctx->priv_user, table_name, 0); /* purecov: inspected */ + grant->grant_table_role= + sctx->priv_role[0] ? table_hash_search("", NULL, db_name, + sctx->priv_role, + table_name, TRUE) : NULL; grant->version= grant_version; /* purecov: inspected */ } - if (!(grant_table= grant->grant_table)) - goto err; /* purecov: deadcode */ - grant_column=column_hash_search(grant_table, name, length); - if (grant_column && !(~grant_column->rights & want_access)) + grant_table= grant->grant_table_user; + grant_table_role= grant->grant_table_role; + + if (!grant_table && !grant_table_role) + goto err; + + if (grant_table) + { + grant_column= column_hash_search(grant_table, name, length); + if (grant_column) + { + want_access&= ~grant_column->rights; + } + } + if (grant_table_role) + { + grant_column= column_hash_search(grant_table_role, name, length); + if (grant_column) + { + want_access&= ~grant_column->rights; + } + } + if (!want_access) { mysql_rwlock_unlock(&LOCK_grant); DBUG_RETURN(0); @@ -4854,6 +7155,7 @@ err: mysql_rwlock_unlock(&LOCK_grant); char command[128]; get_privilege_desc(command, sizeof(command), want_access); + /* TODO perhaps error should print current rolename aswell */ my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), command, sctx->priv_user, @@ -4892,7 +7194,7 @@ bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref, GRANT_INFO *grant; const char *db_name; const char *table_name; - Security_context *sctx= test(table_ref->security_ctx) ? + Security_context *sctx= MY_TEST(table_ref->security_ctx) ? table_ref->security_ctx : thd->security_ctx; if (table_ref->view || table_ref->field_translation) @@ -4902,7 +7204,7 @@ bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref, grant= &(table_ref->grant); db_name= table_ref->view_db.str; table_name= table_ref->view_name.str; - if (table_ref->belong_to_view && + if (table_ref->belong_to_view && thd->lex->sql_command == SQLCOM_SHOW_FIELDS) { view_privs= get_column_grant(thd, grant, db_name, table_name, name); @@ -4912,7 +7214,7 @@ bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref, return FALSE; } table_ref->belong_to_view->allowed_show= FALSE; - my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0)); + my_message(ER_VIEW_NO_EXPLAIN, ER_THD(thd, ER_VIEW_NO_EXPLAIN), MYF(0)); return TRUE; } } @@ -4934,7 +7236,7 @@ bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref, } -/** +/** @brief check if a query can access a set of columns @param thd the current thread @@ -4943,24 +7245,23 @@ bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref, @return Operation status @retval 0 Success @retval 1 Falure - @details This function walks over the columns of a table reference + @details This function walks over the columns of a table reference The columns may originate from different tables, depending on the kind of table reference, e.g. join, view. For each table it will retrieve the grant information and will use it to check the required access privileges for the fields requested from it. -*/ -bool check_grant_all_columns(THD *thd, ulong want_access_arg, +*/ +bool check_grant_all_columns(THD *thd, ulong want_access_arg, Field_iterator_table_ref *fields) { Security_context *sctx= thd->security_ctx; - ulong want_access= want_access_arg; + ulong UNINIT_VAR(want_access); const char *table_name= NULL; - - const char* db_name; + const char* db_name; GRANT_INFO *grant; - /* Initialized only to make gcc happy */ - GRANT_TABLE *grant_table= NULL; - /* + GRANT_TABLE *UNINIT_VAR(grant_table); + GRANT_TABLE *UNINIT_VAR(grant_table_role); + /* Flag that gets set if privilege checking has to be performed on column level. */ @@ -4984,26 +7285,46 @@ bool check_grant_all_columns(THD *thd, ulong want_access_arg, /* reload table if someone has modified any grants */ if (grant->version != grant_version) { - grant->grant_table= + grant->grant_table_user= table_hash_search(sctx->host, sctx->ip, db_name, sctx->priv_user, table_name, 0); /* purecov: inspected */ + grant->grant_table_role= + sctx->priv_role[0] ? table_hash_search("", NULL, db_name, + sctx->priv_role, + table_name, TRUE) : NULL; grant->version= grant_version; /* purecov: inspected */ } - grant_table= grant->grant_table; - DBUG_ASSERT (grant_table); + grant_table= grant->grant_table_user; + grant_table_role= grant->grant_table_role; + DBUG_ASSERT (grant_table || grant_table_role); } } if (want_access) { - GRANT_COLUMN *grant_column= - column_hash_search(grant_table, field_name, - (uint) strlen(field_name)); - if (grant_column) + ulong have_access= 0; + if (grant_table) + { + GRANT_COLUMN *grant_column= + column_hash_search(grant_table, field_name, + (uint) strlen(field_name)); + if (grant_column) + have_access= grant_column->rights; + } + if (grant_table_role) + { + GRANT_COLUMN *grant_column= + column_hash_search(grant_table_role, field_name, + (uint) strlen(field_name)); + if (grant_column) + have_access|= grant_column->rights; + } + + if (have_access) using_column_privileges= TRUE; - if (!grant_column || (~grant_column->rights & want_access)) + if (want_access & ~have_access) goto err; } } @@ -5022,7 +7343,7 @@ err: if (using_column_privileges) my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), command, sctx->priv_user, - sctx->host_or_ip, table_name); + sctx->host_or_ip, table_name); else my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), command, @@ -5048,6 +7369,12 @@ static bool check_grant_db_routine(THD *thd, const char *db, HASH *hash) { return FALSE; } + if (sctx->priv_role[0] && strcmp(item->user, sctx->priv_role) == 0 && + strcmp(item->db, db) == 0 && + (!item->host.hostname || !item->host.hostname[0])) + { + return FALSE; /* Found current role match */ + } } return TRUE; @@ -5060,35 +7387,60 @@ static bool check_grant_db_routine(THD *thd, const char *db, HASH *hash) Return 1 if access is denied */ -bool check_grant_db(THD *thd,const char *db) +bool check_grant_db(THD *thd, const char *db) { Security_context *sctx= thd->security_ctx; char helping [SAFE_NAME_LEN + USERNAME_LENGTH+2], *end; - uint len; + char helping2 [SAFE_NAME_LEN + USERNAME_LENGTH+2], *tmp_db; + uint len, UNINIT_VAR(len2); bool error= TRUE; - end= strmov(helping, sctx->priv_user) + 1; - end= strnmov(end, db, helping + sizeof(helping) - end); + tmp_db= strmov(helping, sctx->priv_user) + 1; + end= strnmov(tmp_db, db, helping + sizeof(helping) - tmp_db); if (end >= helping + sizeof(helping)) // db name was truncated return 1; // no privileges for an invalid db name + if (lower_case_table_names) + { + end = tmp_db + my_casedn_str(files_charset_info, tmp_db); + db=tmp_db; + } + len= (uint) (end - helping) + 1; + /* + If a role is set, we need to check for privileges here as well. + */ + if (sctx->priv_role[0]) + { + end= strmov(helping2, sctx->priv_role) + 1; + end= strnmov(end, db, helping2 + sizeof(helping2) - end); + len2= (uint) (end - helping2) + 1; + } + + mysql_rwlock_rdlock(&LOCK_grant); for (uint idx=0 ; idx < column_priv_hash.records ; idx++) { - GRANT_TABLE *grant_table= (GRANT_TABLE*) - my_hash_element(&column_priv_hash, - idx); + GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, + idx); if (len < grant_table->key_length && - !memcmp(grant_table->hash_key,helping,len) && + !memcmp(grant_table->hash_key, helping, len) && compare_hostname(&grant_table->host, sctx->host, sctx->ip)) { error= FALSE; /* Found match. */ break; } + if (sctx->priv_role[0] && + len2 < grant_table->key_length && + !memcmp(grant_table->hash_key, helping2, len2) && + (!grant_table->host.hostname || !grant_table->host.hostname[0])) + { + error= FALSE; /* Found role match */ + break; + } } if (error) @@ -5125,6 +7477,7 @@ bool check_grant_routine(THD *thd, ulong want_access, Security_context *sctx= thd->security_ctx; char *user= sctx->priv_user; char *host= sctx->priv_host; + char *role= sctx->priv_role; DBUG_ENTER("check_grant_routine"); want_access&= ~sctx->master_access; @@ -5138,6 +7491,12 @@ bool check_grant_routine(THD *thd, ulong want_access, if ((grant_proc= routine_hash_search(host, sctx->ip, table->db, user, table->table_name, is_proc, 0))) table->grant.privilege|= grant_proc->privs; + if (role[0]) /* current role set check */ + { + if ((grant_proc= routine_hash_search("", NULL, table->db, role, + table->table_name, is_proc, 0))) + table->grant.privilege|= grant_proc->privs; + } if (want_access & ~table->grant.privilege) { @@ -5169,9 +7528,9 @@ err: /* - Check if routine has any of the + Check if routine has any of the routine level grants - + SYNPOSIS bool check_routine_level_acl() thd Thread handler @@ -5179,11 +7538,11 @@ err: name Routine name RETURN - 0 Ok + 0 Ok 1 error */ -bool check_routine_level_acl(THD *thd, const char *db, const char *name, +bool check_routine_level_acl(THD *thd, const char *db, const char *name, bool is_proc) { bool no_routine_acl= 1; @@ -5195,6 +7554,15 @@ bool check_routine_level_acl(THD *thd, const char *db, const char *name, sctx->priv_user, name, is_proc, 0))) no_routine_acl= !(grant_proc->privs & SHOW_PROC_ACLS); + + if (no_routine_acl && sctx->priv_role[0]) /* current set role check */ + { + if ((grant_proc= routine_hash_search("", + NULL, db, + sctx->priv_role, + name, is_proc, 0))) + no_routine_acl= !(grant_proc->privs & SHOW_PROC_ACLS); + } mysql_rwlock_unlock(&LOCK_grant); return no_routine_acl; } @@ -5210,18 +7578,26 @@ ulong get_table_grant(THD *thd, TABLE_LIST *table) Security_context *sctx= thd->security_ctx; const char *db = table->db ? table->db : thd->db; GRANT_TABLE *grant_table; + GRANT_TABLE *grant_table_role= NULL; mysql_rwlock_rdlock(&LOCK_grant); #ifdef EMBEDDED_LIBRARY grant_table= NULL; + grant_table_role= NULL; #else grant_table= table_hash_search(sctx->host, sctx->ip, db, sctx->priv_user, table->table_name, 0); + if (sctx->priv_role[0]) + grant_table_role= table_hash_search("", "", db, sctx->priv_role, + table->table_name, 0); #endif - table->grant.grant_table=grant_table; // Remember for column test + table->grant.grant_table_user= grant_table; // Remember for column test + table->grant.grant_table_role= grant_table_role; table->grant.version=grant_version; if (grant_table) table->grant.privilege|= grant_table->privs; + if (grant_table_role) + table->grant.privilege|= grant_table_role->privs; privilege= table->grant.privilege; mysql_rwlock_unlock(&LOCK_grant); return privilege; @@ -5251,31 +7627,53 @@ ulong get_column_grant(THD *thd, GRANT_INFO *grant, const char *field_name) { GRANT_TABLE *grant_table; + GRANT_TABLE *grant_table_role; GRANT_COLUMN *grant_column; - ulong priv; + ulong priv= 0; mysql_rwlock_rdlock(&LOCK_grant); /* reload table if someone has modified any grants */ if (grant->version != grant_version) { Security_context *sctx= thd->security_ctx; - grant->grant_table= + grant->grant_table_user= table_hash_search(sctx->host, sctx->ip, db_name, sctx->priv_user, - table_name, 0); /* purecov: inspected */ + table_name, 0); /* purecov: inspected */ + grant->grant_table_role= + sctx->priv_role[0] ? table_hash_search("", "", db_name, + sctx->priv_role, + table_name, TRUE) : NULL; grant->version= grant_version; /* purecov: inspected */ } - if (!(grant_table= grant->grant_table)) + grant_table= grant->grant_table_user; + grant_table_role= grant->grant_table_role; + + if (!grant_table && !grant_table_role) priv= grant->privilege; else { - grant_column= column_hash_search(grant_table, field_name, - (uint) strlen(field_name)); - if (!grant_column) - priv= (grant->privilege | grant_table->privs); - else - priv= (grant->privilege | grant_table->privs | grant_column->rights); + if (grant_table) + { + grant_column= column_hash_search(grant_table, field_name, + (uint) strlen(field_name)); + if (!grant_column) + priv= (grant->privilege | grant_table->privs); + else + priv= (grant->privilege | grant_table->privs | grant_column->rights); + } + + if (grant_table_role) + { + grant_column= column_hash_search(grant_table_role, field_name, + (uint) strlen(field_name)); + if (!grant_column) + priv|= (grant->privilege | grant_table_role->privs); + else + priv|= (grant->privilege | grant_table_role->privs | + grant_column->rights); + } } mysql_rwlock_unlock(&LOCK_grant); return priv; @@ -5285,7 +7683,7 @@ ulong get_column_grant(THD *thd, GRANT_INFO *grant, /* Help function for mysql_show_grants */ static void add_user_option(String *grant, long value, const char *name, - my_bool is_signed) + bool is_signed) { if (value) { @@ -5298,6 +7696,21 @@ static void add_user_option(String *grant, long value, const char *name, } } + +static void add_user_option(String *grant, double value, const char *name) +{ + if (value != 0.0 ) + { + char buff[FLOATING_POINT_BUFFER]; + size_t len; + grant->append(' '); + grant->append(name, strlen(name)); + grant->append(' '); + len= my_fcvt(value, 6, buff, NULL); + grant->append(buff, len); + } +} + static const char *command_array[]= { "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "RELOAD", @@ -5315,10 +7728,99 @@ static uint command_lengths[]= }; -static int show_routine_grants(THD *thd, LEX_USER *lex_user, HASH *hash, - const char *type, int typelen, - char *buff, int buffsize); +static bool print_grants_for_role(THD *thd, ACL_ROLE * role) +{ + char buff[1024]; + + if (show_role_grants(thd, role->user.str, "", role, buff, sizeof(buff))) + return TRUE; + + if (show_global_privileges(thd, role, TRUE, buff, sizeof(buff))) + return TRUE; + + if (show_database_privileges(thd, role->user.str, "", buff, sizeof(buff))) + return TRUE; + + if (show_table_and_column_privileges(thd, role->user.str, "", buff, sizeof(buff))) + return TRUE; + + if (show_routine_grants(thd, role->user.str, "", &proc_priv_hash, + STRING_WITH_LEN("PROCEDURE"), buff, sizeof(buff))) + return TRUE; + + if (show_routine_grants(thd, role->user.str, "", &func_priv_hash, + STRING_WITH_LEN("FUNCTION"), buff, sizeof(buff))) + return TRUE; + + return FALSE; + +} + + +static int show_grants_callback(ACL_USER_BASE *role, void *data) +{ + THD *thd= (THD *)data; + DBUG_ASSERT(role->flags & IS_ROLE); + if (print_grants_for_role(thd, (ACL_ROLE *)role)) + return -1; + return 0; +} + +void mysql_show_grants_get_fields(THD *thd, List<Item> *fields, + const char *name) +{ + Item_string *field=new (thd->mem_root) Item_string_ascii(thd, "", 0); + field->name= (char *) name; + field->max_length=1024; + fields->push_back(field, thd->mem_root); +} + +bool get_show_user(THD *thd, LEX_USER *lex_user, const char **username, + const char **hostname, const char **rolename) +{ + if (lex_user->user.str == current_user.str) + { + *username= thd->security_ctx->priv_user; + *hostname= thd->security_ctx->priv_host; + return 0; + } + if (lex_user->user.str == current_role.str) + { + *rolename= thd->security_ctx->priv_role; + return 0; + } + if (lex_user->user.str == current_user_and_current_role.str) + { + *username= thd->security_ctx->priv_user; + *hostname= thd->security_ctx->priv_host; + *rolename= thd->security_ctx->priv_role; + return 0; + } + + Security_context *sctx= thd->security_ctx; + bool do_check_access; + + if (!(lex_user= get_current_user(thd, lex_user))) + return 1; + + if (lex_user->is_role()) + { + *rolename= lex_user->user.str; + do_check_access= strcmp(*rolename, sctx->priv_role); + } + else + { + *username= lex_user->user.str; + *hostname= lex_user->host.str; + do_check_access= strcmp(*username, sctx->priv_user) || + strcmp(*hostname, sctx->priv_host); + } + + if (do_check_access && check_access(thd, SELECT_ACL, "mysql", 0, 0, 1, 0)) + return 1; + return 0; +} /* SHOW GRANTS; Send grants for a user to the client @@ -5327,87 +7829,226 @@ static int show_routine_grants(THD *thd, LEX_USER *lex_user, HASH *hash, Send to client grant-like strings depicting user@host privileges */ -bool mysql_show_grants(THD *thd,LEX_USER *lex_user) +bool mysql_show_grants(THD *thd, LEX_USER *lex_user) { - ulong want_access; - uint counter,index; - int error = 0; - ACL_USER *acl_user; - ACL_DB *acl_db; + int error = -1; + ACL_USER *UNINIT_VAR(acl_user); + ACL_ROLE *acl_role= NULL; char buff[1024]; Protocol *protocol= thd->protocol; + const char *username= NULL; + const char *hostname= NULL; + const char *rolename= NULL; DBUG_ENTER("mysql_show_grants"); - LINT_INIT(acl_user); if (!initialized) { my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); DBUG_RETURN(TRUE); } + if (get_show_user(thd, lex_user, &username, &hostname, &rolename)) + DBUG_RETURN(TRUE); + + DBUG_ASSERT(rolename || username); + + List<Item> field_list; + if (!username) + strxmov(buff,"Grants for ",rolename, NullS); + else + strxmov(buff,"Grants for ",username,"@",hostname, NullS); + + mysql_show_grants_get_fields(thd, &field_list, buff); + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + mysql_rwlock_rdlock(&LOCK_grant); mysql_mutex_lock(&acl_cache->lock); - acl_user= find_acl_user(lex_user->host.str, lex_user->user.str, TRUE); - if (!acl_user) + if (username) { - mysql_mutex_unlock(&acl_cache->lock); - mysql_rwlock_unlock(&LOCK_grant); + acl_user= find_user_exact(hostname, username); + if (!acl_user) + { + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); - my_error(ER_NONEXISTING_GRANT, MYF(0), - lex_user->user.str, lex_user->host.str); - DBUG_RETURN(TRUE); + my_error(ER_NONEXISTING_GRANT, MYF(0), + username, hostname); + DBUG_RETURN(TRUE); + } + + /* Show granted roles to acl_user */ + if (show_role_grants(thd, username, hostname, acl_user, buff, sizeof(buff))) + goto end; + + /* Add first global access grants */ + if (show_global_privileges(thd, acl_user, FALSE, buff, sizeof(buff))) + goto end; + + /* Add database access */ + if (show_database_privileges(thd, username, hostname, buff, sizeof(buff))) + goto end; + + /* Add table & column access */ + if (show_table_and_column_privileges(thd, username, hostname, buff, sizeof(buff))) + goto end; + + if (show_routine_grants(thd, username, hostname, &proc_priv_hash, + STRING_WITH_LEN("PROCEDURE"), buff, sizeof(buff))) + goto end; + + if (show_routine_grants(thd, username, hostname, &func_priv_hash, + STRING_WITH_LEN("FUNCTION"), buff, sizeof(buff))) + goto end; + + if (show_proxy_grants(thd, username, hostname, buff, sizeof(buff))) + goto end; } - Item_string *field=new Item_string("",0,&my_charset_latin1); - List<Item> field_list; - field->name=buff; - field->max_length=1024; - strxmov(buff,"Grants for ",lex_user->user.str,"@", - lex_user->host.str,NullS); - field_list.push_back(field); - if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + if (rolename) { - mysql_mutex_unlock(&acl_cache->lock); - mysql_rwlock_unlock(&LOCK_grant); - - DBUG_RETURN(TRUE); + acl_role= find_acl_role(rolename); + if (acl_role) + { + /* get a list of all inherited roles */ + traverse_role_graph_down(acl_role, thd, show_grants_callback, NULL); + } + else + { + if (lex_user->user.str == current_role.str) + { + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); + my_error(ER_NONEXISTING_GRANT, MYF(0), + thd->security_ctx->priv_user, + thd->security_ctx->priv_host); + DBUG_RETURN(TRUE); + } + } } - /* Add first global access grants */ + error= 0; +end: + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); + + my_eof(thd); + DBUG_RETURN(error); +} + +static ROLE_GRANT_PAIR *find_role_grant_pair(const LEX_STRING *u, + const LEX_STRING *h, + const LEX_STRING *r) +{ + char buf[1024]; + String pair_key(buf, sizeof(buf), &my_charset_bin); + + size_t key_length= u->length + h->length + r->length + 3; + pair_key.alloc(key_length); + + strmov(strmov(strmov(const_cast<char*>(pair_key.ptr()), + safe_str(u->str)) + 1, h->str) + 1, r->str); + + return (ROLE_GRANT_PAIR *) + my_hash_search(&acl_roles_mappings, (uchar*)pair_key.ptr(), key_length); +} + +static bool show_role_grants(THD *thd, const char *username, + const char *hostname, ACL_USER_BASE *acl_entry, + char *buff, size_t buffsize) +{ + uint counter; + Protocol *protocol= thd->protocol; + LEX_STRING host= {const_cast<char*>(hostname), strlen(hostname)}; + + String grant(buff,sizeof(buff),system_charset_info); + for (counter= 0; counter < acl_entry->role_grants.elements; counter++) { - String global(buff,sizeof(buff),system_charset_info); - global.length(0); - global.append(STRING_WITH_LEN("GRANT ")); + grant.length(0); + grant.append(STRING_WITH_LEN("GRANT ")); + ACL_ROLE *acl_role= *(dynamic_element(&acl_entry->role_grants, counter, + ACL_ROLE**)); + grant.append(acl_role->user.str, acl_role->user.length, + system_charset_info); + grant.append(STRING_WITH_LEN(" TO '")); + grant.append(acl_entry->user.str, acl_entry->user.length, + system_charset_info); + if (!(acl_entry->flags & IS_ROLE)) + { + grant.append(STRING_WITH_LEN("'@'")); + grant.append(&host); + } + grant.append('\''); - want_access= acl_user->access; - if (test_all_bits(want_access, (GLOBAL_ACLS & ~ GRANT_ACL))) - global.append(STRING_WITH_LEN("ALL PRIVILEGES")); - else if (!(want_access & ~GRANT_ACL)) - global.append(STRING_WITH_LEN("USAGE")); - else + ROLE_GRANT_PAIR *pair= + find_role_grant_pair(&acl_entry->user, &host, &acl_role->user); + DBUG_ASSERT(pair); + + if (pair->with_admin) + grant.append(STRING_WITH_LEN(" WITH ADMIN OPTION")); + + protocol->prepare_for_resend(); + protocol->store(grant.ptr(),grant.length(),grant.charset()); + if (protocol->write()) { - bool found=0; - ulong j,test_access= want_access & ~GRANT_ACL; - for (counter=0, j = SELECT_ACL;j <= GLOBAL_ACLS;counter++,j <<= 1) + return TRUE; + } + } + return FALSE; +} + +static bool show_global_privileges(THD *thd, ACL_USER_BASE *acl_entry, + bool handle_as_role, + char *buff, size_t buffsize) +{ + uint counter; + ulong want_access; + Protocol *protocol= thd->protocol; + + String global(buff,sizeof(buff),system_charset_info); + global.length(0); + global.append(STRING_WITH_LEN("GRANT ")); + + if (handle_as_role) + want_access= ((ACL_ROLE *)acl_entry)->initial_role_access; + else + want_access= acl_entry->access; + if (test_all_bits(want_access, (GLOBAL_ACLS & ~ GRANT_ACL))) + global.append(STRING_WITH_LEN("ALL PRIVILEGES")); + else if (!(want_access & ~GRANT_ACL)) + global.append(STRING_WITH_LEN("USAGE")); + else + { + bool found=0; + ulong j,test_access= want_access & ~GRANT_ACL; + for (counter=0, j = SELECT_ACL;j <= GLOBAL_ACLS;counter++,j <<= 1) + { + if (test_access & j) { - if (test_access & j) - { - if (found) - global.append(STRING_WITH_LEN(", ")); - found=1; - global.append(command_array[counter],command_lengths[counter]); - } + if (found) + global.append(STRING_WITH_LEN(", ")); + found=1; + global.append(command_array[counter],command_lengths[counter]); } } - global.append (STRING_WITH_LEN(" ON *.* TO '")); - global.append(lex_user->user.str, lex_user->user.length, - system_charset_info); - global.append (STRING_WITH_LEN("'@'")); - global.append(lex_user->host.str,lex_user->host.length, - system_charset_info); + } + global.append (STRING_WITH_LEN(" ON *.* TO '")); + global.append(acl_entry->user.str, acl_entry->user.length, + system_charset_info); + global.append('\''); + + if (!handle_as_role) + { + ACL_USER *acl_user= (ACL_USER *)acl_entry; + + global.append (STRING_WITH_LEN("@'")); + global.append(acl_user->host.hostname, acl_user->hostname_length, + system_charset_info); global.append ('\''); + if (acl_user->plugin.str == native_password_plugin_name.str || acl_user->plugin.str == old_password_plugin_name.str) { @@ -5421,14 +8062,14 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) } else { - global.append(STRING_WITH_LEN(" IDENTIFIED VIA ")); - global.append(acl_user->plugin.str, acl_user->plugin.length); - if (acl_user->auth_string.length) - { - global.append(STRING_WITH_LEN(" USING '")); - global.append(acl_user->auth_string.str, acl_user->auth_string.length); - global.append('\''); - } + global.append(STRING_WITH_LEN(" IDENTIFIED VIA ")); + global.append(acl_user->plugin.str, acl_user->plugin.length); + if (acl_user->auth_string.length) + { + global.append(STRING_WITH_LEN(" USING '")); + global.append(acl_user->auth_string.str, acl_user->auth_string.length); + global.append('\''); + } } /* "show grants" SSL related stuff */ if (acl_user->ssl_type == SSL_TYPE_ANY) @@ -5441,67 +8082,76 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) global.append(STRING_WITH_LEN(" REQUIRE ")); if (acl_user->x509_issuer) { - ssl_options++; - global.append(STRING_WITH_LEN("ISSUER \'")); - global.append(acl_user->x509_issuer,strlen(acl_user->x509_issuer)); - global.append('\''); + ssl_options++; + global.append(STRING_WITH_LEN("ISSUER \'")); + global.append(acl_user->x509_issuer,strlen(acl_user->x509_issuer)); + global.append('\''); } if (acl_user->x509_subject) { - if (ssl_options++) - global.append(' '); - global.append(STRING_WITH_LEN("SUBJECT \'")); - global.append(acl_user->x509_subject,strlen(acl_user->x509_subject), + if (ssl_options++) + global.append(' '); + global.append(STRING_WITH_LEN("SUBJECT \'")); + global.append(acl_user->x509_subject,strlen(acl_user->x509_subject), system_charset_info); - global.append('\''); + global.append('\''); } if (acl_user->ssl_cipher) { - if (ssl_options++) - global.append(' '); - global.append(STRING_WITH_LEN("CIPHER '")); - global.append(acl_user->ssl_cipher,strlen(acl_user->ssl_cipher), + if (ssl_options++) + global.append(' '); + global.append(STRING_WITH_LEN("CIPHER '")); + global.append(acl_user->ssl_cipher,strlen(acl_user->ssl_cipher), system_charset_info); - global.append('\''); + global.append('\''); } } if ((want_access & GRANT_ACL) || - (acl_user->user_resource.questions || + (acl_user->user_resource.questions || acl_user->user_resource.updates || acl_user->user_resource.conn_per_hour || - acl_user->user_resource.user_conn)) + acl_user->user_resource.user_conn || + acl_user->user_resource.max_statement_time != 0.0)) { global.append(STRING_WITH_LEN(" WITH")); if (want_access & GRANT_ACL) - global.append(STRING_WITH_LEN(" GRANT OPTION")); + global.append(STRING_WITH_LEN(" GRANT OPTION")); add_user_option(&global, acl_user->user_resource.questions, - "MAX_QUERIES_PER_HOUR", 0); + "MAX_QUERIES_PER_HOUR", false); add_user_option(&global, acl_user->user_resource.updates, - "MAX_UPDATES_PER_HOUR", 0); + "MAX_UPDATES_PER_HOUR", false); add_user_option(&global, acl_user->user_resource.conn_per_hour, - "MAX_CONNECTIONS_PER_HOUR", 0); + "MAX_CONNECTIONS_PER_HOUR", false); add_user_option(&global, acl_user->user_resource.user_conn, - "MAX_USER_CONNECTIONS", 1); - } - protocol->prepare_for_resend(); - protocol->store(global.ptr(),global.length(),global.charset()); - if (protocol->write()) - { - error= -1; - goto end; + "MAX_USER_CONNECTIONS", true); + add_user_option(&global, acl_user->user_resource.max_statement_time, + "MAX_STATEMENT_TIME"); } } - /* Add database access */ - for (counter=0 ; counter < acl_dbs.elements ; counter++) + protocol->prepare_for_resend(); + protocol->store(global.ptr(),global.length(),global.charset()); + if (protocol->write()) + return TRUE; + + return FALSE; + +} + +static bool show_database_privileges(THD *thd, const char *username, + const char *hostname, + char *buff, size_t buffsize) +{ + ulong want_access; + Protocol *protocol= thd->protocol; + + for (uint i=0 ; i < acl_dbs.elements() ; i++) { const char *user, *host; - acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); - if (!(user=acl_db->user)) - user= ""; - if (!(host=acl_db->host.hostname)) - host= ""; + ACL_DB *acl_db= &acl_dbs.at(i); + user= safe_str(acl_db->user); + host=acl_db->host.hostname; /* We do not make SHOW GRANTS case-sensitive here (like REVOKE), @@ -5510,68 +8160,84 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) would be wrong from a security point of view. */ - if (!strcmp(lex_user->user.str,user) && - !my_strcasecmp(system_charset_info, lex_user->host.str, host)) + if (!strcmp(username, user) && + !my_strcasecmp(system_charset_info, hostname, host)) { - want_access=acl_db->access; + /* + do not print inherited access bits for roles, + the role bits present in the table are what matters + */ + if (*hostname) // User + want_access=acl_db->access; + else // Role + want_access=acl_db->initial_access; if (want_access) { - String db(buff,sizeof(buff),system_charset_info); - db.length(0); - db.append(STRING_WITH_LEN("GRANT ")); - - if (test_all_bits(want_access,(DB_ACLS & ~GRANT_ACL))) - db.append(STRING_WITH_LEN("ALL PRIVILEGES")); - else if (!(want_access & ~GRANT_ACL)) - db.append(STRING_WITH_LEN("USAGE")); - else - { - int found=0, cnt; - ulong j,test_access= want_access & ~GRANT_ACL; - for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1) - { - if (test_access & j) - { - if (found) - db.append(STRING_WITH_LEN(", ")); - found = 1; - db.append(command_array[cnt],command_lengths[cnt]); - } - } - } - db.append (STRING_WITH_LEN(" ON ")); - append_identifier(thd, &db, acl_db->db, strlen(acl_db->db)); - db.append (STRING_WITH_LEN(".* TO '")); - db.append(lex_user->user.str, lex_user->user.length, - system_charset_info); - db.append (STRING_WITH_LEN("'@'")); - // host and lex_user->host are equal except for case - db.append(host, strlen(host), system_charset_info); - db.append ('\''); - if (want_access & GRANT_ACL) - db.append(STRING_WITH_LEN(" WITH GRANT OPTION")); - protocol->prepare_for_resend(); - protocol->store(db.ptr(),db.length(),db.charset()); - if (protocol->write()) - { - error= -1; - goto end; - } + String db(buff,sizeof(buff),system_charset_info); + db.length(0); + db.append(STRING_WITH_LEN("GRANT ")); + + if (test_all_bits(want_access,(DB_ACLS & ~GRANT_ACL))) + db.append(STRING_WITH_LEN("ALL PRIVILEGES")); + else if (!(want_access & ~GRANT_ACL)) + db.append(STRING_WITH_LEN("USAGE")); + else + { + int found=0, cnt; + ulong j,test_access= want_access & ~GRANT_ACL; + for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1) + { + if (test_access & j) + { + if (found) + db.append(STRING_WITH_LEN(", ")); + found = 1; + db.append(command_array[cnt],command_lengths[cnt]); + } + } + } + db.append (STRING_WITH_LEN(" ON ")); + append_identifier(thd, &db, acl_db->db, strlen(acl_db->db)); + db.append (STRING_WITH_LEN(".* TO '")); + db.append(username, strlen(username), + system_charset_info); + if (*hostname) + { + db.append (STRING_WITH_LEN("'@'")); + // host and lex_user->host are equal except for case + db.append(host, strlen(host), system_charset_info); + } + db.append ('\''); + if (want_access & GRANT_ACL) + db.append(STRING_WITH_LEN(" WITH GRANT OPTION")); + protocol->prepare_for_resend(); + protocol->store(db.ptr(),db.length(),db.charset()); + if (protocol->write()) + { + return TRUE; + } } } } + return FALSE; + +} + +static bool show_table_and_column_privileges(THD *thd, const char *username, + const char *hostname, + char *buff, size_t buffsize) +{ + uint counter, index; + Protocol *protocol= thd->protocol; - /* Add table & column access */ for (index=0 ; index < column_priv_hash.records ; index++) { const char *user, *host; GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, index); - if (!(user=grant_table->user)) - user= ""; - if (!(host= grant_table->host.hostname)) - host= ""; + user= safe_str(grant_table->user); + host= grant_table->host.hostname; /* We do not make SHOW GRANTS case-sensitive here (like REVOKE), @@ -5580,132 +8246,126 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) would be wrong from a security point of view. */ - if (!strcmp(lex_user->user.str,user) && - !my_strcasecmp(system_charset_info, lex_user->host.str, host)) + if (!strcmp(username,user) && + !my_strcasecmp(system_charset_info, hostname, host)) { - ulong table_access= grant_table->privs; - if ((table_access | grant_table->cols) != 0) + ulong table_access; + ulong cols_access; + if (*hostname) // User { - String global(buff, sizeof(buff), system_charset_info); - ulong test_access= (table_access | grant_table->cols) & ~GRANT_ACL; + table_access= grant_table->privs; + cols_access= grant_table->cols; + } + else // Role + { + table_access= grant_table->init_privs; + cols_access= grant_table->init_cols; + } - global.length(0); - global.append(STRING_WITH_LEN("GRANT ")); + if ((table_access | cols_access) != 0) + { + String global(buff, sizeof(buff), system_charset_info); + ulong test_access= (table_access | cols_access) & ~GRANT_ACL; - if (test_all_bits(table_access, (TABLE_ACLS & ~GRANT_ACL))) - global.append(STRING_WITH_LEN("ALL PRIVILEGES")); - else if (!test_access) - global.append(STRING_WITH_LEN("USAGE")); - else - { + global.length(0); + global.append(STRING_WITH_LEN("GRANT ")); + + if (test_all_bits(table_access, (TABLE_ACLS & ~GRANT_ACL))) + global.append(STRING_WITH_LEN("ALL PRIVILEGES")); + else if (!test_access) + global.append(STRING_WITH_LEN("USAGE")); + else + { /* Add specific column access */ - int found= 0; - ulong j; + int found= 0; + ulong j; - for (counter= 0, j= SELECT_ACL; j <= TABLE_ACLS; counter++, j<<= 1) - { - if (test_access & j) - { - if (found) - global.append(STRING_WITH_LEN(", ")); - found= 1; - global.append(command_array[counter],command_lengths[counter]); + for (counter= 0, j= SELECT_ACL; j <= TABLE_ACLS; counter++, j<<= 1) + { + if (test_access & j) + { + if (found) + global.append(STRING_WITH_LEN(", ")); + found= 1; + global.append(command_array[counter],command_lengths[counter]); - if (grant_table->cols) - { - uint found_col= 0; - for (uint col_index=0 ; - col_index < grant_table->hash_columns.records ; - col_index++) - { - GRANT_COLUMN *grant_column = (GRANT_COLUMN*) - my_hash_element(&grant_table->hash_columns,col_index); - if (grant_column->rights & j) - { - if (!found_col) - { - found_col= 1; - /* - If we have a duplicated table level privilege, we - must write the access privilege name again. - */ - if (table_access & j) - { - global.append(STRING_WITH_LEN(", ")); - global.append(command_array[counter], - command_lengths[counter]); - } - global.append(STRING_WITH_LEN(" (")); - } - else - global.append(STRING_WITH_LEN(", ")); - global.append(grant_column->column, - grant_column->key_length, - system_charset_info); - } - } - if (found_col) - global.append(')'); - } - } - } - } - global.append(STRING_WITH_LEN(" ON ")); - append_identifier(thd, &global, grant_table->db, - strlen(grant_table->db)); - global.append('.'); - append_identifier(thd, &global, grant_table->tname, - strlen(grant_table->tname)); - global.append(STRING_WITH_LEN(" TO '")); - global.append(lex_user->user.str, lex_user->user.length, - system_charset_info); - global.append(STRING_WITH_LEN("'@'")); - // host and lex_user->host are equal except for case - global.append(host, strlen(host), system_charset_info); - global.append('\''); - if (table_access & GRANT_ACL) - global.append(STRING_WITH_LEN(" WITH GRANT OPTION")); - protocol->prepare_for_resend(); - protocol->store(global.ptr(),global.length(),global.charset()); - if (protocol->write()) - { - error= -1; - break; - } + if (grant_table->cols) + { + uint found_col= 0; + HASH *hash_columns; + hash_columns= &grant_table->hash_columns; + + for (uint col_index=0 ; + col_index < hash_columns->records ; + col_index++) + { + GRANT_COLUMN *grant_column = (GRANT_COLUMN*) + my_hash_element(hash_columns,col_index); + if (j & (*hostname ? grant_column->rights // User + : grant_column->init_rights)) // Role + { + if (!found_col) + { + found_col= 1; + /* + If we have a duplicated table level privilege, we + must write the access privilege name again. + */ + if (table_access & j) + { + global.append(STRING_WITH_LEN(", ")); + global.append(command_array[counter], + command_lengths[counter]); + } + global.append(STRING_WITH_LEN(" (")); + } + else + global.append(STRING_WITH_LEN(", ")); + global.append(grant_column->column, + grant_column->key_length, + system_charset_info); + } + } + if (found_col) + global.append(')'); + } + } + } + } + global.append(STRING_WITH_LEN(" ON ")); + append_identifier(thd, &global, grant_table->db, + strlen(grant_table->db)); + global.append('.'); + append_identifier(thd, &global, grant_table->tname, + strlen(grant_table->tname)); + global.append(STRING_WITH_LEN(" TO '")); + global.append(username, strlen(username), + system_charset_info); + if (*hostname) + { + global.append(STRING_WITH_LEN("'@'")); + // host and lex_user->host are equal except for case + global.append(host, strlen(host), system_charset_info); + } + global.append('\''); + if (table_access & GRANT_ACL) + global.append(STRING_WITH_LEN(" WITH GRANT OPTION")); + protocol->prepare_for_resend(); + protocol->store(global.ptr(),global.length(),global.charset()); + if (protocol->write()) + { + return TRUE; + } } } } + return FALSE; - if (show_routine_grants(thd, lex_user, &proc_priv_hash, - STRING_WITH_LEN("PROCEDURE"), buff, sizeof(buff))) - { - error= -1; - goto end; - } - - if (show_routine_grants(thd, lex_user, &func_priv_hash, - STRING_WITH_LEN("FUNCTION"), buff, sizeof(buff))) - { - error= -1; - goto end; - } - - if (show_proxy_grants(thd, lex_user, buff, sizeof(buff))) - { - error= -1; - goto end; - } - -end: - mysql_mutex_unlock(&acl_cache->lock); - mysql_rwlock_unlock(&LOCK_grant); - - my_eof(thd); - DBUG_RETURN(error); } -static int show_routine_grants(THD* thd, LEX_USER *lex_user, HASH *hash, - const char *type, int typelen, +static int show_routine_grants(THD* thd, + const char *username, const char *hostname, + HASH *hash, const char *type, int typelen, char *buff, int buffsize) { uint counter, index; @@ -5717,10 +8377,8 @@ static int show_routine_grants(THD* thd, LEX_USER *lex_user, HASH *hash, const char *user, *host; GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, index); - if (!(user=grant_proc->user)) - user= ""; - if (!(host= grant_proc->host.hostname)) - host= ""; + user= safe_str(grant_proc->user); + host= grant_proc->host.hostname; /* We do not make SHOW GRANTS case-sensitive here (like REVOKE), @@ -5729,10 +8387,15 @@ static int show_routine_grants(THD* thd, LEX_USER *lex_user, HASH *hash, would be wrong from a security point of view. */ - if (!strcmp(lex_user->user.str,user) && - !my_strcasecmp(system_charset_info, lex_user->host.str, host)) + if (!strcmp(username, user) && + !my_strcasecmp(system_charset_info, hostname, host)) { - ulong proc_access= grant_proc->privs; + ulong proc_access; + if (*hostname) // User + proc_access= grant_proc->privs; + else // Role + proc_access= grant_proc->init_privs; + if (proc_access != 0) { String global(buff, buffsize, system_charset_info); @@ -5769,11 +8432,14 @@ static int show_routine_grants(THD* thd, LEX_USER *lex_user, HASH *hash, append_identifier(thd, &global, grant_proc->tname, strlen(grant_proc->tname)); global.append(STRING_WITH_LEN(" TO '")); - global.append(lex_user->user.str, lex_user->user.length, + global.append(username, strlen(username), system_charset_info); - global.append(STRING_WITH_LEN("'@'")); - // host and lex_user->host are equal except for case - global.append(host, strlen(host), system_charset_info); + if (*hostname) + { + global.append(STRING_WITH_LEN("'@'")); + // host and lex_user->host are equal except for case + global.append(host, strlen(host), system_charset_info); + } global.append('\''); if (proc_access & GRANT_ACL) global.append(STRING_WITH_LEN(" WITH GRANT OPTION")); @@ -5790,6 +8456,7 @@ static int show_routine_grants(THD* thd, LEX_USER *lex_user, HASH *hash, return error; } + /* Make a clear-text version of the requested privilege. */ @@ -5798,7 +8465,7 @@ void get_privilege_desc(char *to, uint max_length, ulong access) { uint pos; char *start=to; - DBUG_ASSERT(max_length >= 30); // For end ',' removal + DBUG_ASSERT(max_length >= 30); // For end ', ' removal if (access) { @@ -5809,9 +8476,11 @@ void get_privilege_desc(char *to, uint max_length, ulong access) command_lengths[pos] + (uint) (to-start) < max_length) { to= strmov(to, command_array[pos]); - *to++=','; + *to++= ','; + *to++= ' '; } } + to--; // Remove end ' ' to--; // Remove end ',' } *to=0; @@ -5824,7 +8493,7 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) mysql_mutex_lock(&acl_cache->lock); - if (initialized && (acl_user= find_acl_user(host,user, FALSE))) + if (initialized && (acl_user= find_user_wild(host,user))) uc->user_resources= acl_user->user_resource; else bzero((char*) &uc->user_resources, sizeof(uc->user_resources)); @@ -5832,117 +8501,84 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) mysql_mutex_unlock(&acl_cache->lock); } -/* - Open the grant tables. +static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data) +{ + LEX_CSTRING *rolename= static_cast<LEX_CSTRING *>(data); + if (rolename->length == grantee->user.length && + !strcmp(rolename->str, grantee->user.str)) + return -1; // End search, we've found our role. - SYNOPSIS - open_grant_tables() - thd The current thread. - tables (out) The 4 elements array for the opened tables. + /* Keep looking, we haven't found our role yet. */ + return 0; +} - DESCRIPTION - Tables are numbered as follows: - 0 user - 1 db - 2 tables_priv - 3 columns_priv +/* + Initialize a TABLE_LIST array and open grant tables - RETURN - 1 Skip GRANT handling during replication. - 0 OK. - < 0 Error. + All tables will be opened with the same lock type, either read or write. + + @retval 1 replication filters matched. Abort the operation, but return OK (!) + @retval 0 tables were opened successfully + @retval -1 error, tables could not be opened */ -#define GRANT_TABLES 6 -int open_grant_tables(THD *thd, TABLE_LIST *tables) +static int open_grant_tables(THD *thd, TABLE_LIST *tables, + enum thr_lock_type lock_type, int tables_to_open) { DBUG_ENTER("open_grant_tables"); - if (!initialized) + /* + We can read privilege tables even when !initialized. + This can be acl_load() - server startup or FLUSH PRIVILEGES + */ + if (lock_type >= TL_WRITE_ALLOW_WRITE && !initialized) { my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); DBUG_RETURN(-1); } - tables->init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("user"), "user", TL_WRITE); - (tables+1)->init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("db"), "db", TL_WRITE); - (tables+2)->init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("tables_priv"), - "tables_priv", TL_WRITE); - (tables+3)->init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("columns_priv"), - "columns_priv", TL_WRITE); - (tables+4)->init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("procs_priv"), - "procs_priv", TL_WRITE); - (tables+5)->init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("proxies_priv"), - "proxies_priv", TL_WRITE); - tables[5].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - - tables->next_local= tables->next_global= tables + 1; - (tables+1)->next_local= (tables+1)->next_global= tables + 2; - (tables+2)->next_local= (tables+2)->next_global= tables + 3; - (tables+3)->next_local= (tables+3)->next_global= tables + 4; - (tables+4)->next_local= (tables+4)->next_global= tables + 5; + int prev= -1; + for (int cur=TABLES_MAX-1, mask= 1 << cur; mask; cur--, mask >>= 1) + { + if ((tables_to_open & mask) == 0) + { + tables[cur].table= NULL; + continue; + } + tables[cur].init_one_table(C_STRING_WITH_LEN("mysql"), + acl_table_names[cur].str, + acl_table_names[cur].length, + acl_table_names[cur].str, lock_type); + tables[cur].open_type= OT_BASE_ONLY; + if (lock_type >= TL_WRITE_ALLOW_WRITE) + tables[cur].updating= 1; + if (cur >= FIRST_OPTIONAL_TABLE) + tables[cur].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + if (prev != -1) + tables[cur].next_local= tables[cur].next_global= & tables[prev]; + prev= cur; + } #ifdef HAVE_REPLICATION - /* - GRANT and REVOKE are applied the slave in/exclusion rules as they are - some kind of updates to the mysql.% tables. - */ - if (thd->slave_thread && rpl_filter->is_on()) + if (lock_type >= TL_WRITE_ALLOW_WRITE && thd->slave_thread && !thd->spcont) { /* - The tables must be marked "updating" so that tables_ok() takes them into - account in tests. + GRANT and REVOKE are applied the slave in/exclusion rules as they are + some kind of updates to the mysql.% tables. */ - tables[0].updating= tables[1].updating= tables[2].updating= - tables[3].updating= tables[4].updating= tables[5].updating= 1; - if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) + Rpl_filter *rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (rpl_filter->is_on() && !rpl_filter->tables_ok(0, tables)) DBUG_RETURN(1); - tables[0].updating= tables[1].updating= tables[2].updating= - tables[3].updating= tables[4].updating= tables[5].updating= 0; } #endif - if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) - { // This should never happen + if (open_and_lock_tables(thd, tables + prev, FALSE, + MYSQL_LOCK_IGNORE_TIMEOUT)) DBUG_RETURN(-1); - } DBUG_RETURN(0); } -ACL_USER *check_acl_user(LEX_USER *user_name, - uint *acl_acl_userdx) -{ - ACL_USER *acl_user= 0; - uint counter; - - mysql_mutex_assert_owner(&acl_cache->lock); - - for (counter= 0 ; counter < acl_users.elements ; counter++) - { - const char *user,*host; - acl_user= dynamic_element(&acl_users, counter, ACL_USER*); - if (!(user=acl_user->user)) - user= ""; - if (!(host=acl_user->host.hostname)) - host= ""; - if (!strcmp(user_name->user.str,user) && - !my_strcasecmp(system_charset_info, user_name->host.str, host)) - break; - } - if (counter == acl_users.elements) - return 0; - - *acl_acl_userdx= counter; - return acl_user; -} - /* Modify a privilege table. @@ -5977,7 +8613,7 @@ static int modify_grant_table(TABLE *table, Field *host_field, system_charset_info); user_field->store(user_to->user.str, user_to->user.length, system_charset_info); - if ((error= table->file->ha_update_row(table->record[1], + if ((error= table->file->ha_update_row(table->record[1], table->record[0])) && error != HA_ERR_RECORD_IS_THE_SAME) table->file->print_error(error, MYF(0)); @@ -5995,6 +8631,89 @@ static int modify_grant_table(TABLE *table, Field *host_field, } /* + Handle the roles_mapping privilege table +*/ +static int handle_roles_mappings_table(TABLE *table, bool drop, + LEX_USER *user_from, LEX_USER *user_to) +{ + /* + All entries (Host, User) that match user_from will be renamed, + as well as all Role entries that match if user_from.host.str == "" + + Otherwise, only matching (Host, User) will be renamed. + */ + DBUG_ENTER("handle_roles_mappings_table"); + + int error; + int result= 0; + THD *thd= table->in_use; + const char *host, *user, *role; + Field *host_field= table->field[0]; + Field *user_field= table->field[1]; + Field *role_field= table->field[2]; + + DBUG_PRINT("info", ("Rewriting entry in roles_mapping table: %s@%s", + user_from->user.str, user_from->host.str)); + table->use_all_columns(); + if ((error= table->file->ha_rnd_init(1))) + { + table->file->print_error(error, MYF(0)); + result= -1; + } + else + { + while((error= table->file->ha_rnd_next(table->record[0])) != + HA_ERR_END_OF_FILE) + { + if (error) + { + DBUG_PRINT("info", ("scan error: %d", error)); + continue; + } + + host= safe_str(get_field(thd->mem_root, host_field)); + user= safe_str(get_field(thd->mem_root, user_field)); + + if (!(strcmp(user_from->user.str, user) || + my_strcasecmp(system_charset_info, user_from->host.str, host))) + result= ((drop || user_to) && + modify_grant_table(table, host_field, user_field, user_to)) ? + -1 : result ? result : 1; /* Error or keep result or found. */ + else + { + role= safe_str(get_field(thd->mem_root, role_field)); + + if (!user_from->is_role() || strcmp(user_from->user.str, role)) + continue; + + error= 0; + + if (drop) /* drop if requested */ + { + if ((error= table->file->ha_delete_row(table->record[0]))) + table->file->print_error(error, MYF(0)); + } + else if (user_to) + { + store_record(table, record[1]); + role_field->store(user_to->user.str, user_to->user.length, + system_charset_info); + if ((error= table->file->ha_update_row(table->record[1], + table->record[0])) && + error != HA_ERR_RECORD_IS_THE_SAME) + table->file->print_error(error, MYF(0)); + } + + /* Error or keep result or found. */ + result= error ? -1 : result ? result : 1; + } + } + table->file->ha_rnd_end(); + } + DBUG_RETURN(result); +} + +/* Handle a privilege table. SYNOPSIS @@ -6013,12 +8732,6 @@ static int modify_grant_table(TABLE *table, Field *host_field, Delete from grant table if drop is true. Update in grant table if drop is false and user_to is not NULL. Search in grant table if drop is false and user_to is NULL. - Tables are numbered as follows: - 0 user - 1 db - 2 tables_priv - 3 columns_priv - 4 procs_priv RETURN > 0 At least one record matched. @@ -6026,25 +8739,32 @@ static int modify_grant_table(TABLE *table, Field *host_field, < 0 Error. */ -static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, +static int handle_grant_table(THD *thd, TABLE_LIST *tables, + enum enum_acl_tables table_no, bool drop, LEX_USER *user_from, LEX_USER *user_to) { int result= 0; int error; TABLE *table= tables[table_no].table; Field *host_field= table->field[0]; - Field *user_field= table->field[table_no && table_no != 5 ? 2 : 1]; - char *host_str= user_from->host.str; - char *user_str= user_from->user.str; + Field *user_field= table->field[table_no == USER_TABLE || + table_no == PROXIES_PRIV_TABLE ? 1 : 2]; + const char *host_str= user_from->host.str; + const char *user_str= user_from->user.str; const char *host; const char *user; uchar user_key[MAX_KEY_LENGTH]; uint key_prefix_length; DBUG_ENTER("handle_grant_table"); - THD *thd= current_thd; + + if (table_no == ROLES_MAPPING_TABLE) + { + result= handle_roles_mappings_table(table, drop, user_from, user_to); + DBUG_RETURN(result); + } table->use_all_columns(); - if (! table_no) // mysql.user table + if (table_no == USER_TABLE) // mysql.user table { /* The 'user' table has an unique index on (host, user). @@ -6064,9 +8784,15 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, table->key_info->key_part[1].store_length); key_copy(user_key, table->record[0], table->key_info, key_prefix_length); - if ((error= table->file->ha_index_read_idx_map(table->record[0], 0, - user_key, (key_part_map)3, - HA_READ_KEY_EXACT))) + error= table->file->ha_index_read_idx_map(table->record[0], 0, + user_key, (key_part_map)3, + HA_READ_KEY_EXACT); + if (!error && !*host_str) + { // verify that we got a role or a user, as needed + if (check_is_role(table) != user_from->is_role()) + error= HA_ERR_KEY_NOT_FOUND; + } + if (error) { if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE) { @@ -6101,7 +8827,7 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, DBUG_PRINT("info",("scan table: '%s' search: '%s'@'%s'", table->s->table_name.str, user_str, host_str)); #endif - while ((error= table->file->ha_rnd_next(table->record[0])) != + while ((error= table->file->ha_rnd_next(table->record[0])) != HA_ERR_END_OF_FILE) { if (error) @@ -6110,13 +8836,11 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, DBUG_PRINT("info",("scan error: %d", error)); continue; } - if (! (host= get_field(thd->mem_root, host_field))) - host= ""; - if (! (user= get_field(thd->mem_root, user_field))) - user= ""; + host= safe_str(get_field(thd->mem_root, host_field)); + user= safe_str(get_field(thd->mem_root, user_field)); #ifdef EXTRA_DEBUG - if (table_no != 5) + if (table_no != PROXIES_PRIV_TABLE) { DBUG_PRINT("loop",("scan fields: '%s'@'%s' '%s' '%s' '%s'", user, host, @@ -6150,7 +8874,7 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, /** Handle an in-memory privilege structure. - @param struct_no The number of the structure to handle (0..5). + @param struct_no The number of the structure to handle (0..6). @param drop If user_from is to be dropped. @param user_from The the user to be searched/dropped/renamed. @param user_to The new name for the user if to be renamed, NULL otherwise. @@ -6161,13 +8885,6 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, Delete from grant structure if drop is true. Update in grant structure if drop is false and user_to is not NULL. Search in grant structure if drop is false and user_to is NULL. - Structures are enumerated as follows: - 0 ACL_USER - 1 ACL_DB - 2 COLUMN_PRIVILEGES_HASH - 3 PROC_PRIVILEGES_HASH - 4 FUNC_PRIVILEGES_HASH - 5 PROXY_USERS_ACL @retval > 0 At least one element matched. @retval 0 OK, but no element matched. @@ -6179,29 +8896,81 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, int result= 0; int idx; int elements; - const char *user; - const char *host; + const char *UNINIT_VAR(user); + const char *UNINIT_VAR(host); ACL_USER *acl_user= NULL; + ACL_ROLE *acl_role= NULL; ACL_DB *acl_db= NULL; ACL_PROXY_USER *acl_proxy_user= NULL; GRANT_NAME *grant_name= NULL; + ROLE_GRANT_PAIR *UNINIT_VAR(role_grant_pair); HASH *grant_name_hash= NULL; + HASH *roles_mappings_hash= NULL; DBUG_ENTER("handle_grant_struct"); DBUG_PRINT("info",("scan struct: %u search: '%s'@'%s'", struct_no, user_from->user.str, user_from->host.str)); - LINT_INIT(user); - LINT_INIT(host); - mysql_mutex_assert_owner(&acl_cache->lock); + /* No point in querying ROLE ACL if user_from is not a role */ + if (struct_no == ROLE_ACL && user_from->host.length) + DBUG_RETURN(0); + + /* same. no roles in PROXY_USERS_ACL */ + if (struct_no == PROXY_USERS_ACL && user_from->is_role()) + DBUG_RETURN(0); + + if (struct_no == ROLE_ACL) //no need to scan the structures in this case + { + acl_role= find_acl_role(user_from->user.str); + if (!acl_role) + DBUG_RETURN(0); + + if (!drop && !user_to) //role was found + DBUG_RETURN(1); + + /* this calls for a role update */ + char *old_key= acl_role->user.str; + size_t old_key_length= acl_role->user.length; + if (drop) + { + /* all grants must be revoked from this role by now. propagate this */ + propagate_role_grants(acl_role, PRIVS_TO_MERGE::ALL); + + // delete the role from cross-reference arrays + for (uint i=0; i < acl_role->role_grants.elements; i++) + { + ACL_ROLE *grant= *dynamic_element(&acl_role->role_grants, + i, ACL_ROLE**); + remove_ptr_from_dynarray(&grant->parent_grantee, acl_role); + } + + for (uint i=0; i < acl_role->parent_grantee.elements; i++) + { + ACL_USER_BASE *grantee= *dynamic_element(&acl_role->parent_grantee, + i, ACL_USER_BASE**); + remove_ptr_from_dynarray(&grantee->role_grants, acl_role); + } + + my_hash_delete(&acl_roles, (uchar*) acl_role); + DBUG_RETURN(1); + } + acl_role->user.str= strdup_root(&acl_memroot, user_to->user.str); + acl_role->user.length= user_to->user.length; + + my_hash_update(&acl_roles, (uchar*) acl_role, (uchar*) old_key, + old_key_length); + DBUG_RETURN(1); + + } + /* Get the number of elements in the in-memory structure. */ switch (struct_no) { case USER_ACL: elements= acl_users.elements; break; case DB_ACL: - elements= acl_dbs.elements; + elements= acl_dbs.elements(); break; case COLUMN_PRIVILEGES_HASH: grant_name_hash= &column_priv_hash; @@ -6218,9 +8987,13 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, case PROXY_USERS_ACL: elements= acl_proxy_users.elements; break; + case ROLES_MAPPINGS_HASH: + roles_mappings_hash= &acl_roles_mappings; + elements= roles_mappings_hash->records; + break; default: DBUG_ASSERT(0); - return -1; + DBUG_RETURN(-1); } #ifdef EXTRA_DEBUG @@ -6236,12 +9009,12 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, switch (struct_no) { case USER_ACL: acl_user= dynamic_element(&acl_users, idx, ACL_USER*); - user= acl_user->user; + user= acl_user->user.str; host= acl_user->host.hostname; break; case DB_ACL: - acl_db= dynamic_element(&acl_dbs, idx, ACL_DB*); + acl_db= &acl_dbs.at(idx); user= acl_db->user; host= acl_db->host.hostname; break; @@ -6260,6 +9033,12 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, host= acl_proxy_user->get_host(); break; + case ROLES_MAPPINGS_HASH: + role_grant_pair= (ROLE_GRANT_PAIR *) my_hash_element(roles_mappings_hash, idx); + user= role_grant_pair->u_uname; + host= role_grant_pair->u_hname; + break; + default: DBUG_ASSERT(0); } @@ -6272,9 +9051,41 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, DBUG_PRINT("loop",("scan struct: %u index: %u user: '%s' host: '%s'", struct_no, idx, user, host)); #endif - if (strcmp(user_from->user.str, user) || - my_strcasecmp(system_charset_info, user_from->host.str, host)) - continue; + + if (struct_no == ROLES_MAPPINGS_HASH) + { + const char* role= role_grant_pair->r_uname? role_grant_pair->r_uname: ""; + if (user_from->is_role()) + { + /* When searching for roles within the ROLES_MAPPINGS_HASH, we have + to check both the user field as well as the role field for a match. + + It is possible to have a role granted to a role. If we are going + to modify the mapping entry, it needs to be done on either on the + "user" end (here represented by a role) or the "role" end. At least + one part must match. + + If the "user" end has a not-empty host string, it can never match + as we are searching for a role here. A role always has an empty host + string. + */ + if ((*host || strcmp(user_from->user.str, user)) && + strcmp(user_from->user.str, role)) + continue; + } + else + { + if (strcmp(user_from->user.str, user) || + my_strcasecmp(system_charset_info, user_from->host.str, host)) + continue; + } + } + else + { + if (strcmp(user_from->user.str, user) || + my_strcasecmp(system_charset_info, user_from->host.str, host)) + continue; + } result= 1; /* At least one element found. */ if ( drop ) @@ -6282,11 +9093,12 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, elements--; switch ( struct_no ) { case USER_ACL: + free_acl_user(dynamic_element(&acl_users, idx, ACL_USER*)); delete_dynamic_element(&acl_users, idx); break; case DB_ACL: - delete_dynamic_element(&acl_dbs, idx); + acl_dbs.del(idx); break; case COLUMN_PRIVILEGES_HASH: @@ -6308,19 +9120,30 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, delete_dynamic_element(&acl_proxy_users, idx); break; + case ROLES_MAPPINGS_HASH: + my_hash_delete(roles_mappings_hash, (uchar*) role_grant_pair); + if (idx != elements) + idx++; + break; + + default: + DBUG_ASSERT(0); + break; } } else if ( user_to ) { switch ( struct_no ) { case USER_ACL: - acl_user->user= strdup_root(&mem, user_to->user.str); - update_hostname(&acl_user->host, strdup_root(&mem, user_to->host.str)); + acl_user->user.str= strdup_root(&acl_memroot, user_to->user.str); + acl_user->user.length= user_to->user.length; + update_hostname(&acl_user->host, strdup_root(&acl_memroot, user_to->host.str)); + acl_user->hostname_length= strlen(acl_user->host.hostname); break; case DB_ACL: - acl_db->user= strdup_root(&mem, user_to->user.str); - update_hostname(&acl_db->host, strdup_root(&mem, user_to->host.str)); + acl_db->user= strdup_root(&acl_memroot, user_to->user.str); + update_hostname(&acl_db->host, strdup_root(&acl_memroot, user_to->host.str)); break; case COLUMN_PRIVILEGES_HASH: @@ -6328,7 +9151,7 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, case FUNC_PRIVILEGES_HASH: { /* - Save old hash key and its length to be able properly update + Save old hash key and its length to be able to properly update element position in hash. */ char *old_key= grant_name->hash_key; @@ -6356,16 +9179,47 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, So we need to examine the current element once again, but we don't need to restart the search from the beginning. */ - if (idx != elements) - idx++; + idx++; break; } case PROXY_USERS_ACL: - acl_proxy_user->set_user (&mem, user_to->user.str); - acl_proxy_user->set_host (&mem, user_to->host.str); + acl_proxy_user->set_user (&acl_memroot, user_to->user.str); + acl_proxy_user->set_host (&acl_memroot, user_to->host.str); + break; + + case ROLES_MAPPINGS_HASH: + { + /* + Save old hash key and its length to be able to properly update + element position in hash. + */ + char *old_key= role_grant_pair->hashkey.str; + size_t old_key_length= role_grant_pair->hashkey.length; + bool oom; + + if (user_to->is_role()) + oom= role_grant_pair->init(&acl_memroot, role_grant_pair->u_uname, + role_grant_pair->u_hname, + user_to->user.str, false); + else + oom= role_grant_pair->init(&acl_memroot, user_to->user.str, + user_to->host.str, + role_grant_pair->r_uname, false); + if (oom) + DBUG_RETURN(-1); + + my_hash_update(roles_mappings_hash, (uchar*) role_grant_pair, + (uchar*) old_key, old_key_length); + idx++; // see the comment above + break; + } + + default: + DBUG_ASSERT(0); break; } + } else { @@ -6405,33 +9259,31 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, < 0 Error. */ -static int handle_grant_data(TABLE_LIST *tables, bool drop, +static int handle_grant_data(THD *thd, TABLE_LIST *tables, bool drop, LEX_USER *user_from, LEX_USER *user_to) { int result= 0; int found; + bool handle_as_role= user_from->is_role(); + bool search_only= !drop && !user_to; DBUG_ENTER("handle_grant_data"); - /* Handle user table. */ - if ((found= handle_grant_table(tables, 0, drop, user_from, user_to)) < 0) - { - /* Handle of table failed, don't touch the in-memory array. */ - result= -1; - } - else + if (user_to) + DBUG_ASSERT(handle_as_role == user_to->is_role()); + + if (search_only) { - /* Handle user array. */ - if ((handle_grant_struct(USER_ACL, drop, user_from, user_to)) || found) - { - result= 1; /* At least one record/element found. */ - /* If search is requested, we do not need to search further. */ - if (! drop && ! user_to) - goto end; - } + /* quickly search in-memory structures first */ + if (handle_as_role && find_acl_role(user_from->user.str)) + DBUG_RETURN(1); // found + + if (!handle_as_role && find_user_exact(user_from->host.str, user_from->user.str)) + DBUG_RETURN(1); // found } /* Handle db table. */ - if ((found= handle_grant_table(tables, 1, drop, user_from, user_to)) < 0) + if ((found= handle_grant_table(thd, tables, DB_TABLE, drop, user_from, + user_to)) < 0) { /* Handle of table failed, don't touch the in-memory array. */ result= -1; @@ -6439,18 +9291,20 @@ static int handle_grant_data(TABLE_LIST *tables, bool drop, else { /* Handle db array. */ - if (((handle_grant_struct(DB_ACL, drop, user_from, user_to) && ! result) || - found) && ! result) + if ((handle_grant_struct(DB_ACL, drop, user_from, user_to) || found) + && ! result) { result= 1; /* At least one record/element found. */ /* If search is requested, we do not need to search further. */ - if (! drop && ! user_to) + if (search_only) goto end; + acl_cache->clear(1); } } /* Handle stored routines table. */ - if ((found= handle_grant_table(tables, 4, drop, user_from, user_to)) < 0) + if ((found= handle_grant_table(thd, tables, PROCS_PRIV_TABLE, drop, + user_from, user_to)) < 0) { /* Handle of table failed, don't touch in-memory array. */ result= -1; @@ -6458,27 +9312,28 @@ static int handle_grant_data(TABLE_LIST *tables, bool drop, else { /* Handle procs array. */ - if (((handle_grant_struct(PROC_PRIVILEGES_HASH, drop, user_from, user_to) && ! result) || - found) && ! result) + if ((handle_grant_struct(PROC_PRIVILEGES_HASH, drop, user_from, user_to) || found) + && ! result) { result= 1; /* At least one record/element found. */ /* If search is requested, we do not need to search further. */ - if (! drop && ! user_to) + if (search_only) goto end; } /* Handle funcs array. */ - if (((handle_grant_struct(FUNC_PRIVILEGES_HASH, drop, user_from, user_to) && ! result) || - found) && ! result) + if ((handle_grant_struct(FUNC_PRIVILEGES_HASH, drop, user_from, user_to) || found) + && ! result) { result= 1; /* At least one record/element found. */ /* If search is requested, we do not need to search further. */ - if (! drop && ! user_to) + if (search_only) goto end; } } /* Handle tables table. */ - if ((found= handle_grant_table(tables, 2, drop, user_from, user_to)) < 0) + if ((found= handle_grant_table(thd, tables, TABLES_PRIV_TABLE, drop, + user_from, user_to)) < 0) { /* Handle of table failed, don't touch columns and in-memory array. */ result= -1; @@ -6489,12 +9344,13 @@ static int handle_grant_data(TABLE_LIST *tables, bool drop, { result= 1; /* At least one record found. */ /* If search is requested, we do not need to search further. */ - if (! drop && ! user_to) + if (search_only) goto end; } /* Handle columns table. */ - if ((found= handle_grant_table(tables, 3, drop, user_from, user_to)) < 0) + if ((found= handle_grant_table(thd, tables, COLUMNS_PRIV_TABLE, drop, + user_from, user_to)) < 0) { /* Handle of table failed, don't touch the in-memory array. */ result= -1; @@ -6502,16 +9358,19 @@ static int handle_grant_data(TABLE_LIST *tables, bool drop, else { /* Handle columns hash. */ - if (((handle_grant_struct(COLUMN_PRIVILEGES_HASH, drop, user_from, user_to) && ! result) || - found) && ! result) + if ((handle_grant_struct(COLUMN_PRIVILEGES_HASH, drop, user_from, user_to) || found) + && ! result) result= 1; /* At least one record/element found. */ + if (search_only) + goto end; } } /* Handle proxies_priv table. */ - if (tables[5].table) + if (tables[PROXIES_PRIV_TABLE].table) { - if ((found= handle_grant_table(tables, 5, drop, user_from, user_to)) < 0) + if ((found= handle_grant_table(thd, tables, PROXIES_PRIV_TABLE, drop, + user_from, user_to)) < 0) { /* Handle of table failed, don't touch the in-memory array. */ result= -1; @@ -6519,27 +9378,54 @@ static int handle_grant_data(TABLE_LIST *tables, bool drop, else { /* Handle proxies_priv array. */ - if ((handle_grant_struct(PROXY_USERS_ACL, drop, user_from, user_to) && !result) || - found) + if ((handle_grant_struct(PROXY_USERS_ACL, drop, user_from, user_to) || found) + && ! result) result= 1; /* At least one record/element found. */ + if (search_only) + goto end; } } - end: - DBUG_RETURN(result); -} + /* Handle roles_mapping table. */ + if (tables[ROLES_MAPPING_TABLE].table) + { + if ((found= handle_grant_table(thd, tables, ROLES_MAPPING_TABLE, drop, + user_from, user_to)) < 0) + { + /* Handle of table failed, don't touch the in-memory array. */ + result= -1; + } + else + { + /* Handle acl_roles_mappings array */ + if ((handle_grant_struct(ROLES_MAPPINGS_HASH, drop, user_from, user_to) || found) + && ! result) + result= 1; /* At least one record/element found */ + if (search_only) + goto end; + } + } -static void append_user(String *str, LEX_USER *user) -{ - if (str->length()) - str->append(','); - str->append('\''); - str->append(user->user.str); - str->append(STRING_WITH_LEN("'@'")); - str->append(user->host.str); - str->append('\''); -} + /* Handle user table. */ + if ((found= handle_grant_table(thd, tables, USER_TABLE, drop, user_from, + user_to)) < 0) + { + /* Handle of table failed, don't touch the in-memory array. */ + result= -1; + } + else + { + enum enum_acl_lists what= handle_as_role ? ROLE_ACL : USER_ACL; + if (((handle_grant_struct(what, drop, user_from, user_to)) || found) && !result) + { + result= 1; /* At least one record/element found. */ + DBUG_ASSERT(! search_only); + } + } +end: + DBUG_RETURN(result); +} /* Create a list of users. @@ -6548,88 +9434,178 @@ static void append_user(String *str, LEX_USER *user) mysql_create_user() thd The current thread. list The users to create. + handle_as_role Handle the user list as roles if true RETURN FALSE OK. TRUE Error. */ -bool mysql_create_user(THD *thd, List <LEX_USER> &list) +bool mysql_create_user(THD *thd, List <LEX_USER> &list, bool handle_as_role) { int result; String wrong_users; - LEX_USER *user_name, *tmp_user_name; + LEX_USER *user_name; List_iterator <LEX_USER> user_list(list); - TABLE_LIST tables[GRANT_TABLES]; - bool some_users_created= FALSE; - bool save_binlog_row_based; + TABLE_LIST tables[TABLES_MAX]; + bool binlog= false; + bool some_users_dropped= false; DBUG_ENTER("mysql_create_user"); + DBUG_PRINT("entry", ("Handle as %s", handle_as_role ? "role" : "user")); - /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); + if (handle_as_role && sp_process_definer(thd)) + DBUG_RETURN(TRUE); /* CREATE USER may be skipped on replication client. */ - if ((result= open_grant_tables(thd, tables))) - { - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user | Table_db | + Table_tables_priv | Table_columns_priv | + Table_procs_priv | Table_proxies_priv | + Table_roles_mapping))) DBUG_RETURN(result != 1); - } mysql_rwlock_wrlock(&LOCK_grant); mysql_mutex_lock(&acl_cache->lock); - while ((tmp_user_name= user_list++)) + while ((user_name= user_list++)) { - if (!(user_name= get_current_user(thd, tmp_user_name))) + if (user_name->user.str == current_user.str) { + append_str(&wrong_users, STRING_WITH_LEN("CURRENT_USER")); + result= TRUE; + continue; + } + + if (user_name->user.str == current_role.str) + { + append_str(&wrong_users, STRING_WITH_LEN("CURRENT_ROLE")); + result= TRUE; + continue; + } + + if (handle_as_role && is_invalid_role_name(user_name->user.str)) + { + append_user(thd, &wrong_users, user_name); + result= TRUE; + continue; + } + + if (!user_name->host.str) + user_name->host= host_not_specified; + + if (fix_lex_user(thd, user_name)) + { + append_user(thd, &wrong_users, user_name); result= TRUE; continue; } /* Search all in-memory structures and grant tables - for a mention of the new user name. + for a mention of the new user/role name. */ - if (handle_grant_data(tables, 0, user_name, NULL)) + if (handle_grant_data(thd, tables, 0, user_name, NULL)) + { + if (thd->lex->create_info.or_replace()) + { + // Drop the existing user + if (handle_grant_data(thd, tables, 1, user_name, NULL) <= 0) + { + // DROP failed + append_user(thd, &wrong_users, user_name); + result= true; + continue; + } + else + some_users_dropped= true; + // Proceed with the creation + } + else if (thd->lex->create_info.if_not_exists()) + { + binlog= true; + if (handle_as_role) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_ROLE_CREATE_EXISTS, + ER_THD(thd, ER_ROLE_CREATE_EXISTS), + user_name->user.str); + else + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_USER_CREATE_EXISTS, + ER_THD(thd, ER_USER_CREATE_EXISTS), + user_name->user.str, user_name->host.str); + continue; + } + else + { + // "CREATE USER user1" for an existing user + append_user(thd, &wrong_users, user_name); + result= true; + continue; + } + } + + if (replace_user_table(thd, tables[USER_TABLE].table, *user_name, 0, 0, 1, 0)) { - append_user(&wrong_users, user_name); + append_user(thd, &wrong_users, user_name); result= TRUE; continue; } + binlog= true; - some_users_created= TRUE; - if (replace_user_table(thd, tables[0].table, *user_name, 0, 0, 1, 0)) + // every created role is automatically granted to its creator-admin + if (handle_as_role) { - append_user(&wrong_users, user_name); - result= TRUE; + ACL_USER_BASE *grantee= find_acl_user_base(thd->lex->definer->user.str, + thd->lex->definer->host.str); + ACL_ROLE *role= find_acl_role(user_name->user.str); + + /* + just like with routines, views, triggers, and events we allow + non-existant definers here with a warning (see sp_process_definer()) + */ + if (grantee) + add_role_user_mapping(grantee, role); + + if (replace_roles_mapping_table(tables[ROLES_MAPPING_TABLE].table, + &thd->lex->definer->user, + &thd->lex->definer->host, + &user_name->user, true, + NULL, false)) + { + append_user(thd, &wrong_users, user_name); + if (grantee) + undo_add_role_user_mapping(grantee, role); + result= TRUE; + } + else if (grantee) + update_role_mapping(&thd->lex->definer->user, + &thd->lex->definer->host, + &user_name->user, true, NULL, false); } } + if (result && some_users_dropped && !handle_as_role) + { + /* Rebuild in-memory structs, since 'acl_users' has been modified */ + rebuild_check_host(); + rebuild_role_grants(); + } + mysql_mutex_unlock(&acl_cache->lock); if (result) - my_error(ER_CANNOT_USER, MYF(0), "CREATE USER", wrong_users.c_ptr_safe()); + { + my_error(ER_CANNOT_USER, MYF(0), + (handle_as_role) ? "CREATE ROLE" : "CREATE USER", + wrong_users.c_ptr_safe()); + } - if (some_users_created) + if (binlog) result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); mysql_rwlock_unlock(&LOCK_grant); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } - /* Drop a list of users and all their privileges. @@ -6643,35 +9619,24 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) TRUE Error. */ -bool mysql_drop_user(THD *thd, List <LEX_USER> &list) +bool mysql_drop_user(THD *thd, List <LEX_USER> &list, bool handle_as_role) { int result; String wrong_users; LEX_USER *user_name, *tmp_user_name; List_iterator <LEX_USER> user_list(list); - TABLE_LIST tables[GRANT_TABLES]; - bool some_users_deleted= FALSE; + TABLE_LIST tables[TABLES_MAX]; + bool binlog= false; ulonglong old_sql_mode= thd->variables.sql_mode; - bool save_binlog_row_based; DBUG_ENTER("mysql_drop_user"); - - /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); + DBUG_PRINT("entry", ("Handle as %s", handle_as_role ? "role" : "user")); /* DROP USER may be skipped on replication client. */ - if ((result= open_grant_tables(thd, tables))) - { - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user | Table_db | + Table_tables_priv | Table_columns_priv | + Table_procs_priv | Table_proxies_priv | + Table_roles_mapping))) DBUG_RETURN(result != 1); - } thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH; @@ -6680,41 +9645,78 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list) while ((tmp_user_name= user_list++)) { - if (!(user_name= get_current_user(thd, tmp_user_name))) + int rc; + user_name= get_current_user(thd, tmp_user_name, false); + if (!user_name) { + thd->clear_error(); + append_str(&wrong_users, STRING_WITH_LEN("CURRENT_ROLE")); result= TRUE; continue; - } - if (handle_grant_data(tables, 1, user_name, NULL) <= 0) + } + + if (handle_as_role != user_name->is_role()) { - append_user(&wrong_users, user_name); + append_user(thd, &wrong_users, user_name); result= TRUE; continue; } - some_users_deleted= TRUE; + + if ((rc= handle_grant_data(thd, tables, 1, user_name, NULL)) > 0) + { + // The user or role was successfully deleted + binlog= true; + continue; + } + + if (rc == 0 && thd->lex->if_exists()) + { + // "DROP USER IF EXISTS user1" for a non-existing user or role + if (handle_as_role) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_ROLE_DROP_EXISTS, + ER_THD(thd, ER_ROLE_DROP_EXISTS), + user_name->user.str); + else + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_USER_DROP_EXISTS, + ER_THD(thd, ER_USER_DROP_EXISTS), + user_name->user.str, user_name->host.str); + binlog= true; + continue; + } + // Internal error, or "DROP USER user1" for a non-existing user + append_user(thd, &wrong_users, user_name); + result= TRUE; } - /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ - rebuild_check_host(); + if (!handle_as_role) + { + /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ + rebuild_check_host(); + + /* + Rebuild every user's role_grants since 'acl_users' has been sorted + and old pointers to ACL_USER elements are no longer valid + */ + rebuild_role_grants(); + } mysql_mutex_unlock(&acl_cache->lock); if (result) - my_error(ER_CANNOT_USER, MYF(0), "DROP USER", wrong_users.c_ptr_safe()); + my_error(ER_CANNOT_USER, MYF(0), + (handle_as_role) ? "DROP ROLE" : "DROP USER", + wrong_users.c_ptr_safe()); - if (some_users_deleted) + if (binlog) result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); mysql_rwlock_unlock(&LOCK_grant); thd->variables.sql_mode= old_sql_mode; - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } - /* Rename a user. @@ -6735,77 +9737,73 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list) LEX_USER *user_from, *tmp_user_from; LEX_USER *user_to, *tmp_user_to; List_iterator <LEX_USER> user_list(list); - TABLE_LIST tables[GRANT_TABLES]; + TABLE_LIST tables[TABLES_MAX]; bool some_users_renamed= FALSE; - bool save_binlog_row_based; DBUG_ENTER("mysql_rename_user"); - /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - /* RENAME USER may be skipped on replication client. */ - if ((result= open_grant_tables(thd, tables))) - { - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user | Table_db | + Table_tables_priv | Table_columns_priv | + Table_procs_priv | Table_proxies_priv | + Table_roles_mapping))) DBUG_RETURN(result != 1); - } + + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); mysql_rwlock_wrlock(&LOCK_grant); mysql_mutex_lock(&acl_cache->lock); while ((tmp_user_from= user_list++)) { - if (!(user_from= get_current_user(thd, tmp_user_from))) + tmp_user_to= user_list++; + if (!(user_from= get_current_user(thd, tmp_user_from, false))) { + append_user(thd, &wrong_users, user_from); result= TRUE; continue; - } - tmp_user_to= user_list++; - if (!(user_to= get_current_user(thd, tmp_user_to))) + } + if (!(user_to= get_current_user(thd, tmp_user_to, false))) { + append_user(thd, &wrong_users, user_to); result= TRUE; continue; - } - DBUG_ASSERT(user_to != 0); /* Syntax enforces pairs of users. */ + } + DBUG_ASSERT(!user_from->is_role()); + DBUG_ASSERT(!user_to->is_role()); /* Search all in-memory structures and grant tables for a mention of the new user name. */ - if (handle_grant_data(tables, 0, user_to, NULL) || - handle_grant_data(tables, 0, user_from, user_to) <= 0) + if (handle_grant_data(thd, tables, 0, user_to, NULL) || + handle_grant_data(thd, tables, 0, user_from, user_to) <= 0) { - append_user(&wrong_users, user_from); + /* NOTE TODO renaming roles is not yet implemented */ + append_user(thd, &wrong_users, user_from); result= TRUE; continue; } some_users_renamed= TRUE; } - + /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ rebuild_check_host(); + /* + Rebuild every user's role_grants since 'acl_users' has been sorted + and old pointers to ACL_USER elements are no longer valid + */ + rebuild_role_grants(); + mysql_mutex_unlock(&acl_cache->lock); if (result) my_error(ER_CANNOT_USER, MYF(0), "RENAME USER", wrong_users.c_ptr_safe()); - + if (some_users_renamed && mysql_bin_log.is_open()) result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); mysql_rwlock_unlock(&LOCK_grant); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -6829,26 +9827,16 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) uint counter, revoked, is_proc; int result; ACL_DB *acl_db; - TABLE_LIST tables[GRANT_TABLES]; - bool save_binlog_row_based; + TABLE_LIST tables[TABLES_MAX]; DBUG_ENTER("mysql_revoke_all"); - /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - - if ((result= open_grant_tables(thd, tables))) - { - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user | Table_db | + Table_tables_priv | Table_columns_priv | + Table_procs_priv | Table_proxies_priv | + Table_roles_mapping))) DBUG_RETURN(result != 1); - } + + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); mysql_rwlock_wrlock(&LOCK_grant); mysql_mutex_lock(&acl_cache->lock); @@ -6857,19 +9845,22 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) List_iterator <LEX_USER> user_list(list); while ((tmp_lex_user= user_list++)) { - if (!(lex_user= get_current_user(thd, tmp_lex_user))) + if (!(lex_user= get_current_user(thd, tmp_lex_user, false))) { result= -1; continue; - } - if (!find_acl_user(lex_user->host.str, lex_user->user.str, TRUE)) + } + + /* This is not a role and the user could not be found */ + if (!lex_user->is_role() && + !find_user_exact(lex_user->host.str, lex_user->user.str)) { result= -1; continue; } - if (replace_user_table(thd, tables[0].table, - *lex_user, ~(ulong)0, 1, 0, 0)) + if (replace_user_table(thd, tables[USER_TABLE].table, *lex_user, + ~(ulong)0, 1, 0, 0)) { result= -1; continue; @@ -6883,20 +9874,19 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) */ do { - for (counter= 0, revoked= 0 ; counter < acl_dbs.elements ; ) + for (counter= 0, revoked= 0 ; counter < acl_dbs.elements() ; ) { const char *user,*host; - acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); - if (!(user=acl_db->user)) - user= ""; - if (!(host=acl_db->host.hostname)) - host= ""; + acl_db=&acl_dbs.at(counter); - if (!strcmp(lex_user->user.str,user) && + user= safe_str(acl_db->user); + host= safe_str(acl_db->host.hostname); + + if (!strcmp(lex_user->user.str, user) && !strcmp(lex_user->host.str, host)) { - if (!replace_db_table(tables[1].table, acl_db->db, *lex_user, + if (!replace_db_table(tables[DB_TABLE].table, acl_db->db, *lex_user, ~(ulong)0, 1)) { /* @@ -6920,18 +9910,16 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) const char *user,*host; GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, counter); - if (!(user=grant_table->user)) - user= ""; - if (!(host=grant_table->host.hostname)) - host= ""; + user= safe_str(grant_table->user); + host= safe_str(grant_table->host.hostname); if (!strcmp(lex_user->user.str,user) && !strcmp(lex_user->host.str, host)) { - if (replace_table_table(thd,grant_table,tables[2].table,*lex_user, - grant_table->db, - grant_table->tname, - ~(ulong)0, 0, 1)) + if (replace_table_table(thd, grant_table, + tables[TABLES_PRIV_TABLE].table, + *lex_user, grant_table->db, + grant_table->tname, ~(ulong)0, 0, 1)) { result= -1; } @@ -6943,11 +9931,10 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) continue; } List<LEX_COLUMN> columns; - if (!replace_column_table(grant_table,tables[3].table, *lex_user, - columns, - grant_table->db, - grant_table->tname, - ~(ulong)0, 1)) + if (!replace_column_table(grant_table, + tables[COLUMNS_PRIV_TABLE].table, + *lex_user, columns, grant_table->db, + grant_table->tname, ~(ulong)0, 1)) { revoked= 1; continue; @@ -6966,19 +9953,16 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) { const char *user,*host; GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter); - if (!(user=grant_proc->user)) - user= ""; - if (!(host=grant_proc->host.hostname)) - host= ""; + user= safe_str(grant_proc->user); + host= safe_str(grant_proc->host.hostname); if (!strcmp(lex_user->user.str,user) && !strcmp(lex_user->host.str, host)) { - if (replace_routine_table(thd,grant_proc,tables[4].table,*lex_user, - grant_proc->db, - grant_proc->tname, - is_proc, - ~(ulong)0, 1) == 0) + if (replace_routine_table(thd, grant_proc, + tables[PROCS_PRIV_TABLE].table, *lex_user, + grant_proc->db, grant_proc->tname, + is_proc, ~(ulong)0, 1) == 0) { revoked= 1; continue; @@ -6988,21 +9972,71 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) counter++; } } while (revoked); + + ACL_USER_BASE *user_or_role; + /* remove role grants */ + if (lex_user->is_role()) + { + /* this can not fail due to get_current_user already having searched for it */ + user_or_role= find_acl_role(lex_user->user.str); + } + else + { + user_or_role= find_user_exact(lex_user->host.str, lex_user->user.str); + } + /* + Find every role grant pair matching the role_grants array and remove it, + both from the acl_roles_mappings and the roles_mapping table + */ + for (counter= 0; counter < user_or_role->role_grants.elements; counter++) + { + ACL_ROLE *role_grant= *dynamic_element(&user_or_role->role_grants, + counter, ACL_ROLE**); + ROLE_GRANT_PAIR *pair = find_role_grant_pair(&lex_user->user, + &lex_user->host, + &role_grant->user); + if (replace_roles_mapping_table(tables[ROLES_MAPPING_TABLE].table, + &lex_user->user, &lex_user->host, + &role_grant->user, false, pair, true)) + { + result= -1; //Something went wrong + } + update_role_mapping(&lex_user->user, &lex_user->host, + &role_grant->user, false, pair, true); + /* + Delete from the parent_grantee array of the roles granted, + the entry pointing to this user_or_role + */ + remove_ptr_from_dynarray(&role_grant->parent_grantee, user_or_role); + } + /* TODO + How to handle an error in the replace_roles_mapping_table, in + regards to the privileges held in memory + */ + + /* Finally, clear the role_grants array */ + if (counter == user_or_role->role_grants.elements) + { + reset_dynamic(&user_or_role->role_grants); + } + /* + If we are revoking from a role, we need to update all the parent grantees + */ + if (lex_user->is_role()) + { + propagate_role_grants((ACL_ROLE *)user_or_role, PRIVS_TO_MERGE::ALL); + } } mysql_mutex_unlock(&acl_cache->lock); if (result) - my_message(ER_REVOKE_GRANTS, ER(ER_REVOKE_GRANTS), MYF(0)); - + my_message(ER_REVOKE_GRANTS, ER_THD(thd, ER_REVOKE_GRANTS), MYF(0)); + result= result | write_bin_log(thd, FALSE, thd->query(), thd->query_length()); mysql_rwlock_unlock(&LOCK_grant); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -7031,9 +10065,9 @@ public: virtual bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl); + Sql_condition ** cond_hdl); bool has_errors() { return is_grave; } @@ -7046,18 +10080,18 @@ Silence_routine_definer_errors::handle_condition( THD *thd, uint sql_errno, const char*, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { *cond_hdl= NULL; - if (level == MYSQL_ERROR::WARN_LEVEL_ERROR) + if (level == Sql_condition::WARN_LEVEL_ERROR) { switch (sql_errno) { case ER_NONEXISTING_PROC_GRANT: /* Convert the error into a warning. */ - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, sql_errno, msg); return TRUE; default: @@ -7091,29 +10125,25 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, { uint counter, revoked; int result; - TABLE_LIST tables[GRANT_TABLES]; + TABLE_LIST tables[TABLES_MAX]; HASH *hash= is_proc ? &proc_priv_hash : &func_priv_hash; Silence_routine_definer_errors error_handler; - bool save_binlog_row_based; DBUG_ENTER("sp_revoke_privileges"); - if ((result= open_grant_tables(thd, tables))) + if ((result= open_grant_tables(thd, tables, TL_WRITE, Table_user | Table_db | + Table_tables_priv | Table_columns_priv | + Table_procs_priv | Table_proxies_priv | + Table_roles_mapping))) DBUG_RETURN(result != 1); + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + /* Be sure to pop this before exiting this scope! */ thd->push_internal_handler(&error_handler); mysql_rwlock_wrlock(&LOCK_grant); mysql_mutex_lock(&acl_cache->lock); - /* - This statement will be replicated as a statement, even when using - row-based replication. The flag will be reset at the end of the - statement. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - /* Remove procedure access */ do { @@ -7126,12 +10156,10 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, LEX_USER lex_user; lex_user.user.str= grant_proc->user; lex_user.user.length= strlen(grant_proc->user); - lex_user.host.str= grant_proc->host.hostname ? - grant_proc->host.hostname : (char*)""; - lex_user.host.length= grant_proc->host.hostname ? - strlen(grant_proc->host.hostname) : 0; - - if (replace_routine_table(thd,grant_proc,tables[4].table,lex_user, + lex_user.host.str= safe_str(grant_proc->host.hostname); + lex_user.host.length= strlen(lex_user.host.str); + if (replace_routine_table(thd, grant_proc, + tables[PROCS_PRIV_TABLE].table, lex_user, grant_proc->db, grant_proc->tname, is_proc, ~(ulong)0, 1) == 0) { @@ -7147,10 +10175,6 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, mysql_rwlock_unlock(&LOCK_grant); thd->pop_internal_handler(); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(error_handler.has_errors()); } @@ -7166,7 +10190,7 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, @return @retval FALSE Success - @retval TRUE An error occured. Error message not yet sent. + @retval TRUE An error occurred. Error message not yet sent. */ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, @@ -7178,7 +10202,6 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, List<LEX_USER> user_list; bool result; ACL_USER *au; - char passwd_buff[SCRAMBLED_PASSWORD_CHAR_LENGTH+1]; Dummy_error_handler error_handler; DBUG_ENTER("sp_grant_privileges"); @@ -7188,9 +10211,8 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, combo->user.str= (char *) sctx->priv_user; mysql_mutex_lock(&acl_cache->lock); - - if ((au= find_acl_user(combo->host.str= (char *) sctx->priv_host, - combo->user.str, TRUE))) + if ((au= find_user_exact(combo->host.str= (char *) sctx->priv_host, + combo->user.str))) goto found_acl; mysql_mutex_unlock(&acl_cache->lock); @@ -7205,47 +10227,18 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, tables->db= (char*)sp_db; tables->table_name= tables->alias= (char*)sp_name; - thd->make_lex_string(&combo->user, - combo->user.str, strlen(combo->user.str), 0); - thd->make_lex_string(&combo->host, - combo->host.str, strlen(combo->host.str), 0); + thd->make_lex_string(&combo->user, combo->user.str, strlen(combo->user.str)); + thd->make_lex_string(&combo->host, combo->host.str, strlen(combo->host.str)); - combo->password= empty_lex_str; - combo->plugin= empty_lex_str; - combo->auth= empty_lex_str; + combo->reset_auth(); if(au) { - if (au->salt_len) - { - if (au->salt_len == SCRAMBLE_LENGTH) - { - make_password_from_salt(passwd_buff, au->salt); - combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH; - } - else if (au->salt_len == SCRAMBLE_LENGTH_323) - { - make_password_from_salt_323(passwd_buff, (ulong *) au->salt); - combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; - } - else - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_PASSWD_LENGTH, - ER(ER_PASSWD_LENGTH), SCRAMBLED_PASSWORD_CHAR_LENGTH); - return TRUE; - } - combo->password.str= passwd_buff; - } - - if (au->plugin.str != native_password_plugin_name.str && - au->plugin.str != old_password_plugin_name.str) - { - combo->plugin= au->plugin; - combo->auth= au->auth_string; - } + combo->plugin= au->plugin; + combo->auth= au->auth_string; } - if (user_list.push_back(combo)) + if (user_list.push_back(combo, thd->mem_root)) DBUG_RETURN(TRUE); thd->lex->ssl_type= SSL_TYPE_NOT_SPECIFIED; @@ -7264,23 +10257,12 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, } -/***************************************************************************** - Instantiate used templates -*****************************************************************************/ - -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class List_iterator<LEX_COLUMN>; -template class List_iterator<LEX_USER>; -template class List<LEX_COLUMN>; -template class List<LEX_USER>; -#endif - /** Validate if a user can proxy as another user @thd current thread @param user the logged in user (proxy user) - @param authenticated_as the effective user a plugin is trying to + @param authenticated_as the effective user a plugin is trying to impersonate as (proxied user) @return proxy user definition @retval NULL proxy user definition not found or not applicable @@ -7288,7 +10270,7 @@ template class List<LEX_USER>; */ static ACL_PROXY_USER * -acl_find_proxy_user(const char *user, const char *host, const char *ip, +acl_find_proxy_user(const char *user, const char *host, const char *ip, const char *authenticated_as, bool *proxy_used) { uint i; @@ -7303,10 +10285,10 @@ acl_find_proxy_user(const char *user, const char *host, const char *ip, DBUG_RETURN (NULL); } - *proxy_used= TRUE; + *proxy_used= TRUE; for (i=0; i < acl_proxy_users.elements; i++) { - ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, + ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, ACL_PROXY_USER *); if (proxy->matches(host, user, ip, authenticated_as)) DBUG_RETURN(proxy); @@ -7321,7 +10303,7 @@ acl_check_proxy_grant_access(THD *thd, const char *host, const char *user, bool with_grant) { DBUG_ENTER("acl_check_proxy_grant_access"); - DBUG_PRINT("info", ("user=%s host=%s with_grant=%d", user, host, + DBUG_PRINT("info", ("user=%s host=%s with_grant=%d", user, host, (int) with_grant)); if (!initialized) { @@ -7352,7 +10334,7 @@ acl_check_proxy_grant_access(THD *thd, const char *host, const char *user, !my_strcasecmp(system_charset_info, host, thd->security_ctx->priv_host)) { - DBUG_PRINT("info", ("strcmp (%s, %s) my_casestrcmp (%s, %s) equal", + DBUG_PRINT("info", ("strcmp (%s, %s) my_casestrcmp (%s, %s) equal", thd->security_ctx->priv_user, user, host, thd->security_ctx->priv_host)); DBUG_RETURN(FALSE); @@ -7363,7 +10345,7 @@ acl_check_proxy_grant_access(THD *thd, const char *host, const char *user, /* check for matching WITH PROXY rights */ for (uint i=0; i < acl_proxy_users.elements; i++) { - ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, + ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, ACL_PROXY_USER *); if (proxy->matches(thd->security_ctx->host, thd->security_ctx->user, @@ -7386,7 +10368,8 @@ acl_check_proxy_grant_access(THD *thd, const char *host, const char *user, static bool -show_proxy_grants(THD *thd, LEX_USER *user, char *buff, size_t buffsize) +show_proxy_grants(THD *thd, const char *username, const char *hostname, + char *buff, size_t buffsize) { Protocol *protocol= thd->protocol; int error= 0; @@ -7395,7 +10378,7 @@ show_proxy_grants(THD *thd, LEX_USER *user, char *buff, size_t buffsize) { ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, ACL_PROXY_USER *); - if (proxy->granted_on(user->host.str, user->user.str)) + if (proxy->granted_on(hostname, username)) { String global(buff, buffsize, system_charset_info); global.length(0); @@ -7412,9 +10395,231 @@ show_proxy_grants(THD *thd, LEX_USER *user, char *buff, size_t buffsize) return error; } +static int enabled_roles_insert(ACL_USER_BASE *role, void *context_data) +{ + TABLE *table= (TABLE*) context_data; + DBUG_ASSERT(role->flags & IS_ROLE); + + restore_record(table, s->default_values); + table->field[0]->set_notnull(); + table->field[0]->store(role->user.str, role->user.length, + system_charset_info); + if (schema_table_store_record(table->in_use, table)) + return -1; + return 0; +} + +struct APPLICABLE_ROLES_DATA +{ + TABLE *table; + const LEX_STRING host; + const LEX_STRING user_and_host; + ACL_USER *user; +}; + +static int +applicable_roles_insert(ACL_USER_BASE *grantee, ACL_ROLE *role, void *ptr) +{ + APPLICABLE_ROLES_DATA *data= (APPLICABLE_ROLES_DATA *)ptr; + CHARSET_INFO *cs= system_charset_info; + TABLE *table= data->table; + bool is_role= grantee != data->user; + const LEX_STRING *user_and_host= is_role ? &grantee->user + : &data->user_and_host; + const LEX_STRING *host= is_role ? &empty_lex_str : &data->host; + + restore_record(table, s->default_values); + table->field[0]->store(user_and_host->str, user_and_host->length, cs); + table->field[1]->store(role->user.str, role->user.length, cs); + + ROLE_GRANT_PAIR *pair= + find_role_grant_pair(&grantee->user, host, &role->user); + DBUG_ASSERT(pair); + + if (pair->with_admin) + table->field[2]->store(STRING_WITH_LEN("YES"), cs); + else + table->field[2]->store(STRING_WITH_LEN("NO"), cs); + + /* Default role is only valid when looking at a role granted to a user. */ + if (!is_role) + { + if (data->user->default_rolename.length && + !strcmp(data->user->default_rolename.str, role->user.str)) + table->field[3]->store(STRING_WITH_LEN("YES"), cs); + else + table->field[3]->store(STRING_WITH_LEN("NO"), cs); + table->field[3]->set_notnull(); + } + + if (schema_table_store_record(table->in_use, table)) + return -1; + return 0; +} + +/** + Hash iterate function to count the number of total column privileges granted. +*/ +static my_bool count_column_grants(void *grant_table, + void *current_count) +{ + HASH hash_columns = ((GRANT_TABLE *)grant_table)->hash_columns; + *(ulong *)current_count+= hash_columns.records; + return 0; +} + +/** + SHOW function that computes the number of column grants. + + This must be performed under the mutex in order to make sure the + iteration does not fail. +*/ +static int show_column_grants(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) +{ + var->type= SHOW_ULONG; + var->value= buff; + *(ulong *)buff= 0; + if (initialized) + { + mysql_rwlock_rdlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); + my_hash_iterate(&column_priv_hash, count_column_grants, buff); + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); + } + return 0; +} + +static int show_database_grants(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) +{ + var->type= SHOW_UINT; + var->value= buff; + *(uint *)buff= acl_dbs.elements(); + return 0; +} +#else +bool check_grant(THD *, ulong, TABLE_LIST *, bool, uint, bool) +{ + return 0; +} #endif /*NO_EMBEDDED_ACCESS_CHECKS */ +SHOW_VAR acl_statistics[] = { +#ifndef NO_EMBEDDED_ACCESS_CHECKS + {"column_grants", (char*)show_column_grants, SHOW_SIMPLE_FUNC}, + {"database_grants", (char*)show_database_grants, SHOW_SIMPLE_FUNC}, + {"function_grants", (char*)&func_priv_hash.records, SHOW_ULONG}, + {"procedure_grants", (char*)&proc_priv_hash.records, SHOW_ULONG}, + {"proxy_users", (char*)&acl_proxy_users.elements, SHOW_UINT}, + {"role_grants", (char*)&acl_roles_mappings.records, SHOW_ULONG}, + {"roles", (char*)&acl_roles.records, SHOW_ULONG}, + {"table_grants", (char*)&column_priv_hash.records, SHOW_ULONG}, + {"users", (char*)&acl_users.elements, SHOW_UINT}, +#endif + {NullS, NullS, SHOW_LONG}, +}; + +/* Check if a role is granted to a user/role. We traverse the role graph + and return true if we find a match. + + hostname == NULL means we are looking for a role as a starting point, + otherwise a user. +*/ +bool check_role_is_granted(const char *username, + const char *hostname, + const char *rolename) +{ + DBUG_ENTER("check_role_is_granted"); + bool result= false; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + ACL_USER_BASE *root; + mysql_mutex_lock(&acl_cache->lock); + if (hostname) + root= find_user_exact(username, hostname); + else + root= find_acl_role(username); + + LEX_CSTRING role_lex; + role_lex.str= rolename; + role_lex.length= strlen(rolename); + + if (root && /* No grantee, nothing to search. */ + traverse_role_graph_down(root, &role_lex, check_role_is_granted_callback, + NULL) == -1) + { + /* We have found the role during our search. */ + result= true; + } + + /* We haven't found the role or we had no initial grantee to start from. */ + mysql_mutex_unlock(&acl_cache->lock); +#endif + DBUG_RETURN(result); +} + +int fill_schema_enabled_roles(THD *thd, TABLE_LIST *tables, COND *cond) +{ + TABLE *table= tables->table; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (thd->security_ctx->priv_role[0]) + { + mysql_rwlock_rdlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); + ACL_ROLE *acl_role= find_acl_role(thd->security_ctx->priv_role); + if (acl_role) + traverse_role_graph_down(acl_role, table, enabled_roles_insert, NULL); + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); + if (acl_role) + return 0; + } +#endif + + restore_record(table, s->default_values); + table->field[0]->set_null(); + return schema_table_store_record(table->in_use, table); +} + + +/* + This shows all roles granted to current user + and recursively all roles granted to those roles +*/ +int fill_schema_applicable_roles(THD *thd, TABLE_LIST *tables, COND *cond) +{ + int res= 0; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (initialized) + { + TABLE *table= tables->table; + Security_context *sctx= thd->security_ctx; + mysql_rwlock_rdlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); + ACL_USER *user= find_user_exact(sctx->priv_host, sctx->priv_user); + if (user) + { + char buff[USER_HOST_BUFF_SIZE+10]; + DBUG_ASSERT(user->user.length + user->hostname_length +2 < sizeof(buff)); + char *end= strxmov(buff, user->user.str, "@", user->host.hostname, NULL); + APPLICABLE_ROLES_DATA data= { table, + { user->host.hostname, user->hostname_length }, + { buff, (size_t)(end - buff) }, user + }; + + res= traverse_role_graph_down(user, &data, 0, applicable_roles_insert); + } + + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); + } +#endif + + return res; +} + int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr) { @@ -7507,16 +10712,14 @@ int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond) { const char *user,*host, *is_grantable="YES"; acl_user=dynamic_element(&acl_users,counter,ACL_USER*); - if (!(user=acl_user->user)) - user= ""; - if (!(host=acl_user->host.hostname)) - host= ""; + user= safe_str(acl_user->user.str); + host= safe_str(acl_user->host.hostname); if (no_global_access && (strcmp(thd->security_ctx->priv_user, user) || my_strcasecmp(system_charset_info, curr_host, host))) continue; - + want_access= acl_user->access; if (!(want_access & GRANT_ACL)) is_grantable= "NO"; @@ -7539,7 +10742,7 @@ int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond) { if (test_access & j) { - if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0, + if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0, command_array[priv_id], command_lengths[priv_id], is_grantable)) { @@ -7578,15 +10781,13 @@ int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond) DBUG_RETURN(0); mysql_mutex_lock(&acl_cache->lock); - for (counter=0 ; counter < acl_dbs.elements ; counter++) + for (counter=0 ; counter < acl_dbs.elements() ; counter++) { const char *user, *host, *is_grantable="YES"; - acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); - if (!(user=acl_db->user)) - user= ""; - if (!(host=acl_db->host.hostname)) - host= ""; + acl_db=&acl_dbs.at(counter); + user= safe_str(acl_db->user); + host= safe_str(acl_db->host.hostname); if (no_global_access && (strcmp(thd->security_ctx->priv_user, user) || @@ -7656,11 +10857,9 @@ int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond) { const char *user, *host, *is_grantable= "YES"; GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, - index); - if (!(user=grant_table->user)) - user= ""; - if (!(host= grant_table->host.hostname)) - host= ""; + index); + user= safe_str(grant_table->user); + host= safe_str(grant_table->host.hostname); if (no_global_access && (strcmp(thd->security_ctx->priv_user, user) || @@ -7710,7 +10909,7 @@ int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond) } } } - } + } } err: mysql_rwlock_unlock(&LOCK_grant); @@ -7740,11 +10939,9 @@ int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond) { const char *user, *host, *is_grantable= "YES"; GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, - index); - if (!(user=grant_table->user)) - user= ""; - if (!(host= grant_table->host.hostname)) - host= ""; + index); + user= safe_str(grant_table->user); + host= safe_str(grant_table->host.hostname); if (no_global_access && (strcmp(thd->security_ctx->priv_user, user) || @@ -7822,9 +11019,7 @@ void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant, Security_context *sctx= thd->security_ctx; DBUG_ENTER("fill_effective_table_privileges"); DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', table: `%s`.`%s`", - sctx->priv_host, (sctx->ip ? sctx->ip : "(NULL)"), - (sctx->priv_user ? sctx->priv_user : "(NULL)"), - db, table)); + sctx->priv_host, sctx->ip, sctx->priv_user, db, table)); /* --skip-grants */ if (!initialized) { @@ -7837,28 +11032,40 @@ void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant, /* global privileges */ grant->privilege= sctx->master_access; - if (!sctx->priv_user) + if (!thd->db || strcmp(db, thd->db)) { - DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege)); - DBUG_VOID_RETURN; // it is slave + /* db privileges */ + grant->privilege|= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, 0); + /* db privileges for role */ + if (sctx->priv_role[0]) + grant->privilege|= acl_get("", "", sctx->priv_role, db, 0); + } + else + { + grant->privilege|= sctx->db_access; } - - /* db privileges */ - grant->privilege|= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, 0); /* table privileges */ mysql_rwlock_rdlock(&LOCK_grant); if (grant->version != grant_version) { - grant->grant_table= + grant->grant_table_user= table_hash_search(sctx->host, sctx->ip, db, - sctx->priv_user, - table, 0); /* purecov: inspected */ + sctx->priv_user, + table, 0); /* purecov: inspected */ + grant->grant_table_role= + sctx->priv_role[0] ? table_hash_search("", "", db, + sctx->priv_role, + table, TRUE) : NULL; grant->version= grant_version; /* purecov: inspected */ } - if (grant->grant_table != 0) + if (grant->grant_table_user != 0) { - grant->privilege|= grant->grant_table->privs; + grant->privilege|= grant->grant_table_user->privs; + } + if (grant->grant_table_role != 0) + { + grant->privilege|= grant->grant_table_role->privs; } mysql_rwlock_unlock(&LOCK_grant); @@ -7880,6 +11087,60 @@ bool check_routine_level_acl(THD *thd, const char *db, const char *name, #endif +/** + Return information about user or current user. + + @param[in] thd thread handler + @param[in] user user + @param[in] lock whether &acl_cache->lock mutex needs to be locked + + @return + - On success, return a valid pointer to initialized + LEX_USER, which contains user information. + - On error, return 0. +*/ + +LEX_USER *get_current_user(THD *thd, LEX_USER *user, bool lock) +{ + if (user->user.str == current_user.str) // current_user + return create_default_definer(thd, false); + + if (user->user.str == current_role.str) // current_role + return create_default_definer(thd, true); + + if (user->host.str == NULL) // Possibly a role + { + // to be reexecution friendly we have to make a copy + LEX_USER *dup= (LEX_USER*) thd->memdup(user, sizeof(*user)); + if (!dup) + return 0; + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (has_auth(user, thd->lex)) + { + dup->host= host_not_specified; + return dup; + } + + if (is_invalid_role_name(user->user.str)) + return 0; + + if (lock) + mysql_mutex_lock(&acl_cache->lock); + if (find_acl_role(dup->user.str)) + dup->host= empty_lex_str; + else + dup->host= host_not_specified; + if (lock) + mysql_mutex_unlock(&acl_cache->lock); +#endif + + return dup; + } + + return user; +} + struct ACL_internal_schema_registry_entry { const LEX_STRING *m_name; @@ -8008,7 +11269,6 @@ get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info, struct MPVIO_EXT :public MYSQL_PLUGIN_VIO { MYSQL_SERVER_AUTH_INFO auth_info; - THD *thd; ACL_USER *acl_user; ///< a copy, independent from acl_users array plugin_ref plugin; ///< what plugin we're under LEX_STRING db; ///< db name from the handshake packet @@ -8023,7 +11283,6 @@ struct MPVIO_EXT :public MYSQL_PLUGIN_VIO uint pkt_len; } cached_server_packet; int packets_read, packets_written; ///< counters for send/received packets - uint connect_errors; ///< if there were connect errors for this host bool make_it_fail; /** when plugin returns a failure this tells us what really happened */ enum { SUCCESS, FAILURE, RESTART } status; @@ -8037,24 +11296,24 @@ static void login_failed_error(THD *thd) my_error(access_denied_error_code(thd->password), MYF(0), thd->main_security_ctx.user, thd->main_security_ctx.host_or_ip, - thd->password ? ER(ER_YES) : ER(ER_NO)); + thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO)); general_log_print(thd, COM_CONNECT, - ER(access_denied_error_code(thd->password)), + ER_THD(thd, access_denied_error_code(thd->password)), thd->main_security_ctx.user, thd->main_security_ctx.host_or_ip, - thd->password ? ER(ER_YES) : ER(ER_NO)); + thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO)); status_var_increment(thd->status_var.access_denied_errors); - /* + /* Log access denied messages to the error log when log-warnings = 2 - so that the overhead of the general query log is not required to track + so that the overhead of the general query log is not required to track failed connections. */ if (global_system_variables.log_warnings > 1) { - sql_print_warning(ER(access_denied_error_code(thd->password)), + sql_print_warning(ER_THD(thd, access_denied_error_code(thd->password)), thd->main_security_ctx.user, thd->main_security_ctx.host_or_ip, - thd->password ? ER(ER_YES) : ER(ER_NO)); + thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO)); } } @@ -8063,7 +11322,7 @@ static void login_failed_error(THD *thd) after the connection was established Packet format: - + Bytes Content ----- ---- 1 protocol version (always 10) @@ -8089,7 +11348,7 @@ static bool send_server_handshake_packet(MPVIO_EXT *mpvio, DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE); DBUG_ASSERT(data_len <= 255); - THD *thd= mpvio->thd; + THD *thd= mpvio->auth_info.thd; char *buff= (char *) my_alloca(1 + SERVER_VERSION_LENGTH + 1 + data_len + 64); char scramble_buf[SCRAMBLE_LENGTH]; char *end= buff; @@ -8139,14 +11398,14 @@ static bool send_server_handshake_packet(MPVIO_EXT *mpvio, native_password_plugin will have to send it in a separate packet, adding one more round trip. */ - create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand); + thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH); data= thd->scramble; } data_len= SCRAMBLE_LENGTH; } - end= strnmov(end, server_version, SERVER_VERSION_LENGTH) + 1; - int4store((uchar*) end, mpvio->thd->thread_id); + end= strxnmov(end, SERVER_VERSION_LENGTH, RPL_VERSION_HACK, server_version, NullS) + 1; + int4store((uchar*) end, mpvio->auth_info.thd->thread_id); end+= 4; /* @@ -8157,11 +11416,11 @@ static bool send_server_handshake_packet(MPVIO_EXT *mpvio, end= (char*) memcpy(end, data, SCRAMBLE_LENGTH_323); end+= SCRAMBLE_LENGTH_323; *end++= 0; - + int2store(end, thd->client_capabilities); /* write server characteristics: up to 16 bytes allowed */ end[2]= (char) default_charset_info->number; - int2store(end+3, mpvio->thd->server_status); + int2store(end+3, mpvio->auth_info.thd->server_status); int2store(end+5, thd->client_capabilities >> 16); end[7]= data_len; DBUG_EXECUTE_IF("poison_srv_handshake_scramble_len", end[7]= -100;); @@ -8174,9 +11433,9 @@ static bool send_server_handshake_packet(MPVIO_EXT *mpvio, end= strmake(end, plugin_name(mpvio->plugin)->str, plugin_name(mpvio->plugin)->length); - int res= my_net_write(&mpvio->thd->net, (uchar*) buff, + int res= my_net_write(&mpvio->auth_info.thd->net, (uchar*) buff, (size_t) (end - buff + 1)) || - net_flush(&mpvio->thd->net); + net_flush(&mpvio->auth_info.thd->net); my_afree(buff); DBUG_RETURN (res); } @@ -8187,7 +11446,7 @@ static bool secure_auth(THD *thd) return 0; /* - If the server is running in secure auth mode, short scrambles are + If the server is running in secure auth mode, short scrambles are forbidden. Extra juggling to report the same error as the old code. */ if (thd->client_capabilities & CLIENT_PROTOCOL_41) @@ -8195,14 +11454,16 @@ static bool secure_auth(THD *thd) my_error(ER_SERVER_IS_IN_SECURE_AUTH_MODE, MYF(0), thd->security_ctx->user, thd->security_ctx->host_or_ip); - general_log_print(thd, COM_CONNECT, ER(ER_SERVER_IS_IN_SECURE_AUTH_MODE), + general_log_print(thd, COM_CONNECT, + ER_THD(thd, ER_SERVER_IS_IN_SECURE_AUTH_MODE), thd->security_ctx->user, thd->security_ctx->host_or_ip); } else { my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); - general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + general_log_print(thd, COM_CONNECT, + ER_THD(thd, ER_NOT_SUPPORTED_AUTH_MODE)); } return 1; } @@ -8212,7 +11473,7 @@ static bool secure_auth(THD *thd) using a different authentication plugin Packet format: - + Bytes Content ----- ---- 1 byte with the value 254 @@ -8233,7 +11494,7 @@ static bool send_plugin_request_packet(MPVIO_EXT *mpvio, { DBUG_ASSERT(mpvio->packets_written == 1); DBUG_ASSERT(mpvio->packets_read == 1); - NET *net= &mpvio->thd->net; + NET *net= &mpvio->auth_info.thd->net; static uchar switch_plugin_request_buf[]= { 254 }; DBUG_ENTER("send_plugin_request_packet"); @@ -8259,7 +11520,7 @@ static bool send_plugin_request_packet(MPVIO_EXT *mpvio, client_auth_plugin == old_password_plugin_name.str; if (switch_from_long_to_short_scramble) - DBUG_RETURN (secure_auth(mpvio->thd) || + DBUG_RETURN (secure_auth(mpvio->auth_info.thd) || my_net_write(net, switch_plugin_request_buf, 1) || net_flush(net)); @@ -8275,11 +11536,12 @@ static bool send_plugin_request_packet(MPVIO_EXT *mpvio, if (switch_from_short_to_long_scramble) { my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); - general_log_print(mpvio->thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + general_log_print(mpvio->auth_info.thd, COM_CONNECT, + ER_THD(mpvio->auth_info.thd, ER_NOT_SUPPORTED_AUTH_MODE)); DBUG_RETURN (1); } - DBUG_PRINT("info", ("requesting client to use the %s plugin", + DBUG_PRINT("info", ("requesting client to use the %s plugin", client_auth_plugin)); DBUG_RETURN(net_write_command(net, switch_plugin_request_buf[0], (uchar*) client_auth_plugin, @@ -8290,33 +11552,25 @@ static bool send_plugin_request_packet(MPVIO_EXT *mpvio, #ifndef NO_EMBEDDED_ACCESS_CHECKS /** Finds acl entry in user database for authentication purposes. - + Finds a user and copies it into mpvio. Creates a fake user if no matching user account is found. - @note find_acl_user is not the same, because it doesn't take into - account the case when user is not empty, but acl_user->user is empty - @retval 0 found @retval 1 error */ static bool find_mpvio_user(MPVIO_EXT *mpvio) { - Security_context *sctx= mpvio->thd->security_ctx; + Security_context *sctx= mpvio->auth_info.thd->security_ctx; DBUG_ENTER("find_mpvio_user"); DBUG_ASSERT(mpvio->acl_user == 0); mysql_mutex_lock(&acl_cache->lock); - for (uint i=0; i < acl_users.elements; i++) - { - ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*); - if ((!acl_user_tmp->user || !strcmp(sctx->user, acl_user_tmp->user)) && - compare_hostname(&acl_user_tmp->host, sctx->host, sctx->ip)) - { - mpvio->acl_user= acl_user_tmp->copy(mpvio->thd->mem_root); - break; - } - } + + ACL_USER *user= find_user_or_anon(sctx->host, sctx->user, sctx->ip); + if (user) + mpvio->acl_user= user->copy(mpvio->auth_info.thd->mem_root); + mysql_mutex_unlock(&acl_cache->lock); if (!mpvio->acl_user) @@ -8339,12 +11593,12 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) if (!acl_users.elements) { mysql_mutex_unlock(&acl_cache->lock); - login_failed_error(mpvio->thd); + login_failed_error(mpvio->auth_info.thd); DBUG_RETURN(1); } uint i= nr1 % acl_users.elements; ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*); - mpvio->acl_user= acl_user_tmp->copy(mpvio->thd->mem_root); + mpvio->acl_user= acl_user_tmp->copy(mpvio->auth_info.thd->mem_root); mysql_mutex_unlock(&acl_cache->lock); mpvio->make_it_fail= true; @@ -8353,14 +11607,15 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) /* user account requires non-default plugin and the client is too old */ if (mpvio->acl_user->plugin.str != native_password_plugin_name.str && mpvio->acl_user->plugin.str != old_password_plugin_name.str && - !(mpvio->thd->client_capabilities & CLIENT_PLUGIN_AUTH)) + !(mpvio->auth_info.thd->client_capabilities & CLIENT_PLUGIN_AUTH)) { DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str, native_password_plugin_name.str)); DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str, old_password_plugin_name.str)); my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); - general_log_print(mpvio->thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + general_log_print(mpvio->auth_info.thd, COM_CONNECT, + ER_THD(mpvio->auth_info.thd, ER_NOT_SUPPORTED_AUTH_MODE)); DBUG_RETURN (1); } @@ -8368,8 +11623,7 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) mpvio->auth_info.user_name_length= strlen(sctx->user); mpvio->auth_info.auth_string= mpvio->acl_user->auth_string.str; mpvio->auth_info.auth_string_length= (unsigned long) mpvio->acl_user->auth_string.length; - strmake_buf(mpvio->auth_info.authenticated_as, mpvio->acl_user->user ? - mpvio->acl_user->user : ""); + strmake_buf(mpvio->auth_info.authenticated_as, safe_str(mpvio->acl_user->user.str)); DBUG_PRINT("info", ("exit: user=%s, auth_string=%s, authenticated as=%s" "plugin=%s", @@ -8379,12 +11633,46 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) mpvio->acl_user->plugin.str)); DBUG_RETURN(0); } + +static bool +read_client_connect_attrs(char **ptr, char *end, CHARSET_INFO *from_cs) +{ + size_t length; + char *ptr_save= *ptr; + + /* not enough bytes to hold the length */ + if (ptr_save >= end) + return true; + + length= safe_net_field_length_ll((uchar **) ptr, end - ptr_save); + + /* cannot even read the length */ + if (*ptr == NULL) + return true; + + /* length says there're more data than can fit into the packet */ + if (*ptr + length > end) + return true; + + /* impose an artificial length limit of 64k */ + if (length > 65535) + return true; + +#ifdef HAVE_PSI_THREAD_INTERFACE + if (PSI_THREAD_CALL(set_thread_connect_attrs)(*ptr, length, from_cs) && + current_thd->variables.log_warnings) + sql_print_warning("Connection attributes of length %lu were truncated", + (unsigned long) length); +#endif + return false; +} + #endif /* the packet format is described in send_change_user_packet() */ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) { - THD *thd= mpvio->thd; + THD *thd= mpvio->auth_info.thd; NET *net= &thd->net; Security_context *sctx= thd->security_ctx; @@ -8401,7 +11689,8 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) if (passwd >= end) { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), + MYF(0)); DBUG_RETURN (1); } @@ -8425,19 +11714,21 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) */ if (db >= end) { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), + MYF(0)); DBUG_RETURN (1); } uint db_len= strlen(db); - char *ptr= db + db_len + 1; + char *next_field= db + db_len + 1; - if (ptr + 1 < end) + if (next_field + 1 < end) { - if (thd_init_client_charset(thd, uint2korr(ptr))) + if (thd_init_client_charset(thd, uint2korr(next_field))) DBUG_RETURN(1); thd->update_charset(); + next_field+= 2; } /* Convert database and user names to utf8 */ @@ -8455,7 +11746,7 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) thd->user_connect= 0; strmake_buf(sctx->priv_user, sctx->user); - if (thd->make_lex_string(&mpvio->db, db_buff, db_len, 0) == 0) + if (thd->make_lex_string(&mpvio->db, db_buff, db_len) == 0) DBUG_RETURN(1); /* The error is set by make_lex_string(). */ /* @@ -8480,13 +11771,14 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) char *client_plugin; if (thd->client_capabilities & CLIENT_PLUGIN_AUTH) { - client_plugin= ptr + 2; - if (client_plugin >= end) + if (next_field >= end) { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), + MYF(0)); DBUG_RETURN(1); } - client_plugin= fix_plugin_ptr(client_plugin); + client_plugin= fix_plugin_ptr(next_field); + next_field+= strlen(next_field) + 1; } else { @@ -8498,7 +11790,7 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) /* For a passwordless accounts we use native_password_plugin. But when an old 4.0 client connects to it, we change it to - old_password_plugin, otherwise MySQL will think that server + old_password_plugin, otherwise MySQL will think that server and client plugins don't match. */ if (mpvio->acl_user->auth_string.length == 0) @@ -8506,10 +11798,19 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) } } + if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) && + read_client_connect_attrs(&next_field, end, + thd->charset())) + { + my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), + MYF(0)); + DBUG_RETURN(packet_error); + } + DBUG_PRINT("info", ("client_plugin=%s, restart", client_plugin)); - /* - Remember the data part of the packet, to present it to plugin in - read_packet() + /* + Remember the data part of the packet, to present it to plugin in + read_packet() */ mpvio->cached_client_reply.pkt= passwd; mpvio->cached_client_reply.pkt_len= passwd_len; @@ -8526,7 +11827,7 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, uchar **buff, ulong pkt_len) { #ifndef EMBEDDED_LIBRARY - THD *thd= mpvio->thd; + THD *thd= mpvio->auth_info.thd; NET *net= &thd->net; char *end; DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE); @@ -8540,9 +11841,6 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, */ DBUG_ASSERT(net->read_pos[pkt_len] == 0); - if (mpvio->connect_errors) - reset_host_errors(thd->main_security_ctx.ip); - ulong client_capabilities= uint2korr(net->read_pos); if (client_capabilities & CLIENT_PROTOCOL_41) { @@ -8618,7 +11916,6 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, char *passwd= strend(user)+1; uint user_len= passwd - user - 1, db_len; char *db= passwd; - char db_buff[SAFE_NAME_LEN + 1]; // buffer to store db in utf8 char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 uint dummy_errors; @@ -8632,27 +11929,32 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, Cast *passwd to an unsigned char, so that it doesn't extend the sign for *passwd > 127 and become 2**32-127+ after casting to uint. */ - uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ? - (uchar)(*passwd++) : strlen(passwd); - + uint passwd_len; + if (!(thd->client_capabilities & CLIENT_SECURE_CONNECTION)) + passwd_len= strlen(passwd); + else if (!(thd->client_capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA)) + passwd_len= (uchar)(*passwd++); + else + passwd_len= safe_net_field_length_ll((uchar**)&passwd, + net->read_pos + pkt_len - (uchar*)passwd); + db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ? db + passwd_len + 1 : 0; - if (passwd + passwd_len + test(db) > (char *)net->read_pos + pkt_len) + if (passwd == NULL || + passwd + passwd_len + MY_TEST(db) > (char*) net->read_pos + pkt_len) return packet_error; /* strlen() can't be easily deleted without changing protocol */ - db_len= db ? strlen(db) : 0; + db_len= safe_strlen(db); - char *client_plugin= passwd + passwd_len + (db ? db_len + 1 : 0); + char *next_field; + char *client_plugin= next_field= passwd + passwd_len + (db ? db_len + 1 : 0); /* Since 4.1 all database names are stored in utf8 */ - if (db) - { - db_len= copy_and_convert(db_buff, sizeof(db_buff) - 1, system_charset_info, - db, db_len, thd->charset(), &dummy_errors); - db= db_buff; - } + if (thd->copy_with_error(system_charset_info, &mpvio->db, + thd->charset(), db, db_len)) + return packet_error; user_len= copy_and_convert(user_buff, sizeof(user_buff) - 1, system_charset_info, user, user_len, @@ -8682,8 +11984,6 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, Security_context *sctx= thd->security_ctx; - if (thd->make_lex_string(&mpvio->db, db, db_len, 0) == 0) - return packet_error; /* The error is set by make_lex_string(). */ my_free(sctx->user); if (!(sctx->user= my_strndup(user, user_len, MYF(MY_WME)))) return packet_error; /* The error is set by my_strdup(). */ @@ -8711,6 +12011,7 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, (client_plugin < (char *)net->read_pos + pkt_len)) { client_plugin= fix_plugin_ptr(client_plugin); + next_field+= strlen(next_field) + 1; } else { @@ -8725,14 +12026,19 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, /* For a passwordless accounts we use native_password_plugin. But when an old 4.0 client connects to it, we change it to - old_password_plugin, otherwise MySQL will think that server + old_password_plugin, otherwise MySQL will think that server and client plugins don't match. */ if (mpvio->acl_user->auth_string.length == 0) mpvio->acl_user->plugin= old_password_plugin_name; } } - + + if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) && + read_client_connect_attrs(&next_field, ((char *)net->read_pos) + pkt_len, + mpvio->auth_info.thd->charset())) + return packet_error; + /* if the acl_user needs a different plugin to authenticate (specified in GRANT ... AUTHENTICATED VIA plugin_name ..) @@ -8815,13 +12121,13 @@ static int server_mpvio_write_packet(MYSQL_PLUGIN_VIO *param, We'll escape these bytes with \1. Consequently, we have to escape \1 byte too. */ - res= net_write_command(&mpvio->thd->net, 1, (uchar*)"", 0, + res= net_write_command(&mpvio->auth_info.thd->net, 1, (uchar*)"", 0, packet, packet_len); } else { - res= my_net_write(&mpvio->thd->net, packet, packet_len) || - net_flush(&mpvio->thd->net); + res= my_net_write(&mpvio->auth_info.thd->net, packet, packet_len) || + net_flush(&mpvio->auth_info.thd->net); } mpvio->packets_written++; DBUG_RETURN(res); @@ -8851,7 +12157,7 @@ static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) if (server_mpvio_write_packet(mpvio, 0, 0)) pkt_len= packet_error; else - pkt_len= my_net_read(&mpvio->thd->net); + pkt_len= my_net_read(&mpvio->auth_info.thd->net); } else if (mpvio->cached_client_reply.pkt) { @@ -8874,9 +12180,6 @@ static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) mpvio->cached_client_reply.pkt= 0; mpvio->packets_read++; - if (mpvio->make_it_fail) - goto err; - DBUG_RETURN ((int) mpvio->cached_client_reply.pkt_len); } @@ -8888,10 +12191,10 @@ static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) if (server_mpvio_write_packet(mpvio, 0, 0)) pkt_len= packet_error; else - pkt_len= my_net_read(&mpvio->thd->net); + pkt_len= my_net_read(&mpvio->auth_info.thd->net); } else - pkt_len= my_net_read(&mpvio->thd->net); + pkt_len= my_net_read(&mpvio->auth_info.thd->net); if (pkt_len == packet_error) goto err; @@ -8909,24 +12212,15 @@ static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) goto err; } else - *buf= mpvio->thd->net.read_pos; - - if (mpvio->make_it_fail) - goto err; + *buf= mpvio->auth_info.thd->net.read_pos; DBUG_RETURN((int)pkt_len); err: if (mpvio->status == MPVIO_EXT::FAILURE) { - inc_host_errors(mpvio->thd->security_ctx->ip); - if (!mpvio->thd->is_error()) - { - if (mpvio->make_it_fail) - login_failed_error(mpvio->thd); - else - my_error(ER_HANDSHAKE_ERROR, MYF(0)); - } + if (!mpvio->auth_info.thd->is_error()) + my_error(ER_HANDSHAKE_ERROR, MYF(0)); } DBUG_RETURN(-1); } @@ -8939,7 +12233,7 @@ static void server_mpvio_info(MYSQL_PLUGIN_VIO *vio, MYSQL_PLUGIN_VIO_INFO *info) { MPVIO_EXT *mpvio= (MPVIO_EXT *) vio; - mpvio_info(mpvio->thd->net.vio, info); + mpvio_info(mpvio->auth_info.thd->net.vio, info); } static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) @@ -8996,6 +12290,9 @@ static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) return 1; } } + if (!acl_user->x509_issuer && !acl_user->x509_subject) + return 0; // all done + /* Prepare certificate (if exists) */ if (!(cert= SSL_get_peer_certificate(ssl))) return 1; @@ -9038,7 +12335,7 @@ static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) #else /* HAVE_OPENSSL */ default: /* - If we don't have SSL but SSL is required for this user the + If we don't have SSL but SSL is required for this user the authentication should fail. */ return 1; @@ -9071,11 +12368,11 @@ static int do_auth_once(THD *thd, const LEX_STRING *auth_plugin_name, if (plugin) { st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info; - switch (auth->interface_version) { - case 0x0200: + switch (auth->interface_version >> 8) { + case 0x02: res= auth->authenticate_user(mpvio, &mpvio->auth_info); break; - case 0x0100: + case 0x01: { MYSQL_SERVER_AUTH_INFO_0x0100 compat; compat.downgrade(&mpvio->auth_info); @@ -9092,6 +12389,9 @@ static int do_auth_once(THD *thd, const LEX_STRING *auth_plugin_name, else { /* Server cannot load the required plugin. */ + Host_errors errors; + errors.m_no_auth_plugin= 1; + inc_host_errors(mpvio->auth_info.thd->security_ctx->ip, &errors); my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), auth_plugin_name->str); res= CR_ERROR; } @@ -9115,8 +12415,6 @@ static int do_auth_once(THD *thd, const LEX_STRING *auth_plugin_name, Perform the handshake, authorize the client and update thd sctx variables. @param thd thread handle - @param connect_errors number of previous failed connect attemps - from this host @param com_change_user_pkt_len size of the COM_CHANGE_USER packet (without the first, command, byte) or 0 if it's not a COM_CHANGE_USER (that is, if @@ -9125,8 +12423,7 @@ static int do_auth_once(THD *thd, const LEX_STRING *auth_plugin_name, @retval 0 success, thd is updated. @retval 1 error */ -bool acl_authenticate(THD *thd, uint connect_errors, - uint com_change_user_pkt_len) +bool acl_authenticate(THD *thd, uint com_change_user_pkt_len) { int res= CR_OK; MPVIO_EXT mpvio; @@ -9139,12 +12436,11 @@ bool acl_authenticate(THD *thd, uint connect_errors, mpvio.read_packet= server_mpvio_read_packet; mpvio.write_packet= server_mpvio_write_packet; mpvio.info= server_mpvio_info; - mpvio.thd= thd; - mpvio.connect_errors= connect_errors; mpvio.status= MPVIO_EXT::FAILURE; mpvio.make_it_fail= false; + mpvio.auth_info.thd= thd; mpvio.auth_info.host_or_ip= thd->security_ctx->host_or_ip; - mpvio.auth_info.host_or_ip_length= + mpvio.auth_info.host_or_ip_length= (unsigned int) strlen(thd->security_ctx->host_or_ip); DBUG_PRINT("info", ("com_change_user_pkt_len=%u", com_change_user_pkt_len)); @@ -9172,7 +12468,7 @@ bool acl_authenticate(THD *thd, uint connect_errors, the correct plugin. */ - res= do_auth_once(thd, auth_plugin_name, &mpvio); + res= do_auth_once(thd, auth_plugin_name, &mpvio); } /* @@ -9188,7 +12484,7 @@ bool acl_authenticate(THD *thd, uint connect_errors, auth_plugin_name= &mpvio.acl_user->plugin; res= do_auth_once(thd, auth_plugin_name, &mpvio); } - if (mpvio.make_it_fail) + if (mpvio.make_it_fail && res == CR_OK) { mpvio.status= MPVIO_EXT::FAILURE; res= CR_ERROR; @@ -9197,7 +12493,7 @@ bool acl_authenticate(THD *thd, uint connect_errors, Security_context *sctx= thd->security_ctx; const ACL_USER *acl_user= mpvio.acl_user; - thd->password= mpvio.auth_info.password_used; // remember for error messages + thd->password= mpvio.auth_info.password_used; // remember for error messages /* Log the command here so that the user can check the log @@ -9212,18 +12508,36 @@ bool acl_authenticate(THD *thd, uint connect_errors, general_log_print(thd, command, "%s@%s as %s on %s", sctx->user, sctx->host_or_ip, sctx->priv_user[0] ? sctx->priv_user : "anonymous", - mpvio.db.str ? mpvio.db.str : (char*) ""); + safe_str(mpvio.db.str)); } else general_log_print(thd, command, (char*) "%s@%s on %s", sctx->user, sctx->host_or_ip, - mpvio.db.str ? mpvio.db.str : (char*) ""); + safe_str(mpvio.db.str)); } if (res > CR_OK && mpvio.status != MPVIO_EXT::SUCCESS) { + Host_errors errors; DBUG_ASSERT(mpvio.status == MPVIO_EXT::FAILURE); - + switch (res) + { + case CR_AUTH_PLUGIN_ERROR: + errors.m_auth_plugin= 1; + break; + case CR_AUTH_HANDSHAKE: + errors.m_handshake= 1; + break; + case CR_AUTH_USER_CREDENTIALS: + errors.m_authentication= 1; + break; + case CR_ERROR: + default: + /* Unknown of unspecified auth plugin error. */ + errors.m_auth_plugin= 1; + break; + } + inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors); if (!thd->is_error()) login_failed_error(thd); DBUG_RETURN(1); @@ -9235,7 +12549,7 @@ bool acl_authenticate(THD *thd, uint connect_errors, { #ifndef NO_EMBEDDED_ACCESS_CHECKS bool is_proxy_user= FALSE; - const char *auth_user = acl_user->user ? acl_user->user : ""; + const char *auth_user = safe_str(acl_user->user.str); ACL_PROXY_USER *proxy_user; /* check if the user is allowed to proxy as another user */ proxy_user= acl_find_proxy_user(auth_user, sctx->host, sctx->ip, @@ -9248,6 +12562,9 @@ bool acl_authenticate(THD *thd, uint connect_errors, /* we need to find the proxy user, but there was none */ if (!proxy_user) { + Host_errors errors; + errors.m_proxy_user= 1; + inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors); if (!thd->is_error()) login_failed_error(thd); DBUG_RETURN(1); @@ -9255,16 +12572,19 @@ bool acl_authenticate(THD *thd, uint connect_errors, my_snprintf(sctx->proxy_user, sizeof(sctx->proxy_user) - 1, "'%s'@'%s'", auth_user, - acl_user->host.hostname ? acl_user->host.hostname : ""); + safe_str(acl_user->host.hostname)); /* we're proxying : find the proxy user definition */ mysql_mutex_lock(&acl_cache->lock); - acl_proxy_user= find_acl_user(proxy_user->get_proxied_host() ? - proxy_user->get_proxied_host() : "", - mpvio.auth_info.authenticated_as, TRUE); + acl_proxy_user= find_user_exact(safe_str(proxy_user->get_proxied_host()), + mpvio.auth_info.authenticated_as); if (!acl_proxy_user) { mysql_mutex_unlock(&acl_cache->lock); + + Host_errors errors; + errors.m_proxy_user_acl= 1; + inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors); if (!thd->is_error()) login_failed_error(thd); DBUG_RETURN(1); @@ -9275,8 +12595,8 @@ bool acl_authenticate(THD *thd, uint connect_errors, #endif sctx->master_access= acl_user->access; - if (acl_user->user) - strmake_buf(sctx->priv_user, acl_user->user); + if (acl_user->user.str) + strmake_buf(sctx->priv_user, acl_user->user.str); else *sctx->priv_user= 0; @@ -9292,6 +12612,9 @@ bool acl_authenticate(THD *thd, uint connect_errors, */ if (acl_check_ssl(thd, acl_user)) { + Host_errors errors; + errors.m_ssl= 1; + inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors); login_failed_error(thd); DBUG_RETURN(1); } @@ -9305,12 +12628,22 @@ bool acl_authenticate(THD *thd, uint connect_errors, if ((acl_user->user_resource.questions || acl_user->user_resource.updates || acl_user->user_resource.conn_per_hour || - acl_user->user_resource.user_conn || max_user_connections_checking) && - get_or_create_user_conn(thd, - (opt_old_style_user_limits ? sctx->user : sctx->priv_user), - (opt_old_style_user_limits ? sctx->host_or_ip : sctx->priv_host), - &acl_user->user_resource)) + acl_user->user_resource.user_conn || + acl_user->user_resource.max_statement_time != 0.0 || + max_user_connections_checking) && + get_or_create_user_conn(thd, + (opt_old_style_user_limits ? sctx->user : sctx->priv_user), + (opt_old_style_user_limits ? sctx->host_or_ip : sctx->priv_host), + &acl_user->user_resource)) DBUG_RETURN(1); // The error is set by get_or_create_user_conn() + + if (acl_user->user_resource.max_statement_time != 0.0) + { + thd->variables.max_statement_time_double= + acl_user->user_resource.max_statement_time; + thd->variables.max_statement_time= + (ulonglong) (thd->variables.max_statement_time_double * 1e6 + 0.1); + } } else sctx->skip_grants(); @@ -9319,7 +12652,7 @@ bool acl_authenticate(THD *thd, uint connect_errors, (thd->user_connect->user_resources.conn_per_hour || thd->user_connect->user_resources.user_conn || max_user_connections_checking) && - check_for_max_user_connections(thd, thd->user_connect)) + check_for_max_user_connections(thd, thd->user_connect)) { /* Ensure we don't decrement thd->user_connections->connections twice */ thd->user_connect= 0; @@ -9357,6 +12690,22 @@ bool acl_authenticate(THD *thd, uint connect_errors, */ sctx->db_access=0; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* + In case the user has a default role set, attempt to set that role + */ + if (initialized && acl_user->default_rolename.length) { + ulonglong access= 0; + int result; + result= acl_check_setrole(thd, acl_user->default_rolename.str, &access); + if (!result) + result= acl_setrole(thd, acl_user->default_rolename.str, access); + if (result) + thd->clear_error(); // even if the default role was not granted, do not + // close the connection + } +#endif + /* Change a database if necessary */ if (mpvio.db.length) { @@ -9374,10 +12723,16 @@ bool acl_authenticate(THD *thd, uint connect_errors, sctx->external_user= my_strdup(mpvio.auth_info.external_user, MYF(0)); if (res == CR_OK_HANDSHAKE_COMPLETE) - thd->stmt_da->disable_status(); + thd->get_stmt_da()->disable_status(); else my_ok(thd); +#ifdef HAVE_PSI_THREAD_INTERFACE + PSI_THREAD_CALL(set_thread_user_host) + (thd->main_security_ctx.user, strlen(thd->main_security_ctx.user), + thd->main_security_ctx.host_or_ip, strlen(thd->main_security_ctx.host_or_ip)); +#endif + /* Ready to handle queries */ DBUG_RETURN(0); } @@ -9396,16 +12751,16 @@ static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, uchar *pkt; int pkt_len; MPVIO_EXT *mpvio= (MPVIO_EXT *) vio; - THD *thd=mpvio->thd; + THD *thd=info->thd; DBUG_ENTER("native_password_authenticate"); /* generate the scramble, or reuse the old one */ if (thd->scramble[SCRAMBLE_LENGTH]) { - create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand); + thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH); /* and send it to the client */ if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1)) - DBUG_RETURN(CR_ERROR); + DBUG_RETURN(CR_AUTH_HANDSHAKE); } /* reply and authenticate */ @@ -9447,53 +12802,54 @@ static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, /* read the reply with the encrypted password */ if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) - DBUG_RETURN(CR_ERROR); + DBUG_RETURN(CR_AUTH_HANDSHAKE); DBUG_PRINT("info", ("reply read : pkt_len=%d", pkt_len)); #ifdef NO_EMBEDDED_ACCESS_CHECKS DBUG_RETURN(CR_OK); #endif + DBUG_EXECUTE_IF("native_password_bad_reply", { pkt_len= 12; }); + if (pkt_len == 0) /* no password */ - DBUG_RETURN(info->auth_string[0] ? CR_ERROR : CR_OK); + DBUG_RETURN(mpvio->acl_user->salt_len != 0 ? CR_AUTH_USER_CREDENTIALS : CR_OK); info->password_used= PASSWORD_USED_YES; if (pkt_len == SCRAMBLE_LENGTH) { if (!mpvio->acl_user->salt_len) - DBUG_RETURN(CR_ERROR); + DBUG_RETURN(CR_AUTH_USER_CREDENTIALS); if (check_scramble(pkt, thd->scramble, mpvio->acl_user->salt)) - DBUG_RETURN(CR_ERROR); + DBUG_RETURN(CR_AUTH_USER_CREDENTIALS); else DBUG_RETURN(CR_OK); } - inc_host_errors(mpvio->thd->security_ctx->ip); my_error(ER_HANDSHAKE_ERROR, MYF(0)); - DBUG_RETURN(CR_ERROR); + DBUG_RETURN(CR_AUTH_HANDSHAKE); } -static int old_password_authenticate(MYSQL_PLUGIN_VIO *vio, +static int old_password_authenticate(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) { uchar *pkt; int pkt_len; MPVIO_EXT *mpvio= (MPVIO_EXT *) vio; - THD *thd=mpvio->thd; + THD *thd=info->thd; /* generate the scramble, or reuse the old one */ if (thd->scramble[SCRAMBLE_LENGTH]) { - create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand); + thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH); /* and send it to the client */ if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1)) - return CR_ERROR; + return CR_AUTH_HANDSHAKE; } /* read the reply and authenticate */ if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) - return CR_ERROR; + return CR_AUTH_HANDSHAKE; #ifdef NO_EMBEDDED_ACCESS_CHECKS return CR_OK; @@ -9508,26 +12864,25 @@ static int old_password_authenticate(MYSQL_PLUGIN_VIO *vio, pkt_len= strnlen((char*)pkt, pkt_len); if (pkt_len == 0) /* no password */ - return info->auth_string[0] ? CR_ERROR : CR_OK; + return info->auth_string[0] ? CR_AUTH_USER_CREDENTIALS : CR_OK; if (secure_auth(thd)) - return CR_ERROR; + return CR_AUTH_HANDSHAKE; info->password_used= PASSWORD_USED_YES; if (pkt_len == SCRAMBLE_LENGTH_323) { if (!mpvio->acl_user->salt_len) - return CR_ERROR; + return CR_AUTH_USER_CREDENTIALS; return check_scramble_323(pkt, thd->scramble, (ulong *) mpvio->acl_user->salt) ? - CR_ERROR : CR_OK; + CR_AUTH_USER_CREDENTIALS : CR_OK; } - inc_host_errors(mpvio->thd->security_ctx->ip); my_error(ER_HANDSHAKE_ERROR, MYF(0)); - return CR_ERROR; + return CR_AUTH_HANDSHAKE; } static struct st_mysql_auth native_password_handler= @@ -9558,7 +12913,7 @@ maria_declare_plugin(mysql_password) NULL, /* status variables */ NULL, /* system variables */ "1.0", /* String version */ - MariaDB_PLUGIN_MATURITY_BETA /* Maturity */ + MariaDB_PLUGIN_MATURITY_STABLE /* Maturity */ }, { MYSQL_AUTHENTICATION_PLUGIN, /* type constant */ @@ -9573,6 +12928,6 @@ maria_declare_plugin(mysql_password) NULL, /* status variables */ NULL, /* system variables */ "1.0", /* String version */ - MariaDB_PLUGIN_MATURITY_BETA /* Maturity */ + MariaDB_PLUGIN_MATURITY_STABLE /* Maturity */ } maria_declare_plugin_end; diff --git a/sql/sql_acl.h b/sql/sql_acl.h index 3169746419c..fc3fcdc534a 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -20,30 +20,30 @@ #include "violite.h" /* SSL_type */ #include "sql_class.h" /* LEX_COLUMN */ -#define SELECT_ACL (1L << 0) -#define INSERT_ACL (1L << 1) -#define UPDATE_ACL (1L << 2) -#define DELETE_ACL (1L << 3) -#define CREATE_ACL (1L << 4) -#define DROP_ACL (1L << 5) -#define RELOAD_ACL (1L << 6) -#define SHUTDOWN_ACL (1L << 7) -#define PROCESS_ACL (1L << 8) -#define FILE_ACL (1L << 9) -#define GRANT_ACL (1L << 10) -#define REFERENCES_ACL (1L << 11) -#define INDEX_ACL (1L << 12) -#define ALTER_ACL (1L << 13) -#define SHOW_DB_ACL (1L << 14) -#define SUPER_ACL (1L << 15) -#define CREATE_TMP_ACL (1L << 16) -#define LOCK_TABLES_ACL (1L << 17) -#define EXECUTE_ACL (1L << 18) -#define REPL_SLAVE_ACL (1L << 19) -#define REPL_CLIENT_ACL (1L << 20) -#define CREATE_VIEW_ACL (1L << 21) -#define SHOW_VIEW_ACL (1L << 22) -#define CREATE_PROC_ACL (1L << 23) +#define SELECT_ACL (1L << 0) +#define INSERT_ACL (1L << 1) +#define UPDATE_ACL (1L << 2) +#define DELETE_ACL (1L << 3) +#define CREATE_ACL (1L << 4) +#define DROP_ACL (1L << 5) +#define RELOAD_ACL (1L << 6) +#define SHUTDOWN_ACL (1L << 7) +#define PROCESS_ACL (1L << 8) +#define FILE_ACL (1L << 9) +#define GRANT_ACL (1L << 10) +#define REFERENCES_ACL (1L << 11) +#define INDEX_ACL (1L << 12) +#define ALTER_ACL (1L << 13) +#define SHOW_DB_ACL (1L << 14) +#define SUPER_ACL (1L << 15) +#define CREATE_TMP_ACL (1L << 16) +#define LOCK_TABLES_ACL (1L << 17) +#define EXECUTE_ACL (1L << 18) +#define REPL_SLAVE_ACL (1L << 19) +#define REPL_CLIENT_ACL (1L << 20) +#define CREATE_VIEW_ACL (1L << 21) +#define SHOW_VIEW_ACL (1L << 22) +#define CREATE_PROC_ACL (1L << 23) #define ALTER_PROC_ACL (1L << 24) #define CREATE_USER_ACL (1L << 25) #define EVENT_ACL (1L << 26) @@ -57,7 +57,7 @@ 4. acl_init() or whatever - to define behaviour for old privilege tables 5. sql_yacc.yy - for GRANT/REVOKE to work */ -#define NO_ACCESS (1L << 30) +#define NO_ACCESS (1L << 30) #define DB_ACLS \ (UPDATE_ACL | SELECT_ACL | INSERT_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \ GRANT_ACL | REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL | \ @@ -95,6 +95,14 @@ CREATE_ACL | DROP_ACL | ALTER_ACL | INDEX_ACL | \ TRIGGER_ACL | REFERENCES_ACL | GRANT_ACL | CREATE_VIEW_ACL | SHOW_VIEW_ACL) +/** + Table-level privileges which are automatically "granted" to everyone on + existing temporary tables (CREATE_ACL is necessary for ALTER ... RENAME). +*/ +#define TMP_TABLE_ACLS \ +(SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \ + INDEX_ACL | ALTER_ACL) + /* Defines to change the above bits to how things are stored in tables This is needed as the 'host' and 'db' table is missing a few privileges @@ -106,21 +114,21 @@ #define DB_CHUNK1 (GRANT_ACL | REFERENCES_ACL | INDEX_ACL | ALTER_ACL) #define DB_CHUNK2 (CREATE_TMP_ACL | LOCK_TABLES_ACL) #define DB_CHUNK3 (CREATE_VIEW_ACL | SHOW_VIEW_ACL | \ - CREATE_PROC_ACL | ALTER_PROC_ACL ) + CREATE_PROC_ACL | ALTER_PROC_ACL ) #define DB_CHUNK4 (EXECUTE_ACL) #define DB_CHUNK5 (EVENT_ACL | TRIGGER_ACL) #define fix_rights_for_db(A) (((A) & DB_CHUNK0) | \ - (((A) << 4) & DB_CHUNK1) | \ - (((A) << 6) & DB_CHUNK2) | \ - (((A) << 9) & DB_CHUNK3) | \ - (((A) << 2) & DB_CHUNK4))| \ + (((A) << 4) & DB_CHUNK1) | \ + (((A) << 6) & DB_CHUNK2) | \ + (((A) << 9) & DB_CHUNK3) | \ + (((A) << 2) & DB_CHUNK4))| \ (((A) << 9) & DB_CHUNK5) #define get_rights_for_db(A) (((A) & DB_CHUNK0) | \ - (((A) & DB_CHUNK1) >> 4) | \ - (((A) & DB_CHUNK2) >> 6) | \ - (((A) & DB_CHUNK3) >> 9) | \ - (((A) & DB_CHUNK4) >> 2))| \ + (((A) & DB_CHUNK1) >> 4) | \ + (((A) & DB_CHUNK2) >> 6) | \ + (((A) & DB_CHUNK3) >> 9) | \ + (((A) & DB_CHUNK4) >> 2))| \ (((A) & DB_CHUNK5) >> 9) #define TBL_CHUNK0 DB_CHUNK0 #define TBL_CHUNK1 DB_CHUNK1 @@ -137,11 +145,11 @@ #define fix_rights_for_column(A) (((A) & 7) | (((A) & ~7) << 8)) #define get_rights_for_column(A) (((A) & 7) | ((A) >> 8)) #define fix_rights_for_procedure(A) ((((A) << 18) & EXECUTE_ACL) | \ - (((A) << 23) & ALTER_PROC_ACL) | \ - (((A) << 8) & GRANT_ACL)) + (((A) << 23) & ALTER_PROC_ACL) | \ + (((A) << 8) & GRANT_ACL)) #define get_rights_for_procedure(A) ((((A) & EXECUTE_ACL) >> 18) | \ - (((A) & ALTER_PROC_ACL) >> 23) | \ - (((A) & GRANT_ACL) >> 8)) + (((A) & ALTER_PROC_ACL) >> 23) | \ + (((A) & GRANT_ACL) >> 8)) enum mysql_db_table_field { @@ -173,6 +181,11 @@ enum mysql_db_table_field extern const TABLE_FIELD_DEF mysql_db_table_def; extern bool mysql_user_table_is_in_short_password_format; +extern LEX_STRING host_not_specified; +extern LEX_STRING current_user; +extern LEX_STRING current_role; +extern LEX_STRING current_user_and_current_role; + static inline int access_denied_error_code(int passwd_used) { @@ -184,51 +197,61 @@ static inline int access_denied_error_code(int passwd_used) /* prototypes */ bool hostname_requires_resolving(const char *hostname); -my_bool acl_init(bool dont_read_acl_tables); -my_bool acl_reload(THD *thd); +bool acl_init(bool dont_read_acl_tables); +bool acl_reload(THD *thd); void acl_free(bool end=0); ulong acl_get(const char *host, const char *ip, - const char *user, const char *db, my_bool db_is_pattern); -bool acl_authenticate(THD *thd, uint connect_errors, uint com_change_user_pkt_len); + const char *user, const char *db, my_bool db_is_pattern); +bool acl_authenticate(THD *thd, uint com_change_user_pkt_len); bool acl_getroot(Security_context *sctx, char *user, char *host, char *ip, char *db); bool acl_check_host(const char *host, const char *ip); -int check_change_password(THD *thd, const char *host, const char *user, - char *password, uint password_len); -bool change_password(THD *thd, const char *host, const char *user, - char *password); +bool check_change_password(THD *thd, LEX_USER *user); +bool change_password(THD *thd, LEX_USER *user); + +bool mysql_grant_role(THD *thd, List<LEX_USER> &user_list, bool revoke); bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &user_list, ulong rights, bool revoke, bool is_proxy); int mysql_table_grant(THD *thd, TABLE_LIST *table, List <LEX_USER> &user_list, List <LEX_COLUMN> &column_list, ulong rights, bool revoke); bool mysql_routine_grant(THD *thd, TABLE_LIST *table, bool is_proc, - List <LEX_USER> &user_list, ulong rights, - bool revoke, bool write_to_binlog); -my_bool grant_init(); + List <LEX_USER> &user_list, ulong rights, + bool revoke, bool write_to_binlog); +bool grant_init(); void grant_free(void); -my_bool grant_reload(THD *thd); +bool grant_reload(THD *thd); bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, bool any_combination_will_do, uint number, bool no_errors); bool check_grant_column (THD *thd, GRANT_INFO *grant, - const char *db_name, const char *table_name, - const char *name, uint length, Security_context *sctx); + const char *db_name, const char *table_name, + const char *name, uint length, Security_context *sctx); bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref, const char *name, uint length); -bool check_grant_all_columns(THD *thd, ulong want_access, +bool check_grant_all_columns(THD *thd, ulong want_access, Field_iterator_table_ref *fields); bool check_grant_routine(THD *thd, ulong want_access, - TABLE_LIST *procs, bool is_proc, bool no_error); + TABLE_LIST *procs, bool is_proc, bool no_error); bool check_grant_db(THD *thd,const char *db); +bool check_global_access(THD *thd, ulong want_access, bool no_errors= false); +bool check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, + GRANT_INTERNAL_INFO *grant_internal_info, + bool dont_check_global_grants, bool no_errors); ulong get_table_grant(THD *thd, TABLE_LIST *table); ulong get_column_grant(THD *thd, GRANT_INFO *grant, const char *db_name, const char *table_name, const char *field_name); +bool get_show_user(THD *thd, LEX_USER *lex_user, const char **username, + const char **hostname, const char **rolename); +void mysql_show_grants_get_fields(THD *thd, List<Item> *fields, + const char *name); bool mysql_show_grants(THD *thd, LEX_USER *user); +int fill_schema_enabled_roles(THD *thd, TABLE_LIST *tables, COND *cond); +int fill_schema_applicable_roles(THD *thd, TABLE_LIST *tables, COND *cond); void get_privilege_desc(char *to, uint max_length, ulong access); void get_mqh(const char *user, const char *host, USER_CONN *uc); -bool mysql_create_user(THD *thd, List <LEX_USER> &list); -bool mysql_drop_user(THD *thd, List <LEX_USER> &list); +bool mysql_create_user(THD *thd, List <LEX_USER> &list, bool handle_as_role); +bool mysql_drop_user(THD *thd, List <LEX_USER> &list, bool handle_as_role); bool mysql_rename_user(THD *thd, List <LEX_USER> &list); bool mysql_revoke_all(THD *thd, List <LEX_USER> &list); void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant, @@ -246,11 +269,6 @@ int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond); int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond); int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr); -#ifdef NO_EMBEDDED_ACCESS_CHECKS -#define check_grant(A,B,C,D,E,F) 0 -#define check_grant_db(A,B) 0 -#endif - /** Result of an access check for an internal schema or table. Internal ACL checks are always performed *before* using @@ -382,4 +400,24 @@ get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info, bool acl_check_proxy_grant_access (THD *thd, const char *host, const char *user, bool with_grant); +int acl_setrole(THD *thd, char *rolename, ulonglong access); +int acl_check_setrole(THD *thd, char *rolename, ulonglong *access); +int acl_check_set_default_role(THD *thd, const char *host, const char *user); +int acl_set_default_role(THD *thd, const char *host, const char *user, + const char *rolename); + +extern SHOW_VAR acl_statistics[]; + +/* Check if a role is granted to a user/role. + + If hostname == NULL, search for a role as the starting grantee. +*/ +bool check_role_is_granted(const char *username, + const char *hostname, + const char *rolename); + +#ifndef DBUG_OFF +extern ulong role_global_merges, role_db_merges, role_table_merges, + role_column_merges, role_routine_merges; +#endif #endif /* SQL_ACL_INCLUDED */ diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index 9897e4b4302..0ec6719037c 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -14,7 +14,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "sql_class.h" // THD +#include "sql_class.h" // THD and my_global.h #include "keycaches.h" // get_key_cache #include "sql_base.h" // Open_table_context #include "lock.h" // MYSQL_OPEN_* @@ -28,7 +28,9 @@ #include "sql_acl.h" // *_ACL #include "sp.h" // Sroutine_hash_entry #include "sql_parse.h" // check_table_access +#include "strfunc.h" #include "sql_admin.h" +#include "sql_statistics.h" /* Prepare, run and cleanup for mysql_recreate_table() */ @@ -41,9 +43,19 @@ static bool admin_recreate_table(THD *thd, TABLE_LIST *table_list) trans_rollback(thd); close_thread_tables(thd); thd->mdl_context.release_transactional_locks(); + + /* + table_list->table has been closed and freed. Do not reference + uninitialized data. open_tables() could fail. + */ + table_list->table= NULL; + /* Same applies to MDL ticket. */ + table_list->mdl_request.ticket= NULL; + DEBUG_SYNC(thd, "ha_admin_try_alter"); tmp_disable_binlog(thd); // binlogging is done by caller if wanted - result_code= mysql_recreate_table(thd, table_list); + result_code= (open_temporary_tables(thd, table_list) || + mysql_recreate_table(thd, table_list, false)); reenable_binlog(thd); /* mysql_recreate_table() can push OK or ERROR. @@ -51,8 +63,8 @@ static bool admin_recreate_table(THD *thd, TABLE_LIST *table_list) we will store the error message in a result set row and then clear. */ - if (thd->stmt_da->is_ok()) - thd->stmt_da->reset_diagnostics_area(); + if (thd->get_stmt_da()->is_ok()) + thd->get_stmt_da()->reset_diagnostics_area(); table_list->table= NULL; DBUG_RETURN(result_code); } @@ -87,7 +99,6 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, const char **ext; MY_STAT stat_info; Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH | - MYSQL_OPEN_FOR_REPAIR | MYSQL_OPEN_HAS_MDL_LOCK | MYSQL_LOCK_IGNORE_TIMEOUT)); DBUG_ENTER("prepare_for_repair"); @@ -97,8 +108,6 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, if (!(table= table_list->table)) { - char key[MAX_DBKEY_LENGTH]; - uint key_length; /* If the table didn't exist, we have a shared metadata lock on it that is left from mysql_admin_table()'s attempt to @@ -111,32 +120,23 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, Attempt to do full-blown table open in mysql_admin_table() has failed. Let us try to open at least a .FRM for this table. */ - my_hash_value_type hash_value; - key_length= create_table_def_key(thd, key, table_list, 0); table_list->mdl_request.init(MDL_key::TABLE, table_list->db, table_list->table_name, MDL_EXCLUSIVE, MDL_TRANSACTION); if (lock_table_names(thd, table_list, table_list->next_global, - thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY)) + thd->variables.lock_wait_timeout, 0)) DBUG_RETURN(0); has_mdl_lock= TRUE; - hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); - mysql_mutex_lock(&LOCK_open); - share= get_table_share(thd, table_list, key, key_length, 0, - &error, hash_value); - mysql_mutex_unlock(&LOCK_open); + share= tdc_acquire_share_shortlived(thd, table_list, GTS_TABLE); if (share == NULL) DBUG_RETURN(0); // Can't open frm file if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE)) { - mysql_mutex_lock(&LOCK_open); - release_table_share(share); - mysql_mutex_unlock(&LOCK_open); + tdc_release_share(share); DBUG_RETURN(0); // Out of memory } table= &tmp_table; @@ -198,13 +198,11 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, to close it, but leave it protected by exclusive metadata lock. */ pos_in_locked_tables= table->pos_in_locked_tables; - if (wait_while_table_is_used(thd, table, - HA_EXTRA_PREPARE_FOR_FORCED_CLOSE, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)) + if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_FORCED_CLOSE)) goto end; /* Close table but don't remove from locked list */ close_all_tables_for_name(thd, table_list->table->s, - HA_EXTRA_NOT_USED); + HA_EXTRA_NOT_USED, NULL); table_list->table= 0; } /* @@ -240,7 +238,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, if (thd->locked_tables_list.locked_tables()) { - if (thd->locked_tables_list.reopen_tables(thd)) + if (thd->locked_tables_list.reopen_tables(thd, false)) goto end; /* Restore the table in the table list with the new opened table */ table_list->table= pos_in_locked_tables->table; @@ -251,7 +249,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, Now we should be able to open the partially repaired table to finish the repair in the handler later on. */ - if (open_table(thd, table_list, thd->mem_root, &ot_ctx)) + if (open_table(thd, table_list, &ot_ctx)) { error= send_check_errmsg(thd, table_list, "repair", "Failed to open partially repaired table"); @@ -262,11 +260,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, end: thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); if (table == &tmp_table) - { - mysql_mutex_lock(&LOCK_open); closefrm(table, 1); // Free allocated memory - mysql_mutex_unlock(&LOCK_open); - } /* In case of a temporary table there will be no metadata lock. */ if (error && has_mdl_lock) thd->mdl_context.release_transactional_locks(); @@ -308,7 +302,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt, const char *operator_name, thr_lock_type lock_type, - bool open_for_modify, + bool org_open_for_modify, bool repair_table_use_frm, uint extra_open_options, int (*prepare_func)(THD *, TABLE_LIST *, @@ -325,31 +319,57 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, Protocol *protocol= thd->protocol; LEX *lex= thd->lex; int result_code; + int compl_result_code; bool need_repair_or_alter= 0; + wait_for_commit* suspended_wfc; + DBUG_ENTER("mysql_admin_table"); DBUG_PRINT("enter", ("extra_open_options: %u", extra_open_options)); - field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2)); + field_list.push_back(item= new (thd->mem_root) + Item_empty_string(thd, "Table", + NAME_CHAR_LEN * 2), thd->mem_root); item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Op", 10)); + field_list.push_back(item= new (thd->mem_root) + Item_empty_string(thd, "Op", 10), thd->mem_root); item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Msg_type", 10)); + field_list.push_back(item= new (thd->mem_root) + Item_empty_string(thd, "Msg_type", 10), thd->mem_root); item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Msg_text", - SQL_ADMIN_MSG_TEXT_SIZE)); + field_list.push_back(item= new (thd->mem_root) + Item_empty_string(thd, "Msg_text", + SQL_ADMIN_MSG_TEXT_SIZE), + thd->mem_root); item->maybe_null = 1; if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); + /* + This function calls trans_commit() during its operation, but that does not + imply that the operation is complete or binlogged. So we have to suspend + temporarily the wakeup_subsequent_commits() calls (if used). + */ + suspended_wfc= thd->suspend_subsequent_commits(); + mysql_ha_rm_tables(thd, tables); + /* + Close all temporary tables which were pre-open to simplify + privilege checking. Clear all references to closed tables. + */ + close_thread_tables(thd); + for (table= tables; table; table= table->next_local) + table->table= NULL; + for (table= tables; table; table= table->next_local) { char table_name[SAFE_NAME_LEN*2+2]; - char* db = table->db; + char *db= table->db; bool fatal_error=0; bool open_error; + bool collect_eis= FALSE; + bool open_for_modify= org_open_for_modify; DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name)); strxmov(table_name, db, ".", table->table_name, NullS); @@ -361,9 +381,13 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, To allow concurrent execution of read-only operations we acquire weak metadata lock for them. */ - table->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_NO_READ_WRITE : MDL_SHARED_READ); + table->mdl_request.set_type(lex->sql_command == SQLCOM_REPAIR + ? MDL_SHARED_NO_READ_WRITE + : lock_type >= TL_WRITE_ALLOW_WRITE + ? MDL_SHARED_WRITE : MDL_SHARED_READ); + /* open only one table from local list of command */ + while (1) { TABLE_LIST *save_next_global, *save_next_local; save_next_global= table->next_global; @@ -383,10 +407,10 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, /* CHECK TABLE command is allowed for views as well. Check on alter flags - to differentiate from ALTER TABLE...CHECK PARTITION on which view is not - allowed. + to differentiate from ALTER TABLE...CHECK PARTITION on which view is + not allowed. */ - if (lex->alter_info.flags & ALTER_ADMIN_PARTITION || + if (lex->alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION || view_operator_func == NULL) { table->required_type=FRMTYPE_TABLE; @@ -417,14 +441,15 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, because it's already known that the table is badly damaged. */ - Warning_info wi(thd->query_id, false); - Warning_info *wi_saved= thd->warning_info; + Diagnostics_area *da= thd->get_stmt_da(); + Warning_info tmp_wi(thd->query_id, false, true); - thd->warning_info= &wi; + da->push_warning_info(&tmp_wi); - open_error= open_and_lock_tables(thd, table, TRUE, 0); + open_error= (open_temporary_tables(thd, table) || + open_and_lock_tables(thd, table, TRUE, 0)); - thd->warning_info= wi_saved; + da->pop_warning_info(); } else { @@ -436,7 +461,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, mode. It does make sense for the user to see such errors. */ - open_error= open_and_lock_tables(thd, table, TRUE, 0); + open_error= (open_temporary_tables(thd, table) || + open_and_lock_tables(thd, table, TRUE, 0)); } thd->prepare_derived_at_open= FALSE; @@ -473,6 +499,20 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, result_code= HA_ADMIN_FAILED; goto send_result; } + + if (!table->table || table->mdl_request.type != MDL_SHARED_WRITE || + table->table->file->ha_table_flags() & HA_CONCURRENT_OPTIMIZE) + break; + + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); + table->table= NULL; + thd->mdl_context.release_transactional_locks(); + table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, + MDL_SHARED_NO_READ_WRITE, MDL_TRANSACTION); + } + #ifdef WITH_PARTITION_STORAGE_ENGINE if (table->table) { @@ -483,11 +523,12 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, */ Alter_info *alter_info= &lex->alter_info; - if (alter_info->flags & ALTER_ADMIN_PARTITION) + if (alter_info->flags & Alter_info::ALTER_ADMIN_PARTITION) { if (!table->table->part_info) { my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); + thd->resume_subsequent_commits(suspended_wfc); DBUG_RETURN(TRUE); } if (set_part_state(alter_info, table->table->part_info, PART_ADMIN)) @@ -500,7 +541,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, protocol->store(operator_name, system_charset_info); protocol->store(STRING_WITH_LEN("error"), system_charset_info); length= my_snprintf(buff, sizeof(buff), - ER(ER_DROP_PARTITION_NON_EXISTENT), + ER_THD(thd, ER_DROP_PARTITION_NON_EXISTENT), table_name); protocol->store(buff, length, system_charset_info); if(protocol->write()) @@ -511,7 +552,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, } } #endif - } DBUG_PRINT("admin", ("table: 0x%lx", (long) table->table)); if (prepare_func) @@ -547,16 +587,17 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, if (!table->table) { DBUG_PRINT("admin", ("open table failed")); - if (thd->warning_info->is_empty()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_CHECK_NO_SUCH_TABLE, ER(ER_CHECK_NO_SUCH_TABLE)); + if (thd->get_stmt_da()->is_warning_info_empty()) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CHECK_NO_SUCH_TABLE, + ER_THD(thd, ER_CHECK_NO_SUCH_TABLE)); /* if it was a view will check md5 sum */ if (table->view && view_check(thd, table, check_opt) == HA_ADMIN_WRONG_CHECKSUM) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_VIEW_CHECKSUM, ER(ER_VIEW_CHECKSUM)); - if (thd->stmt_da->is_error() && - table_not_corrupt_error(thd->stmt_da->sql_errno())) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_VIEW_CHECKSUM, ER_THD(thd, ER_VIEW_CHECKSUM)); + if (thd->get_stmt_da()->is_error() && + table_not_corrupt_error(thd->get_stmt_da()->sql_errno())) result_code= HA_ADMIN_FAILED; else /* Default failure code is corrupt table */ @@ -588,7 +629,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, protocol->store(table_name, system_charset_info); protocol->store(operator_name, system_charset_info); protocol->store(STRING_WITH_LEN("error"), system_charset_info); - length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY), + length= my_snprintf(buff, sizeof(buff), ER_THD(thd, ER_OPEN_AS_READONLY), table_name); protocol->store(buff, length, system_charset_info); trans_commit_stmt(thd); @@ -604,30 +645,27 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, table->table=0; // For query cache if (protocol->write()) goto err; - thd->stmt_da->reset_diagnostics_area(); + thd->get_stmt_da()->reset_diagnostics_area(); continue; /* purecov: end */ } /* Close all instances of the table to allow MyISAM "repair" - to rename files. + (which is internally also used from "optimize") to rename files. @todo: This code does not close all instances of the table. It only closes instances in other connections, but if this connection has LOCK TABLE t1 a READ, t1 b WRITE, both t1 instances will be kept open. - There is no need to execute this branch for InnoDB, which does - repair by recreate. There is no need to do it for OPTIMIZE, - which doesn't move files around. - Hence, this code should be moved to prepare_for_repair(), - and executed only for MyISAM engine. + + Note that this code is only executed for engines that request + MDL_SHARED_NO_READ_WRITE lock (MDL_SHARED_WRITE cannot be upgraded) + by *not* having HA_CONCURRENT_OPTIMIZE table_flag. */ - if (lock_type == TL_WRITE && !table->table->s->tmp_table) + if (lock_type == TL_WRITE && !table->table->s->tmp_table && + table->mdl_request.type > MDL_SHARED_WRITE) { - table->table->s->protect_against_usage(); - if (wait_while_table_is_used(thd, table->table, - HA_EXTRA_PREPARE_FOR_RENAME, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)) + if (wait_while_table_is_used(thd, table->table, HA_EXTRA_NOT_USED)) goto err; DEBUG_SYNC(thd, "after_admin_flush"); /* Flush entries in the query cache involving this table. */ @@ -677,11 +715,131 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, } } - DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name)); - thd_proc_info(thd, "executing"); - result_code = (table->table->file->*operator_func)(thd, check_opt); - thd_proc_info(thd, "Sending data"); - DBUG_PRINT("admin", ("operator_func returned: %d", result_code)); + result_code= compl_result_code= HA_ADMIN_OK; + + if (operator_func == &handler::ha_analyze) + { + TABLE *tab= table->table; + Field **field_ptr= tab->field; + + if (lex->with_persistent_for_clause && + tab->s->table_category != TABLE_CATEGORY_USER) + { + compl_result_code= result_code= HA_ADMIN_INVALID; + } + collect_eis= + (table->table->s->table_category == TABLE_CATEGORY_USER && + (get_use_stat_tables_mode(thd) > NEVER || + lex->with_persistent_for_clause)); + + if (collect_eis) + { + if (!lex->column_list) + { + bitmap_clear_all(tab->read_set); + for (uint fields= 0; *field_ptr; field_ptr++, fields++) + { + enum enum_field_types type= (*field_ptr)->type(); + if (type < MYSQL_TYPE_MEDIUM_BLOB || + type > MYSQL_TYPE_BLOB) + bitmap_set_bit(tab->read_set, fields); + else if (collect_eis) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NO_EIS_FOR_FIELD, + ER_THD(thd, ER_NO_EIS_FOR_FIELD), + (*field_ptr)->field_name); + } + } + else + { + int pos; + LEX_STRING *column_name; + List_iterator_fast<LEX_STRING> it(*lex->column_list); + + bitmap_clear_all(tab->read_set); + while ((column_name= it++)) + { + if (tab->s->fieldnames.type_names == 0 || + (pos= find_type(&tab->s->fieldnames, column_name->str, + column_name->length, 1)) <= 0) + { + compl_result_code= result_code= HA_ADMIN_INVALID; + break; + } + pos--; + enum enum_field_types type= tab->field[pos]->type(); + if (type < MYSQL_TYPE_MEDIUM_BLOB || + type > MYSQL_TYPE_BLOB) + bitmap_set_bit(tab->read_set, pos); + else if (collect_eis) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NO_EIS_FOR_FIELD, + ER_THD(thd, ER_NO_EIS_FOR_FIELD), + column_name->str); + } + tab->file->column_bitmaps_signal(); + } + } + else + { + DBUG_ASSERT(!lex->column_list); + } + + if (!lex->index_list) + { + tab->keys_in_use_for_query.init(tab->s->keys); + } + else + { + int pos; + LEX_STRING *index_name; + List_iterator_fast<LEX_STRING> it(*lex->index_list); + + tab->keys_in_use_for_query.clear_all(); + while ((index_name= it++)) + { + if (tab->s->keynames.type_names == 0 || + (pos= find_type(&tab->s->keynames, index_name->str, + index_name->length, 1)) <= 0) + { + compl_result_code= result_code= HA_ADMIN_INVALID; + break; + } + tab->keys_in_use_for_query.set_bit(--pos); + } + } + } + + if (result_code == HA_ADMIN_OK) + { + DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name)); + THD_STAGE_INFO(thd, stage_executing); + result_code = (table->table->file->*operator_func)(thd, check_opt); + THD_STAGE_INFO(thd, stage_sending_data); + DBUG_PRINT("admin", ("operator_func returned: %d", result_code)); + } + + if (compl_result_code == HA_ADMIN_OK && collect_eis) + { + if (!(compl_result_code= + alloc_statistics_for_table(thd, table->table)) && + !(compl_result_code= + collect_statistics_for_table(thd, table->table))) + compl_result_code= update_statistics_for_table(thd, table->table); + if (compl_result_code) + result_code= HA_ADMIN_FAILED; + else + { + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("status"), system_charset_info); + protocol->store(STRING_WITH_LEN("Engine-independent statistics collected"), + system_charset_info); + if (protocol->write()) + goto err; + } + } if (result_code == HA_ADMIN_NOT_IMPLEMENTED && need_repair_or_alter) { @@ -696,8 +854,9 @@ send_result: lex->cleanup_after_one_table_open(); thd->clear_error(); // these errors shouldn't get client { - List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); - MYSQL_ERROR *err; + Diagnostics_area::Sql_condition_iterator it= + thd->get_stmt_da()->sql_conditions(); + const Sql_condition *err; while ((err= it++)) { protocol->prepare_for_resend(); @@ -710,7 +869,7 @@ send_result: if (protocol->write()) goto err; } - thd->warning_info->clear_warning_info(thd->query_id); + thd->get_stmt_da()->clear_warning_info(thd->query_id); } protocol->prepare_for_resend(); protocol->store(table_name, system_charset_info); @@ -724,7 +883,8 @@ send_result_message: { char buf[MYSQL_ERRMSG_SIZE]; size_t length=my_snprintf(buf, sizeof(buf), - ER(ER_CHECK_NOT_IMPLEMENTED), operator_name); + ER_THD(thd, ER_CHECK_NOT_IMPLEMENTED), + operator_name); protocol->store(STRING_WITH_LEN("note"), system_charset_info); protocol->store(buf, length, system_charset_info); } @@ -734,7 +894,8 @@ send_result_message: { char buf[MYSQL_ERRMSG_SIZE]; size_t length= my_snprintf(buf, sizeof(buf), - ER(ER_BAD_TABLE_ERROR), table_name); + ER_THD(thd, ER_BAD_TABLE_ERROR), + table_name); protocol->store(STRING_WITH_LEN("note"), system_charset_info); protocol->store(buf, length, system_charset_info); } @@ -778,19 +939,10 @@ send_result_message: case HA_ADMIN_TRY_ALTER: { - uint save_flags; Alter_info *alter_info= &lex->alter_info; - /* Store the original value of alter_info->flags */ - save_flags= alter_info->flags; - /* - This is currently used only by InnoDB. ha_innobase::optimize() answers - "try with alter", so here we close the table, do an ALTER TABLE, - reopen the table and do ha_innobase::analyze() on it. - We have to end the row, so analyze could return more rows. - */ protocol->store(STRING_WITH_LEN("note"), system_charset_info); - if(alter_info->flags & ALTER_ADMIN_PARTITION) + if (alter_info->flags & Alter_info::ALTER_ADMIN_PARTITION) { protocol->store(STRING_WITH_LEN( "Table does not support optimize on partitions. All partitions " @@ -804,17 +956,21 @@ send_result_message: } if (protocol->write()) goto err; - thd_proc_info(thd, "recreating table"); + THD_STAGE_INFO(thd, stage_recreating_table); DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); TABLE_LIST *save_next_local= table->next_local, *save_next_global= table->next_global; table->next_local= table->next_global= 0; - result_code= admin_recreate_table(thd, table); + tmp_disable_binlog(thd); // binlogging is done by caller if wanted + result_code= admin_recreate_table(thd, table); + reenable_binlog(thd); trans_commit_stmt(thd); trans_commit(thd); close_thread_tables(thd); thd->mdl_context.release_transactional_locks(); + /* Clear references to TABLE and MDL_ticket after releasing them. */ + table->mdl_request.ticket= NULL; if (!result_code) // recreation went ok { @@ -822,22 +978,27 @@ send_result_message: table->mdl_request.ticket= NULL; DEBUG_SYNC(thd, "ha_admin_open_ltable"); table->mdl_request.set_type(MDL_SHARED_WRITE); - /* - Reset the ALTER_ADMIN_PARTITION bit in alter_info->flags - to force analyze on all partitions. - */ - alter_info->flags &= ~(ALTER_ADMIN_PARTITION); - if ((table->table= open_ltable(thd, table, lock_type, 0))) + if (!open_temporary_tables(thd, table) && + (table->table= open_ltable(thd, table, lock_type, 0))) { + uint save_flags; + /* Store the original value of alter_info->flags */ + save_flags= alter_info->flags; + + /* + Reset the ALTER_ADMIN_PARTITION bit in alter_info->flags + to force analyze on all partitions. + */ + alter_info->flags &= ~(Alter_info::ALTER_ADMIN_PARTITION); result_code= table->table->file->ha_analyze(thd, check_opt); if (result_code == HA_ADMIN_ALREADY_DONE) result_code= HA_ADMIN_OK; else if (result_code) // analyze failed table->table->file->print_error(result_code, MYF(0)); + alter_info->flags= save_flags; } else result_code= -1; // open failed - alter_info->flags= save_flags; } /* Start a new row for the final status row */ protocol->prepare_for_resend(); @@ -845,10 +1006,10 @@ send_result_message: protocol->store(operator_name, system_charset_info); if (result_code) // either mysql_recreate_table or analyze failed { - DBUG_ASSERT(thd->is_error() || thd->killed); + DBUG_ASSERT(thd->is_error()); if (thd->is_error()) { - const char *err_msg= thd->stmt_da->message(); + const char *err_msg= thd->get_stmt_da()->message(); if (!thd->vio_ok()) { sql_print_error("%s", err_msg); @@ -867,6 +1028,9 @@ send_result_message: } thd->clear_error(); } + /* Make sure this table instance is not reused after the operation. */ + if (table->table) + table->table->m_needs_reopen= true; } result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; table->next_local= save_next_local; @@ -876,7 +1040,8 @@ send_result_message: case HA_ADMIN_WRONG_CHECKSUM: { protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(ER(ER_VIEW_CHECKSUM), strlen(ER(ER_VIEW_CHECKSUM)), + protocol->store(ER_THD(thd, ER_VIEW_CHECKSUM), + strlen(ER_THD(thd, ER_VIEW_CHECKSUM)), system_charset_info); break; } @@ -886,22 +1051,17 @@ send_result_message: { char buf[MYSQL_ERRMSG_SIZE]; size_t length; + const char *what_to_upgrade= table->view ? "VIEW" : + table->table->file->ha_table_flags() & HA_CAN_REPAIR ? "TABLE" : 0; protocol->store(STRING_WITH_LEN("error"), system_charset_info); -#if MYSQL_VERSION_ID > 100104 -#error fix the error message to take TABLE or VIEW as an argument -#else - if (table->view) + if (what_to_upgrade) length= my_snprintf(buf, sizeof(buf), - "Upgrade required. Please do \"REPAIR VIEW %`s\" or dump/reload to fix it!", - table->table_name); + ER_THD(thd, ER_TABLE_NEEDS_UPGRADE), + what_to_upgrade, table->table_name); else -#endif - if (table->table->file->ha_table_flags() & HA_CAN_REPAIR || table->view) - length= my_snprintf(buf, sizeof(buf), ER(ER_TABLE_NEEDS_UPGRADE), - table->table_name); - else - length= my_snprintf(buf, sizeof(buf), ER(ER_TABLE_NEEDS_REBUILD), + length= my_snprintf(buf, sizeof(buf), + ER_THD(thd, ER_TABLE_NEEDS_REBUILD), table->table_name); protocol->store(buf, length, system_charset_info); fatal_error=1; @@ -944,7 +1104,7 @@ send_result_message: } } /* Error path, a admin command failed. */ - if (thd->transaction_rollback_request) + if (thd->transaction_rollback_request || fatal_error) { /* Unlikely, but transaction rollback was requested by one of storage @@ -955,7 +1115,9 @@ send_result_message: } else { - if (trans_commit_stmt(thd) || trans_commit_implicit(thd)) + if (trans_commit_stmt(thd) || + (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END) && + trans_commit_implicit(thd))) goto err; } close_thread_tables(thd); @@ -982,18 +1144,23 @@ send_result_message: } my_eof(thd); + thd->resume_subsequent_commits(suspended_wfc); + DBUG_EXECUTE_IF("inject_analyze_table_sleep", my_sleep(500000);); DBUG_RETURN(FALSE); err: /* Make sure this table instance is not reused after the failure. */ + trans_rollback_stmt(thd); + if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END)) + trans_rollback(thd); if (table && table->table) + { table->table->m_needs_reopen= true; - trans_rollback_stmt(thd); - trans_rollback(thd); + table->table= 0; + } close_thread_tables(thd); // Shouldn't be needed thd->mdl_context.release_transactional_locks(); - if (table) - table->table=0; + thd->resume_subsequent_commits(suspended_wfc); DBUG_RETURN(TRUE); } @@ -1018,7 +1185,7 @@ bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* tables, KEY_CACHE *key_cache; DBUG_ENTER("mysql_assign_to_keycache"); - thd_proc_info(thd, "Finding key cache"); + THD_STAGE_INFO(thd, stage_finding_key_cache); check_opt.init(); mysql_mutex_lock(&LOCK_global_system_variables); if (!(key_cache= get_key_cache(key_cache_name))) @@ -1067,17 +1234,18 @@ bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) } -bool Analyze_table_statement::execute(THD *thd) +bool Sql_cmd_analyze_table::execute(THD *thd) { + LEX *m_lex= thd->lex; TABLE_LIST *first_table= m_lex->select_lex.table_list.first; bool res= TRUE; thr_lock_type lock_type = TL_READ_NO_INSERT; - DBUG_ENTER("Analyze_table_statement::execute"); + DBUG_ENTER("Sql_cmd_analyze_table::execute"); if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, FALSE, UINT_MAX, FALSE)) goto error; - thd->enable_slow_log= opt_log_slow_admin_statements; + WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "analyze", lock_type, 1, 0, 0, 0, &handler::ha_analyze, 0); @@ -1093,22 +1261,22 @@ bool Analyze_table_statement::execute(THD *thd) m_lex->query_tables= first_table; error: +WSREP_ERROR_LABEL: DBUG_RETURN(res); } -bool Check_table_statement::execute(THD *thd) +bool Sql_cmd_check_table::execute(THD *thd) { + LEX *m_lex= thd->lex; TABLE_LIST *first_table= m_lex->select_lex.table_list.first; thr_lock_type lock_type = TL_READ_NO_INSERT; bool res= TRUE; - DBUG_ENTER("Check_table_statement::execute"); + DBUG_ENTER("Sql_cmd_check_table::execute"); if (check_table_access(thd, SELECT_ACL, first_table, TRUE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "check", lock_type, 0, 0, HA_OPEN_FOR_REPAIR, 0, &handler::ha_check, &view_check); @@ -1121,18 +1289,19 @@ error: } -bool Optimize_table_statement::execute(THD *thd) +bool Sql_cmd_optimize_table::execute(THD *thd) { + LEX *m_lex= thd->lex; TABLE_LIST *first_table= m_lex->select_lex.table_list.first; bool res= TRUE; - DBUG_ENTER("Optimize_table_statement::execute"); + DBUG_ENTER("Sql_cmd_optimize_table::execute"); if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= (specialflag & (SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC)) ? - mysql_recreate_table(thd, first_table) : + WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); + res= (specialflag & SPECIAL_NO_NEW_FUNC) ? + mysql_recreate_table(thd, first_table, true) : mysql_admin_table(thd, first_table, &m_lex->check_opt, "optimize", TL_WRITE, 1, 0, 0, 0, &handler::ha_optimize, 0); @@ -1148,23 +1317,25 @@ bool Optimize_table_statement::execute(THD *thd) m_lex->query_tables= first_table; error: +WSREP_ERROR_LABEL: DBUG_RETURN(res); } -bool Repair_table_statement::execute(THD *thd) +bool Sql_cmd_repair_table::execute(THD *thd) { + LEX *m_lex= thd->lex; TABLE_LIST *first_table= m_lex->select_lex.table_list.first; bool res= TRUE; - DBUG_ENTER("Repair_table_statement::execute"); + DBUG_ENTER("Sql_cmd_repair_table::execute"); if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; + WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "repair", TL_WRITE, 1, - test(m_lex->check_opt.sql_flags & TT_USEFRM), + MY_TEST(m_lex->check_opt.sql_flags & TT_USEFRM), HA_OPEN_FOR_REPAIR, &prepare_for_repair, &handler::ha_repair, &view_repair); @@ -1180,5 +1351,6 @@ bool Repair_table_statement::execute(THD *thd) m_lex->query_tables= first_table; error: +WSREP_ERROR_LABEL: DBUG_RETURN(res); } diff --git a/sql/sql_admin.h b/sql/sql_admin.h index 43d8f70c6f4..96594fad0cb 100644 --- a/sql/sql_admin.h +++ b/sql/sql_admin.h @@ -17,7 +17,7 @@ #define SQL_TABLE_MAINTENANCE_H /* Must be able to hold ALTER TABLE t PARTITION BY ... KEY ALGORITHM = 1 ... */ -#define SQL_ADMIN_MSG_TEXT_SIZE 128 * 1024 +#define SQL_ADMIN_MSG_TEXT_SIZE (128 * 1024) bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* table_list, LEX_STRING *key_cache_name); @@ -26,109 +26,100 @@ int reassign_keycache_tables(THD* thd, KEY_CACHE *src_cache, KEY_CACHE *dst_cache); /** - Analyze_statement represents the ANALYZE TABLE statement. + Sql_cmd_analyze_table represents the ANALYZE TABLE statement. */ -class Analyze_table_statement : public Sql_statement +class Sql_cmd_analyze_table : public Sql_cmd { public: /** Constructor, used to represent a ANALYZE TABLE statement. - @param lex the LEX structure for this statement. */ - Analyze_table_statement(LEX *lex) - : Sql_statement(lex) + Sql_cmd_analyze_table() {} - ~Analyze_table_statement() + ~Sql_cmd_analyze_table() {} - /** - Execute a ANALYZE TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ bool execute(THD *thd); + + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_ANALYZE; + } }; /** - Check_table_statement represents the CHECK TABLE statement. + Sql_cmd_check_table represents the CHECK TABLE statement. */ -class Check_table_statement : public Sql_statement +class Sql_cmd_check_table : public Sql_cmd { public: /** Constructor, used to represent a CHECK TABLE statement. - @param lex the LEX structure for this statement. */ - Check_table_statement(LEX *lex) - : Sql_statement(lex) + Sql_cmd_check_table() {} - ~Check_table_statement() + ~Sql_cmd_check_table() {} - /** - Execute a CHECK TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ bool execute(THD *thd); -}; + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_CHECK; + } +}; /** - Optimize_table_statement represents the OPTIMIZE TABLE statement. + Sql_cmd_optimize_table represents the OPTIMIZE TABLE statement. */ -class Optimize_table_statement : public Sql_statement +class Sql_cmd_optimize_table : public Sql_cmd { public: /** Constructor, used to represent a OPTIMIZE TABLE statement. - @param lex the LEX structure for this statement. */ - Optimize_table_statement(LEX *lex) - : Sql_statement(lex) + Sql_cmd_optimize_table() {} - ~Optimize_table_statement() + ~Sql_cmd_optimize_table() {} - /** - Execute a OPTIMIZE TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ bool execute(THD *thd); + + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_OPTIMIZE; + } }; /** - Repair_table_statement represents the REPAIR TABLE statement. + Sql_cmd_repair_table represents the REPAIR TABLE statement. */ -class Repair_table_statement : public Sql_statement +class Sql_cmd_repair_table : public Sql_cmd { public: /** Constructor, used to represent a REPAIR TABLE statement. - @param lex the LEX structure for this statement. */ - Repair_table_statement(LEX *lex) - : Sql_statement(lex) + Sql_cmd_repair_table() {} - ~Repair_table_statement() + ~Sql_cmd_repair_table() {} - /** - Execute a REPAIR TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ bool execute(THD *thd); + + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_REPAIR; + } }; #endif diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc index c6c02773286..ddac271146e 100644 --- a/sql/sql_alter.cc +++ b/sql/sql_alter.cc @@ -16,9 +16,177 @@ #include "sql_parse.h" // check_access #include "sql_table.h" // mysql_alter_table, // mysql_exchange_partition +#include "sql_base.h" // open_temporary_tables #include "sql_alter.h" +#include "wsrep_mysqld.h" -bool Alter_table_statement::execute(THD *thd) +Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root) + :drop_list(rhs.drop_list, mem_root), + alter_list(rhs.alter_list, mem_root), + key_list(rhs.key_list, mem_root), + create_list(rhs.create_list, mem_root), + flags(rhs.flags), + keys_onoff(rhs.keys_onoff), + partition_names(rhs.partition_names, mem_root), + num_parts(rhs.num_parts), + requested_algorithm(rhs.requested_algorithm), + requested_lock(rhs.requested_lock) +{ + /* + Make deep copies of used objects. + This is not a fully deep copy - clone() implementations + of Alter_drop, Alter_column, Key, foreign_key, Key_part_spec + do not copy string constants. At the same length the only + reason we make a copy currently is that ALTER/CREATE TABLE + code changes input Alter_info definitions, but string + constants never change. + */ + list_copy_and_replace_each_value(drop_list, mem_root); + list_copy_and_replace_each_value(alter_list, mem_root); + list_copy_and_replace_each_value(key_list, mem_root); + list_copy_and_replace_each_value(create_list, mem_root); + /* partition_names are not deeply copied currently */ +} + + +bool Alter_info::set_requested_algorithm(const LEX_STRING *str) +{ + // To avoid adding new keywords to the grammar, we match strings here. + if (!my_strcasecmp(system_charset_info, str->str, "INPLACE")) + requested_algorithm= ALTER_TABLE_ALGORITHM_INPLACE; + else if (!my_strcasecmp(system_charset_info, str->str, "COPY")) + requested_algorithm= ALTER_TABLE_ALGORITHM_COPY; + else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT")) + requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT; + else + return true; + return false; +} + + +bool Alter_info::set_requested_lock(const LEX_STRING *str) +{ + // To avoid adding new keywords to the grammar, we match strings here. + if (!my_strcasecmp(system_charset_info, str->str, "NONE")) + requested_lock= ALTER_TABLE_LOCK_NONE; + else if (!my_strcasecmp(system_charset_info, str->str, "SHARED")) + requested_lock= ALTER_TABLE_LOCK_SHARED; + else if (!my_strcasecmp(system_charset_info, str->str, "EXCLUSIVE")) + requested_lock= ALTER_TABLE_LOCK_EXCLUSIVE; + else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT")) + requested_lock= ALTER_TABLE_LOCK_DEFAULT; + else + return true; + return false; +} + + +Alter_table_ctx::Alter_table_ctx() + : datetime_field(NULL), error_if_not_empty(false), + tables_opened(0), + db(NULL), table_name(NULL), alias(NULL), + new_db(NULL), new_name(NULL), new_alias(NULL), + fk_error_if_delete_row(false), fk_error_id(NULL), + fk_error_table(NULL) +#ifndef DBUG_OFF + , tmp_table(false) +#endif +{ +} + + +Alter_table_ctx::Alter_table_ctx(THD *thd, TABLE_LIST *table_list, + uint tables_opened_arg, + char *new_db_arg, char *new_name_arg) + : datetime_field(NULL), error_if_not_empty(false), + tables_opened(tables_opened_arg), + new_db(new_db_arg), new_name(new_name_arg), + fk_error_if_delete_row(false), fk_error_id(NULL), + fk_error_table(NULL) +#ifndef DBUG_OFF + , tmp_table(false) +#endif +{ + /* + Assign members db, table_name, new_db and new_name + to simplify further comparisions: we want to see if it's a RENAME + later just by comparing the pointers, avoiding the need for strcmp. + */ + db= table_list->db; + table_name= table_list->table_name; + alias= (lower_case_table_names == 2) ? table_list->alias : table_name; + + if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db)) + new_db= db; + + if (new_name) + { + DBUG_PRINT("info", ("new_db.new_name: '%s'.'%s'", new_db, new_name)); + + if (lower_case_table_names == 1) // Convert new_name/new_alias to lower case + { + my_casedn_str(files_charset_info, new_name); + new_alias= new_name; + } + else if (lower_case_table_names == 2) // Convert new_name to lower case + { + strmov(new_alias= new_alias_buff, new_name); + my_casedn_str(files_charset_info, new_name); + } + else + new_alias= new_name; // LCTN=0 => case sensitive + case preserving + + if (!is_database_changed() && + !my_strcasecmp(table_alias_charset, new_name, table_name)) + { + /* + Source and destination table names are equal: + make is_table_renamed() more efficient. + */ + new_alias= table_name; + new_name= table_name; + } + } + else + { + new_alias= alias; + new_name= table_name; + } + + my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix, + current_pid, thd->thread_id); + /* Safety fix for InnoDB */ + if (lower_case_table_names) + my_casedn_str(files_charset_info, tmp_name); + + if (table_list->table->s->tmp_table == NO_TMP_TABLE) + { + build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0); + + build_table_filename(new_path, sizeof(new_path) - 1, new_db, new_name, "", 0); + + build_table_filename(new_filename, sizeof(new_filename) - 1, + new_db, new_name, reg_ext, 0); + + build_table_filename(tmp_path, sizeof(tmp_path) - 1, new_db, tmp_name, "", + FN_IS_TMP); + } + else + { + /* + We are not filling path, new_path and new_filename members if + we are altering temporary table as these members are not used in + this case. This fact is enforced with assert. + */ + build_tmptable_filename(thd, tmp_path, sizeof(tmp_path)); +#ifndef DBUG_OFF + tmp_table= true; +#endif + } +} + + +bool Sql_cmd_alter_table::execute(THD *thd) { LEX *lex= thd->lex; /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ @@ -38,7 +206,7 @@ bool Alter_table_statement::execute(THD *thd) ulong priv_needed= ALTER_ACL; bool result; - DBUG_ENTER("Alter_table_statement::execute"); + DBUG_ENTER("Sql_cmd_alter_table::execute"); if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */ DBUG_RETURN(TRUE); @@ -46,12 +214,14 @@ bool Alter_table_statement::execute(THD *thd) We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well as for RENAME TO, as being done by SQLCOM_RENAME_TABLE */ - if (alter_info.flags & (ALTER_DROP_PARTITION | ALTER_RENAME)) + if (alter_info.flags & (Alter_info::ALTER_DROP_PARTITION | + Alter_info::ALTER_RENAME)) priv_needed|= DROP_ACL; /* Must be set in the parser */ DBUG_ASSERT(select_lex->db); - DBUG_ASSERT(!(alter_info.flags & ALTER_ADMIN_PARTITION)); + DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_EXCHANGE_PARTITION)); + DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION)); if (check_access(thd, priv_needed, first_table->db, &first_table->grant.privilege, &first_table->grant.m_internal, @@ -63,10 +233,47 @@ bool Alter_table_statement::execute(THD *thd) DBUG_RETURN(TRUE); /* purecov: inspected */ /* If it is a merge table, check privileges for merge children. */ - if (create_info.merge_list.first && - check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, - create_info.merge_list.first, FALSE, UINT_MAX, FALSE)) - DBUG_RETURN(TRUE); + if (create_info.merge_list) + { + /* + The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the + underlying base tables, even if there are temporary tables with the same + names. + + From user's point of view, it might look as if the user must have these + privileges on temporary tables to create a merge table over them. This is + one of two cases when a set of privileges is required for operations on + temporary tables (see also CREATE TABLE). + + The reason for this behavior stems from the following facts: + + - For merge tables, the underlying table privileges are checked only + at CREATE TABLE / ALTER TABLE time. + + In other words, once a merge table is created, the privileges of + the underlying tables can be revoked, but the user will still have + access to the merge table (provided that the user has privileges on + the merge table itself). + + - Temporary tables shadow base tables. + + I.e. there might be temporary and base tables with the same name, and + the temporary table takes the precedence in all operations. + + - For temporary MERGE tables we do not track if their child tables are + base or temporary. As result we can't guarantee that privilege check + which was done in presence of temporary child will stay relevant + later as this temporary table might be removed. + + If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for + the underlying *base* tables, it would create a security breach as in + Bug#12771903. + */ + + if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, + create_info.merge_list, FALSE, UINT_MAX, FALSE)) + DBUG_RETURN(TRUE); + } if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE)) DBUG_RETURN(TRUE); /* purecov: inspected */ @@ -75,9 +282,9 @@ bool Alter_table_statement::execute(THD *thd) { // Rename of table TABLE_LIST tmp_table; - bzero((char*) &tmp_table,sizeof(tmp_table)); - tmp_table.table_name= lex->name.str; - tmp_table.db= select_lex->db; + tmp_table.init_one_table(select_lex->db, strlen(select_lex->db), + lex->name.str, lex->name.length, + lex->name.str, TL_IGNORE); tmp_table.grant.privilege= priv; if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE, UINT_MAX, FALSE)) @@ -86,16 +293,31 @@ bool Alter_table_statement::execute(THD *thd) /* Don't yet allow changing of symlinks with ALTER TABLE */ if (create_info.data_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, ER_THD(thd, WARN_OPTION_IGNORED), "DATA DIRECTORY"); if (create_info.index_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, ER_THD(thd, WARN_OPTION_IGNORED), "INDEX DIRECTORY"); create_info.data_file_name= create_info.index_file_name= NULL; - thd->enable_slow_log= opt_log_slow_admin_statements; +#ifdef WITH_WSREP + TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl); + + if (WSREP(thd) && + (!thd->is_current_stmt_binlog_format_row() || + !find_temporary_table(thd, first_table))) + { + WSREP_TO_ISOLATION_BEGIN_ALTER(((lex->name.str) ? select_lex->db : NULL), + ((lex->name.str) ? lex->name.str : NULL), + first_table, + &alter_info); + + thd->variables.auto_increment_offset = 1; + thd->variables.auto_increment_increment = 1; + } +#endif /* WITH_WSREP */ result= mysql_alter_table(thd, select_lex->db, lex->name.str, &create_info, @@ -103,7 +325,41 @@ bool Alter_table_statement::execute(THD *thd) &alter_info, select_lex->order_list.elements, select_lex->order_list.first, - lex->ignore, lex->online); + lex->ignore); DBUG_RETURN(result); + +WSREP_ERROR_LABEL: + WSREP_WARN("ALTER TABLE isolation failure"); + DBUG_RETURN(TRUE); +} + +bool Sql_cmd_discard_import_tablespace::execute(THD *thd) +{ + /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ + SELECT_LEX *select_lex= &thd->lex->select_lex; + /* first table of first SELECT_LEX */ + TABLE_LIST *table_list= (TABLE_LIST*) select_lex->table_list.first; + + if (check_access(thd, ALTER_ACL, table_list->db, + &table_list->grant.privilege, + &table_list->grant.m_internal, + 0, 0)) + return true; + + if (check_grant(thd, ALTER_ACL, table_list, false, UINT_MAX, false)) + return true; + + /* + Check if we attempt to alter mysql.slow_log or + mysql.general_log table and return an error if + it is the case. + TODO: this design is obsolete and will be removed. + */ + if (check_if_log_table(table_list, TRUE, "ALTER")) + return true; + + return + mysql_discard_or_import_tablespace(thd, table_list, + m_tablespace_op == DISCARD_TABLESPACE); } diff --git a/sql/sql_alter.h b/sql/sql_alter.h index 6660748f666..a4505f1d6c1 100644 --- a/sql/sql_alter.h +++ b/sql/sql_alter.h @@ -1,4 +1,5 @@ -/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2014, Oracle and/or its affiliates. + Copyright (c) 2013, 2014, Monty Program Ab. 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 @@ -16,51 +17,410 @@ #ifndef SQL_ALTER_TABLE_H #define SQL_ALTER_TABLE_H +class Alter_drop; +class Alter_column; +class Key; + /** - Alter_table_common represents the common properties of the ALTER TABLE + Data describing the table being created by CREATE TABLE or + altered by ALTER TABLE. +*/ + +class Alter_info +{ +public: + /* + These flags are set by the parser and describes the type of + operation(s) specified by the ALTER TABLE statement. + + They do *not* describe the type operation(s) to be executed + by the storage engine. For example, we don't yet know the + type of index to be added/dropped. + */ + + // Set for ADD [COLUMN] + static const uint ALTER_ADD_COLUMN = 1L << 0; + + // Set for DROP [COLUMN] + static const uint ALTER_DROP_COLUMN = 1L << 1; + + // Set for CHANGE [COLUMN] | MODIFY [CHANGE] + // Set by mysql_recreate_table() + static const uint ALTER_CHANGE_COLUMN = 1L << 2; + + // Set for ADD INDEX | ADD KEY | ADD PRIMARY KEY | ADD UNIQUE KEY | + // ADD UNIQUE INDEX | ALTER ADD [COLUMN] + static const uint ALTER_ADD_INDEX = 1L << 3; + + // Set for DROP PRIMARY KEY | DROP FOREIGN KEY | DROP KEY | DROP INDEX + static const uint ALTER_DROP_INDEX = 1L << 4; + + // Set for RENAME [TO] + static const uint ALTER_RENAME = 1L << 5; + + // Set for ORDER BY + static const uint ALTER_ORDER = 1L << 6; + + // Set for table_options + static const uint ALTER_OPTIONS = 1L << 7; + + // Set for ALTER [COLUMN] ... SET DEFAULT ... | DROP DEFAULT + static const uint ALTER_CHANGE_COLUMN_DEFAULT = 1L << 8; + + // Set for DISABLE KEYS | ENABLE KEYS + static const uint ALTER_KEYS_ONOFF = 1L << 9; + + // Set for FORCE + // Set for ENGINE(same engine) + // Set by mysql_recreate_table() + static const uint ALTER_RECREATE = 1L << 10; + + // Set for ADD PARTITION + static const uint ALTER_ADD_PARTITION = 1L << 11; + + // Set for DROP PARTITION + static const uint ALTER_DROP_PARTITION = 1L << 12; + + // Set for COALESCE PARTITION + static const uint ALTER_COALESCE_PARTITION = 1L << 13; + + // Set for REORGANIZE PARTITION ... INTO + static const uint ALTER_REORGANIZE_PARTITION = 1L << 14; + + // Set for partition_options + static const uint ALTER_PARTITION = 1L << 15; + + // Set for LOAD INDEX INTO CACHE ... PARTITION + // Set for CACHE INDEX ... PARTITION + static const uint ALTER_ADMIN_PARTITION = 1L << 16; + + // Set for REORGANIZE PARTITION + static const uint ALTER_TABLE_REORG = 1L << 17; + + // Set for REBUILD PARTITION + static const uint ALTER_REBUILD_PARTITION = 1L << 18; + + // Set for partitioning operations specifying ALL keyword + static const uint ALTER_ALL_PARTITION = 1L << 19; + + // Set for REMOVE PARTITIONING + static const uint ALTER_REMOVE_PARTITIONING = 1L << 20; + + // Set for ADD FOREIGN KEY + static const uint ADD_FOREIGN_KEY = 1L << 21; + + // Set for DROP FOREIGN KEY + static const uint DROP_FOREIGN_KEY = 1L << 22; + + // Set for EXCHANGE PARITION + static const uint ALTER_EXCHANGE_PARTITION = 1L << 23; + + // Set by Sql_cmd_alter_table_truncate_partition::execute() + static const uint ALTER_TRUNCATE_PARTITION = 1L << 24; + + // Set for ADD [COLUMN] FIRST | AFTER + static const uint ALTER_COLUMN_ORDER = 1L << 25; + + + enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE }; + + /** + The different values of the ALGORITHM clause. + Describes which algorithm to use when altering the table. + */ + enum enum_alter_table_algorithm + { + // In-place if supported, copy otherwise. + ALTER_TABLE_ALGORITHM_DEFAULT, + + // In-place if supported, error otherwise. + ALTER_TABLE_ALGORITHM_INPLACE, + + // Copy if supported, error otherwise. + ALTER_TABLE_ALGORITHM_COPY + }; + + + /** + The different values of the LOCK clause. + Describes the level of concurrency during ALTER TABLE. + */ + enum enum_alter_table_lock + { + // Maximum supported level of concurency for the given operation. + ALTER_TABLE_LOCK_DEFAULT, + + // Allow concurrent reads & writes. If not supported, give erorr. + ALTER_TABLE_LOCK_NONE, + + // Allow concurrent reads only. If not supported, give error. + ALTER_TABLE_LOCK_SHARED, + + // Block reads and writes. + ALTER_TABLE_LOCK_EXCLUSIVE + }; + + + // Columns and keys to be dropped. + List<Alter_drop> drop_list; + // Columns for ALTER_CHANGE_COLUMN_DEFAULT. + List<Alter_column> alter_list; + // List of keys, used by both CREATE and ALTER TABLE. + List<Key> key_list; + // List of columns, used by both CREATE and ALTER TABLE. + List<Create_field> create_list; + // Type of ALTER TABLE operation. + uint flags; + // Enable or disable keys. + enum_enable_or_disable keys_onoff; + // List of partitions. + List<char> partition_names; + // Number of partitions. + uint num_parts; + // Type of ALTER TABLE algorithm. + enum_alter_table_algorithm requested_algorithm; + // Type of ALTER TABLE lock. + enum_alter_table_lock requested_lock; + + + Alter_info() : + flags(0), + keys_onoff(LEAVE_AS_IS), + num_parts(0), + requested_algorithm(ALTER_TABLE_ALGORITHM_DEFAULT), + requested_lock(ALTER_TABLE_LOCK_DEFAULT) + {} + + void reset() + { + drop_list.empty(); + alter_list.empty(); + key_list.empty(); + create_list.empty(); + flags= 0; + keys_onoff= LEAVE_AS_IS; + num_parts= 0; + partition_names.empty(); + requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT; + requested_lock= ALTER_TABLE_LOCK_DEFAULT; + } + + + /** + Construct a copy of this object to be used for mysql_alter_table + and mysql_create_table. + + Historically, these two functions modify their Alter_info + arguments. This behaviour breaks re-execution of prepared + statements and stored procedures and is compensated by always + supplying a copy of Alter_info to these functions. + + @param rhs Alter_info to make copy of + @param mem_root Mem_root for new Alter_info + + @note You need to use check the error in THD for out + of memory condition after calling this function. + */ + Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root); + + + /** + Parses the given string and sets requested_algorithm + if the string value matches a supported value. + Supported values: INPLACE, COPY, DEFAULT + + @param str String containing the supplied value + @retval false Supported value found, state updated + @retval true Not supported value, no changes made + */ + bool set_requested_algorithm(const LEX_STRING *str); + + + /** + Parses the given string and sets requested_lock + if the string value matches a supported value. + Supported values: NONE, SHARED, EXCLUSIVE, DEFAULT + + @param str String containing the supplied value + @retval false Supported value found, state updated + @retval true Not supported value, no changes made + */ + + bool set_requested_lock(const LEX_STRING *str); + +private: + Alter_info &operator=(const Alter_info &rhs); // not implemented + Alter_info(const Alter_info &rhs); // not implemented +}; + + +/** Runtime context for ALTER TABLE. */ +class Alter_table_ctx +{ +public: + Alter_table_ctx(); + + Alter_table_ctx(THD *thd, TABLE_LIST *table_list, uint tables_opened_arg, + char *new_db_arg, char *new_name_arg); + + /** + @return true if the table is moved to another database, false otherwise. + */ + bool is_database_changed() const + { return (new_db != db); }; + + /** + @return true if the table is renamed, false otherwise. + */ + bool is_table_renamed() const + { return (is_database_changed() || new_name != table_name); }; + + /** + @return filename (including .frm) for the new table. + */ + const char *get_new_filename() const + { + DBUG_ASSERT(!tmp_table); + return new_filename; + } + + /** + @return path to the original table. + */ + const char *get_path() const + { + DBUG_ASSERT(!tmp_table); + return path; + } + + /** + @return path to the new table. + */ + const char *get_new_path() const + { + DBUG_ASSERT(!tmp_table); + return new_path; + } + + /** + @return path to the temporary table created during ALTER TABLE. + */ + const char *get_tmp_path() const + { return tmp_path; } + + /** + Mark ALTER TABLE as needing to produce foreign key error if + it deletes a row from the table being changed. + */ + void set_fk_error_if_delete_row(FOREIGN_KEY_INFO *fk) + { + fk_error_if_delete_row= true; + fk_error_id= fk->foreign_id->str; + fk_error_table= fk->foreign_table->str; + } + +public: + Create_field *datetime_field; + bool error_if_not_empty; + uint tables_opened; + char *db; + char *table_name; + char *alias; + char *new_db; + char *new_name; + char *new_alias; + char tmp_name[80]; + /** + Indicates that if a row is deleted during copying of data from old version + of table to the new version ER_FK_CANNOT_DELETE_PARENT error should be + emitted. + */ + bool fk_error_if_delete_row; + /** Name of foreign key for the above error. */ + const char *fk_error_id; + /** Name of table for the above error. */ + const char *fk_error_table; + +private: + char new_filename[FN_REFLEN + 1]; + char new_alias_buff[FN_REFLEN + 1]; + char path[FN_REFLEN + 1]; + char new_path[FN_REFLEN + 1]; + char tmp_path[FN_REFLEN + 1]; + +#ifndef DBUG_OFF + /** Indicates that we are altering temporary table. Used only in asserts. */ + bool tmp_table; +#endif + + Alter_table_ctx &operator=(const Alter_table_ctx &rhs); // not implemented + Alter_table_ctx(const Alter_table_ctx &rhs); // not implemented +}; + + +/** + Sql_cmd_common_alter_table represents the common properties of the ALTER TABLE statements. @todo move Alter_info and other ALTER generic structures from Lex here. */ -class Alter_table_common : public Sql_statement +class Sql_cmd_common_alter_table : public Sql_cmd { protected: /** Constructor. - @param lex the LEX structure for this statement. */ - Alter_table_common(LEX *lex) - : Sql_statement(lex) + Sql_cmd_common_alter_table() {} - virtual ~Alter_table_common() + virtual ~Sql_cmd_common_alter_table() {} + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_ALTER_TABLE; + } }; /** - Alter_table_statement represents the generic ALTER TABLE statement. + Sql_cmd_alter_table represents the generic ALTER TABLE statement. @todo move Alter_info and other ALTER specific structures from Lex here. */ -class Alter_table_statement : public Alter_table_common +class Sql_cmd_alter_table : public Sql_cmd_common_alter_table { public: /** Constructor, used to represent a ALTER TABLE statement. - @param lex the LEX structure for this statement. */ - Alter_table_statement(LEX *lex) - : Alter_table_common(lex) + Sql_cmd_alter_table() {} - ~Alter_table_statement() + ~Sql_cmd_alter_table() {} - /** - Execute a ALTER TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ bool execute(THD *thd); }; + +/** + Sql_cmd_alter_table_tablespace represents ALTER TABLE + IMPORT/DISCARD TABLESPACE statements. +*/ +class Sql_cmd_discard_import_tablespace : public Sql_cmd_common_alter_table +{ +public: + enum enum_tablespace_op_type + { + DISCARD_TABLESPACE, IMPORT_TABLESPACE + }; + + Sql_cmd_discard_import_tablespace(enum_tablespace_op_type tablespace_op_arg) + : m_tablespace_op(tablespace_op_arg) + {} + + bool execute(THD *thd); + +private: + const enum_tablespace_op_type m_tablespace_op; +}; + #endif diff --git a/sql/sql_analyse.cc b/sql/sql_analyse.cc index 36026a39616..45f4f87f172 100644 --- a/sql/sql_analyse.cc +++ b/sql/sql_analyse.cc @@ -29,6 +29,7 @@ #define MYSQL_LEX 1 +#include <my_global.h> #include "sql_priv.h" #include "procedure.h" #include "sql_analyse.h" @@ -282,24 +283,24 @@ bool get_ev_num_info(EV_NUM_INFO *ev_info, NUM_INFO *info, const char *num) { if (((longlong) info->ullval) < 0) return 0; // Impossible to store as a negative number - ev_info->llval = -(longlong) max((ulonglong) -ev_info->llval, + ev_info->llval = -(longlong) MY_MAX((ulonglong) -ev_info->llval, info->ullval); - ev_info->min_dval = (double) -max(-ev_info->min_dval, info->dval); + ev_info->min_dval = (double) -MY_MAX(-ev_info->min_dval, info->dval); } else // ulonglong is as big as bigint in MySQL { if ((check_ulonglong(num, info->integers) == DECIMAL_NUM)) return 0; - ev_info->ullval = (ulonglong) max(ev_info->ullval, info->ullval); - ev_info->max_dval = (double) max(ev_info->max_dval, info->dval); + ev_info->ullval = (ulonglong) MY_MAX(ev_info->ullval, info->ullval); + ev_info->max_dval = (double) MY_MAX(ev_info->max_dval, info->dval); } return 1; } // get_ev_num_info -void free_string(String *s) +void free_string(void* str, TREE_FREE, void*) { - s->free(); + ((String*)str)->free(); } @@ -1040,7 +1041,7 @@ String *field_decimal::avg(String *s, ha_rows rows) my_decimal_div(E_DEC_FATAL_ERROR, &avg_val, sum+cur_sum, &num, prec_increment); /* TODO remove this after decimal_div returns proper frac */ my_decimal_round(E_DEC_FATAL_ERROR, &avg_val, - min(sum[cur_sum].frac + prec_increment, DECIMAL_MAX_SCALE), + MY_MIN(sum[cur_sum].frac + prec_increment, DECIMAL_MAX_SCALE), FALSE,&rounded_avg); my_decimal2string(E_DEC_FATAL_ERROR, &rounded_avg, 0, 0, '0', s); return s; @@ -1065,7 +1066,7 @@ String *field_decimal::std(String *s, ha_rows rows) my_decimal_div(E_DEC_FATAL_ERROR, &tmp, &sum2, &num, prec_increment); my_decimal2double(E_DEC_FATAL_ERROR, &tmp, &std_sqr); s->set_real(((double) std_sqr <= 0.0 ? 0.0 : sqrt(std_sqr)), - min(item->decimals + prec_increment, NOT_FIXED_DEC), my_thd_charset); + MY_MIN(item->decimals + prec_increment, NOT_FIXED_DEC), my_thd_charset); return s; } @@ -1080,7 +1081,7 @@ int collect_string(String *element, else info->found = 1; info->str->append('\''); - if (append_escaped(info->str, element)) + if (info->str->append_for_single_quote(element)) return 1; info->str->append('\''); return 0; @@ -1165,27 +1166,29 @@ int collect_ulonglong(ulonglong *element, } // collect_ulonglong -bool analyse::change_columns(List<Item> &field_list) +bool analyse::change_columns(THD *thd, List<Item> &field_list) { + MEM_ROOT *mem_root= thd->mem_root; field_list.empty(); - func_items[0] = new Item_proc_string("Field_name", 255); - func_items[1] = new Item_proc_string("Min_value", 255); + func_items[0]= new (mem_root) Item_proc_string(thd, "Field_name", 255); + func_items[1]= new (mem_root) Item_proc_string(thd, "Min_value", 255); func_items[1]->maybe_null = 1; - func_items[2] = new Item_proc_string("Max_value", 255); + func_items[2]= new (mem_root) Item_proc_string(thd, "Max_value", 255); func_items[2]->maybe_null = 1; - func_items[3] = new Item_proc_int("Min_length"); - func_items[4] = new Item_proc_int("Max_length"); - func_items[5] = new Item_proc_int("Empties_or_zeros"); - func_items[6] = new Item_proc_int("Nulls"); - func_items[7] = new Item_proc_string("Avg_value_or_avg_length", 255); - func_items[8] = new Item_proc_string("Std", 255); + func_items[3]= new (mem_root) Item_proc_int(thd, "Min_length"); + func_items[4]= new (mem_root) Item_proc_int(thd, "Max_length"); + func_items[5]= new (mem_root) Item_proc_int(thd, "Empties_or_zeros"); + func_items[6]= new (mem_root) Item_proc_int(thd, "Nulls"); + func_items[7]= new (mem_root) Item_proc_string(thd, "Avg_value_or_avg_length", 255); + func_items[8]= new (mem_root) Item_proc_string(thd, "Std", 255); func_items[8]->maybe_null = 1; - func_items[9] = new Item_proc_string("Optimal_fieldtype", - max(64, output_str_length)); + func_items[9]= new (mem_root) Item_proc_string(thd, "Optimal_fieldtype", + MY_MAX(64, + output_str_length)); for (uint i = 0; i < array_elements(func_items); i++) - field_list.push_back(func_items[i]); + field_list.push_back(func_items[i], thd->mem_root); result_fields = field_list; return 0; } // analyse::change_columns @@ -1239,56 +1242,3 @@ uint check_ulonglong(const char *str, uint length) return ((uchar) str[-1] <= (uchar) cmp[-1]) ? smaller : bigger; } /* check_ulonlong */ - -/* - Quote special characters in a string. - - SYNOPSIS - append_escaped(to_str, from_str) - to_str (in) A pointer to a String. - from_str (to) A pointer to an allocated string - - DESCRIPTION - append_escaped() takes a String type variable, where it appends - escaped the second argument. Only characters that require escaping - will be escaped. - - RETURN VALUES - 0 Success - 1 Out of memory -*/ - -bool append_escaped(String *to_str, String *from_str) -{ - char *from, *end, c; - - if (to_str->realloc(to_str->length() + from_str->length())) - return 1; - - from= (char*) from_str->ptr(); - end= from + from_str->length(); - for (; from < end; from++) - { - c= *from; - switch (c) { - case '\0': - c= '0'; - break; - case '\032': - c= 'Z'; - break; - case '\\': - case '\'': - break; - default: - goto normal_character; - } - if (to_str->append('\\')) - return 1; - - normal_character: - if (to_str->append(c)) - return 1; - } - return 0; -} diff --git a/sql/sql_analyse.h b/sql/sql_analyse.h index 34c8e2da3d4..738737f0edc 100644 --- a/sql/sql_analyse.h +++ b/sql/sql_analyse.h @@ -68,7 +68,7 @@ int compare_ulonglong2(void* cmp_arg __attribute__((unused)), int compare_decimal2(int* len, const char *s, const char *t); Procedure *proc_analyse_init(THD *thd, ORDER *param, select_result *result, List<Item> &field_list); -void free_string(String*); +void free_string(void* str, TREE_FREE, void*); class analyse; class field_info :public Sql_alloc @@ -121,7 +121,7 @@ public: must_be_blob(0), was_zero_fill(0), was_maybe_zerofill(0), can_be_still_num(1) { init_tree(&tree, 0, 0, sizeof(String), (qsort_cmp2) sortcmp2, - 0, (tree_element_free) free_string, NULL); }; + free_string, NULL, MYF(MY_THREAD_SPECIFIC)); }; void add(); void get_opt_type(String*, ha_rows); @@ -162,7 +162,7 @@ public: { bin_size= my_decimal_get_binary_size(a->max_length, a->decimals); init_tree(&tree, 0, 0, bin_size, (qsort_cmp2)compare_decimal2, - 0, 0, (void *)&bin_size); + 0, (void *)&bin_size, MYF(MY_THREAD_SPECIFIC)); }; void add(); @@ -190,7 +190,8 @@ public: field_real(Item* a, analyse* b) :field_info(a,b), min_arg(0), max_arg(0), sum(0), sum_sqr(0), max_notzero_dec_len(0) { init_tree(&tree, 0, 0, sizeof(double), - (qsort_cmp2) compare_double2, 0, NULL, NULL); } + (qsort_cmp2) compare_double2, NULL, NULL, + MYF(MY_THREAD_SPECIFIC)); } void add(); void get_opt_type(String*, ha_rows); @@ -244,7 +245,8 @@ public: field_longlong(Item* a, analyse* b) :field_info(a,b), min_arg(0), max_arg(0), sum(0), sum_sqr(0) { init_tree(&tree, 0, 0, sizeof(longlong), - (qsort_cmp2) compare_longlong2, 0, NULL, NULL); } + (qsort_cmp2) compare_longlong2, NULL, NULL, + MYF(MY_THREAD_SPECIFIC)); } void add(); void get_opt_type(String*, ha_rows); @@ -289,7 +291,8 @@ public: field_ulonglong(Item* a, analyse * b) :field_info(a,b), min_arg(0), max_arg(0), sum(0),sum_sqr(0) { init_tree(&tree, 0, 0, sizeof(ulonglong), - (qsort_cmp2) compare_ulonglong2, 0, NULL, NULL); } + (qsort_cmp2) compare_ulonglong2, NULL, NULL, + MYF(MY_THREAD_SPECIFIC)); } void add(); void get_opt_type(String*, ha_rows); String *get_min_arg(String *s) { s->set(min_arg,my_thd_charset); return s; } @@ -352,7 +355,7 @@ public: } } virtual void add() {} - virtual bool change_columns(List<Item> &fields); + virtual bool change_columns(THD *thd, List<Item> &fields); virtual int send_row(List<Item> &field_list); virtual void end_group(void) {} virtual int end_of_records(void); @@ -361,6 +364,4 @@ public: List<Item> &field_list); }; -bool append_escaped(String *to_str, String *from_str); - #endif /* SQL_ANALYSE_INCLUDED */ diff --git a/sql/sql_analyze_stmt.cc b/sql/sql_analyze_stmt.cc new file mode 100644 index 00000000000..68299d024fd --- /dev/null +++ b/sql/sql_analyze_stmt.cc @@ -0,0 +1,143 @@ +/* + Copyright (c) 2015 MariaDB Corporation Ab + + 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 02111-1301 USA */ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include <my_global.h> +#include "sql_priv.h" +#include "sql_select.h" +#include "my_json_writer.h" + +void Filesort_tracker::print_json_members(Json_writer *writer) +{ + const char *varied_str= "(varied across executions)"; + writer->add_member("r_loops").add_ll(get_r_loops()); + + if (get_r_loops() && time_tracker.timed) + { + writer->add_member("r_total_time_ms"). + add_double(time_tracker.get_time_ms()); + } + if (r_limit != HA_POS_ERROR) + { + writer->add_member("r_limit"); + if (r_limit == 0) + writer->add_str(varied_str); + else + writer->add_ll((longlong) rint(r_limit)); + } + + writer->add_member("r_used_priority_queue"); + if (r_used_pq == get_r_loops()) + writer->add_bool(true); + else if (r_used_pq == 0) + writer->add_bool(false); + else + writer->add_str(varied_str); + + writer->add_member("r_output_rows").add_ll((longlong) rint(r_output_rows / + get_r_loops())); + + if (sort_passes) + { + writer->add_member("r_sort_passes").add_ll((longlong) rint(sort_passes / + get_r_loops())); + } + + if (sort_buffer_size != 0) + { + writer->add_member("r_buffer_size"); + if (sort_buffer_size == ulonglong(-1)) + writer->add_str(varied_str); + else + writer->add_size(sort_buffer_size); + } +} + + +/* + Report that we are doing a filesort. + @return + Tracker object to be used with filesort +*/ + +Filesort_tracker *Sort_and_group_tracker::report_sorting(THD *thd) +{ + DBUG_ASSERT(cur_action < MAX_QEP_ACTIONS); + + if (total_actions) + { + /* This is not the first execution. Check */ + if (qep_actions[cur_action] != EXPL_ACTION_FILESORT) + { + varied_executions= true; + cur_action++; + if (!dummy_fsort_tracker) + dummy_fsort_tracker= new (thd->mem_root) Filesort_tracker(is_analyze); + return dummy_fsort_tracker; + } + return qep_actions_data[cur_action++].filesort_tracker; + } + + Filesort_tracker *fs_tracker= new(thd->mem_root)Filesort_tracker(is_analyze); + qep_actions_data[cur_action].filesort_tracker= fs_tracker; + qep_actions[cur_action++]= EXPL_ACTION_FILESORT; + + return fs_tracker; +} + + +void Sort_and_group_tracker::report_tmp_table(TABLE *tbl) +{ + DBUG_ASSERT(cur_action < MAX_QEP_ACTIONS); + if (total_actions) + { + /* This is not the first execution. Check if the steps match. */ + // todo: should also check that tmp.table kinds are the same. + if (qep_actions[cur_action] != EXPL_ACTION_TEMPTABLE) + varied_executions= true; + } + + if (!varied_executions) + { + qep_actions[cur_action]= EXPL_ACTION_TEMPTABLE; + // qep_actions_data[cur_action]= .... + } + + cur_action++; +} + + +void Sort_and_group_tracker::report_duplicate_removal() +{ + DBUG_ASSERT(cur_action < MAX_QEP_ACTIONS); + if (total_actions) + { + /* This is not the first execution. Check if the steps match. */ + if (qep_actions[cur_action] != EXPL_ACTION_REMOVE_DUPS) + varied_executions= true; + } + + if (!varied_executions) + { + qep_actions[cur_action]= EXPL_ACTION_REMOVE_DUPS; + } + + cur_action++; +} + diff --git a/sql/sql_analyze_stmt.h b/sql/sql_analyze_stmt.h new file mode 100644 index 00000000000..7d3d0853417 --- /dev/null +++ b/sql/sql_analyze_stmt.h @@ -0,0 +1,457 @@ +/* + Copyright (c) 2015 MariaDB Corporation Ab + + 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 02111-1301 USA */ + +/* + +== ANALYZE-stmt classes == + +This file contains classes for supporting "ANALYZE statement" feature. These are +a set of data structures that can be used to store the data about how the +statement executed. + +There are two kinds of data collection: + +1. Various counters. We assume that incrementing counters has very low +overhead. Because of that, execution code increments counters unconditionally +(even when not running "ANALYZE $statement" commands. You run regular SELECT/ +UPDATE/DELETE/etc and the counters are incremented). + +As a free bonus, this lets us print detailed information into the slow query +log, should the query be slow. + +2. Timing data. Measuring the time it took to run parts of query has noticeable +overhead. Because of that, we measure the time only when running "ANALYZE +$stmt"). + +*/ + +/* + A class for tracking time it takes to do a certain action +*/ +class Exec_time_tracker +{ +protected: + ulonglong count; + ulonglong cycles; + ulonglong last_start; + + void cycles_stop_tracking() + { + ulonglong end= my_timer_cycles(); + cycles += end - last_start; + if (unlikely(end < last_start)) + cycles += ULONGLONG_MAX; + } +public: + Exec_time_tracker() : count(0), cycles(0) {} + + // interface for collecting time + void start_tracking() + { + last_start= my_timer_cycles(); + } + + void stop_tracking() + { + count++; + cycles_stop_tracking(); + } + + // interface for getting the time + ulonglong get_loops() const { return count; } + double get_time_ms() const + { + // convert 'cycles' to milliseconds. + return 1000 * ((double)cycles) / sys_timer_info.cycles.frequency; + } +}; + + +/* + A class for counting certain actions (in all queries), and optionally + collecting the timings (in ANALYZE queries). +*/ + +class Time_and_counter_tracker: public Exec_time_tracker +{ +public: + const bool timed; + + Time_and_counter_tracker(bool timed_arg) : timed(timed_arg) + {} + + /* Loops are counted in both ANALYZE and regular queries, as this is cheap */ + void incr_loops() { count++; } + + /* + Unlike Exec_time_tracker::stop_tracking, we don't increase loops. + */ + void stop_tracking() + { + cycles_stop_tracking(); + } +}; + +#define ANALYZE_START_TRACKING(tracker) \ + { \ + (tracker)->incr_loops(); \ + if (unlikely((tracker)->timed)) \ + { (tracker)->start_tracking(); } \ + } + +#define ANALYZE_STOP_TRACKING(tracker) \ + if (unlikely((tracker)->timed)) \ + { (tracker)->stop_tracking(); } + +/* + A class for collecting read statistics. + + The idea is that we run several scans. Each scans gets rows, and then filters + some of them out. We count scans, rows, and rows left after filtering. + + (note: at the moment, the class is not actually tied to a physical table. + It can be used to track reading from files, buffers, etc). +*/ + +class Table_access_tracker +{ +public: + Table_access_tracker() : + r_scans(0), r_rows(0), /*r_rows_after_table_cond(0),*/ + r_rows_after_where(0) + {} + + ha_rows r_scans; /* How many scans were ran on this join_tab */ + ha_rows r_rows; /* How many rows we've got after that */ + ha_rows r_rows_after_where; /* Rows after applying attached part of WHERE */ + + bool has_scans() { return (r_scans != 0); } + ha_rows get_loops() { return r_scans; } + double get_avg_rows() + { + return r_scans ? ((double)r_rows / r_scans): 0; + } + + double get_filtered_after_where() + { + double r_filtered; + if (r_rows > 0) + r_filtered= (double)r_rows_after_where / r_rows; + else + r_filtered= 1.0; + + return r_filtered; + } + + inline void on_scan_init() { r_scans++; } + inline void on_record_read() { r_rows++; } + inline void on_record_after_where() { r_rows_after_where++; } +}; + + +class Json_writer; + +/* + This stores the data about how filesort executed. + + A few things from here (e.g. r_used_pq, r_limit) belong to the query plan, + however, these parameters are calculated right during the execution so we + can't easily put them into the query plan. + + The class is designed to handle multiple invocations of filesort(). +*/ + +class Filesort_tracker : public Sql_alloc +{ +public: + Filesort_tracker(bool do_timing) : + time_tracker(do_timing), r_limit(0), r_used_pq(0), + r_examined_rows(0), r_sorted_rows(0), r_output_rows(0), + sort_passes(0), + sort_buffer_size(0) + {} + + /* Functions that filesort uses to report various things about its execution */ + + inline void report_use(ha_rows r_limit_arg) + { + if (!time_tracker.get_loops()) + r_limit= r_limit_arg; + else + r_limit= (r_limit != r_limit_arg)? 0: r_limit_arg; + + ANALYZE_START_TRACKING(&time_tracker); + } + inline void incr_pq_used() { r_used_pq++; } + + inline void report_row_numbers(ha_rows examined_rows, + ha_rows sorted_rows, + ha_rows returned_rows) + { + r_examined_rows += examined_rows; + r_sorted_rows += sorted_rows; + r_output_rows += returned_rows; + } + + inline void report_merge_passes_at_start(ulong passes) + { + sort_passes -= passes; + } + inline void report_merge_passes_at_end(ulong passes) + { + ANALYZE_STOP_TRACKING(&time_tracker); + sort_passes += passes; + } + + inline void report_sort_buffer_size(size_t bufsize) + { + if (sort_buffer_size) + sort_buffer_size= ulonglong(-1); // multiple buffers of different sizes + else + sort_buffer_size= bufsize; + } + + /* Functions to get the statistics */ + void print_json_members(Json_writer *writer); + + ulonglong get_r_loops() const { return time_tracker.get_loops(); } + double get_avg_examined_rows() + { + return ((double)r_examined_rows) / get_r_loops(); + } + double get_avg_returned_rows() + { + return ((double)r_output_rows) / get_r_loops(); + } + double get_r_filtered() + { + if (r_examined_rows > 0) + return ((double)r_sorted_rows / r_examined_rows); + else + return 1.0; + } +private: + Time_and_counter_tracker time_tracker; + + //ulonglong r_loops; /* How many times filesort was invoked */ + /* + LIMIT is typically a constant. There is never "LIMIT 0". + HA_POS_ERROR means we never had a limit + 0 means different values of LIMIT were used in + different filesort invocations + other value means the same LIMIT value was used every time. + */ + ulonglong r_limit; + ulonglong r_used_pq; /* How many times PQ was used */ + + /* How many rows were examined (before checking the select->cond) */ + ulonglong r_examined_rows; + + /* + How many rows were put into sorting (this is examined_rows minus rows that + didn't pass the WHERE condition) + */ + ulonglong r_sorted_rows; + + /* + How many rows were returned. This is equal to r_sorted_rows, unless there + was a LIMIT N clause in which case filesort would not have returned more + than N rows. + */ + ulonglong r_output_rows; + + /* How many sorts in total (divide by r_count to get the average) */ + ulonglong sort_passes; + + /* + 0 - means not used (or not known + (ulonglong)-1 - multiple + other - value + */ + ulonglong sort_buffer_size; +}; + + +typedef enum +{ + EXPL_NO_TMP_TABLE=0, + EXPL_TMP_TABLE_BUFFER, + EXPL_TMP_TABLE_GROUP, + EXPL_TMP_TABLE_DISTINCT +} enum_tmp_table_use; + + +typedef enum +{ + EXPL_ACTION_EOF, /* not-an-action */ + EXPL_ACTION_FILESORT, + EXPL_ACTION_TEMPTABLE, + EXPL_ACTION_REMOVE_DUPS, +} enum_qep_action; + + +/* + This is to track how a JOIN object has resolved ORDER/GROUP BY/DISTINCT + + We are not tied to the query plan at all, because query plan does not have + sufficient information. *A lot* of decisions about ordering/grouping are + made at very late stages (in JOIN::exec, JOIN::init_execution, in + create_sort_index and even in create_tmp_table). + + The idea is that operations that happen during select execution will report + themselves. We have these operations: + - Sorting with filesort() + - Duplicate row removal (the one done by remove_duplicates()). + - Use of temporary table to buffer the result. + + There is also "Selection" operation, done by do_select(). It reads rows, + there are several distinct cases: + 1. doing the join operation on the base tables + 2. reading the temporary table + 3. reading the filesort output + it would be nice to build execution graph, e.g. + + Select(JOIN op) -> temp.table -> filesort -> Select(filesort result) + + the problem is that there is no way to tell what a do_select() call will do. + + Our solution is not to have explicit selection operations. We make these + assumptions about the query plan: + - Select(JOIN op) is the first operation in the query plan + - Unless the first recorded operation is filesort(). filesort() is unable + read result of a select, so when we find it first, the query plan is: + + filesort(first join table) -> Select(JOIN op) -> ... + + the other popular query plan is: + + Select (JOIN op) -> temp.table -> filesort() -> ... + +///TODO: handle repeated execution with subselects! +*/ + +class Sort_and_group_tracker : public Sql_alloc +{ + enum { MAX_QEP_ACTIONS = 5 }; + + /* Query actions in the order they were made. */ + enum_qep_action qep_actions[MAX_QEP_ACTIONS]; + + /* Number for the next action */ + int cur_action; + + /* + Non-zero means there was already an execution which had + #total_actions actions + */ + int total_actions; + + int get_n_actions() + { + return total_actions? total_actions: cur_action; + } + + /* + TRUE<=>there were executions which took different sort/buffer/de-duplicate + routes. The counter values are not meaningful. + */ + bool varied_executions; + + /* Details about query actions */ + union + { + Filesort_tracker *filesort_tracker; + enum_tmp_table_use tmp_table; + } + qep_actions_data[MAX_QEP_ACTIONS]; + + Filesort_tracker *dummy_fsort_tracker; + bool is_analyze; +public: + Sort_and_group_tracker(bool is_analyze_arg) : + cur_action(0), total_actions(0), varied_executions(false), + dummy_fsort_tracker(NULL), + is_analyze(is_analyze_arg) + {} + + /*************** Reporting interface ***************/ + /* Report that join execution is started */ + void report_join_start() + { + if (!total_actions && cur_action != 0) + { + /* This is a second execution */ + total_actions= cur_action; + } + cur_action= 0; + } + + /* + Report that a temporary table is created. The next step is to write to the + this tmp. table + */ + void report_tmp_table(TABLE *tbl); + + /* + Report that we are doing a filesort. + @return + Tracker object to be used with filesort + */ + Filesort_tracker *report_sorting(THD *thd); + + /* + Report that remove_duplicates() is invoked [on a temp. table]. + We don't collect any statistics on this operation, yet. + */ + void report_duplicate_removal(); + + friend class Iterator; + /*************** Statistics retrieval interface ***************/ + bool had_varied_executions() { return varied_executions; } + + class Iterator + { + Sort_and_group_tracker *owner; + int idx; + public: + Iterator(Sort_and_group_tracker *owner_arg) : + owner(owner_arg), idx(owner_arg->get_n_actions() - 1) + {} + + enum_qep_action get_next(Filesort_tracker **tracker/*, + enum_tmp_table_use *tmp_table_use*/) + { + /* Walk back through the array... */ + if (idx < 0) + return EXPL_ACTION_EOF; + switch (owner->qep_actions[idx]) + { + case EXPL_ACTION_FILESORT: + *tracker= owner->qep_actions_data[idx].filesort_tracker; + break; + case EXPL_ACTION_TEMPTABLE: + //*tmp_table_use= tmp_table_kind[tmp_table_idx++]; + break; + default: + break; + } + return owner->qep_actions[idx--]; + } + + bool is_last_element() { return idx == -1; } + }; +}; + diff --git a/sql/sql_array.h b/sql/sql_array.h index 67f1f1c2ad7..3e75a9ea546 100644 --- a/sql/sql_array.h +++ b/sql/sql_array.h @@ -19,8 +19,81 @@ #include <my_sys.h> +/** + A wrapper class which provides array bounds checking. + We do *not* own the array, we simply have a pointer to the first element, + and a length. + + @remark + We want the compiler-generated versions of: + - the copy CTOR (memberwise initialization) + - the assignment operator (memberwise assignment) + + @param Element_type The type of the elements of the container. + */ +template <typename Element_type> class Bounds_checked_array +{ +public: + Bounds_checked_array() : m_array(NULL), m_size(0) {} + + Bounds_checked_array(Element_type *el, size_t size_arg) + : m_array(el), m_size(size_arg) + {} + + void reset() { m_array= NULL; m_size= 0; } + + void reset(Element_type *array_arg, size_t size_arg) + { + m_array= array_arg; + m_size= size_arg; + } + + /** + Set a new bound on the array. Does not resize the underlying + array, so the new size must be smaller than or equal to the + current size. + */ + void resize(size_t new_size) + { + DBUG_ASSERT(new_size <= m_size); + m_size= new_size; + } + + Element_type &operator[](size_t n) + { + DBUG_ASSERT(n < m_size); + return m_array[n]; + } + + const Element_type &operator[](size_t n) const + { + DBUG_ASSERT(n < m_size); + return m_array[n]; + } + + size_t element_size() const { return sizeof(Element_type); } + size_t size() const { return m_size; } + + bool is_null() const { return m_array == NULL; } + + void pop_front() + { + DBUG_ASSERT(m_size > 0); + m_array+= 1; + m_size-= 1; + } + + Element_type *array() const { return m_array; } + +private: + Element_type *m_array; + size_t m_size; +}; + /* A typesafe wrapper around DYNAMIC_ARRAY + + TODO: Change creator to take a THREAD_SPECIFIC option. */ template <class Elem> class Dynamic_array @@ -29,114 +102,154 @@ template <class Elem> class Dynamic_array public: Dynamic_array(uint prealloc=16, uint increment=16) { - my_init_dynamic_array(&array, sizeof(Elem), prealloc, increment); + init(prealloc, increment); + } + + Dynamic_array(MEM_ROOT *root, uint prealloc=16, uint increment=16) + { + void *init_buffer= alloc_root(root, sizeof(Elem) * prealloc); + my_init_dynamic_array2(&array, sizeof(Elem), init_buffer, + prealloc, increment, MYF(0)); } - Elem& at(int idx) + void init(uint prealloc=16, uint increment=16) + { + init_dynamic_array2(&array, sizeof(Elem), 0, prealloc, increment, MYF(0)); + } + + /** + @note Though formally this could be declared "const" it would be + misleading at it returns a non-const pointer to array's data. + */ + Elem& at(size_t idx) + { + DBUG_ASSERT(idx < array.elements); + return *(((Elem*)array.buffer) + idx); + } + /// Const variant of at(), which cannot change data + const Elem& at(size_t idx) const { return *(((Elem*)array.buffer) + idx); } + /// @returns pointer to first element Elem *front() { return (Elem*)array.buffer; } + /// @returns pointer to first element + const Elem *front() const + { + return (const Elem*)array.buffer; + } + + /// @returns pointer to last element Elem *back() { - return ((Elem*)array.buffer) + array.elements; + return ((Elem*)array.buffer) + array.elements - 1; } - bool append(Elem &el) + /// @returns pointer to last element + const Elem *back() const { - return (insert_dynamic(&array, (uchar*)&el)); + return ((const Elem*)array.buffer) + array.elements - 1; } - int elements() + /** + @retval false ok + @retval true OOM, @c my_error() has been called. + */ + bool append(const Elem &el) { - return array.elements; + return insert_dynamic(&array, &el); } - ~Dynamic_array() + bool append_val(Elem el) { - delete_dynamic(&array); + return (insert_dynamic(&array, (uchar*)&el)); } - typedef int (*CMP_FUNC)(const Elem *el1, const Elem *el2); + bool push(Elem &el) + { + return append(el); + } - void sort(CMP_FUNC cmp_func) + /// Pops the last element. Does nothing if array is empty. + Elem& pop() { - my_qsort(array.buffer, array.elements, sizeof(Elem), (qsort_cmp)cmp_func); + return *((Elem*)pop_dynamic(&array)); } -}; -/* - Array of pointers to Elem that uses memory from MEM_ROOT + void del(uint idx) + { + delete_dynamic_element(&array, idx); + } - MEM_ROOT has no realloc() so this is supposed to be used for cases when - reallocations are rare. -*/ + size_t elements() const + { + return array.elements; + } -template <class Elem> class Array -{ - enum {alloc_increment = 16}; - Elem **buffer; - uint n_elements, max_element; -public: - Array(MEM_ROOT *mem_root, uint prealloc=16) + void elements(size_t num_elements) { - buffer= (Elem**)alloc_root(mem_root, prealloc * sizeof(Elem**)); - max_element = buffer? prealloc : 0; - n_elements= 0; + DBUG_ASSERT(num_elements <= array.max_element); + array.elements= num_elements; } - Elem& at(int idx) + void clear() { - return *(((Elem*)buffer) + idx); + elements(0); } - Elem **front() + void set(uint idx, const Elem &el) { - return buffer; + set_dynamic(&array, &el, idx); } - Elem **back() + void freeze() { - return buffer + n_elements; + freeze_size(&array); } - bool append(MEM_ROOT *mem_root, Elem *el) + bool resize(size_t new_size, Elem default_val) { - if (n_elements == max_element) + size_t old_size= elements(); + if (allocate_dynamic(&array, new_size)) + return true; + + if (new_size > old_size) { - Elem **newbuf; - if (!(newbuf= (Elem**)alloc_root(mem_root, (n_elements + alloc_increment)* - sizeof(Elem**)))) + set_dynamic(&array, (uchar*)&default_val, new_size - 1); + /*for (size_t i= old_size; i != new_size; i++) { - return FALSE; - } - memcpy(newbuf, buffer, n_elements*sizeof(Elem*)); - buffer= newbuf; + at(i)= default_val; + }*/ } - buffer[n_elements++]= el; - return FALSE; + return false; } - int elements() + ~Dynamic_array() { - return n_elements; + delete_dynamic(&array); } - void clear() + void free_memory() { - n_elements= 0; + delete_dynamic(&array); } - typedef int (*CMP_FUNC)(Elem * const *el1, Elem *const *el2); + typedef int (*CMP_FUNC)(const Elem *el1, const Elem *el2); void sort(CMP_FUNC cmp_func) { - my_qsort(buffer, n_elements, sizeof(Elem*), (qsort_cmp)cmp_func); + my_qsort(array.buffer, array.elements, sizeof(Elem), (qsort_cmp)cmp_func); + } + + typedef int (*CMP_FUNC2)(void *, const Elem *el1, const Elem *el2); + void sort(CMP_FUNC2 cmp_func, void *data) + { + my_qsort2(array.buffer, array.elements, sizeof(Elem), (qsort2_cmp)cmp_func, data); } }; diff --git a/sql/sql_audit.cc b/sql/sql_audit.cc index 108b74cac5f..60a75cb06e7 100644 --- a/sql/sql_audit.cc +++ b/sql/sql_audit.cc @@ -13,6 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "sql_audit.h" @@ -183,7 +184,7 @@ static my_bool acquire_plugins(THD *thd, plugin_ref plugin, void *arg) { /* specify some reasonable initialization defaults */ my_init_dynamic_array(&thd->audit_class_plugins, - sizeof(plugin_ref), 16, 16); + sizeof(plugin_ref), 16, 16, MYF(0)); } /* lock the plugin and add it to the list */ @@ -368,8 +369,7 @@ int initialize_audit_plugin(st_plugin_int *plugin) { st_mysql_audit *data= (st_mysql_audit*) plugin->plugin->info; - if (!data->class_mask || !data->event_notify || - !data->class_mask[0]) + if (!data->event_notify || !data->class_mask[0]) { sql_print_error("Plugin '%s' has invalid data.", plugin->name.str); diff --git a/sql/sql_audit.h b/sql/sql_audit.h index 3f3f97a2730..68106f099cc 100644 --- a/sql/sql_audit.h +++ b/sql/sql_audit.h @@ -43,6 +43,11 @@ static inline bool mysql_audit_general_enabled() return mysql_global_audit_mask[0] & MYSQL_AUDIT_GENERAL_CLASSMASK; } +static inline bool mysql_audit_connection_enabled() +{ + return mysql_global_audit_mask[0] & MYSQL_AUDIT_CONNECTION_CLASSMASK; +} + static inline bool mysql_audit_table_enabled() { return mysql_global_audit_mask[0] & MYSQL_AUDIT_TABLE_CLASSMASK; @@ -52,6 +57,7 @@ static inline bool mysql_audit_table_enabled() static inline void mysql_audit_notify(THD *thd, uint event_class, uint event_subtype, ...) { } #define mysql_audit_general_enabled() 0 +#define mysql_audit_connection_enabled() 0 #define mysql_audit_table_enabled() 0 #endif extern void mysql_audit_release(THD *thd); @@ -133,7 +139,7 @@ void mysql_audit_general(THD *thd, uint event_subtype, query= thd->query_string; user= user_buff; userlen= make_user_name(thd, user_buff); - rows= thd->warning_info->current_row_for_warning(); + rows= thd->get_stmt_da()->current_row_for_warning(); db= thd->db; db_length= thd->db_length; } @@ -153,46 +159,69 @@ void mysql_audit_general(THD *thd, uint event_subtype, } } -#define MYSQL_AUDIT_NOTIFY_CONNECTION_CONNECT(thd) mysql_audit_notify(\ - (thd), MYSQL_AUDIT_CONNECTION_CLASS, MYSQL_AUDIT_CONNECTION_CONNECT,\ - (thd)->stmt_da->is_error() ? (thd)->stmt_da->sql_errno() : 0,\ - (thd)->thread_id, (thd)->security_ctx->user,\ - (thd)->security_ctx->user ? strlen((thd)->security_ctx->user) : 0,\ - (thd)->security_ctx->priv_user, strlen((thd)->security_ctx->priv_user),\ - (thd)->security_ctx->external_user,\ - (thd)->security_ctx->external_user ?\ - strlen((thd)->security_ctx->external_user) : 0,\ - (thd)->security_ctx->proxy_user, strlen((thd)->security_ctx->proxy_user),\ - (thd)->security_ctx->host,\ - (thd)->security_ctx->host ? strlen((thd)->security_ctx->host) : 0,\ - (thd)->security_ctx->ip,\ - (thd)->security_ctx->ip ? strlen((thd)->security_ctx->ip) : 0,\ - (thd)->db, (thd)->db ? strlen((thd)->db) : 0) - -#define MYSQL_AUDIT_NOTIFY_CONNECTION_DISCONNECT(thd, errcode)\ - mysql_audit_notify(\ - (thd), MYSQL_AUDIT_CONNECTION_CLASS, MYSQL_AUDIT_CONNECTION_DISCONNECT,\ - (errcode), (thd)->thread_id, (thd)->security_ctx->user,\ - (thd)->security_ctx->user ? strlen((thd)->security_ctx->user) : 0,\ - 0, 0, 0, 0, 0, 0, (thd)->security_ctx->host,\ - (thd)->security_ctx->host ? strlen((thd)->security_ctx->host) : 0,\ - 0, 0, 0, 0) - -#define MYSQL_AUDIT_NOTIFY_CONNECTION_CHANGE_USER(thd) mysql_audit_notify(\ - (thd), MYSQL_AUDIT_CONNECTION_CLASS, MYSQL_AUDIT_CONNECTION_CHANGE_USER,\ - (thd)->stmt_da->is_error() ? (thd)->stmt_da->sql_errno() : 0,\ - (thd)->thread_id, (thd)->security_ctx->user,\ - (thd)->security_ctx->user ? strlen((thd)->security_ctx->user) : 0,\ - (thd)->security_ctx->priv_user, strlen((thd)->security_ctx->priv_user),\ - (thd)->security_ctx->external_user,\ - (thd)->security_ctx->external_user ?\ - strlen((thd)->security_ctx->external_user) : 0,\ - (thd)->security_ctx->proxy_user, strlen((thd)->security_ctx->proxy_user),\ - (thd)->security_ctx->host,\ - (thd)->security_ctx->host ? strlen((thd)->security_ctx->host) : 0,\ - (thd)->security_ctx->ip,\ - (thd)->security_ctx->ip ? strlen((thd)->security_ctx->ip) : 0,\ - (thd)->db, (thd)->db ? strlen((thd)->db) : 0) +static inline +void mysql_audit_notify_connection_connect(THD *thd) +{ + if (mysql_audit_connection_enabled()) + { + const Security_context *sctx= thd->security_ctx; + Diagnostics_area *da= thd->get_stmt_da(); + mysql_audit_notify(thd, MYSQL_AUDIT_CONNECTION_CLASS, + MYSQL_AUDIT_CONNECTION_CONNECT, + da->is_error() ? da->sql_errno() : 0, + thd->thread_id, + sctx->user, sctx->user ? strlen(sctx->user) : 0, + sctx->priv_user, strlen(sctx->priv_user), + sctx->external_user, + sctx->external_user ? strlen(sctx->external_user) : 0, + sctx->proxy_user, strlen(sctx->proxy_user), + sctx->host, sctx->host ? strlen(sctx->host) : 0, + sctx->ip, sctx->ip ? strlen(sctx->ip) : 0, + thd->db, thd->db ? strlen(thd->db) : 0); + } +} + +static inline +void mysql_audit_notify_connection_disconnect(THD *thd, int errcode) +{ + if (mysql_audit_connection_enabled()) + { + const Security_context *sctx= thd->security_ctx; + mysql_audit_notify(thd, MYSQL_AUDIT_CONNECTION_CLASS, + MYSQL_AUDIT_CONNECTION_DISCONNECT, + errcode, thd->thread_id, + sctx->user, sctx->user ? strlen(sctx->user) : 0, + sctx->priv_user, strlen(sctx->priv_user), + sctx->external_user, + sctx->external_user ? strlen(sctx->external_user) : 0, + sctx->proxy_user, strlen(sctx->proxy_user), + sctx->host, sctx->host ? strlen(sctx->host) : 0, + sctx->ip, sctx->ip ? strlen(sctx->ip) : 0, + thd->db, thd->db ? strlen(thd->db) : 0); + } +} + +static inline +void mysql_audit_notify_connection_change_user(THD *thd) +{ + if (mysql_audit_connection_enabled()) + { + const Security_context *sctx= thd->security_ctx; + Diagnostics_area *da= thd->get_stmt_da(); + mysql_audit_notify(thd, MYSQL_AUDIT_CONNECTION_CLASS, + MYSQL_AUDIT_CONNECTION_CHANGE_USER, + da->is_error() ? da->sql_errno() : 0, + thd->thread_id, + sctx->user, sctx->user ? strlen(sctx->user) : 0, + sctx->priv_user, strlen(sctx->priv_user), + sctx->external_user, + sctx->external_user ? strlen(sctx->external_user) : 0, + sctx->proxy_user, strlen(sctx->proxy_user), + sctx->host, sctx->host ? strlen(sctx->host) : 0, + sctx->ip, sctx->ip ? strlen(sctx->ip) : 0, + thd->db, thd->db ? strlen(thd->db) : 0); + } +} static inline void mysql_audit_external_lock(THD *thd, TABLE_SHARE *share, int lock) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 272aa11977d..733a3a1f3ed 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -17,8 +17,8 @@ /* Basic functions needed by many modules */ +#include <my_global.h> #include "sql_base.h" // setup_table_map -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "unireg.h" #include "debug_sync.h" @@ -27,7 +27,6 @@ // mysql_lock_have_duplicate #include "sql_show.h" // append_identifier #include "strfunc.h" // find_type -#include "parse_file.h" // sql_parse_prepare, File_parser #include "sql_view.h" // mysql_make_view, VIEW_ANY_ACL #include "sql_parse.h" // check_table_access #include "sql_insert.h" // kill_delayed_threads @@ -49,25 +48,28 @@ #include "sql_trigger.h" #include "transaction.h" #include "sql_prepare.h" +#include "sql_statistics.h" #include <m_ctype.h> #include <my_dir.h> #include <hash.h> #include "rpl_filter.h" #include "sql_table.h" // build_table_filename -#include "datadict.h" // dd_frm_type() +#include "datadict.h" // dd_frm_is_view() #include "sql_hset.h" // Hash_set +#include "rpl_rli.h" // rpl_group_info #ifdef __WIN__ #include <io.h> #endif - +#include "wsrep_mysqld.h" +#include "wsrep_thd.h" bool No_such_table_error_handler::handle_condition(THD *, uint sql_errno, const char*, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char*, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { *cond_hdl= NULL; if (sql_errno == ER_NO_SUCH_TABLE || sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE) @@ -76,7 +78,7 @@ No_such_table_error_handler::handle_condition(THD *, return TRUE; } - if (level == MYSQL_ERROR::WARN_LEVEL_ERROR) + if (level == Sql_condition::WARN_LEVEL_ERROR) m_unhandled_errors++; return FALSE; } @@ -109,9 +111,9 @@ public: bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl); + Sql_condition ** cond_hdl); /** Returns TRUE if there were ER_NO_SUCH_/WRONG_MRG_TABLE and there @@ -139,9 +141,9 @@ bool Repair_mrg_table_error_handler::handle_condition(THD *, uint sql_errno, const char*, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char*, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { *cond_hdl= NULL; if (sql_errno == ER_NO_SUCH_TABLE || @@ -162,679 +164,87 @@ Repair_mrg_table_error_handler::handle_condition(THD *, @{ */ -/** - Protects table_def_hash, used and unused lists in the - TABLE_SHARE object, LRU lists of used TABLEs and used - TABLE_SHAREs, refresh_version and the table id counter. -*/ -mysql_mutex_t LOCK_open; - -#ifdef HAVE_PSI_INTERFACE -static PSI_mutex_key key_LOCK_open; -static PSI_mutex_info all_tdc_mutexes[]= { - { &key_LOCK_open, "LOCK_open", PSI_FLAG_GLOBAL } -}; - -/** - Initialize performance schema instrumentation points - used by the table cache. -*/ - -static void init_tdc_psi_keys(void) -{ - const char *category= "sql"; - int count; - - if (PSI_server == NULL) - return; - - count= array_elements(all_tdc_mutexes); - PSI_server->register_mutex(category, all_tdc_mutexes, count); -} -#endif /* HAVE_PSI_INTERFACE */ - - -/** - Total number of TABLE instances for tables in the table definition cache - (both in use by threads and not in use). This value is accessible to user - as "Open_tables" status variable. -*/ -uint table_cache_count= 0; -/** - List that contains all TABLE instances for tables in the table definition - cache that are not in use by any thread. Recently used TABLE instances are - appended to the end of the list. Thus the beginning of the list contains - tables which have been least recently used. -*/ -TABLE *unused_tables; -HASH table_def_cache; -static TABLE_SHARE *oldest_unused_share, end_of_unused_share; -static bool table_def_inited= 0; -static bool table_def_shutdown_in_progress= 0; - static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, TABLE_SHARE *table_share); static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry); static bool auto_repair_table(THD *thd, TABLE_LIST *table_list); -static void free_cache_entry(TABLE *entry); - -uint cached_open_tables(void) -{ - return table_cache_count; -} - -#ifdef EXTRA_DEBUG -static void check_unused(THD *thd) -{ - uint count= 0, open_files= 0, idx= 0; - TABLE *cur_link, *start_link, *entry; - TABLE_SHARE *share; - DBUG_ENTER("check_unused"); - if ((start_link=cur_link=unused_tables)) - { - do - { - if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next) - { - DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */ - DBUG_VOID_RETURN; /* purecov: inspected */ - } - } while (count++ < table_cache_count && - (cur_link=cur_link->next) != start_link); - if (cur_link != start_link) - { - DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */ - } - } - for (idx=0 ; idx < table_def_cache.records ; idx++) - { - share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); - - I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables); - while ((entry= it++)) - { - /* - We must not have TABLEs in the free list that have their file closed. - */ - DBUG_ASSERT(entry->db_stat && entry->file); - /* Merge children should be detached from a merge parent */ - if (entry->in_use) - { - DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */ - } - /* extra() may assume that in_use is set */ - entry->in_use= thd; - DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); - entry->in_use= 0; - - count--; - open_files++; - } - it.init(share->used_tables); - while ((entry= it++)) - { - if (!entry->in_use) - { - DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */ - } - open_files++; - } - } - if (count != 0) - { - DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */ - count)); /* purecov: inspected */ - } - DBUG_VOID_RETURN; -} -#else -#define check_unused(A) -#endif - - -/* - Create a table cache key +/** + Create a table cache/table definition cache key - SYNOPSIS - create_table_def_key() - thd Thread handler - key Create key here (must be of size MAX_DBKEY_LENGTH) - table_list Table definition - tmp_table Set if table is a tmp table + @param thd Thread context + @param key Buffer for the key to be created (must be of + size MAX_DBKEY_LENGTH). + @param db_name Database name. + @param table_name Table name. - IMPLEMENTATION + @note The table cache_key is created from: db_name + \0 table_name + \0 - if the table is a tmp table, we add the following to make each tmp table + additionally we add the following to make each tmp table unique on the slave: 4 bytes for master thread id 4 bytes pseudo thread id - RETURN - Length of key + @return Length of key. */ -uint create_table_def_key(THD *thd, char *key, - const TABLE_LIST *table_list, - bool tmp_table) +uint create_tmp_table_def_key(THD *thd, char *key, + const char *db, const char *table_name) { - uint key_length= create_table_def_key(key, table_list->db, - table_list->table_name); - - if (tmp_table) - { - int4store(key + key_length, thd->server_id); - int4store(key + key_length + 4, thd->variables.pseudo_thread_id); - key_length+= TMP_TABLE_KEY_EXTRA; - } + uint key_length= tdc_create_key(key, db, table_name); + int4store(key + key_length, thd->variables.server_id); + int4store(key + key_length + 4, thd->variables.pseudo_thread_id); + key_length+= TMP_TABLE_KEY_EXTRA; return key_length; } - -/***************************************************************************** - Functions to handle table definition cach (TABLE_SHARE) -*****************************************************************************/ - -extern "C" uchar *table_def_key(const uchar *record, size_t *length, - my_bool not_used __attribute__((unused))) -{ - TABLE_SHARE *entry=(TABLE_SHARE*) record; - *length= entry->table_cache_key.length; - return (uchar*) entry->table_cache_key.str; -} - - -static void table_def_free_entry(TABLE_SHARE *share) -{ - DBUG_ENTER("table_def_free_entry"); - mysql_mutex_assert_owner(&LOCK_open); - if (share->prev) - { - /* remove from old_unused_share list */ - *share->prev= share->next; - share->next->prev= share->prev; - } - free_table_share(share); - DBUG_VOID_RETURN; -} - - -bool table_def_init(void) -{ - table_def_inited= 1; -#ifdef HAVE_PSI_INTERFACE - init_tdc_psi_keys(); -#endif - mysql_mutex_init(key_LOCK_open, &LOCK_open, MY_MUTEX_INIT_FAST); - oldest_unused_share= &end_of_unused_share; - end_of_unused_share.prev= &oldest_unused_share; - - - return my_hash_init(&table_def_cache, &my_charset_bin, table_def_size, - 0, 0, table_def_key, - (my_hash_free_key) table_def_free_entry, 0) != 0; -} - - /** - Notify table definition cache that process of shutting down server - has started so it has to keep number of TABLE and TABLE_SHARE objects - minimal in order to reduce number of references to pluggable engines. + Get table cache key for a table list element. + + @param table_list[in] Table list element. + @param key[out] On return points to table cache key for the table. + + @note Unlike create_table_def_key() call this function doesn't construct + key in a buffer provider by caller. Instead it relies on the fact + that table list element for which key is requested has properly + initialized MDL_request object and the fact that table definition + cache key is suffix of key used in MDL subsystem. So to get table + definition key it simply needs to return pointer to appropriate + part of MDL_key object nested in this table list element. + Indeed, this means that lifetime of key produced by this call is + limited by the lifetime of table list element which it got as + parameter. + + @return Length of key. */ -void table_def_start_shutdown(void) -{ - if (table_def_inited) - { - mysql_mutex_lock(&LOCK_open); - /* - Ensure that TABLE and TABLE_SHARE objects which are created for - tables that are open during process of plugins' shutdown are - immediately released. This keeps number of references to engine - plugins minimal and allows shutdown to proceed smoothly. - */ - table_def_shutdown_in_progress= TRUE; - mysql_mutex_unlock(&LOCK_open); - /* Free all cached but unused TABLEs and TABLE_SHAREs. */ - close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT); - } -} - - -void table_def_free(void) +uint get_table_def_key(const TABLE_LIST *table_list, const char **key) { - DBUG_ENTER("table_def_free"); - if (table_def_inited) - { - table_def_inited= 0; - /* Free table definitions. */ - my_hash_free(&table_def_cache); - mysql_mutex_destroy(&LOCK_open); - } - DBUG_VOID_RETURN; -} - - -uint cached_table_definitions(void) -{ - return table_def_cache.records; -} - - -/* - Auxiliary routines for manipulating with per-share used/unused and - global unused lists of TABLE objects and table_cache_count counter. - Responsible for preserving invariants between those lists, counter - and TABLE::in_use member. - In fact those routines implement sort of implicit table cache as - part of table definition cache. -*/ - - -/** - Add newly created TABLE object for table share which is going - to be used right away. -*/ - -static void table_def_add_used_table(THD *thd, TABLE *table) -{ - DBUG_ASSERT(table->in_use == thd); - table->s->used_tables.push_front(table); - table_cache_count++; -} - - -/** - Prepare used or unused TABLE instance for destruction by removing - it from share's and global list. -*/ - -static void table_def_remove_table(TABLE *table) -{ - if (table->in_use) - { - /* Remove from per-share chain of used TABLE objects. */ - table->s->used_tables.remove(table); - } - else - { - /* Remove from per-share chain of unused TABLE objects. */ - table->s->free_tables.remove(table); - - /* And global unused chain. */ - table->next->prev=table->prev; - table->prev->next=table->next; - if (table == unused_tables) - { - unused_tables=unused_tables->next; - if (table == unused_tables) - unused_tables=0; - } - check_unused(current_thd); - } - table_cache_count--; -} - - -/** - Mark already existing TABLE instance as used. -*/ - -static void table_def_use_table(THD *thd, TABLE *table) -{ - DBUG_ASSERT(!table->in_use); - - /* Unlink table from list of unused tables for this share. */ - table->s->free_tables.remove(table); - /* Unlink able from global unused tables list. */ - if (table == unused_tables) - { // First unused - unused_tables=unused_tables->next; // Remove from link - if (table == unused_tables) - unused_tables=0; - } - table->prev->next=table->next; /* Remove from unused list */ - table->next->prev=table->prev; - check_unused(thd); - /* Add table to list of used tables for this share. */ - table->s->used_tables.push_front(table); - table->in_use= thd; - /* The ex-unused table must be fully functional. */ - DBUG_ASSERT(table->db_stat && table->file); - /* The children must be detached from the table. */ - DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); -} - - -/** - Mark already existing used TABLE instance as unused. -*/ - -static void table_def_unuse_table(TABLE *table) -{ - THD *thd __attribute__((unused))= table->in_use; - DBUG_ASSERT(table->in_use); - - /* We shouldn't put the table to 'unused' list if the share is old. */ - DBUG_ASSERT(! table->s->has_old_version()); - - table->in_use= 0; - /* Remove table from the list of tables used in this share. */ - table->s->used_tables.remove(table); - /* Add table to the list of unused TABLE objects for this share. */ - table->s->free_tables.push_front(table); - /* Also link it last in the global list of unused TABLE objects. */ - if (unused_tables) - { - table->next=unused_tables; - table->prev=unused_tables->prev; - unused_tables->prev=table; - table->prev->next=table; - } - else - unused_tables=table->next=table->prev=table; - check_unused(thd); -} - - -/* - Get TABLE_SHARE for a table. - - get_table_share() - thd Thread handle - table_list Table that should be opened - key Table cache key - key_length Length of key - db_flags Flags to open_table_def(): - OPEN_VIEW - error out: Error code from open_table_def() - - IMPLEMENTATION - Get a table definition from the table definition cache. - If it doesn't exist, create a new from the table definition file. - - NOTES - We must have wrlock on LOCK_open when we come here - (To be changed later) - - RETURN - 0 Error - # Share for table -*/ - -TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, - uint key_length, uint db_flags, int *error, - my_hash_value_type hash_value) -{ - TABLE_SHARE *share; - DBUG_ENTER("get_table_share"); - - *error= 0; - /* - To be able perform any operation on table we should own - some kind of metadata lock on it. + This call relies on the fact that TABLE_LIST::mdl_request::key object + is properly initialized, so table definition cache can be produced + from key used by MDL subsystem. */ - DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, - table_list->db, - table_list->table_name, - MDL_SHARED)); - - /* Read table definition from cache */ - if ((share= (TABLE_SHARE*) my_hash_search_using_hash_value(&table_def_cache, - hash_value, (uchar*) key, key_length))) - goto found; - - if (!(share= alloc_table_share(table_list, key, key_length))) - { - DBUG_RETURN(0); - } - - /* - We assign a new table id under the protection of LOCK_open. - We do this instead of creating a new mutex - and using it for the sole purpose of serializing accesses to a - static variable, we assign the table id here. We assign it to the - share before inserting it into the table_def_cache to be really - sure that it cannot be read from the cache without having a table - id assigned. - - CAVEAT. This means that the table cannot be used for - binlogging/replication purposes, unless get_table_share() has been - called directly or indirectly. - */ - assign_new_table_id(share); - - if (my_hash_insert(&table_def_cache, (uchar*) share)) - { - free_table_share(share); - DBUG_RETURN(0); // return error - } - if (open_table_def(thd, share, db_flags)) - { - *error= share->error; - (void) my_hash_delete(&table_def_cache, (uchar*) share); - DBUG_RETURN(0); - } - share->ref_count++; // Mark in use - DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", - (ulong) share, share->ref_count)); - DBUG_RETURN(share); - -found: - /* - We found an existing table definition. Return it if we didn't get - an error when reading the table definition from file. - */ - if (share->error) - { - /* Table definition contained an error */ - open_table_error(share, share->error, share->open_errno, share->errarg); - DBUG_RETURN(0); - } - if ((share->is_view && !(db_flags & OPEN_VIEW)) || - (!share->is_view && (db_flags & OPEN_VIEW_ONLY))) - { - open_table_error(share, 1, ENOENT, 0); - DBUG_RETURN(0); - } + DBUG_ASSERT(!strcmp(table_list->get_db_name(), + table_list->mdl_request.key.db_name()) && + !strcmp(table_list->get_table_name(), + table_list->mdl_request.key.name())); - ++share->ref_count; - - if (share->ref_count == 1 && share->prev) - { - /* - Share was not used before and it was in the old_unused_share list - Unlink share from this list - */ - DBUG_PRINT("info", ("Unlinking from not used list")); - *share->prev= share->next; - share->next->prev= share->prev; - share->next= 0; - share->prev= 0; - } - - /* Free cache if too big */ - while (table_def_cache.records > table_def_size && - oldest_unused_share->next) - my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); - - DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", - (ulong) share, share->ref_count)); - DBUG_RETURN(share); + *key= (const char*)table_list->mdl_request.key.ptr() + 1; + return table_list->mdl_request.key.length() - 1; } -/** - Get a table share. If it didn't exist, try creating it from engine - - For arguments and return values, see get_table_share() -*/ - -static TABLE_SHARE * -get_table_share_with_discover(THD *thd, TABLE_LIST *table_list, - char *key, uint key_length, - uint db_flags, int *error, - my_hash_value_type hash_value) - -{ - TABLE_SHARE *share; - bool exists; - DBUG_ENTER("get_table_share_with_discover"); - - share= get_table_share(thd, table_list, key, key_length, db_flags, error, - hash_value); - /* - If share is not NULL, we found an existing share. - - If share is NULL, and there is no error, we're inside - pre-locking, which silences 'ER_NO_SUCH_TABLE' errors - with the intention to silently drop non-existing tables - from the pre-locking list. In this case we still need to try - auto-discover before returning a NULL share. - - Or, we're inside SHOW CREATE VIEW, which - also installs a silencer for ER_NO_SUCH_TABLE error. - - If share is NULL and the error is ER_NO_SUCH_TABLE, this is - the same as above, only that the error was not silenced by - pre-locking or SHOW CREATE VIEW. - - In both these cases it won't harm to try to discover the - table. - - Finally, if share is still NULL, it's a real error and we need - to abort. - - @todo Rework alternative ways to deal with ER_NO_SUCH TABLE. - */ - if (share || - (thd->is_error() && thd->stmt_da->sql_errno() != ER_NO_SUCH_TABLE && - thd->stmt_da->sql_errno() != ER_NO_SUCH_TABLE_IN_ENGINE)) - DBUG_RETURN(share); - - *error= 0; - - /* Table didn't exist. Check if some engine can provide it */ - if (ha_check_if_table_exists(thd, table_list->db, table_list->table_name, - &exists)) - { - thd->clear_error(); - /* Conventionally, the storage engine API does not report errors. */ - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - } - else if (! exists) - { - /* - No such table in any engine. - Hide "Table doesn't exist" errors if the table belongs to a view. - The check for thd->is_error() is necessary to not push an - unwanted error in case the error was already silenced. - @todo Rework the alternative ways to deal with ER_NO_SUCH TABLE. - */ - if (thd->is_error()) - { - if (table_list->parent_l) - { - thd->clear_error(); - my_error(ER_WRONG_MRG_TABLE, MYF(0)); - } - else if (table_list->belong_to_view) - { - TABLE_LIST *view= table_list->belong_to_view; - thd->clear_error(); - my_error(ER_VIEW_INVALID, MYF(0), - view->view_db.str, view->view_name.str); - } - } - } - else - { - thd->clear_error(); - *error= 7; /* Run auto-discover. */ - } - DBUG_RETURN(NULL); -} - - -/** - Mark that we are not using table share anymore. - - @param share Table share - - If the share has no open tables and (we have done a refresh or - if we have already too many open table shares) then delete the - definition. -*/ - -void release_table_share(TABLE_SHARE *share) -{ - DBUG_ENTER("release_table_share"); - DBUG_PRINT("enter", - ("share: 0x%lx table: %s.%s ref_count: %u version: %lu", - (ulong) share, share->db.str, share->table_name.str, - share->ref_count, share->version)); - - mysql_mutex_assert_owner(&LOCK_open); - - DBUG_ASSERT(share->ref_count); - if (!--share->ref_count) - { - if (share->has_old_version() || table_def_shutdown_in_progress) - my_hash_delete(&table_def_cache, (uchar*) share); - else - { - /* Link share last in used_table_share list */ - DBUG_PRINT("info",("moving share to unused list")); - - DBUG_ASSERT(share->next == 0); - share->prev= end_of_unused_share.prev; - *end_of_unused_share.prev= share; - end_of_unused_share.prev= &share->next; - share->next= &end_of_unused_share; - - if (table_def_cache.records > table_def_size) - { - /* Delete the least used share to preserve LRU order. */ - my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); - } - } - } - - DBUG_VOID_RETURN; -} - - -/* - Check if table definition exits in cache - - SYNOPSIS - get_cached_table_share() - db Database name - table_name Table name - - RETURN - 0 Not cached - # TABLE_SHARE for table -*/ - -TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name) -{ - char key[SAFE_NAME_LEN*2+2]; - uint key_length; - mysql_mutex_assert_owner(&LOCK_open); - - key_length= create_table_def_key(key, db, table_name); - return (TABLE_SHARE*) my_hash_search(&table_def_cache, - (uchar*) key, key_length); -} +/***************************************************************************** + Functions to handle table definition cache (TABLE_SHARE) +*****************************************************************************/ /* Create a list for all open tables matching SQL expression @@ -847,62 +257,82 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name) NOTES One gets only a list of tables for which one has any kind of privilege. db and table names are allocated in result struct, so one doesn't need - a lock on LOCK_open when traversing the return list. + a lock when traversing the return list. RETURN VALUES NULL Error (Probably OOM) # Pointer to list of names of open tables. */ -OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) +struct list_open_tables_arg { - int result = 0; - OPEN_TABLE_LIST **start_list, *open_list; + THD *thd; + const char *db; + const char *wild; TABLE_LIST table_list; - DBUG_ENTER("list_open_tables"); + OPEN_TABLE_LIST **start_list, *open_list; +}; - mysql_mutex_lock(&LOCK_open); - bzero((char*) &table_list,sizeof(table_list)); - start_list= &open_list; - open_list=0; - for (uint idx=0 ; result == 0 && idx < table_def_cache.records; idx++) - { - TABLE_SHARE *share= (TABLE_SHARE *)my_hash_element(&table_def_cache, idx); +static my_bool list_open_tables_callback(TDC_element *element, + list_open_tables_arg *arg) +{ + char *db= (char*) element->m_key; + char *table_name= (char*) element->m_key + strlen((char*) element->m_key) + 1; - if (db && my_strcasecmp(system_charset_info, db, share->db.str)) - continue; - if (wild && wild_compare(share->table_name.str, wild, 0)) - continue; + if (arg->db && my_strcasecmp(system_charset_info, arg->db, db)) + return FALSE; + if (arg->wild && wild_compare(table_name, arg->wild, 0)) + return FALSE; - /* Check if user has SELECT privilege for any column in the table */ - table_list.db= share->db.str; - table_list.table_name= share->table_name.str; - table_list.grant.privilege=0; + /* Check if user has SELECT privilege for any column in the table */ + arg->table_list.db= db; + arg->table_list.table_name= table_name; + arg->table_list.grant.privilege= 0; - if (check_table_access(thd,SELECT_ACL,&table_list, TRUE, 1, TRUE)) - continue; + if (check_table_access(arg->thd, SELECT_ACL, &arg->table_list, TRUE, 1, TRUE)) + return FALSE; - if (!(*start_list = (OPEN_TABLE_LIST *) - sql_alloc(sizeof(**start_list)+share->table_cache_key.length))) - { - open_list=0; // Out of memory - break; - } - strmov((*start_list)->table= - strmov(((*start_list)->db= (char*) ((*start_list)+1)), - share->db.str)+1, - share->table_name.str); - (*start_list)->in_use= 0; - I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables); - while (it++) - ++(*start_list)->in_use; - (*start_list)->locked= 0; /* Obsolete. */ - start_list= &(*start_list)->next; - *start_list=0; - } - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(open_list); + if (!(*arg->start_list= (OPEN_TABLE_LIST *) arg->thd->alloc( + sizeof(**arg->start_list) + element->m_key_length))) + return TRUE; + + strmov((*arg->start_list)->table= + strmov(((*arg->start_list)->db= (char*) ((*arg->start_list) + 1)), + db) + 1, table_name); + (*arg->start_list)->in_use= 0; + + mysql_mutex_lock(&element->LOCK_table_share); + TDC_element::All_share_tables_list::Iterator it(element->all_tables); + TABLE *table; + while ((table= it++)) + if (table->in_use) + ++(*arg->start_list)->in_use; + mysql_mutex_unlock(&element->LOCK_table_share); + (*arg->start_list)->locked= 0; /* Obsolete. */ + arg->start_list= &(*arg->start_list)->next; + *arg->start_list= 0; + return FALSE; +} + + +OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) +{ + list_open_tables_arg argument; + DBUG_ENTER("list_open_tables"); + + argument.thd= thd; + argument.db= db; + argument.wild= wild; + bzero((char*) &argument.table_list, sizeof(argument.table_list)); + argument.start_list= &argument.open_list; + argument.open_list= 0; + + if (tdc_iterate(thd, (my_hash_walk_action) list_open_tables_callback, + &argument, true)) + DBUG_RETURN(0); + + DBUG_RETURN(argument.open_list); } /***************************************************************************** @@ -923,33 +353,11 @@ void intern_close_table(TABLE *table) if (table->file) // Not true if placeholder (void) closefrm(table, 1); // close file table->alias.free(); - DBUG_VOID_RETURN; -} - -/* - Remove table from the open table cache - - SYNOPSIS - free_cache_entry() - table Table to remove - - NOTE - We need to have a lock on LOCK_open when calling this -*/ - -static void free_cache_entry(TABLE *table) -{ - DBUG_ENTER("free_cache_entry"); - - /* This should be done before releasing table share. */ - table_def_remove_table(table); - - intern_close_table(table); - my_free(table); DBUG_VOID_RETURN; } + /* Free resources allocated by filesort() and read_record() */ void free_io_cache(TABLE *table) @@ -971,24 +379,28 @@ void free_io_cache(TABLE *table) @param share Table share. - @pre Caller should have LOCK_open mutex. + @pre Caller should have TABLE_SHARE::tdc.LOCK_table_share mutex. */ -static void kill_delayed_threads_for_table(TABLE_SHARE *share) +void kill_delayed_threads_for_table(TDC_element *element) { - I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables); + TDC_element::All_share_tables_list::Iterator it(element->all_tables); TABLE *tab; - mysql_mutex_assert_owner(&LOCK_open); + mysql_mutex_assert_owner(&element->LOCK_table_share); + + if (!delayed_insert_threads) + return; while ((tab= it++)) { THD *in_use= tab->in_use; + DBUG_ASSERT(in_use && tab->s->tdc->flushed); if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && ! in_use->killed) { - in_use->killed= KILL_SYSTEM_THREAD; + in_use->set_killed(KILL_SYSTEM_THREAD); mysql_mutex_lock(&in_use->mysys_var->mutex); if (in_use->mysys_var->current_cond) { @@ -1021,68 +433,78 @@ static void kill_delayed_threads_for_table(TABLE_SHARE *share) lock taken by thread trying to obtain global read lock. */ + +struct close_cached_tables_arg +{ + ulong refresh_version; + TDC_element *element; +}; + + +static my_bool close_cached_tables_callback(TDC_element *element, + close_cached_tables_arg *arg) +{ + mysql_mutex_lock(&element->LOCK_table_share); + if (element->share && element->flushed && + element->version < arg->refresh_version) + { + /* wait_for_old_version() will unlock mutex and free share */ + arg->element= element; + return TRUE; + } + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} + + bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool wait_for_refresh, ulong timeout) { bool result= FALSE; - bool found= TRUE; struct timespec abstime; + ulong refresh_version; DBUG_ENTER("close_cached_tables"); DBUG_ASSERT(thd || (!wait_for_refresh && !tables)); - mysql_mutex_lock(&LOCK_open); + refresh_version= tdc_increment_refresh_version(); + if (!tables) { /* Force close of all open tables. Note that code in TABLE_SHARE::wait_for_old_version() assumes that - incrementing of refresh_version and removal of unused tables and - shares from TDC happens atomically under protection of LOCK_open, - or putting it another way that TDC does not contain old shares - which don't have any tables used. + incrementing of refresh_version is followed by purge of unused table + shares. */ - refresh_version++; - DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", - refresh_version)); kill_delayed_threads(); /* Get rid of all unused TABLE and TABLE_SHARE instances. By doing this we automatically close all tables which were marked as "old". */ - while (unused_tables) - free_cache_entry(unused_tables); + tc_purge(true); /* Free table shares which were not freed implicitly by loop above. */ - while (oldest_unused_share->next) - (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); + tdc_purge(true); } else { bool found=0; for (TABLE_LIST *table= tables; table; table= table->next_local) { - TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name); - - if (share) - { - kill_delayed_threads_for_table(share); - /* tdc_remove_table() calls share->remove_from_cache_at_close() */ - tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db, - table->table_name, TRUE); - found=1; - } + /* tdc_remove_table() also sets TABLE_SHARE::version to 0. */ + found|= tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db, + table->table_name, TRUE); } if (!found) wait_for_refresh=0; // Nothing to wait for } - mysql_mutex_unlock(&LOCK_open); + DBUG_PRINT("info", ("open table definitions: %d", + (int) tdc_records())); if (!wait_for_refresh) DBUG_RETURN(result); - set_timespec(abstime, timeout); - if (thd->locked_tables_mode) { /* @@ -1100,9 +522,10 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, for (TABLE_LIST *table_list= tables_to_reopen; table_list; table_list= table_list->next_global) { + int err; /* A check that the table was locked for write is done by the caller. */ TABLE *table= find_table_for_mdl_upgrade(thd, table_list->db, - table_list->table_name, TRUE); + table_list->table_name, &err); /* May return NULL if this table has already been closed via an alias. */ if (! table) @@ -1114,67 +537,52 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, result= TRUE; goto err_with_reopen; } - close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED); + close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL); } } /* Wait until all threads have closed all the tables we are flushing. */ DBUG_PRINT("info", ("Waiting for other threads to close their open tables")); - while (found && ! thd->killed) - { - TABLE_SHARE *share; - found= FALSE; - /* - To a self-deadlock or deadlocks with other FLUSH threads - waiting on our open HANDLERs, we have to flush them. - */ - mysql_ha_flush(thd); - DEBUG_SYNC(thd, "after_flush_unlock"); + /* + To a self-deadlock or deadlocks with other FLUSH threads + waiting on our open HANDLERs, we have to flush them. + */ + mysql_ha_flush(thd); + DEBUG_SYNC(thd, "after_flush_unlock"); - mysql_mutex_lock(&LOCK_open); + if (!tables) + { + int r= 0; + close_cached_tables_arg argument; + argument.refresh_version= refresh_version; + set_timespec(abstime, timeout); - if (!tables) - { - for (uint idx=0 ; idx < table_def_cache.records ; idx++) - { - share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); - if (share->has_old_version()) - { - found= TRUE; - break; - } - } - } - else - { - for (TABLE_LIST *table= tables; table; table= table->next_local) - { - share= get_cached_table_share(table->db, table->table_name); - if (share && share->has_old_version()) - { - found= TRUE; - break; - } - } - } + while (!thd->killed && + (r= tdc_iterate(thd, + (my_hash_walk_action) close_cached_tables_callback, + &argument)) == 1 && + !argument.element->share->wait_for_old_version(thd, &abstime, + MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL)) + /* no-op */; - if (found) + if (r) + result= TRUE; + } + else + { + for (TABLE_LIST *table= tables; table; table= table->next_local) { - /* - The method below temporarily unlocks LOCK_open and frees - share's memory. - */ - if (share->wait_for_old_version(thd, &abstime, - MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL)) + if (thd->killed) + break; + if (tdc_wait_for_old_version(thd, table->db, table->table_name, timeout, + MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL, + refresh_version)) { - mysql_mutex_unlock(&LOCK_open); result= TRUE; - goto err_with_reopen; + break; } } - - mysql_mutex_unlock(&LOCK_open); } err_with_reopen: @@ -1185,14 +593,14 @@ err_with_reopen: old locks. This should always succeed (unless some external process has removed the tables) */ - thd->locked_tables_list.reopen_tables(thd); + thd->locked_tables_list.reopen_tables(thd, false); /* - Since downgrade_exclusive_lock() won't do anything with shared + Since downgrade_lock() won't do anything with shared metadata lock it is much simpler to go through all open tables rather than picking only those tables that were flushed. */ for (TABLE *tab= thd->open_tables; tab; tab= tab->next) - tab->mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); + tab->mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE); } DBUG_RETURN(result); } @@ -1203,50 +611,72 @@ err_with_reopen: if specified string is NULL, then any table with a connection string. */ -bool close_cached_connection_tables(THD *thd, LEX_STRING *connection) +struct close_cached_connection_tables_arg { - uint idx; - TABLE_LIST tmp, *tables= NULL; - bool result= FALSE; - DBUG_ENTER("close_cached_connections"); - DBUG_ASSERT(thd); + THD *thd; + LEX_STRING *connection; + TABLE_LIST *tables; +}; + + +static my_bool close_cached_connection_tables_callback( + TDC_element *element, close_cached_connection_tables_arg *arg) +{ + TABLE_LIST *tmp; - bzero(&tmp, sizeof(TABLE_LIST)); + mysql_mutex_lock(&element->LOCK_table_share); + /* Ignore if table is not open or does not have a connect_string */ + if (!element->share || !element->share->connect_string.length || + !element->ref_count) + goto end; - mysql_mutex_lock(&LOCK_open); + /* Compare the connection string */ + if (arg->connection && + (arg->connection->length > element->share->connect_string.length || + (arg->connection->length < element->share->connect_string.length && + (element->share->connect_string.str[arg->connection->length] != '/' && + element->share->connect_string.str[arg->connection->length] != '\\')) || + strncasecmp(arg->connection->str, element->share->connect_string.str, + arg->connection->length))) + goto end; - for (idx= 0; idx < table_def_cache.records; idx++) + /* close_cached_tables() only uses these elements */ + if (!(tmp= (TABLE_LIST*) alloc_root(arg->thd->mem_root, sizeof(TABLE_LIST))) || + !(tmp->db= strdup_root(arg->thd->mem_root, element->share->db.str)) || + !(tmp->table_name= strdup_root(arg->thd->mem_root, + element->share->table_name.str))) { - TABLE_SHARE *share= (TABLE_SHARE *) my_hash_element(&table_def_cache, idx); + mysql_mutex_unlock(&element->LOCK_table_share); + return TRUE; + } - /* Ignore if table is not open or does not have a connect_string */ - if (!share->connect_string.length || !share->ref_count) - continue; + tmp->next_local= arg->tables; + arg->tables= tmp; - /* Compare the connection string */ - if (connection && - (connection->length > share->connect_string.length || - (connection->length < share->connect_string.length && - (share->connect_string.str[connection->length] != '/' && - share->connect_string.str[connection->length] != '\\')) || - strncasecmp(connection->str, share->connect_string.str, - connection->length))) - continue; +end: + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} - /* close_cached_tables() only uses these elements */ - tmp.db= share->db.str; - tmp.table_name= share->table_name.str; - tmp.next_local= tables; - tables= (TABLE_LIST *) memdup_root(thd->mem_root, (char*)&tmp, - sizeof(TABLE_LIST)); - } - mysql_mutex_unlock(&LOCK_open); +bool close_cached_connection_tables(THD *thd, LEX_STRING *connection) +{ + close_cached_connection_tables_arg argument; + DBUG_ENTER("close_cached_connections"); + DBUG_ASSERT(thd); - if (tables) - result= close_cached_tables(thd, tables, FALSE, LONG_TIMEOUT); + argument.thd= thd; + argument.connection= connection; + argument.tables= NULL; - DBUG_RETURN(result); + if (tdc_iterate(thd, + (my_hash_walk_action) close_cached_connection_tables_callback, + &argument)) + DBUG_RETURN(true); + + DBUG_RETURN(argument.tables ? + close_cached_tables(thd, argument.tables, FALSE, LONG_TIMEOUT) : + false); } @@ -1262,11 +692,25 @@ bool close_cached_connection_tables(THD *thd, LEX_STRING *connection) static void mark_temp_tables_as_free_for_reuse(THD *thd) { - for (TABLE *table= thd->temporary_tables ; table ; table= table->next) + DBUG_ENTER("mark_temp_tables_as_free_for_reuse"); + + if (thd->query_id == 0) { - if ((table->query_id == thd->query_id) && ! table->open_by_handler) - mark_tmp_table_for_reuse(table); + /* Thread has not executed any statement and has not used any tmp tables */ + DBUG_VOID_RETURN; } + + if (thd->have_temporary_tables()) + { + thd->lock_temporary_tables(); + for (TABLE *table= thd->temporary_tables ; table ; table= table->next) + { + if ((table->query_id == thd->query_id) && ! table->open_by_handler) + mark_tmp_table_for_reuse(table); + } + thd->unlock_temporary_tables(1); + } + DBUG_VOID_RETURN; } @@ -1280,6 +724,7 @@ static void mark_temp_tables_as_free_for_reuse(THD *thd) void mark_tmp_table_for_reuse(TABLE *table) { + DBUG_ENTER("mark_tmp_table_for_reuse"); DBUG_ASSERT(table->s->tmp_table); table->query_id= 0; @@ -1310,6 +755,7 @@ void mark_tmp_table_for_reuse(TABLE *table) LOCK TABLES is allowed (but ignored) for a temporary table. */ table->reginfo.lock_type= TL_WRITE; + DBUG_VOID_RETURN; } @@ -1361,8 +807,6 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table) static void close_open_tables(THD *thd) { - mysql_mutex_assert_not_owner(&LOCK_open); - DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables)); while (thd->open_tables) @@ -1381,38 +825,46 @@ static void close_open_tables(THD *thd) access the table cache key @param[in] extra - HA_EXTRA_PREPRE_FOR_DROP if the table is being dropped - HA_EXTRA_PREPARE_FOR_REANME if the table is being renamed - HA_EXTRA_NOT_USED no drop/rename - In case of drop/reanme the documented behaviour is to + HA_EXTRA_PREPARE_FOR_DROP + - The table is dropped + HA_EXTRA_PREPARE_FOR_RENAME + - The table is renamed + HA_EXTRA_NOT_USED + - The table is marked as closed in the + locked_table_list but kept there so one can call + locked_table_list->reopen_tables() to put it back. + + In case of drop/rename the documented behavior is to implicitly remove the table from LOCK TABLES - list. + list. @pre Must be called with an X MDL lock on the table. */ void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, - ha_extra_function extra) + ha_extra_function extra, + TABLE *skip_table) { char key[MAX_DBKEY_LENGTH]; uint key_length= share->table_cache_key.length; const char *db= key; const char *table_name= db + share->db.length + 1; + bool remove_from_locked_tables= extra != HA_EXTRA_NOT_USED; memcpy(key, share->table_cache_key.str, key_length); - mysql_mutex_assert_not_owner(&LOCK_open); for (TABLE **prev= &thd->open_tables; *prev; ) { TABLE *table= *prev; if (table->s->table_cache_key.length == key_length && - !memcmp(table->s->table_cache_key.str, key, key_length)) + !memcmp(table->s->table_cache_key.str, key, key_length) && + table != skip_table) { thd->locked_tables_list.unlink_from_list(thd, table->pos_in_locked_tables, - extra != HA_EXTRA_NOT_USED); + remove_from_locked_tables); /* Inform handler that there is a drop table or a rename going on */ if (extra != HA_EXTRA_NOT_USED && table->db_stat) { @@ -1434,9 +886,12 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, prev= &table->next; } } - /* Remove the table share from the cache. */ - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name, - FALSE); + if (skip_table == NULL) + { + /* Remove the table share from the cache. */ + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name, + FALSE); + } } @@ -1463,7 +918,7 @@ void close_thread_tables(THD *thd) TABLE *table; DBUG_ENTER("close_thread_tables"); - thd_proc_info(thd, "closing tables"); + THD_STAGE_INFO(thd, stage_closing_tables); #ifdef EXTRA_DEBUG DBUG_PRINT("tcache", ("open tables:")); @@ -1588,16 +1043,14 @@ void close_thread_tables(THD *thd) /* move one table to free list */ -bool close_thread_table(THD *thd, TABLE **table_ptr) +void close_thread_table(THD *thd, TABLE **table_ptr) { - bool found_old_table= 0; TABLE *table= *table_ptr; DBUG_ENTER("close_thread_table"); DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str, table->s->table_name.str, (long) table)); DBUG_ASSERT(table->key_read == 0); DBUG_ASSERT(!table->file || table->file->inited == handler::NONE); - mysql_mutex_assert_not_owner(&LOCK_open); /* The metadata lock must be released after giving back @@ -1621,34 +1074,22 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) if (! table->needs_reopen()) { - /* Avoid having MERGE tables with attached children in unused_tables. */ + /* Avoid having MERGE tables with attached children in table cache. */ table->file->extra(HA_EXTRA_DETACH_CHILDREN); /* Free memory and reset for next loop. */ free_field_buffers_larger_than(table, MAX_TDC_BLOB_SIZE); table->file->ha_reset(); } - mysql_mutex_lock(&LOCK_open); + /* + Do this *before* entering the TABLE_SHARE::tdc.LOCK_table_share + critical section. + */ + if (table->file != NULL) + MYSQL_UNBIND_TABLE(table->file); - if (table->s->has_old_version() || table->needs_reopen() || - table_def_shutdown_in_progress) - { - free_cache_entry(table); - found_old_table= 1; - } - else - { - DBUG_ASSERT(table->file); - table_def_unuse_table(table); - /* - We free the least used table, not the subject table, - to keep the LRU order. - */ - if (table_cache_count > table_cache_size) - free_cache_entry(unused_tables); - } - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(found_old_table); + tc_release_table(table); + DBUG_VOID_RETURN; } @@ -1662,6 +1103,10 @@ static inline uint tmpkeyval(THD *thd, TABLE *table) /* Close all temporary tables created by 'CREATE TEMPORARY TABLE' for thread creates one DROP TEMPORARY TABLE binlog event for each pseudo-thread + + Temporary tables created in a sql slave is closed by + Relay_log_info::close_temporary_tables() + */ bool close_temporary_tables(THD *thd) @@ -1676,6 +1121,7 @@ bool close_temporary_tables(THD *thd) if (!thd->temporary_tables) DBUG_RETURN(FALSE); + DBUG_ASSERT(!thd->rgi_slave); /* Ensure we don't have open HANDLERs for tables we are about to close. @@ -1744,7 +1190,8 @@ bool close_temporary_tables(THD *thd) /* We always quote db,table names though it is slight overkill */ if (found_user_tables && - !(was_quote_show= test(thd->variables.option_bits & OPTION_QUOTE_SHOW_CREATE))) + !(was_quote_show= MY_TEST(thd->variables.option_bits & + OPTION_QUOTE_SHOW_CREATE))) { thd->variables.option_bits |= OPTION_QUOTE_SHOW_CREATE; } @@ -1799,7 +1246,8 @@ bool close_temporary_tables(THD *thd) qinfo.db_len= db.length(); thd->variables.character_set_client= cs_save; - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); + thd->transaction.stmt.mark_dropped_temp_table(); if ((error= (mysql_bin_log.write(&qinfo) || error))) { /* @@ -1817,7 +1265,7 @@ bool close_temporary_tables(THD *thd) sql_print_error("Failed to write the DROP statement for " "temporary tables to binary log"); } - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); thd->variables.pseudo_thread_id= save_pseudo_thread_id; thd->thread_specific_used= save_thread_specific_used; @@ -1876,7 +1324,8 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table, @param thd thread handle @param table table which should be checked @param table_list list of tables - @param check_alias whether to check tables' aliases + @param check_flag whether to check tables' aliases + Currently this is only used by INSERT NOTE: to exclude derived tables from check we use following mechanism: a) during derived table processing set THD::derived_tables_processing @@ -1905,9 +1354,9 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table, static TABLE_LIST* find_dup_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, - bool check_alias) + uint check_flag) { - TABLE_LIST *res; + TABLE_LIST *res= 0; const char *d_name, *t_name, *t_alias; DBUG_ENTER("find_dup_table"); DBUG_PRINT("enter", ("table alias: %s", table->alias)); @@ -1943,17 +1392,15 @@ TABLE_LIST* find_dup_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, retry: DBUG_PRINT("info", ("real table: %s.%s", d_name, t_name)); - for (TABLE_LIST *tl= table_list;;) + for (TABLE_LIST *tl= table_list; tl ; tl= tl->next_global, res= 0) { - if (tl && - tl->select_lex && tl->select_lex->master_unit() && + if (tl->select_lex && tl->select_lex->master_unit() && tl->select_lex->master_unit()->executed) { /* There is no sense to check tables of already executed parts of the query */ - tl= tl->next_global; continue; } /* @@ -1962,23 +1409,29 @@ retry: */ if (! (res= find_table_in_global_list(tl, d_name, t_name))) break; + tl= res; // We can continue search after this table /* Skip if same underlying table. */ if (res->table && (res->table == table->table)) - goto next; + continue; + + if (check_flag & CHECK_DUP_FOR_CREATE) + DBUG_RETURN(res); /* Skip if table alias does not match. */ - if (check_alias) + if (check_flag & CHECK_DUP_ALLOW_DIFFERENT_ALIAS) { - if (lower_case_table_names ? - my_strcasecmp(files_charset_info, t_alias, res->alias) : - strcmp(t_alias, res->alias)) - goto next; + if (my_strcasecmp(table_alias_charset, t_alias, res->alias)) + continue; } /* - Skip if marked to be excluded (could be a derived table) or if - entry is a prelocking placeholder. + If table is not excluded (could be a derived table) and table is not + a prelocking placeholder then we found either a duplicate entry + or a table that is part of a derived table (handled below). + Examples are: + INSERT INTO t1 SELECT * FROM t1; + INSERT INTO t1 SELECT * FROM view_containing_t1; */ if (res->select_lex && !res->select_lex->exclude_from_table_unique_test && @@ -1990,14 +1443,17 @@ retry: processed in derived table or top select of multi-update/multi-delete (exclude_from_table_unique_test) or prelocking placeholder. */ -next: - tl= res->next_global; DBUG_PRINT("info", ("found same copy of table or table which we should skip")); } if (res && res->belong_to_derived) { - /* Try to fix */ + /* + We come here for queries of type: + INSERT INTO t1 (SELECT tmp.a FROM (select * FROM t1) as tmp); + + Try to fix by materializing the derived table + */ TABLE_LIST *derived= res->belong_to_derived; if (derived->is_merged_derived()) { @@ -2029,7 +1485,7 @@ next: TABLE_LIST* unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, - bool check_alias) + uint check_flag) { TABLE_LIST *dup; @@ -2043,12 +1499,12 @@ unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, for (child= table->next_global; child && child->parent_l == table; child= child->next_global) { - if ((dup= find_dup_table(thd, child, child->next_global, check_alias))) + if ((dup= find_dup_table(thd, child, child->next_global, check_flag))) break; } } else - dup= find_dup_table(thd, table, table_list, check_alias); + dup= find_dup_table(thd, table, table_list, check_flag); return dup; } /* @@ -2104,7 +1560,7 @@ void update_non_unique_table_error(TABLE_LIST *update, return; } } - my_error(ER_UPDATE_TABLE_USED, MYF(0), update->alias); + my_error(ER_UPDATE_TABLE_USED, MYF(0), update->alias, operation); } @@ -2117,12 +1573,9 @@ void update_non_unique_table_error(TABLE_LIST *update, TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name) { - TABLE_LIST tl; - - tl.db= (char*) db; - tl.table_name= (char*) table_name; - - return find_temporary_table(thd, &tl); + char key[MAX_DBKEY_LENGTH]; + uint key_length= create_tmp_table_def_key(thd, key, db, table_name); + return find_temporary_table(thd, key, key_length); } @@ -2135,10 +1588,79 @@ TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name) TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl) { + const char *tmp_key; char key[MAX_DBKEY_LENGTH]; - uint key_length= create_table_def_key(thd, key, tl, 1); + uint key_length; - return find_temporary_table(thd, key, key_length); + key_length= get_table_def_key(tl, &tmp_key); + memcpy(key, tmp_key, key_length); + int4store(key + key_length, thd->variables.server_id); + int4store(key + key_length + 4, thd->variables.pseudo_thread_id); + + return find_temporary_table(thd, key, key_length + TMP_TABLE_KEY_EXTRA); +} + + +static bool +use_temporary_table(THD *thd, TABLE *table, TABLE **out_table) +{ + *out_table= table; + if (!table) + return false; + /* + Temporary tables are not safe for parallel replication. They were + designed to be visible to one thread only, so have no table locking. + Thus there is no protection against two conflicting transactions + committing in parallel and things like that. + + So for now, anything that uses temporary tables will be serialised + with anything before it, when using parallel replication. + + ToDo: We might be able to introduce a reference count or something + on temp tables, and have slave worker threads wait for it to reach + zero before being allowed to use the temp table. Might not be worth + it though, as statement-based replication using temporary tables is + in any case rather fragile. + */ + if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec && + thd->wait_for_prior_commit()) + return true; + /* + We need to set the THD as it may be different in case of + parallel replication + */ + if (table->in_use != thd) + { + table->in_use= thd; +#ifdef REMOVE_AFTER_MERGE_WITH_10 + if (thd->rgi_slave) + { + /* + We may be stealing an opened temporary tables from one slave + thread to another, we need to let the performance schema know that, + for aggregates per thread to work properly. + */ + MYSQL_UNBIND_TABLE(table->file); + MYSQL_REBIND_TABLE(table->file); + } +#endif + } + return false; +} + +bool +find_and_use_temporary_table(THD *thd, const char *db, const char *table_name, + TABLE **out_table) +{ + return use_temporary_table(thd, find_temporary_table(thd, db, table_name), + out_table); +} + + +bool +find_and_use_temporary_table(THD *thd, const TABLE_LIST *tl, TABLE **out_table) +{ + return use_temporary_table(thd, find_temporary_table(thd, tl), out_table); } @@ -2152,16 +1674,22 @@ TABLE *find_temporary_table(THD *thd, const char *table_key, uint table_key_length) { + TABLE *result= 0; + if (!thd->have_temporary_tables()) + return NULL; + + thd->lock_temporary_tables(); for (TABLE *table= thd->temporary_tables; table; table= table->next) { if (table->s->table_cache_key.length == table_key_length && !memcmp(table->s->table_cache_key.str, table_key, table_key_length)) { - return table; + result= table; + break; } } - - return NULL; + thd->unlock_temporary_tables(0); + return result; } @@ -2186,35 +1714,33 @@ TABLE *find_temporary_table(THD *thd, thd->temporary_tables list, it's impossible to tell here whether we're dealing with an internal or a user temporary table. - If is_trans is not null, we return the type of the table: - either transactional (e.g. innodb) as TRUE or non-transactional - (e.g. myisam) as FALSE. + @param thd Thread handler + @param table Temporary table to be deleted + @param is_trans Is set to the type of the table: + transactional (e.g. innodb) as TRUE or non-transactional + (e.g. myisam) as FALSE. @retval 0 the table was found and dropped successfully. - @retval 1 the table was not found in the list of temporary tables - of this thread @retval -1 the table is in use by a outer query */ -int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans) +int drop_temporary_table(THD *thd, TABLE *table, bool *is_trans) { - TABLE *table; DBUG_ENTER("drop_temporary_table"); DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'", - table_list->db, table_list->table_name)); - - if (!(table= find_temporary_table(thd, table_list))) - DBUG_RETURN(1); + table->s->db.str, table->s->table_name.str)); /* Table might be in use by some outer statement. */ if (table->query_id && table->query_id != thd->query_id) { + DBUG_PRINT("info", ("table->query_id: %lu thd->query_id: %lu", + (ulong) table->query_id, (ulong) thd->query_id)); + my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr()); DBUG_RETURN(-1); } - if (is_trans != NULL) - *is_trans= table->file->has_transactions(); + *is_trans= table->file->has_transactions(); /* If LOCK TABLES list is not empty and contains this table, @@ -2225,6 +1751,7 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans) DBUG_RETURN(0); } + /* unlink from thd->temporary tables and close temporary table */ @@ -2237,6 +1764,7 @@ void close_temporary_table(THD *thd, TABLE *table, table->s->db.str, table->s->table_name.str, (long) table, table->alias.c_ptr())); + thd->lock_temporary_tables(); if (table->prev) { table->prev->next= table->next; @@ -2256,12 +1784,14 @@ void close_temporary_table(THD *thd, TABLE *table, if (thd->temporary_tables) table->next->prev= 0; } - if (thd->slave_thread) + if (thd->rgi_slave) { /* natural invariant of temporary_tables */ DBUG_ASSERT(slave_open_temp_tables || !thd->temporary_tables); - slave_open_temp_tables--; + thread_safe_decrement32(&slave_open_temp_tables); + table->in_use= 0; // No statistics } + thd->unlock_temporary_tables(0); close_temporary(table, free_share, delete_table); DBUG_VOID_RETURN; } @@ -2282,13 +1812,6 @@ void close_temporary(TABLE *table, bool free_share, bool delete_table) DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'", table->s->db.str, table->s->table_name.str)); - /* in_use is not set for replication temporary tables during shutdown */ - if (table->in_use) - { - table->file->update_global_table_stats(); - table->file->update_global_index_stats(); - } - free_io_cache(table); closefrm(table, 0); if (delete_table) @@ -2316,15 +1839,12 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db, char *key; uint key_length; TABLE_SHARE *share= table->s; - TABLE_LIST table_list; DBUG_ENTER("rename_temporary_table"); if (!(key=(char*) alloc_root(&share->mem_root, MAX_DBKEY_LENGTH))) DBUG_RETURN(1); /* purecov: inspected */ - table_list.db= (char*) db; - table_list.table_name= (char*) table_name; - key_length= create_table_def_key(thd, key, &table_list, 1); + key_length= create_tmp_table_def_key(thd, key, db, table_name); share->set_table_cache_key(key, key_length); DBUG_RETURN(0); } @@ -2349,19 +1869,19 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db, */ bool wait_while_table_is_used(THD *thd, TABLE *table, - enum ha_extra_function function, - enum_tdc_remove_table_type remove_type) + enum ha_extra_function function) { DBUG_ENTER("wait_while_table_is_used"); DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", table->s->table_name.str, (ulong) table->s, - table->db_stat, table->s->version)); + table->db_stat, table->s->tdc->version)); - if (thd->mdl_context.upgrade_shared_lock_to_exclusive( - table->mdl_ticket, thd->variables.lock_wait_timeout)) + if (thd->mdl_context.upgrade_shared_lock( + table->mdl_ticket, MDL_EXCLUSIVE, + thd->variables.lock_wait_timeout)) DBUG_RETURN(TRUE); - tdc_remove_table(thd, remove_type, + tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN, table->s->db.str, table->s->table_name.str, FALSE); /* extra() call must come only after all instances above are closed */ @@ -2407,78 +1927,13 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name, tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db_name, table_name, FALSE); /* Remove the table from the storage engine and rm the .frm. */ - quick_rm_table(table_type, db_name, table_name, 0); - } + quick_rm_table(thd, table_type, db_name, table_name, 0); + } DBUG_VOID_RETURN; } /** - Check that table exists in table definition cache, on disk - or in some storage engine. - - @param thd Thread context - @param table Table list element - @param fast_check Check only if share or .frm file exists - @param[out] exists Out parameter which is set to TRUE if table - exists and to FALSE otherwise. - - @note This function acquires LOCK_open internally. - - @note If there is no .FRM file for the table but it exists in one - of engines (e.g. it was created on another node of NDB cluster) - this function will fetch and create proper .FRM file for it. - - @retval TRUE Some error occurred - @retval FALSE No error. 'exists' out parameter set accordingly. -*/ - -bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool fast_check, - bool *exists) -{ - char path[FN_REFLEN + 1]; - TABLE_SHARE *share; - DBUG_ENTER("check_if_table_exists"); - - *exists= TRUE; - - DBUG_ASSERT(fast_check || - thd->mdl_context. - is_lock_owner(MDL_key::TABLE, table->db, - table->table_name, MDL_SHARED)); - - mysql_mutex_lock(&LOCK_open); - share= get_cached_table_share(table->db, table->table_name); - mysql_mutex_unlock(&LOCK_open); - - if (share) - goto end; - - build_table_filename(path, sizeof(path) - 1, table->db, table->table_name, - reg_ext, 0); - - if (!access(path, F_OK)) - goto end; - - if (fast_check) - { - *exists= FALSE; - goto end; - } - - /* .FRM file doesn't exist. Check if some engine can provide it. */ - if (ha_check_if_table_exists(thd, table->db, table->table_name, exists)) - { - my_printf_error(ER_OUT_OF_RESOURCES, "Failed to open '%-.64s', error while " - "unpacking from engine", MYF(0), table->table_name); - DBUG_RETURN(TRUE); - } -end: - DBUG_RETURN(FALSE); -} - - -/** An error handler which converts, if possible, ER_LOCK_DEADLOCK error that can occur when we are trying to acquire a metadata lock to a request for back-off and re-start of open_tables() process. @@ -2496,9 +1951,9 @@ public: virtual bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl); + Sql_condition ** cond_hdl); private: /** Open table context to be used for back-off request. */ @@ -2515,9 +1970,9 @@ private: bool MDL_deadlock_handler::handle_condition(THD *, uint sql_errno, const char*, - MYSQL_ERROR::enum_warning_level, + Sql_condition::enum_warning_level, const char*, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { *cond_hdl= NULL; if (! m_is_active && sql_errno == ER_LOCK_DEADLOCK) @@ -2669,160 +2124,149 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx, /** - Check if table's share is being removed from the table definition - cache and, if yes, wait until the flush is complete. + Check if the given table is actually a VIEW that was LOCK-ed - @param thd Thread context. - @param table_list Table which share should be checked. - @param timeout Timeout for waiting. - @param deadlock_weight Weight of this wait for deadlock detector. + @param thd Thread context. + @param t Table to check. - @retval FALSE Success. Share is up to date or has been flushed. - @retval TRUE Error (OOM, our was killed, the wait resulted - in a deadlock or timeout). Reported. + @retval TRUE The 't'-table is a locked view + needed to remedy problem before retrying again. + @retval FALSE 't' was not locked, not a VIEW or an error happened. */ - -static bool -tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name, - ulong wait_timeout, uint deadlock_weight) +bool is_locked_view(THD *thd, TABLE_LIST *t) { - TABLE_SHARE *share; - bool res= FALSE; + DBUG_ENTER("check_locked_view"); + /* + Is this table a view and not a base table? + (it is work around to allow to open view with locked tables, + real fix will be made after definition cache will be made) - mysql_mutex_lock(&LOCK_open); - if ((share= get_cached_table_share(db, table_name)) && - share->has_old_version()) + Since opening of view which was not explicitly locked by LOCK + TABLES breaks metadata locking protocol (potentially can lead + to deadlocks) it should be disallowed. + */ + if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, + t->db, t->table_name, + MDL_SHARED)) { - struct timespec abstime; - set_timespec(abstime, wait_timeout); - res= share->wait_for_old_version(thd, &abstime, deadlock_weight); - } - mysql_mutex_unlock(&LOCK_open); - return res; -} - - -/* - Open a table. + char path[FN_REFLEN + 1]; + build_table_filename(path, sizeof(path) - 1, + t->db, t->table_name, reg_ext, 0); + /* + Note that we can't be 100% sure that it is a view since it's + possible that we either simply have not found unused TABLE + instance in THD::open_tables list or were unable to open table + during prelocking process (in this case in theory we still + should hold shared metadata lock on it). + */ + if (dd_frm_is_view(thd, path)) + { + /* + If parent_l of the table_list is non null then a merge table + has this view as child table, which is not supported. + */ + if (t->parent_l) + { + my_error(ER_WRONG_MRG_TABLE, MYF(0)); + DBUG_RETURN(FALSE); + } - SYNOPSIS - open_table() - thd Thread context. - table_list Open first table in list. - action INOUT Pointer to variable of enum_open_table_action type - which will be set according to action which is - required to remedy problem appeared during attempt - to open table. - flags Bitmap of flags to modify how open works: - MYSQL_OPEN_IGNORE_FLUSH - Open table even if - someone has done a flush or there is a pending - exclusive metadata lock requests against it - (i.e. request high priority metadata lock). - No version number checking is done. - MYSQL_OPEN_TEMPORARY_ONLY - Open only temporary - table not the base table or view. - MYSQL_OPEN_TAKE_UPGRADABLE_MDL - Obtain upgradable - metadata lock for tables on which we are going to - take some kind of write table-level lock. + if (!tdc_open_view(thd, t, t->alias, CHECK_METADATA_VERSION)) + { + DBUG_ASSERT(t->view != 0); + DBUG_RETURN(TRUE); // VIEW + } + } + } - IMPLEMENTATION - Uses a cache of open tables to find a table not in use. + DBUG_RETURN(FALSE); +} - If TABLE_LIST::open_strategy is set to OPEN_IF_EXISTS, the table is opened - only if it exists. If the open strategy is OPEN_STUB, the underlying table - is never opened. In both cases, metadata locks are always taken according - to the lock strategy. - RETURN - TRUE Open failed. "action" parameter may contain type of action - needed to remedy problem before retrying again. - FALSE Success. Members of TABLE_LIST structure are filled properly (e.g. - TABLE_LIST::table is set for real tables and TABLE_LIST::view is - set for views). +/** + Open a base table. + + @param thd Thread context. + @param table_list Open first table in list. + @param ot_ctx Context with flags which modify how open works + and which is used to recover from a failed + open_table() attempt. + Some examples of flags: + MYSQL_OPEN_IGNORE_FLUSH - Open table even if + someone has done a flush. No version number + checking is done. + MYSQL_OPEN_HAS_MDL_LOCK - instead of acquiring + metadata locks rely on that caller already has + appropriate ones. + + Uses a cache of open tables to find a TABLE instance not in use. + + If TABLE_LIST::open_strategy is set to OPEN_IF_EXISTS, the table is + opened only if it exists. If the open strategy is OPEN_STUB, the + underlying table is never opened. In both cases, metadata locks are + always taken according to the lock strategy. + + The function used to open temporary tables, but now it opens base tables + only. + + @retval TRUE Open failed. "action" parameter may contain type of action + needed to remedy problem before retrying again. + @retval FALSE Success. Members of TABLE_LIST structure are filled properly + (e.g. TABLE_LIST::table is set for real tables and + TABLE_LIST::view is set for views). */ - -bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, - Open_table_context *ot_ctx) +bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx) { - reg1 TABLE *table; - char key[MAX_DBKEY_LENGTH]; + TABLE *table; + const char *key; uint key_length; char *alias= table_list->alias; uint flags= ot_ctx->get_flags(); MDL_ticket *mdl_ticket; - int error; TABLE_SHARE *share; - my_hash_value_type hash_value; + uint gts_flags; DBUG_ENTER("open_table"); + /* + The table must not be opened already. The table can be pre-opened for + some statements if it is a temporary table. + + open_temporary_table() must be used to open temporary tables. + */ + DBUG_ASSERT(!table_list->table); + /* an open table operation needs a lot of the stack space */ if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias)) DBUG_RETURN(TRUE); - if (thd->killed) + if (!(flags & MYSQL_OPEN_IGNORE_KILLED) && thd->killed) + { + thd->send_kill_message(); DBUG_RETURN(TRUE); - - key_length= (create_table_def_key(thd, key, table_list, 1) - - TMP_TABLE_KEY_EXTRA); + } /* - Unless requested otherwise, try to resolve this table in the list - of temporary tables of this thread. In MySQL temporary tables - are always thread-local and "shadow" possible base tables with the - same name. This block implements the behaviour. - TODO: move this block into a separate function. + Check if we're trying to take a write lock in a read only transaction. + + Note that we allow write locks on log tables as otherwise logging + to general/slow log would be disabled in read only transactions. */ - if (table_list->open_type != OT_BASE_ONLY && - ! (flags & MYSQL_OPEN_SKIP_TEMPORARY)) + if (table_list->mdl_request.type >= MDL_SHARED_WRITE && + thd->tx_read_only && + !(flags & (MYSQL_LOCK_LOG_TABLE | MYSQL_OPEN_HAS_MDL_LOCK))) { - for (table= thd->temporary_tables; table ; table=table->next) - { - if (table->s->table_cache_key.length == key_length + - TMP_TABLE_KEY_EXTRA && - !memcmp(table->s->table_cache_key.str, key, - key_length + TMP_TABLE_KEY_EXTRA)) - { - /* - We're trying to use the same temporary table twice in a query. - Right now we don't support this because a temporary table - is always represented by only one TABLE object in THD, and - it can not be cloned. Emit an error for an unsupported behaviour. - */ - if (table->query_id) - { - DBUG_PRINT("error", - ("query_id: %lu server_id: %u pseudo_thread_id: %lu", - (ulong) table->query_id, (uint) thd->server_id, - (ulong) thd->variables.pseudo_thread_id)); - my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr()); - DBUG_RETURN(TRUE); - } - table->query_id= thd->query_id; - thd->thread_specific_used= TRUE; - DBUG_PRINT("info",("Using temporary table")); - goto reset; - } - } + my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0)); + DBUG_RETURN(true); } - if (table_list->open_type == OT_TEMPORARY_ONLY || - (flags & MYSQL_OPEN_TEMPORARY_ONLY)) - { - if (table_list->open_strategy == TABLE_LIST::OPEN_NORMAL) - { - my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name); - DBUG_RETURN(TRUE); - } - else - DBUG_RETURN(FALSE); - } + key_length= get_table_def_key(table_list, &key); /* - The table is not temporary - if we're in pre-locked or LOCK TABLES - mode, let's try to find the requested table in the list of pre-opened - and locked tables. If the table is not there, return an error - we can't - open not pre-opened tables in pre-locked/LOCK TABLES mode. + If we're in pre-locked or LOCK TABLES mode, let's try to find the + requested table in the list of pre-opened and locked tables. If the + table is not there, return an error - we can't open not pre-opened + tables in pre-locked/LOCK TABLES mode. TODO: move this block into a separate function. */ if (thd->locked_tables_mode && @@ -2881,54 +2325,13 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, DBUG_PRINT("info",("Using locked table")); goto reset; } - /* - Is this table a view and not a base table? - (it is work around to allow to open view with locked tables, - real fix will be made after definition cache will be made) - Since opening of view which was not explicitly locked by LOCK - TABLES breaks metadata locking protocol (potentially can lead - to deadlocks) it should be disallowed. - */ - if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, - table_list->db, - table_list->table_name, - MDL_SHARED)) - { - char path[FN_REFLEN + 1]; - enum legacy_db_type not_used; - build_table_filename(path, sizeof(path) - 1, - table_list->db, table_list->table_name, reg_ext, 0); - /* - Note that we can't be 100% sure that it is a view since it's - possible that we either simply have not found unused TABLE - instance in THD::open_tables list or were unable to open table - during prelocking process (in this case in theory we still - should hold shared metadata lock on it). - */ - if (dd_frm_type(thd, path, ¬_used) == FRMTYPE_VIEW) - { - /* - If parent_l of the table_list is non null then a merge table - has this view as child table, which is not supported. - */ - if (table_list->parent_l) - { - my_error(ER_WRONG_MRG_TABLE, MYF(0)); - DBUG_RETURN(true); - } + if (is_locked_view(thd, table_list)) + DBUG_RETURN(FALSE); // VIEW - if (!tdc_open_view(thd, table_list, alias, key, key_length, - mem_root, 0)) - { - DBUG_ASSERT(table_list->view != 0); - DBUG_RETURN(FALSE); // VIEW - } - } - } /* No table in the locked tables list. In case of explicit LOCK TABLES - this can happen if a user did not include the able into the list. + this can happen if a user did not include the table into the list. In case of pre-locked mode locked tables list is generated automatically, so we may only end up here if the table did not exist when locked tables list was created. @@ -2953,12 +2356,12 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, global read lock until end of this statement in order to have this statement blocked by active FLUSH TABLES WITH READ LOCK. - We don't block acquire this protection under LOCK TABLES as + We don't need to acquire this protection under LOCK TABLES as such protection already acquired at LOCK TABLES time and not released until UNLOCK TABLES. We don't block statements which modify only temporary tables - as these tables are not preserved by backup by any form of + as these tables are not preserved by any form of backup which uses FLUSH TABLES WITH READ LOCK. TODO: The fact that we sometimes acquire protection against @@ -3018,52 +2421,61 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, mdl_ticket= table_list->mdl_request.ticket; } - hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); - - if (table_list->open_strategy == TABLE_LIST::OPEN_IF_EXISTS) { - bool exists; - - if (check_if_table_exists(thd, table_list, 0, &exists)) - DBUG_RETURN(TRUE); - - if (!exists) + if (!ha_table_exists(thd, table_list->db, table_list->table_name)) DBUG_RETURN(FALSE); - - /* Table exists. Let us try to open it. */ } else if (table_list->open_strategy == TABLE_LIST::OPEN_STUB) DBUG_RETURN(FALSE); + /* Table exists. Let us try to open it. */ + + if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) + gts_flags= GTS_TABLE; + else if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) + gts_flags= GTS_VIEW; + else + gts_flags= GTS_TABLE | GTS_VIEW; + retry_share: - mysql_mutex_lock(&LOCK_open); + share= tdc_acquire_share(thd, table_list->db, table_list->table_name, + key, key_length, + table_list->mdl_request.key.tc_hash_value(), + gts_flags, &table); - if (!(share= get_table_share_with_discover(thd, table_list, key, - key_length, - (OPEN_VIEW | - ((table_list->required_type == - FRMTYPE_VIEW) ? - OPEN_VIEW_ONLY : 0)), - &error, - hash_value))) + if (!share) { - mysql_mutex_unlock(&LOCK_open); /* - If thd->is_error() is not set, we either need discover - (error == 7), or the error was silenced by the prelocking - handler (error == 0), in which case we should skip this - table. + Hide "Table doesn't exist" errors if the table belongs to a view. + The check for thd->is_error() is necessary to not push an + unwanted error in case the error was already silenced. + @todo Rework the alternative ways to deal with ER_NO_SUCH TABLE. */ - if (error == 7 && !thd->is_error()) + if (thd->is_error()) { - (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER, - table_list); + if (table_list->parent_l) + { + thd->clear_error(); + my_error(ER_WRONG_MRG_TABLE, MYF(0)); + } + else if (table_list->belong_to_view) + { + TABLE_LIST *view= table_list->belong_to_view; + thd->clear_error(); + my_error(ER_VIEW_INVALID, MYF(0), + view->view_db.str, view->view_name.str); + } } DBUG_RETURN(TRUE); } + /* + Check if this TABLE_SHARE-object corresponds to a view. Note, that there is + no need to check TABLE_SHARE::tdc.flushed as we do for regular tables, + because view shares are always up to date. + */ if (share->is_view) { /* @@ -3073,7 +2485,7 @@ retry_share: if (table_list->parent_l) { my_error(ER_WRONG_MRG_TABLE, MYF(0)); - goto err_unlock; + goto err_lock; } /* @@ -3081,52 +2493,27 @@ retry_share: that it was a view when the statement was prepared. */ if (check_and_update_table_version(thd, table_list, share)) - goto err_unlock; - if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) - { - my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, - table_list->table_name); - goto err_unlock; - } + goto err_lock; /* Open view */ - if (open_new_frm(thd, share, alias, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX | HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, - thd->open_options, - 0, table_list, mem_root)) - goto err_unlock; + if (mysql_make_view(thd, share, table_list, false)) + goto err_lock; + /* TODO: Don't free this */ - release_table_share(share); + tdc_release_share(share); DBUG_ASSERT(table_list->view); - mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(FALSE); } - /* - Note that situation when we are trying to open a table for what - was a view during previous execution of PS will be handled in by - the caller. Here we should simply open our table even if - TABLE_LIST::view is true. - */ - - if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) + if (!(flags & MYSQL_OPEN_IGNORE_FLUSH)) { - my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, - table_list->table_name); - goto err_unlock; - } - - if (!(flags & MYSQL_OPEN_IGNORE_FLUSH) || - (share->protected_against_usage() && - !(flags & MYSQL_OPEN_FOR_REPAIR))) - { - if (share->has_old_version()) + if (share->tdc->flushed) { + DBUG_PRINT("info", ("Found old share version: %lu current: %lu", + share->tdc->version, tdc_refresh_version())); /* We already have an MDL lock. But we have encountered an old version of table in the table definition cache which is possible @@ -3136,13 +2523,14 @@ retry_share: Release our reference to share, wait until old version of share goes away and then try to get new version of table share. */ + if (table) + tc_release_table(table); + else + tdc_release_share(share); + MDL_deadlock_handler mdl_deadlock_handler(ot_ctx); bool wait_result; - DBUG_PRINT("info", ("old version of table share found")); - release_table_share(share); - mysql_mutex_unlock(&LOCK_open); - thd->push_internal_handler(&mdl_deadlock_handler); wait_result= tdc_wait_for_old_version(thd, table_list->db, table_list->table_name, @@ -3156,7 +2544,7 @@ retry_share: goto retry_share; } - if (thd->open_tables && thd->open_tables->s->version != share->version) + if (thd->open_tables && thd->open_tables->s->tdc->flushed) { /* If the version changes while we're opening the tables, @@ -3164,34 +2552,24 @@ retry_share: and try to reopen them. Note: refresh_version is currently changed only during FLUSH TABLES. */ - DBUG_PRINT("info", ("share version differs between tables")); - release_table_share(share); - mysql_mutex_unlock(&LOCK_open); + if (table) + tc_release_table(table); + else + tdc_release_share(share); (void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES, NULL); DBUG_RETURN(TRUE); } } - if (!share->free_tables.is_empty()) + if (table) { - table= share->free_tables.front(); - table_def_use_table(thd, table); - /* - We need to release share as we have EXTRA reference to it in our hands. - */ - DBUG_PRINT("info", ("release temporarily acquired table share")); - release_table_share(share); + DBUG_ASSERT(table->file != NULL); + MYSQL_REBIND_TABLE(table->file); } else { - /* - We have too many TABLE instances around let us try to get rid of them. - */ - while (table_cache_count > table_cache_size && unused_tables) - free_cache_entry(unused_tables); - - mysql_mutex_unlock(&LOCK_open); + enum open_frm_error error; /* make a new table */ if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) @@ -3210,16 +2588,19 @@ retry_share: { my_free(table); - if (error == 7) + if (error == OPEN_FRM_DISCOVER) (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER, table_list); else if (share->crashed) - (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR, - table_list); - + { + if (!(flags & MYSQL_OPEN_IGNORE_REPAIR)) + (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR, + table_list); + else + table_list->crashed= 1; /* Mark that table was crashed */ + } goto err_lock; } - if (open_table_entry_fini(thd, share, table)) { closefrm(table, 0); @@ -3227,13 +2608,10 @@ retry_share: goto err_lock; } - mysql_mutex_lock(&LOCK_open); /* Add table to the share's used tables list. */ - table_def_add_used_table(thd, table); + tc_add_table(thd, table); } - mysql_mutex_unlock(&LOCK_open); - table->mdl_ticket= mdl_ticket; table->next= thd->open_tables; /* Link into simple list */ @@ -3250,15 +2628,27 @@ retry_share: table_list->updatable= 1; // It is not derived table nor non-updatable VIEW table_list->table= table; +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->part_info) + { + /* Set all [named] partitions as used. */ + if (table->part_info->set_partition_bitmaps(table_list)) + DBUG_RETURN(true); + } + else if (table_list->partition_names) + { + /* Don't allow PARTITION () clause on a nonpartitioned table */ + my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(true); + } +#endif + table->init(thd, table_list); DBUG_RETURN(FALSE); err_lock: - mysql_mutex_lock(&LOCK_open); -err_unlock: - release_table_share(share); - mysql_mutex_unlock(&LOCK_open); + tdc_release_share(share); DBUG_PRINT("exit", ("failed")); DBUG_RETURN(TRUE); @@ -3278,7 +2668,7 @@ err_unlock: TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name) { char key[MAX_DBKEY_LENGTH]; - uint key_length= create_table_def_key(key, db, table_name); + uint key_length= tdc_create_key(key, db, table_name); for (TABLE *table= list; table ; table=table->next) { @@ -3298,29 +2688,30 @@ TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name) @param thd Thread context @param db Database name. @param table_name Name of table. - @param no_error Don't emit error if no suitable TABLE - instance were found. + @param p_error In the case of an error (when the function returns NULL) + the error number is stored there. + If the p_error is NULL, function launches the error itself. @note This function checks if the connection holds a global IX metadata lock. If no such lock is found, it is not safe to upgrade the lock and ER_TABLE_NOT_LOCKED_FOR_WRITE will be reported. - @return Pointer to TABLE instance with MDL_SHARED_NO_WRITE, - MDL_SHARED_NO_READ_WRITE, or MDL_EXCLUSIVE metadata - lock, NULL otherwise. + @return Pointer to TABLE instance with MDL_SHARED_UPGRADABLE + MDL_SHARED_NO_WRITE, MDL_SHARED_NO_READ_WRITE, or + MDL_EXCLUSIVE metadata lock, NULL otherwise. */ TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db, - const char *table_name, bool no_error) + const char *table_name, int *p_error) { TABLE *tab= find_locked_table(thd->open_tables, db, table_name); + int error; if (!tab) { - if (!no_error) - my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name); - return NULL; + error= ER_TABLE_NOT_LOCKED; + goto err_exit; } /* @@ -3332,9 +2723,8 @@ TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db, if (!thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE)) { - if (!no_error) - my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name); - return NULL; + error= ER_TABLE_NOT_LOCKED_FOR_WRITE; + goto err_exit; } while (tab->mdl_ticket != NULL && @@ -3342,10 +2732,21 @@ TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db, (tab= find_locked_table(tab->next, db, table_name))) continue; - if (!tab && !no_error) - my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name); + if (unlikely(!tab)) + { + error= ER_TABLE_NOT_LOCKED_FOR_WRITE; + goto err_exit; + } return tab; + +err_exit: + if (p_error) + *p_error= error; + else + my_error(error, MYF(0), table_name); + + return NULL; } @@ -3378,9 +2779,9 @@ Locked_tables_list::init_locked_tables(THD *thd) { TABLE_LIST *src_table_list= table->pos_in_table_list; char *db, *table_name, *alias; - size_t db_len= src_table_list->db_length; - size_t table_name_len= src_table_list->table_name_length; - size_t alias_len= strlen(src_table_list->alias); + size_t db_len= table->s->db.length; + size_t table_name_len= table->s->table_name.length; + size_t alias_len= table->alias.length(); TABLE_LIST *dst_table_list; if (! multi_alloc_root(&m_locked_tables_root, @@ -3390,23 +2791,15 @@ Locked_tables_list::init_locked_tables(THD *thd) &alias, alias_len + 1, NullS)) { - unlock_locked_tables(0); + reset(); return TRUE; } - memcpy(db, src_table_list->db, db_len + 1); - memcpy(table_name, src_table_list->table_name, table_name_len + 1); - memcpy(alias, src_table_list->alias, alias_len + 1); - /** - Sic: remember the *actual* table level lock type taken, to - acquire the exact same type in reopen_tables(). - E.g. if the table was locked for write, src_table_list->lock_type is - TL_WRITE_DEFAULT, whereas reginfo.lock_type has been updated from - thd->update_lock_default. - */ + memcpy(db, table->s->db.str, db_len + 1); + memcpy(table_name, table->s->table_name.str, table_name_len + 1); + strmake(alias, table->alias.ptr(), alias_len); dst_table_list->init_one_table(db, db_len, table_name, table_name_len, - alias, - src_table_list->table->reginfo.lock_type); + alias, table->reginfo.lock_type); dst_table_list->table= table; dst_table_list->mdl_request.ticket= src_table_list->mdl_request.ticket; @@ -3427,7 +2820,7 @@ Locked_tables_list::init_locked_tables(THD *thd) (m_locked_tables_count+1)); if (m_reopen_array == NULL) { - unlock_locked_tables(0); + reset(); return TRUE; } } @@ -3448,42 +2841,82 @@ Locked_tables_list::init_locked_tables(THD *thd) void Locked_tables_list::unlock_locked_tables(THD *thd) { - if (thd) + DBUG_ASSERT(!thd->in_sub_stmt && + !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); + /* + Sic: we must be careful to not close open tables if + we're not in LOCK TABLES mode: unlock_locked_tables() is + sometimes called implicitly, expecting no effect on + open tables, e.g. from begin_trans(). + */ + if (thd->locked_tables_mode != LTM_LOCK_TABLES) + return; + + for (TABLE_LIST *table_list= m_locked_tables; + table_list; table_list= table_list->next_global) { - DBUG_ASSERT(!thd->in_sub_stmt && - !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); /* - Sic: we must be careful to not close open tables if - we're not in LOCK TABLES mode: unlock_locked_tables() is - sometimes called implicitly, expecting no effect on - open tables, e.g. from begin_trans(). + Clear the position in the list, the TABLE object will be + returned to the table cache. */ - if (thd->locked_tables_mode != LTM_LOCK_TABLES) - return; + if (table_list->table) // If not closed + table_list->table->pos_in_locked_tables= NULL; + } + thd->leave_locked_tables_mode(); - for (TABLE_LIST *table_list= m_locked_tables; - table_list; table_list= table_list->next_global) - { - /* - Clear the position in the list, the TABLE object will be - returned to the table cache. - */ - if (table_list->table) // If not closed - table_list->table->pos_in_locked_tables= NULL; - } - thd->leave_locked_tables_mode(); + DBUG_ASSERT(thd->transaction.stmt.is_empty()); + close_thread_tables(thd); + + /* + We rely on the caller to implicitly commit the + transaction and release transactional locks. + */ - DBUG_ASSERT(thd->transaction.stmt.is_empty()); - close_thread_tables(thd); - /* - We rely on the caller to implicitly commit the - transaction and release transactional locks. - */ - } /* After closing tables we can free memory used for storing lock request for metadata locks and TABLE_LIST elements. */ + reset(); +} + + +/** + Remove all meta data locks associated with table and release locked + table mode if there is no locked tables anymore +*/ + +void +Locked_tables_list::unlock_locked_table(THD *thd, MDL_ticket *mdl_ticket) +{ + /* + Ensure we are in locked table mode. + As this function is only called on error condition it's better + to check this condition here than in the caller. + */ + if (thd->locked_tables_mode != LTM_LOCK_TABLES) + return; + + if (mdl_ticket) + { + /* + Under LOCK TABLES we may have several instances of table open + and locked and therefore have to remove several metadata lock + requests associated with them. + */ + thd->mdl_context.release_all_locks_for_name(mdl_ticket); + } + + if (thd->lock->table_count == 0) + unlock_locked_tables(thd); +} + + +/* + Free memory allocated for storing locks +*/ + +void Locked_tables_list::reset() +{ free_root(&m_locked_tables_root, MYF(0)); m_locked_tables= NULL; m_locked_tables_last= &m_locked_tables; @@ -3521,7 +2954,8 @@ void Locked_tables_list::unlink_from_list(THD *thd, If mode is not LTM_LOCK_TABLES, we needn't do anything. Moreover, outside this mode pos_in_locked_tables value is not trustworthy. */ - if (thd->locked_tables_mode != LTM_LOCK_TABLES) + if (thd->locked_tables_mode != LTM_LOCK_TABLES && + thd->locked_tables_mode != LTM_PRELOCKED_UNDER_LOCK_TABLES) return; /* @@ -3548,6 +2982,7 @@ void Locked_tables_list::unlink_from_list(THD *thd, m_locked_tables_last= table_list->prev_global; else table_list->next_global->prev_global= table_list->prev_global; + m_locked_tables_count--; } } @@ -3601,8 +3036,13 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) m_locked_tables_last= table_list->prev_global; else table_list->next_global->prev_global= table_list->prev_global; + m_locked_tables_count--; } } + + /* If no tables left, do an automatic UNLOCK TABLES */ + if (thd->lock && thd->lock->table_count == 0) + unlock_locked_tables(thd); } @@ -3619,24 +3059,37 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) */ bool -Locked_tables_list::reopen_tables(THD *thd) +Locked_tables_list::reopen_tables(THD *thd, bool need_reopen) { Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN); size_t reopen_count= 0; MYSQL_LOCK *lock; MYSQL_LOCK *merged_lock; + DBUG_ENTER("Locked_tables_list::reopen_tables"); for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list= table_list->next_global) { - if (table_list->table) /* The table was not closed */ - continue; + if (need_reopen) + { + if (!table_list->table || !table_list->table->needs_reopen()) + continue; + /* no need to remove the table from the TDC here, thus (TABLE*)1 */ + close_all_tables_for_name(thd, table_list->table->s, + HA_EXTRA_NOT_USED, (TABLE*)1); + DBUG_ASSERT(table_list->table == NULL); + } + else + { + if (table_list->table) /* The table was not closed */ + continue; + } /* Links into thd->open_tables upon success */ - if (open_table(thd, table_list, thd->mem_root, &ot_ctx)) + if (open_table(thd, table_list, &ot_ctx)) { unlink_all_closed_tables(thd, 0, reopen_count); - return TRUE; + DBUG_RETURN(TRUE); } table_list->table->pos_in_locked_tables= table_list; /* See also the comment on lock type in init_locked_tables(). */ @@ -3668,70 +3121,70 @@ Locked_tables_list::reopen_tables(THD *thd) unlink_all_closed_tables(thd, lock, reopen_count); if (! thd->killed) my_error(ER_LOCK_DEADLOCK, MYF(0)); - return TRUE; + DBUG_RETURN(TRUE); } thd->lock= merged_lock; } - return FALSE; + DBUG_RETURN(FALSE); } +/** + Add back a locked table to the locked list that we just removed from it. + This is needed in CREATE OR REPLACE TABLE where we are dropping, creating + and re-opening a locked table. -/* - Function to assign a new table map id to a table share. - - PARAMETERS - - share - Pointer to table share structure + @return 0 0k + @return 1 error +*/ - DESCRIPTION +bool Locked_tables_list::restore_lock(THD *thd, TABLE_LIST *dst_table_list, + TABLE *table, MYSQL_LOCK *lock) +{ + MYSQL_LOCK *merged_lock; + DBUG_ENTER("restore_lock"); + DBUG_ASSERT(!strcmp(dst_table_list->table_name, table->s->table_name.str)); - We are intentionally not checking that share->mutex is locked - since this function should only be called when opening a table - share and before it is entered into the table_def_cache (meaning - that it cannot be fetched by another thread, even accidentally). + /* Ensure we have the memory to add the table back */ + if (!(merged_lock= mysql_lock_merge(thd->lock, lock))) + DBUG_RETURN(1); + thd->lock= merged_lock; - PRE-CONDITION(S) + /* Link to the new table */ + dst_table_list->table= table; + /* + The lock type may have changed (normally it should not as create + table will lock the table in write mode + */ + dst_table_list->lock_type= table->reginfo.lock_type; + table->pos_in_locked_tables= dst_table_list; - share is non-NULL - The LOCK_open mutex is locked. + add_back_last_deleted_lock(dst_table_list); - POST-CONDITION(S) + table->mdl_ticket->downgrade_lock(table->reginfo.lock_type >= + TL_WRITE_ALLOW_WRITE ? + MDL_SHARED_NO_READ_WRITE : + MDL_SHARED_READ); - share->table_map_id is given a value that with a high certainty is - not used by any other table (the only case where a table id can be - reused is on wrap-around, which means more than 4 billion table - share opens have been executed while one table was open all the - time). + DBUG_RETURN(0); +} - share->table_map_id is not ~0UL. - */ -static ulong last_table_id= ~0UL; +/* + Add back the last deleted lock structure. + This should be followed by a call to reopen_tables() to + open the table. +*/ -void assign_new_table_id(TABLE_SHARE *share) +void Locked_tables_list::add_back_last_deleted_lock(TABLE_LIST *dst_table_list) { - - DBUG_ENTER("assign_new_table_id"); - - /* Preconditions */ - DBUG_ASSERT(share != NULL); - mysql_mutex_assert_owner(&LOCK_open); - - ulong tid= ++last_table_id; /* get next id */ - /* - There is one reserved number that cannot be used. Remember to - change this when 6-byte global table id's are introduced. - */ - if (unlikely(tid == ~0UL)) - tid= ++last_table_id; - share->table_map_id= tid; - DBUG_PRINT("info", ("table_id=%lu", tid)); - - /* Post conditions */ - DBUG_ASSERT(share->table_map_id != ~0UL); - - DBUG_VOID_RETURN; + /* Link the lock back in the locked tables list */ + dst_table_list->prev_global= m_locked_tables_last; + *m_locked_tables_last= dst_table_list; + m_locked_tables_last= &dst_table_list->next_global; + dst_table_list->next_global= 0; + m_locked_tables_count++; } + #ifndef DBUG_OFF /* Cause a spurious statement reprepare for debug purposes. */ static bool inject_reprepare(THD *thd) @@ -3872,7 +3325,6 @@ check_and_update_routine_version(THD *thd, Sroutine_hash_entry *rt, @param alias Alias name @param cache_key Key for table definition cache @param cache_key_length Length of cache_key - @param mem_root Memory to be used for .frm parsing. @param flags Flags which modify how we open the view @todo This function is needed for special handling of views under @@ -3882,42 +3334,39 @@ check_and_update_routine_version(THD *thd, Sroutine_hash_entry *rt, */ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, - char *cache_key, uint cache_key_length, - MEM_ROOT *mem_root, uint flags) + const char *cache_key, uint cache_key_length, + uint flags) { TABLE not_used; - int error; - my_hash_value_type hash_value; TABLE_SHARE *share; + bool err= TRUE; - hash_value= my_calc_hash(&table_def_cache, (uchar*) cache_key, - cache_key_length); - mysql_mutex_lock(&LOCK_open); + if (!(share= tdc_acquire_share(thd, table_list->db, table_list->table_name, + cache_key, cache_key_length, GTS_VIEW))) + return TRUE; - if (!(share= get_table_share(thd, table_list, cache_key, - cache_key_length, - OPEN_VIEW, &error, - hash_value))) - goto err; + DBUG_ASSERT(share->is_view); - if (share->is_view && - !open_new_frm(thd, share, alias, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX | HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | - flags, thd->open_options, ¬_used, table_list, - mem_root)) + if (flags & CHECK_METADATA_VERSION) { - release_table_share(share); - mysql_mutex_unlock(&LOCK_open); - return FALSE; + /* + Check TABLE_SHARE-version of view only if we have been instructed to do + so. We do not need to check the version if we're executing CREATE VIEW or + ALTER VIEW statements. + + In the future, this functionality should be moved out from + tdc_open_view(), and tdc_open_view() should became a part of a clean + table-definition-cache interface. + */ + if (check_and_update_table_version(thd, table_list, share)) + goto ret; } - my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "VIEW"); - release_table_share(share); -err: - mysql_mutex_unlock(&LOCK_open); - return TRUE; + err= mysql_make_view(thd, share, table_list, (flags & OPEN_VIEW_NO_PARSE)); +ret: + tdc_release_share(share); + + return err; } @@ -3971,40 +3420,19 @@ static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry) static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) { - char cache_key[MAX_DBKEY_LENGTH]; - uint cache_key_length; TABLE_SHARE *share; TABLE *entry; - int not_used; bool result= TRUE; - my_hash_value_type hash_value; - - cache_key_length= create_table_def_key(thd, cache_key, table_list, 0); thd->clear_error(); - hash_value= my_calc_hash(&table_def_cache, (uchar*) cache_key, - cache_key_length); - mysql_mutex_lock(&LOCK_open); + if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME)))) + return result; - if (!(share= get_table_share(thd, table_list, cache_key, - cache_key_length, - OPEN_VIEW, ¬_used, - hash_value))) - goto end_unlock; + if (!(share= tdc_acquire_share_shortlived(thd, table_list, GTS_TABLE))) + goto end_free; - if (share->is_view) - { - release_table_share(share); - goto end_unlock; - } - - if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME)))) - { - release_table_share(share); - goto end_unlock; - } - mysql_mutex_unlock(&LOCK_open); + DBUG_ASSERT(! share->is_view); if (open_table_from_share(thd, share, table_list->alias, (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | @@ -4029,16 +3457,14 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) closefrm(entry, 0); result= FALSE; } - my_free(entry); - mysql_mutex_lock(&LOCK_open); - release_table_share(share); + tdc_release_share(share); /* Remove the repaired share from the table cache. */ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, table_list->table_name, - TRUE); -end_unlock: - mysql_mutex_unlock(&LOCK_open); + FALSE); +end_free: + my_free(entry); return result; } @@ -4142,6 +3568,7 @@ request_backoff_action(enum_open_table_action action_arg, table->table_name, table->table_name_length, table->alias, TL_WRITE); + m_failed_table->open_strategy= table->open_strategy; m_failed_table->mdl_request.set_type(MDL_EXCLUSIVE); } m_action= action_arg; @@ -4159,9 +3586,9 @@ public: virtual bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { if (sql_errno == ER_LOCK_DEADLOCK) { @@ -4186,8 +3613,7 @@ public: */ bool -Open_table_context:: -recover_from_failed_open() +Open_table_context::recover_from_failed_open() { bool result= FALSE; MDL_deadlock_discovery_repair_handler handler; @@ -4205,18 +3631,33 @@ recover_from_failed_open() break; case OT_DISCOVER: { - if ((result= lock_table_names(m_thd, m_failed_table, NULL, - get_timeout(), - MYSQL_OPEN_SKIP_TEMPORARY))) + if ((result= lock_table_names(m_thd, m_thd->lex->create_info, + m_failed_table, NULL, + get_timeout(), 0))) break; tdc_remove_table(m_thd, TDC_RT_REMOVE_ALL, m_failed_table->db, m_failed_table->table_name, FALSE); - ha_create_table_from_engine(m_thd, m_failed_table->db, - m_failed_table->table_name); - m_thd->warning_info->clear_warning_info(m_thd->query_id); + m_thd->get_stmt_da()->clear_warning_info(m_thd->query_id); m_thd->clear_error(); // Clear error message + + No_such_table_error_handler no_such_table_handler; + bool open_if_exists= m_failed_table->open_strategy == TABLE_LIST::OPEN_IF_EXISTS; + + if (open_if_exists) + m_thd->push_internal_handler(&no_such_table_handler); + + result= !tdc_acquire_share(m_thd, m_failed_table->db, + m_failed_table->table_name, + GTS_TABLE | GTS_FORCE_DISCOVERY | GTS_NOLOCK); + if (open_if_exists) + { + m_thd->pop_internal_handler(); + if (result && no_such_table_handler.safely_trapped_errors()) + result= FALSE; + } + /* Rollback to start of the current statement to release exclusive lock on table which was discovered but preserve locks from previous statements @@ -4227,9 +3668,9 @@ recover_from_failed_open() } case OT_REPAIR: { - if ((result= lock_table_names(m_thd, m_failed_table, NULL, - get_timeout(), - MYSQL_OPEN_SKIP_TEMPORARY))) + if ((result= lock_table_names(m_thd, m_thd->lex->create_info, + m_failed_table, NULL, + get_timeout(), 0))) break; tdc_remove_table(m_thd, TDC_RT_REMOVE_ALL, m_failed_table->db, @@ -4269,9 +3710,12 @@ recover_from_failed_open() /* Return a appropriate read lock type given a table object. - @param thd Thread context - @param prelocking_ctx Prelocking context. - @param table_list Table list element for table to be locked. + @param thd Thread context + @param prelocking_ctx Prelocking context. + @param table_list Table list element for table to be locked. + @param routine_modifies_data + Some routine that is invoked by statement + modifies data. @remark Due to a statement-based replication limitation, statements such as INSERT INTO .. SELECT FROM .. and CREATE TABLE .. SELECT FROM need @@ -4284,9 +3728,13 @@ recover_from_failed_open() This also applies to SELECT/SET/DO statements which use stored functions. Calls to such functions are going to be logged as a whole and thus should be serialized against concurrent changes - to tables used by those functions. This can be avoided if functions - only read data but doing so requires more complex analysis than it - is done now. + to tables used by those functions. This is avoided when functions + do not modify data but only read it, since in this case nothing is + written to the binary log. Argument routine_modifies_data + denotes the same. So effectively, if the statement is not a + update query and routine_modifies_data is false, then + prelocking_placeholder does not take importance. + Furthermore, this does not apply to I_S and log tables as it's always unsafe to replicate such tables under statement-based replication as the table on the slave might contain other data @@ -4301,7 +3749,8 @@ recover_from_failed_open() thr_lock_type read_lock_type_for_table(THD *thd, Query_tables_list *prelocking_ctx, - TABLE_LIST *table_list) + TABLE_LIST *table_list, + bool routine_modifies_data) { /* In cases when this function is called for a sub-statement executed in @@ -4311,12 +3760,11 @@ thr_lock_type read_lock_type_for_table(THD *thd, at THD::variables::sql_log_bin member. */ bool log_on= mysql_bin_log.is_open() && thd->variables.sql_log_bin; - ulong binlog_format= thd->variables.binlog_format; - if ((log_on == FALSE) || (binlog_format == BINLOG_FORMAT_ROW) || + if ((log_on == FALSE) || (thd->wsrep_binlog_format() == BINLOG_FORMAT_ROW) || (table_list->table->s->table_category == TABLE_CATEGORY_LOG) || (table_list->table->s->table_category == TABLE_CATEGORY_PERFORMANCE) || !(is_update_query(prelocking_ctx->sql_command) || - table_list->prelocking_placeholder || + (routine_modifies_data && table_list->prelocking_placeholder) || (thd->locked_tables_mode > LTM_LOCK_TABLES))) return TL_READ; else @@ -4329,19 +3777,21 @@ thr_lock_type read_lock_type_for_table(THD *thd, and, if prelocking strategy prescribes so, extend the prelocking set with tables and routines used by it. - @param[in] thd Thread context. - @param[in] prelocking_ctx Prelocking context. - @param[in] rt Element of prelocking set to be processed. - @param[in] prelocking_strategy Strategy which specifies how the - prelocking set should be extended when - one of its elements is processed. - @param[in] has_prelocking_list Indicates that prelocking set/list for - this statement has already been built. - @param[in] ot_ctx Context of open_table used to recover from - locking failures. - @param[out] need_prelocking Set to TRUE if it was detected that this - statement will require prelocked mode for - its execution, not touched otherwise. + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context. + @param[in] rt Element of prelocking set to be processed. + @param[in] prelocking_strategy Strategy which specifies how the + prelocking set should be extended when + one of its elements is processed. + @param[in] has_prelocking_list Indicates that prelocking set/list for + this statement has already been built. + @param[in] ot_ctx Context of open_table used to recover from + locking failures. + @param[out] need_prelocking Set to TRUE if it was detected that this + statement will require prelocked mode for + its execution, not touched otherwise. + @param[out] routine_modifies_data Set to TRUE if it was detected that this + routine does modify table data. @retval FALSE Success. @retval TRUE Failure (Conflicting metadata lock, OOM, other errors). @@ -4353,11 +3803,13 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, Prelocking_strategy *prelocking_strategy, bool has_prelocking_list, Open_table_context *ot_ctx, - bool *need_prelocking) + bool *need_prelocking, bool *routine_modifies_data) { MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace(); DBUG_ENTER("open_and_process_routine"); + *routine_modifies_data= false; + switch (mdl_type) { case MDL_key::FUNCTION: @@ -4410,10 +3862,13 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, DBUG_RETURN(TRUE); /* 'sp' is NULL when there is no such routine. */ - if (sp && !has_prelocking_list) + if (sp) { - prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp, - need_prelocking); + *routine_modifies_data= sp->modifies_data(); + + if (!has_prelocking_list) + prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp, + need_prelocking); } } else @@ -4488,8 +3943,6 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, this statement has already been built. @param[in] ot_ctx Context used to recover from a failed open_table() attempt. - @param[in] new_frm_mem Temporary MEM_ROOT to be used for - parsing .FRMs for views. @retval FALSE Success. @retval TRUE Error, reported unless there is a chance to recover from it. @@ -4500,8 +3953,7 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, uint *counter, uint flags, Prelocking_strategy *prelocking_strategy, bool has_prelocking_list, - Open_table_context *ot_ctx, - MEM_ROOT *new_frm_mem) + Open_table_context *ot_ctx) { bool error= FALSE; bool safe_to_ignore_table= FALSE; @@ -4567,9 +4019,35 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, tables->db, tables->table_name, tables)); //psergey: invalid read of size 1 here (*counter)++; - /* Not a placeholder: must be a base table or a view. Let us open it. */ - DBUG_ASSERT(!tables->table); + /* + Not a placeholder: must be a base/temporary table or a view. Let us open it. + */ + if (tables->table) + { + /* + If this TABLE_LIST object has an associated open TABLE object + (TABLE_LIST::table is not NULL), that TABLE object must be a pre-opened + temporary table. + */ + DBUG_ASSERT(is_temporary_table(tables)); + } + else if (tables->open_type == OT_TEMPORARY_ONLY) + { + /* + OT_TEMPORARY_ONLY means that we are in CREATE TEMPORARY TABLE statement. + Also such table list element can't correspond to prelocking placeholder + or to underlying table of merge table. + So existing temporary table should have been preopened by this moment + and we can simply continue without trying to open temporary or base + table. + */ + DBUG_ASSERT(tables->open_strategy); + DBUG_ASSERT(!tables->prelocking_placeholder); + DBUG_ASSERT(!tables->parent_l); + DBUG_RETURN(0); + } + /* Not a placeholder: must be a base table or a view. Let us open it. */ if (tables->prelocking_placeholder) { /* @@ -4580,7 +4058,35 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, */ No_such_table_error_handler no_such_table_handler; thd->push_internal_handler(&no_such_table_handler); - error= open_table(thd, tables, new_frm_mem, ot_ctx); + + /* + We're opening a table from the prelocking list. + + Since this table list element might have been added after pre-opening + of temporary tables we have to try to open temporary table for it. + + We can't simply skip this table list element and postpone opening of + temporary tabletill the execution of substatement for several reasons: + - Temporary table can be a MERGE table with base underlying tables, + so its underlying tables has to be properly open and locked at + prelocking stage. + - Temporary table can be a MERGE table and we might be in PREPARE + phase for a prepared statement. In this case it is important to call + HA_ATTACH_CHILDREN for all merge children. + This is necessary because merge children remember "TABLE_SHARE ref type" + and "TABLE_SHARE def version" in the HA_ATTACH_CHILDREN operation. + If HA_ATTACH_CHILDREN is not called, these attributes are not set. + Then, during the first EXECUTE, those attributes need to be updated. + That would cause statement re-preparing (because changing those + attributes during EXECUTE is caught by THD::m_reprepare_observers). + The problem is that since those attributes are not set in merge + children, another round of PREPARE will not help. + */ + error= open_temporary_table(thd, tables); + + if (!error && !tables->table) + error= open_table(thd, tables, ot_ctx); + thd->pop_internal_handler(); safe_to_ignore_table= no_such_table_handler.safely_trapped_errors(); } @@ -4594,14 +4100,29 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, */ Repair_mrg_table_error_handler repair_mrg_table_handler; thd->push_internal_handler(&repair_mrg_table_handler); - error= open_table(thd, tables, new_frm_mem, ot_ctx); + + error= open_temporary_table(thd, tables); + if (!error && !tables->table) + error= open_table(thd, tables, ot_ctx); + thd->pop_internal_handler(); safe_to_ignore_table= repair_mrg_table_handler.safely_trapped_errors(); } else - error= open_table(thd, tables, new_frm_mem, ot_ctx); + { + if (tables->parent_l) + { + /* + Even if we are opening table not from the prelocking list we + still might need to look for a temporary table if this table + list element corresponds to underlying table of a merge table. + */ + error= open_temporary_table(thd, tables); + } - free_root(new_frm_mem, MYF(MY_KEEP_PREALLOC)); + if (!error && !tables->table) + error= open_table(thd, tables, ot_ctx); + } if (error) { @@ -4687,16 +4208,7 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, goto end; } - if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables_mode) - { - if (tables->lock_type == TL_WRITE_DEFAULT) - tables->table->reginfo.lock_type= thd->update_lock_default; - else if (tables->lock_type == TL_READ_DEFAULT) - tables->table->reginfo.lock_type= - read_lock_type_for_table(thd, lex, tables); - else - tables->table->reginfo.lock_type= tables->lock_type; - } + /* Copy grant information from TABLE_LIST instance to TABLE one. */ tables->table->grant= tables->grant; /* Check and update metadata version of a base table. */ @@ -4718,6 +4230,32 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, goto end; } + if (get_use_stat_tables_mode(thd) > NEVER && tables->table) + { + TABLE_SHARE *table_share= tables->table->s; + if (table_share && table_share->table_category == TABLE_CATEGORY_USER && + table_share->tmp_table == NO_TMP_TABLE) + { + if (table_share->stats_cb.stats_can_be_read || + !alloc_statistics_for_table_share(thd, table_share, FALSE)) + { + if (table_share->stats_cb.stats_can_be_read) + { + KEY *key_info= table_share->key_info; + KEY *key_info_end= key_info + table_share->keys; + KEY *table_key_info= tables->table->key_info; + for ( ; key_info < key_info_end; key_info++, table_key_info++) + table_key_info->read_stats= key_info->read_stats; + Field **field_ptr= table_share->field; + Field **table_field_ptr= tables->table->field; + for ( ; *field_ptr; field_ptr++, table_field_ptr++) + (*table_field_ptr)->read_stats= (*field_ptr)->read_stats; + tables->table->stats_is_read= table_share->stats_cb.stats_is_read; + } + } + } + } + process_view_routines: /* Again we may need cache all routines used by this view and add @@ -4744,13 +4282,6 @@ end: DBUG_RETURN(error); } -extern "C" uchar *schema_set_get_key(const uchar *record, size_t *length, - my_bool not_used __attribute__((unused))) -{ - TABLE_LIST *table=(TABLE_LIST*) record; - *length= table->db_length; - return (uchar*) table->db; -} /** Acquire upgradable (SNW, SNRW) metadata locks on tables used by @@ -4781,14 +4312,13 @@ extern "C" uchar *schema_set_get_key(const uchar *record, size_t *length, */ bool -lock_table_names(THD *thd, +lock_table_names(THD *thd, const DDL_options_st &options, TABLE_LIST *tables_start, TABLE_LIST *tables_end, ulong lock_wait_timeout, uint flags) { MDL_request_list mdl_requests; TABLE_LIST *table; MDL_request global_request; - Hash_set<TABLE_LIST, schema_set_get_key> schema_set; ulong org_lock_wait_timeout= lock_wait_timeout; /* Check if we are using CREATE TABLE ... IF NOT EXISTS */ bool create_table; @@ -4800,35 +4330,22 @@ lock_table_names(THD *thd, for (table= tables_start; table && table != tables_end; table= table->next_global) { - if (table->mdl_request.type >= MDL_SHARED_NO_WRITE && - !(table->open_type == OT_TEMPORARY_ONLY || - (flags & MYSQL_OPEN_TEMPORARY_ONLY) || - (table->open_type != OT_BASE_ONLY && - ! (flags & MYSQL_OPEN_SKIP_TEMPORARY) && - find_temporary_table(thd, table)))) + if (table->mdl_request.type < MDL_SHARED_UPGRADABLE || + table->open_type == OT_TEMPORARY_ONLY || + (table->open_type == OT_TEMPORARY_OR_BASE && is_temporary_table(table))) { - if (! (flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK) && - schema_set.insert(table)) - DBUG_RETURN(TRUE); - mdl_requests.push_front(&table->mdl_request); + continue; } - } - if (mdl_requests.is_empty()) - DBUG_RETURN(FALSE); - - /* Check if CREATE TABLE IF NOT EXISTS was used */ - create_table= (tables_start && tables_start->open_strategy == - TABLE_LIST::OPEN_IF_EXISTS); + /* Write lock on normal tables is not allowed in a read only transaction. */ + if (thd->tx_read_only) + { + my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0)); + DBUG_RETURN(true); + } - if (!(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)) - { - /* - Scoped locks: Take intention exclusive locks on all involved - schemas. - */ - Hash_set<TABLE_LIST, schema_set_get_key>::Iterator it(schema_set); - while ((table= it++)) + /* Scoped locks: Take intention exclusive locks on all involved schemas. */ + if (!(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)) { MDL_request *schema_request= new (thd->mem_root) MDL_request; if (schema_request == NULL) @@ -4839,6 +4356,18 @@ lock_table_names(THD *thd, mdl_requests.push_front(schema_request); } + mdl_requests.push_front(&table->mdl_request); + } + + if (mdl_requests.is_empty()) + DBUG_RETURN(FALSE); + + /* Check if CREATE TABLE without REPLACE was used */ + create_table= thd->lex->sql_command == SQLCOM_CREATE_TABLE && + !options.or_replace(); + + if (!(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)) + { /* Protect this statement against concurrent global read lock by acquiring global intention exclusive lock with statement @@ -4851,17 +4380,18 @@ lock_table_names(THD *thd, mdl_requests.push_front(&global_request); if (create_table) + #ifdef WITH_WSREP + if (thd->lex->sql_command != SQLCOM_CREATE_TABLE && + thd->wsrep_exec_mode != REPL_RECV) + #endif lock_wait_timeout= 0; // Don't wait for timeout } for (;;) { - bool exists= TRUE; - bool res; - if (create_table) thd->push_internal_handler(&error_handler); // Avoid warnings & errors - res= thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout); + bool res= thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout); if (create_table) thd->pop_internal_handler(); if (!res) @@ -4871,35 +4401,32 @@ lock_table_names(THD *thd, DBUG_RETURN(TRUE); // Return original error /* - We come here in the case of lock timeout when executing - CREATE TABLE IF NOT EXISTS. - Verify that table really exists (it should as we got a lock conflict) + We come here in the case of lock timeout when executing CREATE TABLE. + Verify that table does exist (it usually does, as we got a lock conflict) */ - if (check_if_table_exists(thd, tables_start, 1, &exists)) - DBUG_RETURN(TRUE); // Should never happen - if (exists) + if (ha_table_exists(thd, tables_start->db, tables_start->table_name)) { - if (thd->lex->create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS) + if (options.if_not_exists()) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_TABLE_EXISTS_ERROR, + ER_THD(thd, ER_TABLE_EXISTS_ERROR), tables_start->table_name); } else my_error(ER_TABLE_EXISTS_ERROR, MYF(0), tables_start->table_name); DBUG_RETURN(TRUE); } - /* purecov: begin inspected */ /* - We got error from acquire_locks but table didn't exists. - In theory this should never happen, except maybe in - CREATE or DROP DATABASE scenario. + We got error from acquire_locks, but the table didn't exists. + This could happen if another connection runs a statement + involving this non-existent table, and this statement took the mdl, + but didn't error out with ER_NO_SUCH_TABLE yet (yes, a race condition). We play safe and restart the original acquire_locks with the - original timeout + original timeout. */ create_table= 0; lock_wait_timeout= org_lock_wait_timeout; - /* purecov: end */ } } @@ -4931,34 +4458,33 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, for (table= tables_start; table && table != tables_end; table= table->next_global) { - if (table->mdl_request.type >= MDL_SHARED_NO_WRITE && - !(table->open_type == OT_TEMPORARY_ONLY || - (flags & MYSQL_OPEN_TEMPORARY_ONLY) || - (table->open_type != OT_BASE_ONLY && - ! (flags & MYSQL_OPEN_SKIP_TEMPORARY) && - find_temporary_table(thd, table)))) + if (table->mdl_request.type < MDL_SHARED_UPGRADABLE || + table->open_type == OT_TEMPORARY_ONLY || + (table->open_type == OT_TEMPORARY_OR_BASE && is_temporary_table(table))) { - /* - We don't need to do anything about the found TABLE instance as it - will be handled later in open_tables(), we only need to check that - an upgradable lock is already acquired. When we enter LOCK TABLES - mode, SNRW locks are acquired before all other locks. So if under - LOCK TABLES we find that there is TABLE instance with upgradeable - lock, all other instances of TABLE for the same table will have the - same ticket. - - Note that this works OK even for CREATE TABLE statements which - request X type of metadata lock. This is because under LOCK TABLES - such statements don't create the table but only check if it exists - or, in most complex case, only insert into it. - Thus SNRW lock should be enough. - - Note that find_table_for_mdl_upgrade() will report an error if - no suitable ticket is found. - */ - if (!find_table_for_mdl_upgrade(thd, table->db, table->table_name, false)) - return TRUE; + continue; } + + /* + We don't need to do anything about the found TABLE instance as it + will be handled later in open_tables(), we only need to check that + an upgradable lock is already acquired. When we enter LOCK TABLES + mode, SNRW locks are acquired before all other locks. So if under + LOCK TABLES we find that there is TABLE instance with upgradeable + lock, all other instances of TABLE for the same table will have the + same ticket. + + Note that this works OK even for CREATE TABLE statements which + request X type of metadata lock. This is because under LOCK TABLES + such statements don't create the table but only check if it exists + or, in most complex case, only insert into it. + Thus SNRW lock should be enough. + + Note that find_table_for_mdl_upgrade() will report an error if + no suitable ticket is found. + */ + if (!find_table_for_mdl_upgrade(thd, table->db, table->table_name, NULL)) + return TRUE; } return FALSE; @@ -4994,22 +4520,24 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, @retval TRUE Error, reported. */ -bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, - Prelocking_strategy *prelocking_strategy) +bool open_tables(THD *thd, const DDL_options_st &options, + TABLE_LIST **start, uint *counter, uint flags, + Prelocking_strategy *prelocking_strategy) { /* - We use pointers to "next_global" member in the last processed TABLE_LIST - element and to the "next" member in the last processed Sroutine_hash_entry - element as iterators over, correspondingly, the table list and stored routines - list which stay valid and allow to continue iteration when new elements are - added to the tail of the lists. + We use pointers to "next_global" member in the last processed + TABLE_LIST element and to the "next" member in the last processed + Sroutine_hash_entry element as iterators over, correspondingly, + the table list and stored routines list which stay valid and allow + to continue iteration when new elements are added to the tail of + the lists. */ TABLE_LIST **table_to_open; Sroutine_hash_entry **sroutine_to_open; TABLE_LIST *tables; Open_table_context ot_ctx(thd, flags); bool error= FALSE; - MEM_ROOT new_frm_mem; + bool some_routine_modifies_data= FALSE; bool has_prelocking_list; DBUG_ENTER("open_tables"); @@ -5021,13 +4549,6 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, DBUG_RETURN(true); } - /* - Initialize temporary MEM_ROOT for new .FRM parsing. Do not allocate - anything yet, to avoid penalty for statements which don't use views - and thus new .FRM format. - */ - init_sql_alloc(&new_frm_mem, 8024, 0); - thd->current_tablenr= 0; restart: /* @@ -5045,7 +4566,7 @@ restart: table_to_open= start; sroutine_to_open= (Sroutine_hash_entry**) &thd->lex->sroutines_list.first; *counter= 0; - thd_proc_info(thd, "Opening tables"); + THD_STAGE_INFO(thd, stage_opening_tables); /* If we are executing LOCK TABLES statement or a DDL statement @@ -5076,22 +4597,23 @@ restart: flags)) { error= TRUE; - goto err; + goto error; } } else { TABLE_LIST *table; - if (lock_table_names(thd, *start, thd->lex->first_not_own_table(), + if (lock_table_names(thd, options, *start, + thd->lex->first_not_own_table(), ot_ctx.get_timeout(), flags)) { error= TRUE; - goto err; + goto error; } for (table= *start; table && table != thd->lex->first_not_own_table(); table= table->next_global) { - if (table->mdl_request.type >= MDL_SHARED_NO_WRITE) + if (table->mdl_request.type >= MDL_SHARED_UPGRADABLE) table->mdl_request.ticket= NULL; } } @@ -5114,8 +4636,7 @@ restart: { error= open_and_process_table(thd, thd->lex, tables, counter, flags, prelocking_strategy, - has_prelocking_list, &ot_ctx, - &new_frm_mem); + has_prelocking_list, &ot_ctx); if (error) { @@ -5144,12 +4665,16 @@ restart: it may change in future. */ if (ot_ctx.recover_from_failed_open()) - goto err; + goto error; + + /* Re-open temporary tables after close_tables_for_reopen(). */ + if (open_temporary_tables(thd, *start)) + goto error; error= FALSE; goto restart; } - goto err; + goto error; } DEBUG_SYNC(thd, "open_tables_after_open_and_process_table"); @@ -5178,11 +4703,16 @@ restart: sroutine_to_open= &rt->next, rt= rt->next) { bool need_prelocking= false; + bool routine_modifies_data; TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; error= open_and_process_routine(thd, thd->lex, rt, prelocking_strategy, has_prelocking_list, &ot_ctx, - &need_prelocking); + &need_prelocking, + &routine_modifies_data); + + // Remember if any of SF modifies data. + some_routine_modifies_data|= routine_modifies_data; if (need_prelocking && ! thd->lex->requires_prelocking()) thd->lex->mark_as_requiring_prelocking(save_query_tables_last); @@ -5197,7 +4727,11 @@ restart: close_tables_for_reopen(thd, start, ot_ctx.start_of_statement_svp()); if (ot_ctx.recover_from_failed_open()) - goto err; + goto error; + + /* Re-open temporary tables after close_tables_for_reopen(). */ + if (open_temporary_tables(thd, *start)) + goto error; error= FALSE; goto restart; @@ -5207,7 +4741,7 @@ restart: Something is wrong with the table or its contents, and an error has been emitted; we must abort. */ - goto err; + goto error; } } } @@ -5218,27 +4752,70 @@ restart: children, attach the children to their parents. At end of statement, the children are detached. Attaching and detaching are always done, even under LOCK TABLES. + + We also convert all TL_WRITE_DEFAULT and TL_READ_DEFAULT locks to + appropriate "real" lock types to be used for locking and to be passed + to storage engine. + + And start wsrep TOI if needed. */ for (tables= *start; tables; tables= tables->next_global) { TABLE *tbl= tables->table; + if (!tbl) + continue; + /* Schema tables may not have a TABLE object here. */ - if (tbl && tbl->file->ht->db_type == DB_TYPE_MRG_MYISAM) + if (tbl->file->ht->db_type == DB_TYPE_MRG_MYISAM) { /* MERGE tables need to access parent and child TABLE_LISTs. */ DBUG_ASSERT(tbl->pos_in_table_list == tables); if (tbl->file->extra(HA_EXTRA_ATTACH_CHILDREN)) { error= TRUE; - goto err; + goto error; } } + + /* Set appropriate TABLE::lock_type. */ + if (tbl && tables->lock_type != TL_UNLOCK && !thd->locked_tables_mode) + { + if (tables->lock_type == TL_WRITE_DEFAULT) + tbl->reginfo.lock_type= thd->update_lock_default; + else if (tables->lock_type == TL_READ_DEFAULT) + tbl->reginfo.lock_type= + read_lock_type_for_table(thd, thd->lex, tables, + some_routine_modifies_data); + else + tbl->reginfo.lock_type= tables->lock_type; + } } -err: - thd_proc_info(thd, "After opening tables"); - free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block + if (WSREP_ON && + wsrep_replicate_myisam && + (*start) && + (*start)->table && + (*start)->table->file->ht == myisam_hton && + wsrep_thd_exec_mode(thd) == LOCAL_STATE && + !is_stat_table((*start)->db, (*start)->alias) && + thd->get_command() != COM_STMT_PREPARE && + ((thd->lex->sql_command == SQLCOM_INSERT || + thd->lex->sql_command == SQLCOM_INSERT_SELECT || + thd->lex->sql_command == SQLCOM_REPLACE || + thd->lex->sql_command == SQLCOM_REPLACE_SELECT || + thd->lex->sql_command == SQLCOM_UPDATE || + thd->lex->sql_command == SQLCOM_UPDATE_MULTI || + thd->lex->sql_command == SQLCOM_LOAD || + thd->lex->sql_command == SQLCOM_DELETE))) + { + WSREP_TO_ISOLATION_BEGIN(NULL, NULL, (*start)); + } + +error: +WSREP_ERROR_LABEL: + THD_STAGE_INFO(thd, stage_after_opening_tables); + thd_proc_info(thd, 0); if (error && *table_to_open) { @@ -5299,6 +4876,25 @@ handle_routine(THD *thd, Query_tables_list *prelocking_ctx, } +/* + @note this can be changed to use a hash, instead of scanning the linked + list, if the performance of this function will ever become an issue +*/ +bool table_already_fk_prelocked(TABLE_LIST *tl, LEX_STRING *db, + LEX_STRING *table, thr_lock_type lock_type) +{ + for (; tl; tl= tl->next_global ) + { + if (tl->lock_type >= lock_type && + tl->prelocking_placeholder == TABLE_LIST::FK && + strcmp(tl->db, db->str) == 0 && + strcmp(tl->table_name, table->str) == 0) + return true; + } + return false; +} + + /** Defines how prelocking algorithm for DML statements should handle table list elements: @@ -5338,6 +4934,52 @@ handle_table(THD *thd, Query_tables_list *prelocking_ctx, add_tables_and_routines_for_triggers(thd, prelocking_ctx, table_list)) return TRUE; } + + if (table_list->table->file->referenced_by_foreign_key()) + { + List <FOREIGN_KEY_INFO> fk_list; + List_iterator<FOREIGN_KEY_INFO> fk_list_it(fk_list); + FOREIGN_KEY_INFO *fk; + Query_arena *arena, backup; + + arena= thd->activate_stmt_arena_if_needed(&backup); + + table_list->table->file->get_parent_foreign_key_list(thd, &fk_list); + if (thd->is_error()) + { + if (arena) + thd->restore_active_arena(arena, &backup); + return TRUE; + } + + *need_prelocking= TRUE; + + while ((fk= fk_list_it++)) + { + // FK_OPTION_RESTRICT and FK_OPTION_NO_ACTION only need read access + uint8 op= table_list->trg_event_map; + thr_lock_type lock_type; + + if ((op & (1 << TRG_EVENT_DELETE) && fk_modifies_child(fk->delete_method)) + || (op & (1 << TRG_EVENT_UPDATE) && fk_modifies_child(fk->update_method))) + lock_type= TL_WRITE_ALLOW_WRITE; + else + lock_type= TL_READ; + + if (table_already_fk_prelocked(prelocking_ctx->query_tables, + fk->foreign_db, fk->foreign_table, + lock_type)) + continue; + + TABLE_LIST *tl= (TABLE_LIST *) thd->alloc(sizeof(TABLE_LIST)); + tl->init_one_table_for_prelocking(fk->foreign_db->str, fk->foreign_db->length, + fk->foreign_table->str, fk->foreign_table->length, + NULL, lock_type, false, table_list->belong_to_view, + op, &prelocking_ctx->query_tables_last); + } + if (arena) + thd->restore_active_arena(arena, &backup); + } } return FALSE; @@ -5504,11 +5146,15 @@ static bool check_lock_and_start_stmt(THD *thd, engine is important as, for example, InnoDB uses it to determine what kind of row locks should be acquired when executing statement in prelocked mode or under LOCK TABLES with @@innodb_table_locks = 0. + + Last argument routine_modifies_data for read_lock_type_for_table() + is ignored, as prelocking placeholder will never be set here. */ + DBUG_ASSERT(table_list->prelocking_placeholder == false); if (table_list->lock_type == TL_WRITE_DEFAULT) lock_type= thd->update_lock_default; else if (table_list->lock_type == TL_READ_DEFAULT) - lock_type= read_lock_type_for_table(thd, prelocking_ctx, table_list); + lock_type= read_lock_type_for_table(thd, prelocking_ctx, table_list, true); else lock_type= table_list->lock_type; @@ -5623,18 +5269,22 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, bool error; DBUG_ENTER("open_ltable"); + /* Ignore temporary tables as they have already ben opened*/ + if (table_list->table) + DBUG_RETURN(table_list->table); + /* should not be used in a prelocked_mode context, see NOTE above */ DBUG_ASSERT(thd->locked_tables_mode < LTM_PRELOCKED); - thd_proc_info(thd, "Opening table"); + THD_STAGE_INFO(thd, stage_opening_tables); thd->current_tablenr= 0; /* open_ltable can be used only for BASIC TABLEs */ table_list->required_type= FRMTYPE_TABLE; /* This function can't properly handle requests for such metadata locks. */ - DBUG_ASSERT(table_list->mdl_request.type < MDL_SHARED_NO_WRITE); + DBUG_ASSERT(table_list->mdl_request.type < MDL_SHARED_UPGRADABLE); - while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx)) && + while ((error= open_table(thd, table_list, &ot_ctx)) && ot_ctx.can_recover_from_failed_open()) { /* @@ -5695,7 +5345,9 @@ end: trans_rollback_stmt(thd); close_thread_tables(thd); } - thd_proc_info(thd, "After opening table"); + THD_STAGE_INFO(thd, stage_after_opening_tables); + + thd_proc_info(thd, 0); DBUG_RETURN(table); } @@ -5720,7 +5372,8 @@ end: @retval TRUE Error */ -bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, +bool open_and_lock_tables(THD *thd, const DDL_options_st &options, + TABLE_LIST *tables, bool derived, uint flags, Prelocking_strategy *prelocking_strategy) { @@ -5729,7 +5382,7 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, DBUG_ENTER("open_and_lock_tables"); DBUG_PRINT("enter", ("derived handling: %d", derived)); - if (open_tables(thd, &tables, &counter, flags, prelocking_strategy)) + if (open_tables(thd, options, &tables, &counter, flags, prelocking_strategy)) goto err; DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", { @@ -5741,6 +5394,8 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, if (lock_tables(thd, tables, counter, flags)) goto err; + (void) read_statistics_for_tables_if_needed(thd, tables); + if (derived) { if (mysql_handle_derived(thd->lex, DT_INIT)) @@ -5871,7 +5526,6 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, uint flags) { TABLE_LIST *table; - DBUG_ENTER("lock_tables"); /* We can't meet statement requiring prelocking if we already @@ -6013,6 +5667,36 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, } +/* + Restart transaction for tables + + This is used when we had to do an implicit commit after tables are opened + and want to restart transactions on tables. + + This is used in case of: + LOCK TABLES xx + CREATE OR REPLACE TABLE xx; +*/ + +bool restart_trans_for_tables(THD *thd, TABLE_LIST *table) +{ + DBUG_ENTER("restart_trans_for_tables"); + + for (; table; table= table->next_global) + { + if (table->placeholder()) + continue; + + if (check_lock_and_start_stmt(thd, thd->lex, table)) + { + DBUG_ASSERT(0); // Should never happen + DBUG_RETURN(TRUE); + } + } + DBUG_RETURN(FALSE); +} + + /** Prepare statement for reopening of tables and recalculation of set of prelocked tables. @@ -6074,12 +5758,18 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, the opened TABLE instance will be addded to THD::temporary_tables list. @param thd Thread context. + @param hton Storage engine of the table, if known, + or NULL otherwise. + @param frm frm image @param path Path (without .frm) @param db Database name. @param table_name Table name. @param add_to_temporary_tables_list Specifies if the opened TABLE instance should be linked into THD::temporary_tables list. + @param open_in_engine Indicates that we need to open table + in storage engine in addition to + constructing TABLE object for it. @note This function is used: - by alter_table() to open a temporary table; @@ -6089,26 +5779,35 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, @retval NULL on error. */ -TABLE *open_table_uncached(THD *thd, const char *path, const char *db, +TABLE *open_table_uncached(THD *thd, handlerton *hton, + LEX_CUSTRING *frm, + const char *path, const char *db, const char *table_name, - bool add_to_temporary_tables_list) + bool add_to_temporary_tables_list, + bool open_in_engine) { TABLE *tmp_table; TABLE_SHARE *share; char cache_key[MAX_DBKEY_LENGTH], *saved_cache_key, *tmp_path; uint key_length; - TABLE_LIST table_list; DBUG_ENTER("open_table_uncached"); DBUG_PRINT("enter", ("table: '%s'.'%s' path: '%s' server_id: %u " "pseudo_thread_id: %lu", db, table_name, path, - (uint) thd->server_id, (ulong) thd->variables.pseudo_thread_id)); + (uint) thd->variables.server_id, + (ulong) thd->variables.pseudo_thread_id)); + + if (add_to_temporary_tables_list) + { + /* Temporary tables are not safe for parallel replication. */ + if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec && + thd->wait_for_prior_commit()) + DBUG_RETURN(NULL); + } - table_list.db= (char*) db; - table_list.table_name= (char*) table_name; /* Create the cache_key for temporary tables */ - key_length= create_table_def_key(thd, cache_key, &table_list, 1); + key_length= create_tmp_table_def_key(thd, cache_key, db, table_name); if (!(tmp_table= (TABLE*) my_malloc(sizeof(*tmp_table) + sizeof(*share) + strlen(path)+1 + key_length, @@ -6122,14 +5821,40 @@ TABLE *open_table_uncached(THD *thd, const char *path, const char *db, init_tmp_table_share(thd, share, saved_cache_key, key_length, strend(saved_cache_key)+1, tmp_path); + share->db_plugin= ha_lock_engine(thd, hton); - if (open_table_def(thd, share, 0) || - open_table_from_share(thd, share, table_name, + /* + Use the frm image, if possible, open the file otherwise. + + The image might be unavailable in ALTER TABLE, when the discovering + engine took over the ownership (see TABLE::read_frm_image). + */ + int res= frm->str + ? share->init_from_binary_frm_image(thd, false, frm->str, frm->length) + : open_table_def(thd, share, GTS_TABLE | GTS_USE_DISCOVERY); + + if (res) + { + /* No need to lock share->mutex as this is not needed for tmp tables */ + free_table_share(share); + my_free(tmp_table); + DBUG_RETURN(0); + } + + share->m_psi= PSI_CALL_get_table_share(true, share); + + if (open_table_from_share(thd, share, table_name, + open_in_engine ? (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX), + HA_GET_INDEX) : 0, READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, ha_open_options, - tmp_table, FALSE)) + tmp_table, + /* + Set "is_create_table" if the table does not + exist in SE + */ + open_in_engine ? false : true)) { /* No need to lock share->mutex as this is not needed for tmp tables */ free_table_share(share); @@ -6138,19 +5863,24 @@ TABLE *open_table_uncached(THD *thd, const char *path, const char *db, } tmp_table->reginfo.lock_type= TL_WRITE; // Simulate locked + tmp_table->grant.privilege= TMP_TABLE_ACLS; share->tmp_table= (tmp_table->file->has_transactions() ? TRANSACTIONAL_TMP_TABLE : NON_TRANSACTIONAL_TMP_TABLE); if (add_to_temporary_tables_list) { + thd->lock_temporary_tables(); /* growing temp list at the head */ tmp_table->next= thd->temporary_tables; if (tmp_table->next) tmp_table->next->prev= tmp_table; thd->temporary_tables= tmp_table; thd->temporary_tables->prev= 0; - if (thd->slave_thread) - slave_open_temp_tables++; + if (thd->rgi_slave) + { + thread_safe_increment32(&slave_open_temp_tables); + } + thd->unlock_temporary_tables(0); } tmp_table->pos_in_table_list= 0; DBUG_PRINT("tmptable", ("opened table: '%s'.'%s' 0x%lx", tmp_table->s->db.str, @@ -6260,6 +5990,164 @@ static void update_field_dependencies(THD *thd, Field *field, TABLE *table) } +/** + Find a temporary table specified by TABLE_LIST instance in the cache and + prepare its TABLE instance for use. + + This function tries to resolve this table in the list of temporary tables + of this thread. Temporary tables are thread-local and "shadow" base + tables with the same name. + + @note In most cases one should use open_temporary_tables() instead + of this call. + + @note One should finalize process of opening temporary table for table + list element by calling open_and_process_table(). This function + is responsible for table version checking and handling of merge + tables. + + @note We used to check global_read_lock before opening temporary tables. + However, that limitation was artificial and is removed now. + + @return Error status. + @retval FALSE On success. If a temporary table exists for the given + key, tl->table is set. + @retval TRUE On error. my_error() has been called. +*/ + +bool open_temporary_table(THD *thd, TABLE_LIST *tl) +{ + TABLE *table; + DBUG_ENTER("open_temporary_table"); + DBUG_PRINT("enter", ("table: '%s'.'%s'", tl->db, tl->table_name)); + + /* + Code in open_table() assumes that TABLE_LIST::table can + be non-zero only for pre-opened temporary tables. + */ + DBUG_ASSERT(tl->table == NULL); + + /* + This function should not be called for cases when derived or I_S + tables can be met since table list elements for such tables can + have invalid db or table name. + Instead open_temporary_tables() should be used. + */ + DBUG_ASSERT(!tl->derived && !tl->schema_table); + + if (tl->open_type == OT_BASE_ONLY || !thd->have_temporary_tables()) + { + DBUG_PRINT("info", ("skip_temporary is set or no temporary tables")); + DBUG_RETURN(FALSE); + } + + if (find_and_use_temporary_table(thd, tl, &table)) + DBUG_RETURN(TRUE); + if (!table) + { + if (tl->open_type == OT_TEMPORARY_ONLY && + tl->open_strategy == TABLE_LIST::OPEN_NORMAL) + { + my_error(ER_NO_SUCH_TABLE, MYF(0), tl->db, tl->table_name); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); + } + + /* + Temporary tables are not safe for parallel replication. They were + designed to be visible to one thread only, so have no table locking. + Thus there is no protection against two conflicting transactions + committing in parallel and things like that. + + So for now, anything that uses temporary tables will be serialised + with anything before it, when using parallel replication. + + ToDo: We might be able to introduce a reference count or something + on temp tables, and have slave worker threads wait for it to reach + zero before being allowed to use the temp table. Might not be worth + it though, as statement-based replication using temporary tables is + in any case rather fragile. + */ + if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec && + thd->wait_for_prior_commit()) + DBUG_RETURN(true); + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (tl->partition_names) + { + /* Partitioned temporary tables is not supported. */ + DBUG_ASSERT(!table->part_info); + my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(true); + } +#endif + + if (table->query_id) + { + /* + We're trying to use the same temporary table twice in a query. + Right now we don't support this because a temporary table is always + represented by only one TABLE object in THD, and it can not be + cloned. Emit an error for an unsupported behaviour. + */ + + DBUG_PRINT("error", + ("query_id: %lu server_id: %u pseudo_thread_id: %lu", + (ulong) table->query_id, (uint) thd->variables.server_id, + (ulong) thd->variables.pseudo_thread_id)); + my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr()); + DBUG_RETURN(TRUE); + } + + table->query_id= thd->query_id; + thd->thread_specific_used= TRUE; + + tl->updatable= 1; // It is not derived table nor non-updatable VIEW. + tl->table= table; + + table->init(thd, tl); + + DBUG_PRINT("info", ("Using temporary table")); + DBUG_RETURN(FALSE); +} + + +/** + Pre-open temporary tables corresponding to table list elements. + + @note One should finalize process of opening temporary tables + by calling open_tables(). This function is responsible + for table version checking and handling of merge tables. + + @return Error status. + @retval FALSE On success. If a temporary tables exists for the + given element, tl->table is set. + @retval TRUE On error. my_error() has been called. +*/ + +bool open_temporary_tables(THD *thd, TABLE_LIST *tl_list) +{ + TABLE_LIST *first_not_own= thd->lex->first_not_own_table(); + DBUG_ENTER("open_temporary_tables"); + + for (TABLE_LIST *tl= tl_list; tl && tl != first_not_own; tl= tl->next_global) + { + if (tl->derived || tl->schema_table) + { + /* + Derived and I_S tables will be handled by a later call to open_tables(). + */ + continue; + } + + if (open_temporary_table(thd, tl)) + DBUG_RETURN(TRUE); + } + + DBUG_RETURN(FALSE); +} + /* Find a field by name in a view that uses merge algorithm. @@ -6388,17 +6276,14 @@ find_field_in_natural_join(THD *thd, TABLE_LIST *table_ref, const char *name, List_iterator_fast<Natural_join_column> field_it(*(table_ref->join_columns)); Natural_join_column *nj_col, *curr_nj_col; - Field *found_field; - Query_arena *arena, backup; + Field *UNINIT_VAR(found_field); + Query_arena *UNINIT_VAR(arena), backup; DBUG_ENTER("find_field_in_natural_join"); DBUG_PRINT("enter", ("field name: '%s', ref 0x%lx", name, (ulong) ref)); DBUG_ASSERT(table_ref->is_natural_join && table_ref->join_columns); DBUG_ASSERT(*actual_table == NULL); - LINT_INIT(arena); - LINT_INIT(found_field); - for (nj_col= NULL, curr_nj_col= field_it++; curr_nj_col; curr_nj_col= field_it++) { @@ -6418,7 +6303,6 @@ find_field_in_natural_join(THD *thd, TABLE_LIST *table_ref, const char *name, if (nj_col->view_field) { Item *item; - LINT_INIT(arena); if (register_tree_change) arena= thd->activate_stmt_arena_if_needed(&backup); /* @@ -6659,6 +6543,7 @@ find_field_in_table_ref(THD *thd, TABLE_LIST *table_list, */ table_name && table_name[0] && (my_strcasecmp(table_alias_charset, table_list->alias, table_name) || + (db_name && db_name[0] && (!table_list->db || !table_list->db[0])) || (db_name && db_name[0] && table_list->db && table_list->db[0] && (table_list->schema_table ? my_strcasecmp(system_charset_info, db_name, table_list->db) : @@ -6919,7 +6804,7 @@ find_field_in_tables(THD *thd, Item_ident *item, if (!table_ref->belong_to_view && !table_ref->belong_to_derived) { - SELECT_LEX *current_sel= thd->lex->current_select; + SELECT_LEX *current_sel= item->context->select_lex; SELECT_LEX *last_select= table_ref->select_lex; bool all_merged= TRUE; for (SELECT_LEX *sl= current_sel; sl && sl!=last_select; @@ -7132,7 +7017,10 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter, for (uint i= 0; (item=li++); i++) { - if (field_name && item->real_item()->type() == Item::FIELD_ITEM) + if (field_name && + (item->real_item()->type() == Item::FIELD_ITEM || + ((item->type() == Item::REF_ITEM) && + (((Item_ref *)item)->ref_type() == Item_ref::VIEW_REF)))) { Item_ident *item_field= (Item_ident*) item; @@ -7258,35 +7146,6 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter, break; } } - else if (table_name && item->type() == Item::REF_ITEM && - ((Item_ref *)item)->ref_type() == Item_ref::VIEW_REF) - { - /* - TODO:Here we process prefixed view references only. What we should - really do is process all types of Item_refs. But this will currently - lead to a clash with the way references to outer SELECTs (from the - HAVING clause) are handled in e.g. : - SELECT 1 FROM t1 AS t1_o GROUP BY a - HAVING (SELECT t1_o.a FROM t1 AS t1_i GROUP BY t1_i.a LIMIT 1). - Processing all Item_refs here will cause t1_o.a to resolve to itself. - We still need to process the special case of Item_direct_view_ref - because in the context of views they have the same meaning as - Item_field for tables. - */ - Item_ident *item_ref= (Item_ident *) item; - if (field_name && item_ref->name && item_ref->table_name && - !my_strcasecmp(system_charset_info, item_ref->name, field_name) && - !my_strcasecmp(table_alias_charset, item_ref->table_name, - table_name) && - (!db_name || (item_ref->db_name && - !strcmp (item_ref->db_name, db_name)))) - { - found= li.ref(); - *counter= i; - *resolution= RESOLVED_IGNORING_ALIAS; - break; - } - } } if (!found) { @@ -7565,7 +7424,7 @@ mark_common_columns(THD *thd, TABLE_LIST *table_ref_1, TABLE_LIST *table_ref_2, set_new_item_local_context(thd, item_ident_2, nj_col_2->table_ref)) goto err; - if (!(eq_cond= new Item_func_eq(item_ident_1, item_ident_2))) + if (!(eq_cond= new (thd->mem_root) Item_func_eq(thd, item_ident_1, item_ident_2))) goto err; /* Out of memory. */ if (field_1 && field_1->vcol_info) @@ -7578,8 +7437,8 @@ mark_common_columns(THD *thd, TABLE_LIST *table_ref_1, TABLE_LIST *table_ref_2, fix_fields() is applied to all ON conditions in setup_conds() so we don't do it here. */ - add_join_on((table_ref_1->outer_join & JOIN_TYPE_RIGHT ? - table_ref_1 : table_ref_2), + add_join_on(thd, (table_ref_1->outer_join & JOIN_TYPE_RIGHT ? + table_ref_1 : table_ref_2), eq_cond); nj_col_1->is_common= nj_col_2->is_common= TRUE; @@ -7624,14 +7483,6 @@ mark_common_columns(THD *thd, TABLE_LIST *table_ref_1, TABLE_LIST *table_ref_2, */ result= FALSE; - /* - Save the lists made during natural join matching (because - the matching done only once but we need the list in case - of prepared statements). - */ - table_ref_1->persistent_used_items= table_ref_1->used_items; - table_ref_2->persistent_used_items= table_ref_2->used_items; - err: if (arena) thd->restore_active_arena(arena, &backup); @@ -7704,12 +7555,12 @@ store_natural_using_join_columns(THD *thd, TABLE_LIST *natural_using_join, nj_col_1= it_1.get_natural_column_ref(); if (nj_col_1->is_common) { - join_columns->push_back(nj_col_1); + join_columns->push_back(nj_col_1, thd->mem_root); /* Reset the common columns for the next call to mark_common_columns. */ nj_col_1->is_common= FALSE; } else - non_join_columns->push_back(nj_col_1); + non_join_columns->push_back(nj_col_1, thd->mem_root); } /* @@ -7749,7 +7600,7 @@ store_natural_using_join_columns(THD *thd, TABLE_LIST *natural_using_join, { nj_col_2= it_2.get_natural_column_ref(); if (!nj_col_2->is_common) - non_join_columns->push_back(nj_col_2); + non_join_columns->push_back(nj_col_2, thd->mem_root); else { /* Reset the common columns for the next call to mark_common_columns. */ @@ -7758,7 +7609,7 @@ store_natural_using_join_columns(THD *thd, TABLE_LIST *natural_using_join, } if (non_join_columns->elements > 0) - join_columns->concat(non_join_columns); + join_columns->append(non_join_columns); natural_using_join->join_columns= join_columns; natural_using_join->is_join_columns_complete= TRUE; @@ -7926,7 +7777,7 @@ store_top_level_join_columns(THD *thd, TABLE_LIST *table_ref, /* Add a TRUE condition to outer joins that have no common columns. */ if (table_ref_2->outer_join && !table_ref_1->on_expr && !table_ref_2->on_expr) - table_ref_2->on_expr= new Item_int((longlong) 1,1); /* Always true. */ + table_ref_2->on_expr= new (thd->mem_root) Item_int(thd, (longlong) 1, 1); // Always true. /* Change this table reference to become a leaf for name resolution. */ if (left_neighbor) @@ -8091,7 +7942,7 @@ int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields, Item_int do not need fix_fields() because it is basic constant. */ - it.replace(new Item_int("Not_used", (longlong) 1, + it.replace(new (thd->mem_root) Item_int(thd, "Not_used", (longlong) 1, MY_INT64_NUM_DECIMAL_DIGITS)); } else if (insert_fields(thd, ((Item_field*) item)->context, @@ -8216,7 +8067,8 @@ bool setup_fields(THD *thd, Item **ref_pointer_array, *(ref++)= item; if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM && sum_func_list) - item->split_sum_func(thd, ref_pointer_array, *sum_func_list); + item->split_sum_func(thd, ref_pointer_array, *sum_func_list, + SPLIT_SUM_SELECT); thd->lex->used_tables|= item->used_tables(); thd->lex->current_select->cur_pos_in_select_list++; } @@ -8226,7 +8078,7 @@ bool setup_fields(THD *thd, Item **ref_pointer_array, thd->lex->allow_sum_func= save_allow_sum_func; thd->mark_used_columns= save_mark_used_columns; DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns)); - DBUG_RETURN(test(thd->is_error())); + DBUG_RETURN(MY_TEST(thd->is_error())); } @@ -8243,7 +8095,7 @@ bool setup_fields(THD *thd, Item **ref_pointer_array, RETURN pointer on pointer to next_leaf of last element */ -void make_leaves_list(List<TABLE_LIST> &list, TABLE_LIST *tables, +void make_leaves_list(THD *thd, List<TABLE_LIST> &list, TABLE_LIST *tables, bool full_table_list, TABLE_LIST *boundary) { @@ -8259,12 +8111,12 @@ void make_leaves_list(List<TABLE_LIST> &list, TABLE_LIST *tables, tables/views were already prepared and has their leaf_tables set properly. */ - make_leaves_list(list, select_lex->get_table_list(), + make_leaves_list(thd, list, select_lex->get_table_list(), full_table_list, boundary); } else { - list.push_back(table); + list.push_back(table, thd->mem_root); } } } @@ -8279,7 +8131,7 @@ void make_leaves_list(List<TABLE_LIST> &list, TABLE_LIST *tables, from_clause Top-level list of table references in the FROM clause tables Table list (select_lex->table_list) leaves List of join table leaves list (select_lex->leaf_tables) - refresh It is onle refresh for subquery + refresh It is only refresh for subquery select_insert It is SELECT ... INSERT command full_table_list a parameter to pass to the make_leaves_list function @@ -8325,7 +8177,7 @@ bool setup_tables(THD *thd, Name_resolution_context *context, leaves.empty(); if (select_lex->prep_leaf_list_state != SELECT_LEX::SAVED) { - make_leaves_list(leaves, tables, full_table_list, first_select_table); + make_leaves_list(thd, leaves, tables, full_table_list, first_select_table); select_lex->prep_leaf_list_state= SELECT_LEX::READY; select_lex->leaf_tables_exec.empty(); } @@ -8333,7 +8185,7 @@ bool setup_tables(THD *thd, Name_resolution_context *context, { List_iterator_fast <TABLE_LIST> ti(select_lex->leaf_tables_prep); while ((table_list= ti++)) - leaves.push_back(table_list); + leaves.push_back(table_list, thd->mem_root); } while ((table_list= ti++)) @@ -8463,7 +8315,6 @@ bool setup_tables_and_check_access(THD *thd, ulong want_access, bool full_table_list) { - bool first_table= true; DBUG_ENTER("setup_tables_and_check_access"); if (setup_tables(thd, context, from_clause, tables, @@ -8472,16 +8323,16 @@ bool setup_tables_and_check_access(THD *thd, List_iterator<TABLE_LIST> ti(leaves); TABLE_LIST *table_list; - while((table_list= ti++)) + ulong access= want_access_first; + while ((table_list= ti++)) { if (table_list->belong_to_view && !table_list->view && - check_single_table_access(thd, first_table ? want_access_first : - want_access, table_list, FALSE)) + check_single_table_access(thd, access, table_list, FALSE)) { tables->hide_view_error(thd); DBUG_RETURN(TRUE); } - first_table= 0; + access= want_access; } DBUG_RETURN(FALSE); } @@ -8697,11 +8548,6 @@ insert_fields(THD *thd, Name_resolution_context *context, const char *db_name, } } #endif - /* - field_iterator.create_item() builds used_items which we - have to save because changes made once and they are persistent - */ - tables->persistent_used_items= tables->used_items; if ((field= field_iterator.field())) { @@ -8761,9 +8607,16 @@ insert_fields(THD *thd, Name_resolution_context *context, const char *db_name, meaningful message than ER_BAD_TABLE_ERROR. */ if (!table_name) - my_message(ER_NO_TABLES_USED, ER(ER_NO_TABLES_USED), MYF(0)); + my_error(ER_NO_TABLES_USED, MYF(0)); + else if (!db_name && !thd->db) + my_error(ER_NO_DB_ERROR, MYF(0)); else - my_error(ER_BAD_TABLE_ERROR, MYF(0), table_name); + { + char name[FN_REFLEN]; + my_snprintf(name, sizeof(name), "%s.%s", + db_name ? db_name : thd->db, table_name); + my_error(ER_BAD_TABLE_ERROR, MYF(0), name); + } DBUG_RETURN(TRUE); } @@ -8782,7 +8635,7 @@ void wrap_ident(THD *thd, Item **conds) DBUG_ASSERT((*conds)->type() == Item::FIELD_ITEM || (*conds)->type() == Item::REF_ITEM); Query_arena *arena, backup; arena= thd->activate_stmt_arena_if_needed(&backup); - if ((wrapper= new Item_direct_ref_to_ident((Item_ident *)(*conds)))) + if ((wrapper= new (thd->mem_root) Item_direct_ref_to_ident(thd, (Item_ident *) (*conds)))) (*conds)= (Item*) wrapper; if (arena) thd->restore_active_arena(arena, &backup); @@ -8871,7 +8724,7 @@ bool setup_on_expr(THD *thd, TABLE_LIST *table, bool is_update) TODO RETURN - TRUE if some error occured (e.g. out of memory) + TRUE if some error occurred (e.g. out of memory) FALSE if all is OK */ @@ -8952,7 +8805,7 @@ int setup_conds(THD *thd, TABLE_LIST *tables, List<TABLE_LIST> &leaves, select_lex->where= *conds; } thd->lex->current_select->is_item_list_lookup= save_is_item_list_lookup; - DBUG_RETURN(test(thd->is_error())); + DBUG_RETURN(MY_TEST(thd->is_error())); err_no_arena: select_lex->is_item_list_lookup= save_is_item_list_lookup; @@ -8966,34 +8819,33 @@ err_no_arena: ******************************************************************************/ -/* - Fill fields with given items. +/** + Fill the fields of a table with the values of an Item list - SYNOPSIS - fill_record() - thd thread handler - fields Item_fields list to be filled - values values to fill with - ignore_errors TRUE if we should ignore errors + @param thd thread handler + @param table_arg the table that is being modified + @param fields Item_fields list to be filled + @param values values to fill with + @param ignore_errors TRUE if we should ignore errors - NOTE + @details fill_record() may set table->auto_increment_field_not_null and a caller should make sure that it is reset after their last call to this function. - RETURN - FALSE OK - TRUE error occured + @return Status + @retval true An error occurred. + @retval false OK. */ -static bool -fill_record(THD * thd, List<Item> &fields, List<Item> &values, +bool +fill_record(THD *thd, TABLE *table_arg, List<Item> &fields, List<Item> &values, bool ignore_errors) { List_iterator_fast<Item> f(fields),v(values); Item *value, *fld; Item_field *field; - TABLE *table= 0, *vcol_table= 0; + TABLE *vcol_table= 0; bool save_abort_on_warning= thd->abort_on_warning; bool save_no_errors= thd->no_errors; DBUG_ENTER("fill_record"); @@ -9010,45 +8862,48 @@ fill_record(THD * thd, List<Item> &fields, List<Item> &values, thus we safely can take table from the first field. */ fld= (Item_field*)f++; - if (!(field= fld->filed_for_view_update())) + if (!(field= fld->field_for_view_update())) { my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name); goto err; } - table= field->field->table; - table->auto_increment_field_not_null= FALSE; + DBUG_ASSERT(field->field->table == table_arg); + table_arg->auto_increment_field_not_null= FALSE; f.rewind(); } else if (thd->lex->unit.insert_table_with_stored_vcol) vcol_table= thd->lex->unit.insert_table_with_stored_vcol; + while ((fld= f++)) { - if (!(field= fld->filed_for_view_update())) + if (!(field= fld->field_for_view_update())) { my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name); goto err; } value=v++; Field *rfield= field->field; - table= rfield->table; - if (rfield == table->next_number_field) + TABLE* table= rfield->table; + if (table->next_number_field && + rfield->field_index == table->next_number_field->field_index) table->auto_increment_field_not_null= TRUE; if (rfield->vcol_info && value->type() != Item::DEFAULT_VALUE_ITEM && value->type() != Item::NULL_ITEM && table->s->table_category != TABLE_CATEGORY_TEMPORARY) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN, - ER(ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN), + ER_THD(thd, ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN), rfield->field_name, table->s->table_name.str); } if ((!rfield->vcol_info || rfield->stored_in_db) && (value->save_in_field(rfield, 0)) < 0 && !ignore_errors) { - my_message(ER_UNKNOWN_ERROR, ER(ER_UNKNOWN_ERROR), MYF(0)); + my_message(ER_UNKNOWN_ERROR, ER_THD(thd, ER_UNKNOWN_ERROR), MYF(0)); goto err; } + rfield->set_explicit_default(value); DBUG_ASSERT(vcol_table == 0 || vcol_table == table); vcol_table= table; } @@ -9063,100 +8918,157 @@ fill_record(THD * thd, List<Item> &fields, List<Item> &values, err: thd->abort_on_warning= save_abort_on_warning; thd->no_errors= save_no_errors; - if (table) - table->auto_increment_field_not_null= FALSE; + if (fields.elements) + table_arg->auto_increment_field_not_null= FALSE; DBUG_RETURN(TRUE); } -/* +/** + Prepare Item_field's for fill_record_n_invoke_before_triggers() + + This means redirecting from table->field to + table->field_to_fill(), if needed. +*/ +void switch_to_nullable_trigger_fields(List<Item> &items, TABLE *table) +{ + Field** field= table->field_to_fill(); + + if (field != table->field) + { + List_iterator_fast<Item> it(items); + Item *item; + + while ((item= it++)) + item->walk(&Item::switch_to_nullable_fields_processor, 1, (uchar*)field); + table->triggers->reset_extra_null_bitmap(); + } +} + + +/** + Test NOT NULL constraint after BEFORE triggers +*/ +static bool not_null_fields_have_null_values(TABLE *table) +{ + Field **orig_field= table->field; + Field **filled_field= table->field_to_fill(); + + if (filled_field != orig_field) + { + THD *thd=table->in_use; + for (uint i=0; i < table->s->fields; i++) + { + Field *of= orig_field[i]; + Field *ff= filled_field[i]; + if (ff != of) + { + // copy after-update flags to of, copy before-update flags to ff + swap_variables(uint32, of->flags, ff->flags); + if (ff->is_real_null()) + { + ff->set_notnull(); // for next row WHERE condition in UPDATE + if (convert_null_to_field_value_or_error(of) || thd->is_error()) + return true; + } + } + } + } + + return false; +} + +/** Fill fields in list with values from the list of items and invoke before triggers. - SYNOPSIS - fill_record_n_invoke_before_triggers() - thd thread context - fields Item_fields list to be filled - values values to fill with - ignore_errors TRUE if we should ignore errors - triggers object holding list of triggers to be invoked - event event type for triggers to be invoked + @param thd thread context + @param table the table that is being modified + @param fields Item_fields list to be filled + @param values values to fill with + @param ignore_errors TRUE if we should ignore errors + @param event event type for triggers to be invoked - NOTE + @detail This function assumes that fields which values will be set and triggers to be invoked belong to the same table, and that TABLE::record[0] and record[1] buffers correspond to new and old versions of row respectively. - RETURN - FALSE OK - TRUE error occured + @return Status + @retval true An error occurred. + @retval false OK. */ bool -fill_record_n_invoke_before_triggers(THD *thd, List<Item> &fields, +fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, List<Item> &fields, List<Item> &values, bool ignore_errors, - Table_triggers_list *triggers, enum trg_event_type event) { bool result; - result= (fill_record(thd, fields, values, ignore_errors) || - (triggers && triggers->process_triggers(thd, event, - TRG_ACTION_BEFORE, TRUE))); + Table_triggers_list *triggers= table->triggers; + + result= fill_record(thd, table, fields, values, ignore_errors); + + if (!result && triggers) + result= triggers->process_triggers(thd, event, TRG_ACTION_BEFORE, TRUE) || + not_null_fields_have_null_values(table); + /* Re-calculate virtual fields to cater for cases when base columns are updated by the triggers. */ if (!result && triggers) { - TABLE *table= 0; List_iterator_fast<Item> f(fields); Item *fld; Item_field *item_field; if (fields.elements) { fld= (Item_field*)f++; - item_field= fld->filed_for_view_update(); - if (item_field && item_field->field && - (table= item_field->field->table) && - table->vfield) + item_field= fld->field_for_view_update(); + if (item_field && item_field->field && table && table->vfield) + { + DBUG_ASSERT(table == item_field->field->table); result= update_virtual_fields(thd, table, VCOL_UPDATE_FOR_WRITE); + } } } return result; } -/* - Fill field buffer with values from Field list +/** + Fill the field buffer of a table with the values of an Item list - SYNOPSIS - fill_record() - thd thread handler - ptr pointer on pointer to record - values list of fields - ignore_errors TRUE if we should ignore errors - use_value forces usage of value of the items instead of result + @param thd thread handler + @param table_arg the table that is being modified + @param ptr pointer on pointer to record of fields + @param values values to fill with + @param ignore_errors TRUE if we should ignore errors + @param use_value forces usage of value of the items instead of result - NOTE + @details fill_record() may set table->auto_increment_field_not_null and a caller should make sure that it is reset after their last call to this function. - RETURN - FALSE OK - TRUE error occured + @return Status + @retval true An error occurred. + @retval false OK. */ bool -fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors, - bool use_value) +fill_record(THD *thd, TABLE *table, Field **ptr, List<Item> &values, + bool ignore_errors, bool use_value) { List_iterator_fast<Item> v(values); List<TABLE> tbl_list; Item *value; - TABLE *table= 0; Field *field; bool abort_on_warning_saved= thd->abort_on_warning; + uint autoinc_index= table->next_number_field + ? table->next_number_field->field_index + : ~0U; DBUG_ENTER("fill_record"); if (!*ptr) @@ -9169,7 +9081,7 @@ fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors, On INSERT or UPDATE fields are checked to be from the same table, thus we safely can take table from the first field. */ - table= (*ptr)->table; + DBUG_ASSERT((*ptr)->table == table); /* Reset the table->auto_increment_field_not_null as it is valid for @@ -9182,16 +9094,16 @@ fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors, DBUG_ASSERT(field->table == table); value=v++; - if (field == table->next_number_field) + if (field->field_index == autoinc_index) table->auto_increment_field_not_null= TRUE; if (field->vcol_info && value->type() != Item::DEFAULT_VALUE_ITEM && value->type() != Item::NULL_ITEM && table->s->table_category != TABLE_CATEGORY_TEMPORARY) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN, - ER(ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN), + ER_THD(thd, ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN), field->field_name, table->s->table_name.str); } @@ -9200,6 +9112,7 @@ fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors, else if (value->save_in_field(field, 0) < 0) goto err; + field->set_explicit_default(value); } /* Update virtual fields*/ thd->abort_on_warning= FALSE; @@ -9217,45 +9130,46 @@ err: /* - Fill fields in array with values from the list of items and invoke + Fill fields in an array with values from the list of items and invoke before triggers. - SYNOPSIS - fill_record_n_invoke_before_triggers() - thd thread context - ptr NULL-ended array of fields to be filled - values values to fill with - ignore_errors TRUE if we should ignore errors - triggers object holding list of triggers to be invoked - event event type for triggers to be invoked + @param thd thread context + @param table the table that is being modified + @param ptr the fields to be filled + @param values values to fill with + @param ignore_errors TRUE if we should ignore errors + @param event event type for triggers to be invoked - NOTE + @detail This function assumes that fields which values will be set and triggers to be invoked belong to the same table, and that TABLE::record[0] and record[1] buffers correspond to new and old versions of row respectively. - RETURN - FALSE OK - TRUE error occured + @return Status + @retval true An error occurred. + @retval false OK. */ bool -fill_record_n_invoke_before_triggers(THD *thd, Field **ptr, +fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Field **ptr, List<Item> &values, bool ignore_errors, - Table_triggers_list *triggers, enum trg_event_type event) { bool result; - result= (fill_record(thd, ptr, values, ignore_errors, FALSE) || - (triggers && triggers->process_triggers(thd, event, - TRG_ACTION_BEFORE, TRUE))); + Table_triggers_list *triggers= table->triggers; + + result= fill_record(thd, table, ptr, values, ignore_errors, FALSE); + + if (!result && triggers && *ptr) + result= triggers->process_triggers(thd, event, TRG_ACTION_BEFORE, TRUE) || + not_null_fields_have_null_values(table); /* Re-calculate virtual fields to cater for cases when base columns are updated by the triggers. */ if (!result && triggers && *ptr) { - TABLE *table= (*ptr)->table; + DBUG_ASSERT(table == (*ptr)->table); if (table->vfield) result= update_virtual_fields(thd, table, VCOL_UPDATE_FOR_WRITE); } @@ -9288,15 +9202,10 @@ my_bool mysql_rm_tmp_tables(void) /* Remove all SQLxxx tables from directory */ - for (idx=0 ; idx < (uint) dirp->number_off_files ; idx++) + for (idx=0 ; idx < (uint) dirp->number_of_files ; idx++) { file=dirp->dir_entry+idx; - /* skiping . and .. */ - if (file->name[0] == '.' && (!file->name[1] || - (file->name[1] == '.' && !file->name[2]))) - continue; - if (!memcmp(file->name, tmp_file_prefix, tmp_file_prefix_length)) { @@ -9312,7 +9221,7 @@ my_bool mysql_rm_tmp_tables(void) memcpy(filePathCopy, filePath, filePath_len - ext_len); filePathCopy[filePath_len - ext_len]= 0; init_tmp_table_share(thd, &share, "", 0, "", filePathCopy); - if (!open_table_def(thd, &share, 0) && + if (!open_table_def(thd, &share) && ((handler_file= get_new_handler(&share, thd->mem_root, share.db_type())))) { @@ -9332,7 +9241,6 @@ my_bool mysql_rm_tmp_tables(void) my_dirend(dirp); } delete thd; - my_pthread_setspecific_ptr(THR_THD, 0); DBUG_RETURN(0); } @@ -9341,201 +9249,6 @@ my_bool mysql_rm_tmp_tables(void) unireg support functions *****************************************************************************/ -/* - free all unused tables - - NOTE - This is called by 'handle_manager' when one wants to periodicly flush - all not used tables. -*/ - -void tdc_flush_unused_tables() -{ - mysql_mutex_lock(&LOCK_open); - while (unused_tables) - free_cache_entry(unused_tables); - mysql_mutex_unlock(&LOCK_open); -} - - -/** - A callback to the server internals that is used to address - special cases of the locking protocol. - Invoked when acquiring an exclusive lock, for each thread that - has a conflicting shared metadata lock. - - This function: - - aborts waiting of the thread on a data lock, to make it notice - the pending exclusive lock and back off. - - if the thread is an INSERT DELAYED thread, sends it a KILL - signal to terminate it. - - @note This function does not wait for the thread to give away its - locks. Waiting is done outside for all threads at once. - - @param thd Current thread context - @param in_use The thread to wake up - @param needs_thr_lock_abort Indicates that to wake up thread - this call needs to abort its waiting - on table-level lock. - - @retval TRUE if the thread was woken up - @retval FALSE otherwise. - - @note It is one of two places where border between MDL and the - rest of the server is broken. -*/ - -bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, - bool needs_thr_lock_abort) -{ - bool signalled= FALSE; - if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && - !in_use->killed) - { - in_use->killed= KILL_SYSTEM_THREAD; - mysql_mutex_lock(&in_use->mysys_var->mutex); - if (in_use->mysys_var->current_cond) - { - mysql_mutex_lock(in_use->mysys_var->current_mutex); - mysql_cond_broadcast(in_use->mysys_var->current_cond); - mysql_mutex_unlock(in_use->mysys_var->current_mutex); - } - mysql_mutex_unlock(&in_use->mysys_var->mutex); - signalled= TRUE; - } - - if (needs_thr_lock_abort) - { - mysql_mutex_lock(&in_use->LOCK_thd_data); - for (TABLE *thd_table= in_use->open_tables; - thd_table ; - thd_table= thd_table->next) - { - /* - Check for TABLE::needs_reopen() is needed since in some places we call - handler::close() for table instance (and set TABLE::db_stat to 0) - and do not remove such instances from the THD::open_tables - for some time, during which other thread can see those instances - (e.g. see partitioning code). - */ - if (!thd_table->needs_reopen()) - signalled|= mysql_lock_abort_for_thread(thd, thd_table); - } - mysql_mutex_unlock(&in_use->LOCK_thd_data); - } - return signalled; -} - - -/** - Remove all or some (depending on parameter) instances of TABLE and - TABLE_SHARE from the table definition cache. - - @param thd Thread context - @param remove_type Type of removal: - TDC_RT_REMOVE_ALL - remove all TABLE instances and - TABLE_SHARE instance. There - should be no used TABLE objects - and caller should have exclusive - metadata lock on the table. - TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances - except those that belong to - this thread. There should be - no TABLE objects used by other - threads and caller should have - exclusive metadata lock on the - table. - TDC_RT_REMOVE_UNUSED - remove all unused TABLE - instances (if there are no - used instances will also - remove TABLE_SHARE). - @param db Name of database - @param table_name Name of table - @param has_lock If TRUE, LOCK_open is already acquired - - @note It assumes that table instances are already not used by any - (other) thread (this should be achieved by using meta-data locks). -*/ - -void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, - const char *db, const char *table_name, - bool has_lock) -{ - char key[MAX_DBKEY_LENGTH]; - uint key_length; - TABLE *table; - TABLE_SHARE *share; - DBUG_ENTER("tdc_remove_table"); - DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type)); - - if (! has_lock) - mysql_mutex_lock(&LOCK_open); - else - { - mysql_mutex_assert_owner(&LOCK_open); - } - - DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || - thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, - MDL_EXCLUSIVE)); - - key_length= create_table_def_key(key, db, table_name); - - if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, - key_length))) - { - if (share->ref_count) - { - I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables); -#ifndef DBUG_OFF - if (remove_type == TDC_RT_REMOVE_ALL) - { - DBUG_ASSERT(share->used_tables.is_empty()); - } - else if (remove_type == TDC_RT_REMOVE_NOT_OWN || - remove_type == TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE) - { - I_P_List_iterator<TABLE, TABLE_share> it2(share->used_tables); - while ((table= it2++)) - if (table->in_use != thd) - { - DBUG_ASSERT(0); - } - } -#endif - /* - Mark share to ensure that it gets automatically deleted once - it is no longer referenced. - - Note that code in TABLE_SHARE::wait_for_old_version() assumes - that marking share as old and removal of its unused tables - and of the share itself from TDC happens atomically under - protection of LOCK_open, or, putting it another way, that - TDC does not contain old shares which don't have any tables - used. - */ - if (remove_type == TDC_RT_REMOVE_NOT_OWN) - share->remove_from_cache_at_close(); - else - { - /* Ensure that no can open the table while it's used */ - share->protect_against_usage(); - } - - while ((table= it++)) - free_cache_entry(table); - } - else - (void) my_hash_delete(&table_def_cache, (uchar*) share); - } - - if (! has_lock) - mysql_mutex_unlock(&LOCK_open); - DBUG_VOID_RETURN; -} - - int setup_ftfuncs(SELECT_LEX *select_lex) { List_iterator<Item_func_match> li(*(select_lex->ftfunc_list)), @@ -9576,79 +9289,12 @@ int init_ftfuncs(THD *thd, SELECT_LEX *select_lex, bool no_order) li.remove(); else #endif - ifm->init_search(no_order); - + ifm->init_search(thd, no_order); } return 0; } -/* - open new .frm format table - - SYNOPSIS - open_new_frm() - THD thread handler - path path to .frm file (without extension) - alias alias for table - db database - table_name name of table - db_stat open flags (for example ->OPEN_KEYFILE|HA_OPEN_RNDFILE..) - can be 0 (example in ha_example_table) - prgflag READ_ALL etc.. - ha_open_flags HA_OPEN_ABORT_IF_LOCKED etc.. - outparam result table - table_desc TABLE_LIST descriptor - mem_root temporary MEM_ROOT for parsing -*/ - -bool -open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, - uint db_stat, uint prgflag, - uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, - MEM_ROOT *mem_root) -{ - LEX_STRING pathstr; - File_parser *parser; - char path[FN_REFLEN+1]; - DBUG_ENTER("open_new_frm"); - - /* Create path with extension */ - pathstr.length= (uint) (strxnmov(path, sizeof(path) - 1, - share->normalized_path.str, - reg_ext, - NullS) - path); - pathstr.str= path; - - if ((parser= sql_parse_prepare(&pathstr, mem_root, 1))) - { - if (is_equal(&view_type, parser->type())) - { - if (table_desc == 0 || table_desc->required_type == FRMTYPE_TABLE) - { - my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, - "BASE TABLE"); - goto err; - } - if (mysql_make_view(thd, parser, table_desc, - (prgflag & OPEN_VIEW_NO_PARSE))) - goto err; - status_var_increment(thd->status_var.opened_views); - } - else - { - /* only VIEWs are supported now */ - my_error(ER_FRM_UNKNOWN_TYPE, MYF(0), share->path.str, parser->type()->str); - goto err; - } - DBUG_RETURN(0); - } - -err: - DBUG_RETURN(1); -} - - bool is_equal(const LEX_STRING *a, const LEX_STRING *b) { return a->length == b->length && !strncmp(a->str, b->str, a->length); @@ -9673,6 +9319,12 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b) must call close_system_tables() to close systems tables opened with this call. + NOTES + In some situations we use this function to open system tables for + writing. It happens, for examples, with statistical tables when + they are updated by an ANALYZE command. In these cases we should + guarantee that system tables will not be deadlocked. + RETURN FALSE Success TRUE Error @@ -9696,6 +9348,7 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, */ lex->reset_n_backup_query_tables_list(&query_tables_list_backup); thd->reset_n_backup_open_tables_state(backup); + thd->lex->sql_command= SQLCOM_SELECT; if (open_and_lock_tables(thd, table_list, FALSE, MYSQL_OPEN_IGNORE_FLUSH | @@ -9825,12 +9478,7 @@ open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_backup *backup) DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_LOG); /* Make sure all columns get assigned to a default value */ table->use_all_columns(); - table->no_replicate= 1; - /* - Don't set automatic timestamps as we may want to use time of logging, - not from query start - */ - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; + DBUG_ASSERT(table->no_replicate); } else thd->restore_backup_open_tables_state(backup); @@ -9886,6 +9534,7 @@ int dynamic_column_error_message(enum_dyncol_func_result rc) switch (rc) { case ER_DYNCOL_YES: case ER_DYNCOL_OK: + case ER_DYNCOL_TRUNCATED: break; // it is not an error case ER_DYNCOL_FORMAT: my_error(ER_DYN_COL_WRONG_FORMAT, MYF(0)); diff --git a/sql/sql_base.h b/sql/sql_base.h index 646e391a58b..94294e3aa43 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -18,10 +18,10 @@ #ifndef SQL_BASE_INCLUDED #define SQL_BASE_INCLUDED -#include "unireg.h" // REQUIRED: for other includes #include "sql_trigger.h" /* trg_event_type */ #include "sql_class.h" /* enum_mark_columns */ #include "mysqld.h" /* key_map */ +#include "table_cache.h" class Item_ident; struct Name_resolution_context; @@ -61,67 +61,20 @@ enum find_item_error_report_type {REPORT_ALL_ERRORS, REPORT_EXCEPT_NOT_FOUND, IGNORE_ERRORS, REPORT_EXCEPT_NON_UNIQUE, IGNORE_EXCEPT_NON_UNIQUE}; -enum enum_tdc_remove_table_type {TDC_RT_REMOVE_ALL, TDC_RT_REMOVE_NOT_OWN, - TDC_RT_REMOVE_UNUSED, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE}; - -/* bits for last argument to remove_table_from_cache() */ -#define RTFC_NO_FLAG 0x0000 -#define RTFC_OWNED_BY_THD_FLAG 0x0001 -#define RTFC_WAIT_OTHER_THREAD_FLAG 0x0002 -#define RTFC_CHECK_KILLED_FLAG 0x0004 - -bool check_dup(const char *db, const char *name, TABLE_LIST *tables); -extern mysql_mutex_t LOCK_open; -bool table_cache_init(void); -void table_cache_free(void); -bool table_def_init(void); -void table_def_free(void); -void table_def_start_shutdown(void); -void assign_new_table_id(TABLE_SHARE *share); -uint cached_open_tables(void); -uint cached_table_definitions(void); -uint create_table_def_key(THD *thd, char *key, - const TABLE_LIST *table_list, - bool tmp_table); - -/** - Create a table cache key for non-temporary table. - - @param key Buffer for key (must be at least NAME_LEN*2+2 bytes). - @param db Database name. - @param table_name Table name. - - @return Length of key. - - @sa create_table_def_key(thd, char *, table_list, bool) -*/ - -inline uint -create_table_def_key(char *key, const char *db, const char *table_name) -{ - /* - In theory caller should ensure that both db and table_name are - not longer than NAME_LEN bytes. In practice we play safe to avoid - buffer overruns. - */ - return (uint)(strmake(strmake(key, db, NAME_LEN) + 1, table_name, - NAME_LEN) - key + 1); -} - -TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, - uint key_length, uint db_flags, int *error, - my_hash_value_type hash_value); -void release_table_share(TABLE_SHARE *share); -TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name); +/* Flag bits for unique_table() */ +#define CHECK_DUP_ALLOW_DIFFERENT_ALIAS 1 +#define CHECK_DUP_FOR_CREATE 2 +uint create_tmp_table_def_key(THD *thd, char *key, const char *db, + const char *table_name); +uint get_table_def_key(const TABLE_LIST *table_list, const char **key); TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, uint lock_flags); /* mysql_lock_tables() and open_table() flags bits */ #define MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK 0x0001 #define MYSQL_OPEN_IGNORE_FLUSH 0x0002 -#define MYSQL_OPEN_TEMPORARY_ONLY 0x0004 +/* MYSQL_OPEN_TEMPORARY_ONLY (0x0004) is not used anymore. */ #define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 #define MYSQL_LOCK_LOG_TABLE 0x0010 /** @@ -134,8 +87,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, a new instance of the table. */ #define MYSQL_OPEN_GET_NEW_TABLE 0x0040 -/** Don't look up the table in the list of temporary tables. */ -#define MYSQL_OPEN_SKIP_TEMPORARY 0x0080 +/* 0x0080 used to be MYSQL_OPEN_SKIP_TEMPORARY */ /** Fail instead of waiting when conficting metadata lock is discovered. */ #define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0100 /** Open tables using MDL_SHARED lock instead of one specified in parser. */ @@ -156,7 +108,15 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, */ #define MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK 0x1000 #define MYSQL_LOCK_NOT_TEMPORARY 0x2000 -#define MYSQL_OPEN_FOR_REPAIR 0x4000 +/** + Only check THD::killed if waits happen (e.g. wait on MDL, wait on + table flush, wait on thr_lock.c locks) while opening and locking table. +*/ +#define MYSQL_OPEN_IGNORE_KILLED 0x8000 +/** + Don't try to auto-repair table +*/ +#define MYSQL_OPEN_IGNORE_REPAIR 0x10000 /** Please refer to the internals manual. */ #define MYSQL_OPEN_REOPEN (MYSQL_OPEN_IGNORE_FLUSH |\ @@ -164,55 +124,60 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\ MYSQL_LOCK_IGNORE_TIMEOUT |\ MYSQL_OPEN_GET_NEW_TABLE |\ - MYSQL_OPEN_SKIP_TEMPORARY |\ MYSQL_OPEN_HAS_MDL_LOCK) -bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, - Open_table_context *ot_ctx); -bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, - uint db_stat, uint prgflag, - uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, - MEM_ROOT *mem_root); +bool is_locked_view(THD *thd, TABLE_LIST *t); +bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx); bool get_key_map_from_key_list(key_map *map, TABLE *table, List<String> *index_list); -TABLE *open_table_uncached(THD *thd, const char *path, const char *db, - const char *table_name, - bool add_to_temporary_tables_list); +TABLE *open_table_uncached(THD *thd, handlerton *hton, + LEX_CUSTRING *frm, const char *path, + const char *db, const char *table_name, + bool add_to_temporary_tables_list, + bool open_in_engine); TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name); TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name); thr_lock_type read_lock_type_for_table(THD *thd, Query_tables_list *prelocking_ctx, - TABLE_LIST *table_list); + TABLE_LIST *table_list, + bool routine_modifies_data); my_bool mysql_rm_tmp_tables(void); bool rm_temporary_table(handlerton *base, const char *path); void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, const MDL_savepoint &start_of_statement_svp); +bool table_already_fk_prelocked(TABLE_LIST *tl, LEX_STRING *db, + LEX_STRING *table, thr_lock_type lock_type); TABLE_LIST *find_table_in_list(TABLE_LIST *table, TABLE_LIST *TABLE_LIST::*link, const char *db_name, const char *table_name); TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name); +bool find_and_use_temporary_table(THD *thd, const char *db, + const char *table_name, TABLE **out_table); TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl); +bool find_and_use_temporary_table(THD *thd, const TABLE_LIST *tl, + TABLE **out_table); TABLE *find_temporary_table(THD *thd, const char *table_key, uint table_key_length); void close_thread_tables(THD *thd); -bool fill_record_n_invoke_before_triggers(THD *thd, List<Item> &fields, +void switch_to_nullable_trigger_fields(List<Item> &items, TABLE *); +bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, + List<Item> &fields, List<Item> &values, bool ignore_errors, - Table_triggers_list *triggers, enum trg_event_type event); -bool fill_record_n_invoke_before_triggers(THD *thd, Field **field, +bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, + Field **field, List<Item> &values, bool ignore_errors, - Table_triggers_list *triggers, enum trg_event_type event); bool insert_fields(THD *thd, Name_resolution_context *context, const char *db_name, const char *table_name, List_iterator<Item> *it, bool any_privileges); -void make_leaves_list(List<TABLE_LIST> &list, TABLE_LIST *tables, +void make_leaves_list(THD *thd, List<TABLE_LIST> &list, TABLE_LIST *tables, bool full_table_list, TABLE_LIST *boundary); int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields, List<Item> *sum_func_list, uint wild_num); @@ -221,7 +186,9 @@ bool setup_fields(THD *thd, Item** ref_pointer_array, List<Item> *sum_func_list, List<Item> *pre_fix, bool allow_sum_func); void unfix_fields(List<Item> &items); -bool fill_record(THD *thd, Field **field, List<Item> &values, +bool fill_record(THD * thd, TABLE *table_arg, List<Item> &fields, + List<Item> &values, bool ignore_errors); +bool fill_record(THD *thd, TABLE *table, Field **field, List<Item> &values, bool ignore_errors, bool use_value); Field * @@ -259,9 +226,7 @@ bool setup_tables_and_check_access(THD *thd, ulong want_access, bool full_table_list); bool wait_while_table_is_used(THD *thd, TABLE *table, - enum ha_extra_function function, - enum_tdc_remove_table_type remove_type= - TDC_RT_REMOVE_NOT_OWN); + enum ha_extra_function function); void drop_open_table(THD *thd, TABLE *table, const char *db_name, const char *table_name); @@ -273,15 +238,41 @@ int setup_conds(THD *thd, TABLE_LIST *tables, List<TABLE_LIST> &leaves, void wrap_ident(THD *thd, Item **conds); int setup_ftfuncs(SELECT_LEX* select); int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order); -bool lock_table_names(THD *thd, TABLE_LIST *table_list, +bool lock_table_names(THD *thd, const DDL_options_st &options, + TABLE_LIST *table_list, TABLE_LIST *table_list_end, ulong lock_wait_timeout, uint flags); -bool open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags, +static inline bool +lock_table_names(THD *thd, TABLE_LIST *table_list, + TABLE_LIST *table_list_end, ulong lock_wait_timeout, + uint flags) +{ + return lock_table_names(thd, thd->lex->create_info, table_list, + table_list_end, lock_wait_timeout, flags); +} +bool open_tables(THD *thd, const DDL_options_st &options, + TABLE_LIST **tables, uint *counter, uint flags, Prelocking_strategy *prelocking_strategy); +static inline bool +open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags, + Prelocking_strategy *prelocking_strategy) +{ + return open_tables(thd, thd->lex->create_info, tables, counter, flags, + prelocking_strategy); +} /* open_and_lock_tables with optional derived handling */ -bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, +bool open_and_lock_tables(THD *thd, const DDL_options_st &options, + TABLE_LIST *tables, bool derived, uint flags, Prelocking_strategy *prelocking_strategy); +static inline bool +open_and_lock_tables(THD *thd, TABLE_LIST *tables, + bool derived, uint flags, + Prelocking_strategy *prelocking_strategy) +{ + return open_and_lock_tables(thd, thd->lex->create_info, + tables, derived, flags, prelocking_strategy); +} /* simple open_and_lock_tables without derived handling for single table */ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, thr_lock_type lock_type, uint flags, @@ -292,18 +283,22 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags); int decide_logging_format(THD *thd, TABLE_LIST *tables); void free_io_cache(TABLE *entry); void intern_close_table(TABLE *entry); -bool close_thread_table(THD *thd, TABLE **table_ptr); +void kill_delayed_threads_for_table(TDC_element *element); +void close_thread_table(THD *thd, TABLE **table_ptr); bool close_temporary_tables(THD *thd); TABLE_LIST *unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, - bool check_alias); -int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans); + uint check_flag); +int drop_temporary_table(THD *thd, TABLE *table, bool *is_trans); void close_temporary_table(THD *thd, TABLE *table, bool free_share, bool delete_table); void close_temporary(TABLE *table, bool free_share, bool delete_table); bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db, const char *table_name); +bool open_temporary_tables(THD *thd, TABLE_LIST *tl_list); +bool open_temporary_table(THD *thd, TABLE_LIST *tl); bool is_equal(const LEX_STRING *a, const LEX_STRING *b); +class Open_tables_backup; /* Functions to work with system tables. */ bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, Open_tables_backup *backup); @@ -321,30 +316,40 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool wait_for_refresh, ulong timeout); bool close_cached_connection_tables(THD *thd, LEX_STRING *connect_string); void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, - ha_extra_function extra); + ha_extra_function extra, + TABLE *skip_table); OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild); -void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, - const char *db, const char *table_name, - bool has_lock); bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, - char *cache_key, uint cache_key_length, - MEM_ROOT *mem_root, uint flags); -void tdc_flush_unused_tables(); + const char *cache_key, uint cache_key_length, uint flags); + +static inline bool tdc_open_view(THD *thd, TABLE_LIST *table_list, + const char *alias, uint flags) +{ + const char *key; + uint key_length= get_table_def_key(table_list, &key); + return tdc_open_view(thd, table_list, alias, key, key_length, flags); +} + TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db, const char *table_name, - bool no_error); + int *p_error); void mark_tmp_table_for_reuse(TABLE *table); -bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool fast_check, - bool *exists); + int update_virtual_fields(THD *thd, TABLE *table, enum enum_vcol_update_mode vcol_update_mode= VCOL_UPDATE_FOR_READ); int dynamic_column_error_message(enum_dyncol_func_result rc); -extern TABLE *unused_tables; +/* open_and_lock_tables with optional derived handling */ +int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived); + +extern "C" int simple_raw_key_cmp(void* arg, const void* key1, + const void* key2); +extern "C" int count_distinct_walk(void *elem, element_count count, void *arg); +int simple_str_key_cmp(void* arg, uchar* key1, uchar* key2); + extern Item **not_found_item; extern Field *not_found_field; extern Field *view_ref_found; -extern HASH table_def_cache; /** clean/setup table fields and map. @@ -480,11 +485,6 @@ class Lock_tables_prelocking_strategy : public DML_prelocking_strategy class Alter_table_prelocking_strategy : public Prelocking_strategy { public: - - Alter_table_prelocking_strategy(Alter_info *alter_info) - : m_alter_info(alter_info) - {} - virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking); @@ -492,20 +492,26 @@ public: TABLE_LIST *table_list, bool *need_prelocking); virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, TABLE_LIST *table_list, bool *need_prelocking); - -private: - Alter_info *m_alter_info; }; inline bool -open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags) +open_tables(THD *thd, const DDL_options_st &options, + TABLE_LIST **tables, uint *counter, uint flags) { DML_prelocking_strategy prelocking_strategy; - return open_tables(thd, tables, counter, flags, &prelocking_strategy); + return open_tables(thd, options, tables, counter, flags, + &prelocking_strategy); } +inline bool +open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags) +{ + DML_prelocking_strategy prelocking_strategy; + return open_tables(thd, thd->lex->create_info, tables, counter, flags, + &prelocking_strategy); +} inline TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, thr_lock_type lock_type, uint flags) @@ -518,15 +524,28 @@ inline TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, /* open_and_lock_tables with derived handling */ -inline bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, +inline bool open_and_lock_tables(THD *thd, + const DDL_options_st &options, + TABLE_LIST *tables, bool derived, uint flags) { DML_prelocking_strategy prelocking_strategy; - return open_and_lock_tables(thd, tables, derived, flags, + return open_and_lock_tables(thd, options, tables, derived, flags, &prelocking_strategy); } +inline bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, + bool derived, uint flags) +{ + DML_prelocking_strategy prelocking_strategy; + return open_and_lock_tables(thd, thd->lex->create_info, + tables, derived, flags, + &prelocking_strategy); +} + + +bool restart_trans_for_tables(THD *thd, TABLE_LIST *table); /** A context of open_tables() function, used to recover @@ -619,6 +638,30 @@ private: /** + Check if a TABLE_LIST instance represents a pre-opened temporary table. +*/ + +inline bool is_temporary_table(TABLE_LIST *tl) +{ + if (tl->view || tl->schema_table) + return FALSE; + + if (!tl->table) + return FALSE; + + /* + NOTE: 'table->s' might be NULL for specially constructed TABLE + instances. See SHOW TRIGGERS for example. + */ + + if (!tl->table->s) + return FALSE; + + return tl->table->s->tmp_table != NO_TMP_TABLE; +} + + +/** This internal handler is used to trap ER_NO_SUCH_TABLE. */ @@ -632,9 +675,9 @@ public: bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl); + Sql_condition ** cond_hdl); /** Returns TRUE if one or more ER_NO_SUCH_TABLE errors have been diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc index 420bd0eb2f0..2e861d00f10 100644 --- a/sql/sql_binlog.cc +++ b/sql/sql_binlog.cc @@ -14,12 +14,12 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "sql_binlog.h" #include "sql_parse.h" // check_global_access #include "sql_acl.h" // *_ACL #include "rpl_rli.h" -#include "base64.h" #include "slave.h" // apply_event_and_update_pos #include "log_event.h" // Format_description_log_event, // EVENT_LEN_OFFSET, @@ -28,6 +28,65 @@ // START_EVENT_V3, // Log_event_type, // Log_event + +/** + Copy fragments into the standard placeholder thd->lex->comment.str. + + Compute the size of the (still) encoded total, + allocate and then copy fragments one after another. + The size can exceed max(max_allowed_packet) which is not a + problem as no String instance is created off this char array. + + @param thd THD handle + @return + 0 at success, + -1 otherwise. +*/ +int binlog_defragment(THD *thd) +{ + user_var_entry *entry[2]; + LEX_STRING name[2]= { thd->lex->comment, thd->lex->ident }; + + /* compute the total size */ + thd->lex->comment.str= NULL; + thd->lex->comment.length= 0; + for (uint k= 0; k < 2; k++) + { + entry[k]= + (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name[k].str, + name[k].length); + if (!entry[k] || entry[k]->type != STRING_RESULT) + { + my_error(ER_WRONG_TYPE_FOR_VAR, MYF(0), name[k].str); + return -1; + } + thd->lex->comment.length += entry[k]->length; + } + + thd->lex->comment.str= // to be freed by the caller + (char *) my_malloc(thd->lex->comment.length, MYF(MY_WME)); + if (!thd->lex->comment.str) + { + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 1); + return -1; + } + + /* fragments are merged into allocated buf while the user var:s get reset */ + size_t gathered_length= 0; + for (uint k=0; k < 2; k++) + { + memcpy(thd->lex->comment.str + gathered_length, entry[k]->value, + entry[k]->length); + gathered_length += entry[k]->length; + update_hash(entry[k], true, NULL, 0, STRING_RESULT, &my_charset_bin, 0); + } + + DBUG_ASSERT(gathered_length == thd->lex->comment.length); + + return 0; +} + + /** Execute a BINLOG statement. @@ -53,14 +112,6 @@ void mysql_client_binlog_statement(THD* thd) if (check_global_access(thd, SUPER_ACL)) DBUG_VOID_RETURN; - size_t coded_len= thd->lex->comment.length; - if (!coded_len) - { - my_error(ER_SYNTAX_ERROR, MYF(0)); - DBUG_VOID_RETURN; - } - size_t decoded_len= base64_needed_decoded_length(coded_len); - /* option_bits will be changed when applying the event. But we don't expect it be changed permanently after BINLOG statement, so backup it first. @@ -80,6 +131,10 @@ void mysql_client_binlog_statement(THD* thd) my_bool have_fd_event= TRUE; int err; Relay_log_info *rli; + rpl_group_info *rgi; + char *buf= NULL; + size_t coded_len= 0, decoded_len= 0; + rli= thd->rli_fake; if (!rli) { @@ -95,30 +150,49 @@ void mysql_client_binlog_statement(THD* thd) new Format_description_log_event(4); have_fd_event= FALSE; } + if (!(rgi= thd->rgi_fake)) + rgi= thd->rgi_fake= new rpl_group_info(rli); + rgi->thd= thd; const char *error= 0; - char *buf= (char *) my_malloc(decoded_len, MYF(MY_WME)); Log_event *ev = 0; + my_bool is_fragmented= FALSE; /* Out of memory check */ - if (!(rli && - rli->relay_log.description_event_for_exec && - buf)) + if (!(rli && rli->relay_log.description_event_for_exec)) { my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 1); /* needed 1 bytes */ goto end; } - rli->sql_thd= thd; + rli->sql_driver_thd= thd; rli->no_storage= TRUE; + if (unlikely(is_fragmented= thd->lex->comment.str && thd->lex->ident.str)) + if (binlog_defragment(thd)) + goto end; + + if (!(coded_len= thd->lex->comment.length)) + { + my_error(ER_SYNTAX_ERROR, MYF(0)); + goto end; + } + + decoded_len= base64_needed_decoded_length(coded_len); + if (!(buf= (char *) my_malloc(decoded_len, MYF(MY_WME)))) + { + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 1); + goto end; + } + for (char const *strptr= thd->lex->comment.str ; strptr < thd->lex->comment.str + thd->lex->comment.length ; ) { char const *endptr= 0; - int bytes_decoded= base64_decode(strptr, coded_len, buf, &endptr); + int bytes_decoded= base64_decode(strptr, coded_len, buf, &endptr, + MY_BASE64_DECODE_ALLOW_MULTIPLE_CHUNKS); #ifndef HAVE_valgrind /* @@ -232,7 +306,7 @@ void mysql_client_binlog_statement(THD* thd) (ev->flags & LOG_EVENT_SKIP_REPLICATION_F ? OPTION_SKIP_REPLICATION : 0); - err= ev->apply_event(rli); + err= ev->apply_event(rgi); thd->variables.option_bits= (thd->variables.option_bits & ~OPTION_SKIP_REPLICATION) | @@ -266,8 +340,10 @@ void mysql_client_binlog_statement(THD* thd) my_ok(thd); end: + if (unlikely(is_fragmented)) + my_free(thd->lex->comment.str); thd->variables.option_bits= thd_options; - rli->slave_close_thread_tables(thd); + rgi->slave_close_thread_tables(thd); my_free(buf); DBUG_VOID_RETURN; } diff --git a/sql/sql_bitmap.h b/sql/sql_bitmap.h index d286d8e1ef0..5a2caf89fe2 100644 --- a/sql/sql_bitmap.h +++ b/sql/sql_bitmap.h @@ -1,4 +1,5 @@ -/* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2003, 2013, Oracle and/or its affiliates + Copyright (c) 2009, 2013, Monty Program Ab. 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 @@ -33,7 +34,7 @@ public: Bitmap() { init(); } Bitmap(const Bitmap& from) { *this=from; } explicit Bitmap(uint prefix_to_set) { init(prefix_to_set); } - void init() { bitmap_init(&map, buffer, default_width, 0); } + void init() { my_bitmap_init(&map, buffer, default_width, 0); } void init(uint prefix_to_set) { init(); set_prefix(prefix_to_set); } uint length() const { return default_width; } Bitmap& operator=(const Bitmap& map2) @@ -50,8 +51,27 @@ public: void intersect(Bitmap& map2) { bitmap_intersect(&map, &map2.map); } void intersect(ulonglong map2buff) { + // Use a spearate temporary buffer, as bitmap_init() clears all the bits. + ulonglong buf2; MY_BITMAP map2; - bitmap_init(&map2, (uint32 *)&map2buff, sizeof(ulonglong)*8, 0); + + my_bitmap_init(&map2, (uint32 *) &buf2, sizeof(ulonglong) * 8, 0); + + // Store the original bits. + if (sizeof(ulonglong) >= 8) + { + int8store(const_cast<uchar *>(static_cast<uchar *> + (static_cast<void *>(&buf2))), + map2buff); + } + else + { + DBUG_ASSERT(sizeof(buffer) >= 4); + int4store(const_cast<uchar *>(static_cast<uchar *> + (static_cast<void *>(&buf2))), + static_cast<uint32>(map2buff)); + } + bitmap_intersect(&map, &map2); } /* Use highest bit for all bits above sizeof(ulonglong)*8. */ @@ -60,7 +80,7 @@ public: intersect(map2buff); if (map.n_bits > sizeof(ulonglong) * 8) bitmap_set_above(&map, sizeof(ulonglong), - test(map2buff & (LL(1) << (sizeof(ulonglong) * 8 - 1)))); + MY_TEST(map2buff & (1LL << (sizeof(ulonglong) * 8 - 1)))); } void subtract(Bitmap& map2) { bitmap_subtract(&map, &map2.map); } void merge(Bitmap& map2) { bitmap_union(&map, &map2.map); } @@ -92,14 +112,33 @@ public: ulonglong to_ulonglong() const { if (sizeof(buffer) >= 8) - return uint8korr(buffer); + return uint8korr(static_cast<const uchar *> + (static_cast<const void *>(buffer))); DBUG_ASSERT(sizeof(buffer) >= 4); - return (ulonglong) uint4korr(buffer); + return (ulonglong) + uint4korr(static_cast<const uchar *> + (static_cast<const void *>(buffer))); } uint bits_set() { return bitmap_bits_set(&map); } + class Iterator + { + Bitmap ↦ + uint no; + public: + Iterator(Bitmap<default_width> &map2): map(map2), no(0) {} + int operator++(int) { + if (no == default_width) return BITMAP_END; + while (!map.is_set(no)) + { + if ((++no) == default_width) return BITMAP_END; + } + return no ++; + } + enum { BITMAP_END= default_width }; + }; }; /* An iterator to quickly walk over bits in unlonglong bitmap. */ @@ -155,7 +194,7 @@ public: void intersect_extended(ulonglong map2) { map&= map2; } void subtract(Bitmap<64>& map2) { map&= ~map2.map; } void merge(Bitmap<64>& map2) { map|= map2.map; } - bool is_set(uint n) const { return test(map & (((ulonglong)1) << n)); } + bool is_set(uint n) const { return MY_TEST(map & (((ulonglong) 1) << n)); } bool is_prefix(uint n) const { return map == (((ulonglong)1) << n)-1; } bool is_clear_all() const { return map == (ulonglong)0; } bool is_set_all() const { return map == ~(ulonglong)0; } @@ -167,7 +206,7 @@ public: class Iterator : public Table_map_iterator { public: - Iterator(Bitmap<64> &bmp) : Table_map_iterator(bmp.map) {} + Iterator(Bitmap<64> &map2) : Table_map_iterator(map2.map) {} }; uint bits_set() { diff --git a/sql/sql_bootstrap.cc b/sql/sql_bootstrap.cc new file mode 100644 index 00000000000..30d03029ce6 --- /dev/null +++ b/sql/sql_bootstrap.cc @@ -0,0 +1,119 @@ +/* Copyright (c) 2010, 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, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ + + +#include <my_global.h> +#include <ctype.h> +#include <string.h> +#include "sql_bootstrap.h" + +int read_bootstrap_query(char *query, int *query_length, + fgets_input_t input, fgets_fn_t fgets_fn, int *error) +{ + char line_buffer[MAX_BOOTSTRAP_LINE_SIZE]; + const char *line; + int len; + int query_len= 0; + int fgets_error= 0; + *error= 0; + + for ( ; ; ) + { + line= (*fgets_fn)(line_buffer, sizeof(line_buffer), input, &fgets_error); + + if (error) + *error= fgets_error; + + if (fgets_error != 0) + return READ_BOOTSTRAP_ERROR; + + if (line == NULL) + return (query_len == 0) ? READ_BOOTSTRAP_EOF : READ_BOOTSTRAP_ERROR; + + len= strlen(line); + + /* + Remove trailing whitespace characters. + This assumes: + - no multibyte encoded character can be found at the very end of a line, + - whitespace characters from the "C" locale only. + which is sufficient for the kind of queries found + in the bootstrap scripts. + */ + while (len && (isspace(line[len - 1]))) + len--; + /* + Cleanly end the string, so we don't have to test len > x + all the time before reading line[x], in the code below. + */ + line_buffer[len]= '\0'; + + /* Skip blank lines */ + if (len == 0) + continue; + + /* Skip # comments */ + if (line[0] == '#') + continue; + + /* Skip -- comments */ + if ((line[0] == '-') && (line[1] == '-')) + continue; + + /* Skip delimiter, ignored. */ + if (strncmp(line, "delimiter", 9) == 0) + continue; + + /* Append the current line to a multi line query. If the new line will make + the query too long, preserve the partial line to provide context for the + error message. + */ + if (query_len + len + 1 >= MAX_BOOTSTRAP_QUERY_SIZE) + { + int new_len= MAX_BOOTSTRAP_QUERY_SIZE - query_len - 1; + if ((new_len > 0) && (query_len < MAX_BOOTSTRAP_QUERY_SIZE)) + { + memcpy(query + query_len, line, new_len); + query_len+= new_len; + } + query[query_len]= '\0'; + *query_length= query_len; + return READ_BOOTSTRAP_QUERY_SIZE; + } + + if (query_len != 0) + { + /* + Append a \n to the current line, if any, + to preserve the intended presentation. + */ + query[query_len++]= '\n'; + } + memcpy(query + query_len, line, len); + query_len+= len; + + if (line[len - 1] == ';') + { + /* + The last line is terminated by ';'. + Return the query found. + */ + query[query_len]= '\0'; + *query_length= query_len; + return READ_BOOTSTRAP_SUCCESS; + } + } +} + diff --git a/sql/sql_bootstrap.h b/sql/sql_bootstrap.h new file mode 100644 index 00000000000..b8a302a8646 --- /dev/null +++ b/sql/sql_bootstrap.h @@ -0,0 +1,47 @@ +/* Copyright (c) 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, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ + + +#ifndef SQL_BOOTSTRAP_H +#define SQL_BOOTSTRAP_H + +/** + The maximum size of a bootstrap query. + Increase this size if parsing a longer query during bootstrap is necessary. + The longest query in use depends on the documentation content, + see the file fill_help_tables.sql +*/ +#define MAX_BOOTSTRAP_QUERY_SIZE 20000 +/** + The maximum size of a bootstrap query, expressed in a single line. + Do not increase this size, use the multiline syntax instead. +*/ +#define MAX_BOOTSTRAP_LINE_SIZE 20000 +#define MAX_BOOTSTRAP_ERROR_LEN 256 + +#define READ_BOOTSTRAP_SUCCESS 0 +#define READ_BOOTSTRAP_EOF 1 +#define READ_BOOTSTRAP_ERROR 2 +#define READ_BOOTSTRAP_QUERY_SIZE 3 + +typedef void *fgets_input_t; +typedef char * (*fgets_fn_t)(char *, size_t, fgets_input_t, int *error); + +int read_bootstrap_query(char *query, int *query_length, + fgets_input_t input, fgets_fn_t fgets_fn, int *error); + +#endif + + diff --git a/sql/sql_builtin.cc.in b/sql/sql_builtin.cc.in index 63850650ac9..a40a064dc4b 100644 --- a/sql/sql_builtin.cc.in +++ b/sql/sql_builtin.cc.in @@ -25,7 +25,11 @@ extern #endif builtin_maria_plugin @mysql_mandatory_plugins@ @mysql_optional_plugins@ - builtin_maria_binlog_plugin, builtin_maria_mysql_password_plugin; + builtin_maria_binlog_plugin, +#ifdef WITH_WSREP + builtin_maria_wsrep_plugin, +#endif /* WITH_WSREP */ + builtin_maria_mysql_password_plugin; struct st_maria_plugin *mysql_optional_plugins[]= { @@ -35,5 +39,8 @@ struct st_maria_plugin *mysql_optional_plugins[]= struct st_maria_plugin *mysql_mandatory_plugins[]= { builtin_maria_binlog_plugin, builtin_maria_mysql_password_plugin, +#ifdef WITH_WSREP + builtin_maria_wsrep_plugin, +#endif /* WITH_WSREP */ @mysql_mandatory_plugins@ 0 }; diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 6effc376a2f..df6c7c35e5a 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -11,8 +11,8 @@ 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 */ + along with this program; if not, write to the Free Software Foundation, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ /* Description of the query cache: @@ -328,7 +328,7 @@ TODO list: (This could be done with almost no speed penalty) */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "sql_cache.h" #include "sql_parse.h" // check_table_access @@ -336,6 +336,7 @@ TODO list: #include "sql_acl.h" // SELECT_ACL #include "sql_base.h" // TMP_TABLE_KEY_EXTRA #include "debug_sync.h" // DEBUG_SYNC +#include "sql_table.h" #ifdef HAVE_QUERY_CACHE #include <m_ctype.h> #include <my_dir.h> @@ -345,6 +346,7 @@ TODO list: #include "probes_mysql.h" #include "log_slow.h" #include "transaction.h" +#include "strfunc.h" const uchar *query_state_map; @@ -395,9 +397,9 @@ const uchar *query_state_map; #if defined(ENABLED_DEBUG_SYNC) #define QC_DEBUG_SYNC(name) \ do { \ - THD *thd= current_thd; \ - if (thd) \ - DEBUG_SYNC(thd, name); \ + THD *thd_tmp= current_thd; \ + if (thd_tmp) \ + DEBUG_SYNC(thd_tmp, name); \ } while (0) #else #define QC_DEBUG_SYNC(name) @@ -412,23 +414,28 @@ const uchar *query_state_map; struct Query_cache_wait_state { THD *m_thd; - const char *m_proc_info; + PSI_stage_info m_old_stage; + const char *m_func; + const char *m_file; + int m_line; Query_cache_wait_state(THD *thd, const char *func, const char *file, unsigned int line) : m_thd(thd), - m_proc_info(NULL) + m_old_stage(), + m_func(func), m_file(file), m_line(line) { if (m_thd) - m_proc_info= set_thd_proc_info(m_thd, - "Waiting for query cache lock", - func, file, line); + set_thd_stage_info(m_thd, + &stage_waiting_for_query_cache_lock, + &m_old_stage, + m_func, m_file, m_line); } ~Query_cache_wait_state() { if (m_thd) - set_thd_proc_info(m_thd, m_proc_info, NULL, NULL, 0); + set_thd_stage_info(m_thd, &m_old_stage, NULL, m_func, m_file, m_line); } }; @@ -954,7 +961,7 @@ inline void Query_cache_query::unlock_reading() void Query_cache_query::init_n_lock() { DBUG_ENTER("Query_cache_query::init_n_lock"); - res=0; wri = 0; len = 0; + res=0; wri = 0; len = 0; ready= 0; mysql_rwlock_init(key_rwlock_query_cache_query_lock, &lock); lock_writing(); DBUG_PRINT("qcache", ("inited & locked query for block 0x%lx", @@ -1030,22 +1037,22 @@ uchar *query_cache_query_get_key(const uchar *record, size_t *length, /** libmysql convenience wrapper to insert data into query cache. */ -void query_cache_insert(const char *packet, ulong length, +void query_cache_insert(void *thd_arg, const char *packet, ulong length, unsigned pkt_nr) { - THD *thd= current_thd; + THD *thd= (THD*) thd_arg; /* Current_thd can be NULL when a new connection is immediately ended due to "Too many connections". thd->store_globals() has not been - called at this time and hence my_pthread_setspecific_ptr(THR_THD, - this) has not been called for this thread. + called at this time and hence set_current_thd(this) has not been + called for this thread. */ if (!thd) return; - query_cache.insert(&thd->query_cache_tls, + query_cache.insert(thd, &thd->query_cache_tls, packet, length, pkt_nr); } @@ -1056,7 +1063,7 @@ void query_cache_insert(const char *packet, ulong length, */ void -Query_cache::insert(Query_cache_tls *query_cache_tls, +Query_cache::insert(THD *thd, Query_cache_tls *query_cache_tls, const char *packet, ulong length, unsigned pkt_nr) { @@ -1066,15 +1073,13 @@ Query_cache::insert(Query_cache_tls *query_cache_tls, if (is_disabled() || query_cache_tls->first_query_block == NULL) DBUG_VOID_RETURN; - DBUG_ASSERT(current_thd); - QC_DEBUG_SYNC("wait_in_query_cache_insert"); /* Lock the cache with try_lock(). try_lock() will fail if cache was disabled between the above test and lock. */ - if (try_lock(current_thd, Query_cache::WAIT)) + if (try_lock(thd, Query_cache::WAIT)) DBUG_VOID_RETURN; Query_cache_block *query_block = query_cache_tls->first_query_block; @@ -1123,16 +1128,15 @@ Query_cache::insert(Query_cache_tls *query_cache_tls, void -Query_cache::abort(Query_cache_tls *query_cache_tls) +Query_cache::abort(THD *thd, Query_cache_tls *query_cache_tls) { - THD *thd; DBUG_ENTER("query_cache_abort"); /* See the comment on double-check locking usage above. */ if (is_disabled() || query_cache_tls->first_query_block == NULL) DBUG_VOID_RETURN; - if (try_lock(current_thd, Query_cache::WAIT)) + if (try_lock(thd, Query_cache::WAIT)) DBUG_VOID_RETURN; /* @@ -1142,8 +1146,7 @@ Query_cache::abort(Query_cache_tls *query_cache_tls) Query_cache_block *query_block= query_cache_tls->first_query_block; if (query_block) { - thd= current_thd; - thd_proc_info(thd, "storing result in query cache"); + THD_STAGE_INFO(thd, stage_storing_result_in_query_cache); DUMP(this); BLOCK_LOCK_WR(query_block); // The following call will remove the lock on query_block @@ -1170,16 +1173,16 @@ void Query_cache::end_of_result(THD *thd) DBUG_VOID_RETURN; /* Ensure that only complete results are cached. */ - DBUG_ASSERT(thd->stmt_da->is_eof()); + DBUG_ASSERT(thd->get_stmt_da()->is_eof()); if (thd->killed) { - query_cache_abort(&thd->query_cache_tls); + query_cache_abort(thd, &thd->query_cache_tls); DBUG_VOID_RETURN; } #ifdef EMBEDDED_LIBRARY - insert(query_cache_tls, (char*)thd, + insert(thd, query_cache_tls, (char*)thd, emb_count_querycache_size(thd), 0); #endif @@ -1198,7 +1201,7 @@ void Query_cache::end_of_result(THD *thd) suitable size if needed and setting block type. Since this is the last block, the writer should be dropped. */ - thd_proc_info(thd, "storing result in query cache"); + THD_STAGE_INFO(thd, stage_storing_result_in_query_cache); DUMP(this); BLOCK_LOCK_WR(query_block); Query_cache_query *header= query_block->query(); @@ -1222,11 +1225,12 @@ void Query_cache::end_of_result(THD *thd) } last_result_block= header->result()->prev; allign_size= ALIGN_SIZE(last_result_block->used); - len= max(query_cache.min_allocation_unit, allign_size); + len= MY_MAX(query_cache.min_allocation_unit, allign_size); if (last_result_block->length >= query_cache.min_allocation_unit + len) query_cache.split_block(last_result_block,len); header->found_rows(limit_found_rows); + header->set_results_ready(); // signal for plugin header->result()->type= Query_cache_block::RESULT; /* Drop the writer. */ @@ -1296,14 +1300,6 @@ ulong Query_cache::resize(ulong query_cache_size_arg) query_cache_size_arg)); DBUG_ASSERT(initialized); - if (global_system_variables.query_cache_type == 0) - { - DBUG_ASSERT(query_cache_size_arg == 0); - if (query_cache_size_arg != 0) - my_error(ER_QUERY_CACHE_IS_DISABLED, MYF(0)); - DBUG_RETURN(0); - } - lock_and_suspend(); /* @@ -1341,7 +1337,7 @@ ulong Query_cache::resize(ulong query_cache_size_arg) m_cache_status is internal query cache switch so switching it on/off will not be reflected on global_system_variables.query_cache_type */ - if (new_query_cache_size) + if (new_query_cache_size && global_system_variables.query_cache_type != 0) { DBUG_EXECUTE("check_querycache",check_integrity(1);); m_cache_status= OK; // size > 0 => enable cache @@ -1402,9 +1398,9 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) Query_cache_query_flags flags; // fill all gaps between fields with 0 to get repeatable key bzero(&flags, QUERY_CACHE_FLAGS_SIZE); - flags.client_long_flag= test(thd->client_capabilities & CLIENT_LONG_FLAG); - flags.client_protocol_41= test(thd->client_capabilities & - CLIENT_PROTOCOL_41); + flags.client_long_flag= MY_TEST(thd->client_capabilities & CLIENT_LONG_FLAG); + flags.client_protocol_41= MY_TEST(thd->client_capabilities & + CLIENT_PROTOCOL_41); /* Protocol influences result format, so statement results in the binary protocol (COM_EXECUTE) cannot be served to statements asking for results @@ -1413,10 +1409,10 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) flags.protocol_type= (unsigned int) thd->protocol->type(); /* PROTOCOL_LOCAL results are not cached. */ DBUG_ASSERT(flags.protocol_type != (unsigned int) Protocol::PROTOCOL_LOCAL); - flags.more_results_exists= test(thd->server_status & - SERVER_MORE_RESULTS_EXISTS); + flags.more_results_exists= MY_TEST(thd->server_status & + SERVER_MORE_RESULTS_EXISTS); flags.in_trans= thd->in_active_multi_stmt_transaction(); - flags.autocommit= test(thd->server_status & SERVER_STATUS_AUTOCOMMIT); + flags.autocommit= MY_TEST(thd->server_status & SERVER_STATUS_AUTOCOMMIT); flags.pkt_nr= net->pkt_nr; flags.character_set_client_num= thd->variables.character_set_client->number; @@ -1640,6 +1636,41 @@ send_data_in_chunks(NET *net, const uchar *packet, ulong len) #endif +/** + Build a normalized table name suitable for query cache engine callback + + This consist of normalized directory '/' normalized_file_name + followed by suffix. + Suffix is needed for partitioned tables. +*/ + +size_t build_normalized_name(char *buff, size_t bufflen, + const char *db, size_t db_len, + const char *table_name, size_t table_len, + size_t suffix_len) +{ + uint errors; + size_t length; + char *pos= buff, *end= buff+bufflen; + DBUG_ENTER("build_normalized_name"); + + (*pos++)= FN_LIBCHAR; + length= strconvert(system_charset_info, db, db_len, + &my_charset_filename, pos, bufflen - 3, + &errors); + pos+= length; + (*pos++)= FN_LIBCHAR; + length= strconvert(system_charset_info, table_name, table_len, + &my_charset_filename, pos, (uint) (end - pos), + &errors); + pos+= length; + if (pos + suffix_len < end) + pos= strmake(pos, table_name + table_len, suffix_len); + + DBUG_RETURN((size_t) (pos - buff)); +} + + /* Check if the query is in the cache. If it was cached, send it to the user. @@ -1858,18 +1889,18 @@ Query_cache::send_result_to_client(THD *thd, char *org_sql, uint query_length) DBUG_PRINT("qcache", ("No active database")); } - thd_proc_info(thd, "checking query cache for query"); + THD_STAGE_INFO(thd, stage_checking_query_cache_for_query); // fill all gaps between fields with 0 to get repeatable key bzero(&flags, QUERY_CACHE_FLAGS_SIZE); - flags.client_long_flag= test(thd->client_capabilities & CLIENT_LONG_FLAG); - flags.client_protocol_41= test(thd->client_capabilities & - CLIENT_PROTOCOL_41); + flags.client_long_flag= MY_TEST(thd->client_capabilities & CLIENT_LONG_FLAG); + flags.client_protocol_41= MY_TEST(thd->client_capabilities & + CLIENT_PROTOCOL_41); flags.protocol_type= (unsigned int) thd->protocol->type(); - flags.more_results_exists= test(thd->server_status & - SERVER_MORE_RESULTS_EXISTS); + flags.more_results_exists= MY_TEST(thd->server_status & + SERVER_MORE_RESULTS_EXISTS); flags.in_trans= thd->in_active_multi_stmt_transaction(); - flags.autocommit= test(thd->server_status & SERVER_STATUS_AUTOCOMMIT); + flags.autocommit= MY_TEST(thd->server_status & SERVER_STATUS_AUTOCOMMIT); flags.pkt_nr= thd->net.pkt_nr; flags.character_set_client_num= thd->variables.character_set_client->number; flags.character_set_results_num= @@ -1909,6 +1940,13 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", (int)flags.autocommit)); memcpy((uchar *)(sql + (tot_length - QUERY_CACHE_FLAGS_SIZE)), (uchar*) &flags, QUERY_CACHE_FLAGS_SIZE); + +#ifdef WITH_WSREP + bool once_more; + once_more= true; +lookup: +#endif /* WITH_WSREP */ + query_block = (Query_cache_block *) my_hash_search(&queries, (uchar*) sql, tot_length); /* Quick abort on unlocked data */ @@ -1921,6 +1959,19 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", } DBUG_PRINT("qcache", ("Query in query hash 0x%lx", (ulong)query_block)); +#ifdef WITH_WSREP + if (once_more && WSREP_CLIENT(thd) && wsrep_must_sync_wait(thd)) + { + unlock(); + if (wsrep_sync_wait(thd)) + goto err; + if (try_lock(thd, Query_cache::TIMEOUT)) + goto err; + once_more= false; + goto lookup; + } +#endif /* WITH_WSREP */ + /* Now lock and test that nothing changed while blocks was unlocked */ BLOCK_LOCK_RD(query_block); @@ -1949,7 +2000,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", } // Check access; - thd_proc_info(thd, "checking privileges on cached query"); + THD_STAGE_INFO(thd, stage_checking_privileges_on_cached_query); block_table= query_block->table(0); block_table_end= block_table+query_block->n_tables; for (; block_table != block_table_end; block_table++) @@ -2013,40 +2064,55 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", } #endif /*!NO_EMBEDDED_ACCESS_CHECKS*/ engine_data= table->engine_data(); - if (table->callback() && - !(*table->callback())(thd, table->db(), - table->key_length(), - &engine_data)) + if (table->callback()) { - DBUG_PRINT("qcache", ("Handler does not allow caching for %s.%s", - table_list.db, table_list.alias)); - BLOCK_UNLOCK_RD(query_block); - if (engine_data != table->engine_data()) - { - DBUG_PRINT("qcache", - ("Handler require invalidation queries of %s.%s %lu-%lu", - table_list.db, table_list.alias, - (ulong) engine_data, (ulong) table->engine_data())); - invalidate_table_internal(thd, - (uchar *) table->db(), - table->key_length()); - } - else + char qcache_se_key_name[FN_REFLEN + 10]; + uint qcache_se_key_len, db_length= strlen(table->db()); + engine_data= table->engine_data(); + + qcache_se_key_len= build_normalized_name(qcache_se_key_name, + sizeof(qcache_se_key_name), + table->db(), + db_length, + table->table(), + table->key_length() - + db_length - 2 - + table->suffix_length(), + table->suffix_length()); + + if (!(*table->callback())(thd, qcache_se_key_name, + qcache_se_key_len, &engine_data)) { + DBUG_PRINT("qcache", ("Handler does not allow caching for %.*s", + qcache_se_key_len, qcache_se_key_name)); + BLOCK_UNLOCK_RD(query_block); + if (engine_data != table->engine_data()) + { + DBUG_PRINT("qcache", + ("Handler require invalidation queries of %.*s %lu-%lu", + qcache_se_key_len, qcache_se_key_name, + (ulong) engine_data, (ulong) table->engine_data())); + invalidate_table_internal(thd, + (uchar *) table->db(), + table->key_length()); + } + else + { + /* + As this can change from call to call, don't reset set + thd->lex->safe_to_cache_query + */ + thd->query_cache_is_applicable= 0; // Query can't be cached + } /* - As this can change from call to call, don't reset set - thd->lex->safe_to_cache_query + End the statement transaction potentially started by engine. + Currently our engines do not request rollback from callbacks. + If this is going to change code needs to be reworked. */ - thd->query_cache_is_applicable= 0; // Query can't be cached + DBUG_ASSERT(! thd->transaction_rollback_request); + trans_rollback_stmt(thd); + goto err_unlock; // Parse query } - /* - End the statement transaction potentially started by engine. - Currently our engines do not request rollback from callbacks. - If this is going to change code needs to be reworked. - */ - DBUG_ASSERT(! thd->transaction_rollback_request); - trans_rollback_stmt(thd); - goto err_unlock; // Parse query } else DBUG_PRINT("qcache", ("handler allow caching %s,%s", @@ -2060,7 +2126,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", Send cached result to client */ #ifndef EMBEDDED_LIBRARY - thd_proc_info(thd, "sending cached result to client"); + THD_STAGE_INFO(thd, stage_sending_cached_result_to_client); do { DBUG_PRINT("qcache", ("Results (len: %lu used: %lu headers: %lu)", @@ -2085,13 +2151,13 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", } #endif /*!EMBEDDED_LIBRARY*/ - thd->sent_row_count= thd->limit_found_rows = query->found_rows(); + thd->set_sent_row_count(thd->limit_found_rows = query->found_rows()); thd->status_var.last_query_cost= 0.0; thd->query_plan_flags= (thd->query_plan_flags & ~QPLAN_QC_NO) | QPLAN_QC; - if (!thd->sent_row_count) + if (!thd->get_sent_row_count()) status_var_increment(thd->status_var.empty_queries); else - status_var_add(thd->status_var.rows_sent, thd->sent_row_count); + status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count()); /* End the statement transaction potentially started by an @@ -2100,8 +2166,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", response, we can't handle it anyway. */ (void) trans_commit_stmt(thd); - if (!thd->stmt_da->is_set()) - thd->stmt_da->disable_status(); + thd->get_stmt_da()->disable_status(); BLOCK_UNLOCK_RD(query_block); MYSQL_QUERY_CACHE_HIT(thd->query(), (ulong) thd->limit_found_rows); @@ -2165,7 +2230,7 @@ void Query_cache::invalidate(THD *thd, CHANGED_TABLE_LIST *tables_used) for (; tables_used; tables_used= tables_used->next) { - thd_proc_info(thd, "invalidating query cache entries (table list)"); + THD_STAGE_INFO(thd, stage_invalidating_query_cache_entries_table_list); invalidate_table(thd, (uchar*) tables_used->key, tables_used->key_length); DBUG_PRINT("qcache", ("db: %s table: %s", tables_used->key, tables_used->key+ @@ -2194,7 +2259,7 @@ void Query_cache::invalidate_locked_for_write(THD *thd, for (; tables_used; tables_used= tables_used->next_local) { - thd_proc_info(thd, "invalidating query cache entries (table)"); + THD_STAGE_INFO(thd, stage_invalidating_query_cache_entries_table); if (tables_used->lock_type >= TL_WRITE_ALLOW_WRITE && tables_used->table) { @@ -2253,6 +2318,8 @@ void Query_cache::invalidate(THD *thd, char *db) if (is_disabled()) DBUG_VOID_RETURN; + DBUG_ASSERT(ok_for_lower_case_names(db)); + bool restart= FALSE; /* Lock the query cache and queue all invalidation attempts to avoid @@ -2322,6 +2389,9 @@ void Query_cache::invalidate_by_MyISAM_filename(const char *filename) { DBUG_ENTER("Query_cache::invalidate_by_MyISAM_filename"); + if (is_disabled()) + DBUG_VOID_RETURN; + /* Calculate the key outside the lock to make the lock shorter */ char key[MAX_DBKEY_LENGTH]; uint32 db_length; @@ -2916,7 +2986,7 @@ Query_cache::write_block_data(ulong data_len, uchar* data, DBUG_ENTER("Query_cache::write_block_data"); DBUG_PRINT("qcache", ("data: %ld, header: %ld, all header: %ld", data_len, header_len, all_headers_len)); - Query_cache_block *block= allocate_block(max(align_len, + Query_cache_block *block= allocate_block(MY_MAX(align_len, min_allocation_unit),1, 0); if (block != 0) { @@ -2971,7 +3041,7 @@ Query_cache::append_result_data(Query_cache_block **current_block, ulong append_min = get_min_append_result_data_size(); if (last_block_free_space < data_len && append_next_free_block(last_block, - max(tail, append_min))) + MY_MAX(tail, append_min))) last_block_free_space = last_block->length - last_block->used; // If no space in last block (even after join) allocate new block if (last_block_free_space < data_len) @@ -2999,7 +3069,7 @@ Query_cache::append_result_data(Query_cache_block **current_block, // Now finally write data to the last block if (success && last_block_free_space > 0) { - ulong to_copy = min(data_len,last_block_free_space); + ulong to_copy = MY_MIN(data_len,last_block_free_space); DBUG_PRINT("qcache", ("use free space %lub at block 0x%lx to copy %lub", last_block_free_space, (ulong)last_block, to_copy)); memcpy((uchar*) last_block + last_block->used, data, to_copy); @@ -3087,8 +3157,8 @@ inline ulong Query_cache::get_min_first_result_data_size() if (queries_in_cache < QUERY_CACHE_MIN_ESTIMATED_QUERIES_NUMBER) return min_result_data_size; ulong avg_result = (query_cache_size - free_memory) / queries_in_cache; - avg_result = min(avg_result, query_cache_limit); - return max(min_result_data_size, avg_result); + avg_result = MY_MIN(avg_result, query_cache_limit); + return MY_MAX(min_result_data_size, avg_result); } inline ulong Query_cache::get_min_append_result_data_size() @@ -3120,7 +3190,7 @@ my_bool Query_cache::allocate_data_chain(Query_cache_block **result_block, ulong len= data_len + all_headers_len; ulong align_len= ALIGN_SIZE(len); - if (!(new_block= allocate_block(max(min_size, align_len), + if (!(new_block= allocate_block(MY_MAX(min_size, align_len), min_result_data_size == 0, all_headers_len + min_result_data_size))) { @@ -3129,7 +3199,7 @@ my_bool Query_cache::allocate_data_chain(Query_cache_block **result_block, } new_block->n_tables = 0; - new_block->used = min(len, new_block->length); + new_block->used = MY_MIN(len, new_block->length); new_block->type = Query_cache_block::RES_INCOMPLETE; new_block->next = new_block->prev = new_block; Query_cache_result *header = new_block->result(); @@ -3170,11 +3240,9 @@ void Query_cache::invalidate_table(THD *thd, TABLE_LIST *table_list) invalidate_table(thd, table_list->table); // Table is open else { - char key[MAX_DBKEY_LENGTH]; + const char *key; uint key_length; - - key_length= create_table_def_key(key, table_list->db, - table_list->table_name); + key_length= get_table_def_key(table_list, &key); // We don't store temporary tables => no key_length+=4 ... invalidate_table(thd, (uchar *)key, key_length); @@ -3287,18 +3355,17 @@ Query_cache::register_tables_from_list(THD *thd, TABLE_LIST *tables_used, (*block_table)->n= n; if (tables_used->view) { - char key[MAX_DBKEY_LENGTH]; + const char *key; uint key_length; DBUG_PRINT("qcache", ("view: %s db: %s", tables_used->view_name.str, tables_used->view_db.str)); - key_length= create_table_def_key(key, tables_used->view_db.str, - tables_used->view_name.str); + key_length= get_table_def_key(tables_used, &key); /* There are not callback function for for VIEWs */ - if (!insert_table(key_length, key, (*block_table), - tables_used->view_db.length, + if (!insert_table(thd, key_length, key, (*block_table), + tables_used->view_db.length, 0, HA_CACHE_TBL_NONTRANSACT, 0, 0, TRUE)) DBUG_RETURN(0); /* @@ -3316,10 +3383,10 @@ Query_cache::register_tables_from_list(THD *thd, TABLE_LIST *tables_used, (ulong) tables_used->table->s->table_cache_key.length, (ulong) tables_used->table->s->table_cache_key.str)); - if (!insert_table(tables_used->table->s->table_cache_key.length, + if (!insert_table(thd, tables_used->table->s->table_cache_key.length, tables_used->table->s->table_cache_key.str, (*block_table), - tables_used->db_length, + tables_used->db_length, 0, tables_used->table->file->table_cache_type(), tables_used->callback_func, tables_used->engine_data, @@ -3369,7 +3436,7 @@ my_bool Query_cache::register_all_tables(THD *thd, if (block_table->parent) unlink_table(block_table); } - return test(n); + return MY_TEST(n); } @@ -3382,9 +3449,10 @@ my_bool Query_cache::register_all_tables(THD *thd, */ my_bool -Query_cache::insert_table(uint key_len, char *key, +Query_cache::insert_table(THD *thd, uint key_len, const char *key, Query_cache_block_table *node, - uint32 db_length, uint8 cache_type, + uint32 db_length, uint8 suffix_length_arg, + uint8 cache_type, qc_engine_callback callback, ulonglong engine_data, my_bool hash) @@ -3393,8 +3461,6 @@ Query_cache::insert_table(uint key_len, char *key, DBUG_PRINT("qcache", ("insert table node 0x%lx, len %d", (ulong)node, key_len)); - THD *thd= current_thd; - Query_cache_block *table_block= (hash ? (Query_cache_block *) my_hash_search(&tables, (uchar*) key, key_len) : @@ -3459,6 +3525,7 @@ Query_cache::insert_table(uint key_len, char *key, char *db= header->db(); header->table(db + db_length + 1); header->key_length(key_len); + header->suffix_length(suffix_length_arg); header->type(cache_type); header->callback(callback); header->engine_data(engine_data); @@ -3535,7 +3602,7 @@ Query_cache::allocate_block(ulong len, my_bool not_less, ulong min) DBUG_PRINT("qcache", ("len %lu, not less %d, min %lu", len, not_less,min)); - if (len >= min(query_cache_size, query_cache_limit)) + if (len >= MY_MIN(query_cache_size, query_cache_limit)) { DBUG_PRINT("qcache", ("Query cache hase only %lu memory and limit %lu", query_cache_size, query_cache_limit)); @@ -4067,7 +4134,7 @@ Query_cache::is_cacheable(THD *thd, LEX *lex, (long) OPTION_TO_QUERY_CACHE, (long) lex->select_lex.options, (int) thd->variables.query_cache_type, - (uint) test(qc_is_able_to_intercept_result(thd)))); + (uint) MY_TEST(qc_is_able_to_intercept_result(thd)))); DBUG_RETURN(0); } @@ -4096,13 +4163,13 @@ my_bool Query_cache::ask_handler_allowance(THD *thd, continue; handler= table->file; if (!handler->register_query_cache_table(thd, - table->s->table_cache_key.str, - table->s->table_cache_key.length, + table->s->normalized_path.str, + table->s->normalized_path.length, &tables_used->callback_func, &tables_used->engine_data)) { - DBUG_PRINT("qcache", ("Handler does not allow caching for %s.%s", - tables_used->db, tables_used->alias)); + DBUG_PRINT("qcache", ("Handler does not allow caching for %s", + table->s->normalized_path.str)); /* As this can change from call to call, don't reset set thd->lex->safe_to_cache_query @@ -4521,7 +4588,7 @@ uint Query_cache::filename_2_table_key (char *key, const char *path, DBUG_PRINT("qcache", ("table '%-.*s.%s'", *db_length, dbname, filename)); DBUG_RETURN((uint) (strmake(strmake(key, dbname, - min(*db_length, NAME_LEN)) + 1, + MY_MIN(*db_length, NAME_LEN)) + 1, filename, NAME_LEN) - key) + 1); } @@ -4565,7 +4632,7 @@ void Query_cache::wreck(uint line, const char *message) DBUG_PRINT("warning", ("%5d QUERY CACHE WRECK => DISABLED",line)); DBUG_PRINT("warning", ("==================================")); if (thd) - thd->killed= KILL_CONNECTION; + thd->set_killed(KILL_CONNECTION); cache_dump(); /* check_integrity(0); */ /* Can't call it here because of locks */ bins_dump(); diff --git a/sql/sql_cache.h b/sql/sql_cache.h index f35ac889b23..657caf4a5bc 100644 --- a/sql/sql_cache.h +++ b/sql/sql_cache.h @@ -156,8 +156,9 @@ struct Query_cache_query Query_cache_block *res; Query_cache_tls *wri; ulong len; - uint8 tbls_type; unsigned int last_pkt_nr; + uint8 tbls_type; + uint8 ready; Query_cache_query() {} /* Remove gcc warning */ inline void init_n_lock(); @@ -177,6 +178,12 @@ struct Query_cache_query { return (((uchar*)this) + ALIGN_SIZE(sizeof(Query_cache_query))); } + /** + following used to check if result ready in plugin without + locking rw_lock of the query. + */ + inline void set_results_ready() { ready= 1; } + inline bool is_results_ready() { return ready; } void lock_writing(); void lock_reading(); bool try_lock_writing(); @@ -190,6 +197,7 @@ struct Query_cache_table Query_cache_table() {} /* Remove gcc warning */ char *tbl; uint32 key_len; + uint8 suffix_len; /* For partitioned tables */ uint8 table_type; /* unique for every engine reference */ qc_engine_callback callback_func; @@ -210,6 +218,8 @@ struct Query_cache_table inline void table(char *table_arg) { tbl= table_arg; } inline uint32 key_length() { return key_len; } inline void key_length(uint32 len) { key_len= len; } + inline uint8 suffix_length() { return suffix_len; } + inline void suffix_length(uint8 len) { suffix_len= len; } inline uint8 type() { return table_type; } inline void type(uint8 t) { table_type= t; } inline qc_engine_callback callback() { return callback_func; } @@ -484,19 +494,20 @@ protected: void destroy(); - void insert(Query_cache_tls *query_cache_tls, + void insert(THD *thd, Query_cache_tls *query_cache_tls, const char *packet, ulong length, unsigned pkt_nr); - my_bool insert_table(uint key_len, char *key, + my_bool insert_table(THD *thd, uint key_len, const char *key, Query_cache_block_table *node, - uint32 db_length, uint8 cache_type, + uint32 db_length, uint8 suffix_length_arg, + uint8 cache_type, qc_engine_callback callback, ulonglong engine_data, my_bool hash); void end_of_result(THD *thd); - void abort(Query_cache_tls *query_cache_tls); + void abort(THD *thd, Query_cache_tls *query_cache_tls); /* The following functions are only used when debugging @@ -554,7 +565,7 @@ struct Query_cache_query_flags #define QUERY_CACHE_FLAGS_SIZE sizeof(Query_cache_query_flags) #define QUERY_CACHE_DB_LENGTH_SIZE 2 #include "sql_cache.h" -#define query_cache_abort(A) query_cache.abort(A) +#define query_cache_abort(A,B) query_cache.abort(A,B) #define query_cache_end_of_result(A) query_cache.end_of_result(A) #define query_cache_store_query(A, B) query_cache.store_query(A, B) #define query_cache_destroy() query_cache.destroy() @@ -586,7 +597,7 @@ struct Query_cache_query_flags #define query_cache_send_result_to_client(A, B, C) 0 #define query_cache_invalidate_by_MyISAM_filename_ref NULL -#define query_cache_abort(A) do { } while(0) +#define query_cache_abort(A,B) do { } while(0) #define query_cache_end_of_result(A) do { } while(0) #define query_cache_maybe_disabled(T) 1 #define query_cache_is_cacheable_query(L) 0 diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ff06b7fb3dc..639c7c1784a 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -28,13 +28,13 @@ #pragma implementation // gcc: Class implementation #endif -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_class.h" #include "sql_cache.h" // query_cache_abort #include "sql_base.h" // close_thread_tables #include "sql_time.h" // date_time_format_copy +#include "tztime.h" // MYSQL_TIME <-> my_time_t #include "sql_acl.h" // NO_ACCESS, // acl_getroot_no_password #include "sql_base.h" // close_temporary_tables @@ -62,7 +62,15 @@ #include "debug_sync.h" #include "sql_parse.h" // is_update_query #include "sql_callback.h" +#include "lock.h" +#include "wsrep_mysqld.h" +#include "wsrep_thd.h" #include "sql_connect.h" +#include "my_atomic.h" + +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif /* The following is used to initialise Table_ident with a internal @@ -73,23 +81,6 @@ char empty_c_string[1]= {0}; /* used for not defined db */ const char * const THD::DEFAULT_WHERE= "field list"; - -/***************************************************************************** -** Instansiate templates -*****************************************************************************/ - -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -/* Used templates */ -template class List<Key>; -template class List_iterator<Key>; -template class List<Key_part_spec>; -template class List_iterator<Key_part_spec>; -template class List<Alter_drop>; -template class List_iterator<Alter_drop>; -template class List<Alter_column>; -template class List_iterator<Alter_column>; -#endif - /**************************************************************************** ** User variables ****************************************************************************/ @@ -124,7 +115,7 @@ bool Key_part_spec::operator==(const Key_part_spec& other) const */ Key::Key(const Key &rhs, MEM_ROOT *mem_root) - :type(rhs.type), + :DDL_options(rhs),type(rhs.type), key_create_info(rhs.key_create_info), columns(rhs.columns, mem_root), name(rhs.name), @@ -143,6 +134,7 @@ Key::Key(const Key &rhs, MEM_ROOT *mem_root) Foreign_key::Foreign_key(const Foreign_key &rhs, MEM_ROOT *mem_root) :Key(rhs,mem_root), + ref_db(rhs.ref_db), ref_table(rhs.ref_table), ref_columns(rhs.ref_columns,mem_root), delete_opt(rhs.delete_opt), @@ -244,7 +236,7 @@ bool Foreign_key::validate(List<Create_field> &table_fields) sql_field->field_name)) {} if (!sql_field) { - my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), column->field_name); + my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), column->field_name.str); DBUG_RETURN(TRUE); } if (type == Key::FOREIGN_KEY && sql_field->vcol_info) @@ -341,7 +333,7 @@ void thd_set_psi(THD *thd, PSI_thread *psi) */ void thd_set_killed(THD *thd) { - thd->killed= KILL_CONNECTION; + thd->set_killed(KILL_CONNECTION); } /** @@ -367,29 +359,6 @@ void thd_set_thread_stack(THD *thd, char *stack_start) } /** - Lock connection data for the set of connections this connection - belongs to - - @param thd THD object -*/ -void thd_lock_thread_count(THD *) -{ - mysql_mutex_lock(&LOCK_thread_count); -} - -/** - Lock connection data for the set of connections this connection - belongs to - - @param thd THD object -*/ -void thd_unlock_thread_count(THD *) -{ - mysql_cond_broadcast(&COND_thread_count); - mysql_mutex_unlock(&LOCK_thread_count); -} - -/** Close the socket used by this connection @param thd THD object @@ -401,16 +370,6 @@ void thd_close_connection(THD *thd) } /** - Get current THD object from thread local data - - @retval The THD object for the thread, NULL if not connection thread -*/ -THD *thd_get_current_thd() -{ - return current_thd; -} - -/** Lock data that needs protection in THD object @param thd THD object @@ -495,7 +454,7 @@ void thd_set_mysys_var(THD *thd, st_my_thread_var *mysys_var) */ my_socket thd_get_fd(THD *thd) { - return thd->net.vio->sd; + return mysql_socket_getfd(thd->net.vio->mysql_socket); } #endif @@ -551,54 +510,73 @@ extern "C" int mysql_tmpfile(const char *prefix) extern "C" int thd_in_lock_tables(const THD *thd) { - return test(thd->in_lock_tables); + return MY_TEST(thd->in_lock_tables); } extern "C" int thd_tablespace_op(const THD *thd) { - return test(thd->tablespace_op); + return MY_TEST(thd->tablespace_op); } - extern "C" -const char *set_thd_proc_info(THD *thd, const char *info, +const char *set_thd_proc_info(THD *thd_arg, const char *info, const char *calling_function, const char *calling_file, const unsigned int calling_line) { - if (!thd) - thd= current_thd; + PSI_stage_info old_stage; + PSI_stage_info new_stage; - const char *old_info= thd->proc_info; - DBUG_PRINT("proc_info", ("%s:%d %s", calling_file, calling_line, info)); + old_stage.m_key= 0; + old_stage.m_name= info; -#if defined(ENABLED_PROFILING) - thd->profiling.status_change(info, - calling_function, calling_file, calling_line); -#endif - thd->proc_info= info; - return old_info; + set_thd_stage_info(thd_arg, & old_stage, & new_stage, + calling_function, calling_file, calling_line); + + return new_stage.m_name; } extern "C" -const char* thd_enter_cond(MYSQL_THD thd, mysql_cond_t *cond, - mysql_mutex_t *mutex, const char *msg) +void set_thd_stage_info(void *thd_arg, + const PSI_stage_info *new_stage, + PSI_stage_info *old_stage, + const char *calling_func, + const char *calling_file, + const unsigned int calling_line) +{ + THD *thd= (THD*) thd_arg; + if (thd == NULL) + thd= current_thd; + + if (old_stage) + thd->backup_stage(old_stage); + + if (new_stage) + thd->enter_stage(new_stage, calling_func, calling_file, calling_line); +} + +void thd_enter_cond(MYSQL_THD thd, mysql_cond_t *cond, mysql_mutex_t *mutex, + const PSI_stage_info *stage, PSI_stage_info *old_stage, + const char *src_function, const char *src_file, + int src_line) { if (!thd) thd= current_thd; - return thd->enter_cond(cond, mutex, msg); + return thd->enter_cond(cond, mutex, stage, old_stage, src_function, src_file, + src_line); } -extern "C" -void thd_exit_cond(MYSQL_THD thd, const char *old_msg) +void thd_exit_cond(MYSQL_THD thd, const PSI_stage_info *stage, + const char *src_function, const char *src_file, + int src_line) { if (!thd) thd= current_thd; - thd->exit_cond(old_msg); + thd->exit_cond(stage, src_function, src_file, src_line); return; } @@ -644,6 +622,17 @@ void thd_set_ha_data(THD *thd, const struct handlerton *hton, } +/** + Allow storage engine to wakeup commits waiting in THD::wait_for_prior_commit. + @see thd_wakeup_subsequent_commits() definition in plugin.h +*/ +extern "C" +void thd_wakeup_subsequent_commits(THD *thd, int wakeup_error) +{ + thd->wakeup_subsequent_commits(wakeup_error); +} + + extern "C" long long thd_test_options(const THD *thd, long long test_options) { @@ -663,109 +652,51 @@ int thd_tx_isolation(const THD *thd) } extern "C" -void thd_inc_row_count(THD *thd) +int thd_tx_is_read_only(const THD *thd) { - thd->warning_info->inc_current_row_for_warning(); + return (int) thd->tx_read_only; } -/** - Dumps a text description of a thread, its security context - (user, host) and the current query. - - @param thd thread context - @param buffer pointer to preferred result buffer - @param length length of buffer - @param max_query_len how many chars of query to copy (0 for all) - - @req LOCK_thread_count - - @note LOCK_thread_count mutex is not necessary when the function is invoked on - the currently running thread (current_thd) or if the caller in some other - way guarantees that access to thd->query is serialized. - - @return Pointer to string -*/ - extern "C" -char *thd_security_context(THD *thd, char *buffer, unsigned int length, - unsigned int max_query_len) -{ - String str(buffer, length, &my_charset_latin1); - const Security_context *sctx= &thd->main_security_ctx; - char header[256]; - int len; - /* - The pointers thd->query and thd->proc_info might change since they are - being modified concurrently. This is acceptable for proc_info since its - values doesn't have to very accurate and the memory it points to is static, - but we need to attempt a snapshot on the pointer values to avoid using NULL - values. The pointer to thd->query however, doesn't point to static memory - and has to be protected by thd->LOCK_thd_data or risk pointing to - uninitialized memory. - */ - const char *proc_info= thd->proc_info; - - len= my_snprintf(header, sizeof(header), - "MySQL thread id %lu, OS thread handle 0x%lx, query id %lu", - thd->thread_id, (ulong) thd->real_id, (ulong) thd->query_id); - str.length(0); - str.append(header, len); - - if (sctx->host) - { - str.append(' '); - str.append(sctx->host); - } +{ /* Functions for thd_error_context_service */ - if (sctx->ip) + const char *thd_get_error_message(const THD *thd) { - str.append(' '); - str.append(sctx->ip); + return thd->get_stmt_da()->message(); } - if (sctx->user) + uint thd_get_error_number(const THD *thd) { - str.append(' '); - str.append(sctx->user); + return thd->get_stmt_da()->sql_errno(); } - if (proc_info) + ulong thd_get_error_row(const THD *thd) { - str.append(' '); - str.append(proc_info); + return thd->get_stmt_da()->current_row_for_warning(); } - /* Don't wait if LOCK_thd_data is used as this could cause a deadlock */ - if (!mysql_mutex_trylock(&thd->LOCK_thd_data)) + void thd_inc_error_row(THD *thd) { - if (thd->query()) - { - if (max_query_len < 1) - len= thd->query_length(); - else - len= min(thd->query_length(), max_query_len); - str.append('\n'); - str.append(thd->query(), len); - } - mysql_mutex_unlock(&thd->LOCK_thd_data); + thd->get_stmt_da()->inc_current_row_for_warning(); } +} - if (str.c_ptr_safe() == buffer) - return buffer; - /* - We have to copy the new string to the destination buffer because the string - was reallocated to a larger buffer to be able to fit. - */ - DBUG_ASSERT(buffer != NULL); - length= min(str.length(), length-1); - memcpy(buffer, str.c_ptr_quick(), length); - /* Make sure that the new string is null terminated */ - buffer[length]= '\0'; - return buffer; +#if MARIA_PLUGIN_INTERFACE_VERSION < 0x0200 +/** + TODO: This function is for API compatibility, remove it eventually. + All engines should switch to use thd_get_error_context_description() + plugin service function. +*/ +extern "C" +char *thd_security_context(THD *thd, + char *buffer, unsigned int length, + unsigned int max_query_len) +{ + return thd_get_error_context_description(thd, buffer, length, max_query_len); } - +#endif /** Implementation of Drop_table_error_handler::handle_condition(). @@ -784,9 +715,9 @@ char *thd_security_context(THD *thd, char *buffer, unsigned int length, bool Drop_table_error_handler::handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { *cond_hdl= NULL; return ((sql_errno == EE_DELETE && my_errno == ENOENT) || @@ -794,10 +725,28 @@ bool Drop_table_error_handler::handle_condition(THD *thd, } -THD::THD() - :Statement(&main_lex, &main_mem_root, STMT_CONVENTIONAL_EXECUTION, - /* statement id */ 0), - rli_fake(0), rli_slave(NULL), +/** + Send timeout to thread. + + Note that this is always safe as the thread will always remove it's + timeouts at end of query (and thus before THD is destroyed) +*/ + +extern "C" void thd_kill_timeout(THD* thd) +{ + thd->status_var.max_statement_time_exceeded++; + mysql_mutex_lock(&thd->LOCK_thd_data); + /* Kill queries that can't cause data corruptions */ + thd->awake(KILL_TIMEOUT); + mysql_mutex_unlock(&thd->LOCK_thd_data); +} + + +THD::THD(bool is_wsrep_applier) + :Statement(&main_lex, &main_mem_root, STMT_CONVENTIONAL_EXECUTION, + /* statement id */ 0), + rli_fake(0), rgi_fake(0), rgi_slave(NULL), + protocol_text(this), protocol_binary(this), in_sub_stmt(0), log_all_errors(0), binlog_unsafe_warning_flags(0), binlog_table_maps(0), @@ -807,10 +756,13 @@ THD::THD() first_successful_insert_id_in_prev_stmt_for_binlog(0), first_successful_insert_id_in_cur_stmt(0), stmt_depends_on_first_successful_insert_id_in_prev_stmt(FALSE), - examined_row_count(0), + m_examined_row_count(0), accessed_rows_and_keys(0), - warning_info(&main_warning_info), - stmt_da(&main_da), + m_digest(NULL), + m_statement_psi(NULL), + m_idle_psi(NULL), + thread_id(0), + os_thread_id(0), global_disable_checkpoint(0), failed_com_change_user(0), is_fatal_error(0), @@ -821,22 +773,54 @@ THD::THD() in_lock_tables(0), bootstrap(0), derived_tables_processing(FALSE), + waiting_on_group_commit(FALSE), has_waiter(FALSE), spcont(NULL), m_parser_state(NULL), #if defined(ENABLED_DEBUG_SYNC) debug_sync_control(0), #endif /* defined(ENABLED_DEBUG_SYNC) */ - main_warning_info(0, false) + wait_for_commit_ptr(0), + m_internal_handler(0), + main_da(0, false, false), + m_stmt_da(&main_da), + tdc_hash_pins(0), + xid_hash_pins(0) +#ifdef WITH_WSREP + , + wsrep_applier(is_wsrep_applier), + wsrep_applier_closing(false), + wsrep_client_thread(false), + wsrep_apply_toi(false), + wsrep_po_handle(WSREP_PO_INITIALIZER), + wsrep_po_cnt(0), + wsrep_apply_format(0), + wsrep_ignore_table(false) +#endif { ulong tmp; + bzero(&variables, sizeof(variables)); + + /* + We set THR_THD to temporally point to this THD to register all the + variables that allocates memory for this THD + */ + THD *old_THR_THD= current_thd; + set_current_thd(this); + status_var.local_memory_used= sizeof(THD); + status_var.global_memory_used= 0; + variables.max_mem_used= global_system_variables.max_mem_used; + main_da.init(); mdl_context.init(this); + /* Pass nominal parameters to init_alloc_root only to ensure that the destructor works OK in case of an error. The main_mem_root will be re-initialized in init_for_queries(). */ - init_sql_alloc(&main_mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0); + init_sql_alloc(&main_mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0, + MYF(MY_THREAD_SPECIFIC)); + stmt_arena= this; thread_stack= 0; scheduler= thread_scheduler; // Will be fixed later @@ -852,28 +836,30 @@ THD::THD() query_start_used= query_start_sec_part_used= 0; count_cuted_fields= CHECK_FIELD_IGNORE; killed= NOT_KILLED; + killed_err= 0; col_access=0; is_slave_error= thread_specific_used= FALSE; my_hash_clear(&handler_tables_hash); + my_hash_clear(&ull_hash); tmp_table=0; cuted_fields= 0L; - sent_row_count= 0L; + m_sent_row_count= 0L; limit_found_rows= 0; m_row_count_func= -1; statement_id_counter= 0UL; // Must be reset to handle error with THD's created for init of mysqld lex->current_select= 0; user_time.val= start_time= start_time_sec_part= 0; - start_utime= prior_thr_create_utime= 0L; + start_utime= utime_after_query= prior_thr_create_utime= 0L; utime_after_lock= 0L; progress.arena= 0; progress.report_to_client= 0; progress.max_counter= 0; current_linfo = 0; slave_thread = 0; - bzero(&variables, sizeof(variables)); - thread_id= 0; - one_shot_set= 0; + connection_name.str= 0; + connection_name.length= 0; + file_id = 0; query_id= 0; query_name_consts= 0; @@ -883,6 +869,7 @@ THD::THD() mysys_var=0; binlog_evt_union.do_union= FALSE; enable_slow_log= 0; + durability_property= HA_REGULAR_DURABILITY; #ifndef DBUG_OFF dbug_sentry=THD_SENTRY_MAGIC; @@ -891,8 +878,8 @@ THD::THD() mysql_audit_init_thd(this); #endif net.vio=0; + net.buff= 0; client_capabilities= 0; // minimalistic client - ull=0; system_thread= NON_SYSTEM_THREAD; cleanup_done= abort_on_warning= 0; peer_port= 0; // For SHOW PROCESSLIST @@ -907,6 +894,7 @@ THD::THD() #endif mysql_mutex_init(key_LOCK_thd_data, &LOCK_thd_data, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_wakeup_ready, &LOCK_wakeup_ready, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_thd_kill, &LOCK_thd_kill, MY_MUTEX_INIT_FAST); mysql_cond_init(key_COND_wakeup_ready, &COND_wakeup_ready, 0); /* LOCK_thread_count goes before LOCK_thd_data - the former is called around @@ -917,11 +905,29 @@ THD::THD() /* Variables with default values */ proc_info="login"; where= THD::DEFAULT_WHERE; - server_id = ::server_id; + variables.server_id = global_system_variables.server_id; slave_net = 0; - command=COM_CONNECT; + m_command=COM_CONNECT; *scramble= '\0'; +#ifdef WITH_WSREP + wsrep_ws_handle.trx_id = WSREP_UNDEFINED_TRX_ID; + wsrep_ws_handle.opaque = NULL; + wsrep_retry_counter = 0; + wsrep_PA_safe = true; + wsrep_retry_query = NULL; + wsrep_retry_query_len = 0; + wsrep_retry_command = COM_CONNECT; + wsrep_consistency_check = NO_CONSISTENCY_CHECK; + wsrep_mysql_replicated = 0; + wsrep_TOI_pre_query = NULL; + wsrep_TOI_pre_query_len = 0; + wsrep_info[sizeof(wsrep_info) - 1] = '\0'; /* make sure it is 0-terminated */ + wsrep_sync_wait_gtid = WSREP_GTID_UNDEFINED; + wsrep_affected_rows = 0; + wsrep_replicate_GTID = false; + wsrep_skip_wsrep_GTID = false; +#endif /* Call to init() below requires fully initialized Open_tables_state. */ reset_open_tables_state(this); @@ -932,7 +938,7 @@ THD::THD() user_connect=(USER_CONN *)0; my_hash_init(&user_vars, system_charset_info, USER_VARS_HASH_SIZE, 0, 0, (my_hash_get_key) get_var_key, - (my_hash_free_key) free_user_var, 0); + (my_hash_free_key) free_user_var, HASH_THREAD_SPECIFIC); sp_proc_cache= NULL; sp_func_cache= NULL; @@ -940,7 +946,7 @@ THD::THD() /* For user vars replication*/ if (opt_bin_log) my_init_dynamic_array(&user_var_events, - sizeof(BINLOG_USER_VAR_EVENT *), 16, 16); + sizeof(BINLOG_USER_VAR_EVENT *), 16, 16, MYF(0)); else bzero((char*) &user_var_events, sizeof(user_var_events)); @@ -949,19 +955,37 @@ THD::THD() protocol_text.init(this); protocol_binary.init(this); + thr_timer_init(&query_timer, (void (*)(void*)) thd_kill_timeout, this); + tablespace_op=FALSE; - tmp= sql_rnd_with_mutex(); + + /* + Initialize the random generator. We call my_rnd() without a lock as + it's not really critical if two threads modifies the structure at the + same time. We ensure that we have an unique number foreach thread + by adding the address of the stack. + */ + tmp= (ulong) (my_rnd(&sql_rand) * 0xffffffff); my_rnd_init(&rand, tmp + (ulong) &rand, tmp + (ulong) ::global_query_id); substitute_null_with_insert_id = FALSE; thr_lock_info_init(&lock_info); /* safety: will be reset after start */ + lock_info.mysql_thd= (void *)this; - m_internal_handler= NULL; - m_binlog_invoker= FALSE; + m_token_array= NULL; + if (max_digest_length > 0) + { + m_token_array= (unsigned char*) my_malloc(max_digest_length, + MYF(MY_WME|MY_THREAD_SPECIFIC)); + } + + m_binlog_invoker= INVOKER_NONE; memset(&invoker_user, 0, sizeof(invoker_user)); memset(&invoker_host, 0, sizeof(invoker_host)); prepare_derived_at_open= FALSE; create_tmp_table_for_derived= FALSE; save_prep_leaf_list= FALSE; + /* Restore THR_THD */ + set_current_thd(old_THR_THD); } @@ -982,9 +1006,9 @@ void THD::push_internal_handler(Internal_error_handler *handler) bool THD::handle_condition(uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { if (!m_internal_handler) { @@ -1018,10 +1042,10 @@ Internal_error_handler *THD::pop_internal_handler() void THD::raise_error(uint sql_errno) { - const char* msg= ER(sql_errno); + const char* msg= ER_THD(this, sql_errno); (void) raise_condition(sql_errno, NULL, - MYSQL_ERROR::WARN_LEVEL_ERROR, + Sql_condition::WARN_LEVEL_ERROR, msg); } @@ -1031,23 +1055,23 @@ void THD::raise_error_printf(uint sql_errno, ...) char ebuff[MYSQL_ERRMSG_SIZE]; DBUG_ENTER("THD::raise_error_printf"); DBUG_PRINT("my", ("nr: %d errno: %d", sql_errno, errno)); - const char* format= ER(sql_errno); + const char* format= ER_THD(this, sql_errno); va_start(args, sql_errno); my_vsnprintf(ebuff, sizeof(ebuff), format, args); va_end(args); (void) raise_condition(sql_errno, NULL, - MYSQL_ERROR::WARN_LEVEL_ERROR, + Sql_condition::WARN_LEVEL_ERROR, ebuff); DBUG_VOID_RETURN; } void THD::raise_warning(uint sql_errno) { - const char* msg= ER(sql_errno); + const char* msg= ER_THD(this, sql_errno); (void) raise_condition(sql_errno, NULL, - MYSQL_ERROR::WARN_LEVEL_WARN, + Sql_condition::WARN_LEVEL_WARN, msg); } @@ -1057,13 +1081,13 @@ void THD::raise_warning_printf(uint sql_errno, ...) char ebuff[MYSQL_ERRMSG_SIZE]; DBUG_ENTER("THD::raise_warning_printf"); DBUG_PRINT("enter", ("warning: %u", sql_errno)); - const char* format= ER(sql_errno); + const char* format= ER_THD(this, sql_errno); va_start(args, sql_errno); my_vsnprintf(ebuff, sizeof(ebuff), format, args); va_end(args); (void) raise_condition(sql_errno, NULL, - MYSQL_ERROR::WARN_LEVEL_WARN, + Sql_condition::WARN_LEVEL_WARN, ebuff); DBUG_VOID_RETURN; } @@ -1074,10 +1098,10 @@ void THD::raise_note(uint sql_errno) DBUG_PRINT("enter", ("code: %d", sql_errno)); if (!(variables.option_bits & OPTION_SQL_NOTES)) DBUG_VOID_RETURN; - const char* msg= ER(sql_errno); + const char* msg= ER_THD(this, sql_errno); (void) raise_condition(sql_errno, NULL, - MYSQL_ERROR::WARN_LEVEL_NOTE, + Sql_condition::WARN_LEVEL_NOTE, msg); DBUG_VOID_RETURN; } @@ -1090,30 +1114,31 @@ void THD::raise_note_printf(uint sql_errno, ...) DBUG_PRINT("enter",("code: %u", sql_errno)); if (!(variables.option_bits & OPTION_SQL_NOTES)) DBUG_VOID_RETURN; - const char* format= ER(sql_errno); + const char* format= ER_THD(this, sql_errno); va_start(args, sql_errno); my_vsnprintf(ebuff, sizeof(ebuff), format, args); va_end(args); (void) raise_condition(sql_errno, NULL, - MYSQL_ERROR::WARN_LEVEL_NOTE, + Sql_condition::WARN_LEVEL_NOTE, ebuff); DBUG_VOID_RETURN; } -MYSQL_ERROR* THD::raise_condition(uint sql_errno, +Sql_condition* THD::raise_condition(uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg) { - MYSQL_ERROR *cond= NULL; + Diagnostics_area *da= get_stmt_da(); + Sql_condition *cond= NULL; DBUG_ENTER("THD::raise_condition"); if (!(variables.option_bits & OPTION_SQL_NOTES) && - (level == MYSQL_ERROR::WARN_LEVEL_NOTE)) + (level == Sql_condition::WARN_LEVEL_NOTE)) DBUG_RETURN(NULL); - warning_info->opt_clear_warning_info(query_id); + da->opt_clear_warning_info(query_id); /* TODO: replace by DBUG_ASSERT(sql_errno != 0) once all bugs similar to @@ -1123,28 +1148,28 @@ MYSQL_ERROR* THD::raise_condition(uint sql_errno, if (sql_errno == 0) sql_errno= ER_UNKNOWN_ERROR; if (msg == NULL) - msg= ER(sql_errno); + msg= ER_THD(this, sql_errno); if (sqlstate == NULL) sqlstate= mysql_errno_to_sqlstate(sql_errno); - if ((level == MYSQL_ERROR::WARN_LEVEL_WARN) && + if ((level == Sql_condition::WARN_LEVEL_WARN) && really_abort_on_warning()) { /* FIXME: push_warning and strict SQL_MODE case. */ - level= MYSQL_ERROR::WARN_LEVEL_ERROR; - killed= KILL_BAD_DATA; + level= Sql_condition::WARN_LEVEL_ERROR; + set_killed(KILL_BAD_DATA); } switch (level) { - case MYSQL_ERROR::WARN_LEVEL_NOTE: - case MYSQL_ERROR::WARN_LEVEL_WARN: + case Sql_condition::WARN_LEVEL_NOTE: + case Sql_condition::WARN_LEVEL_WARN: got_warning= 1; break; - case MYSQL_ERROR::WARN_LEVEL_ERROR: + case Sql_condition::WARN_LEVEL_ERROR: break; default: DBUG_ASSERT(FALSE); @@ -1153,20 +1178,20 @@ MYSQL_ERROR* THD::raise_condition(uint sql_errno, if (handle_condition(sql_errno, sqlstate, level, msg, &cond)) DBUG_RETURN(cond); - if (level == MYSQL_ERROR::WARN_LEVEL_ERROR) + if (level == Sql_condition::WARN_LEVEL_ERROR) { mysql_audit_general(this, MYSQL_AUDIT_GENERAL_ERROR, sql_errno, msg); is_slave_error= 1; // needed to catch query errors during replication - if (! stmt_da->is_error()) + if (!da->is_error()) { set_row_count_func(-1); - stmt_da->set_error_status(this, sql_errno, msg, sqlstate); + da->set_error_status(sql_errno, msg, sqlstate, cond); } } - query_cache_abort(&query_cache_tls); + query_cache_abort(this, &query_cache_tls); /* Avoid pushing a condition for fatal out of memory errors as this will @@ -1176,7 +1201,7 @@ MYSQL_ERROR* THD::raise_condition(uint sql_errno, if (!(is_fatal_error && (sql_errno == EE_OUTOFMEMORY || sql_errno == ER_OUTOFMEMORY))) { - cond= warning_info->push_warning(this, sql_errno, sqlstate, level, msg); + cond= da->push_warning(this, sql_errno, sqlstate, level, msg); } DBUG_RETURN(cond); } @@ -1210,8 +1235,8 @@ LEX_STRING *thd_make_lex_string(THD *thd, LEX_STRING *lex_str, const char *str, unsigned int size, int allocate_lex_string) { - return thd->make_lex_string(lex_str, str, size, - (bool) allocate_lex_string); + return allocate_lex_string ? thd->make_lex_string(str, size) + : thd->make_lex_string(lex_str, str, size); } extern "C" @@ -1226,27 +1251,56 @@ void thd_get_xid(const MYSQL_THD thd, MYSQL_XID *xid) *xid = *(MYSQL_XID *) &thd->transaction.xid_state.xid; } + +extern "C" +my_time_t thd_TIME_to_gmt_sec(MYSQL_THD thd, const MYSQL_TIME *ltime, + unsigned int *errcode) +{ + Time_zone *tz= thd ? thd->variables.time_zone : + global_system_variables.time_zone; + return tz->TIME_to_gmt_sec(ltime, errcode); +} + + +extern "C" +void thd_gmt_sec_to_TIME(MYSQL_THD thd, MYSQL_TIME *ltime, my_time_t t) +{ + Time_zone *tz= thd ? thd->variables.time_zone : + global_system_variables.time_zone; + tz->gmt_sec_to_TIME(ltime, t); +} + + #ifdef _WIN32 extern "C" THD *_current_thd_noinline(void) { return my_pthread_getspecific_ptr(THD*,THR_THD); } #endif + /* Init common variables that has to be reset on start and on change_user */ void THD::init(void) { + DBUG_ENTER("thd::init"); mysql_mutex_lock(&LOCK_global_system_variables); plugin_thdvar_init(this); /* - variables= global_system_variables above has reset - variables.pseudo_thread_id to 0. We need to correct it here to + plugin_thd_var_init() sets variables= global_system_variables, which + has reset variables.pseudo_thread_id to 0. We need to correct it here to avoid temporary tables replication failure. */ variables.pseudo_thread_id= thread_id; + + variables.default_master_connection.str= default_master_connection_buff; + ::strmake(variables.default_master_connection.str, + global_system_variables.default_master_connection.str, + variables.default_master_connection.length); + mysql_mutex_unlock(&LOCK_global_system_variables); + server_status= SERVER_STATUS_AUTOCOMMIT; if (variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) server_status|= SERVER_STATUS_NO_BACKSLASH_ESCAPES; @@ -1255,23 +1309,51 @@ void THD::init(void) transaction.all.modified_non_trans_table= transaction.stmt.modified_non_trans_table= FALSE; + transaction.all.m_unsafe_rollback_flags= + transaction.stmt.m_unsafe_rollback_flags= 0; + open_options=ha_open_options; update_lock_default= (variables.low_priority_updates ? TL_WRITE_LOW_PRIORITY : TL_WRITE); tx_isolation= (enum_tx_isolation) variables.tx_isolation; + tx_read_only= variables.tx_read_only; update_charset(); reset_current_stmt_binlog_format_row(); - bzero((char *) &status_var, sizeof(status_var)); + reset_binlog_local_stmt_filter(); + set_status_var_init(); bzero((char *) &org_status_var, sizeof(org_status_var)); start_bytes_received= 0; + last_commit_gtid.seq_no= 0; status_in_global= 0; +#ifdef WITH_WSREP + wsrep_exec_mode= wsrep_applier ? REPL_RECV : LOCAL_STATE; + wsrep_conflict_state= NO_CONFLICT; + wsrep_query_state= QUERY_IDLE; + wsrep_last_query_id= 0; + wsrep_trx_meta.gtid= WSREP_GTID_UNDEFINED; + wsrep_trx_meta.depends_on= WSREP_SEQNO_UNDEFINED; + wsrep_converted_lock_session= false; + wsrep_retry_counter= 0; + wsrep_rgi= NULL; + wsrep_PA_safe= true; + wsrep_consistency_check = NO_CONSISTENCY_CHECK; + wsrep_mysql_replicated = 0; + wsrep_TOI_pre_query = NULL; + wsrep_TOI_pre_query_len = 0; + wsrep_sync_wait_gtid = WSREP_GTID_UNDEFINED; + wsrep_affected_rows = 0; + wsrep_replicate_GTID = false; + wsrep_skip_wsrep_GTID = false; +#endif /* WITH_WSREP */ if (variables.sql_log_bin) variables.option_bits|= OPTION_BIN_LOG; else variables.option_bits&= ~OPTION_BIN_LOG; + variables.sql_log_bin_off= 0; + select_commands= update_commands= other_commands= 0; /* Set to handle counting of aborted connections */ userstat_running= opt_userstat_running; @@ -1280,6 +1362,8 @@ void THD::init(void) /* Initialize the Debug Sync Facility. See debug_sync.cc. */ debug_sync_init_thread(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ + apc_target.init(&LOCK_thd_data); + DBUG_VOID_RETURN; } @@ -1347,7 +1431,6 @@ void THD::init_for_queries() variables.trans_alloc_block_size, variables.trans_prealloc_size); transaction.xid_state.xid.null(); - transaction.xid_state.in_thd=1; } @@ -1387,7 +1470,7 @@ void THD::cleanup(void) DBUG_ENTER("THD::cleanup"); DBUG_ASSERT(cleanup_done == 0); - killed= KILL_CONNECTION; + set_killed(KILL_CONNECTION); #ifdef ENABLE_WHEN_BINLOG_WILL_BE_ABLE_TO_PREPARE if (transaction.xid_state.xa_state == XA_PREPARED) { @@ -1403,7 +1486,7 @@ void THD::cleanup(void) transaction.xid_state.xa_state= XA_NOTR; trans_rollback(this); - xid_cache_delete(&transaction.xid_state); + xid_cache_delete(this, &transaction.xid_state); DBUG_ASSERT(open_tables == NULL); /* @@ -1418,8 +1501,6 @@ void THD::cleanup(void) if (global_read_lock.is_acquired()) global_read_lock.unlock_global_read_lock(this); - /* All metadata locks must have been released by now. */ - DBUG_ASSERT(!mdl_context.has_locks()); if (user_connect) { decrease_user_connections(user_connect); @@ -1436,14 +1517,12 @@ void THD::cleanup(void) sp_cache_clear(&sp_proc_cache); sp_cache_clear(&sp_func_cache); - if (ull) - { - mysql_mutex_lock(&LOCK_user_locks); - item_user_lock_release(ull); - mysql_mutex_unlock(&LOCK_user_locks); - ull= NULL; - } + mysql_ull_cleanup(this); + stmt_map.reset(); + /* All metadata locks must have been released by now. */ + DBUG_ASSERT(!mdl_context.has_locks()); + apc_target.destroy(); cleanup_done=1; DBUG_VOID_RETURN; } @@ -1451,19 +1530,30 @@ void THD::cleanup(void) THD::~THD() { + THD *orig_thd= current_thd; THD_CHECK_SENTRY(this); DBUG_ENTER("~THD()"); + + /* + In error cases, thd may not be current thd. We have to fix this so + that memory allocation counting is done correctly + */ + set_current_thd(this); + if (!status_in_global) + add_status_to_global(); + /* Ensure that no one is using THD */ mysql_mutex_lock(&LOCK_thd_data); mysql_mutex_unlock(&LOCK_thd_data); +#ifdef WITH_WSREP + if (wsrep_rgi) delete wsrep_rgi; +#endif /* Close connection */ #ifndef EMBEDDED_LIBRARY if (net.vio) - { vio_delete(net.vio); - net_end(&net); - } + net_end(&net); #endif stmt_map.reset(); /* close all prepared statements */ if (!cleanup_done) @@ -1474,7 +1564,6 @@ THD::~THD() mysql_audit_release(this); plugin_thdvar_cleanup(this); - DBUG_PRINT("info", ("freeing security context")); main_security_ctx.destroy(); my_free(db); db= NULL; @@ -1482,10 +1571,16 @@ THD::~THD() mysql_cond_destroy(&COND_wakeup_ready); mysql_mutex_destroy(&LOCK_wakeup_ready); mysql_mutex_destroy(&LOCK_thd_data); + mysql_mutex_destroy(&LOCK_thd_kill); #ifndef DBUG_OFF dbug_sentry= THD_SENTRY_GONE; #endif #ifndef EMBEDDED_LIBRARY + if (rgi_fake) + { + delete rgi_fake; + rgi_fake= NULL; + } if (rli_fake) { delete rli_fake; @@ -1493,12 +1588,28 @@ THD::~THD() } mysql_audit_free_thd(this); - if (rli_slave) - rli_slave->cleanup_after_session(); + if (rgi_slave) + rgi_slave->cleanup_after_session(); my_free(semisync_info); #endif - + main_lex.free_set_stmt_mem_root(); free_root(&main_mem_root, MYF(0)); + my_free(m_token_array); + main_da.free_memory(); + if (tdc_hash_pins) + lf_hash_put_pins(tdc_hash_pins); + if (xid_hash_pins) + lf_hash_put_pins(xid_hash_pins); + /* Ensure everything is freed */ + status_var.local_memory_used-= sizeof(THD); + if (status_var.local_memory_used != 0) + { + DBUG_PRINT("error", ("memory_used: %lld", status_var.local_memory_used)); + SAFEMALLOC_REPORT_MEMORY(my_thread_dbug_id()); + DBUG_ASSERT(status_var.local_memory_used == 0); + } + update_global_memory_status(status_var.global_memory_used); + set_current_thd(orig_thd == this ? 0 : orig_thd); DBUG_VOID_RETURN; } @@ -1516,6 +1627,7 @@ THD::~THD() other types are handled explicitely */ + void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var) { ulong *end= (ulong*) ((uchar*) to_var + @@ -1535,6 +1647,21 @@ void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var) to_var->binlog_bytes_written+= from_var->binlog_bytes_written; to_var->cpu_time+= from_var->cpu_time; to_var->busy_time+= from_var->busy_time; + + /* + Update global_memory_used. We have to do this with atomic_add as the + global value can change outside of LOCK_status. + */ + if (to_var == &global_status_var) + { + DBUG_PRINT("info", ("global memory_used: %lld size: %lld", + (longlong) global_status_var.global_memory_used, + (longlong) from_var->global_memory_used)); + } + // workaround for gcc 4.2.4-1ubuntu4 -fPIE (from DEB_BUILD_HARDENING=1) + int64 volatile * volatile ptr= &to_var->global_memory_used; + my_atomic_add64_explicit(ptr, from_var->global_memory_used, + MY_MEMORY_ORDER_RELAXED); } /* @@ -1572,6 +1699,11 @@ void add_diff_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var, dec_var->binlog_bytes_written; to_var->cpu_time+= from_var->cpu_time - dec_var->cpu_time; to_var->busy_time+= from_var->busy_time - dec_var->busy_time; + + /* + We don't need to accumulate memory_used as these are not reset or used by + the calling functions. See execute_show_status(). + */ } #define SECONDS_TO_WAIT_FOR_KILL 2 @@ -1591,19 +1723,29 @@ void add_diff_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var, This is normally called from another thread's THD object. @note Do always call this while holding LOCK_thd_data. + NOT_KILLED is used to awake a thread for a slave */ void THD::awake(killed_state state_to_set) { DBUG_ENTER("THD::awake"); - DBUG_PRINT("enter", ("this: %p current_thd: %p", this, current_thd)); + DBUG_PRINT("enter", ("this: %p current_thd: %p state: %d", + this, current_thd, (int) state_to_set)); THD_CHECK_SENTRY(this); mysql_mutex_assert_owner(&LOCK_thd_data); print_aborted_warning(3, "KILLED"); + /* + Don't degrade killed state, for example from a KILL_CONNECTION to + STATEMENT TIMEOUT + */ + if (killed >= KILL_CONNECTION) + state_to_set= killed; + /* Set the 'killed' flag of 'this', which is the target THD object. */ - killed= state_to_set; + mysql_mutex_lock(&LOCK_thd_kill); + set_killed_no_mutex(state_to_set); if (state_to_set >= KILL_CONNECTION || state_to_set == NOT_KILLED) { @@ -1633,6 +1775,7 @@ void THD::awake(killed_state state_to_set) mysql_mutex_lock(&mysys_var->mutex); if (!system_thread) // Don't abort locks mysys_var->abort=1; + /* This broadcast could be up in the air if the victim thread exits the cond in the time between read and broadcast, but that is @@ -1688,6 +1831,7 @@ void THD::awake(killed_state state_to_set) } mysql_mutex_unlock(&mysys_var->mutex); } + mysql_mutex_unlock(&LOCK_thd_kill); DBUG_VOID_RETURN; } @@ -1705,7 +1849,7 @@ void THD::disconnect() mysql_mutex_lock(&LOCK_thd_data); - killed= KILL_CONNECTION; + set_killed(KILL_CONNECTION); #ifdef SIGNAL_WITH_VIO_CLOSE /* @@ -1720,21 +1864,95 @@ void THD::disconnect() /* Disconnect even if a active vio is not associated. */ if (net.vio != vio) vio_close(net.vio); + net.thd= 0; // Don't collect statistics mysql_mutex_unlock(&LOCK_thd_data); } +bool THD::notify_shared_lock(MDL_context_owner *ctx_in_use, + bool needs_thr_lock_abort) +{ + THD *in_use= ctx_in_use->get_thd(); + bool signalled= FALSE; + DBUG_ENTER("THD::notify_shared_lock"); + DBUG_PRINT("enter",("needs_thr_lock_abort: %d", needs_thr_lock_abort)); + + if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && + !in_use->killed) + { + /* This code is similar to kill_delayed_threads() */ + DBUG_PRINT("info", ("kill delayed thread")); + mysql_mutex_lock(&in_use->LOCK_thd_data); + if (in_use->killed < KILL_CONNECTION) + in_use->set_killed(KILL_CONNECTION); + if (in_use->mysys_var) + { + mysql_mutex_lock(&in_use->mysys_var->mutex); + if (in_use->mysys_var->current_cond) + mysql_cond_broadcast(in_use->mysys_var->current_cond); + + /* Abort if about to wait in thr_upgrade_write_delay_lock */ + in_use->mysys_var->abort= 1; + mysql_mutex_unlock(&in_use->mysys_var->mutex); + } + mysql_mutex_unlock(&in_use->LOCK_thd_data); + signalled= TRUE; + } + + if (needs_thr_lock_abort) + { + mysql_mutex_lock(&in_use->LOCK_thd_data); + /* If not already dying */ + if (in_use->killed != KILL_CONNECTION_HARD) + { + for (TABLE *thd_table= in_use->open_tables; + thd_table ; + thd_table= thd_table->next) + { + /* + Check for TABLE::needs_reopen() is needed since in some + places we call handler::close() for table instance (and set + TABLE::db_stat to 0) and do not remove such instances from + the THD::open_tables for some time, during which other + thread can see those instances (e.g. see partitioning code). + */ + if (!thd_table->needs_reopen()) + { + signalled|= mysql_lock_abort_for_thread(this, thd_table); + if (WSREP(this) && wsrep_thd_is_BF(this, FALSE)) + { + WSREP_DEBUG("remove_table_from_cache: %llu", + (unsigned long long) this->real_id); + wsrep_abort_thd((void *)this, (void *)in_use, FALSE); + } + } + } + } + mysql_mutex_unlock(&in_use->LOCK_thd_data); + } + DBUG_RETURN(signalled); +} + + /* Get error number for killed state Note that the error message can't have any parameters. + If one needs parameters, one should use THD::killed_err_msg See thd::kill_message() */ -int killed_errno(killed_state killed) +int THD::killed_errno() { DBUG_ENTER("killed_errno"); - DBUG_PRINT("enter", ("killed: %d", killed)); + DBUG_PRINT("enter", ("killed: %d killed_errno: %d", + killed, killed_err ? killed_err->no: 0)); + + /* Ensure that killed_err is not set if we are not killed */ + DBUG_ASSERT(!killed_err || killed != NOT_KILLED); + + if (killed_err) + DBUG_RETURN(killed_err->no); switch (killed) { case NOT_KILLED: @@ -1753,6 +1971,9 @@ int killed_errno(killed_state killed) case KILL_QUERY: case KILL_QUERY_HARD: DBUG_RETURN(ER_QUERY_INTERRUPTED); + case KILL_TIMEOUT: + case KILL_TIMEOUT_HARD: + DBUG_RETURN(ER_STATEMENT_TIMEOUT); case KILL_SERVER: case KILL_SERVER_HARD: DBUG_RETURN(ER_SERVER_SHUTDOWN); @@ -1774,7 +1995,7 @@ bool THD::store_globals() */ DBUG_ASSERT(thread_stack); - if (my_pthread_setspecific_ptr(THR_THD, this) || + if (set_current_thd(this) || my_pthread_setspecific_ptr(THR_MALLOC, &mem_root)) return 1; /* @@ -1792,10 +2013,19 @@ bool THD::store_globals() This allows us to move THD to different threads if needed. */ mysys_var->id= thread_id; +#ifdef __NR_gettid + os_thread_id= (uint32)syscall(__NR_gettid); +#else + os_thread_id= 0; +#endif real_id= pthread_self(); // For debugging mysys_var->stack_ends_here= thread_stack + // for consistency, see libevent_thread_proc STACK_DIRECTION * (long)my_thread_stack_size; - vio_set_thread_id(net.vio, real_id); + if (net.vio) + { + vio_set_thread_id(net.vio, real_id); + net.thd= this; + } /* We have to call thr_lock_info_init() again here as THD may have been created in another thread @@ -1818,9 +2048,9 @@ void THD::reset_globals() mysql_mutex_unlock(&LOCK_thd_data); /* Undocking the thread specific data. */ - my_pthread_setspecific_ptr(THR_THD, NULL); + set_current_thd(0); my_pthread_setspecific_ptr(THR_MALLOC, NULL); - + net.thd= 0; } /* @@ -1871,10 +2101,18 @@ void THD::cleanup_after_query() which is intended to consume its event (there can be other SET statements between them). */ - if ((rli_slave || rli_fake) && is_update_query(lex->sql_command)) + if ((rgi_slave || rli_fake) && is_update_query(lex->sql_command)) auto_inc_intervals_forced.empty(); #endif } + /* + Forget the binlog stmt filter for the next query. + There are some code paths that: + - do not call THD::decide_logging_format() + - do call THD::binlog_query(), + making this reset necessary. + */ + reset_binlog_local_stmt_filter(); if (first_successful_insert_id_in_cur_stmt > 0) { /* set what LAST_INSERT_ID() will return */ @@ -1890,38 +2128,26 @@ void THD::cleanup_after_query() where= THD::DEFAULT_WHERE; /* reset table map for multi-table update */ table_map_for_update= 0; - m_binlog_invoker= FALSE; + m_binlog_invoker= INVOKER_NONE; +#ifdef WITH_WSREP + if (TOTAL_ORDER == wsrep_exec_mode) + { + wsrep_exec_mode = LOCAL_STATE; + } +#endif /* WITH_WSREP */ #ifndef EMBEDDED_LIBRARY - if (rli_slave) - rli_slave->cleanup_after_query(); + if (rgi_slave) + rgi_slave->cleanup_after_query(); #endif - DBUG_VOID_RETURN; -} - +#ifdef WITH_WSREP + wsrep_sync_wait_gtid= WSREP_GTID_UNDEFINED; + if (!in_active_multi_stmt_transaction()) + wsrep_affected_rows= 0; +#endif /* WITH_WSREP */ -/** - Create a LEX_STRING in this connection. - - @param lex_str pointer to LEX_STRING object to be initialized - @param str initializer to be copied into lex_str - @param length length of str, in bytes - @param allocate_lex_string if TRUE, allocate new LEX_STRING object, - instead of using lex_str value - @return NULL on failure, or pointer to the LEX_STRING object -*/ -LEX_STRING *THD::make_lex_string(LEX_STRING *lex_str, - const char* str, uint length, - bool allocate_lex_string) -{ - if (allocate_lex_string) - if (!(lex_str= (LEX_STRING *)alloc_root(mem_root, sizeof(LEX_STRING)))) - return 0; - if (!(lex_str->str= strmake_root(mem_root, str, length))) - return 0; - lex_str->length= length; - return lex_str; + DBUG_VOID_RETURN; } @@ -1949,18 +2175,88 @@ bool THD::convert_string(LEX_STRING *to, CHARSET_INFO *to_cs, const char *from, uint from_length, CHARSET_INFO *from_cs) { - DBUG_ENTER("convert_string"); + DBUG_ENTER("THD::convert_string"); size_t new_length= to_cs->mbmaxlen * from_length; uint dummy_errors; - if (!(to->str= (char*) alloc(new_length+1))) - { - to->length= 0; // Safety fix - DBUG_RETURN(1); // EOM - } + if (alloc_lex_string(to, new_length + 1)) + DBUG_RETURN(true); // EOM to->length= copy_and_convert((char*) to->str, new_length, to_cs, from, from_length, from_cs, &dummy_errors); - to->str[to->length]=0; // Safety - DBUG_RETURN(0); + to->str[to->length]= 0; // Safety + DBUG_RETURN(false); +} + + +/* + Convert a string between two character sets. + dstcs and srccs cannot be &my_charset_bin. +*/ +bool THD::convert_fix(CHARSET_INFO *dstcs, LEX_STRING *dst, + CHARSET_INFO *srccs, const char *src, uint src_length, + String_copier *status) +{ + DBUG_ENTER("THD::convert_fix"); + size_t dst_length= dstcs->mbmaxlen * src_length; + if (alloc_lex_string(dst, dst_length + 1)) + DBUG_RETURN(true); // EOM + dst->length= status->convert_fix(dstcs, (char*) dst->str, dst_length, + srccs, src, src_length, src_length); + dst->str[dst->length]= 0; // Safety + DBUG_RETURN(false); +} + + +/* + Copy or convert a string. +*/ +bool THD::copy_fix(CHARSET_INFO *dstcs, LEX_STRING *dst, + CHARSET_INFO *srccs, const char *src, uint src_length, + String_copier *status) +{ + DBUG_ENTER("THD::copy_fix"); + size_t dst_length= dstcs->mbmaxlen * src_length; + if (alloc_lex_string(dst, dst_length + 1)) + DBUG_RETURN(true); // EOM + dst->length= status->well_formed_copy(dstcs, dst->str, dst_length, + srccs, src, src_length, src_length); + dst->str[dst->length]= '\0'; + DBUG_RETURN(false); +} + + +class String_copier_with_error: public String_copier +{ +public: + bool check_errors(CHARSET_INFO *srccs, const char *src, uint src_length) + { + if (most_important_error_pos()) + { + ErrConvString err(src, src_length, &my_charset_bin); + my_error(ER_INVALID_CHARACTER_STRING, MYF(0), srccs->csname, err.ptr()); + return true; + } + return false; + } +}; + + +bool THD::convert_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst, + CHARSET_INFO *srccs, + const char *src, uint src_length) +{ + String_copier_with_error status; + return convert_fix(dstcs, dst, srccs, src, src_length, &status) || + status.check_errors(srccs, src, src_length); +} + + +bool THD::copy_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst, + CHARSET_INFO *srccs, + const char *src, uint src_length) +{ + String_copier_with_error status; + return copy_fix(dstcs, dst, srccs, src, src_length, &status) || + status.check_errors(srccs, src, src_length); } @@ -2097,7 +2393,7 @@ CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length) { my_error(EE_OUTOFMEMORY, MYF(ME_BELL+ME_FATALERROR), ALIGN_SIZE(sizeof(TABLE_LIST)) + key_length + 1); - killed= KILL_CONNECTION; + set_killed(KILL_CONNECTION); return 0; } @@ -2109,51 +2405,130 @@ CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length) } -int THD::send_explain_fields(select_result *result) +int THD::prepare_explain_fields(select_result *result, List<Item> *field_list, + uint8 explain_flags, bool is_analyze) +{ + if (lex->explain_json) + make_explain_json_field_list(*field_list, is_analyze); + else + make_explain_field_list(*field_list, explain_flags, is_analyze); + + return result->prepare(*field_list, NULL); +} + + +int THD::send_explain_fields(select_result *result, + uint8 explain_flags, + bool is_analyze) { List<Item> field_list; + int rc; + rc= prepare_explain_fields(result, &field_list, explain_flags, is_analyze) || + result->send_result_set_metadata(field_list, Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF); + return rc; +} + + +void THD::make_explain_json_field_list(List<Item> &field_list, bool is_analyze) +{ + Item *item= new (mem_root) Item_empty_string(this, (is_analyze ? + "ANALYZE" : + "EXPLAIN"), + 78, system_charset_info); + field_list.push_back(item, mem_root); +} + + +/* + Populate the provided field_list with EXPLAIN output columns. + this->lex->describe has the EXPLAIN flags + + The set/order of columns must be kept in sync with + Explain_query::print_explain and co. +*/ + +void THD::make_explain_field_list(List<Item> &field_list, uint8 explain_flags, + bool is_analyze) +{ Item *item; CHARSET_INFO *cs= system_charset_info; - field_list.push_back(item= new Item_return_int("id",3, MYSQL_TYPE_LONGLONG)); + field_list.push_back(item= new (mem_root) + Item_return_int(this, "id", 3, + MYSQL_TYPE_LONGLONG), mem_root); item->maybe_null= 1; - field_list.push_back(new Item_empty_string("select_type", 19, cs)); - field_list.push_back(item= new Item_empty_string("table", NAME_CHAR_LEN, cs)); + field_list.push_back(new (mem_root) + Item_empty_string(this, "select_type", 19, cs), + mem_root); + field_list.push_back(item= new (mem_root) + Item_empty_string(this, "table", NAME_CHAR_LEN, cs), + mem_root); item->maybe_null= 1; - if (lex->describe & DESCRIBE_PARTITIONS) + if (explain_flags & DESCRIBE_PARTITIONS) { /* Maximum length of string that make_used_partitions_str() can produce */ - item= new Item_empty_string("partitions", MAX_PARTITIONS * (1 + FN_LEN), - cs); - field_list.push_back(item); + item= new (mem_root) Item_empty_string(this, "partitions", + MAX_PARTITIONS * (1 + FN_LEN), cs); + field_list.push_back(item, mem_root); item->maybe_null= 1; } - field_list.push_back(item= new Item_empty_string("type", 10, cs)); + field_list.push_back(item= new (mem_root) + Item_empty_string(this, "type", 10, cs), + mem_root); item->maybe_null= 1; - field_list.push_back(item=new Item_empty_string("possible_keys", - NAME_CHAR_LEN*MAX_KEY, cs)); + field_list.push_back(item= new (mem_root) + Item_empty_string(this, "possible_keys", + NAME_CHAR_LEN*MAX_KEY, cs), + mem_root); item->maybe_null=1; - field_list.push_back(item=new Item_empty_string("key", NAME_CHAR_LEN, cs)); + field_list.push_back(item=new (mem_root) + Item_empty_string(this, "key", NAME_CHAR_LEN, cs), + mem_root); item->maybe_null=1; - field_list.push_back(item=new Item_empty_string("key_len", - NAME_CHAR_LEN*MAX_KEY)); + field_list.push_back(item=new (mem_root) + Item_empty_string(this, "key_len", + NAME_CHAR_LEN*MAX_KEY), + mem_root); item->maybe_null=1; - field_list.push_back(item=new Item_empty_string("ref", - NAME_CHAR_LEN*MAX_REF_PARTS, - cs)); + field_list.push_back(item=new (mem_root) + Item_empty_string(this, "ref", + NAME_CHAR_LEN*MAX_REF_PARTS, cs), + mem_root); item->maybe_null=1; - field_list.push_back(item= new Item_return_int("rows", 10, - MYSQL_TYPE_LONGLONG)); - if (lex->describe & DESCRIBE_EXTENDED) + field_list.push_back(item= new (mem_root) + Item_return_int(this, "rows", 10, MYSQL_TYPE_LONGLONG), + mem_root); + if (is_analyze) + { + field_list.push_back(item= new (mem_root) + Item_float(this, "r_rows", 0.1234, 2, 4), + mem_root); + item->maybe_null=1; + } + + if (is_analyze || (explain_flags & DESCRIBE_EXTENDED)) + { + field_list.push_back(item= new (mem_root) + Item_float(this, "filtered", 0.1234, 2, 4), + mem_root); + item->maybe_null=1; + } + + if (is_analyze) { - field_list.push_back(item= new Item_float("filtered", 0.1234, 2, 4)); + field_list.push_back(item= new (mem_root) + Item_float(this, "r_filtered", 0.1234, 2, 4), + mem_root); item->maybe_null=1; } + item->maybe_null= 1; - field_list.push_back(new Item_empty_string("Extra", 255, cs)); - return (result->send_result_set_metadata(field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)); + field_list.push_back(new (mem_root) + Item_empty_string(this, "Extra", 255, cs), + mem_root); } + #ifdef SIGNAL_WITH_VIO_CLOSE void THD::close_active_vio() { @@ -2272,17 +2647,6 @@ void THD::rollback_item_tree_changes() ** Functions to provide a interface to select results *****************************************************************************/ -select_result::select_result() -{ - thd=current_thd; -} - -void select_result::send_error(uint errcode,const char *err) -{ - my_message(errcode, err, MYF(0)); -} - - void select_result::cleanup() { /* do nothing */ @@ -2323,6 +2687,13 @@ bool sql_exchange::escaped_given(void) bool select_send::send_result_set_metadata(List<Item> &list, uint flags) { bool res; +#ifdef WITH_WSREP + if (WSREP(thd) && thd->wsrep_retry_query) + { + WSREP_DEBUG("skipping select metadata"); + return FALSE; + } +#endif /* WITH_WSREP */ if (!(res= thd->protocol->send_result_set_metadata(&list, flags))) is_result_set_started= 1; return res; @@ -2367,7 +2738,8 @@ int select_send::send_data(List<Item> &items) Protocol *protocol= thd->protocol; DBUG_ENTER("select_send::send_data"); - if (unit->offset_limit_cnt) + /* unit is not set when using 'delete ... returning' */ + if (unit && unit->offset_limit_cnt) { // using limit offset,count unit->offset_limit_cnt--; DBUG_RETURN(FALSE); @@ -2389,7 +2761,7 @@ int select_send::send_data(List<Item> &items) DBUG_RETURN(TRUE); } - thd->sent_row_count++; + thd->inc_sent_row_count(1); if (thd->vio_ok()) DBUG_RETURN(protocol->write()); @@ -2397,6 +2769,7 @@ int select_send::send_data(List<Item> &items) DBUG_RETURN(0); } + bool select_send::send_eof() { /* @@ -2422,27 +2795,13 @@ bool select_send::send_eof() Handling writing to file ************************************************************************/ -void select_to_file::send_error(uint errcode,const char *err) -{ - my_message(errcode, err, MYF(0)); - if (file > 0) - { - (void) end_io_cache(&cache); - mysql_file_close(file, MYF(0)); - /* Delete file on error */ - mysql_file_delete(key_select_to_file, path, MYF(0)); - file= -1; - } -} - - bool select_to_file::send_eof() { - int error= test(end_io_cache(&cache)); + int error= MY_TEST(end_io_cache(&cache)); if (mysql_file_close(file, MYF(MY_WME)) || thd->is_error()) error= true; - if (!error) + if (!error && !suppress_my_ok) { ::my_ok(thd,row_count); } @@ -2481,7 +2840,7 @@ select_to_file::~select_to_file() select_export::~select_export() { - thd->sent_row_count=row_count; + thd->set_sent_row_count(row_count); } @@ -2603,9 +2962,9 @@ select_export::prepare(List<Item> &list, SELECT_LEX_UNIT *u) Non-ASCII separator arguments are not fully supported */ - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED, - ER(WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED)); + ER_THD(thd, WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED)); } field_term_length=exchange->field_term->length(); field_term_char= field_term_length ? @@ -2619,8 +2978,8 @@ select_export::prepare(List<Item> &list, SELECT_LEX_UNIT *u) escape_char= (int) (uchar) (*exchange->escaped)[0]; else escape_char= -1; - is_ambiguous_field_sep= test(strchr(ESCAPE_CHARS, field_sep_char)); - is_unsafe_field_sep= test(strchr(NUMERIC_CHARS, field_sep_char)); + is_ambiguous_field_sep= MY_TEST(strchr(ESCAPE_CHARS, field_sep_char)); + is_unsafe_field_sep= MY_TEST(strchr(NUMERIC_CHARS, field_sep_char)); line_sep_char= (exchange->line_term->length() ? (int) (uchar) (*exchange->line_term)[0] : INT_MAX); if (!field_term_length) @@ -2634,8 +2993,9 @@ select_export::prepare(List<Item> &list, SELECT_LEX_UNIT *u) (exchange->opt_enclosed && non_string_results && field_term_length && strchr(NUMERIC_CHARS, field_term_char))) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_AMBIGUOUS_FIELD_TERM, ER(ER_AMBIGUOUS_FIELD_TERM)); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_AMBIGUOUS_FIELD_TERM, + ER_THD(thd, ER_AMBIGUOUS_FIELD_TERM)); is_ambiguous_field_term= TRUE; } else @@ -2686,9 +3046,7 @@ int select_export::send_data(List<Item> &items) if (res && !my_charset_same(write_cs, res->charset()) && !my_charset_same(write_cs, &my_charset_bin)) { - const char *well_formed_error_pos; - const char *cannot_convert_error_pos; - const char *from_end_pos; + String_copier copier; const char *error_pos; uint32 bytes; uint64 estimated_bytes= @@ -2701,35 +3059,31 @@ int select_export::send_data(List<Item> &items) goto err; } - bytes= well_formed_copy_nchars(write_cs, (char *) cvt_str.ptr(), + bytes= copier.well_formed_copy(write_cs, (char *) cvt_str.ptr(), cvt_str.alloced_length(), - res->charset(), res->ptr(), res->length(), - UINT_MAX32, // copy all input chars, - // i.e. ignore nchars parameter - &well_formed_error_pos, - &cannot_convert_error_pos, - &from_end_pos); - error_pos= well_formed_error_pos ? well_formed_error_pos - : cannot_convert_error_pos; + res->charset(), + res->ptr(), res->length()); + error_pos= copier.most_important_error_pos(); if (error_pos) { char printable_buff[32]; convert_to_printable(printable_buff, sizeof(printable_buff), error_pos, res->ptr() + res->length() - error_pos, res->charset(), 6); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, - ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), "string", printable_buff, item->name, static_cast<long>(row_count)); } - else if (from_end_pos < res->ptr() + res->length()) + else if (copier.source_end_pos() < res->ptr() + res->length()) { /* result is longer than UINT_MAX32 and doesn't fit into String */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_DATA_TRUNCATED, ER(WARN_DATA_TRUNCATED), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_DATA_TRUNCATED, + ER_THD(thd, WARN_DATA_TRUNCATED), item->full_name(), static_cast<long>(row_count)); } cvt_str.length(bytes); @@ -2763,7 +3117,7 @@ int select_export::send_data(List<Item> &items) else { if (fixed_row_size) - used_length=min(res->length(),item->max_length); + used_length=MY_MIN(res->length(),item->max_length); else used_length=res->length(); if ((result_type == STRING_RESULT || is_unsafe_field_sep) && @@ -2929,7 +3283,7 @@ int select_dump::send_data(List<Item> &items) if (row_count++ > 1) { - my_message(ER_TOO_MANY_ROWS, ER(ER_TOO_MANY_ROWS), MYF(0)); + my_message(ER_TOO_MANY_ROWS, ER_THD(thd, ER_TOO_MANY_ROWS), MYF(0)); goto err; } while ((item=li++)) @@ -2952,19 +3306,13 @@ err: } -select_subselect::select_subselect(Item_subselect *item_arg) -{ - item= item_arg; -} - - int select_singlerow_subselect::send_data(List<Item> &items) { DBUG_ENTER("select_singlerow_subselect::send_data"); Item_singlerow_subselect *it= (Item_singlerow_subselect *)item; if (it->assigned()) { - my_message(ER_SUBQUERY_NO_1_ROW, ER(ER_SUBQUERY_NO_1_ROW), + my_message(ER_SUBQUERY_NO_1_ROW, ER_THD(thd, ER_SUBQUERY_NO_1_ROW), MYF(current_thd->lex->ignore ? ME_JUST_WARNING : 0)); DBUG_RETURN(1); } @@ -3009,7 +3357,7 @@ int select_max_min_finder_subselect::send_data(List<Item> &items) { if (!cache) { - cache= Item_cache::get_cache(val_item); + cache= Item_cache::get_cache(thd, val_item); switch (val_item->result_type()) { case REAL_RESULT: op= &select_max_min_finder_subselect::cmp_real; @@ -3025,7 +3373,6 @@ int select_max_min_finder_subselect::send_data(List<Item> &items) break; case ROW_RESULT: case TIME_RESULT: - case IMPOSSIBLE_RESULT: // This case should never be choosen DBUG_ASSERT(0); op= 0; @@ -3137,7 +3484,7 @@ int select_dumpvar::prepare(List<Item> &list, SELECT_LEX_UNIT *u) if (var_list.elements != list.elements) { my_message(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT, - ER(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT), MYF(0)); + ER_THD(thd, ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT), MYF(0)); return 1; } return 0; @@ -3173,6 +3520,7 @@ void Query_arena::free_items() { next= free_list->next; DBUG_ASSERT(free_list != next); + DBUG_PRINT("info", ("free item: 0x%lx", (ulong) free_list)); free_list->delete_self(); } /* Postcondition: free_list is 0 */ @@ -3261,6 +3609,10 @@ void THD::end_statement() } +/* + Start using arena specified by @set. Current arena data will be saved to + *backup. +*/ void THD::set_n_backup_active_arena(Query_arena *set, Query_arena *backup) { DBUG_ENTER("THD::set_n_backup_active_arena"); @@ -3275,6 +3627,12 @@ void THD::set_n_backup_active_arena(Query_arena *set, Query_arena *backup) } +/* + Stop using the temporary arena, and start again using the arena that is + specified in *backup. + The temporary arena is returned back into *set. +*/ + void THD::restore_active_arena(Query_arena *set, Query_arena *backup) { DBUG_ENTER("THD::restore_active_arena"); @@ -3430,11 +3788,13 @@ void Statement_map::erase(Statement *statement) void Statement_map::reset() { /* Must be first, hash_free will reset st_hash.records */ - mysql_mutex_lock(&LOCK_prepared_stmt_count); - DBUG_ASSERT(prepared_stmt_count >= st_hash.records); - prepared_stmt_count-= st_hash.records; - mysql_mutex_unlock(&LOCK_prepared_stmt_count); - + if (st_hash.records) + { + mysql_mutex_lock(&LOCK_prepared_stmt_count); + DBUG_ASSERT(prepared_stmt_count >= st_hash.records); + prepared_stmt_count-= st_hash.records; + mysql_mutex_unlock(&LOCK_prepared_stmt_count); + } my_hash_reset(&names_hash); my_hash_reset(&st_hash); last_found_statement= 0; @@ -3443,16 +3803,24 @@ void Statement_map::reset() Statement_map::~Statement_map() { - /* Must go first, hash_free will reset st_hash.records */ - mysql_mutex_lock(&LOCK_prepared_stmt_count); - DBUG_ASSERT(prepared_stmt_count >= st_hash.records); - prepared_stmt_count-= st_hash.records; - mysql_mutex_unlock(&LOCK_prepared_stmt_count); - + /* Statement_map::reset() should be called prior to destructor. */ + DBUG_ASSERT(!st_hash.records); my_hash_free(&names_hash); my_hash_free(&st_hash); } +bool my_var_user::set(THD *thd, Item *item) +{ + Item_func_set_user_var *suv= new (thd->mem_root) Item_func_set_user_var(thd, name, item); + suv->save_item_result(item); + return suv->fix_fields(thd, 0) || suv->update(); +} + +bool my_var_sp::set(THD *thd, Item *item) +{ + return thd->spcont->set_variable(thd, offset, &item); +} + int select_dumpvar::send_data(List<Item> &items) { List_iterator_fast<my_var> var_li(var_list); @@ -3468,25 +3836,13 @@ int select_dumpvar::send_data(List<Item> &items) } if (row_count++) { - my_message(ER_TOO_MANY_ROWS, ER(ER_TOO_MANY_ROWS), MYF(0)); + my_message(ER_TOO_MANY_ROWS, ER_THD(thd, ER_TOO_MANY_ROWS), MYF(0)); DBUG_RETURN(1); } while ((mv= var_li++) && (item= it++)) { - if (mv->local) - { - if (thd->spcont->set_variable(thd, mv->offset, &item)) - DBUG_RETURN(1); - } - else - { - Item_func_set_user_var *suv= new Item_func_set_user_var(mv->s, item); - suv->save_item_result(item); - if (suv->fix_fields(thd, 0)) - DBUG_RETURN (1); - if (suv->update()) - DBUG_RETURN (1); - } + if (mv->set(thd, item)) + DBUG_RETURN(1); } DBUG_RETURN(thd->is_error()); } @@ -3494,8 +3850,8 @@ int select_dumpvar::send_data(List<Item> &items) bool select_dumpvar::send_eof() { if (! row_count) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA)); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_SP_FETCH_NO_DATA, ER_THD(thd, ER_SP_FETCH_NO_DATA)); /* Don't send EOF if we're in error condition (which implies we've already sent or are sending an error) @@ -3503,11 +3859,14 @@ bool select_dumpvar::send_eof() if (thd->is_error()) return true; - ::my_ok(thd,row_count); + if (!suppress_my_ok) + ::my_ok(thd,row_count); + return 0; } + bool select_materialize_with_stats:: create_result_table(THD *thd_arg, List<Item> *column_types, @@ -3523,7 +3882,7 @@ create_result_table(THD *thd_arg, List<Item> *column_types, if (! (table= create_tmp_table(thd_arg, &tmp_table_param, *column_types, (ORDER*) 0, is_union_distinct, 1, options, HA_POS_ERROR, (char*) table_alias, - keep_row_order))) + !create_table, keep_row_order))) return TRUE; col_stat= (Column_statistics*) table->in_use->alloc(table->s->fields * @@ -3623,32 +3982,38 @@ void TMP_TABLE_PARAM::init() } -void thd_increment_bytes_sent(ulong length) +void thd_increment_bytes_sent(void *thd, ulong length) { - THD *thd=current_thd; + /* thd == 0 when close_connection() calls net_send_error() */ if (likely(thd != 0)) { - /* current_thd == 0 when close_connection() calls net_send_error() */ - thd->status_var.bytes_sent+= length; + ((THD*) thd)->status_var.bytes_sent+= length; } } +my_bool thd_net_is_killed() +{ + THD *thd= current_thd; + return thd && thd->killed ? 1 : 0; +} -void thd_increment_bytes_received(ulong length) + +void thd_increment_bytes_received(void *thd, ulong length) { - current_thd->status_var.bytes_received+= length; + ((THD*) thd)->status_var.bytes_received+= length; } -void thd_increment_net_big_packet_count(ulong length) +void thd_increment_net_big_packet_count(void *thd, ulong length) { - current_thd->status_var.net_big_packet_count+= length; + ((THD*) thd)->status_var.net_big_packet_count+= length; } void THD::set_status_var_init() { - bzero((char*) &status_var, sizeof(status_var)); + bzero((char*) &status_var, offsetof(STATUS_VAR, + last_cleared_system_status_var)); } @@ -3656,7 +4021,7 @@ void Security_context::init() { host= user= ip= external_user= 0; host_or_ip= "connecting host"; - priv_user[0]= priv_host[0]= proxy_user[0]= '\0'; + priv_user[0]= priv_host[0]= proxy_user[0]= priv_role[0]= '\0'; master_access= 0; #ifndef NO_EMBEDDED_ACCESS_CHECKS db_access= NO_ACCESS; @@ -3666,6 +4031,7 @@ void Security_context::init() void Security_context::destroy() { + DBUG_PRINT("info", ("freeing security context")); // If not pointer to constant if (host != my_localhost) { @@ -3853,12 +4219,7 @@ void THD::restore_backup_open_tables_state(Open_tables_backup *backup) #undef thd_killed extern "C" int thd_killed(const MYSQL_THD thd) { - if (!thd) - thd= current_thd; - - if (!(thd->killed & KILL_HARD_BIT)) - return 0; - return thd->killed != 0; + return thd_kill_level(thd) > THD_ABORT_SOFTLY; } #else #error now thd_killed() function can go away @@ -3867,18 +4228,35 @@ extern "C" int thd_killed(const MYSQL_THD thd) /* return thd->killed status to the client, mapped to the API enum thd_kill_levels values. + + @note Since this function is called quite frequently thd_kill_level(NULL) is + forbidden for performance reasons (saves one conditional branch). If your ever + need to call thd_kill_level() when THD is not available, you options are (most + to least preferred): + - try to pass THD through to thd_kill_level() + - add current_thd to some service and use thd_killed(current_thd) + - add thd_killed_current() function to kill statement service + - add if (!thd) thd= current_thd here */ extern "C" enum thd_kill_levels thd_kill_level(const MYSQL_THD thd) { - if (!thd) - thd= current_thd; + DBUG_ASSERT(thd); if (likely(thd->killed == NOT_KILLED)) + { + Apc_target *apc_target= (Apc_target*) &thd->apc_target; + if (unlikely(apc_target->have_apc_requests())) + { + if (thd == current_thd) + apc_target->process_apc_requests(); + } return THD_IS_NOT_KILLED; + } return thd->killed & KILL_HARD_BIT ? THD_ABORT_ASAP : THD_ABORT_SOFTLY; } + /** Send an out-of-band progress report to the client @@ -3886,6 +4264,10 @@ extern "C" enum thd_kill_levels thd_kill_level(const MYSQL_THD thd) however not more often than global.progress_report_time. If global.progress_report_time is 0, then don't send progress reports, but check every second if the value has changed + + We clear any errors that we get from sending the progress packet to + the client as we don't want to set an error without the caller knowing + about it. */ static void thd_send_progress(THD *thd) @@ -3894,7 +4276,7 @@ static void thd_send_progress(THD *thd) ulonglong report_time= my_interval_timer(); if (report_time > thd->progress.next_report_time) { - uint seconds_to_next= max(thd->variables.progress_report_time, + uint seconds_to_next= MY_MAX(thd->variables.progress_report_time, global_system_variables.progress_report_time); if (seconds_to_next == 0) // Turned off seconds_to_next= 1; // Check again after 1 second @@ -3902,8 +4284,12 @@ static void thd_send_progress(THD *thd) thd->progress.next_report_time= (report_time + seconds_to_next * 1000000000ULL); if (global_system_variables.progress_report_time && - thd->variables.progress_report_time) + thd->variables.progress_report_time && !thd->is_error()) + { net_send_progress_packet(thd); + if (thd->is_error()) + thd->clear_error(); + } } } @@ -4010,11 +4396,37 @@ extern "C" unsigned long thd_get_thread_id(const MYSQL_THD thd) return((unsigned long)thd->thread_id); } -extern "C" enum_tx_isolation thd_get_trx_isolation(const MYSQL_THD thd) +/** + Check if THD socket is still connected. + */ +extern "C" int thd_is_connected(MYSQL_THD thd) +{ + return thd->is_connected(); +} + + +extern "C" double thd_rnd(MYSQL_THD thd) { - return thd->tx_isolation; + return my_rnd(&thd->rand); } + +/** + Generate string of printable random characters of requested length. + + @param to[out] Buffer for generation; must be at least length+1 bytes + long; result string is always null-terminated + @param length[in] How many random characters to put in buffer +*/ +extern "C" void thd_create_random_password(MYSQL_THD thd, + char *to, size_t length) +{ + for (char *end= to + length; to < end; to++) + *to= (char) (my_rnd(&thd->rand)*94 + 33); + *to= '\0'; +} + + #ifdef INNODB_COMPATIBILITY_HOOKS extern "C" const struct charset_info_st *thd_charset(MYSQL_THD thd) { @@ -4046,6 +4458,318 @@ extern "C" int thd_slave_thread(const MYSQL_THD thd) return(thd->slave_thread); } +/* Returns true for a worker thread in parallel replication. */ +extern "C" int thd_rpl_is_parallel(const MYSQL_THD thd) +{ + return thd->rgi_slave && thd->rgi_slave->is_parallel_exec; +} + + +/* Returns high resolution timestamp for the start + of the current query. */ +extern "C" unsigned long long thd_start_utime(const MYSQL_THD thd) +{ + return thd->start_utime; +} + + +/* + This function can optionally be called to check if thd_report_wait_for() + needs to be called for waits done by a given transaction. + + If this function returns false for a given thd, there is no need to do any + calls to thd_report_wait_for() on that thd. + + This call is optional; it is safe to call thd_report_wait_for() in any case. + This call can be used to save some redundant calls to thd_report_wait_for() + if desired. (This is unlikely to matter much unless there are _lots_ of + waits to report, as the overhead of thd_report_wait_for() is small). +*/ +extern "C" int +thd_need_wait_for(const MYSQL_THD thd) +{ + rpl_group_info *rgi; + + if (mysql_bin_log.is_open()) + return true; + if (!thd) + return false; + rgi= thd->rgi_slave; + if (!rgi) + return false; + return rgi->is_parallel_exec; +} + +/* + Used by InnoDB/XtraDB to report that one transaction THD is about to go to + wait for a transactional lock held by another transactions OTHER_THD. + + This is used for parallel replication, where transactions are required to + commit in the same order on the slave as they did on the master. If the + transactions on the slave encounters lock conflicts on the slave that did + not exist on the master, this can cause deadlocks. + + Normally, such conflicts will not occur, because the same conflict would + have prevented the two transactions from committing in parallel on the + master, thus preventing them from running in parallel on the slave in the + first place. However, it is possible in case when the optimizer chooses a + different plan on the slave than on the master (eg. table scan instead of + index scan). + + InnoDB/XtraDB reports lock waits using this call. If a lock wait causes a + deadlock with the pre-determined commit order, we kill the later transaction, + and later re-try it, to resolve the deadlock. + + This call need only receive reports about waits for locks that will remain + until the holding transaction commits. InnoDB/XtraDB auto-increment locks + are released earlier, and so need not be reported. (Such false positives are + not harmful, but could lead to unnecessary kill and retry, so best avoided). +*/ +extern "C" void +thd_report_wait_for(MYSQL_THD thd, MYSQL_THD other_thd) +{ + rpl_group_info *rgi; + rpl_group_info *other_rgi; + + if (!thd) + return; + DEBUG_SYNC(thd, "thd_report_wait_for"); + thd->transaction.stmt.mark_trans_did_wait(); + if (!other_thd) + return; + binlog_report_wait_for(thd, other_thd); + rgi= thd->rgi_slave; + other_rgi= other_thd->rgi_slave; + if (!rgi || !other_rgi) + return; + if (!rgi->is_parallel_exec) + return; + if (rgi->rli != other_rgi->rli) + return; + if (!rgi->gtid_sub_id || !other_rgi->gtid_sub_id) + return; + if (rgi->current_gtid.domain_id != other_rgi->current_gtid.domain_id) + return; + if (rgi->gtid_sub_id > other_rgi->gtid_sub_id) + return; + /* + This transaction is about to wait for another transaction that is required + by replication binlog order to commit after. This would cause a deadlock. + + So send a kill to the other transaction, with a temporary error; this will + cause replication to rollback (and later re-try) the other transaction, + releasing the lock for this transaction so replication can proceed. + */ + other_rgi->killed_for_retry= rpl_group_info::RETRY_KILL_KILLED; + mysql_mutex_lock(&other_thd->LOCK_thd_data); + other_thd->awake(KILL_CONNECTION); + mysql_mutex_unlock(&other_thd->LOCK_thd_data); +} + +/* + Used by storage engines (currently TokuDB) to report that one transaction + THD is about to go to wait for a transactional lock held by another + transactions OTHER_THD. + + This is used for parallel replication, where transactions are required to + commit in the same order on the slave as they did on the master. If the + transactions on the slave encounter lock conflicts on the slave that did not + exist on the master, this can cause deadlocks. This is primarily used in + optimistic (and aggressive) modes. + + Normally, such conflicts will not occur in conservative mode, because the + same conflict would have prevented the two transactions from committing in + parallel on the master, thus preventing them from running in parallel on the + slave in the first place. However, it is possible in case when the optimizer + chooses a different plan on the slave than on the master (eg. table scan + instead of index scan). + + InnoDB/XtraDB reports lock waits using this call. If a lock wait causes a + deadlock with the pre-determined commit order, we kill the later transaction, + and later re-try it, to resolve the deadlock. + + This call need only receive reports about waits for locks that will remain + until the holding transaction commits. InnoDB/XtraDB auto-increment locks, + for example, are released earlier, and so need not be reported. (Such false + positives are not harmful, but could lead to unnecessary kill and retry, so + best avoided). + + Returns 1 if the OTHER_THD will be killed to resolve deadlock, 0 if not. The + actual kill will happen later, asynchronously from another thread. The + caller does not need to take any actions on the return value if the + handlerton kill_query method is implemented to abort the to-be-killed + transaction. +*/ +extern "C" int +thd_rpl_deadlock_check(MYSQL_THD thd, MYSQL_THD other_thd) +{ + rpl_group_info *rgi; + rpl_group_info *other_rgi; + + if (!thd) + return 0; + DEBUG_SYNC(thd, "thd_report_wait_for"); + thd->transaction.stmt.mark_trans_did_wait(); + if (!other_thd) + return 0; + binlog_report_wait_for(thd, other_thd); + rgi= thd->rgi_slave; + other_rgi= other_thd->rgi_slave; + if (!rgi || !other_rgi) + return 0; + if (!rgi->is_parallel_exec) + return 0; + if (rgi->rli != other_rgi->rli) + return 0; + if (!rgi->gtid_sub_id || !other_rgi->gtid_sub_id) + return 0; + if (rgi->current_gtid.domain_id != other_rgi->current_gtid.domain_id) + return 0; + if (rgi->gtid_sub_id > other_rgi->gtid_sub_id) + return 0; + /* + This transaction is about to wait for another transaction that is required + by replication binlog order to commit after. This would cause a deadlock. + + So send a kill to the other transaction, with a temporary error; this will + cause replication to rollback (and later re-try) the other transaction, + releasing the lock for this transaction so replication can proceed. + */ +#ifdef HAVE_REPLICATION + slave_background_kill_request(other_thd); +#endif + return 1; +} + +/* + This function is called from InnoDB/XtraDB to check if the commit order of + two transactions has already been decided by the upper layer. This happens + in parallel replication, where the commit order is forced to be the same on + the slave as it was originally on the master. + + If this function returns false, it means that such commit order will be + enforced. This allows the storage engine to optionally omit gap lock waits + or similar measures that would otherwise be needed to ensure that + transactions would be serialised in a way that would cause a commit order + that is correct for binlogging for statement-based replication. + + Since transactions are only run in parallel on the slave if they ran without + lock conflicts on the master, normally no lock conflicts on the slave happen + during parallel replication. However, there are a couple of corner cases + where it can happen, like these secondary-index operations: + + T1: INSERT INTO t1 VALUES (7, NULL); + T2: DELETE FROM t1 WHERE b <= 3; + + T1: UPDATE t1 SET secondary=NULL WHERE primary=1 + T2: DELETE t1 WHERE secondary <= 3 + + The DELETE takes a gap lock that can block the INSERT/UPDATE, but the row + locks set by INSERT/UPDATE do not block the DELETE. Thus, the execution + order of the transactions determine whether a lock conflict occurs or + not. Thus a lock conflict can occur on the slave where it did not on the + master. + + If this function returns true, normal locking should be done as required by + the binlogging and transaction isolation level in effect. But if it returns + false, the correct order will be enforced anyway, and InnoDB/XtraDB can + avoid taking the gap lock, preventing the lock conflict. + + Calling this function is just an optimisation to avoid unnecessary + deadlocks. If it was not used, a gap lock would be set that could eventually + cause a deadlock; the deadlock would be caught by thd_report_wait_for() and + the transaction T2 killed and rolled back (and later re-tried). +*/ +extern "C" int +thd_need_ordering_with(const MYSQL_THD thd, const MYSQL_THD other_thd) +{ + rpl_group_info *rgi, *other_rgi; + + DBUG_EXECUTE_IF("disable_thd_need_ordering_with", return 1;); + if (!thd || !other_thd) + return 1; + rgi= thd->rgi_slave; + other_rgi= other_thd->rgi_slave; + if (!rgi || !other_rgi) + return 1; + if (!rgi->is_parallel_exec) + return 1; + if (rgi->rli != other_rgi->rli) + return 1; + if (rgi->current_gtid.domain_id != other_rgi->current_gtid.domain_id) + return 1; + if (!rgi->commit_id || rgi->commit_id != other_rgi->commit_id) + return 1; + DBUG_EXECUTE_IF("thd_need_ordering_with_force", return 1;); + /* + Otherwise, these two threads are doing parallel replication within the same + replication domain. Their commit order is already fixed, so we do not need + gap locks or similar to otherwise enforce ordering (and in fact such locks + could lead to unnecessary deadlocks and transaction retry). + */ + return 0; +} + + +/* + If the storage engine detects a deadlock, and needs to choose a victim + transaction to roll back, it can call this function to ask the upper + server layer for which of two possible transactions is prefered to be + aborted and rolled back. + + In parallel replication, if two transactions are running in parallel and + one is fixed to commit before the other, then the one that commits later + will be prefered as the victim - chosing the early transaction as a victim + will not resolve the deadlock anyway, as the later transaction still needs + to wait for the earlier to commit. + + Otherwise, a transaction that uses only transactional tables, and can thus + be safely rolled back, will be prefered as a deadlock victim over a + transaction that also modified non-transactional (eg. MyISAM) tables. + + The return value is -1 if the first transaction is prefered as a deadlock + victim, 1 if the second transaction is prefered, or 0 for no preference (in + which case the storage engine can make the choice as it prefers). +*/ +extern "C" int +thd_deadlock_victim_preference(const MYSQL_THD thd1, const MYSQL_THD thd2) +{ + rpl_group_info *rgi1, *rgi2; + bool nontrans1, nontrans2; + + if (!thd1 || !thd2) + return 0; + + /* + If the transactions are participating in the same replication domain in + parallel replication, then request to select the one that will commit + later (in the fixed commit order from the master) as the deadlock victim. + */ + rgi1= thd1->rgi_slave; + rgi2= thd2->rgi_slave; + if (rgi1 && rgi2 && + rgi1->is_parallel_exec && + rgi1->rli == rgi2->rli && + rgi1->current_gtid.domain_id == rgi2->current_gtid.domain_id) + return rgi1->gtid_sub_id < rgi2->gtid_sub_id ? 1 : -1; + + /* + If one transaction has modified non-transactional tables (so that it + cannot be safely rolled back), and the other has not, then prefer to + select the purely transactional one as the victim. + */ + nontrans1= thd1->transaction.all.modified_non_trans_table; + nontrans2= thd2->transaction.all.modified_non_trans_table; + if (nontrans1 && !nontrans2) + return 1; + else if (!nontrans1 && nontrans2) + return -1; + + /* No preferences, let the storage engine decide. */ + return 0; +} + + extern "C" int thd_non_transactional_update(const MYSQL_THD thd) { return(thd->transaction.all.modified_non_trans_table); @@ -4053,10 +4777,14 @@ extern "C" int thd_non_transactional_update(const MYSQL_THD thd) extern "C" int thd_binlog_format(const MYSQL_THD thd) { + if (WSREP(thd)) + { + /* for wsrep binlog format is meaningful also when binlogging is off */ + return (int) thd->wsrep_binlog_format(); + } if (mysql_bin_log.is_open() && (thd->variables.option_bits & OPTION_BIN_LOG)) return (int) thd->variables.binlog_format; - else - return BINLOG_FORMAT_UNSPEC; + return BINLOG_FORMAT_UNSPEC; } extern "C" void thd_mark_transaction_to_rollback(MYSQL_THD thd, bool all) @@ -4070,12 +4798,57 @@ extern "C" bool thd_binlog_filter_ok(const MYSQL_THD thd) return binlog_filter->db_ok(thd->db); } +/* + This is similar to sqlcom_can_generate_row_events, with the expection + that we only return 1 if we are going to generate row events in a + transaction. + CREATE OR REPLACE is always safe to do as this will run in it's own + transaction. +*/ + extern "C" bool thd_sqlcom_can_generate_row_events(const MYSQL_THD thd) { - return sqlcom_can_generate_row_events(thd); + return (sqlcom_can_generate_row_events(thd) && thd->lex->sql_command != + SQLCOM_CREATE_TABLE); +} + + +extern "C" enum durability_properties thd_get_durability_property(const MYSQL_THD thd) +{ + enum durability_properties ret= HA_REGULAR_DURABILITY; + + if (thd != NULL) + ret= thd->durability_property; + + return ret; +} + +/** Get the auto_increment_offset auto_increment_increment. +Exposed by thd_autoinc_service. +Needed by InnoDB. +@param thd Thread object +@param off auto_increment_offset +@param inc auto_increment_increment */ +extern "C" void thd_get_autoinc(const MYSQL_THD thd, ulong* off, ulong* inc) +{ + *off = thd->variables.auto_increment_offset; + *inc = thd->variables.auto_increment_increment; } +/** + Is strict sql_mode set. + Needed by InnoDB. + @param thd Thread object + @return True if sql_mode has strict mode (all or trans). + @retval true sql_mode has strict mode (all or trans). + @retval false sql_mode has not strict mode (all or trans). +*/ +extern "C" bool thd_is_strict_mode(const MYSQL_THD thd) +{ + return thd->is_strict_mode(); +} + /* Interface for MySQL Server, plugins and storage engines to report @@ -4180,8 +4953,8 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, backup->enable_slow_log= enable_slow_log; backup->query_plan_flags= query_plan_flags; backup->limit_found_rows= limit_found_rows; - backup->examined_row_count= examined_row_count; - backup->sent_row_count= sent_row_count; + backup->examined_row_count= m_examined_row_count; + backup->sent_row_count= m_sent_row_count; backup->cuted_fields= cuted_fields; backup->client_capabilities= client_capabilities; backup->savepoints= transaction.savepoints; @@ -4204,8 +4977,8 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, /* Disable result sets */ client_capabilities &= ~CLIENT_MULTI_RESULTS; in_sub_stmt|= new_state; - examined_row_count= 0; - sent_row_count= 0; + m_examined_row_count= 0; + m_sent_row_count= 0; cuted_fields= 0; transaction.savepoints= 0; first_successful_insert_id_in_cur_stmt= 0; @@ -4252,7 +5025,7 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) first_successful_insert_id_in_cur_stmt= backup->first_successful_insert_id_in_cur_stmt; limit_found_rows= backup->limit_found_rows; - sent_row_count= backup->sent_row_count; + set_sent_row_count(backup->sent_row_count); client_capabilities= backup->client_capabilities; /* If we've left sub-statement mode, reset the fatal error flag. @@ -4273,7 +5046,7 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) The following is added to the old values as we are interested in the total complexity of the query */ - examined_row_count+= backup->examined_row_count; + inc_examined_row_count(backup->examined_row_count); cuted_fields+= backup->cuted_fields; DBUG_VOID_RETURN; } @@ -4286,14 +5059,132 @@ void THD::set_statement(Statement *stmt) mysql_mutex_unlock(&LOCK_thd_data); } +void THD::set_sent_row_count(ha_rows count) +{ + m_sent_row_count= count; + MYSQL_SET_STATEMENT_ROWS_SENT(m_statement_psi, m_sent_row_count); +} -/** Assign a new value to thd->query. */ +void THD::set_examined_row_count(ha_rows count) +{ + m_examined_row_count= count; + MYSQL_SET_STATEMENT_ROWS_EXAMINED(m_statement_psi, m_examined_row_count); +} -void THD::set_query(const CSET_STRING &string_arg) +void THD::inc_sent_row_count(ha_rows count) { - mysql_mutex_lock(&LOCK_thd_data); - set_query_inner(string_arg); - mysql_mutex_unlock(&LOCK_thd_data); + m_sent_row_count+= count; + MYSQL_SET_STATEMENT_ROWS_SENT(m_statement_psi, m_sent_row_count); +} + +void THD::inc_examined_row_count(ha_rows count) +{ + m_examined_row_count+= count; + MYSQL_SET_STATEMENT_ROWS_EXAMINED(m_statement_psi, m_examined_row_count); +} + +void THD::inc_status_created_tmp_disk_tables() +{ + status_var_increment(status_var.created_tmp_disk_tables_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_created_tmp_disk_tables)(m_statement_psi, 1); +#endif +} + +void THD::inc_status_created_tmp_tables() +{ + status_var_increment(status_var.created_tmp_tables_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_created_tmp_tables)(m_statement_psi, 1); +#endif +} + +void THD::inc_status_select_full_join() +{ + status_var_increment(status_var.select_full_join_count_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_select_full_join)(m_statement_psi, 1); +#endif +} + +void THD::inc_status_select_full_range_join() +{ + status_var_increment(status_var.select_full_range_join_count_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_select_full_range_join)(m_statement_psi, 1); +#endif +} + +void THD::inc_status_select_range() +{ + status_var_increment(status_var.select_range_count_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_select_range)(m_statement_psi, 1); +#endif +} + +void THD::inc_status_select_range_check() +{ + status_var_increment(status_var.select_range_check_count_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_select_range_check)(m_statement_psi, 1); +#endif +} + +void THD::inc_status_select_scan() +{ + status_var_increment(status_var.select_scan_count_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_select_scan)(m_statement_psi, 1); +#endif +} + +void THD::inc_status_sort_merge_passes() +{ + status_var_increment(status_var.filesort_merge_passes_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_sort_merge_passes)(m_statement_psi, 1); +#endif +} + +void THD::inc_status_sort_range() +{ + status_var_increment(status_var.filesort_range_count_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_sort_range)(m_statement_psi, 1); +#endif +} + +void THD::inc_status_sort_rows(ha_rows count) +{ + statistic_add(status_var.filesort_rows_, count, &LOCK_status); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_sort_rows)(m_statement_psi, count); +#endif +} + +void THD::inc_status_sort_scan() +{ + status_var_increment(status_var.filesort_scan_count_); +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(inc_statement_sort_scan)(m_statement_psi, 1); +#endif +} + +void THD::set_status_no_index_used() +{ + server_status|= SERVER_QUERY_NO_INDEX_USED; +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(set_statement_no_index_used)(m_statement_psi); +#endif +} + +void THD::set_status_no_good_index_used() +{ + server_status|= SERVER_QUERY_NO_GOOD_INDEX_USED; +#ifdef HAVE_PSI_STATEMENT_INTERFACE + PSI_STATEMENT_CALL(set_statement_no_good_index_used)(m_statement_psi); +#endif } /** Assign a new value to thd->query and thd->query_id. */ @@ -4304,17 +5195,8 @@ void THD::set_query_and_id(char *query_arg, uint32 query_length_arg, { mysql_mutex_lock(&LOCK_thd_data); set_query_inner(query_arg, query_length_arg, cs); - query_id= new_query_id; mysql_mutex_unlock(&LOCK_thd_data); -} - -/** Assign a new value to thd->query_id. */ - -void THD::set_query_id(query_id_t new_query_id) -{ - mysql_mutex_lock(&LOCK_thd_data); query_id= new_query_id; - mysql_mutex_unlock(&LOCK_thd_data); } /** Assign a new value to thd->mysys_var. */ @@ -4348,25 +5230,29 @@ void THD::leave_locked_tables_mode() /* Also ensure that we don't release metadata locks for open HANDLERs. */ if (handler_tables_hash.records) mysql_ha_set_explicit_lock_duration(this); + if (ull_hash.records) + mysql_ull_set_explicit_lock_duration(this); } locked_tables_mode= LTM_NONE; } -void THD::get_definer(LEX_USER *definer) +void THD::get_definer(LEX_USER *definer, bool role) { - binlog_invoker(); + binlog_invoker(role); #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) +#ifdef WITH_WSREP + if ((wsrep_applier || slave_thread) && has_invoker()) +#else if (slave_thread && has_invoker()) +#endif { definer->user = invoker_user; definer->host= invoker_host; - definer->password= null_lex_str; - definer->plugin= empty_lex_str; - definer->auth= empty_lex_str; + definer->reset_auth(); } else #endif - get_default_definer(this, definer); + get_default_definer(this, definer, role); } @@ -4389,120 +5275,259 @@ void THD::mark_transaction_to_rollback(bool all) /*************************************************************************** Handling of XA id cacheing ***************************************************************************/ +class XID_cache_element +{ + /* + m_state is used to prevent elements from being deleted while XA RECOVER + iterates xid cache and to prevent recovered elments from being acquired by + multiple threads. -mysql_mutex_t LOCK_xid_cache; -HASH xid_cache; + bits 1..29 are reference counter + bit 30 is RECOVERED flag + bit 31 is ACQUIRED flag (thread owns this xid) + bit 32 is unused -extern "C" uchar *xid_get_hash_key(const uchar *, size_t *, my_bool); -extern "C" void xid_free_hash(void *); + Newly allocated and deleted elements have m_state set to 0. -uchar *xid_get_hash_key(const uchar *ptr, size_t *length, - my_bool not_used __attribute__((unused))) -{ - *length=((XID_STATE*)ptr)->xid.key_length(); - return ((XID_STATE*)ptr)->xid.key(); -} + On lock() m_state is atomically incremented. It also creates load-ACQUIRE + memory barrier to make sure m_state is actually updated before furhter + memory accesses. Attempting to lock an element that has neither ACQUIRED + nor RECOVERED flag set returns failure and further accesses to element + memory are forbidden. -void xid_free_hash(void *ptr) -{ - if (!((XID_STATE*)ptr)->in_thd) - my_free(ptr); -} + On unlock() m_state is decremented. It also creates store-RELEASE memory + barrier to make sure m_state is actually updated after preceding memory + accesses. -#ifdef HAVE_PSI_INTERFACE -static PSI_mutex_key key_LOCK_xid_cache; + ACQUIRED flag is set when thread registers it's xid or when thread acquires + recovered xid. -static PSI_mutex_info all_xid_mutexes[]= -{ - { &key_LOCK_xid_cache, "LOCK_xid_cache", PSI_FLAG_GLOBAL} + RECOVERED flag is set for elements found during crash recovery. + + ACQUIRED and RECOVERED flags are cleared before element is deleted from + hash in a spin loop, after last reference is released. + */ + int32 m_state; +public: + static const int32 ACQUIRED= 1 << 30; + static const int32 RECOVERED= 1 << 29; + XID_STATE *m_xid_state; + bool is_set(int32 flag) + { return my_atomic_load32_explicit(&m_state, MY_MEMORY_ORDER_RELAXED) & flag; } + void set(int32 flag) + { + DBUG_ASSERT(!is_set(ACQUIRED | RECOVERED)); + my_atomic_add32_explicit(&m_state, flag, MY_MEMORY_ORDER_RELAXED); + } + bool lock() + { + int32 old= my_atomic_add32_explicit(&m_state, 1, MY_MEMORY_ORDER_ACQUIRE); + if (old & (ACQUIRED | RECOVERED)) + return true; + unlock(); + return false; + } + void unlock() + { my_atomic_add32_explicit(&m_state, -1, MY_MEMORY_ORDER_RELEASE); } + void mark_uninitialized() + { + int32 old= ACQUIRED; + while (!my_atomic_cas32_weak_explicit(&m_state, &old, 0, + MY_MEMORY_ORDER_RELAXED, + MY_MEMORY_ORDER_RELAXED)) + { + old&= ACQUIRED | RECOVERED; + (void) LF_BACKOFF; + } + } + bool acquire_recovered() + { + int32 old= RECOVERED; + while (!my_atomic_cas32_weak_explicit(&m_state, &old, ACQUIRED | RECOVERED, + MY_MEMORY_ORDER_RELAXED, + MY_MEMORY_ORDER_RELAXED)) + { + if (!(old & RECOVERED) || (old & ACQUIRED)) + return false; + old= RECOVERED; + (void) LF_BACKOFF; + } + return true; + } + static void lf_hash_initializer(LF_HASH *hash __attribute__((unused)), + XID_cache_element *element, + XID_STATE *xid_state) + { + DBUG_ASSERT(!element->is_set(ACQUIRED | RECOVERED)); + element->m_xid_state= xid_state; + xid_state->xid_cache_element= element; + } + static void lf_alloc_constructor(uchar *ptr) + { + XID_cache_element *element= (XID_cache_element*) (ptr + LF_HASH_OVERHEAD); + element->m_state= 0; + } + static void lf_alloc_destructor(uchar *ptr) + { + XID_cache_element *element= (XID_cache_element*) (ptr + LF_HASH_OVERHEAD); + DBUG_ASSERT(!element->is_set(ACQUIRED)); + if (element->is_set(RECOVERED)) + my_free(element->m_xid_state); + } + static uchar *key(const XID_cache_element *element, size_t *length, + my_bool not_used __attribute__((unused))) + { + *length= element->m_xid_state->xid.key_length(); + return element->m_xid_state->xid.key(); + } }; -static void init_xid_psi_keys(void) -{ - const char* category= "sql"; - int count; - if (PSI_server == NULL) - return; +static LF_HASH xid_cache; +static bool xid_cache_inited; - count= array_elements(all_xid_mutexes); - PSI_server->register_mutex(category, all_xid_mutexes, count); -} -#endif /* HAVE_PSI_INTERFACE */ -bool xid_cache_init() +bool THD::fix_xid_hash_pins() { -#ifdef HAVE_PSI_INTERFACE - init_xid_psi_keys(); -#endif + if (!xid_hash_pins) + xid_hash_pins= lf_hash_get_pins(&xid_cache); + return !xid_hash_pins; +} + - mysql_mutex_init(key_LOCK_xid_cache, &LOCK_xid_cache, MY_MUTEX_INIT_FAST); - return my_hash_init(&xid_cache, &my_charset_bin, 100, 0, 0, - xid_get_hash_key, xid_free_hash, 0) != 0; +void xid_cache_init() +{ + xid_cache_inited= true; + lf_hash_init(&xid_cache, sizeof(XID_cache_element), LF_HASH_UNIQUE, 0, 0, + (my_hash_get_key) XID_cache_element::key, &my_charset_bin); + xid_cache.alloc.constructor= XID_cache_element::lf_alloc_constructor; + xid_cache.alloc.destructor= XID_cache_element::lf_alloc_destructor; + xid_cache.initializer= + (lf_hash_initializer) XID_cache_element::lf_hash_initializer; } + void xid_cache_free() { - if (my_hash_inited(&xid_cache)) + if (xid_cache_inited) { - my_hash_free(&xid_cache); - mysql_mutex_destroy(&LOCK_xid_cache); + lf_hash_destroy(&xid_cache); + xid_cache_inited= false; } } -XID_STATE *xid_cache_search(XID *xid) + +/** + Find recovered XA transaction by XID. +*/ + +XID_STATE *xid_cache_search(THD *thd, XID *xid) { - mysql_mutex_lock(&LOCK_xid_cache); - XID_STATE *res=(XID_STATE *)my_hash_search(&xid_cache, xid->key(), - xid->key_length()); - mysql_mutex_unlock(&LOCK_xid_cache); - return res; + XID_STATE *xs= 0; + DBUG_ASSERT(thd->xid_hash_pins); + XID_cache_element *element= + (XID_cache_element*) lf_hash_search(&xid_cache, thd->xid_hash_pins, + xid->key(), xid->key_length()); + if (element) + { + if (element->acquire_recovered()) + xs= element->m_xid_state; + lf_hash_search_unpin(thd->xid_hash_pins); + DEBUG_SYNC(thd, "xa_after_search"); + } + return xs; } bool xid_cache_insert(XID *xid, enum xa_states xa_state) { XID_STATE *xs; - my_bool res; - mysql_mutex_lock(&LOCK_xid_cache); - if (my_hash_search(&xid_cache, xid->key(), xid->key_length())) - res=0; - else if (!(xs=(XID_STATE *)my_malloc(sizeof(*xs), MYF(MY_WME)))) - res=1; - else + LF_PINS *pins; + int res= 1; + + if (!(pins= lf_hash_get_pins(&xid_cache))) + return true; + + if ((xs= (XID_STATE*) my_malloc(sizeof(*xs), MYF(MY_WME)))) { xs->xa_state=xa_state; xs->xid.set(xid); - xs->in_thd=0; xs->rm_error=0; - res=my_hash_insert(&xid_cache, (uchar*)xs); + + if ((res= lf_hash_insert(&xid_cache, pins, xs))) + my_free(xs); + else + xs->xid_cache_element->set(XID_cache_element::RECOVERED); + if (res == 1) + res= 0; } - mysql_mutex_unlock(&LOCK_xid_cache); + lf_hash_put_pins(pins); return res; } -bool xid_cache_insert(XID_STATE *xid_state) +bool xid_cache_insert(THD *thd, XID_STATE *xid_state) { - mysql_mutex_lock(&LOCK_xid_cache); - if (my_hash_search(&xid_cache, xid_state->xid.key(), - xid_state->xid.key_length())) + if (thd->fix_xid_hash_pins()) + return true; + + int res= lf_hash_insert(&xid_cache, thd->xid_hash_pins, xid_state); + switch (res) { - mysql_mutex_unlock(&LOCK_xid_cache); + case 0: + xid_state->xid_cache_element->set(XID_cache_element::ACQUIRED); + break; + case 1: my_error(ER_XAER_DUPID, MYF(0)); - return true; + /* fall through */ + default: + xid_state->xid_cache_element= 0; } - bool res= my_hash_insert(&xid_cache, (uchar*)xid_state); - mysql_mutex_unlock(&LOCK_xid_cache); return res; } -void xid_cache_delete(XID_STATE *xid_state) +void xid_cache_delete(THD *thd, XID_STATE *xid_state) { - mysql_mutex_lock(&LOCK_xid_cache); - my_hash_delete(&xid_cache, (uchar *)xid_state); - mysql_mutex_unlock(&LOCK_xid_cache); + if (xid_state->xid_cache_element) + { + bool recovered= xid_state->xid_cache_element->is_set(XID_cache_element::RECOVERED); + DBUG_ASSERT(thd->xid_hash_pins); + xid_state->xid_cache_element->mark_uninitialized(); + lf_hash_delete(&xid_cache, thd->xid_hash_pins, + xid_state->xid.key(), xid_state->xid.key_length()); + xid_state->xid_cache_element= 0; + if (recovered) + my_free(xid_state); + } +} + + +struct xid_cache_iterate_arg +{ + my_hash_walk_action action; + void *argument; +}; + +static my_bool xid_cache_iterate_callback(XID_cache_element *element, + xid_cache_iterate_arg *arg) +{ + my_bool res= FALSE; + if (element->lock()) + { + res= arg->action(element->m_xid_state, arg->argument); + element->unlock(); + } + return res; +} + +int xid_cache_iterate(THD *thd, my_hash_walk_action action, void *arg) +{ + xid_cache_iterate_arg argument= { action, arg }; + return thd->fix_xid_hash_pins() ? -1 : + lf_hash_iterate(&xid_cache, thd->xid_hash_pins, + (my_hash_walk_action) xid_cache_iterate_callback, + &argument); } /* @@ -4517,6 +5542,9 @@ void xid_cache_delete(XID_STATE *xid_state) Call this function only when you have established the list of all tables which you'll want to update (including stored functions, triggers, views inside your statement). + + Ignore tables prelocked for foreign key cascading actions, as these + actions cannot generate new auto_increment values. */ static bool @@ -4526,6 +5554,7 @@ has_write_table_with_auto_increment(TABLE_LIST *tables) { /* we must do preliminary checks as table->table may be NULL */ if (!table->placeholder() && + table->prelocking_placeholder != TABLE_LIST::FK && table->table->found_next_number_field && (table->lock_type >= TL_WRITE_ALLOW_WRITE)) return 1; @@ -4559,7 +5588,8 @@ has_write_table_with_auto_increment_and_select(TABLE_LIST *tables) for(TABLE_LIST *table= tables; table; table= table->next_global) { if (!table->placeholder() && - (table->lock_type <= TL_READ_NO_INSERT)) + table->lock_type <= TL_READ_NO_INSERT && + table->prelocking_placeholder != TABLE_LIST::FK) { has_select= true; break; @@ -4665,16 +5695,13 @@ has_write_table_auto_increment_not_first_in_pk(TABLE_LIST *tables) BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-logging. - 6. Error: Cannot execute row injection: binlogging impossible since - BINLOG_FORMAT = STATEMENT. - - 7. Warning: Unsafe statement binlogged in statement format since + 6. Warning: Unsafe statement binlogged in statement format since BINLOG_FORMAT = STATEMENT. In addition, we can produce the following error (not depending on the variables of the decision diagram): - 8. Error: Cannot execute statement: binlogging impossible since more + 7. Error: Cannot execute statement: binlogging impossible since more than one engine is involved and at least one engine is self-logging. @@ -4701,13 +5728,15 @@ int THD::decide_logging_format(TABLE_LIST *tables) DBUG_PRINT("info", ("lex->get_stmt_unsafe_flags(): 0x%x", lex->get_stmt_unsafe_flags())); + reset_binlog_local_stmt_filter(); + /* We should not decide logging format if the binlog is closed or binlogging is off, or if the statement is filtered out from the binlog by filtering rules. */ if (mysql_bin_log.is_open() && (variables.option_bits & OPTION_BIN_LOG) && - !(variables.binlog_format == BINLOG_FORMAT_STMT && + !(wsrep_binlog_format() == BINLOG_FORMAT_STMT && !binlog_filter->db_ok(db))) { /* @@ -4743,6 +5772,28 @@ int THD::decide_logging_format(TABLE_LIST *tables) A pointer to a previous table that was accessed. */ TABLE* prev_access_table= NULL; + /** + The number of tables used in the current statement, + that should be replicated. + */ + uint replicated_tables_count= 0; + /** + The number of tables written to in the current statement, + that should not be replicated. + A table should not be replicated when it is considered + 'local' to a MySQL instance. + Currently, these tables are: + - mysql.slow_log + - mysql.general_log + - mysql.slave_relay_log_info + - mysql.slave_master_info + - mysql.slave_worker_info + - performance_schema.* + - TODO: information_schema.* + In practice, from this list, only performance_schema.* tables + are written to by user queries. + */ + uint non_replicated_tables_count= 0; #ifndef DBUG_OFF { @@ -4758,7 +5809,7 @@ int THD::decide_logging_format(TABLE_LIST *tables) } #endif - if (variables.binlog_format != BINLOG_FORMAT_ROW && tables) + if (wsrep_binlog_format() != BINLOG_FORMAT_ROW && tables) { /* DML statements that modify a table with an auto_increment column based on @@ -4792,14 +5843,38 @@ int THD::decide_logging_format(TABLE_LIST *tables) if (table->placeholder()) continue; - if (table->table->s->table_category == TABLE_CATEGORY_PERFORMANCE || - table->table->s->table_category == TABLE_CATEGORY_LOG) - lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_TABLE); - handler::Table_flags const flags= table->table->file->ha_table_flags(); DBUG_PRINT("info", ("table: %s; ha_table_flags: 0x%llx", table->table_name, flags)); + + if (table->table->no_replicate) + { + /* + The statement uses a table that is not replicated. + The following properties about the table: + - persistent / transient + - transactional / non transactional + - temporary / permanent + - read or write + - multiple engines involved because of this table + are not relevant, as this table is completely ignored. + Because the statement uses a non replicated table, + using STATEMENT format in the binlog is impossible. + Either this statement will be discarded entirely, + or it will be logged (possibly partially) in ROW format. + */ + lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_TABLE); + + if (table->lock_type >= TL_WRITE_ALLOW_WRITE) + { + non_replicated_tables_count++; + continue; + } + } + + replicated_tables_count++; + if (table->lock_type >= TL_WRITE_ALLOW_WRITE) { if (prev_write_table && prev_write_table->file->ht != @@ -4821,32 +5896,11 @@ int THD::decide_logging_format(TABLE_LIST *tables) prev_write_table= table->table; - /* - INSERT...ON DUPLICATE KEY UPDATE on a table with more than one unique keys - can be unsafe. Check for it if the flag is already not marked for the - given statement. - */ - if (!lex->is_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_TWO_KEYS) && - lex->sql_command == SQLCOM_INSERT && - /* Duplicate key update is not supported by INSERT DELAYED */ - command != COM_DELAYED_INSERT && lex->duplicates == DUP_UPDATE) - { - uint keys= table->table->s->keys, i= 0, unique_keys= 0; - for (KEY* keyinfo= table->table->s->key_info; - i < keys && unique_keys <= 1; i++, keyinfo++) - { - if (keyinfo->flags & HA_NOSAME) - unique_keys++; - } - if (unique_keys > 1 ) - lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_TWO_KEYS); - } } flags_access_some_set |= flags; if (lex->sql_command != SQLCOM_CREATE_TABLE || - (lex->sql_command == SQLCOM_CREATE_TABLE && - (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))) + (lex->sql_command == SQLCOM_CREATE_TABLE && lex->tmp_table())) { my_bool trans= table->table->file->has_transactions(); @@ -4918,7 +5972,7 @@ int THD::decide_logging_format(TABLE_LIST *tables) */ my_error((error= ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE), MYF(0)); } - else if (variables.binlog_format == BINLOG_FORMAT_ROW && + else if (wsrep_binlog_format() == BINLOG_FORMAT_ROW && sqlcom_can_generate_row_events(this)) { /* @@ -4939,7 +5993,8 @@ int THD::decide_logging_format(TABLE_LIST *tables) unsafe_type++) if (unsafe_flags & (1 << unsafe_type)) my_error((error= ER_BINLOG_UNSAFE_AND_STMT_ENGINE), MYF(0), - ER(LEX::binlog_stmt_unsafe_errcode[unsafe_type])); + ER_THD(this, + LEX::binlog_stmt_unsafe_errcode[unsafe_type])); } /* log in statement format! */ } @@ -4947,15 +6002,15 @@ int THD::decide_logging_format(TABLE_LIST *tables) else { /* binlog_format = STATEMENT */ - if (variables.binlog_format == BINLOG_FORMAT_STMT) + if (wsrep_binlog_format() == BINLOG_FORMAT_STMT) { if (lex->is_stmt_row_injection()) { /* - 6. Error: Cannot execute row injection since - BINLOG_FORMAT = STATEMENT + We have to log the statement as row or give an error. + Better to accept what master gives us than stopping replication. */ - my_error((error= ER_BINLOG_ROW_INJECTION_AND_STMT_MODE), MYF(0)); + set_current_stmt_binlog_format_row(); } else if ((flags_write_all_set & HA_BINLOG_STMT_CAPABLE) == 0 && sqlcom_can_generate_row_events(this)) @@ -4964,7 +6019,10 @@ int THD::decide_logging_format(TABLE_LIST *tables) 5. Error: Cannot modify table that uses a storage engine limited to row-logging when binlog_format = STATEMENT */ - my_error((error= ER_BINLOG_STMT_MODE_AND_ROW_ENGINE), MYF(0), ""); + if (IF_WSREP((!WSREP(this) || wsrep_exec_mode == LOCAL_STATE),1)) + { + my_error((error= ER_BINLOG_STMT_MODE_AND_ROW_ENGINE), MYF(0), ""); + } } else if (is_write && (unsafe_flags= lex->get_stmt_unsafe_flags()) != 0) { @@ -4976,11 +6034,11 @@ int THD::decide_logging_format(TABLE_LIST *tables) DBUG_PRINT("info", ("Scheduling warning to be issued by " "binlog_query: '%s'", - ER(ER_BINLOG_UNSAFE_STATEMENT))); + ER_THD(this, ER_BINLOG_UNSAFE_STATEMENT))); DBUG_PRINT("info", ("binlog_unsafe_warning_flags: 0x%x", binlog_unsafe_warning_flags)); } - /* log in statement format! */ + /* log in statement format (or row if row event)! */ } /* No statement-only engines and binlog_format != STATEMENT. I.e., nothing prevents us from row logging if needed. */ @@ -4995,6 +6053,30 @@ int THD::decide_logging_format(TABLE_LIST *tables) } } + if (non_replicated_tables_count > 0) + { + if ((replicated_tables_count == 0) || ! is_write) + { + DBUG_PRINT("info", ("decision: no logging, no replicated table affected")); + set_binlog_local_stmt_filter(); + } + else + { + if (! is_current_stmt_binlog_format_row()) + { + my_error((error= ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES), MYF(0)); + } + else + { + clear_binlog_local_stmt_filter(); + } + } + } + else + { + clear_binlog_local_stmt_filter(); + } + if (error) { DBUG_PRINT("info", ("decision: no logging since an error was generated")); DBUG_RETURN(-1); @@ -5033,7 +6115,7 @@ int THD::decide_logging_format(TABLE_LIST *tables) Replace the last ',' with '.' for table_names */ table_names.replace(table_names.length()-1, 1, ".", 1); - push_warning_printf(this, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(this, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Row events are not logged for %s statements " "that modify BLACKHOLE tables in row format. " @@ -5048,11 +6130,11 @@ int THD::decide_logging_format(TABLE_LIST *tables) DBUG_PRINT("info", ("decision: no logging since " "mysql_bin_log.is_open() = %d " "and (options & OPTION_BIN_LOG) = 0x%llx " - "and binlog_format = %lu " + "and binlog_format = %u " "and binlog_filter->db_ok(db) = %d", mysql_bin_log.is_open(), (variables.option_bits & OPTION_BIN_LOG), - variables.binlog_format, + (uint) wsrep_binlog_format(), binlog_filter->db_ok(db))); #endif @@ -5089,20 +6171,22 @@ int THD::decide_logging_format(TABLE_LIST *tables) If error, NULL. */ -template <class RowsEventT> Rows_log_event* +template <class RowsEventT> Rows_log_event* THD::binlog_prepare_pending_rows_event(TABLE* table, uint32 serv_id, - MY_BITMAP const* cols, - size_t colcnt, size_t needed, bool is_transactional, - RowsEventT *hint __attribute__((unused))) + RowsEventT *hint __attribute__((unused))) { DBUG_ENTER("binlog_prepare_pending_rows_event"); /* Pre-conditions */ DBUG_ASSERT(table->s->table_map_id != ~0UL); /* Fetch the type code for the RowsEventT template parameter */ - int const type_code= RowsEventT::TYPE_CODE; + int const general_type_code= RowsEventT::TYPE_CODE; + + /* Ensure that all events in a GTID group are in the same cache */ + if (variables.option_bits & OPTION_GTID_BEGIN) + is_transactional= 1; /* There is no good place to set up the transactional data, so we @@ -5127,16 +6211,15 @@ THD::binlog_prepare_pending_rows_event(TABLE* table, uint32 serv_id, event. */ if (!pending || - pending->server_id != serv_id || + pending->server_id != serv_id || pending->get_table_id() != table->s->table_map_id || - pending->get_type_code() != type_code || - pending->get_data_size() + needed > opt_binlog_rows_event_max_size || - pending->get_width() != colcnt || - !bitmap_cmp(pending->get_cols(), cols)) + pending->get_general_type_code() != general_type_code || + pending->get_data_size() + needed > opt_binlog_rows_event_max_size || + pending->read_write_bitmaps_cmp(table) == FALSE) { /* Create a new RowsEventT... */ Rows_log_event* const - ev= new RowsEventT(this, table, table->s->table_map_id, cols, + ev= new RowsEventT(this, table, table->s->table_map_id, is_transactional); if (unlikely(!ev)) DBUG_RETURN(NULL); @@ -5158,27 +6241,6 @@ THD::binlog_prepare_pending_rows_event(TABLE* table, uint32 serv_id, DBUG_RETURN(pending); /* This is the current pending event */ } -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -/* - Instantiate the versions we need, we have -fno-implicit-template as - compiling option. -*/ -template Rows_log_event* -THD::binlog_prepare_pending_rows_event(TABLE*, uint32, MY_BITMAP const*, - size_t, size_t, bool, - Write_rows_log_event*); - -template Rows_log_event* -THD::binlog_prepare_pending_rows_event(TABLE*, uint32, MY_BITMAP const*, - size_t colcnt, size_t, bool, - Delete_rows_log_event *); - -template Rows_log_event* -THD::binlog_prepare_pending_rows_event(TABLE*, uint32, MY_BITMAP const*, - size_t colcnt, size_t, bool, - Update_rows_log_event *); -#endif - /* Declare in unnamed namespace. */ CPP_UNNAMED_NS_START /** @@ -5302,12 +6364,12 @@ CPP_UNNAMED_NS_START CPP_UNNAMED_NS_END -int THD::binlog_write_row(TABLE* table, bool is_trans, - MY_BITMAP const* cols, size_t colcnt, - uchar const *record) -{ - DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open()); +int THD::binlog_write_row(TABLE* table, bool is_trans, + uchar const *record) +{ + DBUG_ASSERT(is_current_stmt_binlog_format_row() && + ((WSREP(this) && wsrep_emulate_bin_log) || mysql_bin_log.is_open())); /* Pack records into format for transfer. We are allocating more memory than needed, but that doesn't matter. @@ -5318,10 +6380,14 @@ int THD::binlog_write_row(TABLE* table, bool is_trans, uchar *row_data= memory.slot(0); - size_t const len= pack_row(table, cols, row_data, record); + size_t const len= pack_row(table, table->rpl_write_set, row_data, record); + + /* Ensure that all events in a GTID group are in the same cache */ + if (variables.option_bits & OPTION_GTID_BEGIN) + is_trans= 1; Rows_log_event* const ev= - binlog_prepare_pending_rows_event(table, server_id, cols, colcnt, + binlog_prepare_pending_rows_event(table, variables.server_id, len, is_trans, static_cast<Write_rows_log_event*>(0)); @@ -5332,11 +6398,27 @@ int THD::binlog_write_row(TABLE* table, bool is_trans, } int THD::binlog_update_row(TABLE* table, bool is_trans, - MY_BITMAP const* cols, size_t colcnt, const uchar *before_record, const uchar *after_record) -{ - DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open()); +{ + DBUG_ASSERT(is_current_stmt_binlog_format_row() && + ((WSREP(this) && wsrep_emulate_bin_log) || mysql_bin_log.is_open())); + + /** + Save a reference to the original read bitmaps + We will need this to restore the bitmaps at the end as + binlog_prepare_row_images() may change table->read_set. + table->read_set is used by pack_row and deep in + binlog_prepare_pending_events(). + */ + MY_BITMAP *old_read_set= table->read_set; + + /** + This will remove spurious fields required during execution but + not needed for binlogging. This is done according to the: + binlog-row-image option. + */ + binlog_prepare_row_images(table); size_t const before_maxlen = max_row_length(table, before_record); size_t const after_maxlen = max_row_length(table, after_record); @@ -5348,10 +6430,14 @@ int THD::binlog_update_row(TABLE* table, bool is_trans, uchar *before_row= row_data.slot(0); uchar *after_row= row_data.slot(1); - size_t const before_size= pack_row(table, cols, before_row, - before_record); - size_t const after_size= pack_row(table, cols, after_row, - after_record); + size_t const before_size= pack_row(table, table->read_set, before_row, + before_record); + size_t const after_size= pack_row(table, table->rpl_write_set, after_row, + after_record); + + /* Ensure that all events in a GTID group are in the same cache */ + if (variables.option_bits & OPTION_GTID_BEGIN) + is_trans= 1; /* Don't print debug messages when running valgrind since they can @@ -5365,25 +6451,45 @@ int THD::binlog_update_row(TABLE* table, bool is_trans, #endif Rows_log_event* const ev= - binlog_prepare_pending_rows_event(table, server_id, cols, colcnt, - before_size + after_size, is_trans, - static_cast<Update_rows_log_event*>(0)); + binlog_prepare_pending_rows_event(table, variables.server_id, + before_size + after_size, is_trans, + static_cast<Update_rows_log_event*>(0)); if (unlikely(ev == 0)) return HA_ERR_OUT_OF_MEM; - return - ev->add_row_data(before_row, before_size) || - ev->add_row_data(after_row, after_size); + int error= ev->add_row_data(before_row, before_size) || + ev->add_row_data(after_row, after_size); + + /* restore read set for the rest of execution */ + table->column_bitmaps_set_no_signal(old_read_set, + table->write_set); + return error; + } int THD::binlog_delete_row(TABLE* table, bool is_trans, - MY_BITMAP const* cols, size_t colcnt, uchar const *record) -{ - DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open()); +{ + DBUG_ASSERT(is_current_stmt_binlog_format_row() && + ((WSREP(this) && wsrep_emulate_bin_log) || mysql_bin_log.is_open())); + /** + Save a reference to the original read bitmaps + We will need this to restore the bitmaps at the end as + binlog_prepare_row_images() may change table->read_set. + table->read_set is used by pack_row and deep in + binlog_prepare_pending_events(). + */ + MY_BITMAP *old_read_set= table->read_set; - /* + /** + This will remove spurious fields required during execution but + not needed for binlogging. This is done according to the: + binlog-row-image option. + */ + binlog_prepare_row_images(table); + + /* Pack records into format for transfer. We are allocating more memory than needed, but that doesn't matter. */ @@ -5393,28 +6499,108 @@ int THD::binlog_delete_row(TABLE* table, bool is_trans, uchar *row_data= memory.slot(0); - size_t const len= pack_row(table, cols, row_data, record); + DBUG_DUMP("table->read_set", (uchar*) table->read_set->bitmap, (table->s->fields + 7) / 8); + size_t const len= pack_row(table, table->read_set, row_data, record); + + /* Ensure that all events in a GTID group are in the same cache */ + if (variables.option_bits & OPTION_GTID_BEGIN) + is_trans= 1; Rows_log_event* const ev= - binlog_prepare_pending_rows_event(table, server_id, cols, colcnt, - len, is_trans, - static_cast<Delete_rows_log_event*>(0)); + binlog_prepare_pending_rows_event(table, variables.server_id, + len, is_trans, + static_cast<Delete_rows_log_event*>(0)); if (unlikely(ev == 0)) return HA_ERR_OUT_OF_MEM; - return ev->add_row_data(row_data, len); + + int error= ev->add_row_data(row_data, len); + + /* restore read set for the rest of execution */ + table->column_bitmaps_set_no_signal(old_read_set, + table->write_set); + + return error; +} + + +void THD::binlog_prepare_row_images(TABLE *table) +{ + DBUG_ENTER("THD::binlog_prepare_row_images"); + /** + Remove from read_set spurious columns. The write_set has been + handled before in table->mark_columns_needed_for_update. + */ + + DBUG_PRINT_BITSET("debug", "table->read_set (before preparing): %s", table->read_set); + THD *thd= table->in_use; + + /** + if there is a primary key in the table (ie, user declared PK or a + non-null unique index) and we dont want to ship the entire image, + and the handler involved supports this. + */ + if (table->s->primary_key < MAX_KEY && + (thd->variables.binlog_row_image < BINLOG_ROW_IMAGE_FULL) && + !ha_check_storage_engine_flag(table->s->db_type(), HTON_NO_BINLOG_ROW_OPT)) + { + /** + Just to be sure that tmp_set is currently not in use as + the read_set already. + */ + DBUG_ASSERT(table->read_set != &table->tmp_set); + + bitmap_clear_all(&table->tmp_set); + + switch(thd->variables.binlog_row_image) + { + case BINLOG_ROW_IMAGE_MINIMAL: + /* MINIMAL: Mark only PK */ + table->mark_columns_used_by_index_no_reset(table->s->primary_key, + &table->tmp_set); + break; + case BINLOG_ROW_IMAGE_NOBLOB: + /** + NOBLOB: Remove unnecessary BLOB fields from read_set + (the ones that are not part of PK). + */ + bitmap_union(&table->tmp_set, table->read_set); + for (Field **ptr=table->field ; *ptr ; ptr++) + { + Field *field= (*ptr); + if ((field->type() == MYSQL_TYPE_BLOB) && + !(field->flags & PRI_KEY_FLAG)) + bitmap_clear_bit(&table->tmp_set, field->field_index); + } + break; + default: + DBUG_ASSERT(0); // impossible. + } + + /* set the temporary read_set */ + table->column_bitmaps_set_no_signal(&table->tmp_set, + table->write_set); + } + + DBUG_PRINT_BITSET("debug", "table->read_set (after preparing): %s", table->read_set); + DBUG_VOID_RETURN; } + int THD::binlog_remove_pending_rows_event(bool clear_maps, bool is_transactional) { DBUG_ENTER("THD::binlog_remove_pending_rows_event"); - if (!mysql_bin_log.is_open()) + if(!WSREP_EMULATE_BINLOG(this) && !mysql_bin_log.is_open()) DBUG_RETURN(0); + /* Ensure that all events in a GTID group are in the same cache */ + if (variables.option_bits & OPTION_GTID_BEGIN) + is_transactional= 1; + mysql_bin_log.remove_pending_rows_event(this, is_transactional); if (clear_maps) @@ -5431,9 +6617,13 @@ int THD::binlog_flush_pending_rows_event(bool stmt_end, bool is_transactional) mode: it might be the case that we left row-based mode before flushing anything (e.g., if we have explicitly locked tables). */ - if (!mysql_bin_log.is_open()) + if(!WSREP_EMULATE_BINLOG(this) && !mysql_bin_log.is_open()) DBUG_RETURN(0); + /* Ensure that all events in a GTID group are in the same cache */ + if (variables.option_bits & OPTION_GTID_BEGIN) + is_transactional= 1; + /* Mark the event as the last event of a statement if the stmt_end flag is set. @@ -5478,129 +6668,140 @@ show_query_type(THD::enum_binlog_query_type qtype) Constants required for the limit unsafe warnings suppression */ //seconds after which the limit unsafe warnings suppression will be activated -#define LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT 50 +#define LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT 5*60 //number of limit unsafe warnings after which the suppression will be activated -#define LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT 50 +#define LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT 10 -static ulonglong limit_unsafe_suppression_start_time= 0; -static bool unsafe_warning_suppression_is_activated= false; -static int limit_unsafe_warning_count= 0; +static ulonglong unsafe_suppression_start_time= 0; +static bool unsafe_warning_suppression_active[LEX::BINLOG_STMT_UNSAFE_COUNT]; +static ulong unsafe_warnings_count[LEX::BINLOG_STMT_UNSAFE_COUNT]; +static ulong total_unsafe_warnings_count; /** Auxiliary function to reset the limit unsafety warning suppression. + This is done without mutex protection, but this should be good + enough as it doesn't matter if we loose a couple of suppressed + messages or if this is called multiple times. */ -static void reset_binlog_unsafe_suppression() + +static void reset_binlog_unsafe_suppression(ulonglong now) { + uint i; DBUG_ENTER("reset_binlog_unsafe_suppression"); - unsafe_warning_suppression_is_activated= false; - limit_unsafe_warning_count= 0; - limit_unsafe_suppression_start_time= my_interval_timer()/10000000; + + unsafe_suppression_start_time= now; + total_unsafe_warnings_count= 0; + + for (i= 0 ; i < LEX::BINLOG_STMT_UNSAFE_COUNT ; i++) + { + unsafe_warnings_count[i]= 0; + unsafe_warning_suppression_active[i]= 0; + } DBUG_VOID_RETURN; } /** Auxiliary function to print warning in the error log. */ -static void print_unsafe_warning_to_log(int unsafe_type, char* buf, - char* query) +static void print_unsafe_warning_to_log(THD *thd, int unsafe_type, char* buf, + char* query) { DBUG_ENTER("print_unsafe_warning_in_log"); - sprintf(buf, ER(ER_BINLOG_UNSAFE_STATEMENT), - ER(LEX::binlog_stmt_unsafe_errcode[unsafe_type])); - sql_print_warning(ER(ER_MESSAGE_AND_STATEMENT), buf, query); + sprintf(buf, ER_THD(thd, ER_BINLOG_UNSAFE_STATEMENT), + ER_THD(thd, LEX::binlog_stmt_unsafe_errcode[unsafe_type])); + sql_print_warning(ER_THD(thd, ER_MESSAGE_AND_STATEMENT), buf, query); DBUG_VOID_RETURN; } /** - Auxiliary function to check if the warning for limit unsafety should be - thrown or suppressed. Details of the implementation can be found in the - comments inline. + Auxiliary function to check if the warning for unsafe repliction statements + should be thrown or suppressed. + + Logic is: + - If we get more than LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT errors + of one type, that type of errors will be suppressed for + LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT. + - When the time limit has been reached, all suppression is reset. + + This means that if one gets many different types of errors, some of them + may be reset less than LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT. However at + least one error is disable for this time. + SYNOPSIS: @params - buf - buffer to hold the warning message text unsafe_type - The type of unsafety. - query - The actual query statement. - TODO: Remove this function and implement a general service for all warnings - that would prevent flooding the error log. + RETURN: + 0 0k to log + 1 Message suppressed */ -static void do_unsafe_limit_checkout(char* buf, int unsafe_type, char* query) + +static bool protect_against_unsafe_warning_flood(int unsafe_type) { - ulonglong now= 0; - DBUG_ENTER("do_unsafe_limit_checkout"); - DBUG_ASSERT(unsafe_type == LEX::BINLOG_STMT_UNSAFE_LIMIT); - limit_unsafe_warning_count++; + ulong count; + ulonglong now= my_interval_timer()/1000000000ULL; + DBUG_ENTER("protect_against_unsafe_warning_flood"); + + count= ++unsafe_warnings_count[unsafe_type]; + total_unsafe_warnings_count++; + /* INITIALIZING: If this is the first time this function is called with log warning enabled, the monitoring the unsafe warnings should start. */ - if (limit_unsafe_suppression_start_time == 0) + if (unsafe_suppression_start_time == 0) { - limit_unsafe_suppression_start_time= my_interval_timer()/10000000; - print_unsafe_warning_to_log(unsafe_type, buf, query); + reset_binlog_unsafe_suppression(now); + DBUG_RETURN(0); } - else + + /* + The following is true if we got too many errors or if the error was + already suppressed + */ + if (count >= LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT) { - if (!unsafe_warning_suppression_is_activated) - print_unsafe_warning_to_log(unsafe_type, buf, query); + ulonglong diff_time= (now - unsafe_suppression_start_time); - if (limit_unsafe_warning_count >= - LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT) + if (!unsafe_warning_suppression_active[unsafe_type]) { - now= my_interval_timer()/10000000; - if (!unsafe_warning_suppression_is_activated) + /* + ACTIVATION: + We got LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT warnings in + less than LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT we activate the + suppression. + */ + if (diff_time <= LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT) { - /* - ACTIVATION: - We got LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT warnings in - less than LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT we activate the - suppression. - */ - if ((now-limit_unsafe_suppression_start_time) <= - LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT) - { - unsafe_warning_suppression_is_activated= true; - DBUG_PRINT("info",("A warning flood has been detected and the limit \ -unsafety warning suppression has been activated.")); - } - else - { - /* - there is no flooding till now, therefore we restart the monitoring - */ - limit_unsafe_suppression_start_time= my_interval_timer()/10000000; - limit_unsafe_warning_count= 0; - } + unsafe_warning_suppression_active[unsafe_type]= 1; + sql_print_information("Suppressing warnings of type '%s' for up to %d seconds because of flooding", + ER(LEX::binlog_stmt_unsafe_errcode[unsafe_type]), + LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT); } else { /* - Print the suppression note and the unsafe warning. + There is no flooding till now, therefore we restart the monitoring */ - sql_print_information("The following warning was suppressed %d times \ -during the last %d seconds in the error log", - limit_unsafe_warning_count, - (int) - (now-limit_unsafe_suppression_start_time)); - print_unsafe_warning_to_log(unsafe_type, buf, query); - /* - DEACTIVATION: We got LIMIT_UNSAFE_WARNING_ACTIVATION_THRESHOLD_COUNT - warnings in more than LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT, the - suppression should be deactivated. - */ - if ((now - limit_unsafe_suppression_start_time) > - LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT) - { - reset_binlog_unsafe_suppression(); - DBUG_PRINT("info",("The limit unsafety warning supression has been \ -deactivated")); - } + reset_binlog_unsafe_suppression(now); + } + } + else + { + /* This type of warnings was suppressed */ + if (diff_time > LIMIT_UNSAFE_WARNING_ACTIVATION_TIMEOUT) + { + ulong save_count= total_unsafe_warnings_count; + /* Print a suppression note and remove the suppression */ + reset_binlog_unsafe_suppression(now); + sql_print_information("Suppressed %lu unsafe warnings during " + "the last %d seconds", + save_count, (int) diff_time); } - limit_unsafe_warning_count= 0; } } - DBUG_VOID_RETURN; + DBUG_RETURN(unsafe_warning_suppression_active[unsafe_type]); } /** @@ -5612,6 +6813,7 @@ deactivated")); void THD::issue_unsafe_warnings() { char buf[MYSQL_ERRMSG_SIZE * 2]; + uint32 unsafe_type_flags; DBUG_ENTER("issue_unsafe_warnings"); /* Ensure that binlog_unsafe_warning_flags is big enough to hold all @@ -5619,8 +6821,10 @@ void THD::issue_unsafe_warnings() */ DBUG_ASSERT(LEX::BINLOG_STMT_UNSAFE_COUNT <= sizeof(binlog_unsafe_warning_flags) * CHAR_BIT); + + if (!(unsafe_type_flags= binlog_unsafe_warning_flags)) + DBUG_VOID_RETURN; // Nothing to do - uint32 unsafe_type_flags= binlog_unsafe_warning_flags; /* For each unsafe_type, check if the statement is unsafe in this way and issue a warning. @@ -5631,17 +6835,13 @@ void THD::issue_unsafe_warnings() { if ((unsafe_type_flags & (1 << unsafe_type)) != 0) { - push_warning_printf(this, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(this, Sql_condition::WARN_LEVEL_NOTE, ER_BINLOG_UNSAFE_STATEMENT, - ER(ER_BINLOG_UNSAFE_STATEMENT), - ER(LEX::binlog_stmt_unsafe_errcode[unsafe_type])); - if (global_system_variables.log_warnings) - { - if (unsafe_type == LEX::BINLOG_STMT_UNSAFE_LIMIT) - do_unsafe_limit_checkout( buf, unsafe_type, query()); - else //cases other than LIMIT unsafety - print_unsafe_warning_to_log(unsafe_type, buf, query()); - } + ER_THD(this, ER_BINLOG_UNSAFE_STATEMENT), + ER_THD(this, LEX::binlog_stmt_unsafe_errcode[unsafe_type])); + if (global_system_variables.log_warnings > 0 && + !protect_against_unsafe_warning_flood(unsafe_type)) + print_unsafe_warning_to_log(this, unsafe_type, buf, query()); } } DBUG_VOID_RETURN; @@ -5679,7 +6879,26 @@ int THD::binlog_query(THD::enum_binlog_query_type qtype, char const *query_arg, DBUG_ENTER("THD::binlog_query"); DBUG_PRINT("enter", ("qtype: %s query: '%-.*s'", show_query_type(qtype), (int) query_len, query_arg)); - DBUG_ASSERT(query_arg && mysql_bin_log.is_open()); + + DBUG_ASSERT(query_arg); + DBUG_ASSERT(WSREP_EMULATE_BINLOG(this) || mysql_bin_log.is_open()); + + /* If this is withing a BEGIN ... COMMIT group, don't log it */ + if (variables.option_bits & OPTION_GTID_BEGIN) + { + direct= 0; + is_trans= 1; + } + DBUG_PRINT("info", ("is_trans: %d direct: %d", is_trans, direct)); + + if (get_binlog_local_stmt_filter() == BINLOG_FILTER_SET) + { + /* + The current statement is to be ignored, and not written to + the binlog. Do not call issue_unsafe_warnings(). + */ + DBUG_RETURN(0); + } /* If we are not in prelocked mode, mysql_unlock_tables() will be @@ -5792,6 +7011,349 @@ THD::signal_wakeup_ready() } +void THD::rgi_lock_temporary_tables() +{ + mysql_mutex_lock(&rgi_slave->rli->data_lock); + temporary_tables= rgi_slave->rli->save_temporary_tables; +} + +void THD::rgi_unlock_temporary_tables(bool clear) +{ + rgi_slave->rli->save_temporary_tables= temporary_tables; + mysql_mutex_unlock(&rgi_slave->rli->data_lock); + if (clear) + { + /* + Temporary tables are shared with other by sql execution threads. + As a safety messure, clear the pointer to the common area. + */ + temporary_tables= 0; + } +} + +bool THD::rgi_have_temporary_tables() +{ + return rgi_slave->rli->save_temporary_tables != 0; +} + + +void +wait_for_commit::reinit() +{ + subsequent_commits_list= NULL; + next_subsequent_commit= NULL; + waitee= NULL; + opaque_pointer= NULL; + wakeup_error= 0; + wakeup_subsequent_commits_running= false; + commit_started= false; +#ifdef SAFE_MUTEX + /* + When using SAFE_MUTEX, the ordering between taking the LOCK_wait_commit + mutexes is checked. This causes a problem when we re-use a mutex, as then + the expected locking order may change. + + So in this case, do a re-init of the mutex. In release builds, we want to + avoid the overhead of a re-init though. + + To ensure that no one is locking the mutex, we take a lock of it first. + For full explanation, see wait_for_commit::~wait_for_commit() + */ + mysql_mutex_lock(&LOCK_wait_commit); + mysql_mutex_unlock(&LOCK_wait_commit); + + mysql_mutex_destroy(&LOCK_wait_commit); + mysql_mutex_init(key_LOCK_wait_commit, &LOCK_wait_commit, MY_MUTEX_INIT_FAST); +#endif +} + + +wait_for_commit::wait_for_commit() +{ + mysql_mutex_init(key_LOCK_wait_commit, &LOCK_wait_commit, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_wait_commit, &COND_wait_commit, 0); + reinit(); +} + + +wait_for_commit::~wait_for_commit() +{ + /* + Since we do a dirty read of the waiting_for_commit flag in + wait_for_prior_commit() and in unregister_wait_for_prior_commit(), we need + to take extra care before freeing the wait_for_commit object. + + It is possible for the waitee to be pre-empted inside wakeup(), just after + it has cleared the waiting_for_commit flag and before it has released the + LOCK_wait_commit mutex. And then it is possible for the waiter to find the + flag cleared in wait_for_prior_commit() and go finish up things and + de-allocate the LOCK_wait_commit and COND_wait_commit objects before the + waitee has time to be re-scheduled and finish unlocking the mutex and + signalling the condition. This would lead to the waitee accessing no + longer valid memory. + + To prevent this, we do an extra lock/unlock of the mutex here before + deallocation; this makes certain that any waitee has completed wakeup() + first. + */ + mysql_mutex_lock(&LOCK_wait_commit); + mysql_mutex_unlock(&LOCK_wait_commit); + + mysql_mutex_destroy(&LOCK_wait_commit); + mysql_cond_destroy(&COND_wait_commit); +} + + +void +wait_for_commit::wakeup(int wakeup_error) +{ + /* + We signal each waiter on their own condition and mutex (rather than using + pthread_cond_broadcast() or something like that). + + Otherwise we would need to somehow ensure that they were done + waking up before we could allow this THD to be destroyed, which would + be annoying and unnecessary. + + Note that wakeup_subsequent_commits2() depends on this function being a + full memory barrier (it is, because it takes a mutex lock). + + */ + mysql_mutex_lock(&LOCK_wait_commit); + waitee= NULL; + this->wakeup_error= wakeup_error; + /* + Note that it is critical that the mysql_cond_signal() here is done while + still holding the mutex. As soon as we release the mutex, the waiter might + deallocate the condition object. + */ + mysql_cond_signal(&COND_wait_commit); + mysql_mutex_unlock(&LOCK_wait_commit); +} + + +/* + Register that the next commit of this THD should wait to complete until + commit in another THD (the waitee) has completed. + + The wait may occur explicitly, with the waiter sitting in + wait_for_prior_commit() until the waitee calls wakeup_subsequent_commits(). + + Alternatively, the TC (eg. binlog) may do the commits of both waitee and + waiter at once during group commit, resolving both of them in the right + order. + + Only one waitee can be registered for a waiter; it must be removed by + wait_for_prior_commit() or unregister_wait_for_prior_commit() before a new + one is registered. But it is ok for several waiters to register a wait for + the same waitee. It is also permissible for one THD to be both a waiter and + a waitee at the same time. +*/ +void +wait_for_commit::register_wait_for_prior_commit(wait_for_commit *waitee) +{ + DBUG_ASSERT(!this->waitee /* No prior registration allowed */); + wakeup_error= 0; + this->waitee= waitee; + + mysql_mutex_lock(&waitee->LOCK_wait_commit); + /* + If waitee is in the middle of wakeup, then there is nothing to wait for, + so we need not register. This is necessary to avoid a race in unregister, + see comments on wakeup_subsequent_commits2() for details. + */ + if (waitee->wakeup_subsequent_commits_running) + this->waitee= NULL; + else + { + /* + Put ourself at the head of the waitee's list of transactions that must + wait for it to commit first. + */ + this->next_subsequent_commit= waitee->subsequent_commits_list; + waitee->subsequent_commits_list= this; + } + mysql_mutex_unlock(&waitee->LOCK_wait_commit); +} + + +/* + Wait for commit of another transaction to complete, as already registered + with register_wait_for_prior_commit(). If the commit already completed, + returns immediately. +*/ +int +wait_for_commit::wait_for_prior_commit2(THD *thd) +{ + PSI_stage_info old_stage; + wait_for_commit *loc_waitee; + + mysql_mutex_lock(&LOCK_wait_commit); + DEBUG_SYNC(thd, "wait_for_prior_commit_waiting"); + thd->ENTER_COND(&COND_wait_commit, &LOCK_wait_commit, + &stage_waiting_for_prior_transaction_to_commit, + &old_stage); + while ((loc_waitee= this->waitee) && !thd->check_killed()) + mysql_cond_wait(&COND_wait_commit, &LOCK_wait_commit); + if (!loc_waitee) + { + if (wakeup_error) + my_error(ER_PRIOR_COMMIT_FAILED, MYF(0)); + goto end; + } + /* + Wait was interrupted by kill. We need to unregister our wait and give the + error. But if a wakeup is already in progress, then we must ignore the + kill and not give error, otherwise we get inconsistency between waitee and + waiter as to whether we succeed or fail (eg. we may roll back but waitee + might attempt to commit both us and any subsequent commits waiting for us). + */ + mysql_mutex_lock(&loc_waitee->LOCK_wait_commit); + if (loc_waitee->wakeup_subsequent_commits_running) + { + /* We are being woken up; ignore the kill and just wait. */ + mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit); + do + { + mysql_cond_wait(&COND_wait_commit, &LOCK_wait_commit); + } while (this->waitee); + if (wakeup_error) + my_error(ER_PRIOR_COMMIT_FAILED, MYF(0)); + goto end; + } + remove_from_list(&loc_waitee->subsequent_commits_list); + mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit); + this->waitee= NULL; + + wakeup_error= thd->killed_errno(); + if (!wakeup_error) + wakeup_error= ER_QUERY_INTERRUPTED; + my_message(wakeup_error, ER_THD(thd, wakeup_error), MYF(0)); + thd->EXIT_COND(&old_stage); + /* + Must do the DEBUG_SYNC() _after_ exit_cond(), as DEBUG_SYNC is not safe to + use within enter_cond/exit_cond. + */ + DEBUG_SYNC(thd, "wait_for_prior_commit_killed"); + return wakeup_error; + +end: + thd->EXIT_COND(&old_stage); + return wakeup_error; +} + + +/* + Wakeup anyone waiting for us to have committed. + + Note about locking: + + We have a potential race or deadlock between wakeup_subsequent_commits() in + the waitee and unregister_wait_for_prior_commit() in the waiter. + + Both waiter and waitee needs to take their own lock before it is safe to take + a lock on the other party - else the other party might disappear and invalid + memory data could be accessed. But if we take the two locks in different + order, we may end up in a deadlock. + + The waiter needs to lock the waitee to delete itself from the list in + unregister_wait_for_prior_commit(). Thus wakeup_subsequent_commits() can not + hold its own lock while locking waiters, as this could lead to deadlock. + + So we need to prevent unregister_wait_for_prior_commit() running while wakeup + is in progress - otherwise the unregister could complete before the wakeup, + leading to incorrect spurious wakeup or accessing invalid memory. + + However, if we are in the middle of running wakeup_subsequent_commits(), then + there is no need for unregister_wait_for_prior_commit() in the first place - + the waiter can just do a normal wait_for_prior_commit(), as it will be + immediately woken up. + + So the solution to the potential race/deadlock is to set a flag in the waitee + that wakeup_subsequent_commits() is in progress. When this flag is set, + unregister_wait_for_prior_commit() becomes just wait_for_prior_commit(). + + Then also register_wait_for_prior_commit() needs to check if + wakeup_subsequent_commits() is running, and skip the registration if + so. This is needed in case a new waiter manages to register itself and + immediately try to unregister while wakeup_subsequent_commits() is + running. Else the new waiter would also wait rather than unregister, but it + would not be woken up until next wakeup, which could be potentially much + later than necessary. +*/ + +void +wait_for_commit::wakeup_subsequent_commits2(int wakeup_error) +{ + wait_for_commit *waiter; + + mysql_mutex_lock(&LOCK_wait_commit); + wakeup_subsequent_commits_running= true; + waiter= subsequent_commits_list; + subsequent_commits_list= NULL; + mysql_mutex_unlock(&LOCK_wait_commit); + + while (waiter) + { + /* + Important: we must grab the next pointer before waking up the waiter; + once the wakeup is done, the field could be invalidated at any time. + */ + wait_for_commit *next= waiter->next_subsequent_commit; + waiter->wakeup(wakeup_error); + waiter= next; + } + + /* + We need a full memory barrier between walking the list above, and clearing + the flag wakeup_subsequent_commits_running below. This barrier is needed + to ensure that no other thread will start to modify the list pointers + before we are done traversing the list. + + But wait_for_commit::wakeup() does a full memory barrier already (it locks + a mutex), so no extra explicit barrier is needed here. + */ + wakeup_subsequent_commits_running= false; + DBUG_EXECUTE_IF("inject_wakeup_subsequent_commits_sleep", my_sleep(21000);); +} + + +/* Cancel a previously registered wait for another THD to commit before us. */ +void +wait_for_commit::unregister_wait_for_prior_commit2() +{ + wait_for_commit *loc_waitee; + + mysql_mutex_lock(&LOCK_wait_commit); + if ((loc_waitee= this->waitee)) + { + mysql_mutex_lock(&loc_waitee->LOCK_wait_commit); + if (loc_waitee->wakeup_subsequent_commits_running) + { + /* + When a wakeup is running, we cannot safely remove ourselves from the + list without corrupting it. Instead we can just wait, as wakeup is + already in progress and will thus be immediate. + + See comments on wakeup_subsequent_commits2() for more details. + */ + mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit); + while (this->waitee) + mysql_cond_wait(&COND_wait_commit, &LOCK_wait_commit); + } + else + { + /* Remove ourselves from the list in the waitee. */ + remove_from_list(&loc_waitee->subsequent_commits_list); + mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit); + this->waitee= NULL; + } + } + wakeup_error= 0; + mysql_mutex_unlock(&LOCK_wait_commit); +} + + bool Discrete_intervals_list::append(ulonglong start, ulonglong val, ulonglong incr) { diff --git a/sql/sql_class.h b/sql/sql_class.h index bff7492ffec..2517f5cc06f 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -15,105 +15,136 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ - #ifndef SQL_CLASS_INCLUDED #define SQL_CLASS_INCLUDED /* Classes in mysql */ -#ifdef USE_PRAGMA_INTERFACE -#pragma interface /* gcc class implementation */ -#endif - #include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ -#ifdef MYSQL_SERVER -#include "unireg.h" // REQUIRED: for other includes -#endif #include <waiting_threads.h> #include "sql_const.h" #include <mysql/plugin_audit.h> #include "log.h" #include "rpl_tblmap.h" #include "mdl.h" +#include "field.h" // Create_field #include "probes_mysql.h" -#include "sql_locale.h" /* my_locale_st */ -#include "sql_profile.h" /* PROFILING */ -#include "scheduler.h" /* thd_scheduler */ -#include "protocol.h" /* Protocol_text, Protocol_binary */ -#include "violite.h" /* vio_is_connected */ -#include "thr_lock.h" /* thr_lock_type, THR_LOCK_DATA, - THR_LOCK_INFO */ - +#include "sql_locale.h" /* my_locale_st */ +#include "sql_profile.h" /* PROFILING */ +#include "scheduler.h" /* thd_scheduler */ +#include "protocol.h" /* Protocol_text, Protocol_binary */ +#include "violite.h" /* vio_is_connected */ +#include "thr_lock.h" /* thr_lock_type, THR_LOCK_DATA, THR_LOCK_INFO */ +#include "thr_timer.h" + +#include "sql_digest_stream.h" // sql_digest_state + +#include <mysql/psi/mysql_stage.h> +#include <mysql/psi/mysql_statement.h> +#include <mysql/psi/mysql_idle.h> +#include <mysql/psi/mysql_table.h> +#include <mysql_com_server.h> + +extern "C" +void set_thd_stage_info(void *thd, + const PSI_stage_info *new_stage, + PSI_stage_info *old_stage, + const char *calling_func, + const char *calling_file, + const unsigned int calling_line); + +#define THD_STAGE_INFO(thd, stage) \ + (thd)->enter_stage(&stage, __func__, __FILE__, __LINE__) + +#include "my_apc.h" +#include "rpl_gtid.h" +#include "wsrep_mysqld.h" class Reprepare_observer; class Relay_log_info; +struct rpl_group_info; +class Rpl_filter; class Query_log_event; class Load_log_event; -class Slave_log_event; class sp_rcontext; class sp_cache; class Lex_input_stream; class Parser_state; class Rows_log_event; class Sroutine_hash_entry; -class User_level_lock; class user_var_entry; struct Trans_binlog_info; +class rpl_io_thread_info; +class rpl_sql_thread_info; -enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE }; enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY, RNEXT_SAME }; enum enum_duplicates { DUP_ERROR, DUP_REPLACE, DUP_UPDATE }; enum enum_delay_key_write { DELAY_KEY_WRITE_NONE, DELAY_KEY_WRITE_ON, DELAY_KEY_WRITE_ALL }; enum enum_slave_exec_mode { SLAVE_EXEC_MODE_STRICT, SLAVE_EXEC_MODE_IDEMPOTENT, - SLAVE_EXEC_MODE_LAST_BIT}; + SLAVE_EXEC_MODE_LAST_BIT }; +enum enum_slave_run_triggers_for_rbr { SLAVE_RUN_TRIGGERS_FOR_RBR_NO, + SLAVE_RUN_TRIGGERS_FOR_RBR_YES, + SLAVE_RUN_TRIGGERS_FOR_RBR_LOGGING}; enum enum_slave_type_conversions { SLAVE_TYPE_CONVERSIONS_ALL_LOSSY, SLAVE_TYPE_CONVERSIONS_ALL_NON_LOSSY}; enum enum_mark_columns { MARK_COLUMNS_NONE, MARK_COLUMNS_READ, MARK_COLUMNS_WRITE}; enum enum_filetype { FILETYPE_CSV, FILETYPE_XML }; +enum enum_binlog_row_image { + /** PKE in the before image and changed columns in the after image */ + BINLOG_ROW_IMAGE_MINIMAL= 0, + /** Whenever possible, before and after image contain all columns except blobs. */ + BINLOG_ROW_IMAGE_NOBLOB= 1, + /** All columns in both before and after image. */ + BINLOG_ROW_IMAGE_FULL= 2 +}; + + /* Bits for different SQL modes modes (including ANSI mode) */ -#define MODE_REAL_AS_FLOAT 1 -#define MODE_PIPES_AS_CONCAT 2 -#define MODE_ANSI_QUOTES 4 -#define MODE_IGNORE_SPACE 8 -#define MODE_IGNORE_BAD_TABLE_OPTIONS 16 -#define MODE_ONLY_FULL_GROUP_BY 32 -#define MODE_NO_UNSIGNED_SUBTRACTION 64 -#define MODE_NO_DIR_IN_CREATE 128 -#define MODE_POSTGRESQL 256 -#define MODE_ORACLE 512 -#define MODE_MSSQL 1024 -#define MODE_DB2 2048 -#define MODE_MAXDB 4096 -#define MODE_NO_KEY_OPTIONS 8192 -#define MODE_NO_TABLE_OPTIONS 16384 -#define MODE_NO_FIELD_OPTIONS 32768 -#define MODE_MYSQL323 65536L -#define MODE_MYSQL40 (MODE_MYSQL323*2) -#define MODE_ANSI (MODE_MYSQL40*2) -#define MODE_NO_AUTO_VALUE_ON_ZERO (MODE_ANSI*2) -#define MODE_NO_BACKSLASH_ESCAPES (MODE_NO_AUTO_VALUE_ON_ZERO*2) -#define MODE_STRICT_TRANS_TABLES (MODE_NO_BACKSLASH_ESCAPES*2) -#define MODE_STRICT_ALL_TABLES (MODE_STRICT_TRANS_TABLES*2) -#define MODE_NO_ZERO_IN_DATE (MODE_STRICT_ALL_TABLES*2) -#define MODE_NO_ZERO_DATE (MODE_NO_ZERO_IN_DATE*2) -#define MODE_INVALID_DATES (MODE_NO_ZERO_DATE*2) -#define MODE_ERROR_FOR_DIVISION_BY_ZERO (MODE_INVALID_DATES*2) -#define MODE_TRADITIONAL (MODE_ERROR_FOR_DIVISION_BY_ZERO*2) -#define MODE_NO_AUTO_CREATE_USER (MODE_TRADITIONAL*2) -#define MODE_HIGH_NOT_PRECEDENCE (MODE_NO_AUTO_CREATE_USER*2) -#define MODE_NO_ENGINE_SUBSTITUTION (MODE_HIGH_NOT_PRECEDENCE*2) -#define MODE_PAD_CHAR_TO_FULL_LENGTH (ULL(1) << 31) +#define MODE_REAL_AS_FLOAT (1ULL << 0) +#define MODE_PIPES_AS_CONCAT (1ULL << 1) +#define MODE_ANSI_QUOTES (1ULL << 2) +#define MODE_IGNORE_SPACE (1ULL << 3) +#define MODE_IGNORE_BAD_TABLE_OPTIONS (1ULL << 4) +#define MODE_ONLY_FULL_GROUP_BY (1ULL << 5) +#define MODE_NO_UNSIGNED_SUBTRACTION (1ULL << 6) +#define MODE_NO_DIR_IN_CREATE (1ULL << 7) +#define MODE_POSTGRESQL (1ULL << 8) +#define MODE_ORACLE (1ULL << 9) +#define MODE_MSSQL (1ULL << 10) +#define MODE_DB2 (1ULL << 11) +#define MODE_MAXDB (1ULL << 12) +#define MODE_NO_KEY_OPTIONS (1ULL << 13) +#define MODE_NO_TABLE_OPTIONS (1ULL << 14) +#define MODE_NO_FIELD_OPTIONS (1ULL << 15) +#define MODE_MYSQL323 (1ULL << 16) +#define MODE_MYSQL40 (1ULL << 17) +#define MODE_ANSI (1ULL << 18) +#define MODE_NO_AUTO_VALUE_ON_ZERO (1ULL << 19) +#define MODE_NO_BACKSLASH_ESCAPES (1ULL << 20) +#define MODE_STRICT_TRANS_TABLES (1ULL << 21) +#define MODE_STRICT_ALL_TABLES (1ULL << 22) +#define MODE_NO_ZERO_IN_DATE (1ULL << 23) +#define MODE_NO_ZERO_DATE (1ULL << 24) +#define MODE_INVALID_DATES (1ULL << 25) +#define MODE_ERROR_FOR_DIVISION_BY_ZERO (1ULL << 26) +#define MODE_TRADITIONAL (1ULL << 27) +#define MODE_NO_AUTO_CREATE_USER (1ULL << 28) +#define MODE_HIGH_NOT_PRECEDENCE (1ULL << 29) +#define MODE_NO_ENGINE_SUBSTITUTION (1ULL << 30) +#define MODE_PAD_CHAR_TO_FULL_LENGTH (1ULL << 31) /* Bits for different old style modes */ -#define OLD_MODE_NO_DUP_KEY_WARNINGS_WITH_IGNORE 1 -#define OLD_MODE_NO_PROGRESS_INFO 2 +#define OLD_MODE_NO_DUP_KEY_WARNINGS_WITH_IGNORE (1 << 0) +#define OLD_MODE_NO_PROGRESS_INFO (1 << 1) +#define OLD_MODE_ZERO_DATE_TIME_CAST (1 << 2) extern char internal_table_name[2]; extern char empty_c_string[1]; +extern LEX_STRING EMPTY_STR; extern MYSQL_PLUGIN_IMPORT const char **errmesg; extern bool volatile shutdown_in_progress; @@ -153,9 +184,6 @@ public: }; -#define TC_LOG_PAGE_SIZE 8192 -#define TC_LOG_MIN_SIZE (3*TC_LOG_PAGE_SIZE) - #define TC_HEURISTIC_RECOVER_COMMIT 1 #define TC_HEURISTIC_RECOVER_ROLLBACK 2 extern ulong tc_heuristic_recover; @@ -227,11 +255,15 @@ public: class Alter_drop :public Sql_alloc { public: - enum drop_type {KEY, COLUMN }; + enum drop_type {KEY, COLUMN, FOREIGN_KEY }; const char *name; enum drop_type type; - Alter_drop(enum drop_type par_type,const char *par_name) - :name(par_name), type(par_type) {} + bool drop_if_exists; + Alter_drop(enum drop_type par_type,const char *par_name, bool par_exists) + :name(par_name), type(par_type), drop_if_exists(par_exists) + { + DBUG_ASSERT(par_name != NULL); + } /** Used to make a clone of this object for ALTER/CREATE TABLE @sa comment for Key_part_spec::clone @@ -256,7 +288,7 @@ public: }; -class Key :public Sql_alloc { +class Key :public Sql_alloc, public DDL_options { public: enum Keytype { PRIMARY, UNIQUE, MULTIPLE, FULLTEXT, SPATIAL, FOREIGN_KEY}; enum Keytype type; @@ -267,17 +299,27 @@ public: bool generated; Key(enum Keytype type_par, const LEX_STRING &name_arg, + ha_key_alg algorithm_arg, bool generated_arg, DDL_options_st ddl_options) + :DDL_options(ddl_options), + type(type_par), key_create_info(default_key_create_info), + name(name_arg), option_list(NULL), generated(generated_arg) + { + key_create_info.algorithm= algorithm_arg; + } + Key(enum Keytype type_par, const LEX_STRING &name_arg, KEY_CREATE_INFO *key_info_arg, bool generated_arg, List<Key_part_spec> &cols, - engine_option_value *create_opt) - :type(type_par), key_create_info(*key_info_arg), columns(cols), + engine_option_value *create_opt, DDL_options_st ddl_options) + :DDL_options(ddl_options), + type(type_par), key_create_info(*key_info_arg), columns(cols), name(name_arg), option_list(create_opt), generated(generated_arg) {} Key(enum Keytype type_par, const char *name_arg, size_t name_len_arg, KEY_CREATE_INFO *key_info_arg, bool generated_arg, List<Key_part_spec> &cols, - engine_option_value *create_opt) - :type(type_par), key_create_info(*key_info_arg), columns(cols), + engine_option_value *create_opt, DDL_options_st ddl_options) + :DDL_options(ddl_options), + type(type_par), key_create_info(*key_info_arg), columns(cols), option_list(create_opt), generated(generated_arg) { name.str= (char *)name_arg; @@ -295,27 +337,31 @@ public: { return new (mem_root) Key(*this, mem_root); } }; -class Table_ident; class Foreign_key: public Key { public: enum fk_match_opt { FK_MATCH_UNDEF, FK_MATCH_FULL, FK_MATCH_PARTIAL, FK_MATCH_SIMPLE}; - enum fk_option { FK_OPTION_UNDEF, FK_OPTION_RESTRICT, FK_OPTION_CASCADE, - FK_OPTION_SET_NULL, FK_OPTION_NO_ACTION, FK_OPTION_DEFAULT}; - Table_ident *ref_table; + LEX_STRING ref_db; + LEX_STRING ref_table; List<Key_part_spec> ref_columns; uint delete_opt, update_opt, match_opt; Foreign_key(const LEX_STRING &name_arg, List<Key_part_spec> &cols, - Table_ident *table, List<Key_part_spec> &ref_cols, - uint delete_opt_arg, uint update_opt_arg, uint match_opt_arg) - :Key(FOREIGN_KEY, name_arg, &default_key_create_info, 0, cols, NULL), - ref_table(table), ref_columns(ref_cols), + const LEX_STRING &ref_db_arg, const LEX_STRING &ref_table_arg, + List<Key_part_spec> &ref_cols, + uint delete_opt_arg, uint update_opt_arg, uint match_opt_arg, + DDL_options ddl_options) + :Key(FOREIGN_KEY, name_arg, &default_key_create_info, 0, cols, NULL, + ddl_options), + ref_db(ref_db_arg), ref_table(ref_table_arg), ref_columns(ref_cols), delete_opt(delete_opt_arg), update_opt(update_opt_arg), match_opt(match_opt_arg) - {} - Foreign_key(const Foreign_key &rhs, MEM_ROOT *mem_root); + { + // We don't check for duplicate FKs. + key_create_info.check_for_duplicate_indexes= false; + } + Foreign_key(const Foreign_key &rhs, MEM_ROOT *mem_root); /** Used to make a clone of this object for ALTER/CREATE TABLE @sa comment for Key_part_spec::clone @@ -419,31 +465,32 @@ enum killed_state */ ABORT_QUERY= 6, ABORT_QUERY_HARD= 7, + KILL_TIMEOUT= 8, + KILL_TIMEOUT_HARD= 9, /* All of the following killed states will kill the connection KILL_CONNECTION must be the first of these and it must start with an even number (becasue of HARD bit)! */ - KILL_CONNECTION= 8, - KILL_CONNECTION_HARD= 9, - KILL_SYSTEM_THREAD= 10, - KILL_SYSTEM_THREAD_HARD= 11, - KILL_SERVER= 12, - KILL_SERVER_HARD= 13 + KILL_CONNECTION= 10, + KILL_CONNECTION_HARD= 11, + KILL_SYSTEM_THREAD= 12, + KILL_SYSTEM_THREAD_HARD= 13, + KILL_SERVER= 14, + KILL_SERVER_HARD= 15 }; -extern int killed_errno(killed_state killed); #define killed_mask_hard(killed) ((killed_state) ((killed) & ~KILL_HARD_BIT)) enum killed_type { KILL_TYPE_ID, - KILL_TYPE_USER + KILL_TYPE_USER, + KILL_TYPE_QUERY }; #include "sql_lex.h" /* Must be here */ -extern LEX_STRING sql_statement_names[(uint) SQLCOM_END + 1]; class Delayed_insert; class select_result; class Time_zone; @@ -453,6 +500,8 @@ class Time_zone; #define THD_CHECK_SENTRY(thd) DBUG_ASSERT(thd->dbug_sentry == THD_SENTRY_MAGIC) +typedef ulonglong sql_mode_t; + typedef struct system_variables { /* @@ -475,9 +524,10 @@ typedef struct system_variables ulonglong max_heap_table_size; ulonglong tmp_table_size; ulonglong long_query_time; + ulonglong max_statement_time; ulonglong optimizer_switch; - ulonglong sql_mode; ///< which non-standard SQL behaviour should be enabled - ulonglong old_behavior; ///< which old SQL behaviour should be enabled + sql_mode_t sql_mode; ///< which non-standard SQL behaviour should be enabled + sql_mode_t old_behavior; ///< which old SQL behaviour should be enabled ulonglong option_bits; ///< OPTION_xxx constants, e.g. OPTION_PROFILING ulonglong join_buff_space_limit; ulonglong log_slow_filter; @@ -486,10 +536,30 @@ typedef struct system_variables ulonglong join_buff_size; ulonglong sortbuff_size; ulonglong group_concat_max_len; + ulonglong default_regex_flags; + ulonglong max_mem_used; + + /** + Place holders to store Multi-source variables in sys_var.cc during + update and show of variables. + */ + ulonglong slave_skip_counter; + ulonglong max_relay_log_size; + ha_rows select_limit; ha_rows max_join_size; ha_rows expensive_subquery_limit; ulong auto_increment_increment, auto_increment_offset; +#ifdef WITH_WSREP + /* + Stored values of the auto_increment_increment and auto_increment_offset + that are will be restored when wsrep_auto_increment_control will be set + to 'OFF', because the setting it to 'ON' leads to overwriting of the + original values (which are set by the user) by calculated ones (which + are based on the cluster size): + */ + ulong saved_auto_increment_increment, saved_auto_increment_offset; +#endif /* WITH_WSREP */ ulong lock_wait_timeout; ulong join_cache_level; ulong max_allowed_packet; @@ -508,6 +578,11 @@ typedef struct system_variables ulong net_write_timeout; ulong optimizer_prune_level; ulong optimizer_search_depth; + ulong optimizer_selectivity_sampling_limit; + ulong optimizer_use_condition_selectivity; + ulong use_stat_tables; + ulong histogram_size; + ulong histogram_type; ulong preload_buff_size; ulong profiling_history_size; ulong read_buff_size; @@ -528,24 +603,32 @@ typedef struct system_variables /* Flags for slow log filtering */ ulong log_slow_rate_limit; ulong binlog_format; ///< binlog format for this thd (see enum_binlog_format) + ulong binlog_row_image; ulong progress_report_time; - my_bool binlog_annotate_row_events; - my_bool binlog_direct_non_trans_update; - my_bool sql_log_bin; ulong completion_type; ulong query_cache_type; ulong tx_isolation; ulong updatable_views_with_limit; int max_user_connections; + ulong server_id; /** In slave thread we need to know in behalf of which thread the query is being run to replicate temp tables properly */ my_thread_id pseudo_thread_id; + /** + When replicating an event group with GTID, keep these values around so + slave binlog can receive the same GTID as the original. + */ + uint32 gtid_domain_id; + uint64 gtid_seq_no; + /** + Default transaction access mode. READ ONLY (true) or READ WRITE (false). + */ + my_bool tx_read_only; my_bool low_priority_updates; my_bool query_cache_wlock_invalidate; - my_bool engine_condition_pushdown; my_bool keep_files_on_create; my_bool old_mode; @@ -553,8 +636,19 @@ typedef struct system_variables my_bool old_passwords; my_bool big_tables; my_bool query_cache_strip_comments; + my_bool sql_log_slow; + my_bool sql_log_bin; + /* + A flag to help detect whether binary logging was temporarily disabled + (see tmp_disable_binlog(A) macro). + */ + my_bool sql_log_bin_off; + my_bool binlog_annotate_row_events; + my_bool binlog_direct_non_trans_update; plugin_ref table_plugin; + plugin_ref tmp_table_plugin; + plugin_ref enforced_table_plugin; /* Only charset part of these variables is sensible */ CHARSET_INFO *character_set_filesystem; @@ -566,8 +660,13 @@ typedef struct system_variables CHARSET_INFO *collation_database; CHARSET_INFO *collation_connection; + /* Names. These will be allocated in buffers in thd */ + LEX_STRING default_master_connection; + /* Error messages */ MY_LOCALE *lc_messages; + const char **errmsgs; /* lc_messages->errmsg->errmsgs */ + /* Locale Support */ MY_LOCALE *lc_time_names; @@ -579,7 +678,13 @@ typedef struct system_variables ulong wt_timeout_short, wt_deadlock_search_depth_short; ulong wt_timeout_long, wt_deadlock_search_depth_long; - double long_query_time_double; + my_bool wsrep_on; + my_bool wsrep_causal_reads; + my_bool wsrep_dirty_reads; + uint wsrep_sync_wait; + ulong wsrep_retry_autocommit; + ulong wsrep_OSU_method; + double long_query_time_double, max_statement_time_double; my_bool pseudo_slave_mode; @@ -593,10 +698,22 @@ typedef struct system_variables typedef struct system_status_var { - ulong com_other; ulong com_stat[(uint) SQLCOM_END]; - ulong created_tmp_disk_tables; - ulong created_tmp_tables; + ulong com_create_tmp_table; + ulong com_drop_tmp_table; + ulong com_other; + + ulong com_stmt_prepare; + ulong com_stmt_reprepare; + ulong com_stmt_execute; + ulong com_stmt_send_long_data; + ulong com_stmt_fetch; + ulong com_stmt_reset; + ulong com_stmt_close; + + ulong com_register_slave; + ulong created_tmp_disk_tables_; + ulong created_tmp_tables_; ulong ha_commit_count; ulong ha_delete_count; ulong ha_read_first_count; @@ -604,9 +721,11 @@ typedef struct system_status_var ulong ha_read_key_count; ulong ha_read_next_count; ulong ha_read_prev_count; + ulong ha_read_retry_count; ulong ha_read_rnd_count; ulong ha_read_rnd_next_count; ulong ha_read_rnd_deleted_count; + /* This number doesn't include calls to the default implementation and calls made by range access. The intent is to count only calls made by @@ -628,31 +747,27 @@ typedef struct system_status_var ulong ha_discover_count; ulong ha_savepoint_count; ulong ha_savepoint_rollback_count; + ulong ha_external_lock_count; ulong net_big_packet_count; ulong opened_tables; ulong opened_shares; ulong opened_views; /* +1 opening a view */ - ulong select_full_join_count; - ulong select_full_range_join_count; - ulong select_range_count; - ulong select_range_check_count; - ulong select_scan_count; + ulong select_full_join_count_; + ulong select_full_range_join_count_; + ulong select_range_count_; + ulong select_range_check_count_; + ulong select_scan_count_; + ulong update_scan_count; + ulong delete_scan_count; ulong executed_triggers; ulong long_query_count; - ulong filesort_merge_passes; - ulong filesort_range_count; - ulong filesort_rows; - ulong filesort_scan_count; - /* Prepared statements and binary protocol */ - ulong com_stmt_prepare; - ulong com_stmt_reprepare; - ulong com_stmt_execute; - ulong com_stmt_send_long_data; - ulong com_stmt_fetch; - ulong com_stmt_reset; - ulong com_stmt_close; + ulong filesort_merge_passes_; + ulong filesort_range_count_; + ulong filesort_rows_; + ulong filesort_scan_count_; + ulong filesort_pq_sorts_; /* Features used */ ulong feature_dynamic_columns; /* +1 when creating a dynamic column */ @@ -664,9 +779,15 @@ typedef struct system_status_var ulong feature_trigger; /* +1 opening a table with triggers */ ulong feature_xml; /* +1 when XPATH is used */ + /* From MASTER_GTID_WAIT usage */ + ulonglong master_gtid_wait_timeouts; /* Number of timeouts */ + ulonglong master_gtid_wait_time; /* Time in microseconds */ + ulonglong master_gtid_wait_count; + ulong empty_queries; ulong access_denied_errors; ulong lost_connections; + ulong max_statement_time_exceeded; /* Number of statements sent from the client */ @@ -685,6 +806,11 @@ typedef struct system_status_var ulonglong binlog_bytes_written; double last_query_cost; double cpu_time, busy_time; + /* Don't initialize */ + /* Memory used for thread local storage */ + volatile int64 local_memory_used; + /* Memory allocated for global usage */ + volatile int64 global_memory_used; } STATUS_VAR; /* @@ -694,12 +820,66 @@ typedef struct system_status_var */ #define last_system_status_var questions +#define last_cleared_system_status_var local_memory_used + +/* + Global status variables +*/ + +extern ulong feature_files_opened_with_delayed_keys; + void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var); void add_diff_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var, STATUS_VAR *dec_var); +/* + Update global_memory_used. We have to do this with atomic_add as the + global value can change outside of LOCK_status. +*/ +inline void update_global_memory_status(int64 size) +{ + DBUG_PRINT("info", ("global memory_used: %lld size: %lld", + (longlong) global_status_var.global_memory_used, + size)); + // workaround for gcc 4.2.4-1ubuntu4 -fPIE (from DEB_BUILD_HARDENING=1) + int64 volatile * volatile ptr= &global_status_var.global_memory_used; + my_atomic_add64_explicit(ptr, size, MY_MEMORY_ORDER_RELAXED); +} + +/** + Get collation by name, send error to client on failure. + @param name Collation name + @param name_cs Character set of the name string + @return + @retval NULL on error + @retval Pointter to CHARSET_INFO with the given name on success +*/ +inline CHARSET_INFO * +mysqld_collation_get_by_name(const char *name, + CHARSET_INFO *name_cs= system_charset_info) +{ + CHARSET_INFO *cs; + MY_CHARSET_LOADER loader; + my_charset_loader_init_mysys(&loader); + if (!(cs= my_collation_get_by_name(&loader, name, MYF(0)))) + { + ErrConvString err(name, name_cs); + my_error(ER_UNKNOWN_COLLATION, MYF(0), err.ptr()); + if (loader.error[0]) + push_warning_printf(current_thd, + Sql_condition::WARN_LEVEL_WARN, + ER_UNKNOWN_COLLATION, "%s", loader.error); + } + return cs; +} + +inline bool is_supported_parser_charset(CHARSET_INFO *cs) +{ + return MY_TEST(cs->mbminlen == 1); +} + #ifdef MYSQL_SERVER void free_tmp_table(THD *thd, TABLE *entry); @@ -798,6 +978,19 @@ public: }; +class Query_arena_memroot: public Query_arena, public Sql_alloc +{ +public: + Query_arena_memroot(MEM_ROOT *mem_root_arg, enum enum_state state_arg) : + Query_arena(mem_root_arg, state_arg) + {} + Query_arena_memroot() : Query_arena() + {} + + virtual ~Query_arena_memroot() {} +}; + + class Server_side_cursor; /** @@ -983,14 +1176,15 @@ struct st_savepoint { enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED, XA_ROLLBACK_ONLY}; extern const char *xa_state_names[]; +class XID_cache_element; typedef struct st_xid_state { /* For now, this is only used to catch duplicated external xids */ XID xid; // transaction identifier enum xa_states xa_state; // used by external XA only - bool in_thd; /* Error reported by the Resource Manager (RM) to the Transaction Manager. */ uint rm_error; + XID_cache_element *xid_cache_element; /** Check that XA transaction has an uncommitted work. Report an error @@ -1014,17 +1208,15 @@ typedef struct st_xid_state { } return false; } - } XID_STATE; -extern mysql_mutex_t LOCK_xid_cache; -extern HASH xid_cache; -bool xid_cache_init(void); +void xid_cache_init(void); void xid_cache_free(void); -XID_STATE *xid_cache_search(XID *xid); +XID_STATE *xid_cache_search(THD *thd, XID *xid); bool xid_cache_insert(XID *xid, enum xa_states xa_state); -bool xid_cache_insert(XID_STATE *xid_state); -void xid_cache_delete(XID_STATE *xid_state); +bool xid_cache_insert(THD *thd, XID_STATE *xid_state); +void xid_cache_delete(THD *thd, XID_STATE *xid_state); +int xid_cache_iterate(THD *thd, my_hash_walk_action action, void *argument); /** @class Security_context @@ -1046,6 +1238,8 @@ public: char proxy_user[USERNAME_LENGTH + MAX_HOSTNAME + 5]; /* The host privilege we are using */ char priv_host[MAX_HOSTNAME]; + /* The role privilege we are using */ + char priv_role[USERNAME_LENGTH]; /* The external user (if available) */ char *external_user; /* points to host if host is available, otherwise points to ip */ @@ -1284,9 +1478,11 @@ enum enum_thread_type SYSTEM_THREAD_DELAYED_INSERT= 1, SYSTEM_THREAD_SLAVE_IO= 2, SYSTEM_THREAD_SLAVE_SQL= 4, - SYSTEM_THREAD_NDBCLUSTER_BINLOG= 8, - SYSTEM_THREAD_EVENT_SCHEDULER= 16, - SYSTEM_THREAD_EVENT_WORKER= 32 + SYSTEM_THREAD_EVENT_SCHEDULER= 8, + SYSTEM_THREAD_EVENT_WORKER= 16, + SYSTEM_THREAD_BINLOG_BACKGROUND= 32, + SYSTEM_THREAD_SLAVE_INIT= 64, + SYSTEM_THREAD_SLAVE_BACKGROUND= 128 }; inline char const * @@ -1299,9 +1495,9 @@ show_system_thread(enum_thread_type thread) RETURN_NAME_AS_STRING(SYSTEM_THREAD_DELAYED_INSERT); RETURN_NAME_AS_STRING(SYSTEM_THREAD_SLAVE_IO); RETURN_NAME_AS_STRING(SYSTEM_THREAD_SLAVE_SQL); - RETURN_NAME_AS_STRING(SYSTEM_THREAD_NDBCLUSTER_BINLOG); RETURN_NAME_AS_STRING(SYSTEM_THREAD_EVENT_SCHEDULER); RETURN_NAME_AS_STRING(SYSTEM_THREAD_EVENT_WORKER); + RETURN_NAME_AS_STRING(SYSTEM_THREAD_SLAVE_INIT); default: sprintf(buf, "<UNKNOWN SYSTEM THREAD: %d>", thread); return buf; @@ -1351,9 +1547,9 @@ public: virtual bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl) = 0; + Sql_condition ** cond_hdl) = 0; private: Internal_error_handler *m_prev_internal_handler; @@ -1372,9 +1568,9 @@ public: bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { /* Ignore error */ return TRUE; @@ -1384,6 +1580,29 @@ public: /** + Implements the trivial error handler which counts errors as they happen. +*/ + +class Counting_error_handler : public Internal_error_handler +{ +public: + int errors; + bool handle_condition(THD *thd, + uint sql_errno, + const char* sqlstate, + Sql_condition::enum_warning_level level, + const char* msg, + Sql_condition ** cond_hdl) + { + if (level == Sql_condition::WARN_LEVEL_ERROR) + errors++; + return false; + } + Counting_error_handler() : errors(0) {} +}; + + +/** This class is an internal error handler implementation for DROP TABLE statements. The thing is that there may be warnings during execution of these statements, which should not be exposed to the user. @@ -1399,9 +1618,9 @@ public: bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl); + Sql_condition ** cond_hdl); private: }; @@ -1454,13 +1673,16 @@ public: m_reopen_array(NULL), m_locked_tables_count(0) { - init_sql_alloc(&m_locked_tables_root, MEM_ROOT_BLOCK_SIZE, 0); + init_sql_alloc(&m_locked_tables_root, MEM_ROOT_BLOCK_SIZE, 0, + MYF(MY_THREAD_SPECIFIC)); } void unlock_locked_tables(THD *thd); + void unlock_locked_table(THD *thd, MDL_ticket *mdl_ticket); ~Locked_tables_list() { - unlock_locked_tables(0); + reset(); } + void reset(); bool init_locked_tables(THD *thd); TABLE_LIST *locked_tables() { return m_locked_tables; } void unlink_from_list(THD *thd, TABLE_LIST *table_list, @@ -1468,7 +1690,10 @@ public: void unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count); - bool reopen_tables(THD *thd); + bool reopen_tables(THD *thd, bool need_reopen); + bool restore_lock(THD *thd, TABLE_LIST *dst_table_list, TABLE *table, + MYSQL_LOCK *lock); + void add_back_last_deleted_lock(TABLE_LIST *dst_table_list); }; @@ -1558,8 +1783,163 @@ private: }; +/* + Class to facilitate the commit of one transactions waiting for the commit of + another transaction to complete first. + + This is used during (parallel) replication, to allow different transactions + to be applied in parallel, but still commit in order. + + The transaction that wants to wait for a prior commit must first register + to wait with register_wait_for_prior_commit(waitee). Such registration + must be done holding the waitee->LOCK_wait_commit, to prevent the other + THD from disappearing during the registration. + + Then during commit, if a THD is registered to wait, it will call + wait_for_prior_commit() as part of ha_commit_trans(). If no wait is + registered, or if the waitee for has already completed commit, then + wait_for_prior_commit() returns immediately. + + And when a THD that may be waited for has completed commit (more precisely + commit_ordered()), then it must call wakeup_subsequent_commits() to wake + up any waiters. Note that this must be done at a point that is guaranteed + to be later than any waiters registering themselves. It is safe to call + wakeup_subsequent_commits() multiple times, as waiters are removed from + registration as part of the wakeup. + + The reason for separate register and wait calls is that this allows to + register the wait early, at a point where the waited-for THD is known to + exist. And then the actual wait can be done much later, where the + waited-for THD may have been long gone. By registering early, the waitee + can signal before disappearing. +*/ +struct wait_for_commit +{ + /* + The LOCK_wait_commit protects the fields subsequent_commits_list and + wakeup_subsequent_commits_running (for a waitee), and the pointer + waiterr and associated COND_wait_commit (for a waiter). + */ + mysql_mutex_t LOCK_wait_commit; + mysql_cond_t COND_wait_commit; + /* List of threads that did register_wait_for_prior_commit() on us. */ + wait_for_commit *subsequent_commits_list; + /* Link field for entries in subsequent_commits_list. */ + wait_for_commit *next_subsequent_commit; + /* + Our waitee, if we did register_wait_for_prior_commit(), and were not + yet woken up. Else NULL. + + When this is cleared for wakeup, the COND_wait_commit condition is + signalled. + */ + wait_for_commit *waitee; + /* + Generic pointer for use by the transaction coordinator to optimise the + waiting for improved group commit. + + Currently used by binlog TC to signal that a waiter is ready to commit, so + that the waitee can grab it and group commit it directly. It is free to be + used by another transaction coordinator for similar purposes. + */ + void *opaque_pointer; + /* The wakeup error code from the waitee. 0 means no error. */ + int wakeup_error; + /* + Flag set when wakeup_subsequent_commits_running() is active, see comments + on that function for details. + */ + bool wakeup_subsequent_commits_running; + /* + This flag can be set when a commit starts, but has not completed yet. + It is used by binlog group commit to allow a waiting transaction T2 to + join the group commit of an earlier transaction T1. When T1 has queued + itself for group commit, it will set the commit_started flag. Then when + T2 becomes ready to commit and needs to wait for T1 to commit first, T2 + can queue itself before waiting, and thereby participate in the same + group commit as T1. + */ + bool commit_started; + + void register_wait_for_prior_commit(wait_for_commit *waitee); + int wait_for_prior_commit(THD *thd) + { + /* + Quick inline check, to avoid function call and locking in the common case + where no wakeup is registered, or a registered wait was already signalled. + */ + if (waitee) + return wait_for_prior_commit2(thd); + else + { + if (wakeup_error) + my_error(ER_PRIOR_COMMIT_FAILED, MYF(0)); + return wakeup_error; + } + } + void wakeup_subsequent_commits(int wakeup_error_arg) + { + /* + Do the check inline, so only the wakeup case takes the cost of a function + call for every commmit. + + Note that the check is done without locking. It is the responsibility of + the user of the wakeup facility to ensure that no waiters can register + themselves after the last call to wakeup_subsequent_commits(). + + This avoids having to take another lock for every commit, which would be + pointless anyway - even if we check under lock, there is nothing to + prevent a waiter from arriving just after releasing the lock. + */ + if (subsequent_commits_list) + wakeup_subsequent_commits2(wakeup_error_arg); + } + void unregister_wait_for_prior_commit() + { + if (waitee) + unregister_wait_for_prior_commit2(); + else + wakeup_error= 0; + } + /* + Remove a waiter from the list in the waitee. Used to unregister a wait. + The caller must be holding the locks of both waiter and waitee. + */ + void remove_from_list(wait_for_commit **next_ptr_ptr) + { + wait_for_commit *cur; + + while ((cur= *next_ptr_ptr) != NULL) + { + if (cur == this) + { + *next_ptr_ptr= this->next_subsequent_commit; + break; + } + next_ptr_ptr= &cur->next_subsequent_commit; + } + waitee= NULL; + } + + void wakeup(int wakeup_error); + + int wait_for_prior_commit2(THD *thd); + void wakeup_subsequent_commits2(int wakeup_error); + void unregister_wait_for_prior_commit2(); + + wait_for_commit(); + ~wait_for_commit(); + void reinit(); +}; + + extern "C" void my_message_sql(uint error, const char *str, myf MyFlags); +class THD; +#ifndef DBUG_OFF +void dbug_serve_apcs(THD *thd, int n_calls); +#endif + /** @class THD For each client connection we create a separate thread with THD serving as @@ -1567,6 +1947,7 @@ extern "C" void my_message_sql(uint error, const char *str, myf MyFlags); */ class THD :public Statement, + public MDL_context_owner, public Open_tables_state { private: @@ -1587,10 +1968,16 @@ public: /* Used to execute base64 coded binlog events in MySQL server */ Relay_log_info* rli_fake; + rpl_group_info* rgi_fake; /* Slave applier execution context */ - Relay_log_info* rli_slave; + rpl_group_info* rgi_slave; + + union { + rpl_io_thread_info *rpl_io_info; + rpl_sql_thread_info *rpl_sql_info; + } system_thread_info; - void reset_for_next_command(); + void reset_for_next_command(bool do_clear_errors= 1); /* Constant for THD::where initialization in the beginning of every query. @@ -1622,6 +2009,8 @@ public: Query_cache_tls query_cache_tls; #endif NET net; // client connection descriptor + /** Aditional network instrumentation for the server only. */ + NET_SERVER m_net_server_extension; scheduler_functions *scheduler; // Scheduler for this connection Protocol *protocol; // Current protocol Protocol_text protocol_text; // Normal protocol @@ -1644,6 +2033,8 @@ public: Is locked when THD is deleted. */ mysql_mutex_t LOCK_thd_data; + /* Protect kill information */ + mysql_mutex_t LOCK_thd_kill; /* all prepared statements and cursors of this connection */ Statement_map stmt_map; @@ -1688,6 +2079,37 @@ public: */ const char *proc_info; +private: + unsigned int m_current_stage_key; + +public: + void enter_stage(const PSI_stage_info *stage, + const char *calling_func, + const char *calling_file, + const unsigned int calling_line) + { + DBUG_PRINT("THD::enter_stage", ("%s:%d", calling_file, calling_line)); + DBUG_ASSERT(stage); + m_current_stage_key= stage->m_key; + proc_info= stage->m_name; +#if defined(ENABLED_PROFILING) + profiling.status_change(stage->m_name, calling_func, calling_file, + calling_line); +#endif +#ifdef HAVE_PSI_THREAD_INTERFACE + MYSQL_SET_STAGE(m_current_stage_key, calling_file, calling_line); +#endif + } + + void backup_stage(PSI_stage_info *stage) + { + stage->m_key= m_current_stage_key; + stage->m_name= proc_info; + } + + const char *get_proc_info() const + { return proc_info; } + /* Used in error messages to tell user in what part of MySQL we found an error. E. g. when where= "having clause", if fix_fields() fails, user @@ -1703,21 +2125,23 @@ public: HASH handler_tables_hash; /* - One thread can hold up to one named user-level lock. This variable - points to a lock object if the lock is present. See item_func.cc and + A thread can hold named user-level locks. This variable + contains granted tickets if a lock is present. See item_func.cc and chapter 'Miscellaneous functions', for functions GET_LOCK, RELEASE_LOCK. */ - User_level_lock *ull; + HASH ull_hash; #ifndef DBUG_OFF uint dbug_sentry; // watch out for memory corruption #endif struct st_my_thread_var *mysys_var; +private: /* Type of current query: COM_STMT_PREPARE, COM_QUERY, etc. Set from first byte of the packet in do_command() */ - enum enum_server_command command; - uint32 server_id; + enum enum_server_command m_command; + +public: uint32 file_id; // for LOAD DATA INFILE /* remote (peer) port */ uint16 peer_port; @@ -1755,7 +2179,10 @@ public: uint in_sub_stmt; /* True when opt_userstat_running is set at start of query */ bool userstat_running; - /* True if we want to log all errors */ + /* + True if we have to log all errors. Are set by some engines to temporary + force errors to the error log. + */ bool log_all_errors; /* Do not set socket timeouts for wait_timeout (used with threadpool) */ @@ -1772,6 +2199,9 @@ public: */ bool create_tmp_table_for_derived; + /* The flag to force reading statistics from EITS tables */ + bool force_read_stats; + bool save_prep_leaf_list; /* container for handler's private per-connection data */ @@ -1788,27 +2218,23 @@ public: int binlog_write_table_map(TABLE *table, bool is_transactional, my_bool *with_annotate= 0); int binlog_write_row(TABLE* table, bool is_transactional, - MY_BITMAP const* cols, size_t colcnt, const uchar *buf); int binlog_delete_row(TABLE* table, bool is_transactional, - MY_BITMAP const* cols, size_t colcnt, const uchar *buf); int binlog_update_row(TABLE* table, bool is_transactional, - MY_BITMAP const* cols, size_t colcnt, const uchar *old_data, const uchar *new_data); + static void binlog_prepare_row_images(TABLE* table); - void set_server_id(uint32 sid) { server_id = sid; } + void set_server_id(uint32 sid) { variables.server_id = sid; } /* Member functions to handle pending event for row-level logging. */ template <class RowsEventT> Rows_log_event* binlog_prepare_pending_rows_event(TABLE* table, uint32 serv_id, - MY_BITMAP const* cols, - size_t colcnt, size_t needed, bool is_transactional, - RowsEventT* hint); + RowsEventT* hint); Rows_log_event* binlog_get_pending_rows_event(bool is_transactional) const; void binlog_set_pending_rows_event(Rows_log_event* ev, bool is_transactional); inline int binlog_flush_pending_rows_event(bool stmt_end) @@ -1845,8 +2271,46 @@ public: !mysql_bin_log.is_open()); } + enum binlog_filter_state + { + BINLOG_FILTER_UNKNOWN, + BINLOG_FILTER_CLEAR, + BINLOG_FILTER_SET + }; + + inline void reset_binlog_local_stmt_filter() + { + m_binlog_filter_state= BINLOG_FILTER_UNKNOWN; + } + + inline void clear_binlog_local_stmt_filter() + { + DBUG_ASSERT(m_binlog_filter_state == BINLOG_FILTER_UNKNOWN); + m_binlog_filter_state= BINLOG_FILTER_CLEAR; + } + + inline void set_binlog_local_stmt_filter() + { + DBUG_ASSERT(m_binlog_filter_state == BINLOG_FILTER_UNKNOWN); + m_binlog_filter_state= BINLOG_FILTER_SET; + } + + inline binlog_filter_state get_binlog_local_stmt_filter() + { + return m_binlog_filter_state; + } + private: /** + Indicate if the current statement should be discarded + instead of written to the binlog. + This is used to discard special statements, such as + DML or DDL that affects only 'local' (non replicated) + tables, such as performance_schema.* + */ + binlog_filter_state m_binlog_filter_state; + + /** Indicates the format in which the current statement will be logged. This can only be set from @c decide_logging_format(). */ @@ -1924,7 +2388,8 @@ public: { bzero((char*)this, sizeof(*this)); xid_state.xid.null(); - init_sql_alloc(&mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0); + init_sql_alloc(&mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0, + MYF(MY_THREAD_SPECIFIC)); } } transaction; Global_read_lock global_read_lock; @@ -2148,11 +2613,12 @@ public: ha_rows cuted_fields; +private: /* number of rows we actually sent to the client, including "synthetic" rows in ROLLUP etc. */ - ha_rows sent_row_count; + ha_rows m_sent_row_count; /** Number of rows read and/or evaluated for a statement. Used for @@ -2164,12 +2630,42 @@ public: statement including ORDER BY could possibly evaluate the row in filesort() before reading it for e.g. update. */ - ha_rows examined_row_count; + ha_rows m_examined_row_count; + +public: + ha_rows get_sent_row_count() const + { return m_sent_row_count; } + + ha_rows get_examined_row_count() const + { return m_examined_row_count; } + + void set_sent_row_count(ha_rows count); + void set_examined_row_count(ha_rows count); + + void inc_sent_row_count(ha_rows count); + void inc_examined_row_count(ha_rows count); + + void inc_status_created_tmp_disk_tables(); + void inc_status_created_tmp_files(); + void inc_status_created_tmp_tables(); + void inc_status_select_full_join(); + void inc_status_select_full_range_join(); + void inc_status_select_range(); + void inc_status_select_range_check(); + void inc_status_select_scan(); + void inc_status_sort_merge_passes(); + void inc_status_sort_range(); + void inc_status_sort_rows(ha_rows count); + void inc_status_sort_scan(); + void set_status_no_index_used(); + void set_status_no_good_index_used(); + /** The number of rows and/or keys examined by the query, both read, changed or written. */ ulonglong accessed_rows_and_keys; + /** Check if the number of rows accessed by a statement exceeded LIMIT ROWS EXAMINED. If so, signal the query engine to stop execution. @@ -2177,17 +2673,35 @@ public: void check_limit_rows_examined() { if (++accessed_rows_and_keys > lex->limit_rows_examined_cnt) - killed= ABORT_QUERY; + set_killed(ABORT_QUERY); } USER_CONN *user_connect; CHARSET_INFO *db_charset; - Warning_info *warning_info; - Diagnostics_area *stmt_da; #if defined(ENABLED_PROFILING) PROFILING profiling; #endif + /** Current statement digest. */ + sql_digest_state *m_digest; + /** Current statement digest token array. */ + unsigned char *m_token_array; + /** Top level statement digest. */ + sql_digest_state m_digest_state; + + /** Current statement instrumentation. */ + PSI_statement_locker *m_statement_psi; +#ifdef HAVE_PSI_STATEMENT_INTERFACE + /** Current statement instrumentation state. */ + PSI_statement_locker_state m_statement_state; +#endif /* HAVE_PSI_STATEMENT_INTERFACE */ + /** Idle instrumentation. */ + PSI_idle_locker *m_idle_psi; +#ifdef HAVE_PSI_IDLE_INTERFACE + /** Idle instrumentation state. */ + PSI_idle_locker_state m_idle_state; +#endif /* HAVE_PSI_IDLE_INTERFACE */ + /* Id of current query. Statement can be reused to execute several queries query_id is global in context of the whole MySQL server. @@ -2206,10 +2720,10 @@ public: ulong query_plan_fsort_passes; pthread_t real_id; /* For debugging */ my_thread_id thread_id; + uint32 os_thread_id; uint tmp_table, global_disable_checkpoint; uint server_status,open_options; enum enum_thread_type system_thread; - uint select_number; //number of select (used for EXPLAIN) /* Current or next transaction isolation level. When a connection is established, the value is taken from @@ -2235,12 +2749,23 @@ public: above. */ enum_tx_isolation tx_isolation; + /* + Current or next transaction access mode. + See comment above regarding tx_isolation. + */ + bool tx_read_only; enum_check_fields count_cuted_fields; DYNAMIC_ARRAY user_var_events; /* For user variables replication */ MEM_ROOT *user_var_events_alloc; /* Allocate above array elements here */ /* + Define durability properties that engines may check to + improve performance. Not yet used in MariaDB + */ + enum durability_properties durability_property; + + /* If checking this in conjunction with a wait condition, please include a check after enter_cond() if you want to avoid a race condition. For details see the implementation of awake(), @@ -2248,22 +2773,48 @@ public: */ killed_state volatile killed; + /* + The following is used if one wants to have a specific error number and + text for the kill + */ + struct err_info + { + int no; + const char msg[256]; + } *killed_err; + + /* See also thd_killed() */ + inline bool check_killed() + { + if (killed) + return TRUE; + if (apc_target.have_apc_requests()) + apc_target.process_apc_requests(); + return FALSE; + } + /* scramble - random string sent to client on handshake */ char scramble[SCRAMBLE_LENGTH+1]; - bool slave_thread, one_shot_set; + /* + If this is a slave, the name of the connection stored here. + This is used for taging error messages in the log files. + */ + LEX_STRING connection_name; + char default_master_connection_buff[MAX_CONNECTION_NAME+1]; + uint8 password; /* 0, 1 or 2 */ + uint8 failed_com_change_user; + bool slave_thread; bool extra_port; /* If extra connection */ bool no_errors; - uint8 password; - uint8 failed_com_change_user; /** Set to TRUE if execution of the current compound statement can not continue. In particular, disables activation of CONTINUE or EXIT handlers of stored routines. Reset in the end of processing of the current user request, in - @see mysql_reset_thd_for_next_command(). + @see THD::reset_for_next_command(). */ bool is_fatal_error; /** @@ -2289,13 +2840,6 @@ public: /* for IS NULL => = last_insert_id() fix in remove_eq_conds() */ bool substitute_null_with_insert_id; bool in_lock_tables; - /** - True if a slave error. Causes the slave to stop. Not the same - as the statement execution error (is_error()), since - a statement may be expected to return an error, e.g. because - it returned an error on master, and this is OK on the slave. - */ - bool is_slave_error; bool bootstrap, cleanup_done; /** is set if some thread specific value(s) used in a statement. */ @@ -2312,6 +2856,32 @@ public: /* set during loop of derived table processing */ bool derived_tables_processing; bool tablespace_op; /* This is TRUE in DISCARD/IMPORT TABLESPACE */ + /* True if we have to log the current statement */ + bool log_current_statement; + /** + True if a slave error. Causes the slave to stop. Not the same + as the statement execution error (is_error()), since + a statement may be expected to return an error, e.g. because + it returned an error on master, and this is OK on the slave. + */ + bool is_slave_error; + /* + True when a transaction is queued up for binlog group commit. + Used so that if another transaction needs to wait for a row lock held by + this transaction, it can signal to trigger the group commit immediately, + skipping the normal --binlog-commit-wait-count wait. + */ + bool waiting_on_group_commit; + /* + Set true when another transaction goes to wait on a row lock held by this + transaction. Used together with waiting_on_group_commit. + */ + bool has_waiter; + /* + In case of a slave, set to the error code the master got when executing + the query. 0 if no error on the master. + */ + int slave_expected_error; sp_rcontext *spcont; // SP runtime context sp_cache *sp_proc_cache; @@ -2346,10 +2916,13 @@ public: union { my_bool my_bool_value; + int int_value; + uint uint_value; long long_value; ulong ulong_value; ulonglong ulonglong_value; double double_value; + void *ptr_value; } sys_var_tmp; struct { @@ -2378,6 +2951,7 @@ public: query_id_t first_query_id; } binlog_evt_union; + mysql_cond_t COND_wsrep_thd; /** Internal parser state. Note that since the parser is not re-entrant, we keep only one parser @@ -2409,7 +2983,8 @@ public: /* Debug Sync facility. See debug_sync.cc. */ struct st_debug_sync_control *debug_sync_control; #endif /* defined(ENABLED_DEBUG_SYNC) */ - THD(); + THD(bool is_wsrep_applier= false); + ~THD(); void init(void); @@ -2447,10 +3022,21 @@ public: void close_active_vio(); #endif void awake(killed_state state_to_set); - + /** Disconnect the associated communication endpoint. */ void disconnect(); + + /* + Allows this thread to serve as a target for others to schedule Async + Procedure Calls on. + + It's possible to schedule any code to be executed this way, by + inheriting from the Apc_call object. Currently, only + Show_explain_request uses this. + */ + Apc_target apc_target; + #ifndef MYSQL_CLIENT enum enum_binlog_query_type { /* The query can be logged in row format or in statement format. */ @@ -2468,22 +3054,23 @@ public: int errcode); #endif - /* - For enter_cond() / exit_cond() to work the mutex must be got before - enter_cond(); this mutex is then released by exit_cond(). - Usage must be: lock mutex; enter_cond(); your code; exit_cond(). - */ - inline const char* enter_cond(mysql_cond_t *cond, mysql_mutex_t* mutex, - const char* msg) + inline void + enter_cond(mysql_cond_t *cond, mysql_mutex_t* mutex, + const PSI_stage_info *stage, PSI_stage_info *old_stage, + const char *src_function, const char *src_file, + int src_line) { - const char* old_msg = proc_info; mysql_mutex_assert_owner(mutex); mysys_var->current_mutex = mutex; mysys_var->current_cond = cond; - proc_info = msg; - return old_msg; + if (old_stage) + backup_stage(old_stage); + if (stage) + enter_stage(stage, src_function, src_file, src_line); } - inline void exit_cond(const char* old_msg) + inline void exit_cond(const PSI_stage_info *stage, + const char *src_function, const char *src_file, + int src_line) { /* Putting the mutex unlock in thd->exit_cond() ensures that @@ -2495,10 +3082,51 @@ public: mysql_mutex_lock(&mysys_var->mutex); mysys_var->current_mutex = 0; mysys_var->current_cond = 0; - proc_info = old_msg; + if (stage) + enter_stage(stage, src_function, src_file, src_line); mysql_mutex_unlock(&mysys_var->mutex); return; } + virtual int is_killed() { return killed; } + virtual THD* get_thd() { return this; } + + /** + A callback to the server internals that is used to address + special cases of the locking protocol. + Invoked when acquiring an exclusive lock, for each thread that + has a conflicting shared metadata lock. + + This function: + - aborts waiting of the thread on a data lock, to make it notice + the pending exclusive lock and back off. + - if the thread is an INSERT DELAYED thread, sends it a KILL + signal to terminate it. + + @note This function does not wait for the thread to give away its + locks. Waiting is done outside for all threads at once. + + @param ctx_in_use The MDL context owner (thread) to wake up. + @param needs_thr_lock_abort Indicates that to wake up thread + this call needs to abort its waiting + on table-level lock. + + @retval TRUE if the thread was woken up + @retval FALSE otherwise. + */ + virtual bool notify_shared_lock(MDL_context_owner *ctx_in_use, + bool needs_thr_lock_abort); + + // End implementation of MDL_context_owner interface. + + inline bool is_strict_mode() const + { + return (bool) (variables.sql_mode & (MODE_STRICT_TRANS_TABLES | + MODE_STRICT_ALL_TABLES)); + } + inline bool backslash_escapes() const + { + return !MY_TEST(variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES); + } inline my_time_t query_start() { query_start_used=1; return start_time; } inline ulong query_start_sec_part() { query_start_sec_part_used=1; return start_time_sec_part; } @@ -2507,6 +3135,9 @@ public: my_hrtime_t hrtime= my_hrtime(); start_time= hrtime_to_my_time(hrtime); start_time_sec_part= hrtime_sec_part(hrtime); +#ifdef HAVE_PSI_THREAD_INTERFACE + PSI_THREAD_CALL(set_thread_start_time)(start_time); +#endif } inline void set_start_time() { @@ -2514,6 +3145,9 @@ public: { start_time= hrtime_to_my_time(user_time); start_time_sec_part= hrtime_sec_part(user_time); +#ifdef HAVE_PSI_THREAD_INTERFACE + PSI_THREAD_CALL(set_thread_start_time)(start_time); +#endif } else set_current_time(); @@ -2523,17 +3157,22 @@ public: set_start_time(); start_utime= utime_after_lock= microsecond_interval_timer(); } - inline void set_time(my_hrtime_t t) + inline void set_time(my_hrtime_t t) { user_time= t; set_time(); } - inline void set_time(my_time_t t, ulong sec_part) + inline void set_time(my_time_t t, ulong sec_part) { my_hrtime_t hrtime= { hrtime_from_time(t) + sec_part }; set_time(hrtime); } - void set_time_after_lock() { utime_after_lock= microsecond_interval_timer(); } + void set_time_after_lock() + { + utime_after_lock= microsecond_interval_timer(); + MYSQL_SET_STATEMENT_LOCK_TIME(m_statement_psi, + (utime_after_lock - start_utime)); + } ulonglong current_utime() { return microsecond_interval_timer(); } /** @@ -2630,20 +3269,78 @@ public: return alloc_root(&transaction.mem_root,size); } - LEX_STRING *make_lex_string(LEX_STRING *lex_str, - const char* str, uint length, - bool allocate_lex_string); + LEX_STRING *make_lex_string(LEX_STRING *lex_str, const char* str, uint length) + { + if (!(lex_str->str= strmake_root(mem_root, str, length))) + return 0; + lex_str->length= length; + return lex_str; + } + + LEX_STRING *make_lex_string(const char* str, uint length) + { + LEX_STRING *lex_str; + if (!(lex_str= (LEX_STRING *)alloc_root(mem_root, sizeof(LEX_STRING)))) + return 0; + return make_lex_string(lex_str, str, length); + } + // Allocate LEX_STRING for character set conversion + bool alloc_lex_string(LEX_STRING *dst, uint length) + { + if ((dst->str= (char*) alloc(length))) + return false; + dst->length= 0; // Safety + return true; // EOM + } bool convert_string(LEX_STRING *to, CHARSET_INFO *to_cs, const char *from, uint from_length, CHARSET_INFO *from_cs); + /* + Convert a strings between character sets. + Uses my_convert_fix(), which uses an mb_wc .. mc_mb loop internally. + dstcs and srccs cannot be &my_charset_bin. + */ + bool convert_fix(CHARSET_INFO *dstcs, LEX_STRING *dst, + CHARSET_INFO *srccs, const char *src, uint src_length, + String_copier *status); + + /* + Same as above, but additionally sends ER_INVALID_CHARACTER_STRING + in case of bad byte sequences or Unicode conversion problems. + */ + bool convert_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst, + CHARSET_INFO *srccs, + const char *src, uint src_length); + + /* + If either "dstcs" or "srccs" is &my_charset_bin, + then performs native copying using cs->cset->copy_fix(). + Otherwise, performs Unicode conversion using convert_fix(). + */ + bool copy_fix(CHARSET_INFO *dstcs, LEX_STRING *dst, + CHARSET_INFO *srccs, const char *src, uint src_length, + String_copier *status); + + /* + Same as above, but additionally sends ER_INVALID_CHARACTER_STRING + in case of bad byte sequences or Unicode conversion problems. + */ + bool copy_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst, + CHARSET_INFO *srccs, const char *src, uint src_length); bool convert_string(String *s, CHARSET_INFO *from_cs, CHARSET_INFO *to_cs); void add_changed_table(TABLE *table); void add_changed_table(const char *key, long key_length); CHANGED_TABLE_LIST * changed_table_dup(const char *key, long key_length); - int send_explain_fields(select_result *result); + int prepare_explain_fields(select_result *result, List<Item> *field_list, + uint8 explain_flags, bool is_analyze); + int send_explain_fields(select_result *result, uint8 explain_flags, + bool is_analyze); + void make_explain_field_list(List<Item> &field_list, uint8 explain_flags, + bool is_analyze); + void make_explain_json_field_list(List<Item> &field_list, bool is_analyze); /** Clear the current error, if any. @@ -2653,18 +3350,18 @@ public: @todo: To silence an error, one should use Internal_error_handler mechanism. Issuing an error that can be possibly later "cleared" is not compatible with other installed error handlers and audit plugins. - In future this function will be removed. */ - inline void clear_error() + inline void clear_error(bool clear_diagnostics= 0) { DBUG_ENTER("clear_error"); - if (stmt_da->is_error()) - stmt_da->reset_diagnostics_area(); + if (get_stmt_da()->is_error() || clear_diagnostics) + get_stmt_da()->reset_diagnostics_area(); is_slave_error= 0; if (killed == KILL_BAD_DATA) - killed= NOT_KILLED; // KILL_BAD_DATA can be reset w/o a mutex + reset_killed(); DBUG_VOID_RETURN; } + #ifndef EMBEDDED_LIBRARY inline bool vio_ok() const { return net.vio != 0; } /** Return FALSE if connection to client is broken. */ @@ -2688,7 +3385,7 @@ public: */ inline void fatal_error() { - DBUG_ASSERT(stmt_da->is_error() || killed); + DBUG_ASSERT(get_stmt_da()->is_error() || killed); is_fatal_error= 1; DBUG_PRINT("error",("Fatal error set")); } @@ -2705,7 +3402,20 @@ public: To raise this flag, use my_error(). */ - inline bool is_error() const { return stmt_da->is_error(); } + inline bool is_error() const { return m_stmt_da->is_error(); } + + /// Returns Diagnostics-area for the current statement. + Diagnostics_area *get_stmt_da() + { return m_stmt_da; } + + /// Returns Diagnostics-area for the current statement. + const Diagnostics_area *get_stmt_da() const + { return m_stmt_da; } + + /// Sets Diagnostics-area for the current statement. + void set_stmt_da(Diagnostics_area *da) + { m_stmt_da= da; } + inline CHARSET_INFO *charset() { return variables.character_set_client; } void update_charset(); @@ -2765,10 +3475,54 @@ public: state after execution of a non-prepared SQL statement. */ void end_statement(); - inline int killed_errno() const + + /* + Mark thread to be killed, with optional error number and string. + string is not released, so it has to be allocted on thd mem_root + or be a global string + + Ensure that we don't replace a kill with a lesser one. For example + if user has done 'kill_connection' we shouldn't replace it with + KILL_QUERY. + */ + inline void set_killed(killed_state killed_arg, + int killed_errno_arg= 0, + const char *killed_err_msg_arg= 0) { - return ::killed_errno(killed); + mysql_mutex_lock(&LOCK_thd_kill); + set_killed_no_mutex(killed_arg, killed_errno_arg, killed_err_msg_arg); + mysql_mutex_unlock(&LOCK_thd_kill); } + /* + This is only used by THD::awake where we need to keep the lock mutex + locked over some time. + It's ok to have this inline, as in most cases killed_errno_arg will + be a constant 0 and most of the function will disappear. + */ + inline void set_killed_no_mutex(killed_state killed_arg, + int killed_errno_arg= 0, + const char *killed_err_msg_arg= 0) + { + if (killed <= killed_arg) + { + killed= killed_arg; + if (killed_errno_arg) + { + /* + If alloc fails, we only remember the killed flag. + The worst things that can happen is that we get + a suboptimal error message. + */ + if ((killed_err= (err_info*) alloc(sizeof(*killed_err)))) + { + killed_err->no= killed_errno_arg; + ::strmake((char*) killed_err->msg, killed_err_msg_arg, + sizeof(killed_err->msg)-1); + } + } + } + } + int killed_errno(); inline void reset_killed() { /* @@ -2777,16 +3531,28 @@ public: */ if (killed != NOT_KILLED) { - mysql_mutex_lock(&LOCK_thd_data); + mysql_mutex_lock(&LOCK_thd_kill); killed= NOT_KILLED; - mysql_mutex_unlock(&LOCK_thd_data); + killed_err= 0; + mysql_mutex_unlock(&LOCK_thd_kill); + } + } + inline void reset_kill_query() + { + if (killed < KILL_CONNECTION) + { + reset_killed(); + mysys_var->abort= 0; } } - inline void send_kill_message() const + inline void send_kill_message() { + mysql_mutex_lock(&LOCK_thd_kill); int err= killed_errno(); if (err) - my_message(err, ER(err), MYF(0)); + my_message(err, killed_err ? killed_err->msg : ER_THD(this, err), + MYF(0)); + mysql_mutex_unlock(&LOCK_thd_kill); } /* return TRUE if we will abort query if we make a warning now */ inline bool really_abort_on_warning() @@ -2803,6 +3569,31 @@ public: void set_n_backup_active_arena(Query_arena *set, Query_arena *backup); void restore_active_arena(Query_arena *set, Query_arena *backup); + inline void get_binlog_format(enum_binlog_format *format, + enum_binlog_format *current_format) + { + *format= (enum_binlog_format) variables.binlog_format; + *current_format= current_stmt_binlog_format; + } + inline enum_binlog_format get_current_stmt_binlog_format() + { + return current_stmt_binlog_format; + } + inline void set_binlog_format(enum_binlog_format format, + enum_binlog_format current_format) + { + DBUG_ENTER("set_binlog_format"); + variables.binlog_format= format; + current_stmt_binlog_format= current_format; + DBUG_VOID_RETURN; + } + inline void set_binlog_format_stmt() + { + DBUG_ENTER("set_binlog_format_stmt"); + variables.binlog_format= BINLOG_FORMAT_STMT; + current_stmt_binlog_format= BINLOG_FORMAT_STMT; + DBUG_VOID_RETURN; + } /* @todo Make these methods private or remove them completely. Only decide_logging_format should call them. /Sven @@ -2827,22 +3618,31 @@ public: tests fail and so force them to propagate the lex->binlog_row_based_if_mixed upwards to the caller. */ - if ((variables.binlog_format == BINLOG_FORMAT_MIXED) && - (in_sub_stmt == 0)) + if ((wsrep_binlog_format() == BINLOG_FORMAT_MIXED) && (in_sub_stmt == 0)) set_current_stmt_binlog_format_row(); DBUG_VOID_RETURN; } + inline void set_current_stmt_binlog_format_row() { DBUG_ENTER("set_current_stmt_binlog_format_row"); current_stmt_binlog_format= BINLOG_FORMAT_ROW; DBUG_VOID_RETURN; } - inline void clear_current_stmt_binlog_format_row() + /* Set binlog format temporarily to statement. Returns old format */ + inline enum_binlog_format set_current_stmt_binlog_format_stmt() { - DBUG_ENTER("clear_current_stmt_binlog_format_row"); + enum_binlog_format orig_format= current_stmt_binlog_format; + DBUG_ENTER("set_current_stmt_binlog_format_stmt"); current_stmt_binlog_format= BINLOG_FORMAT_STMT; + DBUG_RETURN(orig_format); + } + inline void restore_stmt_binlog_format(enum_binlog_format format) + { + DBUG_ENTER("restore_stmt_binlog_format"); + DBUG_ASSERT(!is_current_stmt_binlog_format_row()); + current_stmt_binlog_format= format; DBUG_VOID_RETURN; } inline void reset_current_stmt_binlog_format_row() @@ -2869,10 +3669,10 @@ public: show_system_thread(system_thread))); if (in_sub_stmt == 0) { - if (variables.binlog_format == BINLOG_FORMAT_ROW) + if (wsrep_binlog_format() == BINLOG_FORMAT_ROW) set_current_stmt_binlog_format_row(); else if (temporary_tables == NULL) - clear_current_stmt_binlog_format_row(); + set_current_stmt_binlog_format_stmt(); } DBUG_VOID_RETURN; } @@ -2918,8 +3718,13 @@ public: db= NULL; } db_length= db ? new_db_len : 0; + bool result= new_db && !db; mysql_mutex_unlock(&LOCK_thd_data); - return new_db && !db; +#ifdef HAVE_PSI_THREAD_INTERFACE + if (result) + PSI_THREAD_CALL(set_thread_db)(new_db, new_db_len); +#endif + return result; } /** @@ -2941,6 +3746,9 @@ public: db= new_db; db_length= new_db_len; mysql_mutex_unlock(&LOCK_thd_data); +#ifdef HAVE_PSI_THREAD_INTERFACE + PSI_THREAD_CALL(set_thread_db)(new_db, new_db_len); +#endif } } /* @@ -2971,6 +3779,7 @@ public: */ void push_internal_handler(Internal_error_handler *handler); +private: /** Handle a sql condition. @param sql_errno the condition error number @@ -2980,12 +3789,13 @@ public: @param[out] cond_hdl the sql condition raised, if any @return true if the condition is handled */ - virtual bool handle_condition(uint sql_errno, - const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char* msg, - MYSQL_ERROR ** cond_hdl); + bool handle_condition(uint sql_errno, + const char* sqlstate, + Sql_condition::enum_warning_level level, + const char* msg, + Sql_condition ** cond_hdl); +public: /** Remove the error handler last pushed. */ @@ -3035,10 +3845,10 @@ private: To raise a SQL condition, the code should use the public raise_error() or raise_warning() methods provided by class THD. */ - friend class Signal_common; - friend class Signal_statement; - friend class Resignal_statement; - friend void push_warning(THD*, MYSQL_ERROR::enum_warning_level, uint, const char*); + friend class Sql_cmd_common_signal; + friend class Sql_cmd_signal; + friend class Sql_cmd_resignal; + friend void push_warning(THD*, Sql_condition::enum_warning_level, uint, const char*); friend void my_message_sql(uint, const char *, myf); /** @@ -3049,15 +3859,24 @@ private: @param msg the condition message text @return The condition raised, or NULL */ - MYSQL_ERROR* + Sql_condition* raise_condition(uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg); public: /** Overloaded to guard query/query_length fields */ virtual void set_statement(Statement *stmt); + void set_command(enum enum_server_command command) + { + m_command= command; +#ifdef HAVE_PSI_THREAD_INTERFACE + PSI_STATEMENT_CALL(set_thread_command)(m_command); +#endif + } + inline enum enum_server_command get_command() const + { return m_command; } /** Assign a new value to thd->query and thd->query_id and mysys_var. @@ -3072,12 +3891,24 @@ public: { set_query(CSET_STRING(query_arg, query_length_arg, charset())); } - void set_query(const CSET_STRING &str); /* Mutex protected */ + void set_query(const CSET_STRING &string_arg) + { + mysql_mutex_lock(&LOCK_thd_data); + set_query_inner(string_arg); + mysql_mutex_unlock(&LOCK_thd_data); + +#ifdef HAVE_PSI_THREAD_INTERFACE + PSI_THREAD_CALL(set_thread_info)(query(), query_length()); +#endif + } void reset_query() /* Mutex protected */ { set_query(CSET_STRING()); } void set_query_and_id(char *query_arg, uint32 query_length_arg, CHARSET_INFO *cs, query_id_t new_query_id); - void set_query_id(query_id_t new_query_id); + void set_query_id(query_id_t new_query_id) + { + query_id= new_query_id; + } void set_open_tables(TABLE *open_tables_arg) { mysql_mutex_lock(&LOCK_thd_data); @@ -3106,9 +3937,11 @@ public: } void leave_locked_tables_mode(); int decide_logging_format(TABLE_LIST *tables); - void binlog_invoker() { m_binlog_invoker= TRUE; } - bool need_binlog_invoker() { return m_binlog_invoker; } - void get_definer(LEX_USER *definer); + + enum need_invoker { INVOKER_NONE=0, INVOKER_USER, INVOKER_ROLE}; + void binlog_invoker(bool role) { m_binlog_invoker= role ? INVOKER_ROLE : INVOKER_USER; } + enum need_invoker need_binlog_invoker() { return m_binlog_invoker; } + void get_definer(LEX_USER *definer, bool role); void set_invoker(const LEX_STRING *user, const LEX_STRING *host) { invoker_user= *user; @@ -3142,13 +3975,37 @@ public: void add_status_to_global() { + DBUG_ASSERT(status_in_global == 0); mysql_mutex_lock(&LOCK_status); add_to_status(&global_status_var, &status_var); /* Mark that this THD status has already been added in global status */ + status_var.global_memory_used= 0; status_in_global= 1; mysql_mutex_unlock(&LOCK_status); } + wait_for_commit *wait_for_commit_ptr; + int wait_for_prior_commit() + { + if (wait_for_commit_ptr) + return wait_for_commit_ptr->wait_for_prior_commit(this); + return 0; + } + void wakeup_subsequent_commits(int wakeup_error) + { + if (wait_for_commit_ptr) + wait_for_commit_ptr->wakeup_subsequent_commits(wakeup_error); + } + wait_for_commit *suspend_subsequent_commits() { + wait_for_commit *suspended= wait_for_commit_ptr; + wait_for_commit_ptr= NULL; + return suspended; + } + void resume_subsequent_commits(wait_for_commit *suspended) { + DBUG_ASSERT(!wait_for_commit_ptr); + wait_for_commit_ptr= suspended; + } + void mark_transaction_to_rollback(bool all); private: @@ -3171,18 +4028,19 @@ private: tree itself is reused between executions and thus is stored elsewhere. */ MEM_ROOT main_mem_root; - Warning_info main_warning_info; Diagnostics_area main_da; + Diagnostics_area *m_stmt_da; /** - It will be set TURE if CURRENT_USER() is called in account management - statements or default definer is set in CREATE/ALTER SP, SF, Event, - TRIGGER or VIEW statements. + It will be set if CURRENT_USER() or CURRENT_ROLE() is called in account + management statements or default definer is set in CREATE/ALTER SP, SF, + Event, TRIGGER or VIEW statements. - Current user will be binlogged into Query_log_event if m_binlog_invoker - is TRUE; It will be stored into invoker_host and invoker_user by SQL thread. + Current user or role will be binlogged into Query_log_event if + m_binlog_invoker is not NONE; It will be stored into invoker_host and + invoker_user by SQL thread. */ - bool m_binlog_invoker; + enum need_invoker m_binlog_invoker; /** It points to the invoker in the Query_log_event. @@ -3192,6 +4050,12 @@ private: */ LEX_STRING invoker_user; LEX_STRING invoker_host; + + /* Protect against add/delete of temporary tables in parallel replication */ + void rgi_lock_temporary_tables(); + void rgi_unlock_temporary_tables(bool clear); + bool rgi_have_temporary_tables(); +public: /* Flag, mutex and condition for a thread to wait for a signal from another thread. @@ -3202,55 +4066,162 @@ private: bool wakeup_ready; mysql_mutex_t LOCK_wakeup_ready; mysql_cond_t COND_wakeup_ready; + /* + The GTID assigned to the last commit. If no GTID was assigned to any commit + so far, this is indicated by last_commit_gtid.seq_no == 0. + */ + rpl_gtid last_commit_gtid; + + inline void lock_temporary_tables() + { + if (rgi_slave) + rgi_lock_temporary_tables(); + } + inline void unlock_temporary_tables(bool clear) + { + if (rgi_slave) + rgi_unlock_temporary_tables(clear); + } + inline bool have_temporary_tables() + { + return (temporary_tables || + (rgi_slave && unlikely(rgi_have_temporary_tables()))); + } + + LF_PINS *tdc_hash_pins; + LF_PINS *xid_hash_pins; + bool fix_xid_hash_pins(); + + inline ulong wsrep_binlog_format() const + { + return WSREP_FORMAT(variables.binlog_format); + } + +#ifdef WITH_WSREP + const bool wsrep_applier; /* dedicated slave applier thread */ + bool wsrep_applier_closing; /* applier marked to close */ + bool wsrep_client_thread; /* to identify client threads*/ + bool wsrep_PA_safe; + bool wsrep_converted_lock_session; + bool wsrep_apply_toi; /* applier processing in TOI */ + enum wsrep_exec_mode wsrep_exec_mode; + query_id_t wsrep_last_query_id; + enum wsrep_query_state wsrep_query_state; + enum wsrep_conflict_state wsrep_conflict_state; + wsrep_trx_meta_t wsrep_trx_meta; + uint32 wsrep_rand; + Relay_log_info *wsrep_rli; + rpl_group_info *wsrep_rgi; + wsrep_ws_handle_t wsrep_ws_handle; + ulong wsrep_retry_counter; // of autocommit + char *wsrep_retry_query; + size_t wsrep_retry_query_len; + enum enum_server_command wsrep_retry_command; + enum wsrep_consistency_check_mode + wsrep_consistency_check; + int wsrep_mysql_replicated; + const char *wsrep_TOI_pre_query; /* a query to apply before + the actual TOI query */ + size_t wsrep_TOI_pre_query_len; + wsrep_po_handle_t wsrep_po_handle; + size_t wsrep_po_cnt; +#ifdef GTID_SUPPORT + rpl_sid wsrep_po_sid; +#endif /* GTID_SUPPORT */ + void *wsrep_apply_format; + char wsrep_info[128]; /* string for dynamic proc info */ + /* + When enabled, do not replicate/binlog updates from the current table that's + being processed. At the moment, it is used to keep mysql.gtid_slave_pos + table updates from being replicated to other nodes via galera replication. + */ + bool wsrep_ignore_table; + wsrep_gtid_t wsrep_sync_wait_gtid; + ulong wsrep_affected_rows; + bool wsrep_replicate_GTID; + bool wsrep_skip_wsrep_GTID; +#endif /* WITH_WSREP */ + + /* Handling of timeouts for commands */ + thr_timer_t query_timer; +public: + void set_query_timer() + { +#ifndef EMBEDDED_LIBRARY + /* + Don't start a query timer if + - If timeouts are not set + - if we are in a stored procedure or sub statement + - If this is a slave thread + - If we already have set a timeout (happens when running prepared + statements that calls mysql_execute_command()) + */ + if (!variables.max_statement_time || spcont || in_sub_stmt || + slave_thread || query_timer.expired == 0) + return; + thr_timer_settime(&query_timer, variables.max_statement_time); +#endif + } + void reset_query_timer() + { +#ifndef EMBEDDED_LIBRARY + if (spcont || in_sub_stmt || slave_thread) + return; + if (!query_timer.expired) + thr_timer_end(&query_timer); +#endif + } + void restore_set_statement_var() + { + main_lex.restore_set_statement_var(); + } + /* Copy relevant `stmt` transaction flags to `all` transaction. */ + void merge_unsafe_rollback_flags() + { + if (transaction.stmt.modified_non_trans_table) + transaction.all.modified_non_trans_table= TRUE; + transaction.all.m_unsafe_rollback_flags|= + (transaction.stmt.m_unsafe_rollback_flags & + (THD_TRANS::DID_WAIT | THD_TRANS::CREATED_TEMP_TABLE | + THD_TRANS::DROPPED_TEMP_TABLE | THD_TRANS::DID_DDL)); + } }; -/** A short cut for thd->stmt_da->set_ok_status(). */ +/** A short cut for thd->get_stmt_da()->set_ok_status(). */ inline void my_ok(THD *thd, ulonglong affected_rows= 0, ulonglong id= 0, const char *message= NULL) { thd->set_row_count_func(affected_rows); - thd->stmt_da->set_ok_status(thd, affected_rows, id, message); + thd->get_stmt_da()->set_ok_status(affected_rows, id, message); } -/** A short cut for thd->stmt_da->set_eof_status(). */ +/** A short cut for thd->get_stmt_da()->set_eof_status(). */ inline void my_eof(THD *thd) { thd->set_row_count_func(-1); - thd->stmt_da->set_eof_status(thd); + thd->get_stmt_da()->set_eof_status(thd); } -#define tmp_disable_binlog(A) \ +#define tmp_disable_binlog(A) \ {ulonglong tmp_disable_binlog__save_options= (A)->variables.option_bits; \ - (A)->variables.option_bits&= ~OPTION_BIN_LOG - -#define reenable_binlog(A) (A)->variables.option_bits= tmp_disable_binlog__save_options;} - - -/* - These functions are for making it later easy to add strict - checking for all date handling. -*/ + (A)->variables.option_bits&= ~OPTION_BIN_LOG; \ + (A)->variables.sql_log_bin_off= 1; -const my_bool strict_date_checking= 0; +#define reenable_binlog(A) \ + (A)->variables.option_bits= tmp_disable_binlog__save_options; \ + (A)->variables.sql_log_bin_off= 0;} -inline ulong sql_mode_for_dates(THD *thd) -{ - if (strict_date_checking) - return (thd->variables.sql_mode & - (MODE_NO_ZERO_DATE | MODE_NO_ZERO_IN_DATE | - MODE_INVALID_DATES)); - return (thd->variables.sql_mode & MODE_INVALID_DATES); -} -inline ulong sql_mode_for_dates() +inline sql_mode_t sql_mode_for_dates(THD *thd) { - return sql_mode_for_dates(current_thd); + return thd->variables.sql_mode & + (MODE_NO_ZERO_DATE | MODE_NO_ZERO_IN_DATE | MODE_INVALID_DATES); } /* @@ -3280,13 +4251,64 @@ public: class JOIN; -class select_result :public Sql_alloc { -protected: +/* Pure interface for sending tabular data */ +class select_result_sink: public Sql_alloc +{ +public: THD *thd; + select_result_sink(THD *thd_arg): thd(thd_arg) {} + /* + send_data returns 0 on ok, 1 on error and -1 if data was ignored, for + example for a duplicate row entry written to a temp table. + */ + virtual int send_data(List<Item> &items)=0; + virtual ~select_result_sink() {}; +}; + +class select_result_interceptor; + +/* + Interface for sending tabular data, together with some other stuff: + + - Primary purpose seems to be seding typed tabular data: + = the DDL is sent with send_fields() + = the rows are sent with send_data() + Besides that, + - there seems to be an assumption that the sent data is a result of + SELECT_LEX_UNIT *unit, + - nest_level is used by SQL parser +*/ + +class select_result :public select_result_sink +{ +protected: + /* + All descendant classes have their send_data() skip the first + unit->offset_limit_cnt rows sent. Select_materialize + also uses unit->get_unit_column_types(). + */ SELECT_LEX_UNIT *unit; + /* Something used only by the parser: */ public: - select_result(); + select_result(THD *thd_arg): select_result_sink(thd_arg) {} virtual ~select_result() {}; + /** + Change wrapped select_result. + + Replace the wrapped result object with new_result and call + prepare() and prepare2() on new_result. + + This base class implementation doesn't wrap other select_results. + + @param new_result The new result object to wrap around + + @retval false Success + @retval true Error + */ + virtual bool change_result(select_result *new_result) + { + return false; + } virtual int prepare(List<Item> &list, SELECT_LEX_UNIT *u) { unit= u; @@ -3301,13 +4323,7 @@ public: virtual uint field_count(List<Item> &fields) const { return fields.elements; } virtual bool send_result_set_metadata(List<Item> &list, uint flags)=0; - /* - send_data returns 0 on ok, 1 on error and -1 if data was ignored, for - example for a duplicate row entry written to a temp table. - */ - virtual int send_data(List<Item> &items)=0; virtual bool initialize_tables (JOIN *join=0) { return 0; } - virtual void send_error(uint errcode,const char *err); virtual bool send_eof()=0; /** Check if this query returns a result set and therefore is allowed in @@ -3330,6 +4346,64 @@ public: void begin_dataset() {} #endif virtual void update_used_tables() {} + + void reset_offset_limit() + { + unit->offset_limit_cnt= 0; + } + + /* + This returns + - NULL if the class sends output row to the client + - this if the output is set elsewhere (a file, @variable, or table). + */ + virtual select_result_interceptor *result_interceptor()=0; +}; + + +/* + This is a select_result_sink which simply writes all data into a (temporary) + table. Creation/deletion of the table is outside of the scope of the class + + It is aimed at capturing SHOW EXPLAIN output, so: + - Unlike select_result class, we don't assume that the sent data is an + output of a SELECT_LEX_UNIT (and so we dont apply "LIMIT x,y" from the + unit) + - We don't try to convert the target table to MyISAM +*/ + +class select_result_explain_buffer : public select_result_sink +{ +public: + select_result_explain_buffer(THD *thd_arg, TABLE *table_arg) : + select_result_sink(thd_arg), dst_table(table_arg) {}; + + TABLE *dst_table; /* table to write into */ + + /* The following is called in the child thread: */ + int send_data(List<Item> &items); +}; + + +/* + This is a select_result_sink which stores the data in text form. + + It is only used to save EXPLAIN output. +*/ + +class select_result_text_buffer : public select_result_sink +{ +public: + select_result_text_buffer(THD *thd_arg): select_result_sink(thd_arg) {} + int send_data(List<Item> &items); + bool send_result_set_metadata(List<Item> &fields, uint flag); + + void save_to(String *res); +private: + int append_row(List<Item> &items, bool send_names); + + List<char*> rows; + int n_columns; }; @@ -3342,14 +4416,24 @@ public: class select_result_interceptor: public select_result { public: - select_result_interceptor() + select_result_interceptor(THD *thd_arg): + select_result(thd_arg), suppress_my_ok(false) { DBUG_ENTER("select_result_interceptor::select_result_interceptor"); - DBUG_PRINT("enter", ("this 0x%lx", (ulong) this)); + DBUG_PRINT("enter", ("this %p", this)); DBUG_VOID_RETURN; } /* Remove gcc warning */ uint field_count(List<Item> &fields) const { return 0; } bool send_result_set_metadata(List<Item> &fields, uint flag) { return FALSE; } + select_result_interceptor *result_interceptor() { return this; } + + /* + Instruct the object to not call my_ok(). Client output will be handled + elsewhere. (this is used by ANALYZE $stmt feature). + */ + void disable_my_ok_calls() { suppress_my_ok= true; } +protected: + bool suppress_my_ok; }; @@ -3361,13 +4445,31 @@ class select_send :public select_result { */ bool is_result_set_started; public: - select_send() :is_result_set_started(FALSE) {} + select_send(THD *thd_arg): + select_result(thd_arg), is_result_set_started(FALSE) {} bool send_result_set_metadata(List<Item> &list, uint flags); int send_data(List<Item> &items); bool send_eof(); virtual bool check_simple_select() const { return FALSE; } void abort_result_set(); virtual void cleanup(); + select_result_interceptor *result_interceptor() { return NULL; } +}; + + +/* + We need this class, because select_send::send_eof() will call ::my_eof. + + See also class Protocol_discard. +*/ + +class select_send_analyze : public select_send +{ + bool send_result_set_metadata(List<Item> &list, uint flags) { return 0; } + bool send_eof() { return 0; } + void abort_result_set() {} +public: + select_send_analyze(THD *thd_arg): select_send(thd_arg) {} }; @@ -3380,10 +4482,10 @@ protected: char path[FN_REFLEN]; public: - select_to_file(sql_exchange *ex) :exchange(ex), file(-1),row_count(0L) + select_to_file(THD *thd_arg, sql_exchange *ex): + select_result_interceptor(thd_arg), exchange(ex), file(-1),row_count(0L) { path[0]=0; } ~select_to_file(); - void send_error(uint errcode,const char *err); bool send_eof(); void cleanup(); }; @@ -3423,7 +4525,7 @@ class select_export :public select_to_file { bool fixed_row_size; CHARSET_INFO *write_cs; // output charset public: - select_export(sql_exchange *ex) :select_to_file(ex) {} + select_export(THD *thd_arg, sql_exchange *ex): select_to_file(thd_arg, ex) {} ~select_export(); int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int send_data(List<Item> &items); @@ -3432,7 +4534,7 @@ public: class select_dump :public select_to_file { public: - select_dump(sql_exchange *ex) :select_to_file(ex) {} + select_dump(THD *thd_arg, sql_exchange *ex): select_to_file(thd_arg, ex) {} int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int send_data(List<Item> &items); }; @@ -3446,7 +4548,7 @@ class select_insert :public select_result_interceptor { ulonglong autoinc_value_of_last_inserted_row; // autogenerated or not COPY_INFO info; bool insert_into_view; - select_insert(TABLE_LIST *table_list_par, + select_insert(THD *thd_arg, TABLE_LIST *table_list_par, TABLE *table_par, List<Item> *fields_par, List<Item> *update_fields, List<Item> *update_values, enum_duplicates duplic, bool ignore); @@ -3456,7 +4558,8 @@ class select_insert :public select_result_interceptor { virtual int send_data(List<Item> &items); virtual void store_values(List<Item> &values); virtual bool can_rollback_data() { return 0; } - void send_error(uint errcode,const char *err); + bool prepare_eof(); + bool send_ok_packet(); bool send_eof(); virtual void abort_result_set(); /* not implemented: select_insert is never re-used in prepared statements */ @@ -3465,9 +4568,8 @@ class select_insert :public select_result_interceptor { class select_create: public select_insert { - ORDER *group; TABLE_LIST *create_table; - HA_CREATE_INFO *create_info; + Table_specification_st *create_info; TABLE_LIST *select_tables; Alter_info *alter_info; Field **field; @@ -3475,24 +4577,25 @@ class select_create: public select_insert { MYSQL_LOCK *m_lock; /* m_lock or thd->extra_lock */ MYSQL_LOCK **m_plock; + bool exit_done; + public: - select_create (TABLE_LIST *table_arg, - HA_CREATE_INFO *create_info_par, - Alter_info *alter_info_arg, - List<Item> &select_fields,enum_duplicates duplic, bool ignore, - TABLE_LIST *select_tables_arg) - :select_insert (NULL, NULL, &select_fields, 0, 0, duplic, ignore), + select_create(THD *thd_arg, TABLE_LIST *table_arg, + Table_specification_st *create_info_par, + Alter_info *alter_info_arg, + List<Item> &select_fields,enum_duplicates duplic, bool ignore, + TABLE_LIST *select_tables_arg): + select_insert(thd_arg, NULL, NULL, &select_fields, 0, 0, duplic, ignore), create_table(table_arg), create_info(create_info_par), select_tables(select_tables_arg), alter_info(alter_info_arg), - m_plock(NULL) + m_plock(NULL), exit_done(0) {} int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int binlog_show_create_table(TABLE **tables, uint count); void store_values(List<Item> &values); - void send_error(uint errcode,const char *err); bool send_eof(); virtual void abort_result_set(); virtual bool can_rollback_data() { return 1; } @@ -3507,12 +4610,22 @@ public: #ifdef WITH_ARIA_STORAGE_ENGINE #include <maria.h> +#else +#undef USE_ARIA_FOR_TMP_TABLES #endif #ifdef USE_ARIA_FOR_TMP_TABLES -#define ENGINE_COLUMNDEF MARIA_COLUMNDEF +#define TMP_ENGINE_COLUMNDEF MARIA_COLUMNDEF +#define TMP_ENGINE_HTON maria_hton +#define TMP_ENGINE_NAME "Aria" +inline uint tmp_table_max_key_length() { return maria_max_key_length(); } +inline uint tmp_table_max_key_parts() { return maria_max_key_segments(); } #else -#define ENGINE_COLUMNDEF MI_COLUMNDEF +#define TMP_ENGINE_COLUMNDEF MI_COLUMNDEF +#define TMP_ENGINE_HTON myisam_hton +#define TMP_ENGINE_NAME "MyISAM" +inline uint tmp_table_max_key_length() { return MI_MAX_KEY_LENGTH; } +inline uint tmp_table_max_key_parts() { return MI_MAX_KEY_SEG; } #endif /* @@ -3535,7 +4648,7 @@ public: Copy_field *save_copy_field, *save_copy_field_end; uchar *group_buff; Item **items_to_copy; /* Fields in tmp table */ - ENGINE_COLUMNDEF *recinfo, *start_recinfo; + TMP_ENGINE_COLUMNDEF *recinfo, *start_recinfo; KEY *keyinfo; ha_rows end_write_records; /** @@ -3568,8 +4681,6 @@ public: uint group_parts,group_length,group_null_parts; uint quick_group; bool using_indirect_summary_function; - /* If >0 convert all blob fields to varchar(convert_blob_length) */ - uint convert_blob_length; CHARSET_INFO *table_charset; bool schema_table; /* TRUE if the temp table is created for subquery materialization. */ @@ -3598,7 +4709,7 @@ public: TMP_TABLE_PARAM() :copy_field(0), group_parts(0), - group_length(0), group_null_parts(0), convert_blob_length(0), + group_length(0), group_null_parts(0), schema_table(0), materialized_subquery(0), force_not_null_cols(0), precomputed_group_by(0), force_copy_fields(0), bit_fields_as_long(0), skip_create_table(0) @@ -3633,11 +4744,23 @@ public: TABLE *table; ha_rows records; - select_union() :write_err(0), table(0), records(0) { tmp_table_param.init(); } + select_union(THD *thd_arg): + select_result_interceptor(thd_arg), write_err(0), table(0), records(0) + { tmp_table_param.init(); } int prepare(List<Item> &list, SELECT_LEX_UNIT *u); + /** + Do prepare() and prepare2() if they have been postponed until + column type information is computed (used by select_union_direct). + + @param types Column types + + @return false on success, true on failure + */ + virtual bool postponed_prepare(List<Item> &types) + { return false; } int send_data(List<Item> &items); bool send_eof(); - bool flush(); + virtual bool flush(); void cleanup(); virtual bool create_result_table(THD *thd, List<Item> *column_types, bool is_distinct, ulonglong options, @@ -3648,13 +4771,108 @@ public: TMP_TABLE_PARAM *get_tmp_table_param() { return &tmp_table_param; } }; + +/** + UNION result that is passed directly to the receiving select_result + without filling a temporary table. + + Function calls are forwarded to the wrapped select_result, but some + functions are expected to be called only once for each query, so + they are only executed for the first SELECT in the union (execept + for send_eof(), which is executed only for the last SELECT). + + This select_result is used when a UNION is not DISTINCT and doesn't + have a global ORDER BY clause. @see st_select_lex_unit::prepare(). +*/ + +class select_union_direct :public select_union +{ +private: + /* Result object that receives all rows */ + select_result *result; + /* The last SELECT_LEX of the union */ + SELECT_LEX *last_select_lex; + + /* Wrapped result has received metadata */ + bool done_send_result_set_metadata; + /* Wrapped result has initialized tables */ + bool done_initialize_tables; + + /* Accumulated limit_found_rows */ + ulonglong limit_found_rows; + + /* Number of rows offset */ + ha_rows offset; + /* Number of rows limit + offset, @see select_union_direct::send_data() */ + ha_rows limit; + +public: + /* Number of rows in the union */ + ha_rows send_records; + select_union_direct(THD *thd_arg, select_result *result_arg, + SELECT_LEX *last_select_lex_arg): + select_union(thd_arg), result(result_arg), + last_select_lex(last_select_lex_arg), + done_send_result_set_metadata(false), done_initialize_tables(false), + limit_found_rows(0) + { send_records= 0; } + bool change_result(select_result *new_result); + uint field_count(List<Item> &fields) const + { + // Only called for top-level select_results, usually select_send + DBUG_ASSERT(false); /* purecov: inspected */ + return 0; /* purecov: inspected */ + } + bool postponed_prepare(List<Item> &types); + bool send_result_set_metadata(List<Item> &list, uint flags); + int send_data(List<Item> &items); + bool initialize_tables (JOIN *join= NULL); + bool send_eof(); + bool flush() { return false; } + bool check_simple_select() const + { + /* Only called for top-level select_results, usually select_send */ + DBUG_ASSERT(false); /* purecov: inspected */ + return false; /* purecov: inspected */ + } + void abort_result_set() + { + result->abort_result_set(); /* purecov: inspected */ + } + void cleanup() + { + send_records= 0; + } + void set_thd(THD *thd_arg) + { + /* + Only called for top-level select_results, usually select_send, + and for the results of subquery engines + (select_<something>_subselect). + */ + DBUG_ASSERT(false); /* purecov: inspected */ + } + void reset_offset_limit_cnt() + { + // EXPLAIN should never output to a select_union_direct + DBUG_ASSERT(false); /* purecov: inspected */ + } + void begin_dataset() + { + // Only called for sp_cursor::Select_fetch_into_spvars + DBUG_ASSERT(false); /* purecov: inspected */ + } +}; + + /* Base subselect interface class */ class select_subselect :public select_result_interceptor { protected: Item_subselect *item; public: - select_subselect(Item_subselect *item); + select_subselect(THD *thd_arg, Item_subselect *item_arg): + select_result_interceptor(thd_arg), item(item_arg) {} int send_data(List<Item> &items)=0; bool send_eof() { return 0; }; }; @@ -3663,8 +4881,8 @@ public: class select_singlerow_subselect :public select_subselect { public: - select_singlerow_subselect(Item_subselect *item_arg) - :select_subselect(item_arg) + select_singlerow_subselect(THD *thd_arg, Item_subselect *item_arg): + select_subselect(thd_arg, item_arg) {} int send_data(List<Item> &items); }; @@ -3709,7 +4927,8 @@ protected: void reset(); public: - select_materialize_with_stats() { tmp_table_param.init(); } + select_materialize_with_stats(THD *thd_arg): select_union(thd_arg) + { tmp_table_param.init(); } bool create_result_table(THD *thd, List<Item> *column_types, bool is_distinct, ulonglong options, const char *alias, @@ -3746,9 +4965,9 @@ class select_max_min_finder_subselect :public select_subselect bool fmax; bool is_all; public: - select_max_min_finder_subselect(Item_subselect *item_arg, bool mx, - bool all) - :select_subselect(item_arg), cache(0), fmax(mx), is_all(all) + select_max_min_finder_subselect(THD *thd_arg, Item_subselect *item_arg, + bool mx, bool all): + select_subselect(thd_arg, item_arg), cache(0), fmax(mx), is_all(all) {} void cleanup(); int send_data(List<Item> &items); @@ -3762,8 +4981,8 @@ public: class select_exists_subselect :public select_subselect { public: - select_exists_subselect(Item_subselect *item_arg) - :select_subselect(item_arg){} + select_exists_subselect(THD *thd_arg, Item_subselect *item_arg): + select_subselect(thd_arg, item_arg) {} int send_data(List<Item> &items); }; @@ -3790,13 +5009,13 @@ public: /* Cost to materialize - execute the sub-join and write rows into temp.table */ - COST_VECT materialization_cost; + Cost_estimate materialization_cost; /* Cost to make one lookup in the temptable */ - COST_VECT lookup_cost; + Cost_estimate lookup_cost; /* Cost of scanning the materialized table */ - COST_VECT scan_cost; + Cost_estimate scan_cost; /* --- Execution structures ---------- */ @@ -3885,7 +5104,7 @@ public: table.str= internal_table_name; table.length=1; } - bool is_derived_table() const { return test(sel); } + bool is_derived_table() const { return MY_TEST(sel); } inline void change_db(char *db_name) { db.str= db_name; db.length= (uint) strlen(db_name); @@ -3895,6 +5114,7 @@ public: // this is needed for user_vars hash class user_var_entry { + CHARSET_INFO *m_charset; public: user_var_entry() {} /* Remove gcc warning */ LEX_STRING name; @@ -3908,9 +5128,12 @@ class user_var_entry longlong val_int(bool *null_value) const; String *val_str(bool *null_value, String *str, uint decimals); my_decimal *val_decimal(bool *null_value, my_decimal *result); - DTCollation collation; + CHARSET_INFO *charset() const { return m_charset; } + void set_charset(CHARSET_INFO *cs) { m_charset= cs; } }; +user_var_entry *get_variable(HASH *hash, LEX_STRING &name, + bool create_if_not_exists); /* Unique -- class for unique (removing of duplicates). @@ -3933,6 +5156,7 @@ class Unique :public Sql_alloc uint size; uint full_size; uint min_dupl_count; /* always 0 for unions, > 0 for intersections */ + bool with_counters; bool merge(TABLE *table, uchar *buff, bool without_last_merge); @@ -4006,17 +5230,16 @@ class multi_delete :public select_result_interceptor bool delete_while_scanning; /* error handling (rollback and binlogging) can happen in send_eof() - so that afterward send_error() needs to find out that. + so that afterward abort_result_set() needs to find out that. */ bool error_handled; public: - multi_delete(TABLE_LIST *dt, uint num_of_tables); + multi_delete(THD *thd_arg, TABLE_LIST *dt, uint num_of_tables); ~multi_delete(); int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int send_data(List<Item> &items); bool initialize_tables (JOIN *join); - void send_error(uint errcode,const char *err); int do_deletes(); int do_table_deletes(TABLE *table, bool ignore); bool send_eof(); @@ -4052,19 +5275,20 @@ class multi_update :public select_result_interceptor bool ignore; /* error handling (rollback and binlogging) can happen in send_eof() - so that afterward send_error() needs to find out that. + so that afterward abort_result_set() needs to find out that. */ bool error_handled; - + + /* Need this to protect against multiple prepare() calls */ + bool prepared; public: - multi_update(TABLE_LIST *ut, List<TABLE_LIST> *leaves_list, + multi_update(THD *thd_arg, TABLE_LIST *ut, List<TABLE_LIST> *leaves_list, List<Item> *fields, List<Item> *values, enum_duplicates handle_duplicates, bool ignore); ~multi_update(); int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int send_data(List<Item> &items); bool initialize_tables (JOIN *join); - void send_error(uint errcode,const char *err); int do_updates(); bool send_eof(); inline ha_rows num_found() @@ -4081,28 +5305,43 @@ public: class my_var : public Sql_alloc { public: - LEX_STRING s; -#ifndef DBUG_OFF + const LEX_STRING name; + enum type { SESSION_VAR, LOCAL_VAR, PARAM_VAR }; + type scope; + my_var(const LEX_STRING& j, enum type s) : name(j), scope(s) { } + virtual ~my_var() {} + virtual bool set(THD *thd, Item *val) = 0; +}; + +class my_var_sp: public my_var { +public: + uint offset; + enum_field_types type; /* Routine to which this Item_splocal belongs. Used for checking if correct runtime context is used for variable handling. */ sp_head *sp; -#endif - bool local; - uint offset; - enum_field_types type; - my_var (LEX_STRING& j, bool i, uint o, enum_field_types t) - :s(j), local(i), offset(o), type(t) - {} - ~my_var() {} + my_var_sp(const LEX_STRING& j, uint o, enum_field_types t, sp_head *s) + : my_var(j, LOCAL_VAR), offset(o), type(t), sp(s) { } + ~my_var_sp() { } + bool set(THD *thd, Item *val); +}; + +class my_var_user: public my_var { +public: + my_var_user(const LEX_STRING& j) + : my_var(j, SESSION_VAR) { } + ~my_var_user() { } + bool set(THD *thd, Item *val); }; class select_dumpvar :public select_result_interceptor { ha_rows row_count; public: List<my_var> var_list; - select_dumpvar() { var_list.empty(); row_count= 0;} + select_dumpvar(THD *thd_arg): select_result_interceptor(thd_arg) + { var_list.empty(); row_count= 0; } ~select_dumpvar() {} int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int send_data(List<Item> &items); @@ -4180,6 +5419,52 @@ public: */ #define CF_CAN_GENERATE_ROW_EVENTS (1U << 9) +/** + Identifies statements which may deal with temporary tables and for which + temporary tables should be pre-opened to simplify privilege checks. +*/ +#define CF_PREOPEN_TMP_TABLES (1U << 10) + +/** + Identifies statements for which open handlers should be closed in the + beginning of the statement. +*/ +#define CF_HA_CLOSE (1U << 11) + +/** + Identifies statements that can be explained with EXPLAIN. +*/ +#define CF_CAN_BE_EXPLAINED (1U << 12) + +/** Identifies statements which may generate an optimizer trace */ +#define CF_OPTIMIZER_TRACE (1U << 14) + +/** + Identifies statements that should always be disallowed in + read only transactions. +*/ +#define CF_DISALLOW_IN_RO_TRANS (1U << 15) + +/** + Statement that need the binlog format to be unchanged. +*/ +#define CF_FORCE_ORIGINAL_BINLOG_FORMAT (1U << 16) + +/** + Statement that inserts new rows (INSERT, REPLACE, LOAD, ALTER TABLE) +*/ +#define CF_INSERTS_DATA (1U << 17) + +/** + Statement that updates existing rows (UPDATE, multi-update) +*/ +#define CF_UPDATES_DATA (1U << 18) + +/** + Not logged into slow log as "admin commands" +*/ +#define CF_ADMIN_COMMAND (1U << 19) + /* Bits in server_command_flags */ /** @@ -4197,6 +5482,11 @@ public: */ #define CF_SKIP_QUESTIONS (1U << 1) +/** + Do not check that wsrep snapshot is ready before allowing this command +*/ +#define CF_SKIP_WSREP_CHECK (1U << 2) + /* Inline functions */ inline bool add_item_to_list(THD *thd, Item *item) @@ -4206,7 +5496,7 @@ inline bool add_item_to_list(THD *thd, Item *item) inline bool add_value_to_list(THD *thd, Item *value) { - return thd->lex->value_list.push_back(value); + return thd->lex->value_list.push_back(value, thd->mem_root); } inline bool add_order_to_list(THD *thd, Item *item, bool asc) @@ -4224,6 +5514,13 @@ inline bool add_group_to_list(THD *thd, Item *item, bool asc) return thd->lex->current_select->add_group_to_list(thd, item, asc); } +inline Item *and_conds(THD *thd, Item *a, Item *b) +{ + if (!b) return a; + if (!a) return b; + return new (thd->mem_root) Item_cond_and(thd, a, b); +} + /* inline handler methods that need to know TABLE and THD structures */ inline void handler::increment_statistics(ulong SSV::*offset) const { @@ -4236,112 +5533,6 @@ inline void handler::decrement_statistics(ulong SSV::*offset) const status_var_decrement(table->in_use->status_var.*offset); } -inline int handler::ha_index_read_map(uchar * buf, const uchar * key, - key_part_map keypart_map, - enum ha_rkey_function find_flag) -{ - DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - increment_statistics(&SSV::ha_read_key_count); - int error= index_read_map(buf, key, keypart_map, find_flag); - if (!error) - update_index_statistics(); - table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); - return error; -} - - -/* - @note: Other index lookup/navigation functions require prior - handler->index_init() call. This function is different, it requires - that the scan is not initialized, and accepts "uint index" as an argument. -*/ - -inline int handler::ha_index_read_idx_map(uchar * buf, uint index, - const uchar * key, - key_part_map keypart_map, - enum ha_rkey_function find_flag) -{ - DBUG_ASSERT(inited==NONE); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - increment_statistics(&SSV::ha_read_key_count); - int error= index_read_idx_map(buf, index, key, keypart_map, find_flag); - if (!error) - { - update_rows_read(); - index_rows_read[index]++; - } - table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); - return error; -} - -inline int handler::ha_index_next(uchar * buf) -{ - DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - increment_statistics(&SSV::ha_read_next_count); - int error= index_next(buf); - if (!error) - update_index_statistics(); - table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); - return error; -} - -inline int handler::ha_index_prev(uchar * buf) -{ - DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - increment_statistics(&SSV::ha_read_prev_count); - int error= index_prev(buf); - if (!error) - update_index_statistics(); - table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); - return error; -} - -inline int handler::ha_index_first(uchar * buf) -{ - DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - increment_statistics(&SSV::ha_read_first_count); - int error= index_first(buf); - if (!error) - update_index_statistics(); - table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); - return error; -} - -inline int handler::ha_index_last(uchar * buf) -{ - DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - increment_statistics(&SSV::ha_read_last_count); - int error= index_last(buf); - if (!error) - update_index_statistics(); - table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); - return error; -} - -inline int handler::ha_index_next_same(uchar *buf, const uchar *key, - uint keylen) -{ - DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); - increment_statistics(&SSV::ha_read_next_count); - int error= index_next_same(buf, key, keylen); - if (!error) - update_index_statistics(); - table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); - return error; -} inline int handler::ha_ft_read(uchar *buf) { @@ -4353,42 +5544,9 @@ inline int handler::ha_ft_read(uchar *buf) return error; } -inline int handler::ha_rnd_next(uchar *buf) -{ - MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str, TRUE); - int error= rnd_next(buf); - if (!error) - { - update_rows_read(); - increment_statistics(&SSV::ha_read_rnd_next_count); - } - else if (error == HA_ERR_RECORD_DELETED) - increment_statistics(&SSV::ha_read_rnd_deleted_count); - else - increment_statistics(&SSV::ha_read_rnd_next_count); - - table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_READ_ROW_DONE(error); - return error; -} - -inline int handler::ha_rnd_pos(uchar *buf, uchar *pos) -{ - MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str, FALSE); - increment_statistics(&SSV::ha_read_rnd_count); - int error= rnd_pos(buf, pos); - if (!error) - update_rows_read(); - table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_READ_ROW_DONE(error); - return error; -} - inline int handler::ha_rnd_pos_by_record(uchar *buf) { int error= rnd_pos_by_record(buf); - if (!error) - update_rows_read(); table->status=error ? STATUS_NOT_FOUND: 0; return error; } @@ -4404,24 +5562,74 @@ inline int handler::ha_read_first_row(uchar *buf, uint primary_key) inline int handler::ha_write_tmp_row(uchar *buf) { + int error; MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_tmp_write_count); - int error= write_row(buf); + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0, + { error= write_row(buf); }) MYSQL_INSERT_ROW_DONE(error); return error; } inline int handler::ha_update_tmp_row(const uchar *old_data, uchar *new_data) { + int error; MYSQL_UPDATE_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_tmp_update_count); - int error= update_row(old_data, new_data); + TABLE_IO_WAIT(tracker, m_psi, PSI_TABLE_UPDATE_ROW, active_index, 0, + { error= update_row(old_data, new_data);}) MYSQL_UPDATE_ROW_DONE(error); return error; } + extern pthread_attr_t *get_connection_attrib(void); +/** + Set thread entering a condition + + This function should be called before putting a thread to wait for + a condition. @a mutex should be held before calling this + function. After being waken up, @f thd_exit_cond should be called. + + @param thd The thread entering the condition, NULL means current thread + @param cond The condition the thread is going to wait for + @param mutex The mutex associated with the condition, this must be + held before call this function + @param stage The new process message for the thread + @param old_stage The old process message for the thread + @param src_function The caller source function name + @param src_file The caller source file name + @param src_line The caller source line number +*/ +void thd_enter_cond(MYSQL_THD thd, mysql_cond_t *cond, mysql_mutex_t *mutex, + const PSI_stage_info *stage, PSI_stage_info *old_stage, + const char *src_function, const char *src_file, + int src_line); + +#define THD_ENTER_COND(P1, P2, P3, P4, P5) \ + thd_enter_cond(P1, P2, P3, P4, P5, __func__, __FILE__, __LINE__) + +/** + Set thread leaving a condition + + This function should be called after a thread being waken up for a + condition. + + @param thd The thread entering the condition, NULL means current thread + @param stage The process message, ususally this should be the old process + message before calling @f thd_enter_cond + @param src_function The caller source function name + @param src_file The caller source file name + @param src_line The caller source line number +*/ +void thd_exit_cond(MYSQL_THD thd, const PSI_stage_info *stage, + const char *src_function, const char *src_file, + int src_line); + +#define THD_EXIT_COND(P1, P2) \ + thd_exit_cond(P1, P2, __func__, __FILE__, __LINE__) + #endif /* MYSQL_SERVER */ #endif /* SQL_CLASS_INCLUDED */ diff --git a/sql/sql_client.cc b/sql/sql_client.cc index eb6c039c065..efac01f9894 100644 --- a/sql/sql_client.cc +++ b/sql/sql_client.cc @@ -18,6 +18,7 @@ This files defines some MySQL C API functions that are server specific */ +#include <my_global.h> #include "sql_priv.h" #include "sql_class.h" // system_variables @@ -36,7 +37,7 @@ void my_net_local_init(NET *net) (uint)global_system_variables.net_write_timeout); net->retry_count= (uint) global_system_variables.net_retry_count; - net->max_packet_size= max(global_system_variables.net_buffer_length, + net->max_packet_size= MY_MAX(global_system_variables.net_buffer_length, global_system_variables.max_allowed_packet); #endif } diff --git a/sql/sql_cmd.h b/sql/sql_cmd.h new file mode 100644 index 00000000000..904578134b4 --- /dev/null +++ b/sql/sql_cmd.h @@ -0,0 +1,164 @@ +/* Copyright (c) 2009, 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-1301 USA */ + +/** + @file Representation of an SQL command. +*/ + +#ifndef SQL_CMD_INCLUDED +#define SQL_CMD_INCLUDED + +/* + When a command is added here, be sure it's also added in mysqld.cc + in "struct show_var_st status_vars[]= {" ... + + If the command returns a result set or is not allowed in stored + functions or triggers, please also make sure that + sp_get_flags_for_command (sp_head.cc) returns proper flags for the + added SQLCOM_. +*/ + +enum enum_sql_command { + SQLCOM_SELECT, SQLCOM_CREATE_TABLE, SQLCOM_CREATE_INDEX, SQLCOM_ALTER_TABLE, + SQLCOM_UPDATE, SQLCOM_INSERT, SQLCOM_INSERT_SELECT, + SQLCOM_DELETE, SQLCOM_TRUNCATE, SQLCOM_DROP_TABLE, SQLCOM_DROP_INDEX, + + SQLCOM_SHOW_DATABASES, SQLCOM_SHOW_TABLES, SQLCOM_SHOW_FIELDS, + SQLCOM_SHOW_KEYS, SQLCOM_SHOW_VARIABLES, SQLCOM_SHOW_STATUS, + SQLCOM_SHOW_ENGINE_LOGS, SQLCOM_SHOW_ENGINE_STATUS, SQLCOM_SHOW_ENGINE_MUTEX, + SQLCOM_SHOW_PROCESSLIST, SQLCOM_SHOW_MASTER_STAT, SQLCOM_SHOW_SLAVE_STAT, + SQLCOM_SHOW_GRANTS, SQLCOM_SHOW_CREATE, SQLCOM_SHOW_CHARSETS, + SQLCOM_SHOW_COLLATIONS, SQLCOM_SHOW_CREATE_DB, SQLCOM_SHOW_TABLE_STATUS, + SQLCOM_SHOW_TRIGGERS, + + SQLCOM_LOAD,SQLCOM_SET_OPTION,SQLCOM_LOCK_TABLES,SQLCOM_UNLOCK_TABLES, + SQLCOM_GRANT, + SQLCOM_CHANGE_DB, SQLCOM_CREATE_DB, SQLCOM_DROP_DB, SQLCOM_ALTER_DB, + SQLCOM_REPAIR, SQLCOM_REPLACE, SQLCOM_REPLACE_SELECT, + SQLCOM_CREATE_FUNCTION, SQLCOM_DROP_FUNCTION, + SQLCOM_REVOKE,SQLCOM_OPTIMIZE, SQLCOM_CHECK, + SQLCOM_ASSIGN_TO_KEYCACHE, SQLCOM_PRELOAD_KEYS, + SQLCOM_FLUSH, SQLCOM_KILL, SQLCOM_ANALYZE, + SQLCOM_ROLLBACK, SQLCOM_ROLLBACK_TO_SAVEPOINT, + SQLCOM_COMMIT, SQLCOM_SAVEPOINT, SQLCOM_RELEASE_SAVEPOINT, + SQLCOM_SLAVE_START, SQLCOM_SLAVE_STOP, + SQLCOM_BEGIN, SQLCOM_CHANGE_MASTER, + SQLCOM_RENAME_TABLE, + SQLCOM_RESET, SQLCOM_PURGE, SQLCOM_PURGE_BEFORE, SQLCOM_SHOW_BINLOGS, + SQLCOM_SHOW_OPEN_TABLES, + SQLCOM_HA_OPEN, SQLCOM_HA_CLOSE, SQLCOM_HA_READ, + SQLCOM_SHOW_SLAVE_HOSTS, SQLCOM_DELETE_MULTI, SQLCOM_UPDATE_MULTI, + SQLCOM_SHOW_BINLOG_EVENTS, SQLCOM_DO, + SQLCOM_SHOW_WARNS, SQLCOM_EMPTY_QUERY, SQLCOM_SHOW_ERRORS, + SQLCOM_SHOW_STORAGE_ENGINES, SQLCOM_SHOW_PRIVILEGES, + SQLCOM_HELP, SQLCOM_CREATE_USER, SQLCOM_DROP_USER, SQLCOM_RENAME_USER, + SQLCOM_REVOKE_ALL, SQLCOM_CHECKSUM, + SQLCOM_CREATE_PROCEDURE, SQLCOM_CREATE_SPFUNCTION, SQLCOM_CALL, + SQLCOM_DROP_PROCEDURE, SQLCOM_ALTER_PROCEDURE,SQLCOM_ALTER_FUNCTION, + SQLCOM_SHOW_CREATE_PROC, SQLCOM_SHOW_CREATE_FUNC, + SQLCOM_SHOW_STATUS_PROC, SQLCOM_SHOW_STATUS_FUNC, + SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE, + SQLCOM_CREATE_VIEW, SQLCOM_DROP_VIEW, + SQLCOM_CREATE_TRIGGER, SQLCOM_DROP_TRIGGER, + SQLCOM_XA_START, SQLCOM_XA_END, SQLCOM_XA_PREPARE, + SQLCOM_XA_COMMIT, SQLCOM_XA_ROLLBACK, SQLCOM_XA_RECOVER, + SQLCOM_SHOW_PROC_CODE, SQLCOM_SHOW_FUNC_CODE, + SQLCOM_ALTER_TABLESPACE, + SQLCOM_INSTALL_PLUGIN, SQLCOM_UNINSTALL_PLUGIN, + SQLCOM_SHOW_AUTHORS, SQLCOM_BINLOG_BASE64_EVENT, + SQLCOM_SHOW_PLUGINS, SQLCOM_SHOW_CONTRIBUTORS, + SQLCOM_CREATE_SERVER, SQLCOM_DROP_SERVER, SQLCOM_ALTER_SERVER, + SQLCOM_CREATE_EVENT, SQLCOM_ALTER_EVENT, SQLCOM_DROP_EVENT, + SQLCOM_SHOW_CREATE_EVENT, SQLCOM_SHOW_EVENTS, + SQLCOM_SHOW_CREATE_TRIGGER, + SQLCOM_ALTER_DB_UPGRADE, + SQLCOM_SHOW_PROFILE, SQLCOM_SHOW_PROFILES, + SQLCOM_SIGNAL, SQLCOM_RESIGNAL, + SQLCOM_SHOW_RELAYLOG_EVENTS, + SQLCOM_GET_DIAGNOSTICS, + SQLCOM_SLAVE_ALL_START, SQLCOM_SLAVE_ALL_STOP, + SQLCOM_SHOW_EXPLAIN, SQLCOM_SHUTDOWN, + SQLCOM_CREATE_ROLE, SQLCOM_DROP_ROLE, SQLCOM_GRANT_ROLE, SQLCOM_REVOKE_ROLE, + SQLCOM_COMPOUND, + SQLCOM_SHOW_GENERIC, + + /* + When a command is added here, be sure it's also added in mysqld.cc + in "struct show_var_st com_status_vars[]= {" ... + */ + /* This should be the last !!! */ + SQLCOM_END +}; + +/** + @class Sql_cmd - Representation of an SQL command. + + This class is an interface between the parser and the runtime. + The parser builds the appropriate derived classes of Sql_cmd + to represent a SQL statement in the parsed tree. + The execute() method in the derived classes of Sql_cmd contain the runtime + implementation. + Note that this interface is used for SQL statements recently implemented, + the code for older statements tend to load the LEX structure with more + attributes instead. + Implement new statements by sub-classing Sql_cmd, as this improves + code modularity (see the 'big switch' in dispatch_command()), and decreases + the total size of the LEX structure (therefore saving memory in stored + programs). + The recommended name of a derived class of Sql_cmd is Sql_cmd_<derived>. + + Notice that the Sql_cmd class should not be confused with the + Statement class. Statement is a class that is used to manage an SQL + command or a set of SQL commands. When the SQL statement text is + analyzed, the parser will create one or more Sql_cmd objects to + represent the actual SQL commands. +*/ +class Sql_cmd : public Sql_alloc +{ +private: + Sql_cmd(const Sql_cmd &); // No copy constructor wanted + void operator=(Sql_cmd &); // No assignment operator wanted + +public: + /** + @brief Return the command code for this statement + */ + virtual enum_sql_command sql_command_code() const = 0; + + /** + Execute this SQL statement. + @param thd the current thread. + @retval false on success. + @retval true on error + */ + virtual bool execute(THD *thd) = 0; + +protected: + Sql_cmd() + {} + + virtual ~Sql_cmd() + { + /* + Sql_cmd objects are allocated in thd->mem_root. + In MySQL, the C++ destructor is never called, the underlying MEM_ROOT is + simply destroyed instead. + Do not rely on the destructor for any cleanup. + */ + DBUG_ASSERT(FALSE); + } +}; + +#endif // SQL_CMD_INCLUDED diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index e2e56c48b3b..abb234ab4b1 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -20,7 +20,7 @@ Functions to autenticate and handle reqests for a connection */ -#include "my_global.h" +#include <my_global.h> #include "sql_priv.h" #ifndef __WIN__ #include <netdb.h> // getservbyname, servent @@ -28,7 +28,6 @@ #include "sql_audit.h" #include "sql_connect.h" #include "probes_mysql.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_parse.h" // sql_command_flags, // execute_init_command, // do_command @@ -37,6 +36,7 @@ // reset_host_errors #include "sql_acl.h" // acl_getroot, NO_ACCESS, SUPER_ACL #include "sql_callback.h" +#include "wsrep_mysqld.h" HASH global_user_stats, global_client_stats, global_table_stats; HASH global_index_stats; @@ -100,7 +100,6 @@ int get_or_create_user_conn(THD *thd, const char *user, end: mysql_mutex_unlock(&LOCK_user_conn); return return_val; - } @@ -124,6 +123,7 @@ end: int check_for_max_user_connections(THD *thd, USER_CONN *uc) { int error= 1; + Host_errors errors; DBUG_ENTER("check_for_max_user_connections"); mysql_mutex_lock(&LOCK_user_conn); @@ -135,6 +135,8 @@ int check_for_max_user_connections(THD *thd, USER_CONN *uc) !(thd->security_ctx->master_access & SUPER_ACL)) { my_error(ER_TOO_MANY_USER_CONNECTIONS, MYF(0), uc->user); + error=1; + errors.m_max_user_connection= 1; goto end; } time_out_user_resource_limits(thd, uc); @@ -144,6 +146,8 @@ int check_for_max_user_connections(THD *thd, USER_CONN *uc) my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, "max_user_connections", (long) uc->user_resources.user_conn); + error= 1; + errors.m_max_user_connection= 1; goto end; } if (uc->user_resources.conn_per_hour && @@ -152,6 +156,8 @@ int check_for_max_user_connections(THD *thd, USER_CONN *uc) my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, "max_connections_per_hour", (long) uc->user_resources.conn_per_hour); + error=1; + errors.m_max_user_connection_per_hour= 1; goto end; } uc->conn_per_hour++; @@ -169,6 +175,10 @@ end: thd->user_connect= NULL; } mysql_mutex_unlock(&LOCK_user_conn); + if (error) + { + inc_host_errors(thd->main_security_ctx.ip, &errors); + } DBUG_RETURN(error); } @@ -226,7 +236,7 @@ void time_out_user_resource_limits(THD *thd, USER_CONN *uc) DBUG_ENTER("time_out_user_resource_limits"); /* If more than a hour since last check, reset resource checking */ - if (check_time - uc->reset_utime >= LL(3600000000)) + if (check_time - uc->reset_utime >= 3600000000ULL) { uc->questions=0; uc->updates=0; @@ -304,13 +314,9 @@ extern "C" void free_user(struct user_conn *uc) void init_max_user_conn(void) { #ifndef NO_EMBEDDED_ACCESS_CHECKS - if (my_hash_init(&hash_user_connections,system_charset_info,max_connections, - 0,0, (my_hash_get_key) get_key_conn, - (my_hash_free_key) free_user, 0)) - { - sql_print_error("Initializing hash_user_connections failed."); - exit(1); - } + my_hash_init(&hash_user_connections, system_charset_info, max_connections, + 0, 0, (my_hash_get_key) get_key_conn, + (my_hash_free_key) free_user, 0); #endif } @@ -406,6 +412,7 @@ void init_user_stats(USER_STATS *user_stats, size_t user_length, const char *priv_user, uint total_connections, + uint total_ssl_connections, uint concurrent_connections, time_t connected_time, double busy_time, @@ -425,19 +432,21 @@ void init_user_stats(USER_STATS *user_stats, ulonglong rollback_trans, ulonglong denied_connections, ulonglong lost_connections, + ulonglong max_statement_time_exceeded, ulonglong access_denied_errors, ulonglong empty_queries) { DBUG_ENTER("init_user_stats"); DBUG_PRINT("enter", ("user: %s priv_user: %s", user, priv_user)); - user_length= min(user_length, sizeof(user_stats->user)-1); + user_length= MY_MIN(user_length, sizeof(user_stats->user)-1); memcpy(user_stats->user, user, user_length); user_stats->user[user_length]= 0; user_stats->user_name_length= user_length; strmake_buf(user_stats->priv_user, priv_user); user_stats->total_connections= total_connections; + user_stats->total_ssl_connections= total_ssl_connections; user_stats->concurrent_connections= concurrent_connections; user_stats->connected_time= connected_time; user_stats->busy_time= busy_time; @@ -446,8 +455,10 @@ void init_user_stats(USER_STATS *user_stats, user_stats->bytes_sent= bytes_sent; user_stats->binlog_bytes_written= binlog_bytes_written; user_stats->rows_sent= rows_sent; - user_stats->rows_updated= rows_updated; user_stats->rows_read= rows_read; + user_stats->rows_inserted= rows_inserted; + user_stats->rows_deleted= rows_deleted; + user_stats->rows_updated= rows_updated; user_stats->select_commands= select_commands; user_stats->update_commands= update_commands; user_stats->other_commands= other_commands; @@ -455,84 +466,25 @@ void init_user_stats(USER_STATS *user_stats, user_stats->rollback_trans= rollback_trans; user_stats->denied_connections= denied_connections; user_stats->lost_connections= lost_connections; + user_stats->max_statement_time_exceeded= max_statement_time_exceeded; user_stats->access_denied_errors= access_denied_errors; user_stats->empty_queries= empty_queries; DBUG_VOID_RETURN; } -#ifdef COMPLETE_PATCH_NOT_ADDED_YET - -void add_user_stats(USER_STATS *user_stats, - uint total_connections, - uint concurrent_connections, - time_t connected_time, - double busy_time, - double cpu_time, - ulonglong bytes_received, - ulonglong bytes_sent, - ulonglong binlog_bytes_written, - ha_rows rows_sent, - ha_rows rows_read, - ha_rows rows_inserted, - ha_rows rows_deleted, - ha_rows rows_updated, - ulonglong select_commands, - ulonglong update_commands, - ulonglong other_commands, - ulonglong commit_trans, - ulonglong rollback_trans, - ulonglong denied_connections, - ulonglong lost_connections, - ulonglong access_denied_errors, - ulonglong empty_queries) -{ - user_stats->total_connections+= total_connections; - user_stats->concurrent_connections+= concurrent_connections; - user_stats->connected_time+= connected_time; - user_stats->busy_time+= busy_time; - user_stats->cpu_time+= cpu_time; - user_stats->bytes_received+= bytes_received; - user_stats->bytes_sent+= bytes_sent; - user_stats->binlog_bytes_written+= binlog_bytes_written; - user_stats->rows_sent+= rows_sent; - user_stats->rows_inserted+= rows_inserted; - user_stats->rows_deleted+= rows_deleted; - user_stats->rows_updated+= rows_updated; - user_stats->rows_read+= rows_read; - user_stats->select_commands+= select_commands; - user_stats->update_commands+= update_commands; - user_stats->other_commands+= other_commands; - user_stats->commit_trans+= commit_trans; - user_stats->rollback_trans+= rollback_trans; - user_stats->denied_connections+= denied_connections; - user_stats->lost_connections+= lost_connections; - user_stats->access_denied_errors+= access_denied_errors; - user_stats->empty_queries+= empty_queries; -} -#endif - - void init_global_user_stats(void) { - if (my_hash_init(&global_user_stats, system_charset_info, max_connections, - 0, 0, (my_hash_get_key) get_key_user_stats, - (my_hash_free_key)free_user_stats, 0)) - { - sql_print_error("Initializing global_user_stats failed."); - exit(1); - } + my_hash_init(&global_user_stats, system_charset_info, max_connections, + 0, 0, (my_hash_get_key) get_key_user_stats, + (my_hash_free_key) free_user_stats, 0); } void init_global_client_stats(void) { - if (my_hash_init(&global_client_stats, system_charset_info, max_connections, - 0, 0, (my_hash_get_key) get_key_user_stats, - (my_hash_free_key)free_user_stats, 0)) - { - sql_print_error("Initializing global_client_stats failed."); - exit(1); - } + my_hash_init(&global_client_stats, system_charset_info, max_connections, + 0, 0, (my_hash_get_key) get_key_user_stats, + (my_hash_free_key) free_user_stats, 0); } extern "C" uchar *get_key_table_stats(TABLE_STATS *table_stats, size_t *length, @@ -549,12 +501,9 @@ extern "C" void free_table_stats(TABLE_STATS* table_stats) void init_global_table_stats(void) { - if (my_hash_init(&global_table_stats, system_charset_info, max_connections, - 0, 0, (my_hash_get_key) get_key_table_stats, - (my_hash_free_key)free_table_stats, 0)) { - sql_print_error("Initializing global_table_stats failed."); - exit(1); - } + my_hash_init(&global_table_stats, system_charset_info, max_connections, + 0, 0, (my_hash_get_key) get_key_table_stats, + (my_hash_free_key) free_table_stats, 0); } extern "C" uchar *get_key_index_stats(INDEX_STATS *index_stats, size_t *length, @@ -571,13 +520,9 @@ extern "C" void free_index_stats(INDEX_STATS* index_stats) void init_global_index_stats(void) { - if (my_hash_init(&global_index_stats, system_charset_info, max_connections, - 0, 0, (my_hash_get_key) get_key_index_stats, - (my_hash_free_key)free_index_stats, 0)) - { - sql_print_error("Initializing global_index_stats failed."); - exit(1); - } + my_hash_init(&global_index_stats, system_charset_info, max_connections, + 0, 0, (my_hash_get_key) get_key_index_stats, + (my_hash_free_key) free_index_stats, 0); } @@ -623,15 +568,16 @@ static bool increment_count_by_name(const char *name, size_t name_length, return TRUE; // Out of memory init_user_stats(user_stats, name, name_length, role_name, - 0, 0, // connections + 0, 0, 0, // connections 0, 0, 0, // time 0, 0, 0, // bytes sent, received and written - 0, 0, // Rows sent and read + 0, 0, // rows sent and read 0, 0, 0, // rows inserted, deleted and updated 0, 0, 0, // select, update and other commands 0, 0, // commit and rollback trans thd->status_var.access_denied_errors, 0, // lost connections + 0, // max query timeouts 0, // access denied errors 0); // empty queries @@ -642,6 +588,8 @@ static bool increment_count_by_name(const char *name, size_t name_length, } } user_stats->total_connections++; + if (thd->net.vio && thd->net.vio->type == VIO_TYPE_SSL) + user_stats->total_ssl_connections++; return FALSE; } @@ -704,7 +652,7 @@ static void update_global_user_stats_with_user(THD *thd, user_stats->cpu_time+= (thd->status_var.cpu_time - thd->org_status_var.cpu_time); /* - This is handle specially as bytes_recieved is incremented BEFORE + This is handle specially as bytes_received is incremented BEFORE org_status_var is copied. */ user_stats->bytes_received+= (thd->org_status_var.bytes_received- @@ -744,6 +692,7 @@ static void update_global_user_stats_with_user(THD *thd, /* The following can only contain 0 or 1 and then connection ends */ user_stats->denied_connections+= thd->status_var.access_denied_errors; user_stats->lost_connections+= thd->status_var.lost_connections; + user_stats->max_statement_time_exceeded+= thd->status_var.max_statement_time_exceeded; } @@ -827,13 +776,10 @@ bool thd_init_client_charset(THD *thd, uint cs_number) Use server character set and collation if - opt_character_set_client_handshake is not set - client has not specified a character set - - client character set is the same as the servers - client character set doesn't exists in server */ if (!opt_character_set_client_handshake || - !(cs= get_charset(cs_number, MYF(0))) || - !my_strcasecmp(&my_charset_latin1, gv->character_set_client->name, - cs->name)) + !(cs= get_charset(cs_number, MYF(0)))) { DBUG_ASSERT(is_supported_parser_charset(gv->character_set_client)); thd->variables.character_set_client= gv->character_set_client; @@ -865,7 +811,10 @@ bool init_new_connection_handler_thread() { pthread_detach_this_thread(); if (my_thread_init()) + { + statistic_increment(connection_errors_internal, &LOCK_status); return 1; + } return 0; } @@ -885,6 +834,7 @@ bool init_new_connection_handler_thread() static int check_connection(THD *thd) { uint connect_errors= 0; + int auth_rc; NET *net= &thd->net; DBUG_PRINT("info", @@ -896,48 +846,116 @@ static int check_connection(THD *thd) if (!thd->main_security_ctx.host) // If TCP/IP connection { + my_bool peer_rc; char ip[NI_MAXHOST]; - if (vio_peer_addr(net->vio, ip, &thd->peer_port, NI_MAXHOST)) - { - my_error(ER_BAD_HOST_ERROR, MYF(0)); - return 1; - } - /* BEGIN : DEBUG */ - DBUG_EXECUTE_IF("addr_fake_ipv4", + peer_rc= vio_peer_addr(net->vio, ip, &thd->peer_port, NI_MAXHOST); + + /* + =========================================================================== + DEBUG code only (begin) + Simulate various output from vio_peer_addr(). + =========================================================================== + */ + + DBUG_EXECUTE_IF("vio_peer_addr_error", + { + peer_rc= 1; + } + ); + DBUG_EXECUTE_IF("vio_peer_addr_fake_ipv4", { struct sockaddr *sa= (sockaddr *) &net->vio->remote; sa->sa_family= AF_INET; - struct in_addr *ip4= &((struct sockaddr_in *)sa)->sin_addr; - /* See RFC 5737, 192.0.2.0/23 is reserved */ + struct in_addr *ip4= &((struct sockaddr_in *) sa)->sin_addr; + /* See RFC 5737, 192.0.2.0/24 is reserved. */ const char* fake= "192.0.2.4"; ip4->s_addr= inet_addr(fake); strcpy(ip, fake); - };); - /* END : DEBUG */ + peer_rc= 0; + } + ); + +#ifdef HAVE_IPV6 + DBUG_EXECUTE_IF("vio_peer_addr_fake_ipv6", + { + struct sockaddr_in6 *sa= (sockaddr_in6 *) &net->vio->remote; + sa->sin6_family= AF_INET6; + struct in6_addr *ip6= & sa->sin6_addr; + /* See RFC 3849, ipv6 2001:DB8::/32 is reserved. */ + const char* fake= "2001:db8::6:6"; + /* inet_pton(AF_INET6, fake, ip6); not available on Windows XP. */ + ip6->s6_addr[ 0] = 0x20; + ip6->s6_addr[ 1] = 0x01; + ip6->s6_addr[ 2] = 0x0d; + ip6->s6_addr[ 3] = 0xb8; + ip6->s6_addr[ 4] = 0x00; + ip6->s6_addr[ 5] = 0x00; + ip6->s6_addr[ 6] = 0x00; + ip6->s6_addr[ 7] = 0x00; + ip6->s6_addr[ 8] = 0x00; + ip6->s6_addr[ 9] = 0x00; + ip6->s6_addr[10] = 0x00; + ip6->s6_addr[11] = 0x00; + ip6->s6_addr[12] = 0x00; + ip6->s6_addr[13] = 0x06; + ip6->s6_addr[14] = 0x00; + ip6->s6_addr[15] = 0x06; + strcpy(ip, fake); + peer_rc= 0; + } + ); +#endif /* HAVE_IPV6 */ + /* + =========================================================================== + DEBUG code only (end) + =========================================================================== + */ + + if (peer_rc) + { + /* + Since we can not even get the peer IP address, + there is nothing to show in the host_cache, + so increment the global status variable for peer address errors. + */ + statistic_increment(connection_errors_peer_addr, &LOCK_status); + my_error(ER_BAD_HOST_ERROR, MYF(0)); + return 1; + } if (!(thd->main_security_ctx.ip= my_strdup(ip,MYF(MY_WME)))) + { + /* + No error accounting per IP in host_cache, + this is treated as a global server OOM error. + TODO: remove the need for my_strdup. + */ + statistic_increment(connection_errors_internal, &LOCK_status); return 1; /* The error is set by my_strdup(). */ + } thd->main_security_ctx.host_or_ip= thd->main_security_ctx.ip; if (!(specialflag & SPECIAL_NO_RESOLVE)) { - if (ip_to_hostname(&net->vio->remote, thd->main_security_ctx.ip, - &thd->main_security_ctx.host, &connect_errors)) - { - my_error(ER_BAD_HOST_ERROR, MYF(0)); - return 1; - } + int rc; + + rc= ip_to_hostname(&net->vio->remote, + thd->main_security_ctx.ip, + &thd->main_security_ctx.host, + &connect_errors); /* Cut very long hostnames to avoid possible overflows */ if (thd->main_security_ctx.host) { if (thd->main_security_ctx.host != my_localhost) - thd->main_security_ctx.host[min(strlen(thd->main_security_ctx.host), + thd->main_security_ctx.host[MY_MIN(strlen(thd->main_security_ctx.host), HOSTNAME_LENGTH)]= 0; thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host; } - if (connect_errors > max_connect_errors) + + if (rc == RC_BLOCKED_HOST) { + /* HOST_CACHE stats updated by ip_to_hostname(). */ my_error(ER_HOST_IS_BLOCKED, MYF(0), thd->main_security_ctx.host_or_ip); return 1; } @@ -949,6 +967,7 @@ static int check_connection(THD *thd) thd->main_security_ctx.ip : "unknown ip"))); if (acl_check_host(thd->main_security_ctx.host, thd->main_security_ctx.ip)) { + /* HOST_CACHE stats updated by acl_check_host(). */ my_error(ER_HOST_NOT_PRIVILEGED, MYF(0), thd->main_security_ctx.host_or_ip); return 1; @@ -965,9 +984,34 @@ static int check_connection(THD *thd) vio_keepalive(net->vio, TRUE); if (thd->packet.alloc(thd->variables.net_buffer_length)) + { + /* + Important note: + net_buffer_length is a SESSION variable, + so it may be tempting to account OOM conditions per IP in the HOST_CACHE, + in case some clients are more demanding than others ... + However, this session variable is *not* initialized with a per client + value during the initial connection, it is initialized from the + GLOBAL net_buffer_length variable from the server. + Hence, there is no reason to account on OOM conditions per client IP, + we count failures in the global server status instead. + */ + statistic_increment(connection_errors_internal, &LOCK_status); return 1; /* The error is set by alloc(). */ + } + + auth_rc= acl_authenticate(thd, 0); + if (auth_rc == 0 && connect_errors != 0) + { + /* + A client connection from this IP was successful, + after some previous failures. + Reset the connection error counter. + */ + reset_host_connect_errors(thd->main_security_ctx.ip); + } - return acl_authenticate(thd, connect_errors, 0); + return auth_rc; } @@ -1050,7 +1094,7 @@ bool login_connection(THD *thd) } exit: - MYSQL_AUDIT_NOTIFY_CONNECTION_CONNECT(thd); + mysql_audit_notify_connection_connect(thd); DBUG_RETURN(error); } @@ -1065,6 +1109,17 @@ exit: void end_connection(THD *thd) { NET *net= &thd->net; +#ifdef WITH_WSREP + if (WSREP(thd)) + { + wsrep_status_t rcode= wsrep->free_connection(wsrep, thd->thread_id); + if (rcode) { + WSREP_WARN("wsrep failed to free connection context: %lu, code: %d", + thd->thread_id, rcode); + } + } + thd->wsrep_client_thread= 0; +#endif plugin_thdvar_cleanup(thd); if (thd->user_connect) @@ -1090,8 +1145,8 @@ void end_connection(THD *thd) } if (!thd->killed && (net->error && net->vio != 0)) - thd->print_aborted_warning(1, - thd->stmt_da->is_error() ? thd->stmt_da->message() : ER(ER_UNKNOWN_ERROR)); + thd->print_aborted_warning(1, thd->get_stmt_da()->is_error() + ? thd->get_stmt_da()->message() : ER_THD(thd, ER_UNKNOWN_ERROR)); } @@ -1112,7 +1167,7 @@ void prepare_new_connection_state(THD* thd) TODO: refactor this to avoid code duplication there */ thd->proc_info= 0; - thd->command= COM_SLEEP; + thd->set_command(COM_SLEEP); thd->set_time(); thd->init_for_queries(); @@ -1121,9 +1176,10 @@ void prepare_new_connection_state(THD* thd) execute_init_command(thd, &opt_init_connect, &LOCK_sys_init_connect); if (thd->is_error()) { - thd->killed= KILL_CONNECTION; + Host_errors errors; + thd->set_killed(KILL_CONNECTION); thd->print_aborted_warning(0, "init_connect command failed"); - sql_print_warning("%s", thd->stmt_da->message()); + sql_print_warning("%s", thd->get_stmt_da()->message()); /* now let client to send its first command, @@ -1148,6 +1204,8 @@ void prepare_new_connection_state(THD* thd) thd->server_status&= ~SERVER_STATUS_CLEAR_SET; thd->protocol->end_statement(); thd->killed = KILL_CONNECTION; + errors.m_init_connect= 1; + inc_host_errors(thd->main_security_ctx.ip, &errors); return; } @@ -1197,6 +1255,9 @@ bool thd_prepare_connection(THD *thd) (char *) thd->security_ctx->host_or_ip); prepare_new_connection_state(thd); +#ifdef WITH_WSREP + thd->wsrep_client_thread= 1; +#endif /* WITH_WSREP */ return FALSE; } @@ -1256,6 +1317,7 @@ void do_handle_one_connection(THD *thd_arg) { bool create_user= TRUE; + mysql_socket_set_thread_owner(thd->net.vio->mysql_socket); if (thd_prepare_connection(thd)) { create_user= FALSE; @@ -1269,7 +1331,15 @@ void do_handle_one_connection(THD *thd_arg) break; } end_connection(thd); - + +#ifdef WITH_WSREP + if (WSREP(thd)) + { + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->wsrep_query_state= QUERY_EXITING; + mysql_mutex_unlock(&thd->LOCK_thd_data); + } +#endif end_thread: close_connection(thd); diff --git a/sql/sql_const.h b/sql/sql_const.h index e8385b537a8..1d6549f777f 100644 --- a/sql/sql_const.h +++ b/sql/sql_const.h @@ -17,12 +17,14 @@ @file File containing constants that can be used throughout the server. - @note This file shall not contain any includes of any kinds. + @note This file shall not contain or include any declarations of any kinds. */ #ifndef SQL_CONST_INCLUDED #define SQL_CONST_INCLUDED +#include <mysql_version.h> + #define LIBLEN FN_REFLEN-FN_LEN /* Max l{ngd p} dev */ /* extra 4+4 bytes for slave tmp tables */ #define MAX_DBKEY_LENGTH (NAME_LEN*2+1+1+4+4) @@ -31,15 +33,27 @@ #define MAX_SYS_VAR_LENGTH 32 #define MAX_KEY MAX_INDEXES /* Max used keys */ #define MAX_REF_PARTS 32 /* Max parts used as ref */ -#define MAX_KEY_LENGTH 3072 /* max possible key */ + +/* + Maximum length of the data part of an index lookup key. + + The "data part" is defined as the value itself, not including the + NULL-indicator bytes or varchar length bytes ("the Extras"). We need this + value because there was a bug where length of the Extras were not counted. + + You probably need MAX_KEY_LENGTH, not this constant. +*/ +#define MAX_DATA_LENGTH_FOR_KEY 3072 #if SIZEOF_OFF_T > 4 #define MAX_REFLENGTH 8 /* Max length for record ref */ #else #define MAX_REFLENGTH 4 /* Max length for record ref */ #endif #define MAX_HOSTNAME 61 /* len+1 in mysql.user */ +#define MAX_CONNECTION_NAME NAME_LEN #define MAX_MBWIDTH 3 /* Max multibyte sequence */ +#define MAX_FILENAME_MBWIDTH 5 #define MAX_FIELD_CHARLENGTH 255 /* In MAX_FIELD_VARCHARLENGTH we reserve extra bytes for the overhead: @@ -78,7 +92,7 @@ RAND_TABLE_BIT) #define CONNECT_STRING_MAXLEN 65535 /* stored in 2 bytes in .frm */ #define MAX_FIELDS 4096 /* Limit in the .frm file */ -#define MAX_PARTITIONS 1024 +#define MAX_PARTITIONS 8192 #define MAX_SELECT_NESTING (sizeof(nesting_map)*8-1) @@ -87,7 +101,6 @@ /* Some portable defines */ -#define portable_sizeof_char_ptr 8 #define STRING_BUFFER_USUAL_SIZE 80 /* Memory allocated when parsing a statement / saving a statement */ @@ -121,8 +134,8 @@ #define MAX_ACCEPT_RETRY 10 // Test accept this many times #define MAX_FIELDS_BEFORE_HASH 32 #define USER_VARS_HASH_SIZE 16 -#define TABLE_OPEN_CACHE_MIN 400 -#define TABLE_OPEN_CACHE_DEFAULT 400 +#define TABLE_OPEN_CACHE_MIN 200 +#define TABLE_OPEN_CACHE_DEFAULT 2000 #define TABLE_DEF_CACHE_DEFAULT 400 /** We must have room for at least 400 table definitions in the table @@ -139,6 +152,13 @@ */ #define TABLE_DEF_CACHE_MIN 400 +/** + Maximum number of connections default value. + 151 is larger than Apache's default max children, + to avoid "too many connections" error in a common setup. +*/ +#define MAX_CONNECTIONS_DEFAULT 151 + /* Stack reservation. Feel free to raise this by the smallest amount you can to get the @@ -146,15 +166,15 @@ */ #define STACK_MIN_SIZE 16000 // Abort if less stack during eval. -#define STACK_MIN_SIZE_FOR_OPEN 1024*80 +#define STACK_MIN_SIZE_FOR_OPEN (1024*80) #define STACK_BUFF_ALLOC 352 ///< For stack overrun checks #ifndef MYSQLD_NET_RETRY_COUNT #define MYSQLD_NET_RETRY_COUNT 10 ///< Abort read after this many int. #endif -#define QUERY_ALLOC_BLOCK_SIZE 8192 -#define QUERY_ALLOC_PREALLOC_SIZE 8192 -#define TRANS_ALLOC_BLOCK_SIZE 4096 +#define QUERY_ALLOC_BLOCK_SIZE 16384 +#define QUERY_ALLOC_PREALLOC_SIZE 24576 +#define TRANS_ALLOC_BLOCK_SIZE 8192 #define TRANS_ALLOC_PREALLOC_SIZE 4096 #define RANGE_ALLOC_BLOCK_SIZE 4096 #define ACL_ALLOC_BLOCK_SIZE 1024 @@ -242,8 +262,8 @@ #define DEFAULT_CONCURRENCY 10 #define DELAYED_LIMIT 100 /**< pause after xxx inserts */ #define DELAYED_QUEUE_SIZE 1000 -#define DELAYED_WAIT_TIMEOUT 5*60 /**< Wait for delayed insert */ -#define MAX_CONNECT_ERRORS 10 ///< errors before disabling host +#define DELAYED_WAIT_TIMEOUT (5*60) /**< Wait for delayed insert */ +#define MAX_CONNECT_ERRORS 100 ///< errors before disabling host #define LONG_TIMEOUT ((ulong) 3600L*24L*365L) diff --git a/sql/sql_crypt.cc b/sql/sql_crypt.cc index bcc8ad0b10f..2460a16551d 100644 --- a/sql/sql_crypt.cc +++ b/sql/sql_crypt.cc @@ -26,6 +26,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include "sql_crypt.h" #include "password.h" diff --git a/sql/sql_crypt.h b/sql/sql_crypt.h index 3a12d603601..3df554e9d31 100644 --- a/sql/sql_crypt.h +++ b/sql/sql_crypt.h @@ -22,7 +22,7 @@ #endif #include "sql_list.h" /* Sql_alloc */ -#include "mysql_com.h" /* rand_struct */ +#include "my_rnd.h" /* rand_struct */ class SQL_CRYPT :public Sql_alloc { diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc index f7ffd86fe83..69781b5def3 100644 --- a/sql/sql_cursor.cc +++ b/sql/sql_cursor.cc @@ -17,6 +17,7 @@ #pragma implementation /* gcc class implementation */ #endif +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_cursor.h" @@ -53,6 +54,8 @@ public: virtual void fetch(ulong num_rows); virtual void close(); virtual ~Materialized_cursor(); + + void on_table_fill_finished(); }; @@ -70,9 +73,21 @@ class Select_materialize: public select_union select_result *result; /**< the result object of the caller (PS or SP) */ public: Materialized_cursor *materialized_cursor; - Select_materialize(select_result *result_arg) - :result(result_arg), materialized_cursor(0) {} + Select_materialize(THD *thd_arg, select_result *result_arg): + select_union(thd_arg), result(result_arg), materialized_cursor(0) {} virtual bool send_result_set_metadata(List<Item> &list, uint flags); + bool send_eof() + { + if (materialized_cursor) + materialized_cursor->on_table_fill_finished(); + return false; + } + + void abort_result_set() + { + if (materialized_cursor) + materialized_cursor->on_table_fill_finished(); + } }; @@ -97,12 +112,14 @@ public: int mysql_open_cursor(THD *thd, select_result *result, Server_side_cursor **pcursor) { + sql_digest_state *parent_digest; + PSI_statement_locker *parent_locker; select_result *save_result; Select_materialize *result_materialize; LEX *lex= thd->lex; int rc; - if (! (result_materialize= new (thd->mem_root) Select_materialize(result))) + if (!(result_materialize= new (thd->mem_root) Select_materialize(thd, result))) return 1; save_result= lex->result; @@ -115,9 +132,16 @@ int mysql_open_cursor(THD *thd, select_result *result, &thd->security_ctx->priv_user[0], (char *) thd->security_ctx->host_or_ip, 2); + parent_digest= thd->m_digest; + parent_locker= thd->m_statement_psi; + thd->m_digest= NULL; + thd->m_statement_psi= NULL; /* Mark that we can't use query cache with cursors */ thd->query_cache_is_applicable= 0; rc= mysql_execute_command(thd); + thd->lex->restore_set_statement_var(); + thd->m_digest= parent_digest; + thd->m_statement_psi= parent_locker; MYSQL_QUERY_EXEC_DONE(rc); lex->result= save_result; @@ -379,6 +403,29 @@ Materialized_cursor::~Materialized_cursor() } +/* + @brief + Perform actions that are to be done when cursor materialization has + finished. + + @detail + This function is called when "OPEN $cursor" has finished filling the + temporary table with rows that the cursor will return. + + Temporary table has table->field->orig_table pointing at the tables + that are used in the cursor definition query. Pointers to these tables + will not be valid after the query finishes. So, we do what is done for + regular tables: have orig_table point at the table that the fields belong + to. +*/ + +void Materialized_cursor::on_table_fill_finished() +{ + uint fields= table->s->fields; + for (uint i= 0; i < fields; i++) + table->field[i]->orig_table= table->field[i]->table; +} + /*************************************************************************** Select_materialize ****************************************************************************/ diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 580590b38a3..d7ed82a2ef3 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -18,7 +18,7 @@ /* create and drop of databases */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "unireg.h" #include "sql_db.h" @@ -32,11 +32,13 @@ #include "log_event.h" // Query_log_event #include "sql_base.h" // lock_table_names, tdc_remove_table #include "sql_handler.h" // mysql_ha_rm_tables +#include "sql_class.h" #include <mysys_err.h> #include "sp_head.h" #include "sp.h" #include "events.h" #include "sql_handler.h" +#include "sql_statistics.h" #include <my_dir.h> #include <m_ctype.h> #include "log.h" @@ -47,15 +49,12 @@ #define MAX_DROP_TABLE_Q_LEN 1024 -const char *del_exts[]= {".frm", ".BAK", ".TMD",".opt", NullS}; +const char *del_exts[]= {".BAK", ".opt", NullS}; static TYPELIB deletable_extentions= {array_elements(del_exts)-1,"del_exts", del_exts, NULL}; -static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp, - const char *db, - const char *path, - TABLE_LIST **tables, - bool *found_other_files); +static bool find_db_tables_and_rm_known_files(THD *, MY_DIR *, char *, + const char *, TABLE_LIST **); long mysql_rm_arc_files(THD *thd, MY_DIR *dirp, const char *org_path); static my_bool rm_dir_w_symlink(const char *org_path, my_bool send_error); @@ -63,6 +62,8 @@ static void mysql_change_db_impl(THD *thd, LEX_STRING *new_db_name, ulong new_db_access, CHARSET_INFO *new_db_charset); +static bool mysql_rm_db_internal(THD *thd, char *db, + bool if_exists, bool silent); /* Database options hash */ @@ -79,6 +80,29 @@ typedef struct my_dbopt_st } my_dbopt_t; +/** + Return TRUE if db1_name is equal to db2_name, FALSE otherwise. + + The function allows to compare database names according to the MariaDB + rules. The database names db1 and db2 are equal if: + - db1 is NULL and db2 is NULL; + or + - db1 is not-NULL, db2 is not-NULL, db1 is equal to db2 in + table_alias_charset + + This is the same rules as we use for filenames. +*/ + +static inline bool +cmp_db_names(const char *db1_name, + const char *db2_name) +{ + return ((!db1_name && !db2_name) || + (db1_name && db2_name && + my_strcasecmp(table_alias_charset, db1_name, db2_name) == 0)); +} + + /* Function we use in the creation of our hash to get key. */ @@ -160,8 +184,7 @@ bool my_dboptions_cache_init(void) if (!dboptions_init) { dboptions_init= 1; - error= my_hash_init(&dboptions, lower_case_table_names ? - &my_charset_bin : system_charset_info, + error= my_hash_init(&dboptions, table_alias_charset, 32, 0, 0, (my_hash_get_key) dboptions_get_key, free_dbopt,0); } @@ -193,8 +216,7 @@ void my_dbopt_cleanup(void) { mysql_rwlock_wrlock(&LOCK_dboptions); my_hash_free(&dboptions); - my_hash_init(&dboptions, lower_case_table_names ? - &my_charset_bin : system_charset_info, + my_hash_init(&dboptions, table_alias_charset, 32, 0, 0, (my_hash_get_key) dboptions_get_key, free_dbopt,0); mysql_rwlock_unlock(&LOCK_dboptions); @@ -213,7 +235,7 @@ void my_dbopt_cleanup(void) 1 on error. */ -static my_bool get_dbopt(const char *dbname, HA_CREATE_INFO *create) +static my_bool get_dbopt(const char *dbname, Schema_specification_st *create) { my_dbopt_t *opt; uint length; @@ -244,7 +266,7 @@ static my_bool get_dbopt(const char *dbname, HA_CREATE_INFO *create) 1 on error. */ -static my_bool put_dbopt(const char *dbname, HA_CREATE_INFO *create) +static my_bool put_dbopt(const char *dbname, Schema_specification_st *create) { my_dbopt_t *opt; uint length; @@ -313,7 +335,8 @@ static void del_dbopt(const char *path) 1 Could not create file or write to it. Error sent through my_error() */ -static bool write_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) +static bool write_db_opt(THD *thd, const char *path, + Schema_specification_st *create) { register File file; char buf[256]; // Should be enough for one option @@ -359,7 +382,7 @@ static bool write_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) */ -bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) +bool load_db_opt(THD *thd, const char *path, Schema_specification_st *create) { File file; char buf[256]; @@ -407,7 +430,7 @@ bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) get_charset_by_name(pos+1, MYF(0)))) { sql_print_error("Error while loading database options: '%s':",path); - sql_print_error(ER(ER_UNKNOWN_CHARACTER_SET),pos+1); + sql_print_error(ER_THD(thd, ER_UNKNOWN_CHARACTER_SET),pos+1); create->default_table_charset= default_charset_info; } } @@ -417,7 +440,7 @@ bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) MYF(0)))) { sql_print_error("Error while loading database options: '%s':",path); - sql_print_error(ER(ER_UNKNOWN_COLLATION),pos+1); + sql_print_error(ER_THD(thd, ER_UNKNOWN_COLLATION),pos+1); create->default_table_charset= default_charset_info; } } @@ -471,7 +494,7 @@ err1: */ bool load_db_opt_by_name(THD *thd, const char *db_name, - HA_CREATE_INFO *db_create_info) + Schema_specification_st *db_create_info) { char db_opt_path[FN_REFLEN + 1]; @@ -498,7 +521,7 @@ bool load_db_opt_by_name(THD *thd, const char *db_name, CHARSET_INFO *get_default_db_collation(THD *thd, const char *db_name) { - HA_CREATE_INFO db_info; + Schema_specification_st db_info; if (thd->db != NULL && strcmp(db_name, thd->db) == 0) return thd->db_charset; @@ -521,10 +544,11 @@ CHARSET_INFO *get_default_db_collation(THD *thd, const char *db_name) Create a database SYNOPSIS - mysql_create_db() + mysql_create_db_iternal() thd Thread handler db Name of database to create Function assumes that this is already validated. + options DDL options, e.g. IF NOT EXISTS create_info Database create options (like character set) silent Used by replication when internally creating a database. In this case the entry should not be logged. @@ -541,14 +565,14 @@ CHARSET_INFO *get_default_db_collation(THD *thd, const char *db_name) */ -int mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, - bool silent) +static int +mysql_create_db_internal(THD *thd, char *db, + const DDL_options_st &options, + Schema_specification_st *create_info, + bool silent) { char path[FN_REFLEN+16]; - long result= 1; - int error= 0; MY_STAT stat_info; - uint create_options= create_info ? create_info->options : 0; uint path_len; DBUG_ENTER("mysql_create_db"); @@ -559,39 +583,63 @@ int mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, DBUG_RETURN(-1); } - if (lock_schema_name(thd, db)) + char db_tmp[SAFE_NAME_LEN], *dbnorm; + if (lower_case_table_names) + { + strmake_buf(db_tmp, db); + my_casedn_str(system_charset_info, db_tmp); + dbnorm= db_tmp; + } + else + dbnorm= db; + + if (lock_schema_name(thd, dbnorm)) DBUG_RETURN(-1); /* Check directory */ path_len= build_table_filename(path, sizeof(path) - 1, db, "", "", 0); path[path_len-1]= 0; // Remove last '/' from path - if (mysql_file_stat(key_file_misc, path, &stat_info, MYF(0))) + long affected_rows= 1; + if (!mysql_file_stat(key_file_misc, path, &stat_info, MYF(0))) { - if (!(create_options & HA_LEX_CREATE_IF_NOT_EXISTS)) + // The database directory does not exist, or my_file_stat() failed + if (my_errno != ENOENT) { - my_error(ER_DB_CREATE_EXISTS, MYF(0), db); - error= -1; - goto exit; + my_error(EE_STAT, MYF(0), path, my_errno); + DBUG_RETURN(1); } - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_DB_CREATE_EXISTS, ER(ER_DB_CREATE_EXISTS), db); - error= 0; + } + else if (options.or_replace()) + { + if (mysql_rm_db_internal(thd, db, 0, true)) // Removing the old database + DBUG_RETURN(1); + /* + Reset the diagnostics m_status. + It might be set ot DA_OK in mysql_rm_db. + */ + thd->get_stmt_da()->reset_diagnostics_area(); + affected_rows= 2; + } + else if (options.if_not_exists()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_DB_CREATE_EXISTS, ER_THD(thd, ER_DB_CREATE_EXISTS), + db); + affected_rows= 0; goto not_silent; } else { - if (my_errno != ENOENT) - { - my_error(EE_STAT, MYF(0), path, my_errno); - goto exit; - } - if (my_mkdir(path,0777,MYF(0)) < 0) - { - my_error(ER_CANT_CREATE_DB, MYF(0), db, my_errno); - error= -1; - goto exit; - } + my_error(ER_DB_CREATE_EXISTS, MYF(0), db); + DBUG_RETURN(-1); + } + + + if (my_mkdir(path, 0777, MYF(0)) < 0) + { + my_error(ER_CANT_CREATE_DB, MYF(0), db, my_errno); + DBUG_RETURN(-1); } path[path_len-1]= FN_LIBCHAR; @@ -604,10 +652,7 @@ int mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, */ path[path_len]= 0; if (rmdir(path) >= 0) - { - error= -1; - goto exit; - } + DBUG_RETURN(-1); /* We come here when we managed to create the database, but not the option file. In this case it's best to just continue as if nothing has @@ -626,10 +671,6 @@ not_silent: query_length= thd->query_length(); DBUG_ASSERT(query); - ha_binlog_log_query(thd, 0, LOGCOM_CREATE_DB, - query, query_length, - db, ""); - if (mysql_bin_log.is_open()) { int errcode= query_error_code(thd, TRUE); @@ -661,22 +702,20 @@ not_silent: metadata lock on the schema */ if (mysql_bin_log.write(&qinfo)) - { - error= -1; - goto exit; - } + DBUG_RETURN(-1); } - my_ok(thd, result); + my_ok(thd, affected_rows); } -exit: - DBUG_RETURN(error); + DBUG_RETURN(0); } /* db-name is already validated when we come here */ -bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) +static bool +mysql_alter_db_internal(THD *thd, const char *db, + Schema_specification_st *create_info) { char path[FN_REFLEN+16]; long result=1; @@ -705,10 +744,6 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) thd->variables.collation_database= thd->db_charset; } - ha_binlog_log_query(thd, 0, LOGCOM_ALTER_DB, - thd->query(), thd->query_length(), - db, ""); - if (mysql_bin_log.is_open()) { int errcode= query_error_code(thd, TRUE); @@ -736,6 +771,33 @@ exit: } +int mysql_create_db(THD *thd, char *db, DDL_options_st options, + const Schema_specification_st *create_info) +{ + /* + As mysql_create_db_internal() may modify Db_create_info structure passed + to it, we need to use a copy to make execution prepared statement- safe. + */ + Schema_specification_st tmp(*create_info); + if (thd->slave_thread && + slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT) + options.add(DDL_options::OPT_IF_NOT_EXISTS); + return mysql_create_db_internal(thd, db, options, &tmp, false); +} + + +bool mysql_alter_db(THD *thd, const char *db, + const Schema_specification_st *create_info) +{ + /* + As mysql_alter_db_internal() may modify Db_create_info structure passed + to it, we need to use a copy to make execution prepared statement- safe. + */ + Schema_specification_st tmp(*create_info); + return mysql_alter_db_internal(thd, db, &tmp); +} + + /** Drop all tables, routines and events in a database and the database itself. @@ -751,26 +813,48 @@ exit: @retval true Error */ -bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) +static bool +mysql_rm_db_internal(THD *thd,char *db, bool if_exists, bool silent) { ulong deleted_tables= 0; - bool error= true; + bool error= true, rm_mysql_schema; char path[FN_REFLEN + 16]; MY_DIR *dirp; uint length; - bool found_other_files= false; TABLE_LIST *tables= NULL; TABLE_LIST *table; Drop_table_error_handler err_handler; DBUG_ENTER("mysql_rm_db"); + char db_tmp[SAFE_NAME_LEN], *dbnorm; + if (lower_case_table_names) + { + strmake_buf(db_tmp, db); + my_casedn_str(system_charset_info, db_tmp); + dbnorm= db_tmp; + } + else + dbnorm= db; - if (lock_schema_name(thd, db)) + if (lock_schema_name(thd, dbnorm)) DBUG_RETURN(true); length= build_table_filename(path, sizeof(path) - 1, db, "", "", 0); strmov(path+length, MY_DB_OPT_FILE); // Append db option file name del_dbopt(path); // Remove dboption hash entry + /* + Now remove the db.opt file. + The 'find_db_tables_and_rm_known_files' doesn't remove this file + if there exists a table with the name 'db', so let's just do it + separately. We know this file exists and needs to be deleted anyway. + */ + if (mysql_file_delete_with_symlink(key_file_misc, path, "", MYF(0)) && + my_errno != ENOENT) + { + my_error(EE_DELETE, MYF(0), path, my_errno); + DBUG_RETURN(true); + } + path[length]= '\0'; // Remove file name /* See if the directory exists */ @@ -783,55 +867,57 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) } else { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_DB_DROP_EXISTS, ER(ER_DB_DROP_EXISTS), db); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_DB_DROP_EXISTS, ER_THD(thd, ER_DB_DROP_EXISTS), + db); error= false; goto update_binlog; } } - if (find_db_tables_and_rm_known_files(thd, dirp, db, path, &tables, - &found_other_files)) + if (find_db_tables_and_rm_known_files(thd, dirp, dbnorm, path, &tables)) goto exit; /* Disable drop of enabled log tables, must be done before name locking. This check is only needed if we are dropping the "mysql" database. */ - if ((my_strcasecmp(system_charset_info, MYSQL_SCHEMA_NAME.str, db) == 0)) + if ((rm_mysql_schema= + (my_strcasecmp(system_charset_info, MYSQL_SCHEMA_NAME.str, db) == 0))) { for (table= tables; table; table= table->next_local) - { - if (check_if_log_table(table->db_length, table->db, - table->table_name_length, table->table_name, true)) - { - my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP"); + if (check_if_log_table(table, TRUE, "DROP")) goto exit; - } - } } /* Lock all tables and stored routines about to be dropped. */ if (lock_table_names(thd, tables, NULL, thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY) || - lock_db_routines(thd, db)) + 0) || + lock_db_routines(thd, dbnorm)) goto exit; + if (!in_bootstrap && !rm_mysql_schema) + { + for (table= tables; table; table= table->next_local) + { + LEX_STRING db_name= { table->db, table->db_length }; + LEX_STRING table_name= { table->table_name, table->table_name_length }; + if (table->open_type == OT_BASE_ONLY || !find_temporary_table(thd, table)) + (void) delete_statistics_for_table(thd, &db_name, &table_name); + } + } + /* mysql_ha_rm_tables() requires a non-null TABLE_LIST. */ if (tables) mysql_ha_rm_tables(thd, tables); for (table= tables; table; table= table->next_local) - { - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name, - false); deleted_tables++; - } thd->push_internal_handler(&err_handler); if (!thd->killed && !(tables && - mysql_rm_table_no_locks(thd, tables, true, false, true, true))) + mysql_rm_table_no_locks(thd, tables, true, false, true, true, false))) { /* We temporarily disable the binary log while dropping the objects @@ -845,30 +931,25 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) should be dropped while the database is being cleaned, but in the event that a change in the code to remove other objects is made, these drops should still not be logged. - - Notice that the binary log have to be enabled over the call to - ha_drop_database(), since NDB otherwise detects the binary log - as disabled and will not log the drop database statement on any - other connected server. */ ha_drop_database(path); tmp_disable_binlog(thd); - query_cache_invalidate1(thd, db); - (void) sp_drop_db_routines(thd, db); /* @todo Do not ignore errors */ + query_cache_invalidate1(thd, dbnorm); + if (!rm_mysql_schema) + { + (void) sp_drop_db_routines(thd, dbnorm); /* @todo Do not ignore errors */ #ifdef HAVE_EVENT_SCHEDULER - Events::drop_schema_events(thd, db); + Events::drop_schema_events(thd, dbnorm); #endif + } reenable_binlog(thd); /* If the directory is a symbolic link, remove the link first, then remove the directory the symbolic link pointed at */ - if (found_other_files) - my_error(ER_DB_DROP_RMDIR, MYF(0), path, EEXIST); - else - error= rm_dir_w_symlink(path, true); + error= rm_dir_w_symlink(path, true); } thd->pop_internal_handler(); @@ -924,16 +1005,10 @@ update_binlog: for (tbl= tables; tbl; tbl= tbl->next_local) { uint tbl_name_len; - bool exists; char quoted_name[FN_REFLEN+3]; // Only write drop table to the binlog for tables that no longer exist. - if (check_if_table_exists(thd, tbl, 0, &exists)) - { - error= true; - goto exit; - } - if (exists) + if (ha_table_exists(thd, tbl->db, tbl->table_name)) continue; my_snprintf(quoted_name, sizeof(quoted_name), "%`s", tbl->table_name); @@ -977,39 +1052,83 @@ exit: SELECT DATABASE() in the future). For this we free() thd->db and set it to 0. */ - if (thd->db && !strcmp(thd->db, db) && !error) + if (thd->db && cmp_db_names(thd->db, db) && !error) mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server); my_dirend(dirp); DBUG_RETURN(error); } +bool mysql_rm_db(THD *thd,char *db, bool if_exists) +{ + if (thd->slave_thread && + slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT) + if_exists= true; + return mysql_rm_db_internal(thd, db, if_exists, false); +} + + static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp, - const char *db, + char *dbname, const char *path, - TABLE_LIST **tables, - bool *found_other_files) + TABLE_LIST **tables) { char filePath[FN_REFLEN]; + LEX_STRING db= { dbname, strlen(dbname) }; TABLE_LIST *tot_list=0, **tot_list_next_local, **tot_list_next_global; DBUG_ENTER("find_db_tables_and_rm_known_files"); DBUG_PRINT("enter",("path: %s", path)); + /* first, get the list of tables */ + Dynamic_array<LEX_STRING*> files(dirp->number_of_files); + Discovered_table_list tl(thd, &files); + if (ha_discover_table_names(thd, &db, dirp, &tl, true)) + DBUG_RETURN(1); + + /* Now put the tables in the list */ tot_list_next_local= tot_list_next_global= &tot_list; + for (size_t idx=0; idx < files.elements(); idx++) + { + LEX_STRING *table= files.at(idx); + + /* Drop the table nicely */ + TABLE_LIST *table_list=(TABLE_LIST*)thd->calloc(sizeof(*table_list)); + + if (!table_list) + DBUG_RETURN(true); + table_list->db= db.str; + table_list->db_length= db.length; + table_list->table_name= table->str; + table_list->table_name_length= table->length; + table_list->open_type= OT_BASE_ONLY; + + /* To be able to correctly look up the table in the table cache. */ + if (lower_case_table_names) + table_list->table_name_length= my_casedn_str(files_charset_info, + table_list->table_name); + + table_list->alias= table_list->table_name; // If lower_case_table_names=2 + table_list->mdl_request.init(MDL_key::TABLE, table_list->db, + table_list->table_name, MDL_EXCLUSIVE, + MDL_TRANSACTION); + /* Link into list */ + (*tot_list_next_local)= table_list; + (*tot_list_next_global)= table_list; + tot_list_next_local= &table_list->next_local; + tot_list_next_global= &table_list->next_global; + } + *tables= tot_list; + + /* and at last delete all non-table files */ for (uint idx=0 ; - idx < (uint) dirp->number_off_files && !thd->killed ; + idx < (uint) dirp->number_of_files && !thd->killed ; idx++) { FILEINFO *file=dirp->dir_entry+idx; char *extension; DBUG_PRINT("info",("Examining: %s", file->name)); - /* skiping . and .. */ - if (file->name[0] == '.' && (!file->name[1] || - (file->name[1] == '.' && !file->name[2]))) - continue; - if (file->name[0] == 'a' && file->name[1] == 'r' && file->name[2] == 'c' && file->name[3] == '\0') { @@ -1026,59 +1145,12 @@ static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp, DBUG_PRINT("my",("Archive subdir found: %s", newpath)); if ((mysql_rm_arc_files(thd, new_dirp, newpath)) < 0) DBUG_RETURN(true); - continue; } - *found_other_files= true; continue; } if (!(extension= strrchr(file->name, '.'))) extension= strend(file->name); - if (find_type(extension, &deletable_extentions, FIND_TYPE_NO_PREFIX) <= 0) - { - if (find_type(extension, ha_known_exts(), FIND_TYPE_NO_PREFIX) <= 0) - *found_other_files= true; - continue; - } - /* just for safety we use files_charset_info */ - if (db && !my_strcasecmp(files_charset_info, - extension, reg_ext)) - { - /* Drop the table nicely */ - *extension= 0; // Remove extension - TABLE_LIST *table_list=(TABLE_LIST*) - thd->calloc(sizeof(*table_list) + - strlen(db) + 1 + - MYSQL50_TABLE_NAME_PREFIX_LENGTH + - strlen(file->name) + 1); - - if (!table_list) - DBUG_RETURN(true); - table_list->db= (char*) (table_list+1); - table_list->db_length= strmov(table_list->db, db) - table_list->db; - table_list->table_name= table_list->db + table_list->db_length + 1; - table_list->table_name_length= filename_to_tablename(file->name, - table_list->table_name, - MYSQL50_TABLE_NAME_PREFIX_LENGTH + - strlen(file->name) + 1); - table_list->open_type= OT_BASE_ONLY; - - /* To be able to correctly look up the table in the table cache. */ - if (lower_case_table_names) - table_list->table_name_length= my_casedn_str(files_charset_info, - table_list->table_name); - - table_list->alias= table_list->table_name; // If lower_case_table_names=2 - table_list->internal_tmp_table= is_prefix(file->name, tmp_file_prefix); - table_list->mdl_request.init(MDL_key::TABLE, table_list->db, - table_list->table_name, MDL_EXCLUSIVE, - MDL_TRANSACTION); - /* Link into list */ - (*tot_list_next_local)= table_list; - (*tot_list_next_global)= table_list; - tot_list_next_local= &table_list->next_local; - tot_list_next_global= &table_list->next_global; - } - else + if (find_type(extension, &deletable_extentions, FIND_TYPE_NO_PREFIX) > 0) { strxmov(filePath, path, "/", file->name, NullS); /* @@ -1093,7 +1165,7 @@ static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp, } } } - *tables= tot_list; + DBUG_RETURN(false); } @@ -1177,18 +1249,13 @@ long mysql_rm_arc_files(THD *thd, MY_DIR *dirp, const char *org_path) DBUG_PRINT("enter", ("path: %s", org_path)); for (uint idx=0 ; - idx < (uint) dirp->number_off_files && !thd->killed ; + idx < (uint) dirp->number_of_files && !thd->killed ; idx++) { FILEINFO *file=dirp->dir_entry+idx; char *extension, *revision; DBUG_PRINT("info",("Examining: %s", file->name)); - /* skiping . and .. */ - if (file->name[0] == '.' && (!file->name[1] || - (file->name[1] == '.' && !file->name[2]))) - continue; - extension= fn_ext(file->name); if (extension[0] != '.' || extension[1] != 'f' || extension[2] != 'r' || @@ -1328,27 +1395,6 @@ static void backup_current_db_name(THD *thd, /** - Return TRUE if db1_name is equal to db2_name, FALSE otherwise. - - The function allows to compare database names according to the MySQL - rules. The database names db1 and db2 are equal if: - - db1 is NULL and db2 is NULL; - or - - db1 is not-NULL, db2 is not-NULL, db1 is equal (ignoring case) to - db2 in system character set (UTF8). -*/ - -static inline bool -cmp_db_names(const char *db1_name, - const char *db2_name) -{ - return ((!db1_name && !db2_name) || - (db1_name && db2_name && - my_strcasecmp(system_charset_info, db1_name, db2_name) == 0)); -} - - -/** @brief Change the current database and its attributes unconditionally. @param thd thread handle @@ -1438,7 +1484,7 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch) } else { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); + my_message(ER_NO_DB_ERROR, ER_THD(thd, ER_NO_DB_ERROR), MYF(0)); DBUG_RETURN(TRUE); } @@ -1492,14 +1538,18 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch) DBUG_PRINT("info",("Use database: %s", new_db_file_name.str)); #ifndef NO_EMBEDDED_ACCESS_CHECKS - db_access= - test_all_bits(sctx->master_access, DB_ACLS) ? - DB_ACLS : - acl_get(sctx->host, - sctx->ip, - sctx->priv_user, - new_db_file_name.str, - FALSE) | sctx->master_access; + if (test_all_bits(sctx->master_access, DB_ACLS)) + db_access= DB_ACLS; + else + { + db_access= acl_get(sctx->host, sctx->ip, sctx->priv_user, + new_db_file_name.str, FALSE) | sctx->master_access; + if (sctx->priv_role[0]) + { + /* include a possible currently set role for access */ + db_access|= acl_get("", "", sctx->priv_role, new_db_file_name.str, FALSE); + } + } if (!force_switch && !(db_access & DB_ACLS) && @@ -1509,7 +1559,7 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch) sctx->priv_user, sctx->priv_host, new_db_file_name.str); - general_log_print(thd, COM_INIT_DB, ER(ER_DBACCESS_DENIED_ERROR), + general_log_print(thd, COM_INIT_DB, ER_THD(thd, ER_DBACCESS_DENIED_ERROR), sctx->priv_user, sctx->priv_host, new_db_file_name.str); my_free(new_db_file_name.str); DBUG_RETURN(TRUE); @@ -1524,8 +1574,8 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch) { /* Throw a warning and free new_db_file_name. */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_BAD_DB_ERROR, ER(ER_BAD_DB_ERROR), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_BAD_DB_ERROR, ER_THD(thd, ER_BAD_DB_ERROR), new_db_file_name.str); my_free(new_db_file_name.str); @@ -1620,7 +1670,7 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) int error= 0, change_to_newdb= 0; char path[FN_REFLEN+16]; uint length; - HA_CREATE_INFO create_info; + Schema_specification_st create_info; MY_DIR *dirp; TABLE_LIST *table_list; SELECT_LEX *sl= thd->lex->current_select; @@ -1668,13 +1718,14 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) } /* Step1: Create the new database */ - if ((error= mysql_create_db(thd, new_db.str, &create_info, 1))) + if ((error= mysql_create_db_internal(thd, new_db.str, + DDL_options(), &create_info, 1))) goto exit; /* Step2: Move tables to the new database */ if ((dirp = my_dir(path,MYF(MY_DONT_SORT)))) { - uint nfiles= (uint) dirp->number_off_files; + uint nfiles= (uint) dirp->number_of_files; for (uint idx=0 ; idx < nfiles && !thd->killed ; idx++) { FILEINFO *file= dirp->dir_entry + idx; @@ -1692,7 +1743,7 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) table_str.length= filename_to_tablename(file->name, tname, sizeof(tname)-1); - table_str.str= (char*) sql_memdup(tname, table_str.length + 1); + table_str.str= (char*) thd->memdup(tname, table_str.length + 1); Table_ident *old_ident= new Table_ident(thd, *old_db, table_str, 0); Table_ident *new_ident= new Table_ident(thd, new_db, table_str, 0); if (!old_ident || !new_ident || @@ -1765,17 +1816,15 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) if ((dirp = my_dir(path,MYF(MY_DONT_SORT)))) { - uint nfiles= (uint) dirp->number_off_files; + uint nfiles= (uint) dirp->number_of_files; for (uint idx=0 ; idx < nfiles ; idx++) { FILEINFO *file= dirp->dir_entry + idx; char oldname[FN_REFLEN + 1], newname[FN_REFLEN + 1]; DBUG_PRINT("info",("Examining: %s", file->name)); - /* skiping . and .. and MY_DB_OPT_FILE */ - if ((file->name[0] == '.' && - (!file->name[1] || (file->name[1] == '.' && !file->name[2]))) || - !my_strcasecmp(files_charset_info, file->name, MY_DB_OPT_FILE)) + /* skiping MY_DB_OPT_FILE */ + if (!my_strcasecmp(files_charset_info, file->name, MY_DB_OPT_FILE)) continue; /* pass empty file name, and file->name as extension to avoid encoding */ @@ -1794,7 +1843,7 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) to execute them again. mysql_rm_db() also "unuses" if we drop the current database. */ - error= mysql_rm_db(thd, old_db->str, 0, 1); + error= mysql_rm_db_internal(thd, old_db->str, 0, true); /* Step8: logging */ if (mysql_bin_log.is_open()) diff --git a/sql/sql_db.h b/sql/sql_db.h index 1f447c11a52..ed8417a7793 100644 --- a/sql/sql_db.h +++ b/sql/sql_db.h @@ -19,11 +19,12 @@ #include "hash.h" /* HASH */ class THD; -typedef struct st_ha_create_information HA_CREATE_INFO; -int mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create, bool silent); -bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create); -bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent); +int mysql_create_db(THD *thd, char *db, DDL_options_st options, + const Schema_specification_st *create); +bool mysql_alter_db(THD *thd, const char *db, + const Schema_specification_st *create); +bool mysql_rm_db(THD *thd, char *db, bool if_exists); bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db); bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch); @@ -36,9 +37,9 @@ bool mysql_opt_change_db(THD *thd, bool my_dboptions_cache_init(void); void my_dboptions_cache_free(void); bool check_db_dir_existence(const char *db_name); -bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create); +bool load_db_opt(THD *thd, const char *path, Schema_specification_st *create); bool load_db_opt_by_name(THD *thd, const char *db_name, - HA_CREATE_INFO *db_create_info); + Schema_specification_st *db_create_info); CHARSET_INFO *get_default_db_collation(THD *thd, const char *db_name); bool my_dbopt_init(void); void my_dbopt_cleanup(void); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index ab573df7992..9f3caf9df4f 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1,5 +1,6 @@ /* Copyright (c) 2000, 2010, Oracle and/or its affiliates. + Copyright (c) 2010, 2015, 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 @@ -20,12 +21,12 @@ Multi-table deletes were introduced by Monty and Sinisa */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_delete.h" #include "sql_cache.h" // query_cache_* #include "sql_base.h" // open_temprary_table -#include "sql_table.h" // build_table_filename #include "lock.h" // unlock_table_name #include "sql_view.h" // check_key_in_view, mysql_frm_type #include "sql_parse.h" // mysql_init_select @@ -35,10 +36,180 @@ #include "sql_select.h" #include "sp_head.h" #include "sql_trigger.h" +#include "sql_statistics.h" #include "transaction.h" #include "records.h" // init_read_record, #include "sql_derived.h" // mysql_handle_derived // end_read_record +#include "sql_partition.h" // make_used_partitions_str + +/* + @brief + Print query plan of a single-table DELETE command + + @detail + This function is used by EXPLAIN DELETE and by SHOW EXPLAIN when it is + invoked on a running DELETE statement. +*/ + +Explain_delete* Delete_plan::save_explain_delete_data(MEM_ROOT *mem_root, THD *thd) +{ + Explain_query *query= thd->lex->explain; + Explain_delete *explain= + new (mem_root) Explain_delete(mem_root, thd->lex->analyze_stmt); + + if (deleting_all_rows) + { + explain->deleting_all_rows= true; + explain->select_type= "SIMPLE"; + explain->rows= scanned_rows; + } + else + { + explain->deleting_all_rows= false; + Update_plan::save_explain_data_intern(mem_root, explain, + thd->lex->analyze_stmt); + } + + query->add_upd_del_plan(explain); + return explain; +} + + +Explain_update* +Update_plan::save_explain_update_data(MEM_ROOT *mem_root, THD *thd) +{ + Explain_query *query= thd->lex->explain; + Explain_update* explain= + new (mem_root) Explain_update(mem_root, thd->lex->analyze_stmt); + save_explain_data_intern(mem_root, explain, thd->lex->analyze_stmt); + query->add_upd_del_plan(explain); + return explain; +} + + +void Update_plan::save_explain_data_intern(MEM_ROOT *mem_root, + Explain_update *explain, + bool is_analyze) +{ + explain->select_type= "SIMPLE"; + explain->table_name.append(table->pos_in_table_list->alias); + + explain->impossible_where= false; + explain->no_partitions= false; + + if (impossible_where) + { + explain->impossible_where= true; + return; + } + + if (no_partitions) + { + explain->no_partitions= true; + return; + } + + if (is_analyze) + table->file->set_time_tracker(&explain->table_tracker); + + select_lex->set_explain_type(TRUE); + explain->select_type= select_lex->type; + /* Partitions */ + { +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *part_info; + if ((part_info= table->part_info)) + { + make_used_partitions_str(mem_root, part_info, &explain->used_partitions, + explain->used_partitions_list); + explain->used_partitions_set= true; + } + else + explain->used_partitions_set= false; +#else + /* just produce empty column if partitioning is not compiled in */ + explain->used_partitions_set= false; +#endif + } + + + /* Set jtype */ + if (select && select->quick) + { + int quick_type= select->quick->get_type(); + if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || + (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) + explain->jtype= JT_INDEX_MERGE; + else + explain->jtype= JT_RANGE; + } + else + { + if (index == MAX_KEY) + explain->jtype= JT_ALL; + else + explain->jtype= JT_NEXT; + } + + explain->using_where= MY_TEST(select && select->cond); + explain->where_cond= select? select->cond: NULL; + + if (using_filesort) + explain->filesort_tracker= new (mem_root) Filesort_tracker(is_analyze); + explain->using_io_buffer= using_io_buffer; + + append_possible_keys(mem_root, explain->possible_keys, table, + possible_keys); + + explain->quick_info= NULL; + + /* Calculate key_len */ + if (select && select->quick) + { + explain->quick_info= select->quick->get_explain(mem_root); + } + else + { + if (index != MAX_KEY) + { + explain->key.set(mem_root, &table->key_info[index], + table->key_info[index].key_length); + } + } + explain->rows= scanned_rows; + + if (select && select->quick && + select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE) + { + explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, + &explain->mrr_type); + } + + bool skip= updating_a_view; + + /* Save subquery children */ + for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit(); + unit; + unit= unit->next_unit()) + { + if (skip) + { + skip= false; + continue; + } + /* + Display subqueries only if they are not parts of eliminated WHERE/ON + clauses. + */ + if (!(unit->item && unit->item->eliminated)) + explain->add_child(unit->first_select()->select_number); + } +} + + /** Implement DELETE SQL word. @@ -48,7 +219,8 @@ */ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - SQL_I_List<ORDER> *order_list, ha_rows limit, ulonglong options) + SQL_I_List<ORDER> *order_list, ha_rows limit, + ulonglong options, select_result *result) { bool will_batch; int error, loc_error; @@ -62,12 +234,17 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, bool reverse= FALSE; ORDER *order= (ORDER *) ((order_list && order_list->elements) ? order_list->first : NULL); - uint usable_index= MAX_KEY; SELECT_LEX *select_lex= &thd->lex->select_lex; killed_state killed_status= NOT_KILLED; THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE; + bool with_select= !select_lex->item_list.is_empty(); + Explain_delete *explain; + Delete_plan query_plan(thd->mem_root); + query_plan.index= MAX_KEY; + query_plan.using_filesort= FALSE; DBUG_ENTER("mysql_delete"); + create_explain_query(thd->lex, thd->mem_root); if (open_and_lock_tables(thd, table_list, TRUE, 0)) DBUG_RETURN(TRUE); @@ -87,11 +264,18 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, table_list->view_db.str, table_list->view_name.str); DBUG_RETURN(TRUE); } - thd_proc_info(thd, "init"); + THD_STAGE_INFO(thd, stage_init); table->map=1; + query_plan.select_lex= &thd->lex->select_lex; + query_plan.table= table; + query_plan.updating_a_view= MY_TEST(table_list->view); - if (mysql_prepare_delete(thd, table_list, &conds)) + if (mysql_prepare_delete(thd, table_list, select_lex->with_wild, + select_lex->item_list, &conds)) DBUG_RETURN(TRUE); + + if (with_select) + (void) result->prepare(select_lex->item_list, NULL); if (thd->lex->current_select->first_cond_optimization) { @@ -113,7 +297,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, setup_order(thd, select_lex->ref_pointer_array, &tables, fields, all_fields, order)) { - delete select; free_underlaid_joins(thd, &thd->lex->select_lex); DBUG_RETURN(TRUE); } @@ -124,11 +307,11 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, DBUG_RETURN(TRUE); const_cond= (!conds || conds->const_item()); - safe_update=test(thd->variables.option_bits & OPTION_SAFE_UPDATES); + safe_update= MY_TEST(thd->variables.option_bits & OPTION_SAFE_UPDATES); if (safe_update && const_cond) { my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, - ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); + ER_THD(thd, ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); DBUG_RETURN(TRUE); } @@ -154,14 +337,19 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - We should not be binlogging this statement in row-based, and - there should be no delete triggers associated with the table. */ - if (!using_limit && const_cond_result && - (!thd->is_current_stmt_binlog_format_row() && - !(table->triggers && table->triggers->has_delete_triggers()))) + if (!with_select && !using_limit && const_cond_result && + (!thd->is_current_stmt_binlog_format_row() && + !(table->triggers && table->triggers->has_delete_triggers()))) { /* Update the table->file->stats.records number */ table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); ha_rows const maybe_deleted= table->file->stats.records; DBUG_PRINT("debug", ("Trying to use delete_all_rows()")); + + query_plan.set_delete_all_rows(maybe_deleted); + if (thd->lex->describe) + goto produce_explain_and_leave; + if (!(error=table->file->ha_delete_all_rows())) { /* @@ -171,6 +359,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, query_type= THD::STMT_QUERY_TYPE; error= -1; deleted= maybe_deleted; + query_plan.save_explain_delete_data(thd->mem_root, thd); goto cleanup; } if (error != HA_ERR_WRONG_COMMAND) @@ -180,26 +369,37 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, goto cleanup; } /* Handler didn't support fast delete; Delete rows one by one */ + query_plan.cancel_delete_all_rows(); } if (conds) { Item::cond_result result; - conds= remove_eq_conds(thd, conds, &result); + conds= conds->remove_eq_conds(thd, &result, true); if (result == Item::COND_FALSE) // Impossible where + { limit= 0; + query_plan.set_impossible_where(); + if (thd->lex->describe || thd->lex->analyze_stmt) + goto produce_explain_and_leave; + } } #ifdef WITH_PARTITION_STORAGE_ENGINE if (prune_partitions(thd, table, conds)) { free_underlaid_joins(thd, select_lex); - // No matching record + + query_plan.set_no_partitions(); + if (thd->lex->describe || thd->lex->analyze_stmt) + goto produce_explain_and_leave; + my_ok(thd, 0); DBUG_RETURN(0); } #endif /* Update the table->file->stats.records number */ table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); + set_statistics_for_table(thd, table); table->covering_keys.clear_all(); table->quick_keys.clear_all(); // Can't use 'only index' @@ -209,6 +409,10 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, DBUG_RETURN(TRUE); if ((select && select->check_quick(thd, safe_update, limit)) || !limit) { + query_plan.set_impossible_where(); + if (thd->lex->describe || thd->lex->analyze_stmt) + goto produce_explain_and_leave; + delete select; free_underlaid_joins(thd, select_lex); /* @@ -226,54 +430,90 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, /* If running in safe sql mode, don't allow updates without keys */ if (table->quick_keys.is_clear_all()) { - thd->server_status|=SERVER_QUERY_NO_INDEX_USED; + thd->set_status_no_index_used(); if (safe_update && !using_limit) { delete select; free_underlaid_joins(thd, select_lex); my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, - ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); + ER_THD(thd, ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); DBUG_RETURN(TRUE); } } if (options & OPTION_QUICK) (void) table->file->extra(HA_EXTRA_QUICK); + query_plan.scanned_rows= select? select->records: table->file->stats.records; if (order) { - uint length= 0; - SORT_FIELD *sortorder; - ha_rows examined_rows; - table->update_const_key_parts(conds); order= simple_remove_const(order, conds); - bool need_sort; if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered") - need_sort= FALSE; - usable_index= MAX_KEY; + query_plan.using_filesort= FALSE; + query_plan.index= MAX_KEY; } else - usable_index= get_index_for_order(order, table, select, limit, - &need_sort, &reverse); - if (need_sort) { - DBUG_ASSERT(usable_index == MAX_KEY); + ha_rows scanned_limit= query_plan.scanned_rows; + query_plan.index= get_index_for_order(order, table, select, limit, + &scanned_limit, + &query_plan.using_filesort, + &reverse); + if (!query_plan.using_filesort) + query_plan.scanned_rows= scanned_limit; + } + } + + query_plan.select= select; + query_plan.possible_keys= select? select->possible_keys: key_map(0); + + /* + Ok, we have generated a query plan for the DELETE. + - if we're running EXPLAIN DELETE, goto produce explain output + - otherwise, execute the query plan + */ + if (thd->lex->describe) + goto produce_explain_and_leave; + + explain= query_plan.save_explain_delete_data(thd->mem_root, thd); + ANALYZE_START_TRACKING(&explain->command_tracker); + + DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", + dbug_serve_apcs(thd, 1);); + + if (!(select && select->quick)) + status_var_increment(thd->status_var.delete_scan_count); + + if (query_plan.using_filesort) + { + ha_rows examined_rows; + ha_rows found_rows; + uint length= 0; + SORT_FIELD *sortorder; + + { + DBUG_ASSERT(query_plan.index == MAX_KEY); table->sort.io_cache= (IO_CACHE *) my_malloc(sizeof(IO_CACHE), - MYF(MY_FAE | MY_ZEROFILL)); - - if (!(sortorder= make_unireg_sortorder(order, &length, NULL)) || - (table->sort.found_records = filesort(thd, table, sortorder, length, - select, HA_POS_ERROR, 1, - &examined_rows)) - == HA_POS_ERROR) + MYF(MY_FAE | MY_ZEROFILL | + MY_THREAD_SPECIFIC)); + Filesort_tracker *fs_tracker= + thd->lex->explain->get_upd_del_plan()->filesort_tracker; + + if (!(sortorder= make_unireg_sortorder(thd, NULL, 0, order, &length, NULL)) || + (table->sort.found_records= filesort(thd, table, sortorder, length, + select, HA_POS_ERROR, + true, + &examined_rows, &found_rows, + fs_tracker)) + == HA_POS_ERROR) { delete select; free_underlaid_joins(thd, &thd->lex->select_lex); DBUG_RETURN(TRUE); } - thd->examined_row_count+= examined_rows; + thd->inc_examined_row_count(examined_rows); /* Filesort has already found and selected the rows we want to delete, so we don't need the where clause @@ -291,48 +531,53 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, free_underlaid_joins(thd, select_lex); DBUG_RETURN(TRUE); } - if (usable_index == MAX_KEY || (select && select->quick)) + + if (query_plan.index == MAX_KEY || (select && select->quick)) + error= init_read_record(&info, thd, table, select, 1, 1, FALSE); + else + error= init_read_record_idx(&info, thd, table, 1, query_plan.index, + reverse); + if (error) { - if (init_read_record(&info, thd, table, select, 1, 1, FALSE)) - { - delete select; - free_underlaid_joins(thd, select_lex); - DBUG_RETURN(TRUE); - } + delete select; + free_underlaid_joins(thd, select_lex); + DBUG_RETURN(TRUE); } - else - init_read_record_idx(&info, thd, table, 1, usable_index, reverse); init_ftfuncs(thd, select_lex, 1); - thd_proc_info(thd, "updating"); + THD_STAGE_INFO(thd, stage_updating); - if (table->triggers && - table->triggers->has_triggers(TRG_EVENT_DELETE, - TRG_ACTION_AFTER)) + if (table->prepare_triggers_for_delete_stmt_or_event()) { - /* - The table has AFTER DELETE triggers that might access to subject table - and therefore might need delete to be done immediately. So we turn-off - the batching. - */ - (void) table->file->extra(HA_EXTRA_DELETE_CANNOT_BATCH); will_batch= FALSE; } else will_batch= !table->file->start_bulk_delete(); - table->mark_columns_needed_for_delete(); + if (with_select) + { + if (result->send_result_set_metadata(select_lex->item_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + goto cleanup; + } + + explain= (Explain_delete*)thd->lex->explain->get_upd_del_plan(); + explain->tracker.on_scan_init(); + while (!(error=info.read_record(&info)) && !thd->killed && ! thd->is_error()) { + explain->tracker.on_record_read(); if (table->vfield) update_virtual_fields(thd, table, VCOL_UPDATE_FOR_READ); - thd->examined_row_count++; + thd->inc_examined_row_count(1); // thd->is_error() is tested to disallow delete row on error if (!select || select->skip_record(thd) > 0) { + explain->tracker.on_record_after_where(); if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, TRG_ACTION_BEFORE, FALSE)) @@ -341,6 +586,12 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, break; } + if (with_select && result->send_data(select_lex->item_list) < 0) + { + error=1; + break; + } + if (!(error= table->file->ha_delete_row(table->record[0]))) { deleted++; @@ -386,10 +637,11 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, table->file->print_error(loc_error,MYF(0)); error=1; } - thd_proc_info(thd, "end"); + THD_STAGE_INFO(thd, stage_end); end_read_record(&info); if (options & OPTION_QUICK) (void) table->file->extra(HA_EXTRA_NORMAL); + ANALYZE_STOP_TRACKING(&explain->command_tracker); cleanup: /* @@ -408,23 +660,24 @@ cleanup: } delete select; + select= NULL; transactional_table= table->file->has_transactions(); if (!transactional_table && deleted > 0) thd->transaction.stmt.modified_non_trans_table= thd->transaction.all.modified_non_trans_table= TRUE; - + /* See similar binlogging code in sql_update.cc, for comments */ if ((error < 0) || thd->transaction.stmt.modified_non_trans_table) { - if (mysql_bin_log.is_open()) + if (WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) { int errcode= 0; if (error < 0) thd->clear_error(); else errcode= query_error_code(thd, killed_status == NOT_KILLED); - + /* [binlog]: If 'handler::delete_all_rows()' was called and the storage engine does not inject the rows itself, we replicate @@ -443,14 +696,42 @@ cleanup: } } DBUG_ASSERT(transactional_table || !deleted || thd->transaction.stmt.modified_non_trans_table); + + free_underlaid_joins(thd, select_lex); if (error < 0 || (thd->lex->ignore && !thd->is_error() && !thd->is_fatal_error)) { - my_ok(thd, deleted); + if (thd->lex->analyze_stmt) + goto send_nothing_and_leave; + + if (with_select) + result->send_eof(); + else + my_ok(thd, deleted); DBUG_PRINT("info",("%ld records deleted",(long) deleted)); } DBUG_RETURN(error >= 0 || thd->is_error()); + + /* Special exits */ +produce_explain_and_leave: + /* + We come here for various "degenerate" query plans: impossible WHERE, + no-partitions-used, impossible-range, etc. + */ + query_plan.save_explain_delete_data(thd->mem_root, thd); + +send_nothing_and_leave: + /* + ANALYZE DELETE jumps here. We can't send explain right here, because + we might be using ANALYZE DELETE ...RETURNING, in which case we have + Protocol_discard active. + */ + + delete select; + free_underlaid_joins(thd, select_lex); + //table->set_keyread(false); + DBUG_RETURN((thd->is_error() || thd->killed) ? 1 : 0); } @@ -461,13 +742,16 @@ cleanup: mysql_prepare_delete() thd - thread handler table_list - global/local table list + wild_num - number of wildcards used in optional SELECT clause + field_list - list of items in optional SELECT clause conds - conditions RETURN VALUE FALSE OK TRUE error */ -int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds) + int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, + uint wild_num, List<Item> &field_list, Item **conds) { Item *fake_conds= 0; SELECT_LEX *select_lex= &thd->lex->select_lex; @@ -479,7 +763,10 @@ int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds) &thd->lex->select_lex.top_join_list, table_list, select_lex->leaf_tables, FALSE, - DELETE_ACL, SELECT_ACL, TRUE) || + DELETE_ACL, SELECT_ACL, TRUE)) + DBUG_RETURN(TRUE); + if ((wild_num && setup_wild(thd, table_list, field_list, NULL, wild_num)) || + setup_fields(thd, NULL, field_list, MARK_COLUMNS_READ, NULL, NULL, 0) || setup_conds(thd, table_list, select_lex->leaf_tables, conds) || setup_ftfuncs(select_lex)) DBUG_RETURN(TRUE); @@ -615,12 +902,12 @@ int mysql_multi_delete_prepare(THD *thd) } -multi_delete::multi_delete(TABLE_LIST *dt, uint num_of_tables_arg) - : delete_tables(dt), deleted(0), found(0), +multi_delete::multi_delete(THD *thd_arg, TABLE_LIST *dt, uint num_of_tables_arg): + select_result_interceptor(thd_arg), delete_tables(dt), deleted(0), found(0), num_of_tables(num_of_tables_arg), error(0), do_delete(0), transactional_tables(0), normal_tables(0), error_handled(0) { - tempfiles= (Unique **) sql_calloc(sizeof(Unique *) * num_of_tables); + tempfiles= (Unique **) thd_arg->calloc(sizeof(Unique *) * num_of_tables); } @@ -630,7 +917,7 @@ multi_delete::prepare(List<Item> &values, SELECT_LEX_UNIT *u) DBUG_ENTER("multi_delete::prepare"); unit= u; do_delete= 1; - thd_proc_info(thd, "deleting from main table"); + THD_STAGE_INFO(thd, stage_deleting_from_main_table); SELECT_LEX *select_lex= u->first_select(); if (select_lex->first_cond_optimization) { @@ -658,7 +945,7 @@ multi_delete::initialize_tables(JOIN *join) TABLE_LIST *tbl= walk->correspondent_table->find_table_for_update(); tables_to_delete_from|= tbl->table->map; if (delete_while_scanning && - unique_table(thd, tbl, join->tables_list, false)) + unique_table(thd, tbl, join->tables_list, 0)) { /* If the table we are going to delete from appears @@ -672,8 +959,8 @@ multi_delete::initialize_tables(JOIN *join) walk= delete_tables; - for (JOIN_TAB *tab= first_linear_tab(join, WITHOUT_BUSH_ROOTS, - WITH_CONST_TABLES); + for (JOIN_TAB *tab= first_linear_tab(join, WITHOUT_BUSH_ROOTS, + WITH_CONST_TABLES); tab; tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS)) { @@ -691,17 +978,7 @@ multi_delete::initialize_tables(JOIN *join) transactional_tables= 1; else normal_tables= 1; - if (tbl->triggers && - tbl->triggers->has_triggers(TRG_EVENT_DELETE, - TRG_ACTION_AFTER)) - { - /* - The table has AFTER DELETE triggers that might access to subject - table and therefore might need delete to be done immediately. - So we turn-off the batching. - */ - (void) tbl->file->extra(HA_EXTRA_DELETE_CANNOT_BATCH); - } + tbl->prepare_triggers_for_delete_stmt_or_event(); tbl->prepare_for_position(); tbl->mark_columns_needed_for_delete(); } @@ -819,17 +1096,6 @@ int multi_delete::send_data(List<Item> &values) } -void multi_delete::send_error(uint errcode,const char *err) -{ - DBUG_ENTER("multi_delete::send_error"); - - /* First send error what ever it is ... */ - my_message(errcode, err, MYF(0)); - - DBUG_VOID_RETURN; -} - - void multi_delete::abort_result_set() { DBUG_ENTER("multi_delete::abort_result_set"); @@ -845,6 +1111,8 @@ void multi_delete::abort_result_set() if (thd->transaction.stmt.modified_non_trans_table) thd->transaction.all.modified_non_trans_table= TRUE; + thd->transaction.all.m_unsafe_rollback_flags|= + (thd->transaction.stmt.m_unsafe_rollback_flags & THD_TRANS::DID_WAIT); /* If rows from the first table only has been deleted and it is @@ -865,13 +1133,13 @@ void multi_delete::abort_result_set() DBUG_ASSERT(error_handled); DBUG_VOID_RETURN; } - + if (thd->transaction.stmt.modified_non_trans_table) { - /* + /* there is only side effects; to binlog with the error */ - if (mysql_bin_log.is_open()) + if (WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) { int errcode= query_error_code(thd, thd->killed == NOT_KILLED); /* possible error of writing binary log is ignored deliberately */ @@ -1023,7 +1291,7 @@ int multi_delete::do_table_deletes(TABLE *table, bool ignore) bool multi_delete::send_eof() { killed_state killed_status= NOT_KILLED; - thd_proc_info(thd, "deleting from reference tables"); + THD_STAGE_INFO(thd, stage_deleting_from_reference_tables); /* Does deletes for the last n - 1 tables, returns 0 if ok */ int local_error= do_deletes(); // returns 0 if success @@ -1032,10 +1300,12 @@ bool multi_delete::send_eof() local_error= local_error || error; killed_status= (local_error == 0)? NOT_KILLED : thd->killed; /* reset used flags */ - thd_proc_info(thd, "end"); + THD_STAGE_INFO(thd, stage_end); if (thd->transaction.stmt.modified_non_trans_table) thd->transaction.all.modified_non_trans_table= TRUE; + thd->transaction.all.m_unsafe_rollback_flags|= + (thd->transaction.stmt.m_unsafe_rollback_flags & THD_TRANS::DID_WAIT); /* We must invalidate the query cache before binlog writing and @@ -1047,7 +1317,7 @@ bool multi_delete::send_eof() } if ((local_error == 0) || thd->transaction.stmt.modified_non_trans_table) { - if (mysql_bin_log.is_open()) + if(WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) { int errcode= 0; if (local_error == 0) @@ -1064,9 +1334,9 @@ bool multi_delete::send_eof() } } if (local_error != 0) - error_handled= TRUE; // to force early leave from ::send_error() + error_handled= TRUE; // to force early leave from ::abort_result_set() - if (!local_error) + if (!local_error && !thd->lex->analyze_stmt) { ::my_ok(thd, deleted); } diff --git a/sql/sql_delete.h b/sql/sql_delete.h index 6147e0ea367..9cd09dc5722 100644 --- a/sql/sql_delete.h +++ b/sql/sql_delete.h @@ -21,12 +21,15 @@ class THD; struct TABLE_LIST; class Item; +class select_result; typedef class Item COND; template <typename T> class SQL_I_List; -int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds); +int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, + uint wild_num, List<Item> &field_list, Item **conds); bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - SQL_I_List<ORDER> *order, ha_rows rows, ulonglong options); + SQL_I_List<ORDER> *order, ha_rows rows, + ulonglong options, select_result *result); #endif /* SQL_DELETE_INCLUDED */ diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 1709669d8a2..c93aa132e37 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -1,5 +1,6 @@ /* Copyright (c) 2002, 2011, Oracle and/or its affiliates. + Copyright (c) 2010, 2015, 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 @@ -21,7 +22,7 @@ */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "unireg.h" #include "sql_derived.h" @@ -387,7 +388,7 @@ bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived) if (!derived->get_unit()->prepared) { dt_select->leaf_tables.empty(); - make_leaves_list(dt_select->leaf_tables, derived, TRUE, 0); + make_leaves_list(thd, dt_select->leaf_tables, derived, TRUE, 0); } derived->nested_join= (NESTED_JOIN*) thd->calloc(sizeof(NESTED_JOIN)); @@ -423,10 +424,10 @@ bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived) if (derived->get_unit()->prepared) { Item *expr= derived->on_expr; - expr= and_conds(expr, dt_select->join ? dt_select->join->conds : 0); + expr= and_conds(thd, expr, dt_select->join ? dt_select->join->conds : 0); if (expr) expr->top_level_item(); - + if (expr && (derived->prep_on_expr || expr != derived->on_expr)) { derived->on_expr= expr; @@ -443,7 +444,8 @@ bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived) // Update used tables cache according to new table map if (derived->on_expr) { - derived->on_expr->fix_after_pullout(parent_lex, &derived->on_expr); + derived->on_expr->fix_after_pullout(parent_lex, &derived->on_expr, + TRUE); fix_list_after_tbl_changes(parent_lex, &derived->nested_join->join_list); } } @@ -489,6 +491,14 @@ bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived) DBUG_PRINT("enter", ("Alias: '%s' Unit: %p", (derived->alias ? derived->alias : "<NULL>"), derived->get_unit())); + DBUG_PRINT("info", ("merged_for_insert: %d is_materialized_derived: %d " + "is_multitable: %d single_table_updatable: %d " + "merge_underlying_list: %d", + derived->merged_for_insert, + derived->is_materialized_derived(), + derived->is_multitable(), + derived->single_table_updatable(), + derived->merge_underlying_list != 0)); if (derived->merged_for_insert) DBUG_RETURN(FALSE); if (derived->init_derived(thd, FALSE)) @@ -507,6 +517,7 @@ bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived) derived->table= derived->merge_underlying_list->table; derived->schema_table= derived->merge_underlying_list->schema_table; derived->merged_for_insert= TRUE; + DBUG_ASSERT(derived->table); } } DBUG_RETURN(FALSE); @@ -610,7 +621,7 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) bool res= FALSE; DBUG_PRINT("enter", ("Alias: '%s' Unit: %p", (derived->alias ? derived->alias : "<NULL>"), - derived->get_unit())); + unit)); // Skip already prepared views/DT if (!unit || unit->prepared || @@ -652,10 +663,9 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) DBUG_RETURN(FALSE); } - derived->fill_me= FALSE; - if (!(derived->derived_result= new select_union)) + if (!(derived->derived_result= new (thd->mem_root) select_union(thd))) DBUG_RETURN(TRUE); // out of memory lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED; @@ -698,6 +708,7 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) thd->create_tmp_table_for_derived= FALSE; derived->table= derived->derived_result->table; + DBUG_ASSERT(derived->table); if (derived->is_derived() && derived->is_merged_derived()) first_select->mark_as_belong_to_derived(derived); @@ -706,9 +717,9 @@ exit: if (derived->view) { if (thd->is_error() && - (thd->stmt_da->sql_errno() == ER_BAD_FIELD_ERROR || - thd->stmt_da->sql_errno() == ER_FUNC_INEXISTENT_NAME_COLLISION || - thd->stmt_da->sql_errno() == ER_SP_DOES_NOT_EXIST)) + (thd->get_stmt_da()->sql_errno() == ER_BAD_FIELD_ERROR || + thd->get_stmt_da()->sql_errno() == ER_FUNC_INEXISTENT_NAME_COLLISION || + thd->get_stmt_da()->sql_errno() == ER_SP_DOES_NOT_EXIST)) { thd->clear_error(); my_error(ER_VIEW_INVALID, MYF(0), derived->db, @@ -801,7 +812,7 @@ bool mysql_derived_optimize(THD *thd, LEX *lex, TABLE_LIST *derived) if (!derived->is_merged_derived()) { JOIN *join= first_select->join; - unit->set_limit(unit->global_parameters); + unit->set_limit(unit->global_parameters()); unit->optimized= TRUE; if ((res= join->optimize())) goto err; @@ -925,7 +936,7 @@ bool mysql_derived_fill(THD *thd, LEX *lex, TABLE_LIST *derived) } else { - unit->set_limit(unit->global_parameters); + unit->set_limit(unit->global_parameters()); if (unit->select_limit_cnt == HA_POS_ERROR) first_select->options&= ~OPTION_FOUND_ROWS; @@ -1006,9 +1017,8 @@ bool mysql_derived_reinit(THD *thd, LEX *lex, TABLE_LIST *derived) derived->merged_for_insert= FALSE; unit->unclean(); unit->types.empty(); - /* for derived tables & PS (which can't be reset by Item_subquery) */ + /* for derived tables & PS (which can't be reset by Item_subselect) */ unit->reinit_exec_mechanism(); unit->set_thd(thd); DBUG_RETURN(FALSE); } - diff --git a/sql/sql_derived.h b/sql/sql_derived.h index 29b898fc65a..301ae31b016 100644 --- a/sql/sql_derived.h +++ b/sql/sql_derived.h @@ -22,6 +22,7 @@ struct LEX; bool mysql_handle_derived(LEX *lex, uint phases); bool mysql_handle_single_derived(LEX *lex, TABLE_LIST *derived, uint phases); +bool mysql_derived_reinit(THD *thd, LEX *lex, TABLE_LIST *derived); /** Cleans up the SELECT_LEX_UNIT for the derived table (if any). diff --git a/sql/sql_digest.cc b/sql/sql_digest.cc new file mode 100644 index 00000000000..6605d0af0a9 --- /dev/null +++ b/sql/sql_digest.cc @@ -0,0 +1,693 @@ +/* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2017, 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, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ + +/* + This code needs extra visibility in the lexer structures +*/ + +#include "my_global.h" +#include "my_md5.h" +#include "mysqld_error.h" + +#include "sql_string.h" +#include "sql_class.h" +#include "sql_lex.h" +#include "sp_pcontext.h" +#include "sql_digest.h" +#include "sql_digest_stream.h" + +#include "sql_get_diagnostics.h" + +#ifdef NEVER +#include "my_sys.h" +#include "sql_signal.h" +#endif + +/* Generated code */ +#include "sql_yacc.h" +#define LEX_TOKEN_WITH_DEFINITION +#include "lex_token.h" + +/* Name pollution from sql/sql_lex.h */ +#ifdef LEX_YYSTYPE +#undef LEX_YYSTYPE +#endif + +#define LEX_YYSTYPE YYSTYPE* + +#define SIZE_OF_A_TOKEN 2 + +/** + Read a single token from token array. +*/ +inline uint read_token(const sql_digest_storage *digest_storage, + uint index, uint *tok) +{ + uint safe_byte_count= digest_storage->m_byte_count; + + if (index + SIZE_OF_A_TOKEN <= safe_byte_count && + safe_byte_count <= digest_storage->m_token_array_length) + { + const unsigned char *src= & digest_storage->m_token_array[index]; + *tok= src[0] | (src[1] << 8); + return index + SIZE_OF_A_TOKEN; + } + + /* The input byte stream is exhausted. */ + *tok= 0; + return MAX_DIGEST_STORAGE_SIZE + 1; +} + +/** + Store a single token in token array. +*/ +inline void store_token(sql_digest_storage* digest_storage, uint token) +{ + DBUG_ASSERT(digest_storage->m_byte_count <= digest_storage->m_token_array_length); + + if (digest_storage->m_byte_count + SIZE_OF_A_TOKEN <= digest_storage->m_token_array_length) + { + unsigned char* dest= & digest_storage->m_token_array[digest_storage->m_byte_count]; + dest[0]= token & 0xff; + dest[1]= (token >> 8) & 0xff; + digest_storage->m_byte_count+= SIZE_OF_A_TOKEN; + } + else + { + digest_storage->m_full= true; + } +} + +/** + Read an identifier from token array. +*/ +inline uint read_identifier(const sql_digest_storage* digest_storage, + uint index, char ** id_string, int *id_length) +{ + uint new_index; + uint safe_byte_count= digest_storage->m_byte_count; + + DBUG_ASSERT(index <= safe_byte_count); + DBUG_ASSERT(safe_byte_count <= digest_storage->m_token_array_length); + + /* + token + length + string are written in an atomic way, + so we do always expect a length + string here + */ + + uint bytes_needed= SIZE_OF_A_TOKEN; + /* If we can read token and identifier length */ + if ((index + bytes_needed) <= safe_byte_count) + { + const unsigned char *src= & digest_storage->m_token_array[index]; + /* Read the length of identifier */ + uint length= src[0] | (src[1] << 8); + bytes_needed+= length; + /* If we can read entire identifier from token array */ + if ((index + bytes_needed) <= safe_byte_count) + { + *id_string= (char *) (src + 2); + *id_length= length; + + new_index= index + bytes_needed; + DBUG_ASSERT(new_index <= safe_byte_count); + return new_index; + } + } + + /* The input byte stream is exhausted. */ + return MAX_DIGEST_STORAGE_SIZE + 1; +} + +/** + Store an identifier in token array. +*/ +inline void store_token_identifier(sql_digest_storage* digest_storage, + uint token, + size_t id_length, const char *id_name) +{ + DBUG_ASSERT(digest_storage->m_byte_count <= digest_storage->m_token_array_length); + + size_t bytes_needed= 2 * SIZE_OF_A_TOKEN + id_length; + if (digest_storage->m_byte_count + bytes_needed <= (unsigned int)digest_storage->m_token_array_length) + { + unsigned char* dest= & digest_storage->m_token_array[digest_storage->m_byte_count]; + /* Write the token */ + dest[0]= token & 0xff; + dest[1]= (token >> 8) & 0xff; + /* Write the string length */ + dest[2]= id_length & 0xff; + dest[3]= (id_length >> 8) & 0xff; + /* Write the string data */ + if (id_length > 0) + memcpy((char *)(dest + 4), id_name, id_length); + digest_storage->m_byte_count+= bytes_needed; + } + else + { + digest_storage->m_full= true; + } +} + +void compute_digest_md5(const sql_digest_storage *digest_storage, unsigned char *md5) +{ + compute_md5_hash(md5, + (const char *) digest_storage->m_token_array, + digest_storage->m_byte_count); +} + +/* + Iterate token array and updates digest_text. +*/ +void compute_digest_text(const sql_digest_storage* digest_storage, + String *digest_text) +{ + DBUG_ASSERT(digest_storage != NULL); + uint byte_count= digest_storage->m_byte_count; + String *digest_output= digest_text; + uint tok= 0; + uint current_byte= 0; + lex_token_string *tok_data; + + /* Reset existing data */ + digest_output->length(0); + + if (byte_count > digest_storage->m_token_array_length) + { + digest_output->append("\0", 1); + return; + } + + /* Convert text to utf8 */ + const CHARSET_INFO *from_cs= get_charset(digest_storage->m_charset_number, MYF(0)); + const CHARSET_INFO *to_cs= &my_charset_utf8_bin; + + if (from_cs == NULL) + { + /* + Can happen, as we do dirty reads on digest_storage, + which can be written to in another thread. + */ + digest_output->append("\0", 1); + return; + } + + char id_buffer[NAME_LEN + 1]= {'\0'}; + char *id_string; + size_t id_length; + bool convert_text= !my_charset_same(from_cs, to_cs); + + while (current_byte < byte_count) + { + current_byte= read_token(digest_storage, current_byte, &tok); + + if (tok <= 0 || tok >= array_elements(lex_token_array) + || current_byte > max_digest_length) + return; + + tok_data= &lex_token_array[tok]; + + switch (tok) + { + /* All identifiers are printed with their name. */ + case IDENT: + case IDENT_QUOTED: + case TOK_IDENT: + { + char *id_ptr= NULL; + int id_len= 0; + uint err_cs= 0; + + /* Get the next identifier from the storage buffer. */ + current_byte= read_identifier(digest_storage, current_byte, + &id_ptr, &id_len); + if (current_byte > max_digest_length) + return; + + if (convert_text) + { + /* Verify that the converted text will fit. */ + if (to_cs->mbmaxlen*id_len > NAME_LEN) + { + digest_output->append("...", 3); + break; + } + /* Convert identifier string into the storage character set. */ + id_length= my_convert(id_buffer, NAME_LEN, to_cs, + id_ptr, id_len, from_cs, &err_cs); + id_string= id_buffer; + } + else + { + id_string= id_ptr; + id_length= id_len; + } + + if (id_length == 0 || err_cs != 0) + { + break; + } + /* Copy the converted identifier into the digest string. */ + digest_output->append("`", 1); + if (id_length > 0) + digest_output->append(id_string, id_length); + digest_output->append("` ", 2); + } + break; + + /* Everything else is printed as is. */ + default: + /* + Make sure not to overflow digest_text buffer. + +1 is to make sure extra space for ' '. + */ + int tok_length= tok_data->m_token_length; + + digest_output->append(tok_data->m_token_string, tok_length); + if (tok_data->m_append_space) + digest_output->append(" ", 1); + break; + } + } +} + +static inline uint peek_token(const sql_digest_storage *digest, uint index) +{ + uint token; + DBUG_ASSERT(index + SIZE_OF_A_TOKEN <= digest->m_byte_count); + DBUG_ASSERT(digest->m_byte_count <= digest->m_token_array_length); + + token= ((digest->m_token_array[index + 1])<<8) | digest->m_token_array[index]; + return token; +} + +/** + Function to read last two tokens from token array. If an identifier + is found, do not look for token before that. +*/ +static inline void peek_last_two_tokens(const sql_digest_storage* digest_storage, + uint last_id_index, uint *t1, uint *t2) +{ + uint byte_count= digest_storage->m_byte_count; + uint peek_index= byte_count; + + if (last_id_index + SIZE_OF_A_TOKEN <= peek_index) + { + /* Take last token. */ + peek_index-= SIZE_OF_A_TOKEN; + *t1= peek_token(digest_storage, peek_index); + + if (last_id_index + SIZE_OF_A_TOKEN <= peek_index) + { + /* Take 2nd token from last. */ + peek_index-= SIZE_OF_A_TOKEN; + *t2= peek_token(digest_storage, peek_index); + } + else + { + *t2= TOK_UNUSED; + } + } + else + { + *t1= TOK_UNUSED; + *t2= TOK_UNUSED; + } +} + +/** + Function to read last three tokens from token array. If an identifier + is found, do not look for token before that. +*/ +static inline void peek_last_three_tokens(const sql_digest_storage* digest_storage, + uint last_id_index, uint *t1, uint *t2, uint *t3) +{ + uint byte_count= digest_storage->m_byte_count; + uint peek_index= byte_count; + + if (last_id_index + SIZE_OF_A_TOKEN <= peek_index) + { + /* Take last token. */ + peek_index-= SIZE_OF_A_TOKEN; + *t1= peek_token(digest_storage, peek_index); + + if (last_id_index + SIZE_OF_A_TOKEN <= peek_index) + { + /* Take 2nd token from last. */ + peek_index-= SIZE_OF_A_TOKEN; + *t2= peek_token(digest_storage, peek_index); + + if (last_id_index + SIZE_OF_A_TOKEN <= peek_index) + { + /* Take 3rd token from last. */ + peek_index-= SIZE_OF_A_TOKEN; + *t3= peek_token(digest_storage, peek_index); + } + else + { + *t3= TOK_UNUSED; + } + } + else + { + *t2= TOK_UNUSED; + *t3= TOK_UNUSED; + } + } + else + { + *t1= TOK_UNUSED; + *t2= TOK_UNUSED; + *t3= TOK_UNUSED; + } +} + +sql_digest_state* digest_add_token(sql_digest_state *state, + uint token, + LEX_YYSTYPE yylval) +{ + sql_digest_storage *digest_storage= NULL; + + digest_storage= &state->m_digest_storage; + + /* + Stop collecting further tokens if digest storage is full or + if END token is received. + */ + if (digest_storage->m_full || token == END_OF_INPUT) + return NULL; + + /* + Take last_token 2 tokens collected till now. These tokens will be used + in reduce for normalisation. Make sure not to consider ID tokens in reduce. + */ + uint last_token; + uint last_token2; + + switch (token) + { + case NUM: + case LONG_NUM: + case ULONGLONG_NUM: + case DECIMAL_NUM: + case FLOAT_NUM: + case BIN_NUM: + case HEX_NUM: + { + bool found_unary; + do + { + found_unary= false; + peek_last_two_tokens(digest_storage, state->m_last_id_index, + &last_token, &last_token2); + + if ((last_token == '-') || (last_token == '+')) + { + /* + We need to differentiate: + - a <unary minus> operator + - a <unary plus> operator + from + - a <binary minus> operator + - a <binary plus> operator + to only reduce "a = -1" to "a = ?", and not change "b - 1" to "b ?" + + Binary operators are found inside an expression, + while unary operators are found at the beginning of an expression, or after operators. + + To achieve this, every token that is followed by an <expr> expression + in the SQL grammar is flagged. + See sql/sql_yacc.yy + See sql/gen_lex_token.cc + + For example, + "(-1)" is parsed as "(", "-", NUM, ")", and lex_token_array["("].m_start_expr is true, + so reduction of the "-" NUM is done, the result is "(?)". + "(a-1)" is parsed as "(", ID, "-", NUM, ")", and lex_token_array[ID].m_start_expr is false, + so the operator is binary, no reduction is done, and the result is "(a-?)". + */ + if (lex_token_array[last_token2].m_start_expr) + { + /* + REDUCE: + TOK_GENERIC_VALUE := (UNARY_PLUS | UNARY_MINUS) (NUM | LOG_NUM | ... | FLOAT_NUM) + + REDUCE: + TOK_GENERIC_VALUE := (UNARY_PLUS | UNARY_MINUS) TOK_GENERIC_VALUE + */ + token= TOK_GENERIC_VALUE; + digest_storage->m_byte_count-= SIZE_OF_A_TOKEN; + found_unary= true; + } + } + } while (found_unary); + } + /* for case NULL_SYM below */ + /* fall through */ + case LEX_HOSTNAME: + case TEXT_STRING: + case NCHAR_STRING: + case PARAM_MARKER: + { + /* + REDUCE: + TOK_GENERIC_VALUE := BIN_NUM | DECIMAL_NUM | ... | ULONGLONG_NUM + */ + token= TOK_GENERIC_VALUE; + + peek_last_two_tokens(digest_storage, state->m_last_id_index, + &last_token, &last_token2); + + if ((last_token2 == TOK_GENERIC_VALUE || + last_token2 == TOK_GENERIC_VALUE_LIST) && + (last_token == ',')) + { + /* + REDUCE: + TOK_GENERIC_VALUE_LIST := + TOK_GENERIC_VALUE ',' TOK_GENERIC_VALUE + + REDUCE: + TOK_GENERIC_VALUE_LIST := + TOK_GENERIC_VALUE_LIST ',' TOK_GENERIC_VALUE + */ + digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN; + token= TOK_GENERIC_VALUE_LIST; + } + /* + Add this token or the resulting reduce to digest storage. + */ + store_token(digest_storage, token); + break; + } + case ')': + { + peek_last_two_tokens(digest_storage, state->m_last_id_index, + &last_token, &last_token2); + + if (last_token == TOK_GENERIC_VALUE && + last_token2 == '(') + { + /* + REDUCE: + TOK_ROW_SINGLE_VALUE := + '(' TOK_GENERIC_VALUE ')' + */ + digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN; + token= TOK_ROW_SINGLE_VALUE; + + /* Read last two tokens again */ + peek_last_two_tokens(digest_storage, state->m_last_id_index, + &last_token, &last_token2); + + if ((last_token2 == TOK_ROW_SINGLE_VALUE || + last_token2 == TOK_ROW_SINGLE_VALUE_LIST) && + (last_token == ',')) + { + /* + REDUCE: + TOK_ROW_SINGLE_VALUE_LIST := + TOK_ROW_SINGLE_VALUE ',' TOK_ROW_SINGLE_VALUE + + REDUCE: + TOK_ROW_SINGLE_VALUE_LIST := + TOK_ROW_SINGLE_VALUE_LIST ',' TOK_ROW_SINGLE_VALUE + */ + digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN; + token= TOK_ROW_SINGLE_VALUE_LIST; + } + } + else if (last_token == TOK_GENERIC_VALUE_LIST && + last_token2 == '(') + { + /* + REDUCE: + TOK_ROW_MULTIPLE_VALUE := + '(' TOK_GENERIC_VALUE_LIST ')' + */ + digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN; + token= TOK_ROW_MULTIPLE_VALUE; + + /* Read last two tokens again */ + peek_last_two_tokens(digest_storage, state->m_last_id_index, + &last_token, &last_token2); + + if ((last_token2 == TOK_ROW_MULTIPLE_VALUE || + last_token2 == TOK_ROW_MULTIPLE_VALUE_LIST) && + (last_token == ',')) + { + /* + REDUCE: + TOK_ROW_MULTIPLE_VALUE_LIST := + TOK_ROW_MULTIPLE_VALUE ',' TOK_ROW_MULTIPLE_VALUE + + REDUCE: + TOK_ROW_MULTIPLE_VALUE_LIST := + TOK_ROW_MULTIPLE_VALUE_LIST ',' TOK_ROW_MULTIPLE_VALUE + */ + digest_storage->m_byte_count-= 2*SIZE_OF_A_TOKEN; + token= TOK_ROW_MULTIPLE_VALUE_LIST; + } + } + /* + Add this token or the resulting reduce to digest storage. + */ + store_token(digest_storage, token); + break; + } + case IDENT: + case IDENT_QUOTED: + { + YYSTYPE *lex_token= yylval; + char *yytext= lex_token->lex_str.str; + size_t yylen= lex_token->lex_str.length; + + /* + REDUCE: + TOK_IDENT := IDENT | IDENT_QUOTED + The parser gives IDENT or IDENT_TOKEN for the same text, + depending on the character set used. + We unify both to always print the same digest text, + and always have the same digest hash. + */ + token= TOK_IDENT; + /* Add this token and identifier string to digest storage. */ + store_token_identifier(digest_storage, token, yylen, yytext); + + /* Update the index of last identifier found. */ + state->m_last_id_index= digest_storage->m_byte_count; + break; + } + default: + { + /* Add this token to digest storage. */ + store_token(digest_storage, token); + break; + } + } + + return state; +} + +sql_digest_state* digest_reduce_token(sql_digest_state *state, + uint token_left, uint token_right) +{ + sql_digest_storage *digest_storage= NULL; + + digest_storage= &state->m_digest_storage; + + /* + Stop collecting further tokens if digest storage is full. + */ + if (digest_storage->m_full) + return NULL; + + uint last_token; + uint last_token2; + uint last_token3; + uint token_to_push= TOK_UNUSED; + + peek_last_two_tokens(digest_storage, state->m_last_id_index, + &last_token, &last_token2); + + /* + There is only one caller of digest_reduce_token(), + see sql/sql_yacc.yy, rule literal := NULL_SYM. + REDUCE: + token_left := token_right + Used for: + TOK_GENERIC_VALUE := NULL_SYM + */ + + if (last_token == token_right) + { + /* + Current stream is like: + TOKEN_X TOKEN_RIGHT . + REDUCE to + TOKEN_X TOKEN_LEFT . + */ + digest_storage->m_byte_count-= SIZE_OF_A_TOKEN; + store_token(digest_storage, token_left); + } + else + { + /* + Current stream is like: + TOKEN_X TOKEN_RIGHT TOKEN_Y . + Pop TOKEN_Y + TOKEN_X TOKEN_RIGHT . TOKEN_Y + REDUCE to + TOKEN_X TOKEN_LEFT . TOKEN_Y + */ + DBUG_ASSERT(last_token2 == token_right); + digest_storage->m_byte_count-= 2 * SIZE_OF_A_TOKEN; + store_token(digest_storage, token_left); + token_to_push= last_token; + } + + peek_last_three_tokens(digest_storage, state->m_last_id_index, + &last_token, &last_token2, &last_token3); + + if ((last_token3 == TOK_GENERIC_VALUE || + last_token3 == TOK_GENERIC_VALUE_LIST) && + (last_token2 == ',') && + (last_token == TOK_GENERIC_VALUE)) + { + /* + REDUCE: + TOK_GENERIC_VALUE_LIST := + TOK_GENERIC_VALUE ',' TOK_GENERIC_VALUE + + REDUCE: + TOK_GENERIC_VALUE_LIST := + TOK_GENERIC_VALUE_LIST ',' TOK_GENERIC_VALUE + */ + digest_storage->m_byte_count-= 3*SIZE_OF_A_TOKEN; + store_token(digest_storage, TOK_GENERIC_VALUE_LIST); + } + + if (token_to_push != TOK_UNUSED) + { + /* + Push TOKEN_Y + */ + store_token(digest_storage, token_to_push); + } + + return state; +} + diff --git a/sql/sql_digest.h b/sql/sql_digest.h new file mode 100644 index 00000000000..eaf74b9542e --- /dev/null +++ b/sql/sql_digest.h @@ -0,0 +1,129 @@ +/* Copyright (c) 2008, 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, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ + +#ifndef SQL_DIGEST_H +#define SQL_DIGEST_H + +#include <string.h> +class String; +#include "my_md5.h" + +#define MAX_DIGEST_STORAGE_SIZE (1024*1024) + +/** + Structure to store token count/array for a statement + on which digest is to be calculated. +*/ +struct sql_digest_storage +{ + bool m_full; + uint m_byte_count; + unsigned char m_md5[MD5_HASH_SIZE]; + /** Character set number. */ + uint m_charset_number; + /** + Token array. + Token array is an array of bytes to store tokens received during parsing. + Following is the way token array is formed. + ... <non-id-token> <non-id-token> <id-token> <id_len> <id_text> ... + For Example: + SELECT * FROM T1; + <SELECT_TOKEN> <*> <FROM_TOKEN> <ID_TOKEN> <2> <T1> + + @note Only the first @c m_byte_count bytes are initialized, + out of @c m_token_array_length. + */ + unsigned char *m_token_array; + /* Length of the token array to be considered for DIGEST_TEXT calculation. */ + uint m_token_array_length; + + sql_digest_storage() + { + reset(NULL, 0); + } + + inline void reset(unsigned char *token_array, uint length) + { + m_token_array= token_array; + m_token_array_length= length; + reset(); + } + + inline void reset() + { + m_full= false; + m_byte_count= 0; + m_charset_number= 0; + memset(m_md5, 0, MD5_HASH_SIZE); + } + + inline bool is_empty() + { + return (m_byte_count == 0); + } + + inline void copy(const sql_digest_storage *from) + { + /* + Keep in mind this is a dirty copy of something that may change, + as the thread producing the digest is executing concurrently, + without any lock enforced. + */ + uint byte_count_copy= m_token_array_length < from->m_byte_count ? + m_token_array_length : from->m_byte_count; + + if (byte_count_copy > 0) + { + m_full= from->m_full; + m_byte_count= byte_count_copy; + m_charset_number= from->m_charset_number; + memcpy(m_token_array, from->m_token_array, m_byte_count); + memcpy(m_md5, from->m_md5, MD5_HASH_SIZE); + } + else + { + m_full= false; + m_byte_count= 0; + m_charset_number= 0; + } + } +}; +typedef struct sql_digest_storage sql_digest_storage; + +/** + Compute a digest hash. + @param digest_storage The digest + @param [out] md5 The computed digest hash. This parameter is a buffer of size @c MD5_HASH_SIZE. +*/ +void compute_digest_md5(const sql_digest_storage *digest_storage, unsigned char *md5); + +/** + Compute a digest text. + A 'digest text' is a textual representation of a query, + where: + - comments are removed, + - non significant spaces are removed, + - literal values are replaced with a special '?' marker, + - lists of values are collapsed using a shorter notation + @param digest_storage The digest + @param [out] digest_text + @param digest_text_length Size of @c digest_text. + @param [out] truncated true if the text representation was truncated +*/ +void compute_digest_text(const sql_digest_storage *digest_storage, + String *digest_text); + +#endif + diff --git a/sql/sql_digest_stream.h b/sql/sql_digest_stream.h new file mode 100644 index 00000000000..55f7e2293c6 --- /dev/null +++ b/sql/sql_digest_stream.h @@ -0,0 +1,51 @@ +/* Copyright (c) 2008, 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, + 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ + +#ifndef SQL_DIGEST_STREAM_H +#define SQL_DIGEST_STREAM_H + +#include "sql_digest.h" + +/** + State data storage for @c digest_start, @c digest_add_token. + This structure extends the @c sql_digest_storage structure + with temporary state used only during parsing. +*/ +struct sql_digest_state +{ + /** + Index, in the digest token array, of the last identifier seen. + Reduce rules used in the digest computation can not + apply to tokens seen before an identifier. + @sa digest_add_token + */ + int m_last_id_index; + sql_digest_storage m_digest_storage; + + inline void reset(unsigned char *token_array, uint length) + { + m_last_id_index= 0; + m_digest_storage.reset(token_array, length); + } + + inline bool is_empty() + { + return m_digest_storage.is_empty(); + } +}; +typedef struct sql_digest_state sql_digest_state; + +#endif + diff --git a/sql/sql_do.cc b/sql/sql_do.cc index 46bdb421b3a..54850494ad0 100644 --- a/sql/sql_do.cc +++ b/sql/sql_do.cc @@ -16,6 +16,7 @@ /* Execute DO statement */ +#include <my_global.h> #include "sql_priv.h" #include "transaction.h" #include "unireg.h" @@ -31,7 +32,7 @@ bool mysql_do(THD *thd, List<Item> &values) if (setup_fields(thd, 0, values, MARK_COLUMNS_NONE, 0, NULL, 0)) DBUG_RETURN(TRUE); while ((value = li++)) - value->val_int(); + (void) value->is_null(); free_underlaid_joins(thd, &thd->lex->select_lex); if (thd->is_error()) diff --git a/sql/sql_error.cc b/sql/sql_error.cc index e12723402a8..b72d642efbc 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -41,18 +41,19 @@ This file contains the implementation of error and warnings related ***********************************************************************/ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_error.h" #include "sp_rcontext.h" /* - Design notes about MYSQL_ERROR::m_message_text. + Design notes about Sql_condition::m_message_text. - The member MYSQL_ERROR::m_message_text contains the text associated with + The member Sql_condition::m_message_text contains the text associated with an error, warning or note (which are all SQL 'conditions') - Producer of MYSQL_ERROR::m_message_text: + Producer of Sql_condition::m_message_text: ---------------------------------------- (#1) the server implementation itself, when invoking functions like @@ -78,16 +79,16 @@ This file contains the implementation of error and warnings related - a RESIGNAL statement, the message text is provided by the user logic, and is expressed in UTF8. - Storage of MYSQL_ERROR::m_message_text: + Storage of Sql_condition::m_message_text: --------------------------------------- - (#4) The class MYSQL_ERROR is used to hold the message text member. + (#4) The class Sql_condition is used to hold the message text member. This class represents a single SQL condition. (#5) The class Warning_info represents a SQL condition area, and contains a collection of SQL conditions in the Warning_info::m_warn_list - Consumer of MYSQL_ERROR::m_message_text: + Consumer of Sql_condition::m_message_text: ---------------------------------------- (#6) The statements SHOW WARNINGS and SHOW ERRORS display the content of @@ -97,9 +98,9 @@ This file contains the implementation of error and warnings related also read the content of: - the top level statement condition area (when executed in a query), - a sub statement (when executed in a stored program) - and return the data stored in a MYSQL_ERROR. + and return the data stored in a Sql_condition. - (#8) The RESIGNAL statement reads the MYSQL_ERROR caught by an exception + (#8) The RESIGNAL statement reads the Sql_condition caught by an exception handler, to raise a new or modified condition (in #3). The big picture @@ -113,7 +114,7 @@ This file contains the implementation of error and warnings related ----------------------------|---------------------------- | | | V | - MYSQL_ERROR(#4) | + Sql_condition(#4) | | | | | V | @@ -151,10 +152,10 @@ This file contains the implementation of error and warnings related As a result, the design choice for (#4) and (#5) is to store data in the 'error_message_charset_info' CHARSET, to minimize impact on the code base. - This is implemented by using 'String MYSQL_ERROR::m_message_text'. + This is implemented by using 'String Sql_condition::m_message_text'. The UTF8 -> error_message_charset_info conversion is implemented in - Signal_common::eval_signal_informations() (for path #B and #C). + Sql_cmd_common_signal::eval_signal_informations() (for path #B and #C). Future work ----------- @@ -164,14 +165,14 @@ This file contains the implementation of error and warnings related - Change (#4 and #5) to store message text in UTF8 natively. In practice, this means changing the type of the message text to - '<UTF8 String 128 class> MYSQL_ERROR::m_message_text', and is a direct + '<UTF8 String 128 class> Sql_condition::m_message_text', and is a direct consequence of WL#751. - Implement (#9) (GET DIAGNOSTICS). See WL#2111 (Stored Procedures: Implement GET DIAGNOSTICS) */ -MYSQL_ERROR::MYSQL_ERROR() +Sql_condition::Sql_condition() : Sql_alloc(), m_class_origin((const char*) NULL, 0, & my_charset_utf8_bin), m_subclass_origin((const char*) NULL, 0, & my_charset_utf8_bin), @@ -185,21 +186,20 @@ MYSQL_ERROR::MYSQL_ERROR() m_cursor_name((const char*) NULL, 0, & my_charset_utf8_bin), m_message_text(), m_sql_errno(0), - m_handled(0), - m_level(MYSQL_ERROR::WARN_LEVEL_ERROR), + m_level(Sql_condition::WARN_LEVEL_ERROR), m_mem_root(NULL) { memset(m_returned_sqlstate, 0, sizeof(m_returned_sqlstate)); } -void MYSQL_ERROR::init(MEM_ROOT *mem_root) +void Sql_condition::init(MEM_ROOT *mem_root) { DBUG_ASSERT(mem_root != NULL); DBUG_ASSERT(m_mem_root == NULL); m_mem_root= mem_root; } -void MYSQL_ERROR::clear() +void Sql_condition::clear() { m_class_origin.length(0); m_subclass_origin.length(0); @@ -213,11 +213,10 @@ void MYSQL_ERROR::clear() m_cursor_name.length(0); m_message_text.length(0); m_sql_errno= 0; - m_handled= 0; - m_level= MYSQL_ERROR::WARN_LEVEL_ERROR; + m_level= Sql_condition::WARN_LEVEL_ERROR; } -MYSQL_ERROR::MYSQL_ERROR(MEM_ROOT *mem_root) +Sql_condition::Sql_condition(MEM_ROOT *mem_root) : Sql_alloc(), m_class_origin((const char*) NULL, 0, & my_charset_utf8_bin), m_subclass_origin((const char*) NULL, 0, & my_charset_utf8_bin), @@ -231,8 +230,7 @@ MYSQL_ERROR::MYSQL_ERROR(MEM_ROOT *mem_root) m_cursor_name((const char*) NULL, 0, & my_charset_utf8_bin), m_message_text(), m_sql_errno(0), - m_handled(0), - m_level(MYSQL_ERROR::WARN_LEVEL_ERROR), + m_level(Sql_condition::WARN_LEVEL_ERROR), m_mem_root(mem_root) { DBUG_ASSERT(mem_root != NULL); @@ -257,7 +255,7 @@ static void copy_string(MEM_ROOT *mem_root, String* dst, const String* src) } void -MYSQL_ERROR::copy_opt_attributes(const MYSQL_ERROR *cond) +Sql_condition::copy_opt_attributes(const Sql_condition *cond) { DBUG_ASSERT(this != cond); copy_string(m_mem_root, & m_class_origin, & cond->m_class_origin); @@ -270,12 +268,11 @@ MYSQL_ERROR::copy_opt_attributes(const MYSQL_ERROR *cond) copy_string(m_mem_root, & m_table_name, & cond->m_table_name); copy_string(m_mem_root, & m_column_name, & cond->m_column_name); copy_string(m_mem_root, & m_cursor_name, & cond->m_cursor_name); - m_handled= cond->m_handled; } void -MYSQL_ERROR::set(uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, const char* msg) +Sql_condition::set(uint sql_errno, const char* sqlstate, + Sql_condition::enum_warning_level level, const char* msg) { DBUG_ASSERT(sql_errno != 0); DBUG_ASSERT(sqlstate != NULL); @@ -290,11 +287,11 @@ MYSQL_ERROR::set(uint sql_errno, const char* sqlstate, } void -MYSQL_ERROR::set_builtin_message_text(const char* str) +Sql_condition::set_builtin_message_text(const char* str) { /* See the comments - "Design notes about MYSQL_ERROR::m_message_text." + "Design notes about Sql_condition::m_message_text." */ const char* copy; @@ -304,24 +301,42 @@ MYSQL_ERROR::set_builtin_message_text(const char* str) } const char* -MYSQL_ERROR::get_message_text() const +Sql_condition::get_message_text() const { return m_message_text.ptr(); } int -MYSQL_ERROR::get_message_octet_length() const +Sql_condition::get_message_octet_length() const { return m_message_text.length(); } void -MYSQL_ERROR::set_sqlstate(const char* sqlstate) +Sql_condition::set_sqlstate(const char* sqlstate) { memcpy(m_returned_sqlstate, sqlstate, SQLSTATE_LENGTH); m_returned_sqlstate[SQLSTATE_LENGTH]= '\0'; } +Diagnostics_area::Diagnostics_area(bool initialize) + : m_main_wi(0, false, initialize) +{ + push_warning_info(&m_main_wi); + + reset_diagnostics_area(); +} + +Diagnostics_area::Diagnostics_area(ulonglong warning_info_id, + bool allow_unlimited_warnings, + bool initialize) + : m_main_wi(warning_info_id, allow_unlimited_warnings, initialize) +{ + push_warning_info(&m_main_wi); + + reset_diagnostics_area(); +} + /** Clear this diagnostics area. @@ -333,7 +348,7 @@ Diagnostics_area::reset_diagnostics_area() { DBUG_ENTER("reset_diagnostics_area"); #ifdef DBUG_OFF - can_overwrite_status= FALSE; + m_can_overwrite_status= FALSE; /** Don't take chances in production */ m_message[0]= '\0'; m_sql_errno= 0; @@ -341,7 +356,8 @@ Diagnostics_area::reset_diagnostics_area() m_last_insert_id= 0; m_statement_warn_count= 0; #endif - is_sent= FALSE; + get_warning_info()->clear_error_condition(); + set_is_sent(false); /** Tiny reset in debug mode to see garbage right away */ m_status= DA_EMPTY; DBUG_VOID_RETURN; @@ -354,9 +370,9 @@ Diagnostics_area::reset_diagnostics_area() */ void -Diagnostics_area::set_ok_status(THD *thd, ulonglong affected_rows_arg, - ulonglong last_insert_id_arg, - const char *message_arg) +Diagnostics_area::set_ok_status(ulonglong affected_rows, + ulonglong last_insert_id, + const char *message) { DBUG_ENTER("set_ok_status"); DBUG_ASSERT(! is_set()); @@ -367,11 +383,11 @@ Diagnostics_area::set_ok_status(THD *thd, ulonglong affected_rows_arg, if (is_error() || is_disabled()) return; - m_statement_warn_count= thd->warning_info->statement_warn_count(); - m_affected_rows= affected_rows_arg; - m_last_insert_id= last_insert_id_arg; - if (message_arg) - strmake_buf(m_message, message_arg); + m_statement_warn_count= current_statement_warn_count(); + m_affected_rows= affected_rows; + m_last_insert_id= last_insert_id; + if (message) + strmake_buf(m_message, message); else m_message[0]= '\0'; m_status= DA_OK; @@ -402,28 +418,67 @@ Diagnostics_area::set_eof_status(THD *thd) anyway. */ m_statement_warn_count= (thd->spcont ? - 0 : thd->warning_info->statement_warn_count()); + 0 : + current_statement_warn_count()); m_status= DA_EOF; DBUG_VOID_RETURN; } /** - Set ERROR status. + Set ERROR status in the Diagnostics Area. This function should be used to + report fatal errors (such as out-of-memory errors) when no further + processing is possible. + + @param sql_errno SQL-condition error number */ void -Diagnostics_area::set_error_status(THD *thd, uint sql_errno_arg, - const char *message_arg, - const char *sqlstate) +Diagnostics_area::set_error_status(uint sql_errno) +{ + set_error_status(sql_errno, + ER(sql_errno), + mysql_errno_to_sqlstate(sql_errno), + NULL); +} + + +/** + Set ERROR status in the Diagnostics Area. + + @note error_condition may be NULL. It happens if a) OOM error is being + reported; or b) when Warning_info is full. + + @param sql_errno SQL-condition error number + @param message SQL-condition message + @param sqlstate SQL-condition state + @param error_condition SQL-condition object representing the error state + + @note Note, that error_condition may be NULL. It happens if a) OOM error is + being reported; or b) when Warning_info is full. +*/ + +void +Diagnostics_area::set_error_status(uint sql_errno, + const char *message, + const char *sqlstate, + const Sql_condition *error_condition) { DBUG_ENTER("set_error_status"); + DBUG_PRINT("enter", ("error: %d", sql_errno)); /* Only allowed to report error if has not yet reported a success The only exception is when we flush the message to the client, an error can happen during the flush. */ - DBUG_ASSERT(! is_set() || can_overwrite_status); + DBUG_ASSERT(! is_set() || m_can_overwrite_status); + + // message must be set properly by the caller. + DBUG_ASSERT(message); + + // sqlstate must be set properly by the caller. + DBUG_ASSERT(sqlstate); + #ifdef DBUG_OFF /* In production, refuse to overwrite a custom response with an @@ -433,19 +488,17 @@ Diagnostics_area::set_error_status(THD *thd, uint sql_errno_arg, return; #endif - if (sqlstate == NULL) - sqlstate= mysql_errno_to_sqlstate(sql_errno_arg); - - m_sql_errno= sql_errno_arg; + m_sql_errno= sql_errno; memcpy(m_sqlstate, sqlstate, SQLSTATE_LENGTH); m_sqlstate[SQLSTATE_LENGTH]= '\0'; - strmake_buf(m_message, message_arg); + strmake_buf(m_message, message); + + get_warning_info()->set_error_condition(error_condition); m_status= DA_ERROR; DBUG_VOID_RETURN; } - /** Mark the diagnostics area as 'DISABLED'. @@ -457,116 +510,207 @@ Diagnostics_area::set_error_status(THD *thd, uint sql_errno_arg, void Diagnostics_area::disable_status() { + DBUG_ENTER("disable_status"); DBUG_ASSERT(! is_set()); m_status= DA_DISABLED; + DBUG_VOID_RETURN; } -Warning_info::Warning_info(ulonglong warn_id_arg, bool allow_unlimited_warnings) - :m_statement_warn_count(0), +Warning_info::Warning_info(ulonglong warn_id_arg, + bool allow_unlimited_warnings, bool initialize) + :m_current_statement_warn_count(0), m_current_row_for_warning(1), m_warn_id(warn_id_arg), + m_error_condition(NULL), m_allow_unlimited_warnings(allow_unlimited_warnings), + initialized(0), m_read_only(FALSE) { - /* Initialize sub structures */ - init_sql_alloc(&m_warn_root, WARN_ALLOC_BLOCK_SIZE, WARN_ALLOC_PREALLOC_SIZE); m_warn_list.empty(); - bzero((char*) m_warn_count, sizeof(m_warn_count)); + memset(m_warn_count, 0, sizeof(m_warn_count)); + if (initialize) + init(); } +void Warning_info::init() +{ + /* Initialize sub structures */ + DBUG_ASSERT(initialized == 0); + init_sql_alloc(&m_warn_root, WARN_ALLOC_BLOCK_SIZE, + WARN_ALLOC_PREALLOC_SIZE, MYF(MY_THREAD_SPECIFIC)); + initialized= 1; +} + +void Warning_info::free_memory() +{ + if (initialized) + free_root(&m_warn_root,MYF(0)); +} Warning_info::~Warning_info() { - free_root(&m_warn_root,MYF(0)); + free_memory(); } -/** - Reset the warning information of this connection. -*/ +bool Warning_info::has_sql_condition(const char *message_str, + ulong message_length) const +{ + Diagnostics_area::Sql_condition_iterator it(m_warn_list); + const Sql_condition *err; + + while ((err= it++)) + { + if (strncmp(message_str, err->get_message_text(), message_length) == 0) + return true; + } + + return false; +} + -void Warning_info::clear_warning_info(ulonglong warn_id_arg) +void Warning_info::clear(ulonglong new_id) { - m_warn_id= warn_id_arg; - free_root(&m_warn_root, MYF(0)); - bzero((char*) m_warn_count, sizeof(m_warn_count)); + id(new_id); m_warn_list.empty(); - m_statement_warn_count= 0; + m_marked_sql_conditions.empty(); + free_memory(); + memset(m_warn_count, 0, sizeof(m_warn_count)); + m_current_statement_warn_count= 0; m_current_row_for_warning= 1; /* Start counting from the first row */ + clear_error_condition(); } -/** - Append warnings only if the original contents of the routine - warning info was replaced. -*/ -void Warning_info::merge_with_routine_info(THD *thd, Warning_info *source) +void Warning_info::append_warning_info(THD *thd, const Warning_info *source) { - /* - If a routine body is empty or if a routine did not - generate any warnings (thus m_warn_id didn't change), - do not duplicate our own contents by appending the - contents of the called routine. We know that the called - routine did not change its warning info. - - On the other hand, if the routine body is not empty and - some statement in the routine generates a warning or - uses tables, m_warn_id is guaranteed to have changed. - In this case we know that the routine warning info - contains only new warnings, and thus we perform a copy. - */ - if (m_warn_id != source->m_warn_id) + const Sql_condition *err; + Diagnostics_area::Sql_condition_iterator it(source->m_warn_list); + const Sql_condition *src_error_condition = source->get_error_condition(); + + while ((err= it++)) { - /* - If the invocation of the routine was a standalone statement, - rather than a sub-statement, in other words, if it's a CALL - of a procedure, rather than invocation of a function or a - trigger, we need to clear the current contents of the caller's - warning info. - - This is per MySQL rules: if a statement generates a warning, - warnings from the previous statement are flushed. Normally - it's done in push_warning(). However, here we don't use - push_warning() to avoid invocation of condition handlers or - escalation of warnings to errors. - */ - opt_clear_warning_info(thd->query_id); - append_warning_info(thd, source); + // Do not use ::push_warning() to avoid invocation of THD-internal-handlers. + Sql_condition *new_error= Warning_info::push_warning(thd, err); + + if (src_error_condition && src_error_condition == err) + set_error_condition(new_error); + + if (source->is_marked_for_removal(err)) + mark_condition_for_removal(new_error); } } + /** - Add a warning to the list of warnings. Increment the respective - counters. + Copy Sql_conditions that are not WARN_LEVEL_ERROR from the source + Warning_info to the current Warning_info. + + @param thd Thread context. + @param sp_wi Stored-program Warning_info + @param thd Thread context. + @param src_wi Warning_info to copy from. */ -MYSQL_ERROR *Warning_info::push_warning(THD *thd, - uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char *msg) +void Diagnostics_area::copy_non_errors_from_wi(THD *thd, + const Warning_info *src_wi) +{ + Sql_condition_iterator it(src_wi->m_warn_list); + const Sql_condition *cond; + Warning_info *wi= get_warning_info(); + + while ((cond= it++)) + { + if (cond->get_level() == Sql_condition::WARN_LEVEL_ERROR) + continue; + + Sql_condition *new_condition= wi->push_warning(thd, cond); + + if (src_wi->is_marked_for_removal(cond)) + wi->mark_condition_for_removal(new_condition); + } +} + + +void Warning_info::mark_sql_conditions_for_removal() { - MYSQL_ERROR *cond= NULL; + Sql_condition_list::Iterator it(m_warn_list); + Sql_condition *cond; + + while ((cond= it++)) + mark_condition_for_removal(cond); +} + + +void Warning_info::remove_marked_sql_conditions() +{ + List_iterator_fast<Sql_condition> it(m_marked_sql_conditions); + Sql_condition *cond; + + while ((cond= it++)) + { + m_warn_list.remove(cond); + m_warn_count[cond->get_level()]--; + m_current_statement_warn_count--; + if (cond == m_error_condition) + m_error_condition= NULL; + } + + m_marked_sql_conditions.empty(); +} + + +bool Warning_info::is_marked_for_removal(const Sql_condition *cond) const +{ + List_iterator_fast<Sql_condition> it( + const_cast<List<Sql_condition>&> (m_marked_sql_conditions)); + Sql_condition *c; + + while ((c= it++)) + { + if (c == cond) + return true; + } + + return false; +} + + +void Warning_info::reserve_space(THD *thd, uint count) +{ + while (m_warn_list.elements() && + (m_warn_list.elements() + count) > thd->variables.max_error_count) + m_warn_list.remove(m_warn_list.front()); +} + +Sql_condition *Warning_info::push_warning(THD *thd, + uint sql_errno, const char* sqlstate, + Sql_condition::enum_warning_level level, + const char *msg) +{ + Sql_condition *cond= NULL; if (! m_read_only) { if (m_allow_unlimited_warnings || - m_warn_list.elements < thd->variables.max_error_count) + m_warn_list.elements() < thd->variables.max_error_count) { - cond= new (& m_warn_root) MYSQL_ERROR(& m_warn_root); + cond= new (& m_warn_root) Sql_condition(& m_warn_root); if (cond) { cond->set(sql_errno, sqlstate, level, msg); - m_warn_list.push_back(cond, &m_warn_root); + m_warn_list.push_back(cond); } } m_warn_count[(uint) level]++; } - m_statement_warn_count++; + m_current_statement_warn_count++; return cond; } -MYSQL_ERROR *Warning_info::push_warning(THD *thd, const MYSQL_ERROR *sql_condition) + +Sql_condition *Warning_info::push_warning(THD *thd, const Sql_condition *sql_condition) { - MYSQL_ERROR *new_condition= push_warning(thd, + Sql_condition *new_condition= push_warning(thd, sql_condition->get_sql_errno(), sql_condition->get_sqlstate(), sql_condition->get_level(), @@ -589,7 +733,7 @@ MYSQL_ERROR *Warning_info::push_warning(THD *thd, const MYSQL_ERROR *sql_conditi msg Clear error message */ -void push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, +void push_warning(THD *thd, Sql_condition::enum_warning_level level, uint code, const char *msg) { DBUG_ENTER("push_warning"); @@ -600,15 +744,15 @@ void push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, WARN_LEVEL_ERROR *is* a bug. Either use my_printf_error(), my_error(), or WARN_LEVEL_WARN. */ - DBUG_ASSERT(level != MYSQL_ERROR::WARN_LEVEL_ERROR); + DBUG_ASSERT(level != Sql_condition::WARN_LEVEL_ERROR); - if (level == MYSQL_ERROR::WARN_LEVEL_ERROR) - level= MYSQL_ERROR::WARN_LEVEL_WARN; + if (level == Sql_condition::WARN_LEVEL_ERROR) + level= Sql_condition::WARN_LEVEL_WARN; (void) thd->raise_condition(code, NULL, level, msg); /* Make sure we also count warnings pushed after calling set_ok_status(). */ - thd->stmt_da->increment_warning(); + thd->get_stmt_da()->increment_warning(); DBUG_VOID_RETURN; } @@ -625,7 +769,7 @@ void push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, msg Clear error message */ -void push_warning_printf(THD *thd, MYSQL_ERROR::enum_warning_level level, +void push_warning_printf(THD *thd, Sql_condition::enum_warning_level level, uint code, const char *format, ...) { va_list args; @@ -672,27 +816,35 @@ const LEX_STRING warning_level_names[]= bool mysqld_show_warnings(THD *thd, ulong levels_to_show) { List<Item> field_list; - DBUG_ENTER("mysqld_show_warnings"); - - DBUG_ASSERT(thd->warning_info->is_read_only()); - - field_list.push_back(new Item_empty_string("Level", 7)); - field_list.push_back(new Item_return_int("Code",4, MYSQL_TYPE_LONG)); - field_list.push_back(new Item_empty_string("Message",MYSQL_ERRMSG_SIZE)); - - if (thd->protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) - DBUG_RETURN(TRUE); - - MYSQL_ERROR *err; + MEM_ROOT *mem_root= thd->mem_root; + const Sql_condition *err; SELECT_LEX *sel= &thd->lex->select_lex; SELECT_LEX_UNIT *unit= &thd->lex->unit; ulonglong idx= 0; Protocol *protocol=thd->protocol; + DBUG_ENTER("mysqld_show_warnings"); + + DBUG_ASSERT(thd->get_stmt_da()->is_warning_info_read_only()); + + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Level", 7), + mem_root); + field_list.push_back(new (mem_root) + Item_return_int(thd, "Code", 4, MYSQL_TYPE_LONG), + mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Message", MYSQL_ERRMSG_SIZE), + mem_root); + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); unit->set_limit(sel); - List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); + Diagnostics_area::Sql_condition_iterator it= + thd->get_stmt_da()->sql_conditions(); while ((err= it++)) { /* Skip levels that the user is not interested in */ @@ -715,7 +867,7 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show) } my_eof(thd); - thd->warning_info->set_read_only(FALSE); + thd->get_stmt_da()->set_warning_info_read_only(FALSE); DBUG_RETURN(FALSE); } @@ -823,7 +975,7 @@ uint32 convert_error_message(char *to, uint32 to_length, CHARSET_INFO *to_cs, if (!to_cs || from_cs == to_cs || to_cs == &my_charset_bin) { - length= min(to_length, from_length); + length= MY_MIN(to_length, from_length); memmove(to, from, length); to[length]= 0; return length; @@ -865,3 +1017,32 @@ uint32 convert_error_message(char *to, uint32 to_length, CHARSET_INFO *to_cs, *errors= error_count; return (uint32) (to - to_start); } + + +/** + Sanity check for SQLSTATEs. The function does not check if it's really an + existing SQL-state (there are just too many), it just checks string length and + looks for bad characters. + + @param sqlstate the condition SQLSTATE. + + @retval true if it's ok. + @retval false if it's bad. +*/ + +bool is_sqlstate_valid(const LEX_STRING *sqlstate) +{ + if (sqlstate->length != 5) + return false; + + for (int i= 0 ; i < 5 ; ++i) + { + char c = sqlstate->str[i]; + + if ((c < '0' || '9' < c) && + (c < 'A' || 'Z' < c)) + return false; + } + + return true; +} diff --git a/sql/sql_error.h b/sql/sql_error.h index 903ad65521d..0134f938c75 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -20,124 +20,13 @@ #include "m_string.h" /* LEX_STRING */ #include "sql_type_int.h" // Longlong_hybrid #include "sql_string.h" /* String */ +#include "sql_plist.h" /* I_P_List */ #include "mysql_com.h" /* MYSQL_ERRMSG_SIZE */ +#include "my_time.h" /* MYSQL_TIME */ +#include "decimal.h" class THD; - -/** - Stores status of the currently executed statement. - Cleared at the beginning of the statement, and then - can hold either OK, ERROR, or EOF status. - Can not be assigned twice per statement. -*/ - -class Diagnostics_area -{ -public: - enum enum_diagnostics_status - { - /** The area is cleared at start of a statement. */ - DA_EMPTY= 0, - /** Set whenever one calls my_ok(). */ - DA_OK, - /** Set whenever one calls my_eof(). */ - DA_EOF, - /** Set whenever one calls my_error() or my_message(). */ - DA_ERROR, - /** Set in case of a custom response, such as one from COM_STMT_PREPARE. */ - DA_DISABLED - }; - /** True if status information is sent to the client. */ - bool is_sent; - /** Set to make set_error_status after set_{ok,eof}_status possible. */ - bool can_overwrite_status; - - void set_ok_status(THD *thd, ulonglong affected_rows_arg, - ulonglong last_insert_id_arg, - const char *message); - void set_eof_status(THD *thd); - void set_error_status(THD *thd, uint sql_errno_arg, const char *message_arg, - const char *sqlstate); - - void disable_status(); - - void reset_diagnostics_area(); - - bool is_set() const { return m_status != DA_EMPTY; } - bool is_error() const { return m_status == DA_ERROR; } - bool is_eof() const { return m_status == DA_EOF; } - bool is_ok() const { return m_status == DA_OK; } - bool is_disabled() const { return m_status == DA_DISABLED; } - enum_diagnostics_status status() const { return m_status; } - - const char *message() const - { DBUG_ASSERT(m_status == DA_ERROR || m_status == DA_OK); return m_message; } - - uint sql_errno() const - { DBUG_ASSERT(m_status == DA_ERROR); return m_sql_errno; } - - const char* get_sqlstate() const - { DBUG_ASSERT(m_status == DA_ERROR); return m_sqlstate; } - - ulonglong affected_rows() const - { DBUG_ASSERT(m_status == DA_OK); return m_affected_rows; } - - ulonglong last_insert_id() const - { DBUG_ASSERT(m_status == DA_OK); return m_last_insert_id; } - - uint statement_warn_count() const - { - DBUG_ASSERT(m_status == DA_OK || m_status == DA_EOF); - return m_statement_warn_count; - } - - /* Used to count any warnings pushed after calling set_ok_status(). */ - void increment_warning() - { - if (m_status != DA_EMPTY) - m_statement_warn_count++; - } - - Diagnostics_area() { reset_diagnostics_area(); } - -private: - /** Message buffer. Can be used by OK or ERROR status. */ - char m_message[MYSQL_ERRMSG_SIZE]; - /** - SQL error number. One of ER_ codes from share/errmsg.txt. - Set by set_error_status. - */ - uint m_sql_errno; - - char m_sqlstate[SQLSTATE_LENGTH+1]; - - /** - The number of rows affected by the last statement. This is - semantically close to thd->row_count_func, but has a different - life cycle. thd->row_count_func stores the value returned by - function ROW_COUNT() and is cleared only by statements that - update its value, such as INSERT, UPDATE, DELETE and few others. - This member is cleared at the beginning of the next statement. - - We could possibly merge the two, but life cycle of thd->row_count_func - can not be changed. - */ - ulonglong m_affected_rows; - /** - Similarly to the previous member, this is a replacement of - thd->first_successful_insert_id_in_prev_stmt, which is used - to implement LAST_INSERT_ID(). - */ - ulonglong m_last_insert_id; - /** - Number of warnings of this last statement. May differ from - the number of warnings returned by SHOW WARNINGS e.g. in case - the statement doesn't clear the warnings, and doesn't generate - them. - */ - uint m_statement_warn_count; - enum_diagnostics_status m_status; -}; +class my_decimal; /////////////////////////////////////////////////////////////////////////// @@ -145,10 +34,8 @@ private: Representation of a SQL condition. A SQL condition can be a completion condition (note, warning), or an exception condition (error, not found). - @note This class is named MYSQL_ERROR instead of SQL_condition for - historical reasons, to facilitate merging code with previous releases. */ -class MYSQL_ERROR : public Sql_alloc +class Sql_condition : public Sql_alloc { public: /* @@ -159,6 +46,17 @@ public: */ enum enum_warning_level { WARN_LEVEL_NOTE, WARN_LEVEL_WARN, WARN_LEVEL_ERROR, WARN_LEVEL_END}; + + /** + Convert a bitmask consisting of MYSQL_TIME_{NOTE|WARN}_XXX bits + to WARN_LEVEL_XXX + */ + static enum_warning_level time_warn_level(int warnings) + { + return MYSQL_TIME_WARN_HAVE_WARNINGS(warnings) ? + WARN_LEVEL_WARN : WARN_LEVEL_NOTE; + } + /** Get the MESSAGE_TEXT of this condition. @return the message text. @@ -189,26 +87,15 @@ public: Get the error level of this condition. @return the error level condition item. */ - MYSQL_ERROR::enum_warning_level get_level() const + Sql_condition::enum_warning_level get_level() const { return m_level; } - /** check if condition was handled by a condition handler */ - bool handled() const - { - return m_handled; - } - /** mark that condition was handled */ - void mark_handled() - { - m_handled= 1; - } - private: /* - The interface of MYSQL_ERROR is mostly private, by design, + The interface of Sql_condition is mostly private, by design, so that only the following code: - various raise_error() or raise_warning() methods in class THD, - - the implementation of SIGNAL / RESIGNAL + - the implementation of SIGNAL / RESIGNAL / GET DIAGNOSTICS - catch / re-throw of SQL conditions in stored procedures (sp_rcontext) is allowed to create / modify a SQL condition. Enforcing this policy prevents confusion, since the only public @@ -218,20 +105,21 @@ private: */ friend class THD; friend class Warning_info; - friend class Signal_common; - friend class Signal_statement; - friend class Resignal_statement; + friend class Sql_cmd_common_signal; + friend class Sql_cmd_signal; + friend class Sql_cmd_resignal; friend class sp_rcontext; + friend class Condition_information_item; /** Default constructor. This constructor is usefull when allocating arrays. - Note that the init() method should be called to complete the MYSQL_ERROR. + Note that the init() method should be called to complete the Sql_condition. */ - MYSQL_ERROR(); + Sql_condition(); /** - Complete the MYSQL_ERROR initialisation. + Complete the Sql_condition initialisation. @param mem_root The memory root to use for the condition items of this condition */ @@ -242,17 +130,17 @@ private: @param mem_root The memory root to use for the condition items of this condition */ - MYSQL_ERROR(MEM_ROOT *mem_root); + Sql_condition(MEM_ROOT *mem_root); /** Destructor. */ - ~MYSQL_ERROR() + ~Sql_condition() {} /** Copy optional condition items attributes. @param cond the condition to copy. */ - void copy_opt_attributes(const MYSQL_ERROR *cond); + void copy_opt_attributes(const Sql_condition *cond); /** Set this condition area with a fixed message text. @@ -263,7 +151,7 @@ private: @param MyFlags additional flags. */ void set(uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg); /** @@ -276,6 +164,12 @@ private: /** Set the SQLSTATE of this condition. */ void set_sqlstate(const char* sqlstate); + /** Set the CLASS_ORIGIN of this condition. */ + void set_class_origin(); + + /** Set the SUBCLASS_ORIGIN of this condition. */ + void set_subclass_origin(); + /** Clear this SQL condition. */ @@ -318,9 +212,6 @@ private: /** MySQL extension, MYSQL_ERRNO condition item. */ uint m_sql_errno; - /** Marker if error/warning was handled by a continue handler */ - bool m_handled; - /** SQL RETURNED_SQLSTATE condition item. This member is always NUL terminated. @@ -328,7 +219,11 @@ private: char m_returned_sqlstate[SQLSTATE_LENGTH+1]; /** Severity (error, warning, note) of this condition. */ - MYSQL_ERROR::enum_warning_level m_level; + Sql_condition::enum_warning_level m_level; + + /** Pointers for participating in the list of conditions. */ + Sql_condition *next_in_wi; + Sql_condition **prev_in_wi; /** Memory root to use to hold condition item values. */ MEM_ROOT *m_mem_root; @@ -339,25 +234,33 @@ private: /** Information about warnings of the current connection. */ - class Warning_info { + /** The type of the counted and doubly linked list of conditions. */ + typedef I_P_List<Sql_condition, + I_P_List_adapter<Sql_condition, + &Sql_condition::next_in_wi, + &Sql_condition::prev_in_wi>, + I_P_List_counter, + I_P_List_fast_push_back<Sql_condition> > + Sql_condition_list; + /** A memory root to allocate warnings and errors */ MEM_ROOT m_warn_root; /** List of warnings of all severities (levels). */ - List <MYSQL_ERROR> m_warn_list; + Sql_condition_list m_warn_list; /** A break down of the number of warnings per severity (level). */ - uint m_warn_count[(uint) MYSQL_ERROR::WARN_LEVEL_END]; + uint m_warn_count[(uint) Sql_condition::WARN_LEVEL_END]; /** The number of warnings of the current statement. Warning_info life cycle differs from statement life cycle -- it may span multiple statements. In that case we get - m_statement_warn_count 0, whereas m_warn_list is not empty. + m_current_statement_warn_count 0, whereas m_warn_list is not empty. */ - uint m_statement_warn_count; + uint m_current_statement_warn_count; /* Row counter, to print in errors and warnings. Not increased in @@ -368,23 +271,67 @@ class Warning_info /** Used to optionally clear warnings only once per statement. */ ulonglong m_warn_id; + /** + A pointer to an element of m_warn_list. It determines SQL-condition + instance which corresponds to the error state in Diagnostics_area. + + This is needed for properly processing SQL-conditions in SQL-handlers. + When an SQL-handler is found for the current error state in Diagnostics_area, + this pointer is needed to remove the corresponding SQL-condition from the + Warning_info list. + + @note m_error_condition might be NULL in the following cases: + - Diagnostics_area set to fatal error state (like OOM); + - Max number of Warning_info elements has been reached (thus, there is + no corresponding SQL-condition object in Warning_info). + */ + const Sql_condition *m_error_condition; + /** Indicates if push_warning() allows unlimited number of warnings. */ bool m_allow_unlimited_warnings; + bool initialized; /* Set to 1 if init() has been called */ + + /** Read only status. */ + bool m_read_only; + + /** Pointers for participating in the stack of Warning_info objects. */ + Warning_info *m_next_in_da; + Warning_info **m_prev_in_da; + + List<Sql_condition> m_marked_sql_conditions; + +public: + Warning_info(ulonglong warn_id_arg, bool allow_unlimited_warnings, + bool initialized); + ~Warning_info(); + /* Allocate memory for structures */ + void init(); + void free_memory(); private: Warning_info(const Warning_info &rhs); /* Not implemented */ Warning_info& operator=(const Warning_info &rhs); /* Not implemented */ -public: - Warning_info(ulonglong warn_id_arg, bool allow_unlimited_warnings); - ~Warning_info(); + /** + Checks if Warning_info contains SQL-condition with the given message. + + @param message_str Message string. + @param message_length Length of message string. + + @return true if the Warning_info contains an SQL-condition with the given + message. + */ + bool has_sql_condition(const char *message_str, ulong message_length) const; /** Reset the warning information. Clear all warnings, the number of warnings, reset current row counter to point to the first row. + + @param new_id new Warning_info id. */ - void clear_warning_info(ulonglong warn_id_arg); + void clear(ulonglong new_id); + /** Only clear warning info if haven't yet done that already for the current query. Allows to be issued at any time @@ -393,46 +340,72 @@ public: @todo: This is a sign of sloppy coding. Instead we need to designate one place in a statement life cycle where we call - clear_warning_info(). + Warning_info::clear(). + + @param query_id Current query id. */ - void opt_clear_warning_info(ulonglong query_id) + void opt_clear(ulonglong query_id) { if (query_id != m_warn_id) - clear_warning_info(query_id); - } - - void append_warning_info(THD *thd, Warning_info *source) - { - append_warnings(thd, & source->warn_list()); + clear(query_id); } /** Concatenate the list of warnings. - It's considered tolerable to lose a warning. - */ - void append_warnings(THD *thd, List<MYSQL_ERROR> *src) - { - MYSQL_ERROR *err; - List_iterator_fast<MYSQL_ERROR> it(*src); - /* - Don't use ::push_warning() to avoid invocation of condition - handlers or escalation of warnings to errors. - */ - while ((err= it++)) - Warning_info::push_warning(thd, err); - } - /** - Conditional merge of related warning information areas. + It's considered tolerable to lose an SQL-condition in case of OOM-error, + or if the number of SQL-conditions in the Warning_info reached top limit. + + @param thd Thread context. + @param source Warning_info object to copy SQL-conditions from. */ - void merge_with_routine_info(THD *thd, Warning_info *source); + void append_warning_info(THD *thd, const Warning_info *source); /** Reset between two COM_ commands. Warnings are preserved between commands, but statement_warn_count indicates the number of warnings of this particular statement only. */ - void reset_for_next_command() { m_statement_warn_count= 0; } + void reset_for_next_command() + { m_current_statement_warn_count= 0; } + + /** + Mark active SQL-conditions for later removal. + This is done to simulate stacked DAs for HANDLER statements. + */ + void mark_sql_conditions_for_removal(); + + /** + Unmark SQL-conditions, which were marked for later removal. + This is done to simulate stacked DAs for HANDLER statements. + */ + void unmark_sql_conditions_from_removal() + { m_marked_sql_conditions.empty(); } + + /** + Remove SQL-conditions that are marked for deletion. + This is done to simulate stacked DAs for HANDLER statements. + */ + void remove_marked_sql_conditions(); + + /** + Check if the given SQL-condition is marked for removal in this Warning_info + instance. + + @param cond the SQL-condition. + + @retval true if the given SQL-condition is marked for removal in this + Warning_info instance. + @retval false otherwise. + */ + bool is_marked_for_removal(const Sql_condition *cond) const; + + /** + Mark a single SQL-condition for removal (add the given SQL-condition to the + removal list of this Warning_info instance). + */ + void mark_condition_for_removal(Sql_condition *cond) + { m_marked_sql_conditions.push_back(cond, &m_warn_root); } /** Used for @@warning_count system variable, which prints @@ -441,52 +414,82 @@ public: ulong warn_count() const { /* - This may be higher than warn_list.elements if we have + This may be higher than warn_list.elements() if we have had more warnings than thd->variables.max_error_count. */ - return (m_warn_count[(uint) MYSQL_ERROR::WARN_LEVEL_NOTE] + - m_warn_count[(uint) MYSQL_ERROR::WARN_LEVEL_ERROR] + - m_warn_count[(uint) MYSQL_ERROR::WARN_LEVEL_WARN]); + return (m_warn_count[(uint) Sql_condition::WARN_LEVEL_NOTE] + + m_warn_count[(uint) Sql_condition::WARN_LEVEL_ERROR] + + m_warn_count[(uint) Sql_condition::WARN_LEVEL_WARN]); } /** - This is for iteration purposes. We return a non-constant reference - since List doesn't have constant iterators. - */ - List<MYSQL_ERROR> &warn_list() { return m_warn_list; } - - /** The number of errors, or number of rows returned by SHOW ERRORS, also the value of session variable @@error_count. */ ulong error_count() const + { return m_warn_count[(uint) Sql_condition::WARN_LEVEL_ERROR]; } + + /** + The number of conditions (errors, warnings and notes) in the list. + */ + uint cond_count() const { - return m_warn_count[(uint) MYSQL_ERROR::WARN_LEVEL_ERROR]; + return m_warn_list.elements(); } /** Id of the warning information area. */ - ulonglong warn_id() const { return m_warn_id; } + ulonglong id() const { return m_warn_id; } + + /** Set id of the warning information area. */ + void id(ulonglong id_arg) { m_warn_id= id_arg; } /** Do we have any errors and warnings that we can *show*? */ - bool is_empty() const { return m_warn_list.elements == 0; } + bool is_empty() const { return m_warn_list.is_empty(); } /** Increment the current row counter to point at the next row. */ void inc_current_row_for_warning() { m_current_row_for_warning++; } + /** Reset the current row counter. Start counting from the first row. */ void reset_current_row_for_warning() { m_current_row_for_warning= 1; } + /** Return the current counter value. */ ulong current_row_for_warning() const { return m_current_row_for_warning; } - ulong statement_warn_count() const { return m_statement_warn_count; } + /** Return the number of warnings thrown by the current statement. */ + ulong current_statement_warn_count() const + { return m_current_statement_warn_count; } - /** Add a new condition to the current list. */ - MYSQL_ERROR *push_warning(THD *thd, - uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char* msg); + /** Make sure there is room for the given number of conditions. */ + void reserve_space(THD *thd, uint count); - /** Add a new condition to the current list. */ - MYSQL_ERROR *push_warning(THD *thd, const MYSQL_ERROR *sql_condition); + /** + Add a new SQL-condition to the current list and increment the respective + counters. + + @param thd Thread context. + @param sql_errno SQL-condition error number. + @param sqlstate SQL-condition state. + @param level SQL-condition level. + @param msg SQL-condition message. + + @return a pointer to the added SQL-condition. + */ + Sql_condition *push_warning(THD *thd, + uint sql_errno, + const char* sqlstate, + Sql_condition::enum_warning_level level, + const char* msg); + + /** + Add a new SQL-condition to the current list and increment the respective + counters. + + @param thd Thread context. + @param sql_condition SQL-condition to copy values from. + + @return a pointer to the added SQL-condition. + */ + Sql_condition *push_warning(THD *thd, const Sql_condition *sql_condition); /** Set the read only status for this statement area. @@ -497,25 +500,51 @@ public: - SHOW WARNINGS - SHOW ERRORS - GET DIAGNOSTICS - @param read_only the read only property to set + @param read_only the read only property to set. */ - void set_read_only(bool read_only) - { m_read_only= read_only; } + void set_read_only(bool read_only_arg) + { m_read_only= read_only_arg; } /** Read only status. - @return the read only property + @return the read only property. */ bool is_read_only() const { return m_read_only; } -private: - /** Read only status. */ - bool m_read_only; + /** + @return SQL-condition, which corresponds to the error state in + Diagnostics_area. - friend class Resignal_statement; + @see m_error_condition. + */ + const Sql_condition *get_error_condition() const + { return m_error_condition; } + + /** + Set SQL-condition, which corresponds to the error state in Diagnostics_area. + + @see m_error_condition. + */ + void set_error_condition(const Sql_condition *error_condition) + { m_error_condition= error_condition; } + + /** + Reset SQL-condition, which corresponds to the error state in + Diagnostics_area. + + @see m_error_condition. + */ + void clear_error_condition() + { m_error_condition= NULL; } + + // for: + // - m_next_in_da / m_prev_in_da + // - is_marked_for_removal() + friend class Diagnostics_area; }; + extern char *err_conv(char *buff, uint to_length, const char *from, uint from_length, CHARSET_INFO *from_cs); @@ -537,7 +566,9 @@ class ErrConvString : public ErrConv public: ErrConvString(const char *str_arg, size_t len_arg, CHARSET_INFO *cs_arg) : ErrConv(), str(str_arg), len(len_arg), cs(cs_arg) {} - ErrConvString(String *s) + ErrConvString(const char *str_arg, CHARSET_INFO *cs_arg) + : ErrConv(), str(str_arg), len(strlen(str_arg)), cs(cs_arg) {} + ErrConvString(const String *s) : ErrConv(), str(s->ptr()), len(s->length()), cs(s->charset()) {} const char *ptr() const { return err_conv(err_buffer, sizeof(err_buffer), str, len, cs); } @@ -592,15 +623,354 @@ public: } }; -void push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, +/////////////////////////////////////////////////////////////////////////// + +/** + Stores status of the currently executed statement. + Cleared at the beginning of the statement, and then + can hold either OK, ERROR, or EOF status. + Can not be assigned twice per statement. +*/ + +class Diagnostics_area +{ +private: + /** The type of the counted and doubly linked list of conditions. */ + typedef I_P_List<Warning_info, + I_P_List_adapter<Warning_info, + &Warning_info::m_next_in_da, + &Warning_info::m_prev_in_da>, + I_P_List_counter, + I_P_List_fast_push_back<Warning_info> > + Warning_info_list; + +public: + /** Const iterator used to iterate through the warning list. */ + typedef Warning_info::Sql_condition_list::Const_Iterator + Sql_condition_iterator; + + enum enum_diagnostics_status + { + /** The area is cleared at start of a statement. */ + DA_EMPTY= 0, + /** Set whenever one calls my_ok(). */ + DA_OK, + /** Set whenever one calls my_eof(). */ + DA_EOF, + /** Set whenever one calls my_error() or my_message(). */ + DA_ERROR, + /** Set in case of a custom response, such as one from COM_STMT_PREPARE. */ + DA_DISABLED + }; + + void set_overwrite_status(bool can_overwrite_status) + { m_can_overwrite_status= can_overwrite_status; } + + /** True if status information is sent to the client. */ + bool is_sent() const { return m_is_sent; } + + void set_is_sent(bool is_sent_arg) { m_is_sent= is_sent_arg; } + + void set_ok_status(ulonglong affected_rows, + ulonglong last_insert_id, + const char *message); + + void set_eof_status(THD *thd); + + void set_error_status(uint sql_errno); + + void set_error_status(uint sql_errno, + const char *message, + const char *sqlstate, + const Sql_condition *error_condition); + + void disable_status(); + + void reset_diagnostics_area(); + + bool is_set() const { return m_status != DA_EMPTY; } + + bool is_error() const { return m_status == DA_ERROR; } + + bool is_eof() const { return m_status == DA_EOF; } + + bool is_ok() const { return m_status == DA_OK; } + + bool is_disabled() const { return m_status == DA_DISABLED; } + + enum_diagnostics_status status() const { return m_status; } + + const char *message() const + { DBUG_ASSERT(m_status == DA_ERROR || m_status == DA_OK); return m_message; } + + uint sql_errno() const + { DBUG_ASSERT(m_status == DA_ERROR); return m_sql_errno; } + + const char* get_sqlstate() const + { DBUG_ASSERT(m_status == DA_ERROR); return m_sqlstate; } + + ulonglong affected_rows() const + { DBUG_ASSERT(m_status == DA_OK); return m_affected_rows; } + + ulonglong last_insert_id() const + { DBUG_ASSERT(m_status == DA_OK); return m_last_insert_id; } + + uint statement_warn_count() const + { + DBUG_ASSERT(m_status == DA_OK || m_status == DA_EOF); + return m_statement_warn_count; + } + + /* Used to count any warnings pushed after calling set_ok_status(). */ + void increment_warning() + { + if (m_status != DA_EMPTY) + m_statement_warn_count++; + } + + Diagnostics_area(bool initialize); + Diagnostics_area(ulonglong warning_info_id, bool allow_unlimited_warnings, + bool initialize); + void init() { m_main_wi.init() ; } + void free_memory() { m_main_wi.free_memory() ; } + + void push_warning_info(Warning_info *wi) + { m_wi_stack.push_front(wi); } + + void pop_warning_info() + { + DBUG_ASSERT(m_wi_stack.elements() > 0); + m_wi_stack.remove(m_wi_stack.front()); + } + + void set_warning_info_id(ulonglong id) + { get_warning_info()->id(id); } + + ulonglong warning_info_id() const + { return get_warning_info()->id(); } + + /** + Compare given current warning info and current warning info + and see if they are different. They will be different if + warnings have been generated or statements that use tables + have been executed. This is checked by comparing m_warn_id. + + @param wi Warning info to compare with current Warning info. + + @return false if they are equal, true if they are not. + */ + bool warning_info_changed(const Warning_info *wi) const + { return get_warning_info()->id() != wi->id(); } + + bool is_warning_info_empty() const + { return get_warning_info()->is_empty(); } + + ulong current_statement_warn_count() const + { return get_warning_info()->current_statement_warn_count(); } + + bool has_sql_condition(const char *message_str, ulong message_length) const + { return get_warning_info()->has_sql_condition(message_str, message_length); } + + void reset_for_next_command() + { get_warning_info()->reset_for_next_command(); } + + void clear_warning_info(ulonglong id) + { get_warning_info()->clear(id); } + + void opt_clear_warning_info(ulonglong query_id) + { get_warning_info()->opt_clear(query_id); } + + ulong current_row_for_warning() const + { return get_warning_info()->current_row_for_warning(); } + + void inc_current_row_for_warning() + { get_warning_info()->inc_current_row_for_warning(); } + + void reset_current_row_for_warning() + { get_warning_info()->reset_current_row_for_warning(); } + + bool is_warning_info_read_only() const + { return get_warning_info()->is_read_only(); } + + void set_warning_info_read_only(bool read_only_arg) + { get_warning_info()->set_read_only(read_only_arg); } + + ulong error_count() const + { return get_warning_info()->error_count(); } + + ulong warn_count() const + { return get_warning_info()->warn_count(); } + + uint cond_count() const + { return get_warning_info()->cond_count(); } + + Sql_condition_iterator sql_conditions() const + { return get_warning_info()->m_warn_list; } + + void reserve_space(THD *thd, uint count) + { get_warning_info()->reserve_space(thd, count); } + + Sql_condition *push_warning(THD *thd, const Sql_condition *sql_condition) + { return get_warning_info()->push_warning(thd, sql_condition); } + + Sql_condition *push_warning(THD *thd, + uint sql_errno_arg, + const char* sqlstate, + Sql_condition::enum_warning_level level, + const char* msg) + { + return get_warning_info()->push_warning(thd, + sql_errno_arg, sqlstate, level, + msg); + } + + void mark_sql_conditions_for_removal() + { get_warning_info()->mark_sql_conditions_for_removal(); } + + void unmark_sql_conditions_from_removal() + { get_warning_info()->unmark_sql_conditions_from_removal(); } + + void remove_marked_sql_conditions() + { get_warning_info()->remove_marked_sql_conditions(); } + + const Sql_condition *get_error_condition() const + { return get_warning_info()->get_error_condition(); } + + void copy_sql_conditions_to_wi(THD *thd, Warning_info *dst_wi) const + { dst_wi->append_warning_info(thd, get_warning_info()); } + + void copy_sql_conditions_from_wi(THD *thd, const Warning_info *src_wi) + { get_warning_info()->append_warning_info(thd, src_wi); } + + void copy_non_errors_from_wi(THD *thd, const Warning_info *src_wi); + +private: + Warning_info *get_warning_info() { return m_wi_stack.front(); } + + const Warning_info *get_warning_info() const { return m_wi_stack.front(); } + +private: + /** True if status information is sent to the client. */ + bool m_is_sent; + + /** Set to make set_error_status after set_{ok,eof}_status possible. */ + bool m_can_overwrite_status; + + /** Message buffer. Can be used by OK or ERROR status. */ + char m_message[MYSQL_ERRMSG_SIZE]; + + /** + SQL error number. One of ER_ codes from share/errmsg.txt. + Set by set_error_status. + */ + uint m_sql_errno; + + char m_sqlstate[SQLSTATE_LENGTH+1]; + + /** + The number of rows affected by the last statement. This is + semantically close to thd->m_row_count_func, but has a different + life cycle. thd->m_row_count_func stores the value returned by + function ROW_COUNT() and is cleared only by statements that + update its value, such as INSERT, UPDATE, DELETE and few others. + This member is cleared at the beginning of the next statement. + + We could possibly merge the two, but life cycle of thd->m_row_count_func + can not be changed. + */ + ulonglong m_affected_rows; + + /** + Similarly to the previous member, this is a replacement of + thd->first_successful_insert_id_in_prev_stmt, which is used + to implement LAST_INSERT_ID(). + */ + + ulonglong m_last_insert_id; + /** + Number of warnings of this last statement. May differ from + the number of warnings returned by SHOW WARNINGS e.g. in case + the statement doesn't clear the warnings, and doesn't generate + them. + */ + uint m_statement_warn_count; + + enum_diagnostics_status m_status; + + Warning_info m_main_wi; + + Warning_info_list m_wi_stack; +}; + +/////////////////////////////////////////////////////////////////////////// + + +void push_warning(THD *thd, Sql_condition::enum_warning_level level, uint code, const char *msg); -void push_warning_printf(THD *thd, MYSQL_ERROR::enum_warning_level level, - uint code, const char *format, ...); + +void push_warning_printf(THD *thd, Sql_condition::enum_warning_level level, + uint code, const char *format, ...); + bool mysqld_show_warnings(THD *thd, ulong levels_to_show); -uint32 convert_error_message(char *to, uint32 to_length, CHARSET_INFO *to_cs, + +uint32 convert_error_message(char *to, uint32 to_length, + CHARSET_INFO *to_cs, const char *from, uint32 from_length, CHARSET_INFO *from_cs, uint *errors); extern const LEX_STRING warning_level_names[]; +bool is_sqlstate_valid(const LEX_STRING *sqlstate); +/** + Checks if the specified SQL-state-string defines COMPLETION condition. + This function assumes that the given string contains a valid SQL-state. + + @param s the condition SQLSTATE. + + @retval true if the given string defines COMPLETION condition. + @retval false otherwise. +*/ +inline bool is_sqlstate_completion(const char *s) +{ return s[0] == '0' && s[1] == '0'; } + + +/** + Checks if the specified SQL-state-string defines WARNING condition. + This function assumes that the given string contains a valid SQL-state. + + @param s the condition SQLSTATE. + + @retval true if the given string defines WARNING condition. + @retval false otherwise. +*/ +inline bool is_sqlstate_warning(const char *s) +{ return s[0] == '0' && s[1] == '1'; } + + +/** + Checks if the specified SQL-state-string defines NOT FOUND condition. + This function assumes that the given string contains a valid SQL-state. + + @param s the condition SQLSTATE. + + @retval true if the given string defines NOT FOUND condition. + @retval false otherwise. +*/ +inline bool is_sqlstate_not_found(const char *s) +{ return s[0] == '0' && s[1] == '2'; } + + +/** + Checks if the specified SQL-state-string defines EXCEPTION condition. + This function assumes that the given string contains a valid SQL-state. + + @param s the condition SQLSTATE. + + @retval true if the given string defines EXCEPTION condition. + @retval false otherwise. +*/ +inline bool is_sqlstate_exception(const char *s) +{ return s[0] != '0' || s[1] > '2'; } + + #endif // SQL_ERROR_H diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc new file mode 100644 index 00000000000..a1d6764d8e4 --- /dev/null +++ b/sql/sql_explain.cc @@ -0,0 +1,2464 @@ +/* + Copyright (c) 2013 Monty Program Ab + + 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 02111-1301 USA */ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include <my_global.h> +#include "sql_priv.h" +#include "sql_select.h" +#include "my_json_writer.h" +#include "opt_range.h" +#include "sql_expression_cache.h" + +const char * STR_DELETING_ALL_ROWS= "Deleting all rows"; +const char * STR_IMPOSSIBLE_WHERE= "Impossible WHERE"; +const char * STR_NO_ROWS_AFTER_PRUNING= "No matching rows after partition pruning"; + +static void write_item(Json_writer *writer, Item *item); + +Explain_query::Explain_query(THD *thd_arg, MEM_ROOT *root) : + mem_root(root), upd_del_plan(NULL), insert_plan(NULL), + unions(root), selects(root), thd(thd_arg), apc_enabled(false), + operations(0) +{ +} + +static void print_json_array(Json_writer *writer, + const char *title, String_list &list) +{ + List_iterator_fast<char> it(list); + const char *name; + writer->add_member(title).start_array(); + while ((name= it++)) + writer->add_str(name); + writer->end_array(); +} + + + +Explain_query::~Explain_query() +{ + if (apc_enabled) + thd->apc_target.disable(); + + delete upd_del_plan; + delete insert_plan; + uint i; + for (i= 0 ; i < unions.elements(); i++) + delete unions.at(i); + for (i= 0 ; i < selects.elements(); i++) + delete selects.at(i); +} + + +Explain_node *Explain_query::get_node(uint select_id) +{ + Explain_union *u; + if ((u= get_union(select_id))) + return u; + else + return get_select(select_id); +} + +Explain_union *Explain_query::get_union(uint select_id) +{ + return (unions.elements() > select_id) ? unions.at(select_id) : NULL; +} + +Explain_select *Explain_query::get_select(uint select_id) +{ + return (selects.elements() > select_id) ? selects.at(select_id) : NULL; +} + + +void Explain_query::add_node(Explain_node *node) +{ + uint select_id; + operations++; + if (node->get_type() == Explain_node::EXPLAIN_UNION) + { + Explain_union *u= (Explain_union*)node; + select_id= u->get_select_id(); + if (unions.elements() <= select_id) + unions.resize(MY_MAX(select_id+1, unions.elements()*2), NULL); + + Explain_union *old_node; + if ((old_node= get_union(select_id))) + delete old_node; + + unions.at(select_id)= u; + } + else + { + Explain_select *sel= (Explain_select*)node; + if (sel->select_id == FAKE_SELECT_LEX_ID) + { + DBUG_ASSERT(0); // this is a "fake select" from a UNION. + } + else + { + select_id= sel->select_id; + Explain_select *old_node; + + if (selects.elements() <= select_id) + selects.resize(MY_MAX(select_id+1, selects.elements()*2), NULL); + + if ((old_node= get_select(select_id))) + delete old_node; + + selects.at(select_id)= sel; + } + } +} + + +void Explain_query::add_insert_plan(Explain_insert *insert_plan_arg) +{ + insert_plan= insert_plan_arg; + query_plan_ready(); +} + + +void Explain_query::add_upd_del_plan(Explain_update *upd_del_plan_arg) +{ + upd_del_plan= upd_del_plan_arg; + query_plan_ready(); +} + + +void Explain_query::query_plan_ready() +{ + if (!apc_enabled) + thd->apc_target.enable(); + apc_enabled= true; +} + +/* + Send EXPLAIN output to the client. +*/ + +int Explain_query::send_explain(THD *thd) +{ + select_result *result; + LEX *lex= thd->lex; + + if (!(result= new (thd->mem_root) select_send(thd)) || + thd->send_explain_fields(result, lex->describe, lex->analyze_stmt)) + return 1; + + int res= 0; + if (thd->lex->explain_json) + print_explain_json(result, thd->lex->analyze_stmt); + else + res= print_explain(result, lex->describe, thd->lex->analyze_stmt); + + if (res) + result->abort_result_set(); + else + result->send_eof(); + + return res; +} + + +/* + The main entry point to print EXPLAIN of the entire query +*/ + +int Explain_query::print_explain(select_result_sink *output, + uint8 explain_flags, bool is_analyze) +{ + if (upd_del_plan) + { + upd_del_plan->print_explain(this, output, explain_flags, is_analyze); + return 0; + } + else if (insert_plan) + { + insert_plan->print_explain(this, output, explain_flags, is_analyze); + return 0; + } + else + { + /* Start printing from node with id=1 */ + Explain_node *node= get_node(1); + if (!node) + return 1; /* No query plan */ + return node->print_explain(this, output, explain_flags, is_analyze); + } +} + + +void Explain_query::print_explain_json(select_result_sink *output, + bool is_analyze) +{ + Json_writer writer; + writer.start_object(); + + if (upd_del_plan) + upd_del_plan->print_explain_json(this, &writer, is_analyze); + else if (insert_plan) + insert_plan->print_explain_json(this, &writer, is_analyze); + else + { + /* Start printing from node with id=1 */ + Explain_node *node= get_node(1); + if (!node) + return; /* No query plan */ + node->print_explain_json(this, &writer, is_analyze); + } + + writer.end_object(); + + CHARSET_INFO *cs= system_charset_info; + List<Item> item_list; + String *buf= &writer.output; + item_list.push_back(new (thd->mem_root) + Item_string(thd, buf->ptr(), buf->length(), cs), + thd->mem_root); + output->send_data(item_list); +} + + +bool print_explain_for_slow_log(LEX *lex, THD *thd, String *str) +{ + return lex->explain->print_explain_str(thd, str, /*is_analyze*/ true); +} + + +/* + Return tabular EXPLAIN output as a text string +*/ + +bool Explain_query::print_explain_str(THD *thd, String *out_str, + bool is_analyze) +{ + List<Item> fields; + thd->make_explain_field_list(fields, thd->lex->describe, is_analyze); + + select_result_text_buffer output_buf(thd); + output_buf.send_result_set_metadata(fields, thd->lex->describe); + if (print_explain(&output_buf, thd->lex->describe, is_analyze)) + return true; + output_buf.save_to(out_str); + return false; +} + + +static void push_str(THD *thd, List<Item> *item_list, const char *str) +{ + item_list->push_back(new (thd->mem_root) Item_string_sys(thd, str), + thd->mem_root); +} + + +static void push_string(THD *thd, List<Item> *item_list, String *str) +{ + item_list->push_back(new (thd->mem_root) + Item_string_sys(thd, str->ptr(), str->length()), + thd->mem_root); +} + +static void push_string_list(THD *thd, List<Item> *item_list, + String_list &lines, String *buf) +{ + List_iterator_fast<char> it(lines); + char *line; + bool first= true; + while ((line= it++)) + { + if (first) + first= false; + else + buf->append(','); + + buf->append(line); + } + push_string(thd, item_list, buf); +} + + +/* + Print an EXPLAIN output row, based on information provided in the parameters + + @note + Parameters that may have NULL value in EXPLAIN output, should be passed + (char*)NULL. + + @return + 0 - OK + 1 - OOM Error +*/ + +static +int print_explain_row(select_result_sink *result, + uint8 options, bool is_analyze, + uint select_number, + const char *select_type, + const char *table_name, + const char *partitions, + enum join_type jtype, + String_list *possible_keys, + const char *index, + const char *key_len, + const char *ref, + ha_rows *rows, + double *r_rows, + double r_filtered, + const char *extra) +{ + THD *thd= result->thd; + MEM_ROOT *mem_root= thd->mem_root; + Item *item_null= new (mem_root) Item_null(thd); + List<Item> item_list; + Item *item; + + item_list.push_back(new (mem_root) Item_int(thd, (int32) select_number), + mem_root); + item_list.push_back(new (mem_root) Item_string_sys(thd, select_type), + mem_root); + item_list.push_back(new (mem_root) Item_string_sys(thd, table_name), + mem_root); + if (options & DESCRIBE_PARTITIONS) + { + if (partitions) + { + item_list.push_back(new (mem_root) Item_string_sys(thd, partitions), + mem_root); + } + else + item_list.push_back(item_null, mem_root); + } + + const char *jtype_str= join_type_str[jtype]; + item_list.push_back(new (mem_root) Item_string_sys(thd, jtype_str), + mem_root); + + /* 'possible_keys' + The buffer must not be deallocated before we call send_data, otherwise + we may end up reading freed memory. + */ + StringBuffer<64> possible_keys_buf; + if (possible_keys && !possible_keys->is_empty()) + { + push_string_list(thd, &item_list, *possible_keys, &possible_keys_buf); + } + else + item_list.push_back(item_null, mem_root); + + /* 'index */ + item= index ? new (mem_root) Item_string_sys(thd, index) : item_null; + item_list.push_back(item, mem_root); + + /* 'key_len */ + item= key_len ? new (mem_root) Item_string_sys(thd, key_len) : item_null; + item_list.push_back(item, mem_root); + + /* 'ref' */ + item= ref ? new (mem_root) Item_string_sys(thd, ref) : item_null; + item_list.push_back(item, mem_root); + + /* 'rows' */ + if (rows) + { + item_list.push_back(new (mem_root) + Item_int(thd, *rows, MY_INT64_NUM_DECIMAL_DIGITS), + mem_root); + } + else + item_list.push_back(item_null, mem_root); + + /* 'r_rows' */ + if (is_analyze) + { + if (r_rows) + item_list.push_back(new (mem_root) Item_float(thd, *r_rows, 2), + mem_root); + else + item_list.push_back(item_null, mem_root); + } + + /* 'filtered' */ + const double filtered=100.0; + if (options & DESCRIBE_EXTENDED || is_analyze) + item_list.push_back(new (mem_root) Item_float(thd, filtered, 2), mem_root); + + /* 'r_filtered' */ + if (is_analyze) + item_list.push_back(new (mem_root) Item_float(thd, r_filtered, 2), + mem_root); + + /* 'Extra' */ + if (extra) + item_list.push_back(new (mem_root) Item_string_sys(thd, extra), mem_root); + else + item_list.push_back(item_null, mem_root); + + if (result->send_data(item_list)) + return 1; + return 0; +} + + + + +uint Explain_union::make_union_table_name(char *buf) +{ + uint childno= 0; + uint len= 6, lastop= 0; + memcpy(buf, STRING_WITH_LEN("<union")); + + for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN; + childno++) + { + len+= lastop; + lastop= my_snprintf(buf + len, NAME_LEN - len, + "%u,", union_members.at(childno)); + } + + if (childno < union_members.elements() || len + lastop >= NAME_LEN) + { + memcpy(buf + len, STRING_WITH_LEN("...>") + 1); + len+= 4; + } + else + { + len+= lastop; + buf[len - 1]= '>'; // change ',' to '>' + } + return len; +} + + +int Explain_union::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags, + bool is_analyze) +{ + THD *thd= output->thd; + MEM_ROOT *mem_root= thd->mem_root; + char table_name_buffer[SAFE_NAME_LEN]; + + /* print all UNION children, in order */ + for (int i= 0; i < (int) union_members.elements(); i++) + { + Explain_select *sel= query->get_select(union_members.at(i)); + sel->print_explain(query, output, explain_flags, is_analyze); + } + + if (!using_tmp) + return 0; + + /* Print a line with "UNION RESULT" */ + List<Item> item_list; + Item *item_null= new (mem_root) Item_null(thd); + + /* `id` column */ + item_list.push_back(item_null, mem_root); + + /* `select_type` column */ + push_str(thd, &item_list, fake_select_type); + + /* `table` column: something like "<union1,2>" */ + uint len= make_union_table_name(table_name_buffer); + item_list.push_back(new (mem_root) + Item_string_sys(thd, table_name_buffer, len), + mem_root); + + /* `partitions` column */ + if (explain_flags & DESCRIBE_PARTITIONS) + item_list.push_back(item_null, mem_root); + + /* `type` column */ + push_str(thd, &item_list, join_type_str[JT_ALL]); + + /* `possible_keys` column */ + item_list.push_back(item_null, mem_root); + + /* `key` */ + item_list.push_back(item_null, mem_root); + + /* `key_len` */ + item_list.push_back(item_null, mem_root); + + /* `ref` */ + item_list.push_back(item_null, mem_root); + + /* `rows` */ + item_list.push_back(item_null, mem_root); + + /* `r_rows` */ + if (is_analyze) + { + double avg_rows= fake_select_lex_tracker.get_avg_rows(); + item_list.push_back(new (mem_root) Item_float(thd, avg_rows, 2), mem_root); + } + + /* `filtered` */ + if (explain_flags & DESCRIBE_EXTENDED || is_analyze) + item_list.push_back(item_null, mem_root); + + /* `r_filtered` */ + if (is_analyze) + item_list.push_back(item_null, mem_root); + + /* `Extra` */ + StringBuffer<256> extra_buf; + if (using_filesort) + { + extra_buf.append(STRING_WITH_LEN("Using filesort")); + } + item_list.push_back(new (mem_root) + Item_string_sys(thd, extra_buf.ptr(), + extra_buf.length()), + mem_root); + + //output->unit.offset_limit_cnt= 0; + if (output->send_data(item_list)) + return 1; + + /* + Print all subquery children (UNION children have already been printed at + the start of this function) + */ + return print_explain_for_children(query, output, explain_flags, is_analyze); +} + + +void Explain_union::print_explain_json(Explain_query *query, + Json_writer *writer, bool is_analyze) +{ + Json_writer_nesting_guard guard(writer); + char table_name_buffer[SAFE_NAME_LEN]; + + bool started_object= print_explain_json_cache(writer, is_analyze); + + writer->add_member("query_block").start_object(); + writer->add_member("union_result").start_object(); + // using_temporary_table + make_union_table_name(table_name_buffer); + writer->add_member("table_name").add_str(table_name_buffer); + writer->add_member("access_type").add_str("ALL"); // not very useful + + /* r_loops (not present in tabular output) */ + if (is_analyze) + { + writer->add_member("r_loops").add_ll(fake_select_lex_tracker.get_loops()); + } + + /* `r_rows` */ + if (is_analyze) + { + writer->add_member("r_rows"); + if (fake_select_lex_tracker.has_scans()) + writer->add_double(fake_select_lex_tracker.get_avg_rows()); + else + writer->add_null(); + } + + writer->add_member("query_specifications").start_array(); + + for (int i= 0; i < (int) union_members.elements(); i++) + { + writer->start_object(); + //writer->add_member("dependent").add_str("TODO"); + //writer->add_member("cacheable").add_str("TODO"); + Explain_select *sel= query->get_select(union_members.at(i)); + sel->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + writer->end_array(); + + print_explain_json_for_children(query, writer, is_analyze); + + writer->end_object(); // union_result + writer->end_object(); // query_block + + if (started_object) + writer->end_object(); +} + + +/* + Print EXPLAINs for all children nodes (i.e. for subqueries) +*/ + +int Explain_node::print_explain_for_children(Explain_query *query, + select_result_sink *output, + uint8 explain_flags, + bool is_analyze) +{ + for (int i= 0; i < (int) children.elements(); i++) + { + Explain_node *node= query->get_node(children.at(i)); + if (node->print_explain(query, output, explain_flags, is_analyze)) + return 1; + } + return 0; +} + +bool Explain_basic_join::add_table(Explain_table_access *tab, Explain_query *query) +{ + if (!join_tabs) + { + n_join_tabs= 0; + if (!(join_tabs= ((Explain_table_access**) + alloc_root(query->mem_root, + sizeof(Explain_table_access*) * + MAX_TABLES)))) + return true; + } + join_tabs[n_join_tabs++]= tab; + return false; +} + +/* + This tells whether a child subquery should be printed in JSON output. + + Derived tables and Non-merged semi-joins should not be printed, because they + are printed inline in Explain_table_access. +*/ +bool is_connection_printable_in_json(enum Explain_node::explain_connection_type type) +{ + return (type != Explain_node::EXPLAIN_NODE_DERIVED && + type != Explain_node::EXPLAIN_NODE_NON_MERGED_SJ); +} + + +void Explain_node::print_explain_json_for_children(Explain_query *query, + Json_writer *writer, + bool is_analyze) +{ + Json_writer_nesting_guard guard(writer); + + bool started= false; + for (int i= 0; i < (int) children.elements(); i++) + { + Explain_node *node= query->get_node(children.at(i)); + /* Derived tables are printed inside Explain_table_access objects */ + + if (!is_connection_printable_in_json(node->connection_type)) + continue; + + if (!started) + { + writer->add_member("subqueries").start_array(); + started= true; + } + + writer->start_object(); + node->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + + if (started) + writer->end_array(); +} + + +bool Explain_node::print_explain_json_cache(Json_writer *writer, + bool is_analyze) +{ + if (cache_tracker) + { + cache_tracker->fetch_current_stats(); + writer->add_member("expression_cache").start_object(); + if (cache_tracker->state != Expression_cache_tracker::OK) + { + writer->add_member("state"). + add_str(Expression_cache_tracker::state_str[cache_tracker->state]); + } + + if (is_analyze) + { + longlong cache_reads= cache_tracker->hit + cache_tracker->miss; + writer->add_member("r_loops").add_ll(cache_reads); + if (cache_reads != 0) + { + double hit_ratio= double(cache_tracker->hit) / cache_reads * 100.0; + writer->add_member("r_hit_ratio").add_double(hit_ratio); + } + } + return true; + } + return false; +} + + +void Explain_select::replace_table(uint idx, Explain_table_access *new_tab) +{ + delete join_tabs[idx]; + join_tabs[idx]= new_tab; +} + + +Explain_basic_join::~Explain_basic_join() +{ + if (join_tabs) + { + for (uint i= 0; i< n_join_tabs; i++) + delete join_tabs[i]; + } +} + + +int Explain_select::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags, bool is_analyze) +{ + THD *thd= output->thd; + MEM_ROOT *mem_root= thd->mem_root; + + if (message) + { + List<Item> item_list; + Item *item_null= new (mem_root) Item_null(thd); + + item_list.push_back(new (mem_root) Item_int(thd, (int32) select_id), + mem_root); + item_list.push_back(new (mem_root) Item_string_sys(thd, select_type), + mem_root); + for (uint i=0 ; i < 7; i++) + item_list.push_back(item_null, mem_root); + if (explain_flags & DESCRIBE_PARTITIONS) + item_list.push_back(item_null, mem_root); + + /* filtered */ + if (is_analyze || explain_flags & DESCRIBE_EXTENDED) + item_list.push_back(item_null, mem_root); + + if (is_analyze) + { + /* r_rows, r_filtered */ + item_list.push_back(item_null, mem_root); + item_list.push_back(item_null, mem_root); + } + + item_list.push_back(new (mem_root) Item_string_sys(thd, message), + mem_root); + + if (output->send_data(item_list)) + return 1; + } + else + { + bool using_tmp; + bool using_fs; + + if (is_analyze) + { + /* + Get the data about "Using temporary; Using filesort" from execution + tracking system. + */ + using_tmp= false; + using_fs= false; + Sort_and_group_tracker::Iterator iter(&ops_tracker); + enum_qep_action action; + Filesort_tracker *dummy; + + while ((action= iter.get_next(&dummy)) != EXPL_ACTION_EOF) + { + if (action == EXPL_ACTION_FILESORT) + using_fs= true; + else if (action == EXPL_ACTION_TEMPTABLE) + using_tmp= true; + } + } + else + { + /* Use imprecise "estimates" we got with the query plan */ + using_tmp= using_temporary; + using_fs= using_filesort; + } + + for (uint i=0; i< n_join_tabs; i++) + { + join_tabs[i]->print_explain(output, explain_flags, is_analyze, select_id, + select_type, using_tmp, using_fs); + if (i == 0) + { + /* + "Using temporary; Using filesort" should only be shown near the 1st + table + */ + using_tmp= false; + using_fs= false; + } + } + for (uint i=0; i< n_join_tabs; i++) + { + Explain_basic_join* nest; + if ((nest= join_tabs[i]->sjm_nest)) + nest->print_explain(query, output, explain_flags, is_analyze); + } + } + + return print_explain_for_children(query, output, explain_flags, is_analyze); +} + + +int Explain_basic_join::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags, bool is_analyze) +{ + for (uint i=0; i< n_join_tabs; i++) + { + if (join_tabs[i]->print_explain(output, explain_flags, is_analyze, + select_id, + "MATERIALIZED" /*select_type*/, + FALSE /*using temporary*/, + FALSE /*using filesort*/)) + return 1; + } + return 0; +} + + +void Explain_select::print_explain_json(Explain_query *query, + Json_writer *writer, bool is_analyze) +{ + Json_writer_nesting_guard guard(writer); + + bool started_cache= print_explain_json_cache(writer, is_analyze); + + if (message) + { + writer->add_member("query_block").start_object(); + writer->add_member("select_id").add_ll(select_id); + + writer->add_member("table").start_object(); + writer->add_member("message").add_str(message); + writer->end_object(); + + print_explain_json_for_children(query, writer, is_analyze); + writer->end_object(); + } + else + { + writer->add_member("query_block").start_object(); + writer->add_member("select_id").add_ll(select_id); + + if (is_analyze && time_tracker.get_loops()) + { + writer->add_member("r_loops").add_ll(time_tracker.get_loops()); + writer->add_member("r_total_time_ms").add_double(time_tracker.get_time_ms()); + } + + if (exec_const_cond) + { + writer->add_member("const_condition"); + write_item(writer, exec_const_cond); + } + /* we do not print HAVING which always evaluates to TRUE */ + if (having || (having_value == Item::COND_FALSE)) + { + writer->add_member("having_condition"); + if (likely(having)) + write_item(writer, having); + else + { + /* Normally we should not go this branch, left just for safety */ + DBUG_ASSERT(having_value == Item::COND_FALSE); + writer->add_str("0"); + } + } + + Filesort_tracker *first_table_sort= NULL; + bool first_table_sort_used= false; + int started_objects= 0; + + if (is_analyze) + { + /* ANALYZE has collected this part of query plan independently */ + if (ops_tracker.had_varied_executions()) + { + writer->add_member("varied-sort-and-tmp").start_object(); + started_objects++; + } + else + { + Sort_and_group_tracker::Iterator iter(&ops_tracker); + enum_qep_action action; + Filesort_tracker *fs_tracker= NULL; + + while ((action= iter.get_next(&fs_tracker)) != EXPL_ACTION_EOF) + { + if (action == EXPL_ACTION_FILESORT) + { + if (iter.is_last_element()) + { + first_table_sort= fs_tracker; + break; + } + writer->add_member("filesort").start_object(); + started_objects++; + fs_tracker->print_json_members(writer); + } + else if (action == EXPL_ACTION_TEMPTABLE) + { + writer->add_member("temporary_table").start_object(); + started_objects++; + /* + if (tmp == EXPL_TMP_TABLE_BUFFER) + func= "buffer"; + else if (tmp == EXPL_TMP_TABLE_GROUP) + func= "group-by"; + else + func= "distinct"; + writer->add_member("function").add_str(func); + */ + } + else if (action == EXPL_ACTION_REMOVE_DUPS) + { + writer->add_member("duplicate_removal").start_object(); + started_objects++; + } + else + DBUG_ASSERT(0); + } + } + + if (first_table_sort) + first_table_sort_used= true; + } + else + { + /* This is just EXPLAIN. Try to produce something meaningful */ + if (using_temporary) + { + started_objects= 1; + if (using_filesort) + { + started_objects++; + writer->add_member("filesort").start_object(); + } + writer->add_member("temporary_table").start_object(); + writer->add_member("function").add_str("buffer"); + } + else + { + if (using_filesort) + first_table_sort_used= true; + } + } + + Explain_basic_join::print_explain_json_interns(query, writer, is_analyze, + first_table_sort, + first_table_sort_used); + + for (;started_objects; started_objects--) + writer->end_object(); + + writer->end_object(); + } + + if (started_cache) + writer->end_object(); +} + + +void Explain_basic_join::print_explain_json(Explain_query *query, + Json_writer *writer, + bool is_analyze) +{ + writer->add_member("query_block").start_object(); + writer->add_member("select_id").add_ll(select_id); + + print_explain_json_interns(query, writer, is_analyze, NULL, false); + + writer->end_object(); +} + + +void Explain_basic_join:: +print_explain_json_interns(Explain_query *query, + Json_writer *writer, + bool is_analyze, + Filesort_tracker *first_table_sort, + bool first_table_sort_used) +{ + Json_writer_nesting_guard guard(writer); + for (uint i=0; i< n_join_tabs; i++) + { + if (join_tabs[i]->start_dups_weedout) + writer->add_member("duplicates_removal").start_object(); + + join_tabs[i]->print_explain_json(query, writer, is_analyze, + first_table_sort, + first_table_sort_used); + + first_table_sort= NULL; + first_table_sort_used= false; + + if (join_tabs[i]->end_dups_weedout) + writer->end_object(); + } + print_explain_json_for_children(query, writer, is_analyze); +} + + +void Explain_table_access::push_extra(enum explain_extra_tag extra_tag) +{ + extra_tags.append(extra_tag); +} + + +/* + Put the contents of 'key' field of EXPLAIN otuput into key_str. + + It is surprisingly complex: + - hash join shows #hash#used_key + - quick selects that use single index will print index name +*/ + +void Explain_table_access::fill_key_str(String *key_str, bool is_json) const +{ + CHARSET_INFO *cs= system_charset_info; + bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || + type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE); + const char *hash_key_prefix= "#hash#"; + + if (key.get_key_name()) + { + if (is_hj) + key_str->append(hash_key_prefix, strlen(hash_key_prefix), cs); + + key_str->append(key.get_key_name()); + + if (is_hj && type != JT_HASH) + key_str->append(':'); + } + + if (quick_info) + { + StringBuffer<64> buf2; + if (is_json) + quick_info->print_extra_recursive(&buf2); + else + quick_info->print_key(&buf2); + key_str->append(buf2); + } + if (type == JT_HASH_NEXT) + key_str->append(hash_next_key.get_key_name()); +} + + +/* + Fill "key_length". + - this is just used key length for ref/range + - for index_merge, it is a comma-separated list of lengths. + - for hash join, it is key_len:pseudo_key_len + + The column looks identical in tabular and json forms. In JSON, we consider + the column legacy, it is superceded by used_key_parts. +*/ + +void Explain_table_access::fill_key_len_str(String *key_len_str) const +{ + bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || + type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE); + if (key.get_key_len() != (uint)-1) + { + char buf[64]; + size_t length; + length= longlong10_to_str(key.get_key_len(), buf, 10) - buf; + key_len_str->append(buf, length); + if (is_hj && type != JT_HASH) + key_len_str->append(':'); + } + + if (quick_info) + { + StringBuffer<64> buf2; + quick_info->print_key_len(&buf2); + key_len_str->append(buf2); + } + + if (type == JT_HASH_NEXT) + { + char buf[64]; + size_t length; + length= longlong10_to_str(hash_next_key.get_key_len(), buf, 10) - buf; + key_len_str->append(buf, length); + } +} + + +void Explain_index_use::set(MEM_ROOT *mem_root, KEY *key, uint key_len_arg) +{ + set_pseudo_key(mem_root, key->name); + key_len= key_len_arg; + uint len= 0; + for (uint i= 0; i < key->usable_key_parts; i++) + { + key_parts_list.append_str(mem_root, key->key_part[i].field->field_name); + len += key->key_part[i].store_length; + if (len >= key_len_arg) + break; + } +} + + +void Explain_index_use::set_pseudo_key(MEM_ROOT *root, const char* key_name_arg) +{ + if (key_name_arg) + { + size_t name_len= strlen(key_name_arg); + if ((key_name= (char*)alloc_root(root, name_len+1))) + memcpy(key_name, key_name_arg, name_len+1); + } + else + key_name= NULL; + key_len= ~(uint) 0; +} + + +/* + Given r_filtered% from join buffer condition and join condition, produce a + combined r_filtered% number. This is needed for tabular EXPLAIN output which + has only one cell for r_filtered value. +*/ + +double Explain_table_access::get_r_filtered() +{ + double r_filtered= tracker.get_filtered_after_where(); + if (bka_type.is_using_jbuf()) + r_filtered *= jbuf_tracker.get_filtered_after_where(); + return r_filtered; +} + + +int Explain_table_access::print_explain(select_result_sink *output, uint8 explain_flags, + bool is_analyze, + uint select_id, const char *select_type, + bool using_temporary, bool using_filesort) +{ + THD *thd= output->thd; + MEM_ROOT *mem_root= thd->mem_root; + + List<Item> item_list; + Item *item_null= new (mem_root) Item_null(thd); + + /* `id` column */ + item_list.push_back(new (mem_root) Item_int(thd, (int32) select_id), + mem_root); + + /* `select_type` column */ + push_str(thd, &item_list, select_type); + + /* `table` column */ + push_string(thd, &item_list, &table_name); + + /* `partitions` column */ + if (explain_flags & DESCRIBE_PARTITIONS) + { + if (used_partitions_set) + { + push_string(thd, &item_list, &used_partitions); + } + else + item_list.push_back(item_null, mem_root); + } + + /* `type` column */ + push_str(thd, &item_list, join_type_str[type]); + + /* `possible_keys` column */ + StringBuffer<64> possible_keys_buf; + if (possible_keys.is_empty()) + item_list.push_back(item_null, mem_root); + else + push_string_list(thd, &item_list, possible_keys, &possible_keys_buf); + + /* `key` */ + StringBuffer<64> key_str; + fill_key_str(&key_str, false); + + if (key_str.length() > 0) + push_string(thd, &item_list, &key_str); + else + item_list.push_back(item_null, mem_root); + + /* `key_len` */ + StringBuffer<64> key_len_str; + fill_key_len_str(&key_len_str); + + if (key_len_str.length() > 0) + push_string(thd, &item_list, &key_len_str); + else + item_list.push_back(item_null, mem_root); + + /* `ref` */ + StringBuffer<64> ref_list_buf; + if (ref_list.is_empty()) + { + if (type == JT_FT) + { + /* Traditionally, EXPLAIN lines with type=fulltext have ref='' */ + push_str(thd, &item_list, ""); + } + else + item_list.push_back(item_null, mem_root); + } + else + push_string_list(thd, &item_list, ref_list, &ref_list_buf); + + /* `rows` */ + if (rows_set) + { + item_list.push_back(new (mem_root) + Item_int(thd, (longlong) (ulonglong) rows, + MY_INT64_NUM_DECIMAL_DIGITS), + mem_root); + } + else + item_list.push_back(item_null, mem_root); + + /* `r_rows` */ + if (is_analyze) + { + if (!tracker.has_scans()) + { + item_list.push_back(item_null, mem_root); + } + else + { + double avg_rows= tracker.get_avg_rows(); + item_list.push_back(new (mem_root) Item_float(thd, avg_rows, 2), + mem_root); + } + } + + /* `filtered` */ + if (explain_flags & DESCRIBE_EXTENDED || is_analyze) + { + if (filtered_set) + { + item_list.push_back(new (mem_root) Item_float(thd, filtered, 2), + mem_root); + } + else + item_list.push_back(item_null, mem_root); + } + + /* `r_filtered` */ + if (is_analyze) + { + if (!tracker.has_scans()) + { + item_list.push_back(item_null, mem_root); + } + else + { + double r_filtered= tracker.get_filtered_after_where(); + if (bka_type.is_using_jbuf()) + r_filtered *= jbuf_tracker.get_filtered_after_where(); + item_list.push_back(new (mem_root) + Item_float(thd, r_filtered * 100.0, 2), + mem_root); + } + } + + /* `Extra` */ + StringBuffer<256> extra_buf; + bool first= true; + for (int i=0; i < (int)extra_tags.elements(); i++) + { + if (first) + first= false; + else + extra_buf.append(STRING_WITH_LEN("; ")); + append_tag_name(&extra_buf, extra_tags.at(i)); + } + + if (using_temporary) + { + if (first) + first= false; + else + extra_buf.append(STRING_WITH_LEN("; ")); + extra_buf.append(STRING_WITH_LEN("Using temporary")); + } + + if (using_filesort) + { + if (first) + first= false; + else + extra_buf.append(STRING_WITH_LEN("; ")); + extra_buf.append(STRING_WITH_LEN("Using filesort")); + } + + item_list.push_back(new (mem_root) + Item_string_sys(thd, extra_buf.ptr(), + extra_buf.length()), + mem_root); + + if (output->send_data(item_list)) + return 1; + + return 0; +} + + +/** + Adds copy of the string to the list + + @param mem_root where to allocate string + @param str string to copy and add + + @return + NULL - out of memory error + poiner on allocated copy of the string +*/ + +const char *String_list::append_str(MEM_ROOT *mem_root, const char *str) +{ + size_t len= strlen(str); + char *cp; + if (!(cp = (char*)alloc_root(mem_root, len+1))) + return NULL; + memcpy(cp, str, len+1); + push_back(cp, mem_root); + return cp; +} + + +static void write_item(Json_writer *writer, Item *item) +{ + THD *thd= current_thd; + char item_buf[256]; + String str(item_buf, sizeof(item_buf), &my_charset_bin); + str.length(0); + + ulonglong save_option_bits= thd->variables.option_bits; + thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE; + + item->print(&str, QT_EXPLAIN); + + thd->variables.option_bits= save_option_bits; + writer->add_str(str.c_ptr_safe()); +} + + +void Explain_table_access::tag_to_json(Json_writer *writer, enum explain_extra_tag tag) +{ + switch (tag) + { + case ET_OPEN_FULL_TABLE: + writer->add_member("open_full_table").add_bool(true); + break; + case ET_SCANNED_0_DATABASES: + writer->add_member("scanned_databases").add_ll(0); + break; + case ET_SCANNED_1_DATABASE: + writer->add_member("scanned_databases").add_ll(1); + break; + case ET_SCANNED_ALL_DATABASES: + writer->add_member("scanned_databases").add_str("all"); + break; + case ET_SKIP_OPEN_TABLE: + writer->add_member("skip_open_table").add_bool(true); + break; + case ET_OPEN_FRM_ONLY: + writer->add_member("open_frm_only").add_bool(true); + break; + case ET_USING_INDEX_CONDITION: + writer->add_member("index_condition"); + write_item(writer, pushed_index_cond); + break; + case ET_USING_INDEX_CONDITION_BKA: + writer->add_member("index_condition_bka"); + write_item(writer, pushed_index_cond); + break; + case ET_USING_WHERE: + { + /* + We are printing the condition that is checked when scanning this + table. + - when join buffer is used, it is cache_cond. + - in other cases, it is where_cond. + */ + Item *item= bka_type.is_using_jbuf()? cache_cond: where_cond; + if (item) + { + writer->add_member("attached_condition"); + write_item(writer, item); + } + } + break; + case ET_USING_INDEX: + writer->add_member("using_index").add_bool(true); + break; + case ET_USING: + // index merge: case ET_USING + break; + case ET_RANGE_CHECKED_FOR_EACH_RECORD: + /* Handled as range_checked_fer */ + case ET_USING_JOIN_BUFFER: + /* Do nothing. Join buffer is handled differently */ + case ET_START_TEMPORARY: + case ET_END_TEMPORARY: + /* Handled as "duplicates_removal: { ... } */ + case ET_FULL_SCAN_ON_NULL_KEY: + /* Handled in full_scan_on_null_key */ + break; + case ET_FIRST_MATCH: + writer->add_member("first_match").add_str(firstmatch_table_name.c_ptr()); + break; + case ET_LOOSESCAN: + writer->add_member("loose_scan").add_bool(true); + break; + case ET_USING_MRR: + writer->add_member("mrr_type").add_str(mrr_type.c_ptr()); + break; + case ET_USING_INDEX_FOR_GROUP_BY: + writer->add_member("using_index_for_group_by"); + if (loose_scan_is_scanning) + writer->add_str("scanning"); + else + writer->add_bool(true); + break; + + /*new:*/ + case ET_CONST_ROW_NOT_FOUND: + writer->add_member("const_row_not_found").add_bool(true); + break; + case ET_UNIQUE_ROW_NOT_FOUND: + /* + Currently, we never get here. All SELECTs that have + ET_UNIQUE_ROW_NOT_FOUND for a table are converted into degenerate + SELECTs with message="Impossible WHERE ...". + MySQL 5.6 has the same property. + I'm leaving the handling in just for the sake of covering all enum + members and safety. + */ + writer->add_member("unique_row_not_found").add_bool(true); + break; + case ET_IMPOSSIBLE_ON_CONDITION: + writer->add_member("impossible_on_condition").add_bool(true); + break; + case ET_USING_WHERE_WITH_PUSHED_CONDITION: + /* + It would be nice to print the pushed condition, but current Storage + Engine API doesn't provide any way to do that + */ + writer->add_member("pushed_condition").add_bool(true); + break; + + case ET_NOT_EXISTS: + writer->add_member("not_exists").add_bool(true); + break; + case ET_DISTINCT: + writer->add_member("distinct").add_bool(true); + break; + + default: + DBUG_ASSERT(0); + } +} + + +static +void add_json_keyset(Json_writer *writer, const char *elem_name, + String_list *keyset) +{ + if (!keyset->is_empty()) + print_json_array(writer, elem_name, *keyset); +} + +/* + @param fs_tracker Normally NULL. When not NULL, it means that the join tab + used filesort to pre-sort the data. Then, sorted data + was read and the rest of the join was executed. + + @note + EXPLAIN command will check whether fs_tracker is present, but it can't use + any value from fs_tracker (these are only valid for ANALYZE). +*/ + +void Explain_table_access::print_explain_json(Explain_query *query, + Json_writer *writer, + bool is_analyze, + Filesort_tracker *fs_tracker, + bool first_table_sort_used) +{ + Json_writer_nesting_guard guard(writer); + + if (first_table_sort_used) + { + /* filesort was invoked on this join tab before doing the join with the rest */ + writer->add_member("read_sorted_file").start_object(); + if (is_analyze) + { + writer->add_member("r_rows"); + /* + r_rows when reading filesort result. This can be less than the number + of rows produced by filesort due to NL-join having LIMIT. + */ + if (tracker.has_scans()) + writer->add_double(tracker.get_avg_rows()); + else + writer->add_null(); + + /* + r_filtered when reading filesort result. We should have checked the + WHERE while doing filesort but lets check just in case. + */ + if (tracker.has_scans() && tracker.get_filtered_after_where() < 1.0) + { + writer->add_member("r_filtered"); + writer->add_double(tracker.get_filtered_after_where()*100.0); + } + } + writer->add_member("filesort").start_object(); + if (is_analyze) + fs_tracker->print_json_members(writer); + } + + if (bka_type.is_using_jbuf()) + { + writer->add_member("block-nl-join").start_object(); + } + + if (range_checked_fer) + { + range_checked_fer->print_json(writer, is_analyze); + } + + if (full_scan_on_null_key) + writer->add_member("full-scan-on-null_key").start_object(); + + writer->add_member("table").start_object(); + + writer->add_member("table_name").add_str(table_name); + + if (used_partitions_set) + print_json_array(writer, "partitions", used_partitions_list); + + writer->add_member("access_type").add_str(join_type_str[type]); + + add_json_keyset(writer, "possible_keys", &possible_keys); + + /* `key` */ + /* For non-basic quick select, 'key' will not be present */ + if (!quick_info || quick_info->is_basic()) + { + StringBuffer<64> key_str; + fill_key_str(&key_str, true); + if (key_str.length()) + writer->add_member("key").add_str(key_str); + } + + /* `key_length` */ + StringBuffer<64> key_len_str; + fill_key_len_str(&key_len_str); + if (key_len_str.length()) + writer->add_member("key_length").add_str(key_len_str); + + /* `used_key_parts` */ + String_list *parts_list= NULL; + if (quick_info && quick_info->is_basic()) + parts_list= &quick_info->range.key_parts_list; + else + parts_list= &key.key_parts_list; + + if (parts_list && !parts_list->is_empty()) + print_json_array(writer, "used_key_parts", *parts_list); + + if (quick_info && !quick_info->is_basic()) + { + writer->add_member("index_merge").start_object(); + quick_info->print_json(writer); + writer->end_object(); + } + + /* `ref` */ + if (!ref_list.is_empty()) + print_json_array(writer, "ref", ref_list); + + /* r_loops (not present in tabular output) */ + if (is_analyze) + { + writer->add_member("r_loops").add_ll(tracker.get_loops()); + } + + /* `rows` */ + if (rows_set) + writer->add_member("rows").add_ll(rows); + + /* `r_rows` */ + if (is_analyze) + { + writer->add_member("r_rows"); + if (fs_tracker) + { + /* Get r_rows value from filesort */ + if (fs_tracker->get_r_loops()) + writer->add_double(fs_tracker->get_avg_examined_rows()); + else + writer->add_null(); + } + else + { + if (tracker.has_scans()) + writer->add_double(tracker.get_avg_rows()); + else + writer->add_null(); + } + + if (op_tracker.get_loops()) + { + writer->add_member("r_total_time_ms"). + add_double(op_tracker.get_time_ms()); + } + } + + /* `filtered` */ + if (filtered_set) + writer->add_member("filtered").add_double(filtered); + + /* `r_filtered` */ + if (is_analyze) + { + writer->add_member("r_filtered"); + if (fs_tracker) + { + /* Get r_filtered value from filesort */ + if (fs_tracker->get_r_loops()) + writer->add_double(fs_tracker->get_r_filtered()*100); + else + writer->add_null(); + } + else + { + /* Get r_filtered from the NL-join runtime */ + if (tracker.has_scans()) + writer->add_double(tracker.get_filtered_after_where()*100.0); + else + writer->add_null(); + } + } + + for (int i=0; i < (int)extra_tags.elements(); i++) + { + tag_to_json(writer, extra_tags.at(i)); + } + + if (full_scan_on_null_key) + writer->end_object(); //"full-scan-on-null_key" + + if (range_checked_fer) + writer->end_object(); // "range-checked-for-each-record" + + if (bka_type.is_using_jbuf()) + { + writer->end_object(); // "block-nl-join" + writer->add_member("buffer_type").add_str(bka_type.incremental? + "incremental":"flat"); + writer->add_member("buffer_size").add_size(bka_type.join_buffer_size); + writer->add_member("join_type").add_str(bka_type.join_alg); + if (bka_type.mrr_type.length()) + writer->add_member("mrr_type").add_str(bka_type.mrr_type); + if (where_cond) + { + writer->add_member("attached_condition"); + write_item(writer, where_cond); + } + + if (is_analyze) + { + //writer->add_member("r_loops").add_ll(jbuf_tracker.get_loops()); + writer->add_member("r_filtered"); + if (jbuf_tracker.has_scans()) + writer->add_double(jbuf_tracker.get_filtered_after_where()*100.0); + else + writer->add_null(); + } + } + + if (derived_select_number) + { + /* This is a derived table. Print its contents here */ + writer->add_member("materialized").start_object(); + Explain_node *node= query->get_node(derived_select_number); + node->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + if (non_merged_sjm_number) + { + /* This is a non-merged semi-join table. Print its contents here */ + writer->add_member("materialized").start_object(); + writer->add_member("unique").add_ll(1); + Explain_node *node= query->get_node(non_merged_sjm_number); + node->connection_type= Explain_node::EXPLAIN_NODE_NON_MERGED_SJ; + node->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + if (sjm_nest) + { + /* This is a non-merged semi-join table. Print its contents here */ + writer->add_member("materialized").start_object(); + writer->add_member("unique").add_ll(1); + sjm_nest->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + + if (first_table_sort_used) + { + writer->end_object(); // filesort + writer->end_object(); // read_sorted_file + } + + writer->end_object(); +} + + +/* + Elements in this array match members of enum Extra_tag, defined in + sql_explain.h +*/ + +const char * extra_tag_text[]= +{ + "ET_none", + "Using index condition", + "Using index condition(BKA)", + "Using ", // special handling + "Range checked for each record (index map: 0x", // special handling + "Using where with pushed condition", + "Using where", + "Not exists", + + "Using index", + "Full scan on NULL key", + "Skip_open_table", + "Open_frm_only", + "Open_full_table", + + "Scanned 0 databases", + "Scanned 1 database", + "Scanned all databases", + + "Using index for group-by", // special handling + + "USING MRR: DONT PRINT ME", // special handling + + "Distinct", + "LooseScan", + "Start temporary", + "End temporary", + "FirstMatch", // special handling + + "Using join buffer", // special handling + + "const row not found", + "unique row not found", + "Impossible ON condition" +}; + + +void Explain_table_access::append_tag_name(String *str, enum explain_extra_tag tag) +{ + switch (tag) { + case ET_USING: + { + // quick select + str->append(STRING_WITH_LEN("Using ")); + quick_info->print_extra(str); + break; + } + case ET_RANGE_CHECKED_FOR_EACH_RECORD: + { + /* 4 bits per 1 hex digit + terminating '\0' */ + char buf[MAX_KEY / 4 + 1]; + str->append(STRING_WITH_LEN("Range checked for each " + "record (index map: 0x")); + str->append(range_checked_fer->keys_map.print(buf)); + str->append(')'); + break; + } + case ET_USING_MRR: + { + str->append(mrr_type); + break; + } + case ET_USING_JOIN_BUFFER: + { + str->append(extra_tag_text[tag]); + + str->append(STRING_WITH_LEN(" (")); + const char *buffer_type= bka_type.incremental ? "incremental" : "flat"; + str->append(buffer_type); + str->append(STRING_WITH_LEN(", ")); + str->append(bka_type.join_alg); + str->append(STRING_WITH_LEN(" join")); + str->append(STRING_WITH_LEN(")")); + if (bka_type.mrr_type.length()) + { + str->append(STRING_WITH_LEN("; ")); + str->append(bka_type.mrr_type); + } + + break; + } + case ET_FIRST_MATCH: + { + if (firstmatch_table_name.length()) + { + str->append("FirstMatch("); + str->append(firstmatch_table_name); + str->append(")"); + } + else + str->append(extra_tag_text[tag]); + break; + } + case ET_USING_INDEX_FOR_GROUP_BY: + { + str->append(extra_tag_text[tag]); + if (loose_scan_is_scanning) + str->append(" (scanning)"); + break; + } + default: + str->append(extra_tag_text[tag]); + } +} + + +/* + This is called for top-level Explain_quick_select only. The point of this + function is: + - index_merge should print $index_merge_type (child, ...) + - 'range' should not print anything. +*/ + +void Explain_quick_select::print_extra(String *str) +{ + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || + quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || + quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) + { + /* print nothing */ + } + else + print_extra_recursive(str); +} + +void Explain_quick_select::print_json(Json_writer *writer) +{ + if (is_basic()) + { + writer->add_member("range").start_object(); + + writer->add_member("key").add_str(range.get_key_name()); + + print_json_array(writer, "used_key_parts", range.key_parts_list); + + writer->end_object(); + } + else + { + writer->add_member(get_name_by_type()).start_object(); + + List_iterator_fast<Explain_quick_select> it (children); + Explain_quick_select* child; + while ((child = it++)) + child->print_json(writer); + + writer->end_object(); + } +} + +void Explain_quick_select::print_extra_recursive(String *str) +{ + if (is_basic()) + { + str->append(range.get_key_name()); + } + else + { + str->append(get_name_by_type()); + str->append('('); + List_iterator_fast<Explain_quick_select> it (children); + Explain_quick_select* child; + bool first= true; + while ((child = it++)) + { + if (first) + first= false; + else + str->append(','); + + child->print_extra_recursive(str); + } + str->append(')'); + } +} + + +const char * Explain_quick_select::get_name_by_type() +{ + switch (quick_type) { + case QUICK_SELECT_I::QS_TYPE_INDEX_MERGE: + return "sort_union"; + case QUICK_SELECT_I::QS_TYPE_ROR_UNION: + return "union"; + case QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT: + return "intersect"; + case QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT: + return "sort_intersect"; + default: + DBUG_ASSERT(0); + return "unknown quick select type"; + } +} + + +/* + This prints a comma-separated list of used indexes, ignoring nesting +*/ + +void Explain_quick_select::print_key(String *str) +{ + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || + quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || + quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) + { + if (str->length() > 0) + str->append(','); + str->append(range.get_key_name()); + } + else + { + List_iterator_fast<Explain_quick_select> it (children); + Explain_quick_select* child; + while ((child = it++)) + { + child->print_key(str); + } + } +} + + +/* + This prints a comma-separated list of used key_lengths, ignoring nesting +*/ + +void Explain_quick_select::print_key_len(String *str) +{ + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || + quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || + quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) + { + char buf[64]; + size_t length; + length= longlong10_to_str(range.get_key_len(), buf, 10) - buf; + if (str->length() > 0) + str->append(','); + str->append(buf, length); + } + else + { + List_iterator_fast<Explain_quick_select> it (children); + Explain_quick_select* child; + while ((child = it++)) + { + child->print_key_len(str); + } + } +} + + +int Explain_delete::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags, + bool is_analyze) +{ + if (deleting_all_rows) + { + const char *msg= STR_DELETING_ALL_ROWS; + int res= print_explain_message_line(output, explain_flags, is_analyze, + 1 /*select number*/, + select_type, &rows, msg); + return res; + + } + else + { + return Explain_update::print_explain(query, output, explain_flags, + is_analyze); + } +} + + +void Explain_delete::print_explain_json(Explain_query *query, + Json_writer *writer, + bool is_analyze) +{ + Json_writer_nesting_guard guard(writer); + + if (deleting_all_rows) + { + writer->add_member("query_block").start_object(); + writer->add_member("select_id").add_ll(1); + writer->add_member("table").start_object(); + // just like mysql-5.6, we don't print table name. Is this ok? + writer->add_member("message").add_str(STR_DELETING_ALL_ROWS); + writer->end_object(); // table + writer->end_object(); // query_block + return; + } + Explain_update::print_explain_json(query, writer, is_analyze); +} + + +int Explain_update::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags, + bool is_analyze) +{ + StringBuffer<64> key_buf; + StringBuffer<64> key_len_buf; + StringBuffer<64> extra_str; + if (impossible_where || no_partitions) + { + const char *msg= impossible_where ? + STR_IMPOSSIBLE_WHERE : + STR_NO_ROWS_AFTER_PRUNING; + int res= print_explain_message_line(output, explain_flags, is_analyze, + 1 /*select number*/, + select_type, + NULL, /* rows */ + msg); + return res; + } + + if (quick_info) + { + quick_info->print_key(&key_buf); + quick_info->print_key_len(&key_len_buf); + + StringBuffer<64> quick_buf; + quick_info->print_extra(&quick_buf); + if (quick_buf.length()) + { + extra_str.append(STRING_WITH_LEN("Using ")); + extra_str.append(quick_buf); + } + } + else if (key.get_key_name()) + { + const char *name= key.get_key_name(); + key_buf.set(name, strlen(name), &my_charset_bin); + char buf[64]; + size_t length= longlong10_to_str(key.get_key_len(), buf, 10) - buf; + key_len_buf.copy(buf, length, &my_charset_bin); + } + + if (using_where) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(STRING_WITH_LEN("Using where")); + } + + if (mrr_type.length() != 0) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(mrr_type); + } + + if (is_using_filesort()) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(STRING_WITH_LEN("Using filesort")); + } + + if (using_io_buffer) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(STRING_WITH_LEN("Using buffer")); + } + + /* + Single-table DELETE commands do not do "Using temporary". + "Using index condition" is also not possible (which is an unjustified limitation) + */ + double r_filtered= 100 * tracker.get_filtered_after_where(); + double r_rows= tracker.get_avg_rows(); + + print_explain_row(output, explain_flags, is_analyze, + 1, /* id */ + select_type, + table_name.c_ptr(), + used_partitions_set? used_partitions.c_ptr() : NULL, + jtype, + &possible_keys, + key_buf.length()? key_buf.c_ptr() : NULL, + key_len_buf.length() ? key_len_buf.c_ptr() : NULL, + NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ + &rows, + tracker.has_scans()? &r_rows : NULL, + r_filtered, + extra_str.c_ptr_safe()); + + return print_explain_for_children(query, output, explain_flags, is_analyze); +} + + +void Explain_update::print_explain_json(Explain_query *query, + Json_writer *writer, + bool is_analyze) +{ + Json_writer_nesting_guard guard(writer); + + writer->add_member("query_block").start_object(); + writer->add_member("select_id").add_ll(1); + + /* This is the total time it took to do the UPDATE/DELETE */ + if (is_analyze && command_tracker.get_loops()) + { + writer->add_member("r_total_time_ms"). + add_double(command_tracker.get_time_ms()); + } + + if (impossible_where || no_partitions) + { + const char *msg= impossible_where ? STR_IMPOSSIBLE_WHERE : + STR_NO_ROWS_AFTER_PRUNING; + writer->add_member("table").start_object(); + writer->add_member("message").add_str(msg); + writer->end_object(); // table + writer->end_object(); // query_block + return; + } + + DBUG_ASSERT(!(is_using_filesort() && using_io_buffer)); + + bool doing_buffering= false; + + if (is_using_filesort()) + { + writer->add_member("filesort").start_object(); + if (is_analyze) + filesort_tracker->print_json_members(writer); + doing_buffering= true; + } + + if (using_io_buffer) + { + writer->add_member("buffer").start_object(); + doing_buffering= true; + } + + /* Produce elements that are common for buffered and un-buffered cases */ + writer->add_member("table").start_object(); + + if (get_type() == EXPLAIN_UPDATE) + writer->add_member("update").add_ll(1); + else + writer->add_member("delete").add_ll(1); + + writer->add_member("table_name").add_str(table_name); + + if (used_partitions_set) + print_json_array(writer, "partitions", used_partitions_list); + + writer->add_member("access_type").add_str(join_type_str[jtype]); + + if (!possible_keys.is_empty()) + { + List_iterator_fast<char> it(possible_keys); + const char *name; + writer->add_member("possible_keys").start_array(); + while ((name= it++)) + writer->add_str(name); + writer->end_array(); + } + + /* `key`, `key_length` */ + if (quick_info && quick_info->is_basic()) + { + StringBuffer<64> key_buf; + StringBuffer<64> key_len_buf; + quick_info->print_extra_recursive(&key_buf); + quick_info->print_key_len(&key_len_buf); + + writer->add_member("key").add_str(key_buf); + writer->add_member("key_length").add_str(key_len_buf); + } + else if (key.get_key_name()) + { + writer->add_member("key").add_str(key.get_key_name()); + writer->add_member("key_length").add_str(key.get_key_len()); + } + + /* `used_key_parts` */ + String_list *parts_list= NULL; + if (quick_info && quick_info->is_basic()) + parts_list= &quick_info->range.key_parts_list; + else + parts_list= &key.key_parts_list; + + if (parts_list && !parts_list->is_empty()) + { + List_iterator_fast<char> it(*parts_list); + const char *name; + writer->add_member("used_key_parts").start_array(); + while ((name= it++)) + writer->add_str(name); + writer->end_array(); + } + + if (quick_info && !quick_info->is_basic()) + { + writer->add_member("index_merge").start_object(); + quick_info->print_json(writer); + writer->end_object(); + } + + /* `rows` */ + writer->add_member("rows").add_ll(rows); + + + if (mrr_type.length() != 0) + writer->add_member("mrr_type").add_str(mrr_type.ptr()); + + if (is_analyze) + { + if (doing_buffering) + { + ha_rows r_rows; + double r_filtered; + + if (is_using_filesort()) + { + if (filesort_tracker->get_r_loops()) + r_rows= (ha_rows) filesort_tracker->get_avg_examined_rows(); + else + r_rows= 0; + r_filtered= filesort_tracker->get_r_filtered() * 100.0; + } + else + { + if (buf_tracker.has_scans()) + r_rows= (ha_rows) buf_tracker.get_avg_rows(); + else + r_rows= 0; + r_filtered= buf_tracker.get_filtered_after_where() * 100.0; + } + writer->add_member("r_rows").add_ll(r_rows); + writer->add_member("r_filtered").add_double(r_filtered); + } + else /* Not doing buffering */ + { + writer->add_member("r_rows"); + if (tracker.has_scans()) + writer->add_double(tracker.get_avg_rows()); + else + writer->add_null(); + + /* There is no 'filtered' estimate in UPDATE/DELETE atm */ + double r_filtered= tracker.get_filtered_after_where() * 100.0; + writer->add_member("r_filtered").add_double(r_filtered); + } + + if (table_tracker.get_loops()) + { + writer->add_member("r_total_time_ms"). + add_double(table_tracker.get_time_ms()); + } + } + + if (where_cond) + { + writer->add_member("attached_condition"); + write_item(writer, where_cond); + } + + /*** The part of plan that is before the buffering/sorting ends here ***/ + if (is_using_filesort()) + writer->end_object(); + + if (using_io_buffer) + writer->end_object(); + + writer->end_object(); // table + + print_explain_json_for_children(query, writer, is_analyze); + writer->end_object(); // query_block +} + + +int Explain_insert::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags, + bool is_analyze) +{ + const char *select_type="INSERT"; + print_explain_row(output, explain_flags, is_analyze, + 1, /* id */ + select_type, + table_name.c_ptr(), + NULL, // partitions + JT_ALL, + NULL, // possible_keys + NULL, // key + NULL, // key_len + NULL, // ref + NULL, // rows + NULL, // r_rows + 100.0, // r_filtered + NULL); + + return print_explain_for_children(query, output, explain_flags, is_analyze); +} + +void Explain_insert::print_explain_json(Explain_query *query, + Json_writer *writer, bool is_analyze) +{ + Json_writer_nesting_guard guard(writer); + + writer->add_member("query_block").start_object(); + writer->add_member("select_id").add_ll(1); + writer->add_member("table").start_object(); + writer->add_member("table_name").add_str(table_name.c_ptr()); + writer->end_object(); // table + print_explain_json_for_children(query, writer, is_analyze); + writer->end_object(); // query_block +} + + +void delete_explain_query(LEX *lex) +{ + DBUG_ENTER("delete_explain_query"); + delete lex->explain; + lex->explain= NULL; + DBUG_VOID_RETURN; +} + + +void create_explain_query(LEX *lex, MEM_ROOT *mem_root) +{ + DBUG_ASSERT(!lex->explain); + DBUG_ENTER("create_explain_query"); + + lex->explain= new (mem_root) Explain_query(lex->thd, mem_root); + DBUG_ASSERT(mem_root == current_thd->mem_root); + + DBUG_VOID_RETURN; +} + +void create_explain_query_if_not_exists(LEX *lex, MEM_ROOT *mem_root) +{ + if (!lex->explain) + create_explain_query(lex, mem_root); +} + + +/** + Build arrays for collectiong keys statistics, sdd possible key names + to the list and name array + + @param alloc MEM_ROOT to put data in + @param list list of possible key names to fill + @param table table of the keys + @patam possible_keys possible keys map + + @retval 0 - OK + @retval 1 - Error +*/ + +int Explain_range_checked_fer::append_possible_keys_stat(MEM_ROOT *alloc, + TABLE *table, + key_map possible_keys) +{ + uint j; + multi_alloc_root(alloc, &keys_stat, sizeof(ha_rows) * table->s->keys, + &keys_stat_names, sizeof(char *) * table->s->keys, NULL); + if ((!keys_stat) || (!keys_stat_names)) + { + keys_stat= NULL; + keys_stat_names= NULL; + return 1; + } + keys_map= possible_keys; + keys= table->s->keys; + bzero(keys_stat, sizeof(ha_rows) * table->s->keys); + for (j= 0; j < table->s->keys; j++) + { + if (possible_keys.is_set(j)) + keys_stat_names[j]= key_set.append_str(alloc, table->key_info[j].name); + else + keys_stat_names[j]= NULL; + } + return 0; +} + +void Explain_range_checked_fer::collect_data(QUICK_SELECT_I *quick) +{ + if (quick) + { + if (quick->index == MAX_KEY) + index_merge++; + else + { + DBUG_ASSERT(quick->index < keys); + DBUG_ASSERT(keys_stat); + DBUG_ASSERT(keys_stat_names); + DBUG_ASSERT(keys_stat_names[ quick->index]); + keys_stat[quick->index]++; + } + } + else + full_scan++; +} + + +void Explain_range_checked_fer::print_json(Json_writer *writer, + bool is_analyze) +{ + writer->add_member("range-checked-for-each-record").start_object(); + add_json_keyset(writer, "keys", &key_set); + if (is_analyze) + { + writer->add_member("r_keys").start_object(); + writer->add_member("full_scan").add_ll(full_scan); + writer->add_member("index_merge").add_ll(index_merge); + if (keys_stat) + { + writer->add_member("range").start_object(); + for (uint i= 0; i < keys; i++) + { + if (keys_stat_names[i]) + { + writer->add_member(keys_stat_names[i]).add_ll(keys_stat[i]); + } + } + writer->end_object(); + } + writer->end_object(); + } +} + diff --git a/sql/sql_explain.h b/sql/sql_explain.h new file mode 100644 index 00000000000..caacf6b3a2f --- /dev/null +++ b/sql/sql_explain.h @@ -0,0 +1,880 @@ +/* + Copyright (c) 2013 Monty Program Ab + + 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 02111-1301 USA */ + +/* + +== EXPLAIN/ANALYZE architecture == + +=== [SHOW] EXPLAIN data === +Query optimization produces two data structures: +1. execution data structures themselves (eg. JOINs, JOIN_TAB, etc, etc) +2. Explain data structures. + +#2 are self contained set of data structures that has sufficient info to +produce output of SHOW EXPLAIN, EXPLAIN [FORMAT=JSON], or +ANALYZE [FORMAT=JSON], without accessing the execution data structures. + +(the only exception is that Explain data structures keep Item* pointers, +and we require that one might call item->print(QT_EXPLAIN) when printing +FORMAT=JSON output) + +=== ANALYZE data === +EXPLAIN data structures have embedded ANALYZE data structures. These are +objects that are used to track how the parts of query plan were executed: +how many times each part of query plan was invoked, how many rows were +read/returned, etc. + +Each execution data structure keeps a direct pointer to its ANALYZE data +structure. It is needed so that execution code can quickly increment the +counters. + +(note that this increases the set of data that is frequently accessed +during the execution. What is the impact of this?) + +Since ANALYZE/EXPLAIN data structures are separated from execution data +structures, it is easy to have them survive until the end of the query, +where we can return ANALYZE [FORMAT=JSON] output to the user, or print +it into the slow query log. + +*/ + +#ifndef SQL_EXPLAIN_INCLUDED +#define SQL_EXPLAIN_INCLUDED + +class String_list: public List<char> +{ +public: + const char *append_str(MEM_ROOT *mem_root, const char *str); +}; + +class Json_writer; + +/************************************************************************************** + + Data structures for producing EXPLAIN outputs. + + These structures + - Can be produced inexpensively from query plan. + - Store sufficient information to produce tabular EXPLAIN output (the goal is + to be able to produce JSON also) + +*************************************************************************************/ + + +const int FAKE_SELECT_LEX_ID= (int)UINT_MAX; + +class Explain_query; + +/* + A node can be either a SELECT, or a UNION. +*/ +class Explain_node : public Sql_alloc +{ +public: + Explain_node(MEM_ROOT *root) : + cache_tracker(NULL), + connection_type(EXPLAIN_NODE_OTHER), + children(root) + {} + /* A type specifying what kind of node this is */ + enum explain_node_type + { + EXPLAIN_UNION, + EXPLAIN_SELECT, + EXPLAIN_BASIC_JOIN, + EXPLAIN_UPDATE, + EXPLAIN_DELETE, + EXPLAIN_INSERT + }; + + /* How this node is connected */ + enum explain_connection_type { + EXPLAIN_NODE_OTHER, + EXPLAIN_NODE_DERIVED, /* Materialized derived table */ + EXPLAIN_NODE_NON_MERGED_SJ /* aka JTBM semi-join */ + }; + + virtual enum explain_node_type get_type()= 0; + virtual int get_select_id()= 0; + + /** + expression cache statistics + */ + Expression_cache_tracker* cache_tracker; + + /* + How this node is connected to its parent. + (NOTE: EXPLAIN_NODE_NON_MERGED_SJ is set very late currently) + */ + enum explain_connection_type connection_type; + + /* + A node may have children nodes. When a node's explain structure is + created, children nodes may not yet have QPFs. This is why we store ids. + */ + Dynamic_array<int> children; + void add_child(int select_no) + { + children.append(select_no); + } + + virtual int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze)=0; + virtual void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze)= 0; + + int print_explain_for_children(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze); + void print_explain_json_for_children(Explain_query *query, + Json_writer *writer, bool is_analyze); + bool print_explain_json_cache(Json_writer *writer, bool is_analyze); + virtual ~Explain_node(){} +}; + + +class Explain_table_access; + + +/* + A basic join. This is only used for SJ-Materialization nests. + + Basic join doesn't have ORDER/GROUP/DISTINCT operations. It also cannot be + degenerate. + + It has its own select_id. +*/ +class Explain_basic_join : public Explain_node +{ +public: + enum explain_node_type get_type() { return EXPLAIN_BASIC_JOIN; } + + Explain_basic_join(MEM_ROOT *root) : Explain_node(root), join_tabs(NULL) {} + ~Explain_basic_join(); + + bool add_table(Explain_table_access *tab, Explain_query *query); + + int get_select_id() { return select_id; } + + int select_id; + + int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); + + void print_explain_json_interns(Explain_query *query, Json_writer *writer, + bool is_analyze, + Filesort_tracker *first_table_sort, + bool first_table_sort_used); + + /* A flat array of Explain structs for tables. */ + Explain_table_access** join_tabs; + uint n_join_tabs; +}; + + +/* + EXPLAIN structure for a SELECT. + + A select can be: + 1. A degenerate case. In this case, message!=NULL, and it contains a + description of what kind of degenerate case it is (e.g. "Impossible + WHERE"). + 2. a non-degenrate join. In this case, join_tabs describes the join. + + In the non-degenerate case, a SELECT may have a GROUP BY/ORDER BY operation. + + In both cases, the select may have children nodes. class Explain_node + provides a way get node's children. +*/ + +class Explain_select : public Explain_basic_join +{ +public: + enum explain_node_type get_type() { return EXPLAIN_SELECT; } + + Explain_select(MEM_ROOT *root, bool is_analyze) : + Explain_basic_join(root), +#ifndef DBUG_OFF + select_lex(NULL), +#endif + message(NULL), + having(NULL), having_value(Item::COND_UNDEF), + using_temporary(false), using_filesort(false), + time_tracker(is_analyze), + ops_tracker(is_analyze) + {} + + /* + This is used to save the results of "late" test_if_skip_sort_order() calls + that are made from JOIN::exec + */ + void replace_table(uint idx, Explain_table_access *new_tab); + +public: +#ifndef DBUG_OFF + SELECT_LEX *select_lex; +#endif + const char *select_type; + + /* + If message != NULL, this is a degenerate join plan, and all subsequent + members have no info + */ + const char *message; + + /* Expensive constant condition */ + Item *exec_const_cond; + + /* HAVING condition */ + COND *having; + Item::cond_result having_value; + + /* Global join attributes. In tabular form, they are printed on the first row */ + bool using_temporary; + bool using_filesort; + + /* ANALYZE members */ + Time_and_counter_tracker time_tracker; + + Sort_and_group_tracker ops_tracker; + + int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); + + Table_access_tracker *get_using_temporary_read_tracker() + { + return &using_temporary_read_tracker; + } +private: + Table_access_tracker using_temporary_read_tracker; +}; + + +/* + Explain structure for a UNION. + + A UNION may or may not have "Using filesort". +*/ + +class Explain_union : public Explain_node +{ +public: + Explain_union(MEM_ROOT *root, bool is_analyze) : + Explain_node(root), + fake_select_lex_explain(root, is_analyze) + {} + + enum explain_node_type get_type() { return EXPLAIN_UNION; } + + int get_select_id() + { + DBUG_ASSERT(union_members.elements() > 0); + return union_members.at(0); + } + /* + Members of the UNION. Note: these are different from UNION's "children". + Example: + + (select * from t1) union + (select * from t2) order by (select col1 from t3 ...) + + here + - select-from-t1 and select-from-t2 are "union members", + - select-from-t3 is the only "child". + */ + Dynamic_array<int> union_members; + + void add_select(int select_no) + { + union_members.append(select_no); + } + int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); + + const char *fake_select_type; + bool using_filesort; + bool using_tmp; + + /* + Explain data structure for "fake_select_lex" (i.e. for the degenerate + SELECT that reads UNION result). + It doesn't have a query plan, but we still need execution tracker, etc. + */ + Explain_select fake_select_lex_explain; + + Table_access_tracker *get_fake_select_lex_tracker() + { + return &fake_select_lex_tracker; + } + Table_access_tracker *get_tmptable_read_tracker() + { + return &tmptable_read_tracker; + } +private: + uint make_union_table_name(char *buf); + + Table_access_tracker fake_select_lex_tracker; + /* This one is for reading after ORDER BY */ + Table_access_tracker tmptable_read_tracker; +}; + + +class Explain_update; +class Explain_delete; +class Explain_insert; + + +/* + Explain structure for a query (i.e. a statement). + + This should be able to survive when the query plan was deleted. Currently, + we do not intend for it survive until after query's MEM_ROOT is freed. It + does surivive freeing of query's items. + + For reference, the process of post-query cleanup is as follows: + + >dispatch_command + | >mysql_parse + | | ... + | | lex_end() + | | ... + | | >THD::cleanup_after_query + | | | ... + | | | free_items() + | | | ... + | | <THD::cleanup_after_query + | | + | <mysql_parse + | + | log_slow_statement() + | + | free_root() + | + >dispatch_command + + That is, the order of actions is: + - free query's Items + - write to slow query log + - free query's MEM_ROOT + +*/ + +class Explain_query : public Sql_alloc +{ +public: + Explain_query(THD *thd, MEM_ROOT *root); + ~Explain_query(); + + /* Add a new node */ + void add_node(Explain_node *node); + void add_insert_plan(Explain_insert *insert_plan_arg); + void add_upd_del_plan(Explain_update *upd_del_plan_arg); + + /* This will return a select, or a union */ + Explain_node *get_node(uint select_id); + + /* This will return a select (even if there is a union with this id) */ + Explain_select *get_select(uint select_id); + + Explain_union *get_union(uint select_id); + + /* Produce a tabular EXPLAIN output */ + int print_explain(select_result_sink *output, uint8 explain_flags, + bool is_analyze); + + /* Send tabular EXPLAIN to the client */ + int send_explain(THD *thd); + + /* Return tabular EXPLAIN output as a text string */ + bool print_explain_str(THD *thd, String *out_str, bool is_analyze); + + void print_explain_json(select_result_sink *output, bool is_analyze); + + /* If true, at least part of EXPLAIN can be printed */ + bool have_query_plan() { return insert_plan || upd_del_plan|| get_node(1) != NULL; } + + void query_plan_ready(); + + MEM_ROOT *mem_root; + + Explain_update *get_upd_del_plan() { return upd_del_plan; } +private: + /* Explain_delete inherits from Explain_update */ + Explain_update *upd_del_plan; + + /* Query "plan" for INSERTs */ + Explain_insert *insert_plan; + + Dynamic_array<Explain_union*> unions; + Dynamic_array<Explain_select*> selects; + + THD *thd; // for APC start/stop + bool apc_enabled; + /* + Debugging aid: count how many times add_node() was called. Ideally, it + should be one, we currently allow O(1) query plan saves for each + select or union. The goal is not to have O(#rows_in_some_table), which + is unacceptable. + */ + longlong operations; +}; + + +/* + Some of the tags have matching text. See extra_tag_text for text names, and + Explain_table_access::append_tag_name() for code to convert from tag form to text + form. +*/ +enum explain_extra_tag +{ + ET_none= 0, /* not-a-tag */ + ET_USING_INDEX_CONDITION, + ET_USING_INDEX_CONDITION_BKA, + ET_USING, /* For quick selects of various kinds */ + ET_RANGE_CHECKED_FOR_EACH_RECORD, + ET_USING_WHERE_WITH_PUSHED_CONDITION, + ET_USING_WHERE, + ET_NOT_EXISTS, + + ET_USING_INDEX, + ET_FULL_SCAN_ON_NULL_KEY, + ET_SKIP_OPEN_TABLE, + ET_OPEN_FRM_ONLY, + ET_OPEN_FULL_TABLE, + + ET_SCANNED_0_DATABASES, + ET_SCANNED_1_DATABASE, + ET_SCANNED_ALL_DATABASES, + + ET_USING_INDEX_FOR_GROUP_BY, + + ET_USING_MRR, // does not print "Using mrr". + + ET_DISTINCT, + ET_LOOSESCAN, + ET_START_TEMPORARY, + ET_END_TEMPORARY, + ET_FIRST_MATCH, + + ET_USING_JOIN_BUFFER, + + ET_CONST_ROW_NOT_FOUND, + ET_UNIQUE_ROW_NOT_FOUND, + ET_IMPOSSIBLE_ON_CONDITION, + + ET_total +}; + + +/* + Explain data structure describing join buffering use. +*/ + +class EXPLAIN_BKA_TYPE +{ +public: + EXPLAIN_BKA_TYPE() : join_alg(NULL) {} + + size_t join_buffer_size; + + bool incremental; + + /* + NULL if no join buferring used. + Other values: BNL, BNLH, BKA, BKAH. + */ + const char *join_alg; + + /* Information about MRR usage. */ + StringBuffer<64> mrr_type; + + bool is_using_jbuf() { return (join_alg != NULL); } +}; + + +/* + Data about how an index is used by some access method +*/ +class Explain_index_use : public Sql_alloc +{ + char *key_name; + uint key_len; +public: + String_list key_parts_list; + + Explain_index_use() + { + clear(); + } + + void clear() + { + key_name= NULL; + key_len= (uint)-1; + } + void set(MEM_ROOT *root, KEY *key_name, uint key_len_arg); + void set_pseudo_key(MEM_ROOT *root, const char *key_name); + + inline const char *get_key_name() const { return key_name; } + inline uint get_key_len() const { return key_len; } +}; + + +/* + QPF for quick range selects, as well as index_merge select +*/ +class Explain_quick_select : public Sql_alloc +{ +public: + Explain_quick_select(int quick_type_arg) : quick_type(quick_type_arg) + {} + + const int quick_type; + + bool is_basic() + { + return (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || + quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || + quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX); + } + + /* This is used when quick_type == QUICK_SELECT_I::QS_TYPE_RANGE */ + Explain_index_use range; + + /* Used in all other cases */ + List<Explain_quick_select> children; + + void print_extra(String *str); + void print_key(String *str); + void print_key_len(String *str); + + void print_json(Json_writer *writer); + + void print_extra_recursive(String *str); +private: + const char *get_name_by_type(); +}; + + +/* + Data structure for "range checked for each record". + It's a set of keys, tabular explain prints hex bitmap, json prints key names. +*/ + +typedef const char* NAME; + +class Explain_range_checked_fer : public Sql_alloc +{ +public: + String_list key_set; + key_map keys_map; +private: + ha_rows full_scan, index_merge; + ha_rows *keys_stat; + NAME *keys_stat_names; + uint keys; + +public: + Explain_range_checked_fer() + :Sql_alloc(), full_scan(0), index_merge(0), + keys_stat(0), keys_stat_names(0), keys(0) + {} + + int append_possible_keys_stat(MEM_ROOT *alloc, + TABLE *table, key_map possible_keys); + void collect_data(QUICK_SELECT_I *quick); + void print_json(Json_writer *writer, bool is_analyze); +}; + +/* + EXPLAIN data structure for a single JOIN_TAB. +*/ + +class Explain_table_access : public Sql_alloc +{ +public: + Explain_table_access(MEM_ROOT *root) : + derived_select_number(0), + non_merged_sjm_number(0), + extra_tags(root), + range_checked_fer(NULL), + full_scan_on_null_key(false), + start_dups_weedout(false), + end_dups_weedout(false), + where_cond(NULL), + cache_cond(NULL), + pushed_index_cond(NULL), + sjm_nest(NULL) + {} + ~Explain_table_access() { delete sjm_nest; } + + void push_extra(enum explain_extra_tag extra_tag); + + /* Internals */ + + /* id and 'select_type' are cared-of by the parent Explain_select */ + StringBuffer<32> table_name; + StringBuffer<32> used_partitions; + String_list used_partitions_list; + // valid with ET_USING_MRR + StringBuffer<32> mrr_type; + StringBuffer<32> firstmatch_table_name; + + /* + Non-zero number means this is a derived table. The number can be used to + find the query plan for the derived table + */ + int derived_select_number; + /* TODO: join with the previous member. */ + int non_merged_sjm_number; + + enum join_type type; + + bool used_partitions_set; + + /* Empty means "NULL" will be printed */ + String_list possible_keys; + + bool rows_set; /* not set means 'NULL' should be printed */ + bool filtered_set; /* not set means 'NULL' should be printed */ + // Valid if ET_USING_INDEX_FOR_GROUP_BY is present + bool loose_scan_is_scanning; + + /* + Index use: key name and length. + Note: that when one is accessing I_S tables, those may show use of + non-existant indexes. + + key.key_name == NULL means 'NULL' will be shown in tabular output. + key.key_len == (uint)-1 means 'NULL' will be shown in tabular output. + */ + Explain_index_use key; + + /* + when type==JT_HASH_NEXT, 'key' stores the hash join pseudo-key. + hash_next_key stores the table's key. + */ + Explain_index_use hash_next_key; + + String_list ref_list; + + ha_rows rows; + double filtered; + + /* + Contents of the 'Extra' column. Some are converted into strings, some have + parameters, values for which are stored below. + */ + Dynamic_array<enum explain_extra_tag> extra_tags; + + // Valid if ET_USING tag is present + Explain_quick_select *quick_info; + + /* Non-NULL value means this tab uses "range checked for each record" */ + Explain_range_checked_fer *range_checked_fer; + + bool full_scan_on_null_key; + + // valid with ET_USING_JOIN_BUFFER + EXPLAIN_BKA_TYPE bka_type; + + bool start_dups_weedout; + bool end_dups_weedout; + + /* + Note: lifespan of WHERE condition is less than lifespan of this object. + The below two are valid if tags include "ET_USING_WHERE". + (TODO: indexsubquery may put ET_USING_WHERE without setting where_cond?) + */ + Item *where_cond; + Item *cache_cond; + + /* + This is either pushed index condition, or BKA's index condition. + (the latter refers to columns of other tables and so can only be checked by + BKA code). Examine extra_tags to tell which one it is. + */ + Item *pushed_index_cond; + + Explain_basic_join *sjm_nest; + + /* ANALYZE members */ + + /* Tracker for reading the table */ + Table_access_tracker tracker; + Exec_time_tracker op_tracker; + Table_access_tracker jbuf_tracker; + + int print_explain(select_result_sink *output, uint8 explain_flags, + bool is_analyze, + uint select_id, const char *select_type, + bool using_temporary, bool using_filesort); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze, + Filesort_tracker *fs_tracker, + bool first_table_sort_used); + +private: + void append_tag_name(String *str, enum explain_extra_tag tag); + void fill_key_str(String *key_str, bool is_json) const; + void fill_key_len_str(String *key_len_str) const; + double get_r_filtered(); + void tag_to_json(Json_writer *writer, enum explain_extra_tag tag); +}; + + +/* + EXPLAIN structure for single-table UPDATE. + + This is similar to Explain_table_access, except that it is more restrictive. + Also, it can have UPDATE operation options, but currently there aren't any. + + Explain_delete inherits from this. +*/ + +class Explain_update : public Explain_node +{ +public: + + Explain_update(MEM_ROOT *root, bool is_analyze) : + Explain_node(root), + filesort_tracker(NULL), + command_tracker(is_analyze) + {} + + virtual enum explain_node_type get_type() { return EXPLAIN_UPDATE; } + virtual int get_select_id() { return 1; /* always root */ } + + const char *select_type; + + StringBuffer<32> used_partitions; + String_list used_partitions_list; + bool used_partitions_set; + + bool impossible_where; + bool no_partitions; + StringBuffer<64> table_name; + + enum join_type jtype; + String_list possible_keys; + + /* Used key when doing a full index scan (possibly with limit) */ + Explain_index_use key; + + /* + MRR that's used with quick select. This should probably belong to the + quick select + */ + StringBuffer<64> mrr_type; + + Explain_quick_select *quick_info; + + bool using_where; + Item *where_cond; + + ha_rows rows; + + bool using_io_buffer; + + /* Tracker for doing reads when filling the buffer */ + Table_access_tracker buf_tracker; + + bool is_using_filesort() { return filesort_tracker? true: false; } + /* + Non-null value of filesort_tracker means "using filesort" + + if we are using filesort, then table_tracker is for the io done inside + filesort. + + 'tracker' is for tracking post-filesort reads. + */ + Filesort_tracker *filesort_tracker; + + /* ANALYZE members and methods */ + Table_access_tracker tracker; + + /* This tracks execution of the whole command */ + Time_and_counter_tracker command_tracker; + + /* TODO: This tracks time to read rows from the table */ + Exec_time_tracker table_tracker; + + virtual int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze); + virtual void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); +}; + + +/* + EXPLAIN data structure for an INSERT. + + At the moment this doesn't do much as we don't really have any query plans + for INSERT statements. +*/ + +class Explain_insert : public Explain_node +{ +public: + Explain_insert(MEM_ROOT *root) : + Explain_node(root) + {} + + StringBuffer<64> table_name; + + enum explain_node_type get_type() { return EXPLAIN_INSERT; } + int get_select_id() { return 1; /* always root */ } + + int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); +}; + + +/* + EXPLAIN data of a single-table DELETE. +*/ + +class Explain_delete: public Explain_update +{ +public: + Explain_delete(MEM_ROOT *root, bool is_analyze) : + Explain_update(root, is_analyze) + {} + + /* + TRUE means we're going to call handler->delete_all_rows() and not read any + rows. + */ + bool deleting_all_rows; + + virtual enum explain_node_type get_type() { return EXPLAIN_DELETE; } + virtual int get_select_id() { return 1; /* always root */ } + + virtual int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze); + virtual void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); +}; + + +#endif //SQL_EXPLAIN_INCLUDED diff --git a/sql/sql_expression_cache.cc b/sql/sql_expression_cache.cc index f3c96ee2d2f..c79783bf561 100644 --- a/sql/sql_expression_cache.cc +++ b/sql/sql_expression_cache.cc @@ -11,8 +11,9 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include <my_global.h> #include "sql_base.h" #include "sql_select.h" #include "sql_expression_cache.h" @@ -42,7 +43,7 @@ ulong subquery_cache_miss, subquery_cache_hit; Expression_cache_tmptable::Expression_cache_tmptable(THD *thd, List<Item> &dependants, Item *value) - :cache_table(NULL), table_thd(thd), items(dependants), val(value), + :cache_table(NULL), table_thd(thd), tracker(NULL), items(dependants), val(value), hit(0), miss(0), inited (0) { DBUG_ENTER("Expression_cache_tmptable::Expression_cache_tmptable"); @@ -60,6 +61,9 @@ void Expression_cache_tmptable::disable_cache() cache_table->file->ha_index_end(); free_tmp_table(table_thd, cache_table); cache_table= NULL; + update_tracker(); + if (tracker) + tracker->cache= NULL; } @@ -157,12 +161,14 @@ void Expression_cache_tmptable::init() goto error; } - if (!(cached_result= new Item_field(cache_table->field[0]))) + if (!(cached_result= new (table_thd->mem_root) + Item_field(table_thd, cache_table->field[0]))) { DBUG_PRINT("error", ("Creating Item_field failed")); goto error; } + update_tracker(); DBUG_VOID_RETURN; error: @@ -179,6 +185,11 @@ Expression_cache_tmptable::~Expression_cache_tmptable() if (cache_table) disable_cache(); + else + { + update_tracker(); + tracker= NULL; + } } @@ -257,7 +268,7 @@ my_bool Expression_cache_tmptable::put_value(Item *value) } *(items.head_ref())= value; - fill_record(table_thd, cache_table->field, items, TRUE, TRUE); + fill_record(table_thd, cache_table, cache_table->field, items, TRUE, TRUE); if (table_thd->is_error()) goto err;; @@ -322,3 +333,7 @@ void Expression_cache_tmptable::print(String *str, enum_query_type query_type) } str->append('>'); } + + +const char *Expression_cache_tracker::state_str[3]= +{"uninitialized", "disabled", "enabled"}; diff --git a/sql/sql_expression_cache.h b/sql/sql_expression_cache.h index 48a8e33a787..05ac51f81f2 100644 --- a/sql/sql_expression_cache.h +++ b/sql/sql_expression_cache.h @@ -12,13 +12,14 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef SQL_EXPRESSION_CACHE_INCLUDED #define SQL_EXPRESSION_CACHE_INCLUDED #include "sql_select.h" + /** Interface for expression cache @@ -62,6 +63,11 @@ public: Initialize this cache */ virtual void init()= 0; + + /** + Save this object's statistics into Expression_cache_tracker object + */ + virtual void update_tracker()= 0; }; struct st_table_ref; @@ -69,6 +75,30 @@ struct st_join_table; class Item_field; +class Expression_cache_tracker :public Sql_alloc +{ +public: + enum expr_cache_state {UNINITED, STOPPED, OK}; + Expression_cache_tracker(Expression_cache *c) : + cache(c), hit(0), miss(0), state(UNINITED) + {} + + Expression_cache *cache; + ulong hit, miss; + enum expr_cache_state state; + + static const char* state_str[3]; + void set(ulong h, ulong m, enum expr_cache_state s) + {hit= h; miss= m; state= s;} + + void fetch_current_stats() + { + if (cache) + cache->update_tracker(); + } +}; + + /** Implementation of expression cache over a temporary table */ @@ -85,6 +115,22 @@ public: bool is_inited() { return inited; }; void init(); + void set_tracker(Expression_cache_tracker *st) + { + tracker= st; + update_tracker(); + } + virtual void update_tracker() + { + if (tracker) + { + tracker->set(hit, miss, (inited ? (cache_table ? + Expression_cache_tracker::OK : + Expression_cache_tracker::STOPPED) : + Expression_cache_tracker::UNINITED)); + } + } + private: void disable_cache(); @@ -94,6 +140,8 @@ private: TABLE *cache_table; /* Thread handle for the temporary table */ THD *table_thd; + /* EXPALIN/ANALYZE statistics */ + Expression_cache_tracker *tracker; /* TABLE_REF for index lookup */ struct st_table_ref ref; /* Cached result */ @@ -103,7 +151,7 @@ private: /* Value Item example */ Item *val; /* hit/miss counters */ - uint hit, miss; + ulong hit, miss; /* Set on if the object has been succesfully initialized with init() */ bool inited; }; diff --git a/sql/sql_get_diagnostics.cc b/sql/sql_get_diagnostics.cc new file mode 100644 index 00000000000..1713cb04ebc --- /dev/null +++ b/sql/sql_get_diagnostics.cc @@ -0,0 +1,342 @@ +/* Copyright (c) 2011, 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 02111-1307 USA */ + +#include "sql_list.h" // Sql_alloc, List, List_iterator +#include "sql_cmd.h" // Sql_cmd +#include "sql_class.h" // Diagnostics_area +#include "sql_get_diagnostics.h" // Sql_cmd_get_diagnostics + +/** + Execute this GET DIAGNOSTICS statement. + + @param thd The current thread. + + @remark Errors or warnings occurring during the execution of the GET + DIAGNOSTICS statement should not affect the diagnostics area + of a previous statement as the diagnostics information there + would be wiped out. Thus, in order to preserve the contents + of the diagnostics area from which information is being + retrieved, the GET DIAGNOSTICS statement is executed under + a separate diagnostics area. If any errors or warnings occur + during the execution of the GET DIAGNOSTICS statement, these + error or warnings (conditions) are appended to the list of + the original diagnostics area. The only exception to this is + fatal errors, which must always cause the statement to fail. + + @retval false on success. + @retval true on error +*/ + +bool +Sql_cmd_get_diagnostics::execute(THD *thd) +{ + bool rv; + Diagnostics_area new_stmt_da(thd->query_id, false, true); + Diagnostics_area *save_stmt_da= thd->get_stmt_da(); + DBUG_ENTER("Sql_cmd_get_diagnostics::execute"); + + /* Disable the unneeded read-only mode of the original DA. */ + save_stmt_da->set_warning_info_read_only(false); + + /* Set new diagnostics area, execute statement and restore. */ + thd->set_stmt_da(&new_stmt_da); + rv= m_info->aggregate(thd, save_stmt_da); + thd->set_stmt_da(save_stmt_da); + + /* Bail out early if statement succeeded. */ + if (! rv) + { + thd->get_stmt_da()->set_ok_status(0, 0, NULL); + DBUG_RETURN(false); + } + + /* Statement failed, retrieve the error information for propagation. */ + uint sql_errno= new_stmt_da.sql_errno(); + const char *message= new_stmt_da.message(); + const char *sqlstate= new_stmt_da.get_sqlstate(); + + /* In case of a fatal error, set it into the original DA.*/ + if (thd->is_fatal_error) + { + save_stmt_da->set_error_status(sql_errno, message, sqlstate, NULL); + DBUG_RETURN(true); + } + + /* Otherwise, just append the new error as a exception condition. */ + save_stmt_da->push_warning(thd, sql_errno, sqlstate, + Sql_condition::WARN_LEVEL_ERROR, + message); + + /* Appending might have failed. */ + if (! (rv= thd->is_error())) + thd->get_stmt_da()->set_ok_status(0, 0, NULL); + + DBUG_RETURN(rv); +} + + +/** + Set a value for this item. + + @param thd The current thread. + @param value The obtained value. + + @retval false on success. + @retval true on error. +*/ + +bool +Diagnostics_information_item::set_value(THD *thd, Item **value) +{ + bool rv; + Settable_routine_parameter *srp; + DBUG_ENTER("Diagnostics_information_item::set_value"); + + /* Get a settable reference to the target. */ + srp= m_target->get_settable_routine_parameter(); + + DBUG_ASSERT(srp); + + /* Set variable/parameter value. */ + rv= srp->set_value(thd, thd->spcont, value); + + DBUG_RETURN(rv); +} + + +/** + Obtain statement information in the context of a given diagnostics area. + + @param thd The current thread. + @param da The diagnostics area. + + @retval false on success. + @retval true on error +*/ + +bool +Statement_information::aggregate(THD *thd, const Diagnostics_area *da) +{ + bool rv= false; + Statement_information_item *stmt_info_item; + List_iterator<Statement_information_item> it(*m_items); + DBUG_ENTER("Statement_information::aggregate"); + + /* + Each specified target gets the value of each given + information item obtained from the diagnostics area. + */ + while ((stmt_info_item= it++)) + { + if ((rv= evaluate(thd, stmt_info_item, da))) + break; + } + + DBUG_RETURN(rv); +} + + +/** + Obtain the value of this statement information item in the context of + a given diagnostics area. + + @param thd The current thread. + @param da The diagnostics area. + + @retval Item representing the value. + @retval NULL on error. +*/ + +Item * +Statement_information_item::get_value(THD *thd, const Diagnostics_area *da) +{ + Item *value= NULL; + DBUG_ENTER("Statement_information_item::get_value"); + + switch (m_name) + { + /* + The number of condition areas that have information. That is, + the number of errors and warnings within the diagnostics area. + */ + case NUMBER: + { + ulong count= da->cond_count(); + value= new (thd->mem_root) Item_uint(thd, count); + break; + } + /* + Number that shows how many rows were directly affected by + a data-change statement (INSERT, UPDATE, DELETE, MERGE, + REPLACE, LOAD). + */ + case ROW_COUNT: + value= new (thd->mem_root) Item_int(thd, thd->get_row_count_func()); + break; + } + + DBUG_RETURN(value); +} + + +/** + Obtain condition information in the context of a given diagnostics area. + + @param thd The current thread. + @param da The diagnostics area. + + @retval false on success. + @retval true on error +*/ + +bool +Condition_information::aggregate(THD *thd, const Diagnostics_area *da) +{ + bool rv= false; + longlong cond_number; + const Sql_condition *cond= NULL; + Condition_information_item *cond_info_item; + Diagnostics_area::Sql_condition_iterator it_conds= da->sql_conditions(); + List_iterator_fast<Condition_information_item> it_items(*m_items); + DBUG_ENTER("Condition_information::aggregate"); + + /* Prepare the expression for evaluation. */ + if (!m_cond_number_expr->fixed && + m_cond_number_expr->fix_fields(thd, &m_cond_number_expr)) + DBUG_RETURN(true); + + cond_number= m_cond_number_expr->val_int(); + + /* + Limit to the number of available conditions. Warning_info::warn_count() + is not used because it indicates the number of condition regardless of + @@max_error_count, which prevents conditions from being pushed, but not + counted. + */ + if (cond_number < 1 || (ulonglong) cond_number > da->cond_count()) + { + my_error(ER_DA_INVALID_CONDITION_NUMBER, MYF(0)); + DBUG_RETURN(true); + } + + /* Advance to the requested condition. */ + while (cond_number--) + cond= it_conds++; + + DBUG_ASSERT(cond); + + /* Evaluate the requested information in the context of the condition. */ + while ((cond_info_item= it_items++)) + { + if ((rv= evaluate(thd, cond_info_item, cond))) + break; + } + + DBUG_RETURN(rv); +} + + +/** + Create an UTF-8 string item to represent a condition item string. + + @remark The string might not have a associated charset. For example, + this can be the case if the server does not or fails to process + the error message file. + + @remark See "Design notes about Sql_condition::m_message_text." in sql_error.cc + + @return Pointer to an string item, NULL on failure. +*/ + +Item * +Condition_information_item::make_utf8_string_item(THD *thd, const String *str) +{ + /* Default is utf8 character set and utf8_general_ci collation. */ + CHARSET_INFO *to_cs= &my_charset_utf8_general_ci; + /* If a charset was not set, assume that no conversion is needed. */ + CHARSET_INFO *from_cs= str->charset() ? str->charset() : to_cs; + String tmp(str->ptr(), str->length(), from_cs); + /* If necessary, convert the string (ignoring errors), then copy it over. */ + uint conv_errors; + return new (thd->mem_root) Item_string(thd, &tmp, to_cs, &conv_errors, + DERIVATION_COERCIBLE, MY_REPERTOIRE_UNICODE30); +} + + +/** + Obtain the value of this condition information item in the context of + a given condition. + + @param thd The current thread. + @param da The diagnostics area. + + @retval Item representing the value. + @retval NULL on error. +*/ + +Item * +Condition_information_item::get_value(THD *thd, const Sql_condition *cond) +{ + String str; + Item *value= NULL; + DBUG_ENTER("Condition_information_item::get_value"); + + switch (m_name) + { + case CLASS_ORIGIN: + value= make_utf8_string_item(thd, &(cond->m_class_origin)); + break; + case SUBCLASS_ORIGIN: + value= make_utf8_string_item(thd, &(cond->m_subclass_origin)); + break; + case CONSTRAINT_CATALOG: + value= make_utf8_string_item(thd, &(cond->m_constraint_catalog)); + break; + case CONSTRAINT_SCHEMA: + value= make_utf8_string_item(thd, &(cond->m_constraint_schema)); + break; + case CONSTRAINT_NAME: + value= make_utf8_string_item(thd, &(cond->m_constraint_name)); + break; + case CATALOG_NAME: + value= make_utf8_string_item(thd, &(cond->m_catalog_name)); + break; + case SCHEMA_NAME: + value= make_utf8_string_item(thd, &(cond->m_schema_name)); + break; + case TABLE_NAME: + value= make_utf8_string_item(thd, &(cond->m_table_name)); + break; + case COLUMN_NAME: + value= make_utf8_string_item(thd, &(cond->m_column_name)); + break; + case CURSOR_NAME: + value= make_utf8_string_item(thd, &(cond->m_cursor_name)); + break; + case MESSAGE_TEXT: + value= make_utf8_string_item(thd, &(cond->m_message_text)); + break; + case MYSQL_ERRNO: + value= new (thd->mem_root) Item_uint(thd, cond->m_sql_errno); + break; + case RETURNED_SQLSTATE: + str.set_ascii(cond->get_sqlstate(), strlen(cond->get_sqlstate())); + value= make_utf8_string_item(thd, &str); + break; + } + + DBUG_RETURN(value); +} + diff --git a/sql/sql_get_diagnostics.h b/sql/sql_get_diagnostics.h new file mode 100644 index 00000000000..f34820757f5 --- /dev/null +++ b/sql/sql_get_diagnostics.h @@ -0,0 +1,318 @@ +/* Copyright (c) 2011, 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 02111-1307 USA */ + +#ifndef SQL_GET_DIAGNOSTICS_H +#define SQL_GET_DIAGNOSTICS_H + +/** Diagnostics information forward reference. */ +class Diagnostics_information; + + +/** + Sql_cmd_get_diagnostics represents a GET DIAGNOSTICS statement. + + The GET DIAGNOSTICS statement retrieves exception or completion + condition information from a diagnostics area, usually pertaining + to the last non-diagnostic SQL statement that was executed. +*/ +class Sql_cmd_get_diagnostics : public Sql_cmd +{ +public: + /** + Constructor, used to represent a GET DIAGNOSTICS statement. + + @param info Diagnostics information to be obtained. + */ + Sql_cmd_get_diagnostics(Diagnostics_information *info) + : m_info(info) + {} + + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_GET_DIAGNOSTICS; + } + + virtual bool execute(THD *thd); + +private: + /** The information to be obtained. */ + Diagnostics_information *m_info; +}; + + +/** + Represents the diagnostics information to be obtained. + + Diagnostic information is made available through statement + information and condition information items. +*/ +class Diagnostics_information : public Sql_alloc +{ +public: + /** + Which diagnostics area to access. + Only CURRENT is supported for now. + */ + enum Which_area + { + /** Access the first diagnostics area. */ + CURRENT_AREA + }; + + /** Set which diagnostics area to access. */ + void set_which_da(Which_area area) + { m_area= area; } + + /** Get which diagnostics area to access. */ + Which_area get_which_da(void) const + { return m_area; } + + /** + Aggregate diagnostics information. + + @param thd The current thread. + @param da The diagnostics area. + + @retval false on success. + @retval true on error + */ + virtual bool aggregate(THD *thd, const Diagnostics_area *da) = 0; + +protected: + /** + Diagnostics_information objects are allocated in thd->mem_root. + Do not rely on the destructor for any cleanup. + */ + virtual ~Diagnostics_information() + { + DBUG_ASSERT(false); + } + + /** + Evaluate a diagnostics information item in a specific context. + + @param thd The current thread. + @param diag_item The diagnostics information item. + @param ctx The context to evaluate the item. + + @retval false on success. + @retval true on error. + */ + template <typename Diag_item, typename Context> + bool evaluate(THD *thd, Diag_item *diag_item, Context ctx) + { + Item *value; + + /* Get this item's value. */ + if (! (value= diag_item->get_value(thd, ctx))) + return true; + + /* Set variable/parameter value. */ + return diag_item->set_value(thd, &value); + } + +private: + /** Which diagnostics area to access. */ + Which_area m_area; +}; + + +/** + A diagnostics information item. Used to associate a specific + diagnostics information item to a target variable. +*/ +class Diagnostics_information_item : public Sql_alloc +{ +public: + /** + Set a value for this item. + + @param thd The current thread. + @param value The obtained value. + + @retval false on success. + @retval true on error. + */ + bool set_value(THD *thd, Item **value); + +protected: + /** + Constructor, used to represent a diagnostics information item. + + @param target A target that gets the value of this item. + */ + Diagnostics_information_item(Item *target) + : m_target(target) + {} + + /** + Diagnostics_information_item objects are allocated in thd->mem_root. + Do not rely on the destructor for any cleanup. + */ + virtual ~Diagnostics_information_item() + { + DBUG_ASSERT(false); + } + +private: + /** The target variable that will receive the value of this item. */ + Item *m_target; +}; + + +/** + A statement information item. +*/ +class Statement_information_item : public Diagnostics_information_item +{ +public: + /** The name of a statement information item. */ + enum Name + { + NUMBER, + ROW_COUNT + }; + + /** + Constructor, used to represent a statement information item. + + @param name The name of this item. + @param target A target that gets the value of this item. + */ + Statement_information_item(Name name, Item *target) + : Diagnostics_information_item(target), m_name(name) + {} + + /** Obtain value of this statement information item. */ + Item *get_value(THD *thd, const Diagnostics_area *da); + +private: + /** The name of this statement information item. */ + Name m_name; +}; + + +/** + Statement information. + + @remark Provides information about the execution of a statement. +*/ +class Statement_information : public Diagnostics_information +{ +public: + /** + Constructor, used to represent the statement information of a + GET DIAGNOSTICS statement. + + @param items List of requested statement information items. + */ + Statement_information(List<Statement_information_item> *items) + : m_items(items) + {} + + /** Obtain statement information in the context of a diagnostics area. */ + bool aggregate(THD *thd, const Diagnostics_area *da); + +private: + /* List of statement information items. */ + List<Statement_information_item> *m_items; +}; + + +/** + A condition information item. +*/ +class Condition_information_item : public Diagnostics_information_item +{ +public: + /** + The name of a condition information item. + */ + enum Name + { + CLASS_ORIGIN, + SUBCLASS_ORIGIN, + CONSTRAINT_CATALOG, + CONSTRAINT_SCHEMA, + CONSTRAINT_NAME, + CATALOG_NAME, + SCHEMA_NAME, + TABLE_NAME, + COLUMN_NAME, + CURSOR_NAME, + MESSAGE_TEXT, + MYSQL_ERRNO, + RETURNED_SQLSTATE + }; + + /** + Constructor, used to represent a condition information item. + + @param name The name of this item. + @param target A target that gets the value of this item. + */ + Condition_information_item(Name name, Item *target) + : Diagnostics_information_item(target), m_name(name) + {} + + /** Obtain value of this condition information item. */ + Item *get_value(THD *thd, const Sql_condition *cond); + +private: + /** The name of this condition information item. */ + Name m_name; + + /** Create an string item to represent a condition item string. */ + Item *make_utf8_string_item(THD *thd, const String *str); +}; + + +/** + Condition information. + + @remark Provides information about conditions raised during the + execution of a statement. +*/ +class Condition_information : public Diagnostics_information +{ +public: + /** + Constructor, used to represent the condition information of a + GET DIAGNOSTICS statement. + + @param cond_number_expr Number that identifies the diagnostic condition. + @param items List of requested condition information items. + */ + Condition_information(Item *cond_number_expr, + List<Condition_information_item> *items) + : m_cond_number_expr(cond_number_expr), m_items(items) + {} + + /** Obtain condition information in the context of a diagnostics area. */ + bool aggregate(THD *thd, const Diagnostics_area *da); + +private: + /** + Number that identifies the diagnostic condition for which + information is to be obtained. + */ + Item *m_cond_number_expr; + + /** List of condition information items. */ + List<Condition_information_item> *m_items; +}; + +#endif + diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 778507ebc38..bc6119b9a9c 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -52,9 +52,9 @@ cursor points at the first record). */ +#include <my_global.h> #include "sql_priv.h" #include "sql_handler.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_base.h" // close_thread_tables #include "lock.h" // mysql_unlock_tables #include "key.h" // key_copy @@ -155,10 +155,11 @@ static void mysql_ha_close_table(SQL_HANDLER *handler) { THD *thd= handler->thd; TABLE *table= handler->table; + DBUG_ENTER("mysql_ha_close_table"); /* check if table was already closed */ if (!table) - return; + DBUG_VOID_RETURN; if (!table->s->tmp_table) { @@ -171,7 +172,7 @@ static void mysql_ha_close_table(SQL_HANDLER *handler) table->file->ha_index_or_rnd_end(); table->open_by_handler= 0; - (void) close_thread_table(thd, &table); + close_thread_table(thd, &table); thd->mdl_context.release_lock(handler->mdl_request.ticket); } else @@ -184,6 +185,7 @@ static void mysql_ha_close_table(SQL_HANDLER *handler) } my_free(handler->lock); handler->init(); + DBUG_VOID_RETURN; } /* @@ -294,7 +296,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen) open_ltable() or open_table() because we would like to be able to open a temporary table. */ - error= open_tables(thd, &tables, &counter, 0); + error= (open_temporary_tables(thd, tables) || + open_tables(thd, &tables, &counter, 0)); if (error) goto err; @@ -304,7 +307,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen) /* There can be only one table in '*tables'. */ if (! (table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) { - my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); + my_error(ER_ILLEGAL_HA, MYF(0), table->file->table_type(), + table->s->db.str, table->s->table_name.str); goto err; } @@ -323,7 +327,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen) /* copy data to sql_handler */ if (!(sql_handler= new SQL_HANDLER(thd))) goto err; - init_alloc_root(&sql_handler->mem_root, 1024, 0); + init_alloc_root(&sql_handler->mem_root, 1024, 0, MYF(MY_THREAD_SPECIFIC)); sql_handler->db.length= strlen(tables->db); sql_handler->table_name.length= strlen(tables->table_name); @@ -355,8 +359,6 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen) sql_handler->reset(); } sql_handler->table= table; - memcpy(&sql_handler->mdl_request, &tables->mdl_request, - sizeof(tables->mdl_request)); if (!(sql_handler->lock= get_lock_data(thd, &sql_handler->table, 1, GET_LOCK_STORE_LOCKS))) @@ -370,9 +372,12 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen) if (error) goto err; + sql_handler->mdl_request.move_from(tables->mdl_request); + /* Always read all columns */ table->read_set= &table->s->all_set; - table->vcol_set= &table->s->all_set; + if (table->vcol_set) + table->vcol_set= &table->s->all_set; /* Restore the state. */ thd->set_open_tables(backup_open_tables); @@ -398,9 +403,6 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen) */ table->open_by_handler= 1; - /* Safety, cleanup the pointer to satisfy MDL assertions. */ - tables->mdl_request.ticket= NULL; - if (! reopen) my_ok(thd); DBUG_PRINT("exit",("OK")); @@ -501,9 +503,9 @@ public: bool handle_condition(THD *thd, uint sql_errno, const char *sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR **cond_hdl); + Sql_condition **cond_hdl); bool need_reopen() const { return m_need_reopen; }; void init() { m_need_reopen= FALSE; }; @@ -522,9 +524,9 @@ Sql_handler_lock_error_handler:: handle_condition(THD *thd, uint sql_errno, const char *sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR **cond_hdl) + Sql_condition **cond_hdl) { *cond_hdl= NULL; if (sql_errno == ER_LOCK_ABORTED) @@ -639,9 +641,10 @@ mysql_ha_fix_cond_and_key(SQL_HANDLER *handler, key_part_map keypart_map; uint key_len; - if (key_expr->elements > keyinfo->key_parts) + if (key_expr->elements > keyinfo->user_defined_key_parts) { - my_error(ER_TOO_MANY_KEY_PARTS, MYF(0), keyinfo->key_parts); + my_error(ER_TOO_MANY_KEY_PARTS, MYF(0), + keyinfo->user_defined_key_parts); return 1; } for (keypart_map= key_len=0 ; (item=it_ke++) ; key_part++) @@ -904,7 +907,8 @@ retry: break; } default: - my_message(ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), MYF(0)); + my_error(ER_ILLEGAL_HA, MYF(0), table->file->table_type(), + table->s->db.str, table->s->table_name.str); goto err; } @@ -1112,8 +1116,6 @@ void mysql_ha_flush(THD *thd) SQL_HANDLER *hash_tables; DBUG_ENTER("mysql_ha_flush"); - mysql_mutex_assert_not_owner(&LOCK_open); - /* Don't try to flush open HANDLERs when we're working with system tables. The main MDL context is backed up and we can't @@ -1132,7 +1134,7 @@ void mysql_ha_flush(THD *thd) ((hash_tables->table->mdl_ticket && hash_tables->table->mdl_ticket->has_pending_conflicting_lock()) || (!hash_tables->table->s->tmp_table && - hash_tables->table->s->has_old_version()))) + hash_tables->table->s->tdc->flushed))) mysql_ha_close_table(hash_tables); } diff --git a/sql/sql_help.cc b/sql/sql_help.cc index 844810af0f4..d71a6caf813 100644 --- a/sql/sql_help.cc +++ b/sql/sql_help.cc @@ -13,6 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_help.h" @@ -92,7 +93,7 @@ static bool init_fields(THD *thd, TABLE_LIST *tables, for (; count-- ; find_fields++) { /* We have to use 'new' here as field will be re_linked on free */ - Item_field *field= new Item_field(context, + Item_field *field= new (thd->mem_root) Item_field(thd, context, "mysql", find_fields->table_name, find_fields->field_name); if (!(find_fields->field= find_field_in_tables(thd, field, tables, NULL, @@ -150,10 +151,10 @@ void memorize_variant_topic(THD *thd, TABLE *topics, int count, else { if (count == 1) - names->push_back(name); + names->push_back(name, thd->mem_root); String *new_name= new (thd->mem_root) String; get_field(mem_root,find_fields[help_topic_name].field,new_name); - names->push_back(new_name); + names->push_back(new_name, thd->mem_root); } DBUG_VOID_RETURN; } @@ -296,7 +297,7 @@ int get_topics_for_keyword(THD *thd, TABLE *topics, TABLE *relations, find_type(primary_key_name, &relations->s->keynames, FIND_TYPE_NO_PREFIX) - 1) < 0) { - my_message(ER_CORRUPT_HELP_DB, ER(ER_CORRUPT_HELP_DB), MYF(0)); + my_message(ER_CORRUPT_HELP_DB, ER_THD(thd, ER_CORRUPT_HELP_DB), MYF(0)); DBUG_RETURN(-1); } rtopic_id= find_fields[help_relation_help_topic_id].field; @@ -307,7 +308,7 @@ int get_topics_for_keyword(THD *thd, TABLE *topics, TABLE *relations, { if (topics->file->inited) topics->file->ha_index_end(); - my_message(ER_CORRUPT_HELP_DB, ER(ER_CORRUPT_HELP_DB), MYF(0)); + my_message(ER_CORRUPT_HELP_DB, ER_THD(thd, ER_CORRUPT_HELP_DB), MYF(0)); DBUG_RETURN(-1); } @@ -379,7 +380,7 @@ int search_categories(THD *thd, TABLE *categories, get_field(thd->mem_root,pfname,lname); if (++count == 1 && res_id) *res_id= (int16) pcat_id->val_int(); - names->push_back(lname); + names->push_back(lname, thd->mem_root); } end_read_record(&read_record_info); @@ -414,7 +415,7 @@ void get_all_items_for_category(THD *thd, TABLE *items, Field *pfname, continue; String *name= new (thd->mem_root) String(); get_field(thd->mem_root,pfname,name); - res->push_back(name); + res->push_back(name, thd->mem_root); } end_read_record(&read_record_info); @@ -448,11 +449,17 @@ void get_all_items_for_category(THD *thd, TABLE *items, Field *pfname, int send_answer_1(Protocol *protocol, String *s1, String *s2, String *s3) { + THD *thd= protocol->thd; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("send_answer_1"); + List<Item> field_list; - field_list.push_back(new Item_empty_string("name",64)); - field_list.push_back(new Item_empty_string("description",1000)); - field_list.push_back(new Item_empty_string("example",1000)); + field_list.push_back(new (mem_root) Item_empty_string(thd, "name", 64), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "description", 1000), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "example", 1000), + mem_root); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) @@ -491,13 +498,22 @@ int send_answer_1(Protocol *protocol, String *s1, String *s2, String *s3) int send_header_2(Protocol *protocol, bool for_category) { + THD *thd= protocol->thd; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("send_header_2"); List<Item> field_list; if (for_category) - field_list.push_back(new Item_empty_string("source_category_name",64)); - field_list.push_back(new Item_empty_string("name",64)); - field_list.push_back(new Item_empty_string("is_it_category",1)); - DBUG_RETURN(protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | + field_list.push_back(new (mem_root) + Item_empty_string(thd, "source_category_name", 64), + mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "name", 64), + mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "is_it_category", 1), + mem_root); + DBUG_RETURN(protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)); } @@ -624,10 +640,15 @@ SQL_SELECT *prepare_select_for_name(THD *thd, const char *mask, uint mlen, TABLE_LIST *tables, TABLE *table, Field *pfname, int *error) { - Item *cond= new Item_func_like(new Item_field(pfname), - new Item_string(mask,mlen,pfname->charset()), - new Item_string("\\",1,&my_charset_latin1), - FALSE); + MEM_ROOT *mem_root= thd->mem_root; + Item *cond= new (mem_root) + Item_func_like(thd, + new (mem_root) + Item_field(thd, pfname), + new (mem_root) Item_string(thd, mask, mlen, + pfname->charset()), + new (mem_root) Item_string_ascii(thd, "\\"), + FALSE); if (thd->is_fatal_error) return 0; // OOM return prepare_simple_select(thd, cond, table, error); @@ -646,7 +667,7 @@ SQL_SELECT *prepare_select_for_name(THD *thd, const char *mask, uint mlen, TRUE Error and send_error already commited */ -bool mysqld_help(THD *thd, const char *mask) +static bool mysqld_help_internal(THD *thd, const char *mask) { Protocol *protocol= thd->protocol; SQL_SELECT *select; @@ -762,11 +783,17 @@ bool mysqld_help(THD *thd, const char *mask) { Field *topic_cat_id= used_fields[help_topic_help_category_id].field; Item *cond_topic_by_cat= - new Item_func_equal(new Item_field(topic_cat_id), - new Item_int((int32)category_id)); + new (mem_root) + Item_func_equal(thd, + new (mem_root) + Item_field(thd, topic_cat_id), + new (mem_root) + Item_int(thd, (int32) category_id)); Item *cond_cat_by_cat= - new Item_func_equal(new Item_field(cat_cat_id), - new Item_int((int32)category_id)); + new (mem_root) + Item_func_equal(thd, + new (mem_root) Item_field(thd, cat_cat_id), + new (mem_root) Item_int(thd, (int32) category_id)); if (!(select= prepare_simple_select(thd, cond_topic_by_cat, tables[0].table, &error))) goto error; @@ -822,3 +849,12 @@ error2: DBUG_RETURN(TRUE); } + +bool mysqld_help(THD *thd, const char *mask) +{ + ulonglong sql_mode_backup= thd->variables.sql_mode; + thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH; + bool rc= mysqld_help_internal(thd, mask); + thd->variables.sql_mode= sql_mode_backup; + return rc; +} diff --git a/sql/sql_hset.h b/sql/sql_hset.h index f3a1467737f..dc3bd487ce5 100644 --- a/sql/sql_hset.h +++ b/sql/sql_hset.h @@ -23,19 +23,19 @@ A type-safe wrapper around mysys HASH. */ -template <typename T, my_hash_get_key K> +template <typename T> class Hash_set { public: - typedef T Value_type; enum { START_SIZE= 8 }; /** Constructs an empty hash. Does not allocate memory, it is done upon the first insert. Thus does not cause or return errors. */ - Hash_set() + Hash_set(uchar *(*K)(const T *, size_t *, my_bool)) { my_hash_clear(&m_hash); + m_hash.get_key= (my_hash_get_key)K; } /** Destroy the hash by freeing the buckets table. Does @@ -56,13 +56,19 @@ public: */ bool insert(T *value) { - my_hash_init_opt(&m_hash, &my_charset_bin, START_SIZE, 0, 0, K, 0, MYF(0)); + my_hash_init_opt(&m_hash, &my_charset_bin, START_SIZE, 0, 0, + m_hash.get_key, 0, MYF(0)); size_t key_len; - const uchar *key= K(reinterpret_cast<uchar*>(value), &key_len, FALSE); - if (my_hash_search(&m_hash, key, key_len) == NULL) - return my_hash_insert(&m_hash, reinterpret_cast<uchar *>(value)); + uchar *v= reinterpret_cast<uchar *>(value); + const uchar *key= m_hash.get_key(v, &key_len, FALSE); + if (find(key, key_len) == NULL) + return my_hash_insert(&m_hash, v); return FALSE; } + T *find(const void *key, size_t klen) const + { + return (T*)my_hash_search(&m_hash, reinterpret_cast<const uchar *>(key), klen); + } /** Is this hash set empty? */ bool is_empty() const { return m_hash.records == 0; } /** Returns the number of unique elements. */ diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index cc040508842..1745c6b4aaa 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -56,9 +56,8 @@ */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_insert.h" #include "sql_update.h" // compare_record #include "sql_base.h" // close_thread_tables @@ -114,9 +113,9 @@ static bool check_view_insertability(THD *thd, TABLE_LIST *view); @returns false if success. */ -bool check_view_single_update(List<Item> &fields, List<Item> *values, - TABLE_LIST *view, table_map *map, - bool insert) +static bool check_view_single_update(List<Item> &fields, List<Item> *values, + TABLE_LIST *view, table_map *map, + bool insert) { /* it is join view => we need to find the table for update */ List_iterator_fast<Item> it(fields); @@ -148,9 +147,11 @@ bool check_view_single_update(List<Item> &fields, List<Item> *values, if (view->check_single_table(&tbl, tables, view) || tbl == 0) goto error; + /* view->table should have been set in mysql_derived_merge_for_insert */ + DBUG_ASSERT(view->table); + /* - A buffer for the insert values was allocated for the merged view. - Use it. + Use buffer for the insert values that was allocated for the merged view. */ tbl->table->insert_values= view->table->insert_values; view->table= tbl->table; @@ -184,10 +185,6 @@ error: @param fields_and_values_from_different_maps If 'values' are allowed to refer to other tables than those of 'fields' @param map See check_view_single_update - NOTE - Clears TIMESTAMP_AUTO_SET_ON_INSERT from table->timestamp_field_type - or leaves it as is, depending on if timestamp should be updated or - not. @returns 0 if success, -1 if error */ @@ -199,11 +196,12 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, table_map *map) { TABLE *table= table_list->table; + DBUG_ENTER("check_insert_fields"); if (!table_list->single_table_updatable()) { my_error(ER_NON_INSERTABLE_TABLE, MYF(0), table_list->alias, "INSERT"); - return -1; + DBUG_RETURN(-1); } if (fields.elements == 0 && values.elements != 0) @@ -212,21 +210,19 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, { my_error(ER_VIEW_NO_INSERT_FIELD_LIST, MYF(0), table_list->view_db.str, table_list->view_name.str); - return -1; + DBUG_RETURN(-1); } if (values.elements != table->s->fields) { my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1L); - return -1; + DBUG_RETURN(-1); } #ifndef NO_EMBEDDED_ACCESS_CHECKS Field_iterator_table_ref field_it; field_it.set(table_list); if (check_grant_all_columns(thd, INSERT_ACL, &field_it)) - return -1; + DBUG_RETURN(-1); #endif - clear_timestamp_auto_bits(table->timestamp_field_type, - TIMESTAMP_AUTO_SET_ON_INSERT); /* No fields are provided so all fields must be provided in the values. Thus we set all bits in the write set. @@ -243,7 +239,7 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, if (fields.elements != values.elements) { my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1L); - return -1; + DBUG_RETURN(-1); } thd->dup_field= 0; @@ -269,7 +265,7 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, thd->lex->select_lex.no_wrap_view_item= FALSE; if (res) - return -1; + DBUG_RETURN(-1); if (table_list->is_view() && table_list->is_merged_derived()) { @@ -277,27 +273,17 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, fields_and_values_from_different_maps ? (List<Item>*) 0 : &values, table_list, map, true)) - return -1; + DBUG_RETURN(-1); table= table_list->table; } if (check_unique && thd->dup_field) { my_error(ER_FIELD_SPECIFIED_TWICE, MYF(0), thd->dup_field->field_name); - return -1; - } - if (table->timestamp_field) // Don't automaticly set timestamp if used - { - if (bitmap_is_set(table->write_set, - table->timestamp_field->field_index)) - clear_timestamp_auto_bits(table->timestamp_field_type, - TIMESTAMP_AUTO_SET_ON_INSERT); - else - { - bitmap_set_bit(table->write_set, - table->timestamp_field->field_index); - } + DBUG_RETURN(-1); } + if (table->default_field) + table->mark_default_fields_for_write(); } /* Mark virtual columns used in the insert statement */ if (table->vfield) @@ -312,10 +298,36 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, check_view_insertability(thd, table_list))) { my_error(ER_NON_INSERTABLE_TABLE, MYF(0), table_list->alias, "INSERT"); - return -1; + DBUG_RETURN(-1); } - return 0; + DBUG_RETURN(0); +} + +static bool has_no_default_value(THD *thd, Field *field, TABLE_LIST *table_list) +{ + if ((field->flags & NO_DEFAULT_VALUE_FLAG) && field->real_type() != MYSQL_TYPE_ENUM) + { + bool view= false; + if (table_list) + { + table_list= table_list->top_table(); + view= table_list->view != NULL; + } + if (view) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NO_DEFAULT_FOR_VIEW_FIELD, + ER_THD(thd, ER_NO_DEFAULT_FOR_VIEW_FIELD), + table_list->view_db.str, table_list->view_name.str); + } + else + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NO_DEFAULT_FOR_FIELD, + ER_THD(thd, ER_NO_DEFAULT_FOR_FIELD), field->field_name); + } + return thd->really_abort_on_warning(); + } + return false; } @@ -330,9 +342,9 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, refer to other tables than those of 'update_fields' @param map See check_view_single_update - NOTE - If the update fields include the timestamp field, - remove TIMESTAMP_AUTO_SET_ON_UPDATE from table->timestamp_field_type. + @note + If the update fields include an autoinc field, set the + table->next_number_field_updated flag. @returns 0 if success, -1 if error */ @@ -344,20 +356,7 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list, table_map *map) { TABLE *table= insert_table_list->table; - my_bool timestamp_mark; - my_bool autoinc_mark; - LINT_INIT(timestamp_mark); - LINT_INIT(autoinc_mark); - - if (table->timestamp_field) - { - /* - Unmark the timestamp field so that we can check if this is modified - by update_fields - */ - timestamp_mark= bitmap_test_and_clear(table->write_set, - table->timestamp_field->field_index); - } + my_bool UNINIT_VAR(autoinc_mark); table->next_number_field_updated= FALSE; @@ -384,17 +383,8 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list, insert_table_list, map, false)) return -1; - if (table->timestamp_field) - { - /* Don't set timestamp column if this is modified. */ - if (bitmap_is_set(table->write_set, - table->timestamp_field->field_index)) - clear_timestamp_auto_bits(table->timestamp_field_type, - TIMESTAMP_AUTO_SET_ON_UPDATE); - if (timestamp_mark) - bitmap_set_bit(table->write_set, - table->timestamp_field->field_index); - } + if (table->default_field) + table->mark_default_fields_for_write(); if (table->found_next_number_field) { @@ -410,48 +400,6 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list, return 0; } -/* - Prepare triggers for INSERT-like statement. - - SYNOPSIS - prepare_triggers_for_insert_stmt() - table Table to which insert will happen - - NOTE - Prepare triggers for INSERT-like statement by marking fields - used by triggers and inform handlers that batching of UPDATE/DELETE - cannot be done if there are BEFORE UPDATE/DELETE triggers. -*/ - -void prepare_triggers_for_insert_stmt(TABLE *table) -{ - if (table->triggers) - { - if (table->triggers->has_triggers(TRG_EVENT_DELETE, - TRG_ACTION_AFTER)) - { - /* - The table has AFTER DELETE triggers that might access to - subject table and therefore might need delete to be done - immediately. So we turn-off the batching. - */ - (void) table->file->extra(HA_EXTRA_DELETE_CANNOT_BATCH); - } - if (table->triggers->has_triggers(TRG_EVENT_UPDATE, - TRG_ACTION_AFTER)) - { - /* - The table has AFTER UPDATE triggers that might access to subject - table and therefore might need update to be done immediately. - So we turn-off the batching. - */ - (void) table->file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH); - } - } - table->mark_columns_needed_for_insert(); -} - - /** Upgrade table-level lock of INSERT statement to TL_WRITE if a more concurrent lock is infeasible for some reason. This is @@ -498,7 +446,8 @@ void upgrade_lock_type(THD *thd, thr_lock_type *lock_type, if (specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE) || thd->variables.max_insert_delayed_threads == 0 || thd->locked_tables_mode > LTM_LOCK_TABLES || - thd->lex->uses_stored_routines()) + thd->lex->uses_stored_routines() /*|| + thd->lex->describe*/) { *lock_type= TL_WRITE; return; @@ -574,6 +523,13 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) DBUG_ENTER("open_and_lock_for_insert_delayed"); #ifndef EMBEDDED_LIBRARY + /* INSERT DELAYED is not allowed in a read only transaction. */ + if (thd->tx_read_only) + { + my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0)); + DBUG_RETURN(true); + } + /* In order for the deadlock detector to be able to find any deadlocks caused by the handler thread waiting for GRL or this table, we acquire @@ -681,6 +637,36 @@ create_insert_stmt_from_insert_delayed(THD *thd, String *buf) } +static void save_insert_query_plan(THD* thd, TABLE_LIST *table_list) +{ + Explain_insert* explain= new (thd->mem_root) Explain_insert(thd->mem_root); + explain->table_name.append(table_list->table->alias); + + thd->lex->explain->add_insert_plan(explain); + + /* See Update_plan::updating_a_view for details */ + bool skip= MY_TEST(table_list->view); + + /* Save subquery children */ + for (SELECT_LEX_UNIT *unit= thd->lex->select_lex.first_inner_unit(); + unit; + unit= unit->next_unit()) + { + if (skip) + { + skip= false; + continue; + } + /* + Table elimination doesn't work for INSERTS, but let's still have this + here for consistency + */ + if (!(unit->item && unit->item->eliminated)) + explain->add_child(unit->first_select()->select_number); + } +} + + /** INSERT statement implementation @@ -697,10 +683,11 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, enum_duplicates duplic, bool ignore) { + bool retval= true; int error, res; bool transactional_table, joins_freed= FALSE; bool changed; - bool was_insert_delayed= (table_list->lock_type == TL_WRITE_DELAYED); + const bool was_insert_delayed= (table_list->lock_type == TL_WRITE_DELAYED); bool using_bulk_insert= 0; uint value_count; ulong counter = 1; @@ -724,6 +711,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, Item *unused_conds= 0; DBUG_ENTER("mysql_insert"); + create_explain_query(thd->lex, thd->mem_root); /* Upgrade lock type if the requested lock is incompatible with the current connection mode or table operation. @@ -764,22 +752,17 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, lock_type= table_list->lock_type; - thd_proc_info(thd, "init"); + THD_STAGE_INFO(thd, stage_init); thd->lex->used_tables=0; values= its++; value_count= values->elements; if (mysql_prepare_insert(thd, table_list, table, fields, values, update_fields, update_values, duplic, &unused_conds, - FALSE, - (fields.elements || !value_count || - table_list->view != 0), - !ignore && (thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES)))) + FALSE)) goto abort; - /* mysql_prepare_insert set table_list->table if it was not set */ + /* mysql_prepare_insert sets table_list->table if it was not set */ table= table_list->table; context= &thd->lex->select_lex.context; @@ -801,6 +784,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, */ table_list->next_local= 0; context->resolve_in_table_list_only(table_list); + switch_to_nullable_trigger_fields(*values, table); while ((values= its++)) { @@ -812,11 +796,23 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, } if (setup_fields(thd, 0, *values, MARK_COLUMNS_READ, 0, NULL, 0)) goto abort; + switch_to_nullable_trigger_fields(*values, table); } its.rewind (); /* Restore the current context. */ ctx_state.restore_state(context, table_list); + + if (thd->lex->unit.first_select()->optimize_unflattened_subqueries(false)) + { + goto abort; + } + save_insert_query_plan(thd, table_list); + if (thd->lex->describe) + { + retval= thd->lex->explain->send_explain(thd); + goto abort; + } /* Fill in the given fields and dump it to the table file @@ -841,15 +837,15 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, table->next_number_field=table->found_next_number_field; #ifdef HAVE_REPLICATION - if (thd->slave_thread && + if (thd->rgi_slave && (info.handle_duplicates == DUP_UPDATE) && (table->next_number_field != NULL) && - rpl_master_has_bug(&active_mi->rli, 24432, TRUE, NULL, NULL)) + rpl_master_has_bug(thd->rgi_slave->rli, 24432, TRUE, NULL, NULL)) goto abort; #endif error=0; - thd_proc_info(thd, "update"); + THD_STAGE_INFO(thd, stage_update); if (duplic == DUP_REPLACE && (!table->triggers || !table->triggers->has_delete_triggers())) table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); @@ -888,24 +884,59 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, } } - thd->abort_on_warning= (!ignore && (thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES))); + thd->abort_on_warning= !ignore && thd->is_strict_mode(); - prepare_triggers_for_insert_stmt(table); + table->prepare_triggers_for_insert_stmt_or_event(); + table->mark_columns_needed_for_insert(); + if (fields.elements || !value_count || table_list->view != 0) + { + if (table->triggers && + table->triggers->has_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE)) + { + /* BEFORE INSERT triggers exist, the check will be done later, per row */ + } + else if (check_that_all_fields_are_given_values(thd, table, table_list)) + { + error= 1; + goto values_loop_end; + } + } if (table_list->prepare_where(thd, 0, TRUE) || table_list->prepare_check_option(thd)) error= 1; + table->reset_default_fields(); + switch_to_nullable_trigger_fields(fields, table); + switch_to_nullable_trigger_fields(update_fields, table); + switch_to_nullable_trigger_fields(update_values, table); + + if (fields.elements || !value_count) + { + /* + There are possibly some default values: + INSERT INTO t1 (fields) VALUES ... + INSERT INTO t1 VALUES () + */ + if (table->validate_default_values_of_unset_fields(thd)) + { + error= 1; + goto values_loop_end; + } + } + while ((values= its++)) { if (fields.elements || !value_count) { + /* + There are possibly some default values: + INSERT INTO t1 (fields) VALUES ... + INSERT INTO t1 VALUES () + */ restore_record(table,s->default_values); // Get empty record - if (fill_record_n_invoke_before_triggers(thd, fields, *values, 0, - table->triggers, + if (fill_record_n_invoke_before_triggers(thd, table, fields, *values, 0, TRG_EVENT_INSERT)) { if (values_list.elements != 1 && ! thd->is_error()) @@ -924,6 +955,10 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, } else { + /* + No field list, all fields are set explicitly: + INSERT INTO t1 VALUES (values) + */ if (thd->lex->used_tables) // Column used in values() restore_record(table,s->default_values); // Get empty record else @@ -935,12 +970,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, be overwritten by fill_record() anyway (and fill_record() does not use default values in this case). */ -#ifdef HAVE_valgrind - if (table->file->ha_table_flags() && HA_RECORD_MUST_BE_CLEAN_ON_WRITE) - restore_record(table,s->default_values); // Get empty record - else -#endif - table->record[0][0]= share->default_values[0]; + table->record[0][0]= share->default_values[0]; /* Fix undefined null_bits. */ if (share->null_bytes > 1 && share->last_null_bit_pos) @@ -949,9 +979,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, share->default_values[share->null_bytes - 1]; } } - if (fill_record_n_invoke_before_triggers(thd, table->field, *values, 0, - table->triggers, - TRG_EVENT_INSERT)) + if (fill_record_n_invoke_before_triggers(thd, table, table->field_to_fill(), + *values, 0, TRG_EVENT_INSERT)) { if (values_list.elements != 1 && ! thd->is_error()) { @@ -962,6 +991,28 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, break; } } + if (table->default_field && table->update_default_fields()) + { + error= 1; + break; + } + /* + with triggers a field can get a value *conditionally*, so we have to repeat + has_no_default_value() check for every row + */ + if (table->triggers && + table->triggers->has_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE)) + { + for (Field **f=table->field ; *f ; f++) + { + if (!((*f)->flags & HAS_EXPLICIT_VALUE) && has_no_default_value(thd, *f, table_list)) + { + error= 1; + goto values_loop_end; + } + (*f)->flags &= ~HAS_EXPLICIT_VALUE; + } + } if ((res= table_list->view_check_option(thd, (values_list.elements == 1 ? @@ -978,7 +1029,9 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, if (lock_type == TL_WRITE_DELAYED) { LEX_STRING const st_query = { query, thd->query_length() }; + DEBUG_SYNC(thd, "before_write_delayed"); error=write_delayed(thd, table, duplic, st_query, ignore, log_on); + DEBUG_SYNC(thd, "after_write_delayed"); query=0; } else @@ -986,9 +1039,10 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, error=write_record(thd, table ,&info); if (error) break; - thd->warning_info->inc_current_row_for_warning(); + thd->get_stmt_da()->inc_current_row_for_warning(); } +values_loop_end: free_underlaid_joins(thd, &thd->lex->select_lex); joins_freed= TRUE; @@ -1035,12 +1089,14 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, if (thd->transaction.stmt.modified_non_trans_table) thd->transaction.all.modified_non_trans_table= TRUE; + thd->transaction.all.m_unsafe_rollback_flags|= + (thd->transaction.stmt.m_unsafe_rollback_flags & THD_TRANS::DID_WAIT); if (error <= 0 || thd->transaction.stmt.modified_non_trans_table || was_insert_delayed) { - if (mysql_bin_log.is_open()) + if(WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) { int errcode= 0; if (error <= 0) @@ -1100,7 +1156,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, DBUG_ASSERT(transactional_table || !changed || thd->transaction.stmt.modified_non_trans_table); } - thd_proc_info(thd, "end"); + THD_STAGE_INFO(thd, stage_end); /* We'll report to the client this id: - if the table contains an autoincrement column and we successfully @@ -1126,6 +1182,11 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, if (error) goto abort; + if (thd->lex->analyze_stmt) + { + retval= thd->lex->explain->send_explain(thd); + goto abort; + } if (values_list.elements == 1 && (!(thd->variables.option_bits & OPTION_WARNINGS) || !thd->cuted_fields)) { @@ -1140,14 +1201,14 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, ha_rows updated=((thd->client_capabilities & CLIENT_FOUND_ROWS) ? info.touched : info.updated); if (ignore) - sprintf(buff, ER(ER_INSERT_INFO), (ulong) info.records, + sprintf(buff, ER_THD(thd, ER_INSERT_INFO), (ulong) info.records, (lock_type == TL_WRITE_DELAYED) ? (ulong) 0 : (ulong) (info.records - info.copied), - (ulong) thd->warning_info->statement_warn_count()); + (long) thd->get_stmt_da()->current_statement_warn_count()); else - sprintf(buff, ER(ER_INSERT_INFO), (ulong) info.records, + sprintf(buff, ER_THD(thd, ER_INSERT_INFO), (ulong) info.records, (ulong) (info.deleted + updated), - (ulong) thd->warning_info->statement_warn_count()); + (long) thd->get_stmt_da()->current_statement_warn_count()); ::my_ok(thd, info.copied + info.deleted + updated, id, buff); } thd->abort_on_warning= 0; @@ -1166,10 +1227,11 @@ abort: #endif if (table != NULL) table->file->ha_release_auto_increment(); + if (!joins_freed) free_underlaid_joins(thd, &thd->lex->select_lex); thd->abort_on_warning= 0; - DBUG_RETURN(TRUE); + DBUG_RETURN(retval); } @@ -1213,7 +1275,7 @@ static bool check_view_insertability(THD * thd, TABLE_LIST *view) DBUG_ASSERT(view->table != 0 && view->field_translation != 0); - (void) bitmap_init(&used_fields, used_fields_buff, table->s->fields, 0); + (void) my_bitmap_init(&used_fields, used_fields_buff, table->s->fields, 0); bitmap_clear_all(&used_fields); view->contain_auto_increment= 0; @@ -1232,7 +1294,7 @@ static bool check_view_insertability(THD * thd, TABLE_LIST *view) } Item_field *field; /* simple SELECT list entry (field without expression) */ - if (!(field= trans->item->filed_for_view_update())) + if (!(field= trans->item->field_for_view_update())) { thd->mark_used_columns= save_mark_used_columns; DBUG_RETURN(TRUE); @@ -1360,10 +1422,6 @@ static void prepare_for_positional_update(TABLE *table, TABLE_LIST *tables) be taken from table_list->table) where Where clause (for insert ... select) select_insert TRUE if INSERT ... SELECT statement - check_fields TRUE if need to check that all INSERT fields are - given values. - abort_on_warning whether to report if some INSERT field is not - assigned as an error (TRUE) or as a warning (FALSE). TODO (in far future) In cases of: @@ -1383,9 +1441,8 @@ static void prepare_for_positional_update(TABLE *table, TABLE_LIST *tables) bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, TABLE *table, List<Item> &fields, List_item *values, List<Item> &update_fields, List<Item> &update_values, - enum_duplicates duplic, - COND **where, bool select_insert, - bool check_fields, bool abort_on_warning) + enum_duplicates duplic, COND **where, + bool select_insert) { SELECT_LEX *select_lex= &thd->lex->select_lex; Name_resolution_context *context= &select_lex->context; @@ -1456,19 +1513,8 @@ bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, check_insert_fields(thd, context->table_list, fields, *values, !insert_into_view, 0, &map)); - if (!res && check_fields) - { - bool saved_abort_on_warning= thd->abort_on_warning; - thd->abort_on_warning= abort_on_warning; - res= check_that_all_fields_are_given_values(thd, - table ? table : - context->table_list->table, - context->table_list); - thd->abort_on_warning= saved_abort_on_warning; - } - - if (!res) - res= setup_fields(thd, 0, update_values, MARK_COLUMNS_READ, 0, NULL, 0); + if (!res) + res= setup_fields(thd, 0, update_values, MARK_COLUMNS_READ, 0, NULL, 0); if (!res && duplic == DUP_UPDATE) { @@ -1504,7 +1550,8 @@ bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, { Item *fake_conds= 0; TABLE_LIST *duplicate; - if ((duplicate= unique_table(thd, table_list, table_list->next_global, 1))) + if ((duplicate= unique_table(thd, table_list, table_list->next_global, + CHECK_DUP_ALLOW_DIFFERENT_ALIAS))) { update_non_unique_table_error(table_list, "INSERT", duplicate); DBUG_RETURN(TRUE); @@ -1660,15 +1707,14 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) if (!key) { - if (!(key=(char*) my_safe_alloca(table->s->max_unique_length, - MAX_KEY_LENGTH))) + if (!(key=(char*) my_safe_alloca(table->s->max_unique_length))) { error=ENOMEM; goto err; } } key_copy((uchar*) key,table->record[0],table->key_info+key_nr,0); - key_part_map keypart_map= (1 << table->key_info[key_nr].key_parts) - 1; + key_part_map keypart_map= (1 << table->key_info[key_nr].user_defined_key_parts) - 1; if ((error= (table->file->ha_index_read_idx_map(table->record[1], key_nr, (uchar*) key, keypart_map, @@ -1686,15 +1732,42 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) DBUG_ASSERT(table->insert_values != NULL); store_record(table,insert_values); restore_record(table,record[1]); + + /* + in INSERT ... ON DUPLICATE KEY UPDATE the set of modified fields can + change per row. Thus, we have to do reset_default_fields() per row. + Twice (before insert and before update). + */ + table->reset_default_fields(); DBUG_ASSERT(info->update_fields->elements == info->update_values->elements); - if (fill_record_n_invoke_before_triggers(thd, *info->update_fields, + if (fill_record_n_invoke_before_triggers(thd, table, *info->update_fields, *info->update_values, info->ignore, - table->triggers, TRG_EVENT_UPDATE)) goto before_trg_err; + bool different_records= (!records_are_comparable(table) || + compare_record(table)); + /* + Default fields must be updated before checking view updateability. + This branch of INSERT is executed only when a UNIQUE key was violated + with the ON DUPLICATE KEY UPDATE option. In this case the INSERT + operation is transformed to an UPDATE, and the default fields must + be updated as if this is an UPDATE. + */ + if (different_records && table->default_field) + { + bool res; + enum_sql_command cmd= thd->lex->sql_command; + thd->lex->sql_command= SQLCOM_UPDATE; + res= table->update_default_fields(); + thd->lex->sql_command= cmd; + if (res) + goto err; + } + table->reset_default_fields(); + /* CHECK OPTION for VIEW ... ON DUPLICATE KEY UPDATE ... */ if (info->view && (res= info->view->view_check_option(current_thd, info->ignore)) == @@ -1705,7 +1778,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) table->file->restore_auto_increment(prev_insert_id); info->touched++; - if (!records_are_comparable(table) || compare_record(table)) + if (different_records) { if ((error=table->file->ha_update_row(table->record[1], table->record[0])) && @@ -1778,8 +1851,6 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) */ if (last_uniq_key(table,key_nr) && !table->file->referenced_by_foreign_key() && - (table->timestamp_field_type == TIMESTAMP_NO_AUTO_SET || - table->timestamp_field_type == TIMESTAMP_AUTO_SET_ON_BOTH) && (!table->triggers || !table->triggers->has_delete_triggers())) { if ((error=table->file->ha_update_row(table->record[1], @@ -1790,7 +1861,6 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) info->deleted++; else error= 0; - thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row); /* Since we pretend that we have done insert we should call its after triggers. @@ -1831,7 +1901,6 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) if (table->file->insert_id_for_cur_row == 0) table->file->insert_id_for_cur_row= insert_id_for_cur_row; - thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row); /* Restore column maps if they where replaced during an duplicate key problem. @@ -1862,7 +1931,7 @@ after_trg_n_copied_inc: ok_or_after_trg_err: if (key) - my_safe_afree(key,table->s->max_unique_length,MAX_KEY_LENGTH); + my_safe_afree(key,table->s->max_unique_length); if (!table->file->has_transactions()) thd->transaction.stmt.modified_non_trans_table= TRUE; DBUG_RETURN(trg_error); @@ -1874,7 +1943,7 @@ err: before_trg_err: table->file->restore_auto_increment(prev_insert_id); if (key) - my_safe_afree(key, table->s->max_unique_length, MAX_KEY_LENGTH); + my_safe_afree(key, table->s->max_unique_length); table->column_bitmaps_set(save_read_set, save_write_set); DBUG_RETURN(1); } @@ -1884,8 +1953,8 @@ before_trg_err: Check that all fields with arn't null_fields are used ******************************************************************************/ -int check_that_all_fields_are_given_values(THD *thd, TABLE *entry, - TABLE_LIST *table_list) + +int check_that_all_fields_are_given_values(THD *thd, TABLE *entry, TABLE_LIST *table_list) { int err= 0; MY_BITMAP *write_set= entry->write_set; @@ -1893,32 +1962,8 @@ int check_that_all_fields_are_given_values(THD *thd, TABLE *entry, for (Field **field=entry->field ; *field ; field++) { if (!bitmap_is_set(write_set, (*field)->field_index) && - ((*field)->flags & NO_DEFAULT_VALUE_FLAG) && - ((*field)->real_type() != MYSQL_TYPE_ENUM)) - { - bool view= FALSE; - if (table_list) - { - table_list= table_list->top_table(); - view= test(table_list->view); - } - if (view) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_NO_DEFAULT_FOR_VIEW_FIELD, - ER(ER_NO_DEFAULT_FOR_VIEW_FIELD), - table_list->view_db.str, - table_list->view_name.str); - } - else - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_NO_DEFAULT_FOR_FIELD, - ER(ER_NO_DEFAULT_FOR_FIELD), - (*field)->field_name); - } - err= 1; - } + has_no_default_value(thd, *field, table_list)) + err=1; } return thd->abort_on_warning ? err : 0; } @@ -1944,7 +1989,6 @@ public: ulonglong forced_insert_id; ulong auto_increment_increment; ulong auto_increment_offset; - timestamp_auto_set_type timestamp_field_type; LEX_STRING query; Time_zone *time_zone; @@ -1975,8 +2019,9 @@ public: TABLE *table; mysql_mutex_t mutex; mysql_cond_t cond, cond_client; - volatile uint tables_in_use,stacked_inserts; + uint tables_in_use, stacked_inserts; volatile bool status; + bool retry; /** When the handler thread starts, it clones a metadata lock ticket which protects against GRL and ticket for the table to be inserted. @@ -2001,14 +2046,14 @@ public: Delayed_insert(SELECT_LEX *current_select) :locks_in_memory(0), table(0),tables_in_use(0),stacked_inserts(0), - status(0), handler_thread_initialized(FALSE), group_count(0) + status(0), retry(0), handler_thread_initialized(FALSE), group_count(0) { DBUG_ENTER("Delayed_insert constructor"); thd.security_ctx->user=(char*) delayed_user; thd.security_ctx->host=(char*) my_localhost; strmake_buf(thd.security_ctx->priv_user, thd.security_ctx->user); thd.current_tablenr=0; - thd.command=COM_DELAYED_INSERT; + thd.set_command(COM_DELAYED_INSERT); thd.lex->current_select= current_select; thd.lex->sql_command= SQLCOM_INSERT; // For innodb::store_lock() /* @@ -2051,9 +2096,9 @@ public: thd.unlink(); // Must be unlinked under lock my_free(thd.query()); thd.security_ctx->user= thd.security_ctx->host=0; - thread_count--; delayed_insert_threads--; mysql_mutex_unlock(&LOCK_thread_count); + thread_safe_decrement32(&thread_count); mysql_cond_broadcast(&COND_thread_count); /* Tell main we are ready */ } @@ -2096,7 +2141,7 @@ I_List<Delayed_insert> delayed_threads; static Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list) { - thd_proc_info(thd, "waiting for delay_list"); + THD_STAGE_INFO(thd, stage_waiting_for_delay_list); mysql_mutex_lock(&LOCK_delayed_insert); // Protect master list I_List_iterator<Delayed_insert> it(delayed_threads); Delayed_insert *di; @@ -2178,7 +2223,7 @@ bool delayed_get_table(THD *thd, MDL_request *grl_protection_request, */ if (delayed_insert_threads >= thd->variables.max_insert_delayed_threads) DBUG_RETURN(0); - thd_proc_info(thd, "Creating delayed handler"); + THD_STAGE_INFO(thd, stage_creating_delayed_handler); mysql_mutex_lock(&LOCK_delayed_create); /* The first search above was done without LOCK_delayed_create. @@ -2188,9 +2233,9 @@ bool delayed_get_table(THD *thd, MDL_request *grl_protection_request, { if (!(di= new Delayed_insert(thd->lex->current_select))) goto end_create; - mysql_mutex_lock(&LOCK_thread_count); - thread_count++; - mysql_mutex_unlock(&LOCK_thread_count); + + thread_safe_increment32(&thread_count); + /* Annotating delayed inserts is not supported. */ @@ -2239,14 +2284,14 @@ bool delayed_get_table(THD *thd, MDL_request *grl_protection_request, handler thread has been properly initialized before exiting. Otherwise we risk doing clone_ticket() on a ticket that is no longer valid. */ - thd_proc_info(thd, "waiting for handler open"); + THD_STAGE_INFO(thd, stage_waiting_for_handler_open); while (!di->handler_thread_initialized || (!di->thd.killed && !di->table && !thd->killed)) { mysql_cond_wait(&di->cond_client, &di->mutex); } mysql_mutex_unlock(&di->mutex); - thd_proc_info(thd, "got old table"); + THD_STAGE_INFO(thd, stage_got_old_table); if (thd->killed) { di->unlock(); @@ -2254,7 +2299,7 @@ bool delayed_get_table(THD *thd, MDL_request *grl_protection_request, } if (di->thd.killed) { - if (di->thd.is_error()) + if (di->thd.is_error() && ! di->retry) { /* Copy the error message. Note that we don't treat fatal @@ -2263,7 +2308,8 @@ bool delayed_get_table(THD *thd, MDL_request *grl_protection_request, want to send "Server shutdown in progress" in the INSERT THREAD. */ - my_message(di->thd.stmt_da->sql_errno(), di->thd.stmt_da->message(), + my_message(di->thd.get_stmt_da()->sql_errno(), + di->thd.get_stmt_da()->message(), MYF(0)); } di->unlock(); @@ -2314,7 +2360,7 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) { my_ptrdiff_t adjust_ptrs; Field **field,**org_field, *found_next_number_field; - Field **UNINIT_VAR(vfield); + Field **UNINIT_VAR(vfield), **UNINIT_VAR(dfield_ptr); TABLE *copy; TABLE_SHARE *share; uchar *bitmap; @@ -2326,13 +2372,13 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) tables_in_use++; if (!thd.lock) // Table is not locked { - thd_proc_info(client_thd, "waiting for handler lock"); + THD_STAGE_INFO(client_thd, stage_waiting_for_handler_lock); mysql_cond_signal(&cond); // Tell handler to lock table while (!thd.killed && !thd.lock && ! client_thd->killed) { mysql_cond_wait(&cond_client, &mutex); } - thd_proc_info(client_thd, "got handler lock"); + THD_STAGE_INFO(client_thd, stage_got_handler_lock); if (client_thd->killed) goto error; if (thd.killed) @@ -2347,13 +2393,15 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) The thread could be killed with an error message if di->handle_inserts() or di->open_and_lock_table() fails. The thread could be killed without an error message if - killed using mysql_notify_thread_having_shared_lock() or + killed using THD::notify_shared_lock() or kill_delayed_threads_for_table(). */ if (!thd.is_error()) - my_message(ER_QUERY_INTERRUPTED, ER(ER_QUERY_INTERRUPTED), MYF(0)); + my_message(ER_QUERY_INTERRUPTED, ER_THD(&thd, ER_QUERY_INTERRUPTED), + MYF(0)); else - my_message(thd.stmt_da->sql_errno(), thd.stmt_da->message(), MYF(0)); + my_message(thd.get_stmt_da()->sql_errno(), + thd.get_stmt_da()->message(), MYF(0)); goto error; } } @@ -2366,7 +2414,7 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) bytes. Since the table copy is used for creating one record only, the other record buffers and alignment are unnecessary. */ - thd_proc_info(client_thd, "allocating local table"); + THD_STAGE_INFO(client_thd, stage_allocating_local_table); copy_tmp= (char*) client_thd->alloc(sizeof(*copy)+ (share->fields+1)*sizeof(Field**)+ share->reclength + @@ -2390,6 +2438,15 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) bitmap= (uchar*) (field + share->fields + 1); copy->record[0]= (bitmap + share->column_bitmap_size*3); memcpy((char*) copy->record[0], (char*) table->record[0], share->reclength); + if (share->default_fields) + { + copy->default_field= (Field**) client_thd->alloc((share->default_fields+1)* + sizeof(Field**)); + if (!copy->default_field) + goto error; + dfield_ptr= copy->default_field; + } + /* Ensure we don't use the table list of the original table */ copy->pos_in_table_list= 0; @@ -2404,17 +2461,30 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) found_next_number_field= table->found_next_number_field; for (org_field= table->field; *org_field; org_field++, field++) { - if (!(*field= (*org_field)->new_field(client_thd->mem_root, copy, 1))) + if (!(*field= (*org_field)->make_new_field(client_thd->mem_root, copy, + 1))) goto error; (*field)->orig_table= copy; // Remove connection (*field)->move_field_offset(adjust_ptrs); // Point at copy->record[0] if (*org_field == found_next_number_field) (*field)->table->found_next_number_field= *field; + if (share->default_fields && + ((*org_field)->has_insert_default_function() || + (*org_field)->has_update_default_function())) + { + /* Put the newly copied field into the set of default fields. */ + *dfield_ptr= *field; + (*dfield_ptr)->unireg_check= (*org_field)->unireg_check; + dfield_ptr++; + } } *field=0; if (share->vfields) { + if (!(copy->def_vcol_set= (MY_BITMAP*) alloc_root(client_thd->mem_root, + sizeof(MY_BITMAP)))) + goto error; copy->vfield= vfield; for (field= copy->field; *field; field++) { @@ -2434,15 +2504,8 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) *vfield= 0; } - /* Adjust timestamp */ - if (table->timestamp_field) - { - /* Restore offset as this may have been reset in handle_inserts */ - copy->timestamp_field= - (Field_timestamp*) copy->field[share->timestamp_field_offset]; - copy->timestamp_field->unireg_check= table->timestamp_field->unireg_check; - copy->timestamp_field_type= copy->timestamp_field->get_auto_set_type(); - } + if (share->default_fields) + *dfield_ptr= NULL; /* Adjust in_use for pointing to client thread */ copy->in_use= client_thd; @@ -2454,20 +2517,23 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) copy->def_read_set.bitmap= (my_bitmap_map*) bitmap; copy->def_write_set.bitmap= ((my_bitmap_map*) (bitmap + share->column_bitmap_size)); - copy->def_vcol_set.bitmap= ((my_bitmap_map*) - (bitmap + 2*share->column_bitmap_size)); + if (share->vfields) + { + my_bitmap_init(copy->def_vcol_set, + (my_bitmap_map*) (bitmap + 2*share->column_bitmap_size), + share->fields, FALSE); + copy->vcol_set= copy->def_vcol_set; + } copy->tmp_set.bitmap= 0; // To catch errors - bzero((char*) bitmap, share->column_bitmap_size*3); + bzero((char*) bitmap, share->column_bitmap_size * (share->vfields ? 3 : 2)); copy->read_set= ©->def_read_set; copy->write_set= ©->def_write_set; - copy->vcol_set= ©->def_vcol_set; DBUG_RETURN(copy); /* Got fatal error */ error: tables_in_use--; - status=1; mysql_cond_signal(&cond); // Inform thread about abort DBUG_RETURN(0); } @@ -2486,11 +2552,11 @@ int write_delayed(THD *thd, TABLE *table, enum_duplicates duplic, DBUG_PRINT("enter", ("query = '%s' length %lu", query.str, (ulong) query.length)); - thd_proc_info(thd, "waiting for handler insert"); + THD_STAGE_INFO(thd, stage_waiting_for_handler_insert); mysql_mutex_lock(&di->mutex); while (di->stacked_inserts >= delayed_queue_size && !thd->killed) mysql_cond_wait(&di->cond_client, &di->mutex); - thd_proc_info(thd, "storing row into queue"); + THD_STAGE_INFO(thd, stage_storing_row_into_queue); if (thd->killed) goto err; @@ -2515,7 +2581,9 @@ int write_delayed(THD *thd, TABLE *table, enum_duplicates duplic, goto err; } - if (!(row->record= (char*) my_malloc(table->s->reclength, MYF(MY_WME)))) + /* This can't be THREAD_SPECIFIC as it's freed in delayed thread */ + if (!(row->record= (char*) my_malloc(table->s->reclength, + MYF(MY_WME)))) goto err; memcpy(row->record, table->record[0], table->s->reclength); row->start_time= thd->start_time; @@ -2532,7 +2600,6 @@ int write_delayed(THD *thd, TABLE *table, enum_duplicates duplic, thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt; row->first_successful_insert_id_in_prev_stmt= thd->first_successful_insert_id_in_prev_stmt; - row->timestamp_field_type= table->timestamp_field_type; /* Add session variable timezone Time_zone object will not be freed even the thread is ended. @@ -2603,14 +2670,16 @@ static void end_delayed_insert(THD *thd) void kill_delayed_threads(void) { + DBUG_ENTER("kill_delayed_threads"); mysql_mutex_lock(&LOCK_delayed_insert); // For unlink from list I_List_iterator<Delayed_insert> it(delayed_threads); Delayed_insert *di; while ((di= it++)) { - di->thd.killed= KILL_CONNECTION; mysql_mutex_lock(&di->thd.LOCK_thd_data); + if (di->thd.killed < KILL_CONNECTION) + di->thd.set_killed(KILL_CONNECTION); if (di->thd.mysys_var) { mysql_mutex_lock(&di->thd.mysys_var->mutex); @@ -2631,6 +2700,7 @@ void kill_delayed_threads(void) mysql_mutex_unlock(&di->thd.LOCK_thd_data); } mysql_mutex_unlock(&LOCK_delayed_insert); // For unlink from list + DBUG_VOID_RETURN; } @@ -2708,13 +2778,20 @@ bool Delayed_insert::open_and_lock_table() /* Use special prelocking strategy to get ER_DELAYED_NOT_SUPPORTED error for tables with engines which don't support delayed inserts. + + We can't do auto-repair in insert delayed thread, as it would hang + when trying to an exclusive MDL_LOCK on the table during repair + as the connection thread has a SHARED_WRITE lock. */ if (!(table= open_n_lock_single_table(&thd, &table_list, TL_WRITE_DELAYED, - MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK, + MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK | + MYSQL_OPEN_IGNORE_REPAIR, &prelocking_strategy))) { - thd.fatal_error(); // Abort waiting inserts + /* If table was crashed, then upper level should retry open+repair */ + retry= table_list.crashed; + thd.fatal_error(); // Abort waiting inserts return TRUE; } @@ -2748,7 +2825,7 @@ pthread_handler_t handle_delayed_insert(void *arg) thd->set_current_time(); threads.append(thd); if (abort_loop) - thd->killed= KILL_CONNECTION; + thd->set_killed(KILL_CONNECTION); else thd->reset_killed(); mysql_mutex_unlock(&LOCK_thread_count); @@ -2767,8 +2844,7 @@ pthread_handler_t handle_delayed_insert(void *arg) if (my_thread_init()) { /* Can't use my_error since store_globals has not yet been called */ - thd->stmt_da->set_error_status(thd, ER_OUT_OF_RESOURCES, - ER(ER_OUT_OF_RESOURCES), NULL); + thd->get_stmt_da()->set_error_status(ER_OUT_OF_RESOURCES); di->handler_thread_initialized= TRUE; } else @@ -2778,8 +2854,7 @@ pthread_handler_t handle_delayed_insert(void *arg) if (init_thr_lock() || thd->store_globals()) { /* Can't use my_error since store_globals has perhaps failed */ - thd->stmt_da->set_error_status(thd, ER_OUT_OF_RESOURCES, - ER(ER_OUT_OF_RESOURCES), NULL); + thd->get_stmt_da()->set_error_status(ER_OUT_OF_RESOURCES); di->handler_thread_initialized= TRUE; thd->fatal_error(); goto err; @@ -2830,6 +2905,14 @@ pthread_handler_t handle_delayed_insert(void *arg) /* Tell client that the thread is initialized */ mysql_cond_signal(&di->cond_client); + /* + Inform mdl that it needs to call mysql_lock_abort to abort locks + for delayed insert. + */ + thd->mdl_context.set_needs_thr_lock_abort(TRUE); + + di->table->mark_columns_needed_for_insert(); + /* Now wait until we get an insert or lock to handle */ /* We will not abort as long as a client thread uses this thread */ @@ -2838,6 +2921,7 @@ pthread_handler_t handle_delayed_insert(void *arg) if (thd->killed) { uint lock_count; + DBUG_PRINT("delayed", ("Insert delayed killed")); /* Remove this from delay insert list so that no one can request a table from this @@ -2848,11 +2932,15 @@ pthread_handler_t handle_delayed_insert(void *arg) lock_count=di->lock_count(); mysql_mutex_unlock(&LOCK_delayed_insert); mysql_mutex_lock(&di->mutex); - if (!lock_count && !di->tables_in_use && !di->stacked_inserts) + if (!lock_count && !di->tables_in_use && !di->stacked_inserts && + !thd->lock) break; // Time to die } /* Shouldn't wait if killed or an insert is waiting. */ + DBUG_PRINT("delayed", + ("thd->killed: %d di->status: %d di->stacked_inserts: %d", + thd->killed, di->status, di->stacked_inserts)); if (!thd->killed && !di->status && !di->stacked_inserts) { struct timespec abstime; @@ -2865,7 +2953,7 @@ pthread_handler_t handle_delayed_insert(void *arg) di->thd.mysys_var->current_cond= &di->cond; mysql_mutex_unlock(&di->thd.mysys_var->mutex); mysql_mutex_lock(&di->mutex); - thd_proc_info(&(di->thd), "Waiting for INSERT"); + THD_STAGE_INFO(&(di->thd), stage_waiting_for_insert); DBUG_PRINT("info",("Waiting for someone to insert rows")); while (!thd->killed && !di->status) @@ -2882,7 +2970,7 @@ pthread_handler_t handle_delayed_insert(void *arg) } #endif if (error == ETIMEDOUT || error == ETIME) - thd->killed= KILL_CONNECTION; + thd->set_killed(KILL_CONNECTION); } /* We can't lock di->mutex and mysys_var->mutex at the same time */ mysql_mutex_unlock(&di->mutex); @@ -2892,7 +2980,9 @@ pthread_handler_t handle_delayed_insert(void *arg) mysql_mutex_unlock(&di->thd.mysys_var->mutex); mysql_mutex_lock(&di->mutex); } - thd_proc_info(&(di->thd), 0); + DBUG_PRINT("delayed", + ("thd->killed: %d di->tables_in_use: %d thd->lock: %d", + thd->killed, di->tables_in_use, thd->lock != 0)); if (di->tables_in_use && ! thd->lock && !thd->killed) { @@ -2909,7 +2999,7 @@ pthread_handler_t handle_delayed_insert(void *arg) if (! (thd->lock= mysql_lock_tables(thd, &di->table, 1, 0))) { /* Fatal error */ - thd->killed= KILL_CONNECTION; + thd->set_killed(KILL_CONNECTION); } mysql_cond_broadcast(&di->cond_client); } @@ -2918,7 +3008,7 @@ pthread_handler_t handle_delayed_insert(void *arg) if (di->handle_inserts()) { /* Some fatal error */ - thd->killed= KILL_CONNECTION; + thd->set_killed(KILL_CONNECTION); } } di->status=0; @@ -2950,24 +3040,38 @@ pthread_handler_t handle_delayed_insert(void *arg) DBUG_LEAVE; } - di->table=0; - thd->killed= KILL_CONNECTION; // If error - mysql_mutex_unlock(&di->mutex); + { + DBUG_ENTER("handle_delayed_insert-cleanup"); + di->table=0; + mysql_mutex_unlock(&di->mutex); - close_thread_tables(thd); // Free the table - thd->mdl_context.release_transactional_locks(); - mysql_cond_broadcast(&di->cond_client); // Safety + /* + Protect against mdl_locks trying to access open tables + We use KILL_CONNECTION_HARD here to ensure that + THD::notify_shared_lock() dosn't try to access open tables after + this. + */ + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->set_killed(KILL_CONNECTION_HARD); // If error + thd->mdl_context.set_needs_thr_lock_abort(0); + mysql_mutex_unlock(&thd->LOCK_thd_data); - mysql_mutex_lock(&LOCK_delayed_create); // Because of delayed_get_table - mysql_mutex_lock(&LOCK_delayed_insert); - /* - di should be unlinked from the thread handler list and have no active - clients - */ - delete di; - mysql_mutex_unlock(&LOCK_delayed_insert); - mysql_mutex_unlock(&LOCK_delayed_create); + close_thread_tables(thd); // Free the table + thd->mdl_context.release_transactional_locks(); + mysql_cond_broadcast(&di->cond_client); // Safety + + mysql_mutex_lock(&LOCK_delayed_create); // Because of delayed_get_table + mysql_mutex_lock(&LOCK_delayed_insert); + /* + di should be unlinked from the thread handler list and have no active + clients + */ + delete di; + mysql_mutex_unlock(&LOCK_delayed_insert); + mysql_mutex_unlock(&LOCK_delayed_create); + DBUG_LEAVE; + } my_thread_end(); pthread_exit(0); @@ -3019,7 +3123,7 @@ bool Delayed_insert::handle_inserts(void) table->next_number_field=table->found_next_number_field; table->use_all_columns(); - thd_proc_info(&thd, "upgrading lock"); + THD_STAGE_INFO(&thd, stage_upgrading_lock); if (thr_upgrade_write_delay_lock(*thd.lock->locks, delayed_lock, thd.variables.lock_wait_timeout)) { @@ -3033,11 +3137,11 @@ bool Delayed_insert::handle_inserts(void) goto err; } - thd_proc_info(&thd, "insert"); + THD_STAGE_INFO(&thd, stage_insert); max_rows= delayed_insert_limit; - if (thd.killed || table->s->has_old_version()) + if (thd.killed || table->s->tdc->flushed) { - thd.killed= KILL_SYSTEM_THREAD; + thd.set_killed(KILL_SYSTEM_THREAD); max_rows= ULONG_MAX; // Do as much as possible } @@ -3092,7 +3196,6 @@ bool Delayed_insert::handle_inserts(void) row->first_successful_insert_id_in_prev_stmt; thd.stmt_depends_on_first_successful_insert_id_in_prev_stmt= row->stmt_depends_on_first_successful_insert_id_in_prev_stmt; - table->timestamp_field_type= row->timestamp_field_type; table->auto_increment_field_not_null= row->auto_increment_field_not_null; /* Copy the session variables. */ @@ -3171,13 +3274,13 @@ bool Delayed_insert::handle_inserts(void) { if (tables_in_use) mysql_cond_broadcast(&cond_client); // If waiting clients - thd_proc_info(&thd, "reschedule"); + THD_STAGE_INFO(&thd, stage_reschedule); mysql_mutex_unlock(&mutex); if ((error=table->file->extra(HA_EXTRA_NO_CACHE))) { /* This should never happen */ table->file->print_error(error,MYF(0)); - sql_print_error("%s", thd.stmt_da->message()); + sql_print_error("%s", thd.get_stmt_da()->message()); DBUG_PRINT("error", ("HA_EXTRA_NO_CACHE failed in loop")); goto err; } @@ -3194,13 +3297,17 @@ bool Delayed_insert::handle_inserts(void) if (!using_bin_log) table->file->extra(HA_EXTRA_WRITE_CACHE); mysql_mutex_lock(&mutex); - thd_proc_info(&thd, "insert"); + THD_STAGE_INFO(&thd, stage_insert); } if (tables_in_use) mysql_cond_broadcast(&cond_client); // If waiting clients } } - thd_proc_info(&thd, 0); + + if (WSREP((&thd))) + thd_proc_info(&thd, "insert done"); + else + thd_proc_info(&thd, 0); mysql_mutex_unlock(&mutex); /* @@ -3224,7 +3331,7 @@ bool Delayed_insert::handle_inserts(void) if ((error=table->file->extra(HA_EXTRA_NO_CACHE))) { // This shouldn't happen table->file->print_error(error,MYF(0)); - sql_print_error("%s", thd.stmt_da->message()); + sql_print_error("%s", thd.get_stmt_da()->message()); DBUG_PRINT("error", ("HA_EXTRA_NO_CACHE failed after loop")); goto err; } @@ -3289,9 +3396,8 @@ bool mysql_insert_select_prepare(THD *thd) if (mysql_prepare_insert(thd, lex->query_tables, lex->query_tables->table, lex->field_list, 0, - lex->update_list, lex->value_list, - lex->duplicates, - &select_lex->where, TRUE, FALSE, FALSE)) + lex->update_list, lex->value_list, lex->duplicates, + &select_lex->where, TRUE)) DBUG_RETURN(TRUE); DBUG_ASSERT(select_lex->leaf_tables.elements != 0); @@ -3330,15 +3436,17 @@ bool mysql_insert_select_prepare(THD *thd) } -select_insert::select_insert(TABLE_LIST *table_list_par, TABLE *table_par, +select_insert::select_insert(THD *thd_arg, TABLE_LIST *table_list_par, + TABLE *table_par, List<Item> *fields_par, List<Item> *update_fields, List<Item> *update_values, enum_duplicates duplic, - bool ignore_check_option_errors) - :table_list(table_list_par), table(table_par), fields(fields_par), - autoinc_value_of_last_inserted_row(0), - insert_into_view(table_list_par && table_list_par->view != 0) + bool ignore_check_option_errors): + select_result_interceptor(thd_arg), + table_list(table_list_par), table(table_par), fields(fields_par), + autoinc_value_of_last_inserted_row(0), + insert_into_view(table_list_par && table_list_par->view != 0) { bzero((char*) &info,sizeof(info)); info.handle_duplicates= duplic; @@ -3375,11 +3483,8 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) if (!res && fields->elements) { bool saved_abort_on_warning= thd->abort_on_warning; - thd->abort_on_warning= !info.ignore && (thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES)); - res= check_that_all_fields_are_given_values(thd, table_list->table, - table_list); + thd->abort_on_warning= !info.ignore && thd->is_strict_mode(); + res= check_that_all_fields_are_given_values(thd, table_list->table, table_list); thd->abort_on_warning= saved_abort_on_warning; } @@ -3438,7 +3543,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) while ((item= li++)) { - item->transform(&Item::update_value_transformer, + item->transform(thd, &Item::update_value_transformer, (uchar*)lex->current_select); } } @@ -3481,13 +3586,14 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) table->file->ha_start_bulk_insert((ha_rows) 0); } restore_record(table,s->default_values); // Get empty record + table->reset_default_fields(); table->next_number_field=table->found_next_number_field; #ifdef HAVE_REPLICATION - if (thd->slave_thread && + if (thd->rgi_slave && (info.handle_duplicates == DUP_UPDATE) && (table->next_number_field != NULL) && - rpl_master_has_bug(&active_mi->rli, 24432, TRUE, NULL, NULL)) + rpl_master_has_bug(thd->rgi_slave->rli, 24432, TRUE, NULL, NULL)) DBUG_RETURN(1); #endif @@ -3499,15 +3605,15 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); if (info.handle_duplicates == DUP_UPDATE) table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE); - thd->abort_on_warning= (!info.ignore && - (thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES))); + thd->abort_on_warning= !info.ignore && thd->is_strict_mode(); res= (table_list->prepare_where(thd, 0, TRUE) || table_list->prepare_check_option(thd)); if (!res) - prepare_triggers_for_insert_stmt(table); + { + table->prepare_triggers_for_insert_stmt_or_event(); + table->mark_columns_needed_for_insert(); + } DBUG_RETURN(res); } @@ -3533,8 +3639,11 @@ int select_insert::prepare2(void) { DBUG_ENTER("select_insert::prepare2"); if (thd->lex->current_select->options & OPTION_BUFFER_RESULT && - thd->locked_tables_mode <= LTM_LOCK_TABLES) + thd->locked_tables_mode <= LTM_LOCK_TABLES && + !thd->lex->describe) table->file->ha_start_bulk_insert((ha_rows) 0); + if (table->validate_default_values_of_unset_fields(thd)) + DBUG_RETURN(1); DBUG_RETURN(0); } @@ -3575,6 +3684,8 @@ int select_insert::send_data(List<Item> &values) thd->count_cuted_fields= CHECK_FIELD_WARN; // Calculate cuted fields store_values(values); + if (table->default_field && table->update_default_fields()) + DBUG_RETURN(1); thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL; if (thd->is_error()) { @@ -3634,38 +3745,31 @@ int select_insert::send_data(List<Item> &values) void select_insert::store_values(List<Item> &values) { if (fields->elements) - fill_record_n_invoke_before_triggers(thd, *fields, values, 1, - table->triggers, TRG_EVENT_INSERT); + fill_record_n_invoke_before_triggers(thd, table, *fields, values, 1, + TRG_EVENT_INSERT); else - fill_record_n_invoke_before_triggers(thd, table->field, values, 1, - table->triggers, TRG_EVENT_INSERT); -} - -void select_insert::send_error(uint errcode,const char *err) -{ - DBUG_ENTER("select_insert::send_error"); - - my_message(errcode, err, MYF(0)); - - DBUG_VOID_RETURN; + fill_record_n_invoke_before_triggers(thd, table, table->field_to_fill(), + values, 1, TRG_EVENT_INSERT); } - -bool select_insert::send_eof() +bool select_insert::prepare_eof() { int error; bool const trans_table= table->file->has_transactions(); - ulonglong id, row_count; bool changed; killed_state killed_status= thd->killed; - DBUG_ENTER("select_insert::send_eof"); + + DBUG_ENTER("select_insert::prepare_eof"); DBUG_PRINT("enter", ("trans_table=%d, table_type='%s'", trans_table, table->file->table_type())); - error= (thd->locked_tables_mode <= LTM_LOCK_TABLES ? - table->file->ha_end_bulk_insert() : 0); + error = IF_WSREP((thd->wsrep_conflict_state == MUST_ABORT || + thd->wsrep_conflict_state == CERT_FAILURE) ? -1 :, ) + (thd->locked_tables_mode <= LTM_LOCK_TABLES ? + table->file->ha_end_bulk_insert() : 0); + if (!error && thd->is_error()) - error= thd->stmt_da->sql_errno(); + error= thd->get_stmt_da()->sql_errno(); table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); @@ -3681,6 +3785,8 @@ bool select_insert::send_eof() if (thd->transaction.stmt.modified_non_trans_table) thd->transaction.all.modified_non_trans_table= TRUE; + thd->transaction.all.m_unsafe_rollback_flags|= + (thd->transaction.stmt.m_unsafe_rollback_flags & THD_TRANS::DID_WAIT); DBUG_ASSERT(trans_table || !changed || thd->transaction.stmt.modified_non_trans_table); @@ -3691,7 +3797,7 @@ bool select_insert::send_eof() events are in the transaction cache and will be written when ha_autocommit_or_rollback() is issued below. */ - if (mysql_bin_log.is_open() && + if ((WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) && (!error || thd->transaction.stmt.modified_non_trans_table)) { int errcode= 0; @@ -3704,7 +3810,7 @@ bool select_insert::send_eof() trans_table, FALSE, FALSE, errcode)) { table->file->ha_release_auto_increment(); - DBUG_RETURN(1); + DBUG_RETURN(true); } } table->file->ha_release_auto_increment(); @@ -3712,27 +3818,49 @@ bool select_insert::send_eof() if (error) { table->file->print_error(error,MYF(0)); - DBUG_RETURN(1); + DBUG_RETURN(true); } - char buff[160]; + + DBUG_RETURN(false); +} + +bool select_insert::send_ok_packet() { + char message[160]; /* status message */ + ulonglong row_count; /* rows affected */ + ulonglong id; /* last insert-id */ + + DBUG_ENTER("select_insert::send_ok_packet"); + if (info.ignore) - sprintf(buff, ER(ER_INSERT_INFO), (ulong) info.records, - (ulong) (info.records - info.copied), - (ulong) thd->warning_info->statement_warn_count()); + my_snprintf(message, sizeof(message), ER(ER_INSERT_INFO), + (ulong) info.records, (ulong) (info.records - info.copied), + (long) thd->get_stmt_da()->current_statement_warn_count()); else - sprintf(buff, ER(ER_INSERT_INFO), (ulong) info.records, - (ulong) (info.deleted+info.updated), - (ulong) thd->warning_info->statement_warn_count()); + my_snprintf(message, sizeof(message), ER(ER_INSERT_INFO), + (ulong) info.records, (ulong) (info.deleted + info.updated), + (long) thd->get_stmt_da()->current_statement_warn_count()); + row_count= info.copied + info.deleted + - ((thd->client_capabilities & CLIENT_FOUND_ROWS) ? - info.touched : info.updated); + ((thd->client_capabilities & CLIENT_FOUND_ROWS) ? + info.touched : info.updated); + id= (thd->first_successful_insert_id_in_cur_stmt > 0) ? thd->first_successful_insert_id_in_cur_stmt : (thd->arg_of_last_insert_id_function ? thd->first_successful_insert_id_in_prev_stmt : (info.copied ? autoinc_value_of_last_inserted_row : 0)); - ::my_ok(thd, row_count, id, buff); - DBUG_RETURN(0); + + ::my_ok(thd, row_count, id, message); + + DBUG_RETURN(false); +} + +bool select_insert::send_eof() +{ + bool res; + DBUG_ENTER("select_insert::send_eof"); + res= (prepare_eof() || (!suppress_my_ok && send_ok_packet())); + DBUG_RETURN(res); } void select_insert::abort_result_set() { @@ -3770,12 +3898,13 @@ void select_insert::abort_result_set() { */ changed= (info.copied || info.deleted || info.updated); transactional_table= table->file->has_transactions(); - if (thd->transaction.stmt.modified_non_trans_table) + if (thd->transaction.stmt.modified_non_trans_table || + thd->log_current_statement) { if (!can_rollback_data()) thd->transaction.all.modified_non_trans_table= TRUE; - if (mysql_bin_log.is_open()) + if(WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) { int errcode= query_error_code(thd, thd->killed == NOT_KILLED); /* error of writing binary log is ignored */ @@ -3799,6 +3928,14 @@ void select_insert::abort_result_set() { CREATE TABLE (SELECT) ... ***************************************************************************/ +Field *Item::create_field_for_create_select(TABLE *table) +{ + Field *def_field, *tmp_field; + return ::create_tmp_field(table->in_use, table, this, type(), + (Item ***) 0, &tmp_field, &def_field, 0, 0, 0, 0); +} + + /** Create table from lists of fields and items (or just return TABLE object for pre-opened existing table). @@ -3838,7 +3975,8 @@ void select_insert::abort_result_set() { @retval 0 Error */ -static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, +static TABLE *create_table_from_items(THD *thd, + Table_specification_st *create_info, TABLE_LIST *create_table, Alter_info *alter_info, List<Item> *items, @@ -3855,35 +3993,22 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, DBUG_ENTER("create_table_from_items"); tmp_table.alias= 0; - tmp_table.timestamp_field= 0; tmp_table.s= &share; init_tmp_table_share(thd, &share, "", 0, "", ""); tmp_table.s->db_create_options=0; - tmp_table.s->blob_ptr_size= portable_sizeof_char_ptr; tmp_table.null_row= 0; tmp_table.maybe_null= 0; + tmp_table.in_use= thd; + + if (!opt_explicit_defaults_for_timestamp) + promote_first_timestamp_column(&alter_info->create_list); while ((item=it++)) { - Field *tmp_table_field; - if (item->type() == Item::FUNC_ITEM) - { - if (item->result_type() != STRING_RESULT) - tmp_table_field= item->tmp_table_field(&tmp_table); - else - tmp_table_field= item->tmp_table_field_from_field_type(&tmp_table, - false); - } - else - { - Field *from_field, * default_field; - tmp_table_field= create_tmp_field(thd, &tmp_table, item, item->type(), - (Item ***) NULL, &from_field, &default_field, - 0, 0, 0, 0, 0); - } + Field *tmp_field= item->create_field_for_create_select(&tmp_table); - if (!tmp_table_field) + if (!tmp_field) DBUG_RETURN(NULL); Field *table_field; @@ -3904,18 +4029,29 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, table_field= NULL; } - Create_field *cr_field= new Create_field(tmp_table_field, table_field); + Create_field *cr_field= new (thd->mem_root) + Create_field(thd, tmp_field, table_field); if (!cr_field) DBUG_RETURN(NULL); if (item->maybe_null) cr_field->flags &= ~NOT_NULL_FLAG; - alter_info->create_list.push_back(cr_field); + alter_info->create_list.push_back(cr_field, thd->mem_root); } DEBUG_SYNC(thd,"create_table_select_before_create"); + /* Check if LOCK TABLES + CREATE OR REPLACE of existing normal table*/ + if (thd->locked_tables_mode && create_table->table && + !create_info->tmp_table()) + { + /* Remember information about the locked table */ + create_info->pos_in_locked_tables= + create_table->table->pos_in_locked_tables; + create_info->mdl_ticket= create_table->table->mdl_ticket; + } + /* Create and lock table. @@ -3932,53 +4068,64 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, TABLE, which is a wrong order. So we keep binary logging disabled when we open_table(). */ + + if (!mysql_create_table_no_lock(thd, create_table->db, + create_table->table_name, + create_info, alter_info, NULL, + select_field_count)) { - if (!mysql_create_table_no_lock(thd, create_table->db, - create_table->table_name, - create_info, alter_info, 0, - select_field_count, NULL)) + DEBUG_SYNC(thd,"create_table_select_before_open"); + + /* + If we had a temporary table or a table used with LOCK TABLES, + it was closed by mysql_create() + */ + create_table->table= 0; + + if (!create_info->tmp_table()) { - DEBUG_SYNC(thd,"create_table_select_before_open"); + Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN); + TABLE_LIST::enum_open_strategy save_open_strategy; - if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) - { - Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN); - /* - Here we open the destination table, on which we already have - an exclusive metadata lock. - */ - if (open_table(thd, create_table, thd->mem_root, &ot_ctx)) - { - quick_rm_table(create_info->db_type, create_table->db, - table_case_name(create_info, create_table->table_name), - 0); - } - else - table= create_table->table; - } - else + /* Force the newly created table to be opened */ + save_open_strategy= create_table->open_strategy; + create_table->open_strategy= TABLE_LIST::OPEN_NORMAL; + /* + Here we open the destination table, on which we already have + an exclusive metadata lock. + */ + if (open_table(thd, create_table, &ot_ctx)) { - Open_table_context ot_ctx(thd, MYSQL_OPEN_TEMPORARY_ONLY); - if (open_table(thd, create_table, thd->mem_root, &ot_ctx)) - { - /* - This shouldn't happen as creation of temporary table should make - it preparable for open. But let us do close_temporary_table() here - just in case. - */ - drop_temporary_table(thd, create_table, NULL); - } - else - table= create_table->table; + quick_rm_table(thd, create_info->db_type, create_table->db, + table_case_name(create_info, create_table->table_name), + 0); } + /* Restore */ + create_table->open_strategy= save_open_strategy; } - if (!table) // open failed + else { - if (!thd->is_error()) // CREATE ... IF NOT EXISTS - my_ok(thd); // succeed, but did nothing - DBUG_RETURN(NULL); + if (open_temporary_table(thd, create_table)) + { + /* + This shouldn't happen as creation of temporary table should make + it preparable for open. Anyway we can't drop temporary table if + we are unable to find it. + */ + DBUG_ASSERT(0); + } + DBUG_ASSERT(create_table->table == create_info->table); } } + else + create_table->table= 0; // Create failed + + if (!(table= create_table->table)) + { + if (!thd->is_error()) // CREATE ... IF NOT EXISTS + my_ok(thd); // succeed, but did nothing + DBUG_RETURN(NULL); + } DEBUG_SYNC(thd,"create_table_select_before_lock"); @@ -3995,7 +4142,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, /* purecov: begin tested */ /* This can happen in innodb when you get a deadlock when using same table - in insert and select + in insert and select or when you run out of memory. */ my_error(ER_CANT_LOCK, MYF(0), my_errno); if (*lock) @@ -4086,18 +4233,17 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) row-based replication for the statement. If we are creating a temporary table, we need to start a statement transaction. */ - if ((thd->lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) == 0 && + if (!thd->lex->tmp_table() && thd->is_current_stmt_binlog_format_row() && mysql_bin_log.is_open()) { thd->binlog_start_trans_and_stmt(); } - DBUG_ASSERT(create_table->table == NULL); - DEBUG_SYNC(thd,"create_table_select_before_check_if_exists"); - if (!(table= create_table_from_items(thd, create_info, create_table, + if (!(table= create_table_from_items(thd, create_info, + create_table, alter_info, &values, &extra_lock, hook_ptr))) /* abort() deletes table */ @@ -4107,7 +4253,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) { DBUG_ASSERT(m_plock == NULL); - if (create_info->options & HA_LEX_CREATE_TMP_TABLE) + if (create_info->tmp_table()) m_plock= &m_lock; else m_plock= &thd->extra_lock; @@ -4128,8 +4274,6 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) for (Field **f= field ; *f ; f++) bitmap_set_bit(table->write_set, (*f)->field_index); - /* Don't set timestamp if used */ - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; table->next_number_field=table->found_next_number_field; restore_record(table,s->default_values); // Get empty record @@ -4143,10 +4287,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE); if (thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_start_bulk_insert((ha_rows) 0); - thd->abort_on_warning= (!info.ignore && - (thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES))); + thd->abort_on_warning= !info.ignore && thd->is_strict_mode(); if (check_that_all_fields_are_given_values(thd, table, table_list)) DBUG_RETURN(1); table->mark_columns_needed_for_insert(); @@ -4161,15 +4302,14 @@ select_create::binlog_show_create_table(TABLE **tables, uint count) { /* Note 1: In RBR mode, we generate a CREATE TABLE statement for the - created table by calling store_create_info() (behaves as SHOW - CREATE TABLE). In the event of an error, nothing should be - written to the binary log, even if the table is non-transactional; - therefore we pretend that the generated CREATE TABLE statement is - for a transactional table. The event will then be put in the - transaction cache, and any subsequent events (e.g., table-map - events and binrow events) will also be put there. We can then use - ha_autocommit_or_rollback() to either throw away the entire - kaboodle of events, or write them to the binary log. + created table by calling show_create_table(). In the event of an error, + nothing should be written to the binary log, even if the table is + non-transactional; therefore we pretend that the generated CREATE TABLE + statement is for a transactional table. The event will then be put in the + transaction cache, and any subsequent events (e.g., table-map events and + binrow events) will also be put there. We can then use + ha_autocommit_or_rollback() to either throw away the entire kaboodle of + events, or write them to the binary log. We write the CREATE TABLE statement here and not in prepare() since there potentially are sub-selects or accesses to information @@ -4179,20 +4319,18 @@ select_create::binlog_show_create_table(TABLE **tables, uint count) DBUG_ASSERT(thd->is_current_stmt_binlog_format_row()); DBUG_ASSERT(tables && *tables && count > 0); - char buf[2048]; - String query(buf, sizeof(buf), system_charset_info); + StringBuffer<2048> query(system_charset_info); int result; TABLE_LIST tmp_table_list; - memset(&tmp_table_list, 0, sizeof(tmp_table_list)); + tmp_table_list.reset(); tmp_table_list.table = *tables; - query.length(0); // Have to zero it since constructor doesn't - result= store_create_info(thd, &tmp_table_list, &query, create_info, - /* show_database */ TRUE); - DBUG_ASSERT(result == 0); /* store_create_info() always return 0 */ + result= show_create_table(thd, &tmp_table_list, &query, + create_info, WITH_DB_NAME); + DBUG_ASSERT(result == 0); /* show_create_table() always return 0 */ - if (mysql_bin_log.is_open()) + if (WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) { int errcode= query_error_code(thd, thd->killed == NOT_KILLED); result= thd->binlog_query(THD::STMT_QUERY_TYPE, @@ -4202,81 +4340,148 @@ select_create::binlog_show_create_table(TABLE **tables, uint count) /* suppress_use */ FALSE, errcode); } + + ha_fake_trx_id(thd); + return result; } void select_create::store_values(List<Item> &values) { - fill_record_n_invoke_before_triggers(thd, field, values, 1, - table->triggers, TRG_EVENT_INSERT); + fill_record_n_invoke_before_triggers(thd, table, field, values, 1, + TRG_EVENT_INSERT); } -void select_create::send_error(uint errcode,const char *err) +bool select_create::send_eof() { - DBUG_ENTER("select_create::send_error"); - - DBUG_PRINT("info", - ("Current statement %s row-based", - thd->is_current_stmt_binlog_format_row() ? "is" : "is NOT")); - DBUG_PRINT("info", - ("Current table (at 0x%lu) %s a temporary (or non-existant) table", - (ulong) table, - table && !table->s->tmp_table ? "is NOT" : "is")); - /* - This will execute any rollbacks that are necessary before writing - the transcation cache. - - We disable the binary log since nothing should be written to the - binary log. This disabling is important, since we potentially do - a "roll back" of non-transactional tables by removing the table, - and the actual rollback might generate events that should not be - written to the binary log. + DBUG_ENTER("select_create::send_eof"); + /* + The routine that writes the statement in the binary log + is in select_insert::prepare_eof(). For that reason, we + mark the flag at this point. */ - tmp_disable_binlog(thd); - select_insert::send_error(errcode, err); - reenable_binlog(thd); - - DBUG_VOID_RETURN; -} - + if (table->s->tmp_table) + thd->transaction.stmt.mark_created_temp_table(); -bool select_create::send_eof() -{ - bool tmp=select_insert::send_eof(); - if (tmp) + if (prepare_eof()) + { abort_result_set(); - else + DBUG_RETURN(true); + } + + /* + Do an implicit commit at end of statement for non-temporary + tables. This can fail, but we should unlock the table + nevertheless. + */ + if (!table->s->tmp_table) { - /* - Do an implicit commit at end of statement for non-temporary - tables. This can fail, but we should unlock the table - nevertheless. - */ - if (!table->s->tmp_table) +#ifdef WITH_WSREP + if (WSREP_ON) { - trans_commit_stmt(thd); + /* + append table level exclusive key for CTAS + */ + wsrep_key_arr_t key_arr= {0, 0}; + wsrep_prepare_keys_for_isolation(thd, + create_table->db, + create_table->table_name, + table_list, + &key_arr); + int rcode = wsrep->append_key( + wsrep, + &thd->wsrep_ws_handle, + key_arr.keys, //&wkey, + key_arr.keys_len, + WSREP_KEY_EXCLUSIVE, + false); + wsrep_keys_free(&key_arr); + if (rcode) { + DBUG_PRINT("wsrep", ("row key failed: %d", rcode)); + WSREP_ERROR("Appending table key for CTAS failed: %s, %d", + (wsrep_thd_query(thd)) ? + wsrep_thd_query(thd) : "void", rcode); + return true; + } + /* If commit fails, we should be able to reset the OK status. */ + thd->get_stmt_da()->set_overwrite_status(TRUE); + } +#endif /* WITH_WSREP */ + trans_commit_stmt(thd); + if (!(thd->variables.option_bits & OPTION_GTID_BEGIN)) trans_commit_implicit(thd); +#ifdef WITH_WSREP + if (WSREP_ON) + { + thd->get_stmt_da()->set_overwrite_status(FALSE); + mysql_mutex_lock(&thd->LOCK_thd_data); + if (thd->wsrep_conflict_state != NO_CONFLICT) + { + WSREP_DEBUG("select_create commit failed, thd: %lu err: %d %s", + thd->thread_id, thd->wsrep_conflict_state, thd->query()); + mysql_mutex_unlock(&thd->LOCK_thd_data); + abort_result_set(); + DBUG_RETURN(true); + } + mysql_mutex_unlock(&thd->LOCK_thd_data); } +#endif /* WITH_WSREP */ + } + else if (!thd->is_current_stmt_binlog_format_row()) + table->s->table_creation_was_logged= 1; - table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); - table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); - if (m_plock) + /* + exit_done must only be set after last potential call to + abort_result_set(). + */ + exit_done= 1; // Avoid double calls + + table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); + table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); + + send_ok_packet(); + + if (m_plock) + { + MYSQL_LOCK *lock= *m_plock; + *m_plock= NULL; + m_plock= NULL; + + if (create_info->pos_in_locked_tables) { - mysql_unlock_tables(thd, *m_plock); - *m_plock= NULL; - m_plock= NULL; + /* + If we are under lock tables, we have created a table that was + originally locked. We should add back the lock to ensure that + all tables in the thd->open_list are locked! + */ + table->mdl_ticket= create_info->mdl_ticket; + + /* The following should never fail, except if out of memory */ + if (!thd->locked_tables_list.restore_lock(thd, + create_info-> + pos_in_locked_tables, + table, lock)) + DBUG_RETURN(false); // ok + /* Fail. Continue without locking the table */ } + mysql_unlock_tables(thd, lock); } - return tmp; + DBUG_RETURN(false); } void select_create::abort_result_set() { + ulonglong save_option_bits; DBUG_ENTER("select_create::abort_result_set"); + /* Avoid double calls, could happen in case of out of memory on cleanup */ + if (exit_done) + DBUG_VOID_RETURN; + exit_done= 1; + /* In select_insert::abort_result_set() we roll back the statement, including truncating the transaction cache of the binary log. To do this, we @@ -4291,14 +4496,26 @@ void select_create::abort_result_set() We also roll back the statement regardless of whether the creation of the table succeeded or not, since we need to reset the binary log state. + + However if there was an original table that was deleted, as part of + create or replace table, then we must log the statement. */ - tmp_disable_binlog(thd); + + save_option_bits= thd->variables.option_bits; + thd->variables.option_bits&= ~OPTION_BIN_LOG; select_insert::abort_result_set(); thd->transaction.stmt.modified_non_trans_table= FALSE; - reenable_binlog(thd); + thd->variables.option_bits= save_option_bits; + /* possible error of writing binary log is ignored deliberately */ (void) thd->binlog_flush_pending_rows_event(TRUE, TRUE); + if (create_info->table_was_deleted) + { + /* Unlock locked table that was dropped by CREATE */ + thd->locked_tables_list.unlock_locked_table(thd, + create_info->mdl_ticket); + } if (m_plock) { mysql_unlock_tables(thd, *m_plock); @@ -4308,25 +4525,21 @@ void select_create::abort_result_set() if (table) { + bool tmp_table= table->s->tmp_table; table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); table->auto_increment_field_not_null= FALSE; drop_open_table(thd, table, create_table->db, create_table->table_name); table=0; // Safety + if (thd->log_current_statement && mysql_bin_log.is_open()) + { + /* Remove logging of drop, create + insert rows */ + binlog_reset_cache(thd); + /* Original table was deleted. We have to log it */ + log_drop_table(thd, create_table->db, create_table->db_length, + create_table->table_name, create_table->table_name_length, + tmp_table); + } } DBUG_VOID_RETURN; } - - -/***************************************************************************** - Instansiate templates -*****************************************************************************/ - -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class List_iterator_fast<List_item>; -#ifndef EMBEDDED_LIBRARY -template class I_List<Delayed_insert>; -template class I_List_iterator<Delayed_insert>; -template class I_List<delayed_row>; -#endif /* EMBEDDED_LIBRARY */ -#endif /* HAVE_EXPLICIT_TEMPLATE_INSTANTIATION */ diff --git a/sql/sql_insert.h b/sql/sql_insert.h index 8564f4d103e..aea0dac6b0d 100644 --- a/sql/sql_insert.h +++ b/sql/sql_insert.h @@ -27,8 +27,7 @@ bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, TABLE *table, List<Item> &fields, List_item *values, List<Item> &update_fields, List<Item> &update_values, enum_duplicates duplic, - COND **where, bool select_insert, - bool check_fields, bool abort_on_warning); + COND **where, bool select_insert); bool mysql_insert(THD *thd,TABLE_LIST *table,List<Item> &fields, List<List_item> &values, List<Item> &update_fields, List<Item> &update_values, enum_duplicates flag, @@ -38,7 +37,6 @@ void upgrade_lock_type_for_insert(THD *thd, thr_lock_type *lock_type, bool is_multi_insert); int check_that_all_fields_are_given_values(THD *thd, TABLE *entry, TABLE_LIST *table_list); -void prepare_triggers_for_insert_stmt(TABLE *table); int write_record(THD *thd, TABLE *table, COPY_INFO *info); void kill_delayed_threads(void); diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc index 13f06024b22..4b7667f1319 100644 --- a/sql/sql_join_cache.cc +++ b/sql/sql_join_cache.cc @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /** @file @@ -222,8 +222,8 @@ void JOIN_CACHE::calc_record_fields() for (; tab != join_tab ; tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS)) { tab->calc_used_field_length(FALSE); - flag_fields+= test(tab->used_null_fields || tab->used_uneven_bit_fields); - flag_fields+= test(tab->table->maybe_null); + flag_fields+= MY_TEST(tab->used_null_fields || tab->used_uneven_bit_fields); + flag_fields+= MY_TEST(tab->table->maybe_null); fields+= tab->used_fields; blobs+= tab->used_blobs; } @@ -318,7 +318,7 @@ void JOIN_CACHE::collect_info_on_key_args() The allocated arrays are adjacent. NOTES - The memory is allocated in join->thd->memroot + The memory is allocated in join->thd->mem_root RETURN VALUE pointer to the first array @@ -328,8 +328,8 @@ int JOIN_CACHE::alloc_fields() { uint ptr_cnt= external_key_arg_fields+blobs+1; uint fields_size= sizeof(CACHE_FIELD)*fields; - field_descr= (CACHE_FIELD*) sql_alloc(fields_size + - sizeof(CACHE_FIELD*)*ptr_cnt); + field_descr= (CACHE_FIELD*) join->thd->alloc(fields_size + + sizeof(CACHE_FIELD*)*ptr_cnt); blob_ptr= (CACHE_FIELD **) ((uchar *) field_descr + fields_size); return (field_descr == NULL); } @@ -701,7 +701,7 @@ void JOIN_CACHE::set_constants() pack_length_with_blob_ptrs= pack_length + blobs*sizeof(uchar *); min_buff_size= 0; min_records= 1; - buff_size= max(join->thd->variables.join_buff_size, + buff_size= MY_MAX(join->thd->variables.join_buff_size, get_min_join_buffer_size()); size_of_rec_ofs= offset_size(buff_size); size_of_rec_len= blobs ? size_of_rec_ofs : offset_size(len); @@ -741,7 +741,7 @@ void JOIN_CACHE::set_constants() uint JOIN_CACHE::get_record_max_affix_length() { uint len= get_prefix_length() + - test(with_match_flag) + + MY_TEST(with_match_flag) + size_of_fld_ofs * data_field_count; return len; } @@ -934,12 +934,15 @@ int JOIN_CACHE::alloc_buffer() join->shrink_join_buffers(join_tab, curr_buff_space_sz, join_buff_space_limit)))) goto fail; + + if (for_explain_only) + return 0; for (ulong buff_size_decr= (buff_size-min_buff_size)/4 + 1; ; ) { ulong next_buff_size; - if ((buff= (uchar*) my_malloc(buff_size, MYF(0)))) + if ((buff= (uchar*) my_malloc(buff_size, MYF(MY_THREAD_SPECIFIC)))) break; next_buff_size= buff_size > buff_size_decr ? buff_size-buff_size_decr : 0; @@ -1017,7 +1020,7 @@ int JOIN_CACHE::realloc_buffer() { int rc; free(); - rc= test(!(buff= (uchar*) my_malloc(buff_size, MYF(0)))); + rc= MY_TEST(!(buff= (uchar*) my_malloc(buff_size, MYF(MY_THREAD_SPECIFIC)))); reset(TRUE); return rc; } @@ -1028,6 +1031,7 @@ int JOIN_CACHE::realloc_buffer() SYNOPSIS init() + for_explain join buffer is initialized for explain only DESCRIPTION The function initializes the join cache structure. It supposed to be called @@ -1049,10 +1053,12 @@ int JOIN_CACHE::realloc_buffer() 1 otherwise */ -int JOIN_CACHE::init() +int JOIN_CACHE::init(bool for_explain) { DBUG_ENTER("JOIN_CACHE::init"); + for_explain_only= for_explain; + calc_record_fields(); collect_info_on_key_args(); @@ -1771,7 +1777,7 @@ uint JOIN_CACHE::read_flag_fields() CACHE_FIELD *copy_end= copy+flag_fields; if (with_match_flag) { - copy->str[0]= test((Match_flag) pos[0] == MATCH_FOUND); + copy->str[0]= MY_TEST((Match_flag) pos[0] == MATCH_FOUND); pos+= copy->length; copy++; } @@ -2205,7 +2211,7 @@ finish: for a match for any record from join_tab. To iterate over the candidates for a match the virtual function get_next_candidate_for_match is used, while the virtual function prepare_look_for_matches is called to prepare - for such iteration proccess. + for such iteration process. NOTES The function produces all matching extensions for the records in the @@ -2259,10 +2265,10 @@ enum_nested_loop_state JOIN_CACHE::join_matching_records(bool skip_last) */ goto finish; } - + while (!(error= join_tab_scan->next())) { - if (join->thd->killed) + if (join->thd->check_killed()) { /* The user has aborted the execution of the query */ join->thd->send_kill_message(); @@ -2276,11 +2282,13 @@ enum_nested_loop_state JOIN_CACHE::join_matching_records(bool skip_last) /* Prepare to read matching candidates from the join buffer */ if (prepare_look_for_matches(skip_last)) continue; + join_tab->jbuf_tracker->r_scans++; uchar *rec_ptr; /* Read each possible candidate from the buffer and look for matches */ while ((rec_ptr= get_next_candidate_for_match())) - { + { + join_tab->jbuf_tracker->r_rows++; /* If only the first match is needed, and, it has been already found for the next record read from the join buffer, then the record is skipped. @@ -2450,6 +2458,8 @@ inline bool JOIN_CACHE::check_match(uchar *rec_ptr) if (join_tab->select && join_tab->select->skip_record(join->thd) <= 0) DBUG_RETURN(FALSE); + + join_tab->jbuf_tracker->r_rows_after_where++; if (!join_tab->is_last_inner_table()) DBUG_RETURN(TRUE); @@ -2525,14 +2535,14 @@ enum_nested_loop_state JOIN_CACHE::join_null_complements(bool skip_last) if (!records) DBUG_RETURN(NESTED_LOOP_OK); - cnt= records - (is_key_access() ? 0 : test(skip_last)); + cnt= records - (is_key_access() ? 0 : MY_TEST(skip_last)); /* This function may be called only for inner tables of outer joins */ DBUG_ASSERT(join_tab->first_inner); for ( ; cnt; cnt--) { - if (join->thd->killed) + if (join->thd->check_killed()) { /* The user has aborted the execution of the query */ join->thd->send_kill_message(); @@ -2558,49 +2568,43 @@ finish: /* - Add a comment on the join algorithm employed by the join cache + Save data on the join algorithm employed by the join cache SYNOPSIS - print_explain_comment() + save_explain_data() str string to add the comment on the employed join algorithm to DESCRIPTION - This function adds info on the type of the used join buffer (flat or + This function puts info about the type of the used join buffer (flat or incremental) and on the type of the the employed join algorithm (BNL, - BNLH, BKA or BKAH) to the the end of the sring str. + BNLH, BKA or BKAH) to the data structure RETURN VALUE none */ -void JOIN_CACHE::print_explain_comment(String *str) +void JOIN_CACHE::save_explain_data(EXPLAIN_BKA_TYPE *explain) { - str->append(STRING_WITH_LEN(" (")); - const char *buffer_type= prev_cache ? "incremental" : "flat"; - str->append(buffer_type); - str->append(STRING_WITH_LEN(", ")); - - const char *join_alg=""; + explain->incremental= MY_TEST(prev_cache); + + explain->join_buffer_size= get_join_buffer_size(); + switch (get_join_alg()) { case BNL_JOIN_ALG: - join_alg= "BNL"; + explain->join_alg= "BNL"; break; case BNLH_JOIN_ALG: - join_alg= "BNLH"; + explain->join_alg= "BNLH"; break; case BKA_JOIN_ALG: - join_alg= "BKA"; + explain->join_alg= "BKA"; break; case BKAH_JOIN_ALG: - join_alg= "BKAH"; + explain->join_alg= "BKAH"; break; default: DBUG_ASSERT(0); } - - str->append(join_alg); - str->append(STRING_WITH_LEN(" join")); - str->append(STRING_WITH_LEN(")")); } /** @@ -2621,23 +2625,23 @@ static void add_mrr_explain_info(String *str, uint mrr_mode, handler *file) sizeof(mrr_str_buf)); if (len > 0) { - str->append(STRING_WITH_LEN("; ")); + if (str->length()) + str->append(STRING_WITH_LEN("; ")); str->append(mrr_str_buf, len); } } - -void JOIN_CACHE_BKA::print_explain_comment(String *str) +void JOIN_CACHE_BKA::save_explain_data(EXPLAIN_BKA_TYPE *explain) { - JOIN_CACHE::print_explain_comment(str); - add_mrr_explain_info(str, mrr_mode, join_tab->table->file); + JOIN_CACHE::save_explain_data(explain); + add_mrr_explain_info(&explain->mrr_type, mrr_mode, join_tab->table->file); } -void JOIN_CACHE_BKAH::print_explain_comment(String *str) +void JOIN_CACHE_BKAH::save_explain_data(EXPLAIN_BKA_TYPE *explain) { - JOIN_CACHE::print_explain_comment(str); - add_mrr_explain_info(str, mrr_mode, join_tab->table->file); + JOIN_CACHE::save_explain_data(explain); + add_mrr_explain_info(&explain->mrr_type, mrr_mode, join_tab->table->file); } @@ -2646,6 +2650,7 @@ void JOIN_CACHE_BKAH::print_explain_comment(String *str) SYNOPSIS init() + for_explain join buffer is initialized for explain only DESCRIPTION The function initializes the cache structure with a hash table in it. @@ -2665,7 +2670,7 @@ void JOIN_CACHE_BKAH::print_explain_comment(String *str) 1 otherwise */ -int JOIN_CACHE_HASHED::init() +int JOIN_CACHE_HASHED::init(bool for_explain) { int rc= 0; TABLE_REF *ref= &join_tab->ref; @@ -2677,10 +2682,10 @@ int JOIN_CACHE_HASHED::init() key_length= ref->key_length; - if ((rc= JOIN_CACHE::init())) - DBUG_RETURN (rc); + if ((rc= JOIN_CACHE::init(for_explain)) || for_explain) + DBUG_RETURN (rc); - if (!(key_buff= (uchar*) sql_alloc(key_length))) + if (!(key_buff= (uchar*) join->thd->alloc(key_length))) DBUG_RETURN(1); /* Take into account a reference to the next record in the key chain */ @@ -2744,7 +2749,7 @@ int JOIN_CACHE_HASHED::init_hash_table() key_entries= 0; /* Calculate the minimal possible value of size_of_key_ofs greater than 1 */ - uint max_size_of_key_ofs= max(2, get_size_of_rec_offset()); + uint max_size_of_key_ofs= MY_MAX(2, get_size_of_rec_offset()); for (size_of_key_ofs= 2; size_of_key_ofs <= max_size_of_key_ofs; size_of_key_ofs+= 2) @@ -2806,7 +2811,7 @@ int JOIN_CACHE_HASHED::realloc_buffer() { int rc; free(); - rc= test(!(buff= (uchar*) my_malloc(buff_size, MYF(0)))); + rc= MY_TEST(!(buff= (uchar*) my_malloc(buff_size, MYF(MY_THREAD_SPECIFIC)))); init_hash_table(); reset(TRUE); return rc; @@ -3340,6 +3345,7 @@ int JOIN_TAB_SCAN::open() { save_or_restore_used_tabs(join_tab, FALSE); is_first_record= TRUE; + join_tab->tracker->r_scans++; return join_init_read_record(join_tab); } @@ -3378,20 +3384,33 @@ int JOIN_TAB_SCAN::next() is_first_record= FALSE; else err= info->read_record(info); - if (!err && table->vfield) - update_virtual_fields(thd, table); + + if (!err) + { + join_tab->tracker->r_rows++; + if (table->vfield) + update_virtual_fields(thd, table); + } + while (!err && select && (skip_rc= select->skip_record(thd)) <= 0) { - if (thd->killed || skip_rc < 0) + if (thd->check_killed() || skip_rc < 0) return 1; /* Move to the next record if the last retrieved record does not meet the condition pushed to the table join_tab. */ err= info->read_record(info); - if (!err && table->vfield) - update_virtual_fields(thd, table); - } + if (!err) + { + join_tab->tracker->r_rows++; + if (table->vfield) + update_virtual_fields(thd, table); + } + } + + if (!err) + join_tab->tracker->r_rows_after_where++; return err; } @@ -3486,7 +3505,7 @@ bool JOIN_CACHE_BNL::prepare_look_for_matches(bool skip_last) if (!records) return TRUE; reset(FALSE); - rem_records= records-test(skip_last); + rem_records= records - MY_TEST(skip_last); return rem_records == 0; } @@ -3586,6 +3605,7 @@ void JOIN_CACHE_BNL::read_next_candidate_for_match(uchar *rec_ptr) SYNOPSIS init + for_explain join buffer is initialized for explain only DESCRIPTION The function initializes the cache structure. It is supposed to be called @@ -3600,14 +3620,14 @@ void JOIN_CACHE_BNL::read_next_candidate_for_match(uchar *rec_ptr) 1 otherwise */ -int JOIN_CACHE_BNL::init() +int JOIN_CACHE_BNL::init(bool for_explain) { DBUG_ENTER("JOIN_CACHE_BNL::init"); if (!(join_tab_scan= new JOIN_TAB_SCAN(join, join_tab))) DBUG_RETURN(1); - DBUG_RETURN(JOIN_CACHE::init()); + DBUG_RETURN(JOIN_CACHE::init(for_explain)); } @@ -3772,6 +3792,7 @@ void JOIN_CACHE_BNLH::read_next_candidate_for_match(uchar *rec_ptr) SYNOPSIS init + for_explain join buffer is initialized for explain only DESCRIPTION The function initializes the cache structure. It is supposed to be called @@ -3786,14 +3807,14 @@ void JOIN_CACHE_BNLH::read_next_candidate_for_match(uchar *rec_ptr) 1 otherwise */ -int JOIN_CACHE_BNLH::init() +int JOIN_CACHE_BNLH::init(bool for_explain) { DBUG_ENTER("JOIN_CACHE_BNLH::init"); if (!(join_tab_scan= new JOIN_TAB_SCAN(join, join_tab))) DBUG_RETURN(1); - DBUG_RETURN(JOIN_CACHE_HASHED::init()); + DBUG_RETURN(JOIN_CACHE_HASHED::init(for_explain)); } @@ -3817,7 +3838,8 @@ uint JOIN_TAB_SCAN_MRR::aux_buffer_incr(ulong recno) uint incr= 0; TABLE_REF *ref= &join_tab->ref; TABLE *tab= join_tab->table; - uint rec_per_key= tab->key_info[ref->key].rec_per_key[ref->key_parts-1]; + ha_rows rec_per_key= + (ha_rows) tab->key_info[ref->key].actual_rec_per_key(ref->key_parts-1); set_if_bigger(rec_per_key, 1); if (recno == 1) incr= ref->key_length + tab->file->ref_length; @@ -3855,6 +3877,7 @@ int JOIN_TAB_SCAN_MRR::open() /* Dynamic range access is never used with BKA */ DBUG_ASSERT(join_tab->use_quick != 2); + join_tab->tracker->r_scans++; save_or_restore_used_tabs(join_tab, FALSE); init_mrr_buff(); @@ -3898,13 +3921,17 @@ int JOIN_TAB_SCAN_MRR::next() int rc= join_tab->table->file->multi_range_read_next((range_id_t*)ptr) ? -1 : 0; if (!rc) { - /* + join_tab->tracker->r_rows++; + join_tab->tracker->r_rows_after_where++; + /* If a record in in an incremental cache contains no fields then the association for the last record in cache will be equal to cache->end_pos - */ - DBUG_ASSERT((!(mrr_mode & HA_MRR_NO_ASSOCIATION))? - (cache->buff <= (uchar *) (*ptr) && - (uchar *) (*ptr) <= cache->end_pos): TRUE); + */ + /* + psergey: this makes no sense where HA_MRR_NO_ASSOC is used. + DBUG_ASSERT(cache->buff <= (uchar *) (*ptr) && + (uchar *) (*ptr) <= cache->end_pos); + */ if (join_tab->table->vfield) update_virtual_fields(join->thd, join_tab->table); } @@ -3914,663 +3941,665 @@ int JOIN_TAB_SCAN_MRR::next() static void bka_range_seq_key_info(void *init_params, uint *length, - key_part_map *map) + key_part_map *map) { - TABLE_REF *ref= &(((JOIN_CACHE*)init_params)->join_tab->ref); - *length= ref->key_length; - *map= (key_part_map(1) << ref->key_parts) - 1; +TABLE_REF *ref= &(((JOIN_CACHE*)init_params)->join_tab->ref); +*length= ref->key_length; +*map= (key_part_map(1) << ref->key_parts) - 1; } /* - Initialize retrieval of range sequence for BKA join algorithm - - SYNOPSIS - bka_range_seq_init() - init_params pointer to the BKA join cache object - n_ranges the number of ranges obtained - flags combination of MRR flags - - DESCRIPTION - The function interprets init_param as a pointer to a JOIN_CACHE_BKA - object. The function prepares for an iteration over the join keys - built for all records from the cache join buffer. - - NOTE - This function are used only as a callback function. - - RETURN VALUE - init_param value that is to be used as a parameter of bka_range_seq_next() +Initialize retrieval of range sequence for BKA join algorithm + +SYNOPSIS + bka_range_seq_init() + init_params pointer to the BKA join cache object + n_ranges the number of ranges obtained + flags combination of MRR flags + +DESCRIPTION + The function interprets init_param as a pointer to a JOIN_CACHE_BKA + object. The function prepares for an iteration over the join keys + built for all records from the cache join buffer. + +NOTE + This function are used only as a callback function. + +RETURN VALUE + init_param value that is to be used as a parameter of bka_range_seq_next() */ static range_seq_t bka_range_seq_init(void *init_param, uint n_ranges, uint flags) { - DBUG_ENTER("bka_range_seq_init"); - JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) init_param; - cache->reset(0); - DBUG_RETURN((range_seq_t) init_param); +DBUG_ENTER("bka_range_seq_init"); +JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) init_param; +cache->reset(0); +DBUG_RETURN((range_seq_t) init_param); } /* - Get the next range/key over records from the join buffer used by a BKA cache - - SYNOPSIS - bka_range_seq_next() - seq the value returned by bka_range_seq_init - range OUT reference to the next range +Get the next range/key over records from the join buffer used by a BKA cache - DESCRIPTION - The function interprets seq as a pointer to a JOIN_CACHE_BKA - object. The function returns a pointer to the range descriptor - for the key built over the next record from the join buffer. - - NOTE - This function are used only as a callback function. - - RETURN VALUE - FALSE ok, the range structure filled with info about the next range/key - TRUE no more ranges +SYNOPSIS + bka_range_seq_next() + seq the value returned by bka_range_seq_init + range OUT reference to the next range + +DESCRIPTION + The function interprets seq as a pointer to a JOIN_CACHE_BKA + object. The function returns a pointer to the range descriptor + for the key built over the next record from the join buffer. + +NOTE + This function are used only as a callback function. + +RETURN VALUE + FALSE ok, the range structure filled with info about the next range/key + TRUE no more ranges */ static bool bka_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range) { - DBUG_ENTER("bka_range_seq_next"); - JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) rseq; - TABLE_REF *ref= &cache->join_tab->ref; - key_range *start_key= &range->start_key; - if ((start_key->length= cache->get_next_key((uchar **) &start_key->key))) - { - start_key->keypart_map= (1 << ref->key_parts) - 1; - start_key->flag= HA_READ_KEY_EXACT; - range->end_key= *start_key; - range->end_key.flag= HA_READ_AFTER_KEY; - range->ptr= (char *) cache->get_curr_rec(); - range->range_flag= EQ_RANGE; - DBUG_RETURN(0); - } - DBUG_RETURN(1); +DBUG_ENTER("bka_range_seq_next"); +JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) rseq; +TABLE_REF *ref= &cache->join_tab->ref; +key_range *start_key= &range->start_key; +if ((start_key->length= cache->get_next_key((uchar **) &start_key->key))) +{ + start_key->keypart_map= (1 << ref->key_parts) - 1; + start_key->flag= HA_READ_KEY_EXACT; + range->end_key= *start_key; + range->end_key.flag= HA_READ_AFTER_KEY; + range->ptr= (char *) cache->get_curr_rec(); + range->range_flag= EQ_RANGE; + DBUG_RETURN(0); +} +DBUG_RETURN(1); } /* - Check whether range_info orders to skip the next record from BKA buffer +Check whether range_info orders to skip the next record from BKA buffer - SYNOPSIS - bka_range_seq_skip_record() - seq value returned by bka_range_seq_init() - range_info information about the next range - rowid [NOT USED] rowid of the record to be checked - - - DESCRIPTION - The function interprets seq as a pointer to a JOIN_CACHE_BKA object. - The function returns TRUE if the record with this range_info - is to be filtered out from the stream of records returned by - multi_range_read_next(). +SYNOPSIS + bka_range_seq_skip_record() + seq value returned by bka_range_seq_init() + range_info information about the next range + rowid [NOT USED] rowid of the record to be checked - NOTE - This function are used only as a callback function. - - RETURN VALUE - 1 record with this range_info is to be filtered out from the stream - of records returned by multi_range_read_next() - 0 the record is to be left in the stream + +DESCRIPTION + The function interprets seq as a pointer to a JOIN_CACHE_BKA object. + The function returns TRUE if the record with this range_info + is to be filtered out from the stream of records returned by + multi_range_read_next(). + +NOTE + This function are used only as a callback function. + +RETURN VALUE + 1 record with this range_info is to be filtered out from the stream + of records returned by multi_range_read_next() + 0 the record is to be left in the stream */ static bool bka_range_seq_skip_record(range_seq_t rseq, range_id_t range_info, uchar *rowid) { - DBUG_ENTER("bka_range_seq_skip_record"); - JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) rseq; - bool res= cache->get_match_flag_by_pos((uchar *) range_info) == - JOIN_CACHE::MATCH_FOUND; - DBUG_RETURN(res); +DBUG_ENTER("bka_range_seq_skip_record"); +JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) rseq; +bool res= cache->get_match_flag_by_pos((uchar *) range_info) == + JOIN_CACHE::MATCH_FOUND; +DBUG_RETURN(res); } /* - Check if the record combination from BKA cache matches the index condition +Check if the record combination from BKA cache matches the index condition - SYNOPSIS - bka_skip_index_tuple() - rseq value returned by bka_range_seq_init() - range_info record chain for the next range/key returned by MRR - - DESCRIPTION - This is wrapper for JOIN_CACHE_BKA::skip_index_tuple method, - see comments there. +SYNOPSIS + bka_skip_index_tuple() + rseq value returned by bka_range_seq_init() + range_info record chain for the next range/key returned by MRR + +DESCRIPTION + This is wrapper for JOIN_CACHE_BKA::skip_index_tuple method, + see comments there. - NOTE - This function is used as a RANGE_SEQ_IF::skip_index_tuple callback. - - RETURN VALUE - 0 The record combination satisfies the index condition - 1 Otherwise +NOTE + This function is used as a RANGE_SEQ_IF::skip_index_tuple callback. + +RETURN VALUE + 0 The record combination satisfies the index condition + 1 Otherwise */ static bool bka_skip_index_tuple(range_seq_t rseq, range_id_t range_info) { - DBUG_ENTER("bka_skip_index_tuple"); - JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) rseq; - THD *thd= cache->thd(); - bool res; - status_var_increment(thd->status_var.ha_icp_attempts); - if (!(res= cache->skip_index_tuple(range_info))) - status_var_increment(thd->status_var.ha_icp_match); - DBUG_RETURN(res); +DBUG_ENTER("bka_skip_index_tuple"); +JOIN_CACHE_BKA *cache= (JOIN_CACHE_BKA *) rseq; +THD *thd= cache->thd(); +bool res; +status_var_increment(thd->status_var.ha_icp_attempts); +if (!(res= cache->skip_index_tuple(range_info))) + status_var_increment(thd->status_var.ha_icp_match); +DBUG_RETURN(res); } /* - Prepare to read the record from BKA cache matching the current joined record - - SYNOPSIS - prepare_look_for_matches() - skip_last <-> ignore the last record in the buffer (always unused here) - - DESCRIPTION - The function prepares to iterate over records in the join cache buffer - matching the record loaded into the record buffer for join_tab when - performing join operation by BKA join algorithm. With BKA algorithms the - record loaded into the record buffer for join_tab always has a direct - reference to the matching records from the join buffer. When the regular - BKA join algorithm is employed the record from join_tab can refer to - only one such record. - The function sets the counter of the remaining records from the cache - buffer that would match the current join_tab record to 1. - - RETURN VALUE - TRUE there are no records in the buffer to iterate over - FALSE otherwise +Prepare to read the record from BKA cache matching the current joined record + +SYNOPSIS + prepare_look_for_matches() + skip_last <-> ignore the last record in the buffer (always unused here) + +DESCRIPTION + The function prepares to iterate over records in the join cache buffer + matching the record loaded into the record buffer for join_tab when + performing join operation by BKA join algorithm. With BKA algorithms the + record loaded into the record buffer for join_tab always has a direct + reference to the matching records from the join buffer. When the regular + BKA join algorithm is employed the record from join_tab can refer to + only one such record. + The function sets the counter of the remaining records from the cache + buffer that would match the current join_tab record to 1. + +RETURN VALUE + TRUE there are no records in the buffer to iterate over + FALSE otherwise */ - + bool JOIN_CACHE_BKA::prepare_look_for_matches(bool skip_last) { - if (!records) - return TRUE; - rem_records= 1; - return FALSE; +if (!records) + return TRUE; +rem_records= 1; +return FALSE; } /* - Get the record from the BKA cache matching the current joined record - - SYNOPSIS - get_next_candidate_for_match - - DESCRIPTION - This method is used for iterations over the records from the join - cache buffer when looking for matches for records from join_tab. - The method performs the necessary preparations to read the next record - from the join buffer into the record buffer by the method - read_next_candidate_for_match, or, to skip the next record from the join - buffer by the method skip_if_not_needed_match. - This implementation of the virtual method get_next_candidate_for_match - just decrements the counter of the records that are to be iterated over - and returns the value of curr_association as a reference to the position - of the beginning of the record fields in the buffer. - - RETURN VALUE - pointer to the start of the record fields in the join buffer - if the there is another record to iterate over, 0 - otherwise. +Get the record from the BKA cache matching the current joined record + +SYNOPSIS + get_next_candidate_for_match + +DESCRIPTION + This method is used for iterations over the records from the join + cache buffer when looking for matches for records from join_tab. + The method performs the necessary preparations to read the next record + from the join buffer into the record buffer by the method + read_next_candidate_for_match, or, to skip the next record from the join + buffer by the method skip_if_not_needed_match. + This implementation of the virtual method get_next_candidate_for_match + just decrements the counter of the records that are to be iterated over + and returns the value of curr_association as a reference to the position + of the beginning of the record fields in the buffer. + +RETURN VALUE + pointer to the start of the record fields in the join buffer + if the there is another record to iterate over, 0 - otherwise. */ uchar *JOIN_CACHE_BKA::get_next_candidate_for_match() { - if (!rem_records) - return 0; - rem_records--; - return curr_association; +if (!rem_records) + return 0; +rem_records--; +return curr_association; } /* - Check whether the matching record from the BKA cache is to be skipped - - SYNOPSIS - skip_next_candidate_for_match - rec_ptr pointer to the position in the join buffer right after - the previous record - - DESCRIPTION - This implementation of the virtual function just calls the - method get_match_flag_by_pos to check whether the record referenced - by ref_ptr has its match flag set to MATCH_FOUND. - - RETURN VALUE - TRUE the record referenced by rec_ptr has its match flag set to - MATCH_FOUND - FALSE otherwise +Check whether the matching record from the BKA cache is to be skipped + +SYNOPSIS + skip_next_candidate_for_match + rec_ptr pointer to the position in the join buffer right after + the previous record + +DESCRIPTION + This implementation of the virtual function just calls the + method get_match_flag_by_pos to check whether the record referenced + by ref_ptr has its match flag set to MATCH_FOUND. + +RETURN VALUE + TRUE the record referenced by rec_ptr has its match flag set to + MATCH_FOUND + FALSE otherwise */ bool JOIN_CACHE_BKA::skip_next_candidate_for_match(uchar *rec_ptr) { - return join_tab->check_only_first_match() && - (get_match_flag_by_pos(rec_ptr) == MATCH_FOUND); +return join_tab->check_only_first_match() && + (get_match_flag_by_pos(rec_ptr) == MATCH_FOUND); } /* - Read the next record from the BKA join cache buffer when looking for matches - - SYNOPSIS - read_next_candidate_for_match - rec_ptr pointer to the position in the join buffer right after - the previous record - - DESCRIPTION - This implementation of the virtual method read_next_candidate_for_match - calls the method get_record_by_pos to read the record referenced by rec_ptr - from the join buffer into the record buffer. If this record refers to - fields in the other join buffers the call of get_record_by_po ensures that - these fields are read into the corresponding record buffers as well. - This function is supposed to be called after a successful call of - the method get_next_candidate_for_match. - - RETURN VALUE - none +Read the next record from the BKA join cache buffer when looking for matches + +SYNOPSIS + read_next_candidate_for_match + rec_ptr pointer to the position in the join buffer right after + the previous record + +DESCRIPTION + This implementation of the virtual method read_next_candidate_for_match + calls the method get_record_by_pos to read the record referenced by rec_ptr + from the join buffer into the record buffer. If this record refers to + fields in the other join buffers the call of get_record_by_po ensures that + these fields are read into the corresponding record buffers as well. + This function is supposed to be called after a successful call of + the method get_next_candidate_for_match. + +RETURN VALUE + none */ void JOIN_CACHE_BKA::read_next_candidate_for_match(uchar *rec_ptr) { - get_record_by_pos(rec_ptr); +get_record_by_pos(rec_ptr); } /* - Initialize the BKA join cache +Initialize the BKA join cache - SYNOPSIS - init +SYNOPSIS + init + for_explain join buffer is initialized for explain only - DESCRIPTION - The function initializes the cache structure. It is supposed to be called - right after a constructor for the JOIN_CACHE_BKA. - NOTES - The function first constructs a companion object of the type - JOIN_TAB_SCAN_MRR, then it calls the init method of the parent class. - - RETURN VALUE - 0 initialization with buffer allocations has been succeeded - 1 otherwise +DESCRIPTION + The function initializes the cache structure. It is supposed to be called + right after a constructor for the JOIN_CACHE_BKA. + +NOTES + The function first constructs a companion object of the type + JOIN_TAB_SCAN_MRR, then it calls the init method of the parent class. + +RETURN VALUE + 0 initialization with buffer allocations has been succeeded + 1 otherwise */ -int JOIN_CACHE_BKA::init() +int JOIN_CACHE_BKA::init(bool for_explain) { - int res; - bool check_only_first_match= join_tab->check_only_first_match(); +int res; +bool check_only_first_match= join_tab->check_only_first_match(); - RANGE_SEQ_IF rs_funcs= { bka_range_seq_key_info, - bka_range_seq_init, - bka_range_seq_next, - check_only_first_match ? - bka_range_seq_skip_record : 0, - bka_skip_index_tuple }; +RANGE_SEQ_IF rs_funcs= { bka_range_seq_key_info, + bka_range_seq_init, + bka_range_seq_next, + check_only_first_match ? + bka_range_seq_skip_record : 0, + bka_skip_index_tuple }; - DBUG_ENTER("JOIN_CACHE_BKA::init"); +DBUG_ENTER("JOIN_CACHE_BKA::init"); - JOIN_TAB_SCAN_MRR *jsm; - if (!(join_tab_scan= jsm= new JOIN_TAB_SCAN_MRR(join, join_tab, - mrr_mode, rs_funcs))) - DBUG_RETURN(1); +JOIN_TAB_SCAN_MRR *jsm; +if (!(join_tab_scan= jsm= new JOIN_TAB_SCAN_MRR(join, join_tab, + mrr_mode, rs_funcs))) + DBUG_RETURN(1); - if ((res= JOIN_CACHE::init())) - DBUG_RETURN(res); +if ((res= JOIN_CACHE::init(for_explain))) + DBUG_RETURN(res); - if (use_emb_key) - jsm->mrr_mode |= HA_MRR_MATERIALIZED_KEYS; +if (use_emb_key) + jsm->mrr_mode |= HA_MRR_MATERIALIZED_KEYS; - DBUG_RETURN(0); +DBUG_RETURN(0); } /* - Get the key built over the next record from BKA join buffer - - SYNOPSIS - get_next_key() - key pointer to the buffer where the key value is to be placed - - DESCRIPTION - The function reads key fields from the current record in the join buffer. - and builds the key value out of these fields that will be used to access - the 'join_tab' table. Some of key fields may belong to previous caches. - They are accessed via record references to the record parts stored in the - previous join buffers. The other key fields always are placed right after - the flag fields of the record. - If the key is embedded, which means that its value can be read directly - from the join buffer, then *key is set to the beginning of the key in - this buffer. Otherwise the key is built in the join_tab->ref->key_buff. - The function returns the length of the key if it succeeds ro read it. - If is assumed that the functions starts reading at the position of - the record length which is provided for each records in a BKA cache. - After the key is built the 'pos' value points to the first position after - the current record. - The function just skips the records with MATCH_IMPOSSIBLE in the - match flag field if there is any. - The function returns 0 if the initial position is after the beginning - of the record fields for last record from the join buffer. - - RETURN VALUE - length of the key value - if the starting value of 'pos' points to - the position before the fields for the last record, - 0 - otherwise. +Get the key built over the next record from BKA join buffer + +SYNOPSIS + get_next_key() + key pointer to the buffer where the key value is to be placed + +DESCRIPTION + The function reads key fields from the current record in the join buffer. + and builds the key value out of these fields that will be used to access + the 'join_tab' table. Some of key fields may belong to previous caches. + They are accessed via record references to the record parts stored in the + previous join buffers. The other key fields always are placed right after + the flag fields of the record. + If the key is embedded, which means that its value can be read directly + from the join buffer, then *key is set to the beginning of the key in + this buffer. Otherwise the key is built in the join_tab->ref->key_buff. + The function returns the length of the key if it succeeds ro read it. + If is assumed that the functions starts reading at the position of + the record length which is provided for each records in a BKA cache. + After the key is built the 'pos' value points to the first position after + the current record. + The function just skips the records with MATCH_IMPOSSIBLE in the + match flag field if there is any. + The function returns 0 if the initial position is after the beginning + of the record fields for last record from the join buffer. + +RETURN VALUE + length of the key value - if the starting value of 'pos' points to + the position before the fields for the last record, + 0 - otherwise. */ uint JOIN_CACHE_BKA::get_next_key(uchar ** key) { - uint len; - uint32 rec_len; - uchar *init_pos; - JOIN_CACHE *cache; - +uint len; +uint32 rec_len; +uchar *init_pos; +JOIN_CACHE *cache; + start: - /* Any record in a BKA cache is prepended with its length */ - DBUG_ASSERT(with_length); - - if ((pos+size_of_rec_len) > last_rec_pos || !records) - return 0; +/* Any record in a BKA cache is prepended with its length */ +DBUG_ASSERT(with_length); + +if ((pos+size_of_rec_len) > last_rec_pos || !records) + return 0; - /* Read the length of the record */ - rec_len= get_rec_length(pos); - pos+= size_of_rec_len; - init_pos= pos; +/* Read the length of the record */ +rec_len= get_rec_length(pos); +pos+= size_of_rec_len; +init_pos= pos; - /* Read a reference to the previous cache if any */ - if (prev_cache) - pos+= prev_cache->get_size_of_rec_offset(); +/* Read a reference to the previous cache if any */ +if (prev_cache) + pos+= prev_cache->get_size_of_rec_offset(); - curr_rec_pos= pos; +curr_rec_pos= pos; - /* Read all flag fields of the record */ - read_flag_fields(); +/* Read all flag fields of the record */ +read_flag_fields(); - if (with_match_flag && - (Match_flag) curr_rec_pos[0] == MATCH_IMPOSSIBLE ) - { - pos= init_pos+rec_len; - goto start; - } - - if (use_emb_key) - { - /* An embedded key is taken directly from the join buffer */ - *key= pos; - len= emb_key_length; - } - else +if (with_match_flag && + (Match_flag) curr_rec_pos[0] == MATCH_IMPOSSIBLE ) +{ + pos= init_pos+rec_len; + goto start; +} + +if (use_emb_key) +{ + /* An embedded key is taken directly from the join buffer */ + *key= pos; + len= emb_key_length; +} +else +{ + /* Read key arguments from previous caches if there are any such fields */ + if (external_key_arg_fields) { - /* Read key arguments from previous caches if there are any such fields */ - if (external_key_arg_fields) - { - uchar *rec_ptr= curr_rec_pos; - uint key_arg_count= external_key_arg_fields; - CACHE_FIELD **copy_ptr= blob_ptr-key_arg_count; - for (cache= prev_cache; key_arg_count; cache= cache->prev_cache) - { - uint len= 0; + uchar *rec_ptr= curr_rec_pos; + uint key_arg_count= external_key_arg_fields; + CACHE_FIELD **copy_ptr= blob_ptr-key_arg_count; + for (cache= prev_cache; key_arg_count; cache= cache->prev_cache) + { + uint len= 0; + DBUG_ASSERT(cache); + rec_ptr= cache->get_rec_ref(rec_ptr); + while (!cache->referenced_fields) + { + cache= cache->prev_cache; DBUG_ASSERT(cache); rec_ptr= cache->get_rec_ref(rec_ptr); - while (!cache->referenced_fields) - { - cache= cache->prev_cache; - DBUG_ASSERT(cache); - rec_ptr= cache->get_rec_ref(rec_ptr); - } - while (key_arg_count && - cache->read_referenced_field(*copy_ptr, rec_ptr, &len)) - { - copy_ptr++; - --key_arg_count; - } + } + while (key_arg_count && + cache->read_referenced_field(*copy_ptr, rec_ptr, &len)) + { + copy_ptr++; + --key_arg_count; } } - - /* - Read the other key arguments from the current record. The fields for - these arguments are always first in the sequence of the record's fields. - */ - CACHE_FIELD *copy= field_descr+flag_fields; - CACHE_FIELD *copy_end= copy+local_key_arg_fields; - bool blob_in_rec_buff= blob_data_is_in_rec_buff(curr_rec_pos); - for ( ; copy < copy_end; copy++) - read_record_field(copy, blob_in_rec_buff); - - /* Build the key over the fields read into the record buffers */ - TABLE_REF *ref= &join_tab->ref; - cp_buffer_from_ref(join->thd, join_tab->table, ref); - *key= ref->key_buff; - len= ref->key_length; } + + /* + Read the other key arguments from the current record. The fields for + these arguments are always first in the sequence of the record's fields. + */ + CACHE_FIELD *copy= field_descr+flag_fields; + CACHE_FIELD *copy_end= copy+local_key_arg_fields; + bool blob_in_rec_buff= blob_data_is_in_rec_buff(curr_rec_pos); + for ( ; copy < copy_end; copy++) + read_record_field(copy, blob_in_rec_buff); + + /* Build the key over the fields read into the record buffers */ + TABLE_REF *ref= &join_tab->ref; + cp_buffer_from_ref(join->thd, join_tab->table, ref); + *key= ref->key_buff; + len= ref->key_length; +} - pos= init_pos+rec_len; +pos= init_pos+rec_len; - return len; +return len; } /* - Check the index condition of the joined table for a record from the BKA cache +Check the index condition of the joined table for a record from the BKA cache - SYNOPSIS - skip_index_tuple() - range_info pointer to the record returned by MRR - - DESCRIPTION - This function is invoked from MRR implementation to check if an index - tuple matches the index condition. It is used in the case where the index - condition actually depends on both columns of the used index and columns - from previous tables. - - NOTES - Accessing columns of the previous tables requires special handling with - BKA. The idea of BKA is to collect record combinations in a buffer and - then do a batch of ref access lookups, i.e. by the time we're doing a - lookup its previous-records-combination is not in prev_table->record[0] - but somewhere in the join buffer. - We need to get it from there back into prev_table(s)->record[0] before we - can evaluate the index condition, and that's why we need this function - instead of regular IndexConditionPushdown. - - NOTES - Possible optimization: - Before we unpack the record from a previous table - check if this table is used in the condition. - If so then unpack the record otherwise skip the unpacking. - This should be done by a special virtual method - get_partial_record_by_pos(). - - RETURN VALUE - 1 the record combination does not satisfies the index condition - 0 otherwise +SYNOPSIS + skip_index_tuple() + range_info pointer to the record returned by MRR + +DESCRIPTION + This function is invoked from MRR implementation to check if an index + tuple matches the index condition. It is used in the case where the index + condition actually depends on both columns of the used index and columns + from previous tables. + +NOTES + Accessing columns of the previous tables requires special handling with + BKA. The idea of BKA is to collect record combinations in a buffer and + then do a batch of ref access lookups, i.e. by the time we're doing a + lookup its previous-records-combination is not in prev_table->record[0] + but somewhere in the join buffer. + We need to get it from there back into prev_table(s)->record[0] before we + can evaluate the index condition, and that's why we need this function + instead of regular IndexConditionPushdown. + +NOTES + Possible optimization: + Before we unpack the record from a previous table + check if this table is used in the condition. + If so then unpack the record otherwise skip the unpacking. + This should be done by a special virtual method + get_partial_record_by_pos(). + +RETURN VALUE + 1 the record combination does not satisfies the index condition + 0 otherwise */ bool JOIN_CACHE_BKA::skip_index_tuple(range_id_t range_info) { - DBUG_ENTER("JOIN_CACHE_BKA::skip_index_tuple"); - get_record_by_pos((uchar*)range_info); - DBUG_RETURN(!join_tab->cache_idx_cond->val_int()); +DBUG_ENTER("JOIN_CACHE_BKA::skip_index_tuple"); +get_record_by_pos((uchar*)range_info); +DBUG_RETURN(!join_tab->cache_idx_cond->val_int()); } /* - Initialize retrieval of range sequence for the BKAH join algorithm - - SYNOPSIS - bkah_range_seq_init() - init_params pointer to the BKAH join cache object - n_ranges the number of ranges obtained - flags combination of MRR flags - - DESCRIPTION - The function interprets init_param as a pointer to a JOIN_CACHE_BKAH - object. The function prepares for an iteration over distinct join keys - built over the records from the cache join buffer. - - NOTE - This function are used only as a callback function. - - RETURN VALUE - init_param value that is to be used as a parameter of - bkah_range_seq_next() +Initialize retrieval of range sequence for the BKAH join algorithm + +SYNOPSIS + bkah_range_seq_init() + init_params pointer to the BKAH join cache object + n_ranges the number of ranges obtained + flags combination of MRR flags + +DESCRIPTION + The function interprets init_param as a pointer to a JOIN_CACHE_BKAH + object. The function prepares for an iteration over distinct join keys + built over the records from the cache join buffer. + +NOTE + This function are used only as a callback function. + +RETURN VALUE + init_param value that is to be used as a parameter of + bkah_range_seq_next() */ static range_seq_t bkah_range_seq_init(void *init_param, uint n_ranges, uint flags) { - DBUG_ENTER("bkah_range_seq_init"); - JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) init_param; - cache->reset(0); - DBUG_RETURN((range_seq_t) init_param); +DBUG_ENTER("bkah_range_seq_init"); +JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) init_param; +cache->reset(0); +DBUG_RETURN((range_seq_t) init_param); } /* - Get the next range/key over records from the join buffer of a BKAH cache - - SYNOPSIS - bkah_range_seq_next() - seq value returned by bkah_range_seq_init() - range OUT reference to the next range +Get the next range/key over records from the join buffer of a BKAH cache - DESCRIPTION - The function interprets seq as a pointer to a JOIN_CACHE_BKAH - object. The function returns a pointer to the range descriptor - for the next unique key built over records from the join buffer. - - NOTE - This function are used only as a callback function. - - RETURN VALUE - FALSE ok, the range structure filled with info about the next range/key - TRUE no more ranges +SYNOPSIS + bkah_range_seq_next() + seq value returned by bkah_range_seq_init() + range OUT reference to the next range + +DESCRIPTION + The function interprets seq as a pointer to a JOIN_CACHE_BKAH + object. The function returns a pointer to the range descriptor + for the next unique key built over records from the join buffer. + +NOTE + This function are used only as a callback function. + +RETURN VALUE + FALSE ok, the range structure filled with info about the next range/key + TRUE no more ranges */ static bool bkah_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range) { - DBUG_ENTER("bkah_range_seq_next"); - JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) rseq; - TABLE_REF *ref= &cache->join_tab->ref; - key_range *start_key= &range->start_key; - if ((start_key->length= cache->get_next_key((uchar **) &start_key->key))) - { - start_key->keypart_map= (1 << ref->key_parts) - 1; - start_key->flag= HA_READ_KEY_EXACT; - range->end_key= *start_key; - range->end_key.flag= HA_READ_AFTER_KEY; - range->ptr= (char *) cache->get_curr_key_chain(); - range->range_flag= EQ_RANGE; - DBUG_RETURN(0); - } - DBUG_RETURN(1); +DBUG_ENTER("bkah_range_seq_next"); +JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) rseq; +TABLE_REF *ref= &cache->join_tab->ref; +key_range *start_key= &range->start_key; +if ((start_key->length= cache->get_next_key((uchar **) &start_key->key))) +{ + start_key->keypart_map= (1 << ref->key_parts) - 1; + start_key->flag= HA_READ_KEY_EXACT; + range->end_key= *start_key; + range->end_key.flag= HA_READ_AFTER_KEY; + range->ptr= (char *) cache->get_curr_key_chain(); + range->range_flag= EQ_RANGE; + DBUG_RETURN(0); +} +DBUG_RETURN(1); } /* - Check whether range_info orders to skip the next record from BKAH join buffer - - SYNOPSIS - bkah_range_seq_skip_record() - seq value returned by bkah_range_seq_init() - range_info information about the next range/key returned by MRR - rowid [NOT USED] rowid of the record to be checked (not used) - - DESCRIPTION - The function interprets seq as a pointer to a JOIN_CACHE_BKAH - object. The function returns TRUE if the record with this range_info - is to be filtered out from the stream of records returned by - multi_range_read_next(). +Check whether range_info orders to skip the next record from BKAH join buffer - NOTE - This function are used only as a callback function. - - RETURN VALUE - 1 record with this range_info is to be filtered out from the stream - of records returned by multi_range_read_next() - 0 the record is to be left in the stream +SYNOPSIS + bkah_range_seq_skip_record() + seq value returned by bkah_range_seq_init() + range_info information about the next range/key returned by MRR + rowid [NOT USED] rowid of the record to be checked (not used) + +DESCRIPTION + The function interprets seq as a pointer to a JOIN_CACHE_BKAH + object. The function returns TRUE if the record with this range_info + is to be filtered out from the stream of records returned by + multi_range_read_next(). + +NOTE + This function are used only as a callback function. + +RETURN VALUE + 1 record with this range_info is to be filtered out from the stream + of records returned by multi_range_read_next() + 0 the record is to be left in the stream */ static bool bkah_range_seq_skip_record(range_seq_t rseq, range_id_t range_info, - uchar *rowid) + uchar *rowid) { - DBUG_ENTER("bkah_range_seq_skip_record"); - JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) rseq; - bool res= cache->check_all_match_flags_for_key((uchar *) range_info); - DBUG_RETURN(res); +DBUG_ENTER("bkah_range_seq_skip_record"); +JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) rseq; +bool res= cache->check_all_match_flags_for_key((uchar *) range_info); +DBUG_RETURN(res); } - + /* - Check if the record combination from BKAH cache matches the index condition +Check if the record combination from BKAH cache matches the index condition - SYNOPSIS - bkah_skip_index_tuple() - rseq value returned by bka_range_seq_init() - range_info record chain for the next range/key returned by MRR - - DESCRIPTION - This is wrapper for JOIN_CACHE_BKA_UNIQUE::skip_index_tuple method, - see comments there. +SYNOPSIS + bkah_skip_index_tuple() + rseq value returned by bka_range_seq_init() + range_info record chain for the next range/key returned by MRR + +DESCRIPTION + This is wrapper for JOIN_CACHE_BKA_UNIQUE::skip_index_tuple method, + see comments there. - NOTE - This function is used as a RANGE_SEQ_IF::skip_index_tuple callback. - - RETURN VALUE - 0 some records from the chain satisfy the index condition - 1 otherwise +NOTE + This function is used as a RANGE_SEQ_IF::skip_index_tuple callback. + +RETURN VALUE + 0 some records from the chain satisfy the index condition + 1 otherwise */ static bool bkah_skip_index_tuple(range_seq_t rseq, range_id_t range_info) { - DBUG_ENTER("bka_unique_skip_index_tuple"); - JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) rseq; - THD *thd= cache->thd(); - bool res; - status_var_increment(thd->status_var.ha_icp_attempts); - if (!(res= cache->skip_index_tuple(range_info))) - status_var_increment(thd->status_var.ha_icp_match); - DBUG_RETURN(res); +DBUG_ENTER("bka_unique_skip_index_tuple"); +JOIN_CACHE_BKAH *cache= (JOIN_CACHE_BKAH *) rseq; +THD *thd= cache->thd(); +bool res; +status_var_increment(thd->status_var.ha_icp_attempts); +if (!(res= cache->skip_index_tuple(range_info))) + status_var_increment(thd->status_var.ha_icp_match); +DBUG_RETURN(res); } /* - Prepare to read record from BKAH cache matching the current joined record - - SYNOPSIS - prepare_look_for_matches() - skip_last <-> ignore the last record in the buffer (always unused here) - - DESCRIPTION - The function prepares to iterate over records in the join cache buffer - matching the record loaded into the record buffer for join_tab when - performing join operation by BKAH join algorithm. With BKAH algorithm, if - association labels are used, then record loaded into the record buffer - for join_tab always has a direct reference to the chain of the mathing - records from the join buffer. If association labels are not used then - then the chain of the matching records is obtained by the call of the - get_key_chain_by_join_key function. - - RETURN VALUE - TRUE there are no records in the buffer to iterate over - FALSE otherwise +Prepare to read record from BKAH cache matching the current joined record + +SYNOPSIS + prepare_look_for_matches() + skip_last <-> ignore the last record in the buffer (always unused here) + +DESCRIPTION + The function prepares to iterate over records in the join cache buffer + matching the record loaded into the record buffer for join_tab when + performing join operation by BKAH join algorithm. With BKAH algorithm, if + association labels are used, then record loaded into the record buffer + for join_tab always has a direct reference to the chain of the mathing + records from the join buffer. If association labels are not used then + then the chain of the matching records is obtained by the call of the + get_key_chain_by_join_key function. + +RETURN VALUE + TRUE there are no records in the buffer to iterate over + FALSE otherwise */ - + bool JOIN_CACHE_BKAH::prepare_look_for_matches(bool skip_last) { - last_matching_rec_ref_ptr= next_matching_rec_ref_ptr= 0; - if (no_association && - !(curr_matching_chain= get_matching_chain_by_join_key())) +last_matching_rec_ref_ptr= next_matching_rec_ref_ptr= 0; +if (no_association && + !(curr_matching_chain= get_matching_chain_by_join_key())) //psergey: added '!' return 1; last_matching_rec_ref_ptr= get_next_rec_ref(curr_matching_chain); return 0; @@ -4581,6 +4610,7 @@ bool JOIN_CACHE_BKAH::prepare_look_for_matches(bool skip_last) SYNOPSIS init + for_explain join buffer is initialized for explain only DESCRIPTION The function initializes the cache structure. It is supposed to be called @@ -4595,11 +4625,11 @@ bool JOIN_CACHE_BKAH::prepare_look_for_matches(bool skip_last) 1 otherwise */ -int JOIN_CACHE_BKAH::init() +int JOIN_CACHE_BKAH::init(bool for_explain) { bool check_only_first_match= join_tab->check_only_first_match(); - no_association= test(mrr_mode & HA_MRR_NO_ASSOCIATION); + no_association= MY_TEST(mrr_mode & HA_MRR_NO_ASSOCIATION); RANGE_SEQ_IF rs_funcs= { bka_range_seq_key_info, bkah_range_seq_init, @@ -4614,7 +4644,7 @@ int JOIN_CACHE_BKAH::init() mrr_mode, rs_funcs))) DBUG_RETURN(1); - DBUG_RETURN(JOIN_CACHE_HASHED::init()); + DBUG_RETURN(JOIN_CACHE_HASHED::init(for_explain)); } diff --git a/sql/sql_join_cache.h b/sql/sql_join_cache.h index 6953f6881ee..4ae843ebfc2 100644 --- a/sql/sql_join_cache.h +++ b/sql/sql_join_cache.h @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* This file contains declarations for implementations @@ -63,12 +63,14 @@ typedef struct st_cache_field { class JOIN_TAB_SCAN; +class EXPLAIN_BKA_TYPE; /* JOIN_CACHE is the base class to support the implementations of - Block Nested Loop (BNL) Join Algorithm, - Block Nested Loop Hash (BNLH) Join Algorithm, - Batched Key Access (BKA) Join Algorithm. + The first algorithm is supported by the derived class JOIN_CACHE_BNL, the second algorithm is supported by the derived class JOIN_CACHE_BNLH, while the third algorithm is implemented in two variant supported by @@ -82,7 +84,7 @@ class JOIN_TAB_SCAN; For the third algorithm the accumulation of records allows to optimize fetching rows of the second operand from disk for some engines (MyISAM, InnoDB), or to minimize the number of round-trips between the Server and - the engine nodes (NDB Cluster). + the engine nodes. */ class JOIN_CACHE :public Sql_alloc @@ -97,6 +99,9 @@ private: /* Size of the offset of a field within a record in the cache */ uint size_of_fld_ofs; + /* This structure is used only for explain, not for execution */ + bool for_explain_only; + protected: /* 3 functions below actually do not use the hidden parameter 'this' */ @@ -420,7 +425,7 @@ protected: /* Shall calculate how much space is remaining in the join buffer */ virtual size_t rem_space() { - return max(buff_size-(end_pos-buff)-aux_buff_size,0); + return MY_MAX(buff_size-(end_pos-buff)-aux_buff_size,0); } /* @@ -593,7 +598,7 @@ public: JOIN_CACHE *next_cache; /* Shall initialize the join cache structure */ - virtual int init(); + virtual int init(bool for_explain); /* Get the current size of the cache join buffer */ size_t get_join_buffer_size() { return buff_size; } @@ -657,7 +662,7 @@ public: enum_nested_loop_state join_records(bool skip_last); /* Add a comment on the join algorithm employed by the join cache */ - virtual void print_explain_comment(String *str); + virtual void save_explain_data(EXPLAIN_BKA_TYPE *explain); THD *thd(); @@ -943,7 +948,7 @@ protected: */ size_t rem_space() { - return max(last_key_entry-end_pos-aux_buff_size,0); + return MY_MAX(last_key_entry-end_pos-aux_buff_size,0); } /* @@ -989,7 +994,7 @@ protected: public: /* Initialize a hashed join cache */ - int init(); + int init(bool for_explain); /* Reset the buffer of a hashed join cache for reading/writing */ void reset(bool for_writing); @@ -1125,7 +1130,7 @@ public: :JOIN_CACHE(j, tab, prev) {} /* Initialize the BNL cache */ - int init(); + int init(bool for_explain); enum Join_algorithm get_join_alg() { return BNL_JOIN_ALG; } @@ -1192,7 +1197,7 @@ public: : JOIN_CACHE_HASHED(j, tab, prev) {} /* Initialize the BNLH cache */ - int init(); + int init(bool for_explain); enum Join_algorithm get_join_alg() { return BNLH_JOIN_ALG; } @@ -1323,7 +1328,7 @@ public: uchar **get_curr_association_ptr() { return &curr_association; } /* Initialize the BKA cache */ - int init(); + int init(bool for_explain); enum Join_algorithm get_join_alg() { return BKA_JOIN_ALG; } @@ -1335,7 +1340,7 @@ public: /* Check index condition of the joined table for a record from BKA cache */ bool skip_index_tuple(range_id_t range_info); - void print_explain_comment(String *str); + void save_explain_data(EXPLAIN_BKA_TYPE *explain); }; @@ -1419,12 +1424,12 @@ public: uchar **get_curr_association_ptr() { return &curr_matching_chain; } /* Initialize the BKAH cache */ - int init(); + int init(bool for_explain); enum Join_algorithm get_join_alg() { return BKAH_JOIN_ALG; } /* Check index condition of the joined table for a record from BKAH cache */ bool skip_index_tuple(range_id_t range_info); - void print_explain_comment(String *str); + void save_explain_data(EXPLAIN_BKA_TYPE *explain); }; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 7d694f90aa2..df868d0321f 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -18,8 +18,8 @@ /* A lexical scanner on a temporary buffer with a yacc interface */ #define MYSQL_LEX 1 +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_class.h" // sql_lex.h: SQLCOM_END #include "sql_lex.h" #include "sql_parse.h" // add_to_list @@ -280,7 +280,6 @@ void Lex_input_stream::reset(char *buffer, unsigned int length) { yylineno= 1; - yytoklen= 0; yylval= NULL; lookahead_token= -1; lookahead_yylval= NULL; @@ -299,7 +298,7 @@ Lex_input_stream::reset(char *buffer, unsigned int length) m_cpp_utf8_processed_ptr= NULL; next_state= MY_LEX_START; found_semicolon= NULL; - ignore_space= test(m_thd->variables.sql_mode & MODE_IGNORE_SPACE); + ignore_space= MY_TEST(m_thd->variables.sql_mode & MODE_IGNORE_SPACE); stmt_prepare_mode= FALSE; multi_statements= TRUE; in_comment=NO_COMMENT; @@ -325,9 +324,7 @@ void Lex_input_stream::body_utf8_start(THD *thd, const char *begin_ptr) DBUG_ASSERT(begin_ptr); DBUG_ASSERT(m_cpp_buf <= begin_ptr && begin_ptr <= m_cpp_buf + m_buf_length); - uint body_utf8_length= - (m_buf_length / thd->variables.character_set_client->mbminlen) * - my_charset_utf8_bin.mbmaxlen; + uint body_utf8_length= get_body_utf8_maximum_length(thd); m_body_utf8= (char *) thd->alloc(body_utf8_length + 1); m_body_utf8_ptr= m_body_utf8; @@ -336,6 +333,22 @@ void Lex_input_stream::body_utf8_start(THD *thd, const char *begin_ptr) m_cpp_utf8_processed_ptr= begin_ptr; } + +uint Lex_input_stream::get_body_utf8_maximum_length(THD *thd) +{ + /* + String literals can grow during escaping: + 1a. Character string '<TAB>' can grow to '\t', 3 bytes to 4 bytes growth. + 1b. Character string '1000 times <TAB>' grows from + 1002 to 2002 bytes (including quotes), which gives a little bit + less than 2 times growth. + "2" should be a reasonable multiplier that safely covers escaping needs. + */ + return (m_buf_length / thd->variables.character_set_client->mbminlen) * + my_charset_utf8_bin.mbmaxlen * 2/*for escaping*/; +} + + /** @brief The operation appends unprocessed part of pre-processed buffer till the given pointer (ptr) and sets m_cpp_utf8_processed_ptr to end_ptr. @@ -403,15 +416,15 @@ void Lex_input_stream::body_utf8_append(const char *ptr) operation. */ -void Lex_input_stream::body_utf8_append_literal(THD *thd, - const LEX_STRING *txt, - CHARSET_INFO *txt_cs, - const char *end_ptr) +void Lex_input_stream::body_utf8_append_ident(THD *thd, + const LEX_STRING *txt, + const char *end_ptr) { if (!m_cpp_utf8_processed_ptr) return; LEX_STRING utf_txt; + CHARSET_INFO *txt_cs= thd->charset(); if (!my_charset_same(txt_cs, &my_charset_utf8_general_ci)) { @@ -436,6 +449,204 @@ void Lex_input_stream::body_utf8_append_literal(THD *thd, } + + +extern "C" { + +/** + Escape a character. Consequently puts "escape" and "wc" characters into + the destination utf8 string. + @param cs - the character set (utf8) + @param escape - the escape character (backslash, single quote, double quote) + @param wc - the character to be escaped + @param str - the destination string + @param end - the end of the destination string + @returns - a code according to the wc_mb() convension. +*/ +int my_wc_mb_utf8_with_escape(CHARSET_INFO *cs, my_wc_t escape, my_wc_t wc, + uchar *str, uchar *end) +{ + DBUG_ASSERT(escape > 0); + if (str + 1 >= end) + return MY_CS_TOOSMALL2; // Not enough space, need at least two bytes. + *str= (uchar)escape; + int cnvres= my_charset_utf8_handler.wc_mb(cs, wc, str + 1, end); + if (cnvres > 0) + return cnvres + 1; // The character was normally put + if (cnvres == MY_CS_ILUNI) + return MY_CS_ILUNI; // Could not encode "wc" (e.g. non-BMP character) + DBUG_ASSERT(cnvres <= MY_CS_TOOSMALL); + return cnvres - 1; // Not enough space +} + + +/** + Optionally escape a character. + If "escape" is non-zero, then both "escape" and "wc" are put to + the destination string. Otherwise, only "wc" is put. + @param cs - the character set (utf8) + @param wc - the character to be optionally escaped + @param escape - the escape character, or 0 + @param ewc - the escaped replacement of "wc" (e.g. 't' for '\t') + @param str - the destination string + @param end - the end of the destination string + @returns - a code according to the wc_mb() conversion. +*/ +int my_wc_mb_utf8_opt_escape(CHARSET_INFO *cs, + my_wc_t wc, my_wc_t escape, my_wc_t ewc, + uchar *str, uchar *end) +{ + return escape ? my_wc_mb_utf8_with_escape(cs, escape, ewc, str, end) : + my_charset_utf8_handler.wc_mb(cs, wc, str, end); +} + +/** + Encode a character with optional backlash escaping and quote escaping. + Quote marks are escaped using another quote mark. + Additionally, if "escape" is non-zero, then special characters are + also escaped using "escape". + Otherwise (if "escape" is zero, e.g. in case of MODE_NO_BACKSLASH_ESCAPES), + then special characters are not escaped and handled as normal characters. + + @param cs - the character set (utf8) + @param wc - the character to be encoded + @param str - the destination string + @param end - the end of the destination string + @param sep - the string delimiter (e.g. ' or ") + @param escape - the escape character (backslash, or 0) + @returns - a code according to the wc_mb() convension. +*/ +int my_wc_mb_utf8_escape(CHARSET_INFO *cs, my_wc_t wc, uchar *str, uchar *end, + my_wc_t sep, my_wc_t escape) +{ + DBUG_ASSERT(escape == 0 || escape == '\\'); + DBUG_ASSERT(sep == '"' || sep == '\''); + switch (wc) { + case 0: return my_wc_mb_utf8_opt_escape(cs, wc, escape, '0', str, end); + case '\t': return my_wc_mb_utf8_opt_escape(cs, wc, escape, 't', str, end); + case '\r': return my_wc_mb_utf8_opt_escape(cs, wc, escape, 'r', str, end); + case '\n': return my_wc_mb_utf8_opt_escape(cs, wc, escape, 'n', str, end); + case '\032': return my_wc_mb_utf8_opt_escape(cs, wc, escape, 'Z', str, end); + case '\'': + case '\"': + if (wc == sep) + return my_wc_mb_utf8_with_escape(cs, wc, wc, str, end); + } + return my_charset_utf8_handler.wc_mb(cs, wc, str, end); // No escaping needed +} + + +/** wc_mb() compatible routines for all sql_mode and delimiter combinations */ +int my_wc_mb_utf8_escape_single_quote_and_backslash(CHARSET_INFO *cs, + my_wc_t wc, + uchar *str, uchar *end) +{ + return my_wc_mb_utf8_escape(cs, wc, str, end, '\'', '\\'); +} + + +int my_wc_mb_utf8_escape_double_quote_and_backslash(CHARSET_INFO *cs, + my_wc_t wc, + uchar *str, uchar *end) +{ + return my_wc_mb_utf8_escape(cs, wc, str, end, '"', '\\'); +} + + +int my_wc_mb_utf8_escape_single_quote(CHARSET_INFO *cs, my_wc_t wc, + uchar *str, uchar *end) +{ + return my_wc_mb_utf8_escape(cs, wc, str, end, '\'', 0); +} + + +int my_wc_mb_utf8_escape_double_quote(CHARSET_INFO *cs, my_wc_t wc, + uchar *str, uchar *end) +{ + return my_wc_mb_utf8_escape(cs, wc, str, end, '"', 0); +} + +}; // End of extern "C" + + +/** + Get an escaping function, depending on the current sql_mode and the + string separator. +*/ +my_charset_conv_wc_mb +Lex_input_stream::get_escape_func(THD *thd, my_wc_t sep) const +{ + return thd->backslash_escapes() ? + (sep == '"' ? my_wc_mb_utf8_escape_double_quote_and_backslash: + my_wc_mb_utf8_escape_single_quote_and_backslash) : + (sep == '"' ? my_wc_mb_utf8_escape_double_quote: + my_wc_mb_utf8_escape_single_quote); +} + + +/** + Append a text literal to the end of m_body_utf8. + The string is escaped according to the current sql_mode and the + string delimiter (e.g. ' or "). + + @param thd - current THD + @param txt - the string to be appended to m_body_utf8. + Note, the string must be already unescaped. + @param cs - the character set of the string + @param end_ptr - m_cpp_utf8_processed_ptr will be set to this value + (see body_utf8_append_ident for details) + @param sep - the string delimiter (single or double quote) +*/ +void Lex_input_stream::body_utf8_append_escape(THD *thd, + const LEX_STRING *txt, + CHARSET_INFO *cs, + const char *end_ptr, + my_wc_t sep) +{ + DBUG_ASSERT(sep == '\'' || sep == '"'); + if (!m_cpp_utf8_processed_ptr) + return; + uint errors; + /** + We previously alloced m_body_utf8 to be able to store the query with all + strings properly escaped. See get_body_utf8_maximum_length(). + So here we have guaranteedly enough space to append any string literal + with escaping. Passing txt->length*2 as "available space" is always safe. + For better safety purposes we could calculate get_body_utf8_maximum_length() + every time we append a string, but this would affect performance negatively, + so let's check that we don't get beyond the allocated buffer in + debug build only. + */ + DBUG_ASSERT(m_body_utf8 + get_body_utf8_maximum_length(thd) >= + m_body_utf8_ptr + txt->length * 2); + uint32 cnv_length= my_convert_using_func(m_body_utf8_ptr, txt->length * 2, + &my_charset_utf8_general_ci, + get_escape_func(thd, sep), + txt->str, txt->length, + cs, cs->cset->mb_wc, + &errors); + m_body_utf8_ptr+= cnv_length; + *m_body_utf8_ptr= 0; + m_cpp_utf8_processed_ptr= end_ptr; +} + + +void Lex_input_stream::add_digest_token(uint token, LEX_YYSTYPE yylval) +{ + if (m_digest != NULL) + { + m_digest= digest_add_token(m_digest, token, yylval); + } +} + +void Lex_input_stream::reduce_digest_token(uint token_left, uint token_right) +{ + if (m_digest != NULL) + { + m_digest= digest_reduce_token(m_digest, token_left, token_right); + } +} + /* This is called before every query that is to be parsed. Because of this, it's critical to not do too much things here. @@ -446,26 +657,34 @@ void lex_start(THD *thd) { LEX *lex= thd->lex; DBUG_ENTER("lex_start"); + DBUG_PRINT("info", ("Lex %p", thd->lex)); lex->thd= lex->unit.thd= thd; + lex->stmt_lex= lex; // default, should be rewritten for VIEWs And CTEs + DBUG_ASSERT(!lex->explain); + lex->context_stack.empty(); lex->unit.init_query(); lex->unit.init_select(); /* 'parent_lex' is used in init_query() so it must be before it. */ lex->select_lex.parent_lex= lex; lex->select_lex.init_query(); + lex->current_select_number= 1; lex->value_list.empty(); lex->update_list.empty(); lex->set_var_list.empty(); lex->param_list.empty(); lex->view_list.empty(); + lex->with_persistent_for_clause= FALSE; + lex->column_list= NULL; + lex->index_list= NULL; lex->prepared_stmt_params.empty(); lex->auxiliary_table_list.empty(); lex->unit.next= lex->unit.master= lex->unit.link_next= lex->unit.return_to= 0; lex->unit.prev= lex->unit.link_prev= 0; - lex->unit.slave= lex->unit.global_parameters= lex->current_select= + lex->unit.slave= lex->current_select= lex->all_selects_list= &lex->select_lex; lex->select_lex.master= &lex->unit; lex->select_lex.prev= &lex->unit.slave; @@ -478,6 +697,8 @@ void lex_start(THD *thd) if (lex->select_lex.group_list_ptrs) lex->select_lex.group_list_ptrs->clear(); lex->describe= 0; + lex->analyze_stmt= 0; + lex->explain_json= false; lex->subqueries= FALSE; lex->context_analysis_only= 0; lex->derived_tables= 0; @@ -493,12 +714,11 @@ void lex_start(THD *thd) lex->select_lex.group_list.empty(); lex->select_lex.order_list.empty(); lex->select_lex.gorder_list.empty(); + lex->m_sql_cmd= NULL; lex->duplicates= DUP_ERROR; lex->ignore= 0; lex->spname= NULL; - lex->sphead= NULL; lex->spcont= NULL; - lex->m_stmt= NULL; lex->proc_list.first= 0; lex->escape_used= FALSE; lex->query_tables= 0; @@ -506,37 +726,28 @@ void lex_start(THD *thd) lex->expr_allows_subselect= TRUE; lex->use_only_table_context= FALSE; lex->parse_vcol_expr= FALSE; + lex->check_exists= FALSE; + lex->create_info.lex_start(); + lex->verbose= 0; - lex->name.str= 0; - lex->name.length= 0; + lex->name= null_lex_str; lex->event_parse_data= NULL; lex->profile_options= PROFILE_NONE; lex->nest_level=0 ; lex->select_lex.nest_level_base= &lex->unit; lex->allow_sum_func= 0; lex->in_sum_func= NULL; - /* - ok, there must be a better solution for this, long-term - I tried "bzero" in the sql_yacc.yy code, but that for - some reason made the values zero, even if they were set - */ - lex->server_options.server_name= 0; - lex->server_options.server_name_length= 0; - lex->server_options.host= 0; - lex->server_options.db= 0; - lex->server_options.username= 0; - lex->server_options.password= 0; - lex->server_options.scheme= 0; - lex->server_options.socket= 0; - lex->server_options.owner= 0; - lex->server_options.port= -1; - lex->is_lex_started= TRUE; lex->used_tables= 0; lex->only_view= FALSE; lex->reset_slave_info.all= false; lex->limit_rows_examined= 0; lex->limit_rows_examined_cnt= ULONGLONG_MAX; + lex->var_list.empty(); + lex->stmt_var_list.empty(); + lex->proc_list.elements=0; + + lex->is_lex_started= TRUE; DBUG_VOID_RETURN; } @@ -563,8 +774,20 @@ void lex_end_stage1(LEX *lex) } reset_dynamic(&lex->plugins); - delete lex->sphead; - lex->sphead= NULL; + if (lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_PREPARE) + { + /* + Don't delete lex->sphead, it'll be needed for EXECUTE. + Note that of all statements that populate lex->sphead + only SQLCOM_COMPOUND can be PREPAREd + */ + DBUG_ASSERT(lex->sphead == 0 || lex->sql_command == SQLCOM_COMPOUND); + } + else + { + delete lex->sphead; + lex->sphead= NULL; + } DBUG_VOID_RETURN; } @@ -579,7 +802,8 @@ void lex_end_stage2(LEX *lex) DBUG_ENTER("lex_end_stage2"); /* Reset LEX_MASTER_INFO */ - lex->mi.reset(); + lex->mi.reset(lex->sql_command == SQLCOM_CHANGE_MASTER); + delete_dynamic(&lex->delete_gtid_domain); DBUG_VOID_RETURN; } @@ -657,7 +881,7 @@ static LEX_STRING get_token(Lex_input_stream *lip, uint skip, uint length) { LEX_STRING tmp; lip->yyUnget(); // ptr points now after last token char - tmp.length=lip->yytoklen=length; + tmp.length= length; tmp.str= lip->m_thd->strmake(lip->get_tok_start() + skip, tmp.length); lip->m_cpp_text_start= lip->get_cpp_tok_start() + skip; @@ -681,7 +905,7 @@ static LEX_STRING get_quoted_token(Lex_input_stream *lip, const char *from, *end; char *to; lip->yyUnget(); // ptr points now after last token char - tmp.length= lip->yytoklen=length; + tmp.length= length; tmp.str=(char*) lip->m_thd->alloc(tmp.length+1); from= lip->get_tok_start() + skip; to= tmp.str; @@ -703,135 +927,152 @@ static LEX_STRING get_quoted_token(Lex_input_stream *lip, } +static size_t +my_unescape(CHARSET_INFO *cs, char *to, const char *str, const char *end, + int sep, bool backslash_escapes) +{ + char *start= to; + for ( ; str != end ; str++) + { +#ifdef USE_MB + int l; + if (use_mb(cs) && (l= my_ismbchar(cs, str, end))) + { + while (l--) + *to++ = *str++; + str--; + continue; + } +#endif + if (backslash_escapes && *str == '\\' && str + 1 != end) + { + switch(*++str) { + case 'n': + *to++='\n'; + break; + case 't': + *to++= '\t'; + break; + case 'r': + *to++ = '\r'; + break; + case 'b': + *to++ = '\b'; + break; + case '0': + *to++= 0; // Ascii null + break; + case 'Z': // ^Z must be escaped on Win32 + *to++='\032'; + break; + case '_': + case '%': + *to++= '\\'; // remember prefix for wildcard + /* Fall through */ + default: + *to++= *str; + break; + } + } + else if (*str == sep) + *to++= *str++; // Two ' or " + else + *to++ = *str; + } + *to= 0; + return to - start; +} + + +size_t +Lex_input_stream::unescape(CHARSET_INFO *cs, char *to, + const char *str, const char *end, + int sep) +{ + return my_unescape(cs, to, str, end, sep, m_thd->backslash_escapes()); +} + + /* Return an unescaped text literal without quotes Fix sometimes to do only one scan of the string */ -static char *get_text(Lex_input_stream *lip, int pre_skip, int post_skip) +bool Lex_input_stream::get_text(LEX_STRING *dst, uint sep, + int pre_skip, int post_skip) { - reg1 uchar c,sep; + reg1 uchar c; uint found_escape=0; - CHARSET_INFO *cs= lip->m_thd->charset(); + CHARSET_INFO *cs= m_thd->charset(); - lip->tok_bitmap= 0; - sep= lip->yyGetLast(); // String should end with this - while (! lip->eof()) + tok_bitmap= 0; + while (! eof()) { - c= lip->yyGet(); - lip->tok_bitmap|= c; + c= yyGet(); + tok_bitmap|= c; #ifdef USE_MB { int l; if (use_mb(cs) && (l = my_ismbchar(cs, - lip->get_ptr() -1, - lip->get_end_of_query()))) { - lip->skip_binary(l-1); + get_ptr() -1, + get_end_of_query()))) { + skip_binary(l-1); continue; } } #endif if (c == '\\' && - !(lip->m_thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)) + !(m_thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)) { // Escaped character found_escape=1; - if (lip->eof()) - return 0; - lip->yySkip(); + if (eof()) + return true; + yySkip(); } else if (c == sep) { - if (c == lip->yyGet()) // Check if two separators in a row + if (c == yyGet()) // Check if two separators in a row { found_escape=1; // duplicate. Remember for delete continue; } else - lip->yyUnget(); + yyUnget(); /* Found end. Unescape and return string */ const char *str, *end; - char *start; - str= lip->get_tok_start(); - end= lip->get_ptr(); + str= get_tok_start(); + end= get_ptr(); /* Extract the text from the token */ str += pre_skip; end -= post_skip; DBUG_ASSERT(end >= str); - if (!(start= (char*) lip->m_thd->alloc((uint) (end-str)+1))) - return (char*) ""; // Sql_alloc has set error flag + if (!(dst->str= (char*) m_thd->alloc((uint) (end - str) + 1))) + { + dst->str= (char*) ""; // Sql_alloc has set error flag + dst->length= 0; + return true; + } - lip->m_cpp_text_start= lip->get_cpp_tok_start() + pre_skip; - lip->m_cpp_text_end= lip->get_cpp_ptr() - post_skip; + m_cpp_text_start= get_cpp_tok_start() + pre_skip; + m_cpp_text_end= get_cpp_ptr() - post_skip; if (!found_escape) { - lip->yytoklen=(uint) (end-str); - memcpy(start,str,lip->yytoklen); - start[lip->yytoklen]=0; + memcpy(dst->str, str, dst->length= (end - str)); + dst->str[dst->length]= 0; } else { - char *to; - - for (to=start ; str != end ; str++) - { -#ifdef USE_MB - int l; - if (use_mb(cs) && - (l = my_ismbchar(cs, str, end))) { - while (l--) - *to++ = *str++; - str--; - continue; - } -#endif - if (!(lip->m_thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) && - *str == '\\' && str+1 != end) - { - switch(*++str) { - case 'n': - *to++='\n'; - break; - case 't': - *to++= '\t'; - break; - case 'r': - *to++ = '\r'; - break; - case 'b': - *to++ = '\b'; - break; - case '0': - *to++= 0; // Ascii null - break; - case 'Z': // ^Z must be escaped on Win32 - *to++='\032'; - break; - case '_': - case '%': - *to++= '\\'; // remember prefix for wildcard - /* Fall through */ - default: - *to++= *str; - break; - } - } - else if (*str == sep) - *to++= *str++; // Two ' or " - else - *to++ = *str; - } - *to=0; - lip->yytoklen=(uint) (to-start); + dst->length= unescape(cs, dst->str, str, end, sep); } - return start; + return false; } } - return 0; // unexpected end of query + return true; // unexpected end of query } @@ -998,6 +1239,7 @@ int MYSQLlex(YYSTYPE *yylval, THD *thd) lip->lookahead_token= -1; *yylval= *(lip->lookahead_yylval); lip->lookahead_yylval= NULL; + lip->add_digest_token(token, yylval); return token; } @@ -1015,8 +1257,10 @@ int MYSQLlex(YYSTYPE *yylval, THD *thd) token= lex_one_token(yylval, thd); switch(token) { case CUBE_SYM: + lip->add_digest_token(WITH_CUBE_SYM, yylval); return WITH_CUBE_SYM; case ROLLUP_SYM: + lip->add_digest_token(WITH_ROLLUP_SYM, yylval); return WITH_ROLLUP_SYM; default: /* @@ -1025,6 +1269,7 @@ int MYSQLlex(YYSTYPE *yylval, THD *thd) lip->lookahead_yylval= lip->yylval; lip->yylval= NULL; lip->lookahead_token= token; + lip->add_digest_token(WITH, yylval); return WITH; } break; @@ -1032,12 +1277,13 @@ int MYSQLlex(YYSTYPE *yylval, THD *thd) break; } + lip->add_digest_token(token, yylval); return token; } static int lex_one_token(YYSTYPE *yylval, THD *thd) { - reg1 uchar c; + reg1 uchar UNINIT_VAR(c); bool comment_closed; int tokval, result_state; uint length; @@ -1048,7 +1294,6 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) const uchar *const state_map= cs->state_map; const uchar *const ident_map= cs->ident_map; - LINT_INIT(c); lip->yylval=yylval; // The global state lip->start_token(); @@ -1125,6 +1370,8 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) return((int) c); case MY_LEX_IDENT_OR_NCHAR: + { + uint sep; if (lip->yyPeek() != '\'') { state= MY_LEX_IDENT; @@ -1132,15 +1379,20 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) } /* Found N'string' */ lip->yySkip(); // Skip ' - if (!(yylval->lex_str.str = get_text(lip, 2, 1))) + if (lip->get_text(&yylval->lex_str, (sep= lip->yyGetLast()), 2, 1)) { state= MY_LEX_CHAR; // Read char by char break; } - yylval->lex_str.length= lip->yytoklen; + + lip->body_utf8_append(lip->m_cpp_text_start); + lip->body_utf8_append_escape(thd, &yylval->lex_str, + national_charset_info, + lip->m_cpp_text_end, sep); + lex->text_string_is_7bit= (lip->tok_bitmap & 0x80) ? 0 : 1; return(NCHAR_STRING); - + } case MY_LEX_IDENT_OR_HEX: if (lip->yyPeek() == '\'') { // Found x'hex-number' @@ -1245,8 +1497,7 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, cs, - lip->m_cpp_text_end); + lip->body_utf8_append_ident(thd, &yylval->lex_str, lip->m_cpp_text_end); return(result_state); // IDENT or IDENT_QUOTED @@ -1350,8 +1601,7 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, cs, - lip->m_cpp_text_end); + lip->body_utf8_append_ident(thd, &yylval->lex_str, lip->m_cpp_text_end); return(result_state); @@ -1394,8 +1644,7 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, cs, - lip->m_cpp_text_end); + lip->body_utf8_append_ident(thd, &yylval->lex_str, lip->m_cpp_text_end); return(IDENT_QUOTED); } @@ -1501,26 +1750,26 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) state= MY_LEX_USER_VARIABLE_DELIMITER; break; } - /* fall through */ /* " used for strings */ + /* " used for strings */ + /* fall through */ case MY_LEX_STRING: // Incomplete text string - if (!(yylval->lex_str.str = get_text(lip, 1, 1))) + { + uint sep; + if (lip->get_text(&yylval->lex_str, (sep= lip->yyGetLast()), 1, 1)) { state= MY_LEX_CHAR; // Read char by char break; } - yylval->lex_str.length=lip->yytoklen; - + CHARSET_INFO *strcs= lip->m_underscore_cs ? lip->m_underscore_cs : cs; lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, - lip->m_underscore_cs ? lip->m_underscore_cs : cs, - lip->m_cpp_text_end); - + lip->body_utf8_append_escape(thd, &yylval->lex_str, strcs, + lip->m_cpp_text_end, sep); lip->m_underscore_cs= NULL; lex->text_string_is_7bit= (lip->tok_bitmap & 0x80) ? 0 : 1; return(TEXT_STRING); - + } case MY_LEX_COMMENT: // Comment lex->select_lex.options|= OPTION_FOUND_COMMENT; while ((c = lip->yyGet()) != '\n' && c) ; @@ -1576,7 +1825,14 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) version= (ulong) my_strtoll10(lip->get_ptr(), &end_ptr, &error); - if (version <= MYSQL_VERSION_ID) + /* + MySQL-5.7 has new features and might have new SQL syntax that + MariaDB-10.0 does not understand. Ignore all versioned comments + with MySQL versions in the range 50700-999999, but + do not ignore MariaDB specific comments for the same versions. + */ + if (version <= MYSQL_VERSION_ID && + (version < 50700 || version > 99999 || maria_comment_syntax)) { /* Accept 'M' 'm' 'm' 'd' 'd' */ lip->yySkipn(length); @@ -1587,6 +1843,17 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) } else { +#ifdef WITH_WSREP + if (WSREP(thd) && version == 99997 && thd->wsrep_exec_mode == LOCAL_STATE) + { + WSREP_DEBUG("consistency check: %s", thd->query()); + thd->wsrep_consistency_check= CONSISTENCY_CHECK_DECLARED; + lip->yySkipn(5); + lip->set_echo(TRUE); + state=MY_LEX_START; + break; /* Do not treat contents as a comment. */ + } +#endif /* WITH_WSREP */ /* Patch and skip the conditional comment to avoid it being propagated infinitely (eg. to a slave). @@ -1751,8 +2018,7 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) lip->body_utf8_append(lip->m_cpp_text_start); - lip->body_utf8_append_literal(thd, &yylval->lex_str, cs, - lip->m_cpp_text_end); + lip->body_utf8_append_ident(thd, &yylval->lex_str, lip->m_cpp_text_end); return(result_state); } @@ -1760,50 +2026,6 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) } -/** - Construct a copy of this object to be used for mysql_alter_table - and mysql_create_table. - - Historically, these two functions modify their Alter_info - arguments. This behaviour breaks re-execution of prepared - statements and stored procedures and is compensated by always - supplying a copy of Alter_info to these functions. - - @return You need to use check the error in THD for out - of memory condition after calling this function. -*/ - -Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root) - :drop_list(rhs.drop_list, mem_root), - alter_list(rhs.alter_list, mem_root), - key_list(rhs.key_list, mem_root), - create_list(rhs.create_list, mem_root), - flags(rhs.flags), - keys_onoff(rhs.keys_onoff), - tablespace_op(rhs.tablespace_op), - partition_names(rhs.partition_names, mem_root), - num_parts(rhs.num_parts), - change_level(rhs.change_level), - datetime_field(rhs.datetime_field), - error_if_not_empty(rhs.error_if_not_empty) -{ - /* - Make deep copies of used objects. - This is not a fully deep copy - clone() implementations - of Alter_drop, Alter_column, Key, foreign_key, Key_part_spec - do not copy string constants. At the same length the only - reason we make a copy currently is that ALTER/CREATE TABLE - code changes input Alter_info definitions, but string - constants never change. - */ - list_copy_and_replace_each_value(drop_list, mem_root); - list_copy_and_replace_each_value(alter_list, mem_root); - list_copy_and_replace_each_value(key_list, mem_root); - list_copy_and_replace_each_value(create_list, mem_root); - /* partition_names are not deeply copied currently */ -} - - void trim_whitespace(CHARSET_INFO *cs, LEX_STRING *str) { /* @@ -1850,7 +2072,6 @@ void st_select_lex_unit::init_query() { st_select_lex_node::init_query(); linkage= GLOBAL_OPTIONS_TYPE; - global_parameters= first_select(); select_limit_cnt= HA_POS_ERROR; offset_limit_cnt= 0; union_distinct= 0; @@ -1859,6 +2080,7 @@ void st_select_lex_unit::init_query() union_result= 0; table= 0; fake_select_lex= 0; + saved_fake_select_lex= 0; cleaned= 0; item_list.empty(); describe= 0; @@ -1893,12 +2115,13 @@ void st_select_lex::init_query() thus push_context should be moved to a place where query initialization is checked for failure. */ - parent_lex->push_context(&context); + parent_lex->push_context(&context, parent_lex->thd->mem_root); cond_count= between_count= with_wild= 0; max_equal_elems= 0; ref_pointer_array= 0; ref_pointer_array_size= 0; select_n_where_fields= 0; + select_n_reserved= 0; select_n_having_items= 0; n_sum_items= 0; n_child_sum_items= 0; @@ -1912,6 +2135,7 @@ void st_select_lex::init_query() nest_level= 0; link_next= 0; prep_leaf_list_state= UNINIT; + have_merged_subqueries= FALSE; bzero((char*) expr_cache_may_be_used, sizeof(expr_cache_may_be_used)); m_non_agg_field_used= false; m_agg_func_used= false; @@ -1931,7 +2155,6 @@ void st_select_lex::init_select() in_sum_expr= with_wild= 0; options= 0; sql_cache= SQL_CACHE_UNSPECIFIED; - braces= 0; interval_list.empty(); ftfunc_list_alloc.empty(); inner_sum_func_list= 0; @@ -1954,6 +2177,7 @@ void st_select_lex::init_select() m_agg_func_used= false; name_visibility_map= 0; join= 0; + lock_type= TL_READ_DEFAULT; } /* @@ -2213,12 +2437,13 @@ bool st_select_lex_node::inc_in_sum_expr() { return 1; } uint st_select_lex_node::get_in_sum_expr() { return 0; } TABLE_LIST* st_select_lex_node::get_table_list() { return 0; } List<Item>* st_select_lex_node::get_item_list() { return 0; } -TABLE_LIST *st_select_lex_node::add_table_to_list (THD *thd, Table_ident *table, +TABLE_LIST *st_select_lex_node::add_table_to_list(THD *thd, Table_ident *table, LEX_STRING *alias, ulong table_join_options, thr_lock_type flags, enum_mdl_type mdl_type, List<Index_hint> *hints, + List<String> *partition_names, LEX_STRING *option) { return 0; @@ -2243,15 +2468,79 @@ bool st_select_lex::test_limit() } -st_select_lex_unit* st_select_lex_unit::master_unit() + +st_select_lex* st_select_lex_unit::outer_select() { - return this; + return (st_select_lex*) master; } -st_select_lex* st_select_lex_unit::outer_select() +ha_rows st_select_lex::get_offset() { - return (st_select_lex*) master; + ulonglong val= 0; + + if (offset_limit) + { + // see comment for st_select_lex::get_limit() + bool fix_fields_successful= true; + if (!offset_limit->fixed) + { + fix_fields_successful= !offset_limit->fix_fields(master_unit()->thd, + NULL); + + DBUG_ASSERT(fix_fields_successful); + } + val= fix_fields_successful ? offset_limit->val_uint() : HA_POS_ERROR; + } + + return (ha_rows)val; +} + + +ha_rows st_select_lex::get_limit() +{ + ulonglong val= HA_POS_ERROR; + + if (select_limit) + { + /* + fix_fields() has not been called for select_limit. That's due to the + historical reasons -- this item could be only of type Item_int, and + Item_int does not require fix_fields(). Thus, fix_fields() was never + called for select_limit. + + Some time ago, Item_splocal was also allowed for LIMIT / OFFSET clauses. + However, the fix_fields() behavior was not updated, which led to a crash + in some cases. + + There is no single place where to call fix_fields() for LIMIT / OFFSET + items during the fix-fields-phase. Thus, for the sake of readability, + it was decided to do it here, on the evaluation phase (which is a + violation of design, but we chose the lesser of two evils). + + We can call fix_fields() here, because select_limit can be of two + types only: Item_int and Item_splocal. Item_int::fix_fields() is trivial, + and Item_splocal::fix_fields() (or rather Item_sp_variable::fix_fields()) + has the following properties: + 1) it does not affect other items; + 2) it does not fail. + + Nevertheless DBUG_ASSERT was added to catch future changes in + fix_fields() implementation. Also added runtime check against a result + of fix_fields() in order to handle error condition in non-debug build. + */ + bool fix_fields_successful= true; + if (!select_limit->fixed) + { + fix_fields_successful= !select_limit->fix_fields(master_unit()->thd, + NULL); + + DBUG_ASSERT(fix_fields_successful); + } + val= fix_fields_successful ? select_limit->val_uint() : HA_POS_ERROR; + } + + return (ha_rows)val; } @@ -2266,11 +2555,12 @@ bool st_select_lex::add_gorder_to_list(THD *thd, Item *item, bool asc) return add_to_list(thd, gorder_list, item, asc); } + bool st_select_lex::add_item_to_list(THD *thd, Item *item) { DBUG_ENTER("st_select_lex::add_item_to_list"); DBUG_PRINT("info", ("Item: 0x%lx", (long) item)); - DBUG_RETURN(item_list.push_back(item)); + DBUG_RETURN(item_list.push_back(item, thd->mem_root)); } @@ -2280,15 +2570,9 @@ bool st_select_lex::add_group_to_list(THD *thd, Item *item, bool asc) } -bool st_select_lex::add_ftfunc_to_list(Item_func_match *func) -{ - return !func || ftfunc_list->push_back(func); // end of memory? -} - - -st_select_lex_unit* st_select_lex::master_unit() +bool st_select_lex::add_ftfunc_to_list(THD *thd, Item_func_match *func) { - return (st_select_lex_unit*) master; + return !func || ftfunc_list->push_back(func, thd->mem_root); // end of memory? } @@ -2347,6 +2631,7 @@ bool st_select_lex::setup_ref_array(THD *thd, uint order_group_num) const uint n_elems= (n_sum_items + n_child_sum_items + item_list.elements + + select_n_reserved + select_n_having_items + select_n_where_fields + order_group_num) * 5; @@ -2392,7 +2677,7 @@ void st_select_lex_unit::print(String *str, enum_query_type query_type) if (sl->braces) str->append(')'); } - if (fake_select_lex == global_parameters) + if (fake_select_lex) { if (fake_select_lex->order_list.elements) { @@ -2403,6 +2688,8 @@ void st_select_lex_unit::print(String *str, enum_query_type query_type) } fake_select_lex->print_limit(thd, str, query_type); } + else if (saved_fake_select_lex) + saved_fake_select_lex->print_limit(thd, str, query_type); } @@ -2414,30 +2701,22 @@ void st_select_lex::print_order(String *str, { if (order->counter_used) { - if (!(query_type & QT_VIEW_INTERNAL)) + char buffer[20]; + size_t length= my_snprintf(buffer, 20, "%d", order->counter); + str->append(buffer, (uint) length); + } + else + { + /* replace numeric reference with equivalent for ORDER constant */ + if (order->item[0]->type() == Item::INT_ITEM && + order->item[0]->basic_const_item()) { - char buffer[20]; - size_t length= my_snprintf(buffer, 20, "%d", order->counter); - str->append(buffer, (uint) length); + /* make it expression instead of integer constant */ + str->append(STRING_WITH_LEN("''")); } else - { - /* replace numeric reference with expression */ - if (order->item[0]->type() == Item::INT_ITEM && - order->item[0]->basic_const_item()) - { - char buffer[20]; - size_t length= my_snprintf(buffer, 20, "%d", order->counter); - str->append(buffer, (uint) length); - /* make it expression instead of integer constant */ - str->append(STRING_WITH_LEN("+0")); - } - else - (*order->item)->print(str, query_type); - } + (*order->item)->print(str, query_type); } - else - (*order->item)->print(str, query_type); if (!order->asc) str->append(STRING_WITH_LEN(" desc")); if (order->next) @@ -2453,7 +2732,7 @@ void st_select_lex::print_limit(THD *thd, SELECT_LEX_UNIT *unit= master_unit(); Item_subselect *item= unit->item; - if (item && unit->global_parameters == this) + if (item && unit->global_parameters() == this) { Item_subselect::subs_type subs_type= item->substype(); if (subs_type == Item_subselect::EXISTS_SUBS || @@ -2592,16 +2871,21 @@ void Query_tables_list::destroy_query_tables_list() */ LEX::LEX() - :result(0), option_type(OPT_DEFAULT), is_lex_started(0), - limit_rows_examined_cnt(ULONGLONG_MAX) + : explain(NULL), + result(0), arena_for_set_stmt(0), mem_root_for_set_stmt(0), + option_type(OPT_DEFAULT), context_analysis_only(0), sphead(0), + is_lex_started(0), limit_rows_examined_cnt(ULONGLONG_MAX) { - my_init_dynamic_array2(&plugins, sizeof(plugin_ref), - plugins_static_buffer, - INITIAL_LEX_PLUGIN_LIST_SIZE, - INITIAL_LEX_PLUGIN_LIST_SIZE); + init_dynamic_array2(&plugins, sizeof(plugin_ref), plugins_static_buffer, + INITIAL_LEX_PLUGIN_LIST_SIZE, + INITIAL_LEX_PLUGIN_LIST_SIZE, 0); reset_query_tables_list(TRUE); mi.init(); + init_dynamic_array2(&delete_gtid_domain, sizeof(uint32), + gtid_domain_static_buffer, + initial_gtid_domain_buffer_size, + initial_gtid_domain_buffer_size, 0); } @@ -2640,7 +2924,8 @@ bool LEX::can_be_merged() if (tmp_unit->first_select()->parent_lex == this && (tmp_unit->item == 0 || (tmp_unit->item->place() != IN_WHERE && - tmp_unit->item->place() != IN_ON))) + tmp_unit->item->place() != IN_ON && + tmp_unit->item->place() != SELECT_LIST))) { selects_allow_merge= 0; break; @@ -2829,7 +3114,7 @@ uint8 LEX::get_effective_with_check(TABLE_LIST *view) bool LEX::copy_db_to(char **p_db, size_t *p_db_length) const { - if (sphead) + if (sphead && sphead->m_name.str) { DBUG_ASSERT(sphead->m_db.str && sphead->m_db.length); /* @@ -2844,97 +3129,39 @@ LEX::copy_db_to(char **p_db, size_t *p_db_length) const return thd->copy_db_to(p_db, p_db_length); } -/* - initialize limit counters +/** + Initialize offset and limit counters. - SYNOPSIS - st_select_lex_unit::set_limit() - values - SELECT_LEX with initial values for counters + @param sl SELECT_LEX to get offset and limit from. */ void st_select_lex_unit::set_limit(st_select_lex *sl) { - ha_rows select_limit_val; - ulonglong val; + DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); - DBUG_ASSERT(! thd->stmt_arena->is_stmt_prepare()); - if (sl->select_limit) - { - Item *item = sl->select_limit; - /* - fix_fields() has not been called for sl->select_limit. That's due to the - historical reasons -- this item could be only of type Item_int, and - Item_int does not require fix_fields(). Thus, fix_fields() was never - called for sl->select_limit. - - Some time ago, Item_splocal was also allowed for LIMIT / OFFSET clauses. - However, the fix_fields() behavior was not updated, which led to a crash - in some cases. - - There is no single place where to call fix_fields() for LIMIT / OFFSET - items during the fix-fields-phase. Thus, for the sake of readability, - it was decided to do it here, on the evaluation phase (which is a - violation of design, but we chose the lesser of two evils). - - We can call fix_fields() here, because sl->select_limit can be of two - types only: Item_int and Item_splocal. Item_int::fix_fields() is trivial, - and Item_splocal::fix_fields() (or rather Item_sp_variable::fix_fields()) - has the following specific: - 1) it does not affect other items; - 2) it does not fail. - - Nevertheless DBUG_ASSERT was added to catch future changes in - fix_fields() implementation. Also added runtime check against a result - of fix_fields() in order to handle error condition in non-debug build. - */ - bool fix_fields_successful= true; - if (!item->fixed) - { - fix_fields_successful= !item->fix_fields(thd, NULL); - - DBUG_ASSERT(fix_fields_successful); - } - val= fix_fields_successful ? item->val_uint() : HA_POS_ERROR; - } + offset_limit_cnt= sl->get_offset(); + select_limit_cnt= sl->get_limit(); + if (select_limit_cnt + offset_limit_cnt >= select_limit_cnt) + select_limit_cnt+= offset_limit_cnt; else - val= HA_POS_ERROR; + select_limit_cnt= HA_POS_ERROR; +} - select_limit_val= (ha_rows)val; -#ifndef BIG_TABLES - /* - Check for overflow : ha_rows can be smaller then ulonglong if - BIG_TABLES is off. - */ - if (val != (ulonglong)select_limit_val) - select_limit_val= HA_POS_ERROR; -#endif - if (sl->offset_limit) - { - Item *item = sl->offset_limit; - // see comment for sl->select_limit branch. - bool fix_fields_successful= true; - if (!item->fixed) - { - fix_fields_successful= !item->fix_fields(thd, NULL); - DBUG_ASSERT(fix_fields_successful); - } - val= fix_fields_successful ? item->val_uint() : 0; - } - else - val= ULL(0); +/** + Decide if a temporary table is needed for the UNION. - offset_limit_cnt= (ha_rows)val; -#ifndef BIG_TABLES - /* Check for truncation. */ - if (val != (ulonglong)offset_limit_cnt) - offset_limit_cnt= HA_POS_ERROR; -#endif - select_limit_cnt= select_limit_val + offset_limit_cnt; - if (select_limit_cnt < select_limit_val) - select_limit_cnt= HA_POS_ERROR; // no limit -} + @retval true A temporary table is needed. + @retval false A temporary table is not needed. + */ +bool st_select_lex_unit::union_needs_tmp_table() +{ + return union_distinct != NULL || + global_parameters()->order_list.elements != 0 || + thd->lex->sql_command == SQLCOM_INSERT_SELECT || + thd->lex->sql_command == SQLCOM_REPLACE_SELECT; +} /** @brief Set the initial purpose of this TABLE_LIST object in the list of used @@ -3121,7 +3348,7 @@ TABLE_LIST *LEX::unlink_first_table(bool *link_to_local) /* and from local list if it is not empty */ - if ((*link_to_local= test(select_lex.table_list.first))) + if ((*link_to_local= MY_TEST(select_lex.table_list.first))) { select_lex.context.table_list= select_lex.context.first_name_resolution_table= first->next_local; @@ -3164,6 +3391,9 @@ void LEX::first_lists_tables_same() if (query_tables_last == &first_table->next_global) query_tables_last= first_table->prev_global; + if (query_tables_own_last == &first_table->next_global) + query_tables_own_last= first_table->prev_global; + if ((next= *first_table->prev_global= first_table->next_global)) next->prev_global= first_table->prev_global; /* include in new place */ @@ -3465,10 +3695,10 @@ void st_select_lex::alloc_index_hints (THD *thd) */ bool st_select_lex::add_index_hint (THD *thd, char *str, uint length) { - return index_hints->push_front (new (thd->mem_root) + return index_hints->push_front(new (thd->mem_root) Index_hint(current_index_hint_type, current_index_hint_clause, - str, length)); + str, length), thd->mem_root); } @@ -3527,6 +3757,8 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) bool empty_union_result= true; bool is_correlated_unit= false; + bool first= true; + bool union_plan_saved= false; /* If the subquery is a UNION, optimize all the subqueries in the UNION. If there is no UNION, then the loop will execute once for the subquery. @@ -3534,27 +3766,52 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) for (SELECT_LEX *sl= un->first_select(); sl; sl= sl->next_select()) { JOIN *inner_join= sl->join; + if (first) + first= false; + else + { + if (!union_plan_saved) + { + union_plan_saved= true; + if (un->save_union_explain(un->thd->lex->explain)) + return true; /* Failure */ + } + } if (!inner_join) continue; SELECT_LEX *save_select= un->thd->lex->current_select; ulonglong save_options; int res; /* We need only 1 row to determine existence */ - un->set_limit(un->global_parameters); + un->set_limit(un->global_parameters()); un->thd->lex->current_select= sl; save_options= inner_join->select_options; if (options & SELECT_DESCRIBE) { /* Optimize the subquery in the context of EXPLAIN. */ - sl->set_explain_type(); + sl->set_explain_type(FALSE); sl->options|= SELECT_DESCRIBE; inner_join->select_options|= SELECT_DESCRIBE; } res= inner_join->optimize(); + if (!inner_join->cleaned) + sl->update_used_tables(); sl->update_correlated_cache(); is_correlated_unit|= sl->is_correlated; inner_join->select_options= save_options; un->thd->lex->current_select= save_select; + + Explain_query *eq; + if ((eq= inner_join->thd->lex->explain)) + { + Explain_select *expl_sel; + if ((expl_sel= eq->get_select(inner_join->select_lex->select_number))) + { + sl->set_explain_type(TRUE); + expl_sel->select_type= sl->type; + } + } + if (empty_union_result) { /* @@ -3781,7 +4038,7 @@ bool SELECT_LEX::merge_subquery(THD *thd, TABLE_LIST *derived, { derived->wrap_into_nested_join(subq_select->top_join_list); - ftfunc_list->concat(subq_select->ftfunc_list); + ftfunc_list->append(subq_select->ftfunc_list); if (join || thd->lex->sql_command == SQLCOM_UPDATE_MULTI || thd->lex->sql_command == SQLCOM_DELETE_MULTI) @@ -3790,7 +4047,7 @@ bool SELECT_LEX::merge_subquery(THD *thd, TABLE_LIST *derived, Item_in_subselect *in_subq; while ((in_subq= li++)) { - sj_subselects.push_back(in_subq); + sj_subselects.push_back(in_subq, thd->mem_root); if (in_subq->emb_on_expr_nest == NO_JOIN_NEST) in_subq->emb_on_expr_nest= derived; } @@ -3856,7 +4113,8 @@ void SELECT_LEX::update_used_tables() tab->covering_keys.intersect(tab->keys_in_use_for_query); tab->merge_keys.clear_all(); bitmap_clear_all(tab->read_set); - bitmap_clear_all(tab->vcol_set); + if (tab->vcol_set) + bitmap_clear_all(tab->vcol_set); break; } } @@ -3870,7 +4128,7 @@ void SELECT_LEX::update_used_tables() do { bool maybe_null; - if ((maybe_null= test(embedding->outer_join))) + if ((maybe_null= MY_TEST(embedding->outer_join))) { tl->table->maybe_null= maybe_null; break; @@ -3934,7 +4192,7 @@ void SELECT_LEX::update_used_tables() } for (ORDER *order= group_list.first; order; order= order->next) (*order->item)->update_used_tables(); - if (!master_unit()->is_union() || master_unit()->global_parameters != this) + if (!master_unit()->is_union() || master_unit()->global_parameters() != this) { for (ORDER *order= order_list.first; order; order= order->next) (*order->item)->update_used_tables(); @@ -3960,37 +4218,40 @@ void st_select_lex::update_correlated_cache() while ((tl= ti++)) { if (tl->on_expr) - is_correlated|= test(tl->on_expr->used_tables() & OUTER_REF_TABLE_BIT); + is_correlated|= MY_TEST(tl->on_expr->used_tables() & OUTER_REF_TABLE_BIT); for (TABLE_LIST *embedding= tl->embedding ; embedding ; embedding= embedding->embedding) { if (embedding->on_expr) - is_correlated|= test(embedding->on_expr->used_tables() & - OUTER_REF_TABLE_BIT); + is_correlated|= MY_TEST(embedding->on_expr->used_tables() & + OUTER_REF_TABLE_BIT); } } if (join->conds) - is_correlated|= test(join->conds->used_tables() & OUTER_REF_TABLE_BIT); + is_correlated|= MY_TEST(join->conds->used_tables() & OUTER_REF_TABLE_BIT); if (join->having) - is_correlated|= test(join->having->used_tables() & OUTER_REF_TABLE_BIT); + is_correlated|= MY_TEST(join->having->used_tables() & OUTER_REF_TABLE_BIT); if (join->tmp_having) - is_correlated|= test(join->tmp_having->used_tables() & OUTER_REF_TABLE_BIT); + is_correlated|= MY_TEST(join->tmp_having->used_tables() & + OUTER_REF_TABLE_BIT); Item *item; List_iterator_fast<Item> it(join->fields_list); while ((item= it++)) - is_correlated|= test(item->used_tables() & OUTER_REF_TABLE_BIT); + is_correlated|= MY_TEST(item->used_tables() & OUTER_REF_TABLE_BIT); for (ORDER *order= group_list.first; order; order= order->next) - is_correlated|= test((*order->item)->used_tables() & OUTER_REF_TABLE_BIT); + is_correlated|= MY_TEST((*order->item)->used_tables() & + OUTER_REF_TABLE_BIT); if (!master_unit()->is_union()) { for (ORDER *order= order_list.first; order; order= order->next) - is_correlated|= test((*order->item)->used_tables() & OUTER_REF_TABLE_BIT); + is_correlated|= MY_TEST((*order->item)->used_tables() & + OUTER_REF_TABLE_BIT); } if (!is_correlated) @@ -4000,9 +4261,12 @@ void st_select_lex::update_correlated_cache() /** Set the EXPLAIN type for this subquery. + + @param on_the_fly TRUE<=> We're running a SHOW EXPLAIN command, so we must + not change any variables */ -void st_select_lex::set_explain_type() +void st_select_lex::set_explain_type(bool on_the_fly) { bool is_primary= FALSE; if (next_select()) @@ -4024,6 +4288,9 @@ void st_select_lex::set_explain_type() } } + if (on_the_fly && !is_primary && have_merged_subqueries) + is_primary= TRUE; + SELECT_LEX *first= master_unit()->first_select(); /* drop UNCACHEABLE_EXPLAIN, because it is for internal usage only */ uint8 is_uncacheable= (uncacheable & ~UNCACHEABLE_EXPLAIN); @@ -4076,10 +4343,15 @@ void st_select_lex::set_explain_type() else { type= is_uncacheable ? "UNCACHEABLE UNION": "UNION"; + if (this == master_unit()->fake_select_lex) + type= "UNION RESULT"; + } } } - options|= SELECT_DESCRIBE; + + if (!on_the_fly) + options|= SELECT_DESCRIBE; } @@ -4120,7 +4392,8 @@ void SELECT_LEX::increase_derived_records(ha_rows records) void SELECT_LEX::mark_const_derived(bool empty) { TABLE_LIST *derived= master_unit()->derived; - if (!join->thd->lex->describe && derived) + /* join == NULL in DELETE ... RETURNING */ + if (!(join && join->thd->lex->describe) && derived) { if (!empty) increase_derived_records(1); @@ -4139,7 +4412,7 @@ bool st_select_lex::save_leaf_tables(THD *thd) TABLE_LIST *table; while ((table= li++)) { - if (leaf_tables_exec.push_back(table)) + if (leaf_tables_exec.push_back(table, thd->mem_root)) return 1; table->tablenr_exec= table->get_tablenr(); table->map_exec= table->get_map(); @@ -4256,6 +4529,177 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) return all_merged; } +/* + This is used by SHOW EXPLAIN. It assuses query plan has been already + collected into QPF structures and we only need to print it out. +*/ + +int LEX::print_explain(select_result_sink *output, uint8 explain_flags, + bool is_analyze, bool *printed_anything) +{ + int res; + if (explain && explain->have_query_plan()) + { + res= explain->print_explain(output, explain_flags, is_analyze); + *printed_anything= true; + } + else + { + res= 0; + *printed_anything= false; + } + return res; +} + + +/** + Allocates and set arena for SET STATEMENT old values. + + @param backup where to save backup of arena. + + @retval 1 Error + @retval 0 OK +*/ + +bool LEX::set_arena_for_set_stmt(Query_arena *backup) +{ + DBUG_ENTER("LEX::set_arena_for_set_stmt"); + DBUG_ASSERT(arena_for_set_stmt== 0); + if (!mem_root_for_set_stmt) + { + mem_root_for_set_stmt= new MEM_ROOT(); + if (!(mem_root_for_set_stmt)) + DBUG_RETURN(1); + init_sql_alloc(mem_root_for_set_stmt, ALLOC_ROOT_SET, ALLOC_ROOT_SET, + MYF(MY_THREAD_SPECIFIC)); + } + if (!(arena_for_set_stmt= new(mem_root_for_set_stmt) + Query_arena_memroot(mem_root_for_set_stmt, + Query_arena::STMT_INITIALIZED))) + DBUG_RETURN(1); + DBUG_PRINT("info", ("mem_root: 0x%lx arena: 0x%lx", + (ulong) mem_root_for_set_stmt, + (ulong) arena_for_set_stmt)); + thd->set_n_backup_active_arena(arena_for_set_stmt, backup); + DBUG_RETURN(0); +} + + +void LEX::reset_arena_for_set_stmt(Query_arena *backup) +{ + DBUG_ENTER("LEX::reset_arena_for_set_stmt"); + DBUG_ASSERT(arena_for_set_stmt); + thd->restore_active_arena(arena_for_set_stmt, backup); + DBUG_PRINT("info", ("mem_root: 0x%lx arena: 0x%lx", + (ulong) arena_for_set_stmt->mem_root, + (ulong) arena_for_set_stmt)); + DBUG_VOID_RETURN; +} + + +void LEX::free_arena_for_set_stmt() +{ + DBUG_ENTER("LEX::free_arena_for_set_stmt"); + if (!arena_for_set_stmt) + return; + DBUG_PRINT("info", ("mem_root: 0x%lx arena: 0x%lx", + (ulong) arena_for_set_stmt->mem_root, + (ulong) arena_for_set_stmt)); + arena_for_set_stmt->free_items(); + delete(arena_for_set_stmt); + free_root(mem_root_for_set_stmt, MYF(MY_KEEP_PREALLOC)); + arena_for_set_stmt= 0; + DBUG_VOID_RETURN; +} + +void LEX::restore_set_statement_var() +{ + DBUG_ENTER("LEX::restore_set_statement_var"); + if (!old_var_list.is_empty()) + { + DBUG_PRINT("info", ("vars: %d", old_var_list.elements)); + sql_set_variables(thd, &old_var_list, false); + old_var_list.empty(); + free_arena_for_set_stmt(); + } + DBUG_ASSERT(!is_arena_for_set_stmt()); + DBUG_VOID_RETURN; +} + +/* + Save explain structures of a UNION. The only variable member is whether the + union has "Using filesort". + + There is also save_union_explain_part2() function, which is called before we read + UNION's output. + + The reason for it is examples like this: + + SELECT col1 FROM t1 UNION SELECT col2 FROM t2 ORDER BY (select ... from t3 ...) + + Here, the (select ... from t3 ...) subquery must be a child of UNION's + st_select_lex. However, it is not connected as child until a very late + stage in execution. +*/ + +int st_select_lex_unit::save_union_explain(Explain_query *output) +{ + SELECT_LEX *first= first_select(); + + if (output->get_union(first->select_number)) + return 0; /* Already added */ + + Explain_union *eu= + new (output->mem_root) Explain_union(output->mem_root, + thd->lex->analyze_stmt); + + + if (derived) + eu->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; + /* + Note: Non-merged semi-joins cannot be made out of UNIONs currently, so we + dont ever set EXPLAIN_NODE_NON_MERGED_SJ. + */ + + for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) + eu->add_select(sl->select_number); + + eu->fake_select_type= "UNION RESULT"; + eu->using_filesort= MY_TEST(global_parameters()->order_list.first); + eu->using_tmp= union_needs_tmp_table(); + + // Save the UNION node + output->add_node(eu); + + if (eu->get_select_id() == 1) + output->query_plan_ready(); + + return 0; +} + + +/* + @see st_select_lex_unit::save_union_explain +*/ + +int st_select_lex_unit::save_union_explain_part2(Explain_query *output) +{ + Explain_union *eu= output->get_union(first_select()->select_number); + if (fake_select_lex) + { + for (SELECT_LEX_UNIT *unit= fake_select_lex->first_inner_unit(); + unit; unit= unit->next_unit()) + { + if (!(unit->item && unit->item->eliminated)) + { + eu->add_child(unit->first_select()->select_number); + } + } + fake_select_lex->join->explain= &eu->fake_select_lex_explain; + } + return 0; +} + /** A routine used by the parser to decide whether we are specifying a full @@ -4271,10 +4715,11 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) bool LEX::is_partition_management() const { return (sql_command == SQLCOM_ALTER_TABLE && - (alter_info.flags == ALTER_ADD_PARTITION || - alter_info.flags == ALTER_REORGANIZE_PARTITION)); + (alter_info.flags == Alter_info::ALTER_ADD_PARTITION || + alter_info.flags == Alter_info::ALTER_REORGANIZE_PARTITION)); } + #ifdef MYSQL_SERVER uint binlog_unsafe_map[256]; @@ -4421,7 +4866,3 @@ void binlog_unsafe_map_init() BINLOG_DIRECT_OFF & TRX_CACHE_NOT_EMPTY); } #endif - -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class Mem_root_array<ORDER*, true>; -#endif diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 5b589714e1a..0142f812632 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -26,6 +26,8 @@ #include "item.h" /* From item_subselect.h: subselect_union_engine */ #include "thr_lock.h" /* thr_lock_type, TL_UNLOCK */ #include "mem_root_array.h" +#include "sql_cmd.h" +#include "sql_alter.h" // Alter_info /* YACC and LEX Definitions */ @@ -43,11 +45,11 @@ class Event_parse_data; class set_var_base; class sys_var; class Item_func_match; -class Alter_drop; -class Alter_column; -class Key; class File_parser; class Key_part_spec; +struct sql_digest_state; + +#define ALLOC_ROOT_SET 1024 #ifdef MYSQL_SERVER /* @@ -125,6 +127,8 @@ struct LEX_TYPE #include "lex_symbol.h" #if MYSQL_LEX #include "item_func.h" /* Cast_target used in sql_yacc.h */ +#include "sql_get_diagnostics.h" /* Types used in sql_yacc.h */ +#include "sp_pcontext.h" #include "sql_yacc.h" #define LEX_YYSTYPE YYSTYPE * #else @@ -133,85 +137,6 @@ struct LEX_TYPE #endif #endif -/* - When a command is added here, be sure it's also added in mysqld.cc - in "struct show_var_st status_vars[]= {" ... - - If the command returns a result set or is not allowed in stored - functions or triggers, please also make sure that - sp_get_flags_for_command (sp_head.cc) returns proper flags for the - added SQLCOM_. -*/ - -enum enum_sql_command { - SQLCOM_SELECT, SQLCOM_CREATE_TABLE, SQLCOM_CREATE_INDEX, SQLCOM_ALTER_TABLE, - SQLCOM_UPDATE, SQLCOM_INSERT, SQLCOM_INSERT_SELECT, - SQLCOM_DELETE, SQLCOM_TRUNCATE, SQLCOM_DROP_TABLE, SQLCOM_DROP_INDEX, - - SQLCOM_SHOW_DATABASES, SQLCOM_SHOW_TABLES, SQLCOM_SHOW_FIELDS, - SQLCOM_SHOW_KEYS, SQLCOM_SHOW_VARIABLES, SQLCOM_SHOW_STATUS, - SQLCOM_SHOW_ENGINE_LOGS, SQLCOM_SHOW_ENGINE_STATUS, SQLCOM_SHOW_ENGINE_MUTEX, - SQLCOM_SHOW_PROCESSLIST, SQLCOM_SHOW_MASTER_STAT, SQLCOM_SHOW_SLAVE_STAT, - SQLCOM_SHOW_GRANTS, SQLCOM_SHOW_CREATE, SQLCOM_SHOW_CHARSETS, - SQLCOM_SHOW_COLLATIONS, SQLCOM_SHOW_CREATE_DB, SQLCOM_SHOW_TABLE_STATUS, - SQLCOM_SHOW_TRIGGERS, - - SQLCOM_LOAD,SQLCOM_SET_OPTION,SQLCOM_LOCK_TABLES,SQLCOM_UNLOCK_TABLES, - SQLCOM_GRANT, - SQLCOM_CHANGE_DB, SQLCOM_CREATE_DB, SQLCOM_DROP_DB, SQLCOM_ALTER_DB, - SQLCOM_REPAIR, SQLCOM_REPLACE, SQLCOM_REPLACE_SELECT, - SQLCOM_CREATE_FUNCTION, SQLCOM_DROP_FUNCTION, - SQLCOM_REVOKE,SQLCOM_OPTIMIZE, SQLCOM_CHECK, - SQLCOM_ASSIGN_TO_KEYCACHE, SQLCOM_PRELOAD_KEYS, - SQLCOM_FLUSH, SQLCOM_KILL, SQLCOM_ANALYZE, - SQLCOM_ROLLBACK, SQLCOM_ROLLBACK_TO_SAVEPOINT, - SQLCOM_COMMIT, SQLCOM_SAVEPOINT, SQLCOM_RELEASE_SAVEPOINT, - SQLCOM_SLAVE_START, SQLCOM_SLAVE_STOP, - SQLCOM_BEGIN, SQLCOM_CHANGE_MASTER, - SQLCOM_RENAME_TABLE, - SQLCOM_RESET, SQLCOM_PURGE, SQLCOM_PURGE_BEFORE, SQLCOM_SHOW_BINLOGS, - SQLCOM_SHOW_OPEN_TABLES, - SQLCOM_HA_OPEN, SQLCOM_HA_CLOSE, SQLCOM_HA_READ, - SQLCOM_SHOW_SLAVE_HOSTS, SQLCOM_DELETE_MULTI, SQLCOM_UPDATE_MULTI, - SQLCOM_SHOW_BINLOG_EVENTS, SQLCOM_DO, - SQLCOM_SHOW_WARNS, SQLCOM_EMPTY_QUERY, SQLCOM_SHOW_ERRORS, - SQLCOM_SHOW_STORAGE_ENGINES, SQLCOM_SHOW_PRIVILEGES, - SQLCOM_HELP, SQLCOM_CREATE_USER, SQLCOM_DROP_USER, SQLCOM_RENAME_USER, - SQLCOM_REVOKE_ALL, SQLCOM_CHECKSUM, - SQLCOM_CREATE_PROCEDURE, SQLCOM_CREATE_SPFUNCTION, SQLCOM_CALL, - SQLCOM_DROP_PROCEDURE, SQLCOM_ALTER_PROCEDURE,SQLCOM_ALTER_FUNCTION, - SQLCOM_SHOW_CREATE_PROC, SQLCOM_SHOW_CREATE_FUNC, - SQLCOM_SHOW_STATUS_PROC, SQLCOM_SHOW_STATUS_FUNC, - SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE, - SQLCOM_CREATE_VIEW, SQLCOM_DROP_VIEW, - SQLCOM_CREATE_TRIGGER, SQLCOM_DROP_TRIGGER, - SQLCOM_XA_START, SQLCOM_XA_END, SQLCOM_XA_PREPARE, - SQLCOM_XA_COMMIT, SQLCOM_XA_ROLLBACK, SQLCOM_XA_RECOVER, - SQLCOM_SHOW_PROC_CODE, SQLCOM_SHOW_FUNC_CODE, - SQLCOM_ALTER_TABLESPACE, - SQLCOM_INSTALL_PLUGIN, SQLCOM_UNINSTALL_PLUGIN, - SQLCOM_SHOW_AUTHORS, SQLCOM_BINLOG_BASE64_EVENT, - SQLCOM_SHOW_PLUGINS, - SQLCOM_SHOW_CONTRIBUTORS, - SQLCOM_CREATE_SERVER, SQLCOM_DROP_SERVER, SQLCOM_ALTER_SERVER, - SQLCOM_CREATE_EVENT, SQLCOM_ALTER_EVENT, SQLCOM_DROP_EVENT, - SQLCOM_SHOW_CREATE_EVENT, SQLCOM_SHOW_EVENTS, - SQLCOM_SHOW_CREATE_TRIGGER, - SQLCOM_ALTER_DB_UPGRADE, - SQLCOM_SHOW_PROFILE, SQLCOM_SHOW_PROFILES, - SQLCOM_SIGNAL, SQLCOM_RESIGNAL, - SQLCOM_SHOW_RELAYLOG_EVENTS, - SQLCOM_SHOW_USER_STATS, SQLCOM_SHOW_TABLE_STATS, SQLCOM_SHOW_INDEX_STATS, - SQLCOM_SHOW_CLIENT_STATS, - - /* - When a command is added here, be sure it's also added in mysqld.cc - in "struct show_var_st status_vars[]= {" ... - */ - /* This should be the last !!! */ - SQLCOM_END -}; - // describe/explain types #define DESCRIBE_NORMAL 1 #define DESCRIBE_EXTENDED 2 @@ -223,6 +148,9 @@ enum enum_sql_command { #ifdef MYSQL_SERVER +extern const LEX_STRING null_lex_str; +extern const LEX_STRING empty_lex_str; + enum enum_sp_suid_behaviour { SP_IS_DEFAULT_SUID= 0, @@ -278,8 +206,13 @@ typedef Mem_root_array<ORDER*, true> Group_list_ptrs; typedef struct st_lex_server_options { long port; - uint server_name_length; - char *server_name, *host, *db, *username, *password, *scheme, *socket, *owner; + LEX_STRING server_name, host, db, username, password, scheme, socket, owner; + void reset(LEX_STRING name) + { + server_name= name; + host= db= username= password= scheme= socket= owner= null_lex_str; + port= -1; + } } LEX_SERVER_OPTIONS; @@ -293,9 +226,15 @@ typedef struct st_lex_server_options struct LEX_MASTER_INFO { DYNAMIC_ARRAY repl_ignore_server_ids; + DYNAMIC_ARRAY repl_do_domain_ids; + DYNAMIC_ARRAY repl_ignore_domain_ids; char *host, *user, *password, *log_file_name; char *ssl_key, *ssl_cert, *ssl_ca, *ssl_capath, *ssl_cipher; + char *ssl_crl, *ssl_crlpath; char *relay_log_name; + LEX_STRING connection_name; + /* Value in START SLAVE UNTIL master_gtid_pos=xxx */ + LEX_STRING gtid_pos_str; ulonglong pos; ulong relay_log_pos; ulong server_id; @@ -306,23 +245,41 @@ struct LEX_MASTER_INFO changed variable or if it should be left at old value */ enum {LEX_MI_UNCHANGED, LEX_MI_DISABLE, LEX_MI_ENABLE} - ssl, ssl_verify_server_cert, heartbeat_opt, repl_ignore_server_ids_opt; + ssl, ssl_verify_server_cert, heartbeat_opt, repl_ignore_server_ids_opt, + repl_do_domain_ids_opt, repl_ignore_domain_ids_opt; + enum { + LEX_GTID_UNCHANGED, LEX_GTID_NO, LEX_GTID_CURRENT_POS, LEX_GTID_SLAVE_POS + } use_gtid_opt; void init() { bzero(this, sizeof(*this)); my_init_dynamic_array(&repl_ignore_server_ids, - sizeof(::server_id), 0, 16); + sizeof(::server_id), 0, 16, MYF(0)); + my_init_dynamic_array(&repl_do_domain_ids, + sizeof(ulong), 0, 16, MYF(0)); + my_init_dynamic_array(&repl_ignore_domain_ids, + sizeof(ulong), 0, 16, MYF(0)); } - void reset() + void reset(bool is_change_master) { - delete_dynamic(&repl_ignore_server_ids); + if (unlikely(is_change_master)) + { + delete_dynamic(&repl_ignore_server_ids); + /* Free all the array elements. */ + delete_dynamic(&repl_do_domain_ids); + delete_dynamic(&repl_ignore_domain_ids); + } + host= user= password= log_file_name= ssl_key= ssl_cert= ssl_ca= ssl_capath= ssl_cipher= relay_log_name= 0; pos= relay_log_pos= server_id= port= connect_retry= 0; heartbeat_period= 0; ssl= ssl_verify_server_cert= heartbeat_opt= - repl_ignore_server_ids_opt= LEX_MI_UNCHANGED; + repl_ignore_server_ids_opt= repl_do_domain_ids_opt= + repl_ignore_domain_ids_opt= LEX_MI_UNCHANGED; + gtid_pos_str= null_lex_str; + use_gtid_opt= LEX_GTID_UNCHANGED; } }; @@ -342,11 +299,6 @@ enum olap_type UNSPECIFIED_OLAP_TYPE, CUBE_TYPE, ROLLUP_TYPE }; -enum tablespace_op_type -{ - NO_TABLESPACE_OP, DISCARD_TABLESPACE, IMPORT_TABLESPACE -}; - /* String names used to print a statement with index hints. Keep in sync with index_hint_type. @@ -365,6 +317,8 @@ typedef uchar index_clause_map; #define INDEX_HINT_MASK_ALL (INDEX_HINT_MASK_JOIN | INDEX_HINT_MASK_GROUP | \ INDEX_HINT_MASK_ORDER) +class select_result_sink; + /* Single element of an USE/FORCE/IGNORE INDEX list specified as a SQL hint */ class Index_hint : public Sql_alloc { @@ -572,7 +526,6 @@ public: void exclude(); void exclude_from_tree(); - virtual st_select_lex_unit* master_unit()= 0; virtual st_select_lex* outer_select()= 0; virtual st_select_lex* return_after_parsing()= 0; @@ -588,13 +541,14 @@ public: thr_lock_type flags= TL_UNLOCK, enum_mdl_type mdl_type= MDL_SHARED_READ, List<Index_hint> *hints= 0, + List<String> *partition_names= 0, LEX_STRING *option= 0); virtual void set_lock_for_tables(thr_lock_type lock_type) {} friend class st_select_lex_unit; friend bool mysql_new_select(LEX *lex, bool move_down); - friend bool mysql_make_view(THD *thd, File_parser *parser, - TABLE_LIST *table, uint flags); + friend bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table, + bool open_view_no_parse); friend bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list); friend bool mysql_derived_merge(THD *thd, LEX *lex, @@ -614,7 +568,12 @@ class select_result; class JOIN; class select_union; class Procedure; +class Explain_query; +void delete_explain_query(LEX *lex); +void create_explain_query(LEX *lex, MEM_ROOT *mem_root); +void create_explain_query_if_not_exists(LEX *lex, MEM_ROOT *mem_root); +bool print_explain_for_slow_log(LEX *lex, THD *thd, String *str); class st_select_lex_unit: public st_select_lex_node { protected: @@ -650,12 +609,29 @@ public: any SELECT of this unit execution */ List<Item> types; - /* - Pointer to 'last' select or pointer to unit where stored - global parameters for union + /** + Pointer to 'last' select, or pointer to select where we stored + global parameters for union. + + If this is a union of multiple selects, the parser puts the global + parameters in fake_select_lex. If the union doesn't use a + temporary table, st_select_lex_unit::prepare() nulls out + fake_select_lex, but saves a copy in saved_fake_select_lex in + order to preserve the global parameters. + + If it is not a union, first_select() is the last select. + + @return select containing the global parameters */ - st_select_lex *global_parameters; - //node on wich we should return current_select pointer after parsing subquery + inline st_select_lex *global_parameters() + { + if (fake_select_lex != NULL) + return fake_select_lex; + else if (saved_fake_select_lex != NULL) + return saved_fake_select_lex; + return first_select(); + }; + //node on which we should return current_select pointer after parsing subquery st_select_lex *return_to; /* LIMIT clause runtime counters */ ha_rows select_limit_cnt, offset_limit_cnt; @@ -673,6 +649,11 @@ public: ORDER BY and LIMIT */ st_select_lex *fake_select_lex; + /** + SELECT_LEX that stores LIMIT and OFFSET for UNION ALL when no + fake_select_lex is used. + */ + st_select_lex *saved_fake_select_lex; st_select_lex *union_distinct; /* pointer to the last UNION DISTINCT */ bool describe; /* union exec() called for EXPLAIN */ @@ -686,7 +667,6 @@ public: TABLE *insert_table_with_stored_vcol; void init_query(); - st_select_lex_unit* master_unit(); st_select_lex* outer_select(); st_select_lex* first_select() { @@ -718,6 +698,7 @@ public: void set_limit(st_select_lex *values); void set_thd(THD *thd_arg) { thd= thd_arg; } inline bool is_union (); + bool union_needs_tmp_table(); void set_unique_exclude(); @@ -725,6 +706,10 @@ public: friend int subselect_union_engine::exec(); List<Item> *get_unit_column_types(); + + select_union *get_union_result() { return union_result; } + int save_union_explain(Explain_query *output); + int save_union_explain_part2(Explain_query *output); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; @@ -746,7 +731,13 @@ public: Item *prep_having;/* saved HAVING clause for prepared statement processing */ /* Saved values of the WHERE and HAVING clauses*/ Item::cond_result cond_value, having_value; - /* point on lex in which it was created, used in view subquery detection */ + /* + Point to the LEX in which it was created, used in view subquery detection. + + TODO: make also st_select_lex::parent_stmt_lex (see LEX::stmt_lex) + and use st_select_lex::parent_lex & st_select_lex::parent_stmt_lex + instead of global (from THD) references where it is possible. + */ LEX *parent_lex; enum olap_type olap; /* FROM clause - points to the beginning of the TABLE_LIST::next_local list. */ @@ -795,6 +786,12 @@ public: those converted to jtbm nests. The list is emptied when conversion is done. */ List<Item_in_subselect> sj_subselects; + + /* + Needed to correctly generate 'PRIMARY' or 'SIMPLE' for select_type column + of EXPLAIN + */ + bool have_merged_subqueries; List<TABLE_LIST> leaf_tables; List<TABLE_LIST> leaf_tables_exec; @@ -820,7 +817,7 @@ public: list during split_sum_func */ uint select_n_having_items; - uint cond_count; /* number of arguments of and/or/xor in where/having/on */ + uint cond_count; /* number of sargable Items in where/having/on */ uint between_count; /* number of between predicates in where/having/on */ uint max_equal_elems; /* maximal number of elements in multiple equalities */ /* @@ -828,6 +825,8 @@ public: and all inner subselects. */ uint select_n_where_fields; + /* reserved for exists 2 in */ + uint select_n_reserved; enum_parsing_place parsing_place; /* where we are parsing expression */ bool with_sum_func; /* sum function indicator */ @@ -911,9 +910,12 @@ public: /* namp of nesting SELECT visibility (for aggregate functions check) */ nesting_map name_visibility_map; + /* it is for correct printing SELECT options */ + thr_lock_type lock_type; + void init_query(); void init_select(); - st_select_lex_unit* master_unit(); + st_select_lex_unit* master_unit() { return (st_select_lex_unit*) master; } st_select_lex_unit* first_inner_unit() { return (st_select_lex_unit*) slave; @@ -942,7 +944,7 @@ public: bool add_item_to_list(THD *thd, Item *item); bool add_group_to_list(THD *thd, Item *item, bool asc); - bool add_ftfunc_to_list(Item_func_match *func); + bool add_ftfunc_to_list(THD *thd, Item_func_match *func); bool add_order_to_list(THD *thd, Item *item, bool asc); bool add_gorder_to_list(THD *thd, Item *item, bool asc); TABLE_LIST* add_table_to_list(THD *thd, Table_ident *table, @@ -951,6 +953,7 @@ public: thr_lock_type flags= TL_UNLOCK, enum_mdl_type mdl_type= MDL_SHARED_READ, List<Index_hint> *hints= 0, + List<String> *partition_names= 0, LEX_STRING *option= 0); TABLE_LIST* get_table_list(); bool init_nested_join(THD *thd); @@ -975,9 +978,26 @@ public: */ void cut_subtree() { slave= 0; } bool test_limit(); + /** + Get offset for LIMIT. + + Evaluate offset item if necessary. + + @return Number of rows to skip. + */ + ha_rows get_offset(); + /** + Get limit. + + Evaluate limit item if necessary. + + @return Limit of rows in result. + */ + ha_rows get_limit(); friend void lex_start(THD *thd); - st_select_lex() : group_list_ptrs(NULL), n_sum_items(0), n_child_sum_items(0) + st_select_lex() : group_list_ptrs(NULL), braces(0), + n_sum_items(0), n_child_sum_items(0) {} void make_empty_select() { @@ -1022,9 +1042,13 @@ public: void clear_index_hints(void) { index_hints= NULL; } bool is_part_of_union() { return master_unit()->is_union(); } + bool is_top_level_node() + { + return (select_number == 1) && !is_part_of_union(); + } bool optimize_unflattened_subqueries(bool const_only); /* Set the EXPLAIN type for this subquery. */ - void set_explain_type(); + void set_explain_type(bool on_the_fly); bool handle_derived(LEX *lex, uint phases); void append_table_to_list(TABLE_LIST *TABLE_LIST::*link, TABLE_LIST *table); bool get_free_table_map(table_map *map, uint *tablenr); @@ -1048,6 +1072,7 @@ public: bool save_leaf_tables(THD *thd); bool save_prep_leaf_tables(THD *thd); + bool is_merged_child_of(st_select_lex *ancestor); /* @@ -1087,110 +1112,6 @@ inline bool st_select_lex_unit::is_union () first_select()->next_select()->linkage == UNION_TYPE; } -#define ALTER_ADD_COLUMN (1L << 0) -#define ALTER_DROP_COLUMN (1L << 1) -#define ALTER_CHANGE_COLUMN (1L << 2) -#define ALTER_ADD_INDEX (1L << 3) -#define ALTER_DROP_INDEX (1L << 4) -#define ALTER_RENAME (1L << 5) -#define ALTER_ORDER (1L << 6) -#define ALTER_OPTIONS (1L << 7) -#define ALTER_CHANGE_COLUMN_DEFAULT (1L << 8) -#define ALTER_KEYS_ONOFF (1L << 9) -#define ALTER_CONVERT (1L << 10) -#define ALTER_RECREATE (1L << 11) -#define ALTER_ADD_PARTITION (1L << 12) -#define ALTER_DROP_PARTITION (1L << 13) -#define ALTER_COALESCE_PARTITION (1L << 14) -#define ALTER_REORGANIZE_PARTITION (1L << 15) -#define ALTER_PARTITION (1L << 16) -#define ALTER_ADMIN_PARTITION (1L << 17) -#define ALTER_TABLE_REORG (1L << 18) -#define ALTER_REBUILD_PARTITION (1L << 19) -#define ALTER_ALL_PARTITION (1L << 20) -#define ALTER_REMOVE_PARTITIONING (1L << 21) -#define ALTER_FOREIGN_KEY (1L << 22) -#define ALTER_TRUNCATE_PARTITION (1L << 23) - -enum enum_alter_table_change_level -{ - ALTER_TABLE_METADATA_ONLY= 0, - ALTER_TABLE_DATA_CHANGED= 1, - ALTER_TABLE_INDEX_CHANGED= 2 -}; - - -/** - Temporary hack to enable a class bound forward declaration - of the enum_alter_table_change_level enumeration. To be - removed once Alter_info is moved to the sql_alter.h - header. -*/ -class Alter_table_change_level -{ -private: - typedef enum enum_alter_table_change_level enum_type; - enum_type value; -public: - void operator = (enum_type v) { value = v; } - operator enum_type () { return value; } -}; - - -/** - @brief Parsing data for CREATE or ALTER TABLE. - - This structure contains a list of columns or indexes to be created, - altered or dropped. -*/ - -class Alter_info -{ -public: - List<Alter_drop> drop_list; - List<Alter_column> alter_list; - List<Key> key_list; - List<Create_field> create_list; - uint flags; - enum enum_enable_or_disable keys_onoff; - enum tablespace_op_type tablespace_op; - List<char> partition_names; - uint num_parts; - enum_alter_table_change_level change_level; - Create_field *datetime_field; - bool error_if_not_empty; - - - Alter_info() : - flags(0), - keys_onoff(LEAVE_AS_IS), - tablespace_op(NO_TABLESPACE_OP), - num_parts(0), - change_level(ALTER_TABLE_METADATA_ONLY), - datetime_field(NULL), - error_if_not_empty(FALSE) - {} - - void reset() - { - drop_list.empty(); - alter_list.empty(); - key_list.empty(); - create_list.empty(); - flags= 0; - keys_onoff= LEAVE_AS_IS; - tablespace_op= NO_TABLESPACE_OP; - num_parts= 0; - partition_names.empty(); - change_level= ALTER_TABLE_METADATA_ONLY; - datetime_field= 0; - error_if_not_empty= FALSE; - } - Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root); -private: - Alter_info &operator=(const Alter_info &rhs); // not implemented - Alter_info(const Alter_info &rhs); // not implemented -}; struct st_sp_chistics { @@ -1200,9 +1121,6 @@ struct st_sp_chistics enum enum_sp_data_access daccess; }; -extern const LEX_STRING null_lex_str; -extern const LEX_STRING empty_lex_str; - struct st_trg_chistics { enum trg_action_time_type action_time; @@ -1214,9 +1132,6 @@ extern sys_var *trg_new_row_fake_var; enum xa_option_words {XA_NONE, XA_JOIN, XA_RESUME, XA_ONE_PHASE, XA_SUSPEND, XA_FOR_MIGRATE}; -extern const LEX_STRING null_lex_str; -extern const LEX_STRING empty_lex_str; - class Sroutine_hash_entry; /* @@ -1273,7 +1188,38 @@ public: Sroutine_hash_entry **sroutines_list_own_last; uint sroutines_list_own_elements; - /* + /** + Locking state of tables in this particular statement. + + If we under LOCK TABLES or in prelocked mode we consider tables + for the statement to be "locked" if there was a call to lock_tables() + (which called handler::start_stmt()) for tables of this statement + and there was no matching close_thread_tables() call. + + As result this state may differ significantly from one represented + by Open_tables_state::lock/locked_tables_mode more, which are always + "on" under LOCK TABLES or in prelocked mode. + */ + enum enum_lock_tables_state { + LTS_NOT_LOCKED = 0, + LTS_LOCKED + }; + enum_lock_tables_state lock_tables_state; + bool is_query_tables_locked() + { + return (lock_tables_state == LTS_LOCKED); + } + + /** + Number of tables which were open by open_tables() and to be locked + by lock_tables(). + Note that we set this member only in some cases, when this value + needs to be passed from open_tables() to lock_tables() which are + separated by some amount of code. + */ + uint table_count; + + /* These constructor and destructor serve for creation/destruction of Query_tables_list instances which are used as backup storage. */ @@ -1300,7 +1246,7 @@ public: } bool requires_prelocking() { - return test(query_tables_own_last); + return MY_TEST(query_tables_own_last); } void mark_as_requiring_prelocking(TABLE_LIST **tables_own_last) { @@ -1891,6 +1837,9 @@ enum enum_comment_state class Lex_input_stream { + size_t unescape(CHARSET_INFO *cs, char *to, + const char *str, const char *end, int sep); + my_charset_conv_wc_mb get_escape_func(THD *thd, my_wc_t sep) const; public: Lex_input_stream() { @@ -2161,23 +2110,29 @@ public: return (uint) (m_body_utf8_ptr - m_body_utf8); } + /** + Get the maximum length of the utf8-body buffer. + The utf8 body can grow because of the character set conversion and escaping. + */ + uint get_body_utf8_maximum_length(THD *thd); + void body_utf8_start(THD *thd, const char *begin_ptr); void body_utf8_append(const char *ptr); void body_utf8_append(const char *ptr, const char *end_ptr); - void body_utf8_append_literal(THD *thd, - const LEX_STRING *txt, - CHARSET_INFO *txt_cs, - const char *end_ptr); - + void body_utf8_append_ident(THD *thd, + const LEX_STRING *txt, + const char *end_ptr); + void body_utf8_append_escape(THD *thd, + const LEX_STRING *txt, + CHARSET_INFO *txt_cs, + const char *end_ptr, + my_wc_t sep); /** Current thread. */ THD *m_thd; /** Current line number. */ uint yylineno; - /** Length of the last token parsed. */ - uint yytoklen; - /** Interface with bison, value of the last token parsed. */ LEX_YYSTYPE yylval; @@ -2192,6 +2147,12 @@ public: /** LALR(2) resolution, value of the look ahead token.*/ LEX_YYSTYPE lookahead_yylval; + bool get_text(LEX_STRING *to, uint sep, int pre_skip, int post_skip); + + void add_digest_token(uint token, LEX_YYSTYPE yylval); + + void reduce_digest_token(uint token_left, uint token_right); + private: /** Pointer to the current position in the raw input stream. */ char *m_ptr; @@ -2307,6 +2268,11 @@ public: NOTE: this member must be used within MYSQLlex() function only. */ CHARSET_INFO *m_underscore_cs; + + /** + Current statement digest instrumentation. + */ + sql_digest_state* m_digest; }; /** @@ -2366,6 +2332,97 @@ protected: LEX *m_lex; }; + +class Delete_plan; +class SQL_SELECT; + +class Explain_query; +class Explain_update; +class Explain_delete; + +/* + Query plan of a single-table UPDATE. + (This is actually a plan for single-table DELETE also) +*/ + +class Update_plan +{ +protected: + bool impossible_where; + bool no_partitions; +public: + /* + When single-table UPDATE updates a VIEW, that VIEW's select is still + listed as the first child. When we print EXPLAIN, it looks like a + subquery. + In order to get rid of it, updating_a_view=TRUE means that first child + select should not be shown when printing EXPLAIN. + */ + bool updating_a_view; + + /* Allocate things there */ + MEM_ROOT *mem_root; + + TABLE *table; + SQL_SELECT *select; + uint index; + ha_rows scanned_rows; + /* + Top-level select_lex. Most of its fields are not used, we need it only to + get to the subqueries. + */ + SELECT_LEX *select_lex; + + key_map possible_keys; + bool using_filesort; + bool using_io_buffer; + + /* Set this plan to be a plan to do nothing because of impossible WHERE */ + void set_impossible_where() { impossible_where= true; } + void set_no_partitions() { no_partitions= true; } + + Explain_update* save_explain_update_data(MEM_ROOT *mem_root, THD *thd); +protected: + void save_explain_data_intern(MEM_ROOT *mem_root, Explain_update *eu, bool is_analyze); +public: + virtual ~Update_plan() {} + + Update_plan(MEM_ROOT *mem_root_arg) : + impossible_where(false), no_partitions(false), + mem_root(mem_root_arg), + using_filesort(false), using_io_buffer(false) + {} +}; + + +/* Query plan of a single-table DELETE */ +class Delete_plan : public Update_plan +{ + bool deleting_all_rows; +public: + + /* Construction functions */ + Delete_plan(MEM_ROOT *mem_root_arg) : + Update_plan(mem_root_arg), + deleting_all_rows(false) + {} + + /* Set this query plan to be a plan to make a call to h->delete_all_rows() */ + void set_delete_all_rows(ha_rows rows_arg) + { + deleting_all_rows= true; + scanned_rows= rows_arg; + } + void cancel_delete_all_rows() + { + deleting_all_rows= false; + } + + Explain_delete* save_explain_delete_data(MEM_ROOT *mem_root, THD *thd); +}; + + +class Query_arena_memroot; /* The state of the lex parsing. This is saved in the THD struct */ struct LEX: public Query_tables_list @@ -2376,33 +2433,57 @@ struct LEX: public Query_tables_list SELECT_LEX *current_select; /* list of all SELECT_LEX */ SELECT_LEX *all_selects_list; + + /* Query Plan Footprint of a currently running select */ + Explain_query *explain; + + // type information + char *length,*dec; + CHARSET_INFO *charset; + /* + LEX which represents current statement (conventional, SP or PS) + + For example during view parsing THD::lex will point to the views LEX and + lex::stmt_lex will point to LEX of the statement where the view will be + included + + Currently it is used to have always correct select numbering inside + statement (LEX::current_select_number) without storing and restoring a + global counter which was THD::select_number. + + TODO: make some unified statement representation (now SP has different) + to store such data like LEX::current_select_number. + */ + LEX *stmt_lex; - char *length,*dec,*change; LEX_STRING name; char *help_arg; char *backup_dir; /* For RESTORE/BACKUP */ char* to_log; /* For PURGE MASTER LOGS TO */ char* x509_subject,*x509_issuer,*ssl_cipher; - String *wild; + String *wild; /* Wildcard in SHOW {something} LIKE 'wild'*/ sql_exchange *exchange; select_result *result; - Item *default_value, *on_update_value; + /** + @c the two may also hold BINLOG arguments: either comment holds a + base64-char string or both represent the BINLOG fragment user variables. + */ LEX_STRING comment, ident; LEX_USER *grant_user; XID *xid; THD *thd; - Virtual_column_info *vcol_info; /* maintain a list of used plugins for this LEX */ DYNAMIC_ARRAY plugins; plugin_ref plugins_static_buffer[INITIAL_LEX_PLUGIN_LIST_SIZE]; - CHARSET_INFO *charset; bool text_string_is_7bit; /** SELECT of CREATE VIEW statement */ LEX_STRING create_view_select; + uint current_select_number; // valid for statment LEX (not view) + /** Start of 'ON table', in trigger statements. */ const char* raw_trg_on_table_name_begin; /** End of 'ON table', in trigger statements. */ @@ -2417,17 +2498,29 @@ struct LEX: public Query_tables_list */ LEX_USER *definer; - List<Key_part_spec> col_list; List<Key_part_spec> ref_list; - List<String> interval_list; List<LEX_USER> users_list; List<LEX_COLUMN> columns; List<Item> *insert_list,field_list,value_list,update_list; List<List_item> many_values; List<set_var_base> var_list; + List<set_var_base> stmt_var_list; //SET_STATEMENT values + List<set_var_base> old_var_list; // SET STATEMENT old values +private: + Query_arena_memroot *arena_for_set_stmt; + MEM_ROOT *mem_root_for_set_stmt; + void parse_error(); +public: + inline bool is_arena_for_set_stmt() {return arena_for_set_stmt != 0;} + bool set_arena_for_set_stmt(Query_arena *backup); + void reset_arena_for_set_stmt(Query_arena *backup); + void free_arena_for_set_stmt(); + List<Item_func_set_user_var> set_var_list; // in-query assignment list List<Item_param> param_list; List<LEX_STRING> view_list; // view list (list of field names in view) + List<LEX_STRING> *column_list; // list of column names (in ANALYZE) + List<LEX_STRING> *index_list; // list of index names (in ANALYZE) /* A stack of name resolution contexts for the query. This stack is used at parse time to set local name resolution contexts for various parts @@ -2449,13 +2542,15 @@ struct LEX: public Query_tables_list Item_sum *in_sum_func; udf_func udf; HA_CHECK_OPT check_opt; // check/repair options - HA_CREATE_INFO create_info; - KEY_CREATE_INFO key_create_info; + Table_specification_st create_info; + Key *last_key; LEX_MASTER_INFO mi; // used by CHANGE MASTER LEX_SERVER_OPTIONS server_options; + LEX_STRING relay_log_connection_name; USER_RESOURCES mqh; LEX_RESET_SLAVE reset_slave_info; - ulong type; + ulonglong type; + ulong next_binlog_file_number; /* The following is used by KILL */ killed_state kill_signal; killed_type kill_type; @@ -2470,7 +2565,7 @@ struct LEX: public Query_tables_list */ nesting_map allow_sum_func; - Sql_statement *m_stmt; + Sql_cmd *m_sql_cmd; /* Usually `expr` rule of yacc is quite reused but some commands better @@ -2495,6 +2590,8 @@ struct LEX: public Query_tables_list union { enum ha_rkey_function ha_rkey_mode; enum xa_option_words xa_opt; + bool with_admin_option; // GRANT role + bool with_persistent_for_clause; // uses PERSISTENT FOR clause (in ANALYZE) }; enum enum_var_type option_type; enum enum_view_create_mode create_view_mode; @@ -2502,11 +2599,10 @@ struct LEX: public Query_tables_list uint profile_query_id; uint profile_options; - uint uint_geom_type; uint grant, grant_tot_col, which_columns; enum Foreign_key::fk_match_opt fk_match_option; - enum Foreign_key::fk_option fk_update_opt; - enum Foreign_key::fk_option fk_delete_opt; + enum_fk_option fk_update_opt; + enum_fk_option fk_delete_opt; uint slave_thd_opt, start_transaction_opt; int nest_level; /* @@ -2516,6 +2612,8 @@ struct LEX: public Query_tables_list */ uint table_count; uint8 describe; + bool analyze_stmt; /* TRUE<=> this is "ANALYZE $stmt" */ + bool explain_json; /* A flag that indicates what kinds of derived tables are present in the query (0 if no derived tables, otherwise a combination of flags @@ -2525,13 +2623,14 @@ struct LEX: public Query_tables_list uint16 create_view_algorithm; uint8 create_view_check; uint8 context_analysis_only; - bool drop_if_exists, drop_temporary, local_file, one_shot_set; + bool local_file; + bool check_exists; bool autocommit; bool verbose, no_write_to_binlog; enum enum_yes_no_unknown tx_chain, tx_release; bool safe_to_cache_query; - bool subqueries, ignore, online; + bool subqueries, ignore; st_parsing_options parsing_options; Alter_info alter_info; /* @@ -2556,6 +2655,7 @@ struct LEX: public Query_tables_list bool sp_lex_in_use; /* Keep track on lex usage in SPs for error handling */ bool all_privileges; bool proxy_priv; + sp_pcontext *spcont; st_sp_chistics sp_chistics; @@ -2609,9 +2709,17 @@ struct LEX: public Query_tables_list }; /** - Collects create options for Field and KEY + Collects create options for KEY + */ + engine_option_value *option_list; + + /** + Helper pointer to the end of the list when parsing options for + LEX::create_info.option_list (for table) + LEX::last_field->option_list (for fields) + LEX::option_list (for indexes) */ - engine_option_value *option_list, *option_list_last; + engine_option_value *option_list_last; /** During name resolution search only in the table list given by @@ -2648,6 +2756,13 @@ struct LEX: public Query_tables_list */ Item *limit_rows_examined; ulonglong limit_rows_examined_cnt; + /** + Holds a set of domain_ids for deletion at FLUSH..DELETE_DOMAIN_ID + */ + DYNAMIC_ARRAY delete_gtid_domain; + static const ulong initial_gtid_domain_buffer_size= 16; + uint32 gtid_domain_static_buffer[initial_gtid_domain_buffer_size]; + inline void set_limit_rows_examined() { if (limit_rows_examined) @@ -2656,10 +2771,23 @@ struct LEX: public Query_tables_list limit_rows_examined_cnt= ULONGLONG_MAX; } + + inline void free_set_stmt_mem_root() + { + DBUG_ASSERT(!is_arena_for_set_stmt()); + if (mem_root_for_set_stmt) + { + free_root(mem_root_for_set_stmt, MYF(0)); + delete mem_root_for_set_stmt; + mem_root_for_set_stmt= 0; + } + } + LEX(); virtual ~LEX() { + free_set_stmt_mem_root(); destroy_query_tables_list(); plugin_unlock_list(NULL, (plugin_ref *)plugins.buffer, plugins.elements); delete_dynamic(&plugins); @@ -2740,9 +2868,9 @@ struct LEX: public Query_tables_list void cleanup_after_one_table_open(); - bool push_context(Name_resolution_context *context) + bool push_context(Name_resolution_context *context, MEM_ROOT *mem_root) { - return context_stack.push_front(context); + return context_stack.push_front(context, mem_root); } void pop_context() @@ -2791,6 +2919,91 @@ struct LEX: public Query_tables_list bool save_prep_leaf_tables(); + int print_explain(select_result_sink *output, uint8 explain_flags, + bool is_analyze, bool *printed_anything); + void restore_set_statement_var(); + + void init_last_field(Create_field *field, const char *name, CHARSET_INFO *cs); + void set_last_field_type(enum enum_field_types type); + bool set_bincmp(CHARSET_INFO *cs, bool bin); + // Check if "KEY IF NOT EXISTS name" used outside of ALTER context + bool check_add_key(DDL_options_st ddl) + { + if (ddl.if_not_exists() && sql_command != SQLCOM_ALTER_TABLE) + { + parse_error(); + return true; + } + return false; + } + // Add a key as a part of CREATE TABLE or ALTER TABLE + bool add_key(Key::Keytype key_type, const LEX_STRING &key_name, + ha_key_alg algorithm, DDL_options_st ddl) + { + if (check_add_key(ddl) || + !(last_key= new Key(key_type, key_name, algorithm, false, ddl))) + return true; + alter_info.key_list.push_back(last_key); + return false; + } + // Add a key for a CREATE INDEX statement + bool add_create_index(Key::Keytype key_type, const LEX_STRING &key_name, + ha_key_alg algorithm, DDL_options_st ddl) + { + if (check_create_options(ddl) || + !(last_key= new Key(key_type, key_name, algorithm, false, ddl))) + return true; + alter_info.key_list.push_back(last_key); + return false; + } + void set_command(enum_sql_command command, + DDL_options_st options) + { + sql_command= command; + create_info.set(options); + } + void set_command(enum_sql_command command, + uint scope, + DDL_options_st options) + { + set_command(command, options); + create_info.options|= scope; // HA_LEX_CREATE_TMP_TABLE or 0 + } + bool check_create_options(DDL_options_st options) + { + if (options.or_replace() && options.if_not_exists()) + { + my_error(ER_WRONG_USAGE, MYF(0), "OR REPLACE", "IF NOT EXISTS"); + return true; + } + return false; + } + bool add_create_options_with_check(DDL_options_st options) + { + create_info.add(options); + return check_create_options(create_info); + } + bool set_command_with_check(enum_sql_command command, + uint scope, + DDL_options_st options) + { + set_command(command, scope, options); + return check_create_options(options); + } + bool set_command_with_check(enum_sql_command command, DDL_options_st options) + { + set_command(command, options); + return check_create_options(options); + } + /* + DROP shares lex->create_info to store TEMPORARY and IF EXISTS options + to save on extra initialization in lex_start(). + Add some wrappers, to avoid direct use of lex->create_info in the + caller code processing DROP statements (which might look confusing). + */ + bool tmp_table() const { return create_info.tmp_table(); } + bool if_exists() const { return create_info.if_exists(); } + /* Run specified phases for derived tables/views in the given list @@ -2931,6 +3144,18 @@ public: }; /** + Input parameters to the parser. +*/ +struct Parser_input +{ + bool m_compute_digest; + + Parser_input() + : m_compute_digest(false) + {} +}; + +/** Internal state of the parser. The complete state consist of: - state data used during lexical parsing, @@ -2957,9 +3182,15 @@ public: ~Parser_state() {} + Parser_input m_input; Lex_input_stream m_lip; Yacc_state m_yacc; + /** + Current performance digest instrumentation. + */ + PSI_digest_locker* m_digest_psi; + void reset(char *found_semicolon, unsigned int length) { m_lip.reset(found_semicolon, length); @@ -2967,21 +3198,14 @@ public: } }; +extern sql_digest_state * +digest_add_token(sql_digest_state *state, uint token, LEX_YYSTYPE yylval); -struct st_lex_local: public LEX +extern sql_digest_state * +digest_reduce_token(sql_digest_state *state, uint token_left, uint token_right); + +struct st_lex_local: public LEX, public Sql_alloc { - static void *operator new(size_t size) throw() - { - return sql_alloc(size); - } - static void *operator new(size_t size, MEM_ROOT *mem_root) throw() - { - return (void*) alloc_root(mem_root, (uint) size); - } - static void operator delete(void *ptr,size_t size) - { TRASH_FREE(ptr, size); } - static void operator delete(void *ptr, MEM_ROOT *mem_root) - { /* Never called */ } }; extern void lex_init(void); diff --git a/sql/sql_lifo_buffer.h b/sql/sql_lifo_buffer.h index f551cc48c23..17024d15beb 100644 --- a/sql/sql_lifo_buffer.h +++ b/sql/sql_lifo_buffer.h @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /** @defgroup Bi-directional LIFO buffers used by DS-MRR implementation diff --git a/sql/sql_list.h b/sql/sql_list.h index 8956a786715..f1c4d1fe914 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -48,8 +48,8 @@ public: { /* never called */ } static void operator delete[](void *ptr, size_t size) { TRASH_FREE(ptr, size); } #ifdef HAVE_valgrind - bool dummy; - inline Sql_alloc() :dummy(0) {} + bool dummy_for_valgrind; + inline Sql_alloc() :dummy_for_valgrind(0) {} inline ~Sql_alloc() {} #else inline Sql_alloc() {} @@ -221,19 +221,22 @@ public: } return 1; } - inline bool push_front(void *info) + bool push_front_impl(list_node *node) { - list_node *node=new list_node(info,first); if (node) { if (last == &first) - last= &node->next; + last= &node->next; first=node; elements++; return 0; } return 1; } + inline bool push_front(void *info) + { return push_front_impl(new list_node(info, first)); } + inline bool push_front(void *info, MEM_ROOT *mem_root) + { return push_front_impl(new (mem_root) list_node(info,first)); } void remove(list_node **prev) { list_node *node=(*prev)->next; @@ -244,7 +247,7 @@ public: delete *prev; *prev=node; } - inline void concat(base_list *list) + inline void append(base_list *list) { if (!list->is_empty()) { @@ -290,7 +293,7 @@ public: *prev= &end_of_list; last= prev; } - inline void prepand(base_list *list) + inline void prepend(base_list *list) { if (!list->is_empty()) { @@ -516,12 +519,14 @@ public: inline bool push_back(T *a, MEM_ROOT *mem_root) { return base_list::push_back(a, mem_root); } inline bool push_front(T *a) { return base_list::push_front(a); } + inline bool push_front(T *a, MEM_ROOT *mem_root) + { return base_list::push_front(a, mem_root); } inline T* head() {return (T*) base_list::head(); } inline T** head_ref() {return (T**) base_list::head_ref(); } inline T* pop() {return (T*) base_list::pop(); } - inline void concat(List<T> *list) { base_list::concat(list); } + inline void append(List<T> *list) { base_list::append(list); } + inline void prepend(List<T> *list) { base_list::prepend(list); } inline void disjoin(List<T> *list) { base_list::disjoin(list); } - inline void prepand(List<T> *list) { base_list::prepand(list); } inline bool add_unique(T *a, bool (*eq)(T *a, T *b)) { return base_list::add_unique(a, (List_eq *)eq); } void delete_elements(void) diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 850360c1dba..85ab43df0ec 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -19,6 +19,7 @@ /* Copy data from a textfile to table */ /* 2006-12 Erik Wetterberg : LOAD XML added */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_load.h" @@ -28,7 +29,6 @@ #include <my_dir.h> #include "sql_view.h" // check_key_in_view #include "sql_insert.h" // check_that_all_fields_are_given_values, - // prepare_triggers_for_insert_stmt, // write_record #include "sql_acl.h" // INSERT_ACL, UPDATE_ACL #include "log_event.h" // Delete_file_log_event, @@ -42,6 +42,8 @@ #include "sql_derived.h" #include "sql_show.h" +extern "C" int _my_b_net_read(IO_CACHE *info, uchar *Buffer, size_t Count); + class XML_TAG { public: int level; @@ -66,24 +68,96 @@ class READ_INFO { File file; uchar *buffer, /* Buffer for read text */ *end_of_buff; /* Data in bufferts ends here */ - uint buff_length, /* Length of buffert */ - max_length; /* Max length of row */ + uint buff_length; /* Length of buffert */ const uchar *field_term_ptr,*line_term_ptr; const char *line_start_ptr,*line_start_end; uint field_term_length,line_term_length,enclosed_length; int field_term_char,line_term_char,enclosed_char,escape_char; int *stack,*stack_pos; bool found_end_of_line,start_of_line,eof; - bool need_end_io_cache; - IO_CACHE cache; - NET *io_net; int level; /* for load xml */ + +#if MYSQL_VERSION_ID >= 100200 +#error This 10.0 and 10.1 specific fix should be removed in 10.2. +#error Fix read_mbtail() to use my_charlen() instead of my_charlen_tmp() +#else + int my_charlen_tmp(CHARSET_INFO *cs, const char *str, const char *end) + { + my_wc_t wc; + return cs->cset->mb_wc(cs, &wc, (const uchar *) str, (const uchar *) end); + } + + /** + Read a tail of a multi-byte character. + The first byte of the character is assumed to be already + read from the file and appended to "str". + + @returns true - if EOF happened unexpectedly + @returns false - no EOF happened: found a good multi-byte character, + or a bad byte sequence + + Note: + The return value depends only on EOF: + - read_mbtail() returns "false" is a good character was read, but also + - read_mbtail() returns "false" if an incomplete byte sequence was found + and no EOF happened. + + For example, suppose we have an ujis file with bytes 0x8FA10A, where: + - 0x8FA1 is an incomplete prefix of a 3-byte character + (it should be [8F][A1-FE][A1-FE] to make a full 3-byte character) + - 0x0A is a line demiliter + This file has some broken data, the trailing [A1-FE] is missing. + + In this example it works as follows: + - 0x8F is read from the file and put into "data" before the call + for read_mbtail() + - 0xA1 is read from the file and put into "data" by read_mbtail() + - 0x0A is kept in the read queue, so the next read iteration after + the current read_mbtail() call will normally find it and recognize as + a line delimiter + - the current call for read_mbtail() returns "false", + because no EOF happened + */ + bool read_mbtail(String *str) + { + int chlen; + if ((chlen= my_charlen_tmp(read_charset, str->end() - 1, str->end())) == 1) + return false; // Single byte character found + for (uint32 length0= str->length() - 1 ; MY_CS_IS_TOOSMALL(chlen); ) + { + int chr= GET; + if (chr == my_b_EOF) + { + DBUG_PRINT("info", ("read_mbtail: chlen=%d; unexpected EOF", chlen)); + return true; // EOF + } + str->append(chr); + chlen= my_charlen_tmp(read_charset, str->ptr() + length0, str->end()); + if (chlen == MY_CS_ILSEQ) + { + /** + It has been an incomplete (but a valid) sequence so far, + but the last byte turned it into a bad byte sequence. + Unget the very last byte. + */ + str->length(str->length() - 1); + PUSH(chr); + DBUG_PRINT("info", ("read_mbtail: ILSEQ")); + return false; // Bad byte sequence + } + } + DBUG_PRINT("info", ("read_mbtail: chlen=%d", chlen)); + return false; // Good multi-byte character + } +#endif + public: bool error,line_cuted,found_null,enclosed; uchar *row_start, /* Found row starts here */ *row_end; /* Found row ends here */ CHARSET_INFO *read_charset; + LOAD_FILE_IO_CACHE cache; READ_INFO(File file,uint tot_length,CHARSET_INFO *cs, String &field_term,String &line_start,String &line_term, @@ -98,28 +172,12 @@ public: /* load xml */ List<XML_TAG> taglist; int read_value(int delim, String *val); - int read_xml(); + int read_xml(THD *thd); int clear_level(int level); - /* - We need to force cache close before destructor is invoked to log - the last read block - */ - void end_io_cache() - { - ::end_io_cache(&cache); - need_end_io_cache = 0; - } my_off_t file_length() { return cache.end_of_file; } my_off_t position() { return my_b_tell(&cache); } - /* - Either this method, or we need to make cache public - Arg must be set from mysql_load() since constructor does not see - either the table or THD value - */ - void set_io_cache_arg(void* arg) { cache.arg = arg; } - /** skip all data till the eof. */ @@ -187,7 +245,6 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, String *enclosed=ex->enclosed; bool is_fifo=0; #ifndef EMBEDDED_LIBRARY - LOAD_FILE_INFO lf_info; killed_state killed_status; bool is_concurrent; #endif @@ -216,7 +273,8 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, if (escaped->length() > 1 || enclosed->length() > 1) { - my_message(ER_WRONG_FIELD_TERMINATORS,ER(ER_WRONG_FIELD_TERMINATORS), + my_message(ER_WRONG_FIELD_TERMINATORS, + ER_THD(thd, ER_WRONG_FIELD_TERMINATORS), MYF(0)); DBUG_RETURN(TRUE); } @@ -226,9 +284,9 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, !field_term->is_ascii() || !ex->line_term->is_ascii() || !ex->line_start->is_ascii()) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED, - ER(WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED)); + ER_THD(thd, WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED)); } if (open_and_lock_tables(thd, table_list, TRUE, 0)) @@ -272,7 +330,8 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, */ if (unique_table(thd, table_list, table_list->next_global, 0)) { - my_error(ER_UPDATE_TABLE_USED, MYF(0), table_list->table_name); + my_error(ER_UPDATE_TABLE_USED, MYF(0), table_list->table_name, + "LOAD DATA"); DBUG_RETURN(TRUE); } @@ -291,10 +350,9 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, Item *item; if (!(item= field_iterator.create_item(thd))) DBUG_RETURN(TRUE); - fields_vars.push_back(item->real_item()); + fields_vars.push_back(item->real_item(), thd->mem_root); } bitmap_set_all(table->write_set); - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; /* Let us also prepare SET clause, altough it is probably empty in this case. @@ -310,27 +368,31 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, setup_fields(thd, 0, set_fields, MARK_COLUMNS_WRITE, 0, NULL, 0) || check_that_all_fields_are_given_values(thd, table, table_list)) DBUG_RETURN(TRUE); - /* - Check whenever TIMESTAMP field with auto-set feature specified - explicitly. - */ - if (table->timestamp_field) - { - if (bitmap_is_set(table->write_set, - table->timestamp_field->field_index)) - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - else - { - bitmap_set_bit(table->write_set, - table->timestamp_field->field_index); - } - } + /* Add all fields with default functions to table->write_set. */ + if (table->default_field) + table->mark_default_fields_for_write(); /* Fix the expressions in SET clause */ if (setup_fields(thd, 0, set_values, MARK_COLUMNS_READ, 0, NULL, 0)) DBUG_RETURN(TRUE); } + switch_to_nullable_trigger_fields(fields_vars, table); + switch_to_nullable_trigger_fields(set_fields, table); + switch_to_nullable_trigger_fields(set_values, table); + + table->prepare_triggers_for_insert_stmt_or_event(); + table->mark_columns_needed_for_insert(); - prepare_triggers_for_insert_stmt(table); + if (table->vfield) + { + for (Field **vfield_ptr= table->vfield; *vfield_ptr; vfield_ptr++) + { + if ((*vfield_ptr)->stored_in_db) + { + thd->lex->unit.insert_table_with_stored_vcol= table; + break; + } + } + } uint tot_length=0; bool use_blobs= 0, use_vars= 0; @@ -357,7 +419,8 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, } if (use_blobs && !ex->line_term->length() && !field_term->length()) { - my_message(ER_BLOBS_AND_NO_TERMINATED,ER(ER_BLOBS_AND_NO_TERMINATED), + my_message(ER_BLOBS_AND_NO_TERMINATED, + ER_THD(thd, ER_BLOBS_AND_NO_TERMINATED), MYF(0)); DBUG_RETURN(TRUE); } @@ -396,11 +459,11 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, MY_RETURN_REAL_PATH); } - if (thd->slave_thread) + if (thd->rgi_slave) { #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) - if (strncmp(active_mi->rli.slave_patternload_file, name, - active_mi->rli.slave_patternload_file_size)) + if (strncmp(thd->rgi_slave->rli->slave_patternload_file, name, + thd->rgi_slave->rli->slave_patternload_file_size)) { /* LOAD DATA INFILE in the slave SQL Thread can only read from @@ -471,11 +534,10 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, #ifndef EMBEDDED_LIBRARY if (mysql_bin_log.is_open()) { - lf_info.thd = thd; - lf_info.wrote_create_file = 0; - lf_info.last_pos_in_file = HA_POS_ERROR; - lf_info.log_delayed= transactional_table; - read_info.set_io_cache_arg((void*) &lf_info); + read_info.cache.thd = thd; + read_info.cache.wrote_create_file = 0; + read_info.cache.last_pos_in_file = HA_POS_ERROR; + read_info.cache.log_delayed= transactional_table; } #endif /*!EMBEDDED_LIBRARY*/ @@ -494,8 +556,9 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, } thd_proc_info(thd, "reading file"); - if (!(error=test(read_info.error))) + if (!(error= MY_TEST(read_info.error))) { + table->reset_default_fields(); table->next_number_field=table->found_next_number_field; if (ignore || handle_duplicates == DUP_REPLACE) @@ -508,13 +571,15 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, table->file->ha_start_bulk_insert((ha_rows) 0); table->copy_blobs=1; - thd->abort_on_warning= (!ignore && - (thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES))); + thd->abort_on_warning= !ignore && thd->is_strict_mode(); thd_progress_init(thd, 2); - if (ex->filetype == FILETYPE_XML) /* load xml */ + if (table_list->table->validate_default_values_of_unset_fields(thd)) + { + read_info.error= true; + error= 1; + } + else if (ex->filetype == FILETYPE_XML) /* load xml */ error= read_xml_field(thd, info, table_list, fields_vars, set_fields, set_values, read_info, *(ex->line_term), skip_lines, ignore); @@ -528,7 +593,8 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, *enclosed, skip_lines, ignore); thd_proc_info(thd, "End bulk insert"); - thd_progress_next_stage(thd); + if (!error) + thd_progress_next_stage(thd); if (thd->locked_tables_mode <= LTM_LOCK_TABLES && table->file->ha_end_bulk_insert() && !error) { @@ -551,7 +617,7 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, DBUG_EXECUTE_IF("simulate_kill_bug27571", { error=1; - thd->killed= KILL_QUERY; + thd->set_killed(KILL_QUERY); };); #ifndef EMBEDDED_LIBRARY @@ -573,30 +639,12 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, { { /* - Make sure last block (the one which caused the error) gets - logged. This is needed because otherwise after write of (to - the binlog, not to read_info (which is a cache)) - Delete_file_log_event the bad block will remain in read_info - (because pre_read is not called at the end of the last - block; remember pre_read is called whenever a new block is - read from disk). At the end of mysql_load(), the destructor - of read_info will call end_io_cache() which will flush - read_info, so we will finally have this in the binlog: - - Append_block # The last successfull block - Delete_file - Append_block # The failing block - which is nonsense. - Or could also be (for a small file) - Create_file # The failing block - which is nonsense (Delete_file is not written in this case, because: - Create_file has not been written, so Delete_file is not written, then - when read_info is destroyed end_io_cache() is called which writes - Create_file. + Make sure last block (the one which caused the error) gets + logged. */ - read_info.end_io_cache(); + log_loaded_block(&read_info.cache, 0, 0); /* If the file was not empty, wrote_create_file is true */ - if (lf_info.wrote_create_file) + if (read_info.cache.wrote_create_file) { int errcode= query_error_code(thd, killed_status == NOT_KILLED); @@ -622,12 +670,15 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, error= -1; // Error on read goto err; } - sprintf(name, ER(ER_LOAD_INFO), (ulong) info.records, (ulong) info.deleted, + sprintf(name, ER_THD(thd, ER_LOAD_INFO), + (ulong) info.records, (ulong) info.deleted, (ulong) (info.records - info.copied), - (ulong) thd->warning_info->statement_warn_count()); + (long) thd->get_stmt_da()->current_statement_warn_count()); if (thd->transaction.stmt.modified_non_trans_table) thd->transaction.all.modified_non_trans_table= TRUE; + thd->transaction.all.m_unsafe_rollback_flags|= + (thd->transaction.stmt.m_unsafe_rollback_flags & THD_TRANS::DID_WAIT); #ifndef EMBEDDED_LIBRARY if (mysql_bin_log.is_open()) { @@ -644,12 +695,11 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, else { /* - As already explained above, we need to call end_io_cache() or the last - block will be logged only after Execute_load_query_log_event (which is - wrong), when read_info is destroyed. + As already explained above, we need to call log_loaded_block() to have + the last block logged */ - read_info.end_io_cache(); - if (lf_info.wrote_create_file) + log_loaded_block(&read_info.cache, 0, 0); + if (read_info.cache.wrote_create_file) { int errcode= query_error_code(thd, killed_status == NOT_KILLED); error= write_execute_load_query_log_event(thd, ex, @@ -801,7 +851,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, List_iterator_fast<Item> it(fields_vars); Item_field *sql_field; TABLE *table= table_list->table; - bool err, progress_reports; + bool err, progress_reports, auto_increment_field_not_null=false; ulonglong counter, time_to_report_progress; DBUG_ENTER("read_fixed_length"); @@ -811,6 +861,12 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if ((thd->progress.max_counter= read_info.file_length()) == ~(my_off_t) 0) progress_reports= 0; + while ((sql_field= (Item_field*) it++)) + { + if (table->field[sql_field->field->field_index] == table->next_number_field) + auto_increment_field_not_null= true; + } + while (!read_info.read_fixed_length()) { if (thd->killed) @@ -853,8 +909,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, while ((sql_field= (Item_field*) it++)) { Field *field= sql_field->field; - if (field == table->next_number_field) - table->auto_increment_field_not_null= TRUE; + table->auto_increment_field_not_null= auto_increment_field_not_null; /* No fields specified in fields_vars list can be null in this format. Mark field as not null, we should do this for each row because of @@ -865,12 +920,16 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if (pos == read_info.row_end) { thd->cuted_fields++; /* Not enough fields */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_TOO_FEW_RECORDS, - ER(ER_WARN_TOO_FEW_RECORDS), - thd->warning_info->current_row_for_warning()); + ER_THD(thd, ER_WARN_TOO_FEW_RECORDS), + thd->get_stmt_da()->current_row_for_warning()); + /* + Timestamp fields that are NOT NULL are autoupdated if there is no + corresponding value in the data file. + */ if (!field->maybe_null() && field->type() == FIELD_TYPE_TIMESTAMP) - ((Field_timestamp*) field)->set_time(); + field->set_time(); } else { @@ -885,25 +944,26 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if ((pos+=length) > read_info.row_end) pos= read_info.row_end; /* Fills rest with space */ } + /* Do not auto-update this field. */ + field->set_has_explicit_value(); } if (pos != read_info.row_end) { thd->cuted_fields++; /* To long row */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_TOO_MANY_RECORDS, - ER(ER_WARN_TOO_MANY_RECORDS), - thd->warning_info->current_row_for_warning()); + ER_THD(thd, ER_WARN_TOO_MANY_RECORDS), + thd->get_stmt_da()->current_row_for_warning()); } if (thd->killed || - fill_record_n_invoke_before_triggers(thd, set_fields, set_values, + fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values, ignore_check_option_errors, - table->triggers, - TRG_EVENT_INSERT)) + TRG_EVENT_INSERT) || + (table->default_field && table->update_default_fields())) DBUG_RETURN(1); - switch (table_list->view_check_option(thd, - ignore_check_option_errors)) { + switch (table_list->view_check_option(thd, ignore_check_option_errors)) { case VIEW_CHECK_SKIP: read_info.next_line(); goto continue_loop; @@ -925,15 +985,15 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if (read_info.line_cuted) { thd->cuted_fields++; /* To long row */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_TOO_MANY_RECORDS, - ER(ER_WARN_TOO_MANY_RECORDS), - thd->warning_info->current_row_for_warning()); + ER_THD(thd, ER_WARN_TOO_MANY_RECORDS), + thd->get_stmt_da()->current_row_for_warning()); } - thd->warning_info->inc_current_row_for_warning(); + thd->get_stmt_da()->inc_current_row_for_warning(); continue_loop:; } - DBUG_RETURN(test(read_info.error)); + DBUG_RETURN(MY_TEST(read_info.error)); } @@ -997,7 +1057,7 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, pos=read_info.row_start; length=(uint) (read_info.row_end-pos); - real_item= item->filed_for_view_update(); + real_item= item->field_for_view_update(); if ((!read_info.enclosed && (enclosed_length && length == 4 && @@ -1020,18 +1080,24 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if (field->reset()) { my_error(ER_WARN_NULL_TO_NOTNULL, MYF(0), field->field_name, - thd->warning_info->current_row_for_warning()); + thd->get_stmt_da()->current_row_for_warning()); DBUG_RETURN(1); } field->set_null(); if (!field->maybe_null()) { + /* + Timestamp fields that are NOT NULL are autoupdated if there is no + corresponding value in the data file. + */ if (field->type() == MYSQL_TYPE_TIMESTAMP) - ((Field_timestamp*) field)->set_time(); + field->set_time(); else if (field != table->next_number_field) - field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + field->set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_NULL_TO_NOTNULL, 1); } + /* Do not auto-update this field. */ + field->set_has_explicit_value(); } continue; @@ -1055,6 +1121,7 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if (field == table->next_number_field) table->auto_increment_field_not_null= TRUE; field->store((char*) pos, length, read_info.read_charset); + field->set_has_explicit_value(); } } @@ -1075,7 +1142,7 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, break; for (; item ; item= it++) { - Item_field *real_item= item->filed_for_view_update(); + Item_field *real_item= item->field_for_view_update(); if (item->type() == Item::STRING_ITEM) { ((Item_user_var_as_out_param *)item)->set_null_value( @@ -1092,11 +1159,12 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if (field->reset()) { my_error(ER_WARN_NULL_TO_NOTNULL, MYF(0),field->field_name, - thd->warning_info->current_row_for_warning()); + thd->get_stmt_da()->current_row_for_warning()); DBUG_RETURN(1); } if (!field->maybe_null() && field->type() == FIELD_TYPE_TIMESTAMP) - ((Field_timestamp*) field)->set_time(); + field->set_time(); + field->set_has_explicit_value(); /* TODO: We probably should not throw warning for each field. But how about intention to always have the same number @@ -1104,19 +1172,19 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, in the end ?) */ thd->cuted_fields++; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_TOO_FEW_RECORDS, - ER(ER_WARN_TOO_FEW_RECORDS), - thd->warning_info->current_row_for_warning()); + ER_THD(thd, ER_WARN_TOO_FEW_RECORDS), + thd->get_stmt_da()->current_row_for_warning()); } } } if (thd->killed || - fill_record_n_invoke_before_triggers(thd, set_fields, set_values, + fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values, ignore_check_option_errors, - table->triggers, - TRG_EVENT_INSERT)) + TRG_EVENT_INSERT) || + (table->default_field && table->update_default_fields())) DBUG_RETURN(1); switch (table_list->view_check_option(thd, @@ -1141,16 +1209,17 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if (read_info.line_cuted) { thd->cuted_fields++; /* To long row */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_TOO_MANY_RECORDS, ER(ER_WARN_TOO_MANY_RECORDS), - thd->warning_info->current_row_for_warning()); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_TOO_MANY_RECORDS, + ER_THD(thd, ER_WARN_TOO_MANY_RECORDS), + thd->get_stmt_da()->current_row_for_warning()); if (thd->killed) DBUG_RETURN(1); } - thd->warning_info->inc_current_row_for_warning(); + thd->get_stmt_da()->inc_current_row_for_warning(); continue_loop:; } - DBUG_RETURN(test(read_info.error)); + DBUG_RETURN(MY_TEST(read_info.error)); } @@ -1182,7 +1251,7 @@ read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, } // read row tag and save values into tag list - if (read_info.read_xml()) + if (read_info.read_xml(thd)) break; List_iterator_fast<XML_TAG> xmlit(read_info.taglist); @@ -1214,11 +1283,19 @@ read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, while(tag && strcmp(tag->field.c_ptr(), item->name) != 0) tag= xmlit++; + Item_field *real_item= item->field_for_view_update(); if (!tag) // found null { - if (item->type() == Item::FIELD_ITEM) + if (item->type() == Item::STRING_ITEM) + ((Item_user_var_as_out_param *) item)->set_null_value(cs); + else if (!real_item) + { + my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), item->name); + DBUG_RETURN(1); + } + else { - Field *field= ((Item_field *) item)->field; + Field *field= real_item->field; field->reset(); field->set_null(); if (field == table->next_number_field) @@ -1226,18 +1303,27 @@ read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if (!field->maybe_null()) { if (field->type() == FIELD_TYPE_TIMESTAMP) - ((Field_timestamp *) field)->set_time(); + field->set_time(); else if (field != table->next_number_field) - field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, + field->set_warning(Sql_condition::WARN_LEVEL_WARN, ER_WARN_NULL_TO_NOTNULL, 1); } + /* Do not auto-update this field. */ + field->set_has_explicit_value(); } - else - ((Item_user_var_as_out_param *) item)->set_null_value(cs); continue; } - if (item->type() == Item::FIELD_ITEM) + if (item->type() == Item::STRING_ITEM) + ((Item_user_var_as_out_param *) item)->set_value( + (char *) tag->value.ptr(), + tag->value.length(), cs); + else if (!real_item) + { + my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), item->name); + DBUG_RETURN(1); + } + else { Field *field= ((Item_field *)item)->field; @@ -1245,11 +1331,8 @@ read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, if (field == table->next_number_field) table->auto_increment_field_not_null= TRUE; field->store((char *) tag->value.ptr(), tag->value.length(), cs); + field->set_has_explicit_value(); } - else - ((Item_user_var_as_out_param *) item)->set_value( - (char *) tag->value.ptr(), - tag->value.length(), cs); } if (read_info.error) @@ -1269,7 +1352,15 @@ read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, for ( ; item; item= it++) { - if (item->type() == Item::FIELD_ITEM) + Item_field *real_item= item->field_for_view_update(); + if (item->type() == Item::STRING_ITEM) + ((Item_user_var_as_out_param *)item)->set_null_value(cs); + else if (!real_item) + { + my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), item->name); + DBUG_RETURN(1); + } + else { /* QQ: We probably should not throw warning for each field. @@ -1278,21 +1369,19 @@ read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, in the end ?) */ thd->cuted_fields++; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_TOO_FEW_RECORDS, - ER(ER_WARN_TOO_FEW_RECORDS), - thd->warning_info->current_row_for_warning()); + ER_THD(thd, ER_WARN_TOO_FEW_RECORDS), + thd->get_stmt_da()->current_row_for_warning()); } - else - ((Item_user_var_as_out_param *)item)->set_null_value(cs); } } if (thd->killed || - fill_record_n_invoke_before_triggers(thd, set_fields, set_values, + fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values, ignore_check_option_errors, - table->triggers, - TRG_EVENT_INSERT)) + TRG_EVENT_INSERT) || + (table->default_field && table->update_default_fields())) DBUG_RETURN(1); switch (table_list->view_check_option(thd, @@ -1312,10 +1401,10 @@ read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, its default value at the beginning of each loop iteration. */ thd->transaction.stmt.modified_non_trans_table= no_trans_update_stmt; - thd->warning_info->inc_current_row_for_warning(); + thd->get_stmt_da()->inc_current_row_for_warning(); continue_loop:; } - DBUG_RETURN(test(read_info.error) || thd->is_error()); + DBUG_RETURN(MY_TEST(read_info.error) || thd->is_error()); } /* load xml end */ @@ -1351,7 +1440,7 @@ READ_INFO::READ_INFO(File file_par, uint tot_length, CHARSET_INFO *cs, String &enclosed_par, int escape, bool get_it_from_net, bool is_fifo) :file(file_par), buffer(NULL), buff_length(tot_length), escape_char(escape), - found_end_of_line(false), eof(false), need_end_io_cache(false), + found_end_of_line(false), eof(false), error(false), line_cuted(false), found_null(false), read_charset(cs) { /* @@ -1391,19 +1480,19 @@ READ_INFO::READ_INFO(File file_par, uint tot_length, CHARSET_INFO *cs, line_term_char= line_term_length ? line_term_ptr[0] : INT_MAX; /* Set of a stack for unget if long terminators */ - uint length= max(cs->mbmaxlen, max(field_term_length, line_term_length)) + 1; + uint length= MY_MAX(cs->mbmaxlen, MY_MAX(field_term_length, line_term_length)) + 1; set_if_bigger(length,line_start.length()); stack=stack_pos=(int*) sql_alloc(sizeof(int)*length); - if (!(buffer=(uchar*) my_malloc(buff_length+1,MYF(0)))) - error=1; /* purecov: inspected */ + if (!(buffer=(uchar*) my_malloc(buff_length+1,MYF(MY_WME | MY_THREAD_SPECIFIC)))) + error= 1; /* purecov: inspected */ else { end_of_buff=buffer+buff_length; if (init_io_cache(&cache,(get_it_from_net) ? -1 : file, 0, (get_it_from_net) ? READ_NET : (is_fifo ? READ_FIFO : READ_CACHE),0L,1, - MYF(MY_WME))) + MYF(MY_WME | MY_THREAD_SPECIFIC))) { my_free(buffer); /* purecov: inspected */ buffer= NULL; @@ -1411,20 +1500,15 @@ READ_INFO::READ_INFO(File file_par, uint tot_length, CHARSET_INFO *cs, } else { - /* - init_io_cache() will not initialize read_function member - if the cache is READ_NET. So we work around the problem with a - manual assignment - */ - need_end_io_cache = 1; - #ifndef EMBEDDED_LIBRARY if (get_it_from_net) cache.read_function = _my_b_net_read; if (mysql_bin_log.is_open()) - cache.pre_read = cache.pre_close = - (IO_CACHE_CALLBACK) log_loaded_block; + { + cache.real_read_function= cache.read_function; + cache.read_function= log_loaded_block; + } #endif } } @@ -1433,8 +1517,7 @@ READ_INFO::READ_INFO(File file_par, uint tot_length, CHARSET_INFO *cs, READ_INFO::~READ_INFO() { - if (need_end_io_cache) - ::end_io_cache(&cache); + ::end_io_cache(&cache); my_free(buffer); List_iterator<XML_TAG> xmlit(taglist); XML_TAG *t; @@ -1463,6 +1546,54 @@ inline int READ_INFO::terminator(const uchar *ptr,uint length) } +/** + Read a field. + + The data in the loaded file was presumably escaped using + - either select_export::send_data() OUTFILE + - or mysql_real_escape_string() + using the same character set with the one specified in the current + "LOAD DATA INFILE ... CHARACTER SET ..." (or the default LOAD character set). + + Note, non-escaped multi-byte characters are scanned as a single entity. + This is needed to correctly distinguish between: + - 0x5C as an escape character versus + - 0x5C as the second byte in a multi-byte sequence (big5, cp932, gbk, sjis) + + Parts of escaped multi-byte characters are scanned on different loop + iterations. See the comment about 0x5C handling in select_export::send_data() + in sql_class.cc. + + READ_INFO::read_field() does not check wellformedness. + Raising wellformedness errors or warnings in READ_INFO::read_field() + would be wrong, as the data after unescaping can go into a BLOB field, + or into a TEXT/VARCHAR field of a different character set. + The loop below only makes sure to revert escaping made by + select_export::send_data() or mysql_real_escape_string(). + Wellformedness is checked later, during Field::store(str,length,cs) time. + + Note, in some cases users can supply data which did not go through + escaping properly. For example, utf8 "\<C3><A4>" + (backslash followed by LATIN SMALL LETTER A WITH DIAERESIS) + is improperly escaped data that could not be generated by + select_export::send_data() / mysql_real_escape_string(): + - either there should be two backslashes: "\\<C3><A4>" + - or there should be no backslashes at all: "<C3><A4>" + "\<C3>" and "<A4> are scanned on two different loop iterations and + store "<C3><A4>" into the field. + + Note, adding useless escapes before multi-byte characters like in the + example above is safe in case of utf8, but is not safe in case of + character sets that have escape_with_backslash_is_dangerous==TRUE, + such as big5, cp932, gbk, sjis. This can lead to mis-interpretation of the + data. Suppose we have a big5 character "<EE><5C>" followed by <30> (digit 0). + If we add an extra escape before this sequence, then we'll get + <5C><EE><5C><30>. The first loop iteration will turn <5C><EE> into <EE>. + The second loop iteration will turn <5C><30> into <30>. + So the program that generates a dump file for further use with LOAD DATA + must make sure to use escapes properly. +*/ + int READ_INFO::read_field() { int chr,found_enclosed_char; @@ -1499,7 +1630,8 @@ int READ_INFO::read_field() for (;;) { - while ( to < end_of_buff) + // Make sure we have enough space for the longest multi-byte character. + while ( to + read_charset->mbmaxlen < end_of_buff) { chr = GET; if (chr == my_b_EOF) @@ -1587,45 +1719,33 @@ int READ_INFO::read_field() } } #ifdef USE_MB - if (my_mbcharlen(read_charset, chr) > 1 && - to + my_mbcharlen(read_charset, chr) <= end_of_buff) +#endif + *to++ = (uchar) chr; +#if MYSQL_VERSION_ID >= 100200 +#error This 10.0 and 10.1 specific fix should be removed in 10.2 +#else + if (my_mbcharlen(read_charset, (uchar) chr) > 1) { - uchar* p= to; - int ml, i; - *to++ = chr; - - ml= my_mbcharlen(read_charset, chr); - - for (i= 1; i < ml; i++) - { - chr= GET; - if (chr == my_b_EOF) - { - /* - Need to back up the bytes already ready from illformed - multi-byte char - */ - to-= i; - goto found_eof; - } - *to++ = chr; - } - if (my_ismbchar(read_charset, - (const char *)p, - (const char *)to)) - continue; - for (i= 0; i < ml; i++) - PUSH(*--to); - chr= GET; + /* + A known MBHEAD found. Try to scan the full multi-byte character. + Otherwise, a possible following second byte 0x5C would be + mis-interpreted as an escape on the next iteration. + (Important for big5, gbk, sjis, cp932). + */ + String tmp((char *) to - 1, read_charset->mbmaxlen, read_charset); + tmp.length(1); + bool eof= read_mbtail(&tmp); + to+= tmp.length() - 1; + if (eof) + goto found_eof; } #endif - *to++ = (uchar) chr; } /* ** We come here if buffer is too small. Enlarge it and continue */ if (!(new_buffer=(uchar*) my_realloc((char*) buffer,buff_length+1+IO_SIZE, - MYF(MY_WME)))) + MYF(MY_WME | MY_THREAD_SPECIFIC)))) return (error=1); to=new_buffer + (to-buffer); buffer=new_buffer; @@ -1909,7 +2029,7 @@ int READ_INFO::read_value(int delim, String *val) tags and attributes are stored in taglist when tag set in ROWS IDENTIFIED BY is closed, we are ready and return */ -int READ_INFO::read_xml() +int READ_INFO::read_xml(THD *thd) { DBUG_ENTER("READ_INFO::read_xml"); int chr, chr2, chr3; @@ -2007,11 +2127,13 @@ int READ_INFO::read_xml() goto found_eof; /* save value to list */ - if(tag.length() > 0 && value.length() > 0) + if (tag.length() > 0 && value.length() > 0) { DBUG_PRINT("read_xml", ("lev:%i tag:%s val:%s", level,tag.c_ptr_safe(), value.c_ptr_safe())); - taglist.push_front( new XML_TAG(level, tag, value)); + XML_TAG *tmp= new XML_TAG(level, tag, value); + if (!tmp || taglist.push_front(tmp, thd->mem_root)) + DBUG_RETURN(1); // End of memory } tag.length(0); value.length(0); @@ -2078,13 +2200,15 @@ int READ_INFO::read_xml() } chr= read_value(delim, &value); - if(attribute.length() > 0 && value.length() > 0) + if (attribute.length() > 0 && value.length() > 0) { DBUG_PRINT("read_xml", ("lev:%i att:%s val:%s\n", level + 1, attribute.c_ptr_safe(), value.c_ptr_safe())); - taglist.push_front(new XML_TAG(level + 1, attribute, value)); + XML_TAG *tmp= new XML_TAG(level + 1, attribute, value); + if (!tmp || taglist.push_front(tmp, thd->mem_root)) + DBUG_RETURN(1); // End of memory } attribute.length(0); value.length(0); diff --git a/sql/sql_locale.cc b/sql/sql_locale.cc index 3123474a59b..58443a9a977 100644 --- a/sql/sql_locale.cc +++ b/sql/sql_locale.cc @@ -20,6 +20,7 @@ !! This file is built from my_locale.pl !! */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_locale.h" @@ -1432,7 +1433,7 @@ MY_LOCALE my_locale_pl_PL ); /***** LOCALE END pl_PL *****/ -/***** LOCALE BEGIN pt_BR: Portugese - Brazil *****/ +/***** LOCALE BEGIN pt_BR: Portuguese - Brazil *****/ static const char *my_locale_month_names_pt_BR[13] = {"janeiro","fevereiro","março","abril","maio","junho","julho","agosto","setembro","outubro","novembro","dezembro", NullS }; static const char *my_locale_ab_month_names_pt_BR[13] = @@ -1453,7 +1454,7 @@ MY_LOCALE my_locale_pt_BR ( 40, "pt_BR", - "Portugese - Brazil", + "Portuguese - Brazil", FALSE, &my_locale_typelib_month_names_pt_BR, &my_locale_typelib_ab_month_names_pt_BR, @@ -1468,7 +1469,7 @@ MY_LOCALE my_locale_pt_BR ); /***** LOCALE END pt_BR *****/ -/***** LOCALE BEGIN pt_PT: Portugese - Portugal *****/ +/***** LOCALE BEGIN pt_PT: Portuguese - Portugal *****/ static const char *my_locale_month_names_pt_PT[13] = {"Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro", NullS }; static const char *my_locale_ab_month_names_pt_PT[13] = @@ -1489,7 +1490,7 @@ MY_LOCALE my_locale_pt_PT ( 41, "pt_PT", - "Portugese - Portugal", + "Portuguese - Portugal", FALSE, &my_locale_typelib_month_names_pt_PT, &my_locale_typelib_ab_month_names_pt_PT, @@ -3247,6 +3248,75 @@ MY_LOCALE my_locale_el_GR ); /***** LOCALE END el_GR *****/ + +/***** LOCALE BEGIN rm_CH: Romansh - Switzerland *****/ +static const char *my_locale_month_names_rm_CH[13]= +{ + "schaner", "favrer", "mars", "avrigl", "matg", "zercladur", + "fanadur", "avust", "settember", "october", "november", "december", NullS +}; + +static const char *my_locale_ab_month_names_rm_CH[13]= +{ + "schan", "favr", "mars", "avr", "matg", "zercl", + "fan", "avust", "sett", "oct", "nov", "dec", NullS +}; + +static const char *my_locale_day_names_rm_CH[8]= +{ + "glindesdi", "mardi", "mesemna", "gievgia", + "venderdi", "sonda", "dumengia", NullS +}; + +static const char *my_locale_ab_day_names_rm_CH[8]= +{ + "gli", "ma", "me", "gie", "ve", "so", "du", NullS +}; + +static TYPELIB my_locale_typelib_month_names_rm_CH= +{ + array_elements(my_locale_month_names_rm_CH) - 1, + "", my_locale_month_names_rm_CH, NULL +}; + +static TYPELIB my_locale_typelib_ab_month_names_rm_CH= +{ + array_elements(my_locale_ab_month_names_rm_CH) - 1, + "", my_locale_ab_month_names_rm_CH, NULL +}; + +static TYPELIB my_locale_typelib_day_names_rm_CH= +{ + array_elements(my_locale_day_names_rm_CH) - 1, + "", my_locale_day_names_rm_CH, NULL +}; + +static TYPELIB my_locale_typelib_ab_day_names_rm_CH= +{ + array_elements(my_locale_ab_day_names_rm_CH) - 1, + "", my_locale_ab_day_names_rm_CH, NULL +}; + +MY_LOCALE my_locale_rm_CH +( + 110, + "rm_CH", + "Romansh - Switzerland", + FALSE, + &my_locale_typelib_month_names_rm_CH, + &my_locale_typelib_ab_month_names_rm_CH, + &my_locale_typelib_day_names_rm_CH, + &my_locale_typelib_ab_day_names_rm_CH, + 9, /* max mon name length */ + 9, /* max day name length */ + ',', /* decimal point rm_CH */ + '\'', /* thousands_sep rm_CH */ + "\x03\x03", /* grouping rm_CH */ + &global_errmsgs[en_US] +); +/***** LOCALE END rm_CH *****/ + + /* The list of all locales. Note, locales must be ordered according to their @@ -3365,6 +3435,7 @@ MY_LOCALE *my_locales[]= &my_locale_sv_FI, &my_locale_zh_HK, &my_locale_el_GR, + &my_locale_rm_CH, NULL }; @@ -3422,8 +3493,9 @@ MY_LOCALE *my_locale_by_name(const char *name) if (thd) { // Send a warning to the client - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_DEPRECATED_SYNTAX, ER(ER_WARN_DEPRECATED_SYNTAX), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_DEPRECATED_SYNTAX, + ER_THD(thd, ER_WARN_DEPRECATED_SYNTAX), name, locale->name); } else diff --git a/sql/sql_manager.cc b/sql/sql_manager.cc index 778c732d443..8cf849b97d0 100644 --- a/sql/sql_manager.cc +++ b/sql/sql_manager.cc @@ -21,9 +21,9 @@ * o Berkeley DB: removing unneeded log files. */ +#include <my_global.h> #include "sql_priv.h" #include "sql_manager.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_base.h" // flush_tables static bool volatile manager_thread_in_use; @@ -110,7 +110,7 @@ pthread_handler_t handle_manager(void *arg __attribute__((unused))) if (error == ETIMEDOUT || error == ETIME) { - tdc_flush_unused_tables(); + tc_purge(); error = 0; reset_flush_time = TRUE; } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index bb53c116b0c..6649c60f827 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2017, Oracle and/or its affiliates. - Copyright (c) 2008, 2017, MariaDB + Copyright (c) 2008, 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 @@ -15,16 +15,15 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define MYSQL_LEX 1 -#include "my_global.h" +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_parse.h" // sql_kill, *_precheck, *_prepare #include "lock.h" // try_transactional_lock, // check_transactional_lock, // set_handler_table_locks, // lock_global_read_lock, // make_global_read_lock_block_commit -#include "sql_base.h" // find_temporary_tablesx +#include "sql_base.h" // find_temporary_table #include "sql_cache.h" // QUERY_CACHE_FLAGS_SIZE, query_cache_* #include "sql_show.h" // mysqld_list_*, mysqld_show_*, // calc_sum_of_all_status @@ -44,7 +43,6 @@ #include "sql_table.h" // mysql_create_like_table, // mysql_create_table, // mysql_alter_table, - // mysql_recreate_table, // mysql_backup_table, // mysql_restore_table #include "sql_reload.h" // reload_acl_and_cache @@ -52,7 +50,7 @@ #include "sql_connect.h" // decrease_user_connections, // check_mqh, // reset_mqh -#include "sql_rename.h" // mysql_rename_table +#include "sql_rename.h" // mysql_rename_tables #include "sql_tablespace.h" // mysql_alter_tablespace #include "hostname.h" // hostname_cache_refresh #include "sql_acl.h" // *_ACL, check_grant, is_acl_user, @@ -82,6 +80,9 @@ #include <myisam.h> #include <my_dir.h> #include "rpl_handler.h" +#include "rpl_mi.h" + +#include "sql_digest.h" #include "sp_head.h" #include "sp.h" @@ -95,6 +96,9 @@ #include "probes_mysql.h" #include "set_var.h" #include "log_slow.h" +#include "sql_bootstrap.h" + +#include "my_json_writer.h" #define FLAGSTR(V,F) ((V)&(F)?#F" ":"") @@ -102,14 +106,18 @@ #include "../storage/maria/ha_maria.h" #endif +#include "wsrep_mysqld.h" +#include "wsrep_thd.h" + +static void wsrep_mysql_parse(THD *thd, char *rawbuf, uint length, + Parser_state *parser_state); + /** @defgroup Runtime_Environment Runtime Environment @{ */ /* Used in error handling only */ -#define SP_TYPE_STRING(LP) \ - ((LP)->sphead->m_type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE") #define SP_COM_STRING(LP) \ ((LP)->sql_command == SQLCOM_CREATE_SPFUNCTION || \ (LP)->sql_command == SQLCOM_ALTER_FUNCTION || \ @@ -118,10 +126,11 @@ "FUNCTION" : "PROCEDURE") static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables); -static void sql_kill(THD *thd, ulong id, killed_state state); +static void sql_kill(THD *thd, longlong id, killed_state state, killed_type type); static void sql_kill_user(THD *thd, LEX_USER *user, killed_state state); +static bool lock_tables_precheck(THD *thd, TABLE_LIST *tables); static bool execute_show_status(THD *, TABLE_LIST *); -static bool execute_rename_table(THD *, TABLE_LIST *, TABLE_LIST *); +static bool check_rename_table(THD *, TABLE_LIST *, TABLE_LIST *); const char *any_db="*any*"; // Special symbol for check_access @@ -169,6 +178,7 @@ const char *xa_state_names[]={ */ inline bool all_tables_not_ok(THD *thd, TABLE_LIST *tables) { + Rpl_filter *rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; return rpl_filter->is_on() && tables && !thd->spcont && !rpl_filter->tables_ok(thd->db, tables); } @@ -188,13 +198,15 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) /* - Implicitly commit a active transaction if statement requires so. + Check whether the statement implicitly commits an active transaction. @param thd Thread handle. @param mask Bitmask used for the SQL command match. + @return 0 No implicit commit + @return 1 Do a commit */ -static bool stmt_causes_implicit_commit(THD *thd, uint mask) +bool stmt_causes_implicit_commit(THD *thd, uint mask) { LEX *lex= thd->lex; bool skip= FALSE; @@ -205,12 +217,22 @@ static bool stmt_causes_implicit_commit(THD *thd, uint mask) switch (lex->sql_command) { case SQLCOM_DROP_TABLE: - skip= lex->drop_temporary; + skip= (lex->tmp_table() || + (thd->variables.option_bits & OPTION_GTID_BEGIN)); break; case SQLCOM_ALTER_TABLE: + /* If ALTER TABLE of non-temporary table, do implicit commit */ + skip= (lex->tmp_table()); + break; case SQLCOM_CREATE_TABLE: - /* If CREATE TABLE of non-temporary table, do implicit commit */ - skip= (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE); + /* + If CREATE TABLE of non-temporary table and the table is not part + if a BEGIN GTID ... COMMIT group, do a implicit commit. + This ensures that CREATE ... SELECT will in the same GTID group on the + master and slave. + */ + skip= (lex->tmp_table() || + (thd->variables.option_bits & OPTION_GTID_BEGIN)); break; case SQLCOM_SET_OPTION: skip= lex->autocommit ? FALSE : TRUE; @@ -244,11 +266,32 @@ void init_update_queries(void) /* Initialize the server command flags array. */ memset(server_command_flags, 0, sizeof(server_command_flags)); - server_command_flags[COM_STATISTICS]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS; - server_command_flags[COM_PING]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS; - server_command_flags[COM_STMT_PREPARE]= CF_SKIP_QUESTIONS; - server_command_flags[COM_STMT_CLOSE]= CF_SKIP_QUESTIONS; - server_command_flags[COM_STMT_RESET]= CF_SKIP_QUESTIONS; + server_command_flags[COM_STATISTICS]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS | CF_SKIP_WSREP_CHECK; + server_command_flags[COM_PING]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS | CF_SKIP_WSREP_CHECK; + + server_command_flags[COM_QUIT]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_PROCESS_INFO]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_PROCESS_KILL]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_SHUTDOWN]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_SLEEP]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_TIME]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_INIT_DB]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_END]= CF_SKIP_WSREP_CHECK; + + /* + COM_QUERY, COM_SET_OPTION and COM_STMT_XXX are allowed to pass the early + COM_xxx filter, they're checked later in mysql_execute_command(). + */ + server_command_flags[COM_QUERY]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_SET_OPTION]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_STMT_PREPARE]= CF_SKIP_QUESTIONS | CF_SKIP_WSREP_CHECK; + server_command_flags[COM_STMT_EXECUTE]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_STMT_FETCH]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_STMT_CLOSE]= CF_SKIP_QUESTIONS | CF_SKIP_WSREP_CHECK; + server_command_flags[COM_STMT_RESET]= CF_SKIP_QUESTIONS | CF_SKIP_WSREP_CHECK; + server_command_flags[COM_STMT_EXECUTE]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_STMT_SEND_LONG_DATA]= CF_SKIP_WSREP_CHECK; + server_command_flags[COM_REGISTER_SLAVE]= CF_SKIP_WSREP_CHECK; /* Initialize the sql command flags array. */ memset(sql_command_flags, 0, sizeof(sql_command_flags)); @@ -266,20 +309,25 @@ void init_update_queries(void) sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS | CF_CAN_GENERATE_ROW_EVENTS; - sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | + CF_REPORT_PROGRESS | CF_ADMIN_COMMAND; sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS ; + CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS | + CF_INSERTS_DATA | CF_ADMIN_COMMAND; sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS | CF_REPORT_PROGRESS; + CF_CAN_GENERATE_ROW_EVENTS | CF_REPORT_PROGRESS | + CF_INSERTS_DATA; sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS; + sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | + CF_ADMIN_COMMAND; + sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | + CF_REPORT_PROGRESS | CF_ADMIN_COMMAND; sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; @@ -290,26 +338,61 @@ void init_update_queries(void) sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_UPDATES_DATA; sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_UPDATES_DATA; sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_INSERTS_DATA; sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_INSERTS_DATA; sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED; sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED;; sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_INSERTS_DATA;; sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_INSERTS_DATA; sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; - sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE | CF_AUTO_COMMIT_TRANS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED; + // (1) so that subquery is traced when doing "SET @var = (subquery)" + /* + @todo SQLCOM_SET_OPTION should have CF_CAN_GENERATE_ROW_EVENTS + set, because it may invoke a stored function that generates row + events. /Sven + */ + sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE | + CF_AUTO_COMMIT_TRANS | + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE; // (1) + // (1) so that subquery is traced when doing "DO @var := (subquery)" sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE; // (1) sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_STATUS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; @@ -318,6 +401,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_EVENTS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_OPEN_TABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_PLUGINS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_GENERIC]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_FIELDS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_KEYS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_VARIABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; @@ -335,6 +419,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_ENGINE_STATUS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_MUTEX]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_LOGS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_EXPLAIN]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROCESSLIST]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND; @@ -350,11 +435,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_BINLOG_BASE64_EVENT]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CLIENT_STATS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_USER_STATS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_TABLE_STATS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_INDEX_STATS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_BINLOG_BASE64_EVENT]= CF_STATUS_COMMAND | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_SHOW_TABLES]= (CF_STATUS_COMMAND | CF_SHOW_TABLE_COMMAND | CF_REEXECUTION_FRAGILE); sql_command_flags[SQLCOM_SHOW_TABLE_STATUS]= (CF_STATUS_COMMAND | CF_SHOW_TABLE_COMMAND | CF_REEXECUTION_FRAGILE); @@ -362,18 +443,21 @@ void init_update_queries(void) sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_ROLE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_GRANT_ROLE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_REVOKE_ROLE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; /* The following is used to preserver CF_ROW_COUNT during the @@ -381,35 +465,163 @@ void init_update_queries(void) last called (or executed) statement is preserved. See mysql_execute_command() for how CF_ROW_COUNT is used. */ + /* + (1): without it, in "CALL some_proc((subq))", subquery would not be + traced. + */ sql_command_flags[SQLCOM_CALL]= CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE; // (1) sql_command_flags[SQLCOM_EXECUTE]= CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_COMPOUND]= CF_CAN_GENERATE_ROW_EVENTS; + + /* + We don't want to change to statement based replication for these commands + */ + sql_command_flags[SQLCOM_ROLLBACK]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* We don't want to replicate ALTER TABLE for temp tables in row format */ + sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* We don't want to replicate TRUNCATE for temp tables in row format */ + sql_command_flags[SQLCOM_TRUNCATE]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* We don't want to replicate DROP for temp tables in row format */ + sql_command_flags[SQLCOM_DROP_TABLE]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* We don't want to replicate CREATE/DROP INDEX for temp tables in row format */ + sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + sql_command_flags[SQLCOM_DROP_INDEX]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* One can change replication mode with SET */ + sql_command_flags[SQLCOM_SET_OPTION]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; /* The following admin table operations are allowed on log tables. */ - sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS; - sql_command_flags[SQLCOM_OPTIMIZE]|= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS; - sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS; - sql_command_flags[SQLCOM_CHECK]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS; + sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | + CF_REPORT_PROGRESS | CF_ADMIN_COMMAND; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | + CF_REPORT_PROGRESS | CF_ADMIN_COMMAND; + sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | + CF_REPORT_PROGRESS | CF_ADMIN_COMMAND; + sql_command_flags[SQLCOM_CHECK]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | + CF_REPORT_PROGRESS | CF_ADMIN_COMMAND; sql_command_flags[SQLCOM_CHECKSUM]= CF_REPORT_PROGRESS; sql_command_flags[SQLCOM_CREATE_USER]|= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_USER]|= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_RENAME_USER]|= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_ROLE]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_ROLE]|= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_REVOKE]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE_ROLE]|= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_GRANT]|= CF_AUTO_COMMIT_TRANS; - - sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_PRELOAD_KEYS]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_GRANT_ROLE]|= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_FLUSH]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_RESET]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_CREATE_SERVER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_SERVER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_SERVER]= CF_AUTO_COMMIT_TRANS; + + /* + The following statements can deal with temporary tables, + so temporary tables should be pre-opened for those statements to + simplify privilege checking. + + There are other statements that deal with temporary tables and open + them, but which are not listed here. The thing is that the order of + pre-opening temporary tables for those statements is somewhat custom. + + Note that SQLCOM_RENAME_TABLE should not be in this list! + */ + sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DROP_TABLE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_TRUNCATE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_LOAD]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DROP_INDEX]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_UPDATE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_UPDATE_MULTI]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_INSERT_SELECT]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DELETE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DELETE_MULTI]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_REPLACE_SELECT]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_SELECT]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_SET_OPTION]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DO]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_HA_OPEN]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_CALL]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_CHECKSUM]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_ANALYZE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_CHECK]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_REPAIR]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_PRELOAD_KEYS]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]|= CF_PREOPEN_TMP_TABLES; + + /* + DDL statements that should start with closing opened handlers. + + We use this flag only for statements for which open HANDLERs + have to be closed before temporary tables are pre-opened. + */ + sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_DROP_TABLE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_TRUNCATE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_REPAIR]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_ANALYZE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_CHECK]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_DROP_INDEX]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_PRELOAD_KEYS]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]|= CF_HA_CLOSE; + + /* + Mark statements that always are disallowed in read-only + transactions. Note that according to the SQL standard, + even temporary table DDL should be disallowed. + */ + sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_TABLE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_RENAME_TABLE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_INDEX]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_DB]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_DB]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_DB]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_VIEW]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_VIEW]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_TRIGGER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_TRIGGER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_EVENT]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_EVENT]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_EVENT]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_USER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_RENAME_USER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_USER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_SERVER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_SERVER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_SERVER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]|=CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_TRUNCATE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_TABLESPACE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_REPAIR]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_GRANT]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_REVOKE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_REVOKE_ALL]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_INSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS; } bool sqlcom_can_generate_row_events(const THD *thd) @@ -462,7 +674,7 @@ void execute_init_command(THD *thd, LEX_STRING *init_command, thd->profiling.set_query_source(buf, len); #endif - thd_proc_info(thd, "Execution of init_command"); + THD_STAGE_INFO(thd, stage_execution_of_init_command); save_client_capabilities= thd->client_capabilities; thd->client_capabilities|= CLIENT_MULTI_QUERIES; /* @@ -471,6 +683,7 @@ void execute_init_command(THD *thd, LEX_STRING *init_command, */ save_vio= thd->net.vio; thd->net.vio= 0; + thd->clear_error(1); dispatch_command(COM_QUERY, thd, buf, len); thd->client_capabilities= save_client_capabilities; thd->net.vio= save_vio; @@ -481,21 +694,29 @@ void execute_init_command(THD *thd, LEX_STRING *init_command, } +static char *fgets_fn(char *buffer, size_t size, fgets_input_t input, int *error) +{ + MYSQL_FILE *in= static_cast<MYSQL_FILE*> (input); + char *line= mysql_file_fgets(buffer, size, in); + if (error) + *error= (line == NULL) ? ferror(in->m_file) : 0; + return line; +} + + static void handle_bootstrap_impl(THD *thd) { MYSQL_FILE *file= bootstrap_file; - char *buff, *res; - - DBUG_ENTER("handle_bootstrap"); + DBUG_ENTER("handle_bootstrap_impl"); #ifndef EMBEDDED_LIBRARY pthread_detach_this_thread(); thd->thread_stack= (char*) &thd; #endif /* EMBEDDED_LIBRARY */ - thd_proc_info(thd, 0); thd->security_ctx->user= (char*) my_strdup("boot", MYF(MY_WME)); - thd->security_ctx->priv_user[0]= thd->security_ctx->priv_host[0]=0; + thd->security_ctx->priv_user[0]= thd->security_ctx->priv_host[0]= + thd->security_ctx->priv_role[0]= 0; /* Make the "client" handle multiple results. This is necessary to enable stored procedures with SELECTs and Dynamic SQL @@ -503,50 +724,58 @@ static void handle_bootstrap_impl(THD *thd) */ thd->client_capabilities|= CLIENT_MULTI_RESULTS; - buff= (char*) thd->net.buff; thd->init_for_queries(); - while (mysql_file_fgets(buff, thd->net.max_packet, file)) + + for ( ; ; ) { + char buffer[MAX_BOOTSTRAP_QUERY_SIZE] = ""; + int rc, length; char *query; - /* strlen() can't be deleted because mysql_file_fgets() doesn't return length */ - ulong length= (ulong) strlen(buff); - while (buff[length-1] != '\n' && !mysql_file_feof(file)) + int error= 0; + + rc= read_bootstrap_query(buffer, &length, file, fgets_fn, &error); + + if (rc == READ_BOOTSTRAP_EOF) + break; + /* + Check for bootstrap file errors. SQL syntax errors will be + caught below. + */ + if (rc != READ_BOOTSTRAP_SUCCESS) { /* - We got only a part of the current string. Will try to increase - net buffer then read the rest of the current string. + mysql_parse() may have set a successful error status for the previous + query. We must clear the error status to report the bootstrap error. */ - /* purecov: begin tested */ - if (net_realloc(&(thd->net), 2 * thd->net.max_packet)) + thd->get_stmt_da()->reset_diagnostics_area(); + + /* Get the nearest query text for reference. */ + char *err_ptr= buffer + (length <= MAX_BOOTSTRAP_ERROR_LEN ? + 0 : (length - MAX_BOOTSTRAP_ERROR_LEN)); + switch (rc) { - thd->protocol->end_statement(); - bootstrap_error= 1; + case READ_BOOTSTRAP_ERROR: + my_printf_error(ER_UNKNOWN_ERROR, "Bootstrap file error, return code (%d). " + "Nearest query: '%s'", MYF(0), error, err_ptr); break; - } - buff= (char*) thd->net.buff; - res= mysql_file_fgets(buff + length, thd->net.max_packet - length, file); - if (!res && !mysql_file_feof(file)) - { - thd->protocol->end_statement(); - bootstrap_error= 1; + + case READ_BOOTSTRAP_QUERY_SIZE: + my_printf_error(ER_UNKNOWN_ERROR, "Boostrap file error. Query size " + "exceeded %d bytes near '%s'.", MYF(0), + MAX_BOOTSTRAP_LINE_SIZE, err_ptr); break; - } - length+= (ulong) strlen(buff + length); - /* purecov: end */ - } - if (bootstrap_error) - break; /* purecov: inspected */ - while (length && (my_isspace(thd->charset(), buff[length-1]) || - buff[length-1] == ';')) - length--; - buff[length]=0; + default: + DBUG_ASSERT(false); + break; + } - /* Skip lines starting with delimiter */ - if (strncmp(buff, STRING_WITH_LEN("delimiter")) == 0) - continue; + thd->protocol->end_statement(); + bootstrap_error= 1; + break; + } - query= (char *) thd->memdup_w_gap(buff, length + 1, + query= (char *) thd->memdup_w_gap(buffer, length + 1, thd->db_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE + QUERY_CACHE_FLAGS_SIZE); @@ -581,12 +810,15 @@ static void handle_bootstrap_impl(THD *thd) #if defined(ENABLED_PROFILING) thd->profiling.finish_current_query(); #endif + delete_explain_query(thd->lex); if (bootstrap_error) break; + thd->reset_kill_query(); /* Ensure that killed_errmsg is released */ free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC)); free_root(&thd->transaction.mem_root,MYF(MY_KEEP_PREALLOC)); + thd->lex->restore_set_statement_var(); } DBUG_VOID_RETURN; @@ -625,14 +857,13 @@ void do_handle_bootstrap(THD *thd) handle_bootstrap_impl(thd); end: - net_end(&thd->net); - thd->cleanup(); delete thd; #ifndef EMBEDDED_LIBRARY - mysql_mutex_lock(&LOCK_thread_count); - thread_count--; + thread_safe_decrement32(&thread_count); in_bootstrap= FALSE; + + mysql_mutex_lock(&LOCK_thread_count); mysql_cond_broadcast(&COND_thread_count); mysql_mutex_unlock(&LOCK_thread_count); my_thread_end(); @@ -669,6 +900,26 @@ void cleanup_items(Item *item) DBUG_VOID_RETURN; } + +#ifdef WITH_WSREP +static bool wsrep_tables_accessible_when_detached(const TABLE_LIST *tables) +{ + for (const TABLE_LIST *table= tables; table; table= table->next_global) + { + TABLE_CATEGORY c; + LEX_STRING db, tn; + lex_string_set(&db, table->db); + lex_string_set(&tn, table->table_name); + c= get_table_category(&db, &tn); + if (c != TABLE_CATEGORY_INFORMATION && + c != TABLE_CATEGORY_PERFORMANCE) + { + return false; + } + } + return true; +} +#endif /* WITH_WSREP */ #ifndef EMBEDDED_LIBRARY /** @@ -687,11 +938,28 @@ bool do_command(THD *thd) { bool return_value; char *packet= 0; +#ifdef WITH_WSREP + ulong packet_length= 0; // just to avoid (false positive) compiler warning +#else ulong packet_length; +#endif /* WITH_WSREP */ NET *net= &thd->net; enum enum_server_command command; DBUG_ENTER("do_command"); +#ifdef WITH_WSREP + if (WSREP(thd)) + { + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->wsrep_query_state= QUERY_IDLE; + if (thd->wsrep_conflict_state==MUST_ABORT) + { + wsrep_client_rollback(thd); + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + } +#endif /* WITH_WSREP */ + /* indicator of uninitialized lex => normal flow of errors handling (see my_message_sql) @@ -707,13 +975,8 @@ bool do_command(THD *thd) if(!thd->skip_wait_timeout) my_net_set_read_timeout(net, thd->variables.net_wait_timeout); - - /* - XXX: this code is here only to clear possible errors of init_connect. - Consider moving to init_connect() instead. - */ - thd->clear_error(); // Clear error message - thd->stmt_da->reset_diagnostics_area(); + /* Errors and diagnostics are cleared once here before query */ + thd->clear_error(1); net_new_transaction(net); @@ -736,18 +999,67 @@ bool do_command(THD *thd) */ DEBUG_SYNC(thd, "before_do_command_net_read"); - if ((packet_length= my_net_read(net)) == packet_error) + packet_length= my_net_read_packet(net, 1); +#ifdef WITH_WSREP + if (WSREP(thd)) { + mysql_mutex_lock(&thd->LOCK_thd_data); + + /* these THD's are aborted or are aborting during being idle */ + if (thd->wsrep_conflict_state == ABORTING) + { + while (thd->wsrep_conflict_state == ABORTING) { + mysql_mutex_unlock(&thd->LOCK_thd_data); + my_sleep(1000); + mysql_mutex_lock(&thd->LOCK_thd_data); + } + thd->store_globals(); + } + else if (thd->wsrep_conflict_state == ABORTED) + { + thd->store_globals(); + } + + thd->wsrep_query_state= QUERY_EXEC; + mysql_mutex_unlock(&thd->LOCK_thd_data); + } +#endif /* WITH_WSREP */ + + if (packet_length == packet_error) { DBUG_PRINT("info",("Got error %d reading command from socket %s", net->error, vio_description(net->vio))); +#ifdef WITH_WSREP + if (WSREP(thd)) + { + mysql_mutex_lock(&thd->LOCK_thd_data); + if (thd->wsrep_conflict_state == MUST_ABORT) + { + DBUG_PRINT("wsrep",("aborted for wsrep rollback: %lu", thd->real_id)); + wsrep_client_rollback(thd); + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + } +#endif /* WITH_WSREP */ + + /* Instrument this broken statement as "statement/com/error" */ + thd->m_statement_psi= MYSQL_REFINE_STATEMENT(thd->m_statement_psi, + com_statement_info[COM_END]. + m_key); + + /* Check if we can continue without closing the connection */ /* The error must be set. */ DBUG_ASSERT(thd->is_error()); thd->protocol->end_statement(); + /* Mark the statement completed. */ + MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da()); + thd->m_statement_psi= NULL; + thd->m_digest= NULL; + if (net->error != 3) { return_value= TRUE; // We have to close it. @@ -786,13 +1098,75 @@ bool do_command(THD *thd) vio_description(net->vio), command, command_name[command].str)); +#ifdef WITH_WSREP + if (WSREP(thd)) + { + /* + Bail out if DB snapshot has not been installed. + */ + if (!thd->wsrep_applier && + (!wsrep_ready || wsrep_reject_queries != WSREP_REJECT_NONE) && + (server_command_flags[command] & CF_SKIP_WSREP_CHECK) == 0) + { + my_message(ER_UNKNOWN_COM_ERROR, + "WSREP has not yet prepared node for application use", MYF(0)); + thd->protocol->end_statement(); + + /* Performance Schema Interface instrumentation end. */ + MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da()); + thd->m_statement_psi= NULL; + thd->m_digest= NULL; + + return_value= FALSE; + goto out; + } + } +#endif + /* Restore read timeout value */ my_net_set_read_timeout(net, thd->variables.net_read_timeout); DBUG_ASSERT(packet_length); + DBUG_ASSERT(!thd->apc_target.is_enabled()); return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1)); +#ifdef WITH_WSREP + if (WSREP(thd)) + { + while (thd->wsrep_conflict_state== RETRY_AUTOCOMMIT) + { + WSREP_DEBUG("Retry autocommit for: %s\n", thd->wsrep_retry_query); + CHARSET_INFO *current_charset = thd->variables.character_set_client; + if (!is_supported_parser_charset(current_charset)) + { + /* Do not use non-supported parser character sets */ + WSREP_WARN("Current client character set is non-supported parser " + "character set: %s", current_charset->csname); + thd->variables.character_set_client = &my_charset_latin1; + WSREP_WARN("For retry temporally setting character set to : %s", + my_charset_latin1.csname); + } + thd->clear_error(); + return_value= dispatch_command(command, thd, thd->wsrep_retry_query, + thd->wsrep_retry_query_len); + thd->variables.character_set_client = current_charset; + } + + if (thd->wsrep_retry_query && thd->wsrep_conflict_state != REPLAYING) + { + my_free(thd->wsrep_retry_query); + thd->wsrep_retry_query = NULL; + thd->wsrep_retry_query_len = 0; + thd->wsrep_retry_command = COM_CONNECT; + } + } +#endif /* WITH_WSREP */ + DBUG_ASSERT(!thd->apc_target.is_enabled()); out: + thd->lex->restore_set_statement_var(); + /* The statement instrumentation must be closed in all cases. */ + DBUG_ASSERT(thd->m_digest == NULL); + DBUG_ASSERT(thd->m_statement_psi == NULL); DBUG_RETURN(return_value); } #endif /* EMBEDDED_LIBRARY */ @@ -840,19 +1214,20 @@ static bool deny_updates_if_read_only_option(THD *thd, TABLE_LIST *all_tables) so CREATE TABLE needs a special treatment */ if (lex->sql_command == SQLCOM_CREATE_TABLE) - DBUG_RETURN(!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)); + DBUG_RETURN(!lex->tmp_table()); /* a table-to-be-dropped might not exist (DROP TEMPORARY TABLE IF EXISTS), cannot use the temp table list either. */ - if (lex->sql_command == SQLCOM_DROP_TABLE && lex->drop_temporary) + if (lex->sql_command == SQLCOM_DROP_TABLE && lex->tmp_table()) DBUG_RETURN(FALSE); /* Now, check thd->temporary_tables list */ DBUG_RETURN(some_non_temp_table_to_be_updated(thd, all_tables)); } + /** Perform one connection-level (COM_XXXX) command. @@ -879,9 +1254,46 @@ bool dispatch_command(enum enum_server_command command, THD *thd, { NET *net= &thd->net; bool error= 0; + bool do_end_of_statement= true; DBUG_ENTER("dispatch_command"); DBUG_PRINT("info", ("command: %d", command)); + inc_thread_running(); + +#ifdef WITH_WSREP + if (WSREP(thd)) + { + if (!thd->in_multi_stmt_transaction_mode()) + { + thd->wsrep_PA_safe= true; + } + + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->wsrep_query_state= QUERY_EXEC; + if (thd->wsrep_conflict_state== RETRY_AUTOCOMMIT) + { + thd->wsrep_conflict_state= NO_CONFLICT; + } + if (thd->wsrep_conflict_state== MUST_ABORT) + { + wsrep_client_rollback(thd); + } + /* We let COM_QUIT and COM_STMT_CLOSE to execute even if wsrep aborted. */ + if (thd->wsrep_conflict_state == ABORTED && + command != COM_STMT_CLOSE && command != COM_QUIT) + { + mysql_mutex_unlock(&thd->LOCK_thd_data); + my_error(ER_LOCK_DEADLOCK, MYF(0), "wsrep aborted transaction"); + WSREP_DEBUG("Deadlock error for: %s", thd->query()); + thd->reset_killed(); + thd->mysys_var->abort = 0; + thd->wsrep_conflict_state = NO_CONFLICT; + thd->wsrep_retry_counter = 0; + goto dispatch_end; + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + } +#endif /* WITH_WSREP */ #if defined(ENABLED_PROFILING) thd->profiling.start_new_query(); #endif @@ -893,23 +1305,30 @@ bool dispatch_command(enum enum_server_command command, THD *thd, { DBUG_PRINT("crash_dispatch_command_before", ("now")); DBUG_ABORT(); }); - thd->command=command; - /* - Commands which always take a long time are logged into - the slow log only if opt_log_slow_admin_statements is set. - */ - thd->enable_slow_log= TRUE; + /* Performance Schema Interface instrumentation, begin */ + thd->m_statement_psi= MYSQL_REFINE_STATEMENT(thd->m_statement_psi, + com_statement_info[command]. + m_key); + thd->set_command(command); + + thd->enable_slow_log= true; thd->query_plan_flags= QPLAN_INIT; thd->lex->sql_command= SQLCOM_END; /* to avoid confusing VIEW detectors */ + thd->reset_kill_query(); DEBUG_SYNC(thd,"dispatch_command_before_set_time"); thd->set_time(); - if (server_command_flags[command] & CF_SKIP_QUERY_ID) - thd->set_query_id(get_query_id()); - else + if (!(server_command_flags[command] & CF_SKIP_QUERY_ID)) thd->set_query_id(next_query_id()); - inc_thread_running(); + else + { + /* + ping, get statistics or similar stateless command. + No reason to increase query id here. + */ + thd->set_query_id(get_query_id()); + } if (!(server_command_flags[command] & CF_SKIP_QUESTIONS)) statistic_increment(thd->status_var.questions, &LOCK_status); @@ -932,8 +1351,9 @@ bool dispatch_command(enum enum_server_command command, THD *thd, { LEX_STRING tmp; status_var_increment(thd->status_var.com_stat[SQLCOM_CHANGE_DB]); - thd->convert_string(&tmp, system_charset_info, - packet, packet_length, thd->charset()); + if (thd->copy_with_error(system_charset_info, &tmp, + thd->charset(), packet, packet_length)) + break; if (!mysql_change_db(thd, &tmp, FALSE)) { general_log_write(thd, command, thd->db, thd->db_length); @@ -944,6 +1364,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #ifdef HAVE_REPLICATION case COM_REGISTER_SLAVE: { + status_var_increment(thd->status_var.com_register_slave); if (!register_slave(thd, (uchar*)packet, packet_length)) my_ok(thd); break; @@ -951,7 +1372,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #endif case COM_CHANGE_USER: { - bool rc; + int auth_rc; status_var_increment(thd->status_var.com_other); thd->change_user(); @@ -981,14 +1402,15 @@ bool dispatch_command(enum enum_server_command command, THD *thd, */ if (thd->failed_com_change_user >= 3) { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - rc= 1; + my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd,ER_UNKNOWN_COM_ERROR), + MYF(0)); + auth_rc= 1; } else - rc= acl_authenticate(thd, 0, packet_length); + auth_rc= acl_authenticate(thd, packet_length); - MYSQL_AUDIT_NOTIFY_CONNECTION_CHANGE_USER(thd); - if (rc) + mysql_audit_notify_connection_change_user(thd); + if (auth_rc) { /* Free user if allocated by acl_authenticate */ my_free(thd->security_ctx->user); @@ -1048,6 +1470,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } case COM_QUERY: { + DBUG_ASSERT(thd->m_digest == NULL); + thd->m_digest= & thd->m_digest_state; + thd->m_digest->reset(thd->m_token_array, max_digest_length); + if (alloc_query(thd, packet, packet_length)) break; // fatal error is set MYSQL_QUERY_START(thd->query(), thd->thread_id, @@ -1060,11 +1486,17 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #if defined(ENABLED_PROFILING) thd->profiling.set_query_source(thd->query(), thd->query_length()); #endif + MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, thd->query(), + thd->query_length()); + Parser_state parser_state; if (parser_state.init(thd, thd->query(), thd->query_length())) break; - mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); + if (WSREP_ON) + wsrep_mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); + else + mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); while (!thd->killed && (parser_state.m_lip.found_semicolon != NULL) && ! thd->is_error()) @@ -1084,12 +1516,15 @@ bool dispatch_command(enum enum_server_command command, THD *thd, query_cache_end_of_result(thd); mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS, - thd->stmt_da->is_error() ? thd->stmt_da->sql_errno() - : 0, command_name[command].str); + thd->get_stmt_da()->is_error() + ? thd->get_stmt_da()->sql_errno() + : 0, + command_name[command].str); ulong length= (ulong)(packet_end - beginning_of_next_stmt); log_slow_statement(thd); + DBUG_ASSERT(!thd->apc_target.is_enabled()); /* Remove garbage at start of query */ while (length > 0 && my_isspace(thd->charset(), *beginning_of_next_stmt)) @@ -1098,6 +1533,12 @@ bool dispatch_command(enum enum_server_command command, THD *thd, length--; } + /* PSI end */ + MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da()); + thd->m_statement_psi= NULL; + thd->m_digest= NULL; + + /* DTRACE end */ if (MYSQL_QUERY_DONE_ENABLED()) { MYSQL_QUERY_DONE(thd->is_error()); @@ -1109,21 +1550,41 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->profiling.set_query_source(beginning_of_next_stmt, length); #endif + /* DTRACE begin */ MYSQL_QUERY_START(beginning_of_next_stmt, thd->thread_id, (char *) (thd->db ? thd->db : ""), &thd->security_ctx->priv_user[0], (char *) thd->security_ctx->host_or_ip); + /* PSI begin */ + thd->m_digest= & thd->m_digest_state; + + thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state, + com_statement_info[command].m_key, + thd->db, thd->db_length, + thd->charset()); + THD_STAGE_INFO(thd, stage_init); + MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, beginning_of_next_stmt, + length); + thd->set_query_and_id(beginning_of_next_stmt, length, thd->charset(), next_query_id()); /* Count each statement from the client. */ statistic_increment(thd->status_var.questions, &LOCK_status); - thd->set_time(); /* Reset the query start time. */ + + if(!WSREP(thd)) + thd->set_time(); /* Reset the query start time. */ + parser_state.reset(beginning_of_next_stmt, length); /* TODO: set thd->lex->sql_command to SQLCOM_END here */ - mysql_parse(thd, beginning_of_next_stmt, length, &parser_state); + + if (WSREP_ON) + wsrep_mysql_parse(thd, beginning_of_next_stmt, length, &parser_state); + else + mysql_parse(thd, beginning_of_next_stmt, length, &parser_state); + } DBUG_PRINT("info",("query ready")); @@ -1131,7 +1592,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } case COM_FIELD_LIST: // This isn't actually needed #ifdef DONT_ALLOW_SHOW_COMMANDS - my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND), + my_message(ER_NOT_ALLOWED_COMMAND, ER_THD(thd, ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */ break; #else @@ -1160,7 +1621,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, /* Check given table name length. */ if (packet_length - arg_length > NAME_LEN + 1 || arg_length > SAFE_NAME_LEN) { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), + MYF(0)); break; } thd->convert_string(&table_name, system_charset_info, @@ -1172,11 +1634,14 @@ bool dispatch_command(enum enum_server_command command, THD *thd, break; } packet= arg_end + 1; - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(0); // Don't clear errors lex_start(thd); /* Must be before we init the table list. */ if (lower_case_table_names) + { table_name.length= my_casedn_str(files_charset_info, table_name.str); + db.length= my_casedn_str(files_charset_info, db.str); + } table_list.init_one_table(db.str, db.length, table_name.str, table_name.length, table_name.str, TL_READ); /* @@ -1202,6 +1667,9 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->set_query(fields, query_length); general_log_print(thd, command, "%s %s", table_list.table_name, fields); + if (open_temporary_tables(thd, &table_list)) + break; + if (check_table_access(thd, SELECT_ACL, &table_list, TRUE, UINT_MAX, FALSE)) break; @@ -1235,10 +1703,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } #endif case COM_QUIT: - /* We don't calculate statistics for this command */ + /* Note: We don't calculate statistics for this command */ + + /* Ensure that quit works even if max_mem_used is set */ + thd->variables.max_mem_used= LONGLONG_MAX; general_log_print(thd, command, NullS); net->error=0; // Don't give 'abort' message - thd->stmt_da->disable_status(); // Don't send anything back + thd->get_stmt_da()->disable_status(); // Don't send anything back error=TRUE; // End server break; #ifndef EMBEDDED_LIBRARY @@ -1250,7 +1721,6 @@ bool dispatch_command(enum enum_server_command command, THD *thd, status_var_increment(thd->status_var.com_other); - thd->enable_slow_log= opt_log_slow_admin_statements; thd->query_plan_flags|= QPLAN_ADMIN; if (check_global_access(thd, REPL_SLAVE_ACL)) break; @@ -1258,10 +1728,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, /* TODO: The following has to be changed to an 8 byte integer */ pos = uint4korr(packet); flags = uint2korr(packet + 4); - thd->server_id=0; /* avoid suicide */ + thd->variables.server_id=0; /* avoid suicide */ if ((slave_server_id= uint4korr(packet+6))) // mysqlbinlog.server_id==0 kill_zombie_dump_threads(slave_server_id); - thd->server_id = slave_server_id; + thd->variables.server_id = slave_server_id; const char *name= packet + 10; size_t nlen= strlen(name); @@ -1287,7 +1757,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, lex_start(thd); status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]); - ulong options= (ulong) (uchar) packet[0]; + ulonglong options= (ulonglong) (uchar) packet[0]; if (trans_commit_implicit(thd)) break; thd->mdl_context.release_transactional_locks(); @@ -1306,17 +1776,20 @@ bool dispatch_command(enum enum_server_command command, THD *thd, and flushes tables. */ bool res; - my_pthread_setspecific_ptr(THR_THD, NULL); + set_current_thd(0); res= reload_acl_and_cache(NULL, options | REFRESH_FAST, NULL, ¬_used); - my_pthread_setspecific_ptr(THR_THD, thd); + set_current_thd(thd); if (res) break; } else #endif - if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) - break; + { + thd->lex->relay_log_connection_name= empty_lex_str; + if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) + break; + } if (trans_commit_implicit(thd)) break; close_thread_tables(thd); @@ -1371,7 +1844,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, if (!(uptime= (ulong) (thd->start_time - server_start_time))) queries_per_second1000= 0; else - queries_per_second1000= thd->query_id * LL(1000) / uptime; + queries_per_second1000= thd->query_id * 1000 / uptime; length= my_snprintf(buff, buff_len - 1, "Uptime: %lu Threads: %d Questions: %lu " @@ -1381,8 +1854,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, (int) thread_count, (ulong) thd->query_id, current_global_status_var->long_query_count, current_global_status_var->opened_tables, - refresh_version, - cached_open_tables(), + tdc_refresh_version(), + tc_records(), (uint) (queries_per_second1000 / 1000), (uint) (queries_per_second1000 % 1000)); #ifdef EMBEDDED_LIBRARY @@ -1391,7 +1864,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #else (void) my_net_write(net, (uchar*) buff, length); (void) net_flush(net); - thd->stmt_da->disable_status(); + thd->get_stmt_da()->disable_status(); #endif break; } @@ -1413,7 +1886,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, { status_var_increment(thd->status_var.com_stat[SQLCOM_KILL]); ulong id=(ulong) uint4korr(packet); - sql_kill(thd,id, KILL_CONNECTION_HARD); + sql_kill(thd, id, KILL_CONNECTION_HARD, KILL_TYPE_ID); break; } case COM_SET_OPTION: @@ -1431,7 +1904,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, my_eof(thd); break; default: - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), + MYF(0)); break; } break; @@ -1450,37 +1924,78 @@ bool dispatch_command(enum enum_server_command command, THD *thd, case COM_DELAYED_INSERT: case COM_END: default: - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), + MYF(0)); break; } - DBUG_ASSERT(thd->derived_tables == NULL && - (thd->open_tables == NULL || + +#ifdef WITH_WSREP + dispatch_end: + + if (WSREP(thd)) + { + /* + MDEV-10812 + In the case of COM_QUIT/COM_STMT_CLOSE thread status should be disabled. + */ + DBUG_ASSERT((command != COM_QUIT && command != COM_STMT_CLOSE) + || thd->get_stmt_da()->is_disabled()); + /* wsrep BF abort in query exec phase */ + mysql_mutex_lock(&thd->LOCK_thd_data); + do_end_of_statement= thd->wsrep_conflict_state != REPLAYING && + thd->wsrep_conflict_state != RETRY_AUTOCOMMIT && + !thd->killed; + mysql_mutex_unlock(&thd->LOCK_thd_data); + } + else + do_end_of_statement= true; + +#endif /* WITH_WSREP */ + + if (do_end_of_statement) + { + DBUG_ASSERT(thd->derived_tables == NULL && + (thd->open_tables == NULL || (thd->locked_tables_mode == LTM_LOCK_TABLES))); - thd_proc_info(thd, "updating status"); - /* Finalize server status flags after executing a command. */ - thd->update_server_status(); - thd->protocol->end_statement(); - query_cache_end_of_result(thd); + thd_proc_info(thd, "updating status"); + /* Finalize server status flags after executing a command. */ + thd->update_server_status(); + thd->protocol->end_statement(); + query_cache_end_of_result(thd); + } if (!thd->is_error() && !thd->killed_errno()) mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_RESULT, 0, 0); mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS, - thd->stmt_da->is_error() ? thd->stmt_da->sql_errno() : 0, + thd->get_stmt_da()->is_error() ? + thd->get_stmt_da()->sql_errno() : 0, command_name[command].str); thd->update_all_stats(); log_slow_statement(thd); - thd_proc_info(thd, "cleaning up"); + THD_STAGE_INFO(thd, stage_cleaning_up); thd->reset_query(); - thd->command=COM_SLEEP; - thd->set_time(); + + /* Performance Schema Interface instrumentation, end */ + MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da()); + thd->set_examined_row_count(0); // For processlist + thd->set_command(COM_SLEEP); + + thd->m_statement_psi= NULL; + thd->m_digest= NULL; + dec_thread_running(); - thd_proc_info(thd, 0); thd->packet.shrink(thd->variables.net_buffer_length); // Reclaim some memory + thd->reset_kill_query(); /* Ensure that killed_errmsg is released */ + /* + LEX::m_sql_cmd can point to Sql_cmd allocated on thd->mem_root. + Unlink it now, before freeing the root. + */ + thd->lex->m_sql_cmd= NULL; free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC)); #if defined(ENABLED_PROFILING) @@ -1500,10 +2015,15 @@ bool dispatch_command(enum enum_server_command command, THD *thd, /* Check that some variables are reset properly */ DBUG_ASSERT(thd->abort_on_warning == 0); + thd->lex->restore_set_statement_var(); DBUG_RETURN(error); } +/* + @note + This function must call delete_explain_query(). +*/ void log_slow_statement(THD *thd) { DBUG_ENTER("log_slow_statement"); @@ -1514,35 +2034,69 @@ void log_slow_statement(THD *thd) statement in a trigger or stored function */ if (unlikely(thd->in_sub_stmt)) - DBUG_VOID_RETURN; // Don't set time for sub stmt + goto end; // Don't set time for sub stmt + + /* + Skip both long_query_count increment and logging if the current + statement forces slow log suppression (e.g. an SP statement). + + Note, we don't check for global_system_variables.sql_log_slow here. + According to the manual, the "Slow_queries" status variable does not require + sql_log_slow to be ON. So even if sql_log_slow is OFF, we still need to + continue and increment long_query_count (and skip only logging, see below): + */ + if (!thd->enable_slow_log) + goto end; // E.g. SP statement + + DBUG_EXECUTE_IF("simulate_slow_query", { + if (thd->get_command() == COM_QUERY || + thd->get_command() == COM_STMT_EXECUTE) + thd->server_status|= SERVER_QUERY_WAS_SLOW; + }); - /* Follow the slow log filter configuration. */ - if (!thd->enable_slow_log || - (thd->variables.log_slow_filter - && !(thd->variables.log_slow_filter & thd->query_plan_flags))) - DBUG_VOID_RETURN; - if (((thd->server_status & SERVER_QUERY_WAS_SLOW) || ((thd->server_status & (SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED)) && opt_log_queries_not_using_indexes && - !(sql_command_flags[thd->lex->sql_command] & CF_STATUS_COMMAND))) && - thd->examined_row_count >= thd->variables.min_examined_row_limit) + !(thd->query_plan_flags & QPLAN_STATUS))) && + thd->get_examined_row_count() >= thd->variables.min_examined_row_limit) { thd->status_var.long_query_count++; + + /* + until opt_log_slow_admin_statements is removed, it + duplicates slow_log_filter=admin + */ + if ((thd->query_plan_flags & QPLAN_ADMIN) && + !opt_log_slow_admin_statements) + goto end; + + if (!global_system_variables.sql_log_slow || !thd->variables.sql_log_slow) + goto end; + /* If rate limiting of slow log writes is enabled, decide whether to log this query to the log or not. */ if (thd->variables.log_slow_rate_limit > 1 && (global_query_id % thd->variables.log_slow_rate_limit) != 0) - DBUG_VOID_RETURN; + goto end; + + /* + Follow the slow log filter configuration: + skip logging if the current statement matches the filter. + */ + if (thd->variables.log_slow_filter && + !(thd->variables.log_slow_filter & thd->query_plan_flags)) + goto end; - thd_proc_info(thd, "logging slow query"); + THD_STAGE_INFO(thd, stage_logging_slow_query); slow_log_print(thd, thd->query(), thd->query_length(), thd->utime_after_query); - thd_proc_info(thd, 0); } + +end: + delete_explain_query(thd->lex); DBUG_VOID_RETURN; } @@ -1583,7 +2137,7 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, case SCH_SCHEMATA: #if defined(DONT_ALLOW_SHOW_COMMANDS) my_message(ER_NOT_ALLOWED_COMMAND, - ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */ + ER_THD(thd, ER_NOT_ALLOWED_COMMAND), MYF(0)); DBUG_RETURN(1); #else break; @@ -1596,7 +2150,7 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, case SCH_EVENTS: #ifdef DONT_ALLOW_SHOW_COMMANDS my_message(ER_NOT_ALLOWED_COMMAND, - ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */ + ER_THD(thd, ER_NOT_ALLOWED_COMMAND), MYF(0)); DBUG_RETURN(1); #else { @@ -1624,7 +2178,7 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, case SCH_STATISTICS: #ifdef DONT_ALLOW_SHOW_COMMANDS my_message(ER_NOT_ALLOWED_COMMAND, - ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */ + ER_THD(thd, ER_NOT_ALLOWED_COMMAND), MYF(0)); DBUG_RETURN(1); #else { @@ -1639,8 +2193,8 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, DBUG_RETURN(1); lex->query_tables_last= query_tables_last; break; - } #endif + } case SCH_PROFILES: /* Mark this current profiling record to be discarded. We don't @@ -1650,38 +2204,15 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, thd->profiling.discard_current_query(); #endif break; - case SCH_USER_STATS: - case SCH_CLIENT_STATS: - if (check_global_access(thd, SUPER_ACL | PROCESS_ACL, true)) - DBUG_RETURN(1); - case SCH_TABLE_STATS: - case SCH_INDEX_STATS: - case SCH_OPEN_TABLES: - case SCH_VARIABLES: - case SCH_STATUS: - case SCH_PROCEDURES: - case SCH_CHARSETS: - case SCH_ENGINES: - case SCH_COLLATIONS: - case SCH_COLLATION_CHARACTER_SET_APPLICABILITY: - case SCH_USER_PRIVILEGES: - case SCH_SCHEMA_PRIVILEGES: - case SCH_TABLE_PRIVILEGES: - case SCH_COLUMN_PRIVILEGES: - case SCH_TABLE_CONSTRAINTS: - case SCH_KEY_COLUMN_USAGE: default: break; } SELECT_LEX *select_lex= lex->current_select; - if (make_schema_select(thd, select_lex, schema_table_idx)) - { + if (make_schema_select(thd, select_lex, get_schema_table(schema_table_idx))) DBUG_RETURN(1); - } - TABLE_LIST *table_list= select_lex->table_list.first; - table_list->schema_select_lex= schema_select_lex; - table_list->schema_table_reformed= 1; + + select_lex->table_list.first->schema_select_lex= schema_select_lex; DBUG_RETURN(0); } @@ -1749,25 +2280,7 @@ bool alloc_query(THD *thd, const char *packet, uint packet_length) return FALSE; } -static void reset_one_shot_variables(THD *thd) -{ - thd->variables.character_set_client= - global_system_variables.character_set_client; - thd->variables.collation_connection= - global_system_variables.collation_connection; - thd->variables.collation_database= - global_system_variables.collation_database; - thd->variables.collation_server= - global_system_variables.collation_server; - thd->update_charset(); - thd->variables.time_zone= - global_system_variables.time_zone; - thd->variables.lc_time_names= &my_locale_en_US; - thd->one_shot_set= 0; -} - -static bool sp_process_definer(THD *thd) { DBUG_ENTER("sp_process_definer"); @@ -1804,7 +2317,7 @@ bool sp_process_definer(THD *thd) Query_arena original_arena; Query_arena *ps_arena= thd->activate_stmt_arena_if_needed(&original_arena); - lex->definer= create_default_definer(thd); + lex->definer= create_default_definer(thd, false); if (ps_arena) thd->restore_active_arena(ps_arena, &original_arena); @@ -1818,20 +2331,24 @@ bool sp_process_definer(THD *thd) } else { + LEX_USER *d= lex->definer= get_current_user(thd, lex->definer); + if (!d) + DBUG_RETURN(TRUE); + /* - If the specified definer differs from the current user, we + If the specified definer differs from the current user or role, we should check that the current user has SUPER privilege (in order to create a stored routine under another user one must have SUPER privilege). */ - if ((strcmp(lex->definer->user.str, thd->security_ctx->priv_user) || - my_strcasecmp(system_charset_info, lex->definer->host.str, - thd->security_ctx->priv_host)) && - check_global_access(thd, SUPER_ACL, true)) - { - my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER"); + bool curuser= !strcmp(d->user.str, thd->security_ctx->priv_user); + bool currole= !curuser && !strcmp(d->user.str, thd->security_ctx->priv_role); + bool curuserhost= curuser && d->host.str && + !my_strcasecmp(system_charset_info, d->host.str, + thd->security_ctx->priv_host); + if (!curuserhost && !currole && + check_global_access(thd, SUPER_ACL, false)) DBUG_RETURN(TRUE); - } } /* Check that the specified definer exists. Emit a warning if not. */ @@ -1840,9 +2357,9 @@ bool sp_process_definer(THD *thd) if (!is_acl_user(lex->definer->host.str, lex->definer->user.str)) { push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_NOTE, + Sql_condition::WARN_LEVEL_NOTE, ER_NO_SUCH_USER, - ER(ER_NO_SUCH_USER), + ER_THD(thd, ER_NO_SUCH_USER), lex->definer->user.str, lex->definer->host.str); } @@ -1913,6 +2430,53 @@ err: } +static bool do_execute_sp(THD *thd, sp_head *sp) +{ + /* bits that should be cleared in thd->server_status */ + uint bits_to_be_cleared= 0; + if (sp->m_flags & sp_head::MULTI_RESULTS) + { + if (!(thd->client_capabilities & CLIENT_MULTI_RESULTS)) + { + /* The client does not support multiple result sets being sent back */ + my_error(ER_SP_BADSELECT, MYF(0), sp->m_qname.str); + return 1; + } + } + /* + If SERVER_MORE_RESULTS_EXISTS is not set, + then remember that it should be cleared + */ + bits_to_be_cleared= (~thd->server_status & + SERVER_MORE_RESULTS_EXISTS); + thd->server_status|= SERVER_MORE_RESULTS_EXISTS; + ha_rows select_limit= thd->variables.select_limit; + thd->variables.select_limit= HA_POS_ERROR; + + /* + We never write CALL statements into binlog: + - If the mode is non-prelocked, each statement will be logged + separately. + - If the mode is prelocked, the invoking statement will care + about writing into binlog. + So just execute the statement. + */ + int res= sp->execute_procedure(thd, &thd->lex->value_list); + + thd->variables.select_limit= select_limit; + thd->server_status&= ~bits_to_be_cleared; + + if (res) + { + DBUG_ASSERT(thd->is_error() || thd->killed); + return 1; // Substatement should already have sent error + } + + my_ok(thd, (thd->get_row_count_func() < 0) ? 0 : thd->get_row_count_func()); + return 0; +} + + /** Execute command saved in thd and lex->sql_command. @@ -1934,7 +2498,7 @@ err: int mysql_execute_command(THD *thd) { - int res= FALSE; + int res= 0; int up_result= 0; LEX *lex= thd->lex; /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ @@ -1948,13 +2512,11 @@ mysql_execute_command(THD *thd) #ifdef HAVE_REPLICATION /* have table map for update for multi-update statement (BUG#37051) */ bool have_table_map_for_update= FALSE; + /* */ + Rpl_filter *rpl_filter; #endif DBUG_ENTER("mysql_execute_command"); -#ifdef WITH_PARTITION_STORAGE_ENGINE - thd->work_part_info= 0; -#endif - DBUG_ASSERT(thd->transaction.stmt.is_empty() || thd->in_sub_stmt); /* Each statement or replication event which might produce deadlock @@ -1993,12 +2555,12 @@ mysql_execute_command(THD *thd) variables, but for now this is probably good enough. */ if ((sql_command_flags[lex->sql_command] & CF_DIAGNOSTIC_STMT) != 0) - thd->warning_info->set_read_only(TRUE); + thd->get_stmt_da()->set_warning_info_read_only(TRUE); else { - thd->warning_info->set_read_only(FALSE); + thd->get_stmt_da()->set_warning_info_read_only(FALSE); if (all_tables) - thd->warning_info->opt_clear_warning_info(thd->query_id); + thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); } #ifdef HAVE_REPLICATION @@ -2021,11 +2583,15 @@ mysql_execute_command(THD *thd) according to slave filtering rules. Returning success without producing any errors in this case. */ - DBUG_RETURN(0); + if (!thd->lex->create_info.if_exists()) + DBUG_RETURN(0); + /* + DROP TRIGGER IF NOT EXISTS will return without an error later + after possibly writing the query to a binlog + */ } - - // force searching in slave.cc:tables_ok() - all_tables->updating= 1; + else // force searching in slave.cc:tables_ok() + all_tables->updating= 1; } /* @@ -2060,10 +2626,8 @@ mysql_execute_command(THD *thd) if (all_tables_not_ok(thd, all_tables)) { /* we warn the slave SQL thread */ - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - if (thd->one_shot_set) - reset_one_shot_variables(thd); - DBUG_RETURN(0); + my_message(ER_SLAVE_IGNORED_TABLE, ER_THD(thd, ER_SLAVE_IGNORED_TABLE), + MYF(0)); } for (table=all_tables; table; table=table->next_global) @@ -2086,28 +2650,12 @@ mysql_execute_command(THD *thd) if (!(lex->sql_command == SQLCOM_UPDATE_MULTI) && !(lex->sql_command == SQLCOM_SET_OPTION) && !(lex->sql_command == SQLCOM_DROP_TABLE && - lex->drop_temporary && lex->drop_if_exists) && + lex->tmp_table() && lex->if_exists()) && all_tables_not_ok(thd, all_tables)) { /* we warn the slave SQL thread */ - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - if (thd->one_shot_set) - { - /* - It's ok to check thd->one_shot_set here: - - The charsets in a MySQL 5.0 slave can change by both a binlogged - SET ONE_SHOT statement and the event-internal charset setting, - and these two ways to change charsets do not seems to work - together. - - At least there seems to be problems in the rli cache for - charsets if we are using ONE_SHOT. Note that this is normally no - problem because either the >= 5.0 slave reads a 4.1 binlog (with - ONE_SHOT) *or* or 5.0 binlog (without ONE_SHOT) but never both." - */ - reset_one_shot_variables(thd); - } + my_message(ER_SLAVE_IGNORED_TABLE, ER_THD(thd, ER_SLAVE_IGNORED_TABLE), + MYF(0)); DBUG_RETURN(0); } /* @@ -2131,13 +2679,197 @@ mysql_execute_command(THD *thd) #ifdef HAVE_REPLICATION } /* endif unlikely slave */ #endif +#ifdef WITH_WSREP + if (wsrep && WSREP(thd)) + { + /* + change LOCK TABLE WRITE to transaction + */ + if (lex->sql_command== SQLCOM_LOCK_TABLES && wsrep_convert_LOCK_to_trx) + { + for (TABLE_LIST *table= all_tables; table; table= table->next_global) + { + if (table->lock_type >= TL_WRITE_ALLOW_WRITE) + { + lex->sql_command= SQLCOM_BEGIN; + thd->wsrep_converted_lock_session= true; + break; + } + } + } + if (lex->sql_command== SQLCOM_UNLOCK_TABLES && + thd->wsrep_converted_lock_session) + { + thd->wsrep_converted_lock_session= false; + lex->sql_command= SQLCOM_COMMIT; + lex->tx_release= TVL_NO; + } + /* + * Bail out if DB snapshot has not been installed. We however, + * allow SET and SHOW queries and reads from information schema + * and dirty reads (if configured) + */ + if (!thd->wsrep_applier && + !(wsrep_ready && wsrep_reject_queries == WSREP_REJECT_NONE) && + !(thd->variables.wsrep_dirty_reads && + (sql_command_flags[lex->sql_command] & CF_CHANGES_DATA) == 0) && + !wsrep_tables_accessible_when_detached(all_tables) && + lex->sql_command != SQLCOM_SET_OPTION && + !wsrep_is_show_query(lex->sql_command)) + { + my_message(ER_UNKNOWN_COM_ERROR, + "WSREP has not yet prepared node for application use", MYF(0)); + goto error; + } + } +#endif /* WITH_WSREP */ status_var_increment(thd->status_var.com_stat[lex->sql_command]); - thd->progress.report_to_client= test(sql_command_flags[lex->sql_command] & - CF_REPORT_PROGRESS); + thd->progress.report_to_client= MY_TEST(sql_command_flags[lex->sql_command] & + CF_REPORT_PROGRESS); DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table == FALSE); + /* store old value of binlog format */ + enum_binlog_format orig_binlog_format,orig_current_stmt_binlog_format; + + thd->get_binlog_format(&orig_binlog_format, + &orig_current_stmt_binlog_format); + + if (!lex->stmt_var_list.is_empty() && !thd->slave_thread) + { + Query_arena backup; + DBUG_PRINT("info", ("SET STATEMENT %d vars", lex->stmt_var_list.elements)); + + lex->old_var_list.empty(); + List_iterator_fast<set_var_base> it(lex->stmt_var_list); + set_var_base *var; + + if (lex->set_arena_for_set_stmt(&backup)) + goto error; + + MEM_ROOT *mem_root= thd->mem_root; + while ((var= it++)) + { + DBUG_ASSERT(var->is_system()); + set_var *o= NULL, *v= (set_var*)var; + if (!v->var->is_set_stmt_ok()) + { + my_error(ER_SET_STATEMENT_NOT_SUPPORTED, MYF(0), v->var->name.str); + lex->reset_arena_for_set_stmt(&backup); + lex->old_var_list.empty(); + lex->free_arena_for_set_stmt(); + goto error; + } + if (v->var->session_is_default(thd)) + o= new set_var(thd,v->type, v->var, &v->base, NULL); + else + { + switch (v->var->option.var_type & GET_TYPE_MASK) + { + case GET_BOOL: + case GET_INT: + case GET_LONG: + case GET_LL: + { + bool null_value; + longlong val= v->var->val_int(&null_value, thd, v->type, &v->base); + o= new set_var(thd, v->type, v->var, &v->base, + (null_value ? + (Item *) new (mem_root) Item_null(thd) : + (Item *) new (mem_root) Item_int(thd, val))); + } + break; + case GET_UINT: + case GET_ULONG: + case GET_ULL: + { + bool null_value; + ulonglong val= v->var->val_int(&null_value, thd, v->type, &v->base); + o= new set_var(thd, v->type, v->var, &v->base, + (null_value ? + (Item *) new (mem_root) Item_null(thd) : + (Item *) new (mem_root) Item_uint(thd, val))); + } + break; + case GET_DOUBLE: + { + bool null_value; + double val= v->var->val_real(&null_value, thd, v->type, &v->base); + o= new set_var(thd, v->type, v->var, &v->base, + (null_value ? + (Item *) new (mem_root) Item_null(thd) : + (Item *) new (mem_root) Item_float(thd, val, 1))); + } + break; + default: + case GET_NO_ARG: + case GET_DISABLED: + DBUG_ASSERT(0); + case 0: + case GET_FLAGSET: + case GET_ENUM: + case GET_SET: + case GET_STR: + case GET_STR_ALLOC: + { + char buff[STRING_BUFFER_USUAL_SIZE]; + String tmp(buff, sizeof(buff), v->var->charset(thd)),*val; + val= v->var->val_str(&tmp, thd, v->type, &v->base); + if (val) + { + Item_string *str= new (mem_root) Item_string(thd, v->var->charset(thd), + val->ptr(), val->length()); + o= new set_var(thd, v->type, v->var, &v->base, str); + } + else + o= new set_var(thd, v->type, v->var, &v->base, + new (mem_root) Item_null(thd)); + } + break; + } + } + DBUG_ASSERT(o); + lex->old_var_list.push_back(o, thd->mem_root); + } + lex->reset_arena_for_set_stmt(&backup); + if (lex->old_var_list.is_empty()) + lex->free_arena_for_set_stmt(); + if (thd->is_error() || + (res= sql_set_variables(thd, &lex->stmt_var_list, false))) + { + if (!thd->is_error()) + my_error(ER_WRONG_ARGUMENTS, MYF(0), "SET"); + lex->restore_set_statement_var(); + goto error; + } + /* + The value of last_insert_id is remembered in THD to be written to binlog + when it's used *the first time* in the statement. But SET STATEMENT + must read the old value of last_insert_id to be able to restore it at + the end. This should not count at "reading of last_insert_id" and + should not remember last_insert_id for binlog. That is, it should clear + stmt_depends_on_first_successful_insert_id_in_prev_stmt flag. + */ + if (!thd->in_sub_stmt) + { + thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0; + } + } + + if (thd->lex->mi.connection_name.str == NULL) + thd->lex->mi.connection_name= thd->variables.default_master_connection; + + /* + Force statement logging for DDL commands to allow us to update + privilege, system or statistic tables directly without the updates + getting logged. + */ + if (!(sql_command_flags[lex->sql_command] & + (CF_CAN_GENERATE_ROW_EVENTS | CF_FORCE_ORIGINAL_BINLOG_FORMAT | + CF_STATUS_COMMAND))) + thd->set_binlog_format_stmt(); + /* End a active transaction so that this command will have it's own transaction and will also sync the binary log. If a DDL is @@ -2153,11 +2885,19 @@ mysql_execute_command(THD *thd) DBUG_ASSERT(! thd->in_sub_stmt); /* Statement transaction still should not be started. */ DBUG_ASSERT(thd->transaction.stmt.is_empty()); - /* Commit the normal transaction if one is active. */ - if (trans_commit_implicit(thd)) - goto error; - /* Release metadata locks acquired in this transaction. */ - thd->mdl_context.release_transactional_locks(); + if (!(thd->variables.option_bits & OPTION_GTID_BEGIN)) + { + /* Commit the normal transaction if one is active. */ + bool commit_failed= trans_commit_implicit(thd); + /* Release metadata locks acquired in this transaction. */ + thd->mdl_context.release_transactional_locks(); + if (commit_failed) + { + WSREP_DEBUG("implicit commit failed, MDL released: %lu", thd->thread_id); + goto error; + } + } + thd->transaction.stmt.mark_trans_did_ddl(); } #ifndef DBUG_OFF @@ -2165,6 +2905,52 @@ mysql_execute_command(THD *thd) DEBUG_SYNC(thd,"before_execute_sql_command"); #endif + /* + Check if we are in a read-only transaction and we're trying to + execute a statement which should always be disallowed in such cases. + + Note that this check is done after any implicit commits. + */ + if (thd->tx_read_only && + (sql_command_flags[lex->sql_command] & CF_DISALLOW_IN_RO_TRANS)) + { + my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0)); + goto error; + } + + /* + Close tables open by HANDLERs before executing DDL statement + which is going to affect those tables. + + This should happen before temporary tables are pre-opened as + otherwise we will get errors about attempt to re-open tables + if table to be changed is open through HANDLER. + + Note that even although this is done before any privilege + checks there is no security problem here as closing open + HANDLER doesn't require any privileges anyway. + */ + if (sql_command_flags[lex->sql_command] & CF_HA_CLOSE) + mysql_ha_rm_tables(thd, all_tables); + + /* + Pre-open temporary tables to simplify privilege checking + for statements which need this. + */ + if (sql_command_flags[lex->sql_command] & CF_PREOPEN_TMP_TABLES) + { + if (open_temporary_tables(thd, all_tables)) + goto error; + } + + if (sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND) + thd->query_plan_flags|= QPLAN_STATUS; + if (sql_command_flags[lex->sql_command] & CF_ADMIN_COMMAND) + thd->query_plan_flags|= QPLAN_ADMIN; + + /* Start timeouts */ + thd->set_query_timer(); + switch (lex->sql_command) { case SQLCOM_SHOW_EVENTS: @@ -2172,23 +2958,48 @@ mysql_execute_command(THD *thd) my_error(ER_NOT_SUPPORTED_YET, MYF(0), "embedded server"); break; #endif - case SQLCOM_SHOW_STATUS_PROC: - case SQLCOM_SHOW_STATUS_FUNC: - if ((res= check_table_access(thd, SELECT_ACL, all_tables, FALSE, - UINT_MAX, FALSE))) - goto error; - res= execute_sqlcom_select(thd, all_tables); - break; case SQLCOM_SHOW_STATUS: { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); execute_show_status(thd, all_tables); + break; } + case SQLCOM_SHOW_EXPLAIN: + { + if (!thd->security_ctx->priv_user[0] && + check_global_access(thd,PROCESS_ACL)) + break; + + /* + The select should use only one table, it's the SHOW EXPLAIN pseudo-table + */ + if (lex->sroutines.records || lex->query_tables->next_global) + { + my_message(ER_SET_CONSTANTS_ONLY, ER_THD(thd, ER_SET_CONSTANTS_ONLY), + MYF(0)); + goto error; + } + + Item **it= lex->value_list.head_ref(); + if (!(*it)->basic_const_item() || + (!(*it)->fixed && (*it)->fix_fields(lex->thd, it)) || + (*it)->check_cols(1)) + { + my_message(ER_SET_CONSTANTS_ONLY, ER_THD(thd, ER_SET_CONSTANTS_ONLY), + MYF(0)); + goto error; + } + } + /* fall through */ + case SQLCOM_SHOW_STATUS_PROC: + case SQLCOM_SHOW_STATUS_FUNC: case SQLCOM_SHOW_DATABASES: case SQLCOM_SHOW_TABLES: case SQLCOM_SHOW_TRIGGERS: case SQLCOM_SHOW_TABLE_STATUS: case SQLCOM_SHOW_OPEN_TABLES: + case SQLCOM_SHOW_GENERIC: case SQLCOM_SHOW_PLUGINS: case SQLCOM_SHOW_FIELDS: case SQLCOM_SHOW_KEYS: @@ -2197,12 +3008,19 @@ mysql_execute_command(THD *thd) case SQLCOM_SHOW_COLLATIONS: case SQLCOM_SHOW_STORAGE_ENGINES: case SQLCOM_SHOW_PROFILE: - case SQLCOM_SHOW_CLIENT_STATS: - case SQLCOM_SHOW_USER_STATS: - case SQLCOM_SHOW_TABLE_STATS: - case SQLCOM_SHOW_INDEX_STATS: case SQLCOM_SELECT: - { + { + if (lex->sql_command == SQLCOM_SELECT) + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_READ); + else + { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); +#ifdef ENABLED_PROFILING + if (lex->sql_command == SQLCOM_SHOW_PROFILE) + thd->profiling.discard_current_query(); +#endif + } + thd->status_var.last_query_cost= 0.0; /* @@ -2219,13 +3037,12 @@ mysql_execute_command(THD *thd) else res= check_access(thd, privileges_requested, any_db, NULL, NULL, 0, 0); - if (res) - break; + if (!res) + res= execute_sqlcom_select(thd, all_tables); - res= execute_sqlcom_select(thd, all_tables); break; } -case SQLCOM_PREPARE: + case SQLCOM_PREPARE: { mysql_sql_stmt_prepare(thd); break; @@ -2279,7 +3096,7 @@ case SQLCOM_PREPARE: my_error(ER_WRONG_ARGUMENTS, MYF(0), "PURGE LOGS BEFORE"); goto error; } - it= new Item_func_unix_timestamp(it); + it= new (thd->mem_root) Item_func_unix_timestamp(thd, it); it->fix_fields(thd, &it); res = purge_master_logs_before_date(thd, (ulong)it->val_int()); break; @@ -2288,16 +3105,16 @@ case SQLCOM_PREPARE: case SQLCOM_SHOW_WARNS: { res= mysqld_show_warnings(thd, (ulong) - ((1L << (uint) MYSQL_ERROR::WARN_LEVEL_NOTE) | - (1L << (uint) MYSQL_ERROR::WARN_LEVEL_WARN) | - (1L << (uint) MYSQL_ERROR::WARN_LEVEL_ERROR) + ((1L << (uint) Sql_condition::WARN_LEVEL_NOTE) | + (1L << (uint) Sql_condition::WARN_LEVEL_WARN) | + (1L << (uint) Sql_condition::WARN_LEVEL_ERROR) )); break; } case SQLCOM_SHOW_ERRORS: { res= mysqld_show_warnings(thd, (ulong) - (1L << (uint) MYSQL_ERROR::WARN_LEVEL_ERROR)); + (1L << (uint) Sql_condition::WARN_LEVEL_ERROR)); break; } case SQLCOM_SHOW_PROFILES: @@ -2325,6 +3142,7 @@ case SQLCOM_PREPARE: case SQLCOM_SHOW_RELAYLOG_EVENTS: /* fall through */ case SQLCOM_SHOW_BINLOG_EVENTS: { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); if (check_global_access(thd, REPL_SLAVE_ACL)) goto error; res = mysql_show_binlog_events(thd); @@ -2357,10 +3175,62 @@ case SQLCOM_PREPARE: #ifdef HAVE_REPLICATION case SQLCOM_CHANGE_MASTER: { + LEX_MASTER_INFO *lex_mi= &thd->lex->mi; + Master_info *mi; + bool new_master= 0; + bool master_info_added; + if (check_global_access(thd, SUPER_ACL)) goto error; + /* + In this code it's ok to use LOCK_active_mi as we are adding new things + into master_info_index + */ mysql_mutex_lock(&LOCK_active_mi); - res = change_master(thd,active_mi); + if (!master_info_index) + { + mysql_mutex_unlock(&LOCK_active_mi); + my_error(ER_SERVER_SHUTDOWN, MYF(0)); + goto error; + } + + mi= master_info_index->get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_NOTE); + + if (mi == NULL) + { + /* New replication created */ + mi= new Master_info(&lex_mi->connection_name, relay_log_recovery); + if (!mi || mi->error()) + { + delete mi; + res= 1; + mysql_mutex_unlock(&LOCK_active_mi); + break; + } + new_master= 1; + } + + res= change_master(thd, mi, &master_info_added); + if (res && new_master) + { + /* + If the new master was added by change_master(), remove it as it didn't + work (this will free mi as well). + + If new master was not added, we still need to free mi. + */ + if (master_info_added) + master_info_index->remove_master_info(mi); + else + delete mi; + } + else + { + mi->rpl_filter= get_or_create_rpl_filter(lex_mi->connection_name.str, + lex_mi->connection_name.length); + } + mysql_mutex_unlock(&LOCK_active_mi); break; } @@ -2369,18 +3239,24 @@ case SQLCOM_PREPARE: /* Accept one of two privileges */ if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL)) goto error; - mysql_mutex_lock(&LOCK_active_mi); - if (active_mi != NULL) + + if (lex->verbose) { - res = show_master_info(thd, active_mi); + mysql_mutex_lock(&LOCK_active_mi); + res= show_all_master_info(thd); + mysql_mutex_unlock(&LOCK_active_mi); } else { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_NO_MASTER_INFO, ER(WARN_NO_MASTER_INFO)); - my_ok(thd); + LEX_MASTER_INFO *lex_mi= &thd->lex->mi; + Master_info *mi; + if ((mi= get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_ERROR))) + { + res= show_master_info(thd, mi, 0); + mi->release(); + } } - mysql_mutex_unlock(&LOCK_active_mi); break; } case SQLCOM_SHOW_MASTER_STAT: @@ -2414,6 +3290,12 @@ case SQLCOM_PREPARE: TABLE_LIST *create_table= first_table; TABLE_LIST *select_tables= lex->create_last_non_select_table->next_global; + if (lex->tmp_table()) + { + status_var_decrement(thd->status_var.com_stat[SQLCOM_CREATE_TABLE]); + status_var_increment(thd->status_var.com_create_tmp_table); + } + /* Code below (especially in mysql_create_table() and select_create methods) may modify HA_CREATE_INFO structure in LEX, so we have to @@ -2421,7 +3303,7 @@ case SQLCOM_PREPARE: safe. A shallow copy is enough as this code won't modify any memory referenced from this structure. */ - HA_CREATE_INFO create_info(lex->create_info); + Table_specification_st create_info(lex->create_info); /* We need to copy alter_info for the same reasons of re-execution safety, only in case of Alter_info we have to do (almost) a deep @@ -2436,26 +3318,26 @@ case SQLCOM_PREPARE: goto end_with_restore_list; } + /* Check privileges */ if ((res= create_table_precheck(thd, select_tables, create_table))) goto end_with_restore_list; /* Might have been updated in create_table_precheck */ create_info.alias= create_table->alias; -#ifdef HAVE_READLINK - /* Fix names if symlinked tables */ + /* Fix names if symlinked or relocated tables */ if (append_file_to_dir(thd, &create_info.data_file_name, create_table->table_name) || append_file_to_dir(thd, &create_info.index_file_name, create_table->table_name)) goto end_with_restore_list; -#endif + /* If no engine type was given, work out the default now rather than at parse-time. */ if (!(create_info.used_fields & HA_CREATE_USED_ENGINE)) - create_info.db_type= ha_default_handlerton(thd); + create_info.use_default_db_type(thd); /* If we are using SET CHARSET without DEFAULT, add an implicit DEFAULT to not confuse old users. (This may change). @@ -2470,10 +3352,24 @@ case SQLCOM_PREPARE: create_info.table_charset= 0; } + /* + If we are a slave, we should add OR REPLACE if we don't have + IF EXISTS. This will help a slave to recover from + CREATE TABLE OR EXISTS failures by dropping the table and + retrying the create. + */ + if (thd->slave_thread && + slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT && + !lex->create_info.if_not_exists()) + { + create_info.add(DDL_options_st::OPT_OR_REPLACE); + create_info.add(DDL_options_st::OPT_OR_REPLACE_SLAVE_GENERATED); + } + #ifdef WITH_PARTITION_STORAGE_ENGINE { partition_info *part_info= thd->lex->part_info; - if (part_info && !(part_info= part_info->get_clone())) + if (part_info && !(part_info= part_info->get_clone(thd))) { res= -1; goto end_with_restore_list; @@ -2482,9 +3378,6 @@ case SQLCOM_PREPARE: } #endif - /* Close any open handlers for the table. */ - mysql_ha_rm_tables(thd, create_table); - if (select_lex->item_list.elements) // With select { select_result *result; @@ -2498,7 +3391,7 @@ case SQLCOM_PREPARE: */ if(lex->ignore) lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_IGNORE_SELECT); - + if(lex->duplicates == DUP_REPLACE) lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_REPLACE_SELECT); @@ -2510,9 +3403,8 @@ case SQLCOM_PREPARE: raise a warning, as it may cause problems (see 'NAME_CONST issues' in 'Binary Logging of Stored Programs') */ - if (thd->query_name_consts && - mysql_bin_log.is_open() && - thd->variables.binlog_format == BINLOG_FORMAT_STMT && + if (thd->query_name_consts && mysql_bin_log.is_open() && + thd->wsrep_binlog_format() == BINLOG_FORMAT_STMT && !mysql_bin_log.is_query_in_union(thd, thd->query_id)) { List_iterator_fast<Item> it(select_lex->item_list); @@ -2521,7 +3413,7 @@ case SQLCOM_PREPARE: /* Count SP local vars in the top-level SELECT list */ while ((item= it++)) { - if (item->is_splocal()) + if (item->get_item_splocal()) splocal_refs++; } /* @@ -2532,7 +3424,7 @@ case SQLCOM_PREPARE: */ if (splocal_refs != thd->query_name_consts) push_warning(thd, - MYSQL_ERROR::WARN_LEVEL_WARN, + Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Invoked routine ran a statement that may cause problems with " "binary log, see 'NAME_CONST issues' in 'Binary Logging of Stored Programs' " @@ -2555,34 +3447,34 @@ case SQLCOM_PREPARE: goto end_with_restore_list; } - res= open_and_lock_tables(thd, lex->query_tables, TRUE, 0); + /* Copy temporarily the statement flags to thd for lock_table_names() */ + uint save_thd_create_info_options= thd->lex->create_info.options; + thd->lex->create_info.options|= create_info.options; + res= open_and_lock_tables(thd, create_info, lex->query_tables, TRUE, 0); + thd->lex->create_info.options= save_thd_create_info_options; if (res) { /* Got error or warning. Set res to 1 if error */ if (!(res= thd->is_error())) my_ok(thd); // CREATE ... IF NOT EXISTS + goto end_with_restore_list; } - else + + /* Ensure we don't try to create something from which we select from */ + if (create_info.or_replace() && !create_info.tmp_table()) { - /* The table already exists */ - if (create_table->table) + TABLE_LIST *duplicate; + if ((duplicate= unique_table(thd, lex->query_tables, + lex->query_tables->next_global, + CHECK_DUP_FOR_CREATE))) { - if (create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_TABLE_EXISTS_ERROR, - ER(ER_TABLE_EXISTS_ERROR), - create_info.alias); - my_ok(thd); - } - else - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_info.alias); - res= 1; - } + update_non_unique_table_error(lex->query_tables, "CREATE", + duplicate); + res= TRUE; goto end_with_restore_list; } - + } + { /* Remove target table from main select and name resolution context. This can't be done earlier as it will break view merging in @@ -2590,40 +3482,40 @@ case SQLCOM_PREPARE: */ lex->unlink_first_table(&link_to_local); - /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */ - if (create_info.options & HA_LEX_CREATE_TMP_TABLE) - thd->variables.option_bits|= OPTION_KEEP_LOG; + /* Store reference to table in case of LOCK TABLES */ + create_info.table= create_table->table; /* select_create is currently not re-execution friendly and needs to be created for every execution of a PS/SP. + Note: In wsrep-patch, CTAS is handled like a regular transaction. */ - if ((result= new select_create(create_table, - &create_info, - &alter_info, - select_lex->item_list, - lex->duplicates, - lex->ignore, - select_tables))) + if ((result= new (thd->mem_root) select_create(thd, create_table, + &create_info, + &alter_info, + select_lex->item_list, + lex->duplicates, + lex->ignore, + select_tables))) { /* CREATE from SELECT give its SELECT_LEX for SELECT, and item_list belong to SELECT */ - res= handle_select(thd, lex, result, 0); + if (!(res= handle_select(thd, lex, result, 0))) + { + if (create_info.tmp_table()) + thd->variables.option_bits|= OPTION_KEEP_LOG; + } delete result; } - lex->link_first_table_back(create_table, link_to_local); } } else { - /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */ - if (create_info.options & HA_LEX_CREATE_TMP_TABLE) - thd->variables.option_bits|= OPTION_KEEP_LOG; /* regular create */ - if (create_info.options & HA_LEX_CREATE_TABLE_LIKE) + if (create_info.like()) { /* CREATE TABLE ... LIKE ... */ res= mysql_create_like_table(thd, create_table, select_tables, @@ -2631,19 +3523,35 @@ case SQLCOM_PREPARE: } else { + /* + In STATEMENT format, we probably have to replicate also temporary + tables, like mysql replication does. Also check if the requested + engine is allowed/supported. + */ + if (WSREP(thd) && + !check_engine(thd, create_table->db, create_table->table_name, + &create_info) && + (!thd->is_current_stmt_binlog_format_row() || + !create_info.tmp_table())) + { + WSREP_TO_ISOLATION_BEGIN(create_table->db, create_table->table_name, NULL) + } /* Regular CREATE TABLE */ - res= mysql_create_table(thd, create_table, - &create_info, &alter_info); + res= mysql_create_table(thd, create_table, &create_info, &alter_info); } if (!res) + { + /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */ + if (create_info.tmp_table()) + thd->variables.option_bits|= OPTION_KEEP_LOG; my_ok(thd); + } } end_with_restore_list: break; } case SQLCOM_CREATE_INDEX: - /* Fall through */ case SQLCOM_DROP_INDEX: /* CREATE INDEX and DROP INDEX are implemented by calling ALTER @@ -2664,13 +3572,7 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_one_table_access(thd, INDEX_ACL, all_tables)) goto error; /* purecov: inspected */ - /* - Currently CREATE INDEX or DROP INDEX cause a full table rebuild - and thus classify as slow administrative statements just like - ALTER TABLE. - */ - thd->enable_slow_log= opt_log_slow_admin_statements; - thd->query_plan_flags|= QPLAN_ADMIN; + WSREP_TO_ISOLATION_BEGIN(first_table->db, first_table->table_name, NULL) bzero((char*) &create_info, sizeof(create_info)); create_info.db_type= 0; @@ -2679,75 +3581,148 @@ end_with_restore_list: res= mysql_alter_table(thd, first_table->db, first_table->table_name, &create_info, first_table, &alter_info, - 0, (ORDER*) 0, 0, 0); + 0, (ORDER*) 0, 0); break; } #ifdef HAVE_REPLICATION case SQLCOM_SLAVE_START: { - mysql_mutex_lock(&LOCK_active_mi); - start_slave(thd,active_mi,1 /* net report*/); - mysql_mutex_unlock(&LOCK_active_mi); + LEX_MASTER_INFO* lex_mi= &thd->lex->mi; + Master_info *mi; + int load_error; + + load_error= rpl_load_gtid_slave_state(thd); + + /* + We don't need to ensure that only one user is using master_info + as start_slave is protected against simultaneous usage + */ + if ((mi= get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_ERROR))) + { + if (load_error) + { + /* + We cannot start a slave using GTID if we cannot load the + GTID position from the mysql.gtid_slave_pos table. But we + can allow non-GTID replication (useful eg. during upgrade). + */ + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + mi->release(); + break; + } + else + thd->clear_error(); + } + if (!start_slave(thd, mi, 1 /* net report*/)) + my_ok(thd); + mi->release(); + } break; } case SQLCOM_SLAVE_STOP: - /* - If the client thread has locked tables, a deadlock is possible. - Assume that - - the client thread does LOCK TABLE t READ. - - then the master updates t. - - then the SQL slave thread wants to update t, - so it waits for the client thread because t is locked by it. + { + LEX_MASTER_INFO *lex_mi; + Master_info *mi; + /* + If the client thread has locked tables, a deadlock is possible. + Assume that + - the client thread does LOCK TABLE t READ. + - then the master updates t. + - then the SQL slave thread wants to update t, + so it waits for the client thread because t is locked by it. - then the client thread does SLAVE STOP. SLAVE STOP waits for the SQL slave thread to terminate its update t, which waits for the client thread because t is locked by it. - To prevent that, refuse SLAVE STOP if the - client thread has locked tables - */ - if (thd->locked_tables_mode || - thd->in_active_multi_stmt_transaction() || thd->global_read_lock.is_acquired()) + To prevent that, refuse SLAVE STOP if the + client thread has locked tables + */ + if (thd->locked_tables_mode || + thd->in_active_multi_stmt_transaction() || + thd->global_read_lock.is_acquired()) + { + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER_THD(thd, ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + goto error; + } + + lex_mi= &thd->lex->mi; + if ((mi= get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_ERROR))) + { + if (stop_slave(thd, mi, 1/* net report*/)) + res= 1; + mi->release(); + if (rpl_parallel_resize_pool_if_no_slaves()) + res= 1; + if (!res) + my_ok(thd); + } + break; + } + case SQLCOM_SLAVE_ALL_START: { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - goto error; + mysql_mutex_lock(&LOCK_active_mi); + if (master_info_index && !master_info_index->start_all_slaves(thd)) + my_ok(thd); + mysql_mutex_unlock(&LOCK_active_mi); + break; } + case SQLCOM_SLAVE_ALL_STOP: { + if (thd->locked_tables_mode || + thd->in_active_multi_stmt_transaction() || + thd->global_read_lock.is_acquired()) + { + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER_THD(thd, ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + goto error; + } mysql_mutex_lock(&LOCK_active_mi); - stop_slave(thd,active_mi,1/* net report*/); + if (master_info_index && !master_info_index->stop_all_slaves(thd)) + my_ok(thd); mysql_mutex_unlock(&LOCK_active_mi); break; } #endif /* HAVE_REPLICATION */ - case SQLCOM_RENAME_TABLE: { - if (execute_rename_table(thd, first_table, all_tables)) + if (check_rename_table(thd, first_table, all_tables)) + goto error; + + WSREP_TO_ISOLATION_BEGIN(0, 0, first_table) + + if (mysql_rename_tables(thd, first_table, 0)) goto error; break; } #ifndef EMBEDDED_LIBRARY case SQLCOM_SHOW_BINLOGS: #ifdef DONT_ALLOW_SHOW_COMMANDS - my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND), + my_message(ER_NOT_ALLOWED_COMMAND, ER_THD(thd, ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */ goto error; #else { if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL)) goto error; + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); res = show_binlogs(thd); break; } #endif #endif /* EMBEDDED_LIBRARY */ case SQLCOM_SHOW_CREATE: - DBUG_ASSERT(first_table == all_tables && first_table != 0); + { + DBUG_ASSERT(first_table == all_tables && first_table != 0); #ifdef DONT_ALLOW_SHOW_COMMANDS - my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND), + my_message(ER_NOT_ALLOWED_COMMAND, ER_THD(thd, ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */ goto error; #else - { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); + /* Access check: SHOW CREATE TABLE require any privileges on the table level (ie @@ -2760,49 +3735,15 @@ end_with_restore_list: DBUG_PRINT("debug", ("lex->only_view: %d, table: %s.%s", lex->only_view, first_table->db, first_table->table_name)); - if (lex->only_view) - { - if (check_table_access(thd, SELECT_ACL, first_table, FALSE, 1, FALSE)) - { - DBUG_PRINT("debug", ("check_table_access failed")); - my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), - "SHOW", thd->security_ctx->priv_user, - thd->security_ctx->host_or_ip, first_table->alias); - goto error; - } - DBUG_PRINT("debug", ("check_table_access succeeded")); - - /* Ignore temporary tables if this is "SHOW CREATE VIEW" */ - first_table->open_type= OT_BASE_ONLY; - - } - else - { - /* - The fact that check_some_access() returned FALSE does not mean that - access is granted. We need to check if first_table->grant.privilege - contains any table-specific privilege. - */ - DBUG_PRINT("debug", ("first_table->grant.privilege: %lx", - first_table->grant.privilege)); - if (check_some_access(thd, SHOW_CREATE_TABLE_ACLS, first_table) || - (first_table->grant.privilege & SHOW_CREATE_TABLE_ACLS) == 0) - { - my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), - "SHOW", thd->security_ctx->priv_user, - thd->security_ctx->host_or_ip, first_table->alias); - goto error; - } - } - - /* Access is granted. Execute the command. */ res= mysqld_show_create(thd, first_table); break; - } #endif + } case SQLCOM_CHECKSUM: { DBUG_ASSERT(first_table == all_tables && first_table != 0); + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_READ); + if (check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ @@ -2812,8 +3753,13 @@ end_with_restore_list: } case SQLCOM_UPDATE: { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); ha_rows found= 0, updated= 0; DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (WSREP_CLIENT(thd) && + wsrep_sync_wait(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE)) + goto error; + if (update_precheck(thd, all_tables)) break; @@ -2850,6 +3796,7 @@ end_with_restore_list: /* if we switched from normal update, rights are checked */ if (up_result != 2) { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); if ((res= multi_update_precheck(thd, all_tables))) break; } @@ -2956,7 +3903,24 @@ end_with_restore_list: /* fall through */ case SQLCOM_INSERT: { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_INSERT_REPLACE); DBUG_ASSERT(first_table == all_tables && first_table != 0); + + if (WSREP_CLIENT(thd) && + wsrep_sync_wait(thd, WSREP_SYNC_WAIT_BEFORE_INSERT_REPLACE)) + goto error; + + /* + Since INSERT DELAYED doesn't support temporary tables, we could + not pre-open temporary tables for SQLCOM_INSERT / SQLCOM_REPLACE. + Open them here instead. + */ + if (first_table->lock_type != TL_WRITE_DELAYED) + { + if ((res= open_temporary_tables(thd, all_tables))) + break; + } + if ((res= insert_precheck(thd, all_tables))) break; @@ -2997,10 +3961,24 @@ end_with_restore_list: case SQLCOM_REPLACE_SELECT: case SQLCOM_INSERT_SELECT: { - select_result *sel_result; + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_INSERT_REPLACE); + select_insert *sel_result; + bool explain= MY_TEST(lex->describe); DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (WSREP_CLIENT(thd) && + wsrep_sync_wait(thd, WSREP_SYNC_WAIT_BEFORE_INSERT_REPLACE)) + goto error; + if ((res= insert_precheck(thd, all_tables))) break; +#ifdef WITH_WSREP + if (WSREP(thd) && thd->wsrep_consistency_check == CONSISTENCY_CHECK_DECLARED) + { + thd->wsrep_consistency_check = CONSISTENCY_CHECK_RUNNING; + WSREP_TO_ISOLATION_BEGIN(first_table->db, first_table->table_name, NULL); + } +#endif /* WITH_WSREP */ + /* INSERT...SELECT...ON DUPLICATE KEY UPDATE/REPLACE SELECT/ INSERT...IGNORE...SELECT can be unsafe, unless ORDER BY PRIMARY KEY @@ -3041,15 +4019,22 @@ end_with_restore_list: select_lex->context.table_list= select_lex->context.first_name_resolution_table= second_table; res= mysql_insert_select_prepare(thd); - if (!res && (sel_result= new select_insert(first_table, - first_table->table, - &lex->field_list, - &lex->update_list, - &lex->value_list, - lex->duplicates, - lex->ignore))) + if (!res && (sel_result= new (thd->mem_root) select_insert(thd, + first_table, + first_table->table, + &lex->field_list, + &lex->update_list, + &lex->value_list, + lex->duplicates, + lex->ignore))) { - res= handle_select(thd, lex, sel_result, OPTION_SETUP_TABLES_DONE); + if (lex->analyze_stmt) + ((select_result_interceptor*)sel_result)->disable_my_ok_calls(); + + if (explain) + res= mysql_explain_union(thd, &thd->lex->unit, sel_result); + else + res= handle_select(thd, lex, sel_result, OPTION_SETUP_TABLES_DONE); /* Invalidate the table in the query cache if something changed after unlocking when changes become visible. @@ -3065,8 +4050,22 @@ end_with_restore_list: query_cache_invalidate3(thd, first_table, 1); first_table->next_local= save_table; } + if (explain) + { + /* + sel_result needs to be cleaned up properly. + INSERT... SELECT statement will call either send_eof() or + abort_result_set(). EXPLAIN doesn't call either, so we need + to cleanup manually. + */ + sel_result->abort_result_set(); + } delete sel_result; } + + if (!res && (explain || lex->analyze_stmt)) + res= thd->lex->explain->send_explain(thd); + /* revert changes for SP */ MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func()); select_lex->table_list.first= first_table; @@ -3085,24 +4084,73 @@ end_with_restore_list: } case SQLCOM_DELETE: { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); + select_result *sel_result= NULL; DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (WSREP_CLIENT(thd) && + wsrep_sync_wait(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE)) + goto error; + if ((res= delete_precheck(thd, all_tables))) break; DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); MYSQL_DELETE_START(thd->query()); - res = mysql_delete(thd, all_tables, select_lex->where, - &select_lex->order_list, - unit->select_limit_cnt, select_lex->options); + Protocol *save_protocol; + bool replaced_protocol= false; + + if (!select_lex->item_list.is_empty()) + { + /* This is DELETE ... RETURNING. It will return output to the client */ + if (thd->lex->analyze_stmt) + { + /* + Actually, it is ANALYZE .. DELETE .. RETURNING. We need to produce + output and then discard it. + */ + sel_result= new (thd->mem_root) select_send_analyze(thd); + replaced_protocol= true; + save_protocol= thd->protocol; + thd->protocol= new Protocol_discard(thd); + } + else + { + if (!lex->result && !(sel_result= new (thd->mem_root) select_send(thd))) + goto error; + } + } + + res = mysql_delete(thd, all_tables, + select_lex->where, &select_lex->order_list, + unit->select_limit_cnt, select_lex->options, + lex->result ? lex->result : sel_result); + + if (replaced_protocol) + { + delete thd->protocol; + thd->protocol= save_protocol; + } + + if (thd->lex->analyze_stmt || thd->lex->describe) + { + if (!res) + res= thd->lex->explain->send_explain(thd); + } + + delete sel_result; MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func()); break; } case SQLCOM_DELETE_MULTI: { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first; - multi_delete *del_result; + multi_delete *result; + if (WSREP_CLIENT(thd) && + wsrep_sync_wait(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE)) + goto error; if ((res= multi_delete_precheck(thd, all_tables))) break; @@ -3110,10 +4158,10 @@ end_with_restore_list: /* condition will be TRUE on SP re-excuting */ if (select_lex->item_list.elements != 0) select_lex->item_list.empty(); - if (add_item_to_list(thd, new Item_null())) + if (add_item_to_list(thd, new (thd->mem_root) Item_null(thd))) goto error; - thd_proc_info(thd, "init"); + THD_STAGE_INFO(thd, stage_init); if ((res= open_and_lock_tables(thd, all_tables, TRUE, 0))) break; @@ -3124,25 +4172,35 @@ end_with_restore_list: goto error; } - if (!thd->is_fatal_error && - (del_result= new multi_delete(aux_tables, lex->table_count))) - { - res= mysql_select(thd, &select_lex->ref_pointer_array, - select_lex->get_table_list(), - select_lex->with_wild, - select_lex->item_list, - select_lex->where, - 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, - (ORDER *)NULL, - (select_lex->options | thd->variables.option_bits | - SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | - OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT, - del_result, unit, select_lex); - res|= thd->is_error(); - MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); - if (res) - del_result->abort_result_set(); - delete del_result; + if (!thd->is_fatal_error) + { + result= new (thd->mem_root) multi_delete(thd, aux_tables, + lex->table_count); + if (result) + { + res= mysql_select(thd, &select_lex->ref_pointer_array, + select_lex->get_table_list(), + select_lex->with_wild, + select_lex->item_list, + select_lex->where, + 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, + (ORDER *)NULL, + (select_lex->options | thd->variables.option_bits | + SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | + OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT, + result, unit, select_lex); + res|= thd->is_error(); + + MYSQL_MULTI_DELETE_DONE(res, result->num_deleted()); + if (res) + result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ + else + { + if (lex->describe || lex->analyze_stmt) + res= thd->lex->explain->send_explain(thd); + } + delete result; + } } else { @@ -3154,21 +4212,47 @@ end_with_restore_list: case SQLCOM_DROP_TABLE: { DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (!lex->drop_temporary) + if (!lex->tmp_table()) { if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ } else { + status_var_decrement(thd->status_var.com_stat[SQLCOM_DROP_TABLE]); + status_var_increment(thd->status_var.com_drop_tmp_table); + /* So that DROP TEMPORARY TABLE gets to binlog at commit/rollback */ thd->variables.option_bits|= OPTION_KEEP_LOG; } + /* + If we are a slave, we should add IF EXISTS if the query executed + on the master without an error. This will help a slave to + recover from multi-table DROP TABLE that was aborted in the + middle. + */ + if (thd->slave_thread && !thd->slave_expected_error && + slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT) + lex->create_info.set(DDL_options_st::OPT_IF_EXISTS); + + if (WSREP(thd)) + { + for (TABLE_LIST *table= all_tables; table; table= table->next_global) + { + if (!lex->tmp_table() && + (!thd->is_current_stmt_binlog_format_row() || + !find_temporary_table(thd, table))) + { + WSREP_TO_ISOLATION_BEGIN(NULL, NULL, all_tables); + break; + } + } + } + /* DDL and binlog write order are protected by metadata locks. */ - res= mysql_rm_table(thd, first_table, lex->drop_if_exists, - lex->drop_temporary); + res= mysql_rm_table(thd, first_table, lex->if_exists(), lex->tmp_table()); + break; } - break; case SQLCOM_SHOW_PROCESSLIST: if (!thd->security_ctx->priv_user[0] && check_global_access(thd,PROCESS_ACL)) @@ -3190,7 +4274,7 @@ end_with_restore_list: break; case SQLCOM_SHOW_ENGINE_LOGS: #ifdef DONT_ALLOW_SHOW_COMMANDS - my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND), + my_message(ER_NOT_ALLOWED_COMMAND, ER_THD(thd, ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */ goto error; #else @@ -3223,7 +4307,7 @@ end_with_restore_list: if (!(thd->client_capabilities & CLIENT_LOCAL_FILES) || !opt_local_infile) { - my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); + my_message(ER_NOT_ALLOWED_COMMAND, ER_THD(thd, ER_NOT_ALLOWED_COMMAND), MYF(0)); goto error; } } @@ -3244,13 +4328,8 @@ end_with_restore_list: if ((check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE) || open_and_lock_tables(thd, all_tables, TRUE, 0))) goto error; - if (!(res= sql_set_variables(thd, lex_var_list))) + if (!(res= sql_set_variables(thd, lex_var_list, true))) { - /* - If the previous command was a SET ONE_SHOT, we don't want to forget - about the ONE_SHOT property of that SET. So we use a |= instead of = . - */ - thd->one_shot_set|= lex->one_shot_set; my_ok(thd); } else @@ -3296,8 +4375,20 @@ end_with_restore_list: thd->mdl_context.release_transactional_locks(); if (res) goto error; - if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, - FALSE, UINT_MAX, FALSE)) + + /* + Here we have to pre-open temporary tables for LOCK TABLES. + + CF_PREOPEN_TMP_TABLES is not set for this SQL statement simply + because LOCK TABLES calls close_thread_tables() as a first thing + (it's called from unlock_locked_tables() above). So even if + CF_PREOPEN_TMP_TABLES was set and the tables would be pre-opened + in a usual way, they would have been closed. + */ + if (open_temporary_tables(thd, all_tables)) + goto error; + + if (lock_tables_precheck(thd, all_tables)) goto error; thd->variables.option_bits|= OPTION_TABLE_LOCK; @@ -3319,15 +4410,7 @@ end_with_restore_list: break; case SQLCOM_CREATE_DB: { - /* - As mysql_create_db() may modify HA_CREATE_INFO structure passed to - it, we need to use a copy of LEX::create_info to make execution - prepared statement- safe. - */ - HA_CREATE_INFO create_info(lex->create_info); - char *alias; - if (!(alias=thd->strmake(lex->name.str, lex->name.length)) || - check_db_name(&lex->name)) + if (check_db_name(&lex->name)) { my_error(ER_WRONG_DB_NAME, MYF(0), lex->name.str); break; @@ -3340,18 +4423,24 @@ end_with_restore_list: above was not called. So we have to check rules again here. */ #ifdef HAVE_REPLICATION - if (thd->slave_thread && - (!rpl_filter->db_ok(lex->name.str) || - !rpl_filter->db_ok_with_wild_table(lex->name.str))) + if (thd->slave_thread) { - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - break; + rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (!rpl_filter->db_ok(lex->name.str) || + !rpl_filter->db_ok_with_wild_table(lex->name.str)) + { + my_message(ER_SLAVE_IGNORED_TABLE, ER_THD(thd, ER_SLAVE_IGNORED_TABLE), MYF(0)); + break; + } } #endif - if (check_access(thd, CREATE_ACL, lex->name.str, NULL, NULL, 1, 0)) + if (check_access(thd, lex->create_info.or_replace() ? + (CREATE_ACL | DROP_ACL) : CREATE_ACL, + lex->name.str, NULL, NULL, 1, 0)) break; - res= mysql_create_db(thd,(lower_case_table_names == 2 ? alias : - lex->name.str), &create_info, 0); + WSREP_TO_ISOLATION_BEGIN(lex->name.str, NULL, NULL) + res= mysql_create_db(thd, lex->name.str, + lex->create_info, &lex->create_info); break; } case SQLCOM_DROP_DB: @@ -3369,30 +4458,37 @@ end_with_restore_list: above was not called. So we have to check rules again here. */ #ifdef HAVE_REPLICATION - if (thd->slave_thread && - (!rpl_filter->db_ok(lex->name.str) || - !rpl_filter->db_ok_with_wild_table(lex->name.str))) + if (thd->slave_thread) { - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - break; + rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (!rpl_filter->db_ok(lex->name.str) || + !rpl_filter->db_ok_with_wild_table(lex->name.str)) + { + my_message(ER_SLAVE_IGNORED_TABLE, ER_THD(thd, ER_SLAVE_IGNORED_TABLE), MYF(0)); + break; + } } #endif if (check_access(thd, DROP_ACL, lex->name.str, NULL, NULL, 1, 0)) break; - res= mysql_rm_db(thd, lex->name.str, lex->drop_if_exists, 0); + WSREP_TO_ISOLATION_BEGIN(lex->name.str, NULL, NULL) + res= mysql_rm_db(thd, lex->name.str, lex->if_exists()); break; } case SQLCOM_ALTER_DB_UPGRADE: { LEX_STRING *db= & lex->name; #ifdef HAVE_REPLICATION - if (thd->slave_thread && - (!rpl_filter->db_ok(db->str) || - !rpl_filter->db_ok_with_wild_table(db->str))) + if (thd->slave_thread) { - res= 1; - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - break; + rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (!rpl_filter->db_ok(db->str) || + !rpl_filter->db_ok_with_wild_table(db->str)) + { + res= 1; + my_message(ER_SLAVE_IGNORED_TABLE, ER_THD(thd, ER_SLAVE_IGNORED_TABLE), MYF(0)); + break; + } } #endif if (check_db_name(db)) @@ -3407,6 +4503,7 @@ end_with_restore_list: res= 1; break; } + WSREP_TO_ISOLATION_BEGIN(db->str, NULL, NULL) res= mysql_upgrade_db(thd, db); if (!res) my_ok(thd); @@ -3415,7 +4512,6 @@ end_with_restore_list: case SQLCOM_ALTER_DB: { LEX_STRING *db= &lex->name; - HA_CREATE_INFO create_info(lex->create_info); if (check_db_name(db)) { my_error(ER_WRONG_DB_NAME, MYF(0), db->str); @@ -3429,29 +4525,42 @@ end_with_restore_list: above was not called. So we have to check rules again here. */ #ifdef HAVE_REPLICATION - if (thd->slave_thread && - (!rpl_filter->db_ok(db->str) || - !rpl_filter->db_ok_with_wild_table(db->str))) + if (thd->slave_thread) { - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - break; + rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (!rpl_filter->db_ok(db->str) || + !rpl_filter->db_ok_with_wild_table(db->str)) + { + my_message(ER_SLAVE_IGNORED_TABLE, ER_THD(thd, ER_SLAVE_IGNORED_TABLE), MYF(0)); + break; + } } #endif if (check_access(thd, ALTER_ACL, db->str, NULL, NULL, 1, 0)) break; - res= mysql_alter_db(thd, db->str, &create_info); + WSREP_TO_ISOLATION_BEGIN(db->str, NULL, NULL) + res= mysql_alter_db(thd, db->str, &lex->create_info); break; } case SQLCOM_SHOW_CREATE_DB: { + char db_name_buff[NAME_LEN+1]; + LEX_STRING db_name; DBUG_EXECUTE_IF("4x_server_emul", my_error(ER_UNKNOWN_ERROR, MYF(0)); goto error;); - if (check_db_name(&lex->name)) + + db_name.str= db_name_buff; + db_name.length= lex->name.length; + strmov(db_name.str, lex->name.str); + + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); + + if (check_db_name(&db_name)) { - my_error(ER_WRONG_DB_NAME, MYF(0), lex->name.str); + my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str); break; } - res= mysqld_show_create_db(thd, lex->name.str, &lex->create_info); + res= mysqld_show_create_db(thd, &db_name, &lex->name, lex->create_info); break; } case SQLCOM_CREATE_EVENT: @@ -3462,8 +4571,7 @@ end_with_restore_list: DBUG_ASSERT(lex->event_parse_data); if (lex->table_or_sp_used()) { - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored " - "function calls as part of this statement"); + my_error(ER_SUBQUERIES_NOT_SUPPORTED, MYF(0), "CREATE/ALTER EVENT"); break; } @@ -3474,9 +4582,7 @@ end_with_restore_list: switch (lex->sql_command) { case SQLCOM_CREATE_EVENT: { - bool if_not_exists= (lex->create_info.options & - HA_LEX_CREATE_IF_NOT_EXISTS); - res= Events::create_event(thd, lex->event_parse_data, if_not_exists); + res= Events::create_event(thd, lex->event_parse_data); break; } case SQLCOM_ALTER_EVENT: @@ -3501,13 +4607,14 @@ end_with_restore_list: /* lex->unit.cleanup() is called outside, no need to call it here */ break; case SQLCOM_SHOW_CREATE_EVENT: + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); res= Events::show_create_event(thd, lex->spname->m_db, lex->spname->m_name); break; case SQLCOM_DROP_EVENT: if (!(res= Events::drop_event(thd, lex->spname->m_db, lex->spname->m_name, - lex->drop_if_exists))) + lex->if_exists()))) my_ok(thd); break; #else @@ -3516,9 +4623,12 @@ end_with_restore_list: #endif case SQLCOM_CREATE_FUNCTION: // UDF function { - if (check_access(thd, INSERT_ACL, "mysql", NULL, NULL, 1, 0)) + if (check_access(thd, lex->create_info.or_replace() ? + (INSERT_ACL | DELETE_ACL) : INSERT_ACL, + "mysql", NULL, NULL, 1, 0)) break; #ifdef HAVE_DLOPEN + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) if (!(res = mysql_create_function(thd, &lex->udf))) my_ok(thd); #else @@ -3529,22 +4639,30 @@ end_with_restore_list: } #ifndef NO_EMBEDDED_ACCESS_CHECKS case SQLCOM_CREATE_USER: + case SQLCOM_CREATE_ROLE: { - if (check_access(thd, INSERT_ACL, "mysql", NULL, NULL, 1, 1) && + if (check_access(thd, lex->create_info.or_replace() ? + INSERT_ACL | DELETE_ACL : INSERT_ACL, + "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) /* Conditionally writes to binlog */ - if (!(res= mysql_create_user(thd, lex->users_list))) + if (!(res= mysql_create_user(thd, lex->users_list, + lex->sql_command == SQLCOM_CREATE_ROLE))) my_ok(thd); break; } case SQLCOM_DROP_USER: + case SQLCOM_DROP_ROLE: { if (check_access(thd, DELETE_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; /* Conditionally writes to binlog */ - if (!(res= mysql_drop_user(thd, lex->users_list))) + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + if (!(res= mysql_drop_user(thd, lex->users_list, + lex->sql_command == SQLCOM_DROP_ROLE))) my_ok(thd); break; } @@ -3554,6 +4672,7 @@ end_with_restore_list: check_global_access(thd,CREATE_USER_ACL)) break; /* Conditionally writes to binlog */ + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) if (!(res= mysql_rename_user(thd, lex->users_list))) my_ok(thd); break; @@ -3564,10 +4683,8 @@ end_with_restore_list: check_global_access(thd,CREATE_USER_ACL)) break; - /* Replicate current user as grantor */ - thd->binlog_invoker(); - /* Conditionally writes to binlog */ + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) if (!(res = mysql_revoke_all(thd, lex->users_list))) my_ok(thd); break; @@ -3584,42 +4701,35 @@ end_with_restore_list: goto error; /* Replicate current user as grantor */ - thd->binlog_invoker(); + thd->binlog_invoker(false); if (thd->security_ctx->user) // If not replication { - LEX_USER *user, *tmp_user; + LEX_USER *user; bool first_user= TRUE; List_iterator <LEX_USER> user_list(lex->users_list); - while ((tmp_user= user_list++)) + while ((user= user_list++)) { - if (!(user= get_current_user(thd, tmp_user))) - goto error; if (specialflag & SPECIAL_NO_RESOLVE && hostname_requires_resolving(user->host.str)) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_HOSTNAME_WONT_WORK, - ER(ER_WARN_HOSTNAME_WONT_WORK)); - // Are we trying to change a password of another user - DBUG_ASSERT(user->host.str != 0); + ER_THD(thd, ER_WARN_HOSTNAME_WONT_WORK)); /* GRANT/REVOKE PROXY has the target user as a first entry in the list. */ if (lex->type == TYPE_ENUM_PROXY && first_user) { + if (!(user= get_current_user(thd, user)) || !user->host.str) + goto error; + first_user= FALSE; if (acl_check_proxy_grant_access (thd, user->host.str, user->user.str, lex->grant & GRANT_ACL)) goto error; } - else if (is_acl_user(user->host.str, user->user.str) && - user->password.str && - check_change_password (thd, user->host.str, user->user.str, - user->password.str, - user->password.length)) - goto error; } } if (first_table) @@ -3634,6 +4744,7 @@ end_with_restore_list: lex->type == TYPE_ENUM_PROCEDURE, 0)) goto error; /* Conditionally writes to binlog */ + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) res= mysql_routine_grant(thd, all_tables, lex->type == TYPE_ENUM_PROCEDURE, lex->users_list, grants, @@ -3647,6 +4758,7 @@ end_with_restore_list: all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* Conditionally writes to binlog */ + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) res= mysql_table_grant(thd, all_tables, lex->users_list, lex->columns, lex->grant, lex->sql_command == SQLCOM_REVOKE); @@ -3656,16 +4768,17 @@ end_with_restore_list: { if (lex->columns.elements || (lex->type && lex->type != TYPE_ENUM_PROXY)) { - my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE), + my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER_THD(thd, ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0)); goto error; } else { + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) /* Conditionally writes to binlog */ - res = mysql_grant(thd, select_lex->db, lex->users_list, lex->grant, - lex->sql_command == SQLCOM_REVOKE, - lex->type == TYPE_ENUM_PROXY); + res= mysql_grant(thd, select_lex->db, lex->users_list, lex->grant, + lex->sql_command == SQLCOM_REVOKE, + lex->type == TYPE_ENUM_PROXY); } if (!res) { @@ -3684,6 +4797,15 @@ end_with_restore_list: } break; } + case SQLCOM_REVOKE_ROLE: + case SQLCOM_GRANT_ROLE: + { + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + if (!(res= mysql_grant_role(thd, lex->users_list, + lex->sql_command != SQLCOM_GRANT_ROLE))) + my_ok(thd); + break; + } #endif /*!NO_EMBEDDED_ACCESS_CHECKS*/ case SQLCOM_RESET: /* @@ -3698,24 +4820,86 @@ end_with_restore_list: if (check_global_access(thd,RELOAD_ACL)) goto error; - if (first_table && lex->type & REFRESH_READ_LOCK) + if (first_table && lex->type & (REFRESH_READ_LOCK|REFRESH_FOR_EXPORT)) { /* Check table-level privileges. */ if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; + if (flush_tables_with_read_lock(thd, all_tables)) goto error; + my_ok(thd); break; } +#ifdef WITH_WSREP + if (lex->type & ( + REFRESH_GRANT | + REFRESH_HOSTS | +#ifdef HAVE_OPENSSL + REFRESH_DES_KEY_FILE | +#endif + /* + Write all flush log statements except + FLUSH LOGS + FLUSH BINARY LOGS + Check reload_acl_and_cache for why. + */ + REFRESH_RELAY_LOG | + REFRESH_SLOW_LOG | + REFRESH_GENERAL_LOG | + REFRESH_ENGINE_LOG | + REFRESH_ERROR_LOG | +#ifdef HAVE_QUERY_CACHE + REFRESH_QUERY_CACHE_FREE | +#endif /* HAVE_QUERY_CACHE */ + REFRESH_STATUS | + REFRESH_USER_RESOURCES)) + { + WSREP_TO_ISOLATION_BEGIN_WRTCHK(WSREP_MYSQL_DB, NULL, NULL) + } +#endif /* WITH_WSREP*/ + +#ifdef HAVE_REPLICATION + if (lex->type & REFRESH_READ_LOCK) + { + /* + We need to pause any parallel replication slave workers during FLUSH + TABLES WITH READ LOCK. Otherwise we might cause a deadlock, as + worker threads eun run in arbitrary order but need to commit in a + specific given order. + */ + if (rpl_pause_for_ftwrl(thd)) + goto error; + } +#endif /* reload_acl_and_cache() will tell us if we are allowed to write to the binlog or not. */ if (!reload_acl_and_cache(thd, lex->type, first_table, &write_to_binlog)) { +#ifdef WITH_WSREP + if ((lex->type & REFRESH_TABLES) && !(lex->type & (REFRESH_FOR_EXPORT|REFRESH_READ_LOCK))) + { + /* + This is done after reload_acl_and_cache is because + LOCK TABLES is not replicated in galera, the upgrade of which + is checked in reload_acl_and_cache. + Hence, done after/if we are able to upgrade locks. + */ + if (first_table) + { + WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); + } + else + { + WSREP_TO_ISOLATION_BEGIN_WRTCHK(WSREP_MYSQL_DB, NULL, NULL); + } + } +#endif /* WITH_WSREP */ /* We WANT to write and we CAN write. ! we write after unlocking the table. @@ -3735,11 +4919,17 @@ end_with_restore_list: reload_acl_and_cache binlog interactions failed */ res= 1; - } + } if (!res) my_ok(thd); } + else + res= 1; // reload_acl_and_cache failed +#ifdef HAVE_REPLICATION + if (lex->type & REFRESH_READ_LOCK) + rpl_unpause_after_ftwrl(thd); +#endif break; } @@ -3747,39 +4937,47 @@ end_with_restore_list: { if (lex->table_or_sp_used()) { - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored " - "function calls as part of this statement"); + my_error(ER_SUBQUERIES_NOT_SUPPORTED, MYF(0), "KILL"); break; } - if (lex->kill_type == KILL_TYPE_ID) + if (lex->kill_type == KILL_TYPE_ID || lex->kill_type == KILL_TYPE_QUERY) { Item *it= (Item *)lex->value_list.head(); if ((!it->fixed && it->fix_fields(lex->thd, &it)) || it->check_cols(1)) { - my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), + my_message(ER_SET_CONSTANTS_ONLY, ER_THD(thd, ER_SET_CONSTANTS_ONLY), MYF(0)); goto error; } - sql_kill(thd, (ulong) it->val_int(), lex->kill_signal); + sql_kill(thd, it->val_int(), lex->kill_signal, lex->kill_type); } else sql_kill_user(thd, get_current_user(thd, lex->users_list.head()), lex->kill_signal); break; } + case SQLCOM_SHUTDOWN: +#ifndef EMBEDDED_LIBRARY + DBUG_EXECUTE_IF("crash_shutdown", DBUG_SUICIDE();); + if (check_global_access(thd,SHUTDOWN_ACL)) + goto error; + kill_mysql(); + my_ok(thd); +#else + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "embedded server"); +#endif + break; + #ifndef NO_EMBEDDED_ACCESS_CHECKS case SQLCOM_SHOW_GRANTS: { - LEX_USER *grant_user= get_current_user(thd, lex->grant_user); + LEX_USER *grant_user= lex->grant_user; if (!grant_user) goto error; - if ((thd->security_ctx->priv_user && - !strcmp(thd->security_ctx->priv_user, grant_user->user.str)) || - !check_access(thd, SELECT_ACL, "mysql", NULL, NULL, 1, 0)) - { - res = mysql_show_grants(thd, grant_user); - } + + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); + res = mysql_show_grants(thd, grant_user); break; } #endif @@ -3787,6 +4985,9 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; + /* Close temporary tables which were pre-opened for privilege checking. */ + close_thread_tables(thd); + all_tables->table= NULL; res= mysql_ha_open(thd, first_table, 0); break; case SQLCOM_HA_CLOSE: @@ -3801,14 +5002,20 @@ end_with_restore_list: able to open it (with SQLCOM_HA_OPEN) in the first place. */ unit->set_limit(select_lex); + res= mysql_ha_read(thd, first_table, lex->ha_read_mode, lex->ident.str, lex->insert_list, lex->ha_rkey_mode, select_lex->where, unit->select_limit_cnt, unit->offset_limit_cnt); break; case SQLCOM_BEGIN: + DBUG_PRINT("info", ("Executing SQLCOM_BEGIN thd: %p", thd)); if (trans_begin(thd, lex->start_transaction_opt)) + { + thd->mdl_context.release_transactional_locks(); + WSREP_DEBUG("BEGIN failed, MDL released: %lu", thd->thread_id); goto error; + } my_ok(thd); break; case SQLCOM_COMMIT: @@ -3821,27 +5028,45 @@ end_with_restore_list: bool tx_release= (lex->tx_release == TVL_YES || (thd->variables.completion_type == 2 && lex->tx_release != TVL_NO)); - if (trans_commit(thd)) - goto error; + bool commit_failed= trans_commit(thd); thd->mdl_context.release_transactional_locks(); + if (commit_failed) + { + WSREP_DEBUG("COMMIT failed, MDL released: %lu", thd->thread_id); + goto error; + } /* Begin transaction with the same isolation level. */ if (tx_chain) { if (trans_begin(thd)) - goto error; + goto error; } else { - /* Reset the isolation level if no chaining transaction. */ + /* Reset the isolation level and access mode if no chaining transaction.*/ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; } /* Disconnect the current client connection. */ if (tx_release) { - thd->killed= KILL_CONNECTION; + thd->set_killed(KILL_CONNECTION); thd->print_aborted_warning(3, "RELEASE"); } - my_ok(thd); +#ifdef WITH_WSREP + if (WSREP(thd)) { + + if (thd->wsrep_conflict_state == NO_CONFLICT || + thd->wsrep_conflict_state == REPLAYING) + { + my_ok(thd); + } + } else { +#endif /* WITH_WSREP */ + my_ok(thd); +#ifdef WITH_WSREP + } +#endif /* WITH_WSREP */ break; } case SQLCOM_ROLLBACK: @@ -3854,9 +5079,14 @@ end_with_restore_list: bool tx_release= (lex->tx_release == TVL_YES || (thd->variables.completion_type == 2 && lex->tx_release != TVL_NO)); - if (trans_rollback(thd)) - goto error; + bool rollback_failed= trans_rollback(thd); thd->mdl_context.release_transactional_locks(); + + if (rollback_failed) + { + WSREP_DEBUG("rollback failed, MDL released: %lu", thd->thread_id); + goto error; + } /* Begin transaction with the same isolation level. */ if (tx_chain) { @@ -3865,14 +5095,25 @@ end_with_restore_list: } else { - /* Reset the isolation level if no chaining transaction. */ + /* Reset the isolation level and access mode if no chaining transaction.*/ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; } /* Disconnect the current client connection. */ if (tx_release) - thd->killed= KILL_CONNECTION; - my_ok(thd); - break; + thd->set_killed(KILL_CONNECTION); +#ifdef WITH_WSREP + if (WSREP(thd)) { + if (thd->wsrep_conflict_state == NO_CONFLICT) { + my_ok(thd); + } + } else { +#endif /* WITH_WSREP */ + my_ok(thd); +#ifdef WITH_WSREP + } +#endif /* WITH_WSREP */ + break; } case SQLCOM_RELEASE_SAVEPOINT: if (trans_release_savepoint(thd, lex->ident)) @@ -3894,7 +5135,6 @@ end_with_restore_list: { uint namelen; char *name; - int sp_result= SP_INTERNAL_ERROR; DBUG_ASSERT(lex->sphead != 0); DBUG_ASSERT(lex->sphead->m_db.str); /* Must be initialized in the parser */ @@ -3905,23 +5145,21 @@ end_with_restore_list: if (check_db_name(&lex->sphead->m_db)) { my_error(ER_WRONG_DB_NAME, MYF(0), lex->sphead->m_db.str); - goto create_sp_error; - } - - /* - Check that a database directory with this name - exists. Design note: This won't work on virtual databases - like information_schema. - */ - if (check_db_dir_existence(lex->sphead->m_db.str)) - { - my_error(ER_BAD_DB_ERROR, MYF(0), lex->sphead->m_db.str); - goto create_sp_error; + goto error; } if (check_access(thd, CREATE_PROC_ACL, lex->sphead->m_db.str, NULL, NULL, 0, 0)) - goto create_sp_error; + goto error; + + /* Checking the drop permissions if CREATE OR REPLACE is used */ + if (lex->create_info.or_replace()) + { + if (check_routine_access(thd, ALTER_PROC_ACL, lex->spname->m_db.str, + lex->spname->m_name.str, + lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) + goto error; + } name= lex->sphead->name(&namelen); #ifdef HAVE_DLOPEN @@ -3932,17 +5170,17 @@ end_with_restore_list: if (udf) { my_error(ER_UDF_EXISTS, MYF(0), name); - goto create_sp_error; + goto error; } } #endif if (sp_process_definer(thd)) - goto create_sp_error; + goto error; - res= (sp_result= sp_create_routine(thd, lex->sphead->m_type, lex->sphead)); - switch (sp_result) { - case SP_OK: { + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + if (!sp_create_routine(thd, lex->sphead->m_type, lex->sphead)) + { #ifndef NO_EMBEDDED_ACCESS_CHECKS /* only add privileges if really neccessary */ @@ -3992,8 +5230,8 @@ end_with_restore_list: { if (sp_grant_privileges(thd, lex->sphead->m_db.str, name, lex->sql_command == SQLCOM_CREATE_PROCEDURE)) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_PROC_AUTO_GRANT_FAIL, ER(ER_PROC_AUTO_GRANT_FAIL)); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_PROC_AUTO_GRANT_FAIL, ER_THD(thd, ER_PROC_AUTO_GRANT_FAIL)); thd->clear_error(); } @@ -4007,31 +5245,8 @@ end_with_restore_list: } #endif - break; } - case SP_WRITE_ROW_FAILED: - my_error(ER_SP_ALREADY_EXISTS, MYF(0), SP_TYPE_STRING(lex), name); - break; - case SP_BAD_IDENTIFIER: - my_error(ER_TOO_LONG_IDENT, MYF(0), name); - break; - case SP_BODY_TOO_LONG: - my_error(ER_TOO_LONG_BODY, MYF(0), name); - break; - case SP_FLD_STORE_FAILED: - my_error(ER_CANT_CREATE_SROUTINE, MYF(0), name); - break; - default: - my_error(ER_SP_STORE_FAILED, MYF(0), SP_TYPE_STRING(lex), name); - break; - } /* end switch */ - - /* - Capture all errors within this CASE and - clean up the environment. - */ -create_sp_error: - if (sp_result != SP_OK ) + else goto error; my_ok(thd); break; /* break super switch */ @@ -4048,6 +5263,10 @@ create_sp_error: open_and_lock_tables(thd, all_tables, TRUE, 0)) goto error; + if (check_routine_access(thd, EXECUTE_ACL, lex->spname->m_db.str, + lex->spname->m_name.str, TRUE, FALSE)) + goto error; + /* By this moment all needed SPs should be in cache so no need to look into DB. @@ -4061,9 +5280,6 @@ create_sp_error: } else { - ha_rows select_limit; - /* bits that should be cleared in thd->server_status */ - uint bits_to_be_cleared= 0; /* Check that the stored procedure doesn't contain Dynamic SQL and doesn't return result sets: such stored procedures can't @@ -4077,61 +5293,20 @@ create_sp_error: goto error; } - if (sp->m_flags & sp_head::MULTI_RESULTS) - { - if (! (thd->client_capabilities & CLIENT_MULTI_RESULTS)) - { - /* - The client does not support multiple result sets being sent - back - */ - my_error(ER_SP_BADSELECT, MYF(0), sp->m_qname.str); - goto error; - } - } - - /* - If SERVER_MORE_RESULTS_EXISTS is not set, - then remember that it should be cleared - */ - bits_to_be_cleared= (~thd->server_status & - SERVER_MORE_RESULTS_EXISTS); - thd->server_status|= SERVER_MORE_RESULTS_EXISTS; - - if (check_routine_access(thd, EXECUTE_ACL, - sp->m_db.str, sp->m_name.str, TRUE, FALSE)) - { - goto error; - } - select_limit= thd->variables.select_limit; - thd->variables.select_limit= HA_POS_ERROR; - - /* - We never write CALL statements into binlog: - - If the mode is non-prelocked, each statement will be logged - separately. - - If the mode is prelocked, the invoking statement will care - about writing into binlog. - So just execute the statement. - */ - res= sp->execute_procedure(thd, &lex->value_list); - - thd->variables.select_limit= select_limit; - - thd->server_status&= ~bits_to_be_cleared; - - if (!res) - { - my_ok(thd, (thd->get_row_count_func() < 0) ? 0 : thd->get_row_count_func()); - } - else - { - DBUG_ASSERT(thd->is_error() || thd->killed); - goto error; // Substatement should already have sent error - } + if (do_execute_sp(thd, sp)) + goto error; } break; } + + case SQLCOM_COMPOUND: + DBUG_ASSERT(all_tables == 0); + DBUG_ASSERT(thd->in_sub_stmt == 0); + lex->sphead->m_sql_mode= thd->variables.sql_mode; + if (do_execute_sp(thd, lex->sphead)) + goto error; + break; + case SQLCOM_ALTER_PROCEDURE: case SQLCOM_ALTER_FUNCTION: { @@ -4196,10 +5371,10 @@ create_sp_error: if (lex->spname->m_db.str == NULL) { - if (lex->drop_if_exists) + if (lex->if_exists()) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SP_DOES_NOT_EXIST, ER_THD(thd, ER_SP_DOES_NOT_EXIST), "FUNCTION (UDF)", lex->spname->m_name.str); res= FALSE; my_ok(thd); @@ -4223,6 +5398,7 @@ create_sp_error: if (check_routine_access(thd, ALTER_PROC_ACL, db, name, lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) goto error; + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) /* Conditionally writes to binlog */ sp_result= sp_drop_routine(thd, type, lex->spname); @@ -4251,9 +5427,9 @@ create_sp_error: sp_revoke_privileges(thd, db, name, lex->sql_command == SQLCOM_DROP_PROCEDURE)) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_PROC_AUTO_REVOKE_FAIL, - ER(ER_PROC_AUTO_REVOKE_FAIL)); + ER_THD(thd, ER_PROC_AUTO_REVOKE_FAIL)); /* If this happens, an error should have been reported. */ goto error; } @@ -4265,11 +5441,11 @@ create_sp_error: my_ok(thd); break; case SP_KEY_NOT_FOUND: - if (lex->drop_if_exists) + if (lex->if_exists()) { res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SP_DOES_NOT_EXIST, ER_THD(thd, ER_SP_DOES_NOT_EXIST), SP_COM_STRING(lex), lex->spname->m_qname.str); if (!res) my_ok(thd); @@ -4287,12 +5463,14 @@ create_sp_error: } case SQLCOM_SHOW_CREATE_PROC: { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); if (sp_show_create_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname)) goto error; break; } case SQLCOM_SHOW_CREATE_FUNC: { + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); if (sp_show_create_routine(thd, TYPE_ENUM_FUNCTION, lex->spname)) goto error; break; @@ -4305,6 +5483,7 @@ create_sp_error: stored_procedure_type type= (lex->sql_command == SQLCOM_SHOW_PROC_CODE ? TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); if (sp_cache_routine(thd, type, lex->spname, FALSE, &sp)) goto error; if (!sp || sp->show_routine_code(thd)) @@ -4326,6 +5505,7 @@ create_sp_error: if (check_ident_length(&lex->spname->m_name)) goto error; + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); if (show_create_trigger(thd, lex->spname)) goto error; /* Error has been already logged. */ @@ -4345,6 +5525,7 @@ create_sp_error: if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* Conditionally writes to binlog. */ + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) res= mysql_drop_view(thd, first_table, thd->lex->drop_mode); break; } @@ -4377,27 +5558,41 @@ create_sp_error: my_ok(thd); break; case SQLCOM_XA_COMMIT: - if (trans_xa_commit(thd)) - goto error; + { + bool commit_failed= trans_xa_commit(thd); thd->mdl_context.release_transactional_locks(); + if (commit_failed) + { + WSREP_DEBUG("XA commit failed, MDL released: %lu", thd->thread_id); + goto error; + } /* We've just done a commit, reset transaction - isolation level to the session default. + isolation level and access mode to the session default. */ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; my_ok(thd); break; + } case SQLCOM_XA_ROLLBACK: - if (trans_xa_rollback(thd)) - goto error; + { + bool rollback_failed= trans_xa_rollback(thd); thd->mdl_context.release_transactional_locks(); + if (rollback_failed) + { + WSREP_DEBUG("XA rollback failed, MDL released: %lu", thd->thread_id); + goto error; + } /* We've just done a rollback, reset transaction - isolation level to the session default. + isolation level and access mode to the session default. */ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; my_ok(thd); break; + } case SQLCOM_XA_RECOVER: res= mysql_xa_recover(thd); break; @@ -4428,37 +5623,31 @@ create_sp_error: } case SQLCOM_CREATE_SERVER: { - int error; - LEX *lex= thd->lex; DBUG_PRINT("info", ("case SQLCOM_CREATE_SERVER")); if (check_global_access(thd, SUPER_ACL)) break; - if ((error= create_server(thd, &lex->server_options))) - { - DBUG_PRINT("info", ("problem creating server <%s>", - lex->server_options.server_name)); - my_error(error, MYF(0), lex->server_options.server_name); - break; - } - my_ok(thd, 1); + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + + res= create_server(thd, &lex->server_options); break; } case SQLCOM_ALTER_SERVER: { int error; - LEX *lex= thd->lex; DBUG_PRINT("info", ("case SQLCOM_ALTER_SERVER")); if (check_global_access(thd, SUPER_ACL)) break; + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + if ((error= alter_server(thd, &lex->server_options))) { DBUG_PRINT("info", ("problem altering server <%s>", - lex->server_options.server_name)); - my_error(error, MYF(0), lex->server_options.server_name); + lex->server_options.server_name.str)); + my_error(error, MYF(0), lex->server_options.server_name.str); break; } my_ok(thd, 1); @@ -4467,19 +5656,20 @@ create_sp_error: case SQLCOM_DROP_SERVER: { int err_code; - LEX *lex= thd->lex; DBUG_PRINT("info", ("case SQLCOM_DROP_SERVER")); if (check_global_access(thd, SUPER_ACL)) break; + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + if ((err_code= drop_server(thd, &lex->server_options))) { - if (! lex->drop_if_exists && err_code == ER_FOREIGN_SERVER_DOESNT_EXIST) + if (! lex->if_exists() && err_code == ER_FOREIGN_SERVER_DOESNT_EXIST) { DBUG_PRINT("info", ("problem dropping server %s", - lex->server_options.server_name)); - my_error(err_code, MYF(0), lex->server_options.server_name); + lex->server_options.server_name.str)); + my_error(err_code, MYF(0), lex->server_options.server_name.str); } else { @@ -4496,49 +5686,44 @@ create_sp_error: case SQLCOM_REPAIR: case SQLCOM_TRUNCATE: case SQLCOM_ALTER_TABLE: - thd->query_plan_flags|= QPLAN_ADMIN; DBUG_ASSERT(first_table == all_tables && first_table != 0); /* fall through */ case SQLCOM_SIGNAL: case SQLCOM_RESIGNAL: - DBUG_ASSERT(lex->m_stmt != NULL); - res= lex->m_stmt->execute(thd); + case SQLCOM_GET_DIAGNOSTICS: + DBUG_ASSERT(lex->m_sql_cmd != NULL); + res= lex->m_sql_cmd->execute(thd); break; default: + #ifndef EMBEDDED_LIBRARY DBUG_ASSERT(0); /* Impossible */ #endif my_ok(thd); break; } - thd_proc_info(thd, "query end"); + THD_STAGE_INFO(thd, stage_query_end); thd->update_stats(); - /* - Binlog-related cleanup: - Reset system variables temporarily modified by SET ONE SHOT. - - Exception: If this is a SET, do nothing. This is to allow - mysqlbinlog to print many SET commands (in this case we want the - charset temp setting to live until the real query). This is also - needed so that SET CHARACTER_SET_CLIENT... does not cancel itself - immediately. - */ - if (thd->one_shot_set && lex->sql_command != SQLCOM_SET_OPTION) - reset_one_shot_variables(thd); - goto finish; error: +WSREP_ERROR_LABEL: res= TRUE; finish: + thd->reset_query_timer(); DBUG_ASSERT(!thd->in_active_multi_stmt_transaction() || thd->in_multi_stmt_transaction_mode()); + lex->unit.cleanup(); + /* close/reopen tables that were marked to need reopen under LOCK TABLES */ + if (! thd->lex->requires_prelocking()) + thd->locked_tables_list.reopen_tables(thd, true); + if (! thd->in_sub_stmt) { if (thd->killed != NOT_KILLED) @@ -4547,23 +5732,38 @@ finish: if (thd->killed_errno()) { /* If we already sent 'ok', we can ignore any kill query statements */ - if (! thd->stmt_da->is_set()) + if (! thd->get_stmt_da()->is_set()) thd->send_kill_message(); } - if (thd->killed < KILL_CONNECTION) - { - thd->reset_killed(); - thd->mysys_var->abort= 0; - } + thd->reset_kill_query(); } if (thd->is_error() || (thd->variables.option_bits & OPTION_MASTER_SQL_ERROR)) trans_rollback_stmt(thd); +#ifdef WITH_WSREP + if (thd->spcont && + (thd->wsrep_conflict_state == MUST_ABORT || + thd->wsrep_conflict_state == ABORTED || + thd->wsrep_conflict_state == CERT_FAILURE)) + { + /* + The error was cleared, but THD was aborted by wsrep and + wsrep_conflict_state is still set accordingly. This + situation is expected if we are running a stored procedure + that declares a handler that catches ER_LOCK_DEADLOCK error. + In which case the error may have been cleared in method + sp_rcontext::handle_sql_condition(). + */ + trans_rollback_stmt(thd); + thd->wsrep_conflict_state= NO_CONFLICT; + thd->killed= NOT_KILLED; + } +#endif /* WITH_WSREP */ else { /* If commit fails, we should be able to reset the OK status. */ - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); trans_commit_stmt(thd); - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); } #ifdef WITH_ARIA_STORAGE_ENGINE ha_maria::implicit_commit(thd, FALSE); @@ -4572,12 +5772,19 @@ finish: /* Free tables */ close_thread_tables(thd); - thd_proc_info(thd, 0); +#ifdef WITH_WSREP + thd->wsrep_consistency_check= NO_CONSISTENCY_CHECK; +#endif /* WITH_WSREP */ #ifndef DBUG_OFF if (lex->sql_command != SQLCOM_SET_OPTION && ! thd->in_sub_stmt) DEBUG_SYNC(thd, "execute_command_after_close_tables"); #endif + if (!(sql_command_flags[lex->sql_command] & + (CF_CAN_GENERATE_ROW_EVENTS | CF_FORCE_ORIGINAL_BINLOG_FORMAT | + CF_STATUS_COMMAND))) + thd->set_binlog_format(orig_binlog_format, + orig_current_stmt_binlog_format); if (! thd->in_sub_stmt && thd->transaction_rollback_request) { @@ -4593,12 +5800,15 @@ finish: { /* No transaction control allowed in sub-statements. */ DBUG_ASSERT(! thd->in_sub_stmt); - /* If commit fails, we should be able to reset the OK status. */ - thd->stmt_da->can_overwrite_status= TRUE; - /* Commit the normal transaction if one is active. */ - trans_commit_implicit(thd); - thd->stmt_da->can_overwrite_status= FALSE; - thd->mdl_context.release_transactional_locks(); + if (!(thd->variables.option_bits & OPTION_GTID_BEGIN)) + { + /* If commit fails, we should be able to reset the OK status. */ + thd->get_stmt_da()->set_overwrite_status(true); + /* Commit the normal transaction if one is active. */ + trans_commit_implicit(thd); + thd->get_stmt_da()->set_overwrite_status(false); + thd->mdl_context.release_transactional_locks(); + } } else if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode()) { @@ -4618,6 +5828,22 @@ finish: { thd->mdl_context.release_statement_locks(); } + WSREP_TO_ISOLATION_END; + +#ifdef WITH_WSREP + /* + Force release of transactional locks if not in active MST and wsrep is on. + */ + if (WSREP(thd) && + ! thd->in_sub_stmt && + ! thd->in_active_multi_stmt_transaction() && + thd->mdl_context.has_transactional_locks()) + { + WSREP_DEBUG("Forcing release of transactional locks for thd %lu", + thd->thread_id); + thd->mdl_context.release_transactional_locks(); + } +#endif /* WITH_WSREP */ DBUG_RETURN(res || thd->is_error()); } @@ -4630,10 +5856,11 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) bool res; /* assign global limit variable if limit is not given */ { - SELECT_LEX *param= lex->unit.global_parameters; + SELECT_LEX *param= lex->unit.global_parameters(); if (!param->explicit_limit) param->select_limit= - new Item_int((ulonglong) thd->variables.select_limit); + new (thd->mem_root) Item_int(thd, + (ulonglong) thd->variables.select_limit); } if (!(res= open_and_lock_tables(thd, all_tables, TRUE, 0))) { @@ -4645,27 +5872,48 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) to prepend EXPLAIN to any query and receive output for it, even if the query itself redirects the output. */ - if (!(result= new select_send())) + if (!(result= new (thd->mem_root) select_send(thd))) return 1; /* purecov: inspected */ - thd->send_explain_fields(result); - res= mysql_explain_union(thd, &thd->lex->unit, result); + thd->send_explain_fields(result, lex->describe, lex->analyze_stmt); + /* - The code which prints the extended description is not robust - against malformed queries, so skip it if we have an error. + This will call optimize() for all parts of query. The query plan is + printed out below. */ - if (!res && (lex->describe & DESCRIBE_EXTENDED)) + res= mysql_explain_union(thd, &lex->unit, result); + + /* Print EXPLAIN only if we don't have an error */ + if (!res) { - char buff[1024]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - str.length(0); - /* - The warnings system requires input in utf8, @see - mysqld_show_warnings(). - */ - thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_YES, str.c_ptr_safe()); + /* + Do like the original select_describe did: remove OFFSET from the + top-level LIMIT + */ + result->reset_offset_limit(); + if (lex->explain_json) + { + lex->explain->print_explain_json(result, lex->analyze_stmt); + } + else + { + lex->explain->print_explain(result, thd->lex->describe, + thd->lex->analyze_stmt); + if (lex->describe & DESCRIBE_EXTENDED) + { + char buff[1024]; + String str(buff,(uint32) sizeof(buff), system_charset_info); + str.length(0); + /* + The warnings system requires input in utf8, @see + mysqld_show_warnings(). + */ + lex->unit.print(&str, QT_EXPLAIN_EXTENDED); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_YES, str.c_ptr_safe()); + } + } } + if (res) result->abort_result_set(); else @@ -4674,19 +5922,47 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) } else { - if (!result && !(result= new select_send())) - return 1; /* purecov: inspected */ + Protocol *save_protocol= NULL; + if (lex->analyze_stmt) + { + if (result && result->result_interceptor()) + result->result_interceptor()->disable_my_ok_calls(); + else + { + DBUG_ASSERT(thd->protocol); + result= new (thd->mem_root) select_send_analyze(thd); + save_protocol= thd->protocol; + thd->protocol= new Protocol_discard(thd); + } + } + else + { + if (!result && !(result= new (thd->mem_root) select_send(thd))) + return 1; /* purecov: inspected */ + } query_cache_store_query(thd, all_tables); res= handle_select(thd, lex, result, 0); if (result != lex->result) delete result; + + if (lex->analyze_stmt) + { + if (save_protocol) + { + delete thd->protocol; + thd->protocol= save_protocol; + } + if (!res) + res= thd->lex->explain->send_explain(thd); + } } } /* Count number of empty select queries */ - if (!thd->sent_row_count) + if (!thd->get_sent_row_count()) status_var_increment(thd->status_var.empty_queries); else - status_var_add(thd->status_var.rows_sent, thd->sent_row_count); + status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count()); + return res; } @@ -4699,6 +5975,7 @@ static bool execute_show_status(THD *thd, TABLE_LIST *all_tables) if (!(res= check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE))) res= execute_sqlcom_select(thd, all_tables); + /* Don't log SHOW STATUS commands to slow query log */ thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED); @@ -4709,14 +5986,69 @@ static bool execute_show_status(THD *thd, TABLE_LIST *all_tables) mysql_mutex_lock(&LOCK_status); add_diff_to_status(&global_status_var, &thd->status_var, &old_status_var); - thd->status_var= old_status_var; + memcpy(&thd->status_var, &old_status_var, + offsetof(STATUS_VAR, last_cleared_system_status_var)); mysql_mutex_unlock(&LOCK_status); return res; } -static bool execute_rename_table(THD *thd, TABLE_LIST *first_table, - TABLE_LIST *all_tables) +/* + Find out if a table is a temporary table + + A table is a temporary table if it's a temporary table or + there has been before a temporary table that has been renamed + to the current name. + + Some examples: + A->B B is a temporary table if and only if A is a temp. + A->B, B->C Second B is temp if A is temp + A->B, A->C Second A can't be temp as if A was temp then B is temp + and Second A can only be a normal table. C is also not temp +*/ + +static TABLE *find_temporary_table_for_rename(THD *thd, + TABLE_LIST *first_table, + TABLE_LIST *cur_table) +{ + TABLE_LIST *table; + TABLE *res= 0; + bool found= 0; + DBUG_ENTER("find_temporary_table_for_rename"); + + /* Find last instance when cur_table is in TO part */ + for (table= first_table; + table != cur_table; + table= table->next_local->next_local) + { + TABLE_LIST *next= table->next_local; + + if (!strcmp(table->get_db_name(), cur_table->get_db_name()) && + !strcmp(table->get_table_name(), cur_table->get_table_name())) + { + /* Table was moved away, can't be same as 'table' */ + found= 1; + res= 0; // Table can't be a temporary table + } + if (!strcmp(next->get_db_name(), cur_table->get_db_name()) && + !strcmp(next->get_table_name(), cur_table->get_table_name())) + { + /* + Table has matching name with new name of this table. cur_table should + have same temporary type as this table. + */ + found= 1; + res= table->table; + } + } + if (!found) + res= find_temporary_table(thd, table); + DBUG_RETURN(res); +} + + +static bool check_rename_table(THD *thd, TABLE_LIST *first_table, + TABLE_LIST *all_tables) { DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *table; @@ -4731,13 +6063,19 @@ static bool execute_rename_table(THD *thd, TABLE_LIST *first_table, &table->next_local->grant.m_internal, 0, 0)) return 1; + + /* check if these are referring to temporary tables */ + table->table= find_temporary_table_for_rename(thd, first_table, table); + table->next_local->table= table->table; + TABLE_LIST old_list, new_list; /* we do not need initialize old_list and new_list because we will - come table[0] and table->next[0] there + copy table[0] and table->next[0] there */ old_list= table[0]; new_list= table->next_local[0]; + if (check_grant(thd, ALTER_ACL | DROP_ACL, &old_list, FALSE, 1, FALSE) || (!test_all_bits(table->next_local->grant.privilege, INSERT_ACL | CREATE_ACL) && @@ -4746,100 +6084,6 @@ static bool execute_rename_table(THD *thd, TABLE_LIST *first_table, return 1; } - return mysql_rename_tables(thd, first_table, 0); -} - - -#ifndef NO_EMBEDDED_ACCESS_CHECKS -/** - Check grants for commands which work only with one table. - - @param thd Thread handler - @param privilege requested privilege - @param all_tables global table list of query - @param no_errors FALSE/TRUE - report/don't report error to - the client (using my_error() call). - - @retval - 0 OK - @retval - 1 access denied, error is sent to client -*/ - -bool check_single_table_access(THD *thd, ulong privilege, - TABLE_LIST *all_tables, bool no_errors) -{ - Security_context * backup_ctx= thd->security_ctx; - - /* we need to switch to the saved context (if any) */ - if (all_tables->security_ctx) - thd->security_ctx= all_tables->security_ctx; - - const char *db_name; - if ((all_tables->view || all_tables->field_translation) && - !all_tables->schema_table) - db_name= all_tables->view_db.str; - else - db_name= all_tables->db; - - if (check_access(thd, privilege, db_name, - &all_tables->grant.privilege, - &all_tables->grant.m_internal, - 0, no_errors)) - goto deny; - - /* Show only 1 table for check_grant */ - if (!(all_tables->belong_to_view && - (thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) && - check_grant(thd, privilege, all_tables, FALSE, 1, no_errors)) - goto deny; - - thd->security_ctx= backup_ctx; - return 0; - -deny: - thd->security_ctx= backup_ctx; - return 1; -} - -/** - Check grants for commands which work only with one table and all other - tables belonging to subselects or implicitly opened tables. - - @param thd Thread handler - @param privilege requested privilege - @param all_tables global table list of query - - @retval - 0 OK - @retval - 1 access denied, error is sent to client -*/ - -bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables) -{ - if (check_single_table_access (thd,privilege,all_tables, FALSE)) - return 1; - - /* Check rights on tables of subselects and implictly opened tables */ - TABLE_LIST *subselects_tables, *view= all_tables->view ? all_tables : 0; - if ((subselects_tables= all_tables->next_global)) - { - /* - Access rights asked for the first table of a view should be the same - as for the view - */ - if (view && subselects_tables->belong_to_view == view) - { - if (check_single_table_access (thd, privilege, subselects_tables, FALSE)) - return 1; - subselects_tables= subselects_tables->next_global; - } - if (subselects_tables && - (check_table_access(thd, SELECT_ACL, subselects_tables, FALSE, - UINT_MAX, FALSE))) - return 1; - } return 0; } @@ -4876,6 +6120,11 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, GRANT_INTERNAL_INFO *grant_internal_info, bool dont_check_global_grants, bool no_errors) { +#ifdef NO_EMBEDDED_ACCESS_CHECKS + if (save_priv) + *save_priv= GLOBAL_ACLS; + return false; +#else Security_context *sctx= thd->security_ctx; ulong db_access; @@ -4902,18 +6151,22 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, dummy= 0; } - thd_proc_info(thd, "checking permissions"); + THD_STAGE_INFO(thd, stage_checking_permissions); if ((!db || !db[0]) && !thd->db && !dont_check_global_grants) { DBUG_PRINT("error",("No database")); if (!no_errors) - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), + my_message(ER_NO_DB_ERROR, ER_THD(thd, ER_NO_DB_ERROR), MYF(0)); /* purecov: tested */ DBUG_RETURN(TRUE); /* purecov: tested */ } if ((db != NULL) && (db != any_db)) { + /* + Check if this is reserved database, like information schema or + performance schema + */ const ACL_internal_schema_access *access; access= get_cached_schema_access(grant_internal_info, db); if (access) @@ -4956,8 +6209,12 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, if (!(sctx->master_access & SELECT_ACL)) { if (db && (!thd->db || db_is_pattern || strcmp(db, thd->db))) + { db_access= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, db_is_pattern); + if (sctx->priv_role[0]) + db_access|= acl_get("", "", sctx->priv_role, db, db_is_pattern); + } else { /* get access for current db */ @@ -4985,8 +6242,8 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, sctx->priv_user, sctx->priv_host, (thd->password ? - ER(ER_YES) : - ER(ER_NO))); /* purecov: tested */ + ER_THD(thd, ER_YES) : + ER_THD(thd, ER_NO))); /* purecov: tested */ } DBUG_RETURN(TRUE); /* purecov: tested */ } @@ -5001,8 +6258,14 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, } if (db && (!thd->db || db_is_pattern || strcmp(db,thd->db))) + { db_access= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, db_is_pattern); + if (sctx->priv_role[0]) + { + db_access|= acl_get("", "", sctx->priv_role, db, db_is_pattern); + } + } else db_access= sctx->db_access; DBUG_PRINT("info",("db_access: %lu want_access: %lu", @@ -5054,6 +6317,101 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, "unknown"))); } DBUG_RETURN(TRUE); +#endif // NO_EMBEDDED_ACCESS_CHECKS +} + + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +/** + Check grants for commands which work only with one table. + + @param thd Thread handler + @param privilege requested privilege + @param all_tables global table list of query + @param no_errors FALSE/TRUE - report/don't report error to + the client (using my_error() call). + + @retval + 0 OK + @retval + 1 access denied, error is sent to client +*/ + +bool check_single_table_access(THD *thd, ulong privilege, + TABLE_LIST *all_tables, bool no_errors) +{ + Security_context * backup_ctx= thd->security_ctx; + + /* we need to switch to the saved context (if any) */ + if (all_tables->security_ctx) + thd->security_ctx= all_tables->security_ctx; + + const char *db_name; + if ((all_tables->view || all_tables->field_translation) && + !all_tables->schema_table) + db_name= all_tables->view_db.str; + else + db_name= all_tables->db; + + if (check_access(thd, privilege, db_name, + &all_tables->grant.privilege, + &all_tables->grant.m_internal, + 0, no_errors)) + goto deny; + + /* Show only 1 table for check_grant */ + if (!(all_tables->belong_to_view && + (thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) && + check_grant(thd, privilege, all_tables, FALSE, 1, no_errors)) + goto deny; + + thd->security_ctx= backup_ctx; + return 0; + +deny: + thd->security_ctx= backup_ctx; + return 1; +} + +/** + Check grants for commands which work only with one table and all other + tables belonging to subselects or implicitly opened tables. + + @param thd Thread handler + @param privilege requested privilege + @param all_tables global table list of query + + @retval + 0 OK + @retval + 1 access denied, error is sent to client +*/ + +bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables) +{ + if (check_single_table_access (thd,privilege,all_tables, FALSE)) + return 1; + + /* Check rights on tables of subselects and implictly opened tables */ + TABLE_LIST *subselects_tables, *view= all_tables->view ? all_tables : 0; + if ((subselects_tables= all_tables->next_global)) + { + /* + Access rights asked for the first table of a view should be the same + as for the view + */ + if (view && subselects_tables->belong_to_view == view) + { + if (check_single_table_access (thd, privilege, subselects_tables, FALSE)) + return 1; + subselects_tables= subselects_tables->next_global; + } + if (subselects_tables && + (check_table_access(thd, SELECT_ACL, subselects_tables, FALSE, + UINT_MAX, FALSE))) + return 1; + } + return 0; } @@ -5111,6 +6469,12 @@ static bool check_show_access(THD *thd, TABLE_LIST *table) DBUG_ASSERT(dst_table); + /* + Open temporary tables to be able to detect them during privilege check. + */ + if (open_temporary_tables(thd, dst_table)) + return TRUE; + if (check_access(thd, SELECT_ACL, dst_table->db, &dst_table->grant.privilege, &dst_table->grant.m_internal, @@ -5121,9 +6485,12 @@ static bool check_show_access(THD *thd, TABLE_LIST *table) Check_grant will grant access if there is any column privileges on all of the tables thanks to the fourth parameter (bool show_table). */ - if (check_grant(thd, SELECT_ACL, dst_table, TRUE, UINT_MAX, FALSE)) + if (check_grant(thd, SELECT_ACL, dst_table, TRUE, 1, FALSE)) return TRUE; /* Access denied */ + close_thread_tables(thd); + dst_table->table= NULL; + /* Access granted */ return FALSE; } @@ -5212,10 +6579,10 @@ check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables, DBUG_PRINT("info", ("derived: %d view: %d", table_ref->derived != 0, table_ref->view != 0)); - if (table_ref->is_anonymous_derived_table() || - (table_ref->table && table_ref->table->s && - (int)table_ref->table->s->tmp_table)) + + if (table_ref->is_anonymous_derived_table()) continue; + thd->security_ctx= sctx; if (check_access(thd, want_access, table_ref->get_db_name(), @@ -5409,8 +6776,8 @@ bool check_fk_parent_table_access(THD *thd, bool is_qualified_table_name; Foreign_key *fk_key= (Foreign_key *)key; LEX_STRING db_name; - LEX_STRING table_name= { fk_key->ref_table->table.str, - fk_key->ref_table->table.length }; + LEX_STRING table_name= { fk_key->ref_table.str, + fk_key->ref_table.length }; const ulong privileges= (SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | REFERENCES_ACL); @@ -5422,15 +6789,15 @@ bool check_fk_parent_table_access(THD *thd, return true; } - if (fk_key->ref_table->db.str) + if (fk_key->ref_db.str) { is_qualified_table_name= true; - db_name.str= (char *) thd->memdup(fk_key->ref_table->db.str, - fk_key->ref_table->db.length+1); - db_name.length= fk_key->ref_table->db.length; + db_name.str= (char *) thd->memdup(fk_key->ref_db.str, + fk_key->ref_db.length+1); + db_name.length= fk_key->ref_db.length; // Check if database name is valid or not. - if (fk_key->ref_table->db.str && check_db_name(&db_name)) + if (fk_key->ref_db.str && check_db_name(&db_name)) { my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str); return true; @@ -5462,9 +6829,10 @@ bool check_fk_parent_table_access(THD *thd, // if lower_case_table_names is set then convert tablename to lower case. if (lower_case_table_names) { - table_name.str= (char *) thd->memdup(fk_key->ref_table->table.str, - fk_key->ref_table->table.length+1); + table_name.str= (char *) thd->memdup(fk_key->ref_table.str, + fk_key->ref_table.length+1); table_name.length= my_casedn_str(files_charset_info, table_name.str); + db_name.length = my_casedn_str(files_charset_info, db_name.str); } parent_table.init_one_table(db_name.str, db_name.length, @@ -5511,12 +6879,6 @@ bool check_fk_parent_table_access(THD *thd, ****************************************************************************/ -#if STACK_DIRECTION < 0 -#define used_stack(A,B) (long) (A - B) -#else -#define used_stack(A,B) (long) (B - A) -#endif - #ifndef DBUG_OFF long max_stack_used; #endif @@ -5533,16 +6895,17 @@ bool check_stack_overrun(THD *thd, long margin, { long stack_used; DBUG_ASSERT(thd == current_thd); - if ((stack_used=used_stack(thd->thread_stack,(char*) &stack_used)) >= + if ((stack_used= available_stack_size(thd->thread_stack, &stack_used)) >= (long) (my_thread_stack_size - margin)) { + thd->is_fatal_error= 1; /* Do not use stack for the message buffer to ensure correct behaviour in cases we have close to no stack left. */ char* ebuff= new char[MYSQL_ERRMSG_SIZE]; if (ebuff) { - my_snprintf(ebuff, MYSQL_ERRMSG_SIZE, ER(ER_STACK_OVERRUN_NEED_MORE), + my_snprintf(ebuff, MYSQL_ERRMSG_SIZE, ER_THD(thd, ER_STACK_OVERRUN_NEED_MORE), stack_used, my_thread_stack_size, margin); my_message(ER_STACK_OVERRUN_NEED_MORE, ebuff, MYF(ME_FATALERROR)); delete [] ebuff; @@ -5550,7 +6913,7 @@ bool check_stack_overrun(THD *thd, long margin, return 1; } #ifndef DBUG_OFF - max_stack_used= max(max_stack_used, stack_used); + max_stack_used= MY_MAX(max_stack_used, stack_used); #endif return 0; } @@ -5598,29 +6961,33 @@ bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, ulong *yystacksize) Reset the part of THD responsible for the state of command processing. + @param do_clear_error Set if we should clear errors + This needs to be called before execution of every statement (prepared or conventional). It is not called by substatements of routines. - @todo Remove mysql_reset_thd_for_next_command and only use the - member function. - @todo Call it after we use THD for queries, not before. */ -void mysql_reset_thd_for_next_command(THD *thd) -{ - thd->reset_for_next_command(); -} -void THD::reset_for_next_command() +void THD::reset_for_next_command(bool do_clear_error) { THD *thd= this; DBUG_ENTER("THD::reset_for_next_command"); DBUG_ASSERT(!thd->spcont); /* not for substatements of routines */ DBUG_ASSERT(! thd->in_sub_stmt); - DBUG_ASSERT(thd->transaction.on); + + if (do_clear_error) + clear_error(1); + thd->free_list= 0; - thd->select_number= 1; + /* + We also assign thd->stmt_lex in lex_start(), but during bootstrap this + code is executed first. + */ + DBUG_ASSERT(lex == &main_lex); + main_lex.stmt_lex= &main_lex; main_lex.current_select_number= 1; + DBUG_PRINT("info", ("Lex and stmt_lex: %p", &main_lex)); /* Those two lines below are theoretically unneeded as THD::cleanup_after_query() should take care of this already. @@ -5628,9 +6995,29 @@ void THD::reset_for_next_command() thd->auto_inc_intervals_in_cur_stmt_for_binlog.empty(); thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0; +#ifdef WITH_WSREP + /* + Autoinc variables should be adjusted only for locally executed + transactions. Appliers and replayers are either processing ROW + events or get autoinc variable values from Query_log_event and + mysql slave may be processing STATEMENT format events, but he should + use autoinc values passed in binlog events, not the values forced by + the cluster. + */ + if (WSREP(thd) && thd->wsrep_exec_mode == LOCAL_STATE && + !thd->slave_thread && wsrep_auto_increment_control) + { + thd->variables.auto_increment_offset= + global_system_variables.auto_increment_offset; + thd->variables.auto_increment_increment= + global_system_variables.auto_increment_increment; + } +#endif /* WITH_WSREP */ thd->query_start_used= 0; thd->query_start_sec_part_used= 0; thd->is_fatal_error= thd->time_zone_used= 0; + thd->log_current_statement= 0; + /* Clear the status flag that are expected to be cleared at the beginning of each SQL statement. @@ -5644,7 +7031,7 @@ void THD::reset_for_next_command() if (!thd->in_multi_stmt_transaction_mode()) { thd->variables.option_bits&= ~OPTION_KEEP_LOG; - thd->transaction.all.modified_non_trans_table= FALSE; + thd->transaction.all.reset(); } DBUG_ASSERT(thd->security_ctx== &thd->main_security_ctx); thd->thread_specific_used= FALSE; @@ -5654,11 +7041,9 @@ void THD::reset_for_next_command() reset_dynamic(&thd->user_var_events); thd->user_var_events_alloc= thd->mem_root; } - thd->clear_error(); - thd->stmt_da->reset_diagnostics_area(); - thd->warning_info->reset_for_next_command(); + thd->get_stmt_da()->reset_for_next_command(); thd->rand_used= 0; - thd->sent_row_count= thd->examined_row_count= 0; + thd->m_sent_row_count= thd->m_examined_row_count= 0; thd->accessed_rows_and_keys= 0; thd->query_plan_flags= QPLAN_INIT; @@ -5720,7 +7105,7 @@ mysql_new_select(LEX *lex, bool move_down) if (!(select_lex= new (thd->mem_root) SELECT_LEX())) DBUG_RETURN(1); - select_lex->select_number= ++thd->select_number; + select_lex->select_number= ++thd->lex->stmt_lex->current_select_number; select_lex->parent_lex= lex; /* Used in init_query. */ select_lex->init_query(); select_lex->init_select(); @@ -5756,11 +7141,28 @@ mysql_new_select(LEX *lex, bool move_down) } else { + bool const outer_most= (lex->current_select->master_unit() == &lex->unit); + if (outer_most && lex->result) + { + my_error(ER_WRONG_USAGE, MYF(0), "UNION", "INTO"); + DBUG_RETURN(TRUE); + } + if (lex->proc_list.elements!=0) + { + my_error(ER_WRONG_USAGE, MYF(0), "UNION", + "SELECT ... PROCEDURE ANALYSE()"); + DBUG_RETURN(TRUE); + } if (lex->current_select->order_list.first && !lex->current_select->braces) { my_error(ER_WRONG_USAGE, MYF(0), "UNION", "ORDER BY"); DBUG_RETURN(1); } + if (lex->current_select->explicit_limit && !lex->current_select->braces) + { + my_error(ER_WRONG_USAGE, MYF(0), "UNION", "LIMIT"); + DBUG_RETURN(1); + } select_lex->include_neighbour(lex->current_select); SELECT_LEX_UNIT *unit= select_lex->master_unit(); if (!unit->fake_select_lex && unit->add_fake_select_lex(lex->thd)) @@ -5769,7 +7171,6 @@ mysql_new_select(LEX *lex, bool move_down) unit->first_select()->context.outer_context; } - select_lex->master_unit()->global_parameters= select_lex; select_lex->include_global((st_select_lex_node**)&lex->all_selects_list); lex->current_select= select_lex; /* @@ -5794,7 +7195,7 @@ void create_select_for_variable(const char *var_name) { THD *thd; LEX *lex; - LEX_STRING tmp, null_lex_string; + LEX_STRING tmp; Item *var; char buff[MAX_SYS_VAR_LENGTH*2+4+8], *end; DBUG_ENTER("create_select_for_variable"); @@ -5805,12 +7206,11 @@ void create_select_for_variable(const char *var_name) lex->sql_command= SQLCOM_SELECT; tmp.str= (char*) var_name; tmp.length=strlen(var_name); - bzero((char*) &null_lex_string.str, sizeof(null_lex_string)); /* We set the name of Item to @@session.var_name because that then is used as the column name in the output. */ - if ((var= get_system_var(thd, OPT_SESSION, tmp, null_lex_string))) + if ((var= get_system_var(thd, OPT_SESSION, tmp, null_lex_str))) { end= strxmov(buff, "@@session.", var_name, NullS); var->set_name(buff, end-buff, system_charset_info); @@ -5831,6 +7231,140 @@ void mysql_init_multi_delete(LEX *lex) lex->query_tables_last= &lex->query_tables; } +static void wsrep_mysql_parse(THD *thd, char *rawbuf, uint length, + Parser_state *parser_state) +{ +#ifdef WITH_WSREP + bool is_autocommit= + !thd->in_multi_stmt_transaction_mode() && + thd->wsrep_conflict_state == NO_CONFLICT && + !thd->wsrep_applier; + + do + { + if (thd->wsrep_conflict_state== RETRY_AUTOCOMMIT) + { + thd->wsrep_conflict_state= NO_CONFLICT; + /* Performance Schema Interface instrumentation, begin */ + thd->m_statement_psi= MYSQL_REFINE_STATEMENT(thd->m_statement_psi, + com_statement_info[thd->get_command()].m_key); + MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, thd->query(), + thd->query_length()); + + DBUG_EXECUTE_IF("sync.wsrep_retry_autocommit", + { + const char act[]= + "now " + "SIGNAL wsrep_retry_autocommit_reached " + "WAIT_FOR wsrep_retry_autocommit_continue"; + DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(act))); + }); + WSREP_DEBUG("Retry autocommit query: %s", thd->query()); + } + + mysql_parse(thd, rawbuf, length, parser_state); + + if (WSREP(thd)) { + /* wsrep BF abort in query exec phase */ + mysql_mutex_lock(&thd->LOCK_thd_data); + if (thd->wsrep_conflict_state == MUST_ABORT) { + wsrep_client_rollback(thd); + + WSREP_DEBUG("abort in exec query state, avoiding autocommit"); + } + + if (thd->wsrep_conflict_state == MUST_REPLAY) + { + mysql_mutex_unlock(&thd->LOCK_thd_data); + if (thd->lex->explain) + delete_explain_query(thd->lex); + mysql_mutex_lock(&thd->LOCK_thd_data); + + wsrep_replay_transaction(thd); + } + + /* setting error code for BF aborted trxs */ + if (thd->wsrep_conflict_state == ABORTED || + thd->wsrep_conflict_state == CERT_FAILURE) + { + thd->reset_for_next_command(); + if (is_autocommit && + thd->lex->sql_command != SQLCOM_SELECT && + (thd->wsrep_retry_counter < thd->variables.wsrep_retry_autocommit)) + { + mysql_mutex_unlock(&thd->LOCK_thd_data); + WSREP_DEBUG("wsrep retrying AC query: %s", + (thd->query()) ? thd->query() : "void"); + + /* Performance Schema Interface instrumentation, end */ + MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da()); + thd->m_statement_psi= NULL; + thd->m_digest= NULL; + // Released thd->LOCK_thd_data above as below could end up + // close_thread_tables()/close_open_tables()/close_thread_table()/mysql_mutex_lock(&thd->LOCK_thd_data) + close_thread_tables(thd); + + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->wsrep_conflict_state= RETRY_AUTOCOMMIT; + thd->wsrep_retry_counter++; // grow + wsrep_copy_query(thd); + thd->set_time(); + parser_state->reset(rawbuf, length); + mysql_mutex_unlock(&thd->LOCK_thd_data); + } + else + { + mysql_mutex_unlock(&thd->LOCK_thd_data); + // This does dirty read to wsrep variables but it is only a debug code + WSREP_DEBUG("%s, thd: %lu is_AC: %d, retry: %lu - %lu SQL: %s", + (thd->wsrep_conflict_state == ABORTED) ? + "BF Aborted" : "cert failure", + thd->thread_id, is_autocommit, thd->wsrep_retry_counter, + thd->variables.wsrep_retry_autocommit, thd->query()); + my_error(ER_LOCK_DEADLOCK, MYF(0), "wsrep aborted transaction"); + + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->wsrep_conflict_state= NO_CONFLICT; + if (thd->wsrep_conflict_state != REPLAYING) + thd->wsrep_retry_counter= 0; // reset + mysql_mutex_unlock(&thd->LOCK_thd_data); + } + + thd->reset_killed(); + } + else + { + set_if_smaller(thd->wsrep_retry_counter, 0); // reset; eventually ok + mysql_mutex_unlock(&thd->LOCK_thd_data); + } + } + + /* If retry is requested clean up explain structure */ + if ((thd->wsrep_conflict_state == RETRY_AUTOCOMMIT || + thd->wsrep_conflict_state == MUST_REPLAY ) + && thd->lex->explain) + { + delete_explain_query(thd->lex); + } + + } while (thd->wsrep_conflict_state== RETRY_AUTOCOMMIT); + + if (thd->wsrep_retry_query) + { + WSREP_DEBUG("releasing retry_query: conf %d sent %d kill %d errno %d SQL %s", + thd->wsrep_conflict_state, + thd->get_stmt_da()->is_sent(), + thd->killed, + thd->get_stmt_da()->is_error() ? thd->get_stmt_da()->sql_errno() : 0, + thd->wsrep_retry_query); + my_free(thd->wsrep_retry_query); + thd->wsrep_retry_query = NULL; + thd->wsrep_retry_query_len = 0; + thd->wsrep_retry_command = COM_CONNECT; + } +#endif /* WITH_WSREP */ +} + /* When you modify mysql_parse(), you may need to modify @@ -5865,22 +7399,26 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, of (among others) lex->safe_to_cache_query and thd->server_status, which are reset respectively in - lex_start() - - mysql_reset_thd_for_next_command() + - THD::reset_for_next_command() So, initializing the lexical analyser *before* using the query cache is required for the cache to work properly. FIXME: cleanup the dependencies in the code to simplify this. */ lex_start(thd); - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(); if (query_cache_send_result_to_client(thd, rawbuf, length) <= 0) { LEX *lex= thd->lex; - bool err= parse_sql(thd, parser_state, NULL); + bool err= parse_sql(thd, parser_state, NULL, true); if (!err) { + thd->m_statement_psi= + MYSQL_REFINE_STATEMENT(thd->m_statement_psi, + sql_statement_info[thd->lex->sql_command]. + m_key); #ifndef NO_EMBEDDED_ACCESS_CHECKS if (mqh_used && thd->user_connect && check_mqh(thd, lex->sql_command)) @@ -5904,10 +7442,9 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, Note that we don't need LOCK_thread_count to modify query_length. */ if (found_semicolon && (ulong) (found_semicolon - thd->query())) - thd->set_query_inner(thd->query(), - (uint32) (found_semicolon - - thd->query() - 1), - thd->charset()); + thd->set_query(thd->query(), + (uint32) (found_semicolon - thd->query() - 1), + thd->charset()); /* Actually execute the query */ if (found_semicolon) { @@ -5929,13 +7466,17 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, } else { + /* Instrument this broken statement as "statement/sql/error" */ + thd->m_statement_psi= + MYSQL_REFINE_STATEMENT(thd->m_statement_psi, + sql_statement_info[SQLCOM_END].m_key); DBUG_ASSERT(thd->is_error()); DBUG_PRINT("info",("Command aborted. Fatal_error: %d", thd->is_fatal_error)); - query_cache_abort(&thd->query_cache_tls); + query_cache_abort(thd, &thd->query_cache_tls); } - thd_proc_info(thd, "freeing items"); + THD_STAGE_INFO(thd, stage_freeing_items); sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size); sp_cache_enforce_limit(thd->sp_func_cache, stored_program_cache_size); thd->end_statement(); @@ -5946,8 +7487,17 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, { /* Update statistics for getting the query from the cache */ thd->lex->sql_command= SQLCOM_SELECT; + thd->m_statement_psi= + MYSQL_REFINE_STATEMENT(thd->m_statement_psi, + sql_statement_info[SQLCOM_SELECT].m_key); status_var_increment(thd->status_var.com_stat[SQLCOM_SELECT]); thd->update_stats(); +#ifdef WITH_WSREP + if (WSREP_CLIENT(thd)) + { + thd->wsrep_sync_wait_gtid= WSREP_GTID_UNDEFINED; + } +#endif /* WITH_WSREP */ } DBUG_VOID_RETURN; } @@ -5974,9 +7524,9 @@ bool mysql_test_parse_for_slave(THD *thd, char *rawbuf, uint length) if (!(error= parser_state.init(thd, rawbuf, length))) { lex_start(thd); - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(); - if (!parse_sql(thd, & parser_state, NULL) && + if (!parse_sql(thd, & parser_state, NULL, true) && all_tables_not_ok(thd, lex->select_lex.table_list.first)) error= 1; /* Ignore question */ thd->end_statement(); @@ -5987,105 +7537,6 @@ bool mysql_test_parse_for_slave(THD *thd, char *rawbuf, uint length) #endif - -/** - Store field definition for create. - - @return - Return 0 if ok -*/ - -bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, - char *length, char *decimals, - uint type_modifier, - Item *default_value, Item *on_update_value, - LEX_STRING *comment, - char *change, - List<String> *interval_list, CHARSET_INFO *cs, - uint uint_geom_type, - Virtual_column_info *vcol_info, - engine_option_value *create_options) -{ - register Create_field *new_field; - LEX *lex= thd->lex; - DBUG_ENTER("add_field_to_list"); - - if (check_ident_length(field_name)) - DBUG_RETURN(1); /* purecov: inspected */ - - if (type_modifier & PRI_KEY_FLAG) - { - Key *key; - lex->col_list.push_back(new Key_part_spec(*field_name, 0)); - key= new Key(Key::PRIMARY, null_lex_str, - &default_key_create_info, - 0, lex->col_list, NULL); - lex->alter_info.key_list.push_back(key); - lex->col_list.empty(); - } - if (type_modifier & (UNIQUE_FLAG | UNIQUE_KEY_FLAG)) - { - Key *key; - lex->col_list.push_back(new Key_part_spec(*field_name, 0)); - key= new Key(Key::UNIQUE, null_lex_str, - &default_key_create_info, 0, - lex->col_list, NULL); - lex->alter_info.key_list.push_back(key); - lex->col_list.empty(); - } - - if (default_value) - { - /* - Default value should be literal => basic constants => - no need fix_fields() - - We allow only one function as part of default value - - NOW() as default for TIMESTAMP type. - */ - if (default_value->type() == Item::FUNC_ITEM && - !(((Item_func*)default_value)->functype() == Item_func::NOW_FUNC && - type == MYSQL_TYPE_TIMESTAMP)) - { - my_error(ER_INVALID_DEFAULT, MYF(0), field_name->str); - DBUG_RETURN(1); - } - else if (default_value->type() == Item::NULL_ITEM) - { - default_value= 0; - if ((type_modifier & (NOT_NULL_FLAG | AUTO_INCREMENT_FLAG)) == - NOT_NULL_FLAG) - { - my_error(ER_INVALID_DEFAULT, MYF(0), field_name->str); - DBUG_RETURN(1); - } - } - else if (type_modifier & AUTO_INCREMENT_FLAG) - { - my_error(ER_INVALID_DEFAULT, MYF(0), field_name->str); - DBUG_RETURN(1); - } - } - - if (on_update_value && type != MYSQL_TYPE_TIMESTAMP) - { - my_error(ER_INVALID_ON_UPDATE, MYF(0), field_name->str); - DBUG_RETURN(1); - } - - if (!(new_field= new Create_field()) || - new_field->init(thd, field_name->str, type, length, decimals, type_modifier, - default_value, on_update_value, comment, change, - interval_list, cs, uint_geom_type, vcol_info, - create_options)) - DBUG_RETURN(1); - - lex->alter_info.create_list.push_back(new_field); - lex->last_field=new_field; - DBUG_RETURN(0); -} - - /** Store position for column in ALTER TABLE .. ADD column. */ void store_position_for_column(const char *name) @@ -6126,6 +7577,7 @@ bool add_to_list(THD *thd, SQL_I_List<ORDER> &list, Item *item,bool asc) order->free_me=0; order->used=0; order->counter_used= 0; + order->fast_field_copier_setup= 0; list.link_in_list(order, &order->next); DBUG_RETURN(0); } @@ -6158,19 +7610,19 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, thr_lock_type lock_type, enum_mdl_type mdl_type, List<Index_hint> *index_hints_arg, + List<String> *partition_names, LEX_STRING *option) { register TABLE_LIST *ptr; - TABLE_LIST *previous_table_ref; /* The table preceding the current one. */ + TABLE_LIST *UNINIT_VAR(previous_table_ref); /* The table preceding the current one. */ char *alias_str; LEX *lex= thd->lex; DBUG_ENTER("add_table_to_list"); - LINT_INIT(previous_table_ref); if (!table) DBUG_RETURN(0); // End of memory alias_str= alias ? alias->str : table->table.str; - if (!test(table_options & TL_OPTION_ALIAS) && + if (!MY_TEST(table_options & TL_OPTION_ALIAS) && check_table_name(table->table.str, table->table.length, FALSE)) { my_error(ER_WRONG_TABLE_NAME, MYF(0), table->table.str); @@ -6189,7 +7641,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, if (table->sel) { my_message(ER_DERIVED_MUST_HAVE_ALIAS, - ER(ER_DERIVED_MUST_HAVE_ALIAS), MYF(0)); + ER_THD(thd, ER_DERIVED_MUST_HAVE_ALIAS), MYF(0)); DBUG_RETURN(0); } if (!(alias_str= (char*) thd->memdup(alias_str,table->table.length+1))) @@ -6210,15 +7662,21 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->alias= alias_str; ptr->is_alias= alias ? TRUE : FALSE; - if (lower_case_table_names && table->table.length) - table->table.length= my_casedn_str(files_charset_info, table->table.str); + if (lower_case_table_names) + { + if (table->table.length) + table->table.length= my_casedn_str(files_charset_info, table->table.str); + if (ptr->db_length && ptr->db != any_db) + ptr->db_length= my_casedn_str(files_charset_info, ptr->db); + } + ptr->table_name=table->table.str; ptr->table_name_length=table->table.length; ptr->lock_type= lock_type; - ptr->updating= test(table_options & TL_OPTION_UPDATING); + ptr->updating= MY_TEST(table_options & TL_OPTION_UPDATING); /* TODO: remove TL_OPTION_FORCE_INDEX as it looks like it's not used */ - ptr->force_index= test(table_options & TL_OPTION_FORCE_INDEX); - ptr->ignore_leaves= test(table_options & TL_OPTION_IGNORE_LEAVES); + ptr->force_index= MY_TEST(table_options & TL_OPTION_FORCE_INDEX); + ptr->ignore_leaves= MY_TEST(table_options & TL_OPTION_IGNORE_LEAVES); ptr->derived= table->sel; if (!ptr->derived && is_infoschema_db(ptr->db, ptr->db_length)) { @@ -6306,11 +7764,14 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, */ table_list.link_in_list(ptr, &ptr->next_local); ptr->next_name_resolution_table= NULL; +#ifdef WITH_PARTITION_STORAGE_ENGINE + ptr->partition_names= partition_names; +#endif /* WITH_PARTITION_STORAGE_ENGINE */ /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); // Pure table aliases do not need to be locked: - if (!test(table_options & TL_OPTION_ALIAS)) + if (!MY_TEST(table_options & TL_OPTION_ALIAS)) { ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type, MDL_TRANSACTION); @@ -6350,7 +7811,7 @@ bool st_select_lex::init_nested_join(THD *thd) nested_join= ptr->nested_join= ((NESTED_JOIN*) ((uchar*) ptr + ALIGN_SIZE(sizeof(TABLE_LIST)))); - join_list->push_front(ptr); + join_list->push_front(ptr, thd->mem_root); ptr->embedding= embedding; ptr->join_list= join_list; ptr->alias= (char*) "(nested_join)"; @@ -6392,7 +7853,7 @@ TABLE_LIST *st_select_lex::end_nested_join(THD *thd) join_list->pop(); embedded->join_list= join_list; embedded->embedding= embedding; - join_list->push_front(embedded); + join_list->push_front(embedded, thd->mem_root); ptr= embedded; embedded->lifted= 1; } @@ -6456,7 +7917,7 @@ TABLE_LIST *st_select_lex::nest_last_join(THD *thd) ptr->join_using_fields= prev_join_using; } } - join_list->push_front(ptr); + join_list->push_front(ptr, thd->mem_root); nested_join->used_tables= nested_join->not_null_tables= (table_map) 0; DBUG_RETURN(ptr); } @@ -6479,7 +7940,7 @@ TABLE_LIST *st_select_lex::nest_last_join(THD *thd) void st_select_lex::add_joined_table(TABLE_LIST *table) { DBUG_ENTER("add_joined_table"); - join_list->push_front(table); + join_list->push_front(table, parent_lex->thd->mem_root); table->join_list= join_list; table->embedding= embedding; DBUG_VOID_RETURN; @@ -6523,8 +7984,8 @@ TABLE_LIST *st_select_lex::convert_right_join() TABLE_LIST *tab1= join_list->pop(); DBUG_ENTER("convert_right_join"); - join_list->push_front(tab2); - join_list->push_front(tab1); + join_list->push_front(tab2, parent_lex->thd->mem_root); + join_list->push_front(tab1, parent_lex->thd->mem_root); tab1->outer_join|= JOIN_TYPE_RIGHT; DBUG_RETURN(tab1); @@ -6605,7 +8066,10 @@ bool st_select_lex_unit::add_fake_select_lex(THD *thd_arg) fake_select_lex->context.outer_context=first_sl->context.outer_context; /* allow item list resolving in fake select for ORDER BY */ fake_select_lex->context.resolve_in_select_list= TRUE; - fake_select_lex->context.select_lex= fake_select_lex; + fake_select_lex->context.select_lex= fake_select_lex; + + fake_select_lex->nest_level_base= first_select()->nest_level_base; + fake_select_lex->nest_level=first_select()->nest_level; if (!is_union()) { @@ -6615,7 +8079,6 @@ bool st_select_lex_unit::add_fake_select_lex(THD *thd_arg) (SELECT ... LIMIT n) ORDER BY order_list [LIMIT m] just before the parser starts processing order_list */ - global_parameters= fake_select_lex; fake_select_lex->no_table_names_allowed= 1; thd_arg->lex->current_select= fake_select_lex; } @@ -6640,7 +8103,7 @@ bool st_select_lex_unit::add_fake_select_lex(THD *thd_arg) @retval FALSE if all is OK @retval - TRUE if a memory allocation error occured + TRUE if a memory allocation error occurred */ bool @@ -6655,7 +8118,7 @@ push_new_name_resolution_context(THD *thd, left_op->first_leaf_for_name_resolution(); on_context->last_name_resolution_table= right_op->last_leaf_for_name_resolution(); - return thd->lex->push_context(on_context); + return thd->lex->push_context(on_context, thd->mem_root); } @@ -6667,14 +8130,14 @@ push_new_name_resolution_context(THD *thd, @return fixed condition */ -Item *normalize_cond(Item *cond) +Item *normalize_cond(THD *thd, Item *cond) { if (cond) { Item::Type type= cond->type(); if (type == Item::FIELD_ITEM || type == Item::REF_ITEM) { - cond= new Item_func_ne(cond, new Item_int(0)); + cond= new (thd->mem_root) Item_func_ne(thd, cond, new (thd->mem_root) Item_int(thd, 0)); } } return cond; @@ -6695,11 +8158,11 @@ Item *normalize_cond(Item *cond) TRUE if all is OK */ -void add_join_on(TABLE_LIST *b, Item *expr) +void add_join_on(THD *thd, TABLE_LIST *b, Item *expr) { if (expr) { - expr= normalize_cond(expr); + expr= normalize_cond(thd, expr); if (!b->on_expr) b->on_expr= expr; else @@ -6709,7 +8172,7 @@ void add_join_on(TABLE_LIST *b, Item *expr) right and left join. If called later, it happens if we add more than one condition to the ON clause. */ - b->on_expr= new Item_cond_and(b->on_expr,expr); + b->on_expr= new (thd->mem_root) Item_cond_and(thd, b->on_expr,expr); } b->on_expr->top_level_item(); } @@ -6758,37 +8221,56 @@ void add_join_natural(TABLE_LIST *a, TABLE_LIST *b, List<String> *using_fields, /** - kill on thread. + Find a thread by id and return it, locking it LOCK_thd_data - @param thd Thread class - @param id Thread id - @param only_kill_query Should it kill the query or the connection + @param id Identifier of the thread we're looking for + @param query_id If true, search by query_id instead of thread_id - @note - This is written such that we have a short lock on LOCK_thread_count + @return NULL - not found + pointer - thread found, and its LOCK_thd_data is locked. */ -uint kill_one_thread(THD *thd, ulong id, killed_state kill_signal) +THD *find_thread_by_id(longlong id, bool query_id) { THD *tmp; - uint error=ER_NO_SUCH_THREAD; - DBUG_ENTER("kill_one_thread"); - DBUG_PRINT("enter", ("id: %lu signal: %u", id, (uint) kill_signal)); - mysql_mutex_lock(&LOCK_thread_count); // For unlink from list I_List_iterator<THD> it(threads); while ((tmp=it++)) { - if (tmp->command == COM_DAEMON) + if (tmp->get_command() == COM_DAEMON) continue; - if (tmp->thread_id == id) + if (id == (query_id ? tmp->query_id : (longlong) tmp->thread_id)) { mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete break; } } mysql_mutex_unlock(&LOCK_thread_count); - if (tmp) + return tmp; +} + + +/** + kill one thread. + + @param thd Thread class + @param id Thread id or query id + @param kill_signal Should it kill the query or the connection + @param type Type of id: thread id or query id + + @note + This is written such that we have a short lock on LOCK_thread_count +*/ + +uint +kill_one_thread(THD *thd, longlong id, killed_state kill_signal, killed_type type) +{ + THD *tmp; + uint error= (type == KILL_TYPE_QUERY ? ER_NO_SUCH_QUERY : ER_NO_SUCH_THREAD); + DBUG_ENTER("kill_one_thread"); + DBUG_PRINT("enter", ("id: %lld signal: %u", id, (uint) kill_signal)); + + if (id && (tmp= find_thread_by_id(id, type == KILL_TYPE_QUERY))) { /* If we're SUPER, we can KILL anything, including system-threads. @@ -6811,14 +8293,16 @@ uint kill_one_thread(THD *thd, ulong id, killed_state kill_signal) faster and do a harder kill than KILL_SYSTEM_THREAD; */ - if ((thd->security_ctx->master_access & SUPER_ACL) || - thd->security_ctx->user_matches(tmp->security_ctx)) + if (((thd->security_ctx->master_access & SUPER_ACL) || + thd->security_ctx->user_matches(tmp->security_ctx)) && + !wsrep_thd_is_BF(tmp, false)) { tmp->awake(kill_signal); error=0; } else - error=ER_KILL_DENIED_ERROR; + error= (type == KILL_TYPE_QUERY ? ER_KILL_QUERY_DENIED_ERROR : + ER_KILL_DENIED_ERROR); mysql_mutex_unlock(&tmp->LOCK_thd_data); } DBUG_PRINT("exit", ("%d", error)); @@ -6876,16 +8360,16 @@ static uint kill_threads_for_user(THD *thd, LEX_USER *user, mysql_mutex_unlock(&LOCK_thread_count); DBUG_RETURN(ER_KILL_DENIED_ERROR); } - if (!threads_to_kill.push_back(tmp, tmp->mem_root)) + if (!threads_to_kill.push_back(tmp, thd->mem_root)) mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete } } mysql_mutex_unlock(&LOCK_thread_count); if (!threads_to_kill.is_empty()) { - List_iterator_fast<THD> it(threads_to_kill); + List_iterator_fast<THD> it2(threads_to_kill); THD *next_ptr; - THD *ptr= it++; + THD *ptr= it2++; do { ptr->awake(kill_signal); @@ -6897,7 +8381,7 @@ static uint kill_threads_for_user(THD *thd, LEX_USER *user, Since the operation "it++" dereferences the "next" pointer of the previous list node, we need to do this while holding LOCK_thd_data. */ - next_ptr= it++; + next_ptr= it2++; mysql_mutex_unlock(&ptr->LOCK_thd_data); (*rows)++; } while ((ptr= next_ptr)); @@ -6906,26 +8390,25 @@ static uint kill_threads_for_user(THD *thd, LEX_USER *user, } -/* - kills a thread and sends response +/** + kills a thread and sends response. - SYNOPSIS - sql_kill() - thd Thread class - id Thread id - only_kill_query Should it kill the query or the connection + @param thd Thread class + @param id Thread id or query id + @param state Should it kill the query or the connection + @param type Type of id: thread id or query id */ static -void sql_kill(THD *thd, ulong id, killed_state state) +void sql_kill(THD *thd, longlong id, killed_state state, killed_type type) { uint error; - if (!(error= kill_one_thread(thd, id, state))) + if (!(error= kill_one_thread(thd, id, state, type))) { - if ((!thd->killed)) + if (!thd->killed) my_ok(thd); else - my_error(killed_errno(thd->killed), MYF(0), id); + thd->send_kill_message(); } else my_error(error, MYF(0), id); @@ -6995,7 +8478,7 @@ bool check_simple_select() char command[80]; Lex_input_stream *lip= & thd->m_parser_state->m_lip; strmake(command, lip->yylval->symbol.str, - min(lip->yylval->symbol.length, sizeof(command)-1)); + MY_MIN(lip->yylval->symbol.length, sizeof(command)-1)); my_error(ER_CANT_USE_OPTION_HERE, MYF(0), command); return 1; } @@ -7050,23 +8533,26 @@ Comp_creator *comp_ne_creator(bool invert) @return constructed Item (or 0 if out of memory) */ -Item * all_any_subquery_creator(Item *left_expr, +Item * all_any_subquery_creator(THD *thd, Item *left_expr, chooser_compare_func_creator cmp, bool all, SELECT_LEX *select_lex) { if ((cmp == &comp_eq_creator) && !all) // = ANY <=> IN - return new Item_in_subselect(left_expr, select_lex); + return new (thd->mem_root) Item_in_subselect(thd, left_expr, select_lex); if ((cmp == &comp_ne_creator) && all) // <> ALL <=> NOT IN - return new Item_func_not(new Item_in_subselect(left_expr, select_lex)); + return new (thd->mem_root) Item_func_not(thd, + new (thd->mem_root) Item_in_subselect(thd, left_expr, select_lex)); Item_allany_subselect *it= - new Item_allany_subselect(left_expr, cmp, select_lex, all); - if (all) - return it->upper_item= new Item_func_not_all(it); /* ALL */ + new (thd->mem_root) Item_allany_subselect(thd, left_expr, cmp, select_lex, + all); + if (all) /* ALL */ + return it->upper_item= new (thd->mem_root) Item_func_not_all(thd, it); - return it->upper_item= new Item_func_nop_all(it); /* ANY/SOME */ + /* ANY/SOME */ + return it->upper_item= new (thd->mem_root) Item_func_nop_all(thd, it); } @@ -7092,7 +8578,7 @@ bool multi_update_precheck(THD *thd, TABLE_LIST *tables) if (select_lex->item_list.elements != lex->value_list.elements) { - my_message(ER_WRONG_VALUE_COUNT, ER(ER_WRONG_VALUE_COUNT), MYF(0)); + my_message(ER_WRONG_VALUE_COUNT, ER_THD(thd, ER_WRONG_VALUE_COUNT), MYF(0)); DBUG_RETURN(TRUE); } /* @@ -7101,6 +8587,8 @@ bool multi_update_precheck(THD *thd, TABLE_LIST *tables) */ for (table= tables; table; table= table->next_local) { + if (table->is_jtbm()) + continue; if (table->derived) table->grant.privilege= SELECT_ACL; else if ((check_access(thd, UPDATE_ACL, table->db, @@ -7115,6 +8603,7 @@ bool multi_update_precheck(THD *thd, TABLE_LIST *tables) check_grant(thd, SELECT_ACL, table, FALSE, 1, FALSE))) DBUG_RETURN(TRUE); + table->grant.orig_want_privilege= 0; table->table_in_first_from_clause= 1; } /* @@ -7168,6 +8657,19 @@ bool multi_delete_precheck(THD *thd, TABLE_LIST *tables) TABLE_LIST **save_query_tables_own_last= thd->lex->query_tables_own_last; DBUG_ENTER("multi_delete_precheck"); + /* + Temporary tables are pre-opened in 'tables' list only. Here we need to + initialize TABLE instances in 'aux_tables' list. + */ + for (TABLE_LIST *tl= aux_tables; tl; tl= tl->next_global) + { + if (tl->table) + continue; + + if (tl->correspondent_table) + tl->table= tl->correspondent_table->table; + } + /* sql_yacc guarantees that tables and aux_tables are not zero */ DBUG_ASSERT(aux_tables != 0); if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) @@ -7189,7 +8691,7 @@ bool multi_delete_precheck(THD *thd, TABLE_LIST *tables) if ((thd->variables.option_bits & OPTION_SAFE_UPDATES) && !select_lex->where) { my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, - ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); + ER_THD(thd, ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); DBUG_RETURN(TRUE); } DBUG_RETURN(FALSE); @@ -7314,7 +8816,7 @@ bool update_precheck(THD *thd, TABLE_LIST *tables) DBUG_ENTER("update_precheck"); if (thd->lex->select_lex.item_list.elements != thd->lex->value_list.elements) { - my_message(ER_WRONG_VALUE_COUNT, ER(ER_WRONG_VALUE_COUNT), MYF(0)); + my_message(ER_WRONG_VALUE_COUNT, ER_THD(thd, ER_WRONG_VALUE_COUNT), MYF(0)); DBUG_RETURN(TRUE); } DBUG_RETURN(check_one_table_access(thd, UPDATE_ACL, tables)); @@ -7374,7 +8876,7 @@ bool insert_precheck(THD *thd, TABLE_LIST *tables) if (lex->update_list.elements != lex->value_list.elements) { - my_message(ER_WRONG_VALUE_COUNT, ER(ER_WRONG_VALUE_COUNT), MYF(0)); + my_message(ER_WRONG_VALUE_COUNT, ER_THD(thd, ER_WRONG_VALUE_COUNT), MYF(0)); DBUG_RETURN(TRUE); } DBUG_RETURN(FALSE); @@ -7390,7 +8892,7 @@ void create_table_set_open_action_and_adjust_tables(LEX *lex) { TABLE_LIST *create_table= lex->query_tables; - if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) + if (lex->tmp_table()) create_table->open_type= OT_TEMPORARY_ONLY; else create_table->open_type= OT_BASE_ONLY; @@ -7436,10 +8938,13 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, CREATE TABLE ... SELECT, also require INSERT. */ - want_priv= ((lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) ? - CREATE_TMP_ACL : CREATE_ACL) | - (select_lex->item_list.elements ? INSERT_ACL : 0); + want_priv= lex->tmp_table() ? CREATE_TMP_ACL : + (CREATE_ACL | (select_lex->item_list.elements ? INSERT_ACL : 0)); + /* CREATE OR REPLACE on not temporary tables require DROP_ACL */ + if (lex->create_info.or_replace() && !lex->tmp_table()) + want_priv|= DROP_ACL; + if (check_access(thd, want_priv, create_table->db, &create_table->grant.privilege, &create_table->grant.m_internal, @@ -7447,11 +8952,47 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, goto err; /* If it is a merge table, check privileges for merge children. */ - if (lex->create_info.merge_list.first && - check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, - lex->create_info.merge_list.first, - FALSE, UINT_MAX, FALSE)) - goto err; + if (lex->create_info.merge_list) + { + /* + The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the + underlying base tables, even if there are temporary tables with the same + names. + + From user's point of view, it might look as if the user must have these + privileges on temporary tables to create a merge table over them. This is + one of two cases when a set of privileges is required for operations on + temporary tables (see also CREATE TABLE). + + The reason for this behavior stems from the following facts: + + - For merge tables, the underlying table privileges are checked only + at CREATE TABLE / ALTER TABLE time. + + In other words, once a merge table is created, the privileges of + the underlying tables can be revoked, but the user will still have + access to the merge table (provided that the user has privileges on + the merge table itself). + + - Temporary tables shadow base tables. + + I.e. there might be temporary and base tables with the same name, and + the temporary table takes the precedence in all operations. + + - For temporary MERGE tables we do not track if their child tables are + base or temporary. As result we can't guarantee that privilege check + which was done in presence of temporary child will stay relevant + later as this temporary table might be removed. + + If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for + the underlying *base* tables, it would create a security breach as in + Bug#12771903. + */ + + if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, + lex->create_info.merge_list, FALSE, UINT_MAX, FALSE)) + goto err; + } if (want_priv != CREATE_TMP_ACL && check_grant(thd, want_priv, create_table, FALSE, 1, FALSE)) @@ -7464,7 +9005,7 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, UINT_MAX, FALSE)) goto err; } - else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE) + else if (lex->create_info.like()) { if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) goto err; @@ -7474,12 +9015,42 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, goto err; error= FALSE; + err: DBUG_RETURN(error); } /** + Check privileges for LOCK TABLES statement. + + @param thd Thread context. + @param tables List of tables to be locked. + + @retval FALSE - Success. + @retval TRUE - Failure. +*/ + +static bool lock_tables_precheck(THD *thd, TABLE_LIST *tables) +{ + TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table(); + + for (TABLE_LIST *table= tables; table != first_not_own_table && table; + table= table->next_global) + { + if (is_temporary_table(table)) + continue; + + if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, table, + FALSE, 1, FALSE)) + return TRUE; + } + + return FALSE; +} + + +/** negate given expression. @param thd thread handler @@ -7498,18 +9069,18 @@ Item *negate_expression(THD *thd, Item *expr) /* it is NOT(NOT( ... )) */ Item *arg= ((Item_func *) expr)->arguments()[0]; enum_parsing_place place= thd->lex->current_select->parsing_place; - if (arg->is_bool_func() || place == IN_WHERE || place == IN_HAVING) + if (arg->is_bool_type() || place == IN_WHERE || place == IN_HAVING) return arg; /* if it is not boolean function then we have to emulate value of not(not(a)), it will be a != 0 */ - return new Item_func_ne(arg, new Item_int((char*) "0", 0, 1)); + return new (thd->mem_root) Item_func_ne(thd, arg, new (thd->mem_root) Item_int(thd, (char*) "0", 0, 1)); } if ((negated= expr->neg_transformer(thd)) != 0) return negated; - return new Item_func_not(expr); + return new (thd->mem_root) Item_func_not(thd, expr); } /** @@ -7520,19 +9091,24 @@ Item *negate_expression(THD *thd, Item *expr) @param[out] definer definer */ -void get_default_definer(THD *thd, LEX_USER *definer) +void get_default_definer(THD *thd, LEX_USER *definer, bool role) { const Security_context *sctx= thd->security_ctx; - definer->user.str= (char *) sctx->priv_user; + if (role) + { + definer->user.str= const_cast<char*>(sctx->priv_role); + definer->host= empty_lex_str; + } + else + { + definer->user.str= const_cast<char*>(sctx->priv_user); + definer->host.str= const_cast<char*>(sctx->priv_host); + definer->host.length= strlen(definer->host.str); + } definer->user.length= strlen(definer->user.str); - definer->host.str= (char *) sctx->priv_host; - definer->host.length= strlen(definer->host.str); - - definer->password= null_lex_str; - definer->plugin= empty_lex_str; - definer->auth= empty_lex_str; + definer->reset_auth(); } @@ -7547,16 +9123,22 @@ void get_default_definer(THD *thd, LEX_USER *definer) - On error, return 0. */ -LEX_USER *create_default_definer(THD *thd) +LEX_USER *create_default_definer(THD *thd, bool role) { LEX_USER *definer; if (! (definer= (LEX_USER*) thd->alloc(sizeof(LEX_USER)))) return 0; - thd->get_definer(definer); + thd->get_definer(definer, role); - return definer; + if (role && definer->user.length == 0) + { + my_error(ER_MALFORMED_DEFINER, MYF(0)); + return 0; + } + else + return definer; } @@ -7584,39 +9166,18 @@ LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name) definer->user= *user_name; definer->host= *host_name; - definer->password.str= NULL; - definer->password.length= 0; + definer->reset_auth(); return definer; } /** - Retuns information about user or current user. - - @param[in] thd thread handler - @param[in] user user - - @return - - On success, return a valid pointer to initialized - LEX_USER, which contains user information. - - On error, return 0. -*/ - -LEX_USER *get_current_user(THD *thd, LEX_USER *user) -{ - if (!user->user.str) // current_user - return create_default_definer(thd); - - return user; -} - - -/** Check that byte length of a string does not exceed some limit. @param str string to be checked - @param err_msg error message to be displayed if the string is too long + @param err_msg Number of error message to be displayed if the string + is too long. 0 if empty error message. @param max_length max length @retval @@ -7628,13 +9189,14 @@ LEX_USER *get_current_user(THD *thd, LEX_USER *user) The function is not used in existing code but can be useful later? */ -bool check_string_byte_length(LEX_STRING *str, const char *err_msg, +bool check_string_byte_length(LEX_STRING *str, uint err_msg, uint max_byte_length) { if (str->length <= max_byte_length) return FALSE; - my_error(ER_WRONG_STRING_LENGTH, MYF(0), str->str, err_msg, max_byte_length); + my_error(ER_WRONG_STRING_LENGTH, MYF(0), str->str, + err_msg ? ER(err_msg) : "", max_byte_length); return TRUE; } @@ -7646,7 +9208,8 @@ bool check_string_byte_length(LEX_STRING *str, const char *err_msg, SYNOPSIS check_string_char_length() str string to be checked - err_msg error message to be displayed if the string is too long + err_msg Number of error message to be displayed if the string + is too long. 0 if empty error message. max_char_length max length in symbols cs string charset @@ -7656,7 +9219,7 @@ bool check_string_byte_length(LEX_STRING *str, const char *err_msg, */ -bool check_string_char_length(LEX_STRING *str, const char *err_msg, +bool check_string_char_length(LEX_STRING *str, uint err_msg, uint max_char_length, CHARSET_INFO *cs, bool no_error) { @@ -7664,13 +9227,15 @@ bool check_string_char_length(LEX_STRING *str, const char *err_msg, uint res= cs->cset->well_formed_len(cs, str->str, str->str + str->length, max_char_length, &well_formed_error); - if (!well_formed_error && str->length == res) + if (!well_formed_error && str->length == res) return FALSE; if (!no_error) { ErrConvString err(str->str, str->length, cs); - my_error(ER_WRONG_STRING_LENGTH, MYF(0), err.ptr(), err_msg, max_char_length); + my_error(ER_WRONG_STRING_LENGTH, MYF(0), err.ptr(), + err_msg ? ER(err_msg) : "", + max_char_length); } return TRUE; } @@ -7759,6 +9324,22 @@ int test_if_data_home_dir(const char *dir) } +int error_if_data_home_dir(const char *path, const char *what) +{ + size_t dirlen; + char dirpath[FN_REFLEN]; + if (path) + { + dirname_part(dirpath, path, &dirlen); + if (test_if_data_home_dir(dirpath)) + { + my_error(ER_WRONG_ARGUMENTS, MYF(0), what); + return 1; + } + } + return 0; +} + /** Check that host name string is valid. @@ -7774,7 +9355,7 @@ bool check_host_name(LEX_STRING *str) { const char *name= str->str; const char *end= str->str + str->length; - if (check_string_byte_length(str, ER(ER_HOSTNAME), HOSTNAME_LENGTH)) + if (check_string_byte_length(str, ER_HOSTNAME, HOSTNAME_LENGTH)) return TRUE; while (name != end) @@ -7808,14 +9389,13 @@ extern int MYSQLparse(THD *thd); // from sql_yacc.cc @retval TRUE on parsing error. */ -bool parse_sql(THD *thd, - Parser_state *parser_state, - Object_creation_ctx *creation_ctx) +bool parse_sql(THD *thd, Parser_state *parser_state, + Object_creation_ctx *creation_ctx, bool do_pfs_digest) { bool ret_value; DBUG_ENTER("parse_sql"); DBUG_ASSERT(thd->m_parser_state == NULL); - DBUG_ASSERT(thd->lex->m_stmt == NULL); + DBUG_ASSERT(thd->lex->m_sql_cmd == NULL); MYSQL_QUERY_PARSE_START(thd->query()); /* Backup creation context. */ @@ -7829,6 +9409,28 @@ bool parse_sql(THD *thd, thd->m_parser_state= parser_state; + parser_state->m_digest_psi= NULL; + parser_state->m_lip.m_digest= NULL; + + if (do_pfs_digest) + { + /* Start Digest */ + parser_state->m_digest_psi= MYSQL_DIGEST_START(thd->m_statement_psi); + + if (parser_state->m_input.m_compute_digest || + (parser_state->m_digest_psi != NULL)) + { + /* + If either: + - the caller wants to compute a digest + - the performance schema wants to compute a digest + set the digest listener in the lexer. + */ + parser_state->m_lip.m_digest= thd->m_digest; + parser_state->m_lip.m_digest->m_digest_storage.m_charset_number= thd->charset()->number; + } + } + /* Parse the query. */ bool mysql_parse_status= MYSQLparse(thd) != 0; @@ -7860,6 +9462,18 @@ bool parse_sql(THD *thd, /* That's it. */ ret_value= mysql_parse_status || thd->is_fatal_error; + + if ((ret_value == 0) && (parser_state->m_digest_psi != NULL)) + { + /* + On parsing success, record the digest in the performance schema. + */ + DBUG_ASSERT(do_pfs_digest); + DBUG_ASSERT(thd->m_digest != NULL); + MYSQL_DIGEST_END(parser_state->m_digest_psi, + & thd->m_digest->m_digest_storage); + } + MYSQL_QUERY_PARSE_DONE(ret_value); DBUG_RETURN(ret_value); } @@ -7901,3 +9515,18 @@ merge_charset_and_collation(CHARSET_INFO *cs, CHARSET_INFO *cl) } return cs; } + +/** find a collation with binary comparison rules +*/ +CHARSET_INFO *find_bin_collation(CHARSET_INFO *cs) +{ + const char *csname= cs->csname; + cs= get_charset_by_csname(csname, MY_CS_BINSORT, MYF(0)); + if (!cs) + { + char tmp[65]; + strxnmov(tmp, sizeof(tmp)-1, csname, "_bin", NULL); + my_error(ER_UNKNOWN_COLLATION, MYF(0), tmp); + } + return cs; +} diff --git a/sql/sql_parse.h b/sql/sql_parse.h index c5ea38706c4..602a3e8d788 100644 --- a/sql/sql_parse.h +++ b/sql/sql_parse.h @@ -35,6 +35,7 @@ enum enum_mysql_completiontype { extern "C" int path_starts_from_data_home_dir(const char *dir); int test_if_data_home_dir(const char *dir); +int error_if_data_home_dir(const char *path, const char *what); bool multi_update_precheck(THD *thd, TABLE_LIST *tables); bool multi_delete_precheck(THD *thd, TABLE_LIST *tables); @@ -51,9 +52,8 @@ bool check_fk_parent_table_access(THD *thd, Alter_info *alter_info, const char* create_db); -bool parse_sql(THD *thd, - Parser_state *parser_state, - Object_creation_ctx *creation_ctx); +bool parse_sql(THD *thd, Parser_state *parser_state, + Object_creation_ctx *creation_ctx, bool do_pfs_digest=false); void free_items(Item *item); void cleanup_items(Item *item); @@ -67,29 +67,31 @@ Comp_creator *comp_ne_creator(bool invert); int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, enum enum_schema_tables schema_table_idx); -void get_default_definer(THD *thd, LEX_USER *definer); -LEX_USER *create_default_definer(THD *thd); +void get_default_definer(THD *thd, LEX_USER *definer, bool role); +LEX_USER *create_default_definer(THD *thd, bool role); LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name); -LEX_USER *get_current_user(THD *thd, LEX_USER *user); -bool check_string_byte_length(LEX_STRING *str, const char *err_msg, +LEX_USER *get_current_user(THD *thd, LEX_USER *user, bool lock=true); +bool sp_process_definer(THD *thd); +bool check_string_byte_length(LEX_STRING *str, uint err_msg, uint max_byte_length); -bool check_string_char_length(LEX_STRING *str, const char *err_msg, +bool check_string_char_length(LEX_STRING *str, uint err_msg, uint max_char_length, CHARSET_INFO *cs, bool no_error); bool check_ident_length(LEX_STRING *ident); CHARSET_INFO* merge_charset_and_collation(CHARSET_INFO *cs, CHARSET_INFO *cl); +CHARSET_INFO *find_bin_collation(CHARSET_INFO *cs); bool check_host_name(LEX_STRING *str); bool check_identifier_name(LEX_STRING *str, uint max_char_length, uint err_code, const char *param_for_err_msg); bool mysql_test_parse_for_slave(THD *thd,char *inBuf,uint length); bool sqlcom_can_generate_row_events(const THD *thd); +bool stmt_causes_implicit_commit(THD *thd, uint mask); bool is_update_query(enum enum_sql_command command); bool is_log_table_write_query(enum enum_sql_command command); bool alloc_query(THD *thd, const char *packet, uint packet_length); void mysql_init_select(LEX *lex); void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state); -void mysql_reset_thd_for_next_command(THD *thd); bool mysql_new_select(LEX *lex, bool move_down); void create_select_for_variable(const char *var_name); void create_table_set_open_action_and_adjust_tables(LEX *lex); @@ -109,18 +111,8 @@ bool append_file_to_dir(THD *thd, const char **filename_ptr, const char *table_name); void execute_init_command(THD *thd, LEX_STRING *init_command, mysql_rwlock_t *var_lock); -bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum enum_field_types type, - char *length, char *decimal, - uint type_modifier, - Item *default_value, Item *on_update_value, - LEX_STRING *comment, - char *change, List<String> *interval_list, - CHARSET_INFO *cs, - uint uint_geom_type, - Virtual_column_info *vcol_info, - engine_option_value *create_options); bool add_to_list(THD *thd, SQL_I_List<ORDER> &list, Item *group, bool asc); -void add_join_on(TABLE_LIST *b,Item *expr); +void add_join_on(THD *thd, TABLE_LIST *b, Item *expr); void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List<String> *using_fields, SELECT_LEX *lex); bool add_proc_to_list(THD *thd, Item *item); @@ -130,7 +122,7 @@ bool push_new_name_resolution_context(THD *thd, void store_position_for_column(const char *name); void init_update_queries(void); bool check_simple_select(); -Item *normalize_cond(Item *cond); +Item *normalize_cond(THD *thd, Item *cond); Item *negate_expression(THD *thd, Item *expr); bool check_stack_overrun(THD *thd, long margin, uchar *dummy); @@ -161,9 +153,6 @@ bool check_routine_access(THD *thd,ulong want_access,char *db,char *name, bool is_proc, bool no_errors); bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table); bool check_some_routine_access(THD *thd, const char *db, const char *name, bool is_proc); -bool check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, - GRANT_INTERNAL_INFO *grant_internal_info, - bool dont_check_global_grants, bool no_errors); bool check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables, bool any_combination_of_privileges_will_do, uint number, @@ -185,13 +174,6 @@ inline bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table) inline bool check_some_routine_access(THD *thd, const char *db, const char *name, bool is_proc) { return false; } -inline bool check_access(THD *, ulong, const char *, ulong *save_priv, - GRANT_INTERNAL_INFO *, bool, bool) -{ - if (save_priv) - *save_priv= GLOBAL_ACLS; - return false; -} inline bool check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables, bool any_combination_of_privileges_will_do, @@ -200,14 +182,4 @@ check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables, { return false; } #endif /*NO_EMBEDDED_ACCESS_CHECKS*/ -/* These were under the INNODB_COMPATIBILITY_HOOKS */ - -bool check_global_access(THD *thd, ulong want_access, bool no_errors= false); - -inline bool is_supported_parser_charset(CHARSET_INFO *cs) -{ - return test(cs->mbminlen == 1); -} - - #endif /* SQL_PARSE_INCLUDED */ diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index b25a3c2bcf3..05ef69e5795 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -47,18 +47,17 @@ /* Some general useful functions */ #define MYSQL_LEX 1 +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_partition.h" #include "key.h" // key_restore #include "sql_parse.h" // parse_sql #include "sql_cache.h" // query_cache_invalidate3 #include "lock.h" // mysql_lock_remove #include "sql_show.h" // append_identifier -#include <errno.h> #include <m_ctype.h> -#include "my_md5.h" #include "transaction.h" +#include "debug_sync.h" #include "sql_base.h" // close_all_tables_for_name #include "sql_table.h" // build_table_filename, @@ -66,7 +65,12 @@ // table_to_filename // mysql_*_alter_copy_data #include "opt_range.h" // store_key_image_to_rec -#include "sql_analyse.h" // append_escaped +#include "sql_alter.h" // Alter_table_ctx +#include "sql_select.h" + +#include <algorithm> +using std::max; +using std::min; #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" @@ -197,7 +201,7 @@ Item* convert_charset_partition_constant(Item *item, CHARSET_INFO *cs) TABLE_LIST *save_list= context->table_list; const char *save_where= thd->where; - item= item->safe_charset_converter(cs); + item= item->safe_charset_converter(thd, cs); context->table_list= NULL; thd->where= "convert character set partition constant"; if (!item || item->fix_fields(thd, (Item**)NULL)) @@ -208,21 +212,18 @@ Item* convert_charset_partition_constant(Item *item, CHARSET_INFO *cs) } -/* - A support function to check if a name is in a list of strings +/** + A support function to check if a name is in a list of strings. - SYNOPSIS - is_name_in_list() - name String searched for - list_names A list of names searched in + @param name String searched for + @param list_names A list of names searched in - RETURN VALUES - TRUE String found - FALSE String not found + @return True if if the name is in the list. + @retval true String found + @retval false String not found */ -bool is_name_in_list(char *name, - List<char> list_names) +static bool is_name_in_list(char *name, List<char> list_names) { List_iterator<char> names_it(list_names); uint num_names= list_names.elements; @@ -283,62 +284,7 @@ bool partition_default_handling(TABLE *table, partition_info *part_info, } } part_info->set_up_defaults_for_partitioning(table->file, - NULL, (uint)0); - DBUG_RETURN(FALSE); -} - - -/* - Check that the reorganized table will not have duplicate partitions. - - SYNOPSIS - check_reorganise_list() - new_part_info New partition info - old_part_info Old partition info - list_part_names The list of partition names that will go away and - can be reused in the new table. - - RETURN VALUES - TRUE Inacceptable name conflict detected. - FALSE New names are OK. - - DESCRIPTION - Can handle that the 'new_part_info' and 'old_part_info' the same - in which case it checks that the list of names in the partitions - doesn't contain any duplicated names. -*/ - -bool check_reorganise_list(partition_info *new_part_info, - partition_info *old_part_info, - List<char> list_part_names) -{ - uint new_count, old_count; - uint num_new_parts= new_part_info->partitions.elements; - uint num_old_parts= old_part_info->partitions.elements; - List_iterator<partition_element> new_parts_it(new_part_info->partitions); - bool same_part_info= (new_part_info == old_part_info); - DBUG_ENTER("check_reorganise_list"); - - new_count= 0; - do - { - List_iterator<partition_element> old_parts_it(old_part_info->partitions); - char *new_name= (new_parts_it++)->partition_name; - new_count++; - old_count= 0; - do - { - char *old_name= (old_parts_it++)->partition_name; - old_count++; - if (same_part_info && old_count == new_count) - break; - if (!(my_strcasecmp(system_charset_info, old_name, new_name))) - { - if (!is_name_in_list(old_name, list_part_names)) - DBUG_RETURN(TRUE); - } - } while (old_count < num_old_parts); - } while (new_count < num_new_parts); + NULL, 0U); DBUG_RETURN(FALSE); } @@ -578,7 +524,13 @@ static bool set_up_field_array(TABLE *table, } while (++inx < num_fields); if (inx == num_fields) { - mem_alloc_error(1); + /* + Should not occur since it should already been checked in either + add_column_list_values, handle_list_of_fields, + check_partition_info etc. + */ + DBUG_ASSERT(0); + my_error(ER_FIELD_NOT_FOUND_PART_ERROR, MYF(0)); result= TRUE; continue; } @@ -697,7 +649,7 @@ static bool create_full_part_field_array(THD *thd, TABLE *table, result= TRUE; goto end; } - if (bitmap_init(&part_info->full_part_field_set, bitmap_buf, + if (my_bitmap_init(&part_info->full_part_field_set, bitmap_buf, table->s->fields, FALSE)) { mem_alloc_error(table->s->fields); @@ -742,7 +694,7 @@ end: static void clear_indicator_in_key_fields(KEY *key_info) { KEY_PART_INFO *key_part; - uint key_parts= key_info->key_parts, i; + uint key_parts= key_info->user_defined_key_parts, i; for (i= 0, key_part=key_info->key_part; i < key_parts; i++, key_part++) key_part->field->flags&= (~GET_FIXED_FIELDS_FLAG); } @@ -762,7 +714,7 @@ static void clear_indicator_in_key_fields(KEY *key_info) static void set_indicator_in_key_fields(KEY *key_info) { KEY_PART_INFO *key_part; - uint key_parts= key_info->key_parts, i; + uint key_parts= key_info->user_defined_key_parts, i; for (i= 0, key_part=key_info->key_part; i < key_parts; i++, key_part++) key_part->field->flags|= GET_FIXED_FIELDS_FLAG; } @@ -882,7 +834,7 @@ static bool handle_list_of_fields(List_iterator<char> it, uint primary_key= table->s->primary_key; if (primary_key != MAX_KEY) { - uint num_key_parts= table->key_info[primary_key].key_parts, i; + uint num_key_parts= table->key_info[primary_key].user_defined_key_parts, i; /* In the case of an empty list we use primary key as partition key. */ @@ -1075,9 +1027,9 @@ static bool fix_fields_part_func(THD *thd, Item* func_expr, TABLE *table, goto end; } else - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR, - ER(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR)); + ER_THD(thd, ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR)); } if ((!is_sub_part) && (error= check_signed_flag(part_info))) @@ -1244,39 +1196,44 @@ void check_range_capable_PF(TABLE *table) } -/* - Set up partition bitmap +/** + Set up partition bitmaps - SYNOPSIS - set_up_partition_bitmap() - thd Thread object - part_info Reference to partitioning data structure + @param thd Thread object + @param part_info Reference to partitioning data structure - RETURN VALUE - TRUE Memory allocation failure - FALSE Success + @return Operation status + @retval TRUE Memory allocation failure + @retval FALSE Success - DESCRIPTION - Allocate memory for bitmap of the partitioned table + Allocate memory for bitmaps of the partitioned table and initialise it. */ -static bool set_up_partition_bitmap(THD *thd, partition_info *part_info) +static bool set_up_partition_bitmaps(THD *thd, partition_info *part_info) { uint32 *bitmap_buf; uint bitmap_bits= part_info->num_subparts? (part_info->num_subparts* part_info->num_parts): part_info->num_parts; uint bitmap_bytes= bitmap_buffer_size(bitmap_bits); - DBUG_ENTER("set_up_partition_bitmap"); + DBUG_ENTER("set_up_partition_bitmaps"); + + DBUG_ASSERT(!part_info->bitmaps_are_initialized); - if (!(bitmap_buf= (uint32*)thd->alloc(bitmap_bytes))) + /* Allocate for both read and lock_partitions */ + if (!(bitmap_buf= (uint32*) alloc_root(&part_info->table->mem_root, + bitmap_bytes * 2))) { - mem_alloc_error(bitmap_bytes); + mem_alloc_error(bitmap_bytes * 2); DBUG_RETURN(TRUE); } - bitmap_init(&part_info->used_partitions, bitmap_buf, bitmap_bytes*8, FALSE); - bitmap_set_all(&part_info->used_partitions); + my_bitmap_init(&part_info->read_partitions, bitmap_buf, bitmap_bits, FALSE); + /* Use the second half of the allocated buffer for lock_partitions */ + my_bitmap_init(&part_info->lock_partitions, bitmap_buf + (bitmap_bytes / 4), + bitmap_bits, FALSE); + part_info->bitmaps_are_initialized= TRUE; + part_info->set_partition_bitmaps(NULL); DBUG_RETURN(FALSE); } @@ -1564,7 +1521,7 @@ bool field_is_partition_charset(Field *field) !(field->type() == MYSQL_TYPE_VARCHAR)) return FALSE; { - CHARSET_INFO *cs= ((Field_str*)field)->charset(); + CHARSET_INFO *cs= field->charset(); if (!(field->type() == MYSQL_TYPE_STRING) || !(cs->state & MY_CS_BINSORT)) return TRUE; @@ -1607,7 +1564,7 @@ bool check_part_func_fields(Field **ptr, bool ok_with_charsets) */ if (field_is_partition_charset(field)) { - CHARSET_INFO *cs= ((Field_str*)field)->charset(); + CHARSET_INFO *cs= field->charset(); if (!ok_with_charsets || cs->mbmaxlen > 1 || cs->strxfrm_multiply > 1) @@ -1796,9 +1753,9 @@ bool fix_partition_func(THD *thd, TABLE *table, (table->s->db_type()->partition_flags() & HA_CAN_PARTITION_UNIQUE))) && check_unique_keys(table))) goto end; - if (unlikely(set_up_partition_bitmap(thd, part_info))) + if (unlikely(set_up_partition_bitmaps(thd, part_info))) goto end; - if (unlikely(part_info->set_up_charset_field_preps())) + if (unlikely(part_info->set_up_charset_field_preps(thd))) { my_error(ER_PARTITION_FUNCTION_IS_NOT_ALLOWED, MYF(0)); goto end; @@ -1812,6 +1769,7 @@ bool fix_partition_func(THD *thd, TABLE *table, set_up_partition_key_maps(table, part_info); set_up_partition_func_pointers(part_info); set_up_range_analysis_info(part_info); + table->file->set_part_info(part_info); result= FALSE; end: thd->mark_used_columns= save_mark_used_columns; @@ -1969,16 +1927,92 @@ static int add_uint(File fptr, ulonglong number) */ static int add_quoted_string(File fptr, const char *quotestr) { - String orgstr(quotestr, system_charset_info); String escapedstr; int err= add_string(fptr, "'"); - err+= append_escaped(&escapedstr, &orgstr); + err+= escapedstr.append_for_single_quote(quotestr); err+= add_string(fptr, escapedstr.c_ptr_safe()); return err + add_string(fptr, "'"); } +/** + @brief Truncate the partition file name from a path it it exists. + + @note A partition file name will contian one or more '#' characters. +One of the occurances of '#' will be either "#P#" or "#p#" depending +on whether the storage engine has converted the filename to lower case. +*/ +void truncate_partition_filename(char *path) +{ + if (path) + { + char* last_slash= strrchr(path, FN_LIBCHAR); + + if (!last_slash) + last_slash= strrchr(path, FN_LIBCHAR2); + + if (last_slash) + { + /* Look for a partition-type filename */ + for (char* pound= strchr(last_slash, '#'); + pound; pound = strchr(pound + 1, '#')) + { + if ((pound[1] == 'P' || pound[1] == 'p') && pound[2] == '#') + { + last_slash[0] = '\0'; /* truncate the file name */ + break; + } + } + } + } +} + + +/** + @brief Output a filepath. Similar to add_keyword_string except it +also converts \ to / on Windows and skips the partition file name at +the end if found. + + @note When Mysql sends a DATA DIRECTORY from SQL for partitions it does +not use a file name, but it does for DATA DIRECTORY on a non-partitioned +table. So when the storage engine is asked for the DATA DIRECTORY string +after a restart through Handler::update_create_options(), the storage +engine may include the filename. +*/ +static int add_keyword_path(File fptr, const char *keyword, + const char *path) +{ + int err= add_string(fptr, keyword); + + err+= add_space(fptr); + err+= add_equal(fptr); + err+= add_space(fptr); + + char temp_path[FN_REFLEN]; + strcpy(temp_path, path); +#ifdef __WIN__ + /* Convert \ to / to be able to create table on unix */ + char *pos, *end; + uint length= strlen(temp_path); + for (pos= temp_path, end= pos+length ; pos < end ; pos++) + { + if (*pos == '\\') + *pos = '/'; + } +#endif + + /* + If the partition file name with its "#P#" identifier + is found after the last slash, truncate that filename. + */ + truncate_partition_filename(temp_path); + + err+= add_quoted_string(fptr, temp_path); + + return err + add_space(fptr); +} + static int add_keyword_string(File fptr, const char *keyword, - bool should_use_quotes, + bool should_use_quotes, const char *keystr) { int err= add_string(fptr, keyword); @@ -2029,11 +2063,9 @@ static int add_partition_options(File fptr, partition_element *p_elem) if (!(current_thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)) { if (p_elem->data_file_name) - err+= add_keyword_string(fptr, "DATA DIRECTORY", TRUE, - p_elem->data_file_name); + err+= add_keyword_path(fptr, "DATA DIRECTORY", p_elem->data_file_name); if (p_elem->index_file_name) - err+= add_keyword_string(fptr, "INDEX DIRECTORY", TRUE, - p_elem->index_file_name); + err+= add_keyword_path(fptr, "INDEX DIRECTORY", p_elem->index_file_name); } if (p_elem->part_comment) err+= add_keyword_string(fptr, "COMMENT", TRUE, p_elem->part_comment); @@ -2085,6 +2117,8 @@ static int check_part_field(enum_field_types sql_type, case MYSQL_TYPE_DATE: case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_DATETIME2: *result_type= STRING_RESULT; *need_cs_check= TRUE; return FALSE; @@ -2097,6 +2131,7 @@ static int check_part_field(enum_field_types sql_type, case MYSQL_TYPE_NEWDECIMAL: case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: case MYSQL_TYPE_NULL: case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: @@ -2368,7 +2403,7 @@ static int add_key_with_algorithm(File fptr, partition_info *part_info, /* If we already are within a comment, end that comment first. */ if (current_comment_start) err+= add_string(fptr, "*/ "); - err+= add_string(fptr, "/*!50531 "); + err+= add_string(fptr, "/*!50611 "); err+= add_part_key_word(fptr, partition_keywords[PKW_ALGORITHM].str); err+= add_equal(fptr); err+= add_space(fptr); @@ -2611,8 +2646,7 @@ char *generate_partition_syntax(partition_info *part_info, { if (!use_sql_alloc) my_free(buf); - else - buf= NULL; + buf= NULL; } else buf[*buf_length]= 0; @@ -2694,114 +2728,13 @@ static inline int part_val_int(Item *item_expr, longlong *result) We have a set of support functions for these 14 variants. There are 4 variants of hash functions and there is a function for each. The KEY - partitioning uses the function calculate_key_value to calculate the hash + partitioning uses the function calculate_key_hash_value to calculate the hash value based on an array of fields. The linear hash variants uses the method get_part_id_from_linear_hash to get the partition id using the hash value and some parameters calculated from the number of partitions. */ /* - Calculate hash value for KEY partitioning using an array of fields. - - SYNOPSIS - calculate_key_value() - field_array An array of the fields in KEY partitioning - - RETURN VALUE - hash_value calculated - - DESCRIPTION - Uses the hash function on the character set of the field. Integer and - floating point fields use the binary character set by default. -*/ - -static uint32 calculate_key_value(Field **field_array) -{ - ulong nr1= 1; - ulong nr2= 4; - bool use_51_hash; - use_51_hash= test((*field_array)->table->part_info->key_algorithm == - partition_info::KEY_ALGORITHM_51); - - do - { - Field *field= *field_array; - if (use_51_hash) - { - switch (field->real_type()) { - case MYSQL_TYPE_TINY: - case MYSQL_TYPE_SHORT: - case MYSQL_TYPE_LONG: - case MYSQL_TYPE_FLOAT: - case MYSQL_TYPE_DOUBLE: - case MYSQL_TYPE_NEWDECIMAL: - case MYSQL_TYPE_TIMESTAMP: - case MYSQL_TYPE_LONGLONG: - case MYSQL_TYPE_INT24: - case MYSQL_TYPE_TIME: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_YEAR: - case MYSQL_TYPE_NEWDATE: - { - if (field->is_null()) - { - nr1^= (nr1 << 1) | 1; - continue; - } - /* Force this to my_hash_sort_bin, which was used in 5.1! */ - uint len= field->pack_length(); - my_charset_bin.coll->hash_sort(&my_charset_bin, field->ptr, len, - &nr1, &nr2); - /* Done with this field, continue with next one. */ - continue; - } - case MYSQL_TYPE_STRING: - case MYSQL_TYPE_VARCHAR: - case MYSQL_TYPE_BIT: - /* Not affected, same in 5.1 and 5.5 */ - break; - /* - ENUM/SET uses my_hash_sort_simple in 5.1 (i.e. my_charset_latin1) - and my_hash_sort_bin in 5.5! - */ - case MYSQL_TYPE_ENUM: - case MYSQL_TYPE_SET: - { - if (field->is_null()) - { - nr1^= (nr1 << 1) | 1; - continue; - } - /* Force this to my_hash_sort_bin, which was used in 5.1! */ - uint len= field->pack_length(); - my_charset_latin1.coll->hash_sort(&my_charset_latin1, field->ptr, - len, &nr1, &nr2); - continue; - } - /* These types should not be allowed for partitioning! */ - case MYSQL_TYPE_NULL: - case MYSQL_TYPE_DECIMAL: - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_TINY_BLOB: - case MYSQL_TYPE_MEDIUM_BLOB: - case MYSQL_TYPE_LONG_BLOB: - case MYSQL_TYPE_BLOB: - case MYSQL_TYPE_VAR_STRING: - case MYSQL_TYPE_GEOMETRY: - /* fall through. */ - default: - DBUG_ASSERT(0); // New type? - /* Fall through for default hashing (5.5). */ - } - /* fall through, use collation based hashing. */ - } - field->hash(&nr1, &nr2); - } while (*(++field_array)); - return (uint32) nr1; -} - - -/* A simple support function to calculate part_id given local part and sub part. @@ -2888,25 +2821,25 @@ static int get_part_id_linear_hash(partition_info *part_info, } -/* +/** Calculate part_id for (SUB)PARTITION BY KEY - SYNOPSIS - get_part_id_key() - field_array Array of fields for PARTTION KEY - num_parts Number of KEY partitions + @param file Handler to storage engine + @param field_array Array of fields for PARTTION KEY + @param num_parts Number of KEY partitions + @param func_value[out] Returns calculated hash value - RETURN VALUE - Calculated partition id + @return Calculated partition id */ inline -static uint32 get_part_id_key(Field **field_array, +static uint32 get_part_id_key(handler *file, + Field **field_array, uint num_parts, longlong *func_value) { DBUG_ENTER("get_part_id_key"); - *func_value= calculate_key_value(field_array); + *func_value= ha_partition::calculate_key_hash_value(field_array); DBUG_RETURN((uint32) (*func_value % num_parts)); } @@ -2933,7 +2866,7 @@ static uint32 get_part_id_linear_key(partition_info *part_info, { DBUG_ENTER("get_part_id_linear_key"); - *func_value= calculate_key_value(field_array); + *func_value= ha_partition::calculate_key_hash_value(field_array); DBUG_RETURN(get_part_id_from_linear_hash(*func_value, part_info->linear_hash_mask, num_parts)); @@ -2969,7 +2902,7 @@ static void copy_to_part_field_buffers(Field **ptr, restore_ptr++; if (!field->maybe_null() || !field->is_null()) { - CHARSET_INFO *cs= ((Field_str*)field)->charset(); + CHARSET_INFO *cs= field->charset(); uint max_len= field->pack_length(); uint data_len= field->data_length(); uchar *field_buf= *field_bufs; @@ -3372,7 +3305,7 @@ uint32 get_list_array_idx_for_endpoint(partition_info *part_info, } else { - DBUG_RETURN(list_index + test(left_endpoint ^ include_endpoint)); + DBUG_RETURN(list_index + MY_TEST(left_endpoint ^ include_endpoint)); } } while (max_list_index >= min_list_index); notfound: @@ -3634,7 +3567,8 @@ int get_partition_id_key_nosub(partition_info *part_info, uint32 *part_id, longlong *func_value) { - *part_id= get_part_id_key(part_info->part_field_array, + *part_id= get_part_id_key(part_info->table->file, + part_info->part_field_array, part_info->num_parts, func_value); return 0; } @@ -3724,7 +3658,8 @@ int get_partition_id_key_sub(partition_info *part_info, uint32 *part_id) { longlong func_value; - *part_id= get_part_id_key(part_info->subpart_field_array, + *part_id= get_part_id_key(part_info->table->file, + part_info->subpart_field_array, part_info->num_subparts, &func_value); return FALSE; } @@ -3961,6 +3896,92 @@ void get_full_part_id_from_key(const TABLE *table, uchar *buf, DBUG_VOID_RETURN; } + +/** + @brief Verify that all rows in a table is in the given partition + + @param table Table which contains the data that will be checked if + it is matching the partition definition. + @param part_table Partitioned table containing the partition to check. + @param part_id Which partition to match with. + + @return Operation status + @retval TRUE Not all rows match the given partition + @retval FALSE OK +*/ +bool verify_data_with_partition(TABLE *table, TABLE *part_table, + uint32 part_id) +{ + uint32 found_part_id; + longlong func_value; /* Unused */ + handler *file; + int error; + uchar *old_rec; + partition_info *part_info; + DBUG_ENTER("verify_data_with_partition"); + DBUG_ASSERT(table && table->file && part_table && part_table->part_info && + part_table->file); + + /* + Verify all table rows. + First implementation uses full scan + evaluates partition functions for + every row. TODO: add optimization to use index if possible, see WL#5397. + + 1) Open both tables (already done) and set the row buffers to use + the same buffer (to avoid copy). + 2) Init rnd on table. + 3) loop over all rows. + 3.1) verify that partition_id on the row is correct. Break if error. + */ + file= table->file; + part_info= part_table->part_info; + bitmap_union(table->read_set, &part_info->full_part_field_set); + old_rec= part_table->record[0]; + part_table->record[0]= table->record[0]; + set_field_ptr(part_info->full_part_field_array, table->record[0], old_rec); + if ((error= file->ha_rnd_init(TRUE))) + { + file->print_error(error, MYF(0)); + goto err; + } + + do + { + if ((error= file->ha_rnd_next(table->record[0]))) + { + if (error == HA_ERR_RECORD_DELETED) + continue; + if (error == HA_ERR_END_OF_FILE) + error= 0; + else + file->print_error(error, MYF(0)); + break; + } + if ((error= part_info->get_partition_id(part_info, &found_part_id, + &func_value))) + { + part_table->file->print_error(error, MYF(0)); + break; + } + DEBUG_SYNC(current_thd, "swap_partition_first_row_read"); + if (found_part_id != part_id) + { + my_error(ER_ROW_DOES_NOT_MATCH_PARTITION, MYF(0)); + error= 1; + break; + } + } while (TRUE); + (void) file->ha_rnd_end(); +err: + set_field_ptr(part_info->full_part_field_array, old_rec, + table->record[0]); + part_table->record[0]= old_rec; + if (error) + DBUG_RETURN(TRUE); + DBUG_RETURN(FALSE); +} + + /* Prune the set of partitions to use in query @@ -3971,7 +3992,7 @@ void get_full_part_id_from_key(const TABLE *table, uchar *buf, DESCRIPTION This function is called to prune the range of partitions to scan by - checking the used_partitions bitmap. + checking the read_partitions bitmap. If start_part > end_part at return it means no partition needs to be scanned. If start_part == end_part it always means a single partition needs to be scanned. @@ -3988,7 +4009,7 @@ void prune_partition_set(const TABLE *table, part_id_range *part_spec) DBUG_ENTER("prune_partition_set"); for (i= part_spec->start_part; i <= part_spec->end_part; i++) { - if (bitmap_is_set(&(part_info->used_partitions), i)) + if (bitmap_is_set(&(part_info->read_partitions), i)) { DBUG_PRINT("info", ("Partition %d is set", i)); if (last_partition == -1) @@ -4070,7 +4091,7 @@ void get_partition_set(const TABLE *table, uchar *buf, const uint index, */ get_full_part_id_from_key(table,buf,key_info,key_spec,part_spec); /* - Check if range can be adjusted by looking in used_partitions + Check if range can be adjusted by looking in read_partitions */ prune_partition_set(table, part_spec); DBUG_VOID_RETURN; @@ -4122,7 +4143,7 @@ void get_partition_set(const TABLE *table, uchar *buf, const uint index, get_full_part_id_from_key(table,buf,key_info,key_spec,part_spec); clear_indicator_in_key_fields(key_info); /* - Check if range can be adjusted by looking in used_partitions + Check if range can be adjusted by looking in read_partitions */ prune_partition_set(table, part_spec); DBUG_VOID_RETURN; @@ -4192,7 +4213,7 @@ void get_partition_set(const TABLE *table, uchar *buf, const uint index, if (found_part_field) clear_indicator_in_key_fields(key_info); /* - Check if range can be adjusted by looking in used_partitions + Check if range can be adjusted by looking in read_partitions */ prune_partition_set(table, part_spec); DBUG_VOID_RETURN; @@ -4263,9 +4284,11 @@ bool mysql_unpack_partition(THD *thd, { bool result= TRUE; partition_info *part_info; - CHARSET_INFO *old_character_set_client= thd->variables.character_set_client; + CHARSET_INFO *old_character_set_client= + thd->variables.character_set_client; LEX *old_lex= thd->lex; LEX lex; + PSI_statement_locker *parent_locker= thd->m_statement_psi; DBUG_ENTER("mysql_unpack_partition"); thd->variables.character_set_client= system_charset_info; @@ -4295,12 +4318,16 @@ bool mysql_unpack_partition(THD *thd, } part_info= lex.part_info; DBUG_PRINT("info", ("Parse: %s", part_buf)); + + thd->m_statement_psi= NULL; if (parse_sql(thd, & parser_state, NULL) || part_info->fix_parser_data(thd)) { thd->free_items(); + thd->m_statement_psi= parent_locker; goto end; } + thd->m_statement_psi= parent_locker; /* The parsed syntax residing in the frm file can still contain defaults. The reason is that the frm file is sometimes saved outside of this @@ -4340,6 +4367,7 @@ bool mysql_unpack_partition(THD *thd, *work_part_info_used= true; } table->part_info= part_info; + part_info->table= table; table->file->set_part_info(part_info); if (!part_info->default_engine_type) part_info->default_engine_type= default_db_type; @@ -4449,7 +4477,7 @@ static int fast_end_partition(THD *thd, ulonglong copied, query_cache_invalidate3(thd, table_list, 0); - my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO), + my_snprintf(tmp_name, sizeof(tmp_name), ER_THD(thd, ER_INSERT_INFO), (ulong) (copied + deleted), (ulong) deleted, (ulong) 0); @@ -4557,7 +4585,7 @@ bool set_part_state(Alter_info *alter_info, partition_info *tab_part_info, do { partition_element *part_elem= part_it++; - if ((alter_info->flags & ALTER_ALL_PARTITION) || + if ((alter_info->flags & Alter_info::ALTER_ALL_PARTITION) || (is_name_in_list(part_elem->partition_name, alter_info->partition_names))) { @@ -4576,7 +4604,7 @@ bool set_part_state(Alter_info *alter_info, partition_info *tab_part_info, } while (++part_count < tab_part_info->num_parts); if (num_parts_found != alter_info->partition_names.elements && - !(alter_info->flags & ALTER_ALL_PARTITION)) + !(alter_info->flags & Alter_info::ALTER_ALL_PARTITION)) { /* Not all given partitions found, revert and return failure */ part_it.rewind(); @@ -4593,16 +4621,56 @@ bool set_part_state(Alter_info *alter_info, partition_info *tab_part_info, /** + @brief Check if partition is exchangable with table by checking table options + + @param table_create_info Table options from table. + @param part_elem All the info of the partition. + + @retval FALSE if they are equal, otherwise TRUE. + + @note Any differens that would cause a change in the frm file is prohibited. + Such options as data_file_name, index_file_name, min_rows, max_rows etc. are + not allowed to differ. But comment is allowed to differ. +*/ +bool compare_partition_options(HA_CREATE_INFO *table_create_info, + partition_element *part_elem) +{ +#define MAX_COMPARE_PARTITION_OPTION_ERRORS 5 + const char *option_diffs[MAX_COMPARE_PARTITION_OPTION_ERRORS + 1]; + int i, errors= 0; + DBUG_ENTER("compare_partition_options"); + DBUG_ASSERT(!part_elem->tablespace_name && + !table_create_info->tablespace); + + /* + Note that there are not yet any engine supporting tablespace together + with partitioning. TODO: when there are, add compare. + */ + if (part_elem->tablespace_name || table_create_info->tablespace) + option_diffs[errors++]= "TABLESPACE"; + if (part_elem->part_max_rows != table_create_info->max_rows) + option_diffs[errors++]= "MAX_ROWS"; + if (part_elem->part_min_rows != table_create_info->min_rows) + option_diffs[errors++]= "MIN_ROWS"; + + for (i= 0; i < errors; i++) + my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0), + option_diffs[i]); + DBUG_RETURN(errors != 0); +} + + +/* Prepare for ALTER TABLE of partition structure @param[in] thd Thread object @param[in] table Table object @param[in,out] alter_info Alter information @param[in,out] create_info Create info for CREATE TABLE - @param[in] old_db_type Old engine type + @param[in] alter_ctx ALTER TABLE runtime context @param[out] partition_changed Boolean indicating whether partition changed - @param[out] fast_alter_table Internal temporary table allowing fast - partition change or NULL if not possible + @param[out] fast_alter_table Boolean indicating if fast partition alter is + possible. @return Operation status @retval TRUE Error @@ -4620,36 +4688,48 @@ bool set_part_state(Alter_info *alter_info, partition_info *tab_part_info, uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, HA_CREATE_INFO *create_info, - handlerton *old_db_type, + Alter_table_ctx *alter_ctx, bool *partition_changed, - char *db, - const char *table_name, - const char *path, - TABLE **fast_alter_table) + bool *fast_alter_table) { - TABLE *new_table= NULL; DBUG_ENTER("prep_alter_part_table"); /* Foreign keys on partitioned tables are not supported, waits for WL#148 */ - if (table->part_info && (alter_info->flags & ALTER_FOREIGN_KEY)) + if (table->part_info && (alter_info->flags & Alter_info::ADD_FOREIGN_KEY || + alter_info->flags & Alter_info::DROP_FOREIGN_KEY)) { my_error(ER_FOREIGN_KEY_ON_PARTITIONED, MYF(0)); DBUG_RETURN(TRUE); } + /* Remove partitioning on a not partitioned table is not possible */ + if (!table->part_info && (alter_info->flags & + Alter_info::ALTER_REMOVE_PARTITIONING)) + { + my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(TRUE); + } - thd->work_part_info= thd->lex->part_info; + /* + One of these is done in handle_if_exists_option(): + thd->work_part_info= thd->lex->part_info; + or + thd->work_part_info= NULL; + */ if (thd->work_part_info && - !(thd->work_part_info= thd->work_part_info->get_clone())) + !(thd->work_part_info= thd->work_part_info->get_clone(thd))) DBUG_RETURN(TRUE); /* ALTER_ADMIN_PARTITION is handled in mysql_admin_table */ - DBUG_ASSERT(!(alter_info->flags & ALTER_ADMIN_PARTITION)); + DBUG_ASSERT(!(alter_info->flags & Alter_info::ALTER_ADMIN_PARTITION)); if (alter_info->flags & - (ALTER_ADD_PARTITION | ALTER_DROP_PARTITION | - ALTER_COALESCE_PARTITION | ALTER_REORGANIZE_PARTITION | - ALTER_TABLE_REORG | ALTER_REBUILD_PARTITION)) + (Alter_info::ALTER_ADD_PARTITION | + Alter_info::ALTER_DROP_PARTITION | + Alter_info::ALTER_COALESCE_PARTITION | + Alter_info::ALTER_REORGANIZE_PARTITION | + Alter_info::ALTER_TABLE_REORG | + Alter_info::ALTER_REBUILD_PARTITION)) { partition_info *tab_part_info; partition_info *alt_part_info= thd->work_part_info; @@ -4671,30 +4751,31 @@ uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, Open it as a copy of the original table, and modify its partition_info object to allow fast_alter_partition_table to perform the changes. */ - DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, + alter_ctx->db, + alter_ctx->table_name, MDL_INTENTION_EXCLUSIVE)); - new_table= open_table_uncached(thd, path, db, table_name, 0); - if (!new_table) - DBUG_RETURN(TRUE); - /* - This table may be used for copy rows between partitions - and also read/write columns when fixing the partition_info struct. - */ - new_table->use_all_columns(); - - tab_part_info= new_table->part_info; + tab_part_info= table->part_info; - if (alter_info->flags & ALTER_TABLE_REORG) + if (alter_info->flags & Alter_info::ALTER_TABLE_REORG) { uint new_part_no, curr_part_no; + /* + 'ALTER TABLE t REORG PARTITION' only allowed with auto partition + if default partitioning is used. + */ + if (tab_part_info->part_type != HASH_PARTITION || - tab_part_info->use_default_num_partitions) + ((table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) && + !tab_part_info->use_default_num_partitions) || + ((!(table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION)) && + tab_part_info->use_default_num_partitions)) { my_error(ER_REORG_NO_PARAM_ERROR, MYF(0)); goto err; } - new_part_no= new_table->file->get_default_no_partitions(create_info); + new_part_no= table->file->get_default_no_partitions(create_info); curr_part_no= tab_part_info->num_parts; if (new_part_no == curr_part_no) { @@ -4703,7 +4784,23 @@ uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, after the change as before. Thus we can reply ok immediately without any changes at all. */ - *fast_alter_table= new_table; + flags= table->file->alter_table_flags(alter_info->flags); + if (flags & (HA_FAST_CHANGE_PARTITION | HA_PARTITION_ONE_PHASE)) + { + *fast_alter_table= true; + /* Force table re-open for consistency with the main case. */ + table->m_needs_reopen= true; + } + else + { + /* + Create copy of partition_info to avoid modifying original + TABLE::part_info, to keep it safe for later use. + */ + if (!(tab_part_info= tab_part_info->get_clone(thd))) + DBUG_RETURN(TRUE); + } + thd->work_part_info= tab_part_info; DBUG_RETURN(FALSE); } @@ -4713,7 +4810,7 @@ uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, We will add more partitions, we use the ADD PARTITION without setting the flag for no default number of partitions */ - alter_info->flags|= ALTER_ADD_PARTITION; + alter_info->flags|= Alter_info::ALTER_ADD_PARTITION; thd->work_part_info->num_parts= new_part_no - curr_part_no; } else @@ -4722,21 +4819,41 @@ uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, We will remove hash partitions, we use the COALESCE PARTITION without setting the flag for no default number of partitions */ - alter_info->flags|= ALTER_COALESCE_PARTITION; + alter_info->flags|= Alter_info::ALTER_COALESCE_PARTITION; alter_info->num_parts= curr_part_no - new_part_no; } } - if (!(flags= new_table->file->alter_table_flags(alter_info->flags))) + if (!(flags= table->file->alter_table_flags(alter_info->flags))) { my_error(ER_PARTITION_FUNCTION_FAILURE, MYF(0)); goto err; } if ((flags & (HA_FAST_CHANGE_PARTITION | HA_PARTITION_ONE_PHASE)) != 0) - *fast_alter_table= new_table; - DBUG_PRINT("info", ("*fast_alter_table: %p flags: 0x%x", - *fast_alter_table, flags)); - if ((alter_info->flags & ALTER_ADD_PARTITION) || - (alter_info->flags & ALTER_REORGANIZE_PARTITION)) + { + /* + "Fast" change of partitioning is supported in this case. + We will change TABLE::part_info (as this is how we pass + information to storage engine in this case), so the table + must be reopened. + */ + *fast_alter_table= true; + table->m_needs_reopen= true; + } + else + { + /* + "Fast" changing of partitioning is not supported. Create + a copy of TABLE::part_info object, so we can modify it safely. + Modifying original TABLE::part_info will cause problems when + we read data from old version of table using this TABLE object + while copying them to new version of table. + */ + if (!(tab_part_info= tab_part_info->get_clone(thd))) + DBUG_RETURN(TRUE); + } + DBUG_PRINT("info", ("*fast_alter_table flags: 0x%x", flags)); + if ((alter_info->flags & Alter_info::ALTER_ADD_PARTITION) || + (alter_info->flags & Alter_info::ALTER_REORGANIZE_PARTITION)) { if (thd->work_part_info->part_type != tab_part_info->part_type) { @@ -4798,7 +4915,7 @@ uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, goto err; } } - if (alter_info->flags & ALTER_ADD_PARTITION) + if (alter_info->flags & Alter_info::ALTER_ADD_PARTITION) { /* We start by moving the new partitions to the list of temporary @@ -4849,8 +4966,7 @@ uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, } alt_part_info->part_type= tab_part_info->part_type; alt_part_info->subpart_type= tab_part_info->subpart_type; - if (alt_part_info->set_up_defaults_for_partitioning(new_table->file, - ULL(0), + if (alt_part_info->set_up_defaults_for_partitioning(table->file, 0, tab_part_info->num_parts)) { goto err; @@ -5024,7 +5140,7 @@ that are reorganised. partition_element *part_elem= alt_it++; if (*fast_alter_table) part_elem->part_state= PART_TO_BE_ADDED; - if (tab_part_info->partitions.push_back(part_elem)) + if (tab_part_info->partitions.push_back(part_elem, thd->mem_root)) { mem_alloc_error(1); goto err; @@ -5038,7 +5154,7 @@ that are reorganised. of partitions anymore. We use this code also for Table reorganisations and here we don't set any default flags to FALSE. */ - if (!(alter_info->flags & ALTER_TABLE_REORG)) + if (!(alter_info->flags & Alter_info::ALTER_TABLE_REORG)) { if (!alt_part_info->use_default_partitions) { @@ -5049,7 +5165,7 @@ that are reorganised. tab_part_info->is_auto_partitioned= FALSE; } } - else if (alter_info->flags & ALTER_DROP_PARTITION) + else if (alter_info->flags & Alter_info::ALTER_DROP_PARTITION) { /* Drop a partition from a range partition and list partitioning is @@ -5093,14 +5209,14 @@ that are reorganised. my_error(ER_DROP_PARTITION_NON_EXISTENT, MYF(0), "DROP"); goto err; } - if (new_table->file->is_fk_defined_on_table_or_index(MAX_KEY)) + if (table->file->is_fk_defined_on_table_or_index(MAX_KEY)) { my_error(ER_ROW_IS_REFERENCED, MYF(0)); goto err; } tab_part_info->num_parts-= num_parts_dropped; } - else if (alter_info->flags & ALTER_REBUILD_PARTITION) + else if (alter_info->flags & Alter_info::ALTER_REBUILD_PARTITION) { set_engine_all_partitions(tab_part_info, tab_part_info->default_engine_type); @@ -5111,11 +5227,11 @@ that are reorganised. } if (!(*fast_alter_table)) { - new_table->file->print_error(HA_ERR_WRONG_COMMAND, MYF(0)); + table->file->print_error(HA_ERR_WRONG_COMMAND, MYF(0)); goto err; } } - else if (alter_info->flags & ALTER_COALESCE_PARTITION) + else if (alter_info->flags & Alter_info::ALTER_COALESCE_PARTITION) { uint num_parts_coalesced= alter_info->num_parts; uint num_parts_remain= tab_part_info->num_parts - num_parts_coalesced; @@ -5213,13 +5329,13 @@ state of p1. } while (part_count < tab_part_info->num_parts); tab_part_info->num_parts= num_parts_remain; } - if (!(alter_info->flags & ALTER_TABLE_REORG)) + if (!(alter_info->flags & Alter_info::ALTER_TABLE_REORG)) { tab_part_info->use_default_num_partitions= FALSE; tab_part_info->is_auto_partitioned= FALSE; } } - else if (alter_info->flags & ALTER_REORGANIZE_PARTITION) + else if (alter_info->flags & Alter_info::ALTER_REORGANIZE_PARTITION) { /* Reorganise partitions takes a number of partitions that are next @@ -5267,9 +5383,9 @@ state of p1. alt_part_info->subpart_type= tab_part_info->subpart_type; alt_part_info->num_subparts= tab_part_info->num_subparts; DBUG_ASSERT(!alt_part_info->use_default_partitions); - if (alt_part_info->set_up_defaults_for_partitioning(new_table->file, - ULL(0), - 0)) + /* We specified partitions explicitly so don't use defaults anymore. */ + tab_part_info->use_default_partitions= FALSE; + if (alt_part_info->set_up_defaults_for_partitioning(table->file, 0, 0)) { goto err; } @@ -5331,7 +5447,8 @@ the generated partition syntax in a correct manner. else tab_max_range= part_elem->range_value; if (*fast_alter_table && - tab_part_info->temp_partitions.push_back(part_elem)) + tab_part_info->temp_partitions.push_back(part_elem, + thd->mem_root)) { mem_alloc_error(1); goto err; @@ -5392,8 +5509,8 @@ the generated partition syntax in a correct manner. } *partition_changed= TRUE; thd->work_part_info= tab_part_info; - if (alter_info->flags & ALTER_ADD_PARTITION || - alter_info->flags & ALTER_REORGANIZE_PARTITION) + if (alter_info->flags & Alter_info::ALTER_ADD_PARTITION || + alter_info->flags & Alter_info::ALTER_REORGANIZE_PARTITION) { if (tab_part_info->use_default_subpartitions && !alt_part_info->use_default_subpartitions) @@ -5402,7 +5519,7 @@ the generated partition syntax in a correct manner. tab_part_info->use_default_num_subpartitions= FALSE; } if (tab_part_info->check_partition_info(thd, (handlerton**)NULL, - new_table->file, ULL(0), TRUE)) + table->file, 0, TRUE)) { goto err; } @@ -5411,7 +5528,7 @@ the generated partition syntax in a correct manner. since this function "fixes" the item trees of the new partitions to reorganize into */ - if (alter_info->flags == ALTER_REORGANIZE_PARTITION && + if (alter_info->flags == Alter_info::ALTER_REORGANIZE_PARTITION && tab_part_info->part_type == RANGE_PARTITION && ((is_last_partition_reorged && (tab_part_info->column_list ? @@ -5494,7 +5611,7 @@ the generated partition syntax in a correct manner. if (tab_part_info) { - if (alter_info->flags & ALTER_REMOVE_PARTITIONING) + if (alter_info->flags & Alter_info::ALTER_REMOVE_PARTITIONING) { DBUG_PRINT("info", ("Remove partitioning")); if (!(create_info->used_fields & HA_CREATE_USED_ENGINE)) @@ -5515,7 +5632,7 @@ the generated partition syntax in a correct manner. Create a copy of TABLE::part_info to be able to modify it freely. */ - if (!(tab_part_info= tab_part_info->get_clone())) + if (!(tab_part_info= tab_part_info->get_clone(thd))) DBUG_RETURN(TRUE); thd->work_part_info= tab_part_info; if (create_info->used_fields & HA_CREATE_USED_ENGINE && @@ -5565,8 +5682,10 @@ the generated partition syntax in a correct manner. rebuild). This is to handle KEY (numeric_cols) partitioned tables created in 5.1. For more info, see bug#14521864. */ - if (alter_info->flags != ALTER_PARTITION || + if (alter_info->flags != Alter_info::ALTER_PARTITION || !table->part_info || + alter_info->requested_algorithm != + Alter_info::ALTER_TABLE_ALGORITHM_INPLACE || !table->part_info->has_same_partitioning(part_info)) { DBUG_PRINT("info", ("partition changed")); @@ -5602,15 +5721,7 @@ the generated partition syntax in a correct manner. } DBUG_RETURN(FALSE); err: - if (new_table) - { - /* - Only remove the intermediate table object and its share object, - do not remove the .frm file, since it is the original one. - */ - close_temporary(new_table, 1, 0); - } - *fast_alter_table= NULL; + *fast_alter_table= false; DBUG_RETURN(TRUE); } @@ -5651,12 +5762,7 @@ static bool mysql_change_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) build_table_filename(path, sizeof(path) - 1, lpt->db, lpt->table_name, "", 0); - /* First lock the original tables */ - if (file->ha_external_lock(thd, F_WRLCK)) - DBUG_RETURN(TRUE); - - /* Disable transactions for all new tables */ - if (mysql_trans_prepare_alter_copy_data(thd)) + if(mysql_trans_prepare_alter_copy_data(thd)) DBUG_RETURN(TRUE); /* TODO: test if bulk_insert would increase the performance */ @@ -5671,10 +5777,7 @@ static bool mysql_change_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) if (mysql_trans_commit_alter_copy_data(thd)) error= 1; /* The error has been reported */ - if (file->ha_external_lock(thd, F_UNLCK)) - error= 1; - - DBUG_RETURN(test(error)); + DBUG_RETURN(MY_TEST(error)); } @@ -5744,6 +5847,11 @@ static bool mysql_drop_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) int error; DBUG_ENTER("mysql_drop_partitions"); + DBUG_ASSERT(lpt->thd->mdl_context.is_lock_owner(MDL_key::TABLE, + lpt->table->s->db.str, + lpt->table->s->table_name.str, + MDL_EXCLUSIVE)); + build_table_filename(path, sizeof(path) - 1, lpt->db, lpt->table_name, "", 0); if ((error= lpt->table->file->ha_drop_partitions(path))) { @@ -6328,7 +6436,8 @@ static bool write_log_final_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt) if (write_log_changed_partitions(lpt, &next_entry, (const char*)path)) goto error; if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path, - lpt->alter_info->flags & ALTER_REORGANIZE_PARTITION)) + lpt->alter_info->flags & + Alter_info::ALTER_REORGANIZE_PARTITION)) goto error; if (write_log_replace_delete_frm(lpt, next_entry, shadow_path, path, TRUE)) goto error; @@ -6425,47 +6534,54 @@ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) { THD *thd= lpt->thd; - if (lpt->old_table) - close_all_tables_for_name(thd, lpt->old_table->s, HA_EXTRA_NOT_USED); if (lpt->table) { /* - Only remove the intermediate table object and its share object, - do not remove the .frm file, since it is the original one. + Remove all instances of the table and its locks and other resources. */ - close_temporary(lpt->table, 1, 0); + close_all_tables_for_name(thd, lpt->table->s, HA_EXTRA_NOT_USED, NULL); } lpt->table= 0; - lpt->old_table= 0; lpt->table_list->table= 0; - if (thd->locked_tables_list.reopen_tables(thd)) - sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE"); + if (thd->locked_tables_mode) + { + Diagnostics_area *stmt_da= NULL; + Diagnostics_area tmp_stmt_da(true); + + if (thd->is_error()) + { + /* reopen might fail if we have a previous error, use a temporary da. */ + stmt_da= thd->get_stmt_da(); + thd->set_stmt_da(&tmp_stmt_da); + } + + if (thd->locked_tables_list.reopen_tables(thd, false)) + sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE"); + + if (stmt_da) + thd->set_stmt_da(stmt_da); + } } -/* - Unlock and close table before renaming and dropping partitions - SYNOPSIS - alter_close_tables() - lpt Struct carrying parameters - close_old Close original table too - RETURN VALUES - 0 +/** + Unlock and close table before renaming and dropping partitions. + + @param lpt Struct carrying parameters + + @return Always 0. */ -static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt, bool close_old) +static int alter_close_table(ALTER_PARTITION_PARAM_TYPE *lpt) { - DBUG_ENTER("alter_close_tables"); + DBUG_ENTER("alter_close_table"); + if (lpt->table->db_stat) { + mysql_lock_remove(lpt->thd, lpt->thd->lock, lpt->table); lpt->table->file->ha_close(); lpt->table->db_stat= 0; // Mark file closed } - if (close_old && lpt->old_table) - { - close_all_tables_for_name(lpt->thd, lpt->old_table->s, HA_EXTRA_NOT_USED); - lpt->old_table= 0; - } DBUG_RETURN(0); } @@ -6487,23 +6603,54 @@ void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt, bool close_table) { partition_info *part_info= lpt->part_info; + THD *thd= lpt->thd; + TABLE *table= lpt->table; DBUG_ENTER("handle_alter_part_error"); + DBUG_ASSERT(table->m_needs_reopen); if (close_table) { /* - Since the error handling (ddl_log) needs to drop newly created - partitions they must be closed first to not issue errors. - But we still need some information from the part_info object, - so we clone it first to have a copy. + All instances of this table needs to be closed. + Better to do that here, than leave the cleaning up to others. + Aquire EXCLUSIVE mdl lock if not already aquired. + */ + if (!thd->mdl_context.is_lock_owner(MDL_key::TABLE, lpt->db, + lpt->table_name, + MDL_EXCLUSIVE)) + { + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + { + /* At least remove this instance on failure */ + goto err_exclusive_lock; + } + } + /* Ensure the share is destroyed and reopened. */ + part_info= lpt->part_info->get_clone(thd); + close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL); + } + else + { +err_exclusive_lock: + /* + Temporarily remove it from the locked table list, so that it will get + reopened. + */ + thd->locked_tables_list.unlink_from_list(thd, + table->pos_in_locked_tables, + false); + /* + Make sure that the table is unlocked, closed and removed from + the table cache. */ - part_info= lpt->part_info->get_clone(); - alter_close_tables(lpt, action_completed); + mysql_lock_remove(thd, thd->lock, table); + part_info= lpt->part_info->get_clone(thd); + close_thread_table(thd, &thd->open_tables); + lpt->table_list->table= NULL; } if (part_info->first_log_entry && - execute_ddl_log_entry(lpt->thd, - part_info->first_log_entry->entry_pos)) + execute_ddl_log_entry(thd, part_info->first_log_entry->entry_pos)) { /* We couldn't recover from error, most likely manual interaction @@ -6516,14 +6663,14 @@ void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt, if (drop_partition) { /* Table is still ok, but we left a shadow frm file behind. */ - push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1, "%s %s", "Operation was unsuccessful, table is still intact,", "but it is possible that a shadow frm file was left behind"); } else { - push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1, "%s %s %s %s", "Operation was unsuccessful, table is still intact,", "but it is possible that a shadow frm file was left behind.", @@ -6539,7 +6686,7 @@ void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt, Failed during install of shadow frm file, table isn't intact and dropped partitions are still there */ - push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1, "%s %s %s", "Failed during alter of partitions, table is no longer intact.", "The frm file is in an unknown state, and a backup", @@ -6553,7 +6700,7 @@ void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt, ask the user to perform the action manually. We remove the log records and ask the user to perform the action manually. */ - push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1, "%s %s", "Failed during drop of partitions, table is intact.", "Manual drop of remaining partitions is required"); @@ -6565,7 +6712,7 @@ void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt, certainly in a very bad state so we give user warning and disable the table by writing an ancient frm version into it. */ - push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1, "%s %s %s", "Failed during renaming of partitions. We are now in a position", "where table is not reusable", @@ -6594,11 +6741,31 @@ void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt, even though we reported an error the operation was successfully completed. */ - push_warning_printf(lpt->thd, MYSQL_ERROR::WARN_LEVEL_WARN, 1,"%s %s", + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, 1,"%s %s", "Operation was successfully completed by failure handling,", "after failure of normal operation"); } } + + if (thd->locked_tables_mode) + { + Diagnostics_area *stmt_da= NULL; + Diagnostics_area tmp_stmt_da(true); + + if (thd->is_error()) + { + /* reopen might fail if we have a previous error, use a temporary da. */ + stmt_da= thd->get_stmt_da(); + thd->set_stmt_da(&tmp_stmt_da); + } + + if (thd->locked_tables_list.reopen_tables(thd, false)) + sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE"); + + if (stmt_da) + thd->set_stmt_da(stmt_da); + } + DBUG_VOID_RETURN; } @@ -6615,7 +6782,7 @@ static void downgrade_mdl_if_lock_tables_mode(THD *thd, MDL_ticket *ticket, enum_mdl_type type) { if (thd->locked_tables_mode) - ticket->downgrade_exclusive_lock(type); + ticket->downgrade_lock(type); } @@ -6624,13 +6791,12 @@ static void downgrade_mdl_if_lock_tables_mode(THD *thd, MDL_ticket *ticket, previously prepared. @param thd Thread object - @param table Original table object + @param table Original table object with new part_info @param alter_info ALTER TABLE info @param create_info Create info for CREATE TABLE @param table_list List of the table involved @param db Database name of new table @param table_name Table name of new table - @param fast_alter_table Prepared table object @return Operation status @retval TRUE Error @@ -6646,8 +6812,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, char *db, - const char *table_name, - TABLE *fast_alter_table) + const char *table_name) { /* Set-up struct used to write frm files */ partition_info *part_info; @@ -6657,21 +6822,17 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, bool close_table_on_failure= FALSE; bool frm_install= FALSE; MDL_ticket *mdl_ticket= table->mdl_ticket; - DBUG_ASSERT(fast_alter_table); DBUG_ENTER("fast_alter_partition_table"); + DBUG_ASSERT(table->m_needs_reopen); - part_info= fast_alter_table->part_info; + part_info= table->part_info; lpt->thd= thd; lpt->table_list= table_list; lpt->part_info= part_info; lpt->alter_info= alter_info; lpt->create_info= create_info; - lpt->db_options= create_info->table_options; - if (create_info->row_type != ROW_TYPE_FIXED && - create_info->row_type != ROW_TYPE_DEFAULT) - lpt->db_options|= HA_OPTION_PACK_RECORD; - lpt->table= fast_alter_table; - lpt->old_table= table; + lpt->db_options= create_info->table_options_with_row_type(); + lpt->table= table; lpt->key_info_buffer= 0; lpt->key_count= 0; lpt->db= db; @@ -6681,9 +6842,6 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, lpt->pack_frm_data= NULL; lpt->pack_frm_len= 0; - /* Never update timestamp columns when alter */ - lpt->table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - if (table->file->alter_table_flags(alter_info->flags) & HA_PARTITION_ONE_PHASE) { @@ -6726,13 +6884,13 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 1) Write the new frm, pack it and then delete it 2) Perform the change within the handler */ - if (mysql_write_frm(lpt, WFRM_WRITE_SHADOW | WFRM_PACK_FRM) || + if (mysql_write_frm(lpt, WFRM_WRITE_SHADOW) || mysql_change_partitions(lpt)) { goto err; } } - else if (alter_info->flags & ALTER_DROP_PARTITION) + else if (alter_info->flags & Alter_info::ALTER_DROP_PARTITION) { /* Now after all checks and setting state on dropped partitions we can @@ -6767,9 +6925,9 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 3) Write the ddl log to ensure that the operation is completed even in the presence of a MySQL Server crash (the log is executed before any other threads are started, so there are no locking issues). - 4) Close all tables that have already been opened but didn't stumble on + 4) Close the table that have already been opened but didn't stumble on the abort locked previously. This is done as part of the - alter_close_tables call. + alter_close_table call. 5) Write the bin log Unfortunately the writing of the binlog is not synchronised with other logging activities. So no matter in which order the binlog @@ -6805,7 +6963,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, (action_completed= TRUE, FALSE) || ERROR_INJECT_CRASH("crash_drop_partition_4") || ERROR_INJECT_ERROR("fail_drop_partition_4") || - alter_close_tables(lpt, action_completed) || + alter_close_table(lpt) || (close_table_on_failure= FALSE, FALSE) || ERROR_INJECT_CRASH("crash_drop_partition_5") || ERROR_INJECT_ERROR("fail_drop_partition_5") || @@ -6832,7 +6990,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, goto err; } } - else if ((alter_info->flags & ALTER_ADD_PARTITION) && + else if ((alter_info->flags & Alter_info::ALTER_ADD_PARTITION) && (part_info->part_type == RANGE_PARTITION || part_info->part_type == LIST_PARTITION)) { @@ -6882,7 +7040,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, ERROR_INJECT_CRASH("crash_add_partition_5") || ERROR_INJECT_ERROR("fail_add_partition_5") || (close_table_on_failure= FALSE, FALSE) || - alter_close_tables(lpt, action_completed) || + alter_close_table(lpt) || ERROR_INJECT_CRASH("crash_add_partition_6") || ERROR_INJECT_ERROR("fail_add_partition_6") || ((!thd->lex->no_write_to_binlog) && @@ -6942,27 +7100,27 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, use a lower lock level. This can be handled inside store_lock in the respective handler. - 0) Write an entry that removes the shadow frm file if crash occurs - 1) Write the shadow frm file of new partitioning + 0) Write an entry that removes the shadow frm file if crash occurs. + 1) Write the shadow frm file of new partitioning. 2) Log such that temporary partitions added in change phase are - removed in a crash situation - 3) Add the new partitions - Copy from the reorganised partitions to the new partitions + removed in a crash situation. + 3) Add the new partitions. + Copy from the reorganised partitions to the new partitions. 4) Get an exclusive metadata lock on the table (waits for all active transactions using this table). This ensures that we can release all other locks on the table and since no one can open the table, there can be no new threads accessing the table. They will be hanging on this exclusive lock. - 5) Log that operation is completed and log all complete actions - needed to complete operation from here - 6) Write bin log - 7) Close all instances of the table and remove them from the table cache. - 8) Prepare handlers for rename and delete of partitions + 5) Close the table. + 6) Log that operation is completed and log all complete actions + needed to complete operation from here. + 7) Write bin log. + 8) Prepare handlers for rename and delete of partitions. 9) Rename and drop the reorged partitions such that they are no longer used and rename those added to their real new names. - 10) Install the shadow frm file - 11) Reopen the table if under lock tables - 12) Complete query + 10) Install the shadow frm file. + 11) Reopen the table if under lock tables. + 12) Complete query. */ if (write_log_drop_shadow_frm(lpt) || ERROR_INJECT_CRASH("crash_change_partition_1") || @@ -6980,22 +7138,22 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, wait_while_table_is_used(thd, table, HA_EXTRA_NOT_USED) || ERROR_INJECT_CRASH("crash_change_partition_5") || ERROR_INJECT_ERROR("fail_change_partition_5") || - write_log_final_change_partition(lpt) || - (action_completed= TRUE, FALSE) || + alter_close_table(lpt) || + (close_table_on_failure= FALSE, FALSE) || ERROR_INJECT_CRASH("crash_change_partition_6") || ERROR_INJECT_ERROR("fail_change_partition_6") || + write_log_final_change_partition(lpt) || + (action_completed= TRUE, FALSE) || + ERROR_INJECT_CRASH("crash_change_partition_7") || + ERROR_INJECT_ERROR("fail_change_partition_7") || ((!thd->lex->no_write_to_binlog) && (write_bin_log(thd, FALSE, thd->query(), thd->query_length()), FALSE)) || - ERROR_INJECT_CRASH("crash_change_partition_7") || - ERROR_INJECT_ERROR("fail_change_partition_7") || + ERROR_INJECT_CRASH("crash_change_partition_8") || + ERROR_INJECT_ERROR("fail_change_partition_8") || ((frm_install= TRUE), FALSE) || mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) || (frm_install= FALSE, FALSE) || - ERROR_INJECT_CRASH("crash_change_partition_8") || - ERROR_INJECT_ERROR("fail_change_partition_8") || - alter_close_tables(lpt, action_completed) || - (close_table_on_failure= FALSE, FALSE) || ERROR_INJECT_CRASH("crash_change_partition_9") || ERROR_INJECT_ERROR("fail_change_partition_9") || mysql_drop_partitions(lpt) || @@ -7021,22 +7179,6 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, */ DBUG_RETURN(fast_end_partition(thd, lpt->copied, lpt->deleted, table_list)); err: - if (action_completed) - { - /* - Although error occurred, the action was forced to retry for completion. - Therefore we must close+reopen all instances of the table. - */ - (void) alter_partition_lock_handling(lpt); - } - else - { - /* - The failed action was reverted, leave the original table as is and - close/destroy the intermediate table object and its share. - */ - close_temporary(lpt->table, 1, 0); - } downgrade_mdl_if_lock_tables_mode(thd, mdl_ticket, MDL_SHARED_NO_READ_WRITE); DBUG_RETURN(TRUE); } @@ -7099,7 +7241,7 @@ void set_key_field_ptr(KEY *key_info, const uchar *new_buf, const uchar *old_buf) { KEY_PART_INFO *key_part= key_info->key_part; - uint key_parts= key_info->key_parts; + uint key_parts= key_info->user_defined_key_parts; uint i= 0; my_ptrdiff_t diff= (new_buf - old_buf); DBUG_ENTER("set_key_field_ptr"); @@ -7135,23 +7277,27 @@ void mem_alloc_error(size_t size) } #ifdef WITH_PARTITION_STORAGE_ENGINE -/* - Return comma-separated list of used partitions in the provided given string +/** + Return comma-separated list of used partitions in the provided given string. - SYNOPSIS - make_used_partitions_str() - part_info IN Partitioning info - parts_str OUT The string to fill + @param mem_root Where to allocate following list + @param part_info Partitioning info + @param[out] parts The resulting list of string to fill + @param[out] used_partitions_list result list to fill - DESCRIPTION - Generate a list of used partitions (from bits in part_info->used_partitions - bitmap), asd store it into the provided String object. + Generate a list of used partitions (from bits in part_info->read_partitions + bitmap), and store it into the provided String object. - NOTE + @note The produced string must not be longer then MAX_PARTITIONS * (1 + FN_LEN). + In case of UPDATE, only the partitions read is given, not the partitions + that was written or locked. */ -void make_used_partitions_str(partition_info *part_info, String *parts_str) +void make_used_partitions_str(MEM_ROOT *alloc, + partition_info *part_info, + String *parts_str, + String_list &used_partitions_list) { parts_str->length(0); partition_element *pe; @@ -7166,10 +7312,11 @@ void make_used_partitions_str(partition_info *part_info, String *parts_str) List_iterator<partition_element> it2(head_pe->subpartitions); while ((pe= it2++)) { - if (bitmap_is_set(&part_info->used_partitions, partition_id)) + if (bitmap_is_set(&part_info->read_partitions, partition_id)) { if (parts_str->length()) parts_str->append(','); + uint index= parts_str->length(); parts_str->append(head_pe->partition_name, strlen(head_pe->partition_name), system_charset_info); @@ -7177,6 +7324,7 @@ void make_used_partitions_str(partition_info *part_info, String *parts_str) parts_str->append(pe->partition_name, strlen(pe->partition_name), system_charset_info); + used_partitions_list.append_str(alloc, parts_str->ptr() + index); } partition_id++; } @@ -7186,10 +7334,11 @@ void make_used_partitions_str(partition_info *part_info, String *parts_str) { while ((pe= it++)) { - if (bitmap_is_set(&part_info->used_partitions, partition_id)) + if (bitmap_is_set(&part_info->read_partitions, partition_id)) { if (parts_str->length()) parts_str->append(','); + used_partitions_list.append_str(alloc, pe->partition_name); parts_str->append(pe->partition_name, strlen(pe->partition_name), system_charset_info); } @@ -7744,7 +7893,7 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, index-in-ordered-array-of-list-constants (for LIST) space. */ store_key_image_to_rec(field, min_value, field_len); - bool include_endp= !test(flags & NEAR_MIN); + bool include_endp= !MY_TEST(flags & NEAR_MIN); part_iter->part_nums.start= get_endpoint(part_info, 1, include_endp); if (!can_match_multiple_values && part_info->part_expr->null_value) { @@ -7779,7 +7928,7 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, else { store_key_image_to_rec(field, max_value, field_len); - bool include_endp= !test(flags & NEAR_MAX); + bool include_endp= !MY_TEST(flags & NEAR_MAX); part_iter->part_nums.end= get_endpoint(part_info, 0, include_endp); if (check_zero_dates && !zero_in_start_date && @@ -7946,8 +8095,8 @@ int get_part_iter_for_interval_via_walking(partition_info *part_info, if ((ulonglong)b - (ulonglong)a == ~0ULL) DBUG_RETURN(-1); - a += test(flags & NEAR_MIN); - b += test(!(flags & NEAR_MAX)); + a+= MY_TEST(flags & NEAR_MIN); + b+= MY_TEST(!(flags & NEAR_MAX)); ulonglong n_values= b - a; /* @@ -8083,8 +8232,7 @@ static uint32 get_next_partition_via_walking(PARTITION_ITERATOR *part_iter) while (part_iter->field_vals.cur != part_iter->field_vals.end) { longlong dummy; - field->store(part_iter->field_vals.cur++, - ((Field_num*)field)->unsigned_flag); + field->store(part_iter->field_vals.cur++, field->flags & UNSIGNED_FLAG); if ((part_iter->part_info->is_sub_partitioned() && !part_iter->part_info->get_part_partition_id(part_iter->part_info, &part_id, &dummy)) || @@ -8108,12 +8256,11 @@ static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR *part_iter) part_iter->field_vals.cur= part_iter->field_vals.start; return NOT_A_PARTITION_ID; } - field->store(part_iter->field_vals.cur++, FALSE); + field->store(part_iter->field_vals.cur++, field->flags & UNSIGNED_FLAG); if (part_iter->part_info->get_subpartition_id(part_iter->part_info, &res)) return NOT_A_PARTITION_ID; return res; - } /* used in error messages below */ @@ -8166,6 +8313,12 @@ int create_partition_name(char *out, size_t outlen, const char *in1, end= strxnmov(out, outlen-1, in1, "#P#", transl_part, "#TMP#", NullS); else if (name_variant == RENAMED_PART_NAME) end= strxnmov(out, outlen-1, in1, "#P#", transl_part, "#REN#", NullS); + else + { + DBUG_ASSERT(0); + out[0]= 0; + end= out + (outlen-1); + } if (end - out == static_cast<ptrdiff_t>(outlen-1)) { my_error(ER_PATH_LENGTH, MYF(0), longest_str(in1, transl_part)); @@ -8208,6 +8361,12 @@ int create_subpartition_name(char *out, size_t outlen, else if (name_variant == RENAMED_PART_NAME) end= strxnmov(out, outlen-1, in1, "#P#", transl_part_name, "#SP#", transl_subpart_name, "#REN#", NullS); + else + { + DBUG_ASSERT(0); + out[0]= 0; + end= out + (outlen-1); + } if (end - out == static_cast<ptrdiff_t>(outlen-1)) { my_error(ER_PATH_LENGTH, MYF(0), diff --git a/sql/sql_partition.h b/sql/sql_partition.h index ea3802b49fe..6629537b2ae 100644 --- a/sql/sql_partition.h +++ b/sql/sql_partition.h @@ -17,7 +17,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifdef __GNUC__ +#ifdef USE_PRAGMA_INTERFACE #pragma interface /* gcc class implementation */ #endif @@ -25,6 +25,7 @@ #include "table.h" /* TABLE_LIST */ class Alter_info; +class Alter_table_ctx; class Field; class String; class handler; @@ -32,7 +33,6 @@ class partition_info; struct TABLE; struct TABLE_LIST; typedef struct st_bitmap MY_BITMAP; -typedef struct st_ha_create_information HA_CREATE_INFO; typedef struct st_key KEY; typedef struct st_key_range key_range; @@ -55,7 +55,6 @@ typedef struct st_lock_param_type HA_CREATE_INFO *create_info; Alter_info *alter_info; TABLE *table; - TABLE *old_table; KEY *key_info_buffer; const char *db; const char *table_name; @@ -76,8 +75,9 @@ typedef struct { uint32 end_part; } part_id_range; +class String_list; struct st_partition_iter; -#define NOT_A_PARTITION_ID ((uint32)-1) +#define NOT_A_PARTITION_ID UINT_MAX32 bool is_partition_in_list(char *part_name, List<char> list_part_names); char *are_partitions_in_table(partition_info *new_part_info, @@ -116,7 +116,9 @@ bool mysql_unpack_partition(THD *thd, char *part_buf, TABLE *table, bool is_create_table_ind, handlerton *default_db_type, bool *work_part_info_used); -void make_used_partitions_str(partition_info *part_info, String *parts_str); +void make_used_partitions_str(MEM_ROOT *mem_root, + partition_info *part_info, String *parts_str, + String_list &used_partitions_list); uint32 get_list_array_idx_for_endpoint(partition_info *part_info, bool left_endpoint, bool include_endpoint); @@ -127,6 +129,7 @@ bool check_part_func_fields(Field **ptr, bool ok_with_charsets); bool field_is_partition_charset(Field *field); Item* convert_charset_partition_constant(Item *item, CHARSET_INFO *cs); void mem_alloc_error(size_t size); +void truncate_partition_filename(char *path); /* A "Get next" function for partition iterator. @@ -252,24 +255,24 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, char *db, - const char *table_name, - TABLE *fast_alter_table); + const char *table_name); bool set_part_state(Alter_info *alter_info, partition_info *tab_part_info, enum partition_state part_state); uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, HA_CREATE_INFO *create_info, - handlerton *old_db_type, + Alter_table_ctx *alter_ctx, bool *partition_changed, - char *db, - const char *table_name, - const char *path, - TABLE **fast_alter_table); + bool *fast_alter_table); char *generate_partition_syntax(partition_info *part_info, uint *buf_length, bool use_sql_alloc, bool show_partition_options, HA_CREATE_INFO *create_info, Alter_info *alter_info, const char *current_comment_start); +bool verify_data_with_partition(TABLE *table, TABLE *part_table, + uint32 part_id); +bool compare_partition_options(HA_CREATE_INFO *table_create_info, + partition_element *part_elem); bool partition_key_modified(TABLE *table, const MY_BITMAP *fields); #else #define partition_key_modified(X,Y) 0 diff --git a/sql/sql_partition_admin.cc b/sql/sql_partition_admin.cc index a104676a3ff..d2fdee934ce 100644 --- a/sql/sql_partition_admin.cc +++ b/sql/sql_partition_admin.cc @@ -1,4 +1,5 @@ /* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2014, SkySQL Ab. 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 @@ -14,8 +15,15 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sql_parse.h" // check_one_table_access + // check_merge_table_access + // check_one_table_access #include "sql_table.h" // mysql_alter_table, etc. -#include "sql_lex.h" // Sql_statement +#include "sql_cmd.h" // Sql_cmd +#include "sql_alter.h" // Sql_cmd_alter_table +#include "sql_partition.h" // struct partition_info, etc. +#include "debug_sync.h" // DEBUG_SYNC +#include "sql_truncate.h" // mysql_truncate_table, + // Sql_cmd_truncate_table #include "sql_admin.h" // Analyze/Check/.._table_statement #include "sql_partition_admin.h" // Alter_table_*_partition #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -25,9 +33,9 @@ #ifndef WITH_PARTITION_STORAGE_ENGINE -bool Partition_statement_unsupported::execute(THD *) +bool Sql_cmd_partition_unsupported::execute(THD *) { - DBUG_ENTER("Partition_statement_unsupported::execute"); + DBUG_ENTER("Sql_cmd_partition_unsupported::execute"); /* error, partitioning support not compiled in... */ my_error(ER_FEATURE_DISABLED, MYF(0), "partitioning", "--with-plugin-partition"); @@ -36,41 +44,680 @@ bool Partition_statement_unsupported::execute(THD *) #else -bool Alter_table_analyze_partition_statement::execute(THD *thd) +bool Sql_cmd_alter_table_exchange_partition::execute(THD *thd) +{ + /* Moved from mysql_execute_command */ + LEX *lex= thd->lex; + /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ + SELECT_LEX *select_lex= &lex->select_lex; + /* first table of first SELECT_LEX */ + TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first; + /* + Code in mysql_alter_table() may modify its HA_CREATE_INFO argument, + so we have to use a copy of this structure to make execution + prepared statement- safe. A shallow copy is enough as no memory + referenced from this structure will be modified. + @todo move these into constructor... + */ + HA_CREATE_INFO create_info(lex->create_info); + Alter_info alter_info(lex->alter_info, thd->mem_root); + ulong priv_needed= ALTER_ACL | DROP_ACL | INSERT_ACL | CREATE_ACL; + + DBUG_ENTER("Sql_cmd_alter_table_exchange_partition::execute"); + + if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */ + DBUG_RETURN(TRUE); + + /* Must be set in the parser */ + DBUG_ASSERT(select_lex->db); + /* also check the table to be exchanged with the partition */ + DBUG_ASSERT(alter_info.flags & Alter_info::ALTER_EXCHANGE_PARTITION); + + if (check_access(thd, priv_needed, first_table->db, + &first_table->grant.privilege, + &first_table->grant.m_internal, + 0, 0) || + check_access(thd, priv_needed, first_table->next_local->db, + &first_table->next_local->grant.privilege, + &first_table->next_local->grant.m_internal, + 0, 0)) + DBUG_RETURN(TRUE); + + if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE)) + DBUG_RETURN(TRUE); + + /* Not allowed with EXCHANGE PARTITION */ + DBUG_ASSERT(!create_info.data_file_name && !create_info.index_file_name); + + DBUG_RETURN(exchange_partition(thd, first_table, &alter_info)); +} + + +/** + @brief Checks that the tables will be able to be used for EXCHANGE PARTITION. + @param table Non partitioned table. + @param part_table Partitioned table. + + @retval FALSE if OK, otherwise error is reported and TRUE is returned. +*/ +static bool check_exchange_partition(TABLE *table, TABLE *part_table) +{ + DBUG_ENTER("check_exchange_partition"); + + /* Both tables must exist */ + if (!part_table || !table) + { + my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0)); + DBUG_RETURN(TRUE); + } + + /* The first table must be partitioned, and the second must not */ + if (!part_table->part_info) + { + my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(TRUE); + } + if (table->part_info) + { + my_error(ER_PARTITION_EXCHANGE_PART_TABLE, MYF(0), + table->s->table_name.str); + DBUG_RETURN(TRUE); + } + + if (part_table->file->ht != partition_hton) + { + /* + Only allowed on partitioned tables throught the generic ha_partition + handler, i.e not yet for native partitioning. + */ + my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(TRUE); + } + + if (table->file->ht != part_table->part_info->default_engine_type) + { + my_error(ER_MIX_HANDLER_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } + + /* Verify that table is not tmp table, partitioned tables cannot be tmp. */ + if (table->s->tmp_table != NO_TMP_TABLE) + { + my_error(ER_PARTITION_EXCHANGE_TEMP_TABLE, MYF(0), + table->s->table_name.str); + DBUG_RETURN(TRUE); + } + + /* The table cannot have foreign keys constraints or be referenced */ + if(!table->file->can_switch_engines()) + { + my_error(ER_PARTITION_EXCHANGE_FOREIGN_KEY, MYF(0), + table->s->table_name.str); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); +} + + +/** + @brief Compare table structure/options between a non partitioned table + and a specific partition of a partitioned table. + + @param thd Thread object. + @param table Non partitioned table. + @param part_table Partitioned table. + @param part_elem Partition element to use for partition specific compare. +*/ +static bool compare_table_with_partition(THD *thd, TABLE *table, + TABLE *part_table, + partition_element *part_elem, + uint part_id) +{ + HA_CREATE_INFO table_create_info, part_create_info; + Alter_info part_alter_info; + Alter_table_ctx part_alter_ctx; // Not used + DBUG_ENTER("compare_table_with_partition"); + + bool metadata_equal= false; + memset(&part_create_info, 0, sizeof(HA_CREATE_INFO)); + memset(&table_create_info, 0, sizeof(HA_CREATE_INFO)); + + update_create_info_from_table(&table_create_info, table); + /* get the current auto_increment value */ + table->file->update_create_info(&table_create_info); + /* mark all columns used, since they are used when preparing the new table */ + part_table->use_all_columns(); + table->use_all_columns(); + if (mysql_prepare_alter_table(thd, part_table, &part_create_info, + &part_alter_info, &part_alter_ctx)) + { + my_error(ER_TABLES_DIFFERENT_METADATA, MYF(0)); + DBUG_RETURN(TRUE); + } + /* db_type is not set in prepare_alter_table */ + part_create_info.db_type= part_table->part_info->default_engine_type; + ((ha_partition*)(part_table->file))->update_part_create_info(&part_create_info, part_id); + /* + Since we exchange the partition with the table, allow exchanging + auto_increment value as well. + */ + part_create_info.auto_increment_value= + table_create_info.auto_increment_value; + + /* Check compatible row_types and set create_info accordingly. */ + { + enum row_type part_row_type= part_table->file->get_row_type(); + enum row_type table_row_type= table->file->get_row_type(); + if (part_row_type != table_row_type) + { + my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0), + "ROW_FORMAT"); + DBUG_RETURN(true); + } + part_create_info.row_type= table->s->row_type; + } + + /* + NOTE: ha_blackhole does not support check_if_compatible_data, + so this always fail for blackhole tables. + ha_myisam compares pointers to verify that DATA/INDEX DIRECTORY is + the same, so any table using data/index_file_name will fail. + */ + if (mysql_compare_tables(table, &part_alter_info, &part_create_info, + &metadata_equal)) + { + my_error(ER_TABLES_DIFFERENT_METADATA, MYF(0)); + DBUG_RETURN(TRUE); + } + + DEBUG_SYNC(thd, "swap_partition_after_compare_tables"); + if (!metadata_equal) + { + my_error(ER_TABLES_DIFFERENT_METADATA, MYF(0)); + DBUG_RETURN(TRUE); + } + DBUG_ASSERT(table->s->db_create_options == + part_table->s->db_create_options); + DBUG_ASSERT(table->s->db_options_in_use == + part_table->s->db_options_in_use); + + if (table_create_info.avg_row_length != part_create_info.avg_row_length) + { + my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0), + "AVG_ROW_LENGTH"); + DBUG_RETURN(TRUE); + } + + if (table_create_info.table_options != part_create_info.table_options) + { + my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0), + "TABLE OPTION"); + DBUG_RETURN(TRUE); + } + + if (table->s->table_charset != part_table->s->table_charset) + { + my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0), + "CHARACTER SET"); + DBUG_RETURN(TRUE); + } + + /* + NOTE: We do not support update of frm-file, i.e. change + max/min_rows, data/index_file_name etc. + The workaround is to use REORGANIZE PARTITION to rewrite + the frm file and then use EXCHANGE PARTITION when they are the same. + */ + if (compare_partition_options(&table_create_info, part_elem)) + DBUG_RETURN(TRUE); + + DBUG_RETURN(FALSE); +} + + +/** + @brief Exchange partition/table with ddl log. + + @details How to handle a crash in the middle of the rename (break on error): + 1) register in ddl_log that we are going to exchange swap_table with part. + 2) do the first rename (swap_table -> tmp-name) and sync the ddl_log. + 3) do the second rename (part -> swap_table) and sync the ddl_log. + 4) do the last rename (tmp-name -> part). + 5) mark the entry done. + + Recover by: + 5) is done, All completed. Nothing to recover. + 4) is done see 3). (No mark or sync in the ddl_log...) + 3) is done -> try rename part -> tmp-name (ignore failure) goto 2). + 2) is done -> try rename swap_table -> part (ignore failure) goto 1). + 1) is done -> try rename tmp-name -> swap_table (ignore failure). + before 1) Nothing to recover... + + @param thd Thread handle + @param name name of table/partition 1 (to be exchanged with 2) + @param from_name name of table/partition 2 (to be exchanged with 1) + @param tmp_name temporary name to use while exchaning + @param ht handlerton of the table/partitions + + @return Operation status + @retval TRUE Error + @retval FALSE Success + + @note ha_heap always succeeds in rename (since it is created upon usage). + This is OK when to recover from a crash since all heap are empty and the + recover is done early in the startup of the server (right before + read_init_file which can populate the tables). + + And if no crash we can trust the syncs in the ddl_log. + + What about if the rename is put into a background thread? That will cause + corruption and is avoided by the exlusive metadata lock. +*/ +static bool exchange_name_with_ddl_log(THD *thd, + const char *name, + const char *from_name, + const char *tmp_name, + handlerton *ht) +{ + DDL_LOG_ENTRY exchange_entry; + DDL_LOG_MEMORY_ENTRY *log_entry= NULL; + DDL_LOG_MEMORY_ENTRY *exec_log_entry= NULL; + bool error= TRUE; + bool error_set= FALSE; + handler *file= NULL; + DBUG_ENTER("exchange_name_with_ddl_log"); + + if (!(file= get_new_handler(NULL, thd->mem_root, ht))) + { + mem_alloc_error(sizeof(handler)); + DBUG_RETURN(TRUE); + } + + /* prepare the action entry */ + exchange_entry.entry_type= DDL_LOG_ENTRY_CODE; + exchange_entry.action_type= DDL_LOG_EXCHANGE_ACTION; + exchange_entry.next_entry= 0; + exchange_entry.name= name; + exchange_entry.from_name= from_name; + exchange_entry.tmp_name= tmp_name; + exchange_entry.handler_name= ha_resolve_storage_engine_name(ht); + exchange_entry.phase= EXCH_PHASE_NAME_TO_TEMP; + + mysql_mutex_lock(&LOCK_gdl); + /* + write to the ddl log what to do by: + 1) write the action entry (i.e. which names to be exchanged) + 2) write the execution entry with a link to the action entry + */ + DBUG_EXECUTE_IF("exchange_partition_fail_1", goto err_no_action_written;); + DBUG_EXECUTE_IF("exchange_partition_abort_1", DBUG_SUICIDE();); + if (write_ddl_log_entry(&exchange_entry, &log_entry)) + goto err_no_action_written; + + DBUG_EXECUTE_IF("exchange_partition_fail_2", goto err_no_execute_written;); + DBUG_EXECUTE_IF("exchange_partition_abort_2", DBUG_SUICIDE();); + if (write_execute_ddl_log_entry(log_entry->entry_pos, FALSE, &exec_log_entry)) + goto err_no_execute_written; + /* ddl_log is written and synced */ + + mysql_mutex_unlock(&LOCK_gdl); + /* + Execute the name exchange. + Do one rename, increase the phase, update the action entry and sync. + In case of errors in the ddl_log we must fail and let the ddl_log try + to revert the changes, since otherwise it could revert the command after + we sent OK to the client. + */ + /* call rename table from table to tmp-name */ + DBUG_EXECUTE_IF("exchange_partition_fail_3", + my_error(ER_ERROR_ON_RENAME, MYF(0), + name, tmp_name, 0, "n/a"); + error_set= TRUE; + goto err_rename;); + DBUG_EXECUTE_IF("exchange_partition_abort_3", DBUG_SUICIDE();); + if (file->ha_rename_table(name, tmp_name)) + { + char errbuf[MYSYS_STRERROR_SIZE]; + my_strerror(errbuf, sizeof(errbuf), my_errno); + my_error(ER_ERROR_ON_RENAME, MYF(0), name, tmp_name, + my_errno, errbuf); + error_set= TRUE; + goto err_rename; + } + DBUG_EXECUTE_IF("exchange_partition_fail_4", goto err_rename;); + DBUG_EXECUTE_IF("exchange_partition_abort_4", DBUG_SUICIDE();); + if (deactivate_ddl_log_entry(log_entry->entry_pos)) + goto err_rename; + + /* call rename table from partition to table */ + DBUG_EXECUTE_IF("exchange_partition_fail_5", + my_error(ER_ERROR_ON_RENAME, MYF(0), + from_name, name, 0, "n/a"); + error_set= TRUE; + goto err_rename;); + DBUG_EXECUTE_IF("exchange_partition_abort_5", DBUG_SUICIDE();); + if (file->ha_rename_table(from_name, name)) + { + char errbuf[MYSYS_STRERROR_SIZE]; + my_strerror(errbuf, sizeof(errbuf), my_errno); + my_error(ER_ERROR_ON_RENAME, MYF(0), from_name, name, + my_errno, errbuf); + error_set= TRUE; + goto err_rename; + } + DBUG_EXECUTE_IF("exchange_partition_fail_6", goto err_rename;); + DBUG_EXECUTE_IF("exchange_partition_abort_6", DBUG_SUICIDE();); + if (deactivate_ddl_log_entry(log_entry->entry_pos)) + goto err_rename; + + /* call rename table from tmp-nam to partition */ + DBUG_EXECUTE_IF("exchange_partition_fail_7", + my_error(ER_ERROR_ON_RENAME, MYF(0), + tmp_name, from_name, 0, "n/a"); + error_set= TRUE; + goto err_rename;); + DBUG_EXECUTE_IF("exchange_partition_abort_7", DBUG_SUICIDE();); + if (file->ha_rename_table(tmp_name, from_name)) + { + char errbuf[MYSYS_STRERROR_SIZE]; + my_strerror(errbuf, sizeof(errbuf), my_errno); + my_error(ER_ERROR_ON_RENAME, MYF(0), tmp_name, from_name, + my_errno, errbuf); + error_set= TRUE; + goto err_rename; + } + DBUG_EXECUTE_IF("exchange_partition_fail_8", goto err_rename;); + DBUG_EXECUTE_IF("exchange_partition_abort_8", DBUG_SUICIDE();); + if (deactivate_ddl_log_entry(log_entry->entry_pos)) + goto err_rename; + + /* The exchange is complete and ddl_log is deactivated */ + DBUG_EXECUTE_IF("exchange_partition_fail_9", goto err_rename;); + DBUG_EXECUTE_IF("exchange_partition_abort_9", DBUG_SUICIDE();); + /* all OK */ + error= FALSE; + delete file; + DBUG_RETURN(error); +err_rename: + /* + Nothing to do if any of these commands fails :( the commands itselfs + will log to the error log about the failures... + */ + /* execute the ddl log entry to revert the renames */ + (void) execute_ddl_log_entry(current_thd, log_entry->entry_pos); + mysql_mutex_lock(&LOCK_gdl); + /* mark the execute log entry done */ + (void) write_execute_ddl_log_entry(0, TRUE, &exec_log_entry); + /* release the execute log entry */ + (void) release_ddl_log_memory_entry(exec_log_entry); +err_no_execute_written: + /* release the action log entry */ + (void) release_ddl_log_memory_entry(log_entry); +err_no_action_written: + mysql_mutex_unlock(&LOCK_gdl); + delete file; + if (!error_set) + my_error(ER_DDL_LOG_ERROR, MYF(0)); + DBUG_RETURN(error); +} + + +/** + @brief Swap places between a partition and a table. + + @details Verify that the tables are compatible (same engine, definition etc), + verify that all rows in the table will fit in the partition, + if all OK, rename table to tmp name, rename partition to table + and finally rename tmp name to partition. + + 1) Take upgradable mdl, open tables and then lock them (inited in parse) + 2) Verify that metadata matches + 3) verify data + 4) Upgrade to exclusive mdl for both tables + 5) Rename table <-> partition + 6) Rely on close_thread_tables to release mdl and table locks + + @param thd Thread handle + @param table_list Table where the partition exists as first table, + Table to swap with the partition as second table + @param alter_info Contains partition name to swap + + @note This is a DDL operation so triggers will not be used. +*/ +bool Sql_cmd_alter_table_exchange_partition:: + exchange_partition(THD *thd, TABLE_LIST *table_list, Alter_info *alter_info) +{ + TABLE *part_table, *swap_table; + TABLE_LIST *swap_table_list; + handlerton *table_hton; + partition_element *part_elem; + char *partition_name; + char temp_name[FN_REFLEN+1]; + char part_file_name[2*FN_REFLEN+1]; + char swap_file_name[FN_REFLEN+1]; + char temp_file_name[FN_REFLEN+1]; + uint swap_part_id; + uint part_file_name_len; + Alter_table_prelocking_strategy alter_prelocking_strategy; + MDL_ticket *swap_table_mdl_ticket= NULL; + MDL_ticket *part_table_mdl_ticket= NULL; + uint table_counter; + bool error= TRUE; + DBUG_ENTER("mysql_exchange_partition"); + DBUG_ASSERT(alter_info->flags & Alter_info::ALTER_EXCHANGE_PARTITION); + + /* Don't allow to exchange with log table */ + swap_table_list= table_list->next_local; + if (check_if_log_table(swap_table_list, FALSE, "ALTER PARTITION")) + DBUG_RETURN(TRUE); + + /* + Currently no MDL lock that allows both read and write and is upgradeable + to exclusive, so leave the lock type to TL_WRITE_ALLOW_READ also on the + partitioned table. + + TODO: add MDL lock that allows both read and write and is upgradable to + exclusive lock. This would allow to continue using the partitioned table + also with update/insert/delete while the verification of the swap table + is running. + */ + + /* + NOTE: It is not possible to exchange a crashed partition/table since + we need some info from the engine, which we can only access after open, + to be able to verify the structure/metadata. + */ + table_list->mdl_request.set_type(MDL_SHARED_NO_WRITE); + if (open_tables(thd, &table_list, &table_counter, 0, + &alter_prelocking_strategy)) + DBUG_RETURN(true); + +#ifdef WITH_WSREP + if (WSREP_ON) + { + /* Forward declaration */ + TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl); + + if ((!thd->is_current_stmt_binlog_format_row() || + /* TODO: Do we really need to check for temp tables in this case? */ + !find_temporary_table(thd, table_list)) && + wsrep_to_isolation_begin(thd, table_list->db, table_list->table_name, + NULL)) + { + WSREP_WARN("ALTER TABLE EXCHANGE PARTITION isolation failure"); + DBUG_RETURN(TRUE); + } + } +#endif /* WITH_WSREP */ + + part_table= table_list->table; + swap_table= swap_table_list->table; + + if (check_exchange_partition(swap_table, part_table)) + DBUG_RETURN(TRUE); + + /* set lock pruning on first table */ + partition_name= alter_info->partition_names.head(); + if (table_list->table->part_info-> + set_named_partition_bitmap(partition_name, strlen(partition_name))) + DBUG_RETURN(true); + + if (lock_tables(thd, table_list, table_counter, 0)) + DBUG_RETURN(true); + + + table_hton= swap_table->file->ht; + + THD_STAGE_INFO(thd, stage_verifying_table); + + /* Will append the partition name later in part_info->get_part_elem() */ + part_file_name_len= build_table_filename(part_file_name, + sizeof(part_file_name), + table_list->db, + table_list->table_name, + "", 0); + build_table_filename(swap_file_name, + sizeof(swap_file_name), + swap_table_list->db, + swap_table_list->table_name, + "", 0); + /* create a unique temp name #sqlx-nnnn_nnnn, x for eXchange */ + my_snprintf(temp_name, sizeof(temp_name), "%sx-%lx_%lx", + tmp_file_prefix, current_pid, thd->thread_id); + if (lower_case_table_names) + my_casedn_str(files_charset_info, temp_name); + build_table_filename(temp_file_name, sizeof(temp_file_name), + table_list->next_local->db, + temp_name, "", FN_IS_TMP); + + if (!(part_elem= part_table->part_info->get_part_elem(partition_name, + part_file_name + part_file_name_len, + sizeof(part_file_name) - part_file_name_len, + &swap_part_id))) + { + // my_error(ER_UNKNOWN_PARTITION, MYF(0), partition_name, + // part_table->alias); + DBUG_RETURN(TRUE); + } + + if (swap_part_id == NOT_A_PARTITION_ID) + { + DBUG_ASSERT(part_table->part_info->is_sub_partitioned()); + my_error(ER_PARTITION_INSTEAD_OF_SUBPARTITION, MYF(0)); + DBUG_RETURN(TRUE); + } + + if (compare_table_with_partition(thd, swap_table, part_table, part_elem, + swap_part_id)) + DBUG_RETURN(TRUE); + + /* Table and partition has same structure/options, OK to exchange */ + + thd_proc_info(thd, "verifying data with partition"); + + if (verify_data_with_partition(swap_table, part_table, swap_part_id)) + DBUG_RETURN(TRUE); + + /* + Get exclusive mdl lock on both tables, alway the non partitioned table + first. Remember the tickets for downgrading locks later. + */ + swap_table_mdl_ticket= swap_table->mdl_ticket; + part_table_mdl_ticket= part_table->mdl_ticket; + + /* + No need to set used_partitions to only propagate + HA_EXTRA_PREPARE_FOR_RENAME to one part since no built in engine uses + that flag. And the action would probably be to force close all other + instances which is what we are doing any way. + */ + if (wait_while_table_is_used(thd, swap_table, HA_EXTRA_PREPARE_FOR_RENAME) || + wait_while_table_is_used(thd, part_table, HA_EXTRA_PREPARE_FOR_RENAME)) + goto err; + + DEBUG_SYNC(thd, "swap_partition_after_wait"); + + close_all_tables_for_name(thd, swap_table->s, HA_EXTRA_NOT_USED, NULL); + close_all_tables_for_name(thd, part_table->s, HA_EXTRA_NOT_USED, NULL); + + DEBUG_SYNC(thd, "swap_partition_before_rename"); + + if (exchange_name_with_ddl_log(thd, swap_file_name, part_file_name, + temp_file_name, table_hton)) + goto err; + + /* + Reopen tables under LOCK TABLES. Ignore the return value for now. It's + better to keep master/slave in consistent state. Alternative would be to + try to revert the exchange operation and issue error. + */ + (void) thd->locked_tables_list.reopen_tables(thd, false); + + if ((error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()))) + { + /* + The error is reported in write_bin_log(). + We try to revert to make it easier to keep the master/slave in sync. + */ + (void) exchange_name_with_ddl_log(thd, part_file_name, swap_file_name, + temp_file_name, table_hton); + } + +err: + if (thd->locked_tables_mode) + { + if (swap_table_mdl_ticket) + swap_table_mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE); + if (part_table_mdl_ticket) + part_table_mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE); + } + + if (!error) + my_ok(thd); + + // For query cache + table_list->table= NULL; + table_list->next_local->table= NULL; + query_cache_invalidate3(thd, table_list, FALSE); + + DBUG_RETURN(error); +} + +bool Sql_cmd_alter_table_analyze_partition::execute(THD *thd) { bool res; - DBUG_ENTER("Alter_table_analyze_partition_statement::execute"); + DBUG_ENTER("Sql_cmd_alter_table_analyze_partition::execute"); /* Flag that it is an ALTER command which administrates partitions, used by ha_partition */ - m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION; - - res= Analyze_table_statement::execute(thd); + thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION; + res= Sql_cmd_analyze_table::execute(thd); + DBUG_RETURN(res); } -bool Alter_table_check_partition_statement::execute(THD *thd) +bool Sql_cmd_alter_table_check_partition::execute(THD *thd) { bool res; - DBUG_ENTER("Alter_table_check_partition_statement::execute"); + DBUG_ENTER("Sql_cmd_alter_table_check_partition::execute"); /* Flag that it is an ALTER command which administrates partitions, used by ha_partition */ - m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION; - res= Check_table_statement::execute(thd); + res= Sql_cmd_check_table::execute(thd); DBUG_RETURN(res); } -bool Alter_table_optimize_partition_statement::execute(THD *thd) +bool Sql_cmd_alter_table_optimize_partition::execute(THD *thd) { bool res; DBUG_ENTER("Alter_table_optimize_partition_statement::execute"); @@ -79,46 +726,49 @@ bool Alter_table_optimize_partition_statement::execute(THD *thd) Flag that it is an ALTER command which administrates partitions, used by ha_partition */ - m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION; - res= Optimize_table_statement::execute(thd); + res= Sql_cmd_optimize_table::execute(thd); DBUG_RETURN(res); } -bool Alter_table_repair_partition_statement::execute(THD *thd) +bool Sql_cmd_alter_table_repair_partition::execute(THD *thd) { bool res; - DBUG_ENTER("Alter_table_repair_partition_statement::execute"); + DBUG_ENTER("Sql_cmd_alter_table_repair_partition::execute"); /* Flag that it is an ALTER command which administrates partitions, used by ha_partition */ - m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION; - res= Repair_table_statement::execute(thd); + res= Sql_cmd_repair_table::execute(thd); DBUG_RETURN(res); } -bool Alter_table_truncate_partition_statement::execute(THD *thd) +bool Sql_cmd_alter_table_truncate_partition::execute(THD *thd) { int error; ha_partition *partition; ulong timeout= thd->variables.lock_wait_timeout; TABLE_LIST *first_table= thd->lex->select_lex.table_list.first; + Alter_info *alter_info= &thd->lex->alter_info; + uint table_counter, i; + List<String> partition_names_list; bool binlog_stmt; - DBUG_ENTER("Alter_table_truncate_partition_statement::execute"); + DBUG_ENTER("Sql_cmd_alter_table_truncate_partition::execute"); /* Flag that it is an ALTER command which administrates partitions, used by ha_partition. */ - m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION | - ALTER_TRUNCATE_PARTITION; + thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION | + Alter_info::ALTER_TRUNCATE_PARTITION; /* Fix the lock types (not the same as ordinary ALTER TABLE). */ first_table->lock_type= TL_WRITE; @@ -134,13 +784,23 @@ bool Alter_table_truncate_partition_statement::execute(THD *thd) if (check_one_table_access(thd, DROP_ACL, first_table)) DBUG_RETURN(TRUE); - if (open_and_lock_tables(thd, first_table, FALSE, 0)) +#ifdef WITH_WSREP + /* Forward declaration */ + TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl); + + if (WSREP(thd) && (!thd->is_current_stmt_binlog_format_row() || + !find_temporary_table(thd, first_table)) && + wsrep_to_isolation_begin( + thd, first_table->db, first_table->table_name, NULL) + ) + { + WSREP_WARN("ALTER TABLE TRUNCATE PARTITION isolation failure"); DBUG_RETURN(TRUE); + } +#endif /* WITH_WSREP */ - /* - TODO: Add support for TRUNCATE PARTITION for NDB and other - engines supporting native partitioning. - */ + if (open_tables(thd, &first_table, &table_counter, 0)) + DBUG_RETURN(true); if (!first_table->table || first_table->view || first_table->table->s->db_type() != partition_hton) @@ -149,24 +809,45 @@ bool Alter_table_truncate_partition_statement::execute(THD *thd) DBUG_RETURN(TRUE); } + + /* + Prune all, but named partitions, + to avoid excessive calls to external_lock(). + */ + List_iterator<char> partition_names_it(alter_info->partition_names); + uint num_names= alter_info->partition_names.elements; + for (i= 0; i < num_names; i++) + { + char *partition_name= partition_names_it++; + String *str_partition_name= new (thd->mem_root) + String(partition_name, system_charset_info); + if (!str_partition_name) + DBUG_RETURN(true); + partition_names_list.push_back(str_partition_name, thd->mem_root); + } + first_table->partition_names= &partition_names_list; + if (first_table->table->part_info->set_partition_bitmaps(first_table)) + DBUG_RETURN(true); + + if (lock_tables(thd, first_table, table_counter, 0)) + DBUG_RETURN(true); + /* Under locked table modes this might still not be an exclusive lock. Hence, upgrade the lock since the handler truncate method mandates an exclusive metadata lock. */ MDL_ticket *ticket= first_table->table->mdl_ticket; - if (thd->mdl_context.upgrade_shared_lock_to_exclusive(ticket, timeout)) + if (thd->mdl_context.upgrade_shared_lock(ticket, MDL_EXCLUSIVE, timeout)) DBUG_RETURN(TRUE); tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN, first_table->db, first_table->table_name, FALSE); - partition= (ha_partition *) first_table->table->file; - + partition= (ha_partition*) first_table->table->file; /* Invoke the handler method responsible for truncating the partition. */ - if ((error= partition->truncate_partition(&thd->lex->alter_info, - &binlog_stmt))) - first_table->table->file->print_error(error, MYF(0)); + if ((error= partition->truncate_partition(alter_info, &binlog_stmt))) + partition->print_error(error, MYF(0)); /* All effects of a truncate operation are committed even if the @@ -191,11 +872,15 @@ bool Alter_table_truncate_partition_statement::execute(THD *thd) to a shared one. */ if (thd->locked_tables_mode) - ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); + ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE); if (! error) my_ok(thd); + // Invalidate query cache + DBUG_ASSERT(!first_table->next_local); + query_cache_invalidate3(thd, first_table, FALSE); + DBUG_RETURN(error); } diff --git a/sql/sql_partition_admin.h b/sql/sql_partition_admin.h index 479371c3b4d..9c53744d9bc 100644 --- a/sql/sql_partition_admin.h +++ b/sql/sql_partition_admin.h @@ -22,214 +22,247 @@ Stub class that returns a error if the partition storage engine is not supported. */ -class Partition_statement_unsupported : public Sql_statement +class Sql_cmd_partition_unsupported : public Sql_cmd { public: - Partition_statement_unsupported(LEX *lex) - : Sql_statement(lex) + Sql_cmd_partition_unsupported() {} - ~Partition_statement_unsupported() + ~Sql_cmd_partition_unsupported() {} + /* Override SQLCOM_*, since it is an ALTER command */ + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_ALTER_TABLE; + } + bool execute(THD *thd); }; -class Alter_table_analyze_partition_statement : - public Partition_statement_unsupported +class Sql_cmd_alter_table_exchange_partition : + public Sql_cmd_partition_unsupported { public: - Alter_table_analyze_partition_statement(LEX *lex) - : Partition_statement_unsupported(lex) + Sql_cmd_alter_table_exchange_partition() {} - ~Alter_table_analyze_partition_statement() + ~Sql_cmd_alter_table_exchange_partition() {} }; -class Alter_table_check_partition_statement : - public Partition_statement_unsupported +class Sql_cmd_alter_table_analyze_partition : + public Sql_cmd_partition_unsupported { public: - Alter_table_check_partition_statement(LEX *lex) - : Partition_statement_unsupported(lex) + Sql_cmd_alter_table_analyze_partition() {} - ~Alter_table_check_partition_statement() + ~Sql_cmd_alter_table_analyze_partition() {} }; -class Alter_table_optimize_partition_statement : - public Partition_statement_unsupported +class Sql_cmd_alter_table_check_partition : + public Sql_cmd_partition_unsupported { public: - Alter_table_optimize_partition_statement(LEX *lex) - : Partition_statement_unsupported(lex) + Sql_cmd_alter_table_check_partition() {} - ~Alter_table_optimize_partition_statement() + ~Sql_cmd_alter_table_check_partition() {} }; -class Alter_table_repair_partition_statement : - public Partition_statement_unsupported +class Sql_cmd_alter_table_optimize_partition : + public Sql_cmd_partition_unsupported { public: - Alter_table_repair_partition_statement(LEX *lex) - : Partition_statement_unsupported(lex) + Sql_cmd_alter_table_optimize_partition() {} - ~Alter_table_repair_partition_statement() + ~Sql_cmd_alter_table_optimize_partition() {} }; -class Alter_table_truncate_partition_statement : - public Partition_statement_unsupported +class Sql_cmd_alter_table_repair_partition : + public Sql_cmd_partition_unsupported { public: - Alter_table_truncate_partition_statement(LEX *lex) - : Partition_statement_unsupported(lex) + Sql_cmd_alter_table_repair_partition() {} - ~Alter_table_truncate_partition_statement() + ~Sql_cmd_alter_table_repair_partition() {} }; +class Sql_cmd_alter_table_truncate_partition : + public Sql_cmd_partition_unsupported +{ +public: + Sql_cmd_alter_table_truncate_partition() + {} + + ~Sql_cmd_alter_table_truncate_partition() + {} +}; + #else /** Class that represents the ALTER TABLE t1 ANALYZE PARTITION p statement. */ -class Alter_table_analyze_partition_statement : public Analyze_table_statement +class Sql_cmd_alter_table_exchange_partition : public Sql_cmd_common_alter_table { public: /** - Constructor, used to represent a ALTER TABLE ANALYZE PARTITION statement. - @param lex the LEX structure for this statement. + Constructor, used to represent a ALTER TABLE EXCHANGE PARTITION statement. */ - Alter_table_analyze_partition_statement(LEX *lex) - : Analyze_table_statement(lex) + Sql_cmd_alter_table_exchange_partition() + : Sql_cmd_common_alter_table() {} - ~Alter_table_analyze_partition_statement() + ~Sql_cmd_alter_table_exchange_partition() {} + bool execute(THD *thd); + +private: + bool exchange_partition(THD *thd, TABLE_LIST *, Alter_info *); +}; + + +/** + Class that represents the ALTER TABLE t1 ANALYZE PARTITION p statement. +*/ +class Sql_cmd_alter_table_analyze_partition : public Sql_cmd_analyze_table +{ +public: /** - Execute a ALTER TABLE ANALYZE PARTITION statement at runtime. - @param thd the current thread. - @return false on success. + Constructor, used to represent a ALTER TABLE ANALYZE PARTITION statement. */ + Sql_cmd_alter_table_analyze_partition() + : Sql_cmd_analyze_table() + {} + + ~Sql_cmd_alter_table_analyze_partition() + {} + bool execute(THD *thd); + + /* Override SQLCOM_ANALYZE, since it is an ALTER command */ + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_ALTER_TABLE; + } }; /** Class that represents the ALTER TABLE t1 CHECK PARTITION p statement. */ -class Alter_table_check_partition_statement : public Check_table_statement +class Sql_cmd_alter_table_check_partition : public Sql_cmd_check_table { public: /** Constructor, used to represent a ALTER TABLE CHECK PARTITION statement. - @param lex the LEX structure for this statement. */ - Alter_table_check_partition_statement(LEX *lex) - : Check_table_statement(lex) + Sql_cmd_alter_table_check_partition() + : Sql_cmd_check_table() {} - ~Alter_table_check_partition_statement() + ~Sql_cmd_alter_table_check_partition() {} - /** - Execute a ALTER TABLE CHECK PARTITION statement at runtime. - @param thd the current thread. - @return false on success. - */ bool execute(THD *thd); + + /* Override SQLCOM_CHECK, since it is an ALTER command */ + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_ALTER_TABLE; + } }; /** Class that represents the ALTER TABLE t1 OPTIMIZE PARTITION p statement. */ -class Alter_table_optimize_partition_statement : public Optimize_table_statement +class Sql_cmd_alter_table_optimize_partition : public Sql_cmd_optimize_table { public: /** Constructor, used to represent a ALTER TABLE OPTIMIZE PARTITION statement. - @param lex the LEX structure for this statement. */ - Alter_table_optimize_partition_statement(LEX *lex) - : Optimize_table_statement(lex) + Sql_cmd_alter_table_optimize_partition() + : Sql_cmd_optimize_table() {} - ~Alter_table_optimize_partition_statement() + ~Sql_cmd_alter_table_optimize_partition() {} - /** - Execute a ALTER TABLE OPTIMIZE PARTITION statement at runtime. - @param thd the current thread. - @return false on success. - */ bool execute(THD *thd); + + /* Override SQLCOM_OPTIMIZE, since it is an ALTER command */ + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_ALTER_TABLE; + } }; /** Class that represents the ALTER TABLE t1 REPAIR PARTITION p statement. */ -class Alter_table_repair_partition_statement : public Repair_table_statement +class Sql_cmd_alter_table_repair_partition : public Sql_cmd_repair_table { public: /** Constructor, used to represent a ALTER TABLE REPAIR PARTITION statement. - @param lex the LEX structure for this statement. */ - Alter_table_repair_partition_statement(LEX *lex) - : Repair_table_statement(lex) + Sql_cmd_alter_table_repair_partition() + : Sql_cmd_repair_table() {} - ~Alter_table_repair_partition_statement() + ~Sql_cmd_alter_table_repair_partition() {} - /** - Execute a ALTER TABLE REPAIR PARTITION statement at runtime. - @param thd the current thread. - @return false on success. - */ bool execute(THD *thd); + + /* Override SQLCOM_REPAIR, since it is an ALTER command */ + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_ALTER_TABLE; + } }; /** Class that represents the ALTER TABLE t1 TRUNCATE PARTITION p statement. */ -class Alter_table_truncate_partition_statement : public Sql_statement +class Sql_cmd_alter_table_truncate_partition : public Sql_cmd_truncate_table { public: /** Constructor, used to represent a ALTER TABLE TRUNCATE PARTITION statement. - @param lex the LEX structure for this statement. */ - Alter_table_truncate_partition_statement(LEX *lex) - : Sql_statement(lex) + Sql_cmd_alter_table_truncate_partition() {} - virtual ~Alter_table_truncate_partition_statement() + virtual ~Sql_cmd_alter_table_truncate_partition() {} - /** - Execute a ALTER TABLE TRUNCATE PARTITION statement at runtime. - @param thd the current thread. - @return false on success. - */ bool execute(THD *thd); + + /* Override SQLCOM_TRUNCATE, since it is an ALTER command */ + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_ALTER_TABLE; + } }; #endif /* WITH_PARTITION_STORAGE_ENGINE */ diff --git a/sql/sql_plist.h b/sql/sql_plist.h index e703b4c0f62..df50cccc874 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -18,7 +18,7 @@ #include <my_global.h> -template <typename T, typename B, typename C, typename I> +template <typename T, typename L> class I_P_List_iterator; class I_P_List_null_counter; template <typename T> class I_P_List_no_push_back; @@ -156,10 +156,14 @@ public: I::set_last(&rhs.m_first); C::swap(rhs); } + typedef B Adapter; + typedef I_P_List<T, B, C, I> Base; + typedef I_P_List_iterator<T, Base> Iterator; + typedef I_P_List_iterator<const T, Base> Const_Iterator; #ifndef _lint - friend class I_P_List_iterator<T, B, C, I>; + friend class I_P_List_iterator<T, Base>; + friend class I_P_List_iterator<const T, Base>; #endif - typedef I_P_List_iterator<T, B, C, I> Iterator; }; @@ -167,33 +171,33 @@ public: Iterator for I_P_List. */ -template <typename T, typename B, - typename C = I_P_List_null_counter, - typename I = I_P_List_no_push_back<T> > +template <typename T, typename L> class I_P_List_iterator { - const I_P_List<T, B, C, I> *list; + const L *list; T *current; public: - I_P_List_iterator(const I_P_List<T, B, C, I> &a) + I_P_List_iterator(const L &a) : list(&a), current(a.m_first) {} - I_P_List_iterator(const I_P_List<T, B, C, I> &a, T* current_arg) + I_P_List_iterator(const L &a, T* current_arg) : list(&a), current(current_arg) {} - inline void init(const I_P_List<T, B, C, I> &a) + inline void init(const L &a) { list= &a; current= a.m_first; } + /* Operator for it++ */ inline T* operator++(int) { T *result= current; if (result) - current= *B::next_ptr(current); + current= *L::Adapter::next_ptr(current); return result; } + /* Operator for ++it */ inline T* operator++() { - current= *B::next_ptr(current); + current= *L::Adapter::next_ptr(current); return current; } inline void rewind() @@ -212,7 +216,7 @@ template <typename T, T* T::*next, T** T::*prev> struct I_P_List_adapter { static inline T **next_ptr(T *el) { return &(el->*next); } - + static inline const T* const* next_ptr(const T *el) { return &(el->*next); } static inline T ***prev_ptr(T *el) { return &(el->*prev); } }; diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index b378cdc0fa1..1000fc3711a 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2005, 2018, Oracle and/or its affiliates. - Copyright (c) 2010, 2018, MariaDB + Copyright (c) 2010, 2018, 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 @@ -15,9 +15,9 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "sql_plugin.h" // Includes my_global.h #include "sql_priv.h" // SHOW_MY_BOOL #include "unireg.h" -#include "my_global.h" // REQUIRED by m_string.h #include "sql_class.h" // set_var.h: THD #include "sys_vars_shared.h" #include "sql_locale.h" @@ -25,6 +25,7 @@ #include "sql_parse.h" // check_table_access #include "sql_base.h" // close_mysql_tables #include "key.h" // key_copy +#include "sql_table.h" #include "sql_show.h" // remove_status_vars, add_status_vars #include "strfunc.h" // find_set #include "sql_acl.h" // *_ACL @@ -35,11 +36,17 @@ #include <mysql/plugin_auth.h> #include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT #include <mysql/plugin_auth.h> +#include <mysql/plugin_password_validation.h> +#include <mysql/plugin_encryption.h> #include "sql_plugin_compat.h" #define REPORT_TO_LOG 1 #define REPORT_TO_USER 2 +#ifdef HAVE_LINK_H +#include <link.h> +#endif + extern struct st_maria_plugin *mysql_optional_plugins[]; extern struct st_maria_plugin *mysql_mandatory_plugins[]; @@ -53,8 +60,8 @@ static TYPELIB global_plugin_typelib= { array_elements(global_plugin_typelib_names)-1, "", global_plugin_typelib_names, NULL }; - -char *opt_plugin_load= NULL; +static I_List<i_string> opt_plugin_load_list; +I_List<i_string> *opt_plugin_load_list_ptr= &opt_plugin_load_list; char *opt_plugin_dir_ptr; char opt_plugin_dir[FN_REFLEN]; ulong plugin_maturity; @@ -82,7 +89,9 @@ const LEX_STRING plugin_type_names[MYSQL_MAX_PLUGIN_TYPE_NUM]= { C_STRING_WITH_LEN("INFORMATION SCHEMA") }, { C_STRING_WITH_LEN("AUDIT") }, { C_STRING_WITH_LEN("REPLICATION") }, - { C_STRING_WITH_LEN("AUTHENTICATION") } + { C_STRING_WITH_LEN("AUTHENTICATION") }, + { C_STRING_WITH_LEN("PASSWORD VALIDATION") }, + { C_STRING_WITH_LEN("ENCRYPTION") } }; extern int initialize_schema_table(st_plugin_int *plugin); @@ -91,6 +100,9 @@ extern int finalize_schema_table(st_plugin_int *plugin); extern int initialize_audit_plugin(st_plugin_int *plugin); extern int finalize_audit_plugin(st_plugin_int *plugin); +extern int initialize_encryption_plugin(st_plugin_int *plugin); +extern int finalize_encryption_plugin(st_plugin_int *plugin); + /* The number of elements in both plugin_type_initialize and plugin_type_deinitialize should equal to the number of plugins @@ -98,14 +110,33 @@ extern int finalize_audit_plugin(st_plugin_int *plugin); */ plugin_type_init plugin_type_initialize[MYSQL_MAX_PLUGIN_TYPE_NUM]= { - 0,ha_initialize_handlerton,0,0,initialize_schema_table, - initialize_audit_plugin, 0, 0 + 0, ha_initialize_handlerton, 0, 0,initialize_schema_table, + initialize_audit_plugin, 0, 0, 0, initialize_encryption_plugin }; plugin_type_init plugin_type_deinitialize[MYSQL_MAX_PLUGIN_TYPE_NUM]= { - 0,ha_finalize_handlerton,0,0,finalize_schema_table, - finalize_audit_plugin, 0, 0 + 0, ha_finalize_handlerton, 0, 0, finalize_schema_table, + finalize_audit_plugin, 0, 0, 0, finalize_encryption_plugin +}; + +/* + Defines in which order plugin types have to be initialized. + Essentially, we want to initialize MYSQL_KEY_MANAGEMENT_PLUGIN before + MYSQL_STORAGE_ENGINE_PLUGIN, and that before MYSQL_INFORMATION_SCHEMA_PLUGIN +*/ +static int plugin_type_initialization_order[MYSQL_MAX_PLUGIN_TYPE_NUM]= +{ + MYSQL_DAEMON_PLUGIN, + MariaDB_ENCRYPTION_PLUGIN, + MYSQL_STORAGE_ENGINE_PLUGIN, + MYSQL_INFORMATION_SCHEMA_PLUGIN, + MYSQL_FTPARSER_PLUGIN, + MYSQL_AUTHENTICATION_PLUGIN, + MariaDB_PASSWORD_VALIDATION_PLUGIN, + MYSQL_AUDIT_PLUGIN, + MYSQL_REPLICATION_PLUGIN, + MYSQL_UDF_PLUGIN }; #ifdef HAVE_DLOPEN @@ -137,7 +168,9 @@ static int min_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION, MYSQL_AUDIT_INTERFACE_VERSION, MYSQL_REPLICATION_INTERFACE_VERSION, - MIN_AUTHENTICATION_INTERFACE_VERSION + MIN_AUTHENTICATION_INTERFACE_VERSION, + MariaDB_PASSWORD_VALIDATION_INTERFACE_VERSION, + MariaDB_ENCRYPTION_INTERFACE_VERSION }; static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= { @@ -148,7 +181,9 @@ static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION, MYSQL_AUDIT_INTERFACE_VERSION, MYSQL_REPLICATION_INTERFACE_VERSION, - MYSQL_AUTHENTICATION_INTERFACE_VERSION + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + MariaDB_PASSWORD_VALIDATION_INTERFACE_VERSION, + MariaDB_ENCRYPTION_INTERFACE_VERSION }; static struct @@ -172,16 +207,15 @@ static struct - yet disable explicitly a component needed for the functionality to work, by using '--skip-performance-schema' (the plugin) */ - { "performance_schema", PLUGIN_FORCE }, + { "performance_schema", PLUGIN_FORCE } /* we disable few other plugins by default */ - { "ndbcluster", PLUGIN_OFF }, - { "feedback", PLUGIN_OFF } + ,{ "feedback", PLUGIN_OFF } }; /* support for Services */ -#include "sql_plugin_services.h" +#include "sql_plugin_services.ic" /* A mutex LOCK_plugin must be acquired before accessing the @@ -196,6 +230,8 @@ static MEM_ROOT plugin_mem_root; static bool reap_needed= false; static bool initialized= 0; +ulong dlopen_count; + /* write-lock on LOCK_system_variables_hash is required before modifying @@ -244,57 +280,33 @@ struct st_mysql_sys_var MYSQL_PLUGIN_VAR_HEADER; }; -static SHOW_TYPE pluginvar_show_type(st_mysql_sys_var *plugin_var); - - /* sys_var class for access to all plugin variables visible to the user */ -class sys_var_pluginvar: public sys_var +class sys_var_pluginvar: public sys_var, public Sql_alloc { public: struct st_plugin_int *plugin; struct st_mysql_sys_var *plugin_var; - /** - variable name from whatever is hard-coded in the plugin source - and doesn't have pluginname- prefix is replaced by an allocated name - with a plugin prefix. When plugin is uninstalled we need to restore the - pointer to point to the hard-coded value, because plugin may be - installed/uninstalled many times without reloading the shared object. - */ - const char *orig_pluginvar_name; - - static void *operator new(size_t size, MEM_ROOT *mem_root) - { return (void*) alloc_root(mem_root, size); } - static void operator delete(void *ptr_arg,size_t size) - { TRASH_FREE(ptr_arg, size); } sys_var_pluginvar(sys_var_chain *chain, const char *name_arg, - struct st_mysql_sys_var *plugin_var_arg, - struct st_plugin_int *plugin_arg) - :sys_var(chain, name_arg, plugin_var_arg->comment, - (plugin_var_arg->flags & PLUGIN_VAR_THDLOCAL ? SESSION : GLOBAL) | - (plugin_var_arg->flags & PLUGIN_VAR_READONLY ? READONLY : 0), - 0, -1, NO_ARG, pluginvar_show_type(plugin_var_arg), 0, 0, - VARIABLE_NOT_IN_BINLOG, NULL, NULL, NULL), - plugin(plugin_arg), plugin_var(plugin_var_arg), - orig_pluginvar_name(plugin_var_arg->name) - { plugin_var->name= name_arg; } + st_plugin_int *p, st_mysql_sys_var *plugin_var_arg); sys_var_pluginvar *cast_pluginvar() { return this; } - bool check_update_type(Item_result type); - SHOW_TYPE show_type(); uchar* real_value_ptr(THD *thd, enum_var_type type); TYPELIB* plugin_var_typelib(void); - uchar* do_value_ptr(THD *thd, enum_var_type type, LEX_STRING *base); - uchar* session_value_ptr(THD *thd, LEX_STRING *base) + uchar* do_value_ptr(THD *thd, enum_var_type type, const LEX_STRING *base); + uchar* session_value_ptr(THD *thd, const LEX_STRING *base) { return do_value_ptr(thd, OPT_SESSION, base); } - uchar* global_value_ptr(THD *thd, LEX_STRING *base) + uchar* global_value_ptr(THD *thd, const LEX_STRING *base) { return do_value_ptr(thd, OPT_GLOBAL, base); } + uchar *default_value_ptr(THD *thd) + { return do_value_ptr(thd, OPT_DEFAULT, 0); } bool do_check(THD *thd, set_var *var); virtual void session_save_default(THD *thd, set_var *var) {} virtual void global_save_default(THD *thd, set_var *var) {} bool session_update(THD *thd, set_var *var); bool global_update(THD *thd, set_var *var); + bool session_is_default(THD *thd); }; @@ -306,11 +318,9 @@ static int test_plugin_options(MEM_ROOT *, struct st_plugin_int *, static bool register_builtin(struct st_maria_plugin *, struct st_plugin_int *, struct st_plugin_int **); static void unlock_variables(THD *thd, struct system_variables *vars); -static void cleanup_variables(THD *thd, struct system_variables *vars); +static void cleanup_variables(struct system_variables *vars); static void plugin_vars_free_values(sys_var *vars); -static void restore_pluginvar_names(sys_var *first); -static void plugin_opt_set_limits(struct my_option *, - const struct st_mysql_sys_var *); +static void restore_ptr_backup(uint n, st_ptr_backup *backup); static void intern_plugin_unlock(LEX *lex, plugin_ref plugin); static void reap_plugins(void); @@ -485,9 +495,16 @@ static struct st_plugin_dl *plugin_dl_find(const LEX_STRING *) #endif /* HAVE_DLOPEN */ -static inline void free_plugin_mem(struct st_plugin_dl *p) +static void free_plugin_mem(struct st_plugin_dl *p) { #ifdef HAVE_DLOPEN + if (p->ptr_backup) + { + DBUG_ASSERT(p->nbackups); + DBUG_ASSERT(p->handle); + restore_ptr_backup(p->nbackups, p->ptr_backup); + my_free(p->ptr_backup); + } if (p->handle) dlclose(p->handle); #endif @@ -518,7 +535,6 @@ static my_bool read_mysql_plugin_info(struct st_plugin_dl *plugin_dl, /* Determine interface version */ if (!sym) { - free_plugin_mem(plugin_dl); report_error(report, ER_CANT_FIND_DL_ENTRY, plugin_interface_version_sym); DBUG_RETURN(TRUE); } @@ -528,15 +544,13 @@ static my_bool read_mysql_plugin_info(struct st_plugin_dl *plugin_dl, if (plugin_dl->mysqlversion < min_plugin_interface_version || (plugin_dl->mysqlversion >> 8) > (MYSQL_PLUGIN_INTERFACE_VERSION >> 8)) { - free_plugin_mem(plugin_dl); - report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, 0, + report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, ENOEXEC, "plugin interface version mismatch"); DBUG_RETURN(TRUE); } /* Find plugin declarations */ if (!(sym= dlsym(plugin_dl->handle, plugin_declarations_sym))) { - free_plugin_mem(plugin_dl); report_error(report, ER_CANT_FIND_DL_ENTRY, plugin_declarations_sym); DBUG_RETURN(TRUE); } @@ -567,7 +581,6 @@ static my_bool read_mysql_plugin_info(struct st_plugin_dl *plugin_dl, MYF(MY_ZEROFILL|MY_WME)); if (!cur) { - free_plugin_mem(plugin_dl); report_error(report, ER_OUTOFMEMORY, static_cast<int>(plugin_dl->dl.length)); DBUG_RETURN(TRUE); @@ -642,7 +655,6 @@ static my_bool read_maria_plugin_info(struct st_plugin_dl *plugin_dl, Actually this branch impossible because in case of absence of maria version we try mysql version. */ - free_plugin_mem(plugin_dl); report_error(report, ER_CANT_FIND_DL_ENTRY, maria_plugin_interface_version_sym); DBUG_RETURN(TRUE); @@ -653,7 +665,6 @@ static my_bool read_maria_plugin_info(struct st_plugin_dl *plugin_dl, if (plugin_dl->mariaversion < min_maria_plugin_interface_version || (plugin_dl->mariaversion >> 8) > (MARIA_PLUGIN_INTERFACE_VERSION >> 8)) { - free_plugin_mem(plugin_dl); report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, ENOEXEC, "plugin interface version mismatch"); DBUG_RETURN(TRUE); @@ -661,7 +672,6 @@ static my_bool read_maria_plugin_info(struct st_plugin_dl *plugin_dl, /* Find plugin declarations */ if (!(sym= dlsym(plugin_dl->handle, maria_plugin_declarations_sym))) { - free_plugin_mem(plugin_dl); report_error(report, ER_CANT_FIND_DL_ENTRY, maria_plugin_declarations_sym); DBUG_RETURN(TRUE); } @@ -675,7 +685,6 @@ static my_bool read_maria_plugin_info(struct st_plugin_dl *plugin_dl, sizeof_st_plugin= *(int *)sym; else { - free_plugin_mem(plugin_dl); report_error(report, ER_CANT_FIND_DL_ENTRY, maria_sizeof_st_plugin_sym); DBUG_RETURN(TRUE); } @@ -693,7 +702,6 @@ static my_bool read_maria_plugin_info(struct st_plugin_dl *plugin_dl, MYF(MY_ZEROFILL|MY_WME)); if (!cur) { - free_plugin_mem(plugin_dl); report_error(report, ER_OUTOFMEMORY, static_cast<int>(plugin_dl->dl.length)); DBUG_RETURN(TRUE); @@ -706,7 +714,7 @@ static my_bool read_maria_plugin_info(struct st_plugin_dl *plugin_dl, for (i=0; (old= (struct st_maria_plugin *)(ptr + i * sizeof_st_plugin))->info; i++) - memcpy(cur + i, old, min(sizeof(cur[i]), sizeof_st_plugin)); + memcpy(cur + i, old, MY_MIN(sizeof(cur[i]), sizeof_st_plugin)); sym= cur; plugin_dl->allocated= true; @@ -724,12 +732,14 @@ static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report) { #ifdef HAVE_DLOPEN char dlpath[FN_REFLEN]; - uint plugin_dir_len, dummy_errors, dlpathlen, i; - struct st_plugin_dl *tmp, plugin_dl; + uint plugin_dir_len, dummy_errors, i; + struct st_plugin_dl *tmp= 0, plugin_dl; void *sym; + st_ptr_backup tmp_backup[array_elements(list_of_services)]; DBUG_ENTER("plugin_dl_add"); DBUG_PRINT("enter", ("dl->str: '%s', dl->length: %d", dl->str, (int) dl->length)); + mysql_mutex_assert_owner(&LOCK_plugin); plugin_dir_len= strlen(opt_plugin_dir); /* Ensure that the dll doesn't have a path. @@ -737,7 +747,7 @@ static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report) plugin directory are used (to make this even remotely secure). */ if (check_valid_path(dl->str, dl->length) || - check_string_char_length((LEX_STRING *) dl, "", NAME_CHAR_LEN, + check_string_char_length((LEX_STRING *) dl, 0, NAME_CHAR_LEN, system_charset_info, 1) || plugin_dir_len + dl->length + 1 >= FN_REFLEN) { @@ -758,17 +768,18 @@ static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report) /* Open new dll handle */ if (!(plugin_dl.handle= dlopen(dlpath, RTLD_NOW))) { - const char *errmsg=dlerror(); - dlpathlen= strlen(dlpath); - if (!strncmp(dlpath, errmsg, dlpathlen)) - { // if errmsg starts from dlpath, trim this prefix. - errmsg+=dlpathlen; - if (*errmsg == ':') errmsg++; - if (*errmsg == ' ') errmsg++; - } - report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, errno, errmsg); - DBUG_RETURN(0); + report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, errno, my_dlerror(dlpath)); + goto ret; + } + dlopen_count++; + +#ifdef HAVE_LINK_H + if (global_system_variables.log_warnings > 2) + { + struct link_map *lm = (struct link_map*) plugin_dl.handle; + sql_print_information("Loaded '%s' with offset 0x%lx", dl->str, lm->l_addr); } +#endif /* Checks which plugin interface present and reads info */ if (!(sym= dlsym(plugin_dl.handle, maria_plugin_interface_version_sym))) @@ -778,12 +789,12 @@ static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report) plugin_interface_version_sym), dlpath, report)) - DBUG_RETURN(0); + goto ret; } else { if (read_maria_plugin_info(&plugin_dl, sym, dlpath, report)) - DBUG_RETURN(0); + goto ret; } /* link the services in */ @@ -791,7 +802,8 @@ static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report) { if ((sym= dlsym(plugin_dl.handle, list_of_services[i].name))) { - uint ver= (uint)(intptr)*(void**)sym; + void **ptr= (void **)sym; + uint ver= (uint)(intptr)*ptr; if (ver > list_of_services[i].version || (ver >> 8) < (list_of_services[i].version >> 8)) { @@ -800,20 +812,33 @@ static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report) "service '%s' interface version mismatch", list_of_services[i].name); report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, ENOEXEC, buf); - DBUG_RETURN(0); + goto ret; } - *(void**)sym= list_of_services[i].service; + tmp_backup[plugin_dl.nbackups++].save(ptr); + *ptr= list_of_services[i].service; + } + } + + if (plugin_dl.nbackups) + { + size_t bytes= plugin_dl.nbackups * sizeof(plugin_dl.ptr_backup[0]); + plugin_dl.ptr_backup= (st_ptr_backup *)my_malloc(bytes, MYF(0)); + if (!plugin_dl.ptr_backup) + { + restore_ptr_backup(plugin_dl.nbackups, tmp_backup); + report_error(report, ER_OUTOFMEMORY, bytes); + goto ret; } + memcpy(plugin_dl.ptr_backup, tmp_backup, bytes); } /* Duplicate and convert dll name */ plugin_dl.dl.length= dl->length * files_charset_info->mbmaxlen + 1; if (! (plugin_dl.dl.str= (char*) my_malloc(plugin_dl.dl.length, MYF(0)))) { - free_plugin_mem(&plugin_dl); report_error(report, ER_OUTOFMEMORY, static_cast<int>(plugin_dl.dl.length)); - DBUG_RETURN(0); + goto ret; } plugin_dl.dl.length= copy_and_convert(plugin_dl.dl.str, plugin_dl.dl.length, files_charset_info, dl->str, dl->length, system_charset_info, @@ -822,12 +847,17 @@ static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report) /* Add this dll to array */ if (! (tmp= plugin_dl_insert_or_reuse(&plugin_dl))) { - free_plugin_mem(&plugin_dl); report_error(report, ER_OUTOFMEMORY, static_cast<int>(sizeof(struct st_plugin_dl))); - DBUG_RETURN(0); + goto ret; } + +ret: + if (!tmp) + free_plugin_mem(&plugin_dl); + DBUG_RETURN(tmp); + #else DBUG_ENTER("plugin_dl_add"); report_error(report, ER_FEATURE_DISABLED, "plugin", "HAVE_DLOPEN"); @@ -836,34 +866,23 @@ static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report) } -static void plugin_dl_del(const LEX_STRING *dl) +static void plugin_dl_del(struct st_plugin_dl *plugin_dl) { -#ifdef HAVE_DLOPEN - uint i; DBUG_ENTER("plugin_dl_del"); + if (!plugin_dl) + DBUG_VOID_RETURN; + mysql_mutex_assert_owner(&LOCK_plugin); - for (i= 0; i < plugin_dl_array.elements; i++) + /* Do not remove this element, unless no other plugin uses this dll. */ + if (! --plugin_dl->ref_count) { - struct st_plugin_dl *tmp= *dynamic_element(&plugin_dl_array, i, - struct st_plugin_dl **); - if (tmp->ref_count && - ! my_strnncoll(files_charset_info, - (const uchar *)dl->str, dl->length, - (const uchar *)tmp->dl.str, tmp->dl.length)) - { - /* Do not remove this element, unless no other plugin uses this dll. */ - if (! --tmp->ref_count) - { - free_plugin_mem(tmp); - bzero(tmp, sizeof(struct st_plugin_dl)); - } - break; - } + free_plugin_mem(plugin_dl); + bzero(plugin_dl, sizeof(struct st_plugin_dl)); } + DBUG_VOID_RETURN; -#endif } @@ -929,7 +948,8 @@ SHOW_COMP_OPTION plugin_status(const char *name, size_t len, int type) static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref rc, uint state_mask= PLUGIN_IS_READY | - PLUGIN_IS_UNINITIALIZED) + PLUGIN_IS_UNINITIALIZED | + PLUGIN_IS_DELETED) { st_plugin_int *pi= plugin_ref_to_int(rc); DBUG_ENTER("intern_plugin_lock"); @@ -1052,13 +1072,15 @@ static st_plugin_int *plugin_insert_or_reuse(struct st_plugin_int *plugin) static bool plugin_add(MEM_ROOT *tmp_root, const LEX_STRING *name, LEX_STRING *dl, int report) { - struct st_plugin_int tmp; + struct st_plugin_int tmp, *maybe_dupe; struct st_maria_plugin *plugin; - uint oks= 0, errs= 0; + uint oks= 0, errs= 0, dupes= 0; DBUG_ENTER("plugin_add"); + DBUG_PRINT("enter", ("name: %s dl: %s", name->str, dl->str)); + if (name->str && plugin_find_internal(name, MYSQL_ANY_PLUGIN)) { - report_error(report, ER_UDF_EXISTS, name->str); + report_error(report, ER_PLUGIN_INSTALLED, name->str); DBUG_RETURN(TRUE); } /* Clear the whole struct to catch future extensions. */ @@ -1073,16 +1095,29 @@ static bool plugin_add(MEM_ROOT *tmp_root, tmp.name.length= strlen(plugin->name); if (plugin->type < 0 || plugin->type >= MYSQL_MAX_PLUGIN_TYPE_NUM) - continue; // invalid plugin + continue; // invalid plugin type + + if (plugin->type == MYSQL_UDF_PLUGIN || + (plugin->type == MariaDB_PASSWORD_VALIDATION_PLUGIN && + tmp.plugin_dl->mariaversion == 0)) + continue; // unsupported plugin type if (name->str && my_strnncoll(system_charset_info, (const uchar *)name->str, name->length, (const uchar *)tmp.name.str, tmp.name.length)) continue; // plugin name doesn't match - if (!name->str && plugin_find_internal(&tmp.name, MYSQL_ANY_PLUGIN)) + if (!name->str && + (maybe_dupe= plugin_find_internal(&tmp.name, MYSQL_ANY_PLUGIN))) + { + if (plugin->name != maybe_dupe->plugin->name) + { + report_error(report, ER_UDF_EXISTS, plugin->name); + DBUG_RETURN(TRUE); + } + dupes++; continue; // already installed - + } struct st_plugin_int *tmp_plugin_ptr; if (*(int*)plugin->info < min_plugin_info_interface_version[plugin->type] || @@ -1118,7 +1153,7 @@ static bool plugin_add(MEM_ROOT *tmp_root, goto err; if (my_hash_insert(&plugin_hash[plugin->type], (uchar*)tmp_plugin_ptr)) tmp_plugin_ptr->state= PLUGIN_IS_FREED; - init_alloc_root(&tmp_plugin_ptr->mem_root, 4096, 4096); + init_alloc_root(&tmp_plugin_ptr->mem_root, 4096, 4096, MYF(0)); if (name->str) DBUG_RETURN(FALSE); // all done @@ -1133,11 +1168,13 @@ err: break; } - if (errs == 0 && oks == 0) // no plugin was found + DBUG_ASSERT(!name->str || !dupes); // dupes is ONLY for name->str == 0 + + if (errs == 0 && oks == 0 && !dupes) // no plugin was found report_error(report, ER_CANT_FIND_DL_ENTRY, name->str); - plugin_dl_del(dl); - DBUG_RETURN(errs > 0 || oks == 0); + plugin_dl_del(tmp.plugin_dl); + DBUG_RETURN(errs > 0 || oks + dupes == 0); } @@ -1152,22 +1189,21 @@ static void plugin_deinitialize(struct st_plugin_int *plugin, bool ref_check) if (plugin->plugin->status_vars) { -#ifdef FIX_LATER - /** - @todo - unfortunately, status variables were introduced without a - pluginname_ namespace, that is pluginname_ was not added automatically - to status variable names. It should be fixed together with the next - incompatible API change. + /* + historical ndb behavior caused MySQL plugins to specify + status var names in full, with the plugin name prefix. + this was never fixed in MySQL. + MariaDB fixes that but supports MySQL style too. */ - SHOW_VAR array[2]= { + SHOW_VAR *show_vars= plugin->plugin->status_vars; + SHOW_VAR tmp_array[2]= { {plugin->plugin->name, (char*)plugin->plugin->status_vars, SHOW_ARRAY}, {0, 0, SHOW_UNDEF} }; - remove_status_vars(array); -#else - remove_status_vars(plugin->plugin->status_vars); -#endif /* FIX_LATER */ + if (strncasecmp(show_vars->name, plugin->name.str, plugin->name.length)) + show_vars= tmp_array; + + remove_status_vars(show_vars); } if (plugin_type_deinitialize[plugin->plugin->type]) @@ -1189,19 +1225,11 @@ static void plugin_deinitialize(struct st_plugin_int *plugin, bool ref_check) } plugin->state= PLUGIN_IS_UNINITIALIZED; - /* maintain the obsolete @@have_innodb variable */ - if (!my_strcasecmp(&my_charset_latin1, plugin->name.str, "InnoDB")) - have_innodb= SHOW_OPTION_DISABLED; - - /* - We do the check here because NDB has a worker THD which doesn't - exit until NDB is shut down. - */ if (ref_check && plugin->ref_count) sql_print_error("Plugin '%s' has ref_count=%d after deinitialization.", plugin->name.str, plugin->ref_count); - restore_pluginvar_names(plugin->system_vars); + mysql_del_sys_var_chain(plugin->system_vars); } static void plugin_del(struct st_plugin_int *plugin) @@ -1210,17 +1238,22 @@ static void plugin_del(struct st_plugin_int *plugin) mysql_mutex_assert_owner(&LOCK_plugin); /* Free allocated strings before deleting the plugin. */ plugin_vars_free_values(plugin->system_vars); - my_hash_delete(&plugin_hash[plugin->plugin->type], (uchar*)plugin); + restore_ptr_backup(plugin->nbackups, plugin->ptr_backup); if (plugin->plugin_dl) - plugin_dl_del(&plugin->plugin_dl->dl); - plugin->state= PLUGIN_IS_FREED; - free_root(&plugin->mem_root, MYF(0)); + { + my_hash_delete(&plugin_hash[plugin->plugin->type], (uchar*)plugin); + plugin_dl_del(plugin->plugin_dl); + plugin->state= PLUGIN_IS_FREED; + free_root(&plugin->mem_root, MYF(0)); + } + else + plugin->state= PLUGIN_IS_UNINITIALIZED; DBUG_VOID_RETURN; } static void reap_plugins(void) { - uint count, idx; + uint count; struct st_plugin_int *plugin, **reap, **list; mysql_mutex_assert_owner(&LOCK_plugin); @@ -1233,14 +1266,18 @@ static void reap_plugins(void) reap= (struct st_plugin_int **)my_alloca(sizeof(plugin)*(count+1)); *(reap++)= NULL; - for (idx= 0; idx < count; idx++) + for (uint i=0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) { - plugin= *dynamic_element(&plugin_array, idx, struct st_plugin_int **); - if (plugin->state == PLUGIN_IS_DELETED && !plugin->ref_count) + HASH *hash= plugin_hash + plugin_type_initialization_order[i]; + for (uint j= 0; j < hash->records; j++) { - /* change the status flag to prevent reaping by another thread */ - plugin->state= PLUGIN_IS_DYING; - *(reap++)= plugin; + plugin= (struct st_plugin_int *) my_hash_element(hash, j); + if (plugin->state == PLUGIN_IS_DELETED && !plugin->ref_count) + { + /* change the status flag to prevent reaping by another thread */ + plugin->state= PLUGIN_IS_DYING; + *(reap++)= plugin; + } } } @@ -1248,7 +1285,7 @@ static void reap_plugins(void) list= reap; while ((plugin= *(--list))) - plugin_deinitialize(plugin, true); + plugin_deinitialize(plugin, true); mysql_mutex_lock(&LOCK_plugin); @@ -1363,19 +1400,10 @@ static int plugin_initialize(MEM_ROOT *tmp_root, struct st_plugin_int *plugin, if (options_only || state == PLUGIN_IS_DISABLED) { ret= !options_only && plugin_is_forced(plugin); + state= PLUGIN_IS_DISABLED; goto err; } - if (plugin->plugin_dl && global_system_variables.log_warnings >= 9) - { - void *sym= dlsym(plugin->plugin_dl->handle, - plugin->plugin_dl->mariaversion ? - maria_plugin_declarations_sym : plugin_declarations_sym); - DBUG_ASSERT(sym); - sql_print_information("Plugin %s loaded at %p", - plugin->name.str, sym); - } - if (plugin_type_initialize[plugin->plugin->type]) { if ((*plugin_type_initialize[plugin->plugin->type])(plugin)) @@ -1398,40 +1426,33 @@ static int plugin_initialize(MEM_ROOT *tmp_root, struct st_plugin_int *plugin, if (plugin->plugin->status_vars) { -#ifdef FIX_LATER /* - We have a problem right now where we can not prepend without - breaking backwards compatibility. We will fix this shortly so - that engines have "use names" and we wil use those for - CREATE TABLE, and use the plugin name then for adding automatic - variable names. + historical ndb behavior caused MySQL plugins to specify + status var names in full, with the plugin name prefix. + this was never fixed in MySQL. + MariaDB fixes that but supports MySQL style too. */ - SHOW_VAR array[2]= { + SHOW_VAR *show_vars= plugin->plugin->status_vars; + SHOW_VAR tmp_array[2]= { {plugin->plugin->name, (char*)plugin->plugin->status_vars, SHOW_ARRAY}, {0, 0, SHOW_UNDEF} }; - if (add_status_vars(array)) // add_status_vars makes a copy - goto err; -#else - if (add_status_vars(plugin->plugin->status_vars)) + if (strncasecmp(show_vars->name, plugin->name.str, plugin->name.length)) + show_vars= tmp_array; + + if (add_status_vars(show_vars)) goto err; -#endif /* FIX_LATER */ } ret= 0; err: if (ret) - restore_pluginvar_names(plugin->system_vars); + mysql_del_sys_var_chain(plugin->system_vars); mysql_mutex_lock(&LOCK_plugin); plugin->state= state; - /* maintain the obsolete @@have_innodb variable */ - if (!my_strcasecmp(&my_charset_latin1, plugin->name.str, "InnoDB")) - have_innodb= state & PLUGIN_IS_READY ? SHOW_OPTION_YES - : SHOW_OPTION_DISABLED; - DBUG_RETURN(ret); } @@ -1502,37 +1523,32 @@ static void init_plugin_psi_keys(void) int plugin_init(int *argc, char **argv, int flags) { uint i; - bool is_myisam; struct st_maria_plugin **builtins; struct st_maria_plugin *plugin; struct st_plugin_int tmp, *plugin_ptr, **reap; MEM_ROOT tmp_root; bool reaped_mandatory_plugin= false; bool mandatory= true; + LEX_STRING MyISAM= { C_STRING_WITH_LEN("MyISAM") }; DBUG_ENTER("plugin_init"); if (initialized) DBUG_RETURN(0); -#ifdef HAVE_PSI_INTERFACE - init_plugin_psi_keys(); -#endif + dlopen_count =0; - init_alloc_root(&plugin_mem_root, 4096, 4096); - init_alloc_root(&plugin_vars_mem_root, 4096, 4096); - init_alloc_root(&tmp_root, 4096, 4096); + init_alloc_root(&plugin_mem_root, 4096, 4096, MYF(0)); + init_alloc_root(&plugin_vars_mem_root, 4096, 4096, MYF(0)); + init_alloc_root(&tmp_root, 4096, 4096, MYF(0)); if (my_hash_init(&bookmark_hash, &my_charset_bin, 16, 0, 0, get_bookmark_hash_key, NULL, HASH_UNIQUE)) goto err; - - mysql_mutex_init(key_LOCK_plugin, &LOCK_plugin, MY_MUTEX_INIT_FAST); - if (my_init_dynamic_array(&plugin_dl_array, - sizeof(struct st_plugin_dl *),16,16) || + sizeof(struct st_plugin_dl *), 16, 16, MYF(0)) || my_init_dynamic_array(&plugin_array, - sizeof(struct st_plugin_int *),16,16)) + sizeof(struct st_plugin_int *), 16, 16, MYF(0))) goto err; for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) @@ -1543,8 +1559,11 @@ int plugin_init(int *argc, char **argv, int flags) } /* prepare debug_sync service */ - DBUG_ASSERT(strcmp(list_of_services[5].name, "debug_sync_service") == 0); - list_of_services[5].service= *(void**)&debug_sync_C_callback_ptr; + DBUG_ASSERT(strcmp(list_of_services[1].name, "debug_sync_service") == 0); + list_of_services[1].service= *(void**)&debug_sync_C_callback_ptr; + + /* prepare encryption_keys service */ + finalize_encryption_plugin(0); mysql_mutex_lock(&LOCK_plugin); @@ -1553,6 +1572,9 @@ int plugin_init(int *argc, char **argv, int flags) /* First we register builtin plugins */ + if (global_system_variables.log_warnings >= 9) + sql_print_information("Initializing built-in plugins"); + for (builtins= mysql_mandatory_plugins; *builtins || mandatory; builtins++) { if (!*builtins) @@ -1590,52 +1612,57 @@ int plugin_init(int *argc, char **argv, int flags) tmp.state= PLUGIN_IS_UNINITIALIZED; if (register_builtin(plugin, &tmp, &plugin_ptr)) goto err_unlock; - - is_myisam= !my_strcasecmp(&my_charset_latin1, plugin->name, "MyISAM"); - - /* - strictly speaking, we should to initialize all plugins, - even for mysqld --help, because important subsystems - may be disabled otherwise, and the help will be incomplete. - For example, if the mysql.plugin table is not MyISAM. - But for now it's an unlikely corner case, and to optimize - mysqld --help for all other users, we will only initialize - MyISAM here. - */ - if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv, !is_myisam && - (flags & PLUGIN_INIT_SKIP_INITIALIZATION))) - { - if (plugin_ptr->load_option == PLUGIN_FORCE) - goto err_unlock; - plugin_ptr->state= PLUGIN_IS_DISABLED; - } - - /* - initialize the global default storage engine so that it may - not be null in any child thread. - */ - if (is_myisam) - { - DBUG_ASSERT(!global_system_variables.table_plugin); - global_system_variables.table_plugin= - intern_plugin_lock(NULL, plugin_int_to_ref(plugin_ptr)); - DBUG_ASSERT(plugin_ptr->ref_count == 1); - } } } - /* should now be set to MyISAM storage engine */ - DBUG_ASSERT(global_system_variables.table_plugin); + /* + First, we initialize only MyISAM - that should almost always succeed + (almost always, because plugins can be loaded outside of the server, too). + */ + plugin_ptr= plugin_find_internal(&MyISAM, MYSQL_STORAGE_ENGINE_PLUGIN); + DBUG_ASSERT(plugin_ptr || !mysql_mandatory_plugins[0]); + if (plugin_ptr) + { + DBUG_ASSERT(plugin_ptr->load_option == PLUGIN_FORCE); + + if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv, false)) + goto err_unlock; + /* + set the global default storage engine variable so that it will + not be null in any child thread. + */ + global_system_variables.table_plugin = + intern_plugin_lock(NULL, plugin_int_to_ref(plugin_ptr)); + DBUG_ASSERT(plugin_ptr->ref_count == 1); + + } mysql_mutex_unlock(&LOCK_plugin); - /* Register all dynamic plugins */ + /* Register (not initialize!) all dynamic plugins */ if (!(flags & PLUGIN_INIT_SKIP_DYNAMIC_LOADING)) { - if (opt_plugin_load) - plugin_load_list(&tmp_root, opt_plugin_load); + I_List_iterator<i_string> iter(opt_plugin_load_list); + i_string *item; + if (global_system_variables.log_warnings >= 9) + sql_print_information("Initializing plugins specified on the command line"); + while (NULL != (item= iter++)) + plugin_load_list(&tmp_root, item->ptr); + if (!(flags & PLUGIN_INIT_SKIP_PLUGIN_TABLE)) - plugin_load(&tmp_root); + { + char path[FN_REFLEN + 1]; + build_table_filename(path, sizeof(path) - 1, "mysql", "plugin", reg_ext, 0); + char engine_name_buf[NAME_CHAR_LEN + 1]; + LEX_STRING maybe_myisam= { engine_name_buf, 0 }; + frm_type_enum frm_type= dd_frm_type(NULL, path, &maybe_myisam); + /* if mysql.plugin table is MyISAM - load it right away */ + if (frm_type == FRMTYPE_TABLE && !strcasecmp(maybe_myisam.str, "MyISAM")) + { + plugin_load(&tmp_root); + flags|= PLUGIN_INIT_SKIP_PLUGIN_TABLE; + } + } } /* @@ -1646,18 +1673,34 @@ int plugin_init(int *argc, char **argv, int flags) reap= (st_plugin_int **) my_alloca((plugin_array.elements+1) * sizeof(void*)); *(reap++)= NULL; - for (i= 0; i < plugin_array.elements; i++) + for(;;) { - plugin_ptr= *dynamic_element(&plugin_array, i, struct st_plugin_int **); - if (plugin_ptr->plugin_dl && plugin_ptr->state == PLUGIN_IS_UNINITIALIZED) + for (i=0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) { - if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv, - (flags & PLUGIN_INIT_SKIP_INITIALIZATION))) + HASH *hash= plugin_hash + plugin_type_initialization_order[i]; + for (uint idx= 0; idx < hash->records; idx++) { - plugin_ptr->state= PLUGIN_IS_DYING; - *(reap++)= plugin_ptr; + plugin_ptr= (struct st_plugin_int *) my_hash_element(hash, idx); + if (plugin_ptr->state == PLUGIN_IS_UNINITIALIZED) + { + if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv, + (flags & PLUGIN_INIT_SKIP_INITIALIZATION))) + { + plugin_ptr->state= PLUGIN_IS_DYING; + *(reap++)= plugin_ptr; + } + } } } + + /* load and init plugins from the plugin table (unless done already) */ + if (flags & PLUGIN_INIT_SKIP_PLUGIN_TABLE) + break; + + mysql_mutex_unlock(&LOCK_plugin); + plugin_load(&tmp_root); + flags|= PLUGIN_INIT_SKIP_PLUGIN_TABLE; + mysql_mutex_lock(&LOCK_plugin); } /* @@ -1718,52 +1761,41 @@ static bool register_builtin(struct st_maria_plugin *plugin, */ static void plugin_load(MEM_ROOT *tmp_root) { - THD thd; TABLE_LIST tables; TABLE *table; READ_RECORD read_record_info; int error; - THD *new_thd= &thd; + THD *new_thd= new THD; bool result; -#ifdef EMBEDDED_LIBRARY - No_such_table_error_handler error_handler; -#endif /* EMBEDDED_LIBRARY */ DBUG_ENTER("plugin_load"); + if (global_system_variables.log_warnings >= 9) + sql_print_information("Initializing installed plugins"); + new_thd->thread_stack= (char*) &tables; new_thd->store_globals(); new_thd->db= my_strdup("mysql", MYF(0)); new_thd->db_length= 5; - bzero((char*) &thd.net, sizeof(thd.net)); - tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_READ); - -#ifdef EMBEDDED_LIBRARY - /* - When building an embedded library, if the mysql.plugin table - does not exist, we silently ignore the missing table - */ - new_thd->push_internal_handler(&error_handler); -#endif /* EMBEDDED_LIBRARY */ + bzero((char*) &new_thd->net, sizeof(new_thd->net)); + tables.init_one_table(STRING_WITH_LEN("mysql"), STRING_WITH_LEN("plugin"), + "plugin", TL_READ); + tables.open_strategy= TABLE_LIST::OPEN_NORMAL; result= open_and_lock_tables(new_thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT); -#ifdef EMBEDDED_LIBRARY - new_thd->pop_internal_handler(); - if (error_handler.safely_trapped_errors()) - goto end; -#endif /* EMBEDDED_LIBRARY */ - + table= tables.table; if (result) { DBUG_PRINT("error",("Can't open plugin table")); if (!opt_help) - sql_print_error("Can't open the mysql.plugin table. Please " - "run mysql_upgrade to create it."); + sql_print_error("Could not open mysql.plugin table. " + "Some plugins may be not loaded"); else - sql_print_warning("Could not open mysql.plugin table. Some options may be missing from the help text"); + sql_print_warning("Could not open mysql.plugin table. " + "Some options may be missing from the help text"); goto end; } - table= tables.table; + if (init_read_record(&read_record_info, new_thd, table, NULL, 1, 0, FALSE)) { sql_print_error("Could not initialize init_read_record; Plugins not " @@ -1788,20 +1820,18 @@ static void plugin_load(MEM_ROOT *tmp_root) the mutex here to satisfy the assert */ mysql_mutex_lock(&LOCK_plugin); - if (plugin_add(tmp_root, &name, &dl, REPORT_TO_LOG)) - sql_print_warning("Couldn't load plugin named '%s' with soname '%s'.", - str_name.c_ptr(), str_dl.c_ptr()); + plugin_add(tmp_root, &name, &dl, REPORT_TO_LOG); free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE)); mysql_mutex_unlock(&LOCK_plugin); } if (error > 0) - sql_print_error(ER(ER_GET_ERRNO), my_errno); + sql_print_error(ER_THD(new_thd, ER_GET_ERRNO), my_errno, + table->file->table_type()); end_read_record(&read_record_info); table->m_needs_reopen= TRUE; // Force close to free memory close_mysql_tables(new_thd); end: - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); + delete new_thd; DBUG_VOID_RETURN; } @@ -1949,8 +1979,6 @@ void plugin_shutdown(void) if (!(plugins[i]->state & (PLUGIN_IS_UNINITIALIZED | PLUGIN_IS_FREED | PLUGIN_IS_DISABLED))) { - sql_print_warning("Plugin '%s' will be forced to shutdown", - plugins[i]->name.str); /* We are forcing deinit on plugins so we don't want to do a ref_count check until we have processed all the plugins. @@ -1983,8 +2011,8 @@ void plugin_shutdown(void) Now we can deallocate all memory. */ - cleanup_variables(NULL, &global_system_variables); - cleanup_variables(NULL, &max_system_variables); + cleanup_variables(&global_system_variables); + cleanup_variables(&max_system_variables); mysql_mutex_unlock(&LOCK_plugin); initialized= 0; @@ -2048,8 +2076,9 @@ static bool finalize_install(THD *thd, TABLE *table, const LEX_STRING *name, if (tmp->state == PLUGIN_IS_DISABLED) { if (global_system_variables.log_warnings) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_CANT_INITIALIZE_UDF, ER(ER_CANT_INITIALIZE_UDF), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CANT_INITIALIZE_UDF, + ER_THD(thd, ER_CANT_INITIALIZE_UDF), name->str, "Plugin is disabled"); } @@ -2084,17 +2113,14 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, bool error; int argc=orig_argc; char **argv=orig_argv; + unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] = + { MYSQL_AUDIT_GENERAL_CLASSMASK }; DBUG_ENTER("mysql_install_plugin"); - if (opt_noacl) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); - DBUG_RETURN(TRUE); - } - tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_WRITE); - if (check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE)) + if (!opt_noacl && check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE)) DBUG_RETURN(TRUE); + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); /* need to open before acquiring LOCK_plugin or it will deadlock */ if (! (table = open_ltable(thd, &tables, TL_WRITE, @@ -2128,8 +2154,7 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, See also mysql_uninstall_plugin() and initialize_audit_plugin() */ - unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] = - { MYSQL_AUDIT_GENERAL_CLASSMASK }; + mysql_audit_acquire_plugins(thd, event_class_mask); mysql_mutex_lock(&LOCK_plugin); @@ -2160,6 +2185,9 @@ err: if (argv) free_defaults(argv); DBUG_RETURN(error); + +WSREP_ERROR_LABEL: + DBUG_RETURN(TRUE); } @@ -2187,8 +2215,8 @@ static bool do_uninstall(THD *thd, TABLE *table, const LEX_STRING *name) plugin->state= PLUGIN_IS_DELETED; if (plugin->ref_count) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_PLUGIN_BUSY, ER(WARN_PLUGIN_BUSY)); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_PLUGIN_BUSY, ER_THD(thd, WARN_PLUGIN_BUSY)); else reap_needed= true; @@ -2226,19 +2254,17 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name, TABLE_LIST tables; LEX_STRING dl= *dl_arg; bool error= false; + unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] = + { MYSQL_AUDIT_GENERAL_CLASSMASK }; DBUG_ENTER("mysql_uninstall_plugin"); - if (opt_noacl) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); - DBUG_RETURN(TRUE); - } - tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_WRITE); - if (check_table_access(thd, DELETE_ACL, &tables, FALSE, 1, FALSE)) + if (!opt_noacl && check_table_access(thd, DELETE_ACL, &tables, FALSE, 1, FALSE)) DBUG_RETURN(TRUE); + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) + /* need to open before acquiring LOCK_plugin or it will deadlock */ if (! (table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT))) DBUG_RETURN(TRUE); @@ -2274,8 +2300,6 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name, See also mysql_install_plugin() and initialize_audit_plugin() */ - unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] = - { MYSQL_AUDIT_GENERAL_CLASSMASK }; mysql_audit_acquire_plugins(thd, event_class_mask); mysql_mutex_lock(&LOCK_plugin); @@ -2305,6 +2329,9 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name, mysql_mutex_unlock(&LOCK_plugin); DBUG_RETURN(error); + +WSREP_ERROR_LABEL: + DBUG_RETURN(TRUE); } @@ -2363,6 +2390,74 @@ bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func *func, } +static bool plugin_dl_foreach_internal(THD *thd, st_plugin_dl *plugin_dl, + st_maria_plugin *plug, + plugin_foreach_func *func, void *arg) +{ + for (; plug->name; plug++) + { + st_plugin_int tmp, *plugin; + + tmp.name.str= const_cast<char*>(plug->name); + tmp.name.length= strlen(plug->name); + tmp.plugin= plug; + tmp.plugin_dl= plugin_dl; + + mysql_mutex_lock(&LOCK_plugin); + if ((plugin= plugin_find_internal(&tmp.name, MYSQL_ANY_PLUGIN)) && + plugin->plugin == plug) + + { + tmp.state= plugin->state; + tmp.load_option= plugin->load_option; + } + else + { + tmp.state= PLUGIN_IS_FREED; + tmp.load_option= PLUGIN_OFF; + } + mysql_mutex_unlock(&LOCK_plugin); + + plugin= &tmp; + if (func(thd, plugin_int_to_ref(plugin), arg)) + return 1; + } + return 0; +} + +bool plugin_dl_foreach(THD *thd, const LEX_STRING *dl, + plugin_foreach_func *func, void *arg) +{ + bool err= 0; + + if (dl) + { + mysql_mutex_lock(&LOCK_plugin); + st_plugin_dl *plugin_dl= plugin_dl_add(dl, REPORT_TO_USER); + mysql_mutex_unlock(&LOCK_plugin); + + if (!plugin_dl) + return 1; + + err= plugin_dl_foreach_internal(thd, plugin_dl, plugin_dl->plugins, + func, arg); + + mysql_mutex_lock(&LOCK_plugin); + plugin_dl_del(plugin_dl); + mysql_mutex_unlock(&LOCK_plugin); + } + else + { + struct st_maria_plugin **builtins; + for (builtins= mysql_mandatory_plugins; !err && *builtins; builtins++) + err= plugin_dl_foreach_internal(thd, 0, *builtins, func, arg); + for (builtins= mysql_optional_plugins; !err && *builtins; builtins++) + err= plugin_dl_foreach_internal(thd, 0, *builtins, func, arg); + } + return err; +} + + /**************************************************************************** Internal type declarations for variables support ****************************************************************************/ @@ -2862,14 +2957,59 @@ static st_bookmark *register_var(const char *plugin, const char *name, return result; } -static void restore_pluginvar_names(sys_var *first) + +void sync_dynamic_session_variables(THD* thd, bool global_lock) { - mysql_del_sys_var_chain(first); - for (sys_var *var= first; var; var= var->next) + uint idx; + + thd->variables.dynamic_variables_ptr= (char*) + my_realloc(thd->variables.dynamic_variables_ptr, + global_variables_dynamic_size, + MYF(MY_WME | MY_FAE | MY_ALLOW_ZERO_PTR)); + + if (global_lock) + mysql_mutex_lock(&LOCK_global_system_variables); + + mysql_mutex_assert_owner(&LOCK_global_system_variables); + + memcpy(thd->variables.dynamic_variables_ptr + + thd->variables.dynamic_variables_size, + global_system_variables.dynamic_variables_ptr + + thd->variables.dynamic_variables_size, + global_system_variables.dynamic_variables_size - + thd->variables.dynamic_variables_size); + + /* + now we need to iterate through any newly copied 'defaults' + and if it is a string type with MEMALLOC flag, we need to strdup + */ + for (idx= 0; idx < bookmark_hash.records; idx++) { - sys_var_pluginvar *pv= var->cast_pluginvar(); - pv->plugin_var->name= pv->orig_pluginvar_name; + st_bookmark *v= (st_bookmark*) my_hash_element(&bookmark_hash,idx); + + if (v->version <= thd->variables.dynamic_variables_version) + continue; /* already in thd->variables */ + + /* Here we do anything special that may be required of the data types */ + + if ((v->key[0] & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR && + v->key[0] & BOOKMARK_MEMALLOC) + { + char **pp= (char**) (thd->variables.dynamic_variables_ptr + v->offset); + if (*pp) + *pp= my_strdup(*pp, MYF(MY_WME|MY_FAE)); + } } + + if (global_lock) + mysql_mutex_unlock(&LOCK_global_system_variables); + + thd->variables.dynamic_variables_version= + global_system_variables.dynamic_variables_version; + thd->variables.dynamic_variables_head= + global_system_variables.dynamic_variables_head; + thd->variables.dynamic_variables_size= + global_system_variables.dynamic_variables_size; } @@ -2888,8 +3028,6 @@ static uchar *intern_sys_var_ptr(THD* thd, int offset, bool global_lock) if (!thd) DBUG_RETURN((uchar*) global_system_variables.dynamic_variables_ptr + offset); - mysql_mutex_assert_not_owner(&LOCK_open); - /* dynamic_variables_head points to the largest valid offset */ @@ -2955,13 +3093,17 @@ static double *mysql_sys_var_double(THD* thd, int offset) void plugin_thdvar_init(THD *thd) { plugin_ref old_table_plugin= thd->variables.table_plugin; + plugin_ref old_tmp_table_plugin= thd->variables.tmp_table_plugin; + plugin_ref old_enforced_table_plugin= thd->variables.enforced_table_plugin; DBUG_ENTER("plugin_thdvar_init"); - + + // This function may be called many times per THD (e.g. on COM_CHANGE_USER) thd->variables.table_plugin= NULL; - cleanup_variables(thd, &thd->variables); - + thd->variables.tmp_table_plugin= NULL; + thd->variables.enforced_table_plugin= NULL; + cleanup_variables(&thd->variables); + thd->variables= global_system_variables; - thd->variables.table_plugin= NULL; /* we are going to allocate these lazily */ thd->variables.dynamic_variables_version= 0; @@ -2970,66 +3112,19 @@ void plugin_thdvar_init(THD *thd) mysql_mutex_lock(&LOCK_plugin); thd->variables.table_plugin= - intern_plugin_lock(NULL, global_system_variables.table_plugin); + intern_plugin_lock(NULL, global_system_variables.table_plugin); + if (global_system_variables.tmp_table_plugin) + thd->variables.tmp_table_plugin= + intern_plugin_lock(NULL, global_system_variables.tmp_table_plugin); + if (global_system_variables.enforced_table_plugin) + thd->variables.enforced_table_plugin= + intern_plugin_lock(NULL, global_system_variables.enforced_table_plugin); intern_plugin_unlock(NULL, old_table_plugin); + intern_plugin_unlock(NULL, old_tmp_table_plugin); + intern_plugin_unlock(NULL, old_enforced_table_plugin); mysql_mutex_unlock(&LOCK_plugin); - DBUG_VOID_RETURN; -} - - -void sync_dynamic_session_variables(THD* thd, bool global_lock) -{ - uint idx; - - thd->variables.dynamic_variables_ptr= (char*) - my_realloc(thd->variables.dynamic_variables_ptr, - global_variables_dynamic_size, - MYF(MY_WME | MY_FAE | MY_ALLOW_ZERO_PTR)); - - if (global_lock) - mysql_mutex_lock(&LOCK_global_system_variables); - - mysql_mutex_assert_owner(&LOCK_global_system_variables); - - memcpy(thd->variables.dynamic_variables_ptr + - thd->variables.dynamic_variables_size, - global_system_variables.dynamic_variables_ptr + - thd->variables.dynamic_variables_size, - global_system_variables.dynamic_variables_size - - thd->variables.dynamic_variables_size); - - /* - now we need to iterate through any newly copied 'defaults' - and if it is a string type with MEMALLOC flag, we need to strdup - */ - for (idx= 0; idx < bookmark_hash.records; idx++) - { - st_bookmark *v= (st_bookmark*) my_hash_element(&bookmark_hash,idx); - - if (v->version <= thd->variables.dynamic_variables_version) - continue; /* already in thd->variables */ - - /* Here we do anything special that may be required of the data types */ - - if ((v->key[0] & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR && - v->key[0] & BOOKMARK_MEMALLOC) - { - char **pp= (char**) (thd->variables.dynamic_variables_ptr + v->offset); - if (*pp) - *pp= my_strdup(*pp, MYF(MY_WME|MY_FAE)); - } - } - - if (global_lock) - mysql_mutex_unlock(&LOCK_global_system_variables); - - thd->variables.dynamic_variables_version= - global_system_variables.dynamic_variables_version; - thd->variables.dynamic_variables_head= - global_system_variables.dynamic_variables_head; - thd->variables.dynamic_variables_size= - global_system_variables.dynamic_variables_size; + DBUG_VOID_RETURN; } @@ -3039,7 +3134,9 @@ void sync_dynamic_session_variables(THD* thd, bool global_lock) static void unlock_variables(THD *thd, struct system_variables *vars) { intern_plugin_unlock(NULL, vars->table_plugin); - vars->table_plugin= NULL; + intern_plugin_unlock(NULL, vars->tmp_table_plugin); + intern_plugin_unlock(NULL, vars->enforced_table_plugin); + vars->table_plugin= vars->tmp_table_plugin= vars->enforced_table_plugin= NULL; } @@ -3049,7 +3146,7 @@ static void unlock_variables(THD *thd, struct system_variables *vars) Unlike plugin_vars_free_values() it frees all variables of all plugins, it's used on shutdown. */ -static void cleanup_variables(THD *thd, struct system_variables *vars) +static void cleanup_variables(struct system_variables *vars) { st_bookmark *v; uint idx; @@ -3064,6 +3161,7 @@ static void cleanup_variables(THD *thd, struct system_variables *vars) DBUG_ASSERT((uint)v->offset <= vars->dynamic_variables_head); + /* free allocated strings (PLUGIN_VAR_STR | PLUGIN_VAR_MEMALLOC) */ if ((v->key[0] & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR && v->key[0] & BOOKMARK_MEMALLOC) { @@ -3075,6 +3173,8 @@ static void cleanup_variables(THD *thd, struct system_variables *vars) mysql_rwlock_unlock(&LOCK_system_variables_hash); DBUG_ASSERT(vars->table_plugin == NULL); + DBUG_ASSERT(vars->tmp_table_plugin == NULL); + DBUG_ASSERT(vars->enforced_table_plugin == NULL); my_free(vars->dynamic_variables_ptr); vars->dynamic_variables_ptr= NULL; @@ -3092,7 +3192,7 @@ void plugin_thdvar_cleanup(THD *thd) mysql_mutex_lock(&LOCK_plugin); unlock_variables(thd, &thd->variables); - cleanup_variables(thd, &thd->variables); + cleanup_variables(&thd->variables); if ((idx= thd->lex->plugins.elements)) { @@ -3144,7 +3244,7 @@ static void plugin_vars_free_values(sys_var *vars) DBUG_VOID_RETURN; } -static SHOW_TYPE pluginvar_show_type(st_mysql_sys_var *plugin_var) +static SHOW_TYPE pluginvar_show_type(const st_mysql_sys_var *plugin_var) { switch (plugin_var->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_UNSIGNED)) { case PLUGIN_VAR_BOOL: @@ -3175,29 +3275,53 @@ static SHOW_TYPE pluginvar_show_type(st_mysql_sys_var *plugin_var) } -bool sys_var_pluginvar::check_update_type(Item_result type) +static int pluginvar_sysvar_flags(const st_mysql_sys_var *p) { - switch (plugin_var->flags & PLUGIN_VAR_TYPEMASK) { - case PLUGIN_VAR_INT: - case PLUGIN_VAR_LONG: - case PLUGIN_VAR_LONGLONG: - return type != INT_RESULT; - case PLUGIN_VAR_STR: - return type != STRING_RESULT; - case PLUGIN_VAR_ENUM: - case PLUGIN_VAR_BOOL: - case PLUGIN_VAR_SET: - return type != STRING_RESULT && type != INT_RESULT; - case PLUGIN_VAR_DOUBLE: - return type != INT_RESULT && type != REAL_RESULT && type != DECIMAL_RESULT; - default: - return true; - } + return (p->flags & PLUGIN_VAR_THDLOCAL ? sys_var::SESSION : sys_var::GLOBAL) + | (p->flags & PLUGIN_VAR_READONLY ? sys_var::READONLY : 0); } +sys_var_pluginvar::sys_var_pluginvar(sys_var_chain *chain, const char *name_arg, + st_plugin_int *p, st_mysql_sys_var *pv) + : sys_var(chain, name_arg, pv->comment, pluginvar_sysvar_flags(pv), + 0, pv->flags & PLUGIN_VAR_NOCMDOPT ? -1 : 0, NO_ARG, + pluginvar_show_type(pv), 0, + NULL, VARIABLE_NOT_IN_BINLOG, NULL, NULL, NULL), + plugin(p), plugin_var(pv) +{ + plugin_var->name= name_arg; + plugin_opt_set_limits(&option, pv); +} uchar* sys_var_pluginvar::real_value_ptr(THD *thd, enum_var_type type) { + if (type == OPT_DEFAULT) + { + switch (plugin_var->flags & PLUGIN_VAR_TYPEMASK) { + case PLUGIN_VAR_BOOL: + thd->sys_var_tmp.my_bool_value= option.def_value; + return (uchar*) &thd->sys_var_tmp.my_bool_value; + case PLUGIN_VAR_INT: + thd->sys_var_tmp.int_value= option.def_value; + return (uchar*) &thd->sys_var_tmp.int_value; + case PLUGIN_VAR_LONG: + case PLUGIN_VAR_ENUM: + thd->sys_var_tmp.long_value= option.def_value; + return (uchar*) &thd->sys_var_tmp.long_value; + case PLUGIN_VAR_LONGLONG: + case PLUGIN_VAR_SET: + return (uchar*) &option.def_value; + case PLUGIN_VAR_STR: + thd->sys_var_tmp.ptr_value= (void*) option.def_value; + return (uchar*) &thd->sys_var_tmp.ptr_value; + case PLUGIN_VAR_DOUBLE: + thd->sys_var_tmp.double_value= getopt_ulonglong2double(option.def_value); + return (uchar*) &thd->sys_var_tmp.double_value; + default: + DBUG_ASSERT(0); + } + } + DBUG_ASSERT(thd || (type == OPT_GLOBAL)); if (plugin_var->flags & PLUGIN_VAR_THDLOCAL) { @@ -3210,6 +3334,39 @@ uchar* sys_var_pluginvar::real_value_ptr(THD *thd, enum_var_type type) } +bool sys_var_pluginvar::session_is_default(THD *thd) +{ + uchar *value= plugin_var->flags & PLUGIN_VAR_THDLOCAL + ? intern_sys_var_ptr(thd, *(int*) (plugin_var+1), true) + : *(uchar**) (plugin_var+1); + + real_value_ptr(thd, OPT_SESSION); + + switch (plugin_var->flags & PLUGIN_VAR_TYPEMASK) { + case PLUGIN_VAR_BOOL: + return option.def_value == *(my_bool*)value; + case PLUGIN_VAR_INT: + return option.def_value == *(int*)value; + case PLUGIN_VAR_LONG: + case PLUGIN_VAR_ENUM: + return option.def_value == *(long*)value; + case PLUGIN_VAR_LONGLONG: + case PLUGIN_VAR_SET: + return option.def_value == *(longlong*)value; + case PLUGIN_VAR_STR: + { + const char *a=(char*)option.def_value; + const char *b=(char*)value; + return (!a && !b) || (a && b && strcmp(a,b)); + } + case PLUGIN_VAR_DOUBLE: + return getopt_ulonglong2double(option.def_value) == *(double*)value; + } + DBUG_ASSERT(0); + return 0; +} + + TYPELIB* sys_var_pluginvar::plugin_var_typelib(void) { switch (plugin_var->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_THDLOCAL)) { @@ -3229,7 +3386,7 @@ TYPELIB* sys_var_pluginvar::plugin_var_typelib(void) uchar* sys_var_pluginvar::do_value_ptr(THD *thd, enum_var_type type, - LEX_STRING *base) + const LEX_STRING *base) { uchar* result; @@ -3266,13 +3423,13 @@ bool sys_var_pluginvar::session_update(THD *thd, set_var *var) DBUG_ASSERT(thd == current_thd); mysql_mutex_lock(&LOCK_global_system_variables); - void *tgt= real_value_ptr(thd, var->type); + void *tgt= real_value_ptr(thd, OPT_SESSION); const void *src= var->value ? (void*)&var->save_result : (void*)real_value_ptr(thd, OPT_GLOBAL); mysql_mutex_unlock(&LOCK_global_system_variables); plugin_var->update(thd, plugin_var, tgt, src); - + return false; } @@ -3323,7 +3480,7 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var) DBUG_ASSERT(!is_readonly()); mysql_mutex_assert_owner(&LOCK_global_system_variables); - void *tgt= real_value_ptr(thd, var->type); + void *tgt= real_value_ptr(thd, OPT_GLOBAL); const void *src= &var->save_result; if (!var->value) @@ -3348,9 +3505,8 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var) options->max_value= getopt_double2ulonglong((opt)->max_val); \ options->block_size= (long) (opt)->blk_sz; - -static void plugin_opt_set_limits(struct my_option *options, - const struct st_mysql_sys_var *opt) +void plugin_opt_set_limits(struct my_option *options, + const struct st_mysql_sys_var *opt) { options->sub_size= 0; @@ -3392,6 +3548,7 @@ static void plugin_opt_set_limits(struct my_option *options, case PLUGIN_VAR_BOOL: options->var_type= GET_BOOL; options->def_value= ((sysvar_bool_t*) opt)->def_val; + options->typelib= &bool_typelib; break; case PLUGIN_VAR_STR: options->var_type= ((opt->flags & PLUGIN_VAR_MEMALLOC) ? @@ -3440,6 +3597,7 @@ static void plugin_opt_set_limits(struct my_option *options, case PLUGIN_VAR_BOOL | PLUGIN_VAR_THDLOCAL: options->var_type= GET_BOOL; options->def_value= ((thdvar_bool_t*) opt)->def_val; + options->typelib= &bool_typelib; break; case PLUGIN_VAR_STR | PLUGIN_VAR_THDLOCAL: options->var_type= ((opt->flags & PLUGIN_VAR_MEMALLOC) ? @@ -3456,17 +3614,6 @@ static void plugin_opt_set_limits(struct my_option *options, options->arg_type= OPT_ARG; } -extern "C" my_bool get_one_plugin_option(int optid, const struct my_option *, - char *); - -my_bool get_one_plugin_option(int optid __attribute__((unused)), - const struct my_option *opt, - char *argument) -{ - return 0; -} - - /** Creates a set of my_option objects associated with a specified plugin- handle. @@ -3497,7 +3644,7 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, char *comment= (char *) alloc_root(mem_root, max_comment_len + 1); char *optname; - int index= 0, offset= 0; + int index= 0, UNINIT_VAR(offset); st_mysql_sys_var *opt, **plugin_option; st_bookmark *v; @@ -3527,7 +3674,7 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, options[0].typelib= options[1].typelib= &global_plugin_typelib; strxnmov(comment, max_comment_len, "Enable or disable ", plugin_name, - " plugin. Possible values are ON, OFF, FORCE (don't start " + " plugin. One of: ON, OFF, FORCE (don't start " "if the plugin fails to load).", NullS); options[0].comment= comment; /* @@ -3543,12 +3690,6 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, options+= 2; } - if (!my_strcasecmp(&my_charset_latin1, plugin_name_ptr, "NDBCLUSTER")) - { - plugin_name_ptr= const_cast<char*>("ndb"); // Use legacy "ndb" prefix - plugin_name_len= 3; - } - /* Two passes as the 2nd pass will take pointer addresses for use by my_getopt and register_var() in the first pass uses realloc @@ -3558,6 +3699,14 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, plugin_option && *plugin_option; plugin_option++, index++) { opt= *plugin_option; + + if (!opt->name) + { + sql_print_error("Missing variable name in plugin '%s'.", + plugin_name); + DBUG_RETURN(-1); + } + if (!(opt->flags & PLUGIN_VAR_THDLOCAL)) continue; if (!(register_var(plugin_name_ptr, opt->name, opt->flags))) @@ -3666,13 +3815,6 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, == PLUGIN_VAR_NOCMDOPT) continue; - if (!opt->name) - { - sql_print_error("Missing variable name in plugin '%s'.", - plugin_name); - DBUG_RETURN(-1); - } - if (!(opt->flags & PLUGIN_VAR_THDLOCAL)) { optnamelen= strlen(opt->name); @@ -3706,7 +3848,7 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, continue; } - optname= (char*) memdup_root(mem_root, v->key + 1, + optname= (char*) memdup_root(mem_root, v->key + 1, (optnamelen= v->name_len) + 1); } @@ -3714,7 +3856,7 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, options->name= optname; options->comment= opt->comment; - options->app_type= opt; + options->app_type= (opt->flags & PLUGIN_VAR_NOSYSVAR) ? NULL : opt; options->id= 0; plugin_opt_set_limits(options, opt); @@ -3762,7 +3904,7 @@ static my_option *construct_help_options(MEM_ROOT *mem_root, to get the correct (not double-prefixed) help text. We won't need @@sysvars anymore and don't care about their proper names. */ - restore_pluginvar_names(p->system_vars); + restore_ptr_backup(p->nbackups, p->ptr_backup); if (construct_options(mem_root, p, opts)) DBUG_RETURN(NULL); @@ -3770,6 +3912,17 @@ static my_option *construct_help_options(MEM_ROOT *mem_root, DBUG_RETURN(opts); } +extern "C" my_bool mark_changed(int, const struct my_option *, char *); +my_bool mark_changed(int, const struct my_option *opt, char *) +{ + if (opt->app_type) + { + sys_var *var= (sys_var*) opt->app_type; + var->value_origin= sys_var::CONFIG; + } + return 0; +} + /** Create and register system variables supplied from the plugin and assigns initial values from corresponding command line arguments. @@ -3801,20 +3954,22 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, &tmp->mem_root : &plugin_vars_mem_root; st_mysql_sys_var **opt; my_option *opts= NULL; - LEX_STRING plugin_name; - char *varname; - int error; - sys_var *v __attribute__((unused)); + int error= 1; struct st_bookmark *var; - uint len, count= EXTRA_OPTIONS; + uint len=0, count= EXTRA_OPTIONS; + st_ptr_backup *tmp_backup= 0; DBUG_ENTER("test_plugin_options"); DBUG_ASSERT(tmp->plugin && tmp->name.str); - for (opt= tmp->plugin->system_vars; opt && *opt; opt++) - count+= 2; /* --{plugin}-{optname} and --plugin-{plugin}-{optname} */ - - if (count > EXTRA_OPTIONS || (*argc > 1)) + if (tmp->plugin->system_vars || (*argc > 1)) { + for (opt= tmp->plugin->system_vars; opt && *opt; opt++) + { + len++; + if (!((*opt)->flags & PLUGIN_VAR_NOCMDOPT)) + count+= 2; /* --{plugin}-{optname} and --plugin-{plugin}-{optname} */ + } + if (!(opts= (my_option*) alloc_root(tmp_root, sizeof(my_option) * count))) { sql_print_error("Out of memory for plugin '%s'.", tmp->name.str); @@ -3828,6 +3983,57 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, DBUG_RETURN(-1); } + if (tmp->plugin->system_vars) + { + tmp_backup= (st_ptr_backup *)my_alloca(len * sizeof(tmp_backup[0])); + DBUG_ASSERT(tmp->nbackups == 0); + DBUG_ASSERT(tmp->ptr_backup == 0); + + for (opt= tmp->plugin->system_vars; *opt; opt++) + { + st_mysql_sys_var *o= *opt; + char *varname; + sys_var *v; + + if (o->flags & PLUGIN_VAR_NOSYSVAR) + continue; + + tmp_backup[tmp->nbackups++].save(&o->name); + if ((var= find_bookmark(tmp->name.str, o->name, o->flags))) + varname= var->key + 1; + else + { + len= tmp->name.length + strlen(o->name) + 2; + varname= (char*) alloc_root(mem_root, len); + strxmov(varname, tmp->name.str, "-", o->name, NullS); + my_casedn_str(&my_charset_latin1, varname); + convert_dash_to_underscore(varname, len-1); + } + v= new (mem_root) sys_var_pluginvar(&chain, varname, tmp, o); + if (!(o->flags & PLUGIN_VAR_NOCMDOPT)) + { + // update app_type, used for I_S.SYSTEM_VARIABLES + for (my_option *mo=opts; mo->name; mo++) + if (mo->app_type == o) + mo->app_type= v; + } + } + + if (tmp->nbackups) + { + size_t bytes= tmp->nbackups * sizeof(tmp->ptr_backup[0]); + tmp->ptr_backup= (st_ptr_backup *)alloc_root(mem_root, bytes); + if (!tmp->ptr_backup) + { + restore_ptr_backup(tmp->nbackups, tmp_backup); + my_afree(tmp_backup); + goto err; + } + memcpy(tmp->ptr_backup, tmp_backup, bytes); + } + my_afree(tmp_backup); + } + /* We adjust the default value to account for the hardcoded exceptions we have set for the federated and ndbcluster storage engines. @@ -3835,7 +4041,7 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, if (!plugin_is_forced(tmp)) opts[0].def_value= opts[1].def_value= plugin_load_option; - error= handle_options(argc, &argv, opts, NULL); + error= handle_options(argc, &argv, opts, mark_changed); (*argc)++; /* add back one for the program name */ if (error) @@ -3855,6 +4061,8 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, disable_plugin= (plugin_load_option == PLUGIN_OFF); tmp->load_option= plugin_load_option; + error= 1; + /* If the plugin is disabled it should not be initialized. */ @@ -3863,73 +4071,49 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, if (global_system_variables.log_warnings) sql_print_information("Plugin '%s' is disabled.", tmp->name.str); - if (opts) - my_cleanup_options(opts); - DBUG_RETURN(1); - } - - if (!my_strcasecmp(&my_charset_latin1, tmp->name.str, "NDBCLUSTER")) - { - plugin_name.str= const_cast<char*>("ndb"); // Use legacy "ndb" prefix - plugin_name.length= 3; + goto err; } - else - plugin_name= tmp->name; - error= 1; - for (opt= tmp->plugin->system_vars; opt && *opt; opt++) + if (tmp->plugin->system_vars) { - st_mysql_sys_var *o= *opt; - - /* - PLUGIN_VAR_STR command-line options without PLUGIN_VAR_MEMALLOC, point - directly to values in the argv[] array. For plugins started at the - server startup, argv[] array is allocated with load_defaults(), and - freed when the server is shut down. But for plugins loaded with - INSTALL PLUGIN, the memory allocated with load_defaults() is freed with - freed() at the end of mysql_install_plugin(). Which means we cannot - allow any pointers into that area. - Thus, for all plugins loaded after the server was started, - we copy string values to a plugin's memroot. - */ - if (mysqld_server_started && - ((o->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_NOCMDOPT | - PLUGIN_VAR_MEMALLOC)) == PLUGIN_VAR_STR)) + for (opt= tmp->plugin->system_vars; *opt; opt++) { - sysvar_str_t* str= (sysvar_str_t *)o; - if (*str->value) - *str->value= strdup_root(mem_root, *str->value); + /* + PLUGIN_VAR_STR command-line options without PLUGIN_VAR_MEMALLOC, point + directly to values in the argv[] array. For plugins started at the + server startup, argv[] array is allocated with load_defaults(), and + freed when the server is shut down. But for plugins loaded with + INSTALL PLUGIN, the memory allocated with load_defaults() is freed with + free() at the end of mysql_install_plugin(). Which means we cannot + allow any pointers into that area. + Thus, for all plugins loaded after the server was started, + we copy string values to a plugin's memroot. + */ + if (mysqld_server_started && + (((*opt)->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_NOCMDOPT | + PLUGIN_VAR_MEMALLOC)) == PLUGIN_VAR_STR)) + { + sysvar_str_t* str= (sysvar_str_t *)*opt; + if (*str->value) + *str->value= strdup_root(mem_root, *str->value); + } } - var= find_bookmark(plugin_name.str, o->name, o->flags); - if (o->flags & PLUGIN_VAR_NOSYSVAR) - continue; - if (var) - v= new (mem_root) sys_var_pluginvar(&chain, var->key + 1, o, tmp); - else - { - len= plugin_name.length + strlen(o->name) + 2; - varname= (char*) alloc_root(mem_root, len); - strxmov(varname, plugin_name.str, "-", o->name, NullS); - my_casedn_str(&my_charset_latin1, varname); - convert_dash_to_underscore(varname, len-1); - v= new (mem_root) sys_var_pluginvar(&chain, varname, o, tmp); - } - DBUG_ASSERT(v); /* check that an object was actually constructed */ - } /* end for */ - if (chain.first) - { - chain.last->next = NULL; - if (mysql_add_sys_var_chain(chain.first)) + if (chain.first) { - sql_print_error("Plugin '%s' has conflicting system variables", - tmp->name.str); - goto err; + chain.last->next = NULL; + if (mysql_add_sys_var_chain(chain.first)) + { + sql_print_error("Plugin '%s' has conflicting system variables", + tmp->name.str); + goto err; + } + tmp->system_vars= chain.first; } - tmp->system_vars= chain.first; } + DBUG_RETURN(0); - + err: if (opts) my_cleanup_options(opts); @@ -3963,3 +4147,159 @@ void add_plugin_options(DYNAMIC_ARRAY *options, MEM_ROOT *mem_root) insert_dynamic(options, (uchar*) opt); } } + + +/** + Returns a sys_var corresponding to a particular MYSQL_SYSVAR(...) +*/ +sys_var *find_plugin_sysvar(st_plugin_int *plugin, st_mysql_sys_var *plugin_var) +{ + for (sys_var *var= plugin->system_vars; var; var= var->next) + { + sys_var_pluginvar *pvar=var->cast_pluginvar(); + if (pvar->plugin_var == plugin_var) + return var; + } + return 0; +} + +/* + On dlclose() we need to restore values of all symbols that we've modified in + the DSO. The reason is - the DSO might not actually be unloaded, so on the + next dlopen() these symbols will have old values, they won't be + reinitialized. + + Perhaps, there can be many reason, why a DSO won't be unloaded. Strictly + speaking, it's implementation defined whether to unload an unused DSO or to + keep it in memory. + + In particular, this happens for some plugins: In 2009 a new ELF stub was + introduced, see Ulrich Drepper's email "Unique symbols for C++" + http://www.redhat.com/archives/posix-c++-wg/2009-August/msg00002.html + + DSO that has objects with this stub (STB_GNU_UNIQUE) cannot be unloaded + (this is mentioned in the email, see the url above). + + These "unique" objects are, for example, static variables in templates, + in inline functions, in classes. So any DSO that uses them can + only be loaded once. And because Boost has them, any DSO that uses Boost + almost certainly cannot be unloaded. + + To know whether a particular DSO has these objects, one can use + + readelf -s /path/to/plugin.so|grep UNIQUE + + There's nothing we can do about it, but to reset the DSO to its initial + state before dlclose(). +*/ +static void restore_ptr_backup(uint n, st_ptr_backup *backup) +{ + while (n--) + (backup++)->restore(); +} + +/**************************************************************************** + thd specifics service, see include/mysql/service_thd_specifics.h +****************************************************************************/ +static const int INVALID_THD_KEY= -1; +static uint thd_key_no = 42; + +int thd_key_create(MYSQL_THD_KEY_T *key) +{ + int flags= PLUGIN_VAR_THDLOCAL | PLUGIN_VAR_STR | + PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT; + char namebuf[256]; + snprintf(namebuf, sizeof(namebuf), "%u", thd_key_no++); + mysql_rwlock_wrlock(&LOCK_system_variables_hash); + // non-letters in the name as an extra safety + st_bookmark *bookmark= register_var("\a\v\a\t\a\r", namebuf, flags); + mysql_rwlock_unlock(&LOCK_system_variables_hash); + if (bookmark) + { + *key= bookmark->offset; + return 0; + } + return ENOMEM; +} + +void thd_key_delete(MYSQL_THD_KEY_T *key) +{ + *key= INVALID_THD_KEY; +} + +void* thd_getspecific(MYSQL_THD thd, MYSQL_THD_KEY_T key) +{ + DBUG_ASSERT(key != INVALID_THD_KEY); + if (key == INVALID_THD_KEY || (!thd && !(thd= current_thd))) + return 0; + + return *(void**)(intern_sys_var_ptr(thd, key, true)); +} + +int thd_setspecific(MYSQL_THD thd, MYSQL_THD_KEY_T key, void *value) +{ + DBUG_ASSERT(key != INVALID_THD_KEY); + if (key == INVALID_THD_KEY || (!thd && !(thd= current_thd))) + return EINVAL; + + memcpy(intern_sys_var_ptr(thd, key, true), &value, sizeof(void*)); + return 0; +} + +void plugin_mutex_init() +{ +#ifdef HAVE_PSI_INTERFACE + init_plugin_psi_keys(); +#endif + mysql_mutex_init(key_LOCK_plugin, &LOCK_plugin, MY_MUTEX_INIT_FAST); +} + +#ifdef WITH_WSREP + +/* + Placeholder for global_system_variables.table_plugin required during + initialization of startup wsrep threads. +*/ +static st_plugin_int wsrep_dummy_plugin; +static st_plugin_int *wsrep_dummy_plugin_ptr; + +/* + Initialize wsrep_dummy_plugin and assign it to + global_system_variables.table_plugin. +*/ +void wsrep_plugins_pre_init() +{ + wsrep_dummy_plugin_ptr= &wsrep_dummy_plugin; + wsrep_dummy_plugin.state= PLUGIN_IS_DISABLED; + global_system_variables.table_plugin= + plugin_int_to_ref(wsrep_dummy_plugin_ptr); +} + +/* + This function is intended to be called after the plugins and related + global system variables are initialized. It re-initializes some data + members of wsrep startup threads with correct values, as these value + were not available at the time these threads were created. +*/ +void wsrep_plugins_post_init() +{ + THD *thd; + I_List_iterator<THD> it(threads); + + while ((thd= it++)) + { + if (IF_WSREP(thd->wsrep_applier,1)) + { + // Save options_bits as it will get overwritten in plugin_thdvar_init() + ulonglong option_bits_saved= thd->variables.option_bits; + + plugin_thdvar_init(thd); + + // Restore option_bits + thd->variables.option_bits= option_bits_saved; + } + } + + return; +} +#endif /* WITH_WSREP */ diff --git a/sql/sql_plugin.h b/sql/sql_plugin.h index fcc73c83adb..d11c449962a 100644 --- a/sql/sql_plugin.h +++ b/sql/sql_plugin.h @@ -17,8 +17,6 @@ #ifndef _sql_plugin_h #define _sql_plugin_h -#include <my_global.h> - /* the following #define adds server-only members to enum_mysql_show_type, that is defined in plugin.h @@ -27,7 +25,7 @@ SHOW_LONG_STATUS, SHOW_DOUBLE_STATUS, \ SHOW_HAVE, SHOW_MY_BOOL, SHOW_HA_ROWS, SHOW_SYS, \ SHOW_LONG_NOFLUSH, SHOW_LONGLONG_STATUS, SHOW_LEX_STRING -#include <mysql/plugin.h> +#include <my_global.h> #undef SHOW_always_last #include "m_string.h" /* LEX_STRING */ @@ -39,7 +37,10 @@ enum enum_plugin_load_option { PLUGIN_OFF, PLUGIN_ON, PLUGIN_FORCE, PLUGIN_FORCE_PLUS_PERMANENT }; extern const char *global_plugin_typelib_names[]; +extern ulong dlopen_count; + #include <my_sys.h> +#include "sql_list.h" #ifdef DBUG_OFF #define plugin_ref_to_int(A) A @@ -77,6 +78,14 @@ typedef struct st_mysql_show_var SHOW_VAR; #define PLUGIN_IS_DYING 16 #define PLUGIN_IS_DISABLED 32 +struct st_ptr_backup { + void **ptr; + void *value; + void save(void **p) { ptr= p; value= *p; } + void save(const char **p) { save((void**)p); } + void restore() { *ptr= value; } +}; + /* A handle for the dynamic library containing a plugin or plugins. */ struct st_plugin_dl @@ -84,10 +93,12 @@ struct st_plugin_dl LEX_STRING dl; void *handle; struct st_maria_plugin *plugins; + st_ptr_backup *ptr_backup; + uint nbackups; + uint ref_count; /* number of plugins loaded from the library */ int mysqlversion; int mariaversion; bool allocated; - uint ref_count; /* number of plugins loaded from the library */ }; /* A handle of a plugin */ @@ -97,6 +108,8 @@ struct st_plugin_int LEX_STRING name; struct st_maria_plugin *plugin; struct st_plugin_dl *plugin_dl; + st_ptr_backup *ptr_backup; + uint nbackups; uint state; uint ref_count; /* number of threads using the plugin */ uint locks_total; /* how many times the plugin was locked */ @@ -137,9 +150,9 @@ typedef struct st_plugin_int **plugin_ref; typedef int (*plugin_type_init)(struct st_plugin_int *); -extern char *opt_plugin_load; +extern I_List<i_string> *opt_plugin_load_list_ptr; extern char *opt_plugin_dir_ptr; -extern char opt_plugin_dir[FN_REFLEN]; +extern MYSQL_PLUGIN_IMPORT char opt_plugin_dir[FN_REFLEN]; extern const LEX_STRING plugin_type_names[]; extern ulong plugin_maturity; extern TYPELIB plugin_maturity_values; @@ -150,9 +163,7 @@ extern void plugin_shutdown(void); void add_plugin_options(DYNAMIC_ARRAY *options, MEM_ROOT *mem_root); extern bool plugin_is_ready(const LEX_STRING *name, int type); #define my_plugin_lock_by_name(A,B,C) plugin_lock_by_name(A,B,C) -#define my_plugin_lock_by_name_ci(A,B,C) plugin_lock_by_name(A,B,C) #define my_plugin_lock(A,B) plugin_lock(A,B) -#define my_plugin_lock_ci(A,B) plugin_lock(A,B) extern plugin_ref plugin_lock(THD *thd, plugin_ref ptr); extern plugin_ref plugin_lock_by_name(THD *thd, const LEX_STRING *name, int type); @@ -165,8 +176,11 @@ extern bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name, extern bool plugin_register_builtin(struct st_mysql_plugin *plugin); extern void plugin_thdvar_init(THD *thd); extern void plugin_thdvar_cleanup(THD *thd); +sys_var *find_plugin_sysvar(st_plugin_int *plugin, st_mysql_sys_var *var); +void plugin_opt_set_limits(struct my_option *, const struct st_mysql_sys_var *); extern SHOW_COMP_OPTION plugin_status(const char *name, size_t len, int type); extern bool check_valid_path(const char *path, size_t length); +extern void plugin_mutex_init(); typedef my_bool (plugin_foreach_func)(THD *thd, plugin_ref plugin, @@ -174,6 +188,16 @@ typedef my_bool (plugin_foreach_func)(THD *thd, #define plugin_foreach(A,B,C,D) plugin_foreach_with_mask(A,B,C,PLUGIN_IS_READY,D) extern bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func *func, int type, uint state_mask, void *arg); +extern void sync_dynamic_session_variables(THD* thd, bool global_lock); + +extern bool plugin_dl_foreach(THD *thd, const LEX_STRING *dl, + plugin_foreach_func *func, void *arg); extern void sync_dynamic_session_variables(THD* thd, bool global_lock); #endif + +#ifdef WITH_WSREP +extern void wsrep_plugins_pre_init(); +extern void wsrep_plugins_post_init(); +#endif /* WITH_WSREP */ + diff --git a/sql/sql_plugin_services.h b/sql/sql_plugin_services.h deleted file mode 100644 index ede8d9a675e..00000000000 --- a/sql/sql_plugin_services.h +++ /dev/null @@ -1,82 +0,0 @@ -/* Copyright (c) 2009, 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-1301 USA */ - -/* support for Services */ -#include <service_versions.h> - -struct st_service_ref { - const char *name; - uint version; - void *service; -}; - -static struct my_snprintf_service_st my_snprintf_handler = { - my_snprintf, - my_vsnprintf -}; - -static struct thd_alloc_service_st thd_alloc_handler= { - thd_alloc, - thd_calloc, - thd_strdup, - thd_strmake, - thd_memdup, - thd_make_lex_string -}; - -static struct thd_wait_service_st thd_wait_handler= { - thd_wait_begin, - thd_wait_end -}; - -static struct my_thread_scheduler_service my_thread_scheduler_handler= { - my_thread_scheduler_set, - my_thread_scheduler_reset, -}; - -static struct progress_report_service_st progress_report_handler= { - thd_progress_init, - thd_progress_report, - thd_progress_next_stage, - thd_progress_end, - set_thd_proc_info -}; - -static struct kill_statement_service_st thd_kill_statement_handler= { - thd_kill_level -}; - -static struct logger_service_st logger_service_handler= { - logger_init_mutexes, - logger_open, - logger_close, - logger_vprintf, - logger_printf, - logger_write, - logger_rotate -}; - -static struct st_service_ref list_of_services[]= -{ - { "my_snprintf_service", VERSION_my_snprintf, &my_snprintf_handler }, - { "thd_alloc_service", VERSION_thd_alloc, &thd_alloc_handler }, - { "thd_wait_service", VERSION_thd_wait, &thd_wait_handler }, - { "my_thread_scheduler_service", VERSION_my_thread_scheduler, &my_thread_scheduler_handler }, - { "progress_report_service", VERSION_progress_report, &progress_report_handler }, - { "debug_sync_service", VERSION_debug_sync, 0 }, // updated in plugin_init() - { "thd_kill_statement_service", VERSION_kill_statement, &thd_kill_statement_handler }, - { "logger_service", VERSION_logger, &logger_service_handler }, -}; - diff --git a/sql/sql_plugin_services.ic b/sql/sql_plugin_services.ic new file mode 100644 index 00000000000..427d8937c57 --- /dev/null +++ b/sql/sql_plugin_services.ic @@ -0,0 +1,243 @@ +/* Copyright (c) 2009, 2010, Oracle and/or its affiliates. + Copyright (c) 2012, 2014, Monty Program Ab + + 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 */ + +/* support for Services */ +#include <service_versions.h> +#include <mysql/service_wsrep.h> + +struct st_service_ref { + const char *name; + uint version; + void *service; +}; + +static struct my_snprintf_service_st my_snprintf_handler = { + my_snprintf, + my_vsnprintf +}; + +static struct thd_alloc_service_st thd_alloc_handler= { + thd_alloc, + thd_calloc, + thd_strdup, + thd_strmake, + thd_memdup, + thd_make_lex_string +}; + +static struct thd_wait_service_st thd_wait_handler= { + thd_wait_begin, + thd_wait_end +}; + +static struct progress_report_service_st progress_report_handler= { + thd_progress_init, + thd_progress_report, + thd_progress_next_stage, + thd_progress_end, + set_thd_proc_info +}; + +static struct kill_statement_service_st thd_kill_statement_handler= { + thd_kill_level +}; + +static struct thd_timezone_service_st thd_timezone_handler= { + thd_TIME_to_gmt_sec, + thd_gmt_sec_to_TIME +}; + +static struct my_sha2_service_st my_sha2_handler = { + my_sha224, + my_sha224_multi, + my_sha224_context_size, + my_sha224_init, + my_sha224_input, + my_sha224_result, + my_sha256, + my_sha256_multi, + my_sha256_context_size, + my_sha256_init, + my_sha256_input, + my_sha256_result, + my_sha384, + my_sha384_multi, + my_sha384_context_size, + my_sha384_init, + my_sha384_input, + my_sha384_result, + my_sha512, + my_sha512_multi, + my_sha512_context_size, + my_sha512_init, + my_sha512_input, + my_sha512_result, +}; + +static struct my_sha1_service_st my_sha1_handler = { + my_sha1, + my_sha1_multi, + my_sha1_context_size, + my_sha1_init, + my_sha1_input, + my_sha1_result +}; + +static struct my_md5_service_st my_md5_handler = { + my_md5, + my_md5_multi, + my_md5_context_size, + my_md5_init, + my_md5_input, + my_md5_result +}; + +static struct logger_service_st logger_service_handler= { + logger_init_mutexes, + logger_open, + logger_close, + logger_vprintf, + logger_printf, + logger_write, + logger_rotate +}; + +static struct thd_autoinc_service_st thd_autoinc_handler= { + thd_get_autoinc +}; + +static struct thd_rnd_service_st thd_rnd_handler= { + thd_rnd, + thd_create_random_password +}; + +static struct base64_service_st base64_handler= { + base64_needed_encoded_length, + base64_encode_max_arg_length, + base64_needed_decoded_length, + base64_decode_max_arg_length, + base64_encode, + base64_decode +}; + +static struct thd_error_context_service_st thd_error_context_handler= { + thd_get_error_message, + thd_get_error_number, + thd_get_error_row, + thd_inc_error_row, + thd_get_error_context_description +}; + +static struct wsrep_service_st wsrep_handler = { + get_wsrep, + get_wsrep_certify_nonPK, + get_wsrep_debug, + get_wsrep_drupal_282555_workaround, + get_wsrep_recovery, + get_wsrep_load_data_splitting, + get_wsrep_log_conflicts, + get_wsrep_protocol_version, + wsrep_aborting_thd_contains, + wsrep_aborting_thd_enqueue, + wsrep_consistency_check, + wsrep_is_wsrep_xid, + wsrep_lock_rollback, + wsrep_on, + wsrep_post_commit, + wsrep_prepare_key, + wsrep_run_wsrep_commit, + wsrep_thd_LOCK, + wsrep_thd_UNLOCK, + wsrep_thd_awake, + wsrep_thd_conflict_state, + wsrep_thd_conflict_state_str, + wsrep_thd_exec_mode, + wsrep_thd_exec_mode_str, + wsrep_thd_get_conflict_state, + wsrep_thd_is_BF, + wsrep_thd_is_wsrep, + wsrep_thd_query, + wsrep_thd_query_state, + wsrep_thd_query_state_str, + wsrep_thd_retry_counter, + wsrep_thd_set_conflict_state, + wsrep_thd_ignore_table, + wsrep_thd_trx_seqno, + wsrep_thd_ws_handle, + wsrep_thd_auto_increment_variables, + wsrep_trx_is_aborting, + wsrep_trx_order_before, + wsrep_unlock_rollback, + wsrep_set_data_home_dir +}; + +static struct thd_specifics_service_st thd_specifics_handler= +{ + thd_key_create, + thd_key_delete, + thd_getspecific, + thd_setspecific +}; + +static struct encryption_scheme_service_st encryption_scheme_handler= +{ + encryption_scheme_encrypt, + encryption_scheme_decrypt +}; + +static struct my_crypt_service_st crypt_handler= +{ + my_aes_crypt_init, + my_aes_crypt_update, + my_aes_crypt_finish, + my_aes_crypt, + my_aes_get_size, + my_aes_ctx_size, + my_random_bytes +}; + +static struct my_print_error_service_st my_print_error_handler= +{ + my_error, + my_printf_error, + my_printv_error +}; + +static struct st_service_ref list_of_services[]= +{ + { "base64_service", VERSION_base64, &base64_handler }, + { "debug_sync_service", VERSION_debug_sync, 0 }, // updated in plugin_init() + { "encryption_scheme_service", VERSION_encryption_scheme, &encryption_scheme_handler }, + { "encryption_service", VERSION_encryption, &encryption_handler }, + { "logger_service", VERSION_logger, &logger_service_handler }, + { "my_crypt_service", VERSION_my_crypt, &crypt_handler}, + { "my_md5_service", VERSION_my_md5, &my_md5_handler}, + { "my_print_error_service", VERSION_my_print_error, &my_print_error_handler}, + { "my_sha1_service", VERSION_my_sha1, &my_sha1_handler}, + { "my_sha2_service", VERSION_my_sha2, &my_sha2_handler}, + { "my_snprintf_service", VERSION_my_snprintf, &my_snprintf_handler }, + { "progress_report_service", VERSION_progress_report, &progress_report_handler }, + { "thd_alloc_service", VERSION_thd_alloc, &thd_alloc_handler }, + { "thd_autoinc_service", VERSION_thd_autoinc, &thd_autoinc_handler }, + { "thd_error_context_service", VERSION_thd_error_context, &thd_error_context_handler }, + { "thd_kill_statement_service", VERSION_kill_statement, &thd_kill_statement_handler }, + { "thd_rnd_service", VERSION_thd_rnd, &thd_rnd_handler }, + { "thd_specifics_service", VERSION_thd_specifics, &thd_specifics_handler }, + { "thd_timezone_service", VERSION_thd_timezone, &thd_timezone_handler }, + { "thd_wait_service", VERSION_thd_wait, &thd_wait_handler }, + { "wsrep_service", VERSION_wsrep, &wsrep_handler } +}; + diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 16923ee5a21..9de2f7f34a5 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2002, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2015, MariaDB + Copyright (c) 2008, 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 @@ -74,7 +74,7 @@ When one supplies long data for a placeholder: - Server gets the long data in pieces with command type 'COM_STMT_SEND_LONG_DATA'. - - The packet recieved will have the format as: + - The packet received will have the format as: [COM_STMT_SEND_LONG_DATA:1][STMT_ID:4][parameter_number:2][data] - data from the packet is appended to the long data value buffer for this placeholder. @@ -84,7 +84,7 @@ When one supplies long data for a placeholder: at statement execute. */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "unireg.h" #include "sql_class.h" // set_var.h: THD @@ -103,9 +103,13 @@ When one supplies long data for a placeholder: #include "sql_derived.h" // mysql_derived_prepare, // mysql_handle_derived #include "sql_cursor.h" +#include "sql_show.h" +#include "sql_repl.h" +#include "slave.h" #include "sp_head.h" #include "sp.h" #include "sp_cache.h" +#include "sql_handler.h" // mysql_ha_rm_tables #include "probes_mysql.h" #ifdef EMBEDDED_LIBRARY /* include MYSQL_BIND headers */ @@ -116,6 +120,7 @@ When one supplies long data for a placeholder: #include "lock.h" // MYSQL_OPEN_FORCE_SHARED_MDL #include "sql_handler.h" #include "transaction.h" // trans_rollback_implicit +#include "wsrep_mysqld.h" /** A result class used to send cursor rows using the binary protocol. @@ -344,7 +349,7 @@ static bool send_prep_stmt(Prepared_statement *stmt, uint columns) int2store(buff+5, columns); int2store(buff+7, stmt->param_count); buff[9]= 0; // Guard against a 4.1 client - tmp= min(stmt->thd->warning_info->statement_warn_count(), 65535); + tmp= MY_MIN(stmt->thd->get_stmt_da()->current_statement_warn_count(), 65535); int2store(buff+10, tmp); /* @@ -361,7 +366,7 @@ static bool send_prep_stmt(Prepared_statement *stmt, uint columns) if (!error) /* Flag that a response has already been sent */ - thd->stmt_da->disable_status(); + thd->get_stmt_da()->disable_status(); DBUG_RETURN(error); } @@ -374,7 +379,7 @@ static bool send_prep_stmt(Prepared_statement *stmt, thd->client_stmt_id= stmt->id; thd->client_param_count= stmt->param_count; thd->clear_error(); - thd->stmt_da->disable_status(); + thd->get_stmt_da()->disable_status(); return 0; } @@ -857,14 +862,9 @@ static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array, THD *thd= stmt->thd; Item_param **begin= stmt->param_array; Item_param **end= begin + stmt->param_count; - uint32 length= 0; - String str; - const String *res; + Copy_query_with_rewrite acc(thd, stmt->query(), stmt->query_length(), query); DBUG_ENTER("insert_params_with_log"); - if (query->copy(stmt->query(), stmt->query_length(), default_charset_info)) - DBUG_RETURN(1); - for (Item_param **it= begin; it < end; ++it) { Item_param *param= *it; @@ -897,15 +897,16 @@ static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array, */ else if (! is_param_long_data_type(param)) DBUG_RETURN(1); - res= param->query_val_str(thd, &str); - if (param->convert_str_value(thd)) - DBUG_RETURN(1); /* out of memory */ - if (query->replace(param->pos_in_query+length, 1, *res)) + if (acc.append(param)) DBUG_RETURN(1); - length+= res->length()-1; + if (param->convert_str_value(thd)) + DBUG_RETURN(1); /* out of memory */ } + if (acc.finalize()) + DBUG_RETURN(1); + DBUG_RETURN(0); } @@ -977,7 +978,7 @@ static bool setup_conversion_functions(Prepared_statement *stmt, typecode= sint2korr(read_pos); read_pos+= 2; - (**it).unsigned_flag= test(typecode & signed_bit); + (**it).unsigned_flag= MY_TEST(typecode & signed_bit); setup_one_conversion_function(thd, *it, (uchar) (typecode & ~signed_bit)); } } @@ -1034,23 +1035,15 @@ static bool emb_insert_params(Prepared_statement *stmt, String *expanded_query) } -static bool emb_insert_params_with_log(Prepared_statement *stmt, - String *query) +static bool emb_insert_params_with_log(Prepared_statement *stmt, String *query) { THD *thd= stmt->thd; Item_param **it= stmt->param_array; Item_param **end= it + stmt->param_count; MYSQL_BIND *client_param= thd->client_params; - - String str; - const String *res; - uint32 length= 0; - + Copy_query_with_rewrite acc(thd, stmt->query(), stmt->query_length(), query); DBUG_ENTER("emb_insert_params_with_log"); - if (query->copy(stmt->query(), stmt->query_length(), default_charset_info)) - DBUG_RETURN(1); - for (; it < end; ++it, ++client_param) { Item_param *param= *it; @@ -1071,15 +1064,15 @@ static bool emb_insert_params_with_log(Prepared_statement *stmt, DBUG_RETURN(1); } } - res= param->query_val_str(thd, &str); - if (param->convert_str_value(thd)) - DBUG_RETURN(1); /* out of memory */ - - if (query->replace(param->pos_in_query+length, 1, *res)) + if (acc.append(param)) DBUG_RETURN(1); - length+= res->length()-1; + if (param->convert_str_value(thd)) + DBUG_RETURN(1); /* out of memory */ } + if (acc.finalize()) + DBUG_RETURN(1); + DBUG_RETURN(0); } @@ -1192,16 +1185,11 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, user_var_entry *entry; LEX_STRING *varname; List_iterator<LEX_STRING> var_it(varnames); - String buf; - const String *val; - uint32 length= 0; THD *thd= stmt->thd; + Copy_query_with_rewrite acc(thd, stmt->query(), stmt->query_length(), query); DBUG_ENTER("insert_params_from_vars_with_log"); - if (query->copy(stmt->query(), stmt->query_length(), default_charset_info)) - DBUG_RETURN(1); - for (Item_param **it= begin; it < end; ++it) { Item_param *param= *it; @@ -1217,15 +1205,16 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, setup_one_conversion_function(thd, param, param->param_type); if (param->set_from_user_var(thd, entry)) DBUG_RETURN(1); - val= param->query_val_str(thd, &buf); - if (param->convert_str_value(thd)) - DBUG_RETURN(1); /* out of memory */ + if (acc.append(param)) + DBUG_RETURN(1); - if (query->replace(param->pos_in_query+length, 1, *val)) + if (param->convert_str_value(thd)) DBUG_RETURN(1); - length+= val->length()-1; } + if (acc.finalize()) + DBUG_RETURN(1); + DBUG_RETURN(0); } @@ -1254,6 +1243,17 @@ static bool mysql_test_insert(Prepared_statement *stmt, List_item *values; DBUG_ENTER("mysql_test_insert"); + /* + Since INSERT DELAYED doesn't support temporary tables, we could + not pre-open temporary tables for SQLCOM_INSERT / SQLCOM_REPLACE. + Open them here instead. + */ + if (table_list->lock_type != TL_WRITE_DELAYED) + { + if (open_temporary_tables(thd, table_list)) + goto error; + } + if (insert_precheck(thd, table_list)) goto error; @@ -1285,7 +1285,7 @@ static bool mysql_test_insert(Prepared_statement *stmt, if (mysql_prepare_insert(thd, table_list, table_list->table, fields, values, update_fields, update_values, - duplic, &unused_conds, FALSE, FALSE, FALSE)) + duplic, &unused_conds, FALSE)) goto error; value_count= values->elements; @@ -1462,7 +1462,10 @@ static bool mysql_test_delete(Prepared_statement *stmt, goto error; } - DBUG_RETURN(mysql_prepare_delete(thd, table_list, &lex->select_lex.where)); + DBUG_RETURN(mysql_prepare_delete(thd, table_list, + lex->select_lex.with_wild, + lex->select_lex.item_list, + &lex->select_lex.where)); error: DBUG_RETURN(TRUE); } @@ -1504,7 +1507,7 @@ static int mysql_test_select(Prepared_statement *stmt, else if (check_access(thd, privilege, any_db, NULL, NULL, 0, 0)) goto error; - if (!lex->result && !(lex->result= new (stmt->mem_root) select_send)) + if (!lex->result && !(lex->result= new (stmt->mem_root) select_send(thd))) { my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), static_cast<int>(sizeof(select_send))); @@ -1524,13 +1527,13 @@ static int mysql_test_select(Prepared_statement *stmt, */ if (unit->prepare(thd, 0, 0)) goto error; - if (!lex->describe && !stmt->is_sql_prepare()) + if (!lex->describe && !thd->lex->analyze_stmt && !stmt->is_sql_prepare()) { /* Make copy of item list, as change_columns may change it */ List<Item> fields(lex->select_lex.item_list); /* Change columns if a procedure like analyse() */ - if (unit->last_procedure && unit->last_procedure->change_columns(fields)) + if (unit->last_procedure && unit->last_procedure->change_columns(thd, fields)) goto error; /* @@ -1764,7 +1767,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt) if (select_lex->item_list.elements) { /* Base table and temporary table are not in the same name space. */ - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) + if (!lex->create_info.tmp_table()) create_table->open_type= OT_BASE_ONLY; if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, @@ -1798,6 +1801,202 @@ static bool mysql_test_create_table(Prepared_statement *stmt) } +static int send_stmt_metadata(THD *thd, Prepared_statement *stmt, List<Item> *fields) +{ + if (stmt->is_sql_prepare()) + return 0; + + if (send_prep_stmt(stmt, fields->elements) || + thd->protocol->send_result_set_metadata(fields, Protocol::SEND_EOF) || + thd->protocol->flush()) + return 1; + + return 2; +} + + +/** + Validate and prepare for execution SHOW CREATE TABLE statement. + + @param stmt prepared statement + @param tables list of tables used in this query + + @retval + FALSE success + @retval + TRUE error, error message is set in THD +*/ + +static int mysql_test_show_create_table(Prepared_statement *stmt, + TABLE_LIST *tables) +{ + DBUG_ENTER("mysql_test_show_create_table"); + THD *thd= stmt->thd; + List<Item> fields; + char buff[2048]; + String buffer(buff, sizeof(buff), system_charset_info); + + if (mysqld_show_create_get_fields(thd, tables, &fields, &buffer)) + DBUG_RETURN(1); + + DBUG_RETURN(send_stmt_metadata(thd, stmt, &fields)); +} + + +/** + Validate and prepare for execution SHOW CREATE DATABASE statement. + + @param stmt prepared statement + + @retval + FALSE success + @retval + TRUE error, error message is set in THD +*/ + +static int mysql_test_show_create_db(Prepared_statement *stmt) +{ + DBUG_ENTER("mysql_test_show_create_db"); + THD *thd= stmt->thd; + List<Item> fields; + + mysqld_show_create_db_get_fields(thd, &fields); + + DBUG_RETURN(send_stmt_metadata(thd, stmt, &fields)); +} + + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +/** + Validate and prepare for execution SHOW GRANTS statement. + + @param stmt prepared statement + + @retval + FALSE success + @retval + TRUE error, error message is set in THD +*/ + +static int mysql_test_show_grants(Prepared_statement *stmt) +{ + DBUG_ENTER("mysql_test_show_grants"); + THD *thd= stmt->thd; + List<Item> fields; + char buff[1024]; + const char *username= NULL, *hostname= NULL, *rolename= NULL; + + if (get_show_user(thd, thd->lex->grant_user, &username, &hostname, &rolename)) + DBUG_RETURN(1); + + if (username) + strxmov(buff,"Grants for ",username,"@",hostname, NullS); + else if (rolename) + strxmov(buff,"Grants for ",rolename, NullS); + else + DBUG_RETURN(1); + + mysql_show_grants_get_fields(thd, &fields, buff); + DBUG_RETURN(send_stmt_metadata(thd, stmt, &fields)); +} +#endif /*NO_EMBEDDED_ACCESS_CHECKS*/ + + +#ifndef EMBEDDED_LIBRARY +/** + Validate and prepare for execution SHOW SLAVE STATUS statement. + + @param stmt prepared statement + + @retval + FALSE success + @retval + TRUE error, error message is set in THD +*/ + +static int mysql_test_show_slave_status(Prepared_statement *stmt) +{ + DBUG_ENTER("mysql_test_show_slave_status"); + THD *thd= stmt->thd; + List<Item> fields; + + show_master_info_get_fields(thd, &fields, thd->lex->verbose, 0); + + DBUG_RETURN(send_stmt_metadata(thd, stmt, &fields)); +} + + +/** + Validate and prepare for execution SHOW MASTER STATUS statement. + + @param stmt prepared statement + + @retval + FALSE success + @retval + TRUE error, error message is set in THD +*/ + +static int mysql_test_show_master_status(Prepared_statement *stmt) +{ + DBUG_ENTER("mysql_test_show_master_status"); + THD *thd= stmt->thd; + List<Item> fields; + + show_binlog_info_get_fields(thd, &fields); + + DBUG_RETURN(send_stmt_metadata(thd, stmt, &fields)); +} + + +/** + Validate and prepare for execution SHOW BINLOGS statement. + + @param stmt prepared statement + + @retval + FALSE success + @retval + TRUE error, error message is set in THD +*/ + +static int mysql_test_show_binlogs(Prepared_statement *stmt) +{ + DBUG_ENTER("mysql_test_show_binlogs"); + THD *thd= stmt->thd; + List<Item> fields; + + show_binlogs_get_fields(thd, &fields); + + DBUG_RETURN(send_stmt_metadata(thd, stmt, &fields)); +} + +#endif /* EMBEDDED_LIBRARY */ + + +/** + Validate and prepare for execution SHOW CREATE PROC/FUNC statement. + + @param stmt prepared statement + + @retval + FALSE success + @retval + TRUE error, error message is set in THD +*/ + +static int mysql_test_show_create_routine(Prepared_statement *stmt, int type) +{ + DBUG_ENTER("mysql_test_show_binlogs"); + THD *thd= stmt->thd; + List<Item> fields; + + sp_head::show_create_routine_get_fields(thd, type, &fields); + + DBUG_RETURN(send_stmt_metadata(thd, stmt, &fields)); +} + + /** @brief Validate and prepare for execution CREATE VIEW statement @@ -1806,7 +2005,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt) @note This function handles create view commands. @retval FALSE Operation was a success. - @retval TRUE An error occured. + @retval TRUE An error occurred. */ static bool mysql_test_create_view(Prepared_statement *stmt) @@ -1823,6 +2022,13 @@ static bool mysql_test_create_view(Prepared_statement *stmt) if (create_view_precheck(thd, tables, view, lex->create_view_mode)) goto err; + /* + Since we can't pre-open temporary tables for SQLCOM_CREATE_VIEW, + (see mysql_create_view) we have to do it here instead. + */ + if (open_temporary_tables(thd, tables)) + goto err; + if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL, DT_PREPARE)) goto err; @@ -1878,14 +2084,17 @@ static bool mysql_test_multiupdate(Prepared_statement *stmt, static bool mysql_test_multidelete(Prepared_statement *stmt, TABLE_LIST *tables) { - stmt->thd->lex->current_select= &stmt->thd->lex->select_lex; - if (add_item_to_list(stmt->thd, new Item_null())) + THD *thd= stmt->thd; + + thd->lex->current_select= &thd->lex->select_lex; + if (add_item_to_list(thd, new (thd->mem_root) + Item_null(thd))) { my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 0); goto error; } - if (multi_delete_precheck(stmt->thd, tables) || + if (multi_delete_precheck(thd, tables) || select_like_stmt_test_with_open(stmt, tables, &mysql_multi_delete_prepare, OPTION_SETUP_TABLES_DONE)) @@ -2005,7 +2214,7 @@ static int mysql_test_handler_read(Prepared_statement *stmt, if (!stmt->is_sql_prepare()) { - if (!lex->result && !(lex->result= new (stmt->mem_root) select_send)) + if (!lex->result && !(lex->result= new (stmt->mem_root) select_send(thd))) { my_error(ER_OUTOFMEMORY, MYF(0), sizeof(select_send)); DBUG_RETURN(1); @@ -2058,7 +2267,20 @@ static bool check_prepared_statement(Prepared_statement *stmt) /* Reset warning count for each query that uses tables */ if (tables) - thd->warning_info->opt_clear_warning_info(thd->query_id); + thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); + + if (sql_command_flags[sql_command] & CF_HA_CLOSE) + mysql_ha_rm_tables(thd, tables); + + /* + Open temporary tables that are known now. Temporary tables added by + prelocking will be opened afterwards (during open_tables()). + */ + if (sql_command_flags[sql_command] & CF_PREOPEN_TMP_TABLES) + { + if (open_temporary_tables(thd, tables)) + goto error; + } switch (sql_command) { case SQLCOM_REPLACE: @@ -2074,7 +2296,6 @@ static bool check_prepared_statement(Prepared_statement *stmt) /* mysql_test_update returns 2 if we need to switch to multi-update */ if (res != 2) break; - /* fall through */ case SQLCOM_UPDATE_MULTI: res= mysql_test_multiupdate(stmt, tables, res == 2); @@ -2109,11 +2330,70 @@ static bool check_prepared_statement(Prepared_statement *stmt) case SQLCOM_CREATE_TABLE: res= mysql_test_create_table(stmt); break; - + case SQLCOM_SHOW_CREATE: + if ((res= mysql_test_show_create_table(stmt, tables)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; + case SQLCOM_SHOW_CREATE_DB: + if ((res= mysql_test_show_create_db(stmt)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + case SQLCOM_SHOW_GRANTS: + if ((res= mysql_test_show_grants(stmt)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ +#ifndef EMBEDDED_LIBRARY + case SQLCOM_SHOW_SLAVE_STAT: + if ((res= mysql_test_show_slave_status(stmt)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; + case SQLCOM_SHOW_MASTER_STAT: + if ((res= mysql_test_show_master_status(stmt)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; + case SQLCOM_SHOW_BINLOGS: + if ((res= mysql_test_show_binlogs(stmt)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; +#endif /* EMBEDDED_LIBRARY */ + case SQLCOM_SHOW_CREATE_PROC: + if ((res= mysql_test_show_create_routine(stmt, TYPE_ENUM_PROCEDURE)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; + case SQLCOM_SHOW_CREATE_FUNC: + if ((res= mysql_test_show_create_routine(stmt, TYPE_ENUM_FUNCTION)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; case SQLCOM_CREATE_VIEW: if (lex->create_view_mode == VIEW_ALTER) { - my_message(ER_UNSUPPORTED_PS, ER(ER_UNSUPPORTED_PS), MYF(0)); + my_message(ER_UNSUPPORTED_PS, ER_THD(thd, ER_UNSUPPORTED_PS), MYF(0)); goto error; } res= mysql_test_create_view(stmt); @@ -2147,6 +2427,7 @@ static bool check_prepared_statement(Prepared_statement *stmt) Note that we don't need to have cases in this list if they are marked with CF_STATUS_COMMAND in sql_command_flags */ + case SQLCOM_SHOW_EXPLAIN: case SQLCOM_DROP_TABLE: case SQLCOM_RENAME_TABLE: case SQLCOM_ALTER_TABLE: @@ -2154,6 +2435,7 @@ static bool check_prepared_statement(Prepared_statement *stmt) case SQLCOM_CREATE_INDEX: case SQLCOM_DROP_INDEX: case SQLCOM_ROLLBACK: + case SQLCOM_ROLLBACK_TO_SAVEPOINT: case SQLCOM_TRUNCATE: case SQLCOM_DROP_VIEW: case SQLCOM_REPAIR: @@ -2164,6 +2446,8 @@ static bool check_prepared_statement(Prepared_statement *stmt) case SQLCOM_FLUSH: case SQLCOM_SLAVE_START: case SQLCOM_SLAVE_STOP: + case SQLCOM_SLAVE_ALL_START: + case SQLCOM_SLAVE_ALL_STOP: case SQLCOM_INSTALL_PLUGIN: case SQLCOM_UNINSTALL_PLUGIN: case SQLCOM_CREATE_DB: @@ -2173,11 +2457,18 @@ static bool check_prepared_statement(Prepared_statement *stmt) case SQLCOM_CREATE_USER: case SQLCOM_RENAME_USER: case SQLCOM_DROP_USER: + case SQLCOM_CREATE_ROLE: + case SQLCOM_DROP_ROLE: case SQLCOM_ASSIGN_TO_KEYCACHE: case SQLCOM_PRELOAD_KEYS: case SQLCOM_GRANT: + case SQLCOM_GRANT_ROLE: case SQLCOM_REVOKE: + case SQLCOM_REVOKE_ALL: + case SQLCOM_REVOKE_ROLE: case SQLCOM_KILL: + case SQLCOM_COMPOUND: + case SQLCOM_SHUTDOWN: break; case SQLCOM_PREPARE: @@ -2191,14 +2482,32 @@ static bool check_prepared_statement(Prepared_statement *stmt) if (!(sql_command_flags[sql_command] & CF_STATUS_COMMAND)) { /* All other statements are not supported yet. */ - my_message(ER_UNSUPPORTED_PS, ER(ER_UNSUPPORTED_PS), MYF(0)); + my_message(ER_UNSUPPORTED_PS, ER_THD(thd, ER_UNSUPPORTED_PS), MYF(0)); goto error; } break; } if (res == 0) - DBUG_RETURN(stmt->is_sql_prepare() ? - FALSE : (send_prep_stmt(stmt, 0) || thd->protocol->flush())); + { + if (!stmt->is_sql_prepare()) + { + if (lex->describe || lex->analyze_stmt) + { + select_send result(thd); + List<Item> field_list; + res= thd->prepare_explain_fields(&result, &field_list, + lex->describe, lex->analyze_stmt) || + send_prep_stmt(stmt, result.field_count(field_list)) || + result.send_result_set_metadata(field_list, + Protocol::SEND_EOF); + } + else + res= send_prep_stmt(stmt, 0); + if (!res) + thd->protocol->flush(); + } + DBUG_RETURN(FALSE); + } error: DBUG_RETURN(TRUE); } @@ -2217,7 +2526,8 @@ static bool init_param_array(Prepared_statement *stmt) if (stmt->param_count > (uint) UINT_MAX16) { /* Error code to be defined in 5.0 */ - my_message(ER_PS_MANY_PARAM, ER(ER_PS_MANY_PARAM), MYF(0)); + my_message(ER_PS_MANY_PARAM, ER_THD(stmt->thd, ER_PS_MANY_PARAM), + MYF(0)); return TRUE; } Item_param **to; @@ -2273,7 +2583,7 @@ void mysqld_stmt_prepare(THD *thd, const char *packet, uint packet_length) DBUG_PRINT("prep_query", ("%s", packet)); /* First of all clear possible warnings from the previous command */ - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(); if (! (stmt= new Prepared_statement(thd))) goto end; /* out of memory: error is set in Sql_alloc */ @@ -2457,6 +2767,15 @@ void mysql_sql_stmt_prepare(THD *thd) DBUG_VOID_RETURN; } +#if MYSQL_VERSION_ID < 100200 + /* + Backpoiting MDEV-14603 from 10.2 to 10.1 + Remove the code between #if..#endif when merging. + */ + Item_change_list change_list_save_point; + thd->change_list.move_elements_to(&change_list_save_point); +#endif + if (stmt->prepare(query, query_len)) { /* Statement map deletes the statement on erase */ @@ -2465,6 +2784,15 @@ void mysql_sql_stmt_prepare(THD *thd) else my_ok(thd, 0L, 0L, "Statement prepared"); +#if MYSQL_VERSION_ID < 100200 + /* + Backpoiting MDEV-14603 from 10.2 to 10.1 + Remove the code between #if..#endif when merging. + */ + thd->rollback_item_tree_changes(); + change_list_save_point.move_elements_to(&thd->change_list); +#endif + DBUG_VOID_RETURN; } @@ -2488,6 +2816,7 @@ void reinit_stmt_before_use(THD *thd, LEX *lex) object and because of this can be used in different threads. */ lex->thd= thd; + DBUG_ASSERT(!lex->explain); if (lex->empty_field_list_on_rset) { @@ -2569,7 +2898,7 @@ void reinit_stmt_before_use(THD *thd, LEX *lex) SELECT_LEX_UNIT *unit= sl->master_unit(); unit->unclean(); unit->types.empty(); - /* for derived tables & PS (which can't be reset by Item_subquery) */ + /* for derived tables & PS (which can't be reset by Item_subselect) */ unit->reinit_exec_mechanism(); unit->set_thd(thd); } @@ -2672,7 +3001,7 @@ void mysqld_stmt_execute(THD *thd, char *packet_arg, uint packet_length) packet+= 9; /* stmt_id + 5 bytes of flags */ /* First of all clear possible warnings from the previous command */ - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(); if (!(stmt= find_prepared_statement(thd, stmt_id))) { @@ -2688,7 +3017,7 @@ void mysqld_stmt_execute(THD *thd, char *packet_arg, uint packet_length) DBUG_PRINT("exec_query", ("%s", stmt->query())); DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt)); - open_cursor= test(flags & (ulong) CURSOR_TYPE_READ_ONLY); + open_cursor= MY_TEST(flags & (ulong) CURSOR_TYPE_READ_ONLY); thd->protocol= &thd->protocol_binary; stmt->execute_loop(&expanded_query, open_cursor, packet, packet_end); @@ -2746,8 +3075,57 @@ void mysql_sql_stmt_execute(THD *thd) DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt)); + /* + thd->free_list can already have some Items, + e.g. for a query like this: + PREPARE stmt FROM 'INSERT INTO t1 VALUES (@@max_sort_length)'; + SET STATEMENT max_sort_length=2048 FOR EXECUTE stmt; + thd->free_list contains a pointer to Item_int corresponding to 2048. + + If Prepared_statement::execute() notices that the table metadata for "t1" + has changed since PREPARE, it returns an error asking the calling + Prepared_statement::execute_loop() to re-prepare the statement. + Before returning the error, Prepared_statement::execute() + calls Prepared_statement::cleanup_stmt(), + which calls thd->cleanup_after_query(), + which calls Query_arena::free_items(). + + We hide "external" Items, e.g. those created while parsing the + "SET STATEMENT" part of the query, + so they don't get freed in case of re-prepare. + See MDEV-10702 Crash in SET STATEMENT FOR EXECUTE + */ + Item *free_list_backup= thd->free_list; + thd->free_list= NULL; // Hide the external (e.g. "SET STATEMENT") Items + +#if MYSQL_VERSION_ID < 100200 + /* + Backpoiting MDEV-14603 from 10.2 to 10.1 + Remove the code between #if..#endif when merging. + */ + Item_change_list change_list_save_point; + thd->change_list.move_elements_to(&change_list_save_point); +#endif + (void) stmt->execute_loop(&expanded_query, FALSE, NULL, NULL); +#if MYSQL_VERSION_ID < 100200 + /* + Backpoiting MDEV-14603 from 10.2 to 10.1 + Remove the code between #if..#endif when merging. + */ + thd->rollback_item_tree_changes(); + change_list_save_point.move_elements_to(&thd->change_list); +#endif + + thd->free_items(); // Free items created by execute_loop() + /* + Now restore the "external" (e.g. "SET STATEMENT") Item list. + It will be freed normaly in THD::cleanup_after_query(). + */ + thd->free_list= free_list_backup; + + stmt->lex->restore_set_statement_var(); DBUG_VOID_RETURN; } @@ -2771,7 +3149,7 @@ void mysqld_stmt_fetch(THD *thd, char *packet, uint packet_length) DBUG_ENTER("mysqld_stmt_fetch"); /* First of all clear possible warnings from the previous command */ - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(); status_var_increment(thd->status_var.com_stmt_fetch); if (!(stmt= find_prepared_statement(thd, stmt_id))) @@ -2831,7 +3209,7 @@ void mysqld_stmt_reset(THD *thd, char *packet) DBUG_ENTER("mysqld_stmt_reset"); /* First of all clear possible warnings from the previous command */ - mysql_reset_thd_for_next_command(thd); + thd->reset_for_next_command(); status_var_increment(thd->status_var.com_stmt_reset); if (!(stmt= find_prepared_statement(thd, stmt_id))) @@ -2852,7 +3230,7 @@ void mysqld_stmt_reset(THD *thd, char *packet) stmt->state= Query_arena::STMT_PREPARED; - general_log_print(thd, thd->command, NullS); + general_log_print(thd, thd->get_command(), NullS); my_ok(thd); @@ -2874,7 +3252,7 @@ void mysqld_stmt_close(THD *thd, char *packet) Prepared_statement *stmt; DBUG_ENTER("mysqld_stmt_close"); - thd->stmt_da->disable_status(); + thd->get_stmt_da()->disable_status(); if (!(stmt= find_prepared_statement(thd, stmt_id))) DBUG_VOID_RETURN; @@ -2885,7 +3263,7 @@ void mysqld_stmt_close(THD *thd, char *packet) */ DBUG_ASSERT(! stmt->is_in_use()); stmt->deallocate(); - general_log_print(thd, thd->command, NullS); + general_log_print(thd, thd->get_command(), NullS); DBUG_VOID_RETURN; } @@ -2950,7 +3328,7 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) status_var_increment(thd->status_var.com_stmt_send_long_data); - thd->stmt_da->disable_status(); + thd->get_stmt_da()->disable_status(); #ifndef EMBEDDED_LIBRARY /* Minimal size of long data packet is 6 bytes */ if (packet_length < MYSQL_LONG_DATA_HEADER) @@ -2971,7 +3349,7 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) /* Error will be sent in execute call */ stmt->state= Query_arena::STMT_ERROR; stmt->last_errno= ER_WRONG_ARGUMENTS; - sprintf(stmt->last_error, ER(ER_WRONG_ARGUMENTS), + sprintf(stmt->last_error, ER_THD(thd, ER_WRONG_ARGUMENTS), "mysqld_stmt_send_long_data"); DBUG_VOID_RETURN; } @@ -2979,28 +3357,25 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) param= stmt->param_array[param_number]; - Diagnostics_area new_stmt_da, *save_stmt_da= thd->stmt_da; - Warning_info new_warnning_info(thd->query_id, false); - Warning_info *save_warinig_info= thd->warning_info; + Diagnostics_area new_stmt_da(thd->query_id, false, true); + Diagnostics_area *save_stmt_da= thd->get_stmt_da(); - thd->stmt_da= &new_stmt_da; - thd->warning_info= &new_warnning_info; + thd->set_stmt_da(&new_stmt_da); #ifndef EMBEDDED_LIBRARY param->set_longdata(packet, (ulong) (packet_end - packet)); #else param->set_longdata(thd->extra_data, thd->extra_length); #endif - if (thd->stmt_da->is_error()) + if (thd->get_stmt_da()->is_error()) { stmt->state= Query_arena::STMT_ERROR; - stmt->last_errno= thd->stmt_da->sql_errno(); - strncpy(stmt->last_error, thd->stmt_da->message(), MYSQL_ERRMSG_SIZE); + stmt->last_errno= thd->get_stmt_da()->sql_errno(); + strmake_buf(stmt->last_error, thd->get_stmt_da()->message()); } - thd->stmt_da= save_stmt_da; - thd->warning_info= save_warinig_info; + thd->set_stmt_da(save_stmt_da); - general_log_print(thd, thd->command, NullS); + general_log_print(thd, thd->get_command(), NullS); DBUG_VOID_RETURN; } @@ -3010,8 +3385,8 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) Select_fetch_protocol_binary ****************************************************************************/ -Select_fetch_protocol_binary::Select_fetch_protocol_binary(THD *thd_arg) - :protocol(thd_arg) +Select_fetch_protocol_binary::Select_fetch_protocol_binary(THD *thd_arg): + select_send(thd_arg), protocol(thd_arg) {} bool Select_fetch_protocol_binary::send_result_set_metadata(List<Item> &list, uint flags) @@ -3074,8 +3449,7 @@ Reprepare_observer::report_error(THD *thd) that this thread execution stops and returns to the caller, backtracking all the way to Prepared_statement::execute_loop(). */ - thd->stmt_da->set_error_status(thd, ER_NEED_REPREPARE, - ER(ER_NEED_REPREPARE), "HY000"); + thd->get_stmt_da()->set_error_status(ER_NEED_REPREPARE); m_invalidated= TRUE; return TRUE; @@ -3110,6 +3484,7 @@ Execute_sql_statement(LEX_STRING sql_text) bool Execute_sql_statement::execute_server_code(THD *thd) { + PSI_statement_locker *parent_locker; bool error; if (alloc_query(thd, m_sql_text.str, m_sql_text.length)) @@ -3129,7 +3504,10 @@ Execute_sql_statement::execute_server_code(THD *thd) thd->lex->set_trg_event_type_for_tables(); + parent_locker= thd->m_statement_psi; + thd->m_statement_psi= NULL; error= mysql_execute_command(thd); + thd->m_statement_psi= parent_locker; /* report error issued during command execution */ if (error == 0 && thd->spcont == NULL) @@ -3137,6 +3515,7 @@ Execute_sql_statement::execute_server_code(THD *thd) thd->query(), thd->query_length()); end: + thd->lex->restore_set_statement_var(); lex_end(thd->lex); return error; @@ -3158,7 +3537,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg) flags((uint) IS_IN_USE) { init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size, - thd_arg->variables.query_prealloc_size); + thd_arg->variables.query_prealloc_size, MYF(MY_THREAD_SPECIFIC)); *last_error= '\0'; } @@ -3176,9 +3555,17 @@ void Prepared_statement::setup_set_params() Decide if we have to expand the query (because we must write it to logs or because we want to look it up in the query cache) or not. */ - if ((mysql_bin_log.is_open() && is_update_query(lex->sql_command)) || - opt_log || opt_slow_log || - query_cache_is_cacheable_query(lex)) + bool replace_params_with_values= false; + // binlog + replace_params_with_values|= mysql_bin_log.is_open() && is_update_query(lex->sql_command); + // general or slow log + replace_params_with_values|= opt_log || thd->variables.sql_log_slow; + // query cache + replace_params_with_values|= query_cache_is_cacheable_query(lex); + // but never for compound statements + replace_params_with_values&= lex->sql_command != SQLCOM_COMPOUND; + + if (replace_params_with_values) { set_params_from_vars= insert_params_from_vars_with_log; #ifndef EMBEDDED_LIBRARY @@ -3220,6 +3607,7 @@ Prepared_statement::~Prepared_statement() free_items(); if (lex) { + delete lex->sphead; delete lex->result; delete (st_lex_local *) lex; } @@ -3238,7 +3626,7 @@ void Prepared_statement::cleanup_stmt() { DBUG_ENTER("Prepared_statement::cleanup_stmt"); DBUG_PRINT("enter",("stmt: 0x%lx", (long) this)); - + thd->restore_set_statement_var(); thd->rollback_item_tree_changes(); cleanup_items(free_list); thd->cleanup_after_query(); @@ -3325,12 +3713,13 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) if (! (lex= new (mem_root) st_lex_local)) DBUG_RETURN(TRUE); + lex->stmt_lex= lex; if (set_db(thd->db, thd->db_length)) DBUG_RETURN(TRUE); /* - alloc_query() uses thd->memroot && thd->query, so we should call + alloc_query() uses thd->mem_root && thd->query, so we should call both of backup_statement() and backup_query_arena() here. */ thd->set_n_backup_statement(this, &stmt_backup); @@ -3398,12 +3787,15 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) if (error == 0) error= check_prepared_statement(this); - /* - Currently CREATE PROCEDURE/TRIGGER/EVENT are prohibited in prepared - statements: ensure we have no memory leak here if by someone tries - to PREPARE stmt FROM "CREATE PROCEDURE ..." - */ - DBUG_ASSERT(lex->sphead == NULL || error != 0); + if (error) + { + /* + let the following code know we're not in PS anymore, + the won't be any EXECUTE, so we need a full cleanup + */ + lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_PREPARE; + } + /* The order is important */ lex->unit.cleanup(); @@ -3554,7 +3946,13 @@ Prepared_statement::execute_loop(String *expanded_query, Reprepare_observer reprepare_observer; bool error; int reprepare_attempt= 0; - bool need_set_parameters= true; + + /* + - In mysql_sql_stmt_execute() we hide all "external" Items + e.g. those created in the "SET STATEMENT" part of the "EXECUTE" query. + - In case of mysqld_stmt_execute() there should not be "external" Items. + */ + DBUG_ASSERT(thd->free_list == NULL); /* Check if we got an error when sending long data */ if (state == Query_arena::STMT_ERROR) @@ -3563,25 +3961,20 @@ Prepared_statement::execute_loop(String *expanded_query, return TRUE; } -reexecute: - if (need_set_parameters && - set_parameters(expanded_query, packet, packet_end)) + if (set_parameters(expanded_query, packet, packet_end)) return TRUE; - /* - if set_parameters() has generated warnings, - we need to repeat it when reexecuting, to recreate these - warnings. - */ - need_set_parameters= thd->warning_info->statement_warn_count(); - - reprepare_observer.reset_reprepare_observer(); +#ifdef NOT_YET_FROM_MYSQL_5_6 + if (unlikely(thd->security_ctx->password_expired && + !lex->is_change_password)) + { + my_error(ER_MUST_CHANGE_PASSWORD, MYF(0)); + return true; + } +#endif - /* - If the free_list is not empty, we'll wrongly free some externally - allocated items when cleaning up after validation of the prepared - statement. - */ +reexecute: + // Make sure that reprepare() did not create any new Items. DBUG_ASSERT(thd->free_list == NULL); /* @@ -3590,22 +3983,47 @@ reexecute: the observer method will be invoked to push an error into the error stack. */ - if (sql_command_flags[lex->sql_command] & - CF_REEXECUTION_FRAGILE) + + if (sql_command_flags[lex->sql_command] & CF_REEXECUTION_FRAGILE) { + reprepare_observer.reset_reprepare_observer(); DBUG_ASSERT(thd->m_reprepare_observer == NULL); - thd->m_reprepare_observer = &reprepare_observer; + thd->m_reprepare_observer= &reprepare_observer; } error= execute(expanded_query, open_cursor) || thd->is_error(); thd->m_reprepare_observer= NULL; +#ifdef WITH_WSREP - if (error && !thd->is_fatal_error && !thd->killed && + if (WSREP_ON) + { + mysql_mutex_lock(&thd->LOCK_thd_data); + switch (thd->wsrep_conflict_state) + { + case CERT_FAILURE: + WSREP_DEBUG("PS execute fail for CERT_FAILURE: thd: %ld err: %d", + thd->thread_id, thd->get_stmt_da()->sql_errno() ); + thd->wsrep_conflict_state = NO_CONFLICT; + break; + + case MUST_REPLAY: + (void) wsrep_replay_transaction(thd); + break; + + default: + break; + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + } +#endif /* WITH_WSREP */ + + if ((sql_command_flags[lex->sql_command] & CF_REEXECUTION_FRAGILE) && + error && !thd->is_fatal_error && !thd->killed && reprepare_observer.is_invalidated() && reprepare_attempt++ < MAX_REPREPARE_ATTEMPTS) { - DBUG_ASSERT(thd->stmt_da->sql_errno() == ER_NEED_REPREPARE); + DBUG_ASSERT(thd->get_stmt_da()->sql_errno() == ER_NEED_REPREPARE); thd->clear_error(); error= reprepare(); @@ -3618,7 +4036,6 @@ reexecute: return error; } - bool Prepared_statement::execute_server_runnable(Server_runnable *server_runnable) { @@ -3707,7 +4124,7 @@ Prepared_statement::reprepare() Sic: we can't simply silence warnings during reprepare, because if it's failed, we need to return all the warnings to the user. */ - thd->warning_info->clear_warning_info(thd->query_id); + thd->get_stmt_da()->clear_warning_info(thd->query_id); } return error; } @@ -3933,13 +4350,17 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) if (query_cache_send_result_to_client(thd, thd->query(), thd->query_length()) <= 0) { + PSI_statement_locker *parent_locker; MYSQL_QUERY_EXEC_START(thd->query(), thd->thread_id, (char *) (thd->db ? thd->db : ""), &thd->security_ctx->priv_user[0], (char *) thd->security_ctx->host_or_ip, 1); + parent_locker= thd->m_statement_psi; + thd->m_statement_psi= NULL; error= mysql_execute_command(thd); + thd->m_statement_psi= parent_locker; MYSQL_QUERY_EXEC_DONE(error); } else @@ -3967,6 +4388,21 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) if (! cursor) cleanup_stmt(); + + /* + EXECUTE command has its own dummy "explain data". We don't need it, + instead, we want to keep the query plan of the statement that was + executed. + */ + if (!stmt_backup.lex->explain || + !stmt_backup.lex->explain->have_query_plan()) + { + delete_explain_query(stmt_backup.lex); + stmt_backup.lex->explain = thd->lex->explain; + thd->lex->explain= NULL; + } + else + delete_explain_query(thd->lex); thd->set_statement(&stmt_backup); thd->stmt_arena= old_stmt_arena; @@ -4001,6 +4437,7 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) general_log_write(thd, COM_STMT_EXECUTE, thd->query(), thd->query_length()); error: + thd->lex->restore_set_statement_var(); flags&= ~ (uint) IS_IN_USE; return error; } @@ -4077,7 +4514,7 @@ Ed_result_set::Ed_result_set(List<Ed_row> *rows_arg, */ Ed_connection::Ed_connection(THD *thd) - :m_warning_info(thd->query_id, false), + :m_diagnostics_area(thd->query_id, false, true), m_thd(thd), m_rsets(0), m_current_rset(0) @@ -4103,7 +4540,7 @@ Ed_connection::free_old_result() } m_current_rset= m_rsets; m_diagnostics_area.reset_diagnostics_area(); - m_warning_info.clear_warning_info(m_thd->query_id); + m_diagnostics_area.clear_warning_info(m_thd->query_id); } @@ -4140,23 +4577,20 @@ bool Ed_connection::execute_direct(Server_runnable *server_runnable) Protocol_local protocol_local(m_thd, this); Prepared_statement stmt(m_thd); Protocol *save_protocol= m_thd->protocol; - Diagnostics_area *save_diagnostics_area= m_thd->stmt_da; - Warning_info *save_warning_info= m_thd->warning_info; + Diagnostics_area *save_diagnostics_area= m_thd->get_stmt_da(); DBUG_ENTER("Ed_connection::execute_direct"); free_old_result(); /* Delete all data from previous execution, if any */ m_thd->protocol= &protocol_local; - m_thd->stmt_da= &m_diagnostics_area; - m_thd->warning_info= &m_warning_info; + m_thd->set_stmt_da(&m_diagnostics_area); rc= stmt.execute_server_runnable(server_runnable); m_thd->protocol->end_statement(); m_thd->protocol= save_protocol; - m_thd->stmt_da= save_diagnostics_area; - m_thd->warning_info= save_warning_info; + m_thd->set_stmt_da(save_diagnostics_area); /* Protocol_local makes use of m_current_rset to keep track of the last result set, while adding result sets to the end. @@ -4446,7 +4880,7 @@ bool Protocol_local::store(const char *str, size_t length, bool Protocol_local::store(MYSQL_TIME *time, int decimals) { if (decimals != AUTO_SEC_PART_DIGITS) - time->second_part= sec_part_truncate(time->second_part, decimals); + my_time_trunc(time, decimals); return store_column(time, sizeof(MYSQL_TIME)); } @@ -4464,7 +4898,7 @@ bool Protocol_local::store_date(MYSQL_TIME *time) bool Protocol_local::store_time(MYSQL_TIME *time, int decimals) { if (decimals != AUTO_SEC_PART_DIGITS) - time->second_part= sec_part_truncate(time->second_part, decimals); + my_time_trunc(time, decimals); return store_column(time, sizeof(MYSQL_TIME)); } @@ -4501,7 +4935,7 @@ bool Protocol_local::send_result_set_metadata(List<Item> *columns, uint) { DBUG_ASSERT(m_rset == 0 && !alloc_root_inited(&m_rset_root)); - init_sql_alloc(&m_rset_root, MEM_ROOT_BLOCK_SIZE, 0); + init_sql_alloc(&m_rset_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC)); if (! (m_rset= new (&m_rset_root) List<Ed_row>)) return TRUE; diff --git a/sql/sql_prepare.h b/sql/sql_prepare.h index e0891bbd188..b468ac1bf9b 100644 --- a/sql/sql_prepare.h +++ b/sql/sql_prepare.h @@ -253,16 +253,9 @@ public: */ ulong get_warn_count() const { - return m_warning_info.warn_count(); + return m_diagnostics_area.warn_count(); } - /** - Get the server warnings as a result set. - The result set has fixed metadata: - The first column is the level. - The second is a numeric code. - The third is warning text. - */ - List<MYSQL_ERROR> *get_warn_list() { return &m_warning_info.warn_list(); } + /** The following members are only valid if execute_direct() or move_to_next_result() returned an error. @@ -297,7 +290,7 @@ public: one. Never fails. */ - bool has_next_result() const { return test(m_current_rset->m_next_rset); } + bool has_next_result() const { return MY_TEST(m_current_rset->m_next_rset); } /** Only valid to call if has_next_result() returned true. Otherwise the result is undefined. @@ -305,13 +298,12 @@ public: bool move_to_next_result() { m_current_rset= m_current_rset->m_next_rset; - return test(m_current_rset); + return MY_TEST(m_current_rset); } ~Ed_connection() { free_old_result(); } private: Diagnostics_area m_diagnostics_area; - Warning_info m_warning_info; /** Execute direct interface does not support multi-statements, only multi-results. So we never have a situation when we have diff --git a/sql/sql_priv.h b/sql/sql_priv.h index 3195c5bb4a4..f54c66e1d99 100644 --- a/sql/sql_priv.h +++ b/sql/sql_priv.h @@ -50,10 +50,10 @@ do { \ compile_time_assert(MYSQL_VERSION_ID < VerHi * 10000 + VerLo * 100); \ if (((THD *) Thd) != NULL) \ - push_warning_printf(((THD *) Thd), MYSQL_ERROR::WARN_LEVEL_WARN, \ - ER_WARN_DEPRECATED_SYNTAX, \ - ER(ER_WARN_DEPRECATED_SYNTAX), \ - (Old), (New)); \ + push_warning_printf(((THD *) Thd), Sql_condition::WARN_LEVEL_WARN, \ + ER_WARN_DEPRECATED_SYNTAX, \ + ER_THD(((THD *) Thd), ER_WARN_DEPRECATED_SYNTAX), \ + (Old), (New)); \ else \ sql_print_warning("The syntax '%s' is deprecated and will be removed " \ "in a future release. Please use %s instead.", \ @@ -77,11 +77,12 @@ #define WARN_DEPRECATED_NO_REPLACEMENT(Thd,Old) \ do { \ - if (((THD *) Thd) != NULL) \ - push_warning_printf(((THD *) Thd), MYSQL_ERROR::WARN_LEVEL_WARN, \ - ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT, \ - ER(ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT), \ - (Old)); \ + THD *thd_= ((THD*) Thd); \ + if (thd_ != NULL) \ + push_warning_printf(thd_, Sql_condition::WARN_LEVEL_WARN, \ + ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT, \ + ER_THD(thd_, ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT), \ + (Old)); \ else \ sql_print_warning("'%s' is deprecated and will be removed " \ "in a future release.", (Old)); \ @@ -137,6 +138,8 @@ /* The following is used to detect a conflict with DISTINCT */ #define SELECT_ALL (1ULL << 24) // SELECT, user, parser +#define OPTION_GTID_BEGIN (1ULL << 25) // GTID BEGIN found in log + /** The following can be set when importing tables in a 'wrong order' to suppress foreign key checks */ #define OPTION_NO_FOREIGN_KEY_CHECKS (1ULL << 26) // THD, user, binlog @@ -170,55 +173,10 @@ however, needs to rollback the effects of the succeeded statement to keep replication consistent. */ -#define OPTION_MASTER_SQL_ERROR (1ULL << 35) - -/* - Dont report errors for individual rows, - But just report error on commit (or read ofcourse) - Note! Reserved for use in MySQL Cluster -*/ -#define OPTION_ALLOW_BATCH (ULL(1) << 36) // THD, intern (slave) -#define OPTION_SKIP_REPLICATION (ULL(1) << 37) // THD, user - -/* - Check how many bytes are available on buffer. - - @param buf_start Pointer to buffer start. - @param buf_current Pointer to the current position on buffer. - @param buf_len Buffer length. - - @return Number of bytes available on event buffer. -*/ -template <class T> T available_buffer(const char* buf_start, - const char* buf_current, - T buf_len) -{ - /* Sanity check */ - if (buf_current < buf_start || - buf_len < static_cast<T>(buf_current - buf_start)) - return static_cast<T>(0); - - return buf_len - (buf_current - buf_start); -} +#define OPTION_MASTER_SQL_ERROR (1ULL << 35) -/* - Check if jump value is within buffer limits. - - @param jump Number of positions we want to advance. - @param buf_start Pointer to buffer start - @param buf_current Pointer to the current position on buffer. - @param buf_len Buffer length. - - @return True If jump value is within buffer limits. - False Otherwise. -*/ -template <class T> bool valid_buffer_range(T jump, - const char* buf_start, - const char* buf_current, - T buf_len) -{ - return (jump <= available_buffer(buf_start, buf_current, buf_len)); -} +#define OPTION_SKIP_REPLICATION (1ULL << 37) // THD, user +#define OPTION_RPL_SKIP_PARALLEL (1ULL << 38) /* The rest of the file is included in the server only */ #ifndef MYSQL_CLIENT @@ -229,7 +187,7 @@ template <class T> bool valid_buffer_range(T jump, #define OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION (1ULL << 2) #define OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT (1ULL << 3) #define OPTIMIZER_SWITCH_INDEX_MERGE_SORT_INTERSECT (1ULL << 4) -#define OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN (1ULL << 5) +#define deprecated_ENGINE_CONDITION_PUSHDOWN (1ULL << 5) #define OPTIMIZER_SWITCH_INDEX_COND_PUSHDOWN (1ULL << 6) #define OPTIMIZER_SWITCH_DERIVED_MERGE (1ULL << 7) #define OPTIMIZER_SWITCH_DERIVED_WITH_KEYS (1ULL << 8) @@ -259,7 +217,8 @@ template <class T> bool valid_buffer_range(T jump, #define OPTIMIZER_SWITCH_OPTIMIZE_JOIN_BUFFER_SIZE (1ULL << 25) #define OPTIMIZER_SWITCH_TABLE_ELIMINATION (1ULL << 26) #define OPTIMIZER_SWITCH_EXTENDED_KEYS (1ULL << 27) -#define OPTIMIZER_SWITCH_LAST (1ULL << 27) +#define OPTIMIZER_SWITCH_EXISTS_TO_IN (1ULL << 28) +#define OPTIMIZER_SWITCH_ORDERBY_EQ_PROP (1ULL << 29) #define OPTIMIZER_SWITCH_DEFAULT (OPTIMIZER_SWITCH_INDEX_MERGE | \ OPTIMIZER_SWITCH_INDEX_MERGE_UNION | \ @@ -269,6 +228,7 @@ template <class T> bool valid_buffer_range(T jump, OPTIMIZER_SWITCH_DERIVED_MERGE | \ OPTIMIZER_SWITCH_DERIVED_WITH_KEYS | \ OPTIMIZER_SWITCH_TABLE_ELIMINATION | \ + OPTIMIZER_SWITCH_EXTENDED_KEYS | \ OPTIMIZER_SWITCH_IN_TO_EXISTS | \ OPTIMIZER_SWITCH_MATERIALIZATION | \ OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE|\ @@ -281,7 +241,8 @@ template <class T> bool valid_buffer_range(T jump, OPTIMIZER_SWITCH_SUBQUERY_CACHE | \ OPTIMIZER_SWITCH_SEMIJOIN | \ OPTIMIZER_SWITCH_FIRSTMATCH | \ - OPTIMIZER_SWITCH_LOOSE_SCAN ) + OPTIMIZER_SWITCH_LOOSE_SCAN | \ + OPTIMIZER_SWITCH_EXISTS_TO_IN) /* Replication uses 8 bytes to store SQL_MODE in the binary log. The day you use strictly more than 64 bits by adding one more define above, you should @@ -353,10 +314,11 @@ template <class T> bool valid_buffer_range(T jump, /* Used to check GROUP BY list in the MODE_ONLY_FULL_GROUP_BY mode */ #define UNDEF_POS (-1) +#endif /* !MYSQL_CLIENT */ + /* BINLOG_DUMP options */ #define BINLOG_DUMP_NON_BLOCK 1 -#endif /* !MYSQL_CLIENT */ #define BINLOG_SEND_ANNOTATE_ROWS_EVENT 2 @@ -381,11 +343,6 @@ enum enum_parsing_place }; -enum enum_var_type -{ - OPT_DEFAULT= 0, OPT_SESSION, OPT_GLOBAL -}; - class sys_var; enum enum_yes_no_unknown @@ -394,10 +351,12 @@ enum enum_yes_no_unknown }; #ifdef MYSQL_SERVER + /* External variables */ + /* sql_yacc.cc */ #ifndef DBUG_OFF extern void turn_parser_debug_on(); diff --git a/sql/sql_profile.cc b/sql/sql_profile.cc index feb7810fa28..48f7987daf5 100644 --- a/sql/sql_profile.cc +++ b/sql/sql_profile.cc @@ -29,11 +29,10 @@ - "profiling_history_size", integer, session + global, "Num queries stored?" */ - +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_profile.h" -#include "my_sys.h" +#include <my_sys.h> #include "sql_show.h" // schema_table_store_record #include "sql_class.h" // THD @@ -121,7 +120,7 @@ int make_profile_table_for_show(THD *thd, ST_SCHEMA_TABLE *schema_table) continue; field_info= &schema_table->fields_info[i]; - Item_field *field= new Item_field(context, + Item_field *field= new (thd->mem_root) Item_field(thd, context, NullS, NullS, field_info->field_name); if (field) { @@ -288,7 +287,7 @@ void QUERY_PROFILE::set_query_source(char *query_source_arg, uint query_length_arg) { /* Truncate to avoid DoS attacks. */ - uint length= min(MAX_QUERY_LENGTH, query_length_arg); + uint length= MY_MIN(MAX_QUERY_LENGTH, query_length_arg); DBUG_ASSERT(query_source == NULL); /* we don't leak memory */ if (query_source_arg != NULL) @@ -302,7 +301,8 @@ void QUERY_PROFILE::new_status(const char *status_arg, PROF_MEASUREMENT *prof; DBUG_ENTER("QUERY_PROFILE::status"); - DBUG_ASSERT(status_arg != NULL); + if (!status_arg) + DBUG_VOID_RETURN; if ((function_arg != NULL) && (file_arg != NULL)) prof= new PROF_MEASUREMENT(this, status_arg, function_arg, base_name(file_arg), line_arg); @@ -337,62 +337,6 @@ PROFILING::~PROFILING() } /** - A new state is given, and that signals the profiler to start a new - timed step for the current query's profile. - - @param status_arg name of this step - @param function_arg calling function (usually supplied from compiler) - @param function_arg calling file (usually supplied from compiler) - @param function_arg calling line number (usually supplied from compiler) -*/ -void PROFILING::status_change(const char *status_arg, - const char *function_arg, - const char *file_arg, unsigned int line_arg) -{ - DBUG_ENTER("PROFILING::status_change"); - - if (status_arg == NULL) /* We don't know how to handle that */ - DBUG_VOID_RETURN; - - if (current == NULL) /* This profile was already discarded. */ - DBUG_VOID_RETURN; - - if (unlikely(enabled)) - current->new_status(status_arg, function_arg, file_arg, line_arg); - - DBUG_VOID_RETURN; -} - -/** - Prepare to start processing a new query. It is an error to do this - if there's a query already in process; nesting is not supported. - - @param initial_state (optional) name of period before first state change -*/ -void PROFILING::start_new_query(const char *initial_state) -{ - DBUG_ENTER("PROFILING::start_new_query"); - - /* This should never happen unless the server is radically altered. */ - if (unlikely(current != NULL)) - { - DBUG_PRINT("warning", ("profiling code was asked to start a new query " - "before the old query was finished. This is " - "probably a bug.")); - finish_current_query(); - } - - enabled= ((thd->variables.option_bits & OPTION_PROFILING) != 0); - - if (! enabled) DBUG_VOID_RETURN; - - DBUG_ASSERT(current == NULL); - current= new QUERY_PROFILE(this, initial_state); - - DBUG_VOID_RETURN; -} - -/** Throw away the current profile, because it's useless or unwanted or corrupted. */ @@ -411,63 +355,65 @@ void PROFILING::discard_current_query() saved, and maintain the profile history size. Naturally, this may not succeed if the profile was previously discarded, and that's expected. */ -void PROFILING::finish_current_query() +void PROFILING::finish_current_query_impl() { DBUG_ENTER("PROFILING::finish_current_profile"); - if (current != NULL) + DBUG_ASSERT(current); + + /* The last fence-post, so we can support the span before this. */ + status_change("ending", NULL, NULL, 0); + + if (enabled && /* ON at end? */ + (current->query_source != NULL) && + (! current->entries.is_empty())) { - /* The last fence-post, so we can support the span before this. */ - status_change("ending", NULL, NULL, 0); + current->profiling_query_id= next_profile_id(); /* assign an id */ - if ((enabled) && /* ON at start? */ - ((thd->variables.option_bits & OPTION_PROFILING) != 0) && /* and ON at end? */ - (current->query_source != NULL) && - (! current->entries.is_empty())) - { - current->profiling_query_id= next_profile_id(); /* assign an id */ + history.push_back(current); + last= current; /* never contains something that is not in the history. */ - history.push_back(current); - last= current; /* never contains something that is not in the history. */ - current= NULL; - } - else - { - delete current; - current= NULL; - } + /* Maintain the history size. */ + while (history.elements > thd->variables.profiling_history_size) + delete history.pop(); } + else + delete current; - /* Maintain the history size. */ - while (history.elements > thd->variables.profiling_history_size) - delete history.pop(); - + current= NULL; DBUG_VOID_RETURN; } bool PROFILING::show_profiles() { - DBUG_ENTER("PROFILING::show_profiles"); QUERY_PROFILE *prof; List<Item> field_list; - - field_list.push_back(new Item_return_int("Query_ID", 10, - MYSQL_TYPE_LONG)); - field_list.push_back(new Item_return_int("Duration", TIME_FLOAT_DIGITS-1, - MYSQL_TYPE_DOUBLE)); - field_list.push_back(new Item_empty_string("Query", 40)); - - if (thd->protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) - DBUG_RETURN(TRUE); - + MEM_ROOT *mem_root= thd->mem_root; SELECT_LEX *sel= &thd->lex->select_lex; SELECT_LEX_UNIT *unit= &thd->lex->unit; ha_rows idx= 0; Protocol *protocol= thd->protocol; + void *iterator; + DBUG_ENTER("PROFILING::show_profiles"); + + field_list.push_back(new (mem_root) + Item_return_int(thd, "Query_ID", 10, + MYSQL_TYPE_LONG), + mem_root); + field_list.push_back(new (mem_root) + Item_return_int(thd, "Duration", + TIME_FLOAT_DIGITS - 1, + MYSQL_TYPE_DOUBLE), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Query", 40), + mem_root); + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); unit->set_limit(sel); - void *iterator; for (iterator= history.new_iterator(); iterator != NULL; iterator= history.iterator_next(iterator)) @@ -501,26 +447,6 @@ bool PROFILING::show_profiles() } /** - At a point in execution where we know the query source, save the text - of it in the query profile. - - This must be called exactly once per descrete statement. -*/ -void PROFILING::set_query_source(char *query_source_arg, uint query_length_arg) -{ - DBUG_ENTER("PROFILING::set_query_source"); - - if (! enabled) - DBUG_VOID_RETURN; - - if (current != NULL) - current->set_query_source(query_source_arg, query_length_arg); - else - DBUG_PRINT("info", ("no current profile to send query source to")); - DBUG_VOID_RETURN; -} - -/** Fill the information schema table, "query_profile", as defined in show.cc . There are two ways to get to this function: Selecting from the information schema, and a SHOW command. @@ -745,4 +671,10 @@ int PROFILING::fill_statistics_info(THD *thd_arg, TABLE_LIST *tables, Item *cond DBUG_RETURN(0); } + + +void PROFILING::reset() +{ + enabled= thd->variables.option_bits & OPTION_PROFILING; +} #endif /* ENABLED_PROFILING */ diff --git a/sql/sql_profile.h b/sql/sql_profile.h index f8970bb162a..1d770ca1147 100644 --- a/sql/sql_profile.h +++ b/sql/sql_profile.h @@ -268,25 +268,62 @@ private: public: PROFILING(); ~PROFILING(); - void set_query_source(char *query_source_arg, uint query_length_arg); - void start_new_query(const char *initial_state= "starting"); + /** + At a point in execution where we know the query source, save the text + of it in the query profile. + + This must be called exactly once per descrete statement. + */ + void set_query_source(char *query_source_arg, uint query_length_arg) + { + if (unlikely(current)) + current->set_query_source(query_source_arg, query_length_arg); + } + + /** + Prepare to start processing a new query. It is an error to do this + if there's a query already in process; nesting is not supported. + + @param initial_state (optional) name of period before first state change + */ + void start_new_query(const char *initial_state= "starting") + { + DBUG_ASSERT(!current); + if (unlikely(enabled)) + current= new QUERY_PROFILE(this, initial_state); + } void discard_current_query(); - void finish_current_query(); + void finish_current_query() + { + if (unlikely(current)) + finish_current_query_impl(); + } + + void finish_current_query_impl(); void status_change(const char *status_arg, const char *function_arg, - const char *file_arg, unsigned int line_arg); + const char *file_arg, unsigned int line_arg) + { + if (unlikely(current)) + current->new_status(status_arg, function_arg, file_arg, line_arg); + } - inline void set_thd(THD *thd_arg) { thd= thd_arg; }; + inline void set_thd(THD *thd_arg) + { + thd= thd_arg; + reset(); + } /* SHOW PROFILES */ bool show_profiles(); /* ... from INFORMATION_SCHEMA.PROFILING ... */ int fill_statistics_info(THD *thd, TABLE_LIST *tables, Item *cond); + void reset(); }; # endif /* ENABLED_PROFILING */ diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc index 8cc9dbd45fb..ab9e7c33a92 100644 --- a/sql/sql_reload.cc +++ b/sql/sql_reload.cc @@ -14,6 +14,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_reload.h" #include "sql_priv.h" #include "mysqld.h" // select_errors @@ -26,6 +27,7 @@ #include "hostname.h" // hostname_cache_refresh #include "sql_repl.h" // reset_master, reset_slave #include "rpl_mi.h" // Master_info::data_lock +#include "sql_show.h" #include "debug_sync.h" #include "des_key_file.h" @@ -54,7 +56,7 @@ static void disable_checkpoints(THD *thd); @retval !=0 Error; thd->killed is set or thd->is_error() is true */ -bool reload_acl_and_cache(THD *thd, unsigned long options, +bool reload_acl_and_cache(THD *thd, unsigned long long options, TABLE_LIST *tables, int *write_to_binlog) { bool result=0; @@ -93,12 +95,11 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, my_error(ER_UNKNOWN_ERROR, MYF(0)); } } + opt_noacl= 0; if (tmp_thd) { delete tmp_thd; - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); thd= 0; } reset_mqh((LEX_USER *)NULL, TRUE); @@ -131,7 +132,7 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, result= 1; } - if ((options & REFRESH_SLOW_LOG) && opt_slow_log) + if ((options & REFRESH_SLOW_LOG) && global_system_variables.sql_log_slow) logger.flush_slow_log(); if ((options & REFRESH_GENERAL_LOG) && opt_log) @@ -152,17 +153,55 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, tmp_write_to_binlog= 0; if (mysql_bin_log.is_open()) { - if (mysql_bin_log.rotate_and_purge(true)) + DYNAMIC_ARRAY *drop_gtid_domain= + (thd && (thd->lex->delete_gtid_domain.elements > 0)) ? + &thd->lex->delete_gtid_domain : NULL; + if (mysql_bin_log.rotate_and_purge(true, drop_gtid_domain)) *write_to_binlog= -1; + + if (WSREP_ON) + { + /* Wait for last binlog checkpoint event to be logged. */ + mysql_bin_log.wait_for_last_checkpoint_event(); + } } } if (options & REFRESH_RELAY_LOG) { #ifdef HAVE_REPLICATION - mysql_mutex_lock(&active_mi->data_lock); - if (rotate_relay_log(active_mi)) - *write_to_binlog= -1; - mysql_mutex_unlock(&active_mi->data_lock); + LEX_STRING connection_name; + Master_info *mi; + if (thd) + connection_name= thd->lex->relay_log_connection_name; + else + { + connection_name.str= (char*) ""; + connection_name.length= 0; + } + + /* + Writing this command to the binlog may cause problems as the + slave is not likely to have the same connection names. + */ + tmp_write_to_binlog= 0; + if (connection_name.length == 0) + { + if (master_info_index->flush_all_relay_logs()) + *write_to_binlog= -1; + } + else if (!(mi= (get_master_info(&connection_name, + Sql_condition::WARN_LEVEL_ERROR)))) + { + result= 1; + } + else + { + mysql_mutex_lock(&mi->data_lock); + if (rotate_relay_log(mi)) + *write_to_binlog= -1; + mysql_mutex_unlock(&mi->data_lock); + mi->release(); + } #endif } #ifdef HAVE_QUERY_CACHE @@ -180,6 +219,7 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, DBUG_ASSERT(!thd || thd->locked_tables_mode || !thd->mdl_context.has_locks() || thd->handler_tables_hash.records || + thd->ull_hash.records || thd->global_read_lock.is_acquired()); /* @@ -216,7 +256,8 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, NOTE: my_error() has been already called by reopen_tables() within close_cached_tables(). */ - result= 1; + thd->global_read_lock.unlock_global_read_lock(thd); + return 1; } if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed @@ -227,7 +268,16 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, } if (options & REFRESH_CHECKPOINT) disable_checkpoints(thd); - } + /* + We need to do it second time after wsrep appliers were blocked in + make_global_read_lock_block_commit(thd) above since they could have + modified the tables too. + */ + if (WSREP(thd) && + close_cached_tables(thd, tables, (options & REFRESH_FAST) ? + FALSE : TRUE, TRUE)) + result= 1; + } else { if (thd && thd->locked_tables_mode) @@ -238,9 +288,18 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, */ if (tables) { + int err; for (TABLE_LIST *t= tables; t; t= t->next_local) - if (!find_table_for_mdl_upgrade(thd, t->db, t->table_name, false)) - return 1; + if (!find_table_for_mdl_upgrade(thd, t->db, t->table_name, &err)) + { + if (is_locked_view(thd, t)) + t->next_local= t->next_global; + else + { + my_error(err, MYF(0), t->table_name); + return 1; + } + } } else { @@ -271,6 +330,16 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, } } +#ifdef WITH_WSREP + if (thd && thd->wsrep_applier) + { + /* + In case of applier thread, do not wait for table share(s) to be + removed from table definition cache. + */ + options|= REFRESH_FAST; + } +#endif if (close_cached_tables(thd, tables, ((options & REFRESH_FAST) ? FALSE : TRUE), (thd ? thd->variables.lock_wait_timeout : @@ -296,7 +365,7 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, { DBUG_ASSERT(thd); tmp_write_to_binlog= 0; - if (reset_master(thd)) + if (reset_master(thd, NULL, 0, thd->lex->next_binlog_file_number)) { /* NOTE: my_error() has been already called by reset_master(). */ result= 1; @@ -316,47 +385,51 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, #ifdef HAVE_REPLICATION if (options & REFRESH_SLAVE) { + LEX_MASTER_INFO* lex_mi= &thd->lex->mi; + Master_info *mi; tmp_write_to_binlog= 0; - mysql_mutex_lock(&LOCK_active_mi); - if (reset_slave(thd, active_mi)) + + if (!(mi= get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_ERROR))) { - /* NOTE: my_error() has been already called by reset_slave(). */ result= 1; } - mysql_mutex_unlock(&LOCK_active_mi); + else + { + /* The following will fail if slave is running */ + if (reset_slave(thd, mi)) + { + mi->release(); + /* NOTE: my_error() has been already called by reset_slave(). */ + result= 1; + } + else if (mi->connection_name.length && thd->lex->reset_slave_info.all) + { + /* If not default connection and 'all' is used */ + mi->release(); + mysql_mutex_lock(&LOCK_active_mi); + if (master_info_index->remove_master_info(mi)) + result= 1; + mysql_mutex_unlock(&LOCK_active_mi); + } + else + mi->release(); + } } #endif if (options & REFRESH_USER_RESOURCES) reset_mqh((LEX_USER *) NULL, 0); /* purecov: inspected */ - if (options & REFRESH_TABLE_STATS) - { - mysql_mutex_lock(&LOCK_global_table_stats); - free_global_table_stats(); - init_global_table_stats(); - mysql_mutex_unlock(&LOCK_global_table_stats); - } - if (options & REFRESH_INDEX_STATS) - { - mysql_mutex_lock(&LOCK_global_index_stats); - free_global_index_stats(); - init_global_index_stats(); - mysql_mutex_unlock(&LOCK_global_index_stats); - } - if (options & (REFRESH_USER_STATS | REFRESH_CLIENT_STATS)) - { - mysql_mutex_lock(&LOCK_global_user_client_stats); - if (options & REFRESH_USER_STATS) - { - free_global_user_stats(); - init_global_user_stats(); - } - if (options & REFRESH_CLIENT_STATS) - { - free_global_client_stats(); - init_global_client_stats(); - } - mysql_mutex_unlock(&LOCK_global_user_client_stats); - } + if (options & REFRESH_GENERIC) + { + List_iterator_fast<LEX_STRING> li(thd->lex->view_list); + LEX_STRING *ls; + while ((ls= li++)) + { + ST_SCHEMA_TABLE *table= find_schema_table(thd, ls->str); + if (table->reset_table()) + result= 1; + } + } if (*write_to_binlog != -1) *write_to_binlog= tmp_write_to_binlog; /* @@ -367,7 +440,8 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, /** - Implementation of FLUSH TABLES <table_list> WITH READ LOCK. + Implementation of FLUSH TABLES <table_list> WITH READ LOCK + and FLUSH TABLES <table_list> FOR EXPORT In brief: take exclusive locks, expel tables from the table cache, reopen the tables, enter the 'LOCKED TABLES' mode, @@ -456,33 +530,38 @@ bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) goto error; } - /* - Acquire SNW locks on tables to be flushed. Don't acquire global - IX and database-scope IX locks on the tables as this will make - this statement incompatible with FLUSH TABLES WITH READ LOCK. - */ - if (lock_table_names(thd, all_tables, NULL, - thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)) - goto error; + if (thd->lex->type & REFRESH_READ_LOCK) + { + /* + Acquire SNW locks on tables to be flushed. Don't acquire global + IX and database-scope IX locks on the tables as this will make + this statement incompatible with FLUSH TABLES WITH READ LOCK. + */ + if (lock_table_names(thd, all_tables, NULL, + thd->variables.lock_wait_timeout, + MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)) + goto error; - DEBUG_SYNC(thd,"flush_tables_with_read_lock_after_acquire_locks"); + DEBUG_SYNC(thd,"flush_tables_with_read_lock_after_acquire_locks"); - for (table_list= all_tables; table_list; - table_list= table_list->next_global) - { - /* Request removal of table from cache. */ - tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, - table_list->db, - table_list->table_name, FALSE); - /* Reset ticket to satisfy asserts in open_tables(). */ - table_list->mdl_request.ticket= NULL; + for (table_list= all_tables; table_list; + table_list= table_list->next_global) + { + /* Request removal of table from cache. */ + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table_list->db, + table_list->table_name, FALSE); + /* Reset ticket to satisfy asserts in open_tables(). */ + table_list->mdl_request.ticket= NULL; + } } + thd->variables.option_bits|= OPTION_TABLE_LOCK; + /* Before opening and locking tables the below call also waits for old shares to go away, so the fact that we don't pass - MYSQL_LOCK_IGNORE_FLUSH flag to it is important. + MYSQL_OPEN_IGNORE_FLUSH flag to it is important. Also we don't pass MYSQL_OPEN_HAS_MDL_LOCK flag as we want to open underlying tables if merge table is flushed. For underlying tables of the merge the below call has to @@ -491,12 +570,27 @@ bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) */ if (open_and_lock_tables(thd, all_tables, FALSE, MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK, - &lock_tables_prelocking_strategy) || - thd->locked_tables_list.init_locked_tables(thd)) + &lock_tables_prelocking_strategy)) + goto error_reset_bits; + + if (thd->lex->type & REFRESH_FOR_EXPORT) { - goto error; + // Check if all storage engines support FOR EXPORT. + for (TABLE_LIST *table_list= all_tables; table_list; + table_list= table_list->next_global) + { + if (!(table_list->table->file->ha_table_flags() & HA_CAN_EXPORT)) + { + my_error(ER_ILLEGAL_HA, MYF(0),table_list->table->file->table_type(), + table_list->db, table_list->table_name); + goto error_reset_bits; + } + } } - thd->variables.option_bits|= OPTION_TABLE_LOCK; + + if (thd->locked_tables_list.init_locked_tables(thd)) + goto error_reset_bits; + /* We don't downgrade MDL_SHARED_NO_WRITE here as the intended @@ -507,6 +601,9 @@ bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) return FALSE; +error_reset_bits: + close_thread_tables(thd); + thd->variables.option_bits&= ~OPTION_TABLE_LOCK; error: return TRUE; } diff --git a/sql/sql_reload.h b/sql/sql_reload.h index ebb3d78c003..33ca022dc14 100644 --- a/sql/sql_reload.h +++ b/sql/sql_reload.h @@ -18,7 +18,7 @@ class THD; struct TABLE_LIST; -bool reload_acl_and_cache(THD *thd, unsigned long options, +bool reload_acl_and_cache(THD *thd, unsigned long long options, TABLE_LIST *tables, int *write_to_binlog); bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables); diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index ee7c0fd2f73..4c164a3a621 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -19,26 +19,30 @@ Atomic rename of table; RENAME TABLE t1 to t2, tmp to t1 [,...] */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_rename.h" #include "sql_cache.h" // query_cache_* -#include "sql_table.h" // build_table_filename +#include "sql_table.h" // write_bin_log #include "sql_view.h" // mysql_frm_type, mysql_rename_view #include "sql_trigger.h" #include "lock.h" // MYSQL_OPEN_SKIP_TEMPORARY #include "sql_base.h" // tdc_remove_table, lock_table_names, #include "sql_handler.h" // mysql_ha_rm_tables -#include "datadict.h" +#include "sql_statistics.h" static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error); +static bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, + char *new_table_name, char *new_table_alias, + bool skip_error); static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list); /* - Every second entry in the table_list is the original name and every - second entry is the new name. + Every two entries in the table_list form a pair of original name and + the new name. */ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) @@ -58,7 +62,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) if (thd->locked_tables_mode || thd->in_active_multi_stmt_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + ER_THD(thd, ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); DBUG_RETURN(1); } @@ -81,12 +85,8 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) for (to_table= 0, ren_table= table_list; ren_table; to_table= 1 - to_table, ren_table= ren_table->next_local) { - int log_table_rename= 0; - - if ((log_table_rename= - check_if_log_table(ren_table->db_length, ren_table->db, - ren_table->table_name_length, - ren_table->table_name, 1))) + int log_table_rename; + if ((log_table_rename= check_if_log_table(ren_table, TRUE, NullS))) { /* as we use log_table_rename as an array index, we need it to start @@ -142,13 +142,9 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) } if (lock_table_names(thd, table_list, 0, thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY)) + 0)) goto err; - for (ren_table= table_list; ren_table; ren_table= ren_table->next_local) - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, ren_table->db, - ren_table->table_name, FALSE); - error=0; /* An exclusive lock on table names is satisfactory to ensure @@ -216,6 +212,28 @@ static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list) } +static bool +do_rename_temporary(THD *thd, TABLE_LIST *ren_table, TABLE_LIST *new_table, + bool skip_error) +{ + const char *new_alias; + DBUG_ENTER("do_rename_temporary"); + + new_alias= (lower_case_table_names == 2) ? new_table->alias : + new_table->table_name; + + if (find_temporary_table(thd, new_table)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); + DBUG_RETURN(1); // This can't be skipped + } + + + DBUG_RETURN(rename_temporary_table(thd, ren_table->table, + new_table->db, new_alias)); +} + + /* Rename a single table or a view @@ -236,16 +254,13 @@ static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list) true rename failed */ -bool +static bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name, char *new_table_alias, bool skip_error) { int rc= 1; - char new_name[FN_REFLEN + 1], old_name[FN_REFLEN + 1]; + handlerton *hton; const char *new_alias, *old_alias; - frm_type_enum frm_type; - enum legacy_db_type table_type; - DBUG_ENTER("do_rename"); if (lower_case_table_names == 2) @@ -260,47 +275,49 @@ do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name, } DBUG_ASSERT(new_alias); - build_table_filename(new_name, sizeof(new_name) - 1, - new_db, new_alias, reg_ext, 0); - build_table_filename(old_name, sizeof(old_name) - 1, - ren_table->db, old_alias, reg_ext, 0); - if (check_table_file_presence(old_name, - new_name, new_db, new_alias, new_alias, TRUE)) + if (ha_table_exists(thd, new_db, new_alias)) { - DBUG_RETURN(1); // This can't be skipped + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); + DBUG_RETURN(1); // This can't be skipped } - frm_type= dd_frm_type(thd, old_name, &table_type); - switch (frm_type) + if (ha_table_exists(thd, ren_table->db, old_alias, &hton) && hton) { - case FRMTYPE_TABLE: + DBUG_ASSERT(!thd->locked_tables_mode); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, + ren_table->db, ren_table->table_name, false); + + if (hton != view_pseudo_hton) + { + if (!(rc= mysql_rename_table(hton, ren_table->db, old_alias, + new_db, new_alias, 0))) { - if (!(rc= mysql_rename_table(ha_resolve_by_legacy_type(thd, - table_type), - ren_table->db, old_alias, - new_db, new_alias, 0))) + LEX_STRING db_name= { ren_table->db, ren_table->db_length }; + LEX_STRING table_name= { ren_table->table_name, + ren_table->table_name_length }; + LEX_STRING new_table= { (char *) new_alias, strlen(new_alias) }; + LEX_STRING new_db_name= { (char*)new_db, strlen(new_db)}; + (void) rename_table_in_stat_tables(thd, &db_name, &table_name, + &new_db_name, &new_table); + if ((rc= Table_triggers_list::change_table_name(thd, ren_table->db, + old_alias, + ren_table->table_name, + new_db, + new_alias))) { - if ((rc= Table_triggers_list::change_table_name(thd, ren_table->db, - old_alias, - ren_table->table_name, - new_db, - new_alias))) - { - /* - We've succeeded in renaming table's .frm and in updating - corresponding handler data, but have failed to update table's - triggers appropriately. So let us revert operations on .frm - and handler's data and report about failure to rename table. - */ - (void) mysql_rename_table(ha_resolve_by_legacy_type(thd, - table_type), - new_db, new_alias, - ren_table->db, old_alias, NO_FK_CHECKS); - } + /* + We've succeeded in renaming table's .frm and in updating + corresponding handler data, but have failed to update table's + triggers appropriately. So let us revert operations on .frm + and handler's data and report about failure to rename table. + */ + (void) mysql_rename_table(hton, new_db, new_alias, + ren_table->db, old_alias, NO_FK_CHECKS); } } - break; - case FRMTYPE_VIEW: + } + else + { /* change of schema is not allowed except of ALTER ...UPGRADE DATA DIRECTORY NAME command @@ -308,23 +325,22 @@ do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name, */ if (thd->lex->sql_command != SQLCOM_ALTER_DB_UPGRADE && strcmp(ren_table->db, new_db)) - my_error(ER_FORBID_SCHEMA_CHANGE, MYF(0), ren_table->db, - new_db); + my_error(ER_FORBID_SCHEMA_CHANGE, MYF(0), ren_table->db, new_db); else rc= mysql_rename_view(thd, new_db, new_alias, ren_table); - break; - default: - DBUG_ASSERT(0); // should never happen - case FRMTYPE_ERROR: - my_error(ER_FILE_NOT_FOUND, MYF(0), old_name, my_errno); - break; + } + } + else + { + my_error(ER_NO_SUCH_TABLE, MYF(0), ren_table->db, old_alias); } if (rc && !skip_error) DBUG_RETURN(1); DBUG_RETURN(0); - } + + /* Rename all tables in list; Return pointer to wrong entry if something goes wrong. Note that the table_list may be empty! @@ -359,8 +375,11 @@ rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error) for (ren_table= table_list; ren_table; ren_table= new_table->next_local) { new_table= ren_table->next_local; - if (do_rename(thd, ren_table, new_table->db, new_table->table_name, - new_table->alias, skip_error)) + + if (is_temporary_table(ren_table) ? + do_rename_temporary(thd, ren_table, new_table, skip_error) : + do_rename(thd, ren_table, new_table->db, new_table->table_name, + new_table->alias, skip_error)) DBUG_RETURN(ren_table); } DBUG_RETURN(0); diff --git a/sql/sql_rename.h b/sql/sql_rename.h index 039a3b8b4a1..aaf09a8d030 100644 --- a/sql/sql_rename.h +++ b/sql/sql_rename.h @@ -20,8 +20,5 @@ class THD; struct TABLE_LIST; bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent); -bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, - char *new_table_name, char *new_table_alias, - bool skip_error); #endif /* SQL_RENAME_INCLUDED */ diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index cb4904bb5a6..f1a1ca6fd2b 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -14,12 +14,15 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" +#include "sql_base.h" #include "sql_parse.h" // check_access #ifdef HAVE_REPLICATION #include "rpl_mi.h" +#include "rpl_rli.h" #include "sql_repl.h" #include "sql_acl.h" // SUPER_ACL #include "log_event.h" @@ -27,6 +30,14 @@ #include <my_dir.h> #include "rpl_handler.h" #include "debug_sync.h" +#include "log.h" // get_gtid_list_event + +enum enum_gtid_until_state { + GTID_UNTIL_NOT_DONE, + GTID_UNTIL_STOP_AFTER_STANDALONE, + GTID_UNTIL_STOP_AFTER_TRANSACTION +}; + int max_binlog_dump_events = 0; // unlimited my_bool opt_sporadic_binlog_dump_fail = 0; @@ -34,15 +45,149 @@ my_bool opt_sporadic_binlog_dump_fail = 0; static int binlog_dump_count = 0; #endif -/** - a copy of active_mi->rli->slave_skip_counter, for showing in SHOW VARIABLES, - INFORMATION_SCHEMA.GLOBAL_VARIABLES and @@sql_slave_skip_counter without - taking all the mutexes needed to access active_mi->rli->slave_skip_counter - properly. +extern TYPELIB binlog_checksum_typelib; + + +static int +fake_event_header(String* packet, Log_event_type event_type, ulong extra_len, + my_bool *do_checksum, ha_checksum *crc, const char** errmsg, + enum enum_binlog_checksum_alg checksum_alg_arg, uint32 end_pos) +{ + char header[LOG_EVENT_HEADER_LEN]; + ulong event_len; + + *do_checksum= checksum_alg_arg != BINLOG_CHECKSUM_ALG_OFF && + checksum_alg_arg != BINLOG_CHECKSUM_ALG_UNDEF; + + /* + 'when' (the timestamp) is set to 0 so that slave could distinguish between + real and fake Rotate events (if necessary) + */ + memset(header, 0, 4); + header[EVENT_TYPE_OFFSET] = (uchar)event_type; + event_len= LOG_EVENT_HEADER_LEN + extra_len + + (*do_checksum ? BINLOG_CHECKSUM_LEN : 0); + int4store(header + SERVER_ID_OFFSET, global_system_variables.server_id); + int4store(header + EVENT_LEN_OFFSET, event_len); + int2store(header + FLAGS_OFFSET, LOG_EVENT_ARTIFICIAL_F); + // TODO: check what problems this may cause and fix them + int4store(header + LOG_POS_OFFSET, end_pos); + if (packet->append(header, sizeof(header))) + { + *errmsg= "Failed due to out-of-memory writing event"; + return -1; + } + if (*do_checksum) + { + *crc= my_checksum(0, (uchar*)header, sizeof(header)); + } + return 0; +} + + +static int +fake_event_footer(String *packet, my_bool do_checksum, ha_checksum crc, const char **errmsg) +{ + if (do_checksum) + { + char b[BINLOG_CHECKSUM_LEN]; + int4store(b, crc); + if (packet->append(b, sizeof(b))) + { + *errmsg= "Failed due to out-of-memory writing event checksum"; + return -1; + } + } + return 0; +} + + +static int +fake_event_write(NET *net, String *packet, const char **errmsg) +{ + if (my_net_write(net, (uchar*) packet->ptr(), packet->length())) + { + *errmsg = "failed on my_net_write()"; + return -1; + } + return 0; +} + + +/* + Helper structure, used to pass miscellaneous info from mysql_binlog_send() + into the helper functions that it calls. */ -uint sql_slave_skip_counter; +struct binlog_send_info { + rpl_binlog_state until_binlog_state; + slave_connection_state gtid_state; + THD *thd; + NET *net; + String *packet; + char *const log_file_name; // ptr/alias to linfo.log_file_name + slave_connection_state *until_gtid_state; + slave_connection_state until_gtid_state_obj; + Format_description_log_event *fdev; + int mariadb_slave_capability; + enum_gtid_skip_type gtid_skip_group; + enum_gtid_until_state gtid_until_group; + ushort flags; + enum enum_binlog_checksum_alg current_checksum_alg; + bool slave_gtid_strict_mode; + bool send_fake_gtid_list; + bool slave_gtid_ignore_duplicates; + bool using_gtid_state; -extern TYPELIB binlog_checksum_typelib; + int error; + const char *errmsg; + char error_text[MAX_SLAVE_ERRMSG]; + rpl_gtid error_gtid; + + ulonglong heartbeat_period; + + /** start file/pos as requested by slave, for error message */ + char start_log_file_name[FN_REFLEN]; + my_off_t start_pos; + + /** last pos for error message */ + my_off_t last_pos; + +#ifndef DBUG_OFF + int left_events; + uint dbug_reconnect_counter; + ulong hb_info_counter; +#endif + + bool clear_initial_log_pos; + bool should_stop; + + binlog_send_info(THD *thd_arg, String *packet_arg, ushort flags_arg, + char *lfn) + : thd(thd_arg), net(&thd_arg->net), packet(packet_arg), + log_file_name(lfn), until_gtid_state(NULL), fdev(NULL), + gtid_skip_group(GTID_SKIP_NOT), gtid_until_group(GTID_UNTIL_NOT_DONE), + flags(flags_arg), current_checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF), + slave_gtid_strict_mode(false), send_fake_gtid_list(false), + slave_gtid_ignore_duplicates(false), + error(0), + errmsg("Unknown error"), + heartbeat_period(0), +#ifndef DBUG_OFF + left_events(max_binlog_dump_events), + dbug_reconnect_counter(0), + hb_info_counter(0), +#endif + clear_initial_log_pos(false), + should_stop(false) + { + error_text[0] = 0; + bzero(&error_gtid, sizeof(error_gtid)); + } +}; + +// prototype +static int reset_transmit_packet(struct binlog_send_info *info, ushort flags, + ulong *ev_offset, const char **errmsg); /* fake_rotate_event() builds a fake (=which does not exist physically in any @@ -62,84 +207,115 @@ extern TYPELIB binlog_checksum_typelib; part. */ -static int fake_rotate_event(NET* net, String* packet, char* log_file_name, - ulonglong position, const char** errmsg, - uint8 checksum_alg_arg) +static int fake_rotate_event(binlog_send_info *info, ulonglong position, + const char** errmsg, enum enum_binlog_checksum_alg checksum_alg_arg) { DBUG_ENTER("fake_rotate_event"); - char header[LOG_EVENT_HEADER_LEN], buf[ROTATE_HEADER_LEN+100]; - - /* - this Rotate is to be sent with checksum if and only if - slave's get_master_version_and_clock time handshake value - of master's @@global.binlog_checksum was TRUE - */ - - my_bool do_checksum= checksum_alg_arg != BINLOG_CHECKSUM_ALG_OFF && - checksum_alg_arg != BINLOG_CHECKSUM_ALG_UNDEF; - - /* - 'when' (the timestamp) is set to 0 so that slave could distinguish between - real and fake Rotate events (if necessary) - */ - memset(header, 0, 4); - header[EVENT_TYPE_OFFSET] = ROTATE_EVENT; - - char* p = log_file_name+dirname_length(log_file_name); + ulong ev_offset; + char buf[ROTATE_HEADER_LEN+100]; + my_bool do_checksum; + int err; + char* p = info->log_file_name+dirname_length(info->log_file_name); uint ident_len = (uint) strlen(p); - ulong event_len = ident_len + LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + - (do_checksum ? BINLOG_CHECKSUM_LEN : 0); - int4store(header + SERVER_ID_OFFSET, server_id); - int4store(header + EVENT_LEN_OFFSET, event_len); - int2store(header + FLAGS_OFFSET, LOG_EVENT_ARTIFICIAL_F); + String *packet= info->packet; + ha_checksum crc; - // TODO: check what problems this may cause and fix them - int4store(header + LOG_POS_OFFSET, 0); + /* reset transmit packet for the fake rotate event below */ + if (reset_transmit_packet(info, info->flags, &ev_offset, &info->errmsg)) + DBUG_RETURN(1); + + if ((err= fake_event_header(packet, ROTATE_EVENT, + ident_len + ROTATE_HEADER_LEN, &do_checksum, + &crc, + errmsg, checksum_alg_arg, 0))) + { + info->error= ER_UNKNOWN_ERROR; + DBUG_RETURN(err); + } - packet->append(header, sizeof(header)); int8store(buf+R_POS_OFFSET,position); packet->append(buf, ROTATE_HEADER_LEN); packet->append(p, ident_len); if (do_checksum) { - char b[BINLOG_CHECKSUM_LEN]; - ha_checksum crc= my_checksum(0L, NULL, 0); - crc= my_checksum(crc, (uchar*)header, sizeof(header)); crc= my_checksum(crc, (uchar*)buf, ROTATE_HEADER_LEN); crc= my_checksum(crc, (uchar*)p, ident_len); - int4store(b, crc); - packet->append(b, sizeof(b)); } - if (my_net_write(net, (uchar*) packet->ptr(), packet->length())) + if ((err= fake_event_footer(packet, do_checksum, crc, errmsg)) || + (err= fake_event_write(info->net, packet, errmsg))) { - *errmsg = "failed on my_net_write()"; - DBUG_RETURN(-1); + info->error= ER_UNKNOWN_ERROR; + DBUG_RETURN(err); } DBUG_RETURN(0); } + +static int fake_gtid_list_event(binlog_send_info *info, + Gtid_list_log_event *glev, const char** errmsg, + uint32 current_pos) +{ + my_bool do_checksum; + int err; + ha_checksum crc; + char buf[128]; + String str(buf, sizeof(buf), system_charset_info); + String* packet= info->packet; + + str.length(0); + if (glev->to_packet(&str)) + { + info->error= ER_UNKNOWN_ERROR; + *errmsg= "Failed due to out-of-memory writing Gtid_list event"; + return -1; + } + if ((err= fake_event_header(packet, GTID_LIST_EVENT, + str.length(), &do_checksum, &crc, + errmsg, info->current_checksum_alg, current_pos))) + { + info->error= ER_UNKNOWN_ERROR; + return err; + } + + packet->append(str); + if (do_checksum) + { + crc= my_checksum(crc, (uchar*)str.ptr(), str.length()); + } + + if ((err= fake_event_footer(packet, do_checksum, crc, errmsg)) || + (err= fake_event_write(info->net, packet, errmsg))) + { + info->error= ER_UNKNOWN_ERROR; + return err; + } + + return 0; +} + + /* Reset thread transmit packet buffer for event sending This function allocates header bytes for event transmission, and should be called before store the event data to the packet buffer. */ -static int reset_transmit_packet(THD *thd, ushort flags, +static int reset_transmit_packet(binlog_send_info *info, ushort flags, ulong *ev_offset, const char **errmsg) { int ret= 0; - String *packet= &thd->packet; + String *packet= &info->thd->packet; /* reserve and set default header */ packet->length(0); packet->set("\0", 1, &my_charset_bin); - if (RUN_HOOK(binlog_transmit, reserve_header, (thd, flags, packet))) + if (RUN_HOOK(binlog_transmit, reserve_header, (info->thd, flags, packet))) { + info->error= ER_UNKNOWN_ERROR; *errmsg= "Failed to run hook 'reserve_header'"; - my_errno= ER_UNKNOWN_ERROR; ret= 1; } *ev_offset= packet->length(); @@ -228,11 +404,11 @@ inline void fix_checksum(String *packet, ulong ev_offset) { /* recalculate the crc for this event */ uint data_len = uint4korr(packet->ptr() + ev_offset + EVENT_LEN_OFFSET); - ha_checksum crc= my_checksum(0L, NULL, 0); + ha_checksum crc; DBUG_ASSERT(data_len == LOG_EVENT_MINIMAL_HEADER_LEN + FORMAT_DESCRIPTION_HEADER_LEN + BINLOG_CHECKSUM_ALG_DESC_LEN + BINLOG_CHECKSUM_LEN); - crc= my_checksum(crc, (uchar *)packet->ptr() + ev_offset, data_len - + crc= my_checksum(0, (uchar *)packet->ptr() + ev_offset, data_len - BINLOG_CHECKSUM_LEN); int4store(packet->ptr() + ev_offset + data_len - BINLOG_CHECKSUM_LEN, crc); } @@ -277,9 +453,9 @@ static bool is_slave_checksum_aware(THD * thd) @c enum enum_binlog_checksum_alg */ -static uint8 get_binlog_checksum_value_at_connect(THD * thd) +static enum enum_binlog_checksum_alg get_binlog_checksum_value_at_connect(THD * thd) { - uint8 ret; + enum enum_binlog_checksum_alg ret; DBUG_ENTER("get_binlog_checksum_value_at_connect"); user_var_entry *entry= get_binlog_checksum_uservar(thd); @@ -294,7 +470,8 @@ static uint8 get_binlog_checksum_value_at_connect(THD * thd) uint dummy_errors; str.copy(entry->value, entry->length, &my_charset_bin, &my_charset_bin, &dummy_errors); - ret= (uint8) find_type ((char*) str.ptr(), &binlog_checksum_typelib, 1) - 1; + ret= (enum_binlog_checksum_alg) + (find_type ((char*) str.ptr(), &binlog_checksum_typelib, 1) - 1); DBUG_ASSERT(ret <= BINLOG_CHECKSUM_ALG_CRC32); // while it's just on CRC32 alg } DBUG_RETURN(ret); @@ -382,7 +559,7 @@ bool purge_error_message(THD* thd, int res) if ((errcode= purge_log_get_error_code(res)) != 0) { - my_message(errcode, ER(errcode), MYF(0)); + my_message(errcode, ER_THD(thd, errcode), MYF(0)); return TRUE; } my_ok(thd); @@ -439,36 +616,41 @@ bool purge_master_logs_before_date(THD* thd, time_t purge_time) mysql_bin_log.purge_logs_before_date(purge_time)); } -int test_for_non_eof_log_read_errors(int error, const char **errmsg) +void set_read_error(binlog_send_info *info, int error) { if (error == LOG_READ_EOF) - return 0; - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; + { + return; + } + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; switch (error) { case LOG_READ_BOGUS: - *errmsg = "bogus data in log event"; + info->errmsg= "bogus data in log event"; break; case LOG_READ_TOO_LARGE: - *errmsg = "log event entry exceeded max_allowed_packet; \ -Increase max_allowed_packet on master"; + info->errmsg= "log event entry exceeded max_allowed_packet; " + "Increase max_allowed_packet on master"; break; case LOG_READ_IO: - *errmsg = "I/O error reading log event"; + info->errmsg= "I/O error reading log event"; break; case LOG_READ_MEM: - *errmsg = "memory allocation failed reading log event"; + info->errmsg= "memory allocation failed reading log event"; break; case LOG_READ_TRUNC: - *errmsg = "binlog truncated in the middle of event; consider out of disk space on master"; + info->errmsg= "binlog truncated in the middle of event; " + "consider out of disk space on master"; break; case LOG_READ_CHECKSUM_FAILURE: - *errmsg = "event read from binlog did not pass crc check"; + info->errmsg= "event read from binlog did not pass crc check"; + break; + case LOG_READ_DECRYPT: + info->errmsg= "event decryption failure"; break; default: - *errmsg = "unknown error reading log event on the master"; + info->errmsg= "unknown error reading log event on the master"; break; } - return error; } @@ -492,6 +674,94 @@ static ulonglong get_heartbeat_period(THD * thd) } /* + Lookup the capabilities of the slave, which it announces by setting a value + MARIA_SLAVE_CAPABILITY_XXX in @mariadb_slave_capability. + + Older MariaDB slaves, and other MySQL slaves, do not set + @mariadb_slave_capability, corresponding to a capability of + MARIA_SLAVE_CAPABILITY_UNKNOWN (0). +*/ +static int +get_mariadb_slave_capability(THD *thd) +{ + bool null_value; + const LEX_STRING name= { C_STRING_WITH_LEN("mariadb_slave_capability") }; + const user_var_entry *entry= + (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, + name.length); + return entry ? + (int)(entry->val_int(&null_value)) : MARIA_SLAVE_CAPABILITY_UNKNOWN; +} + + +/* + Get the value of the @slave_connect_state user variable into the supplied + String (this is the GTID connect state requested by the connecting slave). + + Returns false if error (ie. slave did not set the variable and does not + want to use GTID to set start position), true if success. +*/ +static bool +get_slave_connect_state(THD *thd, String *out_str) +{ + bool null_value; + + const LEX_STRING name= { C_STRING_WITH_LEN("slave_connect_state") }; + user_var_entry *entry= + (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, + name.length); + return entry && entry->val_str(&null_value, out_str, 0) && !null_value; +} + + +static bool +get_slave_gtid_strict_mode(THD *thd) +{ + bool null_value; + + const LEX_STRING name= { C_STRING_WITH_LEN("slave_gtid_strict_mode") }; + user_var_entry *entry= + (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, + name.length); + return entry && entry->val_int(&null_value) && !null_value; +} + + +static bool +get_slave_gtid_ignore_duplicates(THD *thd) +{ + bool null_value; + + const LEX_STRING name= { C_STRING_WITH_LEN("slave_gtid_ignore_duplicates") }; + user_var_entry *entry= + (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, + name.length); + return entry && entry->val_int(&null_value) && !null_value; +} + + +/* + Get the value of the @slave_until_gtid user variable into the supplied + String (this is the GTID position specified for START SLAVE UNTIL + master_gtid_pos='xxx'). + + Returns false if error (ie. slave did not set the variable and is not doing + START SLAVE UNTIL mater_gtid_pos='xxx'), true if success. +*/ +static bool +get_slave_until_gtid(THD *thd, String *out_str) +{ + bool null_value; + + const LEX_STRING name= { C_STRING_WITH_LEN("slave_until_gtid") }; + user_var_entry *entry= + (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, + name.length); + return entry && entry->val_str(&null_value, out_str, 0) && !null_value; +} + + +/* Function prepares and sends repliation heartbeat event. @param net net object of THD @@ -505,11 +775,17 @@ static ulonglong get_heartbeat_period(THD * thd) The error to send is serious and should force terminating the dump thread. */ -static int send_heartbeat_event(NET* net, String* packet, +static int send_heartbeat_event(binlog_send_info *info, + NET* net, String* packet, const struct event_coordinates *coord, - uint8 checksum_alg_arg) + enum enum_binlog_checksum_alg checksum_alg_arg) { DBUG_ENTER("send_heartbeat_event"); + + ulong ev_offset; + if (reset_transmit_packet(info, info->flags, &ev_offset, &info->errmsg)) + DBUG_RETURN(1); + char header[LOG_EVENT_HEADER_LEN]; my_bool do_checksum= checksum_alg_arg != BINLOG_CHECKSUM_ALG_OFF && checksum_alg_arg != BINLOG_CHECKSUM_ALG_UNDEF; @@ -526,7 +802,7 @@ static int send_heartbeat_event(NET* net, String* packet, uint ident_len = strlen(p); ulong event_len = ident_len + LOG_EVENT_HEADER_LEN + (do_checksum ? BINLOG_CHECKSUM_LEN : 0); - int4store(header + SERVER_ID_OFFSET, server_id); + int4store(header + SERVER_ID_OFFSET, global_system_variables.server_id); int4store(header + EVENT_LEN_OFFSET, event_len); int2store(header + FLAGS_OFFSET, 0); @@ -538,8 +814,7 @@ static int send_heartbeat_event(NET* net, String* packet, if (do_checksum) { char b[BINLOG_CHECKSUM_LEN]; - ha_checksum crc= my_checksum(0L, NULL, 0); - crc= my_checksum(crc, (uchar*) header, sizeof(header)); + ha_checksum crc= my_checksum(0, (uchar*) header, sizeof(header)); crc= my_checksum(crc, (uchar*) p, ident_len); int4store(b, crc); packet->append(b, sizeof(b)); @@ -548,12 +823,772 @@ static int send_heartbeat_event(NET* net, String* packet, if (my_net_write(net, (uchar*) packet->ptr(), packet->length()) || net_flush(net)) { + info->error= ER_UNKNOWN_ERROR; DBUG_RETURN(-1); } + DBUG_RETURN(0); } +struct binlog_file_entry +{ + binlog_file_entry *next; + char *name; +}; + +static binlog_file_entry * +get_binlog_list(MEM_ROOT *memroot) +{ + IO_CACHE *index_file; + char fname[FN_REFLEN]; + size_t length; + binlog_file_entry *current_list= NULL, *e; + DBUG_ENTER("get_binlog_list"); + + if (!mysql_bin_log.is_open()) + { + my_error(ER_NO_BINARY_LOGGING, MYF(0)); + DBUG_RETURN(NULL); + } + + mysql_bin_log.lock_index(); + index_file=mysql_bin_log.get_index_file(); + reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 0); + + /* The file ends with EOF or empty line */ + while ((length=my_b_gets(index_file, fname, sizeof(fname))) > 1) + { + --length; /* Remove the newline */ + if (!(e= (binlog_file_entry *)alloc_root(memroot, sizeof(*e))) || + !(e->name= strmake_root(memroot, fname, length))) + { + mysql_bin_log.unlock_index(); + my_error(ER_OUTOFMEMORY, MYF(0), length + 1 + sizeof(*e)); + DBUG_RETURN(NULL); + } + e->next= current_list; + current_list= e; + } + mysql_bin_log.unlock_index(); + + DBUG_RETURN(current_list); +} + + +/* + Check if every GTID requested by the slave is contained in this (or a later) + binlog file. Return true if so, false if not. + + We do the check with a single scan of the list of GTIDs, avoiding the need + to build an in-memory hash or stuff like that. + + We need to check that slave did not request GTID D-S-N1, when the + Gtid_list_log_event for this binlog file has D-S-N2 with N2 >= N1. + (Because this means that requested GTID is in an earlier binlog). + However, if the Gtid_list_log_event indicates that D-S-N1 is the very last + GTID for domain D in prior binlog files, then it is ok to start from the + very start of this binlog file. This special case is important, as it + allows to purge old logs even if some domain is unused for long. + + In addition, we need to check that we do not have a GTID D-S-N3 in the + Gtid_list_log_event where D is not present in the requested slave state at + all. Since if D is not in requested slave state, it means that slave needs + to start at the very first GTID in domain D. +*/ +static bool +contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev) +{ + uint32 i; + + for (i= 0; i < glev->count; ++i) + { + uint32 gl_domain_id= glev->list[i].domain_id; + const rpl_gtid *gtid= st->find(gl_domain_id); + if (!gtid) + { + /* + The slave needs to start from the very beginning of this domain, which + is in an earlier binlog file. So we need to search back further. + */ + return false; + } + if (gtid->server_id == glev->list[i].server_id && + gtid->seq_no <= glev->list[i].seq_no) + { + /* + The slave needs to start after gtid, but it is contained in an earlier + binlog file. So we need to search back further, unless it was the very + last gtid logged for the domain in earlier binlog files. + */ + if (gtid->seq_no < glev->list[i].seq_no) + return false; + + /* + The slave requested D-S-N1, which happens to be the last GTID logged + in prior binlog files with same domain id D and server id S. + + The Gtid_list is kept sorted on domain_id, with the last GTID in each + domain_id group being the last one logged. So if this is the last GTID + within the domain_id group, then it is ok to start from the very + beginning of this group, per the special case explained in comment at + the start of this function. If not, then we need to search back further. + */ + if (i+1 < glev->count && gl_domain_id == glev->list[i+1].domain_id) + return false; + } + } + + return true; +} + + +static void +give_error_start_pos_missing_in_binlog(int *err, const char **errormsg, + rpl_gtid *error_gtid) +{ + rpl_gtid binlog_gtid; + + if (mysql_bin_log.lookup_domain_in_binlog_state(error_gtid->domain_id, + &binlog_gtid) && + binlog_gtid.seq_no >= error_gtid->seq_no) + { + *errormsg= "Requested slave GTID state not found in binlog. The slave has " + "probably diverged due to executing erroneous transactions"; + *err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG2; + } + else + { + *errormsg= "Requested slave GTID state not found in binlog"; + *err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG; + } +} + + +/* + Check the start GTID state requested by the slave against our binlog state. + + Give an error if the slave requests something that we do not have in our + binlog. +*/ + +static int +check_slave_start_position(binlog_send_info *info, const char **errormsg, + rpl_gtid *error_gtid) +{ + uint32 i; + int err; + slave_connection_state::entry **delete_list= NULL; + uint32 delete_idx= 0; + slave_connection_state *st= &info->gtid_state; + + if (rpl_load_gtid_slave_state(info->thd)) + { + *errormsg= "Failed to load replication slave GTID state"; + err= ER_CANNOT_LOAD_SLAVE_GTID_STATE; + goto end; + } + + for (i= 0; i < st->hash.records; ++i) + { + slave_connection_state::entry *slave_gtid_entry= + (slave_connection_state::entry *)my_hash_element(&st->hash, i); + rpl_gtid *slave_gtid= &slave_gtid_entry->gtid; + rpl_gtid master_gtid; + rpl_gtid master_replication_gtid; + rpl_gtid start_gtid; + bool start_at_own_slave_pos= + rpl_global_gtid_slave_state->domain_to_gtid(slave_gtid->domain_id, + &master_replication_gtid) && + slave_gtid->server_id == master_replication_gtid.server_id && + slave_gtid->seq_no == master_replication_gtid.seq_no; + + if (mysql_bin_log.find_in_binlog_state(slave_gtid->domain_id, + slave_gtid->server_id, + &master_gtid) && + master_gtid.seq_no >= slave_gtid->seq_no) + { + /* + If connecting slave requests to start at the GTID we last applied when + we were ourselves a slave, then this GTID may not exist in our binlog + (in case of --log-slave-updates=0). So set the flag to disable the + error about missing GTID in the binlog in this case. + */ + if (start_at_own_slave_pos) + slave_gtid_entry->flags|= slave_connection_state::START_OWN_SLAVE_POS; + continue; + } + + if (!start_at_own_slave_pos) + { + rpl_gtid domain_gtid; + slave_connection_state *until_gtid_state= info->until_gtid_state; + rpl_gtid *until_gtid; + + if (!mysql_bin_log.lookup_domain_in_binlog_state(slave_gtid->domain_id, + &domain_gtid)) + { + /* + We do not have anything in this domain, neither in the binlog nor + in the slave state. So we are probably one master in a multi-master + setup, and this domain is served by a different master. + + But set a flag so that if we then ever _do_ happen to encounter + anything in this domain, then we will re-check that the requested + slave position exists, and give the error at that time if not. + */ + slave_gtid_entry->flags|= slave_connection_state::START_ON_EMPTY_DOMAIN; + continue; + } + + if (info->slave_gtid_ignore_duplicates && + domain_gtid.seq_no < slave_gtid->seq_no) + { + /* + When --gtid-ignore-duplicates, it is ok for the slave to request + something that we do not have (yet) - they might already have gotten + it through another path in a multi-path replication hierarchy. + */ + continue; + } + + if (until_gtid_state && + ( !(until_gtid= until_gtid_state->find(slave_gtid->domain_id)) || + (mysql_bin_log.find_in_binlog_state(until_gtid->domain_id, + until_gtid->server_id, + &master_gtid) && + master_gtid.seq_no >= until_gtid->seq_no))) + { + /* + The slave requested to start from a position that is not (yet) in + our binlog, but it also specified an UNTIL condition that _is_ in + our binlog (or a missing UNTIL, which means stop at the very + beginning). So the stop position is before the start position, and + we just delete the entry from the UNTIL hash to mark that this + domain has already reached the UNTIL condition. + */ + if(until_gtid) + until_gtid_state->remove(until_gtid); + continue; + } + + *error_gtid= *slave_gtid; + give_error_start_pos_missing_in_binlog(&err, errormsg, error_gtid); + goto end; + } + + /* + Ok, so connecting slave asked to start at a GTID that we do not have in + our binlog, but it was in fact the last GTID we applied earlier, when we + were acting as a replication slave. + + So this means that we were running as a replication slave without + --log-slave-updates, but now we switched to be a master. It is worth it + to handle this special case, as it allows users to run a simple + master -> slave without --log-slave-updates, and then exchange slave and + master, as long as they make sure the slave is caught up before switching. + */ + + /* + First check if we logged something ourselves as a master after being a + slave. This will be seen as a GTID with our own server_id and bigger + seq_no than what is in the slave state. + + If we did not log anything ourselves, then start the connecting slave + replicating from the current binlog end position, which in this case + corresponds to our replication slave state and hence what the connecting + slave is requesting. + */ + if (mysql_bin_log.find_in_binlog_state(slave_gtid->domain_id, + global_system_variables.server_id, + &start_gtid) && + start_gtid.seq_no > slave_gtid->seq_no) + { + /* + Start replication within this domain at the first GTID that we logged + ourselves after becoming a master. + + Remember that this starting point is in fact a "fake" GTID which may + not exists in the binlog, so that we do not complain about it in + --gtid-strict-mode. + */ + slave_gtid->server_id= global_system_variables.server_id; + slave_gtid_entry->flags|= slave_connection_state::START_OWN_SLAVE_POS; + } + else if (mysql_bin_log.lookup_domain_in_binlog_state(slave_gtid->domain_id, + &start_gtid)) + { + slave_gtid->server_id= start_gtid.server_id; + slave_gtid->seq_no= start_gtid.seq_no; + } + else + { + /* + We do not have _anything_ in our own binlog for this domain. Just + delete the entry in the slave connection state, then it will pick up + anything new that arrives. + + We just queue up the deletion and do it later, after the loop, so that + we do not mess up the iteration over the hash. + */ + if (!delete_list) + { + if (!(delete_list= (slave_connection_state::entry **) + my_malloc(sizeof(*delete_list) * st->hash.records, MYF(MY_WME)))) + { + *errormsg= "Out of memory while checking slave start position"; + err= ER_OUT_OF_RESOURCES; + goto end; + } + } + delete_list[delete_idx++]= slave_gtid_entry; + } + } + + /* Do any delayed deletes from the hash. */ + if (delete_list) + { + for (i= 0; i < delete_idx; ++i) + st->remove(&(delete_list[i]->gtid)); + } + err= 0; + +end: + if (delete_list) + my_free(delete_list); + return err; +} + +/* + Find the name of the binlog file to start reading for a slave that connects + using GTID state. + + Returns the file name in out_name, which must be of size at least FN_REFLEN. + + Returns NULL on ok, error message on error. + + In case of non-error return, the returned binlog file is guaranteed to + contain the first event to be transmitted to the slave for every domain + present in our binlogs. It is still necessary to skip all GTIDs up to + and including the GTID requested by slave within each domain. + + However, as a special case, if the event to be sent to the slave is the very + first event (within that domain) in the returned binlog, then nothing should + be skipped, so that domain is deleted from the passed in slave connection + state. + + This is necessary in case the slave requests a GTID within a replication + domain that has long been inactive. The binlog file containing that GTID may + have been long since purged. However, as long as no GTIDs after that have + been purged, we have the GTID requested by slave in the Gtid_list_log_event + of the latest binlog. So we can start from there, as long as we delete the + corresponding entry in the slave state so we do not wrongly skip any events + that might turn up if that domain becomes active again, vainly looking for + the requested GTID that was already purged. +*/ +static const char * +gtid_find_binlog_file(slave_connection_state *state, char *out_name, + slave_connection_state *until_gtid_state) +{ + MEM_ROOT memroot; + binlog_file_entry *list; + Gtid_list_log_event *glev= NULL; + const char *errormsg= NULL; + char buf[FN_REFLEN]; + + init_alloc_root(&memroot, 10*(FN_REFLEN+sizeof(binlog_file_entry)), 0, + MYF(MY_THREAD_SPECIFIC)); + if (!(list= get_binlog_list(&memroot))) + { + errormsg= "Out of memory while looking for GTID position in binlog"; + goto end; + } + + while (list) + { + File file; + IO_CACHE cache; + + if (!list->next) + { + /* + It should be safe to read the currently used binlog, as we will only + read the header part that is already written. + + But if that does not work on windows, then we will need to cache the + event somewhere in memory I suppose - that could work too. + */ + } + /* + Read the Gtid_list_log_event at the start of the binlog file to + get the binlog state. + */ + if (normalize_binlog_name(buf, list->name, false)) + { + errormsg= "Failed to determine binlog file name while looking for " + "GTID position in binlog"; + goto end; + } + bzero((char*) &cache, sizeof(cache)); + if ((file= open_binlog(&cache, buf, &errormsg)) == (File)-1) + goto end; + errormsg= get_gtid_list_event(&cache, &glev); + end_io_cache(&cache); + mysql_file_close(file, MYF(MY_WME)); + if (errormsg) + goto end; + + if (!glev || contains_all_slave_gtid(state, glev)) + { + strmake(out_name, buf, FN_REFLEN); + + if (glev) + { + uint32 i; + + /* + As a special case, we allow to start from binlog file N if the + requested GTID is the last event (in the corresponding domain) in + binlog file (N-1), but then we need to remove that GTID from the slave + state, rather than skipping events waiting for it to turn up. + + If slave is doing START SLAVE UNTIL, check for any UNTIL conditions + that are already included in a previous binlog file. Delete any such + from the UNTIL hash, to mark that such domains have already reached + their UNTIL condition. + */ + for (i= 0; i < glev->count; ++i) + { + const rpl_gtid *gtid= state->find(glev->list[i].domain_id); + if (!gtid) + { + /* + Contains_all_slave_gtid() returns false if there is any domain in + Gtid_list_event which is not in the requested slave position. + + We may delete a domain from the slave state inside this loop, but + we only do this when it is the very last GTID logged for that + domain in earlier binlogs, and then we can not encounter it in any + further GTIDs in the Gtid_list. + */ + DBUG_ASSERT(0); + } else if (gtid->server_id == glev->list[i].server_id && + gtid->seq_no == glev->list[i].seq_no) + { + /* + The slave requested to start from the very beginning of this + domain in this binlog file. So delete the entry from the state, + we do not need to skip anything. + */ + state->remove(gtid); + } + + if (until_gtid_state && + (gtid= until_gtid_state->find(glev->list[i].domain_id)) && + gtid->server_id == glev->list[i].server_id && + gtid->seq_no <= glev->list[i].seq_no) + { + /* + We've already reached the stop position in UNTIL for this domain, + since it is before the start position. + */ + until_gtid_state->remove(gtid); + } + } + } + + goto end; + } + delete glev; + glev= NULL; + list= list->next; + } + + /* We reached the end without finding anything. */ + errormsg= "Could not find GTID state requested by slave in any binlog " + "files. Probably the slave state is too old and required binlog files " + "have been purged."; + +end: + if (glev) + delete glev; + + free_root(&memroot, MYF(0)); + return errormsg; +} + + +/* + Given an old-style binlog position with file name and file offset, find the + corresponding gtid position. If the offset is not at an event boundary, give + an error. + + Return NULL on ok, error message string on error. + + ToDo: Improve the performance of this by using binlog index files. +*/ +static const char * +gtid_state_from_pos(const char *name, uint32 offset, + slave_connection_state *gtid_state) +{ + IO_CACHE cache; + File file; + const char *errormsg= NULL; + bool found_gtid_list_event= false; + bool found_format_description_event= false; + bool valid_pos= false; + enum enum_binlog_checksum_alg current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; + int err; + String packet; + Format_description_log_event *fdev= NULL; + + if (gtid_state->load((const rpl_gtid *)NULL, 0)) + { + errormsg= "Internal error (out of memory?) initializing slave state " + "while scanning binlog to find start position"; + return errormsg; + } + + if ((file= open_binlog(&cache, name, &errormsg)) == (File)-1) + return errormsg; + + if (!(fdev= new Format_description_log_event(3))) + { + errormsg= "Out of memory initializing format_description event " + "while scanning binlog to find start position"; + goto end; + } + + /* + First we need to find the initial GTID_LIST_EVENT. We need this even + if the offset is at the very start of the binlog file. + + But if we do not find any GTID_LIST_EVENT, then this is an old binlog + with no GTID information, so we return empty GTID state. + */ + for (;;) + { + Log_event_type typ; + uint32 cur_pos; + + cur_pos= (uint32)my_b_tell(&cache); + if (cur_pos == offset) + valid_pos= true; + if (found_format_description_event && found_gtid_list_event && + cur_pos >= offset) + break; + + packet.length(0); + err= Log_event::read_log_event(&cache, &packet, fdev, + opt_master_verify_checksum ? current_checksum_alg + : BINLOG_CHECKSUM_ALG_OFF); + if (err) + { + errormsg= "Could not read binlog while searching for slave start " + "position on master"; + goto end; + } + /* + The cast to uchar is needed to avoid a signed char being converted to a + negative number. + */ + typ= (Log_event_type)(uchar)packet[EVENT_TYPE_OFFSET]; + if (typ == FORMAT_DESCRIPTION_EVENT) + { + Format_description_log_event *tmp; + + if (found_format_description_event) + { + errormsg= "Duplicate format description log event found while " + "searching for old-style position in binlog"; + goto end; + } + + current_checksum_alg= get_checksum_alg(packet.ptr(), packet.length()); + found_format_description_event= true; + if (!(tmp= new Format_description_log_event(packet.ptr(), packet.length(), + fdev))) + { + errormsg= "Corrupt Format_description event found or out-of-memory " + "while searching for old-style position in binlog"; + goto end; + } + delete fdev; + fdev= tmp; + } + else if (typ == START_ENCRYPTION_EVENT) + { + uint sele_len = packet.length(); + if (current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32) + { + sele_len -= BINLOG_CHECKSUM_LEN; + } + Start_encryption_log_event sele(packet.ptr(), sele_len, fdev); + if (fdev->start_decryption(&sele)) + { + errormsg= "Could not start decryption of binlog."; + goto end; + } + } + else if (typ != FORMAT_DESCRIPTION_EVENT && !found_format_description_event) + { + errormsg= "Did not find format description log event while searching " + "for old-style position in binlog"; + goto end; + } + else if (typ == ROTATE_EVENT || typ == STOP_EVENT || + typ == BINLOG_CHECKPOINT_EVENT) + continue; /* Continue looking */ + else if (typ == GTID_LIST_EVENT) + { + rpl_gtid *gtid_list; + bool status; + uint32 list_len; + + if (found_gtid_list_event) + { + errormsg= "Found duplicate Gtid_list_log_event while scanning binlog " + "to find slave start position"; + goto end; + } + status= Gtid_list_log_event::peek(packet.ptr(), packet.length(), + current_checksum_alg, + >id_list, &list_len, fdev); + if (status) + { + errormsg= "Error reading Gtid_list_log_event while searching " + "for old-style position in binlog"; + goto end; + } + err= gtid_state->load(gtid_list, list_len); + my_free(gtid_list); + if (err) + { + errormsg= "Internal error (out of memory?) initialising slave state " + "while scanning binlog to find start position"; + goto end; + } + found_gtid_list_event= true; + } + else if (!found_gtid_list_event) + { + /* We did not find any Gtid_list_log_event, must be old binlog. */ + goto end; + } + else if (typ == GTID_EVENT) + { + rpl_gtid gtid; + uchar flags2; + if (Gtid_log_event::peek(packet.ptr(), packet.length(), + current_checksum_alg, >id.domain_id, + >id.server_id, >id.seq_no, &flags2, fdev)) + { + errormsg= "Corrupt gtid_log_event found while scanning binlog to find " + "initial slave position"; + goto end; + } + if (gtid_state->update(>id)) + { + errormsg= "Internal error (out of memory?) updating slave state while " + "scanning binlog to find start position"; + goto end; + } + } + } + + if (!valid_pos) + { + errormsg= "Slave requested incorrect position in master binlog. " + "Requested position %u in file '%s', but this position does not " + "correspond to the location of any binlog event."; + } + +end: + delete fdev; + end_io_cache(&cache); + mysql_file_close(file, MYF(MY_WME)); + + return errormsg; +} + + +int +gtid_state_from_binlog_pos(const char *in_name, uint32 pos, String *out_str) +{ + slave_connection_state gtid_state; + const char *lookup_name; + char name_buf[FN_REFLEN]; + LOG_INFO linfo; + + if (!mysql_bin_log.is_open()) + { + my_error(ER_NO_BINARY_LOGGING, MYF(0)); + return 1; + } + + if (in_name && in_name[0]) + { + mysql_bin_log.make_log_name(name_buf, in_name); + lookup_name= name_buf; + } + else + lookup_name= NULL; + linfo.index_file_offset= 0; + if (mysql_bin_log.find_log_pos(&linfo, lookup_name, 1)) + return 1; + + if (pos < 4) + pos= 4; + + if (gtid_state_from_pos(linfo.log_file_name, pos, >id_state) || + gtid_state.to_string(out_str)) + return 1; + return 0; +} + + +static bool +is_until_reached(binlog_send_info *info, ulong *ev_offset, + Log_event_type event_type, const char **errmsg, + uint32 current_pos) +{ + switch (info->gtid_until_group) + { + case GTID_UNTIL_NOT_DONE: + return false; + case GTID_UNTIL_STOP_AFTER_STANDALONE: + if (Log_event::is_part_of_group(event_type)) + return false; + break; + case GTID_UNTIL_STOP_AFTER_TRANSACTION: + if (event_type != XID_EVENT && + (event_type != QUERY_EVENT || + !Query_log_event::peek_is_commit_rollback + (info->packet->ptr()+*ev_offset, + info->packet->length()-*ev_offset, + info->current_checksum_alg))) + return false; + break; + } + + /* + The last event group has been sent, now the START SLAVE UNTIL condition + has been reached. + + Send a last fake Gtid_list_log_event with a flag set to mark that we + stop due to UNTIL condition. + */ + if (reset_transmit_packet(info, info->flags, ev_offset, errmsg)) + return true; + Gtid_list_log_event glev(&info->until_binlog_state, + Gtid_list_log_event::FLAG_UNTIL_REACHED); + if (fake_gtid_list_event(info, &glev, errmsg, current_pos)) + return true; + *errmsg= NULL; + return true; +} + + /* Helper function for mysql_binlog_send() to write an event down the slave connection. @@ -561,671 +1596,1344 @@ static int send_heartbeat_event(NET* net, String* packet, Returns NULL on success, error message string on error. */ static const char * -send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, - Log_event_type event_type, char *log_file_name, - IO_CACHE *log) +send_event_to_slave(binlog_send_info *info, Log_event_type event_type, + IO_CACHE *log, ulong ev_offset, rpl_gtid *error_gtid) { my_off_t pos; + String* const packet= info->packet; + size_t len= packet->length(); + int mariadb_slave_capability= info->mariadb_slave_capability; + enum enum_binlog_checksum_alg current_checksum_alg= info->current_checksum_alg; + slave_connection_state *gtid_state= &info->gtid_state; + slave_connection_state *until_gtid_state= info->until_gtid_state; + + if (event_type == GTID_LIST_EVENT && + info->using_gtid_state && until_gtid_state) + { + rpl_gtid *gtid_list; + uint32 list_len; + bool err; + + if (ev_offset > len || + Gtid_list_log_event::peek(packet->ptr()+ev_offset, len - ev_offset, + current_checksum_alg, + >id_list, &list_len, info->fdev)) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return "Failed to read Gtid_list_log_event: corrupt binlog"; + } + err= info->until_binlog_state.load(gtid_list, list_len); + my_free(gtid_list); + if (err) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return "Failed in internal GTID book-keeping: Out of memory"; + } + } + + /* Skip GTID event groups until we reach slave position within a domain_id. */ + if (event_type == GTID_EVENT && info->using_gtid_state) + { + uchar flags2; + slave_connection_state::entry *gtid_entry; + rpl_gtid *gtid; + + if (gtid_state->count() > 0 || until_gtid_state) + { + rpl_gtid event_gtid; + + if (ev_offset > len || + Gtid_log_event::peek(packet->ptr()+ev_offset, len - ev_offset, + current_checksum_alg, + &event_gtid.domain_id, &event_gtid.server_id, + &event_gtid.seq_no, &flags2, info->fdev)) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return "Failed to read Gtid_log_event: corrupt binlog"; + } + + DBUG_EXECUTE_IF("gtid_force_reconnect_at_10_1_100", + { + rpl_gtid *dbug_gtid; + if ((dbug_gtid= info->until_binlog_state.find_nolock(10,1)) && + dbug_gtid->seq_no == 100) + { + DBUG_SET("-d,gtid_force_reconnect_at_10_1_100"); + DBUG_SET_INITIAL("-d,gtid_force_reconnect_at_10_1_100"); + info->error= ER_UNKNOWN_ERROR; + return "DBUG-injected forced reconnect"; + } + }); + + if (info->until_binlog_state.update_nolock(&event_gtid, false)) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return "Failed in internal GTID book-keeping: Out of memory"; + } + + if (gtid_state->count() > 0) + { + gtid_entry= gtid_state->find_entry(event_gtid.domain_id); + if (gtid_entry != NULL) + { + gtid= >id_entry->gtid; + if (gtid_entry->flags & slave_connection_state::START_ON_EMPTY_DOMAIN) + { + rpl_gtid master_gtid; + if (!mysql_bin_log.find_in_binlog_state(gtid->domain_id, + gtid->server_id, + &master_gtid) || + master_gtid.seq_no < gtid->seq_no) + { + int err; + const char *errormsg; + *error_gtid= *gtid; + give_error_start_pos_missing_in_binlog(&err, &errormsg, error_gtid); + info->error= err; + return errormsg; + } + gtid_entry->flags&= ~(uint32)slave_connection_state::START_ON_EMPTY_DOMAIN; + } + + /* Skip this event group if we have not yet reached slave start pos. */ + if (event_gtid.server_id != gtid->server_id || + event_gtid.seq_no <= gtid->seq_no) + info->gtid_skip_group= (flags2 & Gtid_log_event::FL_STANDALONE ? + GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION); + if (event_gtid.server_id == gtid->server_id && + event_gtid.seq_no >= gtid->seq_no) + { + if (info->slave_gtid_strict_mode && + event_gtid.seq_no > gtid->seq_no && + !(gtid_entry->flags & slave_connection_state::START_OWN_SLAVE_POS)) + { + /* + In strict mode, it is an error if the slave requests to start + in a "hole" in the master's binlog: a GTID that does not + exist, even though both the prior and subsequent seq_no exists + for same domain_id and server_id. + */ + info->error= ER_GTID_START_FROM_BINLOG_HOLE; + *error_gtid= *gtid; + return "The binlog on the master is missing the GTID requested " + "by the slave (even though both a prior and a subsequent " + "sequence number does exist), and GTID strict mode is enabled."; + } + + /* + Send a fake Gtid_list event to the slave. + This allows the slave to update its current binlog position + so MASTER_POS_WAIT() and MASTER_GTID_WAIT() can work. + The fake event will be sent at the end of this event group. + */ + info->send_fake_gtid_list= true; + + /* + Delete this entry if we have reached slave start position (so we + will not skip subsequent events and won't have to look them up + and check). + */ + gtid_state->remove(gtid); + } + } + } + + if (until_gtid_state) + { + gtid= until_gtid_state->find(event_gtid.domain_id); + if (gtid == NULL) + { + /* + This domain already reached the START SLAVE UNTIL stop condition, + so skip this event group. + */ + info->gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ? + GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION); + } + else if (event_gtid.server_id == gtid->server_id && + event_gtid.seq_no >= gtid->seq_no) + { + /* + We have reached the stop condition. + Delete this domain_id from the hash, so we will skip all further + events in this domain and eventually stop when all domains are + done. + */ + uint64 until_seq_no= gtid->seq_no; + until_gtid_state->remove(gtid); + if (until_gtid_state->count() == 0) + info->gtid_until_group= (flags2 & Gtid_log_event::FL_STANDALONE ? + GTID_UNTIL_STOP_AFTER_STANDALONE : + GTID_UNTIL_STOP_AFTER_TRANSACTION); + if (event_gtid.seq_no > until_seq_no) + { + /* + The GTID in START SLAVE UNTIL condition is missing in our binlog. + This should normally not happen (user error), but since we can be + sure that we are now beyond the position that the UNTIL condition + should be in, we can just stop now. And we also need to skip this + event group (as it is beyond the UNTIL condition). + */ + info->gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ? + GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION); + } + } + } + } + } + + /* + Skip event group if we have not yet reached the correct slave GTID position. + + Note that slave that understands GTID can also tolerate holes, so there is + no need to supply dummy event. + */ + switch (info->gtid_skip_group) + { + case GTID_SKIP_STANDALONE: + if (!Log_event::is_part_of_group(event_type)) + info->gtid_skip_group= GTID_SKIP_NOT; + return NULL; + case GTID_SKIP_TRANSACTION: + if (event_type == XID_EVENT || + (event_type == QUERY_EVENT && + Query_log_event::peek_is_commit_rollback(packet->ptr() + ev_offset, + len - ev_offset, + current_checksum_alg))) + info->gtid_skip_group= GTID_SKIP_NOT; + return NULL; + case GTID_SKIP_NOT: + break; + } /* Do not send annotate_rows events unless slave requested it. */ if (event_type == ANNOTATE_ROWS_EVENT && - !(flags & BINLOG_SEND_ANNOTATE_ROWS_EVENT)) - return NULL; + !(info->flags & BINLOG_SEND_ANNOTATE_ROWS_EVENT)) + { + if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES) + { + /* This slave can tolerate events omitted from the binlog stream. */ + return NULL; + } + else if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_ANNOTATE) + { + /* + The slave did not request ANNOTATE_ROWS_EVENT (it does not need them as + it will not log them in its own binary log). However, it understands the + event and will just ignore it, and it would break if we omitted it, + leaving a hole in the binlog stream. So just send the event as-is. + */ + } + else + { + /* + The slave does not understand ANNOTATE_ROWS_EVENT. + + Older MariaDB slaves (and MySQL slaves) will break replication if there + are holes in the binlog stream (they will miscompute the binlog offset + and request the wrong position when reconnecting). + + So replace the event with a dummy event of the same size that will be + a no-operation on the slave. + */ + if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg)) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return "Failed to replace row annotate event with dummy: too small event."; + } + } + } + + /* + Replace GTID events with old-style BEGIN events for slaves that do not + understand global transaction IDs. For stand-alone events, where there is + no terminating COMMIT query event, omit the GTID event or replace it with + a dummy event, as appropriate. + */ + if (event_type == GTID_EVENT && + mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_GTID) + { + bool need_dummy= + mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES; + bool err= Gtid_log_event::make_compatible_event(packet, &need_dummy, + ev_offset, + current_checksum_alg); + if (err) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return "Failed to replace GTID event with backwards-compatible event: " + "currupt event."; + } + if (!need_dummy) + return NULL; + } + + /* + Do not send binlog checkpoint or gtid list events to a slave that does not + understand it. + */ + if ((unlikely(event_type == BINLOG_CHECKPOINT_EVENT) && + mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT) || + (unlikely(event_type == GTID_LIST_EVENT) && + mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_GTID)) + { + if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES) + { + /* This slave can tolerate events omitted from the binlog stream. */ + return NULL; + } + else + { + /* + The slave does not understand BINLOG_CHECKPOINT_EVENT. Send a dummy + event instead, with same length so slave does not get confused about + binlog positions. + */ + if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg)) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return "Failed to replace binlog checkpoint or gtid list event with " + "dummy: too small event."; + } + } + } /* Skip events with the @@skip_replication flag set, if slave requested skipping of such events. */ - if (thd->variables.option_bits & OPTION_SKIP_REPLICATION) + if (info->thd->variables.option_bits & OPTION_SKIP_REPLICATION) { - /* - The first byte of the packet is a '\0' to distinguish it from an error - packet. So the actual event starts at offset +1. - */ - uint16 event_flags= uint2korr(&((*packet)[FLAGS_OFFSET+1])); + uint16 event_flags= uint2korr(&((*packet)[FLAGS_OFFSET + ev_offset])); + if (event_flags & LOG_EVENT_SKIP_REPLICATION_F) return NULL; } - thd_proc_info(thd, "Sending binlog event to slave"); + THD_STAGE_INFO(info->thd, stage_sending_binlog_event_to_slave); pos= my_b_tell(log); if (RUN_HOOK(binlog_transmit, before_send_event, - (thd, flags, packet, log_file_name, pos))) + (info->thd, info->flags, packet, info->log_file_name, pos))) + { + info->error= ER_UNKNOWN_ERROR; return "run 'before_send_event' hook failed"; + } - if (my_net_write(net, (uchar*) packet->ptr(), packet->length())) + if (my_net_write(info->net, (uchar*) packet->ptr(), len)) + { + info->error= ER_UNKNOWN_ERROR; return "Failed on my_net_write()"; + } DBUG_PRINT("info", ("log event code %d", (*packet)[LOG_EVENT_OFFSET+1] )); if (event_type == LOAD_EVENT) { - if (send_file(thd)) + if (send_file(info->thd)) + { + info->error= ER_UNKNOWN_ERROR; return "failed in send_file()"; + } } - if (RUN_HOOK(binlog_transmit, after_send_event, (thd, flags, packet))) + if (RUN_HOOK(binlog_transmit, after_send_event, + (info->thd, info->flags, packet))) + { + info->error= ER_UNKNOWN_ERROR; return "Failed to run hook 'after_send_event'"; + } return NULL; /* Success */ } -void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, - ushort flags) +static int check_start_offset(binlog_send_info *info, + const char *log_file_name, + my_off_t pos) { - LOG_INFO linfo; - char *log_file_name = linfo.log_file_name; - char search_file_name[FN_REFLEN], *name; - - ulong ev_offset; - IO_CACHE log; - File file = -1; - String* const packet = &thd->packet; - int error; - const char *errmsg = "Unknown error", *tmp_msg; - char error_text[MAX_SLAVE_ERRMSG]; // to be send to slave via my_message() - NET* net = &thd->net; - mysql_mutex_t *log_lock; - mysql_cond_t *log_cond; + File file= -1; - uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; - int old_max_allowed_packet= thd->variables.max_allowed_packet; - -#ifndef DBUG_OFF - int left_events = max_binlog_dump_events; -#endif - DBUG_ENTER("mysql_binlog_send"); - DBUG_PRINT("enter",("log_ident: '%s' pos: %ld", log_ident, (long) pos)); - - bzero((char*) &log,sizeof(log)); - /* - heartbeat_period from @master_heartbeat_period user variable - */ - ulonglong heartbeat_period= get_heartbeat_period(thd); - struct timespec heartbeat_buf; - struct timespec *heartbeat_ts= NULL; - const LOG_POS_COORD start_coord= { log_ident, pos }, - *p_start_coord= &start_coord; - LOG_POS_COORD coord_buf= { log_file_name, BIN_LOG_HEADER_SIZE }, - *p_coord= &coord_buf; - if (heartbeat_period != LL(0)) - { - heartbeat_ts= &heartbeat_buf; - set_timespec_nsec(*heartbeat_ts, 0); + /** check that requested position is inside of file */ + if ((file=open_binlog(&log, log_file_name, &info->errmsg)) < 0) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return 1; } - if (global_system_variables.log_warnings > 1) - sql_print_information("Start binlog_dump to slave_server(%u), pos(%s, %lu)", - thd->server_id, log_ident, (ulong)pos); - if (RUN_HOOK(binlog_transmit, transmit_start, (thd, flags, log_ident, pos))) + + if (pos < BIN_LOG_HEADER_SIZE || pos > my_b_filelength(&log)) { - errmsg= "Failed to run hook 'transmit_start'"; - my_errno= ER_UNKNOWN_ERROR; + const char* msg= "Client requested master to start replication from " + "impossible position"; + + info->errmsg= NULL; // don't do further modifications of error_text + snprintf(info->error_text, sizeof(info->error_text), + "%s; the first event '%s' at %lld, " + "the last event read from '%s' at %d, " + "the last byte read from '%s' at %d.", + msg, + my_basename(info->start_log_file_name), pos, + my_basename(info->start_log_file_name), BIN_LOG_HEADER_SIZE, + my_basename(info->start_log_file_name), BIN_LOG_HEADER_SIZE); + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; goto err; } +err: + end_io_cache(&log); + mysql_file_close(file, MYF(MY_WME)); + return info->error; +} + +static int init_binlog_sender(binlog_send_info *info, + LOG_INFO *linfo, + const char *log_ident, + my_off_t *pos) +{ + THD *thd= info->thd; + int error; + char str_buf[128]; + String connect_gtid_state(str_buf, sizeof(str_buf), system_charset_info); + char str_buf2[128]; + String slave_until_gtid_str(str_buf2, sizeof(str_buf2), system_charset_info); + connect_gtid_state.length(0); + + /** save start file/pos that was requested by slave */ + strmake(info->start_log_file_name, log_ident, + sizeof(info->start_log_file_name)); + info->start_pos= *pos; + + /** init last pos */ + info->last_pos= *pos; + + info->current_checksum_alg= get_binlog_checksum_value_at_connect(thd); + info->mariadb_slave_capability= get_mariadb_slave_capability(thd); + info->using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state); + DBUG_EXECUTE_IF("simulate_non_gtid_aware_master", + info->using_gtid_state= false;); + + if (info->using_gtid_state) + { + info->slave_gtid_strict_mode= get_slave_gtid_strict_mode(thd); + info->slave_gtid_ignore_duplicates= get_slave_gtid_ignore_duplicates(thd); + if (get_slave_until_gtid(thd, &slave_until_gtid_str)) + info->until_gtid_state= &info->until_gtid_state_obj; + } + + DBUG_EXECUTE_IF("binlog_force_reconnect_after_22_events", + { + DBUG_SET("-d,binlog_force_reconnect_after_22_events"); + DBUG_SET_INITIAL("-d,binlog_force_reconnect_after_22_events"); + info->dbug_reconnect_counter= 22; + }); + + if (global_system_variables.log_warnings > 1) + sql_print_information( + "Start binlog_dump to slave_server(%lu), pos(%s, %lu)", + thd->variables.server_id, log_ident, (ulong)*pos); + #ifndef DBUG_OFF if (opt_sporadic_binlog_dump_fail && (binlog_dump_count++ % 2)) { - errmsg = "Master failed COM_BINLOG_DUMP to test if slave can recover"; - my_errno= ER_UNKNOWN_ERROR; - goto err; + info->errmsg= "Master failed COM_BINLOG_DUMP to test if slave can recover"; + info->error= ER_UNKNOWN_ERROR; + return 1; } #endif if (!mysql_bin_log.is_open()) { - errmsg = "Binary log is not open"; - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - goto err; + info->errmsg= "Binary log is not open"; + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return 1; } if (!server_id_supplied) { - errmsg = "Misconfigured master - server id was not set"; - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - goto err; + info->errmsg= "Misconfigured master - server id was not set"; + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return 1; } - name=search_file_name; - if (log_ident[0]) - mysql_bin_log.make_log_name(search_file_name, log_ident); - else - name=0; // Find first log + char search_file_name[FN_REFLEN]; + const char *name=search_file_name; + if (info->using_gtid_state) + { + if (info->gtid_state.load(connect_gtid_state.c_ptr_quick(), + connect_gtid_state.length())) + { + info->errmsg= "Out of memory or malformed slave request when obtaining " + "start position from GTID state"; + info->error= ER_UNKNOWN_ERROR; + return 1; + } + if (info->until_gtid_state && + info->until_gtid_state->load(slave_until_gtid_str.c_ptr_quick(), + slave_until_gtid_str.length())) + { + info->errmsg= "Out of memory or malformed slave request when " + "obtaining UNTIL position sent from slave"; + info->error= ER_UNKNOWN_ERROR; + return 1; + } + if ((error= check_slave_start_position(info, &info->errmsg, + &info->error_gtid))) + { + info->error= error; + return 1; + } + if ((info->errmsg= gtid_find_binlog_file(&info->gtid_state, + search_file_name, + info->until_gtid_state))) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return 1; + } - linfo.index_file_offset = 0; + /* start from beginning of binlog file */ + *pos = 4; + } + else + { + if (log_ident[0]) + mysql_bin_log.make_log_name(search_file_name, log_ident); + else + name=0; // Find first log + } + linfo->index_file_offset= 0; - if (mysql_bin_log.find_log_pos(&linfo, name, 1)) + if (mysql_bin_log.find_log_pos(linfo, name, 1)) { - errmsg = "Could not find first log file name in binary log index file"; - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - goto err; + info->errmsg= "Could not find first log file name in binary " + "log index file"; + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + return 1; } + // set current pos too + linfo->pos= *pos; + + // note: publish that we use file, before we open it mysql_mutex_lock(&LOCK_thread_count); - thd->current_linfo = &linfo; + thd->current_linfo= linfo; mysql_mutex_unlock(&LOCK_thread_count); - if ((file=open_binlog(&log, log_file_name, &errmsg)) < 0) + if (check_start_offset(info, linfo->log_file_name, *pos)) + return 1; + + if (*pos > BIN_LOG_HEADER_SIZE) { - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - goto err; + /* + mark that first format descriptor with "log_pos=0", so the slave + should not increment master's binlog position + (rli->group_master_log_pos) + */ + info->clear_initial_log_pos= true; } - if (pos < BIN_LOG_HEADER_SIZE || pos > my_b_filelength(&log)) + + return 0; +} + +/** + * send format descriptor event for one binlog file + */ +static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, + LOG_INFO *linfo, my_off_t start_pos) +{ + int error; + ulong ev_offset; + THD *thd= info->thd; + String *packet= info->packet; + Log_event_type event_type; + DBUG_ENTER("send_format_descriptor_event"); + + /** + * 1) reset fdev before each log-file + * 2) read first event, should be the format descriptor + * 3) read second event, *might* be start encryption event + * if it's isn't, seek back to undo this read + */ + if (info->fdev != NULL) + delete info->fdev; + + if (!(info->fdev= new Format_description_log_event(3))) { - errmsg= "Client requested master to start replication from \ -impossible position"; - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - goto err; + info->errmsg= "Out of memory initializing format_description event"; + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + DBUG_RETURN(1); } - /* reset transmit packet for the fake rotate event below */ - if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) - goto err; + /* reset transmit packet for the event read from binary log file */ + if (reset_transmit_packet(info, info->flags, &ev_offset, &info->errmsg)) + DBUG_RETURN(1); /* - Tell the client about the log name with a fake Rotate event; - this is needed even if we also send a Format_description_log_event - just after, because that event does not contain the binlog's name. - Note that as this Rotate event is sent before - Format_description_log_event, the slave cannot have any info to - understand this event's format, so the header len of - Rotate_log_event is FROZEN (so in 5.0 it will have a header shorter - than other events except FORMAT_DESCRIPTION_EVENT). - Before 4.0.14 we called fake_rotate_event below only if (pos == - BIN_LOG_HEADER_SIZE), because if this is false then the slave - already knows the binlog's name. - Since, we always call fake_rotate_event; if the slave already knew - the log's name (ex: CHANGE MASTER TO MASTER_LOG_FILE=...) this is - useless but does not harm much. It is nice for 3.23 (>=.58) slaves - which test Rotate events to see if the master is 4.0 (then they - choose to stop because they can't replicate 4.0); by always calling - fake_rotate_event we are sure that 3.23.58 and newer will detect the - problem as soon as replication starts (BUG#198). - Always calling fake_rotate_event makes sending of normal - (=from-binlog) Rotate events a priori unneeded, but it is not so - simple: the 2 Rotate events are not equivalent, the normal one is - before the Stop event, the fake one is after. If we don't send the - normal one, then the Stop event will be interpreted (by existing 4.0 - slaves) as "the master stopped", which is wrong. So for safety, - given that we want minimum modification of 4.0, we send the normal - and fake Rotates. + Try to find a Format_description_log_event at the beginning of + the binlog */ - if (fake_rotate_event(net, packet, log_file_name, pos, &errmsg, - get_binlog_checksum_value_at_connect(thd))) + info->last_pos= my_b_tell(log); + error= Log_event::read_log_event(log, packet, info->fdev, + opt_master_verify_checksum + ? info->current_checksum_alg + : BINLOG_CHECKSUM_ALG_OFF); + linfo->pos= my_b_tell(log); + + if (error) { - /* - This error code is not perfect, as fake_rotate_event() does not - read anything from the binlog; if it fails it's because of an - error in my_net_write(), fortunately it will say so in errmsg. - */ - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - goto err; + set_read_error(info, error); + DBUG_RETURN(1); } + event_type= (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET+ev_offset]); + /* - Adding MAX_LOG_EVENT_HEADER_LEN, since a binlog event can become - this larger than the corresponding packet (query) sent - from client to master. + The packet has offsets equal to the normal offsets in a + binlog event + ev_offset (the first ev_offset characters are + the header (default \0)). */ - thd->variables.max_allowed_packet= MAX_MAX_ALLOWED_PACKET; + DBUG_PRINT("info", + ("Looked for a Format_description_log_event, " + "found event type %d", (int)event_type)); + + if (event_type != FORMAT_DESCRIPTION_EVENT) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + info->errmsg= "Failed to find format descriptor event in start of binlog"; + sql_print_warning("Failed to find format descriptor event in " + "start of binlog: %s", + info->log_file_name); + DBUG_RETURN(1); + } + + info->current_checksum_alg= get_checksum_alg(packet->ptr() + ev_offset, + packet->length() - ev_offset); + + DBUG_ASSERT(info->current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || + info->current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || + info->current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32); + + if (!is_slave_checksum_aware(thd) && + info->current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && + info->current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + info->errmsg= "Slave can not handle replication events with the " + "checksum that master is configured to log"; + sql_print_warning("Master is configured to log replication events " + "with checksum, but will not send such events to " + "slaves that cannot process them"); + DBUG_RETURN(1); + } + + uint ev_len= packet->length() - ev_offset; + if (info->current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) + ev_len-= BINLOG_CHECKSUM_LEN; + + Format_description_log_event *tmp; + if (!(tmp= new Format_description_log_event(packet->ptr() + ev_offset, + ev_len, info->fdev))) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + info->errmsg= "Corrupt Format_description event found " + "or out-of-memory"; + DBUG_RETURN(1); + } + delete info->fdev; + info->fdev= tmp; + + (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; + + if (info->clear_initial_log_pos) + { + info->clear_initial_log_pos= false; + /* + mark that this event with "log_pos=0", so the slave + should not increment master's binlog position + (rli->group_master_log_pos) + */ + int4store((char*) packet->ptr()+LOG_POS_OFFSET+ev_offset, (ulong) 0); + + /* + if reconnect master sends FD event with `created' as 0 + to avoid destroying temp tables. + */ + int4store((char*) packet->ptr()+LOG_EVENT_MINIMAL_HEADER_LEN+ + ST_CREATED_OFFSET+ev_offset, (ulong) 0); + + /* fix the checksum due to latest changes in header */ + if (info->current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && + info->current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) + fix_checksum(packet, ev_offset); + } + else if (info->using_gtid_state) + { + /* + If this event has the field `created' set, then it will cause the + slave to delete all active temporary tables. This must not happen + if the slave received any later GTIDs in a previous connect, as + those GTIDs might have created new temporary tables that are still + needed. + + So here, we check if the starting GTID position was already + reached before this format description event. If not, we clear the + `created' flag to preserve temporary tables on the slave. (If the + slave connects at a position past this event, it means that it + already received and handled it in a previous connect). + */ + if (!info->gtid_state.is_pos_reached()) + { + int4store((char*) packet->ptr()+LOG_EVENT_MINIMAL_HEADER_LEN+ + ST_CREATED_OFFSET+ev_offset, (ulong) 0); + if (info->current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && + info->current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) + fix_checksum(packet, ev_offset); + } + } + + /* send it */ + if (my_net_write(info->net, (uchar*) packet->ptr(), packet->length())) + { + info->errmsg= "Failed on my_net_write()"; + info->error= ER_UNKNOWN_ERROR; + DBUG_RETURN(1); + } /* - We can set log_lock now, it does not move (it's a member of - mysql_bin_log, and it's already inited, and it will be destroyed - only at shutdown). + Read the following Start_encryption_log_event but don't send it to slave. + Slave doesn't need to know whether master's binlog is encrypted, + and if it'll want to encrypt its logs, it should generate its own + random nonce, not use the one from the master. */ - p_coord->pos= pos; // the first hb matches the slave's last seen value - log_lock= mysql_bin_log.get_log_lock(); - log_cond= mysql_bin_log.get_log_cond(); - if (pos > BIN_LOG_HEADER_SIZE) + packet->length(0); + info->last_pos= linfo->pos; + error= Log_event::read_log_event(log, packet, info->fdev, + opt_master_verify_checksum + ? info->current_checksum_alg + : BINLOG_CHECKSUM_ALG_OFF); + linfo->pos= my_b_tell(log); + + if (error) { - /* reset transmit packet for the event read from binary log - file */ - if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) - goto err; + set_read_error(info, error); + DBUG_RETURN(1); + } - /* - Try to find a Format_description_log_event at the beginning of - the binlog - */ - if (!(error = Log_event::read_log_event(&log, packet, log_lock, 0))) - { - /* - The packet has offsets equal to the normal offsets in a - binlog event + ev_offset (the first ev_offset characters are - the header (default \0)). - */ - DBUG_PRINT("info", - ("Looked for a Format_description_log_event, found event type %d", - (*packet)[EVENT_TYPE_OFFSET+ev_offset])); - if ((*packet)[EVENT_TYPE_OFFSET+ev_offset] == FORMAT_DESCRIPTION_EVENT) - { - current_checksum_alg= get_checksum_alg(packet->ptr() + ev_offset, - packet->length() - ev_offset); - DBUG_ASSERT(current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || - current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || - current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32); - if (!is_slave_checksum_aware(thd) && - current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && - current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) - { - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - errmsg= "Slave can not handle replication events with the checksum " - "that master is configured to log"; - sql_print_warning("Master is configured to log replication events " - "with checksum, but will not send such events to " - "slaves that cannot process them"); - goto err; - } - (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; - /* - mark that this event with "log_pos=0", so the slave - should not increment master's binlog position - (rli->group_master_log_pos) + event_type= (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET]); + if (event_type == START_ENCRYPTION_EVENT) + { + Start_encryption_log_event *sele= (Start_encryption_log_event *) + Log_event::read_log_event(packet->ptr(), packet->length(), &info->errmsg, + info->fdev, BINLOG_CHECKSUM_ALG_OFF); + if (!sele) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + DBUG_RETURN(1); + } + + if (info->fdev->start_decryption(sele)) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + info->errmsg= "Could not decrypt binlog: encryption key error"; + delete sele; + DBUG_RETURN(1); + } + delete sele; + } + else if (start_pos == BIN_LOG_HEADER_SIZE) + { + /* + not Start_encryption_log_event - seek back. But only if + send_one_binlog_file() isn't going to seek anyway + */ + my_b_seek(log, info->last_pos); + linfo->pos= info->last_pos; + } + + + /** all done */ + DBUG_RETURN(0); +} + +static bool should_stop(binlog_send_info *info) +{ + return + info->net->error || + info->net->vio == NULL || + info->thd->killed || + info->error != 0 || + info->should_stop; +} + +/** + * wait for new events to enter binlog + * this function will send heartbeats while waiting if so configured + */ +static int wait_new_events(binlog_send_info *info, /* in */ + LOG_INFO* linfo, /* in */ + char binlog_end_pos_filename[], /* out */ + my_off_t *end_pos_ptr) /* out */ +{ + int ret= 1; + PSI_stage_info old_stage; + + mysql_bin_log.lock_binlog_end_pos(); + info->thd->ENTER_COND(mysql_bin_log.get_log_cond(), + mysql_bin_log.get_binlog_end_pos_lock(), + &stage_master_has_sent_all_binlog_to_slave, + &old_stage); + + while (!should_stop(info)) + { + *end_pos_ptr= mysql_bin_log.get_binlog_end_pos(binlog_end_pos_filename); + if (strcmp(linfo->log_file_name, binlog_end_pos_filename) != 0) + { + /* there has been a log file switch, we don't need to wait */ + ret= 0; + break; + } + + if (linfo->pos < *end_pos_ptr) + { + /* there is data to read, we don't need to wait */ + ret= 0; + break; + } + + if (info->heartbeat_period) + { + struct timespec ts; + set_timespec_nsec(ts, info->heartbeat_period); + ret= mysql_bin_log.wait_for_update_binlog_end_pos(info->thd, &ts); + if (ret == ETIMEDOUT || ret == ETIME) + { + struct event_coordinates coord = { linfo->log_file_name, linfo->pos }; +#ifndef DBUG_OFF + const ulong hb_info_counter_limit = 3; + if (info->hb_info_counter < hb_info_counter_limit) + { + sql_print_information("master sends heartbeat message %s:%llu", + linfo->log_file_name, linfo->pos); + info->hb_info_counter++; + if (info->hb_info_counter == hb_info_counter_limit) + sql_print_information("the rest of heartbeat info skipped ..."); + } +#endif + mysql_bin_log.unlock_binlog_end_pos(); + ret= send_heartbeat_event(info, + info->net, info->packet, &coord, + info->current_checksum_alg); + mysql_bin_log.lock_binlog_end_pos(); + + if (ret) + { + ret= 1; // error + break; + } + /** + * re-read heartbeat period after each sent */ - int4store((char*) packet->ptr()+LOG_POS_OFFSET+ev_offset, 0); - /* - if reconnect master sends FD event with `created' as 0 - to avoid destroying temp tables. - */ - int4store((char*) packet->ptr()+LOG_EVENT_MINIMAL_HEADER_LEN+ - ST_CREATED_OFFSET+ev_offset, (ulong) 0); - - /* fix the checksum due to latest changes in header */ - if (current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && - current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) - fix_checksum(packet, ev_offset); - - /* send it */ - if (my_net_write(net, (uchar*) packet->ptr(), packet->length())) - { - errmsg = "Failed on my_net_write()"; - my_errno= ER_UNKNOWN_ERROR; - goto err; - } - - /* - No need to save this event. We are only doing simple reads - (no real parsing of the events) so we don't need it. And so - we don't need the artificial Format_description_log_event of - 3.23&4.x. + info->heartbeat_period= get_heartbeat_period(info->thd); + } + else if (ret != 0) + { + ret= 1; // error + break; + } + } + else + { + ret= mysql_bin_log.wait_for_update_binlog_end_pos(info->thd, NULL); + if (ret != 0 && ret != ETIMEDOUT && ret != ETIME) + { + ret= 1; // error + break; + } + } + } + + /* it releases the lock set in ENTER_COND */ + info->thd->EXIT_COND(&old_stage); + return ret; +} + +/** + * get end pos of current log file, this function + * will wait if there is nothing available + */ +static my_off_t get_binlog_end_pos(binlog_send_info *info, + IO_CACHE* log, + LOG_INFO* linfo) +{ + my_off_t log_pos= my_b_tell(log); + + /** + * get current binlog end pos + */ + mysql_bin_log.lock_binlog_end_pos(); + char binlog_end_pos_filename[FN_REFLEN]; + my_off_t end_pos= mysql_bin_log.get_binlog_end_pos(binlog_end_pos_filename); + mysql_bin_log.unlock_binlog_end_pos(); + + do + { + if (strcmp(binlog_end_pos_filename, linfo->log_file_name) != 0) + { + /** + * this file is not active, since it's not written to again, + * it safe to check file length and use that as end_pos + */ + end_pos= my_b_filelength(log); + + if (log_pos == end_pos) + return 0; // already at end of file inactive file + else + return end_pos; // return size of inactive file + } + else + { + /** + * this is the active file + */ + + if (log_pos < end_pos) + { + /** + * there is data available to read */ - } - } - else - { - if (test_for_non_eof_log_read_errors(error, &errmsg)) - goto err; - /* - It's EOF, nothing to do, go on reading next events, the - Format_description_log_event will be found naturally if it is written. + return end_pos; + } + + /** + * check if we should wait for more data */ - } - } /* end of if (pos > BIN_LOG_HEADER_SIZE); */ - else - { - /* The Format_description_log_event event will be found naturally. */ - } + if ((info->flags & BINLOG_DUMP_NON_BLOCK) || + (info->thd->variables.server_id == 0)) + { + info->should_stop= true; + return 0; + } - /* seek to the requested position, to start the requested dump */ - my_b_seek(&log, pos); // Seek will done on next read + /** + * flush data before waiting + */ + if (net_flush(info->net)) + { + info->errmsg= "failed on net_flush()"; + info->error= ER_UNKNOWN_ERROR; + return 1; + } + + if (wait_new_events(info, linfo, binlog_end_pos_filename, &end_pos)) + return 1; + } + } while (!should_stop(info)); - while (!net->error && net->vio != 0 && !thd->killed) + return 0; +} + +/** + * This function sends events from one binlog file + * but only up until end_pos + * + * return 0 - OK + * else NOK + */ +static int send_events(binlog_send_info *info, IO_CACHE* log, LOG_INFO* linfo, + my_off_t end_pos) +{ + int error; + ulong ev_offset; + + String *packet= info->packet; + linfo->pos= my_b_tell(log); + info->last_pos= my_b_tell(log); + + log->end_of_file= end_pos; + while (linfo->pos < end_pos) { - Log_event_type event_type= UNKNOWN_EVENT; + if (should_stop(info)) + return 0; /* reset the transmit packet for the event read from binary log file */ - if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) - goto err; + if (reset_transmit_packet(info, info->flags, &ev_offset, &info->errmsg)) + return 1; - bool is_active_binlog= false; - while (!(error= Log_event::read_log_event(&log, packet, log_lock, - current_checksum_alg, - log_file_name, - &is_active_binlog))) + info->last_pos= linfo->pos; + error= Log_event::read_log_event(log, packet, info->fdev, + opt_master_verify_checksum ? info->current_checksum_alg + : BINLOG_CHECKSUM_ALG_OFF); + linfo->pos= my_b_tell(log); + + if (error) { + set_read_error(info, error); + return 1; + } + + Log_event_type event_type= + (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET+ev_offset]); + #ifndef DBUG_OFF - if (max_binlog_dump_events && !left_events--) + if (info->dbug_reconnect_counter > 0) + { + --info->dbug_reconnect_counter; + if (info->dbug_reconnect_counter == 0) { - net_flush(net); - errmsg = "Debugging binlog dump abort"; - my_errno= ER_UNKNOWN_ERROR; - goto err; + info->errmsg= "DBUG-injected forced reconnect"; + info->error= ER_UNKNOWN_ERROR; + return 1; } + } #endif - /* - log's filename does not change while it's active - */ - p_coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET); - event_type= - (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET+ev_offset]); #ifdef ENABLED_DEBUG_SYNC - DBUG_EXECUTE_IF("dump_thread_wait_before_send_xid", + DBUG_EXECUTE_IF("dump_thread_wait_before_send_xid", + { + if (event_type == XID_EVENT) { - if (event_type == XID_EVENT) - { - net_flush(net); - const char act[]= + net_flush(info->net); + const char act[]= "now " "wait_for signal.continue"; - DBUG_ASSERT(debug_sync_service); - DBUG_ASSERT(!debug_sync_set_action(thd, - STRING_WITH_LEN(act))); - const char act2[]= + DBUG_ASSERT(debug_sync_service); + DBUG_ASSERT(!debug_sync_set_action( + info->thd, + STRING_WITH_LEN(act))); + + const char act2[]= "now " "signal signal.continued"; - DBUG_ASSERT(!debug_sync_set_action(current_thd, - STRING_WITH_LEN(act2))); - } - }); + DBUG_ASSERT(!debug_sync_set_action( + info->thd, + STRING_WITH_LEN(act2))); + } + }); #endif - if (event_type == FORMAT_DESCRIPTION_EVENT) - { - current_checksum_alg= get_checksum_alg(packet->ptr() + ev_offset, - packet->length() - ev_offset); - DBUG_ASSERT(current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || - current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || - current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32); - if (!is_slave_checksum_aware(thd) && - current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && - current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) - { - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - errmsg= "Slave can not handle replication events with the checksum " - "that master is configured to log"; - sql_print_warning("Master is configured to log replication events " - "with checksum, but will not send such events to " - "slaves that cannot process them"); - goto err; - } - (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; - } + if (event_type != START_ENCRYPTION_EVENT && + ((info->errmsg= send_event_to_slave(info, event_type, log, + ev_offset, &info->error_gtid)))) + return 1; - if ((tmp_msg= send_event_to_slave(thd, net, packet, flags, event_type, - log_file_name, &log))) + if (unlikely(info->send_fake_gtid_list) && + info->gtid_skip_group == GTID_SKIP_NOT) + { + Gtid_list_log_event glev(&info->until_binlog_state, 0); + + if (reset_transmit_packet(info, info->flags, &ev_offset, &info->errmsg) || + fake_gtid_list_event(info, &glev, &info->errmsg, my_b_tell(log))) { - errmsg= tmp_msg; - my_errno= ER_UNKNOWN_ERROR; - goto err; + info->error= ER_UNKNOWN_ERROR; + return 1; } + info->send_fake_gtid_list= false; + } - DBUG_EXECUTE_IF("dump_thread_wait_before_send_xid", - { - if (event_type == XID_EVENT) - { - net_flush(net); - } - }); - - /* reset transmit packet for next loop */ - if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) - goto err; + if (info->until_gtid_state && + is_until_reached(info, &ev_offset, event_type, &info->errmsg, + my_b_tell(log))) + { + if (info->errmsg) + { + info->error= ER_UNKNOWN_ERROR; + return 1; + } + info->should_stop= true; + return 0; } - DBUG_EXECUTE_IF("wait_after_binlog_EOF", + /* Abort server before it sends the XID_EVENT */ + DBUG_EXECUTE_IF("crash_before_send_xid", { - const char act[]= "now wait_for signal.rotate_finished"; - DBUG_ASSERT(!debug_sync_set_action(current_thd, - STRING_WITH_LEN(act))); - };); + if (event_type == XID_EVENT) + { + my_sleep(2000000); + DBUG_SUICIDE(); + } + }); + } - /* - TODO: now that we are logging the offset, check to make sure - the recorded offset and the actual match. - Guilhem 2003-06: this is not true if this master is a slave - <4.0.15 running with --log-slave-updates, because then log_pos may - be the offset in the-master-of-this-master's binlog. - */ - if (test_for_non_eof_log_read_errors(error, &errmsg)) - goto err; + return 0; +} - /* - We should only move to the next binlog when the last read event - came from a already deactivated binlog. +/** + * This function sends one binlog file to slave + * + * return 0 - OK + * 1 - NOK + */ +static int send_one_binlog_file(binlog_send_info *info, + IO_CACHE* log, + LOG_INFO* linfo, + my_off_t start_pos) +{ + mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock()); + + /* seek to the requested position, to start the requested dump */ + if (start_pos != BIN_LOG_HEADER_SIZE) + { + my_b_seek(log, start_pos); + linfo->pos= start_pos; + } + + while (!should_stop(info)) + { + /** + * get end pos of current log file, this function + * will wait if there is nothing available */ - if (!(flags & BINLOG_DUMP_NON_BLOCK) && is_active_binlog) + my_off_t end_pos= get_binlog_end_pos(info, log, linfo); + if (end_pos <= 1) { - /* - Block until there is more data in the log - */ - if (net_flush(net)) - { - errmsg = "failed on net_flush()"; - my_errno= ER_UNKNOWN_ERROR; - goto err; - } + /** end of file or error */ + return end_pos; + } - /* - We may have missed the update broadcast from the log - that has just happened, let's try to catch it if it did. - If we did not miss anything, we just wait for other threads - to signal us. - */ - { - log.error=0; - bool read_packet = 0; + /** + * send events from current position up to end_pos + */ + if (send_events(info, log, linfo, end_pos)) + return 1; + } -#ifndef DBUG_OFF - if (max_binlog_dump_events && !left_events--) - { - errmsg = "Debugging binlog dump abort"; - my_errno= ER_UNKNOWN_ERROR; - goto err; - } -#endif + return 1; +} - /* reset the transmit packet for the event read from binary log - file */ - if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) - goto err; - - /* - No one will update the log while we are reading - now, but we'll be quick and just read one record - - TODO: - Add an counter that is incremented for each time we update the - binary log. We can avoid the following read if the counter - has not been updated since last read. - */ - - mysql_mutex_lock(log_lock); - switch (error= Log_event::read_log_event(&log, packet, (mysql_mutex_t*) 0, - current_checksum_alg)) { - case 0: - /* we read successfully, so we'll need to send it to the slave */ - mysql_mutex_unlock(log_lock); - read_packet = 1; - p_coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET); - event_type= - (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET+ev_offset]); - break; - - case LOG_READ_EOF: - { - int ret; - ulong signal_cnt; - DBUG_PRINT("wait",("waiting for data in binary log")); - if (thd->server_id==0) // for mysqlbinlog (mysqlbinlog.server_id==0) - { - mysql_mutex_unlock(log_lock); - goto end; - } +void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, + ushort flags) +{ + LOG_INFO linfo; -#ifndef DBUG_OFF - ulong hb_info_counter= 0; -#endif - const char* old_msg= thd->proc_info; - signal_cnt= mysql_bin_log.signal_cnt; - do - { - if (heartbeat_period != 0) - { - DBUG_ASSERT(heartbeat_ts); - set_timespec_nsec(*heartbeat_ts, heartbeat_period); - } - thd->enter_cond(log_cond, log_lock, - "Master has sent all binlog to slave; " - "waiting for binlog to be updated"); - ret= mysql_bin_log.wait_for_update_bin_log(thd, heartbeat_ts); - DBUG_ASSERT(ret == 0 || (heartbeat_period != 0)); - if (ret == ETIMEDOUT || ret == ETIME) - { -#ifndef DBUG_OFF - if (hb_info_counter < 3) - { - sql_print_information("master sends heartbeat message"); - hb_info_counter++; - if (hb_info_counter == 3) - sql_print_information("the rest of heartbeat info skipped ..."); - } -#endif - /* reset transmit packet for the heartbeat event */ - if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) - { - thd->exit_cond(old_msg); - goto err; - } - if (send_heartbeat_event(net, packet, p_coord, current_checksum_alg)) - { - errmsg = "Failed on my_net_write()"; - my_errno= ER_UNKNOWN_ERROR; - thd->exit_cond(old_msg); - goto err; - } - } - else - { - DBUG_PRINT("wait",("binary log received update or a broadcast signal caught")); - } - } while (signal_cnt == mysql_bin_log.signal_cnt && !thd->killed); - thd->exit_cond(old_msg); - } - break; - - default: - mysql_mutex_unlock(log_lock); - test_for_non_eof_log_read_errors(error, &errmsg); - goto err; - } - - if (read_packet && - (tmp_msg= send_event_to_slave(thd, net, packet, flags, event_type, - log_file_name, &log))) - { - errmsg= tmp_msg; - my_errno= ER_UNKNOWN_ERROR; - goto err; - } + IO_CACHE log; + File file = -1; + String* const packet= &thd->packet; - log.error=0; - } - } - else - { - bool loop_breaker = 0; - /* need this to break out of the for loop from switch */ + binlog_send_info infoobj(thd, packet, flags, linfo.log_file_name); + binlog_send_info *info= &infoobj; - thd_proc_info(thd, "Finished reading one binlog; switching to next binlog"); - switch (mysql_bin_log.find_next_log(&linfo, 1)) { - case 0: - break; - case LOG_INFO_EOF: - if (mysql_bin_log.is_active(log_file_name)) - { - loop_breaker = (flags & BINLOG_DUMP_NON_BLOCK); - break; - } - /* fall through */ - default: - errmsg = "could not find next log"; - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - goto err; - } + int old_max_allowed_packet= thd->variables.max_allowed_packet; + thd->variables.max_allowed_packet= MAX_MAX_ALLOWED_PACKET; - if (loop_breaker) - break; + DBUG_ENTER("mysql_binlog_send"); + DBUG_PRINT("enter",("log_ident: '%s' pos: %ld", log_ident, (long) pos)); - end_io_cache(&log); - mysql_file_close(file, MYF(MY_WME)); + bzero((char*) &log,sizeof(log)); - /* reset transmit packet for the possible fake rotate event */ - if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) - goto err; - + if (init_binlog_sender(info, &linfo, log_ident, &pos)) + goto err; + + /* + run hook first when all check has been made that slave seems to + be requesting a reasonable position. i.e when transmit actually starts + */ + if (RUN_HOOK(binlog_transmit, transmit_start, (thd, flags, log_ident, pos))) + { + info->errmsg= "Failed to run hook 'transmit_start'"; + info->error= ER_UNKNOWN_ERROR; + goto err; + } + + /* + heartbeat_period from @master_heartbeat_period user variable + NOTE: this is initialized after transmit_start-hook so that + the hook can affect value of heartbeat period + */ + info->heartbeat_period= get_heartbeat_period(thd); + + while (!should_stop(info)) + { + /* + Tell the client about the log name with a fake Rotate event; + this is needed even if we also send a Format_description_log_event + just after, because that event does not contain the binlog's name. + Note that as this Rotate event is sent before + Format_description_log_event, the slave cannot have any info to + understand this event's format, so the header len of + Rotate_log_event is FROZEN (so in 5.0 it will have a header shorter + than other events except FORMAT_DESCRIPTION_EVENT). + Before 4.0.14 we called fake_rotate_event below only if (pos == + BIN_LOG_HEADER_SIZE), because if this is false then the slave + already knows the binlog's name. + Since, we always call fake_rotate_event; if the slave already knew + the log's name (ex: CHANGE MASTER TO MASTER_LOG_FILE=...) this is + useless but does not harm much. It is nice for 3.23 (>=.58) slaves + which test Rotate events to see if the master is 4.0 (then they + choose to stop because they can't replicate 4.0); by always calling + fake_rotate_event we are sure that 3.23.58 and newer will detect the + problem as soon as replication starts (BUG#198). + Always calling fake_rotate_event makes sending of normal + (=from-binlog) Rotate events a priori unneeded, but it is not so + simple: the 2 Rotate events are not equivalent, the normal one is + before the Stop event, the fake one is after. If we don't send the + normal one, then the Stop event will be interpreted (by existing 4.0 + slaves) as "the master stopped", which is wrong. So for safety, + given that we want minimum modification of 4.0, we send the normal + and fake Rotates. + */ + if (fake_rotate_event(info, pos, &info->errmsg, info->current_checksum_alg)) + { /* - Call fake_rotate_event() in case the previous log (the one which - we have just finished reading) did not contain a Rotate event - (for example (I don't know any other example) the previous log - was the last one before the master was shutdown & restarted). - This way we tell the slave about the new log's name and - position. If the binlog is 5.0, the next event we are going to - read and send is Format_description_log_event. + This error code is not perfect, as fake_rotate_event() does not + read anything from the binlog; if it fails it's because of an + error in my_net_write(), fortunately it will say so in errmsg. */ - if ((file=open_binlog(&log, log_file_name, &errmsg)) < 0 || - fake_rotate_event(net, packet, log_file_name, BIN_LOG_HEADER_SIZE, - &errmsg, current_checksum_alg)) - { - my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; - goto err; - } + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + goto err; + } - p_coord->file_name= log_file_name; // reset to the next + if ((file=open_binlog(&log, linfo.log_file_name, &info->errmsg)) < 0) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + goto err; } - } -end: - end_io_cache(&log); - mysql_file_close(file, MYF(MY_WME)); + if (send_format_descriptor_event(info, &log, &linfo, pos)) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + goto err; + } + + /* + We want to corrupt the first event that will be sent to the slave. + But we do not want the corruption to happen early, eg. when client does + BINLOG_GTID_POS(). So test case sets a DBUG trigger which causes us to + set the real DBUG injection here. + */ + DBUG_EXECUTE_IF("corrupt_read_log_event2_set", + { + DBUG_SET("-d,corrupt_read_log_event2_set"); + DBUG_SET("+d,corrupt_read_log_event2"); + }); + /* + Handle the case of START SLAVE UNTIL with an UNTIL condition already + fulfilled at the start position. + + We will send one event, the format_description, and then stop. + */ + if (info->until_gtid_state && info->until_gtid_state->count() == 0) + info->gtid_until_group= GTID_UNTIL_STOP_AFTER_STANDALONE; + + THD_STAGE_INFO(thd, stage_sending_binlog_event_to_slave); + if (send_one_binlog_file(info, &log, &linfo, pos)) + break; + + if (should_stop(info)) + break; + + DBUG_EXECUTE_IF("wait_after_binlog_EOF", + { + const char act[]= "now wait_for signal.rotate_finished"; + DBUG_ASSERT(!debug_sync_set_action(current_thd, + STRING_WITH_LEN(act))); + };); + + THD_STAGE_INFO(thd, + stage_finished_reading_one_binlog_switching_to_next_binlog); + if (mysql_bin_log.find_next_log(&linfo, 1)) + { + info->errmsg= "could not find next log"; + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + break; + } + + /** start from start of next file */ + pos= BIN_LOG_HEADER_SIZE; + + /** close current cache/file */ + end_io_cache(&log); + mysql_file_close(file, MYF(MY_WME)); + file= -1; + } + +err: + THD_STAGE_INFO(thd, stage_waiting_to_finalize_termination); RUN_HOOK(binlog_transmit, transmit_stop, (thd, flags)); - my_eof(thd); - thd_proc_info(thd, "Waiting to finalize termination"); + + const bool binlog_open = my_b_inited(&log); + if (file >= 0) + { + end_io_cache(&log); + mysql_file_close(file, MYF(MY_WME)); + } + mysql_mutex_lock(&LOCK_thread_count); thd->current_linfo = 0; mysql_mutex_unlock(&LOCK_thread_count); thd->variables.max_allowed_packet= old_max_allowed_packet; - DBUG_VOID_RETURN; + delete info->fdev; -err: - thd_proc_info(thd, "Waiting to finalize termination"); - if (my_errno == ER_MASTER_FATAL_ERROR_READING_BINLOG && my_b_inited(&log)) + if (info->error == ER_MASTER_FATAL_ERROR_READING_BINLOG && binlog_open) { - /* - detailing the fatal error message with coordinates + /* + detailing the fatal error message with coordinates of the last position read. */ - my_snprintf(error_text, sizeof(error_text), + my_snprintf(info->error_text, sizeof(info->error_text), "%s; the first event '%s' at %lld, " "the last event read from '%s' at %lld, " "the last byte read from '%s' at %lld.", - errmsg, - my_basename(p_start_coord->file_name), p_start_coord->pos, - my_basename(p_coord->file_name), p_coord->pos, - my_basename(log_file_name), my_b_tell(&log)); + info->errmsg, + my_basename(info->start_log_file_name), info->start_pos, + my_basename(info->log_file_name), info->last_pos, + my_basename(info->log_file_name), linfo.pos); + } + else if (info->error == ER_GTID_POSITION_NOT_FOUND_IN_BINLOG) + { + my_snprintf(info->error_text, sizeof(info->error_text), + "Error: connecting slave requested to start from GTID " + "%u-%u-%llu, which is not in the master's binlog", + info->error_gtid.domain_id, + info->error_gtid.server_id, + info->error_gtid.seq_no); + /* Use this error code so slave will know not to try reconnect. */ + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + } + else if (info->error == ER_GTID_POSITION_NOT_FOUND_IN_BINLOG2) + { + my_snprintf(info->error_text, sizeof(info->error_text), + "Error: connecting slave requested to start from GTID " + "%u-%u-%llu, which is not in the master's binlog. Since the " + "master's binlog contains GTIDs with higher sequence numbers, " + "it probably means that the slave has diverged due to " + "executing extra erroneous transactions", + info->error_gtid.domain_id, + info->error_gtid.server_id, + info->error_gtid.seq_no); + /* Use this error code so slave will know not to try reconnect. */ + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + } + else if (info->error == ER_GTID_START_FROM_BINLOG_HOLE) + { + my_snprintf(info->error_text, sizeof(info->error_text), + "The binlog on the master is missing the GTID %u-%u-%llu " + "requested by the slave (even though both a prior and a " + "subsequent sequence number does exist), and GTID strict mode " + "is enabled", + info->error_gtid.domain_id, + info->error_gtid.server_id, + info->error_gtid.seq_no); + /* Use this error code so slave will know not to try reconnect. */ + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + } + else if (info->error == ER_CANNOT_LOAD_SLAVE_GTID_STATE) + { + my_snprintf(info->error_text, sizeof(info->error_text), + "Failed to load replication slave GTID state from table %s.%s", + "mysql", rpl_gtid_slave_state_table_name.str); + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + } + else if (info->error != 0 && info->errmsg != NULL) + strcpy(info->error_text, info->errmsg); + + if (info->error == 0) + { + my_eof(thd); } else - strcpy(error_text, errmsg); - end_io_cache(&log); - RUN_HOOK(binlog_transmit, transmit_stop, (thd, flags)); - /* - Exclude iteration through thread list - this is needed for purge_logs() - it will iterate through - thread list and update thd->current_linfo->index_file_offset - this mutex will make sure that it never tried to update our linfo - after we return from this stack frame - */ - mysql_mutex_lock(&LOCK_thread_count); - thd->current_linfo = 0; - mysql_mutex_unlock(&LOCK_thread_count); - if (file >= 0) - mysql_file_close(file, MYF(MY_WME)); - thd->variables.max_allowed_packet= old_max_allowed_packet; + { + my_message(info->error, info->error_text, MYF(0)); + } - my_message(my_errno, error_text, MYF(0)); DBUG_VOID_RETURN; } @@ -1242,18 +2950,61 @@ err: @retval 0 success @retval 1 error + @retval -1 fatal error */ + int start_slave(THD* thd , Master_info* mi, bool net_report) { int slave_errno= 0; int thread_mask; + char master_info_file_tmp[FN_REFLEN]; + char relay_log_info_file_tmp[FN_REFLEN]; DBUG_ENTER("start_slave"); if (check_access(thd, SUPER_ACL, any_db, NULL, NULL, 0, 0)) - DBUG_RETURN(1); - lock_slave_threads(mi); // this allows us to cleanly read slave_running + DBUG_RETURN(-1); + + create_logfile_name_with_suffix(master_info_file_tmp, + sizeof(master_info_file_tmp), + master_info_file, 0, + &mi->cmp_connection_name); + create_logfile_name_with_suffix(relay_log_info_file_tmp, + sizeof(relay_log_info_file_tmp), + relay_log_info_file, 0, + &mi->cmp_connection_name); + + mi->lock_slave_threads(); + if (mi->killed) + { + /* connection was deleted while we waited for lock_slave_threads */ + mi->unlock_slave_threads(); + my_error(WARN_NO_MASTER_INFO, mi->connection_name.length, + mi->connection_name.str); + DBUG_RETURN(-1); + } + // Get a mask of _stopped_ threads init_thread_mask(&thread_mask,mi,1 /* inverse */); + + if (thd->lex->mi.gtid_pos_str.str) + { + if (thread_mask != (SLAVE_IO|SLAVE_SQL)) + { + slave_errno= ER_SLAVE_WAS_RUNNING; + goto err; + } + if (thd->lex->slave_thd_opt) + { + slave_errno= ER_BAD_SLAVE_UNTIL_COND; + goto err; + } + if (mi->using_gtid == Master_info::USE_GTID_NO) + { + slave_errno= ER_UNTIL_REQUIRES_USING_GTID; + goto err; + } + } + /* Below we will start all stopped threads. But if the user wants to start only one thread, do as if the other thread was running (as we @@ -1264,10 +3015,22 @@ int start_slave(THD* thd , Master_info* mi, bool net_report) thread_mask&= thd->lex->slave_thd_opt; if (thread_mask) //some threads are stopped, start them { - if (init_master_info(mi,master_info_file,relay_log_info_file, 0, + if (init_master_info(mi,master_info_file_tmp,relay_log_info_file_tmp, 0, thread_mask)) slave_errno=ER_MASTER_INFO; - else if (server_id_supplied && *mi->host) + else if (!server_id_supplied) + { + slave_errno= ER_BAD_SLAVE; net_report= 0; + my_message(slave_errno, "Misconfigured slave: server_id was not set; Fix in config file", + MYF(0)); + } + else if (!*mi->host) + { + slave_errno= ER_BAD_SLAVE; net_report= 0; + my_message(slave_errno, "Misconfigured slave: MASTER_HOST was not set; Fix in config file or with CHANGE MASTER TO", + MYF(0)); + } + else { /* If we will start SQL thread we will care about UNTIL options If @@ -1292,16 +3055,26 @@ int start_slave(THD* thd , Master_info* mi, bool net_report) } else if (thd->lex->mi.relay_log_pos) { - if (thd->lex->mi.pos) - slave_errno=ER_BAD_SLAVE_UNTIL_COND; mi->rli.until_condition= Relay_log_info::UNTIL_RELAY_POS; mi->rli.until_log_pos= thd->lex->mi.relay_log_pos; strmake_buf(mi->rli.until_log_name, thd->lex->mi.relay_log_name); } + else if (thd->lex->mi.gtid_pos_str.str) + { + if (mi->rli.until_gtid_pos.load(thd->lex->mi.gtid_pos_str.str, + thd->lex->mi.gtid_pos_str.length)) + { + slave_errno= ER_INCORRECT_GTID_STATE; + mysql_mutex_unlock(&mi->rli.data_lock); + goto err; + } + mi->rli.until_condition= Relay_log_info::UNTIL_GTID; + } else mi->rli.clear_until_condition(); - if (mi->rli.until_condition != Relay_log_info::UNTIL_NONE) + if (mi->rli.until_condition == Relay_log_info::UNTIL_MASTER_POS || + mi->rli.until_condition == Relay_log_info::UNTIL_RELAY_POS) { /* Preparing members for effective until condition checking */ const char *p= fn_ext(mi->rli.until_log_name); @@ -1324,47 +3097,53 @@ int start_slave(THD* thd , Master_info* mi, bool net_report) /* mark the cached result of the UNTIL comparison as "undefined" */ mi->rli.until_log_names_cmp_result= Relay_log_info::UNTIL_LOG_NAMES_CMP_UNKNOWN; + } + if (mi->rli.until_condition != Relay_log_info::UNTIL_NONE) + { /* Issuing warning then started without --skip-slave-start */ if (!opt_skip_slave_start) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_MISSING_SKIP_SLAVE, - ER(ER_MISSING_SKIP_SLAVE)); + ER_THD(thd, ER_MISSING_SKIP_SLAVE)); } mysql_mutex_unlock(&mi->rli.data_lock); } else if (thd->lex->mi.pos || thd->lex->mi.relay_log_pos) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_UNTIL_COND_IGNORED, - ER(ER_UNTIL_COND_IGNORED)); + push_warning(thd, + Sql_condition::WARN_LEVEL_NOTE, ER_UNTIL_COND_IGNORED, + ER_THD(thd, ER_UNTIL_COND_IGNORED)); if (!slave_errno) - slave_errno = start_slave_threads(0 /*no mutex */, - 1 /* wait for start */, - mi, - master_info_file,relay_log_info_file, - thread_mask); + slave_errno = start_slave_threads(thd, + 1, + 1 /* wait for start */, + mi, + master_info_file_tmp, + relay_log_info_file_tmp, + thread_mask); } - else - slave_errno = ER_BAD_SLAVE; } else { /* no error if all threads are already started, only a warning */ - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SLAVE_WAS_RUNNING, - ER(ER_SLAVE_WAS_RUNNING)); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SLAVE_WAS_RUNNING, + ER_THD(thd, ER_SLAVE_WAS_RUNNING)); } - unlock_slave_threads(mi); +err: + mi->unlock_slave_threads(); + thd_proc_info(thd, 0); if (slave_errno) { if (net_report) - my_message(slave_errno, ER(slave_errno), MYF(0)); - DBUG_RETURN(1); + my_error(slave_errno, MYF(0), + (int) mi->connection_name.length, + mi->connection_name.str); + DBUG_RETURN(slave_errno == ER_BAD_SLAVE ? -1 : 1); } - else if (net_report) - my_ok(thd); DBUG_RETURN(0); } @@ -1382,21 +3161,25 @@ int start_slave(THD* thd , Master_info* mi, bool net_report) @retval 0 success @retval 1 error + @retval -1 error */ + int stop_slave(THD* thd, Master_info* mi, bool net_report ) { - DBUG_ENTER("stop_slave"); - int slave_errno; - if (!thd) - thd = current_thd; + DBUG_ENTER("stop_slave"); + DBUG_PRINT("enter",("Connection: %s", mi->connection_name.str)); if (check_access(thd, SUPER_ACL, any_db, NULL, NULL, 0, 0)) - DBUG_RETURN(1); - thd_proc_info(thd, "Killing slave"); + DBUG_RETURN(-1); + THD_STAGE_INFO(thd, stage_killing_slave); int thread_mask; - lock_slave_threads(mi); - // Get a mask of _running_ threads + mi->lock_slave_threads(); + /* + Get a mask of _running_ threads. + We don't have to test for mi->killed as the thread_mask will take care + of checking if threads exists + */ init_thread_mask(&thread_mask,mi,0 /* not inverse*/); /* Below we will stop all running threads. @@ -1409,27 +3192,24 @@ int stop_slave(THD* thd, Master_info* mi, bool net_report ) if (thread_mask) { - slave_errno= terminate_slave_threads(mi,thread_mask, - 1 /*skip lock */); + slave_errno= terminate_slave_threads(mi,thread_mask, 0 /* get lock */); } else { //no error if both threads are already stopped, only a warning slave_errno= 0; - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SLAVE_WAS_NOT_RUNNING, - ER(ER_SLAVE_WAS_NOT_RUNNING)); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SLAVE_WAS_NOT_RUNNING, + ER_THD(thd, ER_SLAVE_WAS_NOT_RUNNING)); } - unlock_slave_threads(mi); - thd_proc_info(thd, 0); + + mi->unlock_slave_threads(); if (slave_errno) { if (net_report) - my_message(slave_errno, ER(slave_errno), MYF(0)); + my_message(slave_errno, ER_THD(thd, slave_errno), MYF(0)); DBUG_RETURN(1); } - else if (net_report) - my_ok(thd); DBUG_RETURN(0); } @@ -1452,20 +3232,30 @@ int reset_slave(THD *thd, Master_info* mi) char fname[FN_REFLEN]; int thread_mask= 0, error= 0; uint sql_errno=ER_UNKNOWN_ERROR; - const char* errmsg= "Unknown error occured while reseting slave"; + const char* errmsg= "Unknown error occurred while reseting slave"; + char master_info_file_tmp[FN_REFLEN]; + char relay_log_info_file_tmp[FN_REFLEN]; DBUG_ENTER("reset_slave"); - lock_slave_threads(mi); + mi->lock_slave_threads(); + if (mi->killed) + { + /* connection was deleted while we waited for lock_slave_threads */ + mi->unlock_slave_threads(); + my_error(WARN_NO_MASTER_INFO, mi->connection_name.length, + mi->connection_name.str); + DBUG_RETURN(-1); + } + init_thread_mask(&thread_mask,mi,0 /* not inverse */); if (thread_mask) // We refuse if any slave thread is running { - sql_errno= ER_SLAVE_MUST_STOP; - error=1; - goto err; + mi->unlock_slave_threads(); + my_error(ER_SLAVE_MUST_STOP, MYF(0), (int) mi->connection_name.length, + mi->connection_name.str); + DBUG_RETURN(ER_SLAVE_MUST_STOP); } - ha_reset_slave(thd); - // delete relay logs, clear relay log coordinates if ((error= purge_relay_logs(&mi->rli, thd, 1 /* just reset */, @@ -1485,30 +3275,46 @@ int reset_slave(THD *thd, Master_info* mi) mi->clear_error(); mi->rli.clear_error(); mi->rli.clear_until_condition(); + mi->rli.slave_skip_counter= 0; // close master_info_file, relay_log_info_file, set mi->inited=rli->inited=0 end_master_info(mi); + end_relay_log_info(&mi->rli); // and delete these two files - fn_format(fname, master_info_file, mysql_data_home, "", 4+32); + create_logfile_name_with_suffix(master_info_file_tmp, + sizeof(master_info_file_tmp), + master_info_file, 0, + &mi->cmp_connection_name); + create_logfile_name_with_suffix(relay_log_info_file_tmp, + sizeof(relay_log_info_file_tmp), + relay_log_info_file, 0, + &mi->cmp_connection_name); + + fn_format(fname, master_info_file_tmp, mysql_data_home, "", 4+32); if (mysql_file_stat(key_file_master_info, fname, &stat_area, MYF(0)) && mysql_file_delete(key_file_master_info, fname, MYF(MY_WME))) { error=1; goto err; } + else if (global_system_variables.log_warnings > 1) + sql_print_information("Deleted Master_info file '%s'.", fname); + // delete relay_log_info_file - fn_format(fname, relay_log_info_file, mysql_data_home, "", 4+32); + fn_format(fname, relay_log_info_file_tmp, mysql_data_home, "", 4+32); if (mysql_file_stat(key_file_relay_log_info, fname, &stat_area, MYF(0)) && mysql_file_delete(key_file_relay_log_info, fname, MYF(MY_WME))) { error=1; goto err; } + else if (global_system_variables.log_warnings > 1) + sql_print_information("Deleted Master_info file '%s'.", fname); RUN_HOOK(binlog_relay_io, after_reset_slave, (thd, mi)); err: - unlock_slave_threads(mi); + mi->unlock_slave_threads(); if (error) my_error(sql_errno, MYF(0), errmsg); DBUG_RETURN(error); @@ -1542,8 +3348,8 @@ void kill_zombie_dump_threads(uint32 slave_server_id) while ((tmp=it++)) { - if (tmp->command == COM_BINLOG_DUMP && - tmp->server_id == slave_server_id) + if (tmp->get_command() == COM_BINLOG_DUMP && + tmp->variables.server_id == slave_server_id) { mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete break; @@ -1598,10 +3404,14 @@ static bool get_string_parameter(char *to, const char *from, size_t length, @param mi Pointer to Master_info object belonging to the slave's IO thread. + @param master_info_added Out parameter saying if the Master_info *mi was + added to the global list of masters. This is useful in error conditions + to know if caller should free Master_info *mi. + @retval FALSE success @retval TRUE error */ -bool change_master(THD* thd, Master_info* mi) +bool change_master(THD* thd, Master_info* mi, bool *master_info_added) { int thread_mask; const char* errmsg= 0; @@ -1610,20 +3420,19 @@ bool change_master(THD* thd, Master_info* mi) char saved_host[HOSTNAME_LENGTH + 1]; uint saved_port; char saved_log_name[FN_REFLEN]; + Master_info::enum_using_gtid saved_using_gtid; + char master_info_file_tmp[FN_REFLEN]; + char relay_log_info_file_tmp[FN_REFLEN]; my_off_t saved_log_pos; + LEX_MASTER_INFO* lex_mi= &thd->lex->mi; + DYNAMIC_ARRAY *do_ids, *ignore_ids; + DBUG_ENTER("change_master"); - lock_slave_threads(mi); - init_thread_mask(&thread_mask,mi,0 /*not inverse*/); - LEX_MASTER_INFO* lex_mi= &thd->lex->mi; - if (thread_mask) // We refuse if any slave thread is running - { - my_message(ER_SLAVE_MUST_STOP, ER(ER_SLAVE_MUST_STOP), MYF(0)); - ret= TRUE; - goto err; - } + DBUG_ASSERT(master_info_index); + mysql_mutex_assert_owner(&LOCK_active_mi); - thd_proc_info(thd, "Changing master"); + *master_info_added= false; /* We need to check if there is an empty master_host. Otherwise change master succeeds, a master.info file is created containing @@ -1631,17 +3440,74 @@ bool change_master(THD* thd, Master_info* mi) is thrown stating that the server is not configured as slave. (See BUG#28796). */ - if(lex_mi->host && !*lex_mi->host) + if (lex_mi->host && !*lex_mi->host) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "MASTER_HOST"); - unlock_slave_threads(mi); DBUG_RETURN(TRUE); } - // TODO: see if needs re-write - if (init_master_info(mi, master_info_file, relay_log_info_file, 0, + if (master_info_index->check_duplicate_master_info(&lex_mi->connection_name, + lex_mi->host, + lex_mi->port)) + DBUG_RETURN(TRUE); + + mi->lock_slave_threads(); + if (mi->killed) + { + /* connection was deleted while we waited for lock_slave_threads */ + mi->unlock_slave_threads(); + my_error(WARN_NO_MASTER_INFO, mi->connection_name.length, + mi->connection_name.str); + DBUG_RETURN(TRUE); + } + + init_thread_mask(&thread_mask,mi,0 /*not inverse*/); + if (thread_mask) // We refuse if any slave thread is running + { + my_error(ER_SLAVE_MUST_STOP, MYF(0), (int) mi->connection_name.length, + mi->connection_name.str); + ret= TRUE; + goto err; + } + + THD_STAGE_INFO(thd, stage_changing_master); + + create_logfile_name_with_suffix(master_info_file_tmp, + sizeof(master_info_file_tmp), + master_info_file, 0, + &mi->cmp_connection_name); + create_logfile_name_with_suffix(relay_log_info_file_tmp, + sizeof(relay_log_info_file_tmp), + relay_log_info_file, 0, + &mi->cmp_connection_name); + + /* if new Master_info doesn't exists, add it */ + if (!master_info_index->get_master_info(&mi->connection_name, + Sql_condition::WARN_LEVEL_NOTE)) + { + if (master_info_index->add_master_info(mi, TRUE)) + { + my_error(ER_MASTER_INFO, MYF(0), + (int) lex_mi->connection_name.length, + lex_mi->connection_name.str); + ret= TRUE; + goto err; + } + *master_info_added= true; + } + if (global_system_variables.log_warnings > 1) + sql_print_information("Master connection name: '%.*s' " + "Master_info_file: '%s' " + "Relay_info_file: '%s'", + (int) mi->connection_name.length, + mi->connection_name.str, + master_info_file_tmp, relay_log_info_file_tmp); + + if (init_master_info(mi, master_info_file_tmp, relay_log_info_file_tmp, 0, thread_mask)) { - my_message(ER_MASTER_INFO, ER(ER_MASTER_INFO), MYF(0)); + my_error(ER_MASTER_INFO, MYF(0), + (int) lex_mi->connection_name.length, + lex_mi->connection_name.str); ret= TRUE; goto err; } @@ -1659,6 +3525,7 @@ bool change_master(THD* thd, Master_info* mi) saved_port= mi->port; strmake_buf(saved_log_name, mi->master_log_name); saved_log_pos= mi->master_log_pos; + saved_using_gtid= mi->using_gtid; /* If the user specified host or port without binlog or position, @@ -1698,36 +3565,33 @@ bool change_master(THD* thd, Master_info* mi) if (lex_mi->heartbeat_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED) mi->heartbeat_period = lex_mi->heartbeat_period; else - mi->heartbeat_period= (float) min(SLAVE_MAX_HEARTBEAT_PERIOD, + mi->heartbeat_period= (float) MY_MIN(SLAVE_MAX_HEARTBEAT_PERIOD, (slave_net_timeout/2.0)); - mi->received_heartbeats= LL(0); // counter lives until master is CHANGEd + mi->received_heartbeats= 0; // counter lives until master is CHANGEd + /* - reset the last time server_id list if the current CHANGE MASTER + Reset the last time server_id list if the current CHANGE MASTER is mentioning IGNORE_SERVER_IDS= (...) */ if (lex_mi->repl_ignore_server_ids_opt == LEX_MASTER_INFO::LEX_MI_ENABLE) - reset_dynamic(&mi->ignore_server_ids); - for (uint i= 0; i < lex_mi->repl_ignore_server_ids.elements; i++) { - ulong s_id; - get_dynamic(&lex_mi->repl_ignore_server_ids, (uchar*) &s_id, i); - if (s_id == ::server_id && replicate_same_server_id) + /* Check if the list contains replicate_same_server_id */ + for (uint i= 0; i < lex_mi->repl_ignore_server_ids.elements; i ++) { - my_error(ER_SLAVE_IGNORE_SERVER_IDS, MYF(0), static_cast<int>(s_id)); - ret= TRUE; - goto err; - } - else - { - if (bsearch((const ulong *) &s_id, - mi->ignore_server_ids.buffer, - mi->ignore_server_ids.elements, sizeof(ulong), - (int (*) (const void*, const void*)) - change_master_server_id_cmp) == NULL) - insert_dynamic(&mi->ignore_server_ids, (uchar*) &s_id); + ulong s_id; + get_dynamic(&lex_mi->repl_ignore_server_ids, (uchar*) &s_id, i); + if (s_id == global_system_variables.server_id && replicate_same_server_id) + { + my_error(ER_SLAVE_IGNORE_SERVER_IDS, MYF(0), static_cast<int>(s_id)); + ret= TRUE; + goto err; + } } + + /* All ok. Update the old server ids with the new ones. */ + update_change_master_ids(&lex_mi->repl_ignore_server_ids, + &mi->ignore_server_ids); } - sort_dynamic(&mi->ignore_server_ids, (qsort_cmp) change_master_server_id_cmp); if (lex_mi->ssl != LEX_MASTER_INFO::LEX_MI_UNCHANGED) mi->ssl= (lex_mi->ssl == LEX_MASTER_INFO::LEX_MI_ENABLE); @@ -1746,12 +3610,18 @@ bool change_master(THD* thd, Master_info* mi) strmake_buf(mi->ssl_cipher, lex_mi->ssl_cipher); if (lex_mi->ssl_key) strmake_buf(mi->ssl_key, lex_mi->ssl_key); + if (lex_mi->ssl_crl) + strmake_buf(mi->ssl_crl, lex_mi->ssl_crl); + if (lex_mi->ssl_crlpath) + strmake_buf(mi->ssl_crlpath, lex_mi->ssl_crlpath); + #ifndef HAVE_OPENSSL if (lex_mi->ssl || lex_mi->ssl_ca || lex_mi->ssl_capath || lex_mi->ssl_cert || lex_mi->ssl_cipher || lex_mi->ssl_key || - lex_mi->ssl_verify_server_cert ) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_SLAVE_IGNORED_SSL_PARAMS, ER(ER_SLAVE_IGNORED_SSL_PARAMS)); + lex_mi->ssl_verify_server_cert || lex_mi->ssl_crl || lex_mi->ssl_crlpath) + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SLAVE_IGNORED_SSL_PARAMS, + ER_THD(thd, ER_SLAVE_IGNORED_SSL_PARAMS)); #endif if (lex_mi->relay_log_name) @@ -1769,6 +3639,36 @@ bool change_master(THD* thd, Master_info* mi) mi->rli.group_relay_log_pos= mi->rli.event_relay_log_pos= lex_mi->relay_log_pos; } + if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_SLAVE_POS) + mi->using_gtid= Master_info::USE_GTID_SLAVE_POS; + else if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_CURRENT_POS) + mi->using_gtid= Master_info::USE_GTID_CURRENT_POS; + else if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_NO || + lex_mi->log_file_name || lex_mi->pos || + lex_mi->relay_log_name || lex_mi->relay_log_pos) + mi->using_gtid= Master_info::USE_GTID_NO; + + do_ids= ((lex_mi->repl_do_domain_ids_opt == + LEX_MASTER_INFO::LEX_MI_ENABLE) ? + &lex_mi->repl_do_domain_ids : NULL); + + ignore_ids= ((lex_mi->repl_ignore_domain_ids_opt == + LEX_MASTER_INFO::LEX_MI_ENABLE) ? + &lex_mi->repl_ignore_domain_ids : NULL); + + /* + Note: mi->using_gtid stores the previous state in case no MASTER_USE_GTID + is specified. + */ + if (mi->domain_id_filter.update_ids(do_ids, ignore_ids, mi->using_gtid)) + { + my_error(ER_MASTER_INFO, MYF(0), + (int) lex_mi->connection_name.length, + lex_mi->connection_name.str); + ret= TRUE; + goto err; + } + /* If user did specify neither host nor port nor any log name nor any log pos, i.e. he specified only user/password/master_connect_retry, he probably @@ -1789,15 +3689,16 @@ bool change_master(THD* thd, Master_info* mi) { /* Sometimes mi->rli.master_log_pos == 0 (it happens when the SQL thread is - not initialized), so we use a max(). + not initialized), so we use a MY_MAX(). What happens to mi->rli.master_log_pos during the initialization stages of replication is not 100% clear, so we guard against problems using - max(). + MY_MAX(). */ - mi->master_log_pos = max(BIN_LOG_HEADER_SIZE, + mi->master_log_pos = MY_MAX(BIN_LOG_HEADER_SIZE, mi->rli.group_master_log_pos); strmake_buf(mi->master_log_name, mi->rli.group_master_log_name); } + /* Relay log's IO_CACHE may not be inited, if rli->inited==0 (server was never a slave before). @@ -1810,8 +3711,7 @@ bool change_master(THD* thd, Master_info* mi) } if (need_relay_log_purge) { - relay_log_purge= 1; - thd_proc_info(thd, "Purging old relay logs"); + THD_STAGE_INFO(thd, stage_purging_old_relay_logs); if (purge_relay_logs(&mi->rli, thd, 0 /* not only reset, but also reinit */, &errmsg)) @@ -1824,7 +3724,6 @@ bool change_master(THD* thd, Master_info* mi) else { const char* msg; - relay_log_purge= 0; /* Relay log is already initialized */ if (init_relay_log_pos(&mi->rli, mi->rli.group_relay_log_name, @@ -1859,6 +3758,7 @@ bool change_master(THD* thd, Master_info* mi) /* Clear the errors, for a clean start */ mi->rli.clear_error(); mi->rli.clear_until_condition(); + mi->rli.slave_skip_counter= 0; sql_print_information("'CHANGE MASTER TO executed'. " "Previous state master_host='%s', master_port='%u', master_log_file='%s', " @@ -1867,6 +3767,11 @@ bool change_master(THD* thd, Master_info* mi) "master_log_pos='%ld'.", saved_host, saved_port, saved_log_name, (ulong) saved_log_pos, mi->host, mi->port, mi->master_log_name, (ulong) mi->master_log_pos); + if (saved_using_gtid != Master_info::USE_GTID_NO || + mi->using_gtid != Master_info::USE_GTID_NO) + sql_print_information("Previous Using_Gtid=%s. New Using_Gtid=%s", + mi->using_gtid_astext(saved_using_gtid), + mi->using_gtid_astext(mi->using_gtid)); /* If we don't write new coordinates to disk now, then old will remain in @@ -1875,13 +3780,13 @@ bool change_master(THD* thd, Master_info* mi) in-memory value at restart (thus causing errors, as the old relay log does not exist anymore). */ - flush_relay_log_info(&mi->rli); + if (flush_relay_log_info(&mi->rli)) + ret= 1; mysql_cond_broadcast(&mi->data_cond); mysql_mutex_unlock(&mi->rli.data_lock); err: - unlock_slave_threads(mi); - thd_proc_info(thd, 0); + mi->unlock_slave_threads(); if (ret == FALSE) my_ok(thd); DBUG_RETURN(ret); @@ -1897,16 +3802,19 @@ err: @retval 0 success @retval 1 error */ -int reset_master(THD* thd) +int reset_master(THD* thd, rpl_gtid *init_state, uint32 init_state_len, + ulong next_log_number) { if (!mysql_bin_log.is_open()) { my_message(ER_FLUSH_MASTER_BINLOG_CLOSED, - ER(ER_FLUSH_MASTER_BINLOG_CLOSED), MYF(ME_BELL+ME_WAITTANG)); + ER_THD(thd, ER_FLUSH_MASTER_BINLOG_CLOSED), + MYF(ME_BELL+ME_WAITTANG)); return 1; } - if (mysql_bin_log.reset_logs(thd)) + if (mysql_bin_log.reset_logs(thd, 1, init_state, init_state_len, + next_log_number)) return 1; RUN_HOOK(binlog_transmit, after_reset_master, (thd, 0 /* flags */)); return 0; @@ -1932,52 +3840,57 @@ bool mysql_show_binlog_events(THD* thd) File file = -1; MYSQL_BIN_LOG *binary_log= NULL; int old_max_allowed_packet= thd->variables.max_allowed_packet; + Master_info *mi= 0; LOG_INFO linfo; + LEX_MASTER_INFO *lex_mi= &thd->lex->mi; DBUG_ENTER("mysql_show_binlog_events"); - Log_event::init_show_field_list(&field_list); + Log_event::init_show_field_list(thd, &field_list); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); - Format_description_log_event *description_event= new - Format_description_log_event(3); /* MySQL 4.0 by default */ - DBUG_ASSERT(thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS || thd->lex->sql_command == SQLCOM_SHOW_RELAYLOG_EVENTS); - /* select wich binary log to use: binlog or relay */ + /* select which binary log to use: binlog or relay */ if ( thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS ) { - /* - Wait for handlers to insert any pending information - into the binlog. For e.g. ndb which updates the binlog asynchronously - this is needed so that the uses sees all its own commands in the binlog - */ - ha_binlog_wait(thd); - binary_log= &mysql_bin_log; } else /* showing relay log contents */ { - if (!active_mi) + if (!lex_mi->connection_name.str) + lex_mi->connection_name= thd->variables.default_master_connection; + if (!(mi= get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_ERROR))) + { DBUG_RETURN(TRUE); - - binary_log= &(active_mi->rli.relay_log); + } + binary_log= &(mi->rli.relay_log); } + Format_description_log_event *description_event= new + Format_description_log_event(3); /* MySQL 4.0 by default */ + if (binary_log->is_open()) { - LEX_MASTER_INFO *lex_mi= &thd->lex->mi; SELECT_LEX_UNIT *unit= &thd->lex->unit; ha_rows event_count, limit_start, limit_end; - my_off_t pos = max(BIN_LOG_HEADER_SIZE, lex_mi->pos); // user-friendly + my_off_t pos = MY_MAX(BIN_LOG_HEADER_SIZE, lex_mi->pos); // user-friendly char search_file_name[FN_REFLEN], *name; const char *log_file_name = lex_mi->log_file_name; mysql_mutex_t *log_lock = binary_log->get_log_lock(); Log_event* ev; + if (mi) + { + /* We can unlock the mutex as we have a lock on the file */ + mi->release(); + mi= 0; + } + unit->set_limit(thd->lex->current_select); limit_start= unit->offset_limit_cnt; limit_end= unit->select_limit_cnt; @@ -1996,6 +3909,7 @@ bool mysql_show_binlog_events(THD* thd) goto err; } + /* These locks is here to enable syncronization with log_in_use() */ mysql_mutex_lock(&LOCK_thread_count); thd->current_linfo = &linfo; mysql_mutex_unlock(&LOCK_thread_count); @@ -2015,40 +3929,54 @@ bool mysql_show_binlog_events(THD* thd) Read the first event in case it's a Format_description_log_event, to know the format. If there's no such event, we are 3.23 or 4.x. This code, like before, can't read 3.23 binlogs. + Also read the second event, in case it's a Start_encryption_log_event. This code will fail on a mixed relay log (one which has Format_desc then Rotate then Format_desc). */ - ev= Log_event::read_log_event(&log, (mysql_mutex_t*)0, description_event, - opt_master_verify_checksum); - if (ev) + + my_off_t scan_pos = BIN_LOG_HEADER_SIZE; + while (scan_pos < pos) { + ev= Log_event::read_log_event(&log, (mysql_mutex_t*)0, description_event, + opt_master_verify_checksum); + scan_pos = my_b_tell(&log); + if (ev == NULL || !ev->is_valid()) + { + mysql_mutex_unlock(log_lock); + errmsg = "Wrong offset or I/O error"; + goto err; + } if (ev->get_type_code() == FORMAT_DESCRIPTION_EVENT) { delete description_event; description_event= (Format_description_log_event*) ev; } else + { + if (ev->get_type_code() == START_ENCRYPTION_EVENT) + { + if (description_event->start_decryption((Start_encryption_log_event*) ev)) + { + delete ev; + mysql_mutex_unlock(log_lock); + errmsg = "Could not initialize decryption of binlog."; + goto err; + } + } delete ev; + break; + } } my_b_seek(&log, pos); - if (!description_event->is_valid()) - { - errmsg="Invalid Format_description event; could be out of memory"; - goto err; - } - for (event_count = 0; (ev = Log_event::read_log_event(&log, (mysql_mutex_t*) 0, description_event, opt_master_verify_checksum)); ) { - if (ev->get_type_code() == FORMAT_DESCRIPTION_EVENT) - description_event->checksum_alg= ev->checksum_alg; - if (event_count >= limit_start && - ev->net_send(thd, protocol, linfo.log_file_name, pos)) + ev->net_send(protocol, linfo.log_file_name, pos)) { errmsg = "Net error"; delete ev; @@ -2056,8 +3984,30 @@ bool mysql_show_binlog_events(THD* thd) goto err; } + if (ev->get_type_code() == FORMAT_DESCRIPTION_EVENT) + { + Format_description_log_event* new_fdle= + (Format_description_log_event*) ev; + new_fdle->copy_crypto_data(description_event); + delete description_event; + description_event= new_fdle; + } + else + { + if (ev->get_type_code() == START_ENCRYPTION_EVENT) + { + if (description_event->start_decryption((Start_encryption_log_event*) ev)) + { + errmsg = "Error starting decryption"; + delete ev; + mysql_mutex_unlock(log_lock); + goto err; + } + } + delete ev; + } + pos = my_b_tell(&log); - delete ev; if (++event_count >= limit_end) break; @@ -2072,6 +4022,9 @@ bool mysql_show_binlog_events(THD* thd) mysql_mutex_unlock(log_lock); } + else if (mi) + mi->release(); + // Check that linfo is still on the function scope. DEBUG_SYNC(thd, "after_show_binlog_events"); @@ -2091,14 +4044,34 @@ err: else my_eof(thd); + /* These locks is here to enable syncronization with log_in_use() */ mysql_mutex_lock(&LOCK_thread_count); - thd->current_linfo = 0; + thd->current_linfo= 0; mysql_mutex_unlock(&LOCK_thread_count); thd->variables.max_allowed_packet= old_max_allowed_packet; DBUG_RETURN(ret); } +void show_binlog_info_get_fields(THD *thd, List<Item> *field_list) +{ + MEM_ROOT *mem_root= thd->mem_root; + field_list->push_back(new (mem_root) + Item_empty_string(thd, "File", FN_REFLEN), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "Position", 20, + MYSQL_TYPE_LONGLONG), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Binlog_Do_DB", 255), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Binlog_Ignore_DB", 255), + mem_root); +} + + /** Execute a SHOW MASTER STATUS statement. @@ -2112,12 +4085,9 @@ bool show_binlog_info(THD* thd) { Protocol *protocol= thd->protocol; DBUG_ENTER("show_binlog_info"); + List<Item> field_list; - field_list.push_back(new Item_empty_string("File", FN_REFLEN)); - field_list.push_back(new Item_return_int("Position",20, - MYSQL_TYPE_LONGLONG)); - field_list.push_back(new Item_empty_string("Binlog_Do_DB",255)); - field_list.push_back(new Item_empty_string("Binlog_Ignore_DB",255)); + show_binlog_info_get_fields(thd, &field_list); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) @@ -2141,6 +4111,19 @@ bool show_binlog_info(THD* thd) } +void show_binlogs_get_fields(THD *thd, List<Item> *field_list) +{ + MEM_ROOT *mem_root= thd->mem_root; + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Log_name", 255), + mem_root); + field_list->push_back(new (mem_root) + Item_return_int(thd, "File_size", 20, + MYSQL_TYPE_LONGLONG), + mem_root); +} + + /** Execute a SHOW BINARY LOGS statement. @@ -2168,9 +4151,8 @@ bool show_binlogs(THD* thd) DBUG_RETURN(TRUE); } - field_list.push_back(new Item_empty_string("Log_name", 255)); - field_list.push_back(new Item_return_int("File_size", 20, - MYSQL_TYPE_LONGLONG)); + show_binlogs_get_fields(thd, &field_list); + if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); @@ -2236,30 +4218,30 @@ err: @retval 0 success @retval 1 failure */ -int log_loaded_block(IO_CACHE* file) +int log_loaded_block(IO_CACHE* file, uchar *Buffer, size_t Count) { DBUG_ENTER("log_loaded_block"); - LOAD_FILE_INFO *lf_info; + LOAD_FILE_IO_CACHE *lf_info= static_cast<LOAD_FILE_IO_CACHE*>(file); uint block_len; /* buffer contains position where we started last read */ uchar* buffer= (uchar*) my_b_get_buffer_start(file); - uint max_event_size= current_thd->variables.max_allowed_packet; - lf_info= (LOAD_FILE_INFO*) file->arg; + uint max_event_size= lf_info->thd->variables.max_allowed_packet; + if (lf_info->thd->is_current_stmt_binlog_format_row()) - DBUG_RETURN(0); + goto ret; if (lf_info->last_pos_in_file != HA_POS_ERROR && lf_info->last_pos_in_file >= my_b_get_pos_in_file(file)) - DBUG_RETURN(0); + goto ret; for (block_len= (uint) (my_b_get_bytes_in_buffer(file)); block_len > 0; - buffer += min(block_len, max_event_size), - block_len -= min(block_len, max_event_size)) + buffer += MY_MIN(block_len, max_event_size), + block_len -= MY_MIN(block_len, max_event_size)) { lf_info->last_pos_in_file= my_b_get_pos_in_file(file); if (lf_info->wrote_create_file) { Append_block_log_event a(lf_info->thd, lf_info->thd->db, buffer, - min(block_len, max_event_size), + MY_MIN(block_len, max_event_size), lf_info->log_delayed); if (mysql_bin_log.write(&a)) DBUG_RETURN(1); @@ -2268,14 +4250,216 @@ int log_loaded_block(IO_CACHE* file) { Begin_load_query_log_event b(lf_info->thd, lf_info->thd->db, buffer, - min(block_len, max_event_size), + MY_MIN(block_len, max_event_size), lf_info->log_delayed); if (mysql_bin_log.write(&b)) DBUG_RETURN(1); lf_info->wrote_create_file= 1; } } - DBUG_RETURN(0); +ret: + int res= Buffer ? lf_info->real_read_function(file, Buffer, Count) : 0; + DBUG_RETURN(res); +} + + +/** + Initialise the slave replication state from the mysql.gtid_slave_pos table. + + This is called each time an SQL thread starts, but the data is only actually + loaded on the first call. + + The slave state is the last GTID applied on the slave within each + replication domain. + + To avoid row lock contention, there are multiple rows for each domain_id. + The one containing the current slave state is the one with the maximal + sub_id value, within each domain_id. + + CREATE TABLE mysql.gtid_slave_pos ( + domain_id INT UNSIGNED NOT NULL, + sub_id BIGINT UNSIGNED NOT NULL, + server_id INT UNSIGNED NOT NULL, + seq_no BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (domain_id, sub_id)) +*/ + +void +rpl_init_gtid_slave_state() +{ + rpl_global_gtid_slave_state= new rpl_slave_state; +} + + +void +rpl_deinit_gtid_slave_state() +{ + delete rpl_global_gtid_slave_state; +} + + +void +rpl_init_gtid_waiting() +{ + rpl_global_gtid_waiting.init(); +} + + +void +rpl_deinit_gtid_waiting() +{ + rpl_global_gtid_waiting.destroy(); +} + + +/* + Format the current GTID state as a string, for returning the value of + @@global.gtid_slave_pos. + + If the flag use_binlog is true, then the contents of the binary log (if + enabled) is merged into the current GTID state (@@global.gtid_current_pos). +*/ +int +rpl_append_gtid_state(String *dest, bool use_binlog) +{ + int err; + rpl_gtid *gtid_list= NULL; + uint32 num_gtids= 0; + + if (use_binlog && opt_bin_log && + (err= mysql_bin_log.get_most_recent_gtid_list(>id_list, &num_gtids))) + return err; + + err= rpl_global_gtid_slave_state->tostring(dest, gtid_list, num_gtids); + my_free(gtid_list); + + return err; +} + + +/* + Load the current GTID position into a slave_connection_state, for use when + connecting to a master server with GTID. + + If the flag use_binlog is true, then the contents of the binary log (if + enabled) is merged into the current GTID state (master_use_gtid=current_pos). +*/ +int +rpl_load_gtid_state(slave_connection_state *state, bool use_binlog) +{ + int err; + rpl_gtid *gtid_list= NULL; + uint32 num_gtids= 0; + + if (use_binlog && opt_bin_log && + (err= mysql_bin_log.get_most_recent_gtid_list(>id_list, &num_gtids))) + return err; + + err= state->load(rpl_global_gtid_slave_state, gtid_list, num_gtids); + my_free(gtid_list); + + return err; +} + + +bool +rpl_gtid_pos_check(THD *thd, char *str, size_t len) +{ + slave_connection_state tmp_slave_state; + bool gave_conflict_warning= false, gave_missing_warning= false; + + /* Check that we can parse the supplied string. */ + if (tmp_slave_state.load(str, len)) + return true; + + /* + Check our own binlog for any of our own transactions that are newer + than the GTID state the user is requesting. Any such transactions would + result in an out-of-order binlog, which could break anyone replicating + with us as master. + + So give an error if this is found, requesting the user to do a + RESET MASTER (to clean up the binlog) if they really want this. + */ + if (mysql_bin_log.is_open()) + { + rpl_gtid *binlog_gtid_list= NULL; + uint32 num_binlog_gtids= 0; + uint32 i; + + if (mysql_bin_log.get_most_recent_gtid_list(&binlog_gtid_list, + &num_binlog_gtids)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME)); + return true; + } + for (i= 0; i < num_binlog_gtids; ++i) + { + rpl_gtid *binlog_gtid= &binlog_gtid_list[i]; + rpl_gtid *slave_gtid; + if (binlog_gtid->server_id != global_system_variables.server_id) + continue; + if (!(slave_gtid= tmp_slave_state.find(binlog_gtid->domain_id))) + { + if (opt_gtid_strict_mode) + { + my_error(ER_MASTER_GTID_POS_MISSING_DOMAIN, MYF(0), + binlog_gtid->domain_id, binlog_gtid->domain_id, + binlog_gtid->server_id, binlog_gtid->seq_no); + break; + } + else if (!gave_missing_warning) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_MASTER_GTID_POS_MISSING_DOMAIN, + ER_THD(thd, ER_MASTER_GTID_POS_MISSING_DOMAIN), + binlog_gtid->domain_id, binlog_gtid->domain_id, + binlog_gtid->server_id, binlog_gtid->seq_no); + gave_missing_warning= true; + } + } + else if (slave_gtid->seq_no < binlog_gtid->seq_no) + { + if (opt_gtid_strict_mode) + { + my_error(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, MYF(0), + slave_gtid->domain_id, slave_gtid->server_id, + slave_gtid->seq_no, binlog_gtid->domain_id, + binlog_gtid->server_id, binlog_gtid->seq_no); + break; + } + else if (!gave_conflict_warning) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, + ER_THD(thd, ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG), + slave_gtid->domain_id, slave_gtid->server_id, + slave_gtid->seq_no, binlog_gtid->domain_id, + binlog_gtid->server_id, binlog_gtid->seq_no); + gave_conflict_warning= true; + } + } + } + my_free(binlog_gtid_list); + if (i != num_binlog_gtids) + return true; + } + + return false; } + +bool +rpl_gtid_pos_update(THD *thd, char *str, size_t len) +{ + if (rpl_global_gtid_slave_state->load(thd, str, len, true, true)) + { + my_error(ER_FAILED_GTID_STATE_INIT, MYF(0)); + return true; + } + else + return false; +} + + #endif /* HAVE_REPLICATION */ diff --git a/sql/sql_repl.h b/sql/sql_repl.h index c5a0b31388e..37acff3141f 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -32,6 +32,8 @@ typedef struct st_slave_info THD* thd; } SLAVE_INFO; +struct slave_connection_state; + extern my_bool opt_show_slave_auth_info; extern char *master_host, *master_info_file; extern bool server_id_supplied; @@ -41,30 +43,49 @@ extern my_bool opt_sporadic_binlog_dump_fail; int start_slave(THD* thd, Master_info* mi, bool net_report); int stop_slave(THD* thd, Master_info* mi, bool net_report); -bool change_master(THD* thd, Master_info* mi); +bool change_master(THD* thd, Master_info* mi, bool *master_info_added); bool mysql_show_binlog_events(THD* thd); int reset_slave(THD *thd, Master_info* mi); -int reset_master(THD* thd); +int reset_master(THD* thd, rpl_gtid *init_state, uint32 init_state_len, + ulong next_log_number); bool purge_master_logs(THD* thd, const char* to_log); bool purge_master_logs_before_date(THD* thd, time_t purge_time); bool log_in_use(const char* log_name); void adjust_linfo_offsets(my_off_t purge_offset); +void show_binlogs_get_fields(THD *thd, List<Item> *field_list); bool show_binlogs(THD* thd); extern int init_master_info(Master_info* mi); void kill_zombie_dump_threads(uint32 slave_server_id); int check_binlog_magic(IO_CACHE* log, const char** errmsg); -typedef struct st_load_file_info +struct LOAD_FILE_IO_CACHE : public IO_CACHE { THD* thd; my_off_t last_pos_in_file; bool wrote_create_file, log_delayed; -} LOAD_FILE_INFO; + int (*real_read_function)(struct st_io_cache *,uchar *,size_t); +}; -int log_loaded_block(IO_CACHE* file); +int log_loaded_block(IO_CACHE* file, uchar *Buffer, size_t Count); int init_replication_sys_vars(); void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, ushort flags); +#ifdef HAVE_PSI_INTERFACE +extern PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state; +#endif +void rpl_init_gtid_slave_state(); +void rpl_deinit_gtid_slave_state(); +void rpl_init_gtid_waiting(); +void rpl_deinit_gtid_waiting(); +int gtid_state_from_binlog_pos(const char *name, uint32 pos, String *out_str); +int rpl_append_gtid_state(String *dest, bool use_binlog); +int rpl_load_gtid_state(slave_connection_state *state, bool use_binlog); +bool rpl_gtid_pos_check(THD *thd, char *str, size_t len); +bool rpl_gtid_pos_update(THD *thd, char *str, size_t len); +#else + +struct LOAD_FILE_IO_CACHE : public IO_CACHE { }; + #endif /* HAVE_REPLICATION */ #endif /* SQL_REPL_INCLUDED */ diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 35937f0536f..62365f48404 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -29,6 +29,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_select.h" @@ -51,6 +52,7 @@ #include "opt_subselect.h" #include "log_slow.h" #include "sql_derived.h" +#include "sql_statistics.h" #include "debug_sync.h" // DEBUG_SYNC #include <m_ctype.h> @@ -58,24 +60,35 @@ #include <hash.h> #include <ft_global.h> +/* + A key part number that means we're using a fulltext scan. + + In order not to confuse it with regular equalities, we need to pick + a number that's greater than MAX_REF_PARTS. + + Hash Join code stores field->field_index in KEYUSE::keypart, so the + number needs to be bigger than MAX_FIELDS, also. + + CAUTION: sql_test.cc has its own definition of FT_KEYPART. +*/ +#define FT_KEYPART (MAX_FIELDS+10) + const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref", "MAYBE_REF","ALL","range","index","fulltext", "ref_or_null","unique_subquery","index_subquery", "index_merge", "hash_ALL", "hash_range", "hash_index", "hash_index_merge" }; -const char *copy_to_tmp_table= "Copying to tmp table"; - struct st_sargable_param; static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array); static bool make_join_statistics(JOIN *join, List<TABLE_LIST> &leaves, - COND *conds, DYNAMIC_ARRAY *keyuse); + DYNAMIC_ARRAY *keyuse); static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse, JOIN_TAB *join_tab, uint tables, COND *conds, table_map table_map, SELECT_LEX *select_lex, - st_sargable_param **sargables); + SARGABLE_PARAM **sargables); static bool sort_and_filter_keyuse(THD *thd, DYNAMIC_ARRAY *keyuse, bool skip_unprefixed_keyparts); static int sort_keyuse(KEYUSE *a,KEYUSE *b); @@ -88,12 +101,14 @@ void best_access_path(JOIN *join, JOIN_TAB *s, POSITION *pos, POSITION *loose_scan_pos); static void optimize_straight_join(JOIN *join, table_map join_tables); static bool greedy_search(JOIN *join, table_map remaining_tables, - uint depth, uint prune_level); + uint depth, uint prune_level, + uint use_cond_selectivity); static bool best_extension_by_limited_search(JOIN *join, table_map remaining_tables, uint idx, double record_count, double read_time, uint depth, - uint prune_level); + uint prune_level, + uint use_cond_selectivity); static uint determine_search_depth(JOIN* join); C_MODE_START static int join_tab_cmp(const void *dummy, const void* ptr1, const void* ptr2); @@ -105,7 +120,7 @@ C_MODE_END tested and approved. */ static bool find_best(JOIN *join,table_map rest_tables,uint index, - double record_count,double read_time); + double record_count,double read_time, uint use_cond_selectivity); static uint cache_record_length(JOIN *join,uint index); bool get_best_combination(JOIN *join); static store_key *get_store_key(THD *thd, @@ -114,7 +129,7 @@ static store_key *get_store_key(THD *thd, uint maybe_null); static bool make_outerjoin_info(JOIN *join); static Item* -make_cond_after_sjm(Item *root_cond, Item *cond, table_map tables, +make_cond_after_sjm(THD *thd, Item *root_cond, Item *cond, table_map tables, table_map sjm_tables, bool inside_or_clause); static bool make_join_select(JOIN *join,SQL_SELECT *select,COND *item); static void revise_cache_usage(JOIN_TAB *join_tab); @@ -133,8 +148,9 @@ static COND *build_equal_items(JOIN *join, COND *cond, COND_EQUAL *inherited, List<TABLE_LIST> *join_list, bool ignore_on_conds, - COND_EQUAL **cond_equal_ref); -static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, + COND_EQUAL **cond_equal_ref, + bool link_equal_fields= FALSE); +static COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, COND *cond, COND_EQUAL *cond_equal, void *table_join_idx); @@ -149,12 +165,10 @@ static uint build_bitmap_for_nested_joins(List<TABLE_LIST> *join_list, static COND *optimize_cond(JOIN *join, COND *conds, List<TABLE_LIST> *join_list, bool ignore_on_conds, - Item::cond_result *cond_value, - COND_EQUAL **cond_equal); + Item::cond_result *cond_value, + COND_EQUAL **cond_equal, + int flags= 0); bool const_expression_in_where(COND *conds,Item *item, Item **comp_item); -static bool create_internal_tmp_table_from_heap2(THD *, TABLE *, - ENGINE_COLUMNDEF *, ENGINE_COLUMNDEF **, - int, bool, handlerton *, const char *, bool *); static int do_select(JOIN *join,List<Item> *fields,TABLE *tmp_table, Procedure *proc); @@ -171,7 +185,7 @@ static enum_nested_loop_state end_unique_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); static int test_if_group_changed(List<Cached_item> &list); -static int join_read_const_table(JOIN_TAB *tab, POSITION *pos); +static int join_read_const_table(THD *thd, JOIN_TAB *tab, POSITION *pos); static int join_read_system(JOIN_TAB *tab); static int join_read_const(JOIN_TAB *tab); static int join_read_key(JOIN_TAB *tab); @@ -275,15 +289,66 @@ Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field, JOIN_TAB *first_depth_first_tab(JOIN* join); JOIN_TAB *next_depth_first_tab(JOIN* join, JOIN_TAB* tab); -enum enum_exec_or_opt {WALK_OPTIMIZATION_TABS , WALK_EXECUTION_TABS}; -JOIN_TAB *first_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind); -JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind, - JOIN_TAB *tab); +static JOIN_TAB *next_breadth_first_tab(JOIN_TAB *first_top_tab, + uint n_top_tabs_count, JOIN_TAB *tab); static bool find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, ORDER *order, List<Item> &fields, List<Item> &all_fields, bool is_group_field, bool add_to_all_fields); +static double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s, + table_map rem_tables); + +#ifndef DBUG_OFF + +/* + SHOW EXPLAIN testing: wait for, and serve n_calls APC requests. +*/ +void dbug_serve_apcs(THD *thd, int n_calls) +{ + const char *save_proc_info= thd->proc_info; + + /* Busy-wait for n_calls APC requests to arrive and be processed */ + int n_apcs= thd->apc_target.n_calls_processed + n_calls; + while (thd->apc_target.n_calls_processed < n_apcs) + { + /* This is so that mysqltest knows we're ready to serve requests: */ + thd_proc_info(thd, "show_explain_trap"); + my_sleep(30000); + thd_proc_info(thd, save_proc_info); + if (thd->check_killed()) + break; + } +} + + +/* + Debugging: check if @name=value, comparing as integer + + Intended usage: + + DBUG_EXECUTE_IF("show_explain_probe_2", + if (dbug_user_var_equals_int(thd, "select_id", select_id)) + dbug_serve_apcs(thd, 1); + ); + +*/ + +bool dbug_user_var_equals_int(THD *thd, const char *name, int value) +{ + user_var_entry *var; + LEX_STRING varname= {(char*)name, strlen(name)}; + if ((var= get_variable(&thd->user_vars, varname, FALSE))) + { + bool null_value; + longlong var_value= var->val_int(&null_value); + if (!null_value && var_value == value) + return TRUE; + } + return FALSE; +} +#endif + /** This handles SELECT with and without UNION. */ @@ -302,7 +367,7 @@ bool handle_select(THD *thd, LEX *lex, select_result *result, else { SELECT_LEX_UNIT *unit= &lex->unit; - unit->set_limit(unit->global_parameters); + unit->set_limit(unit->global_parameters()); /* 'options' of mysql_select will be set in JOIN, as far as JOIN for every PS/SP execution new, we will not need reset this flag if @@ -333,9 +398,9 @@ bool handle_select(THD *thd, LEX *lex, select_result *result, If LIMIT ROWS EXAMINED interrupted query execution, issue a warning, continue with normal processing and produce an incomplete query result. */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT, - ER(ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT), + ER_THD(thd, ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT), thd->accessed_rows_and_keys, thd->lex->limit_rows_examined->val_uint()); thd->reset_killed(); @@ -393,7 +458,7 @@ bool handle_select(THD *thd, LEX *lex, select_result *result, this field from inner subqueries. @return Status - @retval true An error occured. + @retval true An error occurred. @retval false OK. */ @@ -434,7 +499,7 @@ fix_inner_refs(THD *thd, List<Item> &all_fields, SELECT_LEX *select, DBUG_ASSERT(all_fields.elements <= select->ref_pointer_array_size); ref_pointer_array[el]= item; /* Add the field item to the select list of the current select. */ - all_fields.push_front(item); + all_fields.push_front(item, thd->mem_root); /* If it's needed reset each Item_ref item that refers this field with a new reference taken from ref_pointer_array. @@ -465,9 +530,9 @@ fix_inner_refs(THD *thd, List<Item> &all_fields, SELECT_LEX *select, direct_ref= TRUE; new_ref= direct_ref ? - new Item_direct_ref(ref->context, item_ref, ref->table_name, + new (thd->mem_root) Item_direct_ref(thd, ref->context, item_ref, ref->table_name, ref->field_name, ref->alias_name_used) : - new Item_ref(ref->context, item_ref, ref->table_name, + new (thd->mem_root) Item_ref(thd, ref->context, item_ref, ref->table_name, ref->field_name, ref->alias_name_used); if (!new_ref) return TRUE; @@ -566,7 +631,9 @@ inline int setup_without_group(THD *thd, Item **ref_pointer_array, List<Item> &all_fields, COND **conds, ORDER *order, - ORDER *group, bool *hidden_group_fields) + ORDER *group, + bool *hidden_group_fields, + uint *reserved) { int res; st_select_lex *const select= thd->lex->current_select; @@ -580,6 +647,13 @@ inline int setup_without_group(THD *thd, Item **ref_pointer_array, thd->lex->allow_sum_func&= ~((nesting_map)1 << select->nest_level); res= setup_conds(thd, tables, leaves, conds); + if (thd->lex->current_select->first_cond_optimization) + { + if (!res && *conds && ! thd->lex->current_select->merged_into) + (*reserved)= (*conds)->exists2in_reserved_items(); + else + (*reserved)= 0; + } /* it's not wrong to have non-aggregated columns in a WHERE */ select->set_non_agg_field_used(saved_non_agg_field_used); @@ -624,7 +698,7 @@ JOIN::prepare(Item ***rref_pointer_array, DBUG_ENTER("JOIN::prepare"); // to prevent double initialization on EXPLAIN - if (optimized) + if (optimization_state != JOIN::NOT_OPTIMIZED) DBUG_RETURN(0); conds= conds_init; @@ -723,7 +797,7 @@ JOIN::prepare(Item ***rref_pointer_array, uint real_og_num= og_num; if (skip_order_by && - select_lex != select_lex->master_unit()->global_parameters) + select_lex != select_lex->master_unit()->global_parameters()) real_og_num+= select_lex->order_list.elements; if ((wild_num && setup_wild(thd, tables_list, fields_list, &all_fields, @@ -734,13 +808,14 @@ JOIN::prepare(Item ***rref_pointer_array, setup_without_group(thd, (*rref_pointer_array), tables_list, select_lex->leaf_tables, fields_list, all_fields, &conds, order, group_list, - &hidden_group_fields)) + &hidden_group_fields, &select_lex->select_n_reserved)) DBUG_RETURN(-1); /* purecov: inspected */ ref_pointer_array= *rref_pointer_array; /* Resolve the ORDER BY that was skipped, then remove it. */ - if (skip_order_by && select_lex != select_lex->master_unit()->global_parameters) + if (skip_order_by && select_lex != + select_lex->master_unit()->global_parameters()) { thd->where= "order clause"; for (ORDER *order= select_lex->order_list.first; order; order= order->next) @@ -761,8 +836,9 @@ JOIN::prepare(Item ***rref_pointer_array, thd->lex->allow_sum_func|= (nesting_map)1 << select_lex_arg->nest_level; select_lex->having_fix_field= 1; /* - Wrap alone field in HAVING clause in case it will be outer field of subquery - which need persistent pointer on it, but having could be changed by optimizer + Wrap alone field in HAVING clause in case it will be outer field + of subquery which need persistent pointer on it, but having + could be changed by optimizer */ if (having->type() == Item::REF_ITEM && ((Item_ref *)having)->ref_type() == Item_ref::REF) @@ -810,7 +886,7 @@ JOIN::prepare(Item ***rref_pointer_array, real_order= TRUE; if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM) - item->split_sum_func(thd, ref_pointer_array, all_fields); + item->split_sum_func(thd, ref_pointer_array, all_fields, 0); } if (!real_order) order= NULL; @@ -818,7 +894,7 @@ JOIN::prepare(Item ***rref_pointer_array, if (having && having->with_sum_func) having->split_sum_func2(thd, ref_pointer_array, all_fields, - &having, TRUE); + &having, SPLIT_SUM_SKIP_REGISTERED); if (select_lex->inner_sum_func_list) { Item_sum *end=select_lex->inner_sum_func_list; @@ -827,7 +903,7 @@ JOIN::prepare(Item ***rref_pointer_array, { item_sum= item_sum->next; item_sum->split_sum_func2(thd, ref_pointer_array, - all_fields, item_sum->ref_by, FALSE); + all_fields, item_sum->ref_by, 0); } while (item_sum != end); } @@ -848,11 +924,11 @@ JOIN::prepare(Item ***rref_pointer_array, if ((*ord->item)->type() == Item::FIELD_ITEM && (*ord->item)->field_type() == MYSQL_TYPE_BIT) { - Item_field *field= new Item_field(thd, *(Item_field**)ord->item); + Item_field *field= new (thd->mem_root) Item_field(thd, *(Item_field**)ord->item); int el= all_fields.elements; DBUG_ASSERT(all_fields.elements <= select_lex->ref_pointer_array_size); ref_pointer_array[el]= field; - all_fields.push_front(field); + all_fields.push_front(field, thd->mem_root); ord->item= ref_pointer_array + el; } } @@ -871,7 +947,7 @@ JOIN::prepare(Item ***rref_pointer_array, select_lex->agg_func_used()) { my_message(ER_MIX_OF_GROUP_FUNC_AND_FIELDS, - ER(ER_MIX_OF_GROUP_FUNC_AND_FIELDS), MYF(0)); + ER_THD(thd, ER_MIX_OF_GROUP_FUNC_AND_FIELDS), MYF(0)); DBUG_RETURN(-1); } { @@ -893,14 +969,14 @@ JOIN::prepare(Item ***rref_pointer_array, { if (!test_if_subpart(procedure->group,group_list)) { /* purecov: inspected */ - my_message(ER_DIFF_GROUPS_PROC, ER(ER_DIFF_GROUPS_PROC), + my_message(ER_DIFF_GROUPS_PROC, ER_THD(thd, ER_DIFF_GROUPS_PROC), MYF(0)); /* purecov: inspected */ goto err; /* purecov: inspected */ } } if (order && (procedure->flags & PROC_NO_SORT)) { /* purecov: inspected */ - my_message(ER_ORDER_WITH_PROC, ER(ER_ORDER_WITH_PROC), + my_message(ER_ORDER_WITH_PROC, ER_THD(thd, ER_ORDER_WITH_PROC), MYF(0)); /* purecov: inspected */ goto err; /* purecov: inspected */ } @@ -958,7 +1034,7 @@ bool JOIN::prepare_stage2() #ifdef RESTRICTED_GROUP if (implicit_grouping) { - my_message(ER_WRONG_SUM_SELECT,ER(ER_WRONG_SUM_SELECT),MYF(0)); + my_message(ER_WRONG_SUM_SELECT,ER_THD(thd, ER_WRONG_SUM_SELECT),MYF(0)); goto err; } #endif @@ -972,6 +1048,27 @@ err: DBUG_RETURN(res); /* purecov: inspected */ } +int JOIN::optimize() +{ + // to prevent double initialization on EXPLAIN + if (optimization_state != JOIN::NOT_OPTIMIZED) + return FALSE; + optimization_state= JOIN::OPTIMIZATION_IN_PROGRESS; + + int res= optimize_inner(); + if (!res && have_query_plan != QEP_DELETED) + { + create_explain_query_if_not_exists(thd->lex, thd->mem_root); + have_query_plan= QEP_AVAILABLE; + save_explain_data(thd->lex->explain, false /* can overwrite */, + need_tmp, + !skip_sort_order && !no_order && (order || group_list), + select_distinct); + } + optimization_state= JOIN::OPTIMIZATION_DONE; + return res; +} + /** global select optimisation. @@ -986,20 +1083,18 @@ err: */ int -JOIN::optimize() +JOIN::optimize_inner() { ulonglong select_opts_for_readinfo; uint no_jbuf_after; + JOIN_TAB *tab; DBUG_ENTER("JOIN::optimize"); do_send_rows = (unit->select_limit_cnt) ? 1 : 0; - // to prevent double initialization on EXPLAIN - if (optimized) - DBUG_RETURN(0); - optimized= 1; + DEBUG_SYNC(thd, "before_join_optimize"); - thd_proc_info(thd, "optimizing"); + THD_STAGE_INFO(thd, stage_optimizing); set_allowed_join_cache_types(); need_distinct= TRUE; @@ -1019,6 +1114,25 @@ JOIN::optimize() // Update used tables after all handling derived table procedures select_lex->update_used_tables(); + /* + In fact we transform underlying subqueries after their 'prepare' phase and + before 'optimize' from upper query 'optimize' to allow semijoin + conversion happened (which done in the same way. + */ + if (select_lex->first_cond_optimization && + conds && conds->walk(&Item::exists2in_processor, 0, (uchar *)thd)) + DBUG_RETURN(1); + /* + TODO + make view to decide if it is possible to write to WHERE directly or make Semi-Joins able to process ON condition if it is possible + for (TABLE_LIST *tbl= tables_list; tbl; tbl= tbl->next_local) + { + if (tbl->on_expr && + tbl->on_expr->walk(&Item::exists2in_processor, 0, (uchar *)thd)) + DBUG_RETURN(1); + } + */ + if (transform_max_min_subquery()) DBUG_RETURN(1); /* purecov: inspected */ @@ -1054,7 +1168,7 @@ JOIN::optimize() conds= having; having= 0; } - else if ((conds=new Item_cond_and(conds,having))) + else if ((conds=new (thd->mem_root) Item_cond_and(conds,having))) { /* Item_cond_and can't be fixed after creation, so we do not check @@ -1109,7 +1223,7 @@ JOIN::optimize() DBUG_RETURN(1); conds= optimize_cond(this, conds, join_list, FALSE, - &cond_value, &cond_equal); + &cond_value, &cond_equal, OPT_LINK_EQUAL_FIELDS); if (thd->is_error()) { @@ -1176,15 +1290,16 @@ JOIN::optimize() (tbl->embedding && tbl->embedding->sj_on_expr)) { Item *prune_cond= tbl->on_expr? tbl->on_expr : conds; - tbl->table->no_partitions_used= prune_partitions(thd, tbl->table, - prune_cond); - } + tbl->table->all_partitions_pruned_away= prune_partitions(thd, + tbl->table, + prune_cond); + } } } #endif /* - Try to optimize count(*), min() and max() to const fields if + Try to optimize count(*), MY_MIN() and MY_MAX() to const fields if there is implicit grouping (aggregate functions but no group_list). In this case, the result set shall only contain one row. @@ -1257,9 +1372,35 @@ JOIN::optimize() /* get_sort_by_table() call used to be here: */ MEM_UNDEFINED(&sort_by_table, sizeof(sort_by_table)); + /* + We have to remove constants and duplicates from group_list before + calling make_join_statistics() as this may call get_best_group_min_max() + which needs a simplfied group_list. + */ + simple_group= 1; + if (group_list && table_count == 1) + { + group_list= remove_const(this, group_list, conds, + rollup.state == ROLLUP::STATE_NONE, + &simple_group); + if (thd->is_error()) + { + error= 1; + DBUG_RETURN(1); + } + if (!group_list) + { + /* The output has only one row */ + order=0; + simple_order=1; + group_optimized_away= 1; + select_distinct=0; + } + } + /* Calculate how to do the join */ - thd_proc_info(thd, "statistics"); - if (make_join_statistics(this, select_lex->leaf_tables, conds, &keyuse) || + THD_STAGE_INFO(thd, stage_statistics); + if (make_join_statistics(this, select_lex->leaf_tables, &keyuse) || thd->is_fatal_error) { DBUG_PRINT("error",("Error: make_join_statistics() failed")); @@ -1283,7 +1424,7 @@ JOIN::optimize() select_distinct= select_distinct && (const_tables != table_count); } - thd_proc_info(thd, "preparing"); + THD_STAGE_INFO(thd, stage_preparing); if (result->initialize_tables(this)) { DBUG_PRINT("error",("Error: initialize_tables() failed")); @@ -1302,7 +1443,7 @@ JOIN::optimize() best_read > (double) thd->variables.max_join_size && !(select_options & SELECT_DESCRIBE)) { /* purecov: inspected */ - my_message(ER_TOO_BIG_SELECT, ER(ER_TOO_BIG_SELECT), MYF(0)); + my_message(ER_TOO_BIG_SELECT, ER_THD(thd, ER_TOO_BIG_SELECT), MYF(0)); error= -1; DBUG_RETURN(1); } @@ -1312,7 +1453,7 @@ JOIN::optimize() if (!conds && outer_join) { /* Handle the case where we have an OUTER JOIN without a WHERE */ - conds=new Item_int((longlong) 1,1); // Always true + conds= new (thd->mem_root) Item_int(thd, (longlong) 1,1); // Always true } if (impossible_where) @@ -1346,7 +1487,7 @@ JOIN::optimize() */ if (conds) { - conds= substitute_for_best_equal_field(NO_PARTICULAR_TAB, conds, + conds= substitute_for_best_equal_field(thd, NO_PARTICULAR_TAB, conds, cond_equal, map2table); if (thd->is_error()) { @@ -1365,13 +1506,12 @@ JOIN::optimize() Perform the optimization on fields evaluation mentioned above for all on expressions. */ - JOIN_TAB *tab; for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab; tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS)) { if (*tab->on_expr_ref) { - *tab->on_expr_ref= substitute_for_best_equal_field(NO_PARTICULAR_TAB, + *tab->on_expr_ref= substitute_for_best_equal_field(thd, NO_PARTICULAR_TAB, *tab->on_expr_ref, tab->cond_equal, map2table); @@ -1403,7 +1543,7 @@ JOIN::optimize() JOIN_TAB *first_inner= tab->first_inner; while (equals) { - ref_item= substitute_for_best_equal_field(tab, ref_item, + ref_item= substitute_for_best_equal_field(thd, tab, ref_item, equals, map2table); if (first_inner) { @@ -1449,12 +1589,15 @@ JOIN::optimize() if (conds && const_table_map != found_const_table_map && (select_options & SELECT_DESCRIBE)) { - conds=new Item_int((longlong) 0,1); // Always false + conds=new (thd->mem_root) Item_int(thd, (longlong) 0, 1); // Always false } /* Cache constant expressions in WHERE, HAVING, ON clauses. */ cache_const_exprs(); + if (setup_semijoin_loosescan(this)) + DBUG_RETURN(1); + if (make_join_select(this, select, conds)) { zero_result_cause= @@ -1472,7 +1615,6 @@ JOIN::optimize() if (thd->is_error()) { error= 1; - DBUG_PRINT("error",("Error from remove_const")); DBUG_RETURN(1); } @@ -1495,14 +1637,14 @@ JOIN::optimize() The FROM clause must contain a single non-constant table. */ - if (table_count - const_tables == 1 && (group_list || select_distinct) && + if (table_count - const_tables == 1 && (group || select_distinct) && !tmp_table_param.sum_func_count && (!join_tab[const_tables].select || !join_tab[const_tables].select->quick || join_tab[const_tables].select->quick->get_type() != QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)) { - if (group_list && rollup.state == ROLLUP::STATE_NONE && + if (group && rollup.state == ROLLUP::STATE_NONE && list_contains_unique_index(join_tab[const_tables].table, find_field_in_order_list, (void *) group_list)) @@ -1511,9 +1653,11 @@ JOIN::optimize() We have found that grouping can be removed since groups correspond to only one row anyway, but we still have to guarantee correct result order. The line below effectively rewrites the query from GROUP BY - <fields> to ORDER BY <fields>. There are two exceptions: + <fields> to ORDER BY <fields>. There are three exceptions: - if skip_sort_order is set (see above), then we can simply skip GROUP BY; + - if we are in a subquery, we don't have to maintain order unless there + is a limit clause in the subquery. - we can only rewrite ORDER BY if the ORDER BY fields are 'compatible' with the GROUP BY ones, i.e. either one is a prefix of another. We only check if the ORDER BY is a prefix of GROUP BY. In this case @@ -1523,7 +1667,13 @@ JOIN::optimize() 'order' as is. */ if (!order || test_if_subpart(group_list, order)) - order= skip_sort_order ? 0 : group_list; + { + if (skip_sort_order || + (select_lex->master_unit()->item && select_limit == HA_POS_ERROR)) // This is a subquery + order= NULL; + else + order= group_list; + } /* If we have an IGNORE INDEX FOR GROUP BY(fields) clause, this must be rewritten to IGNORE INDEX FOR ORDER BY(fields). @@ -1541,7 +1691,7 @@ JOIN::optimize() select_distinct= 0; } } - if (group_list || tmp_table_param.sum_func_count) + if (group || tmp_table_param.sum_func_count) { if (! hidden_group_fields && rollup.state == ROLLUP::STATE_NONE) select_distinct=0; @@ -1564,11 +1714,14 @@ JOIN::optimize() because in this case we can just create a temporary table that holds LIMIT rows and stop when this table is full. */ - JOIN_TAB *tab= &join_tab[const_tables]; bool all_order_fields_used; + + tab= &join_tab[const_tables]; if (order) + { skip_sort_order= test_if_skip_sort_order(tab, order, select_limit, 1, &tab->table->keys_in_use_for_order_by); + } if ((group_list=create_distinct_group(thd, select_lex->ref_pointer_array, order, fields_list, all_fields, &all_order_fields_used))) @@ -1604,32 +1757,29 @@ JOIN::optimize() else if (thd->is_fatal_error) // End of memory DBUG_RETURN(1); } - simple_group= 0; + if (group) { - ORDER *old_group_list; - group_list= remove_const(this, (old_group_list= group_list), conds, + /* + Update simple_group and group_list as we now have more information, like + which tables or columns are constant. + */ + group_list= remove_const(this, group_list, conds, rollup.state == ROLLUP::STATE_NONE, - &simple_group); + &simple_group); if (thd->is_error()) { error= 1; - DBUG_PRINT("error",("Error from remove_const")); DBUG_RETURN(1); } - if (old_group_list && !group_list) + if (!group_list) { - DBUG_ASSERT(group); + /* The output has only one row */ + order=0; + simple_order=1; select_distinct= 0; + group_optimized_away= 1; } } - if (!group_list && group) - { - order=0; // The output has only one row - simple_order=1; - select_distinct= 0; // No need in distinct for 1 row - group_optimized_away= 1; - } - calc_group_buffer(this, group_list); send_group_parts= tmp_table_param.group_parts; /* Save org parts */ if (procedure && procedure->group) @@ -1639,7 +1789,6 @@ JOIN::optimize() if (thd->is_error()) { error= 1; - DBUG_PRINT("error",("Error from remove_const")); DBUG_RETURN(1); } calc_group_buffer(this, group_list); @@ -1693,7 +1842,7 @@ JOIN::optimize() /* Perform FULLTEXT search before all regular searches */ if (!(select_options & SELECT_DESCRIBE)) - init_ftfuncs(thd, select_lex, test(order)); + init_ftfuncs(thd, select_lex, MY_TEST(order)); if (optimize_unflattened_subqueries()) DBUG_RETURN(1); @@ -1787,13 +1936,101 @@ JOIN::optimize() } } - tmp_having= having; - if (select_options & SELECT_DESCRIBE) + if ((select_lex->options & OPTION_SCHEMA_TABLE)) + optimize_schema_tables_reads(this); + + /* + All optimization is done. Check if we can use the storage engines + group by handler to evaluate the group by + */ + + if ((tmp_table_param.sum_func_count || group_list) && !procedure) { - error= 0; - goto derived_exit; + /* + At the moment we only support push down for queries where + all tables are in the same storage engine + */ + TABLE_LIST *tbl= tables_list; + handlerton *ht= tbl && tbl->table ? tbl->table->file->ht : 0; + for (tbl= tbl->next_local; ht && tbl; tbl= tbl->next_local) + { + if (!tbl->table || tbl->table->file->ht != ht) + ht= 0; + } + + if (ht && ht->create_group_by) + { + /* Check if the storage engine can intercept the query */ + Query query= {&all_fields, select_distinct, tables_list, conds, + group_list, order ? order : group_list, having}; + group_by_handler *gbh= ht->create_group_by(thd, &query); + if (gbh) + { + pushdown_query= new (thd->mem_root) Pushdown_query(select_lex, gbh); + + /* + We must store rows in the tmp table if we need to do an ORDER BY + or DISTINCT and the storage handler can't handle it. + */ + need_tmp= query.order_by || query.group_by || query.distinct; + tmp_table_param.hidden_field_count= (all_fields.elements - + fields_list.elements); + if (!(exec_tmp_table1= + create_tmp_table(thd, &tmp_table_param, all_fields, 0, + query.distinct, 1, + select_options, HA_POS_ERROR, "", + !need_tmp, query.order_by || query.group_by))) + DBUG_RETURN(1); + + /* + Setup reference fields, used by summary functions and group by fields, + to point to the temporary table. + The actual switching to the temporary tables fields for HAVING + and ORDER BY is done in do_select() by calling + set_items_ref_array(items1). + */ + init_items_ref_array(); + items1= items0 + all_fields.elements; + if (change_to_use_tmp_fields(thd, items1, + tmp_fields_list1, tmp_all_fields1, + fields_list.elements, all_fields)) + DBUG_RETURN(1); + + /* Give storage engine access to temporary table */ + gbh->table= exec_tmp_table1; + + pushdown_query->store_data_in_temp_table= need_tmp; + pushdown_query->having= having; + /* + Group by and having is calculated by the group_by handler. + Reset the group by and having + */ + DBUG_ASSERT(query.group_by == NULL); + group= 0; group_list= 0; + having= tmp_having= 0; + /* + Select distinct is handled by handler or by creating an unique index + over all fields in the temporary table + */ + select_distinct= 0; + order= query.order_by; + tmp_table_param.field_count+= tmp_table_param.sum_func_count; + tmp_table_param.sum_func_count= 0; + + /* Remember information about the original join */ + original_join_tab= join_tab; + original_table_count= table_count; + + /* Set up one join tab to get sorting to work */ + const_tables= 0; + table_count= 1; + join_tab= (JOIN_TAB*) thd->calloc(sizeof(JOIN_TAB)); + join_tab[0].table= exec_tmp_table1; + + DBUG_RETURN(thd->is_fatal_error); + } + } } - having= 0; /* The loose index scan access method guarantees that all grouping or @@ -1818,6 +2055,11 @@ JOIN::optimize() error= 0; + tmp_having= having; + if (select_options & SELECT_DESCRIBE) + goto derived_exit; + having= 0; + DBUG_RETURN(0); setup_subq_exit: @@ -1833,6 +2075,7 @@ setup_subq_exit: error= 0; derived_exit: + select_lex->mark_const_derived(zero_result_cause); DBUG_RETURN(0); } @@ -1847,15 +2090,16 @@ int JOIN::init_execution() { DBUG_ENTER("JOIN::init_execution"); - DBUG_ASSERT(optimized); + DBUG_ASSERT(optimization_state == JOIN::OPTIMIZATION_DONE); DBUG_ASSERT(!(select_options & SELECT_DESCRIBE)); initialized= true; /* Enable LIMIT ROWS EXAMINED during query execution if: (1) This JOIN is the outermost query (not a subquery or derived table) - This ensures that the limit is enabled when actual execution begins, and - not if a subquery is evaluated during optimization of the outer query. + This ensures that the limit is enabled when actual execution begins, + and not if a subquery is evaluated during optimization of the outer + query. (2) This JOIN is not the result of a UNION. In this case do not apply the limit in order to produce the partial query result stored in the UNION temp table. @@ -1865,10 +2109,10 @@ int JOIN::init_execution() thd->lex->set_limit_rows_examined(); /* Create a tmp table if distinct or if the sort is too complicated */ - if (need_tmp) + if (need_tmp && !exec_tmp_table1) { DBUG_PRINT("info",("Creating tmp table")); - thd_proc_info(thd, "Creating tmp table"); + THD_STAGE_INFO(thd, stage_creating_tmp_table); init_items_ref_array(); @@ -1894,7 +2138,7 @@ int JOIN::init_execution() group_list && simple_group, select_options, tmp_rows_limit, ""))) DBUG_RETURN(1); - + explain->ops_tracker.report_tmp_table(exec_tmp_table1); /* We don't have to store rows in temp table that doesn't match HAVING if: - we are sorting the table and writing complete group rows to the @@ -1913,7 +2157,7 @@ int JOIN::init_execution() if (group_list && simple_group) { DBUG_PRINT("info",("Sorting for group")); - thd_proc_info(thd, "Sorting for group"); + THD_STAGE_INFO(thd, stage_sorting_for_group); if (create_sort_index(thd, this, group_list, HA_POS_ERROR, HA_POS_ERROR, FALSE) || alloc_group_fields(this, group_list) || @@ -1936,7 +2180,8 @@ int JOIN::init_execution() if (!group_list && ! exec_tmp_table1->distinct && order && simple_order) { - thd_proc_info(thd, "Sorting for order"); + DBUG_PRINT("info",("Sorting for order")); + THD_STAGE_INFO(thd, stage_sorting_for_order); if (create_sort_index(thd, this, order, HA_POS_ERROR, HA_POS_ERROR, TRUE)) { @@ -1972,6 +2217,7 @@ int JOIN::init_execution() &join_tab[const_tables].table-> keys_in_use_for_order_by)) order=0; + join_tab[const_tables].update_explain_data(const_tables); } } @@ -2012,32 +2258,32 @@ bool JOIN::setup_subquery_caches() select_lex->expr_cache_may_be_used[NO_MATTER]) { if (conds) - conds= conds->transform(&Item::expr_cache_insert_transformer, - (uchar*) thd); + conds= conds->transform(thd, &Item::expr_cache_insert_transformer, + NULL); JOIN_TAB *tab; for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab; tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS)) { if (tab->select_cond) tab->select_cond= - tab->select_cond->transform(&Item::expr_cache_insert_transformer, - (uchar*) thd); + tab->select_cond->transform(thd, &Item::expr_cache_insert_transformer, + NULL); if (tab->cache_select && tab->cache_select->cond) tab->cache_select->cond= tab->cache_select-> - cond->transform(&Item::expr_cache_insert_transformer, - (uchar*) thd); + cond->transform(thd, &Item::expr_cache_insert_transformer, + NULL); } if (having) - having= having->transform(&Item::expr_cache_insert_transformer, - (uchar*) thd); + having= having->transform(thd, &Item::expr_cache_insert_transformer, + NULL); if (tmp_having) { DBUG_ASSERT(having == NULL); - tmp_having= tmp_having->transform(&Item::expr_cache_insert_transformer, - (uchar*) thd); + tmp_having= tmp_having->transform(thd, &Item::expr_cache_insert_transformer, + NULL); } } if (select_lex->expr_cache_may_be_used[SELECT_LIST] || @@ -2049,17 +2295,18 @@ bool JOIN::setup_subquery_caches() while ((item= li++)) { Item *new_item= - item->transform(&Item::expr_cache_insert_transformer, (uchar*) thd); + item->transform(thd, &Item::expr_cache_insert_transformer, + NULL); if (new_item != item) { thd->change_item_tree(li.ref(), new_item); } } - for (ORDER *group= group_list; group ; group= group->next) + for (ORDER *tmp_group= group_list; tmp_group ; tmp_group= tmp_group->next) { - *group->item= - (*group->item)->transform(&Item::expr_cache_insert_transformer, - (uchar*) thd); + *tmp_group->item= + (*tmp_group->item)->transform(thd, &Item::expr_cache_insert_transformer, + NULL); } } if (select_lex->expr_cache_may_be_used[NO_MATTER]) @@ -2067,8 +2314,8 @@ bool JOIN::setup_subquery_caches() for (ORDER *ord= order; ord; ord= ord->next) { *ord->item= - (*ord->item)->transform(&Item::expr_cache_insert_transformer, - (uchar*) thd); + (*ord->item)->transform(thd, &Item::expr_cache_insert_transformer, + NULL); } } DBUG_RETURN(FALSE); @@ -2082,7 +2329,7 @@ void JOIN::restore_tmp() { DBUG_PRINT("info", ("restore_tmp this %p tmp_join %p", this, tmp_join)); DBUG_ASSERT(tmp_join != this); - memcpy(tmp_join, this, (size_t) sizeof(JOIN)); + memcpy((void*)tmp_join, this, (size_t) sizeof(JOIN)); } @@ -2151,8 +2398,7 @@ JOIN::reinit() DBUG_ENTER("JOIN::reinit"); unit->offset_limit_cnt= (ha_rows)(select_lex->offset_limit ? - select_lex->offset_limit->val_uint() : - ULL(0)); + select_lex->offset_limit->val_uint() : 0); first_record= 0; cleaned= false; @@ -2211,7 +2457,7 @@ JOIN::reinit() } if (!(select_options & SELECT_DESCRIBE)) - init_ftfuncs(thd, select_lex, test(order)); + init_ftfuncs(thd, select_lex, MY_TEST(order)); DBUG_RETURN(0); } @@ -2251,6 +2497,80 @@ JOIN::save_join_tab() } +void JOIN::save_explain_data(Explain_query *output, bool can_overwrite, + bool need_tmp_table, bool need_order, + bool distinct) +{ + /* + If there is SELECT in this statemet with the same number it must be the + same SELECT + */ + DBUG_ASSERT(select_lex->select_number == UINT_MAX || + select_lex->select_number == INT_MAX || + !output || + !output->get_select(select_lex->select_number) || + output->get_select(select_lex->select_number)->select_lex == + select_lex); + + if (select_lex->select_number != UINT_MAX && + select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && + have_query_plan != JOIN::QEP_NOT_PRESENT_YET && + have_query_plan != JOIN::QEP_DELETED && // this happens when there was + // no QEP ever, but then + //cleanup() is called multiple times + output && // for "SET" command in SPs. + (can_overwrite? true: !output->get_select(select_lex->select_number))) + { + const char *message= NULL; + if (!table_count || !tables_list || zero_result_cause) + { + /* It's a degenerate join */ + message= zero_result_cause ? zero_result_cause : "No tables used"; + } + save_explain_data_intern(thd->lex->explain, need_tmp_table, need_order, + distinct, message); + return; + } + + /* + Can have join_tab==NULL for degenerate cases (e.g. SELECT .. UNION ... SELECT LIMIT 0) + */ + if (select_lex == select_lex->master_unit()->fake_select_lex && join_tab) + { + /* + This is fake_select_lex. It has no query plan, but we need to set up a + tracker for ANALYZE + */ + uint nr= select_lex->master_unit()->first_select()->select_number; + Explain_union *eu= output->get_union(nr); + explain= &eu->fake_select_lex_explain; + join_tab[0].tracker= eu->get_fake_select_lex_tracker(); + } +} + + +void JOIN::exec() +{ + DBUG_EXECUTE_IF("show_explain_probe_join_exec_start", + if (dbug_user_var_equals_int(thd, + "show_explain_probe_select_id", + select_lex->select_number)) + dbug_serve_apcs(thd, 1); + ); + ANALYZE_START_TRACKING(&explain->time_tracker); + explain->ops_tracker.report_join_start(); + exec_inner(); + ANALYZE_STOP_TRACKING(&explain->time_tracker); + + DBUG_EXECUTE_IF("show_explain_probe_join_exec_end", + if (dbug_user_var_equals_int(thd, + "show_explain_probe_select_id", + select_lex->select_number)) + dbug_serve_apcs(thd, 1); + ); +} + + /** Exec select. @@ -2262,28 +2582,32 @@ JOIN::save_join_tab() @todo When can we have here thd->net.report_error not zero? */ -void -JOIN::exec() + +void JOIN::exec_inner() { List<Item> *columns_list= &fields_list; int tmp_error; DBUG_ENTER("JOIN::exec"); - thd_proc_info(thd, "executing"); + const bool has_group_by= this->group; + + THD_STAGE_INFO(thd, stage_executing); error= 0; if (procedure) { procedure_fields_list= fields_list; - if (procedure->change_columns(procedure_fields_list) || + if (procedure->change_columns(thd, procedure_fields_list) || result->prepare(procedure_fields_list, unit)) { - thd->limit_found_rows= thd->examined_row_count= 0; + thd->set_examined_row_count(0); + thd->limit_found_rows= 0; DBUG_VOID_RETURN; } columns_list= &procedure_fields_list; } - (void) result->prepare2(); // Currently, this cannot fail. + if (result->prepare2()) + DBUG_VOID_RETURN; if (!tables_list && (table_count || !select_lex->with_sum_func)) { // Only test of functions @@ -2318,7 +2642,7 @@ JOIN::exec() error= 1; else send_records= ((select_options & OPTION_FOUND_ROWS) ? 1 : - thd->sent_row_count); + thd->get_sent_row_count()); } else send_records= 0; @@ -2330,16 +2654,9 @@ JOIN::exec() } /* Single select (without union) always returns 0 or 1 row */ thd->limit_found_rows= send_records; - thd->examined_row_count= 0; + thd->set_examined_row_count(0); DBUG_VOID_RETURN; } - /* - Don't reset the found rows count if there're no tables as - FOUND_ROWS() may be called. Never reset the examined row count here. - It must be accumulated from all join iterations of all join parts. - */ - if (table_count) - thd->limit_found_rows= 0; /* Evaluate expensive constant conditions that were not evaluated during @@ -2372,9 +2689,10 @@ JOIN::exec() } /* - Evaluate all constant expressions with subqueries in the ORDER/GROUP clauses - to make sure that all subqueries return a single row. The evaluation itself - will trigger an error if that is not the case. + Evaluate all constant expressions with subqueries in the + ORDER/GROUP clauses to make sure that all subqueries return a + single row. The evaluation itself will trigger an error if that is + not the case. */ if (exec_const_order_group_cond.elements && !(select_options & SELECT_DESCRIBE)) @@ -2383,7 +2701,7 @@ JOIN::exec() Item *cur_const_item; while ((cur_const_item= const_item_it++)) { - cur_const_item->val_str(&cur_const_item->str_value); + cur_const_item->val_str(); // This caches val_str() to Item::str_value if (thd->is_error()) { error= thd->is_error(); @@ -2413,15 +2731,26 @@ JOIN::exec() simple_order= simple_group; skip_sort_order= 0; } - if (order && - (order != group_list || !(select_options & SELECT_BIG_RESULT)) && - (const_tables == table_count || - ((simple_order || skip_sort_order) && - test_if_skip_sort_order(&join_tab[const_tables], order, - select_limit, 0, - &join_tab[const_tables].table-> + if (order && join_tab) + { + bool made_call= false; + SQL_SELECT *tmp_select= join_tab[const_tables].select; + if ((order != group_list || + !(select_options & SELECT_BIG_RESULT) || + (tmp_select && tmp_select->quick && + tmp_select->quick->get_type() == + QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)) && + (const_tables == table_count || + ((simple_order || skip_sort_order) && + (made_call=true) && + test_if_skip_sort_order(&join_tab[const_tables], order, + select_limit, 0, + &join_tab[const_tables].table-> keys_in_use_for_query)))) - order=0; + order=0; + if (made_call) + join_tab[const_tables].update_explain_data(const_tables); + } having= tmp_having; select_describe(this, need_tmp, order != 0 && !skip_sort_order, @@ -2454,7 +2783,9 @@ JOIN::exec() must be accumulated in examined_row_count. Hence every join iteration must count from zero. */ - curr_join->examined_rows= 0; + curr_join->join_examined_rows= 0; + + curr_join->do_select_call_count= 0; /* Create a tmp table if distinct or if the sort is too complicated */ if (need_tmp) @@ -2472,13 +2803,13 @@ JOIN::exec() curr_tmp_table= exec_tmp_table1; /* Copy data to the temporary table */ - thd_proc_info(thd, copy_to_tmp_table); + THD_STAGE_INFO(thd, stage_copying_to_tmp_table); DBUG_PRINT("info", ("%s", thd->proc_info)); if (!curr_join->sort_and_group && curr_join->const_tables != curr_join->table_count) { JOIN_TAB *first_tab= curr_join->join_tab + curr_join->const_tables; - first_tab->sorted= test(first_tab->loosescan_match_tab); + first_tab->sorted= MY_TEST(first_tab->loosescan_match_tab); } Procedure *save_proc= curr_join->procedure; @@ -2573,6 +2904,10 @@ JOIN::exec() DBUG_PRINT("info",("Creating group table")); /* Free first data from old join */ + + /* + psergey-todo: this is the place of pre-mature JOIN::free call. + */ curr_join->join_free(); if (curr_join->make_simple_join(this, curr_tmp_table)) DBUG_VOID_RETURN; @@ -2584,7 +2919,6 @@ JOIN::exec() (curr_join->tmp_all_fields1.elements- curr_join->tmp_fields_list1.elements); - if (exec_tmp_table2) curr_tmp_table= exec_tmp_table2; else @@ -2610,14 +2944,16 @@ JOIN::exec() HA_POS_ERROR, ""))) DBUG_VOID_RETURN; curr_join->exec_tmp_table2= exec_tmp_table2; + explain->ops_tracker.report_tmp_table(exec_tmp_table2); } if (curr_join->group_list) { - thd_proc_info(thd, "Creating sort index"); if (curr_join->join_tab == join_tab && save_join_tab()) { DBUG_VOID_RETURN; } + DBUG_PRINT("info",("Sorting for index")); + THD_STAGE_INFO(thd, stage_creating_sort_index); if (create_sort_index(thd, curr_join, curr_join->group_list, HA_POS_ERROR, HA_POS_ERROR, FALSE) || make_group_fields(this, curr_join)) @@ -2627,7 +2963,7 @@ JOIN::exec() sortorder= curr_join->sortorder; } - thd_proc_info(thd, "Copying to group table"); + THD_STAGE_INFO(thd, stage_copying_to_group_table); DBUG_PRINT("info", ("%s", thd->proc_info)); if (curr_join != this) { @@ -2653,7 +2989,7 @@ JOIN::exec() curr_join->const_tables != curr_join->table_count) { JOIN_TAB *first_tab= curr_join->join_tab + curr_join->const_tables; - first_tab->sorted= test(first_tab->loosescan_match_tab); + first_tab->sorted= MY_TEST(first_tab->loosescan_match_tab); } tmp_error= -1; if (setup_sum_funcs(curr_join->thd, curr_join->sum_funcs) || @@ -2697,12 +3033,11 @@ JOIN::exec() if (curr_tmp_table->distinct) curr_join->select_distinct=0; /* Each row is unique */ - curr_join->join_free(); /* Free quick selects */ if (curr_join->select_distinct && ! curr_join->group_list) { - thd_proc_info(thd, "Removing duplicates"); + THD_STAGE_INFO(thd, stage_removing_duplicates); if (remove_duplicates(curr_join, curr_tmp_table, *curr_fields_list, curr_join->tmp_having)) DBUG_VOID_RETURN; @@ -2710,6 +3045,7 @@ JOIN::exec() curr_join->select_distinct=0; } curr_tmp_table->reginfo.lock_type= TL_UNLOCK; + // psergey-todo: here is one place where we switch to if (curr_join->make_simple_join(this, curr_tmp_table)) DBUG_VOID_RETURN; calc_group_buffer(curr_join, curr_join->group_list); @@ -2773,7 +3109,7 @@ JOIN::exec() if (curr_join->group_list || curr_join->order) { DBUG_PRINT("info",("Sorting for send_result_set_metadata")); - thd_proc_info(thd, "Sorting result"); + THD_STAGE_INFO(thd, stage_sorting_result); /* If we have already done the group, add HAVING to sorted table */ if (curr_join->tmp_having && ! curr_join->group_list && ! curr_join->sort_and_group) @@ -2781,6 +3117,7 @@ JOIN::exec() JOIN_TAB *curr_table= &curr_join->join_tab[curr_join->const_tables]; table_map used_tables= (curr_join->const_table_map | curr_table->table->map); + curr_join->tmp_having->update_used_tables(); Item* sort_table_cond= make_cond_for_table(thd, curr_join->tmp_having, used_tables, @@ -2799,8 +3136,8 @@ JOIN::exec() else { if (!(curr_table->select->cond= - new Item_cond_and(curr_table->select->cond, - sort_table_cond))) + new (thd->mem_root) Item_cond_and(thd, curr_table->select->cond, + sort_table_cond))) DBUG_VOID_RETURN; } if (curr_table->pre_idx_push_select_cond) @@ -2808,7 +3145,7 @@ JOIN::exec() if (sort_table_cond->type() == Item::COND_ITEM) sort_table_cond= sort_table_cond->copy_andor_structure(thd); if (!(curr_table->pre_idx_push_select_cond= - new Item_cond_and(curr_table->pre_idx_push_select_cond, + new (thd->mem_root) Item_cond_and(thd, curr_table->pre_idx_push_select_cond, sort_table_cond))) DBUG_VOID_RETURN; } @@ -2873,12 +3210,38 @@ JOIN::exec() the query. XXX: it's never shown in EXPLAIN! OPTION_FOUND_ROWS supersedes LIMIT and is taken into account. */ - if (create_sort_index(thd, curr_join, - curr_join->group_list ? - curr_join->group_list : curr_join->order, - curr_join->select_limit, - (select_options & OPTION_FOUND_ROWS ? - HA_POS_ERROR : unit->select_limit_cnt), + DBUG_PRINT("info",("Sorting for order by/group by")); + ORDER *order_arg= + curr_join->group_list ? curr_join->group_list : curr_join->order; + /* + filesort_limit: Return only this many rows from filesort(). + We can use select_limit_cnt only if we have no group_by and 1 table. + This allows us to use Bounded_queue for queries like: + "select SQL_CALC_FOUND_ROWS * from t1 order by b desc limit 1;" + select_limit == HA_POS_ERROR (we need a full table scan) + unit->select_limit_cnt == 1 (we only need one row in the result set) + */ + const ha_rows filesort_limit_arg= + (has_group_by || curr_join->table_count > 1) + ? curr_join->select_limit : unit->select_limit_cnt; + const ha_rows select_limit_arg= + select_options & OPTION_FOUND_ROWS + ? HA_POS_ERROR : unit->select_limit_cnt; + curr_join->filesort_found_rows= filesort_limit_arg != HA_POS_ERROR; + + DBUG_PRINT("info", ("has_group_by %d " + "curr_join->table_count %d " + "curr_join->m_select_limit %d " + "unit->select_limit_cnt %d", + has_group_by, + curr_join->table_count, + (int) curr_join->select_limit, + (int) unit->select_limit_cnt)); + if (create_sort_index(thd, + curr_join, + order_arg, + filesort_limit_arg, + select_limit_arg, curr_join->group_list ? FALSE : TRUE)) DBUG_VOID_RETURN; sortorder= curr_join->sortorder; @@ -2904,18 +3267,25 @@ JOIN::exec() curr_join->fields= curr_fields_list; curr_join->procedure= procedure; - thd_proc_info(thd, "Sending data"); + THD_STAGE_INFO(thd, stage_sending_data); DBUG_PRINT("info", ("%s", thd->proc_info)); result->send_result_set_metadata((procedure ? curr_join->procedure_fields_list : *curr_fields_list), Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF); error= do_select(curr_join, curr_fields_list, NULL, procedure); - thd->limit_found_rows= curr_join->send_records - curr_join->duplicate_rows; + if (curr_join->order && curr_join->sortorder && + curr_join->filesort_found_rows) + { + /* Use info provided by filesort. */ + DBUG_ASSERT(curr_join->table_count > curr_join->const_tables); + JOIN_TAB *tab= curr_join->join_tab + curr_join->const_tables; + thd->limit_found_rows= tab->records; + } /* Accumulate the counts from all join iterations of all join parts. */ - thd->examined_row_count+= curr_join->examined_rows; + thd->inc_examined_row_count(curr_join->join_examined_rows); DBUG_PRINT("counts", ("thd->examined_row_count: %lu", - (ulong) thd->examined_row_count)); + (ulong) thd->get_examined_row_count())); /* With EXPLAIN EXTENDED we have to restore original ref_array @@ -2964,6 +3334,7 @@ JOIN::destroy() */ tmp_table_param.cleanup(); tmp_join->tmp_table_param.copy_field= 0; + cleanup(1); DBUG_RETURN(tmp_join->destroy()); } cond_equal= 0; @@ -3051,7 +3422,7 @@ mysql_select(THD *thd, Item ***rref_pointer_array, select_result *result, SELECT_LEX_UNIT *unit, SELECT_LEX *select_lex) { - bool err; + int err= 0; bool free_join= 1; DBUG_ENTER("mysql_select"); @@ -3069,11 +3440,6 @@ mysql_select(THD *thd, Item ***rref_pointer_array, { if (select_lex->linkage != GLOBAL_OPTIONS_TYPE) { - //here is EXPLAIN of subselect or derived table - if (join->change_result(result)) - { - DBUG_RETURN(TRUE); - } /* Original join tabs might be overwritten at first subselect execution. So we need to restore them. @@ -3104,9 +3470,9 @@ mysql_select(THD *thd, Item ***rref_pointer_array, if (select_options & SELECT_DESCRIBE) free_join= 0; - if (!(join= new JOIN(thd, fields, select_options, result))) + if (!(join= new (thd->mem_root) JOIN(thd, fields, select_options, result))) DBUG_RETURN(TRUE); - thd_proc_info(thd, "init"); + THD_STAGE_INFO(thd, stage_init); thd->lex->used_tables=0; if ((err= join->prepare(rref_pointer_array, tables, wild_num, conds, og_num, order, false, group, having, proc_param, @@ -3141,11 +3507,11 @@ mysql_select(THD *thd, Item ***rref_pointer_array, err: if (free_join) { - thd_proc_info(thd, "end"); + THD_STAGE_INFO(thd, stage_end); err|= select_lex->cleanup(); DBUG_RETURN(err || thd->is_error()); } - DBUG_RETURN(join->error); + DBUG_RETURN(join->error ? join->error: err); } @@ -3168,7 +3534,8 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, select->head=table; table->reginfo.impossible_range=0; if ((error= select->test_quick_select(thd, *(key_map *)keys,(table_map) 0, - limit, 0, FALSE)) == 1) + limit, 0, FALSE, + TRUE /* remove_where_parts*/)) == 1) DBUG_RETURN(select->quick->records); if (error == -1) { @@ -3187,12 +3554,12 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, We form a bitmap of indexes that can be used for sargable predicates. Only such indexes are involved in range analysis. */ -typedef struct st_sargable_param +struct SARGABLE_PARAM { Field *field; /* field against which to check sargability */ Item **arg_value; /* values of potential keys for lookups */ uint num_values; /* number of values in the above array */ -} SARGABLE_PARAM; +}; /** @@ -3206,10 +3573,10 @@ typedef struct st_sargable_param static bool make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, - COND *conds, DYNAMIC_ARRAY *keyuse_array) + DYNAMIC_ARRAY *keyuse_array) { int error= 0; - TABLE *table; + TABLE *UNINIT_VAR(table); /* inited in all loops */ uint i,table_count,const_count,key; table_map found_const_table_map, all_table_map, found_ref, refs; key_map const_ref, eq_part; @@ -3224,26 +3591,31 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, TABLE_LIST *tables; DBUG_ENTER("make_join_statistics"); - LINT_INIT(table); /* inited in all loops */ table_count=join->table_count; - stat=(JOIN_TAB*) join->thd->calloc(sizeof(JOIN_TAB)*(table_count)); - stat_ref=(JOIN_TAB**) join->thd->alloc(sizeof(JOIN_TAB*)* - (MAX_TABLES + table_count + 1)); - stat_vector= stat_ref + MAX_TABLES; - table_vector=(TABLE**) join->thd->calloc(sizeof(TABLE*)*(table_count*2)); - join->positions= new (join->thd->mem_root) POSITION[(table_count+1)]; /* best_positions is ok to allocate with alloc() as we copy things to it with memcpy() */ - join->best_positions= (POSITION*) join->thd->alloc(sizeof(POSITION)* - (table_count +1)); - if (join->thd->is_fatal_error) - DBUG_RETURN(1); // Eom /* purecov: inspected */ + if (!multi_alloc_root(join->thd->mem_root, + &stat, sizeof(JOIN_TAB)*(table_count), + &stat_ref, sizeof(JOIN_TAB*)* MAX_TABLES, + &stat_vector, sizeof(JOIN_TAB*)* (table_count +1), + &table_vector, sizeof(TABLE*)*(table_count*2), + &join->positions, sizeof(POSITION)*(table_count + 1), + &join->best_positions, + sizeof(POSITION)*(table_count + 1), + NullS)) + DBUG_RETURN(1); + + /* The following should be optimized to only clear critical things */ + bzero((void*)stat, sizeof(JOIN_TAB)* table_count); + /* Initialize POSITION objects */ + for (i=0 ; i <= table_count ; i++) + (void) new ((char*) (join->positions + i)) POSITION; - join->best_ref=stat_vector; + join->best_ref= stat_vector; stat_end=stat+table_count; found_const_table_map= all_table_map=0; @@ -3260,15 +3632,17 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, table_vector[i]=s->table=table=tables->table; table->pos_in_table_list= tables; error= tables->fetch_number_of_rows(); + set_statistics_for_table(join->thd, table); + bitmap_clear_all(&table->cond_set); #ifdef WITH_PARTITION_STORAGE_ENGINE - const bool no_partitions_used= table->no_partitions_used; + const bool all_partitions_pruned_away= table->all_partitions_pruned_away; #else - const bool no_partitions_used= FALSE; + const bool all_partitions_pruned_away= FALSE; #endif DBUG_EXECUTE_IF("bug11747970_raise_error", - { join->thd->killed= KILL_QUERY_HARD; }); + { join->thd->set_killed(KILL_QUERY_HARD); }); if (error) { table->file->print_error(error, MYF(0)); @@ -3285,8 +3659,8 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, s->dependent= tables->dep_tables; if (tables->schema_table) - table->file->stats.records= 2; - table->quick_condition_rows= table->file->stats.records; + table->file->stats.records= table->used_stat_records= 2; + table->quick_condition_rows= table->stat_records(); s->on_expr_ref= &tables->on_expr; if (*s->on_expr_ref) @@ -3295,7 +3669,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, if (!table->is_filled_at_execution() && ((!table->file->stats.records && (table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)) || - no_partitions_used) && !embedding) + all_partitions_pruned_away) && !embedding) { // Empty table s->dependent= 0; // Ignore LEFT JOIN depend. no_rows_const_tables |= table->map; @@ -3339,7 +3713,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, (table->s->system || (table->file->stats.records <= 1 && (table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)) || - no_partitions_used) && + all_partitions_pruned_away) && !s->dependent && !table->fulltext_searched && !join->no_const_tables) { @@ -3401,17 +3775,18 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, if (s->dependent & s->table->map) { join->table_count=0; // Don't use join->table - my_message(ER_WRONG_OUTER_JOIN, ER(ER_WRONG_OUTER_JOIN), MYF(0)); + my_message(ER_WRONG_OUTER_JOIN, + ER_THD(join->thd, ER_WRONG_OUTER_JOIN), MYF(0)); goto error; } s->key_dependent= s->dependent; } } - if (conds || outer_join) + if (join->conds || outer_join) { if (update_ref_and_keys(join->thd, keyuse_array, stat, join->table_count, - conds, ~outer_join, join->select_lex, &sargables)) + join->conds, ~outer_join, join->select_lex, &sargables)) goto error; /* Keyparts without prefixes may be useful if this JOIN is a subquery, and @@ -3446,7 +3821,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, int tmp; s->type=JT_SYSTEM; join->const_table_map|=s->table->map; - if ((tmp=join_read_const_table(s, p_pos))) + if ((tmp=join_read_const_table(join->thd, s, p_pos))) { if (tmp > 0) goto error; // Fatal error @@ -3529,7 +3904,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, s->type=JT_SYSTEM; join->const_table_map|=table->map; set_position(join,const_count++,s,(KEYUSE*) 0); - if ((tmp= join_read_const_table(s, join->positions+const_count-1))) + if ((tmp= join_read_const_table(join->thd, s, join->positions+const_count-1))) { if (tmp > 0) goto error; // Fatal error @@ -3560,7 +3935,9 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, has_expensive_keyparts= false; do { - if (keyuse->val->type() != Item::NULL_ITEM && !keyuse->optimize) + if (keyuse->val->type() != Item::NULL_ITEM && + !keyuse->optimize && + keyuse->keypart != FT_KEYPART) { if (!((~found_const_table_map) & keyuse->used_tables)) { @@ -3589,7 +3966,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, (!embedding || (embedding->sj_on_expr && !embedding->embedding))) { key_map base_part, base_const_ref, base_eq_part; - base_part.set_prefix(keyinfo->key_parts); + base_part.set_prefix(keyinfo->user_defined_key_parts); base_const_ref= const_ref; base_const_ref.intersect(base_part); base_eq_part= eq_part; @@ -3610,7 +3987,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, if (create_ref_for_key(join, s, start_keyuse, FALSE, found_const_table_map)) goto error; - if ((tmp=join_read_const_table(s, + if ((tmp=join_read_const_table(join->thd, s, join->positions+const_count-1))) { if (tmp > 0) @@ -3655,34 +4032,37 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, } join->impossible_where= false; - if (conds && const_count) - { + if (join->conds && const_count) + { + Item* &conds= join->conds; COND_EQUAL *orig_cond_equal = join->cond_equal; + conds->update_used_tables(); - conds= remove_eq_conds(join->thd, conds, &join->cond_value); + conds= conds->remove_eq_conds(join->thd, &join->cond_value, true); if (conds && conds->type() == Item::COND_ITEM && ((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC) - join->cond_equal= &((Item_cond_and*) conds)->cond_equal; + join->cond_equal= &((Item_cond_and*) conds)->m_cond_equal; join->select_lex->where= conds; if (join->cond_value == Item::COND_FALSE) { join->impossible_where= true; - conds=new Item_int((longlong) 0,1); + conds= new (join->thd->mem_root) Item_int(join->thd, (longlong) 0, 1); } - join->conds= conds; + join->cond_equal= NULL; if (conds) { if (conds->type() == Item::COND_ITEM && ((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC) - join->cond_equal= (&((Item_cond_and *) conds)->cond_equal); + join->cond_equal= (&((Item_cond_and *) conds)->m_cond_equal); else if (conds->type() == Item::FUNC_ITEM && ((Item_func*) conds)->functype() == Item_func::MULT_EQUAL_FUNC) { if (!join->cond_equal) join->cond_equal= new COND_EQUAL; join->cond_equal->current_level.empty(); - join->cond_equal->current_level.push_back((Item_equal*) conds); + join->cond_equal->current_level.push_back((Item_equal*) conds, + join->thd->mem_root); } } @@ -3732,7 +4112,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, This is can't be to high as otherwise we are likely to use table scan. */ - s->worst_seeks= min((double) s->found_records / 10, + s->worst_seeks= MY_MIN((double) s->found_records / 10, (double) s->read_time*3); if (s->worst_seeks < 2.0) // Fix for small tables s->worst_seeks=2.0; @@ -3742,6 +4122,8 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, all select distinct fields participate in one index. */ add_group_and_distinct_keys(join, s); + + s->table->cond_selectivity= 1.0; /* Perform range analysis if there are keys it could use (1). @@ -3750,7 +4132,8 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, Don't do range analysis for materialized subqueries (4). Don't do range analysis for materialized derived tables (5) */ - if (!s->const_keys.is_clear_all() && // (1) + if ((!s->const_keys.is_clear_all() || + !bitmap_is_clear_all(&s->table->cond_set)) && // (1) (!s->table->pos_in_table_list->embedding || // (2) (s->table->pos_in_table_list->embedding && // (3) s->table->pos_in_table_list->embedding->sj_on_expr)) && // (3) @@ -3758,20 +4141,43 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, !(s->table->pos_in_table_list->derived && // (5) s->table->pos_in_table_list->is_materialized_derived())) // (5) { - ha_rows records; - SQL_SELECT *select; - select= make_select(s->table, found_const_table_map, - found_const_table_map, - *s->on_expr_ref ? *s->on_expr_ref : conds, - 1, &error); - if (!select) - goto error; - records= get_quick_record_count(join->thd, select, s->table, - &s->const_keys, join->row_limit); - s->quick=select->quick; - s->needed_reg=select->needed_reg; - select->quick=0; - if (records == 0 && s->table->reginfo.impossible_range) + bool impossible_range= FALSE; + ha_rows records= HA_POS_ERROR; + SQL_SELECT *select= 0; + if (!s->const_keys.is_clear_all()) + { + select= make_select(s->table, found_const_table_map, + found_const_table_map, + *s->on_expr_ref ? *s->on_expr_ref : join->conds, + 1, &error); + if (!select) + goto error; + records= get_quick_record_count(join->thd, select, s->table, + &s->const_keys, join->row_limit); + /* Range analyzer could modify the condition. */ + if (*s->on_expr_ref) + *s->on_expr_ref= select->cond; + else + join->conds= select->cond; + + s->quick=select->quick; + s->needed_reg=select->needed_reg; + select->quick=0; + impossible_range= records == 0 && s->table->reginfo.impossible_range; + } + if (!impossible_range) + { + if (join->thd->variables.optimizer_use_condition_selectivity > 1) + calculate_cond_selectivity_for_table(join->thd, s->table, + *s->on_expr_ref ? + s->on_expr_ref : &join->conds); + if (s->table->reginfo.impossible_range) + { + impossible_range= TRUE; + records= 0; + } + } + if (impossible_range) { /* Impossible WHERE or ON expression @@ -3785,7 +4191,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, if (*s->on_expr_ref) { /* Generate empty row */ - s->info= "Impossible ON condition"; + s->info= ET_IMPOSSIBLE_ON_CONDITION; found_const_table_map|= s->table->map; s->type= JT_CONST; mark_as_null_row(s->table); // All fields are NULL @@ -3796,8 +4202,10 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, s->found_records=records; s->read_time= s->quick ? s->quick->read_time : 0.0; } - delete select; + if (select) + delete select; } + } if (pull_out_semijoin_tables(join)) @@ -3819,7 +4227,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, ((Item_cond*) (join->conds))->functype() == Item_func::COND_AND_FUNC && join->cond_equal == - &((Item_cond_and *) (join->conds))->cond_equal) || + &((Item_cond_and *) (join->conds))->m_cond_equal) || (join->conds->type() == Item::FUNC_ITEM && ((Item_func*) (join->conds))->functype() == Item_func::MULT_EQUAL_FUNC && @@ -3843,7 +4251,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, { memcpy((uchar*) join->best_positions,(uchar*) join->positions, sizeof(POSITION)*join->const_tables); - join->record_count= 1.0; + join->join_record_count= 1.0; join->best_read=1.0; } @@ -3868,7 +4276,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, DEBUG_SYNC(join->thd, "inside_make_join_statistics"); /* Generate an execution plan from the found optimal join order. */ - DBUG_RETURN(join->thd->killed || get_best_combination(join)); + DBUG_RETURN(join->thd->check_killed() || get_best_combination(join)); error: /* @@ -3878,10 +4286,10 @@ error: Dangling TABLE::reginfo.join_tab may cause part_of_refkey to choke. */ { - TABLE_LIST *table; - List_iterator<TABLE_LIST> ti(tables_list); - while ((table= ti++)) - table->table->reginfo.join_tab= NULL; + TABLE_LIST *tmp_table; + List_iterator<TABLE_LIST> ti2(tables_list); + while ((tmp_table= ti2++)) + tmp_table->table->reginfo.join_tab= NULL; } DBUG_RETURN (1); } @@ -3896,8 +4304,9 @@ error: *****************************************************************************/ /// Used when finding key fields -typedef struct key_field_t { +struct KEY_FIELD { Field *field; + Item_bool_func *cond; Item *val; ///< May be empty if diff constant uint level; uint optimize; @@ -3909,7 +4318,8 @@ typedef struct key_field_t { bool null_rejecting; bool *cond_guard; /* See KEYUSE::cond_guard */ uint sj_pred_no; /* See KEYUSE::sj_pred_no */ -} KEY_FIELD; +}; + /** Merge new key definitions to old ones, remove those not used in both. @@ -4149,7 +4559,7 @@ static uint get_semi_join_select_list_index(Field *field) static void add_key_field(JOIN *join, - KEY_FIELD **key_fields,uint and_level, Item_func *cond, + KEY_FIELD **key_fields,uint and_level, Item_bool_func *cond, Field *field, bool eq_func, Item **value, uint num_values, table_map usable_tables, SARGABLE_PARAM **sargables) { @@ -4167,11 +4577,12 @@ add_key_field(JOIN *join, else if (!(field->flags & PART_KEY_FLAG)) { // Don't remove column IS NULL on a LEFT JOIN table - if (!eq_func || (*value)->type() != Item::NULL_ITEM || - !field->table->maybe_null || field->null_ptr) - return; // Not a key. Skip it - optimize= KEY_OPTIMIZE_EXISTS; - DBUG_ASSERT(num_values == 1); + if (eq_func && (*value)->type() == Item::NULL_ITEM && + field->table->maybe_null && !field->null_ptr) + { + optimize= KEY_OPTIMIZE_EXISTS; + DBUG_ASSERT(num_values == 1); + } } if (optimize != KEY_OPTIMIZE_EXISTS) { @@ -4179,8 +4590,9 @@ add_key_field(JOIN *join, bool optimizable=0; for (uint i=0; i<num_values; i++) { - used_tables|=(value[i])->used_tables(); - if (!((value[i])->used_tables() & (field->table->map | RAND_TABLE_BIT))) + table_map value_used_tables= (value[i])->used_tables(); + used_tables|= value_used_tables; + if (!(value_used_tables & (field->table->map | RAND_TABLE_BIT))) optimizable=1; } if (!optimizable) @@ -4220,7 +4632,10 @@ add_key_field(JOIN *join, break; } if (is_const) + { stat[0].const_keys.merge(possible_keys); + bitmap_set_bit(&field->table->cond_set, field->field_index); + } else if (!eq_func) { /* @@ -4236,20 +4651,6 @@ add_key_field(JOIN *join, } if (!eq_func) // eq_func is NEVER true when num_values > 1 return; - - /* - We can't use indexes when comparing a string index to a - number or two strings if the effective collation - of the operation differ from the field collation. - */ - - if (field->cmp_type() == STRING_RESULT) - { - if ((*value)->cmp_type() != STRING_RESULT) - return; - if (((Field_str*)field)->charset() != cond->compare_collation()) - return; - } } } /* @@ -4261,6 +4662,7 @@ add_key_field(JOIN *join, (*key_fields)->field= field; (*key_fields)->eq_func= eq_func; (*key_fields)->val= *value; + (*key_fields)->cond= cond; (*key_fields)->level= and_level; (*key_fields)->optimize= optimize; /* @@ -4310,7 +4712,7 @@ add_key_field(JOIN *join, static void add_key_equal_fields(JOIN *join, KEY_FIELD **key_fields, uint and_level, - Item_func *cond, Item *field_item, + Item_bool_func *cond, Item *field_item, bool eq_func, Item **val, uint num_values, table_map usable_tables, SARGABLE_PARAM **sargables) @@ -4379,240 +4781,281 @@ is_local_field (Item *field) operation */ -static void -add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level, - COND *cond, table_map usable_tables, - SARGABLE_PARAM **sargables) + +void +Item_cond_and::add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) { - if (cond->type() == Item_func::COND_ITEM) - { - List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list()); - KEY_FIELD *org_key_fields= *key_fields; + List_iterator_fast<Item> li(*argument_list()); + KEY_FIELD *org_key_fields= *key_fields; - if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) - { - Item *item; - while ((item=li++)) - add_key_fields(join, key_fields, and_level, item, usable_tables, - sargables); - for (; org_key_fields != *key_fields ; org_key_fields++) - org_key_fields->level= *and_level; - } - else - { - (*and_level)++; - add_key_fields(join, key_fields, and_level, li++, usable_tables, - sargables); - Item *item; - while ((item=li++)) - { - KEY_FIELD *start_key_fields= *key_fields; - (*and_level)++; - add_key_fields(join, key_fields, and_level, item, usable_tables, - sargables); - *key_fields=merge_key_fields(org_key_fields,start_key_fields, - *key_fields,++(*and_level)); - } - } - return; + Item *item; + while ((item=li++)) + item->add_key_fields(join, key_fields, and_level, usable_tables, + sargables); + for (; org_key_fields != *key_fields ; org_key_fields++) + org_key_fields->level= *and_level; +} + + +void +Item_cond::add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) +{ + List_iterator_fast<Item> li(*argument_list()); + KEY_FIELD *org_key_fields= *key_fields; + + (*and_level)++; + (li++)->add_key_fields(join, key_fields, and_level, usable_tables, + sargables); + Item *item; + while ((item=li++)) + { + KEY_FIELD *start_key_fields= *key_fields; + (*and_level)++; + item->add_key_fields(join, key_fields, and_level, usable_tables, + sargables); + *key_fields= merge_key_fields(org_key_fields,start_key_fields, + *key_fields, ++(*and_level)); } +} + +void +Item_func_trig_cond::add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) +{ /* Subquery optimization: Conditions that are pushed down into subqueries are wrapped into Item_func_trig_cond. We process the wrapped condition but need to set cond_guard for KEYUSE elements generated from it. */ + if (!join->group_list && !join->order && + join->unit->item && + join->unit->item->substype() == Item_subselect::IN_SUBS && + !join->unit->is_union()) { - if (cond->type() == Item::FUNC_ITEM && - ((Item_func*)cond)->functype() == Item_func::TRIG_COND_FUNC) + KEY_FIELD *save= *key_fields; + args[0]->add_key_fields(join, key_fields, and_level, usable_tables, + sargables); + // Indicate that this ref access candidate is for subquery lookup: + for (; save != *key_fields; save++) + save->cond_guard= get_trig_var(); + } +} + + +void +Item_func_between::add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) +{ + /* + Build list of possible keys for 'a BETWEEN low AND high'. + It is handled similar to the equivalent condition + 'a >= low AND a <= high': + */ + Item_field *field_item; + bool equal_func= false; + uint num_values= 2; + + bool binary_cmp= (args[0]->real_item()->type() == Item::FIELD_ITEM) + ? ((Item_field*) args[0]->real_item())->field->binary() + : true; + /* + Additional optimization: If 'low = high': + Handle as if the condition was "t.key = low". + */ + if (!negated && args[1]->eq(args[2], binary_cmp)) + { + equal_func= true; + num_values= 1; + } + + /* + Append keys for 'field <cmp> value[]' if the + condition is of the form:: + '<field> BETWEEN value[1] AND value[2]' + */ + if (is_local_field(args[0])) + { + field_item= (Item_field *) (args[0]->real_item()); + add_key_equal_fields(join, key_fields, *and_level, this, + field_item, equal_func, &args[1], + num_values, usable_tables, sargables); + } + /* + Append keys for 'value[0] <cmp> field' if the + condition is of the form: + 'value[0] BETWEEN field1 AND field2' + */ + for (uint i= 1; i <= num_values; i++) + { + if (is_local_field(args[i])) { - Item *cond_arg= ((Item_func*)cond)->arguments()[0]; - if (!join->group_list && !join->order && - join->unit->item && - join->unit->item->substype() == Item_subselect::IN_SUBS && - !join->unit->is_union()) - { - KEY_FIELD *save= *key_fields; - add_key_fields(join, key_fields, and_level, cond_arg, usable_tables, - sargables); - // Indicate that this ref access candidate is for subquery lookup: - for (; save != *key_fields; save++) - save->cond_guard= ((Item_func_trig_cond*)cond)->get_trig_var(); - } - return; + field_item= (Item_field *) (args[i]->real_item()); + add_key_equal_fields(join, key_fields, *and_level, this, + field_item, equal_func, args, + 1, usable_tables, sargables); } } +} - /* If item is of type 'field op field/constant' add it to key_fields */ - if (cond->type() != Item::FUNC_ITEM) - return; - Item_func *cond_func= (Item_func*) cond; - switch (cond_func->select_optimize()) { - case Item_func::OPTIMIZE_NONE: - break; - case Item_func::OPTIMIZE_KEY: + +void +Item_func_in::add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) +{ + if (is_local_field(args[0]) && !(used_tables() & OUTER_REF_TABLE_BIT)) + { + DBUG_ASSERT(arg_count != 2); + add_key_equal_fields(join, key_fields, *and_level, this, + (Item_field*) (args[0]->real_item()), false, + args + 1, arg_count - 1, usable_tables, sargables); + } +} + + +void +Item_func_ne::add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) +{ + if (!(used_tables() & OUTER_REF_TABLE_BIT)) { - Item **values; /* - Build list of possible keys for 'a BETWEEN low AND high'. - It is handled similar to the equivalent condition - 'a >= low AND a <= high': + QQ: perhaps test for !is_local_field(args[1]) is not really needed here. + Other comparison functions, e.g. Item_func_le, Item_func_gt, etc, + do not have this test. See Item_bool_func2::add_key_field_optimize_op(). + Check with the optimizer team. */ - if (cond_func->functype() == Item_func::BETWEEN) - { - Item_field *field_item; - bool equal_func= FALSE; - uint num_values= 2; - values= cond_func->arguments(); + if (is_local_field(args[0]) && !is_local_field(args[1])) + add_key_equal_fields(join, key_fields, *and_level, this, + (Item_field*) (args[0]->real_item()), false, + &args[1], 1, usable_tables, sargables); + /* + QQ: perhaps test for !is_local_field(args[0]) is not really needed here. + */ + if (is_local_field(args[1]) && !is_local_field(args[0])) + add_key_equal_fields(join, key_fields, *and_level, this, + (Item_field*) (args[1]->real_item()), false, + &args[0], 1, usable_tables, sargables); + } +} - bool binary_cmp= (values[0]->real_item()->type() == Item::FIELD_ITEM) - ? ((Item_field*)values[0]->real_item())->field->binary() - : TRUE; - /* - Additional optimization: If 'low = high': - Handle as if the condition was "t.key = low". - */ - if (!((Item_func_between*)cond_func)->negated && - values[1]->eq(values[2], binary_cmp)) - { - equal_func= TRUE; - num_values= 1; - } +void +Item_func_like::add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) +{ + if (is_local_field(args[0]) && with_sargable_pattern()) + { + /* + SELECT * FROM t1 WHERE field LIKE const_pattern + const_pattern starts with a non-wildcard character + */ + add_key_equal_fields(join, key_fields, *and_level, this, + (Item_field*) args[0]->real_item(), false, + args + 1, 1, usable_tables, sargables); + } +} - /* - Append keys for 'field <cmp> value[]' if the - condition is of the form:: - '<field> BETWEEN value[1] AND value[2]' - */ - if (is_local_field(values[0])) - { - field_item= (Item_field *) (values[0]->real_item()); - add_key_equal_fields(join, key_fields, *and_level, cond_func, - field_item, equal_func, &values[1], - num_values, usable_tables, sargables); - } - /* - Append keys for 'value[0] <cmp> field' if the - condition is of the form: - 'value[0] BETWEEN field1 AND field2' - */ - for (uint i= 1; i <= num_values; i++) - { - if (is_local_field(values[i])) - { - field_item= (Item_field *) (values[i]->real_item()); - add_key_equal_fields(join, key_fields, *and_level, cond_func, - field_item, equal_func, values, - 1, usable_tables, sargables); - } - } - } // if ( ... Item_func::BETWEEN) - // IN, NE - else if (is_local_field (cond_func->key_item()) && - !(cond_func->used_tables() & OUTER_REF_TABLE_BIT)) - { - values= cond_func->arguments()+1; - if (cond_func->functype() == Item_func::NE_FUNC && - is_local_field (cond_func->arguments()[1])) - values--; - DBUG_ASSERT(cond_func->functype() != Item_func::IN_FUNC || - cond_func->argument_count() != 2); - add_key_equal_fields(join, key_fields, *and_level, cond_func, - (Item_field*) (cond_func->key_item()->real_item()), - 0, values, - cond_func->argument_count()-1, - usable_tables, sargables); - } - break; +void +Item_bool_func2::add_key_fields_optimize_op(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, + table_map usable_tables, + SARGABLE_PARAM **sargables, + bool equal_func) +{ + /* If item is of type 'field op field/constant' add it to key_fields */ + if (is_local_field(args[0])) + { + add_key_equal_fields(join, key_fields, *and_level, this, + (Item_field*) args[0]->real_item(), equal_func, + args + 1, 1, usable_tables, sargables); } - case Item_func::OPTIMIZE_OP: + if (is_local_field(args[1])) { - bool equal_func=(cond_func->functype() == Item_func::EQ_FUNC || - cond_func->functype() == Item_func::EQUAL_FUNC); + add_key_equal_fields(join, key_fields, *and_level, this, + (Item_field*) args[1]->real_item(), equal_func, + args, 1, usable_tables, sargables); + } +} - if (is_local_field (cond_func->arguments()[0])) - { - add_key_equal_fields(join, key_fields, *and_level, cond_func, - (Item_field*) (cond_func->arguments()[0])-> - real_item(), - equal_func, - cond_func->arguments()+1, 1, usable_tables, - sargables); - } - if (is_local_field (cond_func->arguments()[1]) && - cond_func->functype() != Item_func::LIKE_FUNC) - { - add_key_equal_fields(join, key_fields, *and_level, cond_func, - (Item_field*) (cond_func->arguments()[1])-> - real_item(), - equal_func, - cond_func->arguments(),1,usable_tables, - sargables); - } - break; + +void +Item_func_null_predicate::add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, + table_map usable_tables, + SARGABLE_PARAM **sargables) +{ + /* column_name IS [NOT] NULL */ + if (is_local_field(args[0]) && !(used_tables() & OUTER_REF_TABLE_BIT)) + { + Item *tmp= new (join->thd->mem_root) Item_null(join->thd); + if (unlikely(!tmp)) // Should never be true + return; + add_key_equal_fields(join, key_fields, *and_level, this, + (Item_field*) args[0]->real_item(), + functype() == Item_func::ISNULL_FUNC, + &tmp, 1, usable_tables, sargables); } - case Item_func::OPTIMIZE_NULL: - /* column_name IS [NOT] NULL */ - if (is_local_field (cond_func->arguments()[0]) && - !(cond_func->used_tables() & OUTER_REF_TABLE_BIT)) +} + + +void +Item_equal::add_key_fields(JOIN *join, KEY_FIELD **key_fields, + uint *and_level, table_map usable_tables, + SARGABLE_PARAM **sargables) +{ + Item *const_item2= get_const(); + Item_equal_fields_iterator it(*this); + if (const_item2) + { + + /* + For each field field1 from item_equal consider the equality + field1=const_item as a condition allowing an index access of the table + with field1 by the keys value of field1. + */ + while (it++) { - Item *tmp=new Item_null; - if (unlikely(!tmp)) // Should never be true - return; - add_key_equal_fields(join, key_fields, *and_level, cond_func, - (Item_field*) (cond_func->arguments()[0])-> - real_item(), - cond_func->functype() == Item_func::ISNULL_FUNC, - &tmp, 1, usable_tables, sargables); + Field *equal_field= it.get_curr_field(); + add_key_field(join, key_fields, *and_level, this, equal_field, + TRUE, &const_item2, 1, usable_tables, sargables); } - break; - case Item_func::OPTIMIZE_EQUAL: - Item_equal *item_equal= (Item_equal *) cond; - Item *const_item= item_equal->get_const(); - Item_equal_fields_iterator it(*item_equal); - if (const_item) - { - /* - For each field field1 from item_equal consider the equality - field1=const_item as a condition allowing an index access of the table - with field1 by the keys value of field1. - */ - while (it++) + } + else + { + /* + Consider all pairs of different fields included into item_equal. + For each of them (field1, field1) consider the equality + field1=field2 as a condition allowing an index access of the table + with field1 by the keys value of field2. + */ + Item_equal_fields_iterator fi(*this); + while (fi++) + { + Field *field= fi.get_curr_field(); + Item *item; + while ((item= it++)) { Field *equal_field= it.get_curr_field(); - add_key_field(join, key_fields, *and_level, cond_func, equal_field, - TRUE, &const_item, 1, usable_tables, sargables); - } - } - else - { - /* - Consider all pairs of different fields included into item_equal. - For each of them (field1, field1) consider the equality - field1=field2 as a condition allowing an index access of the table - with field1 by the keys value of field2. - */ - Item_equal_fields_iterator fi(*item_equal); - while (fi++) - { - Field *field= fi.get_curr_field(); - Item *item; - while ((item= it++)) + if (!field->eq(equal_field)) { - Field *equal_field= it.get_curr_field(); - if (!field->eq(equal_field)) - { - add_key_field(join, key_fields, *and_level, cond_func, field, - TRUE, &item, 1, usable_tables, - sargables); - } + add_key_field(join, key_fields, *and_level, this, field, + TRUE, &item, 1, usable_tables, + sargables); } - it.rewind(); } + it.rewind(); } - break; } } @@ -4702,7 +5145,8 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array, KEY_FIELD *key_field) uint key_parts= form->actual_n_key_parts(keyinfo); for (uint part=0 ; part < key_parts ; part++) { - if (field->eq(form->key_info[key].key_part[part].field)) + if (field->eq(form->key_info[key].key_part[part].field) && + field->can_optimize_keypart_ref(key_field->cond, key_field->val)) { if (add_keyuse(keyuse_array, key_field, key, part)) return TRUE; @@ -4713,6 +5157,8 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array, KEY_FIELD *key_field) (key_field->optimize & KEY_OPTIMIZE_EQ) && key_field->val->used_tables()) { + if (!field->can_optimize_hash_join(key_field->cond, key_field->val)) + return false; /* If a key use is extracted from an equi-join predicate then it is added not only as a key use for every index whose component can @@ -4727,8 +5173,6 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array, KEY_FIELD *key_field) } -#define FT_KEYPART (MAX_REF_PARTS+10) - static bool add_ft_keys(DYNAMIC_ARRAY *keyuse_array, JOIN_TAB *stat,COND *cond,table_map usable_tables) @@ -4744,7 +5188,7 @@ add_ft_keys(DYNAMIC_ARRAY *keyuse_array, Item_func::Functype functype= func->functype(); if (functype == Item_func::FT_FUNC) cond_func=(Item_func_match *)cond; - else if (func->arg_count == 2) + else if (func->argument_count() == 2) { Item *arg0=(Item *)(func->arguments()[0]), *arg1=(Item *)(func->arguments()[1]); @@ -4808,8 +5252,8 @@ sort_keyuse(KEYUSE *a,KEYUSE *b) if (a->keypart != b->keypart) return (int) (a->keypart - b->keypart); // Place const values before other ones - if ((res= test((a->used_tables & ~OUTER_REF_TABLE_BIT)) - - test((b->used_tables & ~OUTER_REF_TABLE_BIT)))) + if ((res= MY_TEST((a->used_tables & ~OUTER_REF_TABLE_BIT)) - + MY_TEST((b->used_tables & ~OUTER_REF_TABLE_BIT)))) return res; /* Place rows that are not 'OPTIMIZE_REF_OR_NULL' first */ return (int) ((a->optimize & KEY_OPTIMIZE_REF_OR_NULL) - @@ -4883,8 +5327,8 @@ static void add_key_fields_for_nj(JOIN *join, TABLE_LIST *nested_join_table, tables |= table->table->map; } if (nested_join_table->on_expr) - add_key_fields(join, end, and_level, nested_join_table->on_expr, tables, - sargables); + nested_join_table->on_expr->add_key_fields(join, end, and_level, tables, + sargables); } @@ -4944,7 +5388,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, uint and_level,i; KEY_FIELD *key_fields, *end, *field; uint sz; - uint m= max(select_lex->max_equal_elems,1); + uint m= MY_MAX(select_lex->max_equal_elems,1); DBUG_ENTER("update_ref_and_keys"); DBUG_PRINT("enter", ("normal_tables: %llx", normal_tables)); @@ -4990,7 +5434,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, can be not more than select_lex->max_equal_elems such substitutions. */ - sz= max(sizeof(KEY_FIELD),sizeof(SARGABLE_PARAM))* + sz= MY_MAX(sizeof(KEY_FIELD),sizeof(SARGABLE_PARAM))* ((sel->cond_count*2 + sel->between_count)*m+1); if (!(key_fields=(KEY_FIELD*) thd->alloc(sz))) DBUG_RETURN(TRUE); /* purecov: inspected */ @@ -5001,14 +5445,16 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, /* set a barrier for the array of SARGABLE_PARAM */ (*sargables)[0].field= 0; - if (my_init_dynamic_array(keyuse,sizeof(KEYUSE),20,64)) + if (my_init_dynamic_array2(keyuse, sizeof(KEYUSE), + thd->alloc(sizeof(KEYUSE) * 20), 20, 64, + MYF(MY_THREAD_SPECIFIC))) DBUG_RETURN(TRUE); if (cond) { KEY_FIELD *saved_field= field; - add_key_fields(join_tab->join, &end, &and_level, cond, normal_tables, - sargables); + cond->add_key_fields(join_tab->join, &end, &and_level, normal_tables, + sargables); for (; field != end ; field++) { @@ -5031,9 +5477,10 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, into account as well. */ if (*join_tab[i].on_expr_ref) - add_key_fields(join_tab->join, &end, &and_level, - *join_tab[i].on_expr_ref, - join_tab[i].table->map, sargables); + (*join_tab[i].on_expr_ref)->add_key_fields(join_tab->join, &end, + &and_level, + join_tab[i].table->map, + sargables); } /* Process ON conditions for the nested joins */ @@ -5101,7 +5548,8 @@ static bool sort_and_filter_keyuse(THD *thd, DYNAMIC_ARRAY *keyuse, { if (!use->is_for_hash_join()) { - if (!use->used_tables && use->optimize != KEY_OPTIMIZE_REF_OR_NULL) + if (!(use->used_tables & ~OUTER_REF_TABLE_BIT) && + use->optimize != KEY_OPTIMIZE_REF_OR_NULL) use->table->const_key_parts[use->key]|= use->keypart_map; if (use->keypart != FT_KEYPART) { @@ -5172,7 +5620,7 @@ static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array) DBUG_ASSERT(tablenr != Table_map_iterator::BITMAP_END); TABLE *tmp_table=join->table[tablenr]; if (tmp_table) // already created - keyuse->ref_table_rows= max(tmp_table->file->stats.records, 100); + keyuse->ref_table_rows= MY_MAX(tmp_table->file->stats.records, 100); } } /* @@ -5268,7 +5716,7 @@ is_indexed_agg_distinct(JOIN *join, List<Item_field> *out_args) Item_field* item= static_cast<Item_field*>(expr->real_item()); if (out_args) - out_args->push_back(item); + out_args->push_back(item, join->thd->mem_root); cur_aggdistinct_fields.set_bit(item->field->field_index); result= true; @@ -5364,6 +5812,7 @@ void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key) join->positions[idx].table= table; join->positions[idx].key=key; join->positions[idx].records_read=1.0; /* This is a const table */ + join->positions[idx].cond_selectivity= 1.0; join->positions[idx].ref_depend_map= 0; // join->positions[idx].loosescan_key= MAX_KEY; /* Not a LooseScan */ @@ -5383,12 +5832,39 @@ void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key) } -/* Estimate of the number matching candidates in the joined table */ +/* + Estimate how many records we will get if we read just this table and apply + a part of WHERE that can be checked for it. + + @detail + Estimate how many records we will get if we + - read the given table with its "independent" access method (either quick + select or full table/index scan), + - apply the part of WHERE that refers only to this table. + + @seealso + table_cond_selectivity() produces selectivity of condition that is checked + after joining rows from this table to rows from preceding tables. +*/ inline -ha_rows matching_candidates_in_table(JOIN_TAB *s, bool with_found_constraint) +double matching_candidates_in_table(JOIN_TAB *s, bool with_found_constraint, + uint use_cond_selectivity) { - ha_rows records= s->found_records; + ha_rows records; + double dbl_records; + + if (use_cond_selectivity > 1) + { + TABLE *table= s->table; + double sel= table->cond_selectivity; + double table_records= table->stat_records(); + dbl_records= table_records * sel; + return dbl_records; + } + + records = s->found_records; + /* If there is a filtering condition on the table (i.e. ref analyzer found at least one "table.keyXpartY= exprZ", where exprZ refers only to tables @@ -5408,7 +5884,8 @@ ha_rows matching_candidates_in_table(JOIN_TAB *s, bool with_found_constraint) if (s->table->quick_condition_rows != s->found_records) records= s->table->quick_condition_rows; - return records; + dbl_records= records; + return dbl_records; } @@ -5451,6 +5928,7 @@ best_access_path(JOIN *join, POSITION *loose_scan_pos) { THD *thd= join->thd; + uint use_cond_selectivity= thd->variables.optimizer_use_condition_selectivity; KEYUSE *best_key= 0; uint best_max_key_part= 0; my_bool found_constraint= 0; @@ -5592,7 +6070,7 @@ best_access_path(JOIN *join, } else { - found_constraint= test(found_part); + found_constraint= MY_TEST(found_part); loose_scan_opt.check_ref_access_part1(s, key, start_key, found_part); /* Check if we found full key */ @@ -5601,7 +6079,7 @@ best_access_path(JOIN *join, { /* use eq key */ max_key_part= (uint) ~0; if ((key_flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME || - test(key_flags & HA_EXT_NOSAME)) + MY_TEST(key_flags & HA_EXT_NOSAME)) { tmp = prev_record_reads(join->positions, idx, found_ref); records=1.0; @@ -5637,8 +6115,7 @@ best_access_path(JOIN *join, } else { - uint key_parts= table->actual_n_key_parts(keyinfo); - if (!(records=keyinfo->rec_per_key[key_parts-1])) + if (!(records= keyinfo->actual_rec_per_key(key_parts-1))) { /* Prefer longer keys */ records= ((double) s->records / (double) rec * @@ -5675,7 +6152,7 @@ best_access_path(JOIN *join, tmp= table->file->keyread_time(key, 1, (ha_rows) tmp); else tmp= table->file->read_time(key, 1, - (ha_rows) min(tmp,s->worst_seeks)); + (ha_rows) MY_MIN(tmp,s->worst_seeks)); tmp*= record_count; } } @@ -5688,7 +6165,7 @@ best_access_path(JOIN *join, */ if ((found_part & 1) && (!(table->file->index_flags(key, 0, 0) & HA_ONLY_WHOLE_INDEX) || - found_part == PREV_BITS(uint,keyinfo->key_parts))) + found_part == PREV_BITS(uint,keyinfo->user_defined_key_parts))) { max_key_part= max_part_bit(found_part); /* @@ -5732,14 +6209,14 @@ best_access_path(JOIN *join, */ if (table->quick_keys.is_set(key) && !found_ref && //(C1) table->quick_key_parts[key] == max_key_part && //(C2) - table->quick_n_ranges[key] == 1+test(ref_or_null_part)) //(C3) + table->quick_n_ranges[key] == 1 + MY_TEST(ref_or_null_part)) //(C3) { tmp= records= (double) table->quick_rows[key]; } else { /* Check if we have statistic about the distribution */ - if ((records= keyinfo->rec_per_key[max_key_part-1])) + if ((records= keyinfo->actual_rec_per_key(max_key_part-1))) { /* Fix for the case where the index statistics is too @@ -5782,7 +6259,7 @@ best_access_path(JOIN *join, */ double rec_per_key; if (!(rec_per_key=(double) - keyinfo->rec_per_key[keyinfo->key_parts-1])) + keyinfo->rec_per_key[keyinfo->user_defined_key_parts-1])) rec_per_key=(double) s->records/rec+1; if (!s->records) @@ -5792,10 +6269,10 @@ best_access_path(JOIN *join, else { double a=s->records*0.01; - if (keyinfo->key_parts > 1) + if (keyinfo->user_defined_key_parts > 1) tmp= (max_key_part * (rec_per_key - a) + - a*keyinfo->key_parts - rec_per_key)/ - (keyinfo->key_parts-1); + a*keyinfo->user_defined_key_parts - rec_per_key)/ + (keyinfo->user_defined_key_parts-1); else tmp= a; set_if_bigger(tmp,1.0); @@ -5826,8 +6303,8 @@ best_access_path(JOIN *join, table->quick_key_parts[key] <= max_key_part && const_part & ((key_part_map)1 << table->quick_key_parts[key]) && - table->quick_n_ranges[key] == 1 + test(ref_or_null_part & - const_part) && + table->quick_n_ranges[key] == 1 + MY_TEST(ref_or_null_part & + const_part) && records > (double) table->quick_rows[key]) { tmp= records= (double) table->quick_rows[key]; @@ -5840,7 +6317,7 @@ best_access_path(JOIN *join, tmp= table->file->keyread_time(key, 1, (ha_rows) tmp); else tmp= table->file->read_time(key, 1, - (ha_rows) min(tmp,s->worst_seeks)); + (ha_rows) MY_MIN(tmp,s->worst_seeks)); tmp*= record_count; } else @@ -5882,7 +6359,8 @@ best_access_path(JOIN *join, { double join_sel= 0.1; /* Estimate the cost of the hash join access to the table */ - ha_rows rnd_records= matching_candidates_in_table(s, found_constraint); + double rnd_records= matching_candidates_in_table(s, found_constraint, + use_cond_selectivity); tmp= s->quick ? s->quick->read_time : s->scan_time(); tmp+= (s->records - rnd_records)/(double) TIME_FOR_COMPARE; @@ -5894,7 +6372,7 @@ best_access_path(JOIN *join, best_time= tmp + (record_count*join_sel) / TIME_FOR_COMPARE * rnd_records; best= tmp; - records= rows2double(rnd_records); + records= rnd_records; best_key= hj_start_key; best_ref_depends_map= 0; best_uses_jbuf= TRUE; @@ -5941,7 +6419,8 @@ best_access_path(JOIN *join, !(s->table->force_index && best_key && !s->quick) && // (4) !(best_key && s->table->pos_in_table_list->jtbm_subselect)) // (5) { // Check full join - ha_rows rnd_records= matching_candidates_in_table(s, found_constraint); + double rnd_records= matching_candidates_in_table(s, found_constraint, + use_cond_selectivity); /* Range optimizer never proposes a RANGE if it isn't better @@ -5969,7 +6448,11 @@ best_access_path(JOIN *join, else { /* Estimate cost of reading table. */ - tmp= s->scan_time(); + if (s->table->force_index && !best_key) // index scan + tmp= s->table->file->read_time(s->ref.key, 1, s->records); + else // table scan + tmp= s->scan_time(); + if ((s->table->map & join->outer_join) || disable_jbuf) // Can't use join cache { /* @@ -6014,12 +6497,12 @@ best_access_path(JOIN *join, will ensure that this will be used */ best= tmp; - records= rows2double(rnd_records); + records= rnd_records; best_key= 0; /* range/index_merge/ALL/index access method are "independent", so: */ best_ref_depends_map= 0; - best_uses_jbuf= test(!disable_jbuf && !((s->table->map & - join->outer_join))); + best_uses_jbuf= MY_TEST(!disable_jbuf && !((s->table->map & + join->outer_join))); } } @@ -6085,6 +6568,7 @@ static void choose_initial_table_order(JOIN *join) TABLE_LIST *emb_subq; JOIN_TAB **tab= join->best_ref + join->const_tables; JOIN_TAB **tabs_end= tab + join->table_count - join->const_tables; + DBUG_ENTER("choose_initial_table_order"); /* Find where the top-level JOIN_TABs end and subquery JOIN_TABs start */ for (; tab != tabs_end; tab++) { @@ -6094,7 +6578,7 @@ static void choose_initial_table_order(JOIN *join) uint n_subquery_tabs= tabs_end - tab; if (!n_subquery_tabs) - return; + DBUG_VOID_RETURN; /* Copy the subquery JOIN_TABs to a separate array */ JOIN_TAB *subquery_tabs[MAX_TABLES]; @@ -6149,6 +6633,7 @@ static void choose_initial_table_order(JOIN *join) subq_tab += n_subquery_tables - 1; } } + DBUG_VOID_RETURN; } @@ -6180,7 +6665,9 @@ choose_plan(JOIN *join, table_map join_tables) { uint search_depth= join->thd->variables.optimizer_search_depth; uint prune_level= join->thd->variables.optimizer_prune_level; - bool straight_join= test(join->select_options & SELECT_STRAIGHT_JOIN); + uint use_cond_selectivity= + join->thd->variables.optimizer_use_condition_selectivity; + bool straight_join= MY_TEST(join->select_options & SELECT_STRAIGHT_JOIN); DBUG_ENTER("choose_plan"); join->cur_embedding_map= 0; @@ -6235,15 +6722,19 @@ choose_plan(JOIN *join, table_map join_tables) the greedy version. Will be removed when greedy_search is approved. */ join->best_read= DBL_MAX; - if (find_best(join, join_tables, join->const_tables, 1.0, 0.0)) + if (find_best(join, join_tables, join->const_tables, 1.0, 0.0, + use_cond_selectivity)) + { DBUG_RETURN(TRUE); + } } else { if (search_depth == 0) /* Automatically determine a reasonable value for 'search_depth' */ search_depth= determine_search_depth(join); - if (greedy_search(join, join_tables, search_depth, prune_level)) + if (greedy_search(join, join_tables, search_depth, prune_level, + use_cond_selectivity)) DBUG_RETURN(TRUE); } } @@ -6517,6 +7008,8 @@ optimize_straight_join(JOIN *join, table_map join_tables) bool disable_jbuf= join->thd->variables.join_cache_level == 0; double record_count= 1.0; double read_time= 0.0; + uint use_cond_selectivity= + join->thd->variables.optimizer_use_condition_selectivity; POSITION loose_scan_pos; for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++) @@ -6533,6 +7026,11 @@ optimize_straight_join(JOIN *join, table_map join_tables) &loose_scan_pos); join_tables&= ~(s->table->map); + double pushdown_cond_selectivity= 1.0; + if (use_cond_selectivity > 1) + pushdown_cond_selectivity= table_cond_selectivity(join, idx, s, + join_tables); + join->positions[idx].cond_selectivity= pushdown_cond_selectivity; ++idx; } @@ -6541,7 +7039,7 @@ optimize_straight_join(JOIN *join, table_map join_tables) read_time+= record_count; // We have to make a temp table memcpy((uchar*) join->best_positions, (uchar*) join->positions, sizeof(POSITION)*idx); - join->record_count= record_count; + join->join_record_count= record_count; join->best_read= read_time - 0.001; } @@ -6620,6 +7118,8 @@ optimize_straight_join(JOIN *join, table_map join_tables) @param search_depth controlls the exhaustiveness of the search @param prune_level the pruning heuristics that should be applied during search + @param use_cond_selectivity specifies how the selectivity of the conditions + pushed to a table should be taken into account @retval FALSE ok @@ -6631,7 +7131,8 @@ static bool greedy_search(JOIN *join, table_map remaining_tables, uint search_depth, - uint prune_level) + uint prune_level, + uint use_cond_selectivity) { double record_count= 1.0; double read_time= 0.0; @@ -6642,7 +7143,6 @@ greedy_search(JOIN *join, JOIN_TAB *best_table; // the next plan node to be added to the curr QEP // ==join->tables or # tables in the sj-mat nest we're optimizing uint n_tables __attribute__((unused)); - DBUG_ENTER("greedy_search"); /* number of tables that remain to be optimized */ @@ -6657,7 +7157,8 @@ greedy_search(JOIN *join, /* Find the extension of the current QEP with the lowest cost */ join->best_read= DBL_MAX; if (best_extension_by_limited_search(join, remaining_tables, idx, record_count, - read_time, search_depth, prune_level)) + read_time, search_depth, prune_level, + use_cond_selectivity)) DBUG_RETURN(TRUE); /* 'best_read < DBL_MAX' means that optimizer managed to find @@ -6879,17 +7380,18 @@ void JOIN::get_prefix_cost_and_fanout(uint n_tables, double JOIN::get_examined_rows() { - ha_rows examined_rows; + double examined_rows; double prev_fanout= 1; - JOIN_TAB *tab= first_breadth_first_tab(this, WALK_OPTIMIZATION_TABS); + JOIN_TAB *tab= first_breadth_first_optimization_tab(); JOIN_TAB *prev_tab= tab; examined_rows= tab->get_examined_rows(); - while ((tab= next_breadth_first_tab(this, WALK_OPTIMIZATION_TABS, tab))) + while ((tab= next_breadth_first_tab(first_breadth_first_optimization_tab(), + top_table_access_tabs_count, tab))) { prev_fanout *= prev_tab->records_read; - examined_rows+= (ha_rows) (tab->get_examined_rows() * prev_fanout); + examined_rows+= tab->get_examined_rows() * prev_fanout; prev_tab= tab; } return examined_rows; @@ -6897,6 +7399,350 @@ double JOIN::get_examined_rows() /** + @brief + Get the selectivity of equalities between columns when joining a table + + @param join The optimized join + @param idx The number of tables in the evaluated partual join + @param s The table to be joined for evaluation + @param rem_tables The bitmap of tables to be joined later + @param keyparts The number of key parts to used when joining s + @param ref_keyuse_steps Array of references to keyuses employed to join s +*/ + +static +double table_multi_eq_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s, + table_map rem_tables, uint keyparts, + uint16 *ref_keyuse_steps) +{ + double sel= 1.0; + COND_EQUAL *cond_equal= join->cond_equal; + + if (!cond_equal || !cond_equal->current_level.elements) + return sel; + + if (!s->keyuse) + return sel; + + Item_equal *item_equal; + List_iterator_fast<Item_equal> it(cond_equal->current_level); + TABLE *table= s->table; + table_map table_bit= table->map; + POSITION *pos= &join->positions[idx]; + + while ((item_equal= it++)) + { + /* + Check whether we need to take into account the selectivity of + multiple equality item_equal. If this is the case multiply + the current value of sel by this selectivity + */ + table_map used_tables= item_equal->used_tables(); + if (!(used_tables & table_bit)) + continue; + if (item_equal->get_const()) + continue; + + bool adjust_sel= FALSE; + Item_equal_fields_iterator fi(*item_equal); + while((fi++) && !adjust_sel) + { + Field *fld= fi.get_curr_field(); + if (fld->table->map != table_bit) + continue; + if (pos->key == 0) + adjust_sel= TRUE; + else + { + uint i; + KEYUSE *keyuse= pos->key; + uint key= keyuse->key; + for (i= 0; i < keyparts; i++) + { + if (i > 0) + keyuse+= ref_keyuse_steps[i-1]; + uint fldno; + if (is_hash_join_key_no(key)) + fldno= keyuse->keypart; + else + fldno= table->key_info[key].key_part[i].fieldnr - 1; + if (fld->field_index == fldno) + break; + } + keyuse= pos->key; + + if (i == keyparts) + { + /* + Field fld is included in multiple equality item_equal + and is not a part of the ref key. + The selectivity of the multiple equality must be taken + into account unless one of the ref arguments is + equal to fld. + */ + adjust_sel= TRUE; + for (uint j= 0; j < keyparts && adjust_sel; j++) + { + if (j > 0) + keyuse+= ref_keyuse_steps[j-1]; + Item *ref_item= keyuse->val; + if (ref_item->real_item()->type() == Item::FIELD_ITEM) + { + Item_field *field_item= (Item_field *) (ref_item->real_item()); + if (item_equal->contains(field_item->field)) + adjust_sel= FALSE; + } + } + } + } + } + if (adjust_sel) + { + /* + If ref == 0 and there are no fields in the multiple equality + item_equal that belong to the tables joined prior to s + then the selectivity of multiple equality will be set to 1.0. + */ + double eq_fld_sel= 1.0; + fi.rewind(); + while ((fi++)) + { + double curr_eq_fld_sel; + Field *fld= fi.get_curr_field(); + if (!(fld->table->map & ~(table_bit | rem_tables))) + continue; + curr_eq_fld_sel= get_column_avg_frequency(fld) / + fld->table->stat_records(); + if (curr_eq_fld_sel < 1.0) + set_if_bigger(eq_fld_sel, curr_eq_fld_sel); + } + sel*= eq_fld_sel; + } + } + return sel; +} + + +/** + @brief + Get the selectivity of conditions when joining a table + + @param join The optimized join + @param s The table to be joined for evaluation + @param rem_tables The bitmap of tables to be joined later + + @detail + Get selectivity of conditions that can be applied when joining this table + with previous tables. + + For quick selects and full table scans, selectivity of COND(this_table) + is accounted for in matching_candidates_in_table(). Here, we only count + selectivity of COND(this_table, previous_tables). + + For other access methods, we need to calculate selectivity of the whole + condition, "COND(this_table) AND COND(this_table, previous_tables)". + + @retval + selectivity of the conditions imposed on the rows of s +*/ + +static +double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s, + table_map rem_tables) +{ + uint16 ref_keyuse_steps[MAX_REF_PARTS - 1]; + Field *field; + TABLE *table= s->table; + MY_BITMAP *read_set= table->read_set; + double sel= s->table->cond_selectivity; + POSITION *pos= &join->positions[idx]; + uint keyparts= 0; + uint found_part_ref_or_null= 0; + + if (pos->key != 0) + { + /* + A ref access or hash join is used for this table. ref access is created + from + + tbl.keypart1=expr1 AND tbl.keypart2=expr2 AND ... + + and it will only return rows for which this condition is satisified. + Suppose, certain expr{i} is a constant. Since ref access only returns + rows that satisfy + + tbl.keypart{i}=const (*) + + then selectivity of this equality should not be counted in return value + of this function. This function uses the value of + + table->cond_selectivity=selectivity(COND(tbl)) (**) + + as a starting point. This value includes selectivity of equality (*). We + should somehow discount it. + + Looking at calculate_cond_selectivity_for_table(), one can see that that + the value is not necessarily a direct multiplicand in + table->cond_selectivity + + There are three possible ways to discount + 1. There is a potential range access on t.keypart{i}=const. + (an important special case: the used ref access has a const prefix for + which a range estimate is available) + + 2. The field has a histogram. field[x]->cond_selectivity has the data. + + 3. Use index stats on this index: + rec_per_key[key_part+1]/rec_per_key[key_part] + + (TODO: more details about the "t.key=othertable.col" case) + */ + KEYUSE *keyuse= pos->key; + KEYUSE *prev_ref_keyuse= keyuse; + uint key= keyuse->key; + + /* + Check if we have a prefix of key=const that matches a quick select. + */ + if (!is_hash_join_key_no(key)) + { + table_map quick_key_map= (table_map(1) << table->quick_key_parts[key]) - 1; + if (table->quick_rows[key] && + !(quick_key_map & ~table->const_key_parts[key])) + { + /* + Ok, there is an equality for each of the key parts used by the + quick select. This means, quick select's estimate can be reused to + discount the selectivity of a prefix of a ref access. + */ + for (; quick_key_map & 1 ; quick_key_map>>= 1) + { + while (keyuse->table == table && keyuse->key == key && + keyuse->keypart == keyparts) + { + keyuse++; + } + keyparts++; + } + sel /= (double)table->quick_rows[key] / (double) table->stat_records(); + } + } + + /* + Go through the "keypart{N}=..." equalities and find those that were + already taken into account in table->cond_selectivity. + */ + keyuse= pos->key; + keyparts=0; + while (keyuse->table == table && keyuse->key == key) + { + if (!(keyuse->used_tables & (rem_tables | table->map))) + { + if (are_tables_local(s, keyuse->val->used_tables())) + { + if (is_hash_join_key_no(key)) + { + if (keyparts == keyuse->keypart) + keyparts++; + } + else + { + if (keyparts == keyuse->keypart && + !((keyuse->val->used_tables()) & ~pos->ref_depend_map) && + !(found_part_ref_or_null & keyuse->optimize)) + { + /* Found a KEYUSE object that will be used by ref access */ + keyparts++; + found_part_ref_or_null|= keyuse->optimize & ~KEY_OPTIMIZE_EQ; + } + } + + if (keyparts > keyuse->keypart) + { + /* Ok this is the keyuse that will be used for ref access */ + uint fldno; + if (is_hash_join_key_no(key)) + fldno= keyuse->keypart; + else + fldno= table->key_info[key].key_part[keyparts-1].fieldnr - 1; + if (keyuse->val->const_item()) + { + if (table->field[fldno]->cond_selectivity > 0) + { + sel /= table->field[fldno]->cond_selectivity; + set_if_smaller(sel, 1.0); + } + /* + TODO: we could do better here: + 1. cond_selectivity might be =1 (the default) because quick + select on some index prevented us from analyzing + histogram for this column. + 2. we could get an estimate through this? + rec_per_key[key_part-1] / rec_per_key[key_part] + */ + } + if (keyparts > 1) + { + ref_keyuse_steps[keyparts-2]= keyuse - prev_ref_keyuse; + prev_ref_keyuse= keyuse; + } + } + } + } + keyuse++; + } + } + else + { + /* + The table is accessed with full table scan, or quick select. + Selectivity of COND(table) is already accounted for in + matching_candidates_in_table(). + */ + sel= 1; + } + + /* + If the field f from the table is equal to a field from one the + earlier joined tables then the selectivity of the range conditions + over the field f must be discounted. + + We need to discount selectivity only if we're using ref-based + access method (and have sel!=1). + If we use ALL/range/index_merge, then sel==1, and no need to discount. + */ + if (pos->key != NULL) + { + for (Field **f_ptr=table->field ; (field= *f_ptr) ; f_ptr++) + { + if (!bitmap_is_set(read_set, field->field_index) || + !field->next_equal_field) + continue; + for (Field *next_field= field->next_equal_field; + next_field != field; + next_field= next_field->next_equal_field) + { + if (!(next_field->table->map & rem_tables) && next_field->table != table) + { + if (field->cond_selectivity > 0) + { + sel/= field->cond_selectivity; + set_if_smaller(sel, 1.0); + } + break; + } + } + } + } + + sel*= table_multi_eq_cond_selectivity(join, idx, s, rem_tables, + keyparts, ref_keyuse_steps); + + return sel; +} + + +/** Find a good, possibly optimal, query execution plan (QEP) by a possibly exhaustive search. @@ -7006,6 +7852,8 @@ double JOIN::get_examined_rows() @param prune_level pruning heuristics that should be applied during optimization (values: 0 = EXHAUSTIVE, 1 = PRUNE_BY_TIME_OR_ROWS) + @param use_cond_selectivity specifies how the selectivity of the conditions + pushed to a table should be taken into account @retval FALSE ok @@ -7020,12 +7868,21 @@ best_extension_by_limited_search(JOIN *join, double record_count, double read_time, uint search_depth, - uint prune_level) + uint prune_level, + uint use_cond_selectivity) { DBUG_ENTER("best_extension_by_limited_search"); THD *thd= join->thd; - if (thd->killed) // Abort + + DBUG_EXECUTE_IF("show_explain_probe_best_ext_lim_search", + if (dbug_user_var_equals_int(thd, + "show_explain_probe_select_id", + join->select_lex->select_number)) + dbug_serve_apcs(thd, 1); + ); + + if (thd->check_killed()) // Abort DBUG_RETURN(TRUE); DBUG_EXECUTE("opt", print_plan(join, idx, read_time, record_count, idx, @@ -7067,9 +7924,11 @@ best_extension_by_limited_search(JOIN *join, best_access_path(join, s, remaining_tables, idx, disable_jbuf, record_count, join->positions + idx, &loose_scan_pos); - /* Compute the cost of extending the plan with 's' */ - - current_record_count= record_count * position->records_read; + /* Compute the cost of extending the plan with 's', avoid overflow */ + if (position->records_read < DBL_MAX / record_count) + current_record_count= record_count * position->records_read; + else + current_record_count= DBL_MAX; current_read_time=read_time + position->read_time + current_record_count / (double) TIME_FOR_COMPARE; @@ -7123,16 +7982,25 @@ best_extension_by_limited_search(JOIN *join, } } + double pushdown_cond_selectivity= 1.0; + if (use_cond_selectivity > 1) + pushdown_cond_selectivity= table_cond_selectivity(join, idx, s, + remaining_tables & + ~real_table_bit); + join->positions[idx].cond_selectivity= pushdown_cond_selectivity; + double partial_join_cardinality= current_record_count * + pushdown_cond_selectivity; if ( (search_depth > 1) && (remaining_tables & ~real_table_bit) & allowed_tables ) { /* Recursively expand the current partial plan */ swap_variables(JOIN_TAB*, join->best_ref[idx], *pos); if (best_extension_by_limited_search(join, remaining_tables & ~real_table_bit, idx + 1, - current_record_count, + partial_join_cardinality, current_read_time, search_depth - 1, - prune_level)) + prune_level, + use_cond_selectivity)) DBUG_RETURN(TRUE); swap_variables(JOIN_TAB*, join->best_ref[idx], *pos); } @@ -7144,13 +8012,17 @@ best_extension_by_limited_search(JOIN *join, if (join->sort_by_table && join->sort_by_table != join->positions[join->const_tables].table->table) - /* We have to make a temp table */ + /* + We may have to make a temp table, note that this is only a + heuristic since we cannot know for sure at this point. + Hence it may be wrong. + */ current_read_time+= current_record_count; if (current_read_time < join->best_read) { memcpy((uchar*) join->best_positions, (uchar*) join->positions, sizeof(POSITION) * (idx + 1)); - join->record_count= current_record_count; + join->join_record_count= partial_join_cardinality; join->best_read= current_read_time - 0.001; } DBUG_EXECUTE("opt", print_plan(join, idx+1, @@ -7178,11 +8050,11 @@ best_extension_by_limited_search(JOIN *join, */ static bool find_best(JOIN *join,table_map rest_tables,uint idx,double record_count, - double read_time) + double read_time, uint use_cond_selectivity) { DBUG_ENTER("find_best"); THD *thd= join->thd; - if (thd->killed) + if (thd->check_killed()) DBUG_RETURN(TRUE); if (!rest_tables) { @@ -7229,20 +8101,30 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count, advance_sj_state(join, rest_tables, idx, ¤t_record_count, ¤t_read_time, &loose_scan_pos); - if (best_record_count > current_record_count || + double pushdown_cond_selectivity= 1.0; + if (use_cond_selectivity > 1) + pushdown_cond_selectivity= table_cond_selectivity(join, idx, s, + rest_tables & + ~real_table_bit); + join->positions[idx].cond_selectivity= pushdown_cond_selectivity; + double partial_join_cardinality= current_record_count * + pushdown_cond_selectivity; + + if (best_record_count > partial_join_cardinality || best_read_time > current_read_time || (idx == join->const_tables && s->table == join->sort_by_table)) { - if (best_record_count >= current_record_count && + if (best_record_count >= partial_join_cardinality && best_read_time >= current_read_time && (!(s->key_dependent & rest_tables) || records < 2.0)) { - best_record_count=current_record_count; + best_record_count= partial_join_cardinality; best_read_time=current_read_time; } swap_variables(JOIN_TAB*, join->best_ref[idx], *pos); if (find_best(join,rest_tables & ~real_table_bit,idx+1, - current_record_count,current_read_time)) + partial_join_cardinality,current_read_time, + use_cond_selectivity)) DBUG_RETURN(TRUE); swap_variables(JOIN_TAB*, join->best_ref[idx], *pos); } @@ -7563,21 +8445,9 @@ prev_record_reads(POSITION *positions, uint idx, table_map found_ref) Enumerate join tabs in breadth-first fashion, including const tables. */ -JOIN_TAB *first_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind) +static JOIN_TAB *next_breadth_first_tab(JOIN_TAB *first_top_tab, + uint n_top_tabs_count, JOIN_TAB *tab) { - /* There's always one (i.e. first) table */ - return (tabs_kind == WALK_EXECUTION_TABS)? join->join_tab: - join->table_access_tabs; -} - - -JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind, - JOIN_TAB *tab) -{ - JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, tabs_kind); - const uint n_top_tabs_count= (tabs_kind == WALK_EXECUTION_TABS)? - join->top_join_tab_count: - join->top_table_access_tabs_count; if (!tab->bush_root_tab) { /* We're at top level. Get the next top-level tab */ @@ -7619,6 +8489,41 @@ JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind, } +/* + Enumerate JOIN_TABs in "EXPLAIN order". This order + - const tabs are included + - we enumerate "optimization tabs". + - +*/ + +JOIN_TAB *first_explain_order_tab(JOIN* join) +{ + JOIN_TAB* tab; + tab= join->table_access_tabs; + return (tab->bush_children) ? tab->bush_children->start : tab; +} + + +JOIN_TAB *next_explain_order_tab(JOIN* join, JOIN_TAB* tab) +{ + /* If we're inside SJM nest and have reached its end, get out */ + if (tab->last_leaf_in_bush) + return tab->bush_root_tab; + + /* Move to next tab in the array we're traversing */ + tab++; + + if (tab == join->table_access_tabs + join->top_join_tab_count) + return NULL; /* Outside SJM nest and reached EOF */ + + if (tab->bush_children) + return tab->bush_children->start; + + return tab; +} + + + JOIN_TAB *first_top_level_tab(JOIN *join, enum enum_with_const_tables const_tbls) { JOIN_TAB *tab= join->join_tab; @@ -7634,7 +8539,8 @@ JOIN_TAB *first_top_level_tab(JOIN *join, enum enum_with_const_tables const_tbls JOIN_TAB *next_top_level_tab(JOIN *join, JOIN_TAB *tab) { - tab= next_breadth_first_tab(join, WALK_EXECUTION_TABS, tab); + tab= next_breadth_first_tab(join->first_breadth_first_execution_tab(), + join->top_join_tab_count, tab); if (tab && tab->bush_root_tab) tab= NULL; return tab; @@ -7827,13 +8733,13 @@ get_best_combination(JOIN *join) fix_semijoin_strategies_for_picked_join_order(join); JOIN_TAB_RANGE *root_range; - if (!(root_range= new JOIN_TAB_RANGE)) + if (!(root_range= new (thd->mem_root) JOIN_TAB_RANGE)) DBUG_RETURN(TRUE); root_range->start= join->join_tab; /* root_range->end will be set later */ join->join_tab_ranges.empty(); - if (join->join_tab_ranges.push_back(root_range)) + if (join->join_tab_ranges.push_back(root_range, thd->mem_root)) DBUG_RETURN(TRUE); JOIN_TAB *sjm_nest_end= NULL; @@ -7852,7 +8758,7 @@ get_best_combination(JOIN *join) 1. Put into main join order a JOIN_TAB that represents a lookup or scan in the temptable. */ - bzero(j, sizeof(JOIN_TAB)); + bzero((void*)j, sizeof(JOIN_TAB)); j->join= join; j->table= NULL; //temporary way to tell SJM tables from others. j->ref.key = -1; @@ -7864,7 +8770,9 @@ get_best_combination(JOIN *join) sub-order */ SJ_MATERIALIZATION_INFO *sjm= cur_pos->table->emb_sj_nest->sj_mat_info; - j->records= j->records_read= (ha_rows)(sjm->is_sj_scan? sjm->rows : 1); + j->records_read= (sjm->is_sj_scan? sjm->rows : 1); + j->records= (ha_rows) j->records_read; + j->cond_selectivity= 1.0; JOIN_TAB *jt; JOIN_TAB_RANGE *jt_range; if (!(jt= (JOIN_TAB*)join->thd->alloc(sizeof(JOIN_TAB)*sjm->tables)) || @@ -7872,7 +8780,7 @@ get_best_combination(JOIN *join) DBUG_RETURN(TRUE); jt_range->start= jt; jt_range->end= jt + sjm->tables; - join->join_tab_ranges.push_back(jt_range); + join->join_tab_ranges.push_back(jt_range, join->thd->mem_root); j->bush_children= jt_range; sjm_nest_end= jt + sjm->tables; sjm_nest_root= j; @@ -7921,7 +8829,8 @@ get_best_combination(JOIN *join) Save records_read in JOIN_TAB so that select_describe()/etc don't have to access join->best_positions[]. */ - j->records_read= (ha_rows)join->best_positions[tablenr].records_read; + j->records_read= join->best_positions[tablenr].records_read; + j->cond_selectivity= join->best_positions[tablenr].cond_selectivity; join->map2table[j->table->tablenr]= j; /* If we've reached the end of sjm nest, switch back to main sequence */ @@ -7940,7 +8849,7 @@ get_best_combination(JOIN *join) { if (j->bush_children) j= j->bush_children->start; - + used_tables|= j->table->map; if (j->type != JT_CONST && j->type != JT_SYSTEM) { @@ -7960,6 +8869,7 @@ get_best_combination(JOIN *join) join->table_access_tabs= join->join_tab; join->top_table_access_tabs_count= join->top_join_tab_count; + update_depend_map(join); DBUG_RETURN(0); } @@ -8033,12 +8943,13 @@ static bool create_hj_key_for_table(JOIN *join, JOIN_TAB *join_tab, !(key_part_info = (KEY_PART_INFO *) thd->alloc(sizeof(KEY_PART_INFO)* key_parts))) DBUG_RETURN(TRUE); - keyinfo->usable_key_parts= keyinfo->key_parts = key_parts; - keyinfo->ext_key_parts= keyinfo->key_parts; + keyinfo->usable_key_parts= keyinfo->user_defined_key_parts = key_parts; + keyinfo->ext_key_parts= keyinfo->user_defined_key_parts; keyinfo->key_part= key_part_info; keyinfo->key_length=0; keyinfo->algorithm= HA_KEY_ALG_UNDEF; keyinfo->flags= HA_GENERATED_KEY; + keyinfo->is_statistics_from_stat_tables= FALSE; keyinfo->name= (char *) "$hj"; keyinfo->rec_per_key= (ulong*) thd->calloc(sizeof(ulong)*key_parts); if (!keyinfo->rec_per_key) @@ -8083,7 +8994,7 @@ static bool create_hj_key_for_table(JOIN *join, JOIN_TAB *join_tab, keyuse++; } while (keyuse->table == table && keyuse->is_for_hash_join()); - keyinfo->ext_key_parts= keyinfo->key_parts; + keyinfo->ext_key_parts= keyinfo->user_defined_key_parts; keyinfo->ext_key_flags= keyinfo->flags; keyinfo->ext_key_part_map= 0; @@ -8107,7 +9018,7 @@ static bool are_tables_local(JOIN_TAB *jtab, table_map used_tables) table_map local_tables= jtab->emb_sj_nest->nested_join->used_tables | jtab->join->const_table_map | OUTER_REF_TABLE_BIT; - return !test(used_tables & ~local_tables); + return !MY_TEST(used_tables & ~local_tables); } /* @@ -8211,6 +9122,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, j->ref.null_rejecting= 0; j->ref.disable_cache= FALSE; j->ref.null_ref_part= NO_REF_PART; + j->ref.const_ref_part_map= 0; keyuse=org_keyuse; store_key **ref_key= j->ref.key_copy; @@ -8241,12 +9153,19 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, !are_tables_local(j, keyuse->val->used_tables())) keyuse++; /* Skip other parts */ - uint maybe_null= test(keyinfo->key_part[i].null_bit); + uint maybe_null= MY_TEST(keyinfo->key_part[i].null_bit); j->ref.items[i]=keyuse->val; // Save for cond removal j->ref.cond_guards[i]= keyuse->cond_guard; if (keyuse->null_rejecting) j->ref.null_rejecting|= (key_part_map)1 << i; keyuse_uses_no_tables= keyuse_uses_no_tables && !keyuse->used_tables; + /* + Todo: we should remove this check for thd->lex->describe on the next + line. With SHOW EXPLAIN code, EXPLAIN printout code no longer depends + on it. However, removing the check caused change in lots of query + plans! Does the optimizer depend on the contents of + table_ref->key_copy ? If yes, do we produce incorrect EXPLAINs? + */ if (!keyuse->val->used_tables() && !thd->lex->describe) { // Compare against constant store_key_item tmp(thd, @@ -8259,6 +9178,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, if (thd->is_fatal_error) DBUG_RETURN(TRUE); tmp.copy(); + j->ref.const_ref_part_map |= key_part_map(1) << i ; } else *ref_key++= get_store_key(thd, @@ -8284,10 +9204,10 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, ulong key_flags= j->table->actual_key_flags(keyinfo); if (j->type == JT_CONST) j->table->const_table= 1; - else if (!((keyparts == keyinfo->key_parts && + else if (!((keyparts == keyinfo->user_defined_key_parts && ((key_flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME)) || - (keyparts > keyinfo->key_parts && // true only for extended keys - test(key_flags & HA_EXT_NOSAME) && + (keyparts > keyinfo->user_defined_key_parts && // true only for extended keys + MY_TEST(key_flags & HA_EXT_NOSAME) && keyparts == keyinfo->ext_key_parts)) || null_ref_key) { @@ -8376,6 +9296,7 @@ JOIN::make_simple_join(JOIN *parent, TABLE *temp_table) !(parent->join_tab_reexec= (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB)))) DBUG_RETURN(TRUE); /* purecov: inspected */ + // psergey-todo: here, save the pointer for original join_tabs. join_tab= parent->join_tab_reexec; table= &parent->table_reexec[0]; parent->table_reexec[0]= temp_table; table_count= top_join_tab_count= 1; @@ -8429,7 +9350,7 @@ JOIN::make_simple_join(JOIN *parent, TABLE *temp_table) functions are handled. */ // the temporary table was explicitly requested - DBUG_ASSERT(test(select_options & OPTION_BUFFER_RESULT)); + DBUG_ASSERT(MY_TEST(select_options & OPTION_BUFFER_RESULT)); // the temporary table does not have a grouping expression DBUG_ASSERT(!temp_table->group); } @@ -8439,7 +9360,7 @@ JOIN::make_simple_join(JOIN *parent, TABLE *temp_table) row_limit= unit->select_limit_cnt; do_send_rows= row_limit ? 1 : 0; - bzero(join_tab, sizeof(JOIN_TAB)); + bzero((void*)join_tab, sizeof(JOIN_TAB)); join_tab->table=temp_table; join_tab->set_select_cond(NULL, __LINE__); join_tab->type= JT_ALL; /* Map through all records */ @@ -8450,6 +9371,20 @@ JOIN::make_simple_join(JOIN *parent, TABLE *temp_table) join_tab->read_first_record= join_init_read_record; join_tab->join= this; join_tab->ref.key_parts= 0; + + uint select_nr= select_lex->select_number; + if (select_nr == INT_MAX) + { + /* this is a fake_select_lex of a union */ + select_nr= select_lex->master_unit()->first_select()->select_number; + join_tab->tracker= thd->lex->explain->get_union(select_nr)-> + get_tmptable_read_tracker(); + } + else + { + join_tab->tracker= thd->lex->explain->get_select(select_nr)-> + get_using_temporary_read_tracker(); + } bzero((char*) &join_tab->read_record,sizeof(join_tab->read_record)); temp_table->status=0; temp_table->null_row=0; @@ -8464,7 +9399,7 @@ inline void add_cond_and_fix(THD *thd, Item **e1, Item *e2) if (!e2) return; Item *res; - if ((res= new Item_cond_and(*e1, e2))) + if ((res= new (thd->mem_root) Item_cond_and(thd, *e1, e2))) { res->fix_fields(thd, 0); res->update_used_tables(); @@ -8563,9 +9498,8 @@ static void add_not_null_conds(JOIN *join) UPDATE t1 SET t1.f2=(SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1); not_null_item is the t1.f1, but it's referred_tab is 0. */ - if (!referred_tab) - continue; - if (!(notnull= new Item_func_isnotnull(not_null_item))) + if (!(notnull= new (join->thd->mem_root) + Item_func_isnotnull(join->thd, item))) DBUG_VOID_RETURN; /* We need to do full fix_fields() call here in order to have correct @@ -8575,16 +9509,19 @@ static void add_not_null_conds(JOIN *join) */ if (notnull->fix_fields(join->thd, ¬null)) DBUG_VOID_RETURN; + DBUG_EXECUTE("where",print_where(notnull, - referred_tab->table->alias.c_ptr(), - QT_ORDINARY);); + (referred_tab ? + referred_tab->table->alias.c_ptr() : + "outer_ref_cond"), + QT_ORDINARY);); if (!tab->first_inner) - { - COND *new_cond= referred_tab->join == join ? + { + COND *new_cond= (referred_tab && referred_tab->join == join) ? referred_tab->select_cond : join->outer_ref_cond; add_cond_and_fix(join->thd, &new_cond, notnull); - if (referred_tab->join == join) + if (referred_tab && referred_tab->join == join) referred_tab->set_select_cond(new_cond, __LINE__); else join->outer_ref_cond= new_cond; @@ -8621,14 +9558,15 @@ static void add_not_null_conds(JOIN *join) */ static COND* -add_found_match_trig_cond(JOIN_TAB *tab, COND *cond, JOIN_TAB *root_tab) +add_found_match_trig_cond(THD *thd, JOIN_TAB *tab, COND *cond, + JOIN_TAB *root_tab) { COND *tmp; DBUG_ASSERT(cond != 0); if (tab == root_tab) return cond; - if ((tmp= add_found_match_trig_cond(tab->first_upper, cond, root_tab))) - tmp= new Item_func_trig_cond(tmp, &tab->found); + if ((tmp= add_found_match_trig_cond(thd, tab->first_upper, cond, root_tab))) + tmp= new (thd->mem_root) Item_func_trig_cond(thd, tmp, &tab->found); if (tmp) { tmp->quick_fix_field(); @@ -8715,7 +9653,8 @@ make_outerjoin_info(JOIN *join) } } - for (JOIN_TAB *tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab; + for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); + tab; tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS)) { TABLE *table= tab->table; @@ -8795,10 +9734,6 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) { /* there may be a select without a cond. */ if (join->table_count > 1) cond->update_used_tables(); // Tablenr may have changed - if (join->const_tables == join->table_count && - thd->lex->current_select->master_unit() == - &thd->lex->unit) // not upper level SELECT - join->const_table_map|=RAND_TABLE_BIT; /* Extract expressions that depend on constant tables @@ -8876,7 +9811,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) table_map current_map; i= join->const_tables; for (tab= first_depth_first_tab(join); tab; - tab= next_depth_first_tab(join, tab), i++) + tab= next_depth_first_tab(join, tab)) { bool is_hj; /* @@ -8919,7 +9854,8 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) if (tab->type == JT_REF && tab->quick && (((uint) tab->ref.key == tab->quick->index && tab->ref.key_length < tab->quick->max_used_key_length) || - tab->table->intersect_keys.is_set(tab->ref.key))) + (!is_hash_join_key_no(tab->ref.key) && + tab->table->intersect_keys.is_set(tab->ref.key)))) { /* Range uses longer key; Use this instead of ref on key */ tab->type=JT_ALL; @@ -8945,8 +9881,8 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) if (tab->bush_children) { // Reached the materialization tab - tmp= make_cond_after_sjm(cond, cond, save_used_tables, used_tables, - /*inside_or_clause=*/FALSE); + tmp= make_cond_after_sjm(thd, cond, cond, save_used_tables, + used_tables, /*inside_or_clause=*/FALSE); used_tables= save_used_tables | used_tables; save_used_tables= 0; } @@ -8990,7 +9926,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) below to check if we should use 'quick' instead. */ DBUG_PRINT("info", ("Item_int")); - tmp= new Item_int((longlong) 1,1); // Always true + tmp= new (thd->mem_root) Item_int(thd, (longlong) 1, 1); // Always true } } @@ -9017,23 +9953,23 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) Because of QUICK_GROUP_MIN_MAX_SELECT there may be a select without a cond, so neutralize the hack above. */ - if (!(tmp= add_found_match_trig_cond(first_inner_tab, tmp, 0))) + COND *tmp_cond; + if (!(tmp_cond= add_found_match_trig_cond(thd, first_inner_tab, tmp, + 0))) DBUG_RETURN(1); - sel->cond= tmp; - tab->set_select_cond(tmp, __LINE__); + sel->cond= tmp_cond; + tab->set_select_cond(tmp_cond, __LINE__); /* Push condition to storage engine if this is enabled and the condition is not guarded */ if (tab->table) { tab->table->file->pushed_cond= NULL; - if (((thd->variables.optimizer_switch & - OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN) || - (tab->table->file->ha_table_flags() & - HA_MUST_USE_TABLE_CONDITION_PUSHDOWN)) && + if ((tab->table->file->ha_table_flags() & + HA_CAN_TABLE_CONDITION_PUSHDOWN) && !first_inner_tab) { COND *push_cond= - make_cond_for_table(thd, tmp, current_map, current_map, + make_cond_for_table(thd, tmp_cond, current_map, current_map, -1, FALSE, FALSE); if (push_cond) { @@ -9093,9 +10029,14 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) Check again if we should use an index. We could have used an column from a previous table in the index if we are using limit and this is the first table + + (1) - Don't switch the used index if we are using semi-join + LooseScan on this table. Using different index will not + produce the desired ordering and de-duplication. */ if (!tab->table->is_filled_at_execution() && + !tab->loosescan_match_tab && // (1) ((cond && (!tab->keys.is_subset(tab->const_keys) && i > 0)) || (!tab->const_keys.is_clear_all() && i == join->const_tables && join->unit->select_limit_cnt < @@ -9104,7 +10045,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) { /* Join with outer join condition */ COND *orig_cond=sel->cond; - sel->cond= and_conds(sel->cond, *tab->on_expr_ref); + sel->cond= and_conds(thd, sel->cond, *tab->on_expr_ref); /* We can't call sel->cond->fix_fields, @@ -9123,7 +10064,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) OPTION_FOUND_ROWS ? HA_POS_ERROR : join->unit->select_limit_cnt), 0, - FALSE) < 0) + FALSE, FALSE) < 0) { /* Before reporting "Impossible WHERE" for the whole query @@ -9137,7 +10078,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) OPTION_FOUND_ROWS ? HA_POS_ERROR : join->unit->select_limit_cnt),0, - FALSE) < 0) + FALSE, FALSE) < 0) DBUG_RETURN(1); // Impossible WHERE } else @@ -9218,17 +10159,19 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) if (*join_tab->on_expr_ref) { JOIN_TAB *cond_tab= join_tab->first_inner; - COND *tmp= make_cond_for_table(thd, *join_tab->on_expr_ref, - join->const_table_map, - (table_map) 0, -1, FALSE, FALSE); - if (!tmp) + COND *tmp_cond= make_cond_for_table(thd, *join_tab->on_expr_ref, + join->const_table_map, + (table_map) 0, -1, FALSE, FALSE); + if (!tmp_cond) continue; - tmp= new Item_func_trig_cond(tmp, &cond_tab->not_null_compl); - if (!tmp) + tmp_cond= new (thd->mem_root) Item_func_trig_cond(thd, tmp_cond, + &cond_tab->not_null_compl); + if (!tmp_cond) DBUG_RETURN(1); - tmp->quick_fix_field(); - cond_tab->select_cond= !cond_tab->select_cond ? tmp : - new Item_cond_and(cond_tab->select_cond,tmp); + tmp_cond->quick_fix_field(); + cond_tab->select_cond= !cond_tab->select_cond ? tmp_cond : + new (thd->mem_root) Item_cond_and(thd, cond_tab->select_cond, + tmp_cond); if (!cond_tab->select_cond) DBUG_RETURN(1); cond_tab->select_cond->quick_fix_field(); @@ -9260,23 +10203,26 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) start_from= tab->bush_root_tab? tab->bush_root_tab->bush_children->start : join->join_tab + join->const_tables; - for (JOIN_TAB *tab= start_from; tab <= last_tab; tab++) + for (JOIN_TAB *inner_tab= start_from; + inner_tab <= last_tab; + inner_tab++) { - DBUG_ASSERT(tab->table); - current_map= tab->table->map; + DBUG_ASSERT(inner_tab->table); + current_map= inner_tab->table->map; used_tables2|= current_map; /* psergey: have put the -1 below. It's bad, will need to fix it. */ COND *tmp_cond= make_cond_for_table(thd, on_expr, used_tables2, - current_map, /*(tab - first_tab)*/ -1, + current_map, + /*(inner_tab - first_tab)*/ -1, FALSE, FALSE); bool is_sjm_lookup_tab= FALSE; - if (tab->bush_children) + if (inner_tab->bush_children) { /* - 'tab' is an SJ-Materialization tab, i.e. we have a join order - like this: + 'inner_tab' is an SJ-Materialization tab, i.e. we have a join + order like this: ot1 sjm_tab LEFT JOIN ot2 ot3 ^ ^ @@ -9296,24 +10242,27 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) would be true anyway. */ SJ_MATERIALIZATION_INFO *sjm= - tab->bush_children->start->emb_sj_nest->sj_mat_info; + inner_tab->bush_children->start->emb_sj_nest->sj_mat_info; if (sjm->is_used && !sjm->is_sj_scan) is_sjm_lookup_tab= TRUE; } - if (tab == first_inner_tab && tab->on_precond && !is_sjm_lookup_tab) - add_cond_and_fix(thd, &tmp_cond, tab->on_precond); + if (inner_tab == first_inner_tab && inner_tab->on_precond && + !is_sjm_lookup_tab) + add_cond_and_fix(thd, &tmp_cond, inner_tab->on_precond); if (tmp_cond && !is_sjm_lookup_tab) { - JOIN_TAB *cond_tab= tab < first_inner_tab ? first_inner_tab : tab; - Item **sel_cond_ref= tab < first_inner_tab ? - &first_inner_tab->on_precond : - &tab->select_cond; + JOIN_TAB *cond_tab= (inner_tab < first_inner_tab ? + first_inner_tab : inner_tab); + Item **sel_cond_ref= (inner_tab < first_inner_tab ? + &first_inner_tab->on_precond : + &inner_tab->select_cond); /* First add the guards for match variables of all embedding outer join operations. */ - if (!(tmp_cond= add_found_match_trig_cond(cond_tab->first_inner, + if (!(tmp_cond= add_found_match_trig_cond(thd, + cond_tab->first_inner, tmp_cond, first_inner_tab))) DBUG_RETURN(1); @@ -9322,7 +10271,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) the null complemented row. */ DBUG_PRINT("info", ("Item_func_trig_cond")); - tmp_cond= new Item_func_trig_cond(tmp_cond, + tmp_cond= new (thd->mem_root) Item_func_trig_cond(thd, tmp_cond, &first_inner_tab-> not_null_compl); DBUG_PRINT("info", ("Item_func_trig_cond 0x%lx", @@ -9333,7 +10282,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) DBUG_PRINT("info", ("Item_cond_and")); *sel_cond_ref= !(*sel_cond_ref) ? tmp_cond : - new Item_cond_and(*sel_cond_ref, tmp_cond); + new (thd->mem_root) Item_cond_and(thd, *sel_cond_ref, tmp_cond); DBUG_PRINT("info", ("Item_cond_and 0x%lx", (ulong)(*sel_cond_ref))); if (!(*sel_cond_ref)) @@ -9346,6 +10295,8 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) } first_inner_tab= first_inner_tab->first_upper; } + if (!tab->bush_children) + i++; } } DBUG_RETURN(0); @@ -9382,8 +10333,8 @@ uint get_next_field_for_derived_key_simple(uchar *arg) return (uint) (-1); TABLE *table= keyuse->table; uint key= keyuse->key; - uint fldno= keyuse->keypart; - for ( ; + uint fldno= keyuse->keypart; + for ( ; keyuse->table == table && keyuse->key == key && keyuse->keypart == fldno; keyuse++) ; @@ -9425,9 +10376,9 @@ bool generate_derived_keys_for_table(KEYUSE *keyuse, uint count, uint keys) { KEYUSE *save_first_keyuse= first_keyuse; if (table->check_tmp_key(table->s->keys, parts, - get_next_field_for_derived_key_simple, + get_next_field_for_derived_key_simple, (uchar *) &first_keyuse)) - + { first_keyuse= save_first_keyuse; if (table->add_tmp_key(table->s->keys, parts, @@ -9531,26 +10482,26 @@ bool generate_derived_keys(DYNAMIC_ARRAY *keyuse_array) void JOIN::drop_unused_derived_keys() { JOIN_TAB *tab; - for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); + for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab; tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS)) { - TABLE *table=tab->table; - if (!table) + TABLE *tmp_tbl= tab->table; + if (!tmp_tbl) continue; - if (!table->pos_in_table_list->is_materialized_derived()) + if (!tmp_tbl->pos_in_table_list->is_materialized_derived()) continue; - if (table->max_keys > 1 && !tab->is_ref_for_hash_join()) - table->use_index(tab->ref.key); - if (table->s->keys) + if (tmp_tbl->max_keys > 1 && !tab->is_ref_for_hash_join()) + tmp_tbl->use_index(tab->ref.key); + if (tmp_tbl->s->keys) { if (tab->ref.key >= 0 && tab->ref.key < MAX_KEY) tab->ref.key= 0; else - table->s->keys= 0; + tmp_tbl->s->keys= 0; } - tab->keys= (key_map) (table->s->keys ? 1 : 0); + tab->keys= (key_map) (tmp_tbl->s->keys ? 1 : 0); } } @@ -9821,7 +10772,7 @@ end_sj_materialize(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) if (item->is_null()) DBUG_RETURN(NESTED_LOOP_OK); } - fill_record(thd, table->field, sjm->sjm_table_cols, TRUE, FALSE); + fill_record(thd, table, table->field, sjm->sjm_table_cols, TRUE, FALSE); if (thd->is_error()) DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ if ((error= table->file->ha_write_tmp_row(table->record[0]))) @@ -9981,7 +10932,7 @@ uint check_join_cache_usage(JOIN_TAB *tab, uint table_index, JOIN_TAB *prev_tab) { - COST_VECT cost; + Cost_estimate cost; uint flags= 0; ha_rows rows= 0; uint bufsz= 4096; @@ -10115,10 +11066,10 @@ uint check_join_cache_usage(JOIN_TAB *tab, if (cache_level == 1) prev_cache= 0; if ((tab->cache= new JOIN_CACHE_BNL(join, tab, prev_cache)) && - ((options & SELECT_DESCRIBE) || !tab->cache->init())) + !tab->cache->init(options & SELECT_DESCRIBE)) { tab->icp_other_tables_ok= FALSE; - return (2-test(!prev_cache)); + return (2 - MY_TEST(!prev_cache)); } goto no_join_cache; case JT_SYSTEM: @@ -10129,7 +11080,7 @@ uint check_join_cache_usage(JOIN_TAB *tab, goto no_join_cache; if (tab->ref.is_access_triggered()) goto no_join_cache; - + if (!tab->is_ref_for_hash_join() && !no_bka_cache) { flags= HA_MRR_NO_NULL_ENDPOINTS | HA_MRR_SINGLE_POINT; @@ -10150,10 +11101,10 @@ uint check_join_cache_usage(JOIN_TAB *tab, if (cache_level == 3) prev_cache= 0; if ((tab->cache= new JOIN_CACHE_BNLH(join, tab, prev_cache)) && - ((options & SELECT_DESCRIBE) || !tab->cache->init())) + !tab->cache->init(options & SELECT_DESCRIBE)) { tab->icp_other_tables_ok= FALSE; - return (4-test(!prev_cache)); + return (4 - MY_TEST(!prev_cache)); } goto no_join_cache; } @@ -10171,8 +11122,8 @@ uint check_join_cache_usage(JOIN_TAB *tab, if (cache_level == 5) prev_cache= 0; if ((tab->cache= new JOIN_CACHE_BKA(join, tab, flags, prev_cache)) && - ((options & SELECT_DESCRIBE) || !tab->cache->init())) - return (6-test(!prev_cache)); + !tab->cache->init(options & SELECT_DESCRIBE)) + return (6 - MY_TEST(!prev_cache)); goto no_join_cache; } else @@ -10180,10 +11131,10 @@ uint check_join_cache_usage(JOIN_TAB *tab, if (cache_level == 7) prev_cache= 0; if ((tab->cache= new JOIN_CACHE_BKAH(join, tab, flags, prev_cache)) && - ((options & SELECT_DESCRIBE) || !tab->cache->init())) + !tab->cache->init(options & SELECT_DESCRIBE)) { tab->idx_cond_fact_out= FALSE; - return (8-test(!prev_cache)); + return (8 - MY_TEST(!prev_cache)); } goto no_join_cache; } @@ -10240,7 +11191,7 @@ void check_join_cache_usage_for_tables(JOIN *join, ulonglong options, JOIN_TAB *tab; JOIN_TAB *prev_tab; - for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); + for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab; tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS)) { @@ -10248,7 +11199,7 @@ void check_join_cache_usage_for_tables(JOIN *join, ulonglong options, } uint idx= join->const_tables; - for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); + for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab; tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS)) { @@ -10278,7 +11229,7 @@ restart: no_jbuf_after, idx, prev_tab); - tab->use_join_cache= test(tab->used_join_cache_level); + tab->use_join_cache= MY_TEST(tab->used_join_cache_level); /* psergey-merge: todo: raise the question that this is really stupid that we can first allocate a join buffer, then decide not to use it and free @@ -10298,6 +11249,87 @@ restart: } } +/** + Remove pushdown conditions that are already checked by the scan phase + of BNL/BNLH joins. + + @note + If the single-table condition for this table will be used by a + blocked join to pre-filter this table's rows, there is no need + to re-check the same single-table condition for each joined record. + + This method removes from JOIN_TAB::select_cond and JOIN_TAB::select::cond + all top-level conjuncts that also appear in in JOIN_TAB::cache_select::cond. +*/ + +void JOIN_TAB::remove_redundant_bnl_scan_conds() +{ + if (!(select_cond && cache_select && cache && + (cache->get_join_alg() == JOIN_CACHE::BNL_JOIN_ALG || + cache->get_join_alg() == JOIN_CACHE::BNLH_JOIN_ALG))) + return; + + /* + select->cond is not processed separately. This method assumes it is always + the same as select_cond. + */ + DBUG_ASSERT(!select || !select->cond || + (select->cond == select_cond)); + + if (is_cond_and(select_cond)) + { + List_iterator<Item> pushed_cond_li(*((Item_cond*) select_cond)->argument_list()); + Item *pushed_item; + Item_cond_and *reduced_select_cond= new (join->thd->mem_root) + Item_cond_and(join->thd); + + if (is_cond_and(cache_select->cond)) + { + List_iterator<Item> scan_cond_li(*((Item_cond*) cache_select->cond)->argument_list()); + Item *scan_item; + while ((pushed_item= pushed_cond_li++)) + { + bool found_cond= false; + scan_cond_li.rewind(); + while ((scan_item= scan_cond_li++)) + { + if (pushed_item->eq(scan_item, 0)) + { + found_cond= true; + break; + } + } + if (!found_cond) + reduced_select_cond->add(pushed_item, join->thd->mem_root); + } + } + else + { + while ((pushed_item= pushed_cond_li++)) + { + if (!pushed_item->eq(cache_select->cond, 0)) + reduced_select_cond->add(pushed_item, join->thd->mem_root); + } + } + + /* + JOIN_CACHE::check_match uses JOIN_TAB::select->cond instead of + JOIN_TAB::select_cond. set_cond() sets both pointers. + */ + if (reduced_select_cond->argument_list()->is_empty()) + set_cond(NULL); + else if (reduced_select_cond->argument_list()->elements == 1) + set_cond(reduced_select_cond->argument_list()->head()); + else + { + reduced_select_cond->quick_fix_field(); + set_cond(reduced_select_cond); + } + } + else if (select_cond->eq(cache_select->cond, 0)) + set_cond(NULL); +} + /* Plan refinement stage: do various setup things for the executor @@ -10328,7 +11360,7 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) uint i; DBUG_ENTER("make_join_readinfo"); - bool statistics= test(!(join->select_options & SELECT_DESCRIBE)); + bool statistics= MY_TEST(!(join->select_options & SELECT_DESCRIBE)); bool sorted= 1; join->complex_firstmatch_tables= table_map(0); @@ -10368,7 +11400,7 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) check_join_cache_usage_for_tables(join, options, no_jbuf_after); JOIN_TAB *first_tab; - for (tab= first_tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); + for (tab= first_tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab; tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS)) { @@ -10461,10 +11493,10 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) /* These init changes read_record */ if (tab->use_quick == 2) { - join->thd->server_status|=SERVER_QUERY_NO_GOOD_INDEX_USED; + join->thd->set_status_no_good_index_used(); tab->read_first_record= join_init_quick_read_record; if (statistics) - status_var_increment(join->thd->status_var.select_range_check_count); + join->thd->inc_status_select_range_check(); } else { @@ -10475,14 +11507,14 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) if (tab->select && tab->select->quick) { if (statistics) - status_var_increment(join->thd->status_var.select_range_count); + join->thd->inc_status_select_range(); } else { - join->thd->server_status|=SERVER_QUERY_NO_INDEX_USED; + join->thd->set_status_no_index_used(); if (statistics) { - status_var_increment(join->thd->status_var.select_scan_count); + join->thd->inc_status_select_scan(); join->thd->query_plan_flags|= QPLAN_FULL_SCAN; } } @@ -10492,16 +11524,14 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) if (tab->select && tab->select->quick) { if (statistics) - status_var_increment(join->thd->status_var. - select_full_range_join_count); + join->thd->inc_status_select_full_range_join(); } else { - join->thd->server_status|=SERVER_QUERY_NO_INDEX_USED; + join->thd->set_status_no_index_used(); if (statistics) { - status_var_increment(join->thd->status_var. - select_full_join_count); + join->thd->inc_status_select_full_join(); join->thd->query_plan_flags|= QPLAN_FULL_JOIN; } } @@ -10555,6 +11585,15 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) abort(); /* purecov: end */ } + + tab->remove_redundant_bnl_scan_conds(); + DBUG_EXECUTE("where", + char buff[256]; + String str(buff,sizeof(buff),system_charset_info); + str.length(0); + str.append(tab->table? tab->table->alias.c_ptr() :"<no_table_name>"); + str.append(" final_pushdown_cond"); + print_where(tab->select_cond, str.c_ptr_safe(), QT_ORDINARY);); } uint n_top_tables= join->join_tab_ranges.head()->end - join->join_tab_ranges.head()->start; @@ -10637,7 +11676,8 @@ bool error_if_full_join(JOIN *join) if (tab->type == JT_ALL && (!tab->select || !tab->select->quick)) { my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, - ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); + ER_THD(join->thd, + ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); return(1); } } @@ -10688,14 +11728,16 @@ void JOIN_TAB::cleanup() } else { + TABLE_LIST *tmp= table->pos_in_table_list; end_read_record(&read_record); - table->pos_in_table_list->jtbm_subselect->cleanup(); + tmp->jtbm_subselect->cleanup(); /* The above call freed the materializedd temptable. Set it to NULL so that we don't attempt to touch it if JOIN_TAB::cleanup() is invoked multiple times (it may be) */ - table=NULL; + tmp->table= NULL; + table= NULL; } DBUG_VOID_RETURN; } @@ -10706,6 +11748,7 @@ void JOIN_TAB::cleanup() table->reginfo.join_tab= 0; } end_read_record(&read_record); + explain_plan= NULL; DBUG_VOID_RETURN; } @@ -10728,7 +11771,7 @@ double JOIN_TAB::scan_time() } else { - found_records= records= table->file->stats.records; + found_records= records= table->stat_records(); read_time= table->file->scan_time(); /* table->quick_condition_rows has already been set to @@ -10739,7 +11782,7 @@ double JOIN_TAB::scan_time() } else { - found_records= records=table->file->stats.records; + found_records= records=table->stat_records(); read_time= found_records ? (double)found_records: 10.0;// TODO:fix this stub res= read_time; } @@ -10755,9 +11798,9 @@ double JOIN_TAB::scan_time() ha_rows JOIN_TAB::get_examined_rows() { - ha_rows examined_rows; + double examined_rows; - if (select && select->quick) + if (select && select->quick && use_quick != 2) examined_rows= select->quick->records; else if (type == JT_NEXT || type == JT_ALL || type == JT_HASH || type ==JT_HASH_NEXT) @@ -10780,14 +11823,14 @@ ha_rows JOIN_TAB::get_examined_rows() handler->info(HA_STATUS_VARIABLE) has been called in make_join_statistics() */ - examined_rows= table->file->stats.records; + examined_rows= table->stat_records(); } } } else - examined_rows= (ha_rows) records_read; + examined_rows= records_read; - return examined_rows; + return (ha_rows) examined_rows; } @@ -10811,13 +11854,21 @@ bool JOIN_TAB::preread_init() mysql_handle_single_derived(join->thd->lex, derived, DT_CREATE | DT_FILL)) return TRUE; + preread_init_done= TRUE; if (select && select->quick) select->quick->replace_handler(table->file); + DBUG_EXECUTE_IF("show_explain_probe_join_tab_preread", + if (dbug_user_var_equals_int(join->thd, + "show_explain_probe_select_id", + join->select_lex->select_number)) + dbug_serve_apcs(join->thd, 1); + ); + /* init ftfuns for just initialized derived table */ if (table->fulltext_searched) - init_ftfuncs(join->thd, join->select_lex, test(join->order)); + init_ftfuncs(join->thd, join->select_lex, MY_TEST(join->order)); return FALSE; } @@ -10845,7 +11896,7 @@ bool TABLE_REF::tmp_table_index_lookup_init(THD *thd, bool value, uint skip) { - uint tmp_key_parts= tmp_key->key_parts; + uint tmp_key_parts= tmp_key->user_defined_key_parts; uint i; DBUG_ENTER("TABLE_REF::tmp_table_index_lookup_init"); @@ -10873,12 +11924,12 @@ bool TABLE_REF::tmp_table_index_lookup_init(THD *thd, Item *item= it.next(); DBUG_ASSERT(item); items[i]= item; - int null_count= test(cur_key_part->field->real_maybe_null()); + int null_count= MY_TEST(cur_key_part->field->real_maybe_null()); *ref_key= new store_key_item(thd, cur_key_part->field, /* TIMOUR: the NULL byte is taken into account in cur_key_part->store_length, so instead of - cur_ref_buff + test(maybe_null), we could + cur_ref_buff + MY_TEST(maybe_null), we could use that information instead. */ cur_ref_buff + null_count, @@ -10942,7 +11993,7 @@ bool TABLE_REF::is_access_triggered() a correlated subquery itself, but has subqueries, we can free it fully and also free JOINs of all its subqueries. The exception is a subquery in SELECT list, e.g: @n - SELECT a, (select max(b) from t1) group by c @n + SELECT a, (select MY_MAX(b) from t1) group by c @n This subquery will not be evaluated at first sweep and its value will not be inserted into the temporary table. Instead, it's evaluated when selecting from the temporary table. Therefore, it can't be freed @@ -11025,6 +12076,17 @@ void JOIN::cleanup(bool full) { DBUG_ENTER("JOIN::cleanup"); DBUG_PRINT("enter", ("full %u", (uint) full)); + + if (full) + have_query_plan= QEP_DELETED; + + if (original_join_tab) + { + /* Free the original optimized join created for the group_by_handler */ + join_tab= original_join_tab; + original_join_tab= 0; + table_count= original_table_count; + } if (table) { @@ -11044,7 +12106,7 @@ void JOIN::cleanup(bool full) } if (full) { - JOIN_TAB *sort_tab= first_linear_tab(this, WITH_BUSH_ROOTS, + JOIN_TAB *sort_tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); if (pre_sort_join_tab) { @@ -11064,31 +12126,24 @@ void JOIN::cleanup(bool full) w/o tables: they don't have some members initialized and WALK_OPTIMIZATION_TABS may not work correctly for them. */ - enum enum_exec_or_opt tabs_kind; - if (first_breadth_first_tab(this, WALK_OPTIMIZATION_TABS)) - tabs_kind= WALK_OPTIMIZATION_TABS; - else - tabs_kind= WALK_EXECUTION_TABS; if (table_count) { - for (tab= first_breadth_first_tab(this, tabs_kind); tab; - tab= next_breadth_first_tab(this, tabs_kind, tab)) - { + for (tab= first_breadth_first_optimization_tab(); tab; + tab= next_breadth_first_tab(first_breadth_first_optimization_tab(), + top_table_access_tabs_count, tab)) tab->cleanup(); - } - if (tabs_kind == WALK_OPTIMIZATION_TABS && - first_breadth_first_tab(this, WALK_OPTIMIZATION_TABS) != - first_breadth_first_tab(this, WALK_EXECUTION_TABS)) + /* We've walked optimization tabs, do execution ones too. */ + if (first_breadth_first_execution_tab() != + first_breadth_first_optimization_tab()) { - JOIN_TAB *jt= first_breadth_first_tab(this, WALK_EXECUTION_TABS); - /* We've walked optimization tabs. do execution ones too */ - if (jt) - jt->cleanup(); + for (tab= first_breadth_first_execution_tab(); tab; + tab= next_breadth_first_tab(first_breadth_first_execution_tab(), + top_join_tab_count, tab)) + tab->cleanup(); } } cleaned= true; - } else { @@ -11141,6 +12196,9 @@ void JOIN::cleanup(bool full) } tmp_table_param.cleanup(); + delete pushdown_query; + pushdown_query= 0; + if (!join_tab) { List_iterator<TABLE_LIST> li(*join_list); @@ -11290,7 +12348,8 @@ static void update_depend_map_for_order(JOIN *join, ORDER *order) order->used= 0; // Not item_sum(), RAND() and no reference to table outside of sub select if (!(order->depend_map & (OUTER_REF_TABLE_BIT | RAND_TABLE_BIT)) - && !order->item[0]->with_sum_func) + && !order->item[0]->with_sum_func && + join->join_tab) { for (JOIN_TAB **tab=join->map2table; depend_map ; @@ -11318,7 +12377,8 @@ static void update_depend_map_for_order(JOIN *join, ORDER *order) @param cond WHERE statement @param change_list Set to 1 if we should remove things from list. If this is not set, then only simple_order is - calculated. + calculated. This is not set when we + are using ROLLUP @param simple_order Set to 1 if we are only using simple expressions. @@ -11330,34 +12390,48 @@ static ORDER * remove_const(JOIN *join,ORDER *first_order, COND *cond, bool change_list, bool *simple_order) { + *simple_order= 1; if (join->table_count == join->const_tables) return change_list ? 0 : first_order; // No need to sort ORDER *order,**prev_ptr, *tmp_order; - table_map first_table; + table_map UNINIT_VAR(first_table); /* protected by first_is_base_table */ table_map not_const_tables= ~join->const_table_map; table_map ref; bool first_is_base_table= FALSE; DBUG_ENTER("remove_const"); - LINT_INIT(first_table); /* protected by first_is_base_table */ - if (join->join_tab[join->const_tables].table) - { - first_table= join->join_tab[join->const_tables].table->map; - first_is_base_table= TRUE; - } - /* - Cleanup to avoid interference of calls of this function for - ORDER BY and GROUP BY + Join tab is set after make_join_statistics() has been called. + In case of one table with GROUP BY this function is called before + join_tab is set for the GROUP_BY expression */ - for (JOIN_TAB *tab= join->join_tab + join->const_tables; - tab < join->join_tab + join->table_count; - tab++) - tab->cached_eq_ref_table= FALSE; + if (join->join_tab) + { + if (join->join_tab[join->const_tables].table) + { + first_table= join->join_tab[join->const_tables].table->map; + first_is_base_table= TRUE; + } + + /* + Cleanup to avoid interference of calls of this function for + ORDER BY and GROUP BY + */ + for (JOIN_TAB *tab= join->join_tab + join->const_tables; + tab < join->join_tab + join->table_count; + tab++) + tab->cached_eq_ref_table= FALSE; + + *simple_order= *join->join_tab[join->const_tables].on_expr_ref ? 0 : 1; + } + else + { + first_is_base_table= FALSE; + first_table= 0; // Not used, for gcc + } prev_ptr= &first_order; - *simple_order= *join->join_tab[join->const_tables].on_expr_ref ? 0 : 1; /* NOTE: A variable of not_const_tables ^ first_table; breaks gcc 2.7 */ @@ -11387,7 +12461,8 @@ remove_const(JOIN *join,ORDER *first_order, COND *cond, Delay the evaluation of constant ORDER and/or GROUP expressions that contain subqueries until the execution phase. */ - join->exec_const_order_group_cond.push_back(order->item[0]); + join->exec_const_order_group_cond.push_back(order->item[0], + join->thd->mem_root); } DBUG_PRINT("info",("removing: %s", order->item[0]->full_name())); continue; @@ -11403,7 +12478,8 @@ remove_const(JOIN *join,ORDER *first_order, COND *cond, DBUG_PRINT("info",("removing: %s", order->item[0]->full_name())); continue; } - if (first_is_base_table && (ref=order_tables & (not_const_tables ^ first_table))) + if (first_is_base_table && + (ref=order_tables & (not_const_tables ^ first_table))) { if (!(order_tables & first_table) && only_eq_ref_tables(join,first_order, ref)) @@ -11411,7 +12487,50 @@ remove_const(JOIN *join,ORDER *first_order, COND *cond, DBUG_PRINT("info",("removing: %s", order->item[0]->full_name())); continue; } - *simple_order=0; // Must do a temp table to sort + /* + UseMultipleEqualitiesToRemoveTempTable: + Can use multiple-equalities here to check that ORDER BY columns + can be used without tmp. table. + */ + bool can_subst_to_first_table= false; + if (optimizer_flag(join->thd, OPTIMIZER_SWITCH_ORDERBY_EQ_PROP) && + first_is_base_table && + order->item[0]->real_item()->type() == Item::FIELD_ITEM && + join->cond_equal) + { + table_map first_table_bit= + join->join_tab[join->const_tables].table->map; + + Item *item= order->item[0]; + + /* + TODO: equality substitution in the context of ORDER BY is + sometimes allowed when it is not allowed in the general case. + + We make the below call for its side effect: it will locate the + multiple equality the item belongs to and set item->item_equal + accordingly. + */ + Item *res= item->propagate_equal_fields(join->thd, + Value_source:: + Context_identity(), + join->cond_equal); + Item_equal *item_eq; + if ((item_eq= res->get_item_equal())) + { + Item *first= item_eq->get_first(NO_PARTICULAR_TAB, NULL); + if (first->const_item() || first->used_tables() == + first_table_bit) + { + can_subst_to_first_table= true; + } + } + } + + if (!can_subst_to_first_table) + { + *simple_order=0; // Must do a temp table to sort + } } } } @@ -11434,6 +12553,10 @@ remove_const(JOIN *join,ORDER *first_order, COND *cond, *prev_ptr=0; if (prev_ptr == &first_order) // Nothing to sort/group *simple_order=1; +#ifndef DBUG_OFF + if (join->thd->is_error()) + DBUG_PRINT("error",("Error from remove_const")); +#endif DBUG_PRINT("exit",("simple_order: %d",(int) *simple_order)); DBUG_RETURN(first_order); } @@ -11523,6 +12646,14 @@ return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables, if (having && having->val_int() == 0) send_row=0; } + + /* Update results for FOUND_ROWS */ + if (!join->send_row_on_empty_set()) + { + join->thd->set_examined_row_count(0); + join->thd->limit_found_rows= 0; + } + if (!(result->send_result_set_metadata(fields, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))) { @@ -11532,8 +12663,6 @@ return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables, if (!send_error) result->send_eof(); // Should be safe } - /* Update results for FOUND_ROWS */ - join->thd->limit_found_rows= join->thd->examined_row_count= 0; DBUG_RETURN(0); } @@ -11573,18 +12702,10 @@ public: { TRASH_FREE(ptr, size); } Item *and_level; - Item_func *cmp_func; - COND_CMP(Item *a,Item_func *b) :and_level(a),cmp_func(b) {} + Item_bool_func2 *cmp_func; + COND_CMP(Item *a,Item_bool_func2 *b) :and_level(a),cmp_func(b) {} }; -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class I_List<COND_CMP>; -template class I_List_iterator<COND_CMP>; -template class List<Item_func_match>; -template class List_iterator<Item_func_match>; -#endif - - /** Find the multiple equality predicate containing a field. @@ -11672,7 +12793,7 @@ finish: general case, its own constant for each fields from the multiple equality. But at the same time it would allow us to get rid of constant propagation completely: it would be done by the call - to build_equal_items_for_cond. + to cond->build_equal_items(). The implementation does not follow exactly the above rules to @@ -11712,8 +12833,9 @@ finish: FALSE otherwise */ -static bool check_simple_equality(Item *left_item, Item *right_item, - Item *item, COND_EQUAL *cond_equal) +static bool check_simple_equality(THD *thd, const Item::Context &ctx, + Item *left_item, Item *right_item, + COND_EQUAL *cond_equal) { Item *orig_left_item= left_item; Item *orig_right_item= right_item; @@ -11769,27 +12891,27 @@ static bool check_simple_equality(Item *left_item, Item *right_item, if (left_copyfl) { /* left_item_equal of an upper level contains left_item */ - left_item_equal= new Item_equal(left_item_equal); + left_item_equal= new (thd->mem_root) Item_equal(thd, left_item_equal); left_item_equal->set_context_field(((Item_field*) left_item)); - cond_equal->current_level.push_back(left_item_equal); + cond_equal->current_level.push_back(left_item_equal, thd->mem_root); } if (right_copyfl) { /* right_item_equal of an upper level contains right_item */ - right_item_equal= new Item_equal(right_item_equal); + right_item_equal= new (thd->mem_root) Item_equal(thd, right_item_equal); right_item_equal->set_context_field(((Item_field*) right_item)); - cond_equal->current_level.push_back(right_item_equal); + cond_equal->current_level.push_back(right_item_equal, thd->mem_root); } if (left_item_equal) { /* left item was found in the current or one of the upper levels */ if (! right_item_equal) - left_item_equal->add(orig_right_item); + left_item_equal->add(orig_right_item, thd->mem_root); else { /* Merge two multiple equalities forming a new one */ - left_item_equal->merge(right_item_equal); + left_item_equal->merge(thd, right_item_equal); /* Remove the merged multiple equality from the list */ List_iterator<Item_equal> li(cond_equal->current_level); while ((li++) != right_item_equal) ; @@ -11800,15 +12922,16 @@ static bool check_simple_equality(Item *left_item, Item *right_item, { /* left item was not found neither the current nor in upper levels */ if (right_item_equal) - right_item_equal->add(orig_left_item); + right_item_equal->add(orig_left_item, thd->mem_root); else { /* None of the fields was found in multiple equalities */ - Item_equal *item_equal= new Item_equal(orig_left_item, - orig_right_item, - FALSE); + Item_equal *item_equal= new (thd->mem_root) Item_equal(thd, + orig_left_item, + orig_right_item, + FALSE); item_equal->set_context_field((Item_field*)left_item); - cond_equal->current_level.push_back(item_equal); + cond_equal->current_level.push_back(item_equal, thd->mem_root); } } return TRUE; @@ -11837,35 +12960,49 @@ static bool check_simple_equality(Item *left_item, Item *right_item, } if (const_item && - field_item->result_type() == const_item->result_type()) + field_item->field->test_if_equality_guarantees_uniqueness(const_item)) { + /* + field_item and const_item are arguments of a scalar or a row + comparison function: + WHERE column=constant + WHERE (column, ...) = (constant, ...) + + The owner comparison function has previously called fix_fields(), + so field_item and const_item should be directly comparable items, + field_item->cmp_context and const_item->cmp_context should be set. + In case of string comparison, charsets and collations of + field_item and const_item should have already be aggregated + for comparison, all necessary character set converters installed + and fixed. + + In case of string comparison, const_item can be either: + - a weaker constant that does not need to be converted to field_item: + WHERE latin1_field = 'latin1_const' + WHERE varbinary_field = 'latin1_const' + WHERE latin1_bin_field = 'latin1_general_ci_const' + - a stronger constant that does not need to be converted to field_item: + WHERE latin1_field = binary 0xDF + WHERE latin1_field = 'a' COLLATE latin1_bin + - a result of conversion (e.g. from the session character set) + to the character set of field_item: + WHERE latin1_field = 'utf8_string_with_latin1_repertoire' + */ bool copyfl; - if (field_item->cmp_type() == STRING_RESULT) - { - CHARSET_INFO *cs= ((Field_str*) field_item->field)->charset(); - if (!item) - { - Item_func_eq *eq_item; - if (!(eq_item= new Item_func_eq(orig_left_item, orig_right_item)) || - eq_item->set_cmp_func()) - return FALSE; - eq_item->quick_fix_field(); - item= eq_item; - } - if ((cs != ((Item_func *) item)->compare_collation()) || - !cs->coll->propagate(cs, 0, 0)) - return FALSE; - } - Item_equal *item_equal = find_item_equal(cond_equal, field_item->field, ©fl); if (copyfl) { - item_equal= new Item_equal(item_equal); - cond_equal->current_level.push_back(item_equal); + item_equal= new (thd->mem_root) Item_equal(thd, item_equal); + cond_equal->current_level.push_back(item_equal, thd->mem_root); item_equal->set_context_field(field_item); } + Item *const_item2= field_item->field->get_equal_const_item(thd, ctx, + const_item); + if (!const_item2) + return false; + if (item_equal) { /* @@ -11873,13 +13010,14 @@ static bool check_simple_equality(Item *left_item, Item *right_item, already contains a constant and its value is not equal to the value of const_item. */ - item_equal->add_const(const_item, orig_field_item); + item_equal->add_const(thd, const_item2); } else { - item_equal= new Item_equal(const_item, orig_field_item, TRUE); + item_equal= new (thd->mem_root) Item_equal(thd, const_item2, + orig_field_item, TRUE); item_equal->set_context_field(field_item); - cond_equal->current_level.push_back(item_equal); + cond_equal->current_level.push_back(item_equal, thd->mem_root); } return TRUE; } @@ -11914,7 +13052,8 @@ static bool check_simple_equality(Item *left_item, Item *right_item, FALSE otherwise */ -static bool check_row_equality(THD *thd, Item *left_row, Item_row *right_row, +static bool check_row_equality(THD *thd, const Arg_comparator *comparators, + Item *left_row, Item_row *right_row, COND_EQUAL *cond_equal, List<Item>* eq_list) { uint n= left_row->cols(); @@ -11926,24 +13065,31 @@ static bool check_row_equality(THD *thd, Item *left_row, Item_row *right_row, if (left_item->type() == Item::ROW_ITEM && right_item->type() == Item::ROW_ITEM) { - is_converted= check_row_equality(thd, + is_converted= check_row_equality(thd, + comparators[i].subcomparators(), (Item_row *) left_item, (Item_row *) right_item, cond_equal, eq_list); } else { - is_converted= check_simple_equality(left_item, right_item, 0, cond_equal); + const Arg_comparator *tmp= &comparators[i]; + is_converted= check_simple_equality(thd, + Item::Context(Item::ANY_SUBST, + tmp->compare_type(), + tmp->compare_collation()), + left_item, right_item, + cond_equal); } if (!is_converted) { Item_func_eq *eq_item; - if (!(eq_item= new Item_func_eq(left_item, right_item)) || + if (!(eq_item= new (thd->mem_root) Item_func_eq(thd, left_item, right_item)) || eq_item->set_cmp_func()) return FALSE; eq_item->quick_fix_field(); - eq_list->push_back(eq_item); + eq_list->push_back(eq_item, thd->mem_root); } } return TRUE; @@ -11980,32 +13126,34 @@ static bool check_row_equality(THD *thd, Item *left_row, Item_row *right_row, or, if the procedure fails by a fatal error. */ -static bool check_equality(THD *thd, Item *item, COND_EQUAL *cond_equal, - List<Item> *eq_list) +bool Item_func_eq::check_equality(THD *thd, COND_EQUAL *cond_equal, + List<Item> *eq_list) { - if (item->type() == Item::FUNC_ITEM && - ((Item_func*) item)->functype() == Item_func::EQ_FUNC) - { - Item *left_item= ((Item_func*) item)->arguments()[0]; - Item *right_item= ((Item_func*) item)->arguments()[1]; + Item *left_item= arguments()[0]; + Item *right_item= arguments()[1]; - if (left_item->type() == Item::ROW_ITEM && - right_item->type() == Item::ROW_ITEM) - { - return check_row_equality(thd, - (Item_row *) left_item, - (Item_row *) right_item, - cond_equal, eq_list); - } - else - return check_simple_equality(left_item, right_item, item, cond_equal); - } - return FALSE; + if (left_item->type() == Item::ROW_ITEM && + right_item->type() == Item::ROW_ITEM) + { + return check_row_equality(thd, + cmp.subcomparators(), + (Item_row *) left_item, + (Item_row *) right_item, + cond_equal, eq_list); + } + return check_simple_equality(thd, + Context(ANY_SUBST, + compare_type(), + compare_collation()), + left_item, right_item, cond_equal); } /** - Replace all equality predicates in a condition by multiple equality items. + Item_xxx::build_equal_items() + + Replace all equality predicates in a condition referenced by "this" + by multiple equality items. At each 'and' level the function detects items for equality predicates and replaced them by a set of multiple equality items of class Item_equal, @@ -12031,7 +13179,7 @@ static bool check_equality(THD *thd, Item *item, COND_EQUAL *cond_equal, equality predicates that is equivalent to the conjunction. Thus, =(a1,a2,a3) can substitute for ((a1=a3) AND (a2=a3) AND (a2=a1)) as it is equivalent to ((a1=a2) AND (a2=a3)). - The function always makes a substitution of all equality predicates occured + The function always makes a substitution of all equality predicates occurred in a conjuction for a minimal set of multiple equality predicates. This set can be considered as a canonical representation of the sub-conjunction of the equality predicates. @@ -12061,170 +13209,241 @@ static bool check_equality(THD *thd, Item *item, COND_EQUAL *cond_equal, all possible Item_equal objects in upper levels. @param thd thread handle - @param cond condition(expression) where to make replacement @param inherited path to all inherited multiple equality items @return - pointer to the transformed condition + pointer to the transformed condition, + whose Used_tables_and_const_cache is up to date, + so no additional update_used_tables() is needed on the result. */ -static COND *build_equal_items_for_cond(THD *thd, COND *cond, - COND_EQUAL *inherited) +COND *Item_cond_and::build_equal_items(THD *thd, + COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref) { Item_equal *item_equal; COND_EQUAL cond_equal; cond_equal.upper_levels= inherited; if (check_stack_overrun(thd, STACK_MIN_SIZE, NULL)) - return cond; // Fatal error flag is set! + return this; // Fatal error flag is set! - if (cond->type() == Item::COND_ITEM) + List<Item> eq_list; + List<Item> *cond_args= argument_list(); + + List_iterator<Item> li(*cond_args); + Item *item; + + DBUG_ASSERT(!cond_equal_ref || !cond_equal_ref[0]); + /* + Retrieve all conjuncts of this level detecting the equality + that are subject to substitution by multiple equality items and + removing each such predicate from the conjunction after having + found/created a multiple equality whose inference the predicate is. + */ + while ((item= li++)) { - List<Item> eq_list; - bool and_level= ((Item_cond*) cond)->functype() == - Item_func::COND_AND_FUNC; - List<Item> *args= ((Item_cond*) cond)->argument_list(); - - List_iterator<Item> li(*args); - Item *item; + /* + PS/SP note: we can safely remove a node from AND-OR + structure here because it's restored before each + re-execution of any prepared statement/stored procedure. + */ + if (item->check_equality(thd, &cond_equal, &eq_list)) + li.remove(); + } - if (and_level) + /* + Check if we eliminated all the predicates of the level, e.g. + (a=a AND b=b AND a=a). + */ + if (!cond_args->elements && + !cond_equal.current_level.elements && + !eq_list.elements) + return new (thd->mem_root) Item_int(thd, (longlong) 1, 1); + + List_iterator_fast<Item_equal> it(cond_equal.current_level); + while ((item_equal= it++)) + { + item_equal->set_link_equal_fields(link_item_fields); + item_equal->fix_fields(thd, NULL); + item_equal->update_used_tables(); + set_if_bigger(thd->lex->current_select->max_equal_elems, + item_equal->n_field_items()); + } + + m_cond_equal.copy(cond_equal); + cond_equal.current_level= m_cond_equal.current_level; + inherited= &m_cond_equal; + + /* + Make replacement of equality predicates for lower levels + of the condition expression. + */ + li.rewind(); + while ((item= li++)) + { + Item *new_item; + if ((new_item= item->build_equal_items(thd, inherited, false, NULL)) + != item) { + /* This replacement happens only for standalone equalities */ /* - Retrieve all conjuncts of this level detecting the equality - that are subject to substitution by multiple equality items and - removing each such predicate from the conjunction after having - found/created a multiple equality whose inference the predicate is. - */ - while ((item= li++)) - { - /* - PS/SP note: we can safely remove a node from AND-OR - structure here because it's restored before each - re-execution of any prepared statement/stored procedure. - */ - if (check_equality(thd, item, &cond_equal, &eq_list)) - li.remove(); - } + This is ok with PS/SP as the replacement is done for + cond_args of an AND/OR item, which are restored for each + execution of PS/SP. + */ + li.replace(new_item); + } + } + cond_args->append(&eq_list); + cond_args->append((List<Item> *)&cond_equal.current_level); + update_used_tables(); + if (cond_equal_ref) + *cond_equal_ref= &m_cond_equal; + return this; +} + + +COND *Item_cond::build_equal_items(THD *thd, + COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref) +{ + List<Item> *cond_args= argument_list(); + + List_iterator<Item> li(*cond_args); + Item *item; + DBUG_ASSERT(!cond_equal_ref || !cond_equal_ref[0]); + /* + Make replacement of equality predicates for lower levels + of the condition expression. + Update used_tables_cache and const_item_cache on the way. + */ + used_tables_and_const_cache_init(); + while ((item= li++)) + { + Item *new_item; + if ((new_item= item->build_equal_items(thd, inherited, false, NULL)) + != item) + { + /* This replacement happens only for standalone equalities */ /* - Check if we eliminated all the predicates of the level, e.g. - (a=a AND b=b AND a=a). + This is ok with PS/SP as the replacement is done for + arguments of an AND/OR item, which are restored for each + execution of PS/SP. */ - if (!args->elements && - !cond_equal.current_level.elements && - !eq_list.elements) - return new Item_int((longlong) 1, 1); + li.replace(new_item); + } + used_tables_and_const_cache_join(new_item); + } + return this; +} - List_iterator_fast<Item_equal> it(cond_equal.current_level); - while ((item_equal= it++)) + +COND *Item_func_eq::build_equal_items(THD *thd, + COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref) +{ + COND_EQUAL cond_equal; + cond_equal.upper_levels= inherited; + List<Item> eq_list; + + DBUG_ASSERT(!cond_equal_ref || !cond_equal_ref[0]); + /* + If an equality predicate forms the whole and level, + we call it standalone equality and it's processed here. + E.g. in the following where condition + WHERE a=5 AND (b=5 or a=c) + (b=5) and (a=c) are standalone equalities. + In general we can't leave alone standalone eqalities: + for WHERE a=b AND c=d AND (b=c OR d=5) + b=c is replaced by =(a,b,c,d). + */ + if (Item_func_eq::check_equality(thd, &cond_equal, &eq_list)) + { + Item_equal *item_equal; + int n= cond_equal.current_level.elements + eq_list.elements; + if (n == 0) + return new (thd->mem_root) Item_int(thd, (longlong) 1, 1); + else if (n == 1) + { + if ((item_equal= cond_equal.current_level.pop())) { item_equal->fix_fields(thd, NULL); item_equal->update_used_tables(); set_if_bigger(thd->lex->current_select->max_equal_elems, item_equal->n_field_items()); + item_equal->upper_levels= inherited; + if (cond_equal_ref) + *cond_equal_ref= new (thd->mem_root) COND_EQUAL(item_equal, + thd->mem_root); + return item_equal; } - - ((Item_cond_and*)cond)->cond_equal.copy(cond_equal); - cond_equal.current_level= - ((Item_cond_and*)cond)->cond_equal.current_level; - inherited= &(((Item_cond_and*)cond)->cond_equal); - } - /* - Make replacement of equality predicates for lower levels - of the condition expression. - */ - li.rewind(); - while ((item= li++)) - { - Item *new_item; - if ((new_item= build_equal_items_for_cond(thd, item, inherited)) != item) - { - /* This replacement happens only for standalone equalities */ - /* - This is ok with PS/SP as the replacement is done for - arguments of an AND/OR item, which are restored for each - execution of PS/SP. - */ - li.replace(new_item); - } - } - if (and_level) - { - args->concat(&eq_list); - args->concat((List<Item> *)&cond_equal.current_level); + Item *res= eq_list.pop(); + res->update_used_tables(); + DBUG_ASSERT(res->type() == FUNC_ITEM); + return res; } - } - else if (cond->type() == Item::FUNC_ITEM || - cond->real_item()->type() == Item::FIELD_ITEM) - { - List<Item> eq_list; - /* - If an equality predicate forms the whole and level, - we call it standalone equality and it's processed here. - E.g. in the following where condition - WHERE a=5 AND (b=5 or a=c) - (b=5) and (a=c) are standalone equalities. - In general we can't leave alone standalone eqalities: - for WHERE a=b AND c=d AND (b=c OR d=5) - b=c is replaced by =(a,b,c,d). - */ - if (check_equality(thd, cond, &cond_equal, &eq_list)) + else { - int n= cond_equal.current_level.elements + eq_list.elements; - if (n == 0) - return new Item_int((longlong) 1,1); - else if (n == 1) - { - if ((item_equal= cond_equal.current_level.pop())) - { - item_equal->fix_fields(thd, NULL); - item_equal->update_used_tables(); - set_if_bigger(thd->lex->current_select->max_equal_elems, - item_equal->n_field_items()); - item_equal->upper_levels= inherited; - return item_equal; - } - - return eq_list.pop(); - } - else + /* + Here a new AND level must be created. It can happen only + when a row equality is processed as a standalone predicate. + */ + Item_cond_and *and_cond= new (thd->mem_root) Item_cond_and(thd, eq_list); + and_cond->quick_fix_field(); + List<Item> *cond_args= and_cond->argument_list(); + List_iterator_fast<Item_equal> it(cond_equal.current_level); + while ((item_equal= it++)) { - /* - Here a new AND level must be created. It can happen only - when a row equality is processed as a standalone predicate. - */ - Item_cond_and *and_cond= new Item_cond_and(eq_list); - and_cond->quick_fix_field(); - List<Item> *args= and_cond->argument_list(); - List_iterator_fast<Item_equal> it(cond_equal.current_level); - while ((item_equal= it++)) - { - item_equal->fix_length_and_dec(); - item_equal->update_used_tables(); - set_if_bigger(thd->lex->current_select->max_equal_elems, - item_equal->n_field_items()); - } - and_cond->cond_equal.copy(cond_equal); - cond_equal.current_level= and_cond->cond_equal.current_level; - args->concat((List<Item> *)&cond_equal.current_level); - - return and_cond; + item_equal->fix_length_and_dec(); + item_equal->update_used_tables(); + set_if_bigger(thd->lex->current_select->max_equal_elems, + item_equal->n_field_items()); } + and_cond->m_cond_equal.copy(cond_equal); + cond_equal.current_level= and_cond->m_cond_equal.current_level; + cond_args->append((List<Item> *)&cond_equal.current_level); + and_cond->update_used_tables(); + if (cond_equal_ref) + *cond_equal_ref= &and_cond->m_cond_equal; + return and_cond; } - /* - For each field reference in cond, not from equal item predicates, - set a pointer to the multiple equality it belongs to (if there is any) - as soon the field is not of a string type or the field reference is - an argument of a comparison predicate. - */ - uchar* is_subst_valid= (uchar *) Item::ANY_SUBST; - cond= cond->compile(&Item::subst_argument_checker, - &is_subst_valid, - &Item::equal_fields_propagator, - (uchar *) inherited); - cond->update_used_tables(); } + return Item_func::build_equal_items(thd, inherited, link_item_fields, + cond_equal_ref); +} + + +COND *Item_func::build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref) +{ + /* + For each field reference in cond, not from equal item predicates, + set a pointer to the multiple equality it belongs to (if there is any) + as soon the field is not of a string type or the field reference is + an argument of a comparison predicate. + */ + COND *cond= propagate_equal_fields(thd, Context_boolean(), inherited); + cond->update_used_tables(); + DBUG_ASSERT(cond == this); + DBUG_ASSERT(!cond_equal_ref || !cond_equal_ref[0]); + return cond; +} + + +COND *Item_equal::build_equal_items(THD *thd, COND_EQUAL *inherited, + bool link_item_fields, + COND_EQUAL **cond_equal_ref) +{ + COND *cond= Item_func::build_equal_items(thd, inherited, link_item_fields, + cond_equal_ref); + if (cond_equal_ref) + *cond_equal_ref= new (thd->mem_root) COND_EQUAL(this, thd->mem_root); return cond; } @@ -12233,7 +13452,7 @@ static COND *build_equal_items_for_cond(THD *thd, COND *cond, Build multiple equalities for a condition and all on expressions that inherit these multiple equalities. - The function first applies the build_equal_items_for_cond function + The function first applies the cond->build_equal_items() method to build all multiple equalities for condition cond utilizing equalities referred through the parameter inherited. The extended set of equalities is returned in the structure referred by the cond_equal_ref @@ -12281,9 +13500,9 @@ static COND *build_equal_items_for_cond(THD *thd, COND *cond, @endcode Thus, applying equalities from the where condition we basically can get more freedom in performing join operations. - Althogh we don't use this property now, it probably makes sense to use + Although we don't use this property now, it probably makes sense to use it in the future. - @param thd Thread handler + @param thd Thread handler @param cond condition to build the multiple equalities for @param inherited path to all inherited multiple equality items @param join_list list of join tables to which the condition @@ -12292,6 +13511,7 @@ static COND *build_equal_items_for_cond(THD *thd, COND *cond, for on expressions @param[out] cond_equal_ref pointer to the structure to place built equalities in + @param link_equal_items equal fields are to be linked @return pointer to the transformed condition containing multiple equalities @@ -12301,32 +13521,23 @@ static COND *build_equal_items(JOIN *join, COND *cond, COND_EQUAL *inherited, List<TABLE_LIST> *join_list, bool ignore_on_conds, - COND_EQUAL **cond_equal_ref) + COND_EQUAL **cond_equal_ref, + bool link_equal_fields) { THD *thd= join->thd; - COND_EQUAL *cond_equal= 0; + + *cond_equal_ref= NULL; if (cond) { - cond= build_equal_items_for_cond(thd, cond, inherited); - cond->update_used_tables(); - if (cond->type() == Item::COND_ITEM && - ((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) - cond_equal= &((Item_cond_and*) cond)->cond_equal; - - else if (cond->type() == Item::FUNC_ITEM && - ((Item_cond*) cond)->functype() == Item_func::MULT_EQUAL_FUNC) + cond= cond->build_equal_items(thd, inherited, link_equal_fields, + cond_equal_ref); + if (*cond_equal_ref) { - cond_equal= new COND_EQUAL; - cond_equal->current_level.push_back((Item_equal *) cond); + (*cond_equal_ref)->upper_levels= inherited; + inherited= *cond_equal_ref; } } - if (cond_equal) - { - cond_equal->upper_levels= inherited; - inherited= cond_equal; - } - *cond_equal_ref= cond_equal; if (join_list && !ignore_on_conds) { @@ -12439,7 +13650,7 @@ static int compare_fields_by_table_order(Item *field1, if (!cmp) { KEY *key_info= tab->table->key_info + keyno; - for (uint i= 0; i < key_info->key_parts; i++) + for (uint i= 0; i < key_info->user_defined_key_parts; i++) { Field *fld= key_info->key_part[i].field; if (fld->eq(f2->field)) @@ -12531,13 +13742,13 @@ static TABLE_LIST* embedding_sjm(Item *item) - 0, otherwise. */ -Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, +Item *eliminate_item_equal(THD *thd, COND *cond, COND_EQUAL *upper_levels, Item_equal *item_equal) { List<Item> eq_list; Item_func_eq *eq_item= 0; if (((Item *) item_equal)->const_item() && !item_equal->val_int()) - return new Item_int((longlong) 0,1); + return new (thd->mem_root) Item_int(thd, (longlong) 0, 1); Item *item_const= item_equal->get_const(); Item_equal_fields_iterator it(*item_equal); Item *head; @@ -12602,7 +13813,7 @@ Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, Upper item also has "field_item=const". Don't produce equality if const is equal to item_const. */ - Item_func_eq *func= new Item_func_eq(item_const, upper_const); + Item_func_eq *func= new (thd->mem_root) Item_func_eq(thd, item_const, upper_const); func->set_cmp_func(); func->quick_fix_field(); if (func->val_int()) @@ -12622,7 +13833,7 @@ Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, item= NULL; /* Don't produce equality */ } - bool produce_equality= test(item == field_item); + bool produce_equality= MY_TEST(item == field_item); if (!item_const && field_sjm && field_sjm != current_sjm) { /* Entering an SJM nest */ @@ -12633,7 +13844,7 @@ Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, if (produce_equality) { - if (eq_item && eq_list.push_back(eq_item)) + if (eq_item && eq_list.push_back(eq_item, thd->mem_root)) return 0; /* @@ -12651,7 +13862,7 @@ Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, if (head_real_item->type() == Item::FIELD_ITEM) head_item= head_real_item; - eq_item= new Item_func_eq(field_item->real_item(), head_item); + eq_item= new (thd->mem_root) Item_func_eq(thd, field_item->real_item(), head_item); if (!eq_item || eq_item->set_cmp_func()) return 0; @@ -12678,13 +13889,13 @@ Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, - if we have only one condition to return, we don't create an Item_cond_and */ - if (eq_item && eq_list.push_back(eq_item)) + if (eq_item && eq_list.push_back(eq_item, thd->mem_root)) return 0; COND *res= 0; switch (eq_list.elements) { case 0: - res= cond ? cond : new Item_int((longlong) 1, 1); + res= cond ? cond : new (thd->mem_root) Item_int(thd, (longlong) 1, 1); break; case 1: if (!cond || cond->type() == Item::INT_ITEM) @@ -12702,12 +13913,12 @@ Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, res= cond; ((Item_cond *) res)->add_at_end(&eq_list); } - else if (eq_list.push_front(cond)) + else if (eq_list.push_front(cond, thd->mem_root)) return 0; } } if (!res) - res= new Item_cond_and(eq_list); + res= new (thd->mem_root) Item_cond_and(thd, eq_list); if (res) { res->quick_fix_field(); @@ -12774,7 +13985,7 @@ Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, The transformed condition, or NULL in case of error */ -static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, +static COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, COND *cond, COND_EQUAL *cond_equal, void *table_join_idx) @@ -12790,7 +14001,7 @@ static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, Item_func::COND_AND_FUNC; if (and_level) { - cond_equal= &((Item_cond_and *) cond)->cond_equal; + cond_equal= &((Item_cond_and *) cond)->m_cond_equal; cond_list->disjoin((List<Item> *) &cond_equal->current_level);/* remove Item_equal objects from the AND. */ List_iterator_fast<Item_equal> it(cond_equal->current_level); @@ -12804,7 +14015,7 @@ static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, Item *item; while ((item= li++)) { - Item *new_item= substitute_for_best_equal_field(context_tab, + Item *new_item= substitute_for_best_equal_field(thd, context_tab, item, cond_equal, table_join_idx); /* @@ -12822,8 +14033,8 @@ static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, bool false_eq_cond= FALSE; while ((item_equal= it++)) { - eq_cond= eliminate_item_equal(eq_cond, cond_equal->upper_levels, - item_equal); + eq_cond= eliminate_item_equal(thd, eq_cond, cond_equal->upper_levels, + item_equal); if (!eq_cond) { eq_cond= 0; @@ -12855,7 +14066,7 @@ static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, { /* Do not add an equality condition if it's always true */ if (eq_cond->type() != Item::INT_ITEM && - cond_list->push_front(eq_cond)) + cond_list->push_front(eq_cond, thd->mem_root)) eq_cond= 0; } } @@ -12867,7 +14078,7 @@ static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, This is a fatal error now. However we bail out by returning the original condition that we had before we started the transformation. */ - cond_list->concat((List<Item> *) &cond_equal->current_level); + cond_list->append((List<Item> *) &cond_equal->current_level); } } } @@ -12879,7 +14090,7 @@ static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, cond_equal= item_equal->upper_levels; if (cond_equal && cond_equal->current_level.head() == item_equal) cond_equal= cond_equal->upper_levels; - cond= eliminate_item_equal(0, cond_equal, item_equal); + cond= eliminate_item_equal(thd, 0, cond_equal, item_equal); return cond ? cond : org_cond; } else @@ -12890,7 +14101,7 @@ static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, while((item_equal= it++)) { REPLACE_EQUAL_FIELD_ARG arg= {item_equal, context_tab}; - cond= cond->transform(&Item::replace_equal_field, (uchar *) &arg); + cond= cond->transform(thd, &Item::replace_equal_field, (uchar *) &arg); } cond_equal= cond_equal->upper_levels; } @@ -12913,7 +14124,8 @@ static COND* substitute_for_best_equal_field(JOIN_TAB *context_tab, @param const_key mark key parts as constant */ -static void update_const_equal_items(COND *cond, JOIN_TAB *tab, bool const_key) +static void update_const_equal_items(THD *thd, COND *cond, JOIN_TAB *tab, + bool const_key) { if (!(cond->used_tables() & tab->table->map)) return; @@ -12924,7 +14136,7 @@ static void update_const_equal_items(COND *cond, JOIN_TAB *tab, bool const_key) List_iterator_fast<Item> li(*cond_list); Item *item; while ((item= li++)) - update_const_equal_items(item, tab, + update_const_equal_items(thd, item, tab, (((Item_cond*) cond)->top_level() && ((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)); @@ -12934,7 +14146,7 @@ static void update_const_equal_items(COND *cond, JOIN_TAB *tab, bool const_key) { Item_equal *item_equal= (Item_equal *) cond; bool contained_const= item_equal->get_const() != NULL; - item_equal->update_const(); + item_equal->update_const(thd); if (!contained_const && item_equal->get_const()) { /* Update keys for range analysis */ @@ -12954,14 +14166,14 @@ static void update_const_equal_items(COND *cond, JOIN_TAB *tab, bool const_key) */ if (!possible_keys.is_clear_all()) { - TABLE *tab= field->table; + TABLE *field_tab= field->table; KEYUSE *use; - for (use= stat->keyuse; use && use->table == tab; use++) + for (use= stat->keyuse; use && use->table == field_tab; use++) if (const_key && !use->is_for_hash_join() && possible_keys.is_set(use->key) && - tab->key_info[use->key].key_part[use->keypart].field == + field_tab->key_info[use->key].key_part[use->keypart].field == field) - tab->const_key_parts[use->key]|= use->keypart_map; + field_tab->const_key_parts[use->key]|= use->keypart_map; } } } @@ -12969,6 +14181,98 @@ static void update_const_equal_items(COND *cond, JOIN_TAB *tab, bool const_key) } +/** + Check if + WHERE expr=value AND expr=const + can be rewritten as: + WHERE const=value AND expr=const + + @param target - the target operator whose "expr" argument will be + replaced to "const". + @param target_expr - the target's "expr" which will be replaced to "const". + @param target_value - the target's second argument, it will remain unchanged. + @param source - the equality expression ("=" or "<=>") that + can be used to rewrite the "target" part + (under certain conditions, see the code). + @param source_expr - the source's "expr". It should be exactly equal to + the target's "expr" to make condition rewrite possible. + @param source_const - the source's "const" argument, it will be inserted + into "target" instead of "expr". +*/ +static bool +can_change_cond_ref_to_const(Item_bool_func2 *target, + Item *target_expr, Item *target_value, + Item_bool_func2 *source, + Item *source_expr, Item *source_const) +{ + if (!target_expr->eq(source_expr,0) || + target_value == source_const || + target->compare_type() != source->compare_type()) + return false; + if (target->compare_type() == STRING_RESULT) + { + /* + In this example: + SET NAMES utf8 COLLATE utf8_german2_ci; + DROP TABLE IF EXISTS t1; + CREATE TABLE t1 (a CHAR(10) CHARACTER SET utf8); + INSERT INTO t1 VALUES ('o-umlaut'),('oe'); + SELECT * FROM t1 WHERE a='oe' COLLATE utf8_german2_ci AND a='oe'; + + the query should return only the row with 'oe'. + It should not return 'o-umlaut', because 'o-umlaut' does not match + the right part of the condition: a='oe' + ('o-umlaut' is not equal to 'oe' in utf8_general_ci, + which is the collation of the field "a"). + + If we change the right part from: + ... AND a='oe' + to + ... AND 'oe' COLLATE utf8_german2_ci='oe' + it will be evalulated to TRUE and removed from the condition, + so the overall query will be simplified to: + + SELECT * FROM t1 WHERE a='oe' COLLATE utf8_german2_ci; + + which will erroneously start to return both 'oe' and 'o-umlaut'. + So changing "expr" to "const" is not possible if the effective + collations of "target" and "source" are not exactly the same. + + Note, the code before the fix for MDEV-7152 only checked that + collations of "source_const" and "target_value" are the same. + This was not enough, as the bug report demonstrated. + */ + return + target->compare_collation() == source->compare_collation() && + target_value->collation.collation == source_const->collation.collation; + } + if (target->compare_type() == TIME_RESULT) + { + if (target_value->cmp_type() != TIME_RESULT) + { + /* + Can't rewrite: + WHERE COALESCE(time_column)='00:00:00' + AND COALESCE(time_column)=DATE'2015-09-11' + to + WHERE DATE'2015-09-11'='00:00:00' + AND COALESCE(time_column)=DATE'2015-09-11' + because the left part will erroneously try to parse '00:00:00' + as DATE, not as TIME. + + TODO: It could still be rewritten to: + WHERE DATE'2015-09-11'=TIME'00:00:00' + AND COALESCE(time_column)=DATE'2015-09-11' + i.e. we need to replace both target_expr and target_value + at the same time. This is not supported yet. + */ + return false; + } + } + return true; // Non-string comparison +} + + /* change field = field to field = const for each found field = const in the and_level @@ -12977,6 +14281,7 @@ static void update_const_equal_items(COND *cond, JOIN_TAB *tab, bool const_key) static void change_cond_ref_to_const(THD *thd, I_List<COND_CMP> *save_list, Item *and_father, Item *cond, + Item_bool_func2 *field_value_owner, Item *field, Item *value) { if (cond->type() == Item::COND_ITEM) @@ -12987,7 +14292,7 @@ change_cond_ref_to_const(THD *thd, I_List<COND_CMP> *save_list, Item *item; while ((item=li++)) change_cond_ref_to_const(thd, save_list,and_level ? cond : item, item, - field, value); + field_value_owner, field, value); return; } if (cond->eq_cmp_result() == Item::COND_OK) @@ -12999,13 +14304,10 @@ change_cond_ref_to_const(THD *thd, I_List<COND_CMP> *save_list, Item *right_item= args[1]; Item_func::Functype functype= func->functype(); - if (right_item->eq(field,0) && left_item != value && - right_item->cmp_context == field->cmp_context && - (left_item->result_type() != STRING_RESULT || - value->result_type() != STRING_RESULT || - left_item->collation.collation == value->collation.collation)) + if (can_change_cond_ref_to_const(func, right_item, left_item, + field_value_owner, field, value)) { - Item *tmp=value->clone_item(); + Item *tmp=value->clone_item(thd); if (tmp) { tmp->collation.set(right_item->collation); @@ -13019,16 +14321,22 @@ change_cond_ref_to_const(THD *thd, I_List<COND_CMP> *save_list, if ((tmp2=new COND_CMP(and_father,func))) save_list->push_back(tmp2); } - func->set_cmp_func(); + /* + LIKE can be optimized for BINARY/VARBINARY/BLOB columns, e.g.: + + from: WHERE CONCAT(c1)='const1' AND CONCAT(c1) LIKE 'const2' + to: WHERE CONCAT(c1)='const1' AND 'const1' LIKE 'const2' + + So make sure to use set_cmp_func() only for non-LIKE operators. + */ + if (functype != Item_func::LIKE_FUNC) + ((Item_bool_rowready_func2*) func)->set_cmp_func(); } } - else if (left_item->eq(field,0) && right_item != value && - left_item->cmp_context == field->cmp_context && - (right_item->result_type() != STRING_RESULT || - value->result_type() != STRING_RESULT || - right_item->collation.collation == value->collation.collation)) + else if (can_change_cond_ref_to_const(func, left_item, right_item, + field_value_owner, field, value)) { - Item *tmp= value->clone_item(); + Item *tmp= value->clone_item(thd); if (tmp) { tmp->collation.set(left_item->collation); @@ -13045,7 +14353,8 @@ change_cond_ref_to_const(THD *thd, I_List<COND_CMP> *save_list, if ((tmp2=new COND_CMP(and_father,func))) save_list->push_back(tmp2); } - func->set_cmp_func(); + if (functype != Item_func::LIKE_FUNC) + ((Item_bool_rowready_func2*) func)->set_cmp_func(); } } } @@ -13075,7 +14384,8 @@ propagate_cond_constants(THD *thd, I_List<COND_CMP> *save_list, Item **args= cond_cmp->cmp_func->arguments(); if (!args[0]->const_item()) change_cond_ref_to_const(thd, &save,cond_cmp->and_level, - cond_cmp->and_level, args[0], args[1]); + cond_cmp->and_level, + cond_cmp->cmp_func, args[0], args[1]); } } } @@ -13097,14 +14407,14 @@ propagate_cond_constants(THD *thd, I_List<COND_CMP> *save_list, resolve_const_item(thd, &args[1], args[0]); func->update_used_tables(); change_cond_ref_to_const(thd, save_list, and_father, and_father, - args[0], args[1]); + func, args[0], args[1]); } else if (left_const) { resolve_const_item(thd, &args[0], args[1]); func->update_used_tables(); change_cond_ref_to_const(thd, save_list, and_father, and_father, - args[1], args[0]); + func, args[1], args[0]); } } } @@ -13240,7 +14550,7 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top, NESTED_JOIN *nested_join; TABLE_LIST *prev_table= 0; List_iterator<TABLE_LIST> li(*join_list); - bool straight_join= test(join->select_options & SELECT_STRAIGHT_JOIN); + bool straight_join= MY_TEST(join->select_options & SELECT_STRAIGHT_JOIN); DBUG_ENTER("simplify_joins"); /* @@ -13314,12 +14624,15 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top, if (table->outer_join && !table->embedding && table->table) table->table->maybe_null= FALSE; table->outer_join= 0; + if (!(straight_join || table->straight)) + table->dep_tables= table->embedding && !table->embedding->sj_subq_pred ? + table->embedding->dep_tables : 0; if (table->on_expr) { /* Add ON expression to the WHERE or upper-level ON condition. */ if (conds) { - conds= and_conds(conds, table->on_expr); + conds= and_conds(join->thd, conds, table->on_expr); conds->top_level_item(); /* conds is always a new item as both cond and on_expr existed */ DBUG_ASSERT(!conds->fixed); @@ -13337,7 +14650,8 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top, */ if (table->on_expr) { - table->dep_tables|= table->on_expr->used_tables(); + table_map table_on_expr_used_tables= table->on_expr->used_tables(); + table->dep_tables|= table_on_expr_used_tables; if (table->embedding) { table->dep_tables&= ~table->embedding->nested_join->used_tables; @@ -13345,7 +14659,7 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top, Embedding table depends on tables used in embedded on expressions. */ - table->embedding->on_expr_dep_tables|= table->on_expr->used_tables(); + table->embedding->on_expr_dep_tables|= table_on_expr_used_tables; } else table->dep_tables&= ~table->get_map(); @@ -13409,7 +14723,7 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top, } if (sj_nest) continue; - join->select_lex->sj_nests.push_back(table); + join->select_lex->sj_nests.push_back(table, join->thd->mem_root); /* Also, walk through semi-join children and mark those that are now @@ -13434,7 +14748,7 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top, if (!tbl->embedding && !tbl->on_expr && tbl->table) tbl->table->maybe_null= FALSE; tbl->join_list= table->join_list; - repl_list.push_back(tbl); + repl_list.push_back(tbl, join->thd->mem_root); tbl->dep_tables|= table->dep_tables; } li.replace(repl_list); @@ -13848,9 +15162,10 @@ void optimize_wo_join_buffering(JOIN *join, uint first_tab, uint last_tab, static COND * -optimize_cond(JOIN *join, COND *conds, +optimize_cond(JOIN *join, COND *conds, List<TABLE_LIST> *join_list, bool ignore_on_conds, - Item::cond_result *cond_value, COND_EQUAL **cond_equal) + Item::cond_result *cond_value, COND_EQUAL **cond_equal, + int flags) { THD *thd= join->thd; DBUG_ENTER("optimize_cond"); @@ -13873,9 +15188,10 @@ optimize_cond(JOIN *join, COND *conds, multiple equality contains a constant. */ DBUG_EXECUTE("where", print_where(conds, "original", QT_ORDINARY);); - conds= build_equal_items(join, conds, NULL, join_list, ignore_on_conds, - cond_equal); - DBUG_EXECUTE("where",print_where(conds,"after equal_items", QT_ORDINARY);); + conds= build_equal_items(join, conds, NULL, join_list, + ignore_on_conds, cond_equal, + MY_TEST(flags & OPT_LINK_EQUAL_FIELDS)); + DBUG_EXECUTE("where",print_where(conds,"after equal_items", QT_ORDINARY);); /* change field = field to field = const for each found field = const */ propagate_cond_constants(thd, (I_List<COND_CMP> *) 0, conds, conds); @@ -13884,10 +15200,10 @@ optimize_cond(JOIN *join, COND *conds, Remove all and-levels where CONST item != CONST item */ DBUG_EXECUTE("where",print_where(conds,"after const change", QT_ORDINARY);); - conds= remove_eq_conds(thd, conds, cond_value); + conds= conds->remove_eq_conds(thd, cond_value, true); if (conds && conds->type() == Item::COND_ITEM && ((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC) - *cond_equal= &((Item_cond_and*) conds)->cond_equal; + *cond_equal= &((Item_cond_and*) conds)->m_cond_equal; DBUG_EXECUTE("info",print_where(conds,"after remove", QT_ORDINARY);); } DBUG_RETURN(conds); @@ -13928,15 +15244,15 @@ void propagate_new_equalities(THD *thd, Item *cond, if (and_level) { Item_cond_and *cond_and= (Item_cond_and *) cond; - List<Item_equal> *cond_equalities= &cond_and->cond_equal.current_level; - cond_and->cond_equal.upper_levels= inherited; + List<Item_equal> *cond_equalities= &cond_and->m_cond_equal.current_level; + cond_and->m_cond_equal.upper_levels= inherited; if (!cond_equalities->is_empty() && cond_equalities != new_equalities) { Item_equal *equal_item; List_iterator<Item_equal> it(*new_equalities); while ((equal_item= it++)) { - equal_item->merge_into_list(cond_equalities, true, true); + equal_item->merge_into_list(thd, cond_equalities, true, true); } List_iterator<Item_equal> ei(*cond_equalities); while ((equal_item= ei++)) @@ -13955,7 +15271,7 @@ void propagate_new_equalities(THD *thd, Item *cond, while ((item= li++)) { COND_EQUAL *new_inherited= and_level && item->type() == Item::COND_ITEM ? - &((Item_cond_and *) cond)->cond_equal : + &((Item_cond_and *) cond)->m_cond_equal : inherited; propagate_new_equalities(thd, item, new_equalities, new_inherited, is_simplifiable_cond); @@ -13970,18 +15286,15 @@ void propagate_new_equalities(THD *thd, Item *cond, equality->upper_levels= inherited; while ((equal_item= it++)) { - equality->merge_with_check(equal_item, true); + equality->merge_with_check(thd, equal_item, true); } if (equality->const_item() && !equality->val_int()) *is_simplifiable_cond= true; } else { - uchar* is_subst_valid= (uchar *) Item::ANY_SUBST; - cond= cond->compile(&Item::subst_argument_checker, - &is_subst_valid, - &Item::equal_fields_propagator, - (uchar *) inherited); + cond= cond->propagate_equal_fields(thd, + Item::Context_boolean(), inherited); cond->update_used_tables(); } } @@ -14022,20 +15335,9 @@ bool cond_is_datetime_is_null(Item *cond) if (cond->type() == Item::FUNC_ITEM && ((Item_func*) cond)->functype() == Item_func::ISNULL_FUNC) { - Item **args= ((Item_func_isnull*) cond)->arguments(); - if (args[0]->real_item()->type() == Item::FIELD_ITEM) - { - Field *field=((Item_field*) (args[0]->real_item()))->field; - - if (((field->type() == MYSQL_TYPE_DATE) || - (field->type() == MYSQL_TYPE_DATETIME)) && - (field->flags & NOT_NULL_FLAG)) - { - return TRUE; - } - } + return ((Item_func_isnull*) cond)->arg_is_datetime_notnull_field(); } - return FALSE; + return false; } @@ -14121,295 +15423,267 @@ bool cond_is_datetime_is_null(Item *cond) => SELECT * FROM t1 WHERE (b = 5) AND (a = 5) */ -static COND * -internal_remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) + +COND * +Item_cond::remove_eq_conds(THD *thd, Item::cond_result *cond_value, + bool top_level_arg) { - if (cond->type() == Item::COND_ITEM) - { - bool and_level= ((Item_cond*) cond)->functype() - == Item_func::COND_AND_FUNC; - List<Item> *cond_arg_list= ((Item_cond*) cond)->argument_list(); + bool and_level= functype() == Item_func::COND_AND_FUNC; + List<Item> *cond_arg_list= argument_list(); - if (and_level) - { - /* - Remove multiple equalities that became always true (e.g. after - constant row substitution). - They would be removed later in the function anyway, but the list of - them cond_equal.current_level also must be adjusted correspondingly. - So it's easier to do it at one pass through the list of the equalities. - */ - List<Item_equal> *cond_equalities= - &((Item_cond_and *) cond)->cond_equal.current_level; - cond_arg_list->disjoin((List<Item> *) cond_equalities); - List_iterator<Item_equal> it(*cond_equalities); - Item_equal *eq_item; - while ((eq_item= it++)) - { - if (eq_item->const_item() && eq_item->val_int()) - it.remove(); - } - cond_arg_list->concat((List<Item> *) cond_equalities); - } + if (and_level) + { + /* + Remove multiple equalities that became always true (e.g. after + constant row substitution). + They would be removed later in the function anyway, but the list of + them cond_equal.current_level also must be adjusted correspondingly. + So it's easier to do it at one pass through the list of the equalities. + */ + List<Item_equal> *cond_equalities= + &((Item_cond_and *) this)->m_cond_equal.current_level; + cond_arg_list->disjoin((List<Item> *) cond_equalities); + List_iterator<Item_equal> it(*cond_equalities); + Item_equal *eq_item; + while ((eq_item= it++)) + { + if (eq_item->const_item() && eq_item->val_int()) + it.remove(); + } + cond_arg_list->append((List<Item> *) cond_equalities); + } - List<Item_equal> new_equalities; - List_iterator<Item> li(*cond_arg_list); - bool should_fix_fields= 0; - Item::cond_result tmp_cond_value; - Item *item; + List<Item_equal> new_equalities; + List_iterator<Item> li(*cond_arg_list); + bool should_fix_fields= 0; + Item::cond_result tmp_cond_value; + Item *item; - /* - If the list cond_arg_list became empty then it consisted only - of always true multiple equalities. - */ - *cond_value= cond_arg_list->elements ? Item::COND_UNDEF : Item::COND_TRUE; + /* + If the list cond_arg_list became empty then it consisted only + of always true multiple equalities. + */ + *cond_value= cond_arg_list->elements ? Item::COND_UNDEF : Item::COND_TRUE; - while ((item=li++)) + while ((item=li++)) + { + Item *new_item= item->remove_eq_conds(thd, &tmp_cond_value, false); + if (!new_item) { - Item *new_item=internal_remove_eq_conds(thd, item, &tmp_cond_value); - if (!new_item) - { - /* This can happen only when item is converted to TRUE or FALSE */ - li.remove(); - } - else if (item != new_item) + /* This can happen only when item is converted to TRUE or FALSE */ + li.remove(); + } + else if (item != new_item) + { + /* + This can happen when: + - item was an OR formula converted to one disjunct + - item was an AND formula converted to one conjunct + In these cases the disjunct/conjunct must be merged into the + argument list of cond. + */ + if (new_item->type() == Item::COND_ITEM && + item->type() == Item::COND_ITEM) { - /* - This can happen when: - - item was an OR formula converted to one disjunct - - item was an AND formula converted to one conjunct - In these cases the disjunct/conjunct must be merged into the - argument list of cond. - */ - if (new_item->type() == Item::COND_ITEM && - item->type() == Item::COND_ITEM) + DBUG_ASSERT(functype() == ((Item_cond *) new_item)->functype()); + List<Item> *new_item_arg_list= + ((Item_cond *) new_item)->argument_list(); + if (and_level) { - DBUG_ASSERT(((Item_cond *) cond)->functype() == - ((Item_cond *) new_item)->functype()); - List<Item> *new_item_arg_list= - ((Item_cond *) new_item)->argument_list(); - if (and_level) - { + /* + If new_item is an AND formula then multiple equalities + of new_item_arg_list must merged into multiple equalities + of cond_arg_list. + */ + List<Item_equal> *new_item_equalities= + &((Item_cond_and *) new_item)->m_cond_equal.current_level; + if (!new_item_equalities->is_empty()) + { /* - If new_item is an AND formula then multiple equalities - of new_item_arg_list must merged into multiple equalities - of cond_arg_list. - */ - List<Item_equal> *new_item_equalities= - &((Item_cond_and *) new_item)->cond_equal.current_level; - if (!new_item_equalities->is_empty()) - { - /* - Cut the multiple equalities from the new_item_arg_list and - append them on the list new_equalities. Later the equalities - from this list will be merged into the multiple equalities - of cond_arg_list all together. - */ - new_item_arg_list->disjoin((List<Item> *) new_item_equalities); - new_equalities.concat(new_item_equalities); - } - } - if (new_item_arg_list->is_empty()) - li.remove(); - else - { - uint cnt= new_item_arg_list->elements; - li.replace(*new_item_arg_list); - /* Make iterator li ignore new items */ - for (cnt--; cnt; cnt--) - li++; - should_fix_fields= 1; + Cut the multiple equalities from the new_item_arg_list and + append them on the list new_equalities. Later the equalities + from this list will be merged into the multiple equalities + of cond_arg_list all together. + */ + new_item_arg_list->disjoin((List<Item> *) new_item_equalities); + new_equalities.append(new_item_equalities); } } - else if (and_level && - new_item->type() == Item::FUNC_ITEM && - ((Item_cond*) new_item)->functype() == - Item_func::MULT_EQUAL_FUNC) - { + if (new_item_arg_list->is_empty()) li.remove(); - new_equalities.push_back((Item_equal *) new_item); - } else - { - if (new_item->type() == Item::COND_ITEM && - ((Item_cond*) new_item)->functype() == - ((Item_cond*) cond)->functype()) - { - List<Item> *new_item_arg_list= - ((Item_cond *) new_item)->argument_list(); - uint cnt= new_item_arg_list->elements; - li.replace(*new_item_arg_list); - /* Make iterator li ignore new items */ - for (cnt--; cnt; cnt--) - li++; - } - else - li.replace(new_item); + { + uint cnt= new_item_arg_list->elements; + li.replace(*new_item_arg_list); + /* Make iterator li ignore new items */ + for (cnt--; cnt; cnt--) + li++; should_fix_fields= 1; - } - } - if (*cond_value == Item::COND_UNDEF) - *cond_value=tmp_cond_value; - switch (tmp_cond_value) { - case Item::COND_OK: // Not TRUE or FALSE - if (and_level || *cond_value == Item::COND_FALSE) - *cond_value=tmp_cond_value; - break; - case Item::COND_FALSE: - if (and_level) - { - *cond_value=tmp_cond_value; - return (COND*) 0; // Always false - } - break; - case Item::COND_TRUE: - if (!and_level) - { - *cond_value= tmp_cond_value; - return (COND*) 0; // Always true - } - break; - case Item::COND_UNDEF: // Impossible - break; /* purecov: deadcode */ - } - } - if (!new_equalities.is_empty()) - { - DBUG_ASSERT(and_level); - /* - Merge multiple equalities that were cut from the results of - simplification of OR formulas converted into AND formulas. - These multiple equalities are to be merged into the - multiple equalities of cond_arg_list. - */ - COND_EQUAL *cond_equal= &((Item_cond_and *) cond)->cond_equal; - List<Item_equal> *cond_equalities= &cond_equal->current_level; - cond_arg_list->disjoin((List<Item> *) cond_equalities); - Item_equal *equality; - List_iterator_fast<Item_equal> it(new_equalities); - while ((equality= it++)) - { - equality->upper_levels= cond_equal->upper_levels; - equality->merge_into_list(cond_equalities, false, false); - List_iterator_fast<Item_equal> ei(*cond_equalities); - while ((equality= ei++)) - { - if (equality->const_item() && !equality->val_int()) - { - *cond_value= Item::COND_FALSE; - return (COND*) 0; - } } } - cond_arg_list->concat((List<Item> *) cond_equalities); - /* - Propagate the newly formed multiple equalities to - the all AND/OR levels of cond - */ - bool is_simplifiable_cond= false; - propagate_new_equalities(thd, cond, cond_equalities, - cond_equal->upper_levels, - &is_simplifiable_cond); - /* - If the above propagation of multiple equalities brings us - to multiple equalities that are always FALSE then try to - simplify the condition with remove_eq_cond() again. - */ - if (is_simplifiable_cond) + else if (and_level && + new_item->type() == Item::FUNC_ITEM && + ((Item_cond*) new_item)->functype() == + Item_func::MULT_EQUAL_FUNC) { - if (!(cond= internal_remove_eq_conds(thd, cond, cond_value))) - return cond; - } - should_fix_fields= 1; + li.remove(); + new_equalities.push_back((Item_equal *) new_item, thd->mem_root); + } + else + { + if (new_item->type() == Item::COND_ITEM && + ((Item_cond*) new_item)->functype() == functype()) + { + List<Item> *new_item_arg_list= + ((Item_cond *) new_item)->argument_list(); + uint cnt= new_item_arg_list->elements; + li.replace(*new_item_arg_list); + /* Make iterator li ignore new items */ + for (cnt--; cnt; cnt--) + li++; + } + else + li.replace(new_item); + should_fix_fields= 1; + } } - if (should_fix_fields) - cond->update_used_tables(); - - if (!((Item_cond*) cond)->argument_list()->elements || - *cond_value != Item::COND_OK) - return (COND*) 0; - if (((Item_cond*) cond)->argument_list()->elements == 1) - { // Remove list - item= ((Item_cond*) cond)->argument_list()->head(); - ((Item_cond*) cond)->argument_list()->empty(); - return item; + if (*cond_value == Item::COND_UNDEF) + *cond_value= tmp_cond_value; + switch (tmp_cond_value) { + case Item::COND_OK: // Not TRUE or FALSE + if (and_level || *cond_value == Item::COND_FALSE) + *cond_value=tmp_cond_value; + break; + case Item::COND_FALSE: + if (and_level) + { + *cond_value= tmp_cond_value; + return (COND*) 0; // Always false + } + break; + case Item::COND_TRUE: + if (!and_level) + { + *cond_value= tmp_cond_value; + return (COND*) 0; // Always true + } + break; + case Item::COND_UNDEF: // Impossible + break; /* purecov: deadcode */ } } - else if (cond_is_datetime_is_null(cond)) + COND *cond= this; + if (!new_equalities.is_empty()) { - /* fix to replace 'NULL' dates with '0' (shreeve@uci.edu) */ + DBUG_ASSERT(and_level); /* - See BUG#12594011 - Documentation says that - SELECT datetime_notnull d FROM t1 WHERE d IS NULL - shall return rows where d=='0000-00-00' - - Thus, for DATE and DATETIME columns defined as NOT NULL, - "date_notnull IS NULL" has to be modified to - "date_notnull IS NULL OR date_notnull == 0" (if outer join) - "date_notnull == 0" (otherwise) - + Merge multiple equalities that were cut from the results of + simplification of OR formulas converted into AND formulas. + These multiple equalities are to be merged into the + multiple equalities of cond_arg_list. */ - Item **args= ((Item_func_isnull*) cond)->arguments(); - Field *field=((Item_field*) (args[0]->real_item()))->field; - - Item *item0= new(thd->mem_root) Item_int((longlong)0, 1); - Item *eq_cond= new(thd->mem_root) Item_func_eq(args[0], item0); - if (!eq_cond) - return cond; - - if (field->table->pos_in_table_list->is_inner_table_of_outer_join()) - { - // outer join: transform "col IS NULL" to "col IS NULL or col=0" - Item *or_cond= new(thd->mem_root) Item_cond_or(eq_cond, cond); - if (!or_cond) - return cond; - cond= or_cond; + COND_EQUAL *cond_equal= &((Item_cond_and *) this)->m_cond_equal; + List<Item_equal> *cond_equalities= &cond_equal->current_level; + cond_arg_list->disjoin((List<Item> *) cond_equalities); + Item_equal *equality; + List_iterator_fast<Item_equal> it(new_equalities); + while ((equality= it++)) + { + equality->upper_levels= cond_equal->upper_levels; + equality->merge_into_list(thd, cond_equalities, false, false); + List_iterator_fast<Item_equal> ei(*cond_equalities); + while ((equality= ei++)) + { + if (equality->const_item() && !equality->val_int()) + { + *cond_value= Item::COND_FALSE; + return (COND*) 0; + } + } } - else + cond_arg_list->append((List<Item> *) cond_equalities); + /* + Propagate the newly formed multiple equalities to + the all AND/OR levels of cond + */ + bool is_simplifiable_cond= false; + propagate_new_equalities(thd, this, cond_equalities, + cond_equal->upper_levels, + &is_simplifiable_cond); + /* + If the above propagation of multiple equalities brings us + to multiple equalities that are always FALSE then try to + simplify the condition with remove_eq_cond() again. + */ + if (is_simplifiable_cond) { - // not outer join: transform "col IS NULL" to "col=0" - cond= eq_cond; + if (!(cond= cond->remove_eq_conds(thd, cond_value, false))) + return cond; } + should_fix_fields= 1; + } + if (should_fix_fields) + cond->update_used_tables(); - cond->fix_fields(thd, &cond); + if (!((Item_cond*) cond)->argument_list()->elements || + *cond_value != Item::COND_OK) + return (COND*) 0; + if (((Item_cond*) cond)->argument_list()->elements == 1) + { // Remove list + item= ((Item_cond*) cond)->argument_list()->head(); + ((Item_cond*) cond)->argument_list()->empty(); + return item; + } + *cond_value= Item::COND_OK; + return cond; +} - if (cond->const_item() && !cond->is_expensive()) - { - *cond_value= eval_const_cond(cond) ? Item::COND_TRUE : Item::COND_FALSE; - return (COND*) 0; - } + +COND * +Item::remove_eq_conds(THD *thd, Item::cond_result *cond_value, bool top_level_arg) +{ + if (const_item() && !is_expensive()) + { + *cond_value= eval_const_cond() ? Item::COND_TRUE : Item::COND_FALSE; + return (COND*) 0; } - else if (cond->const_item() && !cond->is_expensive()) + *cond_value= Item::COND_OK; + return this; // Point at next and level +} + + +COND * +Item_bool_func2::remove_eq_conds(THD *thd, Item::cond_result *cond_value, + bool top_level_arg) +{ + if (const_item() && !is_expensive()) { - *cond_value= eval_const_cond(cond) ? Item::COND_TRUE : Item::COND_FALSE; + *cond_value= eval_const_cond() ? Item::COND_TRUE : Item::COND_FALSE; return (COND*) 0; } - else if ((*cond_value= cond->eq_cmp_result()) != Item::COND_OK) - { // boolan compare function - Item *left_item= ((Item_func*) cond)->arguments()[0]; - Item *right_item= ((Item_func*) cond)->arguments()[1]; - if (left_item->eq(right_item,1)) + if ((*cond_value= eq_cmp_result()) != Item::COND_OK) + { + if (args[0]->eq(args[1], true)) { - if (!left_item->maybe_null || - ((Item_func*) cond)->functype() == Item_func::EQUAL_FUNC) - return (COND*) 0; // Compare of identical items + if (!args[0]->maybe_null || functype() == Item_func::EQUAL_FUNC) + return (COND*) 0; // Compare of identical items } } - *cond_value=Item::COND_OK; - return cond; // Point at next and level + *cond_value= Item::COND_OK; + return this; // Point at next and level } + /** Remove const and eq items. Return new item, or NULL if no condition cond_value is set to according: COND_OK query is possible (field = constant) - COND_TRUE always true ( 1 = 1 ) - COND_FALSE always false ( 1 = 2 ) + COND_TRUE always true ( 1 = 1 ) + COND_FALSE always false ( 1 = 2 ) SYNPOSIS remove_eq_conds() - thd THD environment + thd THD environment cond the condition to handle cond_value the resulting value of the condition @@ -14421,11 +15695,67 @@ internal_remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) */ COND * -remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) +Item_func_isnull::remove_eq_conds(THD *thd, Item::cond_result *cond_value, + bool top_level_arg) { - if (cond->type() == Item::FUNC_ITEM && - ((Item_func*) cond)->functype() == Item_func::ISNULL_FUNC) + Item *real_item= args[0]->real_item(); + if (real_item->type() == Item::FIELD_ITEM) { + Field *field= ((Item_field*) real_item)->field; + + if (((field->type() == MYSQL_TYPE_DATE) || + (field->type() == MYSQL_TYPE_DATETIME)) && + (field->flags & NOT_NULL_FLAG)) + { + /* fix to replace 'NULL' dates with '0' (shreeve@uci.edu) */ + /* + See BUG#12594011 + Documentation says that + SELECT datetime_notnull d FROM t1 WHERE d IS NULL + shall return rows where d=='0000-00-00' + + Thus, for DATE and DATETIME columns defined as NOT NULL, + "date_notnull IS NULL" has to be modified to + "date_notnull IS NULL OR date_notnull == 0" (if outer join) + "date_notnull == 0" (otherwise) + + */ + + Item *item0= new(thd->mem_root) Item_int(thd, (longlong) 0, 1); + Item *eq_cond= new(thd->mem_root) Item_func_eq(thd, args[0], item0); + if (!eq_cond) + return this; + + COND *cond= this; + if (field->table->pos_in_table_list->is_inner_table_of_outer_join()) + { + // outer join: transform "col IS NULL" to "col IS NULL or col=0" + Item *or_cond= new(thd->mem_root) Item_cond_or(thd, eq_cond, this); + if (!or_cond) + return this; + cond= or_cond; + } + else + { + // not outer join: transform "col IS NULL" to "col=0" + cond= eq_cond; + } + + cond->fix_fields(thd, &cond); + /* + Note: although args[0] is a field, cond can still be a constant + (in case field is a part of a dependent subquery). + + Note: we call cond->Item::remove_eq_conds() non-virtually (statically) + for performance purpose. + A non-qualified call, i.e. just cond->remove_eq_conds(), + would call Item_bool_func2::remove_eq_conds() instead, which would + try to do some extra job to detect if args[0] and args[1] are + equivalent items. We know they are not (we have field=0 here). + */ + return cond->Item::remove_eq_conds(thd, cond_value, false); + } + /* Handles this special case for some ODBC applications: The are requesting the row that was just updated with a auto_increment @@ -14434,35 +15764,37 @@ remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) SELECT * from table_name where auto_increment_column IS NULL This will be changed to: SELECT * from table_name where auto_increment_column = LAST_INSERT_ID + + Note, this substitution is done if the NULL test is the only condition! + If the NULL test is a part of a more complex condition, it is not + substituted and is treated normally: + WHERE auto_increment IS NULL AND something_else */ - Item_func_isnull *func=(Item_func_isnull*) cond; - Item **args= func->arguments(); - if (args[0]->type() == Item::FIELD_ITEM) + if (top_level_arg) // "auto_increment_column IS NULL" is the only condition { - Field *field=((Item_field*) args[0])->field; if (field->flags & AUTO_INCREMENT_FLAG && !field->table->maybe_null && - (thd->variables.option_bits & OPTION_AUTO_IS_NULL) && - (thd->first_successful_insert_id_in_prev_stmt > 0 && + (thd->variables.option_bits & OPTION_AUTO_IS_NULL) && + (thd->first_successful_insert_id_in_prev_stmt > 0 && thd->substitute_null_with_insert_id)) { -#ifdef HAVE_QUERY_CACHE - query_cache_abort(&thd->query_cache_tls); -#endif - COND *new_cond; - if ((new_cond= new Item_func_eq(args[0], - new Item_int("last_insert_id()", + #ifdef HAVE_QUERY_CACHE + query_cache_abort(thd, &thd->query_cache_tls); + #endif + COND *new_cond, *cond= this; + if ((new_cond= new (thd->mem_root) Item_func_eq(thd, args[0], + new (thd->mem_root) Item_int(thd, "last_insert_id()", thd->read_first_successful_insert_id_in_prev_stmt(), MY_INT64_NUM_DECIMAL_DIGITS)))) - { - cond=new_cond; + { + cond= new_cond; /* Item_func_eq can't be fixed after creation so we do not check cond->fixed, also it do not need tables so we use 0 as second argument. */ - cond->fix_fields(thd, &cond); - } + cond->fix_fields(thd, &cond); + } /* IS NULL should be mapped to LAST_INSERT_ID only for first row, so clear for next row @@ -14474,7 +15806,7 @@ remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) } } } - return internal_remove_eq_conds(thd, cond, cond_value); // Scan all the condition + return Item::remove_eq_conds(thd, cond_value, top_level_arg); } @@ -14501,11 +15833,18 @@ remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) @retval true can be used @retval false cannot be used */ + +/* + psergey-todo: this returns false for int_column='1234' (here '1234' is a + constant. Need to discuss this with Bar). + + See also Field::test_if_equality_guaranees_uniqueness(const Item *item); +*/ static bool test_if_equality_guarantees_uniqueness(Item *l, Item *r) { - return r->const_item() && - item_cmp_type(l->cmp_type(), r->cmp_type()) == l->cmp_type() && + return (r->const_item() || !(r->used_tables() & ~OUTER_REF_TABLE_BIT)) && + item_cmp_type(l, r) == l->cmp_type() && (l->cmp_type() != STRING_RESULT || l->collation.collation == r->collation.collation); } @@ -14624,8 +15963,6 @@ const_expression_in_where(COND *cond, Item *comp_item, Field *comp_field, the record in the original table. If item == NULL then fill_record() will update the temporary table - @param convert_blob_length If >0 create a varstring(convert_blob_length) - field instead of blob. @retval NULL on error @@ -14635,23 +15972,12 @@ const_expression_in_where(COND *cond, Item *comp_item, Field *comp_field, Field *create_tmp_field_from_field(THD *thd, Field *org_field, const char *name, TABLE *table, - Item_field *item, uint convert_blob_length) + Item_field *item) { Field *new_field; - /* - Make sure that the blob fits into a Field_varstring which has - 2-byte lenght. - */ - if (convert_blob_length && convert_blob_length <= Field_varstring::MAX_SIZE && - (org_field->flags & BLOB_FLAG)) - new_field= new Field_varstring(convert_blob_length, - org_field->maybe_null(), - org_field->field_name, table->s, - org_field->charset()); - else - new_field= org_field->new_field(thd->mem_root, table, - table == org_field->table); + new_field= org_field->make_new_field(thd->mem_root, table, + table == org_field->table); if (new_field) { new_field->init(table); @@ -14670,92 +15996,56 @@ Field *create_tmp_field_from_field(THD *thd, Field *org_field, ((Field_double *) new_field)->not_fixed= TRUE; new_field->vcol_info= 0; new_field->stored_in_db= TRUE; + new_field->cond_selectivity= 1.0; + new_field->next_equal_field= NULL; + new_field->option_list= NULL; + new_field->option_struct= NULL; } return new_field; } -/** - Create field for temporary table using type of given item. - - @param thd Thread handler - @param item Item to create a field for - @param table Temporary table - @param copy_func If set and item is a function, store copy of - item in this array - @param modify_item 1 if item->result_field should point to new - item. This is relevent for how fill_record() - is going to work: - If modify_item is 1 then fill_record() will - update the record in the original table. - If modify_item is 0 then fill_record() will - update the temporary table - @param convert_blob_length If >0 create a varstring(convert_blob_length) - field instead of blob. - @retval - 0 on error - @retval - new_created field -*/ - -static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, - Item ***copy_func, bool modify_item, - uint convert_blob_length) +Field *Item::create_tmp_field(bool group, TABLE *table, uint convert_int_length) { - bool maybe_null= item->maybe_null; - Field *new_field; - LINT_INIT(new_field); + Field *UNINIT_VAR(new_field); + MEM_ROOT *mem_root= table->in_use->mem_root; - /* - To preserve type or DATE/TIME and GEOMETRY fields, - they need to be handled separately. - */ - if (item->cmp_type() == TIME_RESULT || - item->field_type() == MYSQL_TYPE_GEOMETRY) - new_field= item->tmp_table_field_from_field_type(table, 1); - else - switch (item->result_type()) { + switch (cmp_type()) { case REAL_RESULT: - new_field= new Field_double(item->max_length, maybe_null, - item->name, item->decimals, TRUE); + new_field= new (mem_root) + Field_double(max_length, maybe_null, name, decimals, TRUE); break; case INT_RESULT: - /* + /* Select an integer type with the minimal fit precision. - MY_INT32_NUM_DECIMAL_DIGITS is sign inclusive, don't consider the sign. - Values with MY_INT32_NUM_DECIMAL_DIGITS digits may or may not fit into - Field_long : make them Field_longlong. + convert_int_length is sign inclusive, don't consider the sign. */ - if (item->max_length >= (MY_INT32_NUM_DECIMAL_DIGITS - 1)) - new_field=new Field_longlong(item->max_length, maybe_null, - item->name, item->unsigned_flag); + if (max_char_length() > convert_int_length) + new_field= new (mem_root) + Field_longlong(max_char_length(), maybe_null, name, unsigned_flag); else - new_field=new Field_long(item->max_length, maybe_null, - item->name, item->unsigned_flag); + new_field= new (mem_root) + Field_long(max_char_length(), maybe_null, name, unsigned_flag); + break; + case TIME_RESULT: + new_field= tmp_table_field_from_field_type(table, true, false); break; case STRING_RESULT: - DBUG_ASSERT(item->collation.collation); - - /* - Make sure that the blob fits into a Field_varstring which has - 2-byte lenght. + DBUG_ASSERT(collation.collation); + /* + GEOMETRY fields have STRING_RESULT result type. + To preserve type they needed to be handled separately. */ - if (item->max_length/item->collation.collation->mbmaxlen > 255 && - convert_blob_length <= Field_varstring::MAX_SIZE && - convert_blob_length) - new_field= new Field_varstring(convert_blob_length, maybe_null, - item->name, table->s, - item->collation.collation); + if (field_type() == MYSQL_TYPE_GEOMETRY) + new_field= tmp_table_field_from_field_type(table, true, false); else - new_field= item->make_string_field(table); - new_field->set_derivation(item->collation.derivation, - item->collation.repertoire); + new_field= make_string_field(table); + new_field->set_derivation(collation.derivation, collation.repertoire); break; case DECIMAL_RESULT: - new_field= Field_new_decimal::create_from_item(item); + new_field= Field_new_decimal::create_from_item(mem_root, this); break; case ROW_RESULT: - default: // This case should never be choosen DBUG_ASSERT(0); new_field= 0; @@ -14763,6 +16053,39 @@ static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, } if (new_field) new_field->init(table); + return new_field; +} + + + +/** + Create field for temporary table using type of given item. + + @param thd Thread handler + @param item Item to create a field for + @param table Temporary table + @param copy_func If set and item is a function, store copy of + item in this array + @param modify_item 1 if item->result_field should point to new + item. This is relevent for how fill_record() + is going to work: + If modify_item is 1 then fill_record() will + update the record in the original table. + If modify_item is 0 then fill_record() will + update the temporary table + + @retval + 0 on error + @retval + new_created field +*/ + +static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, + Item ***copy_func, bool modify_item) +{ + Field *UNINIT_VAR(new_field); + DBUG_ASSERT(thd == table->in_use); + new_field= item->Item::create_tmp_field(false, table); if (copy_func && (item->is_result_field() || @@ -14789,23 +16112,21 @@ static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, new_created field */ -Field *create_tmp_field_for_schema(THD *thd, Item *item, TABLE *table) +Field *Item::create_field_for_schema(THD *thd, TABLE *table) { - if (item->field_type() == MYSQL_TYPE_VARCHAR) + if (field_type() == MYSQL_TYPE_VARCHAR) { Field *field; - if (item->max_length > MAX_FIELD_VARCHARLENGTH) - field= new Field_blob(item->max_length, item->maybe_null, - item->name, item->collation.collation); + if (max_length > MAX_FIELD_VARCHARLENGTH) + field= new Field_blob(max_length, maybe_null, name, collation.collation); else - field= new Field_varstring(item->max_length, item->maybe_null, - item->name, - table->s, item->collation.collation); + field= new Field_varstring(max_length, maybe_null, name, + table->s, collation.collation); if (field) field->init(table); return field; } - return item->tmp_table_field_from_field_type(table, 0); + return tmp_table_field_from_field_type(table, false, false); } @@ -14829,8 +16150,6 @@ Field *create_tmp_field_for_schema(THD *thd, Item *item, TABLE *table) the record in the original table. If modify_item is 0 then fill_record() will update the temporary table - @param convert_blob_length If >0 create a varstring(convert_blob_length) - field instead of blob. @retval 0 on error @@ -14843,8 +16162,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, Field **default_field, bool group, bool modify_item, bool table_cant_handle_bit_fields, - bool make_copy_field, - uint convert_blob_length) + bool make_copy_field) { Field *result; Item::Type orig_type= type; @@ -14861,8 +16179,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, switch (type) { case Item::SUM_FUNC_ITEM: { - Item_sum *item_sum=(Item_sum*) item; - result= item_sum->create_tmp_field(group, table, convert_blob_length); + result= item->create_tmp_field(group, table); if (!result) my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); return result; @@ -14898,7 +16215,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, item->maybe_null= orig_item->maybe_null; } result= create_tmp_field_from_item(thd, item, table, NULL, - modify_item, convert_blob_length); + modify_item); *from_field= field->field; if (result && modify_item) field->result_field= result; @@ -14910,7 +16227,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, { *from_field= field->field; result= create_tmp_field_from_item(thd, item, table, copy_func, - modify_item, convert_blob_length); + modify_item); if (result && modify_item) field->result_field= result; } @@ -14920,8 +16237,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, item->name, table, modify_item ? field : - NULL, - convert_blob_length); + NULL); if (orig_type == Item::REF_ITEM && orig_modify) ((Item_ref*)orig_item)->set_result_field(result); /* @@ -14955,8 +16271,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, sp_result_field, item_func_sp->name, table, - NULL, - convert_blob_length); + NULL); if (modify_item) item->set_result_field(result_field); @@ -14975,6 +16290,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, case Item::REAL_ITEM: case Item::DECIMAL_ITEM: case Item::STRING_ITEM: + case Item::DATE_ITEM: case Item::REF_ITEM: case Item::NULL_ITEM: case Item::VARBIN_ITEM: @@ -14988,7 +16304,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, } return create_tmp_field_from_item(thd, item, table, (make_copy_field ? 0 : copy_func), - modify_item, convert_blob_length); + modify_item); case Item::TYPE_HOLDER: result= ((Item_type_holder *)item)->make_field_by_type(table); result->set_derivation(item->collation.derivation, @@ -15010,17 +16326,21 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, void setup_tmp_table_column_bitmaps(TABLE *table, uchar *bitmaps) { uint field_count= table->s->fields; - bitmap_init(&table->def_read_set, (my_bitmap_map*) bitmaps, field_count, + uint bitmap_size= bitmap_buffer_size(field_count); + + DBUG_ASSERT(table->s->vfields == 0 && table->def_vcol_set == 0); + + my_bitmap_init(&table->def_read_set, (my_bitmap_map*) bitmaps, field_count, FALSE); - bitmap_init(&table->def_vcol_set, - (my_bitmap_map*) (bitmaps+ bitmap_buffer_size(field_count)), - field_count, FALSE); - bitmap_init(&table->tmp_set, - (my_bitmap_map*) (bitmaps+ 2*bitmap_buffer_size(field_count)), - field_count, FALSE); - bitmap_init(&table->eq_join_set, - (my_bitmap_map*) (bitmaps+ 3*bitmap_buffer_size(field_count)), - field_count, FALSE); + bitmaps+= bitmap_size; + my_bitmap_init(&table->tmp_set, + (my_bitmap_map*) bitmaps, field_count, FALSE); + bitmaps+= bitmap_size; + my_bitmap_init(&table->eq_join_set, + (my_bitmap_map*) bitmaps, field_count, FALSE); + bitmaps+= bitmap_size; + my_bitmap_init(&table->cond_set, + (my_bitmap_map*) bitmaps, field_count, FALSE); /* write_set and all_set are copies of read_set */ table->def_write_set= table->def_read_set; table->s->all_set= table->def_read_set; @@ -15046,11 +16366,15 @@ void setup_tmp_table_column_bitmaps(TABLE *table, uchar *bitmaps) @param param a description used as input to create the table @param fields list of items that will be used to define column types of the table (also see NOTES) - @param group TODO document + @param group Create an unique key over all group by fields. + This is used to retrive the row during + end_write_group() and update them. @param distinct should table rows be distinct @param save_sum_fields see NOTES - @param select_options - @param rows_limit + @param select_options Optiions for how the select is run. + See sql_priv.h for a list of options. + @param rows_limit Maximum number of rows to insert into the + temporary table @param table_alias possible name of the temporary table that can be used for name resolving; can be "". */ @@ -15084,7 +16408,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, KEY *keyinfo; KEY_PART_INFO *key_part_info; Item **copy_func; - ENGINE_COLUMNDEF *recinfo; + TMP_ENGINE_COLUMNDEF *recinfo; /* total_uneven_bit_length is uneven bit length for visible fields hidden_uneven_bit_length is uneven bit length for hidden fields @@ -15098,9 +16422,8 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, ("table_alias: '%s' distinct: %d save_sum_fields: %d " "rows_limit: %lu group: %d", table_alias, (int) distinct, (int) save_sum_fields, - (ulong) rows_limit,test(group))); + (ulong) rows_limit, MY_TEST(group))); - status_var_increment(thd->status_var.created_tmp_tables); thd->query_plan_flags|= QPLAN_TMP_TABLE; if (use_temp_pool && !(test_flags & TEST_KEEP_TMP_TABLES)) @@ -15168,7 +16491,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, if (param->precomputed_group_by) copy_func_count+= param->sum_func_count; - init_sql_alloc(&own_root, TABLE_ALLOC_BLOCK_SIZE, 0); + init_sql_alloc(&own_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC)); if (!multi_alloc_root(&own_root, &table, sizeof(*table), @@ -15186,7 +16509,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, &tmpname, (uint) strlen(path)+1, &group_buff, (group && ! using_unique_constraint ? param->group_length : 0), - &bitmaps, bitmap_buffer_size(field_count)*4, + &bitmaps, bitmap_buffer_size(field_count)*5, NullS)) { if (temp_pool_slot != MY_BIT_NONE) @@ -15232,7 +16555,6 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, table->s= share; init_tmp_table_share(thd, share, "", 0, tmpname, tmpname); share->blob_field= blob_field; - share->blob_ptr_size= portable_sizeof_char_ptr; share->table_charset= param->table_charset; share->primary_key= MAX_KEY; // Indicate no primary key share->keys_for_keyread.init(); @@ -15286,8 +16608,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, create_tmp_field(thd, table, arg, arg->type(), ©_func, tmp_from_field, &default_field[fieldnr], group != 0,not_all_columns, - distinct, 0, - param->convert_blob_length); + distinct, false); if (!new_field) goto err; // Should be OOM tmp_from_field++; @@ -15307,7 +16628,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, string_total_length+= new_field->pack_length(); } thd->mem_root= mem_root_save; - arg= sum_item->set_arg(i, thd, new Item_field(new_field)); + arg= sum_item->set_arg(i, thd, new (thd->mem_root) Item_temptable_field(thd, new_field)); thd->mem_root= &table->mem_root; if (param->force_not_null_cols) { @@ -15343,7 +16664,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, key over a bit field as heap tables can't handle that. */ Field *new_field= (param->schema_table) ? - create_tmp_field_for_schema(thd, item, table) : + item->create_field_for_schema(thd, table) : create_tmp_field(thd, table, item, type, ©_func, tmp_from_field, &default_field[fieldnr], group != 0, @@ -15352,13 +16673,12 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, /* If item->marker == 4 then we force create_tmp_field to create a 64-bit longs for BIT fields because HEAP - tables can't index BIT fields directly. We do the same - for distinct, as we want the distinct index to be - usable in this case too. + tables can't index BIT fields directly. We do the + same for distinct, as we want the distinct index + to be usable in this case too. */ item->marker == 4 || param->bit_fields_as_long, - force_copy_fields, - param->convert_blob_length); + force_copy_fields); if (!new_field) { @@ -15374,16 +16694,19 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, created temporary table is not to be used for subquery materialization. - The reason is that for subqueries that require materialization as part - of their plan, we create the 'external' temporary table needed for IN - execution, after the 'internal' temporary table needed for grouping. - Since both the external and the internal temporary tables are created - for the same list of SELECT fields of the subquery, setting - 'result_field' for each invocation of create_tmp_table overrides the - previous value of 'result_field'. - - The condition below prevents the creation of the external temp table - to override the 'result_field' that was set for the internal temp table. + The reason is that for subqueries that require + materialization as part of their plan, we create the + 'external' temporary table needed for IN execution, after + the 'internal' temporary table needed for grouping. Since + both the external and the internal temporary tables are + created for the same list of SELECT fields of the subquery, + setting 'result_field' for each invocation of + create_tmp_table overrides the previous value of + 'result_field'. + + The condition below prevents the creation of the external + temp table to override the 'result_field' that was set for + the internal temp table. */ if (!agg_item->result_field || !param->materialized_subquery) agg_item->result_field= new_field; @@ -15475,6 +16798,12 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, if (!table->file) goto err; + if (table->file->set_ha_share_ref(&share->ha_share)) + { + delete table->file; + goto err; + } + if (!using_unique_constraint) reclength+= group_null_items; // null flag is stored separately @@ -15569,20 +16898,17 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, inherit the default value that is defined for the field referred by the Item_field object from which 'field' has been created. */ - my_ptrdiff_t diff; - Field *orig_field= default_field[i]; + const Field *orig_field= default_field[i]; /* Get the value from default_values */ - diff= (my_ptrdiff_t) (orig_field->table->s->default_values- - orig_field->table->record[0]); - orig_field->move_field_offset(diff); // Points now at default_values - if (orig_field->is_real_null()) + if (orig_field->is_null_in_record(orig_field->table->s->default_values)) field->set_null(); else { field->set_notnull(); - memcpy(field->ptr, orig_field->ptr, field->pack_length_in_rec()); + memcpy(field->ptr, + orig_field->ptr_in_record(orig_field->table->s->default_values), + field->pack_length_in_rec()); } - orig_field->move_field_offset(-diff); // Back to record[0] } if (from_field[i]) @@ -15621,7 +16947,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, share->max_rows= ~(ha_rows) 0; else share->max_rows= (ha_rows) (((share->db_type() == heap_hton) ? - min(thd->variables.tmp_table_size, + MY_MIN(thd->variables.tmp_table_size, thd->variables.max_heap_table_size) : thd->variables.tmp_table_size) / share->reclength); @@ -15641,18 +16967,21 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, table->group=group; /* Table is grouped by key */ param->group_buff=group_buff; share->keys=1; - share->uniques= test(using_unique_constraint); + share->uniques= MY_TEST(using_unique_constraint); table->key_info= table->s->key_info= keyinfo; table->keys_in_use_for_query.set_bit(0); share->keys_in_use.set_bit(0); keyinfo->key_part=key_part_info; keyinfo->flags=HA_NOSAME | HA_BINARY_PACK_KEY | HA_PACK_KEY; keyinfo->ext_key_flags= keyinfo->flags; - keyinfo->usable_key_parts=keyinfo->key_parts= param->group_parts; - keyinfo->ext_key_parts= keyinfo->key_parts; + keyinfo->usable_key_parts=keyinfo->user_defined_key_parts= param->group_parts; + keyinfo->ext_key_parts= keyinfo->user_defined_key_parts; keyinfo->key_length=0; - keyinfo->rec_per_key=0; + keyinfo->rec_per_key=NULL; + keyinfo->read_stats= NULL; + keyinfo->collected_stats= NULL; keyinfo->algorithm= HA_KEY_ALG_UNDEF; + keyinfo->is_statistics_from_stat_tables= FALSE; keyinfo->name= (char*) "group_key"; ORDER *cur_group= group; for (; cur_group ; cur_group= cur_group->next, key_part_info++) @@ -15692,7 +17021,8 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, if (!(cur_group->field= field->new_key_field(thd->mem_root,table, group_buff + - test(maybe_null), + MY_TEST(maybe_null), + key_part_info->length, field->null_ptr, field->null_bit))) goto err; /* purecov: inspected */ @@ -15746,16 +17076,18 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, share->uniques= 1; } null_pack_length-=hidden_null_pack_length; - keyinfo->key_parts= ((field_count-param->hidden_field_count)+ - (share->uniques ? test(null_pack_length) : 0)); - keyinfo->ext_key_parts= keyinfo->key_parts; + keyinfo->user_defined_key_parts= + ((field_count-param->hidden_field_count)+ + (share->uniques ? MY_TEST(null_pack_length) : 0)); + keyinfo->ext_key_parts= keyinfo->user_defined_key_parts; + keyinfo->usable_key_parts= keyinfo->user_defined_key_parts; table->distinct= 1; share->keys= 1; if (!(key_part_info= (KEY_PART_INFO*) alloc_root(&table->mem_root, - keyinfo->key_parts * sizeof(KEY_PART_INFO)))) + keyinfo->user_defined_key_parts * sizeof(KEY_PART_INFO)))) goto err; - bzero((void*) key_part_info, keyinfo->key_parts * sizeof(KEY_PART_INFO)); + bzero((void*) key_part_info, keyinfo->user_defined_key_parts * sizeof(KEY_PART_INFO)); table->keys_in_use_for_query.set_bit(0); share->keys_in_use.set_bit(0); table->key_info= table->s->key_info= keyinfo; @@ -15765,6 +17097,10 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, keyinfo->key_length= 0; // Will compute the sum of the parts below. keyinfo->name= (char*) "distinct_key"; keyinfo->algorithm= HA_KEY_ALG_UNDEF; + keyinfo->is_statistics_from_stat_tables= FALSE; + keyinfo->read_stats= NULL; + keyinfo->collected_stats= NULL; + /* Needed by non-merged semi-joins: SJ-Materialized table must have a valid rec_per_key array, because it participates in join optimization. Since @@ -15774,7 +17110,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, (For table record count, we calculate and set JOIN_TAB::found_records, see get_delayed_table_estimates()). */ - size_t rpk_size= keyinfo->key_parts* sizeof(keyinfo->rec_per_key[0]); + size_t rpk_size= keyinfo->user_defined_key_parts * sizeof(keyinfo->rec_per_key[0]); if (!(keyinfo->rec_per_key= (ulong*) alloc_root(&table->mem_root, rpk_size))) goto err; @@ -15935,29 +17271,28 @@ TABLE *create_virtual_tmp_table(THD *thd, List<Create_field> &field_list) &share, sizeof(*share), &field, (field_count + 1) * sizeof(Field*), &blob_field, (field_count+1) *sizeof(uint), - &bitmaps, bitmap_buffer_size(field_count)*4, + &bitmaps, bitmap_buffer_size(field_count)*5, NullS)) return 0; - bzero(table, sizeof(*table)); - bzero(share, sizeof(*share)); + table->reset(); table->field= field; table->s= share; table->temp_pool_slot= MY_BIT_NONE; + share->reset(); share->blob_field= blob_field; share->fields= field_count; - share->blob_ptr_size= portable_sizeof_char_ptr; setup_tmp_table_column_bitmaps(table, bitmaps); /* Create all fields and calculate the total length of record */ List_iterator_fast<Create_field> it(field_list); while ((cdef= it++)) { - *field= make_field(share, 0, cdef->length, + *field= make_field(share, thd->mem_root, 0, cdef->length, (uchar*) (f_maybe_null(cdef->pack_flag) ? "" : 0), f_maybe_null(cdef->pack_flag) ? 1 : 0, cdef->pack_flag, cdef->sql_type, cdef->charset, - cdef->geom_type, cdef->unireg_check, + cdef->geom_type, cdef->srid, cdef->unireg_check, cdef->interval, cdef->field_name); if (!*field) goto error; @@ -16043,19 +17378,23 @@ bool open_tmp_table(TABLE *table) HA_OPEN_TMP_TABLE | HA_OPEN_INTERNAL_TABLE))) { - table->file->print_error(error,MYF(0)); /* purecov: inspected */ - table->db_stat=0; - return(1); + table->file->print_error(error, MYF(0)); /* purecov: inspected */ + table->db_stat= 0; + return 1; } table->db_stat= HA_OPEN_KEYFILE+HA_OPEN_RNDFILE; - (void) table->file->extra(HA_EXTRA_QUICK); /* Faster */ - table->created= TRUE; - return(0); -} + (void) table->file->extra(HA_EXTRA_QUICK); /* Faster */ + if (!table->created) + { + table->created= TRUE; + table->in_use->inc_status_created_tmp_tables(); + } + return 0; +} -#if defined(WITH_ARIA_STORAGE_ENGINE) && defined(USE_ARIA_FOR_TMP_TABLES) +#ifdef USE_ARIA_FOR_TMP_TABLES /* Create internal (MyISAM or Maria) temporary table @@ -16071,12 +17410,12 @@ bool open_tmp_table(TABLE *table) Create an internal emporary table according to passed description. The is assumed to have one unique index or constraint. - The passed array or ENGINE_COLUMNDEF structures must have this form: + The passed array or TMP_ENGINE_COLUMNDEF structures must have this form: 1. 1-byte column (afaiu for 'deleted' flag) (note maybe not 1-byte when there are many nullable columns) 2. Table columns - 3. One free ENGINE_COLUMNDEF element (*recinfo points here) + 3. One free TMP_ENGINE_COLUMNDEF element (*recinfo points here) This function may use the free element to create hash column for unique constraint. @@ -16088,8 +17427,8 @@ bool open_tmp_table(TABLE *table) bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, - ENGINE_COLUMNDEF *start_recinfo, - ENGINE_COLUMNDEF **recinfo, + TMP_ENGINE_COLUMNDEF *start_recinfo, + TMP_ENGINE_COLUMNDEF **recinfo, ulonglong options) { int error; @@ -16103,11 +17442,11 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, { // Get keys for ni_create bool using_unique_constraint=0; HA_KEYSEG *seg= (HA_KEYSEG*) alloc_root(&table->mem_root, - sizeof(*seg) * keyinfo->key_parts); + sizeof(*seg) * keyinfo->user_defined_key_parts); if (!seg) goto err; - bzero(seg, sizeof(*seg) * keyinfo->key_parts); + bzero(seg, sizeof(*seg) * keyinfo->user_defined_key_parts); /* Note that a similar check is performed during subquery_types_allow_materialization. See MDEV-7122 for more details as @@ -16115,7 +17454,7 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, all tmp_table engines. */ if (keyinfo->key_length > table->file->max_key_length() || - keyinfo->key_parts > table->file->max_key_parts() || + keyinfo->user_defined_key_parts > table->file->max_key_parts() || share->uniques) { if (!share->uniques && !(keyinfo->flags & HA_NOSAME)) @@ -16130,7 +17469,7 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, share->uniques= 1; using_unique_constraint=1; bzero((char*) &uniquedef,sizeof(uniquedef)); - uniquedef.keysegs=keyinfo->key_parts; + uniquedef.keysegs=keyinfo->user_defined_key_parts; uniquedef.seg=seg; uniquedef.null_are_equal=1; @@ -16146,10 +17485,10 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, /* Create a key */ bzero((char*) &keydef,sizeof(keydef)); keydef.flag= keyinfo->flags & HA_NOSAME; - keydef.keysegs= keyinfo->key_parts; + keydef.keysegs= keyinfo->user_defined_key_parts; keydef.seg= seg; } - for (uint i=0; i < keyinfo->key_parts ; i++,seg++) + for (uint i=0; i < keyinfo->user_defined_key_parts ; i++,seg++) { Field *field=keyinfo->key_part[i].field; seg->flag= 0; @@ -16161,7 +17500,8 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, seg->type= ((keyinfo->key_part[i].key_type & FIELDFLAG_BINARY) ? HA_KEYTYPE_VARBINARY2 : HA_KEYTYPE_VARTEXT2); - seg->bit_start= (uint8)(field->pack_length() - share->blob_ptr_size); + seg->bit_start= (uint8)(field->pack_length() - + portable_sizeof_char_ptr); seg->flag= HA_BLOB_PART; seg->length=0; // Whole blob in unique constraint } @@ -16204,48 +17544,60 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, delete the row. The cases when this can happen is when there is a group by and no sum functions or if distinct is used. */ - if ((error= maria_create(share->table_name.str, - table->no_rows ? NO_RECORD : - (share->reclength < 64 && - !share->blob_fields ? STATIC_RECORD : - table->used_for_duplicate_elimination || - table->keep_row_order ? - DYNAMIC_RECORD : BLOCK_RECORD), - share->keys, &keydef, - (uint) (*recinfo-start_recinfo), - start_recinfo, - share->uniques, &uniquedef, - &create_info, - HA_CREATE_TMP_TABLE))) { - table->file->print_error(error,MYF(0)); /* purecov: inspected */ - table->db_stat=0; - goto err; + enum data_file_type file_type= table->no_rows ? NO_RECORD : + (share->reclength < 64 && !share->blob_fields ? STATIC_RECORD : + table->used_for_duplicate_elimination ? DYNAMIC_RECORD : BLOCK_RECORD); + uint create_flags= HA_CREATE_TMP_TABLE | HA_CREATE_INTERNAL_TABLE | + (table->keep_row_order ? HA_PRESERVE_INSERT_ORDER : 0); + + if (file_type != NO_RECORD && encrypt_tmp_disk_tables) + { + /* encryption is only supported for BLOCK_RECORD */ + file_type= BLOCK_RECORD; + if (table->used_for_duplicate_elimination) + { + /* + sql-layer expect the last column to be stored/restored also + when it's null. + + This is probably a bug (that sql-layer doesn't annotate + the column as not-null) but both heap, aria-static, aria-dynamic and + myisam has this property. aria-block_record does not since it + does not store null-columns at all. + Emulate behaviour by making column not-nullable when creating the + table. + */ + uint cols= (*recinfo-start_recinfo); + start_recinfo[cols-1].null_bit= 0; + } + } + + if ((error= maria_create(share->table_name.str, + file_type, + share->keys, &keydef, + (uint) (*recinfo-start_recinfo), + start_recinfo, + share->uniques, &uniquedef, + &create_info, + create_flags))) + { + table->file->print_error(error,MYF(0)); /* purecov: inspected */ + table->db_stat=0; + goto err; + } } - status_var_increment(table->in_use->status_var.created_tmp_disk_tables); + + table->in_use->inc_status_created_tmp_disk_tables(); + table->in_use->inc_status_created_tmp_tables(); table->in_use->query_plan_flags|= QPLAN_TMP_DISK; share->db_record_offset= 1; + table->created= TRUE; DBUG_RETURN(0); err: DBUG_RETURN(1); } - -bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table, - ENGINE_COLUMNDEF *start_recinfo, - ENGINE_COLUMNDEF **recinfo, - int error, - bool ignore_last_dupp_key_error, - bool *is_duplicate) -{ - return create_internal_tmp_table_from_heap2(thd, table, - start_recinfo, recinfo, error, - ignore_last_dupp_key_error, - maria_hton, - "converting HEAP to Aria", - is_duplicate); -} - #else /* @@ -16263,12 +17615,12 @@ bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table, Create an internal emporary table according to passed description. The is assumed to have one unique index or constraint. - The passed array or ENGINE_COLUMNDEF structures must have this form: + The passed array or TMP_ENGINE_COLUMNDEF structures must have this form: 1. 1-byte column (afaiu for 'deleted' flag) (note maybe not 1-byte when there are many nullable columns) 2. Table columns - 3. One free ENGINE_COLUMNDEF element (*recinfo points here) + 3. One free TMP_ENGINE_COLUMNDEF element (*recinfo points here) This function may use the free element to create hash column for unique constraint. @@ -16281,8 +17633,8 @@ bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table, /* Create internal MyISAM temporary table */ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, - ENGINE_COLUMNDEF *start_recinfo, - ENGINE_COLUMNDEF **recinfo, + TMP_ENGINE_COLUMNDEF *start_recinfo, + TMP_ENGINE_COLUMNDEF **recinfo, ulonglong options) { int error; @@ -16295,11 +17647,11 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, { // Get keys for ni_create bool using_unique_constraint=0; HA_KEYSEG *seg= (HA_KEYSEG*) alloc_root(&table->mem_root, - sizeof(*seg) * keyinfo->key_parts); + sizeof(*seg) * keyinfo->user_defined_key_parts); if (!seg) goto err; - bzero(seg, sizeof(*seg) * keyinfo->key_parts); + bzero(seg, sizeof(*seg) * keyinfo->user_defined_key_parts); /* Note that a similar check is performed during subquery_types_allow_materialization. See MDEV-7122 for more details as @@ -16307,7 +17659,7 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, all tmp_table engines. */ if (keyinfo->key_length > table->file->max_key_length() || - keyinfo->key_parts > table->file->max_key_parts() || + keyinfo->user_defined_key_parts > table->file->max_key_parts() || share->uniques) { /* Can't create a key; Make a unique constraint instead of a key */ @@ -16315,7 +17667,7 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, share->uniques= 1; using_unique_constraint=1; bzero((char*) &uniquedef,sizeof(uniquedef)); - uniquedef.keysegs=keyinfo->key_parts; + uniquedef.keysegs=keyinfo->user_defined_key_parts; uniquedef.seg=seg; uniquedef.null_are_equal=1; @@ -16332,10 +17684,10 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, bzero((char*) &keydef,sizeof(keydef)); keydef.flag= ((keyinfo->flags & HA_NOSAME) | HA_BINARY_PACK_KEY | HA_PACK_KEY); - keydef.keysegs= keyinfo->key_parts; + keydef.keysegs= keyinfo->user_defined_key_parts; keydef.seg= seg; } - for (uint i=0; i < keyinfo->key_parts ; i++,seg++) + for (uint i=0; i < keyinfo->user_defined_key_parts ; i++,seg++) { Field *field=keyinfo->key_part[i].field; seg->flag= 0; @@ -16347,7 +17699,7 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, seg->type= ((keyinfo->key_part[i].key_type & FIELDFLAG_BINARY) ? HA_KEYTYPE_VARBINARY2 : HA_KEYTYPE_VARTEXT2); - seg->bit_start= (uint8)(field->pack_length() - share->blob_ptr_size); + seg->bit_start= (uint8)(field->pack_length() - portable_sizeof_char_ptr); seg->flag= HA_BLOB_PART; seg->length=0; // Whole blob in unique constraint } @@ -16384,7 +17736,7 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, start_recinfo, share->uniques, &uniquedef, &create_info, - HA_CREATE_TMP_TABLE | + HA_CREATE_TMP_TABLE | HA_CREATE_INTERNAL_TABLE | ((share->db_create_options & HA_OPTION_PACK_RECORD) ? HA_PACK_RECORD : 0) ))) @@ -16393,7 +17745,8 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, table->db_stat=0; goto err; } - status_var_increment(table->in_use->status_var.created_tmp_disk_tables); + table->in_use->inc_status_created_tmp_disk_tables(); + table->in_use->inc_status_created_tmp_tables(); table->in_use->query_plan_flags|= QPLAN_TMP_DISK; share->db_record_offset= 1; table->created= TRUE; @@ -16402,27 +17755,7 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, DBUG_RETURN(1); } - -/** - If a HEAP table gets full, create a MyISAM table and copy all rows to this -*/ - -bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table, - ENGINE_COLUMNDEF *start_recinfo, - ENGINE_COLUMNDEF **recinfo, - int error, - bool ignore_last_dupp_key_error, - bool *is_duplicate) -{ - return create_internal_tmp_table_from_heap2(thd, table, - start_recinfo, recinfo, error, - ignore_last_dupp_key_error, - myisam_hton, - "converting HEAP to MyISAM", - is_duplicate); -} - -#endif /* WITH_MARIA_STORAGE_ENGINE */ +#endif /* USE_ARIA_FOR_TMP_TABLES */ /* @@ -16431,21 +17764,19 @@ bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table, */ -static bool -create_internal_tmp_table_from_heap2(THD *thd, TABLE *table, - ENGINE_COLUMNDEF *start_recinfo, - ENGINE_COLUMNDEF **recinfo, - int error, - bool ignore_last_dupp_key_error, - handlerton *hton, - const char *proc_info, - bool *is_duplicate) +bool +create_internal_tmp_table_from_heap(THD *thd, TABLE *table, + TMP_ENGINE_COLUMNDEF *start_recinfo, + TMP_ENGINE_COLUMNDEF **recinfo, + int error, + bool ignore_last_dupp_key_error, + bool *is_duplicate) { TABLE new_table; TABLE_SHARE share; const char *save_proc_info; int write_err= 0; - DBUG_ENTER("create_internal_tmp_table_from_heap2"); + DBUG_ENTER("create_internal_tmp_table_from_heap"); if (is_duplicate) *is_duplicate= FALSE; @@ -16462,13 +17793,19 @@ create_internal_tmp_table_from_heap2(THD *thd, TABLE *table, new_table= *table; share= *table->s; new_table.s= &share; - new_table.s->db_plugin= ha_lock_engine(thd, hton); + new_table.s->db_plugin= ha_lock_engine(thd, TMP_ENGINE_HTON); if (!(new_table.file= get_new_handler(&share, &new_table.mem_root, new_table.s->db_type()))) DBUG_RETURN(1); // End of memory + if (new_table.file->set_ha_share_ref(&share.ha_share)) + { + delete new_table.file; + DBUG_RETURN(1); + } + save_proc_info=thd->proc_info; - thd_proc_info(thd, proc_info); + THD_STAGE_INFO(thd, stage_converting_heap_to_myisam); new_table.no_rows= table->no_rows; if (create_internal_tmp_table(&new_table, table->key_info, start_recinfo, @@ -16504,7 +17841,7 @@ create_internal_tmp_table_from_heap2(THD *thd, TABLE *table, DBUG_EXECUTE_IF("raise_error", write_err= HA_ERR_FOUND_DUPP_KEY ;); if (write_err) goto err; - if (thd->killed) + if (thd->check_killed()) { thd->send_kill_message(); goto err_killed; @@ -16541,8 +17878,8 @@ create_internal_tmp_table_from_heap2(THD *thd, TABLE *table, table->file->change_table_ptr(table, table->s); table->use_all_columns(); if (save_proc_info) - thd_proc_info(thd, save_proc_info == copy_to_tmp_table ? - "Copying to tmp table on disk" : save_proc_info); + thd_proc_info(thd, (!strcmp(save_proc_info,"Copying to tmp table") ? + "Copying to tmp table on disk" : save_proc_info)); DBUG_RETURN(0); err: @@ -16571,7 +17908,7 @@ free_tmp_table(THD *thd, TABLE *entry) entry->alias.c_ptr())); save_proc_info=thd->proc_info; - thd_proc_info(thd, "removing tmp table"); + THD_STAGE_INFO(thd, stage_removing_tmp_table); if (entry->file && entry->created) { @@ -16690,18 +18027,30 @@ Next_select_func setup_end_select_func(JOIN *join) @retval -1 if error should be sent */ + static int do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) { int rc= 0; enum_nested_loop_state error= NESTED_LOOP_OK; - JOIN_TAB *join_tab; + JOIN_TAB *UNINIT_VAR(join_tab); DBUG_ENTER("do_select"); - LINT_INIT(join_tab); join->procedure=procedure; join->tmp_table= table; /* Save for easy recursion */ join->fields= fields; + join->do_select_call_count++; + + if (join->pushdown_query && join->do_select_call_count == 1) + { + /* Select fields are in the temporary table */ + join->fields= &join->tmp_fields_list1; + /* Setup HAVING to work with fields in temporary table */ + join->set_items_ref_array(join->items1); + /* The storage engine will take care of the group by query result */ + int res= join->pushdown_query->execute(join); + DBUG_RETURN(res); + } if (table) { @@ -16748,8 +18097,8 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) here. join->send_records is increased on success in end_send(), so we don't touch it here. */ - join->examined_rows++; - DBUG_ASSERT(join->examined_rows <= 1); + join->join_examined_rows++; + DBUG_ASSERT(join->join_examined_rows <= 1); } else if (join->send_row_on_empty_set()) { @@ -16771,6 +18120,14 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) else { DBUG_ASSERT(join->table_count); + + DBUG_EXECUTE_IF("show_explain_probe_do_select", + if (dbug_user_var_equals_int(join->thd, + "show_explain_probe_select_id", + join->select_lex->select_number)) + dbug_serve_apcs(join->thd, 1); + ); + if (join->outer_ref_cond && !join->outer_ref_cond->val_int()) error= NESTED_LOOP_NO_MORE_ROWS; else @@ -16781,6 +18138,9 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) if (error == NESTED_LOOP_QUERY_LIMIT) error= NESTED_LOOP_OK; /* select_limit used */ } + + join->thd->limit_found_rows= join->send_records - join->duplicate_rows; + if (error == NESTED_LOOP_NO_MORE_ROWS || join->thd->killed == ABORT_QUERY) error= NESTED_LOOP_OK; @@ -16847,12 +18207,14 @@ int rr_sequential_and_unpack(READ_RECORD *info) /* - Fill the join buffer with partial records, retrieve all full matches for them + Fill the join buffer with partial records, retrieve all full matches for + them SYNOPSIS sub_select_cache() - join pointer to the structure providing all context info for the query - join_tab the first next table of the execution plan to be retrieved + join pointer to the structure providing all context info for the + query + join_tab the first next table of the execution plan to be retrieved end_records true when we need to perform final steps of the retrieval DESCRIPTION @@ -16901,7 +18263,7 @@ sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) rc= sub_select(join, join_tab, end_of_records); DBUG_RETURN(rc); } - if (join->thd->killed) + if (join->thd->check_killed()) { /* The user has aborted the execution of the query */ join->thd->send_kill_message(); @@ -17069,6 +18431,8 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) (*join_tab->next_select)(join,join_tab+1,end_of_records); DBUG_RETURN(nls); } + join_tab->tracker->r_scans++; + int error; enum_nested_loop_state rc= NESTED_LOOP_OK; READ_RECORD *info= &join_tab->read_record; @@ -17098,7 +18462,7 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) if (join_tab->on_precond && !join_tab->on_precond->val_int()) rc= NESTED_LOOP_NO_MORE_ROWS; } - join->thd->warning_info->reset_current_row_for_warning(); + join->thd->get_stmt_da()->reset_current_row_for_warning(); if (rc != NESTED_LOOP_NO_MORE_ROWS && (rc= join_tab_execution_startup(join_tab)) < 0) @@ -17191,23 +18555,27 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, DBUG_ENTER("evaluate_join_record"); DBUG_PRINT("enter", ("evaluate_join_record join: %p join_tab: %p" - " cond: %p error: %d", join, join_tab, select_cond, error)); + " cond: %p error: %d alias %s", + join, join_tab, select_cond, error, + join_tab->table->alias.ptr())); if (error > 0 || (join->thd->is_error())) // Fatal error DBUG_RETURN(NESTED_LOOP_ERROR); if (error < 0) DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS); - if (join->thd->killed) // Aborted by user + if (join->thd->check_killed()) // Aborted by user { join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ } + join_tab->tracker->r_rows++; + if (join_tab->table->vfield) update_virtual_fields(join->thd, join_tab->table); if (select_cond) { - select_cond_result= test(select_cond->val_int()); + select_cond_result= MY_TEST(select_cond->val_int()); /* check for errors evaluating the condition */ if (join->thd->is_error()) @@ -17220,6 +18588,7 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, There is no select condition or the attached pushed down condition is true => a match is found. */ + join_tab->tracker->r_rows_after_where++; bool found= 1; while (join_tab->first_unmatched && found) { @@ -17302,6 +18671,7 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, if (join_tab->check_weed_out_table && found) { int res= join_tab->check_weed_out_table->sj_weedout_check_row(join->thd); + DBUG_PRINT("info", ("weedout_check: %d", res)); if (res == -1) DBUG_RETURN(NESTED_LOOP_ERROR); else if (res == 1) @@ -17321,16 +18691,16 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, of the newly activated predicates is evaluated as false (See above join->return_tab= tab). */ - join->examined_rows++; - DBUG_PRINT("counts", ("join->examined_rows++: %lu", - (ulong) join->examined_rows)); + join->join_examined_rows++; + DBUG_PRINT("counts", ("join->examined_rows++: %lu found: %d", + (ulong) join->join_examined_rows, (int) found)); if (found) { enum enum_nested_loop_state rc; /* A match from join_tab is found for the current partial join. */ rc= (*join_tab->next_select)(join, join_tab+1, 0); - join->thd->warning_info->inc_current_row_for_warning(); + join->thd->get_stmt_da()->inc_current_row_for_warning(); if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS) DBUG_RETURN(rc); if (return_tab < join->return_tab) @@ -17352,7 +18722,7 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, } else { - join->thd->warning_info->inc_current_row_for_warning(); + join->thd->get_stmt_da()->inc_current_row_for_warning(); join_tab->read_record.unlock_row(join_tab); } } @@ -17362,8 +18732,8 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, The condition pushed down to the table join_tab rejects all rows with the beginning coinciding with the current partial join. */ - join->examined_rows++; - join->thd->warning_info->inc_current_row_for_warning(); + join->join_examined_rows++; + join->thd->get_stmt_da()->inc_current_row_for_warning(); join_tab->read_record.unlock_row(join_tab); } DBUG_RETURN(NESTED_LOOP_OK); @@ -17475,13 +18845,8 @@ int report_error(TABLE *table, int error) */ if (error != HA_ERR_LOCK_DEADLOCK && error != HA_ERR_LOCK_WAIT_TIMEOUT && error != HA_ERR_TABLE_DEF_CHANGED && !table->in_use->killed) - { - push_warning_printf(table->in_use, MYSQL_ERROR::WARN_LEVEL_WARN, error, - "Got error %d when reading table %`s.%`s", - error, table->s->db.str, table->s->table_name.str); sql_print_error("Got error %d when reading table '%s'", error, table->s->path.str); - } table->file->print_error(error,MYF(0)); return 1; } @@ -17512,7 +18877,7 @@ int safe_index_read(JOIN_TAB *tab) */ static int -join_read_const_table(JOIN_TAB *tab, POSITION *pos) +join_read_const_table(THD *thd, JOIN_TAB *tab, POSITION *pos) { int error; TABLE_LIST *tbl; @@ -17544,7 +18909,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) { if ((error=join_read_system(tab))) { // Info for DESCRIBE - tab->info="const row not found"; + tab->info= ET_CONST_ROW_NOT_FOUND; /* Mark for EXPLAIN that the row was not found */ pos->records_read=0.0; pos->ref_depend_map= 0; @@ -17570,7 +18935,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) table->disable_keyread(); if (error) { - tab->info="unique row not found"; + tab->info= ET_UNIQUE_ROW_NOT_FOUND; /* Mark for EXPLAIN that the row was not found */ pos->records_read=0.0; pos->ref_depend_map= 0; @@ -17598,7 +18963,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) (*tab->on_expr_ref)->update_used_tables(); DBUG_ASSERT((*tab->on_expr_ref)->const_item()); #endif - if ((table->null_row= test((*tab->on_expr_ref)->val_int() == 0))) + if ((table->null_row= MY_TEST((*tab->on_expr_ref)->val_int() == 0))) mark_as_null_row(table); } if (!table->null_row) @@ -17609,7 +18974,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) List_iterator<TABLE_LIST> ti(join->select_lex->leaf_tables); /* Check appearance of new constant items in Item_equal objects */ if (join->conds) - update_const_equal_items(join->conds, tab, TRUE); + update_const_equal_items(thd, join->conds, tab, TRUE); while ((tbl= ti++)) { TABLE_LIST *embedded; @@ -17618,7 +18983,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) { embedded= embedding; if (embedded->on_expr) - update_const_equal_items(embedded->on_expr, tab, TRUE); + update_const_equal_items(thd, embedded->on_expr, tab, TRUE); embedding= embedded->embedding; } while (embedding && @@ -17986,11 +19351,27 @@ int read_first_record_seq(JOIN_TAB *tab) static int test_if_quick_select(JOIN_TAB *tab) { + DBUG_EXECUTE_IF("show_explain_probe_test_if_quick_select", + if (dbug_user_var_equals_int(tab->join->thd, + "show_explain_probe_select_id", + tab->join->select_lex->select_number)) + dbug_serve_apcs(tab->join->thd, 1); + ); + + delete tab->select->quick; tab->select->quick=0; - return tab->select->test_quick_select(tab->join->thd, tab->keys, - (table_map) 0, HA_POS_ERROR, 0, - FALSE); + + if (tab->table->file->inited != handler::NONE) + tab->table->file->ha_index_or_rnd_end(); + + int res= tab->select->test_quick_select(tab->join->thd, tab->keys, + (table_map) 0, HA_POS_ERROR, 0, + FALSE, /*remove where parts*/FALSE); + if (tab->explain_plan && tab->explain_plan->range_checked_fer) + tab->explain_plan->range_checked_fer->collect_data(tab->select->quick); + + return res; } @@ -18241,7 +19622,25 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), join->duplicate_rows++; } } - if (++join->send_records >= join->unit->select_limit_cnt && + + ++join->send_records; + if (join->send_records >= join->unit->select_limit_cnt && + !join->do_send_rows) + { + /* + If filesort is used for sorting, stop after select_limit_cnt+1 + records are read. Because of optimization in some cases it can + provide only select_limit_cnt+1 records. + */ + if (join->order && join->sortorder && + join->filesort_found_rows && + join->select_options & OPTION_FOUND_ROWS) + { + DBUG_PRINT("info", ("filesort NESTED_LOOP_QUERY_LIMIT")); + DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT); + } + } + if (join->send_records >= join->unit->select_limit_cnt && join->do_send_rows) { if (join->select_options & OPTION_FOUND_ROWS) @@ -18297,7 +19696,15 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), } - /* ARGSUSED */ +/* + @brief + Perform a GROUP BY operation over a stream of rows ordered by their group. The + result is sent into join->result. + + @detail + Also applies HAVING, etc. +*/ + enum_nested_loop_state end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), bool end_of_records) @@ -18465,7 +19872,7 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), } } end: - if (join->thd->killed) + if (join->thd->check_killed()) { join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ @@ -18473,8 +19880,17 @@ end: DBUG_RETURN(NESTED_LOOP_OK); } -/* ARGSUSED */ -/** Group by searching after group record and updating it if possible. */ + +/* + @brief + Perform a GROUP BY operation over rows coming in arbitrary order. + + This is done by looking up the group in a temp.table and updating group + values. + + @detail + Also applies HAVING, etc. +*/ static enum_nested_loop_state end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), @@ -18494,7 +19910,16 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), for (group=table->group ; group ; group=group->next) { Item *item= *group->item; - item->save_org_in_field(group->field); + if (group->fast_field_copier_setup != group->field) + { + DBUG_PRINT("info", ("new setup 0x%lx -> 0x%lx", + (ulong)group->fast_field_copier_setup, + (ulong)group->field)); + group->fast_field_copier_setup= group->field; + group->fast_field_copier_func= + item->setup_fast_field_copier(group->field); + } + item->save_org_in_field(group->field, group->fast_field_copier_func); /* Store in the used key if the field was 0 */ if (item->maybe_null) group->buff[-1]= (char) group->field->is_null(); @@ -18536,7 +19961,7 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), } join->send_records++; end: - if (join->thd->killed) + if (join->thd->check_killed()) { join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ @@ -18586,7 +20011,7 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ } } - if (join->thd->killed) + if (join->thd->check_killed()) { join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ @@ -18595,7 +20020,17 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), } - /* ARGSUSED */ +/* + @brief + Perform a GROUP BY operation over a stream of rows ordered by their group. + Write the result into a temporary table. + + @detail + Also applies HAVING, etc. + + The rows are written into temptable so e.g. filesort can read them. +*/ + enum_nested_loop_state end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), bool end_of_records) @@ -18664,7 +20099,7 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), if (join->procedure) join->procedure->add(); end: - if (join->thd->killed) + if (join->thd->check_killed()) { join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ @@ -18830,7 +20265,7 @@ make_cond_for_table_from_pred(THD *thd, Item *root_cond, Item *cond, if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) { /* Create new top level AND item */ - Item_cond_and *new_cond=new Item_cond_and; + Item_cond_and *new_cond=new (thd->mem_root) Item_cond_and(thd); if (!new_cond) return (COND*) 0; // OOM /* purecov: inspected */ List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); @@ -18843,7 +20278,7 @@ make_cond_for_table_from_pred(THD *thd, Item *root_cond, Item *cond, exclude_expensive_cond, retain_ref_cond); if (fix) - new_cond->argument_list()->push_back(fix); + new_cond->argument_list()->push_back(fix, thd->mem_root); } switch (new_cond->argument_list()->elements) { case 0: @@ -18865,7 +20300,7 @@ make_cond_for_table_from_pred(THD *thd, Item *root_cond, Item *cond, } else { // Or list - Item_cond_or *new_cond=new Item_cond_or; + Item_cond_or *new_cond=new (thd->mem_root) Item_cond_or(thd); if (!new_cond) return (COND*) 0; // OOM /* purecov: inspected */ List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); @@ -18879,7 +20314,7 @@ make_cond_for_table_from_pred(THD *thd, Item *root_cond, Item *cond, retain_ref_cond); if (!fix) return (COND*) 0; // Always true - new_cond->argument_list()->push_back(fix); + new_cond->argument_list()->push_back(fix, thd->mem_root); } /* Call fix_fields to propagate all properties of the children to @@ -18942,17 +20377,20 @@ make_cond_for_table_from_pred(THD *thd, Item *root_cond, Item *cond, */ static COND * -make_cond_after_sjm(Item *root_cond, Item *cond, table_map tables, +make_cond_after_sjm(THD *thd, Item *root_cond, Item *cond, table_map tables, table_map sjm_tables, bool inside_or_clause) { /* We assume that conditions that refer to only join prefix tables or sjm_tables have already been checked. */ - if (!inside_or_clause && - (!(cond->used_tables() & ~tables) || - !(cond->used_tables() & ~sjm_tables))) - return (COND*) 0; // Already checked + if (!inside_or_clause) + { + table_map cond_used_tables= cond->used_tables(); + if((!(cond_used_tables & ~tables) || + !(cond_used_tables & ~sjm_tables))) + return (COND*) 0; // Already checked + } /* AND/OR recursive descent */ if (cond->type() == Item::COND_ITEM) @@ -18960,17 +20398,17 @@ make_cond_after_sjm(Item *root_cond, Item *cond, table_map tables, if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) { /* Create new top level AND item */ - Item_cond_and *new_cond=new Item_cond_and; + Item_cond_and *new_cond= new (thd->mem_root) Item_cond_and(thd); if (!new_cond) return (COND*) 0; // OOM /* purecov: inspected */ List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) { - Item *fix=make_cond_after_sjm(root_cond, item, tables, sjm_tables, + Item *fix=make_cond_after_sjm(thd, root_cond, item, tables, sjm_tables, inside_or_clause); if (fix) - new_cond->argument_list()->push_back(fix); + new_cond->argument_list()->push_back(fix, thd->mem_root); } switch (new_cond->argument_list()->elements) { case 0: @@ -18991,18 +20429,18 @@ make_cond_after_sjm(Item *root_cond, Item *cond, table_map tables, } else { // Or list - Item_cond_or *new_cond=new Item_cond_or; + Item_cond_or *new_cond= new (thd->mem_root) Item_cond_or(thd); if (!new_cond) return (COND*) 0; // OOM /* purecov: inspected */ List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) { - Item *fix= make_cond_after_sjm(root_cond, item, tables, sjm_tables, + Item *fix= make_cond_after_sjm(thd, root_cond, item, tables, sjm_tables, /*inside_or_clause= */TRUE); if (!fix) return (COND*) 0; // Always true - new_cond->argument_list()->push_back(fix); + new_cond->argument_list()->push_back(fix, thd->mem_root); } /* Item_cond_or do not need fix_fields for execution, its parameters @@ -19115,6 +20553,8 @@ part_of_refkey(TABLE *table,Field *field) /** Test if one can use the key to resolve ORDER BY. + @param join if not NULL, can use the join's top-level + multiple-equalities. @param order Sort order @param table Table to sort @param idx Index to check @@ -19137,21 +20577,32 @@ part_of_refkey(TABLE *table,Field *field) -1 Reverse key can be used */ -static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, +static int test_if_order_by_key(JOIN *join, + ORDER *order, TABLE *table, uint idx, uint *used_key_parts= NULL) { KEY_PART_INFO *key_part,*key_part_end; key_part=table->key_info[idx].key_part; - key_part_end=key_part+table->key_info[idx].key_parts; + key_part_end=key_part + table->key_info[idx].ext_key_parts; key_part_map const_key_parts=table->const_key_parts[idx]; + uint user_defined_kp= table->key_info[idx].user_defined_key_parts; int reverse=0; uint key_parts; - my_bool on_pk_suffix= FALSE; + bool have_pk_suffix= false; + uint pk= table->s->primary_key; DBUG_ENTER("test_if_order_by_key"); + + if ((table->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) && + table->key_info[idx].ext_key_part_map && + pk != MAX_KEY && pk != idx) + { + have_pk_suffix= true; + } for (; order ; order=order->next, const_key_parts>>=1) { - Field *field=((Item_field*) (*order->item)->real_item())->field; + Item_field *item_field= ((Item_field*) (*order->item)->real_item()); + Field *field= item_field->field; int flag; /* @@ -19160,61 +20611,50 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, */ for (; const_key_parts & 1 ; const_key_parts>>= 1) key_part++; + + /* + This check was in this function historically (although I think it's + better to check it outside of this function): + + "Test if the primary key parts were all const (i.e. there's one row). + The sorting doesn't matter" + + So, we're checking that + (1) this is an extended key + (2) we've reached its end + */ + key_parts= (key_part - table->key_info[idx].key_part); + if (have_pk_suffix && + reverse == 0 && // all were =const so far + key_parts == table->key_info[idx].ext_key_parts && + table->const_key_parts[pk] == PREV_BITS(uint, + table->key_info[pk]. + user_defined_key_parts)) + { + key_parts= 0; + reverse= 1; // Key is ok to use + goto ok; + } if (key_part == key_part_end) { - /* - We are at the end of the key. Check if the engine has the primary - key as a suffix to the secondary keys. If it has continue to check - the primary key as a suffix. + /* + There are some items left in ORDER BY that we don't */ - if (!on_pk_suffix && (table->key_info[idx].ext_key_part_map & 1) && - (table->file->ha_table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) && - table->s->primary_key != MAX_KEY && - table->s->primary_key != idx) - { - KEY_PART_INFO *start,*end; - uint pk_part_idx= 0; - on_pk_suffix= TRUE; - start= key_part= table->key_info[table->s->primary_key].key_part; - const_key_parts=table->const_key_parts[table->s->primary_key]; - - /* - Calculate true key_part_end and const_key_parts - (we have to stop as first not continous primary key part) - */ - for (key_part_end= key_part, - end= key_part+table->key_info[table->s->primary_key].key_parts; - key_part_end < end; key_part_end++, pk_part_idx++) - { - /* Found hole in the pk_parts; Abort */ - if (!(table->key_info[idx].ext_key_part_map & - (((key_part_map) 1) << pk_part_idx))) - break; - } - - /* Adjust const_key_parts */ - const_key_parts&= (((key_part_map) 1) << pk_part_idx) -1; - - for (; const_key_parts & 1 ; const_key_parts>>= 1) - key_part++; - /* - Test if the primary key parts were all const (i.e. there's one row). - The sorting doesn't matter. - */ - if (key_part == - start+table->key_info[table->s->primary_key].key_parts && - reverse == 0) - { - key_parts= 0; - reverse= 1; // Key is ok to use - goto ok; - } - } - else - DBUG_RETURN(0); + DBUG_RETURN(0); } + if (key_part->field != field) + { + /* + Check if there is a multiple equality that allows to infer that field + and key_part->field are equal + (see also: compute_part_of_sort_key_for_equals) + */ + if (item_field->item_equal && + item_field->item_equal->contains(key_part->field)) + field= key_part->field; + } if (key_part->field != field || !field->part_of_sortkey.is_set(idx)) DBUG_RETURN(0); @@ -19227,27 +20667,20 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, if (key_part < key_part_end) key_part++; } - if (on_pk_suffix) - { - uint used_key_parts_secondary= table->key_info[idx].key_parts; - uint used_key_parts_pk= - (uint) (key_part - table->key_info[table->s->primary_key].key_part); - key_parts= used_key_parts_pk + used_key_parts_secondary; - if (reverse == -1 && - (!(table->file->index_flags(idx, used_key_parts_secondary - 1, 1) & - HA_READ_PREV) || - !(table->file->index_flags(table->s->primary_key, - used_key_parts_pk - 1, 1) & HA_READ_PREV))) - reverse= 0; // Index can't be used - } - else + key_parts= (uint) (key_part - table->key_info[idx].key_part); + + if (reverse == -1 && + !(table->file->index_flags(idx, user_defined_kp, 1) & HA_READ_PREV)) + reverse= 0; // Index can't be used + + if (have_pk_suffix && reverse == -1) { - key_parts= (uint) (key_part - table->key_info[idx].key_part); - if (reverse == -1 && - !(table->file->index_flags(idx, key_parts-1, 1) & HA_READ_PREV)) + uint pk_parts= table->key_info[pk].user_defined_key_parts; + if (!(table->file->index_flags(pk, pk_parts, 1) & HA_READ_PREV)) reverse= 0; // Index can't be used } + ok: if (used_key_parts != NULL) *used_key_parts= key_parts; @@ -19282,6 +20715,7 @@ uint find_shortest_key(TABLE *table, const key_map *usable_keys) min_cost= cost; best=nr; } + DBUG_ASSERT(best < MAX_KEY); } } } @@ -19335,15 +20769,20 @@ test_if_subkey(ORDER *order, TABLE *table, uint ref, uint ref_key_parts, uint best= MAX_KEY; KEY_PART_INFO *ref_key_part= table->key_info[ref].key_part; KEY_PART_INFO *ref_key_part_end= ref_key_part + ref_key_parts; - + + /* + Find the shortest key that + - produces the required ordering + - has key #ref (up to ref_key_parts) as its subkey. + */ for (nr= 0 ; nr < table->s->keys ; nr++) { if (usable_keys->is_set(nr) && table->key_info[nr].key_length < min_length && - table->key_info[nr].key_parts >= ref_key_parts && + table->key_info[nr].user_defined_key_parts >= ref_key_parts && is_subkey(table->key_info[nr].key_part, ref_key_part, ref_key_part_end) && - test_if_order_by_key(order, table, nr)) + test_if_order_by_key(NULL, order, table, nr)) { min_length= table->key_info[nr].key_length; best= nr; @@ -19398,7 +20837,7 @@ list_contains_unique_index(TABLE *table, KEY_PART_INFO *key_part, *key_part_end; for (key_part=keyinfo->key_part, - key_part_end=key_part+ keyinfo->key_parts; + key_part_end=key_part+ keyinfo->user_defined_key_parts; key_part < key_part_end; key_part++) { @@ -19482,6 +20921,71 @@ find_field_in_item_list (Field *field, void *data) } +/* + Fill *col_keys with a union of Field::part_of_sortkey of all fields + that belong to 'table' and are equal to 'item_field'. +*/ + +void compute_part_of_sort_key_for_equals(JOIN *join, TABLE *table, + Item_field *item_field, + key_map *col_keys) +{ + col_keys->clear_all(); + col_keys->merge(item_field->field->part_of_sortkey); + + if (!optimizer_flag(join->thd, OPTIMIZER_SWITCH_ORDERBY_EQ_PROP)) + return; + + Item_equal *item_eq= NULL; + + if (item_field->item_equal) + { + /* + The item_field is from ORDER structure, but it already has an item_equal + pointer set (UseMultipleEqualitiesToRemoveTempTable code have set it) + */ + item_eq= item_field->item_equal; + } + else + { + /* + Walk through join's muliple equalities and find the one that contains + item_field. + */ + if (!join->cond_equal) + return; + table_map needed_tbl_map= item_field->used_tables() | table->map; + List_iterator<Item_equal> li(join->cond_equal->current_level); + Item_equal *cur_item_eq; + while ((cur_item_eq= li++)) + { + if ((cur_item_eq->used_tables() & needed_tbl_map) && + cur_item_eq->contains(item_field->field)) + { + item_eq= cur_item_eq; + item_field->item_equal= item_eq; // Save the pointer to our Item_equal. + break; + } + } + } + + if (item_eq) + { + Item_equal_fields_iterator it(*item_eq); + Item *item; + /* Loop through other members that belong to table table */ + while ((item= it++)) + { + if (item->type() == Item::FIELD_ITEM && + ((Item_field*)item)->field->table == table) + { + col_keys->merge(((Item_field*)item)->field->part_of_sortkey); + } + } + } +} + + /** Test if we can skip the ORDER BY by using an index. @@ -19528,7 +21032,8 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, been taken into account. */ usable_keys= *map; - + + /* Find indexes that cover all ORDER/GROUP BY fields */ for (ORDER *tmp_order=order; tmp_order ; tmp_order=tmp_order->next) { Item *item= (*tmp_order->item)->real_item(); @@ -19537,7 +21042,27 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, usable_keys.clear_all(); DBUG_RETURN(0); } - usable_keys.intersect(((Item_field*) item)->field->part_of_sortkey); + + /* + Take multiple-equalities into account. Suppose we have + ORDER BY col1, col10 + and there are + multiple-equal(col1, col2, col3), + multiple-equal(col10, col11). + + Then, + - when item=col1, we find the set of indexes that cover one of {col1, + col2, col3} + - when item=col10, we find the set of indexes that cover one of {col10, + col11} + + And we compute an intersection of these sets to find set of indexes that + cover all ORDER BY components. + */ + key_map col_keys; + compute_part_of_sort_key_for_equals(tab->join, table, (Item_field*)item, + &col_keys); + usable_keys.intersect(col_keys); if (usable_keys.is_clear_all()) goto use_filesort; // No usable keys } @@ -19548,6 +21073,10 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, { ref_key= tab->ref.key; ref_key_parts= tab->ref.key_parts; + /* + todo: why does JT_REF_OR_NULL mean filesort? We could find another index + that satisfies the ordering. I would just set ref_key=MAX_KEY here... + */ if (tab->type == JT_REF_OR_NULL || tab->type == JT_FT) goto use_filesort; } @@ -19564,7 +21093,15 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) + { + /* + we set ref_key=MAX_KEY instead of -1, because test_if_cheaper ordering + assumes that "ref_key==-1" means doing full index scan. + (This is not very straightforward and we got into this situation for + historical reasons. Should be fixed at some point). + */ ref_key= MAX_KEY; + } else { ref_key= select->quick->index; @@ -19574,15 +21111,12 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, if (ref_key >= 0 && ref_key != MAX_KEY) { - /* - We come here when there is a REF key. - */ + /* Current access method uses index ref_key with ref_key_parts parts */ if (!usable_keys.is_set(ref_key)) { - /* - We come here when ref_key is not among usable_keys - */ + /* However, ref_key doesn't match the needed ordering */ uint new_ref_key; + /* If using index only read, only consider other possible index only keys @@ -19598,27 +21132,23 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, if ((new_ref_key= test_if_subkey(order, table, ref_key, ref_key_parts, &usable_keys)) < MAX_KEY) { - if (tab->ref.key >= 0) - { - /* - We'll use ref access method on key new_ref_key. In general case - the index search tuple for new_ref_key will be different (e.g. - when one index is defined as (part1, part2, ...) and another as - (part1, part2(N), ...) and the WHERE clause contains - "part1 = const1 AND part2=const2". - So we build tab->ref from scratch here. - */ - KEYUSE *keyuse= tab->keyuse; - while (keyuse->key != new_ref_key && keyuse->table == tab->table) - keyuse++; - if (create_ref_for_key(tab->join, tab, keyuse, FALSE, - (tab->join->const_table_map | - OUTER_REF_TABLE_BIT))) - goto use_filesort; + /* + Index new_ref_key + - produces the required ordering, + - also has the same columns as ref_key for #ref_key_parts (this + means we will read the same number of rows as with ref_key). + */ - pick_table_access_method(tab); - } - else + /* + If new_ref_key allows to construct a quick select which uses more key + parts than ref(new_ref_key) would, do that. + + Otherwise, construct a ref access (todo: it's not clear what is the + win in using ref access when we could use quick select also?) + */ + if ((table->quick_keys.is_set(new_ref_key) && + table->quick_key_parts[new_ref_key] > ref_key_parts) || + !(tab->ref.key >= 0)) { /* The range optimizer constructed QUICK_RANGE for ref_key, and @@ -19643,26 +21173,54 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, (tab->join->select_options & OPTION_FOUND_ROWS) ? HA_POS_ERROR : - tab->join->unit->select_limit_cnt,0, - TRUE) <= 0; + tab->join->unit->select_limit_cnt,TRUE, + TRUE, FALSE) <= 0; if (res) { select->cond= save_cond; goto use_filesort; } + DBUG_ASSERT(tab->select->quick); + tab->type= JT_ALL; + tab->ref.key= -1; + tab->ref.key_parts= 0; + tab->use_quick= 1; + best_key= new_ref_key; /* We don't restore select->cond as we want to use the original condition as index condition pushdown is not active for the new index. + todo: why not perform index condition pushdown for the new index? + */ + } + else + { + /* + We'll use ref access method on key new_ref_key. In general case + the index search tuple for new_ref_key will be different (e.g. + when one index is defined as (part1, part2, ...) and another as + (part1, part2(N), ...) and the WHERE clause contains + "part1 = const1 AND part2=const2". + So we build tab->ref from scratch here. */ + KEYUSE *keyuse= tab->keyuse; + while (keyuse->key != new_ref_key && keyuse->table == tab->table) + keyuse++; + if (create_ref_for_key(tab->join, tab, keyuse, FALSE, + (tab->join->const_table_map | + OUTER_REF_TABLE_BIT))) + goto use_filesort; + + pick_table_access_method(tab); } + ref_key= new_ref_key; changed_key= true; } } /* Check if we get the rows in requested sorted order by using the key */ if (usable_keys.is_set(ref_key) && - (order_direction= test_if_order_by_key(order,table,ref_key, + (order_direction= test_if_order_by_key(tab->join, order,table,ref_key, &used_key_parts))) goto check_reverse_order; } @@ -19671,7 +21229,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, uint saved_best_key_parts= 0; int best_key_direction= 0; JOIN *join= tab->join; - ha_rows table_records= table->file->stats.records; + ha_rows table_records= table->stat_records(); test_if_cheaper_ordering(tab, order, table, usable_keys, ref_key, select_limit, @@ -19691,23 +21249,42 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, !(table->file->index_flags(best_key, 0, 1) & HA_CLUSTERED_INDEX))) goto use_filesort; - if (select && + if (select && // psergey: why doesn't this use a quick? table->quick_keys.is_set(best_key) && best_key != ref_key) { - key_map map; - map.clear_all(); // Force the creation of quick select - map.set_bit(best_key); // only best_key. + key_map tmp_map; + tmp_map.clear_all(); // Force the creation of quick select + tmp_map.set_bit(best_key); // only best_key. select->quick= 0; - select->test_quick_select(join->thd, map, 0, + + bool cond_saved= false; + Item *saved_cond; + + /* + Index Condition Pushdown may have removed parts of the condition for + this table. Temporarily put them back because we want the whole + condition for the range analysis. + */ + if (select->pre_idx_push_select_cond) + { + saved_cond= select->cond; + select->cond= select->pre_idx_push_select_cond; + cond_saved= true; + } + + select->test_quick_select(join->thd, tmp_map, 0, join->select_options & OPTION_FOUND_ROWS ? HA_POS_ERROR : join->unit->select_limit_cnt, - TRUE, FALSE); + TRUE, FALSE, FALSE); + + if (cond_saved) + select->cond= saved_cond; } order_direction= best_key_direction; /* saved_best_key_parts is actual number of used keyparts found by the - test_if_order_by_key function. It could differ from keyinfo->key_parts, + test_if_order_by_key function. It could differ from keyinfo->user_defined_key_parts, thus we have to restore it in case of desc order as it affects QUICK_SELECT_DESC behaviour. */ @@ -19761,6 +21338,12 @@ check_reverse_order: */ if (!table->covering_keys.is_set(best_key)) table->disable_keyread(); + else + { + if (!table->key_read) + table->enable_keyread(); + } + if (!quick_created) { if (select) // Throw any existing quick select @@ -19787,7 +21370,7 @@ check_reverse_order: { tab->ref.key= -1; tab->ref.key_parts= 0; - if (select_limit < table->file->stats.records) + if (select_limit < table->stat_records()) tab->limit= select_limit; table->disable_keyread(); } @@ -19942,6 +21525,8 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, { uint length= 0; ha_rows examined_rows; + ha_rows found_rows; + ha_rows filesort_retval= HA_POS_ERROR; TABLE *table; SQL_SELECT *select; JOIN_TAB *tab; @@ -19959,8 +21544,9 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, if (join->pre_sort_join_tab) { /* - we've already been in this function, and stashed away the original access - method in join->pre_sort_join_tab, restore it now. + we've already been in this function, and stashed away the + original access method in join->pre_sort_join_tab, restore it + now. */ /* First, restore state of the handler */ @@ -20014,6 +21600,8 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, and thus force sorting on disk unless a group min-max optimization is going to be used as it is applied now only for one table queries with covering indexes. + The expections is if we are already using the index for GROUP BY + (in which case sort would be free) or ORDER and GROUP BY are different. */ if ((order != join->group_list || !(join->select_options & SELECT_BIG_RESULT) || @@ -20022,15 +21610,24 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, test_if_skip_sort_order(tab,order,select_limit,0, is_order_by ? &table->keys_in_use_for_order_by : &table->keys_in_use_for_group_by)) + { + tab->update_explain_data(join->const_tables); DBUG_RETURN(0); + } + tab->update_explain_data(join->const_tables); + for (ORDER *ord= join->order; ord; ord= ord->next) length++; if (!(join->sortorder= - make_unireg_sortorder(order, &length, join->sortorder))) + make_unireg_sortorder(thd, join, tab->table->map, order, &length, + join->sortorder))) + { goto err; /* purecov: inspected */ + } table->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE), - MYF(MY_WME | MY_ZEROFILL)); + MYF(MY_WME | MY_ZEROFILL| + MY_THREAD_SPECIFIC)); table->status=0; // May be wrong if quick_select if (!tab->preread_init_done && tab->preread_init()) @@ -20074,9 +21671,12 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, if (table->s->tmp_table) table->file->info(HA_STATUS_VARIABLE); // Get record count - table->sort.found_records=filesort(thd, table,join->sortorder, length, - select, filesort_limit, 0, - &examined_rows); + filesort_retval= filesort(thd, table, join->sortorder, length, + select, filesort_limit, 0, + &examined_rows, &found_rows, + join->explain->ops_tracker.report_sorting(thd)); + table->sort.found_records= filesort_retval; + tab->records= join->select_options & OPTION_FOUND_ROWS ? found_rows : filesort_retval; if (quick_created) { @@ -20094,72 +21694,6 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, *(join->pre_sort_join_tab)= *tab; - /*TODO: here, close the index scan, cancel index-only read. */ - tab->records= table->sort.found_records; // For SQL_CALC_ROWS -#if 0 - /* MariaDB doesn't need the following: */ - if (select) - { - /* - We need to preserve tablesort's output resultset here, because - QUICK_INDEX_MERGE_SELECT::~QUICK_INDEX_MERGE_SELECT (called by - SQL_SELECT::cleanup()) may free it assuming it's the result of the quick - select operation that we no longer need. Note that all the other parts of - this data structure are cleaned up when - QUICK_INDEX_MERGE_SELECT::get_next encounters end of data, so the next - SQL_SELECT::cleanup() call changes sort.io_cache alone. - */ - IO_CACHE *tablesort_result_cache; - - tablesort_result_cache= table->sort.io_cache; - table->sort.io_cache= NULL; - - if (select->quick && - select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) - { - tab->filesort_used_loose_index_scan= true; - - QUICK_GROUP_MIN_MAX_SELECT *minmax_quick= - static_cast<QUICK_GROUP_MIN_MAX_SELECT*>(select->quick); - if (minmax_quick->is_agg_distinct()) - tab->filesort_used_loose_index_scan_agg_distinct= true; - } - - /* - If a quick object was created outside of create_sort_index() - that might be reused, then do not call select->cleanup() since - it will delete the quick object. - */ - if (!keep_quick) - { - select->cleanup(); - - // If we deleted the quick object we need to clear quick_keys - table->quick_keys.clear_all(); - table->intersect_keys.clear_all(); - } - else - { - // Need to close the index scan in order to re-use the handler - tab->select->quick->range_end(); - } - - /* - The select object is now ready for the next use. To avoid that - the select object is used when reading the records in sorted - order we set the pointer to it to NULL. The select pointer will - be restored from the saved_select pointer when this select - operation is completed (@see JOIN::exec). This ensures that it - will be re-used when filesort is used by subqueries that are - executed multiple times. - */ - tab->saved_select= tab->select; - tab->select= NULL; - - // Restore the output resultset - table->sort.io_cache= tablesort_result_cache; - } -#endif tab->select=NULL; tab->set_select_cond(NULL, __LINE__); tab->type=JT_ALL; // Read with normal read_record @@ -20169,13 +21703,12 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, if (err) goto err; - tab->join->examined_rows+=examined_rows; - DBUG_RETURN(table->sort.found_records == HA_POS_ERROR); + tab->join->join_examined_rows+= examined_rows; + DBUG_RETURN(filesort_retval == HA_POS_ERROR); err: DBUG_RETURN(-1); } - void JOIN::clean_pre_sort_join_tab() { //TABLE *table= pre_sort_join_tab->table; @@ -20184,29 +21717,6 @@ void JOIN::clean_pre_sort_join_tab() the table already deleted by st_select_lex_unit::cleanup(). We rely on that fake_select_lex didn't have quick select. */ -#if 0 - if (pre_sort_join_tab->select && pre_sort_join_tab->select->quick) - { - /* - We need to preserve tablesort's output resultset here, because - QUICK_INDEX_MERGE_SELECT::~QUICK_INDEX_MERGE_SELECT (called by - SQL_SELECT::cleanup()) may free it assuming it's the result of the quick - select operation that we no longer need. Note that all the other parts of - this data structure are cleaned up when - QUICK_INDEX_MERGE_SELECT::get_next encounters end of data, so the next - SQL_SELECT::cleanup() call changes sort.io_cache alone. - */ - IO_CACHE *tablesort_result_cache; - - tablesort_result_cache= table->sort.io_cache; - table->sort.io_cache= NULL; - pre_sort_join_tab->select->cleanup(); - table->quick_keys.clear_all(); // as far as we cleanup select->quick - table->intersect_keys.clear_all(); - table->sort.io_cache= tablesort_result_cache; - } -#endif - //table->disable_keyread(); // Restore if we used indexes if (pre_sort_join_tab->select && pre_sort_join_tab->select->quick) { pre_sort_join_tab->select->cleanup(); @@ -20258,6 +21768,24 @@ static void free_blobs(Field **ptr) } +/* + @brief + Remove duplicates from a temporary table. + + @detail + Remove duplicate rows from a temporary table. This is used for e.g. queries + like + + select distinct count(*) as CNT from tbl group by col + + Here, we get a group table with count(*) values. It is not possible to + prevent duplicates from appearing in the table (as we don't know the values + before we've done the grouping). Because of that, we have this function to + scan the temptable (maybe, multiple times) and remove the duplicate rows + + Rows that do not satisfy 'having' condition are also removed. +*/ + static int remove_duplicates(JOIN *join, TABLE *table, List<Item> &fields, Item *having) { @@ -20267,6 +21795,7 @@ remove_duplicates(JOIN *join, TABLE *table, List<Item> &fields, Item *having) THD *thd= join->thd; DBUG_ENTER("remove_duplicates"); + join->explain->ops_tracker.report_duplicate_removal(); table->reginfo.lock_type=TL_WRITE; @@ -20330,7 +21859,7 @@ static int remove_dup_with_compare(THD *thd, TABLE *table, Field **first_field, error= file->ha_rnd_next(record); for (;;) { - if (thd->killed) + if (thd->check_killed()) { thd->send_kill_message(); error=0; @@ -20356,7 +21885,8 @@ static int remove_dup_with_compare(THD *thd, TABLE *table, Field **first_field, } if (copy_blobs(first_field)) { - my_message(ER_OUTOFMEMORY, ER(ER_OUTOFMEMORY), MYF(ME_FATALERROR)); + my_message(ER_OUTOFMEMORY, ER_THD(thd,ER_OUTOFMEMORY), + MYF(ME_FATALERROR)); error=0; goto err; } @@ -20451,7 +21981,7 @@ static int remove_dup_with_hash_index(THD *thd, TABLE *table, for (;;) { uchar *org_key_pos; - if (thd->killed) + if (thd->check_killed()) { thd->send_kill_message(); error=0; @@ -20511,7 +22041,9 @@ err: } -SORT_FIELD *make_unireg_sortorder(ORDER *order, uint *length, +SORT_FIELD *make_unireg_sortorder(THD *thd, JOIN *join, + table_map first_table_bit, + ORDER *order, uint *length, SORT_FIELD *sortorder) { uint count; @@ -20522,16 +22054,39 @@ SORT_FIELD *make_unireg_sortorder(ORDER *order, uint *length, for (ORDER *tmp = order; tmp; tmp=tmp->next) count++; if (!sortorder) - sortorder= (SORT_FIELD*) sql_alloc(sizeof(SORT_FIELD) * - (max(count, *length) + 1)); + sortorder= (SORT_FIELD*) thd->alloc(sizeof(SORT_FIELD) * + (MY_MAX(count, *length) + 1)); pos= sort= sortorder; if (!pos) - return 0; + DBUG_RETURN(0); for (;order;order=order->next,pos++) { - Item *const item= order->item[0], *const real_item= item->real_item(); + Item *first= order->item[0]; + /* + It is possible that the query plan is to read table t1, while the + sort criteria actually has "ORDER BY t2.col" and the WHERE clause has + a multi-equality(t1.col, t2.col, ...). + The optimizer detects such cases (grep for + UseMultipleEqualitiesToRemoveTempTable to see where), but doesn't + perform equality substitution in the order->item. We need to do the + substitution here ourselves. + */ + table_map item_map= first->used_tables(); + if (join && (item_map & ~join->const_table_map) && + !(item_map & first_table_bit) && join->cond_equal && + first->get_item_equal()) + { + /* + Ok, this is the case descibed just above. Get the first element of the + multi-equality. + */ + Item_equal *item_eq= first->get_item_equal(); + first= item_eq->get_first(NO_PARTICULAR_TAB, NULL); + } + + Item *const item= first, *const real_item= item->real_item(); pos->field= 0; pos->item= 0; if (real_item->type() == Item::FIELD_ITEM) { @@ -20559,6 +22114,7 @@ SORT_FIELD *make_unireg_sortorder(ORDER *order, uint *length, else pos->item= item; pos->reverse=! order->asc; + DBUG_ASSERT(pos->field != NULL || pos->item != NULL); } *length=count; DBUG_RETURN(sort); @@ -20657,7 +22213,7 @@ cp_buffer_from_ref(THD *thd, TABLE *table, TABLE_REF *ref) ref_pointer_array and all_fields are updated. - @param[in] thd Pointer to current thread structure + @param[in] thd Pointer to current thread structure @param[in,out] ref_pointer_array All select, group and order by fields @param[in] tables List of tables to search in (usually FROM clause) @@ -20695,7 +22251,11 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, */ if (order_item->type() == Item::INT_ITEM && order_item->basic_const_item()) { /* Order by position */ - uint count= (uint) order_item->val_int(); + uint count; + if (order->counter_used) + count= order->counter; // counter was once resolved + else + count= (uint) order_item->val_int(); if (!count || count > fields.elements) { my_error(ER_BAD_FIELD_ERROR, MYF(0), @@ -20706,13 +22266,13 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, order->in_field_list= 1; order->counter= count; order->counter_used= 1; - return FALSE; + return FALSE; } /* Lookup the current GROUP/ORDER field in the SELECT clause. */ select_item= find_item_in_list(order_item, fields, &counter, REPORT_EXCEPT_NOT_FOUND, &resolution); if (!select_item) - return TRUE; /* The item is not unique, or some other error occured. */ + return TRUE; /* The item is not unique, or some other error occurred. */ /* Check whether the resolved field is not ambiguos. */ @@ -20721,7 +22281,7 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, Item *view_ref= NULL; /* If we have found field not by its alias in select list but by its - original field name, we should additionaly check if we have conflict + original field name, we should additionally check if we have conflict for this name (in case if we would perform lookup in all tables). */ if (resolution == RESOLVED_BEHIND_ALIAS && !order_item->fixed && @@ -20774,10 +22334,11 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, warning so the user knows that the field from the FROM clause overshadows the column reference from the SELECT list. */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_NON_UNIQ_ERROR, - ER(ER_NON_UNIQ_ERROR), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_NON_UNIQ_ERROR, + ER_THD(thd, ER_NON_UNIQ_ERROR), ((Item_ident*) order_item)->field_name, - current_thd->where); + thd->where); } } @@ -20805,7 +22366,8 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, uint el= all_fields.elements; DBUG_ASSERT(all_fields.elements <= thd->lex->current_select->ref_pointer_array_size); - all_fields.push_front(order_item); /* Add new field to field list. */ + /* Add new field to field list. */ + all_fields.push_front(order_item, thd->mem_root); ref_pointer_array[el]= order_item; /* If the order_item is a SUM_FUNC_ITEM, when fix_fields is called @@ -20991,7 +22553,7 @@ setup_new_fields(THD *thd, List<Item> &fields, thd->where="procedure list"; if ((*new_field->item)->fix_fields(thd, new_field->item)) DBUG_RETURN(1); /* purecov: inspected */ - all_fields.push_front(*new_field->item); + all_fields.push_front(*new_field->item, thd->mem_root); new_field->item=all_fields.head_ref(); } } @@ -21062,12 +22624,12 @@ create_distinct_group(THD *thd, Item **ref_pointer_array, converted to a LONG field. Original field will remain of the BIT type and will be returned to a client. */ - Item_field *new_item= new Item_field(thd, (Item_field*)item); + Item_field *new_item= new (thd->mem_root) Item_field(thd, (Item_field*)item); int el= all_fields.elements; DBUG_ASSERT(all_fields.elements <= thd->lex->current_select->ref_pointer_array_size); orig_ref_pointer_array[el]= new_item; - all_fields.push_front(new_item); + all_fields.push_front(new_item, thd->mem_root); ord->item= orig_ref_pointer_array + el; } else @@ -21160,7 +22722,7 @@ test_if_subpart(ORDER *a,ORDER *b) else return 0; } - return test(!b); + return MY_TEST(!b); } /** @@ -21249,7 +22811,7 @@ calc_group_buffer(JOIN *join,ORDER *group) } else { - switch (group_item->result_type()) { + switch (group_item->cmp_type()) { case REAL_RESULT: key_length+= sizeof(double); break; @@ -21261,22 +22823,20 @@ calc_group_buffer(JOIN *join,ORDER *group) (group_item->decimals ? 1 : 0), group_item->decimals); break; - case STRING_RESULT: + case TIME_RESULT: { - enum enum_field_types type= group_item->field_type(); /* As items represented as DATE/TIME fields in the group buffer have STRING_RESULT result type, we increase the length by 8 as maximum pack length of such fields. */ - if (type == MYSQL_TYPE_TIME || - type == MYSQL_TYPE_DATE || - type == MYSQL_TYPE_DATETIME || - type == MYSQL_TYPE_TIMESTAMP) - { - key_length+= 8; - } - else if (type == MYSQL_TYPE_BLOB) + key_length+= 8; + break; + } + case STRING_RESULT: + { + enum enum_field_types type= group_item->field_type(); + if (type == MYSQL_TYPE_BLOB) key_length+= MAX_BLOB_WIDTH; // Can't be used as a key else { @@ -21456,7 +23016,7 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, DBUG_ENTER("setup_copy_fields"); if (param->field_count && - !(copy=param->copy_field= new Copy_field[param->field_count])) + !(copy=param->copy_field= new (thd->mem_root) Copy_field[param->field_count])) goto err2; param->copy_funcs.empty(); @@ -21475,7 +23035,7 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, ((Item_ref *)pos)->ref_type() == Item_ref::AGGREGATE_REF)) { Item_field *item; - if (!(item= new Item_field(thd, ((Item_field*) real_pos)))) + if (!(item= new (thd->mem_root) Item_field(thd, ((Item_field*) real_pos)))) goto err; if (pos->type() == Item::REF_ITEM) { @@ -21488,7 +23048,7 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, pos= item; if (item->field->flags & BLOB_FLAG) { - if (!(pos= new Item_copy_string(pos))) + if (!(pos= new (thd->mem_root) Item_copy_string(thd, pos))) goto err; /* Item_copy_string::copy for function can call @@ -21499,7 +23059,7 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, copy_funcs (to see full test case look at having.test, BUG #4358) */ - if (param->copy_funcs.push_front(pos)) + if (param->copy_funcs.push_front(pos, thd->mem_root)) goto err; } else @@ -21509,13 +23069,14 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, saved value */ field= item->field; - item->result_field=field->new_field(thd->mem_root,field->table, 1); + item->result_field=field->make_new_field(thd->mem_root, + field->table, 1); /* We need to allocate one extra byte for null handling and another extra byte to not get warnings from purify in Field_string::val_int */ - if (!(tmp= (uchar*) sql_alloc(field->pack_length()+2))) + if (!(tmp= (uchar*) thd->alloc(field->pack_length()+2))) goto err; if (copy) { @@ -21542,17 +23103,17 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, on how the value is to be used: In some cases this may be an argument in a group function, like: IF(ISNULL(col),0,COUNT(*)) */ - if (!(pos=new Item_copy_string(pos))) + if (!(pos=new (thd->mem_root) Item_copy_string(thd, pos))) goto err; if (i < border) // HAVING, ORDER and GROUP BY { - if (extra_funcs.push_back(pos)) + if (extra_funcs.push_back(pos, thd->mem_root)) goto err; } - else if (param->copy_funcs.push_back(pos)) + else if (param->copy_funcs.push_back(pos, thd->mem_root)) goto err; } - res_all_fields.push_back(pos); + res_all_fields.push_back(pos, thd->mem_root); ref_pointer_array[((i < border)? all_fields.elements-i-1 : i-border)]= pos; } @@ -21565,7 +23126,7 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, Put elements from HAVING, ORDER BY and GROUP BY last to ensure that any reference used in these will resolve to a item that is already calculated */ - param->copy_funcs.concat(&extra_funcs); + param->copy_funcs.append(&extra_funcs); DBUG_RETURN(0); @@ -21750,25 +23311,27 @@ change_to_use_tmp_fields(THD *thd, Item **ref_pointer_array, if (field != NULL) { /* - Replace "@:=<expression>" with "@:=<tmp table column>". Otherwise, we - would re-evaluate <expression>, and if expression were a subquery, this - would access already-unlocked tables. + Replace "@:=<expression>" with "@:=<tmp table + column>". Otherwise, we would re-evaluate <expression>, and + if expression were a subquery, this would access + already-unlocked tables. */ Item_func_set_user_var* suv= - new Item_func_set_user_var(thd, (Item_func_set_user_var*) item); - Item_field *new_field= new Item_field(field); + new (thd->mem_root) Item_func_set_user_var(thd, (Item_func_set_user_var*) item); + Item_field *new_field= new (thd->mem_root) Item_temptable_field(thd, field); if (!suv || !new_field) DBUG_RETURN(true); // Fatal error /* - We are replacing the argument of Item_func_set_user_var after its value - has been read. The argument's null_value should be set by now, so we - must set it explicitly for the replacement argument since the null_value - may be read without any preceeding call to val_*(). + We are replacing the argument of Item_func_set_user_var after + its value has been read. The argument's null_value should be + set by now, so we must set it explicitly for the replacement + argument since the null_value may be read without any + preceeding call to val_*(). */ new_field->update_null_value(); List<Item> list; - list.push_back(new_field); - suv->set_arguments(list); + list.push_back(new_field, thd->mem_root); + suv->set_arguments(thd, list); item_field= suv; } else @@ -21777,9 +23340,9 @@ change_to_use_tmp_fields(THD *thd, Item **ref_pointer_array, else if ((field= item->get_tmp_table_field())) { if (item->type() == Item::SUM_FUNC_ITEM && field->table->group) - item_field= ((Item_sum*) item)->result_item(field); + item_field= ((Item_sum*) item)->result_item(thd, field); else - item_field= (Item*) new Item_field(field); + item_field= (Item *) new (thd->mem_root) Item_temptable_field(thd, field); if (!item_field) DBUG_RETURN(true); // Fatal error @@ -21808,7 +23371,7 @@ change_to_use_tmp_fields(THD *thd, Item **ref_pointer_array, else item_field= item; - res_all_fields.push_back(item_field); + res_all_fields.push_back(item_field, thd->mem_root); ref_pointer_array[((i < border)? all_fields.elements-i-1 : i-border)]= item_field; } @@ -21852,7 +23415,8 @@ change_refs_to_tmp_fields(THD *thd, Item **ref_pointer_array, uint i, border= all_fields.elements - elements; for (i= 0; (item= it++); i++) { - res_all_fields.push_back(new_item= item->get_tmp_table_item(thd)); + res_all_fields.push_back(new_item= item->get_tmp_table_item(thd), + thd->mem_root); ref_pointer_array[((i < border)? all_fields.elements-i-1 : i-border)]= new_item; } @@ -22019,7 +23583,7 @@ static bool add_ref_to_table_cond(THD *thd, JOIN_TAB *join_tab) if (!join_tab->ref.key_parts) DBUG_RETURN(FALSE); - Item_cond_and *cond=new Item_cond_and(); + Item_cond_and *cond= new (thd->mem_root) Item_cond_and(thd); TABLE *table=join_tab->table; int error= 0; if (!cond) @@ -22030,7 +23594,10 @@ static bool add_ref_to_table_cond(THD *thd, JOIN_TAB *join_tab) Field *field=table->field[table->key_info[join_tab->ref.key].key_part[i]. fieldnr-1]; Item *value=join_tab->ref.items[i]; - cond->add(new Item_func_equal(new Item_field(field), value)); + cond->add(new (thd->mem_root) + Item_func_equal(thd, new (thd->mem_root) Item_field(thd, field), + value), + thd->mem_root); } if (thd->is_fatal_error) DBUG_RETURN(TRUE); @@ -22042,16 +23609,16 @@ static bool add_ref_to_table_cond(THD *thd, JOIN_TAB *join_tab) } if (join_tab->select) { - Item *cond_copy; - UNINIT_VAR(cond_copy); // used when pre_idx_push_select_cond!=NULL + Item *UNINIT_VAR(cond_copy); if (join_tab->select->pre_idx_push_select_cond) cond_copy= cond->copy_andor_structure(thd); if (join_tab->select->cond) - error=(int) cond->add(join_tab->select->cond); + error=(int) cond->add(join_tab->select->cond, thd->mem_root); join_tab->select->cond= cond; if (join_tab->select->pre_idx_push_select_cond) { - Item *new_cond= and_conds(cond_copy, join_tab->select->pre_idx_push_select_cond); + Item *new_cond= and_conds(thd, cond_copy, + join_tab->select->pre_idx_push_select_cond); if (!new_cond->fixed && new_cond->fix_fields(thd, &new_cond)) error= 1; join_tab->pre_idx_push_select_cond= @@ -22129,13 +23696,13 @@ void free_underlaid_joins(THD *thd, SELECT_LEX *select) static bool change_group_ref(THD *thd, Item_func *expr, ORDER *group_list, bool *changed) { - if (expr->arg_count) + if (expr->argument_count()) { Name_resolution_context *context= &thd->lex->current_select->context; Item **arg,**arg_end; bool arg_changed= FALSE; for (arg= expr->arguments(), - arg_end= expr->arguments()+expr->arg_count; + arg_end= expr->arguments() + expr->argument_count(); arg != arg_end; arg++) { Item *item= *arg; @@ -22147,8 +23714,8 @@ static bool change_group_ref(THD *thd, Item_func *expr, ORDER *group_list, if (item->eq(*group_tmp->item,0)) { Item *new_item; - if (!(new_item= new Item_ref(context, group_tmp->item, 0, - item->name))) + if (!(new_item= new (thd->mem_root) Item_ref(thd, context, group_tmp->item, 0, + item->name))) return 1; // fatal_error is set thd->change_item_tree(arg, new_item); arg_changed= TRUE; @@ -22205,7 +23772,7 @@ bool JOIN::rollup_init() */ for (i= 0 ; i < send_group_parts ; i++) { - rollup.null_items[i]= new (thd->mem_root) Item_null_result(); + rollup.null_items[i]= new (thd->mem_root) Item_null_result(thd); List<Item> *rollup_fields= &rollup.fields[i]; rollup_fields->empty(); rollup.ref_pointer_arrays[i]= ref_array; @@ -22214,7 +23781,7 @@ bool JOIN::rollup_init() for (i= 0 ; i < send_group_parts; i++) { for (j=0 ; j < fields_list.elements ; j++) - rollup.fields[i].push_back(rollup.null_items[i]); + rollup.fields[i].push_back(rollup.null_items[i], thd->mem_root); } List_iterator<Item> it(all_fields); Item *item; @@ -22285,7 +23852,7 @@ bool JOIN::rollup_process_const_fields() { if (*group_tmp->item == item) { - Item* new_item= new Item_func_rollup_const(item); + Item* new_item= new (thd->mem_root) Item_func_rollup_const(thd, item); if (!new_item) return 1; new_item->fix_fields(thd, (Item **) 0); @@ -22410,7 +23977,7 @@ bool JOIN::rollup_make_fields(List<Item> &fields_arg, List<Item> &sel_fields, This is an element that is used by the GROUP BY and should be set to NULL in this level */ - Item_null_result *null_item= new (thd->mem_root) Item_null_result(); + Item_null_result *null_item= new (thd->mem_root) Item_null_result(thd); if (!null_item) return 1; item->maybe_null= 1; // Value will be null sometimes @@ -22551,603 +24118,770 @@ void JOIN::clear() } } -/** - EXPLAIN handling. - Send a description about what how the select will be done to stdout. +/* + Print an EXPLAIN line with all NULLs and given message in the 'Extra' column */ -static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, - bool distinct,const char *message) +int print_explain_message_line(select_result_sink *result, + uint8 options, bool is_analyze, + uint select_number, + const char *select_type, + ha_rows *rows, + const char *message) { - List<Item> field_list; + THD *thd= result->thd; + MEM_ROOT *mem_root= thd->mem_root; + Item *item_null= new (mem_root) Item_null(thd); List<Item> item_list; - THD *thd=join->thd; - select_result *result=join->result; - Item *item_null= new Item_null(); - CHARSET_INFO *cs= system_charset_info; - int quick_type; - DBUG_ENTER("select_describe"); - DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", - (ulong)join->select_lex, join->select_lex->type, - message ? message : "NULL")); - /* Don't log this into the slow query log */ - thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED); - join->unit->offset_limit_cnt= 0; - /* - NOTE: the number/types of items pushed into item_list must be in sync with - EXPLAIN column types as they're "defined" in THD::send_explain_fields() - */ + item_list.push_back(new (mem_root) Item_int(thd, (int32) select_number), + mem_root); + item_list.push_back(new (mem_root) Item_string_sys(thd, select_type), + mem_root); + /* `table` */ + item_list.push_back(item_null, mem_root); + + /* `partitions` */ + if (options & DESCRIBE_PARTITIONS) + item_list.push_back(item_null, mem_root); + + /* type, possible_keys, key, key_len, ref */ + for (uint i=0 ; i < 5; i++) + item_list.push_back(item_null, mem_root); + + /* `rows` */ + if (rows) + { + item_list.push_back(new (mem_root) Item_int(thd, *rows, + MY_INT64_NUM_DECIMAL_DIGITS), + mem_root); + } + else + item_list.push_back(item_null, mem_root); + + /* `r_rows` */ + if (is_analyze) + item_list.push_back(item_null, mem_root); + + /* `filtered` */ + if (is_analyze || options & DESCRIBE_EXTENDED) + item_list.push_back(item_null, mem_root); + + /* `r_filtered` */ + if (is_analyze) + item_list.push_back(item_null, mem_root); + + /* `Extra` */ if (message) + item_list.push_back(new (mem_root) Item_string_sys(thd, message), + mem_root); + else + item_list.push_back(item_null, mem_root); + + if (result->send_data(item_list)) + return 1; + return 0; +} + + +/* + Append MRR information from quick select to the given string +*/ + +void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) +{ + char mrr_str_buf[128]; + mrr_str_buf[0]=0; + int len; + handler *h= quick->head->file; + len= h->multi_range_read_explain_info(quick->mrr_flags, mrr_str_buf, + sizeof(mrr_str_buf)); + if (len > 0) + { + //res->append(STRING_WITH_LEN("; ")); + res->append(mrr_str_buf, len); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +int append_possible_keys(MEM_ROOT *alloc, String_list &list, TABLE *table, + key_map possible_keys) +{ + uint j; + for (j=0 ; j < table->s->keys ; j++) { - item_list.push_back(new Item_int((int32) - join->select_lex->select_number)); - item_list.push_back(new Item_string(join->select_lex->type, - strlen(join->select_lex->type), cs)); - for (uint i=0 ; i < 7; i++) - item_list.push_back(item_null); - if (join->thd->lex->describe & DESCRIBE_PARTITIONS) - item_list.push_back(item_null); - if (join->thd->lex->describe & DESCRIBE_EXTENDED) - item_list.push_back(item_null); + if (possible_keys.is_set(j)) + list.append_str(alloc, table->key_info[j].name); + } + return 0; +} + + +/* + TODO: this function is only applicable for the first non-const optimization + join tab. +*/ + +void JOIN_TAB::update_explain_data(uint idx) +{ + if (this == join->first_breadth_first_optimization_tab() + join->const_tables && + join->select_lex->select_number != INT_MAX && + join->select_lex->select_number != UINT_MAX) + { + Explain_table_access *eta= new (join->thd->mem_root) + Explain_table_access(join->thd->mem_root); + save_explain_data(eta, join->const_table_map, join->select_distinct, + join->first_breadth_first_optimization_tab()); + + Explain_select *sel= join->thd->lex->explain-> + get_select(join->select_lex->select_number); + idx -= my_count_bits(join->eliminated_tables); + sel->replace_table(idx, eta); + } +} + + +void JOIN_TAB::save_explain_data(Explain_table_access *eta, + table_map prefix_tables, + bool distinct, JOIN_TAB *first_top_tab) +{ + int quick_type; + CHARSET_INFO *cs= system_charset_info; + THD *thd= join->thd; + TABLE_LIST *table_list= table->pos_in_table_list; + QUICK_SELECT_I *cur_quick= NULL; + my_bool key_read; + char table_name_buffer[SAFE_NAME_LEN]; + KEY *key_info= 0; + uint key_len= 0; + quick_type= -1; + + explain_plan= eta; + eta->key.clear(); + eta->quick_info= NULL; - item_list.push_back(new Item_string(message,strlen(message),cs)); - if (result->send_data(item_list)) - join->error= 1; + tracker= &eta->tracker; + jbuf_tracker= &eta->jbuf_tracker; + + /* Enable the table access time tracker only for "ANALYZE stmt" */ + if (thd->lex->analyze_stmt) + table->file->set_time_tracker(&eta->op_tracker); + + /* No need to save id and select_type here, they are kept in Explain_select */ + + /* table */ + if (table->derived_select_number) + { + /* Derived table name generation */ + int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1, + "<derived%u>", + table->derived_select_number); + eta->table_name.copy(table_name_buffer, len, cs); } - else if (join->select_lex == join->unit->fake_select_lex) + else if (bush_children) { - /* - here we assume that the query will return at least two rows, so we - show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong - and no filesort will be actually done, but executing all selects in - the UNION to provide precise EXPLAIN information will hardly be - appreciated :) - */ - char table_name_buffer[SAFE_NAME_LEN]; - item_list.empty(); - /* id */ - item_list.push_back(new Item_null); - /* select_type */ - item_list.push_back(new Item_string(join->select_lex->type, - strlen(join->select_lex->type), - cs)); + JOIN_TAB *ctab= bush_children->start; /* table */ + int len= my_snprintf(table_name_buffer, + sizeof(table_name_buffer)-1, + "<subquery%d>", + ctab->emb_sj_nest->sj_subq_pred->get_identifier()); + eta->table_name.copy(table_name_buffer, len, cs); + } + else + { + TABLE_LIST *real_table= table->pos_in_table_list; + /* + When multi-table UPDATE/DELETE does updates/deletes to a VIEW, the view + is merged in a certain particular way (grep for DT_MERGE_FOR_INSERT). + + As a result, view's underlying tables have $tbl->pos_in_table_list={view}. + We don't want to print view name in EXPLAIN, we want underlying table's + alias (like specified in the view definition). + */ + if (real_table->merged_for_insert) { - SELECT_LEX *sl= join->unit->first_select(); - uint len= 6, lastop= 0; - memcpy(table_name_buffer, STRING_WITH_LEN("<union")); - for (; sl && len + lastop + 5 < NAME_LEN; sl= sl->next_select()) - { - len+= lastop; - lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len, - "%u,", sl->select_number); - } - if (sl || len + lastop >= NAME_LEN) + TABLE_LIST *view_child= real_table->view->select_lex.table_list.first; + for (;view_child; view_child= view_child->next_local) { - memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); - len+= 4; + if (view_child->table == table) + { + real_table= view_child; + break; + } } - else - { - len+= lastop; - table_name_buffer[len - 1]= '>'; // change ',' to '>' - } - item_list.push_back(new Item_string(table_name_buffer, len, cs)); - } - /* partitions */ - if (join->thd->lex->describe & DESCRIBE_PARTITIONS) - item_list.push_back(item_null); - /* type */ - item_list.push_back(new Item_string(join_type_str[JT_ALL], - strlen(join_type_str[JT_ALL]), - cs)); - /* possible_keys */ - item_list.push_back(item_null); - /* key*/ - item_list.push_back(item_null); - /* key_len */ - item_list.push_back(item_null); - /* ref */ - item_list.push_back(item_null); - /* in_rows */ - if (join->thd->lex->describe & DESCRIBE_EXTENDED) - item_list.push_back(item_null); - /* rows */ - item_list.push_back(item_null); - /* extra */ - if (join->unit->global_parameters->order_list.first) - item_list.push_back(new Item_string("Using filesort", - 14, cs)); + } + eta->table_name.copy(real_table->alias, strlen(real_table->alias), cs); + } + + /* "partitions" column */ + { +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *part_info; + if (!table->derived_select_number && + (part_info= table->part_info)) + { //TODO: all thd->mem_root here should be fixed + make_used_partitions_str(thd->mem_root, part_info, &eta->used_partitions, + eta->used_partitions_list); + eta->used_partitions_set= true; + } else - item_list.push_back(new Item_string("", 0, cs)); + eta->used_partitions_set= false; +#else + /* just produce empty column if partitioning is not compiled in */ + eta->used_partitions_set= false; +#endif + } - if (result->send_data(item_list)) - join->error= 1; + /* "type" column */ + enum join_type tab_type= type; + if ((type == JT_ALL || type == JT_HASH) && + select && select->quick && use_quick != 2) + { + cur_quick= select->quick; + quick_type= cur_quick->get_type(); + if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || + (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) + tab_type= type == JT_ALL ? JT_INDEX_MERGE : JT_HASH_INDEX_MERGE; + else + tab_type= type == JT_ALL ? JT_RANGE : JT_HASH_RANGE; } - else if (!join->select_lex->master_unit()->derived || - join->select_lex->master_unit()->derived->is_materialized_derived()) + eta->type= tab_type; + + /* Build "possible_keys" value */ + // psergey-todo: why does this use thd MEM_ROOT??? Doesn't this + // break ANALYZE ? thd->mem_root will be freed, and after that we will + // attempt to print the query plan? + append_possible_keys(thd->mem_root, eta->possible_keys, table, keys); + // psergey-todo: ^ check for error return code + + /* Build "key", "key_len", and "ref" */ + if (tab_type == JT_NEXT) { - table_map used_tables=0; + key_info= table->key_info+index; + key_len= key_info->key_length; + } + else if (ref.key_parts) + { + key_info= get_keyinfo_by_key_no(ref.key); + key_len= ref.key_length; + } + + /* + In STRAIGHT_JOIN queries, there can be join tabs with JT_CONST type + that still have quick selects. + */ + if (select && select->quick && tab_type != JT_CONST) + { + eta->quick_info= select->quick->get_explain(thd->mem_root); + } - bool printing_materialize_nest= FALSE; - uint select_id= join->select_lex->select_number; - - for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; - tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab)) - { - if (tab->bush_root_tab) - { - JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start; - select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); - printing_materialize_nest= TRUE; - } - - TABLE *table=tab->table; - TABLE_LIST *table_list= tab->table->pos_in_table_list; - char buff[512]; - char buff1[512], buff2[512], buff3[512], buff4[512]; - char keylen_str_buf[64]; - my_bool key_read; - String extra(buff, sizeof(buff),cs); - char table_name_buffer[SAFE_NAME_LEN]; - String tmp1(buff1,sizeof(buff1),cs); - String tmp2(buff2,sizeof(buff2),cs); - String tmp3(buff3,sizeof(buff3),cs); - String tmp4(buff4,sizeof(buff4),cs); - char hash_key_prefix[]= "#hash#"; - KEY *key_info= 0; - uint key_len= 0; - bool is_hj= tab->type == JT_HASH || tab->type ==JT_HASH_NEXT; - - extra.length(0); - tmp1.length(0); - tmp2.length(0); - tmp3.length(0); - tmp4.length(0); - quick_type= -1; + if (key_info) /* 'index' or 'ref' access */ + { + eta->key.set(thd->mem_root, key_info, key_len); - /* Don't show eliminated tables */ - if (table->map & join->eliminated_tables) + if (ref.key_parts && tab_type != JT_FT) + { + store_key **key_ref= ref.key_copy; + for (uint kp= 0; kp < ref.key_parts; kp++) { - used_tables|=table->map; - continue; - } - - item_list.empty(); - /* id */ - item_list.push_back(new Item_uint((uint32)select_id)); - /* select_type */ - const char* stype= printing_materialize_nest? "MATERIALIZED" : - join->select_lex->type; - item_list.push_back(new Item_string(stype, strlen(stype), cs)); - - if ((tab->type == JT_ALL || tab->type == JT_HASH) && - tab->select && tab->select->quick) - { - quick_type= tab->select->quick->get_type(); - if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || - (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || - (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || - (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) - tab->type= tab->type == JT_ALL ? JT_INDEX_MERGE : JT_HASH_INDEX_MERGE; + if ((key_part_map(1) << kp) & ref.const_ref_part_map) + eta->ref_list.append_str(thd->mem_root, "const"); else - tab->type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE; + { + eta->ref_list.append_str(thd->mem_root, (*key_ref)->name()); + key_ref++; + } } + } + } + + if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */ + { + eta->hash_next_key.set(thd->mem_root, + & table->key_info[index], + table->key_info[index].key_length); + // psergey-todo: ^ is the above correct? are we necessarily joining on all + // columns? + } - /* table */ - if (table->derived_select_number) + if (!key_info) + { + if (table_list && /* SJM bushes don't have table_list */ + table_list->schema_table && + table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) + { + IS_table_read_plan *is_table_read_plan= table_list->is_table_read_plan; + const char *tmp_buff; + int f_idx; + StringBuffer<64> key_name_buf; + if (is_table_read_plan->trivial_show_command || + is_table_read_plan->has_db_lookup_value()) { - /* Derived table name generation */ - int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1, - "<derived%u>", - table->derived_select_number); - item_list.push_back(new Item_string(table_name_buffer, len, cs)); - } - else if (tab->bush_children) + /* The "key" has the name of the column referring to the database */ + f_idx= table_list->schema_table->idx_field1; + tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; + key_name_buf.append(tmp_buff, strlen(tmp_buff), cs); + } + if (is_table_read_plan->trivial_show_command || + is_table_read_plan->has_table_lookup_value()) { - JOIN_TAB *ctab= tab->bush_children->start; - /* table */ - int len= my_snprintf(table_name_buffer, - sizeof(table_name_buffer)-1, - "<subquery%d>", - ctab->emb_sj_nest->sj_subq_pred->get_identifier()); - item_list.push_back(new Item_string(table_name_buffer, len, cs)); + if (is_table_read_plan->trivial_show_command || + is_table_read_plan->has_db_lookup_value()) + key_name_buf.append(','); + + f_idx= table_list->schema_table->idx_field2; + tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; + key_name_buf.append(tmp_buff, strlen(tmp_buff), cs); } + + if (key_name_buf.length()) + eta->key.set_pseudo_key(thd->mem_root, key_name_buf.c_ptr_safe()); + } + } + + /* "rows" */ + if (table_list /* SJM bushes don't have table_list */ && + table_list->schema_table) + { + /* I_S tables have rows=extra=NULL */ + eta->rows_set= false; + eta->filtered_set= false; + } + else + { + double examined_rows= get_examined_rows(); + + eta->rows_set= true; + eta->rows= (ha_rows) examined_rows; + + /* "filtered" */ + float f= 0.0; + if (examined_rows) + { + double pushdown_cond_selectivity= cond_selectivity; + if (pushdown_cond_selectivity == 1.0) + f= (float) (100.0 * records_read / examined_rows); else + f= (float) (100.0 * pushdown_cond_selectivity); + } + set_if_smaller(f, 100.0); + eta->filtered_set= true; + eta->filtered= f; + } + + /* Build "Extra" field and save it */ + key_read=table->key_read; + if ((tab_type == JT_NEXT || tab_type == JT_CONST) && + table->covering_keys.is_set(index)) + key_read=1; + if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT && + !((QUICK_ROR_INTERSECT_SELECT*)cur_quick)->need_to_fetch_row) + key_read=1; + + if (info) + { + eta->push_extra(info); + } + else if (packed_info & TAB_INFO_HAVE_VALUE) + { + if (packed_info & TAB_INFO_USING_INDEX) + eta->push_extra(ET_USING_INDEX); + if (packed_info & TAB_INFO_USING_WHERE) + eta->push_extra(ET_USING_WHERE); + if (packed_info & TAB_INFO_FULL_SCAN_ON_NULL) + eta->push_extra(ET_FULL_SCAN_ON_NULL_KEY); + } + else + { + uint keyno= MAX_KEY; + if (ref.key_parts) + keyno= ref.key; + else if (select && cur_quick) + keyno = cur_quick->index; + + if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && + table->file->pushed_idx_cond) + { + eta->push_extra(ET_USING_INDEX_CONDITION); + eta->pushed_index_cond= table->file->pushed_idx_cond; + } + else if (cache_idx_cond) + { + eta->push_extra(ET_USING_INDEX_CONDITION_BKA); + eta->pushed_index_cond= cache_idx_cond; + } + + if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || + quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || + quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || + quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + { + eta->push_extra(ET_USING); + } + if (select) + { + if (use_quick == 2) { - TABLE_LIST *real_table= table->pos_in_table_list; - item_list.push_back(new Item_string(real_table->alias, - strlen(real_table->alias), cs)); + eta->push_extra(ET_RANGE_CHECKED_FOR_EACH_RECORD); + eta->range_checked_fer= new (thd->mem_root) Explain_range_checked_fer; + if (eta->range_checked_fer) + eta->range_checked_fer-> + append_possible_keys_stat(thd->mem_root, table, keys); } - /* "partitions" column */ - if (join->thd->lex->describe & DESCRIBE_PARTITIONS) + else if (select->cond || + (cache_select && cache_select->cond)) { -#ifdef WITH_PARTITION_STORAGE_ENGINE - partition_info *part_info; - if (!table->derived_select_number && - (part_info= table->part_info)) - { - Item_string *item_str= new Item_string(cs); - make_used_partitions_str(part_info, &item_str->str_value); - item_list.push_back(item_str); + const COND *pushed_cond= table->file->pushed_cond; + + if ((table->file->ha_table_flags() & + HA_CAN_TABLE_CONDITION_PUSHDOWN) && + pushed_cond) + { + eta->push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION); } else - item_list.push_back(item_null); -#else - /* just produce empty column if partitioning is not compiled in */ - item_list.push_back(item_null); -#endif - } - /* "type" column */ - item_list.push_back(new Item_string(join_type_str[tab->type], - strlen(join_type_str[tab->type]), - cs)); - /* Build "possible_keys" value and add it to item_list */ - if (!tab->keys.is_clear_all()) - { - uint j; - for (j=0 ; j < table->s->keys ; j++) { - if (tab->keys.is_set(j)) - { - if (tmp1.length()) - tmp1.append(','); - tmp1.append(table->key_info[j].name, - strlen(table->key_info[j].name), - system_charset_info); - } + eta->where_cond= select->cond; + eta->cache_cond= cache_select? cache_select->cond : NULL; + eta->push_extra(ET_USING_WHERE); } } - if (tmp1.length()) - item_list.push_back(new Item_string(tmp1.ptr(),tmp1.length(),cs)); + } + if (table_list /* SJM bushes don't have table_list */ && + table_list->schema_table && + table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) + { + if (!table_list->table_open_method) + eta->push_extra(ET_SKIP_OPEN_TABLE); + else if (table_list->table_open_method == OPEN_FRM_ONLY) + eta->push_extra(ET_OPEN_FRM_ONLY); else - item_list.push_back(item_null); - - /* Build "key", "key_len", and "ref" values and add them to item_list */ - if (tab->type == JT_NEXT) - { - key_info= table->key_info+tab->index; - key_len= key_info->key_length; - } - else if (tab->ref.key_parts) - { - key_info= tab->get_keyinfo_by_key_no(tab->ref.key); - key_len= tab->ref.key_length; - } - if (key_info) - { - register uint length; - if (is_hj) - tmp2.append(hash_key_prefix, strlen(hash_key_prefix), cs); - tmp2.append(key_info->name, strlen(key_info->name), cs); - length= (longlong10_to_str(key_len, keylen_str_buf, 10) - - keylen_str_buf); - tmp3.append(keylen_str_buf, length, cs); - if (tab->ref.key_parts) - { - for (store_key **ref=tab->ref.key_copy ; *ref ; ref++) - { - if (tmp4.length()) - tmp4.append(','); - tmp4.append((*ref)->name(), strlen((*ref)->name()), cs); - } - } - } - if (is_hj && tab->type != JT_HASH) - { - tmp2.append(':'); - tmp3.append(':'); - } - if (tab->type == JT_HASH_NEXT) - { - register uint length; - key_info= table->key_info+tab->index; - key_len= key_info->key_length; - tmp2.append(key_info->name, strlen(key_info->name), cs); - length= (longlong10_to_str(key_len, keylen_str_buf, 10) - - keylen_str_buf); - tmp3.append(keylen_str_buf, length, cs); - } - if (tab->type != JT_CONST && tab->select && tab->select->quick) - tab->select->quick->add_keys_and_lengths(&tmp2, &tmp3); - if (key_info || (tab->select && tab->select->quick)) + eta->push_extra(ET_OPEN_FULL_TABLE); + /* psergey-note: the following has a bug.*/ + if (table_list->is_table_read_plan->trivial_show_command || + (table_list->is_table_read_plan->has_db_lookup_value() && + table_list->is_table_read_plan->has_table_lookup_value())) + eta->push_extra(ET_SCANNED_0_DATABASES); + else if (table_list->is_table_read_plan->has_db_lookup_value() || + table_list->is_table_read_plan->has_table_lookup_value()) + eta->push_extra(ET_SCANNED_1_DATABASE); + else + eta->push_extra(ET_SCANNED_ALL_DATABASES); + } + if (key_read) + { + if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) { - if (tmp2.length()) - item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs)); - else - item_list.push_back(item_null); - if (tmp3.length()) - item_list.push_back(new Item_string(tmp3.ptr(),tmp3.length(),cs)); - else - item_list.push_back(item_null); - if (key_info && tab->type != JT_NEXT) - item_list.push_back(new Item_string(tmp4.ptr(),tmp4.length(),cs)); - else - item_list.push_back(item_null); + QUICK_GROUP_MIN_MAX_SELECT *qgs= + (QUICK_GROUP_MIN_MAX_SELECT *) select->quick; + eta->push_extra(ET_USING_INDEX_FOR_GROUP_BY); + eta->loose_scan_is_scanning= qgs->loose_scan_is_scanning(); } else + eta->push_extra(ET_USING_INDEX); + } + if (table->reginfo.not_exists_optimize) + eta->push_extra(ET_NOT_EXISTS); + + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) + { + explain_append_mrr_info((QUICK_RANGE_SELECT*)(select->quick), + &eta->mrr_type); + if (eta->mrr_type.length() > 0) + eta->push_extra(ET_USING_MRR); + } + + if (distinct & test_all_bits(prefix_tables, join->select_list_used_tables)) + eta->push_extra(ET_DISTINCT); + if (loosescan_match_tab) + { + eta->push_extra(ET_LOOSESCAN); + } + + if (first_weedout_table) + { + eta->start_dups_weedout= true; + eta->push_extra(ET_START_TEMPORARY); + } + if (check_weed_out_table) + { + eta->push_extra(ET_END_TEMPORARY); + eta->end_dups_weedout= true; + } + + else if (do_firstmatch) + { + if (do_firstmatch == /*join->join_tab*/ first_top_tab - 1) + eta->push_extra(ET_FIRST_MATCH); + else { - if (table_list && /* SJM bushes don't have table_list */ - table_list->schema_table && - table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) + eta->push_extra(ET_FIRST_MATCH); + TABLE *prev_table=do_firstmatch->table; + if (prev_table->derived_select_number) { - const char *tmp_buff; - int f_idx; - if (table_list->has_db_lookup_value) - { - f_idx= table_list->schema_table->idx_field1; - tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; - tmp2.append(tmp_buff, strlen(tmp_buff), cs); - } - if (table_list->has_table_lookup_value) - { - if (table_list->has_db_lookup_value) - tmp2.append(','); - f_idx= table_list->schema_table->idx_field2; - tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; - tmp2.append(tmp_buff, strlen(tmp_buff), cs); - } - if (tmp2.length()) - item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs)); - else - item_list.push_back(item_null); + char namebuf[NAME_LEN]; + /* Derived table name generation */ + int len= my_snprintf(namebuf, sizeof(namebuf)-1, + "<derived%u>", + prev_table->derived_select_number); + eta->firstmatch_table_name.append(namebuf, len); } else - item_list.push_back(item_null); - item_list.push_back(item_null); - item_list.push_back(item_null); + eta->firstmatch_table_name.append(prev_table->pos_in_table_list->alias); } - - /* Add "rows" field to item_list. */ - if (table_list /* SJM bushes don't have table_list */ && - table_list->schema_table) + } + + for (uint part= 0; part < ref.key_parts; part++) + { + if (ref.cond_guards[part]) { - /* in_rows */ - if (join->thd->lex->describe & DESCRIBE_EXTENDED) - item_list.push_back(item_null); - /* rows */ - item_list.push_back(item_null); + eta->push_extra(ET_FULL_SCAN_ON_NULL_KEY); + eta->full_scan_on_null_key= true; + break; } - else - { - ha_rows examined_rows= tab->get_examined_rows(); + } - item_list.push_back(new Item_int((longlong) (ulonglong) examined_rows, - MY_INT64_NUM_DECIMAL_DIGITS)); + if (cache) + { + eta->push_extra(ET_USING_JOIN_BUFFER); + cache->save_explain_data(&eta->bka_type); + } + } - /* Add "filtered" field to item_list. */ - if (join->thd->lex->describe & DESCRIBE_EXTENDED) - { - float f= 0.0; - if (examined_rows) - f= (float) (100.0 * tab->records_read / examined_rows); - set_if_smaller(f, 100.0); - item_list.push_back(new Item_float(f, 2)); - } - } + /* + In case this is a derived table, here we remember the number of + subselect that used to produce it. + */ + eta->derived_select_number= table->derived_select_number; - /* Build "Extra" field and add it to item_list. */ - key_read=table->key_read; - if ((tab->type == JT_NEXT || tab->type == JT_CONST) && - table->covering_keys.is_set(tab->index)) - key_read=1; - if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT && - !((QUICK_ROR_INTERSECT_SELECT*)tab->select->quick)->need_to_fetch_row) - key_read=1; - - if (tab->info) - item_list.push_back(new Item_string(tab->info,strlen(tab->info),cs)); - else if (tab->packed_info & TAB_INFO_HAVE_VALUE) - { - if (tab->packed_info & TAB_INFO_USING_INDEX) - extra.append(STRING_WITH_LEN("; Using index")); - if (tab->packed_info & TAB_INFO_USING_WHERE) - extra.append(STRING_WITH_LEN("; Using where")); - if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL) - extra.append(STRING_WITH_LEN("; Full scan on NULL key")); - /* Skip initial "; "*/ - const char *str= extra.ptr(); - uint32 len= extra.length(); - if (len) - { - str += 2; - len -= 2; - } - item_list.push_back(new Item_string(str, len, cs)); - } - else - { - uint keyno= MAX_KEY; - if (tab->ref.key_parts) - keyno= tab->ref.key; - else if (tab->select && tab->select->quick) - keyno = tab->select->quick->index; - - if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && - table->file->pushed_idx_cond) - extra.append(STRING_WITH_LEN("; Using index condition")); - else if (tab->cache_idx_cond) - extra.append(STRING_WITH_LEN("; Using index condition(BKA)")); - - if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || - quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) - { - extra.append(STRING_WITH_LEN("; Using ")); - tab->select->quick->add_info_string(&extra); - } - if (tab->select) - { - if (tab->use_quick == 2) - { - /* 4 bits per 1 hex digit + terminating '\0' */ - char buf[MAX_KEY / 4 + 1]; - extra.append(STRING_WITH_LEN("; Range checked for each " - "record (index map: 0x")); - extra.append(tab->keys.print(buf)); - extra.append(')'); - } - else if (tab->select->cond) - { - const COND *pushed_cond= tab->table->file->pushed_cond; + /* The same for non-merged semi-joins */ + eta->non_merged_sjm_number = get_non_merged_semijoin_select(); +} - if (((thd->variables.optimizer_switch & - OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN) || - (tab->table->file->ha_table_flags() & - HA_MUST_USE_TABLE_CONDITION_PUSHDOWN)) && - pushed_cond) - { - extra.append(STRING_WITH_LEN("; Using where with pushed " - "condition")); - if (thd->lex->describe & DESCRIBE_EXTENDED) - { - extra.append(STRING_WITH_LEN(": ")); - ((COND *)pushed_cond)->print(&extra, QT_ORDINARY); - } - } - else - extra.append(STRING_WITH_LEN("; Using where")); - } - } - if (table_list /* SJM bushes don't have table_list */ && - table_list->schema_table && - table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) - { - if (!table_list->table_open_method) - extra.append(STRING_WITH_LEN("; Skip_open_table")); - else if (table_list->table_open_method == OPEN_FRM_ONLY) - extra.append(STRING_WITH_LEN("; Open_frm_only")); - else - extra.append(STRING_WITH_LEN("; Open_full_table")); - if (table_list->has_db_lookup_value && - table_list->has_table_lookup_value) - extra.append(STRING_WITH_LEN("; Scanned 0 databases")); - else if (table_list->has_db_lookup_value || - table_list->has_table_lookup_value) - extra.append(STRING_WITH_LEN("; Scanned 1 database")); - else - extra.append(STRING_WITH_LEN("; Scanned all databases")); - } - if (key_read) - { - if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) - { - QUICK_GROUP_MIN_MAX_SELECT *qgs= - (QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick; - extra.append(STRING_WITH_LEN("; Using index for group-by")); - qgs->append_loose_scan_type(&extra); - } - else - extra.append(STRING_WITH_LEN("; Using index")); - } - if (table->reginfo.not_exists_optimize) - extra.append(STRING_WITH_LEN("; Not exists")); - /* - if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE && - !(((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags & - HA_MRR_USE_DEFAULT_IMPL)) - { - extra.append(STRING_WITH_LEN("; Using MRR")); - } - */ - if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) - { - char mrr_str_buf[128]; - mrr_str_buf[0]=0; - int len; - uint mrr_flags= - ((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags; - len= table->file->multi_range_read_explain_info(mrr_flags, - mrr_str_buf, - sizeof(mrr_str_buf)); - if (len > 0) - { - extra.append(STRING_WITH_LEN("; ")); - extra.append(mrr_str_buf, len); - } - } +/* + Save Query Plan Footprint - if (need_tmp_table) - { - need_tmp_table=0; - extra.append(STRING_WITH_LEN("; Using temporary")); - } - if (need_order) - { - need_order=0; - extra.append(STRING_WITH_LEN("; Using filesort")); - } - if (distinct & test_all_bits(used_tables, - join->select_list_used_tables)) - extra.append(STRING_WITH_LEN("; Distinct")); - if (tab->loosescan_match_tab) - { - extra.append(STRING_WITH_LEN("; LooseScan")); - } + @note + Currently, this function may be called multiple times +*/ - if (tab->first_weedout_table) - extra.append(STRING_WITH_LEN("; Start temporary")); - if (tab->check_weed_out_table) - extra.append(STRING_WITH_LEN("; End temporary")); - else if (tab->do_firstmatch) - { - if (tab->do_firstmatch == join->join_tab - 1) - extra.append(STRING_WITH_LEN("; FirstMatch")); - else - { - extra.append(STRING_WITH_LEN("; FirstMatch(")); - TABLE *prev_table=tab->do_firstmatch->table; - if (prev_table->derived_select_number) - { - char namebuf[NAME_LEN]; - /* Derived table name generation */ - int len= my_snprintf(namebuf, sizeof(namebuf)-1, - "<derived%u>", - prev_table->derived_select_number); - extra.append(namebuf, len); - } - else - extra.append(prev_table->pos_in_table_list->alias); - extra.append(STRING_WITH_LEN(")")); - } - } +int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, + bool need_order, bool distinct, + const char *message) +{ + JOIN *join= this; /* Legacy: this code used to be a non-member function */ + int cur_error= 0; + DBUG_ENTER("JOIN::save_explain_data_intern"); + DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", + (ulong)join->select_lex, join->select_lex->type, + message ? message : "NULL")); + DBUG_ASSERT(have_query_plan == QEP_AVAILABLE); + /* fake_select_lex is created/printed by Explain_union */ + DBUG_ASSERT(join->select_lex != join->unit->fake_select_lex); + + /* There should be no attempts to save query plans for merged selects */ + DBUG_ASSERT(!join->select_lex->master_unit()->derived || + join->select_lex->master_unit()->derived->is_materialized_derived()); + + explain= NULL; + + /* Don't log this into the slow query log */ + + if (message) + { + explain= new (output->mem_root) Explain_select(output->mem_root, + thd->lex->analyze_stmt); + if (!explain) + DBUG_RETURN(1); // EoM +#ifndef DBUG_OFF + explain->select_lex= select_lex; +#endif + join->select_lex->set_explain_type(true); + + explain->select_id= join->select_lex->select_number; + explain->select_type= join->select_lex->type; + explain->using_temporary= need_tmp; + explain->using_filesort= need_order; + /* Setting explain->message means that all other members are invalid */ + explain->message= message; + + if (select_lex->master_unit()->derived) + explain->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; + output->add_node(explain); + } + else if (pushdown_query) + { + explain= new (output->mem_root) Explain_select(output->mem_root, + thd->lex->analyze_stmt); + select_lex->set_explain_type(true); + + explain->select_id= select_lex->select_number; + explain->select_type= select_lex->type; + explain->using_temporary= need_tmp; + explain->using_filesort= need_order; + explain->message= "Storage engine handles GROUP BY"; + + if (select_lex->master_unit()->derived) + explain->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; + output->add_node(explain); + } + else + { + Explain_select *xpl_sel; + explain= xpl_sel= + new (output->mem_root) Explain_select(output->mem_root, + thd->lex->analyze_stmt); + table_map used_tables=0; + + join->select_lex->set_explain_type(true); + xpl_sel->select_id= join->select_lex->select_number; + xpl_sel->select_type= join->select_lex->type; + if (select_lex->master_unit()->derived) + xpl_sel->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; + + if (need_tmp_table) + xpl_sel->using_temporary= true; + + if (need_order) + xpl_sel->using_filesort= true; + + xpl_sel->exec_const_cond= exec_const_cond; + if (tmp_having) + xpl_sel->having= tmp_having; + else + xpl_sel->having= having; + xpl_sel->having_value= having_value; + + JOIN_TAB* const first_top_tab= join->first_breadth_first_optimization_tab(); + JOIN_TAB* prev_bush_root_tab= NULL; + + Explain_basic_join *cur_parent= xpl_sel; + + for (JOIN_TAB *tab= first_explain_order_tab(join); tab; + tab= next_explain_order_tab(join, tab)) + { + JOIN_TAB *saved_join_tab= NULL; + TABLE *cur_table= tab->table; + + /* Don't show eliminated tables */ + if (cur_table->map & join->eliminated_tables) + { + used_tables|= cur_table->map; + continue; + } + + + if (join->table_access_tabs == join->join_tab && + tab == (first_top_tab + join->const_tables) && pre_sort_join_tab) + { + saved_join_tab= tab; + tab= pre_sort_join_tab; + } - for (uint part= 0; part < tab->ref.key_parts; part++) + Explain_table_access *eta= (new (output->mem_root) + Explain_table_access(output->mem_root)); + + if (tab->bush_root_tab != prev_bush_root_tab) + { + if (tab->bush_root_tab) { - if (tab->ref.cond_guards[part]) - { - extra.append(STRING_WITH_LEN("; Full scan on NULL key")); - break; - } - } + /* + We've entered an SJ-Materialization nest. Create an object for it. + */ + cur_parent= new (output->mem_root) Explain_basic_join(output->mem_root); - if (tab->cache) - { - extra.append(STRING_WITH_LEN("; Using join buffer")); - tab->cache->print_explain_comment(&extra); + JOIN_TAB *first_child= tab->bush_root_tab->bush_children->start; + cur_parent->select_id= + first_child->emb_sj_nest->sj_subq_pred->get_identifier(); } - - /* Skip initial "; "*/ - const char *str= extra.ptr(); - uint32 len= extra.length(); - if (len) + else { - str += 2; - len -= 2; + /* + We've just left an SJ-Materialization nest. We are at the join tab + that 'embeds the nest' + */ + DBUG_ASSERT(tab->bush_children); + eta->sjm_nest= cur_parent; + cur_parent= xpl_sel; } - item_list.push_back(new Item_string(str, len, cs)); } - + prev_bush_root_tab= tab->bush_root_tab; + + cur_parent->add_table(eta, output); + tab->save_explain_data(eta, used_tables, distinct, first_top_tab); + + if (saved_join_tab) + tab= saved_join_tab; + // For next iteration - used_tables|=table->map; - if (result->send_data(item_list)) - join->error= 1; + used_tables|= cur_table->map; } + output->add_node(xpl_sel); } + + for (SELECT_LEX_UNIT *tmp_unit= join->select_lex->first_inner_unit(); + tmp_unit; + tmp_unit= tmp_unit->next_unit()) + { + /* + Display subqueries only if + (1) they are not parts of ON clauses that were eliminated by table + elimination. + (2) they are not merged derived tables + */ + if (!(tmp_unit->item && tmp_unit->item->eliminated) && // (1) + (!tmp_unit->derived || + tmp_unit->derived->is_materialized_derived())) // (2) + { + explain->add_child(tmp_unit->first_select()->select_number); + } + } + + if (!cur_error && select_lex->is_top_level_node()) + output->query_plan_ready(); + + DBUG_RETURN(cur_error); +} + + +/* + This function serves as "shortcut point" for EXPLAIN queries. + + The EXPLAIN statement executes just like its SELECT counterpart would + execute, except that JOIN::exec() will call select_describe() instead of + actually executing the query. + + Inside select_describe(): + - Query plan is updated with latest QEP choices made at the start of + JOIN::exec(). + - the proces of "almost execution" is invoked for the children subqueries. + + Overall, select_describe() is a legacy of old EXPLAIN implementation and + should be removed. +*/ + +static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, + bool distinct,const char *message) +{ + THD *thd=join->thd; + select_result *result=join->result; + DBUG_ENTER("select_describe"); + + /* Update the QPF with latest values of using_temporary, using_filesort */ + Explain_select *explain_sel; + uint select_nr= join->select_lex->select_number; + if ((explain_sel= thd->lex->explain->get_select(select_nr))) + { + explain_sel->using_temporary= need_tmp_table; + explain_sel->using_filesort= need_order; + } + for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); unit; unit= unit->next_unit()) @@ -23169,10 +24903,12 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, } /* - Display subqueries only if they are not parts of eliminated WHERE/ON - clauses. + Save plans for child subqueries, when + (1) they are not parts of eliminated WHERE/ON clauses. + (2) they are not VIEWs that were "merged for INSERT". */ - if (!(unit->item && unit->item->eliminated)) + if (!(unit->item && unit->item->eliminated) && // (1) + !(unit->derived && unit->derived->merged_for_insert)) // (2) { if (mysql_explain_union(thd, unit, result)) DBUG_VOID_RETURN; @@ -23190,22 +24926,25 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) { - sl->set_explain_type(); + sl->set_explain_type(FALSE); sl->options|= SELECT_DESCRIBE; } if (unit->is_union()) { - unit->fake_select_lex->select_number= UINT_MAX; // jost for initialization - unit->fake_select_lex->type= "UNION RESULT"; - unit->fake_select_lex->options|= SELECT_DESCRIBE; + if (unit->union_needs_tmp_table()) + { + unit->fake_select_lex->select_number= FAKE_SELECT_LEX_ID; // just for initialization + unit->fake_select_lex->type= "UNION RESULT"; + unit->fake_select_lex->options|= SELECT_DESCRIBE; + } if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE))) res= unit->exec(); } else { thd->lex->current_select= first; - unit->set_limit(unit->global_parameters); + unit->set_limit(unit->global_parameters()); res= mysql_select(thd, &first->ref_pointer_array, first->table_list.first, first->with_wild, first->item_list, @@ -23305,6 +25044,7 @@ static void print_join(THD *thd, /* List is reversed => we should reverse it before using */ List_iterator_fast<TABLE_LIST> ti(*tables); TABLE_LIST **table; + DBUG_ENTER("print_join"); /* If the QT_NO_DATA_EXPANSION flag is specified, we print the @@ -23337,13 +25077,13 @@ static void print_join(THD *thd, if (tables_to_print == 0) { str->append(STRING_WITH_LEN("dual")); - return; // all tables were optimized away + DBUG_VOID_RETURN; // all tables were optimized away } ti.rewind(); if (!(table= static_cast<TABLE_LIST **>(thd->alloc(sizeof(TABLE_LIST*) * tables_to_print)))) - return; // out of memory + DBUG_VOID_RETURN; // out of memory TABLE_LIST *tmp, **t= table + (tables_to_print - 1); while ((tmp= ti++)) @@ -23375,7 +25115,7 @@ static void print_join(THD *thd, { if (!(*t2)->sj_inner_tables) { - TABLE_LIST *tmp= *t2; + tmp= *t2; *t2= *table; *table= tmp; break; @@ -23384,6 +25124,7 @@ static void print_join(THD *thd, } print_table_array(thd, eliminated_tables, str, table, table + tables_to_print, query_type); + DBUG_VOID_RETURN; } /** @@ -23509,6 +25250,22 @@ void TABLE_LIST::print(THD *thd, table_map eliminated_tables, String *str, append_identifier(thd, str, table_name, table_name_length); cmp_name= table_name; } +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (partition_names && partition_names->elements) + { + int i, num_parts= partition_names->elements; + List_iterator<String> name_it(*(partition_names)); + str->append(STRING_WITH_LEN(" PARTITION (")); + for (i= 1; i <= num_parts; i++) + { + String *name= name_it++; + append_identifier(thd, str, name->c_ptr(), name->length()); + if (i != num_parts) + str->append(','); + } + str->append(')'); + } +#endif /* WITH_PARTITION_STORAGE_ENGINE */ } if (my_strcasecmp(table_alias_charset, cmp_name, alias)) { @@ -23516,7 +25273,7 @@ void TABLE_LIST::print(THD *thd, table_map eliminated_tables, String *str, const char *t_alias= alias; str->append(' '); - if (lower_case_table_names== 1) + if (lower_case_table_names == 1) { if (alias && alias[0]) { @@ -23686,33 +25443,44 @@ void st_select_lex::print(THD *thd, String *str, enum_query_type query_type) // limit print_limit(thd, str, query_type); + // lock type + if (lock_type == TL_READ_WITH_SHARED_LOCKS) + str->append(" lock in share mode"); + else if (lock_type == TL_WRITE) + str->append(" for update"); + // PROCEDURE unsupported here } /** - change select_result object of JOIN. + Change the select_result object of the JOIN. - @param res new select_result object + If old_result is not used, forward the call to the current + select_result in case it is a wrapper around old_result. - @retval - FALSE OK - @retval - TRUE error + Call prepare() and prepare2() on the new select_result if we decide + to use it. + + @param new_result New select_result object + @param old_result Old select_result object (NULL to force change) + + @retval false Success + @retval true Error */ -bool JOIN::change_result(select_result *res) +bool JOIN::change_result(select_result *new_result, select_result *old_result) { DBUG_ENTER("JOIN::change_result"); - result= res; - if (tmp_join) - tmp_join->result= res; - if (!procedure && (result->prepare(fields_list, select_lex->master_unit()) || - result->prepare2())) + if (old_result == NULL || result == old_result) { - DBUG_RETURN(TRUE); + result= new_result; + if (result->prepare(fields_list, select_lex->master_unit()) || + result->prepare2()) + DBUG_RETURN(true); /* purecov: inspected */ + DBUG_RETURN(false); } - DBUG_RETURN(FALSE); + DBUG_RETURN(result->change_result(new_result)); } @@ -23773,7 +25541,7 @@ void JOIN::save_query_plan(Join_plan_state *save_to) } memcpy((uchar*) save_to->best_positions, (uchar*) best_positions, sizeof(POSITION) * (table_count + 1)); - memset(best_positions, 0, sizeof(POSITION) * (table_count + 1)); + memset((uchar*) best_positions, 0, sizeof(POSITION) * (table_count + 1)); /* Save SJM nests */ List_iterator<TABLE_LIST> it(select_lex->sj_nests); @@ -23855,7 +25623,7 @@ void JOIN::restore_query_plan(Join_plan_state *restore_from) @retval REOPT_NEW_PLAN there is a new plan. @retval REOPT_OLD_PLAN no new improved plan was produced, use the old one. - @retval REOPT_ERROR an irrecovarable error occured during reoptimization. + @retval REOPT_ERROR an irrecovarable error occurred during reoptimization. */ JOIN::enum_reopt_result @@ -23886,7 +25654,8 @@ JOIN::reoptimize(Item *added_where, table_map join_tables, reset_query_plan(); if (!keyuse.buffer && - my_init_dynamic_array(&keyuse, sizeof(KEYUSE), 20, 64)) + my_init_dynamic_array(&keyuse, sizeof(KEYUSE), 20, 64, + MYF(MY_THREAD_SPECIFIC))) { delete_dynamic(&added_keyuse); return REOPT_ERROR; @@ -23942,11 +25711,11 @@ void JOIN::cache_const_exprs() return; if (conds) - conds->compile(&Item::cache_const_expr_analyzer, (uchar **)&analyzer_arg, + conds->compile(thd, &Item::cache_const_expr_analyzer, (uchar **)&analyzer_arg, &Item::cache_const_expr_transformer, (uchar *)&cache_flag); cache_flag= FALSE; if (having) - having->compile(&Item::cache_const_expr_analyzer, (uchar **)&analyzer_arg, + having->compile(thd, &Item::cache_const_expr_analyzer, (uchar **)&analyzer_arg, &Item::cache_const_expr_transformer, (uchar *)&cache_flag); for (JOIN_TAB *tab= first_depth_first_tab(this); tab; @@ -23955,7 +25724,7 @@ void JOIN::cache_const_exprs() if (*tab->on_expr_ref) { cache_flag= FALSE; - (*tab->on_expr_ref)->compile(&Item::cache_const_expr_analyzer, + (*tab->on_expr_ref)->compile(thd, &Item::cache_const_expr_analyzer, (uchar **)&analyzer_arg, &Item::cache_const_expr_transformer, (uchar *)&cache_flag); @@ -23963,6 +25732,114 @@ void JOIN::cache_const_exprs() } } + +/* + Get a cost of reading rows_limit rows through index keynr. + + @detail + - If there is a quick select, we try to use it. + - if there is a ref(const) access, we try to use it, too. + - quick and ref(const) use different cost formulas, so if both are possible + we should make a cost-based choice. + + @param tab JOIN_TAB with table access (is NULL for single-table + UPDATE/DELETE) + @param read_time OUT Cost of reading using quick or ref(const) access. + + + @return + true There was a possible quick or ref access, its cost is in the OUT + parameters. + false No quick or ref(const) possible (and so, the caller will attempt + to use a full index scan on this index). +*/ + +static bool get_range_limit_read_cost(const JOIN_TAB *tab, + const TABLE *table, + uint keynr, + ha_rows rows_limit, + double *read_time) +{ + bool res= false; + /* + We need to adjust the estimates if we had a quick select (or ref(const)) on + index keynr. + */ + if (table->quick_keys.is_set(keynr)) + { + /* + Start from quick select's rows and cost. These are always cheaper than + full index scan/cost. + */ + double best_rows= table->quick_rows[keynr]; + double best_cost= table->quick_costs[keynr]; + + /* + Check if ref(const) access was possible on this index. + */ + if (tab) + { + key_part_map const_parts= 0; + key_part_map map= 1; + uint kp; + /* Find how many key parts would be used by ref(const) */ + for (kp=0; kp < MAX_REF_PARTS; map=map << 1, kp++) + { + if (!(table->const_key_parts[keynr] & map)) + break; + const_parts |= map; + } + + if (kp > 0) + { + ha_rows ref_rows; + /* + Two possible cases: + 1. ref(const) uses the same #key parts as range access. + 2. ref(const) uses fewer key parts, becasue there is a + range_cond(key_part+1). + */ + if (kp == table->quick_key_parts[keynr]) + ref_rows= table->quick_rows[keynr]; + else + ref_rows= (ha_rows) table->key_info[keynr].actual_rec_per_key(kp-1); + + if (ref_rows > 0) + { + double tmp= ref_rows; + /* Reuse the cost formula from best_access_path: */ + set_if_smaller(tmp, (double) tab->join->thd->variables.max_seeks_for_key); + if (table->covering_keys.is_set(keynr)) + tmp= table->file->keyread_time(keynr, 1, (ha_rows) tmp); + else + tmp= table->file->read_time(keynr, 1, + (ha_rows) MY_MIN(tmp,tab->worst_seeks)); + if (tmp < best_cost) + { + best_cost= tmp; + best_rows= ref_rows; + } + } + } + } + + if (best_rows > rows_limit) + { + /* + LIMIT clause specifies that we will need to read fewer records than + quick select will return. Assume that quick select's cost is + proportional to the number of records we need to return (e.g. if we + only need 1/3rd of records, it will cost us 1/3rd of quick select's + read time) + */ + best_cost *= rows_limit / best_rows; + } + *read_time= best_cost; + res= true; + } + return res; +} + /** Find a cheaper access key than a given @a key @@ -23972,8 +25849,12 @@ void JOIN::cache_const_exprs() @param table Table if tab == NULL or tab->table @param usable_keys Key map to find a cheaper key in @param ref_key - * 0 <= key < MAX_KEY - key number (hint) to start the search - * -1 - no key number provided + 0 <= key < MAX_KEY - Key that is currently used for finding + row + MAX_KEY - means index_merge is used + -1 - means we're currently not using an + index to find rows. + @param select_limit LIMIT value @param [out] new_key Key number if success, otherwise undefined @param [out] new_key_direction Return -1 (reverse) or +1 if success, @@ -24019,9 +25900,9 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, int best_key= -1; bool is_best_covering= FALSE; double fanout= 1; - ha_rows table_records= table->file->stats.records; + ha_rows table_records= table->stat_records(); bool group= join && join->group && order == join->group_list; - ha_rows ref_key_quick_rows= HA_POS_ERROR; + ha_rows refkey_rows_estimate= table->quick_condition_rows; const bool has_limit= (select_limit_arg != HA_POS_ERROR); /* @@ -24047,10 +25928,6 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, else keys= usable_keys; - if (ref_key >= 0 && ref_key != MAX_KEY && - table->covering_keys.is_set(ref_key)) - ref_key_quick_rows= table->quick_rows[ref_key]; - if (join) { uint tablenr= tab - join->join_tab; @@ -24060,6 +25937,27 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, } else read_time= table->file->scan_time(); + + /* + TODO: add cost of sorting here. + */ + read_time += COST_EPS; + + /* + Calculate the selectivity of the ref_key for REF_ACCESS. For + RANGE_ACCESS we use table->quick_condition_rows. + */ + if (ref_key >= 0 && ref_key != MAX_KEY && tab->type == JT_REF) + { + if (table->quick_keys.is_set(ref_key)) + refkey_rows_estimate= table->quick_rows[ref_key]; + else + { + const KEY *ref_keyinfo= table->key_info + ref_key; + refkey_rows_estimate= ref_keyinfo->rec_per_key[tab->ref.key_parts - 1]; + } + set_if_bigger(refkey_rows_estimate, 1); + } for (nr=0; nr < table->s->keys ; nr++) { @@ -24068,7 +25966,8 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, uint used_key_parts= 0; if (keys.is_set(nr) && - (direction= test_if_order_by_key(order, table, nr, &used_key_parts))) + (direction= test_if_order_by_key(join, order, table, nr, + &used_key_parts))) { /* At this point we are sure that ref_key is a non-ordering @@ -24101,17 +26000,17 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, if (group) { /* - Used_key_parts can be larger than keyinfo->key_parts + Used_key_parts can be larger than keyinfo->user_defined_key_parts when using a secondary index clustered with a primary key (e.g. as in Innodb). See Bug #28591 for details. */ - uint used_index_parts= keyinfo->key_parts; + uint used_index_parts= keyinfo->user_defined_key_parts; uint used_pk_parts= 0; if (used_key_parts > used_index_parts) used_pk_parts= used_key_parts-used_index_parts; rec_per_key= used_key_parts ? - keyinfo->rec_per_key[used_key_parts-1] : 1; + keyinfo->actual_rec_per_key(used_key_parts-1) : 1; /* Take into account the selectivity of the used pk prefix */ if (used_pk_parts) { @@ -24121,19 +26020,19 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, of the primary key are considered unknown we assume they are equal to 1. */ - if (used_key_parts == pkinfo->key_parts || + if (used_key_parts == pkinfo->user_defined_key_parts || pkinfo->rec_per_key[0] == 0) rec_per_key= 1; if (rec_per_key > 1) { - rec_per_key*= pkinfo->rec_per_key[used_pk_parts-1]; - rec_per_key/= pkinfo->rec_per_key[0]; + rec_per_key*= pkinfo->actual_rec_per_key(used_pk_parts-1); + rec_per_key/= pkinfo->actual_rec_per_key(0); /* The value of rec_per_key for the extended key has to be adjusted accordingly if some components of the secondary key are included in the primary key. */ - for(uint i= 0; i < used_pk_parts; i++) + for(uint i= 1; i < used_pk_parts; i++) { if (pkinfo->key_part[i].field->key_start.is_set(nr)) { @@ -24141,10 +26040,9 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, We presume here that for any index rec_per_key[i] != 0 if rec_per_key[0] != 0. */ - DBUG_ASSERT(pkinfo->rec_per_key[i]); - DBUG_ASSERT(i > 0); - rec_per_key*= pkinfo->rec_per_key[i-1]; - rec_per_key/= pkinfo->rec_per_key[i]; + DBUG_ASSERT(pkinfo->actual_rec_per_key(i)); + rec_per_key*= pkinfo->actual_rec_per_key(i-1); + rec_per_key/= pkinfo->actual_rec_per_key(i); } } } @@ -24178,18 +26076,18 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, with ref_key. Thus, to select first N records we have to scan N/selectivity(ref_key) index entries. selectivity(ref_key) = #scanned_records/#table_records = - table->quick_condition_rows/table_records. + refkey_rows_estimate/table_records. In any case we can't select more than #table_records. - N/(table->quick_condition_rows/table_records) > table_records - <=> N > table->quick_condition_rows. - */ - if (select_limit > table->quick_condition_rows) + N/(refkey_rows_estimate/table_records) > table_records + <=> N > refkey_rows_estimate. + */ + if (select_limit > refkey_rows_estimate) select_limit= table_records; else select_limit= (ha_rows) (select_limit * (double) table_records / - table->quick_condition_rows); - rec_per_key= keyinfo->rec_per_key[keyinfo->key_parts-1]; + refkey_rows_estimate); + rec_per_key= keyinfo->actual_rec_per_key(keyinfo->user_defined_key_parts-1); set_if_bigger(rec_per_key, 1); /* Here we take into account the fact that rows are @@ -24203,24 +26101,37 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, index entry. */ index_scan_time= select_limit/rec_per_key * - min(rec_per_key, table->file->scan_time()); + MY_MIN(rec_per_key, table->file->scan_time()); + double range_scan_time; + if (get_range_limit_read_cost(tab, table, nr, select_limit, + &range_scan_time)) + { + if (range_scan_time < index_scan_time) + index_scan_time= range_scan_time; + } + if ((ref_key < 0 && (group || table->force_index || is_covering)) || index_scan_time < read_time) { ha_rows quick_records= table_records; + ha_rows refkey_select_limit= (ref_key >= 0 && + !is_hash_join_key_no(ref_key) && + table->covering_keys.is_set(ref_key)) ? + refkey_rows_estimate : + HA_POS_ERROR; if ((is_best_covering && !is_covering) || - (is_covering && ref_key_quick_rows < select_limit)) + (is_covering && refkey_select_limit < select_limit)) continue; if (table->quick_keys.is_set(nr)) quick_records= table->quick_rows[nr]; if (best_key < 0 || - (select_limit <= min(quick_records,best_records) ? - keyinfo->key_parts < best_key_parts : + (select_limit <= MY_MIN(quick_records,best_records) ? + keyinfo->user_defined_key_parts < best_key_parts : quick_records < best_records) || (!is_best_covering && is_covering)) { best_key= nr; - best_key_parts= keyinfo->key_parts; + best_key_parts= keyinfo->user_defined_key_parts; if (saved_best_key_parts) *saved_best_key_parts= used_key_parts; best_records= quick_records; @@ -24253,6 +26164,8 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, @param table Table to find a key @param select Pointer to access/update select->quick (if any) @param limit LIMIT clause parameter + @param [out] scanned_limit How many records we expect to scan + Valid if *need_sort=FALSE. @param [out] need_sort TRUE if filesort needed @param [out] reverse TRUE if the key is reversed again given ORDER (undefined if key == MAX_KEY) @@ -24270,7 +26183,8 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, */ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, - ha_rows limit, bool *need_sort, bool *reverse) + ha_rows limit, ha_rows *scanned_limit, + bool *need_sort, bool *reverse) { if (!order) { @@ -24296,10 +26210,11 @@ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, } uint used_key_parts; - switch (test_if_order_by_key(order, table, select->quick->index, + switch (test_if_order_by_key(NULL, order, table, select->quick->index, &used_key_parts)) { case 1: // desired order - *need_sort= FALSE; + *need_sort= FALSE; + *scanned_limit= MY_MIN(limit, select->quick->records); return select->quick->index; case 0: // unacceptable order *need_sort= TRUE; @@ -24312,6 +26227,7 @@ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, { select->set_quick(reverse_quick); *need_sort= FALSE; + *scanned_limit= MY_MIN(limit, select->quick->records); return select->quick->index; } else @@ -24330,7 +26246,7 @@ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, Update quick_condition_rows since single table UPDATE/DELETE procedures don't call make_join_statistics() and leave this variable uninitialized. */ - table->quick_condition_rows= table->file->stats.records; + table->quick_condition_rows= table->stat_records(); int key, direction; if (test_if_cheaper_ordering(NULL, order, table, @@ -24340,6 +26256,7 @@ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, !is_key_used(table, key, table->write_set)) { *need_sort= FALSE; + *scanned_limit= limit; *reverse= (direction < 0); return key; } @@ -24349,6 +26266,81 @@ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, } +/* + Count how many times the specified conditions are true for first rows_to_read + rows of the table. + + @param thd Thread handle + @param rows_to_read How many rows to sample + @param table Table to use + @conds conds INOUT List of conditions and counters for them + + @return Number of we've checked. It can be equal or less than rows_to_read. + 0 is returned for error or when the table had no rows. +*/ + +ulong check_selectivity(THD *thd, + ulong rows_to_read, + TABLE *table, + List<COND_STATISTIC> *conds) +{ + ulong count= 0; + COND_STATISTIC *cond; + List_iterator_fast<COND_STATISTIC> it(*conds); + handler *file= table->file; + uchar *record= table->record[0]; + int error= 0; + DBUG_ENTER("check_selectivity"); + + DBUG_ASSERT(rows_to_read > 0); + while ((cond= it++)) + { + DBUG_ASSERT(cond->cond); + DBUG_ASSERT(cond->cond->used_tables() == table->map); + cond->positive= 0; + } + it.rewind(); + + if (file->ha_rnd_init_with_error(1)) + DBUG_RETURN(0); + do + { + error= file->ha_rnd_next(record); + + if (thd->killed) + { + thd->send_kill_message(); + count= 0; + goto err; + } + if (error) + { + if (error == HA_ERR_RECORD_DELETED) + continue; + if (error == HA_ERR_END_OF_FILE) + break; + goto err; + } + + count++; + while ((cond= it++)) + { + if (cond->cond->val_bool()) + cond->positive++; + } + it.rewind(); + + } while (count < rows_to_read); + + file->ha_rnd_end(); + DBUG_RETURN(count); + +err: + DBUG_PRINT("error", ("error %d", error)); + file->ha_rnd_end(); + DBUG_RETURN(0); +} + /** @} (end of group Query_Optimizer) */ diff --git a/sql/sql_select.h b/sql/sql_select.h index e208377e275..266fe7a7066 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -2,7 +2,7 @@ #define SQL_SELECT_INCLUDED /* Copyright (c) 2000, 2013, Oracle and/or its affiliates. - Copyright (c) 2008, 2013, Monty Program Ab. + Copyright (c) 2008, 2015, 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 @@ -29,33 +29,10 @@ #endif #include "procedure.h" -#include <myisam.h> #include "sql_array.h" /* Array */ #include "records.h" /* READ_RECORD */ #include "opt_range.h" /* SQL_SELECT, QUICK_SELECT_I */ - -#if defined(WITH_ARIA_STORAGE_ENGINE) -#include <maria.h> -#endif -#if defined(USE_ARIA_FOR_TMP_TABLES) -#define TMP_ENGINE_HTON maria_hton -inline uint tmp_table_max_key_length() { - return maria_max_key_length(); -} - -inline uint tmp_table_max_key_parts() { - return maria_max_key_segments(); -} -#else -#define TMP_ENGINE_HTON myisam_hton -inline uint tmp_table_max_key_length() { - return MI_MAX_KEY_LENGTH; -} -inline uint tmp_table_max_key_parts() { - return MI_MAX_KEY_SEG; -} -#endif /* Values in optimize */ #define KEY_OPTIMIZE_EXISTS 1 #define KEY_OPTIMIZE_REF_OR_NULL 2 @@ -114,6 +91,13 @@ typedef struct st_table_ref uchar *key_buff; ///< value to look for with key uchar *key_buff2; ///< key_buff+key_length store_key **key_copy; // + + /* + Bitmap of key parts which refer to constants. key_copy only has copiers for + non-const key parts. + */ + key_part_map const_ref_part_map; + Item **items; ///< val()'s for each keypart /* Array of pointers to trigger variables. Some/all of the pointers may be @@ -204,12 +188,17 @@ int rr_sequential(READ_RECORD *info); int rr_sequential_and_unpack(READ_RECORD *info); +#include "sql_explain.h" + +/************************************************************************************** + * New EXPLAIN structures END + *************************************************************************************/ + class JOIN_CACHE; class SJ_TMP_TABLE; class JOIN_TAB_RANGE; typedef struct st_join_table { - st_join_table() {} /* Remove gcc warning */ TABLE *table; KEYUSE *keyuse; /**< pointer to first used key */ KEY *hj_key; /**< descriptor of the used best hash join key @@ -258,7 +247,11 @@ typedef struct st_join_table { JOIN_TAB_RANGE *bush_children; /* Special content for EXPLAIN 'Extra' column or NULL if none */ - const char *info; + enum explain_extra_tag info; + + Table_access_tracker *tracker; + + Table_access_tracker *jbuf_tracker; /* Bitmap of TAB_INFO_* bits that encodes special line for EXPLAIN 'Extra' column, or 0 if there is no info. @@ -295,8 +288,11 @@ typedef struct st_join_table { */ double read_time; - /* psergey-todo: make the below have type double, like POSITION::records_read? */ - ha_rows records_read; + /* Copy of POSITION::records_read, set by get_best_combination() */ + double records_read; + + /* The selectivity of the conditions that can be pushed to the table */ + double cond_selectivity; /* Startup cost for execution */ double startup_cost; @@ -364,7 +360,12 @@ typedef struct st_join_table { SJ_TMP_TABLE *check_weed_out_table; /* for EXPLAIN only: */ SJ_TMP_TABLE *first_weedout_table; - + + /** + reference to saved plan and execution statistics + */ + Explain_table_access *explain_plan; + /* If set, means we should stop join enumeration after we've got the first match and return to the specified join tab. May point to @@ -529,7 +530,22 @@ typedef struct st_join_table { ha_rows get_examined_rows(); bool preread_init(); - bool is_sjm_nest() { return test(bush_children); } + bool is_sjm_nest() { return MY_TEST(bush_children); } + + /* + If this join_tab reads a non-merged semi-join (also called jtbm), return + the select's number. Otherwise, return 0. + */ + int get_non_merged_semijoin_select() const + { + Item_in_subselect *subq; + if (table->pos_in_table_list && + (subq= table->pos_in_table_list->jtbm_subselect)) + { + return subq->unit->first_select()->select_number; + } + return 0; /* Not a merged semi-join */ + } bool access_from_tables_is_allowed(table_map used_tables, table_map sjm_lookup_tables) @@ -542,6 +558,12 @@ typedef struct st_join_table { bool keyuse_is_valid_for_access_in_chosen_plan(JOIN *join, KEYUSE *keyuse); + void remove_redundant_bnl_scan_conds(); + + void save_explain_data(Explain_table_access *eta, table_map prefix_tables, + bool distinct, struct st_join_table *first_top_tab); + + void update_explain_data(uint idx); } JOIN_TAB; @@ -723,8 +745,7 @@ public: struct st_position *pos, struct st_position *loose_scan_pos); friend bool get_best_combination(JOIN *join); - friend int setup_semijoin_dups_elimination(JOIN *join, ulonglong options, - uint no_jbuf_after); + friend int setup_semijoin_loosescan(JOIN *join); friend void fix_semijoin_strategies_for_picked_join_order(JOIN *join); }; @@ -746,7 +767,7 @@ public: void set_empty() { sjm_scan_need_tables= 0; - LINT_INIT(sjm_scan_last_inner); + LINT_INIT_STRUCT(sjm_scan_last_inner); is_used= FALSE; } void set_from_prev(struct st_position *prev); @@ -769,7 +790,7 @@ public: Information about a position of table within a join order. Used in join optimization. */ -typedef struct st_position :public Sql_alloc +typedef struct st_position { /* The table that's put into join order */ JOIN_TAB *table; @@ -781,6 +802,9 @@ typedef struct st_position :public Sql_alloc */ double records_read; + /* The selectivity of the pushed down conditions */ + double cond_selectivity; + /* Cost accessing the table in course of the entire complete join execution, i.e. cost of one access method use (e.g. 'range' or 'ref' scan ) times @@ -789,7 +813,7 @@ typedef struct st_position :public Sql_alloc double read_time; /* Cumulative cost and record count for the join prefix */ - COST_VECT prefix_cost; + Cost_estimate prefix_cost; double prefix_record_count; /* @@ -860,6 +884,7 @@ public: JOIN_TAB *end; }; +class Pushdown_query; class JOIN :public Sql_alloc { @@ -889,6 +914,7 @@ protected: { keyuse.elements= 0; keyuse.buffer= NULL; + keyuse.malloc_flags= 0; best_positions= 0; /* To detect errors */ error= my_multi_malloc(MYF(MY_WME), &best_positions, @@ -913,7 +939,7 @@ protected: enum enum_reopt_result { REOPT_NEW_PLAN, /* there is a new reoptimized plan */ REOPT_OLD_PLAN, /* no new improved plan can be found, use the old one */ - REOPT_ERROR, /* an irrecovarable error occured during reoptimization */ + REOPT_ERROR, /* an irrecovarable error occurred during reoptimization */ REOPT_NONE /* not yet reoptimized */ }; @@ -951,7 +977,7 @@ public: */ JOIN_TAB *table_access_tabs; uint top_table_access_tabs_count; - + JOIN_TAB **map2table; ///< mapping between table indexes and JOIN_TABs JOIN_TAB *join_tab_save; ///< saved join_tab for subquery reexecution @@ -986,7 +1012,19 @@ public: */ uint top_join_tab_count; uint send_group_parts; - bool group; /**< If query contains GROUP BY clause */ + /* + This counts how many times do_select() was invoked for this JOIN. + It's used to restrict Pushdown_query::execute() only to the first + do_select() invocation. + */ + uint do_select_call_count; + /* + True if the query has GROUP BY. + (that is, if group_by != NULL. when DISTINCT is converted into GROUP BY, it + will set this, too. It is not clear why we need a separate var from + group_list) + */ + bool group; bool need_distinct; /** @@ -1025,8 +1063,24 @@ public: table_map outer_join; /* Bitmap of tables used in the select list items */ table_map select_list_used_tables; - ha_rows send_records, found_records, examined_rows, - row_limit, select_limit, duplicate_rows; + ha_rows send_records,found_records,join_examined_rows; + + /* + LIMIT for the JOIN operation. When not using aggregation or DISITNCT, this + is the same as select's LIMIT clause specifies. + Note that this doesn't take sql_calc_found_rows into account. + */ + ha_rows row_limit; + + /* + How many output rows should be produced after GROUP BY. + (if sql_calc_found_rows is used, LIMIT is ignored) + */ + ha_rows select_limit; + /* + Number of duplicate rows found in UNION. + */ + ha_rows duplicate_rows; /** Used to fetch no more than given amount of rows per one fetch operation of server side cursor. @@ -1035,11 +1089,17 @@ public: - fetch_limit= HA_POS_ERROR if there is no cursor. - when we open a cursor, we set fetch_limit to 0, - on each fetch iteration we add num_rows to fetch to fetch_limit + NOTE: currently always HA_POS_ERROR. */ ha_rows fetch_limit; + /* Finally picked QEP. This is result of join optimization */ POSITION *best_positions; + Pushdown_query *pushdown_query; + JOIN_TAB *original_join_tab; + uint original_table_count; + /******* Join optimization state members start *******/ /* pointer - we're doing optimization for a semi-join materialization nest. @@ -1084,7 +1144,7 @@ public: reexecutions. This value is equal to the multiplication of all join->positions[i].records_read of a JOIN. */ - double record_count; + double join_record_count; List<Item> *fields; List<Cached_item> group_fields, group_fields_cache; TABLE *tmp_table; @@ -1129,6 +1189,12 @@ public: restore_no_rows_in_result() in ::reinit() */ bool no_rows_in_result_called; + + /** + This is set if SQL_CALC_ROWS was calculated by filesort() + and should be taken from the appropriate JOIN_TAB + */ + bool filesort_found_rows; /** Copy of this JOIN to be used with temporary tables. @@ -1179,7 +1245,8 @@ public: /** Is set if we have a GROUP BY and we have ORDER BY on a constant. */ bool skip_sort_order; - bool need_tmp, hidden_group_fields; + bool need_tmp; + bool hidden_group_fields; /* TRUE if there was full cleunap of the JOIN */ bool cleaned; DYNAMIC_ARRAY keyuse; @@ -1229,9 +1296,18 @@ public: const char *zero_result_cause; ///< not 0 if exec must return zero result bool union_part; ///< this subselect is part of union - bool optimized; ///< flag to avoid double optimization in EXPLAIN + + enum join_optimization_state { NOT_OPTIMIZED=0, + OPTIMIZATION_IN_PROGRESS=1, + OPTIMIZATION_DONE=2}; + // state of JOIN optimization + enum join_optimization_state optimization_state; bool initialized; ///< flag to avoid double init_execution calls + Explain_select *explain; + + enum { QEP_NOT_PRESENT_YET, QEP_AVAILABLE, QEP_DELETED} have_query_plan; + /* Additional WHERE and HAVING predicates to be considered for IN=>EXISTS subquery transformation of a JOIN object. @@ -1274,6 +1350,7 @@ public: table_count= 0; top_join_tab_count= 0; const_tables= 0; + const_table_map= 0; eliminated_tables= 0; join_list= 0; implicit_grouping= FALSE; @@ -1283,7 +1360,7 @@ public: duplicate_rows= send_records= 0; found_records= 0; fetch_limit= HA_POS_ERROR; - examined_rows= 0; + join_examined_rows= 0; exec_tmp_table1= 0; exec_tmp_table2= 0; sortorder= 0; @@ -1298,7 +1375,7 @@ public: lock= thd_arg->lock; select_lex= 0; //for safety tmp_join= 0; - select_distinct= test(select_options & SELECT_DISTINCT); + select_distinct= MY_TEST(select_options & SELECT_DISTINCT); no_order= 0; simple_order= 0; simple_group= 0; @@ -1312,7 +1389,8 @@ public: ref_pointer_array= items0= items1= items2= items3= 0; ref_pointer_array_size= 0; zero_result_cause= 0; - optimized= 0; + optimization_state= JOIN::NOT_OPTIMIZED; + have_query_plan= QEP_NOT_PRESENT_YET; initialized= 0; cleaned= 0; cond_equal= 0; @@ -1321,6 +1399,11 @@ public: group_optimized_away= 0; no_rows_in_result_called= 0; positions= best_positions= 0; + pushdown_query= 0; + original_join_tab= 0; + do_select_call_count= 0; + + explain= NULL; all_fields= fields_arg; if (&fields_list != &fields_arg) /* Avoid valgrind-warning */ @@ -1339,6 +1422,7 @@ public: emb_sjm_nest= NULL; sjm_lookup_tables= 0; sjm_scan_tables= 0; + /* The following is needed because JOIN::cleanup(true) may be called for joins for which JOIN::optimize was aborted with an error before a proper @@ -1353,9 +1437,11 @@ public: SELECT_LEX_UNIT *unit); bool prepare_stage2(); int optimize(); + int optimize_inner(); int reinit(); int init_execution(); void exec(); + void exec_inner(); int destroy(); void restore_tmp(); bool alloc_func_list(); @@ -1401,7 +1487,7 @@ public: having_value != Item::COND_FALSE); } bool empty_result() { return (zero_result_cause && !implicit_grouping); } - bool change_result(select_result *result); + bool change_result(select_result *new_result, select_result *old_result); bool is_top_level_join() const { return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 || @@ -1431,7 +1517,7 @@ public: void set_allowed_join_cache_types(); bool is_allowed_hash_join_access() { - return test(allowed_join_cache_types & JOIN_CACHE_HASHED_BIT) && + return MY_TEST(allowed_join_cache_types & JOIN_CACHE_HASHED_BIT) && max_allowed_join_cache_level > JOIN_CACHE_HASHED_BIT; } /* @@ -1450,7 +1536,7 @@ public: return ((const_tables != table_count && ((select_distinct || !simple_order || !simple_group) || (group_list && order) || - test(select_options & OPTION_BUFFER_RESULT))) || + MY_TEST(select_options & OPTION_BUFFER_RESULT))) || (rollup.state != ROLLUP::STATE_NONE && select_distinct)); } bool choose_subquery_plan(table_map join_tables); @@ -1469,6 +1555,13 @@ public: { return (unit->item && unit->item->is_in_predicate()); } + void save_explain_data(Explain_query *output, bool can_overwrite, + bool need_tmp_table, bool need_order, bool distinct); + int save_explain_data_intern(Explain_query *output, bool need_tmp_table, + bool need_order, bool distinct, + const char *message); + JOIN_TAB *first_breadth_first_optimization_tab() { return table_access_tabs; } + JOIN_TAB *first_breadth_first_execution_tab() { return join_tab; } private: /** TRUE if the query contains an aggregate function but has no GROUP @@ -1482,7 +1575,7 @@ private: enum enum_with_bush_roots { WITH_BUSH_ROOTS, WITHOUT_BUSH_ROOTS}; enum enum_with_const_tables { WITH_CONST_TABLES, WITHOUT_CONST_TABLES}; -JOIN_TAB *first_linear_tab(JOIN *join, +JOIN_TAB *first_linear_tab(JOIN *join, enum enum_with_bush_roots include_bush_roots, enum enum_with_const_tables const_tbls); JOIN_TAB *next_linear_tab(JOIN* join, JOIN_TAB* tab, @@ -1509,8 +1602,8 @@ bool copy_funcs(Item **func_ptr, const THD *thd); uint find_shortest_key(TABLE *table, const key_map *usable_keys); Field* create_tmp_field_from_field(THD *thd, Field* org_field, const char *name, TABLE *table, - Item_field *item, uint convert_blob_length); - + Item_field *item); + bool is_indexed_agg_distinct(JOIN *join, List<Item_field> *out_args); /* functions from opt_sum.cc */ @@ -1532,21 +1625,8 @@ public: store_key(THD *thd, Field *field_arg, uchar *ptr, uchar *null, uint length) :null_key(0), null_ptr(null), err(0) { - if (field_arg->type() == MYSQL_TYPE_BLOB - || field_arg->type() == MYSQL_TYPE_GEOMETRY) - { - /* - Key segments are always packed with a 2 byte length prefix. - See mi_rkey for details. - */ - to_field= new Field_varstring(ptr, length, 2, null, 1, - Field::NONE, field_arg->field_name, - field_arg->table->s, field_arg->charset()); - to_field->init(field_arg->table); - } - else - to_field=field_arg->new_key_field(thd->mem_root, field_arg->table, - ptr, null, 1); + to_field=field_arg->new_key_field(thd->mem_root, field_arg->table, + ptr, length, null, 1); } store_key(store_key &arg) :Sql_alloc(), null_key(arg.null_key), to_field(arg.to_field), @@ -1752,9 +1832,10 @@ bool cp_buffer_from_ref(THD *thd, TABLE *table, TABLE_REF *ref); bool error_if_full_join(JOIN *join); int report_error(TABLE *table, int error); int safe_index_read(JOIN_TAB *tab); -COND *remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value); int get_quick_record(SQL_SELECT *select); -SORT_FIELD * make_unireg_sortorder(ORDER *order, uint *length, +SORT_FIELD *make_unireg_sortorder(THD *thd, JOIN *join, + table_map first_table_map, + ORDER *order, uint *length, SORT_FIELD *sortorder); int setup_order(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, List<Item> &fields, List <Item> &all_fields, ORDER *order); @@ -1782,12 +1863,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, Field **def_field, bool group, bool modify_item, bool table_cant_handle_bit_fields, - bool make_copy_field, - uint convert_blob_length); -bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, - ENGINE_COLUMNDEF *start_recinfo, - ENGINE_COLUMNDEF **recinfo, - ulonglong options, my_bool big_tables); + bool make_copy_field); /* General routine to change field->ptr of a NULL-terminated array of Field @@ -1803,9 +1879,9 @@ int test_if_item_cache_changed(List<Cached_item> &list); int join_init_read_record(JOIN_TAB *tab); int join_read_record_no_init(JOIN_TAB *tab); void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key); -inline Item * and_items(Item* cond, Item *item) +inline Item * and_items(THD *thd, Item* cond, Item *item) { - return (cond? (new Item_cond_and(cond, item)) : item); + return (cond ? (new (thd->mem_root) Item_cond_and(thd, cond, item)) : item); } bool choose_plan(JOIN *join, table_map join_tables); void optimize_wo_join_buffering(JOIN *join, uint first_tab, uint last_tab, @@ -1822,8 +1898,14 @@ inline bool optimizer_flag(THD *thd, uint flag) return (thd->variables.optimizer_switch & flag); } +/* +int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, + SELECT_LEX *select_lex, uint8 select_options); +*/ + uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, - ha_rows limit, bool *need_sort, bool *reverse); + ha_rows limit, ha_rows *scanned_limit, + bool *need_sort, bool *reverse); ORDER *simple_remove_const(ORDER *order, COND *where); bool const_expression_in_where(COND *cond, Item *comp_item, Field *comp_field= NULL, @@ -1837,6 +1919,19 @@ void eliminate_tables(JOIN *join); /* Index Condition Pushdown entry point function */ void push_index_cond(JOIN_TAB *tab, uint keyno); +#define OPT_LINK_EQUAL_FIELDS 1 + +/* EXPLAIN-related utility functions */ +int print_explain_message_line(select_result_sink *result, + uint8 options, bool is_analyze, + uint select_number, + const char *select_type, + ha_rows *rows, + const char *message); +void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res); +int append_possible_keys(MEM_ROOT *alloc, String_list &list, TABLE *table, + key_map possible_keys); + /**************************************************************************** Temporary table support for SQL Runtime ***************************************************************************/ @@ -1853,17 +1948,48 @@ TABLE *create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields, bool keep_row_order= FALSE); void free_tmp_table(THD *thd, TABLE *entry); bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table, - ENGINE_COLUMNDEF *start_recinfo, - ENGINE_COLUMNDEF **recinfo, + TMP_ENGINE_COLUMNDEF *start_recinfo, + TMP_ENGINE_COLUMNDEF **recinfo, int error, bool ignore_last_dupp_key_error, bool *is_duplicate); bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, - ENGINE_COLUMNDEF *start_recinfo, - ENGINE_COLUMNDEF **recinfo, + TMP_ENGINE_COLUMNDEF *start_recinfo, + TMP_ENGINE_COLUMNDEF **recinfo, ulonglong options); bool open_tmp_table(TABLE *table); void setup_tmp_table_column_bitmaps(TABLE *table, uchar *bitmaps); double prev_record_reads(POSITION *positions, uint idx, table_map found_ref); void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist); +struct st_cond_statistic +{ + Item *cond; + Field *field_arg; + ulong positive; +}; +typedef struct st_cond_statistic COND_STATISTIC; + +ulong check_selectivity(THD *thd, + ulong rows_to_read, + TABLE *table, + List<COND_STATISTIC> *conds); + +class Pushdown_query: public Sql_alloc +{ +public: + SELECT_LEX *select_lex; + bool store_data_in_temp_table; + group_by_handler *handler; + Item *having; + + Pushdown_query(SELECT_LEX *select_lex_arg, group_by_handler *handler_arg) + : select_lex(select_lex_arg), store_data_in_temp_table(0), + handler(handler_arg), having(0) {} + + ~Pushdown_query() { delete handler; } + + /* Function that calls the above scan functions */ + int execute(JOIN *join); +}; + #endif /* SQL_SELECT_INCLUDED */ diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index dad7ab152ed..0138c3e5a3b 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -33,12 +33,12 @@ currently running transactions etc will not be disrupted. */ +#include <my_global.h> #include "sql_priv.h" #include "sql_servers.h" #include "unireg.h" #include "sql_base.h" // close_mysql_tables #include "records.h" // init_read_record, end_read_record -#include "hash_filo.h" #include <m_ctype.h> #include <stdarg.h> #include "sp_head.h" @@ -64,9 +64,7 @@ static int insert_server_record_into_cache(FOREIGN_SERVER *server); static FOREIGN_SERVER * prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options); /* drop functions */ -static int delete_server_record(TABLE *table, - char *server_name, - size_t server_name_length); +static int delete_server_record(TABLE *table, LEX_STRING *name); static int delete_server_record_in_cache(LEX_SERVER_OPTIONS *server_options); /* update functions */ @@ -81,8 +79,6 @@ static int update_server_record_in_cache(FOREIGN_SERVER *existing, /* utility functions */ static void merge_server_struct(FOREIGN_SERVER *from, FOREIGN_SERVER *to); - - static uchar *servers_cache_get_key(FOREIGN_SERVER *server, size_t *length, my_bool not_used __attribute__((unused))) { @@ -158,7 +154,7 @@ bool servers_init(bool dont_read_servers_table) } /* Initialize the mem root for data */ - init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0); + init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC)); if (dont_read_servers_table) goto end; @@ -177,8 +173,6 @@ bool servers_init(bool dont_read_servers_table) */ return_val= servers_reload(thd); delete thd; - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); end: DBUG_RETURN(return_val); @@ -209,7 +203,7 @@ static bool servers_load(THD *thd, TABLE_LIST *tables) my_hash_reset(&servers_cache); free_root(&mem, MYF(0)); - init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0); + init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0)); if (init_read_record(&read_record_info,thd,table=tables[0].table,NULL,1,0, FALSE)) @@ -265,9 +259,9 @@ bool servers_reload(THD *thd) Execution might have been interrupted; only print the error message if an error condition has been raised. */ - if (thd->stmt_da->is_error()) + if (thd->get_stmt_da()->is_error()) sql_print_error("Can't open and lock privilege tables: %s", - thd->stmt_da->message()); + thd->get_stmt_da()->message()); return_val= FALSE; goto end; } @@ -587,8 +581,8 @@ int insert_server_record(TABLE *table, FOREIGN_SERVER *server) This function takes as its arguments a THD object pointer and a pointer to a LEX_SERVER_OPTIONS struct from the parser. The member 'server_name' of this LEX_SERVER_OPTIONS struct contains the value of the server to be - deleted. The mysql.servers table is opened via open_ltable, a table object - returned, the servers cache mutex locked, then delete_server_record is + deleted. The mysql.servers table is opened via open_ltable, + a table object returned, then delete_server_record is called with this table object and LEX_SERVER_OPTIONS server_name and server_name_length passed, containing the name of the server to be dropped/deleted, then delete_server_record_in_cache is called to delete @@ -599,22 +593,18 @@ int insert_server_record(TABLE *table, FOREIGN_SERVER *server) > 0 - error code */ -int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options) +static int drop_server_internal(THD *thd, LEX_SERVER_OPTIONS *server_options) { int error; TABLE_LIST tables; TABLE *table; - LEX_STRING name= { server_options->server_name, - server_options->server_name_length }; - DBUG_ENTER("drop_server"); + DBUG_ENTER("drop_server_internal"); DBUG_PRINT("info", ("server name server->server_name %s", - server_options->server_name)); + server_options->server_name.str)); tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE); - mysql_rwlock_wrlock(&THR_LOCK_servers); - /* hit the memory hit first */ if ((error= delete_server_record_in_cache(server_options))) goto end; @@ -625,23 +615,34 @@ int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options) goto end; } - error= delete_server_record(table, name.str, name.length); + error= delete_server_record(table, &server_options->server_name); /* close the servers table before we call closed_cached_connection_tables */ close_mysql_tables(thd); - if (close_cached_connection_tables(thd, &name)) + if (close_cached_connection_tables(thd, &server_options->server_name)) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Server connection in use"); } end: - mysql_rwlock_unlock(&THR_LOCK_servers); DBUG_RETURN(error); } +/** + Drop a server with servers cache mutex lock. +*/ +int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options) +{ + mysql_rwlock_wrlock(&THR_LOCK_servers); + int rc= drop_server_internal(thd, server_options); + mysql_rwlock_unlock(&THR_LOCK_servers); + return rc; +} + + /* SYNOPSIS @@ -667,19 +668,19 @@ delete_server_record_in_cache(LEX_SERVER_OPTIONS *server_options) FOREIGN_SERVER *server; DBUG_ENTER("delete_server_record_in_cache"); - DBUG_PRINT("info",("trying to obtain server name %s length %d", - server_options->server_name, - server_options->server_name_length)); + DBUG_PRINT("info",("trying to obtain server name %s length %zu", + server_options->server_name.str, + server_options->server_name.length)); if (!(server= (FOREIGN_SERVER *) my_hash_search(&servers_cache, - (uchar*) server_options->server_name, - server_options->server_name_length))) + (uchar*) server_options->server_name.str, + server_options->server_name.length))) { - DBUG_PRINT("info", ("server_name %s length %d not found!", - server_options->server_name, - server_options->server_name_length)); + DBUG_PRINT("info", ("server_name %s length %zu not found!", + server_options->server_name.str, + server_options->server_name.length)); goto end; } /* @@ -938,8 +939,7 @@ end: */ static int -delete_server_record(TABLE *table, - char *server_name, size_t server_name_length) +delete_server_record(TABLE *table, LEX_STRING *name) { int error; DBUG_ENTER("delete_server_record"); @@ -947,7 +947,7 @@ delete_server_record(TABLE *table, table->use_all_columns(); /* set the field that's the PK to the value we're looking for */ - table->field[0]->store(server_name, server_name_length, system_charset_info); + table->field[0]->store(name->str, name->length, system_charset_info); if ((error= table->file->ha_index_read_idx_map(table->record[0], 0, (uchar *)table->field[0]->ptr, @@ -990,15 +990,31 @@ int create_server(THD *thd, LEX_SERVER_OPTIONS *server_options) DBUG_ENTER("create_server"); DBUG_PRINT("info", ("server_options->server_name %s", - server_options->server_name)); + server_options->server_name.str)); mysql_rwlock_wrlock(&THR_LOCK_servers); /* hit the memory first */ - if (my_hash_search(&servers_cache, (uchar*) server_options->server_name, - server_options->server_name_length)) - goto end; - + if (my_hash_search(&servers_cache, (uchar*) server_options->server_name.str, + server_options->server_name.length)) + { + if (thd->lex->create_info.or_replace()) + { + if ((error= drop_server_internal(thd, server_options))) + goto end; + } + else if (thd->lex->create_info.if_not_exists()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_FOREIGN_SERVER_EXISTS, + ER_THD(thd, ER_FOREIGN_SERVER_EXISTS), + server_options->server_name.str); + error= 0; + goto end; + } + else + goto end; + } if (!(server= prepare_server_struct_for_insert(server_options))) { @@ -1014,6 +1030,16 @@ int create_server(THD *thd, LEX_SERVER_OPTIONS *server_options) end: mysql_rwlock_unlock(&THR_LOCK_servers); + + if (error) + { + DBUG_PRINT("info", ("problem creating server <%s>", + server_options->server_name.str)); + my_error(error, MYF(0), server_options->server_name.str); + } + else + my_ok(thd); + DBUG_RETURN(error); } @@ -1035,33 +1061,28 @@ end: int alter_server(THD *thd, LEX_SERVER_OPTIONS *server_options) { int error= ER_FOREIGN_SERVER_DOESNT_EXIST; - FOREIGN_SERVER *altered, *existing; - LEX_STRING name= { server_options->server_name, - server_options->server_name_length }; + FOREIGN_SERVER altered, *existing; DBUG_ENTER("alter_server"); DBUG_PRINT("info", ("server_options->server_name %s", - server_options->server_name)); + server_options->server_name.str)); mysql_rwlock_wrlock(&THR_LOCK_servers); if (!(existing= (FOREIGN_SERVER *) my_hash_search(&servers_cache, - (uchar*) name.str, - name.length))) + (uchar*) server_options->server_name.str, + server_options->server_name.length))) goto end; - altered= (FOREIGN_SERVER *)alloc_root(&mem, - sizeof(FOREIGN_SERVER)); + prepare_server_struct_for_update(server_options, existing, &altered); - prepare_server_struct_for_update(server_options, existing, altered); - - error= update_server(thd, existing, altered); + error= update_server(thd, existing, &altered); /* close the servers table before we call closed_cached_connection_tables */ close_mysql_tables(thd); - if (close_cached_connection_tables(thd, &name)) + if (close_cached_connection_tables(thd, &server_options->server_name)) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Server connection in use"); } @@ -1090,49 +1111,49 @@ end: static FOREIGN_SERVER * prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options) { - char *unset_ptr= (char*)""; FOREIGN_SERVER *server; + ulong default_port= 0; DBUG_ENTER("prepare_server_struct"); if (!(server= (FOREIGN_SERVER *)alloc_root(&mem, sizeof(FOREIGN_SERVER)))) DBUG_RETURN(NULL); /* purecov: inspected */ - /* these two MUST be set */ - if (!(server->server_name= strdup_root(&mem, server_options->server_name))) - DBUG_RETURN(NULL); /* purecov: inspected */ - server->server_name_length= server_options->server_name_length; +#define SET_SERVER_OR_RETURN(X, DEFAULT) \ + do { \ + if (!(server->X= server_options->X.str ? \ + strmake_root(&mem, server_options->X.str, \ + server_options->X.length) : "")) \ + DBUG_RETURN(NULL); \ + } while(0) - if (!(server->host= server_options->host ? - strdup_root(&mem, server_options->host) : unset_ptr)) - DBUG_RETURN(NULL); /* purecov: inspected */ + /* name and scheme are always set (the parser guarantees it) */ + SET_SERVER_OR_RETURN(server_name, NULL); + SET_SERVER_OR_RETURN(scheme, NULL); - if (!(server->db= server_options->db ? - strdup_root(&mem, server_options->db) : unset_ptr)) - DBUG_RETURN(NULL); /* purecov: inspected */ + /* scheme-specific checks */ + if (!strcasecmp(server->scheme, "mysql")) + { + default_port= MYSQL_PORT; + if (!server_options->host.str && !server_options->socket.str) + { + my_error(ER_CANT_CREATE_FEDERATED_TABLE, MYF(0), + "either HOST or SOCKET must be set"); + DBUG_RETURN(NULL); + } + } - if (!(server->username= server_options->username ? - strdup_root(&mem, server_options->username) : unset_ptr)) - DBUG_RETURN(NULL); /* purecov: inspected */ + SET_SERVER_OR_RETURN(host, ""); + SET_SERVER_OR_RETURN(db, ""); + SET_SERVER_OR_RETURN(username, ""); + SET_SERVER_OR_RETURN(password, ""); + SET_SERVER_OR_RETURN(socket, ""); + SET_SERVER_OR_RETURN(owner, ""); - if (!(server->password= server_options->password ? - strdup_root(&mem, server_options->password) : unset_ptr)) - DBUG_RETURN(NULL); /* purecov: inspected */ + server->server_name_length= server_options->server_name.length; - /* set to 0 if not specified */ + /* set to default_port if not specified */ server->port= server_options->port > -1 ? - server_options->port : 0; - - if (!(server->socket= server_options->socket ? - strdup_root(&mem, server_options->socket) : unset_ptr)) - DBUG_RETURN(NULL); /* purecov: inspected */ - - if (!(server->scheme= server_options->scheme ? - strdup_root(&mem, server_options->scheme) : unset_ptr)) - DBUG_RETURN(NULL); /* purecov: inspected */ - - if (!(server->owner= server_options->owner ? - strdup_root(&mem, server_options->owner) : unset_ptr)) - DBUG_RETURN(NULL); /* purecov: inspected */ + server_options->port : default_port; DBUG_RETURN(server); } @@ -1157,8 +1178,8 @@ prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options, { DBUG_ENTER("prepare_server_struct_for_update"); - altered->server_name= strdup_root(&mem, server_options->server_name); - altered->server_name_length= server_options->server_name_length; + altered->server_name= existing->server_name; + altered->server_name_length= existing->server_name_length; DBUG_PRINT("info", ("existing name %s altered name %s", existing->server_name, altered->server_name)); @@ -1166,23 +1187,21 @@ prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options, The logic here is this: is this value set AND is it different than the existing value? */ - altered->host= - (server_options->host && (strcmp(server_options->host, existing->host))) ? - strdup_root(&mem, server_options->host) : 0; - - altered->db= - (server_options->db && (strcmp(server_options->db, existing->db))) ? - strdup_root(&mem, server_options->db) : 0; - - altered->username= - (server_options->username && - (strcmp(server_options->username, existing->username))) ? - strdup_root(&mem, server_options->username) : 0; - - altered->password= - (server_options->password && - (strcmp(server_options->password, existing->password))) ? - strdup_root(&mem, server_options->password) : 0; +#define SET_ALTERED(X) \ + do { \ + altered->X= \ + (server_options->X.str && strcmp(server_options->X.str, existing->X)) \ + ? strmake_root(&mem, server_options->X.str, server_options->X.length) \ + : 0; \ + } while(0) + + SET_ALTERED(host); + SET_ALTERED(db); + SET_ALTERED(username); + SET_ALTERED(password); + SET_ALTERED(socket); + SET_ALTERED(scheme); + SET_ALTERED(owner); /* port is initialised to -1, so if unset, it will be -1 @@ -1191,21 +1210,6 @@ prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options, server_options->port != existing->port) ? server_options->port : -1; - altered->socket= - (server_options->socket && - (strcmp(server_options->socket, existing->socket))) ? - strdup_root(&mem, server_options->socket) : 0; - - altered->scheme= - (server_options->scheme && - (strcmp(server_options->scheme, existing->scheme))) ? - strdup_root(&mem, server_options->scheme) : 0; - - altered->owner= - (server_options->owner && - (strcmp(server_options->owner, existing->owner))) ? - strdup_root(&mem, server_options->owner) : 0; - DBUG_VOID_RETURN; } @@ -1273,13 +1277,13 @@ static FOREIGN_SERVER *clone_server(MEM_ROOT *mem, const FOREIGN_SERVER *server, buffer->server_name_length= server->server_name_length; /* TODO: We need to examine which of these can really be NULL */ - buffer->db= server->db ? strdup_root(mem, server->db) : NULL; - buffer->scheme= server->scheme ? strdup_root(mem, server->scheme) : NULL; - buffer->username= server->username? strdup_root(mem, server->username): NULL; - buffer->password= server->password? strdup_root(mem, server->password): NULL; - buffer->socket= server->socket ? strdup_root(mem, server->socket) : NULL; - buffer->owner= server->owner ? strdup_root(mem, server->owner) : NULL; - buffer->host= server->host ? strdup_root(mem, server->host) : NULL; + buffer->db= safe_strdup_root(mem, server->db); + buffer->scheme= safe_strdup_root(mem, server->scheme); + buffer->username= safe_strdup_root(mem, server->username); + buffer->password= safe_strdup_root(mem, server->password); + buffer->socket= safe_strdup_root(mem, server->socket); + buffer->owner= safe_strdup_root(mem, server->owner); + buffer->host= safe_strdup_root(mem, server->host); DBUG_RETURN(buffer); } diff --git a/sql/sql_servers.h b/sql/sql_servers.h index a6186a85ae2..d5668f0dfcb 100644 --- a/sql/sql_servers.h +++ b/sql/sql_servers.h @@ -26,10 +26,10 @@ typedef struct st_mem_root MEM_ROOT; /* structs */ typedef struct st_federated_server { - char *server_name; + const char *server_name; long port; uint server_name_length; - char *db, *scheme, *username, *password, *socket, *owner, *host, *sport; + const char *db, *scheme, *username, *password, *socket, *owner, *host, *sport; } FOREIGN_SERVER; /* cache handlers */ diff --git a/sql/sql_show.cc b/sql/sql_show.cc index db33a9de781..46914ea14c4 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2009, 2016, MariaDB + Copyright (c) 2009, 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 @@ -17,7 +17,7 @@ /* Function with list databases, tables or fields */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" // Includes my_global.h #include "sql_priv.h" #include "unireg.h" #include "sql_acl.h" // fill_schema_*_privileges @@ -28,6 +28,7 @@ #include "sql_table.h" // filename_to_tablename, // primary_key_name, // build_table_filename +#include "sql_view.h" #include "repl_failsafe.h" #include "sql_parse.h" // check_access, check_table_access #include "sql_partition.h" // partition_element @@ -45,6 +46,7 @@ #include "set_var.h" #include "sql_trigger.h" #include "sql_derived.h" +#include "sql_statistics.h" #include "sql_connect.h" #include "authors.h" #include "contributors.h" @@ -56,11 +58,8 @@ #include <my_dir.h> #include "lock.h" // MYSQL_OPEN_IGNORE_FLUSH #include "debug_sync.h" -#include "datadict.h" // dd_frm_type() #include "keycaches.h" -#define STR_OR_NIL(S) ((S) ? (S) : "<nil>") - #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" #endif @@ -92,6 +91,8 @@ enum enum_i_s_events_fields ISE_DB_CL }; +#define USERNAME_WITH_HOST_CHAR_LENGTH (USERNAME_CHAR_LENGTH + HOSTNAME_LENGTH + 2) + #ifndef NO_EMBEDDED_ACCESS_CHECKS static const char *grant_names[]={ "select","insert","update","delete","create","drop","reload","shutdown", @@ -105,8 +106,7 @@ static TYPELIB grant_types = { sizeof(grant_names)/sizeof(char **), /* Match the values of enum ha_choice */ static const char *ha_choice_values[] = {"", "0", "1"}; -static void store_key_options(THD *thd, String *packet, TABLE *table, - KEY *key_info); +static void store_key_options(THD *, String *, TABLE *, KEY *); #ifdef WITH_PARTITION_STORAGE_ENGINE static void get_cs_converted_string_value(THD *thd, @@ -116,15 +116,22 @@ static void get_cs_converted_string_value(THD *thd, bool use_hex); #endif -static void -append_algorithm(TABLE_LIST *table, String *buff); +static int show_create_view(THD *thd, TABLE_LIST *table, String *buff); + +static const LEX_STRING *view_algorithm(TABLE_LIST *table); -static COND * make_cond_for_info_schema(COND *cond, TABLE_LIST *table); +bool get_lookup_field_values(THD *, COND *, TABLE_LIST *, LOOKUP_FIELD_VALUES *); /*************************************************************************** ** List all table types supported ***************************************************************************/ + +static bool is_show_command(THD *thd) +{ + return sql_command_flags[thd->lex->sql_command] & CF_STATUS_COMMAND; +} + static int make_version_string(char *buf, int buf_length, uint version) { return my_snprintf(buf, buf_length, "%d.%d", version>>8,version&0xff); @@ -159,7 +166,6 @@ static my_bool show_plugins(THD *thd, plugin_ref plugin, cs); switch (plugin_state(plugin)) { - /* case PLUGIN_IS_FREED: does not happen */ case PLUGIN_IS_DELETED: table->field[2]->store(STRING_WITH_LEN("DELETED"), cs); break; @@ -172,6 +178,9 @@ static my_bool show_plugins(THD *thd, plugin_ref plugin, case PLUGIN_IS_DISABLED: table->field[2]->store(STRING_WITH_LEN("DISABLED"), cs); break; + case PLUGIN_IS_FREED: // filtered in fill_plugins, used in fill_all_plugins + table->field[2]->store(STRING_WITH_LEN("NOT INSTALLED"), cs); + break; default: DBUG_ASSERT(0); } @@ -269,6 +278,182 @@ int fill_plugins(THD *thd, TABLE_LIST *tables, COND *cond) } +int fill_all_plugins(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_all_plugins"); + TABLE *table= tables->table; + LOOKUP_FIELD_VALUES lookup; + + if (get_lookup_field_values(thd, cond, tables, &lookup)) + DBUG_RETURN(0); + + if (lookup.db_value.str && !lookup.db_value.str[0]) + DBUG_RETURN(0); // empty string never matches a valid SONAME + + MY_DIR *dirp= my_dir(opt_plugin_dir, MY_THREAD_SPECIFIC); + if (!dirp) + { + my_error(ER_CANT_READ_DIR, MYF(0), opt_plugin_dir, my_errno); + DBUG_RETURN(1); + } + + if (!lookup.db_value.str) + plugin_dl_foreach(thd, 0, show_plugins, table); + + const char *wstr= lookup.db_value.str, *wend= wstr + lookup.db_value.length; + for (uint i=0; i < (uint) dirp->number_of_files; i++) + { + FILEINFO *file= dirp->dir_entry+i; + LEX_STRING dl= { file->name, strlen(file->name) }; + const char *dlend= dl.str + dl.length; + const size_t so_ext_len= sizeof(SO_EXT) - 1; + + if (strcasecmp(dlend - so_ext_len, SO_EXT)) + continue; + + if (lookup.db_value.str) + { + if (lookup.wild_db_value) + { + if (my_wildcmp(files_charset_info, dl.str, dlend, wstr, wend, + wild_prefix, wild_one, wild_many)) + continue; + } + else + { + if (my_strnncoll(files_charset_info, + (uchar*)dl.str, dl.length, + (uchar*)lookup.db_value.str, lookup.db_value.length)) + continue; + } + } + + plugin_dl_foreach(thd, &dl, show_plugins, table); + thd->clear_error(); + } + + my_dirend(dirp); + DBUG_RETURN(0); +} + + +#ifdef HAVE_SPATIAL +static int fill_spatial_ref_sys(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_spatial_ref_sys"); + TABLE *table= tables->table; + CHARSET_INFO *cs= system_charset_info; + int result= 1; + + restore_record(table, s->default_values); + + table->field[0]->store(-1, FALSE); /*SRID*/ + table->field[1]->store(STRING_WITH_LEN("Not defined"), cs); /*AUTH_NAME*/ + table->field[2]->store(-1, FALSE); /*AUTH_SRID*/ + table->field[3]->store(STRING_WITH_LEN( + "LOCAL_CS[\"Spatial reference wasn't specified\"," + "LOCAL_DATUM[\"Unknown\",0]," "UNIT[\"m\",1.0]," "AXIS[\"x\",EAST]," + "AXIS[\"y\",NORTH]]"), cs);/*SRTEXT*/ + if (schema_table_store_record(thd, table)) + goto exit; + + table->field[0]->store(0, TRUE); /*SRID*/ + table->field[1]->store(STRING_WITH_LEN("EPSG"), cs); /*AUTH_NAME*/ + table->field[2]->store(404000, TRUE); /*AUTH_SRID*/ + table->field[3]->store(STRING_WITH_LEN( + "LOCAL_CS[\"Wildcard 2D cartesian plane in metric unit\"," + "LOCAL_DATUM[\"Unknown\",0]," "UNIT[\"m\",1.0]," + "AXIS[\"x\",EAST]," "AXIS[\"y\",NORTH]," + "AUTHORITY[\"EPSG\",\"404000\"]]"), cs);/*SRTEXT*/ + if (schema_table_store_record(thd, table)) + goto exit; + + result= 0; + +exit: + DBUG_RETURN(result); +} + + +static int get_geometry_column_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + LEX_STRING *db_name, + LEX_STRING *table_name) +{ + CHARSET_INFO *cs= system_charset_info; + TABLE *show_table; + Field **ptr, *field; + DBUG_ENTER("get_geometry_column_record"); + + if (res) + { + if (thd->lex->sql_command != SQLCOM_SHOW_FIELDS) + { + /* + I.e. we are in SELECT FROM INFORMATION_SCHEMA.COLUMS + rather than in SHOW COLUMNS + */ + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + thd->clear_error(); + res= 0; + } + DBUG_RETURN(res); + } + + if (tables->schema_table) + goto exit; + show_table= tables->table; + ptr= show_table->field; + show_table->use_all_columns(); // Required for default + restore_record(show_table, s->default_values); + + for (; (field= *ptr) ; ptr++) + if (field->type() == MYSQL_TYPE_GEOMETRY) + { + Field_geom *fg= (Field_geom *) field; + + DEBUG_SYNC(thd, "get_schema_column"); + + /* Get default row, with all NULL fields set to NULL */ + restore_record(table, s->default_values); + + /*F_TABLE_CATALOG*/ + table->field[0]->store(STRING_WITH_LEN("def"), cs); + /*F_TABLE_SCHEMA*/ + table->field[1]->store(db_name->str, db_name->length, cs); + /*F_TABLE_NAME*/ + table->field[2]->store(table_name->str, table_name->length, cs); + /*G_TABLE_CATALOG*/ + table->field[4]->store(STRING_WITH_LEN("def"), cs); + /*G_TABLE_SCHEMA*/ + table->field[5]->store(db_name->str, db_name->length, cs); + /*G_TABLE_NAME*/ + table->field[6]->store(table_name->str, table_name->length, cs); + /*G_GEOMETRY_COLUMN*/ + table->field[7]->store(field->field_name, strlen(field->field_name), cs); + /*STORAGE_TYPE*/ + table->field[8]->store(1LL, TRUE); /*Always 1 (binary implementation)*/ + /*GEOMETRY_TYPE*/ + table->field[9]->store((longlong) (fg->get_geometry_type()), TRUE); + /*COORD_DIMENSION*/ + table->field[10]->store(2LL, TRUE); + /*MAX_PPR*/ + table->field[11]->set_null(); + /*SRID*/ + table->field[12]->store((longlong) (fg->get_srid()), TRUE); + + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + +exit: + DBUG_RETURN(0); +} +#endif /*HAVE_SPATIAL*/ + + /*************************************************************************** ** List all Authors. ** If you can update it, you get to be in it :) @@ -278,14 +463,19 @@ bool mysqld_show_authors(THD *thd) { List<Item> field_list; Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("mysqld_show_authors"); - field_list.push_back(new Item_empty_string("Name",40)); - field_list.push_back(new Item_empty_string("Location",40)); - field_list.push_back(new Item_empty_string("Comment",80)); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Name", 40), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Location", 40), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Comment", 512), + mem_root); if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) DBUG_RETURN(TRUE); show_table_authors_st *authors; @@ -312,14 +502,19 @@ bool mysqld_show_contributors(THD *thd) { List<Item> field_list; Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("mysqld_show_contributors"); - field_list.push_back(new Item_empty_string("Name",40)); - field_list.push_back(new Item_empty_string("Location",40)); - field_list.push_back(new Item_empty_string("Comment",80)); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Name", 40), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Location", 40), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Comment", 512), + mem_root); if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) DBUG_RETURN(TRUE); show_table_contributors_st *contributors; @@ -389,14 +584,20 @@ bool mysqld_show_privileges(THD *thd) { List<Item> field_list; Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("mysqld_show_privileges"); - field_list.push_back(new Item_empty_string("Privilege",10)); - field_list.push_back(new Item_empty_string("Context",15)); - field_list.push_back(new Item_empty_string("Comment",NAME_CHAR_LEN)); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Privilege", 10), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Context", 15), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Comment", + NAME_CHAR_LEN), + mem_root); if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) DBUG_RETURN(TRUE); show_privileges_st *privilege= sys_privileges; @@ -457,7 +658,7 @@ bool ignore_db_dirs_init() { return my_init_dynamic_array(&ignore_db_dirs_array, sizeof(LEX_STRING *), - 0, 0); + 0, 0, MYF(0)); } @@ -621,12 +822,24 @@ ignore_db_dirs_process_additions() for (i= 0; i < ignore_db_dirs_array.elements; i++) { get_dynamic(&ignore_db_dirs_array, (uchar *) &dir, i); - if (my_hash_insert(&ignore_db_dirs_hash, (uchar *) dir)) + if (my_hash_insert(&ignore_db_dirs_hash, (uchar *)dir)) + { + /* ignore duplicates from the config file */ + if (my_hash_search(&ignore_db_dirs_hash, (uchar *)dir->str, dir->length)) + { + sql_print_warning("Duplicate ignore-db-dir directory name '%.*s' " + "found in the config file(s). Ignoring the duplicate.", + (int) dir->length, dir->str); + my_free(dir); + goto continue_loop; + } + return true; + } ptr= strnmov(ptr, dir->str, dir->length); - if (i + 1 < ignore_db_dirs_array.elements) - ptr= strmov(ptr, ","); + *(ptr++)= ','; +continue_loop: /* Set the transferred array element to NULL to avoid double free in case of error. @@ -635,6 +848,12 @@ ignore_db_dirs_process_additions() set_dynamic(&ignore_db_dirs_array, (uchar *) &dir, i); } + if (ptr > opt_ignore_db_dirs) + { + ptr--; + DBUG_ASSERT(*ptr == ','); + } + /* make sure the string is terminated */ DBUG_ASSERT(ptr - opt_ignore_db_dirs <= (ptrdiff_t) len); *ptr= 0; @@ -688,6 +907,11 @@ db_name_is_in_ignore_db_dirs_list(const char *directory) return my_hash_search(&ignore_db_dirs_hash, (uchar *) buff, buff_len)!=NULL; } +enum find_files_result { + FIND_FILES_OK, + FIND_FILES_OOM, + FIND_FILES_DIR +}; /* find_files() - find files in a given directory. @@ -696,11 +920,10 @@ db_name_is_in_ignore_db_dirs_list(const char *directory) find_files() thd thread handler files put found files in this list - db database name to set in TABLE_LIST structure + db database name to search tables in + or NULL to search for databases path path to database wild filter for found files - dir read databases in path if TRUE, read .frm files in - database otherwise RETURN FIND_FILES_OK success @@ -709,64 +932,40 @@ db_name_is_in_ignore_db_dirs_list(const char *directory) */ -find_files_result -find_files(THD *thd, List<LEX_STRING> *files, const char *db, - const char *path, const char *wild, bool dir) +static find_files_result +find_files(THD *thd, Dynamic_array<LEX_STRING*> *files, LEX_STRING *db, + const char *path, const LEX_STRING *wild) { - uint i; - char *ext; MY_DIR *dirp; - FILEINFO *file; - LEX_STRING *file_name= 0; - uint file_name_len; -#ifndef NO_EMBEDDED_ACCESS_CHECKS - uint col_access=thd->col_access; -#endif - uint wild_length= 0; - TABLE_LIST table_list; + Discovered_table_list tl(thd, files, wild); DBUG_ENTER("find_files"); - if (wild) - { - if (!wild[0]) - wild= 0; - else - wild_length= strlen(wild); - } - - bzero((char*) &table_list,sizeof(table_list)); - - if (!(dirp = my_dir(path,MYF(dir ? MY_WANT_STAT : 0)))) + if (!(dirp = my_dir(path, MY_THREAD_SPECIFIC | (db ? 0 : MY_WANT_STAT)))) { if (my_errno == ENOENT) - my_error(ER_BAD_DB_ERROR, MYF(ME_BELL+ME_WAITTANG), db); + my_error(ER_BAD_DB_ERROR, MYF(ME_BELL | ME_WAITTANG), db->str); else - my_error(ER_CANT_READ_DIR, MYF(ME_BELL+ME_WAITTANG), path, my_errno); + my_error(ER_CANT_READ_DIR, MYF(ME_BELL | ME_WAITTANG), path, my_errno); DBUG_RETURN(FIND_FILES_DIR); } - for (i=0 ; i < (uint) dirp->number_off_files ; i++) + if (!db) /* Return databases */ { - char uname[SAFE_NAME_LEN + 1]; /* Unencoded name */ - file=dirp->dir_entry+i; - if (dir) - { /* Return databases */ - if ((file->name[0] == '.' && - ((file->name[1] == '.' && file->name[2] == '\0') || - file->name[1] == '\0'))) - continue; /* . or .. */ + for (uint i=0; i < (uint) dirp->number_of_files; i++) + { + FILEINFO *file= dirp->dir_entry+i; #ifdef USE_SYMDIR char *ext; char buff[FN_REFLEN]; if (my_use_symdir && !strcmp(ext=fn_ext(file->name), ".sym")) { - /* Only show the sym file if it points to a directory */ - char *end; + /* Only show the sym file if it points to a directory */ + char *end; *ext=0; /* Remove extension */ - unpack_dirname(buff, file->name); - end= strend(buff); - if (end != buff && end[-1] == FN_LIBCHAR) - end[-1]= 0; // Remove end FN_LIBCHAR + unpack_dirname(buff, file->name); + end= strend(buff); + if (end != buff && end[-1] == FN_LIBCHAR) + end[-1]= 0; // Remove end FN_LIBCHAR if (!mysql_file_stat(key_file_misc, buff, file->mystat, MYF(0))) continue; } @@ -777,70 +976,32 @@ find_files(THD *thd, List<LEX_STRING> *files, const char *db, if (is_in_ignore_db_dirs_list(file->name)) continue; - file_name_len= filename_to_tablename(file->name, uname, sizeof(uname)); - if (wild) - { - if (lower_case_table_names) - { - if (my_wildcmp(files_charset_info, - uname, uname + file_name_len, - wild, wild + wild_length, - wild_prefix, wild_one, wild_many)) - continue; - } - else if (wild_compare(uname, wild, 0)) - continue; - } - } - else - { - // Return only .frm files which aren't temp files. - if (my_strcasecmp(system_charset_info, ext=fn_rext(file->name),reg_ext) || - is_prefix(file->name, tmp_file_prefix)) - continue; - *ext=0; - file_name_len= filename_to_tablename(file->name, uname, sizeof(uname)); - if (wild) - { - if (lower_case_table_names) - { - if (my_wildcmp(files_charset_info, - uname, uname + file_name_len, - wild, wild + wild_length, - wild_prefix, wild_one,wild_many)) - continue; - } - else if (wild_compare(uname, wild, 0)) - continue; - } - } -#ifndef NO_EMBEDDED_ACCESS_CHECKS - /* Don't show tables where we don't have any privileges */ - if (db && !(col_access & TABLE_ACLS)) - { - table_list.db= (char*) db; - table_list.db_length= strlen(db); - table_list.table_name= uname; - table_list.table_name_length= file_name_len; - table_list.grant.privilege=col_access; - if (check_grant(thd, TABLE_ACLS, &table_list, TRUE, 1, TRUE)) - continue; - } -#endif - if (!(file_name= - thd->make_lex_string(file_name, uname, file_name_len, TRUE)) || - files->push_back(file_name)) - { - my_dirend(dirp); - DBUG_RETURN(FIND_FILES_OOM); + if (tl.add_file(file->name)) + goto err; } } - DBUG_PRINT("info",("found: %d files", files->elements)); - my_dirend(dirp); + else + { + if (ha_discover_table_names(thd, db, dirp, &tl, false)) + goto err; + } +#if MYSQL_VERSION_ID < 100300 + /* incomplete optimization, but a less drastic change in GA version */ + if (!thd->lex->select_lex.order_list.elements && + !thd->lex->select_lex.group_list.elements) +#else + if (is_show_command(thd)) +#endif + tl.sort(); - (void) ha_find_files(thd, db, path, wild, dir, files); + DBUG_PRINT("info",("found: %zu files", files->elements())); + my_dirend(dirp); DBUG_RETURN(FIND_FILES_OK); + +err: + my_dirend(dirp); + DBUG_RETURN(FIND_FILES_OOM); } @@ -881,7 +1042,7 @@ public: m_view_access_denied_message_ptr(NULL) { - m_sctx = test(m_top_view->security_ctx) ? + m_sctx= MY_TEST(m_top_view->security_ctx) ? m_top_view->security_ctx : thd->security_ctx; } @@ -894,13 +1055,13 @@ public: failed is not available at this point. The only way for us to check is by reconstructing the actual error message and see if it's the same. */ - char* get_view_access_denied_message() + char* get_view_access_denied_message(THD *thd) { if (!m_view_access_denied_message_ptr) { m_view_access_denied_message_ptr= m_view_access_denied_message; my_snprintf(m_view_access_denied_message, MYSQL_ERRMSG_SIZE, - ER(ER_TABLEACCESS_DENIED_ERROR), "SHOW VIEW", + ER_THD(thd, ER_TABLEACCESS_DENIED_ERROR), "SHOW VIEW", m_sctx->priv_user, m_sctx->host_or_ip, m_top_view->get_table_name()); } @@ -908,8 +1069,8 @@ public: } bool handle_condition(THD *thd, uint sql_errno, const char * /* sqlstate */, - MYSQL_ERROR::enum_warning_level level, - const char *message, MYSQL_ERROR ** /* cond_hdl */) + Sql_condition::enum_warning_level level, + const char *message, Sql_condition ** /* cond_hdl */) { /* The handler does not handle the errors raised by itself. @@ -925,7 +1086,7 @@ public: switch (sql_errno) { case ER_TABLEACCESS_DENIED_ERROR: - if (!strcmp(get_view_access_denied_message(), message)) + if (!strcmp(get_view_access_denied_message(thd), message)) { /* Access to top view is not granted, don't interfere. */ is_handled= FALSE; @@ -944,9 +1105,9 @@ public: case ER_NO_SUCH_TABLE_IN_ENGINE: /* Established behavior: warn if underlying tables, columns, or functions are missing. */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_VIEW_INVALID, - ER(ER_VIEW_INVALID), + ER_THD(thd, ER_VIEW_INVALID), m_top_view->get_db_name(), m_top_view->get_table_name()); is_handled= TRUE; @@ -963,46 +1124,81 @@ public: /* - Return CREATE command for table or view + Return metadata for CREATE command for table or view @param thd Thread handler @param table_list Table / view + @param field_list resulting list of fields + @param buffer resulting CREATE statement @return @retval 0 OK @retval 1 Error - @notes - table_list->db and table_list->table_name are kept unchanged to - not cause problems with SP. */ bool -mysqld_show_create(THD *thd, TABLE_LIST *table_list) +mysqld_show_create_get_fields(THD *thd, TABLE_LIST *table_list, + List<Item> *field_list, String *buffer) { - Protocol *protocol= thd->protocol; - char buff[2048]; - String buffer(buff, sizeof(buff), system_charset_info); - List<Item> field_list; bool error= TRUE; - DBUG_ENTER("mysqld_show_create"); + LEX *lex= thd->lex; + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ENTER("mysqld_show_create_get_fields"); DBUG_PRINT("enter",("db: %s table: %s",table_list->db, table_list->table_name)); - /* - Metadata locks taken during SHOW CREATE should be released when - the statmement completes as it is an information statement. - */ - MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); + if (lex->only_view) + { + if (check_table_access(thd, SELECT_ACL, table_list, FALSE, 1, FALSE)) + { + DBUG_PRINT("debug", ("check_table_access failed")); + my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), + "SHOW", thd->security_ctx->priv_user, + thd->security_ctx->host_or_ip, table_list->alias); + goto exit; + } + DBUG_PRINT("debug", ("check_table_access succeeded")); + + /* Ignore temporary tables if this is "SHOW CREATE VIEW" */ + table_list->open_type= OT_BASE_ONLY; + } + else + { + /* + Temporary tables should be opened for SHOW CREATE TABLE, but not + for SHOW CREATE VIEW. + */ + if (open_temporary_tables(thd, table_list)) + goto exit; + + /* + The fact that check_some_access() returned FALSE does not mean that + access is granted. We need to check if table_list->grant.privilege + contains any table-specific privilege. + */ + DBUG_PRINT("debug", ("table_list->grant.privilege: %lx", + table_list->grant.privilege)); + if (check_some_access(thd, SHOW_CREATE_TABLE_ACLS, table_list) || + (table_list->grant.privilege & SHOW_CREATE_TABLE_ACLS) == 0) + { + my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), + "SHOW", thd->security_ctx->priv_user, + thd->security_ctx->host_or_ip, table_list->alias); + goto exit; + } + } + /* Access is granted. Execute the command. */ /* We want to preserve the tree for views. */ - thd->lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_VIEW; + lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_VIEW; { /* - Use open_tables() directly rather than open_normal_and_derived_tables(). - This ensures that close_thread_tables() is not called if open tables fails - and the error is ignored. This allows us to handle broken views nicely. + Use open_tables() directly rather than + open_normal_and_derived_tables(). This ensures that + close_thread_tables() is not called if open tables fails and the + error is ignored. This allows us to handle broken views nicely. */ uint counter; Show_create_error_handler view_error_suppressor(thd, table_list); @@ -1010,51 +1206,106 @@ mysqld_show_create(THD *thd, TABLE_LIST *table_list) bool open_error= open_tables(thd, &table_list, &counter, MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL) || - mysql_handle_derived(thd->lex, DT_PREPARE); + mysql_handle_derived(lex, DT_PREPARE); thd->pop_internal_handler(); if (open_error && (thd->killed || thd->is_error())) goto exit; } /* TODO: add environment variables show when it become possible */ - if (thd->lex->only_view && !table_list->view) + if (lex->only_view && !table_list->view) { my_error(ER_WRONG_OBJECT, MYF(0), table_list->db, table_list->table_name, "VIEW"); goto exit; } - buffer.length(0); + buffer->length(0); if (table_list->view) - buffer.set_charset(table_list->view_creation_ctx->get_client_cs()); + buffer->set_charset(table_list->view_creation_ctx->get_client_cs()); if ((table_list->view ? - view_store_create_info(thd, table_list, &buffer) : - store_create_info(thd, table_list, &buffer, NULL, - FALSE /* show_database */))) + show_create_view(thd, table_list, buffer) : + show_create_table(thd, table_list, buffer, NULL, WITHOUT_DB_NAME))) goto exit; if (table_list->view) { - field_list.push_back(new Item_empty_string("View",NAME_CHAR_LEN)); - field_list.push_back(new Item_empty_string("Create View", - max(buffer.length(),1024))); - field_list.push_back(new Item_empty_string("character_set_client", - MY_CS_NAME_SIZE)); - field_list.push_back(new Item_empty_string("collation_connection", - MY_CS_NAME_SIZE)); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "View", NAME_CHAR_LEN), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Create View", + MY_MAX(buffer->length(),1024)), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "character_set_client", + MY_CS_NAME_SIZE), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "collation_connection", + MY_CS_NAME_SIZE), + mem_root); } else { - field_list.push_back(new Item_empty_string("Table",NAME_CHAR_LEN)); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Table", NAME_CHAR_LEN), + mem_root); // 1024 is for not to confuse old clients - field_list.push_back(new Item_empty_string("Create Table", - max(buffer.length(),1024))); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Create Table", + MY_MAX(buffer->length(),1024)), + mem_root); } + error= FALSE; + +exit: + DBUG_RETURN(error); +} + + +/* + Return CREATE command for table or view + + @param thd Thread handler + @param table_list Table / view + + @return + @retval 0 OK + @retval 1 Error + + @notes + table_list->db and table_list->table_name are kept unchanged to + not cause problems with SP. +*/ + +bool +mysqld_show_create(THD *thd, TABLE_LIST *table_list) +{ + Protocol *protocol= thd->protocol; + char buff[2048]; + String buffer(buff, sizeof(buff), system_charset_info); + List<Item> field_list; + bool error= TRUE; + DBUG_ENTER("mysqld_show_create"); + DBUG_PRINT("enter",("db: %s table: %s",table_list->db, + table_list->table_name)); + + /* + Metadata locks taken during SHOW CREATE should be released when + the statmement completes as it is an information statement. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); + + + if (mysqld_show_create_get_fields(thd, table_list, &field_list, &buffer)) + goto exit; if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) goto exit; protocol->prepare_for_resend(); @@ -1096,8 +1347,22 @@ exit: DBUG_RETURN(error); } -bool mysqld_show_create_db(THD *thd, char *dbname, - HA_CREATE_INFO *create_info) + +void mysqld_show_create_db_get_fields(THD *thd, List<Item> *field_list) +{ + MEM_ROOT *mem_root= thd->mem_root; + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Database", NAME_CHAR_LEN), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Create Database", 1024), + mem_root); +} + + +bool mysqld_show_create_db(THD *thd, LEX_STRING *dbname, + LEX_STRING *orig_dbname, + const DDL_options_st &options) { char buff[2048]; String buffer(buff, sizeof(buff), system_charset_info); @@ -1105,57 +1370,62 @@ bool mysqld_show_create_db(THD *thd, char *dbname, Security_context *sctx= thd->security_ctx; uint db_access; #endif - HA_CREATE_INFO create; - uint create_options = create_info ? create_info->options : 0; + Schema_specification_st create; Protocol *protocol=thd->protocol; + List<Item> field_list; DBUG_ENTER("mysql_show_create_db"); #ifndef NO_EMBEDDED_ACCESS_CHECKS if (test_all_bits(sctx->master_access, DB_ACLS)) db_access=DB_ACLS; else - db_access= (acl_get(sctx->host, sctx->ip, sctx->priv_user, dbname, 0) | - sctx->master_access); - if (!(db_access & DB_ACLS) && check_grant_db(thd,dbname)) + { + db_access= acl_get(sctx->host, sctx->ip, sctx->priv_user, dbname->str, 0) | + sctx->master_access; + if (sctx->priv_role[0]) + db_access|= acl_get("", "", sctx->priv_role, dbname->str, 0); + } + + if (!(db_access & DB_ACLS) && check_grant_db(thd,dbname->str)) { status_var_increment(thd->status_var.access_denied_errors); my_error(ER_DBACCESS_DENIED_ERROR, MYF(0), - sctx->priv_user, sctx->host_or_ip, dbname); - general_log_print(thd,COM_INIT_DB,ER(ER_DBACCESS_DENIED_ERROR), - sctx->priv_user, sctx->host_or_ip, dbname); + sctx->priv_user, sctx->host_or_ip, dbname->str); + general_log_print(thd,COM_INIT_DB,ER_THD(thd, ER_DBACCESS_DENIED_ERROR), + sctx->priv_user, sctx->host_or_ip, orig_dbname->str); DBUG_RETURN(TRUE); } #endif - if (is_infoschema_db(dbname)) + if (is_infoschema_db(dbname->str)) { - dbname= INFORMATION_SCHEMA_NAME.str; + *dbname= INFORMATION_SCHEMA_NAME; create.default_table_charset= system_charset_info; } else { - if (check_db_dir_existence(dbname)) + if (check_db_dir_existence(dbname->str)) { - my_error(ER_BAD_DB_ERROR, MYF(0), dbname); + my_error(ER_BAD_DB_ERROR, MYF(0), dbname->str); DBUG_RETURN(TRUE); } - load_db_opt_by_name(thd, dbname, &create); + load_db_opt_by_name(thd, dbname->str, &create); } - List<Item> field_list; - field_list.push_back(new Item_empty_string("Database",NAME_CHAR_LEN)); - field_list.push_back(new Item_empty_string("Create Database",1024)); + + mysqld_show_create_db_get_fields(thd, &field_list); if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) DBUG_RETURN(TRUE); protocol->prepare_for_resend(); - protocol->store(dbname, strlen(dbname), system_charset_info); + protocol->store(orig_dbname->str, orig_dbname->length, system_charset_info); buffer.length(0); buffer.append(STRING_WITH_LEN("CREATE DATABASE ")); - if (create_options & HA_LEX_CREATE_IF_NOT_EXISTS) + if (options.if_not_exists()) buffer.append(STRING_WITH_LEN("/*!32312 IF NOT EXISTS*/ ")); - append_identifier(thd, &buffer, dbname, strlen(dbname)); + append_identifier(thd, &buffer, dbname->str, dbname->length); if (create.default_table_charset) { @@ -1188,6 +1458,7 @@ void mysqld_list_fields(THD *thd, TABLE_LIST *table_list, const char *wild) { TABLE *table; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("mysqld_list_fields"); DBUG_PRINT("enter",("table: %s",table_list->table_name)); @@ -1206,16 +1477,19 @@ mysqld_list_fields(THD *thd, TABLE_LIST *table_list, const char *wild) !wild_case_compare(system_charset_info, field->field_name,wild)) { if (table_list->view) - field_list.push_back(new Item_ident_for_show(field, - table_list->view_db.str, - table_list->view_name.str)); + field_list.push_back(new (mem_root) + Item_ident_for_show(thd, field, + table_list->view_db.str, + table_list->view_name.str), + mem_root); else - field_list.push_back(new Item_field(field)); + field_list.push_back(new (mem_root) Item_field(thd, field), mem_root); } } restore_record(table, s->default_values); // Get empty record table->use_all_columns(); - if (thd->protocol->send_result_set_metadata(&field_list, Protocol::SEND_DEFAULTS)) + if (thd->protocol->send_result_set_metadata(&field_list, + Protocol::SEND_DEFAULTS)) DBUG_VOID_RETURN; my_eof(thd); DBUG_VOID_RETURN; @@ -1284,9 +1558,22 @@ append_identifier(THD *thd, String *packet, const char *name, uint length) it's a keyword */ + /* + Special code for swe7. It encodes the letter "E WITH ACUTE" on + the position 0x60, where backtick normally resides. + In swe7 we cannot append 0x60 using system_charset_info, + because it cannot be converted to swe7 and will be replaced to + question mark '?'. Use &my_charset_bin to avoid this. + It will prevent conversion and will append the backtick as is. + */ + CHARSET_INFO *quote_charset= q == 0x60 && + (packet->charset()->state & MY_CS_NONASCII) && + packet->charset()->mbmaxlen == 1 ? + &my_charset_bin : system_charset_info; + (void) packet->reserve(length*2 + 2); quote_char= (char) q; - if (packet->append("e_char, 1, system_charset_info)) + if (packet->append("e_char, 1, quote_charset)) return true; for (name_end= name+length ; name < name_end ; name+= length) @@ -1303,12 +1590,12 @@ append_identifier(THD *thd, String *packet, const char *name, uint length) if (!length) length= 1; if (length == 1 && chr == (uchar) quote_char && - packet->append("e_char, 1, system_charset_info)) + packet->append("e_char, 1, quote_charset)) return true; if (packet->append(name, length, system_charset_info)) return true; } - return packet->append("e_char, 1, system_charset_info); + return packet->append("e_char, 1, quote_charset); } @@ -1378,8 +1665,35 @@ static void append_directory(THD *thd, String *packet, const char *dir_type, #define LIST_PROCESS_HOST_LEN 64 -static bool get_field_default_value(THD *thd, Field *timestamp_field, - Field *field, String *def_value, + +/** + Print "ON UPDATE" clause of a field into a string. + + @param timestamp_field Pointer to timestamp field of a table. + @param field The field to generate ON UPDATE clause for. + @bool lcase Whether to print in lower case. + @return false on success, true on error. +*/ +static bool print_on_update_clause(Field *field, String *val, bool lcase) +{ + DBUG_ASSERT(val->charset()->mbminlen == 1); + val->length(0); + if (field->has_update_default_function()) + { + if (lcase) + val->append(STRING_WITH_LEN("on update ")); + else + val->append(STRING_WITH_LEN("ON UPDATE ")); + val->append(STRING_WITH_LEN("CURRENT_TIMESTAMP")); + if (field->decimals() > 0) + val->append_parenthesized(field->decimals()); + return true; + } + return false; +} + + +static bool get_field_default_value(THD *thd, Field *field, String *def_value, bool quoted) { bool has_default; @@ -1390,8 +1704,7 @@ static bool get_field_default_value(THD *thd, Field *timestamp_field, We are using CURRENT_TIMESTAMP instead of NOW because it is more standard */ - has_now_default= (timestamp_field == field && - field->unireg_check != Field::TIMESTAMP_UN_FIELD); + has_now_default= field->has_insert_default_function(); has_default= (field_type != FIELD_TYPE_BLOB && !(field->flags & NO_DEFAULT_VALUE_FLAG) && @@ -1403,7 +1716,11 @@ static bool get_field_default_value(THD *thd, Field *timestamp_field, if (has_default) { if (has_now_default) + { def_value->append(STRING_WITH_LEN("CURRENT_TIMESTAMP")); + if (field->decimals() > 0) + def_value->append_parenthesized(field->decimals()); + } else if (!field->is_null()) { // Not null by default char tmp[MAX_FIELD_WIDTH]; @@ -1452,13 +1769,34 @@ static bool get_field_default_value(THD *thd, Field *timestamp_field, @param thd thread handler @param packet string to append @param opt list of options + @param check_options only print known options + @param rules list of known options */ static void append_create_options(THD *thd, String *packet, - engine_option_value *opt) + engine_option_value *opt, + bool check_options, + ha_create_table_option *rules) { + bool in_comment= false; for(; opt; opt= opt->next) { + if (check_options) + { + if (is_engine_option_known(opt, rules)) + { + if (in_comment) + packet->append(STRING_WITH_LEN(" */")); + in_comment= false; + } + else + { + if (!in_comment) + packet->append(STRING_WITH_LEN(" /*")); + in_comment= true; + } + } + DBUG_ASSERT(opt->value.str); packet->append(' '); append_identifier(thd, packet, opt->name.str, opt->name.length); @@ -1468,13 +1806,15 @@ static void append_create_options(THD *thd, String *packet, else packet->append(opt->value.str, opt->value.length); } + if (in_comment) + packet->append(STRING_WITH_LEN(" */")); } /* Build a CREATE TABLE statement for a table. SYNOPSIS - store_create_info() + show_create_table() thd The thread table_list A list containing one table to write statement for. @@ -1484,11 +1824,7 @@ static void append_create_options(THD *thd, String *packet, to tailor the format of the statement. Can be NULL, in which case only SQL_MODE is considered when building the statement. - show_database If true, then print the database before the table - name. The database name is only printed in the event - that it is different from the current database. - If false, then do not print the database before - the table name. + with_db_name Add database name to table name NOTE Currently always return 0, but might return error code in the @@ -1498,8 +1834,9 @@ static void append_create_options(THD *thd, String *packet, 0 OK */ -int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, - HA_CREATE_INFO *create_info_arg, bool show_database) +int show_create_table(THD *thd, TABLE_LIST *table_list, String *packet, + Table_specification_st *create_info_arg, + enum_with_db_name with_db_name) { List<Item> field_list; char tmp[MAX_FIELD_WIDTH], *for_str, buff[128], def_value_buf[MAX_FIELD_WIDTH]; @@ -1513,31 +1850,41 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, handler *file= table->file; TABLE_SHARE *share= table->s; HA_CREATE_INFO create_info; -#ifdef WITH_PARTITION_STORAGE_ENGINE - bool show_table_options= FALSE; -#endif /* WITH_PARTITION_STORAGE_ENGINE */ - bool foreign_db_mode= (thd->variables.sql_mode & (MODE_POSTGRESQL | - MODE_ORACLE | - MODE_MSSQL | - MODE_DB2 | - MODE_MAXDB | - MODE_ANSI)) != 0; - bool limited_mysql_mode= (thd->variables.sql_mode & (MODE_NO_FIELD_OPTIONS | - MODE_MYSQL323 | - MODE_MYSQL40)) != 0; + sql_mode_t sql_mode= thd->variables.sql_mode; + bool foreign_db_mode= sql_mode & (MODE_POSTGRESQL | MODE_ORACLE | + MODE_MSSQL | MODE_DB2 | + MODE_MAXDB | MODE_ANSI); + bool limited_mysql_mode= sql_mode & (MODE_NO_FIELD_OPTIONS | MODE_MYSQL323 | + MODE_MYSQL40); + bool show_table_options= !(sql_mode & MODE_NO_TABLE_OPTIONS) && + !foreign_db_mode; + bool check_options= !(sql_mode & MODE_IGNORE_BAD_TABLE_OPTIONS) && + !create_info_arg; + handlerton *hton; my_bitmap_map *old_map; int error= 0; - DBUG_ENTER("store_create_info"); + DBUG_ENTER("show_create_table"); DBUG_PRINT("enter",("table: %s", table->s->table_name.str)); +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->part_info) + hton= table->part_info->default_engine_type; + else +#endif + hton= file->ht; + restore_record(table, s->default_values); // Get empty record - if (share->tmp_table) - packet->append(STRING_WITH_LEN("CREATE TEMPORARY TABLE ")); - else - packet->append(STRING_WITH_LEN("CREATE TABLE ")); + packet->append(STRING_WITH_LEN("CREATE ")); if (create_info_arg && - (create_info_arg->options & HA_LEX_CREATE_IF_NOT_EXISTS)) + ((create_info_arg->or_replace() && + !create_info_arg->or_replace_slave_generated()) || + create_info_arg->table_was_deleted)) + packet->append(STRING_WITH_LEN("OR REPLACE ")); + if (share->tmp_table) + packet->append(STRING_WITH_LEN("TEMPORARY ")); + packet->append(STRING_WITH_LEN("TABLE ")); + if (create_info_arg && create_info_arg->if_not_exists()) packet->append(STRING_WITH_LEN("IF NOT EXISTS ")); if (table_list->schema_table) alias= table_list->schema_table->table_name; @@ -1558,7 +1905,7 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, avoid having to update gazillions of tests and result files, but it also saves a few bytes of the binary log. */ - if (show_database) + if (with_db_name == WITH_DB_NAME) { const LEX_STRING *const db= table_list->schema_table ? &INFORMATION_SCHEMA_NAME : &table->s->db; @@ -1597,8 +1944,7 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, field->sql_type(type); packet->append(type.ptr(), type.length(), system_charset_info); - if (field->has_charset() && - !(thd->variables.sql_mode & (MODE_MYSQL323 | MODE_MYSQL40))) + if (field->has_charset() && !(sql_mode & (MODE_MYSQL323 | MODE_MYSQL40))) { if (field->charset() != share->table_charset) { @@ -1628,40 +1974,44 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, else packet->append(STRING_WITH_LEN(" VIRTUAL")); } - - if (flags & NOT_NULL_FLAG) - packet->append(STRING_WITH_LEN(" NOT NULL")); - else if (field->type() == MYSQL_TYPE_TIMESTAMP) + else { - /* - TIMESTAMP field require explicit NULL flag, because unlike - all other fields they are treated as NOT NULL by default. - */ - packet->append(STRING_WITH_LEN(" NULL")); - } + if (flags & NOT_NULL_FLAG) + packet->append(STRING_WITH_LEN(" NOT NULL")); + else if (field->type() == MYSQL_TYPE_TIMESTAMP) + { + /* + TIMESTAMP field require explicit NULL flag, because unlike + all other fields they are treated as NOT NULL by default. + */ + packet->append(STRING_WITH_LEN(" NULL")); + } - if (!field->vcol_info && - get_field_default_value(thd, table->timestamp_field, - field, &def_value, 1)) - { - packet->append(STRING_WITH_LEN(" DEFAULT ")); - packet->append(def_value.ptr(), def_value.length(), system_charset_info); - } + if (get_field_default_value(thd, field, &def_value, 1)) + { + packet->append(STRING_WITH_LEN(" DEFAULT ")); + packet->append(def_value.ptr(), def_value.length(), system_charset_info); + } - if (!limited_mysql_mode && table->timestamp_field == field && - field->unireg_check != Field::TIMESTAMP_DN_FIELD) - packet->append(STRING_WITH_LEN(" ON UPDATE CURRENT_TIMESTAMP")); + if (!limited_mysql_mode && + print_on_update_clause(field, &def_value, false)) + { + packet->append(STRING_WITH_LEN(" ")); + packet->append(def_value); + } - if (field->unireg_check == Field::NEXT_NUMBER && - !(thd->variables.sql_mode & MODE_NO_FIELD_OPTIONS)) - packet->append(STRING_WITH_LEN(" AUTO_INCREMENT")); + if (field->unireg_check == Field::NEXT_NUMBER && + !(sql_mode & MODE_NO_FIELD_OPTIONS)) + packet->append(STRING_WITH_LEN(" AUTO_INCREMENT")); + } if (field->comment.length) { packet->append(STRING_WITH_LEN(" COMMENT ")); append_unescaped(packet, field->comment.str, field->comment.length); } - append_create_options(thd, packet, field->option_list); + append_create_options(thd, packet, field->option_list, check_options, + hton->field_options); } key_info= table->key_info; @@ -1702,7 +2052,7 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, packet->append(STRING_WITH_LEN(" (")); - for (uint j=0 ; j < key_info->key_parts ; j++,key_part++) + for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++) { if (j) packet->append(','); @@ -1715,13 +2065,8 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, table->field[key_part->fieldnr-1]->key_length() && !(key_info->flags & (HA_FULLTEXT | HA_SPATIAL)))) { - char *end; - buff[0] = '('; - end= int10_to_str((long) key_part->length / - key_part->field->charset()->mbmaxlen, - buff + 1,10); - *end++ = ')'; - packet->append(buff,(uint) (end-buff)); + packet->append_parenthesized((long) key_part->length / + key_part->field->charset()->mbmaxlen); } } packet->append(')'); @@ -1733,7 +2078,8 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, append_identifier(thd, packet, parser_name->str, parser_name->length); packet->append(STRING_WITH_LEN(" */ ")); } - append_create_options(thd, packet, key_info->option_list); + append_create_options(thd, packet, key_info->option_list, check_options, + hton->index_options); } /* @@ -1748,12 +2094,8 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, } packet->append(STRING_WITH_LEN("\n)")); - if (!(thd->variables.sql_mode & MODE_NO_TABLE_OPTIONS) && !foreign_db_mode) + if (show_table_options) { -#ifdef WITH_PARTITION_STORAGE_ENGINE - show_table_options= TRUE; -#endif /* WITH_PARTITION_STORAGE_ENGINE */ - /* IF check_create_info THEN add ENGINE only if it was used when creating the table @@ -1761,19 +2103,11 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, if (!create_info_arg || (create_info_arg->used_fields & HA_CREATE_USED_ENGINE)) { - if (thd->variables.sql_mode & (MODE_MYSQL323 | MODE_MYSQL40)) + if (sql_mode & (MODE_MYSQL323 | MODE_MYSQL40)) packet->append(STRING_WITH_LEN(" TYPE=")); else packet->append(STRING_WITH_LEN(" ENGINE=")); -#ifdef WITH_PARTITION_STORAGE_ENGINE - if (table->part_info) - packet->append(ha_resolve_storage_engine_name( - table->part_info->default_engine_type)); - else - packet->append(file->table_type()); -#else - packet->append(file->table_type()); -#endif + packet->append(hton_name(hton)); } /* @@ -1795,9 +2129,7 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, packet->append(buff, (uint) (end - buff)); } - if (share->table_charset && - !(thd->variables.sql_mode & MODE_MYSQL323) && - !(thd->variables.sql_mode & MODE_MYSQL40)) + if (share->table_charset && !(sql_mode & (MODE_MYSQL323 | MODE_MYSQL40))) { /* IF check_create_info @@ -1844,6 +2176,22 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, packet->append(STRING_WITH_LEN(" PACK_KEYS=1")); if (create_info.options & HA_OPTION_NO_PACK_KEYS) packet->append(STRING_WITH_LEN(" PACK_KEYS=0")); + if (share->db_create_options & HA_OPTION_STATS_PERSISTENT) + packet->append(STRING_WITH_LEN(" STATS_PERSISTENT=1")); + if (share->db_create_options & HA_OPTION_NO_STATS_PERSISTENT) + packet->append(STRING_WITH_LEN(" STATS_PERSISTENT=0")); + if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_ON) + packet->append(STRING_WITH_LEN(" STATS_AUTO_RECALC=1")); + else if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_OFF) + packet->append(STRING_WITH_LEN(" STATS_AUTO_RECALC=0")); + if (share->stats_sample_pages != 0) + { + char *end; + packet->append(STRING_WITH_LEN(" STATS_SAMPLE_PAGES=")); + end= longlong10_to_str(share->stats_sample_pages, buff, 10); + packet->append(buff, (uint) (end - buff)); + } + /* We use CHECKSUM, instead of TABLE_CHECKSUM, for backward compability */ if (create_info.options & HA_OPTION_CHECKSUM) packet->append(STRING_WITH_LEN(" CHECKSUM=1")); @@ -1882,7 +2230,8 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, packet->append(STRING_WITH_LEN(" CONNECTION=")); append_unescaped(packet, share->connect_string.str, share->connect_string.length); } - append_create_options(thd, packet, share->option_list); + append_create_options(thd, packet, share->option_list, check_options, + hton->table_options); append_directory(thd, packet, "DATA", create_info.data_file_name); append_directory(thd, packet, "INDEX", create_info.index_file_name); } @@ -1956,7 +2305,7 @@ static void store_key_options(THD *thd, String *packet, TABLE *table, end= longlong10_to_str(key_info->block_size, buff, 10); packet->append(buff, (uint) (end - buff)); } - DBUG_ASSERT(test(key_info->flags & HA_USES_COMMENT) == + DBUG_ASSERT(MY_TEST(key_info->flags & HA_USES_COMMENT) == (key_info->comment.length > 0)); if (key_info->flags & HA_USES_COMMENT) { @@ -1968,10 +2317,14 @@ static void store_key_options(THD *thd, String *packet, TABLE *table, } -void -view_store_options(THD *thd, TABLE_LIST *table, String *buff) +void view_store_options(THD *thd, TABLE_LIST *table, String *buff) { - append_algorithm(table, buff); + if (table->algorithm != VIEW_ALGORITHM_INHERIT) + { + buff->append(STRING_WITH_LEN("ALGORITHM=")); + buff->append(view_algorithm(table)); + } + buff->append(' '); append_definer(thd, buff, &table->definer.user, &table->definer.host); if (table->view_suid) buff->append(STRING_WITH_LEN("SQL SECURITY DEFINER ")); @@ -1980,32 +2333,24 @@ view_store_options(THD *thd, TABLE_LIST *table, String *buff) } -/* - Append DEFINER clause to the given buffer. - - SYNOPSIS - append_definer() - thd [in] thread handle - buffer [inout] buffer to hold DEFINER clause - definer_user [in] user name part of definer - definer_host [in] host name part of definer +/** + Returns ALGORITHM clause of a view */ -static void append_algorithm(TABLE_LIST *table, String *buff) +static const LEX_STRING *view_algorithm(TABLE_LIST *table) { - buff->append(STRING_WITH_LEN("ALGORITHM=")); - switch ((int16)table->algorithm) { - case VIEW_ALGORITHM_UNDEFINED: - buff->append(STRING_WITH_LEN("UNDEFINED ")); - break; + static const LEX_STRING undefined= { C_STRING_WITH_LEN("UNDEFINED") }; + static const LEX_STRING merge= { C_STRING_WITH_LEN("MERGE") }; + static const LEX_STRING temptable= { C_STRING_WITH_LEN("TEMPTABLE") }; + switch (table->algorithm) { case VIEW_ALGORITHM_TMPTABLE: - buff->append(STRING_WITH_LEN("TEMPTABLE ")); - break; + return &temptable; case VIEW_ALGORITHM_MERGE: - buff->append(STRING_WITH_LEN("MERGE ")); - break; + return &merge; default: DBUG_ASSERT(0); // never should happen + case VIEW_ALGORITHM_UNDEFINED: + return &undefined; } } @@ -2025,14 +2370,16 @@ void append_definer(THD *thd, String *buffer, const LEX_STRING *definer_user, { buffer->append(STRING_WITH_LEN("DEFINER=")); append_identifier(thd, buffer, definer_user->str, definer_user->length); - buffer->append('@'); - append_identifier(thd, buffer, definer_host->str, definer_host->length); + if (definer_host->str && definer_host->str[0]) + { + buffer->append('@'); + append_identifier(thd, buffer, definer_host->str, definer_host->length); + } buffer->append(' '); } -int -view_store_create_info(THD *thd, TABLE_LIST *table, String *buff) +static int show_create_view(THD *thd, TABLE_LIST *table, String *buff) { my_bool compact_view_name= TRUE; my_bool foreign_db_mode= (thd->variables.sql_mode & (MODE_POSTGRESQL | @@ -2085,7 +2432,8 @@ view_store_create_info(THD *thd, TABLE_LIST *table, String *buff) We can't just use table->query, because our SQL_MODE may trigger a different syntax, like when ANSI_QUOTES is defined. */ - table->view->unit.print(buff, QT_VIEW_INTERNAL); + table->view->unit.print(buff, enum_query_type(QT_VIEW_INTERNAL | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); if (table->with_check != VIEW_CHECK_NONE) { @@ -2105,26 +2453,21 @@ view_store_create_info(THD *thd, TABLE_LIST *table, String *buff) class thread_info :public ilink { public: - static void *operator new(size_t size) - { - return (void*) sql_alloc((uint) size); - } + static void *operator new(size_t size, MEM_ROOT *mem_root) throw () + { return alloc_root(mem_root, size); } static void operator delete(void *ptr __attribute__((unused)), size_t size __attribute__((unused))) { TRASH_FREE(ptr, size); } ulong thread_id; - time_t start_time; + uint32 os_thread_id; + ulonglong start_time; uint command; const char *user,*host,*db,*proc_info,*state_info; CSET_STRING query_string; double progress; }; -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class I_List<thread_info>; -#endif - static const char *thread_state_info(THD *tmp) { #ifndef EMBEDDED_LIBRARY @@ -2132,21 +2475,21 @@ static const char *thread_state_info(THD *tmp) { if (tmp->net.reading_or_writing == 2) return "Writing to net"; - else if (tmp->command == COM_SLEEP) + else if (tmp->get_command() == COM_SLEEP) return ""; else return "Reading from net"; } - else +#else + if (tmp->get_command() == COM_SLEEP) + return ""; #endif - { - if (tmp->proc_info) - return tmp->proc_info; - else if (tmp->mysys_var && tmp->mysys_var->current_cond) - return "Waiting on cond"; - else - return NULL; - } + if (tmp->proc_info) + return tmp->proc_info; + else if (tmp->mysys_var && tmp->mysys_var->current_cond) + return "Waiting on cond"; + else + return NULL; } void mysqld_list_processes(THD *thd,const char *user, bool verbose) @@ -2157,28 +2500,49 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) ulong max_query_length= (verbose ? thd->variables.max_allowed_packet : PROCESS_LIST_WIDTH); Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("mysqld_list_processes"); - field_list.push_back(new Item_int("Id", 0, MY_INT32_NUM_DECIMAL_DIGITS)); - field_list.push_back(new Item_empty_string("User", USERNAME_CHAR_LENGTH)); - field_list.push_back(new Item_empty_string("Host",LIST_PROCESS_HOST_LEN)); - field_list.push_back(field=new Item_empty_string("db",NAME_CHAR_LEN)); + field_list.push_back(new (mem_root) + Item_int(thd, "Id", 0, MY_INT32_NUM_DECIMAL_DIGITS), + mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "User", + USERNAME_CHAR_LENGTH), + mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Host", + LIST_PROCESS_HOST_LEN), + mem_root); + field_list.push_back(field=new (mem_root) + Item_empty_string(thd, "db", NAME_CHAR_LEN), + mem_root); field->maybe_null=1; - field_list.push_back(new Item_empty_string("Command",16)); - field_list.push_back(field= new Item_return_int("Time",7, MYSQL_TYPE_LONG)); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Command", 16), + mem_root); + field_list.push_back(field= new (mem_root) + Item_return_int(thd, "Time", 7, MYSQL_TYPE_LONG), + mem_root); field->unsigned_flag= 0; - field_list.push_back(field=new Item_empty_string("State",30)); + field_list.push_back(field=new (mem_root) + Item_empty_string(thd, "State", 30), + mem_root); field->maybe_null=1; - field_list.push_back(field=new Item_empty_string("Info",max_query_length)); + field_list.push_back(field=new (mem_root) + Item_empty_string(thd, "Info", max_query_length), + mem_root); field->maybe_null=1; if (!thd->variables.old_mode && !(thd->variables.old_behavior & OLD_MODE_NO_PROGRESS_INFO)) { - field_list.push_back(field= new Item_float("Progress", 0.0, 3, 7)); + field_list.push_back(field= new (mem_root) + Item_float(thd, "Progress", 0.0, 3, 7), + mem_root); field->maybe_null= 0; } if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) DBUG_VOID_RETURN; if (thd->killed) @@ -2195,9 +2559,10 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) (!user || (!tmp->system_thread && tmp_sctx->user && !strcmp(tmp_sctx->user, user)))) { - thread_info *thd_info= new thread_info; + thread_info *thd_info= new (thd->mem_root) thread_info; thd_info->thread_id=tmp->thread_id; + thd_info->os_thread_id=tmp->os_thread_id; thd_info->user= thd->strdup(tmp_sctx->user ? tmp_sctx->user : (tmp->system_thread ? "system user" : "unauthenticated user")); @@ -2212,7 +2577,7 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) thd_info->host= thd->strdup(tmp_sctx->host_or_ip[0] ? tmp_sctx->host_or_ip : tmp_sctx->host ? tmp_sctx->host : ""); - thd_info->command=(int) tmp->command; + thd_info->command=(int) tmp->get_command(); mysql_mutex_lock(&tmp->LOCK_thd_data); if ((thd_info->db= tmp->db)) // Safe test thd_info->db= thd->strdup(thd_info->db); @@ -2227,7 +2592,7 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) /* Lock THD mutex that protects its data when looking at it. */ if (tmp->query()) { - uint length= min(max_query_length, tmp->query_length()); + uint length= MY_MIN(max_query_length, tmp->query_length()); char *q= thd->strmake(tmp->query(),length); /* Safety: in case strmake failed, we set length to 0. */ thd_info->query_string= @@ -2240,7 +2605,7 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) */ if (tmp->progress.max_counter) { - uint max_stage= max(tmp->progress.max_stage, 1); + uint max_stage= MY_MAX(tmp->progress.max_stage, 1); thd_info->progress= (((tmp->progress.stage / (double) max_stage) + ((tmp->progress.counter / (double) tmp->progress.max_counter) / @@ -2250,7 +2615,10 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) } else thd_info->progress= 0.0; - thd_info->start_time= tmp->start_time; + thd_info->start_time= tmp->start_utime; + ulonglong utime_after_query_snapshot= tmp->utime_after_query; + if (thd_info->start_time < utime_after_query_snapshot) + thd_info->start_time= utime_after_query_snapshot; // COM_SLEEP mysql_mutex_unlock(&tmp->LOCK_thd_data); thread_infos.append(thd_info); } @@ -2258,7 +2626,7 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) mysql_mutex_unlock(&LOCK_thread_count); thread_info *thd_info; - time_t now= my_time(0); + ulonglong now= microsecond_interval_timer(); char buff[20]; // For progress String store_buffer(buff, sizeof(buff), system_charset_info); @@ -2273,8 +2641,8 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) protocol->store(thd_info->proc_info, system_charset_info); else protocol->store(command_name[thd_info->command].str, system_charset_info); - if (thd_info->start_time) - protocol->store_long ((longlong) (now - thd_info->start_time)); + if (thd_info->start_time && now > thd_info->start_time) + protocol->store_long((now - thd_info->start_time) / HRTIME_RESOLUTION); else protocol->store_null(); protocol->store(thd_info->state_info, system_charset_info); @@ -2290,12 +2658,267 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) DBUG_VOID_RETURN; } + +/* + Produce EXPLAIN data. + + This function is APC-scheduled to be run in the context of the thread that + we're producing EXPLAIN for. +*/ + +void Show_explain_request::call_in_target_thread() +{ + Query_arena backup_arena; + bool printed_anything= FALSE; + + /* + Change the arena because JOIN::print_explain and co. are going to allocate + items. Let them allocate them on our arena. + */ + target_thd->set_n_backup_active_arena((Query_arena*)request_thd, + &backup_arena); + + query_str.copy(target_thd->query(), + target_thd->query_length(), + target_thd->query_charset()); + + DBUG_ASSERT(current_thd == target_thd); + set_current_thd(request_thd); + if (target_thd->lex->print_explain(explain_buf, 0 /* explain flags*/, + false /*TODO: analyze? */, &printed_anything)) + { + failed_to_produce= TRUE; + } + set_current_thd(target_thd); + + if (!printed_anything) + failed_to_produce= TRUE; + + target_thd->restore_active_arena((Query_arena*)request_thd, &backup_arena); +} + + +int select_result_explain_buffer::send_data(List<Item> &items) +{ + int res; + THD *cur_thd= current_thd; + DBUG_ENTER("select_result_explain_buffer::send_data"); + + /* + Switch to the recieveing thread, so that we correctly count memory used + by it. This is needed as it's the receiving thread that will free the + memory. + */ + set_current_thd(thd); + fill_record(thd, dst_table, dst_table->field, items, TRUE, FALSE); + res= dst_table->file->ha_write_tmp_row(dst_table->record[0]); + set_current_thd(cur_thd); + DBUG_RETURN(MY_TEST(res)); +} + +bool select_result_text_buffer::send_result_set_metadata(List<Item> &fields, + uint flag) +{ + n_columns= fields.elements; + return append_row(fields, true /*send item names */); +} + + +int select_result_text_buffer::send_data(List<Item> &items) +{ + return append_row(items, false /*send item values */); +} + +int select_result_text_buffer::append_row(List<Item> &items, bool send_names) +{ + List_iterator<Item> it(items); + Item *item; + char **row; + int column= 0; + + if (!(row= (char**) thd->alloc(sizeof(char*) * n_columns)) || + rows.push_back(row, thd->mem_root)) + return true; + + while ((item= it++)) + { + DBUG_ASSERT(column < n_columns); + StringBuffer<32> buf; + const char *data_ptr; + char *ptr; + size_t data_len; + + if (send_names) + { + data_ptr= item->name; + data_len= strlen(item->name); + } + else + { + String *res; + res= item->val_str(&buf); + if (item->null_value) + { + data_ptr= "NULL"; + data_len=4; + } + else + { + data_ptr= res->c_ptr_safe(); + data_len= res->length(); + } + } + + if (!(ptr= (char*) thd->memdup(data_ptr, data_len + 1))) + return true; + row[column]= ptr; + + column++; + } + return false; +} + + +void select_result_text_buffer::save_to(String *res) +{ + List_iterator<char*> it(rows); + char **row; + res->append("#\n"); + while ((row= it++)) + { + res->append("# explain: "); + for (int i=0; i < n_columns; i++) + { + if (i) + res->append('\t'); + res->append(row[i]); + } + res->append("\n"); + } + res->append("#\n"); +} + + +/* + Store the SHOW EXPLAIN output in the temporary table. +*/ + +int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond) +{ + const char *calling_user; + THD *tmp; + my_thread_id thread_id; + DBUG_ENTER("fill_show_explain"); + + DBUG_ASSERT(cond==NULL); + thread_id= thd->lex->value_list.head()->val_int(); + calling_user= (thd->security_ctx->master_access & PROCESS_ACL) ? NullS : + thd->security_ctx->priv_user; + + if ((tmp= find_thread_by_id(thread_id))) + { + Security_context *tmp_sctx= tmp->security_ctx; + /* + If calling_user==NULL, calling thread has SUPER or PROCESS + privilege, and so can do SHOW EXPLAIN on any user. + + if calling_user!=NULL, he's only allowed to view SHOW EXPLAIN on + his own threads. + */ + if (calling_user && (!tmp_sctx->user || strcmp(calling_user, + tmp_sctx->user))) + { + my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESS"); + mysql_mutex_unlock(&tmp->LOCK_thd_data); + DBUG_RETURN(1); + } + + if (tmp == thd) + { + mysql_mutex_unlock(&tmp->LOCK_thd_data); + my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0)); + DBUG_RETURN(1); + } + + bool bres; + /* + Ok we've found the thread of interest and it won't go away because + we're holding its LOCK_thd data. Post it a SHOW EXPLAIN request. + */ + bool timed_out; + int timeout_sec= 30; + Show_explain_request explain_req; + select_result_explain_buffer *explain_buf; + + explain_buf= new select_result_explain_buffer(thd, table->table); + + explain_req.explain_buf= explain_buf; + explain_req.target_thd= tmp; + explain_req.request_thd= thd; + explain_req.failed_to_produce= FALSE; + + /* Ok, we have a lock on target->LOCK_thd_data, can call: */ + bres= tmp->apc_target.make_apc_call(thd, &explain_req, timeout_sec, &timed_out); + + if (bres || explain_req.failed_to_produce) + { + if (thd->killed) + thd->send_kill_message(); + else if (timed_out) + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + else + my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0)); + + bres= TRUE; + } + else + { + /* + Push the query string as a warning. The query may be in a different + charset than the charset that's used for error messages, so, convert it + if needed. + */ + CHARSET_INFO *fromcs= explain_req.query_str.charset(); + CHARSET_INFO *tocs= error_message_charset_info; + char *warning_text; + if (!my_charset_same(fromcs, tocs)) + { + uint conv_length= 1 + tocs->mbmaxlen * explain_req.query_str.length() / + fromcs->mbminlen; + uint dummy_errors; + char *to, *p; + if (!(to= (char*)thd->alloc(conv_length + 1))) + DBUG_RETURN(1); + p= to; + p+= copy_and_convert(to, conv_length, tocs, + explain_req.query_str.c_ptr(), + explain_req.query_str.length(), fromcs, + &dummy_errors); + *p= 0; + warning_text= to; + } + else + warning_text= explain_req.query_str.c_ptr_safe(); + + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_YES, warning_text); + } + DBUG_RETURN(bres); + } + else + { + my_error(ER_NO_SUCH_THREAD, MYF(0), thread_id); + DBUG_RETURN(1); + } +} + + int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) { TABLE *table= tables->table; CHARSET_INFO *cs= system_charset_info; char *user; - my_hrtime_t unow= my_hrtime(); + ulonglong unow= microsecond_interval_timer(); DBUG_ENTER("fill_schema_processlist"); DEBUG_SYNC(thd,"fill_schema_processlist_after_unow"); @@ -2356,12 +2979,15 @@ int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) "Killed" : 0)))) table->field[4]->store(val, strlen(val), cs); else - table->field[4]->store(command_name[tmp->command].str, - command_name[tmp->command].length, cs); + table->field[4]->store(command_name[tmp->get_command()].str, + command_name[tmp->get_command()].length, cs); /* MYSQL_TIME */ - ulonglong start_utime= tmp->start_time * HRTIME_RESOLUTION + tmp->start_time_sec_part; - ulonglong utime= start_utime && start_utime < unow.val - ? unow.val - start_utime : 0; + ulonglong utime= tmp->start_utime; + ulonglong utime_after_query_snapshot= tmp->utime_after_query; + if (utime < utime_after_query_snapshot) + utime= utime_after_query_snapshot; // COM_SLEEP + utime= utime && utime < unow ? unow - utime : 0; + table->field[5]->store(utime / HRTIME_RESOLUTION, TRUE); /* STATE */ if ((val= thread_state_info(tmp))) @@ -2383,11 +3009,20 @@ int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) if (tmp->query()) { table->field[7]->store(tmp->query(), - min(PROCESS_LIST_INFO_WIDTH, + MY_MIN(PROCESS_LIST_INFO_WIDTH, tmp->query_length()), cs); table->field[7]->set_notnull(); } + /* INFO_BINARY */ + if (tmp->query()) + { + table->field[15]->store(tmp->query(), + MY_MIN(PROCESS_LIST_INFO_WIDTH, + tmp->query_length()), &my_charset_bin); + table->field[15]->set_notnull(); + } + /* Progress report. We need to do this under a lock to ensure that all is from the same stage. @@ -2401,6 +3036,22 @@ int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) } mysql_mutex_unlock(&tmp->LOCK_thd_data); + /* + This may become negative if we free a memory allocated by another + thread in this thread. However it's better that we notice it eventually + than hide it. + */ + table->field[12]->store((longlong) tmp->status_var.local_memory_used, + FALSE); + table->field[12]->set_notnull(); + table->field[13]->store((longlong) tmp->get_examined_row_count(), TRUE); + table->field[13]->set_notnull(); + + /* QUERY_ID */ + table->field[14]->store(tmp->query_id, TRUE); + + table->field[16]->store(tmp->os_thread_id); + if (schema_table_store_record(thd, table)) { mysql_mutex_unlock(&LOCK_thread_count); @@ -2423,7 +3074,7 @@ static bool status_vars_inited= 0; C_MODE_START static int show_var_cmp(const void *var1, const void *var2) { - return strcmp(((SHOW_VAR*)var1)->name, ((SHOW_VAR*)var2)->name); + return strcasecmp(((SHOW_VAR*)var1)->name, ((SHOW_VAR*)var2)->name); } C_MODE_END @@ -2471,9 +3122,9 @@ int add_status_vars(SHOW_VAR *list) { int res= 0; if (status_vars_inited) - mysql_mutex_lock(&LOCK_status); + mysql_mutex_lock(&LOCK_show_status); if (!all_status_vars.buffer && // array is not allocated yet - do it now - my_init_dynamic_array(&all_status_vars, sizeof(SHOW_VAR), 200, 20)) + my_init_dynamic_array(&all_status_vars, sizeof(SHOW_VAR), 200, 20, MYF(0))) { res= 1; goto err; @@ -2486,7 +3137,7 @@ int add_status_vars(SHOW_VAR *list) sort_dynamic(&all_status_vars, show_var_cmp); err: if (status_vars_inited) - mysql_mutex_unlock(&LOCK_status); + mysql_mutex_unlock(&LOCK_show_status); return res; } @@ -2520,7 +3171,7 @@ void reset_status_vars() catch-all cleanup function, cleans up everything no matter what DESCRIPTION - This function is not strictly required if all add_to_status/ + This function is not strictly required if all add_status_vars/ remove_status_vars are properly paired, but it's a safety measure that deletes everything from the all_status_vars[] even if some remove_status_vars were forgotten @@ -2548,7 +3199,7 @@ void remove_status_vars(SHOW_VAR *list) { if (status_vars_inited) { - mysql_mutex_lock(&LOCK_status); + mysql_mutex_lock(&LOCK_show_status); SHOW_VAR *all= dynamic_element(&all_status_vars, 0, SHOW_VAR *); for (; list->name; list++) @@ -2569,7 +3220,7 @@ void remove_status_vars(SHOW_VAR *list) } } shrink_var_array(&all_status_vars); - mysql_mutex_unlock(&LOCK_status); + mysql_mutex_unlock(&LOCK_show_status); } else { @@ -2590,10 +3241,9 @@ void remove_status_vars(SHOW_VAR *list) } - static bool show_status_array(THD *thd, const char *wild, SHOW_VAR *variables, - enum enum_var_type value_type, + enum enum_var_type scope, struct system_status_var *status_var, const char *prefix, TABLE *table, bool ucase_names, @@ -2602,10 +3252,8 @@ static bool show_status_array(THD *thd, const char *wild, my_aligned_storage<SHOW_VAR_FUNC_BUFF_SIZE, MY_ALIGNOF(long)> buffer; char * const buff= buffer.data; char *prefix_end; - /* the variable name should not be longer than 64 characters */ - char name_buffer[64]; + char name_buffer[NAME_CHAR_LEN]; int len; - LEX_STRING null_lex_str; SHOW_VAR tmp, *var; enum_check_fields save_count_cuted_fields= thd->count_cuted_fields; bool res= FALSE; @@ -2613,52 +3261,107 @@ static bool show_status_array(THD *thd, const char *wild, DBUG_ENTER("show_status_array"); thd->count_cuted_fields= CHECK_FIELD_WARN; - null_lex_str.str= 0; // For sys_var->value_ptr() - null_lex_str.length= 0; prefix_end=strnmov(name_buffer, prefix, sizeof(name_buffer)-1); if (*prefix) *prefix_end++= '_'; len=name_buffer + sizeof(name_buffer) - prefix_end; +#ifdef WITH_WSREP + bool is_wsrep_var= FALSE; + /* + This is a workaround for lp:1306875 (PBX) to skip switching of wsrep + status variable name's first letter to uppercase. This is an optimization + for status variables defined under wsrep plugin. + TODO: remove once lp:1306875 has been addressed. + */ + if (*prefix && !my_strcasecmp(system_charset_info, prefix, "wsrep")) + { + is_wsrep_var= TRUE; + } +#endif /* WITH_WSREP */ + for (; variables->name; variables++) { + bool wild_checked= 0; strnmov(prefix_end, variables->name, len); name_buffer[sizeof(name_buffer)-1]=0; /* Safety */ + +#ifdef WITH_WSREP + /* + If the prefix is NULL, that means we are looking into the status variables + defined directly under mysqld.cc. Do not capitalize wsrep status variable + names until lp:1306875 has been fixed. + TODO: remove once lp:1306875 has been addressed. + */ + if (!(*prefix) && !strncasecmp(name_buffer, "wsrep", strlen("wsrep"))) + { + is_wsrep_var= TRUE; + } +#endif /* WITH_WSREP */ + if (ucase_names) my_caseup_str(system_charset_info, name_buffer); + else + { + my_casedn_str(system_charset_info, name_buffer); + DBUG_ASSERT(name_buffer[0] >= 'a'); + DBUG_ASSERT(name_buffer[0] <= 'z'); + + // WSREP_TODO: remove once lp:1306875 has been addressed. + if (IF_WSREP(is_wsrep_var == FALSE, 1) && + status_var) + name_buffer[0]-= 'a' - 'A'; + } + restore_record(table, s->default_values); table->field[0]->store(name_buffer, strlen(name_buffer), system_charset_info); + /* - if var->type is SHOW_FUNC, call the function. - Repeat as necessary, if new var is again SHOW_FUNC + Compare name for types that can't return arrays. We do this to not + calculate the value for function variables that we will not access */ - for (var=variables; var->type == SHOW_FUNC; var= &tmp) - ((mysql_show_var_func)(var->value))(thd, &tmp, buff); + if ((variables->type != SHOW_FUNC && variables->type != SHOW_ARRAY)) + { + if (wild && wild[0] && wild_case_compare(system_charset_info, + name_buffer, wild)) + continue; + wild_checked= 1; // Avoid checking it again + } + /* + if var->type is SHOW_FUNC or SHOW_SIMPLE_FUNC, call the function. + Repeat as necessary, if new var is again one of the above + */ + for (var=variables; var->type == SHOW_FUNC || + var->type == SHOW_SIMPLE_FUNC; var= &tmp) + ((mysql_show_var_func)(var->value))(thd, &tmp, buff, + status_var, scope); + SHOW_TYPE show_type=var->type; if (show_type == SHOW_ARRAY) { - show_status_array(thd, wild, (SHOW_VAR *) var->value, value_type, + show_status_array(thd, wild, (SHOW_VAR *) var->value, scope, status_var, name_buffer, table, ucase_names, cond); } else { - if (!(wild && wild[0] && wild_case_compare(system_charset_info, - name_buffer, wild)) && + if ((wild_checked || + (wild && wild[0] && wild_case_compare(system_charset_info, + name_buffer, wild))) && (!cond || cond->val_int())) { - char *value=var->value; + void *value=var->value; const char *pos, *end; // We assign a lot of const's if (show_type == SHOW_SYS) { - sys_var *var= ((sys_var *) value); + sys_var *var= (sys_var *) value; show_type= var->show_type(); mysql_mutex_lock(&LOCK_global_system_variables); - value= (char*) var->value_ptr(thd, value_type, &null_lex_str); + value= var->value_ptr(thd, scope, &null_lex_str); charset= var->charset(thd); } @@ -2718,7 +3421,7 @@ static bool show_status_array(THD *thd, const char *wild, } case SHOW_CHAR: { - if (!(pos= value)) + if (!(pos= (char*)value)) pos= ""; end= strend(pos); break; @@ -2728,13 +3431,6 @@ static bool show_status_array(THD *thd, const char *wild, if (!(pos= *(char**) value)) pos= ""; - DBUG_EXECUTE_IF("alter_server_version_str", - if (!my_strcasecmp(system_charset_info, - variables->name, - "version")) { - pos= "some-other-version"; - }); - end= strend(pos); break; } @@ -2755,7 +3451,6 @@ static bool show_status_array(THD *thd, const char *wild, break; } table->field[1]->store(pos, (uint32) (end - pos), charset); - thd->count_cuted_fields= CHECK_FIELD_IGNORE; table->field[1]->set_notnull(); if (var->type == SHOW_SYS) @@ -2766,6 +3461,7 @@ static bool show_status_array(THD *thd, const char *wild, res= TRUE; goto end; } + thd->get_stmt_da()->inc_current_row_for_warning(); } } } @@ -2774,326 +3470,14 @@ end: DBUG_RETURN(res); } -#ifdef COMPLETE_PATCH_NOT_ADDED_YET -/* - Aggregate values for mapped_user entries by their role. - - SYNOPSIS - aggregate_user_stats - all_user_stats - input to aggregate - agg_user_stats - returns aggregated values - - RETURN - 0 - OK - 1 - error -*/ - -static int aggregate_user_stats(HASH *all_user_stats, HASH *agg_user_stats) -{ - DBUG_ENTER("aggregate_user_stats"); - if (my_hash_init(agg_user_stats, system_charset_info, - max(all_user_stats->records, 1), - 0, 0, (my_hash_get_key)get_key_user_stats, - (my_hash_free_key)free_user_stats, 0)) - { - sql_print_error("Malloc in aggregate_user_stats failed"); - DBUG_RETURN(1); - } - - for (uint i= 0; i < all_user_stats->records; i++) - { - USER_STATS *user= (USER_STATS*)my_hash_element(all_user_stats, i); - USER_STATS *agg_user; - uint name_length= strlen(user->priv_user); - - if (!(agg_user= (USER_STATS*) my_hash_search(agg_user_stats, - (uchar*)user->priv_user, - name_length))) - { - // First entry for this role. - if (!(agg_user= (USER_STATS*) my_malloc(sizeof(USER_STATS), - MYF(MY_WME | MY_ZEROFILL)))) - { - sql_print_error("Malloc in aggregate_user_stats failed"); - DBUG_RETURN(1); - } - - init_user_stats(agg_user, user->priv_user, name_length, - user->priv_user, - user->total_connections, user->concurrent_connections, - user->connected_time, user->busy_time, user->cpu_time, - user->bytes_received, user->bytes_sent, - user->binlog_bytes_written, - user->rows_sent, user->rows_read, - user->rows_inserted, user->rows_deleted, - user->rows_updated, - user->select_commands, user->update_commands, - user->other_commands, - user->commit_trans, user->rollback_trans, - user->denied_connections, user->lost_connections, - user->access_denied_errors, user->empty_queries); - - if (my_hash_insert(agg_user_stats, (uchar*) agg_user)) - { - /* Out of memory */ - my_free(agg_user, 0); - sql_print_error("Malloc in aggregate_user_stats failed"); - DBUG_RETURN(1); - } - } - else - { - /* Aggregate with existing values for this role. */ - add_user_stats(agg_user, - user->total_connections, user->concurrent_connections, - user->connected_time, user->busy_time, user->cpu_time, - user->bytes_received, user->bytes_sent, - user->binlog_bytes_written, - user->rows_sent, user->rows_read, - user->rows_inserted, user->rows_deleted, - user->rows_updated, - user->select_commands, user->update_commands, - user->other_commands, - user->commit_trans, user->rollback_trans, - user->denied_connections, user->lost_connections, - user->access_denied_errors, user->empty_queries); - } - } - DBUG_PRINT("exit", ("aggregated %lu input into %lu output entries", - all_user_stats->records, agg_user_stats->records)); - DBUG_RETURN(0); -} -#endif - -/* - Write result to network for SHOW USER_STATISTICS - - SYNOPSIS - send_user_stats - all_user_stats - values to return - table - I_S table - - RETURN - 0 - OK - 1 - error -*/ - -int send_user_stats(THD* thd, HASH *all_user_stats, TABLE *table) -{ - DBUG_ENTER("send_user_stats"); - - for (uint i= 0; i < all_user_stats->records; i++) - { - uint j= 0; - USER_STATS *user_stats= (USER_STATS*) my_hash_element(all_user_stats, i); - - table->field[j++]->store(user_stats->user, user_stats->user_name_length, - system_charset_info); - table->field[j++]->store((longlong)user_stats->total_connections,TRUE); - table->field[j++]->store((longlong)user_stats->concurrent_connections, TRUE); - table->field[j++]->store((longlong)user_stats->connected_time, TRUE); - table->field[j++]->store((double)user_stats->busy_time); - table->field[j++]->store((double)user_stats->cpu_time); - table->field[j++]->store((longlong)user_stats->bytes_received, TRUE); - table->field[j++]->store((longlong)user_stats->bytes_sent, TRUE); - table->field[j++]->store((longlong)user_stats->binlog_bytes_written, TRUE); - table->field[j++]->store((longlong)user_stats->rows_read, TRUE); - table->field[j++]->store((longlong)user_stats->rows_sent, TRUE); - table->field[j++]->store((longlong)user_stats->rows_deleted, TRUE); - table->field[j++]->store((longlong)user_stats->rows_inserted, TRUE); - table->field[j++]->store((longlong)user_stats->rows_updated, TRUE); - table->field[j++]->store((longlong)user_stats->select_commands, TRUE); - table->field[j++]->store((longlong)user_stats->update_commands, TRUE); - table->field[j++]->store((longlong)user_stats->other_commands, TRUE); - table->field[j++]->store((longlong)user_stats->commit_trans, TRUE); - table->field[j++]->store((longlong)user_stats->rollback_trans, TRUE); - table->field[j++]->store((longlong)user_stats->denied_connections, TRUE); - table->field[j++]->store((longlong)user_stats->lost_connections, TRUE); - table->field[j++]->store((longlong)user_stats->access_denied_errors, TRUE); - table->field[j++]->store((longlong)user_stats->empty_queries, TRUE); - if (schema_table_store_record(thd, table)) - { - DBUG_PRINT("error", ("store record error")); - DBUG_RETURN(1); - } - } - DBUG_RETURN(0); -} - /* - Process SHOW USER_STATISTICS - - SYNOPSIS - mysqld_show_user_stats - thd - current thread - wild - limit results to the entry for this user - with_roles - when true, display role for mapped users - - RETURN - 0 - OK - 1 - error + collect status for all running threads + Return number of threads used */ -int fill_schema_user_stats(THD* thd, TABLE_LIST* tables, COND* cond) -{ - TABLE *table= tables->table; - int result; - DBUG_ENTER("fill_schema_user_stats"); - - if (check_global_access(thd, SUPER_ACL | PROCESS_ACL, true)) - DBUG_RETURN(0); - - /* - Iterates through all the global stats and sends them to the client. - Pattern matching on the client IP is supported. - */ - - mysql_mutex_lock(&LOCK_global_user_client_stats); - result= send_user_stats(thd, &global_user_stats, table) != 0; - mysql_mutex_unlock(&LOCK_global_user_client_stats); - - DBUG_PRINT("exit", ("result: %d", result)); - DBUG_RETURN(result); -} - -/* - Process SHOW CLIENT_STATISTICS - - SYNOPSIS - mysqld_show_client_stats - thd - current thread - wild - limit results to the entry for this client - - RETURN - 0 - OK - 1 - error -*/ - -int fill_schema_client_stats(THD* thd, TABLE_LIST* tables, COND* cond) -{ - TABLE *table= tables->table; - int result; - DBUG_ENTER("fill_schema_client_stats"); - - if (check_global_access(thd, SUPER_ACL | PROCESS_ACL, true)) - DBUG_RETURN(0); - - /* - Iterates through all the global stats and sends them to the client. - Pattern matching on the client IP is supported. - */ - - mysql_mutex_lock(&LOCK_global_user_client_stats); - result= send_user_stats(thd, &global_client_stats, table) != 0; - mysql_mutex_unlock(&LOCK_global_user_client_stats); - - DBUG_PRINT("exit", ("result: %d", result)); - DBUG_RETURN(result); -} - - -/* Fill information schema table with table statistics */ - -int fill_schema_table_stats(THD *thd, TABLE_LIST *tables, COND *cond) -{ - TABLE *table= tables->table; - DBUG_ENTER("fill_schema_table_stats"); - - mysql_mutex_lock(&LOCK_global_table_stats); - for (uint i= 0; i < global_table_stats.records; i++) - { - char *end_of_schema; - TABLE_STATS *table_stats= - (TABLE_STATS*)my_hash_element(&global_table_stats, i); - TABLE_LIST tmp_table; - size_t schema_length, table_name_length; - - end_of_schema= strend(table_stats->table); - schema_length= (size_t) (end_of_schema - table_stats->table); - table_name_length= strlen(table_stats->table + schema_length + 1); - - bzero((char*) &tmp_table,sizeof(tmp_table)); - tmp_table.db= table_stats->table; - tmp_table.table_name= end_of_schema+1; - tmp_table.grant.privilege= 0; - if (check_access(thd, SELECT_ACL, tmp_table.db, - &tmp_table.grant.privilege, NULL, 0, 1) || - check_grant(thd, SELECT_ACL, &tmp_table, 1, UINT_MAX, - 1)) - continue; - - table->field[0]->store(table_stats->table, schema_length, - system_charset_info); - table->field[1]->store(table_stats->table + schema_length+1, - table_name_length, system_charset_info); - table->field[2]->store((longlong)table_stats->rows_read, TRUE); - table->field[3]->store((longlong)table_stats->rows_changed, TRUE); - table->field[4]->store((longlong)table_stats->rows_changed_x_indexes, - TRUE); - if (schema_table_store_record(thd, table)) - { - mysql_mutex_unlock(&LOCK_global_table_stats); - DBUG_RETURN(1); - } - } - mysql_mutex_unlock(&LOCK_global_table_stats); - DBUG_RETURN(0); -} - - -/* Fill information schema table with index statistics */ - -int fill_schema_index_stats(THD *thd, TABLE_LIST *tables, COND *cond) -{ - TABLE *table= tables->table; - DBUG_ENTER("fill_schema_index_stats"); - - mysql_mutex_lock(&LOCK_global_index_stats); - for (uint i= 0; i < global_index_stats.records; i++) - { - INDEX_STATS *index_stats = - (INDEX_STATS*) my_hash_element(&global_index_stats, i); - TABLE_LIST tmp_table; - char *index_name; - size_t schema_name_length, table_name_length, index_name_length; - - bzero((char*) &tmp_table,sizeof(tmp_table)); - tmp_table.db= index_stats->index; - tmp_table.table_name= strend(index_stats->index)+1; - tmp_table.grant.privilege= 0; - if (check_access(thd, SELECT_ACL, tmp_table.db, - &tmp_table.grant.privilege, NULL, 0, 1) || - check_grant(thd, SELECT_ACL, &tmp_table, 1, UINT_MAX, 1)) - continue; - - index_name= strend(tmp_table.table_name)+1; - schema_name_length= (tmp_table.table_name - index_stats->index) -1; - table_name_length= (index_name - tmp_table.table_name)-1; - index_name_length= (index_stats->index_name_length - schema_name_length - - table_name_length - 3); - - table->field[0]->store(tmp_table.db, schema_name_length, - system_charset_info); - table->field[1]->store(tmp_table.table_name, table_name_length, - system_charset_info); - table->field[2]->store(index_name, index_name_length, system_charset_info); - table->field[3]->store((longlong)index_stats->rows_read, TRUE); - - if (schema_table_store_record(thd, table)) - { - mysql_mutex_unlock(&LOCK_global_index_stats); - DBUG_RETURN(1); - } - } - mysql_mutex_unlock(&LOCK_global_index_stats); - DBUG_RETURN(0); -} - - -/* collect status for all running threads */ - -void calc_sum_of_all_status(STATUS_VAR *to) +uint calc_sum_of_all_status(STATUS_VAR *to) { + uint count= 0; DBUG_ENTER("calc_sum_of_all_status"); /* Ensure that thread id not killed during loop */ @@ -3104,57 +3488,27 @@ void calc_sum_of_all_status(STATUS_VAR *to) /* Get global values as base */ *to= global_status_var; + to->local_memory_used= 0; /* Add to this status from existing threads */ while ((tmp= it++)) { + count++; if (!tmp->status_in_global) + { add_to_status(to, &tmp->status_var); + to->local_memory_used+= tmp->status_var.local_memory_used; + } } mysql_mutex_unlock(&LOCK_thread_count); - DBUG_VOID_RETURN; + DBUG_RETURN(count); } /* This is only used internally, but we need it here as a forward reference */ extern ST_SCHEMA_TABLE schema_tables[]; -/** - Condition pushdown used for INFORMATION_SCHEMA / SHOW queries. - This structure is to implement an optimization when - accessing data dictionary data in the INFORMATION_SCHEMA - or SHOW commands. - When the query contain a TABLE_SCHEMA or TABLE_NAME clause, - narrow the search for data based on the constraints given. -*/ -typedef struct st_lookup_field_values -{ - /** - Value of a TABLE_SCHEMA clause. - Note that this value length may exceed @c NAME_LEN. - @sa wild_db_value - */ - LEX_STRING db_value; - /** - Value of a TABLE_NAME clause. - Note that this value length may exceed @c NAME_LEN. - @sa wild_table_value - */ - LEX_STRING table_value; - /** - True when @c db_value is a LIKE clause, - false when @c db_value is an '=' clause. - */ - bool wild_db_value; - /** - True when @c table_value is a LIKE clause, - false when @c table_value is an '=' clause. - */ - bool wild_table_value; -} LOOKUP_FIELD_VALUES; - - /* Store record to I_S table, convert HEAP table to MyISAM if necessary @@ -3172,6 +3526,13 @@ typedef struct st_lookup_field_values bool schema_table_store_record(THD *thd, TABLE *table) { int error; + + if (thd->killed) + { + thd->send_kill_message(); + return 1; + } + if ((error= table->file->ha_write_tmp_row(table->record[0]))) { TMP_TABLE_PARAM *param= table->pos_in_table_list->schema_table_param; @@ -3262,8 +3623,8 @@ bool get_lookup_value(THD *thd, Item_func *item_func, (uchar *) item_field->field_name, strlen(item_field->field_name), 0)) { - thd->make_lex_string(&lookup_field_vals->db_value, tmp_str->ptr(), - tmp_str->length(), FALSE); + thd->make_lex_string(&lookup_field_vals->db_value, + tmp_str->ptr(), tmp_str->length()); } /* Lookup value is table name */ else if (!cs->coll->strnncollsp(cs, (uchar *) field_name2, @@ -3271,8 +3632,8 @@ bool get_lookup_value(THD *thd, Item_func *item_func, (uchar *) item_field->field_name, strlen(item_field->field_name), 0)) { - thd->make_lex_string(&lookup_field_vals->table_value, tmp_str->ptr(), - tmp_str->length(), FALSE); + thd->make_lex_string(&lookup_field_vals->table_value, + tmp_str->ptr(), tmp_str->length()); } } return 0; @@ -3342,6 +3703,15 @@ bool uses_only_table_name_fields(Item *item, TABLE_LIST *table) return 0; } } + else if (item->type() == Item::ROW_ITEM) + { + Item_row *item_row= static_cast<Item_row*>(item); + for (uint i= 0; i < item_row->cols(); i++) + { + if (!uses_only_table_name_fields(item_row->element_index(i), table)) + return 0; + } + } else if (item->type() == Item::FIELD_ITEM) { Item_field *item_field= (Item_field*)item; @@ -3361,6 +3731,11 @@ bool uses_only_table_name_fields(Item *item, TABLE_LIST *table) strlen(item_field->field_name), 0))) return 0; } + else if (item->type() == Item::EXPR_CACHE_ITEM) + { + Item_cache_wrapper *tmp= static_cast<Item_cache_wrapper*>(item); + return uses_only_table_name_fields(tmp->get_orig_item(), table); + } else if (item->type() == Item::REF_ITEM) return uses_only_table_name_fields(item->real_item(), table); @@ -3371,7 +3746,7 @@ bool uses_only_table_name_fields(Item *item, TABLE_LIST *table) } -static COND * make_cond_for_info_schema(COND *cond, TABLE_LIST *table) +COND *make_cond_for_info_schema(THD *thd, COND *cond, TABLE_LIST *table) { if (!cond) return (COND*) 0; @@ -3380,16 +3755,16 @@ static COND * make_cond_for_info_schema(COND *cond, TABLE_LIST *table) if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) { /* Create new top level AND item */ - Item_cond_and *new_cond=new Item_cond_and; + Item_cond_and *new_cond=new (thd->mem_root) Item_cond_and(thd); if (!new_cond) return (COND*) 0; List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) { - Item *fix= make_cond_for_info_schema(item, table); + Item *fix= make_cond_for_info_schema(thd, item, table); if (fix) - new_cond->argument_list()->push_back(fix); + new_cond->argument_list()->push_back(fix, thd->mem_root); } switch (new_cond->argument_list()->elements) { case 0: @@ -3403,17 +3778,17 @@ static COND * make_cond_for_info_schema(COND *cond, TABLE_LIST *table) } else { // Or list - Item_cond_or *new_cond=new Item_cond_or; + Item_cond_or *new_cond= new (thd->mem_root) Item_cond_or(thd); if (!new_cond) return (COND*) 0; List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) { - Item *fix=make_cond_for_info_schema(item, table); + Item *fix=make_cond_for_info_schema(thd, item, table); if (!fix) return (COND*) 0; - new_cond->argument_list()->push_back(fix); + new_cond->argument_list()->push_back(fix, thd->mem_root); } new_cond->quick_fix_field(); new_cond->top_level_item(); @@ -3449,16 +3824,25 @@ bool get_lookup_field_values(THD *thd, COND *cond, TABLE_LIST *tables, LOOKUP_FIELD_VALUES *lookup_field_values) { LEX *lex= thd->lex; - const char *wild= lex->wild ? lex->wild->ptr() : NullS; + String *wild= lex->wild; bool rc= 0; bzero((char*) lookup_field_values, sizeof(LOOKUP_FIELD_VALUES)); switch (lex->sql_command) { + case SQLCOM_SHOW_PLUGINS: + if (lex->ident.str) + { + thd->make_lex_string(&lookup_field_values->db_value, + lex->ident.str, lex->ident.length); + break; + } + /* fall through */ + case SQLCOM_SHOW_GENERIC: case SQLCOM_SHOW_DATABASES: if (wild) { - thd->make_lex_string(&lookup_field_values->db_value, - wild, strlen(wild), 0); + thd->make_lex_string(&lookup_field_values->db_value, + wild->ptr(), wild->length()); lookup_field_values->wild_db_value= 1; } break; @@ -3467,11 +3851,11 @@ bool get_lookup_field_values(THD *thd, COND *cond, TABLE_LIST *tables, case SQLCOM_SHOW_TRIGGERS: case SQLCOM_SHOW_EVENTS: thd->make_lex_string(&lookup_field_values->db_value, - lex->select_lex.db, strlen(lex->select_lex.db), 0); + lex->select_lex.db, strlen(lex->select_lex.db)); if (wild) { thd->make_lex_string(&lookup_field_values->table_value, - wild, strlen(wild), 0); + wild->ptr(), wild->length()); lookup_field_values->wild_table_value= 1; } break; @@ -3517,23 +3901,15 @@ enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table) wild wild string idx_field_vals idx_field_vals->db_name contains db name or wild string - with_i_schema returns 1 if we added 'IS' name to list - otherwise returns 0 RETURN zero success non-zero error */ -int make_db_list(THD *thd, List<LEX_STRING> *files, - LOOKUP_FIELD_VALUES *lookup_field_vals, - bool *with_i_schema) +int make_db_list(THD *thd, Dynamic_array<LEX_STRING*> *files, + LOOKUP_FIELD_VALUES *lookup_field_vals) { - LEX_STRING *i_s_name_copy= 0; - i_s_name_copy= thd->make_lex_string(i_s_name_copy, - INFORMATION_SCHEMA_NAME.str, - INFORMATION_SCHEMA_NAME.length, TRUE); - *with_i_schema= 0; if (lookup_field_vals->wild_db_value) { /* @@ -3546,12 +3922,11 @@ int make_db_list(THD *thd, List<LEX_STRING> *files, INFORMATION_SCHEMA_NAME.str, lookup_field_vals->db_value.str)) { - *with_i_schema= 1; - if (files->push_back(i_s_name_copy)) + if (files->append_val(&INFORMATION_SCHEMA_NAME)) return 1; } - return (find_files(thd, files, NullS, mysql_data_home, - lookup_field_vals->db_value.str, 1) != FIND_FILES_OK); + return find_files(thd, files, 0, mysql_data_home, + &lookup_field_vals->db_value); } @@ -3575,12 +3950,11 @@ int make_db_list(THD *thd, List<LEX_STRING> *files, if (is_infoschema_db(lookup_field_vals->db_value.str, lookup_field_vals->db_value.length)) { - *with_i_schema= 1; - if (files->push_back(i_s_name_copy)) + if (files->append_val(&INFORMATION_SCHEMA_NAME)) return 1; return 0; } - if (files->push_back(&lookup_field_vals->db_value)) + if (files->append_val(&lookup_field_vals->db_value)) return 1; return 0; } @@ -3589,17 +3963,15 @@ int make_db_list(THD *thd, List<LEX_STRING> *files, Create list of existing databases. It is used in case of select from information schema table */ - if (files->push_back(i_s_name_copy)) + if (files->append_val(&INFORMATION_SCHEMA_NAME)) return 1; - *with_i_schema= 1; - return (find_files(thd, files, NullS, - mysql_data_home, NullS, 1) != FIND_FILES_OK); + return find_files(thd, files, 0, mysql_data_home, &null_lex_str); } struct st_add_schema_table { - List<LEX_STRING> *files; + Dynamic_array<LEX_STRING*> *files; const char *wild; }; @@ -3609,7 +3981,7 @@ static my_bool add_schema_table(THD *thd, plugin_ref plugin, { LEX_STRING *file_name= 0; st_add_schema_table *data= (st_add_schema_table *)p_data; - List<LEX_STRING> *file_list= data->files; + Dynamic_array<LEX_STRING*> *file_list= data->files; const char *wild= data->wild; ST_SCHEMA_TABLE *schema_table= plugin_data(plugin, ST_SCHEMA_TABLE *); DBUG_ENTER("add_schema_table"); @@ -3629,16 +4001,16 @@ static my_bool add_schema_table(THD *thd, plugin_ref plugin, DBUG_RETURN(0); } - if ((file_name= thd->make_lex_string(file_name, schema_table->table_name, - strlen(schema_table->table_name), - TRUE)) && - !file_list->push_back(file_name)) + if ((file_name= thd->make_lex_string(schema_table->table_name, + strlen(schema_table->table_name))) && + !file_list->append(file_name)) DBUG_RETURN(0); DBUG_RETURN(1); } -int schema_tables_add(THD *thd, List<LEX_STRING> *files, const char *wild) +int schema_tables_add(THD *thd, Dynamic_array<LEX_STRING*> *files, + const char *wild) { LEX_STRING *file_name= 0; ST_SCHEMA_TABLE *tmp_schema_table= schema_tables; @@ -3662,9 +4034,9 @@ int schema_tables_add(THD *thd, List<LEX_STRING> *files, const char *wild) continue; } if ((file_name= - thd->make_lex_string(file_name, tmp_schema_table->table_name, - strlen(tmp_schema_table->table_name), TRUE)) && - !files->push_back(file_name)) + thd->make_lex_string(tmp_schema_table->table_name, + strlen(tmp_schema_table->table_name))) && + !files->append(file_name)) continue; DBUG_RETURN(1); } @@ -3689,7 +4061,6 @@ int schema_tables_add(THD *thd, List<LEX_STRING> *files, const char *wild) @param[in] table_names List of table names in database @param[in] lex pointer to LEX struct @param[in] lookup_field_vals pointer to LOOKUP_FIELD_VALUE struct - @param[in] with_i_schema TRUE means that we add I_S tables to list @param[in] db_name database name @return Operation status @@ -3699,9 +4070,9 @@ int schema_tables_add(THD *thd, List<LEX_STRING> *files, const char *wild) */ static int -make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex, - LOOKUP_FIELD_VALUES *lookup_field_vals, - bool with_i_schema, LEX_STRING *db_name) +make_table_name_list(THD *thd, Dynamic_array<LEX_STRING*> *table_names, + LEX *lex, LOOKUP_FIELD_VALUES *lookup_field_vals, + LEX_STRING *db_name) { char path[FN_REFLEN + 1]; build_table_filename(path, sizeof(path) - 1, db_name->str, "", "", 0); @@ -3716,32 +4087,23 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex, */ return 0; } - - if (with_i_schema) + if (db_name == &INFORMATION_SCHEMA_NAME) { LEX_STRING *name; ST_SCHEMA_TABLE *schema_table= find_schema_table(thd, lookup_field_vals->table_value.str); if (schema_table && !schema_table->hidden) { - if (!(name= - thd->make_lex_string(NULL, schema_table->table_name, - strlen(schema_table->table_name), TRUE)) || - table_names->push_back(name)) + if (!(name= thd->make_lex_string(schema_table->table_name, + strlen(schema_table->table_name))) || + table_names->append(name)) return 1; } } else { - if (table_names->push_back(&lookup_field_vals->table_value)) + if (table_names->append_val(&lookup_field_vals->table_value)) return 1; - /* - Check that table is relevant in current transaction. - (used for ndb engine, see ndbcluster_find_files(), ha_ndbcluster.cc) - */ - (void) ha_find_files(thd, db_name->str, path, - lookup_field_vals->table_value.str, 0, - table_names); } return 0; } @@ -3750,12 +4112,12 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex, This call will add all matching the wildcards (if specified) IS tables to the list */ - if (with_i_schema) + if (db_name == &INFORMATION_SCHEMA_NAME) return (schema_tables_add(thd, table_names, lookup_field_vals->table_value.str)); - find_files_result res= find_files(thd, table_names, db_name->str, path, - lookup_field_vals->table_value.str, 0); + find_files_result res= find_files(thd, table_names, db_name, path, + &lookup_field_vals->table_value); if (res != FIND_FILES_OK) { /* @@ -3766,7 +4128,7 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex, */ if (res == FIND_FILES_DIR) { - if (sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND) + if (is_show_command(thd)) return 1; thd->clear_error(); return 2; @@ -3777,6 +4139,22 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex, } +static void get_table_engine_for_i_s(THD *thd, char *buf, TABLE_LIST *tl, + LEX_STRING *db, LEX_STRING *table) +{ + LEX_STRING engine_name= { buf, 0 }; + + if (thd->get_stmt_da()->sql_errno() == ER_UNKNOWN_STORAGE_ENGINE) + { + char path[FN_REFLEN]; + build_table_filename(path, sizeof(path) - 1, + db->str, table->str, reg_ext, 0); + if (dd_frm_type(thd, path, &engine_name) == FRMTYPE_TABLE) + tl->option= engine_name.str; + } +} + + /** Fill I_S table with data obtained by performing full-blown table open. @@ -3847,14 +4225,14 @@ fill_schema_table_by_open(THD *thd, bool is_show_fields_or_keys, /* Since make_table_list() might change database and table name passed - to it we create copies of orig_db_name and orig_table_name here. - These copies are used for make_table_list() while unaltered values - are passed to process_table() functions. + to it (if lower_case_table_names) we create copies of orig_db_name and + orig_table_name here. These copies are used for make_table_list() + while unaltered values are passed to process_table() functions. */ - if (!thd->make_lex_string(&db_name, orig_db_name->str, - orig_db_name->length, FALSE) || - !thd->make_lex_string(&table_name, orig_table_name->str, - orig_table_name->length, FALSE)) + if (!thd->make_lex_string(&db_name, + orig_db_name->str, orig_db_name->length) || + !thd->make_lex_string(&table_name, + orig_table_name->str, orig_table_name->length)) goto end; /* @@ -3895,12 +4273,18 @@ fill_schema_table_by_open(THD *thd, bool is_show_fields_or_keys, 'only_view_structure()'. */ lex->sql_command= SQLCOM_SHOW_FIELDS; - result= open_normal_and_derived_tables(thd, table_list, - (MYSQL_OPEN_IGNORE_FLUSH | - MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL | - (can_deadlock ? - MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0)), - DT_PREPARE | DT_CREATE); + thd->force_read_stats= get_schema_table_idx(schema_table) == SCH_STATISTICS; + result= (open_temporary_tables(thd, table_list) || + open_normal_and_derived_tables(thd, table_list, + (MYSQL_OPEN_IGNORE_FLUSH | + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL | + (can_deadlock ? + MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0)), + DT_PREPARE | DT_CREATE)); + + (void) read_statistics_for_tables_if_needed(thd, table_list); + thd->force_read_stats= false; + /* Restore old value of sql_command back as it is being looked at in process_table() function. @@ -3920,19 +4304,25 @@ fill_schema_table_by_open(THD *thd, bool is_show_fields_or_keys, Again we don't do this for SHOW COLUMNS/KEYS because of backward compatibility. */ - if (!is_show_fields_or_keys && result && thd->is_error() && - thd->stmt_da->sql_errno() == ER_NO_SUCH_TABLE) + if (!is_show_fields_or_keys && result && + (thd->get_stmt_da()->sql_errno() == ER_NO_SUCH_TABLE || + thd->get_stmt_da()->sql_errno() == ER_WRONG_OBJECT)) { /* Hide error for a non-existing table. For example, this error can occur when we use a where condition - with a db name and table, but the table does not exist. + with a db name and table, but the table does not exist or + there is a view with the same name. */ result= false; thd->clear_error(); } else { + char buf[NAME_CHAR_LEN + 1]; + if (thd->is_error()) + get_table_engine_for_i_s(thd, buf, table_list, &db_name, &table_name); + result= schema_table->process_table(thd, table_list, table, result, orig_db_name, @@ -3978,7 +4368,6 @@ end: @param[in] table TABLE struct for I_S table @param[in] db_name database name @param[in] table_name table name - @param[in] with_i_schema I_S table if TRUE @return Operation status @retval 0 success @@ -3986,38 +4375,29 @@ end: */ static int fill_schema_table_names(THD *thd, TABLE_LIST *tables, - LEX_STRING *db_name, LEX_STRING *table_name, - bool with_i_schema) + LEX_STRING *db_name, LEX_STRING *table_name) { TABLE *table= tables->table; - if (with_i_schema) + if (db_name == &INFORMATION_SCHEMA_NAME) { table->field[3]->store(STRING_WITH_LEN("SYSTEM VIEW"), system_charset_info); } else if (tables->table_open_method != SKIP_OPEN_TABLE) { - enum legacy_db_type not_used; - char path[FN_REFLEN + 1]; - (void) build_table_filename(path, sizeof(path) - 1, db_name->str, - table_name->str, reg_ext, 0); - switch (dd_frm_type(thd, path, ¬_used)) { - case FRMTYPE_ERROR: - table->field[3]->store(STRING_WITH_LEN("ERROR"), - system_charset_info); - break; - case FRMTYPE_TABLE: - table->field[3]->store(STRING_WITH_LEN("BASE TABLE"), - system_charset_info); - break; - case FRMTYPE_VIEW: - table->field[3]->store(STRING_WITH_LEN("VIEW"), - system_charset_info); - break; - default: - DBUG_ASSERT(0); + CHARSET_INFO *cs= system_charset_info; + handlerton *hton; + if (ha_table_exists(thd, db_name->str, table_name->str, &hton)) + { + if (hton == view_pseudo_hton) + table->field[3]->store(STRING_WITH_LEN("VIEW"), cs); + else + table->field[3]->store(STRING_WITH_LEN("BASE TABLE"), cs); } - if (thd->is_error() && thd->stmt_da->sql_errno() == ER_NO_SUCH_TABLE) + else + table->field[3]->store(STRING_WITH_LEN("ERROR"), cs); + + if (thd->is_error() && thd->get_stmt_da()->sql_errno() == ER_NO_SUCH_TABLE) { thd->clear_error(); return 0; @@ -4061,7 +4441,7 @@ uint get_table_open_method(TABLE_LIST *tables, for (ptr=tables->table->field; (field= *ptr) ; ptr++) { star_table_open_method= - min(star_table_open_method, + MY_MIN(star_table_open_method, schema_table->fields_info[field_indx].open_method); if (bitmap_is_set(tables->table->read_set, field->field_index)) { @@ -4099,7 +4479,7 @@ uint get_table_open_method(TABLE_LIST *tables, @retval FALSE No error, if lock was obtained TABLE_LIST::mdl_request::ticket is set to non-NULL value. - @retval TRUE Some error occured (probably thread was killed). + @retval TRUE Some error occurred (probably thread was killed). */ static bool @@ -4158,23 +4538,17 @@ try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table, open_tables function for this table */ -static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, +static int fill_schema_table_from_frm(THD *thd, TABLE *table, ST_SCHEMA_TABLE *schema_table, LEX_STRING *db_name, LEX_STRING *table_name, - enum enum_schema_tables schema_table_idx, Open_tables_backup *open_tables_state_backup, bool can_deadlock) { - TABLE *table= tables->table; TABLE_SHARE *share; TABLE tbl; TABLE_LIST table_list; uint res= 0; - int not_used; - my_hash_value_type hash_value; - char key[MAX_DBKEY_LENGTH]; - uint key_length; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; bzero((char*) &table_list, sizeof(TABLE_LIST)); @@ -4211,7 +4585,7 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, if (try_acquire_high_prio_shared_mdl_lock(thd, &table_list, can_deadlock)) { /* - Some error occured (most probably we have been killed while + Some error occurred (most probably we have been killed while waiting for conflicting locks to go away), let the caller to handle the situation. */ @@ -4227,16 +4601,16 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, */ DBUG_ASSERT(can_deadlock); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_I_S_SKIPPED_TABLE, - ER(ER_WARN_I_S_SKIPPED_TABLE), + ER_THD(thd, ER_WARN_I_S_SKIPPED_TABLE), table_list.db, table_list.table_name); return 0; } if (schema_table->i_s_requested_object & OPEN_TRIGGER_ONLY) { - init_sql_alloc(&tbl.mem_root, TABLE_ALLOC_BLOCK_SIZE, 0); + init_sql_alloc(&tbl.mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0)); if (!Table_triggers_list::check_n_load(thd, db_name->str, table_name->str, &tbl, 1)) { @@ -4249,15 +4623,23 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, goto end; } - key_length= create_table_def_key(thd, key, &table_list, 0); - hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); - mysql_mutex_lock(&LOCK_open); - share= get_table_share(thd, &table_list, key, - key_length, OPEN_VIEW, ¬_used, hash_value); + share= tdc_acquire_share_shortlived(thd, &table_list, GTS_TABLE | GTS_VIEW); if (!share) { - res= 0; - goto end_unlock; + if (thd->get_stmt_da()->sql_errno() == ER_NO_SUCH_TABLE || + thd->get_stmt_da()->sql_errno() == ER_WRONG_OBJECT) + { + res= 0; + } + else + { + char buf[NAME_CHAR_LEN + 1]; + get_table_engine_for_i_s(thd, buf, &table_list, db_name, table_name); + + res= schema_table->process_table(thd, &table_list, table, + true, db_name, table_name); + } + goto end; } if (share->is_view) @@ -4277,16 +4659,8 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, res= 1; goto end_share; } - } - if (share->is_view) - { - if (open_new_frm(thd, share, table_name->str, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX | HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | - OPEN_VIEW_NO_PARSE, - thd->open_options, &tbl, &table_list, thd->mem_root)) + if (mysql_make_view(thd, share, &table_list, true)) goto end_share; table_list.view= (LEX*) share->is_view; res= schema_table->process_table(thd, &table_list, table, @@ -4306,11 +4680,9 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, free_root(&tbl.mem_root, MYF(0)); } -end_share: - release_table_share(share); -end_unlock: - mysql_mutex_unlock(&LOCK_open); +end_share: + tdc_release_share(share); end: /* @@ -4343,20 +4715,20 @@ public: bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* msg, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { if (sql_errno == ER_PARSE_ERROR || sql_errno == ER_TRG_NO_DEFINER || sql_errno == ER_TRG_NO_CREATION_CTX) return true; - if (level != MYSQL_ERROR::WARN_LEVEL_ERROR) + if (level != Sql_condition::WARN_LEVEL_ERROR) return false; - if (!thd->stmt_da->is_error()) - thd->stmt_da->set_error_status(thd, sql_errno, msg, sqlstate); + if (!thd->get_stmt_da()->is_error()) + thd->get_stmt_da()->set_error_status(sql_errno, msg, sqlstate, *cond_hdl); return true; // handled! } }; @@ -4372,6 +4744,10 @@ public: from frm files and storage engine are filled by the function get_all_tables(). + @note This function assumes optimize_for_get_all_tables() has been + run for the table and produced a "read plan" in + tables->is_table_read_plan. + @param[in] thd thread handler @param[in] tables I_S table @param[in] cond 'WHERE' condition @@ -4385,21 +4761,19 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) { LEX *lex= thd->lex; TABLE *table= tables->table; + TABLE_LIST table_acl_check; SELECT_LEX *lsel= tables->schema_select_lex; ST_SCHEMA_TABLE *schema_table= tables->schema_table; - LOOKUP_FIELD_VALUES lookup_field_vals; - LEX_STRING *db_name, *table_name; - bool with_i_schema; + IS_table_read_plan *plan= tables->is_table_read_plan; enum enum_schema_tables schema_table_idx; - List<LEX_STRING> db_names; - List_iterator_fast<LEX_STRING> it(db_names); - COND *partial_cond= 0; + Dynamic_array<LEX_STRING*> db_names; + Item *partial_cond= plan->partial_cond; int error= 1; Open_tables_backup open_tables_state_backup; #ifndef NO_EMBEDDED_ACCESS_CHECKS Security_context *sctx= thd->security_ctx; #endif - uint table_open_method; + uint table_open_method= tables->table_open_method; bool can_deadlock; DBUG_ENTER("get_all_tables"); @@ -4423,9 +4797,6 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) thd->reset_n_backup_open_tables_state(&open_tables_state_backup); schema_table_idx= get_schema_table_idx(schema_table); - tables->table_open_method= table_open_method= - get_table_open_method(tables, schema_table, schema_table_idx); - DBUG_PRINT("open_method", ("%d", tables->table_open_method)); /* this branch processes SHOW FIELDS, SHOW INDEXES commands. see sql_parse.cc, prepare_schema_table() function where @@ -4449,44 +4820,12 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) goto err; } - if (get_lookup_field_values(thd, cond, tables, &lookup_field_vals)) + if (plan->no_rows) { error= 0; goto err; } - DBUG_PRINT("INDEX VALUES",("db_name='%s', table_name='%s'", - STR_OR_NIL(lookup_field_vals.db_value.str), - STR_OR_NIL(lookup_field_vals.table_value.str))); - - if (!lookup_field_vals.wild_db_value && !lookup_field_vals.wild_table_value) - { - /* - if lookup value is empty string then - it's impossible table name or db name - */ - if ((lookup_field_vals.db_value.str && - !lookup_field_vals.db_value.str[0]) || - (lookup_field_vals.table_value.str && - !lookup_field_vals.table_value.str[0])) - { - error= 0; - goto err; - } - } - - if (lookup_field_vals.db_value.length && - !lookup_field_vals.wild_db_value) - tables->has_db_lookup_value= TRUE; - if (lookup_field_vals.table_value.length && - !lookup_field_vals.wild_table_value) - tables->has_table_lookup_value= TRUE; - - if (tables->has_db_lookup_value && tables->has_table_lookup_value) - partial_cond= 0; - else - partial_cond= make_cond_for_info_schema(cond, tables); - if (lex->describe) { /* EXPLAIN SELECT */ @@ -4494,11 +4833,13 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) goto err; } - if (make_db_list(thd, &db_names, &lookup_field_vals, &with_i_schema)) + bzero((char*) &table_acl_check, sizeof(table_acl_check)); + + if (make_db_list(thd, &db_names, &plan->lookup_field_vals)) goto err; - it.rewind(); /* To get access to new elements in basis list */ - while ((db_name= it++)) + for (size_t i=0; i < db_names.elements(); i++) { + LEX_STRING *db_name= db_names.at(i); DBUG_ASSERT(db_name->length <= NAME_LEN); #ifndef NO_EMBEDDED_ACCESS_CHECKS if (!(check_access(thd, SELECT_ACL, db_name->str, @@ -4508,19 +4849,31 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) acl_get(sctx->host, sctx->ip, sctx->priv_user, db_name->str, 0)) #endif { - List<LEX_STRING> table_names; + Dynamic_array<LEX_STRING*> table_names; int res= make_table_name_list(thd, &table_names, lex, - &lookup_field_vals, - with_i_schema, db_name); + &plan->lookup_field_vals, db_name); if (res == 2) /* Not fatal error, continue */ continue; if (res) goto err; - List_iterator_fast<LEX_STRING> it_files(table_names); - while ((table_name= it_files++)) + for (size_t i=0; i < table_names.elements(); i++) { + LEX_STRING *table_name= table_names.at(i); DBUG_ASSERT(table_name->length <= NAME_LEN); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (!(thd->col_access & TABLE_ACLS)) + { + table_acl_check.db= db_name->str; + table_acl_check.db_length= db_name->length; + table_acl_check.table_name= table_name->str; + table_acl_check.table_name_length= table_name->length; + table_acl_check.grant.privilege= thd->col_access; + if (check_grant(thd, TABLE_ACLS, &table_acl_check, TRUE, 1, TRUE)) + continue; + } +#endif restore_record(table, s->default_values); table->field[schema_table->idx_field1]-> store(db_name->str, db_name->length, system_charset_info); @@ -4536,8 +4889,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) already created by make_table_name_list() function). */ if (!table_open_method && schema_table_idx == SCH_TABLES && - (!lookup_field_vals.table_value.length || - lookup_field_vals.wild_table_value)) + (!plan->lookup_field_vals.table_value.length || + plan->lookup_field_vals.wild_table_value)) { table->field[0]->store(STRING_WITH_LEN("def"), system_charset_info); if (schema_table_store_record(thd, table)) @@ -4548,22 +4901,21 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) /* SHOW TABLE NAMES command */ if (schema_table_idx == SCH_TABLE_NAMES) { - if (fill_schema_table_names(thd, tables, db_name, - table_name, with_i_schema)) + if (fill_schema_table_names(thd, tables, db_name, table_name)) continue; } - else if (schema_table_idx == SCH_TRIGGERS && with_i_schema) + else if (schema_table_idx == SCH_TRIGGERS && + db_name == &INFORMATION_SCHEMA_NAME) { continue; } else { if (!(table_open_method & ~OPEN_FRM_ONLY) && - !with_i_schema) + db_name != &INFORMATION_SCHEMA_NAME) { - if (!fill_schema_table_from_frm(thd, tables, schema_table, + if (!fill_schema_table_from_frm(thd, table, schema_table, db_name, table_name, - schema_table_idx, &open_tables_state_backup, can_deadlock)) continue; @@ -4579,11 +4931,6 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) } } } - /* - If we have information schema its always the first table and only - the first table. Reset for other tables. - */ - with_i_schema= 0; } } @@ -4615,10 +4962,8 @@ int fill_schema_schemata(THD *thd, TABLE_LIST *tables, COND *cond) */ LOOKUP_FIELD_VALUES lookup_field_vals; - List<LEX_STRING> db_names; - LEX_STRING *db_name; - bool with_i_schema; - HA_CREATE_INFO create; + Dynamic_array<LEX_STRING*> db_names; + Schema_specification_st create; TABLE *table= tables->table; #ifndef NO_EMBEDDED_ACCESS_CHECKS Security_context *sctx= thd->security_ctx; @@ -4630,15 +4975,14 @@ int fill_schema_schemata(THD *thd, TABLE_LIST *tables, COND *cond) DBUG_PRINT("INDEX VALUES",("db_name: %s table_name: %s", lookup_field_vals.db_value.str, lookup_field_vals.table_value.str)); - if (make_db_list(thd, &db_names, &lookup_field_vals, - &with_i_schema)) + if (make_db_list(thd, &db_names, &lookup_field_vals)) DBUG_RETURN(1); /* If we have lookup db value we should check that the database exists */ if(lookup_field_vals.db_value.str && !lookup_field_vals.wild_db_value && - !with_i_schema) + db_names.at(0) != &INFORMATION_SCHEMA_NAME) { char path[FN_REFLEN+16]; uint path_len; @@ -4652,22 +4996,23 @@ int fill_schema_schemata(THD *thd, TABLE_LIST *tables, COND *cond) DBUG_RETURN(0); } - List_iterator_fast<LEX_STRING> it(db_names); - while ((db_name=it++)) + for (size_t i=0; i < db_names.elements(); i++) { + LEX_STRING *db_name= db_names.at(i); DBUG_ASSERT(db_name->length <= NAME_LEN); - if (with_i_schema) // information schema name is always first in list + if (db_name == &INFORMATION_SCHEMA_NAME) { if (store_schema_shemata(thd, table, db_name, system_charset_info)) DBUG_RETURN(1); - with_i_schema= 0; continue; } #ifndef NO_EMBEDDED_ACCESS_CHECKS if (sctx->master_access & (DB_ACLS | SHOW_DB_ACL) || - acl_get(sctx->host, sctx->ip, sctx->priv_user, db_name->str, 0) || - !check_grant_db(thd, db_name->str)) + acl_get(sctx->host, sctx->ip, sctx->priv_user, db_name->str, false) || + (sctx->priv_role[0] ? + acl_get("", "", sctx->priv_role, db_name->str, false) : 0) || + !check_grant_db(thd, db_name->str)) #endif { load_db_opt_by_name(thd, db_name->str, &create); @@ -4706,6 +5051,11 @@ static int get_schema_tables_record(THD *thd, TABLE_LIST *tables, else table->field[3]->store(STRING_WITH_LEN("BASE TABLE"), cs); + if (tables->option) + { + table->field[4]->store(tables->option, strlen(tables->option), cs); + table->field[4]->set_notnull(); + } goto err; } @@ -4716,7 +5066,7 @@ static int get_schema_tables_record(THD *thd, TABLE_LIST *tables, } else { - char option_buff[350]; + char option_buff[512]; String str(option_buff,sizeof(option_buff), system_charset_info); TABLE *show_table= tables->table; TABLE_SHARE *share= show_table->s; @@ -4746,7 +5096,7 @@ static int get_schema_tables_record(THD *thd, TABLE_LIST *tables, if (share->db_type() == partition_hton && share->partition_info_str_len) { - tmp_db_type= share->default_part_db_type; + tmp_db_type= plugin_hton(share->default_part_plugin); is_partitioned= TRUE; } #endif @@ -4781,6 +5131,23 @@ static int get_schema_tables_record(THD *thd, TABLE_LIST *tables, if (share->db_create_options & HA_OPTION_NO_PACK_KEYS) str.qs_append(STRING_WITH_LEN(" pack_keys=0")); + if (share->db_create_options & HA_OPTION_STATS_PERSISTENT) + str.qs_append(STRING_WITH_LEN(" stats_persistent=1")); + + if (share->db_create_options & HA_OPTION_NO_STATS_PERSISTENT) + str.qs_append(STRING_WITH_LEN(" stats_persistent=0")); + + if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_ON) + str.qs_append(STRING_WITH_LEN(" stats_auto_recalc=1")); + else if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_OFF) + str.qs_append(STRING_WITH_LEN(" stats_auto_recalc=0")); + + if (share->stats_sample_pages != 0) + { + str.qs_append(STRING_WITH_LEN(" stats_sample_pages=")); + str.qs_append(share->stats_sample_pages); + } + /* We use CHECKSUM, instead of TABLE_CHECKSUM, for backward compability */ if (share->db_create_options & HA_OPTION_CHECKSUM) str.qs_append(STRING_WITH_LEN(" checksum=1")); @@ -4816,7 +5183,16 @@ static int get_schema_tables_record(THD *thd, TABLE_LIST *tables, str.qs_append(STRING_WITH_LEN(" transactional=")); str.qs_append(ha_choice_values[(uint) share->transactional]); } - append_create_options(thd, &str, share->option_list); + append_create_options(thd, &str, share->option_list, false, 0); + + if (file) + { + HA_CREATE_INFO create_info; + memset(&create_info, 0, sizeof(create_info)); + file->update_create_info(&create_info); + append_directory(thd, &str, "DATA", create_info.data_file_name); + append_directory(thd, &str, "INDEX", create_info.index_file_name); + } if (str.length()) table->field[19]->store(str.ptr()+1, str.length()-1, cs); @@ -4838,7 +5214,10 @@ static int get_schema_tables_record(THD *thd, TABLE_LIST *tables, HA_STATUS_TIME | HA_STATUS_VARIABLE_EXTRA | HA_STATUS_AUTO)) != 0) + { + file->print_error(info_error, MYF(0)); goto err; + } enum row_type row_type = file->get_row_type(); switch (row_type) { @@ -4928,15 +5307,12 @@ err: column with the error text, and clear the error so that the operation can continue. */ - const char *error= thd->is_error() ? thd->stmt_da->message() : ""; + const char *error= thd->get_stmt_da()->message(); table->field[20]->store(error, strlen(error), cs); - if (thd->is_error()) - { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - thd->stmt_da->sql_errno(), thd->stmt_da->message()); - thd->clear_error(); - } + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), error); + thd->clear_error(); } DBUG_RETURN(schema_table_store_record(thd, table)); @@ -5085,7 +5461,7 @@ static int get_schema_column_record(THD *thd, TABLE_LIST *tables, const char *wild= lex->wild ? lex->wild->ptr() : NullS; CHARSET_INFO *cs= system_charset_info; TABLE *show_table; - Field **ptr, *field, *timestamp_field; + Field **ptr, *field; int count; DBUG_ENTER("get_schema_column_record"); @@ -5097,9 +5473,9 @@ static int get_schema_column_record(THD *thd, TABLE_LIST *tables, I.e. we are in SELECT FROM INFORMATION_SCHEMA.COLUMS rather than in SHOW COLUMNS */ - if (thd->is_error()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - thd->stmt_da->sql_errno(), thd->stmt_da->message()); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); thd->clear_error(); res= 0; } @@ -5109,7 +5485,6 @@ static int get_schema_column_record(THD *thd, TABLE_LIST *tables, show_table= tables->table; count= 0; ptr= show_table->field; - timestamp_field= show_table->timestamp_field; show_table->use_all_columns(); // Required for default restore_record(show_table, s->default_values); @@ -5132,7 +5507,7 @@ static int get_schema_column_record(THD *thd, TABLE_LIST *tables, #ifndef NO_EMBEDDED_ACCESS_CHECKS uint col_access; check_access(thd,SELECT_ACL, db_name->str, - &tables->grant.privilege, 0, 0, test(tables->schema_table)); + &tables->grant.privilege, 0, 0, MY_TEST(tables->schema_table)); col_access= get_column_grant(thd, &tables->grant, db_name->str, table_name->str, field->field_name) & COL_ACLS; @@ -5157,7 +5532,7 @@ static int get_schema_column_record(THD *thd, TABLE_LIST *tables, cs); table->field[4]->store((longlong) count, TRUE); - if (get_field_default_value(thd, timestamp_field, field, &type, 0)) + if (get_field_default_value(thd, field, &type, 0)) { table->field[5]->store(type.ptr(), type.length(), cs); table->field[5]->set_notnull(); @@ -5174,10 +5549,8 @@ static int get_schema_column_record(THD *thd, TABLE_LIST *tables, if (field->unireg_check == Field::NEXT_NUMBER) table->field[17]->store(STRING_WITH_LEN("auto_increment"), cs); - if (timestamp_field == field && - field->unireg_check != Field::TIMESTAMP_DN_FIELD) - table->field[17]->store(STRING_WITH_LEN("on update CURRENT_TIMESTAMP"), - cs); + if (print_on_update_clause(field, &type, true)) + table->field[17]->store(type.ptr(), type.length(), cs); if (field->vcol_info) { if (field->stored_in_db) @@ -5230,7 +5603,7 @@ static my_bool iter_schema_engines(THD *thd, plugin_ref plugin, void *ptable) { TABLE *table= (TABLE *) ptable; - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS; CHARSET_INFO *scs= system_charset_info; handlerton *default_type= ha_default_handlerton(thd); @@ -5273,13 +5646,13 @@ static my_bool iter_schema_engines(THD *thd, plugin_ref plugin, table->field[1]->store(option_name, strlen(option_name), scs); table->field[2]->store(plugin_decl(plugin)->descr, strlen(plugin_decl(plugin)->descr), scs); - tmp= &yesno[test(hton->commit)]; + tmp= &yesno[MY_TEST(hton->commit)]; table->field[3]->store(tmp->str, tmp->length, scs); table->field[3]->set_notnull(); - tmp= &yesno[test(hton->prepare)]; + tmp= &yesno[MY_TEST(hton->prepare)]; table->field[4]->store(tmp->str, tmp->length, scs); table->field[4]->set_notnull(); - tmp= &yesno[test(hton->savepoint_set)]; + tmp= &yesno[MY_TEST(hton->savepoint_set)]; table->field[5]->store(tmp->str, tmp->length, scs); table->field[5]->set_notnull(); @@ -5426,6 +5799,7 @@ bool store_schema_params(THD *thd, TABLE *table, TABLE *proc_table, sp_head *sp; stored_procedure_type routine_type; bool free_sp_head; + bool error= 0; DBUG_ENTER("store_schema_params"); bzero((char*) &tbl, sizeof(TABLE)); @@ -5476,10 +5850,11 @@ bool store_schema_params(THD *thd, TABLE *table, TABLE *proc_table, &tmp_string); table->field[15]->store(tmp_string.ptr(), tmp_string.length(), cs); field_def= &sp->m_return_field_def; - field= make_field(&share, (uchar*) 0, field_def->length, + field= make_field(&share, thd->mem_root, + (uchar*) 0, field_def->length, (uchar*) "", 0, field_def->pack_flag, field_def->sql_type, field_def->charset, - field_def->geom_type, Field::NONE, + field_def->geom_type, field_def->srid, Field::NONE, field_def->interval, ""); field->table= &tbl; @@ -5499,16 +5874,16 @@ bool store_schema_params(THD *thd, TABLE *table, TABLE *proc_table, for (uint i= 0 ; i < params ; i++) { const char *tmp_buff; - sp_variable_t *spvar= spcont->find_variable(i); + sp_variable *spvar= spcont->find_variable(i); field_def= &spvar->field_def; switch (spvar->mode) { - case sp_param_in: + case sp_variable::MODE_IN: tmp_buff= "IN"; break; - case sp_param_out: + case sp_variable::MODE_OUT: tmp_buff= "OUT"; break; - case sp_param_inout: + case sp_variable::MODE_INOUT: tmp_buff= "INOUT"; break; default: @@ -5529,10 +5904,10 @@ bool store_schema_params(THD *thd, TABLE *table, TABLE *proc_table, &tmp_string); table->field[15]->store(tmp_string.ptr(), tmp_string.length(), cs); - field= make_field(&share, (uchar*) 0, field_def->length, + field= make_field(&share, thd->mem_root, (uchar*) 0, field_def->length, (uchar*) "", 0, field_def->pack_flag, field_def->sql_type, field_def->charset, - field_def->geom_type, Field::NONE, + field_def->geom_type, field_def->srid, Field::NONE, field_def->interval, spvar->name.str); field->table= &tbl; @@ -5540,17 +5915,15 @@ bool store_schema_params(THD *thd, TABLE *table, TABLE *proc_table, store_column_type(table, field, cs, 6); if (schema_table_store_record(thd, table)) { - free_table_share(&share); - if (free_sp_head) - delete sp; - DBUG_RETURN(1); + error= 1; + break; } } if (free_sp_head) delete sp; } free_table_share(&share); - DBUG_RETURN(0); + DBUG_RETURN(error); } @@ -5581,13 +5954,13 @@ bool store_schema_proc(THD *thd, TABLE *table, TABLE *proc_table, val_int() == TYPE_ENUM_PROCEDURE)) return 0; - if ((lex->sql_command == SQLCOM_SHOW_STATUS_PROC && + if (!is_show_command(thd) || + (lex->sql_command == SQLCOM_SHOW_STATUS_PROC && proc_table->field[MYSQL_PROC_MYSQL_TYPE]->val_int() == TYPE_ENUM_PROCEDURE) || (lex->sql_command == SQLCOM_SHOW_STATUS_FUNC && proc_table->field[MYSQL_PROC_MYSQL_TYPE]->val_int() == - TYPE_ENUM_FUNCTION) || - (sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND) == 0) + TYPE_ENUM_FUNCTION)) { restore_record(table, s->default_values); if (!wild || !wild[0] || !wild_case_compare(system_charset_info, @@ -5628,10 +6001,11 @@ bool store_schema_proc(THD *thd, TABLE *table, TABLE *proc_table, bzero((char*) &tbl, sizeof(TABLE)); (void) build_table_filename(path, sizeof(path), "", "", "", 0); init_tmp_table_share(thd, &share, "", 0, "", path); - field= make_field(&share, (uchar*) 0, field_def->length, + field= make_field(&share, thd->mem_root, (uchar*) 0, + field_def->length, (uchar*) "", 0, field_def->pack_flag, field_def->sql_type, field_def->charset, - field_def->geom_type, Field::NONE, + field_def->geom_type, field_def->srid, Field::NONE, field_def->interval, ""); field->table= &tbl; @@ -5778,8 +6152,9 @@ static int get_schema_stat_record(THD *thd, TABLE_LIST *tables, rather than in SHOW KEYS */ if (thd->is_error()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - thd->stmt_da->sql_errno(), thd->stmt_da->message()); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); thd->clear_error(); res= 0; } @@ -5790,14 +6165,17 @@ static int get_schema_stat_record(THD *thd, TABLE_LIST *tables, TABLE *show_table= tables->table; KEY *key_info=show_table->s->key_info; if (show_table->file) + { show_table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK | HA_STATUS_TIME); + set_statistics_for_table(thd, show_table); + } for (uint i=0 ; i < show_table->s->keys ; i++,key_info++) { KEY_PART_INFO *key_part= key_info->key_part; const char *str; - for (uint j=0 ; j < key_info->key_parts ; j++,key_part++) + for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++) { restore_record(table, s->default_values); table->field[0]->store(STRING_WITH_LEN("def"), cs); @@ -5823,8 +6201,8 @@ static int get_schema_stat_record(THD *thd, TABLE_LIST *tables, KEY *key=show_table->key_info+i; if (key->rec_per_key[j]) { - ha_rows records=(show_table->file->stats.records / - key->rec_per_key[j]); + ha_rows records= (ha_rows) ((double) show_table->stat_records() / + key->actual_rec_per_key(j)); table->field[9]->store((longlong) records, TRUE); table->field[9]->set_notnull(); } @@ -5848,7 +6226,7 @@ static int get_schema_stat_record(THD *thd, TABLE_LIST *tables, else table->field[14]->store("", 0, cs); table->field[14]->set_notnull(); - DBUG_ASSERT(test(key_info->flags & HA_USES_COMMENT) == + DBUG_ASSERT(MY_TEST(key_info->flags & HA_USES_COMMENT) == (key_info->comment.length > 0)); if (key_info->flags & HA_USES_COMMENT) table->field[15]->store(key_info->comment.str, @@ -5893,7 +6271,7 @@ static int get_schema_views_record(THD *thd, TABLE_LIST *tables, { TABLE_LIST table_list; uint view_access; - memset(&table_list, 0, sizeof(table_list)); + table_list.reset(); table_list.db= tables->db; table_list.table_name= tables->table_name; table_list.grant.privilege= thd->col_access; @@ -5960,7 +6338,7 @@ static int get_schema_views_record(THD *thd, TABLE_LIST *tables, */ while ((item= it++)) { - if ((field= item->filed_for_view_update()) && field->field && + if ((field= item->field_for_view_update()) && field->field && !field->field->table->pos_in_table_list->schema_table) { updatable_view= 1; @@ -5993,12 +6371,14 @@ static int get_schema_views_record(THD *thd, TABLE_LIST *tables, strlen(tables->view_creation_ctx-> get_connection_cl()->name), cs); + table->field[10]->store(view_algorithm(tables), cs); if (schema_table_store_record(thd, table)) DBUG_RETURN(1); if (res && thd->is_error()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - thd->stmt_da->sql_errno(), thd->stmt_da->message()); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); } if (res) thd->clear_error(); @@ -6031,8 +6411,9 @@ static int get_schema_constraints_record(THD *thd, TABLE_LIST *tables, if (res) { if (thd->is_error()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - thd->stmt_da->sql_errno(), thd->stmt_da->message()); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); thd->clear_error(); DBUG_RETURN(0); } @@ -6137,8 +6518,9 @@ static int get_schema_triggers_record(THD *thd, TABLE_LIST *tables, if (res) { if (thd->is_error()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - thd->stmt_da->sql_errno(), thd->stmt_da->message()); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); thd->clear_error(); DBUG_RETURN(0); } @@ -6218,8 +6600,9 @@ static int get_schema_key_column_usage_record(THD *thd, if (res) { if (thd->is_error()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - thd->stmt_da->sql_errno(), thd->stmt_da->message()); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); thd->clear_error(); DBUG_RETURN(0); } @@ -6238,7 +6621,7 @@ static int get_schema_key_column_usage_record(THD *thd, continue; uint f_idx= 0; KEY_PART_INFO *key_part= key_info->key_part; - for (uint j=0 ; j < key_info->key_parts ; j++,key_part++) + for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++) { if (key_part->field) { @@ -6433,7 +6816,7 @@ static void store_schema_partitions_record(THD *thd, TABLE *schema_table, strlen(part_elem->tablespace_name), cs); else { - char *ts= showing_table->file->get_tablespace_name(thd,0,0); + char *ts= showing_table->s->tablespace; if(ts) table->field[24]->store(ts, strlen(ts), cs); else @@ -6508,8 +6891,9 @@ static int get_schema_partitions_record(THD *thd, TABLE_LIST *tables, if (res) { if (thd->is_error()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - thd->stmt_da->sql_errno(), thd->stmt_da->message()); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); thd->clear_error(); DBUG_RETURN(0); } @@ -6760,7 +7144,7 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) if (et.load_from_row(thd, event_table)) { - my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), event_table->alias.c_ptr()); + my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), "mysql", "event"); DBUG_RETURN(1); } @@ -6945,14 +7329,14 @@ int fill_variables(THD *thd, TABLE_LIST *tables, COND *cond) enum enum_schema_tables schema_table_idx= get_schema_table_idx(tables->schema_table); enum enum_var_type scope= OPT_SESSION; - bool upper_case_names= (schema_table_idx != SCH_VARIABLES); - bool sorted_vars= (schema_table_idx == SCH_VARIABLES); + bool upper_case_names= lex->sql_command != SQLCOM_SHOW_VARIABLES; + bool sorted_vars= lex->sql_command == SQLCOM_SHOW_VARIABLES; if ((sorted_vars && lex->option_type == OPT_GLOBAL) || schema_table_idx == SCH_GLOBAL_VARIABLES) scope= OPT_GLOBAL; - COND *partial_cond= make_cond_for_info_schema(cond, tables); + COND *partial_cond= make_cond_for_info_schema(thd, cond, tables); mysql_rwlock_rdlock(&LOCK_system_variables_hash); @@ -6983,41 +7367,47 @@ int fill_status(THD *thd, TABLE_LIST *tables, COND *cond) STATUS_VAR *tmp1, tmp; enum enum_schema_tables schema_table_idx= get_schema_table_idx(tables->schema_table); - enum enum_var_type option_type; - bool upper_case_names= (schema_table_idx != SCH_STATUS); + enum enum_var_type scope; + bool upper_case_names= lex->sql_command != SQLCOM_SHOW_STATUS; - if (schema_table_idx == SCH_STATUS) + if (lex->sql_command == SQLCOM_SHOW_STATUS) { - option_type= lex->option_type; - if (option_type == OPT_GLOBAL) + scope= lex->option_type; + if (scope == OPT_GLOBAL) tmp1= &tmp; else tmp1= thd->initial_status_var; } else if (schema_table_idx == SCH_GLOBAL_STATUS) { - option_type= OPT_GLOBAL; + scope= OPT_GLOBAL; tmp1= &tmp; } else { - option_type= OPT_SESSION; + scope= OPT_SESSION; tmp1= &thd->status_var; } - COND *partial_cond= make_cond_for_info_schema(cond, tables); + COND *partial_cond= make_cond_for_info_schema(thd, cond, tables); // Evaluate and cache const subqueries now, before the mutex. if (partial_cond) partial_cond->val_int(); - mysql_mutex_lock(&LOCK_status); - if (option_type == OPT_GLOBAL) + if (scope == OPT_GLOBAL) + { + /* We only hold LOCK_status for summary status vars */ + mysql_mutex_lock(&LOCK_status); calc_sum_of_all_status(&tmp); + mysql_mutex_unlock(&LOCK_status); + } + + mysql_mutex_lock(&LOCK_show_status); res= show_status_array(thd, wild, (SHOW_VAR *)all_status_vars.buffer, - option_type, tmp1, "", tables->table, + scope, tmp1, "", tables->table, upper_case_names, partial_cond); - mysql_mutex_unlock(&LOCK_status); + mysql_mutex_unlock(&LOCK_show_status); DBUG_RETURN(res); } @@ -7046,13 +7436,15 @@ get_referential_constraints_record(THD *thd, TABLE_LIST *tables, LEX_STRING *db_name, LEX_STRING *table_name) { CHARSET_INFO *cs= system_charset_info; + LEX_CSTRING *s; DBUG_ENTER("get_referential_constraints_record"); if (res) { if (thd->is_error()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - thd->stmt_da->sql_errno(), thd->stmt_da->message()); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); thd->clear_error(); DBUG_RETURN(0); } @@ -7089,10 +7481,10 @@ get_referential_constraints_record(THD *thd, TABLE_LIST *tables, else table->field[5]->set_null(); table->field[6]->store(STRING_WITH_LEN("NONE"), cs); - table->field[7]->store(f_key_info->update_method->str, - f_key_info->update_method->length, cs); - table->field[8]->store(f_key_info->delete_method->str, - f_key_info->delete_method->length, cs); + s= fk_option_name(f_key_info->update_method); + table->field[7]->store(s->str, s->length, cs); + s= fk_option_name(f_key_info->delete_method); + table->field[8]->store(s->str, s->length, cs); if (schema_table_store_record(thd, table)) DBUG_RETURN(1); } @@ -7106,82 +7498,6 @@ struct schema_table_ref ST_SCHEMA_TABLE *schema_table; }; -ST_FIELD_INFO user_stats_fields_info[]= -{ - {"USER", USERNAME_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, "User", SKIP_OPEN_TABLE}, - {"TOTAL_CONNECTIONS", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Total_connections",SKIP_OPEN_TABLE}, - {"CONCURRENT_CONNECTIONS", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Concurrent_connections",SKIP_OPEN_TABLE}, - {"CONNECTED_TIME", MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, "Connected_time",SKIP_OPEN_TABLE}, - {"BUSY_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_DOUBLE, 0, 0, "Busy_time",SKIP_OPEN_TABLE}, - {"CPU_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_DOUBLE, 0, 0, "Cpu_time",SKIP_OPEN_TABLE}, - {"BYTES_RECEIVED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Bytes_received",SKIP_OPEN_TABLE}, - {"BYTES_SENT", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Bytes_sent",SKIP_OPEN_TABLE}, - {"BINLOG_BYTES_WRITTEN", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Binlog_bytes_written",SKIP_OPEN_TABLE}, - {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read",SKIP_OPEN_TABLE}, - {"ROWS_SENT", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_sent",SKIP_OPEN_TABLE}, - {"ROWS_DELETED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_deleted",SKIP_OPEN_TABLE}, - {"ROWS_INSERTED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_inserted",SKIP_OPEN_TABLE}, - {"ROWS_UPDATED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_updated",SKIP_OPEN_TABLE}, - {"SELECT_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Select_commands",SKIP_OPEN_TABLE}, - {"UPDATE_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Update_commands",SKIP_OPEN_TABLE}, - {"OTHER_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Other_commands",SKIP_OPEN_TABLE}, - {"COMMIT_TRANSACTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Commit_transactions",SKIP_OPEN_TABLE}, - {"ROLLBACK_TRANSACTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rollback_transactions",SKIP_OPEN_TABLE}, - {"DENIED_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Denied_connections",SKIP_OPEN_TABLE}, - {"LOST_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Lost_connections",SKIP_OPEN_TABLE}, - {"ACCESS_DENIED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Access_denied",SKIP_OPEN_TABLE}, - {"EMPTY_QUERIES", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Empty_queries",SKIP_OPEN_TABLE}, - {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0} -}; - -ST_FIELD_INFO client_stats_fields_info[]= -{ - {"CLIENT", LIST_PROCESS_HOST_LEN, MYSQL_TYPE_STRING, 0, 0, "Client",SKIP_OPEN_TABLE}, - {"TOTAL_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Total_connections",SKIP_OPEN_TABLE}, - {"CONCURRENT_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Concurrent_connections",SKIP_OPEN_TABLE}, - {"CONNECTED_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Connected_time",SKIP_OPEN_TABLE}, - {"BUSY_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_DOUBLE, 0, 0, "Busy_time",SKIP_OPEN_TABLE}, - {"CPU_TIME", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_DOUBLE, 0, 0, "Cpu_time",SKIP_OPEN_TABLE}, - {"BYTES_RECEIVED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Bytes_received",SKIP_OPEN_TABLE}, - {"BYTES_SENT", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Bytes_sent",SKIP_OPEN_TABLE}, - {"BINLOG_BYTES_WRITTEN", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Binlog_bytes_written",SKIP_OPEN_TABLE}, - {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read",SKIP_OPEN_TABLE}, - {"ROWS_SENT", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_sent",SKIP_OPEN_TABLE}, - {"ROWS_DELETED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_deleted",SKIP_OPEN_TABLE}, - {"ROWS_INSERTED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_inserted",SKIP_OPEN_TABLE}, - {"ROWS_UPDATED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_updated",SKIP_OPEN_TABLE}, - {"SELECT_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Select_commands",SKIP_OPEN_TABLE}, - {"UPDATE_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Update_commands",SKIP_OPEN_TABLE}, - {"OTHER_COMMANDS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Other_commands",SKIP_OPEN_TABLE}, - {"COMMIT_TRANSACTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Commit_transactions",SKIP_OPEN_TABLE}, - {"ROLLBACK_TRANSACTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rollback_transactions",SKIP_OPEN_TABLE}, - {"DENIED_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Denied_connections",SKIP_OPEN_TABLE}, - {"LOST_CONNECTIONS", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Lost_connections",SKIP_OPEN_TABLE}, - {"ACCESS_DENIED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Access_denied",SKIP_OPEN_TABLE}, - {"EMPTY_QUERIES", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Empty_queries",SKIP_OPEN_TABLE}, - {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0} -}; - - -ST_FIELD_INFO table_stats_fields_info[]= -{ - {"TABLE_SCHEMA", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_schema",SKIP_OPEN_TABLE}, - {"TABLE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_name",SKIP_OPEN_TABLE}, - {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read",SKIP_OPEN_TABLE}, - {"ROWS_CHANGED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_changed",SKIP_OPEN_TABLE}, - {"ROWS_CHANGED_X_INDEXES", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_changed_x_#indexes",SKIP_OPEN_TABLE}, - {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0} -}; - -ST_FIELD_INFO index_stats_fields_info[]= -{ - {"TABLE_SCHEMA", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_schema",SKIP_OPEN_TABLE}, - {"TABLE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_name",SKIP_OPEN_TABLE}, - {"INDEX_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Index_name",SKIP_OPEN_TABLE}, - {"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, "Rows_read",SKIP_OPEN_TABLE}, - {0, 0, MYSQL_TYPE_STRING, 0, 0, 0,0} -}; - /* Find schema_tables elment by name @@ -7228,12 +7544,14 @@ static my_bool find_schema_table_in_plugin(THD *thd, plugin_ref plugin, # pointer to 'schema_tables' element */ -ST_SCHEMA_TABLE *find_schema_table(THD *thd, const char* table_name) +ST_SCHEMA_TABLE *find_schema_table(THD *thd, const char* table_name, + bool *in_plugin) { schema_table_ref schema_table_a; ST_SCHEMA_TABLE *schema_table= schema_tables; DBUG_ENTER("find_schema_table"); + *in_plugin= false; for (; schema_table->table_name; schema_table++) { if (!my_strcasecmp(system_charset_info, @@ -7242,6 +7560,7 @@ ST_SCHEMA_TABLE *find_schema_table(THD *thd, const char* table_name) DBUG_RETURN(schema_table); } + *in_plugin= true; schema_table_a.table_name= table_name; if (plugin_foreach(thd, find_schema_table_in_plugin, MYSQL_INFORMATION_SCHEMA_PLUGIN, &schema_table_a)) @@ -7287,6 +7606,7 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) ST_SCHEMA_TABLE *schema_table= table_list->schema_table; ST_FIELD_INFO *fields_info= schema_table->fields_info; CHARSET_INFO *cs= system_charset_info; + MEM_ROOT *mem_root= thd->mem_root; DBUG_ENTER("create_schema_table"); for (; fields_info->field_name; fields_info++) @@ -7297,43 +7617,50 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_INT24: - if (!(item= new Item_return_int(fields_info->field_name, - fields_info->field_length, - fields_info->field_type, - fields_info->value))) + if (!(item= new (mem_root) + Item_return_int(thd, fields_info->field_name, + fields_info->field_length, + fields_info->field_type, + fields_info->value))) { DBUG_RETURN(0); } item->unsigned_flag= (fields_info->field_flags & MY_I_S_UNSIGNED); break; case MYSQL_TYPE_DATE: - if (!(item=new Item_return_date_time(fields_info->field_name, - strlen(fields_info->field_name), - fields_info->field_type))) + if (!(item=new (mem_root) + Item_return_date_time(thd, fields_info->field_name, + strlen(fields_info->field_name), + fields_info->field_type))) DBUG_RETURN(0); break; case MYSQL_TYPE_TIME: - if (!(item=new Item_return_date_time(fields_info->field_name, - strlen(fields_info->field_name), - fields_info->field_type))) + if (!(item=new (mem_root) + Item_return_date_time(thd, fields_info->field_name, + strlen(fields_info->field_name), + fields_info->field_type))) DBUG_RETURN(0); break; case MYSQL_TYPE_TIMESTAMP: case MYSQL_TYPE_DATETIME: - if (!(item=new Item_return_date_time(fields_info->field_name, - strlen(fields_info->field_name), - fields_info->field_type))) + if (!(item=new (mem_root) + Item_return_date_time(thd, fields_info->field_name, + strlen(fields_info->field_name), + fields_info->field_type))) DBUG_RETURN(0); break; case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: - if ((item= new Item_float(fields_info->field_name, 0.0, NOT_FIXED_DEC, - fields_info->field_length)) == NULL) + if ((item= new (mem_root) + Item_float(thd, fields_info->field_name, 0.0, + NOT_FIXED_DEC, + fields_info->field_length)) == NULL) DBUG_RETURN(NULL); break; case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_NEWDECIMAL: - if (!(item= new Item_decimal((longlong) fields_info->value, false))) + if (!(item= new (mem_root) + Item_decimal(thd, (longlong) fields_info->value, false))) { DBUG_RETURN(0); } @@ -7341,7 +7668,7 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) Create a type holder, as we want the type of the item to defined the type of the object, not the value */ - if (!(item= new Item_type_holder(thd, item))) + if (!(item= new (mem_root) Item_type_holder(thd, item))) DBUG_RETURN(0); item->unsigned_flag= (fields_info->field_flags & MY_I_S_UNSIGNED); item->decimals= fields_info->field_length%10; @@ -7357,8 +7684,9 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: - if (!(item= new Item_blob(fields_info->field_name, - fields_info->field_length))) + if (!(item= new (mem_root) + Item_blob(thd, fields_info->field_name, + fields_info->field_length))) { DBUG_RETURN(0); } @@ -7367,7 +7695,8 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) /* Don't let unimplemented types pass through. Could be a grave error. */ DBUG_ASSERT(fields_info->field_type == MYSQL_TYPE_STRING); - if (!(item= new Item_empty_string("", fields_info->field_length, cs))) + if (!(item= new (mem_root) + Item_empty_string(thd, "", fields_info->field_length, cs))) { DBUG_RETURN(0); } @@ -7375,7 +7704,7 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) strlen(fields_info->field_name), cs); break; } - field_list.push_back(item); + field_list.push_back(item, thd->mem_root); item->maybe_null= (fields_info->field_flags & MY_I_S_MAYBE_NULL); field_count++; } @@ -7386,7 +7715,7 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) tmp_table_param->field_count= field_count; tmp_table_param->schema_table= 1; SELECT_LEX *select_lex= thd->lex->current_select; - bool keep_row_order= sql_command_flags[thd->lex->sql_command] & CF_STATUS_COMMAND; + bool keep_row_order= is_show_command(thd); if (!(table= create_tmp_table(thd, tmp_table_param, field_list, (ORDER*) 0, 0, 0, (select_lex->options | thd->variables.option_bits | @@ -7395,7 +7724,7 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) DBUG_RETURN(0); my_bitmap_map* bitmaps= (my_bitmap_map*) thd->alloc(bitmap_buffer_size(field_count)); - bitmap_init(&table->def_read_set, (my_bitmap_map*) bitmaps, field_count, + my_bitmap_init(&table->def_read_set, (my_bitmap_map*) bitmaps, field_count, FALSE); table->read_set= &table->def_read_set; bitmap_clear_all(table->read_set); @@ -7419,7 +7748,7 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) 0 success */ -int make_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) +static int make_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) { ST_FIELD_INFO *field_info= schema_table->fields_info; Name_resolution_context *context= &thd->lex->select_lex.context; @@ -7427,8 +7756,8 @@ int make_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) { if (field_info->old_name) { - Item_field *field= new Item_field(context, - NullS, NullS, field_info->field_name); + Item_field *field= new (thd->mem_root) + Item_field(thd, context, NullS, NullS, field_info->field_name); if (field) { field->set_name(field_info->old_name, @@ -7454,7 +7783,7 @@ int make_schemata_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) { ST_FIELD_INFO *field_info= &schema_table->fields_info[1]; String buffer(tmp,sizeof(tmp), system_charset_info); - Item_field *field= new Item_field(context, + Item_field *field= new (thd->mem_root) Item_field(thd, context, NullS, NullS, field_info->field_name); if (!field || add_item_to_list(thd, field)) return 1; @@ -7489,7 +7818,7 @@ int make_table_names_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) buffer.append(lex->wild->ptr()); buffer.append(')'); } - Item_field *field= new Item_field(context, + Item_field *field= new (thd->mem_root) Item_field(thd, context, NullS, NullS, field_info->field_name); if (add_item_to_list(thd, field)) return 1; @@ -7498,7 +7827,7 @@ int make_table_names_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) { field->set_name(buffer.ptr(), buffer.length(), system_charset_info); field_info= &schema_table->fields_info[3]; - field= new Item_field(context, NullS, NullS, field_info->field_name); + field= new (thd->mem_root) Item_field(thd, context, NullS, NullS, field_info->field_name); if (add_item_to_list(thd, field)) return 1; field->set_name(field_info->old_name, strlen(field_info->old_name), @@ -7522,7 +7851,7 @@ int make_columns_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) *field_num == 18 || *field_num == 19)) continue; - Item_field *field= new Item_field(context, + Item_field *field= new (thd->mem_root) Item_field(thd, context, NullS, NullS, field_info->field_name); if (field) { @@ -7547,7 +7876,7 @@ int make_character_sets_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) for (; *field_num >= 0; field_num++) { field_info= &schema_table->fields_info[*field_num]; - Item_field *field= new Item_field(context, + Item_field *field= new (thd->mem_root) Item_field(thd, context, NullS, NullS, field_info->field_name); if (field) { @@ -7572,7 +7901,7 @@ int make_proc_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) for (; *field_num >= 0; field_num++) { field_info= &schema_table->fields_info[*field_num]; - Item_field *field= new Item_field(context, + Item_field *field= new (thd->mem_root) Item_field(thd, context, NullS, NullS, field_info->field_name); if (field) { @@ -7605,7 +7934,7 @@ int mysql_schema_table(THD *thd, LEX *lex, TABLE_LIST *table_list) { TABLE *table; DBUG_ENTER("mysql_schema_table"); - if (!(table= table_list->schema_table->create_table(thd, table_list))) + if (!(table= create_schema_table(thd, table_list))) DBUG_RETURN(1); table->s->tmp_table= SYSTEM_TMP_TABLE; table->grant.privilege= SELECT_ACL; @@ -7685,9 +8014,8 @@ int mysql_schema_table(THD *thd, LEX *lex, TABLE_LIST *table_list) */ int make_schema_select(THD *thd, SELECT_LEX *sel, - enum enum_schema_tables schema_table_idx) + ST_SCHEMA_TABLE *schema_table) { - ST_SCHEMA_TABLE *schema_table= get_schema_table(schema_table_idx); LEX_STRING db, table; DBUG_ENTER("make_schema_select"); DBUG_PRINT("enter", ("mysql_schema_select: %s", schema_table->table_name)); @@ -7695,21 +8023,160 @@ int make_schema_select(THD *thd, SELECT_LEX *sel, We have to make non const db_name & table_name because of lower_case_table_names */ - thd->make_lex_string(&db, INFORMATION_SCHEMA_NAME.str, - INFORMATION_SCHEMA_NAME.length, 0); - thd->make_lex_string(&table, schema_table->table_name, - strlen(schema_table->table_name), 0); - if (schema_table->old_format(thd, schema_table) || /* Handle old syntax */ - !sel->add_table_to_list(thd, new Table_ident(thd, db, table, 0), + if (!thd->make_lex_string(&db, INFORMATION_SCHEMA_NAME.str, + INFORMATION_SCHEMA_NAME.length)) + DBUG_RETURN(1); + + if (!thd->make_lex_string(&table, schema_table->table_name, + strlen(schema_table->table_name))) + DBUG_RETURN(1); + + if (schema_table->old_format(thd, schema_table)) + DBUG_RETURN(1); + + if (!sel->add_table_to_list(thd, new Table_ident(thd, db, table, 0), 0, 0, TL_READ, MDL_SHARED_READ)) - { DBUG_RETURN(1); + + sel->table_list.first->schema_table_reformed= 1; + DBUG_RETURN(0); +} + + +/* + Optimize reading from an I_S table. + + @detail + This function prepares a plan for populating an I_S table with + get_all_tables(). + + The plan is in IS_table_read_plan structure, it is saved in + tables->is_table_read_plan. + + @return + false - Ok + true - Out Of Memory + +*/ + +static bool optimize_for_get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) +{ + SELECT_LEX *lsel= tables->schema_select_lex; + ST_SCHEMA_TABLE *schema_table= tables->schema_table; + enum enum_schema_tables schema_table_idx; + IS_table_read_plan *plan; + DBUG_ENTER("get_all_tables"); + + if (!(plan= new IS_table_read_plan())) + DBUG_RETURN(1); + + tables->is_table_read_plan= plan; + + schema_table_idx= get_schema_table_idx(schema_table); + tables->table_open_method= get_table_open_method(tables, schema_table, + schema_table_idx); + DBUG_PRINT("open_method", ("%d", tables->table_open_method)); + + /* + this branch processes SHOW FIELDS, SHOW INDEXES commands. + see sql_parse.cc, prepare_schema_table() function where + this values are initialized + */ + if (lsel && lsel->table_list.first) + { + /* These do not need to have a query plan */ + plan->trivial_show_command= true; + goto end; } + + if (get_lookup_field_values(thd, cond, tables, &plan->lookup_field_vals)) + { + plan->no_rows= true; + goto end; + } + + DBUG_PRINT("info",("db_name='%s', table_name='%s'", + plan->lookup_field_vals.db_value.str, + plan->lookup_field_vals.table_value.str)); + + if (!plan->lookup_field_vals.wild_db_value && + !plan->lookup_field_vals.wild_table_value) + { + /* + if lookup value is empty string then + it's impossible table name or db name + */ + if ((plan->lookup_field_vals.db_value.str && + !plan->lookup_field_vals.db_value.str[0]) || + (plan->lookup_field_vals.table_value.str && + !plan->lookup_field_vals.table_value.str[0])) + { + plan->no_rows= true; + goto end; + } + } + + if (plan->has_db_lookup_value() && plan->has_table_lookup_value()) + plan->partial_cond= 0; + else + plan->partial_cond= make_cond_for_info_schema(thd, cond, tables); + +end: DBUG_RETURN(0); } /* + This is the optimizer part of get_schema_tables_result(). +*/ + +bool optimize_schema_tables_reads(JOIN *join) +{ + THD *thd= join->thd; + bool result= 0; + DBUG_ENTER("optimize_schema_tables_reads"); + + JOIN_TAB *tab; + for (tab= first_linear_tab(join, WITHOUT_BUSH_ROOTS, WITH_CONST_TABLES); + tab; + tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS)) + { + if (!tab->table || !tab->table->pos_in_table_list) + continue; + + TABLE_LIST *table_list= tab->table->pos_in_table_list; + if (table_list->schema_table && thd->fill_information_schema_tables()) + { + /* A value of 0 indicates a dummy implementation */ + if (table_list->schema_table->fill_table == 0) + continue; + + /* skip I_S optimizations specific to get_all_tables */ + if (table_list->schema_table->fill_table != get_all_tables) + continue; + + Item *cond= tab->select_cond; + if (tab->cache_select && tab->cache_select->cond) + { + /* + If join buffering is used, we should use the condition that is + attached to the join cache. Cache condition has a part of WHERE that + can be checked when we're populating this table. + join_tab->select_cond is of no interest, because it only has + conditions that depend on both this table and previous tables in the + join order. + */ + cond= tab->cache_select->cond; + } + + optimize_for_get_all_tables(thd, table_list, cond); + } + } + DBUG_RETURN(result); +} + + +/* Fill temporary schema tables before SELECT SYNOPSIS @@ -7717,6 +8184,10 @@ int make_schema_select(THD *thd, SELECT_LEX *sel, join join which use schema tables executed_place place where I_S table processed + SEE ALSO + The optimization part is done by get_schema_tables_result(). This function + is run on query execution. + RETURN FALSE success TRUE error @@ -7728,17 +8199,18 @@ bool get_schema_tables_result(JOIN *join, THD *thd= join->thd; LEX *lex= thd->lex; bool result= 0; - const char *old_proc_info; + PSI_stage_info org_stage; DBUG_ENTER("get_schema_tables_result"); Warnings_only_error_handler err_handler; thd->push_internal_handler(&err_handler); - old_proc_info= thd_proc_info(thd, "Filling schema table"); - + thd->backup_stage(&org_stage); + THD_STAGE_INFO(thd, stage_filling_schema_table); + JOIN_TAB *tab; for (tab= first_linear_tab(join, WITHOUT_BUSH_ROOTS, WITH_CONST_TABLES); tab; - tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS)) + tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS)) { if (!tab->table || !tab->table->pos_in_table_list) break; @@ -7793,10 +8265,22 @@ bool get_schema_tables_result(JOIN *join, } else table_list->table->file->stats.records= 0; + + Item *cond= tab->select_cond; + if (tab->cache_select && tab->cache_select->cond) + { + /* + If join buffering is used, we should use the condition that is + attached to the join cache. Cache condition has a part of WHERE that + can be checked when we're populating this table. + join_tab->select_cond is of no interest, because it only has + conditions that depend on both this table and previous tables in the + join order. + */ + cond= tab->cache_select->cond; + } - - if (table_list->schema_table->fill_table(thd, table_list, - tab->select_cond)) + if (table_list->schema_table->fill_table(thd, table_list, cond)) { result= 1; join->error= 1; @@ -7822,15 +8306,15 @@ bool get_schema_tables_result(JOIN *join, It also means that an audit plugin cannot process the error correctly either. See also thd->clear_error() */ - thd->warning_info->push_warning(thd, - thd->stmt_da->sql_errno(), - thd->stmt_da->get_sqlstate(), - MYSQL_ERROR::WARN_LEVEL_ERROR, - thd->stmt_da->message()); + thd->get_stmt_da()->push_warning(thd, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->get_sqlstate(), + Sql_condition::WARN_LEVEL_ERROR, + thd->get_stmt_da()->message()); } else if (result) my_error(ER_UNKNOWN_ERROR, MYF(0)); - thd_proc_info(thd, old_proc_info); + THD_STAGE_INFO(thd, org_stage); DBUG_RETURN(result); } @@ -7845,7 +8329,7 @@ static my_bool run_hton_fill_schema_table(THD *thd, plugin_ref plugin, { struct run_hton_fill_schema_table_args *args= (run_hton_fill_schema_table_args *) arg; - handlerton *hton= plugin_data(plugin, handlerton *); + handlerton *hton= plugin_hton(plugin); if (hton->fill_is_table && hton->state == SHOW_OPTION_YES) hton->fill_is_table(hton, thd, args->tables, args->cond, get_schema_table_idx(args->tables->schema_table)); @@ -7998,8 +8482,8 @@ ST_FIELD_INFO tables_fields_info[]= OPEN_FRM_ONLY}, {"CHECKSUM", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Checksum", OPEN_FULL_TABLE}, - {"CREATE_OPTIONS", 255, MYSQL_TYPE_STRING, 0, 1, "Create_options", - OPEN_FRM_ONLY}, + {"CREATE_OPTIONS", 2048, MYSQL_TYPE_STRING, 0, 1, "Create_options", + OPEN_FULL_TABLE}, {"TABLE_COMMENT", TABLE_COMMENT_MAXLEN, MYSQL_TYPE_STRING, 0, 0, "Comment", OPEN_FRM_ONLY}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} @@ -8071,6 +8555,23 @@ ST_FIELD_INFO collation_fields_info[]= }; +ST_FIELD_INFO applicable_roles_fields_info[]= +{ + {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, + {"ROLE_NAME", USERNAME_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, + {"IS_GRANTABLE", 3, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, + {"IS_DEFAULT", 3, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, SKIP_OPEN_TABLE}, + {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} +}; + + +ST_FIELD_INFO enabled_roles_fields_info[]= +{ + {"ROLE_NAME", USERNAME_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, SKIP_OPEN_TABLE}, + {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} +}; + + ST_FIELD_INFO engines_fields_info[]= { {"ENGINE", 64, MYSQL_TYPE_STRING, 0, 0, "Engine", SKIP_OPEN_TABLE}, @@ -8218,13 +8719,14 @@ ST_FIELD_INFO view_fields_info[]= OPEN_FRM_ONLY}, {"COLLATION_CONNECTION", MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY}, + {"ALGORITHM", 10, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} }; ST_FIELD_INFO user_privileges_fields_info[]= { - {"GRANTEE", 81, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, + {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"PRIVILEGE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"IS_GRANTABLE", 3, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, @@ -8234,7 +8736,7 @@ ST_FIELD_INFO user_privileges_fields_info[]= ST_FIELD_INFO schema_privileges_fields_info[]= { - {"GRANTEE", 81, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, + {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"PRIVILEGE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, @@ -8245,7 +8747,7 @@ ST_FIELD_INFO schema_privileges_fields_info[]= ST_FIELD_INFO table_privileges_fields_info[]= { - {"GRANTEE", 81, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, + {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, @@ -8257,7 +8759,7 @@ ST_FIELD_INFO table_privileges_fields_info[]= ST_FIELD_INFO column_privileges_fields_info[]= { - {"GRANTEE", 81, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, + {"GRANTEE", USERNAME_WITH_HOST_CHAR_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, @@ -8415,11 +8917,31 @@ ST_FIELD_INFO variables_fields_info[]= { {"VARIABLE_NAME", 64, MYSQL_TYPE_STRING, 0, 0, "Variable_name", SKIP_OPEN_TABLE}, - {"VARIABLE_VALUE", 1024, MYSQL_TYPE_STRING, 0, 1, "Value", SKIP_OPEN_TABLE}, + {"VARIABLE_VALUE", 2048, MYSQL_TYPE_STRING, 0, 0, "Value", SKIP_OPEN_TABLE}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} }; +ST_FIELD_INFO sysvars_fields_info[]= +{ + {"VARIABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, 0}, + {"SESSION_VALUE", 2048, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, 0}, + {"GLOBAL_VALUE", 2048, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, 0}, + {"GLOBAL_VALUE_ORIGIN", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, 0}, + {"DEFAULT_VALUE", 2048, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, 0}, + {"VARIABLE_SCOPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, 0}, + {"VARIABLE_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, 0}, + {"VARIABLE_COMMENT", TABLE_COMMENT_MAXLEN, MYSQL_TYPE_STRING, 0, 0, 0, 0}, + {"NUMERIC_MIN_VALUE", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, 0}, + {"NUMERIC_MAX_VALUE", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, 0}, + {"NUMERIC_BLOCK_SIZE", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, 0}, + {"ENUM_VALUE_LIST", 65535, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, 0}, + {"READ_ONLY", 3, MYSQL_TYPE_STRING, 0, 0, 0, 0}, + {"COMMAND_LINE_ARGUMENT", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, 0, 0}, + {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0} +}; + + ST_FIELD_INFO processlist_fields_info[]= { {"ID", 4, MYSQL_TYPE_LONGLONG, 0, 0, "Id", SKIP_OPEN_TABLE}, @@ -8439,6 +8961,12 @@ ST_FIELD_INFO processlist_fields_info[]= {"MAX_STAGE", 2, MYSQL_TYPE_TINY, 0, 0, "Max_stage", SKIP_OPEN_TABLE}, {"PROGRESS", 703, MYSQL_TYPE_DECIMAL, 0, 0, "Progress", SKIP_OPEN_TABLE}, + {"MEMORY_USED", 7, MYSQL_TYPE_LONG, 0, 0, "Memory_used", SKIP_OPEN_TABLE}, + {"EXAMINED_ROWS", 7, MYSQL_TYPE_LONG, 0, 0, "Examined_rows", SKIP_OPEN_TABLE}, + {"QUERY_ID", 4, MYSQL_TYPE_LONGLONG, 0, 0, 0, SKIP_OPEN_TABLE}, + {"INFO_BINARY", PROCESS_LIST_INFO_WIDTH, MYSQL_TYPE_BLOB, 0, 1, + "Info_binary", SKIP_OPEN_TABLE}, + {"TID", 4, MYSQL_TYPE_LONGLONG, 0, 0, "Tid", SKIP_OPEN_TABLE}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} }; @@ -8448,7 +8976,7 @@ ST_FIELD_INFO plugin_fields_info[]= {"PLUGIN_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Name", SKIP_OPEN_TABLE}, {"PLUGIN_VERSION", 20, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, - {"PLUGIN_STATUS", 10, MYSQL_TYPE_STRING, 0, 0, "Status", SKIP_OPEN_TABLE}, + {"PLUGIN_STATUS", 16, MYSQL_TYPE_STRING, 0, 0, "Status", SKIP_OPEN_TABLE}, {"PLUGIN_TYPE", 80, MYSQL_TYPE_STRING, 0, 0, "Type", SKIP_OPEN_TABLE}, {"PLUGIN_TYPE_VERSION", 20, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"PLUGIN_LIBRARY", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 1, "Library", @@ -8628,6 +9156,65 @@ ST_FIELD_INFO keycache_fields_info[]= }; +ST_FIELD_INFO show_explain_fields_info[]= +{ + /* field_name, length, type, value, field_flags, old_name*/ + {"id", 3, MYSQL_TYPE_LONGLONG, 0 /*value*/, MY_I_S_MAYBE_NULL, "id", + SKIP_OPEN_TABLE}, + {"select_type", 19, MYSQL_TYPE_STRING, 0 /*value*/, 0, "select_type", + SKIP_OPEN_TABLE}, + {"table", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0 /*value*/, MY_I_S_MAYBE_NULL, + "table", SKIP_OPEN_TABLE}, + {"type", 15, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE}, + {"possible_keys", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "possible_keys", SKIP_OPEN_TABLE}, + {"key", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "key", SKIP_OPEN_TABLE}, + {"key_len", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "key_len", SKIP_OPEN_TABLE}, + {"ref", NAME_CHAR_LEN*MAX_REF_PARTS, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "ref", SKIP_OPEN_TABLE}, + {"rows", 10, MYSQL_TYPE_LONGLONG, 0/*value*/, MY_I_S_MAYBE_NULL, "rows", + SKIP_OPEN_TABLE}, + {"Extra", 255, MYSQL_TYPE_STRING, 0/*value*/, 0 /*flags*/, "Extra", + SKIP_OPEN_TABLE}, + {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} +}; + + +#ifdef HAVE_SPATIAL +ST_FIELD_INFO geometry_columns_fields_info[]= +{ + {"F_TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY}, + {"F_TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY}, + {"F_TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY}, + {"F_GEOMETRY_COLUMN", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Field", + OPEN_FRM_ONLY}, + {"G_TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY}, + {"G_TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY}, + {"G_TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FRM_ONLY}, + {"G_GEOMETRY_COLUMN", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Field", + OPEN_FRM_ONLY}, + {"STORAGE_TYPE", 2, MYSQL_TYPE_TINY, 0, 0, 0, OPEN_FRM_ONLY}, + {"GEOMETRY_TYPE", 7, MYSQL_TYPE_LONG, 0, 0, 0, OPEN_FRM_ONLY}, + {"COORD_DIMENSION", 2, MYSQL_TYPE_TINY, 0, 0, 0, OPEN_FRM_ONLY}, + {"MAX_PPR", 2, MYSQL_TYPE_TINY, 0, 0, 0, OPEN_FRM_ONLY}, + {"SRID", 5, MYSQL_TYPE_SHORT, 0, 0, 0, OPEN_FRM_ONLY}, + {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0} +}; + + +ST_FIELD_INFO spatial_ref_sys_fields_info[]= +{ + {"SRID", 5, MYSQL_TYPE_SHORT, 0, 0, 0, SKIP_OPEN_TABLE}, + {"AUTH_NAME", FN_REFLEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, + {"AUTH_SRID", 5, MYSQL_TYPE_LONG, 0, 0, 0, SKIP_OPEN_TABLE}, + {"SRTEXT", 2048, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, + {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0} +}; +#endif /*HAVE_SPATIAL*/ + + /* Description of ST_FIELD_INFO in table.h @@ -8637,122 +9224,120 @@ ST_FIELD_INFO keycache_fields_info[]= ST_SCHEMA_TABLE schema_tables[]= { - {"CHARACTER_SETS", charsets_fields_info, create_schema_table, + {"ALL_PLUGINS", plugin_fields_info, 0, + fill_all_plugins, make_old_format, 0, 5, -1, 0, 0}, + {"APPLICABLE_ROLES", applicable_roles_fields_info, 0, + fill_schema_applicable_roles, 0, 0, -1, -1, 0, 0}, + {"CHARACTER_SETS", charsets_fields_info, 0, fill_schema_charsets, make_character_sets_old_format, 0, -1, -1, 0, 0}, - {"CLIENT_STATISTICS", client_stats_fields_info, create_schema_table, - fill_schema_client_stats, make_old_format, 0, -1, -1, 0, 0}, - {"COLLATIONS", collation_fields_info, create_schema_table, + {"COLLATIONS", collation_fields_info, 0, fill_schema_collation, make_old_format, 0, -1, -1, 0, 0}, {"COLLATION_CHARACTER_SET_APPLICABILITY", coll_charset_app_fields_info, - create_schema_table, fill_schema_coll_charset_app, 0, 0, -1, -1, 0, 0}, - {"COLUMNS", columns_fields_info, create_schema_table, + 0, fill_schema_coll_charset_app, 0, 0, -1, -1, 0, 0}, + {"COLUMNS", columns_fields_info, 0, get_all_tables, make_columns_old_format, get_schema_column_record, 1, 2, 0, OPTIMIZE_I_S_TABLE|OPEN_VIEW_FULL}, - {"COLUMN_PRIVILEGES", column_privileges_fields_info, create_schema_table, + {"COLUMN_PRIVILEGES", column_privileges_fields_info, 0, fill_schema_column_privileges, 0, 0, -1, -1, 0, 0}, - {"ENGINES", engines_fields_info, create_schema_table, + {"ENABLED_ROLES", enabled_roles_fields_info, 0, + fill_schema_enabled_roles, 0, 0, -1, -1, 0, 0}, + {"ENGINES", engines_fields_info, 0, fill_schema_engines, make_old_format, 0, -1, -1, 0, 0}, #ifdef HAVE_EVENT_SCHEDULER - {"EVENTS", events_fields_info, create_schema_table, + {"EVENTS", events_fields_info, 0, Events::fill_schema_events, make_old_format, 0, -1, -1, 0, 0}, #else - {"EVENTS", events_fields_info, create_schema_table, + {"EVENTS", events_fields_info, 0, 0, make_old_format, 0, -1, -1, 0, 0}, #endif - {"FILES", files_fields_info, create_schema_table, + {"EXPLAIN", show_explain_fields_info, 0, fill_show_explain, + make_old_format, 0, -1, -1, TRUE /*hidden*/ , 0}, + {"FILES", files_fields_info, 0, hton_fill_schema_table, 0, 0, -1, -1, 0, 0}, - {"GLOBAL_STATUS", variables_fields_info, create_schema_table, + {"GLOBAL_STATUS", variables_fields_info, 0, fill_status, make_old_format, 0, 0, -1, 0, 0}, - {"GLOBAL_VARIABLES", variables_fields_info, create_schema_table, + {"GLOBAL_VARIABLES", variables_fields_info, 0, fill_variables, make_old_format, 0, 0, -1, 0, 0}, - {"INDEX_STATISTICS", index_stats_fields_info, create_schema_table, - fill_schema_index_stats, make_old_format, 0, -1, -1, 0, 0}, - {"KEY_CACHES", keycache_fields_info, create_schema_table, - fill_key_cache_tables, make_old_format, 0, -1,-1, 0, 0}, - {"KEY_COLUMN_USAGE", key_column_usage_fields_info, create_schema_table, + {"KEY_CACHES", keycache_fields_info, 0, + fill_key_cache_tables, 0, 0, -1,-1, 0, 0}, + {"KEY_COLUMN_USAGE", key_column_usage_fields_info, 0, get_all_tables, 0, get_schema_key_column_usage_record, 4, 5, 0, OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, - {"OPEN_TABLES", open_tables_fields_info, create_schema_table, + {"OPEN_TABLES", open_tables_fields_info, 0, fill_open_tables, make_old_format, 0, -1, -1, 1, 0}, - {"PARAMETERS", parameters_fields_info, create_schema_table, + {"PARAMETERS", parameters_fields_info, 0, fill_schema_proc, 0, 0, -1, -1, 0, 0}, - {"PARTITIONS", partitions_fields_info, create_schema_table, + {"PARTITIONS", partitions_fields_info, 0, get_all_tables, 0, get_schema_partitions_record, 1, 2, 0, OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, - {"PLUGINS", plugin_fields_info, create_schema_table, + {"PLUGINS", plugin_fields_info, 0, fill_plugins, make_old_format, 0, -1, -1, 0, 0}, - {"PROCESSLIST", processlist_fields_info, create_schema_table, + {"PROCESSLIST", processlist_fields_info, 0, fill_schema_processlist, make_old_format, 0, -1, -1, 0, 0}, - {"PROFILING", query_profile_statistics_info, create_schema_table, + {"PROFILING", query_profile_statistics_info, 0, fill_query_profile_statistics_info, make_profile_table_for_show, NULL, -1, -1, false, 0}, {"REFERENTIAL_CONSTRAINTS", referential_constraints_fields_info, - create_schema_table, get_all_tables, 0, get_referential_constraints_record, + 0, get_all_tables, 0, get_referential_constraints_record, 1, 9, 0, OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, - {"ROUTINES", proc_fields_info, create_schema_table, + {"ROUTINES", proc_fields_info, 0, fill_schema_proc, make_proc_old_format, 0, -1, -1, 0, 0}, - {"SCHEMATA", schema_fields_info, create_schema_table, + {"SCHEMATA", schema_fields_info, 0, fill_schema_schemata, make_schemata_old_format, 0, 1, -1, 0, 0}, - {"SCHEMA_PRIVILEGES", schema_privileges_fields_info, create_schema_table, + {"SCHEMA_PRIVILEGES", schema_privileges_fields_info, 0, fill_schema_schema_privileges, 0, 0, -1, -1, 0, 0}, - {"SESSION_STATUS", variables_fields_info, create_schema_table, + {"SESSION_STATUS", variables_fields_info, 0, fill_status, make_old_format, 0, 0, -1, 0, 0}, - {"SESSION_VARIABLES", variables_fields_info, create_schema_table, + {"SESSION_VARIABLES", variables_fields_info, 0, fill_variables, make_old_format, 0, 0, -1, 0, 0}, - {"STATISTICS", stat_fields_info, create_schema_table, + {"STATISTICS", stat_fields_info, 0, get_all_tables, make_old_format, get_schema_stat_record, 1, 2, 0, OPEN_TABLE_ONLY|OPTIMIZE_I_S_TABLE}, - {"STATUS", variables_fields_info, create_schema_table, fill_status, - make_old_format, 0, 0, -1, 1, 0}, - {"TABLES", tables_fields_info, create_schema_table, + {"SYSTEM_VARIABLES", sysvars_fields_info, 0, + fill_sysvars, make_old_format, 0, 0, -1, 0, 0}, + {"TABLES", tables_fields_info, 0, get_all_tables, make_old_format, get_schema_tables_record, 1, 2, 0, OPTIMIZE_I_S_TABLE}, - {"TABLESPACES", tablespaces_fields_info, create_schema_table, + {"TABLESPACES", tablespaces_fields_info, 0, hton_fill_schema_table, 0, 0, -1, -1, 0, 0}, - {"TABLE_CONSTRAINTS", table_constraints_fields_info, create_schema_table, + {"TABLE_CONSTRAINTS", table_constraints_fields_info, 0, get_all_tables, 0, get_schema_constraints_record, 3, 4, 0, OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, - {"TABLE_NAMES", table_names_fields_info, create_schema_table, + {"TABLE_NAMES", table_names_fields_info, 0, get_all_tables, make_table_names_old_format, 0, 1, 2, 1, OPTIMIZE_I_S_TABLE}, - {"TABLE_PRIVILEGES", table_privileges_fields_info, create_schema_table, + {"TABLE_PRIVILEGES", table_privileges_fields_info, 0, fill_schema_table_privileges, 0, 0, -1, -1, 0, 0}, - {"TABLE_STATISTICS", table_stats_fields_info, create_schema_table, - fill_schema_table_stats, make_old_format, 0, -1, -1, 0, 0}, - {"TRIGGERS", triggers_fields_info, create_schema_table, + {"TRIGGERS", triggers_fields_info, 0, get_all_tables, make_old_format, get_schema_triggers_record, 5, 6, 0, OPEN_TRIGGER_ONLY|OPTIMIZE_I_S_TABLE}, - {"USER_PRIVILEGES", user_privileges_fields_info, create_schema_table, + {"USER_PRIVILEGES", user_privileges_fields_info, 0, fill_schema_user_privileges, 0, 0, -1, -1, 0, 0}, - {"USER_STATISTICS", user_stats_fields_info, create_schema_table, - fill_schema_user_stats, make_old_format, 0, -1, -1, 0, 0}, - {"VARIABLES", variables_fields_info, create_schema_table, fill_variables, - make_old_format, 0, 0, -1, 1, 0}, - {"VIEWS", view_fields_info, create_schema_table, + {"VIEWS", view_fields_info, 0, get_all_tables, 0, get_schema_views_record, 1, 2, 0, OPEN_VIEW_ONLY|OPTIMIZE_I_S_TABLE}, +#ifdef HAVE_SPATIAL + {"GEOMETRY_COLUMNS", geometry_columns_fields_info, 0, + get_all_tables, make_columns_old_format, get_geometry_column_record, + 1, 2, 0, OPTIMIZE_I_S_TABLE|OPEN_VIEW_FULL}, + {"SPATIAL_REF_SYS", spatial_ref_sys_fields_info, 0, + fill_spatial_ref_sys, make_old_format, 0, -1, -1, 0, 0}, +#endif /*HAVE_SPATIAL*/ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class List_iterator_fast<char>; -template class List<char>; -#endif - int initialize_schema_table(st_plugin_int *plugin) { ST_SCHEMA_TABLE *schema_table; DBUG_ENTER("initialize_schema_table"); if (!(schema_table= (ST_SCHEMA_TABLE *)my_malloc(sizeof(ST_SCHEMA_TABLE), - MYF(MY_WME | MY_ZEROFILL)))) + MYF(MY_WME | MY_ZEROFILL)))) DBUG_RETURN(1); /* Historical Requirement */ plugin->data= schema_table; // shortcut for the future if (plugin->plugin->init) { - schema_table->create_table= create_schema_table; - schema_table->old_format= make_old_format; schema_table->idx_field1= -1, schema_table->idx_field2= -1; @@ -8768,6 +9353,14 @@ int initialize_schema_table(st_plugin_int *plugin) DBUG_RETURN(1); } + if (!schema_table->old_format) + for (ST_FIELD_INFO *f= schema_table->fields_info; f->field_name; f++) + if (f->old_name && f->old_name[0]) + { + schema_table->old_format= make_old_format; + break; + } + /* Make sure the plugin name is not set inside the init() function. */ schema_table->table_name= plugin->name.str; } @@ -8813,10 +9406,8 @@ static bool show_create_trigger_impl(THD *thd, int trigger_idx) { int ret_code; - Protocol *p= thd->protocol; List<Item> fields; - LEX_STRING trg_name; ulonglong trg_sql_mode; LEX_STRING trg_sql_mode_str; @@ -8824,8 +9415,8 @@ static bool show_create_trigger_impl(THD *thd, LEX_STRING trg_client_cs_name; LEX_STRING trg_connection_cl_name; LEX_STRING trg_db_cl_name; - CHARSET_INFO *trg_client_cs; + MEM_ROOT *mem_root= thd->mem_root; /* TODO: Check privileges here. This functionality will be added by @@ -8857,8 +9448,11 @@ static bool show_create_trigger_impl(THD *thd, /* Send header. */ - fields.push_back(new Item_empty_string("Trigger", NAME_LEN)); - fields.push_back(new Item_empty_string("sql_mode", trg_sql_mode_str.length)); + fields.push_back(new (mem_root) Item_empty_string(thd, "Trigger", NAME_LEN), + mem_root); + fields.push_back(new (mem_root) + Item_empty_string(thd, "sql_mode", trg_sql_mode_str.length), + mem_root); { /* @@ -8867,24 +9461,33 @@ static bool show_create_trigger_impl(THD *thd, */ Item_empty_string *stmt_fld= - new Item_empty_string("SQL Original Statement", - max(trg_sql_original_stmt.length, 1024)); + new (mem_root) Item_empty_string(thd, "SQL Original Statement", + MY_MAX(trg_sql_original_stmt.length, + 1024)); stmt_fld->maybe_null= TRUE; - fields.push_back(stmt_fld); + fields.push_back(stmt_fld, mem_root); } - fields.push_back(new Item_empty_string("character_set_client", - MY_CS_NAME_SIZE)); + fields.push_back(new (mem_root) + Item_empty_string(thd, "character_set_client", + MY_CS_NAME_SIZE), + mem_root); - fields.push_back(new Item_empty_string("collation_connection", - MY_CS_NAME_SIZE)); + fields.push_back(new (mem_root) + Item_empty_string(thd, "collation_connection", + MY_CS_NAME_SIZE), + mem_root); - fields.push_back(new Item_empty_string("Database Collation", - MY_CS_NAME_SIZE)); + fields.push_back(new (mem_root) + Item_empty_string(thd, "Database Collation", + MY_CS_NAME_SIZE), + mem_root); - if (p->send_result_set_metadata(&fields, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + if (p->send_result_set_metadata(&fields, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) return TRUE; /* Send data. */ @@ -8975,6 +9578,9 @@ TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name) db= trg_name->m_db; db.str= thd->strmake(db.str, db.length); + if (lower_case_table_names) + db.length= my_casedn_str(files_charset_info, db.str); + tbl_name.str= thd->strmake(tbl_name.str, tbl_name.length); if (db.str == NULL || tbl_name.str == NULL) @@ -9213,3 +9819,88 @@ static void get_cs_converted_string_value(THD *thd, return; } #endif + +/** + Dumps a text description of a thread, its security context + (user, host) and the current query. + + @param thd thread context + @param buffer pointer to preferred result buffer + @param length length of buffer + @param max_query_len how many chars of query to copy (0 for all) + + @return Pointer to string +*/ + +extern "C" +char *thd_get_error_context_description(THD *thd, char *buffer, + unsigned int length, + unsigned int max_query_len) +{ + String str(buffer, length, &my_charset_latin1); + const Security_context *sctx= &thd->main_security_ctx; + char header[256]; + int len; + + mysql_mutex_lock(&LOCK_thread_count); + + len= my_snprintf(header, sizeof(header), + "MySQL thread id %lu, OS thread handle 0x%lx, query id %lu", + thd->thread_id, (ulong) thd->real_id, (ulong) thd->query_id); + str.length(0); + str.append(header, len); + + if (sctx->host) + { + str.append(' '); + str.append(sctx->host); + } + + if (sctx->ip) + { + str.append(' '); + str.append(sctx->ip); + } + + if (sctx->user) + { + str.append(' '); + str.append(sctx->user); + } + + /* Don't wait if LOCK_thd_data is used as this could cause a deadlock */ + if (!mysql_mutex_trylock(&thd->LOCK_thd_data)) + { + if (const char *info= thread_state_info(thd)) + { + str.append(' '); + str.append(info); + } + + if (thd->query()) + { + if (max_query_len < 1) + len= thd->query_length(); + else + len= MY_MIN(thd->query_length(), max_query_len); + str.append('\n'); + str.append(thd->query(), len); + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + } + mysql_mutex_unlock(&LOCK_thread_count); + + if (str.c_ptr_safe() == buffer) + return buffer; + + /* + We have to copy the new string to the destination buffer because the string + was reallocated to a larger buffer to be able to fit. + */ + DBUG_ASSERT(buffer != NULL); + length= MY_MIN(str.length(), length-1); + memcpy(buffer, str.c_ptr_quick(), length); + /* Make sure that the new string is null terminated */ + buffer[length]= '\0'; + return buffer; +} diff --git a/sql/sql_show.h b/sql/sql_show.h index 0a0d4a4d4dd..dbae2a42b39 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -20,6 +20,7 @@ #include "sql_list.h" /* List */ #include "handler.h" /* enum_schema_tables */ #include "table.h" /* enum_schema_table_state */ +#include "my_apc.h" /* Forward declarations */ class JOIN; @@ -27,21 +28,13 @@ class String; class THD; class sp_name; struct TABLE_LIST; -struct st_ha_create_information; typedef class st_select_lex SELECT_LEX; -typedef st_ha_create_information HA_CREATE_INFO; struct LEX; typedef struct st_mysql_show_var SHOW_VAR; typedef struct st_schema_table ST_SCHEMA_TABLE; struct TABLE; typedef struct system_status_var STATUS_VAR; -enum find_files_result { - FIND_FILES_OK, - FIND_FILES_OOM, - FIND_FILES_DIR -}; - /* Used by handlers to store things in schema tables */ #define IS_FILES_FILE_ID 0 #define IS_FILES_FILE_NAME 1 @@ -82,12 +75,10 @@ enum find_files_result { #define IS_FILES_STATUS 36 #define IS_FILES_EXTRA 37 -find_files_result find_files(THD *thd, List<LEX_STRING> *files, const char *db, - const char *path, const char *wild, bool dir); - -int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, - HA_CREATE_INFO *create_info_arg, bool show_database); -int view_store_create_info(THD *thd, TABLE_LIST *table, String *buff); +typedef enum { WITHOUT_DB_NAME, WITH_DB_NAME } enum_with_db_name; +int show_create_table(THD *thd, TABLE_LIST *table_list, String *packet, + Table_specification_st *create_info_arg, + enum_with_db_name with_db_name); int copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table); @@ -95,8 +86,13 @@ bool append_identifier(THD *thd, String *packet, const char *name, uint length); void mysqld_list_fields(THD *thd,TABLE_LIST *table, const char *wild); int mysqld_dump_create_info(THD *thd, TABLE_LIST *table_list, int fd); +bool mysqld_show_create_get_fields(THD *thd, TABLE_LIST *table_list, + List<Item> *field_list, String *buffer); bool mysqld_show_create(THD *thd, TABLE_LIST *table_list); -bool mysqld_show_create_db(THD *thd, char *dbname, HA_CREATE_INFO *create); +void mysqld_show_create_db_get_fields(THD *thd, List<Item> *field_list); +bool mysqld_show_create_db(THD *thd, LEX_STRING *db_name, + LEX_STRING *orig_db_name, + const DDL_options_st &options); void mysqld_list_processes(THD *thd,const char *user,bool verbose); int mysqld_show_status(THD *thd); @@ -106,7 +102,7 @@ bool mysqld_show_authors(THD *thd); bool mysqld_show_contributors(THD *thd); bool mysqld_show_privileges(THD *thd); char *make_backup_log_name(char *buff, const char *name, const char* log_ext); -void calc_sum_of_all_status(STATUS_VAR *to); +uint calc_sum_of_all_status(STATUS_VAR *to); void append_definer(THD *thd, String *buffer, const LEX_STRING *definer_user, const LEX_STRING *definer_host); int add_status_vars(SHOW_VAR *list); @@ -120,18 +116,119 @@ void view_store_options(THD *thd, TABLE_LIST *table, String *buff); void init_fill_schema_files_row(TABLE* table); bool schema_table_store_record(THD *thd, TABLE *table); void initialize_information_schema_acl(); +COND *make_cond_for_info_schema(THD *thd, COND *cond, TABLE_LIST *table); + +ST_SCHEMA_TABLE *find_schema_table(THD *thd, const char* table_name, bool *in_plugin); +static inline ST_SCHEMA_TABLE *find_schema_table(THD *thd, const char* table_name) +{ bool unused; return find_schema_table(thd, table_name, &unused); } -ST_SCHEMA_TABLE *find_schema_table(THD *thd, const char* table_name); ST_SCHEMA_TABLE *get_schema_table(enum enum_schema_tables schema_table_idx); int make_schema_select(THD *thd, SELECT_LEX *sel, - enum enum_schema_tables schema_table_idx); + ST_SCHEMA_TABLE *schema_table); int mysql_schema_table(THD *thd, LEX *lex, TABLE_LIST *table_list); bool get_schema_tables_result(JOIN *join, enum enum_schema_table_state executed_place); enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table); +TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list); /* These functions were under INNODB_COMPATIBILITY_HOOKS */ int get_quote_char_for_identifier(THD *thd, const char *name, uint length); +THD *find_thread_by_id(longlong id, bool query_id= false); + +class select_result_explain_buffer; +/* + SHOW EXPLAIN request object. +*/ + +class Show_explain_request : public Apc_target::Apc_call +{ +public: + THD *target_thd; /* thd that we're running SHOW EXPLAIN for */ + THD *request_thd; /* thd that run SHOW EXPLAIN command */ + + /* If true, there was some error when producing EXPLAIN output. */ + bool failed_to_produce; + + /* SHOW EXPLAIN will be stored here */ + select_result_explain_buffer *explain_buf; + + /* Query that we've got SHOW EXPLAIN for */ + String query_str; + + /* Overloaded virtual function */ + void call_in_target_thread(); +}; + +/** + Condition pushdown used for INFORMATION_SCHEMA / SHOW queries. + This structure is to implement an optimization when + accessing data dictionary data in the INFORMATION_SCHEMA + or SHOW commands. + When the query contain a TABLE_SCHEMA or TABLE_NAME clause, + narrow the search for data based on the constraints given. +*/ +typedef struct st_lookup_field_values +{ + /** + Value of a TABLE_SCHEMA clause. + Note that this value length may exceed @c NAME_LEN. + @sa wild_db_value + */ + LEX_STRING db_value; + /** + Value of a TABLE_NAME clause. + Note that this value length may exceed @c NAME_LEN. + @sa wild_table_value + */ + LEX_STRING table_value; + /** + True when @c db_value is a LIKE clause, + false when @c db_value is an '=' clause. + */ + bool wild_db_value; + /** + True when @c table_value is a LIKE clause, + false when @c table_value is an '=' clause. + */ + bool wild_table_value; +} LOOKUP_FIELD_VALUES; + + +/* + INFORMATION_SCHEMA: Execution plan for get_all_tables() call +*/ + +class IS_table_read_plan : public Sql_alloc +{ +public: + IS_table_read_plan() : no_rows(false), trivial_show_command(FALSE) {} + + bool no_rows; + /* + For EXPLAIN only: For SHOW KEYS and SHOW COLUMNS, we know which + db_name.table_name will be read, however for some reason we don't + set the fields in this->lookup_field_vals. + In order to not have JOIN::save_explain_data() walking over uninitialized + data, we set trivial_show_command=true. + */ + bool trivial_show_command; + + LOOKUP_FIELD_VALUES lookup_field_vals; + Item *partial_cond; + + bool has_db_lookup_value() + { + return (lookup_field_vals.db_value.length && + !lookup_field_vals.wild_db_value); + } + bool has_table_lookup_value() + { + return (lookup_field_vals.table_value.length && + !lookup_field_vals.wild_table_value); + } +}; + +bool optimize_schema_tables_reads(JOIN *join); /* Handle the ignored database directories list for SHOW/I_S. */ bool ignore_db_dirs_init(); diff --git a/sql/sql_signal.cc b/sql/sql_signal.cc index e671ad9526f..1b7edbee54a 100644 --- a/sql/sql_signal.cc +++ b/sql/sql_signal.cc @@ -13,6 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <my_global.h> #include "sql_priv.h" #include "sp_head.h" #include "sp_pcontext.h" @@ -62,20 +63,6 @@ const LEX_STRING Diag_condition_item_names[]= { C_STRING_WITH_LEN("TRIGGER_SCHEMA") } }; -const LEX_STRING Diag_statement_item_names[]= -{ - { C_STRING_WITH_LEN("NUMBER") }, - { C_STRING_WITH_LEN("MORE") }, - { C_STRING_WITH_LEN("COMMAND_FUNCTION") }, - { C_STRING_WITH_LEN("COMMAND_FUNCTION_CODE") }, - { C_STRING_WITH_LEN("DYNAMIC_FUNCTION") }, - { C_STRING_WITH_LEN("DYNAMIC_FUNCTION_CODE") }, - { C_STRING_WITH_LEN("ROW_COUNT") }, - { C_STRING_WITH_LEN("TRANSACTIONS_COMMITTED") }, - { C_STRING_WITH_LEN("TRANSACTIONS_ROLLED_BACK") }, - { C_STRING_WITH_LEN("TRANSACTION_ACTIVE") } -}; - Set_signal_information::Set_signal_information( const Set_signal_information& set) @@ -88,10 +75,11 @@ void Set_signal_information::clear() memset(m_item, 0, sizeof(m_item)); } -void Signal_common::assign_defaults(MYSQL_ERROR *cond, - bool set_level_code, - MYSQL_ERROR::enum_warning_level level, - int sqlcode) +void +Sql_cmd_common_signal::assign_defaults(Sql_condition *cond, + bool set_level_code, + Sql_condition::enum_warning_level level, + int sqlcode) { if (set_level_code) { @@ -102,7 +90,7 @@ void Signal_common::assign_defaults(MYSQL_ERROR *cond, cond->set_builtin_message_text(ER(sqlcode)); } -void Signal_common::eval_defaults(THD *thd, MYSQL_ERROR *cond) +void Sql_cmd_common_signal::eval_defaults(THD *thd, Sql_condition *cond) { DBUG_ASSERT(cond); @@ -114,8 +102,8 @@ void Signal_common::eval_defaults(THD *thd, MYSQL_ERROR *cond) /* SIGNAL is restricted in sql_yacc.yy to only signal SQLSTATE conditions. */ - DBUG_ASSERT(m_cond->type == sp_cond_type::state); - sqlstate= m_cond->sqlstate; + DBUG_ASSERT(m_cond->type == sp_condition_value::SQLSTATE); + sqlstate= m_cond->sql_state; cond->set_sqlstate(sqlstate); } else @@ -129,19 +117,19 @@ void Signal_common::eval_defaults(THD *thd, MYSQL_ERROR *cond) { /* SQLSTATE class "01": warning. */ assign_defaults(cond, set_defaults, - MYSQL_ERROR::WARN_LEVEL_WARN, ER_SIGNAL_WARN); + Sql_condition::WARN_LEVEL_WARN, ER_SIGNAL_WARN); } else if ((sqlstate[0] == '0') && (sqlstate[1] == '2')) { /* SQLSTATE class "02": not found. */ assign_defaults(cond, set_defaults, - MYSQL_ERROR::WARN_LEVEL_ERROR, ER_SIGNAL_NOT_FOUND); + Sql_condition::WARN_LEVEL_ERROR, ER_SIGNAL_NOT_FOUND); } else { /* other SQLSTATE classes : error. */ assign_defaults(cond, set_defaults, - MYSQL_ERROR::WARN_LEVEL_ERROR, ER_SIGNAL_EXCEPTION); + Sql_condition::WARN_LEVEL_ERROR, ER_SIGNAL_EXCEPTION); } } @@ -193,16 +181,9 @@ static bool assign_fixed_string(MEM_ROOT *mem_root, dst_str= (char*) alloc_root(mem_root, dst_len + 1); if (dst_str) { - const char* well_formed_error_pos; - const char* cannot_convert_error_pos; - const char* from_end_pos; - - dst_copied= well_formed_copy_nchars(dst_cs, dst_str, dst_len, - src_cs, src_str, src_len, - numchars, - & well_formed_error_pos, - & cannot_convert_error_pos, - & from_end_pos); + dst_copied= String_copier().well_formed_copy(dst_cs, dst_str, dst_len, + src_cs, src_str, src_len, + numchars); DBUG_ASSERT(dst_copied <= dst_len); dst_len= dst_copied; /* In case the copy truncated the data */ dst_str[dst_copied]= '\0'; @@ -243,8 +224,7 @@ static int assign_condition_item(MEM_ROOT *mem_root, const char* name, THD *thd, truncated= assign_fixed_string(mem_root, & my_charset_utf8_bin, 64, ci, str); if (truncated) { - if (thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES)) + if (thd->is_strict_mode()) { thd->raise_error_printf(ER_COND_ITEM_TOO_LONG, name); DBUG_RETURN(1); @@ -257,26 +237,26 @@ static int assign_condition_item(MEM_ROOT *mem_root, const char* name, THD *thd, } -int Signal_common::eval_signal_informations(THD *thd, MYSQL_ERROR *cond) +int Sql_cmd_common_signal::eval_signal_informations(THD *thd, Sql_condition *cond) { struct cond_item_map { enum enum_diag_condition_item_name m_item; - String MYSQL_ERROR::*m_member; + String Sql_condition::*m_member; }; static cond_item_map map[]= { - { DIAG_CLASS_ORIGIN, & MYSQL_ERROR::m_class_origin }, - { DIAG_SUBCLASS_ORIGIN, & MYSQL_ERROR::m_subclass_origin }, - { DIAG_CONSTRAINT_CATALOG, & MYSQL_ERROR::m_constraint_catalog }, - { DIAG_CONSTRAINT_SCHEMA, & MYSQL_ERROR::m_constraint_schema }, - { DIAG_CONSTRAINT_NAME, & MYSQL_ERROR::m_constraint_name }, - { DIAG_CATALOG_NAME, & MYSQL_ERROR::m_catalog_name }, - { DIAG_SCHEMA_NAME, & MYSQL_ERROR::m_schema_name }, - { DIAG_TABLE_NAME, & MYSQL_ERROR::m_table_name }, - { DIAG_COLUMN_NAME, & MYSQL_ERROR::m_column_name }, - { DIAG_CURSOR_NAME, & MYSQL_ERROR::m_cursor_name } + { DIAG_CLASS_ORIGIN, & Sql_condition::m_class_origin }, + { DIAG_SUBCLASS_ORIGIN, & Sql_condition::m_subclass_origin }, + { DIAG_CONSTRAINT_CATALOG, & Sql_condition::m_constraint_catalog }, + { DIAG_CONSTRAINT_SCHEMA, & Sql_condition::m_constraint_schema }, + { DIAG_CONSTRAINT_NAME, & Sql_condition::m_constraint_name }, + { DIAG_CATALOG_NAME, & Sql_condition::m_catalog_name }, + { DIAG_SCHEMA_NAME, & Sql_condition::m_schema_name }, + { DIAG_TABLE_NAME, & Sql_condition::m_table_name }, + { DIAG_COLUMN_NAME, & Sql_condition::m_column_name }, + { DIAG_CURSOR_NAME, & Sql_condition::m_cursor_name } }; Item *set; @@ -289,7 +269,7 @@ int Signal_common::eval_signal_informations(THD *thd, MYSQL_ERROR *cond) String *member; const LEX_STRING *name; - DBUG_ENTER("Signal_common::eval_signal_informations"); + DBUG_ENTER("Sql_cmd_common_signal::eval_signal_informations"); for (i= FIRST_DIAG_SET_PROPERTY; i <= LAST_DIAG_SET_PROPERTY; @@ -348,8 +328,7 @@ int Signal_common::eval_signal_informations(THD *thd, MYSQL_ERROR *cond) & utf8_text, str); if (truncated) { - if (thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES)) + if (thd->is_strict_mode()) { thd->raise_error_printf(ER_COND_ITEM_TOO_LONG, "MESSAGE_TEXT"); @@ -362,7 +341,7 @@ int Signal_common::eval_signal_informations(THD *thd, MYSQL_ERROR *cond) /* See the comments - "Design notes about MYSQL_ERROR::m_message_text." + "Design notes about Sql_condition::m_message_text." in file sql_error.cc */ String converted_text; @@ -415,23 +394,23 @@ end: DBUG_RETURN(result); } -bool Signal_common::raise_condition(THD *thd, MYSQL_ERROR *cond) +bool Sql_cmd_common_signal::raise_condition(THD *thd, Sql_condition *cond) { bool result= TRUE; - DBUG_ENTER("Signal_common::raise_condition"); + DBUG_ENTER("Sql_cmd_common_signal::raise_condition"); - DBUG_ASSERT(m_lex->query_tables == NULL); + DBUG_ASSERT(thd->lex->query_tables == NULL); eval_defaults(thd, cond); if (eval_signal_informations(thd, cond)) DBUG_RETURN(result); /* SIGNAL should not signal WARN_LEVEL_NOTE */ - DBUG_ASSERT((cond->m_level == MYSQL_ERROR::WARN_LEVEL_WARN) || - (cond->m_level == MYSQL_ERROR::WARN_LEVEL_ERROR)); + DBUG_ASSERT((cond->m_level == Sql_condition::WARN_LEVEL_WARN) || + (cond->m_level == Sql_condition::WARN_LEVEL_ERROR)); - MYSQL_ERROR *raised= NULL; + Sql_condition *raised= NULL; raised= thd->raise_condition(cond->get_sql_errno(), cond->get_sqlstate(), cond->get_level(), @@ -439,7 +418,7 @@ bool Signal_common::raise_condition(THD *thd, MYSQL_ERROR *cond) if (raised) raised->copy_opt_attributes(cond); - if (cond->m_level == MYSQL_ERROR::WARN_LEVEL_WARN) + if (cond->m_level == Sql_condition::WARN_LEVEL_WARN) { my_ok(thd); result= FALSE; @@ -448,12 +427,12 @@ bool Signal_common::raise_condition(THD *thd, MYSQL_ERROR *cond) DBUG_RETURN(result); } -bool Signal_statement::execute(THD *thd) +bool Sql_cmd_signal::execute(THD *thd) { bool result= TRUE; - MYSQL_ERROR cond(thd->mem_root); + Sql_condition cond(thd->mem_root); - DBUG_ENTER("Signal_statement::execute"); + DBUG_ENTER("Sql_cmd_signal::execute"); /* WL#2110 SIGNAL specification says: @@ -467,9 +446,9 @@ bool Signal_statement::execute(THD *thd) This has roots in the SQL standard specification for SIGNAL. */ - thd->stmt_da->reset_diagnostics_area(); + thd->get_stmt_da()->reset_diagnostics_area(); thd->set_row_count_func(0); - thd->warning_info->clear_warning_info(thd->query_id); + thd->get_stmt_da()->clear_warning_info(thd->query_id); result= raise_condition(thd, &cond); @@ -477,14 +456,27 @@ bool Signal_statement::execute(THD *thd) } -bool Resignal_statement::execute(THD *thd) +/** + Execute RESIGNAL SQL-statement. + + @param thd Thread context. + + @return Error status + @retval true in case of error + @retval false on success +*/ + +bool Sql_cmd_resignal::execute(THD *thd) { - Sql_condition_info *signaled; + Diagnostics_area *da= thd->get_stmt_da(); + const sp_rcontext::Sql_condition_info *signaled; int result= TRUE; DBUG_ENTER("Resignal_statement::execute"); - thd->warning_info->m_warn_id= thd->query_id; + // This is a way to force sql_conditions from the current Warning_info to be + // passed to the caller's Warning_info. + da->set_warning_info_id(thd->query_id); if (! thd->spcont || ! (signaled= thd->spcont->raised_condition())) { @@ -492,22 +484,38 @@ bool Resignal_statement::execute(THD *thd) DBUG_RETURN(result); } - MYSQL_ERROR signaled_err(thd->mem_root); - signaled_err.set(signaled->m_sql_errno, - signaled->m_sql_state, - signaled->m_level, - signaled->m_message); + Sql_condition signaled_err(thd->mem_root); + signaled_err.set(signaled->sql_errno, + signaled->sql_state, + signaled->level, + signaled->message); - if (m_cond == NULL) + if (m_cond) { - /* RESIGNAL without signal_value */ - result= raise_condition(thd, &signaled_err); - DBUG_RETURN(result); + query_cache_abort(thd, &thd->query_cache_tls); + + /* Keep handled conditions. */ + da->unmark_sql_conditions_from_removal(); + + /* Check if the old condition still exists. */ + if (da->has_sql_condition(signaled->message, strlen(signaled->message))) + { + /* Make room for the new RESIGNAL condition. */ + da->reserve_space(thd, 1); + } + else + { + /* Make room for old condition + the new RESIGNAL condition. */ + da->reserve_space(thd, 2); + + da->push_warning(thd, &signaled_err); + } } /* RESIGNAL with signal_value */ result= raise_condition(thd, &signaled_err); DBUG_RETURN(result); + } diff --git a/sql/sql_signal.h b/sql/sql_signal.h index 058457a3639..2a508eed5bf 100644 --- a/sql/sql_signal.h +++ b/sql/sql_signal.h @@ -18,27 +18,25 @@ #define SQL_SIGNAL_H /** - Signal_common represents the common properties of the SIGNAL and RESIGNAL - statements. + Sql_cmd_common_signal represents the common properties of the + SIGNAL and RESIGNAL statements. */ -class Signal_common : public Sql_statement +class Sql_cmd_common_signal : public Sql_cmd { protected: /** Constructor. - @param lex the LEX structure for this statement. @param cond the condition signaled if any, or NULL. @param set collection of signal condition item assignments. */ - Signal_common(LEX *lex, - const sp_cond_type_t *cond, - const Set_signal_information& set) - : Sql_statement(lex), + Sql_cmd_common_signal(const sp_condition_value *cond, + const Set_signal_information& set) + : Sql_cmd(), m_cond(cond), m_set_signal_information(set) {} - virtual ~Signal_common() + virtual ~Sql_cmd_common_signal() {} /** @@ -49,9 +47,9 @@ protected: @param level the level to assign @param sqlcode the sql code to assign */ - static void assign_defaults(MYSQL_ERROR *cond, + static void assign_defaults(Sql_condition *cond, bool set_level_code, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, int sqlcode); /** @@ -60,7 +58,7 @@ protected: @param thd the current thread. @param cond the condition to update. */ - void eval_defaults(THD *thd, MYSQL_ERROR *cond); + void eval_defaults(THD *thd, Sql_condition *cond); /** Evaluate each signal condition items for this statement. @@ -68,7 +66,7 @@ protected: @param cond the condition to update. @return 0 on success. */ - int eval_signal_informations(THD *thd, MYSQL_ERROR *cond); + int eval_signal_informations(THD *thd, Sql_condition *cond); /** Raise a SQL condition. @@ -76,13 +74,13 @@ protected: @param cond the condition to raise. @return false on success. */ - bool raise_condition(THD *thd, MYSQL_ERROR *cond); + bool raise_condition(THD *thd, Sql_condition *cond); /** The condition to signal or resignal. This member is optional and can be NULL (RESIGNAL). */ - const sp_cond_type_t *m_cond; + const sp_condition_value *m_cond; /** Collection of 'SET item = value' assignments in the @@ -92,60 +90,56 @@ protected: }; /** - Signal_statement represents a SIGNAL statement. + Sql_cmd_signal represents a SIGNAL statement. */ -class Signal_statement : public Signal_common +class Sql_cmd_signal : public Sql_cmd_common_signal { public: /** Constructor, used to represent a SIGNAL statement. - @param lex the LEX structure for this statement. @param cond the SQL condition to signal (required). @param set the collection of signal informations to signal. */ - Signal_statement(LEX *lex, - const sp_cond_type_t *cond, - const Set_signal_information& set) - : Signal_common(lex, cond, set) + Sql_cmd_signal(const sp_condition_value *cond, + const Set_signal_information& set) + : Sql_cmd_common_signal(cond, set) {} - virtual ~Signal_statement() + virtual ~Sql_cmd_signal() {} - /** - Execute a SIGNAL statement at runtime. - @param thd the current thread. - @return false on success. - */ + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_SIGNAL; + } + virtual bool execute(THD *thd); }; /** - Resignal_statement represents a RESIGNAL statement. + Sql_cmd_resignal represents a RESIGNAL statement. */ -class Resignal_statement : public Signal_common +class Sql_cmd_resignal : public Sql_cmd_common_signal { public: /** Constructor, used to represent a RESIGNAL statement. - @param lex the LEX structure for this statement. @param cond the SQL condition to resignal (optional, may be NULL). @param set the collection of signal informations to resignal. */ - Resignal_statement(LEX *lex, - const sp_cond_type_t *cond, - const Set_signal_information& set) - : Signal_common(lex, cond, set) + Sql_cmd_resignal(const sp_condition_value *cond, + const Set_signal_information& set) + : Sql_cmd_common_signal(cond, set) {} - virtual ~Resignal_statement() + virtual ~Sql_cmd_resignal() {} - /** - Execute a RESIGNAL statement at runtime. - @param thd the current thread. - @return 0 on success. - */ + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_RESIGNAL; + } + virtual bool execute(THD *thd); }; diff --git a/sql/sql_sort.h b/sql/sql_sort.h index f1a3a2f9d8b..d30ddfb6eec 100644 --- a/sql/sql_sort.h +++ b/sql/sql_sort.h @@ -16,6 +16,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "m_string.h" /* memset */ #include "my_global.h" /* uchar */ #include "my_base.h" /* ha_rows */ #include "my_sys.h" /* qsort2_cmp */ @@ -27,7 +28,6 @@ typedef struct st_sort_field SORT_FIELD; class Field; struct TABLE; - /* Defines used by filesort and uniques */ #define MERGEBUFF 7 @@ -65,41 +65,51 @@ struct BUFFPEK_COMPARE_CONTEXT void *key_compare_arg; }; -typedef struct st_sort_param { - uint rec_length; /* Length of sorted records */ - uint sort_length; /* Length of sorted columns */ - uint ref_length; /* Length of record ref. */ - uint addon_length; /* Length of added packed fields */ - uint res_length; /* Length of records in final sorted file/buffer */ - uint keys; /* Max keys / buffer */ + +class Sort_param { +public: + uint rec_length; // Length of sorted records. + uint sort_length; // Length of sorted columns. + uint ref_length; // Length of record ref. + uint addon_length; // Length of added packed fields. + uint res_length; // Length of records in final sorted file/buffer. + uint max_keys_per_buffer; // Max keys / buffer. uint min_dupl_count; - ha_rows max_rows,examined_rows; - TABLE *sort_form; /* For quicker make_sortkey */ + ha_rows max_rows; // Select limit, or HA_POS_ERROR if unlimited. + ha_rows examined_rows; // Number of examined rows. + TABLE *sort_form; // For quicker make_sortkey. SORT_FIELD *local_sortorder; SORT_FIELD *end; - SORT_ADDON_FIELD *addon_field; /* Descriptors for companion fields */ + SORT_ADDON_FIELD *addon_field; // Descriptors for companion fields. uchar *unique_buff; bool not_killable; char* tmp_buffer; - /* The fields below are used only by Unique class */ + // The fields below are used only by Unique class. qsort2_cmp compare; BUFFPEK_COMPARE_CONTEXT cmp_context; -} SORTPARAM; + Sort_param() + { + memset(this, 0, sizeof(*this)); + } + void init_for_filesort(uint sortlen, TABLE *table, + ulong max_length_for_sort_data, + ha_rows maxrows, bool sort_positions); +}; -int merge_many_buff(SORTPARAM *param, uchar *sort_buffer, + +int merge_many_buff(Sort_param *param, uchar *sort_buffer, BUFFPEK *buffpek, uint *maxbuffer, IO_CACHE *t_file); uint read_to_buffer(IO_CACHE *fromfile,BUFFPEK *buffpek, uint sort_length); -int merge_buffers(SORTPARAM *param,IO_CACHE *from_file, - IO_CACHE *to_file, uchar *sort_buffer, - BUFFPEK *lastbuff,BUFFPEK *Fb, - BUFFPEK *Tb,int flag); -int merge_index(SORTPARAM *param, uchar *sort_buffer, +int merge_buffers(Sort_param *param,IO_CACHE *from_file, + IO_CACHE *to_file, uchar *sort_buffer, + BUFFPEK *lastbuff,BUFFPEK *Fb, + BUFFPEK *Tb,int flag); +int merge_index(Sort_param *param, uchar *sort_buffer, BUFFPEK *buffpek, uint maxbuffer, IO_CACHE *tempfile, IO_CACHE *outfile); - void reuse_freed_buff(QUEUE *queue, BUFFPEK *reuse, uint key_length); #endif /* SQL_SORT_INCLUDED */ diff --git a/sql/sql_statistics.cc b/sql/sql_statistics.cc new file mode 100644 index 00000000000..b435971a4d6 --- /dev/null +++ b/sql/sql_statistics.cc @@ -0,0 +1,4086 @@ +/* Copyright (C) 2009 MySQL AB + + 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 02111-1301 USA */ + +/** + @file + + @brief + functions to update persitent statistical tables and to read from them + + @defgroup Query_Optimizer Query Optimizer + @{ +*/ + +#include <my_global.h> +#include "sql_base.h" +#include "key.h" +#include "sql_statistics.h" +#include "opt_range.h" +#include "my_atomic.h" +#include "sql_show.h" +#include "sql_partition.h" + +/* + The system variable 'use_stat_tables' can take one of the + following values: + "never", "complementary", "preferably". + If the values of the variable 'use_stat_tables' is set to + "never then any statistical data from the persistent statistical tables + is ignored by the optimizer. + If the value of the variable 'use_stat_tables' is set to + "complementary" then a particular statistical characteristic is used + by the optimizer only if the database engine does not provide similar + statistics. For example, 'nulls_ratio' for table columns currently + are not provided by any engine. So optimizer uses this statistical data + from the statistical tables. At the same time it does not use + 'avg_frequency' for any index prefix from the statistical tables since + the a similar statistical characteristic 'records_per_key' can be + requested from the database engine. + If the value the variable 'use_stat_tables' is set to + "preferably" the optimizer uses a particular statistical data only if + it can't be found in the statistical data. + If an ANALYZE command is executed then it results in collecting + statistical data for the tables specified by the command and storing + the collected statistics in the persistent statistical tables only + when the value of the variable 'use_stat_tables' is not + equal to "never". +*/ + +/* Currently there are only 3 persistent statistical tables */ +static const uint STATISTICS_TABLES= 3; + +/* + The names of the statistical tables in this array must correspond the + definitions of the tables in the file ../scripts/mysql_system_tables.sql +*/ +static const LEX_STRING stat_table_name[STATISTICS_TABLES]= +{ + { C_STRING_WITH_LEN("table_stats") }, + { C_STRING_WITH_LEN("column_stats") }, + { C_STRING_WITH_LEN("index_stats") } +}; + +/* Name of database to which the statistical tables belong */ +static const LEX_STRING stat_tables_db_name= { C_STRING_WITH_LEN("mysql") }; + + +/** + @details + The function builds a list of TABLE_LIST elements for system statistical + tables using array of TABLE_LIST passed as a parameter. + The lock type of each element is set to TL_READ if for_write = FALSE, + otherwise it is set to TL_WRITE. +*/ + +static +inline void init_table_list_for_stat_tables(TABLE_LIST *tables, bool for_write) +{ + uint i; + + memset((char *) &tables[0], 0, sizeof(TABLE_LIST) * STATISTICS_TABLES); + + for (i= 0; i < STATISTICS_TABLES; i++) + { + tables[i].db= stat_tables_db_name.str; + tables[i].db_length= stat_tables_db_name.length; + tables[i].alias= tables[i].table_name= stat_table_name[i].str; + tables[i].table_name_length= stat_table_name[i].length; + tables[i].lock_type= for_write ? TL_WRITE : TL_READ; + if (i < STATISTICS_TABLES - 1) + tables[i].next_global= tables[i].next_local= + tables[i].next_name_resolution_table= &tables[i+1]; + if (i != 0) + tables[i].prev_global= &tables[i-1].next_global; + } +} + + +/** + @details + The function builds a TABLE_LIST containing only one element 'tbl' for + the statistical table called 'stat_tab_name'. + The lock type of the element is set to TL_READ if for_write = FALSE, + otherwise it is set to TL_WRITE. +*/ + +static +inline void init_table_list_for_single_stat_table(TABLE_LIST *tbl, + const LEX_STRING *stat_tab_name, + bool for_write) +{ + memset((char *) tbl, 0, sizeof(TABLE_LIST)); + + tbl->db= stat_tables_db_name.str; + tbl->db_length= stat_tables_db_name.length; + tbl->alias= tbl->table_name= stat_tab_name->str; + tbl->table_name_length= stat_tab_name->length; + tbl->lock_type= for_write ? TL_WRITE : TL_READ; +} + + +static Table_check_intact_log_error stat_table_intact; + +static const +TABLE_FIELD_TYPE table_stat_fields[TABLE_STAT_N_FIELDS] = +{ + { + { C_STRING_WITH_LEN("db_name") }, + { C_STRING_WITH_LEN("varchar(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("table_name") }, + { C_STRING_WITH_LEN("varchar(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("cardinality") }, + { C_STRING_WITH_LEN("bigint(21)") }, + { NULL, 0 } + }, +}; +static const uint table_stat_pk_col[]= {0,1}; +static const TABLE_FIELD_DEF +table_stat_def= {TABLE_STAT_N_FIELDS, table_stat_fields, 2, table_stat_pk_col }; + +static const +TABLE_FIELD_TYPE column_stat_fields[COLUMN_STAT_N_FIELDS] = +{ + { + { C_STRING_WITH_LEN("db_name") }, + { C_STRING_WITH_LEN("varchar(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("table_name") }, + { C_STRING_WITH_LEN("varchar(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("column_name") }, + { C_STRING_WITH_LEN("varchar(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("min_value") }, + { C_STRING_WITH_LEN("varbinary(255)") }, + { NULL, 0 } + }, + { + { C_STRING_WITH_LEN("max_value") }, + { C_STRING_WITH_LEN("varbinary(255)") }, + { NULL, 0 } + }, + { + { C_STRING_WITH_LEN("nulls_ratio") }, + { C_STRING_WITH_LEN("decimal(12,4)") }, + { NULL, 0 } + }, + { + { C_STRING_WITH_LEN("avg_length") }, + { C_STRING_WITH_LEN("decimal(12,4)") }, + { NULL, 0 } + }, + { + { C_STRING_WITH_LEN("avg_frequency") }, + { C_STRING_WITH_LEN("decimal(12,4)") }, + { NULL, 0 } + }, + { + { C_STRING_WITH_LEN("hist_size") }, + { C_STRING_WITH_LEN("tinyint(3)") }, + { NULL, 0 } + }, + { + { C_STRING_WITH_LEN("hist_type") }, + { C_STRING_WITH_LEN("enum('SINGLE_PREC_HB','DOUBLE_PREC_HB')") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("histogram") }, + { C_STRING_WITH_LEN("varbinary(255)") }, + { NULL, 0 } + } +}; +static const uint column_stat_pk_col[]= {0,1,2}; +static const TABLE_FIELD_DEF +column_stat_def= {COLUMN_STAT_N_FIELDS, column_stat_fields, 3, column_stat_pk_col}; + +static const +TABLE_FIELD_TYPE index_stat_fields[INDEX_STAT_N_FIELDS] = +{ + { + { C_STRING_WITH_LEN("db_name") }, + { C_STRING_WITH_LEN("varchar(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("table_name") }, + { C_STRING_WITH_LEN("varchar(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("index") }, + { C_STRING_WITH_LEN("varchar(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("prefix_arity") }, + { C_STRING_WITH_LEN("int(11)") }, + { NULL, 0 } + }, + { + { C_STRING_WITH_LEN("avg_frequency") }, + { C_STRING_WITH_LEN("decimal(12,4)") }, + { NULL, 0 } + } +}; +static const uint index_stat_pk_col[]= {0,1,2,3}; +static const TABLE_FIELD_DEF +index_stat_def= {INDEX_STAT_N_FIELDS, index_stat_fields, 4, index_stat_pk_col}; + + +/** + @brief + Open all statistical tables and lock them +*/ + +static +inline int open_stat_tables(THD *thd, TABLE_LIST *tables, + Open_tables_backup *backup, + bool for_write) +{ + int rc; + + Dummy_error_handler deh; // suppress errors + thd->push_internal_handler(&deh); + init_table_list_for_stat_tables(tables, for_write); + init_mdl_requests(tables); + rc= open_system_tables_for_read(thd, tables, backup); + thd->pop_internal_handler(); + + + /* If the number of tables changes, we should revise the check below. */ + DBUG_ASSERT(STATISTICS_TABLES == 3); + + if (!rc && + (stat_table_intact.check(tables[TABLE_STAT].table, &table_stat_def) || + stat_table_intact.check(tables[COLUMN_STAT].table, &column_stat_def) || + stat_table_intact.check(tables[INDEX_STAT].table, &index_stat_def))) + { + close_system_tables(thd, backup); + rc= 1; + } + + return rc; +} + + +/** + @brief + Open a statistical table and lock it +*/ +static +inline int open_single_stat_table(THD *thd, TABLE_LIST *table, + const LEX_STRING *stat_tab_name, + Open_tables_backup *backup, + bool for_write) +{ + init_table_list_for_single_stat_table(table, stat_tab_name, for_write); + init_mdl_requests(table); + return open_system_tables_for_read(thd, table, backup); +} + + +/* + The class Column_statistics_collected is a helper class used to collect + statistics on a table column. The class is derived directly from + the class Column_statistics, and, additionally to the fields of the + latter, it contains the fields to accumulate the results of aggregation + for the number of nulls in the column and for the size of the column + values. There is also a container for distinct column values used + to calculate the average number of records per distinct column value. +*/ + +class Column_statistics_collected :public Column_statistics +{ + +private: + Field *column; /* The column to collect statistics on */ + ha_rows nulls; /* To accumulate the number of nulls in the column */ + ulonglong column_total_length; /* To accumulate the size of column values */ + Count_distinct_field *count_distinct; /* The container for distinct + column values */ + + bool is_single_pk_col; /* TRUE <-> the only column of the primary key */ + +public: + + inline void init(THD *thd, Field * table_field); + inline bool add(ha_rows rowno); + inline void finish(ha_rows rows); + inline void cleanup(); +}; + + +/** + Stat_table is the base class for classes Table_stat, Column_stat and + Index_stat. The methods of these classes allow us to read statistical + data from statistical tables, write collected statistical data into + statistical tables and update statistical data in these tables + as well as update access fields belonging to the primary key and + delete records by prefixes of the primary key. + Objects of the classes Table_stat, Column_stat and Index stat are used + for reading/writing statistics from/into persistent tables table_stats, + column_stats and index_stats correspondingly. These tables are stored in + the system database 'mysql'. + + Statistics is read and written always for a given database table t. When + an object of any of these classes is created a pointer to the TABLE + structure for this database table is passed as a parameter to the constructor + of the object. The other parameter is a pointer to the TABLE structure for + the corresponding statistical table st. So construction of an object to + read/write statistical data on table t from/into statistical table st + requires both table t and st to be opened. + In some cases the TABLE structure for table t may be undefined. Then + the objects of the classes Table_stat, Column_stat and Index stat are + created by the alternative constructor that require only the name + of the table t and the name of the database it belongs to. Currently the + alternative constructors are used only in the cases when some records + belonging to the table are to be deleted, or its keys are to be updated + + Reading/writing statistical data from/into a statistical table is always + performed by a key. At the moment there is only one key defined for each + statistical table and this key is primary. + The primary key for the table table_stats is built as (db_name, table_name). + The primary key for the table column_stats is built as (db_name, table_name, + column_name). + The primary key for the table index_stats is built as (db_name, table_name, + index_name, prefix_arity). + + Reading statistical data from a statistical table is performed by the + following pattern. First a table dependent method sets the values of the + the fields that comprise the lookup key. Then an implementation of the + method get_stat_values() declared in Stat_table as a pure virtual method + finds the row from the statistical table by the set key. If the row is + found the values of statistical fields are read from this row and are + distributed in the internal structures. + + Let's assume the statistical data is read for table t from database db. + + When statistical data is searched in the table table_stats first + Table_stat::set_key_fields() should set the fields of db_name and + table_name. Then get_stat_values looks for a row by the set key value, + and, if the row is found, reads the value from the column + table_stats.cardinality into the field read_stat.cardinality of the TABLE + structure for table t and sets the value of read_stat.cardinality_is_null + from this structure to FALSE. If the value of the 'cardinality' column + in the row is null or if no row is found read_stat.cardinality_is_null + is set to TRUE. + + When statistical data is searched in the table column_stats first + Column_stat::set_key_fields() should set the fields of db_name, table_name + and column_name with column_name taken out of the only parameter f of the + Field* type passed to this method. After this get_stat_values looks + for a row by the set key value. If the row is found the values of statistical + data columns min_value, max_value, nulls_ratio, avg_length, avg_frequency, + hist_size, hist_type, histogram are read into internal structures. Values + of nulls_ratio, avg_length, avg_frequency, hist_size, hist_type, histogram + are read into the corresponding fields of the read_stat structure from + the Field object f, while values from min_value and max_value are copied + into the min_value and max_value record buffers attached to the TABLE + structure for table t. + If the value of a statistical column in the found row is null, then the + corresponding flag in the f->read_stat.column_stat_nulls bitmap is set off. + Otherwise the flag is set on. If no row is found for the column the all flags + in f->column_stat_nulls are set off. + + When statistical data is searched in the table index_stats first + Index_stat::set_key_fields() has to be called to set the fields of db_name, + table_name, index_name and prefix_arity. The value of index_name is extracted + from the first parameter key_info of the KEY* type passed to the method. + This parameter specifies the index of interest idx. The second parameter + passed to the method specifies the arity k of the index prefix for which + statistical data is to be read. E.g. if the index idx consists of 3 + components (p1,p2,p3) the table index_stats usually will contain 3 rows for + this index: the first - for the prefix (p1), the second - for the prefix + (p1,p2), and the third - for the the prefix (p1,p2,p3). After the key fields + has been set a call of get_stat_value looks for a row by the set key value. + If the row is found and the value of the avg_frequency column is not null + then this value is assigned to key_info->read_stat.avg_frequency[k]. + Otherwise 0 is assigned to this element. + + The method Stat_table::update_stat is used to write statistical data + collected in the internal structures into a statistical table st. + It is assumed that before any invocation of this method a call of the + function st.set_key_fields has set the values of the primary key fields + that serve to locate the row from the statistical table st where the + the collected statistical data from internal structures are to be written + to. The statistical data is written from the counterparts of the + statistical fields of internal structures into which it would be read + by the functions get_stat_values. The counterpart fields are used + only when statistics is collected + When updating/inserting a row from the statistical table st the method + Stat_table::update_stat calls the implementation of the pure virtual + method store_field_values to transfer statistical data from the fields + of internal structures to the fields of record buffer used for updates + of the statistical table st. +*/ + +class Stat_table +{ + +private: + + /* Handler used for the retrieval of the statistical table stat_table */ + handler *stat_file; + + uint stat_key_length; /* Length of the key to access stat_table */ + uchar *record[2]; /* Record buffers used to access/update stat_table */ + uint stat_key_idx; /* The number of the key to access stat_table */ + + /* This is a helper function used only by the Stat_table constructors */ + void common_init_stat_table() + { + stat_file= stat_table->file; + /* Currently any statistical table has only one key */ + stat_key_idx= 0; + stat_key_info= &stat_table->key_info[stat_key_idx]; + stat_key_length= stat_key_info->key_length; + record[0]= stat_table->record[0]; + record[1]= stat_table->record[1]; + } + +protected: + + /* Statistical table to read statistics from or to update/delete */ + TABLE *stat_table; + KEY *stat_key_info; /* Structure for the index to access stat_table */ + + /* Table for which statistical data is read / updated */ + TABLE *table; + TABLE_SHARE *table_share; /* Table share for 'table */ + LEX_STRING *db_name; /* Name of the database containing 'table' */ + LEX_STRING *table_name; /* Name of the table 'table' */ + + void store_record_for_update() + { + store_record(stat_table, record[1]); + } + + void store_record_for_lookup() + { + DBUG_ASSERT(record[0] == stat_table->record[0]); + } + + bool update_record() + { + int err; + if ((err= stat_file->ha_update_row(record[1], record[0])) && + err != HA_ERR_RECORD_IS_THE_SAME) + return TRUE; + /* Make change permanent and avoid 'table is marked as crashed' errors */ + stat_file->extra(HA_EXTRA_FLUSH); + return FALSE; + } + +public: + + + /** + @details + This constructor has to be called by any constructor of the derived + classes. The constructor 'tunes' the private and protected members of + the constructed object to the statistical table 'stat_table' with the + statistical data of our interest and to the table 'tab' for which this + statistics has been collected. + */ + + Stat_table(TABLE *stat, TABLE *tab) + :stat_table(stat), table(tab) + { + table_share= tab->s; + common_init_stat_table(); + db_name= &table_share->db; + table_name= &table_share->table_name; + } + + + /** + @details + This constructor has to be called by any constructor of the derived + classes. The constructor 'tunes' the private and protected members of + the constructed object to the statistical table 'stat_table' with the + statistical data of our interest and to the table t for which this + statistics has been collected. The table t is uniquely specified + by the database name 'db' and the table name 'tab'. + */ + + Stat_table(TABLE *stat, LEX_STRING *db, LEX_STRING *tab) + :stat_table(stat), table_share(NULL) + { + common_init_stat_table(); + db_name= db; + table_name= tab; + } + + + virtual ~Stat_table() {} + + /** + @brief + Store the given values of fields for database name and table name + + @details + This is a purely virtual method. + The implementation for any derived class shall store the given + values of the database name and table name in the corresponding + fields of stat_table. + + @note + The method is called by the update_table_name_key_parts function. + */ + + virtual void change_full_table_name(LEX_STRING *db, LEX_STRING *tab)= 0; + + + /** + @brief + Store statistical data into fields of the statistical table + + @details + This is a purely virtual method. + The implementation for any derived class shall put the appropriate + statistical data into the corresponding fields of stat_table. + + @note + The method is called by the update_stat function. + */ + + virtual void store_stat_fields()= 0; + + + /** + @brief + Read statistical data from fields of the statistical table + + @details + This is a purely virtual method. + The implementation for any derived read shall read the appropriate + statistical data from the corresponding fields of stat_table. + */ + + virtual void get_stat_values()= 0; + + + /** + @brief + Find a record in the statistical table by a primary key + + @details + The function looks for a record in stat_table by its primary key. + It assumes that the key fields have been already stored in the record + buffer of stat_table. + + @retval + FALSE the record is not found + @retval + TRUE the record is found + */ + + bool find_stat() + { + uchar key[MAX_KEY_LENGTH]; + key_copy(key, record[0], stat_key_info, stat_key_length); + return !stat_file->ha_index_read_idx_map(record[0], stat_key_idx, key, + HA_WHOLE_KEY, HA_READ_KEY_EXACT); + } + + + /** + @brief + Find a record in the statistical table by a key prefix value + + @details + The function looks for a record in stat_table by the key value consisting + of 'prefix_parts' major components for the primary index. + It assumes that the key prefix fields have been already stored in the record + buffer of stat_table. + + @retval + FALSE the record is not found + @retval + TRUE the record is found + */ + + bool find_next_stat_for_prefix(uint prefix_parts) + { + uchar key[MAX_KEY_LENGTH]; + uint prefix_key_length= 0; + for (uint i= 0; i < prefix_parts; i++) + prefix_key_length+= stat_key_info->key_part[i].store_length; + key_copy(key, record[0], stat_key_info, prefix_key_length); + key_part_map prefix_map= (key_part_map) ((1 << prefix_parts) - 1); + return !stat_file->ha_index_read_idx_map(record[0], stat_key_idx, key, + prefix_map, HA_READ_KEY_EXACT); + } + + + /** + @brief + Update/insert a record in the statistical table with new statistics + + @details + The function first looks for a record by its primary key in the statistical + table stat_table. If the record is found the function updates statistical + fields of the records. The data for these fields are taken from internal + structures containing info on the table 'table'. If the record is not + found the function inserts a new record with the primary key set to the + search key and the statistical data taken from the internal structures. + The function assumes that the key fields have been already stored in + the record buffer of stat_table. + + @retval + FALSE success with the update/insert of the record + @retval + TRUE failure with the update/insert of the record + + @note + The function calls the virtual method store_stat_fields to populate the + statistical fields of the updated/inserted row with new statistics. + */ + + bool update_stat() + { + if (find_stat()) + { + store_record_for_update(); + store_stat_fields(); + return update_record(); + } + else + { + int err; + store_stat_fields(); + if ((err= stat_file->ha_write_row(record[0]))) + return TRUE; + /* Make change permanent and avoid 'table is marked as crashed' errors */ + stat_file->extra(HA_EXTRA_FLUSH); + } + return FALSE; + } + + + /** + @brief + Update the table name fields in the current record of stat_table + + @details + The function updates the fields containing database name and table name + for the last found record in the statistical table stat_table. + The corresponding names for update is taken from the parameters + db and tab. + + @retval + FALSE success with the update of the record + @retval + TRUE failure with the update of the record + + @note + The function calls the virtual method change_full_table_name + to store the new names in the record buffer used for updates. + */ + + bool update_table_name_key_parts(LEX_STRING *db, LEX_STRING *tab) + { + store_record_for_update(); + change_full_table_name(db, tab); + bool rc= update_record(); + store_record_for_lookup(); + return rc; + } + + + /** + @brief + Delete the current record of the statistical table stat_table + + @details + The function deletes the last found record from the statistical + table stat_table. + + @retval + FALSE success with the deletion of the record + @retval + TRUE failure with the deletion of the record + */ + + bool delete_stat() + { + int err; + if ((err= stat_file->ha_delete_row(record[0]))) + return TRUE; + /* Make change permanent and avoid 'table is marked as crashed' errors */ + stat_file->extra(HA_EXTRA_FLUSH); + return FALSE; + } + + friend class Stat_table_write_iter; +}; + + +/* + An object of the class Table_stat is created to read statistical + data on tables from the statistical table table_stats, to update + table_stats with such statistical data, or to update columns + of the primary key, or to delete the record by its primary key or + its prefix. + Rows from the statistical table are read and updated always by + primary key. +*/ + +class Table_stat: public Stat_table +{ + +private: + + Field *db_name_field; /* Field for the column table_stats.db_name */ + Field *table_name_field; /* Field for the column table_stats.table_name */ + + void common_init_table_stat() + { + db_name_field= stat_table->field[TABLE_STAT_DB_NAME]; + table_name_field= stat_table->field[TABLE_STAT_TABLE_NAME]; + } + + void change_full_table_name(LEX_STRING *db, LEX_STRING *tab) + { + db_name_field->store(db->str, db->length, system_charset_info); + table_name_field->store(tab->str, tab->length, system_charset_info); + } + +public: + + /** + @details + The constructor 'tunes' the private and protected members of the + constructed object for the statistical table table_stats to read/update + statistics on table 'tab'. The TABLE structure for the table table_stat + must be passed as a value for the parameter 'stat'. + */ + + Table_stat(TABLE *stat, TABLE *tab) :Stat_table(stat, tab) + { + common_init_table_stat(); + } + + + /** + @details + The constructor 'tunes' the private and protected members of the + object constructed for the statistical table table_stat for + the future updates/deletes of the record concerning the table 'tab' + from the database 'db'. + */ + + Table_stat(TABLE *stat, LEX_STRING *db, LEX_STRING *tab) + :Stat_table(stat, db, tab) + { + common_init_table_stat(); + } + + + /** + @brief + Set the key fields for the statistical table table_stat + + @details + The function sets the values of the fields db_name and table_name + in the record buffer for the statistical table table_stat. + These fields comprise the primary key for the table. + + @note + The function is supposed to be called before any use of the + method find_stat for an object of the Table_stat class. + */ + + void set_key_fields() + { + db_name_field->store(db_name->str, db_name->length, system_charset_info); + table_name_field->store(table_name->str, table_name->length, + system_charset_info); + } + + + /** + @brief + Store statistical data into statistical fields of table_stat + + @details + This implementation of a purely virtual method sets the value of the + column 'cardinality' of the statistical table table_stat according to + the value of the flag write_stat.cardinality_is_null and the value of + the field write_stat.cardinality' from the TABLE structure for 'table'. + */ + + void store_stat_fields() + { + Field *stat_field= stat_table->field[TABLE_STAT_CARDINALITY]; + if (table->collected_stats->cardinality_is_null) + stat_field->set_null(); + else + { + stat_field->set_notnull(); + stat_field->store(table->collected_stats->cardinality); + } + } + + + /** + @brief + Read statistical data from statistical fields of table_stat + + @details + This implementation of a purely virtual method first looks for a record + the statistical table table_stat by its primary key set the record + buffer with the help of Table_stat::set_key_fields. Then, if the row is + found the function reads the value of the column 'cardinality' of the table + table_stat and sets the value of the flag read_stat.cardinality_is_null + and the value of the field read_stat.cardinality' from the TABLE structure + for 'table' accordingly. + */ + + void get_stat_values() + { + Table_statistics *read_stats= table_share->stats_cb.table_stats; + read_stats->cardinality_is_null= TRUE; + read_stats->cardinality= 0; + if (find_stat()) + { + Field *stat_field= stat_table->field[TABLE_STAT_CARDINALITY]; + if (!stat_field->is_null()) + { + read_stats->cardinality_is_null= FALSE; + read_stats->cardinality= stat_field->val_int(); + } + } + } + +}; + + +/* + An object of the class Column_stat is created to read statistical data + on table columns from the statistical table column_stats, to update + column_stats with such statistical data, or to update columns + of the primary key, or to delete the record by its primary key or + its prefix. + Rows from the statistical table are read and updated always by + primary key. +*/ + +class Column_stat: public Stat_table +{ + +private: + + Field *db_name_field; /* Field for the column column_stats.db_name */ + Field *table_name_field; /* Field for the column column_stats.table_name */ + Field *column_name_field; /* Field for the column column_stats.column_name */ + + Field *table_field; /* Field from 'table' to read /update statistics on */ + + void common_init_column_stat_table() + { + db_name_field= stat_table->field[COLUMN_STAT_DB_NAME]; + table_name_field= stat_table->field[COLUMN_STAT_TABLE_NAME]; + column_name_field= stat_table->field[COLUMN_STAT_COLUMN_NAME]; + } + + void change_full_table_name(LEX_STRING *db, LEX_STRING *tab) + { + db_name_field->store(db->str, db->length, system_charset_info); + table_name_field->store(tab->str, tab->length, system_charset_info); + } + +public: + + /** + @details + The constructor 'tunes' the private and protected members of the + constructed object for the statistical table column_stats to read/update + statistics on fields of the table 'tab'. The TABLE structure for the table + column_stats must be passed as a value for the parameter 'stat'. + */ + + Column_stat(TABLE *stat, TABLE *tab) :Stat_table(stat, tab) + { + common_init_column_stat_table(); + } + + + /** + @details + The constructor 'tunes' the private and protected members of the + object constructed for the statistical table column_stats for + the future updates/deletes of the record concerning the table 'tab' + from the database 'db'. + */ + + Column_stat(TABLE *stat, LEX_STRING *db, LEX_STRING *tab) + :Stat_table(stat, db, tab) + { + common_init_column_stat_table(); + } + + /** + @brief + Set table name fields for the statistical table column_stats + + @details + The function stores the values of the fields db_name and table_name + of the statistical table column_stats in the record buffer. + */ + + void set_full_table_name() + { + db_name_field->store(db_name->str, db_name->length, system_charset_info); + table_name_field->store(table_name->str, table_name->length, + system_charset_info); + } + + + /** + @brief + Set the key fields for the statistical table column_stats + + @param + col Field for the 'table' column to read/update statistics on + + @details + The function stores the values of the fields db_name, table_name and + column_name in the record buffer for the statistical table column_stats. + These fields comprise the primary key for the table. + It also sets table_field to the passed parameter. + + @note + The function is supposed to be called before any use of the + method find_stat for an object of the Column_stat class. + */ + + void set_key_fields(Field *col) + { + set_full_table_name(); + const char *column_name= col->field_name; + column_name_field->store(column_name, strlen(column_name), + system_charset_info); + table_field= col; + } + + + /** + @brief + Update the table name fields in the current record of stat_table + + @details + The function updates the primary key fields containing database name, + table name, and column name for the last found record in the statistical + table column_stats. + + @retval + FALSE success with the update of the record + @retval + TRUE failure with the update of the record + */ + + bool update_column_key_part(const char *col) + { + store_record_for_update(); + set_full_table_name(); + column_name_field->store(col, strlen(col), system_charset_info); + bool rc= update_record(); + store_record_for_lookup(); + return rc; + } + + + /** + @brief + Store statistical data into statistical fields of column_stats + + @details + This implementation of a purely virtual method sets the value of the + columns 'min_value', 'max_value', 'nulls_ratio', 'avg_length', + 'avg_frequency', 'hist_size', 'hist_type' and 'histogram' of the + stistical table columns_stat according to the contents of the bitmap + write_stat.column_stat_nulls and the values of the fields min_value, + max_value, nulls_ratio, avg_length, avg_frequency, hist_size, hist_type + and histogram of the structure write_stat from the Field structure + for the field 'table_field'. + The value of the k-th column in the table columns_stat is set to NULL + if the k-th bit in the bitmap 'column_stat_nulls' is set to 1. + + @note + A value from the field min_value/max_value is always converted + into a varbinary string. If the length of the column 'min_value'/'max_value' + is less than the length of the string the string is trimmed to fit the + length of the column. + */ + + void store_stat_fields() + { + char buff[MAX_FIELD_WIDTH]; + String val(buff, sizeof(buff), &my_charset_bin); + + for (uint i= COLUMN_STAT_MIN_VALUE; i <= COLUMN_STAT_HISTOGRAM; i++) + { + Field *stat_field= stat_table->field[i]; + if (table_field->collected_stats->is_null(i)) + stat_field->set_null(); + else + { + stat_field->set_notnull(); + switch (i) { + case COLUMN_STAT_MIN_VALUE: + if (table_field->type() == MYSQL_TYPE_BIT) + stat_field->store(table_field->collected_stats->min_value->val_int()); + else + { + table_field->collected_stats->min_value->val_str(&val); + stat_field->store(val.ptr(), val.length(), &my_charset_bin); + } + break; + case COLUMN_STAT_MAX_VALUE: + if (table_field->type() == MYSQL_TYPE_BIT) + stat_field->store(table_field->collected_stats->max_value->val_int()); + else + { + table_field->collected_stats->max_value->val_str(&val); + stat_field->store(val.ptr(), val.length(), &my_charset_bin); + } + break; + case COLUMN_STAT_NULLS_RATIO: + stat_field->store(table_field->collected_stats->get_nulls_ratio()); + break; + case COLUMN_STAT_AVG_LENGTH: + stat_field->store(table_field->collected_stats->get_avg_length()); + break; + case COLUMN_STAT_AVG_FREQUENCY: + stat_field->store(table_field->collected_stats->get_avg_frequency()); + break; + case COLUMN_STAT_HIST_SIZE: + stat_field->store(table_field->collected_stats->histogram.get_size()); + break; + case COLUMN_STAT_HIST_TYPE: + stat_field->store(table_field->collected_stats->histogram.get_type() + + 1); + break; + case COLUMN_STAT_HISTOGRAM: + const char * col_histogram= + (const char *) (table_field->collected_stats->histogram.get_values()); + stat_field->store(col_histogram, + table_field->collected_stats->histogram.get_size(), + &my_charset_bin); + break; + } + } + } + } + + + /** + @brief + Read statistical data from statistical fields of column_stats + + @details + This implementation of a purely virtual method first looks for a record + in the statistical table column_stats by its primary key set in the record + buffer with the help of Column_stat::set_key_fields. Then, if the row is + found, the function reads the values of the columns 'min_value', + 'max_value', 'nulls_ratio', 'avg_length', 'avg_frequency', 'hist_size' and + 'hist_type" of the table column_stat and sets accordingly the value of + the bitmap read_stat.column_stat_nulls' and the values of the fields + min_value, max_value, nulls_ratio, avg_length, avg_frequency, hist_size and + hist_type of the structure read_stat from the Field structure for the field + 'table_field'. + */ + + void get_stat_values() + { + table_field->read_stats->set_all_nulls(); + + if (table_field->read_stats->min_value) + table_field->read_stats->min_value->set_null(); + if (table_field->read_stats->max_value) + table_field->read_stats->max_value->set_null(); + + if (find_stat()) + { + char buff[MAX_FIELD_WIDTH]; + String val(buff, sizeof(buff), &my_charset_bin); + + for (uint i= COLUMN_STAT_MIN_VALUE; i <= COLUMN_STAT_HIST_TYPE; i++) + { + Field *stat_field= stat_table->field[i]; + + if (!stat_field->is_null() && + (i > COLUMN_STAT_MAX_VALUE || + (i == COLUMN_STAT_MIN_VALUE && + table_field->read_stats->min_value) || + (i == COLUMN_STAT_MAX_VALUE && + table_field->read_stats->max_value))) + { + table_field->read_stats->set_not_null(i); + + switch (i) { + case COLUMN_STAT_MIN_VALUE: + table_field->read_stats->min_value->set_notnull(); + stat_field->val_str(&val); + table_field->read_stats->min_value->store(val.ptr(), val.length(), + &my_charset_bin); + break; + case COLUMN_STAT_MAX_VALUE: + table_field->read_stats->max_value->set_notnull(); + stat_field->val_str(&val); + table_field->read_stats->max_value->store(val.ptr(), val.length(), + &my_charset_bin); + break; + case COLUMN_STAT_NULLS_RATIO: + table_field->read_stats->set_nulls_ratio(stat_field->val_real()); + break; + case COLUMN_STAT_AVG_LENGTH: + table_field->read_stats->set_avg_length(stat_field->val_real()); + break; + case COLUMN_STAT_AVG_FREQUENCY: + table_field->read_stats->set_avg_frequency(stat_field->val_real()); + break; + case COLUMN_STAT_HIST_SIZE: + table_field->read_stats->histogram.set_size(stat_field->val_int()); + break; + case COLUMN_STAT_HIST_TYPE: + Histogram_type hist_type= (Histogram_type) (stat_field->val_int() - + 1); + table_field->read_stats->histogram.set_type(hist_type); + break; + } + } + } + } + } + + + /** + @brief + Read histogram from of column_stats + + @details + This method first looks for a record in the statistical table column_stats + by its primary key set the record buffer with the help of + Column_stat::set_key_fields. Then, if the row is found, the function reads + the value of the column 'histogram' of the table column_stat and sets + accordingly the corresponding bit in the bitmap read_stat.column_stat_nulls. + The method assumes that the value of histogram size and the pointer to + the histogram location has been already set in the fields size and values + of read_stats->histogram. + */ + + void get_histogram_value() + { + if (find_stat()) + { + char buff[MAX_FIELD_WIDTH]; + String val(buff, sizeof(buff), &my_charset_bin); + uint fldno= COLUMN_STAT_HISTOGRAM; + Field *stat_field= stat_table->field[fldno]; + table_field->read_stats->set_not_null(fldno); + stat_field->val_str(&val); + memcpy(table_field->read_stats->histogram.get_values(), + val.ptr(), table_field->read_stats->histogram.get_size()); + } + } + +}; + + +/* + An object of the class Index_stat is created to read statistical + data on tables from the statistical table table_stat, to update + index_stats with such statistical data, or to update columns + of the primary key, or to delete the record by its primary key or + its prefix. + Rows from the statistical table are read and updated always by + primary key. +*/ + +class Index_stat: public Stat_table +{ + +private: + + Field *db_name_field; /* Field for the column index_stats.db_name */ + Field *table_name_field; /* Field for the column index_stats.table_name */ + Field *index_name_field; /* Field for the column index_stats.table_name */ + Field *prefix_arity_field; /* Field for the column index_stats.prefix_arity */ + + KEY *table_key_info; /* Info on the index to read/update statistics on */ + uint prefix_arity; /* Number of components of the index prefix of interest */ + + void common_init_index_stat_table() + { + db_name_field= stat_table->field[INDEX_STAT_DB_NAME]; + table_name_field= stat_table->field[INDEX_STAT_TABLE_NAME]; + index_name_field= stat_table->field[INDEX_STAT_INDEX_NAME]; + prefix_arity_field= stat_table->field[INDEX_STAT_PREFIX_ARITY]; + } + + void change_full_table_name(LEX_STRING *db, LEX_STRING *tab) + { + db_name_field->store(db->str, db->length, system_charset_info); + table_name_field->store(tab->str, tab->length, system_charset_info); + } + +public: + + + /** + @details + The constructor 'tunes' the private and protected members of the + constructed object for the statistical table index_stats to read/update + statistics on prefixes of different indexes of the table 'tab'. + The TABLE structure for the table index_stats must be passed as a value + for the parameter 'stat'. + */ + + Index_stat(TABLE *stat, TABLE*tab) :Stat_table(stat, tab) + { + common_init_index_stat_table(); + } + + + /** + @details + The constructor 'tunes' the private and protected members of the + object constructed for the statistical table index_stats for + the future updates/deletes of the record concerning the table 'tab' + from the database 'db'. + */ + + Index_stat(TABLE *stat, LEX_STRING *db, LEX_STRING *tab) + :Stat_table(stat, db, tab) + { + common_init_index_stat_table(); + } + + + /** + @brief + Set table name fields for the statistical table index_stats + + @details + The function stores the values of the fields db_name and table_name + of the statistical table index_stats in the record buffer. + */ + + void set_full_table_name() + { + db_name_field->store(db_name->str, db_name->length, system_charset_info); + table_name_field->store(table_name->str, table_name->length, + system_charset_info); + } + + /** + @brief + Set the key fields of index_stats used to access records for index prefixes + + @param + index_info Info for the index of 'table' to read/update statistics on + + @details + The function sets the values of the fields db_name, table_name and + index_name in the record buffer for the statistical table index_stats. + It also sets table_key_info to the passed parameter. + + @note + The function is supposed to be called before any use of the method + find_next_stat_for_prefix for an object of the Index_stat class. + */ + + void set_index_prefix_key_fields(KEY *index_info) + { + set_full_table_name(); + char *index_name= index_info->name; + index_name_field->store(index_name, strlen(index_name), + system_charset_info); + table_key_info= index_info; + } + + + /** + @brief + Set the key fields for the statistical table index_stats + + @param + index_info Info for the index of 'table' to read/update statistics on + @param + index_prefix_arity Number of components in the index prefix of interest + + @details + The function sets the values of the fields db_name, table_name and + index_name, prefix_arity in the record buffer for the statistical + table index_stats. These fields comprise the primary key for the table. + + @note + The function is supposed to be called before any use of the + method find_stat for an object of the Index_stat class. + */ + + void set_key_fields(KEY *index_info, uint index_prefix_arity) + { + set_index_prefix_key_fields(index_info); + prefix_arity= index_prefix_arity; + prefix_arity_field->store(index_prefix_arity, TRUE); + } + + + /** + @brief + Store statistical data into statistical fields of table index_stats + + @details + This implementation of a purely virtual method sets the value of the + column 'avg_frequency' of the statistical table index_stats according to + the value of write_stat.avg_frequency[Index_stat::prefix_arity] + from the KEY_INFO structure 'table_key_info'. + If the value of write_stat. avg_frequency[Index_stat::prefix_arity] is + equal to 0, the value of the column is set to NULL. + */ + + void store_stat_fields() + { + Field *stat_field= stat_table->field[INDEX_STAT_AVG_FREQUENCY]; + double avg_frequency= + table_key_info->collected_stats->get_avg_frequency(prefix_arity-1); + if (avg_frequency == 0) + stat_field->set_null(); + else + { + stat_field->set_notnull(); + stat_field->store(avg_frequency); + } + } + + + /** + @brief + Read statistical data from statistical fields of index_stats + + @details + This implementation of a purely virtual method first looks for a record the + statistical table index_stats by its primary key set the record buffer with + the help of Index_stat::set_key_fields. If the row is found the function + reads the value of the column 'avg_freguency' of the table index_stat and + sets the value of read_stat.avg_frequency[Index_stat::prefix_arity] + from the KEY_INFO structure 'table_key_info' accordingly. If the value of + the column is NULL, read_stat.avg_frequency[Index_stat::prefix_arity] is + set to 0. Otherwise, read_stat.avg_frequency[Index_stat::prefix_arity] is + set to the value of the column. + */ + + void get_stat_values() + { + double avg_frequency= 0; + if(find_stat()) + { + Field *stat_field= stat_table->field[INDEX_STAT_AVG_FREQUENCY]; + if (!stat_field->is_null()) + avg_frequency= stat_field->val_real(); + } + table_key_info->read_stats->set_avg_frequency(prefix_arity-1, avg_frequency); + } + +}; + + +/* + An iterator to enumerate statistics table rows which allows to modify + the rows while reading them. + + Used by RENAME TABLE handling to assign new dbname.tablename to statistic + rows. +*/ +class Stat_table_write_iter +{ + Stat_table *owner; + IO_CACHE io_cache; + uchar *rowid_buf; + uint rowid_size; + +public: + Stat_table_write_iter(Stat_table *stat_table_arg) + : owner(stat_table_arg), rowid_buf(NULL), + rowid_size(owner->stat_file->ref_length) + { + my_b_clear(&io_cache); + } + + /* + Initialize the iterator. It will return rows with n_keyparts matching the + curernt values. + + @return false - OK + true - Error + */ + bool init(uint n_keyparts) + { + if (!(rowid_buf= (uchar*)my_malloc(rowid_size, MYF(0)))) + return true; + + if (open_cached_file(&io_cache, mysql_tmpdir, TEMP_PREFIX, + 1024, MYF(MY_WME))) + return true; + + handler *h= owner->stat_file; + uchar key[MAX_KEY_LENGTH]; + uint prefix_len= 0; + for (uint i= 0; i < n_keyparts; i++) + prefix_len += owner->stat_key_info->key_part[i].store_length; + + key_copy(key, owner->record[0], owner->stat_key_info, + prefix_len); + key_part_map prefix_map= (key_part_map) ((1 << n_keyparts) - 1); + h->ha_index_init(owner->stat_key_idx, false); + int res= h->ha_index_read_map(owner->record[0], key, prefix_map, + HA_READ_KEY_EXACT); + if (res) + { + reinit_io_cache(&io_cache, READ_CACHE, 0L, 0, 0); + /* "Key not found" is not considered an error */ + return (res == HA_ERR_KEY_NOT_FOUND)? false: true; + } + + do { + h->position(owner->record[0]); + my_b_write(&io_cache, h->ref, rowid_size); + + } while (!h->ha_index_next_same(owner->record[0], key, prefix_len)); + + /* Prepare for reading */ + reinit_io_cache(&io_cache, READ_CACHE, 0L, 0, 0); + h->ha_index_or_rnd_end(); + if (h->ha_rnd_init(false)) + return true; + + return false; + } + + /* + Read the next row. + + @return + false OK + true No more rows or error. + */ + bool get_next_row() + { + if (!my_b_inited(&io_cache) || my_b_read(&io_cache, rowid_buf, rowid_size)) + return true; /* No more data */ + + handler *h= owner->stat_file; + /* + We should normally be able to find the row that we have rowid for. If we + don't, let's consider this an error. + */ + int res= h->ha_rnd_pos(owner->record[0], rowid_buf); + + return (res==0)? false : true; + } + + void cleanup() + { + if (rowid_buf) + my_free(rowid_buf); + rowid_buf= NULL; + owner->stat_file->ha_index_or_rnd_end(); + close_cached_file(&io_cache); + my_b_clear(&io_cache); + } + + ~Stat_table_write_iter() + { + /* Ensure that cleanup has been run */ + DBUG_ASSERT(rowid_buf == 0); + } +}; + +/* + Histogram_builder is a helper class that is used to build histograms + for columns +*/ + +class Histogram_builder +{ + Field *column; /* table field for which the histogram is built */ + uint col_length; /* size of this field */ + ha_rows records; /* number of records the histogram is built for */ + Field *min_value; /* pointer to the minimal value for the field */ + Field *max_value; /* pointer to the maximal value for the field */ + Histogram *histogram; /* the histogram location */ + uint hist_width; /* the number of points in the histogram */ + double bucket_capacity; /* number of rows in a bucket of the histogram */ + uint curr_bucket; /* number of the current bucket to be built */ + ulonglong count; /* number of values retrieved */ + ulonglong count_distinct; /* number of distinct values retrieved */ + +public: + Histogram_builder(Field *col, uint col_len, ha_rows rows) + : column(col), col_length(col_len), records(rows) + { + Column_statistics *col_stats= col->collected_stats; + min_value= col_stats->min_value; + max_value= col_stats->max_value; + histogram= &col_stats->histogram; + hist_width= histogram->get_width(); + bucket_capacity= (double) records / (hist_width + 1); + curr_bucket= 0; + count= 0; + count_distinct= 0; + } + + ulonglong get_count_distinct() { return count_distinct; } + + int next(void *elem, element_count elem_cnt) + { + count_distinct++; + count+= elem_cnt; + if (curr_bucket == hist_width) + return 0; + if (count > bucket_capacity * (curr_bucket + 1)) + { + column->store_field_value((uchar *) elem, col_length); + histogram->set_value(curr_bucket, + column->pos_in_interval(min_value, max_value)); + curr_bucket++; + while (curr_bucket != hist_width && + count > bucket_capacity * (curr_bucket + 1)) + { + histogram->set_prev_value(curr_bucket); + curr_bucket++; + } + } + return 0; + } +}; + + +C_MODE_START + +int histogram_build_walk(void *elem, element_count elem_cnt, void *arg) +{ + Histogram_builder *hist_builder= (Histogram_builder *) arg; + return hist_builder->next(elem, elem_cnt); +} + +C_MODE_END + + +/* + The class Count_distinct_field is a helper class used to calculate + the number of distinct values for a column. The class employs the + Unique class for this purpose. + The class Count_distinct_field is used only by the function + collect_statistics_for_table to calculate the values for + column avg_frequency of the statistical table column_stats. +*/ + +class Count_distinct_field: public Sql_alloc +{ +protected: + + /* Field for which the number of distinct values is to be find out */ + Field *table_field; + Unique *tree; /* The helper object to contain distinct values */ + uint tree_key_length; /* The length of the keys for the elements of 'tree */ + +public: + + Count_distinct_field() {} + + /** + @param + field Field for which the number of distinct values is + to be find out + @param + max_heap_table_size The limit for the memory used by the RB tree container + of the constructed Unique object 'tree' + + @details + The constructor sets the values of 'table_field' and 'tree_key_length', + and then calls the 'new' operation to create a Unique object for 'tree'. + The type of 'field' and the value max_heap_table_size of determine the set + of the parameters to be passed to the constructor of the Unique object. + */ + + Count_distinct_field(Field *field, uint max_heap_table_size) + { + table_field= field; + tree_key_length= field->pack_length(); + + tree= new Unique((qsort_cmp2) simple_str_key_cmp, (void*) field, + tree_key_length, max_heap_table_size, 1); + } + + virtual ~Count_distinct_field() + { + delete tree; + tree= NULL; + } + + /* + @brief + Check whether the Unique object tree has been successfully created + */ + bool exists() + { + return (tree != NULL); + } + + /* + @brief + Add the value of 'field' to the container of the Unique object 'tree' + */ + virtual bool add() + { + return tree->unique_add(table_field->ptr); + } + + /* + @brief + Calculate the number of elements accumulated in the container of 'tree' + */ + ulonglong get_value() + { + ulonglong count; + if (tree->elements == 0) + return (ulonglong) tree->elements_in_tree(); + count= 0; + tree->walk(table_field->table, count_distinct_walk, (void*) &count); + return count; + } + + /* + @brief + Build the histogram for the elements accumulated in the container of 'tree' + */ + ulonglong get_value_with_histogram(ha_rows rows) + { + Histogram_builder hist_builder(table_field, tree_key_length, rows); + tree->walk(table_field->table, histogram_build_walk, (void *) &hist_builder); + return hist_builder.get_count_distinct(); + } + + /* + @brief + Get the size of the histogram in bytes built for table_field + */ + uint get_hist_size() + { + return table_field->collected_stats->histogram.get_size(); + } + + /* + @brief + Get the pointer to the histogram built for table_field + */ + uchar *get_histogram() + { + return table_field->collected_stats->histogram.get_values(); + } + +}; + + +static +int simple_ulonglong_key_cmp(void* arg, uchar* key1, uchar* key2) +{ + ulonglong *val1= (ulonglong *) key1; + ulonglong *val2= (ulonglong *) key2; + return *val1 > *val2 ? 1 : *val1 == *val2 ? 0 : -1; +} + + +/* + The class Count_distinct_field_bit is derived from the class + Count_distinct_field to be used only for fields of the MYSQL_TYPE_BIT type. + The class provides a different implementation for the method add +*/ + +class Count_distinct_field_bit: public Count_distinct_field +{ +public: + + Count_distinct_field_bit(Field *field, uint max_heap_table_size) + { + table_field= field; + tree_key_length= sizeof(ulonglong); + + tree= new Unique((qsort_cmp2) simple_ulonglong_key_cmp, + (void*) &tree_key_length, + tree_key_length, max_heap_table_size, 1); + } + + bool add() + { + longlong val= table_field->val_int(); + return tree->unique_add(&val); + } +}; + + +/* + The class Index_prefix_calc is a helper class used to calculate the values + for the column 'avg_frequency' of the statistical table index_stats. + For any table t from the database db and any k-component prefix of the + index i for this table the row from index_stats with the primary key + (db,t,i,k) must contain in the column 'avg_frequency' either NULL or + the number that is the ratio of N and V, where N is the number of index + entries without NULL values in the first k components of the index i, + and V is the number of distinct tuples composed of the first k components + encountered among these index entries. + Currently the objects of this class are used only by the function + collect_statistics_for_index. +*/ + +class Index_prefix_calc: public Sql_alloc +{ + +private: + + /* Table containing index specified by index_info */ + TABLE *index_table; + /* Info for the index i for whose prefix 'avg_frequency' is calculated */ + KEY *index_info; + /* The maximum number of the components in the prefixes of interest */ + uint prefixes; + bool empty; + + /* This structure is created for every k components of the index i */ + class Prefix_calc_state + { + public: + /* + The number of the scanned index entries without nulls + in the first k components + */ + ulonglong entry_count; + /* + The number if the scanned index entries without nulls with + the last encountered k-component prefix + */ + ulonglong prefix_count; + /* The values of the last encountered k-component prefix */ + Cached_item *last_prefix; + }; + + /* + Array of structures used to calculate 'avg_frequency' for different + prefixes of the index i + */ + Prefix_calc_state *calc_state; + +public: + + bool is_single_comp_pk; + + Index_prefix_calc(TABLE *table, KEY *key_info) + : index_table(table), index_info(key_info) + { + uint i; + Prefix_calc_state *state; + uint key_parts= table->actual_n_key_parts(key_info); + empty= TRUE; + prefixes= 0; + LINT_INIT_STRUCT(calc_state); + + is_single_comp_pk= FALSE; + uint pk= table->s->primary_key; + if ((uint) (table->key_info - key_info) == pk && + table->key_info[pk].user_defined_key_parts == 1) + { + prefixes= 1; + is_single_comp_pk= TRUE; + return; + } + + if ((calc_state= + (Prefix_calc_state *) sql_alloc(sizeof(Prefix_calc_state)*key_parts))) + { + uint keyno= key_info-table->key_info; + for (i= 0, state= calc_state; i < key_parts; i++, state++) + { + /* + Do not consider prefixes containing a component that is only part + of the field. This limitation is set to avoid fetching data when + calculating the values of 'avg_frequency' for prefixes. + */ + if (!key_info->key_part[i].field->part_of_key.is_set(keyno)) + break; + + if (!(state->last_prefix= + new Cached_item_field(key_info->key_part[i].field))) + break; + state->entry_count= state->prefix_count= 0; + prefixes++; + } + } + } + + + /** + @breif + Change the elements of calc_state after reading the next index entry + + @details + This function is to be called at the index scan each time the next + index entry has been read into the record buffer. + For each of the index prefixes the function checks whether nulls + are encountered in any of the k components of the prefix. + If this is not the case the value of calc_state[k-1].entry_count + is incremented by 1. Then the function checks whether the value of + any of these k components has changed. If so, the value of + calc_state[k-1].prefix_count is incremented by 1. + */ + + void add() + { + uint i; + Prefix_calc_state *state; + uint first_changed= prefixes; + for (i= prefixes, state= calc_state+prefixes-1; i; i--, state--) + { + if (state->last_prefix->cmp()) + first_changed= i-1; + } + if (empty) + { + first_changed= 0; + empty= FALSE; + } + for (i= 0, state= calc_state; i < prefixes; i++, state++) + { + if (state->last_prefix->null_value) + break; + if (i >= first_changed) + state->prefix_count++; + state->entry_count++; + } + } + + /** + @brief + Calculate the values of avg_frequency for all prefixes of an index + + @details + This function is to be called after the index scan to count the number + of distinct index prefixes has been done. The function calculates + the value of avg_frequency for the index prefix with k components + as calc_state[k-1].entry_count/calc_state[k-1].prefix_count. + If calc_state[k-1].prefix_count happens to be 0, the value of + avg_frequency[k-1] is set to 0, i.e. is considered as unknown. + */ + + void get_avg_frequency() + { + uint i; + Prefix_calc_state *state; + + if (is_single_comp_pk) + { + index_info->collected_stats->set_avg_frequency(0, 1.0); + return; + } + + for (i= 0, state= calc_state; i < prefixes; i++, state++) + { + if (i < prefixes) + { + double val= state->prefix_count == 0 ? + 0 : (double) state->entry_count / state->prefix_count; + index_info->collected_stats->set_avg_frequency(i, val); + } + } + } +}; + + +/** + @brief + Create fields for min/max values to collect column statistics + + @param + table Table the fields are created for + + @details + The function first allocates record buffers to store min/max values + for 'table's fields. Then for each table field f it creates Field structures + that points to these buffers rather that to the record buffer as the + Field object for f does. The pointers of the created fields are placed + in the collected_stats structure of the Field object for f. + The function allocates the buffers for min/max values in the table + memory. + + @note + The buffers allocated when min/max values are used to read statistics + from the persistent statistical tables differ from those buffers that + are used when statistics on min/max values for column is collected + as they are allocated in different mem_roots. + The same is true for the fields created for min/max values. +*/ + +static +void create_min_max_statistical_fields_for_table(TABLE *table) +{ + uint rec_buff_length= table->s->rec_buff_length; + + if ((table->collected_stats->min_max_record_buffers= + (uchar *) alloc_root(&table->mem_root, 2*rec_buff_length))) + { + uchar *record= table->collected_stats->min_max_record_buffers; + memset(record, 0, 2*rec_buff_length); + + for (uint i=0; i < 2; i++, record+= rec_buff_length) + { + for (Field **field_ptr= table->field; *field_ptr; field_ptr++) + { + Field *fld; + Field *table_field= *field_ptr; + my_ptrdiff_t diff= record-table->record[0]; + if (!bitmap_is_set(table->read_set, table_field->field_index)) + continue; + if (!(fld= table_field->clone(&table->mem_root, table, diff, TRUE))) + continue; + if (i == 0) + table_field->collected_stats->min_value= fld; + else + table_field->collected_stats->max_value= fld; + } + } + } +} + + +/** + @brief + Create fields for min/max values to read column statistics + + @param + thd Thread handler + @param + table_share Table share the fields are created for + @param + is_safe TRUE <-> at any time only one thread can perform the function + + @details + The function first allocates record buffers to store min/max values + for 'table_share's fields. Then for each field f it creates Field structures + that points to these buffers rather that to the record buffer as the + Field object for f does. The pointers of the created fields are placed + in the read_stats structure of the Field object for f. + The function allocates the buffers for min/max values in the table share + memory. + If the parameter is_safe is TRUE then it is guaranteed that at any given time + only one thread is executed the code of the function. + + @note + The buffers allocated when min/max values are used to collect statistics + from the persistent statistical tables differ from those buffers that + are used when statistics on min/max values for column is read as they + are allocated in different mem_roots. + The same is true for the fields created for min/max values. +*/ + +static +void create_min_max_statistical_fields_for_table_share(THD *thd, + TABLE_SHARE *table_share) +{ + TABLE_STATISTICS_CB *stats_cb= &table_share->stats_cb; + Table_statistics *stats= stats_cb->table_stats; + + if (stats->min_max_record_buffers) + return; + + uint rec_buff_length= table_share->rec_buff_length; + + if ((stats->min_max_record_buffers= + (uchar *) alloc_root(&stats_cb->mem_root, 2*rec_buff_length))) + { + uchar *record= stats->min_max_record_buffers; + memset(record, 0, 2*rec_buff_length); + + for (uint i=0; i < 2; i++, record+= rec_buff_length) + { + for (Field **field_ptr= table_share->field; *field_ptr; field_ptr++) + { + Field *fld; + Field *table_field= *field_ptr; + my_ptrdiff_t diff= record - table_share->default_values; + if (!(fld= table_field->clone(&stats_cb->mem_root, diff))) + continue; + if (i == 0) + table_field->read_stats->min_value= fld; + else + table_field->read_stats->max_value= fld; + } + } + } + +} + + +/** + @brief + Allocate memory for the table's statistical data to be collected + + @param + table Table for which the memory for statistical data is allocated + + @note + The function allocates the memory for the statistical data on 'table' with + the intention to collect the data there. The memory is allocated for + the statistics on the table, on the table's columns, and on the table's + indexes. The memory is allocated in the table's mem_root. + + @retval + 0 If the memory for all statistical data has been successfully allocated + @retval + 1 Otherwise + + @note + Each thread allocates its own memory to collect statistics on the table + It allows us, for example, to collect statistics on the different indexes + of the same table in parallel. +*/ + +int alloc_statistics_for_table(THD* thd, TABLE *table) +{ + Field **field_ptr; + uint fields; + + DBUG_ENTER("alloc_statistics_for_table"); + + + Table_statistics *table_stats= + (Table_statistics *) alloc_root(&table->mem_root, + sizeof(Table_statistics)); + + fields= table->s->fields ; + Column_statistics_collected *column_stats= + (Column_statistics_collected *) alloc_root(&table->mem_root, + sizeof(Column_statistics_collected) * + (fields+1)); + + uint keys= table->s->keys; + Index_statistics *index_stats= + (Index_statistics *) alloc_root(&table->mem_root, + sizeof(Index_statistics) * keys); + + uint key_parts= table->s->ext_key_parts; + ulong *idx_avg_frequency= (ulong*) alloc_root(&table->mem_root, + sizeof(ulong) * key_parts); + + uint columns= 0; + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + if (bitmap_is_set(table->read_set, (*field_ptr)->field_index)) + columns++; + } + uint hist_size= thd->variables.histogram_size; + Histogram_type hist_type= (Histogram_type) (thd->variables.histogram_type); + uchar *histogram= NULL; + if (hist_size > 0) + histogram= (uchar *) alloc_root(&table->mem_root, hist_size * columns); + + if (!table_stats || !column_stats || !index_stats || !idx_avg_frequency || + (hist_size && !histogram)) + DBUG_RETURN(1); + + table->collected_stats= table_stats; + table_stats->column_stats= column_stats; + table_stats->index_stats= index_stats; + table_stats->idx_avg_frequency= idx_avg_frequency; + table_stats->histograms= histogram; + + memset(column_stats, 0, sizeof(Column_statistics) * (fields+1)); + + for (field_ptr= table->field; *field_ptr; field_ptr++, column_stats++) + { + (*field_ptr)->collected_stats= column_stats; + (*field_ptr)->collected_stats->max_value= NULL; + (*field_ptr)->collected_stats->min_value= NULL; + if (bitmap_is_set(table->read_set, (*field_ptr)->field_index)) + { + column_stats->histogram.set_size(hist_size); + column_stats->histogram.set_type(hist_type); + column_stats->histogram.set_values(histogram); + histogram+= hist_size; + } + } + + memset(idx_avg_frequency, 0, sizeof(ulong) * key_parts); + + KEY *key_info, *end; + for (key_info= table->key_info, end= key_info + table->s->keys; + key_info < end; + key_info++, index_stats++) + { + key_info->collected_stats= index_stats; + key_info->collected_stats->init_avg_frequency(idx_avg_frequency); + idx_avg_frequency+= key_info->ext_key_parts; + } + + create_min_max_statistical_fields_for_table(table); + + DBUG_RETURN(0); +} + + +/** + @brief + Check whether any persistent statistics for the processed command is needed + + @param + thd The thread handle + + @details + The function checks whether any persitent statistics for the processed + command is needed to be read. + + @retval + TRUE statistics is needed to be read + @retval + FALSE Otherwise +*/ + +static +inline bool statistics_for_command_is_needed(THD *thd) +{ + if (thd->bootstrap || thd->variables.use_stat_tables == NEVER) + return FALSE; + + if (thd->force_read_stats) + return TRUE; + + switch(thd->lex->sql_command) { + case SQLCOM_SELECT: + case SQLCOM_INSERT: + case SQLCOM_INSERT_SELECT: + case SQLCOM_UPDATE: + case SQLCOM_UPDATE_MULTI: + case SQLCOM_DELETE: + case SQLCOM_DELETE_MULTI: + case SQLCOM_REPLACE: + case SQLCOM_REPLACE_SELECT: + break; + default: + return FALSE; + } + + return TRUE; +} + + +/** + @brief + Allocate memory for the statistical data used by a table share + + @param + thd Thread handler + @param + table_share Table share for which the memory for statistical data is allocated + @param + is_safe TRUE <-> at any time only one thread can perform the function + + @note + The function allocates the memory for the statistical data on a table in the + table's share memory with the intention to read the statistics there from + the system persistent statistical tables mysql.table_stat, mysql.column_stats, + mysql.index_stats. The memory is allocated for the statistics on the table, + on the tables's columns, and on the table's indexes. The memory is allocated + in the table_share's mem_root. + If the parameter is_safe is TRUE then it is guaranteed that at any given time + only one thread is executed the code of the function. + + @retval + 0 If the memory for all statistical data has been successfully allocated + @retval + 1 Otherwise + + @note + The situation when more than one thread try to allocate memory for + statistical data is rare. It happens under the following scenario: + 1. One thread executes a query over table t with the system variable + 'use_stat_tables' set to 'never'. + 2. After this the second thread sets 'use_stat_tables' to 'preferably' + and executes a query over table t. + 3. Simultaneously the third thread sets 'use_stat_tables' to 'preferably' + and executes a query over table t. + Here the second and the third threads try to allocate the memory for + statistical data at the same time. The precautions are taken to + guarantee the correctness of the allocation. + + @note + Currently the function always is called with the parameter is_safe set + to FALSE. +*/ + +int alloc_statistics_for_table_share(THD* thd, TABLE_SHARE *table_share, + bool is_safe) +{ + + Field **field_ptr; + KEY *key_info, *end; + TABLE_STATISTICS_CB *stats_cb= &table_share->stats_cb; + + DBUG_ENTER("alloc_statistics_for_table_share"); + + DEBUG_SYNC(thd, "statistics_mem_alloc_start1"); + DEBUG_SYNC(thd, "statistics_mem_alloc_start2"); + + if (!statistics_for_command_is_needed(thd)) + DBUG_RETURN(1); + + if (!is_safe) + mysql_mutex_lock(&table_share->LOCK_share); + + if (stats_cb->stats_can_be_read) + { + if (!is_safe) + mysql_mutex_unlock(&table_share->LOCK_share); + DBUG_RETURN(0); + } + + Table_statistics *table_stats= stats_cb->table_stats; + if (!table_stats) + { + table_stats= (Table_statistics *) alloc_root(&stats_cb->mem_root, + sizeof(Table_statistics)); + if (!table_stats) + { + if (!is_safe) + mysql_mutex_unlock(&table_share->LOCK_share); + DBUG_RETURN(1); + } + memset(table_stats, 0, sizeof(Table_statistics)); + stats_cb->table_stats= table_stats; + } + + uint fields= table_share->fields; + Column_statistics *column_stats= table_stats->column_stats; + if (!column_stats) + { + column_stats= (Column_statistics *) alloc_root(&stats_cb->mem_root, + sizeof(Column_statistics) * + (fields+1)); + if (column_stats) + { + memset(column_stats, 0, sizeof(Column_statistics) * (fields+1)); + table_stats->column_stats= column_stats; + for (field_ptr= table_share->field; + *field_ptr; + field_ptr++, column_stats++) + { + (*field_ptr)->read_stats= column_stats; + (*field_ptr)->read_stats->min_value= NULL; + (*field_ptr)->read_stats->max_value= NULL; + } + create_min_max_statistical_fields_for_table_share(thd, table_share); + } + } + + uint keys= table_share->keys; + Index_statistics *index_stats= table_stats->index_stats; + if (!index_stats) + { + index_stats= (Index_statistics *) alloc_root(&stats_cb->mem_root, + sizeof(Index_statistics) * + keys); + if (index_stats) + { + table_stats->index_stats= index_stats; + for (key_info= table_share->key_info, end= key_info + keys; + key_info < end; + key_info++, index_stats++) + { + key_info->read_stats= index_stats; + } + } + } + + uint key_parts= table_share->ext_key_parts; + ulong *idx_avg_frequency= table_stats->idx_avg_frequency; + if (!idx_avg_frequency) + { + idx_avg_frequency= (ulong*) alloc_root(&stats_cb->mem_root, + sizeof(ulong) * key_parts); + if (idx_avg_frequency) + { + memset(idx_avg_frequency, 0, sizeof(ulong) * key_parts); + table_stats->idx_avg_frequency= idx_avg_frequency; + for (key_info= table_share->key_info, end= key_info + keys; + key_info < end; + key_info++) + { + key_info->read_stats->init_avg_frequency(idx_avg_frequency); + idx_avg_frequency+= key_info->ext_key_parts; + } + } + } + + if (column_stats && index_stats && idx_avg_frequency) + stats_cb->stats_can_be_read= TRUE; + + if (!is_safe) + mysql_mutex_unlock(&table_share->LOCK_share); + + DBUG_RETURN(0); +} + + +/** + @brief + Allocate memory for the histogram used by a table share + + @param + thd Thread handler + @param + table_share Table share for which the memory for histogram data is allocated + @param + is_safe TRUE <-> at any time only one thread can perform the function + + @note + The function allocates the memory for the histogram built for a table in the + table's share memory with the intention to read the data there from the + system persistent statistical table mysql.column_stats, + The memory is allocated in the table_share's mem_root. + If the parameter is_safe is TRUE then it is guaranteed that at any given time + only one thread is executed the code of the function. + + @retval + 0 If the memory for all statistical data has been successfully allocated + @retval + 1 Otherwise + + @note + Currently the function always is called with the parameter is_safe set + to FALSE. +*/ + +static +int alloc_histograms_for_table_share(THD* thd, TABLE_SHARE *table_share, + bool is_safe) +{ + TABLE_STATISTICS_CB *stats_cb= &table_share->stats_cb; + + DBUG_ENTER("alloc_histograms_for_table_share"); + + if (!is_safe) + mysql_mutex_lock(&table_share->LOCK_share); + + if (stats_cb->histograms_can_be_read) + { + if (!is_safe) + mysql_mutex_unlock(&table_share->LOCK_share); + DBUG_RETURN(0); + } + + Table_statistics *table_stats= stats_cb->table_stats; + ulong total_hist_size= table_stats->total_hist_size; + + if (total_hist_size && !table_stats->histograms) + { + uchar *histograms= (uchar *) alloc_root(&stats_cb->mem_root, + total_hist_size); + if (!histograms) + { + if (!is_safe) + mysql_mutex_unlock(&table_share->LOCK_share); + DBUG_RETURN(1); + } + memset(histograms, 0, total_hist_size); + table_stats->histograms= histograms; + stats_cb->histograms_can_be_read= TRUE; + } + + if (!is_safe) + mysql_mutex_unlock(&table_share->LOCK_share); + + DBUG_RETURN(0); + +} + +/** + @brief + Initialize the aggregation fields to collect statistics on a column + + @param + thd Thread handler + @param + table_field Column to collect statistics for +*/ + +inline +void Column_statistics_collected::init(THD *thd, Field *table_field) +{ + uint max_heap_table_size= thd->variables.max_heap_table_size; + TABLE *table= table_field->table; + uint pk= table->s->primary_key; + + is_single_pk_col= FALSE; + + if (pk != MAX_KEY && table->key_info[pk].user_defined_key_parts == 1 && + table->key_info[pk].key_part[0].fieldnr == table_field->field_index + 1) + is_single_pk_col= TRUE; + + column= table_field; + + set_all_nulls(); + + nulls= 0; + column_total_length= 0; + if (is_single_pk_col) + count_distinct= NULL; + if (table_field->flags & BLOB_FLAG) + count_distinct= NULL; + else + { + count_distinct= + table_field->type() == MYSQL_TYPE_BIT ? + new Count_distinct_field_bit(table_field, max_heap_table_size) : + new Count_distinct_field(table_field, max_heap_table_size); + } + if (count_distinct && !count_distinct->exists()) + count_distinct= NULL; +} + + +/** + @brief + Perform aggregation for a row when collecting statistics on a column + + @param + rowno The order number of the row +*/ + +inline +bool Column_statistics_collected::add(ha_rows rowno) +{ + + bool err= 0; + if (column->is_null()) + nulls++; + else + { + column_total_length+= column->value_length(); + if (min_value && column->update_min(min_value, rowno == nulls)) + set_not_null(COLUMN_STAT_MIN_VALUE); + if (max_value && column->update_max(max_value, rowno == nulls)) + set_not_null(COLUMN_STAT_MAX_VALUE); + if (count_distinct) + err= count_distinct->add(); + } + return err; +} + + +/** + @brief + Get the results of aggregation when collecting the statistics on a column + + @param + rows The total number of rows in the table +*/ + +inline +void Column_statistics_collected::finish(ha_rows rows) +{ + double val; + + if (rows) + { + val= (double) nulls / rows; + set_nulls_ratio(val); + set_not_null(COLUMN_STAT_NULLS_RATIO); + } + if (rows - nulls) + { + val= (double) column_total_length / (rows - nulls); + set_avg_length(val); + set_not_null(COLUMN_STAT_AVG_LENGTH); + } + if (count_distinct) + { + ulonglong distincts; + uint hist_size= count_distinct->get_hist_size(); + if (hist_size == 0) + distincts= count_distinct->get_value(); + else + distincts= count_distinct->get_value_with_histogram(rows - nulls); + if (distincts) + { + val= (double) (rows - nulls) / distincts; + set_avg_frequency(val); + set_not_null(COLUMN_STAT_AVG_FREQUENCY); + } + else + hist_size= 0; + histogram.set_size(hist_size); + set_not_null(COLUMN_STAT_HIST_SIZE); + if (hist_size && distincts) + { + set_not_null(COLUMN_STAT_HIST_TYPE); + histogram.set_values(count_distinct->get_histogram()); + set_not_null(COLUMN_STAT_HISTOGRAM); + } + delete count_distinct; + count_distinct= NULL; + } + else if (is_single_pk_col) + { + val= 1.0; + set_avg_frequency(val); + set_not_null(COLUMN_STAT_AVG_FREQUENCY); + } +} + + +/** + @brief + Clean up auxiliary structures used for aggregation +*/ + +inline +void Column_statistics_collected::cleanup() +{ + if (count_distinct) + { + delete count_distinct; + count_distinct= NULL; + } +} + + +/** + @brief + Collect statistical data on an index + + @param + table The table the index belongs to + index The number of this index in the table + + @details + The function collects the value of 'avg_frequency' for the prefixes + on an index from 'table'. The index is specified by its number. + If the scan is successful the calculated statistics is saved in the + elements of the array write_stat.avg_frequency of the KEY_INFO structure + for the index. The statistics for the prefix with k components is saved + in the element number k-1. + + @retval + 0 If the statistics has been successfully collected + @retval + 1 Otherwise + + @note + The function collects statistics for the index prefixes for one index + scan during which no data is fetched from the table records. That's why + statistical data for prefixes that contain part of a field is not + collected. + The function employs an object of the helper class Index_prefix_calc to + count for each index prefix the number of index entries without nulls and + the number of distinct entries among them. + +*/ + +static +int collect_statistics_for_index(THD *thd, TABLE *table, uint index) +{ + int rc= 0; + KEY *key_info= &table->key_info[index]; + ha_rows rows= 0; + + DBUG_ENTER("collect_statistics_for_index"); + + /* No statistics for FULLTEXT indexes. */ + if (key_info->flags & (HA_FULLTEXT|HA_SPATIAL)) + DBUG_RETURN(rc); + + Index_prefix_calc index_prefix_calc(table, key_info); + + DEBUG_SYNC(table->in_use, "statistics_collection_start1"); + DEBUG_SYNC(table->in_use, "statistics_collection_start2"); + + if (index_prefix_calc.is_single_comp_pk) + { + index_prefix_calc.get_avg_frequency(); + DBUG_RETURN(rc); + } + + table->key_read= 1; + table->file->extra(HA_EXTRA_KEYREAD); + + table->file->ha_index_init(index, TRUE); + rc= table->file->ha_index_first(table->record[0]); + while (rc != HA_ERR_END_OF_FILE) + { + if (thd->killed) + break; + + if (rc) + break; + rows++; + index_prefix_calc.add(); + rc= table->file->ha_index_next(table->record[0]); + } + table->key_read= 0; + table->file->ha_index_end(); + + rc= (rc == HA_ERR_END_OF_FILE && !thd->killed) ? 0 : 1; + + if (!rc) + index_prefix_calc.get_avg_frequency(); + + DBUG_RETURN(rc); +} + + +/** + @brief + Collect statistical data for a table + + @param + thd The thread handle + @param + table The table to collect statistics on + + @details + The function collects data for various statistical characteristics on + the table 'table'. These data is saved in the internal fields that could + be reached from 'table'. The data is prepared to be saved in the persistent + statistical table by the function update_statistics_for_table. + The collected statistical values are not placed in the same fields that + keep the statistical data used by the optimizer. Therefore, at any time, + there is no collision between the statistics being collected and the one + used by the optimizer to look for optimal query execution plans for other + clients. + + @retval + 0 If the statistics has been successfully collected + @retval + 1 Otherwise + + @note + The function first collects statistical data for statistical characteristics + to be saved in the statistical tables table_stat and column_stats. To do this + it performs a full table scan of 'table'. At this scan the function collects + statistics on each column of the table and count the total number of the + scanned rows. To calculate the value of 'avg_frequency' for a column the + function constructs an object of the helper class Count_distinct_field + (or its derivation). Currently this class cannot count the number of + distinct values for blob columns. So the value of 'avg_frequency' for + blob columns is always null. + After the full table scan the function calls collect_statistics_for_index + for each table index. The latter performs full index scan for each index. + + @note + Currently the statistical data is collected indiscriminately for all + columns/indexes of 'table', for all statistical characteristics. + TODO. Collect only specified statistical characteristics for specified + columns/indexes. + + @note + Currently the process of collecting statistical data is not optimized. + For example, 'avg_frequency' for a column could be copied from the + 'avg_frequency' collected for an index if this column is used as the + first component of the index. Min and min values for this column could + be extracted from the index as well. +*/ + +int collect_statistics_for_table(THD *thd, TABLE *table) +{ + int rc; + Field **field_ptr; + Field *table_field; + ha_rows rows= 0; + handler *file=table->file; + + DBUG_ENTER("collect_statistics_for_table"); + + table->collected_stats->cardinality_is_null= TRUE; + table->collected_stats->cardinality= 0; + + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + table_field= *field_ptr; + if (!bitmap_is_set(table->read_set, table_field->field_index)) + continue; + table_field->collected_stats->init(thd, table_field); + } + + restore_record(table, s->default_values); + + /* Perform a full table scan to collect statistics on 'table's columns */ + if (!(rc= file->ha_rnd_init(TRUE))) + { + DEBUG_SYNC(table->in_use, "statistics_collection_start"); + + while ((rc= file->ha_rnd_next(table->record[0])) != HA_ERR_END_OF_FILE) + { + if (thd->killed) + break; + + if (rc) + { + if (rc == HA_ERR_RECORD_DELETED) + continue; + break; + } + + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + table_field= *field_ptr; + if (!bitmap_is_set(table->read_set, table_field->field_index)) + continue; + if ((rc= table_field->collected_stats->add(rows))) + break; + } + if (rc) + break; + rows++; + } + file->ha_rnd_end(); + } + rc= (rc == HA_ERR_END_OF_FILE && !thd->killed) ? 0 : 1; + + /* + Calculate values for all statistical characteristics on columns and + and for each field f of 'table' save them in the write_stat structure + from the Field object for f. + */ + if (!rc) + { + table->collected_stats->cardinality_is_null= FALSE; + table->collected_stats->cardinality= rows; + } + + bitmap_clear_all(table->write_set); + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + table_field= *field_ptr; + if (!bitmap_is_set(table->read_set, table_field->field_index)) + continue; + bitmap_set_bit(table->write_set, table_field->field_index); + if (!rc) + table_field->collected_stats->finish(rows); + else + table_field->collected_stats->cleanup(); + } + bitmap_clear_all(table->write_set); + + if (!rc) + { + uint key; + key_map::Iterator it(table->keys_in_use_for_query); + + MY_BITMAP *save_read_set= table->read_set; + table->read_set= &table->tmp_set; + bitmap_set_all(table->read_set); + + /* Collect statistics for indexes */ + while ((key= it++) != key_map::Iterator::BITMAP_END) + { + if ((rc= collect_statistics_for_index(thd, table, key))) + break; + } + + table->read_set= save_read_set; + } + + DBUG_RETURN(rc); +} + + +/** + @brief + Update statistics for a table in the persistent statistical tables + + @param + thd The thread handle + @param + table The table to collect statistics on + + @details + For each statistical table st the function looks for the rows from this + table that contain statistical data on 'table'. If rows with given + statistical characteristics exist they are updated with the new statistical + values taken from internal structures for 'table'. Otherwise new rows + with these statistical characteristics are added into st. + It is assumed that values stored in the statistical tables are found and + saved by the function collect_statistics_for_table. + + @retval + 0 If all statistical tables has been successfully updated + @retval + 1 Otherwise + + @note + The function is called when executing the ANALYZE actions for 'table'. + The function first unlocks the opened table the statistics on which has + been collected, but does not closes it, so all collected statistical data + remains in internal structures for 'table'. Then the function opens the + statistical tables and writes the statistical data for 'table'into them. + It is not allowed just to open statistical tables for writing when some + other tables are locked for reading. + After the statistical tables have been opened they are updated one by one + with the new statistics on 'table'. Objects of the helper classes + Table_stat, Column_stat and Index_stat are employed for this. + After having been updated the statistical system tables are closed. +*/ + +int update_statistics_for_table(THD *thd, TABLE *table) +{ + TABLE_LIST tables[STATISTICS_TABLES]; + Open_tables_backup open_tables_backup; + uint i; + int err; + enum_binlog_format save_binlog_format; + int rc= 0; + TABLE *stat_table; + + DBUG_ENTER("update_statistics_for_table"); + + DEBUG_SYNC(thd, "statistics_update_start"); + + if (open_stat_tables(thd, tables, &open_tables_backup, TRUE)) + DBUG_RETURN(rc); + + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + /* Update the statistical table table_stats */ + stat_table= tables[TABLE_STAT].table; + Table_stat table_stat(stat_table, table); + restore_record(stat_table, s->default_values); + table_stat.set_key_fields(); + err= table_stat.update_stat(); + if (err) + rc= 1; + + /* Update the statistical table colum_stats */ + stat_table= tables[COLUMN_STAT].table; + Column_stat column_stat(stat_table, table); + for (Field **field_ptr= table->field; *field_ptr; field_ptr++) + { + Field *table_field= *field_ptr; + if (!bitmap_is_set(table->read_set, table_field->field_index)) + continue; + restore_record(stat_table, s->default_values); + column_stat.set_key_fields(table_field); + err= column_stat.update_stat(); + if (err && !rc) + rc= 1; + } + + /* Update the statistical table index_stats */ + stat_table= tables[INDEX_STAT].table; + uint key; + key_map::Iterator it(table->keys_in_use_for_query); + Index_stat index_stat(stat_table, table); + + while ((key= it++) != key_map::Iterator::BITMAP_END) + { + KEY *key_info= table->key_info+key; + uint key_parts= table->actual_n_key_parts(key_info); + for (i= 0; i < key_parts; i++) + { + restore_record(stat_table, s->default_values); + index_stat.set_key_fields(key_info, i+1); + err= index_stat.update_stat(); + if (err && !rc) + rc= 1; + } + } + + thd->restore_stmt_binlog_format(save_binlog_format); + + close_system_tables(thd, &open_tables_backup); + + DBUG_RETURN(rc); +} + + +/** + @brief + Read statistics for a table from the persistent statistical tables + + @param + thd The thread handle + @param + table The table to read statistics on + @param + stat_tables The array of TABLE_LIST objects for statistical tables + + @details + For each statistical table the function looks for the rows from this + table that contain statistical data on 'table'. If such rows is found + the data from statistical columns of it is read into the appropriate + fields of internal structures for 'table'. Later at the query processing + this data are supposed to be used by the optimizer. + The parameter stat_tables should point to an array of TABLE_LIST + objects for all statistical tables linked into a list. All statistical + tables are supposed to be opened. + The function is called by read_statistics_for_tables_if_needed(). + + @retval + 0 If data has been successfully read for the table + @retval + 1 Otherwise + + @note + Objects of the helper classes Table_stat, Column_stat and Index_stat + are employed to read statistical data from the statistical tables. + now. +*/ + +static +int read_statistics_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables) +{ + uint i; + TABLE *stat_table; + Field *table_field; + Field **field_ptr; + KEY *key_info, *key_info_end; + TABLE_SHARE *table_share= table->s; + Table_statistics *read_stats= table_share->stats_cb.table_stats; + + DBUG_ENTER("read_statistics_for_table"); + + /* Read statistics from the statistical table table_stats */ + stat_table= stat_tables[TABLE_STAT].table; + Table_stat table_stat(stat_table, table); + table_stat.set_key_fields(); + table_stat.get_stat_values(); + + /* Read statistics from the statistical table column_stats */ + stat_table= stat_tables[COLUMN_STAT].table; + ulong total_hist_size= 0; + Column_stat column_stat(stat_table, table); + for (field_ptr= table_share->field; *field_ptr; field_ptr++) + { + table_field= *field_ptr; + column_stat.set_key_fields(table_field); + column_stat.get_stat_values(); + total_hist_size+= table_field->read_stats->histogram.get_size(); + } + read_stats->total_hist_size= total_hist_size; + + /* Read statistics from the statistical table index_stats */ + stat_table= stat_tables[INDEX_STAT].table; + Index_stat index_stat(stat_table, table); + for (key_info= table_share->key_info, + key_info_end= key_info + table_share->keys; + key_info < key_info_end; key_info++) + { + uint key_parts= key_info->ext_key_parts; + for (i= 0; i < key_parts; i++) + { + index_stat.set_key_fields(key_info, i+1); + index_stat.get_stat_values(); + } + + key_part_map ext_key_part_map= key_info->ext_key_part_map; + if (key_info->user_defined_key_parts != key_info->ext_key_parts && + key_info->read_stats->get_avg_frequency(key_info->user_defined_key_parts) == 0) + { + KEY *pk_key_info= table_share->key_info + table_share->primary_key; + uint k= key_info->user_defined_key_parts; + uint pk_parts= pk_key_info->user_defined_key_parts; + ha_rows n_rows= read_stats->cardinality; + double k_dist= n_rows / key_info->read_stats->get_avg_frequency(k-1); + uint m= 0; + for (uint j= 0; j < pk_parts; j++) + { + if (!(ext_key_part_map & 1 << j)) + { + for (uint l= k; l < k + m; l++) + { + double avg_frequency= + pk_key_info->read_stats->get_avg_frequency(j-1); + set_if_smaller(avg_frequency, 1); + double val= pk_key_info->read_stats->get_avg_frequency(j) / + avg_frequency; + key_info->read_stats->set_avg_frequency (l, val); + } + } + else + { + double avg_frequency= pk_key_info->read_stats->get_avg_frequency(j); + key_info->read_stats->set_avg_frequency(k + m, avg_frequency); + m++; + } + } + for (uint l= k; l < k + m; l++) + { + double avg_frequency= key_info->read_stats->get_avg_frequency(l); + if (avg_frequency == 0 || read_stats->cardinality_is_null) + avg_frequency= 1; + else if (avg_frequency > 1) + { + avg_frequency/= k_dist; + set_if_bigger(avg_frequency, 1); + } + key_info->read_stats->set_avg_frequency(l, avg_frequency); + } + } + } + + table->stats_is_read= TRUE; + + DBUG_RETURN(0); +} + + +/** + @breif + Cleanup of min/max statistical values for table share +*/ + +void delete_stat_values_for_table_share(TABLE_SHARE *table_share) +{ + TABLE_STATISTICS_CB *stats_cb= &table_share->stats_cb; + Table_statistics *table_stats= stats_cb->table_stats; + if (!table_stats) + return; + Column_statistics *column_stats= table_stats->column_stats; + if (!column_stats) + return; + + for (Field **field_ptr= table_share->field; + *field_ptr; + field_ptr++, column_stats++) + { + if (column_stats->min_value) + { + delete column_stats->min_value; + column_stats->min_value= NULL; + } + if (column_stats->max_value) + { + delete column_stats->max_value; + column_stats->max_value= NULL; + } + } +} + + +/** + @brief + Check whether any statistics is to be read for tables from a table list + + @param + thd The thread handle + @param + tables The tables list for whose tables the check is to be done + + @details + The function checks whether for any of the tables opened and locked for + a statement statistics from statistical tables is needed to be read. + + @retval + TRUE statistics for any of the tables is needed to be read + @retval + FALSE Otherwise +*/ + +static +bool statistics_for_tables_is_needed(THD *thd, TABLE_LIST *tables) +{ + if (!tables) + return FALSE; + + if (!statistics_for_command_is_needed(thd)) + return FALSE; + + /* + Do not read statistics for any query that explicity involves + statistical tables, failure to to do so we may end up + in a deadlock. + */ + + for (TABLE_LIST *tl= tables; tl; tl= tl->next_global) + { + if (!tl->is_view_or_derived() && !is_temporary_table(tl) && tl->table) + { + TABLE_SHARE *table_share= tl->table->s; + if (table_share && + table_share->table_category != TABLE_CATEGORY_USER + && is_stat_table(tl->db, tl->alias)) + return FALSE; + } + } + + for (TABLE_LIST *tl= tables; tl; tl= tl->next_global) + { + if (!tl->is_view_or_derived() && !is_temporary_table(tl) && tl->table) + { + TABLE_SHARE *table_share= tl->table->s; + if (table_share && + table_share->stats_cb.stats_can_be_read && + (!table_share->stats_cb.stats_is_read || + (!table_share->stats_cb.histograms_are_read && + thd->variables.optimizer_use_condition_selectivity > 3))) + return TRUE; + if (table_share->stats_cb.stats_is_read) + tl->table->stats_is_read= TRUE; + if (table_share->stats_cb.histograms_are_read) + tl->table->histograms_are_read= TRUE; + } + } + + return FALSE; +} + + +/** + @brief + Read histogram for a table from the persistent statistical tables + + @param + thd The thread handle + @param + table The table to read histograms for + @param + stat_tables The array of TABLE_LIST objects for statistical tables + + @details + For the statistical table columns_stats the function looks for the rows + from this table that contain statistical data on 'table'. If such rows + are found the histograms from them are read into the memory allocated + for histograms of 'table'. Later at the query processing these histogram + are supposed to be used by the optimizer. + The parameter stat_tables should point to an array of TABLE_LIST + objects for all statistical tables linked into a list. All statistical + tables are supposed to be opened. + The function is called by read_statistics_for_tables_if_needed(). + + @retval + 0 If data has been successfully read for the table + @retval + 1 Otherwise + + @note + Objects of the helper Column_stat are employed read histogram + from the statistical table column_stats now. +*/ + +static +int read_histograms_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables) +{ + TABLE_SHARE *table_share= table->s; + + DBUG_ENTER("read_histograms_for_table"); + + if (!table_share->stats_cb.histograms_can_be_read) + { + (void) alloc_histograms_for_table_share(thd, table_share, FALSE); + } + if (table_share->stats_cb.histograms_can_be_read && + !table_share->stats_cb.histograms_are_read) + { + Field **field_ptr; + uchar *histogram= table_share->stats_cb.table_stats->histograms; + TABLE *stat_table= stat_tables[COLUMN_STAT].table; + Column_stat column_stat(stat_table, table); + for (field_ptr= table_share->field; *field_ptr; field_ptr++) + { + Field *table_field= *field_ptr; + uint hist_size= table_field->read_stats->histogram.get_size(); + if (hist_size) + { + column_stat.set_key_fields(table_field); + table_field->read_stats->histogram.set_values(histogram); + column_stat.get_histogram_value(); + histogram+= hist_size; + } + } + } + + DBUG_RETURN(0); +} + +/** + @brief + Read statistics for tables from a table list if it is needed + + @param + thd The thread handle + @param + tables The tables list for whose tables to read statistics + + @details + The function first checks whether for any of the tables opened and locked + for a statement statistics from statistical tables is needed to be read. + Then, if so, it opens system statistical tables for read and reads + the statistical data from them for those tables from the list for which it + makes sense. Then the function closes system statistical tables. + + @retval + 0 Statistics for tables was successfully read + @retval + 1 Otherwise +*/ + +int read_statistics_for_tables_if_needed(THD *thd, TABLE_LIST *tables) +{ + TABLE_LIST stat_tables[STATISTICS_TABLES]; + Open_tables_backup open_tables_backup; + + DBUG_ENTER("read_statistics_for_tables_if_needed"); + + DEBUG_SYNC(thd, "statistics_read_start"); + + if (!statistics_for_tables_is_needed(thd, tables)) + DBUG_RETURN(0); + + if (open_stat_tables(thd, stat_tables, &open_tables_backup, FALSE)) + { + thd->clear_error(); + DBUG_RETURN(1); + } + + for (TABLE_LIST *tl= tables; tl; tl= tl->next_global) + { + if (!tl->is_view_or_derived() && !is_temporary_table(tl) && tl->table) + { + TABLE_SHARE *table_share= tl->table->s; + if (table_share && !(table_share->table_category == TABLE_CATEGORY_USER)) + continue; + + if (table_share && + table_share->stats_cb.stats_can_be_read && + !table_share->stats_cb.stats_is_read) + { + (void) read_statistics_for_table(thd, tl->table, stat_tables); + table_share->stats_cb.stats_is_read= TRUE; + } + if (table_share->stats_cb.stats_is_read) + tl->table->stats_is_read= TRUE; + if (thd->variables.optimizer_use_condition_selectivity > 3 && + table_share && !table_share->stats_cb.histograms_are_read) + { + (void) read_histograms_for_table(thd, tl->table, stat_tables); + table_share->stats_cb.histograms_are_read= TRUE; + } + if (table_share->stats_cb.stats_is_read) + tl->table->histograms_are_read= TRUE; + } + } + + close_system_tables(thd, &open_tables_backup); + + DBUG_RETURN(0); +} + + +/** + @brief + Delete statistics on a table from all statistical tables + + @param + thd The thread handle + @param + db The name of the database the table belongs to + @param + tab The name of the table whose statistics is to be deleted + + @details + The function delete statistics on the table called 'tab' of the database + 'db' from all statistical tables: table_stats, column_stats, index_stats. + + @retval + 0 If all deletions are successful + @retval + 1 Otherwise + + @note + The function is called when executing the statement DROP TABLE 'tab'. +*/ + +int delete_statistics_for_table(THD *thd, LEX_STRING *db, LEX_STRING *tab) +{ + int err; + enum_binlog_format save_binlog_format; + TABLE *stat_table; + TABLE_LIST tables[STATISTICS_TABLES]; + Open_tables_backup open_tables_backup; + int rc= 0; + + DBUG_ENTER("delete_statistics_for_table"); + + if (open_stat_tables(thd, tables, &open_tables_backup, TRUE)) + DBUG_RETURN(rc); + + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + /* Delete statistics on table from the statistical table index_stats */ + stat_table= tables[INDEX_STAT].table; + Index_stat index_stat(stat_table, db, tab); + index_stat.set_full_table_name(); + while (index_stat.find_next_stat_for_prefix(2)) + { + err= index_stat.delete_stat(); + if (err & !rc) + rc= 1; + } + + /* Delete statistics on table from the statistical table column_stats */ + stat_table= tables[COLUMN_STAT].table; + Column_stat column_stat(stat_table, db, tab); + column_stat.set_full_table_name(); + while (column_stat.find_next_stat_for_prefix(2)) + { + err= column_stat.delete_stat(); + if (err & !rc) + rc= 1; + } + + /* Delete statistics on table from the statistical table table_stats */ + stat_table= tables[TABLE_STAT].table; + Table_stat table_stat(stat_table, db, tab); + table_stat.set_key_fields(); + if (table_stat.find_stat()) + { + err= table_stat.delete_stat(); + if (err & !rc) + rc= 1; + } + + err= del_global_table_stat(thd, db, tab); + if (err & !rc) + rc= 1; + + thd->restore_stmt_binlog_format(save_binlog_format); + + close_system_tables(thd, &open_tables_backup); + + DBUG_RETURN(rc); +} + + +/** + @brief + Delete statistics on a column of the specified table + + @param + thd The thread handle + @param + tab The table the column belongs to + @param + col The field of the column whose statistics is to be deleted + + @details + The function delete statistics on the column 'col' belonging to the table + 'tab' from the statistical table column_stats. + + @retval + 0 If the deletion is successful + @retval + 1 Otherwise + + @note + The function is called when dropping a table column or when changing + the definition of this column. +*/ + +int delete_statistics_for_column(THD *thd, TABLE *tab, Field *col) +{ + int err; + enum_binlog_format save_binlog_format; + TABLE *stat_table; + TABLE_LIST tables; + Open_tables_backup open_tables_backup; + int rc= 0; + + DBUG_ENTER("delete_statistics_for_column"); + + if (open_single_stat_table(thd, &tables, &stat_table_name[1], + &open_tables_backup, TRUE)) + { + thd->clear_error(); + DBUG_RETURN(rc); + } + + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + stat_table= tables.table; + Column_stat column_stat(stat_table, tab); + column_stat.set_key_fields(col); + if (column_stat.find_stat()) + { + err= column_stat.delete_stat(); + if (err) + rc= 1; + } + + thd->restore_stmt_binlog_format(save_binlog_format); + + close_system_tables(thd, &open_tables_backup); + + DBUG_RETURN(rc); +} + + +/** + @brief + Delete statistics on an index of the specified table + + @param + thd The thread handle + @param + tab The table the index belongs to + @param + key_info The descriptor of the index whose statistics is to be deleted + @param + ext_prefixes_only Delete statistics only on the index prefixes extended by + the components of the primary key + + @details + The function delete statistics on the index specified by 'key_info' + defined on the table 'tab' from the statistical table index_stats. + + @retval + 0 If the deletion is successful + @retval + 1 Otherwise + + @note + The function is called when dropping an index, or dropping/changing the + definition of a column used in the definition of the index. +*/ + +int delete_statistics_for_index(THD *thd, TABLE *tab, KEY *key_info, + bool ext_prefixes_only) +{ + int err; + enum_binlog_format save_binlog_format; + TABLE *stat_table; + TABLE_LIST tables; + Open_tables_backup open_tables_backup; + int rc= 0; + + DBUG_ENTER("delete_statistics_for_index"); + + if (open_single_stat_table(thd, &tables, &stat_table_name[2], + &open_tables_backup, TRUE)) + { + thd->clear_error(); + DBUG_RETURN(rc); + } + + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + stat_table= tables.table; + Index_stat index_stat(stat_table, tab); + if (!ext_prefixes_only) + { + index_stat.set_index_prefix_key_fields(key_info); + while (index_stat.find_next_stat_for_prefix(3)) + { + err= index_stat.delete_stat(); + if (err && !rc) + rc= 1; + } + } + else + { + for (uint i= key_info->user_defined_key_parts; i < key_info->ext_key_parts; i++) + { + index_stat.set_key_fields(key_info, i+1); + if (index_stat.find_next_stat_for_prefix(4)) + { + err= index_stat.delete_stat(); + if (err && !rc) + rc= 1; + } + } + } + + err= del_global_index_stat(thd, tab, key_info); + if (err && !rc) + rc= 1; + + thd->restore_stmt_binlog_format(save_binlog_format); + + close_system_tables(thd, &open_tables_backup); + + DBUG_RETURN(rc); +} + + +/** + @brief + Rename a table in all statistical tables + + @param + thd The thread handle + @param + db The name of the database the table belongs to + @param + tab The name of the table to be renamed in statistical tables + @param + new_tab The new name of the table + + @details + The function replaces the name of the table 'tab' from the database 'db' + for 'new_tab' in all all statistical tables: table_stats, column_stats, + index_stats. + + @retval + 0 If all updates of the table name are successful + @retval + 1 Otherwise + + @note + The function is called when executing any statement that renames a table +*/ + +int rename_table_in_stat_tables(THD *thd, LEX_STRING *db, LEX_STRING *tab, + LEX_STRING *new_db, LEX_STRING *new_tab) +{ + int err; + enum_binlog_format save_binlog_format; + TABLE *stat_table; + TABLE_LIST tables[STATISTICS_TABLES]; + Open_tables_backup open_tables_backup; + int rc= 0; + + DBUG_ENTER("rename_table_in_stat_tables"); + + if (open_stat_tables(thd, tables, &open_tables_backup, TRUE)) + DBUG_RETURN(0); // not an error + + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + /* Rename table in the statistical table index_stats */ + stat_table= tables[INDEX_STAT].table; + Index_stat index_stat(stat_table, db, tab); + index_stat.set_full_table_name(); + + Stat_table_write_iter index_iter(&index_stat); + if (index_iter.init(2)) + rc= 1; + while (!index_iter.get_next_row()) + { + err= index_stat.update_table_name_key_parts(new_db, new_tab); + if (err & !rc) + rc= 1; + index_stat.set_full_table_name(); + } + index_iter.cleanup(); + + /* Rename table in the statistical table column_stats */ + stat_table= tables[COLUMN_STAT].table; + Column_stat column_stat(stat_table, db, tab); + column_stat.set_full_table_name(); + Stat_table_write_iter column_iter(&column_stat); + if (column_iter.init(2)) + rc= 1; + while (!column_iter.get_next_row()) + { + err= column_stat.update_table_name_key_parts(new_db, new_tab); + if (err & !rc) + rc= 1; + column_stat.set_full_table_name(); + } + column_iter.cleanup(); + + /* Rename table in the statistical table table_stats */ + stat_table= tables[TABLE_STAT].table; + Table_stat table_stat(stat_table, db, tab); + table_stat.set_key_fields(); + if (table_stat.find_stat()) + { + err= table_stat.update_table_name_key_parts(new_db, new_tab); + if (err & !rc) + rc= 1; + } + + thd->restore_stmt_binlog_format(save_binlog_format); + + close_system_tables(thd, &open_tables_backup); + + DBUG_RETURN(rc); +} + + +/** + @brief + Rename a column in the statistical table column_stats + + @param + thd The thread handle + @param + tab The table the column belongs to + @param + col The column to be renamed + @param + new_name The new column name + + @details + The function replaces the name of the column 'col' belonging to the table + 'tab' for 'new_name' in the statistical table column_stats. + + @retval + 0 If all updates of the table name are successful + @retval + 1 Otherwise + + @note + The function is called when executing any statement that renames a column, + but does not change the column definition. +*/ + +int rename_column_in_stat_tables(THD *thd, TABLE *tab, Field *col, + const char *new_name) +{ + int err; + enum_binlog_format save_binlog_format; + TABLE *stat_table; + TABLE_LIST tables; + Open_tables_backup open_tables_backup; + int rc= 0; + + DBUG_ENTER("rename_column_in_stat_tables"); + + if (tab->s->tmp_table != NO_TMP_TABLE) + DBUG_RETURN(0); + + if (open_single_stat_table(thd, &tables, &stat_table_name[1], + &open_tables_backup, TRUE)) + { + thd->clear_error(); + DBUG_RETURN(rc); + } + + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + /* Rename column in the statistical table table_stat */ + stat_table= tables.table; + Column_stat column_stat(stat_table, tab); + column_stat.set_key_fields(col); + if (column_stat.find_stat()) + { + err= column_stat.update_column_key_part(new_name); + if (err & !rc) + rc= 1; + } + + thd->restore_stmt_binlog_format(save_binlog_format); + + close_system_tables(thd, &open_tables_backup); + + DBUG_RETURN(rc); +} + + +/** + @brief + Set statistics for a table that will be used by the optimizer + + @param + thd The thread handle + @param + table The table to set statistics for + + @details + Depending on the value of thd->variables.use_stat_tables + the function performs the settings for the table that will control + from where the statistical data used by the optimizer will be taken. +*/ + +void set_statistics_for_table(THD *thd, TABLE *table) +{ + TABLE_STATISTICS_CB *stats_cb= &table->s->stats_cb; + Table_statistics *read_stats= stats_cb->table_stats; + Use_stat_tables_mode use_stat_table_mode= get_use_stat_tables_mode(thd); + table->used_stat_records= + (use_stat_table_mode <= COMPLEMENTARY || + !table->stats_is_read || read_stats->cardinality_is_null) ? + table->file->stats.records : read_stats->cardinality; + + /* + For partitioned table, EITS statistics is based on data from all partitions. + + On the other hand, Partition Pruning figures which partitions will be + accessed and then computes the estimate of rows in used_partitions. + + Use the estimate from Partition Pruning as it is typically more precise. + Ideally, EITS should provide per-partition statistics but this is not + implemented currently. + */ +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->part_info) + table->used_stat_records= table->file->stats.records; +#endif + + KEY *key_info, *key_info_end; + for (key_info= table->key_info, key_info_end= key_info+table->s->keys; + key_info < key_info_end; key_info++) + { + key_info->is_statistics_from_stat_tables= + (use_stat_table_mode > COMPLEMENTARY && + table->stats_is_read && + key_info->read_stats->avg_frequency_is_inited() && + key_info->read_stats->get_avg_frequency(0) > 0.5); + } +} + + +/** + @brief + Get the average frequency for a column + + @param + field The column whose average frequency is required + + @retval + The required average frequency +*/ + +double get_column_avg_frequency(Field * field) +{ + double res; + TABLE *table= field->table; + + /* + Statistics is shared by table instances and is accessed through + the table share. If table->s->field is not set for 'table', then + no column statistics is available for the table . + */ + if (!table->s->field) + { + res= table->stat_records(); + return res; + } + + Column_statistics *col_stats= table->s->field[field->field_index]->read_stats; + + if (!col_stats) + res= table->stat_records(); + else + res= col_stats->get_avg_frequency(); + return res; +} + + +/** + @brief + Estimate the number of rows in a column range using data from stat tables + + @param + field The column whose range cardinality is to be estimated + @param + min_endp The left end of the range whose cardinality is required + @param + max_endp The right end of the range whose cardinality is required + @param + range_flag The range flags + + @details + The function gets an estimate of the number of rows in a column range + using the statistical data from the table column_stats. + + @retval + The required estimate of the rows in the column range +*/ + +double get_column_range_cardinality(Field *field, + key_range *min_endp, + key_range *max_endp, + uint range_flag) +{ + double res; + TABLE *table= field->table; + Column_statistics *col_stats= table->field[field->field_index]->read_stats; + double tab_records= table->stat_records(); + + if (!col_stats) + return tab_records; + /* + Use statistics for a table only when we have actually read + the statistics from the stat tables. For example due to + chances of getting a deadlock we disable reading statistics for + a table. + */ + + if (!table->stats_is_read) + return tab_records; + + double col_nulls= tab_records * col_stats->get_nulls_ratio(); + + double col_non_nulls= tab_records - col_nulls; + + bool nulls_incl= field->null_ptr && min_endp && min_endp->key[0] && + !(range_flag & NEAR_MIN); + + if (col_non_nulls < 1) + { + if (nulls_incl) + res= col_nulls; + else + res= 0; + } + else if (min_endp && max_endp && min_endp->length == max_endp->length && + !memcmp(min_endp->key, max_endp->key, min_endp->length)) + { + if (nulls_incl) + { + /* This is null single point range */ + res= col_nulls; + } + else + { + double avg_frequency= col_stats->get_avg_frequency(); + res= avg_frequency; + if (avg_frequency > 1.0 + 0.000001 && + col_stats->min_max_values_are_provided()) + { + Histogram *hist= &col_stats->histogram; + if (hist->is_available()) + { + store_key_image_to_rec(field, (uchar *) min_endp->key, + field->key_length()); + double pos= field->pos_in_interval(col_stats->min_value, + col_stats->max_value); + res= col_non_nulls * + hist->point_selectivity(pos, + avg_frequency / col_non_nulls); + } + } + else if (avg_frequency == 0.0) + { + /* This actually means there is no statistics data */ + res= tab_records; + } + } + } + else + { + if (col_stats->min_max_values_are_provided()) + { + double sel, min_mp_pos, max_mp_pos; + + if (min_endp && !(field->null_ptr && min_endp->key[0])) + { + store_key_image_to_rec(field, (uchar *) min_endp->key, + field->key_length()); + min_mp_pos= field->pos_in_interval(col_stats->min_value, + col_stats->max_value); + } + else + min_mp_pos= 0.0; + if (max_endp) + { + store_key_image_to_rec(field, (uchar *) max_endp->key, + field->key_length()); + max_mp_pos= field->pos_in_interval(col_stats->min_value, + col_stats->max_value); + } + else + max_mp_pos= 1.0; + + Histogram *hist= &col_stats->histogram; + if (!hist->is_available()) + sel= (max_mp_pos - min_mp_pos); + else + sel= hist->range_selectivity(min_mp_pos, max_mp_pos); + res= col_non_nulls * sel; + set_if_bigger(res, col_stats->get_avg_frequency()); + } + else + res= col_non_nulls; + if (nulls_incl) + res+= col_nulls; + } + return res; +} + + + +/* + Estimate selectivity of "col=const" using a histogram + + @param pos Position of the "const" between column's min_value and + max_value. This is a number in [0..1] range. + @param avg_sel Average selectivity of condition "col=const" in this table. + It is calcuated as (#non_null_values / #distinct_values). + + @return + Expected condition selectivity (a number between 0 and 1) + + @notes + [re_zero_length_buckets] If a bucket with zero value-length is in the + middle of the histogram, we will not have min==max. Example: suppose, + pos_value=0x12, and the histogram is: + + #n #n+1 #n+2 + ... 0x10 0x12 0x12 0x14 ... + | + +------------- bucket with zero value-length + + Here, we will get min=#n+1, max=#n+2, and use the multi-bucket formula. + + The problem happens at the histogram ends. if pos_value=0, and the + histogram is: + + 0x00 0x10 ... + + then min=0, max=0. This means pos_value is contained within bucket #0, + but on the other hand, histogram data says that the bucket has only one + value. +*/ + +double Histogram::point_selectivity(double pos, double avg_sel) +{ + double sel; + /* Find the bucket that contains the value 'pos'. */ + uint min= find_bucket(pos, TRUE); + uint pos_value= (uint) (pos * prec_factor()); + + /* Find how many buckets this value occupies */ + uint max= min; + while (max + 1 < get_width() && get_value(max + 1) == pos_value) + max++; + + /* + A special case: we're looking at a single bucket, and that bucket has + zero value-length. Use the multi-bucket formula (attempt to use + single-bucket formula will cause divison by zero). + + For more details see [re_zero_length_buckets] above. + */ + if (max == min && get_value(max) == ((max==0)? 0 : get_value(max-1))) + max++; + + if (max > min) + { + /* + The value occupies multiple buckets. Use start_bucket ... end_bucket as + selectivity. + */ + double bucket_sel= 1.0/(get_width() + 1); + sel= bucket_sel * (max - min + 1); + } + else + { + /* + The value 'pos' fits within one single histogram bucket. + + Histogram buckets have the same numbers of rows, but they cover + different ranges of values. + + We assume that values are uniformly distributed across the [0..1] value + range. + */ + + /* + If all buckets covered value ranges of the same size, the width of + value range would be: + */ + double avg_bucket_width= 1.0 / (get_width() + 1); + + /* + Let's see what is the width of value range that our bucket is covering. + (min==max currently. they are kept in the formula just in case we + will want to extend it to handle multi-bucket case) + */ + double inv_prec_factor= (double) 1.0 / prec_factor(); + double current_bucket_width= + (max + 1 == get_width() ? 1.0 : (get_value(max) * inv_prec_factor)) - + (min == 0 ? 0.0 : (get_value(min-1) * inv_prec_factor)); + + DBUG_ASSERT(current_bucket_width); /* We shouldn't get a one zero-width bucket */ + + /* + So: + - each bucket has the same #rows + - values are unformly distributed across the [min_value,max_value] domain. + + If a bucket has value range that's N times bigger then average, than + each value will have to have N times fewer rows than average. + */ + sel= avg_sel * avg_bucket_width / current_bucket_width; + + /* + (Q: if we just follow this proportion we may end up in a situation + where number of different values we expect to find in this bucket + exceeds the number of rows that this histogram has in a bucket. Are + we ok with this or we would want to have certain caps?) + */ + } + return sel; +} + +/* + Check whether the table is one of the persistent statistical tables. +*/ +bool is_stat_table(const char *db, const char *table) +{ + DBUG_ASSERT(db && table); + + if (!my_strcasecmp(table_alias_charset, db, stat_tables_db_name.str)) + { + for (uint i= 0; i < STATISTICS_TABLES; i ++) + { + if (!my_strcasecmp(table_alias_charset, table, stat_table_name[i].str)) + return true; + } + } + return false; +} + +/* + Check wheter we can use EITS statistics for a field or not + + TRUE : Use EITS for the columns + FALSE: Otherwise +*/ + +bool is_eits_usable(Field *field) +{ + /* + (1): checks if we have EITS statistics for a particular column + (2): Don't use EITS for GEOMETRY columns + (3): Disabling reading EITS statistics for columns involved in the + partition list of a table. We assume the selecticivity for + such columns would be handled during partition pruning. + */ + DBUG_ASSERT(field->table->stats_is_read); + Column_statistics* col_stats= field->read_stats; + return col_stats && !col_stats->no_stat_values_provided() && //(1) + field->type() != MYSQL_TYPE_GEOMETRY && //(2) +#ifdef WITH_PARTITION_STORAGE_ENGINE + (!field->table->part_info || + !field->table->part_info->field_in_partition_expr(field)) && //(3) +#endif + true; +} diff --git a/sql/sql_statistics.h b/sql/sql_statistics.h new file mode 100644 index 00000000000..6530c2d6c8f --- /dev/null +++ b/sql/sql_statistics.h @@ -0,0 +1,458 @@ +/* Copyright 2006-2008 MySQL AB, 2008 Sun Microsystems, Inc. + + 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 */ + +#ifndef SQL_STATISTICS_H +#define SQL_STATISTICS_H + +typedef +enum enum_use_stat_tables_mode +{ + NEVER, + COMPLEMENTARY, + PEFERABLY, +} Use_stat_tables_mode; + +typedef +enum enum_histogram_type +{ + SINGLE_PREC_HB, + DOUBLE_PREC_HB +} Histogram_type; + +enum enum_stat_tables +{ + TABLE_STAT, + COLUMN_STAT, + INDEX_STAT, +}; + + +/* + These enumeration types comprise the dictionary of three + statistical tables table_stat, column_stat and index_stat + as they defined in ../scripts/mysql_system_tables.sql. + + It would be nice if the declarations of these types were + generated automatically by the table definitions. +*/ + +enum enum_table_stat_col +{ + TABLE_STAT_DB_NAME, + TABLE_STAT_TABLE_NAME, + TABLE_STAT_CARDINALITY, + TABLE_STAT_N_FIELDS +}; + +enum enum_column_stat_col +{ + COLUMN_STAT_DB_NAME, + COLUMN_STAT_TABLE_NAME, + COLUMN_STAT_COLUMN_NAME, + COLUMN_STAT_MIN_VALUE, + COLUMN_STAT_MAX_VALUE, + COLUMN_STAT_NULLS_RATIO, + COLUMN_STAT_AVG_LENGTH, + COLUMN_STAT_AVG_FREQUENCY, + COLUMN_STAT_HIST_SIZE, + COLUMN_STAT_HIST_TYPE, + COLUMN_STAT_HISTOGRAM, + COLUMN_STAT_N_FIELDS +}; + +enum enum_index_stat_col +{ + INDEX_STAT_DB_NAME, + INDEX_STAT_TABLE_NAME, + INDEX_STAT_INDEX_NAME, + INDEX_STAT_PREFIX_ARITY, + INDEX_STAT_AVG_FREQUENCY, + INDEX_STAT_N_FIELDS +}; + +inline +Use_stat_tables_mode get_use_stat_tables_mode(THD *thd) +{ + return (Use_stat_tables_mode) (thd->variables.use_stat_tables); +} + +int read_statistics_for_tables_if_needed(THD *thd, TABLE_LIST *tables); +int collect_statistics_for_table(THD *thd, TABLE *table); +int alloc_statistics_for_table_share(THD* thd, TABLE_SHARE *share, + bool is_safe); +void delete_stat_values_for_table_share(TABLE_SHARE *table_share); +int alloc_statistics_for_table(THD *thd, TABLE *table); +int update_statistics_for_table(THD *thd, TABLE *table); +int delete_statistics_for_table(THD *thd, LEX_STRING *db, LEX_STRING *tab); +int delete_statistics_for_column(THD *thd, TABLE *tab, Field *col); +int delete_statistics_for_index(THD *thd, TABLE *tab, KEY *key_info, + bool ext_prefixes_only); +int rename_table_in_stat_tables(THD *thd, LEX_STRING *db, LEX_STRING *tab, + LEX_STRING *new_db, LEX_STRING *new_tab); +int rename_column_in_stat_tables(THD *thd, TABLE *tab, Field *col, + const char *new_name); +void set_statistics_for_table(THD *thd, TABLE *table); + +double get_column_avg_frequency(Field * field); + +double get_column_range_cardinality(Field *field, + key_range *min_endp, + key_range *max_endp, + uint range_flag); +bool is_stat_table(const char *db, const char *table); +bool is_eits_usable(Field* field); + +class Histogram +{ + +private: + Histogram_type type; + uint8 size; /* Size of values array, in bytes */ + uchar *values; + + uint prec_factor() + { + switch (type) { + case SINGLE_PREC_HB: + return ((uint) (1 << 8) - 1); + case DOUBLE_PREC_HB: + return ((uint) (1 << 16) - 1); + } + return 1; + } + +public: + uint get_width() + { + switch (type) { + case SINGLE_PREC_HB: + return size; + case DOUBLE_PREC_HB: + return size / 2; + } + return 0; + } + +private: + uint get_value(uint i) + { + DBUG_ASSERT(i < get_width()); + switch (type) { + case SINGLE_PREC_HB: + return (uint) (((uint8 *) values)[i]); + case DOUBLE_PREC_HB: + return (uint) uint2korr(values + i * 2); + } + return 0; + } + + /* Find the bucket which value 'pos' falls into. */ + uint find_bucket(double pos, bool first) + { + uint val= (uint) (pos * prec_factor()); + int lp= 0; + int rp= get_width() - 1; + int d= get_width() / 2; + uint i= lp + d; + for ( ; d; d= (rp - lp) / 2, i= lp + d) + { + if (val == get_value(i)) + break; + if (val < get_value(i)) + rp= i; + else if (val > get_value(i + 1)) + lp= i + 1; + else + break; + } + + if (val > get_value(i) && i < (get_width() - 1)) + i++; + + if (val == get_value(i)) + { + if (first) + { + while(i && val == get_value(i - 1)) + i--; + } + else + { + while(i + 1 < get_width() && val == get_value(i + 1)) + i++; + } + } + return i; + } + +public: + + uint get_size() { return (uint) size; } + + Histogram_type get_type() { return type; } + + uchar *get_values() { return (uchar *) values; } + + void set_size (ulonglong sz) { size= (uint8) sz; } + + void set_type (Histogram_type t) { type= t; } + + void set_values (uchar *vals) { values= (uchar *) vals; } + + bool is_available() { return get_size() > 0 && get_values(); } + + void set_value(uint i, double val) + { + switch (type) { + case SINGLE_PREC_HB: + ((uint8 *) values)[i]= (uint8) (val * prec_factor()); + return; + case DOUBLE_PREC_HB: + int2store(values + i * 2, val * prec_factor()); + return; + } + } + + void set_prev_value(uint i) + { + switch (type) { + case SINGLE_PREC_HB: + ((uint8 *) values)[i]= ((uint8 *) values)[i-1]; + return; + case DOUBLE_PREC_HB: + int2store(values + i * 2, uint2korr(values + i * 2 - 2)); + return; + } + } + + double range_selectivity(double min_pos, double max_pos) + { + double sel; + double bucket_sel= 1.0/(get_width() + 1); + uint min= find_bucket(min_pos, TRUE); + uint max= find_bucket(max_pos, FALSE); + sel= bucket_sel * (max - min + 1); + return sel; + } + + /* + Estimate selectivity of "col=const" using a histogram + */ + double point_selectivity(double pos, double avg_sel); +}; + + +class Columns_statistics; +class Index_statistics; + +static inline +int rename_table_in_stat_tables(THD *thd, const char *db, const char *tab, + const char *new_db, const char *new_tab) +{ + LEX_STRING od= { const_cast<char*>(db), strlen(db) }; + LEX_STRING ot= { const_cast<char*>(tab), strlen(tab) }; + LEX_STRING nd= { const_cast<char*>(new_db), strlen(new_db) }; + LEX_STRING nt= { const_cast<char*>(new_tab), strlen(new_tab) }; + return rename_table_in_stat_tables(thd, &od, &ot, &nd, &nt); +} + + +/* Statistical data on a table */ + +class Table_statistics +{ + +public: + my_bool cardinality_is_null; /* TRUE if the cardinality is unknown */ + ha_rows cardinality; /* Number of rows in the table */ + uchar *min_max_record_buffers; /* Record buffers for min/max values */ + Column_statistics *column_stats; /* Array of statistical data for columns */ + Index_statistics *index_stats; /* Array of statistical data for indexes */ + ulong *idx_avg_frequency; /* Array of records per key for index prefixes */ + ulong total_hist_size; /* Total size of all histograms */ + uchar *histograms; /* Sequence of histograms */ +}; + + +/* + Statistical data on a column + + Note: objects of this class may be "empty", where they have almost all fields + as zeros, for example, get_avg_frequency() will return 0. + + objects are allocated in alloc_statistics_for_table[_share]. +*/ + +class Column_statistics +{ + +private: + static const uint Scale_factor_nulls_ratio= 100000; + static const uint Scale_factor_avg_length= 100000; + static const uint Scale_factor_avg_frequency= 100000; + +public: + /* + Bitmap indicating what statistical characteristics + are available for the column + */ + uint32 column_stat_nulls; + + /* For the below two, see comments in get_column_range_cardinality() */ + /* Minimum value for the column */ + Field *min_value; + /* Maximum value for the column */ + Field *max_value; + +private: + + /* + The ratio Z/N multiplied by the scale factor Scale_factor_nulls_ratio, + where + N is the total number of rows, + Z is the number of nulls in the column + */ + ulong nulls_ratio; + + /* + Average number of bytes occupied by the representation of a + value of the column in memory buffers such as join buffer + multiplied by the scale factor Scale_factor_avg_length. + CHAR values are stripped of trailing spaces. + Flexible values are stripped of their length prefixes. + */ + ulong avg_length; + + /* + The ratio N/D multiplied by the scale factor Scale_factor_avg_frequency, + where + N is the number of rows with not null value in the column, + D the number of distinct values among them + */ + ulong avg_frequency; + +public: + + Histogram histogram; + + uint32 no_values_provided_bitmap() + { + return + ((1 << (COLUMN_STAT_HISTOGRAM-COLUMN_STAT_COLUMN_NAME))-1) << + (COLUMN_STAT_COLUMN_NAME+1); + } + + void set_all_nulls() + { + column_stat_nulls= no_values_provided_bitmap(); + } + + void set_not_null(uint stat_field_no) + { + column_stat_nulls&= ~(1 << stat_field_no); + } + + bool is_null(uint stat_field_no) + { + return MY_TEST(column_stat_nulls & (1 << stat_field_no)); + } + + double get_nulls_ratio() + { + return (double) nulls_ratio / Scale_factor_nulls_ratio; + } + + double get_avg_length() + { + return (double) avg_length / Scale_factor_avg_length; + } + + double get_avg_frequency() + { + return (double) avg_frequency / Scale_factor_avg_frequency; + } + + void set_nulls_ratio (double val) + { + nulls_ratio= (ulong) (val * Scale_factor_nulls_ratio); + } + + void set_avg_length (double val) + { + avg_length= (ulong) (val * Scale_factor_avg_length); + } + + void set_avg_frequency (double val) + { + avg_frequency= (ulong) (val * Scale_factor_avg_frequency); + } + + bool min_max_values_are_provided() + { + return !is_null(COLUMN_STAT_MIN_VALUE) && + !is_null(COLUMN_STAT_MAX_VALUE); + } + /* + This function checks whether the values for the fields of the statistical + tables that were NULL by DEFAULT for a column have changed or not. + + @retval + TRUE: Statistics are not present for a column + FALSE: Statisitics are present for a column + */ + bool no_stat_values_provided() + { + if (column_stat_nulls == no_values_provided_bitmap()) + return true; + return false; + } +}; + + +/* Statistical data on an index prefixes */ + +class Index_statistics +{ + +private: + static const uint Scale_factor_avg_frequency= 100000; + /* + The k-th element of this array contains the ratio N/D + multiplied by the scale factor Scale_factor_avg_frequency, + where N is the number of index entries without nulls + in the first k components, and D is the number of distinct + k-component prefixes among them + */ + ulong *avg_frequency; + +public: + + void init_avg_frequency(ulong *ptr) { avg_frequency= ptr; } + + bool avg_frequency_is_inited() { return avg_frequency != NULL; } + + double get_avg_frequency(uint i) + { + return (double) avg_frequency[i] / Scale_factor_avg_frequency; + } + + void set_avg_frequency(uint i, double val) + { + avg_frequency[i]= (ulong) (val * Scale_factor_avg_frequency); + } + +}; + +#endif /* SQL_STATISTICS_H */ diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 885f53ae36a..c22e33182c6 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -41,7 +41,9 @@ bool String::real_alloc(uint32 length) if (Alloced_length < arg_length) { free(); - if (!(Ptr=(char*) my_malloc(arg_length,MYF(MY_WME)))) + if (!(Ptr=(char*) my_malloc(arg_length,MYF(MY_WME | + (thread_specific ? + MY_THREAD_SPECIFIC : 0))))) return TRUE; Alloced_length=arg_length; alloced=1; @@ -74,9 +76,9 @@ bool String::real_alloc(uint32 length) @retval false Either the copy operation is complete or, if the size of the new buffer is smaller than the currently allocated buffer (if one exists), - no allocation occured. + no allocation occurred. - @retval true An error occured when attempting to allocate memory. + @retval true An error occurred when attempting to allocate memory. */ bool String::realloc_raw(uint32 alloc_length) { @@ -89,10 +91,16 @@ bool String::realloc_raw(uint32 alloc_length) return TRUE; /* Overflow */ if (alloced) { - if (!(new_ptr= (char*) my_realloc(Ptr,len,MYF(MY_WME)))) + if (!(new_ptr= (char*) my_realloc(Ptr,len, + MYF(MY_WME | + (thread_specific ? + MY_THREAD_SPECIFIC : 0))))) return TRUE; // Signal error } - else if ((new_ptr= (char*) my_malloc(len,MYF(MY_WME)))) + else if ((new_ptr= (char*) my_malloc(len, + MYF(MY_WME | + (thread_specific ? + MY_THREAD_SPECIFIC : 0))))) { if (str_length > len - 1) str_length= 0; @@ -175,7 +183,16 @@ bool String::copy(const char *str,uint32 arg_length, CHARSET_INFO *cs) { if (alloc(arg_length)) return TRUE; - if ((str_length=arg_length)) + if (Ptr == str && arg_length == str_length) + { + /* + This can happen in some cases. This code is here mainly to avoid + warnings from valgrind, but can also be an indication of error. + */ + DBUG_PRINT("warning", ("Copying string on itself: %p %u", + str, arg_length)); + } + else if ((str_length=arg_length)) memcpy(Ptr,str,arg_length); Ptr[arg_length]=0; str_charset=cs; @@ -537,6 +554,24 @@ bool String::append(IO_CACHE* file, uint32 arg_length) return FALSE; } + +/** + Append a parenthesized number to String. + Used in various pieces of SHOW related code. + + @param nr Number + @param radix Radix, optional parameter, 10 by default. +*/ +bool String::append_parenthesized(long nr, int radix) +{ + char buff[64], *end; + buff[0]= '('; + end= int10_to_str(nr, buff + 1, radix); + *end++ = ')'; + return append(buff, (uint) (end - buff)); +} + + bool String::append_with_prefill(const char *s,uint32 arg_length, uint32 full_length, char fill_char) { @@ -554,7 +589,7 @@ bool String::append_with_prefill(const char *s,uint32 arg_length, return FALSE; } -uint32 String::numchars() +uint32 String::numchars() const { return str_charset->cset->numchars(str_charset, Ptr, Ptr+str_length); } @@ -670,7 +705,7 @@ int String::reserve(uint32 space_needed, uint32 grow_by) { if (Alloced_length < str_length + space_needed) { - if (realloc(Alloced_length + max(space_needed, grow_by) - 1)) + if (realloc(Alloced_length + MY_MAX(space_needed, grow_by) - 1)) return TRUE; } return FALSE; @@ -757,168 +792,136 @@ int sortcmp(const String *s,const String *t, CHARSET_INFO *cs) int stringcmp(const String *s,const String *t) { - uint32 s_len=s->length(),t_len=t->length(),len=min(s_len,t_len); + uint32 s_len=s->length(),t_len=t->length(),len=MY_MIN(s_len,t_len); int cmp= memcmp(s->ptr(), t->ptr(), len); return (cmp) ? cmp : (int) (s_len - t_len); } -String *copy_if_not_alloced(String *to,String *from,uint32 from_length) -{ - if (from->Alloced_length >= from_length) - return from; - if ((from->alloced && (from->Alloced_length != 0)) || !to || from == to) - { - (void) from->realloc(from_length); - return from; - } - if (to->realloc(from_length)) - return from; // Actually an error - if ((to->str_length=min(from->str_length,from_length))) - memcpy(to->Ptr,from->Ptr,to->str_length); - to->str_charset=from->str_charset; - return to; -} +/** + Return a string which has the same value with "from" and + which is safe to modify, trying to avoid unnecessary allocation + and copying when possible. + @param to Buffer. Must not be a constant string. + @param from Some existing value. We'll try to reuse it. + Can be a constant or a variable string. + @param from_length The total size that will be possibly needed. + Note, can be 0. -/**************************************************************************** - Help functions -****************************************************************************/ + Note, in some cases "from" and "to" can point to the same object. -/* - copy a string from one character set to another - - SYNOPSIS - copy_and_convert() - to Store result here - to_cs Character set of result string - from Copy from here - from_length Length of from string - from_cs From character set + If "from" is a variable string and its allocated memory is enough + to store "from_length" bytes, then "from" is returned as is. - NOTES - 'to' must be big enough as form_length * to_cs->mbmaxlen + If "from" is a variable string and its allocated memory is not enough + to store "from_length" bytes, then "from" is reallocated and returned. - RETURN - length of bytes copied to 'to' + Otherwise (if "from" is a constant string, or looks like a constant string), + then "to" is reallocated to fit "from_length" bytes, the value is copied + from "from" to "to", then "to" is returned. */ - - -static uint32 -copy_and_convert_extended(char *to, uint32 to_length, CHARSET_INFO *to_cs, - const char *from, uint32 from_length, - CHARSET_INFO *from_cs, - uint *errors) +String *copy_if_not_alloced(String *to,String *from,uint32 from_length) { - int cnvres; - my_wc_t wc; - const uchar *from_end= (const uchar*) from+from_length; - char *to_start= to; - uchar *to_end= (uchar*) to+to_length; - my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc; - my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb; - uint error_count= 0; - - while (1) + DBUG_ASSERT(to); + /* + If "from" is a constant string, e.g.: + SELECT INSERT('', <pos>, <length>, <replacement>); + we should not return it. See MDEV-9332. + + The code below detects different string types: + + a. All constant strings have Alloced_length==0 and alloced==false. + They point to a static memory array, or a mem_root memory, + and should stay untouched until the end of their life cycle. + Not safe to reuse. + + b. Some variable string have Alloced_length==0 and alloced==false initially, + they are not bound to any char array and allocate space on the first use + (and become #d). A typical example of such String is Item::str_value. + This type of string could be reused, but there is no a way to distinguish + them from the true constant strings (#a). + Not safe to reuse. + + c. Some variable strings have Alloced_length>0 and alloced==false. + They point to a fixed size writtable char array (typically on stack) + initially but can later allocate more space on the heap when the + fixed size array is too small (these strings become #d after allocation). + Safe to reuse. + + d. Some variable strings have Alloced_length>0 and alloced==true. + They already store data on the heap. + Safe to reuse. + + e. Some strings can have Alloced_length==0 and alloced==true. + This type of strings allocate space on the heap, but then are marked + as constant strings using String::mark_as_const(). + A typical example - the result of a character set conversion + of a constant string. + Not safe to reuse. + */ + if (from->Alloced_length > 0) // "from" is #c or #d (not a constant) { - if ((cnvres= (*mb_wc)(from_cs, &wc, (uchar*) from, - from_end)) > 0) - from+= cnvres; - else if (cnvres == MY_CS_ILSEQ) - { - error_count++; - from++; - wc= '?'; - } - else if (cnvres > MY_CS_TOOSMALL) - { - /* - A correct multibyte sequence detected - But it doesn't have Unicode mapping. - */ - error_count++; - from+= (-cnvres); - wc= '?'; - } - else - break; // Not enough characters + if (from->Alloced_length >= from_length) + return from; // #c or #d (large enough to store from_length bytes) -outp: - if ((cnvres= (*wc_mb)(to_cs, wc, (uchar*) to, to_end)) > 0) - to+= cnvres; - else if (cnvres == MY_CS_ILUNI && wc != '?') + if (from->alloced) { - error_count++; - wc= '?'; - goto outp; + (void) from->realloc(from_length); + return from; // #d (reallocated to fit from_length bytes) } - else - break; + /* + "from" is of type #c. It currently points to a writtable char array + (typically on stack), but is too small for "from_length" bytes. + We need to reallocate either "from" or "to". + + "from" typically points to a temporary buffer inside Item_xxx::val_str(), + or to Item::str_value, and thus is "less permanent" than "to". + + Reallocating "to" may give more benifits: + - "to" can point to a "more permanent" storage and can be reused + for multiple rows, e.g. str_buffer in Protocol::send_result_set_row(), + which is passed to val_str() for all string type rows. + - "from" can stay pointing to its original fixed size stack char array, + and thus reduce the total amount of my_alloc/my_free. + */ } - *errors= error_count; - return (uint32) (to - to_start); -} - -/* - Optimized for quick copying of ASCII characters in the range 0x00..0x7F. -*/ -uint32 -copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, - const char *from, uint32 from_length, CHARSET_INFO *from_cs, - uint *errors) -{ - /* - If any of the character sets is not ASCII compatible, - immediately switch to slow mb_wc->wc_mb method. - */ - if ((to_cs->state | from_cs->state) & MY_CS_NONASCII) - return copy_and_convert_extended(to, to_length, to_cs, - from, from_length, from_cs, errors); - - uint32 length= min(to_length, from_length), length2= length; - -#if defined(__i386__) || defined(__x86_64__) - /* - Special loop for i386, it allows to refer to a - non-aligned memory block as UINT32, which makes - it possible to copy four bytes at once. This - gives about 10% performance improvement comparing - to byte-by-byte loop. - */ - for ( ; length >= 4; length-= 4, from+= 4, to+= 4) + if (from == to) { - if ((*(uint32*)from) & 0x80808080) - break; - *((uint32*) to)= *((const uint32*) from); - } -#endif + /* + Possible string types: + #a not possible (constants should not be passed as "to") + #b possible (a fresh variable with no associated char buffer) + #c possible (a variable with a char buffer, + in case it's smaller than fixed_length) + #d not possible (handled earlier) + #e not possible (constants should not be passed as "to") + + If a string of types #a or #e appears here, that means the caller made + something wrong. Otherwise, it's safe to reallocate and return "to". + + Note, as we can't distinguish between #a and #b for sure, + so we can't assert "not #a", but we can at least assert "not #e". + */ + DBUG_ASSERT(!from->alloced || from->Alloced_length > 0); // Not #e - for (; ; *to++= *from++, length--) - { - if (!length) - { - *errors= 0; - return length2; - } - if (*((unsigned char*) from) > 0x7F) /* A non-ASCII character */ - { - uint32 copied_length= length2 - length; - to_length-= copied_length; - from_length-= copied_length; - return copied_length + copy_and_convert_extended(to, to_length, - to_cs, - from, from_length, - from_cs, - errors); - } + (void) from->realloc(from_length); + return from; } - - DBUG_ASSERT(FALSE); // Should never get to here - return 0; // Make compiler happy + if (to->realloc(from_length)) + return from; // Actually an error + if ((to->str_length=MY_MIN(from->str_length,from_length))) + memcpy(to->Ptr,from->Ptr,to->str_length); + to->str_charset=from->str_charset; + return to; // "from" was of types #a, #b, #e, or small #c. } +/**************************************************************************** + Help functions +****************************************************************************/ + /** Copy string with HEX-encoding of "bad" characters. @@ -983,185 +986,61 @@ my_copy_with_hex_escaping(CHARSET_INFO *cs, /* - copy a string, + Copy a string, with optional character set conversion, with optional left padding (for binary -> UCS2 conversion) - - SYNOPSIS - well_formed_copy_nchars() - to Store result here - to_length Maxinum length of "to" string - to_cs Character set of "to" string - from Copy from here - from_length Length of from string - from_cs From character set - nchars Copy not more that nchars characters - well_formed_error_pos Return position when "from" is not well formed + + Bad input bytes are replaced to '?'. + + The string that is written to "to" is always well-formed. + + @param to The destination string + @param to_length Space available in "to" + @param to_cs Character set of the "to" string + @param from The source string + @param from_length Length of the "from" string + @param from_cs Character set of the "from" string + @param nchars Copy not more than "nchars" characters + + The members as set as follows: + m_well_formed_error_pos To the position when "from" is not well formed or NULL otherwise. - cannot_convert_error_pos Return position where a not convertable + m_cannot_convert_error_pos To the position where a not convertable character met, or NULL otherwise. - from_end_pos Return position where scanning of "from" + m_source_end_pos To the position where scanning of the "from" string stopped. - NOTES - RETURN - length of bytes copied to 'to' + @returns number of bytes that were written to 'to' */ - - -uint32 -well_formed_copy_nchars(CHARSET_INFO *to_cs, - char *to, uint to_length, - CHARSET_INFO *from_cs, - const char *from, uint from_length, - uint nchars, - const char **well_formed_error_pos, - const char **cannot_convert_error_pos, - const char **from_end_pos) +uint +String_copier::well_formed_copy(CHARSET_INFO *to_cs, + char *to, uint to_length, + CHARSET_INFO *from_cs, + const char *from, uint from_length, + uint nchars) { - uint res; - if ((to_cs == &my_charset_bin) || (from_cs == &my_charset_bin) || (to_cs == from_cs) || my_charset_same(from_cs, to_cs)) { - if (to_length < to_cs->mbminlen || !nchars) - { - *from_end_pos= from; - *cannot_convert_error_pos= NULL; - *well_formed_error_pos= NULL; - return 0; - } - - if (to_cs == &my_charset_bin) - { - res= min(min(nchars, to_length), from_length); - memmove(to, from, res); - *from_end_pos= from + res; - *well_formed_error_pos= NULL; - *cannot_convert_error_pos= NULL; - } - else - { - int well_formed_error; - uint from_offset; - - if ((from_offset= (from_length % to_cs->mbminlen)) && - (from_cs == &my_charset_bin)) - { - /* - Copying from BINARY to UCS2 needs to prepend zeros sometimes: - INSERT INTO t1 (ucs2_column) VALUES (0x01); - 0x01 -> 0x0001 - */ - uint pad_length= to_cs->mbminlen - from_offset; - bzero(to, pad_length); - memmove(to + pad_length, from, from_offset); - /* - In some cases left zero-padding can create an incorrect character. - For example: - INSERT INTO t1 (utf32_column) VALUES (0x110000); - We'll pad the value to 0x00110000, which is a wrong UTF32 sequence! - The valid characters range is limited to 0x00000000..0x0010FFFF. - - Make sure we didn't pad to an incorrect character. - */ - if (to_cs->cset->well_formed_len(to_cs, - to, to + to_cs->mbminlen, 1, - &well_formed_error) != - to_cs->mbminlen) - { - *from_end_pos= *well_formed_error_pos= from; - *cannot_convert_error_pos= NULL; - return 0; - } - nchars--; - from+= from_offset; - from_length-= from_offset; - to+= to_cs->mbminlen; - to_length-= to_cs->mbminlen; - } - - set_if_smaller(from_length, to_length); - res= to_cs->cset->well_formed_len(to_cs, from, from + from_length, - nchars, &well_formed_error); - memmove(to, from, res); - *from_end_pos= from + res; - *well_formed_error_pos= well_formed_error ? from + res : NULL; - *cannot_convert_error_pos= NULL; - if (from_offset) - res+= to_cs->mbminlen; - } - } - else - { - int cnvres; - my_wc_t wc; - my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc; - my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb; - const uchar *from_end= (const uchar*) from + from_length; - uchar *to_end= (uchar*) to + to_length; - char *to_start= to; - *well_formed_error_pos= NULL; - *cannot_convert_error_pos= NULL; - - for ( ; nchars; nchars--) - { - const char *from_prev= from; - if ((cnvres= (*mb_wc)(from_cs, &wc, (uchar*) from, from_end)) > 0) - from+= cnvres; - else if (cnvres == MY_CS_ILSEQ) - { - if (!*well_formed_error_pos) - *well_formed_error_pos= from; - from++; - wc= '?'; - } - else if (cnvres > MY_CS_TOOSMALL) - { - /* - A correct multibyte sequence detected - But it doesn't have Unicode mapping. - */ - if (!*cannot_convert_error_pos) - *cannot_convert_error_pos= from; - from+= (-cnvres); - wc= '?'; - } - else - break; // Not enough characters - -outp: - if ((cnvres= (*wc_mb)(to_cs, wc, (uchar*) to, to_end)) > 0) - to+= cnvres; - else if (cnvres == MY_CS_ILUNI && wc != '?') - { - if (!*cannot_convert_error_pos) - *cannot_convert_error_pos= from_prev; - wc= '?'; - goto outp; - } - else - { - from= from_prev; - break; - } - } - *from_end_pos= from; - res= (uint) (to - to_start); + m_cannot_convert_error_pos= NULL; + return to_cs->cset->copy_fix(to_cs, to, to_length, from, from_length, + nchars, &m_native_copy_status); } - return (uint32) res; + return my_convert_fix(to_cs, to, to_length, from_cs, from, from_length, + nchars, this); } /* Append characters to a single-quoted string '...', escaping special - characters as necessary. + characters with backslashes as necessary. Does not add the enclosing quotes, this is left up to caller. */ -void String::append_for_single_quote(const char *st, uint len) +#define APPEND(X) if (append(X)) return 1; else break +bool String::append_for_single_quote(const char *st, uint len) { const char *end= st+len; for (; st < end; st++) @@ -1169,35 +1048,33 @@ void String::append_for_single_quote(const char *st, uint len) uchar c= *st; switch (c) { - case '\\': - append(STRING_WITH_LEN("\\\\")); - break; - case '\0': - append(STRING_WITH_LEN("\\0")); - break; - case '\'': - append(STRING_WITH_LEN("\\'")); - break; - case '\n': - append(STRING_WITH_LEN("\\n")); - break; - case '\r': - append(STRING_WITH_LEN("\\r")); - break; - case '\032': // Ctrl-Z - append(STRING_WITH_LEN("\\Z")); - break; - default: - append(c); + case '\\': APPEND(STRING_WITH_LEN("\\\\")); + case '\0': APPEND(STRING_WITH_LEN("\\0")); + case '\'': APPEND(STRING_WITH_LEN("\\'")); + case '\n': APPEND(STRING_WITH_LEN("\\n")); + case '\r': APPEND(STRING_WITH_LEN("\\r")); + case '\032': APPEND(STRING_WITH_LEN("\\Z")); + default: APPEND(c); } } + return 0; } -void String::print(String *str) +void String::print(String *str) const { str->append_for_single_quote(Ptr, str_length); } + +void String::print_with_conversion(String *print, CHARSET_INFO *cs) const +{ + StringBuffer<256> tmp(cs); + uint errors= 0; + tmp.copy(this, cs, &errors); + tmp.print(print); +} + + /* Exchange state of this object and argument. @@ -1248,7 +1125,7 @@ uint convert_to_printable(char *to, size_t to_len, char *t= to; char *t_end= to + to_len - 1; // '- 1' is for the '\0' at the end const char *f= from; - const char *f_end= from + (nbytes ? min(from_len, nbytes) : from_len); + const char *f_end= from + (nbytes ? MY_MIN(from_len, nbytes) : from_len); char *dots= to; // last safe place to append '...' if (!f || t == t_end) diff --git a/sql/sql_string.h b/sql/sql_string.h index 3175a6616bf..ba28dbeea26 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -34,17 +34,58 @@ typedef struct st_mem_root MEM_ROOT; int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); -uint32 copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, - const char *from, uint32 from_length, - CHARSET_INFO *from_cs, uint *errors); -uint32 well_formed_copy_nchars(CHARSET_INFO *to_cs, - char *to, uint to_length, - CHARSET_INFO *from_cs, - const char *from, uint from_length, - uint nchars, - const char **well_formed_error_pos, - const char **cannot_convert_error_pos, - const char **from_end_pos); +inline uint32 copy_and_convert(char *to, uint32 to_length, + CHARSET_INFO *to_cs, + const char *from, uint32 from_length, + CHARSET_INFO *from_cs, uint *errors) +{ + return my_convert(to, to_length, to_cs, from, from_length, from_cs, errors); +} + + +class String_copier: private MY_STRCONV_STATUS +{ +public: + const char *source_end_pos() const + { return m_native_copy_status.m_source_end_pos; } + const char *well_formed_error_pos() const + { return m_native_copy_status.m_well_formed_error_pos; } + const char *cannot_convert_error_pos() const + { return m_cannot_convert_error_pos; } + const char *most_important_error_pos() const + { + return well_formed_error_pos() ? well_formed_error_pos() : + cannot_convert_error_pos(); + } + /* + Convert a string between character sets. + "dstcs" and "srccs" cannot be &my_charset_bin. + */ + uint convert_fix(CHARSET_INFO *dstcs, char *dst, uint dst_length, + CHARSET_INFO *srccs, const char *src, uint src_length, + uint nchars) + { + return my_convert_fix(dstcs, dst, dst_length, + srccs, src, src_length, nchars, this); + } + /* + Copy a string. Fix bad bytes/characters to '?'. + */ + uint well_formed_copy(CHARSET_INFO *to_cs, char *to, uint to_length, + CHARSET_INFO *from_cs, const char *from, + uint from_length, uint nchars); + // Same as above, but without the "nchars" limit. + uint well_formed_copy(CHARSET_INFO *to_cs, char *to, uint to_length, + CHARSET_INFO *from_cs, const char *from, + uint from_length) + { + return well_formed_copy(to_cs, to, to_length, + from_cs, from, from_length, + from_length /* No limit on "nchars"*/); + } +}; + + size_t my_copy_with_hex_escaping(CHARSET_INFO *cs, char *dst, size_t dstlen, const char *src, size_t srclen); @@ -56,23 +97,26 @@ class String { char *Ptr; uint32 str_length,Alloced_length, extra_alloc; - bool alloced; + bool alloced,thread_specific; CHARSET_INFO *str_charset; public: String() { - Ptr=0; str_length=Alloced_length=extra_alloc=0; alloced=0; + Ptr=0; str_length=Alloced_length=extra_alloc=0; + alloced= thread_specific= 0; str_charset= &my_charset_bin; } String(uint32 length_arg) { - alloced=0; Alloced_length= extra_alloc= 0; (void) real_alloc(length_arg); + alloced= thread_specific= 0; + Alloced_length= extra_alloc= 0; (void) real_alloc(length_arg); str_charset= &my_charset_bin; } String(const char *str, CHARSET_INFO *cs) { Ptr=(char*) str; str_length= (uint32) strlen(str); - Alloced_length= extra_alloc= 0; alloced=0; + Alloced_length= extra_alloc= 0; + alloced= thread_specific= 0; str_charset=cs; } /* @@ -82,18 +126,21 @@ public: */ String(const char *str,uint32 len, CHARSET_INFO *cs) { - Ptr=(char*) str; str_length=len; Alloced_length= extra_alloc=0; alloced=0; + Ptr=(char*) str; str_length=len; Alloced_length= extra_alloc=0; + alloced= thread_specific= 0; str_charset=cs; } String(char *str,uint32 len, CHARSET_INFO *cs) { - Ptr=(char*) str; Alloced_length=str_length=len; extra_alloc= 0; alloced=0; + Ptr=(char*) str; Alloced_length=str_length=len; extra_alloc= 0; + alloced= thread_specific= 0; str_charset=cs; } String(const String &str) { Ptr=str.Ptr ; str_length=str.str_length ; - Alloced_length=str.Alloced_length; extra_alloc= 0; alloced=0; + Alloced_length=str.Alloced_length; extra_alloc= 0; + alloced= thread_specific= 0; str_charset=str.str_charset; } static void *operator new(size_t size, MEM_ROOT *mem_root) throw () @@ -108,6 +155,12 @@ public: { /* never called */ } ~String() { free(); } + /* Mark variable thread specific it it's not allocated already */ + inline void set_thread_specific() + { + if (!alloced) + thread_specific= 1; + } inline void set_charset(CHARSET_INFO *charset_arg) { str_charset= charset_arg; } inline CHARSET_INFO *charset() const { return str_charset; } @@ -120,6 +173,7 @@ public: inline bool is_empty() const { return (str_length == 0); } inline void mark_as_const() { Alloced_length= 0;} inline const char *ptr() const { return Ptr; } + inline const char *end() const { return Ptr + str_length; } inline char *c_ptr() { DBUG_ASSERT(!alloced || !Ptr || !Alloced_length || @@ -145,8 +199,13 @@ public: } LEX_STRING lex_string() const { - LEX_STRING lex_string = { (char*) ptr(), length() }; - return lex_string; + LEX_STRING str = { (char*) ptr(), length() }; + return str; + } + LEX_CSTRING lex_cstring() const + { + LEX_CSTRING skr = { ptr(), length() }; + return skr; } void set(String &str,uint32 offset,uint32 arg_length) @@ -190,22 +249,33 @@ public: str_charset=cs; } bool set_int(longlong num, bool unsigned_flag, CHARSET_INFO *cs); - bool set(longlong num, CHARSET_INFO *cs) - { return set_int(num, false, cs); } - bool set(ulonglong num, CHARSET_INFO *cs) - { return set_int((longlong)num, true, cs); } + bool set(int num, CHARSET_INFO *cs) { return set_int(num, false, cs); } + bool set(uint num, CHARSET_INFO *cs) { return set_int(num, true, cs); } + bool set(long num, CHARSET_INFO *cs) { return set_int(num, false, cs); } + bool set(ulong num, CHARSET_INFO *cs) { return set_int(num, true, cs); } + bool set(longlong num, CHARSET_INFO *cs) { return set_int(num, false, cs); } + bool set(ulonglong num, CHARSET_INFO *cs) { return set_int((longlong)num, true, cs); } bool set_real(double num,uint decimals, CHARSET_INFO *cs); - /* Move handling of buffer from some other object to String */ - void reassociate(char *ptr, uint32 length, uint32 alloced_length, - CHARSET_INFO *cs) + /* Take over handling of buffer from some other object */ + void reset(char *ptr_arg, uint32 length_arg, uint32 alloced_length_arg, + CHARSET_INFO *cs) { free(); - Ptr= ptr; - str_length= length; - Alloced_length= alloced_length; + Ptr= ptr_arg; + str_length= length_arg; + Alloced_length= alloced_length_arg; str_charset= cs; - alloced= ptr != 0; + alloced= ptr_arg != 0; + } + + /* Forget about the buffer, let some other object handle it */ + char *release() + { + char *old= Ptr; + Ptr=0; str_length= Alloced_length= extra_alloc= 0; + alloced= thread_specific= 0; + return old; } /* @@ -332,17 +402,33 @@ public: bool set_or_copy_aligned(const char *s, uint32 arg_length, CHARSET_INFO *cs); bool copy(const char*s,uint32 arg_length, CHARSET_INFO *csfrom, CHARSET_INFO *csto, uint *errors); + bool copy(const String *str, CHARSET_INFO *tocs, uint *errors) + { + return copy(str->ptr(), str->length(), str->charset(), tocs, errors); + } + bool copy(CHARSET_INFO *tocs, + CHARSET_INFO *fromcs, const char *src, uint32 src_length, + uint32 nchars, String_copier *copier) + { + if (alloc(tocs->mbmaxlen * src_length)) + return true; + str_length= copier->well_formed_copy(tocs, Ptr, Alloced_length, + fromcs, src, src_length, nchars); + str_charset= tocs; + return false; + } void move(String &s) { free(); Ptr=s.Ptr ; str_length=s.str_length ; Alloced_length=s.Alloced_length; extra_alloc= s.extra_alloc; alloced= s.alloced; + thread_specific= s.thread_specific; s.alloced= 0; } bool append(const String &s); bool append(const char *s); - bool append(LEX_STRING *ls) + bool append(const LEX_STRING *ls) { return append(ls->str, ls->length); } @@ -352,6 +438,7 @@ public: bool append(IO_CACHE* file, uint32 arg_length); bool append_with_prefill(const char *s, uint32 arg_length, uint32 full_length, char fill_char); + bool append_parenthesized(long nr, int radix= 10); int strstr(const String &search,uint32 offset=0); // Returns offset to substring or -1 int strrstr(const String &search,uint32 offset=0); // Returns offset to substring or -1 bool replace(uint32 offset,uint32 arg_length,const char *to,uint32 length); @@ -372,7 +459,7 @@ public: } bool append_hex(const char *src, uint32 srclen) { - for (const char *end= src + srclen ; src != end ; src++) + for (const char *src_end= src + srclen ; src != src_end ; src++) { if (append(_dig_vec_lower[((uchar) *src) >> 4]) || append(_dig_vec_lower[((uchar) *src) & 0x0F])) @@ -380,13 +467,17 @@ public: } return false; } + bool append_hex(const uchar *src, uint32 srclen) + { + return append_hex((const char*)src, srclen); + } bool fill(uint32 max_length,char fill); void strip_sp(); friend int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); friend int stringcmp(const String *a,const String *b); friend String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); friend class Field; - uint32 numchars(); + uint32 numchars() const; int charpos(longlong i,uint32 offset=0); int reserve(uint32 space_needed) @@ -477,8 +568,25 @@ public: str_length+= arg_length; return FALSE; } - void print(String *print); - void append_for_single_quote(const char *st, uint len); + void print(String *to) const; + void print_with_conversion(String *to, CHARSET_INFO *cs) const; + void print(String *to, CHARSET_INFO *cs) const + { + if (my_charset_same(charset(), cs)) + print(to); + else + print_with_conversion(to, cs); + } + + bool append_for_single_quote(const char *st, uint len); + bool append_for_single_quote(const String *s) + { + return append_for_single_quote(s->ptr(), s->length()); + } + bool append_for_single_quote(const char *st) + { + return append_for_single_quote(st, strlen(st)); + } /* Swap two string objects. Efficient way to exchange data without memcpy. */ void swap(String &s); @@ -487,21 +595,63 @@ public: { return (s->alloced && Ptr >= s->Ptr && Ptr < s->Ptr + s->str_length); } + uint well_formed_length() const + { + int dummy_error; + return charset()->cset->well_formed_len(charset(), ptr(), ptr() + length(), + length(), &dummy_error); + } bool is_ascii() const { if (length() == 0) return TRUE; if (charset()->mbminlen > 1) return FALSE; - for (const char *c= ptr(), *end= c + length(); c < end; c++) + for (const char *c= ptr(), *c_end= c + length(); c < c_end; c++) { if (!my_isascii(*c)) return FALSE; } return TRUE; } + bool bin_eq(const String *other) const + { + return length() == other->length() && + !memcmp(ptr(), other->ptr(), length()); + } + bool eq(const String *other, CHARSET_INFO *cs) const + { + return !sortcmp(this, other, cs); + } }; + +// The following class is a backport from MySQL 5.6: +/** + String class wrapper with a preallocated buffer of size buff_sz + + This class allows to replace sequences of: + char buff[12345]; + String str(buff, sizeof(buff)); + str.length(0); + with a simple equivalent declaration: + StringBuffer<12345> str; +*/ + +template<size_t buff_sz> +class StringBuffer : public String +{ + char buff[buff_sz]; + +public: + StringBuffer() : String(buff, buff_sz, &my_charset_bin) { length(0); } + explicit StringBuffer(CHARSET_INFO *cs) : String(buff, buff_sz, cs) + { + length(0); + } +}; + + static inline bool check_if_only_end_space(CHARSET_INFO *cs, const char *str, const char *end) @@ -509,4 +659,7 @@ static inline bool check_if_only_end_space(CHARSET_INFO *cs, return str+ cs->cset->scan(cs, str, end, MY_SEQ_SPACES) == end; } +int append_query_string(CHARSET_INFO *csinfo, String *to, + const char *str, size_t len, bool no_backslash); + #endif /* SQL_STRING_INCLUDED */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index d3448c167c4..57333e7dc30 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -18,11 +18,11 @@ /* drop and alter of tables */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "debug_sync.h" #include "sql_table.h" -#include "sql_rename.h" // do_rename #include "sql_parse.h" // test_if_data_home_dir #include "sql_cache.h" // query_cache_* #include "sql_base.h" // open_table_uncached, lock_table_names @@ -32,6 +32,7 @@ #include "sql_partition.h" // mem_alloc_error, // generate_partition_syntax, // partition_info + // NOT_A_PARTITION_ID #include "sql_db.h" // load_db_opt_by_name #include "sql_time.h" // make_truncated_value_warning #include "records.h" // init_read_record, end_read_record @@ -42,6 +43,7 @@ #include "discover.h" // readfrm #include "my_pthread.h" // pthread_mutex_t #include "log_event.h" // Query_log_event +#include "sql_statistics.h" #include <hash.h> #include <myisam.h> #include <my_dir.h> @@ -52,7 +54,6 @@ #include "sql_parse.h" #include "sql_show.h" #include "transaction.h" -#include "datadict.h" // dd_frm_type() #include "sql_audit.h" #ifdef __WIN__ @@ -63,16 +64,16 @@ const char *primary_key_name="PRIMARY"; static bool check_if_keyname_exists(const char *name,KEY *start, KEY *end); static char *make_unique_key_name(const char *field_name,KEY *start,KEY *end); -static int copy_data_between_tables(THD *thd, TABLE *,TABLE *, - List<Create_field> &, bool, - uint, ORDER *, ha_rows *,ha_rows *, - enum enum_enable_or_disable, bool); +static int copy_data_between_tables(THD *thd, TABLE *from,TABLE *to, + List<Create_field> &create, bool ignore, + uint order_num, ORDER *order, + ha_rows *copied,ha_rows *deleted, + Alter_info::enum_enable_or_disable keys_onoff, + Alter_table_ctx *alter_ctx); static bool prepare_blob_field(THD *thd, Create_field *sql_field); -static bool check_engine(THD *, const char *, const char *, HA_CREATE_INFO *); static int mysql_prepare_create_table(THD *, HA_CREATE_INFO *, Alter_info *, - bool, uint *, handler *, KEY **, uint *, - int); + uint *, handler *, KEY **, uint *, int); static uint blob_length_by_type(enum_field_types type); /** @@ -102,7 +103,8 @@ static char* add_identifier(THD* thd, char *to_p, const char * end_p, tmp_name[name_len]= 0; conv_name= tmp_name; } - res= strconvert(&my_charset_filename, conv_name, system_charset_info, + res= strconvert(&my_charset_filename, conv_name, name_len, + system_charset_info, conv_string, FN_REFLEN, &errors); if (!res || errors) { @@ -367,42 +369,22 @@ uint explain_filename(THD* thd, Table name length. */ -uint filename_to_tablename(const char *from, char *to, uint to_length -#ifndef DBUG_OFF - , bool stay_quiet -#endif /* DBUG_OFF */ - ) +uint filename_to_tablename(const char *from, char *to, uint to_length, + bool stay_quiet) { uint errors; size_t res; DBUG_ENTER("filename_to_tablename"); DBUG_PRINT("enter", ("from '%s'", from)); - if (!strncmp(from, tmp_file_prefix, tmp_file_prefix_length)) + res= strconvert(&my_charset_filename, from, FN_REFLEN, + system_charset_info, to, to_length, &errors); + if (errors) // Old 5.0 name { - /* Temporary table name. */ - res= (strnmov(to, from, to_length) - to); - } - else - { - res= strconvert(&my_charset_filename, from, - system_charset_info, to, to_length, &errors); - if (errors) // Old 5.0 name - { - res= (strxnmov(to, to_length, MYSQL50_TABLE_NAME_PREFIX, from, NullS) - - to); -#ifndef DBUG_OFF - if (!stay_quiet) { -#endif /* DBUG_OFF */ - sql_print_error("Invalid (old?) table or database name '%s'", from); -#ifndef DBUG_OFF - } -#endif /* DBUG_OFF */ - /* - TODO: add a stored procedure for fix table and database names, - and mention its name in error log. - */ - } + res= (strxnmov(to, to_length, MYSQL50_TABLE_NAME_PREFIX, from, NullS) - + to); + if (!stay_quiet) + sql_print_error("Invalid (old?) table or database name '%s'", from); } DBUG_PRINT("exit", ("to '%s'", to)); @@ -486,7 +468,7 @@ uint tablename_to_filename(const char *from, char *to, uint to_length) } DBUG_RETURN(length); } - length= strconvert(system_charset_info, from, + length= strconvert(system_charset_info, from, FN_REFLEN, &my_charset_filename, to, to_length, &errors); if (check_if_legal_tablename(to) && length + 4 < to_length) @@ -542,7 +524,7 @@ uint build_table_filename(char *buff, size_t bufflen, const char *db, db, table_name, ext, flags)); if (flags & FN_IS_TMP) // FN_FROM_IS_TMP | FN_TO_IS_TMP - strnmov(tbbuff, table_name, sizeof(tbbuff)); + strmake(tbbuff, table_name, sizeof(tbbuff)-1); else (void) tablename_to_filename(table_name, tbbuff, sizeof(tbbuff)); @@ -557,8 +539,11 @@ uint build_table_filename(char *buff, size_t bufflen, const char *db, pos= strnmov(pos, FN_ROOTDIR, end - pos); pos= strxnmov(pos, end - pos, dbbuff, FN_ROOTDIR, NullS); #ifdef USE_SYMDIR - unpack_dirname(buff, buff); - pos= strend(buff); + if (!(flags & SKIP_SYMDIR_ACCESS)) + { + unpack_dirname(buff, buff); + pos= strend(buff); + } #endif pos= strxnmov(pos, end - pos, tbbuff, ext, NullS); @@ -567,22 +552,19 @@ uint build_table_filename(char *buff, size_t bufflen, const char *db, } -/* - Creates path to a file: mysql_tmpdir/#sql1234_12_1.ext - - SYNOPSIS - build_tmptable_filename() - thd The thread handle. - buff Where to write result in my_charset_filename. - bufflen buff size +/** + Create path to a temporary table mysql_tmpdir/#sql1234_12_1 + (i.e. to its .FRM file but without an extension). - NOTES + @param thd The thread handle. + @param buff Where to write result in my_charset_filename. + @param bufflen buff size + @note Uses current_pid, thread_id, and tmp_table counter to create a file name in mysql_tmpdir. - RETURN - path length + @return Path length. */ uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen) @@ -590,9 +572,9 @@ uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen) DBUG_ENTER("build_tmptable_filename"); char *p= strnmov(buff, mysql_tmpdir, bufflen); - my_snprintf(p, bufflen - (p - buff), "/%s%lx_%lx_%x%s", + my_snprintf(p, bufflen - (p - buff), "/%s%lx_%lx_%x", tmp_file_prefix, current_pid, - thd->thread_id, thd->tmp_table++, reg_ext); + thd->thread_id, thd->tmp_table++); if (lower_case_table_names) { @@ -631,9 +613,15 @@ uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen) -------------------------------------------------------------------------- */ - struct st_global_ddl_log { + /* + We need to adjust buffer size to be able to handle downgrades/upgrades + where IO_SIZE has changed. We'll set the buffer size such that we can + handle that the buffer size was upto 4 times bigger in the version + that wrote the DDL log. + */ + char file_entry_buf[4*IO_SIZE]; char file_name_str[FN_REFLEN]; char *file_name; DDL_LOG_MEMORY_ENTRY *first_free; @@ -661,31 +649,28 @@ mysql_mutex_t LOCK_gdl; #define DDL_LOG_NUM_ENTRY_POS 0 #define DDL_LOG_NAME_LEN_POS 4 #define DDL_LOG_IO_SIZE_POS 8 -#define DDL_LOG_HEADER_SIZE 12 /** Read one entry from ddl log file. - @param[out] file_entry_buf Buffer to read into - @param entry_no Entry number to read - @param size Number of bytes of the entry to read + + @param entry_no Entry number to read @return Operation status @retval true Error @retval false Success */ -static bool read_ddl_log_file_entry(uchar *file_entry_buf, - uint entry_no, - uint size) +static bool read_ddl_log_file_entry(uint entry_no) { bool error= FALSE; File file_id= global_ddl_log.file_id; - uint io_size= global_ddl_log.io_size; + uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf; + size_t io_size= global_ddl_log.io_size; DBUG_ENTER("read_ddl_log_file_entry"); - DBUG_ASSERT(io_size >= size); - if (mysql_file_pread(file_id, file_entry_buf, size, ((my_off_t)io_size) * entry_no, - MYF(MY_WME)) != size) + mysql_mutex_assert_owner(&LOCK_gdl); + if (mysql_file_pread(file_id, file_entry_buf, io_size, io_size * entry_no, + MYF(MY_WME)) != io_size) error= TRUE; DBUG_RETURN(error); } @@ -694,75 +679,77 @@ static bool read_ddl_log_file_entry(uchar *file_entry_buf, /** Write one entry to ddl log file. - @param file_entry_buf Buffer to write - @param entry_no Entry number to write - @param size Number of bytes of the entry to write + @param entry_no Entry number to write @return Operation status @retval true Error @retval false Success */ -static bool write_ddl_log_file_entry(uchar *file_entry_buf, - uint entry_no, - uint size) +static bool write_ddl_log_file_entry(uint entry_no) { bool error= FALSE; File file_id= global_ddl_log.file_id; - uint io_size= global_ddl_log.io_size; + uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf; DBUG_ENTER("write_ddl_log_file_entry"); - DBUG_ASSERT(io_size >= size); - if (mysql_file_pwrite(file_id, file_entry_buf, size, - io_size * entry_no, MYF(MY_WME)) != size) + mysql_mutex_assert_owner(&LOCK_gdl); + if (mysql_file_pwrite(file_id, file_entry_buf, + IO_SIZE, IO_SIZE * entry_no, MYF(MY_WME)) != IO_SIZE) error= TRUE; DBUG_RETURN(error); } -/* - Write ddl log header - SYNOPSIS - write_ddl_log_header() - RETURN VALUES - TRUE Error - FALSE Success +/** + Sync the ddl log file. + + @return Operation status + @retval FALSE Success + @retval TRUE Error +*/ + + +static bool sync_ddl_log_file() +{ + DBUG_ENTER("sync_ddl_log_file"); + DBUG_RETURN(mysql_file_sync(global_ddl_log.file_id, MYF(MY_WME))); +} + + +/** + Write ddl log header. + + @return Operation status + @retval TRUE Error + @retval FALSE Success */ static bool write_ddl_log_header() { uint16 const_var; - bool error= FALSE; - uchar file_entry_buf[DDL_LOG_HEADER_SIZE]; DBUG_ENTER("write_ddl_log_header"); - DBUG_ASSERT((DDL_LOG_NAME_POS + 3 * global_ddl_log.name_len) - <= global_ddl_log.io_size); - int4store(&file_entry_buf[DDL_LOG_NUM_ENTRY_POS], + int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NUM_ENTRY_POS], global_ddl_log.num_entries); - const_var= global_ddl_log.name_len; - int4store(&file_entry_buf[DDL_LOG_NAME_LEN_POS], + const_var= FN_REFLEN; + int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_LEN_POS], (ulong) const_var); - const_var= global_ddl_log.io_size; - int4store(&file_entry_buf[DDL_LOG_IO_SIZE_POS], + const_var= IO_SIZE; + int4store(&global_ddl_log.file_entry_buf[DDL_LOG_IO_SIZE_POS], (ulong) const_var); - if (write_ddl_log_file_entry(file_entry_buf, 0UL, DDL_LOG_HEADER_SIZE)) + if (write_ddl_log_file_entry(0UL)) { sql_print_error("Error writing ddl log header"); DBUG_RETURN(TRUE); } - (void) sync_ddl_log(); - DBUG_RETURN(error); + DBUG_RETURN(sync_ddl_log_file()); } -/* - Create ddl log file name - SYNOPSIS - create_ddl_log_file_name() - file_name Filename setup - RETURN VALUES - NONE +/** + Create ddl log file name. + @param file_name Filename setup */ static inline void create_ddl_log_file_name(char *file_name) @@ -771,35 +758,32 @@ static inline void create_ddl_log_file_name(char *file_name) } -/* - Read header of ddl log file - SYNOPSIS - read_ddl_log_header() - RETURN VALUES - > 0 Last entry in ddl log - 0 No entries in ddl log - DESCRIPTION - When we read the ddl log header we get information about maximum sizes - of names in the ddl log and we also get information about the number - of entries in the ddl log. +/** + Read header of ddl log file. + + When we read the ddl log header we get information about maximum sizes + of names in the ddl log and we also get information about the number + of entries in the ddl log. + + @return Last entry in ddl log (0 if no entries) */ static uint read_ddl_log_header() { - char file_entry_buf[DDL_LOG_HEADER_SIZE]; + uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf; char file_name[FN_REFLEN]; uint entry_no; bool successful_open= FALSE; DBUG_ENTER("read_ddl_log_header"); - DBUG_ASSERT(global_ddl_log.io_size <= IO_SIZE); + mysql_mutex_init(key_LOCK_gdl, &LOCK_gdl, MY_MUTEX_INIT_SLOW); + mysql_mutex_lock(&LOCK_gdl); create_ddl_log_file_name(file_name); if ((global_ddl_log.file_id= mysql_file_open(key_file_global_ddl_log, file_name, O_RDWR | O_BINARY, MYF(0))) >= 0) { - if (read_ddl_log_file_entry((uchar *) file_entry_buf, 0UL, - DDL_LOG_HEADER_SIZE)) + if (read_ddl_log_file_entry(0UL)) { /* Write message into error log */ sql_print_error("Failed to read ddl log file in recovery"); @@ -812,6 +796,8 @@ static uint read_ddl_log_header() entry_no= uint4korr(&file_entry_buf[DDL_LOG_NUM_ENTRY_POS]); global_ddl_log.name_len= uint4korr(&file_entry_buf[DDL_LOG_NAME_LEN_POS]); global_ddl_log.io_size= uint4korr(&file_entry_buf[DDL_LOG_IO_SIZE_POS]); + DBUG_ASSERT(global_ddl_log.io_size <= + sizeof(global_ddl_log.file_entry_buf)); } else { @@ -820,28 +806,72 @@ static uint read_ddl_log_header() global_ddl_log.first_free= NULL; global_ddl_log.first_used= NULL; global_ddl_log.num_entries= 0; - mysql_mutex_init(key_LOCK_gdl, &LOCK_gdl, MY_MUTEX_INIT_FAST); global_ddl_log.do_release= true; + mysql_mutex_unlock(&LOCK_gdl); DBUG_RETURN(entry_no); } /** - Set ddl log entry struct from buffer - @param read_entry Entry number - @param file_entry_buf Buffer to use - @param ddl_log_entry Entry to be set + Convert from ddl_log_entry struct to file_entry_buf binary blob. + + @param ddl_log_entry filled in ddl_log_entry struct. +*/ + +static void set_global_from_ddl_log_entry(const DDL_LOG_ENTRY *ddl_log_entry) +{ + mysql_mutex_assert_owner(&LOCK_gdl); + global_ddl_log.file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= + (char)DDL_LOG_ENTRY_CODE; + global_ddl_log.file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= + (char)ddl_log_entry->action_type; + global_ddl_log.file_entry_buf[DDL_LOG_PHASE_POS]= 0; + int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NEXT_ENTRY_POS], + ddl_log_entry->next_entry); + DBUG_ASSERT(strlen(ddl_log_entry->name) < FN_REFLEN); + strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS], + ddl_log_entry->name, FN_REFLEN - 1); + if (ddl_log_entry->action_type == DDL_LOG_RENAME_ACTION || + ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION || + ddl_log_entry->action_type == DDL_LOG_EXCHANGE_ACTION) + { + DBUG_ASSERT(strlen(ddl_log_entry->from_name) < FN_REFLEN); + strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_REFLEN], + ddl_log_entry->from_name, FN_REFLEN - 1); + } + else + global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_REFLEN]= 0; + DBUG_ASSERT(strlen(ddl_log_entry->handler_name) < FN_REFLEN); + strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (2*FN_REFLEN)], + ddl_log_entry->handler_name, FN_REFLEN - 1); + if (ddl_log_entry->action_type == DDL_LOG_EXCHANGE_ACTION) + { + DBUG_ASSERT(strlen(ddl_log_entry->tmp_name) < FN_REFLEN); + strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (3*FN_REFLEN)], + ddl_log_entry->tmp_name, FN_REFLEN - 1); + } + else + global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (3*FN_REFLEN)]= 0; +} + + +/** + Convert from file_entry_buf binary blob to ddl_log_entry struct. - @note Pointers in ddl_log_entry will point into file_entry_buf! + @param[out] ddl_log_entry struct to fill in. + + @note Strings (names) are pointing to the global_ddl_log structure, + so LOCK_gdl needs to be hold until they are read or copied. */ -static void set_ddl_log_entry_from_buf(uint read_entry, - uchar *file_entry_buf, - DDL_LOG_ENTRY *ddl_log_entry) +static void set_ddl_log_entry_from_global(DDL_LOG_ENTRY *ddl_log_entry, + const uint read_entry) { + char *file_entry_buf= (char*) global_ddl_log.file_entry_buf; uint inx; uchar single_char; - DBUG_ENTER("set_ddl_log_entry_from_buf"); + + mysql_mutex_assert_owner(&LOCK_gdl); ddl_log_entry->entry_pos= read_entry; single_char= file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]; ddl_log_entry->entry_type= (enum ddl_log_entry_code)single_char; @@ -849,27 +879,56 @@ static void set_ddl_log_entry_from_buf(uint read_entry, ddl_log_entry->action_type= (enum ddl_log_action_code)single_char; ddl_log_entry->phase= file_entry_buf[DDL_LOG_PHASE_POS]; ddl_log_entry->next_entry= uint4korr(&file_entry_buf[DDL_LOG_NEXT_ENTRY_POS]); - ddl_log_entry->name= (char*) &file_entry_buf[DDL_LOG_NAME_POS]; + ddl_log_entry->name= &file_entry_buf[DDL_LOG_NAME_POS]; inx= DDL_LOG_NAME_POS + global_ddl_log.name_len; - ddl_log_entry->from_name= (char*) &file_entry_buf[inx]; + ddl_log_entry->from_name= &file_entry_buf[inx]; inx+= global_ddl_log.name_len; - ddl_log_entry->handler_name= (char*) &file_entry_buf[inx]; - DBUG_VOID_RETURN; + ddl_log_entry->handler_name= &file_entry_buf[inx]; + if (ddl_log_entry->action_type == DDL_LOG_EXCHANGE_ACTION) + { + inx+= global_ddl_log.name_len; + ddl_log_entry->tmp_name= &file_entry_buf[inx]; + } + else + ddl_log_entry->tmp_name= NULL; } - -/* - Initialise ddl log - SYNOPSIS - init_ddl_log() - DESCRIPTION - Write the header of the ddl log file and length of names. Also set - number of entries to zero. +/** + Read a ddl log entry. - RETURN VALUES - TRUE Error - FALSE Success + Read a specified entry in the ddl log. + + @param read_entry Number of entry to read + @param[out] entry_info Information from entry + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool read_ddl_log_entry(uint read_entry, DDL_LOG_ENTRY *ddl_log_entry) +{ + DBUG_ENTER("read_ddl_log_entry"); + + if (read_ddl_log_file_entry(read_entry)) + { + DBUG_RETURN(TRUE); + } + set_ddl_log_entry_from_global(ddl_log_entry, read_entry); + DBUG_RETURN(FALSE); +} + + +/** + Initialise ddl log. + + Write the header of the ddl log file and length of names. Also set + number of entries to zero. + + @return Operation status + @retval TRUE Error + @retval FALSE Success */ static bool init_ddl_log() @@ -881,7 +940,7 @@ static bool init_ddl_log() goto end; global_ddl_log.io_size= IO_SIZE; - global_ddl_log.name_len= FN_LEN; + global_ddl_log.name_len= FN_REFLEN; create_ddl_log_file_name(file_name); if ((global_ddl_log.file_id= mysql_file_create(key_file_global_ddl_log, file_name, CREATE_MODE, @@ -905,14 +964,116 @@ end: } -/* +/** + Sync ddl log file. + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool sync_ddl_log_no_lock() +{ + DBUG_ENTER("sync_ddl_log_no_lock"); + + mysql_mutex_assert_owner(&LOCK_gdl); + if ((!global_ddl_log.recovery_phase) && + init_ddl_log()) + { + DBUG_RETURN(TRUE); + } + DBUG_RETURN(sync_ddl_log_file()); +} + + +/** + @brief Deactivate an individual entry. + + @details For complex rename operations we need to deactivate individual + entries. + + During replace operations where we start with an existing table called + t1 and a replacement table called t1#temp or something else and where + we want to delete t1 and rename t1#temp to t1 this is not possible to + do in a safe manner unless the ddl log is informed of the phases in + the change. + + Delete actions are 1-phase actions that can be ignored immediately after + being executed. + Rename actions from x to y is also a 1-phase action since there is no + interaction with any other handlers named x and y. + Replace action where drop y and x -> y happens needs to be a two-phase + action. Thus the first phase will drop y and the second phase will + rename x -> y. + + @param entry_no Entry position of record to change + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool deactivate_ddl_log_entry_no_lock(uint entry_no) +{ + uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf; + DBUG_ENTER("deactivate_ddl_log_entry_no_lock"); + + mysql_mutex_assert_owner(&LOCK_gdl); + if (!read_ddl_log_file_entry(entry_no)) + { + if (file_entry_buf[DDL_LOG_ENTRY_TYPE_POS] == DDL_LOG_ENTRY_CODE) + { + /* + Log entry, if complete mark it done (IGNORE). + Otherwise increase the phase by one. + */ + if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_DELETE_ACTION || + file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_RENAME_ACTION || + (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION && + file_entry_buf[DDL_LOG_PHASE_POS] == 1) || + (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_EXCHANGE_ACTION && + file_entry_buf[DDL_LOG_PHASE_POS] >= EXCH_PHASE_TEMP_TO_FROM)) + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE; + else if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION) + { + DBUG_ASSERT(file_entry_buf[DDL_LOG_PHASE_POS] == 0); + file_entry_buf[DDL_LOG_PHASE_POS]= 1; + } + else if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_EXCHANGE_ACTION) + { + DBUG_ASSERT(file_entry_buf[DDL_LOG_PHASE_POS] <= + EXCH_PHASE_FROM_TO_NAME); + file_entry_buf[DDL_LOG_PHASE_POS]++; + } + else + { + DBUG_ASSERT(0); + } + if (write_ddl_log_file_entry(entry_no)) + { + sql_print_error("Error in deactivating log entry. Position = %u", + entry_no); + DBUG_RETURN(TRUE); + } + } + } + else + { + sql_print_error("Failed in reading entry before deactivating it"); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); +} + + +/** Execute one action in a ddl log entry - SYNOPSIS - execute_ddl_log_action() - ddl_log_entry Information in action entry to execute - RETURN VALUES - TRUE Error - FALSE Success + + @param ddl_log_entry Information in action entry to execute + + @return Operation status + @retval TRUE Error + @retval FALSE Success */ static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry) @@ -930,28 +1091,31 @@ static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry) handlerton *hton; DBUG_ENTER("execute_ddl_log_action"); + mysql_mutex_assert_owner(&LOCK_gdl); if (ddl_log_entry->entry_type == DDL_IGNORE_LOG_ENTRY_CODE) { DBUG_RETURN(FALSE); } DBUG_PRINT("ddl_log", - ("execute type %c next %u name '%s' from_name '%s' handler '%s'", + ("execute type %c next %u name '%s' from_name '%s' handler '%s'" + " tmp_name '%s'", ddl_log_entry->action_type, ddl_log_entry->next_entry, ddl_log_entry->name, ddl_log_entry->from_name, - ddl_log_entry->handler_name)); + ddl_log_entry->handler_name, + ddl_log_entry->tmp_name)); handler_name.str= (char*)ddl_log_entry->handler_name; handler_name.length= strlen(ddl_log_entry->handler_name); - init_sql_alloc(&mem_root, TABLE_ALLOC_BLOCK_SIZE, 0); + init_sql_alloc(&mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC)); if (!strcmp(ddl_log_entry->handler_name, reg_ext)) frm_action= TRUE; else { - plugin_ref plugin= ha_resolve_by_name(thd, &handler_name); + plugin_ref plugin= ha_resolve_by_name(thd, &handler_name, false); if (!plugin) { - my_error(ER_ILLEGAL_HA, MYF(0), ddl_log_entry->handler_name); + my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), ddl_log_entry->handler_name); goto error; } hton= plugin_data(plugin, handlerton*); @@ -990,9 +1154,9 @@ static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry) break; } } - if ((deactivate_ddl_log_entry(ddl_log_entry->entry_pos))) + if ((deactivate_ddl_log_entry_no_lock(ddl_log_entry->entry_pos))) break; - (void) sync_ddl_log(); + (void) sync_ddl_log_no_lock(); error= FALSE; if (ddl_log_entry->action_type == DDL_LOG_DELETE_ACTION) break; @@ -1025,12 +1189,64 @@ static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry) ddl_log_entry->name)) break; } - if ((deactivate_ddl_log_entry(ddl_log_entry->entry_pos))) + if ((deactivate_ddl_log_entry_no_lock(ddl_log_entry->entry_pos))) break; - (void) sync_ddl_log(); + (void) sync_ddl_log_no_lock(); error= FALSE; break; } + case DDL_LOG_EXCHANGE_ACTION: + { + /* We hold LOCK_gdl, so we can alter global_ddl_log.file_entry_buf */ + char *file_entry_buf= (char*)&global_ddl_log.file_entry_buf; + /* not yet implemented for frm */ + DBUG_ASSERT(!frm_action); + /* + Using a case-switch here to revert all currently done phases, + since it will fall through until the first phase is undone. + */ + switch (ddl_log_entry->phase) { + case EXCH_PHASE_TEMP_TO_FROM: + /* tmp_name -> from_name possibly done */ + (void) file->ha_rename_table(ddl_log_entry->from_name, + ddl_log_entry->tmp_name); + /* decrease the phase and sync */ + file_entry_buf[DDL_LOG_PHASE_POS]--; + if (write_ddl_log_file_entry(ddl_log_entry->entry_pos)) + break; + if (sync_ddl_log_no_lock()) + break; + /* fall through */ + case EXCH_PHASE_FROM_TO_NAME: + /* from_name -> name possibly done */ + (void) file->ha_rename_table(ddl_log_entry->name, + ddl_log_entry->from_name); + /* decrease the phase and sync */ + file_entry_buf[DDL_LOG_PHASE_POS]--; + if (write_ddl_log_file_entry(ddl_log_entry->entry_pos)) + break; + if (sync_ddl_log_no_lock()) + break; + /* fall through */ + case EXCH_PHASE_NAME_TO_TEMP: + /* name -> tmp_name possibly done */ + (void) file->ha_rename_table(ddl_log_entry->tmp_name, + ddl_log_entry->name); + /* disable the entry and sync */ + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE; + if (write_ddl_log_file_entry(ddl_log_entry->entry_pos)) + break; + if (sync_ddl_log_no_lock()) + break; + error= FALSE; + break; + default: + DBUG_ASSERT(0); + break; + } + + break; + } default: DBUG_ASSERT(0); break; @@ -1042,14 +1258,14 @@ error: } -/* +/** Get a free entry in the ddl log - SYNOPSIS - get_free_ddl_log_entry() - out:active_entry A ddl log memory entry returned - RETURN VALUES - TRUE Error - FALSE Success + + @param[out] active_entry A ddl log memory entry returned + + @return Operation status + @retval TRUE Error + @retval FALSE Success */ static bool get_free_ddl_log_entry(DDL_LOG_MEMORY_ENTRY **active_entry, @@ -1059,7 +1275,6 @@ static bool get_free_ddl_log_entry(DDL_LOG_MEMORY_ENTRY **active_entry, DDL_LOG_MEMORY_ENTRY *first_used= global_ddl_log.first_used; DBUG_ENTER("get_free_ddl_log_entry"); - mysql_mutex_assert_owner(&LOCK_gdl); if (global_ddl_log.first_free == NULL) { if (!(used_entry= (DDL_LOG_MEMORY_ENTRY*)my_malloc( @@ -1093,76 +1308,99 @@ static bool get_free_ddl_log_entry(DDL_LOG_MEMORY_ENTRY **active_entry, } +/** + Execute one entry in the ddl log. + + Executing an entry means executing a linked list of actions. + + @param first_entry Reference to first action in entry + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool execute_ddl_log_entry_no_lock(THD *thd, uint first_entry) +{ + DDL_LOG_ENTRY ddl_log_entry; + uint read_entry= first_entry; + DBUG_ENTER("execute_ddl_log_entry_no_lock"); + + mysql_mutex_assert_owner(&LOCK_gdl); + do + { + if (read_ddl_log_entry(read_entry, &ddl_log_entry)) + { + /* Write to error log and continue with next log entry */ + sql_print_error("Failed to read entry = %u from ddl log", + read_entry); + break; + } + DBUG_ASSERT(ddl_log_entry.entry_type == DDL_LOG_ENTRY_CODE || + ddl_log_entry.entry_type == DDL_IGNORE_LOG_ENTRY_CODE); + + if (execute_ddl_log_action(thd, &ddl_log_entry)) + { + /* Write to error log and continue with next log entry */ + sql_print_error("Failed to execute action for entry = %u from ddl log", + read_entry); + break; + } + read_entry= ddl_log_entry.next_entry; + } while (read_entry); + DBUG_RETURN(FALSE); +} + + /* External interface methods for the DDL log Module --------------------------------------------------- */ -/* - SYNOPSIS - write_ddl_log_entry() - ddl_log_entry Information about log entry - out:entry_written Entry information written into +/** + Write a ddl log entry. - RETURN VALUES - TRUE Error - FALSE Success + A careful write of the ddl log is performed to ensure that we can + handle crashes occurring during CREATE and ALTER TABLE processing. - DESCRIPTION - A careful write of the ddl log is performed to ensure that we can - handle crashes occurring during CREATE and ALTER TABLE processing. + @param ddl_log_entry Information about log entry + @param[out] entry_written Entry information written into + + @return Operation status + @retval TRUE Error + @retval FALSE Success */ bool write_ddl_log_entry(DDL_LOG_ENTRY *ddl_log_entry, DDL_LOG_MEMORY_ENTRY **active_entry) { bool error, write_header; - char file_entry_buf[IO_SIZE]; DBUG_ENTER("write_ddl_log_entry"); + mysql_mutex_assert_owner(&LOCK_gdl); if (init_ddl_log()) { DBUG_RETURN(TRUE); } - memset(file_entry_buf, 0, sizeof(file_entry_buf)); - file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= - (char)DDL_LOG_ENTRY_CODE; - file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= - (char)ddl_log_entry->action_type; - file_entry_buf[DDL_LOG_PHASE_POS]= 0; - int4store(&file_entry_buf[DDL_LOG_NEXT_ENTRY_POS], - ddl_log_entry->next_entry); - DBUG_ASSERT(strlen(ddl_log_entry->name) < global_ddl_log.name_len); - strmake(&file_entry_buf[DDL_LOG_NAME_POS], ddl_log_entry->name, - global_ddl_log.name_len - 1); - if (ddl_log_entry->action_type == DDL_LOG_RENAME_ACTION || - ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION) - { - DBUG_ASSERT(strlen(ddl_log_entry->from_name) < global_ddl_log.name_len); - strmake(&file_entry_buf[DDL_LOG_NAME_POS + global_ddl_log.name_len], - ddl_log_entry->from_name, global_ddl_log.name_len - 1); - } - else - file_entry_buf[DDL_LOG_NAME_POS + global_ddl_log.name_len]= 0; - DBUG_ASSERT(strlen(ddl_log_entry->handler_name) < global_ddl_log.name_len); - strmake(&file_entry_buf[DDL_LOG_NAME_POS + (2*global_ddl_log.name_len)], - ddl_log_entry->handler_name, global_ddl_log.name_len - 1); + set_global_from_ddl_log_entry(ddl_log_entry); if (get_free_ddl_log_entry(active_entry, &write_header)) { DBUG_RETURN(TRUE); } error= FALSE; DBUG_PRINT("ddl_log", - ("write type %c next %u name '%s' from_name '%s' handler '%s'", - (char) file_entry_buf[DDL_LOG_ACTION_TYPE_POS], + ("write type %c next %u name '%s' from_name '%s' handler '%s'" + " tmp_name '%s'", + (char) global_ddl_log.file_entry_buf[DDL_LOG_ACTION_TYPE_POS], ddl_log_entry->next_entry, - (char*) &file_entry_buf[DDL_LOG_NAME_POS], - (char*) &file_entry_buf[DDL_LOG_NAME_POS + - global_ddl_log.name_len], - (char*) &file_entry_buf[DDL_LOG_NAME_POS + - (2*global_ddl_log.name_len)])); - if (write_ddl_log_file_entry((uchar*) file_entry_buf, - (*active_entry)->entry_pos, IO_SIZE)) + (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS], + (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + + FN_REFLEN], + (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + + (2*FN_REFLEN)], + (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + + (3*FN_REFLEN)])); + if (write_ddl_log_file_entry((*active_entry)->entry_pos)) { error= TRUE; sql_print_error("Failed to write entry_no = %u", @@ -1170,7 +1408,7 @@ bool write_ddl_log_entry(DDL_LOG_ENTRY *ddl_log_entry, } if (write_header && !error) { - (void) sync_ddl_log(); + (void) sync_ddl_log_no_lock(); if (write_ddl_log_header()) error= TRUE; } @@ -1180,31 +1418,30 @@ bool write_ddl_log_entry(DDL_LOG_ENTRY *ddl_log_entry, } -/* - Write final entry in the ddl log - SYNOPSIS - write_execute_ddl_log_entry() - first_entry First entry in linked list of entries +/** + @brief Write final entry in the ddl log. + + @details This is the last write in the ddl log. The previous log entries + have already been written but not yet synched to disk. + We write a couple of log entries that describes action to perform. + This entries are set-up in a linked list, however only when a first + execute entry is put as the first entry these will be executed. + This routine writes this first. + + @param first_entry First entry in linked list of entries to execute, if 0 = NULL it means that the entry is removed and the entries are put into the free list. - complete Flag indicating we are simply writing + @param complete Flag indicating we are simply writing info about that entry has been completed - in:out:active_entry Entry to execute, 0 = NULL if the entry + @param[in,out] active_entry Entry to execute, 0 = NULL if the entry is written first time and needs to be returned. In this case the entry written is returned in this parameter - RETURN VALUES - TRUE Error - FALSE Success - DESCRIPTION - This is the last write in the ddl log. The previous log entries have - already been written but not yet synched to disk. - We write a couple of log entries that describes action to perform. - This entries are set-up in a linked list, however only when a first - execute entry is put as the first entry these will be executed. - This routine writes this first + @return Operation status + @retval TRUE Error + @retval FALSE Success */ bool write_execute_ddl_log_entry(uint first_entry, @@ -1212,14 +1449,14 @@ bool write_execute_ddl_log_entry(uint first_entry, DDL_LOG_MEMORY_ENTRY **active_entry) { bool write_header= FALSE; - char file_entry_buf[IO_SIZE]; + char *file_entry_buf= (char*)global_ddl_log.file_entry_buf; DBUG_ENTER("write_execute_ddl_log_entry"); + mysql_mutex_assert_owner(&LOCK_gdl); if (init_ddl_log()) { DBUG_RETURN(TRUE); } - memset(file_entry_buf, 0, sizeof(file_entry_buf)); if (!complete) { /* @@ -1228,28 +1465,32 @@ bool write_execute_ddl_log_entry(uint first_entry, any log entries before, we are only here to write the execute entry to indicate it is done. */ - (void) sync_ddl_log(); + (void) sync_ddl_log_no_lock(); file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (char)DDL_LOG_EXECUTE_CODE; } else file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (char)DDL_IGNORE_LOG_ENTRY_CODE; + file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= 0; /* Ignored for execute entries */ + file_entry_buf[DDL_LOG_PHASE_POS]= 0; int4store(&file_entry_buf[DDL_LOG_NEXT_ENTRY_POS], first_entry); + file_entry_buf[DDL_LOG_NAME_POS]= 0; + file_entry_buf[DDL_LOG_NAME_POS + FN_REFLEN]= 0; + file_entry_buf[DDL_LOG_NAME_POS + 2*FN_REFLEN]= 0; if (!(*active_entry)) { if (get_free_ddl_log_entry(active_entry, &write_header)) { DBUG_RETURN(TRUE); } + write_header= TRUE; } - if (write_ddl_log_file_entry((uchar*) file_entry_buf, - (*active_entry)->entry_pos, - IO_SIZE)) + if (write_ddl_log_file_entry((*active_entry)->entry_pos)) { sql_print_error("Error writing execute entry in ddl log"); release_ddl_log_memory_entry(*active_entry); DBUG_RETURN(TRUE); } - (void) sync_ddl_log(); + (void) sync_ddl_log_no_lock(); if (write_header) { if (write_ddl_log_header()) @@ -1262,112 +1503,54 @@ bool write_execute_ddl_log_entry(uint first_entry, } -/* - For complex rename operations we need to deactivate individual entries. - SYNOPSIS - deactivate_ddl_log_entry() - entry_no Entry position of record to change - RETURN VALUES - TRUE Error - FALSE Success - DESCRIPTION - During replace operations where we start with an existing table called - t1 and a replacement table called t1#temp or something else and where - we want to delete t1 and rename t1#temp to t1 this is not possible to - do in a safe manner unless the ddl log is informed of the phases in - the change. - - Delete actions are 1-phase actions that can be ignored immediately after - being executed. - Rename actions from x to y is also a 1-phase action since there is no - interaction with any other handlers named x and y. - Replace action where drop y and x -> y happens needs to be a two-phase - action. Thus the first phase will drop y and the second phase will - rename x -> y. +/** + Deactivate an individual entry. + + @details see deactivate_ddl_log_entry_no_lock. + + @param entry_no Entry position of record to change + + @return Operation status + @retval TRUE Error + @retval FALSE Success */ bool deactivate_ddl_log_entry(uint entry_no) { - uchar file_entry_buf[DDL_LOG_NAME_POS]; + bool error; DBUG_ENTER("deactivate_ddl_log_entry"); - - /* - Only need to read and write the first bytes of the entry, where - ENTRY_TYPE, ACTION_TYPE and PHASE reside. Using DDL_LOG_NAME_POS - to include all info except for the names. - */ - if (!read_ddl_log_file_entry(file_entry_buf, entry_no, DDL_LOG_NAME_POS)) - { - if (file_entry_buf[DDL_LOG_ENTRY_TYPE_POS] == DDL_LOG_ENTRY_CODE) - { - if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_DELETE_ACTION || - file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_RENAME_ACTION || - (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION && - file_entry_buf[DDL_LOG_PHASE_POS] == 1)) - file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE; - else if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION) - { - DBUG_ASSERT(file_entry_buf[DDL_LOG_PHASE_POS] == 0); - file_entry_buf[DDL_LOG_PHASE_POS]= 1; - } - else - { - DBUG_ASSERT(0); - } - if (write_ddl_log_file_entry(file_entry_buf, entry_no, DDL_LOG_NAME_POS)) - { - sql_print_error("Error in deactivating log entry. Position = %u", - entry_no); - DBUG_RETURN(TRUE); - } - } - } - else - { - sql_print_error("Failed in reading entry before deactivating it"); - DBUG_RETURN(TRUE); - } - DBUG_RETURN(FALSE); + mysql_mutex_lock(&LOCK_gdl); + error= deactivate_ddl_log_entry_no_lock(entry_no); + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(error); } -/* - Sync ddl log file - SYNOPSIS - sync_ddl_log() - RETURN VALUES - TRUE Error - FALSE Success +/** + Sync ddl log file. + + @return Operation status + @retval TRUE Error + @retval FALSE Success */ bool sync_ddl_log() { - bool error= FALSE; + bool error; DBUG_ENTER("sync_ddl_log"); - if ((!global_ddl_log.recovery_phase) && - init_ddl_log()) - { - DBUG_RETURN(TRUE); - } - if (mysql_file_sync(global_ddl_log.file_id, MYF(0))) - { - /* Write to error log */ - sql_print_error("Failed to sync ddl log"); - error= TRUE; - } + mysql_mutex_lock(&LOCK_gdl); + error= sync_ddl_log_no_lock(); + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(error); } -/* - Release a log memory entry - SYNOPSIS - release_ddl_log_memory_entry() - log_memory_entry Log memory entry to release - RETURN VALUES - NONE +/** + Release a log memory entry. + @param log_memory_entry Log memory entry to release */ void release_ddl_log_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry) @@ -1376,8 +1559,8 @@ void release_ddl_log_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry) DDL_LOG_MEMORY_ENTRY *next_log_entry= log_entry->next_log_entry; DDL_LOG_MEMORY_ENTRY *prev_log_entry= log_entry->prev_log_entry; DBUG_ENTER("release_ddl_log_memory_entry"); - mysql_mutex_assert_owner(&LOCK_gdl); + mysql_mutex_assert_owner(&LOCK_gdl); global_ddl_log.first_free= log_entry; log_entry->next_log_entry= first_free; @@ -1391,58 +1574,32 @@ void release_ddl_log_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry) } -/* - Execute one entry in the ddl log. Executing an entry means executing - a linked list of actions. - SYNOPSIS - execute_ddl_log_entry() - first_entry Reference to first action in entry - RETURN VALUES - TRUE Error - FALSE Success +/** + Execute one entry in the ddl log. + + Executing an entry means executing a linked list of actions. + + @param first_entry Reference to first action in entry + + @return Operation status + @retval TRUE Error + @retval FALSE Success */ bool execute_ddl_log_entry(THD *thd, uint first_entry) { - DDL_LOG_ENTRY ddl_log_entry; - uint read_entry= first_entry; - uchar file_entry_buf[IO_SIZE]; + bool error; DBUG_ENTER("execute_ddl_log_entry"); mysql_mutex_lock(&LOCK_gdl); - do - { - if (read_ddl_log_file_entry(file_entry_buf, read_entry, IO_SIZE)) - { - /* Print the error to the log and continue with next log entry */ - sql_print_error("Failed to read entry = %u from ddl log", - read_entry); - break; - } - set_ddl_log_entry_from_buf(read_entry, file_entry_buf, &ddl_log_entry); - DBUG_ASSERT(ddl_log_entry.entry_type == DDL_LOG_ENTRY_CODE || - ddl_log_entry.entry_type == DDL_IGNORE_LOG_ENTRY_CODE); - - if (execute_ddl_log_action(thd, &ddl_log_entry)) - { - /* Print the error to the log and continue with next log entry */ - sql_print_error("Failed to execute action for entry = %u from ddl log", - read_entry); - break; - } - read_entry= ddl_log_entry.next_entry; - } while (read_entry); + error= execute_ddl_log_entry_no_lock(thd, first_entry); mysql_mutex_unlock(&LOCK_gdl); - DBUG_RETURN(FALSE); + DBUG_RETURN(error); } -/* - Close the ddl log - SYNOPSIS - close_ddl_log() - RETURN VALUES - NONE +/** + Close the ddl log. */ static void close_ddl_log() @@ -1457,12 +1614,8 @@ static void close_ddl_log() } -/* - Execute the ddl log at recovery of MySQL Server - SYNOPSIS - execute_ddl_log_recovery() - RETURN VALUES - NONE +/** + Execute the ddl log at recovery of MySQL Server. */ void execute_ddl_log_recovery() @@ -1470,14 +1623,14 @@ void execute_ddl_log_recovery() uint num_entries, i; THD *thd; DDL_LOG_ENTRY ddl_log_entry; - uchar *file_entry_buf; - uint io_size; char file_name[FN_REFLEN]; + static char recover_query_string[]= "INTERNAL DDL LOG RECOVER IN PROGRESS"; DBUG_ENTER("execute_ddl_log_recovery"); /* Initialise global_ddl_log struct */ + bzero(global_ddl_log.file_entry_buf, sizeof(global_ddl_log.file_entry_buf)); global_ddl_log.inited= FALSE; global_ddl_log.recovery_phase= TRUE; global_ddl_log.io_size= IO_SIZE; @@ -1491,27 +1644,22 @@ void execute_ddl_log_recovery() thd->thread_stack= (char*) &thd; thd->store_globals(); + thd->set_query(recover_query_string, strlen(recover_query_string)); + + /* this also initialize LOCK_gdl */ num_entries= read_ddl_log_header(); - io_size= global_ddl_log.io_size; - file_entry_buf= (uchar*) my_malloc(io_size, MYF(0)); - if (!file_entry_buf) - { - sql_print_error("Failed to allocate buffer for recover ddl log"); - DBUG_VOID_RETURN; - } + mysql_mutex_lock(&LOCK_gdl); for (i= 1; i < num_entries + 1; i++) { - if (read_ddl_log_file_entry(file_entry_buf, i, io_size)) + if (read_ddl_log_entry(i, &ddl_log_entry)) { sql_print_error("Failed to read entry no = %u from ddl log", i); continue; } - - set_ddl_log_entry_from_buf(i, file_entry_buf, &ddl_log_entry); if (ddl_log_entry.entry_type == DDL_LOG_EXECUTE_CODE) { - if (execute_ddl_log_entry(thd, ddl_log_entry.next_entry)) + if (execute_ddl_log_entry_no_lock(thd, ddl_log_entry.next_entry)) { /* Real unpleasant scenario but we continue anyways. */ continue; @@ -1522,20 +1670,15 @@ void execute_ddl_log_recovery() create_ddl_log_file_name(file_name); (void) mysql_file_delete(key_file_global_ddl_log, file_name, MYF(0)); global_ddl_log.recovery_phase= FALSE; + mysql_mutex_unlock(&LOCK_gdl); + thd->reset_query(); delete thd; - my_free(file_entry_buf); - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); DBUG_VOID_RETURN; } -/* - Release all memory allocated to the ddl log - SYNOPSIS - release_ddl_log() - RETURN VALUES - NONE +/** + Release all memory allocated to the ddl log. */ void release_ddl_log() @@ -1658,14 +1801,10 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) strxmov(shadow_frm_name, shadow_path, reg_ext, NullS); if (flags & WFRM_WRITE_SHADOW) { - if (mysql_prepare_create_table(lpt->thd, lpt->create_info, - lpt->alter_info, - /*tmp_table*/ 1, - &lpt->db_options, - lpt->table->file, - &lpt->key_info_buffer, - &lpt->key_count, - /*select_field_count*/ 0)) + if (mysql_prepare_create_table(lpt->thd, lpt->create_info, lpt->alter_info, + &lpt->db_options, lpt->table->file, + &lpt->key_info_buffer, &lpt->key_count, + C_ALTER_TABLE)) { DBUG_RETURN(TRUE); } @@ -1690,39 +1829,29 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) #endif /* Write shadow frm file */ lpt->create_info->table_options= lpt->db_options; - if ((mysql_create_frm(lpt->thd, shadow_frm_name, lpt->db, - lpt->table_name, lpt->create_info, - lpt->alter_info->create_list, lpt->key_count, - lpt->key_info_buffer, lpt->table->file)) || - lpt->table->file->ha_create_handler_files(shadow_path, NULL, - CHF_CREATE_FLAG, - lpt->create_info)) + LEX_CUSTRING frm= build_frm_image(lpt->thd, + lpt->table_name, + lpt->create_info, + lpt->alter_info->create_list, + lpt->key_count, lpt->key_info_buffer, + lpt->table->file); + if (!frm.str) { - mysql_file_delete(key_file_frm, shadow_frm_name, MYF(0)); error= 1; goto end; } - } - if (flags & WFRM_PACK_FRM) - { - /* - We need to pack the frm file and after packing it we delete the - frm file to ensure it doesn't get used. This is only used for - handlers that have the main version of the frm file stored in the - handler. - */ - uchar *data; - size_t length; - if (readfrm(shadow_path, &data, &length) || - packfrm(data, length, &lpt->pack_frm_data, &lpt->pack_frm_len)) - { - my_free(data); - my_free(lpt->pack_frm_data); - mem_alloc_error(length); + + int error= writefrm(shadow_path, lpt->db, lpt->table_name, + lpt->create_info->tmp_table(), frm.str, frm.length); + my_free(const_cast<uchar*>(frm.str)); + + if (error || lpt->table->file->ha_create_partitioning_metadata(shadow_path, + NULL, CHF_CREATE_FLAG)) + { + mysql_file_delete(key_file_frm, shadow_frm_name, MYF(0)); error= 1; goto end; } - error= mysql_file_delete(key_file_frm, shadow_frm_name, MYF(MY_WME)); } if (flags & WFRM_INSTALL_SHADOW) { @@ -1734,7 +1863,7 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) */ build_table_filename(path, sizeof(path) - 1, lpt->db, lpt->table_name, "", 0); - strxmov(frm_name, path, reg_ext, NullS); + strxnmov(frm_name, sizeof(frm_name), path, reg_ext, NullS); /* When we are changing to use new frm file we need to ensure that we don't collide with another thread in process to open the frm file. @@ -1747,14 +1876,14 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) */ if (mysql_file_delete(key_file_frm, frm_name, MYF(MY_WME)) || #ifdef WITH_PARTITION_STORAGE_ENGINE - lpt->table->file->ha_create_handler_files(path, shadow_path, - CHF_DELETE_FLAG, NULL) || + lpt->table->file->ha_create_partitioning_metadata(path, shadow_path, + CHF_DELETE_FLAG) || deactivate_ddl_log_entry(part_info->frm_log_entry->entry_pos) || (sync_ddl_log(), FALSE) || mysql_file_rename(key_file_frm, shadow_frm_name, frm_name, MYF(MY_WME)) || - lpt->table->file->ha_create_handler_files(path, shadow_path, - CHF_RENAME_FLAG, NULL)) + lpt->table->file->ha_create_partitioning_metadata(path, shadow_path, + CHF_RENAME_FLAG)) #else mysql_file_rename(key_file_frm, shadow_frm_name, frm_name, MYF(MY_WME))) @@ -1881,38 +2010,40 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, bool error; Drop_table_error_handler err_handler; TABLE_LIST *table; - DBUG_ENTER("mysql_rm_table"); /* Disable drop of enabled log tables, must be done before name locking */ for (table= tables; table; table= table->next_local) { - if (check_if_log_table(table->db_length, table->db, - table->table_name_length, table->table_name, true)) - { - my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP"); + if (check_if_log_table(table, TRUE, "DROP")) DBUG_RETURN(true); - } } - mysql_ha_rm_tables(thd, tables); - if (!drop_temporary) { + if (!in_bootstrap) + { + for (table= tables; table; table= table->next_local) + { + LEX_STRING db_name= { table->db, table->db_length }; + LEX_STRING table_name= { table->table_name, table->table_name_length }; + if (table->open_type == OT_BASE_ONLY || + !find_temporary_table(thd, table)) + (void) delete_statistics_for_table(thd, &db_name, &table_name); + } + } + if (!thd->locked_tables_mode) { - if (lock_table_names(thd, tables, NULL, thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY)) + if (lock_table_names(thd, tables, NULL, + thd->variables.lock_wait_timeout, 0)) DBUG_RETURN(true); - for (table= tables; table; table= table->next_local) - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name, - false); } else { for (table= tables; table; table= table->next_local) - if (table->open_type != OT_BASE_ONLY && - find_temporary_table(thd, table)) + { + if (is_temporary_table(table)) { /* A temporary table. @@ -1939,24 +2070,26 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, in its elements. */ table->table= find_table_for_mdl_upgrade(thd, table->db, - table->table_name, false); + table->table_name, NULL); if (!table->table) DBUG_RETURN(true); table->mdl_request.ticket= table->table->mdl_ticket; } + } } } /* mark for close and remove all cached entries */ thd->push_internal_handler(&err_handler); error= mysql_rm_table_no_locks(thd, tables, if_exists, drop_temporary, - false, false); + false, false, false); thd->pop_internal_handler(); if (error) DBUG_RETURN(TRUE); my_ok(thd); DBUG_RETURN(FALSE); + } @@ -2003,7 +2136,6 @@ static uint32 comment_length(THD *thd, uint32 comment_pos, return 0; } - /** Execute the drop of a normal or temporary table. @@ -2015,6 +2147,8 @@ static uint32 comment_length(THD *thd, uint32 comment_pos, @param drop_view Allow to delete VIEW .frm @param dont_log_query Don't write query to log files. This will also not generate warnings if the handler files doesn't exists + @param dont_free_locks Don't do automatic UNLOCK TABLE if no more locked + tables @retval 0 ok @retval 1 Error @@ -2023,6 +2157,9 @@ static uint32 comment_length(THD *thd, uint32 comment_pos, @note This function assumes that metadata locks have already been taken. It is also assumed that the tables have been removed from TDC. + @note This function assumes that temporary tables to be dropped have + been pre-opened using corresponding table list elements. + @todo When logging to the binary log, we should log tmp_tables and transactional tables as separate statements if we are in a transaction; This is needed to get these tables into the @@ -2034,23 +2171,26 @@ static uint32 comment_length(THD *thd, uint32 comment_pos, int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, bool drop_temporary, bool drop_view, - bool dont_log_query) + bool dont_log_query, + bool dont_free_locks) { TABLE_LIST *table; - char path[FN_REFLEN + 1], *alias= NULL; - uint path_length= 0; - String wrong_tables; + char path[FN_REFLEN + 1], wrong_tables_buff[160], *alias= NULL; + String wrong_tables(wrong_tables_buff, sizeof(wrong_tables_buff)-1, + system_charset_info); + uint path_length= 0, errors= 0; int error= 0; int non_temp_tables_count= 0; - bool foreign_key_error=0; bool non_tmp_error= 0; bool trans_tmp_table_deleted= 0, non_trans_tmp_table_deleted= 0; bool non_tmp_table_deleted= 0; bool is_drop_tmp_if_exists_added= 0; + bool was_view= 0; String built_query; String built_trans_tmp_query, built_non_trans_tmp_query; DBUG_ENTER("mysql_rm_table_no_locks"); + wrong_tables.length(0); /* Prepares the drop statements that will be written into the binary log as follows: @@ -2124,11 +2264,11 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, for (table= tables; table; table= table->next_local) { - bool is_trans; + bool is_trans= 0; + bool table_creation_was_logged= 1; char *db=table->db; size_t db_length= table->db_length; - handlerton *table_type; - enum legacy_db_type frm_db_type= DB_TYPE_UNKNOWN; + handlerton *table_type= 0; DBUG_PRINT("table", ("table_l: '%s'.'%s' table: 0x%lx s: 0x%lx", table->db, table->table_name, (long) table->table, @@ -2150,12 +2290,17 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, . 1 - a temporary table was not found. . -1 - a temporary table is used by an outer statement. */ - if (table->open_type == OT_BASE_ONLY) + if (table->open_type == OT_BASE_ONLY || !is_temporary_table(table)) error= 1; - else if ((error= drop_temporary_table(thd, table, &is_trans)) == -1) + else { - DBUG_ASSERT(thd->in_sub_stmt); - goto err; + table_creation_was_logged= table->table->s->table_creation_was_logged; + if ((error= drop_temporary_table(thd, table->table, &is_trans)) == -1) + { + DBUG_ASSERT(thd->in_sub_stmt); + goto err; + } + table->table= 0; } if ((drop_temporary && if_exists) || !error) @@ -2170,7 +2315,7 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, . "DROP" was executed but a temporary table was affected (.i.e !error). */ - if (!dont_log_query) + if (!dont_log_query && table_creation_was_logged) { /* If there is an error, we don't know the type of the engine @@ -2210,36 +2355,20 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, { non_temp_tables_count++; - if (thd->locked_tables_mode) - { - if (wait_while_table_is_used(thd, table->table, HA_EXTRA_NOT_USED, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)) - { - error= -1; - goto err; - } - close_all_tables_for_name(thd, table->table->s, - HA_EXTRA_PREPARE_FOR_DROP); - table->table= 0; - } - - /* Check that we have an exclusive lock on the table to be dropped. */ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db, table->table_name, - MDL_EXCLUSIVE)); + MDL_SHARED)); alias= (lower_case_table_names == 2) ? table->alias : table->table_name; /* remove .frm file and engine files */ path_length= build_table_filename(path, sizeof(path) - 1, db, alias, - reg_ext, - table->internal_tmp_table ? - FN_IS_TMP : 0); + reg_ext, 0); /* This handles the case where a "DROP" was executed and a regular table "may be" dropped as drop_temporary is FALSE and error is TRUE. If the error was FALSE a temporary table was dropped and - regardless of the status of drop_tempoary a "DROP TEMPORARY" + regardless of the status of drop_temporary a "DROP TEMPORARY" must be used. */ if (!dont_log_query) @@ -2266,26 +2395,30 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, } } DEBUG_SYNC(thd, "rm_table_no_locks_before_delete_table"); - DBUG_EXECUTE_IF("sleep_before_no_locks_delete_table", - my_sleep(100000);); error= 0; if (drop_temporary || - ((access(path, F_OK) && - ha_create_table_from_engine(thd, db, alias)) || - (!drop_view && - dd_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE))) + (ha_table_exists(thd, db, alias, &table_type) == 0 && table_type == 0) || + (!drop_view && (was_view= (table_type == view_pseudo_hton)))) { /* One of the following cases happened: . "DROP TEMPORARY" but a temporary table was not found. - . "DROP" but table was not found on disk and table can't be - created from engine. - . ./sql/datadict.cc +32 /Alfranio - TODO: We need to test this. + . "DROP" but table was not found + . "DROP TABLE" statement, but it's a view. */ if (if_exists) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), - table->table_name); + { + char buff[FN_REFLEN]; + String tbl_name(buff, sizeof(buff), system_charset_info); + tbl_name.length(0); + tbl_name.append(db); + tbl_name.append('.'); + tbl_name.append(table->table_name); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_BAD_TABLE_ERROR, + ER_THD(thd, ER_BAD_TABLE_ERROR), + tbl_name.c_ptr_safe()); + } else { non_tmp_error = (drop_temporary ? non_tmp_error : TRUE); @@ -2296,65 +2429,103 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, { char *end; /* - Cannot use the db_type from the table, since that might have changed - while waiting for the exclusive name lock. + It could happen that table's share in the table definition cache + is the only thing that keeps the engine plugin loaded + (if it is uninstalled and waits for the ref counter to drop to 0). + + In this case, the tdc_remove_table() below will release and unload + the plugin. And ha_delete_table() will get a dangling pointer. + + Let's lock the plugin till the end of the statement. */ - if (frm_db_type == DB_TYPE_UNKNOWN) + if (table_type && table_type != view_pseudo_hton) + ha_lock_engine(thd, table_type); + + if (thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) { - dd_frm_type(thd, path, &frm_db_type); - DBUG_PRINT("info", ("frm_db_type %d from %s", frm_db_type, path)); + if (wait_while_table_is_used(thd, table->table, HA_EXTRA_NOT_USED)) + { + error= -1; + goto err; + } + /* the following internally does TDC_RT_REMOVE_ALL */ + close_all_tables_for_name(thd, table->table->s, + HA_EXTRA_PREPARE_FOR_DROP, NULL); + table->table= 0; } - table_type= ha_resolve_by_legacy_type(thd, frm_db_type); + else + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name, + false); + + /* Check that we have an exclusive lock on the table to be dropped. */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db, + table->table_name, + MDL_EXCLUSIVE)); + // Remove extension for delete *(end= path + path_length - reg_ext_length)= '\0'; - DBUG_PRINT("info", ("deleting table of type %d", - (table_type ? table_type->db_type : 0))); + error= ha_delete_table(thd, table_type, path, db, table->table_name, !dont_log_query); - /* No error if non existent table and 'IF EXIST' clause or view */ - if ((error == ENOENT || error == HA_ERR_NO_SUCH_TABLE) && - (if_exists || table_type == NULL)) - { - error= 0; - thd->clear_error(); - } - if (error == HA_ERR_ROW_IS_REFERENCED) - { - /* the table is referenced by a foreign key constraint */ - foreign_key_error= 1; - } - if (!error || error == ENOENT || error == HA_ERR_NO_SUCH_TABLE) + if (!error) { - int new_error; + int frm_delete_error, trigger_drop_error= 0; /* Delete the table definition file */ strmov(end,reg_ext); - if (!(new_error= mysql_file_delete(key_file_frm, path, MYF(MY_WME)))) + if (table_type && table_type != view_pseudo_hton && + table_type->discover_table) + { + /* + Table type is using discovery and may not need a .frm file. + Delete it silently if it exists + */ + (void) mysql_file_delete(key_file_frm, path, MYF(0)); + frm_delete_error= 0; + } + else + frm_delete_error= mysql_file_delete(key_file_frm, path, + MYF(MY_WME)); + if (frm_delete_error) + frm_delete_error= my_errno; + else { non_tmp_table_deleted= TRUE; - new_error= Table_triggers_list::drop_all_triggers(thd, db, - table->table_name); + trigger_drop_error= + Table_triggers_list::drop_all_triggers(thd, db, table->table_name); } - error|= new_error; + + if (trigger_drop_error || + (frm_delete_error && frm_delete_error != ENOENT)) + error= 1; + else if (frm_delete_error && if_exists) + thd->clear_error(); } - non_tmp_error= error ? TRUE : non_tmp_error; + non_tmp_error|= MY_TEST(error); } if (error) { if (wrong_tables.length()) wrong_tables.append(','); - wrong_tables.append(String(table->table_name,system_charset_info)); + wrong_tables.append(db); + wrong_tables.append('.'); + wrong_tables.append(table->table_name); + errors++; } else { + PSI_CALL_drop_table_share(false, table->db, table->db_length, + table->table_name, table->table_name_length); mysql_audit_drop_table(thd, table); } + DBUG_PRINT("table", ("table: 0x%lx s: 0x%lx", (long) table->table, table->table ? (long) table->table->s : (long) -1)); DBUG_EXECUTE_IF("bug43138", my_printf_error(ER_BAD_TABLE_ERROR, - ER(ER_BAD_TABLE_ERROR), MYF(0), + ER_THD(thd, ER_BAD_TABLE_ERROR), MYF(0), table->table_name);); } DEBUG_SYNC(thd, "rm_table_no_locks_before_binlog"); @@ -2364,17 +2535,35 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, err: if (wrong_tables.length()) { - if (!foreign_key_error) - my_printf_error(ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), MYF(0), + DBUG_ASSERT(errors); + if (errors == 1 && was_view) + my_printf_error(ER_IT_IS_A_VIEW, ER_THD(thd, ER_IT_IS_A_VIEW), MYF(0), + wrong_tables.c_ptr_safe()); + else if (errors > 1 || !thd->is_error()) + my_printf_error(ER_BAD_TABLE_ERROR, ER_THD(thd, ER_BAD_TABLE_ERROR), + MYF(0), wrong_tables.c_ptr_safe()); - else - my_message(ER_ROW_IS_REFERENCED, ER(ER_ROW_IS_REFERENCED), MYF(0)); error= 1; } + /* + We are always logging drop of temporary tables. + The reason is to handle the following case: + - Use statement based replication + - CREATE TEMPORARY TABLE foo (logged) + - set row based replication + - DROP TEMPORAY TABLE foo (needs to be logged) + This should be fixed so that we remember if creation of the + temporary table was logged and only log it if the creation was + logged. + */ + if (non_trans_tmp_table_deleted || trans_tmp_table_deleted || non_tmp_table_deleted) { + if (non_trans_tmp_table_deleted || trans_tmp_table_deleted) + thd->transaction.stmt.mark_dropped_temp_table(); + query_cache_invalidate3(thd, tables, 0); if (!dont_log_query && mysql_bin_log.is_open()) { @@ -2383,6 +2572,9 @@ err: /* Chop of the last comma */ built_non_trans_tmp_query.chop(); built_non_trans_tmp_query.append(" /* generated by server */"); +#ifdef WITH_WSREP + thd->wsrep_skip_wsrep_GTID = true; +#endif /* WITH_WSREP */ error |= thd->binlog_query(THD::STMT_QUERY_TYPE, built_non_trans_tmp_query.ptr(), built_non_trans_tmp_query.length(), @@ -2395,6 +2587,9 @@ err: /* Chop of the last comma */ built_trans_tmp_query.chop(); built_trans_tmp_query.append(" /* generated by server */"); +#ifdef WITH_WSREP + thd->wsrep_skip_wsrep_GTID = true; +#endif /* WITH_WSREP */ error |= thd->binlog_query(THD::STMT_QUERY_TYPE, built_trans_tmp_query.ptr(), built_trans_tmp_query.length(), @@ -2407,8 +2602,11 @@ err: /* Chop of the last comma */ built_query.chop(); built_query.append(" /* generated by server */"); - int error_code = (non_tmp_error ? - (foreign_key_error ? ER_ROW_IS_REFERENCED : ER_BAD_TABLE_ERROR) : 0); + int error_code = non_tmp_error ? thd->get_stmt_da()->sql_errno() + : 0; +#ifdef WITH_WSREP + thd->wsrep_skip_wsrep_GTID = false; +#endif /* WITH_WSREP */ error |= thd->binlog_query(THD::STMT_QUERY_TYPE, built_query.ptr(), built_query.length(), @@ -2430,7 +2628,8 @@ err: */ if (thd->locked_tables_mode) { - if (thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0) + if (thd->lock && thd->lock->table_count == 0 && + non_temp_tables_count > 0 && !dont_free_locks) { thd->locked_tables_list.unlock_locked_tables(thd); goto end; @@ -2456,42 +2655,100 @@ err: } end: +#ifdef WITH_WSREP + thd->wsrep_skip_wsrep_GTID = false; +#endif /* WITH_WSREP */ DBUG_RETURN(error); } +/** + Log the drop of a table. -/* + @param thd Thread handler + @param db_name Database name + @param table_name Table name + @param temporary_table 1 if table was a temporary table + + This code is only used in the case of failed CREATE OR REPLACE TABLE + when the original table was dropped but we could not create the new one. +*/ + +bool log_drop_table(THD *thd, const char *db_name, size_t db_name_length, + const char *table_name, size_t table_name_length, + bool temporary_table) +{ + char buff[NAME_LEN*2 + 80]; + String query(buff, sizeof(buff), system_charset_info); + bool error; + DBUG_ENTER("log_drop_table"); + + if (!mysql_bin_log.is_open()) + DBUG_RETURN(0); + + query.length(0); + query.append(STRING_WITH_LEN("DROP ")); + if (temporary_table) + query.append(STRING_WITH_LEN("TEMPORARY ")); + query.append(STRING_WITH_LEN("TABLE IF EXISTS ")); + append_identifier(thd, &query, db_name, db_name_length); + query.append("."); + append_identifier(thd, &query, table_name, table_name_length); + query.append(STRING_WITH_LEN("/* Generated to handle " + "failed CREATE OR REPLACE */")); + error= thd->binlog_query(THD::STMT_QUERY_TYPE, + query.ptr(), query.length(), + FALSE, FALSE, temporary_table, 0); + DBUG_RETURN(error); +} + + +/** Quickly remove a table. - SYNOPSIS - quick_rm_table() - base The handlerton handle. - db The database name. - table_name The table name. - flags flags for build_table_filename(). + @param thd Thread context. + @param base The handlerton handle. + @param db The database name. + @param table_name The table name. + @param flags Flags for build_table_filename() as well as describing + if handler files / .FRM should be deleted as well. - RETURN - 0 OK - != 0 Error + @return False in case of success, True otherwise. */ -bool quick_rm_table(handlerton *base,const char *db, - const char *table_name, uint flags) +bool quick_rm_table(THD *thd, handlerton *base, const char *db, + const char *table_name, uint flags, const char *table_path) { char path[FN_REFLEN + 1]; bool error= 0; DBUG_ENTER("quick_rm_table"); - uint path_length= build_table_filename(path, sizeof(path) - 1, - db, table_name, reg_ext, flags); + uint path_length= table_path ? + (strxnmov(path, sizeof(path) - 1, table_path, reg_ext, NullS) - path) : + build_table_filename(path, sizeof(path)-1, db, table_name, reg_ext, flags); if (mysql_file_delete(key_file_frm, path, MYF(0))) error= 1; /* purecov: inspected */ path[path_length - reg_ext_length]= '\0'; // Remove reg_ext - if (!(flags & FRM_ONLY)) + if (flags & NO_HA_TABLE) + { + handler *file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base); + if (!file) + DBUG_RETURN(true); + (void) file->ha_create_partitioning_metadata(path, NULL, CHF_DELETE_FLAG); + delete file; + } + if (!(flags & (FRM_ONLY|NO_HA_TABLE))) error|= ha_delete_table(current_thd, base, path, db, table_name, 0); + + if (likely(error == 0)) + { + PSI_CALL_drop_table_share(flags & FN_IS_TMP, db, strlen(db), + table_name, strlen(table_name)); + } + DBUG_RETURN(error); } + /* Sort keys in the following order: - PRIMARY KEY @@ -2579,16 +2836,15 @@ bool check_duplicates_in_interval(const char *set_or_name, { THD *thd= current_thd; ErrConvString err(*cur_value, *cur_length, cs); - if ((current_thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))) + if (current_thd->is_strict_mode()) { my_error(ER_DUPLICATED_VALUE_IN_TYPE, MYF(0), name, err.ptr(), set_or_name); return 1; } - push_warning_printf(thd,MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd,Sql_condition::WARN_LEVEL_NOTE, ER_DUPLICATED_VALUE_IN_TYPE, - ER(ER_DUPLICATED_VALUE_IN_TYPE), + ER_THD(thd, ER_DUPLICATED_VALUE_IN_TYPE), name, err.ptr(), set_or_name); (*dup_val_count)++; } @@ -2638,7 +2894,6 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, prepare_create_field() sql_field field to prepare for packing blob_columns count for BLOBs - timestamps count for timestamps table_flags table flags DESCRIPTION @@ -2652,11 +2907,10 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, int prepare_create_field(Create_field *sql_field, uint *blob_columns, - int *timestamps, int *timestamps_with_niladic, longlong table_flags) { unsigned int dup_val_count; - DBUG_ENTER("prepare_field"); + DBUG_ENTER("prepare_create_field"); /* This code came from mysql_prepare_create_table. @@ -2756,6 +3010,8 @@ int prepare_create_field(Create_field *sql_field, case MYSQL_TYPE_NEWDATE: case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_DATETIME2: case MYSQL_TYPE_NULL: sql_field->pack_flag=f_settype((uint) sql_field->sql_type); break; @@ -2774,21 +3030,7 @@ int prepare_create_field(Create_field *sql_field, (sql_field->decimals << FIELDFLAG_DEC_SHIFT)); break; case MYSQL_TYPE_TIMESTAMP: - /* We should replace old TIMESTAMP fields with their newer analogs */ - if (sql_field->unireg_check == Field::TIMESTAMP_OLD_FIELD) - { - if (!*timestamps) - { - sql_field->unireg_check= Field::TIMESTAMP_DNUN_FIELD; - (*timestamps_with_niladic)++; - } - else - sql_field->unireg_check= Field::NONE; - } - else if (sql_field->unireg_check != Field::NONE) - (*timestamps_with_niladic)++; - - (*timestamps)++; + case MYSQL_TYPE_TIMESTAMP2: /* fall-through */ default: sql_field->pack_flag=(FIELDFLAG_NUMBER | @@ -2841,18 +3083,122 @@ CHARSET_INFO* get_sql_field_charset(Create_field *sql_field, } -bool check_duplicate_warning(THD *thd, char *msg, ulong length) +/** + Modifies the first column definition whose SQL type is TIMESTAMP + by adding the features DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP. + + If the first TIMESTAMP column appears to be nullable, or to have an + explicit default, or to be a virtual column, then no promition is done. + + @param column_definitions The list of column definitions, in the physical + order in which they appear in the table. + */ +void promote_first_timestamp_column(List<Create_field> *column_definitions) { - List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); - MYSQL_ERROR *err; - while ((err= it++)) + List_iterator<Create_field> it(*column_definitions); + Create_field *column_definition; + + while ((column_definition= it++) != NULL) { - if (strncmp(msg, err->get_message_text(), length) == 0) + if (is_timestamp_type(column_definition->sql_type) || // TIMESTAMP + column_definition->unireg_check == Field::TIMESTAMP_OLD_FIELD) // Legacy { - return true; + if ((column_definition->flags & NOT_NULL_FLAG) != 0 && // NOT NULL, + column_definition->def == NULL && // no constant default, + column_definition->unireg_check == Field::NONE && // no function default + column_definition->vcol_info == NULL) + { + DBUG_PRINT("info", ("First TIMESTAMP column '%s' was promoted to " + "DEFAULT CURRENT_TIMESTAMP ON UPDATE " + "CURRENT_TIMESTAMP", + column_definition->field_name + )); + column_definition->unireg_check= Field::TIMESTAMP_DNUN_FIELD; + } + return; + } + } +} + + +/** + Check if there is a duplicate key. Report a warning for every duplicate key. + + @param thd Thread context. + @param key Key to be checked. + @param key_info Key meta-data info. + @param key_list List of existing keys. +*/ +static void check_duplicate_key(THD *thd, Key *key, KEY *key_info, + List<Key> *key_list) +{ + /* + We only check for duplicate indexes if it is requested and the + key is not auto-generated. + + Check is requested if the key was explicitly created or altered + by the user (unless it's a foreign key). + */ + if (!key->key_create_info.check_for_duplicate_indexes || key->generated) + return; + + List_iterator<Key> key_list_iterator(*key_list); + List_iterator<Key_part_spec> key_column_iterator(key->columns); + Key *k; + + while ((k= key_list_iterator++)) + { + // Looking for a similar key... + + if (k == key) + break; + + if (k->generated || + (key->type != k->type) || + (key->key_create_info.algorithm != k->key_create_info.algorithm) || + (key->columns.elements != k->columns.elements)) + { + // Keys are different. + continue; + } + + /* + Keys 'key' and 'k' might be identical. + Check that the keys have identical columns in the same order. + */ + + List_iterator<Key_part_spec> k_column_iterator(k->columns); + + bool all_columns_are_identical= true; + + key_column_iterator.rewind(); + + for (uint i= 0; i < key->columns.elements; ++i) + { + Key_part_spec *c1= key_column_iterator++; + Key_part_spec *c2= k_column_iterator++; + + DBUG_ASSERT(c1 && c2); + + if (my_strcasecmp(system_charset_info, + c1->field_name.str, c2->field_name.str) || + (c1->length != c2->length)) + { + all_columns_are_identical= false; + break; + } + } + + // Report a warning if we have two identical keys. + + if (all_columns_are_identical) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_DUP_INDEX, ER_THD(thd, ER_DUP_INDEX), + key_info->name); + break; } } - return false; } @@ -2864,12 +3210,12 @@ bool check_duplicate_warning(THD *thd, char *msg, ulong length) thd Thread object. create_info Create information (like MAX_ROWS). alter_info List of columns and indexes to create - tmp_table If a temporary table is to be created. db_options INOUT Table options (like HA_OPTION_PACK_RECORD). file The handler for the new table. key_info_buffer OUT An array of KEY structs for the indexes. key_count OUT The number of elements in the array. - select_field_count The number of fields coming from a select table. + create_table_mode C_ORDINARY_CREATE, C_ALTER_TABLE, + C_CREATE_SELECT, C_ASSISTED_DISCOVERY DESCRIPTION Prepares the table and key structures for table creation. @@ -2884,11 +3230,9 @@ bool check_duplicate_warning(THD *thd, char *msg, ulong length) static int mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, - Alter_info *alter_info, - bool tmp_table, - uint *db_options, + Alter_info *alter_info, uint *db_options, handler *file, KEY **key_info_buffer, - uint *key_count, int select_field_count) + uint *key_count, int create_table_mode) { const char *key_name; Create_field *sql_field,*dup_field; @@ -2896,12 +3240,13 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, ulong record_offset= 0; KEY *key_info; KEY_PART_INFO *key_part_info; - int timestamps= 0, timestamps_with_niladic= 0; int field_no,dup_no; int select_field_pos,auto_increment=0; List_iterator<Create_field> it(alter_info->create_list); List_iterator<Create_field> it2(alter_info->create_list); uint total_uneven_bit_length= 0; + int select_field_count= C_CREATE_SELECT(create_table_mode); + bool tmp_table= create_table_mode == C_ALTER_TABLE; DBUG_ENTER("mysql_prepare_create_table"); LEX_STRING* connect_string = &create_info->connect_string; @@ -2934,18 +3279,11 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, executing a prepared statement for the second time. */ sql_field->length= sql_field->char_length; - save_cs= sql_field->charset= get_sql_field_charset(sql_field, - create_info); + /* Set field charset. */ + save_cs= sql_field->charset= get_sql_field_charset(sql_field, create_info); if ((sql_field->flags & BINCMP_FLAG) && - !(sql_field->charset= get_charset_by_csname(sql_field->charset->csname, - MY_CS_BINSORT,MYF(0)))) - { - char tmp[65]; - strmake(strmake(tmp, save_cs->csname, sizeof(tmp)-4), - STRING_WITH_LEN("_bin")); - my_error(ER_UNKNOWN_COLLATION, MYF(0), tmp); + !(sql_field->charset= find_bin_collation(sql_field->charset))) DBUG_RETURN(TRUE); - } /* Convert the default value from client character @@ -2968,7 +3306,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, pointer in the parsed tree of a prepared statement or a stored procedure statement. */ - sql_field->def= sql_field->def->safe_charset_converter(save_cs); + sql_field->def= sql_field->def->safe_charset_converter(thd, save_cs); if (sql_field->def == NULL) { @@ -2978,6 +3316,10 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, } } + /* Virtual fields are always NULL */ + if (sql_field->vcol_info) + sql_field->flags&= ~NOT_NULL_FLAG; + if (sql_field->sql_type == MYSQL_TYPE_SET || sql_field->sql_type == MYSQL_TYPE_ENUM) { @@ -3219,7 +3561,6 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, DBUG_ASSERT(sql_field->charset != 0); if (prepare_create_field(sql_field, &blob_columns, - ×tamps, ×tamps_with_niladic, file->ha_table_flags())) DBUG_RETURN(TRUE); if (sql_field->sql_type == MYSQL_TYPE_VARCHAR) @@ -3227,8 +3568,8 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, sql_field->offset= record_offset; if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER) auto_increment++; - if (parse_option_list(thd, &sql_field->option_struct, - sql_field->option_list, + if (parse_option_list(thd, create_info->db_type, &sql_field->option_struct, + &sql_field->option_list, create_info->db_type->field_options, FALSE, thd->mem_root)) DBUG_RETURN(TRUE); @@ -3250,29 +3591,21 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, record_offset+= sql_field->pack_length; } } - if (timestamps_with_niladic > 1) - { - my_message(ER_TOO_MUCH_AUTO_TIMESTAMP_COLS, - ER(ER_TOO_MUCH_AUTO_TIMESTAMP_COLS), MYF(0)); - DBUG_RETURN(TRUE); - } if (auto_increment > 1) { - my_message(ER_WRONG_AUTO_KEY, ER(ER_WRONG_AUTO_KEY), MYF(0)); + my_message(ER_WRONG_AUTO_KEY, ER_THD(thd, ER_WRONG_AUTO_KEY), MYF(0)); DBUG_RETURN(TRUE); } if (auto_increment && (file->ha_table_flags() & HA_NO_AUTO_INCREMENT)) { - my_message(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, - ER(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT), MYF(0)); + my_error(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, MYF(0), file->table_type()); DBUG_RETURN(TRUE); } if (blob_columns && (file->ha_table_flags() & HA_NO_BLOBS)) { - my_message(ER_TABLE_CANT_HANDLE_BLOB, ER(ER_TABLE_CANT_HANDLE_BLOB), - MYF(0)); + my_error(ER_TABLE_CANT_HANDLE_BLOB, MYF(0), file->table_type()); DBUG_RETURN(TRUE); } @@ -3315,7 +3648,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, my_error(ER_WRONG_FK_DEF, MYF(0), (fk_key->name.str ? fk_key->name.str : "foreign key without name"), - ER(ER_KEY_REF_DO_NOT_MATCH_TABLE_REF)); + ER_THD(thd, ER_KEY_REF_DO_NOT_MATCH_TABLE_REF)); DBUG_RETURN(TRUE); } continue; @@ -3377,8 +3710,8 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, DBUG_RETURN(TRUE); } - (*key_info_buffer)= key_info= (KEY*) sql_calloc(sizeof(KEY) * (*key_count)); - key_part_info=(KEY_PART_INFO*) sql_calloc(sizeof(KEY_PART_INFO)*key_parts); + (*key_info_buffer)= key_info= (KEY*) thd->calloc(sizeof(KEY) * (*key_count)); + key_part_info=(KEY_PART_INFO*) thd->calloc(sizeof(KEY_PART_INFO)*key_parts); if (!*key_info_buffer || ! key_part_info) DBUG_RETURN(TRUE); // Out of memory @@ -3429,13 +3762,13 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, if (key->generated) key_info->flags|= HA_GENERATED_KEY; - key_info->key_parts=(uint8) key->columns.elements; + key_info->user_defined_key_parts=(uint8) key->columns.elements; key_info->key_part=key_part_info; key_info->usable_key_parts= key_number; key_info->algorithm= key->key_create_info.algorithm; key_info->option_list= key->option_list; - if (parse_option_list(thd, &key_info->option_struct, - key_info->option_list, + if (parse_option_list(thd, create_info->db_type, &key_info->option_struct, + &key_info->option_list, create_info->db_type->index_options, FALSE, thd->mem_root)) DBUG_RETURN(TRUE); @@ -3444,8 +3777,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, { if (!(file->ha_table_flags() & HA_CAN_FULLTEXT)) { - my_message(ER_TABLE_CANT_HANDLE_FT, ER(ER_TABLE_CANT_HANDLE_FT), - MYF(0)); + my_error(ER_TABLE_CANT_HANDLE_FT, MYF(0), file->table_type()); DBUG_RETURN(TRUE); } } @@ -3462,11 +3794,10 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, { if (!(file->ha_table_flags() & HA_CAN_RTREEKEYS)) { - my_message(ER_TABLE_CANT_HANDLE_SPKEYS, ER(ER_TABLE_CANT_HANDLE_SPKEYS), - MYF(0)); + my_error(ER_TABLE_CANT_HANDLE_SPKEYS, MYF(0), file->table_type()); DBUG_RETURN(TRUE); } - if (key_info->key_parts != 1) + if (key_info->user_defined_key_parts != 1) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "SPATIAL INDEX"); DBUG_RETURN(TRUE); @@ -3475,7 +3806,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, else if (key_info->algorithm == HA_KEY_ALG_RTREE) { #ifdef HAVE_RTREE_KEYS - if ((key_info->key_parts & 1) == 1) + if ((key_info->user_defined_key_parts & 1) == 1) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "RTREE INDEX"); DBUG_RETURN(TRUE); @@ -3526,7 +3857,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, column->field_name.str, dup_column->field_name.str)) { my_printf_error(ER_DUP_FIELDNAME, - ER(ER_DUP_FIELDNAME),MYF(0), + ER_THD(thd, ER_DUP_FIELDNAME),MYF(0), column->field_name.str); DBUG_RETURN(TRUE); } @@ -3552,7 +3883,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, with length (unlike blobs, where ft code takes data length from a data prefix, ignoring column->length). */ - column->length=test(f_is_blob(sql_field->pack_flag)); + column->length= MY_TEST(f_is_blob(sql_field->pack_flag)); } else { @@ -3577,7 +3908,8 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, { if (!(file->ha_table_flags() & HA_CAN_INDEX_BLOBS)) { - my_error(ER_BLOB_USED_AS_KEY, MYF(0), column->field_name.str); + my_error(ER_BLOB_USED_AS_KEY, MYF(0), column->field_name.str, + file->table_type()); DBUG_RETURN(TRUE); } if (f_is_geom(sql_field->pack_flag) && sql_field->geom_type == @@ -3633,7 +3965,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, if (key->type == Key::SPATIAL) { my_message(ER_SPATIAL_CANT_HAVE_NULL, - ER(ER_SPATIAL_CANT_HAVE_NULL), MYF(0)); + ER_THD(thd, ER_SPATIAL_CANT_HAVE_NULL), MYF(0)); DBUG_RETURN(TRUE); } } @@ -3654,19 +3986,20 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, { if (f_is_blob(sql_field->pack_flag)) { - key_part_length= min(column->length, + key_part_length= MY_MIN(column->length, blob_length_by_type(sql_field->sql_type) * sql_field->charset->mbmaxlen); if (key_part_length > max_key_length || key_part_length > file->max_key_part_length()) { - key_part_length= min(max_key_length, file->max_key_part_length()); + key_part_length= MY_MIN(max_key_length, file->max_key_part_length()); if (key->type == Key::MULTIPLE) { /* not a critical problem */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TOO_LONG_KEY, ER(ER_TOO_LONG_KEY), - key_part_length); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_TOO_LONG_KEY, + ER_THD(thd, ER_TOO_LONG_KEY), + key_part_length); /* Align key length to multibyte char boundary */ key_part_length-= key_part_length % sql_field->charset->mbmaxlen; } @@ -3692,7 +4025,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, // and is this a 'unique' key? (key_info->flags & HA_NOSAME)))) { - my_message(ER_WRONG_SUB_KEY, ER(ER_WRONG_SUB_KEY), MYF(0)); + my_message(ER_WRONG_SUB_KEY, ER_THD(thd, ER_WRONG_SUB_KEY), MYF(0)); DBUG_RETURN(TRUE); } else if (!(file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS)) @@ -3700,7 +4033,8 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, } else if (key_part_length == 0 && (sql_field->flags & NOT_NULL_FLAG)) { - my_error(ER_WRONG_KEY_COLUMN, MYF(0), column->field_name.str); + my_error(ER_WRONG_KEY_COLUMN, MYF(0), file->table_type(), + column->field_name.str); DBUG_RETURN(TRUE); } if (key_part_length > file->max_key_part_length() && @@ -3710,9 +4044,9 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, if (key->type == Key::MULTIPLE) { /* not a critical problem */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TOO_LONG_KEY, ER(ER_TOO_LONG_KEY), - key_part_length); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_TOO_LONG_KEY, ER_THD(thd, ER_TOO_LONG_KEY), + key_part_length); /* Align key length to multibyte char boundary */ key_part_length-= key_part_length % sql_field->charset->mbmaxlen; } @@ -3751,7 +4085,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, { if (primary_key) { - my_message(ER_MULTIPLE_PRI_KEY, ER(ER_MULTIPLE_PRI_KEY), + my_message(ER_MULTIPLE_PRI_KEY, ER_THD(thd, ER_MULTIPLE_PRI_KEY), MYF(0)); DBUG_RETURN(TRUE); } @@ -3774,7 +4108,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key_info->name); DBUG_RETURN(TRUE); } - if (!(key_info->flags & HA_NULL_PART_KEY)) + if (key->type == Key::UNIQUE && !(key_info->flags & HA_NULL_PART_KEY)) unique_key=1; key_info->key_length=(uint16) key_length; if (key_length > max_key_length && key->type != Key::FULLTEXT) @@ -3783,31 +4117,10 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, DBUG_RETURN(TRUE); } - uint tmp_len= system_charset_info->cset->charpos(system_charset_info, - key->key_create_info.comment.str, - key->key_create_info.comment.str + - key->key_create_info.comment.length, - INDEX_COMMENT_MAXLEN); - - if (tmp_len < key->key_create_info.comment.length) - { - if ((thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))) - { - my_error(ER_TOO_LONG_INDEX_COMMENT, MYF(0), - key_info->name, static_cast<ulong>(INDEX_COMMENT_MAXLEN)); - DBUG_RETURN(-1); - } - char warn_buff[MYSQL_ERRMSG_SIZE]; - my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_INDEX_COMMENT), - key_info->name, static_cast<ulong>(INDEX_COMMENT_MAXLEN)); - /* do not push duplicate warnings */ - if (!check_duplicate_warning(thd, warn_buff, strlen(warn_buff))) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TOO_LONG_INDEX_COMMENT, warn_buff); - - key->key_create_info.comment.length= tmp_len; - } + if (validate_comment_length(thd, &key->key_create_info.comment, + INDEX_COMMENT_MAXLEN, ER_TOO_LONG_INDEX_COMMENT, + key_info->name)) + DBUG_RETURN(TRUE); key_info->comment.length= key->key_create_info.comment.length; if (key_info->comment.length > 0) @@ -3816,17 +4129,22 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, key_info->comment.str= key->key_create_info.comment.str; } + // Check if a duplicate index is defined. + check_duplicate_key(thd, key, key_info, &alter_info->key_list); + key_info++; } + if (!unique_key && !primary_key && (file->ha_table_flags() & HA_REQUIRE_PRIMARY_KEY)) { - my_message(ER_REQUIRES_PRIMARY_KEY, ER(ER_REQUIRES_PRIMARY_KEY), MYF(0)); + my_message(ER_REQUIRES_PRIMARY_KEY, ER_THD(thd, ER_REQUIRES_PRIMARY_KEY), + MYF(0)); DBUG_RETURN(TRUE); } if (auto_increment > 0) { - my_message(ER_WRONG_AUTO_KEY, ER(ER_WRONG_AUTO_KEY), MYF(0)); + my_message(ER_WRONG_AUTO_KEY, ER_THD(thd, ER_WRONG_AUTO_KEY), MYF(0)); DBUG_RETURN(TRUE); } /* Sort keys in optimized order */ @@ -3840,9 +4158,25 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, { Field::utype type= (Field::utype) MTYP_TYPENR(sql_field->unireg_check); + /* + Set NO_DEFAULT_VALUE_FLAG if this field doesn't have a default value and + it is NOT NULL, not an AUTO_INCREMENT field, not a TIMESTAMP and not + updated trough a NOW() function. + */ + if (!sql_field->def && + !sql_field->has_default_function() && + (sql_field->flags & NOT_NULL_FLAG) && + (!is_timestamp_type(sql_field->sql_type) || + opt_explicit_defaults_for_timestamp)) + { + sql_field->flags|= NO_DEFAULT_VALUE_FLAG; + sql_field->pack_flag|= FIELDFLAG_NO_DEFAULT; + } + if (thd->variables.sql_mode & MODE_NO_ZERO_DATE && !sql_field->def && !sql_field->vcol_info && - sql_field->sql_type == MYSQL_TYPE_TIMESTAMP && + is_timestamp_type(sql_field->sql_type) && + !opt_explicit_defaults_for_timestamp && (sql_field->flags & NOT_NULL_FLAG) && (type == Field::NONE || type == Field::TIMESTAMP_UN_FIELD)) { @@ -3865,8 +4199,20 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, } } - if (parse_option_list(thd, &create_info->option_struct, - create_info->option_list, + /* Give warnings for not supported table options */ +#if defined(WITH_ARIA_STORAGE_ENGINE) + extern handlerton *maria_hton; + if (file->partition_ht() != maria_hton) +#endif + if (create_info->transactional) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_ILLEGAL_HA_CREATE_OPTION, + ER_THD(thd, ER_ILLEGAL_HA_CREATE_OPTION), + file->engine_name()->str, + "TRANSACTIONAL=1"); + + if (parse_option_list(thd, file->partition_ht(), &create_info->option_struct, + &create_info->option_list, file->partition_ht()->table_options, FALSE, thd->mem_root)) DBUG_RETURN(TRUE); @@ -3874,6 +4220,44 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, DBUG_RETURN(FALSE); } +/** + check comment length of table, column, index and partition + + If comment lenght is more than the standard length + truncate it and store the comment lenght upto the standard + comment length size + + @param thd Thread handle + @param[in,out] comment Comment + @param max_len Maximum allowed comment length + @param err_code Error message + @param name Name of commented object + + @return Operation status + @retval true Error found + @retval false On Success +*/ +bool validate_comment_length(THD *thd, LEX_STRING *comment, size_t max_len, + uint err_code, const char *name) +{ + DBUG_ENTER("validate_comment_length"); + uint tmp_len= my_charpos(system_charset_info, comment->str, + comment->str + comment->length, max_len); + if (tmp_len < comment->length) + { + if (thd->is_strict_mode()) + { + my_error(err_code, MYF(0), name, static_cast<ulong>(max_len)); + DBUG_RETURN(true); + } + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, err_code, + ER_THD(thd, err_code), name, + static_cast<ulong>(max_len)); + comment->length= tmp_len; + } + DBUG_RETURN(false); +} + /* Set table default charset, if not set @@ -3898,7 +4282,7 @@ static void set_table_default_charset(THD *thd, */ if (!create_info->default_table_charset) { - HA_CREATE_INFO db_info; + Schema_specification_st db_info; load_db_opt_by_name(thd, db, &db_info); @@ -3930,8 +4314,7 @@ static bool prepare_blob_field(THD *thd, Create_field *sql_field) /* Convert long VARCHAR columns to TEXT or BLOB */ char warn_buff[MYSQL_ERRMSG_SIZE]; - if (sql_field->def || (thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES))) + if (sql_field->def || thd->is_strict_mode()) { my_error(ER_TOO_BIG_FIELDLENGTH, MYF(0), sql_field->field_name, static_cast<ulong>(MAX_FIELD_VARCHARLENGTH / @@ -3940,10 +4323,12 @@ static bool prepare_blob_field(THD *thd, Create_field *sql_field) } sql_field->sql_type= MYSQL_TYPE_BLOB; sql_field->flags|= BLOB_FLAG; - my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_AUTO_CONVERT), sql_field->field_name, - (sql_field->charset == &my_charset_bin) ? "VARBINARY" : "VARCHAR", - (sql_field->charset == &my_charset_bin) ? "BLOB" : "TEXT"); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_AUTO_CONVERT, + my_snprintf(warn_buff, sizeof(warn_buff), ER_THD(thd, ER_AUTO_CONVERT), + sql_field->field_name, + (sql_field->charset == &my_charset_bin) ? "VARBINARY" : + "VARCHAR", + (sql_field->charset == &my_charset_bin) ? "BLOB" : "TEXT"); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_AUTO_CONVERT, warn_buff); } @@ -4014,193 +4399,33 @@ void sp_prepare_create_field(THD *thd, Create_field *sql_field) } -#ifdef WITH_PARTITION_STORAGE_ENGINE -/** - Auxiliary function which allows to check if freshly created .FRM - file for table can be opened. - - @retval FALSE - Success. - @retval TRUE - Failure. -*/ - -static bool check_if_created_table_can_be_opened(THD *thd, - const char *path, - const char *db, - const char *table_name, - HA_CREATE_INFO *create_info, - handler *file) -{ - TABLE table; - TABLE_SHARE share; - bool result; - - /* - It is impossible to open definition of partitioned table without .par file. - */ - if (file->ha_create_handler_files(path, NULL, CHF_CREATE_FLAG, create_info)) - return TRUE; - - init_tmp_table_share(thd, &share, db, 0, table_name, path); - - result= (open_table_def(thd, &share, 0) || - open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, - 0, &table, TRUE)); - /* - Assert that the change list is empty as no partition function currently - needs to modify item tree. May need call THD::rollback_item_tree_changes - later before calling closefrm if the change list is not empty. - */ - DBUG_ASSERT(thd->change_list.is_empty()); - if (! result) - (void) closefrm(&table, 0); - - free_table_share(&share); - (void) file->ha_create_handler_files(path, NULL, CHF_DELETE_FLAG, create_info); - return result; -} -#endif - - -/** - Check that there is no frm file for given table - - @param old_path path to the old frm file - @param path path to the frm file in new encoding - @param db database name - @param table_name table name - @param alias table name for error message (for new encoding) - @param issue_error should we issue error messages - - @retval FALSE there is no frm file - @retval TRUE there is frm file -*/ - -bool check_table_file_presence(char *old_path, - char *path, - const char *db, - const char *table_name, - const char *alias, - bool issue_error) -{ - if (!access(path,F_OK)) - { - if (issue_error) - my_error(ER_TABLE_EXISTS_ERROR,MYF(0),alias); - return TRUE; - } - { - /* - Check if file of the table in 5.0 file name encoding exists. - - Except case when it is the same table. - */ - char tbl50[FN_REFLEN]; -#ifdef _WIN32 - if (check_if_legal_tablename(table_name) != 0) - { - /* - Check for reserved device names for which access() returns 0 - (CON, AUX etc). - */ - return FALSE; - } -#endif - strxmov(tbl50, mysql_data_home, "/", db, "/", table_name, NullS); - fn_format(tbl50, tbl50, "", reg_ext, MY_UNPACK_FILENAME); - if (!access(tbl50, F_OK) && - (old_path == NULL || - strcmp(old_path, tbl50) != 0)) - { - if (issue_error) - { - strxmov(tbl50, MYSQL50_TABLE_NAME_PREFIX, table_name, NullS); - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), tbl50); - } - return TRUE; - } - } - return FALSE; -} - - -/* - Create a table - - SYNOPSIS - mysql_create_table_no_lock() - thd Thread object - db Database - table_name Table name - create_info Create information (like MAX_ROWS) - fields List of fields to create - keys List of keys to create - internal_tmp_table Set to 1 if this is an internal temporary table - (From ALTER TABLE) - select_field_count - is_trans identifies the type of engine where the table - was created: either trans or non-trans. - - DESCRIPTION - If one creates a temporary table, this is automatically opened - - Note that this function assumes that caller already have taken - exclusive metadata lock on table being created or used some other - way to ensure that concurrent operations won't intervene. - mysql_create_table() is a wrapper that can be used for this. - - no_log is needed for the case of CREATE ... SELECT, - as the logging will be done later in sql_insert.cc - select_field_count is also used for CREATE ... SELECT, - and must be zero for standard create of table. - - RETURN VALUES - FALSE OK - TRUE error -*/ - -bool mysql_create_table_no_lock(THD *thd, +handler *mysql_create_frm_image(THD *thd, const char *db, const char *table_name, HA_CREATE_INFO *create_info, - Alter_info *alter_info, - bool internal_tmp_table, - uint select_field_count, - bool *is_trans) + Alter_info *alter_info, int create_table_mode, + KEY **key_info, + uint *key_count, + LEX_CUSTRING *frm) { - char path[FN_REFLEN + 1]; - uint path_length; - const char *alias; - uint db_options, key_count; - KEY *key_info_buffer; - handler *file; - bool error= TRUE; - DBUG_ENTER("mysql_create_table_no_lock"); - DBUG_PRINT("enter", ("db: '%s' table: '%s' tmp: %d", - db, table_name, internal_tmp_table)); + uint db_options; + handler *file; + DBUG_ENTER("mysql_create_frm_image"); - - /* Check for duplicate fields and check type of table to create */ if (!alter_info->create_list.elements) { - my_message(ER_TABLE_MUST_HAVE_COLUMNS, ER(ER_TABLE_MUST_HAVE_COLUMNS), - MYF(0)); - DBUG_RETURN(TRUE); + my_error(ER_TABLE_MUST_HAVE_COLUMNS, MYF(0)); + DBUG_RETURN(NULL); } - if (check_engine(thd, db, table_name, create_info)) - DBUG_RETURN(TRUE); set_table_default_charset(thd, create_info, (char*) db); - db_options= create_info->table_options; - if (!create_info->frm_only && - create_info->row_type != ROW_TYPE_FIXED && - create_info->row_type != ROW_TYPE_DEFAULT) - db_options|= HA_OPTION_PACK_RECORD; - alias= table_case_name(create_info, table_name); + db_options= create_info->table_options_with_row_type(); + if (!(file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root, create_info->db_type))) { mem_alloc_error(sizeof(handler)); - DBUG_RETURN(TRUE); + DBUG_RETURN(NULL); } #ifdef WITH_PARTITION_STORAGE_ENGINE partition_info *part_info= thd->work_part_info; @@ -4217,7 +4442,7 @@ bool mysql_create_table_no_lock(THD *thd, if (!part_info) { mem_alloc_error(sizeof(partition_info)); - DBUG_RETURN(TRUE); + goto err; } file->set_auto_partitions(part_info); part_info->default_engine_type= create_info->db_type; @@ -4232,29 +4457,54 @@ bool mysql_create_table_no_lock(THD *thd, partitions also in the call to check_partition_info. We transport this information in the default_db_type variable, it is either DB_TYPE_DEFAULT or the engine set in the ALTER TABLE command. - - Check that we don't use foreign keys in the table since it won't - work even with InnoDB beneath it. */ - List_iterator<Key> key_iterator(alter_info->key_list); - Key *key; handlerton *part_engine_type= create_info->db_type; char *part_syntax_buf; uint syntax_len; handlerton *engine_type; - if (create_info->options & HA_LEX_CREATE_TMP_TABLE) - { - my_error(ER_PARTITION_NO_TEMPORARY, MYF(0)); - goto err; - } - while ((key= key_iterator++)) + List_iterator<partition_element> part_it(part_info->partitions); + partition_element *part_elem; + + while ((part_elem= part_it++)) { - if (key->type == Key::FOREIGN_KEY && - !part_info->is_auto_partitioned) + if (part_elem->part_comment) { - my_error(ER_FOREIGN_KEY_ON_PARTITIONED, MYF(0)); - goto err; + LEX_STRING comment= { + part_elem->part_comment, strlen(part_elem->part_comment) + }; + if (validate_comment_length(thd, &comment, + TABLE_PARTITION_COMMENT_MAXLEN, + ER_TOO_LONG_TABLE_PARTITION_COMMENT, + part_elem->partition_name)) + DBUG_RETURN(NULL); + part_elem->part_comment[comment.length]= '\0'; + } + if (part_elem->subpartitions.elements) + { + List_iterator<partition_element> sub_it(part_elem->subpartitions); + partition_element *subpart_elem; + while ((subpart_elem= sub_it++)) + { + if (subpart_elem->part_comment) + { + LEX_STRING comment= { + subpart_elem->part_comment, strlen(subpart_elem->part_comment) + }; + if (validate_comment_length(thd, &comment, + TABLE_PARTITION_COMMENT_MAXLEN, + ER_TOO_LONG_TABLE_PARTITION_COMMENT, + subpart_elem->partition_name)) + DBUG_RETURN(NULL); + subpart_elem->part_comment[comment.length]= '\0'; + } + } } + } + + if (create_info->tmp_table()) + { + my_error(ER_PARTITION_NO_TEMPORARY, MYF(0)); + goto err; } if ((part_engine_type == partition_hton) && part_info->default_engine_type) @@ -4276,8 +4526,7 @@ bool mysql_create_table_no_lock(THD *thd, { if (part_info->default_engine_type == NULL) { - part_info->default_engine_type= ha_checktype(thd, - DB_TYPE_DEFAULT, 0, 0); + part_info->default_engine_type= ha_default_handlerton(thd); } } } @@ -4315,9 +4564,8 @@ bool mysql_create_table_no_lock(THD *thd, delete file; create_info->db_type= partition_hton; if (!(file= get_ha_partition(part_info))) - { - DBUG_RETURN(TRUE); - } + DBUG_RETURN(NULL); + /* If we have default number of partitions or subpartitions we might require to set-up the part_info object such that it @@ -4359,193 +4607,295 @@ bool mysql_create_table_no_lock(THD *thd, engine_type))) { mem_alloc_error(sizeof(handler)); - DBUG_RETURN(TRUE); + DBUG_RETURN(NULL); + } + } + } + /* + Unless table's storage engine supports partitioning natively + don't allow foreign keys on partitioned tables (they won't + work work even with InnoDB beneath of partitioning engine). + If storage engine handles partitioning natively (like NDB) + foreign keys support is possible, so we let the engine decide. + */ + if (create_info->db_type == partition_hton) + { + List_iterator_fast<Key> key_iterator(alter_info->key_list); + Key *key; + while ((key= key_iterator++)) + { + if (key->type == Key::FOREIGN_KEY) + { + my_error(ER_FOREIGN_KEY_ON_PARTITIONED, MYF(0)); + goto err; } } } #endif - if (mysql_prepare_create_table(thd, create_info, alter_info, - internal_tmp_table, - &db_options, file, - &key_info_buffer, &key_count, - select_field_count)) + if (mysql_prepare_create_table(thd, create_info, alter_info, &db_options, + file, key_info, key_count, + create_table_mode)) goto err; + create_info->table_options=db_options; - /* Check if table exists */ - if (create_info->options & HA_LEX_CREATE_TMP_TABLE) - { - path_length= build_tmptable_filename(thd, path, sizeof(path)); - create_info->table_options|=HA_CREATE_DELAY_KEY_WRITE; - } - else - { - path_length= build_table_filename(path, sizeof(path) - 1, db, alias, reg_ext, - internal_tmp_table ? FN_IS_TMP : 0); - } + *frm= build_frm_image(thd, table_name, create_info, + alter_info->create_list, *key_count, + *key_info, file); + + if (frm->str) + DBUG_RETURN(file); + +err: + delete file; + DBUG_RETURN(NULL); +} - /* Check if table already exists */ - if ((create_info->options & HA_LEX_CREATE_TMP_TABLE) && - find_temporary_table(thd, db, table_name)) + +/** + Create a table + + @param thd Thread object + @param orig_db Database for error messages + @param orig_table_name Table name for error messages + (it's different from table_name for ALTER TABLE) + @param db Database + @param table_name Table name + @param path Path to table (i.e. to its .FRM file without + the extension). + @param create_info Create information (like MAX_ROWS) + @param alter_info Description of fields and keys for new table + @param create_table_mode C_ORDINARY_CREATE, C_ALTER_TABLE, C_ASSISTED_DISCOVERY + or any positive number (for C_CREATE_SELECT). + @param[out] is_trans Identifies the type of engine where the table + was created: either trans or non-trans. + @param[out] key_info Array of KEY objects describing keys in table + which was created. + @param[out] key_count Number of keys in table which was created. + + If one creates a temporary table, this is automatically opened + + Note that this function assumes that caller already have taken + exclusive metadata lock on table being created or used some other + way to ensure that concurrent operations won't intervene. + mysql_create_table() is a wrapper that can be used for this. + + @retval 0 OK + @retval 1 error + @retval -1 table existed but IF EXISTS was used +*/ + +static +int create_table_impl(THD *thd, + const char *orig_db, const char *orig_table_name, + const char *db, const char *table_name, + const char *path, + const DDL_options_st options, + HA_CREATE_INFO *create_info, + Alter_info *alter_info, + int create_table_mode, + bool *is_trans, + KEY **key_info, + uint *key_count, + LEX_CUSTRING *frm) +{ + const char *alias; + handler *file= 0; + int error= 1; + bool frm_only= create_table_mode == C_ALTER_TABLE_FRM_ONLY; + bool internal_tmp_table= create_table_mode == C_ALTER_TABLE || frm_only; + DBUG_ENTER("mysql_create_table_no_lock"); + DBUG_PRINT("enter", ("db: '%s' table: '%s' tmp: %d path: %s", + db, table_name, internal_tmp_table, path)); + + if (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE) { - if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) - goto warn; - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias); - goto err; + if (create_info->data_file_name) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, + ER_THD(thd, WARN_OPTION_IGNORED), + "DATA DIRECTORY"); + if (create_info->index_file_name) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, + ER_THD(thd, WARN_OPTION_IGNORED), + "INDEX DIRECTORY"); + create_info->data_file_name= create_info->index_file_name= 0; } + else + if (error_if_data_home_dir(create_info->data_file_name, "DATA DIRECTORY") || + error_if_data_home_dir(create_info->index_file_name, "INDEX DIRECTORY")|| + check_partition_dirs(thd->lex->part_info)) + goto err; - /* Give warnings for not supported table options */ -#if defined(WITH_ARIA_STORAGE_ENGINE) - extern handlerton *maria_hton; - if (file->partition_ht() != maria_hton) -#endif - if (create_info->transactional) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_ILLEGAL_HA_CREATE_OPTION, - ER(ER_ILLEGAL_HA_CREATE_OPTION), - file->engine_name()->str, - "TRANSACTIONAL=1"); + alias= table_case_name(create_info, table_name); - if (!internal_tmp_table && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + /* Check if table exists */ + if (create_info->tmp_table()) { - if (check_table_file_presence(NULL, path, db, table_name, table_name, - !(create_info->options & - HA_LEX_CREATE_IF_NOT_EXISTS))) - { - if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) - goto warn; + TABLE *tmp_table; + if (find_and_use_temporary_table(thd, db, table_name, &tmp_table)) goto err; - } - /* - We don't assert here, but check the result, because the table could be - in the table definition cache and in the same time the .frm could be - missing from the disk, in case of manual intervention which deletes - the .frm file. The user has to use FLUSH TABLES; to clear the cache. - Then she could create the table. This case is pretty obscure and - therefore we don't introduce a new error message only for it. - */ - mysql_mutex_lock(&LOCK_open); - if (get_cached_table_share(db, table_name)) + if (tmp_table) { - mysql_mutex_unlock(&LOCK_open); - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); - goto err; + bool table_creation_was_logged= tmp_table->s->table_creation_was_logged; + if (options.or_replace()) + { + bool tmp; + /* + We are using CREATE OR REPLACE on an existing temporary table + Remove the old table so that we can re-create it. + */ + if (drop_temporary_table(thd, tmp_table, &tmp)) + goto err; + } + else if (options.if_not_exists()) + goto warn; + else + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias); + goto err; + } + /* + We have to log this query, even if it failed later to ensure the + drop is done. + */ + if (table_creation_was_logged) + { + thd->variables.option_bits|= OPTION_KEEP_LOG; + thd->log_current_statement= 1; + create_info->table_was_deleted= 1; + } } - mysql_mutex_unlock(&LOCK_open); } - - /* - Check that table with given name does not already - exist in any storage engine. In such a case it should - be discovered and the error ER_TABLE_EXISTS_ERROR be returned - unless user specified CREATE TABLE IF EXISTS - An exclusive metadata lock ensures that no - one else is attempting to discover the table. Since - it's not on disk as a frm file, no one could be using it! - */ - if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + else { - bool create_if_not_exists = - create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS; - int retcode = ha_table_exists_in_engine(thd, db, table_name); - DBUG_PRINT("info", ("exists_in_engine: %u",retcode)); - switch (retcode) + if (!internal_tmp_table && ha_table_exists(thd, db, table_name)) { - case HA_ERR_NO_SUCH_TABLE: - /* Normal case, no table exists. we can go and create it */ - break; - case HA_ERR_TABLE_EXIST: - DBUG_PRINT("info", ("Table existed in handler")); + if (options.or_replace()) + { + LEX_STRING db_name= {(char *) db, strlen(db)}; + LEX_STRING tab_name= {(char *) table_name, strlen(table_name)}; + (void) delete_statistics_for_table(thd, &db_name, &tab_name); - if (create_if_not_exists) - goto warn; - my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); - goto err; - default: - DBUG_PRINT("info", ("error: %u from storage engine", retcode)); - my_error(retcode, MYF(0),table_name); - goto err; - } - } + TABLE_LIST table_list; + table_list.init_one_table(db, strlen(db), table_name, + strlen(table_name), table_name, + TL_WRITE_ALLOW_WRITE); + table_list.table= create_info->table; - thd_proc_info(thd, "creating table"); + if (check_if_log_table(&table_list, TRUE, "CREATE OR REPLACE")) + goto err; + + /* + Rollback the empty transaction started in mysql_create_table() + call to open_and_lock_tables() when we are using LOCK TABLES. + */ + (void) trans_rollback_stmt(thd); + /* Remove normal table without logging. Keep tables locked */ + if (mysql_rm_table_no_locks(thd, &table_list, 0, 0, 0, 1, 1)) + goto err; -#ifdef HAVE_READLINK - { - size_t dirlen; - char dirpath[FN_REFLEN]; + /* + We have to log this query, even if it failed later to ensure the + drop is done. + */ + thd->variables.option_bits|= OPTION_KEEP_LOG; + thd->log_current_statement= 1; + create_info->table_was_deleted= 1; + DBUG_EXECUTE_IF("send_kill_after_delete", thd->set_killed(KILL_QUERY); ); - /* - data_file_name and index_file_name include the table name without - extension. Mostly this does not refer to an existing file. When - comparing data_file_name or index_file_name against the data - directory, we try to resolve all symbolic links. On some systems, - we use realpath(3) for the resolution. This returns ENOENT if the - resolved path does not refer to an existing file. my_realpath() - does then copy the requested path verbatim, without symlink - resolution. Thereafter the comparison can fail even if the - requested path is within the data directory. E.g. if symlinks to - another file system are used. To make realpath(3) return the - resolved path, we strip the table name and compare the directory - path only. If the directory doesn't exist either, table creation - will fail anyway. - */ - if (create_info->data_file_name) - { - dirname_part(dirpath, create_info->data_file_name, &dirlen); - if (test_if_data_home_dir(dirpath)) - { - my_error(ER_WRONG_ARGUMENTS, MYF(0), "DATA DIRECTORY"); - goto err; + /* + Restart statement transactions for the case of CREATE ... SELECT. + */ + if (thd->lex->select_lex.item_list.elements && + restart_trans_for_tables(thd, thd->lex->query_tables)) + goto err; } - } - if (create_info->index_file_name) - { - dirname_part(dirpath, create_info->index_file_name, &dirlen); - if (test_if_data_home_dir(dirpath)) + else if (options.if_not_exists()) + goto warn; + else { - my_error(ER_WRONG_ARGUMENTS, MYF(0), "INDEX DIRECTORY"); + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); goto err; } } } -#ifdef WITH_PARTITION_STORAGE_ENGINE - if (check_partition_dirs(thd->lex->part_info)) - { + THD_STAGE_INFO(thd, stage_creating_table); + + if (check_engine(thd, orig_db, orig_table_name, create_info)) goto err; - } -#endif /* WITH_PARTITION_STORAGE_ENGINE */ - if (!my_use_symdir || (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)) -#endif /* HAVE_READLINK */ + if (create_table_mode == C_ASSISTED_DISCOVERY) { - if (create_info->data_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), - "DATA DIRECTORY"); - if (create_info->index_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), - "INDEX DIRECTORY"); - create_info->data_file_name= create_info->index_file_name= 0; - } - create_info->table_options=db_options; + /* check that it's used correctly */ + DBUG_ASSERT(alter_info->create_list.elements == 0); + DBUG_ASSERT(alter_info->key_list.elements == 0); - path[path_length - reg_ext_length]= '\0'; // Remove .frm extension - if (rea_create_table(thd, path, db, table_name, - create_info, alter_info->create_list, - key_count, key_info_buffer, file)) - goto err; + TABLE_SHARE share; + handlerton *hton= create_info->db_type; + int ha_err; + Field *no_fields= 0; - if (create_info->options & HA_LEX_CREATE_TMP_TABLE) + if (!hton->discover_table_structure) + { + my_error(ER_TABLE_MUST_HAVE_COLUMNS, MYF(0)); + goto err; + } + + init_tmp_table_share(thd, &share, db, 0, table_name, path); + + /* prepare everything for discovery */ + share.field= &no_fields; + share.db_plugin= ha_lock_engine(thd, hton); + share.option_list= create_info->option_list; + share.connect_string= create_info->connect_string; + + if (parse_engine_table_options(thd, hton, &share)) + goto err; + + ha_err= hton->discover_table_structure(hton, thd, &share, create_info); + + /* + if discovery failed, the plugin will be auto-unlocked, as it + was locked on the THD, see above. + if discovery succeeded, the plugin was replaced by a globally + locked plugin, that will be unlocked by free_table_share() + */ + if (ha_err) + share.db_plugin= 0; // will be auto-freed, locked above on the THD + + free_table_share(&share); + + if (ha_err) + { + my_error(ER_GET_ERRNO, MYF(0), ha_err, hton_name(hton)->str); + goto err; + } + } + else + { + file= mysql_create_frm_image(thd, orig_db, orig_table_name, create_info, + alter_info, create_table_mode, key_info, + key_count, frm); + if (!file) + goto err; + if (rea_create_table(thd, frm, path, db, table_name, create_info, + file, frm_only)) + goto err; + } + + create_info->table= 0; + if (!frm_only && create_info->tmp_table()) { /* Open a table (skipping table cache) and add it into THD::temporary_tables list. */ - TABLE *table= open_table_uncached(thd, path, db, table_name, TRUE); + TABLE *table= open_table_uncached(thd, create_info->db_type, frm, path, + db, table_name, true, true); if (!table) { @@ -4557,9 +4907,10 @@ bool mysql_create_table_no_lock(THD *thd, *is_trans= table->file->has_transactions(); thd->thread_specific_used= TRUE; + create_info->table= table; // Store pointer to table } #ifdef WITH_PARTITION_STORAGE_ENGINE - else if (part_info && create_info->frm_only) + else if (thd->work_part_info && frm_only) { /* For partitioned tables we can't find some problems with table @@ -4571,31 +4922,86 @@ bool mysql_create_table_no_lock(THD *thd, In cases when we create .FRM without SE part we have to open table explicitly. */ - if (check_if_created_table_can_be_opened(thd, path, db, table_name, - create_info, file)) + TABLE table; + TABLE_SHARE share; + + init_tmp_table_share(thd, &share, db, 0, table_name, path); + + bool result= (open_table_def(thd, &share, GTS_TABLE) || + open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, + 0, &table, true)); + if (!result) + (void) closefrm(&table, 0); + + free_table_share(&share); + + if (result) { char frm_name[FN_REFLEN]; - strxmov(frm_name, path, reg_ext, NullS); + strxnmov(frm_name, sizeof(frm_name), path, reg_ext, NullS); (void) mysql_file_delete(key_file_frm, frm_name, MYF(0)); + (void) file->ha_create_partitioning_metadata(path, NULL, CHF_DELETE_FLAG); goto err; } } #endif - error= FALSE; + error= 0; err: - thd_proc_info(thd, "After create"); + THD_STAGE_INFO(thd, stage_after_create); delete file; + DBUG_PRINT("exit", ("return: %d", error)); DBUG_RETURN(error); warn: - error= FALSE; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), + error= -1; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_TABLE_EXISTS_ERROR, + ER_THD(thd, ER_TABLE_EXISTS_ERROR), alias); goto err; } +/** + Simple wrapper around create_table_impl() to be used + in various version of CREATE TABLE statement. +*/ + +int mysql_create_table_no_lock(THD *thd, + const char *db, const char *table_name, + Table_specification_st *create_info, + Alter_info *alter_info, bool *is_trans, + int create_table_mode) +{ + KEY *not_used_1; + uint not_used_2; + int res; + char path[FN_REFLEN + 1]; + LEX_CUSTRING frm= {0,0}; + + if (create_info->tmp_table()) + build_tmptable_filename(thd, path, sizeof(path)); + else + { + int length; + const char *alias= table_case_name(create_info, table_name); + length= build_table_filename(path, sizeof(path) - 1, db, alias, + "", 0); + // Check if we hit FN_REFLEN bytes along with file extension. + if (length+reg_ext_length > FN_REFLEN) + { + my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(path)-1, path); + return true; + } + } + + res= create_table_impl(thd, db, table_name, db, table_name, path, + *create_info, create_info, + alter_info, create_table_mode, + is_trans, ¬_used_1, ¬_used_2, &frm); + my_free(const_cast<uchar*>(frm.str)); + return res; +} /** Implementation of SQLCOM_CREATE_TABLE. @@ -4608,43 +5014,114 @@ warn: */ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, - HA_CREATE_INFO *create_info, + Table_specification_st *create_info, Alter_info *alter_info) { - bool result; + const char *db= create_table->db; + const char *table_name= create_table->table_name; bool is_trans= FALSE; + bool result; + int create_table_mode; + TABLE_LIST *pos_in_locked_tables= 0; + MDL_ticket *mdl_ticket= 0; DBUG_ENTER("mysql_create_table"); - /* - Open or obtain an exclusive metadata lock on table being created. - */ - if (open_and_lock_tables(thd, thd->lex->query_tables, FALSE, 0)) + DBUG_ASSERT(create_table == thd->lex->query_tables); + + /* Copy temporarily the statement flags to thd for lock_table_names() */ + uint save_thd_create_info_options= thd->lex->create_info.options; + thd->lex->create_info.options|= create_info->options; + + /* Open or obtain an exclusive metadata lock on table being created */ + result= open_and_lock_tables(thd, *create_info, create_table, FALSE, 0); + + thd->lex->create_info.options= save_thd_create_info_options; + + if (result) { /* is_error() may be 0 if table existed and we generated a warning */ - result= thd->is_error(); - goto end; + DBUG_RETURN(thd->is_error()); } - + /* The following is needed only in case of lock tables */ + if ((create_info->table= create_table->table)) + { + pos_in_locked_tables= create_info->table->pos_in_locked_tables; + mdl_ticket= create_table->table->mdl_ticket; + } + /* Got lock. */ DEBUG_SYNC(thd, "locked_table_name"); - result= mysql_create_table_no_lock(thd, create_table->db, - create_table->table_name, create_info, - alter_info, FALSE, 0, &is_trans); + if (alter_info->create_list.elements || alter_info->key_list.elements) + create_table_mode= C_ORDINARY_CREATE; + else + create_table_mode= C_ASSISTED_DISCOVERY; + + if (!opt_explicit_defaults_for_timestamp) + promote_first_timestamp_column(&alter_info->create_list); + + if (mysql_create_table_no_lock(thd, db, table_name, create_info, alter_info, + &is_trans, create_table_mode) > 0) + { + result= 1; + goto err; + } /* - Don't write statement if: - - Table creation has failed - - Row-based logging is used and we are creating a temporary table - Otherwise, the statement shall be binlogged. + Check if we are doing CREATE OR REPLACE TABLE under LOCK TABLES + on a non temporary table */ - if (!result && - (!thd->is_current_stmt_binlog_format_row() || - (thd->is_current_stmt_binlog_format_row() && - !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) - result= write_bin_log(thd, TRUE, thd->query(), thd->query_length(), is_trans); + if (thd->locked_tables_mode && pos_in_locked_tables && + create_info->or_replace()) + { + /* + Add back the deleted table and re-created table as a locked table + This should always work as we have a meta lock on the table. + */ + thd->locked_tables_list.add_back_last_deleted_lock(pos_in_locked_tables); + if (thd->locked_tables_list.reopen_tables(thd, false)) + { + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + result= 1; + } + else + { + TABLE *table= pos_in_locked_tables->table; + table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE); + } + } -end: +err: + /* In RBR we don't need to log CREATE TEMPORARY TABLE */ + if (thd->is_current_stmt_binlog_format_row() && create_info->tmp_table()) + DBUG_RETURN(result); + + if (create_info->tmp_table()) + thd->transaction.stmt.mark_created_temp_table(); + + /* Write log if no error or if we already deleted a table */ + if (!result || thd->log_current_statement) + { + if (result && create_info->table_was_deleted && pos_in_locked_tables) + { + /* + Possible locked table was dropped. We should remove meta data locks + associated with it and do UNLOCK_TABLES if no more locked tables. + */ + thd->locked_tables_list.unlock_locked_table(thd, mdl_ticket); + } + else if (!result && create_info->tmp_table() && create_info->table) + { + /* + Remember that tmp table creation was logged so that we know if + we should log a delete of it. + */ + create_info->table->s->table_creation_was_logged= 1; + } + if (write_bin_log(thd, result ? FALSE : TRUE, thd->query(), + thd->query_length(), is_trans)) + result= 1; + } DBUG_RETURN(result); } @@ -4693,27 +5170,24 @@ make_unique_key_name(const char *field_name,KEY *start,KEY *end) ****************************************************************************/ -/* +/** Rename a table. - SYNOPSIS - mysql_rename_table() - base The handlerton handle. - old_db The old database name. - old_name The old table name. - new_db The new database name. - new_name The new table name. - flags flags for build_table_filename(). - FN_FROM_IS_TMP old_name is temporary. - FN_TO_IS_TMP new_name is temporary. - NO_FRM_RENAME Don't rename the FRM file - but only the table in the storage engine. - NO_FK_CHECKS Don't check FK constraints - during rename. - - RETURN - FALSE OK - TRUE Error + @param base The handlerton handle. + @param old_db The old database name. + @param old_name The old table name. + @param new_db The new database name. + @param new_name The new table name. + @param flags flags + FN_FROM_IS_TMP old_name is temporary. + FN_TO_IS_TMP new_name is temporary. + NO_FRM_RENAME Don't rename the FRM file + but only the table in the storage engine. + NO_HA_TABLE Don't rename table in engine. + NO_FK_CHECKS Don't check FK constraints during rename. + + @return false OK + @return true Error */ bool @@ -4725,25 +5199,32 @@ mysql_rename_table(handlerton *base, const char *old_db, char from[FN_REFLEN + 1], to[FN_REFLEN + 1], lc_from[FN_REFLEN + 1], lc_to[FN_REFLEN + 1]; char *from_base= from, *to_base= to; - char tmp_name[SAFE_NAME_LEN+1]; + char tmp_name[SAFE_NAME_LEN+1], tmp_db_name[SAFE_NAME_LEN+1]; handler *file; int error=0; ulonglong save_bits= thd->variables.option_bits; + int length; DBUG_ENTER("mysql_rename_table"); + DBUG_ASSERT(base); DBUG_PRINT("enter", ("old: '%s'.'%s' new: '%s'.'%s'", old_db, old_name, new_db, new_name)); - + // Temporarily disable foreign key checks if (flags & NO_FK_CHECKS) thd->variables.option_bits|= OPTION_NO_FOREIGN_KEY_CHECKS; - file= (base == NULL ? 0 : - get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base)); + file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base); build_table_filename(from, sizeof(from) - 1, old_db, old_name, "", flags & FN_FROM_IS_TMP); - build_table_filename(to, sizeof(to) - 1, new_db, new_name, "", - flags & FN_TO_IS_TMP); + length= build_table_filename(to, sizeof(to) - 1, new_db, new_name, "", + flags & FN_TO_IS_TMP); + // Check if we hit FN_REFLEN bytes along with file extension. + if (length+reg_ext_length > FN_REFLEN) + { + my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(to)-1, to); + DBUG_RETURN(TRUE); + } /* If lower_case_table_names == 2 (case-preserving but case-insensitive @@ -4755,35 +5236,64 @@ mysql_rename_table(handlerton *base, const char *old_db, { strmov(tmp_name, old_name); my_casedn_str(files_charset_info, tmp_name); - build_table_filename(lc_from, sizeof(lc_from) - 1, old_db, tmp_name, "", - flags & FN_FROM_IS_TMP); + strmov(tmp_db_name, old_db); + my_casedn_str(files_charset_info, tmp_db_name); + + build_table_filename(lc_from, sizeof(lc_from) - 1, tmp_db_name, tmp_name, + "", flags & FN_FROM_IS_TMP); from_base= lc_from; strmov(tmp_name, new_name); my_casedn_str(files_charset_info, tmp_name); - build_table_filename(lc_to, sizeof(lc_to) - 1, new_db, tmp_name, "", + strmov(tmp_db_name, new_db); + my_casedn_str(files_charset_info, tmp_db_name); + + build_table_filename(lc_to, sizeof(lc_to) - 1, tmp_db_name, tmp_name, "", flags & FN_TO_IS_TMP); to_base= lc_to; } - if (!file || !(error=file->ha_rename_table(from_base, to_base))) + if (flags & NO_HA_TABLE) + { + if (rename_file_ext(from,to,reg_ext)) + error= my_errno; + (void) file->ha_create_partitioning_metadata(to, from, CHF_RENAME_FLAG); + } + else if (!file || !(error=file->ha_rename_table(from_base, to_base))) { if (!(flags & NO_FRM_RENAME) && rename_file_ext(from,to,reg_ext)) { error=my_errno; - /* Restore old file name */ if (file) - file->ha_rename_table(to_base, from_base); + { + if (error == ENOENT) + error= 0; // this is ok if file->ha_rename_table() succeeded + else + file->ha_rename_table(to_base, from_base); // Restore old file name + } } } delete file; if (error == HA_ERR_WRONG_COMMAND) my_error(ER_NOT_SUPPORTED_YET, MYF(0), "ALTER TABLE"); + else if (error == ENOTDIR) + my_error(ER_BAD_DB_ERROR, MYF(0), new_db); else if (error) my_error(ER_ERROR_ON_RENAME, MYF(0), from, to, error); else if (!(flags & FN_IS_TMP)) mysql_audit_rename_table(thd, old_db, old_name, new_db, new_name); - + + /* + Remove the old table share from the pfs table share array. The new table + share will be created when the renamed table is first accessed. + */ + if (likely(error == 0)) + { + PSI_CALL_drop_table_share(flags & FN_FROM_IS_TMP, + old_db, strlen(old_db), + old_name, strlen(old_name)); + } + // Restore options bits to the original value thd->variables.option_bits= save_bits; @@ -4806,16 +5316,26 @@ mysql_rename_table(handlerton *base, const char *old_db, TRUE error */ -bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, - HA_CREATE_INFO *create_info) +bool mysql_create_like_table(THD* thd, TABLE_LIST* table, + TABLE_LIST* src_table, + Table_specification_st *create_info) { - HA_CREATE_INFO local_create_info; + Table_specification_st local_create_info; + TABLE_LIST *pos_in_locked_tables= 0; Alter_info local_alter_info; + Alter_table_ctx local_alter_ctx; // Not used bool res= TRUE; bool is_trans= FALSE; + bool do_logging= FALSE; uint not_used; + int create_res; DBUG_ENTER("mysql_create_like_table"); +#ifdef WITH_WSREP + if (WSREP_ON && !thd->wsrep_applier && + wsrep_create_like_table(thd, table, src_table, create_info)) + DBUG_RETURN(res); +#endif /* We the open source table to get its description in HA_CREATE_INFO @@ -4828,26 +5348,50 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, Thus by holding both these locks we ensure that our statement is properly isolated from all concurrent operations which matter. */ - if (open_tables(thd, &thd->lex->query_tables, ¬_used, 0)) + + /* Copy temporarily the statement flags to thd for lock_table_names() */ + // QQ: is this really needed??? + uint save_thd_create_info_options= thd->lex->create_info.options; + thd->lex->create_info.options|= create_info->options; + res= open_tables(thd, &thd->lex->query_tables, ¬_used, 0); + thd->lex->create_info.options= save_thd_create_info_options; + + if (res) { + /* is_error() may be 0 if table existed and we generated a warning */ res= thd->is_error(); goto err; } + /* Ensure we don't try to create something from which we select from */ + if (create_info->or_replace() && !create_info->tmp_table()) + { + TABLE_LIST *duplicate; + if ((duplicate= unique_table(thd, table, src_table, 0))) + { + update_non_unique_table_error(src_table, "CREATE", duplicate); + goto err; + } + } + src_table->table->use_all_columns(); DEBUG_SYNC(thd, "create_table_like_after_open"); - /* Fill HA_CREATE_INFO and Alter_info with description of source table. */ - bzero((char*) &local_create_info, sizeof(local_create_info)); + /* + Fill Table_specification_st and Alter_info with the source table description. + Set OR REPLACE and IF NOT EXISTS option as in the CREATE TABLE LIKE + statement. + */ + local_create_info.init(create_info->create_like_options()); local_create_info.db_type= src_table->table->s->db_type(); local_create_info.row_type= src_table->table->s->row_type; if (mysql_prepare_alter_table(thd, src_table->table, &local_create_info, - &local_alter_info)) + &local_alter_info, &local_alter_ctx)) goto err; #ifdef WITH_PARTITION_STORAGE_ENGINE /* Partition info is not handled by mysql_prepare_alter_table() call. */ if (src_table->table->part_info) - thd->work_part_info= src_table->table->part_info->get_clone(); + thd->work_part_info= src_table->table->part_info->get_clone(thd); #endif /* @@ -4859,11 +5403,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, */ if (src_table->schema_table) local_create_info.max_rows= 0; - /* Set IF NOT EXISTS option as in the CREATE TABLE LIKE statement. */ - local_create_info.options|= create_info->options&HA_LEX_CREATE_IF_NOT_EXISTS; /* Replace type of source table with one specified in the statement. */ local_create_info.options&= ~HA_LEX_CREATE_TMP_TABLE; - local_create_info.options|= create_info->options & HA_LEX_CREATE_TMP_TABLE; + local_create_info.options|= create_info->tmp_table(); /* Reset auto-increment counter for the new table. */ local_create_info.auto_increment_value= 0; /* @@ -4872,19 +5414,57 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, */ local_create_info.data_file_name= local_create_info.index_file_name= NULL; - if ((res= mysql_create_table_no_lock(thd, table->db, table->table_name, - &local_create_info, &local_alter_info, - FALSE, 0, &is_trans))) + /* The following is needed only in case of lock tables */ + if ((local_create_info.table= thd->lex->query_tables->table)) + pos_in_locked_tables= local_create_info.table->pos_in_locked_tables; + + res= ((create_res= + mysql_create_table_no_lock(thd, table->db, table->table_name, + &local_create_info, &local_alter_info, + &is_trans, C_ORDINARY_CREATE)) > 0); + /* Remember to log if we deleted something */ + do_logging= thd->log_current_statement; + if (res) goto err; /* - Ensure that we have an exclusive lock on target table if we are creating - non-temporary table. + Check if we are doing CREATE OR REPLACE TABLE under LOCK TABLES + on a non temporary table */ - DBUG_ASSERT((create_info->options & HA_LEX_CREATE_TMP_TABLE) || - thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db, - table->table_name, - MDL_EXCLUSIVE)); + if (thd->locked_tables_mode && pos_in_locked_tables && + create_info->or_replace()) + { + /* + Add back the deleted table and re-created table as a locked table + This should always work as we have a meta lock on the table. + */ + thd->locked_tables_list.add_back_last_deleted_lock(pos_in_locked_tables); + if (thd->locked_tables_list.reopen_tables(thd, false)) + { + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + res= 1; // We got an error + } + else + { + /* + Get pointer to the newly opened table. We need this to ensure we + don't reopen the table when doing statment logging below. + */ + table->table= pos_in_locked_tables->table; + table->table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE); + } + } + else + { + /* + Ensure that we have an exclusive lock on target table if we are creating + non-temporary table. + */ + DBUG_ASSERT((create_info->tmp_table()) || + thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db, + table->table_name, + MDL_EXCLUSIVE)); + } DEBUG_SYNC(thd, "create_table_like_before_binlog"); @@ -4906,37 +5486,65 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, Case Target Source Write to binary log ==== ========= ========= ============================== 1 normal normal Original statement - 2 normal temporary Generated statement + 2 normal temporary Generated statement if the table + was created. 3 temporary normal Nothing 4 temporary temporary Nothing ==== ========= ========= ============================== */ - if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + if (!(create_info->tmp_table())) { if (src_table->table->s->tmp_table) // Case 2 { char buf[2048]; String query(buf, sizeof(buf), system_charset_info); query.length(0); // Have to zero it since constructor doesn't - Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN); + Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN | + MYSQL_OPEN_IGNORE_KILLED); + bool new_table= FALSE; // Whether newly created table is open. - /* - The condition avoids a crash as described in BUG#48506. Other - binlogging problems related to CREATE TABLE IF NOT EXISTS LIKE - when the existing object is a view will be solved by BUG 47442. - */ - if (!table->view) + if (create_res != 0) { /* - Here we open the destination table, on which we already have - exclusive metadata lock. This is needed for store_create_info() - to work. The table will be closed by close_thread_table() at - the end of this branch. + Table or view with same name already existed and we where using + IF EXISTS. Continue without logging anything. */ - if (open_table(thd, table, thd->mem_root, &ot_ctx)) - goto err; + do_logging= 0; + goto err; + } + if (!table->table) + { + TABLE_LIST::enum_open_strategy save_open_strategy; + int open_res; + /* Force the newly created table to be opened */ + save_open_strategy= table->open_strategy; + table->open_strategy= TABLE_LIST::OPEN_NORMAL; /* + In order for show_create_table() to work we need to open + destination table if it is not already open (i.e. if it + has not existed before). We don't need acquire metadata + lock in order to do this as we already hold exclusive + lock on this table. The table will be closed by + close_thread_table() at the end of this branch. + */ + open_res= open_table(thd, table, &ot_ctx); + /* Restore */ + table->open_strategy= save_open_strategy; + if (open_res) + { + res= 1; + goto err; + } + new_table= TRUE; + } + /* + We have to re-test if the table was a view as the view may not + have been opened until just above. + */ + if (!table->view) + { + /* After opening a MERGE table add the children to the query list of tables, so that children tables info can be used on "CREATE TABLE" statement generation by the binary log. @@ -4952,73 +5560,120 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, create_info->used_fields|= HA_CREATE_USED_ENGINE; int result __attribute__((unused))= - store_create_info(thd, table, &query, - create_info, TRUE /* show_database */); + show_create_table(thd, table, &query, create_info, WITH_DB_NAME); - DBUG_ASSERT(result == 0); // store_create_info() always return 0 + DBUG_ASSERT(result == 0); // show_create_table() always return 0 + do_logging= FALSE; if (write_bin_log(thd, TRUE, query.ptr(), query.length())) + { + res= 1; + do_logging= 0; goto err; + } - DBUG_ASSERT(thd->open_tables == table->table); - /* - When opening the table, we ignored the locked tables - (MYSQL_OPEN_GET_NEW_TABLE). Now we can close the table without - risking to close some locked table. - */ - close_thread_table(thd, &thd->open_tables); + if (new_table) + { + DBUG_ASSERT(thd->open_tables == table->table); + /* + When opening the table, we ignored the locked tables + (MYSQL_OPEN_GET_NEW_TABLE). Now we can close the table + without risking to close some locked table. + */ + close_thread_table(thd, &thd->open_tables); + } } } else // Case 1 - if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - goto err; + do_logging= TRUE; } /* Case 3 and 4 does nothing under RBR */ } - else if (write_bin_log(thd, TRUE, thd->query(), thd->query_length(), is_trans)) - goto err; + else + { + DBUG_PRINT("info", + ("res: %d tmp_table: %d create_info->table: %p", + res, create_info->tmp_table(), local_create_info.table)); + if (create_info->tmp_table()) + { + thd->transaction.stmt.mark_created_temp_table(); + if (!res && local_create_info.table) + { + /* + Remember that tmp table creation was logged so that we know if + we should log a delete of it. + */ + local_create_info.table->s->table_creation_was_logged= 1; + } + } + do_logging= TRUE; + } err: + if (do_logging) + { + if (res && create_info->table_was_deleted) + { + /* + Table was not deleted. Original table was deleted. + We have to log it. + */ + log_drop_table(thd, table->db, table->db_length, + table->table_name, table->table_name_length, + create_info->tmp_table()); + } + else if (write_bin_log(thd, res ? FALSE : TRUE, thd->query(), + thd->query_length(), is_trans)) + res= 1; + } + DBUG_RETURN(res); } /* table_list should contain just one table */ -static int -mysql_discard_or_import_tablespace(THD *thd, - TABLE_LIST *table_list, - enum tablespace_op_type tablespace_op) +int mysql_discard_or_import_tablespace(THD *thd, + TABLE_LIST *table_list, + bool discard) { - TABLE *table; - my_bool discard; + Alter_table_prelocking_strategy alter_prelocking_strategy; int error; DBUG_ENTER("mysql_discard_or_import_tablespace"); + mysql_audit_alter_table(thd, table_list); + /* Note that DISCARD/IMPORT TABLESPACE always is the only operation in an ALTER TABLE */ - thd_proc_info(thd, "discard_or_import_tablespace"); - - discard= test(tablespace_op == DISCARD_TABLESPACE); + THD_STAGE_INFO(thd, stage_discard_or_import_tablespace); /* We set this flag so that ha_innobase::open and ::external_lock() do not complain when we lock the table */ thd->tablespace_op= TRUE; - table_list->mdl_request.set_type(MDL_SHARED_WRITE); - if (!(table=open_ltable(thd, table_list, TL_WRITE, 0))) + /* + Adjust values of table-level and metadata which was set in parser + for the case general ALTER TABLE. + */ + table_list->mdl_request.set_type(MDL_EXCLUSIVE); + table_list->lock_type= TL_WRITE; + /* Do not open views. */ + table_list->required_type= FRMTYPE_TABLE; + + if (open_and_lock_tables(thd, table_list, FALSE, 0, + &alter_prelocking_strategy)) { thd->tablespace_op=FALSE; DBUG_RETURN(-1); } - error= table->file->ha_discard_or_import_tablespace(discard); + error= table_list->table->file->ha_discard_or_import_tablespace(discard); - thd_proc_info(thd, "end"); + THD_STAGE_INFO(thd, stage_end); if (error) goto err; @@ -5046,279 +5701,777 @@ err: DBUG_RETURN(0); } - table->file->print_error(error, MYF(0)); + table_list->table->file->print_error(error, MYF(0)); DBUG_RETURN(-1); } + /** - @brief Check if both DROP and CREATE are present for an index in ALTER TABLE - - @details Checks if any index is being modified (present as both DROP INDEX - and ADD INDEX) in the current ALTER TABLE statement. Needed for disabling - in-place ALTER TABLE. - - @param table The table being altered - @param alter_info The ALTER TABLE structure - @return presence of index being altered - @retval FALSE No such index - @retval TRUE Have at least 1 index modified + Check if key is a candidate key, i.e. a unique index with no index + fields partial or nullable. */ -static bool -is_index_maintenance_unique (TABLE *table, Alter_info *alter_info) +static bool is_candidate_key(KEY *key) { - List_iterator<Key> key_it(alter_info->key_list); - List_iterator<Alter_drop> drop_it(alter_info->drop_list); - Key *key; + KEY_PART_INFO *key_part; + KEY_PART_INFO *key_part_end= key->key_part + key->user_defined_key_parts; + + if (!(key->flags & HA_NOSAME) || (key->flags & HA_NULL_PART_KEY) || + (key->flags & HA_KEY_HAS_PART_KEY_SEG)) + return false; + + for (key_part= key->key_part; key_part < key_part_end; key_part++) + { + if (key_part->key_part_flag & HA_PART_KEY_SEG) + return false; + } + return true; +} + + +/* + Preparation for table creation + + SYNOPSIS + handle_if_exists_option() + thd Thread object. + table The altered table. + alter_info List of columns and indexes to create + + DESCRIPTION + Looks for the IF [NOT] EXISTS options, checks the states and remove items + from the list if existing found. + + RETURN VALUES + NONE +*/ + +static void +handle_if_exists_options(THD *thd, TABLE *table, Alter_info *alter_info) +{ + Field **f_ptr; + DBUG_ENTER("handle_if_exists_option"); + + /* Handle ADD COLUMN IF NOT EXISTS. */ + { + List_iterator<Create_field> it(alter_info->create_list); + Create_field *sql_field; + + while ((sql_field=it++)) + { + if (!sql_field->create_if_not_exists || sql_field->change) + continue; + /* + If there is a field with the same name in the table already, + remove the sql_field from the list. + */ + for (f_ptr=table->field; *f_ptr; f_ptr++) + { + if (my_strcasecmp(system_charset_info, + sql_field->field_name, (*f_ptr)->field_name) == 0) + goto drop_create_field; + } + { + /* + If in the ADD list there is a field with the same name, + remove the sql_field from the list. + */ + List_iterator<Create_field> chk_it(alter_info->create_list); + Create_field *chk_field; + while ((chk_field= chk_it++) && chk_field != sql_field) + { + if (my_strcasecmp(system_charset_info, + sql_field->field_name, chk_field->field_name) == 0) + goto drop_create_field; + } + } + continue; +drop_create_field: + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_DUP_FIELDNAME, ER_THD(thd, ER_DUP_FIELDNAME), + sql_field->field_name); + it.remove(); + if (alter_info->create_list.is_empty()) + { + alter_info->flags&= ~Alter_info::ALTER_ADD_COLUMN; + if (alter_info->key_list.is_empty()) + alter_info->flags&= ~(Alter_info::ALTER_ADD_INDEX | + Alter_info::ADD_FOREIGN_KEY); + } + } + } + + /* Handle MODIFY COLUMN IF EXISTS. */ + { + List_iterator<Create_field> it(alter_info->create_list); + Create_field *sql_field; + + while ((sql_field=it++)) + { + if (!sql_field->create_if_not_exists || !sql_field->change) + continue; + /* + If there is NO field with the same name in the table already, + remove the sql_field from the list. + */ + for (f_ptr=table->field; *f_ptr; f_ptr++) + { + if (my_strcasecmp(system_charset_info, + sql_field->change, (*f_ptr)->field_name) == 0) + { + break; + } + } + if (*f_ptr == NULL) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_BAD_FIELD_ERROR, + ER_THD(thd, ER_BAD_FIELD_ERROR), + sql_field->change, table->s->table_name.str); + it.remove(); + if (alter_info->create_list.is_empty()) + { + alter_info->flags&= ~(Alter_info::ALTER_ADD_COLUMN | + Alter_info::ALTER_CHANGE_COLUMN); + if (alter_info->key_list.is_empty()) + alter_info->flags&= ~Alter_info::ALTER_ADD_INDEX; + } + } + } + } + + /* Handle DROP COLUMN/KEY IF EXISTS. */ + { + List_iterator<Alter_drop> drop_it(alter_info->drop_list); + Alter_drop *drop; + bool remove_drop; + ulonglong left_flags= 0; + while ((drop= drop_it++)) + { + ulonglong cur_flag= 0; + switch (drop->type) { + case Alter_drop::COLUMN: + cur_flag= Alter_info::ALTER_DROP_COLUMN; + break; + case Alter_drop::FOREIGN_KEY: + cur_flag= Alter_info::DROP_FOREIGN_KEY; + break; + case Alter_drop::KEY: + cur_flag= Alter_info::ALTER_DROP_INDEX; + break; + default: + break; + } + if (!drop->drop_if_exists) + { + left_flags|= cur_flag; + continue; + } + remove_drop= TRUE; + if (drop->type == Alter_drop::COLUMN) + { + /* + If there is NO field with that name in the table, + remove the 'drop' from the list. + */ + for (f_ptr=table->field; *f_ptr; f_ptr++) + { + if (my_strcasecmp(system_charset_info, + drop->name, (*f_ptr)->field_name) == 0) + { + remove_drop= FALSE; + break; + } + } + } + else /* Alter_drop::KEY */ + { + uint n_key; + if (drop->type != Alter_drop::FOREIGN_KEY) + { + for (n_key=0; n_key < table->s->keys; n_key++) + { + if (my_strcasecmp(system_charset_info, + drop->name, table->key_info[n_key].name) == 0) + { + remove_drop= FALSE; + break; + } + } + } + else + { + List <FOREIGN_KEY_INFO> fk_child_key_list; + FOREIGN_KEY_INFO *f_key; + table->file->get_foreign_key_list(thd, &fk_child_key_list); + List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list); + while ((f_key= fk_key_it++)) + { + if (my_strcasecmp(system_charset_info, f_key->foreign_id->str, + drop->name) == 0) + { + remove_drop= FALSE; + break; + } + } + } + } + + if (!remove_drop) + { + /* + Check if the name appears twice in the DROP list. + */ + List_iterator<Alter_drop> chk_it(alter_info->drop_list); + Alter_drop *chk_drop; + while ((chk_drop= chk_it++) && chk_drop != drop) + { + if (drop->type == chk_drop->type && + my_strcasecmp(system_charset_info, + drop->name, chk_drop->name) == 0) + { + remove_drop= TRUE; + break; + } + } + } + + if (remove_drop) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_CANT_DROP_FIELD_OR_KEY, + ER_THD(thd, ER_CANT_DROP_FIELD_OR_KEY), + drop->name); + drop_it.remove(); + } + else + left_flags|= cur_flag; + } + /* Reset state to what's left in drop list */ + alter_info->flags&= ~(Alter_info::ALTER_DROP_COLUMN | + Alter_info::ALTER_DROP_INDEX | + Alter_info::DROP_FOREIGN_KEY); + alter_info->flags|= left_flags; + } - while ((key= key_it++)) + /* ALTER TABLE ADD KEY IF NOT EXISTS */ + /* ALTER TABLE ADD FOREIGN KEY IF NOT EXISTS */ { - if (key->name.str) + Key *key; + List_iterator<Key> key_it(alter_info->key_list); + uint n_key; + const char *keyname= NULL; + while ((key=key_it++)) { - Alter_drop *drop; + if (!key->if_not_exists() && !key->or_replace()) + continue; - drop_it.rewind(); - while ((drop= drop_it++)) + /* Check if the table already has a PRIMARY KEY */ + bool dup_primary_key= + key->type == Key::PRIMARY && + table->s->primary_key != MAX_KEY && + (keyname= table->s->key_info[table->s->primary_key].name) && + my_strcasecmp(system_charset_info, keyname, primary_key_name) == 0; + if (dup_primary_key) + goto remove_key; + + /* If the name of the key is not specified, */ + /* let us check the name of the first key part. */ + if ((keyname= key->name.str) == NULL) { - if (drop->type == Alter_drop::KEY && - !my_strcasecmp(system_charset_info, key->name.str, drop->name)) - return TRUE; + if (key->type == Key::PRIMARY) + keyname= primary_key_name; + else + { + List_iterator<Key_part_spec> part_it(key->columns); + Key_part_spec *kp; + if ((kp= part_it++)) + keyname= kp->field_name.str; + if (keyname == NULL) + continue; + } + } + if (key->type != Key::FOREIGN_KEY) + { + for (n_key=0; n_key < table->s->keys; n_key++) + { + if (my_strcasecmp(system_charset_info, + keyname, table->key_info[n_key].name) == 0) + { + goto remove_key; + } + } + } + else + { + List <FOREIGN_KEY_INFO> fk_child_key_list; + FOREIGN_KEY_INFO *f_key; + table->file->get_foreign_key_list(thd, &fk_child_key_list); + List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list); + while ((f_key= fk_key_it++)) + { + if (my_strcasecmp(system_charset_info, f_key->foreign_id->str, + keyname) == 0) + goto remove_key; + } + } + + { + Key *chk_key; + List_iterator<Key> chk_it(alter_info->key_list); + const char *chkname; + while ((chk_key=chk_it++) && chk_key != key) + { + if ((chkname= chk_key->name.str) == NULL) + { + List_iterator<Key_part_spec> part_it(chk_key->columns); + Key_part_spec *kp; + if ((kp= part_it++)) + chkname= kp->field_name.str; + if (keyname == NULL) + continue; + } + if (key->type == chk_key->type && + my_strcasecmp(system_charset_info, keyname, chkname) == 0) + goto remove_key; + } + } + continue; + +remove_key: + if (key->if_not_exists()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_DUP_KEYNAME, ER_THD(thd, dup_primary_key + ? ER_MULTIPLE_PRI_KEY : ER_DUP_KEYNAME), keyname); + key_it.remove(); + if (key->type == Key::FOREIGN_KEY) + { + /* ADD FOREIGN KEY appends two items. */ + key_it.remove(); + } + if (alter_info->key_list.is_empty()) + alter_info->flags&= ~(Alter_info::ALTER_ADD_INDEX | + Alter_info::ADD_FOREIGN_KEY); + } + else + { + DBUG_ASSERT(key->or_replace()); + Alter_drop::drop_type type= (key->type == Key::FOREIGN_KEY) ? + Alter_drop::FOREIGN_KEY : Alter_drop::KEY; + Alter_drop *ad= new Alter_drop(type, key->name.str, FALSE); + if (ad != NULL) + { + // Adding the index into the drop list for replacing + alter_info->flags |= Alter_info::ALTER_DROP_INDEX; + alter_info->drop_list.push_back(ad, thd->mem_root); + } } } } - return FALSE; + +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *tab_part_info= table->part_info; + thd->work_part_info= thd->lex->part_info; + if (tab_part_info) + { + /* ALTER TABLE ADD PARTITION IF NOT EXISTS */ + if ((alter_info->flags & Alter_info::ALTER_ADD_PARTITION) && + thd->lex->create_info.if_not_exists()) + { + partition_info *alt_part_info= thd->lex->part_info; + if (alt_part_info) + { + List_iterator<partition_element> new_part_it(alt_part_info->partitions); + partition_element *pe; + while ((pe= new_part_it++)) + { + if (!tab_part_info->has_unique_name(pe)) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SAME_NAME_PARTITION, + ER_THD(thd, ER_SAME_NAME_PARTITION), + pe->partition_name); + alter_info->flags&= ~Alter_info::ALTER_ADD_PARTITION; + thd->work_part_info= NULL; + break; + } + } + } + } + /* ALTER TABLE DROP PARTITION IF EXISTS */ + if ((alter_info->flags & Alter_info::ALTER_DROP_PARTITION) && + thd->lex->if_exists()) + { + List_iterator<char> names_it(alter_info->partition_names); + char *name; + + while ((name= names_it++)) + { + List_iterator<partition_element> part_it(tab_part_info->partitions); + partition_element *part_elem; + while ((part_elem= part_it++)) + { + if (my_strcasecmp(system_charset_info, + part_elem->partition_name, name) == 0) + break; + } + if (!part_elem) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_DROP_PARTITION_NON_EXISTENT, + ER_THD(thd, ER_DROP_PARTITION_NON_EXISTENT), + "DROP"); + names_it.remove(); + } + } + if (alter_info->partition_names.elements == 0) + alter_info->flags&= ~Alter_info::ALTER_DROP_PARTITION; + } + } +#endif /*WITH_PARTITION_STORAGE_ENGINE*/ + + DBUG_VOID_RETURN; } -/* - SYNOPSIS - mysql_compare_tables() - table The original table. - alter_info Alter options, fields and keys for the new - table. - create_info Create options for the new table. - order_num Number of order list elements. - need_copy_table OUT Result of the comparison. Undefined if error. - Otherwise is one of: - ALTER_TABLE_METADATA_ONLY No copy needed - ALTER_TABLE_DATA_CHANGED Data changes, - copy needed - ALTER_TABLE_INDEX_CHANGED Index changes, - copy might be needed - key_info_buffer OUT An array of KEY structs for new indexes - index_drop_buffer OUT An array of offsets into table->key_info. - index_drop_count OUT The number of elements in the array. - index_add_buffer OUT An array of offsets into key_info_buffer. - index_add_count OUT The number of elements in the array. - candidate_key_count OUT The number of candidate keys in original table. +/** + Get Create_field object for newly created table by field index. - DESCRIPTION - 'table' (first argument) contains information of the original - table, which includes all corresponding parts that the new - table has in arguments create_list, key_list and create_info. + @param alter_info Alter_info describing newly created table. + @param idx Field index. +*/ - By comparing the changes between the original and new table - we can determine how much it has changed after ALTER TABLE - and whether we need to make a copy of the table, or just change - the .frm file. +static Create_field *get_field_by_index(Alter_info *alter_info, uint idx) +{ + List_iterator_fast<Create_field> field_it(alter_info->create_list); + uint field_idx= 0; + Create_field *field; - If there are no data changes, but index changes, 'index_drop_buffer' - and/or 'index_add_buffer' are populated with offsets into - table->key_info or key_info_buffer respectively for the indexes - that need to be dropped and/or (re-)created. + while ((field= field_it++) && field_idx < idx) + { field_idx++; } - RETURN VALUES - TRUE The tables are not compatible; We have to do a full alter table - FALSE The tables are compatible; We only have to modify the .frm + return field; +} + + +static int compare_uint(const uint *s, const uint *t) +{ + return (*s < *t) ? -1 : ((*s > *t) ? 1 : 0); +} + + +/** + Compare original and new versions of a table and fill Alter_inplace_info + describing differences between those versions. + + @param thd Thread + @param table The original table. + @param varchar Indicates that new definition has new + VARCHAR column. + @param[in/out] ha_alter_info Data structure which already contains + basic information about create options, + field and keys for the new version of + table and which should be completed with + more detailed information needed for + in-place ALTER. + + First argument 'table' contains information of the original + table, which includes all corresponding parts that the new + table has in arguments create_list, key_list and create_info. + + Compare the changes between the original and new table definitions. + The result of this comparison is then passed to SE which determines + whether it can carry out these changes in-place. + + Mark any changes detected in the ha_alter_flags. + We generally try to specify handler flags only if there are real + changes. But in cases when it is cumbersome to determine if some + attribute has really changed we might choose to set flag + pessimistically, for example, relying on parser output only. + + If there are no data changes, but index changes, 'index_drop_buffer' + and/or 'index_add_buffer' are populated with offsets into + table->key_info or key_info_buffer respectively for the indexes + that need to be dropped and/or (re-)created. + + Note that this function assumes that it is OK to change Alter_info + and HA_CREATE_INFO which it gets. It is caller who is responsible + for creating copies for this structures if he needs them unchanged. + + @retval true error + @retval false success */ -bool -mysql_compare_tables(TABLE *table, - Alter_info *alter_info, - HA_CREATE_INFO *create_info, - uint order_num, - enum_alter_table_change_level *need_copy_table, - KEY **key_info_buffer, - uint **index_drop_buffer, uint *index_drop_count, - uint **index_add_buffer, uint *index_add_count, - uint *candidate_key_count) +static bool fill_alter_inplace_info(THD *thd, TABLE *table, bool varchar, + Alter_inplace_info *ha_alter_info) { Field **f_ptr, *field; - uint changes= 0, tmp; - uint key_count; - List_iterator_fast<Create_field> new_field_it, tmp_new_field_it; - Create_field *new_field, *tmp_new_field; - KEY_PART_INFO *key_part; + List_iterator_fast<Create_field> new_field_it; + Create_field *new_field; + KEY_PART_INFO *key_part, *new_part; KEY_PART_INFO *end; - THD *thd= table->in_use; - uint i; + Alter_info *alter_info= ha_alter_info->alter_info; + DBUG_ENTER("fill_alter_inplace_info"); + + /* Allocate result buffers. */ + if (! (ha_alter_info->index_drop_buffer= + (KEY**) thd->alloc(sizeof(KEY*) * table->s->keys)) || + ! (ha_alter_info->index_add_buffer= + (uint*) thd->alloc(sizeof(uint) * + alter_info->key_list.elements))) + DBUG_RETURN(true); + + /* First we setup ha_alter_flags based on what was detected by parser. */ + if (alter_info->flags & Alter_info::ALTER_ADD_COLUMN) + ha_alter_info->handler_flags|= Alter_inplace_info::ADD_COLUMN; + if (alter_info->flags & Alter_info::ALTER_DROP_COLUMN) + ha_alter_info->handler_flags|= Alter_inplace_info::DROP_COLUMN; /* - Remember if the new definition has new VARCHAR column; - create_info->varchar will be reset in mysql_prepare_create_table. + Comparing new and old default values of column is cumbersome. + So instead of using such a comparison for detecting if default + has really changed we rely on flags set by parser to get an + approximate value for storage engine flag. */ - bool varchar= create_info->varchar; - bool not_nullable= true; - DBUG_ENTER("mysql_compare_tables"); + if (alter_info->flags & (Alter_info::ALTER_CHANGE_COLUMN | + Alter_info::ALTER_CHANGE_COLUMN_DEFAULT)) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_DEFAULT; + if (alter_info->flags & Alter_info::ADD_FOREIGN_KEY) + ha_alter_info->handler_flags|= Alter_inplace_info::ADD_FOREIGN_KEY; + if (alter_info->flags & Alter_info::DROP_FOREIGN_KEY) + ha_alter_info->handler_flags|= Alter_inplace_info::DROP_FOREIGN_KEY; + if (alter_info->flags & Alter_info::ALTER_OPTIONS) + ha_alter_info->handler_flags|= Alter_inplace_info::CHANGE_CREATE_OPTION; + if (alter_info->flags & Alter_info::ALTER_RENAME) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_RENAME; + /* Check partition changes */ + if (alter_info->flags & Alter_info::ALTER_ADD_PARTITION) + ha_alter_info->handler_flags|= Alter_inplace_info::ADD_PARTITION; + if (alter_info->flags & Alter_info::ALTER_DROP_PARTITION) + ha_alter_info->handler_flags|= Alter_inplace_info::DROP_PARTITION; + if (alter_info->flags & Alter_info::ALTER_PARTITION) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_PARTITION; + if (alter_info->flags & Alter_info::ALTER_COALESCE_PARTITION) + ha_alter_info->handler_flags|= Alter_inplace_info::COALESCE_PARTITION; + if (alter_info->flags & Alter_info::ALTER_REORGANIZE_PARTITION) + ha_alter_info->handler_flags|= Alter_inplace_info::REORGANIZE_PARTITION; + if (alter_info->flags & Alter_info::ALTER_TABLE_REORG) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_TABLE_REORG; + if (alter_info->flags & Alter_info::ALTER_REMOVE_PARTITIONING) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_REMOVE_PARTITIONING; + if (alter_info->flags & Alter_info::ALTER_ALL_PARTITION) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_ALL_PARTITION; + /* Check for: ALTER TABLE FORCE, ALTER TABLE ENGINE and OPTIMIZE TABLE. */ + if (alter_info->flags & Alter_info::ALTER_RECREATE) + ha_alter_info->handler_flags|= Alter_inplace_info::RECREATE_TABLE; /* - Create a copy of alter_info. - To compare the new and old table definitions, we need to "prepare" - the new definition - transform it from parser output to a format - that describes the final table layout (all column defaults are - initialized, duplicate columns are removed). This is done by - mysql_prepare_create_table. Unfortunately, - mysql_prepare_create_table performs its transformations - "in-place", that is, modifies the argument. Since we would - like to keep mysql_compare_tables() idempotent (not altering any - of the arguments) we create a copy of alter_info here and - pass it to mysql_prepare_create_table, then use the result - to evaluate possibility of in-place ALTER TABLE, and then - destroy the copy. + If we altering table with old VARCHAR fields we will be automatically + upgrading VARCHAR column types. */ - Alter_info tmp_alter_info(*alter_info, thd->mem_root); - uint db_options= 0; /* not used */ - - /* Set default value for return value (to ensure it's always set) */ - *need_copy_table= ALTER_TABLE_DATA_CHANGED; + if (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_TYPE; - /* Create the prepared information. */ - if (mysql_prepare_create_table(thd, create_info, - &tmp_alter_info, - (table->s->tmp_table != NO_TMP_TABLE), - &db_options, - table->file, key_info_buffer, - &key_count, 0)) - DBUG_RETURN(1); - /* Allocate result buffers. */ - if (! (*index_drop_buffer= - (uint*) thd->alloc(sizeof(uint) * table->s->keys)) || - ! (*index_add_buffer= - (uint*) thd->alloc(sizeof(uint) * tmp_alter_info.key_list.elements))) - DBUG_RETURN(1); - /* - Some very basic checks. If number of fields changes, or the - handler, we need to run full ALTER TABLE. In the future - new fields can be added and old dropped without copy, but - not yet. + Go through fields in old version of table and detect changes to them. + We don't want to rely solely on Alter_info flags for this since: + a) new definition of column can be fully identical to the old one + despite the fact that this column is mentioned in MODIFY clause. + b) even if new column type differs from its old column from metadata + point of view, it might be identical from storage engine point + of view (e.g. when ENUM('a','b') is changed to ENUM('a','b',c')). + c) flags passed to storage engine contain more detailed information + about nature of changes than those provided from parser. + */ + bool maybe_alter_vcol= false; + for (f_ptr= table->field; (field= *f_ptr); f_ptr++) + { + /* Clear marker for renamed or dropped field + which we are going to set later. */ + field->flags&= ~(FIELD_IS_RENAMED | FIELD_IS_DROPPED); - Test also that engine was not given during ALTER TABLE, or - we are force to run regular alter table (copy). - E.g. ALTER TABLE tbl_name ENGINE=MyISAM. + /* Use transformed info to evaluate flags for storage engine. */ + uint new_field_index= 0; + new_field_it.init(alter_info->create_list); + while ((new_field= new_field_it++)) + { + if (new_field->field == field) + break; + new_field_index++; + } - For the following ones we also want to run regular alter table: - ALTER TABLE tbl_name ORDER BY .. - ALTER TABLE tbl_name CONVERT TO CHARACTER SET .. + if (new_field) + { + /* Field is not dropped. Evaluate changes bitmap for it. */ - At the moment we can't handle altering temporary tables without a copy. - We also test if OPTIMIZE TABLE was given and was mapped to alter table. - In that case we always do full copy. + /* + Check if type of column has changed to some incompatible type. + */ + uint is_equal= field->is_equal(new_field); + switch (is_equal) + { + case IS_EQUAL_NO: + /* New column type is incompatible with old one. */ + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_TYPE; + if (table->s->tmp_table == NO_TMP_TABLE) + { + delete_statistics_for_column(thd, table, field); + KEY *key_info= table->key_info; + for (uint i=0; i < table->s->keys; i++, key_info++) + { + if (field->part_of_key.is_set(i)) + { + uint key_parts= table->actual_n_key_parts(key_info); + for (uint j= 0; j < key_parts; j++) + { + if (key_info->key_part[j].fieldnr-1 == field->field_index) + { + delete_statistics_for_index(thd, table, key_info, + j >= key_info->user_defined_key_parts); + break; + } + } + } + } + } + break; + case IS_EQUAL_YES: + /* + New column is the same as the old one or the fully compatible with + it (for example, ENUM('a','b') was changed to ENUM('a','b','c')). + Such a change if any can ALWAYS be carried out by simply updating + data-dictionary without even informing storage engine. + No flag is set in this case. + */ + break; + case IS_EQUAL_PACK_LENGTH: + /* + New column type differs from the old one, but has compatible packed + data representation. Depending on storage engine, such a change can + be carried out by simply updating data dictionary without changing + actual data (for example, VARCHAR(300) is changed to VARCHAR(400)). + */ + ha_alter_info->handler_flags|= Alter_inplace_info:: + ALTER_COLUMN_EQUAL_PACK_LENGTH; + break; + default: + DBUG_ASSERT(0); + /* Safety. */ + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_TYPE; + } - There was a bug prior to mysql-4.0.25. Number of null fields was - calculated incorrectly. As a result frm and data files gets out of - sync after in-place alter table. There is no way to determine by which - mysql version (in 4.0 and 4.1 branches) table was created, thus we - disable in-place alter table for all tables created by mysql versions - prior to 5.0 branch. - See BUG#6236. - */ - if (table->s->fields != alter_info->create_list.elements || - table->s->db_type() != create_info->db_type || - table->s->tmp_table || - create_info->used_fields & HA_CREATE_USED_ENGINE || - create_info->used_fields & HA_CREATE_USED_CHARSET || - create_info->used_fields & HA_CREATE_USED_DEFAULT_CHARSET || - (table->s->row_type != create_info->row_type) || - create_info->used_fields & HA_CREATE_USED_PAGE_CHECKSUM || - create_info->used_fields & HA_CREATE_USED_TRANSACTIONAL || - create_info->used_fields & HA_CREATE_USED_PACK_KEYS || - create_info->used_fields & HA_CREATE_USED_MAX_ROWS || - (alter_info->flags & (ALTER_RECREATE | ALTER_FOREIGN_KEY)) || - order_num || - !table->s->mysql_version || - (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar)) - { - DBUG_PRINT("info", ("Basic checks -> ALTER_TABLE_DATA_CHANGED")); - DBUG_RETURN(0); - } + /* + Check if the column is computed and either + is stored or is used in the partitioning expression. + */ + if (field->vcol_info && + (field->stored_in_db || field->vcol_info->is_in_partitioning_expr())) + { + if (is_equal == IS_EQUAL_NO || + !new_field->vcol_info || + !field->vcol_info->is_equal(new_field->vcol_info)) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_VCOL; + else + maybe_alter_vcol= true; + } - if ((create_info->fields_option_struct= (ha_field_option_struct**) - thd->calloc(sizeof(void*) * table->s->fields)) == NULL || - (create_info->indexes_option_struct= (ha_index_option_struct**) - thd->calloc(sizeof(void*) * table->s->keys)) == NULL) - DBUG_RETURN(1); + /* Check if field was renamed */ + if (my_strcasecmp(system_charset_info, field->field_name, + new_field->field_name)) + { + field->flags|= FIELD_IS_RENAMED; + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_NAME; + rename_column_in_stat_tables(thd, table, field, + new_field->field_name); + } - /* - Use transformed info to evaluate possibility of in-place ALTER TABLE - but use the preserved field to persist modifications. - */ - new_field_it.init(alter_info->create_list); - tmp_new_field_it.init(tmp_alter_info.create_list); + /* Check that NULL behavior is same for old and new fields */ + if ((new_field->flags & NOT_NULL_FLAG) != + (uint) (field->flags & NOT_NULL_FLAG)) + { + if (new_field->flags & NOT_NULL_FLAG) + ha_alter_info->handler_flags|= + Alter_inplace_info::ALTER_COLUMN_NOT_NULLABLE; + else + ha_alter_info->handler_flags|= + Alter_inplace_info::ALTER_COLUMN_NULLABLE; + } - /* - Go through fields and check if the original ones are compatible - with new table. - */ - for (i= 0, f_ptr= table->field, new_field= new_field_it++, - tmp_new_field= tmp_new_field_it++; - (field= *f_ptr); - i++, f_ptr++, new_field= new_field_it++, - tmp_new_field= tmp_new_field_it++) - { - DBUG_ASSERT(i < table->s->fields); - create_info->fields_option_struct[i]= tmp_new_field->option_struct; + /* + We do not detect changes to default values in this loop. + See comment above for more details. + */ - /* reset common markers of how field changed */ - field->flags&= ~(FIELD_IS_RENAMED | FIELD_IN_ADD_INDEX); + /* + Detect changes in column order. + */ + if (field->field_index != new_field_index) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_ORDER; - /* Make sure we have at least the default charset in use. */ - if (!new_field->charset) - new_field->charset= create_info->default_table_charset; + /* Detect changes in storage type of column */ + if (new_field->field_storage_type() != field->field_storage_type()) + ha_alter_info->handler_flags|= + Alter_inplace_info::ALTER_COLUMN_STORAGE_TYPE; - /* Check that NULL behavior is same for old and new fields */ - if ((tmp_new_field->flags & NOT_NULL_FLAG) != - (uint) (field->flags & NOT_NULL_FLAG)) + /* Detect changes in column format of column */ + if (new_field->column_format() != field->column_format()) + ha_alter_info->handler_flags|= + Alter_inplace_info::ALTER_COLUMN_COLUMN_FORMAT; + + if (engine_options_differ(field->option_struct, new_field->option_struct, + table->file->ht->field_options)) + { + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_OPTION; + ha_alter_info->create_info->fields_option_struct[f_ptr - table->field]= + new_field->option_struct; + } + + } + else { - DBUG_PRINT("info", ("NULL behaviour difference in field '%s' -> " - "ALTER_TABLE_DATA_CHANGED", new_field->field_name)); - DBUG_RETURN(0); + /* + Field is not present in new version of table and therefore was dropped. + Corresponding storage engine flag should be already set. + */ + DBUG_ASSERT(ha_alter_info->handler_flags & Alter_inplace_info::DROP_COLUMN); + field->flags|= FIELD_IS_DROPPED; } + } + if (maybe_alter_vcol) + { /* - Check if the altered column is computed and either - is stored or is used in the partitioning expression. - TODO: Mark such a column with an alter flag only if - the defining expression has changed. + No virtual column was altered, but perhaps one of the other columns was, + and that column was part of the vcol expression? + We don't detect this correctly (FIXME), so let's just say that a vcol + *might* be affected if any other column was altered. */ - if (field->vcol_info && - (field->stored_in_db || field->vcol_info->is_in_partitioning_expr())) - { - *need_copy_table= ALTER_TABLE_DATA_CHANGED; - DBUG_RETURN(0); - } - - /* Don't pack rows in old tables if the user has requested this. */ - if (create_info->row_type == ROW_TYPE_DYNAMIC || - (tmp_new_field->flags & BLOB_FLAG) || - (tmp_new_field->sql_type == MYSQL_TYPE_VARCHAR && - create_info->row_type != ROW_TYPE_FIXED)) - create_info->table_options|= HA_OPTION_PACK_RECORD; - - /* Check if field was renamed */ - if (my_strcasecmp(system_charset_info, - field->field_name, - tmp_new_field->field_name)) - field->flags|= FIELD_IS_RENAMED; + if (ha_alter_info->handler_flags & + ( Alter_inplace_info::ALTER_COLUMN_TYPE + | Alter_inplace_info::ALTER_COLUMN_NOT_NULLABLE + | Alter_inplace_info::ALTER_COLUMN_OPTION )) + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_VCOL; + } - /* Evaluate changes bitmap and send to check_if_incompatible_data() */ - if (!(tmp= field->is_equal(tmp_new_field))) + new_field_it.init(alter_info->create_list); + while ((new_field= new_field_it++)) + { + if (! new_field->field) { - DBUG_PRINT("info", ("!field_is_equal('%s') -> ALTER_TABLE_DATA_CHANGED", - new_field->field_name)); - DBUG_RETURN(0); + /* + Field is not present in old version of table and therefore was added. + Again corresponding storage engine flag should be already set. + */ + DBUG_ASSERT(ha_alter_info->handler_flags & Alter_inplace_info::ADD_COLUMN); + + if (new_field->vcol_info && + (new_field->stored_in_db || new_field->vcol_info->is_in_partitioning_expr())) + { + ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_VCOL; + } + break; } - changes|= tmp; } /* @@ -5328,90 +6481,121 @@ mysql_compare_tables(TABLE *table, KEY *table_key; KEY *table_key_end= table->key_info + table->s->keys; KEY *new_key; - KEY *new_key_end= *key_info_buffer + key_count; + KEY *new_key_end= + ha_alter_info->key_info_buffer + ha_alter_info->key_count; + /* + Primary key index for the new table + */ + const KEY* const new_pk= (ha_alter_info->key_count > 0 && + (!my_strcasecmp(system_charset_info, + ha_alter_info->key_info_buffer->name, + primary_key_name) || + is_candidate_key(ha_alter_info->key_info_buffer))) ? + ha_alter_info->key_info_buffer : NULL; + const KEY *const old_pk= table->s->primary_key == MAX_KEY ? NULL : + table->key_info + table->s->primary_key; DBUG_PRINT("info", ("index count old: %d new: %d", - table->s->keys, key_count)); + table->s->keys, ha_alter_info->key_count)); + /* Step through all keys of the old table and search matching new keys. */ - *index_drop_count= 0; - *index_add_count= 0; - *candidate_key_count= 0; + ha_alter_info->index_drop_count= 0; + ha_alter_info->index_add_count= 0; for (table_key= table->key_info; table_key < table_key_end; table_key++) { - KEY_PART_INFO *table_part; - KEY_PART_INFO *table_part_end= table_key->key_part + table_key->key_parts; - KEY_PART_INFO *new_part; - - /* - Check if key is a candidate key, i.e. a unique index with no index - fields nullable, then key is either already primary key or could - be promoted to primary key if the original primary key is dropped. - Count all candidate keys. - */ - not_nullable= true; - for (table_part= table_key->key_part; - table_part < table_part_end; - table_part++) - { - not_nullable= not_nullable && (! table_part->field->maybe_null()); - } - if ((table_key->flags & HA_NOSAME) && not_nullable) - (*candidate_key_count)++; - /* Search a new key with the same name. */ - for (new_key= *key_info_buffer; new_key < new_key_end; new_key++) + for (new_key= ha_alter_info->key_info_buffer; + new_key < new_key_end; + new_key++) { if (! strcmp(table_key->name, new_key->name)) break; } if (new_key >= new_key_end) { - /* Key not found. Add the offset of the key to the drop buffer. */ - (*index_drop_buffer)[(*index_drop_count)++]= table_key - table->key_info; + /* Key not found. Add the key to the drop buffer. */ + ha_alter_info->index_drop_buffer + [ha_alter_info->index_drop_count++]= + table_key; DBUG_PRINT("info", ("index dropped: '%s'", table_key->name)); continue; } /* Check that the key types are compatible between old and new tables. */ if ((table_key->algorithm != new_key->algorithm) || - ((table_key->flags & HA_KEYFLAG_MASK) != + ((table_key->flags & HA_KEYFLAG_MASK) != (new_key->flags & HA_KEYFLAG_MASK)) || - (table_key->key_parts != new_key->key_parts)) + (table_key->user_defined_key_parts != + new_key->user_defined_key_parts)) + goto index_changed; + + if (table_key->block_size != new_key->block_size) + goto index_changed; + + if (engine_options_differ(table_key->option_struct, new_key->option_struct, + table->file->ht->index_options)) goto index_changed; /* Check that the key parts remain compatible between the old and new tables. */ - for (table_part= table_key->key_part, new_part= new_key->key_part; - table_part < table_part_end; - table_part++, new_part++) + end= table_key->key_part + table_key->user_defined_key_parts; + for (key_part= table_key->key_part, new_part= new_key->key_part; + key_part < end; + key_part++, new_part++) { /* - Key definition has changed if we are using a different field or - if the used key part length is different. We know that the fields - did not change. Comparing field numbers is sufficient. + Key definition has changed if we are using a different field or + if the used key part length is different. It makes sense to + check lengths first as in case when fields differ it is likely + that lengths differ too and checking fields is more expensive + in general case. */ - if ((table_part->length != new_part->length) || - (table_part->fieldnr - 1 != new_part->fieldnr)) - goto index_changed; + if (key_part->length != new_part->length) + goto index_changed; + + new_field= get_field_by_index(alter_info, new_part->fieldnr); + + /* + For prefix keys KEY_PART_INFO::field points to cloned Field + object with adjusted length. So below we have to check field + indexes instead of simply comparing pointers to Field objects. + */ + if (! new_field->field || + new_field->field->field_index != key_part->fieldnr - 1) + goto index_changed; } + + /* + Rebuild the index if following condition get satisfied: + + (i) Old table doesn't have primary key, new table has it and vice-versa + (ii) Primary key changed to another existing index + */ + if ((new_key == new_pk) != (table_key == old_pk)) + goto index_changed; + + /* Check that key comment is not changed. */ + if (table_key->comment.length != new_key->comment.length || + (table_key->comment.length && + memcmp(table_key->comment.str, new_key->comment.str, + table_key->comment.length) != 0)) + goto index_changed; + continue; index_changed: - /* Key modified. Add the offset of the key to both buffers. */ - (*index_drop_buffer)[(*index_drop_count)++]= table_key - table->key_info; - (*index_add_buffer)[(*index_add_count)++]= new_key - *key_info_buffer; - key_part= new_key->key_part; - end= key_part + new_key->key_parts; - for(; key_part != end; key_part++) - { - // Mark field to be part of new key - field= table->field[key_part->fieldnr]; - field->flags|= FIELD_IN_ADD_INDEX; - } + /* Key modified. Add the key / key offset to both buffers. */ + ha_alter_info->index_drop_buffer + [ha_alter_info->index_drop_count++]= + table_key; + ha_alter_info->index_add_buffer + [ha_alter_info->index_add_count++]= + new_key - ha_alter_info->key_info_buffer; + /* Mark all old fields which are used in newly created index. */ DBUG_PRINT("info", ("index changed: '%s'", table_key->name)); } /*end of for (; table_key < table_key_end;) */ @@ -5419,12 +6603,12 @@ mysql_compare_tables(TABLE *table, /* Step through all keys of the new table and find matching old keys. */ - for (new_key= *key_info_buffer; new_key < new_key_end; new_key++) + for (new_key= ha_alter_info->key_info_buffer; + new_key < new_key_end; + new_key++) { /* Search an old key with the same name. */ - for (i= 0, table_key= table->key_info; - table_key < table_key_end; - i++, table_key++) + for (table_key= table->key_info; table_key < table_key_end; table_key++) { if (! strcmp(table_key->name, new_key->name)) break; @@ -5432,44 +6616,270 @@ mysql_compare_tables(TABLE *table, if (table_key >= table_key_end) { /* Key not found. Add the offset of the key to the add buffer. */ - (*index_add_buffer)[(*index_add_count)++]= new_key - *key_info_buffer; - key_part= new_key->key_part; - end= key_part + new_key->key_parts; - for(; key_part != end; key_part++) - { - // Mark field to be part of new key - field= table->field[key_part->fieldnr]; - field->flags|= FIELD_IN_ADD_INDEX; - } + ha_alter_info->index_add_buffer + [ha_alter_info->index_add_count++]= + new_key - ha_alter_info->key_info_buffer; DBUG_PRINT("info", ("index added: '%s'", new_key->name)); } else + ha_alter_info->create_info->indexes_option_struct[table_key - table->key_info]= + new_key->option_struct; + } + + /* + Sort index_add_buffer according to how key_info_buffer is sorted. + I.e. with primary keys first - see sort_keys(). + */ + my_qsort(ha_alter_info->index_add_buffer, + ha_alter_info->index_add_count, + sizeof(uint), (qsort_cmp) compare_uint); + + /* Now let us calculate flags for storage engine API. */ + + /* Figure out what kind of indexes we are dropping. */ + KEY **dropped_key; + KEY **dropped_key_end= ha_alter_info->index_drop_buffer + + ha_alter_info->index_drop_count; + + for (dropped_key= ha_alter_info->index_drop_buffer; + dropped_key < dropped_key_end; dropped_key++) + { + table_key= *dropped_key; + + if (table_key->flags & HA_NOSAME) { - DBUG_ASSERT(i < table->s->keys); - create_info->indexes_option_struct[i]= new_key->option_struct; + if (table_key == old_pk) + ha_alter_info->handler_flags|= Alter_inplace_info::DROP_PK_INDEX; + else + ha_alter_info->handler_flags|= Alter_inplace_info::DROP_UNIQUE_INDEX; } + else + ha_alter_info->handler_flags|= Alter_inplace_info::DROP_INDEX; } - /* Check if changes are compatible with current handler without a copy */ + /* Now figure out what kind of indexes we are adding. */ + for (uint add_key_idx= 0; add_key_idx < ha_alter_info->index_add_count; add_key_idx++) + { + new_key= ha_alter_info->key_info_buffer + ha_alter_info->index_add_buffer[add_key_idx]; + + if (new_key->flags & HA_NOSAME) + { + if (new_key == new_pk) + ha_alter_info->handler_flags|= Alter_inplace_info::ADD_PK_INDEX; + else + ha_alter_info->handler_flags|= Alter_inplace_info::ADD_UNIQUE_INDEX; + } + else + ha_alter_info->handler_flags|= Alter_inplace_info::ADD_INDEX; + } + + DBUG_RETURN(false); +} + + +/** + Mark fields participating in newly added indexes in TABLE object which + corresponds to new version of altered table. + + @param ha_alter_info Alter_inplace_info describing in-place ALTER. + @param altered_table TABLE object for new version of TABLE in which + fields should be marked. +*/ + +static void update_altered_table(const Alter_inplace_info &ha_alter_info, + TABLE *altered_table) +{ + uint field_idx, add_key_idx; + KEY *key; + KEY_PART_INFO *end, *key_part; + + /* + Clear marker for all fields, as we are going to set it only + for fields which participate in new indexes. + */ + for (field_idx= 0; field_idx < altered_table->s->fields; ++field_idx) + altered_table->field[field_idx]->flags&= ~FIELD_IN_ADD_INDEX; + + /* + Go through array of newly added indexes and mark fields + participating in them. + */ + for (add_key_idx= 0; add_key_idx < ha_alter_info.index_add_count; + add_key_idx++) + { + key= ha_alter_info.key_info_buffer + + ha_alter_info.index_add_buffer[add_key_idx]; + + end= key->key_part + key->user_defined_key_parts; + for (key_part= key->key_part; key_part < end; key_part++) + altered_table->field[key_part->fieldnr]->flags|= FIELD_IN_ADD_INDEX; + } +} + + +/** + Compare two tables to see if their metadata are compatible. + One table specified by a TABLE instance, the other using Alter_info + and HA_CREATE_INFO. + + @param[in] table The first table. + @param[in] alter_info Alter options, fields and keys for the + second table. + @param[in] create_info Create options for the second table. + @param[out] metadata_equal Result of comparison. + + @retval true error + @retval false success +*/ + +bool mysql_compare_tables(TABLE *table, + Alter_info *alter_info, + HA_CREATE_INFO *create_info, + bool *metadata_equal) +{ + DBUG_ENTER("mysql_compare_tables"); + + uint changes= IS_EQUAL_NO; + uint key_count; + List_iterator_fast<Create_field> tmp_new_field_it; + THD *thd= table->in_use; + *metadata_equal= false; + + /* + Create a copy of alter_info. + To compare definitions, we need to "prepare" the definition - transform it + from parser output to a format that describes the table layout (all column + defaults are initialized, duplicate columns are removed). This is done by + mysql_prepare_create_table. Unfortunately, mysql_prepare_create_table + performs its transformations "in-place", that is, modifies the argument. + Since we would like to keep mysql_compare_tables() idempotent (not altering + any of the arguments) we create a copy of alter_info here and pass it to + mysql_prepare_create_table, then use the result to compare the tables, and + then destroy the copy. + */ + Alter_info tmp_alter_info(*alter_info, thd->mem_root); + uint db_options= 0; /* not used */ + KEY *key_info_buffer= NULL; + + /* Create the prepared information. */ + int create_table_mode= table->s->tmp_table == NO_TMP_TABLE ? + C_ORDINARY_CREATE : C_ALTER_TABLE; + if (mysql_prepare_create_table(thd, create_info, &tmp_alter_info, + &db_options, table->file, &key_info_buffer, + &key_count, create_table_mode)) + DBUG_RETURN(1); + + /* Some very basic checks. */ + if (table->s->fields != alter_info->create_list.elements || + table->s->db_type() != create_info->db_type || + table->s->tmp_table || + (table->s->row_type != create_info->row_type)) + DBUG_RETURN(false); + + /* Go through fields and check if they are compatible. */ + tmp_new_field_it.init(tmp_alter_info.create_list); + for (Field **f_ptr= table->field; *f_ptr; f_ptr++) + { + Field *field= *f_ptr; + Create_field *tmp_new_field= tmp_new_field_it++; + + /* Check that NULL behavior is the same. */ + if ((tmp_new_field->flags & NOT_NULL_FLAG) != + (uint) (field->flags & NOT_NULL_FLAG)) + DBUG_RETURN(false); + + /* + mysql_prepare_alter_table() clears HA_OPTION_PACK_RECORD bit when + preparing description of existing table. In ALTER TABLE it is later + updated to correct value by create_table_impl() call. + So to get correct value of this bit in this function we have to + mimic behavior of create_table_impl(). + */ + if (create_info->row_type == ROW_TYPE_DYNAMIC || + create_info->row_type == ROW_TYPE_PAGE || + (tmp_new_field->flags & BLOB_FLAG) || + (tmp_new_field->sql_type == MYSQL_TYPE_VARCHAR && + create_info->row_type != ROW_TYPE_FIXED)) + create_info->table_options|= HA_OPTION_PACK_RECORD; + + /* Check if field was renamed */ + if (my_strcasecmp(system_charset_info, + field->field_name, + tmp_new_field->field_name)) + DBUG_RETURN(false); + + /* Evaluate changes bitmap and send to check_if_incompatible_data() */ + uint field_changes= field->is_equal(tmp_new_field); + if (field_changes != IS_EQUAL_YES) + DBUG_RETURN(false); + + changes|= field_changes; + } + + /* Check if changes are compatible with current handler. */ if (table->file->check_if_incompatible_data(create_info, changes)) + DBUG_RETURN(false); + + /* Go through keys and check if they are compatible. */ + KEY *table_key; + KEY *table_key_end= table->key_info + table->s->keys; + KEY *new_key; + KEY *new_key_end= key_info_buffer + key_count; + + /* Step through all keys of the first table and search matching keys. */ + for (table_key= table->key_info; table_key < table_key_end; table_key++) { - DBUG_PRINT("info", ("check_if_incompatible_data() -> " - "ALTER_TABLE_DATA_CHANGED")); - DBUG_RETURN(0); + /* Search a key with the same name. */ + for (new_key= key_info_buffer; new_key < new_key_end; new_key++) + { + if (! strcmp(table_key->name, new_key->name)) + break; + } + if (new_key >= new_key_end) + DBUG_RETURN(false); + + /* Check that the key types are compatible. */ + if ((table_key->algorithm != new_key->algorithm) || + ((table_key->flags & HA_KEYFLAG_MASK) != + (new_key->flags & HA_KEYFLAG_MASK)) || + (table_key->user_defined_key_parts != + new_key->user_defined_key_parts)) + DBUG_RETURN(false); + + /* Check that the key parts remain compatible. */ + KEY_PART_INFO *table_part; + KEY_PART_INFO *table_part_end= table_key->key_part + table_key->user_defined_key_parts; + KEY_PART_INFO *new_part; + for (table_part= table_key->key_part, new_part= new_key->key_part; + table_part < table_part_end; + table_part++, new_part++) + { + /* + Key definition is different if we are using a different field or + if the used key part length is different. We know that the fields + are equal. Comparing field numbers is sufficient. + */ + if ((table_part->length != new_part->length) || + (table_part->fieldnr - 1 != new_part->fieldnr)) + DBUG_RETURN(false); + } } - if (*index_drop_count || *index_add_count) + /* Step through all keys of the second table and find matching keys. */ + for (new_key= key_info_buffer; new_key < new_key_end; new_key++) { - DBUG_PRINT("info", ("Index dropped=%u added=%u -> " - "ALTER_TABLE_INDEX_CHANGED", - *index_drop_count, *index_add_count)); - *need_copy_table= ALTER_TABLE_INDEX_CHANGED; - DBUG_RETURN(0); + /* Search a key with the same name. */ + for (table_key= table->key_info; table_key < table_key_end; table_key++) + { + if (! strcmp(table_key->name, new_key->name)) + break; + } + if (table_key >= table_key_end) + DBUG_RETURN(false); } - DBUG_PRINT("info", (" -> ALTER_TABLE_METADATA_ONLY")); - *need_copy_table= ALTER_TABLE_METADATA_ONLY; // Tables are compatible - DBUG_RETURN(0); + *metadata_equal= true; // Tables are compatible + DBUG_RETURN(false); } @@ -5490,7 +6900,7 @@ mysql_compare_tables(TABLE *table, static bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled, - enum enum_enable_or_disable keys_onoff) + Alter_info::enum_enable_or_disable keys_onoff) { int error= 0; DBUG_ENTER("alter_table_manage_keys"); @@ -5498,29 +6908,415 @@ bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled, table, indexes_were_disabled, keys_onoff)); switch (keys_onoff) { - case ENABLE: + case Alter_info::ENABLE: + DEBUG_SYNC(table->in_use, "alter_table_enable_indexes"); error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); break; - case LEAVE_AS_IS: + case Alter_info::LEAVE_AS_IS: if (!indexes_were_disabled) break; - /* fall-through */ - case DISABLE: + /* fall through */ + case Alter_info::DISABLE: error= table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); } if (error == HA_ERR_WRONG_COMMAND) { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), - table->s->table_name.str); + THD *thd= table->in_use; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_ILLEGAL_HA, ER_THD(thd, ER_ILLEGAL_HA), + table->file->table_type(), + table->s->db.str, table->s->table_name.str); error= 0; - } else if (error) + } + else if (error) table->file->print_error(error, MYF(0)); DBUG_RETURN(error); } + +/** + Check if the pending ALTER TABLE operations support the in-place + algorithm based on restrictions in the SQL layer or given the + nature of the operations themselves. If in-place isn't supported, + it won't be necessary to check with the storage engine. + + @param table The original TABLE. + @param create_info Information from the parsing phase about new + table properties. + @param alter_info Data related to detected changes. + + @return false In-place is possible, check with storage engine. + @return true Incompatible operations, must use table copy. +*/ + +static bool is_inplace_alter_impossible(TABLE *table, + HA_CREATE_INFO *create_info, + const Alter_info *alter_info) +{ + DBUG_ENTER("is_inplace_alter_impossible"); + + /* At the moment we can't handle altering temporary tables without a copy. */ + if (table->s->tmp_table) + DBUG_RETURN(true); + + /* + For the ALTER TABLE tbl_name ORDER BY ... we always use copy + algorithm. In theory, this operation can be done in-place by some + engine, but since a) no current engine does this and b) our current + API lacks infrastructure for passing information about table ordering + to storage engine we simply always do copy now. + + ENABLE/DISABLE KEYS is a MyISAM/Heap specific operation that is + not supported for in-place in combination with other operations. + Alone, it will be done by simple_rename_or_index_change(). + */ + if (alter_info->flags & (Alter_info::ALTER_ORDER | + Alter_info::ALTER_KEYS_ONOFF)) + DBUG_RETURN(true); + + /* + If the table engine is changed explicitly (using ENGINE clause) + or implicitly (e.g. when non-partitioned table becomes + partitioned) a regular alter table (copy) needs to be + performed. + */ + if (create_info->db_type != table->s->db_type()) + DBUG_RETURN(true); + + /* + There was a bug prior to mysql-4.0.25. Number of null fields was + calculated incorrectly. As a result frm and data files gets out of + sync after fast alter table. There is no way to determine by which + mysql version (in 4.0 and 4.1 branches) table was created, thus we + disable fast alter table for all tables created by mysql versions + prior to 5.0 branch. + See BUG#6236. + */ + if (!table->s->mysql_version) + DBUG_RETURN(true); + + DBUG_RETURN(false); +} + + +/** + Perform in-place alter table. + + @param thd Thread handle. + @param table_list TABLE_LIST for the table to change. + @param table The original TABLE. + @param altered_table TABLE object for new version of the table. + @param ha_alter_info Structure describing ALTER TABLE to be carried + out and serving as a storage place for data + used during different phases. + @param inplace_supported Enum describing the locking requirements. + @param target_mdl_request Metadata request/lock on the target table name. + @param alter_ctx ALTER TABLE runtime context. + + @retval true Error + @retval false Success + + @note + If mysql_alter_table does not need to copy the table, it is + either an alter table where the storage engine does not + need to know about the change, only the frm will change, + or the storage engine supports performing the alter table + operation directly, in-place without mysql having to copy + the table. + + @note This function frees the TABLE object associated with the new version of + the table and removes the .FRM file for it in case of both success and + failure. +*/ + +static bool mysql_inplace_alter_table(THD *thd, + TABLE_LIST *table_list, + TABLE *table, + TABLE *altered_table, + Alter_inplace_info *ha_alter_info, + enum_alter_inplace_result inplace_supported, + MDL_request *target_mdl_request, + Alter_table_ctx *alter_ctx) +{ + Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN | MYSQL_OPEN_IGNORE_KILLED); + handlerton *db_type= table->s->db_type(); + MDL_ticket *mdl_ticket= table->mdl_ticket; + HA_CREATE_INFO *create_info= ha_alter_info->create_info; + Alter_info *alter_info= ha_alter_info->alter_info; + bool reopen_tables= false; + + DBUG_ENTER("mysql_inplace_alter_table"); + + /* + Upgrade to EXCLUSIVE lock if: + - This is requested by the storage engine + - Or the storage engine needs exclusive lock for just the prepare + phase + - Or requested by the user + + Note that we handle situation when storage engine needs exclusive + lock for prepare phase under LOCK TABLES in the same way as when + exclusive lock is required for duration of the whole statement. + */ + if (inplace_supported == HA_ALTER_INPLACE_EXCLUSIVE_LOCK || + ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE || + inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) && + (thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) || + alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE) + { + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto cleanup; + /* + Get rid of all TABLE instances belonging to this thread + except one to be used for in-place ALTER TABLE. + + This is mostly needed to satisfy InnoDB assumptions/asserts. + */ + close_all_tables_for_name(thd, table->s, + alter_ctx->is_table_renamed() ? + HA_EXTRA_PREPARE_FOR_RENAME : + HA_EXTRA_NOT_USED, + table); + /* + If we are under LOCK TABLES we will need to reopen tables which we + just have closed in case of error. + */ + reopen_tables= true; + } + else if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE || + inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) + { + /* + Storage engine has requested exclusive lock only for prepare phase + and we are not under LOCK TABLES. + Don't mark TABLE_SHARE as old in this case, as this won't allow opening + of table by other threads during main phase of in-place ALTER TABLE. + */ + if (thd->mdl_context.upgrade_shared_lock(table->mdl_ticket, MDL_EXCLUSIVE, + thd->variables.lock_wait_timeout)) + goto cleanup; + + tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE, + table->s->db.str, table->s->table_name.str, + false); + } + + /* + Upgrade to SHARED_NO_WRITE lock if: + - The storage engine needs writes blocked for the whole duration + - Or this is requested by the user + Note that under LOCK TABLES, we will already have SHARED_NO_READ_WRITE. + */ + if ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK || + alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED) && + thd->mdl_context.upgrade_shared_lock(table->mdl_ticket, + MDL_SHARED_NO_WRITE, + thd->variables.lock_wait_timeout)) + { + goto cleanup; + } + + // It's now safe to take the table level lock. + if (lock_tables(thd, table_list, alter_ctx->tables_opened, 0)) + goto cleanup; + + DEBUG_SYNC(thd, "alter_table_inplace_after_lock_upgrade"); + THD_STAGE_INFO(thd, stage_alter_inplace_prepare); + + switch (inplace_supported) { + case HA_ALTER_ERROR: + case HA_ALTER_INPLACE_NOT_SUPPORTED: + DBUG_ASSERT(0); + // fall through + case HA_ALTER_INPLACE_NO_LOCK: + case HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE: + switch (alter_info->requested_lock) { + case Alter_info::ALTER_TABLE_LOCK_DEFAULT: + case Alter_info::ALTER_TABLE_LOCK_NONE: + ha_alter_info->online= true; + break; + case Alter_info::ALTER_TABLE_LOCK_SHARED: + case Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE: + break; + } + break; + case HA_ALTER_INPLACE_EXCLUSIVE_LOCK: + case HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE: + case HA_ALTER_INPLACE_SHARED_LOCK: + break; + } + + if (table->file->ha_prepare_inplace_alter_table(altered_table, + ha_alter_info)) + { + goto rollback; + } + + /* + Downgrade the lock if storage engine has told us that exclusive lock was + necessary only for prepare phase (unless we are not under LOCK TABLES) and + user has not explicitly requested exclusive lock. + */ + if ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE || + inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) && + !(thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) && + (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE)) + { + /* If storage engine or user requested shared lock downgrade to SNW. */ + if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE || + alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED) + table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_WRITE); + else + { + DBUG_ASSERT(inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE); + table->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE); + } + } + + DEBUG_SYNC(thd, "alter_table_inplace_after_lock_downgrade"); + THD_STAGE_INFO(thd, stage_alter_inplace); + + if (table->file->ha_inplace_alter_table(altered_table, + ha_alter_info)) + { + goto rollback; + } + + // Upgrade to EXCLUSIVE before commit. + if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME)) + goto rollback; + + /* + If we are killed after this point, we should ignore and continue. + We have mostly completed the operation at this point, there should + be no long waits left. + */ + + DBUG_EXECUTE_IF("alter_table_rollback_new_index", { + table->file->ha_commit_inplace_alter_table(altered_table, + ha_alter_info, + false); + my_error(ER_UNKNOWN_ERROR, MYF(0)); + goto cleanup; + }); + + DEBUG_SYNC(thd, "alter_table_inplace_before_commit"); + THD_STAGE_INFO(thd, stage_alter_inplace_commit); + + if (table->file->ha_commit_inplace_alter_table(altered_table, + ha_alter_info, + true)) + { + goto rollback; + } + + close_all_tables_for_name(thd, table->s, + alter_ctx->is_table_renamed() ? + HA_EXTRA_PREPARE_FOR_RENAME : + HA_EXTRA_NOT_USED, + NULL); + table_list->table= table= NULL; + close_temporary_table(thd, altered_table, true, false); + + /* + Replace the old .FRM with the new .FRM, but keep the old name for now. + Rename to the new name (if needed) will be handled separately below. + */ + if (mysql_rename_table(db_type, alter_ctx->new_db, alter_ctx->tmp_name, + alter_ctx->db, alter_ctx->alias, + FN_FROM_IS_TMP | NO_HA_TABLE)) + { + // Since changes were done in-place, we can't revert them. + (void) quick_rm_table(thd, db_type, + alter_ctx->new_db, alter_ctx->tmp_name, + FN_IS_TMP | NO_HA_TABLE); + DBUG_RETURN(true); + } + + table_list->mdl_request.ticket= mdl_ticket; + if (open_table(thd, table_list, &ot_ctx)) + DBUG_RETURN(true); + + /* + Tell the handler that the changed frm is on disk and table + has been re-opened + */ + table_list->table->file->ha_notify_table_changed(); + + /* + We might be going to reopen table down on the road, so we have to + restore state of the TABLE object which we used for obtaining of + handler object to make it usable for later reopening. + */ + close_thread_table(thd, &thd->open_tables); + table_list->table= NULL; + + // Rename altered table if requested. + if (alter_ctx->is_table_renamed()) + { + // Remove TABLE and TABLE_SHARE for old name from TDC. + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, + alter_ctx->db, alter_ctx->table_name, false); + + if (mysql_rename_table(db_type, alter_ctx->db, alter_ctx->table_name, + alter_ctx->new_db, alter_ctx->new_alias, 0)) + { + /* + If the rename fails we will still have a working table + with the old name, but with other changes applied. + */ + DBUG_RETURN(true); + } + if (Table_triggers_list::change_table_name(thd, + alter_ctx->db, + alter_ctx->alias, + alter_ctx->table_name, + alter_ctx->new_db, + alter_ctx->new_alias)) + { + /* + If the rename of trigger files fails, try to rename the table + back so we at least have matching table and trigger files. + */ + (void) mysql_rename_table(db_type, + alter_ctx->new_db, alter_ctx->new_alias, + alter_ctx->db, alter_ctx->alias, NO_FK_CHECKS); + DBUG_RETURN(true); + } + rename_table_in_stat_tables(thd, alter_ctx->db,alter_ctx->alias, + alter_ctx->new_db, alter_ctx->new_alias); + } + + DBUG_RETURN(false); + + rollback: + table->file->ha_commit_inplace_alter_table(altered_table, + ha_alter_info, + false); + cleanup: + if (reopen_tables) + { + /* Close the only table instance which is still around. */ + close_all_tables_for_name(thd, table->s, + alter_ctx->is_table_renamed() ? + HA_EXTRA_PREPARE_FOR_RENAME : + HA_EXTRA_NOT_USED, + NULL); + if (thd->locked_tables_list.reopen_tables(thd, false)) + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + /* QQ; do something about metadata locks ? */ + } + close_temporary_table(thd, altered_table, true, false); + // Delete temporary .frm/.par + (void) quick_rm_table(thd, create_info->db_type, alter_ctx->new_db, + alter_ctx->tmp_name, FN_IS_TMP | NO_HA_TABLE); + DBUG_RETURN(true); +} + /** maximum possible length for certain blob types. @@ -5579,6 +7375,7 @@ blob_length_by_type(enum_field_types type) But since ALTER might end-up doing CREATE, this distinction is gone and we just carry around two structures. + @param[in,out] alter_ctx Runtime context for ALTER TABLE. @return Fills various create_info members based on information retrieved @@ -5595,7 +7392,8 @@ blob_length_by_type(enum_field_types type) bool mysql_prepare_alter_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, - Alter_info *alter_info) + Alter_info *alter_info, + Alter_table_ctx *alter_ctx) { /* New column definitions are added here */ List<Create_field> new_create_list; @@ -5610,13 +7408,22 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, List<Key_part_spec> key_parts; uint db_create_options= (table->s->db_create_options & ~(HA_OPTION_PACK_RECORD)); - uint used_fields= create_info->used_fields; + uint used_fields; KEY *key_info=table->key_info; bool rc= TRUE; + bool modified_primary_key= FALSE; Create_field *def; Field **f_ptr,*field; DBUG_ENTER("mysql_prepare_alter_table"); + /* + Merge incompatible changes flag in case of upgrade of a table from an + old MariaDB or MySQL version. This ensures that we don't try to do an + online alter table if field packing or character set changes are required. + */ + create_info->used_fields|= table->s->incompatible_version; + used_fields= create_info->used_fields; + create_info->varchar= FALSE; /* Let new create options override the old ones */ if (!(used_fields & HA_CREATE_USED_MIN_ROWS)) @@ -5633,19 +7440,33 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, table->file->info(HA_STATUS_AUTO); create_info->auto_increment_value= table->file->stats.auto_increment_value; } + if (!(used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE)) create_info->key_block_size= table->s->key_block_size; + + if (!(used_fields & HA_CREATE_USED_STATS_SAMPLE_PAGES)) + create_info->stats_sample_pages= table->s->stats_sample_pages; + + if (!(used_fields & HA_CREATE_USED_STATS_AUTO_RECALC)) + create_info->stats_auto_recalc= table->s->stats_auto_recalc; + if (!(used_fields & HA_CREATE_USED_TRANSACTIONAL)) create_info->transactional= table->s->transactional; - /* Creation of federated table with LIKE clause needs connection string */ if (!(used_fields & HA_CREATE_USED_CONNECTION)) create_info->connect_string= table->s->connect_string; restore_record(table, s->default_values); // Empty record for DEFAULT + if ((create_info->fields_option_struct= (ha_field_option_struct**) + thd->calloc(sizeof(void*) * table->s->fields)) == NULL || + (create_info->indexes_option_struct= (ha_index_option_struct**) + thd->calloc(sizeof(void*) * table->s->keys)) == NULL) + DBUG_RETURN(1); + create_info->option_list= merge_engine_table_options(table->s->option_list, create_info->option_list, thd->mem_root); + /* First collect all fields from table which isn't in drop_list */ @@ -5673,13 +7494,9 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, } if (drop) { + if (table->s->tmp_table == NO_TMP_TABLE) + (void) delete_statistics_for_column(thd, table, field); drop_it.remove(); - /* - ALTER TABLE DROP COLUMN always changes table data even in cases - when new version of the table has the same structure as the old - one. - */ - alter_info->change_level= ALTER_TABLE_DATA_CHANGED; continue; } /* Check if field is changed */ @@ -5693,6 +7510,12 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, if (def) { // Field is changed def->field=field; + /* + Add column being updated to the list of new columns. + Note that columns with AFTER clauses are added to the end + of the list for now. Their positions will be corrected later. + */ + new_create_list.push_back(def, thd->mem_root); if (field->stored_in_db != def->stored_in_db) { my_error(ER_UNSUPPORTED_ACTION_ON_VIRTUAL_COLUMN, MYF(0)); @@ -5700,7 +7523,13 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, } if (!def->after) { - new_create_list.push_back(def); + /* + If this ALTER TABLE doesn't have an AFTER clause for the modified + column then remove this column from the list of columns to be + processed. So later we can iterate over the columns remaining + in this list and process modified columns with AFTER clause or + add new columns. + */ def_it.remove(); } } @@ -5710,8 +7539,8 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, This field was not dropped and not changed, add it to the list for the new table. */ - def= new Create_field(field, field); - new_create_list.push_back(def); + def= new (thd->mem_root) Create_field(thd, field, field); + new_create_list.push_back(def, thd->mem_root); alter_it.rewind(); // Change default if ALTER Alter_column *alter; while ((alter=alter_it++)) @@ -5768,46 +7597,60 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, */ if ((def->sql_type == MYSQL_TYPE_DATE || def->sql_type == MYSQL_TYPE_NEWDATE || - def->sql_type == MYSQL_TYPE_DATETIME) && - !alter_info->datetime_field && + def->sql_type == MYSQL_TYPE_DATETIME || + def->sql_type == MYSQL_TYPE_DATETIME2) && + !alter_ctx->datetime_field && !(~def->flags & (NO_DEFAULT_VALUE_FLAG | NOT_NULL_FLAG)) && thd->variables.sql_mode & MODE_NO_ZERO_DATE) { - alter_info->datetime_field= def; - alter_info->error_if_not_empty= TRUE; + alter_ctx->datetime_field= def; + alter_ctx->error_if_not_empty= TRUE; } if (!def->after) - new_create_list.push_back(def); - else if (def->after == first_keyword) - { - new_create_list.push_front(def); - /* - Re-ordering columns in table can't be done using in-place algorithm - as it always changes table data. - */ - alter_info->change_level= ALTER_TABLE_DATA_CHANGED; - } + new_create_list.push_back(def, thd->mem_root); else { Create_field *find; - find_it.rewind(); - while ((find=find_it++)) // Add new columns + if (def->change) { - if (!my_strcasecmp(system_charset_info,def->after, find->field_name)) - break; + find_it.rewind(); + /* + For columns being modified with AFTER clause we should first remove + these columns from the list and then add them back at their correct + positions. + */ + while ((find=find_it++)) + { + /* + Create_fields representing changed columns are added directly + from Alter_info::create_list to new_create_list. We can therefore + safely use pointer equality rather than name matching here. + This prevents removing the wrong column in case of column rename. + */ + if (find == def) + { + find_it.remove(); + break; + } + } } - if (!find) + if (def->after == first_keyword) + new_create_list.push_front(def, thd->mem_root); + else { - my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, - table->s->table_name.str); - goto err; + find_it.rewind(); + while ((find=find_it++)) + { + if (!my_strcasecmp(system_charset_info, def->after, find->field_name)) + break; + } + if (!find) + { + my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table->s->table_name.str); + goto err; + } + find_it.after(def); // Put column after this } - find_it.after(def); // Put element after this - /* - Re-ordering columns in table can't be done using in-place algorithm - as it always changes table data. - */ - alter_info->change_level= ALTER_TABLE_DATA_CHANGED; } /* Check if there is alter for newly added field. @@ -5841,7 +7684,8 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, } if (!new_create_list.elements) { - my_message(ER_CANT_REMOVE_ALL_FIELDS, ER(ER_CANT_REMOVE_ALL_FIELDS), + my_message(ER_CANT_REMOVE_ALL_FIELDS, + ER_THD(thd, ER_CANT_REMOVE_ALL_FIELDS), MYF(0)); goto err; } @@ -5850,7 +7694,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, Collect all keys which isn't in drop list. Add only those for which some fields exists. */ - + for (uint i=0 ; i < table->s->keys ; i++,key_info++) { char *key_name= key_info->name; @@ -5864,13 +7708,29 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, } if (drop) { + if (table->s->tmp_table == NO_TMP_TABLE) + { + (void) delete_statistics_for_index(thd, table, key_info, FALSE); + if (i == table->s->primary_key) + { + KEY *tab_key_info= table->key_info; + for (uint j=0; j < table->s->keys; j++, tab_key_info++) + { + if (tab_key_info->user_defined_key_parts != + tab_key_info->ext_key_parts) + (void) delete_statistics_for_index(thd, table, tab_key_info, + TRUE); + } + } + } drop_it.remove(); continue; } KEY_PART_INFO *key_part= key_info->key_part; key_parts.empty(); - for (uint j=0 ; j < key_info->key_parts ; j++,key_part++) + bool delete_index_stat= FALSE; + for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++) { if (!key_part->field) continue; // Wrong field (from UNIREG) @@ -5892,7 +7752,12 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, break; } if (!cfield) + { + if (table->s->primary_key == i) + modified_primary_key= TRUE; + delete_index_stat= TRUE; continue; // Field is removed + } key_part_length= key_part->length; if (cfield->field) // Not new field { @@ -5932,8 +7797,18 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, key_part_length /= key_part->field->charset()->mbmaxlen; key_parts.push_back(new Key_part_spec(cfield->field_name, strlen(cfield->field_name), - key_part_length)); + key_part_length), + thd->mem_root); } + if (table->s->tmp_table == NO_TMP_TABLE) + { + if (delete_index_stat) + (void) delete_statistics_for_index(thd, table, key_info, FALSE); + else if (modified_primary_key && + key_info->user_defined_key_parts != key_info->ext_key_parts) + (void) delete_statistics_for_index(thd, table, key_info, TRUE); + } + if (key_parts.elements) { KEY_CREATE_INFO key_create_info; @@ -5949,6 +7824,12 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, if (key_info->flags & HA_USES_COMMENT) key_create_info.comment= key_info->comment; + /* + We're refreshing an already existing index. Since the index is not + modified, there is no need to check for duplicate indexes again. + */ + key_create_info.check_for_duplicate_indexes= false; + if (key_info->flags & HA_SPATIAL) key_type= Key::SPATIAL; else if (key_info->flags & HA_NOSAME) @@ -5965,9 +7846,9 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, key= new Key(key_type, key_name, strlen(key_name), &key_create_info, - test(key_info->flags & HA_GENERATED_KEY), - key_parts, key_info->option_list); - new_key_list.push_back(key); + MY_TEST(key_info->flags & HA_GENERATED_KEY), + key_parts, key_info->option_list, DDL_options()); + new_key_list.push_back(key, thd->mem_root); } } { @@ -5977,8 +7858,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, if (key->type == Key::FOREIGN_KEY && ((Foreign_key *)key)->validate(new_create_list)) goto err; - if (key->type != Key::FOREIGN_KEY) - new_key_list.push_back(key); + new_key_list.push_back(key, thd->mem_root); if (key->name.str && !my_strcasecmp(system_charset_info, key->name.str, primary_key_name)) { @@ -5990,9 +7870,20 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, if (alter_info->drop_list.elements) { - my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), - alter_info->drop_list.head()->name); - goto err; + Alter_drop *drop; + drop_it.rewind(); + while ((drop=drop_it++)) { + switch (drop->type) { + case Alter_drop::KEY: + case Alter_drop::COLUMN: + my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), + alter_info->drop_list.head()->name); + goto err; + case Alter_drop::FOREIGN_KEY: + // Leave the DROP FOREIGN KEY names in the alter_info->drop_list. + break; + } + } } if (alter_info->alter_list.elements) { @@ -6012,6 +7903,11 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, (HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS)) || (used_fields & HA_CREATE_USED_PACK_KEYS)) db_create_options&= ~(HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS); + if ((create_info->table_options & + (HA_OPTION_STATS_PERSISTENT | HA_OPTION_NO_STATS_PERSISTENT)) || + (used_fields & HA_CREATE_USED_STATS_PERSISTENT)) + db_create_options&= ~(HA_OPTION_STATS_PERSISTENT | HA_OPTION_NO_STATS_PERSISTENT); + if (create_info->table_options & (HA_OPTION_CHECKSUM | HA_OPTION_NO_CHECKSUM)) db_create_options&= ~(HA_OPTION_CHECKSUM | HA_OPTION_NO_CHECKSUM); @@ -6032,158 +7928,518 @@ err: } -/* - Alter table - - SYNOPSIS - mysql_alter_table() - thd Thread handle - new_db If there is a RENAME clause - new_name If there is a RENAME clause - create_info Information from the parsing phase about new - table properties. - table_list The table to change. - alter_info Lists of fields, keys to be changed, added - or dropped. - order_num How many ORDER BY fields has been specified. - order List of fields to ORDER BY. - ignore Whether we have ALTER IGNORE TABLE - require_online Give an error if we can't do operation online +/** + Get Create_field object for newly created table by its name + in the old version of table. - DESCRIPTION - This is a veery long function and is everything but the kitchen sink :) - It is used to alter a table and not only by ALTER TABLE but also - CREATE|DROP INDEX are mapped on this function. - - When the ALTER TABLE statement just does a RENAME or ENABLE|DISABLE KEYS, - or both, then this function short cuts its operation by renaming - the table and/or enabling/disabling the keys. In this case, the FRM is - not changed, directly by mysql_alter_table. However, if there is a - RENAME + change of a field, or an index, the short cut is not used. - See how `create_list` is used to generate the new FRM regarding the - structure of the fields. The same is done for the indices of the table. - - Important is the fact, that this function tries to do as little work as - possible, by finding out whether a intermediate table is needed to copy - data into and when finishing the altering to use it as the original table. - For this reason the function mysql_compare_tables() is called, which decides - based on all kind of data how similar are the new and the original - tables. + @param alter_info Alter_info describing newly created table. + @param old_name Name of field in old table. - RETURN VALUES - FALSE OK - TRUE Error + @returns Pointer to Create_field object, NULL - if field is + not present in new version of table. */ -bool mysql_alter_table(THD *thd,char *new_db, char *new_name, - HA_CREATE_INFO *create_info, - TABLE_LIST *table_list, - Alter_info *alter_info, - uint order_num, ORDER *order, bool ignore, - bool require_online) +static Create_field *get_field_by_old_name(Alter_info *alter_info, + const char *old_name) { - TABLE *table, *new_table= 0; - MDL_ticket *mdl_ticket; - MDL_request target_mdl_request; - int error= 0; - char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1]; - char old_name_buff[FN_REFLEN + 1]; - char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias; - char index_file[FN_REFLEN], data_file[FN_REFLEN]; - char path[FN_REFLEN + 1]; - char reg_path[FN_REFLEN+1]; - ha_rows copied,deleted; - handlerton *old_db_type, *new_db_type, *save_old_db_type; - enum_alter_table_change_level need_copy_table= ALTER_TABLE_METADATA_ONLY; -#ifdef WITH_PARTITION_STORAGE_ENGINE - TABLE *table_for_fast_alter_partition= NULL; - bool partition_changed= FALSE; -#endif - bool need_lock_for_indexes __attribute__((unused)) = TRUE; - KEY *key_info_buffer; - uint index_drop_count= 0; - uint *index_drop_buffer= NULL; - uint index_add_count= 0; - handler_add_index *add= NULL; - bool pending_inplace_add_index= false; - uint *index_add_buffer= NULL; - uint candidate_key_count= 0; - bool no_pk; - ulong explicit_used_fields= 0; - enum ha_extra_function extra_func= thd->locked_tables_mode - ? HA_EXTRA_NOT_USED - : HA_EXTRA_FORCE_REOPEN; - DBUG_ENTER("mysql_alter_table"); + List_iterator_fast<Create_field> new_field_it(alter_info->create_list); + Create_field *new_field; - /* - Check if we attempt to alter mysql.slow_log or - mysql.general_log table and return an error if - it is the case. - TODO: this design is obsolete and will be removed. - */ - if (table_list && table_list->db && table_list->table_name) + while ((new_field= new_field_it++)) { - int table_kind= 0; + if (new_field->field && + (my_strcasecmp(system_charset_info, + new_field->field->field_name, + old_name) == 0)) + break; + } + return new_field; +} + + +/** Type of change to foreign key column, */ + +enum fk_column_change_type +{ + FK_COLUMN_NO_CHANGE, FK_COLUMN_DATA_CHANGE, + FK_COLUMN_RENAMED, FK_COLUMN_DROPPED +}; + +/** + Check that ALTER TABLE's changes on columns of a foreign key are allowed. + + @param[in] thd Thread context. + @param[in] alter_info Alter_info describing changes to be done + by ALTER TABLE. + @param[in] fk_columns List of columns of the foreign key to check. + @param[out] bad_column_name Name of field on which ALTER TABLE tries to + do prohibited operation. + + @note This function takes into account value of @@foreign_key_checks + setting. + + @retval FK_COLUMN_NO_CHANGE No significant changes are to be done on + foreign key columns. + @retval FK_COLUMN_DATA_CHANGE ALTER TABLE might result in value + change in foreign key column (and + foreign_key_checks is on). + @retval FK_COLUMN_RENAMED Foreign key column is renamed. + @retval FK_COLUMN_DROPPED Foreign key column is dropped. +*/ + +static enum fk_column_change_type +fk_check_column_changes(THD *thd, Alter_info *alter_info, + List<LEX_STRING> &fk_columns, + const char **bad_column_name) +{ + List_iterator_fast<LEX_STRING> column_it(fk_columns); + LEX_STRING *column; - table_kind= check_if_log_table(table_list->db_length, table_list->db, - table_list->table_name_length, - table_list->table_name, 0); + *bad_column_name= NULL; + + while ((column= column_it++)) + { + Create_field *new_field= get_field_by_old_name(alter_info, column->str); - if (table_kind) + if (new_field) { - /* Disable alter of enabled log tables */ - if (logger.is_log_table_enabled(table_kind)) - { - my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER"); - DBUG_RETURN(TRUE); - } + Field *old_field= new_field->field; - /* Disable alter of log tables to unsupported engine */ - if ((create_info->used_fields & HA_CREATE_USED_ENGINE) && - (!create_info->db_type || /* unknown engine */ - !(create_info->db_type->flags & HTON_SUPPORT_LOG_TABLES))) + if (my_strcasecmp(system_charset_info, old_field->field_name, + new_field->field_name)) { - my_error(ER_UNSUPORTED_LOG_ENGINE, MYF(0)); - DBUG_RETURN(TRUE); + /* + Copy algorithm doesn't support proper renaming of columns in + the foreign key yet. At the moment we lack API which will tell + SE that foreign keys should be updated to use new name of column + like it happens in case of in-place algorithm. + */ + *bad_column_name= column->str; + return FK_COLUMN_RENAMED; } -#ifdef WITH_PARTITION_STORAGE_ENGINE - if (alter_info->flags & ALTER_PARTITION) + if ((old_field->is_equal(new_field) == IS_EQUAL_NO) || + ((new_field->flags & NOT_NULL_FLAG) && + !(old_field->flags & NOT_NULL_FLAG))) { - my_error(ER_WRONG_USAGE, MYF(0), "PARTITION", "log table"); - DBUG_RETURN(TRUE); + if (!(thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS)) + { + /* + Column in a FK has changed significantly. Unless + foreign_key_checks are off we prohibit this since this + means values in this column might be changed by ALTER + and thus referential integrity might be broken, + */ + *bad_column_name= column->str; + return FK_COLUMN_DATA_CHANGE; + } } -#endif + } + else + { + /* + Column in FK was dropped. Most likely this will break + integrity constraints of InnoDB data-dictionary (and thus + InnoDB will emit an error), so we prohibit this right away + even if foreign_key_checks are off. + This also includes a rare case when another field replaces + field being dropped since it is easy to break referential + integrity in this case. + */ + *bad_column_name= column->str; + return FK_COLUMN_DROPPED; } } + return FK_COLUMN_NO_CHANGE; +} + + +/** + Check if ALTER TABLE we are about to execute using COPY algorithm + is not supported as it might break referential integrity. + + @note If foreign_key_checks is disabled (=0), we allow to break + referential integrity. But we still disallow some operations + like dropping or renaming columns in foreign key since they + are likely to break consistency of InnoDB data-dictionary + and thus will end-up in error anyway. + + @param[in] thd Thread context. + @param[in] table Table to be altered. + @param[in] alter_info Lists of fields, keys to be changed, added + or dropped. + @param[out] alter_ctx ALTER TABLE runtime context. + Alter_table_ctx::fk_error_if_delete flag + is set if deletion during alter can break + foreign key integrity. + + @retval false Success. + @retval true Error, ALTER - tries to do change which is not compatible + with foreign key definitions on the table. +*/ + +static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, + Alter_info *alter_info, + Alter_table_ctx *alter_ctx) +{ + List <FOREIGN_KEY_INFO> fk_parent_key_list; + List <FOREIGN_KEY_INFO> fk_child_key_list; + FOREIGN_KEY_INFO *f_key; + + DBUG_ENTER("fk_prepare_copy_alter_table"); + + table->file->get_parent_foreign_key_list(thd, &fk_parent_key_list); + + /* OOM when building list. */ + if (thd->is_error()) + DBUG_RETURN(true); + /* - Assign variables table_name, new_name, db, new_db, path, reg_path - to simplify further comparisions: we want to see if it's a RENAME - later just by comparing the pointers, avoiding the need for strcmp. + Remove from the list all foreign keys in which table participates as + parent which are to be dropped by this ALTER TABLE. This is possible + when a foreign key has the same table as child and parent. */ - thd_proc_info(thd, "init"); - table_name=table_list->table_name; - alias= (lower_case_table_names == 2) ? table_list->alias : table_name; - db=table_list->db; - if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db)) - new_db= db; - build_table_filename(reg_path, sizeof(reg_path) - 1, db, table_name, reg_ext, 0); - build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0); + List_iterator<FOREIGN_KEY_INFO> fk_parent_key_it(fk_parent_key_list); - mysql_ha_rm_tables(thd, table_list); + while ((f_key= fk_parent_key_it++)) + { + Alter_drop *drop; + List_iterator_fast<Alter_drop> drop_it(alter_info->drop_list); - /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */ - if (alter_info->tablespace_op != NO_TABLESPACE_OP) + while ((drop= drop_it++)) + { + /* + InnoDB treats foreign key names in case-insensitive fashion. + So we do it here too. For database and table name type of + comparison used depends on lower-case-table-names setting. + For l_c_t_n = 0 we use case-sensitive comparison, for + l_c_t_n > 0 modes case-insensitive comparison is used. + */ + if ((drop->type == Alter_drop::FOREIGN_KEY) && + (my_strcasecmp(system_charset_info, f_key->foreign_id->str, + drop->name) == 0) && + (my_strcasecmp(table_alias_charset, f_key->foreign_db->str, + table->s->db.str) == 0) && + (my_strcasecmp(table_alias_charset, f_key->foreign_table->str, + table->s->table_name.str) == 0)) + fk_parent_key_it.remove(); + } + } + + /* + If there are FKs in which this table is parent which were not + dropped we need to prevent ALTER deleting rows from the table, + as it might break referential integrity. OTOH it is OK to do + so if foreign_key_checks are disabled. + */ + if (!fk_parent_key_list.is_empty() && + !(thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS)) + alter_ctx->set_fk_error_if_delete_row(fk_parent_key_list.head()); + + fk_parent_key_it.rewind(); + while ((f_key= fk_parent_key_it++)) { - mysql_audit_alter_table(thd, table_list); + enum fk_column_change_type changes; + const char *bad_column_name; + + changes= fk_check_column_changes(thd, alter_info, + f_key->referenced_fields, + &bad_column_name); + + switch(changes) + { + case FK_COLUMN_NO_CHANGE: + /* No significant changes. We can proceed with ALTER! */ + break; + case FK_COLUMN_DATA_CHANGE: + { + char buff[NAME_LEN*2+2]; + strxnmov(buff, sizeof(buff)-1, f_key->foreign_db->str, ".", + f_key->foreign_table->str, NullS); + my_error(ER_FK_COLUMN_CANNOT_CHANGE_CHILD, MYF(0), bad_column_name, + f_key->foreign_id->str, buff); + DBUG_RETURN(true); + } + case FK_COLUMN_RENAMED: + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), + "ALGORITHM=COPY", + ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME), + "ALGORITHM=INPLACE"); + DBUG_RETURN(true); + case FK_COLUMN_DROPPED: + { + char buff[NAME_LEN*2+2]; + strxnmov(buff, sizeof(buff)-1, f_key->foreign_db->str, ".", + f_key->foreign_table->str, NullS); + my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD, MYF(0), bad_column_name, + f_key->foreign_id->str, buff); + DBUG_RETURN(true); + } + default: + DBUG_ASSERT(0); + } + } + + table->file->get_foreign_key_list(thd, &fk_child_key_list); + + /* OOM when building list. */ + if (thd->is_error()) + DBUG_RETURN(true); + + /* + Remove from the list all foreign keys which are to be dropped + by this ALTER TABLE. + */ + List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list); + + while ((f_key= fk_key_it++)) + { + Alter_drop *drop; + List_iterator_fast<Alter_drop> drop_it(alter_info->drop_list); + + while ((drop= drop_it++)) + { + /* Names of foreign keys in InnoDB are case-insensitive. */ + if ((drop->type == Alter_drop::FOREIGN_KEY) && + (my_strcasecmp(system_charset_info, f_key->foreign_id->str, + drop->name) == 0)) + fk_key_it.remove(); + } + } + + fk_key_it.rewind(); + while ((f_key= fk_key_it++)) + { + enum fk_column_change_type changes; + const char *bad_column_name; + + changes= fk_check_column_changes(thd, alter_info, + f_key->foreign_fields, + &bad_column_name); + + switch(changes) + { + case FK_COLUMN_NO_CHANGE: + /* No significant changes. We can proceed with ALTER! */ + break; + case FK_COLUMN_DATA_CHANGE: + my_error(ER_FK_COLUMN_CANNOT_CHANGE, MYF(0), bad_column_name, + f_key->foreign_id->str); + DBUG_RETURN(true); + case FK_COLUMN_RENAMED: + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), + "ALGORITHM=COPY", + ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME), + "ALGORITHM=INPLACE"); + DBUG_RETURN(true); + case FK_COLUMN_DROPPED: + my_error(ER_FK_COLUMN_CANNOT_DROP, MYF(0), bad_column_name, + f_key->foreign_id->str); + DBUG_RETURN(true); + default: + DBUG_ASSERT(0); + } + } + + DBUG_RETURN(false); +} + + +/** + Rename table and/or turn indexes on/off without touching .FRM + + @param thd Thread handler + @param table_list TABLE_LIST for the table to change + @param keys_onoff ENABLE or DISABLE KEYS? + @param alter_ctx ALTER TABLE runtime context. + + @return Operation status + @retval false Success + @retval true Failure +*/ + +static bool +simple_rename_or_index_change(THD *thd, TABLE_LIST *table_list, + Alter_info::enum_enable_or_disable keys_onoff, + Alter_table_ctx *alter_ctx) +{ + TABLE *table= table_list->table; + MDL_ticket *mdl_ticket= table->mdl_ticket; + int error= 0; + enum ha_extra_function extra_func= thd->locked_tables_mode + ? HA_EXTRA_NOT_USED + : HA_EXTRA_FORCE_REOPEN; + DBUG_ENTER("simple_rename_or_index_change"); + + if (keys_onoff != Alter_info::LEAVE_AS_IS) + { + if (wait_while_table_is_used(thd, table, extra_func)) + DBUG_RETURN(true); + + // It's now safe to take the table level lock. + if (lock_tables(thd, table_list, alter_ctx->tables_opened, 0)) + DBUG_RETURN(true); + + error= alter_table_manage_keys(table, + table->file->indexes_are_disabled(), + keys_onoff); + } + + if (!error && alter_ctx->is_table_renamed()) + { + THD_STAGE_INFO(thd, stage_rename); + handlerton *old_db_type= table->s->db_type(); + /* + Then do a 'simple' rename of the table. First we need to close all + instances of 'source' table. + Note that if wait_while_table_is_used() returns error here (i.e. if + this thread was killed) then it must be that previous step of + simple rename did nothing and therefore we can safely return + without additional clean-up. + */ + if (wait_while_table_is_used(thd, table, extra_func)) + DBUG_RETURN(true); + close_all_tables_for_name(thd, table->s, HA_EXTRA_PREPARE_FOR_RENAME, NULL); + + LEX_STRING old_db_name= { alter_ctx->db, strlen(alter_ctx->db) }; + LEX_STRING old_table_name= + { alter_ctx->table_name, strlen(alter_ctx->table_name) }; + LEX_STRING new_db_name= { alter_ctx->new_db, strlen(alter_ctx->new_db) }; + LEX_STRING new_table_name= + { alter_ctx->new_alias, strlen(alter_ctx->new_alias) }; + (void) rename_table_in_stat_tables(thd, &old_db_name, &old_table_name, + &new_db_name, &new_table_name); + + if (mysql_rename_table(old_db_type, alter_ctx->db, alter_ctx->table_name, + alter_ctx->new_db, alter_ctx->new_alias, 0)) + error= -1; + else if (Table_triggers_list::change_table_name(thd, + alter_ctx->db, + alter_ctx->alias, + alter_ctx->table_name, + alter_ctx->new_db, + alter_ctx->new_alias)) + { + (void) mysql_rename_table(old_db_type, + alter_ctx->new_db, alter_ctx->new_alias, + alter_ctx->db, alter_ctx->table_name, + NO_FK_CHECKS); + error= -1; + } + } + + if (!error) + { + error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + + if (!error) + my_ok(thd); + } + table_list->table= NULL; // For query cache + query_cache_invalidate3(thd, table_list, 0); + + if ((thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) + { + /* + Under LOCK TABLES we should adjust meta-data locks before finishing + statement. Otherwise we can rely on them being released + along with the implicit commit. + */ + if (alter_ctx->is_table_renamed()) + thd->mdl_context.release_all_locks_for_name(mdl_ticket); + else + mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE); + } + DBUG_RETURN(error != 0); +} + + +/** + Alter table + + @param thd Thread handle + @param new_db If there is a RENAME clause + @param new_name If there is a RENAME clause + @param create_info Information from the parsing phase about new + table properties. + @param table_list The table to change. + @param alter_info Lists of fields, keys to be changed, added + or dropped. + @param order_num How many ORDER BY fields has been specified. + @param order List of fields to ORDER BY. + @param ignore Whether we have ALTER IGNORE TABLE + + @retval true Error + @retval false Success + + This is a veery long function and is everything but the kitchen sink :) + It is used to alter a table and not only by ALTER TABLE but also + CREATE|DROP INDEX are mapped on this function. + + When the ALTER TABLE statement just does a RENAME or ENABLE|DISABLE KEYS, + or both, then this function short cuts its operation by renaming + the table and/or enabling/disabling the keys. In this case, the FRM is + not changed, directly by mysql_alter_table. However, if there is a + RENAME + change of a field, or an index, the short cut is not used. + See how `create_list` is used to generate the new FRM regarding the + structure of the fields. The same is done for the indices of the table. + + Altering a table can be done in two ways. The table can be modified + directly using an in-place algorithm, or the changes can be done using + an intermediate temporary table (copy). In-place is the preferred + algorithm as it avoids copying table data. The storage engine + selects which algorithm to use in check_if_supported_inplace_alter() + based on information about the table changes from fill_alter_inplace_info(). +*/ + +bool mysql_alter_table(THD *thd,char *new_db, char *new_name, + HA_CREATE_INFO *create_info, + TABLE_LIST *table_list, + Alter_info *alter_info, + uint order_num, ORDER *order, bool ignore) +{ + DBUG_ENTER("mysql_alter_table"); + + /* + Check if we attempt to alter mysql.slow_log or + mysql.general_log table and return an error if + it is the case. + TODO: this design is obsolete and will be removed. + */ + int table_kind= check_if_log_table(table_list, FALSE, NullS); - /* Conditionally writes to binlog. */ - bool ret= mysql_discard_or_import_tablespace(thd,table_list, - alter_info->tablespace_op); - DBUG_RETURN(ret); + if (table_kind) + { + /* Disable alter of enabled log tables */ + if (logger.is_log_table_enabled(table_kind)) + { + my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER"); + DBUG_RETURN(true); + } + + /* Disable alter of log tables to unsupported engine */ + if ((create_info->used_fields & HA_CREATE_USED_ENGINE) && + (!create_info->db_type || /* unknown engine */ + !(create_info->db_type->flags & HTON_SUPPORT_LOG_TABLES))) + { + my_error(ER_UNSUPORTED_LOG_ENGINE, MYF(0), + hton_name(create_info->db_type)->str); + DBUG_RETURN(true); + } + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (alter_info->flags & Alter_info::ALTER_PARTITION) + { + my_error(ER_WRONG_USAGE, MYF(0), "PARTITION", "log table"); + DBUG_RETURN(true); + } +#endif } + THD_STAGE_INFO(thd, stage_init); + /* Code below can handle only base tables so ensure that we won't open a view. Note that RENAME TABLE the only ALTER clause which is supported for views @@ -6191,20 +8447,35 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, */ table_list->required_type= FRMTYPE_TABLE; - Alter_table_prelocking_strategy alter_prelocking_strategy(alter_info); + Alter_table_prelocking_strategy alter_prelocking_strategy; DEBUG_SYNC(thd, "alter_table_before_open_tables"); - error= open_and_lock_tables(thd, table_list, FALSE, 0, - &alter_prelocking_strategy); + uint tables_opened; + + thd->open_options|= HA_OPEN_FOR_ALTER; + bool error= open_tables(thd, &table_list, &tables_opened, 0, + &alter_prelocking_strategy); + thd->open_options&= ~HA_OPEN_FOR_ALTER; + + DEBUG_SYNC(thd, "alter_opened_table"); + +#ifdef WITH_WSREP + DBUG_EXECUTE_IF("sync.alter_opened_table", + { + const char act[]= + "now " + "wait_for signal.alter_opened_table"; + DBUG_ASSERT(!debug_sync_set_action(thd, + STRING_WITH_LEN(act))); + };); +#endif // WITH_WSREP if (error) - { - DBUG_RETURN(TRUE); - } + DBUG_RETURN(true); - table= table_list->table; + TABLE *table= table_list->table; table->use_all_columns(); - mdl_ticket= table->mdl_ticket; + MDL_ticket *mdl_ticket= table->mdl_ticket; /* Prohibit changing of the UNION list of a non-temporary MERGE table @@ -6218,102 +8489,73 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, (table->s->tmp_table == NO_TMP_TABLE)) { my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); - DBUG_RETURN(TRUE); + DBUG_RETURN(true); } + Alter_table_ctx alter_ctx(thd, table_list, tables_opened, new_db, new_name); + + MDL_request target_mdl_request; + /* Check that we are not trying to rename to an existing table */ - if (new_name) + if (alter_ctx.is_table_renamed()) { - DBUG_PRINT("info", ("new_db.new_name: '%s'.'%s'", new_db, new_name)); - strmov(new_name_buff,new_name); - strmov(new_alias= new_alias_buff, new_name); - if (lower_case_table_names) + if (table->s->tmp_table != NO_TMP_TABLE) { - if (lower_case_table_names != 2) + if (find_temporary_table(thd, alter_ctx.new_db, alter_ctx.new_name)) { - my_casedn_str(files_charset_info, new_name_buff); - new_alias= new_name; // Create lower case table name + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias); + DBUG_RETURN(true); } - my_casedn_str(files_charset_info, new_name); } - if (new_db == db && - !my_strcasecmp(table_alias_charset, new_name_buff, table_name)) + else { + MDL_request_list mdl_requests; + MDL_request target_db_mdl_request; + + target_mdl_request.init(MDL_key::TABLE, + alter_ctx.new_db, alter_ctx.new_name, + MDL_EXCLUSIVE, MDL_TRANSACTION); + mdl_requests.push_front(&target_mdl_request); + /* - Source and destination table names are equal: make later check - easier. + If we are moving the table to a different database, we also + need IX lock on the database name so that the target database + is protected by MDL while the table is moved. */ - new_alias= new_name= table_name; - } - else - { - if (table->s->tmp_table != NO_TMP_TABLE) + if (alter_ctx.is_database_changed()) { - if (find_temporary_table(thd,new_db,new_name_buff)) - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff); - DBUG_RETURN(TRUE); - } + target_db_mdl_request.init(MDL_key::SCHEMA, alter_ctx.new_db, "", + MDL_INTENTION_EXCLUSIVE, + MDL_TRANSACTION); + mdl_requests.push_front(&target_db_mdl_request); } - else - { - MDL_request_list mdl_requests; - MDL_request target_db_mdl_request; - - target_mdl_request.init(MDL_key::TABLE, new_db, new_name, - MDL_EXCLUSIVE, MDL_TRANSACTION); - mdl_requests.push_front(&target_mdl_request); - - /* - If we are moving the table to a different database, we also - need IX lock on the database name so that the target database - is protected by MDL while the table is moved. - */ - if (new_db != db) - { - target_db_mdl_request.init(MDL_key::SCHEMA, new_db, "", - MDL_INTENTION_EXCLUSIVE, - MDL_TRANSACTION); - mdl_requests.push_front(&target_db_mdl_request); - } - /* - Global intention exclusive lock must have been already acquired when - table to be altered was open, so there is no need to do it here. - */ - DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, - "", "", - MDL_INTENTION_EXCLUSIVE)); + /* + Global intention exclusive lock must have been already acquired when + table to be altered was open, so there is no need to do it here. + */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, + "", "", + MDL_INTENTION_EXCLUSIVE)); - if (thd->mdl_context.acquire_locks(&mdl_requests, - thd->variables.lock_wait_timeout)) - DBUG_RETURN(TRUE); + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) + DBUG_RETURN(true); - DEBUG_SYNC(thd, "locked_table_name"); - /* - Table maybe does not exist, but we got an exclusive lock - on the name, now we can safely try to find out for sure. - */ - build_table_filename(new_name_buff, sizeof(new_name_buff) - 1, - new_db, new_name_buff, reg_ext, 0); - build_table_filename(old_name_buff, sizeof(old_name_buff) - 1, - db, table_name, reg_ext, 0); - if (check_table_file_presence(old_name_buff, new_name_buff, new_db, - new_name, new_alias, TRUE)) - { - /* Table will be closed in do_command() */ - goto err; - } + DEBUG_SYNC(thd, "locked_table_name"); + /* + Table maybe does not exist, but we got an exclusive lock + on the name, now we can safely try to find out for sure. + */ + if (ha_table_exists(thd, alter_ctx.new_db, alter_ctx.new_name, 0)) + { + /* Table will be closed in do_command() */ + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias); + DBUG_RETURN(true); } } } - else - { - new_alias= (lower_case_table_names == 2) ? alias : table_name; - new_name= table_name; - } - old_db_type= table->s->db_type(); if (!create_info->db_type) { #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -6331,19 +8573,18 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, } else #endif - create_info->db_type= old_db_type; + create_info->db_type= table->s->db_type(); } - if (check_engine(thd, new_db, new_name, create_info)) - goto err; - new_db_type= create_info->db_type; + if (check_engine(thd, alter_ctx.new_db, alter_ctx.new_name, create_info)) + DBUG_RETURN(true); - if ((new_db_type != old_db_type || - alter_info->flags & ALTER_PARTITION) && + if ((create_info->db_type != table->s->db_type() || + alter_info->flags & Alter_info::ALTER_PARTITION) && !table->file->can_switch_engines()) { my_error(ER_ROW_IS_REFERENCED, MYF(0)); - goto err; + DBUG_RETURN(true); } /* @@ -6354,441 +8595,217 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, create_info->db_type is set here, check to parent table access is delayed till this point for the alter operation. */ - if ((alter_info->flags & ALTER_FOREIGN_KEY) && + if ((alter_info->flags & Alter_info::ADD_FOREIGN_KEY) && check_fk_parent_table_access(thd, create_info, alter_info, new_db)) - goto err; + DBUG_RETURN(true); /* If this is an ALTER TABLE and no explicit row type specified reuse the table's row type. - Note: this is the same as if the row type was specified explicitly and - we must thus set HA_CREATE_USED_ROW_FORMAT! + Note: this is the same as if the row type was specified explicitly. */ if (create_info->row_type == ROW_TYPE_NOT_USED) { /* ALTER TABLE without explicit row type */ create_info->row_type= table->s->row_type; - /* - We have to mark the row type as used, as otherwise the engine may - change the row format in update_create_info(). - */ - create_info->used_fields|= HA_CREATE_USED_ROW_FORMAT; - explicit_used_fields|= HA_CREATE_USED_ROW_FORMAT; + } + else + { + /* ALTER TABLE with specific row type */ + create_info->used_fields |= HA_CREATE_USED_ROW_FORMAT; } DBUG_PRINT("info", ("old type: %s new type: %s", - ha_resolve_storage_engine_name(old_db_type), - ha_resolve_storage_engine_name(new_db_type))); - if (ha_check_storage_engine_flag(old_db_type, HTON_ALTER_NOT_SUPPORTED) || - ha_check_storage_engine_flag(new_db_type, HTON_ALTER_NOT_SUPPORTED)) + ha_resolve_storage_engine_name(table->s->db_type()), + ha_resolve_storage_engine_name(create_info->db_type))); + if (ha_check_storage_engine_flag(table->s->db_type(), HTON_ALTER_NOT_SUPPORTED)) { DBUG_PRINT("info", ("doesn't support alter")); - my_error(ER_ILLEGAL_HA, MYF(0), table_name); - goto err; + my_error(ER_ILLEGAL_HA, MYF(0), hton_name(table->s->db_type())->str, + alter_ctx.db, alter_ctx.table_name); + DBUG_RETURN(true); + } + + if (ha_check_storage_engine_flag(create_info->db_type, + HTON_ALTER_NOT_SUPPORTED)) + { + DBUG_PRINT("info", ("doesn't support alter")); + my_error(ER_ILLEGAL_HA, MYF(0), hton_name(create_info->db_type)->str, + alter_ctx.new_db, alter_ctx.new_name); + DBUG_RETURN(true); } if (table->s->tmp_table == NO_TMP_TABLE) mysql_audit_alter_table(thd, table_list); - - thd_proc_info(thd, "setup"); - if (!(alter_info->flags & ~(ALTER_RENAME | ALTER_KEYS_ONOFF)) && - !table->s->tmp_table) // no need to touch frm - { - switch (alter_info->keys_onoff) { - case LEAVE_AS_IS: - break; - case ENABLE: - if (wait_while_table_is_used(thd, table, extra_func, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)) - goto err; - DEBUG_SYNC(thd,"alter_table_enable_indexes"); - error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); - table->s->allow_access_to_protected_table(); - break; - case DISABLE: - if (wait_while_table_is_used(thd, table, extra_func, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)) - goto err; - error=table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); - table->s->allow_access_to_protected_table(); - break; - default: - DBUG_ASSERT(FALSE); - error= 0; - break; - } - if (error == HA_ERR_WRONG_COMMAND) - { - error= 0; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), - table->alias.c_ptr()); - } - if (!error && (new_name != table_name || new_db != db)) - { - thd_proc_info(thd, "rename"); - /* - Then do a 'simple' rename of the table. First we need to close all - instances of 'source' table. - Note that if wait_while_table_is_used() returns error here (i.e. if - this thread was killed) then it must be that previous step of - simple rename did nothing and therefore we can safely return - without additional clean-up. - */ - if (wait_while_table_is_used(thd, table, extra_func, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)) - goto err; - close_all_tables_for_name(thd, table->s, HA_EXTRA_PREPARE_FOR_RENAME); - /* - Then, we want check once again that target table does not exist. - Actually the order of these two steps does not matter since - earlier we took exclusive metadata lock on the target table, so - we do them in this particular order only to be consistent with 5.0, - in which we don't take this lock and where this order really matters. - TODO: Investigate if we need this access() check at all. - */ - if (!access(new_name_buff,F_OK)) - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name); - error= -1; - } - else - { - *fn_ext(new_name)=0; - if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0)) - error= -1; - else if (Table_triggers_list::change_table_name(thd, db, - alias, table_name, - new_db, new_alias)) - { - (void) mysql_rename_table(old_db_type, new_db, new_alias, db, - table_name, NO_FK_CHECKS); - error= -1; - } - } - } + THD_STAGE_INFO(thd, stage_setup); - if (error == HA_ERR_WRONG_COMMAND) - { - error= 0; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), - table->alias.c_ptr()); - } + handle_if_exists_options(thd, table, alter_info); - if (!error) - { - error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - if (!error) - my_ok(thd); - } - else if (error > 0) - { - table->file->print_error(error, MYF(0)); - error= -1; - } - table_list->table= NULL; // For query cache - query_cache_invalidate3(thd, table_list, 0); + /* + Look if we have to do anything at all. + ALTER can become NOOP after handling + the IF (NOT) EXISTS options. + */ + if (alter_info->flags == 0) + { + my_snprintf(alter_ctx.tmp_name, sizeof(alter_ctx.tmp_name), + ER_THD(thd, ER_INSERT_INFO), 0L, 0L, + thd->get_stmt_da()->current_statement_warn_count()); + my_ok(thd, 0L, 0L, alter_ctx.tmp_name); + + if (write_bin_log(thd, true, thd->query(), thd->query_length())) + DBUG_RETURN(true); - if ((thd->locked_tables_mode == LTM_LOCK_TABLES || - thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) + DBUG_RETURN(false); + } + + if (!(alter_info->flags & ~(Alter_info::ALTER_RENAME | + Alter_info::ALTER_KEYS_ONOFF)) && + alter_info->requested_algorithm != + Alter_info::ALTER_TABLE_ALGORITHM_COPY && + !table->s->tmp_table) // no need to touch frm + { + // This requires X-lock, no other lock levels supported. + if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_DEFAULT && + alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE) { - /* - Under LOCK TABLES we should adjust meta-data locks before finishing - statement. Otherwise we can rely on them being released - along with the implicit commit. - */ - if (new_name != table_name || new_db != db) - thd->mdl_context.release_all_locks_for_name(mdl_ticket); - else - mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0), + "LOCK=NONE/SHARED", "LOCK=EXCLUSIVE"); + DBUG_RETURN(true); } - DBUG_RETURN(error); + bool res= simple_rename_or_index_change(thd, table_list, + alter_info->keys_onoff, + &alter_ctx); + DBUG_RETURN(res); } /* We have to do full alter table. */ #ifdef WITH_PARTITION_STORAGE_ENGINE - if (prep_alter_part_table(thd, table, alter_info, create_info, old_db_type, - &partition_changed, - db, table_name, path, - &table_for_fast_alter_partition)) - goto err; + bool partition_changed= false; + bool fast_alter_partition= false; + { + if (prep_alter_part_table(thd, table, alter_info, create_info, + &alter_ctx, &partition_changed, + &fast_alter_partition)) + { + DBUG_RETURN(true); + } + } #endif - /* - If the old table had partitions and we are doing ALTER TABLE ... - engine= <new_engine>, the new table must preserve the original - partitioning. That means that the new engine is still the - partitioning engine, not the engine specified in the parser. - This is discovered in prep_alter_part_table, which in such case - updates create_info->db_type. - Now we need to update the stack copy of create_info->db_type, - as otherwise we won't be able to correctly move the files of the - temporary table to the result table files. - */ - new_db_type= create_info->db_type; - - if (is_index_maintenance_unique (table, alter_info)) - need_copy_table= ALTER_TABLE_DATA_CHANGED; - - if (mysql_prepare_alter_table(thd, table, create_info, alter_info)) - goto err; - - /* Remove markers set for update_create_info */ - create_info->used_fields&= ~explicit_used_fields; - - if (need_copy_table == ALTER_TABLE_METADATA_ONLY) - need_copy_table= alter_info->change_level; - - set_table_default_charset(thd, create_info, db); - if (thd->variables.old_alter_table - || (table->s->db_type() != create_info->db_type) -#ifdef WITH_PARTITION_STORAGE_ENGINE - || partition_changed -#endif - ) - need_copy_table= ALTER_TABLE_DATA_CHANGED; - else + if (mysql_prepare_alter_table(thd, table, create_info, alter_info, + &alter_ctx)) { - enum_alter_table_change_level need_copy_table_res; - /* Check how much the tables differ. */ - if (mysql_compare_tables(table, alter_info, - create_info, order_num, - &need_copy_table_res, - &key_info_buffer, - &index_drop_buffer, &index_drop_count, - &index_add_buffer, &index_add_count, - &candidate_key_count)) - goto err; - - DBUG_EXECUTE_IF("alter_table_only_metadata_change", { - if (need_copy_table_res != ALTER_TABLE_METADATA_ONLY) - goto err; }); - DBUG_EXECUTE_IF("alter_table_only_index_change", { - if (need_copy_table_res != ALTER_TABLE_INDEX_CHANGED) - goto err; }); - - if (need_copy_table == ALTER_TABLE_METADATA_ONLY) - need_copy_table= need_copy_table_res; + DBUG_RETURN(true); } - /* - If there are index changes only, try to do them in-place. "Index - changes only" means also that the handler for the table does not - change. The table is open and locked. The handler can be accessed. - */ - if (need_copy_table == ALTER_TABLE_INDEX_CHANGED) - { - int pk_changed= 0; - ulong alter_flags= 0; - ulong needed_inplace_with_read_flags= 0; - ulong needed_inplace_flags= 0; - KEY *key; - uint *idx_p; - uint *idx_end_p; - - alter_flags= table->file->alter_table_flags(alter_info->flags); - DBUG_PRINT("info", ("alter_flags: %lu", alter_flags)); - /* Check dropped indexes. */ - for (idx_p= index_drop_buffer, idx_end_p= idx_p + index_drop_count; - idx_p < idx_end_p; - idx_p++) - { - key= table->key_info + *idx_p; - DBUG_PRINT("info", ("index dropped: '%s'", key->name)); - if (key->flags & HA_NOSAME) - { - /* - Unique key. Check for "PRIMARY". - or if dropping last unique key - */ - if ((uint) (key - table->key_info) == table->s->primary_key) - { - DBUG_PRINT("info", ("Dropping primary key")); - /* Primary key. */ - needed_inplace_with_read_flags|= HA_INPLACE_DROP_PK_INDEX_NO_WRITE; - needed_inplace_flags|= HA_INPLACE_DROP_PK_INDEX_NO_READ_WRITE; - pk_changed++; - candidate_key_count--; - } - else - { - KEY_PART_INFO *part_end= key->key_part + key->key_parts; - bool is_candidate_key= true; + set_table_default_charset(thd, create_info, alter_ctx.db); - /* Non-primary unique key. */ - needed_inplace_with_read_flags|= - HA_INPLACE_DROP_UNIQUE_INDEX_NO_WRITE; - needed_inplace_flags|= HA_INPLACE_DROP_UNIQUE_INDEX_NO_READ_WRITE; + if (!opt_explicit_defaults_for_timestamp) + promote_first_timestamp_column(&alter_info->create_list); - /* - Check if all fields in key are declared - NOT NULL and adjust candidate_key_count - */ - for (KEY_PART_INFO *key_part= key->key_part; - key_part < part_end; - key_part++) - is_candidate_key= - (is_candidate_key && - (! table->field[key_part->fieldnr-1]->maybe_null())); - if (is_candidate_key) - candidate_key_count--; - } - } - else - { - /* Non-unique key. */ - needed_inplace_with_read_flags|= HA_INPLACE_DROP_INDEX_NO_WRITE; - needed_inplace_flags|= HA_INPLACE_DROP_INDEX_NO_READ_WRITE; - } - } - no_pk= ((table->s->primary_key == MAX_KEY) || - (needed_inplace_with_read_flags & - HA_INPLACE_DROP_PK_INDEX_NO_WRITE)); - /* Check added indexes. */ - for (idx_p= index_add_buffer, idx_end_p= idx_p + index_add_count; - idx_p < idx_end_p; - idx_p++) +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (fast_alter_partition) + { + /* + ALGORITHM and LOCK clauses are generally not allowed by the + parser for operations related to partitioning. + The exceptions are ALTER_PARTITION and ALTER_REMOVE_PARTITIONING. + For consistency, we report ER_ALTER_OPERATION_NOT_SUPPORTED here. + */ + if (alter_info->requested_lock != + Alter_info::ALTER_TABLE_LOCK_DEFAULT) { - key= key_info_buffer + *idx_p; - DBUG_PRINT("info", ("index added: '%s'", key->name)); - if (key->flags & HA_NOSAME) - { - /* Unique key */ - - KEY_PART_INFO *part_end= key->key_part + key->key_parts; - bool is_candidate_key= true; - - /* - Check if all fields in key are declared - NOT NULL - */ - for (KEY_PART_INFO *key_part= key->key_part; - key_part < part_end; - key_part++) - is_candidate_key= - (is_candidate_key && - (! table->field[key_part->fieldnr]->maybe_null())); - - /* - Check for "PRIMARY" - or if adding first unique key - defined on non-nullable fields - */ - - if ((!my_strcasecmp(system_charset_info, - key->name, primary_key_name)) || - (no_pk && candidate_key_count == 0 && is_candidate_key)) - { - DBUG_PRINT("info", ("Adding primary key")); - /* Primary key. */ - needed_inplace_with_read_flags|= HA_INPLACE_ADD_PK_INDEX_NO_WRITE; - needed_inplace_flags|= HA_INPLACE_ADD_PK_INDEX_NO_READ_WRITE; - pk_changed++; - no_pk= false; - } - else - { - /* Non-primary unique key. */ - needed_inplace_with_read_flags|= HA_INPLACE_ADD_UNIQUE_INDEX_NO_WRITE; - needed_inplace_flags|= HA_INPLACE_ADD_UNIQUE_INDEX_NO_READ_WRITE; - if (ignore) - { - /* - If ignore is used, we have to remove all duplicate rows, - which require a full table copy. - */ - need_copy_table= ALTER_TABLE_DATA_CHANGED; - pk_changed= 2; // Don't change need_copy_table - break; - } - } - } - else - { - /* Non-unique key. */ - needed_inplace_with_read_flags|= HA_INPLACE_ADD_INDEX_NO_WRITE; - needed_inplace_flags|= HA_INPLACE_ADD_INDEX_NO_READ_WRITE; - } + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), + "LOCK=NONE/SHARED/EXCLUSIVE", + ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION), + "LOCK=DEFAULT"); + DBUG_RETURN(true); } - - if ((candidate_key_count > 0) && - (needed_inplace_with_read_flags & HA_INPLACE_DROP_PK_INDEX_NO_WRITE)) + else if (alter_info->requested_algorithm != + Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT) { - /* - Dropped primary key when there is some other unique - not null key that should be converted to primary key - */ - needed_inplace_with_read_flags|= HA_INPLACE_ADD_PK_INDEX_NO_WRITE; - needed_inplace_flags|= HA_INPLACE_ADD_PK_INDEX_NO_READ_WRITE; - pk_changed= 2; + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), + "ALGORITHM=COPY/INPLACE", + ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION), + "ALGORITHM=DEFAULT"); + DBUG_RETURN(true); } - DBUG_PRINT("info", - ("needed_inplace_with_read_flags: 0x%lx, needed_inplace_flags: 0x%lx", - needed_inplace_with_read_flags, needed_inplace_flags)); /* - In-place add/drop index is possible only if - the primary key is not added and dropped in the same statement. - Otherwise we have to recreate the table. - need_copy_table is no-zero at this place. - - Also, in-place is not possible if we add a primary key - and drop another key in the same statement. If the drop fails, - we will not be able to revert adding of primary key. + Upgrade from MDL_SHARED_UPGRADABLE to MDL_SHARED_NO_WRITE. + Afterwards it's safe to take the table level lock. */ - if ( pk_changed < 2 ) + if (thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE, + thd->variables.lock_wait_timeout) + || lock_tables(thd, table_list, alter_ctx.tables_opened, 0)) { - if ((needed_inplace_with_read_flags & HA_INPLACE_ADD_PK_INDEX_NO_WRITE) && - index_drop_count > 0) - { - /* - Do copy, not in-place ALTER. - Avoid setting ALTER_TABLE_METADATA_ONLY. - */ - } - else if ((alter_flags & needed_inplace_with_read_flags) == - needed_inplace_with_read_flags) - { - /* All required in-place flags to allow concurrent reads are present. */ - need_copy_table= ALTER_TABLE_METADATA_ONLY; - need_lock_for_indexes= FALSE; - } - else if ((alter_flags & needed_inplace_flags) == needed_inplace_flags) - { - /* All required in-place flags are present. */ - need_copy_table= ALTER_TABLE_METADATA_ONLY; - } + DBUG_RETURN(true); } - DBUG_PRINT("info", ("need_copy_table: %u need_lock: %d", - need_copy_table, need_lock_for_indexes)); + + // In-place execution of ALTER TABLE for partitioning. + DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, + create_info, table_list, + alter_ctx.db, + alter_ctx.table_name)); } +#endif /* - better have a negative test here, instead of positive, like - alter_info->flags & ALTER_ADD_COLUMN|ALTER_ADD_INDEX|... - so that ALTER TABLE won't break when somebody will add new flag + Use copy algorithm if: + - old_alter_table system variable is set without in-place requested using + the ALGORITHM clause. + - Or if in-place is impossible for given operation. + - Changes to partitioning which were not handled by fast_alter_part_table() + needs to be handled using table copying algorithm unless the engine + supports auto-partitioning as such engines can do some changes + using in-place API. */ - if (need_copy_table == ALTER_TABLE_METADATA_ONLY) - create_info->frm_only= 1; - + if ((thd->variables.old_alter_table && + alter_info->requested_algorithm != + Alter_info::ALTER_TABLE_ALGORITHM_INPLACE) + || is_inplace_alter_impossible(table, create_info, alter_info) #ifdef WITH_PARTITION_STORAGE_ENGINE - if (table_for_fast_alter_partition) + || (partition_changed && + !(table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION)) +#endif + ) { - DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, - create_info, table_list, - db, table_name, - table_for_fast_alter_partition)); + if (alter_info->requested_algorithm == + Alter_info::ALTER_TABLE_ALGORITHM_INPLACE) + { + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0), + "ALGORITHM=INPLACE", "ALGORITHM=COPY"); + DBUG_RETURN(true); + } + alter_info->requested_algorithm= Alter_info::ALTER_TABLE_ALGORITHM_COPY; } -#endif - my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix, - current_pid, thd->thread_id); - /* Safety fix for innodb */ - if (lower_case_table_names) - my_casedn_str(files_charset_info, tmp_name); + /* + ALTER TABLE ... ENGINE to the same engine is a common way to + request table rebuild. Set ALTER_RECREATE flag to force table + rebuild. + */ + if (create_info->db_type == table->s->db_type() && + create_info->used_fields & HA_CREATE_USED_ENGINE) + alter_info->flags|= Alter_info::ALTER_RECREATE; + + /* + If the old table had partitions and we are doing ALTER TABLE ... + engine= <new_engine>, the new table must preserve the original + partitioning. This means that the new engine is still the + partitioning engine, not the engine specified in the parser. + This is discovered in prep_alter_part_table, which in such case + updates create_info->db_type. + It's therefore important that the assignment below is done + after prep_alter_part_table. + */ + handlerton *new_db_type= create_info->db_type; + handlerton *old_db_type= table->s->db_type(); + TABLE *new_table= NULL; + ha_rows copied=0,deleted=0; /* Handling of symlinked tables: @@ -6814,318 +8831,391 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, Copy data. Remove old table and symlinks. */ - if (!strcmp(db, new_db)) // Ignore symlink if db changed + char index_file[FN_REFLEN], data_file[FN_REFLEN]; + + if (!alter_ctx.is_database_changed()) { if (create_info->index_file_name) { /* Fix index_file_name to have 'tmp_name' as basename */ - strmov(index_file, tmp_name); + strmov(index_file, alter_ctx.tmp_name); create_info->index_file_name=fn_same(index_file, - create_info->index_file_name, - 1); + create_info->index_file_name, + 1); } if (create_info->data_file_name) { /* Fix data_file_name to have 'tmp_name' as basename */ - strmov(data_file, tmp_name); + strmov(data_file, alter_ctx.tmp_name); create_info->data_file_name=fn_same(data_file, - create_info->data_file_name, - 1); + create_info->data_file_name, + 1); } } else + { + /* Ignore symlink if db is changed. */ create_info->data_file_name=create_info->index_file_name=0; + } DEBUG_SYNC(thd, "alter_table_before_create_table_no_lock"); - DBUG_EXECUTE_IF("sleep_before_create_table_no_lock", - my_sleep(100000);); + /* We can abort alter table for any table type */ + thd->abort_on_warning= !ignore && thd->is_strict_mode(); + /* - Create a table with a temporary name. - With create_info->frm_only == 1 this creates a .frm file only and - we keep the original row format. + Create .FRM for new version of table with a temporary name. We don't log the statement, it will be logged later. + + Keep information about keys in newly created table as it + will be used later to construct Alter_inplace_info object + and by fill_alter_inplace_info() call. */ - if (need_copy_table == ALTER_TABLE_METADATA_ONLY) - { - DBUG_ASSERT(create_info->frm_only); - /* Ensure we keep the original table format */ - create_info->table_options= ((create_info->table_options & - ~HA_OPTION_PACK_RECORD) | - (table->s->db_create_options & - HA_OPTION_PACK_RECORD)); - } + KEY *key_info; + uint key_count; + /* + Remember if the new definition has new VARCHAR column; + create_info->varchar will be reset in create_table_impl()/ + mysql_prepare_create_table(). + */ + bool varchar= create_info->varchar; + LEX_CUSTRING frm= {0,0}; + tmp_disable_binlog(thd); create_info->options|=HA_CREATE_TMP_ALTER; - error= mysql_create_table_no_lock(thd, new_db, tmp_name, - create_info, - alter_info, - 1, 0, NULL); + error= create_table_impl(thd, + alter_ctx.db, alter_ctx.table_name, + alter_ctx.new_db, alter_ctx.tmp_name, + alter_ctx.get_tmp_path(), + thd->lex->create_info, create_info, alter_info, + C_ALTER_TABLE_FRM_ONLY, NULL, + &key_info, &key_count, &frm); reenable_binlog(thd); + thd->abort_on_warning= false; if (error) - goto err; + { + my_free(const_cast<uchar*>(frm.str)); + DBUG_RETURN(true); + } - /* Open the table if we need to copy the data. */ - DBUG_PRINT("info", ("need_copy_table: %u", need_copy_table)); - if (need_copy_table != ALTER_TABLE_METADATA_ONLY) + /* Remember that we have not created table in storage engine yet. */ + bool no_ha_table= true; + + if (alter_info->requested_algorithm != Alter_info::ALTER_TABLE_ALGORITHM_COPY) { - if (table->s->tmp_table) + Alter_inplace_info ha_alter_info(create_info, alter_info, + key_info, key_count, + IF_PARTITIONING(thd->work_part_info, NULL), + ignore); + TABLE *altered_table= NULL; + bool use_inplace= true; + + /* Fill the Alter_inplace_info structure. */ + if (fill_alter_inplace_info(thd, table, varchar, &ha_alter_info)) + goto err_new_table_cleanup; + + if (ha_alter_info.handler_flags == 0) { - Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH | - MYSQL_OPEN_FOR_REPAIR | - MYSQL_LOCK_IGNORE_TIMEOUT)); - TABLE_LIST tbl; - bzero((void*) &tbl, sizeof(tbl)); - tbl.db= new_db; - tbl.table_name= tbl.alias= tmp_name; - /* Table is in thd->temporary_tables */ - (void) open_table(thd, &tbl, thd->mem_root, &ot_ctx); - new_table= tbl.table; + /* + No-op ALTER, no need to call handler API functions. + + If this code path is entered for an ALTER statement that + should not be a real no-op, new handler flags should be added + and fill_alter_inplace_info() adjusted. + + Note that we can end up here if an ALTER statement has clauses + that cancel each other out (e.g. ADD/DROP identically index). + + Also note that we ignore the LOCK clause here. + + TODO don't create the frm in the first place + */ + const char *path= alter_ctx.get_tmp_path(); + table->file->ha_create_partitioning_metadata(path, NULL, CHF_DELETE_FLAG); + deletefrm(path); + my_free(const_cast<uchar*>(frm.str)); + goto end_inplace; + } + + // We assume that the table is non-temporary. + DBUG_ASSERT(!table->s->tmp_table); + + if (!(altered_table= open_table_uncached(thd, new_db_type, &frm, + alter_ctx.get_tmp_path(), + alter_ctx.new_db, + alter_ctx.tmp_name, + true, false))) + goto err_new_table_cleanup; + + /* Set markers for fields in TABLE object for altered table. */ + update_altered_table(ha_alter_info, altered_table); + + /* + Mark all columns in 'altered_table' as used to allow usage + of its record[0] buffer and Field objects during in-place + ALTER TABLE. + */ + altered_table->column_bitmaps_set_no_signal(&altered_table->s->all_set, + &altered_table->s->all_set); + restore_record(altered_table, s->default_values); // Create empty record + if (altered_table->default_field && altered_table->update_default_fields()) + goto err_new_table_cleanup; + + // Ask storage engine whether to use copy or in-place + enum_alter_inplace_result inplace_supported= + table->file->check_if_supported_inplace_alter(altered_table, + &ha_alter_info); + + switch (inplace_supported) { + case HA_ALTER_INPLACE_EXCLUSIVE_LOCK: + // If SHARED lock and no particular algorithm was requested, use COPY. + if (alter_info->requested_lock == + Alter_info::ALTER_TABLE_LOCK_SHARED && + alter_info->requested_algorithm == + Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT) + { + use_inplace= false; + } + // Otherwise, if weaker lock was requested, report errror. + else if (alter_info->requested_lock == + Alter_info::ALTER_TABLE_LOCK_NONE || + alter_info->requested_lock == + Alter_info::ALTER_TABLE_LOCK_SHARED) + { + ha_alter_info.report_unsupported_error("LOCK=NONE/SHARED", + "LOCK=EXCLUSIVE"); + close_temporary_table(thd, altered_table, true, false); + goto err_new_table_cleanup; + } + break; + case HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE: + case HA_ALTER_INPLACE_SHARED_LOCK: + // If weaker lock was requested, report errror. + if (alter_info->requested_lock == + Alter_info::ALTER_TABLE_LOCK_NONE) + { + ha_alter_info.report_unsupported_error("LOCK=NONE", "LOCK=SHARED"); + close_temporary_table(thd, altered_table, true, false); + goto err_new_table_cleanup; + } + break; + case HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE: + case HA_ALTER_INPLACE_NO_LOCK: + break; + case HA_ALTER_INPLACE_NOT_SUPPORTED: + // If INPLACE was requested, report error. + if (alter_info->requested_algorithm == + Alter_info::ALTER_TABLE_ALGORITHM_INPLACE) + { + ha_alter_info.report_unsupported_error("ALGORITHM=INPLACE", + "ALGORITHM=COPY"); + close_temporary_table(thd, altered_table, true, false); + goto err_new_table_cleanup; + } + // COPY with LOCK=NONE is not supported, no point in trying. + if (alter_info->requested_lock == + Alter_info::ALTER_TABLE_LOCK_NONE) + { + ha_alter_info.report_unsupported_error("LOCK=NONE", "LOCK=SHARED"); + close_temporary_table(thd, altered_table, true, false); + goto err_new_table_cleanup; + } + // Otherwise use COPY + use_inplace= false; + break; + case HA_ALTER_ERROR: + default: + close_temporary_table(thd, altered_table, true, false); + goto err_new_table_cleanup; + } + + if (use_inplace) + { + table->s->frm_image= &frm; + int res= mysql_inplace_alter_table(thd, table_list, table, altered_table, + &ha_alter_info, inplace_supported, + &target_mdl_request, &alter_ctx); + my_free(const_cast<uchar*>(frm.str)); + + if (res) + DBUG_RETURN(true); + + goto end_inplace; } else { - char path[FN_REFLEN + 1]; - /* table is a normal table: Create temporary table in same directory */ - build_table_filename(path, sizeof(path) - 1, new_db, tmp_name, "", - FN_IS_TMP); - /* Open our intermediate table. */ - new_table= open_table_uncached(thd, path, new_db, tmp_name, TRUE); + close_temporary_table(thd, altered_table, true, false); } - if (!new_table) + } + + /* ALTER TABLE using copy algorithm. */ + + /* Check if ALTER TABLE is compatible with foreign key definitions. */ + if (fk_prepare_copy_alter_table(thd, table, alter_info, &alter_ctx)) + goto err_new_table_cleanup; + + if (!table->s->tmp_table) + { + // COPY algorithm doesn't work with concurrent writes. + if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE) + { + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), + "LOCK=NONE", + ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY), + "LOCK=SHARED"); + goto err_new_table_cleanup; + } + + // If EXCLUSIVE lock is requested, upgrade already. + if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE && + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto err_new_table_cleanup; + /* - Note: In case of MERGE table, we do not attach children. We do not - copy data for MERGE tables. Only the children have data. + Otherwise upgrade to SHARED_NO_WRITE. + Note that under LOCK TABLES, we will already have SHARED_NO_READ_WRITE. */ + if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE && + thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE, + thd->variables.lock_wait_timeout)) + goto err_new_table_cleanup; + + DEBUG_SYNC(thd, "alter_table_copy_after_lock_upgrade"); + } + + // It's now safe to take the table level lock. + if (lock_tables(thd, table_list, alter_ctx.tables_opened, 0)) + goto err_new_table_cleanup; + + if (ha_create_table(thd, alter_ctx.get_tmp_path(), + alter_ctx.new_db, alter_ctx.tmp_name, + create_info, &frm)) + goto err_new_table_cleanup; + + /* Mark that we have created table in storage engine. */ + no_ha_table= false; + + if (create_info->tmp_table()) + { + if (!open_table_uncached(thd, new_db_type, &frm, + alter_ctx.get_tmp_path(), + alter_ctx.new_db, alter_ctx.tmp_name, + true, true)) + goto err_new_table_cleanup; } - /* Check if we can do the ALTER TABLE as online */ - if (require_online) + /* Open the table since we need to copy the data. */ + if (table->s->tmp_table != NO_TMP_TABLE) { - if (index_add_count || index_drop_count || - (new_table && - !(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER))) - { - my_error(ER_CANT_DO_ONLINE, MYF(0), "ALTER"); + TABLE_LIST tbl; + tbl.init_one_table(alter_ctx.new_db, strlen(alter_ctx.new_db), + alter_ctx.tmp_name, strlen(alter_ctx.tmp_name), + alter_ctx.tmp_name, TL_READ_NO_INSERT); + /* Table is in thd->temporary_tables */ + if (open_temporary_table(thd, &tbl)) goto err_new_table_cleanup; + new_table= tbl.table; + DBUG_ASSERT(new_table); + } + else + { + /* table is a normal table: Create temporary table in same directory */ + /* Open our intermediate table. */ + new_table= open_table_uncached(thd, new_db_type, &frm, + alter_ctx.get_tmp_path(), + alter_ctx.new_db, alter_ctx.tmp_name, + true, true); + if (!new_table) + goto err_new_table_cleanup; + + /* + Normally, an attempt to modify an FK parent table will cause + FK children to be prelocked, so the table-being-altered cannot + be modified by a cascade FK action, because ALTER holds a lock + and prelocking will wait. + + But if a new FK is being added by this very ALTER, then the target + table is not locked yet (it's a temporary table). So, we have to + lock FK parents explicitly. + */ + if (alter_info->flags & Alter_info::ADD_FOREIGN_KEY) + { + List <FOREIGN_KEY_INFO> fk_list; + List_iterator<FOREIGN_KEY_INFO> fk_list_it(fk_list); + FOREIGN_KEY_INFO *fk; + + /* tables_opened can be > 1 only for MERGE tables */ + DBUG_ASSERT(tables_opened == 1); + DBUG_ASSERT(&table_list->next_global == thd->lex->query_tables_last); + + new_table->file->get_foreign_key_list(thd, &fk_list); + while ((fk= fk_list_it++)) + { + MDL_request mdl_request; + + if (lower_case_table_names) + { + char buf[NAME_LEN]; + uint len; + strmake_buf(buf, fk->referenced_db->str); + len = my_casedn_str(files_charset_info, buf); + thd->make_lex_string(fk->referenced_db, buf, len); + strmake_buf(buf, fk->referenced_table->str); + len = my_casedn_str(files_charset_info, buf); + thd->make_lex_string(fk->referenced_table, buf, len); + } + + mdl_request.init(MDL_key::TABLE, + fk->referenced_db->str, fk->referenced_table->str, + MDL_SHARED_NO_WRITE, MDL_TRANSACTION); + if (thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) + goto err_new_table_cleanup; + } } } + /* + Note: In case of MERGE table, we do not attach children. We do not + copy data for MERGE tables. Only the children have data. + */ /* Copy the data if necessary. */ thd->count_cuted_fields= CHECK_FIELD_WARN; // calc cuted fields thd->cuted_fields=0L; - copied=deleted=0; /* We do not copy data for MERGE tables. Only the children have data. MERGE tables have HA_NO_COPY_ON_ALTER set. */ - if (new_table && !(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER)) + if (!(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER)) { - /* We don't want update TIMESTAMP fields during ALTER TABLE. */ - new_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; new_table->next_number_field=new_table->found_next_number_field; + THD_STAGE_INFO(thd, stage_copy_to_tmp_table); DBUG_EXECUTE_IF("abort_copy_table", { my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); goto err_new_table_cleanup; }); - error= copy_data_between_tables(thd, table, new_table, - alter_info->create_list, ignore, - order_num, order, &copied, &deleted, - alter_info->keys_onoff, - alter_info->error_if_not_empty); + if (copy_data_between_tables(thd, table, new_table, + alter_info->create_list, ignore, + order_num, order, &copied, &deleted, + alter_info->keys_onoff, + &alter_ctx)) + goto err_new_table_cleanup; } else { - /* - Ensure that we will upgrade the metadata lock if - handler::enable/disable_indexes() will be called. - */ - if (alter_info->keys_onoff != LEAVE_AS_IS || - table->file->indexes_are_disabled()) - need_lock_for_indexes= true; - if (!table->s->tmp_table && need_lock_for_indexes && - wait_while_table_is_used(thd, table, extra_func, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)) + if (!table->s->tmp_table && + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto err_new_table_cleanup; - thd_proc_info(thd, "manage keys"); - DEBUG_SYNC(thd, "alter_table_manage_keys"); + THD_STAGE_INFO(thd, stage_manage_keys); alter_table_manage_keys(table, table->file->indexes_are_disabled(), alter_info->keys_onoff); - error= trans_commit_stmt(thd); - if (trans_commit_implicit(thd)) - error= 1; - /* - If the table was locked, allow one to still run SHOW commands against it - */ - if (table->s->protected_against_usage()) - table->s->allow_access_to_protected_table(); - } - thd->count_cuted_fields= CHECK_FIELD_IGNORE; - - if (error) - goto err_new_table_cleanup; - - /* If we did not need to copy, we might still need to add/drop indexes. */ - if (! new_table) - { - uint *key_numbers; - uint *keyno_p; - KEY *key_info; - KEY *key; - uint *idx_p; - uint *idx_end_p; - KEY_PART_INFO *key_part; - KEY_PART_INFO *part_end; - DBUG_PRINT("info", ("No new_table, checking add/drop index")); - - table->file->ha_prepare_for_alter(); - if (index_add_count) - { - /* The add_index() method takes an array of KEY structs. */ - key_info= (KEY*) thd->alloc(sizeof(KEY) * index_add_count); - key= key_info; - for (idx_p= index_add_buffer, idx_end_p= idx_p + index_add_count; - idx_p < idx_end_p; - idx_p++, key++) - { - /* Copy the KEY struct. */ - *key= key_info_buffer[*idx_p]; - /* Fix the key parts. */ - part_end= key->key_part + key->key_parts; - for (key_part= key->key_part; key_part < part_end; key_part++) - key_part->field= table->field[key_part->fieldnr]; - } - /* Add the indexes. */ - if ((error= table->file->add_index(table, key_info, index_add_count, - &add))) - { - /* Only report error if handler has not already reported an error */ - if (!thd->is_error()) - { - /* - HACK HACK HACK - Prepare the list of keys for an error message. - It must match what the engine does internally in ::add_index(). - Here we emulate what innobase_create_key_def() does. - Luckily, in 10.0 this will go away. - */ - KEY *save_key_info= table->key_info; - uint add_cnt= index_add_count, old_cnt= table->s->keys; - KEY *merged= (KEY*)thd->alloc((old_cnt + add_cnt) * sizeof(KEY)); -#define is_PK(K) (!my_strcasecmp(system_charset_info, (K)->name, "PRIMARY")) - - if (is_PK(key_info)) - { - merged[0]= key_info[0]; - if (table->key_info && is_PK(table->key_info)) - { - old_cnt--; - table->key_info++; - } - memcpy(merged + 1, table->key_info, old_cnt * sizeof(KEY)); - memcpy(merged + old_cnt + 1, key_info + 1, (add_cnt - 1) * sizeof(KEY)); - } - else - merged= key_info; - - table->key_info= merged; - table->file->print_error(error, MYF(0)); - table->key_info= save_key_info; - } - goto err_new_table_cleanup; - } - pending_inplace_add_index= true; - } - /*end of if (index_add_count)*/ - - if (index_drop_count) - { - /* Currently we must finalize add index if we also drop indexes */ - if (pending_inplace_add_index) - { - /* Committing index changes needs exclusive metadata lock. */ - DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, - table_list->db, - table_list->table_name, - MDL_EXCLUSIVE)); - if ((error= table->file->final_add_index(add, true))) - { - table->file->print_error(error, MYF(0)); - goto err_new_table_cleanup; - } - pending_inplace_add_index= false; - } - /* The prepare_drop_index() method takes an array of key numbers. */ - key_numbers= (uint*) thd->alloc(sizeof(uint) * index_drop_count); - keyno_p= key_numbers; - /* Get the number of each key. */ - for (idx_p= index_drop_buffer, idx_end_p= idx_p + index_drop_count; - idx_p < idx_end_p; - idx_p++, keyno_p++) - *keyno_p= *idx_p; - /* - Tell the handler to prepare for drop indexes. - This re-numbers the indexes to get rid of gaps. - */ - error= table->file->prepare_drop_index(table, key_numbers, - index_drop_count); - if (!error) - { - /* Tell the handler to finally drop the indexes. */ - error= table->file->final_drop_index(table); - } - - if (error) - { - table->file->print_error(error, MYF(0)); - if (index_add_count) // Drop any new indexes added. - { - /* - Temporarily set table-key_info to include information about the - indexes added above that we now need to drop. - */ - KEY *save_key_info= table->key_info; - table->key_info= key_info_buffer; - if ((error= table->file->prepare_drop_index(table, index_add_buffer, - index_add_count))) - table->file->print_error(error, MYF(0)); - else if ((error= table->file->final_drop_index(table))) - table->file->print_error(error, MYF(0)); - table->key_info= save_key_info; - } - - /* - Mark this TABLE instance as stale to avoid - out-of-sync index information. - */ - table->m_needs_reopen= true; - goto err_new_table_cleanup; - } - } - /*end of if (index_drop_count)*/ - - /* - The final .frm file is already created as a temporary file - and will be renamed to the original table name later. - */ - - /* Need to commit before a table is unlocked (NDB requirement). */ - DBUG_PRINT("info", ("Committing before unlocking table")); if (trans_commit_stmt(thd) || trans_commit_implicit(thd)) goto err_new_table_cleanup; } - /*end of if (! new_table) for add/drop index*/ - - DBUG_ASSERT(error == 0); + thd->count_cuted_fields= CHECK_FIELD_IGNORE; if (table->s->tmp_table != NO_TMP_TABLE) { - /* - In-place operations are not supported for temporary tables, so - we don't have to call final_add_index() in this case. The assert - verifies that in-place add index has not been done. - */ - DBUG_ASSERT(!pending_inplace_add_index); /* Close lock if this is a transactional table */ if (thd->lock) { @@ -7133,7 +9223,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, thd->locked_tables_mode != LTM_PRELOCKED_UNDER_LOCK_TABLES) { mysql_unlock_tables(thd, thd->lock); - thd->lock=0; + thd->lock= NULL; } else { @@ -7144,15 +9234,19 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, mysql_lock_remove(thd, thd->lock, table); } } + new_table->s->table_creation_was_logged= + table->s->table_creation_was_logged; /* Remove link to old table and rename the new one */ - close_temporary_table(thd, table, 1, 1); + close_temporary_table(thd, table, true, true); /* Should pass the 'new_name' as we store table name in the cache */ - if (rename_temporary_table(thd, new_table, new_db, new_name)) + if (rename_temporary_table(thd, new_table, + alter_ctx.new_db, alter_ctx.new_name)) goto err_new_table_cleanup; /* We don't replicate alter table statement on temporary tables */ if (!thd->is_current_stmt_binlog_format_row() && - write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - DBUG_RETURN(TRUE); + write_bin_log(thd, true, thd->query(), thd->query_length())) + DBUG_RETURN(true); + my_free(const_cast<uchar*>(frm.str)); goto end_temporary; } @@ -7161,11 +9255,9 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, not delete it! Even altough MERGE tables do not have their children attached here it is safe to call close_temporary_table(). */ - if (new_table) - { - close_temporary_table(thd, new_table, 1, 0); - new_table= 0; - } + close_temporary_table(thd, new_table, true, false); + new_table= NULL; + DEBUG_SYNC(thd, "alter_table_before_rename_result_table"); /* @@ -7185,236 +9277,147 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, (mysql_execute_command()) to release metadata locks. */ - thd_proc_info(thd, "rename result table"); - my_snprintf(old_name, sizeof(old_name), "%s2-%lx-%lx", tmp_file_prefix, - current_pid, thd->thread_id); - if (lower_case_table_names) - my_casedn_str(files_charset_info, old_name); + THD_STAGE_INFO(thd, stage_rename_result_table); - if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)) - { - if (pending_inplace_add_index) - { - pending_inplace_add_index= false; - table->file->final_add_index(add, false); - } - // Mark this TABLE instance as stale to avoid out-of-sync index information. - table->m_needs_reopen= true; + if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME)) goto err_new_table_cleanup; - } - if (pending_inplace_add_index) - { - pending_inplace_add_index= false; - DBUG_EXECUTE_IF("alter_table_rollback_new_index", { - table->file->final_add_index(add, false); - my_error(ER_UNKNOWN_ERROR, MYF(0)); - goto err_new_table_cleanup; - }); - if ((error= table->file->final_add_index(add, true))) - { - table->file->print_error(error, MYF(0)); - goto err_new_table_cleanup; - } - } close_all_tables_for_name(thd, table->s, - new_name != table_name || new_db != db ? - HA_EXTRA_PREPARE_FOR_RENAME : - HA_EXTRA_NOT_USED); - - error=0; - table_list->table= table= 0; /* Safety */ - save_old_db_type= old_db_type; + alter_ctx.is_table_renamed() ? + HA_EXTRA_PREPARE_FOR_RENAME: + HA_EXTRA_NOT_USED, + NULL); + table_list->table= table= NULL; /* Safety */ + my_free(const_cast<uchar*>(frm.str)); /* - This leads to the storage engine (SE) not being notified for renames in - mysql_rename_table(), because we just juggle with the FRM and nothing - more. If we have an intermediate table, then we notify the SE that - it should become the actual table. Later, we will recycle the old table. - However, in case of ALTER TABLE RENAME there might be no intermediate - table. This is when the old and new tables are compatible, according to - mysql_compare_table(). Then, we need one additional call to - mysql_rename_table() with flag NO_FRM_RENAME, which does nothing else but - actual rename in the SE and the FRM is not touched. Note that, if the - table is renamed and the SE is also changed, then an intermediate table - is created and the additional call will not take place. + Rename the old table to temporary name to have a backup in case + anything goes wrong while renaming the new table. */ - if (need_copy_table == ALTER_TABLE_METADATA_ONLY) - { - DBUG_ASSERT(new_db_type == old_db_type); - /* This type cannot happen in regular ALTER. */ - new_db_type= old_db_type= NULL; - } - if (mysql_rename_table(old_db_type, db, table_name, db, old_name, - FN_TO_IS_TMP)) + char backup_name[32]; + my_snprintf(backup_name, sizeof(backup_name), "%s2-%lx-%lx", tmp_file_prefix, + current_pid, thd->thread_id); + if (lower_case_table_names) + my_casedn_str(files_charset_info, backup_name); + if (mysql_rename_table(old_db_type, alter_ctx.db, alter_ctx.table_name, + alter_ctx.db, backup_name, FN_TO_IS_TMP)) { - error=1; - (void) quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP); + // Rename to temporary name failed, delete the new table, abort ALTER. + (void) quick_rm_table(thd, new_db_type, alter_ctx.new_db, + alter_ctx.tmp_name, FN_IS_TMP); + goto err_with_mdl; } - else if (mysql_rename_table(new_db_type, new_db, tmp_name, new_db, - new_alias, FN_FROM_IS_TMP)) + + // Rename the new table to the correct name. + if (mysql_rename_table(new_db_type, alter_ctx.new_db, alter_ctx.tmp_name, + alter_ctx.new_db, alter_ctx.new_alias, + FN_FROM_IS_TMP)) { - /* Try to get everything back. */ - error= 1; - (void) quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP); - (void) mysql_rename_table(old_db_type, db, old_name, db, alias, - FN_FROM_IS_TMP | NO_FK_CHECKS); + // Rename failed, delete the temporary table. + (void) quick_rm_table(thd, new_db_type, alter_ctx.new_db, + alter_ctx.tmp_name, FN_IS_TMP); + + // Restore the backup of the original table to the old name. + (void) mysql_rename_table(old_db_type, alter_ctx.db, backup_name, + alter_ctx.db, alter_ctx.alias, + FN_FROM_IS_TMP | NO_FK_CHECKS); + goto err_with_mdl; } - else if (new_name != table_name || new_db != db) + + // Check if we renamed the table and if so update trigger files. + if (alter_ctx.is_table_renamed()) { - if (need_copy_table == ALTER_TABLE_METADATA_ONLY && - mysql_rename_table(save_old_db_type, db, table_name, new_db, - new_alias, NO_FRM_RENAME)) + if (Table_triggers_list::change_table_name(thd, + alter_ctx.db, + alter_ctx.alias, + alter_ctx.table_name, + alter_ctx.new_db, + alter_ctx.new_alias)) { - /* Try to get everything back. */ - error= 1; - (void) quick_rm_table(new_db_type, new_db, new_alias, 0); - (void) mysql_rename_table(old_db_type, db, old_name, db, alias, + // Rename succeeded, delete the new table. + (void) quick_rm_table(thd, new_db_type, + alter_ctx.new_db, alter_ctx.new_alias, 0); + // Restore the backup of the original table to the old name. + (void) mysql_rename_table(old_db_type, alter_ctx.db, backup_name, + alter_ctx.db, alter_ctx.alias, FN_FROM_IS_TMP | NO_FK_CHECKS); + goto err_with_mdl; } - else if (Table_triggers_list::change_table_name(thd, db, alias, - table_name, new_db, - new_alias)) - { - /* Try to get everything back. */ - error= 1; - (void) quick_rm_table(new_db_type, new_db, new_alias, 0); - (void) mysql_rename_table(old_db_type, db, old_name, db, - alias, FN_FROM_IS_TMP | NO_FK_CHECKS); - /* - If we were performing "fast"/in-place ALTER TABLE we also need - to restore old name of table in storage engine as a separate - step, as the above rename affects .FRM only. - */ - if (need_copy_table == ALTER_TABLE_METADATA_ONLY) - { - (void) mysql_rename_table(save_old_db_type, new_db, new_alias, - db, table_name, - NO_FRM_RENAME | NO_FK_CHECKS); - } - } - } - - if (! error) - (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); - - if (error) - { - /* This shouldn't happen. But let us play it safe. */ - goto err_with_mdl; + rename_table_in_stat_tables(thd, alter_ctx.db,alter_ctx.alias, + alter_ctx.new_db, alter_ctx.new_alias); } - if (need_copy_table == ALTER_TABLE_METADATA_ONLY) + // ALTER TABLE succeeded, delete the backup of the old table. + if (quick_rm_table(thd, old_db_type, alter_ctx.db, backup_name, FN_IS_TMP)) { /* - Now we have to inform handler that new .FRM file is in place. - To do this we need to obtain a handler object for it. - NO need to tamper with MERGE tables. The real open is done later. + The fact that deletion of the backup failed is not critical + error, but still worth reporting as it might indicate serious + problem with server. */ - Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN); - TABLE_LIST temp_table_list; - TABLE_LIST *t_table_list; - if (new_name != table_name || new_db != db) - { - temp_table_list.init_one_table(new_db, strlen(new_db), - new_name, strlen(new_name), - new_name, TL_READ_NO_INSERT); - temp_table_list.mdl_request.ticket= target_mdl_request.ticket; - t_table_list= &temp_table_list; - } - else - { - /* - Under LOCK TABLES, we have a different mdl_lock_ticket - points to a different instance than the one set initially - to request the lock. - */ - table_list->mdl_request.ticket= mdl_ticket; - t_table_list= table_list; - } - if (open_table(thd, t_table_list, thd->mem_root, &ot_ctx)) - { - goto err_with_mdl; - } + goto err_with_mdl_after_alter; + } - /* Tell the handler that a new frm file is in place. */ - error= t_table_list->table->file->ha_create_handler_files(path, NULL, - CHF_INDEX_FLAG, - create_info); +end_inplace: - DBUG_ASSERT(thd->open_tables == t_table_list->table); - close_thread_table(thd, &thd->open_tables); - t_table_list->table= NULL; + if (thd->locked_tables_list.reopen_tables(thd, false)) + goto err_with_mdl_after_alter; - if (error) - goto err_with_mdl; - } - if (thd->locked_tables_list.reopen_tables(thd)) - goto err_with_mdl; - - thd_proc_info(thd, "end"); + THD_STAGE_INFO(thd, stage_end); - DBUG_EXECUTE_IF("sleep_alter_before_main_binlog", my_sleep(6000000);); DEBUG_SYNC(thd, "alter_table_before_main_binlog"); - ha_binlog_log_query(thd, create_info->db_type, LOGCOM_ALTER_TABLE, - thd->query(), thd->query_length(), - db, table_name); - DBUG_ASSERT(!(mysql_bin_log.is_open() && thd->is_current_stmt_binlog_format_row() && - (create_info->options & HA_LEX_CREATE_TMP_TABLE))); - if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - DBUG_RETURN(TRUE); + (create_info->tmp_table()))); + if (write_bin_log(thd, true, thd->query(), thd->query_length())) + DBUG_RETURN(true); - table_list->table=0; // For query cache - query_cache_invalidate3(thd, table_list, 0); + table_list->table= NULL; // For query cache + query_cache_invalidate3(thd, table_list, false); if (thd->locked_tables_mode == LTM_LOCK_TABLES || thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) { - if ((new_name != table_name || new_db != db)) + if (alter_ctx.is_table_renamed()) thd->mdl_context.release_all_locks_for_name(mdl_ticket); else - mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); + mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE); } end_temporary: - my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO), + my_snprintf(alter_ctx.tmp_name, sizeof(alter_ctx.tmp_name), + ER_THD(thd, ER_INSERT_INFO), (ulong) (copied + deleted), (ulong) deleted, - (ulong) thd->warning_info->statement_warn_count()); - my_ok(thd, copied + deleted, 0L, tmp_name); - DBUG_RETURN(FALSE); + (ulong) thd->get_stmt_da()->current_statement_warn_count()); + my_ok(thd, copied + deleted, 0L, alter_ctx.tmp_name); + DBUG_RETURN(false); err_new_table_cleanup: + my_free(const_cast<uchar*>(frm.str)); if (new_table) { /* close_temporary_table() frees the new_table pointer. */ - close_temporary_table(thd, new_table, 1, 1); + close_temporary_table(thd, new_table, true, true); } else - (void) quick_rm_table(new_db_type, new_db, tmp_name, - create_info->frm_only ? FN_IS_TMP | FRM_ONLY : FN_IS_TMP); + (void) quick_rm_table(thd, new_db_type, + alter_ctx.new_db, alter_ctx.tmp_name, + (FN_IS_TMP | (no_ha_table ? NO_HA_TABLE : 0)), + alter_ctx.get_tmp_path()); -err: -#ifdef WITH_PARTITION_STORAGE_ENGINE - /* If prep_alter_part_table created an intermediate table, destroy it. */ - if (table_for_fast_alter_partition) - close_temporary(table_for_fast_alter_partition, 1, 0); -#endif /* WITH_PARTITION_STORAGE_ENGINE */ /* No default value was provided for a DATE/DATETIME field, the current sql_mode doesn't allow the '0000-00-00' value and the table to be altered isn't empty. Report error here. */ - if (alter_info->error_if_not_empty && - thd->warning_info->current_row_for_warning()) + if (alter_ctx.error_if_not_empty && + thd->get_stmt_da()->current_row_for_warning()) { const char *f_val= 0; enum enum_mysql_timestamp_type t_type= MYSQL_TIMESTAMP_DATE; - switch (alter_info->datetime_field->sql_type) + switch (alter_ctx.datetime_field->sql_type) { case MYSQL_TYPE_DATE: case MYSQL_TYPE_NEWDATE: @@ -7422,6 +9425,7 @@ err: t_type= MYSQL_TIMESTAMP_DATE; break; case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: f_val= "0000-00-00 00:00:00"; t_type= MYSQL_TIMESTAMP_DATETIME; break; @@ -7430,14 +9434,18 @@ err: DBUG_ASSERT(0); } bool save_abort_on_warning= thd->abort_on_warning; - thd->abort_on_warning= TRUE; - make_truncated_value_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + thd->abort_on_warning= true; + make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN, f_val, strlength(f_val), t_type, - alter_info->datetime_field->field_name); + alter_ctx.datetime_field->field_name); thd->abort_on_warning= save_abort_on_warning; } - DBUG_RETURN(TRUE); + DBUG_RETURN(true); + +err_with_mdl_after_alter: + /* the table was altered. binlog the operation */ + write_bin_log(thd, true, thd->query(), thd->query_length()); err_with_mdl: /* @@ -7448,13 +9456,10 @@ err_with_mdl: */ thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); thd->mdl_context.release_all_locks_for_name(mdl_ticket); - DBUG_RETURN(TRUE); + DBUG_RETURN(true); } -/* Copy all rows from one table to another */ - - /** Prepare the transaction for the alter table's copy phase. @@ -7469,9 +9474,7 @@ bool mysql_trans_prepare_alter_copy_data(THD *thd) This needs to be done before external_lock. */ - if (ha_enable_transaction(thd, FALSE)) - DBUG_RETURN(TRUE); - DBUG_RETURN(FALSE); + DBUG_RETURN(ha_enable_transaction(thd, FALSE) != 0); } @@ -7482,8 +9485,12 @@ bool mysql_trans_prepare_alter_copy_data(THD *thd) bool mysql_trans_commit_alter_copy_data(THD *thd) { bool error= FALSE; + uint save_unsafe_rollback_flags; DBUG_ENTER("mysql_trans_commit_alter_copy_data"); + /* Save flags as transcommit_implicit_are_deleting_them */ + save_unsafe_rollback_flags= thd->transaction.stmt.m_unsafe_rollback_flags; + if (ha_enable_transaction(thd, TRUE)) DBUG_RETURN(TRUE); @@ -7498,21 +9505,20 @@ bool mysql_trans_commit_alter_copy_data(THD *thd) if (trans_commit_implicit(thd)) error= TRUE; + thd->transaction.stmt.m_unsafe_rollback_flags= save_unsafe_rollback_flags; DBUG_RETURN(error); } static int -copy_data_between_tables(THD *thd, TABLE *from,TABLE *to, - List<Create_field> &create, - bool ignore, +copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, + List<Create_field> &create, bool ignore, uint order_num, ORDER *order, - ha_rows *copied, - ha_rows *deleted, - enum enum_enable_or_disable keys_onoff, - bool error_if_not_empty) + ha_rows *copied, ha_rows *deleted, + Alter_info::enum_enable_or_disable keys_onoff, + Alter_table_ctx *alter_ctx) { - int error= 1, errpos= 0; + int error= 1; Copy_field *copy= NULL, *copy_end; ha_rows found_count= 0, delete_count= 0; uint length= 0; @@ -7522,40 +9528,47 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to, List<Item> fields; List<Item> all_fields; ha_rows examined_rows; + ha_rows found_rows; bool auto_increment_field_copied= 0; + bool cleanup_done= 0; ulonglong save_sql_mode= thd->variables.sql_mode; ulonglong prev_insert_id, time_to_report_progress; - List_iterator<Create_field> it(create); - Create_field *def; + Field **dfield_ptr= to->default_field; DBUG_ENTER("copy_data_between_tables"); /* Two or 3 stages; Sorting, copying data and update indexes */ - thd_progress_init(thd, 2 + test(order)); - - if (mysql_trans_prepare_alter_copy_data(thd)) - goto err; - errpos=1; + thd_progress_init(thd, 2 + MY_TEST(order)); if (!(copy= new Copy_field[to->s->fields])) - goto err; /* purecov: inspected */ + DBUG_RETURN(-1); /* purecov: inspected */ - if (to->file->ha_external_lock(thd, F_WRLCK)) - goto err; - errpos= 2; + if (mysql_trans_prepare_alter_copy_data(thd)) + { + delete [] copy; + DBUG_RETURN(-1); + } /* We need external lock before we can disable/enable keys */ + if (to->file->ha_external_lock(thd, F_WRLCK)) + { + /* Undo call to mysql_trans_prepare_alter_copy_data() */ + ha_enable_transaction(thd, TRUE); + delete [] copy; + DBUG_RETURN(-1); + } + alter_table_manage_keys(to, from->file->indexes_are_disabled(), keys_onoff); /* We can abort alter table for any table type */ - thd->abort_on_warning= !ignore && test(thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES)); + thd->abort_on_warning= !ignore && thd->is_strict_mode(); from->file->info(HA_STATUS_VARIABLE); - to->file->ha_start_bulk_insert(from->file->stats.records); - errpos= 3; - + to->file->ha_start_bulk_insert(from->file->stats.records, + ignore ? 0 : HA_CREATE_UNIQUE_INDEX_BY_SORT); + List_iterator<Create_field> it(create); + Create_field *def; copy_end=copy; + to->s->default_fields= 0; for (Field **ptr=to->field ; *ptr ; ptr++) { def=it++; @@ -7575,8 +9588,23 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to, } (copy_end++)->set(*ptr,def->field,0); } - + else + { + /* + Update the set of auto-update fields to contain only the new fields + added to the table. Only these fields should be updated automatically. + Old fields keep their current values, and therefore should not be + present in the set of autoupdate fields. + */ + if ((*ptr)->has_insert_default_function()) + { + *(dfield_ptr++)= *ptr; + ++to->s->default_fields; + } + } } + if (dfield_ptr) + *dfield_ptr= NULL; if (order) { @@ -7587,43 +9615,49 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to, my_snprintf(warn_buff, sizeof(warn_buff), "ORDER BY ignored as there is a user-defined clustered index" " in the table '%-.192s'", from->s->table_name.str); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, warn_buff); } else { from->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE), - MYF(MY_FAE | MY_ZEROFILL)); + MYF(MY_FAE | MY_ZEROFILL | + MY_THREAD_SPECIFIC)); bzero((char *) &tables, sizeof(tables)); tables.table= from; tables.alias= tables.table_name= from->s->table_name.str; tables.db= from->s->db.str; - thd_proc_info(thd, "Sorting"); + THD_STAGE_INFO(thd, stage_sorting); + Filesort_tracker dummy_tracker(false); if (thd->lex->select_lex.setup_ref_array(thd, order_num) || setup_order(thd, thd->lex->select_lex.ref_pointer_array, &tables, fields, all_fields, order) || - !(sortorder= make_unireg_sortorder(order, &length, NULL)) || + !(sortorder= make_unireg_sortorder(thd, NULL, 0, order, &length, NULL)) || (from->sort.found_records= filesort(thd, from, sortorder, length, - (SQL_SELECT *) 0, HA_POS_ERROR, - 1, &examined_rows)) == + NULL, HA_POS_ERROR, + true, + &examined_rows, &found_rows, + &dummy_tracker)) == HA_POS_ERROR) goto err; } thd_progress_next_stage(thd); } - thd_proc_info(thd, "copy to tmp table"); + THD_STAGE_INFO(thd, stage_copy_to_tmp_table); /* Tell handler that we have values for all columns in the to table */ to->use_all_columns(); to->mark_virtual_columns_for_write(TRUE); if (init_read_record(&info, thd, from, (SQL_SELECT *) 0, 1, 1, FALSE)) goto err; - errpos= 4; - if (ignore) + + if (ignore && !alter_ctx->fk_error_if_delete_row) to->file->extra(HA_EXTRA_IGNORE_DUP_KEY); - thd->warning_info->reset_current_row_for_warning(); + thd->get_stmt_da()->reset_current_row_for_warning(); restore_record(to, s->default_values); // Create empty record + if (to->default_field && to->update_default_fields()) + goto err; thd->progress.max_counter= from->file->records(); time_to_report_progress= MY_HOW_OFTEN_TO_WRITE/10; @@ -7646,7 +9680,7 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to, } /* Return error if source table isn't empty. */ - if (error_if_not_empty) + if (alter_ctx->error_if_not_empty) { error= 1; break; @@ -7675,44 +9709,67 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to, to->auto_increment_field_not_null= FALSE; if (error) { - if (!ignore || - to->file->is_fatal_error(error, HA_CHECK_DUP)) - { - if (!to->file->is_fatal_error(error, HA_CHECK_DUP)) - { - uint key_nr= to->file->get_dup_key(error); - if ((int) key_nr >= 0) - { - const char *err_msg= ER(ER_DUP_ENTRY_WITH_KEY_NAME); - if (key_nr == 0 && - (to->key_info[0].key_part[0].field->flags & - AUTO_INCREMENT_FLAG)) - err_msg= ER(ER_DUP_ENTRY_AUTOINCREMENT_CASE); - to->file->print_keydup_error(key_nr, err_msg, MYF(0)); - error= 1; - break; - } - } - - to->file->print_error(error,MYF(0)); + if (to->file->is_fatal_error(error, HA_CHECK_DUP)) + { + /* Not a duplicate key error. */ + to->file->print_error(error, MYF(0)); error= 1; break; } - to->file->restore_auto_increment(prev_insert_id); - delete_count++; + else + { + /* Duplicate key error. */ + if (alter_ctx->fk_error_if_delete_row) + { + /* + We are trying to omit a row from the table which serves as parent + in a foreign key. This might have broken referential integrity so + emit an error. Note that we can't ignore this error even if we are + executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but + doesn't allow to break unique or foreign key constraints, + */ + my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0), + alter_ctx->fk_error_id, + alter_ctx->fk_error_table); + break; + } + + if (ignore) + { + /* This ALTER IGNORE TABLE. Simply skip row and continue. */ + to->file->restore_auto_increment(prev_insert_id); + delete_count++; + } + else + { + /* Ordinary ALTER TABLE. Report duplicate key error. */ + uint key_nr= to->file->get_dup_key(error); + if ((int) key_nr >= 0) + { + const char *err_msg= ER_THD(thd, ER_DUP_ENTRY_WITH_KEY_NAME); + if (key_nr == 0 && to->s->keys > 0 && + (to->key_info[0].key_part[0].field->flags & + AUTO_INCREMENT_FLAG)) + err_msg= ER_THD(thd, ER_DUP_ENTRY_AUTOINCREMENT_CASE); + print_keydup_error(to, + key_nr >= to->s->keys ? NULL : + &to->key_info[key_nr], + err_msg, MYF(0)); + } + else + to->file->print_error(error, MYF(0)); + break; + } + } } else found_count++; - thd->warning_info->inc_current_row_for_warning(); + thd->get_stmt_da()->inc_current_row_for_warning(); } - -err: - if (errpos >= 4) - end_read_record(&info); + end_read_record(&info); free_io_cache(from); - delete [] copy; - thd_proc_info(thd, "Enabling keys"); + THD_STAGE_INFO(thd, stage_enabling_keys); thd_progress_next_stage(thd); if (error > 0) @@ -7720,22 +9777,34 @@ err: /* We are going to drop the temporary table */ to->file->extra(HA_EXTRA_PREPARE_FOR_DROP); } - if (errpos >= 3 && to->file->ha_end_bulk_insert() && error <= 0) + if (to->file->ha_end_bulk_insert() && error <= 0) { to->file->print_error(my_errno,MYF(0)); error= 1; } + cleanup_done= 1; to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); - if (errpos >= 1 && mysql_trans_commit_alter_copy_data(thd)) + if (mysql_trans_commit_alter_copy_data(thd)) error= 1; + err: thd->variables.sql_mode= save_sql_mode; thd->abort_on_warning= 0; *copied= found_count; *deleted=delete_count; to->file->ha_release_auto_increment(); - if (errpos >= 2 && to->file->ha_external_lock(thd,F_UNLCK)) + delete [] copy; + + if (!cleanup_done) + { + /* This happens if we get an error during initialzation of data */ + DBUG_ASSERT(error); + to->file->ha_end_bulk_insert(); + ha_enable_transaction(thd, TRUE); + } + + if (to->file->ha_external_lock(thd,F_UNLCK)) error=1; if (error < 0 && to->file->extra(HA_EXTRA_PREPARE_FOR_RENAME)) error= 1; @@ -7751,24 +9820,20 @@ err: mysql_recreate_table() thd Thread handler table_list Table to recreate + table_copy Recreate the table by using + ALTER TABLE COPY algorithm RETURN Like mysql_alter_table(). */ -bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list) + +bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list, bool table_copy) { HA_CREATE_INFO create_info; Alter_info alter_info; TABLE_LIST *next_table= table_list->next_global; DBUG_ENTER("mysql_recreate_table"); - /* - table_list->table has been closed and freed. Do not reference - uninitialized data. open_tables() could fail. - */ - table_list->table= NULL; - /* Same applies to MDL ticket. */ - table_list->mdl_request.ticket= NULL; /* Set lock type which is appropriate for ALTER TABLE. */ table_list->lock_type= TL_READ_NO_INSERT; /* Same applies to MDL request. */ @@ -7780,10 +9845,15 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list) create_info.row_type=ROW_TYPE_NOT_USED; create_info.default_table_charset=default_charset_info; /* Force alter table to recreate table */ - alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE); + alter_info.flags= (Alter_info::ALTER_CHANGE_COLUMN | + Alter_info::ALTER_RECREATE); + + if (table_copy) + alter_info.requested_algorithm= Alter_info::ALTER_TABLE_ALGORITHM_COPY; + bool res= mysql_alter_table(thd, NullS, NullS, &create_info, table_list, &alter_info, 0, - (ORDER *) 0, 0, 0); + (ORDER *) 0, 0); table_list->next_global= next_table; DBUG_RETURN(res); } @@ -7804,24 +9874,52 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, */ DBUG_ASSERT(! thd->in_sub_stmt); - field_list.push_back(item = new Item_empty_string("Table", NAME_LEN*2)); + field_list.push_back(item= new (thd->mem_root) + Item_empty_string(thd, "Table", NAME_LEN*2), + thd->mem_root); item->maybe_null= 1; - field_list.push_back(item= new Item_int("Checksum", (longlong) 1, - MY_INT64_NUM_DECIMAL_DIGITS)); + field_list.push_back(item= new (thd->mem_root) + Item_int(thd, "Checksum", (longlong) 1, + MY_INT64_NUM_DECIMAL_DIGITS), + thd->mem_root); item->maybe_null= 1; if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); + /* + Close all temporary tables which were pre-open to simplify + privilege checking. Clear all references to closed tables. + */ + close_thread_tables(thd); + for (table= tables; table; table= table->next_local) + table->table= NULL; + /* Open one table after the other to keep lock time as short as possible. */ for (table= tables; table; table= table->next_local) { char table_name[SAFE_NAME_LEN*2+2]; TABLE *t; + TABLE_LIST *save_next_global; strxmov(table_name, table->db ,".", table->table_name, NullS); - t= table->table= open_n_lock_single_table(thd, table, TL_READ, 0); + /* Remember old 'next' pointer and break the list. */ + save_next_global= table->next_global; + table->next_global= NULL; + table->lock_type= TL_READ; + /* Allow to open real tables only. */ + table->required_type= FRMTYPE_TABLE; + + if (open_temporary_tables(thd, table) || + open_and_lock_tables(thd, table, FALSE, 0)) + { + t= NULL; + } + else + t= table->table; + + table->next_global= save_next_global; protocol->prepare_for_resend(); protocol->store(table_name, system_charset_info); @@ -7922,11 +10020,6 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, } trans_rollback_stmt(thd); close_thread_tables(thd); - /* - Don't release metadata locks, this will be done at - statement end. - */ - table->table=0; // For query cache } if (thd->transaction_rollback_request) @@ -7969,27 +10062,47 @@ err: @retval true Engine not available/supported, error has been reported. @retval false Engine available/supported. */ -static bool check_engine(THD *thd, const char *db_name, - const char *table_name, HA_CREATE_INFO *create_info) +bool check_engine(THD *thd, const char *db_name, + const char *table_name, HA_CREATE_INFO *create_info) { DBUG_ENTER("check_engine"); handlerton **new_engine= &create_info->db_type; handlerton *req_engine= *new_engine; - bool no_substitution= - test(thd->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION); - if (!(*new_engine= ha_checktype(thd, ha_legacy_type(req_engine), - no_substitution, 1))) + handlerton *enf_engine= NULL; + bool no_substitution= thd->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION; + *new_engine= ha_checktype(thd, req_engine, no_substitution); + DBUG_ASSERT(*new_engine); + if (!*new_engine) DBUG_RETURN(true); + /* Enforced storage engine should not be used in + ALTER TABLE that does not use explicit ENGINE = x to + avoid unwanted unrelated changes.*/ + if (!(thd->lex->sql_command == SQLCOM_ALTER_TABLE && + !(create_info->used_fields & HA_CREATE_USED_ENGINE))) + enf_engine= thd->variables.enforced_table_plugin ? + plugin_hton(thd->variables.enforced_table_plugin) : NULL; + + if (enf_engine && enf_engine != *new_engine) + { + if (no_substitution) + { + const char *engine_name= ha_resolve_storage_engine_name(req_engine); + my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), engine_name, engine_name); + DBUG_RETURN(TRUE); + } + *new_engine= enf_engine; + } + if (req_engine && req_engine != *new_engine) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_WARN_USING_OTHER_HANDLER, - ER(ER_WARN_USING_OTHER_HANDLER), + ER_THD(thd, ER_WARN_USING_OTHER_HANDLER), ha_resolve_storage_engine_name(*new_engine), table_name); } - if (create_info->options & HA_LEX_CREATE_TMP_TABLE && + if (create_info->tmp_table() && ha_check_storage_engine_flag(*new_engine, HTON_TEMPORARY_NOT_SUPPORTED)) { if (create_info->used_fields & HA_CREATE_USED_ENGINE) diff --git a/sql/sql_table.h b/sql/sql_table.h index 838d763cbc8..474fe9cd90b 100644 --- a/sql/sql_table.h +++ b/sql/sql_table.h @@ -1,4 +1,5 @@ -/* Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2006, 2014, Oracle and/or its affiliates. + Copyright (c) 2011, 2014, Monty Program Ab. 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 @@ -18,20 +19,23 @@ #include "my_global.h" /* my_bool */ #include "my_sys.h" // pthread_mutex_t +#include "m_string.h" // LEX_CUSTRING class Alter_info; +class Alter_table_ctx; class Create_field; struct TABLE_LIST; class THD; struct TABLE; struct handlerton; +class handler; typedef struct st_ha_check_opt HA_CHECK_OPT; -typedef struct st_ha_create_information HA_CREATE_INFO; +struct HA_CREATE_INFO; +struct Table_specification_st; typedef struct st_key KEY; typedef struct st_key_cache KEY_CACHE; typedef struct st_lock_param_type ALTER_PARTITION_PARAM_TYPE; typedef struct st_order ORDER; -class Alter_table_change_level; enum ddl_log_entry_code { @@ -63,10 +67,19 @@ enum ddl_log_action_code DDL_LOG_REPLACE_ACTION: Rename an entity after removing the previous entry with the new name, that is replace this entry. + DDL_LOG_EXCHANGE_ACTION: + Exchange two entities by renaming them a -> tmp, b -> a, tmp -> b. */ DDL_LOG_DELETE_ACTION = 'd', DDL_LOG_RENAME_ACTION = 'r', - DDL_LOG_REPLACE_ACTION = 's' + DDL_LOG_REPLACE_ACTION = 's', + DDL_LOG_EXCHANGE_ACTION = 'e' +}; + +enum enum_ddl_log_exchange_phase { + EXCH_PHASE_NAME_TO_TEMP= 0, + EXCH_PHASE_FROM_TO_NAME= 1, + EXCH_PHASE_TEMP_TO_FROM= 2 }; @@ -75,6 +88,7 @@ typedef struct st_ddl_log_entry const char *name; const char *from_name; const char *handler_name; + const char *tmp_name; uint next_entry; uint entry_pos; enum ddl_log_entry_code entry_type; @@ -112,23 +126,23 @@ enum enum_explain_filename_mode #define WFRM_WRITE_SHADOW 1 #define WFRM_INSTALL_SHADOW 2 -#define WFRM_PACK_FRM 4 -#define WFRM_KEEP_SHARE 8 +#define WFRM_KEEP_SHARE 4 /* Flags for conversion functions. */ -#define FN_FROM_IS_TMP (1 << 0) -#define FN_TO_IS_TMP (1 << 1) -#define FN_IS_TMP (FN_FROM_IS_TMP | FN_TO_IS_TMP) -#define NO_FRM_RENAME (1 << 2) -#define FRM_ONLY (1 << 3) +static const uint FN_FROM_IS_TMP= 1 << 0; +static const uint FN_TO_IS_TMP= 1 << 1; +static const uint FN_IS_TMP= FN_FROM_IS_TMP | FN_TO_IS_TMP; +static const uint NO_FRM_RENAME= 1 << 2; +static const uint FRM_ONLY= 1 << 3; +/** Don't remove table in engine. Remove only .FRM and maybe .PAR files. */ +static const uint NO_HA_TABLE= 1 << 4; +/** Don't resolve MySQL's fake "foo.sym" symbolic directory names. */ +static const uint SKIP_SYMDIR_ACCESS= 1 << 5; /** Don't check foreign key constraints while renaming table */ -#define NO_FK_CHECKS (1 << 4) +static const uint NO_FK_CHECKS= 1 << 6; -uint filename_to_tablename(const char *from, char *to, uint to_length -#ifndef DBUG_OFF - , bool stay_quiet = false -#endif /* DBUG_OFF */ - ); +uint filename_to_tablename(const char *from, char *to, uint to_length, + bool stay_quiet = false); uint tablename_to_filename(const char *from, char *to, uint to_length); uint check_n_cut_mysql50_prefix(const char *from, char *to, uint to_length); bool check_mysql50_prefix(const char *name); @@ -136,42 +150,85 @@ uint build_table_filename(char *buff, size_t bufflen, const char *db, const char *table, const char *ext, uint flags); uint build_table_shadow_filename(char *buff, size_t bufflen, ALTER_PARTITION_PARAM_TYPE *lpt); -bool check_table_file_presence(char *old_path, char *path, const char *db, - const char *table_name, const char *alias, - bool issue_error); +uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen); bool mysql_create_table(THD *thd, TABLE_LIST *create_table, - HA_CREATE_INFO *create_info, + Table_specification_st *create_info, Alter_info *alter_info); -bool mysql_create_table_no_lock(THD *thd, const char *db, - const char *table_name, + +/* + mysql_create_table_no_lock can be called in one of the following + mutually exclusive situations: + + - Just a normal ordinary CREATE TABLE statement that explicitly + defines the table structure. + + - CREATE TABLE ... SELECT. It is special, because only in this case, + the list of fields is allowed to have duplicates, as long as one of the + duplicates comes from the select list, and the other doesn't. For + example in + + CREATE TABLE t1 (a int(5) NOT NUL) SELECT b+10 as a FROM t2; + + the list in alter_info->create_list will have two fields `a`. + + - ALTER TABLE, that creates a temporary table #sql-xxx, which will be later + renamed to replace the original table. + + - ALTER TABLE as above, but which only modifies the frm file, it only + creates an frm file for the #sql-xxx, the table in the engine is not + created. + + - Assisted discovery, CREATE TABLE statement without the table structure. + + These situations are distinguished by the following "create table mode" + values, where a CREATE ... SELECT is denoted by any non-negative number + (which should be the number of fields in the SELECT ... part), and other + cases use constants as defined below. +*/ +#define C_CREATE_SELECT(X) ((X) > 0 ? (X) : 0) +#define C_ORDINARY_CREATE 0 +#define C_ALTER_TABLE -1 +#define C_ALTER_TABLE_FRM_ONLY -2 +#define C_ASSISTED_DISCOVERY -3 + +int mysql_create_table_no_lock(THD *thd, const char *db, + const char *table_name, + Table_specification_st *create_info, + Alter_info *alter_info, bool *is_trans, + int create_table_mode); + +handler *mysql_create_frm_image(THD *thd, + const char *db, const char *table_name, HA_CREATE_INFO *create_info, Alter_info *alter_info, - bool tmp_table, uint select_field_count, - bool *is_trans); + int create_table_mode, + KEY **key_info, + uint *key_count, + LEX_CUSTRING *frm); + +int mysql_discard_or_import_tablespace(THD *thd, + TABLE_LIST *table_list, + bool discard); + bool mysql_prepare_alter_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, - Alter_info *alter_info); + Alter_info *alter_info, + Alter_table_ctx *alter_ctx); bool mysql_trans_prepare_alter_copy_data(THD *thd); bool mysql_trans_commit_alter_copy_data(THD *thd); bool mysql_alter_table(THD *thd, char *new_db, char *new_name, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, Alter_info *alter_info, - uint order_num, ORDER *order, bool ignore, - bool require_online); + uint order_num, ORDER *order, bool ignore); bool mysql_compare_tables(TABLE *table, Alter_info *alter_info, HA_CREATE_INFO *create_info, - uint order_num, - Alter_table_change_level *need_copy_table, - KEY **key_info_buffer, - uint **index_drop_buffer, uint *index_drop_count, - uint **index_add_buffer, uint *index_add_count, - uint *candidate_key_count); -bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list); + bool *metadata_equal); +bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list, bool table_copy); bool mysql_create_like_table(THD *thd, TABLE_LIST *table, TABLE_LIST *src_table, - HA_CREATE_INFO *create_info); + Table_specification_st *create_info); bool mysql_rename_table(handlerton *base, const char *old_db, const char * old_name, const char *new_db, const char * new_name, uint flags); @@ -185,14 +242,17 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, my_bool drop_temporary); int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, bool drop_temporary, bool drop_view, - bool log_query); -bool quick_rm_table(handlerton *base,const char *db, - const char *table_name, uint flags); + bool log_query, bool dont_free_locks); +bool log_drop_table(THD *thd, const char *db_name, size_t db_name_length, + const char *table_name, size_t table_name_length, + bool temporary_table); +bool quick_rm_table(THD *thd, handlerton *base, const char *db, + const char *table_name, uint flags, + const char *table_path=0); void close_cached_table(THD *thd, TABLE *table); void sp_prepare_create_field(THD *thd, Create_field *sql_field); int prepare_create_field(Create_field *sql_field, uint *blob_columns, - int *timestamps, int *timestamps_with_niladic, longlong table_flags); CHARSET_INFO* get_sql_field_charset(Create_field *sql_field, HA_CREATE_INFO *create_info); @@ -211,7 +271,9 @@ bool sync_ddl_log(); void release_ddl_log(); void execute_ddl_log_recovery(); bool execute_ddl_log_entry(THD *thd, uint first_entry); -bool check_duplicate_warning(THD *thd, char *msg, ulong length); + +template<typename T> class List; +void promote_first_timestamp_column(List<Create_field> *column_definitions); /* These prototypes where under INNODB_COMPATIBILITY_HOOKS. @@ -223,4 +285,6 @@ uint explain_filename(THD* thd, const char *from, char *to, uint to_length, extern MYSQL_PLUGIN_IMPORT const char *primary_key_name; extern mysql_mutex_t LOCK_gdl; +bool check_engine(THD *, const char *, const char *, HA_CREATE_INFO *); + #endif /* SQL_TABLE_INCLUDED */ diff --git a/sql/sql_tablespace.cc b/sql/sql_tablespace.cc index 3f6daf7a9ec..8b9e14e5a18 100644 --- a/sql/sql_tablespace.cc +++ b/sql/sql_tablespace.cc @@ -15,6 +15,7 @@ /* drop and alter of tablespaces */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_tablespace.h" @@ -35,9 +36,9 @@ int mysql_alter_tablespace(THD *thd, st_alter_tablespace *ts_info) { hton= ha_default_handlerton(thd); if (ts_info->storage_engine != 0) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_USING_OTHER_HANDLER, - ER(ER_WARN_USING_OTHER_HANDLER), + ER_THD(thd, ER_WARN_USING_OTHER_HANDLER), hton_name(hton)->str, ts_info->tablespace_name ? ts_info->tablespace_name : ts_info->logfile_group_name); @@ -65,9 +66,9 @@ int mysql_alter_tablespace(THD *thd, st_alter_tablespace *ts_info) } else { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_ILLEGAL_HA_CREATE_OPTION, - ER(ER_ILLEGAL_HA_CREATE_OPTION), + ER_THD(thd, ER_ILLEGAL_HA_CREATE_OPTION), hton_name(hton)->str, "TABLESPACE or LOGFILE GROUP"); } diff --git a/sql/sql_test.cc b/sql/sql_test.cc index dc6bc8187ff..8e7525893eb 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -16,11 +16,11 @@ /* Write some debug info */ - +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_test.h" -#include "sql_base.h" // table_def_cache, table_cache_count, unused_tables +#include "sql_base.h" #include "sql_show.h" // calc_sum_of_all_status #include "sql_select.h" #include "keycaches.h" @@ -76,63 +76,39 @@ print_where(COND *cond,const char *info, enum_query_type query_type) /* This is for debugging purposes */ -static void print_cached_tables(void) +static my_bool print_cached_tables_callback(TDC_element *element, + void *arg __attribute__((unused))) { - uint idx,count,unused; - TABLE_SHARE *share; - TABLE *start_link, *lnk, *entry; + TABLE *entry; + + mysql_mutex_lock(&element->LOCK_table_share); + TDC_element::All_share_tables_list::Iterator it(element->all_tables); + while ((entry= it++)) + { + THD *in_use= entry->in_use; + printf("%-14.14s %-32s%6ld%8ld%6d %s\n", + entry->s->db.str, entry->s->table_name.str, element->version, + in_use ? in_use->thread_id : 0, + entry->db_stat ? 1 : 0, + in_use ? lock_descriptions[(int)entry->reginfo.lock_type] : + "Not in use"); + } + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} + +static void print_cached_tables(void) +{ compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions)); /* purecov: begin tested */ - mysql_mutex_lock(&LOCK_open); puts("DB Table Version Thread Open Lock"); - for (idx=unused=0 ; idx < table_def_cache.records ; idx++) - { - share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); + tdc_iterate(0, (my_hash_walk_action) print_cached_tables_callback, NULL, true); - I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables); - while ((entry= it++)) - { - printf("%-14.14s %-32s%6ld%8ld%6d %s\n", - entry->s->db.str, entry->s->table_name.str, entry->s->version, - entry->in_use->thread_id, entry->db_stat ? 1 : 0, - lock_descriptions[(int)entry->reginfo.lock_type]); - } - it.init(share->free_tables); - while ((entry= it++)) - { - unused++; - printf("%-14.14s %-32s%6ld%8ld%6d %s\n", - entry->s->db.str, entry->s->table_name.str, entry->s->version, - 0L, entry->db_stat ? 1 : 0, "Not in use"); - } - } - count=0; - if ((start_link=lnk=unused_tables)) - { - do - { - if (lnk != lnk->next->prev || lnk != lnk->prev->next) - { - printf("unused_links isn't linked properly\n"); - return; - } - } while (count++ < cached_open_tables() && (lnk=lnk->next) != start_link); - if (lnk != start_link) - { - printf("Unused_links aren't connected\n"); - } - } - if (count != unused) - printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count, - unused); - printf("\nCurrent refresh version: %ld\n",refresh_version); - if (my_hash_check(&table_def_cache)) - printf("Error: Table definition hash table is corrupted\n"); + printf("\nCurrent refresh version: %ld\n", tdc_refresh_version()); fflush(stdout); - mysql_mutex_unlock(&LOCK_open); /* purecov: end */ return; } @@ -246,7 +222,7 @@ TEST_join(JOIN *join) } -#define FT_KEYPART (MAX_REF_PARTS+10) +#define FT_KEYPART (MAX_FIELDS+10) static void print_keyuse(KEYUSE *keyuse) { @@ -490,7 +466,8 @@ static void display_table_locks(void) void *saved_base; DYNAMIC_ARRAY saved_table_locks; - (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), cached_open_tables() + 20,50); + (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), + tc_records() + 20, 50, MYF(0)); mysql_mutex_lock(&THR_LOCK_lock); for (list= thr_lock_thread_list; list; list= list_rest(list)) { @@ -597,7 +574,6 @@ void mysql_print_status() /* Print key cache status */ puts("\nKey caches:"); process_key_caches(print_key_cache_status, 0); - mysql_mutex_lock(&LOCK_status); printf("\nhandler status:\n\ read_key: %10lu\n\ read_next: %10lu\n\ @@ -613,14 +589,13 @@ update: %10lu\n", tmp.ha_write_count, tmp.ha_delete_count, tmp.ha_update_count); - mysql_mutex_unlock(&LOCK_status); printf("\nTable status:\n\ Opened tables: %10lu\n\ Open tables: %10lu\n\ Open files: %10lu\n\ Open streams: %10lu\n", tmp.opened_tables, - (ulong) cached_open_tables(), + (ulong) tc_records(), (ulong) my_file_opened, (ulong) my_stream_opened); @@ -636,7 +611,6 @@ Next alarm time: %lu\n", (ulong)alarm_info.next_alarm_time); #endif display_table_locks(); - fflush(stdout); #ifdef HAVE_MALLINFO struct mallinfo info= mallinfo(); printf("\nMemory status:\n\ @@ -668,4 +642,5 @@ Estimated memory (with thread stack): %ld\n", Events::dump_internal_status(); #endif puts(""); + fflush(stdout); } diff --git a/sql/sql_time.cc b/sql/sql_time.cc index 33bb9a460b0..c4d875e4178 100644 --- a/sql/sql_time.cc +++ b/sql/sql_time.cc @@ -17,11 +17,11 @@ /* Functions to handle date and time */ +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED by other includes #include "sql_time.h" -#include "tztime.h" // struct Time_zone -#include "sql_class.h" // THD, MODE_INVALID_DATES, MODE_NO_ZERO_DATE +#include "tztime.h" // struct Time_zone +#include "sql_class.h" // THD #include <m_ctype.h> @@ -106,9 +106,9 @@ uint calc_week(MYSQL_TIME *l_time, uint week_behaviour, uint *year) uint days; ulong daynr=calc_daynr(l_time->year,l_time->month,l_time->day); ulong first_daynr=calc_daynr(l_time->year,1,1); - bool monday_first= test(week_behaviour & WEEK_MONDAY_FIRST); - bool week_year= test(week_behaviour & WEEK_YEAR); - bool first_weekday= test(week_behaviour & WEEK_FIRST_WEEKDAY); + bool monday_first= MY_TEST(week_behaviour & WEEK_MONDAY_FIRST); + bool week_year= MY_TEST(week_behaviour & WEEK_YEAR); + bool first_weekday= MY_TEST(week_behaviour & WEEK_FIRST_WEEKDAY); uint weekday=calc_weekday(first_daynr, !monday_first); *year=l_time->year; @@ -218,11 +218,11 @@ bool check_date_with_warn(const MYSQL_TIME *ltime, ulonglong fuzzy_date, timestamp_type ts_type) { - int dummy_warnings; - if (check_date(ltime, fuzzy_date, &dummy_warnings)) + int unused; + if (check_date(ltime, fuzzy_date, &unused)) { ErrConvTime str(ltime); - make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, &str, ts_type, 0); return true; } @@ -239,7 +239,7 @@ adjust_time_range_with_warn(MYSQL_TIME *ltime, uint dec) if (check_time_range(ltime, dec, &warnings)) return true; if (warnings) - make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, &str, MYSQL_TIMESTAMP_TIME, NullS); return false; } @@ -279,9 +279,9 @@ to_ascii(CHARSET_INFO *cs, /* Character set-aware version of str_to_time() */ -timestamp_type +bool str_to_time(CHARSET_INFO *cs, const char *str,uint length, - MYSQL_TIME *l_time, ulonglong fuzzydate, int *warning) + MYSQL_TIME *l_time, ulonglong fuzzydate, MYSQL_TIME_STATUS *status) { char cnv[32]; if ((cs->state & MY_CS_NONASCII) != 0) @@ -289,14 +289,14 @@ str_to_time(CHARSET_INFO *cs, const char *str,uint length, length= to_ascii(cs, str, length, cnv, sizeof(cnv)); str= cnv; } - return str_to_time(str, length, l_time, fuzzydate, warning); + return str_to_time(str, length, l_time, fuzzydate, status); } /* Character set-aware version of str_to_datetime() */ -timestamp_type str_to_datetime(CHARSET_INFO *cs, - const char *str, uint length, - MYSQL_TIME *l_time, ulonglong flags, int *was_cut) +bool str_to_datetime(CHARSET_INFO *cs, const char *str, uint length, + MYSQL_TIME *l_time, ulonglong flags, + MYSQL_TIME_STATUS *status) { char cnv[32]; if ((cs->state & MY_CS_NONASCII) != 0) @@ -304,7 +304,7 @@ timestamp_type str_to_datetime(CHARSET_INFO *cs, length= to_ascii(cs, str, length, cnv, sizeof(cnv)); str= cnv; } - return str_to_datetime(str, length, l_time, flags, was_cut); + return str_to_datetime(str, length, l_time, flags, status); } @@ -316,26 +316,24 @@ timestamp_type str_to_datetime(CHARSET_INFO *cs, See description of str_to_datetime() for more information. */ -timestamp_type +bool str_to_datetime_with_warn(CHARSET_INFO *cs, const char *str, uint length, MYSQL_TIME *l_time, ulonglong flags) { - int was_cut; + MYSQL_TIME_STATUS status; THD *thd= current_thd; - timestamp_type ts_type; - - ts_type= str_to_datetime(cs, str, length, l_time, - (flags | (sql_mode_for_dates(thd))), - &was_cut); - if (was_cut || ts_type <= MYSQL_TIMESTAMP_ERROR) - make_truncated_value_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + bool ret_val= str_to_datetime(cs, str, length, l_time, flags, &status); + if (ret_val || status.warnings) + make_truncated_value_warning(thd, + ret_val ? Sql_condition::WARN_LEVEL_WARN : + Sql_condition::time_warn_level(status.warnings), str, length, flags & TIME_TIME_ONLY ? - MYSQL_TIMESTAMP_TIME : ts_type, NullS); + MYSQL_TIMESTAMP_TIME : l_time->time_type, NullS); DBUG_EXECUTE_IF("str_to_datetime_warn", - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_YES, str);); - return ts_type; + return ret_val; } @@ -386,7 +384,7 @@ static bool number_to_time_with_warn(bool neg, ulonglong nr, ulong sec_part, if (res < 0 || have_warnings) { make_truncated_value_warning(current_thd, - MYSQL_ERROR::WARN_LEVEL_WARN, str, + Sql_condition::WARN_LEVEL_WARN, str, res < 0 ? MYSQL_TIMESTAMP_ERROR : mysql_type_to_time_type(f_type), field_name); @@ -546,8 +544,7 @@ bool parse_date_time_format(timestamp_type format_type, { if (*ptr == '%' && ptr+1 != end) { - uint position; - LINT_INIT(position); + uint UNINIT_VAR(position); switch (*++ptr) { case 'y': // Year case 'Y': @@ -856,7 +853,7 @@ bool my_TIME_to_str(const MYSQL_TIME *ltime, String *str, uint dec) void make_truncated_value_warning(THD *thd, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const ErrConv *sval, timestamp_type time_type, const char *field_name) @@ -879,18 +876,18 @@ void make_truncated_value_warning(THD *thd, } if (field_name) cs->cset->snprintf(cs, warn_buff, sizeof(warn_buff), - ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), type_str, sval->ptr(), field_name, - (ulong) thd->warning_info->current_row_for_warning()); + (ulong) thd->get_stmt_da()->current_row_for_warning()); else { if (time_type > MYSQL_TIMESTAMP_ERROR) cs->cset->snprintf(cs, warn_buff, sizeof(warn_buff), - ER(ER_TRUNCATED_WRONG_VALUE), + ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), type_str, sval->ptr()); else cs->cset->snprintf(cs, warn_buff, sizeof(warn_buff), - ER(ER_WRONG_VALUE), type_str, sval->ptr()); + ER_THD(thd, ER_WRONG_VALUE), type_str, sval->ptr()); } push_warning(thd, level, ER_TRUNCATED_WRONG_VALUE, warn_buff); @@ -932,6 +929,12 @@ bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type, my_bool neg= 0; enum enum_mysql_timestamp_type time_type= ltime->time_type; + if (((ulonglong) interval.day + + (ulonglong) interval.hour / 24 + + (ulonglong) interval.minute / 24 / 60 + + (ulonglong) interval.second / 24 / 60 / 60) > MAX_DAY_NUMBER) + goto invalid_date; + if (time_type != MYSQL_TIMESTAMP_TIME) ltime->day+= calc_daynr(ltime->year, ltime->month, 1) - 1; @@ -956,6 +959,8 @@ bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type, ltime->day= 0; return 0; } + else if (ltime->neg) + goto invalid_date; if (int_type != INTERVAL_DAY) ltime->time_type= MYSQL_TIMESTAMP_DATETIME; // Return full date @@ -1010,11 +1015,14 @@ bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type, return 0; // Ok invalid_date: - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_DATETIME_FUNCTION_OVERFLOW, - ER(ER_DATETIME_FUNCTION_OVERFLOW), - ltime->time_type == MYSQL_TIMESTAMP_TIME ? - "time" : "datetime"); + { + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_DATETIME_FUNCTION_OVERFLOW, + ER_THD(thd, ER_DATETIME_FUNCTION_OVERFLOW), + ltime->time_type == MYSQL_TIMESTAMP_TIME ? + "time" : "datetime"); + } null_date: return 1; } @@ -1047,8 +1055,8 @@ null_date: */ bool -calc_time_diff(MYSQL_TIME *l_time1, MYSQL_TIME *l_time2, int l_sign, longlong *seconds_out, - long *microseconds_out) +calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2, + int l_sign, longlong *seconds_out, long *microseconds_out) { long days; bool neg; @@ -1074,13 +1082,13 @@ calc_time_diff(MYSQL_TIME *l_time1, MYSQL_TIME *l_time2, int l_sign, longlong *s (uint) l_time2->day); } - microseconds= ((longlong)days*LL(86400) + + microseconds= ((longlong)days * SECONDS_IN_24H + (longlong)(l_time1->hour*3600L + l_time1->minute*60L + l_time1->second) - l_sign*(longlong)(l_time2->hour*3600L + l_time2->minute*60L + - l_time2->second)) * LL(1000000) + + l_time2->second)) * 1000000LL + (longlong)l_time1->second_part - l_sign*(longlong)l_time2->second_part; @@ -1096,6 +1104,35 @@ calc_time_diff(MYSQL_TIME *l_time1, MYSQL_TIME *l_time2, int l_sign, longlong *s } +bool calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2, + int l_sign, MYSQL_TIME *l_time3, ulonglong fuzzydate) +{ + longlong seconds; + long microseconds; + bzero((char *) l_time3, sizeof(*l_time3)); + l_time3->neg= calc_time_diff(l_time1, l_time2, l_sign, + &seconds, µseconds); + /* + For MYSQL_TIMESTAMP_TIME only: + If first argument was negative and diff between arguments + is non-zero we need to swap sign to get proper result. + */ + if (l_time1->neg && (seconds || microseconds)) + l_time3->neg= 1 - l_time3->neg; // Swap sign of result + + /* + seconds is longlong, when casted to long it may become a small number + even if the original seconds value was too large and invalid. + as a workaround we limit seconds by a large invalid long number + ("invalid" means > TIME_MAX_SECOND) + */ + set_if_smaller(seconds, INT_MAX32); + calc_time_from_sec(l_time3, (long) seconds, microseconds); + return ((fuzzydate & TIME_NO_ZERO_DATE) && (seconds == 0) && + (microseconds == 0)); +} + + /* Compares 2 MYSQL_TIME structures @@ -1112,7 +1149,7 @@ calc_time_diff(MYSQL_TIME *l_time1, MYSQL_TIME *l_time2, int l_sign, longlong *s */ -int my_time_compare(MYSQL_TIME *a, MYSQL_TIME *b) +int my_time_compare(const MYSQL_TIME *a, const MYSQL_TIME *b) { ulonglong a_t= pack_time(a); ulonglong b_t= pack_time(b); @@ -1167,7 +1204,7 @@ make_date_with_warn(MYSQL_TIME *ltime, ulonglong fuzzy_date, { /* e.g. negative time */ ErrConvTime str(ltime); - make_truncated_value_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, &str, ts_type, 0); return true; } @@ -1195,3 +1232,165 @@ void time_to_daytime_interval(MYSQL_TIME *ltime) ltime->hour%= 24; ltime->time_type= MYSQL_TIMESTAMP_NONE; } + + +/*** Conversion from TIME to DATETIME ***/ + +/* + Simple case: TIME is within normal 24 hours internal. + Mix DATE part of ldate and TIME part of ltime together. +*/ +static void +mix_date_and_time_simple(MYSQL_TIME *ldate, const MYSQL_TIME *ltime) +{ + DBUG_ASSERT(ldate->time_type == MYSQL_TIMESTAMP_DATE || + ldate->time_type == MYSQL_TIMESTAMP_DATETIME); + ldate->hour= ltime->hour; + ldate->minute= ltime->minute; + ldate->second= ltime->second; + ldate->second_part= ltime->second_part; + ldate->time_type= MYSQL_TIMESTAMP_DATETIME; +} + + +/* + Complex case: TIME is negative or outside of the 24 hour interval. +*/ +static void +mix_date_and_time_complex(MYSQL_TIME *ldate, const MYSQL_TIME *ltime) +{ + DBUG_ASSERT(ldate->time_type == MYSQL_TIMESTAMP_DATE || + ldate->time_type == MYSQL_TIMESTAMP_DATETIME); + longlong seconds; + long days, useconds; + int sign= ltime->neg ? 1 : -1; + ldate->neg= calc_time_diff(ldate, ltime, sign, &seconds, &useconds); + + DBUG_ASSERT(!ldate->neg); + DBUG_ASSERT(ldate->year > 0); + + days= (long) (seconds / SECONDS_IN_24H); + calc_time_from_sec(ldate, seconds % SECONDS_IN_24H, useconds); + get_date_from_daynr(days, &ldate->year, &ldate->month, &ldate->day); + ldate->time_type= MYSQL_TIMESTAMP_DATETIME; +} + + +/** + Mix a date value and a time value. + + @param IN/OUT ldate Date value. + @param ltime Time value. +*/ +static void +mix_date_and_time(MYSQL_TIME *to, const MYSQL_TIME *from) +{ + if (!from->neg && from->hour < 24) + mix_date_and_time_simple(to, from); + else + mix_date_and_time_complex(to, from); +} + + +/** + Get current date in DATE format +*/ +void set_current_date(THD *thd, MYSQL_TIME *to) +{ + thd->variables.time_zone->gmt_sec_to_TIME(to, thd->query_start()); + thd->time_zone_used= 1; + datetime_to_date(to); +} + + +/** + 5.5 compatible conversion from TIME to DATETIME +*/ +static bool +time_to_datetime_old(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to) +{ + DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME); + + if (from->neg) + return true; + + /* Set the date part */ + uint day= from->hour / 24; + to->day= day % 31; + to->month= day / 31; + to->year= 0; + /* Set the time part */ + to->hour= from->hour % 24; + to->minute= from->minute; + to->second= from->second; + to->second_part= from->second_part; + /* set sign and type */ + to->neg= 0; + to->time_type= MYSQL_TIMESTAMP_DATETIME; + return false; +} + + +/** + Convert time to datetime. + + The time value is added to the current datetime value. + @param IN ltime Time value to convert from. + @param OUT ltime2 Datetime value to convert to. +*/ +bool +time_to_datetime(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to) +{ + if (thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) + return time_to_datetime_old(thd, from, to); + set_current_date(thd, to); + mix_date_and_time(to, from); + return false; +} + + +bool +time_to_datetime_with_warn(THD *thd, + const MYSQL_TIME *from, MYSQL_TIME *to, + ulonglong fuzzydate) +{ + int warn= 0; + DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME); + /* + After time_to_datetime() we need to do check_date(), as + the caller may want TIME_NO_ZERO_DATE or TIME_NO_ZERO_IN_DATE. + Note, the SQL standard time->datetime conversion mode always returns + a valid date based on CURRENT_DATE. So we need to do check_date() + only in the old mode. + */ + if (time_to_datetime(thd, from, to) || + ((thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) && + check_date(to, fuzzydate, &warn))) + { + ErrConvTime str(from); + make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN, + &str, MYSQL_TIMESTAMP_DATETIME, 0); + return true; + } + return false; +} + + +bool datetime_to_time_with_warn(THD *thd, const MYSQL_TIME *dt, + MYSQL_TIME *tm, uint dec) +{ + if (thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) + { + *tm= *dt; + datetime_to_time(tm); + return false; + } + else /* new mode */ + { + MYSQL_TIME current_date; + set_current_date(thd, ¤t_date); + calc_time_diff(dt, ¤t_date, 1, tm, 0); + } + int warnings= 0; + return check_time_range(tm, dec, &warnings); +} diff --git a/sql/sql_time.h b/sql/sql_time.h index 9becdcd4200..e0cab5cfa66 100644 --- a/sql/sql_time.h +++ b/sql/sql_time.h @@ -20,7 +20,7 @@ #include "my_global.h" /* ulong */ #include "my_time.h" #include "mysql_time.h" /* timestamp_type */ -#include "sql_error.h" /* MYSQL_ERROR */ +#include "sql_error.h" /* Sql_condition */ #include "structs.h" /* INTERVAL */ typedef enum enum_mysql_timestamp_type timestamp_type; @@ -34,15 +34,14 @@ typedef struct st_known_date_time_format KNOWN_DATE_TIME_FORMAT; ulong convert_period_to_month(ulong period); ulong convert_month_to_period(ulong month); +void set_current_date(THD *thd, MYSQL_TIME *to); bool time_to_datetime(MYSQL_TIME *ltime); void time_to_daytime_interval(MYSQL_TIME *l_time); bool get_date_from_daynr(long daynr,uint *year, uint *month, uint *day); my_time_t TIME_to_timestamp(THD *thd, const MYSQL_TIME *t, uint *error_code); -bool str_to_time_with_warn(CHARSET_INFO *cs, const char *str, uint length, - MYSQL_TIME *l_time, ulonglong fuzzydate); -timestamp_type str_to_datetime_with_warn(CHARSET_INFO *cs, const char *str, - uint length, MYSQL_TIME *l_time, - ulonglong flags); +bool str_to_datetime_with_warn(CHARSET_INFO *cs, const char *str, + uint length, MYSQL_TIME *l_time, + ulonglong flags); bool double_to_datetime_with_warn(double value, MYSQL_TIME *ltime, ulonglong fuzzydate, const char *name); @@ -53,13 +52,78 @@ bool int_to_datetime_with_warn(bool neg, ulonglong value, MYSQL_TIME *ltime, ulonglong fuzzydate, const char *name); -void make_truncated_value_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, +bool time_to_datetime(THD *thd, const MYSQL_TIME *tm, MYSQL_TIME *dt); +bool time_to_datetime_with_warn(THD *thd, + const MYSQL_TIME *tm, MYSQL_TIME *dt, + ulonglong fuzzydate); +/* + Simply truncate the YYYY-MM-DD part to 0000-00-00 + and change time_type to MYSQL_TIMESTAMP_TIME +*/ +inline void datetime_to_time(MYSQL_TIME *ltime) +{ + DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_DATE || + ltime->time_type == MYSQL_TIMESTAMP_DATETIME); + DBUG_ASSERT(ltime->neg == 0); + ltime->year= ltime->month= ltime->day= 0; + ltime->time_type= MYSQL_TIMESTAMP_TIME; +} + + +/** + Convert DATE/DATETIME to TIME(dec) + using CURRENT_DATE in a non-old mode, + or using simple truncation in old mode (OLD_MODE_ZERO_DATE_TIME_CAST). + + @param thd - the thread to get the variables.old_behaviour value from + @param dt - the DATE of DATETIME value to convert + @param[out] tm - store result here + @param dec - the desired scale. The fractional part of the result + is checked according to this parameter before returning + the conversion result. "dec" is important in the corner + cases near the max/min limits. + If the result is '838:59:59.999999' and the desired scale + is less than 6, an error is returned. + Note, dec is not important in the + OLD_MODE_ZERO_DATE_TIME_CAST old mode. + + - in case of OLD_MODE_ZERO_DATE_TIME_CAST + the TIME part is simply truncated and "false" is returned. + - otherwise, the result is calculated effectively similar to: + TIMEDIFF(dt, CAST(CURRENT_DATE AS DATETIME)) + If the difference fits into the supported TIME range, "false" is returned, + otherwise a warning is issued and "true" is returned. + + @return false - on success + @return true - on error +*/ +bool datetime_to_time_with_warn(THD *, const MYSQL_TIME *dt, + MYSQL_TIME *tm, uint dec); + + +inline void datetime_to_date(MYSQL_TIME *ltime) +{ + DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_DATE || + ltime->time_type == MYSQL_TIMESTAMP_DATETIME); + DBUG_ASSERT(ltime->neg == 0); + ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0; + ltime->time_type= MYSQL_TIMESTAMP_DATE; +} +inline void date_to_datetime(MYSQL_TIME *ltime) +{ + DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_DATE || + ltime->time_type == MYSQL_TIMESTAMP_DATETIME); + DBUG_ASSERT(ltime->neg == 0); + ltime->time_type= MYSQL_TIMESTAMP_DATETIME; +} +void make_truncated_value_warning(THD *thd, + Sql_condition::enum_warning_level level, const ErrConv *str_val, timestamp_type time_type, const char *field_name); static inline void make_truncated_value_warning(THD *thd, - MYSQL_ERROR::enum_warning_level level, const char *str_val, + Sql_condition::enum_warning_level level, const char *str_val, uint str_length, timestamp_type time_type, const char *field_name) { @@ -79,9 +143,33 @@ bool my_TIME_to_str(const MYSQL_TIME *ltime, String *str, uint dec); /* MYSQL_TIME operations */ bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type, INTERVAL interval); -bool calc_time_diff(MYSQL_TIME *l_time1, MYSQL_TIME *l_time2, int l_sign, - longlong *seconds_out, long *microseconds_out); -int my_time_compare(MYSQL_TIME *a, MYSQL_TIME *b); +bool calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2, + int l_sign, longlong *seconds_out, long *microseconds_out); +/** + Calculate time difference between two MYSQL_TIME values and + store the result as an out MYSQL_TIME value in MYSQL_TIMESTAMP_TIME format. + + The result can be outside of the supported TIME range. + For example, calc_time_diff('2002-01-01 00:00:00', '2001-01-01 00:00:00') + returns '8760:00:00'. So the caller might want to do check_time_range() or + adjust_time_range_with_warn() on the result of a calc_time_diff() call. + + @param l_time1 - the minuend (TIME/DATE/DATETIME value) + @param l_time2 - the subtrahend TIME/DATE/DATETIME value + @param l_sign - +1 if absolute values are to be subtracted, + or -1 if absolute values are to be added. + @param[out] l_time3 - the result + @param fuzzydate - flags + + @return true - if TIME_NO_ZERO_DATE was passed in flags and + the result appeared to be '00:00:00.000000'. + This is important when calc_time_diff() is called + when calculating DATE_ADD(TIMEDIFF(...),...) + @return false - otherwise +*/ +bool calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2, + int lsign, MYSQL_TIME *l_time3, ulonglong fuzzydate); +int my_time_compare(const MYSQL_TIME *a, const MYSQL_TIME *b); void localtime_to_TIME(MYSQL_TIME *to, struct tm *from); void calc_time_from_sec(MYSQL_TIME *to, long seconds, long microseconds); uint calc_week(MYSQL_TIME *l_time, uint week_behaviour, uint *year); @@ -91,12 +179,14 @@ bool parse_date_time_format(timestamp_type format_type, const char *format, uint format_length, DATE_TIME_FORMAT *date_time_format); /* Character set-aware version of str_to_time() */ -timestamp_type str_to_time(CHARSET_INFO *cs, const char *str,uint length, - MYSQL_TIME *l_time, ulonglong fuzzydate, int *warning); +bool str_to_time(CHARSET_INFO *cs, const char *str,uint length, + MYSQL_TIME *l_time, ulonglong fuzzydate, + MYSQL_TIME_STATUS *status); /* Character set-aware version of str_to_datetime() */ -timestamp_type str_to_datetime(CHARSET_INFO *cs, - const char *str, uint length, - MYSQL_TIME *l_time, ulonglong flags, int *was_cut); +bool str_to_datetime(CHARSET_INFO *cs, + const char *str, uint length, + MYSQL_TIME *l_time, ulonglong flags, + MYSQL_TIME_STATUS *status); /* convenience wrapper */ inline bool parse_date_time_format(timestamp_type format_type, @@ -115,13 +205,24 @@ extern DATE_TIME_FORMAT global_time_format; extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[]; extern LEX_STRING interval_type_to_name[]; - static inline bool -non_zero_date(const MYSQL_TIME *ltime) +non_zero_hhmmssuu(const MYSQL_TIME *ltime) +{ + return ltime->hour || ltime->minute || ltime->second || ltime->second_part; +} +static inline bool +non_zero_YYMMDD(const MYSQL_TIME *ltime) { return ltime->year || ltime->month || ltime->day; } static inline bool +non_zero_date(const MYSQL_TIME *ltime) +{ + return non_zero_YYMMDD(ltime) || + (ltime->time_type == MYSQL_TIMESTAMP_DATETIME && + non_zero_hhmmssuu(ltime)); +} +static inline bool check_date(const MYSQL_TIME *ltime, ulonglong flags, int *was_cut) { return check_date(ltime, non_zero_date(ltime), flags, was_cut); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 344ebdf8407..f6dd48131bf 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -17,7 +17,7 @@ #define MYSQL_LEX 1 -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "unireg.h" #include "sp_head.h" @@ -30,7 +30,7 @@ #include "sql_table.h" // build_table_filename, // check_n_cut_mysql50_prefix #include "sql_db.h" // get_default_db_collation -#include "sql_acl.h" // *_ACL, is_acl_user +#include "sql_acl.h" // *_ACL #include "sql_handler.h" // mysql_ha_rm_tables #include "sp_cache.h" // sp_invalidate_cache #include <mysys_err.h> @@ -158,9 +158,9 @@ Trigger_creation_ctx::create(THD *thd, if (invalid_creation_ctx) { push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_WARN, + Sql_condition::WARN_LEVEL_WARN, ER_TRG_INVALID_CREATION_CTX, - ER(ER_TRG_INVALID_CREATION_CTX), + ER_THD(thd, ER_TRG_INVALID_CREATION_CTX), (const char *) db_name, (const char *) table_name); } @@ -287,8 +287,8 @@ public: Handle_old_incorrect_sql_modes_hook(char *file_path) :path(file_path) {}; - virtual bool process_unknown_string(char *&unknown_key, uchar* base, - MEM_ROOT *mem_root, char *end); + virtual bool process_unknown_string(const char *&unknown_key, uchar* base, + MEM_ROOT *mem_root, const char *end); }; @@ -299,8 +299,8 @@ public: LEX_STRING *trigger_table_arg) :path(file_path), trigger_table_value(trigger_table_arg) {}; - virtual bool process_unknown_string(char *&unknown_key, uchar* base, - MEM_ROOT *mem_root, char *end); + virtual bool process_unknown_string(const char *&unknown_key, uchar* base, + MEM_ROOT *mem_root, const char *end); private: char *path; LEX_STRING *trigger_table_value; @@ -330,9 +330,9 @@ public: virtual bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, + Sql_condition::enum_warning_level level, const char* message, - MYSQL_ERROR ** cond_hdl) + Sql_condition ** cond_hdl) { if (sql_errno != EE_OUTOFMEMORY && sql_errno != ER_OUT_OF_RESOURCES) @@ -341,11 +341,11 @@ public: m_trigger_name= &thd->lex->spname->m_name; if (m_trigger_name) my_snprintf(m_message, sizeof(m_message), - ER(ER_ERROR_IN_TRIGGER_BODY), + ER_THD(thd, ER_ERROR_IN_TRIGGER_BODY), m_trigger_name->str, message); else my_snprintf(m_message, sizeof(m_message), - ER(ER_ERROR_IN_UNKNOWN_TRIGGER_BODY), message); + ER_THD(thd, ER_ERROR_IN_UNKNOWN_TRIGGER_BODY), message); return true; } return false; @@ -435,7 +435,8 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) binlogged, so they share the same danger, so trust_function_creators applies to them too. */ - if (!trust_function_creators && mysql_bin_log.is_open() && + if (!trust_function_creators && + (WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) && !(thd->security_ctx->master_access & SUPER_ACL)) { my_error(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER, MYF(0)); @@ -444,7 +445,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) if (!create) { - bool if_exists= thd->lex->drop_if_exists; + bool if_exists= thd->lex->if_exists(); /* Protect the query table list from the temporary and potentially @@ -503,6 +504,11 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) goto end; } +#ifdef WITH_WSREP + if (thd->wsrep_exec_mode == LOCAL_STATE) + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); +#endif + /* We should have only one table in table list. */ DBUG_ASSERT(tables->next_global == 0); @@ -529,7 +535,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) /* Under LOCK TABLES we must only accept write locked tables. */ if (!(tables->table= find_table_for_mdl_upgrade(thd, tables->db, tables->table_name, - FALSE))) + NULL))) goto end; } else @@ -569,13 +575,13 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) if (result) goto end; - close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED); + close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL); /* Reopen the table if we were under LOCK TABLES. Ignore the return value for now. It's better to keep master/slave in consistent state. */ - if (thd->locked_tables_list.reopen_tables(thd)) + if (thd->locked_tables_list.reopen_tables(thd, false)) thd->clear_error(); /* @@ -596,7 +602,7 @@ end: with the implicit commit. */ if (thd->locked_tables_mode && tables && lock_upgrade_done) - mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); + mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE); /* Restore the query table list. Used only for drop trigger. */ if (!create) @@ -606,6 +612,67 @@ end: my_ok(thd); DBUG_RETURN(result); + +WSREP_ERROR_LABEL: + DBUG_RETURN(true); +} + + +/** + Build stmt_query to write it in the bin-log + and get the trigger definer. + + @param thd current thread context (including trigger definition in + LEX) + @param tables table list containing one open table for which the + trigger is created. + @param[out] stmt_query after successful return, this string contains + well-formed statement for creation this trigger. + + @param[out] trg_definer The triggger definer. + @param[out] trg_definer_holder Used as a buffer for definer. + + @note + - Assumes that trigger name is fully qualified. + - NULL-string means the following LEX_STRING instance: + { str = 0; length = 0 }. + - In other words, definer_user and definer_host should contain + simultaneously NULL-strings (non-SUID/old trigger) or valid strings + (SUID/new trigger). +*/ +static void build_trig_stmt_query(THD *thd, TABLE_LIST *tables, + String *stmt_query, + LEX_STRING *trg_definer, + char trg_definer_holder[]) +{ + LEX *lex= thd->lex; + + /* + Create a query with the full trigger definition. + The original query is not appropriate, as it can miss the DEFINER=XXX part. + */ + stmt_query->append(STRING_WITH_LEN("CREATE ")); + + if (lex->create_info.or_replace()) + stmt_query->append(STRING_WITH_LEN("OR REPLACE ")); + + if (lex->sphead->m_chistics->suid != SP_IS_NOT_SUID) + { + /* SUID trigger */ + lex->definer->set_lex_string(trg_definer, trg_definer_holder); + append_definer(thd, stmt_query, &lex->definer->user, &lex->definer->host); + } + else + { + *trg_definer= empty_lex_str; + } + + LEX_STRING stmt_definition; + stmt_definition.str= (char*) thd->lex->stmt_definition_begin; + stmt_definition.length= thd->lex->stmt_definition_end - + thd->lex->stmt_definition_begin; + trim_whitespace(thd->charset(), &stmt_definition); + stmt_query->append(stmt_definition.str, stmt_definition.length); } @@ -640,8 +707,6 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, char file_buff[FN_REFLEN], trigname_buff[FN_REFLEN]; LEX_STRING file, trigname_file; LEX_STRING *trg_def; - LEX_STRING definer_user; - LEX_STRING definer_host; ulonglong *trg_sql_mode; char trg_definer_holder[USER_HOST_BUFF_SIZE]; LEX_STRING *trg_definer; @@ -650,6 +715,8 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, LEX_STRING *trg_client_cs_name; LEX_STRING *trg_connection_cl_name; LEX_STRING *trg_db_cl_name; + sp_head *trg_body= bodies[lex->trg_chistics.event] + [lex->trg_chistics.action_time]; if (check_for_broken_triggers()) return true; @@ -659,58 +726,31 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, lex->spname->m_db.str)) { my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0)); - return 1; + return true; } - /* We don't allow creation of several triggers of the same type yet */ - if (bodies[lex->trg_chistics.event][lex->trg_chistics.action_time] != 0) + /* + We don't allow creation of several triggers of the same type yet. + If a trigger with the same type already exists: + a. Throw a ER_NOT_SUPPORTED_YET error, + if the old and the new trigger names are different; + b. Or continue, if the old and the new trigger names are the same: + - either to recreate the trigger on "CREATE OR REPLACE" + - or send a "already exists" warning on "CREATE IF NOT EXISTS" + - or send an "alredy exists" error on normal CREATE. + */ + if (trg_body != 0 && + my_strcasecmp(table_alias_charset, + trg_body->m_name.str, lex->spname->m_name.str)) { my_error(ER_NOT_SUPPORTED_YET, MYF(0), "multiple triggers with the same action time" " and event for one table"); - return 1; - } - - if (!lex->definer) - { - /* - DEFINER-clause is missing. - - If we are in slave thread, this means that we received CREATE TRIGGER - from the master, that does not support definer in triggers. So, we - should mark this trigger as non-SUID. Note that this does not happen - when we parse triggers' definitions during opening .TRG file. - LEX::definer is ignored in that case. - - Otherwise, we should use CURRENT_USER() as definer. - - NOTE: when CREATE TRIGGER statement is allowed to be executed in PS/SP, - it will be required to create the definer below in persistent MEM_ROOT - of PS/SP. - */ - - if (!thd->slave_thread) - { - if (!(lex->definer= create_default_definer(thd))) - return 1; - } + return true; } - /* - If the specified definer differs from the current user, we should check - that the current user has SUPER privilege (in order to create trigger - under another user one must have SUPER privilege). - */ - - if (lex->definer && - (strcmp(lex->definer->user.str, thd->security_ctx->priv_user) || - my_strcasecmp(system_charset_info, - lex->definer->host.str, - thd->security_ctx->priv_host))) - { - if (check_global_access(thd, SUPER_ACL)) - return TRUE; - } + if (sp_process_definer(thd)) + return true; /* Let us check if all references to fields in old/new versions of row in @@ -739,7 +779,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, if (!trg_field->fixed && trg_field->fix_fields(thd, (Item **)0)) - return 1; + return true; } /* @@ -760,8 +800,30 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, /* Use the filesystem to enforce trigger namespace constraints. */ if (!access(trigname_buff, F_OK)) { - my_error(ER_TRG_ALREADY_EXISTS, MYF(0)); - return 1; + if (lex->create_info.or_replace()) + { + String drop_trg_query; + drop_trg_query.append("DROP TRIGGER "); + drop_trg_query.append(lex->spname->m_name.str); + if (drop_trigger(thd, tables, &drop_trg_query)) + return 1; + } + else if (lex->create_info.if_not_exists()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_TRG_ALREADY_EXISTS, + ER_THD(thd, ER_TRG_ALREADY_EXISTS), + trigname_buff); + LEX_STRING trg_definer_tmp; + build_trig_stmt_query(thd, tables, stmt_query, + &trg_definer_tmp, trg_definer_holder); + return false; + } + else + { + my_error(ER_TRG_ALREADY_EXISTS, MYF(0)); + return true; + } } trigname.trigger_table.str= tables->table_name; @@ -769,7 +831,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, if (sql_create_definition_file(NULL, &trigname_file, &trigname_file_type, (uchar*)&trigname, trigname_file_parameters)) - return 1; + return true; /* Soon we will invalidate table object and thus Table_triggers_list object @@ -802,44 +864,6 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, *trg_sql_mode= thd->variables.sql_mode; -#ifndef NO_EMBEDDED_ACCESS_CHECKS - if (lex->definer && !is_acl_user(lex->definer->host.str, - lex->definer->user.str)) - { - push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_NO_SUCH_USER, - ER(ER_NO_SUCH_USER), - lex->definer->user.str, - lex->definer->host.str); - } -#endif /* NO_EMBEDDED_ACCESS_CHECKS */ - - if (lex->definer) - { - /* SUID trigger. */ - - definer_user= lex->definer->user; - definer_host= lex->definer->host; - - trg_definer->str= trg_definer_holder; - trg_definer->length= strxmov(trg_definer->str, definer_user.str, "@", - definer_host.str, NullS) - trg_definer->str; - } - else - { - /* non-SUID trigger. */ - - definer_user.str= 0; - definer_user.length= 0; - - definer_host.str= 0; - definer_host.length= 0; - - trg_definer->str= (char*) ""; - trg_definer->length= 0; - } - /* Fill character set information: - client character set contains charset info only; @@ -855,30 +879,8 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, lex_string_set(trg_db_cl_name, get_default_db_collation(thd, tables->db)->name); - /* - Create well-formed trigger definition query. Original query is not - appropriated, because definer-clause can be not truncated. - */ - - stmt_query->append(STRING_WITH_LEN("CREATE ")); - - if (trg_definer) - { - /* - Append definer-clause if the trigger is SUID (a usual trigger in - new MySQL versions). - */ - - append_definer(thd, stmt_query, &definer_user, &definer_host); - } - - LEX_STRING stmt_definition; - stmt_definition.str= (char*) thd->lex->stmt_definition_begin; - stmt_definition.length= thd->lex->stmt_definition_end - - thd->lex->stmt_definition_begin; - trim_whitespace(thd->charset(), & stmt_definition); - - stmt_query->append(stmt_definition.str, stmt_definition.length); + build_trig_stmt_query(thd, tables, stmt_query, + trg_definer, trg_definer_holder); trg_def->str= stmt_query->c_ptr_safe(); trg_def->length= stmt_query->length(); @@ -887,11 +889,11 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, if (!sql_create_definition_file(NULL, &file, &triggers_file_type, (uchar*)this, triggers_file_parameters)) - return 0; + return false; err_with_cleanup: mysql_file_delete(key_file_trn, trigname_buff, MYF(MY_WME)); - return 1; + return true; } @@ -1051,7 +1053,8 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables, } } - my_message(ER_TRG_DOES_NOT_EXIST, ER(ER_TRG_DOES_NOT_EXIST), MYF(0)); + my_message(ER_TRG_DOES_NOT_EXIST, ER_THD(thd, ER_TRG_DOES_NOT_EXIST), + MYF(0)); return 1; } @@ -1062,6 +1065,11 @@ Table_triggers_list::~Table_triggers_list() for (int j= 0; j < (int)TRG_ACTION_MAX; j++) delete bodies[i][j]; + /* Free blobs used in insert */ + if (record0_field) + for (Field **fld_ptr= record0_field; *fld_ptr; fld_ptr++) + (*fld_ptr)->free(); + if (record1_field) for (Field **fld_ptr= record1_field; *fld_ptr; fld_ptr++) delete *fld_ptr; @@ -1080,44 +1088,73 @@ Table_triggers_list::~Table_triggers_list() @retval True error */ -bool Table_triggers_list::prepare_record1_accessors(TABLE *table) +bool Table_triggers_list::prepare_record_accessors(TABLE *table) { - Field **fld, **old_fld; + Field **fld, **trg_fld; - if (!(record1_field= (Field **)alloc_root(&table->mem_root, - (table->s->fields + 1) * - sizeof(Field*)))) - return 1; + if ((bodies[TRG_EVENT_INSERT][TRG_ACTION_BEFORE] || + bodies[TRG_EVENT_UPDATE][TRG_ACTION_BEFORE]) + && (table->s->stored_fields != table->s->null_fields)) - for (fld= table->field, old_fld= record1_field; *fld; fld++, old_fld++) { - /* - QQ: it is supposed that it is ok to use this function for field - cloning... - */ - if (!(*old_fld= (*fld)->new_field(&table->mem_root, table, - table == (*fld)->table))) + int null_bytes= (table->s->fields - table->s->null_fields + 7)/8; + if (!(extra_null_bitmap= (uchar*)alloc_root(&table->mem_root, null_bytes))) + return 1; + if (!(record0_field= (Field **)alloc_root(&table->mem_root, + (table->s->fields + 1) * + sizeof(Field*)))) return 1; - (*old_fld)->move_field_offset((my_ptrdiff_t)(table->record[1] - - table->record[0])); - } - *old_fld= 0; - - return 0; -} + uchar *null_ptr= extra_null_bitmap; + uchar null_bit= 1; + for (fld= table->field, trg_fld= record0_field; *fld; fld++, trg_fld++) + { + if (!(*fld)->null_ptr && !(*fld)->vcol_info) + { + Field *f; + if (!(f= *trg_fld= (*fld)->make_new_field(&table->mem_root, table, + table == (*fld)->table))) + return 1; -/** - Adjust Table_triggers_list with new TABLE pointer. + f->flags= (*fld)->flags; + f->null_ptr= null_ptr; + f->null_bit= null_bit; + if (null_bit == 128) + null_ptr++, null_bit= 1; + else + null_bit*= 2; + } + else + *trg_fld= *fld; + } + *trg_fld= 0; + DBUG_ASSERT(null_ptr <= extra_null_bitmap + null_bytes); + bzero(extra_null_bitmap, null_bytes); + } + else + record0_field= table->field; - @param new_table new pointer to TABLE instance -*/ + if (bodies[TRG_EVENT_UPDATE][TRG_ACTION_BEFORE] || + bodies[TRG_EVENT_UPDATE][TRG_ACTION_AFTER] || + bodies[TRG_EVENT_DELETE][TRG_ACTION_BEFORE] || + bodies[TRG_EVENT_DELETE][TRG_ACTION_AFTER]) + { + if (!(record1_field= (Field **)alloc_root(&table->mem_root, + (table->s->fields + 1) * + sizeof(Field*)))) + return 1; -void Table_triggers_list::set_table(TABLE *new_table) -{ - trigger_table= new_table; - for (Field **field= new_table->triggers->record1_field ; *field ; field++) - (*field)->init(new_table); + for (fld= table->field, trg_fld= record1_field; *fld; fld++, trg_fld++) + { + if (!(*trg_fld= (*fld)->make_new_field(&table->mem_root, table, + table == (*fld)->table))) + return 1; + (*trg_fld)->move_field_offset((my_ptrdiff_t)(table->record[1] - + table->record[0])); + } + *trg_fld= 0; + } + return 0; } @@ -1282,9 +1319,9 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, DBUG_RETURN(1); // EOM } - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_TRG_NO_CREATION_CTX, - ER(ER_TRG_NO_CREATION_CTX), + ER_THD(thd, ER_TRG_NO_CREATION_CTX), (const char*) db, (const char*) table_name); @@ -1341,19 +1378,13 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, table->triggers= triggers; status_var_increment(thd->status_var.feature_trigger); - /* - TODO: This could be avoided if there is no triggers - for UPDATE and DELETE. - */ - if (!names_only && triggers->prepare_record1_accessors(table)) - DBUG_RETURN(1); - List_iterator_fast<ulonglong> itm(triggers->definition_modes_list); List_iterator_fast<LEX_STRING> it_definer(triggers->definers_list); List_iterator_fast<LEX_STRING> it_client_cs_name(triggers->client_cs_names); List_iterator_fast<LEX_STRING> it_connection_cl_name(triggers->connection_cl_names); List_iterator_fast<LEX_STRING> it_db_cl_name(triggers->db_cl_names); - LEX *old_lex= thd->lex, lex; + LEX *old_lex= thd->lex; + LEX lex; sp_rcontext *save_spcont= thd->spcont; ulonglong save_sql_mode= thd->variables.sql_mode; LEX_STRING *on_table_name; @@ -1466,8 +1497,9 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, warning here. */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TRG_NO_DEFINER, ER(ER_TRG_NO_DEFINER), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_TRG_NO_DEFINER, + ER_THD(thd, ER_TRG_NO_DEFINER), (const char*) db, (const char*) sp->m_name.str); @@ -1559,6 +1591,9 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, thd->spcont= save_spcont; thd->variables.sql_mode= save_sql_mode; + if (!names_only && triggers->prepare_record_accessors(table)) + DBUG_RETURN(1); + DBUG_RETURN(0); err_with_lex_cleanup: @@ -1739,9 +1774,9 @@ bool add_table_for_trigger(THD *thd, if (if_exists) { push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_NOTE, + Sql_condition::WARN_LEVEL_NOTE, ER_TRG_DOES_NOT_EXIST, - ER(ER_TRG_DOES_NOT_EXIST)); + ER_THD(thd, ER_TRG_DOES_NOT_EXIST)); *table= NULL; @@ -1783,8 +1818,8 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name) bool result= 0; DBUG_ENTER("drop_all_triggers"); - bzero(&table, sizeof(table)); - init_sql_alloc(&table.mem_root, 8192, 0); + table.reset(); + init_sql_alloc(&table.mem_root, 8192, 0, MYF(0)); if (Table_triggers_list::check_n_load(thd, db, name, &table, 1)) { @@ -2003,8 +2038,8 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, LEX_STRING *err_trigname; DBUG_ENTER("change_table_name"); - bzero(&table, sizeof(table)); - init_sql_alloc(&table.mem_root, 8192, 0); + table.reset(); + init_sql_alloc(&table.mem_root, 8192, 0, MYF(0)); /* This method interfaces the mysql server code protected by @@ -2127,12 +2162,13 @@ bool Table_triggers_list::process_triggers(THD *thd, if (old_row_is_record1) { old_field= record1_field; - new_field= trigger_table->field; + new_field= record0_field; } else { + DBUG_ASSERT(event == TRG_EVENT_DELETE); new_field= record1_field; - old_field= trigger_table->field; + old_field= record0_field; } /* This trigger must have been processed by the pre-locking @@ -2219,6 +2255,37 @@ add_tables_and_routines_for_triggers(THD *thd, /** + Check if any of the marked fields are used in the trigger. + + @param used_fields Bitmap over fields to check + @param event_type Type of event triggers for which we are going to inspect + @param action_time Type of trigger action time we are going to inspect +*/ + +bool Table_triggers_list::is_fields_updated_in_trigger(MY_BITMAP *used_fields, + trg_event_type event_type, + trg_action_time_type action_time) +{ + Item_trigger_field *trg_field; + sp_head *sp= bodies[event_type][action_time]; + DBUG_ASSERT(used_fields->n_bits == trigger_table->s->fields); + + for (trg_field= sp->m_trg_table_fields.first; trg_field; + trg_field= trg_field->next_trg_field) + { + /* We cannot check fields which does not present in table. */ + if (trg_field->field_idx != (uint)-1) + { + if (bitmap_is_set(used_fields, trg_field->field_idx) && + trg_field->get_settable_routine_parameter()) + return true; + } + } + return false; +} + + +/** Mark fields of subject table which we read/set in its triggers as such. @@ -2258,7 +2325,7 @@ void Table_triggers_list::mark_fields_used(trg_event_type event) /** - Signals to the Table_triggers_list that a parse error has occured when + Signals to the Table_triggers_list that a parse error has occurred when reading a trigger from file. This makes the Table_triggers_list enter an error state flagged by m_has_unparseable_trigger == true. The error message will be used whenever a statement invoking or manipulating triggers is @@ -2297,10 +2364,9 @@ void Table_triggers_list::set_parse_error_message(char *error_message) #define INVALID_SQL_MODES_LENGTH 13 bool -Handle_old_incorrect_sql_modes_hook::process_unknown_string(char *&unknown_key, - uchar* base, - MEM_ROOT *mem_root, - char *end) +Handle_old_incorrect_sql_modes_hook:: +process_unknown_string(const char *&unknown_key, uchar* base, + MEM_ROOT *mem_root, const char *end) { DBUG_ENTER("Handle_old_incorrect_sql_modes_hook::process_unknown_string"); DBUG_PRINT("info", ("unknown key: %60s", unknown_key)); @@ -2309,13 +2375,14 @@ Handle_old_incorrect_sql_modes_hook::process_unknown_string(char *&unknown_key, unknown_key[INVALID_SQL_MODES_LENGTH] == '=' && !memcmp(unknown_key, STRING_WITH_LEN("sql_modes"))) { - char *ptr= unknown_key + INVALID_SQL_MODES_LENGTH + 1; + THD *thd= current_thd; + const char *ptr= unknown_key + INVALID_SQL_MODES_LENGTH + 1; DBUG_PRINT("info", ("sql_modes affected by BUG#14090 detected")); - push_warning_printf(current_thd, - MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_NOTE, ER_OLD_FILE_FORMAT, - ER(ER_OLD_FILE_FORMAT), + ER_THD(thd, ER_OLD_FILE_FORMAT), (char *)path, "TRIGGER"); if (get_file_options_ulllist(ptr, end, unknown_key, base, &sql_modes_parameters, mem_root)) @@ -2340,8 +2407,8 @@ Handle_old_incorrect_sql_modes_hook::process_unknown_string(char *&unknown_key, */ bool Handle_old_incorrect_trigger_table_hook:: -process_unknown_string(char *&unknown_key, uchar* base, MEM_ROOT *mem_root, - char *end) +process_unknown_string(const char *&unknown_key, uchar* base, + MEM_ROOT *mem_root, const char *end) { DBUG_ENTER("Handle_old_incorrect_trigger_table_hook::process_unknown_string"); DBUG_PRINT("info", ("unknown key: %60s", unknown_key)); @@ -2350,13 +2417,14 @@ process_unknown_string(char *&unknown_key, uchar* base, MEM_ROOT *mem_root, unknown_key[INVALID_TRIGGER_TABLE_LENGTH] == '=' && !memcmp(unknown_key, STRING_WITH_LEN("trigger_table"))) { - char *ptr= unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1; + THD *thd= current_thd; + const char *ptr= unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1; DBUG_PRINT("info", ("trigger_table affected by BUG#15921 detected")); - push_warning_printf(current_thd, - MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_NOTE, ER_OLD_FILE_FORMAT, - ER(ER_OLD_FILE_FORMAT), + ER_THD(thd, ER_OLD_FILE_FORMAT), (char *)path, "TRIGGER"); if (!(ptr= parse_escaped_string(ptr, end, mem_root, trigger_table_value))) @@ -2463,3 +2531,4 @@ bool load_table_name_for_trigger(THD *thd, DBUG_RETURN(FALSE); } + diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h index 47b1d19ae54..fa858a0582b 100644 --- a/sql/sql_trigger.h +++ b/sql/sql_trigger.h @@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <mysqld_error.h> + /* Forward declarations */ class Item_trigger_field; @@ -66,6 +68,13 @@ class Table_triggers_list: public Sql_alloc */ Item_trigger_field *trigger_fields[TRG_EVENT_MAX][TRG_ACTION_MAX]; /** + Copy of TABLE::Field array which all fields made nullable + (using extra_null_bitmap, if needed). Used for NEW values in + BEFORE INSERT/UPDATE triggers. + */ + Field **record0_field; + uchar *extra_null_bitmap; + /** Copy of TABLE::Field array with field pointers set to TABLE::record[1] buffer instead of TABLE::record[0] (used for OLD values in on UPDATE trigger and DELETE trigger when it is called for REPLACE). @@ -141,7 +150,8 @@ public: /* End of character ser context. */ Table_triggers_list(TABLE *table_arg) - :record1_field(0), trigger_table(table_arg), + :record0_field(0), extra_null_bitmap(0), record1_field(0), + trigger_table(table_arg), m_has_unparseable_trigger(false) { bzero((char *)bodies, sizeof(bodies)); @@ -195,8 +205,6 @@ public: bodies[TRG_EVENT_DELETE][TRG_ACTION_AFTER]); } - void set_table(TABLE *new_table); - void mark_fields_used(trg_event_type event); void set_parse_error_message(char *error_message); @@ -207,8 +215,20 @@ public: Query_tables_list *prelocking_ctx, TABLE_LIST *table_list); + bool is_fields_updated_in_trigger(MY_BITMAP *used_fields, + trg_event_type event_type, + trg_action_time_type action_time); + + Field **nullable_fields() { return record0_field; } + void reset_extra_null_bitmap() + { + int null_bytes= (trigger_table->s->stored_fields - + trigger_table->s->null_fields + 7)/8; + bzero(extra_null_bitmap, null_bytes); + } + private: - bool prepare_record1_accessors(TABLE *table); + bool prepare_record_accessors(TABLE *table); LEX_STRING* change_table_name_in_trignames(const char *old_db_name, const char *new_db_name, LEX_STRING *new_table_name, @@ -230,6 +250,13 @@ private: } }; +inline Field **TABLE::field_to_fill() +{ + return triggers && triggers->nullable_fields() ? triggers->nullable_fields() + : field; +} + + extern const LEX_STRING trg_action_time_type_names[]; extern const LEX_STRING trg_event_type_names[]; diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc index ed36ab428a2..57cb6df55ca 100644 --- a/sql/sql_truncate.cc +++ b/sql/sql_truncate.cc @@ -19,12 +19,12 @@ #include "sql_class.h" // THD #include "sql_base.h" // open_and_lock_tables #include "sql_table.h" // write_bin_log -#include "sql_handler.h" // mysql_ha_rm_tables #include "datadict.h" // dd_recreate_table() -#include "lock.h" // MYSQL_OPEN_TEMPORARY_ONLY +#include "lock.h" // MYSQL_OPEN_* flags #include "sql_acl.h" // DROP_ACL #include "sql_parse.h" // check_one_table_access() #include "sql_truncate.h" +#include "wsrep_mysqld.h" #include "sql_show.h" //append_identifier() @@ -193,13 +193,13 @@ fk_truncate_illegal_if_parent(THD *thd, TABLE *table) binlong the statement. */ -enum Truncate_statement::truncate_result -Truncate_statement::handler_truncate(THD *thd, TABLE_LIST *table_ref, - bool is_tmp_table) +enum Sql_cmd_truncate_table::truncate_result +Sql_cmd_truncate_table::handler_truncate(THD *thd, TABLE_LIST *table_ref, + bool is_tmp_table) { int error= 0; - uint flags; - DBUG_ENTER("Truncate_statement::handler_truncate"); + uint flags= 0; + DBUG_ENTER("Sql_cmd_truncate_table::handler_truncate"); /* Can't recreate, the engine must mechanically delete all rows @@ -207,9 +207,7 @@ Truncate_statement::handler_truncate(THD *thd, TABLE_LIST *table_ref, */ /* If it is a temporary table, no need to take locks. */ - if (is_tmp_table) - flags= MYSQL_OPEN_TEMPORARY_ONLY; - else + if (!is_tmp_table) { /* We don't need to load triggers. */ DBUG_ASSERT(table_ref->trg_event_map == 0); @@ -224,7 +222,7 @@ Truncate_statement::handler_truncate(THD *thd, TABLE_LIST *table_ref, the MDL lock taken above and otherwise there is no way to wait for FLUSH TABLES in deadlock-free fashion. */ - flags= MYSQL_OPEN_IGNORE_FLUSH | MYSQL_OPEN_SKIP_TEMPORARY; + flags= MYSQL_OPEN_IGNORE_FLUSH; /* Even though we have an MDL lock on the table here, we don't pass MYSQL_OPEN_HAS_MDL_LOCK to open_and_lock_tables @@ -265,64 +263,6 @@ Truncate_statement::handler_truncate(THD *thd, TABLE_LIST *table_ref, /* - Close and recreate a temporary table. In case of success, - write truncate statement into the binary log if in statement - mode. - - @param thd Thread context. - @param table The temporary table. - - @retval FALSE Success. - @retval TRUE Error. -*/ - -static bool recreate_temporary_table(THD *thd, TABLE *table) -{ - bool error= TRUE; - TABLE_SHARE *share= table->s; - HA_CREATE_INFO create_info; - handlerton *table_type= table->s->db_type(); - DBUG_ENTER("recreate_temporary_table"); - - memset(&create_info, 0, sizeof(create_info)); - create_info.options|= HA_LEX_CREATE_TMP_TABLE; - - table->file->info(HA_STATUS_AUTO | HA_STATUS_NO_LOCK); - - /* - If LOCK TABLES list is not empty and contains this table - then unlock the table and remove it from this list. - */ - mysql_lock_remove(thd, thd->lock, table); - - /* Don't free share. */ - close_temporary_table(thd, table, FALSE, FALSE); - - /* - We must use share->normalized_path.str since for temporary tables it - differs from what dd_recreate_table() would generate based - on table and schema names. - */ - ha_create_table(thd, share->normalized_path.str, share->db.str, - share->table_name.str, &create_info, 1); - - if (open_table_uncached(thd, share->path.str, share->db.str, - share->table_name.str, TRUE)) - { - error= FALSE; - thd->thread_specific_used= TRUE; - } - else - rm_temporary_table(table_type, share->path.str); - - free_table_share(share); - my_free(table); - - DBUG_RETURN(error); -} - - -/* Handle locking a base table for truncate. @param[in] thd Thread context. @@ -335,11 +275,11 @@ static bool recreate_temporary_table(THD *thd, TABLE *table) @retval TRUE Error. */ -bool Truncate_statement::lock_table(THD *thd, TABLE_LIST *table_ref, - bool *hton_can_recreate) +bool Sql_cmd_truncate_table::lock_table(THD *thd, TABLE_LIST *table_ref, + bool *hton_can_recreate) { TABLE *table= NULL; - DBUG_ENTER("Truncate_statement::lock_table"); + DBUG_ENTER("Sql_cmd_truncate_table::lock_table"); /* Lock types are set in the parser. */ DBUG_ASSERT(table_ref->lock_type == TL_WRITE); @@ -362,7 +302,7 @@ bool Truncate_statement::lock_table(THD *thd, TABLE_LIST *table_ref, if (thd->locked_tables_mode) { if (!(table= find_table_for_mdl_upgrade(thd, table_ref->db, - table_ref->table_name, FALSE))) + table_ref->table_name, NULL))) DBUG_RETURN(TRUE); *hton_can_recreate= ha_check_storage_engine_flag(table->s->db_type(), @@ -374,13 +314,30 @@ bool Truncate_statement::lock_table(THD *thd, TABLE_LIST *table_ref, /* Acquire an exclusive lock. */ DBUG_ASSERT(table_ref->next_global == NULL); if (lock_table_names(thd, table_ref, NULL, - thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY)) + thd->variables.lock_wait_timeout, 0)) DBUG_RETURN(TRUE); - if (dd_check_storage_engine_flag(thd, table_ref->db, table_ref->table_name, - HTON_CAN_RECREATE, hton_can_recreate)) + handlerton *hton; + if (!ha_table_exists(thd, table_ref->db, table_ref->table_name, &hton) || + hton == view_pseudo_hton) + { + my_error(ER_NO_SUCH_TABLE, MYF(0), table_ref->db, table_ref->table_name); DBUG_RETURN(TRUE); + } + + if (!hton) + { + /* + The table exists, but its storage engine is unknown, perhaps not + loaded at the moment. We need to open and parse the frm to know the + storage engine in question, so let's proceed with the truncation and + try to open the table. This will produce the correct error message + about unknown engine. + */ + *hton_can_recreate= false; + } + else + *hton_can_recreate= hton->flags & HTON_CAN_RECREATE; } /* @@ -392,13 +349,13 @@ bool Truncate_statement::lock_table(THD *thd, TABLE_LIST *table_ref, { DEBUG_SYNC(thd, "upgrade_lock_for_truncate"); /* To remove the table from the cache we need an exclusive lock. */ - if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DROP, - TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)) + if (wait_while_table_is_used(thd, table, + *hton_can_recreate ? HA_EXTRA_PREPARE_FOR_DROP : HA_EXTRA_NOT_USED)) DBUG_RETURN(TRUE); m_ticket_downgrade= table->mdl_ticket; /* Close if table is going to be recreated. */ if (*hton_can_recreate) - close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED); + close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL); } else { @@ -425,43 +382,25 @@ bool Truncate_statement::lock_table(THD *thd, TABLE_LIST *table_ref, @retval TRUE Error. */ -bool Truncate_statement::truncate_table(THD *thd, TABLE_LIST *table_ref) +bool Sql_cmd_truncate_table::truncate_table(THD *thd, TABLE_LIST *table_ref) { int error; - TABLE *table; bool binlog_stmt; - DBUG_ENTER("Truncate_statement::truncate_table"); + DBUG_ENTER("Sql_cmd_truncate_table::truncate_table"); + + DBUG_ASSERT((!table_ref->table) || + (table_ref->table && table_ref->table->s)); /* Initialize, or reinitialize in case of reexecution (SP). */ m_ticket_downgrade= NULL; - /* Remove table from the HANDLER's hash. */ - mysql_ha_rm_tables(thd, table_ref); - /* If it is a temporary table, no need to take locks. */ - if ((table= find_temporary_table(thd, table_ref))) + if (is_temporary_table(table_ref)) { /* In RBR, the statement is not binlogged if the table is temporary. */ binlog_stmt= !thd->is_current_stmt_binlog_format_row(); - /* Note that a temporary table cannot be partitioned. */ - if (ha_check_storage_engine_flag(table->s->db_type(), HTON_CAN_RECREATE)) - { - if ((error= recreate_temporary_table(thd, table))) - binlog_stmt= FALSE; /* No need to binlog failed truncate-by-recreate. */ - - DBUG_ASSERT(! thd->transaction.stmt.modified_non_trans_table); - } - else - { - /* - The engine does not support truncate-by-recreate. Open the - table and invoke the handler truncate. In such a manner this - can in fact open several tables if it's a temporary MyISAMMRG - table. - */ - error= handler_truncate(thd, table_ref, TRUE); - } + error= handler_truncate(thd, table_ref, TRUE); /* No need to invalidate the query cache, queries with temporary @@ -474,6 +413,9 @@ bool Truncate_statement::truncate_table(THD *thd, TABLE_LIST *table_ref) { bool hton_can_recreate; + if (WSREP(thd) && + wsrep_to_isolation_begin(thd, table_ref->db, table_ref->table_name, 0)) + DBUG_RETURN(TRUE); if (lock_table(thd, table_ref, &hton_can_recreate)) DBUG_RETURN(TRUE); @@ -485,7 +427,7 @@ bool Truncate_statement::truncate_table(THD *thd, TABLE_LIST *table_ref) */ error= dd_recreate_table(thd, table_ref->db, table_ref->table_name); - if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd)) + if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd, false)) thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); /* No need to binlog a failed truncate-by-recreate. */ @@ -531,7 +473,7 @@ bool Truncate_statement::truncate_table(THD *thd, TABLE_LIST *table_ref) to a shared one. */ if (m_ticket_downgrade) - m_ticket_downgrade->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); + m_ticket_downgrade->downgrade_lock(MDL_SHARED_NO_READ_WRITE); DBUG_RETURN(error); } @@ -545,11 +487,11 @@ bool Truncate_statement::truncate_table(THD *thd, TABLE_LIST *table_ref) @return FALSE on success. */ -bool Truncate_statement::execute(THD *thd) +bool Sql_cmd_truncate_table::execute(THD *thd) { bool res= TRUE; TABLE_LIST *first_table= thd->lex->select_lex.table_list.first; - DBUG_ENTER("Truncate_statement::execute"); + DBUG_ENTER("Sql_cmd_truncate_table::execute"); if (check_one_table_access(thd, DROP_ACL, first_table)) DBUG_RETURN(res); @@ -559,4 +501,3 @@ bool Truncate_statement::execute(THD *thd) DBUG_RETURN(res); } - diff --git a/sql/sql_truncate.h b/sql/sql_truncate.h index 0280ecc4932..b8525fd6abb 100644 --- a/sql/sql_truncate.h +++ b/sql/sql_truncate.h @@ -19,9 +19,9 @@ class THD; struct TABLE_LIST; /** - Truncate_statement represents the TRUNCATE statement. + Sql_cmd_truncate_table represents the TRUNCATE statement. */ -class Truncate_statement : public Sql_statement +class Sql_cmd_truncate_table : public Sql_cmd { private: /* Set if a lock must be downgraded after truncate is done. */ @@ -29,14 +29,12 @@ private: public: /** - Constructor, used to represent a ALTER TABLE statement. - @param lex the LEX structure for this statement. + Constructor, used to represent a TRUNCATE statement. */ - Truncate_statement(LEX *lex) - : Sql_statement(lex) + Sql_cmd_truncate_table() {} - virtual ~Truncate_statement() + virtual ~Sql_cmd_truncate_table() {} /** @@ -46,6 +44,11 @@ public: */ bool execute(THD *thd); + virtual enum_sql_command sql_command_code() const + { + return SQLCOM_TRUNCATE; + } + protected: enum truncate_result{ TRUNCATE_OK=0, diff --git a/sql/sql_type.cc b/sql/sql_type.cc new file mode 100644 index 00000000000..7d52419ae18 --- /dev/null +++ b/sql/sql_type.cc @@ -0,0 +1,152 @@ +/* + Copyright (c) 2015 MariaDB Foundation. + + 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 */ + +#include "sql_type.h" +#include "sql_const.h" + +static Type_handler_tiny type_handler_tiny; +static Type_handler_short type_handler_short; +static Type_handler_long type_handler_long; +static Type_handler_longlong type_handler_longlong; +static Type_handler_int24 type_handler_int24; +static Type_handler_year type_handler_year; +static Type_handler_bit type_handler_bit; +static Type_handler_float type_handler_float; +static Type_handler_double type_handler_double; +static Type_handler_time type_handler_time; +static Type_handler_date type_handler_date; +static Type_handler_datetime type_handler_datetime; +static Type_handler_timestamp type_handler_timestamp; +static Type_handler_olddecimal type_handler_olddecimal; +static Type_handler_newdecimal type_handler_newdecimal; +static Type_handler_null type_handler_null; +static Type_handler_string type_handler_string; +static Type_handler_varchar type_handler_varchar; +static Type_handler_tiny_blob type_handler_tiny_blob; +static Type_handler_medium_blob type_handler_medium_blob; +static Type_handler_long_blob type_handler_long_blob; +static Type_handler_blob type_handler_blob; +static Type_handler_geometry type_handler_geometry; + + +/** + This method is used by: + - Item_user_var_as_out_param::field_type() + - Item_func_udf_str::field_type() + - Item_empty_string::make_field() + + TODO: type_handler_adjusted_to_max_octet_length() and string_type_handler() + provide very similar functionality, to properly choose between + VARCHAR/VARBINARY vs TEXT/BLOB variations taking into accoung maximum + possible octet length. + + We should probably get rid of either of them and use the same method + all around the code. +*/ +const Type_handler * +Type_handler::string_type_handler(uint max_octet_length) const +{ + if (max_octet_length >= 16777216) + return &type_handler_long_blob; + else if (max_octet_length >= 65536) + return &type_handler_medium_blob; + return &type_handler_varchar; +} + + +/** + This method is used by: + - Item_sum_hybrid, e.g. MAX(item), MIN(item). + - Item_func_set_user_var +*/ +const Type_handler * +Type_handler_string_result::type_handler_adjusted_to_max_octet_length( + uint max_octet_length, + CHARSET_INFO *cs) const +{ + if (max_octet_length / cs->mbmaxlen <= CONVERT_IF_BIGGER_TO_BLOB) + return &type_handler_varchar; // See also Item::too_big_for_varchar() + if (max_octet_length >= 16777216) + return &type_handler_long_blob; + else if (max_octet_length >= 65536) + return &type_handler_medium_blob; + return &type_handler_blob; +} + + +const Type_handler * +Type_handler_hybrid_field_type::get_handler_by_result_type(Item_result type) + const +{ + switch (type) { + case REAL_RESULT: return &type_handler_double; + case INT_RESULT: return &type_handler_longlong; + case DECIMAL_RESULT: return &type_handler_newdecimal; + case STRING_RESULT: return &type_handler_long_blob; + case TIME_RESULT: + case ROW_RESULT: + DBUG_ASSERT(0); + } + return &type_handler_string; +} + + +Type_handler_hybrid_field_type::Type_handler_hybrid_field_type() + :m_type_handler(&type_handler_double) +{ +} + + +const Type_handler * +Type_handler_hybrid_field_type::get_handler_by_field_type(enum_field_types type) + const +{ + switch (type) { + case MYSQL_TYPE_DECIMAL: return &type_handler_olddecimal; + case MYSQL_TYPE_NEWDECIMAL: return &type_handler_newdecimal; + case MYSQL_TYPE_TINY: return &type_handler_tiny; + case MYSQL_TYPE_SHORT: return &type_handler_short; + case MYSQL_TYPE_LONG: return &type_handler_long; + case MYSQL_TYPE_LONGLONG: return &type_handler_longlong; + case MYSQL_TYPE_INT24: return &type_handler_int24; + case MYSQL_TYPE_YEAR: return &type_handler_year; + case MYSQL_TYPE_BIT: return &type_handler_bit; + case MYSQL_TYPE_FLOAT: return &type_handler_float; + case MYSQL_TYPE_DOUBLE: return &type_handler_double; + case MYSQL_TYPE_NULL: return &type_handler_null; + case MYSQL_TYPE_VARCHAR: return &type_handler_varchar; + case MYSQL_TYPE_TINY_BLOB: return &type_handler_tiny_blob; + case MYSQL_TYPE_MEDIUM_BLOB: return &type_handler_medium_blob; + case MYSQL_TYPE_LONG_BLOB: return &type_handler_long_blob; + case MYSQL_TYPE_BLOB: return &type_handler_blob; + case MYSQL_TYPE_VAR_STRING: return &type_handler_varchar; // Map to VARCHAR + case MYSQL_TYPE_STRING: return &type_handler_string; + case MYSQL_TYPE_ENUM: return &type_handler_varchar; // Map to VARCHAR + case MYSQL_TYPE_SET: return &type_handler_varchar; // Map to VARCHAR + case MYSQL_TYPE_GEOMETRY: return &type_handler_geometry; + case MYSQL_TYPE_TIMESTAMP: return &type_handler_timestamp; + case MYSQL_TYPE_TIMESTAMP2: return &type_handler_timestamp; + case MYSQL_TYPE_DATE: return &type_handler_date; + case MYSQL_TYPE_TIME: return &type_handler_time; + case MYSQL_TYPE_TIME2: return &type_handler_time; + case MYSQL_TYPE_DATETIME: return &type_handler_datetime; + case MYSQL_TYPE_DATETIME2: return &type_handler_datetime; + case MYSQL_TYPE_NEWDATE: return &type_handler_date; + }; + DBUG_ASSERT(0); + return &type_handler_string; +} + diff --git a/sql/sql_type.h b/sql/sql_type.h new file mode 100644 index 00000000000..f5a42e8d97d --- /dev/null +++ b/sql/sql_type.h @@ -0,0 +1,348 @@ +#ifndef SQL_TYPE_H_INCLUDED +#define SQL_TYPE_H_INCLUDED +/* + Copyright (c) 2015 MariaDB Foundation. + + 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 */ + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + + +#include "mysqld.h" + +class Type_handler +{ +protected: + const Type_handler *string_type_handler(uint max_octet_length) const; +public: + virtual enum_field_types field_type() const= 0; + virtual Item_result result_type() const= 0; + virtual Item_result cmp_type() const= 0; + virtual const Type_handler* + type_handler_adjusted_to_max_octet_length(uint max_octet_length, + CHARSET_INFO *cs) const + { return this; } + virtual ~Type_handler() {} +}; + + +/*** Abstract classes for every XXX_RESULT */ + +class Type_handler_real_result: public Type_handler +{ +public: + Item_result result_type() const { return REAL_RESULT; } + Item_result cmp_type() const { return REAL_RESULT; } + virtual ~Type_handler_real_result() {} +}; + + +class Type_handler_decimal_result: public Type_handler +{ +public: + Item_result result_type() const { return DECIMAL_RESULT; } + Item_result cmp_type() const { return DECIMAL_RESULT; } + virtual ~Type_handler_decimal_result() {}; +}; + + +class Type_handler_int_result: public Type_handler +{ +public: + Item_result result_type() const { return INT_RESULT; } + Item_result cmp_type() const { return INT_RESULT; } + virtual ~Type_handler_int_result() {} +}; + + +class Type_handler_temporal_result: public Type_handler +{ +public: + Item_result result_type() const { return STRING_RESULT; } + Item_result cmp_type() const { return TIME_RESULT; } + virtual ~Type_handler_temporal_result() {} +}; + + +class Type_handler_string_result: public Type_handler +{ +public: + Item_result result_type() const { return STRING_RESULT; } + Item_result cmp_type() const { return STRING_RESULT; } + virtual ~Type_handler_string_result() {} + const Type_handler * + type_handler_adjusted_to_max_octet_length(uint max_octet_length, + CHARSET_INFO *cs) const; +}; + + +/*** + Instantiable classes for every MYSQL_TYPE_XXX + + There are no Type_handler_xxx for the following types: + - MYSQL_TYPE_VAR_STRING (old VARCHAR) - mapped to MYSQL_TYPE_VARSTRING + - MYSQL_TYPE_ENUM - mapped to MYSQL_TYPE_VARSTRING + - MYSQL_TYPE_SET: - mapped to MYSQL_TYPE_VARSTRING + + because the functionality that currently uses Type_handler + (e.g. hybrid type functions) does not need to distinguish between + these types and VARCHAR. + For example: + CREATE TABLE t2 AS SELECT COALESCE(enum_column) FROM t1; + creates a VARCHAR column. + + There most likely be Type_handler_enum and Type_handler_set later, + when the Type_handler infrastructure gets used in more pieces of the code. +*/ + + +class Type_handler_tiny: public Type_handler_int_result +{ +public: + virtual ~Type_handler_tiny() {} + enum_field_types field_type() const { return MYSQL_TYPE_TINY; } +}; + + +class Type_handler_short: public Type_handler_int_result +{ +public: + virtual ~Type_handler_short() {} + enum_field_types field_type() const { return MYSQL_TYPE_SHORT; } +}; + + +class Type_handler_long: public Type_handler_int_result +{ +public: + virtual ~Type_handler_long() {} + enum_field_types field_type() const { return MYSQL_TYPE_LONG; } +}; + + +class Type_handler_longlong: public Type_handler_int_result +{ +public: + virtual ~Type_handler_longlong() {} + enum_field_types field_type() const { return MYSQL_TYPE_LONGLONG; } +}; + + +class Type_handler_int24: public Type_handler_int_result +{ +public: + virtual ~Type_handler_int24() {} + enum_field_types field_type() const { return MYSQL_TYPE_INT24; } +}; + + +class Type_handler_year: public Type_handler_int_result +{ +public: + virtual ~Type_handler_year() {} + enum_field_types field_type() const { return MYSQL_TYPE_YEAR; } +}; + + +class Type_handler_bit: public Type_handler_int_result +{ +public: + virtual ~Type_handler_bit() {} + enum_field_types field_type() const { return MYSQL_TYPE_BIT; } +}; + + +class Type_handler_float: public Type_handler_real_result +{ +public: + virtual ~Type_handler_float() {} + enum_field_types field_type() const { return MYSQL_TYPE_FLOAT; } +}; + + +class Type_handler_double: public Type_handler_real_result +{ +public: + virtual ~Type_handler_double() {} + enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE; } +}; + + +class Type_handler_time: public Type_handler_temporal_result +{ +public: + virtual ~Type_handler_time() {} + enum_field_types field_type() const { return MYSQL_TYPE_TIME; } +}; + + +class Type_handler_date: public Type_handler_temporal_result +{ +public: + virtual ~Type_handler_date() {} + enum_field_types field_type() const { return MYSQL_TYPE_DATE; } +}; + + +class Type_handler_datetime: public Type_handler_temporal_result +{ +public: + virtual ~Type_handler_datetime() {} + enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; } +}; + + +class Type_handler_timestamp: public Type_handler_temporal_result +{ +public: + virtual ~Type_handler_timestamp() {} + enum_field_types field_type() const { return MYSQL_TYPE_TIMESTAMP; } +}; + + +class Type_handler_olddecimal: public Type_handler_decimal_result +{ +public: + virtual ~Type_handler_olddecimal() {} + enum_field_types field_type() const { return MYSQL_TYPE_DECIMAL; } +}; + + +class Type_handler_newdecimal: public Type_handler_decimal_result +{ +public: + virtual ~Type_handler_newdecimal() {} + enum_field_types field_type() const { return MYSQL_TYPE_NEWDECIMAL; } +}; + + +class Type_handler_null: public Type_handler_string_result +{ +public: + virtual ~Type_handler_null() {} + enum_field_types field_type() const { return MYSQL_TYPE_NULL; } +}; + + +class Type_handler_string: public Type_handler_string_result +{ +public: + virtual ~Type_handler_string() {} + enum_field_types field_type() const { return MYSQL_TYPE_STRING; } +}; + + +class Type_handler_varchar: public Type_handler_string_result +{ +public: + virtual ~Type_handler_varchar() {} + enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; } +}; + + +class Type_handler_tiny_blob: public Type_handler_string_result +{ +public: + virtual ~Type_handler_tiny_blob() {} + enum_field_types field_type() const { return MYSQL_TYPE_TINY_BLOB; } +}; + + +class Type_handler_medium_blob: public Type_handler_string_result +{ +public: + virtual ~Type_handler_medium_blob() {} + enum_field_types field_type() const { return MYSQL_TYPE_MEDIUM_BLOB; } +}; + + +class Type_handler_long_blob: public Type_handler_string_result +{ +public: + virtual ~Type_handler_long_blob() {} + enum_field_types field_type() const { return MYSQL_TYPE_LONG_BLOB; } +}; + + +class Type_handler_blob: public Type_handler_string_result +{ +public: + virtual ~Type_handler_blob() {} + enum_field_types field_type() const { return MYSQL_TYPE_BLOB; } +}; + + +class Type_handler_geometry: public Type_handler_string_result +{ +public: + virtual ~Type_handler_geometry() {} + enum_field_types field_type() const { return MYSQL_TYPE_GEOMETRY; } +}; + + +/** + A handler for hybrid type functions, e.g. + COALESCE(), IF(), IFNULL(), NULLIF(), CASE, + numeric operators, + UNIX_TIMESTAMP(), TIME_TO_SEC(). + + Makes sure that field_type(), cmp_type() and result_type() + are always in sync to each other for hybrid functions. +*/ +class Type_handler_hybrid_field_type: public Type_handler +{ + const Type_handler *m_type_handler; + const Type_handler *get_handler_by_result_type(Item_result type) const; + const Type_handler *get_handler_by_field_type(enum_field_types type) const; +public: + Type_handler_hybrid_field_type(); + Type_handler_hybrid_field_type(enum_field_types type) + :m_type_handler(get_handler_by_field_type(type)) + { } + Type_handler_hybrid_field_type(const Type_handler_hybrid_field_type *other) + :m_type_handler(other->m_type_handler) + { } + enum_field_types field_type() const { return m_type_handler->field_type(); } + Item_result result_type() const { return m_type_handler->result_type(); } + Item_result cmp_type() const { return m_type_handler->cmp_type(); } + const Type_handler *set_handler_by_result_type(Item_result type) + { + return (m_type_handler= get_handler_by_result_type(type)); + } + const Type_handler *set_handler_by_result_type(Item_result type, + uint max_octet_length, + CHARSET_INFO *cs) + { + m_type_handler= get_handler_by_result_type(type); + return m_type_handler= + m_type_handler->type_handler_adjusted_to_max_octet_length(max_octet_length, + cs); + } + const Type_handler *set_handler_by_field_type(enum_field_types type) + { + return (m_type_handler= get_handler_by_field_type(type)); + } + const Type_handler * + type_handler_adjusted_to_max_octet_length(uint max_octet_length, + CHARSET_INFO *cs) const + { + return + m_type_handler->type_handler_adjusted_to_max_octet_length(max_octet_length, + cs); + } +}; + +#endif /* SQL_TYPE_H_INCLUDED */ diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc index d18498de784..4ccd4948b58 100644 --- a/sql/sql_udf.cc +++ b/sql/sql_udf.cc @@ -31,6 +31,7 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_base.h" // close_mysql_tables @@ -89,10 +90,11 @@ static char *init_syms(udf_func *tmp, char *nm) */ if (!tmp->func_init && !tmp->func_deinit && tmp->type != UDFTYPE_AGGREGATE) { + THD *thd= current_thd; if (!opt_allow_suspicious_udfs) return nm; - if (current_thd->variables.log_warnings) - sql_print_warning(ER(ER_CANT_FIND_DL_ENTRY), nm); + if (thd->variables.log_warnings) + sql_print_warning(ER_THD(thd, ER_CANT_FIND_DL_ENTRY), nm); } return 0; } @@ -151,7 +153,7 @@ void udf_init() mysql_rwlock_init(key_rwlock_THR_LOCK_udf, &THR_LOCK_udf); - init_sql_alloc(&mem, UDF_ALLOC_BLOCK_SIZE, 0); + init_sql_alloc(&mem, UDF_ALLOC_BLOCK_SIZE, 0, MYF(0)); THD *new_thd = new THD; if (!new_thd || my_hash_init(&udf_hash,system_charset_info,32,0,0,get_hash_key, NULL, 0)) @@ -206,7 +208,7 @@ void udf_init() On windows we must check both FN_LIBCHAR and '/'. */ if (check_valid_path(dl_name, strlen(dl_name)) || - check_string_char_length(&name, "", NAME_CHAR_LEN, + check_string_char_length(&name, 0, NAME_CHAR_LEN, system_charset_info, 1)) { sql_print_error("Invalid row in mysql.func table for function '%.64s'", @@ -225,13 +227,13 @@ void udf_init() if (dl == NULL) { char dlpath[FN_REFLEN]; - strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", tmp->dl, - NullS); + strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", tmp->dl, NullS); (void) unpack_filename(dlpath, dlpath); if (!(dl= dlopen(dlpath, RTLD_NOW))) { /* Print warning to log */ - sql_print_error(ER(ER_CANT_OPEN_LIBRARY), tmp->dl, errno, dlerror()); + sql_print_error(ER_THD(new_thd, ER_CANT_OPEN_LIBRARY), + tmp->dl, errno, my_dlerror(dlpath)); /* Keep the udf in the hash so that we can remove it later */ continue; } @@ -242,7 +244,7 @@ void udf_init() char buf[SAFE_NAME_LEN+16], *missing; if ((missing= init_syms(tmp, buf))) { - sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), missing); + sql_print_error(ER_THD(new_thd, ER_CANT_FIND_DL_ENTRY), missing); del_udf(tmp); if (new_dl) dlclose(dl); @@ -257,8 +259,6 @@ void udf_init() end: close_mysql_tables(new_thd); delete new_thd; - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); DBUG_VOID_RETURN; } @@ -414,6 +414,50 @@ static udf_func *add_udf(LEX_STRING *name, Item_result ret, char *dl, return tmp; } +/* + Drop user defined function. + + @param thd Thread handler. + @param udf Existing udf_func pointer which is to be deleted. + @param table mysql.func table reference (opened and locked) + + Assumption + + - udf is not null. + - table is already opened and locked +*/ +static int mysql_drop_function_internal(THD *thd, udf_func *udf, TABLE *table) +{ + DBUG_ENTER("mysql_drop_function_internal"); + + char *exact_name_str= udf->name.str; + uint exact_name_len= udf->name.length; + + del_udf(udf); + /* + Close the handle if this was function that was found during boot or + CREATE FUNCTION and it's not in use by any other udf function + */ + if (udf->dlhandle && !find_udf_dl(udf->dl)) + dlclose(udf->dlhandle); + + if (!table) + DBUG_RETURN(1); + + table->use_all_columns(); + table->field[0]->store(exact_name_str, exact_name_len, &my_charset_bin); + if (!table->file->ha_index_read_idx_map(table->record[0], 0, + (uchar*) table->field[0]->ptr, + HA_WHOLE_KEY, + HA_READ_KEY_EXACT)) + { + int error; + if ((error= table->file->ha_delete_row(table->record[0]))) + table->file->print_error(error, MYF(0)); + } + DBUG_RETURN(0); +} + /** Create a user defined function. @@ -431,7 +475,6 @@ int mysql_create_function(THD *thd,udf_func *udf) TABLE *table; TABLE_LIST tables; udf_func *u_d; - bool save_binlog_row_based; DBUG_ENTER("mysql_create_function"); if (!initialized) @@ -441,7 +484,8 @@ int mysql_create_function(THD *thd,udf_func *udf) udf->name.str, "UDFs are unavailable with the --skip-grant-tables option"); else - my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); + my_message(ER_OUT_OF_RESOURCES, ER_THD(thd, ER_OUT_OF_RESOURCES), + MYF(0)); DBUG_RETURN(1); } @@ -452,29 +496,38 @@ int mysql_create_function(THD *thd,udf_func *udf) */ if (check_valid_path(udf->dl, strlen(udf->dl))) { - my_message(ER_UDF_NO_PATHS, ER(ER_UDF_NO_PATHS), MYF(0)); + my_message(ER_UDF_NO_PATHS, ER_THD(thd, ER_UDF_NO_PATHS), MYF(0)); DBUG_RETURN(1); } if (check_ident_length(&udf->name)) DBUG_RETURN(1); - /* - Turn off row binlogging of this statement and use statement-based - so that all supporting tables are updated for CREATE FUNCTION command. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - tables.init_one_table(STRING_WITH_LEN("mysql"), STRING_WITH_LEN("func"), "func", TL_WRITE); table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT); mysql_rwlock_wrlock(&THR_LOCK_udf); DEBUG_SYNC(current_thd, "mysql_create_function_after_lock"); - if ((my_hash_search(&udf_hash,(uchar*) udf->name.str, udf->name.length))) + if ((u_d= (udf_func*) my_hash_search(&udf_hash, (uchar*) udf->name.str, + udf->name.length))) { - my_error(ER_UDF_EXISTS, MYF(0), udf->name.str); - goto err; + if (thd->lex->create_info.or_replace()) + { + if ((error= mysql_drop_function_internal(thd, u_d, table))) + goto err; + } + else if (thd->lex->create_info.if_not_exists()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_UDF_EXISTS, + ER_THD(thd, ER_UDF_EXISTS), udf->name.str); + + goto done; + } + else + { + my_error(ER_UDF_EXISTS, MYF(0), udf->name.str); + goto err; + } } if (!(dl = find_udf_dl(udf->dl))) { @@ -484,10 +537,10 @@ int mysql_create_function(THD *thd,udf_func *udf) if (!(dl = dlopen(dlpath, RTLD_NOW))) { + my_error(ER_CANT_OPEN_LIBRARY, MYF(0), + udf->dl, errno, my_dlerror(dlpath)); DBUG_PRINT("error",("dlopen of %s failed, error: %d (%s)", udf->dl, errno, dlerror())); - my_error(ER_CANT_OPEN_LIBRARY, MYF(0), - udf->dl, errno, dlerror()); goto err; } new_dl=1; @@ -501,16 +554,16 @@ int mysql_create_function(THD *thd,udf_func *udf) goto err; } } - udf->name.str=strdup_root(&mem,udf->name.str); - udf->dl=strdup_root(&mem,udf->dl); + udf->name.str= strdup_root(&mem,udf->name.str); + udf->dl= strdup_root(&mem,udf->dl); if (!(u_d=add_udf(&udf->name,udf->returns,udf->dl,udf->type))) goto err; - u_d->dlhandle = dl; - u_d->func=udf->func; - u_d->func_init=udf->func_init; - u_d->func_deinit=udf->func_deinit; - u_d->func_clear=udf->func_clear; - u_d->func_add=udf->func_add; + u_d->dlhandle= dl; + u_d->func= udf->func; + u_d->func_init= udf->func_init; + u_d->func_deinit= udf->func_deinit; + u_d->func_clear= udf->func_clear; + u_d->func_add= udf->func_add; /* create entry in mysql.func table */ @@ -532,31 +585,20 @@ int mysql_create_function(THD *thd,udf_func *udf) del_udf(u_d); goto err; } + +done: mysql_rwlock_unlock(&THR_LOCK_udf); /* Binlog the create function. */ if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - { - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(1); - } - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + DBUG_RETURN(0); - err: +err: if (new_dl) dlclose(dl); mysql_rwlock_unlock(&THR_LOCK_udf); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(1); } @@ -566,9 +608,6 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) TABLE *table; TABLE_LIST tables; udf_func *udf; - char *exact_name_str; - uint exact_name_len; - bool save_binlog_row_based; DBUG_ENTER("mysql_drop_function"); if (!initialized) @@ -576,52 +615,37 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) if (opt_noacl) my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), udf_name->str); else - my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); + my_message(ER_OUT_OF_RESOURCES, ER_THD(thd, ER_OUT_OF_RESOURCES), + MYF(0)); DBUG_RETURN(1); } - /* - Turn off row binlogging of this statement and use statement-based - so that all supporting tables are updated for DROP FUNCTION command. - */ - if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) - thd->clear_current_stmt_binlog_format_row(); - tables.init_one_table(STRING_WITH_LEN("mysql"), STRING_WITH_LEN("func"), "func", TL_WRITE); table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT); mysql_rwlock_wrlock(&THR_LOCK_udf); DEBUG_SYNC(current_thd, "mysql_drop_function_after_lock"); - if (!(udf=(udf_func*) my_hash_search(&udf_hash,(uchar*) udf_name->str, - (uint) udf_name->length))) + if (!(udf= (udf_func*) my_hash_search(&udf_hash, (uchar*) udf_name->str, + (uint) udf_name->length)) ) { + if (thd->lex->check_exists) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_FUNCTION_NOT_DEFINED, + ER_THD(thd, ER_FUNCTION_NOT_DEFINED), + udf_name->str); + goto done; + } + my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), udf_name->str); goto err; } - exact_name_str= udf->name.str; - exact_name_len= udf->name.length; - del_udf(udf); - /* - Close the handle if this was function that was found during boot or - CREATE FUNCTION and it's not in use by any other udf function - */ - if (udf->dlhandle && !find_udf_dl(udf->dl)) - dlclose(udf->dlhandle); - if (!table) + if (mysql_drop_function_internal(thd, udf, table)) goto err; - table->use_all_columns(); - table->field[0]->store(exact_name_str, exact_name_len, &my_charset_bin); - if (!table->file->ha_index_read_idx_map(table->record[0], 0, - (uchar*) table->field[0]->ptr, - HA_WHOLE_KEY, - HA_READ_KEY_EXACT)) - { - int error; - if ((error = table->file->ha_delete_row(table->record[0]))) - table->file->print_error(error, MYF(0)); - } + +done: mysql_rwlock_unlock(&THR_LOCK_udf); /* @@ -629,24 +653,12 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) while binlogging, to avoid binlog inconsistency. */ if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - { - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(1); - } - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); + DBUG_RETURN(0); + err: mysql_rwlock_unlock(&THR_LOCK_udf); - /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(1); } diff --git a/sql/sql_udf.h b/sql/sql_udf.h index 68c01964687..d3ec1cc1f95 100644 --- a/sql/sql_udf.h +++ b/sql/sql_udf.h @@ -65,8 +65,6 @@ class udf_handler :public Sql_alloc Item **args; public: - table_map used_tables_cache; - bool const_item_cache; bool not_original; udf_handler(udf_func *udf_arg) :u_d(udf_arg), buffers(0), error(0), is_null(0), initialized(0), not_original(0) @@ -76,7 +74,7 @@ class udf_handler :public Sql_alloc Item_result result_type () const { return u_d ? u_d->returns : STRING_RESULT;} bool get_arguments(); - bool fix_fields(THD *thd, Item_result_field *item, + bool fix_fields(THD *thd, Item_func_or_sum *item, uint arg_count, Item **args); void cleanup(); double val(my_bool *null_value) @@ -103,14 +101,14 @@ class udf_handler :public Sql_alloc if (get_arguments()) { *null_value=1; - return LL(0); + return 0; } Udf_func_longlong func= (Udf_func_longlong) u_d->func; longlong tmp=func(&initid, &f_args, &is_null, &error); if (is_null || error) { *null_value=1; - return LL(0); + return 0; } *null_value=0; return tmp; diff --git a/sql/sql_union.cc b/sql/sql_union.cc index e855b64e600..72926a26e13 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -20,7 +20,7 @@ UNION's were introduced by Monty and Sinisa <sinisa@mysql.com> */ - +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_union.h" @@ -64,7 +64,7 @@ int select_union::send_data(List<Item> &values) return 0; if (table->no_rows_with_nulls) table->null_catch_flags= CHECK_ROW_FOR_NULLS_TO_REJECT; - fill_record(thd, table->field, values, TRUE, FALSE); + fill_record(thd, table, table->field, values, TRUE, FALSE); if (thd->is_error()) return 1; if (table->no_rows_with_nulls) @@ -188,6 +188,104 @@ void select_union::cleanup() } + +/** + Replace the current result with new_result and prepare it. + + @param new_result New result pointer + + @retval FALSE Success + @retval TRUE Error +*/ + +bool select_union_direct::change_result(select_result *new_result) +{ + result= new_result; + return (result->prepare(unit->types, unit) || result->prepare2()); +} + + +bool select_union_direct::postponed_prepare(List<Item> &types) +{ + if (result != NULL) + return (result->prepare(types, unit) || result->prepare2()); + else + return false; +} + + +bool select_union_direct::send_result_set_metadata(List<Item> &list, uint flags) +{ + if (done_send_result_set_metadata) + return false; + done_send_result_set_metadata= true; + + /* + Set global offset and limit to be used in send_data(). These can + be variables in prepared statements or stored programs, so they + must be reevaluated for each execution. + */ + offset= unit->global_parameters()->get_offset(); + limit= unit->global_parameters()->get_limit(); + if (limit + offset >= limit) + limit+= offset; + else + limit= HA_POS_ERROR; /* purecov: inspected */ + + return result->send_result_set_metadata(unit->types, flags); +} + + +int select_union_direct::send_data(List<Item> &items) +{ + if (!limit) + return false; + limit--; + if (offset) + { + offset--; + return false; + } + + send_records++; + fill_record(thd, table, table->field, items, true, false); + if (thd->is_error()) + return true; /* purecov: inspected */ + + return result->send_data(unit->item_list); +} + + +bool select_union_direct::initialize_tables (JOIN *join) +{ + if (done_initialize_tables) + return false; + done_initialize_tables= true; + + return result->initialize_tables(join); +} + + +bool select_union_direct::send_eof() +{ + // Reset for each SELECT_LEX, so accumulate here + limit_found_rows+= thd->limit_found_rows; + + if (unit->thd->lex->current_select == last_select_lex) + { + thd->limit_found_rows= limit_found_rows; + + // Reset and make ready for re-execution + done_send_result_set_metadata= false; + done_initialize_tables= false; + + return result->send_eof(); + } + else + return false; +} + + /* initialization procedures before fake_select_lex preparation() @@ -219,12 +317,12 @@ st_select_lex_unit::init_prepare_fake_select_lex(THD *thd_arg, if ((fake_select_lex->changed_elements & TOUCHED_SEL_COND) && first_execution) { - for (ORDER *order= global_parameters->order_list.first; + for (ORDER *order= global_parameters()->order_list.first; order; order= order->next) order->item= &order->item_ptr; } - for (ORDER *order= global_parameters->order_list.first; + for (ORDER *order= global_parameters()->order_list.first; order; order=order->next) { @@ -243,9 +341,18 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, SELECT_LEX *sl, *first_sl= first_select(); select_result *tmp_result; bool is_union_select; + bool instantiate_tmp_table= false; DBUG_ENTER("st_select_lex_unit::prepare"); + DBUG_ASSERT(thd == thd_arg && thd == current_thd); + + describe= MY_TEST(additional_options & SELECT_DESCRIBE); - describe= test(additional_options & SELECT_DESCRIBE); + /* + Save fake_select_lex in case we don't need it for anything but + global parameters. + */ + if (saved_fake_select_lex == NULL) // Don't overwrite on PS second prepare + saved_fake_select_lex= fake_select_lex; /* result object should be reassigned even if preparing already done for @@ -285,10 +392,25 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, if (is_union_select) { - if (!(tmp_result= union_result= new select_union)) - goto err; - if (describe) - tmp_result= sel_result; + if (is_union() && !union_needs_tmp_table()) + { + SELECT_LEX *last= first_select(); + while (last->next_select()) + last= last->next_select(); + if (!(tmp_result= union_result= + new (thd_arg->mem_root) select_union_direct(thd_arg, sel_result, + last))) + goto err; /* purecov: inspected */ + fake_select_lex= NULL; + instantiate_tmp_table= false; + } + else + { + if (!(tmp_result= union_result= + new (thd_arg->mem_root) select_union(thd_arg))) + goto err; /* purecov: inspected */ + instantiate_tmp_table= true; + } } else tmp_result= sel_result; @@ -377,7 +499,8 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, item_tmp= item_tmp->real_item(); /* Error's in 'new' will be detected after loop */ - types.push_back(new Item_type_holder(thd_arg, item_tmp)); + types.push_back(new (thd_arg->mem_root) + Item_type_holder(thd_arg, item_tmp)); } if (thd_arg->is_fatal_error) @@ -388,7 +511,7 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, if (types.elements != sl->item_list.elements) { my_message(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT, - ER(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT),MYF(0)); + ER_THD(thd, ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT),MYF(0)); goto err; } List_iterator_fast<Item> it(sl->item_list); @@ -402,6 +525,14 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, } } + /* + If the query is using select_union_direct, we have postponed + preparation of the underlying select_result until column types + are known. + */ + if (union_result != NULL && union_result->postponed_prepare(types)) + DBUG_RETURN(true); + if (is_union_select) { /* @@ -438,13 +569,13 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, the meaning of these accumulated flags and what to carry over to the recipient query (SELECT_LEX). */ - if (global_parameters->ftfunc_list->elements && - global_parameters->order_list.elements && - global_parameters != fake_select_lex) + if (global_parameters()->ftfunc_list->elements && + global_parameters()->order_list.elements && + global_parameters() != fake_select_lex) { ORDER *ord; Item_func::Functype ft= Item_func::FT_FUNC; - for (ord= global_parameters->order_list.first; ord; ord= ord->next) + for (ord= global_parameters()->order_list.first; ord; ord= ord->next) if ((*ord->item)->walk (&Item::find_function_processor, FALSE, (uchar *) &ft)) { @@ -462,11 +593,12 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, from it (this should be removed in 5.2 when fulltext search is moved out of MyISAM). */ - if (global_parameters->ftfunc_list->elements) + if (global_parameters()->ftfunc_list->elements) create_options= create_options | TMP_TABLE_FORCE_MYISAM; - if (union_result->create_result_table(thd, &types, test(union_distinct), - create_options, "", FALSE, TRUE)) + if (union_result->create_result_table(thd, &types, MY_TEST(union_distinct), + create_options, "", false, + instantiate_tmp_table)) goto err; if (fake_select_lex && !fake_select_lex->first_cond_optimization) { @@ -500,7 +632,9 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, if (saved_error) goto err; - if (thd->stmt_arena->is_stmt_prepare()) + if (fake_select_lex != NULL && + (thd->stmt_arena->is_stmt_prepare() || + (thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW))) { /* Validate the global parameters of this union */ @@ -526,17 +660,7 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, We need to add up n_sum_items in order to make the correct allocation in setup_ref_array(). */ - fake_select_lex->n_child_sum_items+= global_parameters->n_sum_items; - - saved_error= fake_select_lex->join-> - prepare(&fake_select_lex->ref_pointer_array, - fake_select_lex->table_list.first, - 0, 0, - global_parameters->order_list.elements, // og_num - global_parameters->order_list.first, // order - false, NULL, NULL, NULL, - fake_select_lex, this); - fake_select_lex->table_list.empty(); + fake_select_lex->n_child_sum_items+= global_parameters()->n_sum_items; } } else @@ -547,6 +671,27 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, */ table->reset_item_list(&item_list); } + if (fake_select_lex != NULL && + (thd->stmt_arena->is_stmt_prepare() || + (thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW))) + { + if (!fake_select_lex->join && + !(fake_select_lex->join= + new JOIN(thd, item_list, thd->variables.option_bits, result))) + { + fake_select_lex->table_list.empty(); + DBUG_RETURN(TRUE); + } + saved_error= fake_select_lex->join-> + prepare(&fake_select_lex->ref_pointer_array, + fake_select_lex->table_list.first, + 0, 0, + global_parameters()->order_list.elements, // og_num + global_parameters()->order_list.first, // order + false, NULL, NULL, NULL, + fake_select_lex, this); + fake_select_lex->table_list.empty(); + } } thd_arg->lex->current_select= lex_select_save; @@ -585,7 +730,11 @@ bool st_select_lex_unit::optimize() { item->assigned(0); // We will reinit & rexecute unit item->reset(); - table->file->ha_delete_all_rows(); + if (table->created) + { + table->file->ha_delete_all_rows(); + table->file->info(HA_STATUS_VARIABLE); + } } /* re-enabling indexes for next subselect iteration */ if (union_distinct && table->file->ha_enable_indexes(HA_KEY_SWITCH_ALL)) @@ -602,7 +751,7 @@ bool st_select_lex_unit::optimize() else { set_limit(sl); - if (sl == global_parameters || describe) + if (sl == global_parameters() || describe) { offset_limit_cnt= 0; /* @@ -647,6 +796,7 @@ bool st_select_lex_unit::exec() ha_rows examined_rows= 0; bool first_execution= !executed; DBUG_ENTER("st_select_lex_unit::exec"); + bool was_executed= executed; if (executed && !uncacheable && !describe) DBUG_RETURN(FALSE); @@ -654,23 +804,35 @@ bool st_select_lex_unit::exec() if (!(uncacheable & ~UNCACHEABLE_EXPLAIN) && item) item->make_const(); - if ((saved_error= optimize())) + saved_error= optimize(); + + create_explain_query_if_not_exists(thd->lex, thd->mem_root); + + if (!saved_error && !was_executed) + save_union_explain(thd->lex->explain); + + if (saved_error) DBUG_RETURN(saved_error); if (uncacheable || !item || !item->assigned() || describe) { + if (!fake_select_lex) + union_result->cleanup(); for (SELECT_LEX *sl= select_cursor; sl; sl= sl->next_select()) { ha_rows records_at_start= 0; thd->lex->current_select= sl; - if (sl != &thd->lex->select_lex) - fake_select_lex->uncacheable|= sl->uncacheable; - else - fake_select_lex->uncacheable= 0; + if (fake_select_lex) + { + if (sl != &thd->lex->select_lex) + fake_select_lex->uncacheable|= sl->uncacheable; + else + fake_select_lex->uncacheable= 0; + } { set_limit(sl); - if (sl == global_parameters || describe) + if (sl == global_parameters() || describe) { offset_limit_cnt= 0; /* @@ -697,6 +859,8 @@ bool st_select_lex_unit::exec() sl->join->exec(); if (sl == union_distinct) { + // This is UNION DISTINCT, so there should be a fake_select_lex + DBUG_ASSERT(fake_select_lex != NULL); if (table->file->ha_disable_indexes(HA_KEY_SWITCH_ALL)) DBUG_RETURN(TRUE); table->no_keyread=1; @@ -707,13 +871,8 @@ bool st_select_lex_unit::exec() 0); if (!saved_error) { - /* - Save the current examined row count locally and clear the global - counter, so that we can accumulate the number of evaluated rows for - the current query block. - */ - examined_rows+= thd->examined_row_count; - thd->examined_row_count= 0; + examined_rows+= thd->get_examined_row_count(); + thd->set_examined_row_count(0); if (union_result->flush()) { thd->lex->current_select= lex_select_save; @@ -726,12 +885,15 @@ bool st_select_lex_unit::exec() thd->lex->current_select= lex_select_save; DBUG_RETURN(saved_error); } - /* Needed for the following test and for records_at_start in next loop */ - int error= table->file->info(HA_STATUS_VARIABLE); - if(error) + if (fake_select_lex != NULL) { - table->file->print_error(error, MYF(0)); - DBUG_RETURN(1); + /* Needed for the following test and for records_at_start in next loop */ + int error= table->file->info(HA_STATUS_VARIABLE); + if(error) + { + table->file->print_error(error, MYF(0)); + DBUG_RETURN(1); + } } if (found_rows_for_union && !sl->braces && select_limit_cnt != HA_POS_ERROR) @@ -751,9 +913,9 @@ bool st_select_lex_unit::exec() Stop execution of the remaining queries in the UNIONS, and produce the current result. */ - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT, - ER(ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT), + ER_THD(thd, ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT), thd->accessed_rows_and_keys, thd->lex->limit_rows_examined->val_uint()); thd->reset_killed(); @@ -762,8 +924,8 @@ bool st_select_lex_unit::exec() } } - /* Send result to 'result' */ - saved_error= TRUE; + DBUG_EXECUTE_IF("show_explain_probe_union_read", + dbug_serve_apcs(thd, 1);); { List<Item_func_match> empty_list; empty_list.empty(); @@ -773,11 +935,15 @@ bool st_select_lex_unit::exec() */ thd->lex->limit_rows_examined_cnt= ULONGLONG_MAX; - if (!thd->is_fatal_error) // Check if EOM + if (fake_select_lex != NULL && !thd->is_fatal_error) // Check if EOM { - set_limit(global_parameters); + /* Send result to 'result' */ + saved_error= true; + + set_limit(global_parameters()); init_prepare_fake_select_lex(thd, first_execution); JOIN *join= fake_select_lex->join; + saved_error= false; if (!join) { /* @@ -809,13 +975,16 @@ bool st_select_lex_unit::exec() for this (with a different join object) */ if (!fake_select_lex->ref_pointer_array) - fake_select_lex->n_child_sum_items+= global_parameters->n_sum_items; + fake_select_lex->n_child_sum_items+= global_parameters()->n_sum_items; + + if (!was_executed) + save_union_explain_part2(thd->lex->explain); saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array, &result_table_list, 0, item_list, NULL, - global_parameters->order_list.elements, - global_parameters->order_list.first, + global_parameters()->order_list.elements, + global_parameters()->order_list.first, NULL, NULL, NULL, fake_select_lex->options | SELECT_NO_UNLOCK, result, this, fake_select_lex); @@ -832,20 +1001,20 @@ bool st_select_lex_unit::exec() 1st execution sets certain members (e.g. select_result) to perform subquery execution rather than EXPLAIN line production. In order to reset them back, we re-do all of the actions (yes it is ugly): - */ + */ // psergey-todo: is the above really necessary anymore?? join->init(thd, item_list, fake_select_lex->options, result); saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array, &result_table_list, 0, item_list, NULL, - global_parameters->order_list.elements, - global_parameters->order_list.first, + global_parameters()->order_list.elements, + global_parameters()->order_list.first, NULL, NULL, NULL, fake_select_lex->options | SELECT_NO_UNLOCK, result, this, fake_select_lex); } else { - join->examined_rows= 0; + join->join_examined_rows= 0; saved_error= join->reinit(); join->exec(); } @@ -855,7 +1024,7 @@ bool st_select_lex_unit::exec() if (!saved_error) { thd->limit_found_rows = (ulonglong)table->file->stats.records + add_rows; - thd->examined_row_count+= examined_rows; + thd->inc_examined_row_count(examined_rows); } /* Mark for slow query log if any of the union parts didn't use @@ -920,11 +1089,11 @@ bool st_select_lex_unit::cleanup() Note: global_parameters and fake_select_lex are always initialized for UNION */ - DBUG_ASSERT(global_parameters); - if (global_parameters->order_list.elements) + DBUG_ASSERT(global_parameters()); + if (global_parameters()->order_list.elements) { ORDER *ord; - for (ord= global_parameters->order_list.first; ord; ord= ord->next) + for (ord= global_parameters()->order_list.first; ord; ord= ord->next) (*ord->item)->walk (&Item::cleanup_processor, 0, 0); } } @@ -939,32 +1108,33 @@ void st_select_lex_unit::reinit_exec_mechanism() } -/* - change select_result object of unit +/** + Change the select_result object used to return the final result of + the unit, replacing occurences of old_result with new_result. - SYNOPSIS - st_select_lex_unit::change_result() - result new select_result object - old_result old select_result object + @param new_result New select_result object + @param old_result Old select_result object - RETURN - FALSE - OK - TRUE - error + @retval false Success + @retval true Error */ bool st_select_lex_unit::change_result(select_result_interceptor *new_result, select_result_interceptor *old_result) { - bool res= FALSE; for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select()) { - if (sl->join && sl->join->result == old_result) - if (sl->join->change_result(new_result)) - return TRUE; + if (sl->join) + if (sl->join->change_result(new_result, old_result)) + return true; /* purecov: inspected */ } - if (fake_select_lex && fake_select_lex->join) - res= fake_select_lex->join->change_result(new_result); - return (res); + /* + If there were a fake_select_lex->join, we would have to change the + result of that also, but change_result() is called before such an + object is created. + */ + DBUG_ASSERT(fake_select_lex == NULL || fake_select_lex->join == NULL); + return false; } /* @@ -988,7 +1158,7 @@ bool st_select_lex_unit::change_result(select_result_interceptor *new_result, List<Item> *st_select_lex_unit::get_unit_column_types() { SELECT_LEX *sl= first_select(); - bool is_procedure= test(sl->join->procedure); + bool is_procedure= MY_TEST(sl->join->procedure); if (is_procedure) { @@ -1008,11 +1178,22 @@ List<Item> *st_select_lex_unit::get_unit_column_types() return &sl->item_list; } + +static void cleanup_order(ORDER *order) +{ + for (; order; order= order->next) + order->counter_used= 0; +} + + bool st_select_lex::cleanup() { bool error= FALSE; DBUG_ENTER("st_select_lex::cleanup()"); + cleanup_order(order_list.first); + cleanup_order(group_list.first); + if (join) { DBUG_ASSERT((st_select_lex*)join->select_lex == this); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index fe007d5823d..78aa059f64f 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -20,9 +20,8 @@ Multi-table updates were introduced by Sinisa & Monty */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_update.h" #include "sql_cache.h" // query_cache_* #include "sql_base.h" // close_tables_for_reopen @@ -32,6 +31,7 @@ #include "sql_view.h" // check_key_in_view #include "sp_head.h" #include "sql_trigger.h" +#include "sql_statistics.h" #include "probes_mysql.h" #include "debug_sync.h" #include "key.h" // is_key_used @@ -137,7 +137,7 @@ static bool check_fields(THD *thd, List<Item> &items) while ((item= it++)) { - if (!(field= item->filed_for_view_update())) + if (!(field= item->field_for_view_update())) { /* item has name, because it comes from VIEW SELECT list */ my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), item->name); @@ -147,7 +147,7 @@ static bool check_fields(THD *thd, List<Item> &items) we make temporary copy of Item_field, to avoid influence of changing result_field on Item_ref which refer on this field */ - thd->change_item_tree(it.ref(), new Item_field(thd, field)); + thd->change_item_tree(it.ref(), new (thd->mem_root) Item_field(thd, field)); } return FALSE; } @@ -190,7 +190,7 @@ static void prepare_record_for_error_message(int error, TABLE *table) DBUG_VOID_RETURN; /* Create unique_map with all fields used by that index. */ - bitmap_init(&unique_map, unique_map_buf, table->s->fields, FALSE); + my_bitmap_init(&unique_map, unique_map_buf, table->s->fields, FALSE); table->mark_columns_used_by_index_no_reset(keynr, &unique_map); /* Subtract read_set and write_set. */ @@ -254,12 +254,12 @@ int mysql_update(THD *thd, ha_rows *found_return, ha_rows *updated_return) { bool using_limit= limit != HA_POS_ERROR; - bool safe_update= test(thd->variables.option_bits & OPTION_SAFE_UPDATES); + bool safe_update= MY_TEST(thd->variables.option_bits & OPTION_SAFE_UPDATES); bool used_key_is_modified= FALSE, transactional_table, will_batch; bool can_compare_record; int res; int error, loc_error; - uint used_index, dup_key_found; + uint dup_key_found; bool need_sort= TRUE; bool reverse= FALSE; #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -269,14 +269,19 @@ int mysql_update(THD *thd, ha_rows updated, found; key_map old_covering_keys; TABLE *table; - SQL_SELECT *select; + SQL_SELECT *select= NULL; READ_RECORD info; SELECT_LEX *select_lex= &thd->lex->select_lex; ulonglong id; List<Item> all_fields; killed_state killed_status= NOT_KILLED; + Update_plan query_plan(thd->mem_root); + Explain_update *explain; + query_plan.index= MAX_KEY; + query_plan.using_filesort= FALSE; DBUG_ENTER("mysql_update"); + create_explain_query(thd->lex, thd->mem_root); if (open_tables(thd, &table_list, &table_count, 0)) DBUG_RETURN(1); @@ -296,12 +301,14 @@ int mysql_update(THD *thd, if (lock_tables(thd, table_list, table_count, 0)) DBUG_RETURN(1); + (void) read_statistics_for_tables_if_needed(thd, table_list); + if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT)) DBUG_RETURN(1); if (table_list->handle_derived(thd->lex, DT_PREPARE)) DBUG_RETURN(1); - thd_proc_info(thd, "init"); + THD_STAGE_INFO(thd, stage_init); table= table_list->table; if (!table_list->single_table_updatable()) @@ -309,10 +316,14 @@ int mysql_update(THD *thd, my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "UPDATE"); DBUG_RETURN(1); } + query_plan.updating_a_view= MY_TEST(table_list->view); + /* Calculate "table->covering_keys" based on the WHERE */ table->covering_keys= table->s->keys_in_use; table->quick_keys.clear_all(); + query_plan.select_lex= &thd->lex->select_lex; + query_plan.table= table; #ifndef NO_EMBEDDED_ACCESS_CHECKS /* Force privilege re-checking for views after they have been opened. */ want_privilege= (table_list->view ? UPDATE_ACL : @@ -342,19 +353,8 @@ int mysql_update(THD *thd, my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "UPDATE"); DBUG_RETURN(1); } - if (table->timestamp_field) - { - // Don't set timestamp column if this is modified - if (bitmap_is_set(table->write_set, - table->timestamp_field->field_index)) - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - else - { - if (((uint) table->timestamp_field_type) & TIMESTAMP_AUTO_SET_ON_UPDATE) - bitmap_set_bit(table->write_set, - table->timestamp_field->field_index); - } - } + if (table->default_field) + table->mark_default_fields_for_write(); #ifndef NO_EMBEDDED_ACCESS_CHECKS /* Check values */ @@ -370,6 +370,9 @@ int mysql_update(THD *thd, if (check_unique_table(thd, table_list)) DBUG_RETURN(TRUE); + switch_to_nullable_trigger_fields(fields, table); + switch_to_nullable_trigger_fields(values, table); + /* Apply the IN=>EXISTS transformation to all subqueries and optimize them. */ if (select_lex->optimize_unflattened_subqueries(false)) DBUG_RETURN(TRUE); @@ -381,9 +384,14 @@ int mysql_update(THD *thd, if (conds) { Item::cond_result cond_value; - conds= remove_eq_conds(thd, conds, &cond_value); + conds= conds->remove_eq_conds(thd, &cond_value, true); if (cond_value == Item::COND_FALSE) + { limit= 0; // Impossible WHERE + query_plan.set_impossible_where(); + if (thd->lex->describe || thd->lex->analyze_stmt) + goto produce_explain_and_leave; + } } /* @@ -392,7 +400,7 @@ int mysql_update(THD *thd, to compare records and detect data change. */ if ((table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) && - (((uint) table->timestamp_field_type) & TIMESTAMP_AUTO_SET_ON_UPDATE)) + table->default_field && table->has_default_function(true)) bitmap_union(table->read_set, table->write_set); // Don't count on usage of 'only index' when calculating which key to use table->covering_keys.clear_all(); @@ -401,17 +409,27 @@ int mysql_update(THD *thd, if (prune_partitions(thd, table, conds)) { free_underlaid_joins(thd, select_lex); + + query_plan.set_no_partitions(); + if (thd->lex->describe || thd->lex->analyze_stmt) + goto produce_explain_and_leave; + my_ok(thd); // No matching records DBUG_RETURN(0); } #endif /* Update the table->file->stats.records number */ table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); + set_statistics_for_table(thd, table); select= make_select(table, 0, 0, conds, 0, &error); if (error || !limit || thd->is_error() || (select && select->check_quick(thd, safe_update, limit))) { + query_plan.set_impossible_where(); + if (thd->lex->describe || thd->lex->analyze_stmt) + goto produce_explain_and_leave; + delete select; free_underlaid_joins(thd, select_lex); /* @@ -432,11 +450,11 @@ int mysql_update(THD *thd, /* If running in safe sql mode, don't allow updates without keys */ if (table->quick_keys.is_clear_all()) { - thd->server_status|=SERVER_QUERY_NO_INDEX_USED; + thd->set_status_no_index_used(); if (safe_update && !using_limit) { my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, - ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); + ER_THD(thd, ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); goto err; } } @@ -446,55 +464,97 @@ int mysql_update(THD *thd, table->update_const_key_parts(conds); order= simple_remove_const(order, conds); + query_plan.scanned_rows= select? select->records: table->file->stats.records; if (select && select->quick && select->quick->unique_key_range()) - { // Single row select (always "ordered"): Ok to use with key field UPDATE + { + /* Single row select (always "ordered"): Ok to use with key field UPDATE */ need_sort= FALSE; - used_index= MAX_KEY; + query_plan.index= MAX_KEY; used_key_is_modified= FALSE; } else { - used_index= get_index_for_order(order, table, select, limit, - &need_sort, &reverse); + ha_rows scanned_limit= query_plan.scanned_rows; + query_plan.index= get_index_for_order(order, table, select, limit, + &scanned_limit, &need_sort, + &reverse); + if (!need_sort) + query_plan.scanned_rows= scanned_limit; + if (select && select->quick) { - DBUG_ASSERT(need_sort || used_index == select->quick->index); + DBUG_ASSERT(need_sort || query_plan.index == select->quick->index); used_key_is_modified= (!select->quick->unique_key_range() && select->quick->is_keys_used(table->write_set)); } else { if (need_sort) - { // Assign table scan index to check below for modified key fields: - used_index= table->file->key_used_on_scan; + { + /* Assign table scan index to check below for modified key fields: */ + query_plan.index= table->file->key_used_on_scan; } - if (used_index != MAX_KEY) - { // Check if we are modifying a key that we are used to search with: - used_key_is_modified= is_key_used(table, used_index, table->write_set); + if (query_plan.index != MAX_KEY) + { + /* Check if we are modifying a key that we are used to search with: */ + used_key_is_modified= is_key_used(table, query_plan.index, + table->write_set); } } } - + + /* + Query optimization is finished at this point. + - Save the decisions in the query plan + - if we're running EXPLAIN UPDATE, get out + */ + query_plan.select= select; + query_plan.possible_keys= select? select->possible_keys: key_map(0); + if (used_key_is_modified || order || partition_key_modified(table, table->write_set)) { + if (order && need_sort) + query_plan.using_filesort= true; + else + query_plan.using_io_buffer= true; + } + + + /* + Ok, we have generated a query plan for the UPDATE. + - if we're running EXPLAIN UPDATE, goto produce explain output + - otherwise, execute the query plan + */ + if (thd->lex->describe) + goto produce_explain_and_leave; + explain= query_plan.save_explain_update_data(query_plan.mem_root, thd); + + ANALYZE_START_TRACKING(&explain->command_tracker); + + DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", + dbug_serve_apcs(thd, 1);); + + if (!(select && select->quick)) + status_var_increment(thd->status_var.update_scan_count); + + if (query_plan.using_filesort || query_plan.using_io_buffer) + { /* We can't update table directly; We must first search after all matching rows before updating the table! */ + MY_BITMAP *save_read_set= table->read_set; + MY_BITMAP *save_write_set= table->write_set; - // Verify that table->restore_column_maps_after_mark_index() will work - DBUG_ASSERT(table->read_set == &table->def_read_set); - DBUG_ASSERT(table->write_set == &table->def_write_set); - - if (used_index < MAX_KEY && old_covering_keys.is_set(used_index)) - table->add_read_columns_used_by_index(used_index); + if (query_plan.index < MAX_KEY && old_covering_keys.is_set(query_plan.index)) + table->add_read_columns_used_by_index(query_plan.index); else table->use_all_columns(); /* note: We avoid sorting if we sort on the used index */ - if (order && (need_sort || used_key_is_modified)) + if (query_plan.using_filesort) { /* Doing an ORDER BY; Let filesort find and sort the rows we are going @@ -504,18 +564,25 @@ int mysql_update(THD *thd, uint length= 0; SORT_FIELD *sortorder; ha_rows examined_rows; + ha_rows found_rows; table->sort.io_cache = (IO_CACHE *) my_malloc(sizeof(IO_CACHE), - MYF(MY_FAE | MY_ZEROFILL)); - if (!(sortorder=make_unireg_sortorder(order, &length, NULL)) || + MYF(MY_FAE | MY_ZEROFILL | + MY_THREAD_SPECIFIC)); + Filesort_tracker *fs_tracker= + thd->lex->explain->get_upd_del_plan()->filesort_tracker; + + if (!(sortorder=make_unireg_sortorder(thd, NULL, 0, order, &length, NULL)) || (table->sort.found_records= filesort(thd, table, sortorder, length, - select, limit, 1, - &examined_rows)) + select, limit, + true, + &examined_rows, &found_rows, + fs_tracker)) == HA_POS_ERROR) { goto err; } - thd->examined_row_count+= examined_rows; + thd->inc_examined_row_count(examined_rows); /* Filesort has already found and selected the rows we want to update, so we don't need the where clause @@ -530,7 +597,7 @@ int mysql_update(THD *thd, we go trough the matching rows, save a pointer to them and update these in a separate loop based on the pointer. */ - + explain->buf_tracker.on_scan_init(); IO_CACHE tempfile; if (open_cached_file(&tempfile, mysql_tmpdir,TEMP_PREFIX, DISK_BUFFER_SIZE, MYF(MY_WME))) @@ -542,40 +609,47 @@ int mysql_update(THD *thd, close_cached_file(&tempfile); goto err; } + table->file->try_semi_consistent_read(1); /* When we get here, we have one of the following options: - A. used_index == MAX_KEY + A. query_plan.index == MAX_KEY This means we should use full table scan, and start it with init_read_record call - B. used_index != MAX_KEY + B. query_plan.index != MAX_KEY B.1 quick select is used, start the scan with init_read_record B.2 quick select is not used, this is full index scan (with LIMIT) - Full index scan must be started with init_read_record_idx + Full index scan must be started with init_read_record_idx */ - if (used_index == MAX_KEY || (select && select->quick)) + if (query_plan.index == MAX_KEY || (select && select->quick)) + error= init_read_record(&info, thd, table, select, 0, 1, FALSE); + else + error= init_read_record_idx(&info, thd, table, 1, query_plan.index, + reverse); + + if (error) { - if (init_read_record(&info, thd, table, select, 0, 1, FALSE)) - goto err; + close_cached_file(&tempfile); + goto err; } - else - init_read_record_idx(&info, thd, table, 1, used_index, reverse); - thd_proc_info(thd, "Searching rows for update"); + THD_STAGE_INFO(thd, stage_searching_rows_for_update); ha_rows tmp_limit= limit; while (!(error=info.read_record(&info)) && !thd->killed) { + explain->buf_tracker.on_record_read(); if (table->vfield) update_virtual_fields(thd, table, VCOL_UPDATE_FOR_READ); - thd->examined_row_count++; + thd->inc_examined_row_count(1); if (!select || (error= select->skip_record(thd)) > 0) { - if (table->file->was_semi_consistent_read()) + if (table->file->ha_was_semi_consistent_read()) continue; /* repeat the read of the same row if it still exists */ + explain->buf_tracker.on_record_after_where(); table->file->position(table->record[0]); if (my_b_write(&tempfile,table->file->ref, table->file->ref_length)) @@ -625,6 +699,7 @@ int mysql_update(THD *thd, select= new SQL_SELECT; select->head=table; } + if (reinit_io_cache(&tempfile,READ_CACHE,0L,0,0)) error=1; /* purecov: inspected */ select->file=tempfile; // Read row ptrs from this file @@ -633,11 +708,8 @@ int mysql_update(THD *thd, if (error >= 0) goto err; } - /* - This restore bitmaps, works for add_read_columns_used_by_index() and - use_all_columns(): - */ - table->restore_column_maps_after_mark_index(); + table->disable_keyread(); + table->column_bitmaps_set(save_read_set, save_write_set); } if (ignore) @@ -656,23 +728,12 @@ int mysql_update(THD *thd, */ thd->count_cuted_fields= CHECK_FIELD_WARN; thd->cuted_fields=0L; - thd_proc_info(thd, "Updating"); + THD_STAGE_INFO(thd, stage_updating); transactional_table= table->file->has_transactions(); - thd->abort_on_warning= test(!ignore && - (thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES))); - if (table->triggers && - table->triggers->has_triggers(TRG_EVENT_UPDATE, - TRG_ACTION_AFTER)) + thd->abort_on_warning= !ignore && thd->is_strict_mode(); + if (table->prepare_triggers_for_update_stmt_or_event()) { - /* - The table has AFTER UPDATE triggers that might access to subject - table and therefore might need update to be done immediately. - So we turn-off the batching. - */ - (void) table->file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH); will_batch= FALSE; } else @@ -685,26 +746,30 @@ int mysql_update(THD *thd, if (table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) table->prepare_for_position(); + table->reset_default_fields(); + /* We can use compare_record() to optimize away updates if the table handler is returning all columns OR if if all updated columns are read */ can_compare_record= records_are_comparable(table); + explain->tracker.on_scan_init(); while (!(error=info.read_record(&info)) && !thd->killed) { + explain->tracker.on_record_read(); if (table->vfield) update_virtual_fields(thd, table, VCOL_UPDATE_FOR_READ); - thd->examined_row_count++; + thd->inc_examined_row_count(1); if (!select || select->skip_record(thd) > 0) { - if (table->file->was_semi_consistent_read()) + if (table->file->ha_was_semi_consistent_read()) continue; /* repeat the read of the same row if it still exists */ + explain->tracker.on_record_after_where(); store_record(table,record[1]); - if (fill_record_n_invoke_before_triggers(thd, fields, values, 0, - table->triggers, + if (fill_record_n_invoke_before_triggers(thd, table, fields, values, 0, TRG_EVENT_UPDATE)) break; /* purecov: inspected */ @@ -712,6 +777,11 @@ int mysql_update(THD *thd, if (!can_compare_record || compare_record(table)) { + if (table->default_field && table->update_default_fields()) + { + error= 1; + break; + } if ((res= table_list->view_check_option(thd, ignore)) != VIEW_CHECK_OK) { @@ -852,13 +922,14 @@ int mysql_update(THD *thd, error= 1; break; } - thd->warning_info->inc_current_row_for_warning(); + thd->get_stmt_da()->inc_current_row_for_warning(); if (thd->is_error()) { error= 1; break; } } + ANALYZE_STOP_TRACKING(&explain->command_tracker); table->auto_increment_field_not_null= FALSE; dup_key_found= 0; /* @@ -873,7 +944,7 @@ int mysql_update(THD *thd, // simulated killing after the loop must be ineffective for binlogging DBUG_EXECUTE_IF("simulate_kill_bug27571", { - thd->killed= KILL_QUERY; + thd->set_killed(KILL_QUERY); };); error= (killed_status == NOT_KILLED)? error : 1; @@ -906,7 +977,8 @@ int mysql_update(THD *thd, end_read_record(&info); delete select; - thd_proc_info(thd, "end"); + select= NULL; + THD_STAGE_INFO(thd, stage_end); (void) table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); /* @@ -920,6 +992,8 @@ int mysql_update(THD *thd, if (thd->transaction.stmt.modified_non_trans_table) thd->transaction.all.modified_non_trans_table= TRUE; + thd->transaction.all.m_unsafe_rollback_flags|= + (thd->transaction.stmt.m_unsafe_rollback_flags & THD_TRANS::DID_WAIT); /* error < 0 means really no error at all: we processed all rows until the @@ -932,7 +1006,7 @@ int mysql_update(THD *thd, */ if ((error < 0) || thd->transaction.stmt.modified_non_trans_table) { - if (mysql_bin_log.is_open()) + if (WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) { int errcode= 0; if (error < 0) @@ -955,12 +1029,12 @@ int mysql_update(THD *thd, id= thd->arg_of_last_insert_id_function ? thd->first_successful_insert_id_in_prev_stmt : 0; - if (error < 0) + if (error < 0 && !thd->lex->analyze_stmt) { char buff[MYSQL_ERRMSG_SIZE]; - my_snprintf(buff, sizeof(buff), ER(ER_UPDATE_INFO), (ulong) found, + my_snprintf(buff, sizeof(buff), ER_THD(thd, ER_UPDATE_INFO), (ulong) found, (ulong) updated, - (ulong) thd->warning_info->statement_warn_count()); + (ulong) thd->get_stmt_da()->current_statement_warn_count()); my_ok(thd, (thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated, id, buff); DBUG_PRINT("info",("%ld records updated", (long) updated)); @@ -974,6 +1048,11 @@ int mysql_update(THD *thd, } *found_return= found; *updated_return= updated; + + + if (thd->lex->analyze_stmt) + goto emit_explain_and_leave; + DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); err: @@ -982,6 +1061,20 @@ err: table->disable_keyread(); thd->abort_on_warning= 0; DBUG_RETURN(1); + +produce_explain_and_leave: + /* + We come here for various "degenerate" query plans: impossible WHERE, + no-partitions-used, impossible-range, etc. + */ + query_plan.save_explain_update_data(query_plan.mem_root, thd); + +emit_explain_and_leave: + int err2= thd->lex->explain->send_explain(thd); + + delete select; + free_underlaid_joins(thd, select_lex); + DBUG_RETURN((err2 || thd->is_error()) ? 1 : 0); } /* @@ -1119,7 +1212,7 @@ bool unsafe_key_update(List<TABLE_LIST> leaves, table_map tables_for_update) while ((tl= it++)) { - if (tl->table->map & tables_for_update) + if (!tl->is_jtbm() && (tl->table->map & tables_for_update)) { TABLE *table1= tl->table; bool primkey_clustered= (table1->file->primary_key_is_clustered() && @@ -1136,6 +1229,8 @@ bool unsafe_key_update(List<TABLE_LIST> leaves, table_map tables_for_update) it2.rewind(); while ((tl2= it2++)) { + if (tl2->is_jtbm()) + continue; /* Look at "next" tables only since all previous tables have already been checked @@ -1163,7 +1258,7 @@ bool unsafe_key_update(List<TABLE_LIST> leaves, table_map tables_for_update) // The primary key can cover multiple columns KEY key_info= table1->key_info[table1->s->primary_key]; KEY_PART_INFO *key_part= key_info.key_part; - KEY_PART_INFO *key_part_end= key_part + key_info.key_parts; + KEY_PART_INFO *key_part_end= key_part + key_info.user_defined_key_parts; for (;key_part != key_part_end; ++key_part) { @@ -1187,6 +1282,87 @@ bool unsafe_key_update(List<TABLE_LIST> leaves, table_map tables_for_update) return false; } +/** + Check if there is enough privilege on specific table used by the + main select list of multi-update directly or indirectly (through + a view). + + @param[in] thd Thread context. + @param[in] table Table list element for the table. + @param[in] tables_for_update Bitmap with tables being updated. + @param[in/out] updated_arg Set to true if table in question is + updated, also set to true if it is + a view and one of its underlying + tables is updated. Should be + initialized to false by the caller + before a sequence of calls to this + function. + + @note To determine which tables/views are updated we have to go from + leaves to root since tables_for_update contains map of leaf + tables being updated and doesn't include non-leaf tables + (fields are already resolved to leaf tables). + + @retval false - Success, all necessary privileges on all tables are + present or might be present on column-level. + @retval true - Failure, some necessary privilege on some table is + missing. +*/ + +static bool multi_update_check_table_access(THD *thd, TABLE_LIST *table, + table_map tables_for_update, + bool *updated_arg) +{ + if (table->view) + { + bool updated= false; + /* + If it is a mergeable view then we need to check privileges on its + underlying tables being merged (including views). We also need to + check if any of them is updated in order to find if this view is + updated. + If it is a non-mergeable view then it can't be updated. + */ + DBUG_ASSERT(table->merge_underlying_list || + (!table->updatable && + !(table->table->map & tables_for_update))); + + for (TABLE_LIST *tbl= table->merge_underlying_list; tbl; + tbl= tbl->next_local) + { + if (multi_update_check_table_access(thd, tbl, tables_for_update, + &updated)) + { + tbl->hide_view_error(thd); + return true; + } + } + if (check_table_access(thd, updated ? UPDATE_ACL: SELECT_ACL, table, + FALSE, 1, FALSE)) + return true; + *updated_arg|= updated; + /* We only need SELECT privilege for columns in the values list. */ + table->grant.want_privilege= SELECT_ACL & ~table->grant.privilege; + } + else + { + /* Must be a base or derived table. */ + const bool updated= table->table->map & tables_for_update; + if (check_table_access(thd, updated ? UPDATE_ACL : SELECT_ACL, table, + FALSE, 1, FALSE)) + return true; + *updated_arg|= updated; + /* We only need SELECT privilege for columns in the values list. */ + if (!table->derived) + { + table->grant.want_privilege= SELECT_ACL & ~table->grant.privilege; + table->table->grant.want_privilege= (SELECT_ACL & + ~table->table->grant.privilege); + } + } + return false; +} + /* make update specific preparation and checks after opening tables @@ -1285,11 +1461,9 @@ int mysql_multi_update_prepare(THD *thd) while ((tl= ti++)) { TABLE *table= tl->table; - /* Only set timestamp column if this is not modified */ - if (table->timestamp_field && - bitmap_is_set(table->write_set, - table->timestamp_field->field_index)) - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; + + if (tl->is_jtbm()) + continue; /* if table will be updated then check that it is unique */ if (table->map & tables_for_update) @@ -1318,32 +1492,38 @@ int mysql_multi_update_prepare(THD *thd) another table instance used by this statement which is going to be write-locked (for example, trigger to be invoked might try to update this table). + Last argument routine_modifies_data for read_lock_type_for_table() + is ignored, as prelocking placeholder will never be set here. */ + DBUG_ASSERT(tl->prelocking_placeholder == false); + thr_lock_type lock_type= read_lock_type_for_table(thd, lex, tl, true); if (using_lock_tables) - tl->lock_type= read_lock_type_for_table(thd, lex, tl); + tl->lock_type= lock_type; else - tl->set_lock_type(thd, read_lock_type_for_table(thd, lex, tl)); + tl->set_lock_type(thd, lock_type); tl->updating= 0; } } + + /* + Check access privileges for tables being updated or read. + Note that unlike in the above loop we need to iterate here not only + through all leaf tables but also through all view hierarchy. + */ for (tl= table_list; tl; tl= tl->next_local) { - /* Check access privileges for table */ - if (!tl->is_derived()) - { - uint want_privilege= tl->updating ? UPDATE_ACL : SELECT_ACL; - if (check_access(thd, want_privilege, tl->db, - &tl->grant.privilege, - &tl->grant.m_internal, - 0, 0) || - check_grant(thd, want_privilege, tl, FALSE, 1, FALSE)) - DBUG_RETURN(TRUE); - } + bool not_used= false; + if (tl->is_jtbm()) + continue; + if (multi_update_check_table_access(thd, tl, tables_for_update, ¬_used)) + DBUG_RETURN(TRUE); } /* check single table update for view compound from several tables */ for (tl= table_list; tl; tl= tl->next_local) { + if (tl->is_jtbm()) + continue; if (tl->is_merged_derived()) { TABLE_LIST *for_update= 0; @@ -1362,6 +1542,7 @@ int mysql_multi_update_prepare(THD *thd) { DBUG_RETURN(TRUE); } + (void) read_statistics_for_tables_if_needed(thd, table_list); /* @todo: downgrade the metadata locks here. */ /* @@ -1373,6 +1554,8 @@ int mysql_multi_update_prepare(THD *thd) ti.rewind(); while ((tl= ti++)) { + if (tl->is_jtbm()) + continue; TABLE *table= tl->table; TABLE_LIST *tlist; if (!(tlist= tl->top_table())->derived) @@ -1425,19 +1608,16 @@ bool mysql_multi_update(THD *thd, { bool res; DBUG_ENTER("mysql_multi_update"); - - if (!(*result= new multi_update(table_list, - &thd->lex->select_lex.leaf_tables, - fields, values, - handle_duplicates, ignore))) + + if (!(*result= new (thd->mem_root) multi_update(thd, table_list, + &thd->lex->select_lex.leaf_tables, + fields, values, + handle_duplicates, ignore))) { DBUG_RETURN(TRUE); } - thd->abort_on_warning= test(thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | - MODE_STRICT_ALL_TABLES)); - + thd->abort_on_warning= !ignore && thd->is_strict_mode(); List<Item> total_list; res= mysql_select(thd, &select_lex->ref_pointer_array, @@ -1453,21 +1633,27 @@ bool mysql_multi_update(THD *thd, res|= thd->is_error(); if (unlikely(res)) (*result)->abort_result_set(); + else + { + if (thd->lex->describe || thd->lex->analyze_stmt) + res= thd->lex->explain->send_explain(thd); + } thd->abort_on_warning= 0; DBUG_RETURN(res); } -multi_update::multi_update(TABLE_LIST *table_list, +multi_update::multi_update(THD *thd_arg, TABLE_LIST *table_list, List<TABLE_LIST> *leaves_list, List<Item> *field_list, List<Item> *value_list, enum enum_duplicates handle_duplicates_arg, - bool ignore_arg) - :all_tables(table_list), leaves(leaves_list), update_tables(0), + bool ignore_arg): + select_result_interceptor(thd_arg), + all_tables(table_list), leaves(leaves_list), update_tables(0), tmp_tables(0), updated(0), found(0), fields(field_list), values(value_list), table_count(0), copy_field(0), handle_duplicates(handle_duplicates_arg), do_update(1), trans_safe(1), - transactional_tables(0), ignore(ignore_arg), error_handled(0) + transactional_tables(0), ignore(ignore_arg), error_handled(0), prepared(0) {} @@ -1490,15 +1676,19 @@ int multi_update::prepare(List<Item> ¬_used_values, List_iterator<TABLE_LIST> ti(*leaves); DBUG_ENTER("multi_update::prepare"); + if (prepared) + DBUG_RETURN(0); + prepared= true; + thd->count_cuted_fields= CHECK_FIELD_WARN; thd->cuted_fields=0L; - thd_proc_info(thd, "updating main table"); + THD_STAGE_INFO(thd, stage_updating_main_table); tables_to_update= get_table_map(fields); if (!tables_to_update) { - my_message(ER_NO_TABLES_USED, ER(ER_NO_TABLES_USED), MYF(0)); + my_message(ER_NO_TABLES_USED, ER_THD(thd, ER_NO_TABLES_USED), MYF(0)); DBUG_RETURN(1); } @@ -1509,6 +1699,9 @@ int multi_update::prepare(List<Item> ¬_used_values, */ while ((table_ref= ti++)) { + if (table_ref->is_jtbm()) + continue; + TABLE *table= table_ref->table; if (tables_to_update & table->map) { @@ -1528,6 +1721,9 @@ int multi_update::prepare(List<Item> ¬_used_values, ti.rewind(); while ((table_ref= ti++)) { + if (table_ref->is_jtbm()) + continue; + TABLE *table= table_ref->table; if (tables_to_update & table->map) { @@ -1539,8 +1735,7 @@ int multi_update::prepare(List<Item> ¬_used_values, to compare records and detect data change. */ if ((table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) && - (((uint) table->timestamp_field_type) & - TIMESTAMP_AUTO_SET_ON_UPDATE)) + table->default_field && table->has_default_function(true)) bitmap_union(table->read_set, table->write_set); } } @@ -1559,6 +1754,8 @@ int multi_update::prepare(List<Item> ¬_used_values, while ((table_ref= ti++)) { /* TODO: add support of view of join support */ + if (table_ref->is_jtbm()) + continue; TABLE *table=table_ref->table; leaf_table_count++; if (tables_to_update & table->map) @@ -1572,21 +1769,11 @@ int multi_update::prepare(List<Item> ¬_used_values, table->no_keyread=1; table->covering_keys.clear_all(); table->pos_in_table_list= tl; - if (table->triggers && - table->triggers->has_triggers(TRG_EVENT_UPDATE, - TRG_ACTION_AFTER)) - { - /* - The table has AFTER UPDATE triggers that might access to subject - table and therefore might need update to be done immediately. - So we turn-off the batching. - */ - (void) table->file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH); - } + table->prepare_triggers_for_update_stmt_or_event(); + table->reset_default_fields(); } } - table_count= update.elements; update_tables= update.first; @@ -1613,8 +1800,8 @@ int multi_update::prepare(List<Item> ¬_used_values, { Item *value= value_it++; uint offset= item->field->table->pos_in_table_list->shared; - fields_for_table[offset]->push_back(item); - values_for_table[offset]->push_back(value); + fields_for_table[offset]->push_back(item, thd->mem_root); + values_for_table[offset]->push_back(value, thd->mem_root); } if (thd->is_fatal_error) DBUG_RETURN(1); @@ -1622,7 +1809,15 @@ int multi_update::prepare(List<Item> ¬_used_values, /* Allocate copy fields */ max_fields=0; for (i=0 ; i < table_count ; i++) + { set_if_bigger(max_fields, fields_for_table[i]->elements + leaf_table_count); + if (fields_for_table[i]->elements) + { + TABLE *table= ((Item_field*)(fields_for_table[i]->head()))->field->table; + switch_to_nullable_trigger_fields(*fields_for_table[i], table); + switch_to_nullable_trigger_fields(*values_for_table[i], table); + } + } copy_field= new Copy_field[max_fields]; DBUG_RETURN(thd->is_fatal_error != 0); } @@ -1818,6 +2013,13 @@ loop_end: TABLE *tbl= table; do { + /* + Signal each table (including tables referenced by WITH CHECK OPTION + clause) for which we will store row position in the temporary table + that we need a position to be read first. + */ + tbl->prepare_for_position(); + Field_string *field= new Field_string(tbl->file->ref_length, 0, tbl->alias.c_ptr(), &my_charset_bin); @@ -1829,15 +2031,15 @@ loop_end: table to be updated was created by mysql 4.1. Deny this. */ field->can_alter_field_type= 0; - Item_field *ifield= new Item_field((Field *) field); + Item_field *ifield= new (thd->mem_root) Item_field(join->thd, (Field *) field); if (!ifield) DBUG_RETURN(1); ifield->maybe_null= 0; - if (temp_fields.push_back(ifield)) + if (temp_fields.push_back(ifield, thd->mem_root)) DBUG_RETURN(1); } while ((tbl= tbl_it++)); - temp_fields.concat(fields_for_table[cnt]); + temp_fields.append(fields_for_table[cnt]); /* Make an unique key over the first field to avoid duplicated updates */ bzero((char*) &group, sizeof(group)); @@ -1928,9 +2130,8 @@ int multi_update::send_data(List<Item> ¬_used_values) table->status|= STATUS_UPDATED; store_record(table,record[1]); - if (fill_record_n_invoke_before_triggers(thd, *fields_for_table[offset], + if (fill_record_n_invoke_before_triggers(thd, table, *fields_for_table[offset], *values_for_table[offset], 0, - table->triggers, TRG_EVENT_UPDATE)) DBUG_RETURN(1); @@ -1943,6 +2144,10 @@ int multi_update::send_data(List<Item> ¬_used_values) if (!can_compare_record || compare_record(table)) { int error; + + if (table->default_field && table->update_default_fields()) + DBUG_RETURN(1); + if ((error= cur_table->view_check_option(thd, ignore)) != VIEW_CHECK_OK) { @@ -2032,7 +2237,7 @@ int multi_update::send_data(List<Item> ¬_used_values) } while ((tbl= tbl_it++)); /* Store regular updated fields in the row. */ - fill_record(thd, + fill_record(thd, tmp_table, tmp_table->field + 1 + unupdated_check_opt_tables.elements, *values_for_table[offset], TRUE, FALSE); @@ -2057,13 +2262,6 @@ int multi_update::send_data(List<Item> ¬_used_values) } -void multi_update::send_error(uint errcode,const char *err) -{ - /* First send error what ever it is ... */ - my_error(errcode, MYF(0), err); -} - - void multi_update::abort_result_set() { /* the error was handled or nothing deleted and no side effects return */ @@ -2098,7 +2296,7 @@ void multi_update::abort_result_set() The query has to binlog because there's a modified non-transactional table either from the query's list or via a stored routine: bug#13270,23333 */ - if (mysql_bin_log.is_open()) + if (WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) { /* THD::killed status might not have been set ON at time of an error @@ -2113,6 +2311,8 @@ void multi_update::abort_result_set() } thd->transaction.all.modified_non_trans_table= TRUE; } + thd->transaction.all.m_unsafe_rollback_flags|= + (thd->transaction.stmt.m_unsafe_rollback_flags & THD_TRANS::DID_WAIT); DBUG_ASSERT(trans_safe || !updated || thd->transaction.stmt.modified_non_trans_table); } @@ -2150,7 +2350,7 @@ int multi_update::do_updates() check_opt_it.rewind(); while(TABLE *tbl= check_opt_it++) { - if ((local_error= tbl->file->ha_rnd_init(1))) + if ((local_error= tbl->file->ha_rnd_init(0))) { err_table= tbl; goto err; @@ -2222,7 +2422,10 @@ int multi_update::do_updates() for (copy_field_ptr=copy_field; copy_field_ptr != copy_field_end; copy_field_ptr++) + { (*copy_field_ptr->do_copy)(copy_field_ptr); + copy_field_ptr->to_field->set_has_explicit_value(); + } if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, @@ -2232,6 +2435,8 @@ int multi_update::do_updates() if (!can_compare_record || compare_record(table)) { int error; + if (table->default_field && (error= table->update_default_fields())) + goto err2; if (table->vfield && update_virtual_fields(thd, table, VCOL_UPDATE_FOR_WRITE)) goto err2; @@ -2328,7 +2533,7 @@ bool multi_update::send_eof() ulonglong id; killed_state killed_status= NOT_KILLED; DBUG_ENTER("multi_update::send_eof"); - thd_proc_info(thd, "updating reference tables"); + THD_STAGE_INFO(thd, stage_updating_reference_tables); /* Does updates for the last n - 1 tables, returns 0 if ok; @@ -2342,7 +2547,7 @@ bool multi_update::send_eof() later carried out killing should not affect binlogging. */ killed_status= (local_error == 0) ? NOT_KILLED : thd->killed; - thd_proc_info(thd, "end"); + THD_STAGE_INFO(thd, stage_end); /* We must invalidate the query cache before binlog writing and ha_autocommit_... */ @@ -2362,10 +2567,12 @@ bool multi_update::send_eof() if (thd->transaction.stmt.modified_non_trans_table) thd->transaction.all.modified_non_trans_table= TRUE; + thd->transaction.all.m_unsafe_rollback_flags|= + (thd->transaction.stmt.m_unsafe_rollback_flags & THD_TRANS::DID_WAIT); if (local_error == 0 || thd->transaction.stmt.modified_non_trans_table) { - if (mysql_bin_log.is_open()) + if (WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) { int errcode= 0; if (local_error == 0) @@ -2384,21 +2591,24 @@ bool multi_update::send_eof() thd->transaction.stmt.modified_non_trans_table); if (local_error != 0) - error_handled= TRUE; // to force early leave from ::send_error() + error_handled= TRUE; // to force early leave from ::abort_result_set() if (local_error > 0) // if the above log write did not fail ... { /* Safety: If we haven't got an error before (can happen in do_updates) */ - my_message(ER_UNKNOWN_ERROR, "An error occured in multi-table update", + my_message(ER_UNKNOWN_ERROR, "An error occurred in multi-table update", MYF(0)); DBUG_RETURN(TRUE); } - id= thd->arg_of_last_insert_id_function ? + if (!thd->lex->analyze_stmt) + { + id= thd->arg_of_last_insert_id_function ? thd->first_successful_insert_id_in_prev_stmt : 0; - my_snprintf(buff, sizeof(buff), ER(ER_UPDATE_INFO), - (ulong) found, (ulong) updated, (ulong) thd->cuted_fields); - ::my_ok(thd, (thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated, - id, buff); + my_snprintf(buff, sizeof(buff), ER_THD(thd, ER_UPDATE_INFO), + (ulong) found, (ulong) updated, (ulong) thd->cuted_fields); + ::my_ok(thd, (thd->client_capabilities & CLIENT_FOUND_ROWS) ? found : updated, + id, buff); + } DBUG_RETURN(FALSE); } diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 5bd82fdd842..4d7c3de9337 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2004, 2013, Oracle and/or its affiliates. - Copyright (c) 2011, 2014, SkySQL Ab. + Copyright (c) 2011, 2015, 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 @@ -16,7 +16,7 @@ */ #define MYSQL_LEX 1 -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "unireg.h" #include "sql_view.h" @@ -33,7 +33,8 @@ #include "sp_head.h" #include "sp.h" #include "sp_cache.h" -#include "datadict.h" // dd_frm_type() +#include "datadict.h" // dd_frm_is_view() +#include "sql_derived.h" #define MD5_BUFF_LENGTH 33 @@ -210,16 +211,12 @@ static void make_valid_column_names(List<Item> &item_list) static bool fill_defined_view_parts (THD *thd, TABLE_LIST *view) { - char key[MAX_DBKEY_LENGTH]; - uint key_length; LEX *lex= thd->lex; TABLE_LIST decoy; - memcpy (&decoy, view, sizeof (TABLE_LIST)); - key_length= create_table_def_key(thd, key, view, 0); - - if (tdc_open_view(thd, &decoy, decoy.alias, key, key_length, - thd->mem_root, OPEN_VIEW_NO_PARSE)) + decoy= *view; + decoy.mdl_request.key.mdl_key_init(&view->mdl_request.key); + if (tdc_open_view(thd, &decoy, decoy.alias, OPEN_VIEW_NO_PARSE)) return TRUE; if (!lex->definer) @@ -248,7 +245,7 @@ fill_defined_view_parts (THD *thd, TABLE_LIST *view) @param mode VIEW_CREATE_NEW, VIEW_ALTER, VIEW_CREATE_OR_REPLACE @retval FALSE Operation was a success. - @retval TRUE An error occured. + @retval TRUE An error occurred. */ bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view, @@ -334,12 +331,11 @@ bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view, { if (!tbl->table_in_first_from_clause) { - if (check_access(thd, SELECT_ACL, tbl->db, - &tbl->grant.privilege, - &tbl->grant.m_internal, - 0, 0) || - check_grant(thd, SELECT_ACL, tbl, FALSE, 1, FALSE)) + if (check_single_table_access(thd, SELECT_ACL, tbl, FALSE)) + { + tbl->hide_view_error(thd); goto err; + } } } } @@ -353,7 +349,7 @@ bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view, while ((item= it++)) { Item_field *field; - if ((field= item->filed_for_view_update())) + if ((field= item->field_for_view_update())) { /* any_privileges may be reset later by the Item_field::set_field @@ -391,7 +387,7 @@ bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view, @note This function handles both create and alter view commands. @retval FALSE Operation was a success. - @retval TRUE An error occured. + @retval TRUE An error occurred. */ bool mysql_create_view(THD *thd, TABLE_LIST *views, @@ -432,8 +428,19 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, lex->link_first_table_back(view, link_to_local); view->open_type= OT_BASE_ONLY; + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); + + /* + ignore lock specs for CREATE statement + */ + if (lex->current_select->lock_type != TL_READ_DEFAULT) + { + lex->current_select->set_lock_for_tables(TL_READ_DEFAULT); + view->mdl_request.set_type(MDL_EXCLUSIVE); + } - if (open_and_lock_tables(thd, lex->query_tables, TRUE, 0)) + if (open_temporary_tables(thd, lex->query_tables) || + open_and_lock_tables(thd, lex->query_tables, TRUE, 0)) { view= lex->unlink_first_table(&link_to_local); res= TRUE; @@ -467,60 +474,9 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, } sp_cache_invalidate(); + if (sp_process_definer(thd)) + goto err; - if (!lex->definer) - { - /* - DEFINER-clause is missing; we have to create default definer in - persistent arena to be PS/SP friendly. - If this is an ALTER VIEW then the current user should be set as - the definer. - */ - Query_arena original_arena; - Query_arena *ps_arena = thd->activate_stmt_arena_if_needed(&original_arena); - - if (!(lex->definer= create_default_definer(thd))) - res= TRUE; - - if (ps_arena) - thd->restore_active_arena(ps_arena, &original_arena); - - if (res) - goto err; - } - -#ifndef NO_EMBEDDED_ACCESS_CHECKS - /* - check definer of view: - - same as current user - - current user has SUPER_ACL - */ - if (lex->definer && - (strcmp(lex->definer->user.str, thd->security_ctx->priv_user) != 0 || - my_strcasecmp(system_charset_info, - lex->definer->host.str, - thd->security_ctx->priv_host) != 0)) - { - if (!(thd->security_ctx->master_access & SUPER_ACL)) - { - my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER"); - res= TRUE; - goto err; - } - else - { - if (!is_acl_user(lex->definer->host.str, - lex->definer->user.str)) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_NO_SUCH_USER, - ER(ER_NO_SUCH_USER), - lex->definer->user.str, - lex->definer->host.str); - } - } - } -#endif /* check that tables are not temporary and this VIEW do not used in query (it is possible with ALTERing VIEW). @@ -587,7 +543,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, if (lex->view_list.elements != select_lex->item_list.elements) { - my_message(ER_VIEW_WRONG_LIST, ER(ER_VIEW_WRONG_LIST), MYF(0)); + my_message(ER_VIEW_WRONG_LIST, ER_THD(thd, ER_VIEW_WRONG_LIST), MYF(0)); res= TRUE; goto err; } @@ -638,7 +594,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, Item *item; while ((item= it++)) { - Item_field *fld= item->filed_for_view_update(); + Item_field *fld= item->field_for_view_update(); uint priv= (get_column_grant(thd, &view->grant, view->db, view->table_name, item->name) & VIEW_ANY_ACL); @@ -668,6 +624,15 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, res= mysql_register_view(thd, view, mode); + /* + View TABLE_SHARE must be removed from the table definition cache in order to + make ALTER VIEW work properly. Otherwise, we would not be able to detect + meta-data changes after ALTER VIEW. + */ + + if (!res) + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, view->db, view->table_name, false); + if (!res && mysql_bin_log.is_open()) { String buff; @@ -680,6 +645,11 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, command[thd->lex->create_view_mode].length); view_store_options(thd, views, &buff); buff.append(STRING_WITH_LEN("VIEW ")); + + /* Appending IF NOT EXISTS if present in the query */ + if (lex->create_info.if_not_exists()) + buff.append(STRING_WITH_LEN("IF NOT EXISTS ")); + /* Test if user supplied a db (ie: we did not use thd->db) */ if (views->db && views->db[0] && (thd->db == NULL || strcmp(views->db, thd->db))) @@ -721,8 +691,12 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, lex->link_first_table_back(view, link_to_local); DBUG_RETURN(0); + +WSREP_ERROR_LABEL: + res= TRUE; + err: - thd_proc_info(thd, "end"); + THD_STAGE_INFO(thd, stage_end); lex->link_first_table_back(view, link_to_local); unit->cleanup(); DBUG_RETURN(res || thd->is_error()); @@ -917,15 +891,8 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, View definition query is stored in the client character set. */ - char view_query_buff[4096]; - String view_query(view_query_buff, - sizeof (view_query_buff), - thd->charset()); - - char is_query_buff[4096]; - String is_query(is_query_buff, - sizeof (is_query_buff), - system_charset_info); + StringBuffer<4096> view_query(thd->charset()); + StringBuffer<4096> is_query(system_charset_info); char md5[MD5_BUFF_LENGTH]; bool can_be_merged; @@ -941,9 +908,11 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, ulong sql_mode= thd->variables.sql_mode & MODE_ANSI_QUOTES; thd->variables.sql_mode&= ~MODE_ANSI_QUOTES; - lex->unit.print(&view_query, QT_VIEW_INTERNAL); - lex->unit.print(&is_query, - enum_query_type(QT_TO_SYSTEM_CHARSET | QT_WITHOUT_INTRODUCERS)); + lex->unit.print(&view_query, enum_query_type(QT_VIEW_INTERNAL | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); + lex->unit.print(&is_query, enum_query_type(QT_TO_SYSTEM_CHARSET | + QT_WITHOUT_INTRODUCERS | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); thd->variables.sql_mode|= sql_mode; } @@ -953,14 +922,18 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, view->source= thd->lex->create_view_select; if (!thd->make_lex_string(&view->select_stmt, view_query.ptr(), - view_query.length(), false)) + view_query.length())) { my_error(ER_OUT_OF_RESOURCES, MYF(0)); error= -1; goto err; } - view->file_version= 1; + /* + version 1 - before 10.0.5 + version 2 - empty definer_host means a role + */ + view->file_version= 2; view->mariadb_version= MYSQL_VERSION_ID; view->calc_md5(md5); if (!(view->md5.str= (char*) thd->memdup(md5, 32))) @@ -974,8 +947,8 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, if (lex->create_view_algorithm == VIEW_ALGORITHM_MERGE && !lex->can_be_merged()) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_VIEW_MERGE, - ER(ER_WARN_VIEW_MERGE)); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_VIEW_MERGE, + ER_THD(thd, ER_WARN_VIEW_MERGE)); lex->create_view_algorithm= DTYPE_ALGORITHM_UNDEFINED; } view->algorithm= lex->create_view_algorithm; @@ -983,6 +956,14 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, view->definer.host= lex->definer->host; view->view_suid= lex->create_view_suid; view->with_check= lex->create_view_check; + + DBUG_EXECUTE_IF("simulate_register_view_failure", + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + error= -1; + goto err; + }); + if ((view->updatable_view= (can_be_merged && view->algorithm != VIEW_ALGORITHM_TMPTABLE))) { @@ -1025,9 +1006,17 @@ loop_out: fn_format(path_buff, file.str, dir.str, "", MY_UNPACK_FILENAME); path.length= strlen(path_buff); - if (!access(path.str, F_OK)) + if (ha_table_exists(thd, view->db, view->table_name, NULL)) { - if (mode == VIEW_CREATE_NEW) + if (lex->create_info.if_not_exists()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_TABLE_EXISTS_ERROR, + ER_THD(thd, ER_TABLE_EXISTS_ERROR), + view->table_name); + DBUG_RETURN(0); + } + else if (mode == VIEW_CREATE_NEW) { my_error(ER_TABLE_EXISTS_ERROR, MYF(0), view->alias); error= -1; @@ -1079,7 +1068,7 @@ loop_out: view->view_creation_ctx->get_connection_cl()->name); if (!thd->make_lex_string(&view->view_body_utf8, is_query.ptr(), - is_query.length(), false)) + is_query.length())) { my_error(ER_OUT_OF_RESOURCES, MYF(0)); error= -1; @@ -1131,35 +1120,36 @@ err: -/* +/** read VIEW .frm and create structures - SYNOPSIS - mysql_make_view() - thd Thread handle - parser parser object - table TABLE_LIST structure for filling - flags flags - RETURN - 0 ok - 1 error -*/ + @param[in] thd Thread handler + @param[in] share Share object of view + @param[in] table TABLE_LIST structure for filling + @param[in] open_view_no_parse Flag to indicate open view but + do not parse. -bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, - uint flags) + @return false-in case of success, true-in case of error. +*/ +bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table, + bool open_view_no_parse) { - SELECT_LEX *end, *view_select; + SELECT_LEX *end, *UNINIT_VAR(view_select); LEX *old_lex, *lex; Query_arena *arena, backup; TABLE_LIST *top_view= table->top_table(); - bool parse_status; + bool UNINIT_VAR(parse_status); bool result, view_is_mergeable; TABLE_LIST *UNINIT_VAR(view_main_select_tables); DBUG_ENTER("mysql_make_view"); DBUG_PRINT("info", ("table: 0x%lx (%s)", (ulong) table, table->table_name)); - LINT_INIT(parse_status); - LINT_INIT(view_select); + if (table->required_type == FRMTYPE_TABLE) + { + my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, + "BASE TABLE"); + DBUG_RETURN(true); + } if (table->view) { @@ -1180,6 +1170,16 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, DBUG_PRINT("info", ("VIEW %s.%s is already processed on previous PS/SP execution", table->view_db.str, table->view_name.str)); + + /* + Clear old variables in the TABLE_LIST that could be left from an old view + This is only needed if there was an error at last usage of view, + in which case the reinit call wasn't done. + See MDEV-6668 for details. + */ + mysql_derived_reinit(thd, NULL, table); + + DEBUG_SYNC(thd, "after_cached_view_opened"); DBUG_RETURN(0); } @@ -1224,12 +1224,14 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, table->definer.user.length= table->definer.host.length= 0; /* - TODO: when VIEWs will be stored in cache, table mem_root should - be used here + TODO: when VIEWs will be stored in cache (not only parser), + table mem_root should be used here */ - if ((result= parser->parse((uchar*)table, thd->mem_root, - view_parameters, required_view_parameters, - &file_parser_dummy_hook))) + DBUG_ASSERT(share->view_def != NULL); + if ((result= share->view_def->parse((uchar*)table, thd->mem_root, + view_parameters, + required_view_parameters, + &file_parser_dummy_hook))) goto end; /* @@ -1240,11 +1242,19 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, DBUG_ASSERT(!table->definer.host.str && !table->definer.user.length && !table->definer.host.length); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_VIEW_FRM_NO_USER, ER(ER_VIEW_FRM_NO_USER), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_VIEW_FRM_NO_USER, ER_THD(thd, ER_VIEW_FRM_NO_USER), table->db, table->table_name); - get_default_definer(thd, &table->definer); + get_default_definer(thd, &table->definer, false); } + + /* + since 10.0.5 definer.host can never be "" for a User, but it's + always "" for a Role. Before 10.0.5 it could be "" for a User, + but roles didn't exist. file_version helps. + */ + if (!table->definer.host.str[0] && table->file_version < 2) + table->definer.host= host_not_specified; // User, not Role /* Initialize view definition context by character set names loaded from @@ -1253,7 +1263,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, */ table->view_creation_ctx= View_creation_ctx::create(thd, table); - if (flags & OPEN_VIEW_NO_PARSE) + if (open_view_no_parse) { if (arena) thd->restore_active_arena(arena, &backup); @@ -1283,6 +1293,11 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, */ table->open_type= OT_BASE_ONLY; + /* + Clear old variables in the TABLE_LIST that could be left from an old view + */ + table->merged_for_insert= FALSE; + /*TODO: md5 test here and warning if it is differ */ @@ -1292,6 +1307,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, now Lex placed in statement memory */ + table->view= lex= thd->lex= (LEX*) new(thd->mem_root) st_lex_local; if (!table->view) { @@ -1317,8 +1333,9 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, goto end; lex_start(thd); + lex->stmt_lex= old_lex; view_select= &lex->select_lex; - view_select->select_number= ++thd->select_number; + view_select->select_number= ++thd->lex->stmt_lex->current_select_number; ulonglong saved_mode= thd->variables.sql_mode; /* switch off modes which can prevent normal parsing of VIEW @@ -1368,15 +1385,15 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, TABLE_LIST *view_tables= lex->query_tables; TABLE_LIST *view_tables_tail= 0; TABLE_LIST *tbl; - Security_context *security_ctx; + Security_context *security_ctx= 0; /* - Check rights to run commands (EXPLAIN SELECT & SHOW CREATE) which show - underlying tables. + Check rights to run commands (ANALYZE SELECT, EXPLAIN SELECT & + SHOW CREATE) which show underlying tables. Skip this step if we are opening view for prelocking only. */ - if (!table->prelocking_placeholder && - (old_lex->sql_command == SQLCOM_SELECT && old_lex->describe)) + if (!table->prelocking_placeholder && (old_lex->describe || + old_lex->analyze_stmt)) { /* The user we run EXPLAIN as (either the connected user who issued @@ -1414,7 +1431,8 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, check_table_access(thd, SHOW_VIEW_ACL, &view_no_suid, FALSE, UINT_MAX, TRUE)) { - my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0)); + my_message(ER_VIEW_NO_EXPLAIN, ER_THD(thd, ER_VIEW_NO_EXPLAIN), + MYF(0)); goto err; } } @@ -1509,8 +1527,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local) { tbl->lock_type= table->lock_type; - tbl->mdl_request.set_type((tbl->lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_WRITE : MDL_SHARED_READ); + tbl->mdl_request.set_type(table->mdl_request.type); } /* If the view is mergeable, we might want to @@ -1548,6 +1565,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, if (view_select->options & OPTION_TO_QUERY_CACHE) old_lex->select_lex.options|= OPTION_TO_QUERY_CACHE; +#ifndef NO_EMBEDDED_ACCESS_CHECKS if (table->view_suid) { /* @@ -1568,6 +1586,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, */ security_ctx= table->security_ctx; } +#endif /* Assign the context to the tables referenced in the view */ if (view_tables) @@ -1645,9 +1664,9 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, lex->select_lex.order_list.elements && !table->select_lex->master_unit()->is_union()) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_VIEW_ORDERBY_IGNORED, - ER(ER_VIEW_ORDERBY_IGNORED), + ER_THD(thd, ER_VIEW_ORDERBY_IGNORED), table->db, table->table_name); } } @@ -1694,6 +1713,7 @@ end: if (arena) thd->restore_active_arena(arena, &backup); thd->lex= old_lex; + status_var_increment(thd->status_var.opened_views); DBUG_RETURN(result); err: @@ -1727,7 +1747,6 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) String non_existant_views; char *wrong_object_db= NULL, *wrong_object_name= NULL; bool error= FALSE; - enum legacy_db_type not_used; bool some_views_deleted= FALSE; bool something_wrong= FALSE; DBUG_ENTER("mysql_drop_view"); @@ -1744,29 +1763,34 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) DBUG_RETURN(TRUE); } - if (lock_table_names(thd, views, 0, thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY)) + if (lock_table_names(thd, views, 0, thd->variables.lock_wait_timeout, 0)) DBUG_RETURN(TRUE); for (view= views; view; view= view->next_local) { - frm_type_enum type= FRMTYPE_ERROR; + bool not_exist; build_table_filename(path, sizeof(path) - 1, view->db, view->table_name, reg_ext, 0); - if (access(path, F_OK) || - FRMTYPE_VIEW != (type= dd_frm_type(thd, path, ¬_used))) + if ((not_exist= my_access(path, F_OK)) || !dd_frm_is_view(thd, path)) { char name[FN_REFLEN]; my_snprintf(name, sizeof(name), "%s.%s", view->db, view->table_name); - if (thd->lex->drop_if_exists) + if (thd->lex->if_exists()) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_BAD_TABLE_ERROR, + ER_THD(thd, ER_BAD_TABLE_ERROR), name); continue; } - if (type == FRMTYPE_TABLE) + if (not_exist) + { + if (non_existant_views.length()) + non_existant_views.append(','); + non_existant_views.append(name); + } + else { if (!wrong_object_name) { @@ -1774,12 +1798,6 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) wrong_object_name= view->table_name; } } - else - { - if (non_existant_views.length()) - non_existant_views.append(','); - non_existant_views.append(String(view->table_name,system_charset_info)); - } continue; } if (mysql_file_delete(key_file_frm, path, MYF(MY_WME))) @@ -1788,9 +1806,8 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) some_views_deleted= TRUE; /* - For a view, there is a TABLE_SHARE object, but its - ref_count never goes above 1. Remove it from the table - definition cache, in case the view was cached. + For a view, there is a TABLE_SHARE object. + Remove it from the table definition cache, in case the view was cached. */ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, view->db, view->table_name, FALSE); @@ -1897,7 +1914,7 @@ bool check_key_in_view(THD *thd, TABLE_LIST *view) if ((key_info->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME) { KEY_PART_INFO *key_part= key_info->key_part; - KEY_PART_INFO *key_part_end= key_part + key_info->key_parts; + KEY_PART_INFO *key_part_end= key_part + key_info->user_defined_key_parts; /* check that all key parts are used */ for (;;) @@ -1906,7 +1923,7 @@ bool check_key_in_view(THD *thd, TABLE_LIST *view) for (k= trans; k < end_of_trans; k++) { Item_field *field; - if ((field= k->item->filed_for_view_update()) && + if ((field= k->item->field_for_view_update()) && field->field == key_part->field) break; } @@ -1928,7 +1945,7 @@ bool check_key_in_view(THD *thd, TABLE_LIST *view) for (fld= trans; fld < end_of_trans; fld++) { Item_field *field; - if ((field= fld->item->filed_for_view_update()) && + if ((field= fld->item->field_for_view_update()) && field->field == *field_ptr) break; } @@ -1942,8 +1959,9 @@ bool check_key_in_view(THD *thd, TABLE_LIST *view) if (thd->variables.updatable_views_with_limit) { /* update allowed, but issue warning */ - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_WARN_VIEW_WITHOUT_KEY, ER(ER_WARN_VIEW_WITHOUT_KEY)); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_WARN_VIEW_WITHOUT_KEY, + ER_THD(thd, ER_WARN_VIEW_WITHOUT_KEY)); DBUG_RETURN(FALSE); } /* prohibit update */ @@ -1982,8 +2000,8 @@ bool insert_view_fields(THD *thd, List<Item> *list, TABLE_LIST *view) for (Field_translator *entry= trans; entry < trans_end; entry++) { Item_field *fld; - if ((fld= entry->item->filed_for_view_update())) - list->push_back(fld); + if ((fld= entry->item->field_for_view_update())) + list->push_back(fld, thd->mem_root); else { my_error(ER_NON_INSERTABLE_TABLE, MYF(0), view->alias, "INSERT"); @@ -2116,7 +2134,7 @@ mysql_rename_view(THD *thd, view definition parsing or use temporary 'view_def' object for it. */ - bzero(&view_def, sizeof(view_def)); + view_def.reset(); view_def.timestamp.str= view_def.timestamp_buffer; view_def.view_suid= TRUE; diff --git a/sql/sql_view.h b/sql/sql_view.h index 1026c57aaaf..ce83dc656ad 100644 --- a/sql/sql_view.h +++ b/sql/sql_view.h @@ -2,7 +2,8 @@ #define SQL_VIEW_INCLUDED /* -*- C++ -*- */ -/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. + Copyright (c) 2015, 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 @@ -34,8 +35,9 @@ bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view, bool mysql_create_view(THD *thd, TABLE_LIST *view, enum_view_create_mode mode); -bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, - uint flags); +bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table, + bool open_view_no_parse); + bool mysql_drop_view(THD *thd, TABLE_LIST *view, enum_drop_mode drop_mode); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ba0041cf477..5111f0690ab 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2010, 2015, MariaDB + Copyright (c) 2010, 2016, 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 @@ -23,8 +23,9 @@ */ %{ -#define YYLIP (& thd->m_parser_state->m_lip) -#define YYPS (& thd->m_parser_state->m_yacc) +#define YYLIP (& thd->m_parser_state->m_lip) +#define YYPS (& thd->m_parser_state->m_yacc) +#define YYCSCL (thd->variables.character_set_client) #define MYSQL_YACC #define YYINITDEPTH 100 @@ -32,32 +33,35 @@ #define Lex (thd->lex) #define Select Lex->current_select +#include <my_global.h> #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "sql_parse.h" /* comp_*_creator */ #include "sql_table.h" /* primary_key_name */ #include "sql_partition.h" /* mem_alloc_error, partition_info, HASH_PARTITION */ #include "sql_acl.h" /* *_ACL */ -#include "password.h" /* my_make_scrambled_password_323, my_make_scrambled_password */ #include "sql_class.h" /* Key_part_spec, enum_filetype, Diag_condition_item_name */ #include "slave.h" #include "lex_symbol.h" #include "item_create.h" #include "sp_head.h" -#include "sp_pcontext.h" #include "sp_rcontext.h" #include "sp.h" -#include "sql_alter.h" // Alter_table*_statement -#include "sql_truncate.h" // Truncate_statement -#include "sql_admin.h" // Analyze/Check..._table_stmt -#include "sql_partition_admin.h" // Alter_table_*_partition_stmt +#include "sql_show.h" +#include "sql_alter.h" // Sql_cmd_alter_table* +#include "sql_truncate.h" // Sql_cmd_truncate_table +#include "sql_admin.h" // Sql_cmd_analyze/Check..._table +#include "sql_partition_admin.h" // Sql_cmd_alter_table_*_part. +#include "sql_handler.h" // Sql_cmd_handler_* #include "sql_signal.h" +#include "sql_get_diagnostics.h" // Sql_cmd_get_diagnostics #include "event_parse_data.h" #include "create_options.h" #include <myisam.h> #include <myisammrg.h> #include "keycaches.h" #include "set_var.h" +#include "rpl_mi.h" +#include "lex_token.h" /* this is to get the bison compilation windows warnings out */ #ifdef _MSC_VER @@ -88,35 +92,15 @@ int yylex(void *yylval, void *yythd); YYABORT; \ } while (0) -#define MYSQL_YYABORT_UNLESS(A) \ - if (!(A)) \ - { \ - my_parse_error(ER(ER_SYNTAX_ERROR));\ - MYSQL_YYABORT; \ +#define MYSQL_YYABORT_UNLESS(A) \ + if (!(A)) \ + { \ + my_parse_error(thd, ER_SYNTAX_ERROR); \ + MYSQL_YYABORT; \ } -/* - Work around for broken code generated by bison 1.875. - - The code generated by bison 1.875a and later, bison 2.1 and bison 2.2 is ok. - With bison 1.875 however, the generated code contains: -<pre> - yyerrlab1: - #if defined (__GNUC_MINOR__) && 2093 <= (__GNUC__ * 1000 + __GNUC_MINOR__) - __attribute__ ((__unused__)) - #endif -</pre> - This usage of __attribute__ is illegal, so we remove it. - See the following references for details: - http://lists.gnu.org/archive/html/bug-bison/2004-02/msg00014.html - http://gcc.gnu.org/bugzilla/show_bug.cgi?id=14273 -*/ - -#if defined (__GNUC_MINOR__) && 2093 <= (__GNUC__ * 1000 + __GNUC_MINOR__) -#undef __attribute__ -#define __attribute__(X) -#endif - +#define my_yyabort_error(A) \ + do { my_error A; MYSQL_YYABORT; } while(0) #ifndef DBUG_OFF #define YYDEBUG 1 @@ -135,21 +119,34 @@ int yylex(void *yylval, void *yythd); parser. */ -void my_parse_error(const char *s) +static void my_parse_error_intern(THD *thd, const char *err_text, + const char *yytext) { - THD *thd= current_thd; - Lex_input_stream *lip= & thd->m_parser_state->m_lip; - - const char *yytext= lip->get_tok_start(); + Lex_input_stream *lip= &thd->m_parser_state->m_lip; if (!yytext) - yytext= ""; - + { + if (!(yytext= lip->get_tok_start())) + yytext= ""; + } /* Push an error into the error stack */ - ErrConvString err(yytext, strlen(yytext), thd->variables.character_set_client); - my_printf_error(ER_PARSE_ERROR, ER(ER_PARSE_ERROR), MYF(0), s, - err.ptr(), lip->yylineno); + ErrConvString err(yytext, strlen(yytext), + thd->variables.character_set_client); + my_printf_error(ER_PARSE_ERROR, ER_THD(thd, ER_PARSE_ERROR), MYF(0), + err_text, err.ptr(), lip->yylineno); +} + + +static void my_parse_error(THD *thd, uint err_number, const char *yytext=0) +{ + return my_parse_error_intern(thd, ER_THD(thd, err_number), yytext); +} + +void LEX::parse_error() +{ + my_parse_error(thd, ER_SYNTAX_ERROR); } + /** @brief Bison callback to report a syntax/OOM error @@ -181,8 +178,8 @@ void MYSQLerror(THD *thd, const char *s) /* "parse error" changed into "syntax error" between bison 1.75 and 1.875 */ if (strcmp(s,"parse error") == 0 || strcmp(s,"syntax error") == 0) - s= ER(ER_SYNTAX_ERROR); - my_parse_error(s); + s= ER_THD(thd, ER_SYNTAX_ERROR); + my_parse_error_intern(thd, s, 0); } @@ -220,59 +217,67 @@ static bool is_native_function(THD *thd, const LEX_STRING *name) } -/** - Helper action for a case statement (entering the CASE). - This helper is used for both 'simple' and 'searched' cases. - This helper, with the other case_stmt_action_..., is executed when - the following SQL code is parsed: -<pre> -CREATE PROCEDURE proc_19194_simple(i int) -BEGIN - DECLARE str CHAR(10); - - CASE i - WHEN 1 THEN SET str="1"; - WHEN 2 THEN SET str="2"; - WHEN 3 THEN SET str="3"; - ELSE SET str="unknown"; - END CASE; - - SELECT str; -END -</pre> - The actions are used to generate the following code: -<pre> -SHOW PROCEDURE CODE proc_19194_simple; -Pos Instruction -0 set str@1 NULL -1 set_case_expr (12) 0 i@0 -2 jump_if_not 5(12) (case_expr@0 = 1) -3 set str@1 _latin1'1' -4 jump 12 -5 jump_if_not 8(12) (case_expr@0 = 2) -6 set str@1 _latin1'2' -7 jump 12 -8 jump_if_not 11(12) (case_expr@0 = 3) -9 set str@1 _latin1'3' -10 jump 12 -11 set str@1 _latin1'unknown' -12 stmt 0 "SELECT str" -</pre> +static sp_head *make_sp_head(THD *thd, sp_name *name, + enum stored_procedure_type type) +{ + LEX *lex= thd->lex; + sp_head *sp; - @param lex the parser lex context -*/ + /* Order is important here: new - reset - init */ + if ((sp= new sp_head())) + { + sp->reset_thd_mem_root(thd); + sp->init(lex); + sp->m_type= type; + if (name) + sp->init_sp_name(thd, name); + sp->m_chistics= &lex->sp_chistics; + lex->sphead= sp; + } + bzero(&lex->sp_chistics, sizeof(lex->sp_chistics)); + return sp; +} -void case_stmt_action_case(LEX *lex) +static bool maybe_start_compound_statement(THD *thd) { - lex->sphead->new_cont_backpatch(NULL); + if (!thd->lex->sphead) + { + if (!make_sp_head(thd, NULL, TYPE_ENUM_PROCEDURE)) + return 1; - /* - BACKPATCH: Creating target label for the jump to - "case_stmt_action_end_case" - (Instruction 12 in the example) - */ + Lex->sp_chistics.suid= SP_IS_NOT_SUID; + Lex->sphead->set_body_start(thd, YYLIP->get_cpp_ptr()); + } + return 0; +} - lex->spcont->push_label((char *)"", lex->sphead->instructions()); +static bool push_sp_label(THD *thd, LEX_STRING label) +{ + sp_pcontext *ctx= thd->lex->spcont; + sp_label *lab= ctx->find_label(label); + + if (lab) + { + my_error(ER_SP_LABEL_REDEFINE, MYF(0), label.str); + return 1; + } + else + { + lab= thd->lex->spcont->push_label(thd, label, + thd->lex->sphead->instructions()); + lab->type= sp_label::ITERATION; + } + return 0; +} + +static bool push_sp_empty_label(THD *thd) +{ + if (maybe_start_compound_statement(thd)) + return 1; + /* Unlabeled controls get an empty label. */ + thd->lex->spcont->push_label(thd, empty_lex_str, + thd->lex->sphead->instructions()); + return 0; } /** @@ -293,8 +298,9 @@ int case_stmt_action_expr(LEX *lex, Item* expr) if (parsing_ctx->push_case_expr_id(case_expr_id)) return 1; - i= new sp_instr_set_case_expr(sp->instructions(), - parsing_ctx, case_expr_id, expr, lex); + i= new (lex->thd->mem_root) + sp_instr_set_case_expr(sp->instructions(), parsing_ctx, case_expr_id, expr, + lex); sp->add_cont_backpatch(i); return sp->add_instr(i); @@ -316,10 +322,12 @@ int case_stmt_action_when(LEX *lex, Item *when, bool simple) sp_instr_jump_if_not *i; Item_case_expr *var; Item *expr; + THD *thd= lex->thd; if (simple) { - var= new Item_case_expr(ctx->get_current_case_expr_id()); + var= new (thd->mem_root) + Item_case_expr(thd, ctx->get_current_case_expr_id()); #ifndef DBUG_OFF if (var) @@ -328,11 +336,11 @@ int case_stmt_action_when(LEX *lex, Item *when, bool simple) } #endif - expr= new Item_func_eq(var, when); - i= new sp_instr_jump_if_not(ip, ctx, expr, lex); + expr= new (thd->mem_root) Item_func_eq(thd, var, when); + i= new (thd->mem_root) sp_instr_jump_if_not(ip, ctx, expr, lex); } else - i= new sp_instr_jump_if_not(ip, ctx, when, lex); + i= new (thd->mem_root) sp_instr_jump_if_not(ip, ctx, when, lex); /* BACKPATCH: Registering forward jump from @@ -340,8 +348,8 @@ int case_stmt_action_when(LEX *lex, Item *when, bool simple) (jump_if_not from instruction 2 to 5, 5 to 8 ... in the example) */ - return !test(i) || - sp->push_backpatch(i, ctx->push_label((char *)"", 0)) || + return !MY_TEST(i) || + sp->push_backpatch(i, ctx->push_label(thd, empty_lex_str, 0)) || sp->add_cont_backpatch(i) || sp->add_instr(i); } @@ -357,8 +365,8 @@ int case_stmt_action_then(LEX *lex) sp_head *sp= lex->sphead; sp_pcontext *ctx= lex->spcont; uint ip= sp->instructions(); - sp_instr_jump *i = new sp_instr_jump(ip, ctx); - if (!test(i) || sp->add_instr(i)) + sp_instr_jump *i= new (lex->thd->mem_root) sp_instr_jump(ip, ctx); + if (!MY_TEST(i) || sp->add_instr(i)) return 1; /* @@ -371,44 +379,19 @@ int case_stmt_action_then(LEX *lex) /* BACKPATCH: Registering forward jump from - "case_stmt_action_then" to "case_stmt_action_end_case" + "case_stmt_action_then" to after END CASE (jump from instruction 4 to 12, 7 to 12 ... in the example) */ return sp->push_backpatch(i, ctx->last_label()); } -/** - Helper action for an end case. - This helper is used for both 'simple' and 'searched' cases. - @param lex the parser lex context - @param simple true for simple cases, false for searched cases -*/ - -void case_stmt_action_end_case(LEX *lex, bool simple) -{ - /* - BACKPATCH: Resolving forward jump from - "case_stmt_action_then" to "case_stmt_action_end_case" - (jump from instruction 4 to 12, 7 to 12 ... in the example) - */ - lex->sphead->backpatch(lex->spcont->pop_label()); - - if (simple) - lex->spcont->pop_case_expr_id(); - - lex->sphead->do_cont_backpatch(); -} - - static bool find_sys_var_null_base(THD *thd, struct sys_var_with_base *tmp) { tmp->var= find_sys_var(thd, tmp->base_name.str, tmp->base_name.length); - if (tmp->var == NULL) - my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), tmp->base_name.str); - else + if (tmp->var != NULL) tmp->base_name= null_lex_str; return thd->is_error(); @@ -445,10 +428,11 @@ set_system_variable(THD *thd, struct sys_var_with_base *tmp, return TRUE; } - if (! (var= new set_var(var_type, tmp->var, &tmp->base_name, val))) + if (! (var= new (thd->mem_root) + set_var(thd, var_type, tmp->var, &tmp->base_name, val))) return TRUE; - return lex->var_list.push_back(var); + return lex->var_list.push_back(var, thd->mem_root); } @@ -464,7 +448,7 @@ set_system_variable(THD *thd, struct sys_var_with_base *tmp, */ static bool -set_local_variable(THD *thd, sp_variable_t *spv, Item *val) +set_local_variable(THD *thd, sp_variable *spv, Item *val) { Item *it; LEX *lex= thd->lex; @@ -472,17 +456,18 @@ set_local_variable(THD *thd, sp_variable_t *spv, Item *val) if (val) it= val; - else if (spv->dflt) - it= spv->dflt; + else if (spv->default_value) + it= spv->default_value; else { - it= new (thd->mem_root) Item_null(); + it= new (thd->mem_root) Item_null(thd); if (it == NULL) return TRUE; } - sp_set= new sp_instr_set(lex->sphead->instructions(), lex->spcont, - spv->offset, it, spv->type, lex, TRUE); + sp_set= new (thd->mem_root) + sp_instr_set(lex->sphead->instructions(), lex->spcont, + spv->offset, it, spv->type, lex, TRUE); return (sp_set == NULL || lex->sphead->add_instr(sp_set)); } @@ -508,22 +493,24 @@ set_trigger_new_row(THD *thd, LEX_STRING *name, Item *val) /* QQ: Shouldn't this be field's default value ? */ if (! val) - val= new Item_null(); + val= new (thd->mem_root) Item_null(thd); DBUG_ASSERT(lex->trg_chistics.action_time == TRG_ACTION_BEFORE && (lex->trg_chistics.event == TRG_EVENT_INSERT || lex->trg_chistics.event == TRG_EVENT_UPDATE)); trg_fld= new (thd->mem_root) - Item_trigger_field(lex->current_context(), + Item_trigger_field(thd, lex->current_context(), Item_trigger_field::NEW_ROW, name->str, UPDATE_ACL, FALSE); if (trg_fld == NULL) return TRUE; - sp_fld= new sp_instr_set_trigger_field(lex->sphead->instructions(), - lex->spcont, trg_fld, val, lex); + sp_fld= new (thd->mem_root) + sp_instr_set_trigger_field(lex->sphead->instructions(), + lex->spcont, trg_fld, val, + lex); if (sp_fld == NULL) return TRUE; @@ -539,6 +526,57 @@ set_trigger_new_row(THD *thd, LEX_STRING *name, Item *val) /** + Create an object to represent a SP variable in the Item-hierarchy. + + @param thd The current thread. + @param name The SP variable name. + @param spvar The SP variable (optional). + @param start_in_q Start position of the SP variable name in the query. + @param end_in_q End position of the SP variable name in the query. + + @remark If spvar is not specified, the name is used to search for the + variable in the parse-time context. If the variable does not + exist, a error is set and NULL is returned to the caller. + + @return An Item_splocal object representing the SP variable, or NULL on error. +*/ +static Item_splocal* +create_item_for_sp_var(THD *thd, LEX_STRING name, sp_variable *spvar, + const char *start_in_q, const char *end_in_q) +{ + Item_splocal *item; + LEX *lex= thd->lex; + uint pos_in_q, len_in_q; + sp_pcontext *spc = lex->spcont; + + /* If necessary, look for the variable. */ + if (spc && !spvar) + spvar= spc->find_variable(name, false); + + if (!spvar) + { + my_error(ER_SP_UNDECLARED_VAR, MYF(0), name.str); + return NULL; + } + + DBUG_ASSERT(spc && spvar); + + /* Position and length of the SP variable name in the query. */ + pos_in_q= start_in_q - lex->sphead->m_tmp_query; + len_in_q= end_in_q - start_in_q; + + item= new (thd->mem_root) + Item_splocal(thd, name, spvar->offset, spvar->type, pos_in_q, len_in_q); + +#ifndef DBUG_OFF + if (item) + item->m_sp= lex->sphead; +#endif + + return item; +} + +/** Helper to resolve the SQL:2003 Syntax exception 1) in <in predicate>. See SQL:2003, Part 2, section 8.4 <in predicate>, Note 184, page 383. This function returns the proper item for the SQL expression @@ -596,7 +634,7 @@ Item* handle_sql2003_note184_exception(THD *thd, Item* left, bool equal, Item_in_subselect(left, subselect) */ subselect= expr3->invalidate_and_restore_select_lex(); - result= new (thd->mem_root) Item_in_subselect(left, subselect); + result= new (thd->mem_root) Item_in_subselect(thd, left, subselect); if (! equal) result = negate_expression(thd, result); @@ -606,9 +644,9 @@ Item* handle_sql2003_note184_exception(THD *thd, Item* left, bool equal, } if (equal) - result= new (thd->mem_root) Item_func_eq(left, expr); + result= new (thd->mem_root) Item_func_eq(thd, left, expr); else - result= new (thd->mem_root) Item_func_ne(left, expr); + result= new (thd->mem_root) Item_func_ne(thd, left, expr); DBUG_RETURN(result); } @@ -622,11 +660,11 @@ Item* handle_sql2003_note184_exception(THD *thd, Item* left, bool equal, @param lex The parser state. - @param is_union_distinct True if the union preceding the new select statement - uses UNION DISTINCT. + @param is_union_distinct True if the union preceding the new select + statement uses UNION DISTINCT. @param is_top_level This should be @c TRUE if the newly created SELECT_LEX - is a non-nested statement. + is a non-nested statement. @return <code>false</code> if successful, <code>true</code> if an error was reported. In the latter case parsing should stop. @@ -643,9 +681,20 @@ bool add_select_to_union_list(LEX *lex, bool is_union_distinct, my_error(ER_WRONG_USAGE, MYF(0), "UNION", "INTO"); return TRUE; } + if (lex->current_select->order_list.first && !lex->current_select->braces) + { + my_error(ER_WRONG_USAGE, MYF(0), "UNION", "ORDER BY"); + return TRUE; + } + + if (lex->current_select->explicit_limit && !lex->current_select->braces) + { + my_error(ER_WRONG_USAGE, MYF(0), "UNION", "LIMIT"); + return TRUE; + } if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(lex->thd, ER_SYNTAX_ERROR); return TRUE; } /* This counter shouldn't be incremented for UNION parts */ @@ -670,17 +719,20 @@ bool add_select_to_union_list(LEX *lex, bool is_union_distinct, bool setup_select_in_parentheses(LEX *lex) { SELECT_LEX * sel= lex->current_select; + /* if (sel->set_braces(1)) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(lex->thd, ER_SYNTAX_ERROR); return TRUE; } + */ + DBUG_ASSERT(sel->braces); if (sel->linkage == UNION_TYPE && !sel->master_unit()->first_select()->braces && sel->master_unit()->first_select()->linkage == UNION_TYPE) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(lex->thd, ER_SYNTAX_ERROR); return TRUE; } if (sel->linkage == UNION_TYPE && @@ -690,110 +742,294 @@ bool setup_select_in_parentheses(LEX *lex) my_error(ER_WRONG_USAGE, MYF(0), "CUBE/ROLLUP", "ORDER BY"); return TRUE; } - /* select in braces, can't contain global parameters */ - if (sel->master_unit()->fake_select_lex) - sel->master_unit()->global_parameters= - sel->master_unit()->fake_select_lex; return FALSE; } -static bool add_create_index_prepare (LEX *lex, Table_ident *table) +static bool add_create_index_prepare(LEX *lex, Table_ident *table) { lex->sql_command= SQLCOM_CREATE_INDEX; if (!lex->current_select->add_table_to_list(lex->thd, table, NULL, TL_OPTION_UPDATING, TL_READ_NO_INSERT, - MDL_SHARED_NO_WRITE)) + MDL_SHARED_UPGRADABLE)) return TRUE; lex->alter_info.reset(); - lex->alter_info.flags= ALTER_ADD_INDEX; - lex->col_list.empty(); - lex->change= NullS; + lex->alter_info.flags= Alter_info::ALTER_ADD_INDEX; lex->option_list= NULL; return FALSE; } -static bool add_create_index (LEX *lex, Key::Keytype type, - const LEX_STRING &name, - KEY_CREATE_INFO *info= NULL, bool generated= 0) + +/** + Create a separate LEX for each assignment if in SP. + + If we are in SP we want have own LEX for each assignment. + This is mostly because it is hard for several sp_instr_set + and sp_instr_set_trigger instructions share one LEX. + (Well, it is theoretically possible but adds some extra + overhead on preparation for execution stage and IMO less + robust). + + QQ: May be we should simply prohibit group assignments in SP? + + @see sp_create_assignment_instr + + @param thd Thread context + @param no_lookahead True if the parser has no lookahead +*/ + +static void sp_create_assignment_lex(THD *thd, bool no_lookahead) +{ + LEX *lex= thd->lex; + + if (lex->sphead) + { + Lex_input_stream *lip= &thd->m_parser_state->m_lip; + LEX *old_lex= lex; + lex->sphead->reset_lex(thd); + lex= thd->lex; + + /* Set new LEX as if we at start of set rule. */ + lex->sql_command= SQLCOM_SET_OPTION; + mysql_init_select(lex); + lex->var_list.empty(); + lex->autocommit= 0; + /* get_ptr() is only correct with no lookahead. */ + if (no_lookahead) + lex->sphead->m_tmp_query= lip->get_ptr(); + else + lex->sphead->m_tmp_query= lip->get_tok_end(); + /* Inherit from outer lex. */ + lex->option_type= old_lex->option_type; + } +} + + +/** + Create a SP instruction for a SET assignment. + + @see sp_create_assignment_lex + + @param thd Thread context + @param no_lookahead True if the parser has no lookahead + + @return false if success, true otherwise. +*/ + +static bool sp_create_assignment_instr(THD *thd, bool no_lookahead) +{ + LEX *lex= thd->lex; + + if (lex->sphead) + { + sp_head *sp= lex->sphead; + + if (!lex->var_list.is_empty()) + { + /* + We have assignment to user or system variable or + option setting, so we should construct sp_instr_stmt + for it. + */ + LEX_STRING qbuff; + sp_instr_stmt *i; + Lex_input_stream *lip= &thd->m_parser_state->m_lip; + + if (!(i= new (thd->mem_root) + sp_instr_stmt(sp->instructions(), lex->spcont, lex))) + return true; + + /* + Extract the query statement from the tokenizer. The + end is either lip->ptr, if there was no lookahead, + lip->tok_end otherwise. + */ + if (no_lookahead) + qbuff.length= lip->get_ptr() - sp->m_tmp_query; + else + qbuff.length= lip->get_tok_end() - sp->m_tmp_query; + + if (!(qbuff.str= (char*) alloc_root(thd->mem_root, + qbuff.length + 5))) + return true; + + strmake(strmake(qbuff.str, "SET ", 4), sp->m_tmp_query, + qbuff.length); + qbuff.length+= 4; + i->m_query= qbuff; + if (sp->add_instr(i)) + return true; + } + enum_var_type inner_option_type= lex->option_type; + if (lex->sphead->restore_lex(thd)) + return true; + /* Copy option_type to outer lex in case it has changed. */ + thd->lex->option_type= inner_option_type; + } + return false; +} + + +static void add_key_to_list(LEX *lex, LEX_STRING *field_name, + enum Key::Keytype type, bool check_exists) { Key *key; - key= new Key(type, name, info ? info : &lex->key_create_info, generated, - lex->col_list, lex->option_list); - if (key == NULL) - return TRUE; + MEM_ROOT *mem_root= lex->thd->mem_root; + key= new (mem_root) + Key(type, null_lex_str, HA_KEY_ALG_UNDEF, false, + DDL_options(check_exists ? + DDL_options::OPT_IF_NOT_EXISTS : + DDL_options::OPT_NONE)); + key->columns.push_back(new (mem_root) Key_part_spec(*field_name, 0), + mem_root); + lex->alter_info.key_list.push_back(key, mem_root); +} - lex->alter_info.key_list.push_back(key); - lex->col_list.empty(); - return FALSE; +void LEX::init_last_field(Create_field *field, const char *field_name, + CHARSET_INFO *cs) +{ + last_field= field; + + field->field_name= field_name; + + /* reset LEX fields that are used in Create_field::set_and_check() */ + length= 0; + dec= 0; + charset= cs; +} + +void LEX::set_last_field_type(enum enum_field_types field_type) +{ + last_field->sql_type= field_type; + last_field->create_if_not_exists= check_exists; + last_field->charset= charset; + + if (length) + { + int err; + last_field->length= my_strtoll10(length, NULL, &err); + if (err) + last_field->length= ~0ULL; // safety + } + else + last_field->length= 0; + + last_field->decimals= dec ? (uint)atoi(dec) : 0; +} + +bool LEX::set_bincmp(CHARSET_INFO *cs, bool bin) +{ + /* + if charset is NULL - we're parsing a field declaration. + we cannot call find_bin_collation for a field here, because actual + field charset is determined in get_sql_field_charset() much later. + so we only set a flag. + */ + if (!charset) + { + charset= cs; + last_field->flags|= bin ? BINCMP_FLAG : 0; + return false; + } + + charset= bin ? find_bin_collation(cs ? cs : charset) + : cs ? cs : charset; + return charset == NULL; } +#define bincmp_collation(X,Y) \ + do \ + { \ + if (Lex->set_bincmp(X,Y)) \ + MYSQL_YYABORT; \ + } while(0) + %} %union { int num; ulong ulong_num; ulonglong ulonglong_number; longlong longlong_number; + + /* structs */ LEX_STRING lex_str; - LEX_STRING *lex_str_ptr; LEX_SYMBOL symbol; - LEX_TYPE lex_type; - Table_ident *table; - char *simple_string; + struct sys_var_with_base variable; + struct { int vars, conds, hndlrs, curs; } spblock; + + /* pointers */ + CHARSET_INFO *charset; + Condition_information_item *cond_info_item; + DYNCALL_CREATE_DEF *dyncol_def; + Diagnostics_information *diag_info; Item *item; Item_num *item_num; + Item_param *item_param; + Key_part_spec *key_part; + LEX *lex; + LEX_STRING *lex_str_ptr; + LEX_USER *lex_user; + List<Condition_information_item> *cond_info_list; + List<DYNCALL_CREATE_DEF> *dyncol_def_list; List<Item> *item_list; + List<Statement_information_item> *stmt_info_list; List<String> *string_list; + Statement_information_item *stmt_info_item; String *string; - Key_part_spec *key_part; TABLE_LIST *table_list; - udf_func *udf; - LEX_USER *lex_user; - struct sys_var_with_base variable; - enum enum_var_type var_type; - Key::Keytype key_type; - enum ha_key_alg key_alg; + Table_ident *table; + char *simple_string; + chooser_compare_func_creator boolfunc2creator; + class my_var *myvar; + class sp_condition_value *spcondvalue; + class sp_head *sphead; + class sp_label *splabel; + class sp_name *spname; + class sp_variable *spvar; handlerton *db_type; - enum row_type row_type; - enum ha_rkey_function ha_rkey_mode; - enum enum_tx_isolation tx_isolation; - enum Cast_target cast_type; - enum Item_udftype udf_type; - enum ha_choice choice; - CHARSET_INFO *charset; - thr_lock_type lock_type; - interval_type interval, interval_time_st; - timestamp_type date_time_type; st_select_lex *select_lex; - chooser_compare_func_creator boolfunc2creator; - struct sp_cond_type *spcondtype; - struct { int vars, conds, hndlrs, curs; } spblock; - sp_name *spname; - LEX *lex; - sp_head *sphead; struct p_elem_val *p_elem_value; - enum index_hint_type index_hint; + udf_func *udf; + + /* enums */ + enum Cast_target cast_type; + enum Condition_information_item::Name cond_info_item_name; + enum enum_diag_condition_item_name diag_condition_item_name; + enum Diagnostics_information::Which_area diag_area; + enum Field::geometry_type geom_type; + enum enum_fk_option m_fk_option; + enum Item_udftype udf_type; + enum Key::Keytype key_type; + enum Statement_information_item::Name stmt_info_item_name; + enum enum_field_types field_type; enum enum_filetype filetype; - enum Foreign_key::fk_option m_fk_option; + enum enum_tx_isolation tx_isolation; + enum enum_var_type var_type; enum enum_yes_no_unknown m_yes_no_unk; - Diag_condition_item_name diag_condition_item_name; - DYNCALL_CREATE_DEF *dyncol_def; - List<DYNCALL_CREATE_DEF> *dyncol_def_list; - bool is_not_empty; + enum ha_choice choice; + enum ha_key_alg key_alg; + enum ha_rkey_function ha_rkey_mode; + enum index_hint_type index_hint; + enum interval_type interval, interval_time_st; + enum row_type row_type; + enum sp_variable::enum_mode spvar_mode; + enum thr_lock_type lock_type; + enum enum_mysql_timestamp_type date_time_type; + DDL_options_st object_ddl_options; } %{ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %} -%pure_parser /* We have threads */ +%pure-parser /* We have threads */ %parse-param { THD *thd } %lex-param { THD *thd } /* - Currently there are 175 shift/reduce conflicts. + Currently there are 160 shift/reduce conflicts. We should not introduce new conflicts any more. */ -%expect 175 +%expect 162 /* Comments for TOKENS. @@ -807,15 +1043,16 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); MYSQL-FUNC : MySQL extention, function INTERNAL : Not a real token, lex optimization OPERATOR : SQL operator - FUTURE-USE : Reserved for futur use + FUTURE-USE : Reserved for future use This makes the code grep-able, and helps maintenance. */ - + %token ABORT_SYM /* INTERNAL (used in lex) */ %token ACCESSIBLE_SYM %token ACTION /* SQL-2003-N */ %token ADD /* SQL-2003-R */ +%token ADMIN_SYM /* SQL-2003-N */ %token ADDDATE_SYM /* MYSQL-FUNC */ %token AFTER_SYM /* SQL-2003-N */ %token AGAINST @@ -833,9 +1070,11 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token ASCII_SYM /* MYSQL-FUNC */ %token ASENSITIVE_SYM /* FUTURE-USE */ %token AT_SYM /* SQL-2003-R */ +%token ATOMIC_SYM /* SQL-2003-R */ %token AUTHORS_SYM %token AUTOEXTEND_SIZE_SYM %token AUTO_INC +%token AUTO_SYM %token AVG_ROW_LENGTH %token AVG_SYM /* SQL-2003-N */ %token BACKUP_SYM @@ -876,7 +1115,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token CIPHER_SYM %token CLASS_ORIGIN_SYM /* SQL-2003-N */ %token CLIENT_SYM -%token CLIENT_STATS_SYM %token CLOSE_SYM /* SQL-2003-R */ %token COALESCE /* SQL-2003-N */ %token CODE_SYM @@ -884,11 +1122,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token COLLATION_SYM /* SQL-2003-N */ %token COLUMNS %token COLUMN_ADD_SYM +%token COLUMN_CHECK_SYM %token COLUMN_CREATE_SYM %token COLUMN_DELETE_SYM -%token COLUMN_EXISTS_SYM %token COLUMN_GET_SYM -%token COLUMN_LIST_SYM %token COLUMN_SYM /* SQL-2003-R */ %token COLUMN_NAME_SYM /* SQL-2003-N */ %token COMMENT_SYM @@ -916,7 +1153,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token CROSS /* SQL-2003-R */ %token CUBE_SYM /* SQL-2003-R */ %token CURDATE /* MYSQL-FUNC */ +%token CURRENT_SYM /* SQL-2003-R */ %token CURRENT_USER /* SQL-2003-R */ +%token CURRENT_ROLE /* SQL-2003-R */ +%token CURRENT_POS_SYM %token CURSOR_SYM /* SQL-2003-R */ %token CURSOR_NAME_SYM /* SQL-2003-N */ %token CURTIME /* MYSQL-FUNC */ @@ -942,10 +1182,12 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token DELAYED_SYM %token DELAY_KEY_WRITE_SYM %token DELETE_SYM /* SQL-2003-R */ +%token DELETE_DOMAIN_ID_SYM %token DESC /* SQL-2003-N */ %token DESCRIBE /* SQL-2003-R */ %token DES_KEY_FILE %token DETERMINISTIC_SYM /* SQL-2003-R */ +%token DIAGNOSTICS_SYM /* SQL-2003-N */ %token DIRECTORY_SYM %token DISABLE_SYM %token DISCARD @@ -953,6 +1195,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token DISTINCT /* SQL-2003-R */ %token DIV_SYM %token DOUBLE_SYM /* SQL-2003-R */ +%token DO_DOMAIN_IDS_SYM %token DO_SYM %token DROP /* SQL-2003-R */ %token DUAL_SYM @@ -978,11 +1221,13 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token EVENTS_SYM %token EVENT_SYM %token EVERY_SYM /* SQL-2003-N */ +%token EXCHANGE_SYM %token EXAMINED_SYM %token EXECUTE_SYM /* SQL-2003-R */ %token EXISTS /* SQL-2003-R */ %token EXIT_SYM %token EXPANSION_SYM +%token EXPORT_SYM %token EXTENDED_SYM %token EXTENT_SIZE_SYM %token EXTRACT_SYM /* SQL-2003-N */ @@ -999,6 +1244,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token FORCE_SYM %token FOREIGN /* SQL-2003-R */ %token FOR_SYM /* SQL-2003-R */ +%token FORMAT_SYM %token FOUND_SYM /* SQL-2003-R */ %token FROM %token FULL /* SQL-2003-R */ @@ -1010,6 +1256,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token GEOMETRYCOLLECTION %token GEOMETRY_SYM %token GET_FORMAT /* MYSQL-FUNC */ +%token GET_SYM /* SQL-2003-R */ %token GLOBAL_SYM /* SQL-2003-R */ %token GRANT /* SQL-2003-R */ %token GRANTS @@ -1029,16 +1276,17 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token HOUR_MINUTE_SYM %token HOUR_SECOND_SYM %token HOUR_SYM /* SQL-2003-R */ +%token ID_SYM /* MYSQL */ %token IDENT %token IDENTIFIED_SYM %token IDENT_QUOTED -%token IF +%token IF_SYM +%token IGNORE_DOMAIN_IDS_SYM %token IGNORE_SYM %token IGNORE_SERVER_IDS_SYM %token IMPORT %token INDEXES %token INDEX_SYM -%token INDEX_STATS_SYM %token INFILE %token INITIAL_SIZE_SYM %token INNER_SYM /* SQL-2003-R */ @@ -1094,6 +1342,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token LOOP_SYM %token LOW_PRIORITY %token MASTER_CONNECT_RETRY_SYM +%token MASTER_GTID_POS_SYM %token MASTER_HOST_SYM %token MASTER_LOG_FILE_SYM %token MASTER_LOG_POS_SYM @@ -1104,11 +1353,14 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token MASTER_SSL_CA_SYM %token MASTER_SSL_CERT_SYM %token MASTER_SSL_CIPHER_SYM +%token MASTER_SSL_CRL_SYM +%token MASTER_SSL_CRLPATH_SYM %token MASTER_SSL_KEY_SYM %token MASTER_SSL_SYM %token MASTER_SSL_VERIFY_SERVER_CERT_SYM %token MASTER_SYM %token MASTER_USER_SYM +%token MASTER_USE_GTID_SYM %token MASTER_HEARTBEAT_PERIOD_SYM %token MATCH /* SQL-2003-R */ %token MAX_CONNECTIONS_PER_HOUR @@ -1117,6 +1369,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token MAX_SIZE_SYM %token MAX_SYM /* SQL-2003-N */ %token MAX_UPDATES_PER_HOUR +%token MAX_STATEMENT_TIME_SYM %token MAX_USER_CONNECTIONS_SYM %token MAX_VALUE_SYM /* SQL-2003-N */ %token MEDIUMBLOB @@ -1150,7 +1403,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token NATURAL /* SQL-2003-R */ %token NCHAR_STRING %token NCHAR_SYM /* SQL-2003-R */ -%token NDBCLUSTER_SYM %token NE /* OPERATOR */ %token NEG %token NEW_SYM /* SQL-2003-R */ @@ -1165,13 +1417,14 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token NO_WRITE_TO_BINLOG %token NULL_SYM /* SQL-2003-R */ %token NUM +%token NUMBER_SYM /* SQL-2003-N */ %token NUMERIC_SYM /* SQL-2003-R */ %token NVARCHAR_SYM %token OFFSET_SYM -%token OLD_PASSWORD +%token OLD_PASSWORD_SYM %token ON /* SQL-2003-R */ -%token ONE_SHOT_SYM %token ONE_SYM +%token ONLY_SYM /* SQL-2003-R */ %token ONLINE_SYM %token OPEN_SYM /* SQL-2003-R */ %token OPTIMIZE @@ -1193,10 +1446,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token PARSER_SYM %token PARSE_VCOL_EXPR_SYM %token PARTIAL /* SQL-2003-N */ -%token PARTITIONING_SYM -%token PARTITIONS_SYM %token PARTITION_SYM /* SQL-2003-R */ -%token PASSWORD +%token PARTITIONS_SYM +%token PARTITIONING_SYM +%token PASSWORD_SYM %token PERSISTENT_SYM %token PHASE_SYM %token PLUGINS_SYM @@ -1256,16 +1509,21 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token RESTORE_SYM %token RESTRICT %token RESUME_SYM +%token RETURNED_SQLSTATE_SYM /* SQL-2003-N */ +%token RETURNING_SYM %token RETURNS_SYM /* SQL-2003-R */ %token RETURN_SYM /* SQL-2003-R */ +%token REVERSE_SYM %token REVOKE /* SQL-2003-R */ %token RIGHT /* SQL-2003-R */ +%token ROLE_SYM %token ROLLBACK_SYM /* SQL-2003-R */ %token ROLLUP_SYM /* SQL-2003-R */ %token ROUTINE_SYM /* SQL-2003-N */ %token ROWS_SYM /* SQL-2003-R */ %token ROW_FORMAT_SYM %token ROW_SYM /* SQL-2003-R */ +%token ROW_COUNT_SYM /* SQL-2003-N */ %token RTREE_SYM %token SAVEPOINT_SYM /* SQL-2003-R */ %token SCHEDULE_SYM @@ -1292,6 +1550,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token SIGNED_SYM %token SIMPLE_SYM /* SQL-2003-N */ %token SLAVE +%token SLAVES +%token SLAVE_POS_SYM %token SLOW %token SMALLINT /* SQL-2003-R */ %token SNAPSHOT_SYM @@ -1313,10 +1573,15 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token SQL_SMALL_RESULT %token SQL_SYM /* SQL-2003-R */ %token SQL_THREAD +%token REF_SYSTEM_ID_SYM %token SSL_SYM %token STARTING %token STARTS_SYM %token START_SYM /* SQL-2003-R */ +%token STATEMENT_SYM +%token STATS_AUTO_RECALC_SYM +%token STATS_PERSISTENT_SYM +%token STATS_SAMPLE_PAGES_SYM %token STATUS_SYM %token STDDEV_SAMP_SYM /* SQL-2003-N */ %token STD_SYM @@ -1339,7 +1604,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token TABLES %token TABLESPACE %token TABLE_REF_PRIORITY -%token TABLE_STATS_SYM %token TABLE_SYM /* SQL-2003-R */ %token TABLE_CHECKSUM_SYM %token TABLE_NAME_SYM /* SQL-2003-N */ @@ -1387,8 +1651,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token UPDATE_SYM /* SQL-2003-R */ %token UPGRADE_SYM %token USAGE /* SQL-2003-N */ -%token USER /* SQL-2003-R */ -%token USER_STATS_SYM +%token USER_SYM /* SQL-2003-R */ %token USE_FRM %token USE_SYM %token USING /* SQL-2003-R */ @@ -1409,6 +1672,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token WAIT_SYM %token WARNINGS %token WEEK_SYM +%token WEIGHT_STRING_SYM %token WHEN_SYM /* SQL-2003-R */ %token WHERE /* SQL-2003-R */ %token WHILE_SYM @@ -1450,7 +1714,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type <lex_str> IDENT IDENT_QUOTED TEXT_STRING DECIMAL_NUM FLOAT_NUM NUM LONG_NUM - HEX_NUM HEX_STRING hex_num_or_string + HEX_NUM HEX_STRING LEX_HOSTNAME ULONGLONG_NUM field_ident select_alias ident ident_or_text IDENT_sys TEXT_STRING_sys TEXT_STRING_literal NCHAR_STRING opt_component key_cache_name @@ -1462,29 +1726,47 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type <table> table_ident table_ident_nodb references xid - table_ident_opt_wild + table_ident_opt_wild create_like %type <simple_string> - remember_name remember_end opt_db text_or_password + remember_name remember_end opt_db remember_tok_start + wild_and_where %type <string> - text_string opt_gconcat_separator + text_string hex_or_bin_String opt_gconcat_separator -%type <lex_type> field_def +%type <field_type> type_with_opt_collate int_type real_type field_type + +%type <geom_type> spatial_type %type <num> - type type_with_opt_collate int_type real_type order_dir lock_option - udf_type if_exists opt_local opt_table_options table_options - table_option opt_if_not_exists opt_no_write_to_binlog + order_dir lock_option + udf_type opt_local opt_no_write_to_binlog opt_temporary all_or_any opt_distinct - opt_ignore_leaves fulltext_options spatial_type union_option - start_transaction_opts - union_opt select_derived_init option_type2 + opt_ignore_leaves fulltext_options union_option + opt_not opt_union_order_or_limit + union_opt select_derived_init transaction_access_mode_types opt_natural_language_mode opt_query_expansion opt_ev_status opt_ev_on_completion ev_on_completion opt_ev_comment ev_alter_on_schedule_completion opt_ev_rename_to opt_ev_sql_stmt optional_flush_tables_arguments opt_dyncol_type dyncol_type opt_time_precision kill_type kill_option int_num + opt_default_time_precision + case_stmt_body opt_bin_mod + opt_if_exists_table_element opt_if_not_exists_table_element + opt_into opt_procedure_clause + +%type <object_ddl_options> + create_or_replace + opt_if_not_exists + opt_if_exists + +/* + Bit field of MYSQL_START_TRANS_OPT_* flags. +*/ +%type <num> opt_start_transaction_option_list +%type <num> start_transaction_option_list +%type <num> start_transaction_option %type <m_yes_no_unk> opt_chain opt_release @@ -1494,6 +1776,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type <ulong_num> ulong_num real_ulong_num merge_insert_types + ws_nweights + ws_level_flag_desc ws_level_flag_reverse ws_level_flags + opt_ws_levels ws_level_list ws_level_list_item ws_level_number + ws_level_range ws_level_list_or_range %type <ulonglong_number> ulonglong_num real_ulonglong_num size_number @@ -1504,13 +1790,13 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); replace_lock_option opt_low_priority insert_lock_option load_data_lock %type <item> - literal text_literal insert_ident order_ident + literal text_literal insert_ident order_ident temporal_literal simple_ident expr opt_expr opt_else sum_expr in_sum_expr variable variable_aux bool_pri predicate bit_expr table_wild simple_expr udf_expr expr_or_default set_expr_or_default - param_marker geometry_function + geometry_function signed_literal now_or_signed_literal opt_escape sp_opt_default simple_ident_nospvar simple_ident_q @@ -1521,6 +1807,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); function_call_generic function_call_conflict kill_expr signal_allowed_expr + simple_target_specification + condition_number + +%type <item_param> param_marker %type <item_num> NUM_literal @@ -1533,13 +1823,13 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); option_type opt_var_type opt_var_ident_type %type <key_type> - normal_key_type opt_unique constraint_key_type fulltext spatial + opt_unique constraint_key_type fulltext spatial %type <key_alg> - btree_or_rtree + btree_or_rtree opt_key_algorithm_clause opt_USING_key_algorithm %type <string_list> - using_list + using_list opt_use_partition use_partition %type <key_part> key_part @@ -1567,11 +1857,13 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type <symbol> keyword keyword_sp -%type <lex_user> user grant_user +%type <lex_user> user grant_user grant_role user_or_role current_role + admin_option_for_role user_maybe_role %type <charset> opt_collate charset_name + charset_or_alias charset_name_or_default old_or_new_charset_name old_or_new_charset_name_or_default @@ -1592,13 +1884,21 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type <dyncol_def_list> dyncall_create_list +%type <myvar> select_outvar + %type <NONE> + analyze_stmt_command query verb_clause create change select do drop insert replace insert2 - insert_values update delete truncate rename + insert_values update delete truncate rename compound_statement show describe load alter optimize keycache preload flush reset purge begin commit rollback savepoint release slave master_def master_defs master_file_def slave_until_opts - repair analyze check start checksum + repair analyze opt_with_admin opt_with_admin_option + analyze_table_list analyze_table_elem_spec + opt_persistent_stat_clause persistent_stat_spec + persistent_column_stat_spec persistent_index_stat_spec + table_column_list table_index_list table_index_name + check start checksum field_list field_list_item field_spec kill column_def key_def keycache_list keycache_list_or_parts assign_to_keycache assign_to_keycache_parts @@ -1606,21 +1906,22 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); select_item_list select_item values_list no_braces opt_limit_clause delete_limit_clause fields opt_values values procedure_list procedure_list2 procedure_item - handler + field_def handler opt_generated_always opt_precision opt_ignore opt_column opt_restrict grant revoke set lock unlock string_list field_options field_option - field_opt_list opt_binary ascii unicode table_lock_list table_lock + field_opt_list opt_binary table_lock_list table_lock ref_list opt_match_clause opt_on_update_delete use opt_delete_options opt_delete_option varchar nchar nvarchar opt_outer table_list table_name table_alias_ref_list table_alias_ref - opt_option opt_place + opt_place opt_attribute opt_attribute_list attribute column_list column_list_id opt_column_list grant_privileges grant_ident grant_list grant_option - object_privilege object_privilege_list user_list rename_list + object_privilege object_privilege_list user_list user_and_role_list + rename_list table_or_tables clear_privileges flush_options flush_option - opt_with_read_lock flush_options_list + opt_flush_lock flush_lock flush_options_list equal optional_braces - opt_mi_check_type opt_to mi_check_types normal_join + opt_mi_check_type opt_to mi_check_types table_to_table_list table_to_table opt_table_list opt_as handler_rkey_function handler_read_or_scan single_multi table_wild_list table_wild_one opt_wild @@ -1628,60 +1929,77 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); precision subselect_start opt_and charset subselect_end select_var_list select_var_list_init help field_length opt_field_length - opt_extended_describe + opt_extended_describe shutdown + opt_format_json prepare prepare_src execute deallocate statement sp_suid sp_c_chistics sp_a_chistics sp_chistic sp_c_chistic xa opt_field_or_var_spec fields_or_vars opt_load_data_set_spec - view_replace_or_algorithm view_replace view_algorithm view_or_trigger_or_sp_or_event definer_tail no_definer_tail view_suid view_tail view_list_opt view_list view_select - view_check_option trigger_tail sp_tail sf_tail udf_tail event_tail + view_check_option trigger_tail sp_tail sf_tail event_tail + udf_tail udf_tail2 install uninstall partition_entry binlog_base64_event - init_key_options normal_key_options normal_key_opts all_key_opt + normal_key_options normal_key_opts all_key_opt spatial_key_options fulltext_key_options normal_key_opt fulltext_key_opt spatial_key_opt fulltext_key_opts spatial_key_opts keep_gcc_happy key_using_alg part_column_list server_def server_options_list server_option - definer_opt no_definer definer + definer_opt no_definer definer get_diagnostics parse_vcol_expr vcol_opt_specifier vcol_opt_attribute vcol_opt_attribute_list vcol_attribute + explainable_command + opt_delete_gtid_domain END_OF_INPUT %type <NONE> call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt %type <NONE> sp_proc_stmt_statement sp_proc_stmt_return +%type <NONE> sp_proc_stmt_compound_ok %type <NONE> sp_proc_stmt_if -%type <NONE> sp_labeled_control sp_proc_stmt_unlabeled -%type <NONE> sp_labeled_block sp_unlabeled_block +%type <NONE> sp_labeled_control sp_unlabeled_control +%type <NONE> sp_labeled_block sp_unlabeled_block sp_unlabeled_block_not_atomic %type <NONE> sp_proc_stmt_leave %type <NONE> sp_proc_stmt_iterate %type <NONE> sp_proc_stmt_open sp_proc_stmt_fetch sp_proc_stmt_close -%type <NONE> case_stmt_specification simple_case_stmt searched_case_stmt +%type <NONE> case_stmt_specification +%type <NONE> loop_body while_body repeat_body -%type <num> sp_decl_idents sp_opt_inout sp_handler_type sp_hcond_list -%type <spcondtype> sp_cond sp_hcond sqlstate signal_value opt_signal_value +%type <num> sp_decl_idents sp_handler_type sp_hcond_list +%type <spcondvalue> sp_cond sp_hcond sqlstate signal_value opt_signal_value %type <spblock> sp_decls sp_decl %type <lex> sp_cursor_stmt %type <spname> sp_name +%type <splabel> sp_block_content +%type <spvar> sp_param_name_and_type +%type <spvar_mode> sp_opt_inout %type <index_hint> index_hint_type -%type <num> index_hint_clause +%type <num> index_hint_clause normal_join inner_join %type <filetype> data_or_xml %type <NONE> signal_stmt resignal_stmt %type <diag_condition_item_name> signal_condition_information_item_name +%type <diag_area> which_area; +%type <diag_info> diagnostics_information; +%type <stmt_info_item> statement_information_item; +%type <stmt_info_item_name> statement_information_item_name; +%type <stmt_info_list> statement_information; +%type <cond_info_item> condition_information_item; +%type <cond_info_item_name> condition_information_item_name; +%type <cond_info_list> condition_information; + %type <NONE> '-' '+' '*' '/' '%' '(' ')' ',' '!' '{' '}' '&' '|' AND_SYM OR_SYM OR_OR_SYM BETWEEN_SYM CASE_SYM THEN_SYM WHEN_SYM DIV_SYM MOD_SYM OR2_SYM AND_AND_SYM DELETE_SYM - -%type <is_not_empty> opt_union_order_or_limit + ROLE_SYM %% + /* Indentation of grammar rules: @@ -1708,10 +2026,8 @@ query: { if (!thd->bootstrap && (!(thd->lex->select_lex.options & OPTION_FOUND_COMMENT))) - { - my_message(ER_EMPTY_QUERY, ER(ER_EMPTY_QUERY), MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_EMPTY_QUERY, MYF(0))); + thd->lex->sql_command= SQLCOM_EMPTY_QUERY; YYLIP->found_semicolon= NULL; } @@ -1755,12 +2071,14 @@ opt_end_of_input: verb_clause: statement | begin + | compound_statement ; -/* Verb clauses, except begin */ +/* Verb clauses, except begin and compound_statement */ statement: alter | analyze + | analyze_stmt_command | binlog_base64_event | call | change @@ -1775,6 +2093,7 @@ statement: | drop | execute | flush + | get_diagnostics | grant | handler | help @@ -1804,6 +2123,7 @@ statement: | set | signal_stmt | show + | shutdown | slave | start | truncate @@ -1877,8 +2197,9 @@ execute_var_ident: '@' ident_or_text { LEX *lex=Lex; - LEX_STRING *lexstr= (LEX_STRING*)sql_memdup(&$2, sizeof(LEX_STRING)); - if (!lexstr || lex->prepared_stmt_params.push_back(lexstr)) + LEX_STRING *lexstr= (LEX_STRING*)thd->memdup(&$2, sizeof(LEX_STRING)); + if (!lexstr || lex->prepared_stmt_params.push_back(lexstr, + thd->mem_root)) MYSQL_YYABORT; } ; @@ -1889,10 +2210,7 @@ help: HELP_SYM { if (Lex->sphead) - { - my_error(ER_SP_BADSTATEMENT, MYF(0), "HELP"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "HELP")); } ident_or_text { @@ -1905,7 +2223,7 @@ help: /* change master */ change: - CHANGE MASTER_SYM TO_SYM + CHANGE MASTER_SYM optional_connection_name TO_SYM { Lex->sql_command = SQLCOM_CHANGE_MASTER; } @@ -1969,32 +2287,36 @@ master_def: Lex->mi.ssl_verify_server_cert= $3 ? LEX_MASTER_INFO::LEX_MI_ENABLE : LEX_MASTER_INFO::LEX_MI_DISABLE; } + | MASTER_SSL_CRL_SYM '=' TEXT_STRING_sys + { + Lex->mi.ssl_crl= $3.str; + } + | MASTER_SSL_CRLPATH_SYM '=' TEXT_STRING_sys + { + Lex->mi.ssl_crlpath= $3.str; + } | MASTER_HEARTBEAT_PERIOD_SYM '=' NUM_literal { Lex->mi.heartbeat_period= (float) $3->val_real(); if (Lex->mi.heartbeat_period > SLAVE_MAX_HEARTBEAT_PERIOD || Lex->mi.heartbeat_period < 0.0) - { - const char format[]= "%d"; - char buf[4*sizeof(SLAVE_MAX_HEARTBEAT_PERIOD) + sizeof(format)]; - sprintf(buf, format, SLAVE_MAX_HEARTBEAT_PERIOD); - my_error(ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE, MYF(0), buf); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE, MYF(0), + SLAVE_MAX_HEARTBEAT_PERIOD)); + if (Lex->mi.heartbeat_period > slave_net_timeout) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX, - ER(ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX)); + ER_THD(thd, ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX)); } if (Lex->mi.heartbeat_period < 0.001) { if (Lex->mi.heartbeat_period != 0.0) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN, - ER(ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN)); + ER_THD(thd, ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN)); Lex->mi.heartbeat_period= 0.0; } Lex->mi.heartbeat_opt= LEX_MASTER_INFO::LEX_MI_DISABLE; @@ -2005,6 +2327,14 @@ master_def: { Lex->mi.repl_ignore_server_ids_opt= LEX_MASTER_INFO::LEX_MI_ENABLE; } + | DO_DOMAIN_IDS_SYM '=' '(' do_domain_id_list ')' + { + Lex->mi.repl_do_domain_ids_opt= LEX_MASTER_INFO::LEX_MI_ENABLE; + } + | IGNORE_DOMAIN_IDS_SYM '=' '(' ignore_domain_id_list ')' + { + Lex->mi.repl_ignore_domain_ids_opt= LEX_MASTER_INFO::LEX_MI_ENABLE; + } | master_file_def ; @@ -2020,6 +2350,33 @@ ignore_server_id: { insert_dynamic(&Lex->mi.repl_ignore_server_ids, (uchar*) &($1)); } + ; + +do_domain_id_list: + /* Empty */ + | do_domain_id + | do_domain_id_list ',' do_domain_id + ; + +do_domain_id: + ulong_num + { + insert_dynamic(&Lex->mi.repl_do_domain_ids, (uchar*) &($1)); + } + ; + +ignore_domain_id_list: + /* Empty */ + | ignore_domain_id + | ignore_domain_id_list ',' ignore_domain_id + ; + +ignore_domain_id: + ulong_num + { + insert_dynamic(&Lex->mi.repl_ignore_domain_ids, (uchar*) &($1)); + } + ; master_file_def: MASTER_LOG_FILE_SYM '=' TEXT_STRING_sys @@ -2028,7 +2385,6 @@ master_file_def: } | MASTER_LOG_POS_SYM '=' ulonglong_num { - Lex->mi.pos = $3; /* If the user specified a value < BIN_LOG_HEADER_SIZE, adjust it instead of causing subsequent errors. @@ -2040,7 +2396,7 @@ master_file_def: from 0" (4 in fact), unspecified means "don't change the position (keep the preceding value)"). */ - Lex->mi.pos = max(BIN_LOG_HEADER_SIZE, Lex->mi.pos); + Lex->mi.pos= MY_MAX(BIN_LOG_HEADER_SIZE, $3); } | RELAY_LOG_FILE_SYM '=' TEXT_STRING_sys { @@ -2050,86 +2406,119 @@ master_file_def: { Lex->mi.relay_log_pos = $3; /* Adjust if < BIN_LOG_HEADER_SIZE (same comment as Lex->mi.pos) */ - Lex->mi.relay_log_pos = max(BIN_LOG_HEADER_SIZE, Lex->mi.relay_log_pos); + Lex->mi.relay_log_pos= MY_MAX(BIN_LOG_HEADER_SIZE, Lex->mi.relay_log_pos); + } + | MASTER_USE_GTID_SYM '=' CURRENT_POS_SYM + { + if (Lex->mi.use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_UNCHANGED) + my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "MASTER_use_gtid")); + Lex->mi.use_gtid_opt= LEX_MASTER_INFO::LEX_GTID_CURRENT_POS; } + | MASTER_USE_GTID_SYM '=' SLAVE_POS_SYM + { + if (Lex->mi.use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_UNCHANGED) + my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "MASTER_use_gtid")); + Lex->mi.use_gtid_opt= LEX_MASTER_INFO::LEX_GTID_SLAVE_POS; + } + | MASTER_USE_GTID_SYM '=' NO_SYM + { + if (Lex->mi.use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_UNCHANGED) + my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "MASTER_use_gtid")); + Lex->mi.use_gtid_opt= LEX_MASTER_INFO::LEX_GTID_NO; + } + ; + +optional_connection_name: + /* empty */ + { + LEX *lex= thd->lex; + lex->mi.connection_name= null_lex_str; + } + | connection_name ; +connection_name: + TEXT_STRING_sys + { + Lex->mi.connection_name= $1; +#ifdef HAVE_REPLICATION + if (check_master_connection_name(&$1)) + my_yyabort_error((ER_WRONG_ARGUMENTS, MYF(0), "MASTER_CONNECTION_NAME")); +#endif + } + ; + /* create a table */ create: - CREATE opt_table_options TABLE_SYM opt_if_not_exists table_ident + create_or_replace opt_temporary TABLE_SYM opt_if_not_exists table_ident { LEX *lex= thd->lex; - lex->sql_command= SQLCOM_CREATE_TABLE; + lex->create_info.init(); + if (lex->set_command_with_check(SQLCOM_CREATE_TABLE, $2, $1 | $4)) + MYSQL_YYABORT; if (!lex->select_lex.add_table_to_list(thd, $5, NULL, TL_OPTION_UPDATING, TL_WRITE, MDL_EXCLUSIVE)) MYSQL_YYABORT; + lex->alter_info.reset(); /* - For CREATE TABLE, an non-existing table is not an error. - Instruct open_tables() to just take an MDL lock if the - table does not exist. + For CREATE TABLE we should not open the table even if it exists. + If the table exists, we should either not create it or replace it */ - lex->query_tables->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - lex->alter_info.reset(); - lex->col_list.empty(); - lex->change=NullS; - bzero((char*) &lex->create_info,sizeof(lex->create_info)); - lex->create_info.options=$2 | $4; + lex->query_tables->open_strategy= TABLE_LIST::OPEN_STUB; lex->create_info.default_table_charset= NULL; - lex->name.str= 0; - lex->name.length= 0; + lex->name= null_lex_str; lex->create_last_non_select_table= lex->last_table(); } - create2 + create_body { LEX *lex= thd->lex; lex->current_select= &lex->select_lex; if ((lex->create_info.used_fields & HA_CREATE_USED_ENGINE) && !lex->create_info.db_type) { - lex->create_info.db_type= ha_default_handlerton(thd); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + lex->create_info.use_default_db_type(thd); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_USING_OTHER_HANDLER, - ER(ER_WARN_USING_OTHER_HANDLER), + ER_THD(thd, ER_WARN_USING_OTHER_HANDLER), hton_name(lex->create_info.db_type)->str, $5->table.str); } create_table_set_open_action_and_adjust_tables(lex); } - | CREATE opt_unique INDEX_SYM ident key_alg ON table_ident + | create_or_replace opt_unique INDEX_SYM opt_if_not_exists ident + opt_key_algorithm_clause + ON table_ident { - if (add_create_index_prepare(Lex, $7)) + if (add_create_index_prepare(Lex, $8)) MYSQL_YYABORT; - } - '(' key_list ')' normal_key_options - { - if (add_create_index(Lex, $2, $4)) + if (Lex->add_create_index($2, $5, $6, $1 | $4)) MYSQL_YYABORT; } - | CREATE fulltext INDEX_SYM ident init_key_options ON - table_ident + '(' key_list ')' normal_key_options + opt_index_lock_algorithm { } + | create_or_replace fulltext INDEX_SYM opt_if_not_exists ident + ON table_ident { if (add_create_index_prepare(Lex, $7)) MYSQL_YYABORT; - } - '(' key_list ')' fulltext_key_options - { - if (add_create_index(Lex, $2, $4)) + if (Lex->add_create_index($2, $5, HA_KEY_ALG_UNDEF, $1 | $4)) MYSQL_YYABORT; } - | CREATE spatial INDEX_SYM ident init_key_options ON - table_ident + '(' key_list ')' fulltext_key_options + opt_index_lock_algorithm { } + | create_or_replace spatial INDEX_SYM opt_if_not_exists ident + ON table_ident { if (add_create_index_prepare(Lex, $7)) MYSQL_YYABORT; - } - '(' key_list ')' spatial_key_options - { - if (add_create_index(Lex, $2, $4)) + if (Lex->add_create_index($2, $5, HA_KEY_ALG_UNDEF, $1 | $4)) MYSQL_YYABORT; } - | CREATE DATABASE opt_if_not_exists ident + '(' key_list ')' spatial_key_options + opt_index_lock_algorithm { } + | create_or_replace DATABASE opt_if_not_exists ident { Lex->create_info.default_table_charset= NULL; Lex->create_info.used_fields= 0; @@ -2137,21 +2526,29 @@ create: opt_create_database_options { LEX *lex=Lex; - lex->sql_command=SQLCOM_CREATE_DB; + if (lex->set_command_with_check(SQLCOM_CREATE_DB, 0, $1 | $3)) + MYSQL_YYABORT; lex->name= $4; - lex->create_info.options=$3; } - | CREATE + | create_or_replace { - Lex->create_view_mode= VIEW_CREATE_NEW; + Lex->create_info.set($1); + Lex->create_view_mode= ($1.or_replace() ? VIEW_CREATE_OR_REPLACE : + VIEW_CREATE_NEW); Lex->create_view_algorithm= DTYPE_ALGORITHM_UNDEFINED; Lex->create_view_suid= TRUE; } - view_or_trigger_or_sp_or_event - {} - | CREATE USER clear_privileges grant_list + view_or_trigger_or_sp_or_event { } + | create_or_replace USER_SYM opt_if_not_exists clear_privileges grant_list { - Lex->sql_command = SQLCOM_CREATE_USER; + if (Lex->set_command_with_check(SQLCOM_CREATE_USER, $1 | $3)) + MYSQL_YYABORT; + } + | create_or_replace ROLE_SYM opt_if_not_exists + clear_privileges role_list opt_with_admin + { + if (Lex->set_command_with_check(SQLCOM_CREATE_ROLE, $1 | $3)) + MYSQL_YYABORT; } | CREATE LOGFILE_SYM GROUP_SYM logfile_group_info { @@ -2161,23 +2558,21 @@ create: { Lex->alter_tablespace_info->ts_cmd_type= CREATE_TABLESPACE; } - | CREATE server_def - { - Lex->sql_command= SQLCOM_CREATE_SERVER; - } + | create_or_replace { Lex->set_command(SQLCOM_CREATE_SERVER, $1); } + server_def + { } ; server_def: - SERVER_SYM - ident_or_text - FOREIGN DATA_SYM WRAPPER_SYM - ident_or_text - OPTIONS_SYM '(' server_options_list ')' + SERVER_SYM opt_if_not_exists ident_or_text { - Lex->server_options.server_name= $2.str; - Lex->server_options.server_name_length= $2.length; - Lex->server_options.scheme= $6.str; + if (Lex->add_create_options_with_check($2)) + MYSQL_YYABORT; + Lex->server_options.reset($3); } + FOREIGN DATA_SYM WRAPPER_SYM ident_or_text + OPTIONS_SYM '(' server_options_list ')' + { Lex->server_options.scheme= $8; } ; server_options_list: @@ -2186,29 +2581,35 @@ server_options_list: ; server_option: - USER TEXT_STRING_sys + USER_SYM TEXT_STRING_sys { - Lex->server_options.username= $2.str; + MYSQL_YYABORT_UNLESS(Lex->server_options.username.str == 0); + Lex->server_options.username= $2; } | HOST_SYM TEXT_STRING_sys { - Lex->server_options.host= $2.str; + MYSQL_YYABORT_UNLESS(Lex->server_options.host.str == 0); + Lex->server_options.host= $2; } | DATABASE TEXT_STRING_sys { - Lex->server_options.db= $2.str; + MYSQL_YYABORT_UNLESS(Lex->server_options.db.str == 0); + Lex->server_options.db= $2; } | OWNER_SYM TEXT_STRING_sys { - Lex->server_options.owner= $2.str; + MYSQL_YYABORT_UNLESS(Lex->server_options.owner.str == 0); + Lex->server_options.owner= $2; } - | PASSWORD TEXT_STRING_sys + | PASSWORD_SYM TEXT_STRING_sys { - Lex->server_options.password= $2.str; + MYSQL_YYABORT_UNLESS(Lex->server_options.password.str == 0); + Lex->server_options.password= $2; } | SOCKET_SYM TEXT_STRING_sys { - Lex->server_options.socket= $2.str; + MYSQL_YYABORT_UNLESS(Lex->server_options.socket.str == 0); + Lex->server_options.socket= $2; } | PORT_SYM ulong_num { @@ -2222,7 +2623,8 @@ event_tail: LEX *lex=Lex; lex->stmt_definition_begin= $1; - lex->create_info.options= $3; + if (lex->add_create_options_with_check($3)) + MYSQL_YYABORT; if (!(lex->event_parse_data= Event_parse_data::new_instance(thd))) MYSQL_YYABORT; lex->event_parse_data->identifier= $4; @@ -2285,7 +2687,7 @@ opt_ev_status: ev_starts: /* empty */ { - Item *item= new (thd->mem_root) Item_func_now_local(0); + Item *item= new (thd->mem_root) Item_func_now_local(thd, 0); if (item == NULL) MYSQL_YYABORT; Lex->event_parse_data->item_starts= item; @@ -2310,16 +2712,11 @@ opt_ev_on_completion: ; ev_on_completion: - ON COMPLETION_SYM PRESERVE_SYM + ON COMPLETION_SYM opt_not PRESERVE_SYM { - Lex->event_parse_data->on_completion= - Event_parse_data::ON_COMPLETION_PRESERVE; - $$= 1; - } - | ON COMPLETION_SYM NOT_SYM PRESERVE_SYM - { - Lex->event_parse_data->on_completion= - Event_parse_data::ON_COMPLETION_DROP; + Lex->event_parse_data->on_completion= $3 + ? Event_parse_data::ON_COMPLETION_DROP + : Event_parse_data::ON_COMPLETION_PRESERVE; $$= 1; } ; @@ -2356,26 +2753,15 @@ ev_sql_stmt: - CREATE PROCEDURE ... BEGIN DROP EVENT ... END| */ if (lex->sphead) - { - my_error(ER_EVENT_RECURSION_FORBIDDEN, MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_EVENT_RECURSION_FORBIDDEN, MYF(0))); - if (!(lex->sphead= new sp_head())) + if (!make_sp_head(thd, lex->event_parse_data->identifier, TYPE_ENUM_PROCEDURE)) MYSQL_YYABORT; - lex->sphead->reset_thd_mem_root(thd); - lex->sphead->init(lex); - lex->sphead->init_sp_name(thd, lex->event_parse_data->identifier); - - lex->sphead->m_type= TYPE_ENUM_PROCEDURE; - - bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); - lex->sphead->m_chistics= &lex->sp_chistics; - + lex->sp_chistics.suid= SP_IS_SUID; //always the definer! lex->sphead->set_body_start(thd, lip->get_cpp_ptr()); } - ev_sql_stmt_inner + sp_proc_stmt { LEX *lex= thd->lex; @@ -2383,28 +2769,10 @@ ev_sql_stmt: lex->sphead->set_stmt_end(thd); lex->sphead->restore_thd_mem_root(thd); - lex->sp_chistics.suid= SP_IS_SUID; //always the definer! - lex->event_parse_data->body_changed= TRUE; } ; -ev_sql_stmt_inner: - sp_proc_stmt_statement - | sp_proc_stmt_return - | sp_proc_stmt_if - | case_stmt_specification - | sp_labeled_block - | sp_unlabeled_block - | sp_labeled_control - | sp_proc_stmt_unlabeled - | sp_proc_stmt_leave - | sp_proc_stmt_iterate - | sp_proc_stmt_open - | sp_proc_stmt_fetch - | sp_proc_stmt_close - ; - clear_privileges: /* Nothing */ { @@ -2424,15 +2792,10 @@ sp_name: ident '.' ident { if (!$1.str || check_db_name(&$1)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_DB_NAME, MYF(0), $1.str)); if (check_routine_name(&$3)) - { MYSQL_YYABORT; - } - $$= new sp_name($1, $3, true); + $$= new (thd->mem_root) sp_name($1, $3, true); if ($$ == NULL) MYSQL_YYABORT; $$->init_qname(thd); @@ -2447,7 +2810,7 @@ sp_name: } if (lex->copy_db_to(&db.str, &db.length)) MYSQL_YYABORT; - $$= new sp_name(db, $1, false); + $$= new (thd->mem_root) sp_name(db, $1, false); if ($$ == NULL) MYSQL_YYABORT; $$->init_qname(thd); @@ -2485,8 +2848,7 @@ sp_chistic: /* Create characteristics */ sp_c_chistic: sp_chistic { } - | DETERMINISTIC_SYM { Lex->sp_chistics.detistic= TRUE; } - | not DETERMINISTIC_SYM { Lex->sp_chistics.detistic= FALSE; } + | opt_not DETERMINISTIC_SYM { Lex->sp_chistics.detistic= ! $1; } ; sp_suid: @@ -2527,11 +2889,11 @@ opt_sp_cparams: sp_cparams: sp_cparams ',' expr { - Lex->value_list.push_back($3); + Lex->value_list.push_back($3, thd->mem_root); } | expr { - Lex->value_list.push_back($1); + Lex->value_list.push_back($1, thd->mem_root); } ; @@ -2542,54 +2904,40 @@ sp_fdparam_list: ; sp_fdparams: - sp_fdparams ',' sp_fdparam - | sp_fdparam + sp_fdparams ',' sp_param_name_and_type + | sp_param_name_and_type ; -sp_init_param: - /* Empty */ +sp_param_name_and_type: + ident { LEX *lex= Lex; + sp_pcontext *spc= lex->spcont; - lex->length= 0; - lex->dec= 0; - lex->type= 0; - - lex->default_value= 0; - lex->on_update_value= 0; + if (spc->find_variable($1, TRUE)) + my_yyabort_error((ER_SP_DUP_PARAM, MYF(0), $1.str)); - lex->comment= null_lex_str; - lex->charset= NULL; + sp_variable *spvar= spc->add_variable(thd, $1); - lex->interval_list.empty(); - lex->uint_geom_type= 0; - lex->vcol_info= 0; + lex->init_last_field(&spvar->field_def, $1.str, + thd->variables.collation_database); + $<spvar>$= spvar; } - ; - -sp_fdparam: - ident sp_init_param type_with_opt_collate + type_with_opt_collate { LEX *lex= Lex; - sp_pcontext *spc= lex->spcont; - - if (spc->find_variable(&$1, TRUE)) - { - my_error(ER_SP_DUP_PARAM, MYF(0), $1.str); - MYSQL_YYABORT; - } - sp_variable_t *spvar= spc->push_variable(&$1, - (enum enum_field_types)$3, - sp_param_in); + sp_variable *spvar= $<spvar>2; - if (lex->sphead->fill_field_definition(thd, lex, - (enum enum_field_types) $3, - &spvar->field_def)) + spvar->type= $3; + if (lex->sphead->fill_field_definition(thd, lex, $3, + lex->last_field)) { MYSQL_YYABORT; } spvar->field_def.field_name= spvar->name.str; spvar->field_def.pack_flag |= FIELDFLAG_MAYBE_NULL; + + $$= spvar; } ; @@ -2605,36 +2953,14 @@ sp_pdparams: ; sp_pdparam: - sp_opt_inout sp_init_param ident type_with_opt_collate - { - LEX *lex= Lex; - sp_pcontext *spc= lex->spcont; - - if (spc->find_variable(&$3, TRUE)) - { - my_error(ER_SP_DUP_PARAM, MYF(0), $3.str); - MYSQL_YYABORT; - } - sp_variable_t *spvar= spc->push_variable(&$3, - (enum enum_field_types)$4, - (sp_param_mode_t)$1); - - if (lex->sphead->fill_field_definition(thd, lex, - (enum enum_field_types) $4, - &spvar->field_def)) - { - MYSQL_YYABORT; - } - spvar->field_def.field_name= spvar->name.str; - spvar->field_def.pack_flag |= FIELDFLAG_MAYBE_NULL; - } + sp_opt_inout sp_param_name_and_type { $2->mode=$1; } ; sp_opt_inout: - /* Empty */ { $$= sp_param_in; } - | IN_SYM { $$= sp_param_in; } - | OUT_SYM { $$= sp_param_out; } - | INOUT_SYM { $$= sp_param_inout; } + /* Empty */ { $$= sp_variable::MODE_IN; } + | IN_SYM { $$= sp_variable::MODE_IN; } + | OUT_SYM { $$= sp_variable::MODE_OUT; } + | INOUT_SYM { $$= sp_variable::MODE_INOUT; } ; sp_proc_stmts: @@ -2659,17 +2985,9 @@ sp_decls: shift/reduce conflicts with the wrong result. (And we get better error handling this way.) */ if (($2.vars || $2.conds) && ($1.curs || $1.hndlrs)) - { /* Variable or condition following cursor or handler */ - my_message(ER_SP_VARCOND_AFTER_CURSHNDLR, - ER(ER_SP_VARCOND_AFTER_CURSHNDLR), MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_VARCOND_AFTER_CURSHNDLR, MYF(0))); if ($2.curs && $1.hndlrs) - { /* Cursor following handler */ - my_message(ER_SP_CURSOR_AFTER_HANDLER, - ER(ER_SP_CURSOR_AFTER_HANDLER), MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_CURSOR_AFTER_HANDLER, MYF(0))); $$.vars= $1.vars + $2.vars; $$.conds= $1.conds + $2.conds; $$.hndlrs= $1.hndlrs + $2.hndlrs; @@ -2681,9 +2999,17 @@ sp_decl: DECLARE_SYM sp_decl_idents { LEX *lex= Lex; + sp_pcontext *pctx= lex->spcont; + + // get the last variable: + uint num_vars= pctx->context_var_count(); + uint var_idx= pctx->var_context2runtime(num_vars - 1); + sp_variable *spvar= pctx->find_variable(var_idx); lex->sphead->reset_lex(thd); - lex->spcont->declare_var_boundary($2); + pctx->declare_var_boundary($2); + thd->lex->init_last_field(&spvar->field_def, spvar->name.str, + thd->variables.collation_database); } type_with_opt_collate sp_opt_default @@ -2691,12 +3017,12 @@ sp_decl: LEX *lex= Lex; sp_pcontext *pctx= lex->spcont; uint num_vars= pctx->context_var_count(); - enum enum_field_types var_type= (enum enum_field_types) $4; + enum enum_field_types var_type= $4; Item *dflt_value_item= $5; if (!dflt_value_item) { - dflt_value_item= new (thd->mem_root) Item_null(); + dflt_value_item= new (thd->mem_root) Item_null(thd); if (dflt_value_item == NULL) MYSQL_YYABORT; /* QQ Set to the var_type with null_value? */ @@ -2705,13 +3031,18 @@ sp_decl: for (uint i = num_vars-$2 ; i < num_vars ; i++) { uint var_idx= pctx->var_context2runtime(i); - sp_variable_t *spvar= pctx->find_variable(var_idx); + sp_variable *spvar= pctx->find_variable(var_idx); + bool last= i == num_vars - 1; if (!spvar) MYSQL_YYABORT; + if (!last) + spvar->field_def= *lex->last_field; + spvar->type= var_type; - spvar->dflt= dflt_value_item; + spvar->default_value= dflt_value_item; + spvar->field_def.field_name= spvar->name.str; if (lex->sphead->fill_field_definition(thd, lex, var_type, &spvar->field_def)) @@ -2719,20 +3050,15 @@ sp_decl: MYSQL_YYABORT; } - spvar->field_def.field_name= spvar->name.str; spvar->field_def.pack_flag |= FIELDFLAG_MAYBE_NULL; /* The last instruction is responsible for freeing LEX. */ - sp_instr_set *is= new sp_instr_set(lex->sphead->instructions(), - pctx, - var_idx, - dflt_value_item, - var_type, - lex, - (i == num_vars - 1)); - if (is == NULL || - lex->sphead->add_instr(is)) + sp_instr_set *is= new (thd->mem_root) + sp_instr_set(lex->sphead->instructions(), + pctx, var_idx, dflt_value_item, + var_type, lex, last); + if (is == NULL || lex->sphead->add_instr(is)) MYSQL_YYABORT; } @@ -2747,12 +3073,9 @@ sp_decl: LEX *lex= Lex; sp_pcontext *spc= lex->spcont; - if (spc->find_cond(&$2, TRUE)) - { - my_error(ER_SP_DUP_COND, MYF(0), $2.str); - MYSQL_YYABORT; - } - if(thd->lex->spcont->push_cond(&$2, $5)) + if (spc->find_condition($2, TRUE)) + my_yyabort_error((ER_SP_DUP_COND, MYF(0), $2.str)); + if(spc->add_condition(thd, $2, $5)) MYSQL_YYABORT; $$.vars= $$.hndlrs= $$.curs= 0; $$.conds= 1; @@ -2762,21 +3085,26 @@ sp_decl: LEX *lex= Lex; sp_head *sp= lex->sphead; - lex->spcont= lex->spcont->push_context(LABEL_HANDLER_SCOPE); + sp_handler *h= lex->spcont->add_handler(thd, + (sp_handler::enum_type) $2); + + lex->spcont= lex->spcont->push_context(thd, + sp_pcontext::HANDLER_SCOPE); sp_pcontext *ctx= lex->spcont; sp_instr_hpush_jump *i= - new sp_instr_hpush_jump(sp->instructions(), ctx, $2, - ctx->current_var_count()); + new (thd->mem_root) sp_instr_hpush_jump(sp->instructions(), + ctx, h); + if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; /* For continue handlers, mark end of handler scope. */ - if ($2 == SP_HANDLER_CONTINUE && + if ($2 == sp_handler::CONTINUE && sp->push_backpatch(i, ctx->last_label())) MYSQL_YYABORT; - if (sp->push_backpatch(i, ctx->push_label(empty_c_string, 0))) + if (sp->push_backpatch(i, ctx->push_label(thd, empty_lex_str, 0))) MYSQL_YYABORT; } sp_hcond_list sp_proc_stmt @@ -2784,20 +3112,21 @@ sp_decl: LEX *lex= Lex; sp_head *sp= lex->sphead; sp_pcontext *ctx= lex->spcont; - sp_label_t *hlab= lex->spcont->pop_label(); /* After this hdlr */ + sp_label *hlab= lex->spcont->pop_label(); /* After this hdlr */ sp_instr_hreturn *i; - if ($2 == SP_HANDLER_CONTINUE) + if ($2 == sp_handler::CONTINUE) { - i= new sp_instr_hreturn(sp->instructions(), ctx, - ctx->current_var_count()); + i= new (thd->mem_root) + sp_instr_hreturn(sp->instructions(), ctx); if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; } else { /* EXIT or UNDO handler, just jump to the end of the block */ - i= new sp_instr_hreturn(sp->instructions(), ctx, 0); + i= new (thd->mem_root) + sp_instr_hreturn(sp->instructions(), ctx); if (i == NULL || sp->add_instr(i) || sp->push_backpatch(i, lex->spcont->last_label())) /* Block end */ @@ -2808,8 +3137,7 @@ sp_decl: lex->spcont= ctx->pop_context(); $$.vars= $$.conds= $$.curs= 0; - $$.hndlrs= $6; - lex->spcont->add_handlers($6); + $$.hndlrs= 1; } | DECLARE_SYM ident CURSOR_SYM FOR_SYM sp_cursor_stmt { @@ -2819,17 +3147,13 @@ sp_decl: uint offp; sp_instr_cpush *i; - if (ctx->find_cursor(&$2, &offp, TRUE)) - { - my_error(ER_SP_DUP_CURS, MYF(0), $2.str); - delete $5; - MYSQL_YYABORT; - } - i= new sp_instr_cpush(sp->instructions(), ctx, $5, - ctx->current_cursor_count()); - if (i == NULL || - sp->add_instr(i) || - ctx->push_cursor(&$2)) + if (ctx->find_cursor($2, &offp, TRUE)) + my_yyabort_error((ER_SP_DUP_CURS, MYF(0), $2.str)); + + i= new (thd->mem_root) + sp_instr_cpush(sp->instructions(), ctx, $5, + ctx->current_cursor_count()); + if (i == NULL || sp->add_instr(i) || ctx->add_cursor($2)) MYSQL_YYABORT; $$.vars= $$.conds= $$.hndlrs= 0; $$.curs= 1; @@ -2847,11 +3171,7 @@ sp_cursor_stmt: DBUG_ASSERT(lex->sql_command == SQLCOM_SELECT); if (lex->result) - { - my_message(ER_SP_BAD_CURSOR_SELECT, ER(ER_SP_BAD_CURSOR_SELECT), - MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BAD_CURSOR_SELECT, MYF(0))); lex->sp_lex_in_use= TRUE; $$= lex; if (lex->sphead->restore_lex(thd)) @@ -2860,9 +3180,9 @@ sp_cursor_stmt: ; sp_handler_type: - EXIT_SYM { $$= SP_HANDLER_EXIT; } - | CONTINUE_SYM { $$= SP_HANDLER_CONTINUE; } - /*| UNDO_SYM { QQ No yet } */ + EXIT_SYM { $$= sp_handler::EXIT; } + | CONTINUE_SYM { $$= sp_handler::CONTINUE; } + /*| UNDO_SYM { QQ No yet } */ ; sp_hcond_list: @@ -2879,19 +3199,11 @@ sp_hcond_element: sp_head *sp= lex->sphead; sp_pcontext *ctx= lex->spcont->parent_context(); - if (ctx->find_handler($1)) - { - my_message(ER_SP_DUP_HANDLER, ER(ER_SP_DUP_HANDLER), MYF(0)); - MYSQL_YYABORT; - } - else - { - sp_instr_hpush_jump *i= - (sp_instr_hpush_jump *)sp->last_instruction(); + if (ctx->check_duplicate_handler($1)) + my_yyabort_error((ER_SP_DUP_HANDLER, MYF(0))); - i->add_condition($1); - ctx->push_handler($1); - } + sp_instr_hpush_jump *i= (sp_instr_hpush_jump *)sp->last_instruction(); + i->add_condition($1); } ; @@ -2899,15 +3211,10 @@ sp_cond: ulong_num { /* mysql errno */ if ($1 == 0) - { - my_error(ER_WRONG_VALUE, MYF(0), "CONDITION", "0"); - MYSQL_YYABORT; - } - $$= (sp_cond_type_t *) thd->alloc(sizeof(sp_cond_type_t)); + my_yyabort_error((ER_WRONG_VALUE, MYF(0), "CONDITION", "0")); + $$= new (thd->mem_root) sp_condition_value($1); if ($$ == NULL) MYSQL_YYABORT; - $$->type= sp_cond_type_t::number; - $$->mysqlerr= $1; } | sqlstate ; @@ -2915,17 +3222,19 @@ sp_cond: sqlstate: SQLSTATE_SYM opt_value TEXT_STRING_literal { /* SQLSTATE */ - if (!sp_cond_check(&$3)) - { - my_error(ER_SP_BAD_SQLSTATE, MYF(0), $3.str); - MYSQL_YYABORT; - } - $$= (sp_cond_type_t *) thd->alloc(sizeof(sp_cond_type_t)); + + /* + An error is triggered: + - if the specified string is not a valid SQLSTATE, + - or if it represents the completion condition -- it is not + allowed to SIGNAL, or declare a handler for the completion + condition. + */ + if (!is_sqlstate_valid(&$3) || is_sqlstate_completion($3.str)) + my_yyabort_error((ER_SP_BAD_SQLSTATE, MYF(0), $3.str)); + $$= new (thd->mem_root) sp_condition_value($3.str); if ($$ == NULL) MYSQL_YYABORT; - $$->type= sp_cond_type_t::state; - memcpy($$->sqlstate, $3.str, SQLSTATE_LENGTH); - $$->sqlstate[SQLSTATE_LENGTH]= '\0'; } ; @@ -2941,33 +3250,27 @@ sp_hcond: } | ident /* CONDITION name */ { - $$= Lex->spcont->find_cond(&$1); + $$= Lex->spcont->find_condition($1, false); if ($$ == NULL) - { - my_error(ER_SP_COND_MISMATCH, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_COND_MISMATCH, MYF(0), $1.str)); } | SQLWARNING_SYM /* SQLSTATEs 01??? */ { - $$= (sp_cond_type_t *) thd->alloc(sizeof(sp_cond_type_t)); + $$= new (thd->mem_root) sp_condition_value(sp_condition_value::WARNING); if ($$ == NULL) MYSQL_YYABORT; - $$->type= sp_cond_type_t::warning; } | not FOUND_SYM /* SQLSTATEs 02??? */ { - $$= (sp_cond_type_t *) thd->alloc(sizeof(sp_cond_type_t)); + $$= new (thd->mem_root) sp_condition_value(sp_condition_value::NOT_FOUND); if ($$ == NULL) MYSQL_YYABORT; - $$->type= sp_cond_type_t::notfound; } | SQLEXCEPTION_SYM /* All other SQLSTATEs */ { - $$= (sp_cond_type_t *) thd->alloc(sizeof(sp_cond_type_t)); + $$= new (thd->mem_root) sp_condition_value(sp_condition_value::EXCEPTION); if ($$ == NULL) MYSQL_YYABORT; - $$->type= sp_cond_type_t::exception; } ; @@ -2978,9 +3281,9 @@ signal_stmt: Yacc_state *state= & thd->m_parser_state->m_yacc; lex->sql_command= SQLCOM_SIGNAL; - lex->m_stmt= new (thd->mem_root) Signal_statement(lex, $2, - state->m_set_signal_info); - if (lex->m_stmt == NULL) + lex->m_sql_cmd= + new (thd->mem_root) Sql_cmd_signal($2, state->m_set_signal_info); + if (lex->m_sql_cmd == NULL) MYSQL_YYABORT; } ; @@ -2989,24 +3292,16 @@ signal_value: ident { LEX *lex= Lex; - sp_cond_type_t *cond; + sp_condition_value *cond; + + /* SIGNAL foo cannot be used outside of stored programs */ if (lex->spcont == NULL) - { - /* SIGNAL foo cannot be used outside of stored programs */ - my_error(ER_SP_COND_MISMATCH, MYF(0), $1.str); - MYSQL_YYABORT; - } - cond= lex->spcont->find_cond(&$1); + my_yyabort_error((ER_SP_COND_MISMATCH, MYF(0), $1.str)); + cond= lex->spcont->find_condition($1, false); if (cond == NULL) - { - my_error(ER_SP_COND_MISMATCH, MYF(0), $1.str); - MYSQL_YYABORT; - } - if (cond->type != sp_cond_type_t::state) - { - my_error(ER_SIGNAL_BAD_CONDITION_TYPE, MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_COND_MISMATCH, MYF(0), $1.str)); + if (cond->type != sp_condition_value::SQLSTATE) + my_yyabort_error((ER_SIGNAL_BAD_CONDITION_TYPE, MYF(0))); $$= cond; } | sqlstate @@ -3044,11 +3339,8 @@ signal_information_item_list: info= &thd->m_parser_state->m_yacc.m_set_signal_info; int index= (int) $3; if (info->m_item[index] != NULL) - { - my_error(ER_DUP_SIGNAL_SET, MYF(0), - Diag_condition_item_names[index].str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_DUP_SIGNAL_SET, MYF(0), + Diag_condition_item_names[index].str)); info->m_item[index]= $5; } ; @@ -3071,7 +3363,7 @@ signal_allowed_expr: SIGNAL/RESIGNAL ... SET <signal condition item name> = @foo := expr */ - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } } @@ -3116,13 +3408,160 @@ resignal_stmt: Yacc_state *state= & thd->m_parser_state->m_yacc; lex->sql_command= SQLCOM_RESIGNAL; - lex->m_stmt= new (thd->mem_root) Resignal_statement(lex, $2, - state->m_set_signal_info); - if (lex->m_stmt == NULL) + lex->m_sql_cmd= + new (thd->mem_root) Sql_cmd_resignal($2, + state->m_set_signal_info); + if (lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } + ; + +get_diagnostics: + GET_SYM which_area DIAGNOSTICS_SYM diagnostics_information + { + Diagnostics_information *info= $4; + + info->set_which_da($2); + + Lex->sql_command= SQLCOM_GET_DIAGNOSTICS; + Lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_get_diagnostics(info); + + if (Lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } + ; + +which_area: + /* If <which area> is not specified, then CURRENT is implicit. */ + { $$= Diagnostics_information::CURRENT_AREA; } + | CURRENT_SYM + { $$= Diagnostics_information::CURRENT_AREA; } + ; + +diagnostics_information: + statement_information + { + $$= new (thd->mem_root) Statement_information($1); + if ($$ == NULL) + MYSQL_YYABORT; + } + | CONDITION_SYM condition_number condition_information + { + $$= new (thd->mem_root) Condition_information($2, $3); + if ($$ == NULL) MYSQL_YYABORT; } ; +statement_information: + statement_information_item + { + $$= new (thd->mem_root) List<Statement_information_item>; + if ($$ == NULL || $$->push_back($1, thd->mem_root)) + MYSQL_YYABORT; + } + | statement_information ',' statement_information_item + { + if ($1->push_back($3, thd->mem_root)) + MYSQL_YYABORT; + $$= $1; + } + ; + +statement_information_item: + simple_target_specification '=' statement_information_item_name + { + $$= new (thd->mem_root) Statement_information_item($3, $1); + if ($$ == NULL) + MYSQL_YYABORT; + } + +simple_target_specification: + ident + { + Lex_input_stream *lip= &thd->m_parser_state->m_lip; + $$= create_item_for_sp_var(thd, $1, NULL, + lip->get_tok_start(), lip->get_ptr()); + + if ($$ == NULL) + MYSQL_YYABORT; + } + | '@' ident_or_text + { + $$= new (thd->mem_root) Item_func_get_user_var(thd, $2); + if ($$ == NULL) + MYSQL_YYABORT; + } + ; + +statement_information_item_name: + NUMBER_SYM + { $$= Statement_information_item::NUMBER; } + | ROW_COUNT_SYM + { $$= Statement_information_item::ROW_COUNT; } + ; + +/* + Only a limited subset of <expr> are allowed in GET DIAGNOSTICS + <condition number>, same subset as for SIGNAL/RESIGNAL. +*/ +condition_number: + signal_allowed_expr + { $$= $1; } + ; + +condition_information: + condition_information_item + { + $$= new (thd->mem_root) List<Condition_information_item>; + if ($$ == NULL || $$->push_back($1, thd->mem_root)) + MYSQL_YYABORT; + } + | condition_information ',' condition_information_item + { + if ($1->push_back($3, thd->mem_root)) + MYSQL_YYABORT; + $$= $1; + } + ; + +condition_information_item: + simple_target_specification '=' condition_information_item_name + { + $$= new (thd->mem_root) Condition_information_item($3, $1); + if ($$ == NULL) + MYSQL_YYABORT; + } + +condition_information_item_name: + CLASS_ORIGIN_SYM + { $$= Condition_information_item::CLASS_ORIGIN; } + | SUBCLASS_ORIGIN_SYM + { $$= Condition_information_item::SUBCLASS_ORIGIN; } + | CONSTRAINT_CATALOG_SYM + { $$= Condition_information_item::CONSTRAINT_CATALOG; } + | CONSTRAINT_SCHEMA_SYM + { $$= Condition_information_item::CONSTRAINT_SCHEMA; } + | CONSTRAINT_NAME_SYM + { $$= Condition_information_item::CONSTRAINT_NAME; } + | CATALOG_NAME_SYM + { $$= Condition_information_item::CATALOG_NAME; } + | SCHEMA_NAME_SYM + { $$= Condition_information_item::SCHEMA_NAME; } + | TABLE_NAME_SYM + { $$= Condition_information_item::TABLE_NAME; } + | COLUMN_NAME_SYM + { $$= Condition_information_item::COLUMN_NAME; } + | CURSOR_NAME_SYM + { $$= Condition_information_item::CURSOR_NAME; } + | MESSAGE_TEXT_SYM + { $$= Condition_information_item::MESSAGE_TEXT; } + | MYSQL_ERRNO_SYM + { $$= Condition_information_item::MYSQL_ERRNO; } + | RETURNED_SQLSTATE_SYM + { $$= Condition_information_item::RETURNED_SQLSTATE; } + ; + sp_decl_idents: ident { @@ -3131,12 +3570,9 @@ sp_decl_idents: LEX *lex= Lex; sp_pcontext *spc= lex->spcont; - if (spc->find_variable(&$1, TRUE)) - { - my_error(ER_SP_DUP_VAR, MYF(0), $1.str); - MYSQL_YYABORT; - } - spc->push_variable(&$1, (enum_field_types)0, sp_param_in); + if (spc->find_variable($1, TRUE)) + my_yyabort_error((ER_SP_DUP_VAR, MYF(0), $1.str)); + spc->add_variable(thd, $1); $$= 1; } | sp_decl_idents ',' ident @@ -3146,12 +3582,9 @@ sp_decl_idents: LEX *lex= Lex; sp_pcontext *spc= lex->spcont; - if (spc->find_variable(&$3, TRUE)) - { - my_error(ER_SP_DUP_VAR, MYF(0), $3.str); - MYSQL_YYABORT; - } - spc->push_variable(&$3, (enum_field_types)0, sp_param_in); + if (spc->find_variable($3, TRUE)) + my_yyabort_error((ER_SP_DUP_VAR, MYF(0), $3.str)); + spc->add_variable(thd, $3); $$= $1 + 1; } ; @@ -3164,23 +3597,32 @@ sp_opt_default: sp_proc_stmt: sp_proc_stmt_statement | sp_proc_stmt_return - | sp_proc_stmt_if - | case_stmt_specification | sp_labeled_block | sp_unlabeled_block | sp_labeled_control - | sp_proc_stmt_unlabeled | sp_proc_stmt_leave | sp_proc_stmt_iterate | sp_proc_stmt_open | sp_proc_stmt_fetch | sp_proc_stmt_close + | sp_proc_stmt_compound_ok + ; + +sp_proc_stmt_compound_ok: + sp_proc_stmt_if + | case_stmt_specification + | sp_unlabeled_block_not_atomic + | sp_unlabeled_control ; sp_proc_stmt_if: - IF - { Lex->sphead->new_cont_backpatch(NULL); } - sp_if END IF + IF_SYM + { + if (maybe_start_compound_statement(thd)) + MYSQL_YYABORT; + Lex->sphead->new_cont_backpatch(NULL); + } + sp_if END IF_SYM { Lex->sphead->do_cont_backpatch(); } ; @@ -3199,11 +3641,9 @@ sp_proc_stmt_statement: sp_head *sp= lex->sphead; sp->m_flags|= sp_get_flags_for_command(lex); + /* "USE db" doesn't work in a procedure */ if (lex->sql_command == SQLCOM_CHANGE_DB) - { /* "USE db" doesn't work in a procedure */ - my_error(ER_SP_BADSTATEMENT, MYF(0), "USE"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "USE")); /* Don't add an instruction for SET statements, since all instructions for them were already added during processing @@ -3213,8 +3653,8 @@ sp_proc_stmt_statement: lex->var_list.is_empty()); if (lex->sql_command != SQLCOM_SET_OPTION) { - sp_instr_stmt *i=new sp_instr_stmt(sp->instructions(), - lex->spcont, lex); + sp_instr_stmt *i=new (thd->mem_root) + sp_instr_stmt(sp->instructions(), lex->spcont, lex); if (i == NULL) MYSQL_YYABORT; @@ -3226,7 +3666,7 @@ sp_proc_stmt_statement: if (yychar == YYEMPTY) i->m_query.length= lip->get_ptr() - sp->m_tmp_query; else - i->m_query.length= lip->get_tok_end() - sp->m_tmp_query; + i->m_query.length= lip->get_tok_start() - sp->m_tmp_query;; if (!(i->m_query.str= strmake_root(thd->mem_root, sp->m_tmp_query, i->m_query.length)) || @@ -3247,90 +3687,69 @@ sp_proc_stmt_return: sp_head *sp= lex->sphead; if (sp->m_type != TYPE_ENUM_FUNCTION) - { - my_message(ER_SP_BADRETURN, ER(ER_SP_BADRETURN), MYF(0)); + my_yyabort_error((ER_SP_BADRETURN, MYF(0))); + + sp_instr_freturn *i; + + i= new (thd->mem_root) + sp_instr_freturn(sp->instructions(), lex->spcont, $3, + sp->m_return_field_def.sql_type, lex); + if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; - } - else - { - sp_instr_freturn *i; + sp->m_flags|= sp_head::HAS_RETURN; - i= new sp_instr_freturn(sp->instructions(), lex->spcont, $3, - sp->m_return_field_def.sql_type, lex); - if (i == NULL || - sp->add_instr(i)) - MYSQL_YYABORT; - sp->m_flags|= sp_head::HAS_RETURN; - } if (sp->restore_lex(thd)) MYSQL_YYABORT; } ; -sp_proc_stmt_unlabeled: - { /* Unlabeled controls get a secret label. */ - LEX *lex= Lex; - - lex->spcont->push_label((char *)"", lex->sphead->instructions()); - } - sp_unlabeled_control - { - LEX *lex= Lex; - - lex->sphead->backpatch(lex->spcont->pop_label()); - } - ; - sp_proc_stmt_leave: LEAVE_SYM label_ident { LEX *lex= Lex; sp_head *sp = lex->sphead; sp_pcontext *ctx= lex->spcont; - sp_label_t *lab= ctx->find_label($2.str); + sp_label *lab= ctx->find_label($2); if (! lab) + my_yyabort_error((ER_SP_LILABEL_MISMATCH, MYF(0), "LEAVE", $2.str)); + + sp_instr_jump *i; + uint ip= sp->instructions(); + uint n; + /* + When jumping to a BEGIN-END block end, the target jump + points to the block hpop/cpop cleanup instructions, + so we should exclude the block context here. + When jumping to something else (i.e., SP_LAB_ITER), + there are no hpop/cpop at the jump destination, + so we should include the block context here for cleanup. + */ + bool exclusive= (lab->type == sp_label::BEGIN); + + n= ctx->diff_handlers(lab->ctx, exclusive); + if (n) { - my_error(ER_SP_LILABEL_MISMATCH, MYF(0), "LEAVE", $2.str); - MYSQL_YYABORT; + sp_instr_hpop *hpop= new (thd->mem_root) + sp_instr_hpop(ip++, ctx, n); + if (hpop == NULL) + MYSQL_YYABORT; + sp->add_instr(hpop); } - else + n= ctx->diff_cursors(lab->ctx, exclusive); + if (n) { - sp_instr_jump *i; - uint ip= sp->instructions(); - uint n; - /* - When jumping to a BEGIN-END block end, the target jump - points to the block hpop/cpop cleanup instructions, - so we should exclude the block context here. - When jumping to something else (i.e., SP_LAB_ITER), - there are no hpop/cpop at the jump destination, - so we should include the block context here for cleanup. - */ - bool exclusive= (lab->type == SP_LAB_BEGIN); - - n= ctx->diff_handlers(lab->ctx, exclusive); - if (n) - { - sp_instr_hpop *hpop= new sp_instr_hpop(ip++, ctx, n); - if (hpop == NULL) - MYSQL_YYABORT; - sp->add_instr(hpop); - } - n= ctx->diff_cursors(lab->ctx, exclusive); - if (n) - { - sp_instr_cpop *cpop= new sp_instr_cpop(ip++, ctx, n); - if (cpop == NULL) - MYSQL_YYABORT; - sp->add_instr(cpop); - } - i= new sp_instr_jump(ip, ctx); - if (i == NULL) + sp_instr_cpop *cpop= new (thd->mem_root) + sp_instr_cpop(ip++, ctx, n); + if (cpop == NULL) MYSQL_YYABORT; - sp->push_backpatch(i, lab); /* Jumping forward */ - sp->add_instr(i); + sp->add_instr(cpop); } + i= new (thd->mem_root) sp_instr_jump(ip, ctx); + if (i == NULL) + MYSQL_YYABORT; + sp->push_backpatch(i, lab); /* Jumping forward */ + sp->add_instr(i); } ; @@ -3340,40 +3759,38 @@ sp_proc_stmt_iterate: LEX *lex= Lex; sp_head *sp= lex->sphead; sp_pcontext *ctx= lex->spcont; - sp_label_t *lab= ctx->find_label($2.str); + sp_label *lab= ctx->find_label($2); + + if (! lab || lab->type != sp_label::ITERATION) + my_yyabort_error((ER_SP_LILABEL_MISMATCH, MYF(0), "ITERATE", $2.str)); + + sp_instr_jump *i; + uint ip= sp->instructions(); + uint n; - if (! lab || lab->type != SP_LAB_ITER) + n= ctx->diff_handlers(lab->ctx, FALSE); /* Inclusive the dest. */ + if (n) { - my_error(ER_SP_LILABEL_MISMATCH, MYF(0), "ITERATE", $2.str); - MYSQL_YYABORT; + sp_instr_hpop *hpop= new (thd->mem_root) + sp_instr_hpop(ip++, ctx, n); + if (hpop == NULL || + sp->add_instr(hpop)) + MYSQL_YYABORT; } - else + n= ctx->diff_cursors(lab->ctx, FALSE); /* Inclusive the dest. */ + if (n) { - sp_instr_jump *i; - uint ip= sp->instructions(); - uint n; - - n= ctx->diff_handlers(lab->ctx, FALSE); /* Inclusive the dest. */ - if (n) - { - sp_instr_hpop *hpop= new sp_instr_hpop(ip++, ctx, n); - if (hpop == NULL || - sp->add_instr(hpop)) - MYSQL_YYABORT; - } - n= ctx->diff_cursors(lab->ctx, FALSE); /* Inclusive the dest. */ - if (n) - { - sp_instr_cpop *cpop= new sp_instr_cpop(ip++, ctx, n); - if (cpop == NULL || - sp->add_instr(cpop)) - MYSQL_YYABORT; - } - i= new sp_instr_jump(ip, ctx, lab->ip); /* Jump back */ - if (i == NULL || - sp->add_instr(i)) + sp_instr_cpop *cpop= new (thd->mem_root) + sp_instr_cpop(ip++, ctx, n); + if (cpop == NULL || + sp->add_instr(cpop)) MYSQL_YYABORT; } + i= new (thd->mem_root) + sp_instr_jump(ip, ctx, lab->ip); /* Jump back */ + if (i == NULL || + sp->add_instr(i)) + MYSQL_YYABORT; } ; @@ -3385,12 +3802,10 @@ sp_proc_stmt_open: uint offset; sp_instr_copen *i; - if (! lex->spcont->find_cursor(&$2, &offset)) - { - my_error(ER_SP_CURSOR_MISMATCH, MYF(0), $2.str); - MYSQL_YYABORT; - } - i= new sp_instr_copen(sp->instructions(), lex->spcont, offset); + if (! lex->spcont->find_cursor($2, &offset, false)) + my_yyabort_error((ER_SP_CURSOR_MISMATCH, MYF(0), $2.str)); + i= new (thd->mem_root) + sp_instr_copen(sp->instructions(), lex->spcont, offset); if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; @@ -3405,12 +3820,10 @@ sp_proc_stmt_fetch: uint offset; sp_instr_cfetch *i; - if (! lex->spcont->find_cursor(&$3, &offset)) - { - my_error(ER_SP_CURSOR_MISMATCH, MYF(0), $3.str); - MYSQL_YYABORT; - } - i= new sp_instr_cfetch(sp->instructions(), lex->spcont, offset); + if (! lex->spcont->find_cursor($3, &offset, false)) + my_yyabort_error((ER_SP_CURSOR_MISMATCH, MYF(0), $3.str)); + i= new (thd->mem_root) + sp_instr_cfetch(sp->instructions(), lex->spcont, offset); if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; @@ -3427,12 +3840,10 @@ sp_proc_stmt_close: uint offset; sp_instr_cclose *i; - if (! lex->spcont->find_cursor(&$2, &offset)) - { - my_error(ER_SP_CURSOR_MISMATCH, MYF(0), $2.str); - MYSQL_YYABORT; - } - i= new sp_instr_cclose(sp->instructions(), lex->spcont, offset); + if (! lex->spcont->find_cursor($2, &offset, false)) + my_yyabort_error((ER_SP_CURSOR_MISMATCH, MYF(0), $2.str)); + i= new (thd->mem_root) + sp_instr_cclose(sp->instructions(), lex->spcont, offset); if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; @@ -3451,40 +3862,28 @@ sp_fetch_list: LEX *lex= Lex; sp_head *sp= lex->sphead; sp_pcontext *spc= lex->spcont; - sp_variable_t *spv; + sp_variable *spv; - if (!spc || !(spv = spc->find_variable(&$1))) - { - my_error(ER_SP_UNDECLARED_VAR, MYF(0), $1.str); - MYSQL_YYABORT; - } - else - { - /* An SP local variable */ - sp_instr_cfetch *i= (sp_instr_cfetch *)sp->last_instruction(); + if (!spc || !(spv = spc->find_variable($1, false))) + my_yyabort_error((ER_SP_UNDECLARED_VAR, MYF(0), $1.str)); - i->add_to_varlist(spv); - } + /* An SP local variable */ + sp_instr_cfetch *i= (sp_instr_cfetch *)sp->last_instruction(); + i->add_to_varlist(spv); } | sp_fetch_list ',' ident { LEX *lex= Lex; sp_head *sp= lex->sphead; sp_pcontext *spc= lex->spcont; - sp_variable_t *spv; + sp_variable *spv; - if (!spc || !(spv = spc->find_variable(&$3))) - { - my_error(ER_SP_UNDECLARED_VAR, MYF(0), $3.str); - MYSQL_YYABORT; - } - else - { - /* An SP local variable */ - sp_instr_cfetch *i= (sp_instr_cfetch *)sp->last_instruction(); + if (!spc || !(spv = spc->find_variable($3, false))) + my_yyabort_error((ER_SP_UNDECLARED_VAR, MYF(0), $3.str)); - i->add_to_varlist(spv); - } + /* An SP local variable */ + sp_instr_cfetch *i= (sp_instr_cfetch *)sp->last_instruction(); + i->add_to_varlist(spv); } ; @@ -3496,10 +3895,10 @@ sp_if: sp_head *sp= lex->sphead; sp_pcontext *ctx= lex->spcont; uint ip= sp->instructions(); - sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, ctx, - $2, lex); + sp_instr_jump_if_not *i= new (thd->mem_root) + sp_instr_jump_if_not(ip, ctx, $2, lex); if (i == NULL || - sp->push_backpatch(i, ctx->push_label((char *)"", 0)) || + sp->push_backpatch(i, ctx->push_label(thd, empty_lex_str, 0)) || sp->add_cont_backpatch(i) || sp->add_instr(i)) MYSQL_YYABORT; @@ -3511,12 +3910,12 @@ sp_if: sp_head *sp= Lex->sphead; sp_pcontext *ctx= Lex->spcont; uint ip= sp->instructions(); - sp_instr_jump *i = new sp_instr_jump(ip, ctx); + sp_instr_jump *i= new (thd->mem_root) sp_instr_jump(ip, ctx); if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; sp->backpatch(ctx->pop_label()); - sp->push_backpatch(i, ctx->push_label((char *)"", 0)); + sp->push_backpatch(i, ctx->push_label(thd, empty_lex_str, 0)); } sp_elseifs { @@ -3533,51 +3932,89 @@ sp_elseifs: ; case_stmt_specification: - simple_case_stmt - | searched_case_stmt - ; - -simple_case_stmt: CASE_SYM { - LEX *lex= Lex; - case_stmt_action_case(lex); - lex->sphead->reset_lex(thd); /* For expr $3 */ - } - expr - { - LEX *lex= Lex; - if (case_stmt_action_expr(lex, $3)) - MYSQL_YYABORT; + if (maybe_start_compound_statement(thd)) + MYSQL_YYABORT; + + /** + An example of the CASE statement in use is + <pre> + CREATE PROCEDURE proc_19194_simple(i int) + BEGIN + DECLARE str CHAR(10); + + CASE i + WHEN 1 THEN SET str="1"; + WHEN 2 THEN SET str="2"; + WHEN 3 THEN SET str="3"; + ELSE SET str="unknown"; + END CASE; + + SELECT str; + END + </pre> + The actions are used to generate the following code: + <pre> + SHOW PROCEDURE CODE proc_19194_simple; + Pos Instruction + 0 set str@1 NULL + 1 set_case_expr (12) 0 i@0 + 2 jump_if_not 5(12) (case_expr@0 = 1) + 3 set str@1 _latin1'1' + 4 jump 12 + 5 jump_if_not 8(12) (case_expr@0 = 2) + 6 set str@1 _latin1'2' + 7 jump 12 + 8 jump_if_not 11(12) (case_expr@0 = 3) + 9 set str@1 _latin1'3' + 10 jump 12 + 11 set str@1 _latin1'unknown' + 12 stmt 0 "SELECT str" + </pre> + */ - /* For expr $3 */ - if (lex->sphead->restore_lex(thd)) - MYSQL_YYABORT; + Lex->sphead->new_cont_backpatch(NULL); + + /* + BACKPATCH: Creating target label for the jump to after END CASE + (instruction 12 in the example) + */ + Lex->spcont->push_label(thd, empty_lex_str, Lex->sphead->instructions()); } - simple_when_clause_list + case_stmt_body else_clause_opt END CASE_SYM { - LEX *lex= Lex; - case_stmt_action_end_case(lex, true); + /* + BACKPATCH: Resolving forward jump from + "case_stmt_action_then" to after END CASE + (jump from instruction 4 to 12, 7 to 12 ... in the example) + */ + Lex->sphead->backpatch(Lex->spcont->pop_label()); + + if ($3) + Lex->spcont->pop_case_expr_id(); + + Lex->sphead->do_cont_backpatch(); } ; -searched_case_stmt: - CASE_SYM - { - LEX *lex= Lex; - case_stmt_action_case(lex); - } - searched_when_clause_list - else_clause_opt - END - CASE_SYM +case_stmt_body: + { Lex->sphead->reset_lex(thd); /* For expr $2 */ } + expr { - LEX *lex= Lex; - case_stmt_action_end_case(lex, false); + if (case_stmt_action_expr(Lex, $2)) + MYSQL_YYABORT; + + if (Lex->sphead->restore_lex(thd)) + MYSQL_YYABORT; } + simple_when_clause_list + { $$= 1; } + | searched_when_clause_list + { $$= 0; } ; simple_when_clause_list: @@ -3644,8 +4081,8 @@ else_clause_opt: LEX *lex= Lex; sp_head *sp= lex->sphead; uint ip= sp->instructions(); - sp_instr_error *i= new sp_instr_error(ip, lex->spcont, - ER_SP_CASE_NOT_FOUND); + sp_instr_error *i= new (thd->mem_root) + sp_instr_error(ip, lex->spcont, ER_SP_CASE_NOT_FOUND); if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; @@ -3653,101 +4090,60 @@ else_clause_opt: | ELSE sp_proc_stmts1 ; -sp_labeled_control: - label_ident ':' - { - LEX *lex= Lex; - sp_pcontext *ctx= lex->spcont; - sp_label_t *lab= ctx->find_label($1.str); - - if (lab) - { - my_error(ER_SP_LABEL_REDEFINE, MYF(0), $1.str); - MYSQL_YYABORT; - } - else - { - lab= lex->spcont->push_label($1.str, - lex->sphead->instructions()); - lab->type= SP_LAB_ITER; - } - } - sp_unlabeled_control sp_opt_label - { - LEX *lex= Lex; - sp_label_t *lab= lex->spcont->pop_label(); - - if ($5.str) - { - if (my_strcasecmp(system_charset_info, $5.str, lab->name) != 0) - { - my_error(ER_SP_LABEL_MISMATCH, MYF(0), $5.str); - MYSQL_YYABORT; - } - } - lex->sphead->backpatch(lab); - } - ; - sp_opt_label: /* Empty */ { $$= null_lex_str; } | label_ident { $$= $1; } ; sp_labeled_block: - label_ident ':' + label_ident ':' BEGIN_SYM { LEX *lex= Lex; sp_pcontext *ctx= lex->spcont; - sp_label_t *lab= ctx->find_label($1.str); + sp_label *lab= ctx->find_label($1); if (lab) - { - my_error(ER_SP_LABEL_REDEFINE, MYF(0), $1.str); - MYSQL_YYABORT; - } - - lab= lex->spcont->push_label($1.str, - lex->sphead->instructions()); - lab->type= SP_LAB_BEGIN; + my_yyabort_error((ER_SP_LABEL_REDEFINE, MYF(0), $1.str)); + lex->name= $1; } sp_block_content sp_opt_label { - LEX *lex= Lex; - sp_label_t *lab= lex->spcont->pop_label(); - - if ($5.str) + if ($6.str) { - if (my_strcasecmp(system_charset_info, $5.str, lab->name) != 0) - { - my_error(ER_SP_LABEL_MISMATCH, MYF(0), $5.str); - MYSQL_YYABORT; - } + if (my_strcasecmp(system_charset_info, $6.str, $5->name.str) != 0) + my_yyabort_error((ER_SP_LABEL_MISMATCH, MYF(0), $6.str)); } } ; sp_unlabeled_block: - { /* Unlabeled blocks get a secret label. */ - LEX *lex= Lex; - uint ip= lex->sphead->instructions(); - sp_label_t *lab= lex->spcont->push_label((char *)"", ip); - lab->type= SP_LAB_BEGIN; + BEGIN_SYM + { + Lex->name= empty_lex_str; // Unlabeled blocks get an empty label } sp_block_content + { } + ; + +sp_unlabeled_block_not_atomic: + BEGIN_SYM not ATOMIC_SYM /* TODO: BEGIN ATOMIC (not -> opt_not) */ { - LEX *lex= Lex; - lex->spcont->pop_label(); + if (maybe_start_compound_statement(thd)) + MYSQL_YYABORT; + Lex->name= empty_lex_str; // Unlabeled blocks get an empty label } + sp_block_content + { } ; sp_block_content: - BEGIN_SYM - { /* QQ This is just a dummy for grouping declarations and statements - together. No [[NOT] ATOMIC] yet, and we need to figure out how - make it coexist with the existing BEGIN COMMIT/ROLLBACK. */ + { LEX *lex= Lex; - lex->spcont= lex->spcont->push_context(LABEL_DEFAULT_SCOPE); + sp_label *lab= lex->spcont->push_label(thd, lex->name, + lex->sphead->instructions()); + lab->type= sp_label::BEGIN; + lex->spcont= lex->spcont->push_context(thd, + sp_pcontext::REGULAR_SCOPE); } sp_decls sp_proc_stmts @@ -3759,45 +4155,49 @@ sp_block_content: sp_instr *i; sp->backpatch(ctx->last_label()); /* We always have a label */ - if ($3.hndlrs) + if ($2.hndlrs) { - i= new sp_instr_hpop(sp->instructions(), ctx, $3.hndlrs); + i= new (thd->mem_root) + sp_instr_hpop(sp->instructions(), ctx, $2.hndlrs); if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; } - if ($3.curs) + if ($2.curs) { - i= new sp_instr_cpop(sp->instructions(), ctx, $3.curs); + i= new (thd->mem_root) + sp_instr_cpop(sp->instructions(), ctx, $2.curs); if (i == NULL || sp->add_instr(i)) MYSQL_YYABORT; } lex->spcont= ctx->pop_context(); + $$ = lex->spcont->pop_label(); } ; -sp_unlabeled_control: - LOOP_SYM +loop_body: sp_proc_stmts1 END LOOP_SYM { LEX *lex= Lex; uint ip= lex->sphead->instructions(); - sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */ - sp_instr_jump *i = new sp_instr_jump(ip, lex->spcont, lab->ip); + sp_label *lab= lex->spcont->last_label(); /* Jumping back */ + sp_instr_jump *i= new (thd->mem_root) + sp_instr_jump(ip, lex->spcont, lab->ip); if (i == NULL || lex->sphead->add_instr(i)) MYSQL_YYABORT; } - | WHILE_SYM - { Lex->sphead->reset_lex(thd); } + ; + +while_body: expr DO_SYM { LEX *lex= Lex; sp_head *sp= lex->sphead; uint ip= sp->instructions(); - sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont, - $3, lex); + sp_instr_jump_if_not *i= new (thd->mem_root) + sp_instr_jump_if_not(ip, lex->spcont, $1, lex); if (i == NULL || /* Jumping forward */ sp->push_backpatch(i, lex->spcont->last_label()) || @@ -3811,23 +4211,26 @@ sp_unlabeled_control: { LEX *lex= Lex; uint ip= lex->sphead->instructions(); - sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */ - sp_instr_jump *i = new sp_instr_jump(ip, lex->spcont, lab->ip); + sp_label *lab= lex->spcont->last_label(); /* Jumping back */ + sp_instr_jump *i= new (thd->mem_root) + sp_instr_jump(ip, lex->spcont, lab->ip); if (i == NULL || lex->sphead->add_instr(i)) MYSQL_YYABORT; lex->sphead->do_cont_backpatch(); } - | REPEAT_SYM sp_proc_stmts1 UNTIL_SYM + ; + +repeat_body: + sp_proc_stmts1 UNTIL_SYM { Lex->sphead->reset_lex(thd); } expr END REPEAT_SYM { LEX *lex= Lex; uint ip= lex->sphead->instructions(); - sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */ - sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont, - $5, lab->ip, - lex); + sp_label *lab= lex->spcont->last_label(); /* Jumping back */ + sp_instr_jump_if_not *i= new (thd->mem_root) + sp_instr_jump_if_not(ip, lex->spcont, $4, lab->ip, lex); if (i == NULL || lex->sphead->add_instr(i)) MYSQL_YYABORT; @@ -3838,6 +4241,81 @@ sp_unlabeled_control: } ; +pop_sp_label: + sp_opt_label + { + sp_label *lab; + Lex->sphead->backpatch(lab= Lex->spcont->pop_label()); + if ($1.str) + { + if (my_strcasecmp(system_charset_info, $1.str, + lab->name.str) != 0) + my_yyabort_error((ER_SP_LABEL_MISMATCH, MYF(0), $1.str)); + } + } + ; + +pop_sp_empty_label: + { + sp_label *lab; + Lex->sphead->backpatch(lab= Lex->spcont->pop_label()); + DBUG_ASSERT(lab->name.length == 0); + } + ; + +sp_labeled_control: + label_ident ':' LOOP_SYM + { + if (push_sp_label(thd, $1)) + MYSQL_YYABORT; + } + loop_body pop_sp_label + { } + | label_ident ':' WHILE_SYM + { + if (push_sp_label(thd, $1)) + MYSQL_YYABORT; + Lex->sphead->reset_lex(thd); + } + while_body pop_sp_label + { } + | label_ident ':' REPEAT_SYM + { + if (push_sp_label(thd, $1)) + MYSQL_YYABORT; + } + repeat_body pop_sp_label + { } + ; + +sp_unlabeled_control: + LOOP_SYM + { + if (push_sp_empty_label(thd)) + MYSQL_YYABORT; + } + loop_body + pop_sp_empty_label + { } + | WHILE_SYM + { + if (push_sp_empty_label(thd)) + MYSQL_YYABORT; + Lex->sphead->reset_lex(thd); + } + while_body + pop_sp_empty_label + { } + | REPEAT_SYM + { + if (push_sp_empty_label(thd)) + MYSQL_YYABORT; + } + repeat_body + pop_sp_empty_label + { } + ; + trg_action_time: BEFORE_SYM { Lex->trg_chistics.action_time= TRG_ACTION_BEFORE; } @@ -4046,7 +4524,8 @@ tablespace_name: ident { LEX *lex= Lex; - lex->alter_tablespace_info= new st_alter_tablespace(); + lex->alter_tablespace_info= (new (thd->mem_root) + st_alter_tablespace()); if (lex->alter_tablespace_info == NULL) MYSQL_YYABORT; lex->alter_tablespace_info->tablespace_name= $1.str; @@ -4058,7 +4537,8 @@ logfile_group_name: ident { LEX *lex= Lex; - lex->alter_tablespace_info= new st_alter_tablespace(); + lex->alter_tablespace_info= (new (thd->mem_root) + st_alter_tablespace()); if (lex->alter_tablespace_info == NULL) MYSQL_YYABORT; lex->alter_tablespace_info->logfile_group_name= $1.str; @@ -4137,10 +4617,7 @@ opt_ts_nodegroup: { LEX *lex= Lex; if (lex->alter_tablespace_info->nodegroup_id != UNDEF_NODEGROUP) - { - my_error(ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),"NODEGROUP"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),"NODEGROUP")); lex->alter_tablespace_info->nodegroup_id= $3; } ; @@ -4150,10 +4627,7 @@ opt_ts_comment: { LEX *lex= Lex; if (lex->alter_tablespace_info->ts_comment != NULL) - { - my_error(ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),"COMMENT"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),"COMMENT")); lex->alter_tablespace_info->ts_comment= $3.str; } ; @@ -4163,11 +4637,8 @@ opt_ts_engine: { LEX *lex= Lex; if (lex->alter_tablespace_info->storage_engine != NULL) - { - my_error(ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0), - "STORAGE ENGINE"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_FILEGROUP_OPTION_ONLY_ONCE, MYF(0), + "STORAGE ENGINE")); lex->alter_tablespace_info->storage_engine= $4; } ; @@ -4187,10 +4658,7 @@ ts_wait: { LEX *lex= Lex; if (!(lex->alter_tablespace_info->wait_until_completed)) - { - my_error(ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),"NO_WAIT"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_FILEGROUP_OPTION_ONLY_ONCE,MYF(0),"NO_WAIT")); lex->alter_tablespace_info->wait_until_completed= FALSE; } ; @@ -4218,23 +4686,14 @@ size_number: case 'k': case 'K': text_shift_number+=10; break; default: - { - my_error(ER_WRONG_SIZE_NUMBER, MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_SIZE_NUMBER, MYF(0))); } if (prefix_number >> 31) - { - my_error(ER_SIZE_OVERFLOW_ERROR, MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SIZE_OVERFLOW_ERROR, MYF(0))); number= prefix_number << text_shift_number; } else - { - my_error(ER_WRONG_SIZE_NUMBER, MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_SIZE_NUMBER, MYF(0))); $$= number; } ; @@ -4243,34 +4702,23 @@ size_number: End tablespace part */ -create2: - '(' create2a {} - | opt_create_table_options - opt_create_partitioning - create3 {} - | LIKE table_ident - { - TABLE_LIST *src_table; - LEX *lex= thd->lex; - - lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE; - src_table= lex->select_lex.add_table_to_list(thd, $2, NULL, 0, - TL_READ, - MDL_SHARED_READ); - if (! src_table) - MYSQL_YYABORT; - /* CREATE TABLE ... LIKE is not allowed for views. */ - src_table->required_type= FRMTYPE_TABLE; - } - | '(' LIKE table_ident ')' +create_body: + '(' create_field_list ')' + { Lex->create_info.option_list= NULL; } + opt_create_table_options opt_create_partitioning opt_create_select {} + | opt_create_table_options opt_create_partitioning opt_create_select {} + /* + the following rule is redundant, but there's a shift/reduce + conflict that prevents the rule above from parsing a syntax like + CREATE TABLE t1 (SELECT 1); + */ + | '(' create_select ')' { Select->set_braces(1);} union_opt {} + | create_like { - TABLE_LIST *src_table; - LEX *lex= thd->lex; - lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE; - src_table= lex->select_lex.add_table_to_list(thd, $3, NULL, 0, - TL_READ, - MDL_SHARED_READ); + Lex->create_info.add(DDL_options_st::OPT_LIKE); + TABLE_LIST *src_table= Lex->select_lex.add_table_to_list(thd, + $1, NULL, 0, TL_READ, MDL_SHARED_READ); if (! src_table) MYSQL_YYABORT; /* CREATE TABLE ... LIKE is not allowed for views. */ @@ -4278,21 +4726,12 @@ create2: } ; -create2a: - create_field_list ')' - { - Lex->create_info.option_list= NULL; - } - opt_create_table_options - opt_create_partitioning - create3 {} - | opt_create_partitioning - create_select ')' - { Select->set_braces(1);} - union_opt {} +create_like: + LIKE table_ident { $$= $2; } + | '(' LIKE table_ident ')' { $$= $3; } ; -create3: +opt_create_select: /* empty */ {} | opt_duplicate opt_as create_select { Select->set_braces(0);} @@ -4348,7 +4787,7 @@ partitioning: PARTITION_SYM have_partitioning { LEX *lex= Lex; - lex->part_info= new partition_info(); + lex->part_info= new (thd->mem_root) partition_info(); if (!lex->part_info) { mem_alloc_error(sizeof(partition_info)); @@ -4356,7 +4795,7 @@ partitioning: } if (lex->sql_command == SQLCOM_ALTER_TABLE) { - lex->alter_info.flags|= ALTER_PARTITION; + lex->alter_info.flags|= Alter_info::ALTER_PARTITION; } } partition @@ -4368,15 +4807,11 @@ have_partitioning: #ifdef WITH_PARTITION_STORAGE_ENGINE LEX_STRING partition_name={C_STRING_WITH_LEN("partition")}; if (!plugin_is_ready(&partition_name, MYSQL_STORAGE_ENGINE_PLUGIN)) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), - "--skip-partition"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_OPTION_PREVENTS_STATEMENT, MYF(0), + "--skip-partition")); #else - my_error(ER_FEATURE_DISABLED, MYF(0), "partitioning", - "--with-plugin-partition"); - MYSQL_YYABORT; + my_yyabort_error((ER_FEATURE_DISABLED, MYF(0), "partitioning", + "--with-plugin-partition")); #endif } ; @@ -4387,7 +4822,7 @@ partition_entry: LEX *lex= Lex; if (!lex->part_info) { - my_parse_error(ER(ER_PARTITION_ENTRY_ERROR)); + my_parse_error(thd, ER_PARTITION_ENTRY_ERROR); MYSQL_YYABORT; } /* @@ -4442,7 +4877,7 @@ opt_key_algo: Lex->part_info->key_algorithm= partition_info::KEY_ALGORITHM_55; break; default: - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } } @@ -4463,17 +4898,14 @@ part_field_item: { partition_info *part_info= Lex->part_info; part_info->num_columns++; - if (part_info->part_field_list.push_back($1.str)) + if (part_info->part_field_list.push_back($1.str, thd->mem_root)) { mem_alloc_error(1); MYSQL_YYABORT; } if (part_info->num_columns > MAX_REF_PARTS) - { - my_error(ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0), - "list of partition fields"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0), + "list of partition fields")); } ; @@ -4514,10 +4946,7 @@ opt_num_parts: uint num_parts= $2; partition_info *part_info= Lex->part_info; if (num_parts == 0) - { - my_error(ER_NO_PARTS_ERROR, MYF(0), "partitions"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_NO_PARTS_ERROR, MYF(0), "partitions")); part_info->num_parts= num_parts; part_info->use_default_num_partitions= FALSE; @@ -4548,17 +4977,14 @@ sub_part_field_item: ident { partition_info *part_info= Lex->part_info; - if (part_info->subpart_field_list.push_back($1.str)) + if (part_info->subpart_field_list.push_back($1.str, thd->mem_root)) { mem_alloc_error(1); MYSQL_YYABORT; } if (part_info->subpart_field_list.elements > MAX_REF_PARTS) - { - my_error(ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0), - "list of subpartition fields"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR, MYF(0), + "list of subpartition fields")); } ; @@ -4571,7 +4997,7 @@ part_func_expr: lex->safe_to_cache_query= 1; if (not_corr_func) { - my_parse_error(ER(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR)); + my_parse_error(thd, ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR); MYSQL_YYABORT; } $$=$1; @@ -4585,10 +5011,7 @@ opt_num_subparts: uint num_parts= $2; LEX *lex= Lex; if (num_parts == 0) - { - my_error(ER_NO_PARTS_ERROR, MYF(0), "subpartitions"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_NO_PARTS_ERROR, MYF(0), "subpartitions")); lex->part_info->num_subparts= num_parts; lex->part_info->use_default_num_subpartitions= FALSE; } @@ -4599,17 +5022,11 @@ part_defs: { partition_info *part_info= Lex->part_info; if (part_info->part_type == RANGE_PARTITION) - { - my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), - "RANGE"); - MYSQL_YYABORT; - } - else if (part_info->part_type == LIST_PARTITION) - { - my_error(ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), - "LIST"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), + "RANGE")); + if (part_info->part_type == LIST_PARTITION) + my_yyabort_error((ER_PARTITIONS_MUST_BE_DEFINED_ERROR, MYF(0), + "LIST")); } | '(' part_def_list ')' { @@ -4620,7 +5037,7 @@ part_defs: if (part_info->num_parts != count_curr_parts) { - my_parse_error(ER(ER_PARTITION_WRONG_NO_PART_ERROR)); + my_parse_error(thd, ER_PARTITION_WRONG_NO_PART_ERROR); MYSQL_YYABORT; } } @@ -4641,9 +5058,10 @@ part_definition: PARTITION_SYM { partition_info *part_info= Lex->part_info; - partition_element *p_elem= new partition_element(); + partition_element *p_elem= new (thd->mem_root) partition_element(); - if (!p_elem || part_info->partitions.push_back(p_elem)) + if (!p_elem || + part_info->partitions.push_back(p_elem, thd->mem_root)) { mem_alloc_error(sizeof(partition_element)); MYSQL_YYABORT; @@ -4692,11 +5110,8 @@ opt_part_values: if (! lex->is_partition_management()) { if (part_info->part_type != RANGE_PARTITION) - { - my_error(ER_PARTITION_WRONG_VALUES_ERROR, MYF(0), - "RANGE", "LESS THAN"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_PARTITION_WRONG_VALUES_ERROR, MYF(0), + "RANGE", "LESS THAN")); } else part_info->part_type= RANGE_PARTITION; @@ -4709,11 +5124,8 @@ opt_part_values: if (! lex->is_partition_management()) { if (part_info->part_type != LIST_PARTITION) - { - my_error(ER_PARTITION_WRONG_VALUES_ERROR, MYF(0), - "LIST", "IN"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_PARTITION_WRONG_VALUES_ERROR, MYF(0), + "LIST", "IN")); } else part_info->part_type= LIST_PARTITION; @@ -4730,16 +5142,16 @@ part_func_max: part_info->num_columns != 1U) { part_info->print_debug("Kilroy II", NULL); - my_parse_error(ER(ER_PARTITION_COLUMN_LIST_ERROR)); + my_parse_error(thd, ER_PARTITION_COLUMN_LIST_ERROR); MYSQL_YYABORT; } else part_info->num_columns= 1U; - if (part_info->init_column_part()) + if (part_info->init_column_part(thd)) { MYSQL_YYABORT; } - if (part_info->add_max_value()) + if (part_info->add_max_value(thd)) { MYSQL_YYABORT; } @@ -4761,7 +5173,7 @@ part_values_in: part_info->num_columns > MAX_REF_PARTS) { part_info->print_debug("Kilroy III", NULL); - my_parse_error(ER(ER_PARTITION_COLUMN_LIST_ERROR)); + my_parse_error(thd, ER_PARTITION_COLUMN_LIST_ERROR); MYSQL_YYABORT; } /* @@ -4771,7 +5183,7 @@ part_values_in: we ADD or REORGANIZE partitions. Also can only happen for LIST partitions. */ - if (part_info->reorganize_into_single_field_col_val()) + if (part_info->reorganize_into_single_field_col_val(thd)) { MYSQL_YYABORT; } @@ -4782,7 +5194,7 @@ part_values_in: partition_info *part_info= Lex->part_info; if (part_info->num_columns < 2U) { - my_parse_error(ER(ER_ROW_SINGLE_PARTITION_FIELD_ERROR)); + my_parse_error(thd, ER_ROW_SINGLE_PARTITION_FIELD_ERROR); MYSQL_YYABORT; } } @@ -4801,7 +5213,7 @@ part_value_item: /* Initialisation code needed for each list of value expressions */ if (!(part_info->part_type == LIST_PARTITION && part_info->num_columns == 1U) && - part_info->init_column_part()) + part_info->init_column_part(thd)) { MYSQL_YYABORT; } @@ -4823,7 +5235,7 @@ part_value_item: error. */ part_info->print_debug("Kilroy I", NULL); - my_parse_error(ER(ER_PARTITION_COLUMN_LIST_ERROR)); + my_parse_error(thd, ER_PARTITION_COLUMN_LIST_ERROR); MYSQL_YYABORT; } part_info->curr_list_object= 0; @@ -4841,10 +5253,10 @@ part_value_expr_item: partition_info *part_info= Lex->part_info; if (part_info->part_type == LIST_PARTITION) { - my_parse_error(ER(ER_MAXVALUE_IN_VALUES_IN)); + my_parse_error(thd, ER_MAXVALUE_IN_VALUES_IN); MYSQL_YYABORT; } - if (part_info->add_max_value()) + if (part_info->add_max_value(thd)) { MYSQL_YYABORT; } @@ -4857,7 +5269,7 @@ part_value_expr_item: if (!lex->safe_to_cache_query) { - my_parse_error(ER(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR)); + my_parse_error(thd, ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR); MYSQL_YYABORT; } if (part_info->add_column_list_value(thd, part_expr)) @@ -4879,7 +5291,7 @@ opt_sub_partition: We come here when we have defined subpartitions on the first partition but not on all the subsequent partitions. */ - my_parse_error(ER(ER_PARTITION_WRONG_NO_SUBPART_ERROR)); + my_parse_error(thd, ER_PARTITION_WRONG_NO_SUBPART_ERROR); MYSQL_YYABORT; } } @@ -4891,7 +5303,7 @@ opt_sub_partition: if (part_info->num_subparts != part_info->count_curr_subparts) { - my_parse_error(ER(ER_PARTITION_WRONG_NO_SUBPART_ERROR)); + my_parse_error(thd, ER_PARTITION_WRONG_NO_SUBPART_ERROR); MYSQL_YYABORT; } } @@ -4899,7 +5311,7 @@ opt_sub_partition: { if (part_info->partitions.elements > 1) { - my_parse_error(ER(ER_PARTITION_WRONG_NO_SUBPART_ERROR)); + my_parse_error(thd, ER_PARTITION_WRONG_NO_SUBPART_ERROR); MYSQL_YYABORT; } part_info->num_subparts= part_info->count_curr_subparts; @@ -4918,7 +5330,8 @@ sub_part_definition: { partition_info *part_info= Lex->part_info; partition_element *curr_part= part_info->current_partition; - partition_element *sub_p_elem= new partition_element(curr_part); + partition_element *sub_p_elem= new (thd->mem_root) + partition_element(curr_part); if (part_info->use_default_subpartitions && part_info->partitions.elements >= 2) { @@ -4933,11 +5346,11 @@ sub_part_definition: the second partition (the current partition processed have already been put into the partitions list. */ - my_parse_error(ER(ER_PARTITION_WRONG_NO_SUBPART_ERROR)); + my_parse_error(thd, ER_PARTITION_WRONG_NO_SUBPART_ERROR); MYSQL_YYABORT; } if (!sub_p_elem || - curr_part->subpartitions.push_back(sub_p_elem)) + curr_part->subpartitions.push_back(sub_p_elem, thd->mem_root)) { mem_alloc_error(sizeof(partition_element)); MYSQL_YYABORT; @@ -5022,7 +5435,7 @@ create_select: { Select->parsing_place= NO_MATTER; } - opt_select_from + table_expression { /* The following work only with the local list, the global list @@ -5052,24 +5465,38 @@ create_database_option: | default_charset {} ; -opt_table_options: - /* empty */ { $$= 0; } - | table_options { $$= $1;} - ; - -table_options: - table_option { $$=$1; } - | table_option table_options { $$= $1 | $2; } - ; - -table_option: - TEMPORARY { $$=HA_LEX_CREATE_TMP_TABLE; } - ; +opt_if_not_exists_table_element: + /* empty */ + { + Lex->check_exists= FALSE; + } + | IF_SYM not EXISTS + { + Lex->check_exists= TRUE; + } + ; opt_if_not_exists: - /* empty */ { $$= 0; } - | IF not EXISTS { $$=HA_LEX_CREATE_IF_NOT_EXISTS; } - ; + /* empty */ + { + $$.init(); + } + | IF_SYM not EXISTS + { + $$.set(DDL_options_st::OPT_IF_NOT_EXISTS); + } + ; + +create_or_replace: + CREATE /* empty */ + { + $$.init(); + } + | CREATE OR_SYM REPLACE + { + $$.set(DDL_options_st::OPT_OR_REPLACE); + } + ; opt_create_table_options: /* empty */ @@ -5108,7 +5535,7 @@ create_table_option: Lex->create_info.avg_row_length=$3; Lex->create_info.used_fields|= HA_CREATE_USED_AVG_ROW_LENGTH; } - | PASSWORD opt_equal TEXT_STRING_sys + | PASSWORD_SYM opt_equal TEXT_STRING_sys { Lex->create_info.password=$3.str; Lex->create_info.used_fields|= HA_CREATE_USED_PASSWORD; @@ -5133,7 +5560,7 @@ create_table_option: Lex->create_info.table_options|= HA_OPTION_PACK_KEYS; break; default: - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } Lex->create_info.used_fields|= HA_CREATE_USED_PACK_KEYS; @@ -5144,6 +5571,70 @@ create_table_option: ~(HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS); Lex->create_info.used_fields|= HA_CREATE_USED_PACK_KEYS; } + | STATS_AUTO_RECALC_SYM opt_equal ulong_num + { + switch($3) { + case 0: + Lex->create_info.stats_auto_recalc= HA_STATS_AUTO_RECALC_OFF; + break; + case 1: + Lex->create_info.stats_auto_recalc= HA_STATS_AUTO_RECALC_ON; + break; + default: + my_parse_error(thd, ER_SYNTAX_ERROR); + MYSQL_YYABORT; + } + Lex->create_info.used_fields|= HA_CREATE_USED_STATS_AUTO_RECALC; + } + | STATS_AUTO_RECALC_SYM opt_equal DEFAULT + { + Lex->create_info.stats_auto_recalc= HA_STATS_AUTO_RECALC_DEFAULT; + Lex->create_info.used_fields|= HA_CREATE_USED_STATS_AUTO_RECALC; + } + | STATS_PERSISTENT_SYM opt_equal ulong_num + { + switch($3) { + case 0: + Lex->create_info.table_options|= HA_OPTION_NO_STATS_PERSISTENT; + break; + case 1: + Lex->create_info.table_options|= HA_OPTION_STATS_PERSISTENT; + break; + default: + my_parse_error(thd, ER_SYNTAX_ERROR); + MYSQL_YYABORT; + } + Lex->create_info.used_fields|= HA_CREATE_USED_STATS_PERSISTENT; + } + | STATS_PERSISTENT_SYM opt_equal DEFAULT + { + Lex->create_info.table_options&= + ~(HA_OPTION_STATS_PERSISTENT | HA_OPTION_NO_STATS_PERSISTENT); + Lex->create_info.used_fields|= HA_CREATE_USED_STATS_PERSISTENT; + } + | STATS_SAMPLE_PAGES_SYM opt_equal ulong_num + { + /* From user point of view STATS_SAMPLE_PAGES can be specified as + STATS_SAMPLE_PAGES=N (where 0<N<=65535, it does not make sense to + scan 0 pages) or STATS_SAMPLE_PAGES=default. Internally we record + =default as 0. See create_frm() in sql/table.cc, we use only two + bytes for stats_sample_pages and this is why we do not allow + larger values. 65535 pages, 16kb each means to sample 1GB, which + is impractical. If at some point this needs to be extended, then + we can store the higher bits from stats_sample_pages in .frm too. */ + if ($3 == 0 || $3 > 0xffff) + { + my_parse_error(thd, ER_SYNTAX_ERROR); + MYSQL_YYABORT; + } + Lex->create_info.stats_sample_pages=$3; + Lex->create_info.used_fields|= HA_CREATE_USED_STATS_SAMPLE_PAGES; + } + | STATS_SAMPLE_PAGES_SYM opt_equal DEFAULT + { + Lex->create_info.stats_sample_pages=0; + Lex->create_info.used_fields|= HA_CREATE_USED_STATS_SAMPLE_PAGES; + } | CHECKSUM_SYM opt_equal ulong_num { Lex->create_info.table_options|= $3 ? HA_OPTION_CHECKSUM : HA_OPTION_NO_CHECKSUM; @@ -5180,7 +5671,7 @@ create_table_option: from the global list. */ LEX *lex=Lex; - lex->create_info.merge_list= lex->select_lex.table_list; + lex->create_info.merge_list= lex->select_lex.table_list.first; lex->select_lex.table_list= lex->save_list; /* When excluding union list from the global list we assume that @@ -5189,7 +5680,7 @@ create_table_option: */ TABLE_LIST *last_non_sel_table= lex->create_last_non_select_table; DBUG_ASSERT(last_non_sel_table->next_global == - lex->create_info.merge_list.first); + lex->create_info.merge_list); last_non_sel_table->next_global= 0; Lex->query_tables_last= &last_non_sel_table->next_global; @@ -5236,12 +5727,16 @@ create_table_option: } | IDENT_sys equal TEXT_STRING_sys { + if ($3.length > ENGINE_OPTION_MAX_LENGTH) + my_yyabort_error((ER_VALUE_TOO_LONG, MYF(0), $1.str)); new (thd->mem_root) engine_option_value($1, $3, true, &Lex->create_info.option_list, &Lex->option_list_last); } | IDENT_sys equal ident { + if ($3.length > ENGINE_OPTION_MAX_LENGTH) + my_yyabort_error((ER_VALUE_TOO_LONG, MYF(0), $1.str)); new (thd->mem_root) engine_option_value($1, $3, false, &Lex->create_info.option_list, &Lex->option_list_last); @@ -5263,18 +5758,8 @@ create_table_option: default_charset: opt_default charset opt_equal charset_name_or_default { - HA_CREATE_INFO *cinfo= &Lex->create_info; - if ((cinfo->used_fields & HA_CREATE_USED_DEFAULT_CHARSET) && - cinfo->default_table_charset && $4 && - !my_charset_same(cinfo->default_table_charset,$4)) - { - my_error(ER_CONFLICTING_DECLARATIONS, MYF(0), - "CHARACTER SET ", cinfo->default_table_charset->csname, - "CHARACTER SET ", $4->csname); + if (Lex->create_info.add_table_option_default_charset($4)) MYSQL_YYABORT; - } - Lex->create_info.default_table_charset= $4; - Lex->create_info.used_fields|= HA_CREATE_USED_DEFAULT_CHARSET; } ; @@ -5298,21 +5783,19 @@ default_collation: storage_engines: ident_or_text { - plugin_ref plugin= ha_resolve_by_name(thd, &$1); + plugin_ref plugin= ha_resolve_by_name(thd, &$1, + thd->lex->create_info.tmp_table()); if (plugin) - $$= plugin_data(plugin, handlerton*); + $$= plugin_hton(plugin); else { if (thd->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION) - { - my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str)); $$= 0; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_STORAGE_ENGINE, - ER(ER_UNKNOWN_STORAGE_ENGINE), + ER_THD(thd, ER_UNKNOWN_STORAGE_ENGINE), $1.str); } } @@ -5322,13 +5805,10 @@ known_storage_engines: ident_or_text { plugin_ref plugin; - if ((plugin= ha_resolve_by_name(thd, &$1))) - $$= plugin_data(plugin, handlerton*); + if ((plugin= ha_resolve_by_name(thd, &$1, false))) + $$= plugin_hton(plugin); else - { - my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str)); } ; @@ -5348,11 +5828,6 @@ merge_insert_types: | LAST_SYM { $$= MERGE_INSERT_TO_LAST; } ; -opt_select_from: - opt_limit_clause {} - | select_from select_lock_type - ; - udf_type: STRING_SYM {$$ = (int) STRING_RESULT; } | REAL {$$ = (int) REAL_RESULT; } @@ -5381,66 +5856,90 @@ field_list_item: column_def: field_spec opt_check_constraint | field_spec references - { - Lex->col_list.empty(); /* Alloced by sql_alloc */ - } ; key_def: - normal_key_type opt_ident key_alg '(' key_list ')' - { Lex->option_list= NULL; } - normal_key_options + key_or_index opt_if_not_exists opt_ident opt_USING_key_algorithm + { + Lex->option_list= NULL; + if (Lex->add_key(Key::MULTIPLE, $3, $4, $2)) + MYSQL_YYABORT; + } + '(' key_list ')' normal_key_options { } + | key_or_index opt_if_not_exists ident TYPE_SYM btree_or_rtree + { + Lex->option_list= NULL; + if (Lex->add_key(Key::MULTIPLE, $3, $5, $2)) + MYSQL_YYABORT; + } + '(' key_list ')' normal_key_options { } + | fulltext opt_key_or_index opt_if_not_exists opt_ident { - if (add_create_index (Lex, $1, $2)) + Lex->option_list= NULL; + if (Lex->add_key($1, $4, HA_KEY_ALG_UNDEF, $3)) MYSQL_YYABORT; } - | fulltext opt_key_or_index opt_ident init_key_options - '(' key_list ')' - { Lex->option_list= NULL; } - fulltext_key_options + '(' key_list ')' fulltext_key_options { } + | spatial opt_key_or_index opt_if_not_exists opt_ident { - if (add_create_index (Lex, $1, $3)) + Lex->option_list= NULL; + if (Lex->add_key($1, $4, HA_KEY_ALG_UNDEF, $3)) MYSQL_YYABORT; } - | spatial opt_key_or_index opt_ident init_key_options - '(' key_list ')' - { Lex->option_list= NULL; } - spatial_key_options + '(' key_list ')' spatial_key_options { } + | opt_constraint constraint_key_type + opt_if_not_exists opt_ident + opt_USING_key_algorithm { - if (add_create_index (Lex, $1, $3)) + Lex->option_list= NULL; + if (Lex->add_key($2, $4.str ? $4 : $1, $5, $3)) MYSQL_YYABORT; } - | opt_constraint constraint_key_type opt_ident key_alg - '(' key_list ')' - { Lex->option_list= NULL; } - normal_key_options + '(' key_list ')' normal_key_options { } + | opt_constraint constraint_key_type opt_if_not_exists ident + TYPE_SYM btree_or_rtree { - if (add_create_index (Lex, $2, $3.str ? $3 : $1)) + Lex->option_list= NULL; + if (Lex->add_key($2, $4.str ? $4 : $1, $6, $3)) MYSQL_YYABORT; } - | opt_constraint FOREIGN KEY_SYM opt_ident '(' key_list ')' references + '(' key_list ')' normal_key_options { } + | opt_constraint FOREIGN KEY_SYM opt_if_not_exists opt_ident + { + if (Lex->check_add_key($4) || + !(Lex->last_key= (new (thd->mem_root) + Key(Key::MULTIPLE, $1.str ? $1 : $5, + HA_KEY_ALG_UNDEF, true, $4)))) + MYSQL_YYABORT; + Lex->option_list= NULL; + } + '(' key_list ')' references { LEX *lex=Lex; - Key *key= new Foreign_key($4.str ? $4 : $1, lex->col_list, - $8, - lex->ref_list, - lex->fk_delete_opt, - lex->fk_update_opt, - lex->fk_match_option); + Key *key= (new (thd->mem_root) + Foreign_key($5.str ? $5 : $1, + lex->last_key->columns, + $10->db, + $10->table, + lex->ref_list, + lex->fk_delete_opt, + lex->fk_update_opt, + lex->fk_match_option, + $4)); if (key == NULL) MYSQL_YYABORT; - lex->alter_info.key_list.push_back(key); + /* + handle_if_exists_options() expectes the two keys in this order: + the Foreign_key, followed by its auto-generated Key. + */ + lex->alter_info.key_list.push_back(key, thd->mem_root); + lex->alter_info.key_list.push_back(Lex->last_key, thd->mem_root); lex->option_list= NULL; - if (add_create_index (lex, Key::MULTIPLE, $1.str ? $1 : $4, - &default_key_create_info, 1)) - MYSQL_YYABORT; + /* Only used for ALTER TABLE. Ignored otherwise. */ - lex->alter_info.flags|= ALTER_FOREIGN_KEY; - } - | opt_constraint check_constraint - { - Lex->col_list.empty(); /* Alloced by sql_alloc */ + lex->alter_info.flags|= Alter_info::ADD_FOREIGN_KEY; } + | opt_constraint check_constraint { } ; opt_check_constraint: @@ -5465,58 +5964,59 @@ field_spec: field_ident { LEX *lex=Lex; - lex->length=lex->dec=0; - lex->type=0; - lex->default_value= lex->on_update_value= 0; - lex->comment=null_lex_str; - lex->charset=NULL; - lex->vcol_info= 0; - lex->option_list= NULL; + Create_field *f= new (thd->mem_root) Create_field(); + + if (check_string_char_length(&$1, 0, NAME_CHAR_LEN, + system_charset_info, 1)) + my_yyabort_error((ER_TOO_LONG_IDENT, MYF(0), $1.str)); + + if (!f) + MYSQL_YYABORT; + + lex->init_last_field(f, $1.str, NULL); } + field_type { Lex->set_last_field_type($3); } field_def { LEX *lex=Lex; - if (add_field_to_list(lex->thd, &$1, $3.type, - $3.length, $3.dec, lex->type, - lex->default_value, lex->on_update_value, - &lex->comment, - lex->change, &lex->interval_list, $3.charset, - lex->uint_geom_type, - lex->vcol_info, lex->option_list)) + Create_field *f= lex->last_field; + + if (f->check(thd)) MYSQL_YYABORT; + + lex->alter_info.create_list.push_back(f, thd->mem_root); + + if (f->flags & PRI_KEY_FLAG) + add_key_to_list(lex, &$1, Key::PRIMARY, Lex->check_exists); + else if (f->flags & (UNIQUE_FLAG | UNIQUE_KEY_FLAG)) + add_key_to_list(lex, &$1, Key::UNIQUE, Lex->check_exists); } ; field_def: - type opt_attribute - { $$.set($1, Lex->length, Lex->dec, Lex->charset); } - | type opt_generated_always AS - { $<lex_type>$.set($1, Lex->length, Lex->dec, Lex->charset); } - '(' virtual_column_func ')' vcol_opt_specifier vcol_opt_attribute - { - $$= $<lex_type>4; - Lex->vcol_info->set_field_type($$.type); - $$.type= (enum enum_field_types)MYSQL_TYPE_VIRTUAL; - } + opt_attribute + | opt_generated_always AS + '(' virtual_column_func ')' + vcol_opt_specifier vcol_opt_attribute ; opt_generated_always: - /* empty */ + /* empty */ {} | GENERATED_SYM ALWAYS_SYM {} ; vcol_opt_specifier: /* empty */ { - Lex->vcol_info->set_stored_in_db_flag(FALSE); + Lex->last_field->vcol_info->set_stored_in_db_flag(FALSE); } | VIRTUAL_SYM { - Lex->vcol_info->set_stored_in_db_flag(FALSE); + Lex->last_field->vcol_info->set_stored_in_db_flag(FALSE); } | PERSISTENT_SYM { - Lex->vcol_info->set_stored_in_db_flag(TRUE); + Lex->last_field->vcol_info->set_stored_in_db_flag(TRUE); } ; @@ -5534,16 +6034,16 @@ vcol_attribute: UNIQUE_SYM { LEX *lex=Lex; - lex->type|= UNIQUE_FLAG; - lex->alter_info.flags|= ALTER_ADD_INDEX; + lex->last_field->flags|= UNIQUE_FLAG; + lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX; } | UNIQUE_SYM KEY_SYM { LEX *lex=Lex; - lex->type|= UNIQUE_KEY_FLAG; - lex->alter_info.flags|= ALTER_ADD_INDEX; + lex->last_field->flags|= UNIQUE_FLAG; + lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX; } - | COMMENT_SYM TEXT_STRING_sys { Lex->comment= $2; } + | COMMENT_SYM TEXT_STRING_sys { Lex->last_field->comment= $2; } ; parse_vcol_expr: @@ -5555,33 +6055,45 @@ parse_vcol_expr: Prevent the end user from invoking this command. */ if (!Lex->parse_vcol_expr) - { - my_message(ER_SYNTAX_ERROR, ER(ER_SYNTAX_ERROR), MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SYNTAX_ERROR, MYF(0))); } ; virtual_column_func: remember_name expr remember_end { - Lex->vcol_info= new Virtual_column_info(); - if (!Lex->vcol_info) + Virtual_column_info *v= new (thd->mem_root) Virtual_column_info(); + if (!v) { mem_alloc_error(sizeof(Virtual_column_info)); MYSQL_YYABORT; } uint expr_len= (uint)($3 - $1) - 1; - Lex->vcol_info->expr_str.str= (char* ) sql_memdup($1 + 1, expr_len); - Lex->vcol_info->expr_str.length= expr_len; - Lex->vcol_info->expr_item= $2; + v->expr_str.str= (char* ) thd->memdup($1 + 1, expr_len); + v->expr_str.length= expr_len; + v->expr_item= $2; + Lex->last_field->vcol_info= v; } ; -type: +field_type: int_type opt_field_length field_options { $$=$1; } | real_type opt_precision field_options { $$=$1; } - | FLOAT_SYM float_options field_options { $$=MYSQL_TYPE_FLOAT; } + | FLOAT_SYM float_options field_options + { + $$=MYSQL_TYPE_FLOAT; + if (Lex->length && !Lex->dec) + { + int err; + ulonglong tmp_length= my_strtoll10(Lex->length, NULL, &err); + if (err || tmp_length > PRECISION_FOR_DOUBLE) + my_yyabort_error((ER_WRONG_FIELD_SPEC, MYF(0), + Lex->last_field->field_name)); + if (tmp_length > PRECISION_FOR_FLOAT) + $$= MYSQL_TYPE_DOUBLE; + Lex->length= 0; + } + } | BIT_SYM { Lex->length= (char*) "1"; @@ -5613,13 +6125,13 @@ type: | nchar field_length opt_bin_mod { $$=MYSQL_TYPE_STRING; - Lex->charset=national_charset_info; + bincmp_collation(national_charset_info, $3); } | nchar opt_bin_mod { Lex->length= (char*) "1"; $$=MYSQL_TYPE_STRING; - Lex->charset=national_charset_info; + bincmp_collation(national_charset_info, $2); } | BINARY field_length { @@ -5639,7 +6151,7 @@ type: | nvarchar field_length opt_bin_mod { $$= MYSQL_TYPE_VARCHAR; - Lex->charset=national_charset_info; + bincmp_collation(national_charset_info, $3); } | VARBINARY field_length { @@ -5656,9 +6168,9 @@ type: { char buff[sizeof("YEAR()") + MY_INT64_NUM_DECIMAL_DIGITS + 1]; my_snprintf(buff, sizeof(buff), "YEAR(%lu)", length); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_WARN_DEPRECATED_SYNTAX, - ER(ER_WARN_DEPRECATED_SYNTAX), + ER_THD(thd, ER_WARN_DEPRECATED_SYNTAX), buff, "YEAR(4)"); } } @@ -5667,22 +6179,28 @@ type: | DATE_SYM { $$=MYSQL_TYPE_DATE; } | TIME_SYM opt_field_length - { $$=MYSQL_TYPE_TIME; } + { $$= opt_mysql56_temporal_format ? + MYSQL_TYPE_TIME2 : MYSQL_TYPE_TIME; } | TIMESTAMP opt_field_length { if (thd->variables.sql_mode & MODE_MAXDB) - $$=MYSQL_TYPE_DATETIME; + $$= opt_mysql56_temporal_format ? + MYSQL_TYPE_DATETIME2 : MYSQL_TYPE_DATETIME; else { /* Unlike other types TIMESTAMP fields are NOT NULL by default. + Unless --explicit-defaults-for-timestamp is given. */ - Lex->type|= NOT_NULL_FLAG; - $$=MYSQL_TYPE_TIMESTAMP; + if (!opt_explicit_defaults_for_timestamp) + Lex->last_field->flags|= NOT_NULL_FLAG; + $$= opt_mysql56_temporal_format ? MYSQL_TYPE_TIMESTAMP2 + : MYSQL_TYPE_TIMESTAMP; } } | DATETIME opt_field_length - { $$=MYSQL_TYPE_DATETIME; } + { $$= opt_mysql56_temporal_format ? + MYSQL_TYPE_DATETIME2 : MYSQL_TYPE_DATETIME; } | TINYBLOB { Lex->charset=&my_charset_bin; @@ -5693,16 +6211,15 @@ type: Lex->charset=&my_charset_bin; $$=MYSQL_TYPE_BLOB; } - | spatial_type + | spatial_type float_options srid_option { #ifdef HAVE_SPATIAL Lex->charset=&my_charset_bin; - Lex->uint_geom_type= (uint)$1; + Lex->last_field->geom_type= $1; $$=MYSQL_TYPE_GEOMETRY; #else - my_error(ER_FEATURE_DISABLED, MYF(0), - sym_group_geom.name, sym_group_geom.needed_define); - MYSQL_YYABORT; + my_yyabort_error((ER_FEATURE_DISABLED, MYF(0), sym_group_geom.name, + sym_group_geom.needed_define)); #endif } | MEDIUMBLOB @@ -5736,20 +6253,16 @@ type: { $$=MYSQL_TYPE_NEWDECIMAL;} | FIXED_SYM float_options field_options { $$=MYSQL_TYPE_NEWDECIMAL;} - | ENUM - {Lex->interval_list.empty();} - '(' string_list ')' opt_binary + | ENUM '(' string_list ')' opt_binary { $$=MYSQL_TYPE_ENUM; } - | SET - { Lex->interval_list.empty();} - '(' string_list ')' opt_binary + | SET '(' string_list ')' opt_binary { $$=MYSQL_TYPE_SET; } | LONG_SYM opt_binary { $$=MYSQL_TYPE_MEDIUM_BLOB; } | SERIAL_SYM { $$=MYSQL_TYPE_LONGLONG; - Lex->type|= (AUTO_INCREMENT_FLAG | NOT_NULL_FLAG | UNSIGNED_FLAG | + Lex->last_field->flags|= (AUTO_INCREMENT_FLAG | NOT_NULL_FLAG | UNSIGNED_FLAG | UNIQUE_FLAG); } ; @@ -5812,6 +6325,16 @@ real_type: { $$=MYSQL_TYPE_DOUBLE; } ; +srid_option: + /* empty */ + { Lex->last_field->srid= 0; } + | + REF_SYSTEM_ID_SYM '=' NUM + { + Lex->last_field->srid=atoi($3.str); + } + ; + float_options: /* empty */ { Lex->dec=Lex->length= (char*)0; } @@ -5842,8 +6365,8 @@ field_opt_list: field_option: SIGNED_SYM {} - | UNSIGNED { Lex->type|= UNSIGNED_FLAG;} - | ZEROFILL { Lex->type|= UNSIGNED_FLAG | ZEROFILL_FLAG; } + | UNSIGNED { Lex->last_field->flags|= UNSIGNED_FLAG;} + | ZEROFILL { Lex->last_field->flags|= UNSIGNED_FLAG | ZEROFILL_FLAG; } ; field_length: @@ -5873,106 +6396,98 @@ opt_attribute_list: ; attribute: - NULL_SYM { Lex->type&= ~ NOT_NULL_FLAG; } - | not NULL_SYM { Lex->type|= NOT_NULL_FLAG; } - | DEFAULT now_or_signed_literal { Lex->default_value=$2; } - | ON UPDATE_SYM NOW_SYM optional_braces + NULL_SYM { Lex->last_field->flags&= ~ NOT_NULL_FLAG; } + | not NULL_SYM { Lex->last_field->flags|= NOT_NULL_FLAG; } + | DEFAULT now_or_signed_literal { Lex->last_field->def= $2; } + | ON UPDATE_SYM NOW_SYM opt_default_time_precision { - Item *item= new (thd->mem_root) Item_func_now_local(6); + Item *item= new (thd->mem_root) Item_func_now_local(thd, $4); if (item == NULL) MYSQL_YYABORT; - Lex->on_update_value= item; + Lex->last_field->on_update= item; } - | AUTO_INC { Lex->type|= AUTO_INCREMENT_FLAG | NOT_NULL_FLAG; } + | AUTO_INC { Lex->last_field->flags|= AUTO_INCREMENT_FLAG | NOT_NULL_FLAG; } | SERIAL_SYM DEFAULT VALUE_SYM { LEX *lex=Lex; - lex->type|= AUTO_INCREMENT_FLAG | NOT_NULL_FLAG | UNIQUE_FLAG; - lex->alter_info.flags|= ALTER_ADD_INDEX; + lex->last_field->flags|= AUTO_INCREMENT_FLAG | NOT_NULL_FLAG | UNIQUE_FLAG; + lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX; } | opt_primary KEY_SYM { LEX *lex=Lex; - lex->type|= PRI_KEY_FLAG | NOT_NULL_FLAG; - lex->alter_info.flags|= ALTER_ADD_INDEX; + lex->last_field->flags|= PRI_KEY_FLAG | NOT_NULL_FLAG; + lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX; } | UNIQUE_SYM { LEX *lex=Lex; - lex->type|= UNIQUE_FLAG; - lex->alter_info.flags|= ALTER_ADD_INDEX; + lex->last_field->flags|= UNIQUE_FLAG; + lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX; } | UNIQUE_SYM KEY_SYM { LEX *lex=Lex; - lex->type|= UNIQUE_KEY_FLAG; - lex->alter_info.flags|= ALTER_ADD_INDEX; + lex->last_field->flags|= UNIQUE_KEY_FLAG; + lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX; } - | COMMENT_SYM TEXT_STRING_sys { Lex->comment= $2; } + | COMMENT_SYM TEXT_STRING_sys { Lex->last_field->comment= $2; } | COLLATE_SYM collation_name { if (Lex->charset && !my_charset_same(Lex->charset,$2)) - { - my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0), - $2->name,Lex->charset->csname); - MYSQL_YYABORT; - } - else - { - Lex->charset=$2; - } + my_yyabort_error((ER_COLLATION_CHARSET_MISMATCH, MYF(0), + $2->name,Lex->charset->csname)); + Lex->last_field->charset= $2; } | IDENT_sys equal TEXT_STRING_sys { + if ($3.length > ENGINE_OPTION_MAX_LENGTH) + my_yyabort_error((ER_VALUE_TOO_LONG, MYF(0), $1.str)); new (thd->mem_root) - engine_option_value($1, $3, true, &Lex->option_list, + engine_option_value($1, $3, true, &Lex->last_field->option_list, &Lex->option_list_last); } | IDENT_sys equal ident { + if ($3.length > ENGINE_OPTION_MAX_LENGTH) + my_yyabort_error((ER_VALUE_TOO_LONG, MYF(0), $1.str)); new (thd->mem_root) - engine_option_value($1, $3, false, &Lex->option_list, + engine_option_value($1, $3, false, &Lex->last_field->option_list, &Lex->option_list_last); } | IDENT_sys equal real_ulonglong_num { new (thd->mem_root) - engine_option_value($1, $3, &Lex->option_list, + engine_option_value($1, $3, &Lex->last_field->option_list, &Lex->option_list_last, thd->mem_root); } | IDENT_sys equal DEFAULT { new (thd->mem_root) - engine_option_value($1, &Lex->option_list, &Lex->option_list_last); + engine_option_value($1, &Lex->last_field->option_list, &Lex->option_list_last); } ; type_with_opt_collate: - type opt_collate + field_type opt_collate { $$= $1; - if (Lex->charset) /* Lex->charset is scanned in "type" */ + if ($2) { if (!(Lex->charset= merge_charset_and_collation(Lex->charset, $2))) MYSQL_YYABORT; } - else if ($2) - { - my_error(ER_NOT_SUPPORTED_YET, MYF(0), - "COLLATE with no CHARACTER SET " - "in SP parameters, RETURNS, DECLARE"); - MYSQL_YYABORT; - } + Lex->set_last_field_type($1); } ; now_or_signed_literal: - NOW_SYM optional_braces + NOW_SYM opt_default_time_precision { - $$= new (thd->mem_root) Item_func_now_local(6); + $$= new (thd->mem_root) Item_func_now_local(thd, $2); if ($$ == NULL) MYSQL_YYABORT; } @@ -5980,11 +6495,6 @@ now_or_signed_literal: { $$=$1; } ; -hex_num_or_string: - HEX_NUM {} - | HEX_STRING {} - ; - charset: CHAR_SYM SET {} | CHARSET {} @@ -5994,10 +6504,7 @@ charset_name: ident_or_text { if (!($$=get_charset_by_csname($1.str,MY_CS_PRIMARY,MYF(0)))) - { - my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_UNKNOWN_CHARACTER_SET, MYF(0), $1.str)); } | BINARY { $$= &my_charset_bin; } ; @@ -6017,10 +6524,7 @@ old_or_new_charset_name: { if (!($$=get_charset_by_csname($1.str,MY_CS_PRIMARY,MYF(0))) && !($$=get_old_charset_by_name($1.str))) - { - my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_UNKNOWN_CHARACTER_SET, MYF(0), $1.str)); } | BINARY { $$= &my_charset_bin; } ; @@ -6033,11 +6537,8 @@ old_or_new_charset_name_or_default: collation_name: ident_or_text { - if (!($$=get_charset_by_name($1.str,MYF(0)))) - { - my_error(ER_UNKNOWN_COLLATION, MYF(0), $1.str); + if (!($$= mysqld_collation_get_by_name($1.str))) MYSQL_YYABORT; - } } ; @@ -6056,70 +6557,96 @@ opt_default: | DEFAULT {} ; - -ascii: - ASCII_SYM { Lex->charset= &my_charset_latin1; } - | BINARY ASCII_SYM - { - Lex->charset= &my_charset_latin1_bin; - } - | ASCII_SYM BINARY +charset_or_alias: + charset charset_name { $$= $2; } + | ASCII_SYM { $$= &my_charset_latin1; } + | UNICODE_SYM { - Lex->charset= &my_charset_latin1_bin; + if (!($$= get_charset_by_csname("ucs2", MY_CS_PRIMARY,MYF(0)))) + my_yyabort_error((ER_UNKNOWN_CHARACTER_SET, MYF(0), "ucs2")); } ; -unicode: - UNICODE_SYM - { - if (!(Lex->charset=get_charset_by_csname("ucs2", - MY_CS_PRIMARY,MYF(0)))) - { - my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), "ucs2"); - MYSQL_YYABORT; - } - } - | UNICODE_SYM BINARY - { - if (!(Lex->charset=get_charset_by_name("ucs2_bin", MYF(0)))) - { - my_error(ER_UNKNOWN_COLLATION, MYF(0), "ucs2_bin"); - MYSQL_YYABORT; - } - } - | BINARY UNICODE_SYM - { - if (!(Lex->charset=get_charset_by_name("ucs2_bin", MYF(0)))) - { - my_error(ER_UNKNOWN_COLLATION, MYF(0), "ucs2_bin"); - MYSQL_YYABORT; - } - } +opt_binary: + /* empty */ { bincmp_collation(NULL, false); } + | BYTE_SYM { bincmp_collation(&my_charset_bin, false); } + | charset_or_alias opt_bin_mod { bincmp_collation($1, $2); } + | BINARY { bincmp_collation(NULL, true); } + | BINARY charset_or_alias { bincmp_collation($2, true); } ; -opt_binary: - /* empty */ { Lex->charset=NULL; } - | ascii - | unicode - | BYTE_SYM { Lex->charset=&my_charset_bin; } - | charset charset_name opt_bin_mod { Lex->charset=$2; } - | BINARY - { - Lex->charset= NULL; - Lex->type|= BINCMP_FLAG; - } - | BINARY charset charset_name +opt_bin_mod: + /* empty */ { $$= false; } + | BINARY { $$= true; } + ; + +ws_nweights: + '(' real_ulong_num + { + if ($2 == 0) { - Lex->charset= $3; - Lex->type|= BINCMP_FLAG; + my_parse_error(thd, ER_SYNTAX_ERROR); + MYSQL_YYABORT; } + } + ')' + { $$= $2; } ; -opt_bin_mod: - /* empty */ { } - | BINARY { Lex->type|= BINCMP_FLAG; } +ws_level_flag_desc: + ASC { $$= 0; } + | DESC { $$= 1 << MY_STRXFRM_DESC_SHIFT; } + ; + +ws_level_flag_reverse: + REVERSE_SYM { $$= 1 << MY_STRXFRM_REVERSE_SHIFT; } ; + +ws_level_flags: + /* empty */ { $$= 0; } + | ws_level_flag_desc { $$= $1; } + | ws_level_flag_desc ws_level_flag_reverse { $$= $1 | $2; } + | ws_level_flag_reverse { $$= $1 ; } ; +ws_level_number: + real_ulong_num + { + $$= $1 < 1 ? 1 : ($1 > MY_STRXFRM_NLEVELS ? MY_STRXFRM_NLEVELS : $1); + $$--; + } + ; + +ws_level_list_item: + ws_level_number ws_level_flags + { + $$= (1 | $2) << $1; + } + ; + +ws_level_list: + ws_level_list_item { $$= $1; } + | ws_level_list ',' ws_level_list_item { $$|= $3; } + ; + +ws_level_range: + ws_level_number '-' ws_level_number + { + uint start= $1; + uint end= $3; + for ($$= 0; start <= end; start++) + $$|= (1 << start); + } + ; + +ws_level_list_or_range: + ws_level_list { $$= $1; } + | ws_level_range { $$= $1; } + ; + +opt_ws_levels: + /* empty*/ { $$= 0; } + | LEVEL_SYM ws_level_list_or_range { $$= $2; } + ; opt_primary: /* empty */ @@ -6146,19 +6673,19 @@ opt_ref_list: ref_list: ref_list ',' ident { - Key_part_spec *key= new Key_part_spec($3, 0); + Key_part_spec *key= new (thd->mem_root) Key_part_spec($3, 0); if (key == NULL) MYSQL_YYABORT; - Lex->ref_list.push_back(key); + Lex->ref_list.push_back(key, thd->mem_root); } | ident { - Key_part_spec *key= new Key_part_spec($1, 0); + Key_part_spec *key= new (thd->mem_root) Key_part_spec($1, 0); if (key == NULL) MYSQL_YYABORT; LEX *lex= Lex; lex->ref_list.empty(); - lex->ref_list.push_back(key); + lex->ref_list.push_back(key, thd->mem_root); } ; @@ -6177,19 +6704,19 @@ opt_on_update_delete: /* empty */ { LEX *lex= Lex; - lex->fk_update_opt= Foreign_key::FK_OPTION_UNDEF; - lex->fk_delete_opt= Foreign_key::FK_OPTION_UNDEF; + lex->fk_update_opt= FK_OPTION_UNDEF; + lex->fk_delete_opt= FK_OPTION_UNDEF; } | ON UPDATE_SYM delete_option { LEX *lex= Lex; lex->fk_update_opt= $3; - lex->fk_delete_opt= Foreign_key::FK_OPTION_UNDEF; + lex->fk_delete_opt= FK_OPTION_UNDEF; } | ON DELETE_SYM delete_option { LEX *lex= Lex; - lex->fk_update_opt= Foreign_key::FK_OPTION_UNDEF; + lex->fk_update_opt= FK_OPTION_UNDEF; lex->fk_delete_opt= $3; } | ON UPDATE_SYM delete_option @@ -6209,15 +6736,11 @@ opt_on_update_delete: ; delete_option: - RESTRICT { $$= Foreign_key::FK_OPTION_RESTRICT; } - | CASCADE { $$= Foreign_key::FK_OPTION_CASCADE; } - | SET NULL_SYM { $$= Foreign_key::FK_OPTION_SET_NULL; } - | NO_SYM ACTION { $$= Foreign_key::FK_OPTION_NO_ACTION; } - | SET DEFAULT { $$= Foreign_key::FK_OPTION_DEFAULT; } - ; - -normal_key_type: - key_or_index { $$= Key::MULTIPLE; } + RESTRICT { $$= FK_OPTION_RESTRICT; } + | CASCADE { $$= FK_OPTION_CASCADE; } + | SET NULL_SYM { $$= FK_OPTION_SET_NULL; } + | NO_SYM ACTION { $$= FK_OPTION_NO_ACTION; } + | SET DEFAULT { $$= FK_OPTION_SET_DEFAULT; } ; constraint_key_type: @@ -6256,43 +6779,25 @@ spatial: #ifdef HAVE_SPATIAL $$= Key::SPATIAL; #else - my_error(ER_FEATURE_DISABLED, MYF(0), - sym_group_geom.name, sym_group_geom.needed_define); - MYSQL_YYABORT; + my_yyabort_error((ER_FEATURE_DISABLED, MYF(0), sym_group_geom.name, + sym_group_geom.needed_define)); #endif } ; -init_key_options: - { - Lex->key_create_info= default_key_create_info; - } - ; - -/* - For now, key_alg initializies lex->key_create_info. - In the future, when all key options are after key definition, - we can remove key_alg and move init_key_options to key_options -*/ - -key_alg: - init_key_options - | init_key_options key_using_alg - ; - normal_key_options: /* empty */ {} - | normal_key_opts + | normal_key_opts { Lex->last_key->option_list= Lex->option_list; } ; fulltext_key_options: /* empty */ {} - | fulltext_key_opts + | fulltext_key_opts { Lex->last_key->option_list= Lex->option_list; } ; spatial_key_options: /* empty */ {} - | spatial_key_opts + | spatial_key_opts { Lex->last_key->option_list= Lex->option_list; } ; normal_key_opts: @@ -6310,23 +6815,40 @@ fulltext_key_opts: | fulltext_key_opts fulltext_key_opt ; +opt_USING_key_algorithm: + /* Empty*/ { $$= HA_KEY_ALG_UNDEF; } + | USING btree_or_rtree { $$= $2; } + +/* TYPE is a valid identifier, so it's handled differently than USING */ +opt_key_algorithm_clause: + /* Empty*/ { $$= HA_KEY_ALG_UNDEF; } + | USING btree_or_rtree { $$= $2; } + | TYPE_SYM btree_or_rtree { $$= $2; } + key_using_alg: - USING btree_or_rtree { Lex->key_create_info.algorithm= $2; } - | TYPE_SYM btree_or_rtree { Lex->key_create_info.algorithm= $2; } + USING btree_or_rtree + { Lex->last_key->key_create_info.algorithm= $2; } + | TYPE_SYM btree_or_rtree + { Lex->last_key->key_create_info.algorithm= $2; } ; all_key_opt: KEY_BLOCK_SIZE opt_equal ulong_num - { Lex->key_create_info.block_size= $3; } - | COMMENT_SYM TEXT_STRING_sys { Lex->key_create_info.comment= $2; } + { Lex->last_key->key_create_info.block_size= $3; } + | COMMENT_SYM TEXT_STRING_sys + { Lex->last_key->key_create_info.comment= $2; } | IDENT_sys equal TEXT_STRING_sys { + if ($3.length > ENGINE_OPTION_MAX_LENGTH) + my_yyabort_error((ER_VALUE_TOO_LONG, MYF(0), $1.str)); new (thd->mem_root) engine_option_value($1, $3, true, &Lex->option_list, &Lex->option_list_last); } | IDENT_sys equal ident { + if ($3.length > ENGINE_OPTION_MAX_LENGTH) + my_yyabort_error((ER_VALUE_TOO_LONG, MYF(0), $1.str)); new (thd->mem_root) engine_option_value($1, $3, false, &Lex->option_list, &Lex->option_list_last); @@ -6358,12 +6880,9 @@ fulltext_key_opt: | WITH PARSER_SYM IDENT_sys { if (plugin_is_ready(&$3, MYSQL_FTPARSER_PLUGIN)) - Lex->key_create_info.parser_name= $3; + Lex->last_key->key_create_info.parser_name= $3; else - { - my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), $3.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_FUNCTION_NOT_DEFINED, MYF(0), $3.str)); } ; @@ -6374,14 +6893,20 @@ btree_or_rtree: ; key_list: - key_list ',' key_part order_dir { Lex->col_list.push_back($3); } - | key_part order_dir { Lex->col_list.push_back($1); } + key_list ',' key_part order_dir + { + Lex->last_key->columns.push_back($3, thd->mem_root); + } + | key_part order_dir + { + Lex->last_key->columns.push_back($1, thd->mem_root); + } ; key_part: ident { - $$= new Key_part_spec($1, 0); + $$= new (thd->mem_root) Key_part_spec($1, 0); if ($$ == NULL) MYSQL_YYABORT; } @@ -6389,10 +6914,8 @@ key_part: { int key_part_len= atoi($3.str); if (!key_part_len) - { - my_error(ER_KEY_PART_0, MYF(0), $1.str); - } - $$= new Key_part_spec($1, (uint) key_part_len); + my_yyabort_error((ER_KEY_PART_0, MYF(0), $1.str)); + $$= new (thd->mem_root) Key_part_spec($1, (uint) key_part_len); if ($$ == NULL) MYSQL_YYABORT; } @@ -6409,47 +6932,47 @@ opt_component: ; string_list: - text_string { Lex->interval_list.push_back($1); } - | string_list ',' text_string { Lex->interval_list.push_back($3); }; + text_string + { Lex->last_field->interval_list.push_back($1, thd->mem_root); } + | string_list ',' text_string + { Lex->last_field->interval_list.push_back($3, thd->mem_root); }; /* ** Alter table */ alter: - ALTER alter_options TABLE_SYM table_ident + ALTER + { + Lex->name= null_lex_str; + Lex->only_view= FALSE; + Lex->sql_command= SQLCOM_ALTER_TABLE; + Lex->duplicates= DUP_ERROR; + Lex->select_lex.init_order(); + Lex->create_info.init(); + Lex->create_info.row_type= ROW_TYPE_NOT_USED; + Lex->alter_info.reset(); + Lex->no_write_to_binlog= 0; + Lex->create_info.storage_media= HA_SM_DEFAULT; + DBUG_ASSERT(!Lex->m_sql_cmd); + } + alter_options TABLE_SYM table_ident { - LEX *lex= thd->lex; - lex->name.str= 0; - lex->name.length= 0; - lex->sql_command= SQLCOM_ALTER_TABLE; - lex->duplicates= DUP_ERROR; - if (!lex->select_lex.add_table_to_list(thd, $4, NULL, + if (!Lex->select_lex.add_table_to_list(thd, $5, NULL, TL_OPTION_UPDATING, TL_READ_NO_INSERT, - MDL_SHARED_NO_WRITE)) + MDL_SHARED_UPGRADABLE)) MYSQL_YYABORT; - lex->col_list.empty(); - lex->select_lex.init_order(); - lex->select_lex.db= (lex->select_lex.table_list.first)->db; - bzero((char*) &lex->create_info,sizeof(lex->create_info)); - lex->create_info.db_type= 0; - lex->create_info.default_table_charset= NULL; - lex->create_info.row_type= ROW_TYPE_NOT_USED; - lex->alter_info.reset(); - lex->no_write_to_binlog= 0; - lex->create_info.storage_media= HA_SM_DEFAULT; - lex->create_last_non_select_table= lex->last_table(); - DBUG_ASSERT(!lex->m_stmt); + Lex->select_lex.db= (Lex->select_lex.table_list.first)->db; + Lex->create_last_non_select_table= Lex->last_table(); } alter_commands { - LEX *lex= thd->lex; - if (!lex->m_stmt) + if (!Lex->m_sql_cmd) { /* Create a generic ALTER TABLE statment. */ - lex->m_stmt= new (thd->mem_root) Alter_table_statement(lex); - if (lex->m_stmt == NULL) + Lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_alter_table(); + if (Lex->m_sql_cmd == NULL) MYSQL_YYABORT; } } @@ -6471,10 +6994,7 @@ alter: { LEX *lex= Lex; if (lex->sphead) - { - my_error(ER_SP_NO_DROP_SP, MYF(0), "DATABASE"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_NO_DROP_SP, MYF(0), "DATABASE")); lex->sql_command= SQLCOM_ALTER_DB_UPGRADE; lex->name= $3; } @@ -6483,10 +7003,7 @@ alter: LEX *lex= Lex; if (lex->sphead) - { - my_error(ER_SP_NO_DROP_SP, MYF(0), "PROCEDURE"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_NO_DROP_SP, MYF(0), "PROCEDURE")); bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); } sp_a_chistics @@ -6501,10 +7018,7 @@ alter: LEX *lex= Lex; if (lex->sphead) - { - my_error(ER_SP_NO_DROP_SP, MYF(0), "FUNCTION"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_NO_DROP_SP, MYF(0), "FUNCTION")); bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); } sp_a_chistics @@ -6519,10 +7033,7 @@ alter: LEX *lex= Lex; if (lex->sphead) - { - my_error(ER_SP_BADSTATEMENT, MYF(0), "ALTER VIEW"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "ALTER VIEW")); lex->create_view_mode= VIEW_ALTER; } view_tail @@ -6537,16 +7048,13 @@ alter: LEX *lex= Lex; if (lex->sphead) - { - my_error(ER_SP_BADSTATEMENT, MYF(0), "ALTER VIEW"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "ALTER VIEW")); lex->create_view_algorithm= VIEW_ALGORITHM_INHERIT; lex->create_view_mode= VIEW_ALTER; } view_tail {} - | ALTER definer_opt EVENT_SYM sp_name + | ALTER definer_opt remember_name EVENT_SYM sp_name { /* It is safe to use Lex->spname because @@ -6558,9 +7066,10 @@ alter: if (!(Lex->event_parse_data= Event_parse_data::new_instance(thd))) MYSQL_YYABORT; - Lex->event_parse_data->identifier= $4; + Lex->event_parse_data->identifier= $5; Lex->sql_command= SQLCOM_ALTER_EVENT; + Lex->stmt_definition_begin= $3; } ev_alter_on_schedule_completion opt_ev_rename_to @@ -6568,9 +7077,9 @@ alter: opt_ev_comment opt_ev_sql_stmt { - if (!($6 || $7 || $8 || $9 || $10)) + if (!($7 || $8 || $9 || $10 || $11)) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } /* @@ -6578,6 +7087,7 @@ alter: can overwrite it */ Lex->sql_command= SQLCOM_ALTER_EVENT; + Lex->stmt_definition_end= (char*)YYLIP->get_cpp_ptr(); } | ALTER TABLESPACE alter_tablespace_info { @@ -6599,13 +7109,12 @@ alter: LEX *lex= Lex; lex->alter_tablespace_info->ts_cmd_type= ALTER_ACCESS_MODE_TABLESPACE; } - | ALTER SERVER_SYM ident_or_text OPTIONS_SYM '(' server_options_list ')' + | ALTER SERVER_SYM ident_or_text { LEX *lex= Lex; lex->sql_command= SQLCOM_ALTER_SERVER; - lex->server_options.server_name= $3.str; - lex->server_options.server_name_length= $3.length; - } + lex->server_options.reset($3); + } OPTIONS_SYM '(' server_options_list ')' { } ; ev_alter_on_schedule_completion: @@ -6634,14 +7143,28 @@ opt_ev_sql_stmt: ; ident_or_empty: - /* empty */ { $$.str= 0; $$.length= 0; } + /* empty */ { $$= null_lex_str; } | ident { $$= $1; } ; alter_commands: /* empty */ - | DISCARD TABLESPACE { Lex->alter_info.tablespace_op= DISCARD_TABLESPACE; } - | IMPORT TABLESPACE { Lex->alter_info.tablespace_op= IMPORT_TABLESPACE; } + | DISCARD TABLESPACE + { + Lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_discard_import_tablespace( + Sql_cmd_discard_import_tablespace::DISCARD_TABLESPACE); + if (Lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } + | IMPORT TABLESPACE + { + Lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_discard_import_tablespace( + Sql_cmd_discard_import_tablespace::IMPORT_TABLESPACE); + if (Lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } | alter_list opt_partitioning | alter_list @@ -6653,19 +7176,20 @@ alter_commands: From here we insert a number of commands to manage the partitions of a partitioned table such as adding partitions, dropping partitions, reorganising partitions in various manners. In future releases the list - will be longer and also include moving partitions to a - new table and so forth. + will be longer. */ | add_partition_rule - | DROP PARTITION_SYM alt_part_name_list + | DROP PARTITION_SYM opt_if_exists alt_part_name_list { - Lex->alter_info.flags|= ALTER_DROP_PARTITION; + Lex->alter_info.flags|= Alter_info::ALTER_DROP_PARTITION; + DBUG_ASSERT(!Lex->if_exists()); + Lex->create_info.add($3); } | REBUILD_SYM PARTITION_SYM opt_no_write_to_binlog all_or_alt_part_name_list { LEX *lex= Lex; - lex->alter_info.flags|= ALTER_REBUILD_PARTITION; + lex->alter_info.flags|= Alter_info::ALTER_REBUILD_PARTITION; lex->no_write_to_binlog= $3; } | OPTIMIZE PARTITION_SYM opt_no_write_to_binlog @@ -6674,10 +7198,10 @@ alter_commands: LEX *lex= thd->lex; lex->no_write_to_binlog= $3; lex->check_opt.init(); - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) - Alter_table_optimize_partition_statement(lex); - if (lex->m_stmt == NULL) + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_alter_table_optimize_partition(); + if (lex->m_sql_cmd == NULL) MYSQL_YYABORT; } opt_no_write_to_binlog @@ -6687,20 +7211,20 @@ alter_commands: LEX *lex= thd->lex; lex->no_write_to_binlog= $3; lex->check_opt.init(); - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) - Alter_table_analyze_partition_statement(lex); - if (lex->m_stmt == NULL) - MYSQL_YYABORT; + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_alter_table_analyze_partition(); + if (lex->m_sql_cmd == NULL) + MYSQL_YYABORT; } | CHECK_SYM PARTITION_SYM all_or_alt_part_name_list { LEX *lex= thd->lex; lex->check_opt.init(); - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) - Alter_table_check_partition_statement(lex); - if (lex->m_stmt == NULL) + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_alter_table_check_partition(); + if (lex->m_sql_cmd == NULL) MYSQL_YYABORT; } opt_mi_check_type @@ -6710,17 +7234,17 @@ alter_commands: LEX *lex= thd->lex; lex->no_write_to_binlog= $3; lex->check_opt.init(); - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) - Alter_table_repair_partition_statement(lex); - if (lex->m_stmt == NULL) + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_alter_table_repair_partition(); + if (lex->m_sql_cmd == NULL) MYSQL_YYABORT; } opt_mi_repair_type | COALESCE PARTITION_SYM opt_no_write_to_binlog real_ulong_num { LEX *lex= Lex; - lex->alter_info.flags|= ALTER_COALESCE_PARTITION; + lex->alter_info.flags|= Alter_info::ALTER_COALESCE_PARTITION; lex->no_write_to_binlog= $3; lex->alter_info.num_parts= $4; } @@ -6728,42 +7252,69 @@ alter_commands: { LEX *lex= thd->lex; lex->check_opt.init(); - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) - Alter_table_truncate_partition_statement(lex); - if (lex->m_stmt == NULL) + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_alter_table_truncate_partition(); + if (lex->m_sql_cmd == NULL) MYSQL_YYABORT; } | reorg_partition_rule + | EXCHANGE_SYM PARTITION_SYM alt_part_name_item + WITH TABLE_SYM table_ident have_partitioning + { + LEX *lex= thd->lex; + size_t dummy; + lex->select_lex.db=$6->db.str; + if (lex->select_lex.db == NULL && + lex->copy_db_to(&lex->select_lex.db, &dummy)) + { + MYSQL_YYABORT; + } + lex->name= $6->table; + lex->alter_info.flags|= Alter_info::ALTER_EXCHANGE_PARTITION; + if (!lex->select_lex.add_table_to_list(thd, $6, NULL, + TL_OPTION_UPDATING, + TL_READ_NO_INSERT, + MDL_SHARED_NO_WRITE)) + MYSQL_YYABORT; + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_alter_table_exchange_partition(); + if (lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } ; remove_partitioning: REMOVE_SYM PARTITIONING_SYM { - Lex->alter_info.flags|= ALTER_REMOVE_PARTITIONING; + Lex->alter_info.flags|= Alter_info::ALTER_REMOVE_PARTITIONING; } ; all_or_alt_part_name_list: ALL { - Lex->alter_info.flags|= ALTER_ALL_PARTITION; + Lex->alter_info.flags|= Alter_info::ALTER_ALL_PARTITION; } | alt_part_name_list ; add_partition_rule: - ADD PARTITION_SYM opt_no_write_to_binlog + ADD PARTITION_SYM opt_if_not_exists + opt_no_write_to_binlog { LEX *lex= Lex; - lex->part_info= new partition_info(); + lex->part_info= new (thd->mem_root) partition_info(); if (!lex->part_info) { mem_alloc_error(sizeof(partition_info)); MYSQL_YYABORT; } - lex->alter_info.flags|= ALTER_ADD_PARTITION; - lex->no_write_to_binlog= $3; + lex->alter_info.flags|= Alter_info::ALTER_ADD_PARTITION; + DBUG_ASSERT(!Lex->create_info.if_not_exists()); + lex->create_info.set($3); + lex->no_write_to_binlog= $4; } add_part_extra {} @@ -6786,7 +7337,7 @@ reorg_partition_rule: REORGANIZE_SYM PARTITION_SYM opt_no_write_to_binlog { LEX *lex= Lex; - lex->part_info= new partition_info(); + lex->part_info= new (thd->mem_root) partition_info(); if (!lex->part_info) { mem_alloc_error(sizeof(partition_info)); @@ -6800,11 +7351,11 @@ reorg_partition_rule: reorg_parts_rule: /* empty */ { - Lex->alter_info.flags|= ALTER_TABLE_REORG; + Lex->alter_info.flags|= Alter_info::ALTER_TABLE_REORG; } | alt_part_name_list { - Lex->alter_info.flags|= ALTER_REORGANIZE_PARTITION; + Lex->alter_info.flags|= Alter_info::ALTER_REORGANIZE_PARTITION; } INTO '(' part_def_list ')' { @@ -6821,7 +7372,8 @@ alt_part_name_list: alt_part_name_item: ident { - if (Lex->alter_info.partition_names.push_back($1.str)) + if (Lex->alter_info.partition_names.push_back($1.str, + thd->mem_root)) { mem_alloc_error(1); MYSQL_YYABORT; @@ -6839,11 +7391,10 @@ alter_list: ; add_column: - ADD opt_column + ADD opt_column opt_if_not_exists_table_element { LEX *lex=Lex; - lex->change=0; - lex->alter_info.flags|= ALTER_ADD_COLUMN; + lex->alter_info.flags|= Alter_info::ALTER_ADD_COLUMN; } ; @@ -6855,111 +7406,98 @@ alter_list_item: | ADD key_def { Lex->create_last_non_select_table= Lex->last_table(); - Lex->alter_info.flags|= ALTER_ADD_INDEX; + Lex->alter_info.flags|= Alter_info::ALTER_ADD_INDEX; } | add_column '(' create_field_list ')' { - Lex->alter_info.flags|= ALTER_ADD_COLUMN | ALTER_ADD_INDEX; - } - | CHANGE opt_column field_ident - { - LEX *lex=Lex; - lex->change= $3.str; - lex->alter_info.flags|= ALTER_CHANGE_COLUMN; - lex->option_list= NULL; + Lex->alter_info.flags|= Alter_info::ALTER_ADD_COLUMN | + Alter_info::ALTER_ADD_INDEX; } + | CHANGE opt_column opt_if_exists_table_element field_ident field_spec opt_place { + Lex->alter_info.flags|= Alter_info::ALTER_CHANGE_COLUMN; Lex->create_last_non_select_table= Lex->last_table(); + Lex->last_field->change= $4.str; } - | MODIFY_SYM opt_column field_ident + | MODIFY_SYM opt_column opt_if_exists_table_element + field_spec opt_place { - LEX *lex=Lex; - lex->length=lex->dec=0; lex->type=0; - lex->default_value= lex->on_update_value= 0; - lex->comment=null_lex_str; - lex->charset= NULL; - lex->alter_info.flags|= ALTER_CHANGE_COLUMN; - lex->vcol_info= 0; - lex->option_list= NULL; + Lex->alter_info.flags|= Alter_info::ALTER_CHANGE_COLUMN; + Lex->create_last_non_select_table= Lex->last_table(); + Lex->last_field->change= Lex->last_field->field_name; } - field_def + | DROP opt_column opt_if_exists_table_element field_ident opt_restrict { LEX *lex=Lex; - if (add_field_to_list(lex->thd,&$3, - $5.type, - $5.length, $5.dec, lex->type, - lex->default_value, lex->on_update_value, - &lex->comment, - $3.str, &lex->interval_list, $5.charset, - lex->uint_geom_type, - lex->vcol_info, lex->option_list)) + Alter_drop *ad= (new (thd->mem_root) + Alter_drop(Alter_drop::COLUMN, $4.str, $3)); + if (ad == NULL) MYSQL_YYABORT; + lex->alter_info.drop_list.push_back(ad, thd->mem_root); + lex->alter_info.flags|= Alter_info::ALTER_DROP_COLUMN; } - opt_place - { - Lex->create_last_non_select_table= Lex->last_table(); - } - | DROP opt_column field_ident opt_restrict + | DROP FOREIGN KEY_SYM opt_if_exists_table_element field_ident { LEX *lex=Lex; - Alter_drop *ad= new Alter_drop(Alter_drop::COLUMN, $3.str); + Alter_drop *ad= (new (thd->mem_root) + Alter_drop(Alter_drop::FOREIGN_KEY, $5.str, $4)); if (ad == NULL) MYSQL_YYABORT; - lex->alter_info.drop_list.push_back(ad); - lex->alter_info.flags|= ALTER_DROP_COLUMN; - } - | DROP FOREIGN KEY_SYM opt_ident - { - Lex->alter_info.flags|= ALTER_DROP_INDEX | ALTER_FOREIGN_KEY; + lex->alter_info.drop_list.push_back(ad, thd->mem_root); + lex->alter_info.flags|= Alter_info::DROP_FOREIGN_KEY; } | DROP PRIMARY_SYM KEY_SYM { LEX *lex=Lex; - Alter_drop *ad= new Alter_drop(Alter_drop::KEY, primary_key_name); + Alter_drop *ad= (new (thd->mem_root) + Alter_drop(Alter_drop::KEY, primary_key_name, + FALSE)); if (ad == NULL) MYSQL_YYABORT; - lex->alter_info.drop_list.push_back(ad); - lex->alter_info.flags|= ALTER_DROP_INDEX; + lex->alter_info.drop_list.push_back(ad, thd->mem_root); + lex->alter_info.flags|= Alter_info::ALTER_DROP_INDEX; } - | DROP key_or_index field_ident + | DROP key_or_index opt_if_exists_table_element field_ident { LEX *lex=Lex; - Alter_drop *ad= new Alter_drop(Alter_drop::KEY, $3.str); + Alter_drop *ad= (new (thd->mem_root) + Alter_drop(Alter_drop::KEY, $4.str, $3)); if (ad == NULL) MYSQL_YYABORT; - lex->alter_info.drop_list.push_back(ad); - lex->alter_info.flags|= ALTER_DROP_INDEX; + lex->alter_info.drop_list.push_back(ad, thd->mem_root); + lex->alter_info.flags|= Alter_info::ALTER_DROP_INDEX; } | DISABLE_SYM KEYS { LEX *lex=Lex; - lex->alter_info.keys_onoff= DISABLE; - lex->alter_info.flags|= ALTER_KEYS_ONOFF; + lex->alter_info.keys_onoff= Alter_info::DISABLE; + lex->alter_info.flags|= Alter_info::ALTER_KEYS_ONOFF; } | ENABLE_SYM KEYS { LEX *lex=Lex; - lex->alter_info.keys_onoff= ENABLE; - lex->alter_info.flags|= ALTER_KEYS_ONOFF; + lex->alter_info.keys_onoff= Alter_info::ENABLE; + lex->alter_info.flags|= Alter_info::ALTER_KEYS_ONOFF; } | ALTER opt_column field_ident SET DEFAULT signed_literal { LEX *lex=Lex; - Alter_column *ac= new Alter_column($3.str,$6); + Alter_column *ac= new (thd->mem_root) Alter_column($3.str,$6); if (ac == NULL) MYSQL_YYABORT; - lex->alter_info.alter_list.push_back(ac); - lex->alter_info.flags|= ALTER_CHANGE_COLUMN_DEFAULT; + lex->alter_info.alter_list.push_back(ac, thd->mem_root); + lex->alter_info.flags|= Alter_info::ALTER_CHANGE_COLUMN_DEFAULT; } | ALTER opt_column field_ident DROP DEFAULT { LEX *lex=Lex; - Alter_column *ac= new Alter_column($3.str, (Item*) 0); + Alter_column *ac= (new (thd->mem_root) + Alter_column($3.str, (Item*) 0)); if (ac == NULL) MYSQL_YYABORT; - lex->alter_info.alter_list.push_back(ac); - lex->alter_info.flags|= ALTER_CHANGE_COLUMN_DEFAULT; + lex->alter_info.alter_list.push_back(ac, thd->mem_root); + lex->alter_info.flags|= Alter_info::ALTER_CHANGE_COLUMN_DEFAULT; } | RENAME opt_to table_ident { @@ -6973,12 +7511,9 @@ alter_list_item: } if (check_table_name($3->table.str,$3->table.length, FALSE) || ($3->db.str && check_db_name(&$3->db))) - { - my_error(ER_WRONG_TABLE_NAME, MYF(0), $3->table.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_TABLE_NAME, MYF(0), $3->table.str)); lex->name= $3->table; - lex->alter_info.flags|= ALTER_RENAME; + lex->alter_info.flags|= Alter_info::ALTER_RENAME; } | CONVERT_SYM TO_SYM charset charset_name_or_default opt_collate { @@ -6988,22 +7523,16 @@ alter_list_item: } $5= $5 ? $5 : $4; if (!my_charset_same($4,$5)) - { - my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0), - $5->name, $4->csname); + my_yyabort_error((ER_COLLATION_CHARSET_MISMATCH, MYF(0), + $5->name, $4->csname)); + if (Lex->create_info.add_alter_list_item_convert_to_charset($5)) MYSQL_YYABORT; - } - LEX *lex= Lex; - lex->create_info.table_charset= - lex->create_info.default_table_charset= $5; - lex->create_info.used_fields|= (HA_CREATE_USED_CHARSET | - HA_CREATE_USED_DEFAULT_CHARSET); - lex->alter_info.flags|= ALTER_CONVERT; + Lex->alter_info.flags|= Alter_info::ALTER_OPTIONS; } | create_table_options_space_separated { LEX *lex=Lex; - lex->alter_info.flags|= ALTER_OPTIONS; + lex->alter_info.flags|= Alter_info::ALTER_OPTIONS; if ((lex->create_info.used_fields & HA_CREATE_USED_ENGINE) && !lex->create_info.db_type) { @@ -7012,12 +7541,47 @@ alter_list_item: } | FORCE_SYM { - Lex->alter_info.flags|= ALTER_RECREATE; + Lex->alter_info.flags|= Alter_info::ALTER_RECREATE; } | alter_order_clause { LEX *lex=Lex; - lex->alter_info.flags|= ALTER_ORDER; + lex->alter_info.flags|= Alter_info::ALTER_ORDER; + } + | alter_algorithm_option + | alter_lock_option + ; + +opt_index_lock_algorithm: + /* empty */ + | alter_lock_option + | alter_algorithm_option + | alter_lock_option alter_algorithm_option + | alter_algorithm_option alter_lock_option + +alter_algorithm_option: + ALGORITHM_SYM opt_equal DEFAULT + { + Lex->alter_info.requested_algorithm= + Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT; + } + | ALGORITHM_SYM opt_equal ident + { + if (Lex->alter_info.set_requested_algorithm(&$3)) + my_yyabort_error((ER_UNKNOWN_ALTER_ALGORITHM, MYF(0), $3.str)); + } + ; + +alter_lock_option: + LOCK_SYM opt_equal DEFAULT + { + Lex->alter_info.requested_lock= + Alter_info::ALTER_TABLE_LOCK_DEFAULT; + } + | LOCK_SYM opt_equal ident + { + if (Lex->alter_info.set_requested_lock(&$3)) + my_yyabort_error((ER_UNKNOWN_ALTER_LOCK, MYF(0), $3.str)); } ; @@ -7032,7 +7596,7 @@ opt_ignore: ; alter_options: - { Lex->ignore= Lex->online= 0;} alter_options_part2 + { Lex->ignore= 0;} alter_options_part2 ; alter_options_part2: @@ -7047,7 +7611,11 @@ alter_option_list: alter_option: IGNORE_SYM { Lex->ignore= 1;} - | ONLINE_SYM { Lex->online= 1;} + | ONLINE_SYM + { + Lex->alter_info.requested_lock= + Alter_info::ALTER_TABLE_LOCK_NONE; + } opt_restrict: @@ -7058,8 +7626,16 @@ opt_restrict: opt_place: /* empty */ {} - | AFTER_SYM ident { store_position_for_column($2.str); } - | FIRST_SYM { store_position_for_column(first_keyword); } + | AFTER_SYM ident + { + store_position_for_column($2.str); + Lex->alter_info.flags |= Alter_info::ALTER_COLUMN_ORDER; + } + | FIRST_SYM + { + store_position_for_column(first_keyword); + Lex->alter_info.flags |= Alter_info::ALTER_COLUMN_ORDER; + } ; opt_to: @@ -7069,12 +7645,8 @@ opt_to: | AS {} ; -/* - SLAVE START and SLAVE STOP are deprecated. We keep them for compatibility. -*/ - slave: - START_SYM SLAVE slave_thread_opts + START_SYM SLAVE optional_connection_name slave_thread_opts { LEX *lex=Lex; lex->sql_command = SQLCOM_SLAVE_START; @@ -7083,44 +7655,80 @@ slave: } slave_until {} - | STOP_SYM SLAVE slave_thread_opts + | START_SYM ALL SLAVES slave_thread_opts { LEX *lex=Lex; - lex->sql_command = SQLCOM_SLAVE_STOP; + lex->sql_command = SQLCOM_SLAVE_ALL_START; lex->type = 0; - /* If you change this code don't forget to update SLAVE STOP too */ } - | SLAVE START_SYM slave_thread_opts + {} + | STOP_SYM SLAVE optional_connection_name slave_thread_opts { LEX *lex=Lex; - lex->sql_command = SQLCOM_SLAVE_START; + lex->sql_command = SQLCOM_SLAVE_STOP; lex->type = 0; + /* If you change this code don't forget to update SLAVE STOP too */ } - slave_until - {} - | SLAVE STOP_SYM slave_thread_opts + | STOP_SYM ALL SLAVES slave_thread_opts { LEX *lex=Lex; - lex->sql_command = SQLCOM_SLAVE_STOP; + lex->sql_command = SQLCOM_SLAVE_ALL_STOP; lex->type = 0; + /* If you change this code don't forget to update SLAVE STOP too */ } ; start: - START_SYM TRANSACTION_SYM start_transaction_opts + START_SYM TRANSACTION_SYM opt_start_transaction_option_list { LEX *lex= Lex; lex->sql_command= SQLCOM_BEGIN; + /* READ ONLY and READ WRITE are mutually exclusive. */ + if (($3 & MYSQL_START_TRANS_OPT_READ_WRITE) && + ($3 & MYSQL_START_TRANS_OPT_READ_ONLY)) + { + my_parse_error(thd, ER_SYNTAX_ERROR); + MYSQL_YYABORT; + } lex->start_transaction_opt= $3; } ; -start_transaction_opts: - /*empty*/ { $$ = 0; } - | WITH CONSISTENT_SYM SNAPSHOT_SYM +opt_start_transaction_option_list: + /* empty */ + { + $$= 0; + } + | start_transaction_option_list + { + $$= $1; + } + ; + +start_transaction_option_list: + start_transaction_option + { + $$= $1; + } + | start_transaction_option_list ',' start_transaction_option + { + $$= $1 | $3; + } + ; + +start_transaction_option: + WITH CONSISTENT_SYM SNAPSHOT_SYM { $$= MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT; } + | READ_SYM ONLY_SYM + { + $$= MYSQL_START_TRANS_OPT_READ_ONLY; + } + | READ_SYM WRITE_SYM + { + $$= MYSQL_START_TRANS_OPT_READ_WRITE; + } ; slave_thread_opts: @@ -7149,11 +7757,11 @@ slave_until: (lex->mi.relay_log_name || lex->mi.relay_log_pos)) || !((lex->mi.log_file_name && lex->mi.pos) || (lex->mi.relay_log_name && lex->mi.relay_log_pos))) - { - my_message(ER_BAD_SLAVE_UNTIL_COND, - ER(ER_BAD_SLAVE_UNTIL_COND), MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_BAD_SLAVE_UNTIL_COND, MYF(0))); + } + | UNTIL_SYM MASTER_GTID_POS_SYM '=' TEXT_STRING_sys + { + Lex->mi.gtid_pos_str = $4; } ; @@ -7199,9 +7807,9 @@ repair: repair_table_or_view { LEX* lex= thd->lex; - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) Repair_table_statement(lex); - if (lex->m_stmt == NULL) + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_repair_table(); + if (lex->m_sql_cmd == NULL) MYSQL_YYABORT; } ; @@ -7238,23 +7846,121 @@ analyze: /* Will be overriden during execution. */ YYPS->m_lock_type= TL_UNLOCK; } - table_list + analyze_table_list { LEX* lex= thd->lex; - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) Analyze_table_statement(lex); - if (lex->m_stmt == NULL) + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_analyze_table(); + if (lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } + ; + +analyze_table_list: + analyze_table_elem_spec + | analyze_table_list ',' analyze_table_elem_spec + ; + +analyze_table_elem_spec: + table_name opt_persistent_stat_clause + ; + +opt_persistent_stat_clause: + /* empty */ + {} + | PERSISTENT_SYM FOR_SYM persistent_stat_spec + { + thd->lex->with_persistent_for_clause= TRUE; + } + ; + +persistent_stat_spec: + ALL + {} + | COLUMNS persistent_column_stat_spec INDEXES persistent_index_stat_spec + {} + +persistent_column_stat_spec: + ALL {} + | '(' + { + LEX* lex= thd->lex; + lex->column_list= new (thd->mem_root) List<LEX_STRING>; + if (lex->column_list == NULL) + MYSQL_YYABORT; + } + table_column_list + ')' + ; + +persistent_index_stat_spec: + ALL {} + | '(' + { + LEX* lex= thd->lex; + lex->index_list= new (thd->mem_root) List<LEX_STRING>; + if (lex->index_list == NULL) MYSQL_YYABORT; } + table_index_list + ')' + ; + +table_column_list: + /* empty */ + {} + | ident + { + Lex->column_list->push_back((LEX_STRING*) + thd->memdup(&$1, sizeof(LEX_STRING)), thd->mem_root); + } + | table_column_list ',' ident + { + Lex->column_list->push_back((LEX_STRING*) + thd->memdup(&$3, sizeof(LEX_STRING)), thd->mem_root); + } + ; + +table_index_list: + /* empty */ + {} + | table_index_name + | table_index_list ',' table_index_name ; +table_index_name: + ident + { + Lex->index_list->push_back((LEX_STRING*) + thd->memdup(&$1, sizeof(LEX_STRING)), + thd->mem_root); + } + | + PRIMARY_SYM + { + LEX_STRING str= {(char*) "PRIMARY", 7}; + Lex->index_list->push_back((LEX_STRING*) + thd->memdup(&str, sizeof(LEX_STRING)), + thd->mem_root); + } + ; + binlog_base64_event: BINLOG_SYM TEXT_STRING_sys { Lex->sql_command = SQLCOM_BINLOG_BASE64_EVENT; Lex->comment= $2; + Lex->ident.str= NULL; + Lex->ident.length= 0; } - ; + | + BINLOG_SYM '@' ident_or_text ',' '@' ident_or_text + { + Lex->sql_command = SQLCOM_BINLOG_BASE64_EVENT; + Lex->comment= $3; + Lex->ident= $6; + } + ; check_view_or_table: table_or_tables table_list opt_mi_check_type @@ -7275,13 +7981,10 @@ check: CHECK_SYM { LEX* lex= thd->lex; if (lex->sphead) - { - my_error(ER_SP_BADSTATEMENT, MYF(0), "CHECK"); - MYSQL_YYABORT; - } - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) Check_table_statement(lex); - if (lex->m_stmt == NULL) + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "CHECK")); + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_check_table(); + if (lex->m_sql_cmd == NULL) MYSQL_YYABORT; } ; @@ -7324,9 +8027,9 @@ optimize: table_list { LEX* lex= thd->lex; - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) Optimize_table_statement(lex); - if (lex->m_stmt == NULL) + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_optimize_table(); + if (lex->m_sql_cmd == NULL) MYSQL_YYABORT; } ; @@ -7344,7 +8047,7 @@ rename: } table_to_table_list {} - | RENAME USER clear_privileges rename_list + | RENAME USER_SYM clear_privileges rename_list { Lex->sql_command = SQLCOM_RENAME_USER; } @@ -7353,12 +8056,14 @@ rename: rename_list: user TO_SYM user { - if (Lex->users_list.push_back($1) || Lex->users_list.push_back($3)) + if (Lex->users_list.push_back($1, thd->mem_root) || + Lex->users_list.push_back($3, thd->mem_root)) MYSQL_YYABORT; } | rename_list ',' user TO_SYM user { - if (Lex->users_list.push_back($3) || Lex->users_list.push_back($5)) + if (Lex->users_list.push_back($3, thd->mem_root) || + Lex->users_list.push_back($5, thd->mem_root)) MYSQL_YYABORT; } ; @@ -7373,9 +8078,9 @@ table_to_table: { LEX *lex=Lex; SELECT_LEX *sl= lex->current_select; - if (!sl->add_table_to_list(lex->thd, $1,NULL,TL_OPTION_UPDATING, + if (!sl->add_table_to_list(thd, $1,NULL,TL_OPTION_UPDATING, TL_IGNORE, MDL_EXCLUSIVE) || - !sl->add_table_to_list(lex->thd, $3,NULL,TL_OPTION_UPDATING, + !sl->add_table_to_list(thd, $3,NULL,TL_OPTION_UPDATING, TL_IGNORE, MDL_EXCLUSIVE)) MYSQL_YYABORT; } @@ -7473,7 +8178,7 @@ preload_keys_parts: adm_partition: PARTITION_SYM have_partitioning { - Lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + Lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION; } '(' all_or_alt_part_name_list ')' ; @@ -7518,6 +8223,13 @@ select_init: ; select_paren: + { + /* + In order to correctly parse UNION's global ORDER BY we need to + set braces before parsing the clause. + */ + Lex->current_select->set_braces(true); + } SELECT_SYM select_part2 { if (setup_select_in_parentheses(Lex)) @@ -7528,7 +8240,11 @@ select_paren: /* The equivalent of select_paren for nested queries. */ select_paren_derived: + { + Lex->current_select->set_braces(true); + } SELECT_SYM select_part2_derived + table_expression { if (setup_select_in_parentheses(Lex)) MYSQL_YYABORT; @@ -7540,23 +8256,44 @@ select_init2: select_part2 { LEX *lex= Lex; - SELECT_LEX * sel= lex->current_select; - if (lex->current_select->set_braces(0)) - { - my_parse_error(ER(ER_SYNTAX_ERROR)); - MYSQL_YYABORT; - } - if (sel->linkage == UNION_TYPE && - sel->master_unit()->first_select()->braces) - { - my_parse_error(ER(ER_SYNTAX_ERROR)); - MYSQL_YYABORT; - } + /* Parentheses carry no meaning here */ + lex->current_select->set_braces(false); } union_clause ; +/* + Theoretically we can merge all 3 right hand sides of the select_part2 + rule into one, however such a transformation adds one shift/reduce + conflict more. +*/ select_part2: + select_options_and_item_list + opt_order_clause + opt_limit_clause + opt_select_lock_type + | select_options_and_item_list into opt_select_lock_type + | select_options_and_item_list + opt_into + from_clause + opt_where_clause + opt_group_clause + opt_having_clause + opt_order_clause + opt_limit_clause + opt_procedure_clause + opt_into + opt_select_lock_type + { + if ($2 && $10) /* double "INTO" clause */ + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "INTO", "INTO")); + + if ($9 && ($2 || $10)) /* "INTO" with "PROCEDURE ANALYSE" */ + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "PROCEDURE", "INTO")); + } + ; + +select_options_and_item_list: { LEX *lex= Lex; SELECT_LEX *sel= lex->current_select; @@ -7568,26 +8305,36 @@ select_part2: { Select->parsing_place= NO_MATTER; } - select_into select_lock_type ; -select_into: - opt_order_clause opt_limit_clause {} - | into - | select_from - | into select_from - | select_from into +table_expression: + opt_from_clause + opt_where_clause + opt_group_clause + opt_having_clause + opt_order_clause + opt_limit_clause + opt_procedure_clause + opt_select_lock_type + ; + +from_clause: + FROM table_reference_list + ; + +opt_from_clause: + /* empty */ + | from_clause ; -select_from: - FROM join_table_list where_clause group_clause having_clause - opt_order_clause opt_limit_clause procedure_clause +table_reference_list: + join_table_list { Select->context.table_list= Select->context.first_name_resolution_table= Select->table_list.first; } - | FROM DUAL_SYM where_clause opt_limit_clause + | DUAL_SYM /* oracle compatibility: oracle always requires FROM clause, and DUAL is system table without fields. Is "SELECT 1 FROM DUAL" any better than "SELECT 1" ? @@ -7599,10 +8346,7 @@ select_options: | select_option_list { if (Select->options & SELECT_DISTINCT && Select->options & SELECT_ALL) - { - my_error(ER_WRONG_USAGE, MYF(0), "ALL", "DISTINCT"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "ALL", "DISTINCT")); } ; @@ -7620,26 +8364,15 @@ select_option: SQL_CACHE wasn't specified, and only once per query. */ if (Lex->current_select != &Lex->select_lex) - { - my_error(ER_CANT_USE_OPTION_HERE, MYF(0), "SQL_NO_CACHE"); - MYSQL_YYABORT; - } - else if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_CACHE) - { - my_error(ER_WRONG_USAGE, MYF(0), "SQL_CACHE", "SQL_NO_CACHE"); - MYSQL_YYABORT; - } - else if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_NO_CACHE) - { - my_error(ER_DUP_ARGUMENT, MYF(0), "SQL_NO_CACHE"); - MYSQL_YYABORT; - } - else - { - Lex->safe_to_cache_query=0; - Lex->select_lex.options&= ~OPTION_TO_QUERY_CACHE; - Lex->select_lex.sql_cache= SELECT_LEX::SQL_NO_CACHE; - } + my_yyabort_error((ER_CANT_USE_OPTION_HERE, MYF(0), "SQL_NO_CACHE")); + if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_CACHE) + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "SQL_CACHE", "SQL_NO_CACHE")); + if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_NO_CACHE) + my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "SQL_NO_CACHE")); + + Lex->safe_to_cache_query=0; + Lex->select_lex.options&= ~OPTION_TO_QUERY_CACHE; + Lex->select_lex.sql_cache= SELECT_LEX::SQL_NO_CACHE; } | SQL_CACHE_SYM { @@ -7648,40 +8381,31 @@ select_option: SQL_NO_CACHE wasn't specified, and only once per query. */ if (Lex->current_select != &Lex->select_lex) - { - my_error(ER_CANT_USE_OPTION_HERE, MYF(0), "SQL_CACHE"); - MYSQL_YYABORT; - } - else if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_NO_CACHE) - { - my_error(ER_WRONG_USAGE, MYF(0), "SQL_NO_CACHE", "SQL_CACHE"); - MYSQL_YYABORT; - } - else if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_CACHE) - { - my_error(ER_DUP_ARGUMENT, MYF(0), "SQL_CACHE"); - MYSQL_YYABORT; - } - else - { - Lex->safe_to_cache_query=1; - Lex->select_lex.options|= OPTION_TO_QUERY_CACHE; - Lex->select_lex.sql_cache= SELECT_LEX::SQL_CACHE; - } + my_yyabort_error((ER_CANT_USE_OPTION_HERE, MYF(0), "SQL_CACHE")); + if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_NO_CACHE) + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "SQL_NO_CACHE", "SQL_CACHE")); + if (Lex->select_lex.sql_cache == SELECT_LEX::SQL_CACHE) + my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "SQL_CACHE")); + + Lex->safe_to_cache_query=1; + Lex->select_lex.options|= OPTION_TO_QUERY_CACHE; + Lex->select_lex.sql_cache= SELECT_LEX::SQL_CACHE; } ; -select_lock_type: +opt_select_lock_type: /* empty */ | FOR_SYM UPDATE_SYM { LEX *lex=Lex; + lex->current_select->lock_type= TL_WRITE; lex->current_select->set_lock_for_tables(TL_WRITE); lex->safe_to_cache_query=0; } | LOCK_SYM IN_SYM SHARE_SYM MODE_SYM { LEX *lex=Lex; + lex->current_select->lock_type= TL_READ_WITH_SHARED_LOCKS; lex->current_select-> set_lock_for_tables(TL_READ_WITH_SHARED_LOCKS); lex->safe_to_cache_query=0; @@ -7694,7 +8418,7 @@ select_item_list: | '*' { Item *item= new (thd->mem_root) - Item_field(&thd->lex->current_select->context, + Item_field(thd, &thd->lex->current_select->context, NULL, NULL, "*"); if (item == NULL) MYSQL_YYABORT; @@ -7720,10 +8444,7 @@ select_item: { if (Lex->sql_command == SQLCOM_CREATE_VIEW && check_column_name($4.str)) - { - my_error(ER_WRONG_COLUMN_NAME, MYF(0), $4.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_COLUMN_NAME, MYF(0), $4.str)); $2->is_autogenerated_name= FALSE; $2->set_name($4.str, $4.length, system_charset_info); } @@ -7734,6 +8455,12 @@ select_item: } ; +remember_tok_start: + { + $$= (char*) YYLIP->get_tok_start(); + } + ; + remember_name: { $$= (char*) YYLIP->get_cpp_tok_start(); @@ -7754,6 +8481,12 @@ select_alias: | TEXT_STRING_sys { $$=$1; } ; +opt_default_time_precision: + /* empty */ { $$= NOT_FIXED_DEC; } + | '(' ')' { $$= NOT_FIXED_DEC; } + | '(' real_ulong_num ')' { $$= $2; }; + ; + opt_time_precision: /* empty */ { $$= 0; } | '(' ')' { $$= 0; } @@ -7797,7 +8530,7 @@ expr: /* (X1 OR X2) OR Y ==> OR (X1, X2, Y) */ - item1->add($3); + item1->add($3, thd->mem_root); $$ = $1; } } @@ -7807,13 +8540,13 @@ expr: /* X OR (Y1 OR Y2) ==> OR (X, Y1, Y2) */ - item3->add_at_head($1); + item3->add_at_head($1, thd->mem_root); $$ = $3; } else { /* X OR Y */ - $$ = new (thd->mem_root) Item_cond_or($1, $3); + $$= new (thd->mem_root) Item_cond_or(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -7821,7 +8554,7 @@ expr: | expr XOR expr %prec XOR { /* XOR is a proprietary extension */ - $$ = new (thd->mem_root) Item_func_xor($1, $3); + $$= new (thd->mem_root) Item_func_xor(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -7847,7 +8580,7 @@ expr: /* (X1 AND X2) AND Y ==> AND (X1, X2, Y) */ - item1->add($3); + item1->add($3, thd->mem_root); $$ = $1; } } @@ -7857,13 +8590,13 @@ expr: /* X AND (Y1 AND Y2) ==> AND (X, Y1, Y2) */ - item3->add_at_head($1); + item3->add_at_head($1, thd->mem_root); $$ = $3; } else { /* X AND Y */ - $$ = new (thd->mem_root) Item_cond_and($1, $3); + $$= new (thd->mem_root) Item_cond_and(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -7876,37 +8609,37 @@ expr: } | bool_pri IS TRUE_SYM %prec IS { - $$= new (thd->mem_root) Item_func_istrue($1); + $$= new (thd->mem_root) Item_func_istrue(thd, $1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS not TRUE_SYM %prec IS { - $$= new (thd->mem_root) Item_func_isnottrue($1); + $$= new (thd->mem_root) Item_func_isnottrue(thd, $1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS FALSE_SYM %prec IS { - $$= new (thd->mem_root) Item_func_isfalse($1); + $$= new (thd->mem_root) Item_func_isfalse(thd, $1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS not FALSE_SYM %prec IS { - $$= new (thd->mem_root) Item_func_isnotfalse($1); + $$= new (thd->mem_root) Item_func_isnotfalse(thd, $1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS UNKNOWN_SYM %prec IS { - $$= new (thd->mem_root) Item_func_isnull($1); + $$= new (thd->mem_root) Item_func_isnull(thd, $1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS not UNKNOWN_SYM %prec IS { - $$= new (thd->mem_root) Item_func_isnotnull($1); + $$= new (thd->mem_root) Item_func_isnotnull(thd, $1); if ($$ == NULL) MYSQL_YYABORT; } @@ -7916,31 +8649,31 @@ expr: bool_pri: bool_pri IS NULL_SYM %prec IS { - $$= new (thd->mem_root) Item_func_isnull($1); + $$= new (thd->mem_root) Item_func_isnull(thd, $1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS not NULL_SYM %prec IS { - $$= new (thd->mem_root) Item_func_isnotnull($1); + $$= new (thd->mem_root) Item_func_isnotnull(thd, $1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri EQUAL_SYM predicate %prec EQUAL_SYM { - $$= new (thd->mem_root) Item_func_equal($1,$3); + $$= new (thd->mem_root) Item_func_equal(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri comp_op predicate %prec '=' { - $$= (*$2)(0)->create($1,$3); + $$= (*$2)(0)->create(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri comp_op all_or_any '(' subselect ')' %prec '=' { - $$= all_any_subquery_creator($1, $2, $3, $5); + $$= all_any_subquery_creator(thd, $1, $2, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } @@ -7950,13 +8683,13 @@ bool_pri: predicate: bit_expr IN_SYM '(' subselect ')' { - $$= new (thd->mem_root) Item_in_subselect($1, $4); + $$= new (thd->mem_root) Item_in_subselect(thd, $1, $4); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr not IN_SYM '(' subselect ')' { - Item *item= new (thd->mem_root) Item_in_subselect($1, $5); + Item *item= new (thd->mem_root) Item_in_subselect(thd, $1, $5); if (item == NULL) MYSQL_YYABORT; $$= negate_expression(thd, item); @@ -7971,9 +8704,9 @@ predicate: } | bit_expr IN_SYM '(' expr ',' expr_list ')' { - $6->push_front($4); - $6->push_front($1); - $$= new (thd->mem_root) Item_func_in(*$6); + $6->push_front($4, thd->mem_root); + $6->push_front($1, thd->mem_root); + $$= new (thd->mem_root) Item_func_in(thd, *$6); if ($$ == NULL) MYSQL_YYABORT; } @@ -7985,9 +8718,9 @@ predicate: } | bit_expr not IN_SYM '(' expr ',' expr_list ')' { - $7->push_front($5); - $7->push_front($1); - Item_func_in *item = new (thd->mem_root) Item_func_in(*$7); + $7->push_front($5, thd->mem_root); + $7->push_front($1, thd->mem_root); + Item_func_in *item= new (thd->mem_root) Item_func_in(thd, *$7); if (item == NULL) MYSQL_YYABORT; item->negate(); @@ -7995,14 +8728,14 @@ predicate: } | bit_expr BETWEEN_SYM bit_expr AND_SYM predicate { - $$= new (thd->mem_root) Item_func_between($1,$3,$5); + $$= new (thd->mem_root) Item_func_between(thd, $1, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr not BETWEEN_SYM bit_expr AND_SYM predicate { Item_func_between *item; - item= new (thd->mem_root) Item_func_between($1,$4,$6); + item= new (thd->mem_root) Item_func_between(thd, $1, $4, $6); if (item == NULL) MYSQL_YYABORT; item->negate(); @@ -8010,39 +8743,40 @@ predicate: } | bit_expr SOUNDS_SYM LIKE bit_expr { - Item *item1= new (thd->mem_root) Item_func_soundex($1); - Item *item4= new (thd->mem_root) Item_func_soundex($4); + Item *item1= new (thd->mem_root) Item_func_soundex(thd, $1); + Item *item4= new (thd->mem_root) Item_func_soundex(thd, $4); if ((item1 == NULL) || (item4 == NULL)) MYSQL_YYABORT; - $$= new (thd->mem_root) Item_func_eq(item1, item4); + $$= new (thd->mem_root) Item_func_eq(thd, item1, item4); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr LIKE simple_expr opt_escape { - $$= new (thd->mem_root) Item_func_like($1,$3,$4,Lex->escape_used); + $$= new (thd->mem_root) Item_func_like(thd, $1, $3, $4, + Lex->escape_used); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr not LIKE simple_expr opt_escape { - Item *item= new (thd->mem_root) Item_func_like($1,$4,$5, + Item *item= new (thd->mem_root) Item_func_like(thd, $1, $4, $5, Lex->escape_used); if (item == NULL) MYSQL_YYABORT; - $$= new (thd->mem_root) Item_func_not(item); + $$= new (thd->mem_root) Item_func_not(thd, item); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr REGEXP bit_expr { - $$= new (thd->mem_root) Item_func_regex($1,$3); + $$= new (thd->mem_root) Item_func_regex(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr not REGEXP bit_expr { - Item *item= new (thd->mem_root) Item_func_regex($1,$4); + Item *item= new (thd->mem_root) Item_func_regex(thd, $1, $4); if (item == NULL) MYSQL_YYABORT; $$= negate_expression(thd, item); @@ -8055,85 +8789,85 @@ predicate: bit_expr: bit_expr '|' bit_expr %prec '|' { - $$= new (thd->mem_root) Item_func_bit_or($1,$3); + $$= new (thd->mem_root) Item_func_bit_or(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '&' bit_expr %prec '&' { - $$= new (thd->mem_root) Item_func_bit_and($1,$3); + $$= new (thd->mem_root) Item_func_bit_and(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr SHIFT_LEFT bit_expr %prec SHIFT_LEFT { - $$= new (thd->mem_root) Item_func_shift_left($1,$3); + $$= new (thd->mem_root) Item_func_shift_left(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr SHIFT_RIGHT bit_expr %prec SHIFT_RIGHT { - $$= new (thd->mem_root) Item_func_shift_right($1,$3); + $$= new (thd->mem_root) Item_func_shift_right(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '+' bit_expr %prec '+' { - $$= new (thd->mem_root) Item_func_plus($1,$3); + $$= new (thd->mem_root) Item_func_plus(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '-' bit_expr %prec '-' { - $$= new (thd->mem_root) Item_func_minus($1,$3); + $$= new (thd->mem_root) Item_func_minus(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '+' INTERVAL_SYM expr interval %prec '+' { - $$= new (thd->mem_root) Item_date_add_interval($1,$4,$5,0); + $$= new (thd->mem_root) Item_date_add_interval(thd, $1, $4, $5, 0); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '-' INTERVAL_SYM expr interval %prec '-' { - $$= new (thd->mem_root) Item_date_add_interval($1,$4,$5,1); + $$= new (thd->mem_root) Item_date_add_interval(thd, $1, $4, $5, 1); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '*' bit_expr %prec '*' { - $$= new (thd->mem_root) Item_func_mul($1,$3); + $$= new (thd->mem_root) Item_func_mul(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '/' bit_expr %prec '/' { - $$= new (thd->mem_root) Item_func_div($1,$3); + $$= new (thd->mem_root) Item_func_div(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '%' bit_expr %prec '%' { - $$= new (thd->mem_root) Item_func_mod($1,$3); + $$= new (thd->mem_root) Item_func_mod(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr DIV_SYM bit_expr %prec DIV_SYM { - $$= new (thd->mem_root) Item_func_int_div($1,$3); + $$= new (thd->mem_root) Item_func_int_div(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr MOD_SYM bit_expr %prec MOD_SYM { - $$= new (thd->mem_root) Item_func_mod($1,$3); + $$= new (thd->mem_root) Item_func_mod(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '^' bit_expr { - $$= new (thd->mem_root) Item_func_bit_xor($1,$3); + $$= new (thd->mem_root) Item_func_bit_xor(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -8226,7 +8960,9 @@ dyncol_type: $$= DYN_COL_DECIMAL; Lex->charset= NULL; } - | char opt_binary + | char + { Lex->charset= thd->variables.collation_connection; } + opt_binary { LEX *lex= Lex; $$= DYN_COL_STRING; @@ -8272,7 +9008,7 @@ dyncall_create_element: alloc_root(thd->mem_root, sizeof(DYNCALL_CREATE_DEF)); if ($$ == NULL) MYSQL_YYABORT; - $$->num= $1; + $$->key= $1; $$->value= $3; $$->type= (DYNAMIC_COLUMN_TYPE)$4; $$->cs= lex->charset; @@ -8292,11 +9028,11 @@ dyncall_create_list: $$= new (thd->mem_root) List<DYNCALL_CREATE_DEF>; if ($$ == NULL) MYSQL_YYABORT; - $$->push_back($1); + $$->push_back($1, thd->mem_root); } | dyncall_create_list ',' dyncall_create_element { - $1->push_back($3); + $1->push_back($3, thd->mem_root); $$= $1; } ; @@ -8309,22 +9045,22 @@ simple_expr: | function_call_conflict | simple_expr COLLATE_SYM ident_or_text %prec NEG { - Item *i1= new (thd->mem_root) Item_string($3.str, + Item *i1= new (thd->mem_root) Item_string(thd, $3.str, $3.length, thd->charset()); if (i1 == NULL) MYSQL_YYABORT; - $$= new (thd->mem_root) Item_func_set_collation($1, i1); + $$= new (thd->mem_root) Item_func_set_collation(thd, $1, i1); if ($$ == NULL) MYSQL_YYABORT; } | literal - | param_marker + | param_marker { $$= $1; } | variable | sum_expr | simple_expr OR_OR_SYM simple_expr { - $$= new (thd->mem_root) Item_func_concat($1, $3); + $$= new (thd->mem_root) Item_func_concat(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -8334,13 +9070,13 @@ simple_expr: } | '-' simple_expr %prec NEG { - $$= new (thd->mem_root) Item_func_neg($2); + $$= new (thd->mem_root) Item_func_neg(thd, $2); if ($$ == NULL) MYSQL_YYABORT; } | '~' simple_expr %prec NEG { - $$= new (thd->mem_root) Item_func_bit_neg($2); + $$= new (thd->mem_root) Item_func_bit_neg(thd, $2); if ($$ == NULL) MYSQL_YYABORT; } @@ -8352,7 +9088,7 @@ simple_expr: } | '(' subselect ')' { - $$= new (thd->mem_root) Item_singlerow_subselect($2); + $$= new (thd->mem_root) Item_singlerow_subselect(thd, $2); if ($$ == NULL) MYSQL_YYABORT; } @@ -8360,33 +9096,56 @@ simple_expr: { $$= $2; } | '(' expr ',' expr_list ')' { - $4->push_front($2); - $$= new (thd->mem_root) Item_row(*$4); + $4->push_front($2, thd->mem_root); + $$= new (thd->mem_root) Item_row(thd, *$4); if ($$ == NULL) MYSQL_YYABORT; } | ROW_SYM '(' expr ',' expr_list ')' { - $5->push_front($3); - $$= new (thd->mem_root) Item_row(*$5); + $5->push_front($3, thd->mem_root); + $$= new (thd->mem_root) Item_row(thd, *$5); if ($$ == NULL) MYSQL_YYABORT; } | EXISTS '(' subselect ')' { - $$= new (thd->mem_root) Item_exists_subselect($3); + $$= new (thd->mem_root) Item_exists_subselect(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | '{' ident expr '}' - { $$= $3; } + { + $$= NULL; + /* + If "expr" is reasonably short pure ASCII string literal, + try to parse known ODBC style date, time or timestamp literals, + e.g: + SELECT {d'2001-01-01'}; + SELECT {t'10:20:30'}; + SELECT {ts'2001-01-01 10:20:30'}; + */ + if ($3->type() == Item::STRING_ITEM) + { + Item_string *item= (Item_string *) $3; + enum_field_types type= item->odbc_temporal_literal_type(&$2); + if (type != MYSQL_TYPE_STRING) + { + $$= create_temporal_literal(thd, item->val_str(NULL), + type, false); + } + } + if ($$ == NULL) + $$= $3; + } | MATCH ident_list_arg AGAINST '(' bit_expr fulltext_options ')' { - $2->push_front($5); - Item_func_match *i1= new (thd->mem_root) Item_func_match(*$2, $6); + $2->push_front($5, thd->mem_root); + Item_func_match *i1= new (thd->mem_root) Item_func_match(thd, *$2, + $6); if (i1 == NULL) MYSQL_YYABORT; - Select->add_ftfunc_to_list(i1); + Select->add_ftfunc_to_list(thd, i1); $$= i1; } | BINARY simple_expr %prec NEG @@ -8406,7 +9165,7 @@ simple_expr: } | CASE_SYM opt_expr when_list opt_else END { - $$= new (thd->mem_root) Item_func_case(* $3, $2, $4 ); + $$= new (thd->mem_root) Item_func_case(thd, *$3, $2, $4); if ($$ == NULL) MYSQL_YYABORT; } @@ -8419,27 +9178,23 @@ simple_expr: } | CONVERT_SYM '(' expr USING charset_name ')' { - $$= new (thd->mem_root) Item_func_conv_charset($3,$5); + $$= new (thd->mem_root) Item_func_conv_charset(thd, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } | DEFAULT '(' simple_ident ')' { - if ($3->is_splocal()) - { - Item_splocal *il= static_cast<Item_splocal *>($3); - - my_error(ER_WRONG_COLUMN_NAME, MYF(0), il->my_name()->str); - MYSQL_YYABORT; - } - $$= new (thd->mem_root) Item_default_value(Lex->current_context(), + Item_splocal *il= $3->get_item_splocal(); + if (il) + my_yyabort_error((ER_WRONG_COLUMN_NAME, MYF(0), il->my_name()->str)); + $$= new (thd->mem_root) Item_default_value(thd, Lex->current_context(), $3); if ($$ == NULL) MYSQL_YYABORT; } | VALUES '(' simple_ident_nospvar ')' { - $$= new (thd->mem_root) Item_insert_value(Lex->current_context(), + $$= new (thd->mem_root) Item_insert_value(thd, Lex->current_context(), $3); if ($$ == NULL) MYSQL_YYABORT; @@ -8447,7 +9202,7 @@ simple_expr: | INTERVAL_SYM expr interval '+' expr %prec INTERVAL_SYM /* we cannot put interval before - */ { - $$= new (thd->mem_root) Item_date_add_interval($5,$2,$3,0); + $$= new (thd->mem_root) Item_date_add_interval(thd, $5, $2, $3, 0); if ($$ == NULL) MYSQL_YYABORT; } @@ -8462,19 +9217,29 @@ simple_expr: function_call_keyword: CHAR_SYM '(' expr_list ')' { - $$= new (thd->mem_root) Item_func_char(*$3); + $$= new (thd->mem_root) Item_func_char(thd, *$3); if ($$ == NULL) MYSQL_YYABORT; } | CHAR_SYM '(' expr_list USING charset_name ')' { - $$= new (thd->mem_root) Item_func_char(*$3, $5); + $$= new (thd->mem_root) Item_func_char(thd, *$3, $5); if ($$ == NULL) MYSQL_YYABORT; } | CURRENT_USER optional_braces { - $$= new (thd->mem_root) Item_func_current_user(Lex->current_context()); + $$= new (thd->mem_root) Item_func_current_user(thd, + Lex->current_context()); + if ($$ == NULL) + MYSQL_YYABORT; + Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); + Lex->safe_to_cache_query= 0; + } + | CURRENT_ROLE optional_braces + { + $$= new (thd->mem_root) Item_func_current_role(thd, + Lex->current_context()); if ($$ == NULL) MYSQL_YYABORT; Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); @@ -8482,25 +9247,25 @@ function_call_keyword: } | DATE_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_date_typecast($3); + $$= new (thd->mem_root) Item_date_typecast(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | DAY_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_dayofmonth($3); + $$= new (thd->mem_root) Item_func_dayofmonth(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | HOUR_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_hour($3); + $$= new (thd->mem_root) Item_func_hour(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | INSERT '(' expr ',' expr ',' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_insert($3,$5,$7,$9); + $$= new (thd->mem_root) Item_func_insert(thd, $3, $5, $7, $9); if ($$ == NULL) MYSQL_YYABORT; } @@ -8509,125 +9274,127 @@ function_call_keyword: List<Item> *list= new (thd->mem_root) List<Item>; if (list == NULL) MYSQL_YYABORT; - list->push_front($5); - list->push_front($3); - Item_row *item= new (thd->mem_root) Item_row(*list); + list->push_front($5, thd->mem_root); + list->push_front($3, thd->mem_root); + Item_row *item= new (thd->mem_root) Item_row(thd, *list); if (item == NULL) MYSQL_YYABORT; - $$= new (thd->mem_root) Item_func_interval(item); + $$= new (thd->mem_root) Item_func_interval(thd, item); if ($$ == NULL) MYSQL_YYABORT; } | INTERVAL_SYM '(' expr ',' expr ',' expr_list ')' %prec INTERVAL_SYM { - $7->push_front($5); - $7->push_front($3); - Item_row *item= new (thd->mem_root) Item_row(*$7); + $7->push_front($5, thd->mem_root); + $7->push_front($3, thd->mem_root); + Item_row *item= new (thd->mem_root) Item_row(thd, *$7); if (item == NULL) MYSQL_YYABORT; - $$= new (thd->mem_root) Item_func_interval(item); + $$= new (thd->mem_root) Item_func_interval(thd, item); if ($$ == NULL) MYSQL_YYABORT; } | LEFT '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_left($3,$5); + $$= new (thd->mem_root) Item_func_left(thd, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } | MINUTE_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_minute($3); + $$= new (thd->mem_root) Item_func_minute(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | MONTH_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_month($3); + $$= new (thd->mem_root) Item_func_month(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | RIGHT '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_right($3,$5); + $$= new (thd->mem_root) Item_func_right(thd, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } | SECOND_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_second($3); + $$= new (thd->mem_root) Item_func_second(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | TIME_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_time_typecast($3, AUTO_SEC_PART_DIGITS); + $$= new (thd->mem_root) Item_time_typecast(thd, $3, + AUTO_SEC_PART_DIGITS); if ($$ == NULL) MYSQL_YYABORT; } | TIMESTAMP '(' expr ')' { - $$= new (thd->mem_root) Item_datetime_typecast($3, AUTO_SEC_PART_DIGITS); + $$= new (thd->mem_root) Item_datetime_typecast(thd, $3, + AUTO_SEC_PART_DIGITS); if ($$ == NULL) MYSQL_YYABORT; } | TIMESTAMP '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_add_time($3, $5, 1, 0); + $$= new (thd->mem_root) Item_func_add_time(thd, $3, $5, 1, 0); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' expr ')' { - $$= new (thd->mem_root) Item_func_trim($3); + $$= new (thd->mem_root) Item_func_trim(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' LEADING expr FROM expr ')' { - $$= new (thd->mem_root) Item_func_ltrim($6,$4); + $$= new (thd->mem_root) Item_func_ltrim(thd, $6, $4); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' TRAILING expr FROM expr ')' { - $$= new (thd->mem_root) Item_func_rtrim($6,$4); + $$= new (thd->mem_root) Item_func_rtrim(thd, $6, $4); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' BOTH expr FROM expr ')' { - $$= new (thd->mem_root) Item_func_trim($6,$4); + $$= new (thd->mem_root) Item_func_trim(thd, $6, $4); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' LEADING FROM expr ')' { - $$= new (thd->mem_root) Item_func_ltrim($5); + $$= new (thd->mem_root) Item_func_ltrim(thd, $5); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' TRAILING FROM expr ')' { - $$= new (thd->mem_root) Item_func_rtrim($5); + $$= new (thd->mem_root) Item_func_rtrim(thd, $5); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' BOTH FROM expr ')' { - $$= new (thd->mem_root) Item_func_trim($5); + $$= new (thd->mem_root) Item_func_trim(thd, $5); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' expr FROM expr ')' { - $$= new (thd->mem_root) Item_func_trim($5,$3); + $$= new (thd->mem_root) Item_func_trim(thd, $5, $3); if ($$ == NULL) MYSQL_YYABORT; } - | USER '(' ')' + | USER_SYM '(' ')' { - $$= new (thd->mem_root) Item_func_user(); + $$= new (thd->mem_root) Item_func_user(thd); if ($$ == NULL) MYSQL_YYABORT; Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); @@ -8635,7 +9402,7 @@ function_call_keyword: } | YEAR_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_year($3); + $$= new (thd->mem_root) Item_func_year(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -8656,27 +9423,27 @@ function_call_keyword: function_call_nonkeyword: ADDDATE_SYM '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_date_add_interval($3, $5, + $$= new (thd->mem_root) Item_date_add_interval(thd, $3, $5, INTERVAL_DAY, 0); if ($$ == NULL) MYSQL_YYABORT; } | ADDDATE_SYM '(' expr ',' INTERVAL_SYM expr interval ')' { - $$= new (thd->mem_root) Item_date_add_interval($3, $6, $7, 0); + $$= new (thd->mem_root) Item_date_add_interval(thd, $3, $6, $7, 0); if ($$ == NULL) MYSQL_YYABORT; } | CURDATE optional_braces { - $$= new (thd->mem_root) Item_func_curdate_local(); + $$= new (thd->mem_root) Item_func_curdate_local(thd); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | CURTIME opt_time_precision { - $$= new (thd->mem_root) Item_func_curtime_local($2); + $$= new (thd->mem_root) Item_func_curtime_local(thd, $2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; @@ -8684,76 +9451,76 @@ function_call_nonkeyword: | DATE_ADD_INTERVAL '(' expr ',' INTERVAL_SYM expr interval ')' %prec INTERVAL_SYM { - $$= new (thd->mem_root) Item_date_add_interval($3,$6,$7,0); + $$= new (thd->mem_root) Item_date_add_interval(thd, $3, $6, $7, 0); if ($$ == NULL) MYSQL_YYABORT; } | DATE_SUB_INTERVAL '(' expr ',' INTERVAL_SYM expr interval ')' %prec INTERVAL_SYM { - $$= new (thd->mem_root) Item_date_add_interval($3,$6,$7,1); + $$= new (thd->mem_root) Item_date_add_interval(thd, $3, $6, $7, 1); if ($$ == NULL) MYSQL_YYABORT; } | EXTRACT_SYM '(' interval FROM expr ')' { - $$=new (thd->mem_root) Item_extract( $3, $5); + $$=new (thd->mem_root) Item_extract(thd, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } | GET_FORMAT '(' date_time_type ',' expr ')' { - $$= new (thd->mem_root) Item_func_get_format($3, $5); + $$= new (thd->mem_root) Item_func_get_format(thd, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } | NOW_SYM opt_time_precision { - $$= new (thd->mem_root) Item_func_now_local($2); + $$= new (thd->mem_root) Item_func_now_local(thd, $2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | POSITION_SYM '(' bit_expr IN_SYM expr ')' { - $$ = new (thd->mem_root) Item_func_locate($5,$3); + $$= new (thd->mem_root) Item_func_locate(thd, $5, $3); if ($$ == NULL) MYSQL_YYABORT; } | SUBDATE_SYM '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_date_add_interval($3, $5, + $$= new (thd->mem_root) Item_date_add_interval(thd, $3, $5, INTERVAL_DAY, 1); if ($$ == NULL) MYSQL_YYABORT; } | SUBDATE_SYM '(' expr ',' INTERVAL_SYM expr interval ')' { - $$= new (thd->mem_root) Item_date_add_interval($3, $6, $7, 1); + $$= new (thd->mem_root) Item_date_add_interval(thd, $3, $6, $7, 1); if ($$ == NULL) MYSQL_YYABORT; } | SUBSTRING '(' expr ',' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_substr($3,$5,$7); + $$= new (thd->mem_root) Item_func_substr(thd, $3, $5, $7); if ($$ == NULL) MYSQL_YYABORT; } | SUBSTRING '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_substr($3,$5); + $$= new (thd->mem_root) Item_func_substr(thd, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } | SUBSTRING '(' expr FROM expr FOR_SYM expr ')' { - $$= new (thd->mem_root) Item_func_substr($3,$5,$7); + $$= new (thd->mem_root) Item_func_substr(thd, $3, $5, $7); if ($$ == NULL) MYSQL_YYABORT; } | SUBSTRING '(' expr FROM expr ')' { - $$= new (thd->mem_root) Item_func_substr($3,$5); + $$= new (thd->mem_root) Item_func_substr(thd, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } @@ -8768,42 +9535,42 @@ function_call_nonkeyword: */ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); if (global_system_variables.sysdate_is_now == 0) - $$= new (thd->mem_root) Item_func_sysdate_local($2); + $$= new (thd->mem_root) Item_func_sysdate_local(thd, $2); else - $$= new (thd->mem_root) Item_func_now_local($2); + $$= new (thd->mem_root) Item_func_now_local(thd, $2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | TIMESTAMP_ADD '(' interval_time_stamp ',' expr ',' expr ')' { - $$= new (thd->mem_root) Item_date_add_interval($7,$5,$3,0); + $$= new (thd->mem_root) Item_date_add_interval(thd, $7, $5, $3, 0); if ($$ == NULL) MYSQL_YYABORT; } | TIMESTAMP_DIFF '(' interval_time_stamp ',' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_timestamp_diff($5,$7,$3); + $$= new (thd->mem_root) Item_func_timestamp_diff(thd, $5, $7, $3); if ($$ == NULL) MYSQL_YYABORT; } | UTC_DATE_SYM optional_braces { - $$= new (thd->mem_root) Item_func_curdate_utc(); + $$= new (thd->mem_root) Item_func_curdate_utc(thd); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | UTC_TIME_SYM opt_time_precision { - $$= new (thd->mem_root) Item_func_curtime_utc($2); + $$= new (thd->mem_root) Item_func_curtime_utc(thd, $2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | UTC_TIMESTAMP_SYM opt_time_precision { - $$= new (thd->mem_root) Item_func_now_utc($2); + $$= new (thd->mem_root) Item_func_now_utc(thd, $2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; @@ -8823,16 +9590,9 @@ function_call_nonkeyword: MYSQL_YYABORT; } | - COLUMN_EXISTS_SYM '(' expr ',' expr ')' - { - $$= new (thd->mem_root) Item_func_dyncol_exists($3, $5); - if ($$ == NULL) - MYSQL_YYABORT; - } - | - COLUMN_LIST_SYM '(' expr ')' + COLUMN_CHECK_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_dyncol_list($3); + $$= new (thd->mem_root) Item_func_dyncol_check(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -8863,114 +9623,174 @@ function_call_nonkeyword: function_call_conflict: ASCII_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_ascii($3); + $$= new (thd->mem_root) Item_func_ascii(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | CHARSET '(' expr ')' { - $$= new (thd->mem_root) Item_func_charset($3); + $$= new (thd->mem_root) Item_func_charset(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | COALESCE '(' expr_list ')' { - $$= new (thd->mem_root) Item_func_coalesce(* $3); + $$= new (thd->mem_root) Item_func_coalesce(thd, *$3); if ($$ == NULL) MYSQL_YYABORT; } | COLLATION_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_collation($3); + $$= new (thd->mem_root) Item_func_collation(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | DATABASE '(' ')' { - $$= new (thd->mem_root) Item_func_database(); + $$= new (thd->mem_root) Item_func_database(thd); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } - | IF '(' expr ',' expr ',' expr ')' + | IF_SYM '(' expr ',' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_if($3,$5,$7); + $$= new (thd->mem_root) Item_func_if(thd, $3, $5, $7); + if ($$ == NULL) + MYSQL_YYABORT; + } + | FORMAT_SYM '(' expr ',' expr ')' + { + $$= new (thd->mem_root) Item_func_format(thd, $3, $5); + if ($$ == NULL) + MYSQL_YYABORT; + } + | FORMAT_SYM '(' expr ',' expr ',' expr ')' + { + $$= new (thd->mem_root) Item_func_format(thd, $3, $5, $7); if ($$ == NULL) MYSQL_YYABORT; } | LAST_VALUE '(' expr_list ')' { - $$= new (thd->mem_root) Item_func_last_value(* $3); + $$= new (thd->mem_root) Item_func_last_value(thd, *$3); if ($$ == NULL) MYSQL_YYABORT; } | MICROSECOND_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_microsecond($3); + $$= new (thd->mem_root) Item_func_microsecond(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | MOD_SYM '(' expr ',' expr ')' { - $$ = new (thd->mem_root) Item_func_mod($3, $5); + $$= new (thd->mem_root) Item_func_mod(thd, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } - | OLD_PASSWORD '(' expr ')' + | OLD_PASSWORD_SYM '(' expr ')' { - $$= new (thd->mem_root) Item_func_old_password($3); + $$= new (thd->mem_root) + Item_func_password(thd, $3, Item_func_password::OLD); if ($$ == NULL) MYSQL_YYABORT; } - | PASSWORD '(' expr ')' + | PASSWORD_SYM '(' expr ')' { Item* i1; - if (thd->variables.old_passwords) - i1= new (thd->mem_root) Item_func_old_password($3); - else - i1= new (thd->mem_root) Item_func_password($3); + i1= new (thd->mem_root) Item_func_password(thd, $3); if (i1 == NULL) MYSQL_YYABORT; $$= i1; } | QUARTER_SYM '(' expr ')' { - $$ = new (thd->mem_root) Item_func_quarter($3); + $$= new (thd->mem_root) Item_func_quarter(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | REPEAT_SYM '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_repeat($3,$5); + $$= new (thd->mem_root) Item_func_repeat(thd, $3, $5); if ($$ == NULL) MYSQL_YYABORT; } | REPLACE '(' expr ',' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_replace($3,$5,$7); + $$= new (thd->mem_root) Item_func_replace(thd, $3, $5, $7); + if ($$ == NULL) + MYSQL_YYABORT; + } + | REVERSE_SYM '(' expr ')' + { + $$= new (thd->mem_root) Item_func_reverse(thd, $3); + if ($$ == NULL) + MYSQL_YYABORT; + } + | ROW_COUNT_SYM '(' ')' + { + $$= new (thd->mem_root) Item_func_row_count(thd); if ($$ == NULL) MYSQL_YYABORT; + Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); + Lex->safe_to_cache_query= 0; } | TRUNCATE_SYM '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_round($3,$5,1); + $$= new (thd->mem_root) Item_func_round(thd, $3, $5, 1); if ($$ == NULL) MYSQL_YYABORT; } | WEEK_SYM '(' expr ')' { - Item *i1= new (thd->mem_root) Item_int((char*) "0", - thd->variables.default_week_format, - 1); - if (i1 == NULL) + Item *i1; + LEX_STRING name= {C_STRING_WITH_LEN("default_week_format")}; + if (!(i1= get_system_var(thd, OPT_SESSION, + name, null_lex_str))) MYSQL_YYABORT; - $$= new (thd->mem_root) Item_func_week($3, i1); + i1->set_name((const char *) + STRING_WITH_LEN("@@default_week_format"), + system_charset_info); + $$= new (thd->mem_root) Item_func_week(thd, $3, i1); if ($$ == NULL) MYSQL_YYABORT; } | WEEK_SYM '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_week($3,$5); + $$= new (thd->mem_root) Item_func_week(thd, $3, $5); + if ($$ == NULL) + MYSQL_YYABORT; + } + | WEIGHT_STRING_SYM '(' expr opt_ws_levels ')' + { + $$= new (thd->mem_root) Item_func_weight_string(thd, $3, 0, 0, $4); + if ($$ == NULL) + MYSQL_YYABORT; + } + | WEIGHT_STRING_SYM '(' expr AS CHAR_SYM ws_nweights opt_ws_levels ')' + { + $$= new (thd->mem_root) + Item_func_weight_string(thd, $3, 0, $6, + $7 | MY_STRXFRM_PAD_WITH_SPACE); + if ($$ == NULL) + MYSQL_YYABORT; + } + | WEIGHT_STRING_SYM '(' expr AS BINARY ws_nweights ')' + { + Item *item= new (thd->mem_root) Item_char_typecast(thd, $3, $6, + &my_charset_bin); + if (item == NULL) + MYSQL_YYABORT; + $$= new (thd->mem_root) + Item_func_weight_string(thd, item, 0, $6, + MY_STRXFRM_PAD_WITH_SPACE); + if ($$ == NULL) + MYSQL_YYABORT; + } + | WEIGHT_STRING_SYM '(' expr ',' ulong_num ',' ulong_num ',' ulong_num ')' + { + $$= new (thd->mem_root) Item_func_weight_string(thd, $3, $5, $7, + $9); if ($$ == NULL) MYSQL_YYABORT; } @@ -8982,9 +9802,8 @@ function_call_conflict: if ($$ == NULL) MYSQL_YYABORT; #else - my_error(ER_FEATURE_DISABLED, MYF(0), - sym_group_geom.name, sym_group_geom.needed_define); - MYSQL_YYABORT; + my_yyabort_error((ER_FEATURE_DISABLED, MYF(0), sym_group_geom.name, + sym_group_geom.needed_define)); #endif } ; @@ -8993,52 +9812,52 @@ geometry_function: CONTAINS_SYM '(' expr ',' expr ')' { $$= GEOM_NEW(thd, - Item_func_spatial_rel($3, $5, - Item_func::SP_CONTAINS_FUNC)); + Item_func_spatial_precise_rel(thd, $3, $5, + Item_func::SP_CONTAINS_FUNC)); } | GEOMETRYCOLLECTION '(' expr_list ')' { $$= GEOM_NEW(thd, - Item_func_spatial_collection(* $3, + Item_func_spatial_collection(thd, *$3, Geometry::wkb_geometrycollection, Geometry::wkb_point)); } | LINESTRING '(' expr_list ')' { $$= GEOM_NEW(thd, - Item_func_spatial_collection(* $3, + Item_func_spatial_collection(thd, *$3, Geometry::wkb_linestring, Geometry::wkb_point)); } | MULTILINESTRING '(' expr_list ')' { $$= GEOM_NEW(thd, - Item_func_spatial_collection(* $3, + Item_func_spatial_collection(thd, *$3, Geometry::wkb_multilinestring, Geometry::wkb_linestring)); } | MULTIPOINT '(' expr_list ')' { $$= GEOM_NEW(thd, - Item_func_spatial_collection(* $3, + Item_func_spatial_collection(thd, *$3, Geometry::wkb_multipoint, Geometry::wkb_point)); } | MULTIPOLYGON '(' expr_list ')' { $$= GEOM_NEW(thd, - Item_func_spatial_collection(* $3, + Item_func_spatial_collection(thd, *$3, Geometry::wkb_multipolygon, Geometry::wkb_polygon)); } | POINT_SYM '(' expr ',' expr ')' { - $$= GEOM_NEW(thd, Item_func_point($3,$5)); + $$= GEOM_NEW(thd, Item_func_point(thd, $3, $5)); } | POLYGON '(' expr_list ')' { $$= GEOM_NEW(thd, - Item_func_spatial_collection(* $3, + Item_func_spatial_collection(thd, *$3, Geometry::wkb_polygon, Geometry::wkb_linestring)); } @@ -9065,7 +9884,7 @@ function_call_generic: { if (lex->current_select->inc_in_sum_expr()) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } } @@ -9146,10 +9965,7 @@ function_call_generic: */ if (!$1.str || check_db_name(&$1)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_DB_NAME, MYF(0), $1.str)); if (check_routine_name(&$3)) { MYSQL_YYABORT; @@ -9194,11 +10010,11 @@ udf_expr_list: $$= new (thd->mem_root) List<Item>; if ($$ == NULL) MYSQL_YYABORT; - $$->push_back($1); + $$->push_back($1, thd->mem_root); } | udf_expr_list ',' udf_expr { - $1->push_back($3); + $1->push_back($3, thd->mem_root); $$= $1; } ; @@ -9233,46 +10049,46 @@ udf_expr: sum_expr: AVG_SYM '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_avg($3, FALSE); + $$= new (thd->mem_root) Item_sum_avg(thd, $3, FALSE); if ($$ == NULL) MYSQL_YYABORT; } | AVG_SYM '(' DISTINCT in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_avg($4, TRUE); + $$= new (thd->mem_root) Item_sum_avg(thd, $4, TRUE); if ($$ == NULL) MYSQL_YYABORT; } | BIT_AND '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_and($3); + $$= new (thd->mem_root) Item_sum_and(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | BIT_OR '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_or($3); + $$= new (thd->mem_root) Item_sum_or(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | BIT_XOR '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_xor($3); + $$= new (thd->mem_root) Item_sum_xor(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | COUNT_SYM '(' opt_all '*' ')' { - Item *item= new (thd->mem_root) Item_int((int32) 0L,1); + Item *item= new (thd->mem_root) Item_int(thd, (int32) 0L, 1); if (item == NULL) MYSQL_YYABORT; - $$= new (thd->mem_root) Item_sum_count(item); + $$= new (thd->mem_root) Item_sum_count(thd, item); if ($$ == NULL) MYSQL_YYABORT; } | COUNT_SYM '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_count($3); + $$= new (thd->mem_root) Item_sum_count(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -9282,13 +10098,13 @@ sum_expr: { Select->in_sum_expr--; } ')' { - $$= new (thd->mem_root) Item_sum_count(* $5); + $$= new (thd->mem_root) Item_sum_count(thd, *$5); if ($$ == NULL) MYSQL_YYABORT; } | MIN_SYM '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_min($3); + $$= new (thd->mem_root) Item_sum_min(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -9299,55 +10115,55 @@ sum_expr: */ | MIN_SYM '(' DISTINCT in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_min($4); + $$= new (thd->mem_root) Item_sum_min(thd, $4); if ($$ == NULL) MYSQL_YYABORT; } | MAX_SYM '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_max($3); + $$= new (thd->mem_root) Item_sum_max(thd, $3); if ($$ == NULL) MYSQL_YYABORT; } | MAX_SYM '(' DISTINCT in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_max($4); + $$= new (thd->mem_root) Item_sum_max(thd, $4); if ($$ == NULL) MYSQL_YYABORT; } | STD_SYM '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_std($3, 0); + $$= new (thd->mem_root) Item_sum_std(thd, $3, 0); if ($$ == NULL) MYSQL_YYABORT; } | VARIANCE_SYM '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_variance($3, 0); + $$= new (thd->mem_root) Item_sum_variance(thd, $3, 0); if ($$ == NULL) MYSQL_YYABORT; } | STDDEV_SAMP_SYM '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_std($3, 1); + $$= new (thd->mem_root) Item_sum_std(thd, $3, 1); if ($$ == NULL) MYSQL_YYABORT; } | VAR_SAMP_SYM '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_variance($3, 1); + $$= new (thd->mem_root) Item_sum_variance(thd, $3, 1); if ($$ == NULL) MYSQL_YYABORT; } | SUM_SYM '(' in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_sum($3, FALSE); + $$= new (thd->mem_root) Item_sum_sum(thd, $3, FALSE); if ($$ == NULL) MYSQL_YYABORT; } | SUM_SYM '(' DISTINCT in_sum_expr ')' { - $$= new (thd->mem_root) Item_sum_sum($4, TRUE); + $$= new (thd->mem_root) Item_sum_sum(thd, $4, TRUE); if ($$ == NULL) MYSQL_YYABORT; } @@ -9360,7 +10176,7 @@ sum_expr: SELECT_LEX *sel= Select; sel->in_sum_expr--; $$= new (thd->mem_root) - Item_func_group_concat(Lex->current_context(), $3, $5, + Item_func_group_concat(thd, Lex->current_context(), $3, $5, sel->gorder_list, $7); if ($$ == NULL) MYSQL_YYABORT; @@ -9373,10 +10189,7 @@ variable: '@' { if (! Lex->parsing_options.allows_variable) - { - my_error(ER_VIEW_SELECT_VARIABLE, MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_VIEW_SELECT_VARIABLE, MYF(0))); } variable_aux { @@ -9388,16 +10201,16 @@ variable_aux: ident_or_text SET_VAR expr { Item_func_set_user_var *item; - $$= item= new (thd->mem_root) Item_func_set_user_var($1, $3); + $$= item= new (thd->mem_root) Item_func_set_user_var(thd, $1, $3); if ($$ == NULL) MYSQL_YYABORT; LEX *lex= Lex; lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - lex->set_var_list.push_back(item); + lex->set_var_list.push_back(item, thd->mem_root); } | ident_or_text { - $$= new (thd->mem_root) Item_func_get_user_var($1); + $$= new (thd->mem_root) Item_func_get_user_var(thd, $1); if ($$ == NULL) MYSQL_YYABORT; LEX *lex= Lex; @@ -9408,7 +10221,7 @@ variable_aux: /* disallow "SELECT @@global.global.variable" */ if ($3.str && $4.str && check_reserved_words(&$3)) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } if (!($$= get_system_var(thd, $2, $3, $4))) @@ -9443,9 +10256,8 @@ opt_gorder_clause: sel->olap != UNSPECIFIED_OLAP_TYPE && (sel->linkage != UNION_TYPE || sel->braces)) { - my_error(ER_WRONG_USAGE, MYF(0), - "CUBE/ROLLUP", "ORDER BY"); - MYSQL_YYABORT; + my_yyabort_error((ER_WRONG_USAGE, MYF(0), + "CUBE/ROLLUP", "ORDER BY")); } } gorder_list; @@ -9464,7 +10276,7 @@ in_sum_expr: LEX *lex= Lex; if (lex->current_select->inc_in_sum_expr()) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } } @@ -9478,7 +10290,9 @@ in_sum_expr: cast_type: BINARY opt_field_length { $$=ITEM_CAST_CHAR; Lex->charset= &my_charset_bin; Lex->dec= 0; } - | CHAR_SYM opt_field_length opt_binary + | CHAR_SYM opt_field_length + { Lex->charset= thd->variables.collation_connection; } + opt_binary { $$=ITEM_CAST_CHAR; Lex->dec= 0; } | NCHAR_SYM opt_field_length { $$=ITEM_CAST_CHAR; Lex->charset= national_charset_info; Lex->dec=0; } @@ -9524,11 +10338,11 @@ expr_list: $$= new (thd->mem_root) List<Item>; if ($$ == NULL) MYSQL_YYABORT; - $$->push_back($1); + $$->push_back($1, thd->mem_root); } | expr_list ',' expr { - $1->push_back($3); + $1->push_back($3, thd->mem_root); $$= $1; } ; @@ -9544,11 +10358,11 @@ ident_list: $$= new (thd->mem_root) List<Item>; if ($$ == NULL) MYSQL_YYABORT; - $$->push_back($1); + $$->push_back($1, thd->mem_root); } | ident_list ',' simple_ident { - $1->push_back($3); + $1->push_back($3, thd->mem_root); $$= $1; } ; @@ -9566,16 +10380,16 @@ opt_else: when_list: WHEN_SYM expr THEN_SYM expr { - $$= new List<Item>; + $$= new (thd->mem_root) List<Item>; if ($$ == NULL) MYSQL_YYABORT; - $$->push_back($2); - $$->push_back($4); + $$->push_back($2, thd->mem_root); + $$->push_back($4, thd->mem_root); } | when_list WHEN_SYM expr THEN_SYM expr { - $1->push_back($3); - $1->push_back($5); + $1->push_back($3, thd->mem_root); + $1->push_back($5, thd->mem_root); $$= $1; } ; @@ -9587,9 +10401,9 @@ table_ref: | join_table { LEX *lex= Lex; - if (!($$= lex->current_select->nest_last_join(lex->thd))) + if (!($$= lex->current_select->nest_last_join(thd))) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } } @@ -9636,9 +10450,7 @@ join_table: left-associative joins. */ table_ref normal_join table_ref %prec TABLE_REF_PRIORITY - { MYSQL_YYABORT_UNLESS($1 && ($$=$3)); } - | table_ref STRAIGHT_JOIN table_factor - { MYSQL_YYABORT_UNLESS($1 && ($$=$3)); $3->straight=1; } + { MYSQL_YYABORT_UNLESS($1 && ($$=$3)); $3->straight=$2; } | table_ref normal_join table_ref ON { @@ -9650,23 +10462,8 @@ join_table: } expr { - add_join_on($3,$6); - Lex->pop_context(); - Select->parsing_place= NO_MATTER; - } - | table_ref STRAIGHT_JOIN table_factor - ON - { - MYSQL_YYABORT_UNLESS($1 && $3); - /* Change the current name resolution context to a local context. */ - if (push_new_name_resolution_context(thd, $1, $3)) - MYSQL_YYABORT; - Select->parsing_place= IN_ON; - } - expr - { - $3->straight=1; - add_join_on($3,$6); + $3->straight=$2; + add_join_on(thd, $3, $6); Lex->pop_context(); Select->parsing_place= NO_MATTER; } @@ -9676,10 +10473,15 @@ join_table: MYSQL_YYABORT_UNLESS($1 && $3); } '(' using_list ')' - { add_join_natural($1,$3,$7,Select); $$=$3; } - | table_ref NATURAL JOIN_SYM table_factor + { + $3->straight=$2; + add_join_natural($1,$3,$7,Select); + $$=$3; + } + | table_ref NATURAL inner_join table_factor { MYSQL_YYABORT_UNLESS($1 && ($$=$4)); + $4->straight=$3; add_join_natural($1,$4,NULL,Select); } @@ -9695,7 +10497,7 @@ join_table: } expr { - add_join_on($5,$8); + add_join_on(thd, $5, $8); Lex->pop_context(); $5->outer_join|=JOIN_TYPE_LEFT; $$=$5; @@ -9734,7 +10536,7 @@ join_table: LEX *lex= Lex; if (!($$= lex->current_select->convert_right_join())) MYSQL_YYABORT; - add_join_on($$, $8); + add_join_on(thd, $$, $8); Lex->pop_context(); Select->parsing_place= NO_MATTER; } @@ -9759,12 +10561,34 @@ join_table: } ; + +inner_join: /* $$ set if using STRAIGHT_JOIN, false otherwise */ + JOIN_SYM { $$ = 0; } + | INNER_SYM JOIN_SYM { $$ = 0; } + | STRAIGHT_JOIN { $$ = 1; } + ; + normal_join: - JOIN_SYM {} - | INNER_SYM JOIN_SYM {} - | CROSS JOIN_SYM {} + inner_join { $$ = $1; } + | CROSS JOIN_SYM { $$ = 0; } ; +/* + table PARTITION (list of partitions), reusing using_list instead of creating + a new rule for partition_list. +*/ +opt_use_partition: + /* empty */ { $$= 0;} + | use_partition + ; + +use_partition: + PARTITION_SYM '(' using_list ')' have_partitioning + { + $$= $3; + } + ; + /* This is a flattening of the rules <table factor> and <table primary> in the SQL:2003 standard, since we don't have <sample clause> @@ -9778,13 +10602,14 @@ table_factor: SELECT_LEX *sel= Select; sel->table_join_options= 0; } - table_ident opt_table_alias opt_key_definition + table_ident opt_use_partition opt_table_alias opt_key_definition { - if (!($$= Select->add_table_to_list(thd, $2, $3, + if (!($$= Select->add_table_to_list(thd, $2, $4, Select->get_table_join_options(), YYPS->m_lock_type, YYPS->m_mdl_type, - Select->pop_index_hints()))) + Select->pop_index_hints(), + $3))) MYSQL_YYABORT; Select->add_joined_table($$); } @@ -9796,15 +10621,11 @@ table_factor: { if (sel->set_braces(1)) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } - /* select in braces, can't contain global parameters */ - if (sel->master_unit()->fake_select_lex) - sel->master_unit()->global_parameters= - sel->master_unit()->fake_select_lex; } - if ($2->init_nested_join(lex->thd)) + if ($2->init_nested_join(thd)) MYSQL_YYABORT; $$= 0; /* incomplete derived tables return NULL, we must be @@ -9850,11 +10671,11 @@ table_factor: SELECT_LEX *sel= lex->current_select; SELECT_LEX_UNIT *unit= sel->master_unit(); lex->current_select= sel= unit->outer_select(); - Table_ident *ti= new Table_ident(unit); + Table_ident *ti= new (thd->mem_root) Table_ident(unit); if (ti == NULL) MYSQL_YYABORT; - if (!($$= sel->add_table_to_list(lex->thd, - new Table_ident(unit), $5, 0, + if (!($$= sel->add_table_to_list(thd, + ti, $5, 0, TL_READ, MDL_SHARED_READ))) MYSQL_YYABORT; @@ -9872,7 +10693,7 @@ table_factor: Tables with or without joins within parentheses cannot have aliases, and we ruled out derived tables above. */ - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } else @@ -9917,7 +10738,7 @@ select_derived_union: { if ($1 && $2) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } } @@ -9935,12 +10756,10 @@ select_derived_union: last select in the union. */ Lex->pop_context(); - } - opt_union_order_or_limit - { + if ($1 != NULL) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } } @@ -9951,16 +10770,9 @@ select_init2_derived: select_part2_derived { LEX *lex= Lex; - SELECT_LEX * sel= lex->current_select; if (lex->current_select->set_braces(0)) { - my_parse_error(ER(ER_SYNTAX_ERROR)); - MYSQL_YYABORT; - } - if (sel->linkage == UNION_TYPE && - sel->master_unit()->first_select()->braces) - { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } } @@ -9979,28 +10791,25 @@ select_part2_derived: { Select->parsing_place= NO_MATTER; } - opt_select_from select_lock_type ; /* handle contents of parentheses in join expression */ select_derived: get_select_lex { - LEX *lex= Lex; - if ($1->init_nested_join(lex->thd)) + if ($1->init_nested_join(thd)) MYSQL_YYABORT; } derived_table_list { - LEX *lex= Lex; /* for normal joins, $3 != NULL and end_nested_join() != NULL, for derived tables, both must equal NULL */ - if (!($$= $1->end_nested_join(lex->thd)) && $3) + if (!($$= $1->end_nested_join(thd)) && $3) MYSQL_YYABORT; if (!$3 && $$) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } } @@ -10013,7 +10822,7 @@ select_derived2: if (!lex->expr_allows_subselect || lex->sql_command == (int)SQLCOM_PURGE) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE || @@ -10027,7 +10836,7 @@ select_derived2: { Select->parsing_place= NO_MATTER; } - opt_select_from + table_expression ; get_select_lex: @@ -10040,17 +10849,14 @@ select_derived_init: LEX *lex= Lex; if (! lex->parsing_options.allows_derived) - { - my_error(ER_VIEW_SELECT_DERIVED, MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_VIEW_SELECT_DERIVED, MYF(0))); SELECT_LEX *sel= lex->current_select; TABLE_LIST *embedding; - if (!sel->embedding || sel->end_nested_join(lex->thd)) + if (!sel->embedding || sel->end_nested_join(thd)) { /* we are not in parentheses */ - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } embedding= Select->embedding; @@ -10128,14 +10934,14 @@ key_usage_list: using_list: ident { - if (!($$= new List<String>)) + if (!($$= new (thd->mem_root) List<String>)) MYSQL_YYABORT; String *s= new (thd->mem_root) String((const char *) $1.str, $1.length, system_charset_info); if (s == NULL) MYSQL_YYABORT; - $$->push_back(s); + $$->push_back(s, thd->mem_root); } | using_list ',' ident { @@ -10144,7 +10950,7 @@ using_list: system_charset_info); if (s == NULL) MYSQL_YYABORT; - $1->push_back(s); + $1->push_back(s, thd->mem_root); $$= $1; } ; @@ -10193,7 +10999,7 @@ opt_table_alias: /* empty */ { $$=0; } | table_alias ident { - $$= (LEX_STRING*) sql_memdup(&$2,sizeof(LEX_STRING)); + $$= (LEX_STRING*) thd->memdup(&$2,sizeof(LEX_STRING)); if ($$ == NULL) MYSQL_YYABORT; } @@ -10204,7 +11010,7 @@ opt_all: | ALL ; -where_clause: +opt_where_clause: /* empty */ { Select->where= 0; } | WHERE { @@ -10213,14 +11019,14 @@ where_clause: expr { SELECT_LEX *select= Select; - select->where= normalize_cond($3); + select->where= normalize_cond(thd, $3); select->parsing_place= NO_MATTER; if ($3) $3->top_level_item(); } ; -having_clause: +opt_having_clause: /* empty */ | HAVING { @@ -10229,7 +11035,7 @@ having_clause: expr { SELECT_LEX *sel= Select; - sel->having= normalize_cond($3); + sel->having= normalize_cond(thd, $3); sel->parsing_place= NO_MATTER; if ($3) $3->top_level_item(); @@ -10246,8 +11052,8 @@ opt_escape: { Lex->escape_used= FALSE; $$= ((thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) ? - new (thd->mem_root) Item_string("", 0, &my_charset_latin1) : - new (thd->mem_root) Item_string("\\", 1, &my_charset_latin1)); + new (thd->mem_root) Item_string_ascii(thd, "", 0) : + new (thd->mem_root) Item_string_ascii(thd, "\\", 1)); if ($$ == NULL) MYSQL_YYABORT; } @@ -10257,7 +11063,7 @@ opt_escape: group by statement in select */ -group_clause: +opt_group_clause: /* empty */ | GROUP_SYM BY group_list olap_opt ; @@ -10282,14 +11088,11 @@ olap_opt: */ LEX *lex=Lex; if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE) - { - my_error(ER_WRONG_USAGE, MYF(0), "WITH CUBE", - "global union parameters"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "WITH CUBE", + "global union parameters")); lex->current_select->olap= CUBE_TYPE; - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "CUBE"); - MYSQL_YYABORT; + + my_yyabort_error((ER_NOT_SUPPORTED_YET, MYF(0), "CUBE")); } | WITH_ROLLUP_SYM { @@ -10302,11 +11105,8 @@ olap_opt: */ LEX *lex= Lex; if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE) - { - my_error(ER_WRONG_USAGE, MYF(0), "WITH ROLLUP", - "global union parameters"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "WITH ROLLUP", + "global union parameters")); lex->current_select->olap= ROLLUP_TYPE; } ; @@ -10356,7 +11156,8 @@ order_clause: "CUBE/ROLLUP", "ORDER BY"); MYSQL_YYABORT; } - if (lex->sql_command != SQLCOM_ALTER_TABLE && !unit->fake_select_lex) + if (lex->sql_command != SQLCOM_ALTER_TABLE && + !unit->fake_select_lex) { /* A query of the of the form (SELECT ...) ORDER BY order_list is @@ -10370,12 +11171,26 @@ order_clause: if (!unit->is_union() && (first_sl->order_list.elements || first_sl->select_limit) && - unit->add_fake_select_lex(lex->thd)) + unit->add_fake_select_lex(thd)) MYSQL_YYABORT; } + if (sel->master_unit()->is_union() && !sel->braces) + { + /* + At this point we don't know yet whether this is the last + select in union or not, but we move ORDER BY to + fake_select_lex anyway. If there would be one more select + in union mysql_new_select will correctly throw error. + */ + DBUG_ASSERT(sel->master_unit()->fake_select_lex); + lex->current_select= sel->master_unit()->fake_select_lex; + } } order_list - ; + { + + } + ; order_list: order_list ',' order_ident order_dir @@ -10390,33 +11205,38 @@ order_dir: | DESC { $$ =0; } ; -opt_limit_clause_init: - /* empty */ - { - LEX *lex= Lex; - SELECT_LEX *sel= lex->current_select; - sel->offset_limit= 0; - sel->select_limit= 0; - lex->limit_rows_examined= 0; - } - | limit_clause {} - ; - opt_limit_clause: /* empty */ {} | limit_clause {} ; +limit_clause_init: + LIMIT + { + SELECT_LEX *sel= Select; + if (sel->master_unit()->is_union() && !sel->braces) + { + /* Move LIMIT that belongs to UNION to fake_select_lex */ + Lex->current_select= sel->master_unit()->fake_select_lex; + DBUG_ASSERT(Select); + } + } + ; + limit_clause: - LIMIT limit_options + limit_clause_init limit_options { - Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT); + SELECT_LEX *sel= Select; + if (!sel->select_limit->basic_const_item() || + sel->select_limit->val_int() > 0) + Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT); } - | LIMIT limit_options ROWS_SYM EXAMINED_SYM limit_rows_option + | limit_clause_init limit_options + ROWS_SYM EXAMINED_SYM limit_rows_option { Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT); } - | LIMIT ROWS_SYM EXAMINED_SYM limit_rows_option + | limit_clause_init ROWS_SYM EXAMINED_SYM limit_rows_option { Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT); } @@ -10452,12 +11272,12 @@ limit_option: Item_splocal *splocal; LEX *lex= thd->lex; Lex_input_stream *lip= & thd->m_parser_state->m_lip; - sp_variable_t *spv; + sp_variable *spv; sp_pcontext *spc = lex->spcont; - if (spc && (spv = spc->find_variable(&$1))) + if (spc && (spv = spc->find_variable($1, false))) { splocal= new (thd->mem_root) - Item_splocal($1, spv->offset, spv->type, + Item_splocal(thd, $1, spv->offset, spv->type, lip->get_tok_start() - lex->sphead->m_tmp_query, lip->get_ptr() - lip->get_tok_start()); if (splocal == NULL) @@ -10468,37 +11288,31 @@ limit_option: lex->safe_to_cache_query=0; } else - { - my_error(ER_SP_UNDECLARED_VAR, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_UNDECLARED_VAR, MYF(0), $1.str)); if (splocal->type() != Item::INT_ITEM) - { - my_error(ER_WRONG_SPVAR_TYPE_IN_LIMIT, MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_SPVAR_TYPE_IN_LIMIT, MYF(0))); splocal->limit_clause_param= TRUE; $$= splocal; } | param_marker { - ((Item_param *) $1)->limit_clause_param= TRUE; + $1->limit_clause_param= TRUE; } | ULONGLONG_NUM { - $$= new (thd->mem_root) Item_uint($1.str, $1.length); + $$= new (thd->mem_root) Item_uint(thd, $1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | LONG_NUM { - $$= new (thd->mem_root) Item_uint($1.str, $1.length); + $$= new (thd->mem_root) Item_uint(thd, $1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | NUM { - $$= new (thd->mem_root) Item_uint($1.str, $1.length); + $$= new (thd->mem_root) Item_uint(thd, $1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } @@ -10524,8 +11338,8 @@ delete_limit_clause: Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_LIMIT); sel->explicit_limit= 1; } - | LIMIT ROWS_SYM EXAMINED_SYM { my_parse_error(ER(ER_SYNTAX_ERROR)); MYSQL_YYABORT; } - | LIMIT limit_option ROWS_SYM EXAMINED_SYM { my_parse_error(ER(ER_SYNTAX_ERROR)); MYSQL_YYABORT; } + | LIMIT ROWS_SYM EXAMINED_SYM { my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } + | LIMIT limit_option ROWS_SYM EXAMINED_SYM { my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } ; int_num: @@ -10569,7 +11383,7 @@ real_ulonglong_num: dec_num_error: dec_num - { my_parse_error(ER(ER_ONLY_INTEGERS_ALLOWED)); } + { my_parse_error(thd, ER_ONLY_INTEGERS_ALLOWED); } ; dec_num: @@ -10582,32 +11396,26 @@ choice: | DEFAULT { $$= HA_CHOICE_UNDEF; } ; -procedure_clause: - /* empty */ +opt_procedure_clause: + /* empty */ { $$= false; } | PROCEDURE_SYM ident /* Procedure name */ { LEX *lex=Lex; if (! lex->parsing_options.allows_select_procedure) - { - my_error(ER_VIEW_SELECT_CLAUSE, MYF(0), "PROCEDURE"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_VIEW_SELECT_CLAUSE, MYF(0), "PROCEDURE")); if (&lex->select_lex != lex->current_select) - { - my_error(ER_WRONG_USAGE, MYF(0), "PROCEDURE", "subquery"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "PROCEDURE", "subquery")); lex->proc_list.elements=0; lex->proc_list.first=0; lex->proc_list.next= &lex->proc_list.first; Item_field *item= new (thd->mem_root) - Item_field(&lex->current_select->context, + Item_field(thd, &lex->current_select->context, NULL, NULL, $2.str); if (item == NULL) MYSQL_YYABORT; - if (add_proc_to_list(lex->thd, item)) + if (add_proc_to_list(thd, item)) MYSQL_YYABORT; Lex->uncacheable(UNCACHEABLE_SIDEEFFECT); @@ -10623,6 +11431,7 @@ procedure_clause: { /* Subqueries are allowed from now.*/ Lex->expr_allows_subselect= true; + $$= true; } ; @@ -10649,7 +11458,8 @@ procedure_item: select_var_list_init: { LEX *lex=Lex; - if (!lex->describe && (!(lex->result= new select_dumpvar()))) + if (!lex->describe && + (!(lex->result= new (thd->mem_root) select_dumpvar(thd)))) MYSQL_YYABORT; } select_var_list @@ -10661,16 +11471,13 @@ select_var_list: | select_var_ident {} ; -select_var_ident: - '@' ident_or_text +select_var_ident: select_outvar { - LEX *lex=Lex; - if (lex->result) + if (Lex->result) { - my_var *var= new my_var($2,0,0,(enum_field_types)0); - if (var == NULL) + if ($1 == NULL) MYSQL_YYABORT; - ((select_dumpvar *)lex->result)->var_list.push_back(var); + ((select_dumpvar *)Lex->result)->var_list.push_back($1, thd->mem_root); } else { @@ -10678,48 +11485,39 @@ select_var_ident: The parser won't create select_result instance only if it's an EXPLAIN. */ - DBUG_ASSERT(lex->describe); + DBUG_ASSERT(Lex->describe); } } + ; + +select_outvar: + '@' ident_or_text + { + $$ = Lex->result ? new (thd->mem_root) my_var_user($2) : NULL; + } | ident_or_text { - LEX *lex=Lex; - sp_variable_t *t; + sp_variable *t; - if (!lex->spcont || !(t=lex->spcont->find_variable(&$1))) - { - my_error(ER_SP_UNDECLARED_VAR, MYF(0), $1.str); - MYSQL_YYABORT; - } - if (lex->result) - { - my_var *var= new my_var($1,1,t->offset,t->type); - if (var == NULL) - MYSQL_YYABORT; - ((select_dumpvar *)lex->result)->var_list.push_back(var); -#ifndef DBUG_OFF - var->sp= lex->sphead; -#endif - } - else - { - /* - The parser won't create select_result instance only - if it's an EXPLAIN. - */ - DBUG_ASSERT(lex->describe); - } + if (!Lex->spcont || !(t= Lex->spcont->find_variable($1, false))) + my_yyabort_error((ER_SP_UNDECLARED_VAR, MYF(0), $1.str)); + $$ = Lex->result ? (new (thd->mem_root) + my_var_sp($1, t->offset, t->type, + Lex->sphead)) : + NULL; } ; +opt_into: + /* empty */ { $$= false; } + | into { $$= true; } + ; + into: INTO { if (! Lex->parsing_options.allows_select_into) - { - my_error(ER_VIEW_SELECT_CLAUSE, MYF(0), "INTO"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_VIEW_SELECT_CLAUSE, MYF(0), "INTO")); } into_destination ; @@ -10729,8 +11527,10 @@ into_destination: { LEX *lex= Lex; lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - if (!(lex->exchange= new sql_exchange($2.str, 0)) || - !(lex->result= new select_export(lex->exchange))) + if (!(lex->exchange= + new (thd->mem_root) sql_exchange($2.str, 0)) || + !(lex->result= + new (thd->mem_root) select_export(thd, lex->exchange))) MYSQL_YYABORT; } opt_load_data_charset @@ -10742,9 +11542,10 @@ into_destination: if (!lex->describe) { lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - if (!(lex->exchange= new sql_exchange($2.str,1))) + if (!(lex->exchange= new (thd->mem_root) sql_exchange($2.str,1))) MYSQL_YYABORT; - if (!(lex->result= new select_dump(lex->exchange))) + if (!(lex->result= + new (thd->mem_root) select_dump(thd, lex->exchange))) MYSQL_YYABORT; } } @@ -10776,119 +11577,103 @@ do: */ drop: - DROP opt_temporary table_or_tables if_exists + DROP opt_temporary table_or_tables opt_if_exists { LEX *lex=Lex; - lex->sql_command = SQLCOM_DROP_TABLE; - lex->drop_temporary= $2; - lex->drop_if_exists= $4; + lex->set_command(SQLCOM_DROP_TABLE, $2, $4); YYPS->m_lock_type= TL_UNLOCK; YYPS->m_mdl_type= MDL_EXCLUSIVE; } table_list opt_restrict {} - | DROP INDEX_SYM ident ON table_ident {} + | DROP INDEX_SYM opt_if_exists_table_element ident ON table_ident {} { LEX *lex=Lex; - Alter_drop *ad= new Alter_drop(Alter_drop::KEY, $3.str); + Alter_drop *ad= (new (thd->mem_root) + Alter_drop(Alter_drop::KEY, $4.str, $3)); if (ad == NULL) MYSQL_YYABORT; lex->sql_command= SQLCOM_DROP_INDEX; lex->alter_info.reset(); - lex->alter_info.flags= ALTER_DROP_INDEX; - lex->alter_info.drop_list.push_back(ad); - if (!lex->current_select->add_table_to_list(lex->thd, $5, NULL, + lex->alter_info.flags= Alter_info::ALTER_DROP_INDEX; + lex->alter_info.drop_list.push_back(ad, thd->mem_root); + if (!lex->current_select->add_table_to_list(thd, $6, NULL, TL_OPTION_UPDATING, TL_READ_NO_INSERT, - MDL_SHARED_NO_WRITE)) + MDL_SHARED_UPGRADABLE)) MYSQL_YYABORT; } - | DROP DATABASE if_exists ident + | DROP DATABASE opt_if_exists ident { LEX *lex=Lex; - lex->sql_command= SQLCOM_DROP_DB; - lex->drop_if_exists=$3; + lex->set_command(SQLCOM_DROP_DB, $3); lex->name= $4; } - | DROP FUNCTION_SYM if_exists ident '.' ident + | DROP FUNCTION_SYM opt_if_exists ident '.' ident { LEX *lex= thd->lex; sp_name *spname; if ($4.str && check_db_name(&$4)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), $4.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_DB_NAME, MYF(0), $4.str)); if (lex->sphead) - { - my_error(ER_SP_NO_DROP_SP, MYF(0), "FUNCTION"); - MYSQL_YYABORT; - } - lex->sql_command = SQLCOM_DROP_FUNCTION; - lex->drop_if_exists= $3; - spname= new sp_name($4, $6, true); + my_yyabort_error((ER_SP_NO_DROP_SP, MYF(0), "FUNCTION")); + lex->set_command(SQLCOM_DROP_FUNCTION, $3); + spname= new (thd->mem_root) sp_name($4, $6, true); if (spname == NULL) MYSQL_YYABORT; spname->init_qname(thd); lex->spname= spname; } - | DROP FUNCTION_SYM if_exists ident + | DROP FUNCTION_SYM opt_if_exists ident { LEX *lex= thd->lex; LEX_STRING db= {0, 0}; sp_name *spname; if (lex->sphead) - { - my_error(ER_SP_NO_DROP_SP, MYF(0), "FUNCTION"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_NO_DROP_SP, MYF(0), "FUNCTION")); if (thd->db && lex->copy_db_to(&db.str, &db.length)) MYSQL_YYABORT; - lex->sql_command = SQLCOM_DROP_FUNCTION; - lex->drop_if_exists= $3; - spname= new sp_name(db, $4, false); + lex->set_command(SQLCOM_DROP_FUNCTION, $3); + spname= new (thd->mem_root) sp_name(db, $4, false); if (spname == NULL) MYSQL_YYABORT; spname->init_qname(thd); lex->spname= spname; } - | DROP PROCEDURE_SYM if_exists sp_name + | DROP PROCEDURE_SYM opt_if_exists sp_name { LEX *lex=Lex; if (lex->sphead) - { - my_error(ER_SP_NO_DROP_SP, MYF(0), "PROCEDURE"); - MYSQL_YYABORT; - } - lex->sql_command = SQLCOM_DROP_PROCEDURE; - lex->drop_if_exists= $3; + my_yyabort_error((ER_SP_NO_DROP_SP, MYF(0), "PROCEDURE")); + lex->set_command(SQLCOM_DROP_PROCEDURE, $3); lex->spname= $4; } - | DROP USER clear_privileges user_list + | DROP USER_SYM opt_if_exists clear_privileges user_list + { + Lex->set_command(SQLCOM_DROP_USER, $3); + } + | DROP ROLE_SYM opt_if_exists clear_privileges role_list { - Lex->sql_command = SQLCOM_DROP_USER; + Lex->set_command(SQLCOM_DROP_ROLE, $3); } - | DROP VIEW_SYM if_exists + | DROP VIEW_SYM opt_if_exists { LEX *lex= Lex; - lex->sql_command= SQLCOM_DROP_VIEW; - lex->drop_if_exists= $3; + lex->set_command(SQLCOM_DROP_VIEW, $3); YYPS->m_lock_type= TL_UNLOCK; YYPS->m_mdl_type= MDL_EXCLUSIVE; } table_list opt_restrict {} - | DROP EVENT_SYM if_exists sp_name + | DROP EVENT_SYM opt_if_exists sp_name { - Lex->drop_if_exists= $3; Lex->spname= $4; - Lex->sql_command = SQLCOM_DROP_EVENT; + Lex->set_command(SQLCOM_DROP_EVENT, $3); } - | DROP TRIGGER_SYM if_exists sp_name + | DROP TRIGGER_SYM opt_if_exists sp_name { LEX *lex= Lex; - lex->sql_command= SQLCOM_DROP_TRIGGER; - lex->drop_if_exists= $3; + lex->set_command(SQLCOM_DROP_TRIGGER, $3); lex->spname= $4; } | DROP TABLESPACE tablespace_name opt_ts_engine opt_ts_wait @@ -10901,12 +11686,10 @@ drop: LEX *lex= Lex; lex->alter_tablespace_info->ts_cmd_type= DROP_LOGFILE_GROUP; } - | DROP SERVER_SYM if_exists ident_or_text + | DROP SERVER_SYM opt_if_exists ident_or_text { - Lex->sql_command = SQLCOM_DROP_SERVER; - Lex->drop_if_exists= $3; - Lex->server_options.server_name= $4.str; - Lex->server_options.server_name_length= $4.length; + Lex->set_command(SQLCOM_DROP_SERVER, $3); + Lex->server_options.reset($4); } ; @@ -10926,6 +11709,19 @@ table_name: } ; +table_name_with_opt_use_partition: + table_ident opt_use_partition + { + if (!Select->add_table_to_list(thd, $1, NULL, + TL_OPTION_UPDATING, + YYPS->m_lock_type, + YYPS->m_mdl_type, + NULL, + $2)) + MYSQL_YYABORT; + } + ; + table_alias_ref_list: table_alias_ref | table_alias_ref_list ',' table_alias_ref @@ -10942,14 +11738,33 @@ table_alias_ref: } ; -if_exists: - /* empty */ { $$= 0; } - | IF EXISTS { $$= 1; } +opt_if_exists_table_element: + /* empty */ + { + Lex->check_exists= FALSE; + $$= 0; + } + | IF_SYM EXISTS + { + Lex->check_exists= TRUE; + $$= 1; + } + ; + +opt_if_exists: + /* empty */ + { + $$.set(DDL_options_st::OPT_NONE); + } + | IF_SYM EXISTS + { + $$.set(DDL_options_st::OPT_IF_EXISTS); + } ; opt_temporary: /* empty */ { $$= 0; } - | TEMPORARY { $$= 1; } + | TEMPORARY { $$= HA_LEX_CREATE_TMP_TABLE; } ; /* ** Insert : add new data to table @@ -11030,7 +11845,7 @@ insert2: ; insert_table: - table_name + table_name_with_opt_use_partition { LEX *lex=Lex; lex->field_list.empty(); @@ -11045,16 +11860,17 @@ insert_field_spec: | SET { LEX *lex=Lex; - if (!(lex->insert_list = new List_item) || - lex->many_values.push_back(lex->insert_list)) + if (!(lex->insert_list= new (thd->mem_root) List_item) || + lex->many_values.push_back(lex->insert_list, thd->mem_root)) MYSQL_YYABORT; } ident_eq_list ; fields: - fields ',' insert_ident { Lex->field_list.push_back($3); } - | insert_ident { Lex->field_list.push_back($1); } + fields ',' insert_ident + { Lex->field_list.push_back($3, thd->mem_root); } + | insert_ident { Lex->field_list.push_back($1, thd->mem_root); } ; insert_values: @@ -11082,8 +11898,8 @@ ident_eq_value: simple_ident_nospvar equal expr_or_default { LEX *lex=Lex; - if (lex->field_list.push_back($1) || - lex->insert_list->push_back($3)) + if (lex->field_list.push_back($1, thd->mem_root) || + lex->insert_list->push_back($3, thd->mem_root)) MYSQL_YYABORT; } ; @@ -11101,13 +11917,13 @@ opt_equal: no_braces: '(' { - if (!(Lex->insert_list = new List_item)) + if (!(Lex->insert_list= new (thd->mem_root) List_item)) MYSQL_YYABORT; } opt_values ')' { LEX *lex=Lex; - if (lex->many_values.push_back(lex->insert_list)) + if (lex->many_values.push_back(lex->insert_list, thd->mem_root)) MYSQL_YYABORT; } ; @@ -11120,12 +11936,12 @@ opt_values: values: values ',' expr_or_default { - if (Lex->insert_list->push_back($3)) + if (Lex->insert_list->push_back($3, thd->mem_root)) MYSQL_YYABORT; } | expr_or_default { - if (Lex->insert_list->push_back($1)) + if (Lex->insert_list->push_back($1, thd->mem_root)) MYSQL_YYABORT; } ; @@ -11134,7 +11950,7 @@ expr_or_default: expr { $$= $1;} | DEFAULT { - $$= new (thd->mem_root) Item_default_value(Lex->current_context()); + $$= new (thd->mem_root) Item_default_value(thd, Lex->current_context()); if ($$ == NULL) MYSQL_YYABORT; } @@ -11176,7 +11992,7 @@ update: */ Select->set_lock_for_tables($3); } - where_clause opt_order_clause delete_limit_clause {} + opt_where_clause opt_order_clause delete_limit_clause {} ; update_list: @@ -11201,8 +12017,8 @@ insert_update_elem: simple_ident_nospvar equal expr_or_default { LEX *lex= Lex; - if (lex->update_list.push_back($1) || - lex->value_list.push_back($3)) + if (lex->update_list.push_back($1, thd->mem_root) || + lex->value_list.push_back($3, thd->mem_root)) MYSQL_YYABORT; } ; @@ -11230,24 +12046,27 @@ delete: ; single_multi: - FROM table_ident + FROM table_ident opt_use_partition { if (!Select->add_table_to_list(thd, $2, NULL, TL_OPTION_UPDATING, YYPS->m_lock_type, - YYPS->m_mdl_type)) + YYPS->m_mdl_type, + NULL, + $3)) MYSQL_YYABORT; YYPS->m_lock_type= TL_READ_DEFAULT; YYPS->m_mdl_type= MDL_SHARED_READ; } - where_clause opt_order_clause + opt_where_clause opt_order_clause delete_limit_clause {} + opt_select_expressions {} | table_wild_list { mysql_init_multi_delete(Lex); YYPS->m_lock_type= TL_READ_DEFAULT; YYPS->m_mdl_type= MDL_SHARED_READ; } - FROM join_table_list where_clause + FROM join_table_list opt_where_clause { if (multi_delete_set_locks_and_link_aux_tables(Lex)) MYSQL_YYABORT; @@ -11258,13 +12077,18 @@ single_multi: YYPS->m_lock_type= TL_READ_DEFAULT; YYPS->m_mdl_type= MDL_SHARED_READ; } - USING join_table_list where_clause + USING join_table_list opt_where_clause { if (multi_delete_set_locks_and_link_aux_tables(Lex)) MYSQL_YYABORT; } ; +opt_select_expressions: + /* empty */ + | RETURNING_SYM select_item_list + ; + table_wild_list: table_wild_one | table_wild_list ',' table_wild_one @@ -11273,7 +12097,7 @@ table_wild_list: table_wild_one: ident opt_wild { - Table_ident *ti= new Table_ident($1); + Table_ident *ti= new (thd->mem_root) Table_ident($1); if (ti == NULL) MYSQL_YYABORT; if (!Select->add_table_to_list(thd, @@ -11286,7 +12110,7 @@ table_wild_one: } | ident '.' ident opt_wild { - Table_ident *ti= new Table_ident(thd, $1, $3, 0); + Table_ident *ti= new (thd->mem_root) Table_ident(thd, $1, $3, 0); if (ti == NULL) MYSQL_YYABORT; if (!Select->add_table_to_list(thd, @@ -11330,9 +12154,9 @@ truncate: table_name { LEX* lex= thd->lex; - DBUG_ASSERT(!lex->m_stmt); - lex->m_stmt= new (thd->mem_root) Truncate_statement(lex); - if (lex->m_stmt == NULL) + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_truncate_table(); + if (lex->m_sql_cmd == NULL) MYSQL_YYABORT; } ; @@ -11407,9 +12231,10 @@ show: { LEX *lex=Lex; lex->wild=0; + lex->ident=null_lex_str; mysql_init_select(lex); lex->current_select->parsing_place= SELECT_LIST; - bzero((char*) &lex->create_info,sizeof(lex->create_info)); + lex->create_info.init(); } show_param { @@ -11472,6 +12297,19 @@ show_param: if (prepare_schema_table(thd, lex, 0, SCH_PLUGINS)) MYSQL_YYABORT; } + | PLUGINS_SYM SONAME_SYM TEXT_STRING_sys + { + Lex->ident= $3; + Lex->sql_command= SQLCOM_SHOW_PLUGINS; + if (prepare_schema_table(thd, Lex, 0, SCH_ALL_PLUGINS)) + MYSQL_YYABORT; + } + | PLUGINS_SYM SONAME_SYM wild_and_where + { + Lex->sql_command= SQLCOM_SHOW_PLUGINS; + if (prepare_schema_table(thd, Lex, 0, SCH_ALL_PLUGINS)) + MYSQL_YYABORT; + } | ENGINE_SYM known_storage_engines show_engine_param { Lex->create_info.db_type= $2; } | ENGINE_SYM ALL show_engine_param @@ -11497,13 +12335,14 @@ show_param: { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_BINLOG_EVENTS; - } opt_limit_clause_init - | RELAYLOG_SYM EVENTS_SYM binlog_in binlog_from + } + opt_limit_clause + | RELAYLOG_SYM optional_connection_name EVENTS_SYM binlog_in binlog_from { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_RELAYLOG_EVENTS; - } opt_limit_clause_init - | keys_or_index from_or_in table_ident opt_db where_clause + } opt_limit_clause + | keys_or_index from_or_in table_ident opt_db opt_where_clause { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_KEYS; @@ -11523,19 +12362,11 @@ show_param: { LEX *lex=Lex; lex->sql_command= SQLCOM_SHOW_AUTHORS; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT, - ER(ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT), - "SHOW AUTHORS"); } | CONTRIBUTORS_SYM { LEX *lex=Lex; lex->sql_command= SQLCOM_SHOW_CONTRIBUTORS; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT, - ER(ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT), - "SHOW CONTRIBUTORS"); } | PRIVILEGES { @@ -11546,25 +12377,25 @@ show_param: { (void) create_select_for_variable("warning_count"); } | COUNT_SYM '(' '*' ')' ERRORS { (void) create_select_for_variable("error_count"); } - | WARNINGS opt_limit_clause_init + | WARNINGS opt_limit_clause { Lex->sql_command = SQLCOM_SHOW_WARNS;} - | ERRORS opt_limit_clause_init + | ERRORS opt_limit_clause { Lex->sql_command = SQLCOM_SHOW_ERRORS;} | PROFILES_SYM { Lex->sql_command = SQLCOM_SHOW_PROFILES; } - | PROFILE_SYM opt_profile_defs opt_profile_args opt_limit_clause_init + | PROFILE_SYM opt_profile_defs opt_profile_args opt_limit_clause { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_PROFILE; if (prepare_schema_table(thd, lex, NULL, SCH_PROFILES) != 0) - YYABORT; + MYSQL_YYABORT; } | opt_var_type STATUS_SYM wild_and_where { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_STATUS; lex->option_type= $1; - if (prepare_schema_table(thd, lex, 0, SCH_STATUS)) + if (prepare_schema_table(thd, lex, 0, SCH_SESSION_STATUS)) MYSQL_YYABORT; } | opt_full PROCESSLIST_SYM @@ -11574,7 +12405,7 @@ show_param: LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_VARIABLES; lex->option_type= $1; - if (prepare_schema_table(thd, lex, 0, SCH_VARIABLES)) + if (prepare_schema_table(thd, lex, 0, SCH_SESSION_VARIABLES)) MYSQL_YYABORT; } | charset wild_and_where @@ -11593,25 +12424,20 @@ show_param: } | GRANTS { - LEX *lex=Lex; - lex->sql_command= SQLCOM_SHOW_GRANTS; - LEX_USER *curr_user; - if (!(curr_user= (LEX_USER*) lex->thd->alloc(sizeof(st_lex_user)))) + Lex->sql_command= SQLCOM_SHOW_GRANTS; + if (!(Lex->grant_user= (LEX_USER*)thd->alloc(sizeof(LEX_USER)))) MYSQL_YYABORT; - bzero(curr_user, sizeof(st_lex_user)); - lex->grant_user= curr_user; + Lex->grant_user->user= current_user_and_current_role; } - | GRANTS FOR_SYM user + | GRANTS FOR_SYM user_or_role clear_privileges { LEX *lex=Lex; lex->sql_command= SQLCOM_SHOW_GRANTS; lex->grant_user=$3; - lex->grant_user->password=null_lex_str; } | CREATE DATABASE opt_if_not_exists ident { - Lex->sql_command=SQLCOM_SHOW_CREATE_DB; - Lex->create_info.options=$3; + Lex->set_command(SQLCOM_SHOW_CREATE_DB, $3); Lex->name= $4; } | CREATE TABLE_SYM table_ident @@ -11634,37 +12460,22 @@ show_param: { Lex->sql_command = SQLCOM_SHOW_MASTER_STAT; } - | SLAVE STATUS_SYM + | ALL SLAVES STATUS_SYM { Lex->sql_command = SQLCOM_SHOW_SLAVE_STAT; + Lex->verbose= 1; } - | CLIENT_STATS_SYM - { - LEX *lex= Lex; - lex->sql_command= SQLCOM_SHOW_CLIENT_STATS; - if (prepare_schema_table(thd, lex, 0, SCH_CLIENT_STATS)) - MYSQL_YYABORT; - } - | USER_STATS_SYM - { - LEX *lex= Lex; - lex->sql_command= SQLCOM_SHOW_USER_STATS; - if (prepare_schema_table(thd, lex, 0, SCH_USER_STATS)) - MYSQL_YYABORT; - } - | TABLE_STATS_SYM + | SLAVE STATUS_SYM { - LEX *lex= Lex; - lex->sql_command= SQLCOM_SHOW_TABLE_STATS; - if (prepare_schema_table(thd, lex, 0, SCH_TABLE_STATS)) - MYSQL_YYABORT; + LEX *lex= thd->lex; + lex->mi.connection_name= null_lex_str; + lex->sql_command = SQLCOM_SHOW_SLAVE_STAT; + lex->verbose= 0; } - | INDEX_STATS_SYM + | SLAVE connection_name STATUS_SYM { - LEX *lex= Lex; - lex->sql_command= SQLCOM_SHOW_INDEX_STATS; - if (prepare_schema_table(thd, lex, 0, SCH_INDEX_STATS)) - MYSQL_YYABORT; + Lex->sql_command = SQLCOM_SHOW_SLAVE_STAT; + Lex->verbose= 0; } | CREATE PROCEDURE_SYM sp_name { @@ -11715,6 +12526,32 @@ show_param: Lex->spname= $3; Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT; } + | describe_command FOR_SYM expr + { + Lex->sql_command= SQLCOM_SHOW_EXPLAIN; + if (prepare_schema_table(thd, Lex, 0, SCH_EXPLAIN)) + MYSQL_YYABORT; + add_value_to_list(thd, $3); + } + | IDENT_sys remember_tok_start wild_and_where + { + LEX *lex= Lex; + bool in_plugin; + lex->sql_command= SQLCOM_SHOW_GENERIC; + ST_SCHEMA_TABLE *table= find_schema_table(thd, $1.str, &in_plugin); + if (!table || !table->old_format || !in_plugin) + { + my_parse_error(thd, ER_SYNTAX_ERROR, $2); + MYSQL_YYABORT; + } + if (lex->wild && table->idx_field1 < 0) + { + my_parse_error(thd, ER_SYNTAX_ERROR, $3); + MYSQL_YYABORT; + } + if (make_schema_select(thd, Lex->current_select, table)) + MYSQL_YYABORT; + } ; show_engine_param: @@ -11762,19 +12599,21 @@ binlog_from: ; wild_and_where: - /* empty */ - | LIKE TEXT_STRING_sys + /* empty */ { $$= 0; } + | LIKE remember_tok_start TEXT_STRING_sys { - Lex->wild= new (thd->mem_root) String($2.str, $2.length, + Lex->wild= new (thd->mem_root) String($3.str, $3.length, system_charset_info); if (Lex->wild == NULL) MYSQL_YYABORT; + $$= $2; } - | WHERE expr + | WHERE remember_tok_start expr { - Select->where= normalize_cond($2); - if ($2) - $2->top_level_item(); + Select->where= normalize_cond(thd, $3); + if ($3) + $3->top_level_item(); + $$= $2; } ; @@ -11797,22 +12636,50 @@ describe: } | describe_command opt_extended_describe { Lex->describe|= DESCRIBE_NORMAL; } - select + explainable_command { LEX *lex=Lex; lex->select_lex.options|= SELECT_DESCRIBE; } ; +explainable_command: + select + | insert + | replace + | update + | delete + ; + describe_command: DESC | DESCRIBE ; +analyze_stmt_command: + ANALYZE_SYM opt_format_json explainable_command + { + Lex->analyze_stmt= true; + } + ; + opt_extended_describe: - /* empty */ {} - | EXTENDED_SYM { Lex->describe|= DESCRIBE_EXTENDED; } + EXTENDED_SYM { Lex->describe|= DESCRIBE_EXTENDED; } | PARTITIONS_SYM { Lex->describe|= DESCRIBE_PARTITIONS; } + | opt_format_json {} + ; + +opt_format_json: + /* empty */ {} + | FORMAT_SYM '=' ident_or_text + { + if (!my_strcasecmp(system_charset_info, $3.str, "JSON")) + Lex->explain_json= true; + else if (!my_strcasecmp(system_charset_info, $3.str, "TRADITIONAL")) + DBUG_ASSERT(Lex->explain_json==false); + else + my_yyabort_error((ER_UNKNOWN_EXPLAIN_FORMAT, MYF(0), $3.str)); + } ; opt_describe_column: @@ -11854,24 +12721,37 @@ flush_options: YYPS->m_lock_type= TL_READ_NO_INSERT; YYPS->m_mdl_type= MDL_SHARED_HIGH_PRIO; } - opt_table_list {} - opt_with_read_lock {} + opt_table_list opt_flush_lock + {} | flush_options_list ; -opt_with_read_lock: +opt_flush_lock: /* empty */ {} - | WITH READ_SYM LOCK_SYM optional_flush_tables_arguments + | flush_lock + { + TABLE_LIST *tables= Lex->query_tables; + for (; tables; tables= tables->next_global) { - TABLE_LIST *tables= Lex->query_tables; - Lex->type|= REFRESH_READ_LOCK | $4; - for (; tables; tables= tables->next_global) - { - tables->mdl_request.set_type(MDL_SHARED_NO_WRITE); - tables->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */ - tables->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */ - } + tables->mdl_request.set_type(MDL_SHARED_NO_WRITE); + tables->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */ + tables->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */ } + } + ; + +flush_lock: + WITH READ_SYM LOCK_SYM optional_flush_tables_arguments + { Lex->type|= REFRESH_READ_LOCK | $4; } + | FOR_SYM + { + if (Lex->query_tables == NULL) // Table list can't be empty + { + my_parse_error(thd, ER_NO_TABLES_USED); + MYSQL_YYABORT; + } + Lex->type|= REFRESH_FOR_EXPORT; + } EXPORT_SYM {} ; flush_options_list: @@ -11889,10 +12769,16 @@ flush_option: { Lex->type|= REFRESH_GENERAL_LOG; } | SLOW LOGS_SYM { Lex->type|= REFRESH_SLOW_LOG; } - | BINARY LOGS_SYM + | BINARY LOGS_SYM opt_delete_gtid_domain { Lex->type|= REFRESH_BINARY_LOG; } - | RELAY LOGS_SYM - { Lex->type|= REFRESH_RELAY_LOG; } + | RELAY LOGS_SYM optional_connection_name + { + LEX *lex= Lex; + if (lex->type & REFRESH_RELAY_LOG) + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "FLUSH", "RELAY LOGS")); + lex->type|= REFRESH_RELAY_LOG; + lex->relay_log_connection_name= lex->mi.connection_name; + } | QUERY_SYM CACHE_SYM { Lex->type|= REFRESH_QUERY_CACHE_FREE; } | HOSTS_SYM @@ -11900,28 +12786,39 @@ flush_option: | PRIVILEGES { Lex->type|= REFRESH_GRANT; } | LOGS_SYM - { Lex->type|= REFRESH_LOG; } + { + Lex->type|= REFRESH_LOG; + Lex->relay_log_connection_name= empty_lex_str; + } | STATUS_SYM { Lex->type|= REFRESH_STATUS; } - | SLAVE + | SLAVE optional_connection_name { - Lex->type|= REFRESH_SLAVE; - Lex->reset_slave_info.all= false; - } - | CLIENT_STATS_SYM - { Lex->type|= REFRESH_CLIENT_STATS; } - | USER_STATS_SYM - { Lex->type|= REFRESH_USER_STATS; } - | TABLE_STATS_SYM - { Lex->type|= REFRESH_TABLE_STATS; } - | INDEX_STATS_SYM - { Lex->type|= REFRESH_INDEX_STATS; } + LEX *lex= Lex; + if (lex->type & REFRESH_SLAVE) + my_yyabort_error((ER_WRONG_USAGE, MYF(0), "FLUSH","SLAVE")); + lex->type|= REFRESH_SLAVE; + lex->reset_slave_info.all= false; + } | MASTER_SYM { Lex->type|= REFRESH_MASTER; } | DES_KEY_FILE { Lex->type|= REFRESH_DES_KEY_FILE; } | RESOURCES { Lex->type|= REFRESH_USER_RESOURCES; } + | IDENT_sys remember_tok_start + { + Lex->type|= REFRESH_GENERIC; + ST_SCHEMA_TABLE *table= find_schema_table(thd, $1.str); + if (!table || !table->reset_table) + { + my_parse_error(thd, ER_SYNTAX_ERROR, $2); + MYSQL_YYABORT; + } + Lex->view_list.push_back((LEX_STRING*) + thd->memdup(&$1, sizeof(LEX_STRING)), + thd->mem_root); + } ; opt_table_list: @@ -11929,6 +12826,33 @@ opt_table_list: | table_list {} ; +opt_delete_gtid_domain: + /* empty */ {} + | DELETE_DOMAIN_ID_SYM '=' '(' delete_domain_id_list ')' + {} + ; +delete_domain_id_list: + /* Empty */ + | delete_domain_id + | delete_domain_id_list ',' delete_domain_id + ; + +delete_domain_id: + ulonglong_num + { + uint32 value= (uint32) $1; + if ($1 > UINT_MAX32) + { + my_printf_error(ER_BINLOG_CANT_DELETE_GTID_DOMAIN, + "The value of gtid domain being deleted ('%llu') " + "exceeds its maximum size " + "of 32 bit unsigned integer", MYF(0), $1); + MYSQL_YYABORT; + } + insert_dynamic(&Lex->delete_gtid_domain, (uchar*) &value); + } + ; + optional_flush_tables_arguments: /* empty */ {$$= 0;} | AND_SYM DISABLE_SYM CHECKPOINT_SYM {$$= REFRESH_CHECKPOINT; } @@ -11950,8 +12874,14 @@ reset_options: reset_option: SLAVE { Lex->type|= REFRESH_SLAVE; } + optional_connection_name slave_reset_options { } - | MASTER_SYM { Lex->type|= REFRESH_MASTER; } + | MASTER_SYM + { + Lex->type|= REFRESH_MASTER; + Lex->next_binlog_file_number= 0; + } + master_reset_options | QUERY_SYM CACHE_SYM { Lex->type|= REFRESH_QUERY_CACHE;} ; @@ -11960,6 +12890,14 @@ slave_reset_options: | ALL { Lex->reset_slave_info.all= true; } ; +master_reset_options: + /* empty */ {} + | TO_SYM ulong_num + { + Lex->next_binlog_file_number = $2; + } + ; + purge: PURGE { @@ -11984,7 +12922,7 @@ purge_option: { LEX *lex= Lex; lex->value_list.empty(); - lex->value_list.push_front($2); + lex->value_list.push_front($2, thd->mem_root); lex->sql_command= SQLCOM_PURGE_BEFORE; } ; @@ -11998,6 +12936,7 @@ kill: lex->value_list.empty(); lex->users_list.empty(); lex->sql_command= SQLCOM_KILL; + lex->kill_type= KILL_TYPE_ID; } kill_type kill_option kill_expr { @@ -12014,21 +12953,30 @@ kill_option: /* empty */ { $$= (int) KILL_CONNECTION; } | CONNECTION_SYM { $$= (int) KILL_CONNECTION; } | QUERY_SYM { $$= (int) KILL_QUERY; } + | QUERY_SYM ID_SYM + { + $$= (int) KILL_QUERY; + Lex->kill_type= KILL_TYPE_QUERY; + } ; kill_expr: expr { - Lex->value_list.push_front($$); - Lex->kill_type= KILL_TYPE_ID; + Lex->value_list.push_front($$, thd->mem_root); } - | USER user + | USER_SYM user { - Lex->users_list.push_back($2); + Lex->users_list.push_back($2, thd->mem_root); Lex->kill_type= KILL_TYPE_USER; } ; + +shutdown: + SHUTDOWN { Lex->sql_command= SQLCOM_SHUTDOWN; } + ; + /* change database */ use: @@ -12046,6 +12994,7 @@ load: LOAD data_or_xml { LEX *lex= thd->lex; + mysql_init_select(lex); if (lex->sphead) { @@ -12061,14 +13010,14 @@ load: lex->local_file= $5; lex->duplicates= DUP_ERROR; lex->ignore= 0; - if (!(lex->exchange= new sql_exchange($7.str, 0, $2))) + if (!(lex->exchange= new (thd->mem_root) sql_exchange($7.str, 0, $2))) MYSQL_YYABORT; } - opt_duplicate INTO TABLE_SYM table_ident + opt_duplicate INTO TABLE_SYM table_ident opt_use_partition { LEX *lex=Lex; if (!Select->add_table_to_list(thd, $12, NULL, TL_OPTION_UPDATING, - $4, MDL_SHARED_WRITE)) + $4, MDL_SHARED_WRITE, NULL, $13)) MYSQL_YYABORT; lex->field_list.empty(); lex->update_list.empty(); @@ -12076,7 +13025,7 @@ load: lex->many_values.empty(); } opt_load_data_charset - { Lex->exchange->cs= $14; } + { Lex->exchange->cs= $15; } opt_xml_rows_identified_by opt_field_term opt_line_term opt_ignore_lines opt_field_or_var_spec opt_load_data_set_spec @@ -12185,8 +13134,7 @@ opt_ignore_lines: ; lines_or_rows: - LINES { } - + LINES { } | ROWS_SYM { } ; @@ -12198,16 +13146,16 @@ opt_field_or_var_spec: fields_or_vars: fields_or_vars ',' field_or_var - { Lex->field_list.push_back($3); } + { Lex->field_list.push_back($3, thd->mem_root); } | field_or_var - { Lex->field_list.push_back($1); } + { Lex->field_list.push_back($1, thd->mem_root); } ; field_or_var: simple_ident_nospvar {$$= $1;} | '@' ident_or_text { - $$= new (thd->mem_root) Item_user_var_as_out_param($2); + $$= new (thd->mem_root) Item_user_var_as_out_param(thd, $2); if ($$ == NULL) MYSQL_YYABORT; } @@ -12227,8 +13175,8 @@ load_data_set_elem: simple_ident_nospvar equal remember_name expr_or_default remember_end { LEX *lex= Lex; - if (lex->update_list.push_back($1) || - lex->value_list.push_back($4)) + if (lex->update_list.push_back($1, thd->mem_root) || + lex->value_list.push_back($4, thd->mem_root)) MYSQL_YYABORT; $4->set_name_no_truncate($3, (uint) ($5 - $3), thd->charset()); } @@ -12254,7 +13202,8 @@ text_literal: if (thd->convert_string(&tmp, cs_con, $1.str, $1.length, cs_cli)) MYSQL_YYABORT; } - $$= new (thd->mem_root) Item_string(tmp.str, tmp.length, cs_con, + $$= new (thd->mem_root) Item_string(thd, tmp.str, tmp.length, + cs_con, DERIVATION_COERCIBLE, repertoire); if ($$ == NULL) @@ -12265,7 +13214,7 @@ text_literal: uint repertoire= Lex->text_string_is_7bit ? MY_REPERTOIRE_ASCII : MY_REPERTOIRE_UNICODE30; DBUG_ASSERT(my_charset_is_ascii_based(national_charset_info)); - $$= new (thd->mem_root) Item_string($1.str, $1.length, + $$= new (thd->mem_root) Item_string(thd, $1.str, $1.length, national_charset_info, DERIVATION_COERCIBLE, repertoire); @@ -12274,14 +13223,10 @@ text_literal: } | UNDERSCORE_CHARSET TEXT_STRING { - Item_string *str= new (thd->mem_root) Item_string($2.str, + $$= new (thd->mem_root) Item_string_with_introducer(thd, $2.str, $2.length, $1); - if (str == NULL) + if ($$ == NULL) MYSQL_YYABORT; - str->set_repertoire_from_value(); - str->set_cs_specified(TRUE); - - $$= str; } | text_literal TEXT_STRING_literal { @@ -12310,9 +13255,15 @@ text_string: if ($$ == NULL) MYSQL_YYABORT; } - | HEX_NUM + | hex_or_bin_String { $$= $1; } + ; + + +hex_or_bin_String: + HEX_NUM { - Item *tmp= new (thd->mem_root) Item_hex_hybrid($1.str, $1.length); + Item *tmp= new (thd->mem_root) Item_hex_hybrid(thd, $1.str, + $1.length); if (tmp == NULL) MYSQL_YYABORT; /* @@ -12324,7 +13275,8 @@ text_string: } | HEX_STRING { - Item *tmp= new (thd->mem_root) Item_hex_string($1.str, $1.length); + Item *tmp= new (thd->mem_root) Item_hex_string(thd, $1.str, + $1.length); if (tmp == NULL) MYSQL_YYABORT; tmp->quick_fix_field(); @@ -12332,7 +13284,8 @@ text_string: } | BIN_NUM { - Item *tmp= new (thd->mem_root) Item_bin_string($1.str, $1.length); + Item *tmp= new (thd->mem_root) Item_bin_string(thd, $1.str, + $1.length); if (tmp == NULL) MYSQL_YYABORT; /* @@ -12351,16 +13304,13 @@ param_marker: Lex_input_stream *lip= YYLIP; Item_param *item; if (! lex->parsing_options.allows_variable) - { - my_error(ER_VIEW_SELECT_VARIABLE, MYF(0)); - MYSQL_YYABORT; - } - item= new (thd->mem_root) Item_param((uint) (lip->get_tok_start() - thd->query())); - if (!($$= item) || lex->param_list.push_back(item)) - { - my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_VIEW_SELECT_VARIABLE, MYF(0))); + const char *query_start= lex->sphead ? lex->sphead->m_tmp_query + : thd->query(); + item= new (thd->mem_root) Item_param(thd, lip->get_tok_start() - + query_start); + if (!($$= item) || lex->param_list.push_back(item, thd->mem_root)) + my_yyabort_error((ER_OUT_OF_RESOURCES, MYF(0))); } ; @@ -12370,110 +13320,73 @@ signed_literal: | '-' NUM_literal { $2->max_length++; - $$= $2->neg(); + $$= $2->neg(thd); } ; literal: text_literal { $$ = $1; } | NUM_literal { $$ = $1; } + | temporal_literal { $$= $1; } | NULL_SYM { - $$ = new (thd->mem_root) Item_null(); + /* + For the digest computation, in this context only, + NULL is considered a literal, hence reduced to '?' + REDUCE: + TOK_GENERIC_VALUE := NULL_SYM + */ + YYLIP->reduce_digest_token(TOK_GENERIC_VALUE, NULL_SYM); + $$= new (thd->mem_root) Item_null(thd); if ($$ == NULL) MYSQL_YYABORT; YYLIP->next_state= MY_LEX_OPERATOR_OR_IDENT; } | FALSE_SYM { - $$= new (thd->mem_root) Item_int((char*) "FALSE",0,1); + $$= new (thd->mem_root) Item_int(thd, (char*) "FALSE",0,1); if ($$ == NULL) MYSQL_YYABORT; } | TRUE_SYM { - $$= new (thd->mem_root) Item_int((char*) "TRUE",1,1); + $$= new (thd->mem_root) Item_int(thd, (char*) "TRUE",1,1); if ($$ == NULL) MYSQL_YYABORT; } | HEX_NUM { - $$ = new (thd->mem_root) Item_hex_hybrid($1.str, $1.length); + $$= new (thd->mem_root) Item_hex_hybrid(thd, $1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | HEX_STRING { - $$ = new (thd->mem_root) Item_hex_string($1.str, $1.length); + $$= new (thd->mem_root) Item_hex_string(thd, $1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | BIN_NUM { - $$= new (thd->mem_root) Item_bin_string($1.str, $1.length); + $$= new (thd->mem_root) Item_bin_string(thd, $1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } - | UNDERSCORE_CHARSET hex_num_or_string - { - Item *tmp= new (thd->mem_root) Item_hex_string($2.str, $2.length); - if (tmp == NULL) - MYSQL_YYABORT; - /* - it is OK only emulate fix_fieds, because we need only - value of constant - */ - tmp->quick_fix_field(); - String *str= tmp->val_str((String*) 0); - - Item_string *item_str; - item_str= new (thd->mem_root) - Item_string(NULL, /* name will be set in select_item */ - str ? str->ptr() : "", - str ? str->length() : 0, - $1); - if (!item_str || - !item_str->check_well_formed_result(&item_str->str_value, TRUE)) - { - MYSQL_YYABORT; - } - - item_str->set_repertoire_from_value(); - item_str->set_cs_specified(TRUE); - - $$= item_str; - } - | UNDERSCORE_CHARSET BIN_NUM + | UNDERSCORE_CHARSET hex_or_bin_String { - Item *tmp= new (thd->mem_root) Item_bin_string($2.str, $2.length); - if (tmp == NULL) - MYSQL_YYABORT; + Item_string_with_introducer *item_str; /* - it is OK only emulate fix_fieds, because we need only - value of constant + Pass NULL as name. Name will be set in the "select_item" rule and + will include the introducer and the original hex/bin notation. */ - tmp->quick_fix_field(); - String *str= tmp->val_str((String*) 0); - - Item_string *item_str; item_str= new (thd->mem_root) - Item_string(NULL, /* name will be set in select_item */ - str ? str->ptr() : "", - str ? str->length() : 0, - $1); - if (!item_str || - !item_str->check_well_formed_result(&item_str->str_value, TRUE)) - { + Item_string_with_introducer(thd, NULL, $2->ptr(), $2->length(), + $1); + if (!item_str || !item_str->check_well_formed_result(true)) MYSQL_YYABORT; - } - - item_str->set_cs_specified(TRUE); $$= item_str; } - | DATE_SYM text_literal { $$ = $2; } - | TIME_SYM text_literal { $$ = $2; } - | TIMESTAMP text_literal { $$ = $2; } ; NUM_literal: @@ -12481,7 +13394,7 @@ NUM_literal: { int error; $$= new (thd->mem_root) - Item_int($1.str, + Item_int(thd, $1.str, (longlong) my_strtoll10($1.str, NULL, &error), $1.length); if ($$ == NULL) @@ -12491,7 +13404,7 @@ NUM_literal: { int error; $$= new (thd->mem_root) - Item_int($1.str, + Item_int(thd, $1.str, (longlong) my_strtoll10($1.str, NULL, &error), $1.length); if ($$ == NULL) @@ -12499,13 +13412,13 @@ NUM_literal: } | ULONGLONG_NUM { - $$= new (thd->mem_root) Item_uint($1.str, $1.length); + $$= new (thd->mem_root) Item_uint(thd, $1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | DECIMAL_NUM { - $$= new (thd->mem_root) Item_decimal($1.str, $1.length, + $$= new (thd->mem_root) Item_decimal(thd, $1.str, $1.length, thd->charset()); if (($$ == NULL) || (thd->is_error())) { @@ -12514,7 +13427,7 @@ NUM_literal: } | FLOAT_NUM { - $$= new (thd->mem_root) Item_float($1.str, $1.length); + $$= new (thd->mem_root) Item_float(thd, $1.str, $1.length); if (($$ == NULL) || (thd->is_error())) { MYSQL_YYABORT; @@ -12522,6 +13435,31 @@ NUM_literal: } ; + +temporal_literal: + DATE_SYM TEXT_STRING + { + if (!($$= create_temporal_literal(thd, $2.str, $2.length, YYCSCL, + MYSQL_TYPE_DATE, true))) + MYSQL_YYABORT; + } + | TIME_SYM TEXT_STRING + { + if (!($$= create_temporal_literal(thd, $2.str, $2.length, YYCSCL, + MYSQL_TYPE_TIME, true))) + MYSQL_YYABORT; + } + | TIMESTAMP TEXT_STRING + { + if (!($$= create_temporal_literal(thd, $2.str, $2.length, YYCSCL, + MYSQL_TYPE_DATETIME, true))) + MYSQL_YYABORT; + } + ; + + + + /********************************************************************** ** Creating different items. **********************************************************************/ @@ -12535,7 +13473,7 @@ table_wild: ident '.' '*' { SELECT_LEX *sel= Select; - $$= new (thd->mem_root) Item_field(Lex->current_context(), + $$= new (thd->mem_root) Item_field(thd, Lex->current_context(), NullS, $1.str, "*"); if ($$ == NULL) MYSQL_YYABORT; @@ -12546,7 +13484,7 @@ table_wild: SELECT_LEX *sel= Select; const char* schema= thd->client_capabilities & CLIENT_NO_SCHEMA ? NullS : $1.str; - $$= new (thd->mem_root) Item_field(Lex->current_context(), + $$= new (thd->mem_root) Item_field(thd, Lex->current_context(), schema, $3.str,"*"); if ($$ == NULL) @@ -12564,20 +13502,17 @@ simple_ident: { LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; - sp_variable_t *spv; + sp_variable *spv; sp_pcontext *spc = lex->spcont; - if (spc && (spv = spc->find_variable(&$1))) + if (spc && (spv = spc->find_variable($1, false))) { /* We're compiling a stored procedure and found a variable */ if (! lex->parsing_options.allows_variable) - { - my_error(ER_VIEW_SELECT_VARIABLE, MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_VIEW_SELECT_VARIABLE, MYF(0))); Item_splocal *splocal; splocal= new (thd->mem_root) - Item_splocal($1, spv->offset, spv->type, + Item_splocal(thd, $1, spv->offset, spv->type, lip->get_tok_start_prev() - lex->sphead->m_tmp_query, lip->get_tok_end() - lip->get_tok_start_prev()); if (splocal == NULL) @@ -12594,12 +13529,12 @@ simple_ident: if ((sel->parsing_place != IN_HAVING) || (sel->get_in_sum_expr() > 0)) { - $$= new (thd->mem_root) Item_field(Lex->current_context(), + $$= new (thd->mem_root) Item_field(thd, Lex->current_context(), NullS, NullS, $1.str); } else { - $$= new (thd->mem_root) Item_ref(Lex->current_context(), + $$= new (thd->mem_root) Item_ref(thd, Lex->current_context(), NullS, NullS, $1.str); } if ($$ == NULL) @@ -12616,12 +13551,12 @@ simple_ident_nospvar: if ((sel->parsing_place != IN_HAVING) || (sel->get_in_sum_expr() > 0)) { - $$= new (thd->mem_root) Item_field(Lex->current_context(), + $$= new (thd->mem_root) Item_field(thd, Lex->current_context(), NullS, NullS, $1.str); } else { - $$= new (thd->mem_root) Item_ref(Lex->current_context(), + $$= new (thd->mem_root) Item_ref(thd, Lex->current_context(), NullS, NullS, $1.str); } if ($$ == NULL) @@ -12649,31 +13584,25 @@ simple_ident_q: if (lex->trg_chistics.event == TRG_EVENT_INSERT && !new_row) - { - my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "OLD", "on INSERT"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "OLD", "on INSERT")); if (lex->trg_chistics.event == TRG_EVENT_DELETE && new_row) - { - my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "NEW", "on DELETE"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "NEW", "on DELETE")); DBUG_ASSERT(!new_row || (lex->trg_chistics.event == TRG_EVENT_INSERT || lex->trg_chistics.event == TRG_EVENT_UPDATE)); - const bool read_only= + const bool tmp_read_only= !(new_row && lex->trg_chistics.action_time == TRG_ACTION_BEFORE); trg_fld= new (thd->mem_root) - Item_trigger_field(Lex->current_context(), + Item_trigger_field(thd, Lex->current_context(), new_row ? Item_trigger_field::NEW_ROW: Item_trigger_field::OLD_ROW, $3.str, SELECT_ACL, - read_only); + tmp_read_only); if (trg_fld == NULL) MYSQL_YYABORT; @@ -12697,12 +13626,12 @@ simple_ident_q: if ((sel->parsing_place != IN_HAVING) || (sel->get_in_sum_expr() > 0)) { - $$= new (thd->mem_root) Item_field(Lex->current_context(), + $$= new (thd->mem_root) Item_field(thd, Lex->current_context(), NullS, $1.str, $3.str); } else { - $$= new (thd->mem_root) Item_ref(Lex->current_context(), + $$= new (thd->mem_root) Item_ref(thd, Lex->current_context(), NullS, $1.str, $3.str); } if ($$ == NULL) @@ -12721,13 +13650,13 @@ simple_ident_q: if ((sel->parsing_place != IN_HAVING) || (sel->get_in_sum_expr() > 0)) { - $$= new (thd->mem_root) Item_field(Lex->current_context(), + $$= new (thd->mem_root) Item_field(thd, Lex->current_context(), NullS, $2.str, $4.str); } else { - $$= new (thd->mem_root) Item_ref(Lex->current_context(), + $$= new (thd->mem_root) Item_ref(thd, Lex->current_context(), NullS, $2.str, $4.str); } if ($$ == NULL) @@ -12747,13 +13676,13 @@ simple_ident_q: if ((sel->parsing_place != IN_HAVING) || (sel->get_in_sum_expr() > 0)) { - $$= new (thd->mem_root) Item_field(Lex->current_context(), + $$= new (thd->mem_root) Item_field(thd, Lex->current_context(), schema, $3.str, $5.str); } else { - $$= new (thd->mem_root) Item_ref(Lex->current_context(), + $$= new (thd->mem_root) Item_ref(thd, Lex->current_context(), schema, $3.str, $5.str); } @@ -12768,26 +13697,17 @@ field_ident: { TABLE_LIST *table= Select->table_list.first; if (my_strcasecmp(table_alias_charset, $1.str, table->db)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_DB_NAME, MYF(0), $1.str)); if (my_strcasecmp(table_alias_charset, $3.str, table->table_name)) - { - my_error(ER_WRONG_TABLE_NAME, MYF(0), $3.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_TABLE_NAME, MYF(0), $3.str)); $$=$5; } | ident '.' ident { TABLE_LIST *table= Select->table_list.first; if (my_strcasecmp(table_alias_charset, $1.str, table->alias)) - { - my_error(ER_WRONG_TABLE_NAME, MYF(0), $1.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_WRONG_TABLE_NAME, MYF(0), $1.str)); $$=$3; } | '.' ident { $$=$2;} /* For Delphi */ @@ -12796,20 +13716,20 @@ field_ident: table_ident: ident { - $$= new Table_ident($1); + $$= new (thd->mem_root) Table_ident($1); if ($$ == NULL) MYSQL_YYABORT; } | ident '.' ident { - $$= new Table_ident(thd, $1,$3,0); + $$= new (thd->mem_root) Table_ident(thd, $1, $3, 0); if ($$ == NULL) MYSQL_YYABORT; } | '.' ident { /* For Delphi */ - $$= new Table_ident($2); + $$= new (thd->mem_root) Table_ident($2); if ($$ == NULL) MYSQL_YYABORT; } @@ -12818,13 +13738,13 @@ table_ident: table_ident_opt_wild: ident opt_wild { - $$= new Table_ident($1); + $$= new (thd->mem_root) Table_ident($1); if ($$ == NULL) MYSQL_YYABORT; } | ident '.' ident opt_wild { - $$= new Table_ident(thd, $1,$3,0); + $$= new (thd->mem_root) Table_ident(thd, $1, $3, 0); if ($$ == NULL) MYSQL_YYABORT; } @@ -12834,7 +13754,7 @@ table_ident_nodb: ident { LEX_STRING db={(char*) any_db,3}; - $$= new Table_ident(thd, db,$1,0); + $$= new (thd->mem_root) Table_ident(thd, db, $1, 0); if ($$ == NULL) MYSQL_YYABORT; } @@ -12862,8 +13782,8 @@ IDENT_sys: } else { - if (thd->convert_string(&$$, system_charset_info, - $1.str, $1.length, thd->charset())) + if (thd->convert_with_error(system_charset_info, &$$, + thd->charset(), $1.str, $1.length)) MYSQL_YYABORT; } } @@ -12940,19 +13860,16 @@ ident_or_text: | LEX_HOSTNAME { $$=$1;} ; -user: +user_maybe_role: ident_or_text { if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) MYSQL_YYABORT; $$->user = $1; - $$->host.str= (char *) "%"; - $$->host.length= 1; - $$->password= null_lex_str; - $$->plugin= empty_lex_str; - $$->auth= empty_lex_str; + $$->host= null_lex_str; // User or Role, see get_current_user() + $$->reset_auth(); - if (check_string_char_length(&$$->user, ER(ER_USERNAME), + if (check_string_char_length(&$$->user, ER_USERNAME, username_char_length, system_charset_info, 0)) MYSQL_YYABORT; @@ -12962,35 +13879,51 @@ user: if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) MYSQL_YYABORT; $$->user = $1; $$->host=$3; - $$->password= null_lex_str; - $$->plugin= empty_lex_str; - $$->auth= empty_lex_str; + $$->reset_auth(); - if (check_string_char_length(&$$->user, ER(ER_USERNAME), + if (check_string_char_length(&$$->user, ER_USERNAME, username_char_length, system_charset_info, 0) || check_host_name(&$$->host)) MYSQL_YYABORT; - /* - Convert hostname part of username to lowercase. - It's OK to use in-place lowercase as long as - the character set is utf8. - */ - my_casedn_str(system_charset_info, $$->host.str); + if ($$->host.str[0]) + { + /* + Convert hostname part of username to lowercase. + It's OK to use in-place lowercase as long as + the character set is utf8. + */ + my_casedn_str(system_charset_info, $$->host.str); + } + else + { + /* + fix historical undocumented convention that empty host is the + same as '%' + */ + $$->host= host_not_specified; + } } | CURRENT_USER optional_braces { - if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) + if (!($$=(LEX_USER*)thd->calloc(sizeof(LEX_USER)))) MYSQL_YYABORT; - /* - empty LEX_USER means current_user and - will be handled in the get_current_user() function - later - */ - bzero($$, sizeof(LEX_USER)); + $$->user= current_user; + $$->plugin= empty_lex_str; + $$->auth= empty_lex_str; } ; +user_or_role: user_maybe_role | current_role; + +user: user_maybe_role + { + if ($1->user.str != current_user.str && $1->host.str == 0) + $1->host= host_not_specified; + $$= $1; + } + ; + /* Keyword that we allow for identifiers (except SP labels) */ keyword: keyword_sp {} @@ -13004,11 +13937,10 @@ keyword: | CHECKPOINT_SYM {} | CLOSE_SYM {} | COLUMN_ADD_SYM {} + | COLUMN_CHECK_SYM {} | COLUMN_CREATE_SYM {} | COLUMN_DELETE_SYM {} - | COLUMN_EXISTS_SYM {} | COLUMN_GET_SYM {} - | COLUMN_LIST_SYM {} | COMMENT_SYM {} | COMMIT_SYM {} | CONTAINS_SYM {} @@ -13018,6 +13950,8 @@ keyword: | EXAMINED_SYM {} | EXECUTE_SYM {} | FLUSH_SYM {} + | FORMAT_SYM {} + | GET_SYM {} | HANDLER_SYM {} | HELP_SYM {} | HOST_SYM {} @@ -13029,7 +13963,6 @@ keyword: | OPTIONS_SYM {} | OWNER_SYM {} | PARSER_SYM {} - | PARTITION_SYM {} | PORT_SYM {} | PREPARE_SYM {} | REMOVE_SYM {} @@ -13040,9 +13973,11 @@ keyword: | SAVEPOINT_SYM {} | SECURITY_SYM {} | SERVER_SYM {} + | SHUTDOWN {} | SIGNED_SYM {} | SOCKET_SYM {} | SLAVE {} + | SLAVES {} | SONAME_SYM {} | START_SYM {} | STOP_SYM {} @@ -13063,6 +13998,7 @@ keyword: keyword_sp: ACTION {} | ADDDATE_SYM {} + | ADMIN_SYM {} | AFTER_SYM {} | AGAINST {} | AGGREGATE_SYM {} @@ -13070,9 +14006,11 @@ keyword_sp: | ALWAYS_SYM {} | ANY_SYM {} | AT_SYM {} + | ATOMIC_SYM {} | AUTHORS_SYM {} | AUTO_INC {} | AUTOEXTEND_SIZE_SYM {} + | AUTO_SYM {} | AVG_ROW_LENGTH {} | AVG_SYM {} | BINLOG_SYM {} @@ -13086,7 +14024,6 @@ keyword_sp: | CHAIN_SYM {} | CHANGED {} | CIPHER_SYM {} - | CLIENT_STATS_SYM {} | CLIENT_SYM {} | CLASS_ORIGIN_SYM {} | COALESCE {} @@ -13106,8 +14043,14 @@ keyword_sp: | CONSTRAINT_NAME_SYM {} | CONTEXT_SYM {} | CONTRIBUTORS_SYM {} + | CURRENT_POS_SYM {} | CPU_SYM {} | CUBE_SYM {} + /* + Although a reserved keyword in SQL:2003 (and :2008), + not reserved in MySQL per WL#2111 specification. + */ + | CURRENT_SYM {} | CURSOR_NAME_SYM {} | DATA_SYM {} | DATAFILE_SYM {} @@ -13117,6 +14060,7 @@ keyword_sp: | DEFINER_SYM {} | DELAY_KEY_WRITE_SYM {} | DES_KEY_FILE {} + | DIAGNOSTICS_SYM {} | DIRECTORY_SYM {} | DISABLE_SYM {} | DISCARD {} @@ -13134,7 +14078,9 @@ keyword_sp: | EVENT_SYM {} | EVENTS_SYM {} | EVERY_SYM {} + | EXCHANGE_SYM {} | EXPANSION_SYM {} + | EXPORT_SYM {} | EXTENDED_SYM {} | EXTENT_SIZE_SYM {} | FAULTS_SYM {} @@ -13156,9 +14102,9 @@ keyword_sp: | HARD_SYM {} | HOSTS_SYM {} | HOUR_SYM {} + | ID_SYM {} | IDENTIFIED_SYM {} | IGNORE_SERVER_IDS_SYM {} - | INDEX_STATS_SYM {} | INVOKER_SYM {} | IMPORT {} | INDEXES {} @@ -13183,11 +14129,13 @@ keyword_sp: | MAX_ROWS {} | MASTER_SYM {} | MASTER_HEARTBEAT_PERIOD_SYM {} + | MASTER_GTID_POS_SYM {} | MASTER_HOST_SYM {} | MASTER_PORT_SYM {} | MASTER_LOG_FILE_SYM {} | MASTER_LOG_POS_SYM {} | MASTER_USER_SYM {} + | MASTER_USE_GTID_SYM {} | MASTER_PASSWORD_SYM {} | MASTER_SERVER_ID_SYM {} | MASTER_CONNECT_RETRY_SYM {} @@ -13196,10 +14144,13 @@ keyword_sp: | MASTER_SSL_CAPATH_SYM {} | MASTER_SSL_CERT_SYM {} | MASTER_SSL_CIPHER_SYM {} + | MASTER_SSL_CRL_SYM {} + | MASTER_SSL_CRLPATH_SYM {} | MASTER_SSL_KEY_SYM {} | MAX_CONNECTIONS_PER_HOUR {} | MAX_QUERIES_PER_HOUR {} | MAX_SIZE_SYM {} + | MAX_STATEMENT_TIME_SYM {} | MAX_UPDATES_PER_HOUR {} | MAX_USER_CONNECTIONS_SYM {} | MEDIUM_SYM {} @@ -13223,24 +14174,24 @@ keyword_sp: | NAMES_SYM {} | NATIONAL_SYM {} | NCHAR_SYM {} - | NDBCLUSTER_SYM {} | NEXT_SYM {} | NEW_SYM {} | NO_WAIT_SYM {} | NODEGROUP_SYM {} | NONE_SYM {} + | NUMBER_SYM {} | NVARCHAR_SYM {} | OFFSET_SYM {} - | OLD_PASSWORD {} - | ONE_SHOT_SYM {} + | OLD_PASSWORD_SYM {} | ONE_SYM {} | ONLINE_SYM {} + | ONLY_SYM {} | PACK_KEYS_SYM {} | PAGE_SYM {} | PARTIAL {} | PARTITIONING_SYM {} | PARTITIONS_SYM {} - | PASSWORD {} + | PASSWORD_SYM {} | PERSISTENT_SYM {} | PHASE_SYM {} | PLUGIN_SYM {} @@ -13275,10 +14226,14 @@ keyword_sp: | REPLICATION {} | RESOURCES {} | RESUME_SYM {} + | RETURNED_SQLSTATE_SYM {} | RETURNS_SYM {} + | REVERSE_SYM {} + | ROLE_SYM {} | ROLLUP_SYM {} | ROUTINE_SYM {} | ROWS_SYM {} + | ROW_COUNT_SYM {} | ROW_FORMAT_SYM {} | ROW_SYM {} | RTREE_SYM {} @@ -13290,7 +14245,7 @@ keyword_sp: | SESSION_SYM {} | SIMPLE_SYM {} | SHARE_SYM {} - | SHUTDOWN {} + | SLAVE_POS_SYM {} | SLOW {} | SNAPSHOT_SYM {} | SOFT_SYM {} @@ -13301,6 +14256,7 @@ keyword_sp: | SQL_NO_CACHE_SYM {} | SQL_THREAD {} | STARTS_SYM {} + | STATEMENT_SYM {} | STATUS_SYM {} | STORAGE_SYM {} | STRING_SYM {} @@ -13314,7 +14270,6 @@ keyword_sp: | SWAPS_SYM {} | SWITCHES_SYM {} | TABLE_NAME_SYM {} - | TABLE_STATS_SYM {} | TABLES {} | TABLE_CHECKSUM_SYM {} | TABLESPACE {} @@ -13339,8 +14294,7 @@ keyword_sp: | UNDOFILE_SYM {} | UNKNOWN_SYM {} | UNTIL_SYM {} - | USER {} - | USER_STATS_SYM {} + | USER_SYM {} | USE_FRM {} | VARIABLES {} | VIEW_SYM {} @@ -13349,6 +14303,7 @@ keyword_sp: | WARNINGS {} | WAIT_SYM {} | WEEK_SYM {} + | WEIGHT_STRING_SYM {} | WORK_SYM {} | X509_SYM {} | XML_SYM {} @@ -13356,125 +14311,138 @@ keyword_sp: | VIA_SYM {} ; -/* Option functions */ +/* + SQLCOM_SET_OPTION statement. + + Note that to avoid shift/reduce conflicts, we have separate rules for the + first option listed in the statement. +*/ set: - SET opt_option + SET { LEX *lex=Lex; lex->sql_command= SQLCOM_SET_OPTION; mysql_init_select(lex); lex->option_type=OPT_SESSION; lex->var_list.empty(); - lex->one_shot_set= 0; lex->autocommit= 0; + sp_create_assignment_lex(thd, yychar == YYEMPTY); } - option_value_list + start_option_value_list {} + | SET STATEMENT_SYM + { + LEX *lex= Lex; + mysql_init_select(lex); + lex->option_type= OPT_SESSION; + lex->sql_command= SQLCOM_SET_OPTION; + lex->autocommit= 0; + } + set_stmt_option_value_following_option_type_list + { + LEX *lex= Lex; + if (lex->table_or_sp_used()) + my_yyabort_error((ER_SUBQUERIES_NOT_SUPPORTED, MYF(0), "SET STATEMENT")); + lex->stmt_var_list= lex->var_list; + lex->var_list.empty(); + } + FOR_SYM verb_clause + {} ; -opt_option: - /* empty */ {} - | OPTION {} - ; - -option_value_list: - option_type_value - | option_value_list ',' option_type_value - ; +set_stmt_option_value_following_option_type_list: + /* + Only system variables can be used here. If this condition is changed + please check careful code under lex->option_type == OPT_STATEMENT + condition on wrong type casts. + */ + option_value_following_option_type + | set_stmt_option_value_following_option_type_list ',' option_value_following_option_type -option_type_value: +// Start of option value list +start_option_value_list: + option_value_no_option_type { - LEX *lex= thd->lex; - Lex_input_stream *lip= YYLIP; + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) + MYSQL_YYABORT; + } + option_value_list_continued + | TRANSACTION_SYM + { + Lex->option_type= OPT_DEFAULT; + } + transaction_characteristics + { + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) + MYSQL_YYABORT; + } + | option_type + { + Lex->option_type= $1; + } + start_option_value_list_following_option_type + ; - if (lex->sphead) - { - /* - If we are in SP we want have own LEX for each assignment. - This is mostly because it is hard for several sp_instr_set - and sp_instr_set_trigger instructions share one LEX. - (Well, it is theoretically possible but adds some extra - overhead on preparation for execution stage and IMO less - robust). - - QQ: May be we should simply prohibit group assignments in SP? - */ - lex->sphead->reset_lex(thd); - lex= thd->lex; - /* Set new LEX as if we at start of set rule. */ - lex->sql_command= SQLCOM_SET_OPTION; - mysql_init_select(lex); - lex->option_type=OPT_SESSION; - lex->var_list.empty(); - lex->one_shot_set= 0; - lex->autocommit= 0; - lex->sphead->m_tmp_query= lip->get_tok_start(); - } +// Start of option value list, option_type was given +start_option_value_list_following_option_type: + option_value_following_option_type + { + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) + MYSQL_YYABORT; } - ext_option_value + option_value_list_continued + | TRANSACTION_SYM transaction_characteristics { - LEX *lex= thd->lex; - Lex_input_stream *lip= YYLIP; - - if (lex->sphead) - { - sp_head *sp= lex->sphead; + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) + MYSQL_YYABORT; + } + ; - if (!lex->var_list.is_empty()) - { - /* - We have assignment to user or system variable or - option setting, so we should construct sp_instr_stmt - for it. - */ - LEX_STRING qbuff; - sp_instr_stmt *i; +// Remainder of the option value list after first option value. +option_value_list_continued: + /* empty */ + | ',' option_value_list + ; - if (!(i= new sp_instr_stmt(sp->instructions(), lex->spcont, - lex))) - MYSQL_YYABORT; +// Repeating list of option values after first option value. +option_value_list: + { + sp_create_assignment_lex(thd, yychar == YYEMPTY); + } + option_value + { + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) + MYSQL_YYABORT; + } + | option_value_list ',' + { + sp_create_assignment_lex(thd, yychar == YYEMPTY); + } + option_value + { + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) + MYSQL_YYABORT; + } + ; - /* - Extract the query statement from the tokenizer. The - end is either lip->ptr, if there was no lookahead, - lip->tok_end otherwise. - */ - if (yychar == YYEMPTY) - qbuff.length= lip->get_ptr() - sp->m_tmp_query; - else - qbuff.length= lip->get_tok_end() - sp->m_tmp_query; - - if (!(qbuff.str= (char*) alloc_root(thd->mem_root, - qbuff.length + 5))) - MYSQL_YYABORT; - - strmake(strmake(qbuff.str, "SET ", 4), sp->m_tmp_query, - qbuff.length); - qbuff.length+= 4; - i->m_query= qbuff; - if (sp->add_instr(i)) - MYSQL_YYABORT; - } - if (lex->sphead->restore_lex(thd)) - MYSQL_YYABORT; - } +// Wrapper around option values following the first option value in the stmt. +option_value: + option_type + { + Lex->option_type= $1; } + option_value_following_option_type + | option_value_no_option_type ; option_type: - option_type2 {} - | GLOBAL_SYM { $$=OPT_GLOBAL; } + GLOBAL_SYM { $$=OPT_GLOBAL; } | LOCAL_SYM { $$=OPT_SESSION; } | SESSION_SYM { $$=OPT_SESSION; } ; -option_type2: - /* empty */ { $$= OPT_DEFAULT; } - | ONE_SHOT_SYM { Lex->one_shot_set= 1; $$= OPT_SESSION; } - ; - opt_var_type: /* empty */ { $$=OPT_SESSION; } | GLOBAL_SYM { $$=OPT_GLOBAL; } @@ -13489,81 +14457,68 @@ opt_var_ident_type: | SESSION_SYM '.' { $$=OPT_SESSION; } ; -ext_option_value: - sys_option_value - | option_type2 option_value +// Option values with preceding option_type. +option_value_following_option_type: + internal_variable_name equal set_expr_or_default + { + LEX *lex= Lex; + + if ($1.var && $1.var != trg_new_row_fake_var) + { + /* It is a system variable. */ + if (set_system_variable(thd, &$1, lex->option_type, $3)) + MYSQL_YYABORT; + } + else + { + /* + Not in trigger assigning value to new row, + and option_type preceding local variable is illegal. + */ + my_parse_error(thd, ER_SYNTAX_ERROR); + MYSQL_YYABORT; + } + } ; -sys_option_value: - option_type internal_variable_name equal set_expr_or_default +// Option values without preceding option_type. +option_value_no_option_type: + internal_variable_name equal set_expr_or_default { LEX *lex= Lex; - LEX_STRING *name= &$2.base_name; - if ($2.var == trg_new_row_fake_var) + if ($1.var == trg_new_row_fake_var) { /* We are in trigger and assigning value to field of new row */ - if ($1) - { - my_parse_error(ER(ER_SYNTAX_ERROR)); - MYSQL_YYABORT; - } - if (set_trigger_new_row(thd, name, $4)) + if (set_trigger_new_row(thd, &$1.base_name, $3)) MYSQL_YYABORT; } - else if ($2.var) + else if ($1.var) { - if ($1) - lex->option_type= $1; - /* It is a system variable. */ - if (set_system_variable(thd, &$2, lex->option_type, $4)) + if (set_system_variable(thd, &$1, lex->option_type, $3)) MYSQL_YYABORT; } else { sp_pcontext *spc= lex->spcont; - sp_variable_t *spv= spc->find_variable(name); - - if ($1) - { - my_parse_error(ER(ER_SYNTAX_ERROR)); - MYSQL_YYABORT; - } + sp_variable *spv= spc->find_variable($1.base_name, false); /* It is a local variable. */ - if (set_local_variable(thd, spv, $4)) + if (set_local_variable(thd, spv, $3)) MYSQL_YYABORT; } } - | option_type TRANSACTION_SYM ISOLATION LEVEL_SYM isolation_types - { - LEX *lex=Lex; - lex->option_type= $1; - Item *item= new (thd->mem_root) Item_int((int32) $5); - if (item == NULL) - MYSQL_YYABORT; - set_var *var= new set_var(lex->option_type, - find_sys_var(thd, "tx_isolation"), - &null_lex_str, - item); - if (var == NULL) - MYSQL_YYABORT; - lex->var_list.push_back(var); - } - ; - -option_value: - '@' ident_or_text equal expr + | '@' ident_or_text equal expr { Item_func_set_user_var *item; - item= new (thd->mem_root) Item_func_set_user_var($2, $4); + item= new (thd->mem_root) Item_func_set_user_var(thd, $2, $4); if (item == NULL) MYSQL_YYABORT; - set_var_user *var= new set_var_user(item); + set_var_user *var= new (thd->mem_root) set_var_user(item); if (var == NULL) MYSQL_YYABORT; - Lex->var_list.push_back(var); + Lex->var_list.push_back(var, thd->mem_root); } | '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default { @@ -13588,12 +14543,13 @@ option_value: CHARSET_INFO *cs2; cs2= $2 ? $2: global_system_variables.character_set_client; set_var_collation_client *var; - var= new set_var_collation_client(cs2, - thd->variables.collation_database, - cs2); + var= (new (thd->mem_root) + set_var_collation_client(cs2, + thd->variables.collation_database, + cs2)); if (var == NULL) MYSQL_YYABORT; - lex->var_list.push_back(var); + lex->var_list.push_back(var, thd->mem_root); } | NAMES_SYM equal expr { @@ -13603,10 +14559,10 @@ option_value: names.str= (char *)"names"; names.length= 5; - if (spc && spc->find_variable(&names)) + if (spc && spc->find_variable(names, false)) my_error(ER_SP_BAD_VAR_SHADOW, MYF(0), names.str); else - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } @@ -13624,57 +14580,71 @@ option_value: MYSQL_YYABORT; } set_var_collation_client *var; - var= new set_var_collation_client(cs3, cs3, cs3); + var= new (thd->mem_root) set_var_collation_client(cs3, cs3, cs3); if (var == NULL) MYSQL_YYABORT; - lex->var_list.push_back(var); + lex->var_list.push_back(var, thd->mem_root); } - | PASSWORD equal text_or_password + | DEFAULT ROLE_SYM grant_role { - LEX *lex= thd->lex; + LEX *lex = Lex; LEX_USER *user; - sp_pcontext *spc= lex->spcont; - LEX_STRING pw; - - pw.str= (char *)"password"; - pw.length= 8; - if (spc && spc->find_variable(&pw)) - { - my_error(ER_SP_BAD_VAR_SHADOW, MYF(0), pw.str); + if (!(user=(LEX_USER *) thd->calloc(sizeof(LEX_USER)))) MYSQL_YYABORT; - } - if (!(user=(LEX_USER*) thd->alloc(sizeof(LEX_USER)))) + user->user= current_user; + set_var_default_role *var= (new (thd->mem_root) + set_var_default_role(user, + $3->user)); + if (var == NULL) MYSQL_YYABORT; - user->host=null_lex_str; - user->user.str=thd->security_ctx->user; - set_var_password *var= new set_var_password(user, $3); + lex->var_list.push_back(var, thd->mem_root); + thd->lex->autocommit= TRUE; + if (lex->sphead) + lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT; + } + | DEFAULT ROLE_SYM grant_role FOR_SYM user + { + LEX *lex = Lex; + set_var_default_role *var= (new (thd->mem_root) + set_var_default_role($5, $3->user)); if (var == NULL) MYSQL_YYABORT; - thd->lex->var_list.push_back(var); + lex->var_list.push_back(var, thd->mem_root); thd->lex->autocommit= TRUE; if (lex->sphead) lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT; } - | PASSWORD FOR_SYM user equal text_or_password + | ROLE_SYM ident_or_text + { + LEX *lex = Lex; + set_var_role *var= new (thd->mem_root) set_var_role($2); + if (var == NULL) + MYSQL_YYABORT; + lex->var_list.push_back(var, thd->mem_root); + } + | PASSWORD_SYM opt_for_user text_or_password { - set_var_password *var= new set_var_password($3,$5); + LEX *lex = Lex; + set_var_password *var= (new (thd->mem_root) + set_var_password(lex->definer)); if (var == NULL) MYSQL_YYABORT; - Lex->var_list.push_back(var); - Lex->autocommit= TRUE; - if (Lex->sphead) - Lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT; + lex->var_list.push_back(var, thd->mem_root); + lex->autocommit= TRUE; + if (lex->sphead) + lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT; } ; + internal_variable_name: ident { sp_pcontext *spc= thd->lex->spcont; - sp_variable_t *spv; + sp_variable *spv; /* Best effort lookup for system variable. */ - if (!spc || !(spv = spc->find_variable(&$1))) + if (!spc || !(spv = spc->find_variable($1, false))) { struct sys_var_with_base tmp= {NULL, $1}; @@ -13699,7 +14669,7 @@ internal_variable_name: LEX *lex= Lex; if (check_reserved_words(&$1)) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } if (lex->sphead && lex->sphead->m_type == TYPE_ENUM_TRIGGER && @@ -13707,10 +14677,7 @@ internal_variable_name: !my_strcasecmp(system_charset_info, $1.str, "OLD"))) { if ($1.str[0]=='O' || $1.str[0]=='o') - { - my_error(ER_TRG_CANT_CHANGE_ROW, MYF(0), "OLD", ""); - MYSQL_YYABORT; - } + my_yyabort_error((ER_TRG_CANT_CHANGE_ROW, MYF(0), "OLD", "")); if (lex->trg_chistics.event == TRG_EVENT_DELETE) { my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), @@ -13718,10 +14685,7 @@ internal_variable_name: MYSQL_YYABORT; } if (lex->trg_chistics.action_time == TRG_ACTION_AFTER) - { - my_error(ER_TRG_CANT_CHANGE_ROW, MYF(0), "NEW", "after "); - MYSQL_YYABORT; - } + my_yyabort_error((ER_TRG_CANT_CHANGE_ROW, MYF(0), "NEW", "after ")); /* This special combination will denote field of NEW row */ $$.var= trg_new_row_fake_var; $$.base_name= $3; @@ -13750,6 +14714,54 @@ internal_variable_name: } ; +transaction_characteristics: + transaction_access_mode + | isolation_level + | transaction_access_mode ',' isolation_level + | isolation_level ',' transaction_access_mode + ; + +transaction_access_mode: + transaction_access_mode_types + { + LEX *lex=Lex; + Item *item= new (thd->mem_root) Item_int(thd, (int32) $1); + if (item == NULL) + MYSQL_YYABORT; + set_var *var= (new (thd->mem_root) + set_var(thd, lex->option_type, + find_sys_var(thd, "tx_read_only"), + &null_lex_str, + item)); + if (var == NULL) + MYSQL_YYABORT; + lex->var_list.push_back(var, thd->mem_root); + } + ; + +isolation_level: + ISOLATION LEVEL_SYM isolation_types + { + LEX *lex=Lex; + Item *item= new (thd->mem_root) Item_int(thd, (int32) $3); + if (item == NULL) + MYSQL_YYABORT; + set_var *var= (new (thd->mem_root) + set_var(thd, lex->option_type, + find_sys_var(thd, "tx_isolation"), + &null_lex_str, + item)); + if (var == NULL) + MYSQL_YYABORT; + lex->var_list.push_back(var, thd->mem_root); + } + ; + +transaction_access_mode_types: + READ_SYM ONLY_SYM { $$= true; } + | READ_SYM WRITE_SYM { $$= false; } + ; + isolation_types: READ_SYM UNCOMMITTED_SYM { $$= ISO_READ_UNCOMMITTED; } | READ_SYM COMMITTED_SYM { $$= ISO_READ_COMMITTED; } @@ -13757,46 +14769,54 @@ isolation_types: | SERIALIZABLE_SYM { $$= ISO_SERIALIZABLE; } ; -text_or_password: - TEXT_STRING { $$=$1.str;} - | PASSWORD '(' TEXT_STRING ')' +opt_for_user: + equal { - $$= $3.length ? thd->variables.old_passwords ? - Item_func_old_password::alloc(thd, $3.str, $3.length) : - Item_func_password::alloc(thd, $3.str, $3.length) : - $3.str; - if ($$ == NULL) + LEX *lex= thd->lex; + sp_pcontext *spc= lex->spcont; + LEX_STRING pw= { C_STRING_WITH_LEN("password") }; + + if (spc && spc->find_variable(pw, false)) + my_yyabort_error((ER_SP_BAD_VAR_SHADOW, MYF(0), pw.str)); + if (!(lex->definer= (LEX_USER*) thd->calloc(sizeof(LEX_USER)))) MYSQL_YYABORT; + lex->definer->user= current_user; + lex->definer->plugin= empty_lex_str; + lex->definer->auth= empty_lex_str; } - | OLD_PASSWORD '(' TEXT_STRING ')' + | FOR_SYM user equal { Lex->definer= $2; } + ; + +text_or_password: + TEXT_STRING { Lex->definer->pwhash= $1;} + | PASSWORD_SYM '(' TEXT_STRING ')' { Lex->definer->pwtext= $3; } + | OLD_PASSWORD_SYM '(' TEXT_STRING ')' { - $$= $3.length ? Item_func_old_password::alloc(thd, $3.str, - $3.length) : - $3.str; - if ($$ == NULL) - MYSQL_YYABORT; + Lex->definer->pwtext= $3; + Lex->definer->pwhash.str= Item_func_password::alloc(thd, + $3.str, $3.length, Item_func_password::OLD); + Lex->definer->pwhash.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; } ; - set_expr_or_default: expr { $$=$1; } | DEFAULT { $$=0; } | ON { - $$=new (thd->mem_root) Item_string("ON", 2, system_charset_info); + $$=new (thd->mem_root) Item_string_sys(thd, "ON", 2); if ($$ == NULL) MYSQL_YYABORT; } | ALL { - $$=new (thd->mem_root) Item_string("ALL", 3, system_charset_info); + $$=new (thd->mem_root) Item_string_sys(thd, "ALL", 3); if ($$ == NULL) MYSQL_YYABORT; } | BINARY { - $$=new (thd->mem_root) Item_string("binary", 6, system_charset_info); + $$=new (thd->mem_root) Item_string_sys(thd, "binary", 6); if ($$ == NULL) MYSQL_YYABORT; } @@ -13810,10 +14830,7 @@ lock: LEX *lex= Lex; if (lex->sphead) - { - my_error(ER_SP_BADSTATEMENT, MYF(0), "LOCK"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "LOCK")); lex->sql_command= SQLCOM_LOCK_TABLES; } table_lock_list @@ -13863,10 +14880,7 @@ unlock: LEX *lex= Lex; if (lex->sphead) - { - my_error(ER_SP_BADSTATEMENT, MYF(0), "UNLOCK"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "UNLOCK")); lex->sql_command= SQLCOM_UNLOCK_TABLES; } table_or_tables @@ -13882,47 +14896,38 @@ handler: { LEX *lex= Lex; if (lex->sphead) - { - my_error(ER_SP_BADSTATEMENT, MYF(0), "HANDLER"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "HANDLER")); lex->sql_command = SQLCOM_HA_OPEN; - if (!lex->current_select->add_table_to_list(lex->thd, $2, $4, 0)) + if (!lex->current_select->add_table_to_list(thd, $2, $4, 0)) MYSQL_YYABORT; } | HANDLER_SYM table_ident_nodb CLOSE_SYM { LEX *lex= Lex; if (lex->sphead) - { - my_error(ER_SP_BADSTATEMENT, MYF(0), "HANDLER"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "HANDLER")); lex->sql_command = SQLCOM_HA_CLOSE; - if (!lex->current_select->add_table_to_list(lex->thd, $2, 0, 0)) + if (!lex->current_select->add_table_to_list(thd, $2, 0, 0)) MYSQL_YYABORT; } | HANDLER_SYM table_ident_nodb READ_SYM { LEX *lex=Lex; if (lex->sphead) - { - my_error(ER_SP_BADSTATEMENT, MYF(0), "HANDLER"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "HANDLER")); lex->expr_allows_subselect= FALSE; lex->sql_command = SQLCOM_HA_READ; lex->ha_rkey_mode= HA_READ_KEY_EXACT; /* Avoid purify warnings */ - Item *one= new (thd->mem_root) Item_int((int32) 1); + Item *one= new (thd->mem_root) Item_int(thd, (int32) 1); if (one == NULL) MYSQL_YYABORT; lex->current_select->select_limit= one; lex->current_select->offset_limit= 0; lex->limit_rows_examined= 0; - if (!lex->current_select->add_table_to_list(lex->thd, $2, 0, 0)) + if (!lex->current_select->add_table_to_list(thd, $2, 0, 0)) MYSQL_YYABORT; } - handler_read_or_scan where_clause opt_limit_clause + handler_read_or_scan opt_where_clause opt_limit_clause { Lex->expr_allows_subselect= TRUE; /* Stored functions are not supported for HANDLER READ. */ @@ -13955,7 +14960,7 @@ handler_rkey_function: LEX *lex=Lex; lex->ha_read_mode = RKEY; lex->ha_rkey_mode=$1; - if (!(lex->insert_list = new List_item)) + if (!(lex->insert_list= new (thd->mem_root) List_item)) MYSQL_YYABORT; } '(' values ')' @@ -13978,35 +14983,35 @@ revoke: ; revoke_command: - grant_privileges ON opt_table grant_ident FROM user_list + grant_privileges ON opt_table grant_ident FROM user_and_role_list { LEX *lex= Lex; lex->sql_command= SQLCOM_REVOKE; lex->type= 0; } - | grant_privileges ON FUNCTION_SYM grant_ident FROM user_list + | grant_privileges ON FUNCTION_SYM grant_ident FROM user_and_role_list { LEX *lex= Lex; if (lex->columns.elements) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } lex->sql_command= SQLCOM_REVOKE; lex->type= TYPE_ENUM_FUNCTION; } - | grant_privileges ON PROCEDURE_SYM grant_ident FROM user_list + | grant_privileges ON PROCEDURE_SYM grant_ident FROM user_and_role_list { LEX *lex= Lex; if (lex->columns.elements) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } lex->sql_command= SQLCOM_REVOKE; lex->type= TYPE_ENUM_PROCEDURE; } - | ALL opt_privileges ',' GRANT OPTION FROM user_list + | ALL opt_privileges ',' GRANT OPTION FROM user_and_role_list { Lex->sql_command = SQLCOM_REVOKE_ALL; } @@ -14016,9 +15021,22 @@ revoke_command: lex->users_list.push_front ($3); lex->sql_command= SQLCOM_REVOKE; lex->type= TYPE_ENUM_PROXY; - } + } + | admin_option_for_role FROM user_and_role_list + { + Lex->sql_command= SQLCOM_REVOKE_ROLE; + if (Lex->users_list.push_front($1, thd->mem_root)) + MYSQL_YYABORT; + } ; +admin_option_for_role: + ADMIN_SYM OPTION FOR_SYM grant_role + { Lex->with_admin_option= true; $$= $4; } + | grant_role + { Lex->with_admin_option= false; $$= $1; } + ; + grant: GRANT clear_privileges grant_command {} @@ -14038,7 +15056,7 @@ grant_command: LEX *lex= Lex; if (lex->columns.elements) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } lex->sql_command= SQLCOM_GRANT; @@ -14050,7 +15068,7 @@ grant_command: LEX *lex= Lex; if (lex->columns.elements) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } lex->sql_command= SQLCOM_GRANT; @@ -14062,7 +15080,70 @@ grant_command: lex->users_list.push_front ($3); lex->sql_command= SQLCOM_GRANT; lex->type= TYPE_ENUM_PROXY; - } + } + | grant_role TO_SYM grant_list opt_with_admin_option + { + LEX *lex= Lex; + lex->sql_command= SQLCOM_GRANT_ROLE; + /* The first role is the one that is granted */ + if (Lex->users_list.push_front($1, thd->mem_root)) + MYSQL_YYABORT; + } + + ; + +opt_with_admin: + /* nothing */ { Lex->definer = 0; } + | WITH ADMIN_SYM user_or_role { Lex->definer = $3; } + +opt_with_admin_option: + /* nothing */ { Lex->with_admin_option= false; } + | WITH ADMIN_SYM OPTION { Lex->with_admin_option= true; } + +role_list: + grant_role + { + if (Lex->users_list.push_back($1, thd->mem_root)) + MYSQL_YYABORT; + } + | role_list ',' grant_role + { + if (Lex->users_list.push_back($3, thd->mem_root)) + MYSQL_YYABORT; + } + ; + +current_role: + CURRENT_ROLE optional_braces + { + if (!($$=(LEX_USER*) thd->calloc(sizeof(LEX_USER)))) + MYSQL_YYABORT; + $$->user= current_role; + $$->reset_auth(); + } + ; + +grant_role: + ident_or_text + { + CHARSET_INFO *cs= system_charset_info; + /* trim end spaces (as they'll be lost in mysql.user anyway) */ + $1.length= cs->cset->lengthsp(cs, $1.str, $1.length); + $1.str[$1.length] = '\0'; + if ($1.length == 0) + my_yyabort_error((ER_INVALID_ROLE, MYF(0), "")); + if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) + MYSQL_YYABORT; + $$->user = $1; + $$->host= empty_lex_str; + $$->reset_auth(); + + if (check_string_char_length(&$$->user, ER_USERNAME, + username_char_length, + cs, 0)) + MYSQL_YYABORT; + } + | current_role ; opt_table: @@ -14124,7 +15205,7 @@ object_privilege: | SHOW VIEW_SYM { Lex->grant |= SHOW_VIEW_ACL; } | CREATE ROUTINE_SYM { Lex->grant |= CREATE_PROC_ACL; } | ALTER ROUTINE_SYM { Lex->grant |= ALTER_PROC_ACL; } - | CREATE USER { Lex->grant |= CREATE_USER_ACL; } + | CREATE USER_SYM { Lex->grant |= CREATE_USER_ACL; } | EVENT_SYM { Lex->grant |= EVENT_ACL;} | TRIGGER_SYM { Lex->grant |= TRIGGER_ACL; } | CREATE TABLESPACE { Lex->grant |= CREATE_TABLESPACE_ACL; } @@ -14145,30 +15226,21 @@ require_list_element: { LEX *lex=Lex; if (lex->x509_subject) - { - my_error(ER_DUP_ARGUMENT, MYF(0), "SUBJECT"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "SUBJECT")); lex->x509_subject=$2.str; } | ISSUER_SYM TEXT_STRING { LEX *lex=Lex; if (lex->x509_issuer) - { - my_error(ER_DUP_ARGUMENT, MYF(0), "ISSUER"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "ISSUER")); lex->x509_issuer=$2.str; } | CIPHER_SYM TEXT_STRING { LEX *lex=Lex; if (lex->ssl_cipher) - { - my_error(ER_DUP_ARGUMENT, MYF(0), "CIPHER"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "CIPHER")); lex->ssl_cipher=$2.str; } ; @@ -14183,11 +15255,7 @@ grant_ident: if (lex->grant == GLOBAL_ACLS) lex->grant = DB_ACLS & ~GRANT_ACL; else if (lex->columns.elements) - { - my_message(ER_ILLEGAL_GRANT_FOR_TABLE, - ER(ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_ILLEGAL_GRANT_FOR_TABLE, MYF(0))); } | ident '.' '*' { @@ -14196,11 +15264,7 @@ grant_ident: if (lex->grant == GLOBAL_ACLS) lex->grant = DB_ACLS & ~GRANT_ACL; else if (lex->columns.elements) - { - my_message(ER_ILLEGAL_GRANT_FOR_TABLE, - ER(ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_ILLEGAL_GRANT_FOR_TABLE, MYF(0))); } | '*' '.' '*' { @@ -14209,16 +15273,12 @@ grant_ident: if (lex->grant == GLOBAL_ACLS) lex->grant= GLOBAL_ACLS & ~GRANT_ACL; else if (lex->columns.elements) - { - my_message(ER_ILLEGAL_GRANT_FOR_TABLE, - ER(ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0)); - MYSQL_YYABORT; - } + my_yyabort_error((ER_ILLEGAL_GRANT_FOR_TABLE, MYF(0))); } | table_ident { LEX *lex=Lex; - if (!lex->current_select->add_table_to_list(lex->thd, $1,NULL, + if (!lex->current_select->add_table_to_list(thd, $1,NULL, TL_OPTION_UPDATING)) MYSQL_YYABORT; if (lex->grant == GLOBAL_ACLS) @@ -14229,12 +15289,12 @@ grant_ident: user_list: user { - if (Lex->users_list.push_back($1)) + if (Lex->users_list.push_back($1, thd->mem_root)) MYSQL_YYABORT; } | user_list ',' user { - if (Lex->users_list.push_back($3)) + if (Lex->users_list.push_back($3, thd->mem_root)) MYSQL_YYABORT; } ; @@ -14242,12 +15302,25 @@ user_list: grant_list: grant_user { - if (Lex->users_list.push_back($1)) + if (Lex->users_list.push_back($1, thd->mem_root)) MYSQL_YYABORT; } | grant_list ',' grant_user { - if (Lex->users_list.push_back($3)) + if (Lex->users_list.push_back($3, thd->mem_root)) + MYSQL_YYABORT; + } + ; + +user_and_role_list: + user_or_role + { + if (Lex->users_list.push_back($1, thd->mem_root)) + MYSQL_YYABORT; + } + | user_and_role_list ',' user_or_role + { + if (Lex->users_list.push_back($3, thd->mem_root)) MYSQL_YYABORT; } ; @@ -14258,35 +15331,15 @@ using_or_as: USING | AS ; grant_user: user IDENTIFIED_SYM BY TEXT_STRING { - $$=$1; $1->password=$4; - if ($4.length) - { - if (thd->variables.old_passwords) - { - char *buff= - (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1); - if (buff == NULL) - MYSQL_YYABORT; - my_make_scrambled_password_323(buff, $4.str, $4.length); - $1->password.str= buff; - $1->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; - } - else - { - char *buff= - (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1); - if (buff == NULL) - MYSQL_YYABORT; - my_make_scrambled_password(buff, $4.str, $4.length); - $1->password.str= buff; - $1->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH; - } - } + $$= $1; + $1->pwtext= $4; + if (Lex->sql_command == SQLCOM_REVOKE) + MYSQL_YYABORT; } - | user IDENTIFIED_SYM BY PASSWORD TEXT_STRING + | user IDENTIFIED_SYM BY PASSWORD_SYM TEXT_STRING { $$= $1; - $1->password= $5; + $1->pwhash= $5; } | user IDENTIFIED_SYM via_or_with ident_or_text { @@ -14300,8 +15353,8 @@ grant_user: $1->plugin= $4; $1->auth= $6; } - | user - { $$= $1; $1->password= null_lex_str; } + | user_or_role + { $$= $1; } ; opt_column_list: @@ -14321,7 +15374,7 @@ column_list: column_list_id: ident { - String *new_str = new (thd->mem_root) String((const char*) $1.str,$1.length,system_charset_info); + String *new_str= new (thd->mem_root) String((const char*) $1.str,$1.length,system_charset_info); if (new_str == NULL) MYSQL_YYABORT; List_iterator <LEX_COLUMN> iter(Lex->columns); @@ -14338,10 +15391,11 @@ column_list_id: point->rights |= lex->which_columns; else { - LEX_COLUMN *col= new LEX_COLUMN (*new_str,lex->which_columns); + LEX_COLUMN *col= (new (thd->mem_root) + LEX_COLUMN(*new_str,lex->which_columns)); if (col == NULL) MYSQL_YYABORT; - lex->columns.push_back(col); + lex->columns.push_back(col, thd->mem_root); } } ; @@ -14407,6 +15461,12 @@ grant_option: lex->mqh.user_conn= $2; lex->mqh.specified_limits|= USER_RESOURCES::USER_CONNECTIONS; } + | MAX_STATEMENT_TIME_SYM NUM_literal + { + LEX *lex=Lex; + lex->mqh.max_statement_time= $2->val_real(); + lex->mqh.specified_limits|= USER_RESOURCES::MAX_STATEMENT_TIME; + } ; begin: @@ -14417,6 +15477,20 @@ begin: lex->start_transaction_opt= 0; } opt_work {} + ; + +compound_statement: + sp_proc_stmt_compound_ok + { + Lex->sql_command= SQLCOM_COMPOUND; + Lex->sphead->set_stmt_end(thd); + Lex->sphead->restore_thd_mem_root(thd); + } + ; + +opt_not: + /* nothing */ { $$= 0; } + | not { $$= 1; } ; opt_work: @@ -14519,14 +15593,13 @@ union_list: ; union_opt: - /* Empty */ { $$= 0; } + opt_union_order_or_limit | union_list { $$= 1; } - | union_order_or_limit { $$= 1; } ; opt_union_order_or_limit: - /* Empty */{ $$= false; } - | union_order_or_limit { $$= true; } + /* Empty */ { $$= 0; } + | union_order_or_limit { $$= 1; } ; union_order_or_limit: @@ -14538,7 +15611,6 @@ union_order_or_limit: SELECT_LEX *fake= unit->fake_select_lex; if (fake) { - unit->global_parameters= fake; fake->no_table_names_allowed= 1; lex->current_select= fake; } @@ -14552,7 +15624,7 @@ union_order_or_limit: ; order_or_limit: - order_clause opt_limit_clause_init + order_clause opt_limit_clause | limit_clause ; @@ -14564,17 +15636,19 @@ union_option: query_specification: SELECT_SYM select_init2_derived - { + table_expression + { $$= Lex->current_select->master_unit()->first_select(); } | '(' select_paren_derived ')' + opt_union_order_or_limit { $$= Lex->current_select->master_unit()->first_select(); } ; query_expression_body: - query_specification opt_union_order_or_limit + query_specification | query_expression_body UNION_SYM union_option { @@ -14582,7 +15656,6 @@ query_expression_body: MYSQL_YYABORT; } query_specification - opt_union_order_or_limit { Lex->pop_context(); $$= $1; @@ -14603,7 +15676,7 @@ subselect_start: if (!lex->expr_allows_subselect || lex->sql_command == (int)SQLCOM_PURGE) { - my_parse_error(ER(ER_SYNTAX_ERROR)); + my_parse_error(thd, ER_SYNTAX_ERROR); MYSQL_YYABORT; } /* @@ -14694,7 +15767,7 @@ view_or_trigger_or_sp_or_event: {} | no_definer no_definer_tail {} - | view_replace_or_algorithm definer_opt view_tail + | view_algorithm definer_opt view_tail {} ; @@ -14741,9 +15814,12 @@ no_definer: ; definer: - DEFINER_SYM '=' user + DEFINER_SYM '=' user_or_role { - thd->lex->definer= get_current_user(thd, $3); + Lex->definer= $3; + Lex->ssl_type= SSL_TYPE_NOT_SPECIFIED; + Lex->ssl_cipher= Lex->x509_subject= Lex->x509_issuer= 0; + bzero(&(Lex->mqh), sizeof(Lex->mqh)); } ; @@ -14753,20 +15829,6 @@ definer: **************************************************************************/ -view_replace_or_algorithm: - view_replace - {} - | view_replace view_algorithm - {} - | view_algorithm - {} - ; - -view_replace: - OR_SYM REPLACE - { Lex->create_view_mode= VIEW_CREATE_OR_REPLACE; } - ; - view_algorithm: ALGORITHM_SYM '=' UNDEFINED_SYM { Lex->create_view_algorithm= DTYPE_ALGORITHM_UNDEFINED; } @@ -14786,12 +15848,14 @@ view_suid: ; view_tail: - view_suid VIEW_SYM table_ident + view_suid VIEW_SYM opt_if_not_exists table_ident { LEX *lex= thd->lex; + if (lex->add_create_options_with_check($3)) + MYSQL_YYABORT; lex->sql_command= SQLCOM_CREATE_VIEW; /* first table in list is target VIEW name */ - if (!lex->select_lex.add_table_to_list(thd, $3, NULL, + if (!lex->select_lex.add_table_to_list(thd, $4, NULL, TL_OPTION_UPDATING, TL_IGNORE, MDL_EXCLUSIVE)) @@ -14809,15 +15873,17 @@ view_list_opt: view_list: ident - { - Lex->view_list.push_back((LEX_STRING*) - sql_memdup(&$1, sizeof(LEX_STRING))); - } + { + Lex->view_list.push_back((LEX_STRING*) + thd->memdup(&$1, sizeof(LEX_STRING)), + thd->mem_root); + } | view_list ',' ident - { - Lex->view_list.push_back((LEX_STRING*) - sql_memdup(&$3, sizeof(LEX_STRING))); - } + { + Lex->view_list.push_back((LEX_STRING*) + thd->memdup(&$3, sizeof(LEX_STRING)), + thd->mem_root); + } ; view_select: @@ -14869,52 +15935,46 @@ view_check_option: trigger_tail: TRIGGER_SYM remember_name + opt_if_not_exists + { + if (Lex->add_create_options_with_check($3)) + MYSQL_YYABORT; + } sp_name trg_action_time trg_event ON - remember_name /* $7 */ - { /* $8 */ + remember_name /* $9 */ + { /* $10 */ Lex->raw_trg_on_table_name_begin= YYLIP->get_tok_start(); } - table_ident /* $9 */ + table_ident /* $11 */ FOR_SYM - remember_name /* $11 */ - { /* $12 */ + remember_name /* $13 */ + { /* $14 */ Lex->raw_trg_on_table_name_end= YYLIP->get_tok_start(); } EACH_SYM ROW_SYM - { /* $15 */ + { /* $17 */ LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; - sp_head *sp; if (lex->sphead) - { - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "TRIGGER"); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_NO_RECURSIVE_CREATE, MYF(0), "TRIGGER")); - if (!(sp= new sp_head())) - MYSQL_YYABORT; - sp->reset_thd_mem_root(thd); - sp->init(lex); - sp->m_type= TYPE_ENUM_TRIGGER; - sp->init_sp_name(thd, $3); lex->stmt_definition_begin= $2; - lex->ident.str= $7; - lex->ident.length= $11 - $7; + lex->ident.str= $9; + lex->ident.length= $13 - $9; + lex->spname= $5; - lex->sphead= sp; - lex->spname= $3; + if (!make_sp_head(thd, $5, TYPE_ENUM_TRIGGER)) + MYSQL_YYABORT; - bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); - lex->sphead->m_chistics= &lex->sp_chistics; lex->sphead->set_body_start(thd, lip->get_cpp_ptr()); } - sp_proc_stmt /* $16 */ - { /* $17 */ + sp_proc_stmt /* $18 */ + { /* $19 */ LEX *lex= Lex; sp_head *sp= lex->sphead; @@ -14930,7 +15990,7 @@ trigger_tail: sp_proc_stmt alternatives are not saving/restoring LEX, so lex->query_tables can be wiped out. */ - if (!lex->select_lex.add_table_to_list(thd, $9, + if (!lex->select_lex.add_table_to_list(thd, $11, (LEX_STRING*) 0, TL_OPTION_UPDATING, TL_READ_NO_INSERT, @@ -14946,71 +16006,45 @@ trigger_tail: **************************************************************************/ udf_tail: - AGGREGATE_SYM remember_name FUNCTION_SYM ident + AGGREGATE_SYM udf_tail2 { thd->lex->udf.type= UDFTYPE_AGGREGATE; } + | udf_tail2 { thd->lex->udf.type= UDFTYPE_FUNCTION; } + ; + +udf_tail2: + FUNCTION_SYM opt_if_not_exists ident RETURNS_SYM udf_type SONAME_SYM TEXT_STRING_sys { LEX *lex= thd->lex; - if (is_native_function(thd, & $4)) - { - my_error(ER_NATIVE_FCT_NAME_COLLISION, MYF(0), - $4.str); + if (lex->add_create_options_with_check($2)) MYSQL_YYABORT; - } - lex->sql_command = SQLCOM_CREATE_FUNCTION; - lex->udf.type= UDFTYPE_AGGREGATE; - lex->stmt_definition_begin= $2; - lex->udf.name = $4; - lex->udf.returns=(Item_result) $6; - lex->udf.dl=$8.str; - } - | remember_name FUNCTION_SYM ident - RETURNS_SYM udf_type SONAME_SYM TEXT_STRING_sys - { - LEX *lex= thd->lex; if (is_native_function(thd, & $3)) - { - my_error(ER_NATIVE_FCT_NAME_COLLISION, MYF(0), - $3.str); - MYSQL_YYABORT; - } - lex->sql_command = SQLCOM_CREATE_FUNCTION; - lex->udf.type= UDFTYPE_FUNCTION; - lex->stmt_definition_begin= $1; - lex->udf.name = $3; - lex->udf.returns=(Item_result) $5; - lex->udf.dl=$7.str; + my_yyabort_error((ER_NATIVE_FCT_NAME_COLLISION, MYF(0), $3.str)); + lex->sql_command= SQLCOM_CREATE_FUNCTION; + lex->udf.name= $3; + lex->udf.returns= (Item_result) $5; + lex->udf.dl= $7.str; } ; sf_tail: - remember_name /* $1 */ - FUNCTION_SYM /* $2 */ + FUNCTION_SYM /* $1 */ + opt_if_not_exists /* $2 */ sp_name /* $3 */ '(' /* $4 */ { /* $5 */ - LEX *lex= thd->lex; + LEX *lex= Lex; Lex_input_stream *lip= YYLIP; - sp_head *sp; const char* tmp_param_begin; - lex->stmt_definition_begin= $1; + if (lex->add_create_options_with_check($2)) + MYSQL_YYABORT; lex->spname= $3; if (lex->sphead) - { - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "FUNCTION"); - MYSQL_YYABORT; - } - /* Order is important here: new - reset - init */ - sp= new sp_head(); - if (sp == NULL) - MYSQL_YYABORT; - sp->reset_thd_mem_root(thd); - sp->init(lex); - sp->init_sp_name(thd, lex->spname); + my_yyabort_error((ER_SP_NO_RECURSIVE_CREATE, MYF(0), "FUNCTION")); - sp->m_type= TYPE_ENUM_FUNCTION; - lex->sphead= sp; + if (!make_sp_head(thd, $3, TYPE_ENUM_FUNCTION)) + MYSQL_YYABORT; tmp_param_begin= lip->get_cpp_tok_start(); tmp_param_begin++; @@ -15024,41 +16058,20 @@ sf_tail: RETURNS_SYM /* $9 */ { /* $10 */ LEX *lex= Lex; - lex->charset= NULL; - lex->length= lex->dec= NULL; - lex->interval_list.empty(); - lex->type= 0; - lex->vcol_info= 0; + lex->init_last_field(&lex->sphead->m_return_field_def, NULL, + thd->variables.collation_database); } type_with_opt_collate /* $11 */ { /* $12 */ - LEX *lex= Lex; - sp_head *sp= lex->sphead; - /* - This was disabled in 5.1.12. See bug #20701 - When collation support in SP is implemented, then this test - should be removed. - */ - if (($11 == MYSQL_TYPE_STRING || $11 == MYSQL_TYPE_VARCHAR) - && (lex->type & BINCMP_FLAG)) - { - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "return value collation"); + if (Lex->sphead->fill_field_definition(thd, Lex, $11, + Lex->last_field)) MYSQL_YYABORT; - } - - if (sp->fill_field_definition(thd, lex, - (enum enum_field_types) $11, - &sp->m_return_field_def)) - MYSQL_YYABORT; - - bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); } sp_c_chistics /* $13 */ { /* $14 */ LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; - lex->sphead->m_chistics= &lex->sp_chistics; lex->sphead->set_body_start(thd, lip->get_cpp_tok_start()); } sp_proc_stmt /* $15 */ @@ -15072,10 +16085,7 @@ sf_tail: lex->sql_command= SQLCOM_CREATE_SPFUNCTION; sp->set_stmt_end(thd); if (!(sp->m_flags & sp_head::HAS_RETURN)) - { - my_error(ER_SP_NORETURN, MYF(0), sp->m_qname.str); - MYSQL_YYABORT; - } + my_yyabort_error((ER_SP_NORETURN, MYF(0), sp->m_qname.str)); if (is_native_function(thd, & sp->m_name)) { /* @@ -15094,9 +16104,9 @@ sf_tail: - The user deploys 5.{N+1}. At this point, 'select foo()' means something different, and the user code is most likely broken (it's only safe if the code is 'select db.foo()'). - With a warning printed when the SF is loaded (which has to occur - before the call), the warning will provide a hint explaining - the root cause of a later failure of 'select foo()'. + With a warning printed when the SF is loaded (which has to + occur before the call), the warning will provide a hint + explaining the root cause of a later failure of 'select foo()'. With no warning printed, the user code will fail with no apparent reason. Printing a warning each time db_load_routine is executed for @@ -15106,9 +16116,9 @@ sf_tail: If a collision exists, it should not be silenced but fixed. */ push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_NOTE, + Sql_condition::WARN_LEVEL_NOTE, ER_NATIVE_FCT_NAME_COLLISION, - ER(ER_NATIVE_FCT_NAME_COLLISION), + ER_THD(thd, ER_NATIVE_FCT_NAME_COLLISION), sp->m_name.str); } sp->restore_thd_mem_root(thd); @@ -15116,29 +16126,17 @@ sf_tail: ; sp_tail: - PROCEDURE_SYM remember_name sp_name + PROCEDURE_SYM opt_if_not_exists sp_name { - LEX *lex= Lex; - sp_head *sp; - - if (lex->sphead) - { - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "PROCEDURE"); + if (Lex->add_create_options_with_check($2)) MYSQL_YYABORT; - } - lex->stmt_definition_begin= $2; + if (Lex->sphead) + my_yyabort_error((ER_SP_NO_RECURSIVE_CREATE, MYF(0), "PROCEDURE")); - /* Order is important here: new - reset - init */ - sp= new sp_head(); - if (sp == NULL) + if (!make_sp_head(thd, $3, TYPE_ENUM_PROCEDURE)) MYSQL_YYABORT; - sp->reset_thd_mem_root(thd); - sp->init(lex); - sp->m_type= TYPE_ENUM_PROCEDURE; - sp->init_sp_name(thd, $3); - - lex->sphead= sp; + Lex->spname= $3; } '(' { @@ -15151,17 +16149,11 @@ sp_tail: sp_pdparam_list ')' { - LEX *lex= thd->lex; - - lex->sphead->m_param_end= YYLIP->get_cpp_tok_start(); - bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); + Lex->sphead->m_param_end= YYLIP->get_cpp_tok_start(); } sp_c_chistics { - LEX *lex= thd->lex; - - lex->sphead->m_chistics= &lex->sp_chistics; - lex->sphead->set_body_start(thd, YYLIP->get_cpp_tok_start()); + Lex->sphead->set_body_start(thd, YYLIP->get_cpp_tok_start()); } sp_proc_stmt { diff --git a/sql/strfunc.cc b/sql/strfunc.cc index 06bd92e0bc7..b8100e05ce5 100644 --- a/sql/strfunc.cc +++ b/sql/strfunc.cc @@ -15,6 +15,7 @@ /* Some useful string utility functions used by the MySQL server */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "strfunc.h" @@ -264,27 +265,22 @@ uint check_word(TYPELIB *lib, const char *val, const char *end, */ -uint strconvert(CHARSET_INFO *from_cs, const char *from, +uint strconvert(CHARSET_INFO *from_cs, const char *from, uint from_length, CHARSET_INFO *to_cs, char *to, uint to_length, uint *errors) { int cnvres; my_wc_t wc; char *to_start= to; uchar *to_end= (uchar*) to + to_length - 1; + const uchar *from_end= (const uchar*) from + from_length; my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc; my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb; uint error_count= 0; while (1) { - /* - Using 'from + 10' is safe: - - it is enough to scan a single character in any character set. - - if remaining string is shorter than 10, then mb_wc will return - with error because of unexpected '\0' character. - */ if ((cnvres= (*mb_wc)(from_cs, &wc, - (uchar*) from, (uchar*) from + 10)) > 0) + (uchar*) from, from_end)) > 0) { if (!wc) break; diff --git a/sql/strfunc.h b/sql/strfunc.h index 57c5427fcd0..7b031710c76 100644 --- a/sql/strfunc.h +++ b/sql/strfunc.h @@ -43,7 +43,7 @@ char *set_to_string(THD *thd, LEX_STRING *result, ulonglong set, /* These functions were protected by INNODB_COMPATIBILITY_HOOKS */ -uint strconvert(CHARSET_INFO *from_cs, const char *from, +uint strconvert(CHARSET_INFO *from_cs, const char *from, uint from_length, CHARSET_INFO *to_cs, char *to, uint to_length, uint *errors); #endif /* STRFUNC_INCLUDED */ diff --git a/sql/structs.h b/sql/structs.h index 9840cec6a35..2ab102d82f9 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -29,6 +29,7 @@ struct TABLE; class Field; +class Index_statistics; class THD; @@ -89,14 +90,31 @@ struct ha_index_option_struct; typedef struct st_key { uint key_length; /* Tot length of key */ ulong flags; /* dupp key and pack flags */ - uint key_parts; /* How many key_parts */ - uint usable_key_parts; /* Should normally be = key_parts */ + uint user_defined_key_parts; /* How many key_parts */ + uint usable_key_parts; /* Should normally be = user_defined_key_parts */ uint ext_key_parts; /* Number of key parts in extended key */ ulong ext_key_flags; /* Flags for extended key */ - key_part_map ext_key_part_map; /* Bitmap of pk key parts in extension */ + /* + Parts of primary key that are in the extension of this index. + + Example: if this structure describes idx1, which is defined as + INDEX idx1 (pk2, col2) + and pk is defined as: + PRIMARY KEY (pk1, pk2) + then + pk1 is in the extension idx1, ext_key_part_map.is_set(0) == true + pk2 is explicitly present in idx1, it is not in the extension, so + ext_key_part_map.is_set(1) == false + */ + key_part_map ext_key_part_map; uint block_size; uint name_length; enum ha_key_alg algorithm; + /* + The flag is on if statistical data for the index prefixes + has to be taken from the system statistical tables. + */ + bool is_statistics_from_stat_tables; /* Note that parser is used when the table is opened for use, and parser_name is used when the table is being created. @@ -116,6 +134,18 @@ typedef struct st_key { For temporary heap tables this member is NULL. */ ulong *rec_per_key; + + /* + This structure is used for statistical data on the index + that has been read from the statistical table index_stat + */ + Index_statistics *read_stats; + /* + This structure is used for statistical data on the index that + is collected by the function collect_statistics_for_table + */ + Index_statistics *collected_stats; + union { int bdb_return_if_eq; } handler; @@ -124,6 +154,9 @@ typedef struct st_key { /** reference to the list of options or NULL */ engine_option_value *option_list; ha_index_option_struct *option_struct; /* structure with parsed options */ + + double actual_rec_per_key(uint i); + } KEY; @@ -169,7 +202,22 @@ extern const char *show_comp_option_name[]; typedef int *(*update_var)(THD *, struct st_mysql_show_var *); typedef struct st_lex_user { - LEX_STRING user, host, password, plugin, auth; + LEX_STRING user, host, plugin, auth; + LEX_STRING pwtext, pwhash; + bool is_role() const { return user.str[0] && !host.str[0]; } + void set_lex_string(LEX_STRING *l, char *buf) + { + if (is_role()) + *l= user; + else + l->length= strxmov(l->str= buf, user.str, "@", host.str, NullS) - buf; + } + void reset_auth() + { + pwtext.length= pwhash.length= plugin.length= auth.length= 0; + pwtext.str= pwhash.str= 0; + plugin.str= auth.str= const_cast<char*>(""); + } } LEX_USER; /* @@ -192,12 +240,15 @@ typedef struct user_resources { connections allowed */ int user_conn; + /* Max query timeout */ + double max_statement_time; + /* Values of this enum and specified_limits member are used by the parser to store which user limits were specified in GRANT statement. */ enum {QUERIES_PER_HOUR= 1, UPDATES_PER_HOUR= 2, CONNECTIONS_PER_HOUR= 4, - USER_CONNECTIONS= 8}; + USER_CONNECTIONS= 8, MAX_STATEMENT_TIME= 16}; uint specified_limits; } USER_RESOURCES; @@ -236,90 +287,29 @@ typedef struct user_conn { typedef struct st_user_stats { - char user[max(USERNAME_LENGTH, LIST_PROCESS_HOST_LEN) + 1]; + char user[MY_MAX(USERNAME_LENGTH, LIST_PROCESS_HOST_LEN) + 1]; // Account name the user is mapped to when this is a user from mapped_user. // Otherwise, the same value as user. - char priv_user[max(USERNAME_LENGTH, LIST_PROCESS_HOST_LEN) + 1]; + char priv_user[MY_MAX(USERNAME_LENGTH, LIST_PROCESS_HOST_LEN) + 1]; uint user_name_length; uint total_connections; + uint total_ssl_connections; uint concurrent_connections; time_t connected_time; // in seconds - double busy_time; // in seconds - double cpu_time; // in seconds + ha_rows rows_read, rows_sent; + ha_rows rows_updated, rows_deleted, rows_inserted; ulonglong bytes_received; ulonglong bytes_sent; ulonglong binlog_bytes_written; - ha_rows rows_read, rows_sent; - ha_rows rows_updated, rows_deleted, rows_inserted; ulonglong select_commands, update_commands, other_commands; ulonglong commit_trans, rollback_trans; - ulonglong denied_connections, lost_connections; + ulonglong denied_connections, lost_connections, max_statement_time_exceeded; ulonglong access_denied_errors; ulonglong empty_queries; + double busy_time; // in seconds + double cpu_time; // in seconds } USER_STATS; -/* Lookup function for hash tables with USER_STATS entries */ -extern "C" uchar *get_key_user_stats(USER_STATS *user_stats, size_t *length, - my_bool not_used __attribute__((unused))); - -/* Free all memory for a hash table with USER_STATS entries */ -extern void free_user_stats(USER_STATS* user_stats); - -/* Intialize an instance of USER_STATS */ -extern void -init_user_stats(USER_STATS *user_stats, - const char *user, - size_t user_length, - const char *priv_user, - uint total_connections, - uint concurrent_connections, - time_t connected_time, - double busy_time, - double cpu_time, - ulonglong bytes_received, - ulonglong bytes_sent, - ulonglong binlog_bytes_written, - ha_rows rows_sent, - ha_rows rows_read, - ha_rows rows_inserted, - ha_rows rows_deleted, - ha_rows rows_updated, - ulonglong select_commands, - ulonglong update_commands, - ulonglong other_commands, - ulonglong commit_trans, - ulonglong rollback_trans, - ulonglong denied_connections, - ulonglong lost_connections, - ulonglong access_denied_errors, - ulonglong empty_queries); - -/* Increment values of an instance of USER_STATS */ -extern void -add_user_stats(USER_STATS *user_stats, - uint total_connections, - uint concurrent_connections, - time_t connected_time, - double busy_time, - double cpu_time, - ulonglong bytes_received, - ulonglong bytes_sent, - ulonglong binlog_bytes_written, - ha_rows rows_sent, - ha_rows rows_read, - ha_rows rows_inserted, - ha_rows rows_deleted, - ha_rows rows_updated, - ulonglong select_commands, - ulonglong update_commands, - ulonglong other_commands, - ulonglong commit_trans, - ulonglong rollback_trans, - ulonglong denied_connections, - ulonglong lost_connections, - ulonglong access_denied_errors, - ulonglong empty_queries); - typedef struct st_table_stats { char table[NAME_LEN * 2 + 2]; // [db] + '\0' + [table] + '\0' @@ -489,4 +479,82 @@ public: Discrete_interval* get_current() const { return current; }; }; + +/* + DDL options: + - CREATE IF NOT EXISTS + - DROP IF EXISTS + - CREATE LIKE + - REPLACE +*/ +struct DDL_options_st +{ +public: + enum Options + { + OPT_NONE= 0, + OPT_IF_NOT_EXISTS= 2, // CREATE TABLE IF NOT EXISTS + OPT_LIKE= 4, // CREATE TABLE LIKE + OPT_OR_REPLACE= 16, // CREATE OR REPLACE TABLE + OPT_OR_REPLACE_SLAVE_GENERATED= 32,// REPLACE was added on slave, it was + // not in the original query on master. + OPT_IF_EXISTS= 64 + }; + +private: + Options m_options; + +public: + Options create_like_options() const + { + return (DDL_options_st::Options) + (((uint) m_options) & (OPT_IF_NOT_EXISTS | OPT_OR_REPLACE)); + } + void init() { m_options= OPT_NONE; } + void init(Options options) { m_options= options; } + void set(Options other) + { + m_options= other; + } + void set(const DDL_options_st other) + { + m_options= other.m_options; + } + bool if_not_exists() const { return m_options & OPT_IF_NOT_EXISTS; } + bool or_replace() const { return m_options & OPT_OR_REPLACE; } + bool or_replace_slave_generated() const + { return m_options & OPT_OR_REPLACE_SLAVE_GENERATED; } + bool like() const { return m_options & OPT_LIKE; } + bool if_exists() const { return m_options & OPT_IF_EXISTS; } + void add(const DDL_options_st::Options other) + { + m_options= (Options) ((uint) m_options | (uint) other); + } + void add(const DDL_options_st &other) + { + add(other.m_options); + } + DDL_options_st operator|(const DDL_options_st &other) + { + add(other.m_options); + return *this; + } + DDL_options_st operator|=(DDL_options_st::Options other) + { + add(other); + return *this; + } +}; + + +class DDL_options: public DDL_options_st +{ +public: + DDL_options() { init(); } + DDL_options(Options options) { init(options); } + DDL_options(const DDL_options_st &options) + { DDL_options_st::operator=(options); } +}; + + #endif /* STRUCTS_INCLUDED */ diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index ffe861ddfbc..223015e81c2 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2002, 2015, Oracle and/or its affiliates. - Copyright (c) 2012, 2015, MariaDB + Copyright (c) 2012, 2018, 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 @@ -14,7 +14,10 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -/* +/** + @file + Definitions of all server's session or global variables. + How to add new variables: 1. copy one of the existing variables, and edit the declaration. @@ -28,11 +31,10 @@ (for example in storage/myisam/ha_myisam.cc) ! */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_plugin.h" // Includes my_global.h #include "sql_priv.h" #include "sql_class.h" // set_var.h: THD -#include "sql_parse.h" -#include "sys_vars.h" +#include "sys_vars.ic" #include "events.h" #include <thr_alarm.h> @@ -46,6 +48,7 @@ // mysql_user_table_is_in_short_password_format #include "derror.h" // read_texts #include "sql_base.h" // close_cached_tables +#include "hostname.h" // host_cache_size #include <myisam.h> #include "log_slow.h" #include "debug_sync.h" // DEBUG_SYNC @@ -56,6 +59,9 @@ #include "../storage/perfschema/pfs_server.h" #endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */ #include "threadpool.h" +#include "sql_repl.h" +#include "opt_range.h" +#include "rpl_parallel.h" /* The rule for this file: everything should be 'static'. When a sys_var @@ -73,20 +79,22 @@ static Sys_var_mybool Sys_pfs_enabled( PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_enabled), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); -static Sys_var_ulong Sys_pfs_events_waits_history_long_size( +static Sys_var_long Sys_pfs_events_waits_history_long_size( "performance_schema_events_waits_history_long_size", - "Number of rows in EVENTS_WAITS_HISTORY_LONG.", + "Number of rows in EVENTS_WAITS_HISTORY_LONG." + " Use 0 to disable, -1 for automated sizing.", PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_waits_history_long_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024), - DEFAULT(PFS_WAITS_HISTORY_LONG_SIZE), BLOCK_SIZE(1)); + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_pfs_events_waits_history_size( +static Sys_var_long Sys_pfs_events_waits_history_size( "performance_schema_events_waits_history_size", - "Number of rows per thread in EVENTS_WAITS_HISTORY.", + "Number of rows per thread in EVENTS_WAITS_HISTORY." + " Use 0 to disable, -1 for automated sizing.", PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_waits_history_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024), - DEFAULT(PFS_WAITS_HISTORY_SIZE), BLOCK_SIZE(1)); + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024), + DEFAULT(-1), BLOCK_SIZE(1)); static Sys_var_ulong Sys_pfs_max_cond_classes( "performance_schema_max_cond_classes", @@ -95,12 +103,13 @@ static Sys_var_ulong Sys_pfs_max_cond_classes( CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256), DEFAULT(PFS_MAX_COND_CLASS), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_pfs_max_cond_instances( +static Sys_var_long Sys_pfs_max_cond_instances( "performance_schema_max_cond_instances", - "Maximum number of instrumented condition objects.", + "Maximum number of instrumented condition objects." + " Use 0 to disable, -1 for automated sizing.", PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_cond_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024), - DEFAULT(PFS_MAX_COND), BLOCK_SIZE(1)); + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), BLOCK_SIZE(1)); static Sys_var_ulong Sys_pfs_max_file_classes( "performance_schema_max_file_classes", @@ -116,12 +125,30 @@ static Sys_var_ulong Sys_pfs_max_file_handles( CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024), DEFAULT(PFS_MAX_FILE_HANDLE), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_pfs_max_file_instances( +static Sys_var_long Sys_pfs_max_file_instances( "performance_schema_max_file_instances", - "Maximum number of instrumented files.", + "Maximum number of instrumented files." + " Use 0 to disable, -1 for automated sizing.", PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_file_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024), - DEFAULT(PFS_MAX_FILE), BLOCK_SIZE(1)); + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_max_sockets( + "performance_schema_max_socket_instances", + "Maximum number of opened instrumented sockets." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_socket_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), + BLOCK_SIZE(1)); + +static Sys_var_ulong Sys_pfs_max_socket_classes( + "performance_schema_max_socket_classes", + "Maximum number of socket instruments.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_socket_class_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256), + DEFAULT(PFS_MAX_SOCKET_CLASS), + BLOCK_SIZE(1)); static Sys_var_ulong Sys_pfs_max_mutex_classes( "performance_schema_max_mutex_classes", @@ -130,12 +157,13 @@ static Sys_var_ulong Sys_pfs_max_mutex_classes( CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256), DEFAULT(PFS_MAX_MUTEX_CLASS), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_pfs_max_mutex_instances( +static Sys_var_long Sys_pfs_max_mutex_instances( "performance_schema_max_mutex_instances", - "Maximum number of instrumented MUTEX objects.", + "Maximum number of instrumented MUTEX objects." + " Use 0 to disable, -1 for automated sizing.", PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_mutex_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 100*1024*1024), - DEFAULT(PFS_MAX_MUTEX), BLOCK_SIZE(1)); + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 100*1024*1024), + DEFAULT(-1), BLOCK_SIZE(1)); static Sys_var_ulong Sys_pfs_max_rwlock_classes( "performance_schema_max_rwlock_classes", @@ -144,26 +172,29 @@ static Sys_var_ulong Sys_pfs_max_rwlock_classes( CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256), DEFAULT(PFS_MAX_RWLOCK_CLASS), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_pfs_max_rwlock_instances( +static Sys_var_long Sys_pfs_max_rwlock_instances( "performance_schema_max_rwlock_instances", - "Maximum number of instrumented RWLOCK objects.", + "Maximum number of instrumented RWLOCK objects." + " Use 0 to disable, -1 for automated sizing.", PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_rwlock_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 100*1024*1024), - DEFAULT(PFS_MAX_RWLOCK), BLOCK_SIZE(1)); + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 100*1024*1024), + DEFAULT(-1), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_pfs_max_table_handles( +static Sys_var_long Sys_pfs_max_table_handles( "performance_schema_max_table_handles", - "Maximum number of opened instrumented tables.", + "Maximum number of opened instrumented tables." + " Use 0 to disable, -1 for automated sizing.", PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_table_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024), - DEFAULT(PFS_MAX_TABLE), BLOCK_SIZE(1)); + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_pfs_max_table_instances( +static Sys_var_long Sys_pfs_max_table_instances( "performance_schema_max_table_instances", - "Maximum number of instrumented tables.", + "Maximum number of instrumented tables." + " Use 0 to disable, -1 for automated sizing.", PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_table_share_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024), - DEFAULT(PFS_MAX_TABLE_SHARE), BLOCK_SIZE(1)); + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), BLOCK_SIZE(1)); static Sys_var_ulong Sys_pfs_max_thread_classes( "performance_schema_max_thread_classes", @@ -172,22 +203,198 @@ static Sys_var_ulong Sys_pfs_max_thread_classes( CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256), DEFAULT(PFS_MAX_THREAD_CLASS), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_pfs_max_thread_instances( +static Sys_var_long Sys_pfs_max_thread_instances( "performance_schema_max_thread_instances", - "Maximum number of instrumented threads.", + "Maximum number of instrumented threads." + " Use 0 to disable, -1 for automated sizing.", PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_thread_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), BLOCK_SIZE(1)); + +static Sys_var_ulong Sys_pfs_setup_actors_size( + "performance_schema_setup_actors_size", + "Maximum number of rows in SETUP_ACTORS.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_setup_actor_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024), + DEFAULT(PFS_MAX_SETUP_ACTOR), + BLOCK_SIZE(1)); + +static Sys_var_ulong Sys_pfs_setup_objects_size( + "performance_schema_setup_objects_size", + "Maximum number of rows in SETUP_OBJECTS.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_setup_object_sizing), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024), - DEFAULT(PFS_MAX_THREAD), BLOCK_SIZE(1)); + DEFAULT(PFS_MAX_SETUP_OBJECT), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_accounts_size( + "performance_schema_accounts_size", + "Maximum number of instrumented user@host accounts." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_account_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_hosts_size( + "performance_schema_hosts_size", + "Maximum number of instrumented hosts." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_host_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_users_size( + "performance_schema_users_size", + "Maximum number of instrumented users." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_user_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), + BLOCK_SIZE(1)); + +static Sys_var_ulong Sys_pfs_max_stage_classes( + "performance_schema_max_stage_classes", + "Maximum number of stage instruments.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_stage_class_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256), + DEFAULT(PFS_MAX_STAGE_CLASS), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_events_stages_history_long_size( + "performance_schema_events_stages_history_long_size", + "Number of rows in EVENTS_STAGES_HISTORY_LONG." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_stages_history_long_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_events_stages_history_size( + "performance_schema_events_stages_history_size", + "Number of rows per thread in EVENTS_STAGES_HISTORY." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_stages_history_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024), + DEFAULT(-1), + BLOCK_SIZE(1)); + +/** + Variable performance_schema_max_statement_classes. + The default number of statement classes is the sum of: + - COM_END for all regular "statement/com/...", + - 1 for "statement/com/new_packet", for unknown enum_server_command + - 1 for "statement/com/Error", for invalid enum_server_command + - SQLCOM_END for all regular "statement/sql/...", + - 1 for "statement/sql/error", for invalid enum_sql_command + - 1 for "statement/rpl/relay_log", for replicated statements. +*/ +static Sys_var_ulong Sys_pfs_max_statement_classes( + "performance_schema_max_statement_classes", + "Maximum number of statement instruments.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_statement_class_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256), + DEFAULT((ulong) SQLCOM_END + (ulong) COM_END + 4), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_events_statements_history_long_size( + "performance_schema_events_statements_history_long_size", + "Number of rows in EVENTS_STATEMENTS_HISTORY_LONG." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_statements_history_long_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024*1024), + DEFAULT(-1), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_events_statements_history_size( + "performance_schema_events_statements_history_size", + "Number of rows per thread in EVENTS_STATEMENTS_HISTORY." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_events_statements_history_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024), + DEFAULT(-1), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_digest_size( + "performance_schema_digests_size", + "Size of the statement digest." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_digest_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 200), + DEFAULT(-1), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_max_digest_length( + "performance_schema_max_digest_length", + "Maximum length considered for digest text, when stored in performance_schema tables.", + PARSED_EARLY READ_ONLY GLOBAL_VAR(pfs_param.m_max_digest_length), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024 * 1024), + DEFAULT(1024), + BLOCK_SIZE(1)); + +static Sys_var_long Sys_pfs_connect_attrs_size( + "performance_schema_session_connect_attrs_size", + "Size of session attribute string buffer per thread." + " Use 0 to disable, -1 for automated sizing.", + PARSED_EARLY READ_ONLY + GLOBAL_VAR(pfs_param.m_session_connect_attrs_sizing), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(-1, 1024 * 1024), + DEFAULT(-1), + BLOCK_SIZE(1)); #endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */ +#ifdef WITH_WSREP + +/* + We need to keep the original values set by the user, as they will + be lost if wsrep_auto_increment_control set to 'ON': +*/ +static bool update_auto_increment_increment (sys_var *self, THD *thd, enum_var_type type) +{ + if (type == OPT_GLOBAL) + global_system_variables.saved_auto_increment_increment= + global_system_variables.auto_increment_increment; + else + thd->variables.saved_auto_increment_increment= + thd->variables.auto_increment_increment; + return false; +} + +#endif /* WITH_WSREP */ + static Sys_var_ulong Sys_auto_increment_increment( "auto_increment_increment", "Auto-increment columns are incremented by this", SESSION_VAR(auto_increment_increment), CMD_LINE(OPT_ARG), VALID_RANGE(1, 65535), DEFAULT(1), BLOCK_SIZE(1), +#ifdef WITH_WSREP + NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0), + ON_UPDATE(update_auto_increment_increment)); +#else NO_MUTEX_GUARD, IN_BINLOG); +#endif /* WITH_WSREP */ + +#ifdef WITH_WSREP + +/* + We need to keep the original values set by the user, as they will + be lost if wsrep_auto_increment_control set to 'ON': +*/ +static bool update_auto_increment_offset (sys_var *self, THD *thd, enum_var_type type) +{ + if (type == OPT_GLOBAL) + global_system_variables.saved_auto_increment_offset= + global_system_variables.auto_increment_offset; + else + thd->variables.saved_auto_increment_offset= + thd->variables.auto_increment_offset; + return false; +} + +#endif /* WITH_WSREP */ static Sys_var_ulong Sys_auto_increment_offset( "auto_increment_offset", @@ -196,7 +403,12 @@ static Sys_var_ulong Sys_auto_increment_offset( SESSION_VAR(auto_increment_offset), CMD_LINE(OPT_ARG), VALID_RANGE(1, 65535), DEFAULT(1), BLOCK_SIZE(1), +#ifdef WITH_WSREP + NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0), + ON_UPDATE(update_auto_increment_offset)); +#else NO_MUTEX_GUARD, IN_BINLOG); +#endif /* WITH_WSREP */ static Sys_var_mybool Sys_automatic_sp_privileges( "automatic_sp_privileges", @@ -206,10 +418,10 @@ static Sys_var_mybool Sys_automatic_sp_privileges( static Sys_var_ulong Sys_back_log( "back_log", "The number of outstanding connection requests " - "MySQL can have. This comes into play when the main MySQL thread " + "MariaDB can have. This comes into play when the main MariaDB thread " "gets very many connection requests in a very short time", - READ_ONLY GLOBAL_VAR(back_log), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, 65535), DEFAULT(50), BLOCK_SIZE(1)); + AUTO_SET READ_ONLY GLOBAL_VAR(back_log), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, 65535), DEFAULT(150), BLOCK_SIZE(1)); static Sys_var_charptr Sys_basedir( "basedir", "Path to installation directory. All paths are " @@ -230,7 +442,7 @@ static Sys_var_ulonglong Sys_binlog_stmt_cache_size( "binlog_stmt_cache_size", "The size of the statement cache for " "updates to non-transactional engines for the binary log. " "If you often use statements updating a great number of rows, " - "you can increase this to get more performance", + "you can increase this to get more performance.", GLOBAL_VAR(binlog_stmt_cache_size), CMD_LINE(REQUIRED_ARG), VALID_RANGE(IO_SIZE, SIZE_T_MAX), DEFAULT(32768), BLOCK_SIZE(IO_SIZE)); @@ -264,7 +476,7 @@ error_if_in_trans_or_substatement(THD *thd, int in_substatement_error, return false; } -static bool check_has_super(sys_var *self, THD *thd, set_var *var) +bool check_has_super(sys_var *self, THD *thd, set_var *var) { DBUG_ASSERT(self->scope() != sys_var::GLOBAL);// don't abuse check_has_super() #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -276,11 +488,35 @@ static bool check_has_super(sys_var *self, THD *thd, set_var *var) #endif return false; } + +static Sys_var_bit Sys_core_file("core_file", "write a core-file on crashes", + READ_ONLY GLOBAL_VAR(test_flags), NO_CMD_LINE, + TEST_CORE_ON_SIGNAL, DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, + 0,0,0); + static bool binlog_format_check(sys_var *self, THD *thd, set_var *var) { if (check_has_super(self, thd, var)) return true; + /* + MariaDB Galera does not support STATEMENT or MIXED binlog format currently. + */ + if (WSREP(thd) && var->save_result.ulonglong_value != BINLOG_FORMAT_ROW) + { + // Push a warning to the error log. + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, + "MariaDB Galera does not support binlog format: %s", + binlog_format_names[var->save_result.ulonglong_value]); + + if (var->type == OPT_GLOBAL) + { + WSREP_ERROR("MariaDB Galera does not support binlog format: %s", + binlog_format_names[var->save_result.ulonglong_value]); + return true; + } + } + if (var->type == OPT_GLOBAL) return false; @@ -327,9 +563,7 @@ static Sys_var_enum Sys_binlog_format( "based binary logging except for those statements where only row-" "based is correct: those which involve user-defined functions (i.e. " "UDFs) or the UUID() function; for those, row-based binary logging is " - "automatically used. If NDBCLUSTER is enabled and binlog-format is " - "MIXED, the format switches to row-based and back implicitly per each " - "query accessing an NDBCLUSTER table", + "automatically used.", SESSION_VAR(binlog_format), CMD_LINE(REQUIRED_ARG, OPT_BINLOG_FORMAT), binlog_format_names, DEFAULT(BINLOG_FORMAT_STMT), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(binlog_format_check), @@ -362,6 +596,17 @@ static Sys_var_mybool Sys_binlog_direct( CMD_LINE(OPT_ARG), DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(binlog_direct_check)); + +static Sys_var_mybool Sys_explicit_defaults_for_timestamp( + "explicit_defaults_for_timestamp", + "This option causes CREATE TABLE to create all TIMESTAMP columns " + "as NULL with DEFAULT NULL attribute, Without this option, " + "TIMESTAMP columns are NOT NULL and have implicit DEFAULT clauses. " + "The old behavior is deprecated.", + READ_ONLY GLOBAL_VAR(opt_explicit_defaults_for_timestamp), + CMD_LINE(OPT_ARG), DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG); + + static Sys_var_ulonglong Sys_bulk_insert_buff_size( "bulk_insert_buffer_size", "Size of tree cache used in bulk " "insert optimisation. Note that this is a limit per thread!", @@ -386,16 +631,19 @@ static bool check_charset(sys_var *self, THD *thd, set_var *var) if (var->value->result_type() == STRING_RESULT) { String str(buff, sizeof(buff), system_charset_info), *res; - if (!(res=var->value->val_str(&str))) + if (!(res= var->value->val_str(&str))) var->save_result.ptr= NULL; - else if (!(var->save_result.ptr= get_charset_by_csname(res->c_ptr(), - MY_CS_PRIMARY, - MYF(0))) && - !(var->save_result.ptr=get_old_charset_by_name(res->c_ptr()))) + else { - ErrConvString err(res); - my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), err.ptr()); - return true; + ErrConvString err(res); /* Get utf8 '\0' terminated string */ + if (!(var->save_result.ptr= get_charset_by_csname(err.ptr(), + MY_CS_PRIMARY, + MYF(0))) && + !(var->save_result.ptr= get_old_charset_by_name(err.ptr()))) + { + my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), err.ptr()); + return true; + } } } else // INT_RESULT @@ -435,7 +683,7 @@ static bool check_charset_db(sys_var *self, THD *thd, set_var *var) } static Sys_var_struct Sys_character_set_database( "character_set_database", - " The character set used by the default database", + "The character set used by the default database", SESSION_VAR(collation_database), NO_CMD_LINE, offsetof(CHARSET_INFO, csname), DEFAULT(&default_charset_info), NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_charset_db)); @@ -460,7 +708,7 @@ static bool fix_thd_charset(sys_var *self, THD *thd, enum_var_type type) static Sys_var_struct Sys_character_set_client( "character_set_client", "The character set for statements " "that arrive from the client", - SESSION_VAR(character_set_client), NO_CMD_LINE, + NO_SET_STMT SESSION_VAR(character_set_client), NO_CMD_LINE, offsetof(CHARSET_INFO, csname), DEFAULT(&default_charset_info), NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_cs_client), ON_UPDATE(fix_thd_charset)); @@ -469,7 +717,7 @@ static Sys_var_struct Sys_character_set_connection( "character_set_connection", "The character set used for " "literals that do not have a character set introducer and for " "number-to-string conversion", - SESSION_VAR(collation_connection), NO_CMD_LINE, + NO_SET_STMT SESSION_VAR(collation_connection), NO_CMD_LINE, offsetof(CHARSET_INFO, csname), DEFAULT(&default_charset_info), NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_charset_not_null), ON_UPDATE(fix_thd_charset)); @@ -483,15 +731,14 @@ static Sys_var_struct Sys_character_set_results( static Sys_var_struct Sys_character_set_filesystem( "character_set_filesystem", "The filesystem character set", - SESSION_VAR(character_set_filesystem), NO_CMD_LINE, + NO_SET_STMT SESSION_VAR(character_set_filesystem), NO_CMD_LINE, offsetof(CHARSET_INFO, csname), DEFAULT(&character_set_filesystem), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_charset_not_null), ON_UPDATE(fix_thd_charset)); static const char *completion_type_names[]= {"NO_CHAIN", "CHAIN", "RELEASE", 0}; static Sys_var_enum Sys_completion_type( - "completion_type", "The transaction completion type, one of " - "NO_CHAIN, CHAIN, RELEASE", + "completion_type", "The transaction completion type", SESSION_VAR(completion_type), CMD_LINE(REQUIRED_ARG), completion_type_names, DEFAULT(0)); @@ -506,11 +753,14 @@ static bool check_collation_not_null(sys_var *self, THD *thd, set_var *var) String str(buff, sizeof(buff), system_charset_info), *res; if (!(res= var->value->val_str(&str))) var->save_result.ptr= NULL; - else if (!(var->save_result.ptr= get_charset_by_name(res->c_ptr(), MYF(0)))) + else { - ErrConvString err(res); - my_error(ER_UNKNOWN_COLLATION, MYF(0), err.ptr()); - return true; + ErrConvString err(res); /* Get utf8 '\0'-terminated string */ + if (!(var->save_result.ptr= get_charset_by_name(err.ptr(), MYF(0)))) + { + my_error(ER_UNKNOWN_COLLATION, MYF(0), err.ptr()); + return true; + } } } else // INT_RESULT @@ -527,7 +777,7 @@ static bool check_collation_not_null(sys_var *self, THD *thd, set_var *var) static Sys_var_struct Sys_collation_connection( "collation_connection", "The collation of the connection " "character set", - SESSION_VAR(collation_connection), NO_CMD_LINE, + NO_SET_STMT SESSION_VAR(collation_connection), NO_CMD_LINE, offsetof(CHARSET_INFO, name), DEFAULT(&default_charset_info), NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_collation_not_null), ON_UPDATE(fix_thd_charset)); @@ -555,8 +805,7 @@ static Sys_var_struct Sys_collation_server( static const char *concurrent_insert_names[]= {"NEVER", "AUTO", "ALWAYS", 0}; static Sys_var_enum Sys_concurrent_insert( - "concurrent_insert", "Use concurrent insert with MyISAM. Possible " - "values are NEVER, AUTO, ALWAYS", + "concurrent_insert", "Use concurrent insert with MyISAM", GLOBAL_VAR(myisam_concurrent_insert), CMD_LINE(OPT_ARG), concurrent_insert_names, DEFAULT(1)); @@ -614,7 +863,15 @@ export bool fix_delay_key_write(sys_var *self, THD *thd, enum_var_type type) } static const char *delay_key_write_names[]= { "OFF", "ON", "ALL", NullS }; static Sys_var_enum Sys_delay_key_write( - "delay_key_write", "Type of DELAY_KEY_WRITE", + "delay_key_write", "Specifies how MyISAM tables handles CREATE " + "TABLE DELAY_KEY_WRITE. If set to ON, the default, any DELAY KEY " + "WRITEs are honored. The key buffer is then flushed only when the " + "table closes, speeding up writes. MyISAM tables should be " + "automatically checked upon startup in this case, and " + "--external locking should not be used, as it can lead to index " + "corruption. If set to OFF, DELAY KEY WRITEs are ignored, while if " + "set to ALL, all new opened tables are treated as if created with " + "DELAY KEY WRITEs enabled.", GLOBAL_VAR(delay_key_write_options), CMD_LINE(OPT_ARG), delay_key_write_names, DEFAULT(DELAY_KEY_WRITE_ON), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), @@ -624,7 +881,7 @@ static Sys_var_ulong Sys_delayed_insert_limit( "delayed_insert_limit", "After inserting delayed_insert_limit rows, the INSERT DELAYED " "handler will check if there are any SELECT statements pending. " - "If so, it allows these to execute before continuing", + "If so, it allows these to execute before continuing.", GLOBAL_VAR(delayed_insert_limit), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1, UINT_MAX), DEFAULT(DELAYED_LIMIT), BLOCK_SIZE(1)); @@ -645,30 +902,26 @@ static Sys_var_ulong Sys_delayed_queue_size( VALID_RANGE(1, UINT_MAX), DEFAULT(DELAYED_QUEUE_SIZE), BLOCK_SIZE(1)); #ifdef HAVE_EVENT_SCHEDULER -static const char *event_scheduler_names[]= { "OFF", "ON", "DISABLED", NullS }; +static const char *event_scheduler_names[]= { "OFF", "ON", "DISABLED", + "ORIGINAL", NullS }; static bool event_scheduler_check(sys_var *self, THD *thd, set_var *var) { - /* DISABLED is only accepted on the command line */ - if (var->save_result.ulonglong_value == Events::EVENTS_DISABLED) - return true; - /* - If the scheduler was disabled because there are no/bad - system tables, produce a more meaningful error message - than ER_OPTION_PREVENTS_STATEMENT - */ - if (Events::check_if_system_tables_error()) - return true; if (Events::opt_event_scheduler == Events::EVENTS_DISABLED) { my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--event-scheduler=DISABLED or --skip-grant-tables"); return true; } + /* DISABLED is only accepted on the command line */ + if (var->save_result.ulonglong_value == Events::EVENTS_DISABLED) + return true; return false; } + static bool event_scheduler_update(sys_var *self, THD *thd, enum_var_type type) { int err_no= 0; + bool ret; uint opt_event_scheduler_value= Events::opt_event_scheduler; mysql_mutex_unlock(&LOCK_global_system_variables); /* @@ -687,9 +940,25 @@ static bool event_scheduler_update(sys_var *self, THD *thd, enum_var_type type) rare and it's difficult to avoid it without opening up possibilities for deadlocks. See bug#51160. */ - bool ret= opt_event_scheduler_value == Events::EVENTS_ON - ? Events::start(&err_no) - : Events::stop(); + + /* EVENTS_ORIGINAL means we should revert back to the startup state */ + if (opt_event_scheduler_value == Events::EVENTS_ORIGINAL) + { + opt_event_scheduler_value= Events::opt_event_scheduler= + Events::startup_state; + } + + /* + If the scheduler was not properly inited (because of wrong system tables), + try to init it again. This is needed for mysql_upgrade to work properly if + the event tables where upgraded. + */ + if (!Events::inited && (Events::init(thd, 0) || !Events::inited)) + ret= 1; + else + ret= opt_event_scheduler_value == Events::EVENTS_ON ? + Events::start(&err_no) : + Events::stop(); mysql_mutex_lock(&LOCK_global_system_variables); if (ret) { @@ -802,6 +1071,26 @@ static Sys_var_lexstring Sys_init_connect( DEFAULT(""), &PLock_sys_init_connect, NOT_IN_BINLOG, ON_CHECK(check_init_string)); +#ifdef HAVE_REPLICATION +static bool check_master_connection(sys_var *self, THD *thd, set_var *var) +{ + LEX_STRING tmp; + tmp.str= var->save_result.string_value.str; + tmp.length= var->save_result.string_value.length; + if (!tmp.str || check_master_connection_name(&tmp)) + return true; + + return false; +} + +static Sys_var_session_lexstring Sys_default_master_connection( + "default_master_connection", + "Master connection to use for all slave variables and slave commands", + SESSION_ONLY(default_master_connection), + NO_CMD_LINE, IN_SYSTEM_CHARSET, + DEFAULT(""), MAX_CONNECTION_NAME, ON_CHECK(check_master_connection)); +#endif + static Sys_var_charptr Sys_init_file( "init_file", "Read SQL commands from this file at startup", READ_ONLY GLOBAL_VAR(opt_init_file), @@ -824,7 +1113,7 @@ static Sys_var_ulong Sys_interactive_timeout( "interactive_timeout", "The number of seconds the server waits for activity on an interactive " "connection before closing it", - SESSION_VAR(net_interactive_timeout), + NO_SET_STMT SESSION_VAR(net_interactive_timeout), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(NET_WAIT_TIMEOUT), BLOCK_SIZE(1)); @@ -832,7 +1121,7 @@ static Sys_var_ulonglong Sys_join_buffer_size( "join_buffer_size", "The size of the buffer that is used for joins", SESSION_VAR(join_buff_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(128, SIZE_T_MAX), DEFAULT(128*1024), BLOCK_SIZE(128)); + VALID_RANGE(128, SIZE_T_MAX), DEFAULT(256*1024), BLOCK_SIZE(128)); static Sys_var_keycache Sys_key_buffer_size( "key_buffer_size", "The size of the buffer used for " @@ -874,11 +1163,22 @@ static Sys_var_keycache Sys_key_cache_age_threshold( BLOCK_SIZE(100), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(change_keycache_param)); +static Sys_var_keycache Sys_key_cache_file_hash_size( + "key_cache_file_hash_size", + "Number of hash buckets for open and changed files. If you have a lot of MyISAM " + "files open you should increase this for faster flush of changes. A good " + "value is probably 1/10 of number of possible open MyISAM files.", + KEYCACHE_VAR(changed_blocks_hash_size), + CMD_LINE(REQUIRED_ARG, OPT_KEY_CACHE_CHANGED_BLOCKS_HASH_SIZE), + VALID_RANGE(128, 16384), DEFAULT(512), + BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(resize_keycache)); + static Sys_var_mybool Sys_large_files_support( "large_files_support", "Whether mysqld was compiled with options for large file support", READ_ONLY GLOBAL_VAR(opt_large_files), - NO_CMD_LINE, DEFAULT(sizeof(my_off_t) > 4)); + CMD_LINE_HELP_ONLY, DEFAULT(sizeof(my_off_t) > 4)); static Sys_var_uint Sys_large_page_size( "large_page_size", @@ -946,6 +1246,19 @@ static Sys_var_mybool Sys_log_queries_not_using_indexes( GLOBAL_VAR(opt_log_queries_not_using_indexes), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); +static Sys_var_mybool Sys_log_slow_admin_statements( + "log_slow_admin_statements", + "Log slow OPTIMIZE, ANALYZE, ALTER and other administrative statements to " + "the slow log if it is open.", + GLOBAL_VAR(opt_log_slow_admin_statements), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_mybool Sys_log_slow_slave_statements( + "log_slow_slave_statements", + "Log slow statements executed by slave thread to the slow log if it is open.", + GLOBAL_VAR(opt_log_slow_slave_statements), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + static Sys_var_ulong Sys_log_warnings( "log_warnings", "Log some not critical warnings to the general log file." @@ -976,6 +1289,29 @@ static Sys_var_double Sys_long_query_time( NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(update_cached_long_query_time)); + +static bool update_cached_max_statement_time(sys_var *self, THD *thd, + enum_var_type type) +{ + if (type == OPT_SESSION) + thd->variables.max_statement_time= + double2ulonglong(thd->variables.max_statement_time_double * 1e6); + else + global_system_variables.max_statement_time= + double2ulonglong(global_system_variables.max_statement_time_double * 1e6); + return false; +} + +static Sys_var_double Sys_max_statement_time( + "max_statement_time", + "A query that has taken more than max_statement_time seconds " + "will be aborted. The argument will be treated as a decimal value " + "with microsecond precision. A value of 0 (default) means no timeout", + SESSION_VAR(max_statement_time_double), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, LONG_TIMEOUT), DEFAULT(0), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(update_cached_max_statement_time)); + static bool fix_low_prio_updates(sys_var *self, THD *thd, enum_var_type type) { if (type == OPT_SESSION) @@ -995,20 +1331,12 @@ static Sys_var_mybool Sys_low_priority_updates( DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_low_prio_updates)); -#ifndef TO_BE_DELETED /* Alias for the low_priority_updates */ -static Sys_var_mybool Sys_sql_low_priority_updates( - "sql_low_priority_updates", - "INSERT/DELETE/UPDATE has lower priority than selects", - SESSION_VAR(low_priority_updates), NO_CMD_LINE, - DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), - ON_UPDATE(fix_low_prio_updates)); -#endif - static Sys_var_mybool Sys_lower_case_file_system( "lower_case_file_system", "Case sensitivity of file names on the file system where the " "data directory is located", - READ_ONLY GLOBAL_VAR(lower_case_file_system), NO_CMD_LINE, + READ_ONLY GLOBAL_VAR(lower_case_file_system), + CMD_LINE_HELP_ONLY, DEFAULT(FALSE)); static Sys_var_uint Sys_lower_case_table_names( @@ -1044,8 +1372,9 @@ static bool check_max_allowed_packet(sys_var *self, THD *thd, set_var *var) val= var->save_result.ulonglong_value; if (val < (longlong) global_system_variables.net_buffer_length) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_BELOW_LIMIT, ER(WARN_OPTION_BELOW_LIMIT), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_BELOW_LIMIT, + ER_THD(thd, WARN_OPTION_BELOW_LIMIT), "max_allowed_packet", "net_buffer_length"); } return false; @@ -1056,7 +1385,7 @@ static Sys_var_ulong Sys_max_allowed_packet( "max_allowed_packet", "Max packet length to send to or receive from the server", SESSION_VAR(max_allowed_packet), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1024, 1024*1024*1024), DEFAULT(1024*1024), + VALID_RANGE(1024, 1024*1024*1024), DEFAULT(4*1024*1024), BLOCK_SIZE(1024), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_max_allowed_packet)); @@ -1087,16 +1416,12 @@ static Sys_var_ulonglong Sys_max_binlog_stmt_cache_size( static bool fix_max_binlog_size(sys_var *self, THD *thd, enum_var_type type) { mysql_bin_log.set_max_size(max_binlog_size); -#ifdef HAVE_REPLICATION - if (!max_relay_log_size) - active_mi->rli.relay_log.set_max_size(max_binlog_size); -#endif return false; } static Sys_var_ulong Sys_max_binlog_size( "max_binlog_size", "Binary log will be rotated automatically when the size exceeds this " - "value. Will also apply to relay logs if max_relay_log_size is 0", + "value.", GLOBAL_VAR(max_binlog_size), CMD_LINE(REQUIRED_ARG), VALID_RANGE(IO_SIZE, 1024*1024L*1024L), DEFAULT(1024*1024L*1024L), BLOCK_SIZE(IO_SIZE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), @@ -1115,8 +1440,9 @@ static bool fix_max_connections(sys_var *self, THD *thd, enum_var_type type) // children, to avoid "too many connections" error in a common setup static Sys_var_ulong Sys_max_connections( "max_connections", "The number of simultaneous clients allowed", - GLOBAL_VAR(max_connections), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, 100000), DEFAULT(151), BLOCK_SIZE(1), NO_MUTEX_GUARD, + PARSED_EARLY GLOBAL_VAR(max_connections), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(10, 100000), + DEFAULT(MAX_CONNECTIONS_DEFAULT), BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_max_connections)); static Sys_var_ulong Sys_max_connect_errors( @@ -1127,6 +1453,12 @@ static Sys_var_ulong Sys_max_connect_errors( VALID_RANGE(1, UINT_MAX), DEFAULT(MAX_CONNECT_ERRORS), BLOCK_SIZE(1)); +static Sys_var_uint Sys_max_digest_length( + "max_digest_length", "Maximum length considered for digest text.", + READ_ONLY GLOBAL_VAR(max_digest_length), + CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, 1024 * 1024), DEFAULT(1024), BLOCK_SIZE(1)); + static bool check_max_delayed_threads(sys_var *self, THD *thd, set_var *var) { return var->type != OPT_GLOBAL && @@ -1167,10 +1499,18 @@ static Sys_var_ulonglong Sys_max_heap_table_size( VALID_RANGE(16384, (ulonglong)~(intptr)0), DEFAULT(16*1024*1024), BLOCK_SIZE(1024)); +static ulong mdl_locks_cache_size; static Sys_var_ulong Sys_metadata_locks_cache_size( - "metadata_locks_cache_size", "Size of unused metadata locks cache", + "metadata_locks_cache_size", "Unused", READ_ONLY GLOBAL_VAR(mdl_locks_cache_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, 1024*1024), DEFAULT(MDL_LOCKS_CACHE_SIZE_DEFAULT), + VALID_RANGE(1, 1024*1024), DEFAULT(1024), + BLOCK_SIZE(1)); + +static ulong mdl_locks_hash_partitions; +static Sys_var_ulong Sys_metadata_locks_hash_instances( + "metadata_locks_hash_instances", "Unused", + READ_ONLY GLOBAL_VAR(mdl_locks_hash_partitions), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1, 1024), DEFAULT(8), BLOCK_SIZE(1)); /* @@ -1186,6 +1526,604 @@ static Sys_var_ulong Sys_pseudo_thread_id( BLOCK_SIZE(1), NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_has_super)); +static bool +check_gtid_domain_id(sys_var *self, THD *thd, set_var *var) +{ + if (check_has_super(self, thd, var)) + return true; + if (var->type != OPT_GLOBAL && + error_if_in_trans_or_substatement(thd, + ER_STORED_FUNCTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO, + ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO)) + return true; + + return false; +} + + +static Sys_var_uint Sys_gtid_domain_id( + "gtid_domain_id", + "Used with global transaction ID to identify logically independent " + "replication streams. When events can propagate through multiple " + "parallel paths (for example multiple masters), each independent " + "source server must use a distinct domain_id. For simple tree-shaped " + "replication topologies, it can be left at its default, 0.", + SESSION_VAR(gtid_domain_id), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, UINT_MAX32), DEFAULT(0), + BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(check_gtid_domain_id)); + + +static bool check_gtid_seq_no(sys_var *self, THD *thd, set_var *var) +{ + uint32 domain_id, server_id; + uint64 seq_no; + + if (check_has_super(self, thd, var)) + return true; + if (error_if_in_trans_or_substatement(thd, + ER_STORED_FUNCTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO, + ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_GTID_DOMAIN_ID_SEQ_NO)) + return true; + + domain_id= thd->variables.gtid_domain_id; + server_id= thd->variables.server_id; + seq_no= (uint64)var->value->val_uint(); + DBUG_EXECUTE_IF("ignore_set_gtid_seq_no_check", return 0;); + if (opt_gtid_strict_mode && opt_bin_log && + mysql_bin_log.check_strict_gtid_sequence(domain_id, server_id, seq_no)) + return true; + + return false; +} + + +static Sys_var_ulonglong Sys_gtid_seq_no( + "gtid_seq_no", + "Internal server usage, for replication with global transaction id. " + "When set, next event group logged to the binary log will use this " + "sequence number, not generate a new one, thus allowing to preserve " + "master's GTID in slave's binlog.", + SESSION_ONLY(gtid_seq_no), + NO_CMD_LINE, VALID_RANGE(0, ULONGLONG_MAX), DEFAULT(0), + BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(check_gtid_seq_no)); + + +#ifdef HAVE_REPLICATION +static unsigned char opt_gtid_binlog_pos_dummy; +static Sys_var_gtid_binlog_pos Sys_gtid_binlog_pos( + "gtid_binlog_pos", "Last GTID logged to the binary log, per replication" + "domain", + READ_ONLY GLOBAL_VAR(opt_gtid_binlog_pos_dummy), NO_CMD_LINE); + + +uchar * +Sys_var_gtid_binlog_pos::global_value_ptr(THD *thd, const LEX_STRING *base) +{ + char buf[128]; + String str(buf, sizeof(buf), system_charset_info); + char *p; + + str.length(0); + if ((opt_bin_log && mysql_bin_log.append_state_pos(&str)) || + !(p= thd->strmake(str.ptr(), str.length()))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return NULL; + } + + return (uchar *)p; +} + + +static unsigned char opt_gtid_current_pos_dummy; +static Sys_var_gtid_current_pos Sys_gtid_current_pos( + "gtid_current_pos", "Current GTID position of the server. Per " + "replication domain, this is either the last GTID replicated by a " + "slave thread, or the GTID logged to the binary log, whichever is " + "most recent.", + READ_ONLY GLOBAL_VAR(opt_gtid_current_pos_dummy), NO_CMD_LINE); + + +uchar * +Sys_var_gtid_current_pos::global_value_ptr(THD *thd, const LEX_STRING *base) +{ + String str; + char *p; + + str.length(0); + if (rpl_append_gtid_state(&str, true) || + !(p= thd->strmake(str.ptr(), str.length()))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return NULL; + } + + return (uchar *)p; +} + + +bool +Sys_var_gtid_slave_pos::do_check(THD *thd, set_var *var) +{ + String str, *res; + + DBUG_ASSERT(var->type == OPT_GLOBAL); + + if (rpl_load_gtid_slave_state(thd)) + { + my_error(ER_CANNOT_LOAD_SLAVE_GTID_STATE, MYF(0), "mysql", + rpl_gtid_slave_state_table_name.str); + return true; + } + + if (give_error_if_slave_running(0)) + return true; + if (!(res= var->value->val_str(&str))) + return true; + if (thd->in_active_multi_stmt_transaction()) + { + my_error(ER_CANT_DO_THIS_DURING_AN_TRANSACTION, MYF(0)); + return true; + } + if (rpl_gtid_pos_check(thd, &((*res)[0]), res->length())) + return true; + + if (!(var->save_result.string_value.str= + thd->strmake(res->ptr(), res->length()))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return true; + } + var->save_result.string_value.length= res->length(); + return false; +} + + +bool +Sys_var_gtid_slave_pos::global_update(THD *thd, set_var *var) +{ + bool err; + + DBUG_ASSERT(var->type == OPT_GLOBAL); + + if (!var->value) + { + my_error(ER_NO_DEFAULT, MYF(0), var->var->name.str); + return true; + } + + mysql_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_lock(&LOCK_active_mi); + if (give_error_if_slave_running(1)) + err= true; + else + err= rpl_gtid_pos_update(thd, var->save_result.string_value.str, + var->save_result.string_value.length); + mysql_mutex_unlock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_global_system_variables); + return err; +} + + +uchar * +Sys_var_gtid_slave_pos::global_value_ptr(THD *thd, const LEX_STRING *base) +{ + String str; + char *p; + + str.length(0); + /* + If the mysql.rpl_slave_pos table could not be loaded, then we cannot + easily automatically try to reload it here - we may be inside a statement + that already has tables locked and so opening more tables is problematic. + + But if the table is not loaded (eg. missing mysql_upgrade_db or some such), + then the slave state must be empty anyway. + */ + if ((rpl_global_gtid_slave_state->loaded && + rpl_append_gtid_state(&str, false)) || + !(p= thd->strmake(str.ptr(), str.length()))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return NULL; + } + + return (uchar *)p; +} + + +static unsigned char opt_gtid_slave_pos_dummy; +static Sys_var_gtid_slave_pos Sys_gtid_slave_pos( + "gtid_slave_pos", + "The list of global transaction IDs that were last replicated on the " + "server, one for each replication domain.", + GLOBAL_VAR(opt_gtid_slave_pos_dummy), NO_CMD_LINE); + + +static Sys_var_mybool Sys_gtid_strict_mode( + "gtid_strict_mode", + "Enforce strict seq_no ordering of events in the binary log. Slave " + "stops with an error if it encounters an event that would cause it to " + "generate an out-of-order binlog if executed.", + GLOBAL_VAR(opt_gtid_strict_mode), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + + +struct gtid_binlog_state_data { rpl_gtid *list; uint32 list_len; }; + +bool +Sys_var_gtid_binlog_state::do_check(THD *thd, set_var *var) +{ + String str, *res; + struct gtid_binlog_state_data *data; + rpl_gtid *list; + uint32 list_len; + + DBUG_ASSERT(var->type == OPT_GLOBAL); + + if (!(res= var->value->val_str(&str))) + return true; + if (thd->in_active_multi_stmt_transaction()) + { + my_error(ER_CANT_DO_THIS_DURING_AN_TRANSACTION, MYF(0)); + return true; + } + if (!mysql_bin_log.is_open()) + { + my_error(ER_FLUSH_MASTER_BINLOG_CLOSED, MYF(0)); + return true; + } + if (!mysql_bin_log.is_empty_state()) + { + my_error(ER_BINLOG_MUST_BE_EMPTY, MYF(0)); + return true; + } + if (res->length() == 0) + { + list= NULL; + list_len= 0; + } + else if (!(list= gtid_parse_string_to_list(res->ptr(), res->length(), + &list_len))) + { + my_error(ER_INCORRECT_GTID_STATE, MYF(0)); + return true; + } + if (!(data= (gtid_binlog_state_data *)my_malloc(sizeof(*data), MYF(0)))) + { + my_free(list); + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return true; + } + data->list= list; + data->list_len= list_len; + var->save_result.ptr= data; + return false; +} + + +bool +Sys_var_gtid_binlog_state::global_update(THD *thd, set_var *var) +{ + bool res; + + DBUG_ASSERT(var->type == OPT_GLOBAL); + + if (!var->value) + { + my_error(ER_NO_DEFAULT, MYF(0), var->var->name.str); + return true; + } + + struct gtid_binlog_state_data *data= + (struct gtid_binlog_state_data *)var->save_result.ptr; + mysql_mutex_unlock(&LOCK_global_system_variables); + res= (reset_master(thd, data->list, data->list_len, 0) != 0); + mysql_mutex_lock(&LOCK_global_system_variables); + my_free(data->list); + my_free(data); + return res; +} + + +uchar * +Sys_var_gtid_binlog_state::global_value_ptr(THD *thd, const LEX_STRING *base) +{ + char buf[512]; + String str(buf, sizeof(buf), system_charset_info); + char *p; + + str.length(0); + if ((opt_bin_log && mysql_bin_log.append_state(&str)) || + !(p= thd->strmake(str.ptr(), str.length()))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return NULL; + } + + return (uchar *)p; +} + + +static unsigned char opt_gtid_binlog_state_dummy; +static Sys_var_gtid_binlog_state Sys_gtid_binlog_state( + "gtid_binlog_state", + "The internal GTID state of the binlog, used to keep track of all " + "GTIDs ever logged to the binlog.", + GLOBAL_VAR(opt_gtid_binlog_state_dummy), NO_CMD_LINE); + + +static Sys_var_last_gtid Sys_last_gtid( + "last_gtid", "The GTID of the last commit (if binlogging was enabled), " + "or the empty string if none.", + READ_ONLY sys_var::ONLY_SESSION, NO_CMD_LINE); + + +uchar * +Sys_var_last_gtid::session_value_ptr(THD *thd, const LEX_STRING *base) +{ + char buf[10+1+10+1+20+1]; + String str(buf, sizeof(buf), system_charset_info); + char *p; + bool first= true; + + str.length(0); + if ((thd->last_commit_gtid.seq_no > 0 && + rpl_slave_state_tostring_helper(&str, &thd->last_commit_gtid, &first)) || + !(p= thd->strmake(str.ptr(), str.length()))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return NULL; + } + + return (uchar *)p; +} + + +static bool +check_slave_parallel_threads(sys_var *self, THD *thd, set_var *var) +{ + return give_error_if_slave_running(0); +} + +static bool +fix_slave_parallel_threads(sys_var *self, THD *thd, enum_var_type type) +{ + bool err; + + mysql_mutex_unlock(&LOCK_global_system_variables); + err= give_error_if_slave_running(0); + mysql_mutex_lock(&LOCK_global_system_variables); + + return err; +} + + +static Sys_var_ulong Sys_slave_parallel_threads( + "slave_parallel_threads", + "If non-zero, number of threads to spawn to apply in parallel events " + "on the slave that were group-committed on the master or were logged " + "with GTID in different replication domains. Note that these threads " + "are in addition to the IO and SQL threads, which are always created " + "by a replication slave", + GLOBAL_VAR(opt_slave_parallel_threads), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0,16383), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD, + NOT_IN_BINLOG, ON_CHECK(check_slave_parallel_threads), + ON_UPDATE(fix_slave_parallel_threads)); + + +static bool +check_slave_domain_parallel_threads(sys_var *self, THD *thd, set_var *var) +{ + return give_error_if_slave_running(0); +} + +static bool +fix_slave_domain_parallel_threads(sys_var *self, THD *thd, enum_var_type type) +{ + bool running; + + mysql_mutex_unlock(&LOCK_global_system_variables); + running= give_error_if_slave_running(0); + mysql_mutex_lock(&LOCK_global_system_variables); + + return running; +} + + +static Sys_var_ulong Sys_slave_domain_parallel_threads( + "slave_domain_parallel_threads", + "Maximum number of parallel threads to use on slave for events in a " + "single replication domain. When using multiple domains, this can be " + "used to limit a single domain from grabbing all threads and thus " + "stalling other domains. The default of 0 means to allow a domain to " + "grab as many threads as it wants, up to the value of " + "slave_parallel_threads.", + GLOBAL_VAR(opt_slave_domain_parallel_threads), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0,16383), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD, + NOT_IN_BINLOG, ON_CHECK(check_slave_domain_parallel_threads), + ON_UPDATE(fix_slave_domain_parallel_threads)); + + +static Sys_var_ulong Sys_slave_parallel_max_queued( + "slave_parallel_max_queued", + "Limit on how much memory SQL threads should use per parallel " + "replication thread when reading ahead in the relay log looking for " + "opportunities for parallel replication. Only used when " + "--slave-parallel-threads > 0.", + GLOBAL_VAR(opt_slave_parallel_max_queued), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0,2147483647), DEFAULT(131072), BLOCK_SIZE(1)); + + +bool +Sys_var_slave_parallel_mode::global_update(THD *thd, set_var *var) +{ + enum_slave_parallel_mode new_value= + (enum_slave_parallel_mode)var->save_result.ulonglong_value; + LEX_STRING *base_name= &var->base; + Master_info *mi; + bool res= false; + + if (!base_name->length) + base_name= &thd->variables.default_master_connection; + + mysql_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_lock(&LOCK_active_mi); + + mi= master_info_index-> + get_master_info(base_name, !base_name->length ? + Sql_condition::WARN_LEVEL_ERROR : + Sql_condition::WARN_LEVEL_WARN); + + if (mi) + { + if (mi->rli.slave_running) + { + my_error(ER_SLAVE_MUST_STOP, MYF(0), + mi->connection_name.length, mi->connection_name.str); + res= true; + } + else + { + mi->parallel_mode= new_value; + if (!base_name->length) + { + /* Use as default value for new connections */ + opt_slave_parallel_mode= new_value; + } + } + } + + mysql_mutex_unlock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_global_system_variables); + + return res; +} + + +uchar * +Sys_var_slave_parallel_mode::global_value_ptr(THD *thd, + const LEX_STRING *base_name) +{ + Master_info *mi; + enum_slave_parallel_mode val= + (enum_slave_parallel_mode)opt_slave_parallel_mode; + + if (!base_name->length) + base_name= &thd->variables.default_master_connection; + + mysql_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_lock(&LOCK_active_mi); + + mi= master_info_index-> + get_master_info(base_name, !base_name->length ? + Sql_condition::WARN_LEVEL_ERROR : + Sql_condition::WARN_LEVEL_WARN); + if (mi) + val= mi->parallel_mode; + + mysql_mutex_unlock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_global_system_variables); + if (!mi) + return 0; + + return valptr(thd, val); +} + + +/* The order here must match enum_slave_parallel_mode in mysqld.h. */ +static const char *slave_parallel_mode_names[] = { + "none", "minimal", "conservative", "optimistic", "aggressive", NULL +}; +export TYPELIB slave_parallel_mode_typelib = { + array_elements(slave_parallel_mode_names)-1, + "", + slave_parallel_mode_names, + NULL +}; + +static Sys_var_slave_parallel_mode Sys_slave_parallel_mode( + "slave_parallel_mode", + "Controls what transactions are applied in parallel when using " + "--slave-parallel-threads. Possible values: \"optimistic\" tries to " + "apply most transactional DML in parallel, and handles any conflicts " + "with rollback and retry. \"conservative\" limits parallelism in an " + "effort to avoid any conflicts. \"aggressive\" tries to maximise the " + "parallelism, possibly at the cost of increased conflict rate. " + "\"minimal\" only parallelizes the commit steps of transactions. " + "\"none\" disables parallel apply completely.", + GLOBAL_VAR(opt_slave_parallel_mode), NO_CMD_LINE, + slave_parallel_mode_names, DEFAULT(SLAVE_PARALLEL_CONSERVATIVE)); + + +static Sys_var_bit Sys_skip_parallel_replication( + "skip_parallel_replication", + "If set when a transaction is written to the binlog, parallel apply of " + "that transaction will be avoided on a slave where slave_parallel_mode " + "is not \"aggressive\". Can be used to avoid unnecessary rollback and " + "retry for transactions that are likely to cause a conflict if " + "replicated in parallel.", + SESSION_ONLY(option_bits), NO_CMD_LINE, OPTION_RPL_SKIP_PARALLEL, + DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG); + + +static bool +check_gtid_ignore_duplicates(sys_var *self, THD *thd, set_var *var) +{ + return give_error_if_slave_running(0); +} + +static bool +fix_gtid_ignore_duplicates(sys_var *self, THD *thd, enum_var_type type) +{ + bool running; + + mysql_mutex_unlock(&LOCK_global_system_variables); + running= give_error_if_slave_running(0); + mysql_mutex_lock(&LOCK_global_system_variables); + + return running; +} + + +static Sys_var_mybool Sys_gtid_ignore_duplicates( + "gtid_ignore_duplicates", + "When set, different master connections in multi-source replication are " + "allowed to receive and process event groups with the same GTID (when " + "using GTID mode). Only one will be applied, any others will be " + "ignored. Within a given replication domain, just the sequence number " + "will be used to decide whether a given GTID has been already applied; " + "this means it is the responsibility of the user to ensure that GTID " + "sequence numbers are strictly increasing.", + GLOBAL_VAR(opt_gtid_ignore_duplicates), CMD_LINE(OPT_ARG), + DEFAULT(FALSE), NO_MUTEX_GUARD, + NOT_IN_BINLOG, ON_CHECK(check_gtid_ignore_duplicates), + ON_UPDATE(fix_gtid_ignore_duplicates)); +#endif + + +static Sys_var_ulong Sys_binlog_commit_wait_count( + "binlog_commit_wait_count", + "If non-zero, binlog write will wait at most binlog_commit_wait_usec " + "microseconds for at least this many commits to queue up for group " + "commit to the binlog. This can reduce I/O on the binlog and provide " + "increased opportunity for parallel apply on the slave, but too high " + "a value will decrease commit throughput.", + GLOBAL_VAR(opt_binlog_commit_wait_count), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, ULONG_MAX), DEFAULT(0), BLOCK_SIZE(1)); + + +static Sys_var_ulong Sys_binlog_commit_wait_usec( + "binlog_commit_wait_usec", + "Maximum time, in microseconds, to wait for more commits to queue up " + "for binlog group commit. Only takes effect if the value of " + "binlog_commit_wait_count is non-zero.", + GLOBAL_VAR(opt_binlog_commit_wait_usec), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, ULONG_MAX), DEFAULT(100000), BLOCK_SIZE(1)); + + static bool fix_max_join_size(sys_var *self, THD *thd, enum_var_type type) { SV *sv= type == OPT_GLOBAL ? &global_system_variables : &thd->variables; @@ -1216,13 +2154,6 @@ static Sys_var_ulong Sys_max_length_for_sort_data( SESSION_VAR(max_length_for_sort_data), CMD_LINE(REQUIRED_ARG), VALID_RANGE(4, 8192*1024L), DEFAULT(1024), BLOCK_SIZE(1)); -static Sys_var_harows Sys_sql_max_join_size( - "sql_max_join_size", "Alias for max_join_size", - SESSION_VAR(max_join_size), NO_CMD_LINE, - VALID_RANGE(1, HA_POS_ERROR), DEFAULT(HA_POS_ERROR), BLOCK_SIZE(1), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), - ON_UPDATE(fix_max_join_size), DEPRECATED("'@@max_join_size'")); - static Sys_var_ulong Sys_max_long_data_size( "max_long_data_size", "The maximum BLOB length to send to server from " @@ -1234,31 +2165,13 @@ static Sys_var_ulong Sys_max_long_data_size( BLOCK_SIZE(1)); static PolyLock_mutex PLock_prepared_stmt_count(&LOCK_prepared_stmt_count); -static Sys_var_ulong Sys_max_prepared_stmt_count( +static Sys_var_uint Sys_max_prepared_stmt_count( "max_prepared_stmt_count", "Maximum number of prepared statements in the server", GLOBAL_VAR(max_prepared_stmt_count), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, 1024*1024), DEFAULT(16382), BLOCK_SIZE(1), + VALID_RANGE(0, UINT_MAX32), DEFAULT(16382), BLOCK_SIZE(1), &PLock_prepared_stmt_count); -static bool fix_max_relay_log_size(sys_var *self, THD *thd, enum_var_type type) -{ -#ifdef HAVE_REPLICATION - active_mi->rli.relay_log.set_max_size(max_relay_log_size ? - max_relay_log_size: max_binlog_size); -#endif - return false; -} -static Sys_var_ulong Sys_max_relay_log_size( - "max_relay_log_size", - "If non-zero: relay log will be rotated automatically when the " - "size exceeds this value; if zero: when the size " - "exceeds max_binlog_size", - GLOBAL_VAR(max_relay_log_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, 1024L*1024*1024), DEFAULT(0), BLOCK_SIZE(IO_SIZE), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), - ON_UPDATE(fix_max_relay_log_size)); - static Sys_var_ulong Sys_max_sort_length( "max_sort_length", "The number of bytes to use when sorting BLOB or TEXT values (only " @@ -1297,10 +2210,11 @@ static Sys_var_max_user_conn Sys_max_user_connections( NOT_IN_BINLOG, ON_CHECK(if_checking_enabled)); static Sys_var_ulong Sys_max_tmp_tables( - "max_tmp_tables", - "Maximum number of temporary tables a client can keep open at a time", + "max_tmp_tables", "Unused, will be removed.", SESSION_VAR(max_tmp_tables), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, UINT_MAX), DEFAULT(32), BLOCK_SIZE(1)); + VALID_RANGE(1, UINT_MAX), DEFAULT(32), BLOCK_SIZE(1), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0), + DEPRECATED("")); static Sys_var_ulong Sys_max_write_lock_count( "max_write_lock_count", @@ -1332,8 +2246,9 @@ static bool check_net_buffer_length(sys_var *self, THD *thd, set_var *var) val= var->save_result.ulonglong_value; if (val > (longlong) global_system_variables.max_allowed_packet) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_BELOW_LIMIT, ER(WARN_OPTION_BELOW_LIMIT), + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_OPTION_BELOW_LIMIT, + ER_THD(thd, WARN_OPTION_BELOW_LIMIT), "max_allowed_packet", "net_buffer_length"); } return false; @@ -1405,8 +2320,10 @@ static bool check_old_passwords(sys_var *self, THD *thd, set_var *var) static Sys_var_mybool Sys_old_passwords( "old_passwords", "Use old password encryption method (needed for 4.0 and older clients)", - SESSION_VAR(old_passwords), CMD_LINE(OPT_ARG), DEFAULT(FALSE), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_old_passwords)); + SESSION_VAR(old_passwords), CMD_LINE(OPT_ARG), + DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(check_old_passwords)); +export sys_var *Sys_old_passwords_ptr= &Sys_old_passwords; // for sql_acl.cc static Sys_var_ulong Sys_open_files_limit( "open_files_limit", @@ -1427,13 +2344,42 @@ static Sys_var_ulong Sys_optimizer_prune_level( SESSION_VAR(optimizer_prune_level), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1), DEFAULT(1), BLOCK_SIZE(1)); +static Sys_var_ulong Sys_optimizer_selectivity_sampling_limit( + "optimizer_selectivity_sampling_limit", + "Controls number of record samples to check condition selectivity", + SESSION_VAR(optimizer_selectivity_sampling_limit), + CMD_LINE(REQUIRED_ARG), + VALID_RANGE(SELECTIVITY_SAMPLING_THRESHOLD, UINT_MAX), + DEFAULT(SELECTIVITY_SAMPLING_LIMIT), BLOCK_SIZE(1)); + +static Sys_var_ulong Sys_optimizer_use_condition_selectivity( + "optimizer_use_condition_selectivity", + "Controls selectivity of which conditions the optimizer takes into " + "account to calculate cardinality of a partial join when it searches " + "for the best execution plan " + "Meaning: " + "1 - use selectivity of index backed range conditions to calculate " + "the cardinality of a partial join if the last joined table is " + "accessed by full table scan or an index scan, " + "2 - use selectivity of index backed range conditions to calculate " + "the cardinality of a partial join in any case, " + "3 - additionally always use selectivity of range conditions that are " + "not backed by any index to calculate the cardinality of a partial join, " + "4 - use histograms to calculate selectivity of range conditions that " + "are not backed by any index to calculate the cardinality of " + "a partial join." + "5 - additionally use selectivity of certain non-range predicates " + "calculated on record samples", + SESSION_VAR(optimizer_use_condition_selectivity), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1, 5), DEFAULT(1), BLOCK_SIZE(1)); + /** Warns about deprecated value 63 */ static bool fix_optimizer_search_depth(sys_var *self, THD *thd, enum_var_type type) { SV *sv= type == OPT_GLOBAL ? &global_system_variables : &thd->variables; if (sv->optimizer_search_depth == MAX_TABLES+2) - WARN_DEPRECATED(thd, 6, 0, "optimizer-search-depth=63", + WARN_DEPRECATED(thd, 10, 2, "optimizer-search-depth=63", "a search depth less than 63"); return false; } @@ -1447,7 +2393,7 @@ static Sys_var_ulong Sys_optimizer_search_depth( "optimization, but may produce very bad query plans. If set to 0, " "the system will automatically pick a reasonable value; if set to " "63, the optimizer will switch to the original find_best search. " - "NOTE: The value 63 and its associated behaviour is deprecated", + "NOTE: The value 63 and its associated behaviour is deprecated.", SESSION_VAR(optimizer_search_depth), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, MAX_TABLES+2), DEFAULT(MAX_TABLES+1), BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), @@ -1476,49 +2422,25 @@ export const char *optimizer_switch_names[]= "optimize_join_buffer_size", "table_elimination", "extended_keys", - "default", NullS + "exists_to_in", + "orderby_uses_equalities", + "default", + NullS }; -/** propagates changes to @@engine_condition_pushdown */ static bool fix_optimizer_switch(sys_var *self, THD *thd, enum_var_type type) { SV *sv= (type == OPT_GLOBAL) ? &global_system_variables : &thd->variables; - sv->engine_condition_pushdown= - test(sv->optimizer_switch & OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN); + if (sv->optimizer_switch & deprecated_ENGINE_CONDITION_PUSHDOWN) + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT, + ER_THD(thd, ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT), + "engine_condition_pushdown=on"); return false; } static Sys_var_flagset Sys_optimizer_switch( "optimizer_switch", - "optimizer_switch=option=val[,option=val...], where option is one of {" - "derived_merge, " - "derived_with_keys, " - "firstmatch, " - "in_to_exists, " - "engine_condition_pushdown, " - "index_condition_pushdown, " - "index_merge, " - "index_merge_intersection, " - "index_merge_sort_intersection, " - "index_merge_sort_union, " - "index_merge_union, " - "join_cache_bka, " - "join_cache_hashed, " - "join_cache_incremental, " - "loosescan, " - "materialization, " - "mrr, " - "mrr_cost_based, " - "mrr_sort_keys, " - "optimize_join_buffer_size, " - "outer_join_with_cache, " - "partial_match_rowid_merge, " - "partial_match_table_scan, " - "semijoin, " - "semijoin_with_cache, " - "subquery_cache, " - "table_elimination, " - "extended_keys " - "} and val is one of {on, off, default}", + "Fine-tune the optimizer behavior", SESSION_VAR(optimizer_switch), CMD_LINE(REQUIRED_ARG), optimizer_switch_names, DEFAULT(OPTIMIZER_SWITCH_DEFAULT), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL), @@ -1553,8 +2475,8 @@ static Sys_var_ulong Sys_preload_buff_size( static Sys_var_uint Sys_protocol_version( "protocol_version", - "The version of the client/server protocol used by the MySQL server", - READ_ONLY GLOBAL_VAR(protocol_version), NO_CMD_LINE, + "The version of the client/server protocol used by the MariaDB server", + READ_ONLY GLOBAL_VAR(protocol_version), CMD_LINE_HELP_ONLY, VALID_RANGE(0, ~0), DEFAULT(PROTOCOL_VERSION), BLOCK_SIZE(1)); static Sys_var_proxy_user Sys_proxy_user( @@ -1677,13 +2599,6 @@ static Sys_var_ulong Sys_div_precincrement( SESSION_VAR(div_precincrement), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, DECIMAL_MAX_SCALE), DEFAULT(4), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_rpl_recovery_rank( - "rpl_recovery_rank", "Unused, will be removed", - GLOBAL_VAR(rpl_recovery_rank), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, ULONG_MAX), DEFAULT(0), BLOCK_SIZE(1), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0), - DEPRECATED("")); - static Sys_var_ulong Sys_range_alloc_block_size( "range_alloc_block_size", "Allocation block size for storing ranges during optimization", @@ -1718,7 +2633,7 @@ static Sys_var_ulong Sys_query_prealloc_size( "query_prealloc_size", "Persistent buffer for query parsing and execution", SESSION_VAR(query_prealloc_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(QUERY_ALLOC_PREALLOC_SIZE, UINT_MAX), + VALID_RANGE(1024, UINT_MAX), DEFAULT(QUERY_ALLOC_PREALLOC_SIZE), BLOCK_SIZE(1024), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_thd_mem_root)); @@ -1809,7 +2724,7 @@ static Sys_var_ulong Sys_trans_alloc_block_size( "transaction_alloc_block_size", "Allocation block size for transactions to be stored in binary log", SESSION_VAR(trans_alloc_block_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1024, 128 * 1024 * 1024), DEFAULT(QUERY_ALLOC_BLOCK_SIZE), + VALID_RANGE(1024, 128 * 1024 * 1024), DEFAULT(TRANS_ALLOC_BLOCK_SIZE), BLOCK_SIZE(1024), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_trans_mem_root)); @@ -1839,28 +2754,13 @@ static const char *thread_handling_names[]= static Sys_var_enum Sys_thread_handling( "thread_handling", - "Define threads usage for handling queries, one of " - "one-thread-per-connection, no-threads" -#ifdef HAVE_POOL_OF_THREADS - ", pool-of-threads" -#endif - , READ_ONLY GLOBAL_VAR(thread_handling), CMD_LINE(REQUIRED_ARG), + "Define threads usage for handling queries", + READ_ONLY GLOBAL_VAR(thread_handling), CMD_LINE(REQUIRED_ARG), thread_handling_names, DEFAULT(DEFAULT_THREAD_HANDLING) ); #ifdef HAVE_QUERY_CACHE -static bool check_query_cache_size(sys_var *self, THD *thd, set_var *var) -{ - if (global_system_variables.query_cache_type == 0 && - var->value && var->value->val_int() != 0) - { - my_error(ER_QUERY_CACHE_DISABLED, MYF(0)); - return true; - } - - return false; -} static bool fix_query_cache_size(sys_var *self, THD *thd, enum_var_type type) { ulong new_cache_size= query_cache.resize(query_cache_size); @@ -1869,26 +2769,35 @@ static bool fix_query_cache_size(sys_var *self, THD *thd, enum_var_type type) requested cache size. See also query_cache_size_arg */ if (query_cache_size != new_cache_size) - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARN_QC_RESIZE, ER(ER_WARN_QC_RESIZE), + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_QC_RESIZE, ER_THD(thd, ER_WARN_QC_RESIZE), query_cache_size, new_cache_size); query_cache_size= new_cache_size; + + return false; +} + +static bool fix_query_cache_limit(sys_var *self, THD *thd, enum_var_type type) +{ + query_cache.result_size_limit(query_cache_limit); return false; } static Sys_var_ulonglong Sys_query_cache_size( "query_cache_size", "The memory allocated to store results from old queries", GLOBAL_VAR(query_cache_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, ULONG_MAX), DEFAULT(0), BLOCK_SIZE(1024), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_query_cache_size), + VALID_RANGE(0, ULONG_MAX), DEFAULT(1024*1024), BLOCK_SIZE(1024), + NO_MUTEX_GUARD, NOT_IN_BINLOG, NULL, ON_UPDATE(fix_query_cache_size)); static Sys_var_ulong Sys_query_cache_limit( "query_cache_limit", "Don't cache results that are bigger than this", - GLOBAL_VAR(query_cache.query_cache_limit), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, UINT_MAX), DEFAULT(1024*1024), BLOCK_SIZE(1)); + GLOBAL_VAR(query_cache_limit), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, UINT_MAX), DEFAULT(1024*1024), BLOCK_SIZE(1), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL), + ON_UPDATE(fix_query_cache_limit)); static bool fix_qcache_min_res_unit(sys_var *self, THD *thd, enum_var_type type) { @@ -1905,6 +2814,7 @@ static Sys_var_ulong Sys_query_cache_min_res_unit( ON_UPDATE(fix_qcache_min_res_unit)); static const char *query_cache_type_names[]= { "OFF", "ON", "DEMAND", 0 }; + static bool check_query_cache_type(sys_var *self, THD *thd, set_var *var) { if (query_cache.is_disable_in_progress()) @@ -1912,16 +2822,22 @@ static bool check_query_cache_type(sys_var *self, THD *thd, set_var *var) my_error(ER_QUERY_CACHE_IS_DISABLED, MYF(0)); return true; } - if (var->type != OPT_GLOBAL && - global_system_variables.query_cache_type == 0 && - var->value->val_int() != 0) + + if (var->type != OPT_GLOBAL && global_system_variables.query_cache_type == 0) { - my_error(ER_QUERY_CACHE_IS_GLOBALY_DISABLED, MYF(0)); - return true; + if (var->value) + { + if (var->save_result.ulonglong_value != 0) + { + my_error(ER_QUERY_CACHE_IS_GLOBALY_DISABLED, MYF(0)); + return true; + } + } } - return false; } + + static bool fix_query_cache_type(sys_var *self, THD *thd, enum_var_type type) { if (type != OPT_GLOBAL) @@ -1944,8 +2860,8 @@ static Sys_var_enum Sys_query_cache_type( "OFF = Don't cache or retrieve results. ON = Cache all results " "except SELECT SQL_NO_CACHE ... queries. DEMAND = Cache only " "SELECT SQL_CACHE ... queries", - SESSION_VAR(query_cache_type), CMD_LINE(REQUIRED_ARG), - query_cache_type_names, DEFAULT(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, + NO_SET_STMT SESSION_VAR(query_cache_type), CMD_LINE(REQUIRED_ARG), + query_cache_type_names, DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_query_cache_type), ON_UPDATE(fix_query_cache_type)); @@ -1961,7 +2877,7 @@ static Sys_var_mybool Sys_secure_auth( "Disallow authentication for accounts that have old (pre-4.1) " "passwords", GLOBAL_VAR(opt_secure_auth), CMD_LINE(OPT_ARG), - DEFAULT(FALSE)); + DEFAULT(TRUE)); static Sys_var_charptr Sys_secure_file_priv( "secure_file_priv", @@ -1972,17 +2888,27 @@ static Sys_var_charptr Sys_secure_file_priv( static bool fix_server_id(sys_var *self, THD *thd, enum_var_type type) { - server_id_supplied = 1; - thd->server_id= server_id; + if (type == OPT_GLOBAL) + { + server_id_supplied = 1; + thd->variables.server_id= global_system_variables.server_id; + /* + Historically, server_id was a global variable that is exported to + plugins. Now it is a session variable, and lives in the + global_system_variables struct, but we still need to export the + value for reading to plugins for backwards compatibility reasons. + */ + ::server_id= global_system_variables.server_id; + } return false; } static Sys_var_ulong Sys_server_id( "server_id", "Uniquely identifies the server instance in the community of " "replication partners", - GLOBAL_VAR(server_id), CMD_LINE(REQUIRED_ARG, OPT_SERVER_ID), + SESSION_VAR(server_id), CMD_LINE(REQUIRED_ARG, OPT_SERVER_ID), VALID_RANGE(0, UINT_MAX32), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD, - NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_server_id)); + NOT_IN_BINLOG, ON_CHECK(check_has_super), ON_UPDATE(fix_server_id)); static Sys_var_mybool Sys_slave_compressed_protocol( "slave_compressed_protocol", @@ -1994,22 +2920,47 @@ static Sys_var_mybool Sys_slave_compressed_protocol( static const char *slave_exec_mode_names[]= {"STRICT", "IDEMPOTENT", 0}; static Sys_var_enum Slave_exec_mode( "slave_exec_mode", - "Modes for how replication events should be executed. Legal values " + "How replication events should be executed. Legal values " "are STRICT (default) and IDEMPOTENT. In IDEMPOTENT mode, " "replication will not stop for operations that are idempotent. " + "For example, in row based replication attempts to delete rows that " + "doesn't exist will be ignored. " "In STRICT mode, replication will stop on any unexpected difference " - "between the master and the slave", + "between the master and the slave.", GLOBAL_VAR(slave_exec_mode_options), CMD_LINE(REQUIRED_ARG), slave_exec_mode_names, DEFAULT(SLAVE_EXEC_MODE_STRICT)); +static Sys_var_enum Slave_ddl_exec_mode( + "slave_ddl_exec_mode", + "How replication events should be executed. Legal values " + "are STRICT and IDEMPOTENT (default). In IDEMPOTENT mode, " + "replication will not stop for DDL operations that are idempotent. " + "This means that CREATE TABLE is treated as CREATE TABLE OR REPLACE and " + "DROP TABLE is treated as DROP TABLE IF EXISTS.", + GLOBAL_VAR(slave_ddl_exec_mode_options), CMD_LINE(REQUIRED_ARG), + slave_exec_mode_names, DEFAULT(SLAVE_EXEC_MODE_IDEMPOTENT)); + +static const char *slave_run_triggers_for_rbr_names[]= + {"NO", "YES", "LOGGING", 0}; +static Sys_var_enum Slave_run_triggers_for_rbr( + "slave_run_triggers_for_rbr", + "Modes for how triggers in row-base replication on slave side will be " + "executed. Legal values are NO (default), YES and LOGGING. NO means " + "that trigger for RBR will not be running on slave. YES and LOGGING " + "means that triggers will be running on slave, if there was not " + "triggers running on the master for the statement. LOGGING also means " + "results of that the executed triggers work will be written to " + "the binlog.", + GLOBAL_VAR(slave_run_triggers_for_rbr), CMD_LINE(REQUIRED_ARG), + slave_run_triggers_for_rbr_names, + DEFAULT(SLAVE_RUN_TRIGGERS_FOR_RBR_NO)); + static const char *slave_type_conversions_name[]= {"ALL_LOSSY", "ALL_NON_LOSSY", 0}; static Sys_var_set Slave_type_conversions( "slave_type_conversions", - "Set of slave type conversions that are enabled. Legal values are:" - " ALL_LOSSY to enable lossy conversions and" - " ALL_NON_LOSSY to enable non-lossy conversions." - " If the variable is assigned the empty set, no conversions are" - " allowed and it is expected that the types match exactly.", + "Set of slave type conversions that are enabled." + " If the variable is empty, no conversions are" + " allowed and it is expected that the types match exactly", GLOBAL_VAR(slave_type_conversions_options), CMD_LINE(REQUIRED_ARG), slave_type_conversions_name, DEFAULT(0)); @@ -2032,52 +2983,22 @@ static Sys_var_mybool Sys_master_verify_checksum( /* These names must match RPL_SKIP_XXX #defines in slave.h. */ static const char *replicate_events_marked_for_skip_names[]= { - "replicate", "filter_on_slave", "filter_on_master", 0 + "REPLICATE", "FILTER_ON_SLAVE", "FILTER_ON_MASTER", 0 }; -static bool -replicate_events_marked_for_skip_check(sys_var *self, THD *thd, - set_var *var) -{ - int thread_mask; - DBUG_ENTER("sys_var_replicate_events_marked_for_skip_check"); - - /* Slave threads must be stopped to change the variable. */ - mysql_mutex_lock(&LOCK_active_mi); - lock_slave_threads(active_mi); - init_thread_mask(&thread_mask, active_mi, 0 /*not inverse*/); - unlock_slave_threads(active_mi); - mysql_mutex_unlock(&LOCK_active_mi); - if (thread_mask) // We refuse if any slave thread is running - { - my_error(ER_SLAVE_MUST_STOP, MYF(0)); - DBUG_RETURN(true); - } - DBUG_RETURN(false); -} bool Sys_var_replicate_events_marked_for_skip::global_update(THD *thd, set_var *var) { - bool result; - int thread_mask; + bool result= true; // Assume error DBUG_ENTER("Sys_var_replicate_events_marked_for_skip::global_update"); - /* Slave threads must be stopped to change the variable. */ - mysql_mutex_lock(&LOCK_active_mi); - lock_slave_threads(active_mi); - init_thread_mask(&thread_mask, active_mi, 0 /*not inverse*/); - if (thread_mask) // We refuse if any slave thread is running - { - my_error(ER_SLAVE_MUST_STOP, MYF(0)); - result= true; - } - else + mysql_mutex_unlock(&LOCK_global_system_variables); + if (!give_error_if_slave_running(0)) result= Sys_var_enum::global_update(thd, var); - - unlock_slave_threads(active_mi); - mysql_mutex_unlock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_global_system_variables); DBUG_RETURN(result); } + static Sys_var_replicate_events_marked_for_skip Replicate_events_marked_for_skip ("replicate_events_marked_for_skip", "Whether the slave should replicate events that were created with " @@ -2087,9 +3008,7 @@ static Sys_var_replicate_events_marked_for_skip Replicate_events_marked_for_skip "@@skip_replication=1 will be filtered on the master and never be sent to " "the slave).", GLOBAL_VAR(opt_replicate_events_marked_for_skip), CMD_LINE(REQUIRED_ARG), - replicate_events_marked_for_skip_names, DEFAULT(RPL_SKIP_REPLICATE), - NO_MUTEX_GUARD, NOT_IN_BINLOG, - ON_CHECK(replicate_events_marked_for_skip_check)); + replicate_events_marked_for_skip_names, DEFAULT(RPL_SKIP_REPLICATE)); #endif @@ -2211,35 +3130,31 @@ export bool sql_mode_string_representation(THD *thd, ulonglong sql_mode, */ static Sys_var_set Sys_sql_mode( "sql_mode", - "Syntax: sql-mode=mode[,mode[,mode...]]. See the manual for the " - "complete list of valid sql modes", + "Sets the sql mode", SESSION_VAR(sql_mode), CMD_LINE(REQUIRED_ARG), - sql_mode_names, DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG, + sql_mode_names, + DEFAULT(MODE_NO_ENGINE_SUBSTITUTION | + MODE_NO_AUTO_CREATE_USER), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_sql_mode), ON_UPDATE(fix_sql_mode)); static const char *old_mode_names[]= { - "NO_DUP_KEY_WARNINGS_WITH_IGNORE", "NO_PROGRESS_INFO", + "NO_DUP_KEY_WARNINGS_WITH_IGNORE", + "NO_PROGRESS_INFO", + "ZERO_DATE_TIME_CAST", 0 }; -export bool old_mode_string_representation(THD *thd, ulonglong sql_mode, - LEX_STRING *ls) -{ - set_to_string(thd, ls, sql_mode, old_mode_names); - return ls->str == 0; -} /* sql_mode should *not* be IN_BINLOG as the slave can't remember this anyway on restart. */ static Sys_var_set Sys_old_behavior( "old_mode", - "Used to emulate old behavior from earlier MariaDB or MySQL versions. " - "Syntax: old_mode=mode[,mode[,mode...]]. " - "See the manual for the complete list of valid old modes", + "Used to emulate old behavior from earlier MariaDB or MySQL versions", SESSION_VAR(old_behavior), CMD_LINE(REQUIRED_ARG), - old_mode_names, DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG); + old_mode_names, DEFAULT(0)); #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) #define SSL_OPT(X) CMD_LINE(REQUIRED_ARG,X) @@ -2274,6 +3189,19 @@ static Sys_var_charptr Sys_ssl_key( READ_ONLY GLOBAL_VAR(opt_ssl_key), SSL_OPT(OPT_SSL_KEY), IN_FS_CHARSET, DEFAULT(0)); +static Sys_var_charptr Sys_ssl_crl( + "ssl_crl", + "CRL file in PEM format (check OpenSSL docs, implies --ssl)", + READ_ONLY GLOBAL_VAR(opt_ssl_crl), SSL_OPT(OPT_SSL_CRL), + IN_FS_CHARSET, DEFAULT(0)); + +static Sys_var_charptr Sys_ssl_crlpath( + "ssl_crlpath", + "CRL directory (check OpenSSL docs, implies --ssl)", + READ_ONLY GLOBAL_VAR(opt_ssl_crlpath), SSL_OPT(OPT_SSL_CRLPATH), + IN_FS_CHARSET, DEFAULT(0)); + + // why ENUM and not BOOL ? static const char *updatable_views_with_limit_names[]= {"NO", "YES", 0}; static Sys_var_enum Sys_updatable_views_with_limit( @@ -2294,21 +3222,37 @@ static Sys_var_mybool Sys_sync_frm( static char *system_time_zone_ptr; static Sys_var_charptr Sys_system_time_zone( "system_time_zone", "The server system time zone", - READ_ONLY GLOBAL_VAR(system_time_zone_ptr), NO_CMD_LINE, + READ_ONLY GLOBAL_VAR(system_time_zone_ptr), + CMD_LINE_HELP_ONLY, IN_SYSTEM_CHARSET, DEFAULT(system_time_zone)); +/* + If One use views with prepared statements this should be bigger than + table_open_cache (now we allow 2 times bigger value) +*/ static Sys_var_ulong Sys_table_def_size( "table_definition_cache", "The number of cached table definitions", - GLOBAL_VAR(table_def_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(TABLE_DEF_CACHE_MIN, 512*1024), + GLOBAL_VAR(tdc_size), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(TABLE_DEF_CACHE_MIN, 2*1024*1024), DEFAULT(TABLE_DEF_CACHE_DEFAULT), BLOCK_SIZE(1)); + +static bool fix_table_open_cache(sys_var *, THD *, enum_var_type) +{ + mysql_mutex_unlock(&LOCK_global_system_variables); + tc_purge(); + mysql_mutex_lock(&LOCK_global_system_variables); + return false; +} + +/* Check the table_definition_cache comment if makes changes */ static Sys_var_ulong Sys_table_cache_size( "table_open_cache", "The number of cached open tables", - GLOBAL_VAR(table_cache_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, 512*1024), DEFAULT(TABLE_OPEN_CACHE_DEFAULT), - BLOCK_SIZE(1)); + GLOBAL_VAR(tc_size), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(10, 1024*1024), DEFAULT(TABLE_OPEN_CACHE_DEFAULT), + BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(fix_table_open_cache)); static Sys_var_ulong Sys_thread_cache_size( "thread_cache_size", @@ -2391,7 +3335,7 @@ static Sys_var_uint Sys_threadpool_size( "This parameter is roughly equivalent to maximum number of concurrently " "executing threads (threads in a waiting state do not count as executing).", GLOBAL_VAR(threadpool_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, MAX_THREAD_GROUPS), DEFAULT(my_getncpus()), BLOCK_SIZE(1), + VALID_RANGE(1, MAX_THREAD_GROUPS), DEFAULT(8), BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_threadpool_size), ON_UPDATE(fix_threadpool_size) ); @@ -2411,7 +3355,7 @@ static Sys_var_uint Sys_threadpool_max_threads( "thread_pool_max_threads", "Maximum allowed number of worker threads in the thread pool", GLOBAL_VAR(threadpool_max_threads), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, 65536), DEFAULT(500), BLOCK_SIZE(1), + VALID_RANGE(1, 65536), DEFAULT(1000), BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_tp_max_threads) ); @@ -2427,7 +3371,7 @@ static bool check_tx_isolation(sys_var *self, THD *thd, set_var *var) if (var->type == OPT_DEFAULT && thd->in_active_multi_stmt_transaction()) { DBUG_ASSERT(thd->in_multi_stmt_transaction_mode()); - my_error(ER_CANT_CHANGE_TX_ISOLATION, MYF(0)); + my_error(ER_CANT_CHANGE_TX_CHARACTERISTICS, MYF(0)); return TRUE; } return FALSE; @@ -2436,14 +3380,53 @@ static bool check_tx_isolation(sys_var *self, THD *thd, set_var *var) // NO_CMD_LINE - different name of the option static Sys_var_tx_isolation Sys_tx_isolation( "tx_isolation", "Default transaction isolation level", - SESSION_VAR(tx_isolation), NO_CMD_LINE, + NO_SET_STMT SESSION_VAR(tx_isolation), NO_CMD_LINE, tx_isolation_names, DEFAULT(ISO_REPEATABLE_READ), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_tx_isolation)); + +/** + Can't change the tx_read_only state if we are already in a + transaction. +*/ + +static bool check_tx_read_only(sys_var *self, THD *thd, set_var *var) +{ + if (var->type == OPT_DEFAULT && thd->in_active_multi_stmt_transaction()) + { + DBUG_ASSERT(thd->in_multi_stmt_transaction_mode()); + my_error(ER_CANT_CHANGE_TX_CHARACTERISTICS, MYF(0)); + return true; + } + return false; +} + + +bool Sys_var_tx_read_only::session_update(THD *thd, set_var *var) +{ + if (var->type == OPT_SESSION && Sys_var_mybool::session_update(thd, var)) + return true; + if (var->type == OPT_DEFAULT || !thd->in_active_multi_stmt_transaction()) + { + // @see Sys_var_tx_isolation::session_update() above for the rules. + thd->tx_read_only= var->save_result.ulonglong_value; + } + return false; +} + + +static Sys_var_tx_read_only Sys_tx_read_only( + "tx_read_only", "Default transaction access mode. If set to OFF, " + "the default, access is read/write. If set to ON, access is read-only. " + "The SET TRANSACTION statement can also change the value of this variable. " + "See SET TRANSACTION and START TRANSACTION.", + SESSION_VAR(tx_read_only), NO_CMD_LINE, DEFAULT(0), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_tx_read_only)); + static Sys_var_ulonglong Sys_tmp_table_size( "tmp_table_size", - "If an internal in-memory temporary table exceeds this size, MySQL " - "will automatically convert it to an on-disk MyISAM or Aria table", + "If an internal in-memory temporary table exceeds this size, MariaDB " + "will automatically convert it to an on-disk MyISAM or Aria table.", SESSION_VAR(tmp_table_size), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1024, (ulonglong)~(intptr)0), DEFAULT(16*1024*1024), BLOCK_SIZE(1)); @@ -2457,57 +3440,69 @@ static Sys_var_mybool Sys_timed_mutexes( static char *server_version_ptr; static Sys_var_charptr Sys_version( - "version", "Server version", - READ_ONLY GLOBAL_VAR(server_version_ptr), NO_CMD_LINE, + "version", "Server version number. It may also include a suffix " + "with configuration or build information. -debug indicates " + "debugging support was enabled on the server, and -log indicates " + "at least one of the binary log, general log or slow query log are " + "enabled, for example 10.1.1-MariaDB-mariadb1precise-log.", + READ_ONLY GLOBAL_VAR(server_version_ptr), + CMD_LINE_HELP_ONLY, IN_SYSTEM_CHARSET, DEFAULT(server_version)); static char *server_version_comment_ptr; static Sys_var_charptr Sys_version_comment( - "version_comment", "version_comment", - READ_ONLY GLOBAL_VAR(server_version_comment_ptr), NO_CMD_LINE, + "version_comment", "Value of the COMPILATION_COMMENT option " + "specified by CMake when building MariaDB, for example " + "mariadb.org binary distribution.", + READ_ONLY GLOBAL_VAR(server_version_comment_ptr), + CMD_LINE_HELP_ONLY, IN_SYSTEM_CHARSET, DEFAULT(MYSQL_COMPILATION_COMMENT)); static char *server_version_compile_machine_ptr; static Sys_var_charptr Sys_version_compile_machine( - "version_compile_machine", "version_compile_machine", - READ_ONLY GLOBAL_VAR(server_version_compile_machine_ptr), NO_CMD_LINE, - IN_SYSTEM_CHARSET, DEFAULT(MACHINE_TYPE)); + "version_compile_machine", "The machine type or architecture " + "MariaDB was built on, for example i686.", + READ_ONLY GLOBAL_VAR(server_version_compile_machine_ptr), + CMD_LINE_HELP_ONLY, IN_SYSTEM_CHARSET, DEFAULT(DEFAULT_MACHINE)); static char *server_version_compile_os_ptr; static Sys_var_charptr Sys_version_compile_os( - "version_compile_os", "version_compile_os", - READ_ONLY GLOBAL_VAR(server_version_compile_os_ptr), NO_CMD_LINE, + "version_compile_os", "Operating system that MariaDB was built " + "on, for example debian-linux-gnu.", + READ_ONLY GLOBAL_VAR(server_version_compile_os_ptr), + CMD_LINE_HELP_ONLY, IN_SYSTEM_CHARSET, DEFAULT(SYSTEM_TYPE)); +static char *malloc_library; +static Sys_var_charptr Sys_malloc_library( + "version_malloc_library", "Version of the used malloc library", + READ_ONLY GLOBAL_VAR(malloc_library), CMD_LINE_HELP_ONLY, + IN_SYSTEM_CHARSET, DEFAULT(MALLOC_LIBRARY)); + +#ifdef HAVE_YASSL +#include <openssl/ssl.h> +#define SSL_LIBRARY "YaSSL " YASSL_VERSION +#elif HAVE_OPENSSL +#include <openssl/crypto.h> +#define SSL_LIBRARY SSLeay_version(SSLEAY_VERSION) +#else +#error No SSL? +#endif + +static char *ssl_library; +static Sys_var_charptr Sys_ssl_library( + "version_ssl_library", "Version of the used SSL library", + READ_ONLY GLOBAL_VAR(ssl_library), CMD_LINE_HELP_ONLY, + IN_SYSTEM_CHARSET, DEFAULT(SSL_LIBRARY)); + static Sys_var_ulong Sys_net_wait_timeout( "wait_timeout", "The number of seconds the server waits for activity on a " "connection before closing it", - SESSION_VAR(net_wait_timeout), CMD_LINE(REQUIRED_ARG), + NO_SET_STMT SESSION_VAR(net_wait_timeout), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1, IF_WIN(INT_MAX32/1000, LONG_TIMEOUT)), DEFAULT(NET_WAIT_TIMEOUT), BLOCK_SIZE(1)); -/** propagates changes to the relevant flag of @@optimizer_switch */ -static bool fix_engine_condition_pushdown(sys_var *self, THD *thd, - enum_var_type type) -{ - SV *sv= (type == OPT_GLOBAL) ? &global_system_variables : &thd->variables; - if (sv->engine_condition_pushdown) - sv->optimizer_switch|= OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN; - else - sv->optimizer_switch&= ~OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN; - return false; -} -static Sys_var_mybool Sys_engine_condition_pushdown( - "engine_condition_pushdown", - "Push supported query conditions to the storage engine." - " Deprecated, use --optimizer-switch instead.", - SESSION_VAR(engine_condition_pushdown), - CMD_LINE(OPT_ARG, OPT_ENGINE_CONDITION_PUSHDOWN), - DEFAULT(TRUE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL), - ON_UPDATE(fix_engine_condition_pushdown), - DEPRECATED("'@@optimizer_switch'")); - static Sys_var_plugin Sys_default_storage_engine( "default_storage_engine", "The default storage engine for new tables", SESSION_VAR(table_plugin), NO_CMD_LINE, @@ -2521,6 +3516,17 @@ static Sys_var_plugin Sys_storage_engine( MYSQL_STORAGE_ENGINE_PLUGIN, DEFAULT(&default_storage_engine), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_not_null)); +static Sys_var_plugin Sys_default_tmp_storage_engine( + "default_tmp_storage_engine", "The default storage engine for user-created temporary tables", + SESSION_VAR(tmp_table_plugin), NO_CMD_LINE, + MYSQL_STORAGE_ENGINE_PLUGIN, DEFAULT(&default_tmp_storage_engine)); + +static Sys_var_plugin Sys_enforce_storage_engine( + "enforce_storage_engine", "Force the use of a storage engine for new tables", + SESSION_VAR(enforced_table_plugin), + NO_CMD_LINE, MYSQL_STORAGE_ENGINE_PLUGIN, + DEFAULT(&enforced_storage_engine), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_has_super)); + #if defined(ENABLED_DEBUG_SYNC) /* Variable can be set for the session only. @@ -2536,7 +3542,7 @@ static Sys_var_plugin Sys_storage_engine( */ static Sys_var_debug_sync Sys_debug_sync( "debug_sync", "Debug Sync Facility", - sys_var::ONLY_SESSION, NO_CMD_LINE, + NO_SET_STMT sys_var::ONLY_SESSION, NO_CMD_LINE, DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_has_super)); #endif /* defined(ENABLED_DEBUG_SYNC) */ @@ -2553,19 +3559,22 @@ static Sys_var_charptr Sys_date_format( "date_format", "The DATE format (ignored)", READ_ONLY GLOBAL_VAR(global_date_format.format.str), CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET, - DEFAULT(known_date_time_formats[ISO_FORMAT].date_format)); + DEFAULT(known_date_time_formats[ISO_FORMAT].date_format), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0), DEPRECATED("")); static Sys_var_charptr Sys_datetime_format( "datetime_format", "The DATETIME format (ignored)", READ_ONLY GLOBAL_VAR(global_datetime_format.format.str), CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET, - DEFAULT(known_date_time_formats[ISO_FORMAT].datetime_format)); + DEFAULT(known_date_time_formats[ISO_FORMAT].datetime_format), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0), DEPRECATED("")); static Sys_var_charptr Sys_time_format( "time_format", "The TIME format (ignored)", READ_ONLY GLOBAL_VAR(global_time_format.format.str), CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET, - DEFAULT(known_date_time_formats[ISO_FORMAT].time_format)); + DEFAULT(known_date_time_formats[ISO_FORMAT].time_format), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0), DEPRECATED("")); static bool fix_autocommit(sys_var *self, THD *thd, enum_var_type type) { @@ -2578,37 +3587,42 @@ static bool fix_autocommit(sys_var *self, THD *thd, enum_var_type type) return false; } - if (thd->variables.option_bits & OPTION_AUTOCOMMIT && - thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT) - { // activating autocommit - + if (test_all_bits(thd->variables.option_bits, + (OPTION_AUTOCOMMIT | OPTION_NOT_AUTOCOMMIT))) + { + // activating autocommit if (trans_commit_stmt(thd) || trans_commit(thd)) { thd->variables.option_bits&= ~OPTION_AUTOCOMMIT; + thd->mdl_context.release_transactional_locks(); + WSREP_DEBUG("autocommit, MDL TRX lock released: %lu", thd->thread_id); return true; } /* Don't close thread tables or release metadata locks: if we do so, we risk releasing locks/closing tables of expressions used to assign other variables, as in: - set @var=my_stored_function1(), @@autocommit=1, @var2=(select max(a) + set @var=my_stored_function1(), @@autocommit=1, @var2=(select MY_MAX(a) from my_table), ... The locks will be released at statement end anyway, as SET statement that assigns autocommit is marked to commit transaction implicitly at the end (@sa stmt_causes_implicitcommit()). */ thd->variables.option_bits&= - ~(OPTION_BEGIN | OPTION_KEEP_LOG | OPTION_NOT_AUTOCOMMIT); + ~(OPTION_BEGIN | OPTION_KEEP_LOG | OPTION_NOT_AUTOCOMMIT | + OPTION_GTID_BEGIN); thd->transaction.all.modified_non_trans_table= false; + thd->transaction.all.m_unsafe_rollback_flags&= ~THD_TRANS::DID_WAIT; thd->server_status|= SERVER_STATUS_AUTOCOMMIT; return false; } - if (!(thd->variables.option_bits & OPTION_AUTOCOMMIT) && - !(thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT)) - { // disabling autocommit - + if ((thd->variables.option_bits & + (OPTION_AUTOCOMMIT |OPTION_NOT_AUTOCOMMIT)) == 0) + { + // disabling autocommit thd->transaction.all.modified_non_trans_table= false; + thd->transaction.all.m_unsafe_rollback_flags&= ~THD_TRANS::DID_WAIT; thd->server_status&= ~SERVER_STATUS_AUTOCOMMIT; thd->variables.option_bits|= OPTION_NOT_AUTOCOMMIT; return false; @@ -2616,31 +3630,37 @@ static bool fix_autocommit(sys_var *self, THD *thd, enum_var_type type) return false; // autocommit value wasn't changed } + static Sys_var_bit Sys_autocommit( - "autocommit", "autocommit", - SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_AUTOCOMMIT, DEFAULT(TRUE), + "autocommit", "If set to 1, the default, all queries are committed " + "immediately. If set to 0, they are only committed upon a COMMIT statement" + ", or rolled back with a ROLLBACK statement. If autocommit is set to 0, " + "and then changed to 1, all open transactions are immediately committed.", + NO_SET_STMT SESSION_VAR(option_bits), NO_CMD_LINE, + OPTION_AUTOCOMMIT, DEFAULT(TRUE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_autocommit)); export sys_var *Sys_autocommit_ptr= &Sys_autocommit; // for sql_yacc.yy static Sys_var_mybool Sys_big_tables( - "big_tables", "Allow big result sets by saving all " - "temporary sets on file (Solves most 'table full' errors)", + "big_tables", "Old variable, which if set to 1, allows large result sets " + "by saving all temporary sets to disk, avoiding 'table full' errors. No " + "longer needed, as the server now handles this automatically. " + "sql_big_tables is a synonym.", SESSION_VAR(big_tables), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); -#ifndef TO_BE_DELETED /* Alias for big_tables */ -static Sys_var_mybool Sys_sql_big_tables( - "sql_big_tables", "alias for big_tables", - SESSION_VAR(big_tables), NO_CMD_LINE, DEFAULT(FALSE)); -#endif - static Sys_var_bit Sys_big_selects( - "sql_big_selects", "sql_big_selects", + "sql_big_selects", "If set to 0, MariaDB will not perform large SELECTs." + " See max_join_size for details. If max_join_size is set to anything but " + "DEFAULT, sql_big_selects is automatically set to 0. If sql_big_selects " + "is again set, max_join_size will be ignored.", SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_BIG_SELECTS, DEFAULT(FALSE)); static Sys_var_bit Sys_log_off( - "sql_log_off", "sql_log_off", - SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_LOG_OFF, + "sql_log_off", "If set to 1 (0 is the default), no logging to the general " + "query log is done for the client. Only clients with the SUPER privilege " + "can update this variable.", + NO_SET_STMT SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_LOG_OFF, DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_has_super)); /** @@ -2696,63 +3716,104 @@ static bool check_sql_log_bin(sys_var *self, THD *thd, set_var *var) return FALSE; } -static Sys_var_mybool Sys_log_binlog( - "sql_log_bin", "Controls whether logging to the binary log is done", +static Sys_var_mybool Sys_log_binlog( + "sql_log_bin", "If set to 0 (1 is the default), no logging to the binary " + "log is done for the client. Only clients with the SUPER privilege can " + "update this variable. Can have unintended consequences if set globally, " + "see SET SQL_LOG_BIN. Starting MariaDB 10.1.7, this variable does not " + "affect the replication of events in a Galera cluster.", SESSION_VAR(sql_log_bin), NO_CMD_LINE, DEFAULT(TRUE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_sql_log_bin), ON_UPDATE(fix_sql_log_bin_after_update)); static Sys_var_bit Sys_sql_warnings( - "sql_warnings", "sql_warnings", + "sql_warnings", "If set to 1, single-row INSERTs will produce a string " + "containing warning information if a warning occurs.", SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_WARNINGS, DEFAULT(FALSE)); static Sys_var_bit Sys_sql_notes( - "sql_notes", "sql_notes", + "sql_notes", "If set to 1, the default, warning_count is incremented each " + "time a Note warning is encountered. If set to 0, Note warnings are not " + "recorded. mysqldump has outputs to set this variable to 0 so that no " + "unnecessary increments occur when data is reloaded.", SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_SQL_NOTES, DEFAULT(TRUE)); static Sys_var_bit Sys_auto_is_null( - "sql_auto_is_null", "sql_auto_is_null", + "sql_auto_is_null", "If set to 1, the query SELECT * FROM table_name WHERE " + "auto_increment_column IS NULL will return an auto-increment that has just " + "been successfully inserted, the same as the LAST_INSERT_ID() function. Some" + " ODBC programs make use of this IS NULL comparison.", SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_AUTO_IS_NULL, DEFAULT(FALSE), NO_MUTEX_GUARD, IN_BINLOG); static Sys_var_bit Sys_safe_updates( - "sql_safe_updates", "sql_safe_updates", + "sql_safe_updates", "If set to 1, UPDATEs and DELETEs need either a key in " + "the WHERE clause, or a LIMIT clause, or else they will aborted. Prevents " + "the common mistake of accidentally deleting or updating every row in a table.", SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_SAFE_UPDATES, DEFAULT(FALSE)); static Sys_var_bit Sys_buffer_results( - "sql_buffer_result", "sql_buffer_result", + "sql_buffer_result", "If set to 1 (0 is default), results from SELECT " + "statements are always placed into temporary tables. This can help the " + "server when it takes a long time to send the results to the client by " + "allowing the table locks to be freed early.", SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_BUFFER_RESULT, DEFAULT(FALSE)); static Sys_var_bit Sys_quote_show_create( - "sql_quote_show_create", "sql_quote_show_create", + "sql_quote_show_create", "If set to 1, the default, the server will " + "quote identifiers for SHOW CREATE DATABASE, SHOW CREATE TABLE and " + "SHOW CREATE VIEW statements. Quoting is disabled if set to 0. Enable " + "to ensure replications works when identifiers require quoting.", SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_QUOTE_SHOW_CREATE, DEFAULT(TRUE)); static Sys_var_bit Sys_foreign_key_checks( - "foreign_key_checks", "foreign_key_checks", + "foreign_key_checks", "If set to 1 (the default) foreign key constraints" + " (including ON UPDATE and ON DELETE behavior) InnoDB tables are checked," + " while if set to 0, they are not checked. 0 is not recommended for normal " + "use, though it can be useful in situations where you know the data is " + "consistent, but want to reload data in a different order from that that " + "specified by parent/child relationships. Setting this variable to 1 does " + "not retrospectively check for inconsistencies introduced while set to 0.", SESSION_VAR(option_bits), NO_CMD_LINE, REVERSE(OPTION_NO_FOREIGN_KEY_CHECKS), DEFAULT(TRUE), NO_MUTEX_GUARD, IN_BINLOG); static Sys_var_bit Sys_unique_checks( - "unique_checks", "unique_checks", + "unique_checks", "If set to 1, the default, secondary indexes in InnoDB " + "tables are performed. If set to 0, storage engines can (but are not " + "required to) assume that duplicate keys are not present in input data. " + "Set to 0 to speed up imports of large tables to InnoDB. The storage " + "engine will still issue a duplicate key error if it detects one, even " + "if set to 0.", SESSION_VAR(option_bits), NO_CMD_LINE, REVERSE(OPTION_RELAXED_UNIQUE_CHECKS), DEFAULT(TRUE), NO_MUTEX_GUARD, IN_BINLOG); #ifdef ENABLED_PROFILING +static bool update_profiling(sys_var *self, THD *thd, enum_var_type type) +{ + if (type == OPT_SESSION) + thd->profiling.reset(); + return false; +} + static Sys_var_bit Sys_profiling( - "profiling", "profiling", - SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_PROFILING, - DEFAULT(FALSE)); + "profiling", "If set to 1 (0 is default), statement profiling will be " + "enabled. See SHOW PROFILES and SHOW PROFILE.", + NO_SET_STMT SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_PROFILING, + DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(update_profiling)); static Sys_var_ulong Sys_profiling_history_size( - "profiling_history_size", "Limit of query profiling memory", - SESSION_VAR(profiling_history_size), CMD_LINE(REQUIRED_ARG), + "profiling_history_size", "Number of statements about which profiling " + "information is maintained. If set to 0, no profiles are stored. " + "See SHOW PROFILES.", + NO_SET_STMT SESSION_VAR(profiling_history_size), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 100), DEFAULT(15), BLOCK_SIZE(1)); #endif @@ -2785,8 +3846,13 @@ static bool check_skip_replication(sys_var *self, THD *thd, set_var *var) } static Sys_var_bit Sys_skip_replication( - "skip_replication", "skip_replication", - SESSION_ONLY(option_bits), NO_CMD_LINE, OPTION_SKIP_REPLICATION, + "skip_replication", "Changes are logged into the binary log with the " + "@@skip_replication flag set. Such events will not be replicated by " + "slaves that run with --replicate-events-marked-for-skip set different " + "from its default of REPLICATE. See Selectively skipping replication " + "of binlog events for more information.", + NO_SET_STMT SESSION_ONLY(option_bits), + NO_CMD_LINE, OPTION_SKIP_REPLICATION, DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_skip_replication)); @@ -2796,28 +3862,11 @@ static Sys_var_harows Sys_select_limit( SESSION_VAR(select_limit), NO_CMD_LINE, VALID_RANGE(0, HA_POS_ERROR), DEFAULT(HA_POS_ERROR), BLOCK_SIZE(1)); -static bool update_timestamp(THD *thd, set_var *var) -{ - if (var->value) - { - my_hrtime_t hrtime = { hrtime_from_time(var->save_result.double_value) }; - thd->set_time(hrtime); - } - else // SET timestamp=DEFAULT - thd->user_time.val= 0; - return false; -} -static double read_timestamp(THD *thd) -{ - return thd->start_time + - thd->start_time_sec_part/(double)TIME_SECOND_PART_FACTOR; -} -static Sys_var_session_special_double Sys_timestamp( +static Sys_var_timestamp Sys_timestamp( "timestamp", "Set the time for this client", sys_var::ONLY_SESSION, NO_CMD_LINE, VALID_RANGE(0, TIMESTAMP_MAX_VALUE), - NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0), - ON_UPDATE(update_timestamp), ON_READ(read_timestamp)); + NO_MUTEX_GUARD, IN_BINLOG); static bool update_last_insert_id(THD *thd, set_var *var) { @@ -2901,9 +3950,9 @@ static bool update_rand_seed1(THD *thd, set_var *var) thd->rand.seed1= (ulong) var->save_result.ulonglong_value; return false; } -static ulonglong read_rand_seed(THD *thd) +static ulonglong read_rand_seed1(THD *thd) { - return 0; + return thd->rand.seed1; } static Sys_var_session_special Sys_rand_seed1( "rand_seed1", "Sets the internal state of the RAND() " @@ -2911,7 +3960,7 @@ static Sys_var_session_special Sys_rand_seed1( sys_var::ONLY_SESSION, NO_CMD_LINE, VALID_RANGE(0, ULONG_MAX), BLOCK_SIZE(1), NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0), - ON_UPDATE(update_rand_seed1), ON_READ(read_rand_seed)); + ON_UPDATE(update_rand_seed1), ON_READ(read_rand_seed1)); static bool update_rand_seed2(THD *thd, set_var *var) { @@ -2923,17 +3972,21 @@ static bool update_rand_seed2(THD *thd, set_var *var) thd->rand.seed2= (ulong) var->save_result.ulonglong_value; return false; } +static ulonglong read_rand_seed2(THD *thd) +{ + return thd->rand.seed2; +} static Sys_var_session_special Sys_rand_seed2( "rand_seed2", "Sets the internal state of the RAND() " "generator for replication purposes", sys_var::ONLY_SESSION, NO_CMD_LINE, VALID_RANGE(0, ULONG_MAX), BLOCK_SIZE(1), NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(0), - ON_UPDATE(update_rand_seed2), ON_READ(read_rand_seed)); + ON_UPDATE(update_rand_seed2), ON_READ(read_rand_seed2)); static ulonglong read_error_count(THD *thd) { - return thd->warning_info->error_count(); + return thd->get_stmt_da()->error_count(); } // this really belongs to the SHOW STATUS static Sys_var_session_special Sys_error_count( @@ -2945,7 +3998,7 @@ static Sys_var_session_special Sys_error_count( static ulonglong read_warning_count(THD *thd) { - return thd->warning_info->warn_count(); + return thd->get_stmt_da()->warn_count(); } // this really belongs to the SHOW STATUS static Sys_var_session_special Sys_warning_count( @@ -2971,7 +4024,7 @@ static char *glob_hostname_ptr; static Sys_var_charptr Sys_hostname( "hostname", "Server host name", READ_ONLY GLOBAL_VAR(glob_hostname_ptr), NO_CMD_LINE, - IN_FS_CHARSET, DEFAULT(glob_hostname)); + IN_SYSTEM_CHARSET, DEFAULT(glob_hostname)); #ifndef EMBEDDED_LIBRARY static Sys_var_charptr Sys_repl_report_host( @@ -2984,21 +4037,21 @@ static Sys_var_charptr Sys_repl_report_host( "NAT and other routing issues, that IP may not be valid for connecting " "to the slave from the master or other hosts", READ_ONLY GLOBAL_VAR(report_host), CMD_LINE(REQUIRED_ARG), - IN_FS_CHARSET, DEFAULT(0)); + IN_SYSTEM_CHARSET, DEFAULT(0)); static Sys_var_charptr Sys_repl_report_user( "report_user", "The account user name of the slave to be reported to the master " "during slave registration", READ_ONLY GLOBAL_VAR(report_user), CMD_LINE(REQUIRED_ARG), - IN_FS_CHARSET, DEFAULT(0)); + IN_SYSTEM_CHARSET, DEFAULT(0)); static Sys_var_charptr Sys_repl_report_password( "report_password", "The account password of the slave to be reported to the master " "during slave registration", READ_ONLY GLOBAL_VAR(report_password), CMD_LINE(REQUIRED_ARG), - IN_FS_CHARSET, DEFAULT(0)); + IN_SYSTEM_CHARSET, DEFAULT(0)); static Sys_var_uint Sys_repl_report_port( "report_port", @@ -3044,6 +4097,14 @@ static bool check_log_path(sys_var *self, THD *thd, set_var *var) if (!path_length) return true; + if (!is_filename_allowed(var->save_result.string_value.str, + var->save_result.string_value.length, TRUE)) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), + self->name.str, var->save_result.string_value.str); + return true; + } + static const LEX_CSTRING my_cnf= { STRING_WITH_LEN("my.cnf") }; static const LEX_CSTRING my_ini= { STRING_WITH_LEN("my.ini") }; if (path_length >= my_cnf.length) @@ -3121,7 +4182,7 @@ static void reopen_slow_log(char* name) static bool fix_slow_log_file(sys_var *self, THD *thd, enum_var_type type) { return fix_log(&opt_slow_logname, opt_log_basename, "-slow.log", - opt_slow_log, reopen_slow_log); + global_system_variables.sql_log_slow, reopen_slow_log); } static Sys_var_charptr Sys_slow_log_path( "slow_query_log_file", "Log slow queries to given log file. " @@ -3131,97 +4192,82 @@ static Sys_var_charptr Sys_slow_log_path( IN_FS_CHARSET, DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_log_path), ON_UPDATE(fix_slow_log_file)); -/// @todo deprecate these four legacy have_PLUGIN variables and use I_S instead -export SHOW_COMP_OPTION have_csv, have_innodb= SHOW_OPTION_DISABLED; -export SHOW_COMP_OPTION have_ndbcluster, have_partitioning; -static Sys_var_have Sys_have_csv( - "have_csv", "have_csv", - READ_ONLY GLOBAL_VAR(have_csv), NO_CMD_LINE); - -static Sys_var_have Sys_have_innodb( - "have_innodb", "have_innodb", - READ_ONLY GLOBAL_VAR(have_innodb), NO_CMD_LINE); - -static Sys_var_have Sys_have_ndbcluster( - "have_ndbcluster", "have_ndbcluster", - READ_ONLY GLOBAL_VAR(have_ndbcluster), NO_CMD_LINE); - -static Sys_var_have Sys_have_partition_db( - "have_partitioning", "have_partitioning", - READ_ONLY GLOBAL_VAR(have_partitioning), NO_CMD_LINE); - static Sys_var_have Sys_have_compress( - "have_compress", "have_compress", + "have_compress", "If the zlib compression library is accessible to the " + "server, this will be set to YES, otherwise it will be NO. The COMPRESS() " + "and UNCOMPRESS() functions will only be available if set to YES.", READ_ONLY GLOBAL_VAR(have_compress), NO_CMD_LINE); static Sys_var_have Sys_have_crypt( - "have_crypt", "have_crypt", + "have_crypt", "If the crypt() system call is available this variable will " + "be set to YES, otherwise it will be set to NO. If set to NO, the " + "ENCRYPT() function cannot be used.", READ_ONLY GLOBAL_VAR(have_crypt), NO_CMD_LINE); static Sys_var_have Sys_have_dlopen( - "have_dynamic_loading", "have_dynamic_loading", + "have_dynamic_loading", "If the server supports dynamic loading of plugins, " + "will be set to YES, otherwise will be set to NO.", READ_ONLY GLOBAL_VAR(have_dlopen), NO_CMD_LINE); static Sys_var_have Sys_have_geometry( - "have_geometry", "have_geometry", + "have_geometry", "If the server supports spatial data types, will be set to " + "YES, otherwise will be set to NO.", READ_ONLY GLOBAL_VAR(have_geometry), NO_CMD_LINE); static Sys_var_have Sys_have_openssl( - "have_openssl", "have_openssl", - READ_ONLY GLOBAL_VAR(have_ssl), NO_CMD_LINE); + "have_openssl", "Comparing have_openssl with have_ssl will indicate whether " + "YaSSL or openssl was used. If YaSSL, have_ssl will be YES, but have_openssl " + "will be NO.", + READ_ONLY GLOBAL_VAR(have_openssl), NO_CMD_LINE); static Sys_var_have Sys_have_profiling( - "have_profiling", "have_profiling", + "have_profiling", "If statement profiling is available, will be set to YES, " + "otherwise will be set to NO. See SHOW PROFILES and SHOW PROFILE.", READ_ONLY GLOBAL_VAR(have_profiling), NO_CMD_LINE); static Sys_var_have Sys_have_query_cache( - "have_query_cache", "have_query_cache", + "have_query_cache", "If the server supports the query cache, will be set to " + "YES, otherwise will be set to NO.", READ_ONLY GLOBAL_VAR(have_query_cache), NO_CMD_LINE); static Sys_var_have Sys_have_rtree_keys( - "have_rtree_keys", "have_rtree_keys", + "have_rtree_keys", "If RTREE indexes (used for spatial indexes) " + "are available, will be set to YES, otherwise will be set to NO.", READ_ONLY GLOBAL_VAR(have_rtree_keys), NO_CMD_LINE); static Sys_var_have Sys_have_ssl( - "have_ssl", "have_ssl", + "have_ssl", "If the server supports secure connections, will be set to YES, " + "otherwise will be set to NO. If set to DISABLED, the server was compiled with " + "TLS support, but was not started with TLS support (see the mysqld options). " + "See also have_openssl.", READ_ONLY GLOBAL_VAR(have_ssl), NO_CMD_LINE); static Sys_var_have Sys_have_symlink( - "have_symlink", "have_symlink", + "have_symlink", "If symbolic link support is enabled, will be set to YES, " + "otherwise will be set to NO. Required for the INDEX DIRECTORY and DATA " + "DIRECTORY table options (see CREATE TABLE) and Windows symlink support. " + "Will be set to DISABLED if the server is started with the " + "--skip-symbolic-links option.", READ_ONLY GLOBAL_VAR(have_symlink), NO_CMD_LINE); static bool fix_log_state(sys_var *self, THD *thd, enum_var_type type); + static Sys_var_mybool Sys_general_log( "general_log", "Log connections and queries to a table or log file. " "Defaults logging to a file 'hostname'.log or a table mysql.general_log" - "if --log-output=TABLE is used", + "if --log-output=TABLE is used.", GLOBAL_VAR(opt_log), CMD_LINE(OPT_ARG), DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(fix_log_state)); -// Synonym of "general_log" for consistency with SHOW VARIABLES output -static Sys_var_mybool Sys_log( - "log", "Alias for --general-log. Deprecated", - GLOBAL_VAR(opt_log), NO_CMD_LINE, - DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), - ON_UPDATE(fix_log_state), DEPRECATED("'@@general_log'")); - static Sys_var_mybool Sys_slow_query_log( "slow_query_log", "Log slow queries to a table or log file. Defaults logging to a file " "'hostname'-slow.log or a table mysql.slow_log if --log-output=TABLE is " - "used. Must be enabled to activate other slow log options", - GLOBAL_VAR(opt_slow_log), CMD_LINE(OPT_ARG), - DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), - ON_UPDATE(fix_log_state)); - -/* Synonym of "slow_query_log" for consistency with SHOW VARIABLES output */ -static Sys_var_mybool Sys_log_slow( - "log_slow_queries", - "Alias for --slow-query-log. Deprecated", - GLOBAL_VAR(opt_slow_log), NO_CMD_LINE, - DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), - ON_UPDATE(fix_log_state), DEPRECATED("'@@slow_query_log'")); + "used. Must be enabled to activate other slow log options.", + SESSION_VAR(sql_log_slow), CMD_LINE(OPT_ARG), + DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(0), ON_UPDATE(fix_log_state)); static bool fix_log_state(sys_var *self, THD *thd, enum_var_type type) { @@ -3229,15 +4275,18 @@ static bool fix_log_state(sys_var *self, THD *thd, enum_var_type type) my_bool *UNINIT_VAR(newvalptr), newval, UNINIT_VAR(oldval); uint UNINIT_VAR(log_type); - if (self == &Sys_general_log || self == &Sys_log) + if (type != OPT_GLOBAL) + return 0; + + if (self == &Sys_general_log) { newvalptr= &opt_log; oldval= logger.get_log_file_handler()->is_open(); log_type= QUERY_LOG_GENERAL; } - else if (self == &Sys_slow_query_log || self == &Sys_log_slow) + else if (self == &Sys_slow_query_log) { - newvalptr= &opt_slow_log; + newvalptr= &global_system_variables.sql_log_slow; oldval= logger.get_slow_log_file_handler()->is_open(); log_type= QUERY_LOG_SLOW; } @@ -3278,8 +4327,7 @@ static bool fix_log_output(sys_var *self, THD *thd, enum_var_type type) static const char *log_output_names[] = { "NONE", "FILE", "TABLE", NULL}; static Sys_var_set Sys_log_output( - "log_output", "Syntax: log-output=value[,value...], " - "where \"value\" could be TABLE, FILE or NONE", + "log_output", "How logs should be written", GLOBAL_VAR(log_output_options), CMD_LINE(REQUIRED_ARG), log_output_names, DEFAULT(LOG_FILE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_not_empty_set), ON_UPDATE(fix_log_output)); @@ -3288,110 +4336,106 @@ static Sys_var_set Sys_log_output( static Sys_var_mybool Sys_log_slave_updates( "log_slave_updates", "Tells the slave to log the updates from " "the slave thread to the binary log. You will need to turn it on if " - "you plan to daisy-chain the slaves", + "you plan to daisy-chain the slaves.", READ_ONLY GLOBAL_VAR(opt_log_slave_updates), CMD_LINE(OPT_ARG), DEFAULT(0)); static Sys_var_charptr Sys_relay_log( - "relay_log", "The location and name to use for relay logs", + "relay_log", "The location and name to use for relay logs.", READ_ONLY GLOBAL_VAR(opt_relay_logname), CMD_LINE(REQUIRED_ARG), IN_FS_CHARSET, DEFAULT(0)); +/* + Uses NO_CMD_LINE since the --relay-log-index option set + opt_relaylog_index_name variable and computes a value for the + relay_log_index variable. +*/ static Sys_var_charptr Sys_relay_log_index( "relay_log_index", "The location and name to use for the file " - "that keeps a list of the last relay logs", - READ_ONLY GLOBAL_VAR(opt_relaylog_index_name), CMD_LINE(REQUIRED_ARG), + "that keeps a list of the last relay logs.", + READ_ONLY GLOBAL_VAR(relay_log_index), NO_CMD_LINE, + IN_FS_CHARSET, DEFAULT(0)); + +/* + Uses NO_CMD_LINE since the --log-bin-index option set + opt_binlog_index_name variable and computes a value for the + log_bin_index variable. +*/ +static Sys_var_charptr Sys_binlog_index( + "log_bin_index", "File that holds the names for last binary log files.", + READ_ONLY GLOBAL_VAR(log_bin_index), NO_CMD_LINE, + IN_FS_CHARSET, DEFAULT(0)); + +static Sys_var_charptr Sys_relay_log_basename( + "relay_log_basename", + "The full path of the relay log file names, excluding the extension.", + READ_ONLY GLOBAL_VAR(relay_log_basename), NO_CMD_LINE, + IN_FS_CHARSET, DEFAULT(0)); + +static Sys_var_charptr Sys_log_bin_basename( + "log_bin_basename", + "The full path of the binary log file names, excluding the extension.", + READ_ONLY GLOBAL_VAR(log_bin_basename), NO_CMD_LINE, IN_FS_CHARSET, DEFAULT(0)); static Sys_var_charptr Sys_relay_log_info_file( "relay_log_info_file", "The location and name of the file that " - "remembers where the SQL replication thread is in the relay logs", + "remembers where the SQL replication thread is in the relay logs.", READ_ONLY GLOBAL_VAR(relay_log_info_file), CMD_LINE(REQUIRED_ARG), IN_FS_CHARSET, DEFAULT(0)); static Sys_var_mybool Sys_relay_log_purge( "relay_log_purge", "if disabled - do not purge relay logs. " - "if enabled - purge them as soon as they are no more needed", + "if enabled - purge them as soon as they are no more needed.", GLOBAL_VAR(relay_log_purge), CMD_LINE(OPT_ARG), DEFAULT(TRUE)); static Sys_var_mybool Sys_relay_log_recovery( "relay_log_recovery", "Enables automatic relay log recovery " "right after the database startup, which means that the IO Thread " "starts re-fetching from the master right after the last transaction " - "processed", + "processed.", GLOBAL_VAR(relay_log_recovery), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); -bool Sys_var_rpl_filter::do_check(THD *thd, set_var *var) -{ - bool status; - - /* - We must not be holding LOCK_global_system_variables here, otherwise we can - deadlock with THD::init() which is invoked from within the slave threads - with opposite locking order. - */ - mysql_mutex_assert_not_owner(&LOCK_global_system_variables); - - mysql_mutex_lock(&LOCK_active_mi); - mysql_mutex_lock(&active_mi->rli.run_lock); - - status= active_mi->rli.slave_running; - - mysql_mutex_unlock(&active_mi->rli.run_lock); - mysql_mutex_unlock(&LOCK_active_mi); - - if (status) - my_error(ER_SLAVE_MUST_STOP, MYF(0)); - else - status= Sys_var_charptr::do_string_check(thd, var, charset(thd)); - - return status; -} - -void Sys_var_rpl_filter::lock(void) -{ - /* - Starting a slave thread causes the new thread to attempt to - acquire LOCK_global_system_variables (in THD::init) while - LOCK_active_mi is being held by the thread that initiated - the process. In order to not violate the lock order, unlock - LOCK_global_system_variables before grabbing LOCK_active_mi. - */ - mysql_mutex_unlock(&LOCK_global_system_variables); - - mysql_mutex_lock(&LOCK_active_mi); - mysql_mutex_lock(&active_mi->rli.run_lock); -} - -void Sys_var_rpl_filter::unlock(void) -{ - mysql_mutex_unlock(&active_mi->rli.run_lock); - mysql_mutex_unlock(&LOCK_active_mi); - - mysql_mutex_lock(&LOCK_global_system_variables); -} bool Sys_var_rpl_filter::global_update(THD *thd, set_var *var) { - bool slave_running, status= false; + bool result= true; // Assume error + LEX_STRING *base_name= &var->base; - lock(); + if (!base_name->length) + base_name= &thd->variables.default_master_connection; - if (! (slave_running= active_mi->rli.slave_running)) - status= set_filter_value(var->save_result.string_value.str); - - if (slave_running) - my_error(ER_SLAVE_MUST_STOP, MYF(0)); + mysql_mutex_unlock(&LOCK_global_system_variables); - unlock(); + if (Master_info *mi= get_master_info(base_name, !var->base.length ? + Sql_condition::WARN_LEVEL_ERROR : + Sql_condition::WARN_LEVEL_WARN)) + { + if (mi->rli.slave_running) + { + my_error(ER_SLAVE_MUST_STOP, MYF(0), + mi->connection_name.length, + mi->connection_name.str); + result= true; + } + else + { + result= set_filter_value(var->save_result.string_value.str, mi); + } + mi->release(); + } - return slave_running || status; + mysql_mutex_lock(&LOCK_global_system_variables); + return result; } -bool Sys_var_rpl_filter::set_filter_value(const char *value) +bool Sys_var_rpl_filter::set_filter_value(const char *value, Master_info *mi) { bool status= true; + Rpl_filter* rpl_filter= mi->rpl_filter; + /* Proctect against other threads */ + mysql_mutex_lock(&LOCK_active_mi); switch (opt_id) { case OPT_REPLICATE_DO_DB: status= rpl_filter->set_do_db(value); @@ -3412,19 +4456,34 @@ bool Sys_var_rpl_filter::set_filter_value(const char *value) status= rpl_filter->set_wild_ignore_table(value); break; } - + mysql_mutex_unlock(&LOCK_active_mi); return status; } -uchar *Sys_var_rpl_filter::global_value_ptr(THD *thd, LEX_STRING *base) +uchar *Sys_var_rpl_filter::global_value_ptr(THD *thd, + const LEX_STRING *base_name) { char buf[256]; String tmp(buf, sizeof(buf), &my_charset_bin); + uchar *ret; + Master_info *mi; + Rpl_filter *rpl_filter; - tmp.length(0); + mysql_mutex_unlock(&LOCK_global_system_variables); + mi= get_master_info(base_name, !base_name->length ? + Sql_condition::WARN_LEVEL_ERROR : + Sql_condition::WARN_LEVEL_WARN); - lock(); + if (!mi) + { + mysql_mutex_lock(&LOCK_global_system_variables); + return 0; + } + + rpl_filter= mi->rpl_filter; + tmp.length(0); + mysql_mutex_lock(&LOCK_active_mi); switch (opt_id) { case OPT_REPLICATE_DO_DB: rpl_filter->get_do_db(&tmp); @@ -3445,10 +4504,14 @@ uchar *Sys_var_rpl_filter::global_value_ptr(THD *thd, LEX_STRING *base) rpl_filter->get_wild_ignore_table(&tmp); break; } + mysql_mutex_unlock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_global_system_variables); - unlock(); + mi->release(); - return (uchar *) thd->strmake(tmp.ptr(), tmp.length()); + ret= (uchar *) thd->strmake(tmp.ptr(), tmp.length()); + + return ret; } static Sys_var_rpl_filter Sys_replicate_do_db( @@ -3497,72 +4560,116 @@ static Sys_var_charptr Sys_slave_load_tmpdir( READ_ONLY GLOBAL_VAR(slave_load_tmpdir), CMD_LINE(REQUIRED_ARG), IN_FS_CHARSET, DEFAULT(0)); -static bool fix_slave_net_timeout(sys_var *self, THD *thd, enum_var_type type) -{ - DEBUG_SYNC(thd, "fix_slave_net_timeout"); +static Sys_var_uint Sys_slave_net_timeout( + "slave_net_timeout", "Number of seconds to wait for more data " + "from any master/slave connection before aborting the read", + GLOBAL_VAR(slave_net_timeout), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(SLAVE_NET_TIMEOUT), BLOCK_SIZE(1)); + + +/* + Access a multi_source variable + Return 0 + warning if it doesn't exist +*/ +ulonglong Sys_var_multi_source_ulonglong:: +get_master_info_ulonglong_value(THD *thd, ptrdiff_t offset) +{ + Master_info *mi; + ulonglong res= 0; // Default value mysql_mutex_unlock(&LOCK_global_system_variables); - mysql_mutex_lock(&LOCK_active_mi); - DBUG_PRINT("info", ("slave_net_timeout=%u mi->heartbeat_period=%.3f", - slave_net_timeout, - (active_mi? active_mi->heartbeat_period : 0.0))); - if (active_mi && slave_net_timeout < active_mi->heartbeat_period) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX, - ER(ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX)); - mysql_mutex_unlock(&LOCK_active_mi); + if ((mi= get_master_info(&thd->variables.default_master_connection, + Sql_condition::WARN_LEVEL_WARN))) + { + res= *((ulonglong*) (((uchar*) mi) + master_info_offset)); + mi->release(); + } mysql_mutex_lock(&LOCK_global_system_variables); - return false; + return res; } -static Sys_var_uint Sys_slave_net_timeout( - "slave_net_timeout", "Number of seconds to wait for more data " - "from a master/slave connection before aborting the read", - GLOBAL_VAR(slave_net_timeout), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(SLAVE_NET_TIMEOUT), BLOCK_SIZE(1), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), - ON_UPDATE(fix_slave_net_timeout)); + -static bool check_slave_skip_counter(sys_var *self, THD *thd, set_var *var) +bool update_multi_source_variable(sys_var *self_var, THD *thd, + enum_var_type type) { - bool result= false; - mysql_mutex_lock(&LOCK_active_mi); - mysql_mutex_lock(&active_mi->rli.run_lock); - if (active_mi->rli.slave_running) + Sys_var_multi_source_ulonglong *self= (Sys_var_multi_source_ulonglong*) self_var; + bool result= true; + Master_info *mi; + + if (type == OPT_GLOBAL) + mysql_mutex_unlock(&LOCK_global_system_variables); + if ((mi= (get_master_info(&thd->variables.default_master_connection, + Sql_condition::WARN_LEVEL_ERROR)))) { - my_message(ER_SLAVE_MUST_STOP, ER(ER_SLAVE_MUST_STOP), MYF(0)); - result= true; + mysql_mutex_lock(&mi->rli.run_lock); + mysql_mutex_lock(&mi->rli.data_lock); + result= self->update_variable(thd, mi); + mysql_mutex_unlock(&mi->rli.data_lock); + mysql_mutex_unlock(&mi->rli.run_lock); + mi->release(); } - mysql_mutex_unlock(&active_mi->rli.run_lock); - mysql_mutex_unlock(&LOCK_active_mi); + if (type == OPT_GLOBAL) + mysql_mutex_lock(&LOCK_global_system_variables); return result; } -static bool fix_slave_skip_counter(sys_var *self, THD *thd, enum_var_type type) + +static bool update_slave_skip_counter(sys_var *self, THD *thd, Master_info *mi) { - mysql_mutex_unlock(&LOCK_global_system_variables); - mysql_mutex_lock(&LOCK_active_mi); - mysql_mutex_lock(&active_mi->rli.run_lock); - /* - The following test should normally never be true as we test this - in the check function; To be safe against multiple - SQL_SLAVE_SKIP_COUNTER request, we do the check anyway - */ - if (!active_mi->rli.slave_running) + if (mi->rli.slave_running) { - mysql_mutex_lock(&active_mi->rli.data_lock); - active_mi->rli.slave_skip_counter= sql_slave_skip_counter; - mysql_mutex_unlock(&active_mi->rli.data_lock); + my_error(ER_SLAVE_MUST_STOP, MYF(0), mi->connection_name.length, + mi->connection_name.str); + return true; } - mysql_mutex_unlock(&active_mi->rli.run_lock); - mysql_mutex_unlock(&LOCK_active_mi); - mysql_mutex_lock(&LOCK_global_system_variables); - return 0; + if (mi->using_gtid != Master_info::USE_GTID_NO && mi->using_parallel()) + { + ulong domain_count; + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + domain_count= rpl_global_gtid_slave_state->count(); + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + if (domain_count > 1) + { + /* + With domain-based parallel replication, the slave position is + multi-dimensional, so the relay log position is not very meaningful. + It might not even correspond to the next GTID to execute in _any_ + domain (the case after error stop). So slave_skip_counter will most + likely not do what the user intends. Instead give an error, with a + suggestion to instead set @@gtid_slave_pos past the point of error; + this works reliably also in the case of multiple domains. + */ + my_error(ER_SLAVE_SKIP_NOT_IN_GTID, MYF(0)); + return true; + } + } + + /* The value was stored temporarily in thd */ + mi->rli.slave_skip_counter= thd->variables.slave_skip_counter; + return false; } -static Sys_var_uint Sys_slave_skip_counter( - "sql_slave_skip_counter", "sql_slave_skip_counter", - GLOBAL_VAR(sql_slave_skip_counter), NO_CMD_LINE, + +static Sys_var_multi_source_ulonglong Sys_slave_skip_counter( + "sql_slave_skip_counter", "Skip the next N events from the master log", + SESSION_VAR(slave_skip_counter), NO_CMD_LINE, + MASTER_INFO_VAR(rli.slave_skip_counter), VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_slave_skip_counter), - ON_UPDATE(fix_slave_skip_counter)); + ON_UPDATE(update_slave_skip_counter)); + +static bool update_max_relay_log_size(sys_var *self, THD *thd, Master_info *mi) +{ + mi->rli.max_relay_log_size= thd->variables.max_relay_log_size; + mi->rli.relay_log.set_max_size(mi->rli.max_relay_log_size); + return false; +} + +static Sys_var_multi_source_ulonglong Sys_max_relay_log_size( + "max_relay_log_size", + "relay log will be rotated automatically when the size exceeds this " + "value. If 0 at startup, it's set to max_binlog_size", + SESSION_VAR(max_relay_log_size), CMD_LINE(REQUIRED_ARG), + MASTER_INFO_VAR(rli.max_relay_log_size), + VALID_RANGE(0, 1024L*1024*1024), DEFAULT(0), BLOCK_SIZE(IO_SIZE), + ON_UPDATE(update_max_relay_log_size)); static Sys_var_charptr Sys_slave_skip_errors( "slave_skip_errors", "Tells the slave thread to continue " @@ -3578,16 +4685,16 @@ static Sys_var_ulonglong Sys_relay_log_space_limit( static Sys_var_uint Sys_sync_relaylog_period( "sync_relay_log", "Synchronously flush relay log to disk after " - "every #th event. Use 0 (default) to disable synchronous flushing", + "every #th event. Use 0 to disable synchronous flushing", GLOBAL_VAR(sync_relaylog_period), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1)); + VALID_RANGE(0, UINT_MAX), DEFAULT(10000), BLOCK_SIZE(1)); static Sys_var_uint Sys_sync_relayloginfo_period( "sync_relay_log_info", "Synchronously flush relay log info " - "to disk after every #th transaction. Use 0 (default) to disable " + "to disk after every #th transaction. Use 0 to disable " "synchronous flushing", GLOBAL_VAR(sync_relayloginfo_period), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1)); + VALID_RANGE(0, UINT_MAX), DEFAULT(10000), BLOCK_SIZE(1)); #endif static Sys_var_uint Sys_sync_binlog_period( @@ -3598,9 +4705,9 @@ static Sys_var_uint Sys_sync_binlog_period( static Sys_var_uint Sys_sync_masterinfo_period( "sync_master_info", "Synchronously flush master info to disk " - "after every #th event. Use 0 (default) to disable synchronous flushing", + "after every #th event. Use 0 to disable synchronous flushing", GLOBAL_VAR(sync_masterinfo_period), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1)); + VALID_RANGE(0, UINT_MAX), DEFAULT(10000), BLOCK_SIZE(1)); #ifdef HAVE_REPLICATION static Sys_var_ulong Sys_slave_trans_retries( @@ -3655,7 +4762,7 @@ static bool check_locale(sys_var *self, THD *thd, set_var *var) mysql_mutex_unlock(&LOCK_error_messages); if (res) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Can't process error message file for locale '%s'", locale->name); return true; @@ -3665,11 +4772,22 @@ static bool check_locale(sys_var *self, THD *thd, set_var *var) return false; } +static bool update_locale(sys_var *self, THD* thd, enum_var_type type) +{ + /* Cache pointer to error messages */ + if (type == OPT_SESSION) + thd->variables.errmsgs= thd->variables.lc_messages->errmsgs->errmsgs; + else + global_system_variables.errmsgs= + global_system_variables.lc_messages->errmsgs->errmsgs; + return false; +} + static Sys_var_struct Sys_lc_messages( "lc_messages", "Set the language used for the error messages", SESSION_VAR(lc_messages), NO_CMD_LINE, my_offsetof(MY_LOCALE, name), DEFAULT(&my_default_lc_messages), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_locale)); + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_locale), ON_UPDATE(update_locale)); static Sys_var_struct Sys_lc_time_names( "lc_time_names", "Set the language used for the month " @@ -3679,10 +4797,391 @@ static Sys_var_struct Sys_lc_time_names( NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_locale)); static Sys_var_tz Sys_time_zone( - "time_zone", "time_zone", + "time_zone", "The current time zone, used to initialize the time " + "zone for a client when it connects. Set to SYSTEM by default, in " + "which the client uses the system time zone value.", SESSION_VAR(time_zone), NO_CMD_LINE, DEFAULT(&default_tz), NO_MUTEX_GUARD, IN_BINLOG); +#ifdef WITH_WSREP +#include "wsrep_var.h" +#include "wsrep_sst.h" +#include "wsrep_binlog.h" + +static Sys_var_charptr Sys_wsrep_provider( + "wsrep_provider", "Path to replication provider library", + PREALLOCATED GLOBAL_VAR(wsrep_provider), CMD_LINE(REQUIRED_ARG), + IN_FS_CHARSET, DEFAULT(WSREP_NONE), + NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_provider_check), ON_UPDATE(wsrep_provider_update)); + +static Sys_var_charptr Sys_wsrep_provider_options( + "wsrep_provider_options", "Semicolon (;) separated list of wsrep " + "options (see wsrep_provider_options documentation).", + PREALLOCATED GLOBAL_VAR(wsrep_provider_options), + CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(""), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_provider_options_check), + ON_UPDATE(wsrep_provider_options_update)); + +static Sys_var_charptr Sys_wsrep_data_home_dir( + "wsrep_data_home_dir", "home directory for wsrep provider", + READ_ONLY GLOBAL_VAR(wsrep_data_home_dir), CMD_LINE(REQUIRED_ARG), + IN_FS_CHARSET, DEFAULT(mysql_real_data_home)); + +static Sys_var_charptr Sys_wsrep_cluster_name( + "wsrep_cluster_name", "Name for the cluster", + PREALLOCATED GLOBAL_VAR(wsrep_cluster_name), CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(WSREP_CLUSTER_NAME), + NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_cluster_name_check), + ON_UPDATE(wsrep_cluster_name_update)); + +static PolyLock_mutex PLock_wsrep_slave_threads(&LOCK_wsrep_slave_threads); +static Sys_var_charptr Sys_wsrep_cluster_address ( + "wsrep_cluster_address", "Address to initially connect to cluster", + PREALLOCATED GLOBAL_VAR(wsrep_cluster_address), + CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(""), + &PLock_wsrep_slave_threads, NOT_IN_BINLOG, + ON_CHECK(wsrep_cluster_address_check), + ON_UPDATE(wsrep_cluster_address_update)); + +static Sys_var_charptr Sys_wsrep_node_name ( + "wsrep_node_name", "Name of this node. This name can be used in " + "wsrep_sst_donor as a preferred donor. Note that multiple nodes " + "in a cluster can have the same name.", + PREALLOCATED GLOBAL_VAR(wsrep_node_name), CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(glob_hostname), NO_MUTEX_GUARD, NOT_IN_BINLOG, + wsrep_node_name_check, wsrep_node_name_update); + +static Sys_var_charptr Sys_wsrep_node_address ( + "wsrep_node_address", "Specifies the node's network address, in " + "the format ip address[:port]. Used in situations where autoguessing " + "is not reliable. As of MariaDB 10.1.8, supports IPv6.", + PREALLOCATED GLOBAL_VAR(wsrep_node_address), CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(""), + NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_node_address_check), + ON_UPDATE(wsrep_node_address_update)); + +static Sys_var_charptr Sys_wsrep_node_incoming_address( + "wsrep_node_incoming_address", "Client connection address", + PREALLOCATED GLOBAL_VAR(wsrep_node_incoming_address),CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(WSREP_NODE_INCOMING_AUTO)); + +static Sys_var_ulong Sys_wsrep_slave_threads( + "wsrep_slave_threads", "Number of slave appliers to launch", + GLOBAL_VAR(wsrep_slave_threads), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1, 512), DEFAULT(1), BLOCK_SIZE(1), + &PLock_wsrep_slave_threads, NOT_IN_BINLOG, + ON_CHECK(NULL), + ON_UPDATE(wsrep_slave_threads_update)); + +static Sys_var_charptr Sys_wsrep_dbug_option( + "wsrep_dbug_option", "DBUG options to provider library", + GLOBAL_VAR(wsrep_dbug_option),CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT("")); + +static Sys_var_mybool Sys_wsrep_debug( + "wsrep_debug", "To enable debug level logging", + GLOBAL_VAR(wsrep_debug), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_mybool Sys_wsrep_convert_LOCK_to_trx( + "wsrep_convert_LOCK_to_trx", "To convert locking sessions " + "into transactions", + GLOBAL_VAR(wsrep_convert_LOCK_to_trx), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_ulong Sys_wsrep_retry_autocommit( + "wsrep_retry_autocommit", "Max number of times to retry " + "a failed autocommit statement", + SESSION_VAR(wsrep_retry_autocommit), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, 10000), DEFAULT(1), BLOCK_SIZE(1)); + +static bool update_wsrep_auto_increment_control (sys_var *self, THD *thd, enum_var_type type) +{ + if (wsrep_auto_increment_control) + { + /* + The variables that control auto increment shall be calculated + automaticaly based on the size of the cluster. This usually done + within the wsrep_view_handler_cb callback. However, if the user + manually sets the value of wsrep_auto_increment_control to 'ON', + then we should to re-calculate these variables again (because + these values may be required before wsrep_view_handler_cb will + be re-invoked, which is rarely invoked if the cluster stays in + the stable state): + */ + global_system_variables.auto_increment_increment= + wsrep_cluster_size ? wsrep_cluster_size : 1; + global_system_variables.auto_increment_offset= + wsrep_local_index >= 0 ? wsrep_local_index + 1 : 1; + thd->variables.auto_increment_increment= + global_system_variables.auto_increment_increment; + thd->variables.auto_increment_offset= + global_system_variables.auto_increment_offset; + } + else + { + /* + We must restore the last values of the variables that + are explicitly specified by the user: + */ + global_system_variables.auto_increment_increment= + global_system_variables.saved_auto_increment_increment; + global_system_variables.auto_increment_offset= + global_system_variables.saved_auto_increment_offset; + thd->variables.auto_increment_increment= + thd->variables.saved_auto_increment_increment; + thd->variables.auto_increment_offset= + thd->variables.saved_auto_increment_offset; + } + return false; +} + +static Sys_var_mybool Sys_wsrep_auto_increment_control( + "wsrep_auto_increment_control", "To automatically control the " + "assignment of autoincrement variables", + GLOBAL_VAR(wsrep_auto_increment_control), + CMD_LINE(OPT_ARG), DEFAULT(TRUE), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(update_wsrep_auto_increment_control)); + +static Sys_var_mybool Sys_wsrep_drupal_282555_workaround( + "wsrep_drupal_282555_workaround", "Enable a workaround to handle the " + "cases where inserting a DEFAULT value into an auto-increment column " + "could fail with duplicate key error", + GLOBAL_VAR(wsrep_drupal_282555_workaround), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_charptr sys_wsrep_sst_method( + "wsrep_sst_method", "State snapshot transfer method", + GLOBAL_VAR(wsrep_sst_method),CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(WSREP_SST_DEFAULT), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_sst_method_check), + ON_UPDATE(wsrep_sst_method_update)); + +static Sys_var_charptr Sys_wsrep_sst_receive_address( + "wsrep_sst_receive_address", "Address where node is waiting for " + "SST contact", + GLOBAL_VAR(wsrep_sst_receive_address),CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(WSREP_SST_ADDRESS_AUTO), NO_MUTEX_GUARD, + NOT_IN_BINLOG, + ON_CHECK(wsrep_sst_receive_address_check), + ON_UPDATE(wsrep_sst_receive_address_update)); + +static Sys_var_charptr Sys_wsrep_sst_auth( + "wsrep_sst_auth", "Authentication for SST connection", + PREALLOCATED GLOBAL_VAR(wsrep_sst_auth), CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(NULL), NO_MUTEX_GUARD, + NOT_IN_BINLOG, + ON_CHECK(wsrep_sst_auth_check), + ON_UPDATE(wsrep_sst_auth_update)); + +static Sys_var_charptr Sys_wsrep_sst_donor( + "wsrep_sst_donor", "preferred donor node for the SST", + GLOBAL_VAR(wsrep_sst_donor),CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(""), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_sst_donor_check), + ON_UPDATE(wsrep_sst_donor_update)); + +static Sys_var_mybool Sys_wsrep_sst_donor_rejects_queries( + "wsrep_sst_donor_rejects_queries", "Reject client queries " + "when donating state snapshot transfer", + GLOBAL_VAR(wsrep_sst_donor_rejects_queries), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_mybool Sys_wsrep_on ( + "wsrep_on", "To enable wsrep replication ", + SESSION_VAR(wsrep_on), + CMD_LINE(OPT_ARG), DEFAULT(FALSE), + NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_on_check), + ON_UPDATE(wsrep_on_update)); + +static Sys_var_charptr Sys_wsrep_start_position ( + "wsrep_start_position", "global transaction position to start from ", + PREALLOCATED GLOBAL_VAR(wsrep_start_position), + CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT(WSREP_START_POSITION_ZERO), + NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_start_position_check), + ON_UPDATE(wsrep_start_position_update)); + +static Sys_var_ulong Sys_wsrep_max_ws_size ( + "wsrep_max_ws_size", "Max write set size (bytes)", + GLOBAL_VAR(wsrep_max_ws_size), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1024, WSREP_MAX_WS_SIZE), DEFAULT(WSREP_MAX_WS_SIZE), + BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_max_ws_size_check), ON_UPDATE(wsrep_max_ws_size_update)); + +static Sys_var_ulong Sys_wsrep_max_ws_rows ( + "wsrep_max_ws_rows", "Max number of rows in write set", + GLOBAL_VAR(wsrep_max_ws_rows), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, 1048576), DEFAULT(0), BLOCK_SIZE(1)); + +static Sys_var_charptr Sys_wsrep_notify_cmd( + "wsrep_notify_cmd", "", + GLOBAL_VAR(wsrep_notify_cmd),CMD_LINE(REQUIRED_ARG), + IN_SYSTEM_CHARSET, DEFAULT("")); + +static Sys_var_mybool Sys_wsrep_certify_nonPK( + "wsrep_certify_nonPK", "Certify tables with no primary key", + GLOBAL_VAR(wsrep_certify_nonPK), + CMD_LINE(OPT_ARG), DEFAULT(TRUE)); + +static const char *wsrep_certification_rules_names[]= { "strict", "optimized", NullS }; +static Sys_var_enum Sys_wsrep_certification_rules( + "wsrep_certification_rules", + "Certification rules to use in the cluster. Possible values are: " + "\"strict\": stricter rules that could result in more certification " + "failures. " + "\"optimized\": relaxed rules that allow more concurrency and " + "cause less certification failures.", + GLOBAL_VAR(wsrep_certification_rules), CMD_LINE(REQUIRED_ARG), + wsrep_certification_rules_names, DEFAULT(WSREP_CERTIFICATION_RULES_STRICT), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(0)); + +static Sys_var_mybool Sys_wsrep_causal_reads( + "wsrep_causal_reads", "Setting this variable is equivalent " + "to setting wsrep_sync_wait READ flag", + SESSION_VAR(wsrep_causal_reads), + CMD_LINE(OPT_ARG, OPT_WSREP_CAUSAL_READS), DEFAULT(FALSE), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(wsrep_causal_reads_update), + DEPRECATED("'@@wsrep_sync_wait=1'")); + +static Sys_var_uint Sys_wsrep_sync_wait( + "wsrep_sync_wait", "Ensure \"synchronous\" read view before executing " + "an operation of the type specified by bitmask: 1 - READ(includes " + "SELECT, SHOW and BEGIN/START TRANSACTION); 2 - UPDATE and DELETE; 4 - " + "INSERT and REPLACE", + SESSION_VAR(wsrep_sync_wait), CMD_LINE(OPT_ARG, OPT_WSREP_SYNC_WAIT), + VALID_RANGE(WSREP_SYNC_WAIT_NONE, WSREP_SYNC_WAIT_MAX), + DEFAULT(WSREP_SYNC_WAIT_NONE), BLOCK_SIZE(1), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(wsrep_sync_wait_update)); + +static const char *wsrep_OSU_method_names[]= { "TOI", "RSU", NullS }; +static Sys_var_enum Sys_wsrep_OSU_method( + "wsrep_OSU_method", "Method for Online Schema Upgrade", + SESSION_VAR(wsrep_OSU_method), CMD_LINE(OPT_ARG), + wsrep_OSU_method_names, DEFAULT(WSREP_OSU_TOI), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(0)); + +static PolyLock_mutex PLock_wsrep_desync(&LOCK_wsrep_desync); +static Sys_var_mybool Sys_wsrep_desync ( + "wsrep_desync", "To desynchronize the node from the cluster", + GLOBAL_VAR(wsrep_desync), + CMD_LINE(OPT_ARG), DEFAULT(FALSE), + &PLock_wsrep_desync, NOT_IN_BINLOG, + ON_CHECK(wsrep_desync_check), + ON_UPDATE(wsrep_desync_update)); + +static const char *wsrep_reject_queries_names[]= { "NONE", "ALL", "ALL_KILL", NullS }; +static Sys_var_enum Sys_wsrep_reject_queries( + "wsrep_reject_queries", "Variable to set to reject queries", + GLOBAL_VAR(wsrep_reject_queries), CMD_LINE(OPT_ARG), + wsrep_reject_queries_names, DEFAULT(WSREP_REJECT_NONE), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(wsrep_reject_queries_update)); + +static const char *wsrep_binlog_format_names[]= + {"MIXED", "STATEMENT", "ROW", "NONE", NullS}; +static Sys_var_enum Sys_wsrep_forced_binlog_format( + "wsrep_forced_binlog_format", "binlog format to take effect over user's choice", + GLOBAL_VAR(wsrep_forced_binlog_format), CMD_LINE(REQUIRED_ARG), + wsrep_binlog_format_names, DEFAULT(BINLOG_FORMAT_UNSPEC)); + +static Sys_var_mybool Sys_wsrep_recover_datadir( + "wsrep_recover", "Recover database state after crash and exit", + READ_ONLY GLOBAL_VAR(wsrep_recovery), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_mybool Sys_wsrep_replicate_myisam( + "wsrep_replicate_myisam", "To enable myisam replication", + GLOBAL_VAR(wsrep_replicate_myisam), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_mybool Sys_wsrep_log_conflicts( + "wsrep_log_conflicts", "To log multi-master conflicts", + GLOBAL_VAR(wsrep_log_conflicts), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_ulong Sys_wsrep_mysql_replication_bundle( + "wsrep_mysql_replication_bundle", "mysql replication group commit ", + GLOBAL_VAR(wsrep_mysql_replication_bundle), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, 1000), DEFAULT(0), BLOCK_SIZE(1)); + +static Sys_var_mybool Sys_wsrep_load_data_splitting( + "wsrep_load_data_splitting", "To commit LOAD DATA " + "transaction after every 10K rows inserted", + GLOBAL_VAR(wsrep_load_data_splitting), + CMD_LINE(OPT_ARG), DEFAULT(TRUE)); + +static Sys_var_mybool Sys_wsrep_slave_FK_checks( + "wsrep_slave_FK_checks", "Should slave thread do " + "foreign key constraint checks", + GLOBAL_VAR(wsrep_slave_FK_checks), + CMD_LINE(OPT_ARG), DEFAULT(TRUE)); + +static Sys_var_mybool Sys_wsrep_slave_UK_checks( + "wsrep_slave_UK_checks", "Should slave thread do " + "secondary index uniqueness checks", + GLOBAL_VAR(wsrep_slave_UK_checks), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_mybool Sys_wsrep_restart_slave( + "wsrep_restart_slave", "Should MariaDB slave be restarted automatically, when node joins back to cluster", + GLOBAL_VAR(wsrep_restart_slave), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_mybool Sys_wsrep_dirty_reads( + "wsrep_dirty_reads", + "Allow reads even when the node is not in the primary component.", + SESSION_VAR(wsrep_dirty_reads), CMD_LINE(OPT_ARG), + DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG); + +static Sys_var_uint Sys_wsrep_gtid_domain_id( + "wsrep_gtid_domain_id", "When wsrep_gtid_mode is set, this value is " + "used as gtid_domain_id for galera transactions and also copied to the " + "joiner nodes during state transfer. It is ignored, otherwise.", + GLOBAL_VAR(wsrep_gtid_domain_id), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, UINT_MAX32), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD, + NOT_IN_BINLOG); + +static Sys_var_mybool Sys_wsrep_gtid_mode( + "wsrep_gtid_mode", "Automatically update the (joiner) node's " + "wsrep_gtid_domain_id value with that of donor's (received during " + "state transfer) and use it in place of gtid_domain_id for all galera " + "transactions. When OFF (default), wsrep_gtid_domain_id is simply " + "ignored (backward compatibility).", + GLOBAL_VAR(wsrep_gtid_mode), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static char *wsrep_patch_version_ptr; +static Sys_var_charptr Sys_wsrep_patch_version( + "wsrep_patch_version", "Wsrep patch version, for example wsrep_25.10.", + READ_ONLY GLOBAL_VAR(wsrep_patch_version_ptr), CMD_LINE_HELP_ONLY, + IN_SYSTEM_CHARSET, DEFAULT(WSREP_PATCH_VERSION)); + +#endif /* WITH_WSREP */ + +static bool fix_host_cache_size(sys_var *, THD *, enum_var_type) +{ + hostname_cache_resize((uint) host_cache_size); + return false; +} + +static Sys_var_ulong Sys_host_cache_size( + "host_cache_size", + "How many host names should be cached to avoid resolving.", + AUTO_SET GLOBAL_VAR(host_cache_size), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 65536), + DEFAULT(HOST_CACHE_SIZE), + BLOCK_SIZE(1), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL), + ON_UPDATE(fix_host_cache_size)); + static Sys_var_charptr Sys_ignore_db_dirs( "ignore_db_dirs", "Specifies a directory to add to the ignore list when collecting " @@ -3703,9 +5202,8 @@ export const char *plugin_maturity_names[]= { "unknown", "experimental", "alpha", "beta", "gamma", "stable", 0 }; static Sys_var_enum Sys_plugin_maturity( "plugin_maturity", - "The lowest desirable plugin maturity " - "(unknown, experimental, alpha, beta, gamma, or stable). " - "Plugins less mature than that will not be installed or loaded.", + "The lowest desirable plugin maturity. " + "Plugins less mature than that will not be installed or loaded", READ_ONLY GLOBAL_VAR(plugin_maturity), CMD_LINE(REQUIRED_ARG), plugin_maturity_names, DEFAULT(MariaDB_PLUGIN_MATURITY_UNKNOWN)); @@ -3733,14 +5231,6 @@ static Sys_var_ulong Sys_deadlock_timeout_depth_long( SESSION_VAR(wt_timeout_long), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, UINT_MAX), DEFAULT(50000000), BLOCK_SIZE(1)); -#ifndef DBUG_OFF -static Sys_var_ulong Sys_debug_crc_break( - "debug_crc_break", - "Call my_debug_put_break_here() if crc matches this number (for debug)", - GLOBAL_VAR(my_crc_dbug_check), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, ULONG_MAX), DEFAULT(0), BLOCK_SIZE(1)); -#endif - static Sys_var_uint Sys_extra_port( "extra_port", "Extra port number to use for tcp connections in a " @@ -3756,7 +5246,7 @@ static Sys_var_ulong Sys_extra_max_connections( #ifdef SAFE_MUTEX static Sys_var_mybool Sys_mutex_deadlock_detector( - "mutex_deadlock_detector", "Enable checking of wrong mutex usage", + "debug_mutex_deadlock_detector", "Enable checking of wrong mutex usage", READ_ONLY GLOBAL_VAR(safe_mutex_deadlock_detector), CMD_LINE(OPT_ARG), DEFAULT(TRUE)); #endif @@ -3776,13 +5266,48 @@ static const char *log_slow_filter_names[]= }; static Sys_var_set Sys_log_slow_filter( "log_slow_filter", - "Log only certain types of queries. Multiple " - "flags can be specified, separated by commas. Valid values are admin, " - "slave, filesort, filesort_on_disk, full_join, full_scan, query_cache, " - "query_cache_miss, tmp_table, tmp_table_on_disk", + "Log only certain types of queries", SESSION_VAR(log_slow_filter), CMD_LINE(REQUIRED_ARG), log_slow_filter_names, - DEFAULT(MAX_SET(array_elements(log_slow_filter_names)-1))); + DEFAULT(my_set_bits(array_elements(log_slow_filter_names)-1))); + +static const char *default_regex_flags_names[]= +{ + "DOTALL", // (?s) . matches anything including NL + "DUPNAMES", // (?J) Allow duplicate names for subpatterns + "EXTENDED", // (?x) Ignore white space and # comments + "EXTRA", // (?X) extra features (e.g. error on unknown escape character) + "MULTILINE", // (?m) ^ and $ match newlines within data + "UNGREEDY", // (?U) Invert greediness of quantifiers + 0 +}; +static const int default_regex_flags_to_pcre[]= +{ + PCRE_DOTALL, + PCRE_DUPNAMES, + PCRE_EXTENDED, + PCRE_EXTRA, + PCRE_MULTILINE, + PCRE_UNGREEDY, + 0 +}; +int default_regex_flags_pcre(const THD *thd) +{ + ulonglong src= thd->variables.default_regex_flags; + int i, res; + for (i= res= 0; default_regex_flags_to_pcre[i]; i++) + { + if (src & (1 << i)) + res|= default_regex_flags_to_pcre[i]; + } + return res; +} +static Sys_var_set Sys_default_regex_flags( + "default_regex_flags", + "Default flags for the regex library", + SESSION_VAR(default_regex_flags), CMD_LINE(REQUIRED_ARG), + default_regex_flags_names, + DEFAULT(0)); static Sys_var_ulong Sys_log_slow_rate_limit( "log_slow_rate_limit", @@ -3792,11 +5317,11 @@ static Sys_var_ulong Sys_log_slow_rate_limit( SESSION_VAR(log_slow_rate_limit), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1, UINT_MAX), DEFAULT(1), BLOCK_SIZE(1)); -static const char *log_slow_verbosity_names[]= { "innodb", "query_plan", 0 }; +static const char *log_slow_verbosity_names[]= { "innodb", "query_plan", + "explain", 0 }; static Sys_var_set Sys_log_slow_verbosity( "log_slow_verbosity", - "log-slow-verbosity=[value[,value ...]] where value is one of " - "'innodb', 'query_plan'", + "Verbosity level for the slow log", SESSION_VAR(log_slow_verbosity), CMD_LINE(REQUIRED_ARG), log_slow_verbosity_names, DEFAULT(LOG_SLOW_VERBOSITY_INIT)); @@ -3838,7 +5363,7 @@ static Sys_var_mybool Sys_binlog_annotate_row_events( #ifdef HAVE_REPLICATION static Sys_var_mybool Sys_replicate_annotate_row_events( "replicate_annotate_row_events", - "Tells the slave to write annotate rows events recieved from the master " + "Tells the slave to write annotate rows events received from the master " "to its own binary log. Ignored if log_slave_updates is not set", READ_ONLY GLOBAL_VAR(opt_replicate_annotate_row_events), CMD_LINE(OPT_ARG), DEFAULT(0)); @@ -3858,6 +5383,31 @@ static Sys_var_ulong Sys_progress_report_time( SESSION_VAR(progress_report_time), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, UINT_MAX), DEFAULT(5), BLOCK_SIZE(1)); +const char *use_stat_tables_modes[] = + {"NEVER", "COMPLEMENTARY", "PREFERABLY", 0}; +static Sys_var_enum Sys_optimizer_use_stat_tables( + "use_stat_tables", + "Specifies how to use system statistics tables", + SESSION_VAR(use_stat_tables), CMD_LINE(REQUIRED_ARG), + use_stat_tables_modes, DEFAULT(0)); + +static Sys_var_ulong Sys_histogram_size( + "histogram_size", + "Number of bytes used for a histogram. " + "If set to 0, no histograms are created by ANALYZE.", + SESSION_VAR(histogram_size), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, 255), DEFAULT(0), BLOCK_SIZE(1)); + +extern const char *histogram_types[]; +static Sys_var_enum Sys_histogram_type( + "histogram_type", + "Specifies type of the histograms created by ANALYZE. " + "Possible values are: " + "SINGLE_PREC_HB - single precision height-balanced, " + "DOUBLE_PREC_HB - double precision height-balanced.", + SESSION_VAR(histogram_type), CMD_LINE(REQUIRED_ARG), + histogram_types, DEFAULT(0)); + static Sys_var_mybool Sys_no_thread_alarm( "debug_no_thread_alarm", "Disable system thread alarm calls. Disabling it may be useful " @@ -3874,7 +5424,7 @@ static Sys_var_mybool Sys_query_cache_strip_comments( static ulonglong in_transaction(THD *thd) { - return test(thd->in_active_multi_stmt_transaction()); + return MY_TEST(thd->in_active_multi_stmt_transaction()); } static Sys_var_session_special Sys_in_transaction( "in_transaction", "Whether there is an active transaction", @@ -3898,6 +5448,39 @@ static Sys_var_harows Sys_expensive_subquery_limit( SESSION_VAR(expensive_subquery_limit), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, HA_POS_ERROR), DEFAULT(100), BLOCK_SIZE(1)); +static Sys_var_mybool Sys_encrypt_tmp_disk_tables( + "encrypt_tmp_disk_tables", + "Encrypt temporary on-disk tables (created as part of query execution)", + GLOBAL_VAR(encrypt_tmp_disk_tables), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_mybool Sys_encrypt_tmp_files( + "encrypt_tmp_files", + "Encrypt temporary files (created for filesort, binary log cache, etc)", + READ_ONLY GLOBAL_VAR(encrypt_tmp_files), + CMD_LINE(OPT_ARG), DEFAULT(FALSE)); + +static Sys_var_mybool Sys_binlog_encryption( + "encrypt_binlog", "Encrypt binary logs (including relay logs)", + READ_ONLY GLOBAL_VAR(encrypt_binlog), CMD_LINE(OPT_ARG), + DEFAULT(FALSE)); + +static const char *binlog_row_image_names[]= {"MINIMAL", "NOBLOB", "FULL", NullS}; +static Sys_var_enum Sys_binlog_row_image( + "binlog_row_image", + "Controls whether rows should be logged in 'FULL', 'NOBLOB' or " + "'MINIMAL' formats. 'FULL', means that all columns in the before " + "and after image are logged. 'NOBLOB', means that mysqld avoids logging " + "blob columns whenever possible (eg, blob column was not changed or " + "is not part of primary key). 'MINIMAL', means that a PK equivalent (PK " + "columns or full row if there is no PK in the table) is logged in the " + "before image, and only changed columns are logged in the after image. " + "(Default: FULL).", + SESSION_VAR(binlog_row_image), CMD_LINE(REQUIRED_ARG), + binlog_row_image_names, DEFAULT(BINLOG_ROW_IMAGE_FULL), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL), + ON_UPDATE(NULL)); + static bool check_pseudo_slave_mode(sys_var *self, THD *thd, set_var *var) { longlong previous_val= thd->variables.pseudo_slave_mode; @@ -3915,12 +5498,14 @@ static bool check_pseudo_slave_mode(sys_var *self, THD *thd, set_var *var) #ifndef EMBEDDED_LIBRARY delete thd->rli_fake; thd->rli_fake= NULL; + delete thd->rgi_fake; + thd->rgi_fake= NULL; #endif } else if (previous_val && val) goto ineffective; else if (!previous_val && val) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_VALUE_FOR_VAR, "'pseudo_slave_mode' is already ON."); } @@ -3929,7 +5514,7 @@ static bool check_pseudo_slave_mode(sys_var *self, THD *thd, set_var *var) if (!previous_val && !val) goto ineffective; else if (previous_val && !val) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_VALUE_FOR_VAR, "Slave applier execution mode not active, " "statement ineffective."); @@ -3937,7 +5522,7 @@ static bool check_pseudo_slave_mode(sys_var *self, THD *thd, set_var *var) goto end; ineffective: - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_VALUE_FOR_VAR, "'pseudo_slave_mode' change was ineffective."); @@ -3954,3 +5539,33 @@ static Sys_var_mybool Sys_pseudo_slave_mode( SESSION_ONLY(pseudo_slave_mode), NO_CMD_LINE, DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_pseudo_slave_mode)); +static Sys_var_mybool Sys_mysql56_temporal_format( + "mysql56_temporal_format", + "Use MySQL-5.6 (instead of MariaDB-5.3) format for TIME, DATETIME, TIMESTAMP columns.", + GLOBAL_VAR(opt_mysql56_temporal_format), + CMD_LINE(OPT_ARG), DEFAULT(TRUE), NO_MUTEX_GUARD, NOT_IN_BINLOG); + +static Sys_var_mybool Sys_strict_password_validation( + "strict_password_validation", + "When password validation plugins are enabled, reject passwords " + "that cannot be validated (passwords specified as a hash)", + GLOBAL_VAR(strict_password_validation), + CMD_LINE(OPT_ARG), DEFAULT(TRUE), NO_MUTEX_GUARD, NOT_IN_BINLOG); + +#ifdef HAVE_MMAP +static Sys_var_ulong Sys_log_tc_size( + "log_tc_size", + "Size of transaction coordinator log.", + READ_ONLY GLOBAL_VAR(opt_tc_log_size), + CMD_LINE(REQUIRED_ARG), + VALID_RANGE(my_getpagesize() * 3, ULONG_MAX), + DEFAULT(my_getpagesize() * 6), + BLOCK_SIZE(my_getpagesize())); +#endif + +static Sys_var_ulonglong Sys_max_thread_mem( + "max_session_mem_used", "Amount of memory a single user session " + "is allowed to allocate. This limits the value of the " + "session variable MEM_USED", SESSION_VAR(max_mem_used), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(8192, ULONGLONG_MAX), + DEFAULT(LONGLONG_MAX), BLOCK_SIZE(1)); diff --git a/sql/sys_vars.h b/sql/sys_vars.ic index dbe27ab107e..cbc10c85351 100644 --- a/sql/sys_vars.h +++ b/sql/sys_vars.ic @@ -29,6 +29,8 @@ #include "keycaches.h" #include "strfunc.h" #include "tztime.h" // my_tz_find, my_tz_SYSTEM, struct Time_zone +#include "rpl_mi.h" // For Multi-Source Replication +#include "debug_sync.h" /* a set of mostly trivial (as in f(X)=X) defines below to make system variable @@ -40,7 +42,8 @@ #define GLOBAL_VAR(X) sys_var::GLOBAL, (((char*)&(X))-(char*)&global_system_variables), sizeof(X) #define SESSION_VAR(X) sys_var::SESSION, offsetof(SV, X), sizeof(((SV *)0)->X) #define SESSION_ONLY(X) sys_var::ONLY_SESSION, offsetof(SV, X), sizeof(((SV *)0)->X) -#define NO_CMD_LINE CMD_LINE(NO_ARG, -1) +#define NO_CMD_LINE CMD_LINE(NO_ARG, sys_var::NO_GETOPT) +#define CMD_LINE_HELP_ONLY CMD_LINE(NO_ARG, sys_var::GETOPT_ONLY_HELP) /* the define below means that there's no *second* mutex guard, LOCK_global_system_variables always guards all system variables @@ -52,9 +55,12 @@ #define ON_CHECK(X) X #define ON_UPDATE(X) X #define READ_ONLY sys_var::READONLY+ +#define AUTO_SET sys_var::AUTO_SET+ // this means that Sys_var_charptr initial value was malloc()ed #define PREALLOCATED sys_var::ALLOCATED+ #define PARSED_EARLY sys_var::PARSE_EARLY+ +#define NO_SET_STMT sys_var::NO_SET_STATEMENT+ + /* Sys_var_bit meaning is reversed, like in @@foreign_key_checks <-> OPTION_NO_FOREIGN_KEY_CHECKS @@ -81,8 +87,8 @@ #define SYSVAR_ASSERT(X) \ while(!(X)) \ { \ - fprintf(stderr, "Sysvar '%s' failed '%s'\n", name_arg, #X); \ - DBUG_ABORT(); \ + fprintf(stderr, "Sysvar '%s' failed '%s'\n", name_arg, #X); \ + DBUG_ABORT(); \ exit(255); \ } @@ -132,7 +138,7 @@ public: getopt.arg_type, SHOWT, def_val, lock, binlog_status_arg, on_check_func, on_update_func, substitute) { - option.var_type= ARGT; + option.var_type|= ARGT; option.min_value= min_val; option.max_value= max_val; option.block_size= block_size; @@ -205,8 +211,6 @@ public: global_var(T)= static_cast<T>(var->save_result.ulonglong_value); return false; } - bool check_update_type(Item_result type) - { return type != INT_RESULT; } void session_save_default(THD *thd, set_var *var) { var->save_result.ulonglong_value= (ulonglong)*(T*)global_value_ptr(thd, 0); } void global_save_default(THD *thd, set_var *var) @@ -217,6 +221,7 @@ public: return scope() == SESSION ? (T*)(((uchar*)&max_system_variables) + offset) : 0; } + uchar *default_value_ptr(THD *thd) { return (uchar*) &option.def_value; } }; typedef Sys_var_integer<int, GET_INT, SHOW_SINT> Sys_var_int; @@ -224,6 +229,33 @@ typedef Sys_var_integer<uint, GET_UINT, SHOW_UINT> Sys_var_uint; typedef Sys_var_integer<ulong, GET_ULONG, SHOW_ULONG> Sys_var_ulong; typedef Sys_var_integer<ha_rows, GET_HA_ROWS, SHOW_HA_ROWS> Sys_var_harows; typedef Sys_var_integer<ulonglong, GET_ULL, SHOW_ULONGLONG> Sys_var_ulonglong; +typedef Sys_var_integer<long, GET_LONG, SHOW_SLONG> Sys_var_long; + + +template<> uchar *Sys_var_int::default_value_ptr(THD *thd) +{ + thd->sys_var_tmp.int_value= option.def_value; + return (uchar*) &thd->sys_var_tmp.int_value; +} + +template<> uchar *Sys_var_uint::default_value_ptr(THD *thd) +{ + thd->sys_var_tmp.uint_value= option.def_value; + return (uchar*) &thd->sys_var_tmp.uint_value; +} + +template<> uchar *Sys_var_long::default_value_ptr(THD *thd) +{ + thd->sys_var_tmp.long_value= option.def_value; + return (uchar*) &thd->sys_var_tmp.long_value; +} + +template<> uchar *Sys_var_ulong::default_value_ptr(THD *thd) +{ + thd->sys_var_tmp.ulong_value= option.def_value; + return (uchar*) &thd->sys_var_tmp.ulong_value; +} + /** Helper class for variables that take values from a TYPELIB @@ -279,8 +311,6 @@ public: return false; } - bool check_update_type(Item_result type) - { return type != INT_RESULT && type != STRING_RESULT; } }; /** @@ -313,7 +343,7 @@ public: binlog_status_arg, on_check_func, on_update_func, substitute) { - option.var_type= GET_ENUM; + option.var_type|= GET_ENUM; global_var(ulong)= def_val; SYSVAR_ASSERT(def_val < typelib.count); SYSVAR_ASSERT(size == sizeof(ulong)); @@ -332,10 +362,14 @@ public: { var->save_result.ulonglong_value= global_var(ulong); } void global_save_default(THD *thd, set_var *var) { var->save_result.ulonglong_value= option.def_value; } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) - { return (uchar*)typelib.type_names[session_var(thd, ulong)]; } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) - { return (uchar*)typelib.type_names[global_var(ulong)]; } + uchar *valptr(THD *thd, ulong val) + { return (uchar*)typelib.type_names[val]; } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, session_var(thd, ulong)); } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, global_var(ulong)); } + uchar *default_value_ptr(THD *thd) + { return valptr(thd, option.def_value); } }; /** @@ -360,10 +394,10 @@ public: binlog_status_arg, on_check_func, on_update_func, substitute) { - option.var_type= GET_BOOL; + option.var_type|= GET_BOOL; global_var(my_bool)= def_val; SYSVAR_ASSERT(def_val < 2); - SYSVAR_ASSERT(getopt.arg_type == OPT_ARG || getopt.id == -1); + SYSVAR_ASSERT(getopt.arg_type == OPT_ARG || getopt.id < 0); SYSVAR_ASSERT(size == sizeof(my_bool)); } bool session_update(THD *thd, set_var *var) @@ -380,6 +414,11 @@ public: { var->save_result.ulonglong_value= (ulonglong)*(my_bool *)global_value_ptr(thd, 0); } void global_save_default(THD *thd, set_var *var) { var->save_result.ulonglong_value= option.def_value; } + uchar *default_value_ptr(THD *thd) + { + thd->sys_var_tmp.my_bool_value= option.def_value; + return (uchar*) &thd->sys_var_tmp.my_bool_value; + } }; /** @@ -421,7 +460,7 @@ public: otherwise (GET_STR) you'll never know whether to free it or not. (think of an exit because of an error right after my_getopt) */ - option.var_type= (flags & ALLOCATED) ? GET_STR_ALLOC : GET_STR; + option.var_type|= (flags & ALLOCATED) ? GET_STR_ALLOC : GET_STR; global_var(const char*)= def_val; SYSVAR_ASSERT(scope() == GLOBAL); SYSVAR_ASSERT(size == sizeof(char *)); @@ -494,8 +533,6 @@ public: var->save_result.string_value.str= ptr; var->save_result.string_value.length= ptr ? strlen(ptr) : 0; } - bool check_update_type(Item_result type) - { return type != STRING_RESULT; } }; @@ -505,12 +542,12 @@ public: Sys_var_proxy_user(const char *name_arg, const char *comment, enum charset_enum is_os_charset_arg) : sys_var(&all_sys_vars, name_arg, comment, - sys_var::READONLY+sys_var::ONLY_SESSION, 0, -1, + sys_var::READONLY+sys_var::ONLY_SESSION, 0, NO_GETOPT, NO_ARG, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG, NULL, NULL, NULL) { is_os_charset= is_os_charset_arg == IN_FS_CHARSET; - option.var_type= GET_STR; + option.var_type|= GET_STR; } bool do_check(THD *thd, set_var *var) { @@ -531,10 +568,8 @@ public: { DBUG_ASSERT(FALSE); } void global_save_default(THD *thd, set_var *var) { DBUG_ASSERT(FALSE); } - bool check_update_type(Item_result type) - { return true; } protected: - virtual uchar *session_value_ptr(THD *thd, LEX_STRING *base) + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) { return thd->security_ctx->proxy_user[0] ? (uchar *) &(thd->security_ctx->proxy_user[0]) : NULL; @@ -550,12 +585,13 @@ public: {} protected: - virtual uchar *session_value_ptr(THD *thd, LEX_STRING *base) + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) { return (uchar*)thd->security_ctx->external_user; } }; +class Master_info; class Sys_var_rpl_filter: public sys_var { private: @@ -563,18 +599,17 @@ private: public: Sys_var_rpl_filter(const char *name, int getopt_id, const char *comment) - : sys_var(&all_sys_vars, name, comment, sys_var::GLOBAL, 0, -1, + : sys_var(&all_sys_vars, name, comment, sys_var::GLOBAL, 0, NO_GETOPT, NO_ARG, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG, NULL, NULL, NULL), opt_id(getopt_id) { - option.var_type= GET_STR; + option.var_type|= GET_STR | GET_ASK_ADDR; } - bool check_update_type(Item_result type) - { return type != STRING_RESULT; } - - bool do_check(THD *thd, set_var *var); - + bool do_check(THD *thd, set_var *var) + { + return Sys_var_charptr::do_string_check(thd, var, charset(thd)); + } void session_save_default(THD *thd, set_var *var) { DBUG_ASSERT(FALSE); } @@ -590,10 +625,8 @@ public: bool global_update(THD *thd, set_var *var); protected: - uchar *global_value_ptr(THD *thd, LEX_STRING *base); - bool set_filter_value(const char *value); - void lock(void); - void unlock(void); + uchar *global_value_ptr(THD *thd, const LEX_STRING *base); + bool set_filter_value(const char *value, Master_info *mi); }; /** @@ -637,9 +670,90 @@ public: } }; + +/* + A LEX_STRING stored only in thd->variables + Only to be used for small buffers +*/ + +class Sys_var_session_lexstring: public sys_var +{ + size_t max_length; +public: + Sys_var_session_lexstring(const char *name_arg, + const char *comment, int flag_args, + ptrdiff_t off, size_t size, CMD_LINE getopt, + enum charset_enum is_os_charset_arg, + const char *def_val, size_t max_length_arg, + on_check_function on_check_func=0, + on_update_function on_update_func=0) + : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id, + getopt.arg_type, SHOW_CHAR, (intptr)def_val, + 0, VARIABLE_NOT_IN_BINLOG, on_check_func, on_update_func, + 0),max_length(max_length_arg) + { + option.var_type|= GET_STR; + SYSVAR_ASSERT(scope() == ONLY_SESSION) + *const_cast<SHOW_TYPE*>(&show_val_type)= SHOW_LEX_STRING; + } + bool do_check(THD *thd, set_var *var) + { + char buff[STRING_BUFFER_USUAL_SIZE]; + String str(buff, sizeof(buff), system_charset_info), *res; + + if (!(res=var->value->val_str(&str))) + { + var->save_result.string_value.str= 0; /* NULL */ + var->save_result.string_value.length= 0; + } + else + { + if (res->length() > max_length) + { + my_error(ER_WRONG_STRING_LENGTH, MYF(0), + res->ptr(), name.str, (int) max_length); + return true; + } + var->save_result.string_value.str= thd->strmake(res->ptr(), + res->length()); + var->save_result.string_value.length= res->length(); + } + return false; + } + bool session_update(THD *thd, set_var *var) + { + LEX_STRING *tmp= &session_var(thd, LEX_STRING); + tmp->length= var->save_result.string_value.length; + /* Store as \0 terminated string (just to be safe) */ + strmake(tmp->str, var->save_result.string_value.str, tmp->length); + return false; + } + bool global_update(THD *thd, set_var *var) + { + DBUG_ASSERT(FALSE); + return false; + } + void session_save_default(THD *thd, set_var *var) + { + char *ptr= (char*)(intptr)option.def_value; + var->save_result.string_value.str= ptr; + var->save_result.string_value.length= strlen(ptr); + } + void global_save_default(THD *thd, set_var *var) + { + DBUG_ASSERT(FALSE); + } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { + DBUG_ASSERT(FALSE); + return NULL; + } +}; + + #ifndef DBUG_OFF /** - @@session.dbug and @@global.dbug variables. + @@session.debug_dbug and @@global.debug_dbug variables. @@dbug variable differs from other variables in one aspect: if its value is not assigned in the session, it "points" to the global @@ -660,11 +774,12 @@ public: on_check_function on_check_func=0, on_update_function on_update_func=0, const char *substitute=0) - : sys_var(&all_sys_vars, name_arg, comment, flag_args, 0, getopt.id, + : sys_var(&all_sys_vars, name_arg, comment, flag_args, + (char*)¤t_dbug_option-(char*)&global_system_variables, getopt.id, getopt.arg_type, SHOW_CHAR, (intptr)def_val, lock, binlog_status_arg, on_check_func, on_update_func, substitute) - { option.var_type= GET_NO_ARG; } + { option.var_type|= GET_STR; } bool do_check(THD *thd, set_var *var) { char buff[STRING_BUFFER_USUAL_SIZE]; @@ -698,20 +813,20 @@ public: char *ptr= (char*)(intptr)option.def_value; var->save_result.string_value.str= ptr; } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) { char buf[256]; DBUG_EXPLAIN(buf, sizeof(buf)); return (uchar*) thd->strdup(buf); } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) { char buf[256]; DBUG_EXPLAIN_INITIAL(buf, sizeof(buf)); return (uchar*) thd->strdup(buf); } - bool check_update_type(Item_result type) - { return type != STRING_RESULT; } + uchar *default_value_ptr(THD *thd) + { return (uchar*)""; } }; #endif @@ -786,7 +901,7 @@ public: return keycache_update(thd, key_cache, offset, new_value); } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) { KEY_CACHE *key_cache= get_key_cache(base); if (!key_cache) @@ -911,7 +1026,7 @@ public: lock, binlog_status_arg, on_check_func, on_update_func, substitute) { - option.var_type= GET_DOUBLE; + option.var_type|= GET_DOUBLE; option.min_value= (longlong) getopt_double2ulonglong(min_val); option.max_value= (longlong) getopt_double2ulonglong(max_val); global_var(double)= (double)option.def_value; @@ -938,10 +1053,6 @@ public: global_var(double)= var->save_result.double_value; return false; } - bool check_update_type(Item_result type) - { - return type != INT_RESULT && type != REAL_RESULT && type != DECIMAL_RESULT; - } void session_save_default(THD *thd, set_var *var) { var->save_result.double_value= global_var(double); } void global_save_default(THD *thd, set_var *var) @@ -975,7 +1086,7 @@ public: lock, binlog_status_arg, on_check_func, on_update_func, substitute) { } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) { if (thd->user_connect && thd->user_connect->user_resources.user_conn) return (uchar*) &(thd->user_connect->user_resources.user_conn); @@ -983,9 +1094,6 @@ public: } }; -// overflow-safe (1 << X)-1 -#define MAX_SET(X) ((((1UL << ((X)-1))-1) << 1) | 1) - /** The class for flagset variables - a variant of SET that allows in-place editing (turning on/off individual bits). String representations looks like @@ -1016,11 +1124,11 @@ public: binlog_status_arg, on_check_func, on_update_func, substitute) { - option.var_type= GET_FLAGSET; + option.var_type|= GET_FLAGSET; global_var(ulonglong)= def_val; SYSVAR_ASSERT(typelib.count > 1); SYSVAR_ASSERT(typelib.count <= 65); - SYSVAR_ASSERT(def_val < MAX_SET(typelib.count)); + SYSVAR_ASSERT(def_val < my_set_bits(typelib.count)); SYSVAR_ASSERT(strcmp(values[typelib.count-1], "default") == 0); SYSVAR_ASSERT(size == sizeof(ulonglong)); } @@ -1068,7 +1176,7 @@ public: { longlong tmp=var->value->val_int(); if ((tmp < 0 && ! var->value->unsigned_flag) - || (ulonglong)tmp > MAX_SET(typelib.count)) + || (ulonglong)tmp > my_set_bits(typelib.count)) return true; else var->save_result.ulonglong_value= tmp; @@ -1090,16 +1198,14 @@ public: { var->save_result.ulonglong_value= global_var(ulonglong); } void global_save_default(THD *thd, set_var *var) { var->save_result.ulonglong_value= option.def_value; } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) - { - return (uchar*)flagset_to_string(thd, 0, session_var(thd, ulonglong), - typelib.type_names); - } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) - { - return (uchar*)flagset_to_string(thd, 0, global_var(ulonglong), - typelib.type_names); - } + uchar *valptr(THD *thd, ulonglong val) + { return (uchar*)flagset_to_string(thd, 0, val, typelib.type_names); } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, session_var(thd, ulonglong)); } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, global_var(ulonglong)); } + uchar *default_value_ptr(THD *thd) + { return valptr(thd, option.def_value); } }; /** @@ -1127,11 +1233,11 @@ public: binlog_status_arg, on_check_func, on_update_func, substitute) { - option.var_type= GET_SET; + option.var_type|= GET_SET; global_var(ulonglong)= def_val; SYSVAR_ASSERT(typelib.count > 0); SYSVAR_ASSERT(typelib.count <= 64); - SYSVAR_ASSERT(def_val <= MAX_SET(typelib.count)); + SYSVAR_ASSERT(def_val <= my_set_bits(typelib.count)); SYSVAR_ASSERT(size == sizeof(ulonglong)); } bool do_check(THD *thd, set_var *var) @@ -1169,7 +1275,7 @@ public: { longlong tmp=var->value->val_int(); if ((tmp < 0 && ! var->value->unsigned_flag) - || (ulonglong)tmp > MAX_SET(typelib.count)) + || (ulonglong)tmp > my_set_bits(typelib.count)) return true; else var->save_result.ulonglong_value= tmp; @@ -1191,16 +1297,14 @@ public: { var->save_result.ulonglong_value= global_var(ulonglong); } void global_save_default(THD *thd, set_var *var) { var->save_result.ulonglong_value= option.def_value; } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) - { - return (uchar*)set_to_string(thd, 0, session_var(thd, ulonglong), - typelib.type_names); - } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) - { - return (uchar*)set_to_string(thd, 0, global_var(ulonglong), - typelib.type_names); - } + uchar *valptr(THD *thd, ulonglong val) + { return (uchar*)set_to_string(thd, 0, val, typelib.type_names); } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, session_var(thd, ulonglong)); } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, global_var(ulonglong)); } + uchar *default_value_ptr(THD *thd) + { return valptr(thd, option.def_value); } }; /** @@ -1234,9 +1338,9 @@ public: substitute), plugin_type(plugin_type_arg) { - option.var_type= GET_STR; + option.var_type|= GET_STR; SYSVAR_ASSERT(size == sizeof(plugin_ref)); - SYSVAR_ASSERT(getopt.id == -1); // force NO_CMD_LINE + SYSVAR_ASSERT(getopt.id < 0); // force NO_CMD_LINE } bool do_check(THD *thd, set_var *var) { @@ -1251,7 +1355,7 @@ public: // special code for storage engines (e.g. to handle historical aliases) if (plugin_type == MYSQL_STORAGE_ENGINE_PLUGIN) - plugin= ha_resolve_by_name(thd, &pname); + plugin= ha_resolve_by_name(thd, &pname, false); else plugin= my_plugin_lock_by_name(thd, &pname, plugin_type); if (!plugin) @@ -1273,7 +1377,7 @@ public: plugin_ref oldval= *valptr; if (oldval != newval) { - *valptr= my_plugin_lock(NULL, newval); + *valptr= newval ? my_plugin_lock(NULL, newval) : 0; plugin_unlock(NULL, oldval); } } @@ -1292,38 +1396,41 @@ public: void session_save_default(THD *thd, set_var *var) { plugin_ref plugin= global_var(plugin_ref); - var->save_result.plugin= my_plugin_lock(thd, plugin); + var->save_result.plugin= plugin ? my_plugin_lock(thd, plugin) : 0; } - void global_save_default(THD *thd, set_var *var) + plugin_ref get_default(THD *thd) { - LEX_STRING pname; - char **default_value= reinterpret_cast<char**>(option.def_value); - pname.str= *default_value; - pname.length= strlen(pname.str); + char *default_value= *reinterpret_cast<char**>(option.def_value); + if (!default_value) + return 0; + LEX_STRING pname= { default_value, strlen(pname.str) }; plugin_ref plugin; + if (plugin_type == MYSQL_STORAGE_ENGINE_PLUGIN) - plugin= ha_resolve_by_name(thd, &pname); + plugin= ha_resolve_by_name(thd, &pname, false); else plugin= my_plugin_lock_by_name(thd, &pname, plugin_type); DBUG_ASSERT(plugin); - - var->save_result.plugin= my_plugin_lock(thd, plugin); + return my_plugin_lock(thd, plugin); } - bool check_update_type(Item_result type) - { return type != STRING_RESULT; } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) + + void global_save_default(THD *thd, set_var *var) { - plugin_ref plugin= session_var(thd, plugin_ref); - return (uchar*)(plugin ? thd->strmake(plugin_name(plugin)->str, - plugin_name(plugin)->length) : 0); + var->save_result.plugin= get_default(thd); } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) + + uchar *valptr(THD *thd, plugin_ref plugin) { - plugin_ref plugin= global_var(plugin_ref); return (uchar*)(plugin ? thd->strmake(plugin_name(plugin)->str, plugin_name(plugin)->length) : 0); } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, session_var(thd, plugin_ref)); } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, global_var(plugin_ref)); } + uchar *default_value_ptr(THD *thd) + { return valptr(thd, get_default(thd)); } }; #if defined(ENABLED_DEBUG_SYNC) @@ -1347,7 +1454,7 @@ public: substitute) { SYSVAR_ASSERT(scope() == ONLY_SESSION); - option.var_type= GET_NO_ARG; + option.var_type|= GET_STR; } bool do_check(THD *thd, set_var *var) { @@ -1362,7 +1469,6 @@ public: } bool session_update(THD *thd, set_var *var) { - extern bool debug_sync_update(THD *thd, char *val_str); return debug_sync_update(thd, var->save_result.string_value.str); } bool global_update(THD *thd, set_var *var) @@ -1379,21 +1485,22 @@ public: { DBUG_ASSERT(FALSE); } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) { extern uchar *debug_sync_value_ptr(THD *thd); return debug_sync_value_ptr(thd); } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) { DBUG_ASSERT(FALSE); return 0; } - bool check_update_type(Item_result type) - { return type != STRING_RESULT; } + uchar *default_value_ptr(THD *thd) + { return (uchar*)""; } }; #endif /* defined(ENABLED_DEBUG_SYNC) */ + /** The class for bit variables - a variant of boolean that stores the value in a bit. @@ -1438,12 +1545,12 @@ public: binlog_status_arg, on_check_func, on_update_func, substitute) { - option.var_type= GET_BOOL; + option.var_type|= GET_BOOL; reverse_semantics= my_count_bits(bitmask_arg) > 1; bitmask= reverse_semantics ? ~bitmask_arg : bitmask_arg; set(global_var_ptr(), def_val); SYSVAR_ASSERT(def_val < 2); - SYSVAR_ASSERT(getopt.id == -1); // force NO_CMD_LINE + SYSVAR_ASSERT(getopt.id < 0); // force NO_CMD_LINE SYSVAR_ASSERT(size == sizeof(ulonglong)); } bool session_update(THD *thd, set_var *var) @@ -1460,16 +1567,19 @@ public: { var->save_result.ulonglong_value= global_var(ulonglong) & bitmask; } void global_save_default(THD *thd, set_var *var) { var->save_result.ulonglong_value= option.def_value; } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) + + uchar *valptr(THD *thd, ulonglong val) { - thd->sys_var_tmp.my_bool_value= reverse_semantics ^ - ((session_var(thd, ulonglong) & bitmask) != 0); + thd->sys_var_tmp.my_bool_value= reverse_semantics ^ ((val & bitmask) != 0); return (uchar*) &thd->sys_var_tmp.my_bool_value; } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, session_var(thd, ulonglong)); } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, global_var(ulonglong)); } + uchar *default_value_ptr(THD *thd) { - thd->sys_var_tmp.my_bool_value= reverse_semantics ^ - ((global_var(ulonglong) & bitmask) != 0); + thd->sys_var_tmp.my_bool_value= option.def_value != 0; return (uchar*) &thd->sys_var_tmp.my_bool_value; } }; @@ -1513,7 +1623,7 @@ public: read_func(read_func_arg), update_func(update_func_arg) { SYSVAR_ASSERT(scope() == ONLY_SESSION); - SYSVAR_ASSERT(getopt.id == -1); // NO_CMD_LINE, because the offset is fake + SYSVAR_ASSERT(getopt.id < 0); // NO_CMD_LINE, because the offset is fake } bool session_update(THD *thd, set_var *var) { return update_func(thd, var); } @@ -1526,66 +1636,87 @@ public: { var->value= 0; } void global_save_default(THD *thd, set_var *var) { DBUG_ASSERT(FALSE); } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) { thd->sys_var_tmp.ulonglong_value= read_func(thd); return (uchar*) &thd->sys_var_tmp.ulonglong_value; } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) { DBUG_ASSERT(FALSE); return 0; } + uchar *default_value_ptr(THD *thd) + { + thd->sys_var_tmp.ulonglong_value= 0; + return (uchar*) &thd->sys_var_tmp.ulonglong_value; + } }; -class Sys_var_session_special_double: public Sys_var_double -{ - typedef bool (*session_special_update_function)(THD *thd, set_var *var); - typedef double (*session_special_read_function)(THD *thd); +/* + Dedicated class because of a weird behavior of a default value. + Assigning timestamp to itself - session_special_read_function read_func; - session_special_update_function update_func; + SET @@timestamp = @@timestamp + + make it non-default and stops the time flow. +*/ +class Sys_var_timestamp: public Sys_var_double +{ public: - Sys_var_session_special_double(const char *name_arg, + Sys_var_timestamp(const char *name_arg, const char *comment, int flag_args, CMD_LINE getopt, double min_val, double max_val, - PolyLock *lock, enum binlog_status_enum binlog_status_arg, - on_check_function on_check_func, - session_special_update_function update_func_arg, - session_special_read_function read_func_arg, - const char *substitute=0) + PolyLock *lock, enum binlog_status_enum binlog_status_arg) : Sys_var_double(name_arg, comment, flag_args, 0, sizeof(double), getopt, min_val, - max_val, 0, lock, binlog_status_arg, on_check_func, 0, - substitute), - read_func(read_func_arg), update_func(update_func_arg) + max_val, 0, lock, binlog_status_arg) { SYSVAR_ASSERT(scope() == ONLY_SESSION); - SYSVAR_ASSERT(getopt.id == -1); // NO_CMD_LINE, because the offset is fake + SYSVAR_ASSERT(getopt.id < 0); // NO_CMD_LINE, because the offset is fake } bool session_update(THD *thd, set_var *var) - { return update_func(thd, var); } + { + if (var->value) + { + my_hrtime_t hrtime = { hrtime_from_time(var->save_result.double_value) }; + thd->set_time(hrtime); + } + else // SET timestamp=DEFAULT + thd->user_time.val= 0; + return false; + } bool global_update(THD *thd, set_var *var) { DBUG_ASSERT(FALSE); return true; } + bool session_is_default(THD *thd) + { + return thd->user_time.val == 0; + } void session_save_default(THD *thd, set_var *var) { var->value= 0; } void global_save_default(THD *thd, set_var *var) { DBUG_ASSERT(FALSE); } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) { - thd->sys_var_tmp.double_value= read_func(thd); + thd->sys_var_tmp.double_value= thd->start_time + + thd->start_time_sec_part/(double)TIME_SECOND_PART_FACTOR; return (uchar*) &thd->sys_var_tmp.double_value; } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) { DBUG_ASSERT(FALSE); return 0; } + uchar *default_value_ptr(THD *thd) + { + thd->sys_var_tmp.double_value= 0; + return (uchar*) &thd->sys_var_tmp.double_value; + } }; @@ -1616,12 +1747,13 @@ public: substitute) { SYSVAR_ASSERT(scope() == GLOBAL); - SYSVAR_ASSERT(getopt.id == -1); + SYSVAR_ASSERT(getopt.id < 0); SYSVAR_ASSERT(lock == 0); SYSVAR_ASSERT(binlog_status_arg == VARIABLE_NOT_IN_BINLOG); SYSVAR_ASSERT(is_readonly()); SYSVAR_ASSERT(on_update == 0); SYSVAR_ASSERT(size == sizeof(enum SHOW_COMP_OPTION)); + option.var_type|= GET_STR; } bool do_check(THD *thd, set_var *var) { DBUG_ASSERT(FALSE); @@ -1639,16 +1771,15 @@ public: } void session_save_default(THD *thd, set_var *var) { } void global_save_default(THD *thd, set_var *var) { } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) { DBUG_ASSERT(FALSE); return 0; } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) { return (uchar*)show_comp_option_name[global_var(enum SHOW_COMP_OPTION)]; } - bool check_update_type(Item_result type) { return false; } }; /** @@ -1684,7 +1815,7 @@ public: substitute), name_offset(name_off) { - option.var_type= GET_STR; + option.var_type|= GET_ENUM; // because we accept INT and STRING here /* struct variables are special on the command line - often (e.g. for charsets) the name cannot be immediately resolved, but only after all @@ -1693,7 +1824,7 @@ public: thus all struct command-line options should be added manually to my_long_options in mysqld.cc */ - SYSVAR_ASSERT(getopt.id == -1); + SYSVAR_ASSERT(getopt.id < 0); SYSVAR_ASSERT(size == sizeof(void *)); } bool do_check(THD *thd, set_var *var) @@ -1715,18 +1846,14 @@ public: void **default_value= reinterpret_cast<void**>(option.def_value); var->save_result.ptr= *default_value; } - bool check_update_type(Item_result type) - { return type != INT_RESULT && type != STRING_RESULT; } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) - { - uchar *ptr= session_var(thd, uchar*); - return ptr ? *(uchar**)(ptr+name_offset) : 0; - } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) - { - uchar *ptr= global_var(uchar*); - return ptr ? *(uchar**)(ptr+name_offset) : 0; - } + uchar *valptr(THD *thd, uchar *val) + { return val ? *(uchar**)(val+name_offset) : 0; } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, session_var(thd, uchar*)); } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, global_var(uchar*)); } + uchar *default_value_ptr(THD *thd) + { return valptr(thd, *(uchar**)option.def_value); } }; /** @@ -1755,8 +1882,9 @@ public: lock, binlog_status_arg, on_check_func, on_update_func, substitute) { - SYSVAR_ASSERT(getopt.id == -1); + SYSVAR_ASSERT(getopt.id < 0); SYSVAR_ASSERT(size == sizeof(Time_zone *)); + option.var_type|= GET_STR; } bool do_check(THD *thd, set_var *var) { @@ -1794,7 +1922,9 @@ public: var->save_result.time_zone= *(Time_zone**)(intptr)option.def_value; } - uchar *session_value_ptr(THD *thd, LEX_STRING *base) + uchar *valptr(THD *thd, Time_zone *val) + { return (uchar *)(val->get_name()->ptr()); } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) { /* This is an ugly fix for replication: we don't replicate properly queries @@ -1805,14 +1935,12 @@ public: (binlog code stores session value only). */ thd->time_zone_used= 1; - return (uchar *)(session_var(thd, Time_zone*)->get_name()->ptr()); + return valptr(thd, session_var(thd, Time_zone *)); } - uchar *global_value_ptr(THD *thd, LEX_STRING *base) - { - return (uchar *)(global_var(Time_zone*)->get_name()->ptr()); - } - bool check_update_type(Item_result type) - { return type != STRING_RESULT; } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, global_var(Time_zone*)); } + uchar *default_value_ptr(THD *thd) + { return valptr(thd, *(Time_zone**)option.def_value); } }; /** @@ -1847,6 +1975,30 @@ public: } }; + +/** + Class representing the tx_read_only system variable for setting + default transaction access mode. + + Note that there is a special syntax - SET TRANSACTION READ ONLY + (or READ WRITE) that sets the access mode for the next transaction + only. +*/ + +class Sys_var_tx_read_only: public Sys_var_mybool +{ +public: + Sys_var_tx_read_only(const char *name_arg, const char *comment, int flag_args, + ptrdiff_t off, size_t size, CMD_LINE getopt, + my_bool def_val, PolyLock *lock, + enum binlog_status_enum binlog_status_arg, + on_check_function on_check_func) + :Sys_var_mybool(name_arg, comment, flag_args, off, size, getopt, + def_val, lock, binlog_status_arg, on_check_func) + {} + virtual bool session_update(THD *thd, set_var *var); +}; + /* Class for replicate_events_marked_for_skip. We need a custom update function that ensures the slave is stopped when @@ -1858,26 +2010,331 @@ public: Sys_var_replicate_events_marked_for_skip(const char *name_arg, const char *comment, int flag_args, ptrdiff_t off, size_t size, CMD_LINE getopt, - const char *values[], uint def_val, PolyLock *lock, - enum binlog_status_enum binlog_status_arg, - on_check_function on_check_func) + const char *values[], uint def_val, PolyLock *lock= 0, + enum binlog_status_enum binlog_status_arg= VARIABLE_NOT_IN_BINLOG) :Sys_var_enum(name_arg, comment, flag_args, off, size, getopt, - values, def_val, lock, binlog_status_arg, on_check_func) + values, def_val, lock, binlog_status_arg) {} bool global_update(THD *thd, set_var *var); }; -/**************************************************************************** - Used templates -****************************************************************************/ - -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class List<set_var_base>; -template class List_iterator_fast<set_var_base>; -template class Sys_var_integer<int, GET_INT, SHOW_SINT>; -template class Sys_var_integer<uint, GET_UINT, SHOW_INT>; -template class Sys_var_integer<ulong, GET_ULONG, SHOW_LONG>; -template class Sys_var_integer<ha_rows, GET_HA_ROWS, SHOW_HA_ROWS>; -template class Sys_var_integer<ulonglong, GET_ULL, SHOW_LONGLONG>; -#endif +/* + Class for handing multi-source replication variables + Variable values are store in Master_info, but to make it possible to + access variable without locks we also store it thd->variables. + These can be used as GLOBAL or SESSION, but both points to the same + variable. This is to make things compatible with MySQL 5.5 where variables + like sql_slave_skip_counter are GLOBAL. +*/ + +#define MASTER_INFO_VAR(X) my_offsetof(Master_info, X), sizeof(((Master_info *)0x10)->X) +class Sys_var_multi_source_ulonglong; +class Master_info; +typedef bool (*on_multi_source_update_function)(sys_var *self, THD *thd, + Master_info *mi); +bool update_multi_source_variable(sys_var *self, + THD *thd, enum_var_type type); + + +class Sys_var_multi_source_ulonglong :public Sys_var_ulonglong +{ + ptrdiff_t master_info_offset; + on_multi_source_update_function update_multi_source_variable_func; +public: + Sys_var_multi_source_ulonglong(const char *name_arg, + const char *comment, int flag_args, + ptrdiff_t off, size_t size, + CMD_LINE getopt, + ptrdiff_t master_info_offset_arg, + size_t master_info_arg_size, + ulonglong min_val, ulonglong max_val, + ulonglong def_val, uint block_size, + on_multi_source_update_function on_update_func) + :Sys_var_ulonglong(name_arg, comment, flag_args, off, size, + getopt, min_val, max_val, def_val, block_size, + 0, VARIABLE_NOT_IN_BINLOG, 0, update_multi_source_variable), + master_info_offset(master_info_offset_arg), + update_multi_source_variable_func(on_update_func) + { + SYSVAR_ASSERT(master_info_arg_size == size); + } + bool global_update(THD *thd, set_var *var) + { + return session_update(thd, var); + } + void session_save_default(THD *thd, set_var *var) + { + /* Use value given in variable declaration */ + global_save_default(thd, var); + } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { + ulonglong *tmp, res; + tmp= (ulonglong*) (((uchar*)&(thd->variables)) + offset); + res= get_master_info_ulonglong_value(thd, master_info_offset); + *tmp= res; + return (uchar*) tmp; + } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { + return session_value_ptr(thd, base); + } + ulonglong get_master_info_ulonglong_value(THD *thd, ptrdiff_t offset); + bool update_variable(THD *thd, Master_info *mi) + { + return update_multi_source_variable_func(this, thd, mi); + } +}; + + +/** + Class for @@global.gtid_current_pos. +*/ +class Sys_var_gtid_current_pos: public sys_var +{ +public: + Sys_var_gtid_current_pos(const char *name_arg, + const char *comment, int flag_args, ptrdiff_t off, size_t size, + CMD_LINE getopt) + : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id, + getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG, + NULL, NULL, NULL) + { + SYSVAR_ASSERT(getopt.id < 0); + SYSVAR_ASSERT(is_readonly()); + option.var_type|= GET_STR; + } + bool do_check(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + bool session_update(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + bool global_update(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + void session_save_default(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + } + void global_save_default(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { + DBUG_ASSERT(false); + return NULL; + } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base); +}; + + +/** + Class for @@global.gtid_binlog_pos. +*/ +class Sys_var_gtid_binlog_pos: public sys_var +{ +public: + Sys_var_gtid_binlog_pos(const char *name_arg, + const char *comment, int flag_args, ptrdiff_t off, size_t size, + CMD_LINE getopt) + : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id, + getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG, + NULL, NULL, NULL) + { + SYSVAR_ASSERT(getopt.id < 0); + SYSVAR_ASSERT(is_readonly()); + option.var_type|= GET_STR; + } + bool do_check(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + bool session_update(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + bool global_update(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + void session_save_default(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + } + void global_save_default(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { + DBUG_ASSERT(false); + return NULL; + } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base); +}; + + +/** + Class for @@global.gtid_slave_pos. +*/ +class Sys_var_gtid_slave_pos: public sys_var +{ +public: + Sys_var_gtid_slave_pos(const char *name_arg, + const char *comment, int flag_args, ptrdiff_t off, size_t size, + CMD_LINE getopt) + : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id, + getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG, + NULL, NULL, NULL) + { + option.var_type|= GET_STR; + } + bool do_check(THD *thd, set_var *var); + bool session_update(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + bool global_update(THD *thd, set_var *var); + void session_save_default(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + } + void global_save_default(THD *thd, set_var *var) + { + /* Record the attempt to use default so we can error. */ + var->value= 0; + } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { + DBUG_ASSERT(false); + return NULL; + } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base); + uchar *default_value_ptr(THD *thd) + { return 0; } +}; + + +/** + Class for @@global.gtid_binlog_state. +*/ +class Sys_var_gtid_binlog_state: public sys_var +{ +public: + Sys_var_gtid_binlog_state(const char *name_arg, + const char *comment, int flag_args, ptrdiff_t off, size_t size, + CMD_LINE getopt) + : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id, + getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG, + NULL, NULL, NULL) + { + option.var_type|= GET_STR; + } + bool do_check(THD *thd, set_var *var); + bool session_update(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + bool global_update(THD *thd, set_var *var); + void session_save_default(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + } + void global_save_default(THD *thd, set_var *var) + { + /* Record the attempt to use default so we can error. */ + var->value= 0; + } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { + DBUG_ASSERT(false); + return NULL; + } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base); + uchar *default_value_ptr(THD *thd) + { return 0; } +}; + + +/** + Class for @@session.last_gtid. +*/ +class Sys_var_last_gtid: public sys_var +{ +public: + Sys_var_last_gtid(const char *name_arg, + const char *comment, int flag_args, CMD_LINE getopt) + : sys_var(&all_sys_vars, name_arg, comment, flag_args, 0, getopt.id, + getopt.arg_type, SHOW_CHAR, 0, NULL, VARIABLE_NOT_IN_BINLOG, + NULL, NULL, NULL) + { + SYSVAR_ASSERT(getopt.id < 0); + SYSVAR_ASSERT(is_readonly()); + option.var_type|= GET_STR; + } + bool do_check(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + bool session_update(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + bool global_update(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + return true; + } + void session_save_default(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + } + void global_save_default(THD *thd, set_var *var) + { + DBUG_ASSERT(false); + } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base); + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { + DBUG_ASSERT(false); + return NULL; + } +}; + + +/** + Class for connection_name.slave_parallel_mode. +*/ +class Sys_var_slave_parallel_mode: public Sys_var_enum +{ +public: + Sys_var_slave_parallel_mode(const char *name_arg, + const char *comment, int flag_args, ptrdiff_t off, size_t size, + CMD_LINE getopt, const char *values[], + enum_slave_parallel_mode def_val) + : Sys_var_enum(name_arg, comment, flag_args, off, size, + getopt, values, def_val) + { + option.var_type|= GET_ASK_ADDR; + option.value= (uchar**)1; // crash me, please + SYSVAR_ASSERT(scope() == GLOBAL); + } + bool global_update(THD *thd, set_var *var); + uchar *global_value_ptr(THD *thd, const LEX_STRING *base); +}; diff --git a/sql/table.cc b/sql/table.cc index f6152a36eef..31f0d255847 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -17,11 +17,9 @@ /* Some general useful functions */ -#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" -#include "unireg.h" // REQUIRED: for other includes #include "table.h" -#include "frm_crypt.h" // get_crypt_for_frm #include "key.h" // find_ref_key #include "sql_table.h" // build_table_filename, // primary_key_name @@ -31,14 +29,17 @@ #include "sql_partition.h" // mysql_unpack_partition, // fix_partition_func, partition_info #include "sql_acl.h" // *_ACL, acl_getroot_no_password -#include "sql_base.h" // release_table_share +#include "sql_base.h" #include "create_options.h" #include <m_ctype.h> #include "my_md5.h" #include "my_bit.h" #include "sql_select.h" #include "sql_derived.h" +#include "sql_statistics.h" +#include "discover.h" #include "mdl.h" // MDL_wait_for_graph_visitor +#include "sql_view.h" /* INFORMATION_SCHEMA name */ LEX_STRING INFORMATION_SCHEMA_NAME= {C_STRING_WITH_LEN("information_schema")}; @@ -63,18 +64,12 @@ LEX_STRING parse_vcol_keyword= { C_STRING_WITH_LEN("PARSE_VCOL_EXPR ") }; /* Functions defined in this file */ -void open_table_error(TABLE_SHARE *share, int error, int db_errno, - myf errortype, int errarg); -static int open_binary_frm(THD *thd, TABLE_SHARE *share, - uchar *head, File file); static void fix_type_pointers(const char ***array, TYPELIB *point_to_type, uint types, char **names); static uint find_field(Field **fields, uchar *record, uint start, uint length); inline bool is_system_table_name(const char *name, uint length); -static ulong get_form_pos(File file, uchar *head); - /************************************************************************** Object_creation_ctx implementation. **************************************************************************/ @@ -152,9 +147,9 @@ View_creation_ctx * View_creation_ctx::create(THD *thd, if (!view->view_client_cs_name.str || !view->view_connection_cl_name.str) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_VIEW_NO_CREATION_CTX, - ER(ER_VIEW_NO_CREATION_CTX), + ER_THD(thd, ER_VIEW_NO_CREATION_CTX), (const char *) view->db, (const char *) view->table_name); @@ -186,9 +181,9 @@ View_creation_ctx * View_creation_ctx::create(THD *thd, (const char *) view->view_client_cs_name.str, (const char *) view->view_connection_cl_name.str); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_VIEW_INVALID_CREATION_CTX, - ER(ER_VIEW_INVALID_CREATION_CTX), + ER_THD(thd, ER_VIEW_INVALID_CREATION_CTX), (const char *) view->db, (const char *) view->table_name); } @@ -277,7 +272,7 @@ TABLE_CATEGORY get_table_category(const LEX_STRING *db, const LEX_STRING *name) /* - Allocate a setup TABLE_SHARE structure + Allocate and setup a TABLE_SHARE structure SYNOPSIS alloc_table_share() @@ -290,8 +285,8 @@ TABLE_CATEGORY get_table_category(const LEX_STRING *db, const LEX_STRING *name) # Share */ -TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, - uint key_length) +TABLE_SHARE *alloc_table_share(const char *db, const char *table_name, + const char *key, uint key_length) { MEM_ROOT mem_root; TABLE_SHARE *share; @@ -299,13 +294,11 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, char path[FN_REFLEN]; uint path_length; DBUG_ENTER("alloc_table_share"); - DBUG_PRINT("enter", ("table: '%s'.'%s'", - table_list->db, table_list->table_name)); + DBUG_PRINT("enter", ("table: '%s'.'%s'", db, table_name)); path_length= build_table_filename(path, sizeof(path) - 1, - table_list->db, - table_list->table_name, "", 0); - init_sql_alloc(&mem_root, TABLE_ALLOC_BLOCK_SIZE, 0); + db, table_name, "", 0); + init_sql_alloc(&mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0)); if (multi_alloc_root(&mem_root, &share, sizeof(*share), &key_buff, key_length, @@ -321,26 +314,18 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, strmov(share->path.str, path); share->normalized_path.str= share->path.str; share->normalized_path.length= path_length; - - share->set_refresh_version(); - - /* - Since alloc_table_share() can be called without any locking (for - example, ha_create_table... functions), we do not assign a table - map id here. Instead we assign a value that is not used - elsewhere, and then assign a table map id inside open_table() - under the protection of the LOCK_open mutex. - */ - share->table_map_id= ~0UL; + share->table_category= get_table_category(& share->db, & share->table_name); + share->open_errno= ENOENT; share->cached_row_logging_check= -1; - share->used_tables.empty(); - share->free_tables.empty(); - share->m_flush_tickets.empty(); + init_sql_alloc(&share->stats_cb.mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0)); memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root)); + mysql_mutex_init(key_TABLE_SHARE_LOCK_share, + &share->LOCK_share, MY_MUTEX_INIT_SLOW); mysql_mutex_init(key_TABLE_SHARE_LOCK_ha_data, &share->LOCK_ha_data, MY_MUTEX_INIT_FAST); + tdc_assign_new_table_id(share); } DBUG_RETURN(share); } @@ -353,7 +338,7 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, init_tmp_table_share() thd thread handle share Share to fill - key Table_cache_key, as generated from create_table_def_key. + key Table_cache_key, as generated from tdc_create_key. must start with db name. key_length Length of key table_name Table name @@ -377,7 +362,12 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key, DBUG_PRINT("enter", ("table: '%s'.'%s'", key, table_name)); bzero((char*) share, sizeof(*share)); - init_sql_alloc(&share->mem_root, TABLE_ALLOC_BLOCK_SIZE, 0); + /* + This can't be MY_THREAD_SPECIFIC for slaves as they are freed + during cleanup() from Relay_log_info::close_temporary_tables() + */ + init_sql_alloc(&share->mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, + MYF(thd->slave_thread ? 0 : MY_THREAD_SPECIFIC)); share->table_category= TABLE_CATEGORY_TEMPORARY; share->tmp_table= INTERNAL_TMP_TABLE; share->db.str= (char*) key; @@ -398,11 +388,6 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key, compatibility checks. */ share->table_map_id= (ulong) thd->query_id; - - share->used_tables.empty(); - share->free_tables.empty(); - share->m_flush_tickets.empty(); - DBUG_VOID_RETURN; } @@ -417,10 +402,28 @@ void TABLE_SHARE::destroy() { uint idx; KEY *info_it; + DBUG_ENTER("TABLE_SHARE::destroy"); + DBUG_PRINT("info", ("db: %s table: %s", db.str, table_name.str)); + + if (ha_share) + { + delete ha_share; + ha_share= NULL; // Safety + } - /* The mutex is initialized only for shares that are part of the TDC */ + delete_stat_values_for_table_share(this); + free_root(&stats_cb.mem_root, MYF(0)); + stats_cb.stats_can_be_read= FALSE; + stats_cb.stats_is_read= FALSE; + stats_cb.histograms_can_be_read= FALSE; + stats_cb.histograms_are_read= FALSE; + + /* The mutexes are initialized only for shares that are part of the TDC */ if (tmp_table == NO_TMP_TABLE) + { + mysql_mutex_destroy(&LOCK_share); mysql_mutex_destroy(&LOCK_ha_data); + } my_hash_free(&name_hash); plugin_unlock(NULL, db_plugin); @@ -436,24 +439,20 @@ void TABLE_SHARE::destroy() info_it->flags= 0; } } - if (ha_data_destroy) - { - ha_data_destroy(ha_data); - ha_data_destroy= NULL; - } + #ifdef WITH_PARTITION_STORAGE_ENGINE - if (ha_part_data_destroy) - { - ha_part_data_destroy(ha_part_data); - ha_part_data_destroy= NULL; - } + plugin_unlock(NULL, default_part_plugin); #endif /* WITH_PARTITION_STORAGE_ENGINE */ + + PSI_CALL_release_table_share(m_psi); + /* Make a copy since the share is allocated in its own root, and free_root() updates its argument after freeing the memory. */ MEM_ROOT own_root= mem_root; free_root(&own_root, MYF(0)); + DBUG_VOID_RETURN; } /* @@ -468,37 +467,7 @@ void free_table_share(TABLE_SHARE *share) { DBUG_ENTER("free_table_share"); DBUG_PRINT("enter", ("table: %s.%s", share->db.str, share->table_name.str)); - DBUG_ASSERT(share->ref_count == 0); - - if (share->m_flush_tickets.is_empty()) - { - /* - No threads are waiting for this share to be flushed (the - share is not old, is for a temporary table, or just nobody - happens to be waiting for it). Destroy it. - */ - share->destroy(); - } - else - { - Wait_for_flush_list::Iterator it(share->m_flush_tickets); - Wait_for_flush *ticket; - /* - We're about to iterate over a list that is used - concurrently. Make sure this never happens without a lock. - */ - mysql_mutex_assert_owner(&LOCK_open); - - while ((ticket= it++)) - (void) ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED); - /* - If there are threads waiting for this share to be flushed, - the last one to receive the notification will destroy the - share. At this point the share is removed from the table - definition cache, so is OK to proceed here without waiting - for this thread to do the work. - */ - } + share->destroy(); DBUG_VOID_RETURN; } @@ -543,6 +512,17 @@ inline bool is_system_table_name(const char *name, uint length) my_tolower(ci, name[2]) == 'm' && my_tolower(ci, name[3]) == 'e') || + /* one of mysql.*_stat tables, but not mysql.innodb* tables*/ + ((my_tolower(ci, name[length-5]) == 's' && + my_tolower(ci, name[length-4]) == 't' && + my_tolower(ci, name[length-3]) == 'a' && + my_tolower(ci, name[length-2]) == 't' && + my_tolower(ci, name[length-1]) == 's') && + !(my_tolower(ci, name[0]) == 'i' && + my_tolower(ci, name[1]) == 'n' && + my_tolower(ci, name[2]) == 'n' && + my_tolower(ci, name[3]) == 'o')) || + /* mysql.event table */ (my_tolower(ci, name[0]) == 'e' && my_tolower(ci, name[1]) == 'v' && @@ -555,27 +535,6 @@ inline bool is_system_table_name(const char *name, uint length) } -/** - Check if a string contains path elements -*/ - -static bool has_disabled_path_chars(const char *str) -{ - for (; *str; str++) - { - switch (*str) { - case FN_EXTCHAR: - case '/': - case '\\': - case '~': - case '@': - return TRUE; - } - } - return FALSE; -} - - /* Read table definition from a binary / text based .frm file @@ -587,155 +546,130 @@ static bool has_disabled_path_chars(const char *str) NOTES This function is called when the table definition is not cached in - table_def_cache + table definition cache The data is returned in 'share', which is alloced by alloc_table_share().. The code assumes that share is initialized. - - RETURN VALUES - 0 ok - 1 Error (see open_table_error) - 2 Error (see open_table_error) - 3 Wrong data in .frm file - 4 Error (see open_table_error) - 5 Error (see open_table_error: charset unavailable) - 6 Unknown .frm version */ -int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags) +enum open_frm_error open_table_def(THD *thd, TABLE_SHARE *share, uint flags) { - int error, table_type; - bool error_given; + bool error_given= false; File file; - uchar head[64]; + uchar *buf; + uchar head[FRM_HEADER_SIZE]; char path[FN_REFLEN]; - MEM_ROOT **root_ptr, *old_root; + size_t frmlen, read_length; + uint length; DBUG_ENTER("open_table_def"); DBUG_PRINT("enter", ("table: '%s'.'%s' path: '%s'", share->db.str, share->table_name.str, share->normalized_path.str)); - error= 1; - error_given= 0; + share->error= OPEN_FRM_OPEN_ERROR; - strxmov(path, share->normalized_path.str, reg_ext, NullS); - if ((file= mysql_file_open(key_file_frm, - path, O_RDONLY | O_SHARE, MYF(0))) < 0) + length=(uint) (strxmov(path, share->normalized_path.str, reg_ext, NullS) - + path); + if (flags & GTS_FORCE_DISCOVERY) { - /* - We don't try to open 5.0 unencoded name, if - - non-encoded name contains '@' signs, - because '@' can be misinterpreted. - It is not clear if '@' is escape character in 5.1, - or a normal character in 5.0. - - - non-encoded db or table name contain "#mysql50#" prefix. - This kind of tables must have been opened only by the - mysql_file_open() above. - */ - if (has_disabled_path_chars(share->table_name.str) || - has_disabled_path_chars(share->db.str) || - !strncmp(share->db.str, MYSQL50_TABLE_NAME_PREFIX, - MYSQL50_TABLE_NAME_PREFIX_LENGTH) || - !strncmp(share->table_name.str, MYSQL50_TABLE_NAME_PREFIX, - MYSQL50_TABLE_NAME_PREFIX_LENGTH)) - goto err_not_open; - - /* Try unencoded 5.0 name */ - uint length; - strxnmov(path, sizeof(path)-1, - mysql_data_home, "/", share->db.str, "/", - share->table_name.str, reg_ext, NullS); - length= unpack_filename(path, path) - reg_ext_length; - /* - The following is a safety test and should never fail - as the old file name should never be longer than the new one. - */ - DBUG_ASSERT(length <= share->normalized_path.length); - /* - If the old and the new names have the same length, - then table name does not have tricky characters, - so no need to check the old file name. - */ - if (length == share->normalized_path.length || - ((file= mysql_file_open(key_file_frm, - path, O_RDONLY | O_SHARE, MYF(0))) < 0)) - goto err_not_open; + DBUG_ASSERT(flags & GTS_TABLE); + DBUG_ASSERT(flags & GTS_USE_DISCOVERY); + mysql_file_delete_with_symlink(key_file_frm, path, "", MYF(0)); + file= -1; + } + else + file= mysql_file_open(key_file_frm, path, O_RDONLY | O_SHARE, MYF(0)); - /* Unencoded 5.0 table name found */ - path[length]= '\0'; // Remove .frm extension - strmov(share->normalized_path.str, path); - share->normalized_path.length= length; + if (file < 0) + { + if ((flags & GTS_TABLE) && (flags & GTS_USE_DISCOVERY)) + { + ha_discover_table(thd, share); + error_given= true; + } + goto err_not_open; } - error= 4; - if (mysql_file_read(file, head, 64, MYF(MY_NABP))) + if (mysql_file_read(file, head, sizeof(head), MYF(MY_NABP))) + { + share->error = my_errno == HA_ERR_FILE_TOO_SHORT + ? OPEN_FRM_CORRUPTED : OPEN_FRM_READ_ERROR; goto err; + } - if (head[0] == (uchar) 254 && head[1] == 1) + if (memcmp(head, STRING_WITH_LEN("TYPE=VIEW\n")) == 0) { - if (head[2] == FRM_VER || head[2] == FRM_VER+1 || - (head[2] >= FRM_VER+3 && head[2] <= FRM_VER+4)) + share->is_view= 1; + if (flags & GTS_VIEW) { - /* Open view only */ - if (db_flags & OPEN_VIEW_ONLY) - { - error_given= 1; - goto err; - } - table_type= 1; + LEX_STRING pathstr= { path, length }; + /* + Create view file parser and hold it in TABLE_SHARE member + view_def. + */ + share->view_def= sql_parse_prepare(&pathstr, &share->mem_root, true); + if (!share->view_def) + share->error= OPEN_FRM_ERROR_ALREADY_ISSUED; + else + share->error= OPEN_FRM_OK; } else - { - error= 6; // Unkown .frm version - goto err; - } + share->error= OPEN_FRM_NOT_A_TABLE; + goto err; } - else if (memcmp(head, STRING_WITH_LEN("TYPE=")) == 0) + if (!is_binary_frm_header(head)) { - error= 5; - if (memcmp(head+5,"VIEW",4) == 0) - { - share->is_view= 1; - if (db_flags & OPEN_VIEW) - error= 0; - } + /* No handling of text based files yet */ + share->error = OPEN_FRM_CORRUPTED; goto err; } - else + if (!(flags & GTS_TABLE)) + { + share->error = OPEN_FRM_NOT_A_VIEW; + goto err; + } + + frmlen= uint4korr(head+10); + set_if_smaller(frmlen, FRM_MAX_SIZE); // safety + + if (!(buf= (uchar*)my_malloc(frmlen, MYF(MY_THREAD_SPECIFIC|MY_WME)))) goto err; - /* No handling of text based files yet */ - if (table_type == 1) + memcpy(buf, head, sizeof(head)); + + read_length= mysql_file_read(file, buf + sizeof(head), + frmlen - sizeof(head), MYF(MY_WME)); + if (read_length == 0 || read_length == (size_t)-1) { - root_ptr= my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC); - old_root= *root_ptr; - *root_ptr= &share->mem_root; - error= open_binary_frm(thd, share, head, file); - *root_ptr= old_root; - error_given= 1; + share->error = OPEN_FRM_READ_ERROR; + my_free(buf); + goto err; } + mysql_file_close(file, MYF(MY_WME)); + + frmlen= read_length + sizeof(head); - share->table_category= get_table_category(& share->db, & share->table_name); + share->init_from_binary_frm_image(thd, false, buf, frmlen); + error_given= true; // init_from_binary_frm_image has already called my_error() + my_free(buf); - if (!error) - thd->status_var.opened_shares++; + goto err_not_open; err: mysql_file_close(file, MYF(MY_WME)); err_not_open: - if (error && !error_given) + if (share->error && !error_given) { - share->error= error; - open_table_error(share, error, (share->open_errno= my_errno), 0); + share->open_errno= my_errno; + open_table_error(share, share->error, share->open_errno); } - DBUG_RETURN(error); + DBUG_RETURN(share->error); } - -static bool create_key_infos(uchar *strpos, uint keys, KEY *keyinfo, - uint new_frm_ver, - uint &ext_key_parts, TABLE_SHARE *share, uint len, +static bool create_key_infos(const uchar *strpos, const uchar *frm_image_end, + uint keys, KEY *keyinfo, + uint new_frm_ver, uint &ext_key_parts, + TABLE_SHARE *share, uint len, KEY *first_keyinfo, char* &keynames) { uint i, j, n_length; @@ -770,25 +704,29 @@ static bool create_key_infos(uchar *strpos, uint keys, KEY *keyinfo, { if (new_frm_ver >= 3) { + if (strpos + 8 >= frm_image_end) + return 1; keyinfo->flags= (uint) uint2korr(strpos) ^ HA_NOSAME; keyinfo->key_length= (uint) uint2korr(strpos+2); - keyinfo->key_parts= (uint) strpos[4]; + keyinfo->user_defined_key_parts= (uint) strpos[4]; keyinfo->algorithm= (enum ha_key_alg) strpos[5]; keyinfo->block_size= uint2korr(strpos+6); strpos+=8; } else { + if (strpos + 4 >= frm_image_end) + return 1; keyinfo->flags= ((uint) strpos[0]) ^ HA_NOSAME; keyinfo->key_length= (uint) uint2korr(strpos+1); - keyinfo->key_parts= (uint) strpos[3]; + keyinfo->user_defined_key_parts= (uint) strpos[3]; keyinfo->algorithm= HA_KEY_ALG_UNDEF; strpos+=4; } if (i == 0) { - ext_key_parts+= (share->use_ext_keys ? first_keyinfo->key_parts*(keys-1) : 0); + ext_key_parts+= (share->use_ext_keys ? first_keyinfo->user_defined_key_parts*(keys-1) : 0); n_length=keys * sizeof(KEY) + ext_key_parts * sizeof(KEY_PART_INFO); if (!(keyinfo= (KEY*) alloc_root(&share->mem_root, n_length + len))) @@ -801,10 +739,10 @@ static bool create_key_infos(uchar *strpos, uint keys, KEY *keyinfo, sizeof(ulong) * ext_key_parts))) return 1; first_key_part= key_part; - first_key_parts= first_keyinfo->key_parts; + first_key_parts= first_keyinfo->user_defined_key_parts; keyinfo->flags= first_keyinfo->flags; keyinfo->key_length= first_keyinfo->key_length; - keyinfo->key_parts= first_keyinfo->key_parts; + keyinfo->user_defined_key_parts= first_keyinfo->user_defined_key_parts; keyinfo->algorithm= first_keyinfo->algorithm; if (new_frm_ver >= 3) keyinfo->block_size= first_keyinfo->block_size; @@ -812,8 +750,10 @@ static bool create_key_infos(uchar *strpos, uint keys, KEY *keyinfo, keyinfo->key_part= key_part; keyinfo->rec_per_key= rec_per_key; - for (j=keyinfo->key_parts ; j-- ; key_part++) + for (j=keyinfo->user_defined_key_parts ; j-- ; key_part++) { + if (strpos + (new_frm_ver >= 1 ? 9 : 7) >= frm_image_end) + return 1; *rec_per_key++=0; key_part->fieldnr= (uint16) (uint2korr(strpos) & FIELD_NR_MASK); key_part->offset= (uint) uint2korr(strpos+2)-1; @@ -838,16 +778,21 @@ static bool create_key_infos(uchar *strpos, uint keys, KEY *keyinfo, } key_part->store_length=key_part->length; } - keyinfo->ext_key_parts= keyinfo->key_parts; + + /* + Add primary key to end of extended keys for non unique keys for + storage engines that supports it. + */ + keyinfo->ext_key_parts= keyinfo->user_defined_key_parts; keyinfo->ext_key_flags= keyinfo->flags; keyinfo->ext_key_part_map= 0; - if (share->use_ext_keys && i) + if (share->use_ext_keys && i && !(keyinfo->flags & HA_NOSAME)) { for (j= 0; j < first_key_parts && keyinfo->ext_key_parts < MAX_REF_PARTS; j++) { - uint key_parts= keyinfo->key_parts; + uint key_parts= keyinfo->user_defined_key_parts; KEY_PART_INFO* curr_key_part= keyinfo->key_part; KEY_PART_INFO* curr_key_part_end= curr_key_part+key_parts; for ( ; curr_key_part < curr_key_part_end; curr_key_part++) @@ -869,20 +814,28 @@ static bool create_key_infos(uchar *strpos, uint keys, KEY *keyinfo, share->ext_key_parts+= keyinfo->ext_key_parts; } keynames=(char*) key_part; - strpos+= (strmov(keynames, (char *) strpos) - keynames)+1; + strpos+= strnmov(keynames, (char *) strpos, frm_image_end - strpos) - keynames; + if (*strpos++) // key names are \0-terminated + return 1; //reading index comments for (keyinfo= share->key_info, i=0; i < keys; i++, keyinfo++) { if (keyinfo->flags & HA_USES_COMMENT) { + if (strpos + 2 >= frm_image_end) + return 1; keyinfo->comment.length= uint2korr(strpos); - keyinfo->comment.str= strmake_root(&share->mem_root, (char*) strpos+2, + strpos+= 2; + + if (strpos + keyinfo->comment.length >= frm_image_end) + return 1; + keyinfo->comment.str= strmake_root(&share->mem_root, (char*) strpos, keyinfo->comment.length); - strpos+= 2 + keyinfo->comment.length; + strpos+= keyinfo->comment.length; } - DBUG_ASSERT(test(keyinfo->flags & HA_USES_COMMENT) == - (keyinfo->comment.length > 0)); + DBUG_ASSERT(MY_TEST(keyinfo->flags & HA_USES_COMMENT) == + (keyinfo->comment.length > 0)); } share->keys= keys; // do it *after* all key_info's are initialized @@ -890,11 +843,13 @@ static bool create_key_infos(uchar *strpos, uint keys, KEY *keyinfo, return 0; } + /** ensures that the enum value (read from frm) is within limits if not - issues a warning and resets the value to 0 (that is, 0 is assumed to be a default value) */ + static uint enum_value_with_check(THD *thd, TABLE_SHARE *share, const char *name, uint value, uint limit) { @@ -907,31 +862,77 @@ static uint enum_value_with_check(THD *thd, TABLE_SHARE *share, } -/* - Read data from a binary .frm file from MySQL 3.23 - 5.0 into TABLE_SHARE +/** + Check if a collation has changed number + + @param mysql_version + @param current collation number + + @retval new collation number (same as current collation number of no change) */ -static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, - File file) +static uint upgrade_collation(ulong mysql_version, uint cs_number) { - int error, errarg= 0; + if (mysql_version >= 50300 && mysql_version <= 50399) + { + switch (cs_number) { + case 149: return MY_PAGE2_COLLATION_ID_UCS2; // ucs2_crotian_ci + case 213: return MY_PAGE2_COLLATION_ID_UTF8; // utf8_crotian_ci + } + } + if ((mysql_version >= 50500 && mysql_version <= 50599) || + (mysql_version >= 100000 && mysql_version <= 100005)) + { + switch (cs_number) { + case 149: return MY_PAGE2_COLLATION_ID_UCS2; // ucs2_crotian_ci + case 213: return MY_PAGE2_COLLATION_ID_UTF8; // utf8_crotian_ci + case 214: return MY_PAGE2_COLLATION_ID_UTF32; // utf32_croatian_ci + case 215: return MY_PAGE2_COLLATION_ID_UTF16; // utf16_croatian_ci + case 245: return MY_PAGE2_COLLATION_ID_UTF8MB4;// utf8mb4_croatian_ci + } + } + return cs_number; +} + + +/** + Read data from a binary .frm file image into a TABLE_SHARE + + @note + frm bytes at the following offsets are unused in MariaDB 10.0: + + 8..9 (used to be the number of "form names") + 28..29 (used to be key_info_length) + + They're still set, for compatibility reasons, but never read. + + 42..46 are unused since 5.0 (were for RAID support) + Also, there're few unused bytes in forminfo. + +*/ + +int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, + const uchar *frm_image, + size_t frm_length) +{ + TABLE_SHARE *share= this; uint new_frm_ver, field_pack_length, new_field_pack_flag; uint interval_count, interval_parts, read_length, int_length; uint db_create_options, keys, key_parts, n_length; - uint key_info_length, com_length, null_bit_pos; + uint com_length, null_bit_pos; uint extra_rec_buf_length; uint i; bool use_hash; char *keynames, *names, *comment_pos; - uchar forminfo[288]; - uchar *record; - uchar *disk_buff, *strpos, *null_flags, *null_pos; - ulong pos, record_offset; + const uchar *forminfo, *extra2; + const uchar *frm_image_end = frm_image + frm_length; + uchar *record, *null_flags, *null_pos; + const uchar *disk_buff, *strpos; + ulong pos, record_offset; ulong rec_buff_length; handler *handler_file= 0; KEY *keyinfo; KEY_PART_INFO *key_part= NULL; - SQL_CRYPT *crypted=0; Field **field_ptr, *reg_field; const char **interval_array; enum legacy_db_type legacy_db_type; @@ -939,80 +940,171 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, bool null_bits_are_used; uint vcol_screen_length, UNINIT_VAR(options_len); char *vcol_screen_pos; - uchar *UNINIT_VAR(options); - uchar *extra_segment_buff= 0; + const uchar *options= 0; + uint UNINIT_VAR(gis_options_len); + const uchar *gis_options= 0; KEY first_keyinfo; uint len; uint ext_key_parts= 0; + plugin_ref se_plugin= 0; keyinfo= &first_keyinfo; share->ext_key_parts= 0; - DBUG_ENTER("open_binary_frm"); + MEM_ROOT **root_ptr, *old_root; + DBUG_ENTER("TABLE_SHARE::init_from_binary_frm_image"); + + root_ptr= my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC); + old_root= *root_ptr; + *root_ptr= &share->mem_root; - new_field_pack_flag= head[27]; - new_frm_ver= (head[2] - FRM_VER); + if (write && write_frm_image(frm_image, frm_length)) + goto err; + + if (frm_length < FRM_HEADER_SIZE + FRM_FORMINFO_SIZE) + goto err; + + new_field_pack_flag= frm_image[27]; + new_frm_ver= (frm_image[2] - FRM_VER); field_pack_length= new_frm_ver < 2 ? 11 : 17; - disk_buff= 0; - error= 3; - /* Position of the form in the form file. */ - if (!(pos= get_form_pos(file, head))) - goto err; /* purecov: inspected */ + /* Length of the MariaDB extra2 segment in the form file. */ + len = uint2korr(frm_image+4); + extra2= frm_image + 64; + + if (*extra2 != '/') // old frm had '/' there + { + const uchar *e2end= extra2 + len; + while (extra2 + 3 < e2end) + { + uchar type= *extra2++; + size_t length= *extra2++; + if (!length) + { + if (extra2 + 2 >= e2end) + goto err; + length= uint2korr(extra2); + extra2+= 2; + if (length < 256) + goto err; + } + if (extra2 + length > e2end) + goto err; + switch (type) { + case EXTRA2_TABLEDEF_VERSION: + if (tabledef_version.str) // see init_from_sql_statement_string() + { + if (length != tabledef_version.length || + memcmp(extra2, tabledef_version.str, length)) + goto err; + } + else + { + tabledef_version.length= length; + tabledef_version.str= (uchar*)memdup_root(&mem_root, extra2, length); + if (!tabledef_version.str) + goto err; + } + break; + case EXTRA2_ENGINE_TABLEOPTS: + if (options) + goto err; + /* remember but delay parsing until we have read fields and keys */ + options= extra2; + options_len= length; + break; + case EXTRA2_DEFAULT_PART_ENGINE: +#ifdef WITH_PARTITION_STORAGE_ENGINE + { + LEX_STRING name= { (char*)extra2, length }; + share->default_part_plugin= ha_resolve_by_name(NULL, &name, false); + if (!share->default_part_plugin) + goto err; + } +#endif + break; + case EXTRA2_GIS: +#ifdef HAVE_SPATIAL + { + if (gis_options) + goto err; + gis_options= extra2; + gis_options_len= length; + } +#endif /*HAVE_SPATIAL*/ + break; + default: + /* abort frm parsing if it's an unknown but important extra2 value */ + if (type >= EXTRA2_ENGINE_IMPORTANT) + goto err; + } + extra2+= length; + } + if (extra2 != e2end) + goto err; + } + + if (frm_length < FRM_HEADER_SIZE + len || + !(pos= uint4korr(frm_image + FRM_HEADER_SIZE + len))) + goto err; - mysql_file_seek(file,pos,MY_SEEK_SET,MYF(0)); - if (mysql_file_read(file, forminfo,288,MYF(MY_NABP))) + forminfo= frm_image + pos; + if (forminfo + FRM_FORMINFO_SIZE >= frm_image_end) goto err; - share->frm_version= head[2]; + + share->frm_version= frm_image[2]; /* Check if .frm file created by MySQL 5.0. In this case we want to display CHAR fields as CHAR and not as VARCHAR. We do it this way as we want to keep the old frm version to enable MySQL 4.1 to read these files. */ - if (share->frm_version == FRM_VER_TRUE_VARCHAR -1 && head[33] == 5) + if (share->frm_version == FRM_VER_TRUE_VARCHAR -1 && frm_image[33] == 5) share->frm_version= FRM_VER_TRUE_VARCHAR; #ifdef WITH_PARTITION_STORAGE_ENGINE - /* - Yuck! Double-bad. Doesn't work with dynamic engine codes. - And doesn't lock the plugin. Fixed in 10.0.4 - */ - compile_time_assert(MYSQL_VERSION_ID < 100000); - if (*(head+61) && - !(share->default_part_db_type= - ha_checktype(thd, (enum legacy_db_type) (uint) *(head+61), 1, 0))) - goto err; - DBUG_PRINT("info", ("default_part_db_type = %u", head[61])); + if (frm_image[61] && !share->default_part_plugin) + { + enum legacy_db_type db_type= (enum legacy_db_type) (uint) frm_image[61]; + share->default_part_plugin= ha_lock_engine(NULL, ha_checktype(thd, db_type)); + if (!share->default_part_plugin) + goto err; + } #endif - legacy_db_type= (enum legacy_db_type) (uint) *(head+3); - DBUG_ASSERT(share->db_plugin == NULL); + legacy_db_type= (enum legacy_db_type) (uint) frm_image[3]; /* if the storage engine is dynamic, no point in resolving it by its dynamically allocated legacy_db_type. We will resolve it later by name. */ if (legacy_db_type > DB_TYPE_UNKNOWN && legacy_db_type < DB_TYPE_FIRST_DYNAMIC) - share->db_plugin= ha_lock_engine(NULL, - ha_checktype(thd, legacy_db_type, 0, 0)); - share->db_create_options= db_create_options= uint2korr(head+30); + se_plugin= ha_lock_engine(NULL, ha_checktype(thd, legacy_db_type)); + share->db_create_options= db_create_options= uint2korr(frm_image+30); share->db_options_in_use= share->db_create_options; - share->mysql_version= uint4korr(head+51); + share->mysql_version= uint4korr(frm_image+51); share->null_field_first= 0; - if (!head[32]) // New frm file in 3.23 + if (!frm_image[32]) // New frm file in 3.23 { - share->avg_row_length= uint4korr(head+34); + uint cs_org= (((uint) frm_image[41]) << 8) + (uint) frm_image[38]; + uint cs_new= upgrade_collation(share->mysql_version, cs_org); + if (cs_org != cs_new) + share->incompatible_version|= HA_CREATE_USED_CHARSET; + + share->avg_row_length= uint4korr(frm_image+34); share->transactional= (ha_choice) - enum_value_with_check(thd, share, "transactional", (head[39] & 3), HA_CHOICE_MAX); + enum_value_with_check(thd, share, "transactional", frm_image[39] & 3, HA_CHOICE_MAX); share->page_checksum= (ha_choice) - enum_value_with_check(thd, share, "page_checksum", (head[39] >> 2) & 3, HA_CHOICE_MAX); - share->row_type= (row_type) - enum_value_with_check(thd, share, "row_format", head[40], ROW_TYPE_MAX); - share->table_charset= get_charset((((uint) head[41]) << 8) + - (uint) head[38],MYF(0)); + enum_value_with_check(thd, share, "page_checksum", (frm_image[39] >> 2) & 3, HA_CHOICE_MAX); + share->row_type= (enum row_type) + enum_value_with_check(thd, share, "row_format", frm_image[40], ROW_TYPE_MAX); + + if (cs_new && !(share->table_charset= get_charset(cs_new, MYF(MY_WME)))) + goto err; share->null_field_first= 1; + share->stats_sample_pages= uint2korr(frm_image+42); + share->stats_auto_recalc= (enum_stats_auto_recalc)(frm_image[44]); } if (!share->table_charset) { - /* unknown charset in head[38] or pre-3.23 frm */ + /* unknown charset in frm_image[38] or pre-3.23 frm */ if (use_mb(default_charset_info)) { /* Warn that we may be changing the size of character columns */ @@ -1023,18 +1115,17 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, } share->table_charset= default_charset_info; } + share->db_record_offset= 1; - if (db_create_options & HA_OPTION_LONG_BLOB_PTR) - share->blob_ptr_size= portable_sizeof_char_ptr; - error=4; - share->max_rows= uint4korr(head+18); - share->min_rows= uint4korr(head+22); + share->max_rows= uint4korr(frm_image+18); + share->min_rows= uint4korr(frm_image+22); /* Read keyinformation */ - key_info_length= (uint) uint2korr(head+28); - mysql_file_seek(file, (ulong) uint2korr(head+6), MY_SEEK_SET, MYF(0)); - if (read_string(file,(uchar**) &disk_buff,key_info_length)) - goto err; /* purecov: inspected */ + disk_buff= frm_image + uint2korr(frm_image+6); + + if (disk_buff + 6 >= frm_image_end) + goto err; + if (disk_buff[0] & 0x80) { keys= (disk_buff[1] << 7) | (disk_buff[0] & 0x7f); @@ -1051,36 +1142,29 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, len= (uint) uint2korr(disk_buff+4); - share->reclength = uint2korr((head+16)); + share->reclength = uint2korr(frm_image+16); share->stored_rec_length= share->reclength; - if (*(head+26) == 1) + if (frm_image[26] == 1) share->system= 1; /* one-record-database */ -#ifdef HAVE_CRYPTED_FRM - else if (*(head+26) == 2) - { - crypted= get_crypt_for_frm(); - share->crypted= 1; - } -#endif - record_offset= (ulong) (uint2korr(head+6)+ - ((uint2korr(head+14) == 0xffff ? - uint4korr(head+47) : uint2korr(head+14)))); + record_offset= (ulong) (uint2korr(frm_image+6)+ + ((uint2korr(frm_image+14) == 0xffff ? + uint4korr(frm_image+47) : uint2korr(frm_image+14)))); + + if (record_offset + share->reclength >= frm_length) + goto err; - if ((n_length= uint4korr(head+55))) + if ((n_length= uint4korr(frm_image+55))) { /* Read extra data segment */ - uchar *next_chunk, *buff_end; + const uchar *next_chunk, *buff_end; DBUG_PRINT("info", ("extra segment size is %u bytes", n_length)); - if (!(extra_segment_buff= (uchar*) my_malloc(n_length + 1, MYF(MY_WME)))) - goto err; - next_chunk= extra_segment_buff; - if (mysql_file_pread(file, extra_segment_buff, - n_length, record_offset + share->reclength, - MYF(MY_NABP))) - { + next_chunk= frm_image + record_offset + share->reclength; + buff_end= next_chunk + n_length; + + if (buff_end >= frm_image_end) goto err; - } + share->connect_string.length= uint2korr(next_chunk); if (!(share->connect_string.str= strmake_root(&share->mem_root, (char*) next_chunk + 2, @@ -1090,7 +1174,6 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, goto err; } next_chunk+= share->connect_string.length + 2; - buff_end= extra_segment_buff + n_length; if (next_chunk + 2 < buff_end) { uint str_db_type_length= uint2korr(next_chunk); @@ -1098,13 +1181,10 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, name.str= (char*) next_chunk + 2; name.length= str_db_type_length; - plugin_ref tmp_plugin= ha_resolve_by_name(thd, &name); - if (tmp_plugin != NULL && !plugin_equals(tmp_plugin, share->db_plugin)) + plugin_ref tmp_plugin= ha_resolve_by_name(thd, &name, false); + if (tmp_plugin != NULL && !plugin_equals(tmp_plugin, se_plugin)) { - if (legacy_db_type > DB_TYPE_UNKNOWN && - legacy_db_type < DB_TYPE_FIRST_DYNAMIC && - legacy_db_type != ha_legacy_type( - plugin_data(tmp_plugin, handlerton *))) + if (se_plugin) { /* bad file, legacy_db_type did not match the name */ sql_print_warning("%s.frm is inconsistent: engine typecode %d, engine name %s (%d)", @@ -1114,14 +1194,11 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, } /* tmp_plugin is locked with a local lock. - we unlock the old value of share->db_plugin before + we unlock the old value of se_plugin before replacing it with a globally locked version of tmp_plugin */ - plugin_unlock(NULL, share->db_plugin); - share->db_plugin= my_plugin_lock(NULL, tmp_plugin); - DBUG_PRINT("info", ("setting dbtype to '%.*s' (%d)", - str_db_type_length, next_chunk + 2, - ha_legacy_type(share->db_type()))); + plugin_unlock(NULL, se_plugin); + se_plugin= plugin_lock(NULL, tmp_plugin); } #ifdef WITH_PARTITION_STORAGE_ENGINE else if (str_db_type_length == 9 && @@ -1130,28 +1207,23 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, /* Use partition handler tmp_plugin is locked with a local lock. - we unlock the old value of share->db_plugin before + we unlock the old value of se_plugin before replacing it with a globally locked version of tmp_plugin */ /* Check if the partitioning engine is ready */ if (!plugin_is_ready(&name, MYSQL_STORAGE_ENGINE_PLUGIN)) { - error= 8; my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-partition"); goto err; } - plugin_unlock(NULL, share->db_plugin); - share->db_plugin= ha_lock_engine(NULL, partition_hton); - DBUG_PRINT("info", ("setting dbtype to '%.*s' (%d)", - str_db_type_length, next_chunk + 2, - ha_legacy_type(share->db_type()))); + plugin_unlock(NULL, se_plugin); + se_plugin= ha_lock_engine(NULL, partition_hton); } #endif else if (!tmp_plugin) { /* purecov: begin inspected */ - error= 8; name.str[name.length]=0; my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), name.str); goto err; @@ -1160,10 +1232,10 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, next_chunk+= str_db_type_length + 2; } - share->set_use_ext_keys_flag(share->db_type()->flags & HTON_EXTENDED_KEYS); + share->set_use_ext_keys_flag(plugin_hton(se_plugin)->flags & HTON_SUPPORTS_EXTENDED_KEYS); - if (create_key_infos(disk_buff + 6, keys, keyinfo, new_frm_ver, - ext_key_parts, + if (create_key_infos(disk_buff + 6, frm_image_end, keys, keyinfo, + new_frm_ver, ext_key_parts, share, len, &first_keyinfo, keynames)) goto err; @@ -1243,12 +1315,10 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, DBUG_ASSERT(next_chunk <= buff_end); - if (share->db_create_options & HA_OPTION_TEXT_CREATE_OPTIONS) + if (share->db_create_options & HA_OPTION_TEXT_CREATE_OPTIONS_legacy) { - /* - store options position, but skip till the time we will - know number of fields - */ + if (options) + goto err; options_len= uint4korr(next_chunk); options= next_chunk + 4; next_chunk+= options_len + 4; @@ -1257,16 +1327,18 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, } else { - if (create_key_infos(disk_buff + 6, keys, keyinfo, new_frm_ver, - ext_key_parts, + if (create_key_infos(disk_buff + 6, frm_image_end, keys, keyinfo, + new_frm_ver, ext_key_parts, share, len, &first_keyinfo, keynames)) goto err; } - share->key_block_size= uint2korr(head+62); + share->key_block_size= uint2korr(frm_image+62); - error=4; - extra_rec_buf_length= uint2korr(head+59); + if (share->db_plugin && !plugin_equals(share->db_plugin, se_plugin)) + goto err; // wrong engine (someone changed the frm under our feet?) + + extra_rec_buf_length= uint2korr(frm_image+59); rec_buff_length= ALIGN_SIZE(share->reclength + 1 + extra_rec_buf_length); share->rec_buff_length= rec_buff_length; if (!(record= (uchar *) alloc_root(&share->mem_root, rec_buff_length))) @@ -1274,19 +1346,9 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, MEM_NOACCESS(record, rec_buff_length); MEM_UNDEFINED(record, share->reclength); share->default_values= record; - if (mysql_file_pread(file, record, (size_t) share->reclength, - record_offset, MYF(MY_NABP))) - goto err; /* purecov: inspected */ + memcpy(record, frm_image + record_offset, share->reclength); - mysql_file_seek(file, pos+288, MY_SEEK_SET, MYF(0)); -#ifdef HAVE_CRYPTED_FRM - if (crypted) - { - crypted->decode((char*) forminfo+256,288-256); - if (sint2korr(forminfo+284) != 0) // Should be 0 - goto err; // Wrong password - } -#endif + disk_buff= frm_image + pos + FRM_FORMINFO_SIZE; share->fields= uint2korr(forminfo+258); pos= uint2korr(forminfo+260); /* Length of all screens */ @@ -1298,6 +1360,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, com_length= uint2korr(forminfo+284); vcol_screen_length= uint2korr(forminfo+286); share->vfields= 0; + share->default_fields= 0; share->stored_fields= share->fields; if (forminfo[46] != (uchar)255) { @@ -1323,16 +1386,6 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, read_length=(uint) (share->fields * field_pack_length + pos+ (uint) (n_length+int_length+com_length+ vcol_screen_length)); - if (read_string(file,(uchar**) &disk_buff,read_length)) - goto err; /* purecov: inspected */ -#ifdef HAVE_CRYPTED_FRM - if (crypted) - { - crypted->decode((char*) disk_buff,read_length); - delete crypted; - crypted=0; - } -#endif strpos= disk_buff+pos; share->intervals= (TYPELIB*) (field_ptr+share->fields+1); @@ -1380,14 +1433,17 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, /* Allocate handler */ if (!(handler_file= get_new_handler(share, thd->mem_root, - share->db_type()))) + plugin_hton(se_plugin)))) + goto err; + + if (handler_file->set_ha_share_ref(&share->ha_share)) goto err; record= share->default_values-1; /* Fieldstart = 1 */ null_bits_are_used= share->null_fields != 0; if (share->null_field_first) { - null_flags= null_pos= (uchar*) record+1; + null_flags= null_pos= record+1; null_bit_pos= (db_create_options & HA_OPTION_PACK_RECORD) ? 0 : 1; /* null_bytes below is only correct under the condition that @@ -1400,8 +1456,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, else { share->null_bytes= (share->null_fields+7)/8; - null_flags= null_pos= (uchar*) (record + 1 +share->reclength - - share->null_bytes); + null_flags= null_pos= record + 1 + share->reclength - share->null_bytes; null_bit_pos= 0; } #endif @@ -1424,6 +1479,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, LEX_STRING comment; Virtual_column_info *vcol_info= 0; bool fld_stored_in_db= TRUE; + uint gis_length, gis_decimals, srid= 0; if (new_frm_ver >= 3) { @@ -1440,22 +1496,38 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if (field_type == MYSQL_TYPE_GEOMETRY) { #ifdef HAVE_SPATIAL + uint gis_opt_read; + Field_geom::storage_type st_type; geom_type= (Field::geometry_type) strpos[14]; charset= &my_charset_bin; + gis_opt_read= gis_field_options_read(gis_options, gis_options_len, + &st_type, &gis_length, &gis_decimals, &srid); + gis_options+= gis_opt_read; + gis_options_len-= gis_opt_read; #else - error= 4; // unsupported field type goto err; #endif } else { - uint csid= strpos[14] + (((uint) strpos[11]) << 8); - if (!csid) + uint cs_org= strpos[14] + (((uint) strpos[11]) << 8); + uint cs_new= upgrade_collation(share->mysql_version, cs_org); + if (cs_org != cs_new) + share->incompatible_version|= HA_CREATE_USED_CHARSET; + if (!cs_new) charset= &my_charset_bin; - else if (!(charset= get_charset(csid, MYF(0)))) + else if (!(charset= get_charset(cs_new, MYF(0)))) { - error= 5; // Unknown or unavailable charset - errarg= (int) csid; + const char *csname= get_charset_name((uint) cs_new); + char tmp[10]; + if (!csname || csname[0] =='?') + { + my_snprintf(tmp, sizeof(tmp), "#%d", cs_new); + csname= tmp; + } + my_printf_error(ER_UNKNOWN_COLLATION, + "Unknown collation '%s' in table '%-.64s' definition", + MYF(0), csname, share->table_name.str); goto err; } } @@ -1463,10 +1535,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if ((uchar)field_type == (uchar)MYSQL_TYPE_VIRTUAL) { if (!interval_nr) // Expect non-null expression - { - error= 4; goto err; - } /* The interval_id byte in the .frm file stores the length of the expression statement for a virtual column. @@ -1503,10 +1572,8 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if (opt_interval_id) interval_nr= (uint)vcol_screen_pos[3]; else if ((uint)vcol_screen_pos[0] != 1) - { - error= 4; goto err; - } + fld_stored_in_db= (bool) (uint) vcol_screen_pos[2]; vcol_expr_length= vcol_info_length - (uint)(FRM_VCOL_HEADER_SIZE(opt_interval_id)); @@ -1580,7 +1647,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, "Please do \"ALTER TABLE '%s' FORCE\" to fix it!", share->fieldnames.type_names[i], share->table_name.str, share->table_name.str); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_CRASHED_ON_USAGE, "Found incompatible DECIMAL field '%s' in %s; " "Please do \"ALTER TABLE '%s' FORCE\" to fix it!", @@ -1592,23 +1659,21 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, #endif *field_ptr= reg_field= - make_field(share, record+recpos, + make_field(share, &share->mem_root, record+recpos, (uint32) field_length, null_pos, null_bit_pos, pack_flag, field_type, charset, - geom_type, + geom_type, srid, (Field::utype) MTYP_TYPENR(unireg_type), (interval_nr ? share->intervals+interval_nr-1 : (TYPELIB*) 0), share->fieldnames.type_names[i]); if (!reg_field) // Not supported field type - { - error= 4; - goto err; /* purecov: inspected */ - } + goto err; + reg_field->field_index= i; reg_field->comment=comment; @@ -1633,29 +1698,18 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if (reg_field->unireg_check == Field::NEXT_NUMBER) share->found_next_number_field= field_ptr; - if (share->timestamp_field == reg_field) - share->timestamp_field_offset= i; - if (use_hash) - { - if (my_hash_insert(&share->name_hash, - (uchar*) field_ptr)) - { - /* - Set return code 8 here to indicate that an error has - occurred but that the error message already has been - sent (OOM). - */ - error= 8; - goto err; - } - } + if (use_hash && my_hash_insert(&share->name_hash, (uchar*) field_ptr)) + goto err; if (!reg_field->stored_in_db) { share->stored_fields--; if (share->stored_rec_length>=recpos) share->stored_rec_length= recpos-1; } + if (reg_field->has_insert_default_function() || + reg_field->has_update_default_function()) + ++share->default_fields; } *field_ptr=0; // End marker /* Sanity checks: */ @@ -1666,10 +1720,45 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if (key_parts) { uint add_first_key_parts= 0; - uint primary_key=(uint) (find_type(primary_key_name, &share->keynames, - FIND_TYPE_NO_PREFIX) - 1); longlong ha_option= handler_file->ha_table_flags(); keyinfo= share->key_info; + uint primary_key= my_strcasecmp(system_charset_info, share->keynames.type_names[0], + primary_key_name) ? MAX_KEY : 0; + KEY* key_first_info= NULL; + + if (primary_key >= MAX_KEY && keyinfo->flags & HA_NOSAME) + { + /* + If the UNIQUE key doesn't have NULL columns and is not a part key + declare this as a primary key. + */ + primary_key= 0; + key_part= keyinfo->key_part; + for (i=0 ; i < keyinfo->user_defined_key_parts ;i++) + { + DBUG_ASSERT(key_part[i].fieldnr > 0); + // Table field corresponding to the i'th key part. + Field *table_field= share->field[key_part[i].fieldnr - 1]; + + /* + If the key column is of NOT NULL BLOB type, then it + will definitly have key prefix. And if key part prefix size + is equal to the BLOB column max size, then we can promote + it to primary key. + */ + if (!table_field->real_maybe_null() && + table_field->type() == MYSQL_TYPE_BLOB && + table_field->field_length == key_part[i].length) + continue; + + if (table_field->real_maybe_null() || + table_field->key_length() != key_part[i].length) + { + primary_key= MAX_KEY; // Can't be used + break; + } + } + } if (share->use_ext_keys) { @@ -1680,12 +1769,12 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, } else { - add_first_key_parts= first_keyinfo.key_parts; + add_first_key_parts= first_keyinfo.user_defined_key_parts; /* Do not add components of the primary key starting from the major component defined over the beginning of a field. */ - for (i= 0; i < first_keyinfo.key_parts; i++) + for (i= 0; i < first_keyinfo.user_defined_key_parts; i++) { uint fieldnr= keyinfo[0].key_part[i].fieldnr; if (share->field[fieldnr-1]->key_length() != @@ -1715,34 +1804,72 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, keyinfo->name_length+1); } + if (!key) + key_first_info= keyinfo; + if (ext_key_parts > share->key_parts && key) { KEY_PART_INFO *new_key_part= (keyinfo-1)->key_part + (keyinfo-1)->ext_key_parts; + uint add_keyparts_for_this_key= add_first_key_parts; + uint length_bytes= 0, len_null_byte= 0, ext_key_length= 0; + Field *field; /* Do not extend the key that contains a component defined over the beginning of a field. */ - for (i= 0; i < keyinfo->key_parts; i++) - { + for (i= 0; i < keyinfo->user_defined_key_parts; i++) + { uint fieldnr= keyinfo->key_part[i].fieldnr; + field= share->field[keyinfo->key_part[i].fieldnr-1]; + + if (field->null_ptr) + len_null_byte= HA_KEY_NULL_LENGTH; + + if (field->type() == MYSQL_TYPE_BLOB || + field->real_type() == MYSQL_TYPE_VARCHAR || + field->type() == MYSQL_TYPE_GEOMETRY) + { + length_bytes= HA_KEY_BLOB_LENGTH; + } + + ext_key_length+= keyinfo->key_part[i].length + len_null_byte + + length_bytes; if (share->field[fieldnr-1]->key_length() != keyinfo->key_part[i].length) { - add_first_key_parts= 0; + add_keyparts_for_this_key= 0; break; } } - if (add_first_key_parts < keyinfo->ext_key_parts-keyinfo->key_parts) - { + if (add_keyparts_for_this_key) + { + for (i= 0; i < add_keyparts_for_this_key; i++) + { + uint pk_part_length= key_first_info->key_part[i].store_length; + if (keyinfo->ext_key_part_map & 1<<i) + { + if (ext_key_length + pk_part_length > MAX_DATA_LENGTH_FOR_KEY) + { + add_keyparts_for_this_key= i; + break; + } + ext_key_length+= pk_part_length; + } + } + } + + if (add_keyparts_for_this_key < keyinfo->ext_key_parts - + keyinfo->user_defined_key_parts) + { share->ext_key_parts-= keyinfo->ext_key_parts; key_part_map ext_key_part_map= keyinfo->ext_key_part_map; - keyinfo->ext_key_parts= keyinfo->key_parts; + keyinfo->ext_key_parts= keyinfo->user_defined_key_parts; keyinfo->ext_key_flags= keyinfo->flags; keyinfo->ext_key_part_map= 0; - for (i= 0; i < add_first_key_parts; i++) + for (i= 0; i < add_keyparts_for_this_key; i++) { if (ext_key_part_map & 1<<i) { @@ -1764,43 +1891,9 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if (share->key_info[key].flags & HA_FULLTEXT) share->key_info[key].algorithm= HA_KEY_ALG_FULLTEXT; - if (primary_key >= MAX_KEY && (keyinfo->flags & HA_NOSAME)) - { - /* - If the UNIQUE key doesn't have NULL columns and is not a part key - declare this as a primary key. - */ - primary_key=key; - key_part= keyinfo->key_part; - for (i=0 ; i < keyinfo->key_parts ;i++) - { - DBUG_ASSERT(key_part[i].fieldnr > 0); - // Table field corresponding to the i'th key part. - Field *table_field= share->field[key_part[i].fieldnr - 1]; - - /* - If the key column is of NOT NULL BLOB type, then it - will definitly have key prefix. And if key part prefix size - is equal to the BLOB column max size, then we can promote - it to primary key. - */ - if (!table_field->real_maybe_null() && - table_field->type() == MYSQL_TYPE_BLOB && - table_field->field_length == key_part[i].length) - continue; - - if (table_field->real_maybe_null() || - table_field->key_length() != key_part[i].length) - { - primary_key= MAX_KEY; // Can't be used - break; - } - } - } - key_part= keyinfo->key_part; uint key_parts= share->use_ext_keys ? keyinfo->ext_key_parts : - keyinfo->key_parts; + keyinfo->user_defined_key_parts; for (i=0; i < key_parts; key_part++, i++) { Field *field; @@ -1810,10 +1903,8 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, (uint) key_part->offset, (uint) key_part->length); if (!key_part->fieldnr) - { - error= 4; // Wrong file goto err; - } + field= key_part->field= share->field[key_part->fieldnr-1]; key_part->type= field->key_type(); if (field->null_ptr) @@ -1842,7 +1933,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if (i == 0 && key != primary_key) field->flags |= (((keyinfo->flags & HA_NOSAME) && - (keyinfo->key_parts == 1)) ? + (keyinfo->user_defined_key_parts == 1)) ? UNIQUE_KEY_FLAG : MULTIPLE_KEY_FLAG); if (i == 0) field->key_start.set_bit(key); @@ -1853,7 +1944,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, { share->keys_for_keyread.set_bit(key); field->part_of_key.set_bit(key); - if (i < keyinfo->key_parts) + if (i < keyinfo->user_defined_key_parts) field->part_of_key_not_clustered.set_bit(key); } if (handler_file->index_flags(key, i, 1) & HA_READ_ORDER) @@ -1899,7 +1990,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, "Please do \"ALTER TABLE '%s' FORCE \" to fix it!", share->table_name.str, share->table_name.str); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_CRASHED_ON_USAGE, "Found wrong key definition in %s; " "Please do \"ALTER TABLE '%s' FORCE\" to fix " @@ -1927,7 +2018,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, keyinfo->usable_key_parts= usable_parts; // Filesort set_if_bigger(share->max_key_length,keyinfo->key_length+ - keyinfo->key_parts); + keyinfo->user_defined_key_parts); share->total_key_length+= keyinfo->key_length; /* MERGE tables do not have unique indexes. But every key could be @@ -1945,7 +2036,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, If we are using an integer as the primary key then allow the user to refer to it as '_rowid' */ - if (share->key_info[primary_key].key_parts == 1) + if (share->key_info[primary_key].user_defined_key_parts == 1) { Field *field= share->key_info[primary_key].key_part[0].field; if (field && field->result_type() == INT_RESULT) @@ -1961,8 +2052,6 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, } else share->primary_key= MAX_KEY; - my_free(disk_buff); - disk_buff=0; if (new_field_pack_flag <= 1) { /* Old file format with default as not null */ @@ -1971,7 +2060,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, null_length, 255); } - if (share->db_create_options & HA_OPTION_TEXT_CREATE_OPTIONS) + if (options) { DBUG_ASSERT(options_len); if (engine_table_options_frm_read(options, options_len, share)) @@ -1988,13 +2077,8 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, share->default_values, reg_field, &share->next_number_key_offset, &share->next_number_keypart)) < 0) - { - /* Wrong field definition */ - error= 4; - goto err; - } - else - reg_field->flags |= AUTO_INCREMENT_FLAG; + goto err; // Wrong field definition + reg_field->flags |= AUTO_INCREMENT_FLAG; } if (share->blob_fields) @@ -2030,7 +2114,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if (!(bitmaps= (my_bitmap_map*) alloc_root(&share->mem_root, share->column_bitmap_size))) goto err; - bitmap_init(&share->all_set, bitmaps, share->fields, FALSE); + my_bitmap_init(&share->all_set, bitmaps, share->fields, FALSE); bitmap_set_all(&share->all_set); delete handler_file; @@ -2038,34 +2122,193 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if (use_hash) (void) my_hash_check(&share->name_hash); #endif - my_free(extra_segment_buff); - DBUG_RETURN (0); + + share->db_plugin= se_plugin; + share->error= OPEN_FRM_OK; + thd->status_var.opened_shares++; + *root_ptr= old_root; + DBUG_RETURN(0); err: - share->error= error; + share->error= OPEN_FRM_CORRUPTED; share->open_errno= my_errno; - share->errarg= errarg; - my_free(disk_buff); - my_free(extra_segment_buff); - delete crypted; delete handler_file; + plugin_unlock(0, se_plugin); my_hash_free(&share->name_hash); - if (share->ha_data_destroy) + + if (!thd->is_error()) + open_table_error(share, OPEN_FRM_CORRUPTED, share->open_errno); + + *root_ptr= old_root; + DBUG_RETURN(HA_ERR_NOT_A_TABLE); +} + + +static bool sql_unusable_for_discovery(THD *thd, handlerton *engine, + const char *sql) +{ + LEX *lex= thd->lex; + HA_CREATE_INFO *create_info= &lex->create_info; + + // ... not CREATE TABLE + if (lex->sql_command != SQLCOM_CREATE_TABLE) + return 1; + // ... create like + if (lex->create_info.like()) + return 1; + // ... create select + if (lex->select_lex.item_list.elements) + return 1; + // ... temporary + if (create_info->tmp_table()) + return 1; + // ... if exists + if (lex->create_info.if_not_exists()) + return 1; + + // XXX error out or rather ignore the following: + // ... partitioning + if (lex->part_info) + return 1; + // ... union + if (create_info->used_fields & HA_CREATE_USED_UNION) + return 1; + // ... index/data directory + if (create_info->data_file_name || create_info->index_file_name) + return 1; + // ... engine + if (create_info->db_type && create_info->db_type != engine) + return 1; + + return 0; +} + +int TABLE_SHARE::init_from_sql_statement_string(THD *thd, bool write, + const char *sql, size_t sql_length) +{ + ulonglong saved_mode= thd->variables.sql_mode; + CHARSET_INFO *old_cs= thd->variables.character_set_client; + Parser_state parser_state; + bool error; + char *sql_copy; + handler *file; + LEX *old_lex; + Query_arena *arena, backup; + LEX tmp_lex; + KEY *unused1; + uint unused2; + handlerton *hton= plugin_hton(db_plugin); + LEX_CUSTRING frm= {0,0}; + LEX_STRING db_backup= { thd->db, thd->db_length }; + + DBUG_ENTER("TABLE_SHARE::init_from_sql_statement_string"); + + /* + Ouch. Parser may *change* the string it's working on. + Currently (2013-02-26) it is used to permanently disable + conditional comments. + Anyway, let's copy the caller's string... + */ + if (!(sql_copy= thd->strmake(sql, sql_length))) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + + if (parser_state.init(thd, sql_copy, sql_length)) + DBUG_RETURN(HA_ERR_OUT_OF_MEM); + + thd->variables.sql_mode= MODE_NO_ENGINE_SUBSTITUTION | MODE_NO_DIR_IN_CREATE; + thd->variables.character_set_client= system_charset_info; + tmp_disable_binlog(thd); + old_lex= thd->lex; + thd->lex= &tmp_lex; + + arena= thd->stmt_arena; + if (arena->is_conventional()) + arena= 0; + else + thd->set_n_backup_active_arena(arena, &backup); + + thd->reset_db(db.str, db.length); + lex_start(thd); + + if ((error= parse_sql(thd, & parser_state, NULL) || + sql_unusable_for_discovery(thd, hton, sql_copy))) + goto ret; + + thd->lex->create_info.db_type= hton; +#ifdef WITH_PARTITION_STORAGE_ENGINE + thd->work_part_info= 0; // For partitioning +#endif + + if (tabledef_version.str) + thd->lex->create_info.tabledef_version= tabledef_version; + + promote_first_timestamp_column(&thd->lex->alter_info.create_list); + file= mysql_create_frm_image(thd, db.str, table_name.str, + &thd->lex->create_info, &thd->lex->alter_info, + C_ORDINARY_CREATE, &unused1, &unused2, &frm); + error|= file == 0; + delete file; + + if (frm.str) { - share->ha_data_destroy(share->ha_data); - share->ha_data_destroy= NULL; + option_list= 0; // cleanup existing options ... + option_struct= 0; // ... if it's an assisted discovery + error= init_from_binary_frm_image(thd, write, frm.str, frm.length); } -#ifdef WITH_PARTITION_STORAGE_ENGINE - if (share->ha_part_data_destroy) + +ret: + my_free(const_cast<uchar*>(frm.str)); + lex_end(thd->lex); + thd->reset_db(db_backup.str, db_backup.length); + thd->lex= old_lex; + if (arena) + thd->restore_active_arena(arena, &backup); + reenable_binlog(thd); + thd->variables.sql_mode= saved_mode; + thd->variables.character_set_client= old_cs; + if (thd->is_error() || error) { - share->ha_part_data_destroy(share->ha_part_data); - share->ha_data_destroy= NULL; + thd->clear_error(); + my_error(ER_SQL_DISCOVER_ERROR, MYF(0), + plugin_name(db_plugin)->str, db.str, table_name.str, + sql_copy); + DBUG_RETURN(HA_ERR_GENERIC); } -#endif /* WITH_PARTITION_STORAGE_ENGINE */ + DBUG_RETURN(0); +} + +bool TABLE_SHARE::write_frm_image(const uchar *frm, size_t len) +{ + return writefrm(normalized_path.str, db.str, table_name.str, false, frm, len); +} + + +bool TABLE_SHARE::read_frm_image(const uchar **frm, size_t *len) +{ + if (IF_PARTITIONING(partition_info_str, 0)) // cannot discover a partition + { + DBUG_ASSERT(db_type()->discover_table == 0); + return 1; + } + + if (frm_image) + { + *frm= frm_image->str; + *len= frm_image->length; + frm_image->str= 0; // pass the ownership to the caller + frm_image= 0; + return 0; + } + return readfrm(normalized_path.str, frm, len); +} + + +void TABLE_SHARE::free_frm_image(const uchar *frm) +{ + if (frm) + my_free(const_cast<uchar*>(frm)); +} - open_table_error(share, error, share->open_errno, errarg); - DBUG_RETURN(error); -} /* open_binary_frm */ /* @brief @@ -2251,6 +2494,7 @@ bool unpack_vcol_info_from_frm(THD *thd, Query_arena *backup_stmt_arena_ptr; Query_arena backup_arena; Query_arena *vcol_arena= 0; + Create_field vcol_storage; // placeholder for vcol_info Parser_state parser_state; LEX *old_lex= thd->lex; LEX lex; @@ -2314,7 +2558,8 @@ bool unpack_vcol_info_from_frm(THD *thd, if (init_lex_with_single_table(thd, table, &lex)) goto err; - thd->lex->parse_vcol_expr= TRUE; + lex.parse_vcol_expr= TRUE; + lex.last_field= &vcol_storage; /* Step 3: Use the parser to build an Item object from vcol_expr_str. @@ -2324,7 +2569,7 @@ bool unpack_vcol_info_from_frm(THD *thd, goto err; } /* From now on use vcol_info generated by the parser. */ - field->vcol_info= thd->lex->vcol_info; + field->vcol_info= vcol_storage.vcol_info; /* Validate the Item tree. */ if (fix_vcol_expr(thd, table, field)) @@ -2378,15 +2623,16 @@ end: 7 Table definition has changed in engine */ -int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, - uint db_stat, uint prgflag, uint ha_open_flags, - TABLE *outparam, bool is_create_table) +enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share, + const char *alias, uint db_stat, uint prgflag, + uint ha_open_flags, TABLE *outparam, + bool is_create_table) { - int error; - uint records, i, bitmap_size; + enum open_frm_error error; + uint records, i, bitmap_size, bitmap_count; bool error_reported= FALSE; uchar *record, *bitmaps; - Field **field_ptr, **vfield_ptr; + Field **field_ptr, **UNINIT_VAR(vfield_ptr), **UNINIT_VAR(dfield_ptr); uint8 save_context_analysis_only= thd->lex->context_analysis_only; DBUG_ENTER("open_table_from_share"); DBUG_PRINT("enter",("name: '%s.%s' form: 0x%lx", share->db.str, @@ -2394,20 +2640,28 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, thd->lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_VIEW; // not a view - error= 1; + error= OPEN_FRM_ERROR_ALREADY_ISSUED; // for OOM errors below bzero((char*) outparam, sizeof(*outparam)); outparam->in_use= thd; outparam->s= share; outparam->db_stat= db_stat; outparam->write_row_record= NULL; - init_sql_alloc(&outparam->mem_root, TABLE_ALLOC_BLOCK_SIZE, 0); + if (share->incompatible_version && + !(ha_open_flags & (HA_OPEN_FOR_ALTER | HA_OPEN_FOR_REPAIR))) + { + /* one needs to run mysql_upgrade on the table */ + error= OPEN_FRM_NEEDS_REBUILD; + goto err; + } + init_sql_alloc(&outparam->mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0)); if (outparam->alias.copy(alias, strlen(alias), table_alias_charset)) goto err; outparam->quick_keys.init(); outparam->covering_keys.init(); outparam->merge_keys.init(); + outparam->intersect_keys.init(); outparam->keys_in_use_for_query.init(); /* Allocate handler */ @@ -2417,13 +2671,15 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, if (!(outparam->file= get_new_handler(share, &outparam->mem_root, share->db_type()))) goto err; + + if (outparam->file->set_ha_share_ref(&share->ha_share)) + goto err; } else { DBUG_ASSERT(!db_stat); } - error= 4; outparam->reginfo.lock_type= TL_UNLOCK; outparam->current_lock= F_UNLCK; records=0; @@ -2478,9 +2734,6 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, if (share->found_next_number_field) outparam->found_next_number_field= outparam->field[(uint) (share->found_next_number_field - share->field)]; - if (share->timestamp_field) - outparam->timestamp_field= (Field_timestamp*) outparam->field[share->timestamp_field_offset]; - /* Fix key->name and key_part->field */ if (share->key_parts) @@ -2508,7 +2761,7 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, key_info->key_part= key_part; key_part_end= key_part + (share->use_ext_keys ? key_info->ext_key_parts : - key_info->key_parts) ; + key_info->user_defined_key_parts) ; for ( ; key_part < key_part_end; key_part++) { Field *field= key_part->field= outparam->field[key_part->fieldnr - 1]; @@ -2520,22 +2773,20 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, We are using only a prefix of the column as a key: Create a new field for the key part that matches the index */ - field= key_part->field=field->new_field(&outparam->mem_root, - outparam, 0); + field= key_part->field=field->make_new_field(&outparam->mem_root, + outparam, 0); field->field_length= key_part->length; } } if (!share->use_ext_keys) - key_part+= key_info->ext_key_parts - key_info->key_parts; + key_part+= key_info->ext_key_parts - key_info->user_defined_key_parts; } } /* - Process virtual columns, if any. + Process virtual and default columns, if any. */ - if (!share->vfields) - outparam->vfield= NULL; - else + if (share->vfields) { if (!(vfield_ptr = (Field **) alloc_root(&outparam->mem_root, (uint) ((share->vfields+1)* @@ -2543,10 +2794,24 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, goto err; outparam->vfield= vfield_ptr; + } + if (share->default_fields) + { + if (!(dfield_ptr = (Field **) alloc_root(&outparam->mem_root, + (uint) ((share->default_fields+1)* + sizeof(Field*))))) + goto err; + + outparam->default_field= dfield_ptr; + } + + if (share->vfields || share->default_fields) + { + /* Reuse the same loop both for virtual and default fields. */ for (field_ptr= outparam->field; *field_ptr; field_ptr++) { - if ((*field_ptr)->vcol_info) + if (share->vfields && (*field_ptr)->vcol_info) { if (unpack_vcol_info_from_frm(thd, &outparam->mem_root, @@ -2555,13 +2820,20 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, &(*field_ptr)->vcol_info->expr_str, &error_reported)) { - error= 4; // in case no error is reported + error= OPEN_FRM_CORRUPTED; goto err; } *(vfield_ptr++)= *field_ptr; } + if (share->default_fields && + ((*field_ptr)->has_insert_default_function() || + (*field_ptr)->has_update_default_function())) + *(dfield_ptr++)= *field_ptr; } - *vfield_ptr= 0; // End marker + if (share->vfields) + *vfield_ptr= 0; // End marker + if (share->default_fields) + *dfield_ptr= 0; // End marker } #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -2590,7 +2862,7 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, tmp= mysql_unpack_partition(thd, share->partition_info_str, share->partition_info_str_len, outparam, is_create_table, - share->default_part_db_type, + plugin_hton(share->default_part_plugin), &work_part_info_used); if (tmp) { @@ -2600,8 +2872,9 @@ int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, } outparam->part_info->is_auto_partitioned= share->auto_partitioned; DBUG_PRINT("info", ("autopartitioned: %u", share->auto_partitioned)); - /* we should perform the fix_partition_func in either local or - caller's arena depending on work_part_info_used value + /* + We should perform the fix_partition_func in either local or + caller's arena depending on work_part_info_used value. */ if (!work_part_info_used) tmp= fix_partition_func(thd, outparam, is_create_table); @@ -2644,88 +2917,116 @@ partititon_err: /* Allocate bitmaps */ bitmap_size= share->column_bitmap_size; - if (!(bitmaps= (uchar*) alloc_root(&outparam->mem_root, bitmap_size*5))) + bitmap_count= 6; + if (share->vfields) + { + if (!(outparam->def_vcol_set= (MY_BITMAP*) + alloc_root(&outparam->mem_root, sizeof(*outparam->def_vcol_set)))) + goto err; + bitmap_count++; + } + if (!(bitmaps= (uchar*) alloc_root(&outparam->mem_root, + bitmap_size * bitmap_count))) goto err; - bitmap_init(&outparam->def_read_set, - (my_bitmap_map*) bitmaps, share->fields, FALSE); - bitmap_init(&outparam->def_write_set, - (my_bitmap_map*) (bitmaps+bitmap_size), share->fields, FALSE); - bitmap_init(&outparam->def_vcol_set, - (my_bitmap_map*) (bitmaps+bitmap_size*2), share->fields, FALSE); - bitmap_init(&outparam->tmp_set, - (my_bitmap_map*) (bitmaps+bitmap_size*3), share->fields, FALSE); - bitmap_init(&outparam->eq_join_set, - (my_bitmap_map*) (bitmaps+bitmap_size*4), share->fields, FALSE); + + my_bitmap_init(&outparam->def_read_set, + (my_bitmap_map*) bitmaps, share->fields, FALSE); + bitmaps+= bitmap_size; + my_bitmap_init(&outparam->def_write_set, + (my_bitmap_map*) bitmaps, share->fields, FALSE); + bitmaps+= bitmap_size; + if (share->vfields) + { + /* Don't allocate vcol_bitmap if we don't need it */ + my_bitmap_init(outparam->def_vcol_set, + (my_bitmap_map*) bitmaps, share->fields, FALSE); + bitmaps+= bitmap_size; + } + my_bitmap_init(&outparam->tmp_set, + (my_bitmap_map*) bitmaps, share->fields, FALSE); + bitmaps+= bitmap_size; + my_bitmap_init(&outparam->eq_join_set, + (my_bitmap_map*) bitmaps, share->fields, FALSE); + bitmaps+= bitmap_size; + my_bitmap_init(&outparam->cond_set, + (my_bitmap_map*) bitmaps, share->fields, FALSE); + bitmaps+= bitmap_size; + my_bitmap_init(&outparam->def_rpl_write_set, + (my_bitmap_map*) bitmaps, share->fields, FALSE); outparam->default_column_bitmaps(); + outparam->cond_selectivity= 1.0; + /* The table struct is now initialized; Open the table */ - error= 2; if (db_stat) { - int ha_err; - if ((ha_err= (outparam->file-> - ha_open(outparam, share->normalized_path.str, - (db_stat & HA_READ_ONLY ? O_RDONLY : O_RDWR), - (db_stat & HA_OPEN_TEMPORARY ? HA_OPEN_TMP_TABLE : - ((db_stat & HA_WAIT_IF_LOCKED) || - (specialflag & SPECIAL_WAIT_IF_LOCKED)) ? - HA_OPEN_WAIT_IF_LOCKED : - (db_stat & (HA_ABORT_IF_LOCKED | HA_GET_INFO)) ? - HA_OPEN_ABORT_IF_LOCKED : - HA_OPEN_IGNORE_IF_LOCKED) | ha_open_flags)))) + if (db_stat & HA_OPEN_TEMPORARY) + ha_open_flags|= HA_OPEN_TMP_TABLE; + else if ((db_stat & HA_WAIT_IF_LOCKED) || + (specialflag & SPECIAL_WAIT_IF_LOCKED)) + ha_open_flags|= HA_OPEN_WAIT_IF_LOCKED; + else if (db_stat & (HA_ABORT_IF_LOCKED | HA_GET_INFO)) + ha_open_flags|= HA_OPEN_ABORT_IF_LOCKED; + else + ha_open_flags|= HA_OPEN_IGNORE_IF_LOCKED; + + int ha_err= outparam->file->ha_open(outparam, share->normalized_path.str, + (db_stat & HA_READ_ONLY ? O_RDONLY : O_RDWR), + ha_open_flags); + if (ha_err) { + share->open_errno= ha_err; /* Set a flag if the table is crashed and it can be auto. repaired */ share->crashed= (outparam->file->auto_repair(ha_err) && !(ha_open_flags & HA_OPEN_FOR_REPAIR)); + outparam->file->print_error(ha_err, MYF(0)); + error_reported= TRUE; - switch (ha_err) - { - case HA_ERR_NO_SUCH_TABLE: - /* - The table did not exists in storage engine, use same error message - as if the .frm file didn't exist - */ - error= 1; - my_errno= ENOENT; - break; - case EMFILE: - /* - Too many files opened, use same error message as if the .frm - file can't open - */ - DBUG_PRINT("error", ("open file: %s failed, too many files opened (errno: %d)", - share->normalized_path.str, ha_err)); - error= 1; - my_errno= EMFILE; - break; - default: - outparam->file->print_error(ha_err, MYF(0)); - error_reported= TRUE; - if (ha_err == HA_ERR_TABLE_DEF_CHANGED) - error= 7; - break; - } - goto err; /* purecov: inspected */ + if (ha_err == HA_ERR_TABLE_DEF_CHANGED) + error= OPEN_FRM_DISCOVER; + + /* + We're here, because .frm file was successfully opened. + + But if the table doesn't exist in the engine and the engine + supports discovery, we force rediscover to discover + the fact that table doesn't in fact exist and remove + the stray .frm file. + */ + if (share->db_type()->discover_table && + (ha_err == ENOENT || ha_err == HA_ERR_NO_SUCH_TABLE)) + error= OPEN_FRM_DISCOVER; + + goto err; } } -#if defined(HAVE_valgrind) && !defined(DBUG_OFF) - bzero((char*) bitmaps, bitmap_size*3); -#endif + if (share->table_category == TABLE_CATEGORY_LOG) + { + outparam->no_replicate= TRUE; + } + else if (outparam->file) + { + handler::Table_flags flags= outparam->file->ha_table_flags(); + outparam->no_replicate= ! MY_TEST(flags & (HA_BINLOG_STMT_CAPABLE + | HA_BINLOG_ROW_CAPABLE)) + || MY_TEST(flags & HA_HAS_OWN_BINLOGGING); + } + else + { + outparam->no_replicate= FALSE; + } - outparam->no_replicate= outparam->file && - test(outparam->file->ha_table_flags() & - HA_HAS_OWN_BINLOGGING); /* Increment the opened_tables counter, only when open flags set. */ if (db_stat) thd->status_var.opened_tables++; thd->lex->context_analysis_only= save_context_analysis_only; - DBUG_RETURN (0); + DBUG_RETURN (OPEN_FRM_OK); err: if (! error_reported) - open_table_error(share, error, my_errno, 0); + open_table_error(share, error, my_errno); delete outparam->file; #ifdef WITH_PARTITION_STORAGE_ENGINE if (outparam->part_info) @@ -2777,6 +3078,7 @@ int closefrm(register TABLE *table, bool free_share) #ifdef WITH_PARTITION_STORAGE_ENGINE if (table->part_info) { + /* Allocated through table->mem_root, freed below */ free_items(table->part_info->item_free_list); table->part_info->item_free_list= 0; table->part_info= 0; @@ -2785,7 +3087,7 @@ int closefrm(register TABLE *table, bool free_share) if (free_share) { if (table->s->tmp_table == NO_TMP_TABLE) - release_table_share(table->s); + tdc_release_share(table->s); else free_table_share(table->s); } @@ -2836,158 +3138,18 @@ void free_field_buffers_larger_than(TABLE *table, uint32 size) } } -/** - Find where a form starts. - - @param head The start of the form file. - - @remark If formname is NULL then only formnames is read. - - @retval The form position. -*/ - -static ulong get_form_pos(File file, uchar *head) -{ - uchar *pos, *buf; - uint names, length; - ulong ret_value=0; - DBUG_ENTER("get_form_pos"); - - names= uint2korr(head+8); - - if (!(names= uint2korr(head+8))) - DBUG_RETURN(0); - - length= uint2korr(head+4); - - mysql_file_seek(file, 64L, MY_SEEK_SET, MYF(0)); - - if (!(buf= (uchar*) my_malloc(length+names*4, MYF(MY_WME)))) - DBUG_RETURN(0); - - if (mysql_file_read(file, buf, length+names*4, MYF(MY_NABP))) - { - my_free(buf); - DBUG_RETURN(0); - } - - pos= buf+length; - ret_value= uint4korr(pos); - - my_free(buf); - - DBUG_RETURN(ret_value); -} - - -/* - Read string from a file with malloc - - NOTES: - We add an \0 at end of the read string to make reading of C strings easier -*/ - -int read_string(File file, uchar**to, size_t length) -{ - DBUG_ENTER("read_string"); - - my_free(*to); - if (!(*to= (uchar*) my_malloc(length+1,MYF(MY_WME))) || - mysql_file_read(file, *to, length, MYF(MY_NABP))) - { - my_free(*to); /* purecov: inspected */ - *to= 0; /* purecov: inspected */ - DBUG_RETURN(1); /* purecov: inspected */ - } - *((char*) *to+length)= '\0'; - DBUG_RETURN (0); -} /* read_string */ - - - /* Add a new form to a form file */ - -ulong make_new_entry(File file, uchar *fileinfo, TYPELIB *formnames, - const char *newname) -{ - uint i,bufflength,maxlength,n_length,length,names; - ulong endpos,newpos; - uchar buff[IO_SIZE]; - uchar *pos; - DBUG_ENTER("make_new_entry"); - - length=(uint) strlen(newname)+1; - n_length=uint2korr(fileinfo+4); - maxlength=uint2korr(fileinfo+6); - names=uint2korr(fileinfo+8); - newpos=uint4korr(fileinfo+10); - - if (64+length+n_length+(names+1)*4 > maxlength) - { /* Expand file */ - newpos+=IO_SIZE; - int4store(fileinfo+10,newpos); - /* Copy from file-end */ - endpos= (ulong) mysql_file_seek(file, 0L, MY_SEEK_END, MYF(0)); - bufflength= (uint) (endpos & (IO_SIZE-1)); /* IO_SIZE is a power of 2 */ - - while (endpos > maxlength) - { - mysql_file_seek(file, (ulong) (endpos-bufflength), MY_SEEK_SET, MYF(0)); - if (mysql_file_read(file, buff, bufflength, MYF(MY_NABP+MY_WME))) - DBUG_RETURN(0L); - mysql_file_seek(file, (ulong) (endpos-bufflength+IO_SIZE), MY_SEEK_SET, - MYF(0)); - if ((mysql_file_write(file, buff, bufflength, MYF(MY_NABP+MY_WME)))) - DBUG_RETURN(0); - endpos-=bufflength; bufflength=IO_SIZE; - } - bzero(buff,IO_SIZE); /* Null new block */ - mysql_file_seek(file, (ulong) maxlength, MY_SEEK_SET, MYF(0)); - if (mysql_file_write(file, buff, bufflength, MYF(MY_NABP+MY_WME))) - DBUG_RETURN(0L); - maxlength+=IO_SIZE; /* Fix old ref */ - int2store(fileinfo+6,maxlength); - for (i=names, pos= (uchar*) *formnames->type_names+n_length-1; i-- ; - pos+=4) - { - endpos=uint4korr(pos)+IO_SIZE; - int4store(pos,endpos); - } - } - - if (n_length == 1 ) - { /* First name */ - length++; - (void) strxmov((char*) buff,"/",newname,"/",NullS); - } - else - (void) strxmov((char*) buff,newname,"/",NullS); /* purecov: inspected */ - mysql_file_seek(file, 63L+(ulong) n_length, MY_SEEK_SET, MYF(0)); - if (mysql_file_write(file, buff, (size_t) length+1, MYF(MY_NABP+MY_WME)) || - (names && mysql_file_write(file, - (uchar*) (*formnames->type_names+n_length-1), - names*4, MYF(MY_NABP+MY_WME))) || - mysql_file_write(file, fileinfo+10, 4, MYF(MY_NABP+MY_WME))) - DBUG_RETURN(0L); /* purecov: inspected */ - - int2store(fileinfo+8,names+1); - int2store(fileinfo+4,n_length+length); - (void) mysql_file_chsize(file, newpos, 0, MYF(MY_WME));/* Append file with '\0' */ - DBUG_RETURN(newpos); -} /* make_new_entry */ - +/* error message when opening a form file */ - /* error message when opening a form file */ - -void open_table_error(TABLE_SHARE *share, int error, int db_errno, int errarg) +void open_table_error(TABLE_SHARE *share, enum open_frm_error error, + int db_errno) { - int err_no; char buff[FN_REFLEN]; - myf errortype= ME_ERROR+ME_WAITTANG; // Write fatals error to log + const myf errortype= ME_ERROR+ME_WAITTANG; // Write fatals error to log DBUG_ENTER("open_table_error"); + DBUG_PRINT("info", ("error: %d db_errno: %d", error, db_errno)); switch (error) { - case 7: - case 1: + case OPEN_FRM_OPEN_ERROR: /* Test if file didn't exists. We have to also test for EINVAL as this may happen on windows when opening a file with a not legal file name @@ -3001,55 +3163,35 @@ void open_table_error(TABLE_SHARE *share, int error, int db_errno, int errarg) errortype, buff, db_errno); } break; - case 2: - { - handler *file= 0; - const char *datext= ""; - - if (share->db_type() != NULL) - { - if ((file= get_new_handler(share, current_thd->mem_root, - share->db_type()))) - { - if (!(datext= *file->bas_ext())) - datext= ""; - } - } - err_no= (db_errno == ENOENT) ? ER_FILE_NOT_FOUND : (db_errno == EAGAIN) ? - ER_FILE_USED : ER_CANT_OPEN_FILE; - strxmov(buff, share->normalized_path.str, datext, NullS); - my_error(err_no,errortype, buff, db_errno); - delete file; + case OPEN_FRM_OK: + DBUG_ASSERT(0); // open_table_error() is never called for this one break; - } - case 5: - { - const char *csname= get_charset_name((uint) errarg); - char tmp[10]; - if (!csname || csname[0] =='?') - { - my_snprintf(tmp, sizeof(tmp), "#%d", errarg); - csname= tmp; - } - my_printf_error(ER_UNKNOWN_COLLATION, - "Unknown collation '%s' in table '%-.64s' definition", - MYF(0), csname, share->table_name.str); + case OPEN_FRM_ERROR_ALREADY_ISSUED: break; - } - case 6: - strxmov(buff, share->normalized_path.str, reg_ext, NullS); - my_printf_error(ER_NOT_FORM_FILE, - "Table '%-.64s' was created with a different version " - "of MySQL and cannot be read", - MYF(0), buff); + case OPEN_FRM_NOT_A_VIEW: + my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, + share->table_name.str, "VIEW"); + break; + case OPEN_FRM_NOT_A_TABLE: + my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, + share->table_name.str, "TABLE"); break; - case 8: + case OPEN_FRM_DISCOVER: + DBUG_ASSERT(0); // open_table_error() is never called for this one break; - default: /* Better wrong error than none */ - case 4: + case OPEN_FRM_CORRUPTED: strxmov(buff, share->normalized_path.str, reg_ext, NullS); my_error(ER_NOT_FORM_FILE, errortype, buff); break; + case OPEN_FRM_READ_ERROR: + strxmov(buff, share->normalized_path.str, reg_ext, NullS); + my_error(ER_ERROR_ON_READ, errortype, buff, db_errno); + break; + case OPEN_FRM_NEEDS_REBUILD: + strxnmov(buff, sizeof(buff)-1, + share->db.str, ".", share->table_name.str, NullS); + my_error(ER_TABLE_NEEDS_REBUILD, errortype, buff); + break; } DBUG_VOID_RETURN; } /* open_table_error */ @@ -3153,28 +3295,6 @@ static uint find_field(Field **fields, uchar *record, uint start, uint length) } - /* Check that the integer is in the internal */ - -int set_zone(register int nr, int min_zone, int max_zone) -{ - if (nr<=min_zone) - return (min_zone); - if (nr>=max_zone) - return (max_zone); - return (nr); -} /* set_zone */ - - /* Adjust number to next larger disk buffer */ - -ulong next_io_size(register ulong pos) -{ - reg2 ulong offset; - if ((offset= pos & (IO_SIZE-1))) - return pos-offset+IO_SIZE; - return pos; -} /* next_io_size */ - - /* Store an SQL quoted string. @@ -3237,22 +3357,12 @@ void append_unescaped(String *res, const char *pos, uint length) } - /* Create a .frm file */ - -File create_frm(THD *thd, const char *name, const char *db, - const char *table, uint reclength, uchar *fileinfo, - HA_CREATE_INFO *create_info, uint keys, KEY *key_info) +void prepare_frm_header(THD *thd, uint reclength, uchar *fileinfo, + HA_CREATE_INFO *create_info, uint keys, KEY *key_info) { - register File file; - ulong length; - uchar fill[IO_SIZE]; - int create_flags= O_RDWR | O_TRUNC; ulong key_comment_total_bytes= 0; uint i; - DBUG_ENTER("create_frm"); - - if (create_info->options & HA_LEX_CREATE_TMP_TABLE) - create_flags|= O_EXCL | O_NOFOLLOW; + DBUG_ENTER("prepare_frm_header"); /* Fix this when we have new .frm files; Current limit is 4G rows (TODO) */ if (create_info->max_rows > UINT_MAX32) @@ -3260,101 +3370,76 @@ File create_frm(THD *thd, const char *name, const char *db, if (create_info->min_rows > UINT_MAX32) create_info->min_rows= UINT_MAX32; - if ((file= mysql_file_create(key_file_frm, - name, CREATE_MODE, create_flags, MYF(0))) >= 0) - { - uint key_length, tmp_key_length, tmp, csid; - bzero((char*) fileinfo,64); - /* header */ - fileinfo[0]=(uchar) 254; - fileinfo[1]= 1; - fileinfo[2]= FRM_VER+3+ test(create_info->varchar); + uint key_length, tmp_key_length, tmp, csid; + bzero((char*) fileinfo, FRM_HEADER_SIZE); + /* header */ + fileinfo[0]=(uchar) 254; + fileinfo[1]= 1; + fileinfo[2]= FRM_VER + 3 + MY_TEST(create_info->varchar); - fileinfo[3]= (uchar) ha_legacy_type( - ha_checktype(thd,ha_legacy_type(create_info->db_type),0,0)); - fileinfo[4]=1; - int2store(fileinfo+6,IO_SIZE); /* Next block starts here */ - /* - Keep in sync with pack_keys() in unireg.cc - For each key: - 8 bytes for the key header - 9 bytes for each key-part (MAX_REF_PARTS) - NAME_LEN bytes for the name - 1 byte for the NAMES_SEP_CHAR (before the name) - For all keys: - 6 bytes for the header - 1 byte for the NAMES_SEP_CHAR (after the last name) - 9 extra bytes (padding for safety? alignment?) - */ - for (i= 0; i < keys; i++) - { - DBUG_ASSERT(test(key_info[i].flags & HA_USES_COMMENT) == - (key_info[i].comment.length > 0)); - if (key_info[i].flags & HA_USES_COMMENT) - key_comment_total_bytes += 2 + key_info[i].comment.length; - } + DBUG_ASSERT(ha_storage_engine_is_enabled(create_info->db_type)); + fileinfo[3]= (uchar) ha_legacy_type(create_info->db_type); - key_length= keys * (8 + MAX_REF_PARTS * 9 + NAME_LEN + 1) + 16 - + key_comment_total_bytes; - - length= next_io_size((ulong) (IO_SIZE+key_length+reclength+ - create_info->extra_size)); - int4store(fileinfo+10,length); - tmp_key_length= (key_length < 0xffff) ? key_length : 0xffff; - int2store(fileinfo+14,tmp_key_length); - int2store(fileinfo+16,reclength); - int4store(fileinfo+18,create_info->max_rows); - int4store(fileinfo+22,create_info->min_rows); - /* fileinfo[26] is set in mysql_create_frm() */ - fileinfo[27]=2; // Use long pack-fields - /* fileinfo[28 & 29] is set to key_info_length in mysql_create_frm() */ - create_info->table_options|=HA_OPTION_LONG_BLOB_PTR; // Use portable blob pointers - int2store(fileinfo+30,create_info->table_options); - fileinfo[32]=0; // No filename anymore - fileinfo[33]=5; // Mark for 5.0 frm file - int4store(fileinfo+34,create_info->avg_row_length); - csid= (create_info->default_table_charset ? - create_info->default_table_charset->number : 0); - fileinfo[38]= (uchar) csid; - fileinfo[39]= (uchar) ((uint) create_info->transactional | - ((uint) create_info->page_checksum << 2)); - fileinfo[40]= (uchar) create_info->row_type; - /* Next few bytes where for RAID support */ - fileinfo[41]= (uchar) (csid >> 8); - fileinfo[42]= 0; - fileinfo[43]= 0; - fileinfo[44]= 0; - fileinfo[45]= 0; - fileinfo[46]= 0; - int4store(fileinfo+47, key_length); - tmp= MYSQL_VERSION_ID; // Store to avoid warning from int4store - int4store(fileinfo+51, tmp); - int4store(fileinfo+55, create_info->extra_size); - /* - 59-60 is reserved for extra_rec_buf_length, - 61 for default_part_db_type - */ - int2store(fileinfo+62, create_info->key_block_size); - bzero(fill,IO_SIZE); - for (; length > IO_SIZE ; length-= IO_SIZE) - { - if (mysql_file_write(file, fill, IO_SIZE, MYF(MY_WME | MY_NABP))) - { - (void) mysql_file_close(file, MYF(0)); - (void) mysql_file_delete(key_file_frm, name, MYF(0)); - return(-1); - } - } - } - else - { - if (my_errno == ENOENT) - my_error(ER_BAD_DB_ERROR,MYF(0),db); - else - my_error(ER_CANT_CREATE_TABLE,MYF(0),table,my_errno); - } - DBUG_RETURN(file); -} /* create_frm */ + /* + Keep in sync with pack_keys() in unireg.cc + For each key: + 8 bytes for the key header + 9 bytes for each key-part (MAX_REF_PARTS) + NAME_LEN bytes for the name + 1 byte for the NAMES_SEP_CHAR (before the name) + For all keys: + 6 bytes for the header + 1 byte for the NAMES_SEP_CHAR (after the last name) + 9 extra bytes (padding for safety? alignment?) + */ + for (i= 0; i < keys; i++) + { + DBUG_ASSERT(MY_TEST(key_info[i].flags & HA_USES_COMMENT) == + (key_info[i].comment.length > 0)); + if (key_info[i].flags & HA_USES_COMMENT) + key_comment_total_bytes += 2 + key_info[i].comment.length; + } + + key_length= keys * (8 + MAX_REF_PARTS * 9 + NAME_LEN + 1) + 16 + + key_comment_total_bytes; + + int2store(fileinfo+8,1); + tmp_key_length= (key_length < 0xffff) ? key_length : 0xffff; + int2store(fileinfo+14,tmp_key_length); + int2store(fileinfo+16,reclength); + int4store(fileinfo+18,create_info->max_rows); + int4store(fileinfo+22,create_info->min_rows); + /* fileinfo[26] is set in mysql_create_frm() */ + fileinfo[27]=2; // Use long pack-fields + /* fileinfo[28 & 29] is set to key_info_length in mysql_create_frm() */ + create_info->table_options|=HA_OPTION_LONG_BLOB_PTR; // Use portable blob pointers + int2store(fileinfo+30,create_info->table_options); + fileinfo[32]=0; // No filename anymore + fileinfo[33]=5; // Mark for 5.0 frm file + int4store(fileinfo+34,create_info->avg_row_length); + csid= (create_info->default_table_charset ? + create_info->default_table_charset->number : 0); + fileinfo[38]= (uchar) csid; + fileinfo[39]= (uchar) ((uint) create_info->transactional | + ((uint) create_info->page_checksum << 2)); + fileinfo[40]= (uchar) create_info->row_type; + /* Bytes 41-46 were for RAID support; now reused for other purposes */ + fileinfo[41]= (uchar) (csid >> 8); + int2store(fileinfo+42, create_info->stats_sample_pages & 0xffff); + fileinfo[44]= (uchar) create_info->stats_auto_recalc; + fileinfo[45]= 0; + fileinfo[46]= 0; + int4store(fileinfo+47, key_length); + tmp= MYSQL_VERSION_ID; // Store to avoid warning from int4store + int4store(fileinfo+51, tmp); + int4store(fileinfo+55, create_info->extra_size); + /* + 59-60 is reserved for extra_rec_buf_length, + 61 for default_part_db_type + */ + int2store(fileinfo+62, create_info->key_block_size); + DBUG_VOID_RETURN; +} /* prepare_fileinfo */ void update_create_info_from_table(HA_CREATE_INFO *create_info, TABLE *table) @@ -3383,7 +3468,7 @@ rename_file_ext(const char * from,const char * to,const char * ext) char from_b[FN_REFLEN],to_b[FN_REFLEN]; (void) strxmov(from_b,from,ext,NullS); (void) strxmov(to_b,to,ext,NullS); - return (mysql_file_rename(key_file_frm, from_b, to_b, MYF(MY_WME))); + return mysql_file_rename(key_file_frm, from_b, to_b, MYF(0)); } @@ -3405,18 +3490,23 @@ bool get_field(MEM_ROOT *mem, Field *field, String *res) { char buff[MAX_FIELD_WIDTH], *to; String str(buff,sizeof(buff),&my_charset_bin); - uint length; + bool rc; + THD *thd= field->get_thd(); + ulonglong sql_mode_backup= thd->variables.sql_mode; + thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH; field->val_str(&str); - if (!(length= str.length())) + if ((rc= !str.length() || + !(to= strmake_root(mem, str.ptr(), str.length())))) { res->length(0); - return 1; + goto ex; } - if (!(to= strmake_root(mem, str.ptr(), length))) - length= 0; // Safety fix - res->set(to, length, ((Field_str*)field)->charset()); - return 0; + res->set(to, str.length(), field->charset()); + +ex: + thd->variables.sql_mode= sql_mode_backup; + return rc; } @@ -3435,17 +3525,10 @@ bool get_field(MEM_ROOT *mem, Field *field, String *res) char *get_field(MEM_ROOT *mem, Field *field) { - char buff[MAX_FIELD_WIDTH], *to; - String str(buff,sizeof(buff),&my_charset_bin); - uint length; - - field->val_str(&str); - length= str.length(); - if (!length || !(to= (char*) alloc_root(mem,length+1))) - return NullS; - memcpy(to,str.ptr(),(uint) length); - to[length]=0; - return to; + String str; + bool rc= get_field(mem, field, &str); + DBUG_ASSERT(rc || str.ptr()[str.length()] == '\0'); + return rc ? NullS : (char *) str.ptr(); } /* @@ -3473,15 +3556,34 @@ uint calculate_key_len(TABLE *table, uint key, const uchar *buf, return length; } +#ifndef DBUG_OFF +/** + Verifies that database/table name is in lowercase, when it should be + + This is supposed to be used only inside DBUG_ASSERT() +*/ +bool ok_for_lower_case_names(const char *name) +{ + if (!lower_case_table_names || !name) + return true; + + char buf[SAFE_NAME_LEN]; + strmake_buf(buf, name); + my_casedn_str(files_charset_info, buf); + return strcmp(name, buf) == 0; +} +#endif + /* Check if database name is valid SYNPOSIS check_db_name() - org_name Name of database and length + org_name Name of database NOTES - If lower_case_table_names is set then database is converted to lower case + If lower_case_table_names is set to 1 then database name is converted + to lower case RETURN 0 ok @@ -3503,9 +3605,12 @@ bool check_db_name(LEX_STRING *org_name) if (!name_length || name_length > NAME_LEN) return 1; - if (lower_case_table_names && name != any_db) - my_casedn_str(files_charset_info, name); - + if (lower_case_table_names == 1 && name != any_db) + { + org_name->length= name_length= my_casedn_str(files_charset_info, name); + if (check_for_path_chars) + org_name->length+= MYSQL50_TABLE_NAME_PREFIX_LENGTH; + } if (db_name_is_in_ignore_db_dirs_list(name)) return 1; @@ -3638,13 +3743,14 @@ Table_check_intact::check(TABLE *table, const TABLE_FIELD_DEF *table_def) if (table->s->fields != table_def->count) { + THD *thd= current_thd; DBUG_PRINT("info", ("Column count has changed, checking the definition")); /* previous MySQL version */ if (MYSQL_VERSION_ID > table->s->mysql_version) { report_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, - ER(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE), + ER_THD(thd, ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE), table->alias.c_ptr(), table_def->count, table->s->fields, static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID); @@ -3652,9 +3758,9 @@ Table_check_intact::check(TABLE *table, const TABLE_FIELD_DEF *table_def) } else if (MYSQL_VERSION_ID == table->s->mysql_version) { - report_error(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED, - ER(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED), - table->alias.c_ptr(), + report_error(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2, + ER_THD(thd, ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2), + table->s->db.str, table->s->table_name.str, table_def->count, table->s->fields); DBUG_RETURN(TRUE); } @@ -3754,6 +3860,46 @@ Table_check_intact::check(TABLE *table, const TABLE_FIELD_DEF *table_def) } } + if (table_def->primary_key_parts) + { + if (table->s->primary_key == MAX_KEY) + { + report_error(0, "Incorrect definition of table %s.%s: " + "missing primary key.", table->s->db.str, + table->alias.c_ptr()); + error= TRUE; + } + else + { + KEY *pk= &table->s->key_info[table->s->primary_key]; + if (pk->user_defined_key_parts != table_def->primary_key_parts) + { + report_error(0, "Incorrect definition of table %s.%s: " + "Expected primary key to have %u columns, but instead " + "found %u columns.", table->s->db.str, + table->alias.c_ptr(), table_def->primary_key_parts, + pk->user_defined_key_parts); + error= TRUE; + } + else + { + for (i= 0; i < pk->user_defined_key_parts; ++i) + { + if (table_def->primary_key_columns[i] + 1 != pk->key_part[i].fieldnr) + { + report_error(0, "Incorrect definition of table %s.%s: Expected " + "primary key part %u to refer to column %u, but " + "instead found column %u.", table->s->db.str, + table->alias.c_ptr(), i + 1, + table_def->primary_key_columns[i] + 1, + pk->key_part[i].fieldnr); + error= TRUE; + } + } + } + } + } + if (! error) table->s->table_field_def_cache= table_def; @@ -3771,6 +3917,15 @@ end: } +void Table_check_intact_log_error::report_error(uint, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + error_log_print(ERROR_LEVEL, fmt, args); + va_end(args); +} + + /** Traverse portion of wait-for graph which is reachable through edge represented by this flush ticket in search for deadlocks. @@ -3812,16 +3967,17 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, bool result= TRUE; /* - To protect used_tables list from being concurrently modified - while we are iterating through it we acquire LOCK_open. + To protect all_tables list from being concurrently modified + while we are iterating through it we increment tdc.all_tables_refs. This does not introduce deadlocks in the deadlock detector - because we won't try to acquire LOCK_open while + because we won't try to acquire tdc.LOCK_table_share while holding a write-lock on MDL_lock::m_rwlock. */ - if (gvisitor->m_lock_open_count++ == 0) - mysql_mutex_lock(&LOCK_open); + mysql_mutex_lock(&tdc->LOCK_table_share); + tdc->all_tables_refs++; + mysql_mutex_unlock(&tdc->LOCK_table_share); - I_P_List_iterator <TABLE, TABLE_share> tables_it(used_tables); + TDC_element::All_share_tables_list::Iterator tables_it(tdc->all_tables); /* In case of multiple searches running in parallel, avoid going @@ -3839,6 +3995,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, while ((table= tables_it++)) { + DBUG_ASSERT(table->in_use && tdc->flushed); if (gvisitor->inspect_edge(&table->in_use->mdl_context)) { goto end_leave_node; @@ -3848,6 +4005,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, tables_it.rewind(); while ((table= tables_it++)) { + DBUG_ASSERT(table->in_use && tdc->flushed); if (table->in_use->mdl_context.visit_subgraph(gvisitor)) { goto end_leave_node; @@ -3860,8 +4018,10 @@ end_leave_node: gvisitor->leave_node(src_ctx); end: - if (gvisitor->m_lock_open_count-- == 1) - mysql_mutex_unlock(&LOCK_open); + mysql_mutex_lock(&tdc->LOCK_table_share); + if (!--tdc->all_tables_refs) + mysql_cond_broadcast(&tdc->COND_release); + mysql_mutex_unlock(&tdc->LOCK_table_share); return result; } @@ -3875,10 +4035,15 @@ end: @param abstime Timeout for waiting as absolute time value. @param deadlock_weight Weight of this wait for deadlock detector. - @pre LOCK_open is write locked, the share is used (has - non-zero reference count), is marked for flush and + @pre LOCK_table_share is locked, the share is marked for flush and this connection does not reference the share. - LOCK_open will be unlocked temporarily during execution. + LOCK_table_share will be unlocked temporarily during execution. + + It may happen that another FLUSH TABLES thread marked this share + for flush, but didn't yet purge it from table definition cache. + In this case we may start waiting for a table share that has no + references (ref_count == 0). We do this with assumption that this + another FLUSH TABLES thread is about to purge this share. @retval FALSE - Success. @retval TRUE - Error (OOM, deadlock, timeout, etc...). @@ -3891,41 +4056,29 @@ bool TABLE_SHARE::wait_for_old_version(THD *thd, struct timespec *abstime, Wait_for_flush ticket(mdl_context, this, deadlock_weight); MDL_wait::enum_wait_status wait_status; - mysql_mutex_assert_owner(&LOCK_open); - /* - We should enter this method only when share's version is not - up to date and the share is referenced. Otherwise our - thread will never be woken up from wait. - */ - DBUG_ASSERT(version != refresh_version && ref_count != 0); + mysql_mutex_assert_owner(&tdc->LOCK_table_share); + DBUG_ASSERT(tdc->flushed); - m_flush_tickets.push_front(&ticket); + tdc->m_flush_tickets.push_front(&ticket); mdl_context->m_wait.reset_status(); - mysql_mutex_unlock(&LOCK_open); + mysql_mutex_unlock(&tdc->LOCK_table_share); mdl_context->will_wait_for(&ticket); mdl_context->find_deadlock(); wait_status= mdl_context->m_wait.timed_wait(thd, abstime, TRUE, - "Waiting for table flush"); + &stage_waiting_for_table_flush); mdl_context->done_waiting_for(); - mysql_mutex_lock(&LOCK_open); - - m_flush_tickets.remove(&ticket); + mysql_mutex_lock(&tdc->LOCK_table_share); + tdc->m_flush_tickets.remove(&ticket); + mysql_cond_broadcast(&tdc->COND_release); + mysql_mutex_unlock(&tdc->LOCK_table_share); - if (m_flush_tickets.is_empty() && ref_count == 0) - { - /* - If our thread was the last one using the share, - we must destroy it here. - */ - destroy(); - } /* In cases when our wait was aborted by KILL statement, @@ -3970,7 +4123,7 @@ bool TABLE_SHARE::wait_for_old_version(THD *thd, struct timespec *abstime, void TABLE::init(THD *thd, TABLE_LIST *tl) { - DBUG_ASSERT(s->ref_count > 0 || s->tmp_table != NO_TMP_TABLE); + DBUG_ASSERT(s->tmp_table != NO_TMP_TABLE || s->tdc->ref_count > 0); if (thd->lex->need_correct_ident()) alias_name_used= my_strcasecmp(table_alias_charset, @@ -3994,30 +4147,29 @@ void TABLE::init(THD *thd, TABLE_LIST *tl) file->ft_handler= 0; reginfo.impossible_range= 0; created= TRUE; + cond_selectivity= 1.0; + cond_selectivity_sampling_explain= NULL; +#ifdef HAVE_REPLICATION + /* used in RBR Triggers */ + master_had_triggers= 0; +#endif /* Catch wrong handling of the auto_increment_field_not_null. */ DBUG_ASSERT(!auto_increment_field_not_null); auto_increment_field_not_null= FALSE; - if (timestamp_field) - timestamp_field_type= timestamp_field->get_auto_set_type(); - pos_in_table_list= tl; clear_column_bitmaps(); + for (Field **f_ptr= field ; *f_ptr ; f_ptr++) + { + (*f_ptr)->next_equal_field= NULL; + (*f_ptr)->cond_selectivity= 1.0; + } DBUG_ASSERT(key_read == 0); - /* mark the record[0] uninitialized */ - TRASH_ALLOC(record[0], s->reclength); - - /* - Initialize the null marker bits, to ensure that if we are doing a read - of only selected columns (like in keyread), all null markers are - initialized. - */ - memset(record[0], 255, s->null_bytes); - memset(record[1], 255, s->null_bytes); + restore_record(this, s->default_values); /* Tables may be reused in a sub statement. */ DBUG_ASSERT(!file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); @@ -4049,7 +4201,7 @@ bool TABLE::fill_item_list(List<Item> *item_list) const */ for (Field **ptr= field; *ptr; ptr++) { - Item_field *item= new Item_field(*ptr); + Item_field *item= new (in_use->mem_root) Item_field(in_use, *ptr); if (!item || item_list->push_back(item)) return TRUE; } @@ -4093,7 +4245,8 @@ void TABLE::reset_item_list(List<Item> *item_list) const void TABLE_LIST::calc_md5(char *buffer) { uchar digest[16]; - MY_MD5_HASH(digest, (uchar *) select_stmt.str, select_stmt.length); + compute_md5_hash(digest, select_stmt.str, + select_stmt.length); sprintf((char *) buffer, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", digest[0], digest[1], digest[2], digest[3], @@ -4299,7 +4452,7 @@ bool TABLE_LIST::prep_where(THD *thd, Item **conds, this expression will not be moved to WHERE condition (i.e. will be clean correctly for PS/SP) */ - tbl->on_expr= and_conds(tbl->on_expr, + tbl->on_expr= and_conds(thd, tbl->on_expr, where->copy_andor_structure(thd)); break; } @@ -4309,7 +4462,7 @@ bool TABLE_LIST::prep_where(THD *thd, Item **conds, if (*conds && !(*conds)->fixed) res= (*conds)->fix_fields(thd, conds); if (!res) - *conds= and_conds(*conds, where->copy_andor_structure(thd)); + *conds= and_conds(thd, *conds, where->copy_andor_structure(thd)); if (*conds && !(*conds)->fixed && !res) res= (*conds)->fix_fields(thd, conds); } @@ -4381,7 +4534,7 @@ merge_on_conds(THD *thd, TABLE_LIST *table, bool is_cascaded) { if (tbl->view && !is_cascaded) continue; - cond= and_conds(cond, merge_on_conds(thd, tbl, is_cascaded)); + cond= and_conds(thd, cond, merge_on_conds(thd, tbl, is_cascaded)); } DBUG_RETURN(cond); } @@ -4441,10 +4594,10 @@ bool TABLE_LIST::prep_check_option(THD *thd, uint8 check_opt_type) for (TABLE_LIST *tbl= merge_underlying_list; tbl; tbl= tbl->next_local) { if (tbl->check_option) - check_option= and_conds(check_option, tbl->check_option); + check_option= and_conds(thd, check_option, tbl->check_option); } } - check_option= and_conds(check_option, + check_option= and_conds(thd, check_option, merge_on_conds(thd, this, is_cascaded)); if (arena) @@ -4488,27 +4641,32 @@ void TABLE_LIST::hide_view_error(THD *thd) return; /* Hide "Unknown column" or "Unknown function" error */ DBUG_ASSERT(thd->is_error()); + switch (thd->get_stmt_da()->sql_errno()) { + case ER_BAD_FIELD_ERROR: + case ER_SP_DOES_NOT_EXIST: + case ER_FUNC_INEXISTENT_NAME_COLLISION: + case ER_PROCACCESS_DENIED_ERROR: + case ER_COLUMNACCESS_DENIED_ERROR: + case ER_TABLEACCESS_DENIED_ERROR: + case ER_TABLE_NOT_LOCKED: + case ER_NO_SUCH_TABLE: + { + TABLE_LIST *top= top_table(); + thd->clear_error(); + my_error(ER_VIEW_INVALID, MYF(0), + top->view_db.str, top->view_name.str); + break; + } - if (thd->stmt_da->sql_errno() == ER_BAD_FIELD_ERROR || - thd->stmt_da->sql_errno() == ER_SP_DOES_NOT_EXIST || - thd->stmt_da->sql_errno() == ER_FUNC_INEXISTENT_NAME_COLLISION || - thd->stmt_da->sql_errno() == ER_PROCACCESS_DENIED_ERROR || - thd->stmt_da->sql_errno() == ER_COLUMNACCESS_DENIED_ERROR || - thd->stmt_da->sql_errno() == ER_TABLEACCESS_DENIED_ERROR || - thd->stmt_da->sql_errno() == ER_TABLE_NOT_LOCKED || - thd->stmt_da->sql_errno() == ER_NO_SUCH_TABLE) - { - TABLE_LIST *top= top_table(); - thd->clear_error(); - my_error(ER_VIEW_INVALID, MYF(0), top->view_db.str, top->view_name.str); - } - else if (thd->stmt_da->sql_errno() == ER_NO_DEFAULT_FOR_FIELD) - { - TABLE_LIST *top= top_table(); - thd->clear_error(); - // TODO: make correct error message - my_error(ER_NO_DEFAULT_FOR_VIEW_FIELD, MYF(0), - top->view_db.str, top->view_name.str); + case ER_NO_DEFAULT_FOR_FIELD: + { + TABLE_LIST *top= top_table(); + thd->clear_error(); + // TODO: make correct error message + my_error(ER_NO_DEFAULT_FOR_VIEW_FIELD, MYF(0), + top->view_db.str, top->view_name.str); + break; + } } } @@ -4579,19 +4737,29 @@ void TABLE_LIST::cleanup_items() int TABLE_LIST::view_check_option(THD *thd, bool ignore_failure) { - if (check_option && check_option->val_int() == 0) + if (check_option) { - TABLE_LIST *main_view= top_table(); - if (ignore_failure) + Counting_error_handler ceh; + thd->push_internal_handler(&ceh); + bool res= check_option->val_int() == 0; + thd->pop_internal_handler(); + if (ceh.errors) + return(VIEW_CHECK_ERROR); + if (res) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_VIEW_CHECK_FAILED, ER(ER_VIEW_CHECK_FAILED), - main_view->view_db.str, main_view->view_name.str); - return(VIEW_CHECK_SKIP); + TABLE_LIST *main_view= top_table(); + if (ignore_failure) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_VIEW_CHECK_FAILED, + ER_THD(thd, ER_VIEW_CHECK_FAILED), + main_view->view_db.str, main_view->view_name.str); + return(VIEW_CHECK_SKIP); + } + my_error(ER_VIEW_CHECK_FAILED, MYF(0), main_view->view_db.str, + main_view->view_name.str); + return(VIEW_CHECK_ERROR); } - my_error(ER_VIEW_CHECK_FAILED, MYF(0), main_view->view_db.str, - main_view->view_name.str); - return(VIEW_CHECK_ERROR); } return(VIEW_CHECK_OK); } @@ -4663,23 +4831,26 @@ bool TABLE_LIST::check_single_table(TABLE_LIST **table_arg, bool TABLE_LIST::set_insert_values(MEM_ROOT *mem_root) { + DBUG_ENTER("set_insert_values"); if (table) { + DBUG_PRINT("info", ("setting insert_value for table")); if (!table->insert_values && !(table->insert_values= (uchar *)alloc_root(mem_root, table->s->rec_buff_length))) - return TRUE; + DBUG_RETURN(TRUE); } else { + DBUG_PRINT("info", ("setting insert_value for view")); DBUG_ASSERT(is_view_or_derived() && is_merged_derived()); for (TABLE_LIST *tbl= (TABLE_LIST*)view->select_lex.table_list.first; tbl; tbl= tbl->next_local) if (tbl->set_insert_values(mem_root)) - return TRUE; + DBUG_RETURN(TRUE); } - return FALSE; + DBUG_RETURN(FALSE); } @@ -4731,9 +4902,8 @@ bool TABLE_LIST::is_leaf_for_name_resolution() TABLE_LIST *TABLE_LIST::first_leaf_for_name_resolution() { - TABLE_LIST *cur_table_ref; + TABLE_LIST *UNINIT_VAR(cur_table_ref); NESTED_JOIN *cur_nested_join; - LINT_INIT(cur_table_ref); if (is_leaf_for_name_resolution()) return this; @@ -4852,7 +5022,7 @@ void TABLE_LIST::register_want_access(ulong want_access) Load security context information for this view SYNOPSIS - TABLE_LIST::prepare_view_securety_context() + TABLE_LIST::prepare_view_security_context() thd [in] thread handler RETURN @@ -4861,9 +5031,9 @@ void TABLE_LIST::register_want_access(ulong want_access) */ #ifndef NO_EMBEDDED_ACCESS_CHECKS -bool TABLE_LIST::prepare_view_securety_context(THD *thd) +bool TABLE_LIST::prepare_view_security_context(THD *thd) { - DBUG_ENTER("TABLE_LIST::prepare_view_securety_context"); + DBUG_ENTER("TABLE_LIST::prepare_view_security_context"); DBUG_PRINT("enter", ("table: %s", alias)); DBUG_ASSERT(!prelocking_placeholder && view); @@ -4877,9 +5047,9 @@ bool TABLE_LIST::prepare_view_securety_context(THD *thd) if ((thd->lex->sql_command == SQLCOM_SHOW_CREATE) || (thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_NO_SUCH_USER, - ER(ER_NO_SUCH_USER), + ER_THD(thd, ER_NO_SUCH_USER), definer.user.str, definer.host.str); } else @@ -4899,13 +5069,15 @@ bool TABLE_LIST::prepare_view_securety_context(THD *thd) my_error(ER_ACCESS_DENIED_ERROR, MYF(0), thd->security_ctx->priv_user, thd->security_ctx->priv_host, - (thd->password ? ER(ER_YES) : ER(ER_NO))); + (thd->password ? ER_THD(thd, ER_YES) : + ER_THD(thd, ER_NO))); } DBUG_RETURN(TRUE); } } } DBUG_RETURN(FALSE); + } #endif @@ -4970,7 +5142,7 @@ bool TABLE_LIST::prepare_security(THD *thd) Security_context *save_security_ctx= thd->security_ctx; DBUG_ASSERT(!prelocking_placeholder); - if (prepare_view_securety_context(thd)) + if (prepare_view_security_context(thd)) DBUG_RETURN(TRUE); thd->security_ctx= find_view_security_context(thd); while ((tbl= tb++)) @@ -5196,9 +5368,10 @@ Item *Field_iterator_table::create_item(THD *thd) { SELECT_LEX *select= thd->lex->current_select; - Item_field *item= new Item_field(thd, &select->context, *ptr); + Item_field *item= new (thd->mem_root) Item_field(thd, &select->context, *ptr); if (item && thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY && - !thd->lex->in_sum_func && select->cur_pos_in_select_list != UNDEF_POS) + !thd->lex->in_sum_func && select->cur_pos_in_select_list != UNDEF_POS && + select->join) { select->join->non_agg_fields.push_back(item); item->marker= select->cur_pos_in_select_list; @@ -5253,9 +5426,10 @@ Item *create_view_field(THD *thd, TABLE_LIST *view, Item **field_ref, { DBUG_RETURN(field); } - Item *item= new Item_direct_view_ref(&view->view->select_lex.context, - field_ref, view->alias, - name, view); + Item *item= (new (thd->mem_root) + Item_direct_view_ref(thd, &view->view->select_lex.context, + field_ref, view->alias, + name, view)); if (!item) return NULL; /* @@ -5265,7 +5439,13 @@ Item *create_view_field(THD *thd, TABLE_LIST *view, Item **field_ref, if (view->table && view->table->maybe_null) item->maybe_null= TRUE; /* Save item in case we will need to fall back to materialization. */ - view->used_items.push_front(item); + view->used_items.push_front(item, thd->mem_root); + /* + If we create this reference on persistent memory then it should be + present in persistent list + */ + if (thd->mem_root == thd->stmt_arena->mem_root) + view->persistent_used_items.push_front(item, thd->mem_root); DBUG_RETURN(item); } @@ -5451,17 +5631,16 @@ Field_iterator_table_ref::get_or_create_column_ref(THD *thd, TABLE_LIST *parent_ { Natural_join_column *nj_col; bool is_created= TRUE; - uint field_count; + uint UNINIT_VAR(field_count); TABLE_LIST *add_table_ref= parent_table_ref ? parent_table_ref : table_ref; - LINT_INIT(field_count); if (field_it == &table_field_it) { /* The field belongs to a stored table. */ Field *tmp_field= table_field_it.field(); Item_field *tmp_item= - new Item_field(thd, &thd->lex->current_select->context, tmp_field); + new (thd->mem_root) Item_field(thd, &thd->lex->current_select->context, tmp_field); if (!tmp_item) return NULL; nj_col= new Natural_join_column(tmp_item, table_ref); @@ -5569,10 +5748,14 @@ void TABLE::clear_column_bitmaps() Reset column read/write usage. It's identical to: bitmap_clear_all(&table->def_read_set); bitmap_clear_all(&table->def_write_set); - bitmap_clear_all(&table->def_vcol_set); + if (s->vfields) bitmap_clear_all(table->def_vcol_set); + The code assumes that the bitmaps are allocated after each other, as + guaranteed by open_table_from_share() */ - bzero((char*) def_read_set.bitmap, s->column_bitmap_size*3); - column_bitmaps_set(&def_read_set, &def_write_set, &def_vcol_set); + bzero((char*) def_read_set.bitmap, + s->column_bitmap_size * (s->vfields ? 3 : 2)); + column_bitmaps_set(&def_read_set, &def_write_set, def_vcol_set); + rpl_write_set= 0; // Safety } @@ -5675,7 +5858,7 @@ void TABLE::mark_columns_used_by_index_no_reset(uint index, { KEY_PART_INFO *key_part= key_info[index].key_part; KEY_PART_INFO *key_part_end= (key_part + - key_info[index].key_parts); + key_info[index].user_defined_key_parts); for (;key_part != key_part_end; key_part++) { bitmap_set_bit(bitmap, key_part->fieldnr-1); @@ -5731,6 +5914,8 @@ void TABLE::mark_auto_increment_column() void TABLE::mark_columns_needed_for_delete() { + mark_columns_per_binlog_row_image(); + if (triggers) triggers->mark_fields_used(TRG_EVENT_DELETE); if (file->ha_table_flags() & HA_REQUIRES_KEY_COLUMNS_FOR_DELETE) @@ -5782,6 +5967,9 @@ void TABLE::mark_columns_needed_for_delete() void TABLE::mark_columns_needed_for_update() { DBUG_ENTER("mark_columns_needed_for_update"); + + mark_columns_per_binlog_row_image(); + if (triggers) triggers->mark_fields_used(TRG_EVENT_UPDATE); if (file->ha_table_flags() & HA_REQUIRES_KEY_COLUMNS_FOR_DELETE) @@ -5826,6 +6014,8 @@ void TABLE::mark_columns_needed_for_update() void TABLE::mark_columns_needed_for_insert() { + mark_columns_per_binlog_row_image(); + if (triggers) { /* @@ -5843,6 +6033,123 @@ void TABLE::mark_columns_needed_for_insert() mark_virtual_columns_for_write(TRUE); } +/* + Mark columns according the binlog row image option. + + Columns to be written are stored in 'rpl_write_set' + + When logging in RBR, the user can select whether to + log partial or full rows, depending on the table + definition, and the value of binlog_row_image. + + Semantics of the binlog_row_image are the following + (PKE - primary key equivalent, ie, PK fields if PK + exists, all fields otherwise): + + binlog_row_image= MINIMAL + - This marks the PKE fields in the read_set + - This marks all fields where a value was specified + in the rpl_write_set + + binlog_row_image= NOBLOB + - This marks PKE + all non-blob fields in the read_set + - This marks all fields where a value was specified + and all non-blob fields in the rpl_write_set + + binlog_row_image= FULL + - all columns in the read_set + - all columns in the rpl_write_set + + This marking is done without resetting the original + bitmaps. This means that we will strip extra fields in + the read_set at binlogging time (for those cases that + we only want to log a PK and we needed other fields for + execution). +*/ + +void TABLE::mark_columns_per_binlog_row_image() +{ + THD *thd= in_use; + DBUG_ENTER("mark_columns_per_binlog_row_image"); + DBUG_ASSERT(read_set->bitmap); + DBUG_ASSERT(write_set->bitmap); + + /* If not using row format */ + rpl_write_set= write_set; + + /** + If in RBR we may need to mark some extra columns, + depending on the binlog-row-image command line argument. + */ + if ((WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) && + thd->is_current_stmt_binlog_format_row() && + !ha_check_storage_engine_flag(s->db_type(), HTON_NO_BINLOG_ROW_OPT)) + { + /* if there is no PK, then mark all columns for the BI. */ + if (s->primary_key >= MAX_KEY) + { + bitmap_set_all(read_set); + rpl_write_set= read_set; + } + else + { + switch (thd->variables.binlog_row_image) { + case BINLOG_ROW_IMAGE_FULL: + bitmap_set_all(read_set); + /* Set of columns that should be written (all) */ + rpl_write_set= read_set; + break; + case BINLOG_ROW_IMAGE_NOBLOB: + /* Only write changed columns + not blobs */ + rpl_write_set= &def_rpl_write_set; + bitmap_copy(rpl_write_set, write_set); + + /* + for every field that is not set, mark it unless it is a blob or + part of a primary key + */ + for (Field **ptr=field ; *ptr ; ptr++) + { + Field *my_field= *ptr; + /* + bypass blob fields. These can be set or not set, we don't care. + Later, at binlogging time, if we don't need them in the before + image, we will discard them. + + If set in the AI, then the blob is really needed, there is + nothing we can do about it. + */ + if ((my_field->flags & PRI_KEY_FLAG) || + (my_field->type() != MYSQL_TYPE_BLOB)) + { + bitmap_set_bit(read_set, my_field->field_index); + bitmap_set_bit(rpl_write_set, my_field->field_index); + } + } + break; + case BINLOG_ROW_IMAGE_MINIMAL: + /* + mark the primary key in the read set so that we can find the row + that is updated / deleted. + We don't need to mark the primary key in the rpl_write_set as the + binary log will include all columns read anyway. + */ + mark_columns_used_by_index_no_reset(s->primary_key, read_set); + /* Only write columns that have changed */ + rpl_write_set= write_set; + if (default_field) + mark_default_fields_for_write(rpl_write_set); + break; + + default: + DBUG_ASSERT(FALSE); + } + } + file->column_bitmaps_signal(); + } + + DBUG_VOID_RETURN; +} /* @brief Mark a column as virtual used by the query @@ -5950,6 +6257,51 @@ void TABLE::mark_virtual_columns_for_write(bool insert_fl) /** + Check if a table has a default function either for INSERT or UPDATE-like + operation + @retval true there is a default function + @retval false there is no default function +*/ + +bool TABLE::has_default_function(bool is_update) +{ + Field **dfield_ptr, *dfield; + bool res= false; + for (dfield_ptr= default_field; *dfield_ptr; dfield_ptr++) + { + dfield= (*dfield_ptr); + if (is_update) + res= dfield->has_update_default_function(); + else + res= dfield->has_insert_default_function(); + if (res) + return res; + } + return res; +} + + +/** + Add all fields that have a default function to the table write set. +*/ + +void TABLE::mark_default_fields_for_write(MY_BITMAP* bset) +{ + Field **dfield_ptr, *dfield; + enum_sql_command cmd= in_use->lex->sql_command; + for (dfield_ptr= default_field; *dfield_ptr; dfield_ptr++) + { + dfield= (*dfield_ptr); + if (((sql_command_flags[cmd] & CF_INSERTS_DATA) && + dfield->has_insert_default_function()) || + ((sql_command_flags[cmd] & CF_UPDATES_DATA) && + dfield->has_update_default_function())) + bitmap_set_bit(bset, dfield->field_index); + } +} + + +/** @brief Allocate space for keys @@ -5989,6 +6341,7 @@ bool TABLE::alloc_keys(uint key_count) void TABLE::create_key_part_by_field(KEY_PART_INFO *key_part_info, Field *field, uint fieldnr) { + DBUG_ASSERT(field->field_index + 1 == (int)fieldnr); key_part_info->null_bit= field->null_bit; key_part_info->null_offset= (uint) (field->null_ptr - (uchar*) record[0]); @@ -6132,7 +6485,7 @@ bool TABLE::add_tmp_key(uint key, uint key_parts, KEY* keyinfo; Field **reg_field; uint i; - + bool key_start= TRUE; KEY_PART_INFO* key_part_info= (KEY_PART_INFO*) alloc_root(&mem_root, sizeof(KEY_PART_INFO)*key_parts); @@ -6140,12 +6493,13 @@ bool TABLE::add_tmp_key(uint key, uint key_parts, return TRUE; keyinfo= key_info + key; keyinfo->key_part= key_part_info; - keyinfo->usable_key_parts= keyinfo->key_parts = key_parts; - keyinfo->ext_key_parts= keyinfo->key_parts; + keyinfo->usable_key_parts= keyinfo->user_defined_key_parts = key_parts; + keyinfo->ext_key_parts= keyinfo->user_defined_key_parts; keyinfo->key_length=0; keyinfo->algorithm= HA_KEY_ALG_UNDEF; keyinfo->flags= HA_GENERATED_KEY; keyinfo->ext_key_flags= keyinfo->flags; + keyinfo->is_statistics_from_stat_tables= FALSE; if (unique) keyinfo->flags|= HA_NOSAME; sprintf(buf, "key%i", key); @@ -6156,6 +6510,8 @@ bool TABLE::add_tmp_key(uint key, uint key_parts, if (!keyinfo->rec_per_key) return TRUE; bzero(keyinfo->rec_per_key, sizeof(ulong)*key_parts); + keyinfo->read_stats= NULL; + keyinfo->collected_stats= NULL; for (i= 0; i < key_parts; i++) { @@ -6216,9 +6572,9 @@ bool TABLE::is_filled_at_execution() do not have a corresponding table reference. Such tables are filled during execution. */ - return test(!pos_in_table_list || - pos_in_table_list->jtbm_subselect || - pos_in_table_list->is_active_sjm()); + return MY_TEST(!pos_in_table_list || + pos_in_table_list->jtbm_subselect || + pos_in_table_list->is_active_sjm()); } @@ -6239,7 +6595,7 @@ bool TABLE::is_filled_at_execution() uint TABLE::actual_n_key_parts(KEY *keyinfo) { return optimizer_flag(in_use, OPTIMIZER_SWITCH_EXTENDED_KEYS) ? - keyinfo->ext_key_parts : keyinfo->key_parts; + keyinfo->ext_key_parts : keyinfo->user_defined_key_parts; } @@ -6556,7 +6912,7 @@ bool TABLE::update_const_key_parts(COND *conds) for (uint index= 0; index < s->keys; index++) { KEY_PART_INFO *keyinfo= key_info[index].key_part; - KEY_PART_INFO *keyinfo_end= keyinfo + key_info[index].key_parts; + KEY_PART_INFO *keyinfo_end= keyinfo + key_info[index].user_defined_key_parts; for (key_part_map part_map= (key_part_map)1; keyinfo < keyinfo_end; @@ -6639,6 +6995,177 @@ int update_virtual_fields(THD *thd, TABLE *table, DBUG_RETURN(0); } + +/** + Update all DEFAULT and/or ON INSERT fields. + + @details + Compute and set the default value of all fields with a default function. + There are two kinds of default functions - one is used for INSERT-like + operations, the other for UPDATE-like operations. Depending on the field + definition and the current operation one or the other kind of update + function is evaluated. + + @retval + 0 Success + @retval + >0 Error occurred when storing a virtual field value +*/ + +int TABLE::update_default_fields() +{ + DBUG_ENTER("update_default_fields"); + Field **dfield_ptr, *dfield; + int res= 0; + enum_sql_command cmd= in_use->lex->sql_command; + + DBUG_ASSERT(default_field); + + /* Iterate over fields with default functions in the table */ + for (dfield_ptr= default_field; *dfield_ptr; dfield_ptr++) + { + dfield= (*dfield_ptr); + /* + If an explicit default value for a filed overrides the default, + do not update the field with its automatic default value. + */ + if (!(dfield->flags & HAS_EXPLICIT_VALUE)) + { + if (sql_command_flags[cmd] & CF_INSERTS_DATA) + res= dfield->evaluate_insert_default_function(); + if (sql_command_flags[cmd] & CF_UPDATES_DATA) + res= dfield->evaluate_update_default_function(); + if (res) + DBUG_RETURN(res); + } + } + DBUG_RETURN(res); +} + +void TABLE::reset_default_fields() +{ + if (default_field) + for (Field **df= default_field; *df; df++) + (*df)->flags&= ~HAS_EXPLICIT_VALUE; +} + +/* + Prepare triggers for INSERT-like statement. + + SYNOPSIS + prepare_triggers_for_insert_stmt_or_event() + + NOTE + Prepare triggers for INSERT-like statement by marking fields + used by triggers and inform handlers that batching of UPDATE/DELETE + cannot be done if there are BEFORE UPDATE/DELETE triggers. +*/ + +void TABLE::prepare_triggers_for_insert_stmt_or_event() +{ + if (triggers) + { + if (triggers->has_triggers(TRG_EVENT_DELETE, + TRG_ACTION_AFTER)) + { + /* + The table has AFTER DELETE triggers that might access to + subject table and therefore might need delete to be done + immediately. So we turn-off the batching. + */ + (void) file->extra(HA_EXTRA_DELETE_CANNOT_BATCH); + } + if (triggers->has_triggers(TRG_EVENT_UPDATE, + TRG_ACTION_AFTER)) + { + /* + The table has AFTER UPDATE triggers that might access to subject + table and therefore might need update to be done immediately. + So we turn-off the batching. + */ + (void) file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH); + } + } +} + + +bool TABLE::prepare_triggers_for_delete_stmt_or_event() +{ + if (triggers && + triggers->has_triggers(TRG_EVENT_DELETE, + TRG_ACTION_AFTER)) + { + /* + The table has AFTER DELETE triggers that might access to subject table + and therefore might need delete to be done immediately. So we turn-off + the batching. + */ + (void) file->extra(HA_EXTRA_DELETE_CANNOT_BATCH); + return TRUE; + } + return FALSE; +} + + +bool TABLE::prepare_triggers_for_update_stmt_or_event() +{ + if (triggers && + triggers->has_triggers(TRG_EVENT_UPDATE, + TRG_ACTION_AFTER)) + { + /* + The table has AFTER UPDATE triggers that might access to subject + table and therefore might need update to be done immediately. + So we turn-off the batching. + */ + (void) file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH); + return TRUE; + } + return FALSE; +} + + +/** + Validates default value of fields which are not specified in + the column list of INSERT/LOAD statement. + + @Note s->default_values should be properly populated + before calling this function. + + @param thd thread context + @param record the record to check values in + + @return + @retval false Success. + @retval true Failure. +*/ + +bool TABLE::validate_default_values_of_unset_fields(THD *thd) const +{ + DBUG_ENTER("TABLE::validate_default_values_of_unset_fields"); + for (Field **fld= field; *fld; fld++) + { + if (!bitmap_is_set(write_set, (*fld)->field_index) && + !((*fld)->flags & NO_DEFAULT_VALUE_FLAG)) + { + if (!(*fld)->is_null_in_record(s->default_values) && + (*fld)->validate_value_in_record_with_warn(thd, s->default_values) && + thd->is_error()) + { + /* + We're here if: + - validate_value_in_record_with_warn() failed and + strict mode converted WARN to ERROR + - or the connection was killed, or closed unexpectedly + */ + DBUG_RETURN(true); + } + } + } + DBUG_RETURN(false); +} + + /* @brief Reset const_table flag @@ -6679,15 +7206,17 @@ void TABLE_LIST::reset_const_table() bool TABLE_LIST::handle_derived(LEX *lex, uint phases) { - SELECT_LEX_UNIT *unit= get_unit(); - if (unit) + SELECT_LEX_UNIT *unit; + DBUG_ENTER("handle_derived"); + DBUG_PRINT("enter", ("phases: 0x%x", phases)); + if ((unit= get_unit())) { for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) if (sl->handle_derived(lex, phases)) - return TRUE; - return mysql_handle_single_derived(lex, this, phases); + DBUG_RETURN(TRUE); + DBUG_RETURN(mysql_handle_single_derived(lex, this, phases)); } - return FALSE; + DBUG_RETURN(FALSE); } @@ -6844,12 +7373,21 @@ int TABLE_LIST::fetch_number_of_rows() { int error= 0; if (jtbm_subselect) + { + if (jtbm_subselect->is_jtbm_merged) + { + table->file->stats.records= jtbm_subselect->jtbm_record_count; + set_if_bigger(table->file->stats.records, 2); + table->used_stat_records= table->file->stats.records; + } return 0; + } if (is_materialized_derived() && !fill_me) { table->file->stats.records= ((select_union*)derived->result)->records; set_if_bigger(table->file->stats.records, 2); + table->used_stat_records= table->file->stats.records; } else error= table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); @@ -6922,7 +7460,7 @@ bool TABLE_LIST::change_refs_to_fields() DBUG_ASSERT(!field_it.end_of_fields()); if (!materialized_items[idx]) { - materialized_items[idx]= new Item_field(table->field[idx]); + materialized_items[idx]= new (thd->mem_root) Item_field(thd, table->field[idx]); if (!materialized_items[idx]) return TRUE; } @@ -6967,11 +7505,30 @@ uint TABLE_SHARE::actual_n_key_parts(THD *thd) } -/***************************************************************************** -** Instansiate templates -*****************************************************************************/ +double KEY::actual_rec_per_key(uint i) +{ + if (rec_per_key == 0) + return 0; + return (is_statistics_from_stat_tables ? + read_stats->get_avg_frequency(i) : (double) rec_per_key[i]); +} -#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION -template class List<String>; -template class List_iterator<String>; -#endif +LEX_CSTRING *fk_option_name(enum_fk_option opt) +{ + static LEX_CSTRING names[]= + { + { STRING_WITH_LEN("???") }, + { STRING_WITH_LEN("RESTRICT") }, + { STRING_WITH_LEN("CASCADE") }, + { STRING_WITH_LEN("SET NULL") }, + { STRING_WITH_LEN("NO ACTION") }, + { STRING_WITH_LEN("SET DEFAULT") } + }; + return names + opt; +} + +bool fk_modifies_child(enum_fk_option opt) +{ + static bool can_write[]= { false, false, true, true, false, true }; + return can_write[opt]; +} diff --git a/sql/table.h b/sql/table.h index 4725eb96432..afe3220c943 100644 --- a/sql/table.h +++ b/sql/table.h @@ -29,6 +29,8 @@ #include "handler.h" /* row_type, ha_choice, handler */ #include "mysql_com.h" /* enum_field_types */ #include "thr_lock.h" /* thr_lock_type */ +#include "filesort_utils.h" +#include "parse_file.h" /* Structs that defines the TABLE */ @@ -46,6 +48,8 @@ struct TABLE_LIST; class ACL_internal_schema_access; class ACL_internal_table_access; class Field; +class Table_statistics; +class TDC_element; /* Used to identify NESTED_JOIN structures within a join (applicable only to @@ -193,10 +197,20 @@ private: /* Order clause list element */ +typedef int (*fast_field_copier)(Field *to, Field *from); + + typedef struct st_order { struct st_order *next; Item **item; /* Point at item in select fields */ Item *item_ptr; /* Storage for initial item */ + /* + Reference to the function we are trying to optimize copy to + a temporary table + */ + fast_field_copier fast_field_copier_func; + /* Field for which above optimizer function setup */ + Field *fast_field_copier_setup; int counter; /* position in SELECT list, correct only if counter_used is true*/ bool asc; /* true if ascending */ @@ -250,7 +264,8 @@ typedef struct st_grant_info @details The version of this copy is found in GRANT_INFO::version. */ - GRANT_TABLE *grant_table; + GRANT_TABLE *grant_table_user; + GRANT_TABLE *grant_table_role; /** @brief Used for cache invalidation when caching privilege information. @@ -306,11 +321,13 @@ enum enum_vcol_update_mode VCOL_UPDATE_FOR_WRITE }; -typedef struct st_filesort_info +class Filesort_info { + /// Buffer for sorting keys. + Filesort_buffer filesort_buffer; + +public: IO_CACHE *io_cache; /* If sorted through filesort */ - uchar **sort_keys; /* Buffer for sorting keys */ - uint keys; /* Number of key pointers in buffer */ uchar *buffpek; /* Buffer for buffpek structures */ uint buffpek_len; /* Max number of buffpeks in the buffer */ uchar *addon_buf; /* Pointer to a buffer if sorted with fields */ @@ -319,28 +336,40 @@ typedef struct st_filesort_info void (*unpack)(struct st_sort_addon_field *, uchar *, uchar *); /* To unpack back */ uchar *record_pointers; /* If sorted in memory */ ha_rows found_records; /* How many records in sort */ -} FILESORT_INFO; + /** Sort filesort_buffer */ + void sort_buffer(Sort_param *param, uint count) + { filesort_buffer.sort_buffer(param, count); } -/* - Values in this enum are used to indicate how a tables TIMESTAMP field - should be treated. It can be set to the current timestamp on insert or - update or both. - WARNING: The values are used for bit operations. If you change the - enum, you must keep the bitwise relation of the values. For example: - (int) TIMESTAMP_AUTO_SET_ON_BOTH must be equal to - (int) TIMESTAMP_AUTO_SET_ON_INSERT | (int) TIMESTAMP_AUTO_SET_ON_UPDATE. - We use an enum here so that the debugger can display the value names. -*/ -enum timestamp_auto_set_type -{ - TIMESTAMP_NO_AUTO_SET= 0, TIMESTAMP_AUTO_SET_ON_INSERT= 1, - TIMESTAMP_AUTO_SET_ON_UPDATE= 2, TIMESTAMP_AUTO_SET_ON_BOTH= 3 + /** + Accessors for Filesort_buffer (which @c). + */ + uchar *get_record_buffer(uint idx) + { return filesort_buffer.get_record_buffer(idx); } + + uchar **get_sort_keys() + { return filesort_buffer.get_sort_keys(); } + + uchar **alloc_sort_buffer(uint num_records, uint record_length) + { return filesort_buffer.alloc_sort_buffer(num_records, record_length); } + + bool check_sort_buffer_properties(uint num_records, uint record_length) + { + return filesort_buffer.check_sort_buffer_properties(num_records, + record_length); + } + + void free_sort_buffer() + { filesort_buffer.free_sort_buffer(); } + + void init_record_pointers() + { filesort_buffer.init_record_pointers(); } + + size_t sort_buffer_size() const + { return filesort_buffer.sort_buffer_size(); } }; -#define clear_timestamp_auto_bits(_target_, _bits_) \ - (_target_)= (enum timestamp_auto_set_type)((int)(_target_) & ~(int)(_bits_)) -class Field_timestamp; + class Field_blob; class Table_triggers_list; @@ -462,8 +491,7 @@ TABLE_CATEGORY get_table_category(const LEX_STRING *db, struct TABLE_share; - -extern ulong refresh_version; +struct All_share_tables; typedef struct st_table_field_type { @@ -477,22 +505,11 @@ typedef struct st_table_field_def { uint count; const TABLE_FIELD_TYPE *field; + uint primary_key_parts; + const uint *primary_key_columns; } TABLE_FIELD_DEF; -#ifdef WITH_PARTITION_STORAGE_ENGINE -/** - Partition specific ha_data struct. -*/ -typedef struct st_ha_data_partition -{ - bool auto_inc_initialized; - mysql_mutex_t LOCK_auto_inc; /**< protecting auto_inc val */ - ulonglong next_auto_inc_val; /**< first non reserved value */ -} HA_DATA_PARTITION; -#endif - - class Table_check_intact { protected: @@ -500,7 +517,7 @@ protected: virtual void report_error(uint code, const char *fmt, ...)= 0; public: - Table_check_intact() : has_keys(FALSE) {} + Table_check_intact(bool keys= false) : has_keys(keys) {} virtual ~Table_check_intact() {} /** Checks whether a table is intact. */ @@ -508,6 +525,18 @@ public: }; +/* + If the table isn't valid, report the error to the server log only. +*/ +class Table_check_intact_log_error : public Table_check_intact +{ +protected: + void report_error(uint, const char *fmt, ...); +public: + Table_check_intact_log_error() : Table_check_intact(true) {} +}; + + /** Class representing the fact that some thread waits for table share to be flushed. Is used to represent information about @@ -547,6 +576,35 @@ typedef I_P_List <Wait_for_flush, Wait_for_flush_list; +enum open_frm_error { + OPEN_FRM_OK = 0, + OPEN_FRM_OPEN_ERROR, + OPEN_FRM_READ_ERROR, + OPEN_FRM_CORRUPTED, + OPEN_FRM_DISCOVER, + OPEN_FRM_ERROR_ALREADY_ISSUED, + OPEN_FRM_NOT_A_VIEW, + OPEN_FRM_NOT_A_TABLE, + OPEN_FRM_NEEDS_REBUILD +}; + +/** + Control block to access table statistics loaded + from persistent statistical tables +*/ + +struct TABLE_STATISTICS_CB +{ + MEM_ROOT mem_root; /* MEM_ROOT to allocate statistical data for the table */ + Table_statistics *table_stats; /* Structure to access the statistical data */ + bool stats_can_be_read; /* Memory for statistical data is allocated */ + bool stats_is_read; /* Statistical data for table has been read + from statistical tables */ + bool histograms_can_be_read; + bool histograms_are_read; +}; + + /** This structure is shared between different table objects. There is one instance of table share per one table in the database. @@ -566,14 +624,11 @@ struct TABLE_SHARE TYPELIB fieldnames; /* Pointer to fieldnames */ TYPELIB *intervals; /* pointer to interval info */ mysql_mutex_t LOCK_ha_data; /* To protect access to ha_data */ - TABLE_SHARE *next, **prev; /* Link to unused shares */ + mysql_mutex_t LOCK_share; /* To protect TABLE_SHARE */ - /* - Doubly-linked (back-linked) lists of used and unused TABLE objects - for this share. - */ - I_P_List <TABLE, TABLE_share> used_tables; - I_P_List <TABLE, TABLE_share> free_tables; + TDC_element *tdc; + + LEX_CUSTRING tabledef_version; engine_option_value *option_list; /* text options for table */ ha_table_option_struct *option_struct; /* structure with parsed options */ @@ -581,10 +636,11 @@ struct TABLE_SHARE /* The following is copied to each TABLE on OPEN */ Field **field; Field **found_next_number_field; - Field *timestamp_field; /* Used only during open */ KEY *key_info; /* data of keys in database */ uint *blob_field; /* Index to blobs in Field arrray*/ + TABLE_STATISTICS_CB stats_cb; + uchar *default_values; /* row with default values */ LEX_STRING comment; /* Comment about table */ CHARSET_INFO *table_charset; /* Default charset of string fields */ @@ -615,7 +671,7 @@ struct TABLE_SHARE key_map keys_for_keyread; ha_rows min_rows, max_rows; /* create information */ ulong avg_row_length; /* create information */ - ulong version, mysql_version; + ulong mysql_version; /* 0 if .frm is created before 5.0 */ ulong reclength; /* Recordlength */ /* Stored record length. No generated-only virtual fields are included */ ulong stored_rec_length; @@ -623,8 +679,8 @@ struct TABLE_SHARE plugin_ref db_plugin; /* storage engine plugin */ inline handlerton *db_type() const /* table_type for handler */ { - // DBUG_ASSERT(db_plugin); - return db_plugin ? plugin_data(db_plugin, handlerton*) : NULL; + return is_view ? view_pseudo_hton : + db_plugin ? plugin_hton(db_plugin) : NULL; } enum row_type row_type; /* How rows are stored */ enum tmp_table_type tmp_table; @@ -634,9 +690,10 @@ struct TABLE_SHARE /** Per-page checksums or not. */ enum ha_choice page_checksum; - uint ref_count; /* How many TABLE objects uses this */ - uint blob_ptr_size; /* 4 or 8 */ uint key_block_size; /* create key_block_size, if used */ + uint stats_sample_pages; /* number of pages to sample during + stats estimation, if used, otherwise 0. */ + enum_stats_auto_recalc stats_auto_recalc; /* Automatic recalc of stats. */ uint null_bytes, last_null_bit_pos; /* Same as null_bytes, except that if there is only a 'delete-marker' in @@ -653,7 +710,6 @@ struct TABLE_SHARE uint uniques; /* Number of UNIQUE index */ uint null_fields; /* number of null fields */ uint blob_fields; /* number of blob fields */ - uint timestamp_field_offset; /* Field number for timestamp field */ uint varchar_fields; /* number of varchar fields */ uint db_create_options; /* Create options from database */ uint db_options_in_use; /* Options in use */ @@ -664,10 +720,12 @@ struct TABLE_SHARE uint next_number_index; /* autoincrement key number */ uint next_number_key_offset; /* autoinc keypart offset in a key */ uint next_number_keypart; /* autoinc keypart number in a key */ - uint error, open_errno, errarg; /* error from open_table_def() */ + enum open_frm_error error; /* error from open_table_def() */ + uint open_errno; /* error from open_table_def() */ uint column_bitmap_size; uchar frm_version; uint vfields; /* Number of computed (virtual) fields */ + uint default_fields; /* Number of default fields */ bool use_ext_keys; /* Extended keys can be used */ bool null_field_first; bool system; /* Set if system table (one record) */ @@ -676,9 +734,24 @@ struct TABLE_SHARE bool is_view; bool deleting; /* going to delete this table */ bool can_cmp_whole_record; + bool table_creation_was_logged; ulong table_map_id; /* for row-based replication */ /* + Things that are incompatible between the stored version and the + current version. This is a set of HA_CREATE... bits that can be used + to modify create_info->used_fields for ALTER TABLE. + */ + ulong incompatible_version; + + /** + For shares representing views File_parser object with view + definition read from .FRM file. + */ + const File_parser *view_def; + + + /* Cache for row-based replication table share checks that does not need to be repeated. Possible values are: -1 when cache value is not calculated yet, 0 when table *shall not* be replicated, 1 when @@ -686,13 +759,16 @@ struct TABLE_SHARE */ int cached_row_logging_check; + /* Name of the tablespace used for this table */ + char *tablespace; + #ifdef WITH_PARTITION_STORAGE_ENGINE /* filled in when reading from frm */ bool auto_partitioned; char *partition_info_str; uint partition_info_str_len; uint partition_info_buffer_size; - handlerton *default_part_db_type; + plugin_ref default_part_plugin; #endif /** @@ -707,24 +783,13 @@ struct TABLE_SHARE */ const TABLE_FIELD_DEF *table_field_def_cache; - /** place to store storage engine specific data */ - void *ha_data; - void (*ha_data_destroy)(void *); /* An optional destructor for ha_data */ - -#ifdef WITH_PARTITION_STORAGE_ENGINE - /** place to store partition specific data, LOCK_ha_data hold while init. */ - HA_DATA_PARTITION *ha_part_data; - /* Destructor for ha_part_data */ - void (*ha_part_data_destroy)(HA_DATA_PARTITION *); -#endif + /** Main handler's share */ + Handler_share *ha_share; /** Instrumentation for this table share. */ PSI_table_share *m_psi; - /** - List of tickets representing threads waiting for the share to be flushed. - */ - Wait_for_flush_list m_flush_tickets; + inline void reset() { bzero((void*)this, sizeof(*this)); } /* Set share's table cache key and update its db and table name appropriately. @@ -794,42 +859,6 @@ struct TABLE_SHARE return table_map_id; } - /** Is this table share being expelled from the table definition cache? */ - inline bool has_old_version() const - { - return version != refresh_version; - } - inline bool protected_against_usage() const - { - return version == 0; - } - inline void protect_against_usage() - { - version= 0; - } - /* - This is used only for the case of locked tables, as we want to - allow one to do SHOW commands on them even after ALTER or REPAIR - */ - inline void allow_access_to_protected_table() - { - DBUG_ASSERT(version == 0); - version= 1; - } - /* - Remove from table definition cache at close. - Table can still be opened by SHOW - */ - inline void remove_from_cache_at_close() - { - if (version != 0) /* Don't remove protection */ - version= 1; - } - inline void set_refresh_version() - { - version= refresh_version; - } - /** Convert unrelated members of TABLE_SHARE to one enum representing its type. @@ -851,7 +880,7 @@ struct TABLE_SHARE } /** Return a table metadata version. - * for base tables, we return table_map_id. + * for base tables and views, we return table_map_id. It is assigned from a global counter incremented for each new table loaded into the table definition cache (TDC). * for temporary tables it's table_map_id again. But for @@ -860,7 +889,7 @@ struct TABLE_SHARE counter incremented for every new SQL statement. Since temporary tables are thread-local, each temporary table gets a unique id. - * for everything else (views, information schema tables), + * for everything else (e.g. information schema tables), the version id is zero. This choice of version id is a large compromise @@ -875,8 +904,8 @@ struct TABLE_SHARE version id of a temporary table is never compared with a version id of a view, and vice versa. - Secondly, for base tables, we know that each DDL flushes the - respective share from the TDC. This ensures that whenever + Secondly, for base tables and views, we know that each DDL flushes + the respective share from the TDC. This ensures that whenever a table is altered or dropped and recreated, it gets a new version id. Unfortunately, since elements of the TDC are also flushed on @@ -897,26 +926,6 @@ struct TABLE_SHARE Metadata of information schema tables never changes. Thus we can safely assume 0 for a good enough version id. - Views are a special and tricky case. A view is always inlined - into the parse tree of a prepared statement at prepare. - Thus, when we execute a prepared statement, the parse tree - will not get modified even if the view is replaced with another - view. Therefore, we can safely choose 0 for version id of - views and effectively never invalidate a prepared statement - when a view definition is altered. Note, that this leads to - wrong binary log in statement-based replication, since we log - prepared statement execution in form Query_log_events - containing conventional statements. But since there is no - metadata locking for views, the very same problem exists for - conventional statements alone, as reported in Bug#25144. The only - difference between prepared and conventional execution is, - effectively, that for prepared statements the race condition - window is much wider. - In 6.0 we plan to support view metadata locking (WL#3726) and - extend table definition cache to cache views (WL#4298). - When this is done, views will be handled in the same fashion - as the base tables. - Finally, by taking into account table type, we always track that a change has taken place when a view is replaced with a base table, a base table is replaced with a temporary @@ -926,7 +935,7 @@ struct TABLE_SHARE */ ulong get_table_ref_version() const { - return (tmp_table == SYSTEM_TMP_TABLE || is_view) ? 0 : table_map_id; + return (tmp_table == SYSTEM_TMP_TABLE) ? 0 : table_map_id; } bool visit_subgraph(Wait_for_flush *waiting_ticket, @@ -943,6 +952,94 @@ struct TABLE_SHARE } uint actual_n_key_parts(THD *thd); + + LEX_CUSTRING *frm_image; ///< only during CREATE TABLE (@sa ha_create_table) + + /* + populates TABLE_SHARE from the table description in the binary frm image. + if 'write' is true, this frm image is also written into a corresponding + frm file, that serves as a persistent metadata cache to avoid + discovering the table over and over again + */ + int init_from_binary_frm_image(THD *thd, bool write, + const uchar *frm_image, size_t frm_length); + + /* + populates TABLE_SHARE from the table description, specified as the + complete CREATE TABLE sql statement. + if 'write' is true, this frm image is also written into a corresponding + frm file, that serves as a persistent metadata cache to avoid + discovering the table over and over again + */ + int init_from_sql_statement_string(THD *thd, bool write, + const char *sql, size_t sql_length); + /* + writes the frm image to an frm file, corresponding to this table + */ + bool write_frm_image(const uchar *frm_image, size_t frm_length); + + bool write_frm_image(void) + { return frm_image ? write_frm_image(frm_image->str, frm_image->length) : 0; } + + /* + returns an frm image for this table. + the memory is allocated and must be freed later + */ + bool read_frm_image(const uchar **frm_image, size_t *frm_length); + + /* frees the memory allocated in read_frm_image */ + void free_frm_image(const uchar *frm); +}; + + +/** + Class is used as a BLOB field value storage for + intermediate GROUP_CONCAT results. Used only for + GROUP_CONCAT with DISTINCT or ORDER BY options. + */ + +class Blob_mem_storage: public Sql_alloc +{ +private: + MEM_ROOT storage; + /** + Sign that some values were cut + during saving into the storage. + */ + bool truncated_value; +public: + Blob_mem_storage() :truncated_value(false) + { + init_alloc_root(&storage, MAX_FIELD_VARCHARLENGTH, 0, MYF(0)); + } + ~ Blob_mem_storage() + { + free_root(&storage, MYF(0)); + } + void reset() + { + free_root(&storage, MYF(MY_MARK_BLOCKS_FREE)); + truncated_value= false; + } + /** + Fuction creates duplicate of 'from' + string in 'storage' MEM_ROOT. + + @param from string to copy + @param length string length + + @retval Pointer to the copied string. + @retval 0 if an error occured. + */ + char *store(const char *from, uint length) + { + return (char*) memdup_root(&storage, from, length); + } + void set_truncated_value(bool is_truncated_value) + { + truncated_value= is_truncated_value; + } + bool is_truncated_value() { return truncated_value; } }; @@ -954,6 +1051,7 @@ enum index_hint_type INDEX_HINT_FORCE }; +struct st_cond_statistic; #define CHECK_ROW_FOR_NULLS_TO_REJECT (1 << 0) #define REJECT_ROW_DUE_TO_NULL_FIELDS (1 << 1) @@ -971,17 +1069,18 @@ struct TABLE private: /** - Links for the lists of used/unused TABLE objects for this share. + Links for the list of all TABLE objects for this share. Declared as private to avoid direct manipulation with those objects. One should use methods of I_P_List template instead. */ - TABLE *share_next, **share_prev; - - friend struct TABLE_share; + TABLE *share_all_next, **share_all_prev; + friend struct All_share_tables; public: THD *in_use; /* Which thread uses this */ + /* Time when table was released to table cache. Valid for unused tables. */ + ulonglong tc_time; Field **field; /* Pointer to fields */ uchar *record[2]; /* Pointer to records */ @@ -1014,21 +1113,33 @@ public: Field *next_number_field; /* Set if next_number is activated */ Field *found_next_number_field; /* Set on open */ - Field_timestamp *timestamp_field; Field **vfield; /* Pointer to virtual fields*/ + /* Fields that are updated automatically on INSERT or UPDATE. */ + Field **default_field; /* Table's triggers, 0 if there are no of them */ Table_triggers_list *triggers; TABLE_LIST *pos_in_table_list;/* Element referring to this table */ /* Position in thd->locked_table_list under LOCK TABLES */ TABLE_LIST *pos_in_locked_tables; + + /* + Not-null for temporary tables only. Non-null values means this table is + used to compute GROUP BY, it has a unique of GROUP BY columns. + (set by create_tmp_table) + */ ORDER *group; String alias; /* alias or table name */ uchar *null_flags; - my_bitmap_map *bitmap_init_value; - MY_BITMAP def_read_set, def_write_set, def_vcol_set, tmp_set; + MY_BITMAP def_read_set, def_write_set, tmp_set; + MY_BITMAP def_rpl_write_set; MY_BITMAP eq_join_set; /* used to mark equi-joined fields */ - MY_BITMAP *read_set, *write_set, *vcol_set; /* Active column sets */ + MY_BITMAP cond_set; /* used to mark fields from sargable conditions*/ + /* Active column sets */ + MY_BITMAP *read_set, *write_set, *rpl_write_set; + /* Set if using virtual fields */ + MY_BITMAP *vcol_set, *def_vcol_set; + /* The ID of the query that opened and is using this table. Has different meanings depending on the table type. @@ -1049,13 +1160,27 @@ public: */ query_id_t query_id; + /* + This structure is used for statistical data on the table that + is collected by the function collect_statistics_for_table + */ + Table_statistics *collected_stats; + + /* The estimate of the number of records in the table used by optimizer */ + ha_rows used_stat_records; + /* For each key that has quick_keys.is_set(key) == TRUE: estimate of #records and max #key parts that range access would use. */ ha_rows quick_rows[MAX_KEY]; + double quick_costs[MAX_KEY]; - /* Bitmaps of key parts that =const for the entire join. */ + /* + Bitmaps of key parts that =const for the duration of join execution. If + we're in a subquery, then the constant may be different across subquery + re-executions. + */ key_part_map const_key_parts[MAX_KEY]; uint quick_key_parts[MAX_KEY]; @@ -1071,19 +1196,9 @@ public: */ ha_rows quick_condition_rows; - /* - If this table has TIMESTAMP field with auto-set property (pointed by - timestamp_field member) then this variable indicates during which - operations (insert only/on update/in both cases) we should set this - field to current timestamp. If there are no such field in this table - or we should not automatically set its value during execution of current - statement then the variable contains TIMESTAMP_NO_AUTO_SET (i.e. 0). - - Value of this variable is set for each statement in open_table() and - if needed cleared later in statement processing code (see mysql_update() - as example). - */ - timestamp_auto_set_type timestamp_field_type; + double cond_selectivity; + List<st_cond_statistic> *cond_selectivity_sampling_explain; + table_map map; /* ID bit of table (1,2,4,8,16...) */ uint lock_position; /* Position in MYSQL_LOCK.table */ @@ -1153,7 +1268,12 @@ public: See TABLE_LIST::process_index_hints(). */ bool force_index_group; - bool distinct,const_table,no_rows, used_for_duplicate_elimination; + /* + TRUE<=> this table was created with create_tmp_table(... distinct=TRUE..) + call + */ + bool distinct; + bool const_table,no_rows, used_for_duplicate_elimination; /** Forces DYNAMIC Aria row format for internal temporary tables. */ @@ -1165,6 +1285,9 @@ public: */ bool key_read; bool no_keyread; + /** + If set, indicate that the table is not replicated by the server. + */ bool locked_by_logger; bool no_replicate; bool locked_by_name; @@ -1183,11 +1306,21 @@ public: bool get_fields_in_item_tree; /* Signal to fix_field */ bool m_needs_reopen; bool created; /* For tmp tables. TRUE <=> tmp table was actually created.*/ +#ifdef HAVE_REPLICATION + /* used in RBR Triggers */ + bool master_had_triggers; +#endif REGINFO reginfo; /* field connections */ MEM_ROOT mem_root; + /** + Initialized in Item_func_group_concat::setup for appropriate + temporary table if GROUP_CONCAT is used with ORDER BY | DISTINCT + and BLOB field count > 0. + */ + Blob_mem_storage *blob_storage; GRANT_INFO grant; - FILESORT_INFO sort; + Filesort_info sort; /* The arena which the items for expressions from the table definition are associated with. @@ -1198,11 +1331,15 @@ public: Query_arena *expr_arena; #ifdef WITH_PARTITION_STORAGE_ENGINE partition_info *part_info; /* Partition related information */ - bool no_partitions_used; /* If true, all partitions have been pruned away */ + /* If true, all partitions have been pruned away */ + bool all_partitions_pruned_away; #endif uint max_keys; /* Size of allocated key_info array. */ + bool stats_is_read; /* Persistent statistics is read for the table */ + bool histograms_are_read; MDL_ticket *mdl_ticket; + inline void reset() { bzero((void*)this, sizeof(*this)); } void init(THD *thd, TABLE_LIST *tl); bool fill_item_list(List<Item> *item_list) const; void reset_item_list(List<Item> *item_list) const; @@ -1216,8 +1353,15 @@ public: void mark_columns_needed_for_update(void); void mark_columns_needed_for_delete(void); void mark_columns_needed_for_insert(void); + void mark_columns_per_binlog_row_image(void); bool mark_virtual_col(Field *field); void mark_virtual_columns_for_write(bool insert_fl); + void mark_default_fields_for_write(MY_BITMAP* bset); + inline void mark_default_fields_for_write() + { + mark_default_fields_for_write(write_set); + } + bool has_default_function(bool is_update); inline void column_bitmaps_set(MY_BITMAP *read_set_arg, MY_BITMAP *write_set_arg) { @@ -1258,7 +1402,8 @@ public: { read_set= &def_read_set; write_set= &def_write_set; - vcol_set= &def_vcol_set; + vcol_set= def_vcol_set; /* Note that this may be 0 */ + rpl_write_set= 0; } /** Should this instance of the table be reopened? */ inline bool needs_reopen() @@ -1304,8 +1449,22 @@ public: } bool update_const_key_parts(COND *conds); + + my_ptrdiff_t default_values_offset() const + { return (my_ptrdiff_t) (s->default_values - record[0]); } + uint actual_n_key_parts(KEY *keyinfo); ulong actual_key_flags(KEY *keyinfo); + int update_default_fields(); + void reset_default_fields(); + inline ha_rows stat_records() { return used_stat_records; } + + void prepare_triggers_for_insert_stmt_or_event(); + bool prepare_triggers_for_delete_stmt_or_event(); + bool prepare_triggers_for_update_stmt_or_event(); + + inline Field **field_to_fill(); + bool validate_default_values_of_unset_fields(THD *thd) const; }; @@ -1318,11 +1477,24 @@ struct TABLE_share { static inline TABLE **next_ptr(TABLE *l) { - return &l->share_next; + return &l->next; + } + static inline TABLE ***prev_ptr(TABLE *l) + { + return (TABLE ***) &l->prev; + } +}; + + +struct All_share_tables +{ + static inline TABLE **next_ptr(TABLE *l) + { + return &l->share_all_next; } static inline TABLE ***prev_ptr(TABLE *l) { - return &l->share_prev; + return &l->share_all_prev; } }; @@ -1334,6 +1506,9 @@ enum enum_schema_table_state PROCESSED_BY_JOIN_EXEC }; +enum enum_fk_option { FK_OPTION_UNDEF, FK_OPTION_RESTRICT, FK_OPTION_CASCADE, + FK_OPTION_SET_NULL, FK_OPTION_NO_ACTION, FK_OPTION_SET_DEFAULT}; + typedef struct st_foreign_key_info { LEX_STRING *foreign_id; @@ -1341,13 +1516,16 @@ typedef struct st_foreign_key_info LEX_STRING *foreign_table; LEX_STRING *referenced_db; LEX_STRING *referenced_table; - LEX_STRING *update_method; - LEX_STRING *delete_method; + enum_fk_option update_method; + enum_fk_option delete_method; LEX_STRING *referenced_key_name; List<LEX_STRING> foreign_fields; List<LEX_STRING> referenced_fields; } FOREIGN_KEY_INFO; +LEX_CSTRING *fk_option_name(enum_fk_option opt); +bool fk_modifies_child(enum_fk_option opt); + #define MY_I_S_MAYBE_NULL 1 #define MY_I_S_UNSIGNED 2 @@ -1399,8 +1577,8 @@ typedef struct st_schema_table { const char* table_name; ST_FIELD_INFO *fields_info; - /* Create information_schema table */ - TABLE *(*create_table) (THD *thd, TABLE_LIST *table_list); + /* for FLUSH table_name */ + int (*reset_table) (); /* Fill table with data */ int (*fill_table) (THD *thd, TABLE_LIST *tables, COND *cond); /* Handle fileds for old SHOW */ @@ -1412,6 +1590,7 @@ typedef struct st_schema_table uint i_s_requested_object; /* the object we need to open(TABLE | VIEW) */ } ST_SCHEMA_TABLE; +class IS_table_read_plan; /* Types of derived tables. The ending part is a bitmap of phases that are @@ -1585,8 +1764,9 @@ struct TABLE_LIST /** Prepare TABLE_LIST that consists of one table instance to use in - simple_open_and_lock_tables + open_and_lock_tables */ + inline void reset() { bzero((void*)this, sizeof(*this)); } inline void init_one_table(const char *db_name_arg, size_t db_length_arg, const char *table_name_arg, @@ -1594,17 +1774,46 @@ struct TABLE_LIST const char *alias_arg, enum thr_lock_type lock_type_arg) { - bzero((char*) this, sizeof(*this)); + enum enum_mdl_type mdl_type; + if (lock_type_arg >= TL_WRITE_ALLOW_WRITE) + mdl_type= MDL_SHARED_WRITE; + else if (lock_type_arg == TL_READ_NO_INSERT) + mdl_type= MDL_SHARED_NO_WRITE; + else + mdl_type= MDL_SHARED_READ; + + reset(); db= (char*) db_name_arg; db_length= db_length_arg; table_name= (char*) table_name_arg; table_name_length= table_name_length_arg; alias= (char*) (alias_arg ? alias_arg : table_name_arg); lock_type= lock_type_arg; - mdl_request.init(MDL_key::TABLE, db, table_name, - (lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_WRITE : MDL_SHARED_READ, - MDL_TRANSACTION); + mdl_request.init(MDL_key::TABLE, db, table_name, mdl_type, MDL_TRANSACTION); + } + + inline void init_one_table_for_prelocking(const char *db_name_arg, + size_t db_length_arg, + const char *table_name_arg, + size_t table_name_length_arg, + const char *alias_arg, + enum thr_lock_type lock_type_arg, + bool routine, + TABLE_LIST *belong_to_view_arg, + uint8 trg_event_map_arg, + TABLE_LIST ***last_ptr) + { + init_one_table(db_name_arg, db_length_arg, table_name_arg, + table_name_length_arg, alias_arg, lock_type_arg); + cacheable_table= 1; + prelocking_placeholder= routine ? ROUTINE : FK; + open_type= routine ? OT_TEMPORARY_OR_BASE : OT_BASE_ONLY; + belong_to_view= belong_to_view_arg; + trg_event_map= trg_event_map_arg; + + **last_ptr= this; + prev_global= *last_ptr; + *last_ptr= &next_global; } /* @@ -1686,7 +1895,7 @@ struct TABLE_LIST /* Index names in a "... JOIN ... USE/IGNORE INDEX ..." clause. */ List<Index_hint> *index_hints; TABLE *table; /* opened table */ - uint table_id; /* table id (from binlog) for opened table */ + ulonglong table_id; /* table id (from binlog) for opened table */ /* select_result for derived table to pass it from table creation to table filling procedure @@ -1852,6 +2061,7 @@ struct TABLE_LIST bool updating; /* for replicate-do/ignore table */ bool force_index; /* prefer index over table scan */ bool ignore_leaves; /* preload only non-leaf nodes */ + bool crashed; /* Table was found crashed */ table_map dep_tables; /* tables the table depends on */ table_map on_expr_dep_tables; /* tables on expression depends on */ struct st_nested_join *nested_join; /* if the element is a nested join */ @@ -1882,12 +2092,12 @@ struct TABLE_LIST This TABLE_LIST object is just placeholder for prelocking, it will be used for implicit LOCK TABLES only and won't be used in real statement. */ - bool prelocking_placeholder; + enum { USER, ROUTINE, FK } prelocking_placeholder; /** Indicates that if TABLE_LIST object corresponds to the table/view which requires special handling. */ - enum + enum enum_open_strategy { /* Normal open. */ OPEN_NORMAL= 0, @@ -1899,7 +2109,6 @@ struct TABLE_LIST /* For transactional locking. */ int lock_timeout; /* NOWAIT or WAIT [X] */ bool lock_transactional; /* If transactional lock requested. */ - bool internal_tmp_table; /** TRUE if an alias for this table was specified in the SQL. */ bool is_alias; /** TRUE if the table is referred to in the statement using a fully @@ -1961,14 +2170,30 @@ struct TABLE_LIST /* TRUE <=> this table is a const one and was optimized away. */ bool optimized_away; + /* I_S: Flags to open_table (e.g. OPEN_TABLE_ONLY or OPEN_VIEW_ONLY) */ uint i_s_requested_object; - bool has_db_lookup_value; - bool has_table_lookup_value; + + /* + I_S: how to read the tables (SKIP_OPEN_TABLE/OPEN_FRM_ONLY/OPEN_FULL_TABLE) + */ uint table_open_method; + /* + I_S: where the schema table was filled + (this is a hack. The code should be able to figure out whether reading + from I_S should be done by create_sort_index() or by JOIN::exec.) + */ enum enum_schema_table_state schema_table_state; + /* Something like a "query plan" for reading INFORMATION_SCHEMA table */ + IS_table_read_plan *is_table_read_plan; + MDL_request mdl_request; +#ifdef WITH_PARTITION_STORAGE_ENGINE + /* List to carry partition names from PARTITION (...) clause in statement */ + List<String> *partition_names; +#endif /* WITH_PARTITION_STORAGE_ENGINE */ + void calc_md5(char *buffer); int view_check_option(THD *thd, bool ignore_failure); bool create_field_translation(THD *thd); @@ -2028,7 +2253,7 @@ struct TABLE_LIST bool prepare_security(THD *thd); #ifndef NO_EMBEDDED_ACCESS_CHECKS Security_context *find_view_security_context(THD *thd); - bool prepare_view_securety_context(THD *thd); + bool prepare_view_security_context(THD *thd); #endif /* Cleanup for re-execution in a prepared statement or a stored @@ -2051,8 +2276,7 @@ struct TABLE_LIST @sa check_and_update_table_version() */ - inline - bool is_table_ref_id_equal(TABLE_SHARE *s) const + inline bool is_table_ref_id_equal(TABLE_SHARE *s) const { return (m_table_ref_type == s->get_table_ref_type() && m_table_ref_version == s->get_table_ref_version()); @@ -2064,12 +2288,10 @@ struct TABLE_LIST @sa check_and_update_table_version() */ - inline - void set_table_ref_id(TABLE_SHARE *s) + inline void set_table_ref_id(TABLE_SHARE *s) { set_table_ref_id(s->get_table_ref_type(), s->get_table_ref_version()); } - inline - void set_table_ref_id(enum_table_ref_type table_ref_type_arg, + inline void set_table_ref_id(enum_table_ref_type table_ref_type_arg, ulong table_ref_version_arg) { m_table_ref_type= table_ref_type_arg; @@ -2153,7 +2375,7 @@ struct TABLE_LIST @brief Returns the name of the database that the referenced table belongs to. */ - char *get_db_name() { return view != NULL ? view_db.str : db; } + char *get_db_name() const { return view != NULL ? view_db.str : db; } /** @brief Returns the name of the table that this TABLE_LIST represents. @@ -2161,9 +2383,9 @@ struct TABLE_LIST @details The unqualified table name or view name for a table or view, respectively. */ - char *get_table_name() { return view != NULL ? view_name.str : table_name; } + char *get_table_name() const { return view != NULL ? view_name.str : table_name; } bool is_active_sjm(); - bool is_jtbm() { return test(jtbm_subselect!=NULL); } + bool is_jtbm() { return MY_TEST(jtbm_subselect != NULL); } st_select_lex_unit *get_unit(); st_select_lex *get_single_select(); void wrap_into_nested_join(List<TABLE_LIST> &join_list); @@ -2203,9 +2425,9 @@ private: #else inline void set_check_merged() {} #endif - /** See comments for set_metadata_id() */ + /** See comments for set_table_ref_id() */ enum enum_table_ref_type m_table_ref_type; - /** See comments for set_metadata_id() */ + /** See comments for set_table_ref_id() */ ulong m_table_ref_version; }; @@ -2459,26 +2681,38 @@ static inline void dbug_tmp_restore_column_maps(MY_BITMAP *read_set, #endif } +bool ok_for_lower_case_names(const char *names); -size_t max_row_length(TABLE *table, const uchar *data); +enum get_table_share_flags { + GTS_TABLE = 1, + GTS_VIEW = 2, + GTS_NOLOCK = 4, + GTS_USE_DISCOVERY = 8, + GTS_FORCE_DISCOVERY = 16 +}; +size_t max_row_length(TABLE *table, const uchar *data); void init_mdl_requests(TABLE_LIST *table_list); -int open_table_from_share(THD *thd, TABLE_SHARE *share, const char *alias, - uint db_stat, uint prgflag, uint ha_open_flags, - TABLE *outparam, bool is_create_table); +enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share, + const char *alias, uint db_stat, uint prgflag, + uint ha_open_flags, TABLE *outparam, + bool is_create_table); bool unpack_vcol_info_from_frm(THD *thd, MEM_ROOT *mem_root, TABLE *table, Field *field, LEX_STRING *vcol_expr, bool *error_reported); -TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, - uint key_length); +TABLE_SHARE *alloc_table_share(const char *db, const char *table_name, + const char *key, uint key_length); void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key, uint key_length, const char *table_name, const char *path); void free_table_share(TABLE_SHARE *share); -int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags); -void open_table_error(TABLE_SHARE *share, int error, int db_errno, int errarg); +enum open_frm_error open_table_def(THD *thd, TABLE_SHARE *share, + uint flags = GTS_TABLE); + +void open_table_error(TABLE_SHARE *share, enum open_frm_error error, + int db_errno); void update_create_info_from_table(HA_CREATE_INFO *info, TABLE *form); bool check_and_convert_db_name(LEX_STRING *db, bool preserve_lettercase); bool check_db_name(LEX_STRING *db); @@ -2488,21 +2722,28 @@ int rename_file_ext(const char * from,const char * to,const char * ext); char *get_field(MEM_ROOT *mem, Field *field); bool get_field(MEM_ROOT *mem, Field *field, class String *res); +bool validate_comment_length(THD *thd, LEX_STRING *comment, size_t max_len, + uint err_code, const char *name); + int closefrm(TABLE *table, bool free_share); -int read_string(File file, uchar* *to, size_t length); void free_blobs(TABLE *table); void free_field_buffers_larger_than(TABLE *table, uint32 size); -int set_zone(int nr,int min_zone,int max_zone); ulong get_form_pos(File file, uchar *head, TYPELIB *save_names); -ulong make_new_entry(File file,uchar *fileinfo,TYPELIB *formnames, - const char *newname); -ulong next_io_size(ulong pos); void append_unescaped(String *res, const char *pos, uint length); -File create_frm(THD *thd, const char *name, const char *db, - const char *table, uint reclength, uchar *fileinfo, - HA_CREATE_INFO *create_info, uint keys, KEY *key_info); +void prepare_frm_header(THD *thd, uint reclength, uchar *fileinfo, + HA_CREATE_INFO *create_info, uint keys, KEY *key_info); char *fn_rext(char *name); +/* Check that the integer is in the internal */ +static inline int set_zone(int nr,int min_zone,int max_zone) +{ + if (nr <= min_zone) + return min_zone; + if (nr >= max_zone) + return max_zone; + return nr; +} + /* performance schema */ extern LEX_STRING PERFORMANCE_SCHEMA_DB_NAME; diff --git a/sql/table_cache.cc b/sql/table_cache.cc new file mode 100644 index 00000000000..b3cf6cd2892 --- /dev/null +++ b/sql/table_cache.cc @@ -0,0 +1,1152 @@ +/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. + Copyright (c) 2010, 2011 Monty Program Ab + Copyright (C) 2013 Sergey Vojtovich and MariaDB Foundation + + 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 */ + +/** + @file + Table definition cache and table cache implementation. + + Table definition cache actions: + - add new TABLE_SHARE object to cache (tdc_acquire_share()) + - acquire TABLE_SHARE object from cache (tdc_acquire_share()) + - release TABLE_SHARE object to cache (tdc_release_share()) + - purge unused TABLE_SHARE objects from cache (tdc_purge()) + - remove TABLE_SHARE object from cache (tdc_remove_table()) + - get number of TABLE_SHARE objects in cache (tdc_records()) + + Table cache actions: + - add new TABLE object to cache (tc_add_table()) + - acquire TABLE object from cache (tc_acquire_table()) + - release TABLE object to cache (tc_release_table()) + - purge unused TABLE objects from cache (tc_purge()) + - purge unused TABLE objects of a table from cache (tdc_remove_table()) + - get number of TABLE objects in cache (tc_records()) + + Dependencies: + - intern_close_table(): frees TABLE object + - kill_delayed_threads_for_table() + - close_cached_tables(): flush tables on shutdown + - alloc_table_share() + - free_table_share() + + Table cache invariants: + - TABLE_SHARE::free_tables shall not contain objects with TABLE::in_use != 0 + - TABLE_SHARE::free_tables shall not receive new objects if + TABLE_SHARE::tdc.flushed is true +*/ + +#include "my_global.h" +#include "lf.h" +#include "table.h" +#include "sql_base.h" + + +/** Configuration. */ +ulong tdc_size; /**< Table definition cache threshold for LRU eviction. */ +ulong tc_size; /**< Table cache threshold for LRU eviction. */ + +/** Data collections. */ +static LF_HASH tdc_hash; /**< Collection of TABLE_SHARE objects. */ +/** Collection of unused TABLE_SHARE objects. */ +I_P_List <TDC_element, + I_P_List_adapter<TDC_element, &TDC_element::next, &TDC_element::prev>, + I_P_List_null_counter, + I_P_List_fast_push_back<TDC_element> > unused_shares; + +static int64 tdc_version; /* Increments on each reload */ +static int64 last_table_id; +static bool tdc_inited; + +static int32 tc_count; /**< Number of TABLE objects in table cache. */ + + +/** + Protects unused shares list. + + TDC_element::prev + TDC_element::next + unused_shares +*/ + +static mysql_mutex_t LOCK_unused_shares; + +#ifdef HAVE_PSI_INTERFACE +PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share; +static PSI_mutex_info all_tc_mutexes[]= +{ + { &key_LOCK_unused_shares, "LOCK_unused_shares", PSI_FLAG_GLOBAL }, + { &key_TABLE_SHARE_LOCK_table_share, "TABLE_SHARE::tdc.LOCK_table_share", 0 } +}; + +PSI_cond_key key_TABLE_SHARE_COND_release; +static PSI_cond_info all_tc_conds[]= +{ + { &key_TABLE_SHARE_COND_release, "TABLE_SHARE::tdc.COND_release", 0 } +}; + + +static void init_tc_psi_keys(void) +{ + const char *category= "sql"; + int count; + + count= array_elements(all_tc_mutexes); + mysql_mutex_register(category, all_tc_mutexes, count); + + count= array_elements(all_tc_conds); + mysql_cond_register(category, all_tc_conds, count); +} +#endif + + +static int fix_thd_pins(THD *thd) +{ + return thd->tdc_hash_pins ? 0 : + (thd->tdc_hash_pins= lf_hash_get_pins(&tdc_hash)) == 0; +} + + +/* + Auxiliary routines for manipulating with per-share all/unused lists + and tc_count counter. + Responsible for preserving invariants between those lists, counter + and TABLE::in_use member. + In fact those routines implement sort of implicit table cache as + part of table definition cache. +*/ + + +/** + Get number of TABLE objects (used and unused) in table cache. +*/ + +uint tc_records(void) +{ + return my_atomic_load32_explicit(&tc_count, MY_MEMORY_ORDER_RELAXED); +} + + +/** + Remove TABLE object from table cache. + + - decrement tc_count + - remove object from TABLE_SHARE::tdc.all_tables +*/ + +static void tc_remove_table(TABLE *table) +{ + my_atomic_add32_explicit(&tc_count, -1, MY_MEMORY_ORDER_RELAXED); + table->s->tdc->all_tables.remove(table); +} + + +/** + Free all unused TABLE objects. + + While locked: + - remove unused objects from TABLE_SHARE::tdc.free_tables and + TABLE_SHARE::tdc.all_tables + - decrement tc_count + + While unlocked: + - free resources related to unused objects + + @note This is called by 'handle_manager' when one wants to + periodicly flush all not used tables. +*/ + +struct tc_purge_arg +{ + TDC_element::TABLE_list purge_tables; + bool mark_flushed; +}; + + +static my_bool tc_purge_callback(TDC_element *element, tc_purge_arg *arg) +{ + TABLE *table; + + mysql_mutex_lock(&element->LOCK_table_share); + element->wait_for_mdl_deadlock_detector(); + if (arg->mark_flushed) + element->flushed= true; + while ((table= element->free_tables.pop_front())) + { + tc_remove_table(table); + arg->purge_tables.push_front(table); + } + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} + + +void tc_purge(bool mark_flushed) +{ + tc_purge_arg argument; + TABLE *table; + + argument.mark_flushed= mark_flushed; + tdc_iterate(0, (my_hash_walk_action) tc_purge_callback, &argument); + while ((table= argument.purge_tables.pop_front())) + intern_close_table(table); +} + + +/** + Add new TABLE object to table cache. + + @pre TABLE object is used by caller. + + Added object cannot be evicted or acquired. + + While locked: + - add object to TABLE_SHARE::tdc.all_tables + - increment tc_count + - evict LRU object from table cache if we reached threshold + + While unlocked: + - free evicted object +*/ + +struct tc_add_table_arg +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + ulonglong purge_time; +}; + + +my_bool tc_add_table_callback(TDC_element *element, tc_add_table_arg *arg) +{ + TABLE *table; + + mysql_mutex_lock(&element->LOCK_table_share); + if ((table= element->free_tables_back()) && table->tc_time < arg->purge_time) + { + memcpy(arg->key, element->m_key, element->m_key_length); + arg->key_length= element->m_key_length; + arg->purge_time= table->tc_time; + } + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} + + +void tc_add_table(THD *thd, TABLE *table) +{ + bool need_purge; + DBUG_ASSERT(table->in_use == thd); + mysql_mutex_lock(&table->s->tdc->LOCK_table_share); + table->s->tdc->wait_for_mdl_deadlock_detector(); + table->s->tdc->all_tables.push_front(table); + mysql_mutex_unlock(&table->s->tdc->LOCK_table_share); + + /* If we have too many TABLE instances around, try to get rid of them */ + need_purge= my_atomic_add32_explicit(&tc_count, 1, MY_MEMORY_ORDER_RELAXED) >= + (int32) tc_size; + + if (need_purge) + { + tc_add_table_arg argument; + argument.purge_time= ULONGLONG_MAX; + tdc_iterate(thd, (my_hash_walk_action) tc_add_table_callback, &argument); + + if (argument.purge_time != ULONGLONG_MAX) + { + TDC_element *element= (TDC_element*) lf_hash_search(&tdc_hash, + thd->tdc_hash_pins, + argument.key, + argument.key_length); + if (element) + { + TABLE *entry; + mysql_mutex_lock(&element->LOCK_table_share); + lf_hash_search_unpin(thd->tdc_hash_pins); + element->wait_for_mdl_deadlock_detector(); + + /* + It may happen that oldest table was acquired meanwhile. In this case + just go ahead, number of objects in table cache will normalize + eventually. + */ + if ((entry= element->free_tables_back()) && + entry->tc_time == argument.purge_time) + { + element->free_tables.remove(entry); + tc_remove_table(entry); + mysql_mutex_unlock(&element->LOCK_table_share); + intern_close_table(entry); + } + else + mysql_mutex_unlock(&element->LOCK_table_share); + } + } + } +} + + +/** + Release TABLE object to table cache. + + @pre object is used by caller. + + Released object may be evicted or acquired again. + + While locked: + - if object is marked for purge, decrement tc_count + - add object to TABLE_SHARE::tdc.free_tables + - evict LRU object from table cache if we reached threshold + + While unlocked: + - mark object not in use by any thread + - free evicted/purged object + + @note Another thread may mark share for purge any moment (even + after version check). It means to-be-purged object may go to + unused lists. This other thread is expected to call tc_purge(), + which is synchronized with us on TABLE_SHARE::tdc.LOCK_table_share. + + @return + @retval true object purged + @retval false object released +*/ + +bool tc_release_table(TABLE *table) +{ + DBUG_ASSERT(table->in_use); + DBUG_ASSERT(table->file); + DBUG_ASSERT(!table->pos_in_locked_tables); + + if (table->needs_reopen() || tc_records() > tc_size) + { + mysql_mutex_lock(&table->s->tdc->LOCK_table_share); + goto purge; + } + + table->tc_time= my_interval_timer(); + + mysql_mutex_lock(&table->s->tdc->LOCK_table_share); + if (table->s->tdc->flushed) + goto purge; + /* + in_use doesn't really need mutex protection, but must be reset after + checking tdc.flushed and before this table appears in free_tables. + Resetting in_use is needed only for print_cached_tables() and + list_open_tables(). + */ + table->in_use= 0; + /* Add table to the list of unused TABLE objects for this share. */ + table->s->tdc->free_tables.push_front(table); + mysql_mutex_unlock(&table->s->tdc->LOCK_table_share); + return false; + +purge: + table->s->tdc->wait_for_mdl_deadlock_detector(); + tc_remove_table(table); + mysql_mutex_unlock(&table->s->tdc->LOCK_table_share); + table->in_use= 0; + intern_close_table(table); + return true; +} + + +/** + Delete share from hash and free share object. +*/ + +static void tdc_delete_share_from_hash(TDC_element *element) +{ + THD *thd= current_thd; + LF_PINS *pins; + TABLE_SHARE *share; + DBUG_ENTER("tdc_delete_share_from_hash"); + + mysql_mutex_assert_owner(&element->LOCK_table_share); + share= element->share; + DBUG_ASSERT(share); + element->share= 0; + PSI_CALL_release_table_share(share->m_psi); + share->m_psi= 0; + + if (!element->m_flush_tickets.is_empty()) + { + Wait_for_flush_list::Iterator it(element->m_flush_tickets); + Wait_for_flush *ticket; + while ((ticket= it++)) + (void) ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED); + + do + { + mysql_cond_wait(&element->COND_release, &element->LOCK_table_share); + } while (!element->m_flush_tickets.is_empty()); + } + + mysql_mutex_unlock(&element->LOCK_table_share); + + if (thd) + { + fix_thd_pins(thd); + pins= thd->tdc_hash_pins; + } + else + pins= lf_hash_get_pins(&tdc_hash); + + DBUG_ASSERT(pins); // What can we do about it? + element->assert_clean_share(); + lf_hash_delete(&tdc_hash, pins, element->m_key, element->m_key_length); + if (!thd) + lf_hash_put_pins(pins); + free_table_share(share); + DBUG_VOID_RETURN; +} + + +/** + Initialize table definition cache. +*/ + +void tdc_init(void) +{ + DBUG_ENTER("tdc_init"); +#ifdef HAVE_PSI_INTERFACE + init_tc_psi_keys(); +#endif + tdc_inited= true; + mysql_mutex_init(key_LOCK_unused_shares, &LOCK_unused_shares, + MY_MUTEX_INIT_FAST); + tdc_version= 1L; /* Increments on each reload */ + lf_hash_init(&tdc_hash, sizeof(TDC_element), LF_HASH_UNIQUE, 0, 0, + (my_hash_get_key) TDC_element::key, + &my_charset_bin); + tdc_hash.alloc.constructor= TDC_element::lf_alloc_constructor; + tdc_hash.alloc.destructor= TDC_element::lf_alloc_destructor; + tdc_hash.initializer= (lf_hash_initializer) TDC_element::lf_hash_initializer; + DBUG_VOID_RETURN; +} + + +/** + Notify table definition cache that process of shutting down server + has started so it has to keep number of TABLE and TABLE_SHARE objects + minimal in order to reduce number of references to pluggable engines. +*/ + +void tdc_start_shutdown(void) +{ + DBUG_ENTER("table_def_start_shutdown"); + if (tdc_inited) + { + /* + Ensure that TABLE and TABLE_SHARE objects which are created for + tables that are open during process of plugins' shutdown are + immediately released. This keeps number of references to engine + plugins minimal and allows shutdown to proceed smoothly. + */ + tdc_size= 0; + tc_size= 0; + /* Free all cached but unused TABLEs and TABLE_SHAREs. */ + close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT); + } + DBUG_VOID_RETURN; +} + + +/** + Deinitialize table definition cache. +*/ + +void tdc_deinit(void) +{ + DBUG_ENTER("tdc_deinit"); + if (tdc_inited) + { + tdc_inited= false; + lf_hash_destroy(&tdc_hash); + mysql_mutex_destroy(&LOCK_unused_shares); + } + DBUG_VOID_RETURN; +} + + +/** + Get number of cached table definitions. + + @return Number of cached table definitions +*/ + +ulong tdc_records(void) +{ + return my_atomic_load32_explicit(&tdc_hash.count, MY_MEMORY_ORDER_RELAXED); +} + + +void tdc_purge(bool all) +{ + DBUG_ENTER("tdc_purge"); + while (all || tdc_records() > tdc_size) + { + TDC_element *element; + + mysql_mutex_lock(&LOCK_unused_shares); + if (!(element= unused_shares.pop_front())) + { + mysql_mutex_unlock(&LOCK_unused_shares); + break; + } + + /* Concurrent thread may start using share again, reset prev and next. */ + element->prev= 0; + element->next= 0; + mysql_mutex_lock(&element->LOCK_table_share); + if (element->ref_count) + { + mysql_mutex_unlock(&element->LOCK_table_share); + mysql_mutex_unlock(&LOCK_unused_shares); + continue; + } + mysql_mutex_unlock(&LOCK_unused_shares); + + tdc_delete_share_from_hash(element); + } + DBUG_VOID_RETURN; +} + + +/** + Lock table share. + + Find table share with given db.table_name in table definition cache. Return + locked table share if found. + + Locked table share means: + - table share is protected against removal from table definition cache + - no other thread can acquire/release table share + + Caller is expected to unlock table share with tdc_unlock_share(). + + @retval 0 Share not found + @retval MY_ERRPTR OOM + @retval ptr Pointer to locked table share +*/ + +TDC_element *tdc_lock_share(THD *thd, const char *db, const char *table_name) +{ + TDC_element *element; + char key[MAX_DBKEY_LENGTH]; + + DBUG_ENTER("tdc_lock_share"); + if (fix_thd_pins(thd)) + DBUG_RETURN((TDC_element*) MY_ERRPTR); + + element= (TDC_element *) lf_hash_search(&tdc_hash, thd->tdc_hash_pins, + (uchar*) key, + tdc_create_key(key, db, table_name)); + if (element) + { + mysql_mutex_lock(&element->LOCK_table_share); + if (!element->share || element->share->error) + { + mysql_mutex_unlock(&element->LOCK_table_share); + element= 0; + } + lf_hash_search_unpin(thd->tdc_hash_pins); + } + + DBUG_RETURN(element); +} + + +/** + Unlock share locked by tdc_lock_share(). +*/ + +void tdc_unlock_share(TDC_element *element) +{ + DBUG_ENTER("tdc_unlock_share"); + mysql_mutex_unlock(&element->LOCK_table_share); + DBUG_VOID_RETURN; +} + + +/* + Get TABLE_SHARE for a table. + + tdc_acquire_share() + thd Thread handle + table_list Table that should be opened + key Table cache key + key_length Length of key + flags operation: what to open table or view + + IMPLEMENTATION + Get a table definition from the table definition cache. + If it doesn't exist, create a new from the table definition file. + + RETURN + 0 Error + # Share for table +*/ + +TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name, + const char *key, uint key_length, + my_hash_value_type hash_value, uint flags, + TABLE **out_table) +{ + TABLE_SHARE *share; + TDC_element *element; + bool was_unused; + DBUG_ENTER("tdc_acquire_share"); + + if (fix_thd_pins(thd)) + DBUG_RETURN(0); + +retry: + while (!(element= (TDC_element*) lf_hash_search_using_hash_value(&tdc_hash, + thd->tdc_hash_pins, hash_value, (uchar*) key, key_length))) + { + LEX_STRING tmp= { const_cast<char*>(key), key_length }; + int res= lf_hash_insert(&tdc_hash, thd->tdc_hash_pins, (uchar*) &tmp); + + if (res == -1) + DBUG_RETURN(0); + else if (res == 1) + continue; + + element= (TDC_element*) lf_hash_search_using_hash_value(&tdc_hash, + thd->tdc_hash_pins, hash_value, (uchar*) key, key_length); + lf_hash_search_unpin(thd->tdc_hash_pins); + DBUG_ASSERT(element); + + if (!(share= alloc_table_share(db, table_name, key, key_length))) + { + lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length); + DBUG_RETURN(0); + } + + /* note that tdc_acquire_share() *always* uses discovery */ + open_table_def(thd, share, flags | GTS_USE_DISCOVERY); + + if (share->error) + { + free_table_share(share); + lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length); + DBUG_RETURN(0); + } + + mysql_mutex_lock(&element->LOCK_table_share); + element->share= share; + share->tdc= element; + element->ref_count++; + element->version= tdc_refresh_version(); + element->flushed= false; + mysql_mutex_unlock(&element->LOCK_table_share); + + tdc_purge(false); + if (out_table) + *out_table= 0; + share->m_psi= PSI_CALL_get_table_share(false, share); + goto end; + } + + /* cannot force discovery of a cached share */ + DBUG_ASSERT(!(flags & GTS_FORCE_DISCOVERY)); + + if (out_table && (flags & GTS_TABLE)) + { + if ((*out_table= element->acquire_table(thd))) + { + lf_hash_search_unpin(thd->tdc_hash_pins); + DBUG_ASSERT(!(flags & GTS_NOLOCK)); + DBUG_ASSERT(element->share); + DBUG_ASSERT(!element->share->error); + DBUG_ASSERT(!element->share->is_view); + DBUG_RETURN(element->share); + } + } + + mysql_mutex_lock(&element->LOCK_table_share); + if (!(share= element->share)) + { + mysql_mutex_unlock(&element->LOCK_table_share); + lf_hash_search_unpin(thd->tdc_hash_pins); + goto retry; + } + lf_hash_search_unpin(thd->tdc_hash_pins); + + /* + We found an existing table definition. Return it if we didn't get + an error when reading the table definition from file. + */ + if (share->error) + { + open_table_error(share, share->error, share->open_errno); + goto err; + } + + if (share->is_view && !(flags & GTS_VIEW)) + { + open_table_error(share, OPEN_FRM_NOT_A_TABLE, ENOENT); + goto err; + } + if (!share->is_view && !(flags & GTS_TABLE)) + { + open_table_error(share, OPEN_FRM_NOT_A_VIEW, ENOENT); + goto err; + } + + was_unused= !element->ref_count; + element->ref_count++; + mysql_mutex_unlock(&element->LOCK_table_share); + if (was_unused) + { + mysql_mutex_lock(&LOCK_unused_shares); + if (element->prev) + { + /* + Share was not used before and it was in the old_unused_share list + Unlink share from this list + */ + DBUG_PRINT("info", ("Unlinking from not used list")); + unused_shares.remove(element); + element->next= 0; + element->prev= 0; + } + mysql_mutex_unlock(&LOCK_unused_shares); + } + +end: + DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", + (ulong) share, share->tdc->ref_count)); + if (flags & GTS_NOLOCK) + { + tdc_release_share(share); + /* + if GTS_NOLOCK is requested, the returned share pointer cannot be used, + the share it points to may go away any moment. + But perhaps the caller is only interested to know whether a share or + table existed? + Let's return an invalid pointer here to catch dereferencing attempts. + */ + share= (TABLE_SHARE*) 1; + } + DBUG_RETURN(share); + +err: + mysql_mutex_unlock(&element->LOCK_table_share); + DBUG_RETURN(0); +} + + +/** + Release table share acquired by tdc_acquire_share(). +*/ + +void tdc_release_share(TABLE_SHARE *share) +{ + DBUG_ENTER("tdc_release_share"); + + mysql_mutex_lock(&share->tdc->LOCK_table_share); + DBUG_PRINT("enter", + ("share: 0x%lx table: %s.%s ref_count: %u version: %lu", + (ulong) share, share->db.str, share->table_name.str, + share->tdc->ref_count, share->tdc->version)); + DBUG_ASSERT(share->tdc->ref_count); + + if (share->tdc->ref_count > 1) + { + share->tdc->ref_count--; + if (!share->is_view) + mysql_cond_broadcast(&share->tdc->COND_release); + mysql_mutex_unlock(&share->tdc->LOCK_table_share); + DBUG_VOID_RETURN; + } + mysql_mutex_unlock(&share->tdc->LOCK_table_share); + + mysql_mutex_lock(&LOCK_unused_shares); + mysql_mutex_lock(&share->tdc->LOCK_table_share); + if (--share->tdc->ref_count) + { + if (!share->is_view) + mysql_cond_broadcast(&share->tdc->COND_release); + mysql_mutex_unlock(&share->tdc->LOCK_table_share); + mysql_mutex_unlock(&LOCK_unused_shares); + DBUG_VOID_RETURN; + } + if (share->tdc->flushed || tdc_records() > tdc_size) + { + mysql_mutex_unlock(&LOCK_unused_shares); + tdc_delete_share_from_hash(share->tdc); + DBUG_VOID_RETURN; + } + /* Link share last in used_table_share list */ + DBUG_PRINT("info", ("moving share to unused list")); + DBUG_ASSERT(share->tdc->next == 0); + unused_shares.push_back(share->tdc); + mysql_mutex_unlock(&share->tdc->LOCK_table_share); + mysql_mutex_unlock(&LOCK_unused_shares); + DBUG_VOID_RETURN; +} + + +/** + Remove all or some (depending on parameter) instances of TABLE and + TABLE_SHARE from the table definition cache. + + @param thd Thread context + @param remove_type Type of removal: + TDC_RT_REMOVE_ALL - remove all TABLE instances and + TABLE_SHARE instance. There + should be no used TABLE objects + and caller should have exclusive + metadata lock on the table. + TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances + except those that belong to + this thread. There should be + no TABLE objects used by other + threads and caller should have + exclusive metadata lock on the + table. + TDC_RT_REMOVE_UNUSED - remove all unused TABLE + instances (if there are no + used instances will also + remove TABLE_SHARE). + TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE - + remove all TABLE instances + except those that belong to + this thread, but don't mark + TABLE_SHARE as old. There + should be no TABLE objects + used by other threads and + caller should have exclusive + metadata lock on the table. + @param db Name of database + @param table_name Name of table + @param kill_delayed_threads If TRUE, kill INSERT DELAYED threads + + @note It assumes that table instances are already not used by any + (other) thread (this should be achieved by using meta-data locks). +*/ + +bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, + const char *db, const char *table_name, + bool kill_delayed_threads) +{ + I_P_List <TABLE, TABLE_share> purge_tables; + TABLE *table; + TDC_element *element; + uint my_refs= 1; + DBUG_ENTER("tdc_remove_table"); + DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type)); + + DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || + thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, + MDL_EXCLUSIVE)); + + + mysql_mutex_lock(&LOCK_unused_shares); + if (!(element= tdc_lock_share(thd, db, table_name))) + { + mysql_mutex_unlock(&LOCK_unused_shares); + DBUG_ASSERT(remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE); + DBUG_RETURN(false); + } + + DBUG_ASSERT(element != MY_ERRPTR); // What can we do about it? + + if (!element->ref_count) + { + if (element->prev) + { + unused_shares.remove(element); + element->prev= 0; + element->next= 0; + } + mysql_mutex_unlock(&LOCK_unused_shares); + + tdc_delete_share_from_hash(element); + DBUG_RETURN(true); + } + mysql_mutex_unlock(&LOCK_unused_shares); + + element->ref_count++; + + element->wait_for_mdl_deadlock_detector(); + /* + Mark share flushed in order to ensure that it gets + automatically deleted once it is no longer referenced. + + Note that code in TABLE_SHARE::wait_for_old_version() assumes that + marking share flushed is followed by purge of unused table + shares. + */ + if (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) + element->flushed= true; + + while ((table= element->free_tables.pop_front())) + { + tc_remove_table(table); + purge_tables.push_front(table); + } + if (kill_delayed_threads) + kill_delayed_threads_for_table(element); + + if (remove_type == TDC_RT_REMOVE_NOT_OWN || + remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) + { + TDC_element::All_share_tables_list::Iterator it(element->all_tables); + while ((table= it++)) + { + my_refs++; + DBUG_ASSERT(table->in_use == thd); + } + } + DBUG_ASSERT(element->all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL); + mysql_mutex_unlock(&element->LOCK_table_share); + + while ((table= purge_tables.pop_front())) + intern_close_table(table); + + if (remove_type != TDC_RT_REMOVE_UNUSED) + { + /* + Even though current thread holds exclusive metadata lock on this share + (asserted above), concurrent FLUSH TABLES threads may be in process of + closing unused table instances belonging to this share. E.g.: + thr1 (FLUSH TABLES): table= share->tdc.free_tables.pop_front(); + thr1 (FLUSH TABLES): share->tdc.all_tables.remove(table); + thr2 (ALTER TABLE): tdc_remove_table(); + thr1 (FLUSH TABLES): intern_close_table(table); + + Current remove type assumes that all table instances (except for those + that are owned by current thread) must be closed before + thd_remove_table() returns. Wait for such tables now. + + intern_close_table() decrements ref_count and signals COND_release. When + ref_count drops down to number of references owned by current thread + waiting is completed. + + Unfortunately TABLE_SHARE::wait_for_old_version() cannot be used here + because it waits for all table instances, whereas we have to wait only + for those that are not owned by current thread. + */ + mysql_mutex_lock(&element->LOCK_table_share); + while (element->ref_count > my_refs) + mysql_cond_wait(&element->COND_release, &element->LOCK_table_share); + mysql_mutex_unlock(&element->LOCK_table_share); + } + + tdc_release_share(element->share); + + DBUG_RETURN(true); +} + + +/** + Check if table's share is being removed from the table definition + cache and, if yes, wait until the flush is complete. + + @param thd Thread context. + @param table_list Table which share should be checked. + @param timeout Timeout for waiting. + @param deadlock_weight Weight of this wait for deadlock detector. + + @retval 0 Success. Share is up to date or has been flushed. + @retval 1 Error (OOM, was killed, the wait resulted + in a deadlock or timeout). Reported. +*/ + +int tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name, + ulong wait_timeout, uint deadlock_weight, + ulong refresh_version) +{ + TDC_element *element; + + if (!(element= tdc_lock_share(thd, db, table_name))) + return FALSE; + else if (element == MY_ERRPTR) + return TRUE; + else if (element->flushed && refresh_version > element->version) + { + struct timespec abstime; + set_timespec(abstime, wait_timeout); + return element->share->wait_for_old_version(thd, &abstime, deadlock_weight); + } + tdc_unlock_share(element); + return FALSE; +} + + +ulong tdc_refresh_version(void) +{ + return my_atomic_load64_explicit(&tdc_version, MY_MEMORY_ORDER_RELAXED); +} + + +ulong tdc_increment_refresh_version(void) +{ + ulong v= my_atomic_add64_explicit(&tdc_version, 1, MY_MEMORY_ORDER_RELAXED); + DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", v)); + return v + 1; +} + + +/** + Iterate table definition cache. + + Object is protected against removal from table definition cache. + + @note Returned TABLE_SHARE is not guaranteed to be fully initialized: + tdc_acquire_share() added new share, but didn't open it yet. If caller + needs fully initializer share, it must lock table share mutex. +*/ + +struct eliminate_duplicates_arg +{ + HASH hash; + MEM_ROOT root; + my_hash_walk_action action; + void *argument; +}; + + +static uchar *eliminate_duplicates_get_key(const uchar *element, size_t *length, + my_bool not_used __attribute__((unused))) +{ + LEX_STRING *key= (LEX_STRING *) element; + *length= key->length; + return (uchar *) key->str; +} + + +static my_bool eliminate_duplicates(TDC_element *element, + eliminate_duplicates_arg *arg) +{ + LEX_STRING *key= (LEX_STRING *) alloc_root(&arg->root, sizeof(LEX_STRING)); + + if (!key || !(key->str= (char*) memdup_root(&arg->root, element->m_key, + element->m_key_length))) + return TRUE; + + key->length= element->m_key_length; + + if (my_hash_insert(&arg->hash, (uchar *) key)) + return FALSE; + + return arg->action(element, arg->argument); +} + + +int tdc_iterate(THD *thd, my_hash_walk_action action, void *argument, + bool no_dups) +{ + eliminate_duplicates_arg no_dups_argument; + LF_PINS *pins; + myf alloc_flags= 0; + uint hash_flags= HASH_UNIQUE; + int res; + + if (thd) + { + fix_thd_pins(thd); + pins= thd->tdc_hash_pins; + alloc_flags= MY_THREAD_SPECIFIC; + hash_flags|= HASH_THREAD_SPECIFIC; + } + else + pins= lf_hash_get_pins(&tdc_hash); + + if (!pins) + return ER_OUTOFMEMORY; + + if (no_dups) + { + init_alloc_root(&no_dups_argument.root, 4096, 4096, MYF(alloc_flags)); + my_hash_init(&no_dups_argument.hash, &my_charset_bin, tdc_records(), 0, 0, + eliminate_duplicates_get_key, 0, hash_flags); + no_dups_argument.action= action; + no_dups_argument.argument= argument; + action= (my_hash_walk_action) eliminate_duplicates; + argument= &no_dups_argument; + } + + res= lf_hash_iterate(&tdc_hash, pins, action, argument); + + if (!thd) + lf_hash_put_pins(pins); + + if (no_dups) + { + my_hash_free(&no_dups_argument.hash); + free_root(&no_dups_argument.root, MYF(0)); + } + return res; +} + + +/* + Function to assign a new table map id to a table share. + + PARAMETERS + + share - Pointer to table share structure + + DESCRIPTION + + We are intentionally not checking that share->mutex is locked + since this function should only be called when opening a table + share and before it is entered into the table definition cache + (meaning that it cannot be fetched by another thread, even + accidentally). + + PRE-CONDITION(S) + + share is non-NULL + last_table_id_lock initialized (tdc_inited) + + POST-CONDITION(S) + + share->table_map_id is given a value that with a high certainty is + not used by any other table (the only case where a table id can be + reused is on wrap-around, which means more than 4 billion table + share opens have been executed while one table was open all the + time). + + share->table_map_id is not ~0UL. +*/ + +void tdc_assign_new_table_id(TABLE_SHARE *share) +{ + ulong tid; + DBUG_ENTER("assign_new_table_id"); + DBUG_ASSERT(share); + DBUG_ASSERT(tdc_inited); + + DBUG_EXECUTE_IF("simulate_big_table_id", + if (last_table_id < UINT_MAX32) + last_table_id= UINT_MAX32 - 1;); + /* + There is one reserved number that cannot be used. Remember to + change this when 6-byte global table id's are introduced. + */ + do + { + tid= my_atomic_add64_explicit(&last_table_id, 1, MY_MEMORY_ORDER_RELAXED); + } while (unlikely(tid == ~0UL || tid == 0)); + + share->table_map_id= tid; + DBUG_PRINT("info", ("table_id= %lu", share->table_map_id)); + DBUG_VOID_RETURN; +} diff --git a/sql/table_cache.h b/sql/table_cache.h new file mode 100644 index 00000000000..2c5b0fc45a2 --- /dev/null +++ b/sql/table_cache.h @@ -0,0 +1,298 @@ +/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. + Copyright (c) 2010, 2011 Monty Program Ab + Copyright (C) 2013 Sergey Vojtovich and MariaDB Foundation + + 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 */ + + +#ifdef HAVE_PSI_INTERFACE +extern PSI_mutex_key key_TABLE_SHARE_LOCK_table_share; +extern PSI_cond_key key_TABLE_SHARE_COND_release; +#endif + +class TDC_element +{ +public: + uchar m_key[NAME_LEN + 1 + NAME_LEN + 1]; + uint m_key_length; + ulong version; + bool flushed; + TABLE_SHARE *share; + + typedef I_P_List <TABLE, TABLE_share> TABLE_list; + typedef I_P_List <TABLE, All_share_tables> All_share_tables_list; + /** + Protects ref_count, m_flush_tickets, all_tables, free_tables, flushed, + all_tables_refs. + */ + mysql_mutex_t LOCK_table_share; + mysql_cond_t COND_release; + TDC_element *next, **prev; /* Link to unused shares */ + uint ref_count; /* How many TABLE objects uses this */ + uint all_tables_refs; /* Number of refs to all_tables */ + /** + List of tickets representing threads waiting for the share to be flushed. + */ + Wait_for_flush_list m_flush_tickets; + /* + Doubly-linked (back-linked) lists of used and unused TABLE objects + for this share. + */ + All_share_tables_list all_tables; + TABLE_list free_tables; + + TDC_element() {} + + TDC_element(const char *key_arg, uint key_length) : m_key_length(key_length) + { + memcpy(m_key, key_arg, key_length); + } + + + void assert_clean_share() + { + DBUG_ASSERT(share == 0); + DBUG_ASSERT(ref_count == 0); + DBUG_ASSERT(m_flush_tickets.is_empty()); + DBUG_ASSERT(all_tables.is_empty()); + DBUG_ASSERT(free_tables.is_empty()); + DBUG_ASSERT(all_tables_refs == 0); + DBUG_ASSERT(next == 0); + DBUG_ASSERT(prev == 0); + } + + + /** + Acquire TABLE object from table cache. + + @pre share must be protected against removal. + + Acquired object cannot be evicted or acquired again. + + @return TABLE object, or NULL if no unused objects. + */ + + TABLE *acquire_table(THD *thd) + { + TABLE *table; + + mysql_mutex_lock(&LOCK_table_share); + table= free_tables.pop_front(); + if (table) + { + DBUG_ASSERT(!table->in_use); + table->in_use= thd; + /* The ex-unused table must be fully functional. */ + DBUG_ASSERT(table->db_stat && table->file); + /* The children must be detached from the table. */ + DBUG_ASSERT(!table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); + } + mysql_mutex_unlock(&LOCK_table_share); + return table; + } + + + /** + Get last element of free_tables. + */ + + TABLE *free_tables_back() + { + TABLE_list::Iterator it(free_tables); + TABLE *entry, *last= 0; + while ((entry= it++)) + last= entry; + return last; + } + + + /** + Wait for MDL deadlock detector to complete traversing tdc.all_tables. + + Must be called before updating TABLE_SHARE::tdc.all_tables. + */ + + void wait_for_mdl_deadlock_detector() + { + while (all_tables_refs) + mysql_cond_wait(&COND_release, &LOCK_table_share); + } + + + /** + Prepeare table share for use with table definition cache. + */ + + static void lf_alloc_constructor(uchar *arg) + { + TDC_element *element= (TDC_element*) (arg + LF_HASH_OVERHEAD); + DBUG_ENTER("lf_alloc_constructor"); + mysql_mutex_init(key_TABLE_SHARE_LOCK_table_share, + &element->LOCK_table_share, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_TABLE_SHARE_COND_release, &element->COND_release, 0); + element->m_flush_tickets.empty(); + element->all_tables.empty(); + element->free_tables.empty(); + element->all_tables_refs= 0; + element->share= 0; + element->ref_count= 0; + element->next= 0; + element->prev= 0; + DBUG_VOID_RETURN; + } + + + /** + Release table definition cache specific resources of table share. + */ + + static void lf_alloc_destructor(uchar *arg) + { + TDC_element *element= (TDC_element*) (arg + LF_HASH_OVERHEAD); + DBUG_ENTER("lf_alloc_destructor"); + element->assert_clean_share(); + mysql_cond_destroy(&element->COND_release); + mysql_mutex_destroy(&element->LOCK_table_share); + DBUG_VOID_RETURN; + } + + + static void lf_hash_initializer(LF_HASH *hash __attribute__((unused)), + TDC_element *element, LEX_STRING *key) + { + memcpy(element->m_key, key->str, key->length); + element->m_key_length= key->length; + element->assert_clean_share(); + } + + + static uchar *key(const TDC_element *element, size_t *length, + my_bool not_used __attribute__((unused))) + { + *length= element->m_key_length; + return (uchar*) element->m_key; + } +}; + + +enum enum_tdc_remove_table_type +{ + TDC_RT_REMOVE_ALL, + TDC_RT_REMOVE_NOT_OWN, + TDC_RT_REMOVE_UNUSED, + TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE +}; + +extern ulong tdc_size; +extern ulong tc_size; + +extern void tdc_init(void); +extern void tdc_start_shutdown(void); +extern void tdc_deinit(void); +extern ulong tdc_records(void); +extern void tdc_purge(bool all); +extern TDC_element *tdc_lock_share(THD *thd, const char *db, + const char *table_name); +extern void tdc_unlock_share(TDC_element *element); +extern TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, + const char *table_name, + const char *key, uint key_length, + my_hash_value_type hash_value, + uint flags, TABLE **out_table); +extern void tdc_release_share(TABLE_SHARE *share); +extern bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, + const char *db, const char *table_name, + bool kill_delayed_threads); +extern int tdc_wait_for_old_version(THD *thd, const char *db, + const char *table_name, + ulong wait_timeout, uint deadlock_weight, + ulong refresh_version= ULONG_MAX); +extern ulong tdc_refresh_version(void); +extern ulong tdc_increment_refresh_version(void); +extern void tdc_assign_new_table_id(TABLE_SHARE *share); +extern int tdc_iterate(THD *thd, my_hash_walk_action action, void *argument, + bool no_dups= false); + +extern uint tc_records(void); +extern void tc_purge(bool mark_flushed= false); +extern void tc_add_table(THD *thd, TABLE *table); +extern bool tc_release_table(TABLE *table); + +/** + Create a table cache key for non-temporary table. + + @param key Buffer for key (must be at least NAME_LEN*2+2 bytes). + @param db Database name. + @param table_name Table name. + + @return Length of key. +*/ + +inline uint tdc_create_key(char *key, const char *db, const char *table_name) +{ + /* + In theory caller should ensure that both db and table_name are + not longer than NAME_LEN bytes. In practice we play safe to avoid + buffer overruns. + */ + return (uint) (strmake(strmake(key, db, NAME_LEN) + 1, table_name, + NAME_LEN) - key + 1); +} + +/** + Convenience helper: call tdc_acquire_share() without out_table. +*/ + +static inline TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, + const char *table_name, + const char *key, + uint key_length, uint flags) +{ + return tdc_acquire_share(thd, db, table_name, key, key_length, + my_hash_sort(&my_charset_bin, (uchar*) key, + key_length), flags, 0); +} + + +/** + Convenience helper: call tdc_acquire_share() without precomputed cache key. +*/ + +static inline TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, + const char *table_name, uint flags) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + key_length= tdc_create_key(key, db, table_name); + return tdc_acquire_share(thd, db, table_name, key, key_length, flags); +} + + +/** + Convenience helper: call tdc_acquire_share() reusing the MDL cache key. + + @note lifetime of the returned TABLE_SHARE is limited by the + lifetime of the TABLE_LIST object!!! +*/ + +uint get_table_def_key(const TABLE_LIST *table_list, const char **key); + +static inline TABLE_SHARE *tdc_acquire_share_shortlived(THD *thd, TABLE_LIST *tl, + uint flags) +{ + const char *key; + uint key_length= get_table_def_key(tl, &key); + return tdc_acquire_share(thd, tl->db, tl->table_name, key, key_length, + tl->mdl_request.key.tc_hash_value(), flags, 0); +} diff --git a/sql/thr_malloc.cc b/sql/thr_malloc.cc index cedcbefc26f..b82d29e51f4 100644 --- a/sql/thr_malloc.cc +++ b/sql/thr_malloc.cc @@ -17,6 +17,7 @@ /* Mallocs for used in threads */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "thr_malloc.h" @@ -46,24 +47,21 @@ extern "C" { returned in the error packet. - SHOW ERROR/SHOW WARNINGS may be empty. */ - thd->stmt_da->set_error_status(thd, - ER_OUT_OF_RESOURCES, - ER(ER_OUT_OF_RESOURCES), - NULL); + thd->get_stmt_da()->set_error_status(ER_OUT_OF_RESOURCES); } } /* Skip writing to the error log to avoid mtr complaints */ DBUG_EXECUTE_IF("simulate_out_of_memory", return;); - sql_print_error("%s", ER(ER_OUT_OF_RESOURCES)); - + sql_print_error("%s", ER_THD_OR_DEFAULT(thd, ER_OUT_OF_RESOURCES)); } } -void init_sql_alloc(MEM_ROOT *mem_root, uint block_size, uint pre_alloc) +void init_sql_alloc(MEM_ROOT *mem_root, uint block_size, uint pre_alloc, + myf my_flags) { - init_alloc_root(mem_root, block_size, pre_alloc); + init_alloc_root(mem_root, block_size, pre_alloc, my_flags); mem_root->error_handler=sql_alloc_error_handler; } @@ -133,7 +131,7 @@ char *sql_strmake_with_convert(const char *str, size_t arg_length, if ((from_cs == &my_charset_bin) || (to_cs == &my_charset_bin)) { // Safety if to_cs->mbmaxlen > 0 - new_length= min(arg_length, max_res_length); + new_length= MY_MIN(arg_length, max_res_length); memcpy(pos, str, new_length); } else diff --git a/sql/thr_malloc.h b/sql/thr_malloc.h index 81b7d3cc238..0b17c5cdaf1 100644 --- a/sql/thr_malloc.h +++ b/sql/thr_malloc.h @@ -20,7 +20,8 @@ typedef struct st_mem_root MEM_ROOT; -void init_sql_alloc(MEM_ROOT *root, uint block_size, uint pre_alloc_size); +void init_sql_alloc(MEM_ROOT *root, uint block_size, uint pre_alloc_size, + myf my_flags); void *sql_alloc(size_t); void *sql_calloc(size_t); char *sql_strdup(const char *str); diff --git a/sql/threadpool.h b/sql/threadpool.h index c080e5ba343..719a3878ebd 100644 --- a/sql/threadpool.h +++ b/sql/threadpool.h @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #define MAX_THREAD_GROUPS 100000 @@ -27,6 +27,7 @@ extern uint threadpool_oversubscribe; /* Maximum active threads in group */ /* Common thread pool routines, suitable for different implementations */ +extern void threadpool_cleanup_connection(THD *thd); extern void threadpool_remove_connection(THD *thd); extern int threadpool_process_request(THD *thd); extern int threadpool_add_connection(THD *thd); @@ -67,4 +68,5 @@ extern void tp_set_threadpool_stall_limit(uint val); /* Activate threadpool scheduler */ extern void tp_scheduler(void); -extern int show_threadpool_idle_threads(THD *thd, SHOW_VAR *var, char *buff); +extern int show_threadpool_idle_threads(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope); diff --git a/sql/threadpool_common.cc b/sql/threadpool_common.cc index 9e0cb07b86c..b99346ee106 100644 --- a/sql/threadpool_common.cc +++ b/sql/threadpool_common.cc @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <my_global.h> #include <violite.h> @@ -73,17 +73,16 @@ struct Worker_thread_context void save() { -#ifdef HAVE_PSI_INTERFACE - psi_thread= PSI_server?PSI_server->get_thread():0; +#ifdef HAVE_PSI_THREAD_INTERFACE + psi_thread = PSI_THREAD_CALL(get_thread)(); #endif mysys_var= (st_my_thread_var *)pthread_getspecific(THR_KEY_mysys); } void restore() { -#ifdef HAVE_PSI_INTERFACE - if (PSI_server) - PSI_server->set_thread(psi_thread); +#ifdef HAVE_PSI_THREAD_INTERFACE + PSI_THREAD_CALL(set_thread)(psi_thread); #endif pthread_setspecific(THR_KEY_mysys,mysys_var); pthread_setspecific(THR_THD, 0); @@ -92,6 +91,41 @@ struct Worker_thread_context }; +#ifdef HAVE_PSI_INTERFACE + +/* + The following fixes PSI "idle" psi instrumentation. + The server assumes that connection becomes idle + just before net_read_packet() and switches to active after it. + In out setup, server becomes idle when async socket io is made. +*/ + +extern void net_before_header_psi(struct st_net *net, void *user_data, size_t); + +static void dummy_before_header(struct st_net *, void *, size_t) +{ +} + +static void re_init_net_server_extension(THD *thd) +{ + thd->m_net_server_extension.m_before_header = dummy_before_header; +} + +#else + +#define re_init_net_server_extension(thd) + +#endif /* HAVE_PSI_INTERFACE */ + + +static inline void set_thd_idle(THD *thd) +{ + thd->net.reading_or_writing= 1; +#ifdef HAVE_PSI_INTERFACE + net_before_header_psi(&thd->net, thd, 0); +#endif +} + /* Attach/associate the connection with the OS thread, */ @@ -100,10 +134,10 @@ static bool thread_attach(THD* thd) pthread_setspecific(THR_KEY_mysys,thd->mysys_var); thd->thread_stack=(char*)&thd; thd->store_globals(); -#ifdef HAVE_PSI_INTERFACE - if (PSI_server) - PSI_server->set_thread(thd->event_scheduler.m_psi); +#ifdef HAVE_PSI_THREAD_INTERFACE + PSI_THREAD_CALL(set_thread)(thd->event_scheduler.m_psi); #endif + mysql_socket_set_thread_owner(thd->net.vio->mysql_socket); return 0; } @@ -130,60 +164,64 @@ int threadpool_add_connection(THD *thd) } /* Create new PSI thread for use with the THD. */ -#ifdef HAVE_PSI_INTERFACE - if (PSI_server) - { - thd->event_scheduler.m_psi = - PSI_server->new_thread(key_thread_one_connection, thd, thd->thread_id); - } +#ifdef HAVE_PSI_THREAD_INTERFACE + thd->event_scheduler.m_psi= + PSI_THREAD_CALL(new_thread)(key_thread_one_connection, thd, thd->thread_id); #endif /* Login. */ thread_attach(thd); + re_init_net_server_extension(thd); ulonglong now= microsecond_interval_timer(); thd->prior_thr_create_utime= now; thd->start_utime= now; thd->thr_create_utime= now; - if (!setup_connection_thread_globals(thd)) - { - if (!login_connection(thd)) - { - prepare_new_connection_state(thd); - - /* - Check if THD is ok, as prepare_new_connection_state() - can fail, for example if init command failed. - */ - if (thd_is_connection_alive(thd)) - { - retval= 0; - thd->net.reading_or_writing= 1; - thd->skip_wait_timeout= true; - } - } - } + if (setup_connection_thread_globals(thd)) + goto end; + + if (thd_prepare_connection(thd)) + goto end; + + /* + Check if THD is ok, as prepare_new_connection_state() + can fail, for example if init command failed. + */ + if (!thd_is_connection_alive(thd)) + goto end; + + retval= 0; + thd->skip_wait_timeout= true; + set_thd_idle(thd); + +end: worker_context.restore(); return retval; } +/* + threadpool_cleanup_connection() does the bulk of connection shutdown work. + Usually called from threadpool_remove_connection(), but rarely it might + be called also in the main polling thread if connection initialization fails. +*/ +void threadpool_cleanup_connection(THD *thd) +{ + thd->net.reading_or_writing = 0; + end_connection(thd); + close_connection(thd, 0); + unlink_thd(thd); + mysql_cond_broadcast(&COND_thread_count); +} + void threadpool_remove_connection(THD *thd) { - Worker_thread_context worker_context; worker_context.save(); - thread_attach(thd); - thd->net.reading_or_writing= 0; - - end_connection(thd); - close_connection(thd, 0); - - unlink_thd(thd); - mysql_cond_broadcast(&COND_thread_count); + threadpool_cleanup_connection(thd); /* Free resources associated with this connection: mysys thread_var and PSI thread. @@ -239,12 +277,13 @@ int threadpool_process_request(THD *thd) goto end; } + set_thd_idle(thd); + vio= thd->net.vio; if (!vio->has_data(vio)) { /* More info on this debug sync is in sql_parse.cc*/ DEBUG_SYNC(thd, "before_do_command_net_read"); - thd->net.reading_or_writing= 1; goto end; } } diff --git a/sql/threadpool_unix.cc b/sql/threadpool_unix.cc index 0ffe08a8051..f1133b22cf5 100644 --- a/sql/threadpool_unix.cc +++ b/sql/threadpool_unix.cc @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <my_global.h> #include <violite.h> @@ -19,6 +19,9 @@ #include <sql_class.h> #include <my_pthread.h> #include <scheduler.h> + +#ifdef HAVE_POOL_OF_THREADS + #include <sql_connect.h> #include <mysqld.h> #include <debug_sync.h> @@ -460,7 +463,7 @@ static void timeout_check(pool_timer_t *timer) { /* Wait timeout exceeded, kill connection. */ mysql_mutex_lock(&thd->LOCK_thd_data); - thd->killed = KILL_CONNECTION; + thd->set_killed(KILL_CONNECTION); post_kill_notification(thd); mysql_mutex_unlock(&thd->LOCK_thd_data); } @@ -1256,7 +1259,7 @@ void tp_add_connection(THD *thd) else { /* Allocation failed */ - threadpool_remove_connection(thd); + threadpool_cleanup_connection(thd); } DBUG_VOID_RETURN; } @@ -1370,7 +1373,7 @@ static int change_group(connection_t *c, thread_group_t *new_group) { int ret= 0; - int fd = c->thd->net.vio->sd; + int fd= mysql_socket_getfd(c->thd->net.vio->mysql_socket); DBUG_ASSERT(c->thread_group == old_group); @@ -1398,7 +1401,7 @@ static int change_group(connection_t *c, static int start_io(connection_t *connection) { - int fd = connection->thd->net.vio->sd; + int fd = mysql_socket_getfd(connection->thd->net.vio->mysql_socket); /* Usually, connection will stay in the same group for the entire @@ -1518,7 +1521,7 @@ static void *worker_main(void *param) bool tp_init() { DBUG_ENTER("tp_init"); - threadpool_max_size= max(threadpool_size, 128); + threadpool_max_size= MY_MAX(threadpool_size, 128); all_groups= (thread_group_t *) my_malloc(sizeof(thread_group_t) * threadpool_max_size, MYF(MY_WME|MY_ZEROFILL)); if (!all_groups) @@ -1635,7 +1638,7 @@ int tp_get_idle_thread_count() Delay in microseconds, after which "pool blocked" message is printed. (30 sec == 30 Mio usec) */ -#define BLOCK_MSG_DELAY 30*1000000 +#define BLOCK_MSG_DELAY (30*1000000) #define MAX_THREADS_REACHED_MSG \ "Threadpool could not create additional thread to handle queries, because the \ @@ -1682,3 +1685,5 @@ static void print_pool_blocked_message(bool max_threads_reached) msg_written= true; } } + +#endif /* HAVE_POOL_OF_THREADS */ diff --git a/sql/threadpool_win.cc b/sql/threadpool_win.cc index 72e03da2453..cb44687f154 100644 --- a/sql/threadpool_win.cc +++ b/sql/threadpool_win.cc @@ -11,7 +11,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifdef _WIN32_WINNT #undef _WIN32_WINNT @@ -255,7 +255,7 @@ int init_io(connection_t *connection, THD *thd) { case VIO_TYPE_SSL: case VIO_TYPE_TCPIP: - connection->handle= (HANDLE)vio->sd; + connection->handle= (HANDLE)mysql_socket_getfd(connection->thd->net.vio->mysql_socket); break; case VIO_TYPE_NAMEDPIPE: connection->handle= (HANDLE)vio->hPipe; @@ -342,7 +342,7 @@ int start_io(connection_t *connection, PTP_CALLBACK_INSTANCE instance) if (vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SSL) { /* Start async io (sockets). */ - if (WSARecv(vio->sd , &buf, 1, &num_bytes, &flags, + if (WSARecv(mysql_socket_getfd(vio->mysql_socket) , &buf, 1, &num_bytes, &flags, overlapped, NULL) == 0) { retval= last_error= 0; @@ -388,7 +388,7 @@ int start_io(connection_t *connection, PTP_CALLBACK_INSTANCE instance) return 0; } - /* Some error occured */ + /* Some error occurred */ CancelThreadpoolIo(io); return -1; } @@ -575,7 +575,7 @@ static VOID CALLBACK io_completion_callback(PTP_CALLBACK_INSTANCE instance, return; error: - /* Some error has occured. */ + /* Some error has occurred. */ destroy_connection(connection, instance); free(connection); @@ -613,7 +613,7 @@ static VOID CALLBACK timer_callback(PTP_CALLBACK_INSTANCE instance, if (timeout <= now()) { - con->thd->killed = KILL_CONNECTION; + con->thd->set_killed(KILL_CONNECTION); if(con->thd->net.vio) vio_shutdown(con->thd->net.vio, SD_BOTH); } @@ -667,7 +667,7 @@ void tp_add_connection(THD *thd) if(!con) { tp_log_warning("Allocation failed", "tp_add_connection"); - threadpool_remove_connection(thd); + threadpool_cleanup_connection(thd); return; } @@ -685,7 +685,7 @@ void tp_add_connection(THD *thd) else { /* Likely memory pressure */ - login_callback(NULL, con, NULL); /* deletes connection if something goes wrong */ + threadpool_cleanup_connection(thd); } } diff --git a/sql/transaction.cc b/sql/transaction.cc index 6b09bd18d4c..1744feea151 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -18,10 +18,12 @@ #pragma implementation // gcc: Class implementation #endif +#include <my_global.h> #include "sql_priv.h" #include "transaction.h" #include "rpl_handler.h" #include "debug_sync.h" // DEBUG_SYNC +#include "sql_acl.h" /* Conditions under which the transaction state must not change. */ static bool trans_check(THD *thd) @@ -96,6 +98,8 @@ static bool xa_trans_force_rollback(THD *thd) by ha_rollback()/THD::transaction::cleanup(). */ thd->transaction.xid_state.rm_error= 0; + if (WSREP_ON) + wsrep_register_hton(thd, TRUE); if (ha_rollback_trans(thd, true)) { my_error(ER_XAER_RMERR, MYF(0)); @@ -134,12 +138,25 @@ bool trans_begin(THD *thd, uint flags) (thd->variables.option_bits & OPTION_TABLE_LOCK)) { thd->variables.option_bits&= ~OPTION_TABLE_LOCK; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - res= test(ha_commit_trans(thd, TRUE)); + if (WSREP_ON) + wsrep_register_hton(thd, TRUE); + thd->server_status&= + ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); + DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS")); + res= MY_TEST(ha_commit_trans(thd, TRUE)); + if (WSREP_ON) + wsrep_post_commit(thd, TRUE); } thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; + + /* + The following set should not be needed as transaction state should + already be reset. We should at some point change this to an assert. + */ + thd->transaction.all.reset(); + thd->has_waiter= false; + thd->waiting_on_group_commit= false; if (res) DBUG_RETURN(TRUE); @@ -150,13 +167,46 @@ bool trans_begin(THD *thd, uint flags) */ thd->mdl_context.release_transactional_locks(); + // The RO/RW options are mutually exclusive. + DBUG_ASSERT(!((flags & MYSQL_START_TRANS_OPT_READ_ONLY) && + (flags & MYSQL_START_TRANS_OPT_READ_WRITE))); + if (flags & MYSQL_START_TRANS_OPT_READ_ONLY) + thd->tx_read_only= true; + else if (flags & MYSQL_START_TRANS_OPT_READ_WRITE) + { + /* + Explicitly starting a RW transaction when the server is in + read-only mode, is not allowed unless the user has SUPER priv. + Implicitly starting a RW transaction is allowed for backward + compatibility. + */ + const bool user_is_super= + MY_TEST(thd->security_ctx->master_access & SUPER_ACL); + if (opt_readonly && !user_is_super) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only"); + DBUG_RETURN(true); + } + thd->tx_read_only= false; + } + +#ifdef WITH_WSREP + thd->wsrep_PA_safe= true; + if (WSREP_CLIENT(thd) && wsrep_sync_wait(thd)) + DBUG_RETURN(TRUE); +#endif /* WITH_WSREP */ + thd->variables.option_bits|= OPTION_BEGIN; thd->server_status|= SERVER_STATUS_IN_TRANS; + if (thd->tx_read_only) + thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY; + DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS")); + /* ha_start_consistent_snapshot() relies on OPTION_BEGIN flag set. */ if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) res= ha_start_consistent_snapshot(thd); - DBUG_RETURN(test(res)); + DBUG_RETURN(MY_TEST(res)); } @@ -177,21 +227,33 @@ bool trans_commit(THD *thd) if (trans_check(thd)) DBUG_RETURN(TRUE); - thd->server_status&= ~SERVER_STATUS_IN_TRANS; + if (WSREP_ON) + wsrep_register_hton(thd, TRUE); + thd->server_status&= + ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); + DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS")); res= ha_commit_trans(thd, TRUE); - if (res) + + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); + mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock()); + mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync); + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); + + if (WSREP_ON) + wsrep_post_commit(thd, TRUE); /* if res is non-zero, then ha_commit_trans has rolled back the transaction, so the hooks for rollback will be called. */ - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + if (res) + (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE)); else - RUN_HOOK(transaction, after_commit, (thd, FALSE)); + (void) RUN_HOOK(transaction, after_commit, (thd, FALSE)); thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; + thd->transaction.all.reset(); thd->lex->start_transaction_opt= 0; - DBUG_RETURN(test(res)); + DBUG_RETURN(MY_TEST(res)); } @@ -214,26 +276,37 @@ bool trans_commit_implicit(THD *thd) if (trans_check(thd)) DBUG_RETURN(TRUE); + if (thd->variables.option_bits & OPTION_GTID_BEGIN) + DBUG_PRINT("error", ("OPTION_GTID_BEGIN is set. " + "Master and slave will have different GTID values")); + if (thd->in_multi_stmt_transaction_mode() || (thd->variables.option_bits & OPTION_TABLE_LOCK)) { /* Safety if one did "drop table" on locked tables */ if (!thd->locked_tables_mode) thd->variables.option_bits&= ~OPTION_TABLE_LOCK; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - res= test(ha_commit_trans(thd, TRUE)); + if (WSREP_ON) + wsrep_register_hton(thd, TRUE); + thd->server_status&= + ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); + DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS")); + res= MY_TEST(ha_commit_trans(thd, TRUE)); + if (WSREP_ON) + wsrep_post_commit(thd, TRUE); } thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; + thd->transaction.all.reset(); /* Upon implicit commit, reset the current transaction - isolation level. We do not care about + isolation level and access mode. We do not care about @@session.completion_type since it's documented to not have any effect on implicit commit. */ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; DBUG_RETURN(res); } @@ -253,17 +326,26 @@ bool trans_rollback(THD *thd) int res; DBUG_ENTER("trans_rollback"); +#ifdef WITH_WSREP + thd->wsrep_PA_safe= true; +#endif /* WITH_WSREP */ if (trans_check(thd)) DBUG_RETURN(TRUE); - thd->server_status&= ~SERVER_STATUS_IN_TRANS; + if (WSREP_ON) + wsrep_register_hton(thd, TRUE); + thd->server_status&= + ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); + DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS")); res= ha_rollback_trans(thd, TRUE); - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE)); thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; + /* Reset the binlog transaction marker */ + thd->variables.option_bits&= ~OPTION_GTID_BEGIN; + thd->transaction.all.reset(); thd->lex->start_transaction_opt= 0; - DBUG_RETURN(test(res)); + DBUG_RETURN(MY_TEST(res)); } @@ -304,12 +386,12 @@ bool trans_rollback_implicit(THD *thd) preserve backward compatibility. */ thd->variables.option_bits&= ~(OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= false; + thd->transaction.all.reset(); /* Rollback should clear transaction_rollback_request flag. */ DBUG_ASSERT(! thd->transaction_rollback_request); - DBUG_RETURN(test(res)); + DBUG_RETURN(MY_TEST(res)); } @@ -340,25 +422,39 @@ bool trans_commit_stmt(THD *thd) */ DBUG_ASSERT(! thd->in_sub_stmt); + thd->merge_unsafe_rollback_flags(); + if (thd->transaction.stmt.ha_list) { + if (WSREP_ON) + wsrep_register_hton(thd, FALSE); res= ha_commit_trans(thd, FALSE); if (! thd->in_active_multi_stmt_transaction()) + { thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; + if (WSREP_ON) + wsrep_post_commit(thd, FALSE); + } } - if (res) + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); + mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock()); + mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync); + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); + /* if res is non-zero, then ha_commit_trans has rolled back the transaction, so the hooks for rollback will be called. */ - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + if (res) + (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE)); else - RUN_HOOK(transaction, after_commit, (thd, FALSE)); + (void) RUN_HOOK(transaction, after_commit, (thd, FALSE)); thd->transaction.stmt.reset(); - DBUG_RETURN(test(res)); + DBUG_RETURN(MY_TEST(res)); } @@ -382,14 +478,21 @@ bool trans_rollback_stmt(THD *thd) */ DBUG_ASSERT(! thd->in_sub_stmt); + thd->merge_unsafe_rollback_flags(); + if (thd->transaction.stmt.ha_list) { + if (WSREP_ON) + wsrep_register_hton(thd, FALSE); ha_rollback_trans(thd, FALSE); if (! thd->in_active_multi_stmt_transaction()) + { thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; + } } - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE)); thd->transaction.stmt.reset(); @@ -512,28 +615,47 @@ bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name) if (thd->transaction.xid_state.check_has_uncommitted_xa()) DBUG_RETURN(TRUE); + /** + Checking whether it is safe to release metadata locks acquired after + savepoint, if rollback to savepoint is successful. + + Whether it is safe to release MDL after rollback to savepoint depends + on storage engines participating in transaction: + + - InnoDB doesn't release any row-locks on rollback to savepoint so it + is probably a bad idea to release MDL as well. + - Binary log implementation in some cases (e.g when non-transactional + tables involved) may choose not to remove events added after savepoint + from transactional cache, but instead will write them to binary + log accompanied with ROLLBACK TO SAVEPOINT statement. Since the real + write happens at the end of transaction releasing MDL on tables + mentioned in these events (i.e. acquired after savepoint and before + rollback ot it) can break replication, as concurrent DROP TABLES + statements will be able to drop these tables before events will get + into binary log, + + For backward-compatibility reasons we always release MDL if binary + logging is off. + */ + bool mdl_can_safely_rollback_to_savepoint= + (!(mysql_bin_log.is_open() && thd->variables.sql_log_bin) || + ha_rollback_to_savepoint_can_release_mdl(thd)); + if (ha_rollback_to_savepoint(thd, sv)) res= TRUE; else if (((thd->variables.option_bits & OPTION_KEEP_LOG) || thd->transaction.all.modified_non_trans_table) && !thd->slave_thread) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARNING_NOT_COMPLETE_ROLLBACK, - ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); + ER_THD(thd, ER_WARNING_NOT_COMPLETE_ROLLBACK)); thd->transaction.savepoints= sv; - /* - Release metadata locks that were acquired during this savepoint unit - unless binlogging is on. Releasing locks with binlogging on can break - replication as it allows other connections to drop these tables before - rollback to savepoint is written to the binlog. - */ - bool binlog_on= mysql_bin_log.is_open() && thd->variables.sql_log_bin; - if (!res && !binlog_on) + if (!res && mdl_can_safely_rollback_to_savepoint) thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint); - DBUG_RETURN(test(res)); + DBUG_RETURN(MY_TEST(res)); } @@ -568,7 +690,7 @@ bool trans_release_savepoint(THD *thd, LEX_STRING name) thd->transaction.savepoints= sv->prev; - DBUG_RETURN(test(res)); + DBUG_RETURN(MY_TEST(res)); } @@ -609,7 +731,7 @@ bool trans_xa_start(THD *thd) thd->transaction.xid_state.xa_state= XA_ACTIVE; thd->transaction.xid_state.rm_error= 0; thd->transaction.xid_state.xid.set(thd->lex->xid); - if (xid_cache_insert(&thd->transaction.xid_state)) + if (xid_cache_insert(thd, &thd->transaction.xid_state)) { thd->transaction.xid_state.xa_state= XA_NOTR; thd->transaction.xid_state.xid.null(); @@ -672,7 +794,7 @@ bool trans_xa_prepare(THD *thd) my_error(ER_XAER_NOTA, MYF(0)); else if (ha_prepare(thd)) { - xid_cache_delete(&thd->transaction.xid_state); + xid_cache_delete(thd, &thd->transaction.xid_state); thd->transaction.xid_state.xa_state= XA_NOTR; my_error(ER_XA_RBROLLBACK, MYF(0)); } @@ -701,25 +823,21 @@ bool trans_xa_commit(THD *thd) if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) { - /* - xid_state.in_thd is always true beside of xa recovery procedure. - Note, that there is no race condition here between xid_cache_search - and xid_cache_delete, since we always delete our own XID - (thd->lex->xid == thd->transaction.xid_state.xid). - The only case when thd->lex->xid != thd->transaction.xid_state.xid - and xid_state->in_thd == 0 is in the function - xa_cache_insert(XID, xa_states), which is called before starting - client connections, and thus is always single-threaded. - */ - XID_STATE *xs= xid_cache_search(thd->lex->xid); - res= !xs || xs->in_thd; + if (thd->fix_xid_hash_pins()) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + DBUG_RETURN(TRUE); + } + + XID_STATE *xs= xid_cache_search(thd, thd->lex->xid); + res= !xs; if (res) my_error(ER_XAER_NOTA, MYF(0)); else { res= xa_trans_rolled_back(xs); ha_commit_or_rollback_by_xid(thd->lex->xid, !res); - xid_cache_delete(xs); + xid_cache_delete(thd, xs); } DBUG_RETURN(res); } @@ -731,9 +849,13 @@ bool trans_xa_commit(THD *thd) } else if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_ONE_PHASE) { + if (WSREP_ON) + wsrep_register_hton(thd, TRUE); int r= ha_commit_trans(thd, TRUE); - if ((res= test(r))) + if ((res= MY_TEST(r))) my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0)); + if (WSREP_ON) + wsrep_post_commit(thd, TRUE); } else if (xa_state == XA_PREPARED && thd->lex->xa_opt == XA_NONE) { @@ -752,6 +874,8 @@ bool trans_xa_commit(THD *thd) if (thd->mdl_context.acquire_lock(&mdl_request, thd->variables.lock_wait_timeout)) { + if (WSREP_ON) + wsrep_register_hton(thd, TRUE); ha_rollback_trans(thd, TRUE); my_error(ER_XAER_RMERR, MYF(0)); } @@ -759,7 +883,7 @@ bool trans_xa_commit(THD *thd) { DEBUG_SYNC(thd, "trans_xa_commit_after_acquire_commit_lock"); - res= test(ha_commit_one_phase(thd, 1)); + res= MY_TEST(ha_commit_one_phase(thd, 1)); if (res) my_error(ER_XAER_RMERR, MYF(0)); } @@ -771,9 +895,11 @@ bool trans_xa_commit(THD *thd) } thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - xid_cache_delete(&thd->transaction.xid_state); + thd->transaction.all.reset(); + thd->server_status&= + ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); + DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS")); + xid_cache_delete(thd, &thd->transaction.xid_state); thd->transaction.xid_state.xa_state= XA_NOTR; DBUG_RETURN(res); @@ -797,16 +923,22 @@ bool trans_xa_rollback(THD *thd) if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) { - XID_STATE *xs= xid_cache_search(thd->lex->xid); - if (!xs || xs->in_thd) + if (thd->fix_xid_hash_pins()) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + DBUG_RETURN(TRUE); + } + + XID_STATE *xs= xid_cache_search(thd, thd->lex->xid); + if (!xs) my_error(ER_XAER_NOTA, MYF(0)); else { xa_trans_rolled_back(xs); ha_commit_or_rollback_by_xid(thd->lex->xid, 0); - xid_cache_delete(xs); + xid_cache_delete(thd, xs); } - DBUG_RETURN(thd->stmt_da->is_error()); + DBUG_RETURN(thd->get_stmt_da()->is_error()); } if (xa_state != XA_IDLE && xa_state != XA_PREPARED && xa_state != XA_ROLLBACK_ONLY) @@ -818,9 +950,11 @@ bool trans_xa_rollback(THD *thd) res= xa_trans_force_rollback(thd); thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - xid_cache_delete(&thd->transaction.xid_state); + thd->transaction.all.reset(); + thd->server_status&= + ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); + DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS")); + xid_cache_delete(thd, &thd->transaction.xid_state); thd->transaction.xid_state.xa_state= XA_NOTR; DBUG_RETURN(res); diff --git a/sql/tztime.cc b/sql/tztime.cc index 079611504cc..60a3ceafe0a 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -180,7 +180,7 @@ tz_load(const char *name, TIME_ZONE_INFO *sp, MEM_ROOT *storage) uchar buf[sizeof(struct tzhead) + sizeof(my_time_t) * TZ_MAX_TIMES + TZ_MAX_TIMES + sizeof(TRAN_TYPE_INFO) * TZ_MAX_TYPES + #ifdef ABBR_ARE_USED - max(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1))) + + MY_MAX(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1))) + #endif sizeof(LS_INFO) * TZ_MAX_LEAPS]; } u; @@ -309,7 +309,7 @@ tz_load(const char *name, TIME_ZONE_INFO *sp, MEM_ROOT *storage) Note: See description of TIME_to_gmt_sec() function first. In order to perform MYSQL_TIME -> my_time_t conversion we need to build table which defines "shifted by tz offset and leap seconds my_time_t" -> - my_time_t function wich is almost the same (except ranges of ambiguity) + my_time_t function which is almost the same (except ranges of ambiguity) as reverse function to piecewise linear function used for my_time_t -> "shifted my_time_t" conversion and which is also specified as table in zoneinfo file or in our db (It is specified as start of time type ranges @@ -327,7 +327,7 @@ static my_bool prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage) { my_time_t cur_t= MY_TIME_T_MIN; - my_time_t cur_l, end_t, end_l; + my_time_t cur_l, end_t, UNINIT_VAR(end_l); my_time_t cur_max_seen_l= MY_TIME_T_MIN; long cur_offset, cur_corr, cur_off_and_corr; uint next_trans_idx, next_leap_idx; @@ -340,8 +340,6 @@ prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage) my_time_t revts[TZ_MAX_REV_RANGES]; REVT_INFO revtis[TZ_MAX_REV_RANGES]; - LINT_INIT(end_l); - /* Let us setup fallback time type which will be used if we have not any transitions or if we have moment of time before first transition. @@ -409,7 +407,7 @@ prepare_tz_info(TIME_ZONE_INFO *sp, MEM_ROOT *storage) Let us choose end_t as point before next time type change or leap second correction. */ - end_t= min((next_trans_idx < sp->timecnt) ? sp->ats[next_trans_idx] - 1: + end_t= MY_MIN((next_trans_idx < sp->timecnt) ? sp->ats[next_trans_idx] - 1: MY_TIME_T_MAX, (next_leap_idx < sp->leapcnt) ? sp->lsis[next_leap_idx].ls_trans - 1: MY_TIME_T_MAX); @@ -614,7 +612,7 @@ sec_to_TIME(MYSQL_TIME * tmp, my_time_t t, long offset) /* - Find time range wich contains given my_time_t value + Find time range which contains given my_time_t value SYNOPSIS find_time_range() @@ -710,7 +708,7 @@ find_transition_type(my_time_t t, const TIME_ZONE_INFO *sp) TODO We can improve this function by creating joined array of transitions and leap corrections. This will require adding extra field to TRAN_TYPE_INFO - for storing number of "extra" seconds to minute occured due to correction + for storing number of "extra" seconds to minute occurred due to correction (60th and 61st second, look how we calculate them as "hit" in this function). Under realistic assumptions about frequency of transitions the same array @@ -1539,16 +1537,11 @@ my_offset_tzs_get_key(Time_zone_offset *entry, static void tz_init_table_list(TABLE_LIST *tz_tabs) { - bzero(tz_tabs, sizeof(TABLE_LIST) * MY_TZ_TABLES_COUNT); - for (int i= 0; i < MY_TZ_TABLES_COUNT; i++) { - tz_tabs[i].alias= tz_tabs[i].table_name= tz_tables_names[i].str; - tz_tabs[i].table_name_length= tz_tables_names[i].length; - tz_tabs[i].db= tz_tables_db_name.str; - tz_tabs[i].db_length= tz_tables_db_name.length; - tz_tabs[i].lock_type= TL_READ; - + tz_tabs[i].init_one_table(tz_tables_db_name.str, tz_tables_db_name.length, + tz_tables_names[i].str, tz_tables_names[i].length, + NULL, TL_READ); if (i != MY_TZ_TABLES_COUNT - 1) tz_tabs[i].next_global= tz_tabs[i].next_local= &tz_tabs[i+1]; if (i != 0) @@ -1641,7 +1634,7 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) my_hash_free(&tz_names); goto end; } - init_sql_alloc(&tz_storage, 32 * 1024, 0); + init_sql_alloc(&tz_storage, 32 * 1024, 0, MYF(0)); mysql_mutex_init(key_tz_LOCK, &tz_LOCK, MY_MUTEX_INIT_FAST); tz_inited= 1; @@ -1663,7 +1656,7 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) { /* If we are in bootstrap mode we should not load time zone tables */ return_val= time_zone_tables_exist= 0; - goto end_with_setting_default_tz; + goto end_with_cleanup; } /* @@ -1694,7 +1687,8 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) MYSQL_OPEN_IGNORE_FLUSH | MYSQL_LOCK_IGNORE_TIMEOUT)) { sql_print_warning("Can't open and lock time zone table: %s " - "trying to live without them", thd->stmt_da->message()); + "trying to live without them", + thd->get_stmt_da()->message()); /* We will try emulate that everything is ok */ return_val= time_zone_tables_exist= 0; goto end_with_setting_default_tz; @@ -1802,11 +1796,7 @@ end: if (org_thd) org_thd->store_globals(); /* purecov: inspected */ else - { - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); my_pthread_setspecific_ptr(THR_MALLOC, 0); - } default_tz= default_tz_name ? global_system_variables.time_zone : my_tz_SYSTEM; @@ -1880,7 +1870,7 @@ tz_load_from_open_tables(const String *tz_name, TABLE_LIST *tz_tables) uchar types[TZ_MAX_TIMES]; TRAN_TYPE_INFO ttis[TZ_MAX_TYPES]; #ifdef ABBR_ARE_USED - char chars[max(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1)))]; + char chars[MY_MAX(TZ_MAX_CHARS + 1, (2 * (MY_TZNAME_MAX + 1)))]; #endif /* Used as a temporary tz_info until we decide that we actually want to @@ -1931,7 +1921,7 @@ tz_load_from_open_tables(const String *tz_name, TABLE_LIST *tz_tables) field->store((longlong) tzid, TRUE); DBUG_ASSERT(field->key_length() <= sizeof(keybuff)); field->get_key_image(keybuff, - min(field->key_length(), sizeof(keybuff)), + MY_MIN(field->key_length(), sizeof(keybuff)), Field::itRAW); if (table->file->ha_index_init(0, 1)) goto end; @@ -1964,7 +1954,7 @@ tz_load_from_open_tables(const String *tz_name, TABLE_LIST *tz_tables) field->store((longlong) tzid, TRUE); DBUG_ASSERT(field->key_length() <= sizeof(keybuff)); field->get_key_image(keybuff, - min(field->key_length(), sizeof(keybuff)), + MY_MIN(field->key_length(), sizeof(keybuff)), Field::itRAW); if (table->file->ha_index_init(0, 1)) goto end; @@ -2032,7 +2022,7 @@ tz_load_from_open_tables(const String *tz_name, TABLE_LIST *tz_tables) /* At last we are doing the same thing for records in - mysql.time_zone_transition table. Here we additionaly need records + mysql.time_zone_transition table. Here we additionally need records in ascending order by index scan also satisfies us. */ table= tz_tables->table; @@ -2514,12 +2504,13 @@ scan_tz_dir(char * name_end, uint symlink_recursion_level, uint verbose) char *name_end_tmp; uint i; - if (!(cur_dir= my_dir(fullname, MYF(MY_WANT_STAT)))) + /* Sort directory data, to pass mtr tests on different platforms. */ + if (!(cur_dir= my_dir(fullname, MYF(MY_WANT_STAT|MY_WANT_SORT)))) return 1; name_end= strmake(name_end, "/", FN_REFLEN - (name_end - fullname)); - for (i= 0; i < cur_dir->number_off_files; i++) + for (i= 0; i < cur_dir->number_of_files; i++) { if (cur_dir->dir_entry[i].name[0] != '.' && strcmp(cur_dir->dir_entry[i].name, "Factory")) @@ -2571,7 +2562,7 @@ scan_tz_dir(char * name_end, uint symlink_recursion_level, uint verbose) } else if (MY_S_ISREG(cur_dir->dir_entry[i].mystat->st_mode)) { - init_alloc_root(&tz_storage, 32768, 0); + init_alloc_root(&tz_storage, 32768, 0, MYF(MY_THREAD_SPECIFIC)); if (!tz_load(fullname, &tz_info, &tz_storage)) print_tz_as_sql(root_name_end + 1, &tz_info); else @@ -2693,9 +2684,7 @@ main(int argc, char **argv) char **default_argv; MY_INIT(argv[0]); - if (load_defaults("my",load_default_groups,&argc,&argv)) - exit(1); - + load_defaults_or_exit("my", load_default_groups, &argc, &argv); default_argv= argv; if ((handle_options(&argc, &argv, my_long_options, get_one_option))) @@ -2707,6 +2696,13 @@ main(int argc, char **argv) free_defaults(default_argv); return 1; } + + // Replicate MyISAM DDL for this session, cf. lp:1161432 + // timezone info unfixable in XtraDB Cluster + printf("set @prep=if((select count(*) from information_schema.global_variables where variable_name='wsrep_on'), 'SET GLOBAL wsrep_replicate_myisam=?', 'do ?');\n" + "prepare set_wsrep_myisam from @prep;\n" + "set @toggle=1; execute set_wsrep_myisam using @toggle;\n"); + if (argc == 1 && !opt_leap) { /* Argument is timezonedir */ @@ -2738,7 +2734,7 @@ main(int argc, char **argv) First argument is timezonefile. The second is timezonename if opt_leap is not given */ - init_alloc_root(&tz_storage, 32768, 0); + init_alloc_root(&tz_storage, 32768, 0, MYF(0)); if (tz_load(argv[0], &tz_info, &tz_storage)) { @@ -2754,6 +2750,9 @@ main(int argc, char **argv) free_root(&tz_storage, MYF(0)); } + // Reset wsrep_replicate_myisam. lp:1161432 + printf("set @toggle=0; execute set_wsrep_myisam using @toggle;\n"); + free_defaults(default_argv); my_end(0); return 0; @@ -2765,7 +2764,7 @@ main(int argc, char **argv) #ifdef TESTTIME /* - Some simple brute-force test wich allowed to catch a pair of bugs. + Some simple brute-force test which allowed to catch a pair of bugs. Also can provide interesting facts about system's time zone support implementation. */ @@ -2809,7 +2808,7 @@ main(int argc, char **argv) MY_INIT(argv[0]); - init_alloc_root(&tz_storage, 32768, 0); + init_alloc_root(&tz_storage, 32768, MYF(0)); /* let us set some well known timezone */ setenv("TZ", "MET", 1); @@ -2821,7 +2820,7 @@ main(int argc, char **argv) if (TYPE_SIGNED(time_t)) { t= -100; - localtime_negative= test(localtime_r(&t, &tmp) != 0); + localtime_negative= MY_TEST(localtime_r(&t, &tmp) != 0); printf("localtime_r %s negative params \ (time_t=%d is %d-%d-%d %d:%d:%d)\n", (localtime_negative ? "supports" : "doesn't support"), (int)t, diff --git a/sql/uniques.cc b/sql/uniques.cc index 89ab4682829..1ce186b48e1 100644 --- a/sql/uniques.cc +++ b/sql/uniques.cc @@ -1,4 +1,5 @@ -/* Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2001, 2010, Oracle and/or its affiliates. + Copyright (c) 2010, 2015, 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 @@ -30,6 +31,7 @@ deletes in disk order. */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_sort.h" @@ -51,7 +53,7 @@ int unique_write_to_file(uchar* key, element_count count, Unique *unique) int unique_write_to_file_with_count(uchar* key, element_count count, Unique *unique) { return my_b_write(&unique->file, key, unique->size) || - my_b_write(&unique->file, &count, sizeof(element_count)) ? 1 : 0; + my_b_write(&unique->file, (uchar*)&count, sizeof(element_count)) ? 1 : 0; } int unique_write_to_ptrs(uchar* key, element_count count, Unique *unique) @@ -86,11 +88,13 @@ Unique::Unique(qsort_cmp2 comp_func, void * comp_func_fixed_arg, full_size= size; if (min_dupl_count_arg) full_size+= sizeof(element_count); + with_counters= MY_TEST(min_dupl_count_arg); my_b_clear(&file); - init_tree(&tree, (ulong) (max_in_memory_size / 16), 0, size, comp_func, 0, - NULL, comp_func_fixed_arg); + init_tree(&tree, (ulong) (max_in_memory_size / 16), 0, size, comp_func, + NULL, comp_func_fixed_arg, MYF(MY_THREAD_SPECIFIC)); /* If the following fail's the next add will also fail */ - my_init_dynamic_array(&file_ptrs, sizeof(BUFFPEK), 16, 16); + my_init_dynamic_array(&file_ptrs, sizeof(BUFFPEK), 16, 16, + MYF(MY_THREAD_SPECIFIC)); /* If you change the following, change it in get_max_elements function, too. */ @@ -430,6 +434,22 @@ static int buffpek_compare(void *arg, uchar *key_ptr1, uchar *key_ptr2) C_MODE_END +inline +element_count get_counter_from_merged_element(void *ptr, uint ofs) +{ + element_count cnt; + memcpy((uchar *) &cnt, (uchar *) ptr + ofs, sizeof(element_count)); + return cnt; +} + + +inline +void put_counter_into_merged_element(void *ptr, uint ofs, element_count cnt) +{ + memcpy((uchar *) ptr + ofs, (uchar *) &cnt, sizeof(element_count)); +} + + /* DESCRIPTION @@ -459,6 +479,8 @@ C_MODE_END file file with all trees dumped. Trees in the file must contain sorted unique values. Cache must be initialized in read mode. + with counters take into account counters for equal merged + elements RETURN VALUE 0 ok <> 0 error @@ -468,7 +490,7 @@ static bool merge_walk(uchar *merge_buffer, size_t merge_buffer_size, uint key_length, BUFFPEK *begin, BUFFPEK *end, tree_walk_action walk_action, void *walk_action_arg, qsort_cmp2 compare, void *compare_arg, - IO_CACHE *file) + IO_CACHE *file, bool with_counters) { BUFFPEK_COMPARE_CONTEXT compare_context = { compare, compare_arg }; QUEUE queue; @@ -487,6 +509,8 @@ static bool merge_walk(uchar *merge_buffer, size_t merge_buffer_size, uint bytes_read; /* to hold return value of read_to_buffer */ BUFFPEK *top; int res= 1; + uint cnt_ofs= key_length - (with_counters ? sizeof(element_count) : 0); + element_count cnt; /* Invariant: queue must contain top element from each tree, until a tree is not completely walked through. @@ -545,9 +569,17 @@ static bool merge_walk(uchar *merge_buffer, size_t merge_buffer_size, /* new top has been obtained; if old top is unique, apply the action */ if (compare(compare_arg, old_key, top->key)) { - if (walk_action(old_key, 1, walk_action_arg)) + cnt= with_counters ? + get_counter_from_merged_element(old_key, cnt_ofs) : 1; + if (walk_action(old_key, cnt, walk_action_arg)) goto end; } + else if (with_counters) + { + cnt= get_counter_from_merged_element(top->key, cnt_ofs); + cnt+= get_counter_from_merged_element(old_key, cnt_ofs); + put_counter_into_merged_element(top->key, cnt_ofs, cnt); + } } /* Applying walk_action to the tail of the last tree: this is safe because @@ -558,7 +590,10 @@ static bool merge_walk(uchar *merge_buffer, size_t merge_buffer_size, { do { - if (walk_action(top->key, 1, walk_action_arg)) + + cnt= with_counters ? + get_counter_from_merged_element(top->key, cnt_ofs) : 1; + if (walk_action(top->key, cnt, walk_action_arg)) goto end; top->key+= key_length; } @@ -615,19 +650,19 @@ bool Unique::walk(TABLE *table, tree_walk_action action, void *walk_action_arg) merge_index() can merge that many BUFFPEKs at once. The extra space for one key is needed when a piece of merge buffer is re-read, see merge_walk() */ - size_t buff_sz= max(MERGEBUFF2+1, max_in_memory_size/full_size+1) * full_size; + size_t buff_sz= MY_MAX(MERGEBUFF2+1, max_in_memory_size/full_size+1) * full_size; if (!(merge_buffer = (uchar *)my_malloc(buff_sz, MYF(MY_WME)))) return 1; - if (buff_sz < (ulong) (full_size * (file_ptrs.elements + 1))) - res= merge(table, merge_buffer, true) ; - + if (buff_sz < full_size * (file_ptrs.elements + 1UL)) + res= merge(table, merge_buffer, buff_sz >= full_size * MERGEBUFF2) ; + if (!res) - { + { res= merge_walk(merge_buffer, buff_sz, full_size, (BUFFPEK *) file_ptrs.buffer, (BUFFPEK *) file_ptrs.buffer + file_ptrs.elements, action, walk_action_arg, - tree.compare, tree.custom_arg, &file); + tree.compare, tree.custom_arg, &file, with_counters); } my_free(merge_buffer); return res; @@ -652,7 +687,6 @@ bool Unique::walk(TABLE *table, tree_walk_action action, void *walk_action_arg) bool Unique::merge(TABLE *table, uchar *buff, bool without_last_merge) { - SORTPARAM sort_param; IO_CACHE *outfile= table->sort.io_cache; BUFFPEK *file_ptr= (BUFFPEK*) file_ptrs.buffer; uint maxbuffer= file_ptrs.elements - 1; @@ -662,14 +696,14 @@ bool Unique::merge(TABLE *table, uchar *buff, bool without_last_merge) /* Open cached file if it isn't open */ if (!outfile) outfile= table->sort.io_cache= (IO_CACHE*) my_malloc(sizeof(IO_CACHE), - MYF(MY_ZEROFILL)); + MYF(MY_THREAD_SPECIFIC|MY_ZEROFILL)); if (!outfile || (! my_b_inited(outfile) && open_cached_file(outfile,mysql_tmpdir,TEMP_PREFIX,READ_RECORD_BUFFER, MYF(MY_WME)))) return 1; - reinit_io_cache(outfile,WRITE_CACHE,0L,0,0); + Sort_param sort_param; bzero((char*) &sort_param,sizeof(sort_param)); sort_param.max_rows= elements; sort_param.sort_form= table; @@ -677,10 +711,12 @@ bool Unique::merge(TABLE *table, uchar *buff, bool without_last_merge) full_size; sort_param.min_dupl_count= min_dupl_count; sort_param.res_length= 0; - sort_param.keys= (uint) max((max_in_memory_size / sort_param.sort_length), MERGEBUFF2); + sort_param.max_keys_per_buffer= + (uint) MY_MAX((max_in_memory_size / sort_param.sort_length), MERGEBUFF2); sort_param.not_killable= 1; - sort_param.unique_buff= buff + (sort_param.keys * sort_param.sort_length); + sort_param.unique_buff= buff +(sort_param.max_keys_per_buffer * + sort_param.sort_length); sort_param.compare= (qsort2_cmp) buffpek_compare; sort_param.cmp_context.key_compare= tree.compare; @@ -730,7 +766,7 @@ bool Unique::get(TABLE *table) { /* Whole tree is in memory; Don't use disk if you don't need to */ if ((record_pointers=table->sort.record_pointers= (uchar*) - my_malloc(size * tree.elements_in_tree, MYF(0)))) + my_malloc(size * tree.elements_in_tree, MYF(MY_THREAD_SPECIFIC)))) { tree_walk_action action= min_dupl_count ? (tree_walk_action) unique_intersect_write_to_ptrs : @@ -746,7 +782,7 @@ bool Unique::get(TABLE *table) if (flush()) return 1; size_t buff_sz= (max_in_memory_size / full_size + 1) * full_size; - if (!(sort_buffer= (uchar*) my_malloc(buff_sz, MYF(MY_WME)))) + if (!(sort_buffer= (uchar*) my_malloc(buff_sz, MYF(MY_THREAD_SPECIFIC|MY_WME)))) return 1; if (merge(table, sort_buffer, FALSE)) diff --git a/sql/unireg.cc b/sql/unireg.cc index 936203a78ab..e41cca2dfcb 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -25,173 +25,108 @@ str is a (long) to record position where 0 is the first position. */ +#include <my_global.h> #include "sql_priv.h" #include "unireg.h" #include "sql_partition.h" // struct partition_info -#include "sql_table.h" // check_duplicate_warning #include "sql_class.h" // THD, Internal_error_handler #include "create_options.h" +#include "discover.h" #include <m_ctype.h> -#include <assert.h> #define FCOMP 17 /* Bytes for a packed field */ /* threshold for safe_alloca */ #define ALLOCA_THRESHOLD 2048 -static uchar * pack_screens(List<Create_field> &create_fields, - uint *info_length, uint *screens, bool small_file); -static uint pack_keys(uchar *keybuff,uint key_count, KEY *key_info, - ulong data_offset); -static bool pack_header(uchar *forminfo,enum legacy_db_type table_type, - List<Create_field> &create_fields, - uint info_length, uint screens, uint table_options, - ulong data_offset, handler *file); +static uint pack_keys(uchar *,uint, KEY *, ulong); +static bool pack_header(THD *, uchar *, List<Create_field> &, uint, ulong, handler *); static uint get_interval_id(uint *,List<Create_field> &, Create_field *); -static bool pack_fields(File file, List<Create_field> &create_fields, - ulong data_offset); -static bool make_empty_rec(THD *thd, int file, enum legacy_db_type table_type, - uint table_options, - List<Create_field> &create_fields, - uint reclength, ulong data_offset, - handler *handler); - -/** - An interceptor to hijack ER_TOO_MANY_FIELDS error from - pack_screens and retry again without UNIREG screens. +static bool pack_fields(uchar *, List<Create_field> &, ulong); +static size_t packed_fields_length(List<Create_field> &); +static bool make_empty_rec(THD *, uchar *, uint, List<Create_field> &, uint, ulong); - XXX: what is a UNIREG screen? +/* + write the length as + if ( 0 < length <= 255) one byte + if (256 < length <= 65535) zero byte, then two bytes, low-endian */ - -struct Pack_header_error_handler: public Internal_error_handler +static uchar *extra2_write_len(uchar *pos, size_t len) { - virtual bool handle_condition(THD *thd, - uint sql_errno, - const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char* msg, - MYSQL_ERROR ** cond_hdl); - bool is_handled; - Pack_header_error_handler() :is_handled(FALSE) {} -}; - - -bool -Pack_header_error_handler:: -handle_condition(THD *, - uint sql_errno, - const char*, - MYSQL_ERROR::enum_warning_level, - const char*, - MYSQL_ERROR ** cond_hdl) + if (len <= 255) + *pos++= len; + else + { + /* + At the moment we support options_len up to 64K. + We can easily extend it in the future, if the need arises. + */ + DBUG_ASSERT(len <= 65535); + int2store(pos + 1, len); + pos+= 3; + } + return pos; +} + +static uchar *extra2_write(uchar *pos, enum extra2_frm_value_type type, + LEX_STRING *str) { - *cond_hdl= NULL; - is_handled= (sql_errno == ER_TOO_MANY_FIELDS); - return is_handled; + *pos++ = type; + pos= extra2_write_len(pos, str->length); + memcpy(pos, str->str, str->length); + return pos + str->length; } -/* - In ALTER TABLE, mysql_create_frm() gets the temporary table name, - like #sql-3696_2. It's used for error messages, when a table cannot - be created because of some OS error. - If there's a user error (like, too long comment), we want to show - the real table name, though. -*/ -static const char *get_real_table_name(const char *table, - List<Create_field> &fields) +static uchar *extra2_write(uchar *pos, enum extra2_frm_value_type type, + LEX_CUSTRING *str) { - const char *real_table_name= table; - List_iterator<Create_field> it(fields); - Create_field *field; - while ((field=it++)) - { - if (field->field && field->field->table && - (real_table_name= field->field->table->s->table_name.str)) - break; - } - return real_table_name; + return extra2_write(pos, type, reinterpret_cast<LEX_STRING *>(str)); } /* Create a frm (table definition) file - SYNOPSIS - mysql_create_frm() - thd Thread handler - file_name Path for file (including database and .frm) - db Name of database - table Name of table - create_info create info parameters - create_fields Fields to create - keys number of keys to create - key_info Keys to create - db_file Handler to use. May be zero, in which case we use - create_info->db_type - RETURN - false ok - true error + @param thd Thread handler + @param table Name of table + @param create_info create info parameters + @param create_fields Fields to create + @param keys number of keys to create + @param key_info Keys to create + @param db_file Handler to use. + + @return the generated frm image as a LEX_CUSTRING, + or null LEX_CUSTRING (str==0) in case of an error. */ -bool mysql_create_frm(THD *thd, const char *file_name, - const char *db, const char *table, - HA_CREATE_INFO *create_info, - List<Create_field> &create_fields, - uint keys, KEY *key_info, - handler *db_file) +LEX_CUSTRING build_frm_image(THD *thd, const char *table, + HA_CREATE_INFO *create_info, + List<Create_field> &create_fields, + uint keys, KEY *key_info, handler *db_file) { LEX_STRING str_db_type; - uint reclength, info_length, screens, key_info_length, maxlength, tmp_len, i; + uint reclength, key_info_length, i; ulong key_buff_length; - File file; ulong filepos, data_offset; uint options_len; - uchar fileinfo[64],forminfo[288],*keybuff; - uchar *screen_buff; - char buff[128]; -#ifdef WITH_PARTITION_STORAGE_ENGINE - partition_info *part_info= thd->work_part_info; -#endif - Pack_header_error_handler pack_header_error_handler; + uint gis_extra2_len= 0; + uchar fileinfo[FRM_HEADER_SIZE],forminfo[FRM_FORMINFO_SIZE]; + const partition_info *part_info= IF_PARTITIONING(thd->work_part_info, 0); int error; - DBUG_ENTER("mysql_create_frm"); - - DBUG_ASSERT(*fn_rext((char*)file_name)); // Check .frm extension - - if (!(screen_buff=pack_screens(create_fields,&info_length,&screens,0))) - DBUG_RETURN(1); - DBUG_ASSERT(db_file != NULL); + uchar *frm_ptr, *pos; + LEX_CUSTRING frm= {0,0}; + DBUG_ENTER("build_frm_image"); /* If fixed row records, we need one bit to check for deleted rows */ if (!(create_info->table_options & HA_OPTION_PACK_RECORD)) create_info->null_bits++; data_offset= (create_info->null_bits + 7) / 8; - thd->push_internal_handler(&pack_header_error_handler); - - error= pack_header(forminfo, ha_legacy_type(create_info->db_type), - create_fields,info_length, - screens, create_info->table_options, + error= pack_header(thd, forminfo, create_fields, create_info->table_options, data_offset, db_file); - thd->pop_internal_handler(); - if (error) - { - my_free(screen_buff); - if (! pack_header_error_handler.is_handled) - DBUG_RETURN(1); - - // Try again without UNIREG screens (to get more columns) - if (!(screen_buff=pack_screens(create_fields,&info_length,&screens,1))) - DBUG_RETURN(1); - if (pack_header(forminfo, ha_legacy_type(create_info->db_type), - create_fields,info_length, - screens, create_info->table_options, data_offset, db_file)) - { - my_free(screen_buff); - DBUG_RETURN(1); - } - } + DBUG_RETURN(frm); + reclength=uint2korr(forminfo+266); /* Calculate extra data segment length */ @@ -207,12 +142,8 @@ bool mysql_create_frm(THD *thd, const char *file_name, => Total 6 byte */ create_info->extra_size+= 6; -#ifdef WITH_PARTITION_STORAGE_ENGINE if (part_info) - { create_info->extra_size+= part_info->part_info_len; - } -#endif for (i= 0; i < keys; i++) { @@ -223,236 +154,190 @@ bool mysql_create_frm(THD *thd, const char *file_name, options_len= engine_table_options_frm_length(create_info->option_list, create_fields, keys, key_info); +#ifdef HAVE_SPATIAL + gis_extra2_len= gis_field_options_image(NULL, create_fields); +#endif /*HAVE_SPATIAL*/ DBUG_PRINT("info", ("Options length: %u", options_len)); - if (options_len) - { - create_info->table_options|= HA_OPTION_TEXT_CREATE_OPTIONS; - create_info->extra_size+= (options_len + 4); - } - else - create_info->table_options&= ~HA_OPTION_TEXT_CREATE_OPTIONS; - /* - This gives us the byte-position of the character at - (character-position, not byte-position) TABLE_COMMENT_MAXLEN. - The trick here is that character-positions start at 0, so the last - character in a maximum-allowed length string would be at char-pos - MAXLEN-1; charpos MAXLEN will be the position of the terminator. - Consequently, bytepos(charpos(MAXLEN)) should be equal to - comment[length] (which should also be the terminator, or at least - the first byte after the payload in the strict sense). If this is - not so (bytepos(charpos(MAXLEN)) comes /before/ the end of the - string), the string is too long. - - For additional credit, realise that UTF-8 has 1-3 bytes before 6.0, - and 1-4 bytes in 6.0 (6.0 also has UTF-32). - */ - tmp_len= system_charset_info->cset->charpos(system_charset_info, - create_info->comment.str, - create_info->comment.str + - create_info->comment.length, - TABLE_COMMENT_MAXLEN); - - if (tmp_len < create_info->comment.length) - { - const char *real_table_name= get_real_table_name(table, create_fields); - if ((thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))) - { - my_error(ER_TOO_LONG_TABLE_COMMENT, MYF(0), - real_table_name, static_cast<ulong>(TABLE_COMMENT_MAXLEN)); - my_free(screen_buff); - DBUG_RETURN(1); - } - char warn_buff[MYSQL_ERRMSG_SIZE]; - my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_TABLE_COMMENT), - real_table_name, static_cast<ulong>(TABLE_COMMENT_MAXLEN)); - /* do not push duplicate warnings */ - if (!check_duplicate_warning(current_thd, warn_buff, strlen(warn_buff))) - push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TOO_LONG_TABLE_COMMENT, warn_buff); - create_info->comment.length= tmp_len; - } + if (validate_comment_length(thd, &create_info->comment, TABLE_COMMENT_MAXLEN, + ER_TOO_LONG_TABLE_COMMENT, + table)) + DBUG_RETURN(frm); /* If table comment is longer than TABLE_COMMENT_INLINE_MAXLEN bytes, store the comment in an extra segment (up to TABLE_COMMENT_MAXLEN bytes). - Pre 6.0, the limit was 60 characters, with no extra segment-handling. + Pre 5.5, the limit was 60 characters, with no extra segment-handling. */ if (create_info->comment.length > TABLE_COMMENT_INLINE_MAXLEN) { forminfo[46]=255; create_info->extra_size+= 2 + create_info->comment.length; } - else{ + else + { strmake((char*) forminfo+47, create_info->comment.str ? create_info->comment.str : "", create_info->comment.length); forminfo[46]=(uchar) create_info->comment.length; } - if ((file=create_frm(thd, file_name, db, table, reclength, fileinfo, - create_info, keys, key_info)) < 0) + if (!create_info->tabledef_version.str) { - my_free(screen_buff); - DBUG_RETURN(1); + uchar *to= (uchar*) thd->alloc(MY_UUID_SIZE); + if (unlikely(!to)) + DBUG_RETURN(frm); + my_uuid(to); + create_info->tabledef_version.str= to; + create_info->tabledef_version.length= MY_UUID_SIZE; } + DBUG_ASSERT(create_info->tabledef_version.length > 0); + DBUG_ASSERT(create_info->tabledef_version.length <= 255); + + prepare_frm_header(thd, reclength, fileinfo, create_info, keys, key_info); + + /* one byte for a type, one or three for a length */ + uint extra2_size= 1 + 1 + create_info->tabledef_version.length; + if (options_len) + extra2_size+= 1 + (options_len > 255 ? 3 : 1) + options_len; + + if (part_info) + extra2_size+= 1 + 1 + hton_name(part_info->default_engine_type)->length; + + if (gis_extra2_len) + extra2_size+= 1 + (gis_extra2_len > 255 ? 3 : 1) + gis_extra2_len; + key_buff_length= uint4korr(fileinfo+47); - keybuff=(uchar*) my_malloc(key_buff_length, MYF(0)); - key_info_length= pack_keys(keybuff, keys, key_info, data_offset); + frm.length= FRM_HEADER_SIZE; // fileinfo; + frm.length+= extra2_size + 4; // mariadb extra2 frm segment + + int2store(fileinfo+4, extra2_size); + int2store(fileinfo+6, frm.length); // Position to key information + frm.length+= key_buff_length; + frm.length+= reclength; // row with default values + frm.length+= create_info->extra_size; + + filepos= frm.length; + frm.length+= FRM_FORMINFO_SIZE; // forminfo + frm.length+= packed_fields_length(create_fields); + + if (frm.length > FRM_MAX_SIZE) + { + my_error(ER_TABLE_DEFINITION_TOO_BIG, MYF(0), table); + DBUG_RETURN(frm); + } + + frm_ptr= (uchar*) my_malloc(frm.length, MYF(MY_WME | MY_ZEROFILL | + MY_THREAD_SPECIFIC)); + if (!frm_ptr) + DBUG_RETURN(frm); + + /* write the extra2 segment */ + pos = frm_ptr + 64; + compile_time_assert(EXTRA2_TABLEDEF_VERSION != '/'); + pos= extra2_write(pos, EXTRA2_TABLEDEF_VERSION, + &create_info->tabledef_version); + + if (part_info) + pos= extra2_write(pos, EXTRA2_DEFAULT_PART_ENGINE, + hton_name(part_info->default_engine_type)); + + if (options_len) + { + *pos++= EXTRA2_ENGINE_TABLEOPTS; + pos= extra2_write_len(pos, options_len); + pos= engine_table_options_frm_image(pos, create_info->option_list, + create_fields, keys, key_info); + } + +#ifdef HAVE_SPATIAL + if (gis_extra2_len) + { + *pos= EXTRA2_GIS; + pos= extra2_write_len(pos+1, gis_extra2_len); + pos+= gis_field_options_image(pos, create_fields); + } +#endif /*HAVE_SPATIAL*/ + + int4store(pos, filepos); // end of the extra2 segment + pos+= 4; + + DBUG_ASSERT(pos == frm_ptr + uint2korr(fileinfo+6)); + key_info_length= pack_keys(pos, keys, key_info, data_offset); if (key_info_length > UINT_MAX16) { my_printf_error(ER_CANT_CREATE_TABLE, - "Cannot create table %`s.%`s: index information is too long. " + "Cannot create table %`s: index information is too long. " "Decrease number of indexes or use shorter index names or shorter comments.", - MYF(0), db, get_real_table_name(table, create_fields)); + MYF(0), table); goto err; } - /* - Ensure that there are no forms in this newly created form file. - Even if the form file exists, create_frm must truncate it to - ensure one form per form file. - */ - DBUG_ASSERT(uint2korr(fileinfo+8) == 0); - - if (!(filepos= make_new_entry(file, fileinfo, NULL, ""))) - goto err; - maxlength=(uint) next_io_size((ulong) (uint2korr(forminfo)+1000)); - int2store(forminfo+2,maxlength); - int4store(fileinfo+10,(ulong) (filepos+maxlength)); - fileinfo[26]= (uchar) test((create_info->max_rows == 1) && - (create_info->min_rows == 1) && (keys == 0)); + int2store(forminfo+2, frm.length - filepos); + int4store(fileinfo+10, frm.length); + fileinfo[26]= (uchar) MY_TEST((create_info->max_rows == 1) && + (create_info->min_rows == 1) && (keys == 0)); int2store(fileinfo+28,key_info_length); -#ifdef WITH_PARTITION_STORAGE_ENGINE if (part_info) { fileinfo[61]= (uchar) ha_legacy_type(part_info->default_engine_type); DBUG_PRINT("info", ("part_db_type = %d", fileinfo[61])); } -#endif - int2store(fileinfo+59,db_file->extra_rec_buf_length()); - if (mysql_file_pwrite(file, fileinfo, 64, 0L, MYF_RW) || - mysql_file_pwrite(file, keybuff, key_info_length, - (ulong) uint2korr(fileinfo+6), MYF_RW)) - goto err; - mysql_file_seek(file, - (ulong) uint2korr(fileinfo+6) + (ulong) key_buff_length, - MY_SEEK_SET, MYF(0)); - if (make_empty_rec(thd,file,ha_legacy_type(create_info->db_type), - create_info->table_options, - create_fields,reclength, data_offset, db_file)) - goto err; + int2store(fileinfo+59,db_file->extra_rec_buf_length()); - int2store(buff, create_info->connect_string.length); - if (mysql_file_write(file, (const uchar*)buff, 2, MYF(MY_NABP)) || - mysql_file_write(file, (const uchar*)create_info->connect_string.str, - create_info->connect_string.length, MYF(MY_NABP))) - goto err; + memcpy(frm_ptr, fileinfo, FRM_HEADER_SIZE); - int2store(buff, str_db_type.length); - if (mysql_file_write(file, (const uchar*)buff, 2, MYF(MY_NABP)) || - mysql_file_write(file, (const uchar*)str_db_type.str, - str_db_type.length, MYF(MY_NABP))) + pos+= key_buff_length; + if (make_empty_rec(thd, pos, create_info->table_options, create_fields, + reclength, data_offset)) goto err; -#ifdef WITH_PARTITION_STORAGE_ENGINE + pos+= reclength; + int2store(pos, create_info->connect_string.length); + pos+= 2; + memcpy(pos, create_info->connect_string.str, create_info->connect_string.length); + pos+= create_info->connect_string.length; + int2store(pos, str_db_type.length); + pos+= 2; + memcpy(pos, str_db_type.str, str_db_type.length); + pos+= str_db_type.length; + if (part_info) { char auto_partitioned= part_info->is_auto_partitioned ? 1 : 0; - int4store(buff, part_info->part_info_len); - if (mysql_file_write(file, (const uchar*)buff, 4, MYF_RW) || - mysql_file_write(file, (const uchar*)part_info->part_info_string, - part_info->part_info_len + 1, MYF_RW) || - mysql_file_write(file, (const uchar*)&auto_partitioned, 1, MYF_RW)) - goto err; + int4store(pos, part_info->part_info_len); + pos+= 4; + memcpy(pos, part_info->part_info_string, part_info->part_info_len + 1); + pos+= part_info->part_info_len + 1; + *pos++= auto_partitioned; } else -#endif { - bzero((uchar*) buff, 6); - if (mysql_file_write(file, (uchar*) buff, 6, MYF_RW)) - goto err; + pos+= 6; } for (i= 0; i < keys; i++) { if (key_info[i].parser_name) { - if (mysql_file_write(file, (const uchar*)key_info[i].parser_name->str, - key_info[i].parser_name->length + 1, MYF(MY_NABP))) - goto err; + memcpy(pos, key_info[i].parser_name->str, key_info[i].parser_name->length + 1); + pos+= key_info[i].parser_name->length + 1; } } - if (forminfo[46] == (uchar)255) + if (forminfo[46] == (uchar)255) // New style MySQL 5.5 table comment { - uchar comment_length_buff[2]; - int2store(comment_length_buff,create_info->comment.length); - if (mysql_file_write(file, comment_length_buff, 2, MYF(MY_NABP)) || - mysql_file_write(file, (uchar*) create_info->comment.str, - create_info->comment.length, MYF(MY_NABP))) - goto err; + int2store(pos, create_info->comment.length); + pos+=2; + memcpy(pos, create_info->comment.str, create_info->comment.length); + pos+= create_info->comment.length; } - if (options_len) - { - uchar *optbuff= (uchar *)my_safe_alloca(options_len + 4, ALLOCA_THRESHOLD); - my_bool error; - DBUG_PRINT("info", ("Create options length: %u", options_len)); - if (!optbuff) - goto err; - int4store(optbuff, options_len); - engine_table_options_frm_image(optbuff + 4, - create_info->option_list, - create_fields, - keys, key_info); - error= my_write(file, optbuff, options_len + 4, MYF_RW); - my_safe_afree(optbuff, options_len + 4, ALLOCA_THRESHOLD); - if (error) - goto err; - } - - mysql_file_seek(file, filepos, MY_SEEK_SET, MYF(0)); - if (mysql_file_write(file, forminfo, 288, MYF_RW) || - mysql_file_write(file, screen_buff, info_length, MYF_RW) || - pack_fields(file, create_fields, data_offset)) + memcpy(frm_ptr + filepos, forminfo, 288); + if (pack_fields(frm_ptr + filepos + 288, create_fields, data_offset)) goto err; -#ifdef HAVE_CRYPTED_FRM - if (create_info->password) - { - char tmp=2,*disk_buff=0; - SQL_CRYPT *crypted=new SQL_CRYPT(create_info->password); - if (!crypted || mysql_file_pwrite(file, &tmp, 1, 26, MYF_RW))// Mark crypted - goto err; - uint read_length=uint2korr(forminfo)-256; - mysql_file_seek(file, filepos+256, MY_SEEK_SET, MYF(0)); - if (read_string(file,(uchar**) &disk_buff,read_length)) - goto err; - crypted->encode(disk_buff,read_length); - delete crypted; - if (mysql_file_pwrite(file, disk_buff, read_length, filepos+256, MYF_RW)) - { - my_free(disk_buff); - goto err; - } - my_free(disk_buff); - } -#endif - - my_free(screen_buff); - my_free(keybuff); - - if (opt_sync_frm && !(create_info->options & HA_LEX_CREATE_TMP_TABLE) && - (mysql_file_sync(file, MYF(MY_WME)) || - my_sync_dir_by_file(file_name, MYF(MY_WME)))) - goto err2; - - if (mysql_file_close(file, MYF(MY_WME))) - goto err3; - { /* Restore all UCS2 intervals. @@ -469,149 +354,67 @@ bool mysql_create_frm(THD *thd, const char *file_name, } } } - DBUG_RETURN(0); + + frm.str= frm_ptr; + DBUG_RETURN(frm); err: - my_free(screen_buff); - my_free(keybuff); -err2: - (void) mysql_file_close(file, MYF(MY_WME)); -err3: - mysql_file_delete(key_file_frm, file_name, MYF(0)); - DBUG_RETURN(1); -} /* mysql_create_frm */ + my_free(frm_ptr); + DBUG_RETURN(frm); +} -/* +/** Create a frm (table definition) file and the tables - SYNOPSIS - rea_create_table() - thd Thread handler - path Name of file (including database, without .frm) - db Data base name - table_name Table name - create_info create info parameters - create_fields Fields to create - keys number of keys to create - key_info Keys to create - file Handler to use - - RETURN - 0 ok - 1 error + @param thd Thread handler + @param frm Binary frm image of the table to create + @param path Name of file (including database, without .frm) + @param db Data base name + @param table_name Table name + @param create_info create info parameters + @param file Handler to use or NULL if only frm needs to be created + + @retval 0 ok + @retval 1 error */ -int rea_create_table(THD *thd, const char *path, - const char *db, const char *table_name, - HA_CREATE_INFO *create_info, - List<Create_field> &create_fields, - uint keys, KEY *key_info, handler *file) +int rea_create_table(THD *thd, LEX_CUSTRING *frm, + const char *path, const char *db, const char *table_name, + HA_CREATE_INFO *create_info, handler *file, + bool no_ha_create_table) { DBUG_ENTER("rea_create_table"); - char frm_name[FN_REFLEN]; - strxmov(frm_name, path, reg_ext, NullS); - if (mysql_create_frm(thd, frm_name, db, table_name, create_info, - create_fields, keys, key_info, file)) - - DBUG_RETURN(1); + if (no_ha_create_table) + { + if (writefrm(path, db, table_name, true, frm->str, frm->length)) + goto err_frm; + } - // Make sure mysql_create_frm din't remove extension - DBUG_ASSERT(*fn_rext(frm_name)); if (thd->variables.keep_files_on_create) create_info->options|= HA_CREATE_KEEP_FILES; - if (!create_info->frm_only && - (file->ha_create_handler_files(path, NULL, CHF_CREATE_FLAG, - create_info) || - ha_create_table(thd, path, db, table_name, create_info, 0))) - goto err_handler; - DBUG_RETURN(0); -err_handler: - (void) file->ha_create_handler_files(path, NULL, CHF_DELETE_FLAG, create_info); - mysql_file_delete(key_file_frm, frm_name, MYF(0)); - DBUG_RETURN(1); -} /* rea_create_table */ - - - /* Pack screens to a screen for save in a form-file */ - -static uchar *pack_screens(List<Create_field> &create_fields, - uint *info_length, uint *screens, - bool small_file) -{ - reg1 uint i; - uint row,start_row,end_row,fields_on_screen; - uint length,cols; - uchar *info,*pos,*start_screen; - uint fields=create_fields.elements; - List_iterator<Create_field> it(create_fields); - DBUG_ENTER("pack_screens"); + if (file->ha_create_partitioning_metadata(path, NULL, CHF_CREATE_FLAG)) + goto err_part; - start_row=4; end_row=22; cols=80; fields_on_screen=end_row+1-start_row; - - *screens=(fields-1)/fields_on_screen+1; - length= (*screens) * (SC_INFO_LENGTH+ (cols>> 1)+4); - - Create_field *field; - while ((field=it++)) - length+=(uint) strlen(field->field_name)+1+TE_INFO_LENGTH+cols/2; - - if (!(info=(uchar*) my_malloc(length,MYF(MY_WME)))) - DBUG_RETURN(0); - - start_screen=0; - row=end_row; - pos=info; - it.rewind(); - for (i=0 ; i < fields ; i++) + if (!no_ha_create_table) { - Create_field *cfield=it++; - if (row++ == end_row) - { - if (i) - { - length=(uint) (pos-start_screen); - int2store(start_screen,length); - start_screen[2]=(uchar) (fields_on_screen+1); - start_screen[3]=(uchar) (fields_on_screen); - } - row=start_row; - start_screen=pos; - pos+=4; - pos[0]= (uchar) start_row-2; /* Header string */ - pos[1]= (uchar) (cols >> 2); - pos[2]= (uchar) (cols >> 1) +1; - strfill((char *) pos+3,(uint) (cols >> 1),' '); - pos+=(cols >> 1)+4; - } - length=(uint) strlen(cfield->field_name); - if (length > cols-3) - length=cols-3; - - if (!small_file) - { - pos[0]=(uchar) row; - pos[1]=0; - pos[2]=(uchar) (length+1); - pos=(uchar*) strmake((char*) pos+3,cfield->field_name,length)+1; - } - cfield->row=(uint8) row; - cfield->col=(uint8) (length+1); - cfield->sc_length=(uint8) min(cfield->length,cols-(length+2)); + if (ha_create_table(thd, path, db, table_name, create_info, frm)) + goto err_part; } - length=(uint) (pos-start_screen); - int2store(start_screen,length); - start_screen[2]=(uchar) (row-start_row+2); - start_screen[3]=(uchar) (row-start_row+1); - *info_length=(uint) (pos-info); - DBUG_RETURN(info); -} /* pack_screens */ + DBUG_RETURN(0); + +err_part: + file->ha_create_partitioning_metadata(path, NULL, CHF_DELETE_FLAG); +err_frm: + deletefrm(path); + DBUG_RETURN(1); +} /* rea_create_table */ - /* Pack keyinfo and keynames to keybuff for save in form-file. */ +/* Pack keyinfo and keynames to keybuff for save in form-file. */ static uint pack_keys(uchar *keybuff, uint key_count, KEY *keyinfo, ulong data_offset) @@ -628,15 +431,15 @@ static uint pack_keys(uchar *keybuff, uint key_count, KEY *keyinfo, { int2store(pos, (key->flags ^ HA_NOSAME)); int2store(pos+2,key->key_length); - pos[4]= (uchar) key->key_parts; + pos[4]= (uchar) key->user_defined_key_parts; pos[5]= (uchar) key->algorithm; int2store(pos+6, key->block_size); pos+=8; - key_parts+=key->key_parts; + key_parts+=key->user_defined_key_parts; DBUG_PRINT("loop", ("flags: %lu key_parts: %d key_part: 0x%lx", - key->flags, key->key_parts, + key->flags, key->user_defined_key_parts, (long) key->key_part)); - for (key_part=key->key_part,key_part_end=key_part+key->key_parts ; + for (key_part=key->key_part,key_part_end=key_part+key->user_defined_key_parts ; key_part != key_part_end ; key_part++) @@ -694,12 +497,11 @@ static uint pack_keys(uchar *keybuff, uint key_count, KEY *keyinfo, } /* pack_keys */ - /* Make formheader */ +/* Make formheader */ -static bool pack_header(uchar *forminfo, enum legacy_db_type table_type, - List<Create_field> &create_fields, - uint info_length, uint screens, uint table_options, - ulong data_offset, handler *file) +static bool pack_header(THD *thd, uchar *forminfo, + List<Create_field> &create_fields, + uint table_options, ulong data_offset, handler *file) { uint length,int_count,int_length,no_empty, int_parts; uint time_stamp_pos,null_fields; @@ -708,7 +510,7 @@ static bool pack_header(uchar *forminfo, enum legacy_db_type table_type, if (create_fields.elements > MAX_FIELDS) { - my_message(ER_TOO_MANY_FIELDS, ER(ER_TOO_MANY_FIELDS), MYF(0)); + my_message(ER_TOO_MANY_FIELDS, ER_THD(thd, ER_TOO_MANY_FIELDS), MYF(0)); DBUG_RETURN(1); } @@ -718,45 +520,23 @@ static bool pack_header(uchar *forminfo, enum legacy_db_type table_type, com_length=vcol_info_length=0; n_length=2L; - /* Check fields */ - + /* Check fields */ List_iterator<Create_field> it(create_fields); Create_field *field; while ((field=it++)) { - uint tmp_len= system_charset_info->cset->charpos(system_charset_info, - field->comment.str, - field->comment.str + - field->comment.length, - COLUMN_COMMENT_MAXLEN); - if (tmp_len < field->comment.length) - { - if ((current_thd->variables.sql_mode & - (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))) - { - my_error(ER_TOO_LONG_FIELD_COMMENT, MYF(0), field->field_name, - static_cast<ulong>(COLUMN_COMMENT_MAXLEN)); - DBUG_RETURN(1); - } - char warn_buff[MYSQL_ERRMSG_SIZE]; - my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_FIELD_COMMENT), - field->field_name, - static_cast<ulong>(COLUMN_COMMENT_MAXLEN)); - /* do not push duplicate warnings */ - if (!check_duplicate_warning(current_thd, warn_buff, strlen(warn_buff))) - push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TOO_LONG_FIELD_COMMENT, warn_buff); - field->comment.length= tmp_len; - } + if (validate_comment_length(thd, &field->comment, COLUMN_COMMENT_MAXLEN, + ER_TOO_LONG_FIELD_COMMENT, field->field_name)) + DBUG_RETURN(1); + if (field->vcol_info) { uint col_expr_maxlen= field->virtual_col_expr_maxlen(); - tmp_len= - system_charset_info->cset->charpos(system_charset_info, - field->vcol_info->expr_str.str, - field->vcol_info->expr_str.str + - field->vcol_info->expr_str.length, - col_expr_maxlen); + uint tmp_len= my_charpos(system_charset_info, + field->vcol_info->expr_str.str, + field->vcol_info->expr_str.str + + field->vcol_info->expr_str.length, + col_expr_maxlen); if (tmp_len < field->vcol_info->expr_str.length) { @@ -771,7 +551,7 @@ static bool pack_header(uchar *forminfo, enum legacy_db_type table_type, expressions saved in the frm file for virtual columns. */ vcol_info_length+= field->vcol_info->expr_str.length+ - FRM_VCOL_HEADER_SIZE(field->interval!=NULL); + FRM_VCOL_HEADER_SIZE(field->interval); } totlength+= field->length; @@ -812,14 +592,14 @@ static bool pack_header(uchar *forminfo, enum legacy_db_type table_type, The HEX representation is created from this copy. */ field->save_interval= field->interval; - field->interval= (TYPELIB*) sql_alloc(sizeof(TYPELIB)); + field->interval= (TYPELIB*) thd->alloc(sizeof(TYPELIB)); *field->interval= *field->save_interval; field->interval->type_names= - (const char **) sql_alloc(sizeof(char*) * - (field->interval->count+1)); + (const char **) thd->alloc(sizeof(char*) * + (field->interval->count+1)); field->interval->type_names[field->interval->count]= 0; field->interval->type_lengths= - (uint *) sql_alloc(sizeof(uint) * field->interval->count); + (uint *) thd->alloc(sizeof(uint) * field->interval->count); for (uint pos= 0; pos < field->interval->count; pos++) { @@ -829,8 +609,8 @@ static bool pack_header(uchar *forminfo, enum legacy_db_type table_type, length= field->save_interval->type_lengths[pos]; hex_length= length * 2; field->interval->type_lengths[pos]= hex_length; - field->interval->type_names[pos]= dst= (char*) sql_alloc(hex_length + - 1); + field->interval->type_names[pos]= dst= + (char*) thd->alloc(hex_length + 1); octet2hex(dst, src, length); } } @@ -848,30 +628,29 @@ static bool pack_header(uchar *forminfo, enum legacy_db_type table_type, } int_length+=int_count*2; // 255 prefix + 0 suffix - /* Save values in forminfo */ - + /* Save values in forminfo */ if (reclength > (ulong) file->max_record_length()) { my_error(ER_TOO_BIG_ROWSIZE, MYF(0), static_cast<long>(file->max_record_length())); DBUG_RETURN(1); } /* Hack to avoid bugs with small static rows in MySQL */ - reclength=max(file->min_record_length(table_options),reclength); - if (info_length+(ulong) create_fields.elements*FCOMP+288+ + reclength=MY_MAX(file->min_record_length(table_options),reclength); + if ((ulong) create_fields.elements*FCOMP+FRM_FORMINFO_SIZE+ n_length+int_length+com_length+vcol_info_length > 65535L || int_count > 255) { - my_message(ER_TOO_MANY_FIELDS, ER(ER_TOO_MANY_FIELDS), MYF(0)); + my_message(ER_TOO_MANY_FIELDS, ER_THD(thd, ER_TOO_MANY_FIELDS), MYF(0)); DBUG_RETURN(1); } - bzero((char*)forminfo,288); - length=(info_length+create_fields.elements*FCOMP+288+n_length+int_length+ + bzero((char*)forminfo,FRM_FORMINFO_SIZE); + length=(create_fields.elements*FCOMP+FRM_FORMINFO_SIZE+n_length+int_length+ com_length+vcol_info_length); int2store(forminfo,length); - forminfo[256] = (uint8) screens; + forminfo[256] = 0; int2store(forminfo+258,create_fields.elements); - int2store(forminfo+260,info_length); + int2store(forminfo+260,0); int2store(forminfo+262,totlength); int2store(forminfo+264,no_empty); int2store(forminfo+266,reclength); @@ -885,13 +664,11 @@ static bool pack_header(uchar *forminfo, enum legacy_db_type table_type, int2store(forminfo+282,null_fields); int2store(forminfo+284,com_length); int2store(forminfo+286,vcol_info_length); - /* forminfo+288 is free to use for additional information */ DBUG_RETURN(0); } /* pack_header */ - /* get each unique interval each own id */ - +/* get each unique interval each own id */ static uint get_interval_id(uint *int_count,List<Create_field> &create_fields, Create_field *last_field) { @@ -918,29 +695,57 @@ static uint get_interval_id(uint *int_count,List<Create_field> &create_fields, } - /* Save fields, fieldnames and intervals */ +static size_t packed_fields_length(List<Create_field> &create_fields) +{ + Create_field *field; + size_t length= 0; + DBUG_ENTER("packed_fields_length"); + + List_iterator<Create_field> it(create_fields); + uint int_count=0; + while ((field=it++)) + { + if (field->interval_id > int_count) + { + int_count= field->interval_id; + length++; + for (int i=0; field->interval->type_names[i]; i++) + { + length+= field->interval->type_lengths[i]; + length++; + } + length++; + } + if (field->vcol_info) + { + length+= field->vcol_info->expr_str.length + + FRM_VCOL_HEADER_SIZE(field->interval); + } + length+= FCOMP; + length+= strlen(field->field_name)+1; + length+= field->comment.length; + } + length++; + length++; + DBUG_RETURN(length); +} + +/* Save fields, fieldnames and intervals */ -static bool pack_fields(File file, List<Create_field> &create_fields, +static bool pack_fields(uchar *buff, List<Create_field> &create_fields, ulong data_offset) { - reg2 uint i; uint int_count, comment_length= 0, vcol_info_length=0; - uchar buff[MAX_FIELD_WIDTH]; Create_field *field; DBUG_ENTER("pack_fields"); - /* Write field info */ - + /* Write field info */ List_iterator<Create_field> it(create_fields); - int_count=0; while ((field=it++)) { uint recpos; uint cur_vcol_expr_len= 0; - buff[0]= (uchar) field->row; - buff[1]= (uchar) field->col; - buff[2]= (uchar) field->sc_length; int2store(buff+3, field->length); /* The +1 is here becasue the col offset in .frm file have offset 1 */ recpos= field->offset+1 + (uint) data_offset; @@ -974,40 +779,29 @@ static bool pack_fields(File file, List<Create_field> &create_fields, the additional data saved for the virtual field */ buff[12]= cur_vcol_expr_len= field->vcol_info->expr_str.length + - FRM_VCOL_HEADER_SIZE(field->interval!=NULL); - vcol_info_length+= cur_vcol_expr_len + - FRM_VCOL_HEADER_SIZE(field->interval!=NULL); + FRM_VCOL_HEADER_SIZE(field->interval); + vcol_info_length+= cur_vcol_expr_len; buff[13]= (uchar) MYSQL_TYPE_VIRTUAL; } int2store(buff+15, field->comment.length); comment_length+= field->comment.length; set_if_bigger(int_count,field->interval_id); - if (mysql_file_write(file, buff, FCOMP, MYF_RW)) - DBUG_RETURN(1); + buff+= FCOMP; } - /* Write fieldnames */ - buff[0]=(uchar) NAMES_SEP_CHAR; - if (mysql_file_write(file, buff, 1, MYF_RW)) - DBUG_RETURN(1); - i=0; + /* Write fieldnames */ + *buff++= NAMES_SEP_CHAR; it.rewind(); while ((field=it++)) { - char *pos= strmov((char*) buff,field->field_name); - * (uchar*) pos++= (uchar) NAMES_SEP_CHAR; - if (i == create_fields.elements-1) - *pos++=0; - if (mysql_file_write(file, buff, (size_t) (pos-(char*) buff), MYF_RW)) - DBUG_RETURN(1); - i++; + buff= (uchar*)strmov((char*) buff, field->field_name); + *buff++=NAMES_SEP_CHAR; } + *buff++= 0; - /* Write intervals */ + /* Write intervals */ if (int_count) { - String tmp((char*) buff,sizeof(buff), &my_charset_bin); - tmp.length(0); it.rewind(); int_count=0; while ((field=it++)) @@ -1040,43 +834,41 @@ static bool pack_fields(File file, List<Create_field> &create_fields, } } - if(!sep) /* disaster, enum uses all characters, none left as separator */ + if (!sep) { - my_message(ER_WRONG_FIELD_TERMINATORS,ER(ER_WRONG_FIELD_TERMINATORS), + /* disaster, enum uses all characters, none left as separator */ + my_message(ER_WRONG_FIELD_TERMINATORS, + ER(ER_WRONG_FIELD_TERMINATORS), MYF(0)); DBUG_RETURN(1); } } int_count= field->interval_id; - tmp.append(sep); - for (const char **pos=field->interval->type_names ; *pos ; pos++) + *buff++= sep; + for (int i=0; field->interval->type_names[i]; i++) { - tmp.append(*pos); - tmp.append(sep); + memcpy(buff, field->interval->type_names[i], field->interval->type_lengths[i]); + buff+= field->interval->type_lengths[i]; + *buff++= sep; } - tmp.append('\0'); // End of intervall + *buff++= 0; + } } - if (mysql_file_write(file, (uchar*) tmp.ptr(), tmp.length(), MYF_RW)) - DBUG_RETURN(1); } if (comment_length) { it.rewind(); - int_count=0; while ((field=it++)) { - if (field->comment.length) - if (mysql_file_write(file, (uchar*) field->comment.str, - field->comment.length, MYF_RW)) - DBUG_RETURN(1); + memcpy(buff, field->comment.str, field->comment.length); + buff+= field->comment.length; } } if (vcol_info_length) { it.rewind(); - int_count=0; while ((field=it++)) { /* @@ -1089,18 +881,13 @@ static bool pack_fields(File file, List<Create_field> &create_fields, */ if (field->vcol_info && field->vcol_info->expr_str.length) { - buff[0]= (uchar)(1 + test(field->interval_id)); - buff[1]= (uchar) field->sql_type; - buff[2]= (uchar) field->stored_in_db; - if (field->interval_id) - buff[3]= (uchar) field->interval_id; - if (my_write(file, buff, 3 + test(field->interval_id), MYF_RW)) - DBUG_RETURN(1); - if (my_write(file, - (uchar*) field->vcol_info->expr_str.str, - field->vcol_info->expr_str.length, - MYF_RW)) - DBUG_RETURN(1); + *buff++= (uchar) (1 + MY_TEST(field->interval)); + *buff++= (uchar) field->sql_type; + *buff++= (uchar) field->stored_in_db; + if (field->interval) + *buff++= (uchar) field->interval_id; + memcpy(buff, field->vcol_info->expr_str.str, field->vcol_info->expr_str.length); + buff+= field->vcol_info->expr_str.length; } } } @@ -1108,19 +895,16 @@ static bool pack_fields(File file, List<Create_field> &create_fields, } - /* save an empty record on start of formfile */ +/* save an empty record on start of formfile */ -static bool make_empty_rec(THD *thd, File file,enum legacy_db_type table_type, - uint table_options, +static bool make_empty_rec(THD *thd, uchar *buff, uint table_options, List<Create_field> &create_fields, - uint reclength, - ulong data_offset, - handler *handler) + uint reclength, ulong data_offset) { int error= 0; Field::utype type; uint null_count; - uchar *buff,*null_pos; + uchar *null_pos; TABLE table; TABLE_SHARE share; Create_field *field; @@ -1132,13 +916,7 @@ static bool make_empty_rec(THD *thd, File file,enum legacy_db_type table_type, bzero((char*) &share, sizeof(share)); table.s= &share; - if (!(buff=(uchar*) my_malloc((size_t) reclength,MYF(MY_WME | MY_ZEROFILL)))) - { - DBUG_RETURN(1); - } - table.in_use= thd; - table.s->blob_ptr_size= portable_sizeof_char_ptr; null_count=0; if (!(table_options & HA_OPTION_PACK_RECORD)) @@ -1155,7 +933,7 @@ static bool make_empty_rec(THD *thd, File file,enum legacy_db_type table_type, /* regfield don't have to be deleted as it's allocated with sql_alloc() */ - Field *regfield= make_field(&share, + Field *regfield= make_field(&share, thd->mem_root, buff+field->offset + data_offset, field->length, null_pos + null_count / 8, @@ -1163,7 +941,7 @@ static bool make_empty_rec(THD *thd, File file,enum legacy_db_type table_type, field->pack_flag, field->sql_type, field->charset, - field->geom_type, + field->geom_type, field->srid, field->unireg_check, field->save_interval ? field->save_interval : field->interval, @@ -1207,9 +985,11 @@ static bool make_empty_rec(THD *thd, File file,enum legacy_db_type table_type, regfield->store((longlong) 1, TRUE); } else if (type == Field::YES) // Old unireg type - regfield->store(ER(ER_YES),(uint) strlen(ER(ER_YES)),system_charset_info); + regfield->store(ER_THD(thd, ER_YES),(uint) strlen(ER_THD(thd, ER_YES)), + system_charset_info); else if (type == Field::NO) // Old unireg type - regfield->store(ER(ER_NO), (uint) strlen(ER(ER_NO)),system_charset_info); + regfield->store(ER_THD(thd, ER_NO), (uint) strlen(ER_THD(thd, ER_NO)), + system_charset_info); else regfield->reset(); } @@ -1222,10 +1002,7 @@ static bool make_empty_rec(THD *thd, File file,enum legacy_db_type table_type, if (null_count & 7) *(null_pos + null_count / 8)|= ~(((uchar) 1 << (null_count & 7)) - 1); - error= mysql_file_write(file, buff, (size_t) reclength, MYF_RW) != 0; - err: - my_free(buff); thd->count_cuted_fields= old_count_cuted_fields; DBUG_RETURN(error); } /* make_empty_rec */ diff --git a/sql/unireg.h b/sql/unireg.h index a35c25f92ee..86d88fcdc21 100644 --- a/sql/unireg.h +++ b/sql/unireg.h @@ -18,13 +18,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ -#include "my_global.h" /* ulonglong */ -#include "mysql_version.h" /* FRM_VER */ +#include <mysql_version.h> /* FRM_VER */ /* Extra functions used by unireg library */ -typedef struct st_ha_create_information HA_CREATE_INFO; - #ifndef NO_ALARM_LOOP #define NO_ALARM_LOOP /* lib5 and popen can't use alarm */ #endif @@ -46,18 +43,18 @@ typedef struct st_ha_create_information HA_CREATE_INFO; #define PLUGINDIR "lib/plugin" #endif -#define CURRENT_THD_ERRMSGS current_thd->variables.lc_messages->errmsgs->errmsgs +#define CURRENT_THD_ERRMSGS current_thd->variables.errmsgs #define DEFAULT_ERRMSGS my_default_lc_messages->errmsgs->errmsgs #define ER(X) CURRENT_THD_ERRMSGS[(X) - ER_ERROR_FIRST] #define ER_DEFAULT(X) DEFAULT_ERRMSGS[(X) - ER_ERROR_FIRST] #define ER_SAFE(X) (((X) >= ER_ERROR_FIRST && (X) <= ER_ERROR_LAST) ? ER(X) : "Invalid error code") -#define ER_THD(thd,X) ((thd)->variables.lc_messages->errmsgs->errmsgs[(X) - \ - ER_ERROR_FIRST]) +#define ER_SAFE_THD(T,X) (((X) >= ER_ERROR_FIRST && (X) <= ER_ERROR_LAST) ? ER_THD(T,X) : "Invalid error code") +#define ER_THD(thd,X) ((thd)->variables.errmsgs[(X) - ER_ERROR_FIRST]) #define ER_THD_OR_DEFAULT(thd,X) ((thd) ? ER_THD(thd, X) : ER_DEFAULT(X)) -#define ME_INFO (ME_HOLDTANG+ME_OLDWIN+ME_NOREFRESH) -#define ME_ERROR (ME_BELL+ME_OLDWIN+ME_NOREFRESH) +#define ME_INFO (ME_HOLDTANG | ME_NOREFRESH) +#define ME_ERROR (ME_BELL | ME_NOREFRESH) #define MYF_RW MYF(MY_WME+MY_NABP) /* Vid my_read & my_write */ #define SPECIAL_USE_LOCKS 1 /* Lock used databases */ @@ -88,7 +85,7 @@ typedef struct st_ha_create_information HA_CREATE_INFO; #define READ_ALL 1 /* openfrm: Read all parameters */ #define CHANGE_FRM 2 /* openfrm: open .frm as O_RDWR */ #define READ_KEYINFO 4 /* L{s nyckeldata fr}n filen */ -#define EXTRA_RECORD 8 /* Reservera plats f|r extra record */ +#define EXTRA_RECORD 8 /* Reserve space for an extra record */ #define DONT_OPEN_TABLES 8 /* Don't open database-files (frd) */ #define DONT_OPEN_MASTER_REG 16 /* Don't open first reg-file (prt) */ #define EXTRA_LONG_RECORD 16 /* Plats f|r dubbel s|k-record */ @@ -113,32 +110,36 @@ typedef struct st_ha_create_information HA_CREATE_INFO; The flag means that we need to process tables only to get necessary data. Views are not processed. */ -#define OPEN_TABLE_ONLY OPEN_FRM_FILE_ONLY*2 +#define OPEN_TABLE_ONLY (OPEN_FRM_FILE_ONLY*2) /** This flag is used in function get_all_tables() which fills I_S tables with data which are retrieved from frm files and storage engine The flag means that we need to process views only to get necessary data. Tables are not processed. */ -#define OPEN_VIEW_ONLY OPEN_TABLE_ONLY*2 +#define OPEN_VIEW_ONLY (OPEN_TABLE_ONLY*2) /** This flag is used in function get_all_tables() which fills I_S tables with data which are retrieved from frm files and storage engine. The flag means that we need to open a view using open_normal_and_derived_tables() function. */ -#define OPEN_VIEW_FULL OPEN_VIEW_ONLY*2 +#define OPEN_VIEW_FULL (OPEN_VIEW_ONLY*2) /** This flag is used in function get_all_tables() which fills I_S tables with data which are retrieved from frm files and storage engine. The flag means that I_S table uses optimization algorithm. */ -#define OPTIMIZE_I_S_TABLE OPEN_VIEW_FULL*2 +#define OPTIMIZE_I_S_TABLE (OPEN_VIEW_FULL*2) +/** + This flag is used to instruct tdc_open_view() to check metadata version. +*/ +#define CHECK_METADATA_VERSION (OPEN_TRIGGER_ONLY*2) /* The flag means that we need to process trigger files only. */ -#define OPEN_TRIGGER_ONLY OPTIMIZE_I_S_TABLE*2 +#define OPEN_TRIGGER_ONLY (OPTIMIZE_I_S_TABLE*2) #define SC_INFO_LENGTH 4 /* Form format constant */ #define TE_INFO_LENGTH 3 @@ -169,15 +170,47 @@ typedef struct st_ha_create_information HA_CREATE_INFO; #include "sql_list.h" /* List<> */ #include "field.h" /* Create_field */ -bool mysql_create_frm(THD *thd, const char *file_name, - const char *db, const char *table, - HA_CREATE_INFO *create_info, - List<Create_field> &create_field, - uint key_count,KEY *key_info,handler *db_type); -int rea_create_table(THD *thd, const char *path, - const char *db, const char *table_name, - HA_CREATE_INFO *create_info, - List<Create_field> &create_field, - uint key_count,KEY *key_info, - handler *file); +/* + Types of values in the MariaDB extra2 frm segment. + Each value is written as + type: 1 byte + length: 1 byte (1..255) or \0 and 2 bytes. + binary value of the 'length' bytes. + + Older MariaDB servers can ignore values of unknown types if + the type code is less than 128 (EXTRA2_ENGINE_IMPORTANT). + Otherwise older (but newer than 10.0.1) servers are required + to report an error. +*/ +enum extra2_frm_value_type { + EXTRA2_TABLEDEF_VERSION=0, + EXTRA2_DEFAULT_PART_ENGINE=1, + EXTRA2_GIS=2, + +#define EXTRA2_ENGINE_IMPORTANT 128 + + EXTRA2_ENGINE_TABLEOPTS=128, +}; + +int rea_create_table(THD *thd, LEX_CUSTRING *frm, + const char *path, const char *db, const char *table_name, + HA_CREATE_INFO *create_info, handler *file, + bool no_ha_create_table); +LEX_CUSTRING build_frm_image(THD *thd, const char *table, + HA_CREATE_INFO *create_info, + List<Create_field> &create_fields, + uint keys, KEY *key_info, handler *db_file); + +#define FRM_HEADER_SIZE 64 +#define FRM_FORMINFO_SIZE 288 +#define FRM_MAX_SIZE (1024*1024) + +static inline bool is_binary_frm_header(uchar *head) +{ + return head[0] == 254 + && head[1] == 1 + && head[2] >= FRM_VER + && head[2] <= FRM_VER+4; +} + #endif diff --git a/sql/winservice.c b/sql/winservice.c index 1cf9f8d7823..efbbb527c9b 100644 --- a/sql/winservice.c +++ b/sql/winservice.c @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* Get Properties of an existing mysqld Windows service @@ -81,8 +81,10 @@ void normalize_path(char *path, size_t size) and services. We do not want to mess up with these installations. We will just ignore such services, pretending it is not MySQL. - ´@return - TRUE, if this service should be excluded from UI lists etc (OEM install) + We also exclude MySQL5.7+ since we cannot upgrade it (and it is not an upgrade anyway) + + @return + TRUE, if this service should be excluded from UI lists etc FALSE otherwise. */ BOOL exclude_service(mysqld_service_properties *props) @@ -104,7 +106,12 @@ BOOL exclude_service(mysqld_service_properties *props) if (strstr(buf, exclude_patterns[i])) return TRUE; } - + if ((props->version_major == 0) || + (props->version_major > 5 && props->version_major < 10) || + (props->version_major == 5 && props->version_minor > 6)) + { + return TRUE; + } return FALSE; } diff --git a/sql/winservice.h b/sql/winservice.h index c3e2bfe1b20..fe3fe526548 100644 --- a/sql/winservice.h +++ b/sql/winservice.h @@ -12,7 +12,7 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* Extract properties of a windows service binary path diff --git a/sql/wsrep_applier.cc b/sql/wsrep_applier.cc new file mode 100644 index 00000000000..723804c76db --- /dev/null +++ b/sql/wsrep_applier.cc @@ -0,0 +1,406 @@ +/* Copyright (C) 2013-2015 Codership Oy <info@codership.com> + + 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-1301 USA. */ + +#include "wsrep_priv.h" +#include "wsrep_binlog.h" // wsrep_dump_rbr_buf() +#include "wsrep_xid.h" + +#include "log_event.h" // class THD, EVENT_LEN_OFFSET, etc. +#include "wsrep_applier.h" +#include "debug_sync.h" + +/* + read the first event from (*buf). The size of the (*buf) is (*buf_len). + At the end (*buf) is shitfed to point to the following event or NULL and + (*buf_len) will be changed to account just being read bytes of the 1st event. +*/ + +static Log_event* wsrep_read_log_event( + char **arg_buf, size_t *arg_buf_len, + const Format_description_log_event *description_event) +{ + DBUG_ENTER("wsrep_read_log_event"); + char *head= (*arg_buf); + + uint data_len = uint4korr(head + EVENT_LEN_OFFSET); + char *buf= (*arg_buf); + const char *error= 0; + Log_event *res= 0; + + res= Log_event::read_log_event(buf, data_len, &error, description_event, + true); + + if (!res) + { + DBUG_ASSERT(error != 0); + sql_print_error("Error in Log_event::read_log_event(): " + "'%s', data_len: %d, event_type: %d", + error,data_len,head[EVENT_TYPE_OFFSET]); + } + (*arg_buf)+= data_len; + (*arg_buf_len)-= data_len; + DBUG_RETURN(res); +} + +#include "transaction.h" // trans_commit(), trans_rollback() +#include "rpl_rli.h" // class Relay_log_info; +#include "sql_base.h" // close_temporary_table() + +void wsrep_set_apply_format(THD* thd, Format_description_log_event* ev) +{ + if (thd->wsrep_apply_format) + { + delete (Format_description_log_event*)thd->wsrep_apply_format; + } + thd->wsrep_apply_format= ev; +} + +Format_description_log_event* wsrep_get_apply_format(THD* thd) +{ + if (thd->wsrep_apply_format) + { + return (Format_description_log_event*) thd->wsrep_apply_format; + } + + DBUG_ASSERT(thd->wsrep_rgi); + + return thd->wsrep_rgi->rli->relay_log.description_event_for_exec; +} + +static wsrep_cb_status_t wsrep_apply_events(THD* thd, + const void* events_buf, + size_t buf_len) +{ + char *buf= (char *)events_buf; + int rcode= 0; + int event= 1; + Log_event_type typ; + + DBUG_ENTER("wsrep_apply_events"); + + if (thd->killed == KILL_CONNECTION && + thd->wsrep_conflict_state != REPLAYING) + { + WSREP_INFO("applier has been aborted, skipping apply_rbr: %lld", + (long long) wsrep_thd_trx_seqno(thd)); + DBUG_RETURN(WSREP_CB_FAILURE); + } + + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->wsrep_query_state= QUERY_EXEC; + if (thd->wsrep_conflict_state!= REPLAYING) + thd->wsrep_conflict_state= NO_CONFLICT; + mysql_mutex_unlock(&thd->LOCK_thd_data); + + if (!buf_len) WSREP_DEBUG("empty rbr buffer to apply: %lld", + (long long) wsrep_thd_trx_seqno(thd)); + + while(buf_len) + { + int exec_res; + Log_event* ev= wsrep_read_log_event(&buf, &buf_len, + wsrep_get_apply_format(thd)); + + if (!ev) + { + WSREP_ERROR("applier could not read binlog event, seqno: %lld, len: %zu", + (long long)wsrep_thd_trx_seqno(thd), buf_len); + rcode= 1; + goto error; + } + + typ= ev->get_type_code(); + + switch (typ) { + case FORMAT_DESCRIPTION_EVENT: + wsrep_set_apply_format(thd, (Format_description_log_event*)ev); + continue; +#ifdef GTID_SUPPORT + case GTID_LOG_EVENT: + { + Gtid_log_event* gev= (Gtid_log_event*)ev; + if (gev->get_gno() == 0) + { + /* Skip GTID log event to make binlog to generate LTID on commit */ + delete ev; + continue; + } + } +#endif /* GTID_SUPPORT */ + default: + break; + } + + /* Use the original server id for logging. */ + thd->set_server_id(ev->server_id); + thd->set_time(); // time the query + wsrep_xid_init(&thd->transaction.xid_state.xid, + thd->wsrep_trx_meta.gtid.uuid, + thd->wsrep_trx_meta.gtid.seqno); + thd->lex->current_select= 0; + if (!ev->when) + { + my_hrtime_t hrtime= my_hrtime(); + ev->when= hrtime_to_my_time(hrtime); + ev->when_sec_part= hrtime_sec_part(hrtime); + } + + thd->variables.option_bits= + (thd->variables.option_bits & ~OPTION_SKIP_REPLICATION) | + (ev->flags & LOG_EVENT_SKIP_REPLICATION_F ? OPTION_SKIP_REPLICATION : 0); + + ev->thd = thd; + exec_res = ev->apply_event(thd->wsrep_rgi); + DBUG_PRINT("info", ("exec_event result: %d", exec_res)); + + if (exec_res) + { + WSREP_WARN("RBR event %d %s apply warning: %d, %lld", + event, ev->get_type_str(), exec_res, + (long long) wsrep_thd_trx_seqno(thd)); + rcode= exec_res; + /* stop processing for the first error */ + delete ev; + goto error; + } + event++; + + if (thd->wsrep_conflict_state!= NO_CONFLICT && + thd->wsrep_conflict_state!= REPLAYING) + WSREP_WARN("conflict state after RBR event applying: %d, %lld", + thd->wsrep_query_state, (long long)wsrep_thd_trx_seqno(thd)); + + if (thd->wsrep_conflict_state == MUST_ABORT) { + WSREP_WARN("RBR event apply failed, rolling back: %lld", + (long long) wsrep_thd_trx_seqno(thd)); + trans_rollback(thd); + thd->locked_tables_list.unlock_locked_tables(thd); + /* Release transactional metadata locks. */ + thd->mdl_context.release_transactional_locks(); + thd->wsrep_conflict_state= NO_CONFLICT; + DBUG_RETURN(WSREP_CB_FAILURE); + } + + delete_or_keep_event_post_apply(thd->wsrep_rgi, typ, ev); + } + + error: + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->wsrep_query_state= QUERY_IDLE; + mysql_mutex_unlock(&thd->LOCK_thd_data); + + assert(thd->wsrep_exec_mode== REPL_RECV); + + if (thd->killed == KILL_CONNECTION) + WSREP_INFO("applier aborted: %lld", (long long)wsrep_thd_trx_seqno(thd)); + + if (rcode) DBUG_RETURN(WSREP_CB_FAILURE); + DBUG_RETURN(WSREP_CB_SUCCESS); +} + +wsrep_cb_status_t wsrep_apply_cb(void* const ctx, + const void* const buf, + size_t const buf_len, + uint32_t const flags, + const wsrep_trx_meta_t* meta) +{ + THD* const thd((THD*)ctx); + + assert(thd->wsrep_apply_toi == false); + + // Allow tests to block the applier thread using the DBUG facilities. + DBUG_EXECUTE_IF("sync.wsrep_apply_cb", + { + const char act[]= + "now " + "SIGNAL sync.wsrep_apply_cb_reached " + "WAIT_FOR signal.wsrep_apply_cb"; + DBUG_ASSERT(!debug_sync_set_action(thd, + STRING_WITH_LEN(act))); + };); + + thd->wsrep_trx_meta = *meta; + +#ifdef WSREP_PROC_INFO + snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "applying write set %lld: %p, %zu", + (long long)wsrep_thd_trx_seqno(thd), buf, buf_len); + thd_proc_info(thd, thd->wsrep_info); +#else + thd_proc_info(thd, "applying write set"); +#endif /* WSREP_PROC_INFO */ + + /* tune FK and UK checking policy */ + if (wsrep_slave_UK_checks == FALSE) + thd->variables.option_bits|= OPTION_RELAXED_UNIQUE_CHECKS; + else + thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS; + + if (wsrep_slave_FK_checks == FALSE) + thd->variables.option_bits|= OPTION_NO_FOREIGN_KEY_CHECKS; + else + thd->variables.option_bits&= ~OPTION_NO_FOREIGN_KEY_CHECKS; + + if (flags & WSREP_FLAG_ISOLATION) + { + thd->wsrep_apply_toi= true; + /* + Don't run in transaction mode with TOI actions. + */ + thd->variables.option_bits&= ~OPTION_BEGIN; + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + } + wsrep_cb_status_t rcode(wsrep_apply_events(thd, buf, buf_len)); + +#ifdef WSREP_PROC_INFO + snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "applied write set %lld", (long long)wsrep_thd_trx_seqno(thd)); + thd_proc_info(thd, thd->wsrep_info); +#else + thd_proc_info(thd, "applied write set"); +#endif /* WSREP_PROC_INFO */ + + if (WSREP_CB_SUCCESS != rcode) + { + wsrep_dump_rbr_buf_with_header(thd, buf, buf_len); + } + + TABLE *tmp; + while ((tmp = thd->temporary_tables)) + { + WSREP_DEBUG("Applier %lu, has temporary tables: %s.%s", + thd->thread_id, + (tmp->s) ? tmp->s->db.str : "void", + (tmp->s) ? tmp->s->table_name.str : "void"); + close_temporary_table(thd, tmp, 1, 1); + } + + return rcode; +} + +static wsrep_cb_status_t wsrep_commit(THD* const thd) +{ +#ifdef WSREP_PROC_INFO + snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "committing %lld", (long long)wsrep_thd_trx_seqno(thd)); + thd_proc_info(thd, thd->wsrep_info); +#else + thd_proc_info(thd, "committing"); +#endif /* WSREP_PROC_INFO */ + + wsrep_cb_status_t const rcode(trans_commit(thd) ? + WSREP_CB_FAILURE : WSREP_CB_SUCCESS); + + if (WSREP_CB_SUCCESS == rcode) + { + thd->wsrep_rgi->cleanup_context(thd, false); +#ifdef GTID_SUPPORT + thd->variables.gtid_next.set_automatic(); +#endif /* GTID_SUPPORT */ + if (thd->wsrep_apply_toi) + { + wsrep_set_SE_checkpoint(thd->wsrep_trx_meta.gtid.uuid, + thd->wsrep_trx_meta.gtid.seqno); + } + } + +#ifdef WSREP_PROC_INFO + snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "committed %lld", (long long) wsrep_thd_trx_seqno(thd)); + thd_proc_info(thd, thd->wsrep_info); +#else + thd_proc_info(thd, "committed"); +#endif /* WSREP_PROC_INFO */ + + return rcode; +} + +static wsrep_cb_status_t wsrep_rollback(THD* const thd) +{ +#ifdef WSREP_PROC_INFO + snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "rolling back %lld", (long long)wsrep_thd_trx_seqno(thd)); + thd_proc_info(thd, thd->wsrep_info); +#else + thd_proc_info(thd, "rolling back"); +#endif /* WSREP_PROC_INFO */ + + wsrep_cb_status_t const rcode(trans_rollback(thd) ? + WSREP_CB_FAILURE : WSREP_CB_SUCCESS); + +#ifdef WSREP_PROC_INFO + snprintf(thd->wsrep_info, sizeof(thd->wsrep_info) - 1, + "rolled back %lld", (long long)wsrep_thd_trx_seqno(thd)); + thd_proc_info(thd, thd->wsrep_info); +#else + thd_proc_info(thd, "rolled back"); +#endif /* WSREP_PROC_INFO */ + + return rcode; +} + +wsrep_cb_status_t wsrep_commit_cb(void* const ctx, + uint32_t const flags, + const wsrep_trx_meta_t* meta, + wsrep_bool_t* const exit, + bool const commit) +{ + THD* const thd((THD*)ctx); + + assert(meta->gtid.seqno == wsrep_thd_trx_seqno(thd)); + + wsrep_cb_status_t rcode; + + if (commit) + rcode = wsrep_commit(thd); + else + rcode = wsrep_rollback(thd); + + /* Cleanup */ + wsrep_set_apply_format(thd, NULL); + thd->mdl_context.release_transactional_locks(); + thd->reset_query(); /* Mutex protected */ + free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC)); + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + + if (wsrep_slave_count_change < 0 && commit && WSREP_CB_SUCCESS == rcode) + { + mysql_mutex_lock(&LOCK_wsrep_slave_threads); + if (wsrep_slave_count_change < 0) + { + wsrep_slave_count_change++; + *exit = true; + } + mysql_mutex_unlock(&LOCK_wsrep_slave_threads); + } + + if (thd->wsrep_applier) + { + /* From trans_begin() */ + thd->variables.option_bits|= OPTION_BEGIN; + thd->server_status|= SERVER_STATUS_IN_TRANS; + thd->wsrep_apply_toi= false; + } + + return rcode; +} + + +wsrep_cb_status_t wsrep_unordered_cb(void* const ctx, + const void* const data, + size_t const size) +{ + return WSREP_CB_SUCCESS; +} diff --git a/sql/wsrep_applier.h b/sql/wsrep_applier.h new file mode 100644 index 00000000000..f19d2d46d0c --- /dev/null +++ b/sql/wsrep_applier.h @@ -0,0 +1,44 @@ +/* Copyright 2013 Codership Oy <http://www.codership.com> + + 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 02111-1301 USA */ + +#ifndef WSREP_APPLIER_H +#define WSREP_APPLIER_H + +#include <my_config.h> +#include "../wsrep/wsrep_api.h" + +void wsrep_set_apply_format(THD* thd, Format_description_log_event* ev); +Format_description_log_event* wsrep_get_apply_format(THD* thd); + +/* wsrep callback prototypes */ +extern "C" { + +wsrep_cb_status_t wsrep_apply_cb(void *ctx, + const void* buf, size_t buf_len, + uint32_t flags, + const wsrep_trx_meta_t* meta); + +wsrep_cb_status_t wsrep_commit_cb(void *ctx, + uint32_t flags, + const wsrep_trx_meta_t* meta, + wsrep_bool_t* exit, + bool commit); + +wsrep_cb_status_t wsrep_unordered_cb(void* ctx, + const void* data, + size_t size); + +} /* extern "C" */ +#endif /* WSREP_APPLIER_H */ diff --git a/sql/wsrep_binlog.cc b/sql/wsrep_binlog.cc new file mode 100644 index 00000000000..39c77133f02 --- /dev/null +++ b/sql/wsrep_binlog.cc @@ -0,0 +1,542 @@ +/* Copyright (C) 2013 Codership Oy <info@codership.com> + + 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-1301 USA. */ + +#include "wsrep_binlog.h" +#include "wsrep_priv.h" +#include "log.h" +#include "log_event.h" +#include "wsrep_applier.h" + +extern handlerton *binlog_hton; +/* + Write the contents of a cache to a memory buffer. + + This function quite the same as MYSQL_BIN_LOG::write_cache(), + with the exception that here we write in buffer instead of log file. + */ +int wsrep_write_cache_buf(IO_CACHE *cache, uchar **buf, size_t *buf_len) +{ + *buf= NULL; + *buf_len= 0; + + my_off_t const saved_pos(my_b_tell(cache)); + + if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0)) + { + WSREP_ERROR("failed to initialize io-cache"); + return ER_ERROR_ON_WRITE; + } + + uint length = my_b_bytes_in_cache(cache); + if (unlikely(0 == length)) length = my_b_fill(cache); + + size_t total_length = 0; + + if (likely(length > 0)) do + { + total_length += length; + /* + Bail out if buffer grows too large. + A temporary fix to avoid allocating indefinitely large buffer, + not a real limit on a writeset size which includes other things + like header and keys. + */ + if (total_length > wsrep_max_ws_size) + { + WSREP_WARN("transaction size limit (%lu) exceeded: %zu", + wsrep_max_ws_size, total_length); + goto error; + } + uchar* tmp = (uchar *)my_realloc(*buf, total_length, + MYF(MY_ALLOW_ZERO_PTR)); + if (!tmp) + { + WSREP_ERROR("could not (re)allocate buffer: %zu + %u", + *buf_len, length); + goto error; + } + *buf = tmp; + + memcpy(*buf + *buf_len, cache->read_pos, length); + *buf_len = total_length; + + if (cache->file < 0) + { + cache->read_pos= cache->read_end; + break; + } + } while ((length = my_b_fill(cache))); + + if (reinit_io_cache(cache, WRITE_CACHE, saved_pos, 0, 0)) + { + WSREP_WARN("failed to initialize io-cache"); + goto cleanup; + } + + return 0; + +error: + if (reinit_io_cache(cache, WRITE_CACHE, saved_pos, 0, 0)) + { + WSREP_WARN("failed to initialize io-cache"); + } +cleanup: + my_free(*buf); + *buf= NULL; + *buf_len= 0; + return ER_ERROR_ON_WRITE; +} + +#define STACK_SIZE 4096 /* 4K - for buffer preallocated on the stack: + * many transactions would fit in there + * so there is no need to reach for the heap */ + +/* Returns minimum multiple of HEAP_PAGE_SIZE that is >= length */ +static inline size_t +heap_size(size_t length) +{ + return (length + HEAP_PAGE_SIZE - 1)/HEAP_PAGE_SIZE*HEAP_PAGE_SIZE; +} + +/* append data to writeset */ +static inline wsrep_status_t +wsrep_append_data(wsrep_t* const wsrep, + wsrep_ws_handle_t* const ws, + const void* const data, + size_t const len) +{ + struct wsrep_buf const buff = { data, len }; + wsrep_status_t const rc(wsrep->append_data(wsrep, ws, &buff, 1, + WSREP_DATA_ORDERED, true)); + if (rc != WSREP_OK) + { + WSREP_WARN("append_data() returned %d", rc); + } + + return rc; +} + +/* + Write the contents of a cache to wsrep provider. + + This function quite the same as MYSQL_BIN_LOG::write_cache(), + with the exception that here we write in buffer instead of log file. + + This version reads all of cache into single buffer and then appends to a + writeset at once. + */ +static int wsrep_write_cache_once(wsrep_t* const wsrep, + THD* const thd, + IO_CACHE* const cache, + size_t* const len) +{ + my_off_t const saved_pos(my_b_tell(cache)); + + if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0)) + { + WSREP_ERROR("failed to initialize io-cache"); + return ER_ERROR_ON_WRITE; + } + + int err(WSREP_OK); + + size_t total_length(0); + uchar stack_buf[STACK_SIZE]; /* to avoid dynamic allocations for few data*/ + uchar* heap_buf(NULL); + uchar* buf(stack_buf); + size_t allocated(sizeof(stack_buf)); + size_t used(0); + + uint length(my_b_bytes_in_cache(cache)); + if (unlikely(0 == length)) length = my_b_fill(cache); + + if (likely(length > 0)) do + { + total_length += length; + /* + Bail out if buffer grows too large. + A temporary fix to avoid allocating indefinitely large buffer, + not a real limit on a writeset size which includes other things + like header and keys. + */ + if (unlikely(total_length > wsrep_max_ws_size)) + { + WSREP_WARN("transaction size limit (%lu) exceeded: %zu", + wsrep_max_ws_size, total_length); + err = WSREP_TRX_SIZE_EXCEEDED; + goto cleanup; + } + + if (total_length > allocated) + { + size_t const new_size(heap_size(total_length)); + uchar* tmp = (uchar *)my_realloc(heap_buf, new_size, + MYF(MY_ALLOW_ZERO_PTR)); + if (!tmp) + { + WSREP_ERROR("could not (re)allocate buffer: %zu + %u", + allocated, length); + err = WSREP_TRX_SIZE_EXCEEDED; + goto cleanup; + } + + heap_buf = tmp; + buf = heap_buf; + allocated = new_size; + + if (used <= STACK_SIZE && used > 0) // there's data in stack_buf + { + DBUG_ASSERT(buf == stack_buf); + memcpy(heap_buf, stack_buf, used); + } + } + + memcpy(buf + used, cache->read_pos, length); + used = total_length; + if (cache->file < 0) + { + cache->read_pos= cache->read_end; + break; + } + } while ((length = my_b_fill(cache))); + + if (used > 0) + err = wsrep_append_data(wsrep, &thd->wsrep_ws_handle, buf, used); + + if (WSREP_OK == err) *len = total_length; + +cleanup: + if (reinit_io_cache(cache, WRITE_CACHE, saved_pos, 0, 0)) + { + WSREP_ERROR("failed to reinitialize io-cache"); + } + + if (unlikely(WSREP_OK != err)) + { + wsrep_dump_rbr_buf_with_header(thd, buf, used); + } + + my_free(heap_buf); + return err; +} + +/* + Write the contents of a cache to wsrep provider. + + This function quite the same as MYSQL_BIN_LOG::write_cache(), + with the exception that here we write in buffer instead of log file. + + This version uses incremental data appending as it reads it from cache. + */ +static int wsrep_write_cache_inc(wsrep_t* const wsrep, + THD* const thd, + IO_CACHE* const cache, + size_t* const len) +{ + my_off_t const saved_pos(my_b_tell(cache)); + + if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0)) + { + WSREP_ERROR("failed to initialize io-cache"); + return WSREP_TRX_ERROR; + } + + int err(WSREP_OK); + + size_t total_length(0); + + uint length(my_b_bytes_in_cache(cache)); + if (unlikely(0 == length)) length = my_b_fill(cache); + + if (likely(length > 0)) do + { + total_length += length; + /* bail out if buffer grows too large + not a real limit on a writeset size which includes other things + like header and keys. + */ + if (unlikely(total_length > wsrep_max_ws_size)) + { + WSREP_WARN("transaction size limit (%lu) exceeded: %zu", + wsrep_max_ws_size, total_length); + err = WSREP_TRX_SIZE_EXCEEDED; + goto cleanup; + } + + if(WSREP_OK != (err=wsrep_append_data(wsrep, &thd->wsrep_ws_handle, + cache->read_pos, length))) + goto cleanup; + + if (cache->file < 0) + { + cache->read_pos= cache->read_end; + break; + } + } while ((length = my_b_fill(cache))); + + if (WSREP_OK == err) *len = total_length; + +cleanup: + if (reinit_io_cache(cache, WRITE_CACHE, saved_pos, 0, 0)) + { + WSREP_ERROR("failed to reinitialize io-cache"); + } + + return err; +} + +/* + Write the contents of a cache to wsrep provider. + + This function quite the same as MYSQL_BIN_LOG::write_cache(), + with the exception that here we write in buffer instead of log file. + */ +int wsrep_write_cache(wsrep_t* const wsrep, + THD* const thd, + IO_CACHE* const cache, + size_t* const len) +{ + if (wsrep_incremental_data_collection) { + return wsrep_write_cache_inc(wsrep, thd, cache, len); + } + else { + return wsrep_write_cache_once(wsrep, thd, cache, len); + } +} + +void wsrep_dump_rbr_buf(THD *thd, const void* rbr_buf, size_t buf_len) +{ + int len= snprintf(NULL, 0, "%s/GRA_%ld_%lld.log", + wsrep_data_home_dir, thd->thread_id, + (long long)wsrep_thd_trx_seqno(thd)); + if (len < 0) + { + WSREP_ERROR("snprintf error: %d, skipping dump.", len); + return; + } + /* + len doesn't count the \0 end-of-string. Use len+1 below + to alloc and pass as an argument to snprintf. + */ + + char *filename= (char *)malloc(len+1); + int len1= snprintf(filename, len+1, "%s/GRA_%ld_%lld.log", + wsrep_data_home_dir, thd->thread_id, + (long long)wsrep_thd_trx_seqno(thd)); + + if (len > len1) + { + WSREP_ERROR("RBR dump path truncated: %d, skipping dump.", len); + free(filename); + return; + } + + FILE *of= fopen(filename, "wb"); + + if (of) + { + if (fwrite(rbr_buf, buf_len, 1, of) == 0) + WSREP_ERROR("Failed to write buffer of length %llu to '%s'", + (unsigned long long)buf_len, filename); + + fclose(of); + } + else + { + WSREP_ERROR("Failed to open file '%s': %d (%s)", + filename, errno, strerror(errno)); + } + free(filename); +} + +/* + wsrep exploits binlog's caches even if binlogging itself is not + activated. In such case connection close needs calling + actual binlog's method. + Todo: split binlog hton from its caches to use ones by wsrep + without referring to binlog's stuff. +*/ +int wsrep_binlog_close_connection(THD* thd) +{ + DBUG_ENTER("wsrep_binlog_close_connection"); + if (thd_get_ha_data(thd, binlog_hton) != NULL) + binlog_hton->close_connection (binlog_hton, thd); + DBUG_RETURN(0); +} + +int wsrep_binlog_savepoint_set(THD *thd, void *sv) +{ + if (!wsrep_emulate_bin_log) return 0; + int rcode = binlog_hton->savepoint_set(binlog_hton, thd, sv); + return rcode; +} + +int wsrep_binlog_savepoint_rollback(THD *thd, void *sv) +{ + if (!wsrep_emulate_bin_log) return 0; + int rcode = binlog_hton->savepoint_rollback(binlog_hton, thd, sv); + return rcode; +} + +#if 0 +void wsrep_dump_rbr_direct(THD* thd, IO_CACHE* cache) +{ + char filename[PATH_MAX]= {0}; + int len= snprintf(filename, PATH_MAX, "%s/GRA_%ld_%lld.log", + wsrep_data_home_dir, thd->thread_id, + (long long)wsrep_thd_trx_seqno(thd)); + size_t bytes_in_cache = 0; + // check path + if (len >= PATH_MAX) + { + WSREP_ERROR("RBR dump path too long: %d, skipping dump.", len); + return ; + } + // init cache + my_off_t const saved_pos(my_b_tell(cache)); + if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0)) + { + WSREP_ERROR("failed to initialize io-cache"); + return ; + } + // open file + FILE* of = fopen(filename, "wb"); + if (!of) + { + WSREP_ERROR("Failed to open file '%s': %d (%s)", + filename, errno, strerror(errno)); + goto cleanup; + } + // ready to write + bytes_in_cache= my_b_bytes_in_cache(cache); + if (unlikely(bytes_in_cache == 0)) bytes_in_cache = my_b_fill(cache); + if (likely(bytes_in_cache > 0)) do + { + if (my_fwrite(of, cache->read_pos, bytes_in_cache, + MYF(MY_WME | MY_NABP)) == (size_t) -1) + { + WSREP_ERROR("Failed to write file '%s'", filename); + goto cleanup; + } + + if (cache->file < 0) + { + cache->read_pos= cache->read_end; + break; + } + } while ((bytes_in_cache= my_b_fill(cache))); + if(cache->error == -1) + { + WSREP_ERROR("RBR inconsistent"); + goto cleanup; + } +cleanup: + // init back + if (reinit_io_cache(cache, WRITE_CACHE, saved_pos, 0, 0)) + { + WSREP_ERROR("failed to reinitialize io-cache"); + } + // close file + if (of) fclose(of); +} +#endif + +void thd_binlog_flush_pending_rows_event(THD *thd, bool stmt_end) +{ + thd->binlog_flush_pending_rows_event(stmt_end); +} + +/* Dump replication buffer along with header to a file. */ +void wsrep_dump_rbr_buf_with_header(THD *thd, const void *rbr_buf, + size_t buf_len) +{ + DBUG_ENTER("wsrep_dump_rbr_buf_with_header"); + + File file; + IO_CACHE cache; + Log_event_writer writer(&cache); + Format_description_log_event *ev=NULL; + + longlong thd_trx_seqno= (long long)wsrep_thd_trx_seqno(thd); + + int len= snprintf(NULL, 0, "%s/GRA_%ld_%lld_v2.log", + wsrep_data_home_dir, thd->thread_id, + thd_trx_seqno); + /* + len doesn't count the \0 end-of-string. Use len+1 below + to alloc and pass as an argument to snprintf. + */ + char *filename; + if (len < 0 || !(filename= (char*)malloc(len+1))) + { + WSREP_ERROR("snprintf error: %d, skipping dump.", len); + DBUG_VOID_RETURN; + } + + int len1= snprintf(filename, len+1, "%s/GRA_%ld_%lld_v2.log", + wsrep_data_home_dir, thd->thread_id, + thd_trx_seqno); + + if (len > len1) + { + WSREP_ERROR("RBR dump path truncated: %d, skipping dump.", len); + free(filename); + DBUG_VOID_RETURN; + } + + if ((file= mysql_file_open(key_file_wsrep_gra_log, filename, + O_RDWR | O_CREAT | O_BINARY, MYF(MY_WME))) < 0) + { + WSREP_ERROR("Failed to open file '%s' : %d (%s)", + filename, errno, strerror(errno)); + goto cleanup1; + } + + if (init_io_cache(&cache, file, 0, WRITE_CACHE, 0, 0, MYF(MY_WME | MY_NABP))) + { + goto cleanup2; + } + + if (my_b_safe_write(&cache, BINLOG_MAGIC, BIN_LOG_HEADER_SIZE)) + { + goto cleanup2; + } + + /* + Instantiate an FDLE object for non-wsrep threads (to be written + to the dump file). + */ + ev= (thd->wsrep_applier) ? wsrep_get_apply_format(thd) : + (new Format_description_log_event(4)); + + if (writer.write(ev) || my_b_write(&cache, (uchar*)rbr_buf, buf_len) || + flush_io_cache(&cache)) + { + WSREP_ERROR("Failed to write to '%s'.", filename); + goto cleanup2; + } + +cleanup2: + end_io_cache(&cache); + +cleanup1: + free(filename); + mysql_file_close(file, MYF(MY_WME)); + + if (!thd->wsrep_applier) delete ev; + + DBUG_VOID_RETURN; +} + diff --git a/sql/wsrep_binlog.h b/sql/wsrep_binlog.h new file mode 100644 index 00000000000..864813d5c98 --- /dev/null +++ b/sql/wsrep_binlog.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2013 Codership Oy <info@codership.com> + + 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-1301 USA. */ + +#ifndef WSREP_BINLOG_H +#define WSREP_BINLOG_H + +#include "sql_class.h" // THD, IO_CACHE + +#define HEAP_PAGE_SIZE 65536 /* 64K */ +#define WSREP_MAX_WS_SIZE 2147483647 /* 2GB */ + +/* + Write the contents of a cache to a memory buffer. + + This function quite the same as MYSQL_BIN_LOG::write_cache(), + with the exception that here we write in buffer instead of log file. + */ +int wsrep_write_cache_buf(IO_CACHE *cache, uchar **buf, size_t *buf_len); + +/* + Write the contents of a cache to wsrep provider. + + This function quite the same as MYSQL_BIN_LOG::write_cache(), + with the exception that here we write in buffer instead of log file. + + @param len total amount of data written + @return wsrep error status + */ +int wsrep_write_cache (wsrep_t* const wsrep, + THD* const thd, + IO_CACHE* const cache, + size_t* const len); + +/* Dump replication buffer to disk */ +void wsrep_dump_rbr_buf(THD *thd, const void* rbr_buf, size_t buf_len); + +/* Dump replication buffer to disk without intermediate buffer */ +void wsrep_dump_rbr_direct(THD* thd, IO_CACHE* cache); + +/* Dump replication buffer along with header to a file */ +void wsrep_dump_rbr_buf_with_header(THD *thd, const void *rbr_buf, + size_t buf_len); + +int wsrep_binlog_close_connection(THD* thd); +int wsrep_binlog_savepoint_set(THD *thd, void *sv); +int wsrep_binlog_savepoint_rollback(THD *thd, void *sv); + +#endif /* WSREP_BINLOG_H */ diff --git a/sql/wsrep_check_opts.cc b/sql/wsrep_check_opts.cc new file mode 100644 index 00000000000..28bd3a4492b --- /dev/null +++ b/sql/wsrep_check_opts.cc @@ -0,0 +1,102 @@ +/* Copyright 2011 Codership Oy <http://www.codership.com> + Copyright 2014 SkySQL Ab. + + 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 02111-1301 USA */ + +#include "mysqld.h" +#include "sys_vars_shared.h" +#include "wsrep.h" +#include "wsrep_sst.h" +//#include <sql_class.h> +//#include "wsrep_mysqld.h" + +extern char *my_bind_addr_str; + +int wsrep_check_opts() +{ + if (wsrep_slave_threads > 1) + { + sys_var *autoinc_lock_mode= + intern_find_sys_var(STRING_WITH_LEN("innodb_autoinc_lock_mode")); + bool is_null; + if (autoinc_lock_mode && + autoinc_lock_mode->val_int(&is_null, 0, OPT_GLOBAL, 0) != 2) + { + WSREP_ERROR("Parallel applying (wsrep_slave_threads > 1) requires" + " innodb_autoinc_lock_mode = 2."); + return 1; + } + } + + if (locked_in_memory) + { + WSREP_ERROR("Memory locking is not supported (locked_in_memory=ON)"); + return 1; + } + + if (!strcasecmp(wsrep_sst_method, "mysqldump")) + { + if (my_bind_addr_str && + (!strcasecmp(my_bind_addr_str, "127.0.0.1") || + !strcasecmp(my_bind_addr_str, "localhost"))) + { + WSREP_WARN("wsrep_sst_method is set to 'mysqldump' yet " + "mysqld bind_address is set to '%s', which makes it " + "impossible to receive state transfer from another " + "node, since mysqld won't accept such connections. " + "If you wish to use mysqldump state transfer method, " + "set bind_address to allow mysql client connections " + "from other cluster members (e.g. 0.0.0.0).", + my_bind_addr_str); + } + } + else + { + // non-mysqldump SST requires wsrep_cluster_address on startup + if (!wsrep_cluster_address || !wsrep_cluster_address[0]) + { + WSREP_ERROR ("%s SST method requires wsrep_cluster_address to be " + "configured on startup.", wsrep_sst_method); + return 1; + } + } + + if (strcasecmp(wsrep_sst_receive_address, "AUTO")) + { + if (!strncasecmp(wsrep_sst_receive_address, STRING_WITH_LEN("127.0.0.1")) || + !strncasecmp(wsrep_sst_receive_address, STRING_WITH_LEN("localhost"))) + { + WSREP_WARN("wsrep_sst_receive_address is set to '%s' which " + "makes it impossible for another host to reach this " + "one. Please set it to the address which this node " + "can be connected at by other cluster members.", + wsrep_sst_receive_address); + } + } + + if (strcasecmp(wsrep_provider, "NONE")) + { + if (global_system_variables.binlog_format != BINLOG_FORMAT_ROW) + { + WSREP_ERROR("Only binlog_format = 'ROW' is currently supported. " + "Configured value: '%s'. Please adjust your " + "configuration.", + binlog_format_names[global_system_variables.binlog_format]); + + return 1; + } + } + return 0; +} + diff --git a/sql/wsrep_dummy.cc b/sql/wsrep_dummy.cc new file mode 100644 index 00000000000..795e2d19252 --- /dev/null +++ b/sql/wsrep_dummy.cc @@ -0,0 +1,143 @@ +/* Copyright (C) 2014 SkySQL Ab. + + 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-1301 USA. */ + +#include <my_global.h> +#include <sql_class.h> +#include <mysql/service_wsrep.h> + +my_bool wsrep_thd_is_BF(THD *, my_bool) +{ return 0; } + +int wsrep_trx_order_before(THD *, THD *) +{ return 0; } + +enum wsrep_conflict_state wsrep_thd_conflict_state(THD *, my_bool) +{ return NO_CONFLICT; } + +int wsrep_is_wsrep_xid(const XID*) +{ return 0; } + +bool wsrep_prepare_key(const uchar*, size_t, const uchar*, size_t, struct wsrep_buf*, size_t*) +{ return 0; } + +struct wsrep *get_wsrep() +{ return 0; } + +my_bool get_wsrep_certify_nonPK() +{ return 0; } + +my_bool get_wsrep_debug() +{ return 0; } + +my_bool get_wsrep_drupal_282555_workaround() +{ return 0; } + +my_bool get_wsrep_load_data_splitting() +{ return 0; } + +my_bool get_wsrep_recovery() +{ return 0; } + +my_bool get_wsrep_log_conflicts() +{ return 0; } + +long get_wsrep_protocol_version() +{ return 0; } + +my_bool wsrep_aborting_thd_contains(THD *) +{ return 0; } + +void wsrep_aborting_thd_enqueue(THD *) +{ } + +bool wsrep_consistency_check(THD *) +{ return 0; } + +void wsrep_lock_rollback() +{ } + +int wsrep_on(THD *thd) +{ return 0; } + +void wsrep_post_commit(THD*, bool) +{ } + +enum wsrep_trx_status wsrep_run_wsrep_commit(THD *, bool) +{ return WSREP_TRX_ERROR; } + +void wsrep_thd_LOCK(THD *) +{ } + +void wsrep_thd_UNLOCK(THD *) +{ } + +void wsrep_thd_awake(THD *, my_bool) +{ } + +const char *wsrep_thd_conflict_state_str(THD *) +{ return 0; } + +enum wsrep_exec_mode wsrep_thd_exec_mode(THD *) +{ return LOCAL_STATE; } + +const char *wsrep_thd_exec_mode_str(THD *) +{ return NULL; } + +enum wsrep_conflict_state wsrep_thd_get_conflict_state(THD *) +{ return NO_CONFLICT; } + +my_bool wsrep_thd_is_wsrep(THD *) +{ return 0; } + +char *wsrep_thd_query(THD *) +{ return 0; } + +enum wsrep_query_state wsrep_thd_query_state(THD *) +{ return QUERY_IDLE; } + +const char *wsrep_thd_query_state_str(THD *) +{ return 0; } + +int wsrep_thd_retry_counter(THD *) +{ return 0; } + +void wsrep_thd_set_conflict_state(THD *, enum wsrep_conflict_state) +{ } + +bool wsrep_thd_ignore_table(THD *) +{ return 0; } + +longlong wsrep_thd_trx_seqno(THD *) +{ return -1; } + +struct wsrep_ws_handle* wsrep_thd_ws_handle(THD *) +{ return 0; } + +void wsrep_thd_auto_increment_variables(THD *thd, + unsigned long long *offset, + unsigned long long *increment) +{ + *offset= thd->variables.auto_increment_offset; + *increment= thd->variables.auto_increment_increment; +} + +int wsrep_trx_is_aborting(THD *) +{ return 0; } + +void wsrep_unlock_rollback() +{ } + +void wsrep_set_data_home_dir(const char *) +{ } diff --git a/sql/wsrep_hton.cc b/sql/wsrep_hton.cc new file mode 100644 index 00000000000..a935f8c69b8 --- /dev/null +++ b/sql/wsrep_hton.cc @@ -0,0 +1,642 @@ +/* Copyright 2008-2015 Codership Oy <http://www.codership.com> + + 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 02111-1301 USA */ + +#include <mysqld.h> +#include "sql_base.h" +#include "rpl_filter.h" +#include <sql_class.h> +#include "wsrep_mysqld.h" +#include "wsrep_binlog.h" +#include "wsrep_xid.h" +#include <cstdio> +#include <cstdlib> +#include "debug_sync.h" + +extern handlerton *binlog_hton; +extern int binlog_close_connection(handlerton *hton, THD *thd); +extern ulonglong thd_to_trx_id(THD *thd); + +extern "C" int thd_binlog_format(const MYSQL_THD thd); +// todo: share interface with ha_innodb.c + +/* + Cleanup after local transaction commit/rollback, replay or TOI. +*/ +void wsrep_cleanup_transaction(THD *thd) +{ + if (!WSREP(thd)) return; + + if (wsrep_emulate_bin_log) thd_binlog_trx_reset(thd); + thd->wsrep_ws_handle.trx_id= WSREP_UNDEFINED_TRX_ID; + thd->wsrep_trx_meta.gtid= WSREP_GTID_UNDEFINED; + thd->wsrep_trx_meta.depends_on= WSREP_SEQNO_UNDEFINED; + thd->wsrep_exec_mode= LOCAL_STATE; + thd->wsrep_affected_rows= 0; + thd->wsrep_skip_wsrep_GTID= false; + return; +} + +/* + wsrep hton +*/ +handlerton *wsrep_hton; + + +/* + Registers wsrep hton at commit time if transaction has registered htons + for supported engine types. + + Hton should not be registered for TOTAL_ORDER operations. + + Registration is needed for both LOCAL_MODE and REPL_RECV transactions to run + commit in 2pc so that wsrep position gets properly recorded in storage + engines. + + Note that all hton calls should immediately return for threads that are + in REPL_RECV mode as their states are controlled by wsrep appliers or + replaying code. Only threads in LOCAL_MODE should run wsrep callbacks + from hton methods. +*/ +void wsrep_register_hton(THD* thd, bool all) +{ + if (WSREP(thd) && thd->wsrep_exec_mode != TOTAL_ORDER && + !thd->wsrep_apply_toi) + { + if (thd->wsrep_exec_mode == LOCAL_STATE && + (thd_sql_command(thd) == SQLCOM_OPTIMIZE || + thd_sql_command(thd) == SQLCOM_ANALYZE || + thd_sql_command(thd) == SQLCOM_REPAIR) && + thd->lex->no_write_to_binlog == 1) + { + WSREP_DEBUG("Skipping wsrep_register_hton for LOCAL sql admin command : %s", + thd->query()); + return; + } + + THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt; + for (Ha_trx_info *i= trans->ha_list; i; i = i->next()) + { + if ((i->ht()->db_type == DB_TYPE_INNODB) || + (i->ht()->db_type == DB_TYPE_TOKUDB)) + { + trans_register_ha(thd, all, wsrep_hton); + + /* follow innodb read/write settting + * but, as an exception: CTAS with empty result set will not be + * replicated unless we declare wsrep hton as read/write here + */ + if (i->is_trx_read_write() || + (thd->lex->sql_command == SQLCOM_CREATE_TABLE && + thd->wsrep_exec_mode == LOCAL_STATE)) + { + thd->ha_data[wsrep_hton->slot].ha_info[all].set_trx_read_write(); + } + break; + } + } + } +} + +/* + Calls wsrep->post_commit() for locally executed transactions that have + got seqno from provider (must commit) and don't require replaying. + */ +void wsrep_post_commit(THD* thd, bool all) +{ + if (!WSREP(thd)) return; + + switch (thd->wsrep_exec_mode) + { + case LOCAL_COMMIT: + { + DBUG_ASSERT(thd->wsrep_trx_meta.gtid.seqno != WSREP_SEQNO_UNDEFINED); + if (wsrep && wsrep->post_commit(wsrep, &thd->wsrep_ws_handle)) + { + DBUG_PRINT("wsrep", ("set committed fail")); + WSREP_WARN("set committed fail: %llu %d", + (long long)thd->real_id, thd->get_stmt_da()->status()); + } + wsrep_cleanup_transaction(thd); + break; + } + case LOCAL_STATE: + { + /* non-InnoDB statements may have populated events in stmt cache + => cleanup + */ + WSREP_DEBUG("cleanup transaction for LOCAL_STATE"); + /* + Run post-rollback hook to clean up in the case if + some keys were populated for the transaction in provider + but during commit time there was no write set to replicate. + This may happen when client sets the SAVEPOINT and immediately + rolls back to savepoint after first operation. + */ + if (all && thd->wsrep_conflict_state != MUST_REPLAY && + wsrep && wsrep->post_rollback(wsrep, &thd->wsrep_ws_handle)) + { + WSREP_WARN("post_rollback fail: %llu %d", + (long long)thd->thread_id, thd->get_stmt_da()->status()); + } + wsrep_cleanup_transaction(thd); + break; + } + default: break; + } +} + +/* + wsrep exploits binlog's caches even if binlogging itself is not + activated. In such case connection close needs calling + actual binlog's method. + Todo: split binlog hton from its caches to use ones by wsrep + without referring to binlog's stuff. +*/ +static int +wsrep_close_connection(handlerton* hton, THD* thd) +{ + DBUG_ENTER("wsrep_close_connection"); + + if (thd->wsrep_exec_mode == REPL_RECV) + { + DBUG_RETURN(0); + } + + if (wsrep_emulate_bin_log && thd_get_ha_data(thd, binlog_hton) != NULL) + binlog_hton->close_connection (binlog_hton, thd); + DBUG_RETURN(0); +} + +/* + prepare/wsrep_run_wsrep_commit can fail in two ways + - certification test or an equivalent. As a result, + the current transaction just rolls back + Error codes: + WSREP_TRX_CERT_FAIL, WSREP_TRX_SIZE_EXCEEDED, WSREP_TRX_ERROR + - a post-certification failure makes this server unable to + commit its own WS and therefore the server must abort +*/ +static int wsrep_prepare(handlerton *hton, THD *thd, bool all) +{ + DBUG_ENTER("wsrep_prepare"); + + if (thd->wsrep_exec_mode == REPL_RECV) + { + DBUG_RETURN(0); + } + + DBUG_ASSERT(thd->ha_data[wsrep_hton->slot].ha_info[all].is_trx_read_write()); + DBUG_ASSERT(thd->wsrep_exec_mode == LOCAL_STATE); + DBUG_ASSERT(thd->wsrep_trx_meta.gtid.seqno == WSREP_SEQNO_UNDEFINED); + + if ((all || + !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && + (thd->variables.wsrep_on && !wsrep_trans_cache_is_empty(thd))) + { + int res= wsrep_run_wsrep_commit(thd, all); + if (res != 0) + { + if (res == WSREP_TRX_SIZE_EXCEEDED) + res= EMSGSIZE; + else + res= EDEADLK; // for a better error message + } + DBUG_RETURN (res); + } + DBUG_RETURN(0); +} + +static int wsrep_savepoint_set(handlerton *hton, THD *thd, void *sv) +{ + DBUG_ENTER("wsrep_savepoint_set"); + + if (thd->wsrep_exec_mode == REPL_RECV) + { + DBUG_RETURN(0); + } + + if (!wsrep_emulate_bin_log) DBUG_RETURN(0); + int rcode = wsrep_binlog_savepoint_set(thd, sv); + DBUG_RETURN(rcode); +} + +static int wsrep_savepoint_rollback(handlerton *hton, THD *thd, void *sv) +{ + DBUG_ENTER("wsrep_savepoint_rollback"); + + if (thd->wsrep_exec_mode == REPL_RECV) + { + DBUG_RETURN(0); + } + + if (!wsrep_emulate_bin_log) DBUG_RETURN(0); + int rcode = wsrep_binlog_savepoint_rollback(thd, sv); + DBUG_RETURN(rcode); +} + +static int wsrep_rollback(handlerton *hton, THD *thd, bool all) +{ + DBUG_ENTER("wsrep_rollback"); + + if (thd->wsrep_exec_mode == REPL_RECV) + { + DBUG_RETURN(0); + } + + mysql_mutex_lock(&thd->LOCK_thd_data); + switch (thd->wsrep_exec_mode) + { + case TOTAL_ORDER: + case REPL_RECV: + mysql_mutex_unlock(&thd->LOCK_thd_data); + WSREP_DEBUG("Avoiding wsrep rollback for failed DDL: %s", thd->query()); + DBUG_RETURN(0); + default: break; + } + + if ((all || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && + thd->variables.wsrep_on && thd->wsrep_conflict_state != MUST_REPLAY) + { + if (wsrep && wsrep->post_rollback(wsrep, &thd->wsrep_ws_handle)) + { + DBUG_PRINT("wsrep", ("setting rollback fail")); + WSREP_ERROR("settting rollback fail: thd: %llu, schema: %s, SQL: %s", + (long long)thd->real_id, (thd->db ? thd->db : "(null)"), + thd->query()); + } + wsrep_cleanup_transaction(thd); + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + DBUG_RETURN(0); +} + +int wsrep_commit(handlerton *hton, THD *thd, bool all) +{ + DBUG_ENTER("wsrep_commit"); + + if (thd->wsrep_exec_mode == REPL_RECV) + { + DBUG_RETURN(0); + } + + mysql_mutex_lock(&thd->LOCK_thd_data); + if ((all || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && + (thd->variables.wsrep_on && thd->wsrep_conflict_state != MUST_REPLAY)) + { + if (thd->wsrep_exec_mode == LOCAL_COMMIT) + { + DBUG_ASSERT(thd->ha_data[wsrep_hton->slot].ha_info[all].is_trx_read_write()); + /* + Call to wsrep->post_commit() (moved to wsrep_post_commit()) must + be done only after commit has done for all involved htons. + */ + DBUG_PRINT("wsrep", ("commit")); + } + else + { + /* + Transaction didn't go through wsrep->pre_commit() so just roll back + possible changes to clean state. + */ + if (WSREP_PROVIDER_EXISTS) { + if (wsrep && wsrep->post_rollback(wsrep, &thd->wsrep_ws_handle)) + { + DBUG_PRINT("wsrep", ("setting rollback fail")); + WSREP_ERROR("settting rollback fail: thd: %llu, schema: %s, SQL: %s", + (long long)thd->real_id, (thd->db ? thd->db : "(null)"), + thd->query()); + } + } + wsrep_cleanup_transaction(thd); + } + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + DBUG_RETURN(0); +} + + +extern Rpl_filter* binlog_filter; +extern my_bool opt_log_slave_updates; + +enum wsrep_trx_status +wsrep_run_wsrep_commit(THD *thd, bool all) +{ + int rcode= -1; + size_t data_len= 0; + IO_CACHE *cache; + int replay_round= 0; + DBUG_ENTER("wsrep_run_wsrep_commit"); + + if (thd->get_stmt_da()->is_error()) { + WSREP_DEBUG("commit issue, error: %d %s", + thd->get_stmt_da()->sql_errno(), thd->get_stmt_da()->message()); + } + + DEBUG_SYNC(thd, "wsrep_before_replication"); + + if (thd->slave_thread && !opt_log_slave_updates) DBUG_RETURN(WSREP_TRX_OK); + + if (thd->wsrep_exec_mode == REPL_RECV) { + + mysql_mutex_lock(&thd->LOCK_thd_data); + if (thd->wsrep_conflict_state == MUST_ABORT) { + if (wsrep_debug) + WSREP_INFO("WSREP: must abort for BF"); + DBUG_PRINT("wsrep", ("BF apply commit fail")); + thd->wsrep_conflict_state = NO_CONFLICT; + mysql_mutex_unlock(&thd->LOCK_thd_data); + // + // TODO: test all calls of the rollback. + // rollback must happen automagically innobase_rollback(hton, thd, 1); + // + DBUG_RETURN(WSREP_TRX_ERROR); + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + } + + if (thd->wsrep_exec_mode != LOCAL_STATE) DBUG_RETURN(WSREP_TRX_OK); + + if (thd->wsrep_consistency_check == CONSISTENCY_CHECK_RUNNING) { + WSREP_DEBUG("commit for consistency check: %s", thd->query()); + DBUG_RETURN(WSREP_TRX_OK); + } + + DBUG_PRINT("wsrep", ("replicating commit")); + + mysql_mutex_lock(&thd->LOCK_thd_data); + if (thd->wsrep_conflict_state == MUST_ABORT) { + DBUG_PRINT("wsrep", ("replicate commit fail")); + thd->wsrep_conflict_state = ABORTED; + mysql_mutex_unlock(&thd->LOCK_thd_data); + if (wsrep_debug) { + WSREP_INFO("innobase_commit, abort %s", + (thd->query()) ? thd->query() : "void"); + } + DBUG_RETURN(WSREP_TRX_CERT_FAIL); + } + + mysql_mutex_lock(&LOCK_wsrep_replaying); + + while (wsrep_replaying > 0 && + thd->wsrep_conflict_state == NO_CONFLICT && + thd->killed == NOT_KILLED && + !shutdown_in_progress) + { + + mysql_mutex_unlock(&LOCK_wsrep_replaying); + mysql_mutex_unlock(&thd->LOCK_thd_data); + + mysql_mutex_lock(&thd->mysys_var->mutex); + thd_proc_info(thd, "wsrep waiting on replaying"); + thd->mysys_var->current_mutex= &LOCK_wsrep_replaying; + thd->mysys_var->current_cond= &COND_wsrep_replaying; + mysql_mutex_unlock(&thd->mysys_var->mutex); + + mysql_mutex_lock(&LOCK_wsrep_replaying); + // Using timedwait is a hack to avoid deadlock in case if BF victim + // misses the signal. + struct timespec wtime = {0, 1000000}; + mysql_cond_timedwait(&COND_wsrep_replaying, &LOCK_wsrep_replaying, + &wtime); + + if (replay_round++ % 100000 == 0) + WSREP_DEBUG("commit waiting for replaying: replayers %d, thd: (%lu) " + "conflict: %d (round: %d)", + wsrep_replaying, thd->thread_id, + thd->wsrep_conflict_state, replay_round); + + mysql_mutex_unlock(&LOCK_wsrep_replaying); + + mysql_mutex_lock(&thd->mysys_var->mutex); + thd->mysys_var->current_mutex= 0; + thd->mysys_var->current_cond= 0; + mysql_mutex_unlock(&thd->mysys_var->mutex); + + mysql_mutex_lock(&thd->LOCK_thd_data); + mysql_mutex_lock(&LOCK_wsrep_replaying); + } + mysql_mutex_unlock(&LOCK_wsrep_replaying); + + if (thd->wsrep_conflict_state == MUST_ABORT) { + DBUG_PRINT("wsrep", ("replicate commit fail")); + thd->wsrep_conflict_state = ABORTED; + mysql_mutex_unlock(&thd->LOCK_thd_data); + WSREP_DEBUG("innobase_commit abort after replaying wait %s", + (thd->query()) ? thd->query() : "void"); + DBUG_RETURN(WSREP_TRX_CERT_FAIL); + } + + thd->wsrep_query_state = QUERY_COMMITTING; + mysql_mutex_unlock(&thd->LOCK_thd_data); + + cache = get_trans_log(thd); + rcode = 0; + if (cache) { + thd->binlog_flush_pending_rows_event(true); + rcode = wsrep_write_cache(wsrep, thd, cache, &data_len); + if (WSREP_OK != rcode) { + WSREP_ERROR("rbr write fail, data_len: %zu, %d", data_len, rcode); + DBUG_RETURN(WSREP_TRX_SIZE_EXCEEDED); + } + } + + if (data_len == 0) + { + if (thd->get_stmt_da()->is_ok() && + thd->get_stmt_da()->affected_rows() > 0 && + !binlog_filter->is_on()) + { + WSREP_DEBUG("empty rbr buffer, query: %s, " + "affected rows: %llu, " + "changed tables: %d, " + "sql_log_bin: %d, " + "wsrep status (%d %d %d)", + thd->query(), thd->get_stmt_da()->affected_rows(), + stmt_has_updated_trans_table(thd), thd->variables.sql_log_bin, + thd->wsrep_exec_mode, thd->wsrep_query_state, + thd->wsrep_conflict_state); + } + else + { + WSREP_DEBUG("empty rbr buffer, query: %s", thd->query()); + } + thd->wsrep_query_state= QUERY_EXEC; + DBUG_RETURN(WSREP_TRX_OK); + } + + if (WSREP_UNDEFINED_TRX_ID == thd->wsrep_ws_handle.trx_id) + { + WSREP_WARN("SQL statement was ineffective, THD: %lu, buf: %zu\n" + "schema: %s \n" + "QUERY: %s\n" + " => Skipping replication", + thd->thread_id, data_len, + (thd->db ? thd->db : "(null)"), thd->query()); + rcode = WSREP_TRX_FAIL; + } + else if (!rcode) + { + if (WSREP_OK == rcode && wsrep) + rcode = wsrep->pre_commit(wsrep, + (wsrep_conn_id_t)thd->thread_id, + &thd->wsrep_ws_handle, + WSREP_FLAG_COMMIT | + ((thd->wsrep_PA_safe) ? + 0ULL : WSREP_FLAG_PA_UNSAFE), + &thd->wsrep_trx_meta); + + if (rcode == WSREP_TRX_MISSING) { + WSREP_WARN("Transaction missing in provider, thd: %ld, schema: %s, SQL: %s", + thd->thread_id, (thd->db ? thd->db : "(null)"), thd->query()); + rcode = WSREP_TRX_FAIL; + } else if (rcode == WSREP_BF_ABORT) { + WSREP_DEBUG("thd %lu seqno %lld BF aborted by provider, will replay", + thd->thread_id, (long long)thd->wsrep_trx_meta.gtid.seqno); + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->wsrep_conflict_state = MUST_REPLAY; + DBUG_ASSERT(wsrep_thd_trx_seqno(thd) > 0); + mysql_mutex_unlock(&thd->LOCK_thd_data); + mysql_mutex_lock(&LOCK_wsrep_replaying); + wsrep_replaying++; + WSREP_DEBUG("replaying increased: %d, thd: %lu", + wsrep_replaying, thd->thread_id); + mysql_mutex_unlock(&LOCK_wsrep_replaying); + } + } else { + WSREP_ERROR("I/O error reading from thd's binlog iocache: " + "errno=%d, io cache code=%d", my_errno, cache->error); + DBUG_ASSERT(0); // failure like this can not normally happen + DBUG_RETURN(WSREP_TRX_ERROR); + } + + mysql_mutex_lock(&thd->LOCK_thd_data); + + DEBUG_SYNC(thd, "wsrep_after_replication"); + + switch(rcode) { + case 0: + /* + About MUST_ABORT: We assume that even if thd conflict state was set + to MUST_ABORT, underlying transaction was not rolled back or marked + as deadlock victim in QUERY_COMMITTING state. Conflict state is + set to NO_CONFLICT and commit proceeds as usual. + */ + if (thd->wsrep_conflict_state == MUST_ABORT) + thd->wsrep_conflict_state= NO_CONFLICT; + + if (thd->wsrep_conflict_state != NO_CONFLICT) + { + WSREP_WARN("thd %lu seqno %lld: conflict state %d after post commit", + thd->thread_id, + (long long)thd->wsrep_trx_meta.gtid.seqno, + thd->wsrep_conflict_state); + } + thd->wsrep_exec_mode= LOCAL_COMMIT; + DBUG_ASSERT(thd->wsrep_trx_meta.gtid.seqno != WSREP_SEQNO_UNDEFINED); + /* Override XID iff it was generated by mysql */ + if (thd->transaction.xid_state.xid.get_my_xid()) + { + wsrep_xid_init(&thd->transaction.xid_state.xid, + thd->wsrep_trx_meta.gtid.uuid, + thd->wsrep_trx_meta.gtid.seqno); + } + DBUG_PRINT("wsrep", ("replicating commit success")); + break; + case WSREP_BF_ABORT: + DBUG_ASSERT(thd->wsrep_trx_meta.gtid.seqno != WSREP_SEQNO_UNDEFINED); + /* fall through */ + case WSREP_TRX_FAIL: + WSREP_DEBUG("commit failed for reason: %d", rcode); + DBUG_PRINT("wsrep", ("replicating commit fail")); + + thd->wsrep_query_state= QUERY_EXEC; + + if (thd->wsrep_conflict_state == MUST_ABORT) { + thd->wsrep_conflict_state= ABORTED; + } + else + { + WSREP_DEBUG("conflict state: %d", thd->wsrep_conflict_state); + if (thd->wsrep_conflict_state == NO_CONFLICT) + { + thd->wsrep_conflict_state = CERT_FAILURE; + WSREP_LOG_CONFLICT(NULL, thd, FALSE); + } + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + + DBUG_RETURN(WSREP_TRX_CERT_FAIL); + + case WSREP_SIZE_EXCEEDED: + WSREP_ERROR("transaction size exceeded"); + mysql_mutex_unlock(&thd->LOCK_thd_data); + DBUG_RETURN(WSREP_TRX_SIZE_EXCEEDED); + case WSREP_CONN_FAIL: + WSREP_ERROR("connection failure"); + mysql_mutex_unlock(&thd->LOCK_thd_data); + DBUG_RETURN(WSREP_TRX_ERROR); + default: + WSREP_ERROR("unknown connection failure"); + mysql_mutex_unlock(&thd->LOCK_thd_data); + DBUG_RETURN(WSREP_TRX_ERROR); + } + + thd->wsrep_query_state= QUERY_EXEC; + mysql_mutex_unlock(&thd->LOCK_thd_data); + + DBUG_RETURN(WSREP_TRX_OK); +} + + +static int wsrep_hton_init(void *p) +{ + wsrep_hton= (handlerton *)p; + //wsrep_hton->state=opt_bin_log ? SHOW_OPTION_YES : SHOW_OPTION_NO; + wsrep_hton->state= SHOW_OPTION_YES; + wsrep_hton->db_type=(legacy_db_type)0; + wsrep_hton->savepoint_offset= sizeof(my_off_t); + wsrep_hton->close_connection= wsrep_close_connection; + wsrep_hton->savepoint_set= wsrep_savepoint_set; + wsrep_hton->savepoint_rollback= wsrep_savepoint_rollback; + wsrep_hton->commit= wsrep_commit; + wsrep_hton->rollback= wsrep_rollback; + wsrep_hton->prepare= wsrep_prepare; + wsrep_hton->flags= HTON_NOT_USER_SELECTABLE | HTON_HIDDEN; // todo: fix flags + return 0; +} + + +struct st_mysql_storage_engine wsrep_storage_engine= +{ MYSQL_HANDLERTON_INTERFACE_VERSION }; + + +maria_declare_plugin(wsrep) +{ + MYSQL_STORAGE_ENGINE_PLUGIN, + &wsrep_storage_engine, + "wsrep", + "Codership Oy", + "A pseudo storage engine to represent transactions in multi-master " + "synchornous replication", + PLUGIN_LICENSE_GPL, + wsrep_hton_init, /* Plugin Init */ + NULL, /* Plugin Deinit */ + 0x0100 /* 1.0 */, + NULL, /* status variables */ + NULL, /* system variables */ + "1.0", /* string version */ + MariaDB_PLUGIN_MATURITY_STABLE /* maturity */ +} +maria_declare_plugin_end; diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc new file mode 100644 index 00000000000..ee8509e3fa2 --- /dev/null +++ b/sql/wsrep_mysqld.cc @@ -0,0 +1,2878 @@ +/* Copyright 2008-2015 Codership Oy <http://www.codership.com> + + 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.x1 + + 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 02111-1301 USA */ + +#include <sql_plugin.h> // SHOW_MY_BOOL +#include <mysqld.h> +#include <sql_class.h> +#include <sql_parse.h> +#include <sql_base.h> /* find_temporary_table() */ +#include "slave.h" +#include "rpl_mi.h" +#include "sql_repl.h" +#include "rpl_filter.h" +#include "sql_callback.h" +#include "sp_head.h" +#include "sql_show.h" +#include "sp.h" +#include "wsrep_priv.h" +#include "wsrep_thd.h" +#include "wsrep_sst.h" +#include "wsrep_utils.h" +#include "wsrep_var.h" +#include "wsrep_binlog.h" +#include "wsrep_applier.h" +#include "wsrep_xid.h" +#include <cstdio> +#include <cstdlib> +#include "log_event.h" +#include <slave.h> +#include "sql_plugin.h" /* wsrep_plugins_pre_init() */ + +wsrep_t *wsrep = NULL; +/* + wsrep_emulate_bin_log is a flag to tell that binlog has not been configured. + wsrep needs to get binlog events from transaction cache even when binlog is + not enabled, wsrep_emulate_bin_log opens needed code paths to make this + possible +*/ +my_bool wsrep_emulate_bin_log = FALSE; // activating parts of binlog interface +#ifdef GTID_SUPPORT +/* Sidno in global_sid_map corresponding to group uuid */ +rpl_sidno wsrep_sidno= -1; +#endif /* GTID_SUPPORT */ +my_bool wsrep_preordered_opt= FALSE; + +/* + * Begin configuration options and their default values + */ + +extern my_bool plugins_are_initialized; +extern uint kill_cached_threads; +extern mysql_cond_t COND_thread_cache; + +const char* wsrep_data_home_dir = NULL; +const char* wsrep_dbug_option = ""; + +long wsrep_slave_threads = 1; // # of slave action appliers wanted +int wsrep_slave_count_change = 0; // # of appliers to stop or start +my_bool wsrep_debug = 0; // enable debug level logging +my_bool wsrep_convert_LOCK_to_trx = 1; // convert locking sessions to trx +ulong wsrep_retry_autocommit = 5; // retry aborted autocommit trx +my_bool wsrep_auto_increment_control = 1; // control auto increment variables +my_bool wsrep_drupal_282555_workaround = 1; // retry autoinc insert after dupkey +my_bool wsrep_incremental_data_collection = 0; // incremental data collection +ulong wsrep_max_ws_size = 1073741824UL;//max ws (RBR buffer) size +ulong wsrep_max_ws_rows = 65536; // max number of rows in ws +int wsrep_to_isolation = 0; // # of active TO isolation threads +my_bool wsrep_certify_nonPK = 1; // certify, even when no primary key +ulong wsrep_certification_rules = WSREP_CERTIFICATION_RULES_STRICT; +long wsrep_max_protocol_version = 3; // maximum protocol version to use +ulong wsrep_forced_binlog_format = BINLOG_FORMAT_UNSPEC; +my_bool wsrep_recovery = 0; // recovery +my_bool wsrep_replicate_myisam = 0; // enable myisam replication +my_bool wsrep_log_conflicts = 0; +ulong wsrep_mysql_replication_bundle = 0; +my_bool wsrep_desync = 0; // desynchronize the node from the + // cluster +my_bool wsrep_load_data_splitting = 1; // commit load data every 10K intervals +my_bool wsrep_restart_slave = 0; // should mysql slave thread be + // restarted, if node joins back +my_bool wsrep_restart_slave_activated = 0; // node has dropped, and slave + // restart will be needed +my_bool wsrep_slave_UK_checks = 0; // slave thread does UK checks +my_bool wsrep_slave_FK_checks = 0; // slave thread does FK checks +bool wsrep_new_cluster = false; // Bootstrap the cluster ? + +// Use wsrep_gtid_domain_id for galera transactions? +bool wsrep_gtid_mode = 0; +// gtid_domain_id for galera transactions. +uint32 wsrep_gtid_domain_id = 0; +// Allow reads even if the node is not in the primary component. +bool wsrep_dirty_reads = false; + +/* + * End configuration options + */ + +/* + * Other wsrep global variables. + */ + +mysql_mutex_t LOCK_wsrep_ready; +mysql_cond_t COND_wsrep_ready; +mysql_mutex_t LOCK_wsrep_sst; +mysql_cond_t COND_wsrep_sst; +mysql_mutex_t LOCK_wsrep_sst_init; +mysql_cond_t COND_wsrep_sst_init; +mysql_mutex_t LOCK_wsrep_rollback; +mysql_cond_t COND_wsrep_rollback; +wsrep_aborting_thd_t wsrep_aborting_thd= NULL; +mysql_mutex_t LOCK_wsrep_replaying; +mysql_cond_t COND_wsrep_replaying; +mysql_mutex_t LOCK_wsrep_slave_threads; +mysql_mutex_t LOCK_wsrep_desync; +mysql_mutex_t LOCK_wsrep_config_state; + +int wsrep_replaying= 0; +ulong wsrep_running_threads = 0; // # of currently running wsrep threads +ulong my_bind_addr; + +#ifdef HAVE_PSI_INTERFACE +PSI_mutex_key key_LOCK_wsrep_rollback, + key_LOCK_wsrep_replaying, key_LOCK_wsrep_ready, key_LOCK_wsrep_sst, + key_LOCK_wsrep_sst_thread, key_LOCK_wsrep_sst_init, + key_LOCK_wsrep_slave_threads, key_LOCK_wsrep_desync, + key_LOCK_wsrep_config_state; + +PSI_cond_key key_COND_wsrep_rollback, + key_COND_wsrep_replaying, key_COND_wsrep_ready, key_COND_wsrep_sst, + key_COND_wsrep_sst_init, key_COND_wsrep_sst_thread; + +PSI_file_key key_file_wsrep_gra_log; + +static PSI_mutex_info wsrep_mutexes[]= +{ + { &key_LOCK_wsrep_ready, "LOCK_wsrep_ready", PSI_FLAG_GLOBAL}, + { &key_LOCK_wsrep_sst, "LOCK_wsrep_sst", PSI_FLAG_GLOBAL}, + { &key_LOCK_wsrep_sst_thread, "wsrep_sst_thread", 0}, + { &key_LOCK_wsrep_sst_init, "LOCK_wsrep_sst_init", PSI_FLAG_GLOBAL}, + { &key_LOCK_wsrep_sst, "LOCK_wsrep_sst", PSI_FLAG_GLOBAL}, + { &key_LOCK_wsrep_rollback, "LOCK_wsrep_rollback", PSI_FLAG_GLOBAL}, + { &key_LOCK_wsrep_replaying, "LOCK_wsrep_replaying", PSI_FLAG_GLOBAL}, + { &key_LOCK_wsrep_slave_threads, "LOCK_wsrep_slave_threads", PSI_FLAG_GLOBAL}, + { &key_LOCK_wsrep_desync, "LOCK_wsrep_desync", PSI_FLAG_GLOBAL}, + { &key_LOCK_wsrep_config_state, "LOCK_wsrep_config_state", PSI_FLAG_GLOBAL} +}; + +static PSI_cond_info wsrep_conds[]= +{ + { &key_COND_wsrep_ready, "COND_wsrep_ready", PSI_FLAG_GLOBAL}, + { &key_COND_wsrep_sst, "COND_wsrep_sst", PSI_FLAG_GLOBAL}, + { &key_COND_wsrep_sst_init, "COND_wsrep_sst_init", PSI_FLAG_GLOBAL}, + { &key_COND_wsrep_sst_thread, "wsrep_sst_thread", 0}, + { &key_COND_wsrep_rollback, "COND_wsrep_rollback", PSI_FLAG_GLOBAL}, + { &key_COND_wsrep_replaying, "COND_wsrep_replaying", PSI_FLAG_GLOBAL} +}; + +static PSI_file_info wsrep_files[]= +{ + { &key_file_wsrep_gra_log, "wsrep_gra_log", 0} +}; +#endif + +my_bool wsrep_inited = 0; // initialized ? + +static wsrep_uuid_t cluster_uuid = WSREP_UUID_UNDEFINED; +static char cluster_uuid_str[40]= { 0, }; +static const char* cluster_status_str[WSREP_VIEW_MAX] = +{ + "Primary", + "non-Primary", + "Disconnected" +}; + +static char provider_name[256]= { 0, }; +static char provider_version[256]= { 0, }; +static char provider_vendor[256]= { 0, }; + +/* + * wsrep status variables + */ +my_bool wsrep_connected = FALSE; +my_bool wsrep_ready = FALSE; // node can accept queries +const char* wsrep_cluster_state_uuid = cluster_uuid_str; +long long wsrep_cluster_conf_id = WSREP_SEQNO_UNDEFINED; +const char* wsrep_cluster_status = cluster_status_str[WSREP_VIEW_DISCONNECTED]; +long wsrep_cluster_size = 0; +long wsrep_local_index = -1; +long long wsrep_local_bf_aborts = 0; +const char* wsrep_provider_name = provider_name; +const char* wsrep_provider_version = provider_version; +const char* wsrep_provider_vendor = provider_vendor; +/* End wsrep status variables */ + +wsrep_uuid_t local_uuid = WSREP_UUID_UNDEFINED; +wsrep_seqno_t local_seqno = WSREP_SEQNO_UNDEFINED; +long wsrep_protocol_version = 3; + +wsp::Config_state wsrep_config_state; + +// Boolean denoting if server is in initial startup phase. This is needed +// to make sure that main thread waiting in wsrep_sst_wait() is signaled +// if there was no state gap on receiving first view event. +static my_bool wsrep_startup = TRUE; + + +static void wsrep_log_cb(wsrep_log_level_t level, const char *msg) { + switch (level) { + case WSREP_LOG_INFO: + sql_print_information("WSREP: %s", msg); + break; + case WSREP_LOG_WARN: + sql_print_warning("WSREP: %s", msg); + break; + case WSREP_LOG_ERROR: + case WSREP_LOG_FATAL: + sql_print_error("WSREP: %s", msg); + break; + case WSREP_LOG_DEBUG: + if (wsrep_debug) sql_print_information ("[Debug] WSREP: %s", msg); + default: + break; + } +} + +static void wsrep_log_states (wsrep_log_level_t const level, + const wsrep_uuid_t* const group_uuid, + wsrep_seqno_t const group_seqno, + const wsrep_uuid_t* const node_uuid, + wsrep_seqno_t const node_seqno) +{ + char uuid_str[37]; + char msg[256]; + + wsrep_uuid_print (group_uuid, uuid_str, sizeof(uuid_str)); + snprintf (msg, 255, "WSREP: Group state: %s:%lld", + uuid_str, (long long)group_seqno); + wsrep_log_cb (level, msg); + + wsrep_uuid_print (node_uuid, uuid_str, sizeof(uuid_str)); + snprintf (msg, 255, "WSREP: Local state: %s:%lld", + uuid_str, (long long)node_seqno); + wsrep_log_cb (level, msg); +} + +#ifdef GTID_SUPPORT +void wsrep_init_sidno(const wsrep_uuid_t& wsrep_uuid) +{ + /* generate new Sid map entry from inverted uuid */ + rpl_sid sid; + wsrep_uuid_t ltid_uuid; + + for (size_t i= 0; i < sizeof(ltid_uuid.data); ++i) + { + ltid_uuid.data[i] = ~wsrep_uuid.data[i]; + } + + sid.copy_from(ltid_uuid.data); + global_sid_lock->wrlock(); + wsrep_sidno= global_sid_map->add_sid(sid); + WSREP_INFO("Initialized wsrep sidno %d", wsrep_sidno); + global_sid_lock->unlock(); +} +#endif /* GTID_SUPPORT */ + +static wsrep_cb_status_t +wsrep_view_handler_cb (void* app_ctx, + void* recv_ctx, + const wsrep_view_info_t* view, + const char* state, + size_t state_len, + void** sst_req, + size_t* sst_req_len) +{ + *sst_req = NULL; + *sst_req_len = 0; + + wsrep_member_status_t memb_status= wsrep_config_state.get_status(); + + if (memcmp(&cluster_uuid, &view->state_id.uuid, sizeof(wsrep_uuid_t))) + { + memcpy(&cluster_uuid, &view->state_id.uuid, sizeof(cluster_uuid)); + + wsrep_uuid_print (&cluster_uuid, cluster_uuid_str, + sizeof(cluster_uuid_str)); + } + + wsrep_cluster_conf_id= view->view; + wsrep_cluster_status= cluster_status_str[view->status]; + wsrep_cluster_size= view->memb_num; + wsrep_local_index= view->my_idx; + + WSREP_INFO("New cluster view: global state: %s:%lld, view# %lld: %s, " + "number of nodes: %ld, my index: %ld, protocol version %d", + wsrep_cluster_state_uuid, (long long)view->state_id.seqno, + (long long)wsrep_cluster_conf_id, wsrep_cluster_status, + wsrep_cluster_size, wsrep_local_index, view->proto_ver); + + /* Proceed further only if view is PRIMARY */ + if (WSREP_VIEW_PRIMARY != view->status) + { +#ifdef HAVE_QUERY_CACHE + // query cache must be initialised by now + query_cache.flush(); +#endif /* HAVE_QUERY_CACHE */ + + wsrep_ready_set(FALSE); + memb_status= WSREP_MEMBER_UNDEFINED; + /* Always record local_uuid and local_seqno in non-prim since this + * may lead to re-initializing provider and start position is + * determined according to these variables */ + // WRONG! local_uuid should be the last primary configuration uuid we were + // a member of. local_seqno should be updated in commit calls. + // local_uuid= cluster_uuid; + // local_seqno= view->first - 1; + goto out; + } + + switch (view->proto_ver) + { + case 0: + case 1: + case 2: + case 3: + // version change + if (view->proto_ver != wsrep_protocol_version) + { + my_bool wsrep_ready_saved= wsrep_ready_get(); + wsrep_ready_set(FALSE); + WSREP_INFO("closing client connections for " + "protocol change %ld -> %d", + wsrep_protocol_version, view->proto_ver); + wsrep_close_client_connections(TRUE); + wsrep_protocol_version= view->proto_ver; + wsrep_ready_set(wsrep_ready_saved); + } + break; + default: + WSREP_ERROR("Unsupported application protocol version: %d", + view->proto_ver); + unireg_abort(1); + } + + if (view->state_gap) + { + WSREP_WARN("Gap in state sequence. Need state transfer."); + + /* After that wsrep will call wsrep_sst_prepare. */ + /* keep ready flag 0 until we receive the snapshot */ + wsrep_ready_set(FALSE); + + /* Close client connections to ensure that they don't interfere + * with SST. Necessary only if storage engines are initialized + * before SST. + * TODO: Just killing all ongoing transactions should be enough + * since wsrep_ready is OFF and no new transactions can start. + */ + if (!wsrep_before_SE()) + { + WSREP_DEBUG("[debug]: closing client connections for PRIM"); + wsrep_close_client_connections(FALSE); + } + + ssize_t const req_len= wsrep_sst_prepare (sst_req); + + if (req_len < 0) + { + WSREP_ERROR("SST preparation failed: %zd (%s)", -req_len, + strerror(-req_len)); + memb_status= WSREP_MEMBER_UNDEFINED; + } + else + { + assert(sst_req != NULL); + *sst_req_len= req_len; + memb_status= WSREP_MEMBER_JOINER; + } + } + else + { + /* + * NOTE: Initialize wsrep_group_uuid here only if it wasn't initialized + * before - OR - it was reinitilized on startup (lp:992840) + */ + if (wsrep_startup) + { + if (wsrep_before_SE()) + { + wsrep_SE_init_grab(); + // Signal mysqld init thread to continue + wsrep_sst_complete (&cluster_uuid, view->state_id.seqno, false); + // and wait for SE initialization + wsrep_SE_init_wait(); + } + else + { + local_uuid= cluster_uuid; + local_seqno= view->state_id.seqno; + } + /* Init storage engine XIDs from first view */ + wsrep_set_SE_checkpoint(local_uuid, local_seqno); +#ifdef GTID_SUPPORT + wsrep_init_sidno(local_uuid); +#endif /* GTID_SUPPORT */ + memb_status= WSREP_MEMBER_JOINED; + } + + // just some sanity check + if (memcmp (&local_uuid, &cluster_uuid, sizeof (wsrep_uuid_t))) + { + WSREP_ERROR("Undetected state gap. Can't continue."); + wsrep_log_states(WSREP_LOG_FATAL, &cluster_uuid, view->state_id.seqno, + &local_uuid, -1); + unireg_abort(1); + } + } + + if (wsrep_auto_increment_control) + { + global_system_variables.auto_increment_offset= view->my_idx + 1; + global_system_variables.auto_increment_increment= view->memb_num; + } + + { /* capabilities may be updated on new configuration */ + uint64_t const caps(wsrep->capabilities (wsrep)); + + my_bool const idc((caps & WSREP_CAP_INCREMENTAL_WRITESET) != 0); + if (TRUE == wsrep_incremental_data_collection && FALSE == idc) + { + WSREP_WARN("Unsupported protocol downgrade: " + "incremental data collection disabled. Expect abort."); + } + wsrep_incremental_data_collection = idc; + } + +out: + if (view->status == WSREP_VIEW_PRIMARY) wsrep_startup= FALSE; + wsrep_config_state.set(memb_status, view); + + return WSREP_CB_SUCCESS; +} + +my_bool wsrep_ready_set (my_bool x) +{ + WSREP_DEBUG("Setting wsrep_ready to %d", x); + if (mysql_mutex_lock (&LOCK_wsrep_ready)) abort(); + my_bool ret= (wsrep_ready != x); + if (ret) + { + wsrep_ready= x; + mysql_cond_signal (&COND_wsrep_ready); + } + mysql_mutex_unlock (&LOCK_wsrep_ready); + return ret; +} + +my_bool wsrep_ready_get (void) +{ + if (mysql_mutex_lock (&LOCK_wsrep_ready)) abort(); + my_bool ret= wsrep_ready; + mysql_mutex_unlock (&LOCK_wsrep_ready); + return ret; +} + +int wsrep_show_ready(THD *thd, SHOW_VAR *var, char *buff) +{ + var->type= SHOW_MY_BOOL; + var->value= buff; + *((my_bool *)buff)= wsrep_ready_get(); + return 0; +} + +// Wait until wsrep has reached ready state +void wsrep_ready_wait () +{ + if (mysql_mutex_lock (&LOCK_wsrep_ready)) abort(); + while (!wsrep_ready) + { + WSREP_INFO("Waiting to reach ready state"); + mysql_cond_wait (&COND_wsrep_ready, &LOCK_wsrep_ready); + } + WSREP_INFO("ready state reached"); + mysql_mutex_unlock (&LOCK_wsrep_ready); +} + +static void wsrep_synced_cb(void* app_ctx) +{ + WSREP_INFO("Synchronized with group, ready for connections"); + my_bool signal_main= wsrep_ready_set(TRUE); + wsrep_config_state.set(WSREP_MEMBER_SYNCED); + + if (signal_main) + { + wsrep_SE_init_grab(); + // Signal mysqld init thread to continue + wsrep_sst_complete (&local_uuid, local_seqno, false); + // and wait for SE initialization + wsrep_SE_init_wait(); + } + if (wsrep_restart_slave_activated) + { + int rcode; + WSREP_INFO("MySQL slave restart"); + wsrep_restart_slave_activated= FALSE; + + mysql_mutex_lock(&LOCK_active_mi); + if ((rcode = start_slave_threads(0, + 1 /* need mutex */, + 0 /* no wait for start*/, + active_mi, + master_info_file, + relay_log_info_file, + SLAVE_SQL))) + { + WSREP_WARN("Failed to create slave threads: %d", rcode); + } + mysql_mutex_unlock(&LOCK_active_mi); + + } +} + +static void wsrep_init_position() +{ + /* read XIDs from storage engines */ + wsrep_uuid_t uuid; + wsrep_seqno_t seqno; + wsrep_get_SE_checkpoint(uuid, seqno); + + if (!memcmp(&uuid, &WSREP_UUID_UNDEFINED, sizeof(wsrep_uuid_t))) + { + WSREP_INFO("Read nil XID from storage engines, skipping position init"); + return; + } + + char uuid_str[40] = {0, }; + wsrep_uuid_print(&uuid, uuid_str, sizeof(uuid_str)); + WSREP_INFO("Initial position: %s:%lld", uuid_str, (long long)seqno); + + if (!memcmp(&local_uuid, &WSREP_UUID_UNDEFINED, sizeof(local_uuid)) && + local_seqno == WSREP_SEQNO_UNDEFINED) + { + // Initial state + local_uuid= uuid; + local_seqno= seqno; + } + else if (memcmp(&local_uuid, &uuid, sizeof(local_uuid)) || + local_seqno != seqno) + { + WSREP_WARN("Initial position was provided by configuration or SST, " + "avoiding override"); + } +} + +extern char* my_bind_addr_str; + +int wsrep_init() +{ + int rcode= -1; + DBUG_ASSERT(wsrep_inited == 0); + + if (strcmp(wsrep_start_position, WSREP_START_POSITION_ZERO)) + wsrep_start_position_init(wsrep_start_position); + + wsrep_sst_auth_init(wsrep_sst_auth); + + wsrep_ready_set(FALSE); + assert(wsrep_provider); + + wsrep_init_position(); + + if ((rcode= wsrep_load(wsrep_provider, &wsrep, wsrep_log_cb)) != WSREP_OK) + { + if (strcasecmp(wsrep_provider, WSREP_NONE)) + { + WSREP_ERROR("wsrep_load(%s) failed: %s (%d). Reverting to no provider.", + wsrep_provider, strerror(rcode), rcode); + strcpy((char*)wsrep_provider, WSREP_NONE); // damn it's a dirty hack + return wsrep_init(); + } + else /* this is for recursive call above */ + { + WSREP_ERROR("Could not revert to no provider: %s (%d). Need to abort.", + strerror(rcode), rcode); + unireg_abort(1); + } + } + + if (!WSREP_PROVIDER_EXISTS) + { + // enable normal operation in case no provider is specified + wsrep_ready_set(TRUE); + wsrep_inited= 1; + global_system_variables.wsrep_on = 0; + wsrep_init_args args; + args.logger_cb = wsrep_log_cb; + args.options = (wsrep_provider_options) ? + wsrep_provider_options : ""; + rcode = wsrep->init(wsrep, &args); + if (rcode) + { + DBUG_PRINT("wsrep",("wsrep::init() failed: %d", rcode)); + WSREP_ERROR("wsrep::init() failed: %d, must shutdown", rcode); + wsrep->free(wsrep); + free(wsrep); + wsrep = NULL; + } + return rcode; + } + else + { + global_system_variables.wsrep_on = 1; + strncpy(provider_name, + wsrep->provider_name, sizeof(provider_name) - 1); + strncpy(provider_version, + wsrep->provider_version, sizeof(provider_version) - 1); + strncpy(provider_vendor, + wsrep->provider_vendor, sizeof(provider_vendor) - 1); + } + + if (!wsrep_data_home_dir || strlen(wsrep_data_home_dir) == 0) + wsrep_data_home_dir = mysql_real_data_home; + + /* Initialize node address */ + char node_addr[512]= { 0, }; + size_t const node_addr_max= sizeof(node_addr) - 1; + if (!wsrep_node_address || !strcmp(wsrep_node_address, "")) + { + size_t const ret= wsrep_guess_ip(node_addr, node_addr_max); + if (!(ret > 0 && ret < node_addr_max)) + { + WSREP_WARN("Failed to guess base node address. Set it explicitly via " + "wsrep_node_address."); + node_addr[0]= '\0'; + } + } + else + { + strncpy(node_addr, wsrep_node_address, node_addr_max); + } + + /* Initialize node's incoming address */ + char inc_addr[512]= { 0, }; + size_t const inc_addr_max= sizeof (inc_addr); + + /* + In case wsrep_node_incoming_address is either not set or set to AUTO, + we need to use mysqld's my_bind_addr_str:mysqld_port, lastly fallback + to wsrep_node_address' value if mysqld's bind-address is not set either. + */ + if ((!wsrep_node_incoming_address || + !strcmp (wsrep_node_incoming_address, WSREP_NODE_INCOMING_AUTO))) + { + bool is_ipv6= false; + unsigned int my_bind_ip= INADDR_ANY; // default if not set + + if (my_bind_addr_str && strlen(my_bind_addr_str)) + { + my_bind_ip= wsrep_check_ip(my_bind_addr_str, &is_ipv6); + } + + if (INADDR_ANY != my_bind_ip) + { + /* + If its a not a valid address, leave inc_addr as empty string. mysqld + is not listening for client connections on network interfaces. + */ + if (INADDR_NONE != my_bind_ip && INADDR_LOOPBACK != my_bind_ip) + { + const char *fmt= (is_ipv6) ? "[%s]:%u" : "%s:%u"; + snprintf(inc_addr, inc_addr_max, fmt, my_bind_addr_str, mysqld_port); + } + } + else /* mysqld binds to 0.0.0.0, try taking IP from wsrep_node_address. */ + { + size_t const node_addr_len= strlen(node_addr); + if (node_addr_len > 0) + { + wsp::Address addr(node_addr); + + if (!addr.is_valid()) + { + WSREP_DEBUG("Could not parse node address : %s", node_addr); + WSREP_WARN("Guessing address for incoming client connections failed. " + "Try setting wsrep_node_incoming_address explicitly."); + goto done; + } + + const char *fmt= (addr.is_ipv6()) ? "[%s]:%u" : "%s:%u"; + snprintf(inc_addr, inc_addr_max, fmt, addr.get_address(), + (int) mysqld_port); + } + } + } + else + { + wsp::Address addr(wsrep_node_incoming_address); + + if (!addr.is_valid()) + { + WSREP_WARN("Could not parse wsrep_node_incoming_address : %s", + wsrep_node_incoming_address); + goto done; + } + + /* + In case port is not specified in wsrep_node_incoming_address, we use + mysqld_port. + */ + int port= (addr.get_port() > 0) ? addr.get_port() : (int) mysqld_port; + const char *fmt= (addr.is_ipv6()) ? "[%s]:%u" : "%s:%u"; + + snprintf(inc_addr, inc_addr_max, fmt, addr.get_address(), port); + } + +done: + struct wsrep_init_args wsrep_args; + + struct wsrep_gtid const state_id = { local_uuid, local_seqno }; + + wsrep_args.data_dir = wsrep_data_home_dir; + wsrep_args.node_name = (wsrep_node_name) ? wsrep_node_name : ""; + wsrep_args.node_address = node_addr; + wsrep_args.node_incoming = inc_addr; + wsrep_args.options = (wsrep_provider_options) ? + wsrep_provider_options : ""; + wsrep_args.proto_ver = wsrep_max_protocol_version; + + wsrep_args.state_id = &state_id; + + wsrep_args.logger_cb = wsrep_log_cb; + wsrep_args.view_handler_cb = wsrep_view_handler_cb; + wsrep_args.apply_cb = wsrep_apply_cb; + wsrep_args.commit_cb = wsrep_commit_cb; + wsrep_args.unordered_cb = wsrep_unordered_cb; + wsrep_args.sst_donate_cb = wsrep_sst_donate_cb; + wsrep_args.synced_cb = wsrep_synced_cb; + + rcode = wsrep->init(wsrep, &wsrep_args); + + if (rcode) + { + DBUG_PRINT("wsrep",("wsrep::init() failed: %d", rcode)); + WSREP_ERROR("wsrep::init() failed: %d, must shutdown", rcode); + wsrep->free(wsrep); + free(wsrep); + wsrep = NULL; + } else { + wsrep_inited= 1; + } + + return rcode; +} + + +/* Initialize wsrep thread LOCKs and CONDs */ +void wsrep_thr_init() +{ +#ifdef HAVE_PSI_INTERFACE + mysql_mutex_register("sql", wsrep_mutexes, array_elements(wsrep_mutexes)); + mysql_cond_register("sql", wsrep_conds, array_elements(wsrep_conds)); + mysql_file_register("sql", wsrep_files, array_elements(wsrep_files)); +#endif + + mysql_mutex_init(key_LOCK_wsrep_ready, &LOCK_wsrep_ready, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_wsrep_ready, &COND_wsrep_ready, NULL); + mysql_mutex_init(key_LOCK_wsrep_sst, &LOCK_wsrep_sst, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_wsrep_sst, &COND_wsrep_sst, NULL); + mysql_mutex_init(key_LOCK_wsrep_sst_init, &LOCK_wsrep_sst_init, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_wsrep_sst_init, &COND_wsrep_sst_init, NULL); + mysql_mutex_init(key_LOCK_wsrep_rollback, &LOCK_wsrep_rollback, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_wsrep_rollback, &COND_wsrep_rollback, NULL); + mysql_mutex_init(key_LOCK_wsrep_replaying, &LOCK_wsrep_replaying, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_wsrep_replaying, &COND_wsrep_replaying, NULL); + mysql_mutex_init(key_LOCK_wsrep_slave_threads, &LOCK_wsrep_slave_threads, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_wsrep_desync, &LOCK_wsrep_desync, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_LOCK_wsrep_config_state, &LOCK_wsrep_config_state, MY_MUTEX_INIT_FAST); +} + +void wsrep_init_startup (bool first) +{ + if (wsrep_init()) unireg_abort(1); + + wsrep_thr_lock_init( + (wsrep_thd_is_brute_force_fun)wsrep_thd_is_BF, + (wsrep_abort_thd_fun)wsrep_abort_thd, + wsrep_debug, wsrep_convert_LOCK_to_trx, + (wsrep_on_fun)wsrep_on); + + /* + Pre-initialize global_system_variables.table_plugin with a dummy engine + (placeholder) required during the initialization of wsrep threads (THDs). + (see: plugin_thdvar_init()) + Note: This only needs to be done for rsync & xtrabackup based SST methods. + In case of mysqldump SST method, the wsrep threads are created after the + server plugins & global system variables are initialized. + */ + if (wsrep_before_SE()) + wsrep_plugins_pre_init(); + + /* Skip replication start if dummy wsrep provider is loaded */ + if (!strcmp(wsrep_provider, WSREP_NONE)) return; + + /* Skip replication start if no cluster address */ + if (!wsrep_cluster_address || strlen(wsrep_cluster_address) == 0) return; + + if (first) wsrep_sst_grab(); // do it so we can wait for SST below + + if (!wsrep_start_replication()) unireg_abort(1); + + wsrep_create_rollbacker(); + wsrep_create_appliers(1); + + if (first && !wsrep_sst_wait()) unireg_abort(1);// wait until SST is completed +} + + +void wsrep_deinit(bool free_options) +{ + DBUG_ASSERT(wsrep_inited == 1); + wsrep_unload(wsrep); + wsrep= 0; + provider_name[0]= '\0'; + provider_version[0]= '\0'; + provider_vendor[0]= '\0'; + + wsrep_inited= 0; + + if (free_options) + { + wsrep_sst_auth_free(); + } +} + +/* Destroy wsrep thread LOCKs and CONDs */ +void wsrep_thr_deinit() +{ + mysql_mutex_destroy(&LOCK_wsrep_ready); + mysql_cond_destroy(&COND_wsrep_ready); + mysql_mutex_destroy(&LOCK_wsrep_sst); + mysql_cond_destroy(&COND_wsrep_sst); + mysql_mutex_destroy(&LOCK_wsrep_sst_init); + mysql_cond_destroy(&COND_wsrep_sst_init); + mysql_mutex_destroy(&LOCK_wsrep_rollback); + mysql_cond_destroy(&COND_wsrep_rollback); + mysql_mutex_destroy(&LOCK_wsrep_replaying); + mysql_cond_destroy(&COND_wsrep_replaying); + mysql_mutex_destroy(&LOCK_wsrep_slave_threads); + mysql_mutex_destroy(&LOCK_wsrep_desync); + mysql_mutex_destroy(&LOCK_wsrep_config_state); +} + +void wsrep_recover() +{ + char uuid_str[40]; + + if (!memcmp(&local_uuid, &WSREP_UUID_UNDEFINED, sizeof(wsrep_uuid_t)) && + local_seqno == -2) + { + wsrep_uuid_print(&local_uuid, uuid_str, sizeof(uuid_str)); + WSREP_INFO("Position %s:%lld given at startup, skipping position recovery", + uuid_str, (long long)local_seqno); + return; + } + wsrep_uuid_t uuid; + wsrep_seqno_t seqno; + wsrep_get_SE_checkpoint(uuid, seqno); + wsrep_uuid_print(&uuid, uuid_str, sizeof(uuid_str)); + WSREP_INFO("Recovered position: %s:%lld", uuid_str, (long long)seqno); +} + + +void wsrep_stop_replication(THD *thd) +{ + WSREP_INFO("Stop replication"); + if (!wsrep) + { + WSREP_INFO("Provider was not loaded, in stop replication"); + return; + } + + /* disconnect from group first to get wsrep_ready == FALSE */ + WSREP_DEBUG("Provider disconnect"); + wsrep->disconnect(wsrep); + + wsrep_connected= FALSE; + + wsrep_close_client_connections(TRUE); + + /* wait until appliers have stopped */ + wsrep_wait_appliers_close(thd); + + return; +} + +bool wsrep_start_replication() +{ + wsrep_status_t rcode; + + /* wsrep provider must be loaded. */ + DBUG_ASSERT(wsrep); + + /* + if provider is trivial, don't even try to connect, + but resume local node operation + */ + if (!WSREP_PROVIDER_EXISTS) + { + // enable normal operation in case no provider is specified + wsrep_ready_set(TRUE); + return true; + } + + if (!wsrep_cluster_address || strlen(wsrep_cluster_address)== 0) + { + // if provider is non-trivial, but no address is specified, wait for address + wsrep_ready_set(FALSE); + return true; + } + + bool const bootstrap= wsrep_new_cluster; + + WSREP_INFO("Start replication"); + + if (wsrep_new_cluster) + { + WSREP_INFO("'wsrep-new-cluster' option used, bootstrapping the cluster"); + wsrep_new_cluster= false; + } + + if ((rcode = wsrep->connect(wsrep, + wsrep_cluster_name, + wsrep_cluster_address, + wsrep_sst_donor, + bootstrap))) + { + DBUG_PRINT("wsrep",("wsrep->connect(%s) failed: %d", + wsrep_cluster_address, rcode)); + WSREP_ERROR("wsrep::connect(%s) failed: %d", + wsrep_cluster_address, rcode); + return false; + } + else + { + wsrep_connected= TRUE; + + char* opts= wsrep->options_get(wsrep); + if (opts) + { + wsrep_provider_options_init(opts); + free(opts); + } + else + { + WSREP_WARN("Failed to get wsrep options"); + } + } + + return true; +} + +bool wsrep_must_sync_wait (THD* thd, uint mask) +{ + return (thd->variables.wsrep_sync_wait & mask) && + thd->variables.wsrep_on && + !(thd->variables.wsrep_dirty_reads && + !is_update_query(thd->lex->sql_command)) && + !thd->in_active_multi_stmt_transaction() && + thd->wsrep_conflict_state != REPLAYING && + thd->wsrep_sync_wait_gtid.seqno == WSREP_SEQNO_UNDEFINED; +} + +bool wsrep_sync_wait (THD* thd, uint mask) +{ + if (wsrep_must_sync_wait(thd, mask)) + { + WSREP_DEBUG("wsrep_sync_wait: thd->variables.wsrep_sync_wait = %u, mask = %u", + thd->variables.wsrep_sync_wait, mask); + // This allows autocommit SELECTs and a first SELECT after SET AUTOCOMMIT=0 + // TODO: modify to check if thd has locked any rows. + wsrep_status_t ret= wsrep->causal_read (wsrep, &thd->wsrep_sync_wait_gtid); + + if (unlikely(WSREP_OK != ret)) + { + const char* msg; + int err; + + // Possibly relevant error codes: + // ER_CHECKREAD, ER_ERROR_ON_READ, ER_INVALID_DEFAULT, ER_EMPTY_QUERY, + // ER_FUNCTION_NOT_DEFINED, ER_NOT_ALLOWED_COMMAND, ER_NOT_SUPPORTED_YET, + // ER_FEATURE_DISABLED, ER_QUERY_INTERRUPTED + + switch (ret) + { + case WSREP_NOT_IMPLEMENTED: + msg= "synchronous reads by wsrep backend. " + "Please unset wsrep_causal_reads variable."; + err= ER_NOT_SUPPORTED_YET; + break; + default: + msg= "Synchronous wait failed."; + err= ER_LOCK_WAIT_TIMEOUT; // NOTE: the above msg won't be displayed + // with ER_LOCK_WAIT_TIMEOUT + } + + my_error(err, MYF(0), msg); + + return true; + } + } + + return false; +} + +void wsrep_keys_free(wsrep_key_arr_t* key_arr) +{ + for (size_t i= 0; i < key_arr->keys_len; ++i) + { + my_free((void*)key_arr->keys[i].key_parts); + } + my_free(key_arr->keys); + key_arr->keys= 0; + key_arr->keys_len= 0; +} + + +/*! + * @param db Database string + * @param table Table string + * @param key Array of wsrep_key_t + * @param key_len In: number of elements in key array, Out: number of + * elements populated + * + * @return true if preparation was successful, otherwise false. + */ + +static bool wsrep_prepare_key_for_isolation(const char* db, + const char* table, + wsrep_buf_t* key, + size_t* key_len) +{ + if (*key_len < 2) return false; + + switch (wsrep_protocol_version) + { + case 0: + *key_len= 0; + break; + case 1: + case 2: + case 3: + { + *key_len= 0; + if (db) + { + // sql_print_information("%s.%s", db, table); + key[*key_len].ptr= db; + key[*key_len].len= strlen(db); + ++(*key_len); + if (table) + { + key[*key_len].ptr= table; + key[*key_len].len= strlen(table); + ++(*key_len); + } + } + break; + } + default: + return false; + } + return true; +} + + +static bool wsrep_prepare_key_for_isolation(const char* db, + const char* table, + wsrep_key_arr_t* ka) +{ + wsrep_key_t* tmp; + + if (!ka->keys) + tmp= (wsrep_key_t*)my_malloc((ka->keys_len + 1) * sizeof(wsrep_key_t), + MYF(0)); + else + tmp= (wsrep_key_t*)my_realloc(ka->keys, + (ka->keys_len + 1) * sizeof(wsrep_key_t), + MYF(0)); + + if (!tmp) + { + WSREP_ERROR("Can't allocate memory for key_array"); + return false; + } + ka->keys= tmp; + if (!(ka->keys[ka->keys_len].key_parts= (wsrep_buf_t*) + my_malloc(sizeof(wsrep_buf_t)*2, MYF(0)))) + { + WSREP_ERROR("Can't allocate memory for key_parts"); + return false; + } + ka->keys[ka->keys_len].key_parts_num= 2; + ++ka->keys_len; + if (!wsrep_prepare_key_for_isolation(db, table, + (wsrep_buf_t*)ka->keys[ka->keys_len - 1].key_parts, + &ka->keys[ka->keys_len - 1].key_parts_num)) + { + WSREP_ERROR("Preparing keys for isolation failed"); + return false; + } + + return true; +} + + +static bool wsrep_prepare_keys_for_alter_add_fk(char* child_table_db, + Alter_info* alter_info, + wsrep_key_arr_t* ka) +{ + Key *key; + List_iterator<Key> key_iterator(alter_info->key_list); + while ((key= key_iterator++)) + { + if (key->type == Key::FOREIGN_KEY) + { + Foreign_key *fk_key= (Foreign_key *)key; + const char *db_name= fk_key->ref_db.str; + const char *table_name= fk_key->ref_table.str; + if (!db_name) + { + db_name= child_table_db; + } + if (!wsrep_prepare_key_for_isolation(db_name, table_name, ka)) + { + return false; + } + } + } + return true; +} + + +static bool wsrep_prepare_keys_for_isolation(THD* thd, + const char* db, + const char* table, + const TABLE_LIST* table_list, + Alter_info* alter_info, + wsrep_key_arr_t* ka) +{ + ka->keys= 0; + ka->keys_len= 0; + + if (db || table) + { + if (!wsrep_prepare_key_for_isolation(db, table, ka)) + goto err; + } + + for (const TABLE_LIST* table= table_list; table; table= table->next_global) + { + if (!wsrep_prepare_key_for_isolation(table->db, table->table_name, ka)) + goto err; + } + + if (alter_info && (alter_info->flags & (Alter_info::ADD_FOREIGN_KEY))) + { + if (!wsrep_prepare_keys_for_alter_add_fk(table_list->db, alter_info, ka)) + goto err; + } + + return false; + +err: + wsrep_keys_free(ka); + return true; +} + + +/* Prepare key list from db/table and table_list */ +bool wsrep_prepare_keys_for_isolation(THD* thd, + const char* db, + const char* table, + const TABLE_LIST* table_list, + wsrep_key_arr_t* ka) +{ + return wsrep_prepare_keys_for_isolation(thd, db, table, table_list, NULL, ka); +} + + +bool wsrep_prepare_key(const uchar* cache_key, size_t cache_key_len, + const uchar* row_id, size_t row_id_len, + wsrep_buf_t* key, size_t* key_len) +{ + if (*key_len < 3) return false; + + *key_len= 0; + switch (wsrep_protocol_version) + { + case 0: + { + key[0].ptr = cache_key; + key[0].len = cache_key_len; + + *key_len = 1; + break; + } + case 1: + case 2: + case 3: + { + key[0].ptr = cache_key; + key[0].len = strlen( (char*)cache_key ); + + key[1].ptr = cache_key + strlen( (char*)cache_key ) + 1; + key[1].len = strlen( (char*)(key[1].ptr) ); + + *key_len = 2; + break; + } + default: + return false; + } + + key[*key_len].ptr = row_id; + key[*key_len].len = row_id_len; + ++(*key_len); + + return true; +} + + +/* + * Construct Query_log_Event from thd query and serialize it + * into buffer. + * + * Return 0 in case of success, 1 in case of error. + */ +int wsrep_to_buf_helper( + THD* thd, const char *query, uint query_len, uchar** buf, size_t* buf_len) +{ + IO_CACHE tmp_io_cache; + Log_event_writer writer(&tmp_io_cache); + if (open_cached_file(&tmp_io_cache, mysql_tmpdir, TEMP_PREFIX, + 65536, MYF(MY_WME))) + return 1; + int ret(0); + + Format_description_log_event *tmp_fd= new Format_description_log_event(4); + tmp_fd->checksum_alg= (enum_binlog_checksum_alg)binlog_checksum_options; + writer.write(tmp_fd); + delete tmp_fd; + +#ifdef GTID_SUPPORT + if (thd->variables.gtid_next.type == GTID_GROUP) + { + Gtid_log_event gtid_ev(thd, FALSE, &thd->variables.gtid_next); + if (!gtid_ev.is_valid()) ret= 0; + if (!ret && writer.write(>id_ev)) ret= 1; + } +#endif /* GTID_SUPPORT */ + if (wsrep_gtid_mode && thd->variables.gtid_seq_no) + { + Gtid_log_event gtid_event(thd, thd->variables.gtid_seq_no, + thd->variables.gtid_domain_id, + true, LOG_EVENT_SUPPRESS_USE_F, + true, 0); + gtid_event.server_id= thd->variables.server_id; + if (!gtid_event.is_valid()) ret= 0; + ret= writer.write(>id_event); + } + + /* if there is prepare query, add event for it */ + if (!ret && thd->wsrep_TOI_pre_query) + { + Query_log_event ev(thd, thd->wsrep_TOI_pre_query, + thd->wsrep_TOI_pre_query_len, + FALSE, FALSE, FALSE, 0); + if (writer.write(&ev)) ret= 1; + } + + /* continue to append the actual query */ + Query_log_event ev(thd, query, query_len, FALSE, FALSE, FALSE, 0); + if (!ret && writer.write(&ev)) ret= 1; + if (!ret && wsrep_write_cache_buf(&tmp_io_cache, buf, buf_len)) ret= 1; + close_cached_file(&tmp_io_cache); + return ret; +} + +static int +wsrep_alter_query_string(THD *thd, String *buf) +{ + /* Append the "ALTER" part of the query */ + if (buf->append(STRING_WITH_LEN("ALTER "))) + return 1; + /* Append definer */ + append_definer(thd, buf, &(thd->lex->definer->user), &(thd->lex->definer->host)); + /* Append the left part of thd->query after event name part */ + if (buf->append(thd->lex->stmt_definition_begin, + thd->lex->stmt_definition_end - + thd->lex->stmt_definition_begin)) + return 1; + + return 0; +} + +static int wsrep_alter_event_query(THD *thd, uchar** buf, size_t* buf_len) +{ + String log_query; + + if (wsrep_alter_query_string(thd, &log_query)) + { + WSREP_WARN("events alter string failed: schema: %s, query: %s", + (thd->db ? thd->db : "(null)"), thd->query()); + return 1; + } + return wsrep_to_buf_helper(thd, log_query.ptr(), log_query.length(), buf, buf_len); +} + +#include "sql_show.h" +static int +create_view_query(THD *thd, uchar** buf, size_t* buf_len) +{ + LEX *lex= thd->lex; + SELECT_LEX *select_lex= &lex->select_lex; + TABLE_LIST *first_table= select_lex->table_list.first; + TABLE_LIST *views = first_table; + + String buff; + const LEX_STRING command[3]= + {{ C_STRING_WITH_LEN("CREATE ") }, + { C_STRING_WITH_LEN("ALTER ") }, + { C_STRING_WITH_LEN("CREATE OR REPLACE ") }}; + + buff.append(command[thd->lex->create_view_mode].str, + command[thd->lex->create_view_mode].length); + + LEX_USER *definer; + + if (lex->definer) + { + definer= get_current_user(thd, lex->definer); + } + else + { + /* + DEFINER-clause is missing; we have to create default definer in + persistent arena to be PS/SP friendly. + If this is an ALTER VIEW then the current user should be set as + the definer. + */ + definer= create_default_definer(thd, false); + } + + if (definer) + { + views->definer.user = definer->user; + views->definer.host = definer->host; + } else { + WSREP_ERROR("Failed to get DEFINER for VIEW."); + return 1; + } + + views->algorithm = lex->create_view_algorithm; + views->view_suid = lex->create_view_suid; + views->with_check = lex->create_view_check; + + view_store_options(thd, views, &buff); + buff.append(STRING_WITH_LEN("VIEW ")); + /* Test if user supplied a db (ie: we did not use thd->db) */ + if (views->db && views->db[0] && + (thd->db == NULL || strcmp(views->db, thd->db))) + { + append_identifier(thd, &buff, views->db, + views->db_length); + buff.append('.'); + } + append_identifier(thd, &buff, views->table_name, + views->table_name_length); + if (lex->view_list.elements) + { + List_iterator_fast<LEX_STRING> names(lex->view_list); + LEX_STRING *name; + int i; + + for (i= 0; (name= names++); i++) + { + buff.append(i ? ", " : "("); + append_identifier(thd, &buff, name->str, name->length); + } + buff.append(')'); + } + buff.append(STRING_WITH_LEN(" AS ")); + //buff.append(views->source.str, views->source.length); + buff.append(thd->lex->create_view_select.str, + thd->lex->create_view_select.length); + //int errcode= query_error_code(thd, TRUE); + //if (thd->binlog_query(THD::STMT_QUERY_TYPE, + // buff.ptr(), buff.length(), FALSE, FALSE, FALSE, errcod + return wsrep_to_buf_helper(thd, buff.ptr(), buff.length(), buf, buf_len); +} + +/* + Rewrite DROP TABLE for TOI. Temporary tables are eliminated from + the query as they are visible only to client connection. + + TODO: See comments for sql_base.cc:drop_temporary_table() and refine + the function to deal with transactional locked tables. + */ +static int wsrep_drop_table_query(THD* thd, uchar** buf, size_t* buf_len) +{ + + LEX* lex= thd->lex; + SELECT_LEX* select_lex= &lex->select_lex; + TABLE_LIST* first_table= select_lex->table_list.first; + String buff; + + DBUG_ASSERT(!lex->create_info.tmp_table()); + + bool found_temp_table= false; + for (TABLE_LIST* table= first_table; table; table= table->next_global) + { + if (find_temporary_table(thd, table->db, table->table_name)) + { + found_temp_table= true; + break; + } + } + + if (found_temp_table) + { + buff.append("DROP TABLE "); + if (lex->check_exists) + buff.append("IF EXISTS "); + + for (TABLE_LIST* table= first_table; table; table= table->next_global) + { + if (!find_temporary_table(thd, table->db, table->table_name)) + { + append_identifier(thd, &buff, table->db, strlen(table->db)); + buff.append("."); + append_identifier(thd, &buff, table->table_name, + strlen(table->table_name)); + buff.append(","); + } + } + + /* Chop the last comma */ + buff.chop(); + buff.append(" /* generated by wsrep */"); + + WSREP_DEBUG("Rewrote '%s' as '%s'", thd->query(), buff.ptr()); + + return wsrep_to_buf_helper(thd, buff.ptr(), buff.length(), buf, buf_len); + } + else + { + return wsrep_to_buf_helper(thd, thd->query(), thd->query_length(), + buf, buf_len); + } +} + + +/* Forward declarations. */ +static int wsrep_create_sp(THD *thd, uchar** buf, size_t* buf_len); +static int wsrep_create_trigger_query(THD *thd, uchar** buf, size_t* buf_len); + +/* + Decide if statement should run in TOI. + + Look if table or table_list contain temporary tables. If the + statement affects only temporary tables, statement should not run + in TOI. If the table list contains mix of regular and temporary tables + (DROP TABLE, OPTIMIZE, ANALYZE), statement should be run in TOI but + should be rewritten at later time for replication to contain only + non-temporary tables. + */ +static bool wsrep_can_run_in_toi(THD *thd, const char *db, const char *table, + const TABLE_LIST *table_list) +{ + DBUG_ASSERT(!table || db); + DBUG_ASSERT(table_list || db); + + LEX* lex= thd->lex; + SELECT_LEX* select_lex= &lex->select_lex; + TABLE_LIST* first_table= select_lex->table_list.first; + + switch (lex->sql_command) + { + case SQLCOM_CREATE_TABLE: + DBUG_ASSERT(!table_list); + if (thd->lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) + { + return false; + } + return true; + + case SQLCOM_CREATE_VIEW: + + DBUG_ASSERT(!table_list); + DBUG_ASSERT(first_table); /* First table is view name */ + /* + If any of the remaining tables refer to temporary table error + is returned to client, so TOI can be skipped + */ + for (TABLE_LIST* it= first_table->next_global; it; it= it->next_global) + { + if (find_temporary_table(thd, it)) + { + return false; + } + } + return true; + + case SQLCOM_CREATE_TRIGGER: + + DBUG_ASSERT(!table_list); + DBUG_ASSERT(first_table); + + if (find_temporary_table(thd, first_table)) + { + return false; + } + return true; + + default: + if (table && !find_temporary_table(thd, db, table)) + { + return true; + } + + if (table_list) + { + for (TABLE_LIST* table= first_table; table; table= table->next_global) + { + if (!find_temporary_table(thd, table->db, table->table_name)) + { + return true; + } + } + } + return !(table || table_list); + } +} + +static const char* wsrep_get_query_or_msg(const THD* thd) +{ + switch(thd->lex->sql_command) + { + case SQLCOM_CREATE_USER: + return "CREATE USER"; + case SQLCOM_GRANT: + return "GRANT"; + case SQLCOM_REVOKE: + return "REVOKE"; + case SQLCOM_SET_OPTION: + if (thd->lex->definer) + return "SET PASSWORD"; + /* fallthrough */ + default: + return thd->query(); + } +} + +/* + returns: + 0: statement was replicated as TOI + 1: TOI replication was skipped + -1: TOI replication failed + */ +static int wsrep_TOI_begin(THD *thd, char *db_, char *table_, + const TABLE_LIST* table_list, + Alter_info* alter_info) +{ + wsrep_status_t ret(WSREP_WARNING); + uchar* buf(0); + size_t buf_len(0); + int buf_err; + int rc= 0; + + if (wsrep_can_run_in_toi(thd, db_, table_, table_list) == false) + { + WSREP_DEBUG("No TOI for %s", WSREP_QUERY(thd)); + return 1; + } + + WSREP_DEBUG("TO BEGIN: %lld, %d : %s", (long long)wsrep_thd_trx_seqno(thd), + thd->wsrep_exec_mode, wsrep_get_query_or_msg(thd)); + + switch (thd->lex->sql_command) + { + case SQLCOM_CREATE_VIEW: + buf_err= create_view_query(thd, &buf, &buf_len); + break; + case SQLCOM_CREATE_PROCEDURE: + case SQLCOM_CREATE_SPFUNCTION: + buf_err= wsrep_create_sp(thd, &buf, &buf_len); + break; + case SQLCOM_CREATE_TRIGGER: + buf_err= wsrep_create_trigger_query(thd, &buf, &buf_len); + break; + case SQLCOM_CREATE_EVENT: + buf_err= wsrep_create_event_query(thd, &buf, &buf_len); + break; + case SQLCOM_ALTER_EVENT: + buf_err= wsrep_alter_event_query(thd, &buf, &buf_len); + break; + case SQLCOM_DROP_TABLE: + buf_err= wsrep_drop_table_query(thd, &buf, &buf_len); + break; + case SQLCOM_CREATE_ROLE: + if (sp_process_definer(thd)) + { + WSREP_WARN("Failed to set CREATE ROLE definer for TOI."); + } + /* fallthrough */ + default: + buf_err= wsrep_to_buf_helper(thd, thd->query(), thd->query_length(), + &buf, &buf_len); + break; + } + + wsrep_key_arr_t key_arr= {0, 0}; + struct wsrep_buf buff = { buf, buf_len }; + if (!buf_err && + !wsrep_prepare_keys_for_isolation(thd, db_, table_, + table_list, alter_info, &key_arr) && + key_arr.keys_len > 0 && + WSREP_OK == (ret = wsrep->to_execute_start(wsrep, thd->thread_id, + key_arr.keys, key_arr.keys_len, + &buff, 1, + &thd->wsrep_trx_meta))) + { + thd->wsrep_exec_mode= TOTAL_ORDER; + wsrep_to_isolation++; + wsrep_keys_free(&key_arr); + WSREP_DEBUG("TO BEGIN: %lld, %d",(long long)wsrep_thd_trx_seqno(thd), + thd->wsrep_exec_mode); + } + else if (key_arr.keys_len > 0) { + /* jump to error handler in mysql_execute_command() */ + WSREP_WARN("TO isolation failed for: %d, schema: %s, sql: %s. Check wsrep " + "connection state and retry the query.", + ret, + (thd->db ? thd->db : "(null)"), + (thd->query()) ? thd->query() : "void"); + my_error(ER_LOCK_DEADLOCK, MYF(0), "WSREP replication failed. Check " + "your wsrep connection state and retry the query."); + wsrep_keys_free(&key_arr); + rc= -1; + } + else { + /* non replicated DDL, affecting temporary tables only */ + WSREP_DEBUG("TO isolation skipped for: %d, sql: %s." + "Only temporary tables affected.", + ret, (thd->query()) ? thd->query() : "void"); + rc= 1; + } + if (buf) my_free(buf); + return rc; +} + +static void wsrep_TOI_end(THD *thd) { + wsrep_status_t ret; + wsrep_to_isolation--; + + WSREP_DEBUG("TO END: %lld, %d: %s", (long long)wsrep_thd_trx_seqno(thd), + thd->wsrep_exec_mode, wsrep_get_query_or_msg(thd)); + + wsrep_set_SE_checkpoint(thd->wsrep_trx_meta.gtid.uuid, + thd->wsrep_trx_meta.gtid.seqno); + WSREP_DEBUG("TO END: %lld, update seqno", + (long long)wsrep_thd_trx_seqno(thd)); + + if (WSREP_OK == (ret = wsrep->to_execute_end(wsrep, thd->thread_id))) { + WSREP_DEBUG("TO END: %lld", (long long)wsrep_thd_trx_seqno(thd)); + } + else { + WSREP_WARN("TO isolation end failed for: %d, schema: %s, sql: %s", + ret, + (thd->db ? thd->db : "(null)"), + (thd->query()) ? thd->query() : "void"); + } +} + +static int wsrep_RSU_begin(THD *thd, char *db_, char *table_) +{ + wsrep_status_t ret(WSREP_WARNING); + WSREP_DEBUG("RSU BEGIN: %lld, %d : %s", (long long)wsrep_thd_trx_seqno(thd), + thd->wsrep_exec_mode, thd->query() ); + + ret = wsrep->desync(wsrep); + if (ret != WSREP_OK) + { + WSREP_WARN("RSU desync failed %d for schema: %s, query: %s", + ret, (thd->db ? thd->db : "(null)"), thd->query()); + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return(ret); + } + + mysql_mutex_lock(&LOCK_wsrep_replaying); + wsrep_replaying++; + mysql_mutex_unlock(&LOCK_wsrep_replaying); + + if (wsrep_wait_committing_connections_close(5000)) + { + /* no can do, bail out from DDL */ + WSREP_WARN("RSU failed due to pending transactions, schema: %s, query %s", + (thd->db ? thd->db : "(null)"), + thd->query()); + mysql_mutex_lock(&LOCK_wsrep_replaying); + wsrep_replaying--; + mysql_mutex_unlock(&LOCK_wsrep_replaying); + + ret = wsrep->resync(wsrep); + if (ret != WSREP_OK) + { + WSREP_WARN("resync failed %d for schema: %s, query: %s", + ret, (thd->db ? thd->db : "(null)"), thd->query()); + } + + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return(1); + } + + wsrep_seqno_t seqno = wsrep->pause(wsrep); + if (seqno == WSREP_SEQNO_UNDEFINED) + { + WSREP_WARN("pause failed %lld for schema: %s, query: %s", (long long)seqno, + (thd->db ? thd->db : "(null)"), + thd->query()); + return(1); + } + WSREP_DEBUG("paused at %lld", (long long)seqno); + thd->variables.wsrep_on = 0; + return 0; +} + +static void wsrep_RSU_end(THD *thd) +{ + wsrep_status_t ret(WSREP_WARNING); + WSREP_DEBUG("RSU END: %lld, %d : %s", (long long)wsrep_thd_trx_seqno(thd), + thd->wsrep_exec_mode, thd->query() ); + + + mysql_mutex_lock(&LOCK_wsrep_replaying); + wsrep_replaying--; + mysql_mutex_unlock(&LOCK_wsrep_replaying); + + ret = wsrep->resume(wsrep); + if (ret != WSREP_OK) + { + WSREP_WARN("resume failed %d for schema: %s, query: %s", ret, + (thd->db ? thd->db : "(null)"), + thd->query()); + } + + ret = wsrep->resync(wsrep); + if (ret != WSREP_OK) + { + WSREP_WARN("resync failed %d for schema: %s, query: %s", ret, + (thd->db ? thd->db : "(null)"), thd->query()); + return; + } + + thd->variables.wsrep_on = 1; +} + +int wsrep_to_isolation_begin(THD *thd, char *db_, char *table_, + const TABLE_LIST* table_list, + Alter_info* alter_info) +{ + int ret= 0; + + /* + No isolation for applier or replaying threads. + */ + if (thd->wsrep_exec_mode == REPL_RECV) + return 0; + + mysql_mutex_lock(&thd->LOCK_thd_data); + + if (thd->wsrep_conflict_state == MUST_ABORT) + { + WSREP_INFO("thread: %lu, schema: %s, query: %s has been aborted due to multi-master conflict", + thd->thread_id, + (thd->db ? thd->db : "(null)"), + thd->query()); + mysql_mutex_unlock(&thd->LOCK_thd_data); + return WSREP_TRX_FAIL; + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + + DBUG_ASSERT(thd->wsrep_exec_mode == LOCAL_STATE); + DBUG_ASSERT(thd->wsrep_trx_meta.gtid.seqno == WSREP_SEQNO_UNDEFINED); + + if (thd->global_read_lock.can_acquire_protection()) + { + WSREP_DEBUG("Aborting TOI: Global Read-Lock (FTWRL) in place: %s %lu", + thd->query(), thd->thread_id); + return -1; + } + + if (wsrep_debug && thd->mdl_context.has_locks()) + { + WSREP_DEBUG("thread holds MDL locks at TI begin: %s %lu", + thd->query(), thd->thread_id); + } + + /* + It makes sense to set auto_increment_* to defaults in TOI operations. + Must be done before wsrep_TOI_begin() since Query_log_event encapsulating + TOI statement and auto inc variables for wsrep replication is constructed + there. Variables are reset back in THD::reset_for_next_command() before + processing of next command. + */ + if (wsrep_auto_increment_control) + { + thd->variables.auto_increment_offset = 1; + thd->variables.auto_increment_increment = 1; + } + + if (thd->variables.wsrep_on && thd->wsrep_exec_mode==LOCAL_STATE) + { + switch (thd->variables.wsrep_OSU_method) { + case WSREP_OSU_TOI: + ret= wsrep_TOI_begin(thd, db_, table_, table_list, alter_info); + break; + case WSREP_OSU_RSU: + ret= wsrep_RSU_begin(thd, db_, table_); + break; + default: + WSREP_ERROR("Unsupported OSU method: %lu", + thd->variables.wsrep_OSU_method); + ret= -1; + break; + } + switch (ret) { + case 0: thd->wsrep_exec_mode= TOTAL_ORDER; break; + case 1: + /* TOI replication skipped, treat as success */ + ret = 0; + break; + case -1: + /* TOI replication failed, treat as error */ + break; + } + } + return ret; +} + +void wsrep_to_isolation_end(THD *thd) +{ + if (thd->wsrep_exec_mode == TOTAL_ORDER) + { + switch(thd->variables.wsrep_OSU_method) + { + case WSREP_OSU_TOI: wsrep_TOI_end(thd); break; + case WSREP_OSU_RSU: wsrep_RSU_end(thd); break; + default: + WSREP_WARN("Unsupported wsrep OSU method at isolation end: %lu", + thd->variables.wsrep_OSU_method); + break; + } + wsrep_cleanup_transaction(thd); + } +} + +#define WSREP_MDL_LOG(severity, msg, schema, schema_len, req, gra) \ + WSREP_##severity( \ + "%s\n" \ + "schema: %.*s\n" \ + "request: (%lu \tseqno %lld \twsrep (%d, %d, %d) cmd %d %d \t%s)\n" \ + "granted: (%lu \tseqno %lld \twsrep (%d, %d, %d) cmd %d %d \t%s)", \ + msg, schema_len, schema, \ + req->thread_id, (long long)wsrep_thd_trx_seqno(req), \ + req->wsrep_exec_mode, req->wsrep_query_state, req->wsrep_conflict_state, \ + req->get_command(), req->lex->sql_command, req->query(), \ + gra->thread_id, (long long)wsrep_thd_trx_seqno(gra), \ + gra->wsrep_exec_mode, gra->wsrep_query_state, gra->wsrep_conflict_state, \ + gra->get_command(), gra->lex->sql_command, gra->query()); + +/** + Check if request for the metadata lock should be granted to the requester. + + @param requestor_ctx The MDL context of the requestor + @param ticket MDL ticket for the requested lock + + @retval TRUE Lock request can be granted + @retval FALSE Lock request cannot be granted +*/ + +bool +wsrep_grant_mdl_exception(MDL_context *requestor_ctx, + MDL_ticket *ticket, + const MDL_key *key +) { + /* Fallback to the non-wsrep behaviour */ + if (!WSREP_ON) return FALSE; + + THD *request_thd = requestor_ctx->get_thd(); + THD *granted_thd = ticket->get_ctx()->get_thd(); + bool ret = FALSE; + + const char* schema= key->db_name(); + int schema_len= key->db_name_length(); + + mysql_mutex_lock(&request_thd->LOCK_thd_data); + if (request_thd->wsrep_exec_mode == TOTAL_ORDER || + request_thd->wsrep_exec_mode == REPL_RECV) + { + mysql_mutex_unlock(&request_thd->LOCK_thd_data); + WSREP_MDL_LOG(DEBUG, "MDL conflict ", schema, schema_len, + request_thd, granted_thd); + ticket->wsrep_report(wsrep_debug); + + mysql_mutex_lock(&granted_thd->LOCK_thd_data); + if (granted_thd->wsrep_exec_mode == TOTAL_ORDER || + granted_thd->wsrep_exec_mode == REPL_RECV) + { + WSREP_MDL_LOG(INFO, "MDL BF-BF conflict", schema, schema_len, + request_thd, granted_thd); + ticket->wsrep_report(true); + mysql_mutex_unlock(&granted_thd->LOCK_thd_data); + ret = TRUE; + } + else if (granted_thd->lex->sql_command == SQLCOM_FLUSH || + granted_thd->mdl_context.has_explicit_locks()) + { + WSREP_DEBUG("BF thread waiting for FLUSH"); + ticket->wsrep_report(wsrep_debug); + mysql_mutex_unlock(&granted_thd->LOCK_thd_data); + ret = FALSE; + } + else if (request_thd->lex->sql_command == SQLCOM_DROP_TABLE) + { + WSREP_DEBUG("DROP caused BF abort, conf %d", granted_thd->wsrep_conflict_state); + ticket->wsrep_report(wsrep_debug); + mysql_mutex_unlock(&granted_thd->LOCK_thd_data); + wsrep_abort_thd((void*)request_thd, (void*)granted_thd, 1); + ret = FALSE; + } + else if (granted_thd->wsrep_query_state == QUERY_COMMITTING) + { + WSREP_DEBUG("MDL granted, but committing thd abort scheduled"); + ticket->wsrep_report(wsrep_debug); + mysql_mutex_unlock(&granted_thd->LOCK_thd_data); + wsrep_abort_thd((void*)request_thd, (void*)granted_thd, 1); + ret = FALSE; + } + else + { + WSREP_MDL_LOG(DEBUG, "MDL conflict-> BF abort", schema, schema_len, + request_thd, granted_thd); + ticket->wsrep_report(wsrep_debug); + mysql_mutex_unlock(&granted_thd->LOCK_thd_data); + wsrep_abort_thd((void*)request_thd, (void*)granted_thd, 1); + ret = FALSE; + } + } + else + { + mysql_mutex_unlock(&request_thd->LOCK_thd_data); + } + return ret; +} + + +pthread_handler_t start_wsrep_THD(void *arg) +{ + THD *thd; + wsrep_thd_processor_fun processor= (wsrep_thd_processor_fun)arg; + + if (my_thread_init() || (!(thd= new THD(true)))) + { + goto error; + } + + mysql_mutex_lock(&LOCK_thread_count); + thd->thread_id=thread_id++; + + if (wsrep_gtid_mode) + { + /* Adjust domain_id. */ + thd->variables.gtid_domain_id= wsrep_gtid_domain_id; + } + + thd->real_id=pthread_self(); // Keep purify happy + thread_count++; + thread_created++; + threads.append(thd); + + my_net_init(&thd->net,(st_vio*) 0, thd, MYF(0)); + + DBUG_PRINT("wsrep",(("creating thread %lld"), (long long)thd->thread_id)); + thd->prior_thr_create_utime= thd->start_utime= microsecond_interval_timer(); + (void) mysql_mutex_unlock(&LOCK_thread_count); + + /* from bootstrap()... */ + thd->bootstrap=1; + thd->max_client_packet_length= thd->net.max_packet; + thd->security_ctx->master_access= ~(ulong)0; + + /* from handle_one_connection... */ + pthread_detach_this_thread(); + + mysql_thread_set_psi_id(thd->thread_id); + thd->thr_create_utime= microsecond_interval_timer(); + if (MYSQL_CALLBACK_ELSE(thread_scheduler, init_new_connection_thread, (), 0)) + { + close_connection(thd, ER_OUT_OF_RESOURCES); + statistic_increment(aborted_connects,&LOCK_status); + MYSQL_CALLBACK(thread_scheduler, end_thread, (thd, 0)); + + goto error; + } + +// </5.1.17> + /* + handle_one_connection() is normally the only way a thread would + start and would always be on the very high end of the stack , + therefore, the thread stack always starts at the address of the + first local variable of handle_one_connection, which is thd. We + need to know the start of the stack so that we could check for + stack overruns. + */ + DBUG_PRINT("wsrep", ("handle_one_connection called by thread %lld\n", + (long long)thd->thread_id)); + /* now that we've called my_thread_init(), it is safe to call DBUG_* */ + + thd->thread_stack= (char*) &thd; + if (thd->store_globals()) + { + close_connection(thd, ER_OUT_OF_RESOURCES); + statistic_increment(aborted_connects,&LOCK_status); + MYSQL_CALLBACK(thread_scheduler, end_thread, (thd, 0)); + delete thd; + goto error; + } + + thd->system_thread= SYSTEM_THREAD_SLAVE_SQL; + thd->security_ctx->skip_grants(); + + /* handle_one_connection() again... */ + //thd->version= refresh_version; + thd->proc_info= 0; + thd->set_command(COM_SLEEP); + thd->init_for_queries(); + + mysql_mutex_lock(&LOCK_thread_count); + wsrep_running_threads++; + mysql_cond_broadcast(&COND_thread_count); + mysql_mutex_unlock(&LOCK_thread_count); + + processor(thd); + + close_connection(thd, 0); + + mysql_mutex_lock(&LOCK_thread_count); + wsrep_running_threads--; + WSREP_DEBUG("wsrep running threads now: %lu", wsrep_running_threads); + mysql_cond_broadcast(&COND_thread_count); + mysql_mutex_unlock(&LOCK_thread_count); + + // Note: We can't call THD destructor without crashing + // if plugins have not been initialized. However, in most of the + // cases this means that pre SE initialization SST failed and + // we are going to exit anyway. + if (plugins_are_initialized) + { + net_end(&thd->net); + MYSQL_CALLBACK(thread_scheduler, end_thread, (thd, 1)); + } + else + { + // TODO: lightweight cleanup to get rid of: + // 'Error in my_thread_global_end(): 2 threads didn't exit' + // at server shutdown + } + + my_thread_end(); + if (thread_handling > SCHEDULER_ONE_THREAD_PER_CONNECTION) + { + mysql_mutex_lock(&LOCK_thread_count); + delete thd; + thread_count--; + mysql_mutex_unlock(&LOCK_thread_count); + } + return(NULL); + +error: + WSREP_ERROR("Failed to create/initialize system thread"); + + /* Abort if its the first applier/rollbacker thread. */ + if (!mysqld_server_initialized) + unireg_abort(1); + else + return NULL; +} + + +/**/ +static bool abort_replicated(THD *thd) +{ + bool ret_code= false; + if (thd->wsrep_query_state== QUERY_COMMITTING) + { + WSREP_DEBUG("aborting replicated trx: %llu", (ulonglong)(thd->real_id)); + + (void)wsrep_abort_thd(thd, thd, TRUE); + ret_code= true; + } + return ret_code; +} + + +/**/ +static inline bool is_client_connection(THD *thd) +{ + return (thd->wsrep_client_thread && thd->variables.wsrep_on); +} + + +static inline bool is_replaying_connection(THD *thd) +{ + bool ret; + + mysql_mutex_lock(&thd->LOCK_thd_data); + ret= (thd->wsrep_conflict_state == REPLAYING) ? true : false; + mysql_mutex_unlock(&thd->LOCK_thd_data); + + return ret; +} + + +static inline bool is_committing_connection(THD *thd) +{ + bool ret; + + mysql_mutex_lock(&thd->LOCK_thd_data); + ret= (thd->wsrep_query_state == QUERY_COMMITTING) ? true : false; + mysql_mutex_unlock(&thd->LOCK_thd_data); + + return ret; +} + + +static bool have_client_connections() +{ + THD *tmp; + + I_List_iterator<THD> it(threads); + while ((tmp=it++)) + { + DBUG_PRINT("quit",("Informing thread %ld that it's time to die", + tmp->thread_id)); + if (is_client_connection(tmp) && tmp->killed == KILL_CONNECTION) + { + (void)abort_replicated(tmp); + return true; + } + } + return false; +} + +static void wsrep_close_thread(THD *thd) +{ + thd->set_killed(KILL_CONNECTION); + MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (thd)); + if (thd->mysys_var) + { + thd->mysys_var->abort=1; + mysql_mutex_lock(&thd->mysys_var->mutex); + if (thd->mysys_var->current_cond) + { + mysql_mutex_lock(thd->mysys_var->current_mutex); + mysql_cond_broadcast(thd->mysys_var->current_cond); + mysql_mutex_unlock(thd->mysys_var->current_mutex); + } + mysql_mutex_unlock(&thd->mysys_var->mutex); + } +} + + +static my_bool have_committing_connections() +{ + THD *tmp; + mysql_mutex_lock(&LOCK_thread_count); // For unlink from list + + I_List_iterator<THD> it(threads); + while ((tmp=it++)) + { + if (!is_client_connection(tmp)) + continue; + + if (is_committing_connection(tmp)) + { + return TRUE; + } + } + mysql_mutex_unlock(&LOCK_thread_count); + return FALSE; +} + + +int wsrep_wait_committing_connections_close(int wait_time) +{ + int sleep_time= 100; + + while (have_committing_connections() && wait_time > 0) + { + WSREP_DEBUG("wait for committing transaction to close: %d", wait_time); + my_sleep(sleep_time); + wait_time -= sleep_time; + } + if (have_committing_connections()) + { + return 1; + } + return 0; +} + + +void wsrep_close_client_connections(my_bool wait_to_end, THD *except_caller_thd) +{ + /* + First signal all threads that it's time to die + */ + + THD *tmp; + mysql_mutex_lock(&LOCK_thread_count); // For unlink from list + + bool kill_cached_threads_saved= kill_cached_threads; + kill_cached_threads= true; // prevent future threads caching + mysql_cond_broadcast(&COND_thread_cache); // tell cached threads to die + + I_List_iterator<THD> it(threads); + while ((tmp=it++)) + { + DBUG_PRINT("quit",("Informing thread %ld that it's time to die", + tmp->thread_id)); + /* We skip slave threads & scheduler on this first loop through. */ + if (!is_client_connection(tmp)) + continue; + + if (tmp == except_caller_thd) + { + DBUG_ASSERT(is_client_connection(tmp)); + continue; + } + + if (is_replaying_connection(tmp)) + { + tmp->set_killed(KILL_CONNECTION); + continue; + } + + /* replicated transactions must be skipped */ + if (abort_replicated(tmp)) + continue; + + WSREP_DEBUG("closing connection %ld", tmp->thread_id); + + /* + instead of wsrep_close_thread() we do now soft kill by THD::awake + */ + mysql_mutex_lock(&tmp->LOCK_thd_data); + + tmp->awake(KILL_CONNECTION); + + mysql_mutex_unlock(&tmp->LOCK_thd_data); + + } + mysql_mutex_unlock(&LOCK_thread_count); + + if (thread_count) + sleep(2); // Give threads time to die + + mysql_mutex_lock(&LOCK_thread_count); + /* + Force remaining threads to die by closing the connection to the client + */ + + I_List_iterator<THD> it2(threads); + while ((tmp=it2++)) + { +#ifndef __bsdi__ // Bug in BSDI kernel + if (is_client_connection(tmp) && + !abort_replicated(tmp) && + !is_replaying_connection(tmp) && + tmp != except_caller_thd) + { + WSREP_INFO("killing local connection: %ld",tmp->thread_id); + close_connection(tmp,0); + } +#endif + } + + DBUG_PRINT("quit",("Waiting for threads to die (count=%u)",thread_count)); + WSREP_DEBUG("waiting for client connections to close: %u", thread_count); + + while (wait_to_end && have_client_connections()) + { + mysql_cond_wait(&COND_thread_count, &LOCK_thread_count); + DBUG_PRINT("quit",("One thread died (count=%u)", thread_count)); + } + + kill_cached_threads= kill_cached_threads_saved; + + mysql_mutex_unlock(&LOCK_thread_count); + + /* All client connection threads have now been aborted */ +} + + +void wsrep_close_applier(THD *thd) +{ + WSREP_DEBUG("closing applier %ld", thd->thread_id); + wsrep_close_thread(thd); +} + + +void wsrep_close_threads(THD *thd) +{ + THD *tmp; + mysql_mutex_lock(&LOCK_thread_count); // For unlink from list + + I_List_iterator<THD> it(threads); + while ((tmp=it++)) + { + DBUG_PRINT("quit",("Informing thread %ld that it's time to die", + tmp->thread_id)); + /* We skip slave threads & scheduler on this first loop through. */ + if (tmp->wsrep_applier && tmp != thd) + { + WSREP_DEBUG("closing wsrep thread %ld", tmp->thread_id); + wsrep_close_thread (tmp); + } + } + + mysql_mutex_unlock(&LOCK_thread_count); +} + +void wsrep_wait_appliers_close(THD *thd) +{ + /* Wait for wsrep appliers to gracefully exit */ + mysql_mutex_lock(&LOCK_thread_count); + while (wsrep_running_threads > 1) + // 1 is for rollbacker thread which needs to be killed explicitly. + // This gotta be fixed in a more elegant manner if we gonna have arbitrary + // number of non-applier wsrep threads. + { + if (thread_handling > SCHEDULER_ONE_THREAD_PER_CONNECTION) + { + mysql_mutex_unlock(&LOCK_thread_count); + my_sleep(100); + mysql_mutex_lock(&LOCK_thread_count); + } + else + mysql_cond_wait(&COND_thread_count,&LOCK_thread_count); + DBUG_PRINT("quit",("One applier died (count=%u)",thread_count)); + } + mysql_mutex_unlock(&LOCK_thread_count); + /* Now kill remaining wsrep threads: rollbacker */ + wsrep_close_threads (thd); + /* and wait for them to die */ + mysql_mutex_lock(&LOCK_thread_count); + while (wsrep_running_threads > 0) + { + if (thread_handling > SCHEDULER_ONE_THREAD_PER_CONNECTION) + { + mysql_mutex_unlock(&LOCK_thread_count); + my_sleep(100); + mysql_mutex_lock(&LOCK_thread_count); + } + else + mysql_cond_wait(&COND_thread_count,&LOCK_thread_count); + DBUG_PRINT("quit",("One thread died (count=%u)",thread_count)); + } + mysql_mutex_unlock(&LOCK_thread_count); + + /* All wsrep applier threads have now been aborted. However, if this thread + is also applier, we are still running... + */ +} + + +void wsrep_kill_mysql(THD *thd) +{ + if (mysqld_server_started) + { + if (!shutdown_in_progress) + { + WSREP_INFO("starting shutdown"); + kill_mysql(); + } + } + else + { + unireg_abort(1); + } +} + + +static int wsrep_create_sp(THD *thd, uchar** buf, size_t* buf_len) +{ + String log_query; + sp_head *sp = thd->lex->sphead; + ulong saved_mode= thd->variables.sql_mode; + String retstr(64); + retstr.set_charset(system_charset_info); + + log_query.set_charset(system_charset_info); + + if (sp->m_type == TYPE_ENUM_FUNCTION) + { + sp_returns_type(thd, retstr, sp); + } + + if (!show_create_sp(thd, &log_query, + sp->m_type, + (sp->m_explicit_name ? sp->m_db.str : NULL), + (sp->m_explicit_name ? sp->m_db.length : 0), + sp->m_name.str, sp->m_name.length, + sp->m_params.str, sp->m_params.length, + retstr.c_ptr(), retstr.length(), + sp->m_body.str, sp->m_body.length, + sp->m_chistics, &(thd->lex->definer->user), + &(thd->lex->definer->host), + saved_mode)) + { + WSREP_WARN("SP create string failed: schema: %s, query: %s", + (thd->db ? thd->db : "(null)"), thd->query()); + return 1; + } + + return wsrep_to_buf_helper(thd, log_query.ptr(), log_query.length(), buf, buf_len); +} + + +extern int wsrep_on(THD *thd) +{ + return (int)(WSREP(thd)); +} + + +extern "C" bool wsrep_thd_is_wsrep_on(THD *thd) +{ + return thd->variables.wsrep_on; +} + + +bool wsrep_consistency_check(THD *thd) +{ + return thd->wsrep_consistency_check == CONSISTENCY_CHECK_RUNNING; +} + + +extern "C" void wsrep_thd_set_exec_mode(THD *thd, enum wsrep_exec_mode mode) +{ + thd->wsrep_exec_mode= mode; +} + + +extern "C" void wsrep_thd_set_query_state( + THD *thd, enum wsrep_query_state state) +{ + thd->wsrep_query_state= state; +} + + +void wsrep_thd_set_conflict_state(THD *thd, enum wsrep_conflict_state state) +{ + if (WSREP(thd)) thd->wsrep_conflict_state= state; +} + + +enum wsrep_exec_mode wsrep_thd_exec_mode(THD *thd) +{ + return thd->wsrep_exec_mode; +} + + +const char *wsrep_thd_exec_mode_str(THD *thd) +{ + return + (!thd) ? "void" : + (thd->wsrep_exec_mode == LOCAL_STATE) ? "local" : + (thd->wsrep_exec_mode == REPL_RECV) ? "applier" : + (thd->wsrep_exec_mode == TOTAL_ORDER) ? "total order" : + (thd->wsrep_exec_mode == LOCAL_COMMIT) ? "local commit" : "void"; +} + + +enum wsrep_query_state wsrep_thd_query_state(THD *thd) +{ + return thd->wsrep_query_state; +} + + +const char *wsrep_thd_query_state_str(THD *thd) +{ + return + (!thd) ? "void" : + (thd->wsrep_query_state == QUERY_IDLE) ? "idle" : + (thd->wsrep_query_state == QUERY_EXEC) ? "executing" : + (thd->wsrep_query_state == QUERY_COMMITTING) ? "committing" : + (thd->wsrep_query_state == QUERY_EXITING) ? "exiting" : + (thd->wsrep_query_state == QUERY_ROLLINGBACK) ? "rolling back" : "void"; +} + + +enum wsrep_conflict_state wsrep_thd_get_conflict_state(THD *thd) +{ + return thd->wsrep_conflict_state; +} + + +const char *wsrep_thd_conflict_state_str(THD *thd) +{ + return + (!thd) ? "void" : + (thd->wsrep_conflict_state == NO_CONFLICT) ? "no conflict" : + (thd->wsrep_conflict_state == MUST_ABORT) ? "must abort" : + (thd->wsrep_conflict_state == ABORTING) ? "aborting" : + (thd->wsrep_conflict_state == MUST_REPLAY) ? "must replay" : + (thd->wsrep_conflict_state == REPLAYING) ? "replaying" : + (thd->wsrep_conflict_state == RETRY_AUTOCOMMIT) ? "retrying" : + (thd->wsrep_conflict_state == CERT_FAILURE) ? "cert failure" : "void"; +} + + +wsrep_ws_handle_t* wsrep_thd_ws_handle(THD *thd) +{ + return &thd->wsrep_ws_handle; +} + + +void wsrep_thd_LOCK(THD *thd) +{ + mysql_mutex_lock(&thd->LOCK_thd_data); +} + + +void wsrep_thd_UNLOCK(THD *thd) +{ + mysql_mutex_unlock(&thd->LOCK_thd_data); +} + + +extern "C" time_t wsrep_thd_query_start(THD *thd) +{ + return thd->query_start(); +} + + +extern "C" uint32 wsrep_thd_wsrep_rand(THD *thd) +{ + return thd->wsrep_rand; +} + +longlong wsrep_thd_trx_seqno(THD *thd) +{ + return (thd) ? thd->wsrep_trx_meta.gtid.seqno : WSREP_SEQNO_UNDEFINED; +} + + +extern "C" query_id_t wsrep_thd_query_id(THD *thd) +{ + return thd->query_id; +} + + +char *wsrep_thd_query(THD *thd) +{ + return (thd) ? thd->query() : NULL; +} + + +extern "C" query_id_t wsrep_thd_wsrep_last_query_id(THD *thd) +{ + return thd->wsrep_last_query_id; +} + + +extern "C" void wsrep_thd_set_wsrep_last_query_id(THD *thd, query_id_t id) +{ + thd->wsrep_last_query_id= id; +} + + +extern "C" void wsrep_thd_awake(THD *thd, my_bool signal) +{ + if (signal) + { + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->awake(KILL_QUERY); + mysql_mutex_unlock(&thd->LOCK_thd_data); + } + else + { + mysql_mutex_lock(&LOCK_wsrep_replaying); + mysql_cond_broadcast(&COND_wsrep_replaying); + mysql_mutex_unlock(&LOCK_wsrep_replaying); + } +} + + +int wsrep_thd_retry_counter(THD *thd) +{ + return(thd->wsrep_retry_counter); +} + + +extern "C" bool wsrep_thd_ignore_table(THD *thd) +{ + return thd->wsrep_ignore_table; +} + + +extern int +wsrep_trx_order_before(THD *thd1, THD *thd2) +{ + if (wsrep_thd_trx_seqno(thd1) < wsrep_thd_trx_seqno(thd2)) { + WSREP_DEBUG("BF conflict, order: %lld %lld\n", + (long long)wsrep_thd_trx_seqno(thd1), + (long long)wsrep_thd_trx_seqno(thd2)); + return 1; + } + WSREP_DEBUG("waiting for BF, trx order: %lld %lld\n", + (long long)wsrep_thd_trx_seqno(thd1), + (long long)wsrep_thd_trx_seqno(thd2)); + return 0; +} + + +int wsrep_trx_is_aborting(THD *thd_ptr) +{ + if (thd_ptr) { + if ((((THD *)thd_ptr)->wsrep_conflict_state == MUST_ABORT) || + (((THD *)thd_ptr)->wsrep_conflict_state == ABORTING)) { + return 1; + } + } + return 0; +} + + +void wsrep_copy_query(THD *thd) +{ + thd->wsrep_retry_command = thd->get_command(); + thd->wsrep_retry_query_len = thd->query_length(); + if (thd->wsrep_retry_query) { + my_free(thd->wsrep_retry_query); + } + thd->wsrep_retry_query = (char *)my_malloc( + thd->wsrep_retry_query_len + 1, MYF(0)); + strncpy(thd->wsrep_retry_query, thd->query(), thd->wsrep_retry_query_len); + thd->wsrep_retry_query[thd->wsrep_retry_query_len] = '\0'; +} + + +bool wsrep_is_show_query(enum enum_sql_command command) +{ + DBUG_ASSERT(command >= 0 && command <= SQLCOM_END); + return (sql_command_flags[command] & CF_STATUS_COMMAND) != 0; +} + +bool wsrep_create_like_table(THD* thd, TABLE_LIST* table, + TABLE_LIST* src_table, + HA_CREATE_INFO *create_info) +{ + TABLE *tmp_table; + bool is_tmp_table= FALSE; + + for (tmp_table= thd->temporary_tables; tmp_table; tmp_table=tmp_table->next) + { + if (!strcmp(src_table->db, tmp_table->s->db.str) && + !strcmp(src_table->table_name, tmp_table->s->table_name.str)) + { + is_tmp_table= TRUE; + break; + } + } + if (create_info->tmp_table()) + { + + /* CREATE TEMPORARY TABLE LIKE must be skipped from replication */ + WSREP_DEBUG("CREATE TEMPORARY TABLE LIKE... skipped replication\n %s", + thd->query()); + } + else if (!is_tmp_table) + { + /* this is straight CREATE TABLE LIKE... eith no tmp tables */ + WSREP_TO_ISOLATION_BEGIN(table->db, table->table_name, NULL); + } + else + { + /* here we have CREATE TABLE LIKE <temporary table> + the temporary table definition will be needed in slaves to + enable the create to succeed + */ + TABLE_LIST tbl; + bzero((void*) &tbl, sizeof(tbl)); + tbl.db= src_table->db; + tbl.table_name= tbl.alias= src_table->table_name; + tbl.table= tmp_table; + char buf[2048]; + String query(buf, sizeof(buf), system_charset_info); + query.length(0); // Have to zero it since constructor doesn't + + (void) show_create_table(thd, &tbl, &query, NULL, WITH_DB_NAME); + WSREP_DEBUG("TMP TABLE: %s", query.ptr()); + + thd->wsrep_TOI_pre_query= query.ptr(); + thd->wsrep_TOI_pre_query_len= query.length(); + + WSREP_TO_ISOLATION_BEGIN(table->db, table->table_name, NULL); + + thd->wsrep_TOI_pre_query= NULL; + thd->wsrep_TOI_pre_query_len= 0; + } + + return(false); + +WSREP_ERROR_LABEL: + thd->wsrep_TOI_pre_query= NULL; + return (true); +} + + +static int wsrep_create_trigger_query(THD *thd, uchar** buf, size_t* buf_len) +{ + LEX *lex= thd->lex; + String stmt_query; + + LEX_STRING definer_user; + LEX_STRING definer_host; + + if (!lex->definer) + { + if (!thd->slave_thread) + { + if (!(lex->definer= create_default_definer(thd, false))) + return 1; + } + } + + if (lex->definer) + { + /* SUID trigger. */ + LEX_USER *d= get_current_user(thd, lex->definer); + + if (!d) + return 1; + + definer_user= d->user; + definer_host= d->host; + } + else + { + /* non-SUID trigger. */ + + definer_user.str= 0; + definer_user.length= 0; + + definer_host.str= 0; + definer_host.length= 0; + } + + stmt_query.append(STRING_WITH_LEN("CREATE ")); + + append_definer(thd, &stmt_query, &definer_user, &definer_host); + + LEX_STRING stmt_definition; + stmt_definition.str= (char*) thd->lex->stmt_definition_begin; + stmt_definition.length= thd->lex->stmt_definition_end + - thd->lex->stmt_definition_begin; + trim_whitespace(thd->charset(), & stmt_definition); + + stmt_query.append(stmt_definition.str, stmt_definition.length); + + return wsrep_to_buf_helper(thd, stmt_query.c_ptr(), stmt_query.length(), + buf, buf_len); +} + +/***** callbacks for wsrep service ************/ + +my_bool get_wsrep_debug() +{ + return wsrep_debug; +} + +my_bool get_wsrep_load_data_splitting() +{ + return wsrep_load_data_splitting; +} + +long get_wsrep_protocol_version() +{ + return wsrep_protocol_version; +} + +my_bool get_wsrep_drupal_282555_workaround() +{ + return wsrep_drupal_282555_workaround; +} + +my_bool get_wsrep_recovery() +{ + return wsrep_recovery; +} + +my_bool get_wsrep_log_conflicts() +{ + return wsrep_log_conflicts; +} + +wsrep_t *get_wsrep() +{ + return wsrep; +} + +my_bool get_wsrep_certify_nonPK() +{ + return wsrep_certify_nonPK; +} + +void wsrep_lock_rollback() +{ + mysql_mutex_lock(&LOCK_wsrep_rollback); +} + +void wsrep_unlock_rollback() +{ + mysql_cond_signal(&COND_wsrep_rollback); + mysql_mutex_unlock(&LOCK_wsrep_rollback); +} + +my_bool wsrep_aborting_thd_contains(THD *thd) +{ + wsrep_aborting_thd_t abortees = wsrep_aborting_thd; + while (abortees) + { + if (abortees->aborting_thd == thd) + return true; + abortees = abortees->next; + } + return false; +} + +void wsrep_aborting_thd_enqueue(THD *thd) +{ + wsrep_aborting_thd_t aborting = (wsrep_aborting_thd_t) + my_malloc(sizeof(struct wsrep_aborting_thd), MYF(0)); + aborting->aborting_thd = thd; + aborting->next = wsrep_aborting_thd; + wsrep_aborting_thd = aborting; +} + +bool wsrep_node_is_donor() +{ + return (WSREP_ON) ? (wsrep_config_state.get_status() == 2) : false; +} + +bool wsrep_node_is_synced() +{ + return (WSREP_ON) ? (wsrep_config_state.get_status() == 4) : false; +} diff --git a/sql/wsrep_mysqld.h b/sql/wsrep_mysqld.h new file mode 100644 index 00000000000..2b55fbe42ee --- /dev/null +++ b/sql/wsrep_mysqld.h @@ -0,0 +1,375 @@ +/* Copyright 2008-2015 Codership Oy <http://www.codership.com> + + 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 02111-1301 USA */ + +#include <wsrep.h> + +#ifndef WSREP_MYSQLD_H +#define WSREP_MYSQLD_H + +#include <mysql/plugin.h> +#include <mysql/service_wsrep.h> + +#ifdef WITH_WSREP + +typedef struct st_mysql_show_var SHOW_VAR; +#include <sql_priv.h> +//#include "rpl_gtid.h" +#include "../wsrep/wsrep_api.h" +#include "mdl.h" +#include "mysqld.h" +#include "sql_table.h" +#include "wsrep_mysqld_c.h" + +#define WSREP_UNDEFINED_TRX_ID ULONGLONG_MAX + +class set_var; +class THD; + +enum wsrep_consistency_check_mode { + NO_CONSISTENCY_CHECK, + CONSISTENCY_CHECK_DECLARED, + CONSISTENCY_CHECK_RUNNING, +}; + +struct wsrep_thd_shadow { + ulonglong options; + uint server_status; + enum wsrep_exec_mode wsrep_exec_mode; + Vio *vio; + ulong tx_isolation; + char *db; + size_t db_length; + my_hrtime_t user_time; + longlong row_count_func; +}; + +// Global wsrep parameters +extern wsrep_t* wsrep; + +// MySQL wsrep options +extern const char* wsrep_provider; +extern const char* wsrep_provider_options; +extern const char* wsrep_cluster_name; +extern const char* wsrep_cluster_address; +extern const char* wsrep_node_name; +extern const char* wsrep_node_address; +extern const char* wsrep_node_incoming_address; +extern const char* wsrep_data_home_dir; +extern const char* wsrep_dbug_option; +extern long wsrep_slave_threads; +extern int wsrep_slave_count_change; +extern my_bool wsrep_convert_LOCK_to_trx; +extern ulong wsrep_retry_autocommit; +extern my_bool wsrep_auto_increment_control; +extern my_bool wsrep_incremental_data_collection; +extern const char* wsrep_start_position; +extern ulong wsrep_max_ws_size; +extern ulong wsrep_max_ws_rows; +extern const char* wsrep_notify_cmd; +extern long wsrep_max_protocol_version; +extern ulong wsrep_forced_binlog_format; +extern my_bool wsrep_desync; +extern ulong wsrep_reject_queries; +extern my_bool wsrep_replicate_myisam; +extern ulong wsrep_mysql_replication_bundle; +extern my_bool wsrep_restart_slave; +extern my_bool wsrep_restart_slave_activated; +extern my_bool wsrep_slave_FK_checks; +extern my_bool wsrep_slave_UK_checks; +extern ulong wsrep_running_threads; +extern bool wsrep_new_cluster; +extern bool wsrep_gtid_mode; +extern uint32 wsrep_gtid_domain_id; +extern bool wsrep_dirty_reads; + +enum enum_wsrep_reject_types { + WSREP_REJECT_NONE, /* nothing rejected */ + WSREP_REJECT_ALL, /* reject all queries, with UNKNOWN_COMMAND error */ + WSREP_REJECT_ALL_KILL /* kill existing connections and reject all queries*/ +}; + +enum enum_wsrep_OSU_method { + WSREP_OSU_TOI, + WSREP_OSU_RSU, + WSREP_OSU_NONE, +}; + +enum enum_wsrep_sync_wait { + WSREP_SYNC_WAIT_NONE = 0x0, + // select, begin + WSREP_SYNC_WAIT_BEFORE_READ = 0x1, + WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE = 0x2, + WSREP_SYNC_WAIT_BEFORE_INSERT_REPLACE = 0x4, + WSREP_SYNC_WAIT_BEFORE_SHOW = 0x8, + WSREP_SYNC_WAIT_MAX = 0xF +}; + +// MySQL status variables +extern my_bool wsrep_connected; +extern my_bool wsrep_ready; +extern const char* wsrep_cluster_state_uuid; +extern long long wsrep_cluster_conf_id; +extern const char* wsrep_cluster_status; +extern long wsrep_cluster_size; +extern long wsrep_local_index; +extern long long wsrep_local_bf_aborts; +extern const char* wsrep_provider_name; +extern const char* wsrep_provider_version; +extern const char* wsrep_provider_vendor; + +int wsrep_show_status(THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope); +int wsrep_init(); +void wsrep_deinit(bool free_options); + +/* Initialize wsrep thread LOCKs and CONDs */ +void wsrep_thr_init(); +/* Destroy wsrep thread LOCKs and CONDs */ +void wsrep_thr_deinit(); + +void wsrep_recover(); +bool wsrep_before_SE(); // initialize wsrep before storage + // engines (true) or after (false) +/* wsrep initialization sequence at startup + * @param before wsrep_before_SE() value */ +void wsrep_init_startup(bool before); + +// Other wsrep global variables +extern my_bool wsrep_inited; // whether wsrep is initialized ? + + +extern "C" void wsrep_thd_set_exec_mode(THD *thd, enum wsrep_exec_mode mode); +extern "C" void wsrep_thd_set_query_state( + THD *thd, enum wsrep_query_state state); + +extern "C" void wsrep_thd_set_trx_to_replay(THD *thd, uint64 trx_id); + +extern "C" uint32 wsrep_thd_wsrep_rand(THD *thd); +extern "C" time_t wsrep_thd_query_start(THD *thd); +extern "C" query_id_t wsrep_thd_query_id(THD *thd); +extern "C" query_id_t wsrep_thd_wsrep_last_query_id(THD *thd); +extern "C" void wsrep_thd_set_wsrep_last_query_id(THD *thd, query_id_t id); + +extern int wsrep_wait_committing_connections_close(int wait_time); +extern void wsrep_close_applier(THD *thd); +extern void wsrep_wait_appliers_close(THD *thd); +extern void wsrep_close_applier_threads(int count); +extern void wsrep_kill_mysql(THD *thd); + +/* new defines */ +extern void wsrep_stop_replication(THD *thd); +extern bool wsrep_start_replication(); +extern bool wsrep_must_sync_wait(THD* thd, uint mask = WSREP_SYNC_WAIT_BEFORE_READ); +extern bool wsrep_sync_wait(THD* thd, uint mask = WSREP_SYNC_WAIT_BEFORE_READ); +extern int wsrep_check_opts(); +extern void wsrep_prepend_PATH (const char* path); + +/* Other global variables */ +extern wsrep_seqno_t wsrep_locked_seqno; + +#define WSREP_ON \ + (global_system_variables.wsrep_on) + +#define WSREP_ON_NEW \ + ((global_system_variables.wsrep_on) && \ + wsrep_provider && \ + strcmp(wsrep_provider, WSREP_NONE)) + +#define WSREP(thd) \ + (WSREP_ON && thd->variables.wsrep_on) + +#define WSREP_CLIENT(thd) \ + (WSREP(thd) && thd->wsrep_client_thread) + +#define WSREP_EMULATE_BINLOG(thd) \ + (WSREP(thd) && wsrep_emulate_bin_log) + +#define WSREP_FORMAT(my_format) \ + ((wsrep_forced_binlog_format != BINLOG_FORMAT_UNSPEC) \ + ? wsrep_forced_binlog_format : (ulong)(my_format)) + +// prefix all messages with "WSREP" +#define WSREP_LOG(fun, ...) \ + do { \ + char msg[1024] = {'\0'}; \ + snprintf(msg, sizeof(msg) - 1, ## __VA_ARGS__); \ + fun("WSREP: %s", msg); \ + } while(0) + +#define WSREP_LOG_CONFLICT_THD(thd, role) \ + WSREP_LOG(sql_print_information, \ + "%s: \n " \ + " THD: %lu, mode: %s, state: %s, conflict: %s, seqno: %lld\n " \ + " SQL: %s", \ + role, thd_get_thread_id(thd), wsrep_thd_exec_mode_str(thd), \ + wsrep_thd_query_state_str(thd), \ + wsrep_thd_conflict_state_str(thd), (long long)wsrep_thd_trx_seqno(thd), \ + wsrep_thd_query(thd) \ + ); + +#define WSREP_LOG_CONFLICT(bf_thd, victim_thd, bf_abort) \ + if (wsrep_debug || wsrep_log_conflicts) \ + { \ + WSREP_LOG(sql_print_information, "cluster conflict due to %s for threads:",\ + (bf_abort) ? "high priority abort" : "certification failure" \ + ); \ + if (bf_thd != NULL) WSREP_LOG_CONFLICT_THD(bf_thd, "Winning thread"); \ + if (victim_thd) WSREP_LOG_CONFLICT_THD(victim_thd, "Victim thread"); \ + } + +#define WSREP_PROVIDER_EXISTS \ + (wsrep_provider && strncasecmp(wsrep_provider, WSREP_NONE, FN_REFLEN)) + +#define WSREP_QUERY(thd) (thd->query()) + +extern my_bool wsrep_ready_get(); +extern void wsrep_ready_wait(); + +class Ha_trx_info; +struct THD_TRANS; +void wsrep_register_hton(THD* thd, bool all); +void wsrep_brute_force_killer(THD *thd); +int wsrep_hire_brute_force_killer(THD *thd, uint64_t trx_id); + +/* this is visible for client build so that innodb plugin gets this */ +typedef struct wsrep_aborting_thd { + struct wsrep_aborting_thd *next; + THD *aborting_thd; +} *wsrep_aborting_thd_t; + +extern mysql_mutex_t LOCK_wsrep_ready; +extern mysql_cond_t COND_wsrep_ready; +extern mysql_mutex_t LOCK_wsrep_sst; +extern mysql_cond_t COND_wsrep_sst; +extern mysql_mutex_t LOCK_wsrep_sst_init; +extern mysql_cond_t COND_wsrep_sst_init; +extern mysql_mutex_t LOCK_wsrep_rollback; +extern mysql_cond_t COND_wsrep_rollback; +extern int wsrep_replaying; +extern mysql_mutex_t LOCK_wsrep_replaying; +extern mysql_cond_t COND_wsrep_replaying; +extern mysql_mutex_t LOCK_wsrep_slave_threads; +extern mysql_mutex_t LOCK_wsrep_desync; +extern mysql_mutex_t LOCK_wsrep_config_state; +extern wsrep_aborting_thd_t wsrep_aborting_thd; +extern my_bool wsrep_emulate_bin_log; +extern int wsrep_to_isolation; +#ifdef GTID_SUPPORT +extern rpl_sidno wsrep_sidno; +#endif /* GTID_SUPPORT */ +extern my_bool wsrep_preordered_opt; +extern handlerton *wsrep_hton; + +#ifdef HAVE_PSI_INTERFACE +extern PSI_mutex_key key_LOCK_wsrep_ready; +extern PSI_mutex_key key_COND_wsrep_ready; +extern PSI_mutex_key key_LOCK_wsrep_sst; +extern PSI_cond_key key_COND_wsrep_sst; +extern PSI_mutex_key key_LOCK_wsrep_sst_init; +extern PSI_cond_key key_COND_wsrep_sst_init; +extern PSI_mutex_key key_LOCK_wsrep_sst_thread; +extern PSI_cond_key key_COND_wsrep_sst_thread; +extern PSI_mutex_key key_LOCK_wsrep_rollback; +extern PSI_cond_key key_COND_wsrep_rollback; +extern PSI_mutex_key key_LOCK_wsrep_replaying; +extern PSI_cond_key key_COND_wsrep_replaying; +extern PSI_mutex_key key_LOCK_wsrep_slave_threads; +extern PSI_mutex_key key_LOCK_wsrep_desync; + +extern PSI_file_key key_file_wsrep_gra_log; +#endif /* HAVE_PSI_INTERFACE */ +struct TABLE_LIST; +class Alter_info; +int wsrep_to_isolation_begin(THD *thd, char *db_, char *table_, + const TABLE_LIST* table_list, + Alter_info* alter_info = NULL); +void wsrep_to_isolation_end(THD *thd); +void wsrep_cleanup_transaction(THD *thd); +int wsrep_to_buf_helper( + THD* thd, const char *query, uint query_len, uchar** buf, size_t* buf_len); +int wsrep_create_event_query(THD *thd, uchar** buf, size_t* buf_len); + +extern bool +wsrep_grant_mdl_exception(MDL_context *requestor_ctx, + MDL_ticket *ticket, + const MDL_key *key); +IO_CACHE * get_trans_log(THD * thd); +bool wsrep_trans_cache_is_empty(THD *thd); +void thd_binlog_flush_pending_rows_event(THD *thd, bool stmt_end); +void thd_binlog_rollback_stmt(THD * thd); +void thd_binlog_trx_reset(THD * thd); + +typedef void (*wsrep_thd_processor_fun)(THD *); +pthread_handler_t start_wsrep_THD(void *arg); +int wsrep_wait_committing_connections_close(int wait_time); +extern void wsrep_close_client_connections(my_bool wait_to_end, + THD *except_caller_thd = NULL); +void wsrep_close_applier(THD *thd); +void wsrep_close_applier_threads(int count); +void wsrep_wait_appliers_close(THD *thd); +void wsrep_kill_mysql(THD *thd); +void wsrep_close_threads(THD *thd); +void wsrep_copy_query(THD *thd); +bool wsrep_is_show_query(enum enum_sql_command command); +void wsrep_replay_transaction(THD *thd); +bool wsrep_create_like_table(THD* thd, TABLE_LIST* table, + TABLE_LIST* src_table, + HA_CREATE_INFO *create_info); +bool wsrep_node_is_donor(); +bool wsrep_node_is_synced(); + +typedef struct wsrep_key_arr +{ + wsrep_key_t* keys; + size_t keys_len; +} wsrep_key_arr_t; +bool wsrep_prepare_keys_for_isolation(THD* thd, + const char* db, + const char* table, + const TABLE_LIST* table_list, + wsrep_key_arr_t* ka); +void wsrep_keys_free(wsrep_key_arr_t* key_arr); + +#else /* WITH_WSREP */ + +#define WSREP(T) (0) +#define WSREP_ON (0) +#define WSREP_EMULATE_BINLOG(thd) (0) +#define WSREP_CLIENT(thd) (0) +#define WSREP_FORMAT(my_format) ((ulong)my_format) +#define WSREP_PROVIDER_EXISTS (0) +#define wsrep_emulate_bin_log (0) +#define wsrep_xid_seqno(X) (0) +#define wsrep_to_isolation (0) +#define wsrep_init() (1) +#define wsrep_prepend_PATH(X) +#define wsrep_before_SE() (0) +#define wsrep_init_startup(X) +#define wsrep_must_sync_wait(...) (0) +#define wsrep_sync_wait(...) (0) +#define wsrep_to_isolation_begin(...) (0) +#define wsrep_register_hton(...) do { } while(0) +#define wsrep_check_opts() (0) +#define wsrep_stop_replication(X) do { } while(0) +#define wsrep_inited (0) +#define wsrep_deinit(X) do { } while(0) +#define wsrep_recover() do { } while(0) +#define wsrep_slave_threads (1) +#define wsrep_replicate_myisam (0) +#define wsrep_thr_init() do {} while(0) +#define wsrep_thr_deinit() do {} while(0) +#define wsrep_running_threads (0) +#endif /* WITH_WSREP */ +#endif /* WSREP_MYSQLD_H */ diff --git a/sql/wsrep_mysqld_c.h b/sql/wsrep_mysqld_c.h new file mode 100644 index 00000000000..235a871c113 --- /dev/null +++ b/sql/wsrep_mysqld_c.h @@ -0,0 +1,30 @@ +/* Copyright 2018-2018 Codership Oy <http://www.codership.com> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef WSREP_MYSQLD_C_H +#define WSREP_MYSQLD_C_H + +enum enum_wsrep_certification_rules { + WSREP_CERTIFICATION_RULES_STRICT, + WSREP_CERTIFICATION_RULES_OPTIMIZED +}; + +/* This is intentionally declared as a weak global symbol, so that +the same ha_innodb.so can be used with the embedded server +(which does not link to the definition of this variable) +and with the regular server built WITH_WSREP. */ +extern ulong wsrep_certification_rules __attribute__((weak)); + +#endif /* WSREP_MYSQLD_C_H */ diff --git a/sql/wsrep_notify.cc b/sql/wsrep_notify.cc new file mode 100644 index 00000000000..20cc9111a72 --- /dev/null +++ b/sql/wsrep_notify.cc @@ -0,0 +1,111 @@ +/* Copyright 2010 Codership Oy <http://www.codership.com> + + 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 02111-1301 USA */ + +#include <mysqld.h> +#include "wsrep_priv.h" +#include "wsrep_utils.h" + +const char* wsrep_notify_cmd=""; + +static const char* _status_str(wsrep_member_status_t status) +{ + switch (status) + { + case WSREP_MEMBER_UNDEFINED: return "Undefined"; + case WSREP_MEMBER_JOINER: return "Joiner"; + case WSREP_MEMBER_DONOR: return "Donor"; + case WSREP_MEMBER_JOINED: return "Joined"; + case WSREP_MEMBER_SYNCED: return "Synced"; + default: return "Error(?)"; + } +} + +void wsrep_notify_status (wsrep_member_status_t status, + const wsrep_view_info_t* view) +{ + if (!wsrep_notify_cmd || 0 == strlen(wsrep_notify_cmd)) + { + WSREP_INFO("wsrep_notify_cmd is not defined, skipping notification."); + return; + } + + char cmd_buf[1 << 16]; // this can be long + long cmd_len = sizeof(cmd_buf) - 1; + char* cmd_ptr = cmd_buf; + long cmd_off = 0; + + cmd_off += snprintf (cmd_ptr + cmd_off, cmd_len - cmd_off, "%s", + wsrep_notify_cmd); + + if (status >= WSREP_MEMBER_UNDEFINED && status < WSREP_MEMBER_ERROR) + { + cmd_off += snprintf (cmd_ptr + cmd_off, cmd_len - cmd_off, " --status %s", + _status_str(status)); + } + else + { + /* here we preserve provider error codes */ + cmd_off += snprintf (cmd_ptr + cmd_off, cmd_len - cmd_off, + " --status 'Error(%d)'", status); + } + + if (0 != view) + { + char uuid_str[40]; + + wsrep_uuid_print (&view->state_id.uuid, uuid_str, sizeof(uuid_str)); + cmd_off += snprintf (cmd_ptr + cmd_off, cmd_len - cmd_off, + " --uuid %s", uuid_str); + + cmd_off += snprintf (cmd_ptr + cmd_off, cmd_len - cmd_off, + " --primary %s", view->view >= 0 ? "yes" : "no"); + + cmd_off += snprintf (cmd_ptr + cmd_off, cmd_len - cmd_off, + " --index %d", view->my_idx); + + if (view->memb_num) + { + cmd_off += snprintf (cmd_ptr + cmd_off, cmd_len - cmd_off, " --members"); + + for (int i = 0; i < view->memb_num; i++) + { + wsrep_uuid_print (&view->members[i].id, uuid_str, sizeof(uuid_str)); + cmd_off += snprintf (cmd_ptr + cmd_off, cmd_len - cmd_off, + "%c%s/%s/%s", i > 0 ? ',' : ' ', + uuid_str, view->members[i].name, + view->members[i].incoming); + } + } + } + + if (cmd_off == cmd_len) + { + WSREP_ERROR("Notification buffer too short (%ld). Aborting notification.", + cmd_len); + return; + } + + wsp::process p(cmd_ptr, "r", NULL); + + p.wait(); + int err = p.error(); + + if (err) + { + WSREP_ERROR("Notification command failed: %d (%s): \"%s\"", + err, strerror(err), cmd_ptr); + } +} + diff --git a/sql/wsrep_priv.h b/sql/wsrep_priv.h new file mode 100644 index 00000000000..b00bfda16fa --- /dev/null +++ b/sql/wsrep_priv.h @@ -0,0 +1,54 @@ +/* Copyright 2010 Codership Oy <http://www.codership.com> + + 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 02111-1301 USA + */ + +//! @file declares symbols private to wsrep integration layer + +#ifndef WSREP_PRIV_H +#define WSREP_PRIV_H + +#include "wsrep_mysqld.h" +#include "../wsrep/wsrep_api.h" + +#include <log.h> +#include <pthread.h> +#include <cstdio> + +my_bool wsrep_ready_set (my_bool x); + +ssize_t wsrep_sst_prepare (void** msg); +wsrep_cb_status wsrep_sst_donate_cb (void* app_ctx, + void* recv_ctx, + const void* msg, size_t msg_len, + const wsrep_gtid_t* state_id, + const char* state, size_t state_len, + bool bypass); + +extern wsrep_uuid_t local_uuid; +extern wsrep_seqno_t local_seqno; + +// a helper function +void wsrep_sst_received (wsrep_t* const wsrep, + const wsrep_uuid_t& uuid, + wsrep_seqno_t const segno, + const void * const state, + size_t const state_len); +/*! SST thread signals init thread about sst completion */ +void wsrep_sst_complete(const wsrep_uuid_t*, wsrep_seqno_t, bool); + +void wsrep_notify_status (wsrep_member_status_t new_status, + const wsrep_view_info_t* view = 0); + +#endif /* WSREP_PRIV_H */ diff --git a/sql/wsrep_sst.cc b/sql/wsrep_sst.cc new file mode 100644 index 00000000000..a54b96a4aa2 --- /dev/null +++ b/sql/wsrep_sst.cc @@ -0,0 +1,1432 @@ +/* Copyright 2008-2015 Codership Oy <http://www.codership.com> + + 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 02111-1301 USA */ + +#include "wsrep_sst.h" + +#include <inttypes.h> +#include <mysqld.h> +#include <m_ctype.h> +#include <my_sys.h> +#include <strfunc.h> +#include <sql_class.h> +#include <set_var.h> +#include <sql_acl.h> +#include <sql_reload.h> +#include <sql_parse.h> +#include "wsrep_priv.h" +#include "wsrep_utils.h" +#include "wsrep_xid.h" +#include <cstdio> +#include <cstdlib> + +#if MYSQL_VERSION_ID < 100200 +# include <my_service_manager.h> +#endif + +static char wsrep_defaults_file[FN_REFLEN * 2 + 10 + 30 + + sizeof(WSREP_SST_OPT_CONF) + + sizeof(WSREP_SST_OPT_CONF_SUFFIX) + + sizeof(WSREP_SST_OPT_CONF_EXTRA)] = {0}; + +const char* wsrep_sst_method = WSREP_SST_DEFAULT; +const char* wsrep_sst_receive_address = WSREP_SST_ADDRESS_AUTO; +const char* wsrep_sst_donor = ""; + char* wsrep_sst_auth = NULL; + +// container for real auth string +static const char* sst_auth_real = NULL; +my_bool wsrep_sst_donor_rejects_queries = FALSE; + +bool wsrep_sst_method_check (sys_var *self, THD* thd, set_var* var) +{ + if ((! var->save_result.string_value.str) || + (var->save_result.string_value.length == 0 )) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str, + var->save_result.string_value.str ? + var->save_result.string_value.str : "NULL"); + return 1; + } + + return 0; +} + +bool wsrep_sst_method_update (sys_var *self, THD* thd, enum_var_type type) +{ + return 0; +} + +static const char* data_home_dir = NULL; + +void wsrep_set_data_home_dir(const char *data_dir) +{ + data_home_dir= (data_dir && *data_dir) ? data_dir : NULL; +} + +static void make_wsrep_defaults_file() +{ + if (!wsrep_defaults_file[0]) + { + char *ptr= wsrep_defaults_file; + char *end= ptr + sizeof(wsrep_defaults_file); + if (my_defaults_file) + ptr= strxnmov(ptr, end - ptr, + WSREP_SST_OPT_CONF, " '", my_defaults_file, "' ", NULL); + + if (my_defaults_extra_file) + ptr= strxnmov(ptr, end - ptr, + WSREP_SST_OPT_CONF_EXTRA, " '", my_defaults_extra_file, "' ", NULL); + + if (my_defaults_group_suffix) + ptr= strxnmov(ptr, end - ptr, + WSREP_SST_OPT_CONF_SUFFIX, " '", my_defaults_group_suffix, "' ", NULL); + } +} + + +bool wsrep_sst_receive_address_check (sys_var *self, THD* thd, set_var* var) +{ + if ((! var->save_result.string_value.str) || + (var->save_result.string_value.length > (FN_REFLEN - 1))) // safety + { + goto err; + } + + return 0; + +err: + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str, + var->save_result.string_value.str ? + var->save_result.string_value.str : "NULL"); + return 1; +} + +bool wsrep_sst_receive_address_update (sys_var *self, THD* thd, + enum_var_type type) +{ + return 0; +} + +bool wsrep_sst_auth_check (sys_var *self, THD* thd, set_var* var) +{ + return 0; +} + +static bool sst_auth_real_set (const char* value) +{ + const char* v= NULL; + + if (value) + { + v= my_strdup(value, MYF(0)); + } + else // its NULL + { + wsrep_sst_auth_free(); + return 0; + } + + if (v) + { + // set sst_auth_real + if (sst_auth_real) { my_free((void *) sst_auth_real); } + sst_auth_real = v; + + // mask wsrep_sst_auth + if (strlen(sst_auth_real)) + { + if (wsrep_sst_auth) { my_free((void*) wsrep_sst_auth); } + wsrep_sst_auth= my_strdup(WSREP_SST_AUTH_MASK, MYF(0)); + } + return 0; + } + return 1; +} + +void wsrep_sst_auth_free() +{ + if (wsrep_sst_auth) { my_free((void *) wsrep_sst_auth); } + if (sst_auth_real) { my_free((void *) sst_auth_real); } + wsrep_sst_auth= NULL; + sst_auth_real= NULL; +} + +bool wsrep_sst_auth_update (sys_var *self, THD* thd, enum_var_type type) +{ + return sst_auth_real_set (wsrep_sst_auth); +} + +void wsrep_sst_auth_init (const char* value) +{ + DBUG_ASSERT(wsrep_sst_auth == value); + sst_auth_real_set (wsrep_sst_auth); +} + +bool wsrep_sst_donor_check (sys_var *self, THD* thd, set_var* var) +{ + return 0; +} + +bool wsrep_sst_donor_update (sys_var *self, THD* thd, enum_var_type type) +{ + return 0; +} + +bool wsrep_before_SE() +{ + return (wsrep_provider != NULL + && strcmp (wsrep_provider, WSREP_NONE) + && strcmp (wsrep_sst_method, WSREP_SST_SKIP) + && strcmp (wsrep_sst_method, WSREP_SST_MYSQLDUMP)); +} + +static bool sst_complete = false; +static bool sst_needed = false; + +#define WSREP_EXTEND_TIMEOUT_INTERVAL 30 +#define WSREP_TIMEDWAIT_SECONDS 10 + +void wsrep_sst_grab () +{ + WSREP_INFO("wsrep_sst_grab()"); + if (mysql_mutex_lock (&LOCK_wsrep_sst)) abort(); + sst_complete = false; + mysql_mutex_unlock (&LOCK_wsrep_sst); +} + +// Wait for end of SST +bool wsrep_sst_wait () +{ + double total_wtime = 0; + + if (mysql_mutex_lock (&LOCK_wsrep_sst)) + abort(); + + WSREP_INFO("Waiting for SST to complete."); + + while (!sst_complete) + { + struct timespec wtime; + set_timespec(wtime, WSREP_TIMEDWAIT_SECONDS); + time_t start_time = time(NULL); + mysql_cond_timedwait (&COND_wsrep_sst, &LOCK_wsrep_sst, &wtime); + time_t end_time = time(NULL); + + if (!sst_complete) + { + total_wtime += difftime(end_time, start_time); + WSREP_DEBUG("Waiting for SST to complete. current seqno: %" PRId64 " waited %f secs.", local_seqno, total_wtime); + service_manager_extend_timeout(WSREP_EXTEND_TIMEOUT_INTERVAL, + "WSREP state transfer ongoing, current seqno: %" PRId64 " waited %f secs", local_seqno, total_wtime); + } + } + + if (local_seqno >= 0) + { + WSREP_INFO("SST complete, seqno: %lld", (long long) local_seqno); + } + else + { + WSREP_ERROR("SST failed: %d (%s)", + int(-local_seqno), strerror(-local_seqno)); + } + + mysql_mutex_unlock (&LOCK_wsrep_sst); + + return (local_seqno >= 0); +} + +// Signal end of SST +void wsrep_sst_complete (const wsrep_uuid_t* sst_uuid, + wsrep_seqno_t sst_seqno, + bool needed) +{ + if (mysql_mutex_lock (&LOCK_wsrep_sst)) abort(); + if (!sst_complete) + { + sst_complete = true; + sst_needed = needed; + local_uuid = *sst_uuid; + local_seqno = sst_seqno; + mysql_cond_signal (&COND_wsrep_sst); + } + else + { + /* This can happen when called from wsrep_synced_cb(). + At the moment there is no way to check there + if main thread is still waiting for signal, + so wsrep_sst_complete() is called from there + each time wsrep_ready changes from FALSE -> TRUE. + */ + WSREP_DEBUG("Nobody is waiting for SST."); + } + mysql_mutex_unlock (&LOCK_wsrep_sst); +} + +void wsrep_sst_received (wsrep_t* const wsrep, + const wsrep_uuid_t& uuid, + wsrep_seqno_t const seqno, + const void* const state, + size_t const state_len) +{ + wsrep_get_SE_checkpoint(local_uuid, local_seqno); + + if (memcmp(&local_uuid, &uuid, sizeof(wsrep_uuid_t)) || + local_seqno < seqno || seqno < 0) + { + wsrep_set_SE_checkpoint(uuid, seqno); + local_uuid = uuid; + local_seqno = seqno; + } + else if (local_seqno > seqno) + { + WSREP_WARN("SST postion is in the past: %" PRId64 ", current: %" PRId64 + ". Can't continue.", seqno, local_seqno); + unireg_abort(1); + } + +#ifdef GTID_SUPPORT + wsrep_init_sidno(uuid); +#endif /* GTID_SUPPORT */ + + if (wsrep) + { + int const rcode(seqno < 0 ? seqno : 0); + wsrep_gtid_t const state_id = { + uuid, (rcode ? WSREP_SEQNO_UNDEFINED : seqno) + }; + + wsrep->sst_received(wsrep, &state_id, state, state_len, rcode); + } +} + +// Let applier threads to continue +void wsrep_sst_continue () +{ + if (sst_needed) + { + WSREP_INFO("Signalling provider to continue."); + wsrep_sst_received (wsrep, local_uuid, local_seqno, NULL, 0); + } +} + +struct sst_thread_arg +{ + const char* cmd; + char** env; + char* ret_str; + int err; + mysql_mutex_t lock; + mysql_cond_t cond; + + sst_thread_arg (const char* c, char** e) + : cmd(c), env(e), ret_str(0), err(-1) + { + mysql_mutex_init(key_LOCK_wsrep_sst_thread, &lock, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_wsrep_sst_thread, &cond, NULL); + } + + ~sst_thread_arg() + { + mysql_cond_destroy (&cond); + mysql_mutex_unlock (&lock); + mysql_mutex_destroy (&lock); + } +}; + +static int sst_scan_uuid_seqno (const char* str, + wsrep_uuid_t* uuid, wsrep_seqno_t* seqno) +{ + int offt = wsrep_uuid_scan (str, strlen(str), uuid); + errno= 0; /* Reset the errno */ + if (offt > 0 && strlen(str) > (unsigned int)offt && ':' == str[offt]) + { + *seqno = strtoll (str + offt + 1, NULL, 10); + if (*seqno != LLONG_MAX || errno != ERANGE) + { + return 0; + } + } + + WSREP_ERROR("Failed to parse uuid:seqno pair: '%s'", str); + return EINVAL; +} + +// get rid of trailing \n +static char* my_fgets (char* buf, size_t buf_len, FILE* stream) +{ + char* ret= fgets (buf, buf_len, stream); + + if (ret) + { + size_t len = strlen(ret); + if (len > 0 && ret[len - 1] == '\n') ret[len - 1] = '\0'; + } + + return ret; +} + +/* + Generate opt_binlog_opt_val for sst_donate_other(), sst_prepare_other(). + + Returns zero on success, negative error code otherwise. + + String containing binlog name is stored in param ret if binlog is enabled + and GTID mode is on, otherwise empty string. Returned string should be + freed with my_free(). + */ +static int generate_binlog_opt_val(char** ret) +{ + DBUG_ASSERT(ret); + *ret= NULL; + if (opt_bin_log) + { + assert(opt_bin_logname); + *ret= strcmp(opt_bin_logname, "0") ? + my_strdup(opt_bin_logname, MYF(0)) : my_strdup("", MYF(0)); + } + else + { + *ret= my_strdup("", MYF(0)); + } + if (!*ret) return -ENOMEM; + return 0; +} + +static int generate_binlog_index_opt_val(char** ret) +{ + DBUG_ASSERT(ret); + *ret= NULL; + if (opt_binlog_index_name) { + *ret= strcmp(opt_binlog_index_name, "0") ? + my_strdup(opt_binlog_index_name, MYF(0)) : my_strdup("", MYF(0)); + } + else + { + *ret= my_strdup("", MYF(0)); + } + if (!*ret) return -ENOMEM; + return 0; +} + +static void* sst_joiner_thread (void* a) +{ + sst_thread_arg* arg= (sst_thread_arg*) a; + int err= 1; + + { + const char magic[] = "ready"; + const size_t magic_len = sizeof(magic) - 1; + const size_t out_len = 512; + char out[out_len]; + + WSREP_INFO("Running: '%s'", arg->cmd); + + wsp::process proc (arg->cmd, "r", arg->env); + + if (proc.pipe() && !proc.error()) + { + const char* tmp= my_fgets (out, out_len, proc.pipe()); + + if (!tmp || strlen(tmp) < (magic_len + 2) || + strncasecmp (tmp, magic, magic_len)) + { + WSREP_ERROR("Failed to read '%s <addr>' from: %s\n\tRead: '%s'", + magic, arg->cmd, tmp); + proc.wait(); + if (proc.error()) err = proc.error(); + } + else + { + err = 0; + } + } + else + { + err = proc.error(); + WSREP_ERROR("Failed to execute: %s : %d (%s)", + arg->cmd, err, strerror(err)); + } + + // signal sst_prepare thread with ret code, + // it will go on sending SST request + mysql_mutex_lock (&arg->lock); + if (!err) + { + arg->ret_str = strdup (out + magic_len + 1); + if (!arg->ret_str) err = ENOMEM; + } + arg->err = -err; + mysql_cond_signal (&arg->cond); + mysql_mutex_unlock (&arg->lock); //! @note arg is unusable after that. + + if (err) return NULL; /* lp:808417 - return immediately, don't signal + * initializer thread to ensure single thread of + * shutdown. */ + + wsrep_uuid_t ret_uuid = WSREP_UUID_UNDEFINED; + wsrep_seqno_t ret_seqno = WSREP_SEQNO_UNDEFINED; + + // in case of successfull receiver start, wait for SST completion/end + char* tmp = my_fgets (out, out_len, proc.pipe()); + + proc.wait(); + err= EINVAL; + + if (!tmp) + { + WSREP_ERROR("Failed to read uuid:seqno and wsrep_gtid_domain_id from " + "joiner script."); + if (proc.error()) err = proc.error(); + } + else + { + // Read state ID (UUID:SEQNO) followed by wsrep_gtid_domain_id (if any). + const char *pos= strchr(out, ' '); + + if (!pos) { + // There is no wsrep_gtid_domain_id (some older version SST script?). + err= sst_scan_uuid_seqno (out, &ret_uuid, &ret_seqno); + + } else { + // Scan state ID first followed by wsrep_gtid_domain_id. + unsigned long int domain_id; + + // Null-terminate the state-id. + out[pos - out]= 0; + err= sst_scan_uuid_seqno (out, &ret_uuid, &ret_seqno); + + if (err) + { + goto err; + } + else if (wsrep_gtid_mode) + { + errno= 0; /* Reset the errno */ + domain_id= strtoul(pos + 1, NULL, 10); + err= errno; + + /* Check if we received a valid gtid_domain_id. */ + if (err == EINVAL || err == ERANGE) + { + WSREP_ERROR("Failed to get donor wsrep_gtid_domain_id."); + err= EINVAL; + goto err; + } else { + wsrep_gtid_domain_id= (uint32) domain_id; + } + } + } + } + +err: + + if (err) + { + ret_uuid= WSREP_UUID_UNDEFINED; + ret_seqno= -err; + } + + // Tell initializer thread that SST is complete + wsrep_sst_complete (&ret_uuid, ret_seqno, true); + } + + return NULL; +} + +#define WSREP_SST_AUTH_ENV "WSREP_SST_OPT_AUTH" + +static int sst_append_auth_env(wsp::env& env, const char* sst_auth) +{ + int const sst_auth_size= strlen(WSREP_SST_AUTH_ENV) + 1 /* = */ + + (sst_auth ? strlen(sst_auth) : 0) + 1 /* \0 */; + + wsp::string sst_auth_str(sst_auth_size); // for automatic cleanup on return + if (!sst_auth_str()) return -ENOMEM; + + int ret= snprintf(sst_auth_str(), sst_auth_size, "%s=%s", + WSREP_SST_AUTH_ENV, sst_auth ? sst_auth : ""); + + if (ret < 0 || ret >= sst_auth_size) + { + WSREP_ERROR("sst_append_auth_env(): snprintf() failed: %d", ret); + return (ret < 0 ? ret : -EMSGSIZE); + } + + env.append(sst_auth_str()); + return -env.error(); +} + +#define DATA_HOME_DIR_ENV "INNODB_DATA_HOME_DIR" + +static int sst_append_data_dir(wsp::env& env, const char* data_dir) +{ + int const data_dir_size= strlen(DATA_HOME_DIR_ENV) + 1 /* = */ + + (data_dir ? strlen(data_dir) : 0) + 1 /* \0 */; + + wsp::string data_dir_str(data_dir_size); // for automatic cleanup on return + if (!data_dir_str()) return -ENOMEM; + + int ret= snprintf(data_dir_str(), data_dir_size, "%s=%s", + DATA_HOME_DIR_ENV, data_dir ? data_dir : ""); + + if (ret < 0 || ret >= data_dir_size) + { + WSREP_ERROR("sst_append_data_dir(): snprintf() failed: %d", ret); + return (ret < 0 ? ret : -EMSGSIZE); + } + + env.append(data_dir_str()); + return -env.error(); +} + +static ssize_t sst_prepare_other (const char* method, + const char* sst_auth, + const char* addr_in, + const char** addr_out) +{ + int const cmd_len= 4096; + wsp::string cmd_str(cmd_len); + + if (!cmd_str()) + { + WSREP_ERROR("sst_prepare_other(): could not allocate cmd buffer of %d bytes", + cmd_len); + return -ENOMEM; + } + + const char* binlog_opt= ""; + const char* binlog_index_opt= ""; + char* binlog_opt_val= NULL; + char* binlog_index_opt_val= NULL; + + int ret; + if ((ret= generate_binlog_opt_val(&binlog_opt_val))) + { + WSREP_ERROR("sst_prepare_other(): generate_binlog_opt_val() failed: %d", + ret); + return ret; + } + + if ((ret= generate_binlog_index_opt_val(&binlog_index_opt_val))) + { + WSREP_ERROR("sst_prepare_other(): generate_binlog_index_opt_val() failed %d", + ret); + } + + if (strlen(binlog_opt_val)) binlog_opt= WSREP_SST_OPT_BINLOG; + if (strlen(binlog_index_opt_val)) binlog_index_opt= WSREP_SST_OPT_BINLOG_INDEX; + + make_wsrep_defaults_file(); + + ret= snprintf (cmd_str(), cmd_len, + "wsrep_sst_%s " + WSREP_SST_OPT_ROLE " 'joiner' " + WSREP_SST_OPT_ADDR " '%s' " + WSREP_SST_OPT_DATA " '%s' " + " %s " + WSREP_SST_OPT_PARENT " '%d'" + " %s '%s'" + " %s '%s'", + method, addr_in, mysql_real_data_home, + wsrep_defaults_file, + (int)getpid(), binlog_opt, binlog_opt_val, + binlog_index_opt, binlog_index_opt_val); + my_free(binlog_opt_val); + my_free(binlog_index_opt_val); + + if (ret < 0 || ret >= cmd_len) + { + WSREP_ERROR("sst_prepare_other(): snprintf() failed: %d", ret); + return (ret < 0 ? ret : -EMSGSIZE); + } + + wsp::env env(NULL); + if (env.error()) + { + WSREP_ERROR("sst_prepare_other(): env. var ctor failed: %d", -env.error()); + return -env.error(); + } + + if ((ret= sst_append_auth_env(env, sst_auth))) + { + WSREP_ERROR("sst_prepare_other(): appending auth failed: %d", ret); + return ret; + } + + if (data_home_dir) + { + if ((ret= sst_append_data_dir(env, data_home_dir))) + { + WSREP_ERROR("sst_prepare_other(): appending data " + "directory failed: %d", ret); + return ret; + } + } + + pthread_t tmp; + sst_thread_arg arg(cmd_str(), env()); + mysql_mutex_lock (&arg.lock); + ret = pthread_create (&tmp, NULL, sst_joiner_thread, &arg); + if (ret) + { + WSREP_ERROR("sst_prepare_other(): pthread_create() failed: %d (%s)", + ret, strerror(ret)); + return -ret; + } + mysql_cond_wait (&arg.cond, &arg.lock); + + *addr_out= arg.ret_str; + + if (!arg.err) + ret = strlen(*addr_out); + else + { + assert (arg.err < 0); + ret = arg.err; + } + + pthread_detach (tmp); + + return ret; +} + +extern uint mysqld_port; + +/*! Just tells donor where to send mysqldump */ +static ssize_t sst_prepare_mysqldump (const char* addr_in, + const char** addr_out) +{ + ssize_t ret = strlen (addr_in); + + if (!strrchr(addr_in, ':')) + { + ssize_t s = ret + 7; + char* tmp = (char*) malloc (s); + + if (tmp) + { + ret= snprintf (tmp, s, "%s:%u", addr_in, mysqld_port); + + if (ret > 0 && ret < s) + { + *addr_out= tmp; + return ret; + } + if (ret > 0) /* buffer too short */ ret = -EMSGSIZE; + free (tmp); + } + else { + ret= -ENOMEM; + } + + WSREP_ERROR ("Could not prepare state transfer request: " + "adding default port failed: %zd.", ret); + } + else { + *addr_out= addr_in; + } + + return ret; +} + +static bool SE_initialized = false; + +ssize_t wsrep_sst_prepare (void** msg) +{ + const char* addr_in= NULL; + const char* addr_out= NULL; + + if (!strcmp(wsrep_sst_method, WSREP_SST_SKIP)) + { + ssize_t ret = strlen(WSREP_STATE_TRANSFER_TRIVIAL) + 1; + *msg = strdup(WSREP_STATE_TRANSFER_TRIVIAL); + if (!msg) + { + WSREP_ERROR("Could not allocate %zd bytes for state request", ret); + unireg_abort(1); + } + return ret; + } + + /* + Figure out SST receive address. Common for all SST methods. + */ + char ip_buf[256]; + const ssize_t ip_max= sizeof(ip_buf); + + // Attempt 1: wsrep_sst_receive_address + if (wsrep_sst_receive_address && + strcmp (wsrep_sst_receive_address, WSREP_SST_ADDRESS_AUTO)) + { + addr_in= wsrep_sst_receive_address; + } + + //Attempt 2: wsrep_node_address + else if (wsrep_node_address && strlen(wsrep_node_address)) + { + wsp::Address addr(wsrep_node_address); + + if (!addr.is_valid()) + { + WSREP_ERROR("Could not parse wsrep_node_address : %s", + wsrep_node_address); + unireg_abort(1); + } + memcpy(ip_buf, addr.get_address(), addr.get_address_len()); + addr_in= ip_buf; + } + // Attempt 3: Try to get the IP from the list of available interfaces. + else + { + ssize_t ret= wsrep_guess_ip (ip_buf, ip_max); + + if (ret && ret < ip_max) + { + addr_in= ip_buf; + } + else + { + WSREP_ERROR("Failed to guess address to accept state transfer. " + "wsrep_sst_receive_address must be set manually."); + unireg_abort(1); + } + } + + ssize_t addr_len= -ENOSYS; + if (!strcmp(wsrep_sst_method, WSREP_SST_MYSQLDUMP)) + { + addr_len= sst_prepare_mysqldump (addr_in, &addr_out); + if (addr_len < 0) unireg_abort(1); + } + else + { + /*! A heuristic workaround until we learn how to stop and start engines */ + if (SE_initialized) + { + // we already did SST at initializaiton, now engines are running + // sql_print_information() is here because the message is too long + // for WSREP_INFO. + sql_print_information ("WSREP: " + "You have configured '%s' state snapshot transfer method " + "which cannot be performed on a running server. " + "Wsrep provider won't be able to fall back to it " + "if other means of state transfer are unavailable. " + "In that case you will need to restart the server.", + wsrep_sst_method); + *msg = 0; + return 0; + } + + addr_len = sst_prepare_other (wsrep_sst_method, sst_auth_real, + addr_in, &addr_out); + if (addr_len < 0) + { + WSREP_ERROR("Failed to prepare for '%s' SST. Unrecoverable.", + wsrep_sst_method); + unireg_abort(1); + } + } + + size_t const method_len(strlen(wsrep_sst_method)); + size_t const msg_len (method_len + addr_len + 2 /* + auth_len + 1*/); + + *msg = malloc (msg_len); + if (NULL != *msg) { + char* const method_ptr(reinterpret_cast<char*>(*msg)); + strcpy (method_ptr, wsrep_sst_method); + char* const addr_ptr(method_ptr + method_len + 1); + strcpy (addr_ptr, addr_out); + + WSREP_INFO ("Prepared SST request: %s|%s", method_ptr, addr_ptr); + } + else { + WSREP_ERROR("Failed to allocate SST request of size %zu. Can't continue.", + msg_len); + unireg_abort(1); + } + + if (addr_out != addr_in) /* malloc'ed */ free ((char*)addr_out); + + return msg_len; +} + +// helper method for donors +static int sst_run_shell (const char* cmd_str, char** env, int max_tries) +{ + int ret = 0; + + for (int tries=1; tries <= max_tries; tries++) + { + wsp::process proc (cmd_str, "r", env); + + if (NULL != proc.pipe()) + { + proc.wait(); + } + + if ((ret = proc.error())) + { + WSREP_ERROR("Try %d/%d: '%s' failed: %d (%s)", + tries, max_tries, proc.cmd(), ret, strerror(ret)); + sleep (1); + } + else + { + WSREP_DEBUG("SST script successfully completed."); + break; + } + } + + return -ret; +} + +static void sst_reject_queries(my_bool close_conn) +{ + wsrep_ready_set (FALSE); // this will be resotred when donor becomes synced + WSREP_INFO("Rejecting client queries for the duration of SST."); + if (TRUE == close_conn) wsrep_close_client_connections(FALSE); +} + +static int sst_donate_mysqldump (const char* addr, + const wsrep_uuid_t* uuid, + const char* uuid_str, + wsrep_seqno_t seqno, + bool bypass, + char** env) // carries auth info +{ + char host[256]; + wsp::Address address(addr); + if (!address.is_valid()) + { + WSREP_ERROR("Could not parse SST address : %s", addr); + return 0; + } + memcpy(host, address.get_address(), address.get_address_len()); + int port= address.get_port(); + int const cmd_len= 4096; + wsp::string cmd_str(cmd_len); + + if (!cmd_str()) + { + WSREP_ERROR("sst_donate_mysqldump(): " + "could not allocate cmd buffer of %d bytes", cmd_len); + return -ENOMEM; + } + + if (!bypass && wsrep_sst_donor_rejects_queries) sst_reject_queries(TRUE); + + make_wsrep_defaults_file(); + + int ret= snprintf (cmd_str(), cmd_len, + "wsrep_sst_mysqldump " + WSREP_SST_OPT_ADDR " '%s' " + WSREP_SST_OPT_PORT " '%d' " + WSREP_SST_OPT_LPORT " '%u' " + WSREP_SST_OPT_SOCKET " '%s' " + " %s " + WSREP_SST_OPT_GTID " '%s:%lld' " + WSREP_SST_OPT_GTID_DOMAIN_ID " '%d'" + "%s", + addr, port, mysqld_port, mysqld_unix_port, + wsrep_defaults_file, uuid_str, + (long long)seqno, wsrep_gtid_domain_id, + bypass ? " " WSREP_SST_OPT_BYPASS : ""); + + if (ret < 0 || ret >= cmd_len) + { + WSREP_ERROR("sst_donate_mysqldump(): snprintf() failed: %d", ret); + return (ret < 0 ? ret : -EMSGSIZE); + } + + WSREP_DEBUG("Running: '%s'", cmd_str()); + + ret= sst_run_shell (cmd_str(), env, 3); + + wsrep_gtid_t const state_id = { *uuid, (ret ? WSREP_SEQNO_UNDEFINED : seqno)}; + + wsrep->sst_sent (wsrep, &state_id, ret); + + return ret; +} + +wsrep_seqno_t wsrep_locked_seqno= WSREP_SEQNO_UNDEFINED; + + +/* + Create a file under data directory. +*/ +static int sst_create_file(const char *name, const char *content) +{ + int err= 0; + char *real_name; + char *tmp_name; + ssize_t len; + FILE *file; + + len= strlen(mysql_real_data_home) + strlen(name) + 2; + real_name= (char *) alloca(len); + + snprintf(real_name, (size_t) len, "%s/%s", mysql_real_data_home, name); + + tmp_name= (char *) alloca(len + 4); + snprintf(tmp_name, (size_t) len + 4, "%s.tmp", real_name); + + file= fopen(tmp_name, "w+"); + + if (0 == file) + { + err= errno; + WSREP_ERROR("Failed to open '%s': %d (%s)", tmp_name, err, strerror(err)); + } + else + { + // Write the specified content into the file. + if (content != NULL) + { + fprintf(file, "%s\n", content); + fsync(fileno(file)); + } + + fclose(file); + + if (rename(tmp_name, real_name) == -1) + { + err= errno; + WSREP_ERROR("Failed to rename '%s' to '%s': %d (%s)", tmp_name, + real_name, err, strerror(err)); + } + } + + return err; +} + + +static int run_sql_command(THD *thd, const char *query) +{ + thd->set_query((char *)query, strlen(query)); + + Parser_state ps; + if (ps.init(thd, thd->query(), thd->query_length())) + { + WSREP_ERROR("SST query: %s failed", query); + return -1; + } + + mysql_parse(thd, thd->query(), thd->query_length(), &ps); + if (thd->is_error()) + { + int const err= thd->get_stmt_da()->sql_errno(); + WSREP_WARN ("Error executing '%s': %d (%s)%s", + query, err, thd->get_stmt_da()->message(), + err == ER_UNKNOWN_SYSTEM_VARIABLE ? + ". Was mysqld built with --with-innodb-disallow-writes ?" : ""); + thd->clear_error(); + return -1; + } + return 0; +} + + +static int sst_flush_tables(THD* thd) +{ + WSREP_INFO("Flushing tables for SST..."); + + int err= 0; + int not_used; + /* + Files created to notify the SST script about the outcome of table flush + operation. + */ + const char *flush_success= "tables_flushed"; + const char *flush_error= "sst_error"; + + CHARSET_INFO *current_charset= thd->variables.character_set_client; + + if (!is_supported_parser_charset(current_charset)) + { + /* Do not use non-supported parser character sets */ + WSREP_WARN("Current client character set is non-supported parser character set: %s", current_charset->csname); + thd->variables.character_set_client = &my_charset_latin1; + WSREP_WARN("For SST temporally setting character set to : %s", + my_charset_latin1.csname); + } + + if (run_sql_command(thd, "FLUSH TABLES WITH READ LOCK")) + { + err= -1; + } + else + { + /* + Make sure logs are flushed after global read lock acquired. In case + reload fails, we must also release the acquired FTWRL. + */ + if (reload_acl_and_cache(thd, REFRESH_ENGINE_LOG | REFRESH_BINARY_LOG, + (TABLE_LIST*) 0, ¬_used)) + { + thd->global_read_lock.unlock_global_read_lock(thd); + err= -1; + } + } + + thd->variables.character_set_client = current_charset; + + if (err) + { + WSREP_ERROR("Failed to flush and lock tables"); + + /* + The SST must be aborted as the flush tables failed. Notify this to SST + script by creating the error file. + */ + int tmp; + if ((tmp= sst_create_file(flush_error, NULL))) { + err= tmp; + } + } + else + { + WSREP_INFO("Tables flushed."); + + /* + Tables have been flushed. Create a file with cluster state ID and + wsrep_gtid_domain_id. + */ + char content[100]; + snprintf(content, sizeof(content), "%s:%lld %d\n", wsrep_cluster_state_uuid, + (long long)wsrep_locked_seqno, wsrep_gtid_domain_id); + err= sst_create_file(flush_success, content); + } + + return err; +} + + +static void sst_disallow_writes (THD* thd, bool yes) +{ + char query_str[64] = { 0, }; + ssize_t const query_max = sizeof(query_str) - 1; + CHARSET_INFO *current_charset; + + current_charset = thd->variables.character_set_client; + + if (!is_supported_parser_charset(current_charset)) + { + /* Do not use non-supported parser character sets */ + WSREP_WARN("Current client character set is non-supported parser character set: %s", current_charset->csname); + thd->variables.character_set_client = &my_charset_latin1; + WSREP_WARN("For SST temporally setting character set to : %s", + my_charset_latin1.csname); + } + + snprintf (query_str, query_max, "SET GLOBAL innodb_disallow_writes=%d", + yes ? 1 : 0); + + if (run_sql_command(thd, query_str)) + { + WSREP_ERROR("Failed to disallow InnoDB writes"); + } + thd->variables.character_set_client = current_charset; +} + +static void* sst_donor_thread (void* a) +{ + sst_thread_arg* arg= (sst_thread_arg*)a; + + WSREP_INFO("Running: '%s'", arg->cmd); + + int err= 1; + bool locked= false; + + const char* out= NULL; + const size_t out_len= 128; + char out_buf[out_len]; + + wsrep_uuid_t ret_uuid= WSREP_UUID_UNDEFINED; + wsrep_seqno_t ret_seqno= WSREP_SEQNO_UNDEFINED; // seqno of complete SST + + wsp::thd thd(FALSE); // we turn off wsrep_on for this THD so that it can + // operate with wsrep_ready == OFF + wsp::process proc(arg->cmd, "r", arg->env); + + err= proc.error(); + +/* Inform server about SST script startup and release TO isolation */ + mysql_mutex_lock (&arg->lock); + arg->err = -err; + mysql_cond_signal (&arg->cond); + mysql_mutex_unlock (&arg->lock); //! @note arg is unusable after that. + + if (proc.pipe() && !err) + { +wait_signal: + out= my_fgets (out_buf, out_len, proc.pipe()); + + if (out) + { + const char magic_flush[]= "flush tables"; + const char magic_cont[]= "continue"; + const char magic_done[]= "done"; + + if (!strcasecmp (out, magic_flush)) + { + err= sst_flush_tables (thd.ptr); + if (!err) + { + sst_disallow_writes (thd.ptr, true); + /* + Lets also keep statements that modify binary logs (like RESET LOGS, + RESET MASTER) from proceeding until the files have been transferred + to the joiner node. + */ + if (mysql_bin_log.is_open()) + { + mysql_mutex_lock(mysql_bin_log.get_log_lock()); + } + + locked= true; + goto wait_signal; + } + } + else if (!strcasecmp (out, magic_cont)) + { + if (locked) + { + if (mysql_bin_log.is_open()) + { + mysql_mutex_assert_owner(mysql_bin_log.get_log_lock()); + mysql_mutex_unlock(mysql_bin_log.get_log_lock()); + } + sst_disallow_writes (thd.ptr, false); + thd.ptr->global_read_lock.unlock_global_read_lock (thd.ptr); + locked= false; + } + err= 0; + goto wait_signal; + } + else if (!strncasecmp (out, magic_done, strlen(magic_done))) + { + err= sst_scan_uuid_seqno (out + strlen(magic_done) + 1, + &ret_uuid, &ret_seqno); + } + else + { + WSREP_WARN("Received unknown signal: '%s'", out); + } + } + else + { + WSREP_ERROR("Failed to read from: %s", proc.cmd()); + proc.wait(); + } + if (!err && proc.error()) err= proc.error(); + } + else + { + WSREP_ERROR("Failed to execute: %s : %d (%s)", + proc.cmd(), err, strerror(err)); + } + + if (locked) // don't forget to unlock server before return + { + if (mysql_bin_log.is_open()) + { + mysql_mutex_assert_owner(mysql_bin_log.get_log_lock()); + mysql_mutex_unlock(mysql_bin_log.get_log_lock()); + } + sst_disallow_writes (thd.ptr, false); + thd.ptr->global_read_lock.unlock_global_read_lock (thd.ptr); + } + + // signal to donor that SST is over + struct wsrep_gtid const state_id = { + ret_uuid, err ? WSREP_SEQNO_UNDEFINED : ret_seqno + }; + wsrep->sst_sent (wsrep, &state_id, -err); + proc.wait(); + + return NULL; +} + + + +static int sst_donate_other (const char* method, + const char* addr, + const char* uuid, + wsrep_seqno_t seqno, + bool bypass, + char** env) // carries auth info +{ + int const cmd_len= 4096; + wsp::string cmd_str(cmd_len); + + if (!cmd_str()) + { + WSREP_ERROR("sst_donate_other(): " + "could not allocate cmd buffer of %d bytes", cmd_len); + return -ENOMEM; + } + + const char* binlog_opt= ""; + char* binlog_opt_val= NULL; + + int ret; + if ((ret= generate_binlog_opt_val(&binlog_opt_val))) + { + WSREP_ERROR("sst_donate_other(): generate_binlog_opt_val() failed: %d",ret); + return ret; + } + if (strlen(binlog_opt_val)) binlog_opt= WSREP_SST_OPT_BINLOG; + + make_wsrep_defaults_file(); + + ret= snprintf (cmd_str(), cmd_len, + "wsrep_sst_%s " + WSREP_SST_OPT_ROLE " 'donor' " + WSREP_SST_OPT_ADDR " '%s' " + WSREP_SST_OPT_SOCKET " '%s' " + WSREP_SST_OPT_DATA " '%s' " + " %s " + " %s '%s' " + WSREP_SST_OPT_GTID " '%s:%lld' " + WSREP_SST_OPT_GTID_DOMAIN_ID " '%d'" + "%s", + method, addr, mysqld_unix_port, mysql_real_data_home, + wsrep_defaults_file, + binlog_opt, binlog_opt_val, + uuid, (long long) seqno, wsrep_gtid_domain_id, + bypass ? " " WSREP_SST_OPT_BYPASS : ""); + my_free(binlog_opt_val); + + if (ret < 0 || ret >= cmd_len) + { + WSREP_ERROR("sst_donate_other(): snprintf() failed: %d", ret); + return (ret < 0 ? ret : -EMSGSIZE); + } + + if (!bypass && wsrep_sst_donor_rejects_queries) sst_reject_queries(FALSE); + + pthread_t tmp; + sst_thread_arg arg(cmd_str(), env); + mysql_mutex_lock (&arg.lock); + ret = pthread_create (&tmp, NULL, sst_donor_thread, &arg); + if (ret) + { + WSREP_ERROR("sst_donate_other(): pthread_create() failed: %d (%s)", + ret, strerror(ret)); + return ret; + } + mysql_cond_wait (&arg.cond, &arg.lock); + + WSREP_INFO("sst_donor_thread signaled with %d", arg.err); + return arg.err; +} + +wsrep_cb_status_t wsrep_sst_donate_cb (void* app_ctx, void* recv_ctx, + const void* msg, size_t msg_len, + const wsrep_gtid_t* current_gtid, + const char* state, size_t state_len, + bool bypass) +{ + /* This will be reset when sync callback is called. + * Should we set wsrep_ready to FALSE here too? */ + + wsrep_config_state.set(WSREP_MEMBER_DONOR); + + const char* method = (char*)msg; + size_t method_len = strlen (method); + const char* data = method + method_len + 1; + + char uuid_str[37]; + wsrep_uuid_print (¤t_gtid->uuid, uuid_str, sizeof(uuid_str)); + + wsp::env env(NULL); + if (env.error()) + { + WSREP_ERROR("wsrep_sst_donate_cb(): env var ctor failed: %d", -env.error()); + return WSREP_CB_FAILURE; + } + + int ret; + if ((ret= sst_append_auth_env(env, sst_auth_real))) + { + WSREP_ERROR("wsrep_sst_donate_cb(): appending auth env failed: %d", ret); + return WSREP_CB_FAILURE; + } + + if (data_home_dir) + { + if ((ret= sst_append_data_dir(env, data_home_dir))) + { + WSREP_ERROR("wsrep_sst_donate_cb(): appending data " + "directory failed: %d", ret); + return WSREP_CB_FAILURE; + } + } + + if (!strcmp (WSREP_SST_MYSQLDUMP, method)) + { + ret = sst_donate_mysqldump(data, ¤t_gtid->uuid, uuid_str, + current_gtid->seqno, bypass, env()); + } + else + { + ret = sst_donate_other(method, data, uuid_str, + current_gtid->seqno, bypass, env()); + } + + return (ret >= 0 ? WSREP_CB_SUCCESS : WSREP_CB_FAILURE); +} + +void wsrep_SE_init_grab() +{ + if (mysql_mutex_lock (&LOCK_wsrep_sst_init)) abort(); +} + +void wsrep_SE_init_wait() +{ + double total_wtime=0; + + while (SE_initialized == false) + { + struct timespec wtime; + set_timespec(wtime, WSREP_TIMEDWAIT_SECONDS); + time_t start_time = time(NULL); + mysql_cond_timedwait (&COND_wsrep_sst_init, &LOCK_wsrep_sst_init, &wtime); + time_t end_time = time(NULL); + + if (!SE_initialized) + { + total_wtime += difftime(end_time, start_time); + WSREP_DEBUG("Waiting for SST to complete. current seqno: %" PRId64 " waited %f secs.", local_seqno, total_wtime); + service_manager_extend_timeout(WSREP_EXTEND_TIMEOUT_INTERVAL, + "WSREP state transfer ongoing, current seqno: %" PRId64 " waited %f secs", local_seqno, total_wtime); + } + } + + mysql_mutex_unlock (&LOCK_wsrep_sst_init); +} + +void wsrep_SE_init_done() +{ + mysql_cond_signal (&COND_wsrep_sst_init); + mysql_mutex_unlock (&LOCK_wsrep_sst_init); +} + +void wsrep_SE_initialized() +{ + SE_initialized = true; +} diff --git a/sql/wsrep_sst.h b/sql/wsrep_sst.h new file mode 100644 index 00000000000..a35ce46cae8 --- /dev/null +++ b/sql/wsrep_sst.h @@ -0,0 +1,83 @@ +/* Copyright (C) 2013 Codership Oy <info@codership.com> + + 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-1301 USA. */ + +#include <my_config.h> + +#ifndef WSREP_SST_H +#define WSREP_SST_H + +#ifdef WITH_WSREP + +#include <mysql.h> // my_bool + +#define WSREP_SST_OPT_ROLE "--role" +#define WSREP_SST_OPT_ADDR "--address" +#define WSREP_SST_OPT_AUTH "--auth" +#define WSREP_SST_OPT_DATA "--datadir" +#define WSREP_SST_OPT_CONF "--defaults-file" +#define WSREP_SST_OPT_CONF_SUFFIX "--defaults-group-suffix" +#define WSREP_SST_OPT_CONF_EXTRA "--defaults-extra-file" +#define WSREP_SST_OPT_PARENT "--parent" +#define WSREP_SST_OPT_BINLOG "--binlog" +#define WSREP_SST_OPT_BINLOG_INDEX "--binlog-index" + +// mysqldump-specific options +#define WSREP_SST_OPT_USER "--user" +#define WSREP_SST_OPT_PSWD "--password" +#define WSREP_SST_OPT_HOST "--host" +#define WSREP_SST_OPT_PORT "--port" +#define WSREP_SST_OPT_LPORT "--local-port" + +// donor-specific +#define WSREP_SST_OPT_SOCKET "--socket" +#define WSREP_SST_OPT_GTID "--gtid" +#define WSREP_SST_OPT_BYPASS "--bypass" +#define WSREP_SST_OPT_GTID_DOMAIN_ID "--gtid-domain-id" + +#define WSREP_SST_MYSQLDUMP "mysqldump" +#define WSREP_SST_RSYNC "rsync" +#define WSREP_SST_SKIP "skip" +#define WSREP_SST_DEFAULT WSREP_SST_RSYNC +#define WSREP_SST_ADDRESS_AUTO "AUTO" +#define WSREP_SST_AUTH_MASK "********" + +/* system variables */ +extern const char* wsrep_sst_method; +extern const char* wsrep_sst_receive_address; +extern const char* wsrep_sst_donor; +extern char* wsrep_sst_auth; +extern my_bool wsrep_sst_donor_rejects_queries; + +/*! Synchronizes applier thread start with init thread */ +extern void wsrep_sst_grab(); +/*! Init thread waits for SST completion */ +extern bool wsrep_sst_wait(); +/*! Signals wsrep that initialization is complete, writesets can be applied */ +extern void wsrep_sst_continue(); +extern void wsrep_sst_auth_free(); + +extern void wsrep_SE_init_grab(); /*! grab init critical section */ +extern void wsrep_SE_init_wait(); /*! wait for SE init to complete */ +extern void wsrep_SE_init_done(); /*! signal that SE init is complte */ +extern void wsrep_SE_initialized(); /*! mark SE initialization complete */ + +#else +#define wsrep_SE_initialized() do { } while(0) +#define wsrep_SE_init_grab() do { } while(0) +#define wsrep_SE_init_done() do { } while(0) +#define wsrep_sst_continue() do { } while(0) + +#endif /* WITH_WSREP */ +#endif /* WSREP_SST_H */ diff --git a/sql/wsrep_thd.cc b/sql/wsrep_thd.cc new file mode 100644 index 00000000000..551e710cfeb --- /dev/null +++ b/sql/wsrep_thd.cc @@ -0,0 +1,699 @@ +/* Copyright (C) 2013 Codership Oy <info@codership.com> + + 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-1301 USA. */ + +#include "wsrep_thd.h" + +#include "transaction.h" +#include "rpl_rli.h" +#include "log_event.h" +#include "sql_parse.h" +//#include "global_threads.h" // LOCK_thread_count, etc. +#include "sql_base.h" // close_thread_tables() +#include "mysqld.h" // start_wsrep_THD(); + +#include "slave.h" // opt_log_slave_updates +#include "rpl_filter.h" +#include "rpl_rli.h" +#include "rpl_mi.h" + +#if (__LP64__) +static volatile int64 wsrep_bf_aborts_counter(0); +#define WSREP_ATOMIC_LOAD_LONG my_atomic_load64 +#define WSREP_ATOMIC_ADD_LONG my_atomic_add64 +#else +static volatile int32 wsrep_bf_aborts_counter(0); +#define WSREP_ATOMIC_LOAD_LONG my_atomic_load32 +#define WSREP_ATOMIC_ADD_LONG my_atomic_add32 +#endif + +int wsrep_show_bf_aborts (THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) +{ + wsrep_local_bf_aborts = WSREP_ATOMIC_LOAD_LONG(&wsrep_bf_aborts_counter); + var->type = SHOW_LONGLONG; + var->value = (char*)&wsrep_local_bf_aborts; + return 0; +} + +/* must have (&thd->LOCK_thd_data) */ +void wsrep_client_rollback(THD *thd) +{ + WSREP_DEBUG("client rollback due to BF abort for (%ld), query: %s", + thd->thread_id, thd->query()); + + WSREP_ATOMIC_ADD_LONG(&wsrep_bf_aborts_counter, 1); + + thd->wsrep_conflict_state= ABORTING; + mysql_mutex_unlock(&thd->LOCK_thd_data); + trans_rollback(thd); + + if (thd->locked_tables_mode && thd->lock) + { + WSREP_DEBUG("unlocking tables for BF abort (%ld)", thd->thread_id); + thd->locked_tables_list.unlock_locked_tables(thd); + thd->variables.option_bits&= ~(OPTION_TABLE_LOCK); + } + + if (thd->global_read_lock.is_acquired()) + { + WSREP_DEBUG("unlocking GRL for BF abort (%ld)", thd->thread_id); + thd->global_read_lock.unlock_global_read_lock(thd); + } + + /* Release transactional metadata locks. */ + thd->mdl_context.release_transactional_locks(); + + /* release explicit MDL locks */ + thd->mdl_context.release_explicit_locks(); + + if (thd->get_binlog_table_maps()) + { + WSREP_DEBUG("clearing binlog table map for BF abort (%ld)", thd->thread_id); + thd->clear_binlog_table_maps(); + } + mysql_mutex_lock(&thd->LOCK_thd_data); + thd->wsrep_conflict_state= ABORTED; +} + +#define NUMBER_OF_FIELDS_TO_IDENTIFY_COORDINATOR 1 +#define NUMBER_OF_FIELDS_TO_IDENTIFY_WORKER 2 + +static rpl_group_info* wsrep_relay_group_init(const char* log_fname) +{ + Relay_log_info* rli= new Relay_log_info(false); + + rli->no_storage= true; + if (!rli->relay_log.description_event_for_exec) + { + rli->relay_log.description_event_for_exec= + new Format_description_log_event(4); + } + + static LEX_STRING connection_name= { C_STRING_WITH_LEN("wsrep") }; + + /* + Master_info's constructor initializes rpl_filter by either an already + constructed Rpl_filter object from global 'rpl_filters' list if the + specified connection name is same, or it constructs a new Rpl_filter + object and adds it to rpl_filters. This object is later destructed by + Mater_info's destructor by looking it up based on connection name in + rpl_filters list. + + However, since all Master_info objects created here would share same + connection name ("wsrep"), destruction of any of the existing Master_info + objects (in wsrep_return_from_bf_mode()) would free rpl_filter referenced + by any/all existing Master_info objects. + + In order to avoid that, we have added a check in Master_info's destructor + to not free the "wsrep" rpl_filter. It will eventually be freed by + free_all_rpl_filters() when server terminates. + */ + rli->mi = new Master_info(&connection_name, false); + + struct rpl_group_info *rgi= new rpl_group_info(rli); + rgi->thd= rli->sql_driver_thd= current_thd; + + if ((rgi->deferred_events_collecting= rli->mi->rpl_filter->is_on())) + { + rgi->deferred_events= new Deferred_log_events(rli); + } + + return rgi; +} + +static void wsrep_prepare_bf_thd(THD *thd, struct wsrep_thd_shadow* shadow) +{ + shadow->options = thd->variables.option_bits; + shadow->server_status = thd->server_status; + shadow->wsrep_exec_mode = thd->wsrep_exec_mode; + shadow->vio = thd->net.vio; + + // Disable general logging on applier threads + thd->variables.option_bits |= OPTION_LOG_OFF; + // Enable binlogging if opt_log_slave_updates is set + if (opt_log_slave_updates) + thd->variables.option_bits|= OPTION_BIN_LOG; + else + thd->variables.option_bits&= ~(OPTION_BIN_LOG); + + if (!thd->wsrep_rgi) thd->wsrep_rgi= wsrep_relay_group_init("wsrep_relay"); + + /* thd->system_thread_info.rpl_sql_info isn't initialized. */ + thd->system_thread_info.rpl_sql_info= + new rpl_sql_thread_info(thd->wsrep_rgi->rli->mi->rpl_filter); + + thd->wsrep_exec_mode= REPL_RECV; + thd->net.vio= 0; + thd->clear_error(); + + shadow->tx_isolation = thd->variables.tx_isolation; + thd->variables.tx_isolation = ISO_READ_COMMITTED; + thd->tx_isolation = ISO_READ_COMMITTED; + + shadow->db = thd->db; + shadow->db_length = thd->db_length; + shadow->user_time = thd->user_time; + shadow->row_count_func= thd->get_row_count_func(); + thd->reset_db(NULL, 0); +} + +static void wsrep_return_from_bf_mode(THD *thd, struct wsrep_thd_shadow* shadow) +{ + thd->variables.option_bits = shadow->options; + thd->server_status = shadow->server_status; + thd->wsrep_exec_mode = shadow->wsrep_exec_mode; + thd->net.vio = shadow->vio; + thd->variables.tx_isolation = shadow->tx_isolation; + thd->user_time = shadow->user_time; + thd->reset_db(shadow->db, shadow->db_length); + + delete thd->system_thread_info.rpl_sql_info; + delete thd->wsrep_rgi->rli->mi; + delete thd->wsrep_rgi->rli; + + thd->wsrep_rgi->cleanup_after_session(); + delete thd->wsrep_rgi; + thd->wsrep_rgi = NULL; + thd->set_row_count_func(shadow->row_count_func); +} + +void wsrep_replay_transaction(THD *thd) +{ + DBUG_ENTER("wsrep_replay_transaction"); + /* checking if BF trx must be replayed */ + if (thd->wsrep_conflict_state== MUST_REPLAY) { + DBUG_ASSERT(wsrep_thd_trx_seqno(thd)); + if (thd->wsrep_exec_mode!= REPL_RECV) { + if (thd->get_stmt_da()->is_sent()) + { + WSREP_ERROR("replay issue, thd has reported status already"); + } + + + /* + PS reprepare observer should have been removed already. + open_table() will fail if we have dangling observer here. + */ + DBUG_ASSERT(thd->m_reprepare_observer == NULL); + + struct da_shadow + { + enum Diagnostics_area::enum_diagnostics_status status; + ulonglong affected_rows; + ulonglong last_insert_id; + char message[MYSQL_ERRMSG_SIZE]; + }; + struct da_shadow da_status; + da_status.status= thd->get_stmt_da()->status(); + if (da_status.status == Diagnostics_area::DA_OK) + { + da_status.affected_rows= thd->get_stmt_da()->affected_rows(); + da_status.last_insert_id= thd->get_stmt_da()->last_insert_id(); + strmake(da_status.message, + thd->get_stmt_da()->message(), + sizeof(da_status.message)-1); + } + + thd->get_stmt_da()->reset_diagnostics_area(); + + thd->wsrep_conflict_state= REPLAYING; + mysql_mutex_unlock(&thd->LOCK_thd_data); + + thd->reset_for_next_command(); + thd->reset_killed(); + close_thread_tables(thd); + if (thd->locked_tables_mode && thd->lock) + { + WSREP_DEBUG("releasing table lock for replaying (%ld)", + thd->thread_id); + thd->locked_tables_list.unlock_locked_tables(thd); + thd->variables.option_bits&= ~(OPTION_TABLE_LOCK); + } + thd->mdl_context.release_transactional_locks(); + /* + Replaying will call MYSQL_START_STATEMENT when handling + BEGIN Query_log_event so end statement must be called before + replaying. + */ + MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da()); + thd->m_statement_psi= NULL; + thd->m_digest= NULL; + thd_proc_info(thd, "wsrep replaying trx"); + WSREP_DEBUG("replay trx: %s %lld", + thd->query() ? thd->query() : "void", + (long long)wsrep_thd_trx_seqno(thd)); + struct wsrep_thd_shadow shadow; + wsrep_prepare_bf_thd(thd, &shadow); + + /* From trans_begin() */ + thd->variables.option_bits|= OPTION_BEGIN; + thd->server_status|= SERVER_STATUS_IN_TRANS; + + int rcode = wsrep->replay_trx(wsrep, + &thd->wsrep_ws_handle, + (void *)thd); + + wsrep_return_from_bf_mode(thd, &shadow); + if (thd->wsrep_conflict_state!= REPLAYING) + WSREP_WARN("lost replaying mode: %d", thd->wsrep_conflict_state ); + + mysql_mutex_lock(&thd->LOCK_thd_data); + + switch (rcode) + { + case WSREP_OK: + thd->wsrep_conflict_state= NO_CONFLICT; + wsrep->post_commit(wsrep, &thd->wsrep_ws_handle); + WSREP_DEBUG("trx_replay successful for: %ld %llu", + thd->thread_id, (long long)thd->real_id); + if (thd->get_stmt_da()->is_sent()) + { + WSREP_WARN("replay ok, thd has reported status"); + } + else if (thd->get_stmt_da()->is_set()) + { + if (thd->get_stmt_da()->status() != Diagnostics_area::DA_OK) + { + WSREP_WARN("replay ok, thd has error status %d", + thd->get_stmt_da()->status()); + } + } + else + { + if (da_status.status == Diagnostics_area::DA_OK) + { + my_ok(thd, + da_status.affected_rows, + da_status.last_insert_id, + da_status.message); + } + else + { + my_ok(thd); + } + } + break; + case WSREP_TRX_FAIL: + if (thd->get_stmt_da()->is_sent()) + { + WSREP_ERROR("replay failed, thd has reported status"); + } + else + { + WSREP_DEBUG("replay failed, rolling back"); + //my_error(ER_LOCK_DEADLOCK, MYF(0), "wsrep aborted transaction"); + } + thd->wsrep_conflict_state= ABORTED; + wsrep->post_rollback(wsrep, &thd->wsrep_ws_handle); + break; + default: + WSREP_ERROR("trx_replay failed for: %d, schema: %s, query: %s", + rcode, + (thd->db ? thd->db : "(null)"), + thd->query() ? thd->query() : "void"); + /* we're now in inconsistent state, must abort */ + + /* http://bazaar.launchpad.net/~codership/codership-mysql/5.6/revision/3962#sql/wsrep_thd.cc */ + mysql_mutex_unlock(&thd->LOCK_thd_data); + + unireg_abort(1); + break; + } + + wsrep_cleanup_transaction(thd); + + mysql_mutex_lock(&LOCK_wsrep_replaying); + wsrep_replaying--; + WSREP_DEBUG("replaying decreased: %d, thd: %lu", + wsrep_replaying, thd->thread_id); + mysql_cond_broadcast(&COND_wsrep_replaying); + mysql_mutex_unlock(&LOCK_wsrep_replaying); + } + } + DBUG_VOID_RETURN; +} + +static void wsrep_replication_process(THD *thd) +{ + int rcode; + DBUG_ENTER("wsrep_replication_process"); + + struct wsrep_thd_shadow shadow; + wsrep_prepare_bf_thd(thd, &shadow); + + /* From trans_begin() */ + thd->variables.option_bits|= OPTION_BEGIN; + thd->server_status|= SERVER_STATUS_IN_TRANS; + + rcode = wsrep->recv(wsrep, (void *)thd); + DBUG_PRINT("wsrep",("wsrep_repl returned: %d", rcode)); + + WSREP_INFO("applier thread exiting (code:%d)", rcode); + + switch (rcode) { + case WSREP_OK: + case WSREP_NOT_IMPLEMENTED: + case WSREP_CONN_FAIL: + /* provider does not support slave operations / disconnected from group, + * just close applier thread */ + break; + case WSREP_NODE_FAIL: + /* data inconsistency => SST is needed */ + /* Note: we cannot just blindly restart replication here, + * SST might require server restart if storage engines must be + * initialized after SST */ + WSREP_ERROR("node consistency compromised, aborting"); + wsrep_kill_mysql(thd); + break; + case WSREP_WARNING: + case WSREP_TRX_FAIL: + case WSREP_TRX_MISSING: + /* these suggests a bug in provider code */ + WSREP_WARN("bad return from recv() call: %d", rcode); + /* Shut down this node. */ + /* fall through */ + case WSREP_FATAL: + /* Cluster connectivity is lost. + * + * If applier was killed on purpose (KILL_CONNECTION), we + * avoid mysql shutdown. This is because the killer will then handle + * shutdown processing (or replication restarting) + */ + if (thd->killed != KILL_CONNECTION) + { + wsrep_kill_mysql(thd); + } + break; + } + + mysql_mutex_lock(&LOCK_thread_count); + wsrep_close_applier(thd); + mysql_cond_broadcast(&COND_thread_count); + mysql_mutex_unlock(&LOCK_thread_count); + + TABLE *tmp; + while ((tmp = thd->temporary_tables)) + { + WSREP_WARN("Applier %lu, has temporary tables at exit: %s.%s", + thd->thread_id, + (tmp->s) ? tmp->s->db.str : "void", + (tmp->s) ? tmp->s->table_name.str : "void"); + } + wsrep_return_from_bf_mode(thd, &shadow); + DBUG_VOID_RETURN; +} + +static bool create_wsrep_THD(wsrep_thd_processor_fun processor) +{ + ulong old_wsrep_running_threads= wsrep_running_threads; + pthread_t unused; + mysql_mutex_lock(&LOCK_thread_count); + bool res= pthread_create(&unused, &connection_attrib, start_wsrep_THD, + (void*)processor); + /* + if starting a thread on server startup, wait until the this thread's THD + is fully initialized (otherwise a THD initialization code might + try to access a partially initialized server data structure - MDEV-8208). + */ + if (!mysqld_server_initialized) + while (old_wsrep_running_threads == wsrep_running_threads) + mysql_cond_wait(&COND_thread_count, &LOCK_thread_count); + mysql_mutex_unlock(&LOCK_thread_count); + return res; +} + +void wsrep_create_appliers(long threads) +{ + if (!wsrep_connected) + { + /* see wsrep_replication_start() for the logic */ + if (wsrep_cluster_address && strlen(wsrep_cluster_address) && + wsrep_provider && strcasecmp(wsrep_provider, "none")) + { + WSREP_ERROR("Trying to launch slave threads before creating " + "connection at '%s'", wsrep_cluster_address); + assert(0); + } + return; + } + + long wsrep_threads=0; + while (wsrep_threads++ < threads) { + if (create_wsrep_THD(wsrep_replication_process)) + WSREP_WARN("Can't create thread to manage wsrep replication"); + } +} + +static void wsrep_rollback_process(THD *thd) +{ + DBUG_ENTER("wsrep_rollback_process"); + + mysql_mutex_lock(&LOCK_wsrep_rollback); + wsrep_aborting_thd= NULL; + + while (thd->killed == NOT_KILLED) { + thd_proc_info(thd, "wsrep aborter idle"); + thd->mysys_var->current_mutex= &LOCK_wsrep_rollback; + thd->mysys_var->current_cond= &COND_wsrep_rollback; + + mysql_cond_wait(&COND_wsrep_rollback,&LOCK_wsrep_rollback); + + WSREP_DEBUG("WSREP rollback thread wakes for signal"); + + mysql_mutex_lock(&thd->mysys_var->mutex); + thd_proc_info(thd, "wsrep aborter active"); + thd->mysys_var->current_mutex= 0; + thd->mysys_var->current_cond= 0; + mysql_mutex_unlock(&thd->mysys_var->mutex); + + /* check for false alarms */ + if (!wsrep_aborting_thd) + { + WSREP_DEBUG("WSREP rollback thread has empty abort queue"); + } + /* process all entries in the queue */ + while (wsrep_aborting_thd) { + THD *aborting; + wsrep_aborting_thd_t next = wsrep_aborting_thd->next; + aborting = wsrep_aborting_thd->aborting_thd; + my_free(wsrep_aborting_thd); + wsrep_aborting_thd= next; + /* + * must release mutex, appliers my want to add more + * aborting thds in our work queue, while we rollback + */ + mysql_mutex_unlock(&LOCK_wsrep_rollback); + + mysql_mutex_lock(&aborting->LOCK_thd_data); + if (aborting->wsrep_conflict_state== ABORTED) + { + WSREP_DEBUG("WSREP, thd already aborted: %llu state: %d", + (long long)aborting->real_id, + aborting->wsrep_conflict_state); + + mysql_mutex_unlock(&aborting->LOCK_thd_data); + mysql_mutex_lock(&LOCK_wsrep_rollback); + continue; + } + aborting->wsrep_conflict_state= ABORTING; + + mysql_mutex_unlock(&aborting->LOCK_thd_data); + + set_current_thd(aborting); + aborting->store_globals(); + + mysql_mutex_lock(&aborting->LOCK_thd_data); + wsrep_client_rollback(aborting); + WSREP_DEBUG("WSREP rollbacker aborted thd: (%lu %llu)", + aborting->thread_id, (long long)aborting->real_id); + mysql_mutex_unlock(&aborting->LOCK_thd_data); + + set_current_thd(thd); + thd->store_globals(); + + mysql_mutex_lock(&LOCK_wsrep_rollback); + } + } + + mysql_mutex_unlock(&LOCK_wsrep_rollback); + sql_print_information("WSREP: rollbacker thread exiting"); + + DBUG_PRINT("wsrep",("wsrep rollbacker thread exiting")); + DBUG_VOID_RETURN; +} + +void wsrep_create_rollbacker() +{ + if (wsrep_provider && strcasecmp(wsrep_provider, "none")) + { + /* create rollbacker */ + if (create_wsrep_THD(wsrep_rollback_process)) + WSREP_WARN("Can't create thread to manage wsrep rollback"); + } +} + +void wsrep_thd_set_PA_safe(void *thd_ptr, my_bool safe) +{ + if (thd_ptr) + { + THD* thd = (THD*)thd_ptr; + thd->wsrep_PA_safe = safe; + } +} + +enum wsrep_conflict_state wsrep_thd_conflict_state(THD *thd, my_bool sync) +{ + enum wsrep_conflict_state state = NO_CONFLICT; + if (thd) + { + if (sync) mysql_mutex_lock(&thd->LOCK_thd_data); + + state = thd->wsrep_conflict_state; + if (sync) mysql_mutex_unlock(&thd->LOCK_thd_data); + } + return state; +} + +my_bool wsrep_thd_is_wsrep(THD *thd) +{ + my_bool status = FALSE; + if (thd) + { + status = (WSREP(thd) && WSREP_PROVIDER_EXISTS); + } + return status; +} + +my_bool wsrep_thd_is_BF(THD *thd, my_bool sync) +{ + my_bool status = FALSE; + if (thd) + { + // THD can be BF only if provider exists + if (wsrep_thd_is_wsrep(thd)) + { + if (sync) + mysql_mutex_lock(&thd->LOCK_thd_data); + + status = ((thd->wsrep_exec_mode == REPL_RECV) || + (thd->wsrep_exec_mode == TOTAL_ORDER)); + if (sync) + mysql_mutex_unlock(&thd->LOCK_thd_data); + } + } + return status; +} + +extern "C" +my_bool wsrep_thd_is_BF_or_commit(void *thd_ptr, my_bool sync) +{ + bool status = FALSE; + if (thd_ptr) + { + THD* thd = (THD*)thd_ptr; + if (sync) mysql_mutex_lock(&thd->LOCK_thd_data); + + status = ((thd->wsrep_exec_mode == REPL_RECV) || + (thd->wsrep_exec_mode == TOTAL_ORDER) || + (thd->wsrep_exec_mode == LOCAL_COMMIT)); + if (sync) mysql_mutex_unlock(&thd->LOCK_thd_data); + } + return status; +} + +extern "C" +my_bool wsrep_thd_is_local(void *thd_ptr, my_bool sync) +{ + bool status = FALSE; + if (thd_ptr) + { + THD* thd = (THD*)thd_ptr; + if (sync) mysql_mutex_lock(&thd->LOCK_thd_data); + + status = (thd->wsrep_exec_mode == LOCAL_STATE); + if (sync) mysql_mutex_unlock(&thd->LOCK_thd_data); + } + return status; +} + +int wsrep_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, my_bool signal) +{ + THD *victim_thd = (THD *) victim_thd_ptr; + THD *bf_thd = (THD *) bf_thd_ptr; + DBUG_ENTER("wsrep_abort_thd"); + + if ( (WSREP(bf_thd) || + ( (WSREP_ON || bf_thd->variables.wsrep_OSU_method == WSREP_OSU_RSU) && + bf_thd->wsrep_exec_mode == TOTAL_ORDER) ) && + victim_thd) + { + if ((victim_thd->wsrep_conflict_state == MUST_ABORT) || + (victim_thd->wsrep_conflict_state == ABORTED) || + (victim_thd->wsrep_conflict_state == ABORTING)) + { + WSREP_DEBUG("wsrep_abort_thd called by %llu with victim %llu already " + "aborted. Ignoring.", + (bf_thd) ? (long long)bf_thd->real_id : 0, + (long long)victim_thd->real_id); + DBUG_RETURN(1); + } + + WSREP_DEBUG("wsrep_abort_thd, by: %llu, victim: %llu", (bf_thd) ? + (long long)bf_thd->real_id : 0, (long long)victim_thd->real_id); + ha_abort_transaction(bf_thd, victim_thd, signal); + } + else + { + WSREP_DEBUG("wsrep_abort_thd not effective: %p %p", bf_thd, victim_thd); + } + + DBUG_RETURN(1); +} + +extern "C" +int wsrep_thd_in_locking_session(void *thd_ptr) +{ + if (thd_ptr && ((THD *)thd_ptr)->in_lock_tables) { + return 1; + } + return 0; +} + +bool wsrep_thd_has_explicit_locks(THD *thd) +{ + assert(thd); + return thd->mdl_context.has_explicit_locks(); +} + +/* + Get auto increment variables for THD. Use global settings for + applier threads. + */ +void wsrep_thd_auto_increment_variables(THD* thd, + unsigned long long* offset, + unsigned long long* increment) +{ + if (thd->wsrep_exec_mode == REPL_RECV && + thd->wsrep_conflict_state != REPLAYING) + { + *offset= global_system_variables.auto_increment_offset; + *increment= global_system_variables.auto_increment_increment; + } + else + { + *offset= thd->variables.auto_increment_offset; + *increment= thd->variables.auto_increment_increment; + } +} diff --git a/sql/wsrep_thd.h b/sql/wsrep_thd.h new file mode 100644 index 00000000000..5900668f3fb --- /dev/null +++ b/sql/wsrep_thd.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2013 Codership Oy <info@codership.com> + + 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-1301 USA. */ + +#include <my_config.h> + +#ifndef WSREP_THD_H +#define WSREP_THD_H + +#ifdef WITH_WSREP + +#include "sql_class.h" + +int wsrep_show_bf_aborts (THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope); +void wsrep_client_rollback(THD *thd); +void wsrep_replay_transaction(THD *thd); +void wsrep_create_appliers(long threads); +void wsrep_create_rollbacker(); + +int wsrep_abort_thd(void *bf_thd_ptr, void *victim_thd_ptr, + my_bool signal); + +/* + PA = Parallel Applying (on the slave side) +*/ +extern void wsrep_thd_set_PA_safe(void *thd_ptr, my_bool safe); +extern my_bool wsrep_thd_is_BF(THD *thd, my_bool sync); +extern my_bool wsrep_thd_is_wsrep(void *thd_ptr); + +enum wsrep_conflict_state wsrep_thd_conflict_state(void *thd_ptr, my_bool sync); +extern "C" my_bool wsrep_thd_is_BF_or_commit(void *thd_ptr, my_bool sync); +extern "C" my_bool wsrep_thd_is_local(void *thd_ptr, my_bool sync); +extern "C" int wsrep_thd_in_locking_session(void *thd_ptr); + +#else /* WITH_WSREP */ + +#define wsrep_thd_is_BF(T, S) (0) +#define wsrep_abort_thd(X,Y,Z) do { } while(0) +#define wsrep_create_appliers(T) do { } while(0) + +#endif +#endif /* WSREP_THD_H */ diff --git a/sql/wsrep_utils.cc b/sql/wsrep_utils.cc new file mode 100644 index 00000000000..8a72d754a43 --- /dev/null +++ b/sql/wsrep_utils.cc @@ -0,0 +1,589 @@ +/* Copyright 2010-2015 Codership Oy <http://www.codership.com> + + 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 02111-1301 USA + */ + +//! @file some utility functions and classes not directly related to replication + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // POSIX_SPAWN_USEVFORK flag +#endif + +#include "wsrep_utils.h" +#include "wsrep_mysqld.h" + +#include <sql_class.h> + +#include <spawn.h> // posix_spawn() +#include <unistd.h> // pipe() +#include <errno.h> // errno +#include <string.h> // strerror() +#include <sys/wait.h> // waitpid() +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> // getaddrinfo() + +#ifdef HAVE_GETIFADDRS +#include <net/if.h> +#include <ifaddrs.h> +#endif /* HAVE_GETIFADDRS */ + +extern char** environ; // environment variables + +static wsp::string wsrep_PATH; + +void +wsrep_prepend_PATH (const char* path) +{ + int count = 0; + + while (environ[count]) + { + if (strncmp (environ[count], "PATH=", 5)) + { + count++; + continue; + } + + char* const old_path (environ[count]); + + if (strstr (old_path, path)) return; // path already there + + size_t const new_path_len(strlen(old_path) + strlen(":") + + strlen(path) + 1); + + char* const new_path (static_cast<char*>(malloc(new_path_len))); + + if (new_path) + { + snprintf (new_path, new_path_len, "PATH=%s:%s", path, + old_path + strlen("PATH=")); + + wsrep_PATH.set (new_path); + environ[count] = new_path; + } + else + { + WSREP_ERROR ("Failed to allocate 'PATH' environment variable " + "buffer of size %zu.", new_path_len); + } + + return; + } + + WSREP_ERROR ("Failed to find 'PATH' environment variable. " + "State snapshot transfer may not be working."); +} + +namespace wsp +{ + +bool +env::ctor_common(char** e) +{ + env_ = static_cast<char**>(malloc((len_ + 1) * sizeof(char*))); + + if (env_) + { + for (size_t i(0); i < len_; ++i) + { + assert(e[i]); // caller should make sure about len_ + env_[i] = strdup(e[i]); + if (!env_[i]) + { + errno_ = errno; + WSREP_ERROR("Failed to allocate env. var: %s", e[i]); + return true; + } + } + + env_[len_] = NULL; + return false; + } + else + { + errno_ = errno; + WSREP_ERROR("Failed to allocate env. var vector of length: %zu", len_); + return true; + } +} + +void +env::dtor() +{ + if (env_) + { + /* don't need to go beyond the first NULL */ + for (size_t i(0); env_[i] != NULL; ++i) { free(env_[i]); } + free(env_); + env_ = NULL; + } + len_ = 0; +} + +env::env(char** e) + : len_(0), env_(NULL), errno_(0) +{ + if (!e) { e = environ; } + /* count the size of the vector */ + while (e[len_]) { ++len_; } + + if (ctor_common(e)) dtor(); +} + +env::env(const env& e) + : len_(e.len_), env_(0), errno_(0) +{ + if (ctor_common(e.env_)) dtor(); +} + +env::~env() { dtor(); } + +int +env::append(const char* val) +{ + char** tmp = static_cast<char**>(realloc(env_, (len_ + 2)*sizeof(char*))); + + if (tmp) + { + env_ = tmp; + env_[len_] = strdup(val); + + if (env_[len_]) + { + ++len_; + env_[len_] = NULL; + } + else errno_ = errno; + } + else errno_ = errno; + + return errno_; +} + + +#define PIPE_READ 0 +#define PIPE_WRITE 1 +#define STDIN_FD 0 +#define STDOUT_FD 1 + +#ifndef POSIX_SPAWN_USEVFORK +# define POSIX_SPAWN_USEVFORK 0 +#endif + +process::process (const char* cmd, const char* type, char** env) + : str_(cmd ? strdup(cmd) : strdup("")), io_(NULL), err_(EINVAL), pid_(0) +{ + if (0 == str_) + { + WSREP_ERROR ("Can't allocate command line of size: %zu", strlen(cmd)); + err_ = ENOMEM; + return; + } + + if (0 == strlen(str_)) + { + WSREP_ERROR ("Can't start a process: null or empty command line."); + return; + } + + if (NULL == type || (strcmp (type, "w") && strcmp(type, "r"))) + { + WSREP_ERROR ("type argument should be either \"r\" or \"w\"."); + return; + } + + if (NULL == env) { env = environ; } // default to global environment + + int pipe_fds[2] = { -1, }; + if (::pipe(pipe_fds)) + { + err_ = errno; + WSREP_ERROR ("pipe() failed: %d (%s)", err_, strerror(err_)); + return; + } + + // which end of pipe will be returned to parent + int const parent_end (strcmp(type,"w") ? PIPE_READ : PIPE_WRITE); + int const child_end (parent_end == PIPE_READ ? PIPE_WRITE : PIPE_READ); + int const close_fd (parent_end == PIPE_READ ? STDOUT_FD : STDIN_FD); + + char* const pargv[4] = { strdup("sh"), strdup("-c"), strdup(str_), NULL }; + if (!(pargv[0] && pargv[1] && pargv[2])) + { + err_ = ENOMEM; + WSREP_ERROR ("Failed to allocate pargv[] array."); + goto cleanup_pipe; + } + + posix_spawnattr_t attr; + err_ = posix_spawnattr_init (&attr); + if (err_) + { + WSREP_ERROR ("posix_spawnattr_init() failed: %d (%s)", + err_, strerror(err_)); + goto cleanup_pipe; + } + + /* make sure that no signlas are masked in child process */ + sigset_t sigmask_empty; sigemptyset(&sigmask_empty); + err_ = posix_spawnattr_setsigmask(&attr, &sigmask_empty); + if (err_) + { + WSREP_ERROR ("posix_spawnattr_setsigmask() failed: %d (%s)", + err_, strerror(err_)); + goto cleanup_attr; + } + + /* make sure the following signals are not ignored in child process */ + sigset_t default_signals; sigemptyset(&default_signals); + sigaddset(&default_signals, SIGHUP); + sigaddset(&default_signals, SIGINT); + sigaddset(&default_signals, SIGQUIT); + sigaddset(&default_signals, SIGPIPE); + sigaddset(&default_signals, SIGTERM); + sigaddset(&default_signals, SIGCHLD); + err_ = posix_spawnattr_setsigdefault(&attr, &default_signals); + if (err_) + { + WSREP_ERROR ("posix_spawnattr_setsigdefault() failed: %d (%s)", + err_, strerror(err_)); + goto cleanup_attr; + } + + err_ = posix_spawnattr_setflags (&attr, POSIX_SPAWN_SETSIGDEF | + POSIX_SPAWN_SETSIGMASK | + POSIX_SPAWN_USEVFORK); + if (err_) + { + WSREP_ERROR ("posix_spawnattr_setflags() failed: %d (%s)", + err_, strerror(err_)); + goto cleanup_attr; + } + + posix_spawn_file_actions_t fact; + err_ = posix_spawn_file_actions_init (&fact); + if (err_) + { + WSREP_ERROR ("posix_spawn_file_actions_init() failed: %d (%s)", + err_, strerror(err_)); + goto cleanup_attr; + } + + // close child's stdout|stdin depending on what we returning + err_ = posix_spawn_file_actions_addclose (&fact, close_fd); + if (err_) + { + WSREP_ERROR ("posix_spawn_file_actions_addclose() failed: %d (%s)", + err_, strerror(err_)); + goto cleanup_fact; + } + + // substitute our pipe descriptor in place of the closed one + err_ = posix_spawn_file_actions_adddup2 (&fact, + pipe_fds[child_end], close_fd); + if (err_) + { + WSREP_ERROR ("posix_spawn_file_actions_addup2() failed: %d (%s)", + err_, strerror(err_)); + goto cleanup_fact; + } + + err_ = posix_spawnp (&pid_, pargv[0], &fact, &attr, pargv, env); + if (err_) + { + WSREP_ERROR ("posix_spawnp(%s) failed: %d (%s)", + pargv[2], err_, strerror(err_)); + pid_ = 0; // just to make sure it was not messed up in the call + goto cleanup_fact; + } + + io_ = fdopen (pipe_fds[parent_end], type); + + if (io_) + { + pipe_fds[parent_end] = -1; // skip close on cleanup + } + else + { + err_ = errno; + WSREP_ERROR ("fdopen() failed: %d (%s)", err_, strerror(err_)); + } + +cleanup_fact: + int err; // to preserve err_ code + err = posix_spawn_file_actions_destroy (&fact); + if (err) + { + WSREP_ERROR ("posix_spawn_file_actions_destroy() failed: %d (%s)\n", + err, strerror(err)); + } + +cleanup_attr: + err = posix_spawnattr_destroy (&attr); + if (err) + { + WSREP_ERROR ("posix_spawnattr_destroy() failed: %d (%s)", + err, strerror(err)); + } + +cleanup_pipe: + if (pipe_fds[0] >= 0) close (pipe_fds[0]); + if (pipe_fds[1] >= 0) close (pipe_fds[1]); + + free (pargv[0]); + free (pargv[1]); + free (pargv[2]); +} + +process::~process () +{ + if (io_) + { + assert (pid_); + assert (str_); + + WSREP_WARN("Closing pipe to child process: %s, PID(%ld) " + "which might still be running.", str_, (long)pid_); + + if (fclose (io_) == -1) + { + err_ = errno; + WSREP_ERROR("fclose() failed: %d (%s)", err_, strerror(err_)); + } + } + + if (str_) free (const_cast<char*>(str_)); +} + +int +process::wait () +{ + if (pid_) + { + int status; + if (-1 == waitpid(pid_, &status, 0)) + { + err_ = errno; assert (err_); + WSREP_ERROR("Waiting for process failed: %s, PID(%ld): %d (%s)", + str_, (long)pid_, err_, strerror (err_)); + } + else + { // command completed, check exit status + if (WIFEXITED (status)) { + err_ = WEXITSTATUS (status); + } + else { // command didn't complete with exit() + WSREP_ERROR("Process was aborted."); + err_ = errno ? errno : ECHILD; + } + + if (err_) { + switch (err_) /* Translate error codes to more meaningful */ + { + case 126: err_ = EACCES; break; /* Permission denied */ + case 127: err_ = ENOENT; break; /* No such file or directory */ + case 143: err_ = EINTR; break; /* Subprocess killed */ + } + WSREP_ERROR("Process completed with error: %s: %d (%s)", + str_, err_, strerror(err_)); + } + + pid_ = 0; + if (io_) fclose (io_); + io_ = NULL; + } + } + else { + assert (NULL == io_); + WSREP_ERROR("Command did not run: %s", str_); + } + + return err_; +} + +thd::thd (my_bool won) : init(), ptr(new THD) +{ + if (ptr) + { + ptr->thread_stack= (char*) &ptr; + ptr->store_globals(); + ptr->variables.option_bits&= ~OPTION_BIN_LOG; // disable binlog + ptr->variables.wsrep_on = won; + ptr->security_ctx->master_access= ~(ulong)0; + lex_start(ptr); + } +} + +thd::~thd () +{ + if (ptr) + { + delete ptr; + my_pthread_setspecific_ptr (THR_THD, 0); + } +} + +} // namespace wsp + +/* Returns INADDR_NONE, INADDR_ANY, INADDR_LOOPBACK or something else */ +unsigned int wsrep_check_ip (const char* const addr, bool *is_ipv6) +{ + unsigned int ret = INADDR_NONE; + struct addrinfo *res, hints; + + memset (&hints, 0, sizeof(hints)); + hints.ai_flags= AI_PASSIVE/*|AI_ADDRCONFIG*/; + hints.ai_socktype= SOCK_STREAM; + hints.ai_family= AF_UNSPEC; + + *is_ipv6= false; + + int gai_ret = getaddrinfo(addr, NULL, &hints, &res); + if (0 == gai_ret) + { + if (AF_INET == res->ai_family) /* IPv4 */ + { + struct sockaddr_in* a= (struct sockaddr_in*)res->ai_addr; + ret= htonl(a->sin_addr.s_addr); + } + else /* IPv6 */ + { + struct sockaddr_in6* a= (struct sockaddr_in6*)res->ai_addr; + if (IN6_IS_ADDR_UNSPECIFIED(&a->sin6_addr)) + ret= INADDR_ANY; + else if (IN6_IS_ADDR_LOOPBACK(&a->sin6_addr)) + ret= INADDR_LOOPBACK; + else + ret= 0xdeadbeef; + + *is_ipv6= true; + } + freeaddrinfo (res); + } + else { + WSREP_ERROR ("getaddrinfo() failed on '%s': %d (%s)", + addr, gai_ret, gai_strerror(gai_ret)); + } + + return ret; +} + +extern char* my_bind_addr_str; + +size_t wsrep_guess_ip (char* buf, size_t buf_len) +{ + size_t ret= 0; + + // Attempt 1: Try to get the IP from bind-address. + if (my_bind_addr_str && my_bind_addr_str[0] != '\0') + { + bool unused; + unsigned int const ip_type= wsrep_check_ip(my_bind_addr_str, &unused); + + if (INADDR_NONE == ip_type) { + WSREP_ERROR("Networking not configured, cannot receive state " + "transfer."); + ret= 0; + goto done; + } else if (INADDR_ANY != ip_type) { + strncpy (buf, my_bind_addr_str, buf_len); + ret= strlen(buf); + goto done; + } + } + + // Attempt 2: mysqld binds to all interfaces, try IP from wsrep_node_address. + if (wsrep_node_address && wsrep_node_address[0] != '\0') { + wsp::Address addr(wsrep_node_address); + if (!addr.is_valid()) + { + WSREP_WARN("Could not parse wsrep_node_address : %s", + wsrep_node_address); + ret= 0; + goto done; + } + + /* Safety check: Buffer length should be sufficiently large. */ + DBUG_ASSERT(buf_len >= addr.get_address_len()); + + memcpy(buf, addr.get_address(), addr.get_address_len()); + ret= addr.get_address_len(); + goto done; + } + + /* + Attempt 3: Try to get the IP from the list of available interfaces. + + getifaddrs() is avaiable at least on Linux since glib 2.3, FreeBSD, + MAC OSX, OpenSolaris, Solaris. + + On platforms which do not support getifaddrs() this function returns + a failure and user is prompted to do manual configuration. + */ +#if HAVE_GETIFADDRS + struct ifaddrs *ifaddr, *ifa; + int family; + + if (getifaddrs(&ifaddr) == 0) + { + for (ifa= ifaddr; ifa != NULL; ifa = ifa->ifa_next) + { + if (!ifa->ifa_addr) + continue; + + family= ifa->ifa_addr->sa_family; + + if ((family != AF_INET) && (family != AF_INET6)) + continue; + + // Skip loopback interfaces (like lo:127.0.0.1) + if (ifa->ifa_flags & IFF_LOOPBACK) + continue; + + /* + Get IP address from the socket address. The resulting address may have + zone ID appended for IPv6 addresses (<address>%<zone-id>). + */ + if (vio_getnameinfo(ifa->ifa_addr, buf, buf_len, NULL, 0, NI_NUMERICHOST)) + continue; + + freeifaddrs(ifaddr); + + ret= strlen(buf); + goto done; + } + freeifaddrs(ifaddr); + } +#endif /* HAVE_GETIFADDRS */ + +done: + WSREP_DEBUG("wsrep_guess_ip() : %s", (ret > 0) ? buf : "????"); + return ret; +} + +/* returns the length of the host part of the address string */ +size_t wsrep_host_len(const char* const addr, size_t const addr_len) +{ + // check for IPv6 notation first + const char* const bracket= ('[' == addr[0] ? strchr(addr, ']') : NULL); + + if (bracket) { // IPv6 + return (bracket - addr + 1); + } + else { // host part ends at ':' or end of string + const char* const colon= strchr(addr, ':'); + return (colon ? colon - addr : addr_len); + } +} diff --git a/sql/wsrep_utils.h b/sql/wsrep_utils.h new file mode 100644 index 00000000000..dee7eb11504 --- /dev/null +++ b/sql/wsrep_utils.h @@ -0,0 +1,426 @@ +/* Copyright (C) 2013-2015 Codership Oy <info@codership.com> + + 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-1301 USA. */ + +#ifndef WSREP_UTILS_H +#define WSREP_UTILS_H + +#include "wsrep_priv.h" +#include "wsrep_mysqld.h" + +unsigned int wsrep_check_ip (const char* const addr, bool *is_ipv6); +size_t wsrep_guess_ip (char* buf, size_t buf_len); + +/* returns the length of the host part of the address string */ +size_t wsrep_host_len(const char* addr, size_t addr_len); + +namespace wsp { + +class Address { +public: + Address() + : m_address_len(0), m_family(UNSPEC), m_port(0), m_valid(false) + { + memset(m_address, 0, sizeof(m_address)); + } + Address(const char *addr_in) + : m_address_len(0), m_family(UNSPEC), m_port(0), m_valid(false) + { + memset(m_address, 0, sizeof(m_address)); + parse_addr(addr_in); + } + bool is_valid() { return m_valid; } + bool is_ipv6() { return (m_family == INET6); } + + const char* get_address() { return m_address; } + size_t get_address_len() { return m_address_len; } + int get_port() { return m_port; } + +private: + enum family { + UNSPEC= 0, + INET, /* IPv4 */ + INET6, /* IPv6 */ + }; + + char m_address[256]; + size_t m_address_len; + family m_family; + int m_port; + bool m_valid; + + void parse_addr(const char *addr_in) { + const char *start; + const char *end; + const char *port; + const char* open_bracket= strchr(const_cast<char *>(addr_in), '['); + const char* close_bracket= strchr(const_cast<char *>(addr_in), ']'); + const char* colon= strchr(const_cast<char *>(addr_in), ':'); + const char* dot= strchr(const_cast<char *>(addr_in), '.'); + + int cc= colon_count(addr_in); + + if (open_bracket != NULL || + dot == NULL || + (colon != NULL && (dot == NULL || colon < dot))) + { + // This could be an IPv6 address or a hostname + if (open_bracket != NULL) { + /* Sanity check: Address with '[' must include ']' */ + if (close_bracket == NULL && + open_bracket < close_bracket) /* Error: malformed address */ + { + m_valid= false; + return; + } + + start= open_bracket + 1; + end= close_bracket; + + /* Check for port */ + port= strchr(close_bracket, ':'); + if ((port != NULL) && parse_port(port + 1)) + { + return; /* Error: invalid port */ + } + m_family= INET6; + } + else + { + switch (cc) { + case 0: + /* Hostname with no port */ + start= addr_in; + end= addr_in + strlen(addr_in); + break; + case 1: + /* Hostname with port (host:port) */ + start= addr_in; + end= colon; + if (parse_port(colon + 1)) + return; /* Error: invalid port */ + break; + default: + /* IPv6 address */ + start= addr_in; + end= addr_in + strlen(addr_in); + m_family= INET6; + break; + } + } + } else { /* IPv4 address or hostname */ + start= addr_in; + if (colon != NULL) { /* Port */ + end= colon; + if (parse_port(colon + 1)) + return; /* Error: invalid port */ + } else { + end= addr_in + strlen(addr_in); + } + } + + size_t len= end - start; + + /* Safety */ + if (len >= sizeof(m_address)) + { + // The supplied address is too large to fit into the internal buffer. + m_valid= false; + return; + } + + memcpy(m_address, start, len); + m_address[len]= '\0'; + m_address_len= ++ len; + m_valid= true; + return; + } + + int colon_count(const char *addr) { + int count= 0, i= 0; + + while(addr[i] != '\0') + { + if (addr[i] == ':') ++count; + ++ i; + } + return count; + } + + bool parse_port(const char *port) { + errno= 0; /* Reset the errno */ + m_port= strtol(port, NULL, 10); + if (errno == EINVAL || errno == ERANGE) + { + m_port= 0; /* Error: invalid port */ + m_valid= false; + return true; + } + return false; + } +}; + +class Config_state +{ +public: + Config_state() : view_(), status_(WSREP_MEMBER_UNDEFINED) + {} + + void set(wsrep_member_status_t status, const wsrep_view_info_t* view) + { + wsrep_notify_status(status, view); + + lock(); + + status_= status; + view_= *view; + member_info_.clear(); + + wsrep_member_info_t memb; + for(int i= 0; i < view->memb_num; i ++) + { + memb= view->members[i]; + member_info_.append_val(memb); + } + + unlock(); + } + + void set(wsrep_member_status_t status) + { + wsrep_notify_status(status, 0); + lock(); + status_= status; + unlock(); + } + + wsrep_view_info_t get_view_info() const + { + return view_; + } + + wsrep_member_status_t get_status() const + { + return status_; + } + + Dynamic_array<wsrep_member_info_t> * get_member_info() + { + return &member_info_; + } + + int lock() + { + return mysql_mutex_lock(&LOCK_wsrep_config_state); + } + + int unlock() + { + return mysql_mutex_unlock(&LOCK_wsrep_config_state); + } + +private: + wsrep_view_info_t view_; + wsrep_member_status_t status_; + Dynamic_array<wsrep_member_info_t> member_info_; +}; + +} /* namespace wsp */ + +extern wsp::Config_state wsrep_config_state; + +namespace wsp { +/* a class to manage env vars array */ +class env +{ +private: + size_t len_; + char** env_; + int errno_; + bool ctor_common(char** e); + void dtor(); + env& operator =(env); +public: + explicit env(char** env); + explicit env(const env&); + ~env(); + int append(const char* var); /* add a new env. var */ + int error() const { return errno_; } + char** operator()() { return env_; } +}; + +/* A small class to run external programs. */ +class process +{ +private: + const char* const str_; + FILE* io_; + int err_; + pid_t pid_; + +public: +/*! @arg type is a pointer to a null-terminated string which must contain + either the letter 'r' for reading or the letter 'w' for writing. + @arg env optional null-terminated vector of environment variables + */ + process (const char* cmd, const char* type, char** env); + ~process (); + + FILE* pipe () { return io_; } + int error() { return err_; } + int wait (); + const char* cmd() { return str_; } +}; + +class thd +{ + class thd_init + { + public: + thd_init() { my_thread_init(); } + ~thd_init() { my_thread_end(); } + } + init; + + thd (const thd&); + thd& operator= (const thd&); + +public: + + thd(my_bool wsrep_on); + ~thd(); + THD* const ptr; +}; + +class string +{ +public: + string() : string_(0) {} + explicit string(size_t s) : string_(static_cast<char*>(malloc(s))) {} + char* operator()() { return string_; } + void set(char* str) { if (string_) free (string_); string_ = str; } + ~string() { set (0); } +private: + char* string_; +}; + +#ifdef REMOVED +class lock +{ + pthread_mutex_t* const mtx_; + +public: + + lock (pthread_mutex_t* mtx) : mtx_(mtx) + { + int err = pthread_mutex_lock (mtx_); + + if (err) + { + WSREP_ERROR("Mutex lock failed: %s", strerror(err)); + abort(); + } + } + + virtual ~lock () + { + int err = pthread_mutex_unlock (mtx_); + + if (err) + { + WSREP_ERROR("Mutex unlock failed: %s", strerror(err)); + abort(); + } + } + + inline void wait (pthread_cond_t* cond) + { + pthread_cond_wait (cond, mtx_); + } + +private: + + lock (const lock&); + lock& operator=(const lock&); + +}; + +class monitor +{ + int mutable refcnt; + pthread_mutex_t mutable mtx; + pthread_cond_t mutable cond; + +public: + + monitor() : refcnt(0) + { + pthread_mutex_init (&mtx, NULL); + pthread_cond_init (&cond, NULL); + } + + ~monitor() + { + pthread_mutex_destroy (&mtx); + pthread_cond_destroy (&cond); + } + + void enter() const + { + lock l(&mtx); + + while (refcnt) + { + l.wait(&cond); + } + refcnt++; + } + + void leave() const + { + lock l(&mtx); + + refcnt--; + if (refcnt == 0) + { + pthread_cond_signal (&cond); + } + } + +private: + + monitor (const monitor&); + monitor& operator= (const monitor&); +}; + +class critical +{ + const monitor& mon; + +public: + + critical(const monitor& m) : mon(m) { mon.enter(); } + + ~critical() { mon.leave(); } + +private: + + critical (const critical&); + critical& operator= (const critical&); +}; +#endif + +} // namespace wsrep + +#endif /* WSREP_UTILS_H */ diff --git a/sql/wsrep_var.cc b/sql/wsrep_var.cc new file mode 100644 index 00000000000..3959746156a --- /dev/null +++ b/sql/wsrep_var.cc @@ -0,0 +1,734 @@ +/* Copyright 2008-2015 Codership Oy <http://www.codership.com> + + 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 02111-1301 USA */ + +#include "wsrep_var.h" + +#include <mysqld.h> +#include <sql_class.h> +#include <set_var.h> +#include <sql_acl.h> +#include "wsrep_priv.h" +#include "wsrep_thd.h" +#include "wsrep_xid.h" +#include <my_dir.h> +#include <cstdio> +#include <cstdlib> + +const char* wsrep_provider = 0; +const char* wsrep_provider_options = 0; +const char* wsrep_cluster_address = 0; +const char* wsrep_cluster_name = 0; +const char* wsrep_node_name = 0; +const char* wsrep_node_address = 0; +const char* wsrep_node_incoming_address = 0; +const char* wsrep_start_position = 0; +ulong wsrep_reject_queries; + +static long wsrep_prev_slave_threads = wsrep_slave_threads; + +int wsrep_init_vars() +{ + wsrep_provider = my_strdup(WSREP_NONE, MYF(MY_WME)); + wsrep_provider_options= my_strdup("", MYF(MY_WME)); + wsrep_cluster_address = my_strdup("", MYF(MY_WME)); + wsrep_cluster_name = my_strdup(WSREP_CLUSTER_NAME, MYF(MY_WME)); + wsrep_node_name = my_strdup("", MYF(MY_WME)); + wsrep_node_address = my_strdup("", MYF(MY_WME)); + wsrep_node_incoming_address= my_strdup(WSREP_NODE_INCOMING_AUTO, MYF(MY_WME)); + wsrep_start_position = my_strdup(WSREP_START_POSITION_ZERO, MYF(MY_WME)); + + global_system_variables.binlog_format=BINLOG_FORMAT_ROW; + return 0; +} + +extern ulong innodb_lock_schedule_algorithm; + +bool wsrep_on_update (sys_var *self, THD* thd, enum_var_type var_type) +{ + if (var_type == OPT_GLOBAL) { + // FIXME: this variable probably should be changed only per session + thd->variables.wsrep_on = global_system_variables.wsrep_on; + } + + return false; +} + +bool wsrep_on_check(sys_var *self, THD* thd, set_var* var) +{ + bool new_wsrep_on= (bool)var->save_result.ulonglong_value; + + if (check_has_super(self, thd, var)) + return true; + + if (new_wsrep_on && innodb_lock_schedule_algorithm != 0) { + my_message(ER_WRONG_ARGUMENTS, " WSREP (galera) can't be enabled " + "if innodb_lock_schedule_algorithm=VATS. Please configure" + " innodb_lock_schedule_algorithm=FCFS and restart.", MYF(0)); + return true; + } + return false; +} + +bool wsrep_causal_reads_update (sys_var *self, THD* thd, enum_var_type var_type) +{ + // global setting should not affect session setting. + // if (var_type == OPT_GLOBAL) { + // thd->variables.wsrep_causal_reads = global_system_variables.wsrep_causal_reads; + // } + if (thd->variables.wsrep_causal_reads) { + thd->variables.wsrep_sync_wait |= WSREP_SYNC_WAIT_BEFORE_READ; + } else { + thd->variables.wsrep_sync_wait &= ~WSREP_SYNC_WAIT_BEFORE_READ; + } + + // update global settings too. + if (global_system_variables.wsrep_causal_reads) { + global_system_variables.wsrep_sync_wait |= WSREP_SYNC_WAIT_BEFORE_READ; + } else { + global_system_variables.wsrep_sync_wait &= ~WSREP_SYNC_WAIT_BEFORE_READ; + } + + return false; +} + +bool wsrep_sync_wait_update (sys_var* self, THD* thd, enum_var_type var_type) +{ + // global setting should not affect session setting. + // if (var_type == OPT_GLOBAL) { + // thd->variables.wsrep_sync_wait = global_system_variables.wsrep_sync_wait; + // } + thd->variables.wsrep_causal_reads = thd->variables.wsrep_sync_wait & + WSREP_SYNC_WAIT_BEFORE_READ; + + // update global settings too + global_system_variables.wsrep_causal_reads = global_system_variables.wsrep_sync_wait & + WSREP_SYNC_WAIT_BEFORE_READ; + + return false; +} + +static int wsrep_start_position_verify (const char* start_str) +{ + size_t start_len; + wsrep_uuid_t uuid; + ssize_t uuid_len; + + start_len = strlen (start_str); + if (start_len < 34) + return 1; + + uuid_len = wsrep_uuid_scan (start_str, start_len, &uuid); + if (uuid_len < 0 || (start_len - uuid_len) < 2) + return 1; + + if (start_str[uuid_len] != ':') // separator should follow UUID + return 1; + + char* endptr; + wsrep_seqno_t const seqno __attribute__((unused)) // to avoid GCC warnings + (strtoll(&start_str[uuid_len + 1], &endptr, 10)); + + if (*endptr == '\0') return 0; // remaining string was seqno + + return 1; +} + +bool wsrep_start_position_check (sys_var *self, THD* thd, set_var* var) +{ + char start_pos_buf[FN_REFLEN]; + + if ((! var->save_result.string_value.str) || + (var->save_result.string_value.length > (FN_REFLEN - 1))) // safety + goto err; + + memcpy(start_pos_buf, var->save_result.string_value.str, + var->save_result.string_value.length); + start_pos_buf[var->save_result.string_value.length]= 0; + + if (!wsrep_start_position_verify(start_pos_buf)) return 0; + +err: + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str, + var->save_result.string_value.str ? + var->save_result.string_value.str : "NULL"); + return 1; +} + +static +void wsrep_set_local_position(const char* const value, bool const sst) +{ + size_t const value_len = strlen(value); + wsrep_uuid_t uuid; + size_t const uuid_len = wsrep_uuid_scan(value, value_len, &uuid); + wsrep_seqno_t const seqno = strtoll(value + uuid_len + 1, NULL, 10); + + if (sst) { + wsrep_sst_received (wsrep, uuid, seqno, NULL, 0); + } else { + // initialization + local_uuid = uuid; + local_seqno = seqno; + } +} + +bool wsrep_start_position_update (sys_var *self, THD* thd, enum_var_type type) +{ + WSREP_INFO ("wsrep_start_position var submitted: '%s'", + wsrep_start_position); + // since this value passed wsrep_start_position_check, don't check anything + // here + wsrep_set_local_position (wsrep_start_position, true); + return 0; +} + +void wsrep_start_position_init (const char* val) +{ + if (NULL == val || wsrep_start_position_verify (val)) + { + WSREP_ERROR("Bad initial value for wsrep_start_position: %s", + (val ? val : "")); + return; + } + + wsrep_set_local_position (val, false); +} + +static int get_provider_option_value(const char* opts, + const char* opt_name, + ulong* opt_value) +{ + int ret= 1; + ulong opt_value_tmp; + char *opt_value_str, *s, *opts_copy= my_strdup(opts, MYF(MY_WME)); + + if ((opt_value_str= strstr(opts_copy, opt_name)) == NULL) + goto end; + opt_value_str= strtok_r(opt_value_str, "=", &s); + if (opt_value_str == NULL) goto end; + opt_value_str= strtok_r(NULL, ";", &s); + if (opt_value_str == NULL) goto end; + + opt_value_tmp= strtoul(opt_value_str, NULL, 10); + if (errno == ERANGE) goto end; + + *opt_value= opt_value_tmp; + ret= 0; + +end: + my_free(opts_copy); + return ret; +} + +static bool refresh_provider_options() +{ + DBUG_ASSERT(wsrep); + + WSREP_DEBUG("refresh_provider_options: %s", + (wsrep_provider_options) ? wsrep_provider_options : "null"); + char* opts= wsrep->options_get(wsrep); + if (opts) + { + wsrep_provider_options_init(opts); + get_provider_option_value(wsrep_provider_options, + (char*)"repl.max_ws_size", + &wsrep_max_ws_size); + free(opts); + } + else + { + WSREP_ERROR("Failed to get provider options"); + return true; + } + return false; +} + +static int wsrep_provider_verify (const char* provider_str) +{ + MY_STAT f_stat; + char path[FN_REFLEN]; + + if (!provider_str || strlen(provider_str)== 0) + return 1; + + if (!strcmp(provider_str, WSREP_NONE)) + return 0; + + if (!unpack_filename(path, provider_str)) + return 1; + + /* check that provider file exists */ + memset(&f_stat, 0, sizeof(MY_STAT)); + if (!my_stat(path, &f_stat, MYF(0))) + { + return 1; + } + return 0; +} + +bool wsrep_provider_check (sys_var *self, THD* thd, set_var* var) +{ + char wsrep_provider_buf[FN_REFLEN]; + + if ((! var->save_result.string_value.str) || + (var->save_result.string_value.length > (FN_REFLEN - 1))) // safety + goto err; + + memcpy(wsrep_provider_buf, var->save_result.string_value.str, + var->save_result.string_value.length); + wsrep_provider_buf[var->save_result.string_value.length]= 0; + + if (!wsrep_provider_verify(wsrep_provider_buf)) return 0; + +err: + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str, + var->save_result.string_value.str ? + var->save_result.string_value.str : "NULL"); + return 1; +} + +bool wsrep_provider_update (sys_var *self, THD* thd, enum_var_type type) +{ + bool rcode= false; + + bool wsrep_on_saved= thd->variables.wsrep_on; + thd->variables.wsrep_on= false; + + WSREP_DEBUG("wsrep_provider_update: %s", wsrep_provider); + + /* stop replication is heavy operation, and includes closing all client + connections. Closing clients may need to get LOCK_global_system_variables + at least in MariaDB. + + Note: releasing LOCK_global_system_variables may cause race condition, if + there can be several concurrent clients changing wsrep_provider + */ + mysql_mutex_unlock(&LOCK_global_system_variables); + wsrep_stop_replication(thd); + mysql_mutex_lock(&LOCK_global_system_variables); + + if (wsrep_inited == 1) + wsrep_deinit(false); + + char* tmp= strdup(wsrep_provider); // wsrep_init() rewrites provider + //when fails + + if (wsrep_init()) + { + my_error(ER_CANT_OPEN_LIBRARY, MYF(0), tmp); + rcode = true; + } + free(tmp); + + // we sure don't want to use old address with new provider + wsrep_cluster_address_init(NULL); + wsrep_provider_options_init(NULL); + + thd->variables.wsrep_on= wsrep_on_saved; + + refresh_provider_options(); + + return rcode; +} + +void wsrep_provider_init (const char* value) +{ + WSREP_DEBUG("wsrep_provider_init: %s -> %s", + (wsrep_provider) ? wsrep_provider : "null", + (value) ? value : "null"); + if (NULL == value || wsrep_provider_verify (value)) + { + WSREP_ERROR("Bad initial value for wsrep_provider: %s", + (value ? value : "")); + return; + } + + if (wsrep_provider) my_free((void *)wsrep_provider); + wsrep_provider = my_strdup(value, MYF(0)); +} + +bool wsrep_provider_options_check(sys_var *self, THD* thd, set_var* var) +{ + if (wsrep == NULL) + { + my_message(ER_WRONG_ARGUMENTS, "WSREP (galera) not started", MYF(0)); + return true; + } + return false; +} + +bool wsrep_provider_options_update(sys_var *self, THD* thd, enum_var_type type) +{ + DBUG_ASSERT(wsrep); + wsrep_status_t ret= wsrep->options_set(wsrep, wsrep_provider_options); + if (ret != WSREP_OK) + { + WSREP_ERROR("Set options returned %d", ret); + refresh_provider_options(); + return true; + } + return refresh_provider_options(); +} + +void wsrep_provider_options_init(const char* value) +{ + if (wsrep_provider_options && wsrep_provider_options != value) + my_free((void *)wsrep_provider_options); + wsrep_provider_options = (value) ? my_strdup(value, MYF(0)) : NULL; +} + +bool wsrep_reject_queries_update(sys_var *self, THD* thd, enum_var_type type) +{ + switch (wsrep_reject_queries) { + case WSREP_REJECT_NONE: + WSREP_INFO("Allowing client queries due to manual setting"); + break; + case WSREP_REJECT_ALL: + WSREP_INFO("Rejecting client queries due to manual setting"); + break; + case WSREP_REJECT_ALL_KILL: + /* close all client connections, but this one */ + wsrep_close_client_connections(FALSE, thd); + WSREP_INFO("Rejecting client queries and killing connections due to manual setting"); + break; + default: + WSREP_INFO("Unknown value for wsrep_reject_queries: %lu", + wsrep_reject_queries); + return true; + } + return false; +} + +static int wsrep_cluster_address_verify (const char* cluster_address_str) +{ + /* There is no predefined address format, it depends on provider. */ + return 0; +} + +bool wsrep_cluster_address_check (sys_var *self, THD* thd, set_var* var) +{ + char addr_buf[FN_REFLEN]; + + if ((! var->save_result.string_value.str) || + (var->save_result.string_value.length > (FN_REFLEN - 1))) // safety + goto err; + + memcpy(addr_buf, var->save_result.string_value.str, + var->save_result.string_value.length); + addr_buf[var->save_result.string_value.length]= 0; + + if (!wsrep_cluster_address_verify(addr_buf)) return 0; + + err: + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str, + var->save_result.string_value.str ? + var->save_result.string_value.str : "NULL"); + return 1; +} + +bool wsrep_cluster_address_update (sys_var *self, THD* thd, enum_var_type type) +{ + bool wsrep_on_saved; + + /* Do not proceed if wsrep provider is not loaded. */ + if (!wsrep) + { + WSREP_INFO("wsrep provider is not loaded, can't re(start) replication."); + return false; + } + + wsrep_on_saved= thd->variables.wsrep_on; + thd->variables.wsrep_on= false; + + /* stop replication is heavy operation, and includes closing all client + connections. Closing clients may need to get LOCK_global_system_variables + at least in MariaDB. + + Note: releasing LOCK_global_system_variables may cause race condition, if + there can be several concurrent clients changing wsrep_provider + */ + mysql_mutex_unlock(&LOCK_global_system_variables); + wsrep_stop_replication(thd); + + /* + Unlock and lock LOCK_wsrep_slave_threads to maintain lock order & avoid + any potential deadlock. + */ + mysql_mutex_unlock(&LOCK_wsrep_slave_threads); + mysql_mutex_lock(&LOCK_global_system_variables); + mysql_mutex_lock(&LOCK_wsrep_slave_threads); + + if (wsrep_start_replication()) + { + wsrep_create_rollbacker(); + wsrep_create_appliers(wsrep_slave_threads); + } + + thd->variables.wsrep_on= wsrep_on_saved; + + return false; +} + +void wsrep_cluster_address_init (const char* value) +{ + WSREP_DEBUG("wsrep_cluster_address_init: %s -> %s", + (wsrep_cluster_address) ? wsrep_cluster_address : "null", + (value) ? value : "null"); + + if (wsrep_cluster_address) my_free ((void*)wsrep_cluster_address); + wsrep_cluster_address = (value) ? my_strdup(value, MYF(0)) : NULL; +} + +/* wsrep_cluster_name cannot be NULL or an empty string. */ +bool wsrep_cluster_name_check (sys_var *self, THD* thd, set_var* var) +{ + if (!var->save_result.string_value.str || + (var->save_result.string_value.length == 0)) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str, + (var->save_result.string_value.str ? + var->save_result.string_value.str : "NULL")); + return 1; + } + return 0; +} + +bool wsrep_cluster_name_update (sys_var *self, THD* thd, enum_var_type type) +{ + return 0; +} + +bool wsrep_node_name_check (sys_var *self, THD* thd, set_var* var) +{ + // TODO: for now 'allow' 0-length string to be valid (default) + if (!var->save_result.string_value.str) + { + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str, + (var->save_result.string_value.str ? + var->save_result.string_value.str : "NULL")); + return 1; + } + return 0; +} + +bool wsrep_node_name_update (sys_var *self, THD* thd, enum_var_type type) +{ + return 0; +} + +// TODO: do something more elaborate, like checking connectivity +bool wsrep_node_address_check (sys_var *self, THD* thd, set_var* var) +{ + char addr_buf[FN_REFLEN]; + + if ((! var->save_result.string_value.str) || + (var->save_result.string_value.length > (FN_REFLEN - 1))) // safety + goto err; + + memcpy(addr_buf, var->save_result.string_value.str, + var->save_result.string_value.length); + addr_buf[var->save_result.string_value.length]= 0; + + // TODO: for now 'allow' 0-length string to be valid (default) + return 0; + +err: + my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str, + var->save_result.string_value.str ? + var->save_result.string_value.str : "NULL"); + return 1; +} + +bool wsrep_node_address_update (sys_var *self, THD* thd, enum_var_type type) +{ + return 0; +} + +void wsrep_node_address_init (const char* value) +{ + if (wsrep_node_address && strcmp(wsrep_node_address, value)) + my_free ((void*)wsrep_node_address); + + wsrep_node_address = (value) ? my_strdup(value, MYF(0)) : NULL; +} + +static void wsrep_slave_count_change_update () +{ + wsrep_slave_count_change = (wsrep_slave_threads - wsrep_prev_slave_threads); + WSREP_DEBUG("Change on slave threads: New %lu old %lu difference %d", + wsrep_slave_threads, wsrep_prev_slave_threads, wsrep_slave_count_change); + wsrep_prev_slave_threads = wsrep_slave_threads; +} + +bool wsrep_slave_threads_update (sys_var *self, THD* thd, enum_var_type type) +{ + wsrep_slave_count_change_update(); + if (wsrep_slave_count_change > 0) + { + wsrep_create_appliers(wsrep_slave_count_change); + wsrep_slave_count_change = 0; + } + return false; +} + +bool wsrep_desync_check (sys_var *self, THD* thd, set_var* var) +{ + if (wsrep == NULL) + { + my_message(ER_WRONG_ARGUMENTS, "WSREP (galera) not started", MYF(0)); + return true; + } + + if (thd->global_read_lock.is_acquired()) + { + my_message (ER_CANNOT_USER, "Global read lock acquired. Can't set 'wsrep_desync'", MYF(0)); + return true; + } + + bool new_wsrep_desync= (bool) var->save_result.ulonglong_value; + if (wsrep_desync == new_wsrep_desync) { + if (new_wsrep_desync) { + push_warning (thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_VALUE_FOR_VAR, + "'wsrep_desync' is already ON."); + } else { + push_warning (thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_VALUE_FOR_VAR, + "'wsrep_desync' is already OFF."); + } + return false; + } + wsrep_status_t ret(WSREP_WARNING); + if (new_wsrep_desync) { + ret = wsrep->desync (wsrep); + if (ret != WSREP_OK) { + WSREP_WARN ("SET desync failed %d for schema: %s, query: %s", ret, + (thd->db ? thd->db : "(null)"), + thd->query()); + my_error (ER_CANNOT_USER, MYF(0), "'desync'", thd->query()); + return true; + } + } else { + ret = wsrep->resync (wsrep); + if (ret != WSREP_OK) { + WSREP_WARN ("SET resync failed %d for schema: %s, query: %s", ret, + (thd->db ? thd->db : "(null)"), + thd->query()); + my_error (ER_CANNOT_USER, MYF(0), "'resync'", thd->query()); + return true; + } + } + return false; +} + +bool wsrep_desync_update (sys_var *self, THD* thd, enum_var_type type) +{ + DBUG_ASSERT(wsrep); + return false; +} + +bool wsrep_max_ws_size_check(sys_var *self, THD* thd, set_var* var) +{ + if (wsrep == NULL) + { + my_message(ER_WRONG_ARGUMENTS, "WSREP (galera) not started", MYF(0)); + return true; + } + return false; +} + +bool wsrep_max_ws_size_update (sys_var *self, THD *thd, enum_var_type) +{ + DBUG_ASSERT(wsrep); + + char max_ws_size_opt[128]; + my_snprintf(max_ws_size_opt, sizeof(max_ws_size_opt), + "repl.max_ws_size=%d", wsrep_max_ws_size); + wsrep_status_t ret= wsrep->options_set(wsrep, max_ws_size_opt); + if (ret != WSREP_OK) + { + WSREP_ERROR("Set options returned %d", ret); + refresh_provider_options(); + return true; + } + return refresh_provider_options(); +} + +static SHOW_VAR wsrep_status_vars[]= +{ + {"connected", (char*) &wsrep_connected, SHOW_BOOL}, + {"ready", (char*) &wsrep_ready, SHOW_BOOL}, + {"cluster_state_uuid",(char*) &wsrep_cluster_state_uuid,SHOW_CHAR_PTR}, + {"cluster_conf_id", (char*) &wsrep_cluster_conf_id, SHOW_LONGLONG}, + {"cluster_status", (char*) &wsrep_cluster_status, SHOW_CHAR_PTR}, + {"cluster_size", (char*) &wsrep_cluster_size, SHOW_LONG_NOFLUSH}, + {"local_index", (char*) &wsrep_local_index, SHOW_LONG_NOFLUSH}, + {"local_bf_aborts", (char*) &wsrep_show_bf_aborts, SHOW_SIMPLE_FUNC}, + {"provider_name", (char*) &wsrep_provider_name, SHOW_CHAR_PTR}, + {"provider_version", (char*) &wsrep_provider_version, SHOW_CHAR_PTR}, + {"provider_vendor", (char*) &wsrep_provider_vendor, SHOW_CHAR_PTR}, + {"thread_count", (char*) &wsrep_running_threads, SHOW_LONG_NOFLUSH} +}; + +static int show_var_cmp(const void *var1, const void *var2) +{ + return strcasecmp(((SHOW_VAR*)var1)->name, ((SHOW_VAR*)var2)->name); +} + +int wsrep_show_status (THD *thd, SHOW_VAR *var, char *buff, + enum enum_var_type scope) +{ + uint i, maxi= SHOW_VAR_FUNC_BUFF_SIZE / sizeof(*var) - 1; + SHOW_VAR *v= (SHOW_VAR *)buff; + + var->type= SHOW_ARRAY; + var->value= buff; + + for (i=0; i < array_elements(wsrep_status_vars); i++) + *v++= wsrep_status_vars[i]; + + DBUG_ASSERT(i < maxi); + + if (wsrep != NULL) + { + wsrep_stats_var* stats= wsrep->stats_get(wsrep); + for (wsrep_stats_var *sv= stats; + i < maxi && sv && sv->name; i++, + sv++, v++) + { + v->name = thd->strdup(sv->name); + switch (sv->type) { + case WSREP_VAR_INT64: + v->value = (char*)thd->memdup(&sv->value._int64, sizeof(longlong)); + v->type = SHOW_LONGLONG; + break; + case WSREP_VAR_STRING: + v->value = thd->strdup(sv->value._string); + v->type = SHOW_CHAR; + break; + case WSREP_VAR_DOUBLE: + v->value = (char*)thd->memdup(&sv->value._double, sizeof(double)); + v->type = SHOW_DOUBLE; + break; + } + } + wsrep->stats_free(wsrep, stats); + } + + my_qsort(buff, i, sizeof(*v), show_var_cmp); + + v->name= 0; // terminator + return 0; +} + diff --git a/sql/wsrep_var.h b/sql/wsrep_var.h new file mode 100644 index 00000000000..53952173c83 --- /dev/null +++ b/sql/wsrep_var.h @@ -0,0 +1,107 @@ +/* Copyright (C) 2013 Codership Oy <info@codership.com> + + 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-1301 USA. */ + +#include <my_config.h> + +#ifndef WSREP_VAR_H +#define WSREP_VAR_H + +#ifdef WITH_WSREP + +#define WSREP_CLUSTER_NAME "my_wsrep_cluster" +#define WSREP_NODE_INCOMING_AUTO "AUTO" +#define WSREP_START_POSITION_ZERO "00000000-0000-0000-0000-000000000000:-1" + +// MySQL variables funcs + +#include "sql_priv.h" +#include <sql_plugin.h> +#include <mysql/plugin.h> + +class sys_var; +class set_var; +class THD; + +int wsrep_init_vars(); + +#define CHECK_ARGS (sys_var *self, THD* thd, set_var *var) +#define UPDATE_ARGS (sys_var *self, THD* thd, enum_var_type type) +#define DEFAULT_ARGS (THD* thd, enum_var_type var_type) +#define INIT_ARGS (const char* opt) + +extern bool wsrep_causal_reads_update UPDATE_ARGS; +extern bool wsrep_on_check CHECK_ARGS; +extern bool wsrep_on_update UPDATE_ARGS; +extern bool wsrep_sync_wait_update UPDATE_ARGS; +extern bool wsrep_start_position_check CHECK_ARGS; +extern bool wsrep_start_position_update UPDATE_ARGS; +extern void wsrep_start_position_init INIT_ARGS; + +extern bool wsrep_provider_check CHECK_ARGS; +extern bool wsrep_provider_update UPDATE_ARGS; +extern void wsrep_provider_init INIT_ARGS; + +extern bool wsrep_provider_options_check CHECK_ARGS; +extern bool wsrep_provider_options_update UPDATE_ARGS; +extern void wsrep_provider_options_init INIT_ARGS; + +extern bool wsrep_cluster_address_check CHECK_ARGS; +extern bool wsrep_cluster_address_update UPDATE_ARGS; +extern void wsrep_cluster_address_init INIT_ARGS; + +extern bool wsrep_cluster_name_check CHECK_ARGS; +extern bool wsrep_cluster_name_update UPDATE_ARGS; + +extern bool wsrep_node_name_check CHECK_ARGS; +extern bool wsrep_node_name_update UPDATE_ARGS; + +extern bool wsrep_node_address_check CHECK_ARGS; +extern bool wsrep_node_address_update UPDATE_ARGS; +extern void wsrep_node_address_init INIT_ARGS; + +extern bool wsrep_sst_method_check CHECK_ARGS; +extern bool wsrep_sst_method_update UPDATE_ARGS; +extern void wsrep_sst_method_init INIT_ARGS; + +extern bool wsrep_sst_receive_address_check CHECK_ARGS; +extern bool wsrep_sst_receive_address_update UPDATE_ARGS; + +extern bool wsrep_sst_auth_check CHECK_ARGS; +extern bool wsrep_sst_auth_update UPDATE_ARGS; +extern void wsrep_sst_auth_init INIT_ARGS; + +extern bool wsrep_sst_donor_check CHECK_ARGS; +extern bool wsrep_sst_donor_update UPDATE_ARGS; + +extern bool wsrep_slave_threads_check CHECK_ARGS; +extern bool wsrep_slave_threads_update UPDATE_ARGS; + +extern bool wsrep_desync_check CHECK_ARGS; +extern bool wsrep_desync_update UPDATE_ARGS; + +extern bool wsrep_max_ws_size_check CHECK_ARGS; +extern bool wsrep_max_ws_size_update UPDATE_ARGS; +extern bool wsrep_reject_queries_update UPDATE_ARGS; + +#else /* WITH_WSREP */ + +#define WSREP_NONE +#define wsrep_provider_init(X) +#define wsrep_init_vars() (0) +#define wsrep_start_position_init(X) +#define wsrep_sst_auth_init(X) + +#endif /* WITH_WSREP */ +#endif /* WSREP_VAR_H */ diff --git a/sql/wsrep_xid.cc b/sql/wsrep_xid.cc new file mode 100644 index 00000000000..132956e88b3 --- /dev/null +++ b/sql/wsrep_xid.cc @@ -0,0 +1,146 @@ +/* Copyright 2015 Codership Oy <http://www.codership.com> + + 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 02111-1301 USA + */ + +//! @file some utility functions and classes not directly related to replication + +#include "wsrep_xid.h" +#include "sql_class.h" +#include "wsrep_mysqld.h" // for logging macros + +/* + * WSREPXid + */ + +#define WSREP_XID_PREFIX "WSREPXid" +#define WSREP_XID_PREFIX_LEN MYSQL_XID_PREFIX_LEN +#define WSREP_XID_UUID_OFFSET 8 +#define WSREP_XID_SEQNO_OFFSET (WSREP_XID_UUID_OFFSET + sizeof(wsrep_uuid_t)) +#define WSREP_XID_GTRID_LEN (WSREP_XID_SEQNO_OFFSET + sizeof(wsrep_seqno_t)) + +void wsrep_xid_init(XID* xid, const wsrep_uuid_t& uuid, wsrep_seqno_t seqno) +{ + xid->formatID= 1; + xid->gtrid_length= WSREP_XID_GTRID_LEN; + xid->bqual_length= 0; + memset(xid->data, 0, sizeof(xid->data)); + memcpy(xid->data, WSREP_XID_PREFIX, WSREP_XID_PREFIX_LEN); + memcpy(xid->data + WSREP_XID_UUID_OFFSET, &uuid, sizeof(wsrep_uuid_t)); + memcpy(xid->data + WSREP_XID_SEQNO_OFFSET, &seqno, sizeof(wsrep_seqno_t)); +} + +int wsrep_is_wsrep_xid(const XID* xid) +{ + return (xid->formatID == 1 && + xid->gtrid_length == WSREP_XID_GTRID_LEN && + xid->bqual_length == 0 && + !memcmp(xid->data, WSREP_XID_PREFIX, WSREP_XID_PREFIX_LEN)); +} + +const wsrep_uuid_t* wsrep_xid_uuid(const XID& xid) +{ + if (wsrep_is_wsrep_xid(&xid)) + return reinterpret_cast<const wsrep_uuid_t*>(xid.data + + WSREP_XID_UUID_OFFSET); + else + return &WSREP_UUID_UNDEFINED; +} + +wsrep_seqno_t wsrep_xid_seqno(const XID& xid) +{ + if (wsrep_is_wsrep_xid(&xid)) + { + wsrep_seqno_t seqno; + memcpy(&seqno, xid.data + WSREP_XID_SEQNO_OFFSET, sizeof(wsrep_seqno_t)); + return seqno; + } + else + { + return WSREP_SEQNO_UNDEFINED; + } +} + +static my_bool set_SE_checkpoint(THD* unused, plugin_ref plugin, void* arg) +{ + XID* xid= static_cast<XID*>(arg); + handlerton* hton= plugin_data(plugin, handlerton *); + + if (hton->set_checkpoint) + { + const wsrep_uuid_t* uuid(wsrep_xid_uuid(*xid)); + char uuid_str[40] = {0, }; + wsrep_uuid_print(uuid, uuid_str, sizeof(uuid_str)); + WSREP_DEBUG("Set WSREPXid for InnoDB: %s:%lld", + uuid_str, (long long)wsrep_xid_seqno(*xid)); + hton->set_checkpoint(hton, xid); + } + return FALSE; +} + +void wsrep_set_SE_checkpoint(XID& xid) +{ + plugin_foreach(NULL, set_SE_checkpoint, MYSQL_STORAGE_ENGINE_PLUGIN, &xid); +} + +void wsrep_set_SE_checkpoint(const wsrep_uuid_t& uuid, wsrep_seqno_t seqno) +{ + XID xid; + wsrep_xid_init(&xid, uuid, seqno); + wsrep_set_SE_checkpoint(xid); +} + +static my_bool get_SE_checkpoint(THD* unused, plugin_ref plugin, void* arg) +{ + XID* xid= reinterpret_cast<XID*>(arg); + handlerton* hton= plugin_data(plugin, handlerton *); + + if (hton->get_checkpoint) + { + hton->get_checkpoint(hton, xid); + const wsrep_uuid_t* uuid(wsrep_xid_uuid(*xid)); + char uuid_str[40] = {0, }; + wsrep_uuid_print(uuid, uuid_str, sizeof(uuid_str)); + WSREP_DEBUG("Read WSREPXid from InnoDB: %s:%lld", + uuid_str, (long long)wsrep_xid_seqno(*xid)); + } + return FALSE; +} + +void wsrep_get_SE_checkpoint(XID& xid) +{ + plugin_foreach(NULL, get_SE_checkpoint, MYSQL_STORAGE_ENGINE_PLUGIN, &xid); +} + +void wsrep_get_SE_checkpoint(wsrep_uuid_t& uuid, wsrep_seqno_t& seqno) +{ + uuid= WSREP_UUID_UNDEFINED; + seqno= WSREP_SEQNO_UNDEFINED; + + XID xid; + xid.null(); + + wsrep_get_SE_checkpoint(xid); + + if (xid.is_null()) return; + + if (!wsrep_is_wsrep_xid(&xid)) + { + WSREP_WARN("Read non-wsrep XID from storage engines."); + return; + } + + uuid= *wsrep_xid_uuid(xid); + seqno= wsrep_xid_seqno(xid); +} diff --git a/sql/wsrep_xid.h b/sql/wsrep_xid.h new file mode 100644 index 00000000000..c3cad0231d7 --- /dev/null +++ b/sql/wsrep_xid.h @@ -0,0 +1,36 @@ +/* Copyright (C) 2015 Codership Oy <info@codership.com> + + 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-1301 USA. */ + +#ifndef WSREP_XID_H +#define WSREP_XID_H + +#include <my_config.h> + +#ifdef WITH_WSREP + +#include "../wsrep/wsrep_api.h" +#include "handler.h" // XID typedef + +void wsrep_xid_init(xid_t*, const wsrep_uuid_t&, wsrep_seqno_t); +const wsrep_uuid_t* wsrep_xid_uuid(const XID&); +wsrep_seqno_t wsrep_xid_seqno(const XID&); + +//void wsrep_get_SE_checkpoint(XID&); /* uncomment if needed */ +void wsrep_get_SE_checkpoint(wsrep_uuid_t&, wsrep_seqno_t&); +//void wsrep_set_SE_checkpoint(XID&); /* uncomment if needed */ +void wsrep_set_SE_checkpoint(const wsrep_uuid_t&, wsrep_seqno_t); + +#endif /* WITH_WSREP */ +#endif /* WSREP_UTILS_H */ |